From bc662ba705041e6573dd356e00d6418d40a405b9 Mon Sep 17 00:00:00 2001 From: Fizaa Luthra Date: Fri, 17 Apr 2026 09:35:01 -0400 Subject: [PATCH 1/2] yugabyte-db squash --- .clang-format | 115 + CMakeLists.txt | 233 + contrib/adminpack/Makefile | 2 + contrib/adminpack/adminpack.c | 8 + contrib/adminpack/yb_schedule | 5 + contrib/amcheck/verify_nbtree.c | 3 + contrib/auto_explain/auto_explain.c | 52 + contrib/btree_gin/btree_gin.c | 1 + contrib/cube/expected/yb.port.cube.out | 1907 + contrib/cube/sql/yb.port.cube.sql | 422 + contrib/cube/yb_schedule | 6 + .../expected/yb.port.earthdistance.out | 1100 + .../sql/yb.port.earthdistance.sql | 364 + contrib/earthdistance/yb_schedule | 3 + contrib/file_fdw/Makefile | 6 +- .../file_fdw/expected/yb.port.file_fdw.out | 496 + contrib/file_fdw/file_fdw.c | 11 +- contrib/file_fdw/sql/yb.port.file_fdw.sql | 272 + contrib/file_fdw/yb_schedule | 3 + contrib/fuzzystrmatch/dmetaphone.c | 3 + contrib/fuzzystrmatch/fuzzystrmatch.c | 15 + contrib/fuzzystrmatch/yb_schedule | 5 + contrib/hstore/Makefile | 7 +- contrib/hstore/expected/yb.port.hstore.out | 1518 + contrib/hstore/hstore--1.5--1.5-yb-1.0.sql | 23 + .../hstore/hstore--1.5-yb-1.0--1.8-yb-1.0.sql | 52 + contrib/hstore/hstore.control | 2 +- contrib/hstore/sql/yb.port.hstore.sql | 366 + contrib/hstore/yb_schedule | 3 + contrib/intarray/expected/yb.port._int.out | 482 + contrib/intarray/sql/yb.port._int.sql | 103 + contrib/intarray/yb_schedule | 3 + contrib/isn/expected/yb.port.isn.out | 231 + contrib/isn/isn.c | 1 + contrib/isn/sql/yb.port.isn.sql | 113 + contrib/isn/yb_schedule | 3 + contrib/lo/lo.c | 3 +- contrib/passwordcheck/.gitignore | 4 +- contrib/passwordcheck/Makefile | 9 +- contrib/passwordcheck/README | 62 + .../expected/passwordcheck_extra.out | 84 + .../expected/yb.port.passwordcheck.out | 24 + .../expected/yb.port.passwordcheck_extra.out | 83 + contrib/passwordcheck/passwordcheck.c | 151 +- contrib/passwordcheck/passwordcheck_extra.c | 299 + .../passwordcheck/sql/passwordcheck_extra.sql | 75 + .../sql/yb.port.passwordcheck.sql | 27 + .../sql/yb.port.passwordcheck_extra.sql | 73 + contrib/passwordcheck/yb_schedule | 4 + contrib/pg_stat_statements/Makefile | 12 +- .../expected/yb.orig.backfill_queryid.out | 78 + .../expected/yb.orig.pg_stat_statements.out | 78 + .../expected/yb.port.pg_stat_statements.out | 1114 + .../pg_stat_statements--1.10--1.10-yb-1.0.sql | 71 + ...t_statements--1.10-yb-1.0--1.10-yb-2.0.sql | 82 + .../pg_stat_statements--1.10-yb-1.0.sql | 390 + ...t_statements--1.10-yb-2.0--1.10-yb-2.1.sql | 88 + .../pg_stat_statements--1.10-yb-2.0.sql | 471 + .../pg_stat_statements--1.10-yb-2.1.sql | 558 + .../pg_stat_statements--1.6--1.6-yb-1.0.sql | 51 + ...at_statements--1.6-yb-1.0--1.10-yb-1.0.sql | 277 + .../pg_stat_statements/pg_stat_statements.c | 1307 +- .../pg_stat_statements.control | 2 +- .../sql/yb.orig.backfill_queryid.sql | 44 + .../sql/yb.orig.pg_stat_statements.sql | 44 + .../sql/yb.port.pg_stat_statements.sql | 454 + contrib/pg_stat_statements/yb_schedule | 5 + contrib/pg_trgm/Makefile | 2 +- .../expected/yb.port.pg_strict_word_trgm.out | 122 + contrib/pg_trgm/expected/yb.port.pg_trgm.out | 1636 + .../pg_trgm/expected/yb.port.pg_word_trgm.out | 127 + contrib/pg_trgm/pg_trgm--1.4--1.4-yb-1.0.sql | 45 + .../pg_trgm--1.4-yb-1.0--1.6-yb-1.0.sql | 40 + contrib/pg_trgm/pg_trgm.control | 2 +- .../sql/yb.port.pg_strict_word_trgm.sql | 34 + contrib/pg_trgm/sql/yb.port.pg_trgm.sql | 168 + contrib/pg_trgm/sql/yb.port.pg_word_trgm.sql | 34 + contrib/pg_trgm/trgm_gin.c | 6 + contrib/pg_trgm/trgm_gist.c | 4 + contrib/pg_trgm/trgm_regexp.c | 43 +- contrib/pg_trgm/yb_schedule | 6 + contrib/pgcrypto/crypt-blowfish.c | 4 +- contrib/pgcrypto/crypt-gensalt.c | 16 +- contrib/pgcrypto/pgp-info.c | 1 + contrib/pgcrypto/px-crypt.c | 10 +- contrib/pgcrypto/px-crypt.h | 20 +- contrib/pgcrypto/yb_schedule | 27 + contrib/postgres_fdw/Makefile | 2 +- contrib/postgres_fdw/deparse.c | 131 +- .../expected/yb.orig.global_views.out | 1327 + .../expected/yb.orig.postgres_fdw.out | 667 + .../expected/yb.port.postgres_fdw.out | 11827 ++++ contrib/postgres_fdw/option.c | 19 + contrib/postgres_fdw/postgres_fdw.c | 445 +- contrib/postgres_fdw/postgres_fdw.h | 17 + .../postgres_fdw/sql/yb.orig.global_views.sql | 516 + .../postgres_fdw/sql/yb.orig.postgres_fdw.sql | 240 + .../postgres_fdw/sql/yb.port.postgres_fdw.sql | 3909 ++ contrib/postgres_fdw/yb_gv_schedule | 4 + contrib/postgres_fdw/yb_pg_schedule | 3 + contrib/postgres_fdw/yb_schedule | 3 + contrib/seg/Makefile | 2 +- contrib/seg/seg.c | 12 +- contrib/spi/expected/yb.orig.spi.out | 215 + contrib/spi/sql/yb.orig.spi.sql | 190 + contrib/spi/yb_schedule | 3 + .../tablefunc/expected/yb.port.tablefunc.out | 430 + contrib/tablefunc/sql/yb.port.tablefunc.sql | 208 + contrib/tablefunc/yb_schedule | 3 + contrib/test_decoding/test_decoding.c | 51 +- contrib/unaccent/generate_unaccent_rules.py | 11 +- contrib/uuid-ossp/yb_schedule | 5 + doc/bug.template | 53 + doc/src/sgml/ref/allfiles.sgml | 2 + doc/src/sgml/ref/alter_role.sgml | 2 + doc/src/sgml/ref/alter_tablegroup.sgml | 114 + doc/src/sgml/ref/alter_user.sgml | 2 + doc/src/sgml/ref/create_profile.sgml | 31 + doc/src/sgml/ref/create_tablegroup.sgml | 107 + doc/src/sgml/ref/drop_profile.sgml | 29 + doc/src/sgml/ref/drop_tablegroup.sgml | 85 + src/Makefile | 3 + src/Makefile.global.in | 52 +- src/Makefile_ybpostgres.shlib | 540 + src/backend/Makefile | 51 +- src/backend/access/Makefile | 2 +- src/backend/access/brin/brin.c | 6 +- src/backend/access/common/attmap.c | 12 +- src/backend/access/common/heaptuple.c | 45 + src/backend/access/common/indextuple.c | 42 +- src/backend/access/common/printtup.c | 10 +- src/backend/access/common/reloptions.c | 291 +- src/backend/access/common/toast_compression.c | 4 +- src/backend/access/common/toast_internals.c | 3 +- src/backend/access/common/tupconvert.c | 16 +- src/backend/access/gin/ginscan.c | 5 +- src/backend/access/gin/ginutil.c | 27 + src/backend/access/heap/heapam.c | 97 +- src/backend/access/heap/heapam_handler.c | 592 +- src/backend/access/heap/heaptoast.c | 89 + src/backend/access/index/genam.c | 111 +- src/backend/access/index/indexam.c | 236 +- src/backend/access/nbtree/nbtdedup.c | 3 + src/backend/access/nbtree/nbtutils.c | 28 +- src/backend/access/table/toast_helper.c | 2 +- src/backend/access/transam/parallel.c | 89 +- src/backend/access/transam/varsup.c | 209 +- src/backend/access/transam/xact.c | 673 +- src/backend/access/transam/xlog.c | 8 +- src/backend/access/transam/xlogfuncs.c | 10 + src/backend/access/transam/xlogreader.c | 16 + src/backend/access/transam/xlogrecovery.c | 1 + src/backend/access/yb_access/Makefile | 20 + src/backend/access/yb_access/yb_lsm.c | 815 + .../access/yb_access/yb_pg_inherits_scan.c | 235 + src/backend/access/yb_access/yb_scan.c | 5522 ++ src/backend/access/ybgin/Makefile | 17 + src/backend/access/ybgin/ybgin.c | 90 + src/backend/access/ybgin/ybginget.c | 638 + src/backend/access/ybgin/ybginscan.c | 107 + src/backend/access/ybgin/ybginutil.c | 167 + src/backend/access/ybgin/ybginwrite.c | 382 + src/backend/bootstrap/Makefile | 2 + src/backend/bootstrap/bootparse.y | 119 +- src/backend/bootstrap/bootscanner.l | 4 + src/backend/bootstrap/bootstrap.c | 62 +- src/backend/bootstrap/yb_bootstrap.c | 153 + src/backend/catalog/Makefile | 29 +- src/backend/catalog/aclchk.c | 598 +- src/backend/catalog/catalog.c | 669 +- src/backend/catalog/dependency.c | 129 +- src/backend/catalog/heap.c | 503 +- src/backend/catalog/index.c | 670 +- src/backend/catalog/indexing.c | 315 +- src/backend/catalog/namespace.c | 92 +- src/backend/catalog/objectaddress.c | 211 +- src/backend/catalog/partition.c | 17 +- src/backend/catalog/pg_attrdef.c | 5 +- src/backend/catalog/pg_constraint.c | 67 +- src/backend/catalog/pg_db_role_setting.c | 6 +- src/backend/catalog/pg_depend.c | 97 +- src/backend/catalog/pg_enum.c | 100 +- src/backend/catalog/pg_inherits.c | 6 +- src/backend/catalog/pg_largeobject.c | 4 +- src/backend/catalog/pg_publication.c | 227 + src/backend/catalog/pg_range.c | 2 +- src/backend/catalog/pg_shdepend.c | 196 +- src/backend/catalog/pg_subscription.c | 2 +- src/backend/catalog/pg_type.c | 92 +- src/backend/catalog/system_functions.sql | 2 + src/backend/catalog/system_views.sql | 85 +- src/backend/catalog/toasting.c | 24 +- src/backend/catalog/yb_catalog/Makefile | 17 + .../catalog/yb_catalog/yb_catalog_version.c | 1086 + .../catalog/yb_catalog/yb_oid_assignment.c | 433 + src/backend/catalog/yb_catalog/yb_type.c | 1915 + src/backend/catalog/yb_genbki.pl | 997 + .../catalog/yb_logical_client/Makefile | 17 + .../yb_logical_client_version.c | 286 + src/backend/catalog/yb_system_functions.sql | 238 + src/backend/catalog/yb_system_views.sql | 104 + src/backend/commands/Makefile | 5 + src/backend/commands/aggregatecmds.c | 6 +- src/backend/commands/alter.c | 28 +- src/backend/commands/amcmds.c | 9 +- src/backend/commands/analyze.c | 76 +- src/backend/commands/async.c | 1050 +- src/backend/commands/cluster.c | 71 +- src/backend/commands/collationcmds.c | 14 + src/backend/commands/comment.c | 8 +- src/backend/commands/constraint.c | 16 +- src/backend/commands/copy.c | 54 +- src/backend/commands/copyfrom.c | 344 +- src/backend/commands/copyfromparse.c | 25 +- src/backend/commands/copyto.c | 43 + src/backend/commands/createas.c | 93 +- src/backend/commands/dbcommands.c | 522 +- src/backend/commands/define.c | 27 + src/backend/commands/dropcmds.c | 18 +- src/backend/commands/event_trigger.c | 77 +- src/backend/commands/explain.c | 1841 +- src/backend/commands/extension.c | 13 +- src/backend/commands/foreigncmds.c | 121 +- src/backend/commands/functioncmds.c | 178 +- src/backend/commands/indexcmds.c | 1294 +- src/backend/commands/matview.c | 277 +- src/backend/commands/opclasscmds.c | 53 +- src/backend/commands/operatorcmds.c | 2 +- src/backend/commands/policy.c | 68 +- src/backend/commands/portalcmds.c | 30 +- src/backend/commands/prepare.c | 159 +- src/backend/commands/publicationcmds.c | 106 +- src/backend/commands/schemacmds.c | 55 +- src/backend/commands/seclabel.c | 10 +- src/backend/commands/sequence.c | 584 +- src/backend/commands/statscmds.c | 14 +- src/backend/commands/subscriptioncmds.c | 2 +- src/backend/commands/tablecmds.c | 4627 +- src/backend/commands/tablespace.c | 355 +- src/backend/commands/trigger.c | 325 +- src/backend/commands/tsearchcmds.c | 60 +- src/backend/commands/typecmds.c | 62 +- src/backend/commands/user.c | 217 +- src/backend/commands/vacuum.c | 69 +- src/backend/commands/variable.c | 194 +- src/backend/commands/view.c | 17 + src/backend/commands/yb_builtin.c | 234 + src/backend/commands/yb_cmds.c | 2403 + src/backend/commands/yb_profile.c | 805 + src/backend/commands/yb_tablegroup.c | 686 + src/backend/common.mk | 3 + src/backend/executor/Makefile | 9 + src/backend/executor/execAmi.c | 49 +- src/backend/executor/execCurrent.c | 2 + src/backend/executor/execExpr.c | 96 +- src/backend/executor/execExprInterp.c | 177 +- src/backend/executor/execGrouping.c | 181 +- src/backend/executor/execIndexing.c | 1440 +- src/backend/executor/execMain.c | 137 +- src/backend/executor/execParallel.c | 32 + src/backend/executor/execPartition.c | 47 +- src/backend/executor/execProcnode.c | 707 +- src/backend/executor/execReplication.c | 28 +- src/backend/executor/execScan.c | 9 + src/backend/executor/execTuples.c | 34 +- src/backend/executor/execUtils.c | 51 +- src/backend/executor/functions.c | 53 +- src/backend/executor/instrument.c | 49 + src/backend/executor/nodeAgg.c | 511 +- src/backend/executor/nodeBitmapAnd.c | 39 +- src/backend/executor/nodeBitmapIndexscan.c | 20 +- src/backend/executor/nodeBitmapOr.c | 63 +- src/backend/executor/nodeForeignscan.c | 10 + src/backend/executor/nodeHash.c | 7 + src/backend/executor/nodeHashjoin.c | 6 +- src/backend/executor/nodeIndexonlyscan.c | 266 +- src/backend/executor/nodeIndexscan.c | 513 +- src/backend/executor/nodeLimit.c | 29 + src/backend/executor/nodeLockRows.c | 97 +- src/backend/executor/nodeModifyTable.c | 1666 +- src/backend/executor/nodeRecursiveunion.c | 3 +- src/backend/executor/nodeSeqscan.c | 4 + src/backend/executor/nodeSetOp.c | 3 +- src/backend/executor/nodeSort.c | 13 + src/backend/executor/nodeSubplan.c | 12 +- src/backend/executor/nodeTidrangescan.c | 5 + src/backend/executor/nodeTidscan.c | 242 +- src/backend/executor/nodeUnique.c | 3 + src/backend/executor/nodeYbBatchedNestloop.c | 1189 + src/backend/executor/nodeYbBitmapIndexscan.c | 390 + src/backend/executor/nodeYbBitmapTablescan.c | 529 + src/backend/executor/nodeYbSeqscan.c | 469 + src/backend/executor/spi.c | 40 +- src/backend/executor/ybExpr.c | 836 + src/backend/executor/ybFunction.c | 98 + src/backend/executor/ybModifyTable.c | 1538 + src/backend/executor/ybOptimizeModifyTable.c | 597 + src/backend/exports-common.txt | 63 + src/backend/exports-test.txt | 5 + src/backend/foreign/foreign.c | 22 + src/backend/jit/jit.c | 2 +- src/backend/jit/llvm/llvmjit_expr.c | 6 + src/backend/lib/Makefile | 2 + src/backend/lib/yb_percentile.c | 201 + src/backend/libpq/auth-scram.c | 16 +- src/backend/libpq/auth.c | 500 +- src/backend/libpq/crypt.c | 34 + src/backend/libpq/hba.c | 410 +- src/backend/libpq/pqcomm.c | 83 +- src/backend/main/Makefile | 5 + src/backend/main/main.c | 11 +- src/backend/main/main_cpp_wrapper.cc | 7 + src/backend/nodes/Makefile | 3 + src/backend/nodes/README | 1 + src/backend/nodes/copyfuncs.c | 416 + src/backend/nodes/equalfuncs.c | 128 + src/backend/nodes/makefuncs.c | 4 + src/backend/nodes/nodeFuncs.c | 71 +- src/backend/nodes/outfuncs.c | 318 +- src/backend/nodes/read.c | 61 +- src/backend/nodes/readfuncs.c | 287 +- src/backend/nodes/ybbitmatrix.c | 205 + src/backend/nodes/ybtidbitmap.c | 302 + src/backend/optimizer/README | 1 + src/backend/optimizer/geqo/geqo_main.c | 2 +- src/backend/optimizer/path/Makefile | 2 + src/backend/optimizer/path/allpaths.c | 1124 +- src/backend/optimizer/path/costsize.c | 2897 +- src/backend/optimizer/path/equivclass.c | 65 + src/backend/optimizer/path/indxpath.c | 1392 +- src/backend/optimizer/path/joinpath.c | 229 +- src/backend/optimizer/path/joinrels.c | 24 +- src/backend/optimizer/path/pathkeys.c | 180 +- src/backend/optimizer/path/tidpath.c | 27 +- src/backend/optimizer/path/yb_uniqkeys.c | 725 + src/backend/optimizer/plan/analyzejoins.c | 14 +- src/backend/optimizer/plan/createplan.c | 2628 +- src/backend/optimizer/plan/initsplan.c | 382 + src/backend/optimizer/plan/planagg.c | 24 + src/backend/optimizer/plan/planner.c | 2155 +- src/backend/optimizer/plan/setrefs.c | 283 +- src/backend/optimizer/plan/subselect.c | 50 +- src/backend/optimizer/prep/prepjointree.c | 4 + src/backend/optimizer/prep/preptlist.c | 195 +- src/backend/optimizer/prep/prepunion.c | 22 + src/backend/optimizer/util/Makefile | 3 + src/backend/optimizer/util/appendinfo.c | 37 +- src/backend/optimizer/util/clauses.c | 63 +- src/backend/optimizer/util/inherit.c | 71 +- src/backend/optimizer/util/paramassign.c | 55 + src/backend/optimizer/util/pathnode.c | 1131 +- src/backend/optimizer/util/plancat.c | 159 +- src/backend/optimizer/util/relnode.c | 297 +- src/backend/optimizer/util/restrictinfo.c | 79 + src/backend/optimizer/util/tlist.c | 12 + src/backend/optimizer/util/var.c | 36 +- src/backend/optimizer/util/yb_merge_scan.c | 498 + src/backend/optimizer/util/ybplan.c | 1006 + src/backend/parser/analyze.c | 48 +- src/backend/parser/check_keywords.pl | 1 + src/backend/parser/gram.y | 1397 +- src/backend/parser/parse_clause.c | 22 +- src/backend/parser/parse_collate.c | 3 +- src/backend/parser/parse_expr.c | 3 +- src/backend/parser/parse_relation.c | 18 +- src/backend/parser/parse_target.c | 34 +- src/backend/parser/parse_utilcmd.c | 792 +- src/backend/parser/scan.l | 4 +- src/backend/partitioning/partdesc.c | 6 +- src/backend/partitioning/partprune.c | 201 +- src/backend/port/sysv_shmem.c | 13 + src/backend/postmaster/autovacuum.c | 3 +- src/backend/postmaster/bgworker.c | 27 + src/backend/postmaster/checkpointer.c | 15 + src/backend/postmaster/postmaster.c | 635 +- src/backend/regex/regc_lex.c | 1 + src/backend/regex/regc_nfa.c | 19 + src/backend/regex/regc_pg_locale.c | 33 +- src/backend/regex/regcomp.c | 1 + src/backend/regex/regerror.c | 3 + src/backend/replication/logical/Makefile | 3 + src/backend/replication/logical/decode.c | 21 + src/backend/replication/logical/logical.c | 170 +- .../replication/logical/logicalfuncs.c | 11 + src/backend/replication/logical/origin.c | 48 +- src/backend/replication/logical/proto.c | 55 +- .../replication/logical/reorderbuffer.c | 310 +- src/backend/replication/logical/snapbuild.c | 37 +- src/backend/replication/logical/worker.c | 3 +- src/backend/replication/logical/yb_decode.c | 633 + .../logical/yb_virtual_wal_client.c | 933 + src/backend/replication/pgoutput/pgoutput.c | 58 +- src/backend/replication/repl_gram.y | 31 + src/backend/replication/repl_scanner.l | 6 + src/backend/replication/slot.c | 343 +- src/backend/replication/slotfuncs.c | 433 +- src/backend/replication/syncrep.c | 8 +- src/backend/replication/walreceiver.c | 1 + src/backend/replication/walreceiverfuncs.c | 1 + src/backend/replication/walsender.c | 465 +- src/backend/rewrite/rewriteDefine.c | 7 + src/backend/rewrite/rewriteHandler.c | 31 +- src/backend/rewrite/rewriteRemove.c | 2 +- src/backend/statistics/attribute_stats.c | 38 +- src/backend/statistics/extended_stats.c | 7 + src/backend/statistics/relation_stats.c | 18 + src/backend/storage/buffer/bufmgr.c | 43 + src/backend/storage/file/fd.c | 12 + src/backend/storage/ipc/ipc.c | 8 +- src/backend/storage/ipc/ipci.c | 31 + src/backend/storage/ipc/latch.c | 14 + src/backend/storage/ipc/pmsignal.c | 8 + src/backend/storage/ipc/procarray.c | 133 +- src/backend/storage/ipc/procsignal.c | 78 +- src/backend/storage/ipc/sinvaladt.c | 120 +- src/backend/storage/large_object/inv_api.c | 8 +- src/backend/storage/lmgr/condition_variable.c | 40 +- src/backend/storage/lmgr/lmgr.c | 198 +- src/backend/storage/lmgr/lock.c | 303 +- src/backend/storage/lmgr/lwlock.c | 95 +- src/backend/storage/lmgr/predicate.c | 25 +- src/backend/storage/lmgr/proc.c | 172 +- src/backend/storage/lmgr/s_lock.c | 8 +- src/backend/storage/page/itemptr.c | 3 + src/backend/tcop/dest.c | 1 + src/backend/tcop/postgres.c | 3038 +- src/backend/tcop/pquery.c | 32 + src/backend/tcop/utility.c | 313 +- src/backend/utils/Gen_fmgrtab.pl | 2 +- src/backend/utils/activity/Makefile | 2 + src/backend/utils/activity/backend_progress.c | 8 +- src/backend/utils/activity/backend_status.c | 221 +- src/backend/utils/activity/pgstat.c | 9 +- src/backend/utils/activity/pgstat_replslot.c | 3 +- src/backend/utils/activity/wait_event.c | 662 + .../utils/activity/yb_terminated_queries.c | 316 + src/backend/utils/adt/Makefile | 2 + src/backend/utils/adt/acl.c | 8 + src/backend/utils/adt/arrayfuncs.c | 99 +- src/backend/utils/adt/datetime.c | 4 + src/backend/utils/adt/dbsize.c | 96 +- src/backend/utils/adt/expandedrecord.c | 1 + src/backend/utils/adt/formatting.c | 5 +- src/backend/utils/adt/genfile.c | 7 + src/backend/utils/adt/hbafuncs.c | 28 + src/backend/utils/adt/int.c | 77 +- src/backend/utils/adt/int8.c | 16 +- src/backend/utils/adt/jsonb_util.c | 1 + src/backend/utils/adt/jsonfuncs.c | 246 + src/backend/utils/adt/jsonpath.c | 5 + src/backend/utils/adt/like_support.c | 15 +- src/backend/utils/adt/lockfuncs.c | 124 +- src/backend/utils/adt/misc.c | 9 + src/backend/utils/adt/network.c | 2 +- src/backend/utils/adt/numeric.c | 48 +- src/backend/utils/adt/pg_locale.c | 42 +- src/backend/utils/adt/pg_upgrade_support.c | 46 +- src/backend/utils/adt/pgstatfuncs.c | 341 +- src/backend/utils/adt/pseudotypes.c | 19 +- src/backend/utils/adt/rangetypes.c | 93 +- src/backend/utils/adt/regexp.c | 63 +- src/backend/utils/adt/ri_triggers.c | 448 +- src/backend/utils/adt/rowtypes.c | 83 +- src/backend/utils/adt/ruleutils.c | 625 +- src/backend/utils/adt/selfuncs.c | 401 +- src/backend/utils/adt/timestamp.c | 65 +- src/backend/utils/adt/tsvector_op.c | 3 +- src/backend/utils/adt/varlena.c | 93 + src/backend/utils/adt/xml.c | 1 + src/backend/utils/adt/yb_lockfuncs.c | 187 + src/backend/utils/cache/Makefile | 3 + src/backend/utils/cache/catcache.c | 1248 +- src/backend/utils/cache/evtcache.c | 29 +- src/backend/utils/cache/inval.c | 568 +- src/backend/utils/cache/lsyscache.c | 85 +- src/backend/utils/cache/partcache.c | 3 +- src/backend/utils/cache/plancache.c | 186 +- src/backend/utils/cache/relcache.c | 3316 +- src/backend/utils/cache/spccache.c | 298 +- src/backend/utils/cache/syscache.c | 1001 + src/backend/utils/cache/typcache.c | 5 +- src/backend/utils/cache/yb_inheritscache.c | 450 + src/backend/utils/cache/yb_tuplecache.c | 109 + src/backend/utils/errcodes.txt | 10 + src/backend/utils/error/elog.c | 1212 +- src/backend/utils/fmgr/fmgr.c | 77 +- src/backend/utils/hash/dynahash.c | 6 +- src/backend/utils/init/globals.c | 28 + src/backend/utils/init/miscinit.c | 37 +- src/backend/utils/init/postinit.c | 678 +- .../conversion_procs/euc_tw_and_big5/big5.c | 1 + src/backend/utils/mb/mbutils.c | 3 + src/backend/utils/misc/Makefile | 11 + src/backend/utils/misc/guc.c | 6191 +- src/backend/utils/misc/help_config.c | 8 + src/backend/utils/misc/pg_yb_utils.c | 8976 +++ src/backend/utils/misc/postgresql.conf.sample | 17 + src/backend/utils/misc/ps_status.c | 15 +- src/backend/utils/misc/queryjumble.c | 57 +- src/backend/utils/misc/timeout.c | 25 + src/backend/utils/misc/yb_ash.c | 1445 + .../misc/yb_exceptions_for_func_pushdown.c | 252 + src/backend/utils/misc/yb_file_utils.c | 134 + src/backend/utils/misc/yb_index_check.c | 1397 + src/backend/utils/misc/yb_jumblefuncs.c | 5993 ++ src/backend/utils/misc/yb_qpm.c | 1954 + src/backend/utils/misc/yb_query_diagnostics.c | 3063 + src/backend/utils/misc/yb_tcmalloc_utils.c | 331 + .../utils/misc/yb_ysql_conn_mgr_helper.c | 1093 + src/backend/utils/mmgr/aset.c | 43 +- src/backend/utils/mmgr/generation.c | 16 + src/backend/utils/mmgr/mcxt.c | 369 +- src/backend/utils/mmgr/portalmem.c | 11 + src/backend/utils/mmgr/slab.c | 15 + src/backend/utils/resowner/resowner.c | 74 + src/backend/utils/sort/logtape.c | 8 +- src/backend/utils/sort/sortsupport.c | 2 +- src/backend/utils/sort/tuplesort.c | 44 +- src/backend/utils/sort/tuplestore.c | 1 + src/backend/utils/time/snapmgr.c | 383 +- src/backend/ybgate/Makefile | 20 + src/backend/ybgate/ybgate_api-test.c | 367 + src/backend/ybgate/ybgate_api.c | 889 + src/backend/ybgate/ybgate_status.c | 333 + src/bin/initdb/Makefile | 9 +- src/bin/initdb/initdb.c | 564 +- src/bin/pg_dump/Makefile | 26 +- src/bin/pg_dump/common.c | 36 +- src/bin/pg_dump/dumputils.c | 205 +- src/bin/pg_dump/dumputils.h | 20 +- src/bin/pg_dump/parallel.c | 2 +- src/bin/pg_dump/pg_backup.h | 15 + src/bin/pg_dump/pg_backup_archiver.c | 212 +- src/bin/pg_dump/pg_dump.c | 1484 +- src/bin/pg_dump/pg_dump.h | 28 +- src/bin/pg_dump/pg_dump_sort.c | 9 +- src/bin/pg_dump/pg_dumpall.c | 503 +- src/bin/pg_dump/pg_restore.c | 2 +- src/bin/pg_dump/t/002_pg_dump.pl | 72 +- src/bin/pg_upgrade/check.c | 618 +- src/bin/pg_upgrade/controldata.c | 8 +- src/bin/pg_upgrade/dump.c | 6 +- src/bin/pg_upgrade/exec.c | 32 +- src/bin/pg_upgrade/option.c | 105 +- src/bin/pg_upgrade/pg_upgrade.c | 272 +- src/bin/pg_upgrade/pg_upgrade.h | 9 + src/bin/pg_upgrade/server.c | 25 +- src/bin/pg_upgrade/util.c | 47 + src/bin/pg_upgrade/version.c | 17 +- src/bin/pg_waldump/.gitignore | 49 +- src/bin/pg_waldump/brindesc.c | 1 + src/bin/pg_waldump/clogdesc.c | 1 + src/bin/pg_waldump/committsdesc.c | 1 + src/bin/pg_waldump/dbasedesc.c | 1 + src/bin/pg_waldump/genericdesc.c | 1 + src/bin/pg_waldump/gindesc.c | 1 + src/bin/pg_waldump/gistdesc.c | 1 + src/bin/pg_waldump/hashdesc.c | 1 + src/bin/pg_waldump/heapdesc.c | 1 + src/bin/pg_waldump/logicalmsgdesc.c | 1 + src/bin/pg_waldump/mxactdesc.c | 1 + src/bin/pg_waldump/nbtdesc.c | 1 + src/bin/pg_waldump/relmapdesc.c | 1 + src/bin/pg_waldump/replorigindesc.c | 1 + src/bin/pg_waldump/seqdesc.c | 1 + src/bin/pg_waldump/smgrdesc.c | 1 + src/bin/pg_waldump/spgdesc.c | 1 + src/bin/pg_waldump/standbydesc.c | 1 + src/bin/pg_waldump/tblspcdesc.c | 1 + src/bin/pg_waldump/xactdesc.c | 1 + src/bin/pg_waldump/xlogdesc.c | 1 + src/bin/pg_waldump/xlogreader.c | 1 + src/bin/pgbench/Makefile | 18 +- src/bin/pgbench/pgbench.c | 277 +- src/bin/psql/Makefile | 12 +- src/bin/psql/command.c | 13 +- src/bin/psql/common.c | 2 +- src/bin/psql/describe.c | 374 +- src/bin/psql/describe.h | 3 + src/bin/psql/help.c | 28 +- src/bin/psql/startup.c | 10 + src/bin/psql/tab-complete.c | 33 +- src/common/Makefile | 2 + src/common/exec.c | 10 + src/common/fe_memutils.c | 6 + src/common/hashfn.c | 72 + src/common/logging.c | 10 +- src/common/pg_yb_common.c | 322 + src/common/username.c | 45 + src/common/wchar.c | 3 + src/fe_utils/string_utils.c | 14 +- src/include/Makefile | 2 +- src/include/access/amapi.h | 105 + src/include/access/attmap.h | 3 +- src/include/access/genam.h | 30 +- src/include/access/gin_private.h | 2 +- src/include/access/heapam.h | 2 +- src/include/access/heaptoast.h | 2 + src/include/access/htup.h | 3 +- src/include/access/htup_details.h | 5 + src/include/access/itup.h | 26 +- src/include/access/nbtree.h | 3 + src/include/access/reloptions.h | 30 +- src/include/access/relscan.h | 51 + src/include/access/skey.h | 78 + src/include/access/sysattr.h | 16 + src/include/access/tableam.h | 57 +- src/include/access/transam.h | 32 +- src/include/access/tupconvert.h | 5 +- src/include/access/xact.h | 58 +- src/include/access/xlog.h | 2 + src/include/access/xlogreader.h | 7 + src/include/access/yb_pg_inherits_scan.h | 36 + src/include/access/yb_scan.h | 421 + src/include/access/yb_sys_scan_base.h | 41 + src/include/access/ybgin.h | 75 + src/include/access/ybgin_private.h | 51 + src/include/bootstrap/yb_bootstrap.h | 44 + src/include/c.h | 9 +- src/include/catalog/binary_upgrade.h | 6 + src/include/catalog/catalog.h | 24 + src/include/catalog/catversion.h | 1 + src/include/catalog/dependency.h | 31 +- src/include/catalog/heap.h | 10 +- src/include/catalog/index.h | 60 +- src/include/catalog/indexing.h | 9 +- src/include/catalog/pg_am.dat | 6 + src/include/catalog/pg_amop.dat | 962 + src/include/catalog/pg_amproc.dat | 216 + src/include/catalog/pg_authid.dat | 16 + src/include/catalog/pg_class.h | 10 + src/include/catalog/pg_constraint.h | 3 + src/include/catalog/pg_default_acl.h | 1 + src/include/catalog/pg_index.h | 3 + src/include/catalog/pg_opclass.dat | 116 + src/include/catalog/pg_opfamily.dat | 75 + src/include/catalog/pg_opfamily.h | 2 +- src/include/catalog/pg_proc.dat | 560 +- src/include/catalog/pg_publication.h | 7 + src/include/catalog/pg_type.dat | 2 +- src/include/catalog/pg_type.h | 11 +- src/include/catalog/pg_yb_catalog_version.dat | 18 + src/include/catalog/pg_yb_catalog_version.h | 53 + .../catalog/pg_yb_invalidation_messages.h | 55 + .../catalog/pg_yb_logical_client_version.dat | 19 + .../catalog/pg_yb_logical_client_version.h | 46 + src/include/catalog/pg_yb_migration.dat | 18 + src/include/catalog/pg_yb_migration.h | 58 + src/include/catalog/pg_yb_profile.h | 47 + src/include/catalog/pg_yb_role_profile.h | 58 + src/include/catalog/pg_yb_tablegroup.dat | 13 + src/include/catalog/pg_yb_tablegroup.h | 50 + src/include/catalog/unused_oids | 9 + src/include/catalog/yb_catalog_version.h | 65 + .../catalog/yb_logical_client_version.h | 40 + src/include/catalog/yb_oid_assignment.h | 39 + src/include/catalog/yb_type.h | 68 + src/include/commands/async.h | 9 + src/include/commands/cluster.h | 8 +- src/include/commands/copy.h | 22 +- src/include/commands/copyfrom_internal.h | 4 + src/include/commands/defrem.h | 9 + src/include/commands/event_trigger.h | 2 + src/include/commands/explain.h | 32 + src/include/commands/matview.h | 3 +- src/include/commands/policy.h | 5 +- src/include/commands/prepare.h | 3 +- src/include/commands/progress.h | 19 + src/include/commands/tablecmds.h | 8 +- src/include/commands/tablespace.h | 13 + src/include/commands/trigger.h | 23 +- src/include/commands/variable.h | 12 + src/include/commands/yb_cmds.h | 168 + src/include/commands/yb_profile.h | 51 + src/include/commands/yb_tablegroup.h | 45 + src/include/common/logging.h | 2 + src/include/common/pg_yb_common.h | 160 + src/include/common/pg_yb_param_status_flags.h | 33 + src/include/executor/execExpr.h | 13 + src/include/executor/execPartition.h | 3 + src/include/executor/execdesc.h | 7 + src/include/executor/executor.h | 60 +- src/include/executor/instrument.h | 36 + src/include/executor/nodeForeignscan.h | 6 + src/include/executor/nodeLockRows.h | 3 + src/include/executor/nodeYbBatchedNestloop.h | 33 + src/include/executor/nodeYbBitmapIndexscan.h | 24 + src/include/executor/nodeYbBitmapTablescan.h | 22 + src/include/executor/nodeYbSeqscan.h | 45 + src/include/executor/spi.h | 1 + src/include/executor/tuptable.h | 28 + src/include/executor/ybExpr.h | 64 + src/include/executor/ybFunction.h | 41 + src/include/executor/ybModifyTable.h | 281 + src/include/executor/ybOptimizeModifyTable.h | 40 + src/include/fmgr.h | 7 + src/include/funcapi.h | 49 + src/include/lib/sort_template.h | 9 + src/include/libpq/crypt.h | 6 + src/include/libpq/hba.h | 18 +- src/include/libpq/libpq-be.h | 22 + src/include/libpq/libpq.h | 3 + src/include/libpq/yb_pqcomm_extensions.h | 24 + src/include/miscadmin.h | 60 + src/include/nodes/execnodes.h | 367 +- src/include/nodes/memnodes.h | 3 + src/include/nodes/nodeFuncs.h | 3 + src/include/nodes/nodes.h | 40 +- src/include/nodes/parsenodes.h | 154 +- src/include/nodes/pathnodes.h | 263 +- src/include/nodes/pg_list.h | 9 + src/include/nodes/plannodes.h | 337 +- src/include/nodes/primnodes.h | 17 +- src/include/nodes/readfuncs.h | 2 + src/include/nodes/tidbitmap.h | 9 + src/include/nodes/ybbitmatrix.h | 54 + src/include/nodes/ybtidbitmap.h | 89 + src/include/optimizer/clauses.h | 4 + src/include/optimizer/cost.h | 96 + src/include/optimizer/optimizer.h | 2 + src/include/optimizer/pathnode.h | 23 +- src/include/optimizer/paths.h | 39 +- src/include/optimizer/plancat.h | 4 +- src/include/optimizer/planmain.h | 14 + src/include/optimizer/planner.h | 9 + src/include/optimizer/restrictinfo.h | 11 + src/include/optimizer/yb_merge_scan.h | 52 + src/include/optimizer/ybplan.h | 59 + src/include/parser/kwlist.h | 15 + src/include/parser/parse_utilcmd.h | 13 + src/include/partitioning/partdesc.h | 3 + src/include/partitioning/partprune.h | 6 +- src/include/pg_yb_utils.h | 1652 + src/include/pgstat.h | 199 + src/include/port.h | 6 + src/include/postgres.h | 11 + src/include/postmaster/bgworker.h | 14 + src/include/postmaster/postmaster.h | 8 + src/include/regex/regguts.h | 13 +- src/include/replication/logical.h | 25 + src/include/replication/logicalproto.h | 4 +- src/include/replication/output_plugin.h | 23 + src/include/replication/reorderbuffer.h | 21 + src/include/replication/slot.h | 64 +- src/include/replication/snapbuild.h | 4 + src/include/replication/syncrep.h | 5 +- src/include/replication/walsender.h | 18 + src/include/replication/walsender_private.h | 3 + src/include/replication/yb_decode.h | 33 + .../replication/yb_virtual_wal_client.h | 46 + .../yb_virtual_wal_client_typedefs.h | 31 + src/include/storage/condition_variable.h | 13 + src/include/storage/ipc.h | 7 +- src/include/storage/latch.h | 3 + src/include/storage/lock.h | 4 + src/include/storage/lwlock.h | 5 + src/include/storage/pg_shmem.h | 3 + src/include/storage/proc.h | 69 + src/include/storage/procarray.h | 2 + src/include/storage/procsignal.h | 10 + src/include/storage/sinval.h | 66 + src/include/storage/sinvaladt.h | 4 + src/include/storage/spin.h | 23 +- src/include/tcop/cmdtaglist.h | 9 +- src/include/tcop/pquery.h | 9 + src/include/tcop/tcopprot.h | 2 + src/include/utils/acl.h | 7 + src/include/utils/array.h | 2 +- src/include/utils/backend_status.h | 60 +- src/include/utils/builtins.h | 3 + src/include/utils/catcache.h | 38 + src/include/utils/elog.h | 63 +- src/include/utils/fmgrtab.h | 6 +- src/include/utils/formatting.h | 2 + src/include/utils/guc.h | 53 +- src/include/utils/guc_tables.h | 28 +- src/include/utils/inval.h | 8 +- src/include/utils/jsonfuncs.h | 8 + src/include/utils/lsyscache.h | 3 + src/include/utils/memutils.h | 77 + src/include/utils/numeric.h | 9 + src/include/utils/palloc.h | 74 +- src/include/utils/partcache.h | 3 + src/include/utils/plancache.h | 25 +- src/include/utils/rel.h | 63 + src/include/utils/relcache.h | 29 +- src/include/utils/resowner_private.h | 11 + src/include/utils/ruleutils.h | 7 + src/include/utils/selfuncs.h | 9 + src/include/utils/snapmgr.h | 8 + src/include/utils/snapshot.h | 12 + src/include/utils/spccache.h | 3 + src/include/utils/syscache.h | 95 +- src/include/utils/timestamp.h | 3 + src/include/utils/wait_event.h | 69 +- src/include/utils/yb_inheritscache.h | 49 + src/include/utils/yb_jumblefuncs.h | 35 + src/include/utils/yb_like_support.h | 37 + src/include/utils/yb_queryjumble.h | 73 + src/include/utils/yb_rowtypes.h | 24 + src/include/utils/yb_tuplecache.h | 66 + src/include/yb_ash.h | 92 + src/include/yb_file_utils.h | 38 + src/include/yb_overflow_utils.h | 110 + src/include/yb_qpm.h | 45 + src/include/yb_query_diagnostics.h | 237 + src/include/yb_tcmalloc_utils.h | 41 + src/include/yb_terminated_queries.h | 35 + src/include/yb_ysql_conn_mgr_helper.h | 154 + src/include/ybctid.h | 55 + src/include/ybgate/ybgate_api-test.h | 78 + src/include/ybgate/ybgate_api.h | 283 + src/include/ybgate/ybgate_cpp_util.h | 78 + src/include/ybgate/ybgate_status.h | 136 + src/interfaces/ecpg/pgtypeslib/interval.c | 3 + src/interfaces/ecpg/preproc/ecpg.c | 1 + src/interfaces/libpq/Makefile | 3 +- src/interfaces/libpq/exports.txt | 2 + src/interfaces/libpq/fe-connect.c | 44 +- src/interfaces/libpq/fe-exec.c | 13 +- src/interfaces/libpq/fe-gssapi-common.c | 1 + src/interfaces/libpq/fe-misc.c | 5 +- src/interfaces/libpq/fe-protocol3.c | 2 + src/interfaces/libpq/fe-secure.c | 1 + src/interfaces/libpq/libpq-fe.h | 5 + src/interfaces/libpq/libpq-int.h | 14 +- src/makefiles/pgxs.mk | 2 +- src/pl/plperl/plperl.c | 7 +- src/pl/plpgsql/src/Makefile | 2 +- src/pl/plpgsql/src/pl_comp.c | 42 +- src/pl/plpgsql/src/pl_exec.c | 247 +- src/pl/plpgsql/src/pl_gram.y | 47 + src/pl/plpgsql/src/plpgsql.h | 11 + src/pl/plpython/plpy_procedure.c | 4 +- src/pl/plpython/plpy_procedure.h | 3 + src/pl/tcl/pltcl.c | 1 + src/port/quotes.c | 4 + src/port/snprintf.c | 1 + ...b.orig.advisory-locks-fail-on-conflict.out | 54 + .../expected/yb.orig.advisory_locks.out | 614 + .../expected/yb.orig.analyze_ddl_deadlock.out | 27 + .../yb.orig.avoid_stale_catalog_reads-1.out | 65 + .../yb.orig.avoid_stale_catalog_reads-2.out | 22 + .../yb.orig.avoid_stale_catalog_reads-3.out | 23 + .../yb.orig.avoid_stale_catalog_reads-4.out | 24 + .../yb.orig.check-constraint-locking.out | 398 + .../yb.orig.check-constraint-locking_1.out | 398 + ...concurrent-inserts-duplicate-key-error.out | 57 + .../expected/yb.orig.concurrent_ddls.out | 76 + ...oncurrent_ddls_read_latest_cat_version.out | 21 + ....orig.concurrent_insert_during_rewrite.out | 23 + .../yb.orig.concurrent_partition_insert.out | 26 + .../isolation/expected/yb.orig.deadlock.out | 101 + ...orig.drop-expression-partitioned-table.out | 7 + ...ig.drop-expression-partitioned-table_1.out | 7 + ...g.drop-probe-waiting-on-aborted-subtxn.out | 30 + ...sure-lock-only-conflict-always-ignored.out | 61 + .../expected/yb.orig.fk-relationship.out | 156 + .../expected/yb.orig.fk-relationship_1.out | 158 + .../expected/yb.orig.fk-row-locking.out | 360 + ...b.orig.fk_triggers_use_latest_snapshot.out | 933 + ...ig.ignore-rolled-back-savepoint-intent.out | 39 + .../yb.orig.inplace_catalog_updates.out | 173 + .../expected/yb.orig.lock-status-waiters.out | 59 + .../yb.orig.lock-status-waiters_1.out | 61 + .../yb.orig.lock-status-waiters_2.out | 61 + .../yb.orig.lock-status-waiters_3.out | 80 + .../yb.orig.lock-status-waiters_4.out | 80 + .../yb.orig.lock-status-waiters_5.out | 80 + .../expected/yb.orig.lock-status.out | 104 + .../yb.orig.modification-followed-by-lock.out | 26 + ...rig.modify-transaction-characteristics.out | 832 + .../expected/yb.orig.only-abort-sub-txn.out | 25 + .../yb.orig.pg_stat_statements-retry.out | 44 + ...g.query_layer_retries_multi_stmt_query.out | 123 + ...yb.orig.rc-ignores-txn-priority-bounds.out | 78 + .../yb.orig.read_committed_insert.out | 56 + ...d_committed_insert_duplicate_key_error.out | 43 + ...d_committed_non_atomic_commit_no_retry.out | 58 + .../yb.orig.read_committed_test_ddl_txn.out | 65 + .../yb.orig.read_committed_test_do_call.out | 27 + ...read_committed_test_internal_savepoint.out | 16 + ..._committed_update_and_explicit_locking.out | 27 + .../yb.orig.reset_txn_snapshot_after_ddl.out | 24 + ...alizable_conflict_with_index_only_scan.out | 107 + ....orig.single-shard-waiters-same-tablet.out | 20 + .../yb.orig.skip-locked-after-update.out | 82 + ...g.skip-locked-single-shard-transaction.out | 17 + .../yb.orig.snapshot_switch_dml_ddl_txn.out | 12 + ...tat-activity-waitevent-statement-retry.out | 52 + ....unblock-waiter-after-subtxn-rollsback.out | 37 + .../yb.orig.user-managed-constraint.out | 245 + .../yb.orig.user-managed-constraint_1.out | 246 + .../yb.orig.user_table_access_in_a_ddl.out | 25 + ...yb.orig.wait-queues-serializable-reads.out | 13 + ...ait-queues-single-shard-waiter-unlocks.out | 29 + .../yb.orig.wait-queues-weak-read-unlocks.out | 48 + .../expected/yb.port.async-notify.out | 128 + .../expected/yb.port.deadlock-hard.out | 36 + .../expected/yb.port.deadlock-simple.out | 12 + .../expected/yb.port.deadlock-soft-2.out | 19 + .../expected/yb.port.deadlock-soft.out | 18 + .../expected/yb.port.deadlock-soft_1.out | 19 + .../expected/yb.port.eval-plan-qual.out | 211 + .../expected/yb.port.fk-snapshot.out | 186 + .../expected/yb.port.fk-snapshot_1.out | 186 + .../yb.port.insert-conflict-do-nothing-2.out | 119 + .../yb.port.partition-drop-index-locking.out | 96 + .../isolation/expected/yb.port.timeouts.out | 60 + src/test/isolation/isolationtester.c | 87 +- src/test/isolation/isolationtester.h | 3 + ....orig.advisory-locks-fail-on-conflict.spec | 20 + .../specs/yb.orig.advisory_locks.spec | 96 + .../specs/yb.orig.analyze_ddl_deadlock.spec | 32 + .../yb.orig.avoid_stale_catalog_reads-1.spec | 57 + .../yb.orig.avoid_stale_catalog_reads-2.spec | 26 + .../yb.orig.avoid_stale_catalog_reads-3.spec | 26 + .../yb.orig.avoid_stale_catalog_reads-4.spec | 26 + .../yb.orig.check-constraint-locking.spec | 107 + ...oncurrent-inserts-duplicate-key-error.spec | 41 + .../specs/yb.orig.concurrent_ddls.spec | 48 + ...ncurrent_ddls_read_latest_cat_version.spec | 29 + ...orig.concurrent_insert_during_rewrite.spec | 28 + .../yb.orig.concurrent_partition_insert.spec | 31 + .../isolation/specs/yb.orig.deadlock.spec | 54 + ...rig.drop-expression-partitioned-table.spec | 25 + ....drop-probe-waiting-on-aborted-subtxn.spec | 50 + ...ure-lock-only-conflict-always-ignored.spec | 51 + .../specs/yb.orig.fk-relationship.spec | 53 + .../specs/yb.orig.fk-row-locking.spec | 85 + ....orig.fk_triggers_use_latest_snapshot.spec | 155 + ...g.ignore-rolled-back-savepoint-intent.spec | 27 + .../yb.orig.inplace_catalog_updates.spec | 70 + .../specs/yb.orig.lock-status-waiters.spec | 54 + .../isolation/specs/yb.orig.lock-status.spec | 60 + ...yb.orig.modification-followed-by-lock.spec | 31 + ...ig.modify-transaction-characteristics.spec | 175 + .../specs/yb.orig.only-abort-sub-txn.spec | 24 + .../yb.orig.pg_stat_statements-retry.spec | 32 + ....query_layer_retries_multi_stmt_query.spec | 49 + ...b.orig.rc-ignores-txn-priority-bounds.spec | 46 + .../specs/yb.orig.read_committed_insert.spec | 32 + ..._committed_insert_duplicate_key_error.spec | 36 + ..._committed_non_atomic_commit_no_retry.spec | 77 + .../yb.orig.read_committed_test_ddl_txn.spec | 41 + .../yb.orig.read_committed_test_do_call.spec | 38 + ...ead_committed_test_internal_savepoint.spec | 40 + ...committed_update_and_explicit_locking.spec | 48 + .../yb.orig.reset_txn_snapshot_after_ddl.spec | 29 + ...lizable_conflict_with_index_only_scan.spec | 47 + ...orig.single-shard-waiters-same-tablet.spec | 29 + .../yb.orig.skip-locked-after-update.spec | 61 + ....skip-locked-single-shard-transaction.spec | 24 + .../yb.orig.snapshot_switch_dml_ddl_txn.spec | 24 + ...at-activity-waitevent-statement-retry.spec | 69 + ...unblock-waiter-after-subtxn-rollsback.spec | 44 + .../yb.orig.user-managed-constraint.spec | 77 + .../yb.orig.user_table_access_in_a_ddl.spec | 26 + ...b.orig.wait-queues-serializable-reads.spec | 23 + ...it-queues-single-shard-waiter-unlocks.spec | 27 + ...yb.orig.wait-queues-weak-read-unlocks.spec | 29 + .../isolation/specs/yb.port.async-notify.spec | 90 + .../specs/yb.port.deadlock-hard.spec | 79 + .../specs/yb.port.deadlock-simple.spec | 33 + .../specs/yb.port.deadlock-soft-2.spec | 50 + .../specs/yb.port.deadlock-soft.spec | 50 + .../specs/yb.port.eval-plan-qual.spec | 200 + .../isolation/specs/yb.port.fk-snapshot.spec | 89 + .../yb.port.insert-conflict-do-nothing-2.spec | 49 + .../yb.port.partition-drop-index-locking.spec | 46 + .../isolation/specs/yb.port.timeouts.spec | 43 + src/test/isolation/specscanner.l | 11 + .../yb_isolation_object_locking_schedule | 12 + .../isolation/yb_isolation_txn_ddl_schedule | 2 + .../isolation/yb_pg_async_isolation_schedule | 1 + .../yb_pg_isolation_advisory_lock_schedule | 3 + .../isolation/yb_pg_isolation_lock_schedule | 8 + src/test/isolation/yb_pg_isolation_schedule | 57 + src/test/isolation/yb_wait_queues_schedule | 18 + ...wait_queues_schedule_skip_prefix_locks_off | 17 + .../isolation/yb_without_wait_queues_schedule | 9 + ...wait_queues_schedule_skip_prefix_locks_off | 8 + .../expected/yb.port.dummy_seclabel.out | 135 + .../sql/yb.port.dummy_seclabel.sql | 121 + src/test/modules/dummy_seclabel/yb_schedule | 1 + .../expected/yb.port.rolenames.out | 1098 + .../unsafe_tests/sql/yb.port.rolenames.sql | 513 + src/test/modules/unsafe_tests/yb_schedule | 1 + src/test/regress/GNUmakefile | 2 + src/test/regress/README | 18 + .../regress/data/airport-codes-corrupt.csv | 10001 +++ src/test/regress/data/airport-codes.csv | 10000 +++ src/test/regress/data/all-airport-codes.csv | 55114 ++++++++++++++++ src/test/regress/data/yb_catcache_keys.data | 3333 + .../regress/expected/yb.depd.create_index.out | 55 + .../regress/expected/yb.depd.float8_tbl.out | 19 + .../regress/expected/yb.depd.int4_tbl.out | 19 + .../regress/expected/yb.depd.int8_tbl.out | 19 + src/test/regress/expected/yb.depd.onek.out | 47 + src/test/regress/expected/yb.depd.onek2.out | 29 + .../expected/yb.depd.part_test_int4_ops.out | 17 + src/test/regress/expected/yb.depd.person.out | 21 + src/test/regress/expected/yb.depd.tenk1.out | 51 + src/test/regress/expected/yb.depd.tenk2.out | 24 + .../regress/expected/yb.orig.aggregates.out | 3322 + .../expected/yb.orig.alter_operator.out | 49 + .../regress/expected/yb.orig.alter_table.out | 522 + .../expected/yb.orig.alter_table_rewrite.out | 1482 + .../regress/expected/yb.orig.alter_type.out | 179 + src/test/regress/expected/yb.orig.analyze.out | 304 + .../yb.orig.analyze_system_tables.out | 63 + src/test/regress/expected/yb.orig.async.out | 10 + .../regress/expected/yb.orig.base_type.out | 575 + .../expected/yb.orig.beta_extensions.out | 10 + .../expected/yb.orig.bitmap_scan_plan.out | 227 + .../regress/expected/yb.orig.bitmap_scans.out | 2057 + .../expected/yb.orig.bitmap_scans_colo.out | 2084 + .../yb.orig.bitmap_scans_distinct.out | 256 + .../expected/yb.orig.bitmap_scans_flags.out | 174 + .../expected/yb.orig.bitmap_scans_joins.out | 1034 + .../expected/yb.orig.bitmap_scans_system.out | 123 + src/test/regress/expected/yb.orig.boolean.out | 77 + .../yb.orig.buffering_with_sql_errors.out | 835 + src/test/regress/expected/yb.orig.bytea.out | 133 + .../regress/expected/yb.orig.cacheinfo.out | 90 + .../yb.orig.catalog_cache_hashing.out | 13596 ++++ .../expected/yb.orig.catalog_version.out | 1285 + .../yb.orig.catalog_version_increment.out | 178 + src/test/regress/expected/yb.orig.char.out | 205 + .../expected/yb.orig.collate.icu.utf8.out | 787 + ...orig.colocated_tables_with_tablespaces.out | 704 + .../regress/expected/yb.orig.compound_key.out | 77 + .../expected/yb.orig.copy_55k_rows.out | 21 + .../expected/yb.orig.create_function.out | 41 + .../regress/expected/yb.orig.create_index.out | 1727 + ...reate_index_large_table_backfill_index.out | 11 + .../yb.orig.create_index_update_reltuples.out | 704 + .../expected/yb.orig.create_language.out | 20 + .../expected/yb.orig.create_large_table.out | 16 + .../yb.orig.create_large_table_indexes.out | 32 + ...b.orig.create_large_table_tablet_split.out | 16 + .../regress/expected/yb.orig.create_table.out | 846 + ....orig.create_table_55k_secondary_index.out | 70 + .../expected/yb.orig.create_table_like.out | 520 + .../yb.orig.create_table_scattering_keys.out | 19 + .../regress/expected/yb.orig.create_type.out | 436 + src/test/regress/expected/yb.orig.ctid.out | 1213 + src/test/regress/expected/yb.orig.cursor.out | 180 + .../yb.orig.cursor_preserve_in_txn_limit.out | 40 + .../expected/yb.orig.ddl_exceptions.out | 90 + .../expected/yb.orig.ddl_rc_rollback.out | 100 + .../expected/yb.orig.ddl_savepoint.out | 401 + .../expected/yb.orig.ddl_savepoint_toggle.out | 93 + .../expected/yb.orig.ddl_txn_block.out | 596 + .../regress/expected/yb.orig.ddl_txn_copy.out | 36 + .../yb.orig.ddl_txn_event_triggers.out | 226 + .../regress/expected/yb.orig.ddl_txn_fk.out | 63 + .../expected/yb.orig.ddl_txn_indices.out | 83 + .../expected/yb.orig.ddl_txn_isolation.out | 291 + .../expected/yb.orig.ddl_txn_procedures.out | 88 + .../yb.orig.ddl_txn_recursive_functions.out | 126 + .../expected/yb.orig.ddl_txn_triggers.out | 225 + .../expected/yb.orig.ddl_txn_visibility.out | 85 + src/test/regress/expected/yb.orig.depend.out | 20 + .../regress/expected/yb.orig.dependency.out | 28 + .../yb.orig.distinct_pushdown_base.out | 437 + .../yb.orig.distinct_pushdown_join.out | 814 + .../yb.orig.distinct_pushdown_pred.out | 988 + src/test/regress/expected/yb.orig.dml.out | 136 + .../yb.orig.dml_derived_equalities.out | 407 + .../expected/yb.orig.dml_insert_conflict.out | 122 + .../yb.orig.dml_insert_conflict_1.out | 126 + .../expected/yb.orig.dml_key_pushdown.out | 532 + .../regress/expected/yb.orig.dml_pushdown.out | 3196 + .../expected/yb.orig.dml_read_time.out | 90 + .../regress/expected/yb.orig.dml_scankey.out | 200 + .../expected/yb.orig.dml_single_row.out | 3309 + .../expected/yb.orig.dml_systable_scan.out | 169 + .../regress/expected/yb.orig.drop_table.out | 22 + src/test/regress/expected/yb.orig.errors.out | 286 + .../expected/yb.orig.event_trigger.out | 119 + src/test/regress/expected/yb.orig.explain.out | 330 + .../yb.orig.explicit_row_lock_batching.out | 1440 + .../yb.orig.explicit_row_lock_planning.out | 587 + .../regress/expected/yb.orig.extensions.out | 252 + .../expected/yb.orig.feature_alter_rename.out | 113 + .../expected/yb.orig.feature_alter_table.out | 960 + .../expected/yb.orig.feature_colocation.out | 1348 + .../expected/yb.orig.feature_config.out | 317 + .../regress/expected/yb.orig.feature_copy.out | 589 + .../expected/yb.orig.feature_copy_1.out | 592 + .../expected/yb.orig.feature_copyselect.out | 161 + .../regress/expected/yb.orig.feature_db.out | 67 + .../expected/yb.orig.feature_delete.out | 57 + .../expected/yb.orig.feature_hash_types.out | 180 + .../expected/yb.orig.feature_insert.out | 471 + .../yb.orig.feature_legacy_colocation.out | 793 + .../expected/yb.orig.feature_pk_split.out | 609 + .../yb.orig.feature_pk_split_desc.out | 658 + .../expected/yb.orig.feature_range_types.out | 216 + .../expected/yb.orig.feature_select.out | 334 + .../regress/expected/yb.orig.feature_temp.out | 740 + .../expected/yb.orig.feature_types.out | 152 + .../expected/yb.orig.feature_update.out | 182 + .../regress/expected/yb.orig.fetch_limits.out | 503 + .../expected/yb.orig.fetch_limits_joins.out | 405 + .../regress/expected/yb.orig.foreign_data.out | 15 + .../regress/expected/yb.orig.foreign_key.out | 893 + .../regress/expected/yb.orig.function.out | 64 + ....orig.functional_index_multiple_column.out | 656 + .../expected/yb.orig.functional_indexes.out | 761 + .../regress/expected/yb.orig.generated.out | 1022 + ....orig.get_current_transaction_priority.out | 262 + .../yb.orig.get_range_split_clause.out | 815 + src/test/regress/expected/yb.orig.gin.out | 653 + src/test/regress/expected/yb.orig.guc.out | 355 + .../regress/expected/yb.orig.hash_func.out | 333 + .../expected/yb.orig.hash_in_queries.out | 1264 + .../expected/yb.orig.hdr_percentile.out | 95 + src/test/regress/expected/yb.orig.hints.out | 2216 + .../expected/yb.orig.index_including.out | 16 + .../yb.orig.index_only_scan_decoded_pk.out | 1345 + .../expected/yb.orig.index_recheck.out | 742 + .../regress/expected/yb.orig.index_scan.out | 4529 ++ .../expected/yb.orig.index_scan_null_asc.out | 247 + .../yb.orig.index_scan_null_create.out | 9 + .../expected/yb.orig.index_scan_null_hash.out | 265 + .../expected/yb.orig.index_selectivity.out | 396 + .../expected/yb.orig.indexing_order.out | 421 + src/test/regress/expected/yb.orig.inherit.out | 75 + .../expected/yb.orig.insert_on_conflict.out | 1388 + .../expected/yb.orig.insert_on_conflict_1.out | 1406 + .../yb.orig.insert_on_conflict_multiplan.out | 1098 + ...yb.orig.insert_on_conflict_multiplan_2.out | 1033 + ...yb.orig.insert_on_conflict_multiplan_3.out | 1031 + ...yb.orig.insert_on_conflict_multiplan_9.out | 1020 + .../yb.orig.insert_on_conflict_with.out | 207 + .../yb.orig.insert_on_conflict_with_1.out | 161 + .../yb.orig.insert_on_conflict_with_2.out | 144 + ...b.orig.insert_on_conflict_with_explain.out | 275 + ...orig.insert_on_conflict_with_explain_1.out | 282 + ...orig.insert_on_conflict_with_explain_2.out | 280 + ...orig.insert_on_conflict_with_explain_3.out | 280 + .../yb.orig.insert_on_conflict_with_spi.out | 267 + .../yb.orig.insert_on_conflict_with_spi_1.out | 267 + .../yb.orig.insert_on_conflict_with_spi_2.out | 216 + .../regress/expected/yb.orig.int2_array.out | 43 + .../regress/expected/yb.orig.int4_array.out | 43 + .../yb.orig.internal_subtransactions.out | 226 + .../regress/expected/yb.orig.interval.out | 56 + .../expected/yb.orig.is_local_table.out | 403 + .../expected/yb.orig.join_batching.out | 3276 + .../yb.orig.join_batching_parallel.out | 238 + .../expected/yb.orig.join_batching_plans.out | 716 + .../regress/expected/yb.orig.large_copy.out | 7 + .../expected/yb.orig.large_copy_abort.out | 38 + .../expected/yb.orig.large_copy_ddl.out | 17 + .../expected/yb.orig.legacy_join_plans.out | 3298 + .../expected/yb.orig.levenshtein_pushdown.out | 384 + .../expected/yb.orig.local_tablets.out | 17 + .../expected/yb.orig.lock_row_for_update.out | 115 + .../yb.orig.lock_row_for_update_1.out | 111 + .../regress/expected/yb.orig.lock_status.out | 550 + .../expected/yb.orig.lock_status_1.out | 583 + .../expected/yb.orig.lock_status_2.out | 583 + src/test/regress/expected/yb.orig.matview.out | 634 + .../regress/expected/yb.orig.matview_1.out | 636 + .../expected/yb.orig.merge_scan_colocated.out | 5406 ++ .../expected/yb.orig.merge_scan_derive.out | 1909 + .../expected/yb.orig.merge_scan_hash.out | 632 + .../expected/yb.orig.merge_scan_join.out | 1012 + .../expected/yb.orig.merge_scan_partition.out | 549 + .../expected/yb.orig.merge_scan_range.out | 5443 ++ .../expected/yb.orig.merge_scan_setup.out | 165 + .../expected/yb.orig.merge_scan_temp.out | 64 + src/test/regress/expected/yb.orig.money.out | 208 + .../expected/yb.orig.no_explicit_target.out | 452 + .../regress/expected/yb.orig.non_txn_copy.out | 143 + src/test/regress/expected/yb.orig.numeric.out | 65 + .../yb.orig.obj_properties_functions.out | 59 + src/test/regress/expected/yb.orig.oid.out | 119 + .../expected/yb.orig.operator_class.out | 81 + .../expected/yb.orig.optimizer_guc.out | 587 + .../expected/yb.orig.parallel_append.out | 220 + .../yb.orig.parallel_big_colocated.out | 220 + .../expected/yb.orig.parallel_colocated.out | 1275 + .../expected/yb.orig.parallel_hashed.out | 954 + .../expected/yb.orig.parallel_plans.out | 418 + .../expected/yb.orig.parallel_ranged.out | 1291 + .../yb.orig.parallel_snapshot_switching.out | 331 + .../expected/yb.orig.partition_aggregate.out | 479 + .../expected/yb.orig.partition_alter.out | 61 + .../regress/expected/yb.orig.partition_fk.out | 78 + .../yb.orig.partition_out_of_order.out | 276 + .../regress/expected/yb.orig.partition_pk.out | 278 + .../expected/yb.orig.partition_prune.out | 138 + .../yb.orig.partition_prune_plancache.out | 263 + .../yb.orig.perf_secondary_index_scan.out | 242 + src/test/regress/expected/yb.orig.pg_stat.out | 728 + .../expected/yb.orig.pg_stat_backend.out | 106 + .../yb.orig.planner_base_scans_cost_model.out | 177 + ...lanner_base_scans_cost_model_colocated.out | 89 + .../yb.orig.planner_heuristic_selectivity.out | 175 + .../expected/yb.orig.planner_indexes.out | 467 + .../expected/yb.orig.planner_join_order.out | 226 + .../expected/yb.orig.planner_joins.out | 589 + .../yb.orig.planner_size_estimates.out | 422 + ...orig.planner_taqo_seek_next_estimation.out | 652 + .../yb.orig.planner_taqo_tuning_tests.out | 2611 + src/test/regress/expected/yb.orig.plpgsql.out | 554 + src/test/regress/expected/yb.orig.profile.out | 170 + .../expected/yb.orig.profile_permissions.out | 67 + .../regress/expected/yb.orig.publication.out | 91 + .../yb.orig.query_consistent_snapshot.out | 177 + src/test/regress/expected/yb.orig.reindex.out | 706 + .../expected/yb.orig.replica_identity.out | 52 + .../expected/yb.orig.replication_slot.out | 128 + .../expected/yb.orig.reset_analyze.out | 598 + .../regress/expected/yb.orig.role_profile.out | 155 + src/test/regress/expected/yb.orig.roles.out | 108 + .../expected/yb.orig.row_estimates.out | 603 + .../regress/expected/yb.orig.rowsecurity.out | 43 + src/test/regress/expected/yb.orig.schema.out | 205 + .../expected/yb.orig.secondary_index_scan.out | 223 + src/test/regress/expected/yb.orig.select.out | 829 + .../regress/expected/yb.orig.select_large.out | 10710 +++ .../expected/yb.orig.select_large_abort.out | 10 + .../expected/yb.orig.select_large_ddl.out | 18 + .../yb.orig.select_large_index_backfill.out | 153 + .../expected/yb.orig.select_no_pushdown.out | 778 + .../expected/yb.orig.select_parallel.out | 848 + src/test/regress/expected/yb.orig.stat.out | 243 + .../expected/yb.orig.system_targets.out | 49 + .../regress/expected/yb.orig.tablegroup.out | 429 + .../expected/yb.orig.tablegroup_alter.out | 71 + .../yb.orig.tablegroup_colocation_id.out | 301 + .../yb.orig.tablegroup_deprecation.out | 49 + .../expected/yb.orig.tablegroup_dml.out | 342 + .../yb.orig.tablegroup_partitioned_table.out | 37 + .../yb.orig.tablegroup_permissions.out | 156 + .../regress/expected/yb.orig.tablespaces.out | 1200 + .../expected/yb.orig.tablet_metadata.out | 88 + src/test/regress/expected/yb.orig.time.out | 79 + .../regress/expected/yb.orig.transactions.out | 57 + .../regress/expected/yb.orig.triggers.out | 590 + .../regress/expected/yb.orig.truncate.out | 127 + .../expected/yb.orig.ts_remote_filter.out | 311 + src/test/regress/expected/yb.orig.tsdicts.out | 141 + .../regress/expected/yb.orig.update_large.out | 5 + .../expected/yb.orig.update_optimize_base.out | 2141 + .../yb.orig.update_optimize_index_updates.out | 1865 + .../yb.orig.update_optimize_indices.out | 1744 + .../yb.orig.update_optimize_triggers.out | 405 + src/test/regress/expected/yb.orig.util.out | 13 + src/test/regress/expected/yb.orig.view.out | 125 + src/test/regress/expected/yb.orig.with.out | 98 + .../yb.orig.yb_get_tablet_for_key.out | 432 + .../regress/expected/yb.orig.yb_hash_code.out | 1568 + .../expected/yb.orig.yb_index_check.out | 349 + .../expected/yb.orig.yb_index_check_1.out | 348 + src/test/regress/expected/yb.orig.ybgin.out | 681 + .../expected/yb.orig.ybgin_cost_estimate.out | 210 + .../regress/expected/yb.orig.ybgin_misc.out | 527 + .../regress/expected/yb.orig.ybgin_nulls.out | 12 + .../expected/yb.orig.ybgin_operators.out | 716 + .../expected/yb.orig.ybgin_pushdown.out | 282 + .../expected/yb.orig.ybgin_search_modes.out | 23 + .../expected/yb.port.advisory_lock.out | 381 + .../regress/expected/yb.port.aggregates.out | 2862 + .../expected/yb.port.alter_generic.out | 743 + .../regress/expected/yb.port.alter_table.out | 4072 ++ src/test/regress/expected/yb.port.arrays.out | 2442 + src/test/regress/expected/yb.port.boolean.out | 476 + src/test/regress/expected/yb.port.box.out | 645 + src/test/regress/expected/yb.port.case.out | 422 + .../expected/yb.port.collate.icu.utf8.out | 1211 + src/test/regress/expected/yb.port.collate.out | 776 + .../regress/expected/yb.port.combocid.out | 173 + .../regress/expected/yb.port.compression.out | 432 + .../regress/expected/yb.port.constraints.out | 174 + .../regress/expected/yb.port.conversion.out | 25 + src/test/regress/expected/yb.port.copy.out | 242 + src/test/regress/expected/yb.port.copy2.out | 675 + .../regress/expected/yb.port.create_am.out | 109 + .../expected/yb.port.create_function_c.out | 45 + .../expected/yb.port.create_function_sql.out | 743 + .../regress/expected/yb.port.create_index.out | 318 + .../regress/expected/yb.port.create_misc.out | 483 + .../regress/expected/yb.port.create_role.out | 145 + .../regress/expected/yb.port.create_table.out | 1106 + .../expected/yb.port.create_table_like.out | 532 + .../regress/expected/yb.port.create_type.out | 382 + .../regress/expected/yb.port.create_view.out | 19 + .../expected/yb.port.drop_if_exists.out | 359 + .../expected/yb.port.drop_operator.out | 66 + src/test/regress/expected/yb.port.enum.out | 695 + src/test/regress/expected/yb.port.errors.out | 457 + .../expected/yb.port.event_trigger.out | 479 + .../regress/expected/yb.port.expressions.out | 77 + .../regress/expected/yb.port.fast_default.out | 829 + src/test/regress/expected/yb.port.float4.out | 959 + src/test/regress/expected/yb.port.float8.out | 1384 + .../regress/expected/yb.port.foreign_data.out | 2152 + .../regress/expected/yb.port.foreign_key.out | 2387 + .../regress/expected/yb.port.generated.out | 1094 + .../regress/expected/yb.port.geometry.out | 5315 ++ src/test/regress/expected/yb.port.gin.out | 38 + .../regress/expected/yb.port.groupingsets.out | 1790 + .../regress/expected/yb.port.horology.out | 3443 + .../regress/expected/yb.port.identity.out | 360 + .../expected/yb.port.incremental_sort.out | 1434 + .../expected/yb.port.index_including.out | 415 + .../regress/expected/yb.port.indexing.out | 1545 + .../expected/yb.port.indirect_toast.out | 11 + src/test/regress/expected/yb.port.inet.out | 798 + src/test/regress/expected/yb.port.inherit.out | 2193 + src/test/regress/expected/yb.port.insert.out | 969 + .../expected/yb.port.insert_conflict.out | 874 + .../expected/yb.port.insert_conflict_1.out | 846 + src/test/regress/expected/yb.port.int8.out | 922 + src/test/regress/expected/yb.port.join.out | 2195 + src/test/regress/expected/yb.port.jsonb.out | 5519 ++ src/test/regress/expected/yb.port.limit.out | 591 + src/test/regress/expected/yb.port.lock.out | 302 + src/test/regress/expected/yb.port.macaddr.out | 161 + .../regress/expected/yb.port.macaddr8.out | 355 + src/test/regress/expected/yb.port.matview.out | 656 + src/test/regress/expected/yb.port.misc.out | 399 + .../expected/yb.port.misc_functions.out | 534 + .../expected/yb.port.multirangetypes.out | 3330 + .../regress/expected/yb.port.namespace.out | 125 + src/test/regress/expected/yb.port.numeric.out | 3363 + .../regress/expected/yb.port.numeric_big.out | 2103 + .../expected/yb.port.partition_aggregate.out | 1539 + .../expected/yb.port.partition_join.out | 2939 + .../expected/yb.port.partition_prune.out | 3600 + src/test/regress/expected/yb.port.pg_lsn.out | 224 + src/test/regress/expected/yb.port.plpgsql.out | 5962 ++ src/test/regress/expected/yb.port.polygon.out | 299 + src/test/regress/expected/yb.port.portals.out | 1252 + src/test/regress/expected/yb.port.prepare.out | 177 + .../regress/expected/yb.port.privileges.out | 2558 + .../regress/expected/yb.port.publication.out | 305 + .../regress/expected/yb.port.rangetypes.out | 1151 + .../expected/yb.port.replica_identity.out | 89 + .../regress/expected/yb.port.rowsecurity.out | 4391 ++ .../regress/expected/yb.port.rowtypes.out | 1229 + src/test/regress/expected/yb.port.rules.out | 2893 + src/test/regress/expected/yb.port.select.out | 952 + .../expected/yb.port.select_distinct.out | 245 + .../regress/expected/yb.port.sequence.out | 855 + .../regress/expected/yb.port.stats_ext.out | 3274 + .../regress/expected/yb.port.stats_import.out | 1327 + src/test/regress/expected/yb.port.strings.out | 2665 + .../regress/expected/yb.port.subselect.out | 30 + .../regress/expected/yb.port.test_setup.out | 232 + .../regress/expected/yb.port.test_setup_1.out | 262 + src/test/regress/expected/yb.port.text.out | 438 + .../regress/expected/yb.port.transactions.out | 1109 + .../regress/expected/yb.port.triggers.out | 3419 + .../regress/expected/yb.port.truncate.out | 605 + src/test/regress/expected/yb.port.tsdicts.out | 620 + src/test/regress/expected/yb.port.tsearch.out | 2101 + src/test/regress/expected/yb.port.update.out | 757 + src/test/regress/expected/yb.port.uuid.out | 137 + src/test/regress/expected/yb.port.varchar.out | 124 + src/test/regress/expected/yb.port.window.out | 3847 ++ src/test/regress/expected/yb.port.with.out | 3429 + src/test/regress/expected/yb.port.xmlmap.out | 1294 + src/test/regress/pg_regress.c | 40 +- src/test/regress/pg_regress_main.c | 30 +- src/test/regress/regress.c | 202 + src/test/regress/sql/yb.depd.create_index.sql | 60 + src/test/regress/sql/yb.depd.float8_tbl.sql | 21 + src/test/regress/sql/yb.depd.int4_tbl.sql | 21 + src/test/regress/sql/yb.depd.int8_tbl.sql | 21 + src/test/regress/sql/yb.depd.onek.sql | 53 + src/test/regress/sql/yb.depd.onek2.sql | 31 + .../sql/yb.depd.part_test_int4_ops.sql | 20 + src/test/regress/sql/yb.depd.person.sql | 25 + src/test/regress/sql/yb.depd.tenk1.sql | 58 + src/test/regress/sql/yb.depd.tenk2.sql | 31 + src/test/regress/sql/yb.orig.aggregates.sql | 380 + .../regress/sql/yb.orig.alter_operator.sql | 43 + src/test/regress/sql/yb.orig.alter_table.sql | 241 + .../sql/yb.orig.alter_table_rewrite.sql | 716 + src/test/regress/sql/yb.orig.alter_type.sql | 77 + src/test/regress/sql/yb.orig.analyze.sql | 178 + .../sql/yb.orig.analyze_system_tables.sql | 63 + src/test/regress/sql/yb.orig.async.sql | 12 + src/test/regress/sql/yb.orig.base_type.sql | 418 + .../regress/sql/yb.orig.beta_extensions.sql | 7 + .../regress/sql/yb.orig.bitmap_scan_plan.sql | 108 + src/test/regress/sql/yb.orig.bitmap_scans.sql | 441 + .../regress/sql/yb.orig.bitmap_scans_colo.sql | 443 + .../sql/yb.orig.bitmap_scans_distinct.sql | 54 + .../sql/yb.orig.bitmap_scans_flags.sql | 44 + .../sql/yb.orig.bitmap_scans_joins.sql | 263 + .../sql/yb.orig.bitmap_scans_system.sql | 38 + src/test/regress/sql/yb.orig.boolean.sql | 31 + .../sql/yb.orig.buffering_with_sql_errors.sql | 539 + src/test/regress/sql/yb.orig.bytea.sql | 51 + src/test/regress/sql/yb.orig.cacheinfo.sql | 1 + .../sql/yb.orig.catalog_cache_hashing.sql | 211 + .../regress/sql/yb.orig.catalog_version.sql | 508 + .../sql/yb.orig.catalog_version_increment.sql | 90 + src/test/regress/sql/yb.orig.char.sql | 84 + .../regress/sql/yb.orig.collate.icu.utf8.sql | 363 + ...orig.colocated_tables_with_tablespaces.sql | 372 + src/test/regress/sql/yb.orig.compound_key.sql | 21 + .../regress/sql/yb.orig.copy_55k_rows.sql | 13 + .../regress/sql/yb.orig.create_function.sql | 45 + src/test/regress/sql/yb.orig.create_index.sql | 539 + ...reate_index_large_table_backfill_index.sql | 14 + .../yb.orig.create_index_update_reltuples.sql | 422 + .../regress/sql/yb.orig.create_language.sql | 11 + .../sql/yb.orig.create_large_table.sql | 16 + .../yb.orig.create_large_table_indexes.sql | 34 + ...b.orig.create_large_table_tablet_split.sql | 16 + src/test/regress/sql/yb.orig.create_table.sql | 642 + ....orig.create_table_55k_secondary_index.sql | 47 + .../regress/sql/yb.orig.create_table_like.sql | 214 + .../yb.orig.create_table_scattering_keys.sql | 20 + src/test/regress/sql/yb.orig.create_type.sql | 193 + src/test/regress/sql/yb.orig.ctid.sql | 372 + src/test/regress/sql/yb.orig.cursor.sql | 138 + .../yb.orig.cursor_preserve_in_txn_limit.sql | 24 + .../regress/sql/yb.orig.ddl_exceptions.sql | 92 + .../regress/sql/yb.orig.ddl_rc_rollback.sql | 92 + .../regress/sql/yb.orig.ddl_savepoint.sql | 235 + .../sql/yb.orig.ddl_savepoint_toggle.sql | 89 + .../regress/sql/yb.orig.ddl_txn_block.sql | 407 + src/test/regress/sql/yb.orig.ddl_txn_copy.sql | 35 + .../sql/yb.orig.ddl_txn_event_triggers.sql | 199 + src/test/regress/sql/yb.orig.ddl_txn_fk.sql | 68 + .../regress/sql/yb.orig.ddl_txn_indices.sql | 81 + .../regress/sql/yb.orig.ddl_txn_isolation.sql | 207 + .../sql/yb.orig.ddl_txn_procedures.sql | 93 + .../yb.orig.ddl_txn_recursive_functions.sql | 114 + .../regress/sql/yb.orig.ddl_txn_triggers.sql | 199 + .../sql/yb.orig.ddl_txn_visibility.sql | 80 + src/test/regress/sql/yb.orig.depend.sql | 10 + src/test/regress/sql/yb.orig.dependency.sql | 24 + .../sql/yb.orig.distinct_pushdown_base.sql | 152 + .../sql/yb.orig.distinct_pushdown_join.sql | 163 + .../sql/yb.orig.distinct_pushdown_pred.sql | 325 + src/test/regress/sql/yb.orig.dml.sql | 122 + .../sql/yb.orig.dml_derived_equalities.sql | 173 + .../sql/yb.orig.dml_insert_conflict.sql | 85 + .../regress/sql/yb.orig.dml_key_pushdown.sql | 118 + src/test/regress/sql/yb.orig.dml_pushdown.sql | 1075 + .../regress/sql/yb.orig.dml_read_time.sql | 61 + src/test/regress/sql/yb.orig.dml_scankey.sql | 76 + .../regress/sql/yb.orig.dml_single_row.sql | 1053 + .../regress/sql/yb.orig.dml_systable_scan.sql | 103 + src/test/regress/sql/yb.orig.drop_table.sql | 17 + src/test/regress/sql/yb.orig.errors.sql | 102 + .../regress/sql/yb.orig.event_trigger.sql | 102 + src/test/regress/sql/yb.orig.explain.sql | 119 + .../yb.orig.explicit_row_lock_batching.sql | 267 + .../yb.orig.explicit_row_lock_planning.sql | 184 + src/test/regress/sql/yb.orig.extensions.sql | 160 + .../sql/yb.orig.feature_alter_rename.sql | 39 + .../sql/yb.orig.feature_alter_table.sql | 526 + .../sql/yb.orig.feature_colocation.sql | 554 + .../regress/sql/yb.orig.feature_config.sql | 123 + src/test/regress/sql/yb.orig.feature_copy.sql | 464 + .../sql/yb.orig.feature_copyselect.sql | 96 + src/test/regress/sql/yb.orig.feature_db.sql | 45 + .../regress/sql/yb.orig.feature_delete.sql | 43 + .../sql/yb.orig.feature_hash_types.sql | 133 + .../regress/sql/yb.orig.feature_insert.sql | 420 + .../sql/yb.orig.feature_legacy_colocation.sql | 303 + .../regress/sql/yb.orig.feature_pk_split.sql | 195 + .../sql/yb.orig.feature_pk_split_desc.sql | 200 + .../sql/yb.orig.feature_range_types.sql | 169 + .../regress/sql/yb.orig.feature_select.sql | 125 + src/test/regress/sql/yb.orig.feature_temp.sql | 539 + .../regress/sql/yb.orig.feature_types.sql | 133 + .../regress/sql/yb.orig.feature_update.sql | 142 + src/test/regress/sql/yb.orig.fetch_limits.sql | 94 + .../sql/yb.orig.fetch_limits_joins.sql | 57 + src/test/regress/sql/yb.orig.foreign_data.sql | 16 + src/test/regress/sql/yb.orig.foreign_key.sql | 575 + src/test/regress/sql/yb.orig.function.sql | 58 + ....orig.functional_index_multiple_column.sql | 152 + .../sql/yb.orig.functional_indexes.sql | 229 + src/test/regress/sql/yb.orig.generated.sql | 192 + ....orig.get_current_transaction_priority.sql | 133 + .../sql/yb.orig.get_range_split_clause.sql | 539 + src/test/regress/sql/yb.orig.gin.sql | 193 + src/test/regress/sql/yb.orig.guc.sql | 173 + src/test/regress/sql/yb.orig.hash_func.sql | 231 + .../regress/sql/yb.orig.hash_in_queries.sql | 224 + .../regress/sql/yb.orig.hdr_percentile.sql | 18 + src/test/regress/sql/yb.orig.hints.sql | 491 + .../regress/sql/yb.orig.index_including.sql | 9 + .../yb.orig.index_only_scan_decoded_pk.sql | 507 + .../regress/sql/yb.orig.index_recheck.sql | 188 + src/test/regress/sql/yb.orig.index_scan.sql | 631 + .../sql/yb.orig.index_scan_null_asc.sql | 15 + .../sql/yb.orig.index_scan_null_create.sql | 11 + .../sql/yb.orig.index_scan_null_hash.sql | 15 + .../regress/sql/yb.orig.index_selectivity.sql | 101 + .../regress/sql/yb.orig.indexing_order.sql | 232 + src/test/regress/sql/yb.orig.inherit.sql | 18 + .../sql/yb.orig.insert_on_conflict.sql | 608 + .../yb.orig.insert_on_conflict_multiplan.sql | 646 + .../sql/yb.orig.insert_on_conflict_with.sql | 62 + ...b.orig.insert_on_conflict_with_explain.sql | 58 + .../yb.orig.insert_on_conflict_with_spi.sql | 153 + src/test/regress/sql/yb.orig.int2_array.sql | 24 + src/test/regress/sql/yb.orig.int4_array.sql | 24 + .../sql/yb.orig.internal_subtransactions.sql | 170 + src/test/regress/sql/yb.orig.interval.sql | 29 + .../regress/sql/yb.orig.is_local_table.sql | 147 + .../regress/sql/yb.orig.join_batching.sql | 1161 + .../sql/yb.orig.join_batching_parallel.sql | 201 + .../sql/yb.orig.join_batching_plans.sql | 306 + src/test/regress/sql/yb.orig.large_copy.sql | 9 + .../regress/sql/yb.orig.large_copy_abort.sql | 32 + .../regress/sql/yb.orig.large_copy_ddl.sql | 16 + .../regress/sql/yb.orig.legacy_join_plans.sql | 149 + .../sql/yb.orig.levenshtein_pushdown.sql | 180 + .../regress/sql/yb.orig.local_tablets.sql | 12 + .../sql/yb.orig.lock_row_for_update.sql | 77 + src/test/regress/sql/yb.orig.lock_status.sql | 266 + src/test/regress/sql/yb.orig.matview.sql | 356 + .../sql/yb.orig.merge_scan_colocated.sql | 657 + .../regress/sql/yb.orig.merge_scan_derive.sql | 223 + .../regress/sql/yb.orig.merge_scan_hash.sql | 65 + .../regress/sql/yb.orig.merge_scan_join.sql | 66 + .../sql/yb.orig.merge_scan_partition.sql | 41 + .../regress/sql/yb.orig.merge_scan_range.sql | 686 + .../regress/sql/yb.orig.merge_scan_setup.sql | 165 + .../regress/sql/yb.orig.merge_scan_temp.sql | 24 + src/test/regress/sql/yb.orig.money.sql | 98 + .../sql/yb.orig.no_explicit_target.sql | 79 + src/test/regress/sql/yb.orig.non_txn_copy.sql | 129 + src/test/regress/sql/yb.orig.numeric.sql | 29 + .../sql/yb.orig.obj_properties_functions.sql | 45 + src/test/regress/sql/yb.orig.oid.sql | 38 + .../regress/sql/yb.orig.operator_class.sql | 79 + .../regress/sql/yb.orig.optimizer_guc.sql | 317 + .../regress/sql/yb.orig.parallel_append.sql | 69 + .../sql/yb.orig.parallel_big_colocated.sql | 79 + .../sql/yb.orig.parallel_colocated.sql | 378 + .../regress/sql/yb.orig.parallel_hashed.sql | 279 + .../regress/sql/yb.orig.parallel_plans.sql | 271 + .../regress/sql/yb.orig.parallel_ranged.sql | 367 + .../yb.orig.parallel_snapshot_switching.sql | 60 + .../sql/yb.orig.partition_aggregate.sql | 131 + .../regress/sql/yb.orig.partition_alter.sql | 37 + src/test/regress/sql/yb.orig.partition_fk.sql | 59 + .../sql/yb.orig.partition_out_of_order.sql | 122 + src/test/regress/sql/yb.orig.partition_pk.sql | 123 + .../regress/sql/yb.orig.partition_prune.sql | 47 + .../sql/yb.orig.partition_prune_plancache.sql | 81 + .../sql/yb.orig.perf_secondary_index_scan.sql | 128 + src/test/regress/sql/yb.orig.pg_stat.sql | 228 + .../regress/sql/yb.orig.pg_stat_backend.sql | 73 + .../yb.orig.planner_base_scans_cost_model.sql | 110 + ...lanner_base_scans_cost_model_colocated.sql | 35 + .../yb.orig.planner_heuristic_selectivity.sql | 83 + .../regress/sql/yb.orig.planner_indexes.sql | 193 + .../sql/yb.orig.planner_join_order.sql | 71 + .../regress/sql/yb.orig.planner_joins.sql | 261 + .../sql/yb.orig.planner_size_estimates.sql | 26 + ...orig.planner_taqo_seek_next_estimation.sql | 249 + .../sql/yb.orig.planner_taqo_tuning_tests.sql | 888 + src/test/regress/sql/yb.orig.plpgsql.sql | 419 + src/test/regress/sql/yb.orig.profile.sql | 67 + .../sql/yb.orig.profile_permissions.sql | 64 + src/test/regress/sql/yb.orig.publication.sql | 94 + .../sql/yb.orig.query_consistent_snapshot.sql | 60 + src/test/regress/sql/yb.orig.reindex.sql | 331 + .../regress/sql/yb.orig.replica_identity.sql | 30 + .../regress/sql/yb.orig.replication_slot.sql | 60 + .../regress/sql/yb.orig.reset_analyze.sql | 356 + src/test/regress/sql/yb.orig.role_profile.sql | 86 + src/test/regress/sql/yb.orig.roles.sql | 98 + .../regress/sql/yb.orig.row_estimates.sql | 448 + src/test/regress/sql/yb.orig.rowsecurity.sql | 17 + src/test/regress/sql/yb.orig.schema.sql | 112 + .../sql/yb.orig.secondary_index_scan.sql | 102 + src/test/regress/sql/yb.orig.select.sql | 275 + src/test/regress/sql/yb.orig.select_large.sql | 13 + .../sql/yb.orig.select_large_abort.sql | 5 + .../regress/sql/yb.orig.select_large_ddl.sql | 6 + .../yb.orig.select_large_index_backfill.sql | 26 + .../sql/yb.orig.select_no_pushdown.sql | 233 + .../regress/sql/yb.orig.select_parallel.sql | 489 + src/test/regress/sql/yb.orig.stat.sql | 102 + .../regress/sql/yb.orig.system_targets.sql | 18 + src/test/regress/sql/yb.orig.tablegroup.sql | 205 + .../regress/sql/yb.orig.tablegroup_alter.sql | 47 + .../sql/yb.orig.tablegroup_colocation_id.sql | 117 + .../sql/yb.orig.tablegroup_deprecation.sql | 9 + .../regress/sql/yb.orig.tablegroup_dml.sql | 144 + .../yb.orig.tablegroup_partitioned_table.sql | 15 + .../sql/yb.orig.tablegroup_permissions.sql | 148 + src/test/regress/sql/yb.orig.tablespaces.sql | 739 + .../regress/sql/yb.orig.tablet_metadata.sql | 64 + src/test/regress/sql/yb.orig.time.sql | 35 + src/test/regress/sql/yb.orig.transactions.sql | 34 + src/test/regress/sql/yb.orig.triggers.sql | 367 + src/test/regress/sql/yb.orig.truncate.sql | 62 + .../regress/sql/yb.orig.ts_remote_filter.sql | 64 + src/test/regress/sql/yb.orig.tsdicts.sql | 29 + src/test/regress/sql/yb.orig.update_large.sql | 5 + .../sql/yb.orig.update_optimize_base.sql | 475 + .../yb.orig.update_optimize_index_updates.sql | 262 + .../sql/yb.orig.update_optimize_indices.sql | 283 + .../sql/yb.orig.update_optimize_triggers.sql | 159 + src/test/regress/sql/yb.orig.util.sql | 4 + src/test/regress/sql/yb.orig.view.sql | 40 + src/test/regress/sql/yb.orig.with.sql | 44 + .../sql/yb.orig.yb_get_tablet_for_key.sql | 344 + src/test/regress/sql/yb.orig.yb_hash_code.sql | 318 + .../regress/sql/yb.orig.yb_index_check.sql | 214 + src/test/regress/sql/yb.orig.ybgin.sql | 248 + .../sql/yb.orig.ybgin_cost_estimate.sql | 67 + src/test/regress/sql/yb.orig.ybgin_misc.sql | 274 + src/test/regress/sql/yb.orig.ybgin_nulls.sql | 11 + .../regress/sql/yb.orig.ybgin_operators.sql | 282 + .../regress/sql/yb.orig.ybgin_pushdown.sql | 101 + .../sql/yb.orig.ybgin_search_modes.sql | 21 + .../regress/sql/yb.port.advisory_lock.sql | 166 + src/test/regress/sql/yb.port.aggregates.sql | 1289 + .../regress/sql/yb.port.alter_generic.sql | 637 + src/test/regress/sql/yb.port.alter_table.sql | 2726 + src/test/regress/sql/yb.port.arrays.sql | 766 + src/test/regress/sql/yb.port.boolean.sql | 233 + src/test/regress/sql/yb.port.box.sql | 283 + src/test/regress/sql/yb.port.case.sql | 268 + .../regress/sql/yb.port.collate.icu.utf8.sql | 492 + src/test/regress/sql/yb.port.collate.sql | 302 + src/test/regress/sql/yb.port.combocid.sql | 125 + src/test/regress/sql/yb.port.compression.sql | 197 + src/test/regress/sql/yb.port.constraints.sql | 110 + src/test/regress/sql/yb.port.conversion.sql | 23 + src/test/regress/sql/yb.port.copy.sql | 270 + src/test/regress/sql/yb.port.copy2.sql | 472 + src/test/regress/sql/yb.port.create_am.sql | 73 + .../regress/sql/yb.port.create_function_c.sql | 40 + .../sql/yb.port.create_function_sql.sql | 421 + src/test/regress/sql/yb.port.create_index.sql | 258 + src/test/regress/sql/yb.port.create_misc.sql | 260 + src/test/regress/sql/yb.port.create_role.sql | 138 + src/test/regress/sql/yb.port.create_table.sql | 738 + .../regress/sql/yb.port.create_table_like.sql | 223 + src/test/regress/sql/yb.port.create_type.sql | 291 + src/test/regress/sql/yb.port.create_view.sql | 23 + .../regress/sql/yb.port.drop_if_exists.sql | 304 + .../regress/sql/yb.port.drop_operator.sql | 60 + src/test/regress/sql/yb.port.enum.sql | 343 + src/test/regress/sql/yb.port.errors.sql | 381 + .../regress/sql/yb.port.event_trigger.sql | 433 + src/test/regress/sql/yb.port.expressions.sql | 36 + src/test/regress/sql/yb.port.fast_default.sql | 555 + src/test/regress/sql/yb.port.float4.sql | 359 + src/test/regress/sql/yb.port.float8.sql | 505 + src/test/regress/sql/yb.port.foreign_data.sql | 870 + src/test/regress/sql/yb.port.foreign_key.sql | 1716 + src/test/regress/sql/yb.port.generated.sql | 601 + src/test/regress/sql/yb.port.geometry.sql | 539 + src/test/regress/sql/yb.port.gin.sql | 38 + src/test/regress/sql/yb.port.groupingsets.sql | 499 + src/test/regress/sql/yb.port.horology.sql | 603 + src/test/regress/sql/yb.port.identity.sql | 262 + .../regress/sql/yb.port.incremental_sort.sql | 206 + .../regress/sql/yb.port.index_including.sql | 222 + src/test/regress/sql/yb.port.indexing.sql | 781 + .../regress/sql/yb.port.indirect_toast.sql | 14 + src/test/regress/sql/yb.port.inet.sql | 155 + src/test/regress/sql/yb.port.inherit.sql | 1100 + src/test/regress/sql/yb.port.insert.sql | 585 + .../regress/sql/yb.port.insert_conflict.sql | 594 + src/test/regress/sql/yb.port.int8.sql | 249 + src/test/regress/sql/yb.port.join.sql | 374 + src/test/regress/sql/yb.port.jsonb.sql | 1523 + src/test/regress/sql/yb.port.limit.sql | 210 + src/test/regress/sql/yb.port.lock.sql | 210 + src/test/regress/sql/yb.port.macaddr.sql | 44 + src/test/regress/sql/yb.port.macaddr8.sql | 90 + src/test/regress/sql/yb.port.matview.sql | 298 + src/test/regress/sql/yb.port.misc.sql | 280 + .../regress/sql/yb.port.misc_functions.sql | 199 + .../regress/sql/yb.port.multirangetypes.sql | 853 + src/test/regress/sql/yb.port.namespace.sql | 73 + src/test/regress/sql/yb.port.numeric.sql | 1437 + src/test/regress/sql/yb.port.numeric_big.sql | 1366 + .../sql/yb.port.partition_aggregate.sql | 348 + .../regress/sql/yb.port.partition_join.sql | 1335 + .../regress/sql/yb.port.partition_prune.sql | 1237 + src/test/regress/sql/yb.port.pg_lsn.sql | 54 + src/test/regress/sql/yb.port.plpgsql.sql | 4864 ++ src/test/regress/sql/yb.port.polygon.sql | 142 + src/test/regress/sql/yb.port.portals.sql | 563 + src/test/regress/sql/yb.port.prepare.sql | 83 + src/test/regress/sql/yb.port.privileges.sql | 1664 + src/test/regress/sql/yb.port.publication.sql | 193 + src/test/regress/sql/yb.port.rangetypes.sql | 449 + .../regress/sql/yb.port.replica_identity.sql | 39 + src/test/regress/sql/yb.port.rowsecurity.sql | 2088 + src/test/regress/sql/yb.port.rowtypes.sql | 508 + src/test/regress/sql/yb.port.rules.sql | 850 + src/test/regress/sql/yb.port.select.sql | 265 + .../regress/sql/yb.port.select_distinct.sql | 76 + src/test/regress/sql/yb.port.sequence.sql | 428 + src/test/regress/sql/yb.port.stats_ext.sql | 1668 + src/test/regress/sql/yb.port.stats_import.sql | 962 + src/test/regress/sql/yb.port.strings.sql | 855 + src/test/regress/sql/yb.port.subselect.sql | 17 + src/test/regress/sql/yb.port.test_setup.sql | 261 + src/test/regress/sql/yb.port.text.sql | 114 + src/test/regress/sql/yb.port.transactions.sql | 613 + src/test/regress/sql/yb.port.triggers.sql | 2585 + src/test/regress/sql/yb.port.truncate.sql | 353 + src/test/regress/sql/yb.port.tsdicts.sql | 203 + src/test/regress/sql/yb.port.tsearch.sql | 679 + src/test/regress/sql/yb.port.update.sql | 550 + src/test/regress/sql/yb.port.uuid.sql | 81 + src/test/regress/sql/yb.port.varchar.sql | 80 + src/test/regress/sql/yb.port.window.sql | 1302 + src/test/regress/sql/yb.port.with.sql | 1587 + src/test/regress/sql/yb.port.xmlmap.sql | 63 + src/test/regress/yb_analyze_schedule | 7 + src/test/regress/yb_arrays_schedule | 12 + src/test/regress/yb_async_schedule | 2 + src/test/regress/yb_auth_schedule | 5 + src/test/regress/yb_beta_extensions_schedule | 6 + .../yb_big_parallelquery_serial_schedule | 7 + src/test/regress/yb_bitmap_plan_schedule | 6 + src/test/regress/yb_bitmap_scans_schedule | 11 + .../yb_buffering_with_sql_errors_schedule | 6 + src/test/regress/yb_catalog_cache_schedule | 5 + ...colocated_tables_with_tablespaces_schedule | 4 + .../yb_commands/bitmap_hint_iter_Q2.sql | 4 + .../yb_commands/index_scan_null_queries.sql | 60 + src/test/regress/yb_commands/iter_P2.sql | 8 + src/test/regress/yb_commands/iter_P3.sql | 10 + src/test/regress/yb_commands/iter_P4.sql | 12 + src/test/regress/yb_commands/iter_P5.sql | 14 + src/test/regress/yb_commands/iter_Q2.sql | 8 + src/test/regress/yb_commands/iter_Q3.sql | 10 + src/test/regress/yb_commands/iter_Q4.sql | 12 + src/test/regress/yb_commands/iter_Q5.sql | 14 + src/test/regress/yb_commands/iter_R2.sql | 8 + src/test/regress/yb_commands/iter_R3.sql | 10 + src/test/regress/yb_commands/iter_R4.sql | 12 + src/test/regress/yb_commands/iter_R5.sql | 14 + src/test/regress/yb_commands/iter_query.sql | 3 + .../yb_commands/legacy_join_plan_queries.sql | 91 + .../legacy_join_plan_queries_with_costs.sql | 4 + .../regress/yb_commands/merge_scan_setup.sql | 12 + .../parallel_snapshot_switching_query.sql | 11 + ...shot_switching_query_with_gather_merge.sql | 12 + .../yb_commands/parameterized_query.sql | 51 + .../planner_size_estimates_cmds.sql | 62 + src/test/regress/yb_ctid_schedule | 6 + .../yb_cursor_low_prefetching_schedule | 6 + src/test/regress/yb_cursor_schedule | 20 + src/test/regress/yb_ddl_savepoint_schedule | 9 + src/test/regress/yb_ddl_txn_block_schedule | 15 + .../regress/yb_distinct_pushdown_schedule | 8 + src/test/regress/yb_dml_serial_schedule | 7 + src/test/regress/yb_extensions_schedule | 7 + .../regress/yb_feature_colocation_schedule | 6 + .../yb_feature_database_serial_schedule | 8 + .../yb_feature_legacy_colocation_schedule | 6 + .../yb_feature_partition_serial_schedule | 10 + src/test/regress/yb_feature_serial_schedule | 17 + src/test/regress/yb_fetch_limit_schedule | 8 + src/test/regress/yb_foreign_data_schedule | 3 + src/test/regress/yb_foreign_key_schedule | 2 + src/test/regress/yb_gin_schedule | 16 + src/test/regress/yb_hash_in_schedule | 8 + .../regress/yb_hash_index_serial_schedule | 9 + src/test/regress/yb_index_check_schedule | 7 + src/test/regress/yb_index_schedule | 15 + src/test/regress/yb_inherit_schedule | 7 + .../regress/yb_insert_on_conflict_schedule | 8 + src/test/regress/yb_join_plans_schedule | 7 + src/test/regress/yb_join_schedule | 7 + .../yb_large_table_backfill_index_schedule | 11 + .../regress/yb_large_table_serial_schedule | 14 + .../yb_large_table_tablet_split_schedule | 7 + .../regress/yb_levenshtein_pushdown_schedule | 6 + src/test/regress/yb_limit_schedule | 9 + src/test/regress/yb_matview_schedule | 7 + src/test/regress/yb_merge_scan_schedule | 34 + ...yb_misc_catalog_version_increment_schedule | 10 + src/test/regress/yb_misc_independent_schedule | 16 + src/test/regress/yb_misc_serial2_schedule | 6 + src/test/regress/yb_misc_serial3_schedule | 6 + src/test/regress/yb_misc_serial4_schedule | 9 + src/test/regress/yb_misc_serial5_schedule | 7 + src/test/regress/yb_misc_serial_schedule | 22 + src/test/regress/yb_non_txn_copy_schedule | 6 + src/test/regress/yb_parallel_plans_schedule | 6 + .../regress/yb_parallelquery_serial_schedule | 16 + src/test/regress/yb_partition_prune_schedule | 6 + src/test/regress/yb_partitions_schedule | 11 + src/test/regress/yb_percentile_schedule | 6 + src/test/regress/yb_pg_advisory_lock_schedule | 2 + src/test/regress/yb_pg_aggregates_schedule | 12 + src/test/regress/yb_pg_async_schedule | 2 + src/test/regress/yb_pg_auth_schedule | 14 + src/test/regress/yb_pg_constraints_schedule | 2 + src/test/regress/yb_pg_copy_schedule | 10 + src/test/regress/yb_pg_dml_serial_schedule | 7 + src/test/regress/yb_pg_foreign_data_schedule | 7 + src/test/regress/yb_pg_foreign_key_schedule | 2 + src/test/regress/yb_pg_generated_schedule | 2 + src/test/regress/yb_pg_index_schedule | 3 + src/test/regress/yb_pg_inherit_schedule | 8 + src/test/regress/yb_pg_join_schedule | 12 + src/test/regress/yb_pg_json_schedule | 11 + src/test/regress/yb_pg_lock_schedule | 2 + src/test/regress/yb_pg_matview_schedule | 10 + .../regress/yb_pg_misc_independent_1_schedule | 16 + .../regress/yb_pg_misc_independent_2_schedule | 15 + src/test/regress/yb_pg_misc_serial_schedule | 27 + .../regress/yb_pg_partition_join_schedule | 7 + .../regress/yb_pg_partitions_misc_schedule | 9 + src/test/regress/yb_pg_plpgsql_schedule | 10 + .../regress/yb_pg_pushdown_serial_schedule | 7 + src/test/regress/yb_pg_rules_schedule | 7 + src/test/regress/yb_pg_select_schedule | 18 + src/test/regress/yb_pg_stat_schedule | 7 + src/test/regress/yb_pg_stats_ext_schedule | 6 + src/test/regress/yb_pg_stats_import_schedule | 6 + src/test/regress/yb_pg_table_schedule | 14 + src/test/regress/yb_pg_transactions_schedule | 8 + .../regress/yb_pg_types_datetime_schedule | 9 + .../regress/yb_pg_types_geo_serial_schedule | 17 + .../regress/yb_pg_types_misc_serial_schedule | 13 + src/test/regress/yb_pg_types_numeric_schedule | 20 + src/test/regress/yb_pg_types_string_schedule | 24 + .../regress/yb_pg_types_udt_serial_schedule | 16 + src/test/regress/yb_pg_window_schedule | 12 + src/test/regress/yb_pg_xml_schedule | 11 + src/test/regress/yb_planner_serial_schedule | 14 + src/test/regress/yb_planner_taqo_schedule | 7 + src/test/regress/yb_proc_schedule | 14 + src/test/regress/yb_profile_schedule | 12 + src/test/regress/yb_publication_schedule | 7 + src/test/regress/yb_pushdown_serial_schedule | 10 + src/test/regress/yb_replica_identity_schedule | 7 + src/test/regress/yb_replication_slot_schedule | 6 + src/test/regress/yb_reset_analyze_schedule | 6 + src/test/regress/yb_scan_target_schedule | 7 + .../yb_secondary_index_scan_serial_schedule | 14 + src/test/regress/yb_stat_schedule | 6 + src/test/regress/yb_table_schedule | 7 + .../yb_tablegroup_deprecation_schedule | 6 + src/test/regress/yb_tablegroups_schedule | 14 + src/test/regress/yb_tablespaces_schedule | 7 + src/test/regress/yb_transactions_schedule | 8 + src/test/regress/yb_triggers_schedule | 12 + src/test/regress/yb_types_datetime_schedule | 4 + src/test/regress/yb_types_numeric_schedule | 4 + src/test/regress/yb_types_string_schedule | 8 + src/test/regress/yb_types_udt_serial_schedule | 15 + src/test/regress/yb_update_optimized_schedule | 9 + src/timezone/zic.c | 4 + src/tools/pg_bsd_indent/indent.c | 3 + src/tools/pg_bsd_indent/parse.c | 2 + src/tools/pgindent/README | 43 + src/tools/pgindent/pgindent | 4 +- src/tools/pgindent/yb_exclude_file_patterns | 7 + src/tools/pgindent/yb_pgindent | 46 + .../pgindent/yb_thirdparty_typedefs.list | 5 + src/tools/pgindent/yb_typedefs.list | 390 + yb-extensions/Makefile | 29 + yb-extensions/README | 6 + yb-extensions/pg_dist_rag/Makefile | 15 + yb-extensions/pg_dist_rag/README.md | 255 + .../pg_dist_rag/pg_dist_rag--0.0.1.sql | 585 + yb-extensions/pg_dist_rag/pg_dist_rag.control | 5 + .../pg_dist_rag/sql/pg_dist_rag_test.sql | 535 + yb-extensions/pg_dist_rag/yb_schedule | 1 + yb-extensions/yb_pg_metrics/Makefile | 20 + yb-extensions/yb_pg_metrics/yb_pg_metrics.c | 1488 + .../yb_pg_metrics/yb_pg_metrics.control | 3 + yb-extensions/yb_test_extension/Makefile | 19 + .../yb_test_extension--1.0.sql | 110 + .../yb_test_extension.control | 4 + .../yb_xcluster_ddl_replication/Makefile | 25 + .../expected/colocated_setup.out | 68 + .../expected/create_colocated_table.out | 40 + .../expected/create_drop_index.out | 93 + .../expected/create_drop_table.out | 111 + .../expected/materialized_views.out | 80 + .../mixed_temporary_and_persistent.out | 27 + .../expected/routines.out | 95 + .../expected/setup.out | 65 + .../expected/table_rewrite.out | 256 + .../expected/truncate.out | 22 + .../extension_util.c | 342 + .../extension_util.h | 103 + .../yb_xcluster_ddl_replication/json_util.c | 78 + .../yb_xcluster_ddl_replication/json_util.h | 32 + .../source_ddl_end_handler.c | 1170 + .../source_ddl_end_handler.h | 54 + .../sql/colocated_setup.sql | 3 + .../sql/create_colocated_table.sql | 19 + .../sql/create_drop_index.sql | 46 + .../sql/create_drop_table.sql | 55 + .../sql/materialized_views.sql | 49 + .../sql/mixed_temporary_and_persistent.sql | 13 + .../sql/routines.sql | 53 + .../yb_xcluster_ddl_replication/sql/setup.sql | 69 + .../sql/table_rewrite.sql | 145 + .../sql/truncate.sql | 10 + .../xcluster_ddl_replication_gucs.def | 33 + .../yb_xcluster_ddl_replication/yb_schedule | 15 + .../yb_xcluster_ddl_replication--1.0.sql | 107 + .../yb_xcluster_ddl_replication.c | 818 + .../yb_xcluster_ddl_replication.control | 7 + yb-extensions/yb_ycql_utils/Makefile | 21 + .../yb_ycql_utils/expected/basic.out | 11 + yb-extensions/yb_ycql_utils/sql/basic.sql | 3 + yb-extensions/yb_ycql_utils/yb_cql_utils.conf | 1 + yb-extensions/yb_ycql_utils/yb_schedule | 3 + .../yb_ycql_utils/yb_ycql_utils--1.0--1.1.sql | 34 + .../yb_ycql_utils/yb_ycql_utils--1.0.sql | 22 + yb-extensions/yb_ycql_utils/yb_ycql_utils.c | 95 + .../yb_ycql_utils/yb_ycql_utils.control | 5 + 1930 files changed, 672234 insertions(+), 6246 deletions(-) create mode 100644 .clang-format create mode 100644 CMakeLists.txt create mode 100644 contrib/adminpack/yb_schedule create mode 100644 contrib/cube/expected/yb.port.cube.out create mode 100644 contrib/cube/sql/yb.port.cube.sql create mode 100644 contrib/cube/yb_schedule create mode 100644 contrib/earthdistance/expected/yb.port.earthdistance.out create mode 100644 contrib/earthdistance/sql/yb.port.earthdistance.sql create mode 100644 contrib/earthdistance/yb_schedule create mode 100644 contrib/file_fdw/expected/yb.port.file_fdw.out create mode 100644 contrib/file_fdw/sql/yb.port.file_fdw.sql create mode 100644 contrib/file_fdw/yb_schedule create mode 100644 contrib/fuzzystrmatch/yb_schedule create mode 100644 contrib/hstore/expected/yb.port.hstore.out create mode 100644 contrib/hstore/hstore--1.5--1.5-yb-1.0.sql create mode 100644 contrib/hstore/hstore--1.5-yb-1.0--1.8-yb-1.0.sql create mode 100644 contrib/hstore/sql/yb.port.hstore.sql create mode 100644 contrib/hstore/yb_schedule create mode 100644 contrib/intarray/expected/yb.port._int.out create mode 100644 contrib/intarray/sql/yb.port._int.sql create mode 100644 contrib/intarray/yb_schedule create mode 100644 contrib/isn/expected/yb.port.isn.out create mode 100644 contrib/isn/sql/yb.port.isn.sql create mode 100644 contrib/isn/yb_schedule create mode 100644 contrib/passwordcheck/README create mode 100644 contrib/passwordcheck/expected/passwordcheck_extra.out create mode 100644 contrib/passwordcheck/expected/yb.port.passwordcheck.out create mode 100644 contrib/passwordcheck/expected/yb.port.passwordcheck_extra.out mode change 100644 => 120000 contrib/passwordcheck/passwordcheck.c create mode 100644 contrib/passwordcheck/passwordcheck_extra.c create mode 100644 contrib/passwordcheck/sql/passwordcheck_extra.sql create mode 100644 contrib/passwordcheck/sql/yb.port.passwordcheck.sql create mode 100644 contrib/passwordcheck/sql/yb.port.passwordcheck_extra.sql create mode 100644 contrib/passwordcheck/yb_schedule create mode 100644 contrib/pg_stat_statements/expected/yb.orig.backfill_queryid.out create mode 100644 contrib/pg_stat_statements/expected/yb.orig.pg_stat_statements.out create mode 100644 contrib/pg_stat_statements/expected/yb.port.pg_stat_statements.out create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.10--1.10-yb-1.0.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.10-yb-1.0--1.10-yb-2.0.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.10-yb-1.0.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.0--1.10-yb-2.1.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.0.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.1.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.6--1.6-yb-1.0.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.6-yb-1.0--1.10-yb-1.0.sql create mode 100644 contrib/pg_stat_statements/sql/yb.orig.backfill_queryid.sql create mode 100644 contrib/pg_stat_statements/sql/yb.orig.pg_stat_statements.sql create mode 100644 contrib/pg_stat_statements/sql/yb.port.pg_stat_statements.sql create mode 100644 contrib/pg_stat_statements/yb_schedule create mode 100644 contrib/pg_trgm/expected/yb.port.pg_strict_word_trgm.out create mode 100644 contrib/pg_trgm/expected/yb.port.pg_trgm.out create mode 100644 contrib/pg_trgm/expected/yb.port.pg_word_trgm.out create mode 100644 contrib/pg_trgm/pg_trgm--1.4--1.4-yb-1.0.sql create mode 100644 contrib/pg_trgm/pg_trgm--1.4-yb-1.0--1.6-yb-1.0.sql create mode 100644 contrib/pg_trgm/sql/yb.port.pg_strict_word_trgm.sql create mode 100644 contrib/pg_trgm/sql/yb.port.pg_trgm.sql create mode 100644 contrib/pg_trgm/sql/yb.port.pg_word_trgm.sql create mode 100644 contrib/pg_trgm/yb_schedule create mode 100644 contrib/pgcrypto/yb_schedule create mode 100644 contrib/postgres_fdw/expected/yb.orig.global_views.out create mode 100644 contrib/postgres_fdw/expected/yb.orig.postgres_fdw.out create mode 100644 contrib/postgres_fdw/expected/yb.port.postgres_fdw.out create mode 100644 contrib/postgres_fdw/sql/yb.orig.global_views.sql create mode 100644 contrib/postgres_fdw/sql/yb.orig.postgres_fdw.sql create mode 100644 contrib/postgres_fdw/sql/yb.port.postgres_fdw.sql create mode 100644 contrib/postgres_fdw/yb_gv_schedule create mode 100644 contrib/postgres_fdw/yb_pg_schedule create mode 100644 contrib/postgres_fdw/yb_schedule create mode 100644 contrib/spi/expected/yb.orig.spi.out create mode 100644 contrib/spi/sql/yb.orig.spi.sql create mode 100644 contrib/spi/yb_schedule create mode 100644 contrib/tablefunc/expected/yb.port.tablefunc.out create mode 100644 contrib/tablefunc/sql/yb.port.tablefunc.sql create mode 100644 contrib/tablefunc/yb_schedule create mode 100644 contrib/uuid-ossp/yb_schedule create mode 100644 doc/bug.template create mode 100644 doc/src/sgml/ref/alter_tablegroup.sgml create mode 100644 doc/src/sgml/ref/create_profile.sgml create mode 100644 doc/src/sgml/ref/create_tablegroup.sgml create mode 100644 doc/src/sgml/ref/drop_profile.sgml create mode 100644 doc/src/sgml/ref/drop_tablegroup.sgml create mode 100644 src/Makefile_ybpostgres.shlib create mode 100644 src/backend/access/yb_access/Makefile create mode 100644 src/backend/access/yb_access/yb_lsm.c create mode 100644 src/backend/access/yb_access/yb_pg_inherits_scan.c create mode 100644 src/backend/access/yb_access/yb_scan.c create mode 100644 src/backend/access/ybgin/Makefile create mode 100644 src/backend/access/ybgin/ybgin.c create mode 100644 src/backend/access/ybgin/ybginget.c create mode 100644 src/backend/access/ybgin/ybginscan.c create mode 100644 src/backend/access/ybgin/ybginutil.c create mode 100644 src/backend/access/ybgin/ybginwrite.c create mode 100644 src/backend/bootstrap/yb_bootstrap.c create mode 100644 src/backend/catalog/yb_catalog/Makefile create mode 100644 src/backend/catalog/yb_catalog/yb_catalog_version.c create mode 100644 src/backend/catalog/yb_catalog/yb_oid_assignment.c create mode 100644 src/backend/catalog/yb_catalog/yb_type.c create mode 100644 src/backend/catalog/yb_genbki.pl create mode 100644 src/backend/catalog/yb_logical_client/Makefile create mode 100644 src/backend/catalog/yb_logical_client/yb_logical_client_version.c create mode 100644 src/backend/catalog/yb_system_functions.sql create mode 100644 src/backend/catalog/yb_system_views.sql create mode 100644 src/backend/commands/yb_builtin.c create mode 100644 src/backend/commands/yb_cmds.c create mode 100644 src/backend/commands/yb_profile.c create mode 100644 src/backend/commands/yb_tablegroup.c create mode 100644 src/backend/executor/nodeYbBatchedNestloop.c create mode 100644 src/backend/executor/nodeYbBitmapIndexscan.c create mode 100644 src/backend/executor/nodeYbBitmapTablescan.c create mode 100644 src/backend/executor/nodeYbSeqscan.c create mode 100644 src/backend/executor/ybExpr.c create mode 100644 src/backend/executor/ybFunction.c create mode 100644 src/backend/executor/ybModifyTable.c create mode 100644 src/backend/executor/ybOptimizeModifyTable.c create mode 100644 src/backend/exports-common.txt create mode 100644 src/backend/exports-test.txt create mode 100644 src/backend/lib/yb_percentile.c create mode 100644 src/backend/main/main_cpp_wrapper.cc create mode 100644 src/backend/nodes/ybbitmatrix.c create mode 100644 src/backend/nodes/ybtidbitmap.c create mode 100644 src/backend/optimizer/path/yb_uniqkeys.c create mode 100644 src/backend/optimizer/util/yb_merge_scan.c create mode 100644 src/backend/optimizer/util/ybplan.c create mode 100644 src/backend/replication/logical/yb_decode.c create mode 100644 src/backend/replication/logical/yb_virtual_wal_client.c create mode 100644 src/backend/utils/activity/yb_terminated_queries.c create mode 100644 src/backend/utils/adt/yb_lockfuncs.c create mode 100644 src/backend/utils/cache/yb_inheritscache.c create mode 100644 src/backend/utils/cache/yb_tuplecache.c create mode 100644 src/backend/utils/misc/pg_yb_utils.c create mode 100644 src/backend/utils/misc/yb_ash.c create mode 100644 src/backend/utils/misc/yb_exceptions_for_func_pushdown.c create mode 100644 src/backend/utils/misc/yb_file_utils.c create mode 100644 src/backend/utils/misc/yb_index_check.c create mode 100644 src/backend/utils/misc/yb_jumblefuncs.c create mode 100644 src/backend/utils/misc/yb_qpm.c create mode 100644 src/backend/utils/misc/yb_query_diagnostics.c create mode 100644 src/backend/utils/misc/yb_tcmalloc_utils.c create mode 100644 src/backend/utils/misc/yb_ysql_conn_mgr_helper.c create mode 100644 src/backend/ybgate/Makefile create mode 100644 src/backend/ybgate/ybgate_api-test.c create mode 100644 src/backend/ybgate/ybgate_api.c create mode 100644 src/backend/ybgate/ybgate_status.c create mode 120000 src/bin/pg_waldump/brindesc.c create mode 120000 src/bin/pg_waldump/clogdesc.c create mode 120000 src/bin/pg_waldump/committsdesc.c create mode 120000 src/bin/pg_waldump/dbasedesc.c create mode 120000 src/bin/pg_waldump/genericdesc.c create mode 120000 src/bin/pg_waldump/gindesc.c create mode 120000 src/bin/pg_waldump/gistdesc.c create mode 120000 src/bin/pg_waldump/hashdesc.c create mode 120000 src/bin/pg_waldump/heapdesc.c create mode 120000 src/bin/pg_waldump/logicalmsgdesc.c create mode 120000 src/bin/pg_waldump/mxactdesc.c create mode 120000 src/bin/pg_waldump/nbtdesc.c create mode 120000 src/bin/pg_waldump/relmapdesc.c create mode 120000 src/bin/pg_waldump/replorigindesc.c create mode 120000 src/bin/pg_waldump/seqdesc.c create mode 120000 src/bin/pg_waldump/smgrdesc.c create mode 120000 src/bin/pg_waldump/spgdesc.c create mode 120000 src/bin/pg_waldump/standbydesc.c create mode 120000 src/bin/pg_waldump/tblspcdesc.c create mode 120000 src/bin/pg_waldump/xactdesc.c create mode 120000 src/bin/pg_waldump/xlogdesc.c create mode 120000 src/bin/pg_waldump/xlogreader.c create mode 100644 src/common/pg_yb_common.c create mode 100644 src/include/access/yb_pg_inherits_scan.h create mode 100644 src/include/access/yb_scan.h create mode 100644 src/include/access/yb_sys_scan_base.h create mode 100644 src/include/access/ybgin.h create mode 100644 src/include/access/ybgin_private.h create mode 100644 src/include/bootstrap/yb_bootstrap.h create mode 100644 src/include/catalog/pg_yb_catalog_version.dat create mode 100644 src/include/catalog/pg_yb_catalog_version.h create mode 100644 src/include/catalog/pg_yb_invalidation_messages.h create mode 100644 src/include/catalog/pg_yb_logical_client_version.dat create mode 100644 src/include/catalog/pg_yb_logical_client_version.h create mode 100644 src/include/catalog/pg_yb_migration.dat create mode 100644 src/include/catalog/pg_yb_migration.h create mode 100644 src/include/catalog/pg_yb_profile.h create mode 100644 src/include/catalog/pg_yb_role_profile.h create mode 100644 src/include/catalog/pg_yb_tablegroup.dat create mode 100644 src/include/catalog/pg_yb_tablegroup.h create mode 100644 src/include/catalog/yb_catalog_version.h create mode 100644 src/include/catalog/yb_logical_client_version.h create mode 100644 src/include/catalog/yb_oid_assignment.h create mode 100644 src/include/catalog/yb_type.h create mode 100644 src/include/commands/yb_cmds.h create mode 100644 src/include/commands/yb_profile.h create mode 100644 src/include/commands/yb_tablegroup.h create mode 100644 src/include/common/pg_yb_common.h create mode 100644 src/include/common/pg_yb_param_status_flags.h create mode 100644 src/include/executor/nodeYbBatchedNestloop.h create mode 100644 src/include/executor/nodeYbBitmapIndexscan.h create mode 100644 src/include/executor/nodeYbBitmapTablescan.h create mode 100644 src/include/executor/nodeYbSeqscan.h create mode 100644 src/include/executor/ybExpr.h create mode 100644 src/include/executor/ybFunction.h create mode 100644 src/include/executor/ybModifyTable.h create mode 100644 src/include/executor/ybOptimizeModifyTable.h create mode 100644 src/include/libpq/yb_pqcomm_extensions.h create mode 100644 src/include/nodes/ybbitmatrix.h create mode 100644 src/include/nodes/ybtidbitmap.h create mode 100644 src/include/optimizer/yb_merge_scan.h create mode 100644 src/include/optimizer/ybplan.h create mode 100644 src/include/pg_yb_utils.h create mode 100644 src/include/replication/yb_decode.h create mode 100644 src/include/replication/yb_virtual_wal_client.h create mode 100644 src/include/replication/yb_virtual_wal_client_typedefs.h create mode 100644 src/include/utils/yb_inheritscache.h create mode 100644 src/include/utils/yb_jumblefuncs.h create mode 100644 src/include/utils/yb_like_support.h create mode 100644 src/include/utils/yb_queryjumble.h create mode 100644 src/include/utils/yb_rowtypes.h create mode 100644 src/include/utils/yb_tuplecache.h create mode 100644 src/include/yb_ash.h create mode 100644 src/include/yb_file_utils.h create mode 100644 src/include/yb_overflow_utils.h create mode 100644 src/include/yb_qpm.h create mode 100644 src/include/yb_query_diagnostics.h create mode 100644 src/include/yb_tcmalloc_utils.h create mode 100644 src/include/yb_terminated_queries.h create mode 100644 src/include/yb_ysql_conn_mgr_helper.h create mode 100644 src/include/ybctid.h create mode 100644 src/include/ybgate/ybgate_api-test.h create mode 100644 src/include/ybgate/ybgate_api.h create mode 100644 src/include/ybgate/ybgate_cpp_util.h create mode 100644 src/include/ybgate/ybgate_status.h create mode 100644 src/test/isolation/expected/yb.orig.advisory-locks-fail-on-conflict.out create mode 100644 src/test/isolation/expected/yb.orig.advisory_locks.out create mode 100644 src/test/isolation/expected/yb.orig.analyze_ddl_deadlock.out create mode 100644 src/test/isolation/expected/yb.orig.avoid_stale_catalog_reads-1.out create mode 100644 src/test/isolation/expected/yb.orig.avoid_stale_catalog_reads-2.out create mode 100644 src/test/isolation/expected/yb.orig.avoid_stale_catalog_reads-3.out create mode 100644 src/test/isolation/expected/yb.orig.avoid_stale_catalog_reads-4.out create mode 100644 src/test/isolation/expected/yb.orig.check-constraint-locking.out create mode 100644 src/test/isolation/expected/yb.orig.check-constraint-locking_1.out create mode 100644 src/test/isolation/expected/yb.orig.concurrent-inserts-duplicate-key-error.out create mode 100644 src/test/isolation/expected/yb.orig.concurrent_ddls.out create mode 100644 src/test/isolation/expected/yb.orig.concurrent_ddls_read_latest_cat_version.out create mode 100644 src/test/isolation/expected/yb.orig.concurrent_insert_during_rewrite.out create mode 100644 src/test/isolation/expected/yb.orig.concurrent_partition_insert.out create mode 100644 src/test/isolation/expected/yb.orig.deadlock.out create mode 100644 src/test/isolation/expected/yb.orig.drop-expression-partitioned-table.out create mode 100644 src/test/isolation/expected/yb.orig.drop-expression-partitioned-table_1.out create mode 100644 src/test/isolation/expected/yb.orig.drop-probe-waiting-on-aborted-subtxn.out create mode 100644 src/test/isolation/expected/yb.orig.ensure-lock-only-conflict-always-ignored.out create mode 100644 src/test/isolation/expected/yb.orig.fk-relationship.out create mode 100644 src/test/isolation/expected/yb.orig.fk-relationship_1.out create mode 100644 src/test/isolation/expected/yb.orig.fk-row-locking.out create mode 100644 src/test/isolation/expected/yb.orig.fk_triggers_use_latest_snapshot.out create mode 100644 src/test/isolation/expected/yb.orig.ignore-rolled-back-savepoint-intent.out create mode 100644 src/test/isolation/expected/yb.orig.inplace_catalog_updates.out create mode 100644 src/test/isolation/expected/yb.orig.lock-status-waiters.out create mode 100644 src/test/isolation/expected/yb.orig.lock-status-waiters_1.out create mode 100644 src/test/isolation/expected/yb.orig.lock-status-waiters_2.out create mode 100644 src/test/isolation/expected/yb.orig.lock-status-waiters_3.out create mode 100644 src/test/isolation/expected/yb.orig.lock-status-waiters_4.out create mode 100644 src/test/isolation/expected/yb.orig.lock-status-waiters_5.out create mode 100644 src/test/isolation/expected/yb.orig.lock-status.out create mode 100644 src/test/isolation/expected/yb.orig.modification-followed-by-lock.out create mode 100644 src/test/isolation/expected/yb.orig.modify-transaction-characteristics.out create mode 100644 src/test/isolation/expected/yb.orig.only-abort-sub-txn.out create mode 100644 src/test/isolation/expected/yb.orig.pg_stat_statements-retry.out create mode 100644 src/test/isolation/expected/yb.orig.query_layer_retries_multi_stmt_query.out create mode 100644 src/test/isolation/expected/yb.orig.rc-ignores-txn-priority-bounds.out create mode 100644 src/test/isolation/expected/yb.orig.read_committed_insert.out create mode 100644 src/test/isolation/expected/yb.orig.read_committed_insert_duplicate_key_error.out create mode 100644 src/test/isolation/expected/yb.orig.read_committed_non_atomic_commit_no_retry.out create mode 100644 src/test/isolation/expected/yb.orig.read_committed_test_ddl_txn.out create mode 100644 src/test/isolation/expected/yb.orig.read_committed_test_do_call.out create mode 100644 src/test/isolation/expected/yb.orig.read_committed_test_internal_savepoint.out create mode 100644 src/test/isolation/expected/yb.orig.read_committed_update_and_explicit_locking.out create mode 100644 src/test/isolation/expected/yb.orig.reset_txn_snapshot_after_ddl.out create mode 100644 src/test/isolation/expected/yb.orig.serializable_conflict_with_index_only_scan.out create mode 100644 src/test/isolation/expected/yb.orig.single-shard-waiters-same-tablet.out create mode 100644 src/test/isolation/expected/yb.orig.skip-locked-after-update.out create mode 100644 src/test/isolation/expected/yb.orig.skip-locked-single-shard-transaction.out create mode 100644 src/test/isolation/expected/yb.orig.snapshot_switch_dml_ddl_txn.out create mode 100644 src/test/isolation/expected/yb.orig.stat-activity-waitevent-statement-retry.out create mode 100644 src/test/isolation/expected/yb.orig.unblock-waiter-after-subtxn-rollsback.out create mode 100644 src/test/isolation/expected/yb.orig.user-managed-constraint.out create mode 100644 src/test/isolation/expected/yb.orig.user-managed-constraint_1.out create mode 100644 src/test/isolation/expected/yb.orig.user_table_access_in_a_ddl.out create mode 100644 src/test/isolation/expected/yb.orig.wait-queues-serializable-reads.out create mode 100644 src/test/isolation/expected/yb.orig.wait-queues-single-shard-waiter-unlocks.out create mode 100644 src/test/isolation/expected/yb.orig.wait-queues-weak-read-unlocks.out create mode 100644 src/test/isolation/expected/yb.port.async-notify.out create mode 100644 src/test/isolation/expected/yb.port.deadlock-hard.out create mode 100644 src/test/isolation/expected/yb.port.deadlock-simple.out create mode 100644 src/test/isolation/expected/yb.port.deadlock-soft-2.out create mode 100644 src/test/isolation/expected/yb.port.deadlock-soft.out create mode 100644 src/test/isolation/expected/yb.port.deadlock-soft_1.out create mode 100644 src/test/isolation/expected/yb.port.eval-plan-qual.out create mode 100644 src/test/isolation/expected/yb.port.fk-snapshot.out create mode 100644 src/test/isolation/expected/yb.port.fk-snapshot_1.out create mode 100644 src/test/isolation/expected/yb.port.insert-conflict-do-nothing-2.out create mode 100644 src/test/isolation/expected/yb.port.partition-drop-index-locking.out create mode 100644 src/test/isolation/expected/yb.port.timeouts.out create mode 100644 src/test/isolation/specs/yb.orig.advisory-locks-fail-on-conflict.spec create mode 100644 src/test/isolation/specs/yb.orig.advisory_locks.spec create mode 100644 src/test/isolation/specs/yb.orig.analyze_ddl_deadlock.spec create mode 100644 src/test/isolation/specs/yb.orig.avoid_stale_catalog_reads-1.spec create mode 100644 src/test/isolation/specs/yb.orig.avoid_stale_catalog_reads-2.spec create mode 100644 src/test/isolation/specs/yb.orig.avoid_stale_catalog_reads-3.spec create mode 100644 src/test/isolation/specs/yb.orig.avoid_stale_catalog_reads-4.spec create mode 100644 src/test/isolation/specs/yb.orig.check-constraint-locking.spec create mode 100644 src/test/isolation/specs/yb.orig.concurrent-inserts-duplicate-key-error.spec create mode 100644 src/test/isolation/specs/yb.orig.concurrent_ddls.spec create mode 100644 src/test/isolation/specs/yb.orig.concurrent_ddls_read_latest_cat_version.spec create mode 100644 src/test/isolation/specs/yb.orig.concurrent_insert_during_rewrite.spec create mode 100644 src/test/isolation/specs/yb.orig.concurrent_partition_insert.spec create mode 100644 src/test/isolation/specs/yb.orig.deadlock.spec create mode 100644 src/test/isolation/specs/yb.orig.drop-expression-partitioned-table.spec create mode 100644 src/test/isolation/specs/yb.orig.drop-probe-waiting-on-aborted-subtxn.spec create mode 100644 src/test/isolation/specs/yb.orig.ensure-lock-only-conflict-always-ignored.spec create mode 100644 src/test/isolation/specs/yb.orig.fk-relationship.spec create mode 100644 src/test/isolation/specs/yb.orig.fk-row-locking.spec create mode 100644 src/test/isolation/specs/yb.orig.fk_triggers_use_latest_snapshot.spec create mode 100644 src/test/isolation/specs/yb.orig.ignore-rolled-back-savepoint-intent.spec create mode 100644 src/test/isolation/specs/yb.orig.inplace_catalog_updates.spec create mode 100644 src/test/isolation/specs/yb.orig.lock-status-waiters.spec create mode 100644 src/test/isolation/specs/yb.orig.lock-status.spec create mode 100644 src/test/isolation/specs/yb.orig.modification-followed-by-lock.spec create mode 100644 src/test/isolation/specs/yb.orig.modify-transaction-characteristics.spec create mode 100644 src/test/isolation/specs/yb.orig.only-abort-sub-txn.spec create mode 100644 src/test/isolation/specs/yb.orig.pg_stat_statements-retry.spec create mode 100644 src/test/isolation/specs/yb.orig.query_layer_retries_multi_stmt_query.spec create mode 100644 src/test/isolation/specs/yb.orig.rc-ignores-txn-priority-bounds.spec create mode 100644 src/test/isolation/specs/yb.orig.read_committed_insert.spec create mode 100644 src/test/isolation/specs/yb.orig.read_committed_insert_duplicate_key_error.spec create mode 100644 src/test/isolation/specs/yb.orig.read_committed_non_atomic_commit_no_retry.spec create mode 100644 src/test/isolation/specs/yb.orig.read_committed_test_ddl_txn.spec create mode 100644 src/test/isolation/specs/yb.orig.read_committed_test_do_call.spec create mode 100644 src/test/isolation/specs/yb.orig.read_committed_test_internal_savepoint.spec create mode 100644 src/test/isolation/specs/yb.orig.read_committed_update_and_explicit_locking.spec create mode 100644 src/test/isolation/specs/yb.orig.reset_txn_snapshot_after_ddl.spec create mode 100644 src/test/isolation/specs/yb.orig.serializable_conflict_with_index_only_scan.spec create mode 100644 src/test/isolation/specs/yb.orig.single-shard-waiters-same-tablet.spec create mode 100644 src/test/isolation/specs/yb.orig.skip-locked-after-update.spec create mode 100644 src/test/isolation/specs/yb.orig.skip-locked-single-shard-transaction.spec create mode 100644 src/test/isolation/specs/yb.orig.snapshot_switch_dml_ddl_txn.spec create mode 100644 src/test/isolation/specs/yb.orig.stat-activity-waitevent-statement-retry.spec create mode 100644 src/test/isolation/specs/yb.orig.unblock-waiter-after-subtxn-rollsback.spec create mode 100644 src/test/isolation/specs/yb.orig.user-managed-constraint.spec create mode 100644 src/test/isolation/specs/yb.orig.user_table_access_in_a_ddl.spec create mode 100644 src/test/isolation/specs/yb.orig.wait-queues-serializable-reads.spec create mode 100644 src/test/isolation/specs/yb.orig.wait-queues-single-shard-waiter-unlocks.spec create mode 100644 src/test/isolation/specs/yb.orig.wait-queues-weak-read-unlocks.spec create mode 100644 src/test/isolation/specs/yb.port.async-notify.spec create mode 100644 src/test/isolation/specs/yb.port.deadlock-hard.spec create mode 100644 src/test/isolation/specs/yb.port.deadlock-simple.spec create mode 100644 src/test/isolation/specs/yb.port.deadlock-soft-2.spec create mode 100644 src/test/isolation/specs/yb.port.deadlock-soft.spec create mode 100644 src/test/isolation/specs/yb.port.eval-plan-qual.spec create mode 100644 src/test/isolation/specs/yb.port.fk-snapshot.spec create mode 100644 src/test/isolation/specs/yb.port.insert-conflict-do-nothing-2.spec create mode 100644 src/test/isolation/specs/yb.port.partition-drop-index-locking.spec create mode 100644 src/test/isolation/specs/yb.port.timeouts.spec create mode 100644 src/test/isolation/yb_isolation_object_locking_schedule create mode 100644 src/test/isolation/yb_isolation_txn_ddl_schedule create mode 100644 src/test/isolation/yb_pg_async_isolation_schedule create mode 100644 src/test/isolation/yb_pg_isolation_advisory_lock_schedule create mode 100644 src/test/isolation/yb_pg_isolation_lock_schedule create mode 100644 src/test/isolation/yb_pg_isolation_schedule create mode 100644 src/test/isolation/yb_wait_queues_schedule create mode 100644 src/test/isolation/yb_wait_queues_schedule_skip_prefix_locks_off create mode 100644 src/test/isolation/yb_without_wait_queues_schedule create mode 100644 src/test/isolation/yb_without_wait_queues_schedule_skip_prefix_locks_off create mode 100644 src/test/modules/dummy_seclabel/expected/yb.port.dummy_seclabel.out create mode 100644 src/test/modules/dummy_seclabel/sql/yb.port.dummy_seclabel.sql create mode 100644 src/test/modules/dummy_seclabel/yb_schedule create mode 100644 src/test/modules/unsafe_tests/expected/yb.port.rolenames.out create mode 100644 src/test/modules/unsafe_tests/sql/yb.port.rolenames.sql create mode 100644 src/test/modules/unsafe_tests/yb_schedule create mode 100644 src/test/regress/data/airport-codes-corrupt.csv create mode 100644 src/test/regress/data/airport-codes.csv create mode 100644 src/test/regress/data/all-airport-codes.csv create mode 100644 src/test/regress/data/yb_catcache_keys.data create mode 100644 src/test/regress/expected/yb.depd.create_index.out create mode 100644 src/test/regress/expected/yb.depd.float8_tbl.out create mode 100644 src/test/regress/expected/yb.depd.int4_tbl.out create mode 100644 src/test/regress/expected/yb.depd.int8_tbl.out create mode 100644 src/test/regress/expected/yb.depd.onek.out create mode 100644 src/test/regress/expected/yb.depd.onek2.out create mode 100644 src/test/regress/expected/yb.depd.part_test_int4_ops.out create mode 100644 src/test/regress/expected/yb.depd.person.out create mode 100644 src/test/regress/expected/yb.depd.tenk1.out create mode 100644 src/test/regress/expected/yb.depd.tenk2.out create mode 100644 src/test/regress/expected/yb.orig.aggregates.out create mode 100644 src/test/regress/expected/yb.orig.alter_operator.out create mode 100644 src/test/regress/expected/yb.orig.alter_table.out create mode 100644 src/test/regress/expected/yb.orig.alter_table_rewrite.out create mode 100644 src/test/regress/expected/yb.orig.alter_type.out create mode 100644 src/test/regress/expected/yb.orig.analyze.out create mode 100644 src/test/regress/expected/yb.orig.analyze_system_tables.out create mode 100644 src/test/regress/expected/yb.orig.async.out create mode 100644 src/test/regress/expected/yb.orig.base_type.out create mode 100644 src/test/regress/expected/yb.orig.beta_extensions.out create mode 100644 src/test/regress/expected/yb.orig.bitmap_scan_plan.out create mode 100644 src/test/regress/expected/yb.orig.bitmap_scans.out create mode 100644 src/test/regress/expected/yb.orig.bitmap_scans_colo.out create mode 100644 src/test/regress/expected/yb.orig.bitmap_scans_distinct.out create mode 100644 src/test/regress/expected/yb.orig.bitmap_scans_flags.out create mode 100644 src/test/regress/expected/yb.orig.bitmap_scans_joins.out create mode 100644 src/test/regress/expected/yb.orig.bitmap_scans_system.out create mode 100644 src/test/regress/expected/yb.orig.boolean.out create mode 100644 src/test/regress/expected/yb.orig.buffering_with_sql_errors.out create mode 100644 src/test/regress/expected/yb.orig.bytea.out create mode 100644 src/test/regress/expected/yb.orig.cacheinfo.out create mode 100644 src/test/regress/expected/yb.orig.catalog_cache_hashing.out create mode 100644 src/test/regress/expected/yb.orig.catalog_version.out create mode 100644 src/test/regress/expected/yb.orig.catalog_version_increment.out create mode 100644 src/test/regress/expected/yb.orig.char.out create mode 100644 src/test/regress/expected/yb.orig.collate.icu.utf8.out create mode 100644 src/test/regress/expected/yb.orig.colocated_tables_with_tablespaces.out create mode 100644 src/test/regress/expected/yb.orig.compound_key.out create mode 100644 src/test/regress/expected/yb.orig.copy_55k_rows.out create mode 100644 src/test/regress/expected/yb.orig.create_function.out create mode 100644 src/test/regress/expected/yb.orig.create_index.out create mode 100644 src/test/regress/expected/yb.orig.create_index_large_table_backfill_index.out create mode 100644 src/test/regress/expected/yb.orig.create_index_update_reltuples.out create mode 100644 src/test/regress/expected/yb.orig.create_language.out create mode 100644 src/test/regress/expected/yb.orig.create_large_table.out create mode 100644 src/test/regress/expected/yb.orig.create_large_table_indexes.out create mode 100644 src/test/regress/expected/yb.orig.create_large_table_tablet_split.out create mode 100644 src/test/regress/expected/yb.orig.create_table.out create mode 100644 src/test/regress/expected/yb.orig.create_table_55k_secondary_index.out create mode 100644 src/test/regress/expected/yb.orig.create_table_like.out create mode 100644 src/test/regress/expected/yb.orig.create_table_scattering_keys.out create mode 100644 src/test/regress/expected/yb.orig.create_type.out create mode 100644 src/test/regress/expected/yb.orig.ctid.out create mode 100644 src/test/regress/expected/yb.orig.cursor.out create mode 100644 src/test/regress/expected/yb.orig.cursor_preserve_in_txn_limit.out create mode 100644 src/test/regress/expected/yb.orig.ddl_exceptions.out create mode 100644 src/test/regress/expected/yb.orig.ddl_rc_rollback.out create mode 100644 src/test/regress/expected/yb.orig.ddl_savepoint.out create mode 100644 src/test/regress/expected/yb.orig.ddl_savepoint_toggle.out create mode 100644 src/test/regress/expected/yb.orig.ddl_txn_block.out create mode 100644 src/test/regress/expected/yb.orig.ddl_txn_copy.out create mode 100644 src/test/regress/expected/yb.orig.ddl_txn_event_triggers.out create mode 100644 src/test/regress/expected/yb.orig.ddl_txn_fk.out create mode 100644 src/test/regress/expected/yb.orig.ddl_txn_indices.out create mode 100644 src/test/regress/expected/yb.orig.ddl_txn_isolation.out create mode 100644 src/test/regress/expected/yb.orig.ddl_txn_procedures.out create mode 100644 src/test/regress/expected/yb.orig.ddl_txn_recursive_functions.out create mode 100644 src/test/regress/expected/yb.orig.ddl_txn_triggers.out create mode 100644 src/test/regress/expected/yb.orig.ddl_txn_visibility.out create mode 100644 src/test/regress/expected/yb.orig.depend.out create mode 100644 src/test/regress/expected/yb.orig.dependency.out create mode 100644 src/test/regress/expected/yb.orig.distinct_pushdown_base.out create mode 100644 src/test/regress/expected/yb.orig.distinct_pushdown_join.out create mode 100644 src/test/regress/expected/yb.orig.distinct_pushdown_pred.out create mode 100644 src/test/regress/expected/yb.orig.dml.out create mode 100644 src/test/regress/expected/yb.orig.dml_derived_equalities.out create mode 100644 src/test/regress/expected/yb.orig.dml_insert_conflict.out create mode 100644 src/test/regress/expected/yb.orig.dml_insert_conflict_1.out create mode 100644 src/test/regress/expected/yb.orig.dml_key_pushdown.out create mode 100644 src/test/regress/expected/yb.orig.dml_pushdown.out create mode 100644 src/test/regress/expected/yb.orig.dml_read_time.out create mode 100644 src/test/regress/expected/yb.orig.dml_scankey.out create mode 100644 src/test/regress/expected/yb.orig.dml_single_row.out create mode 100644 src/test/regress/expected/yb.orig.dml_systable_scan.out create mode 100644 src/test/regress/expected/yb.orig.drop_table.out create mode 100644 src/test/regress/expected/yb.orig.errors.out create mode 100644 src/test/regress/expected/yb.orig.event_trigger.out create mode 100644 src/test/regress/expected/yb.orig.explain.out create mode 100644 src/test/regress/expected/yb.orig.explicit_row_lock_batching.out create mode 100644 src/test/regress/expected/yb.orig.explicit_row_lock_planning.out create mode 100644 src/test/regress/expected/yb.orig.extensions.out create mode 100644 src/test/regress/expected/yb.orig.feature_alter_rename.out create mode 100644 src/test/regress/expected/yb.orig.feature_alter_table.out create mode 100644 src/test/regress/expected/yb.orig.feature_colocation.out create mode 100644 src/test/regress/expected/yb.orig.feature_config.out create mode 100644 src/test/regress/expected/yb.orig.feature_copy.out create mode 100644 src/test/regress/expected/yb.orig.feature_copy_1.out create mode 100644 src/test/regress/expected/yb.orig.feature_copyselect.out create mode 100644 src/test/regress/expected/yb.orig.feature_db.out create mode 100644 src/test/regress/expected/yb.orig.feature_delete.out create mode 100644 src/test/regress/expected/yb.orig.feature_hash_types.out create mode 100644 src/test/regress/expected/yb.orig.feature_insert.out create mode 100644 src/test/regress/expected/yb.orig.feature_legacy_colocation.out create mode 100644 src/test/regress/expected/yb.orig.feature_pk_split.out create mode 100644 src/test/regress/expected/yb.orig.feature_pk_split_desc.out create mode 100644 src/test/regress/expected/yb.orig.feature_range_types.out create mode 100644 src/test/regress/expected/yb.orig.feature_select.out create mode 100644 src/test/regress/expected/yb.orig.feature_temp.out create mode 100644 src/test/regress/expected/yb.orig.feature_types.out create mode 100644 src/test/regress/expected/yb.orig.feature_update.out create mode 100644 src/test/regress/expected/yb.orig.fetch_limits.out create mode 100644 src/test/regress/expected/yb.orig.fetch_limits_joins.out create mode 100644 src/test/regress/expected/yb.orig.foreign_data.out create mode 100644 src/test/regress/expected/yb.orig.foreign_key.out create mode 100644 src/test/regress/expected/yb.orig.function.out create mode 100644 src/test/regress/expected/yb.orig.functional_index_multiple_column.out create mode 100644 src/test/regress/expected/yb.orig.functional_indexes.out create mode 100644 src/test/regress/expected/yb.orig.generated.out create mode 100644 src/test/regress/expected/yb.orig.get_current_transaction_priority.out create mode 100644 src/test/regress/expected/yb.orig.get_range_split_clause.out create mode 100644 src/test/regress/expected/yb.orig.gin.out create mode 100644 src/test/regress/expected/yb.orig.guc.out create mode 100644 src/test/regress/expected/yb.orig.hash_func.out create mode 100644 src/test/regress/expected/yb.orig.hash_in_queries.out create mode 100644 src/test/regress/expected/yb.orig.hdr_percentile.out create mode 100644 src/test/regress/expected/yb.orig.hints.out create mode 100644 src/test/regress/expected/yb.orig.index_including.out create mode 100644 src/test/regress/expected/yb.orig.index_only_scan_decoded_pk.out create mode 100644 src/test/regress/expected/yb.orig.index_recheck.out create mode 100644 src/test/regress/expected/yb.orig.index_scan.out create mode 100644 src/test/regress/expected/yb.orig.index_scan_null_asc.out create mode 100644 src/test/regress/expected/yb.orig.index_scan_null_create.out create mode 100644 src/test/regress/expected/yb.orig.index_scan_null_hash.out create mode 100644 src/test/regress/expected/yb.orig.index_selectivity.out create mode 100644 src/test/regress/expected/yb.orig.indexing_order.out create mode 100644 src/test/regress/expected/yb.orig.inherit.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_1.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_multiplan.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_multiplan_2.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_multiplan_3.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_multiplan_9.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_with.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_with_1.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_with_2.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_with_explain.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_with_explain_1.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_with_explain_2.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_with_explain_3.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_with_spi.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_with_spi_1.out create mode 100644 src/test/regress/expected/yb.orig.insert_on_conflict_with_spi_2.out create mode 100644 src/test/regress/expected/yb.orig.int2_array.out create mode 100644 src/test/regress/expected/yb.orig.int4_array.out create mode 100644 src/test/regress/expected/yb.orig.internal_subtransactions.out create mode 100644 src/test/regress/expected/yb.orig.interval.out create mode 100644 src/test/regress/expected/yb.orig.is_local_table.out create mode 100644 src/test/regress/expected/yb.orig.join_batching.out create mode 100644 src/test/regress/expected/yb.orig.join_batching_parallel.out create mode 100644 src/test/regress/expected/yb.orig.join_batching_plans.out create mode 100644 src/test/regress/expected/yb.orig.large_copy.out create mode 100644 src/test/regress/expected/yb.orig.large_copy_abort.out create mode 100644 src/test/regress/expected/yb.orig.large_copy_ddl.out create mode 100644 src/test/regress/expected/yb.orig.legacy_join_plans.out create mode 100644 src/test/regress/expected/yb.orig.levenshtein_pushdown.out create mode 100644 src/test/regress/expected/yb.orig.local_tablets.out create mode 100644 src/test/regress/expected/yb.orig.lock_row_for_update.out create mode 100644 src/test/regress/expected/yb.orig.lock_row_for_update_1.out create mode 100644 src/test/regress/expected/yb.orig.lock_status.out create mode 100644 src/test/regress/expected/yb.orig.lock_status_1.out create mode 100644 src/test/regress/expected/yb.orig.lock_status_2.out create mode 100644 src/test/regress/expected/yb.orig.matview.out create mode 100644 src/test/regress/expected/yb.orig.matview_1.out create mode 100644 src/test/regress/expected/yb.orig.merge_scan_colocated.out create mode 100644 src/test/regress/expected/yb.orig.merge_scan_derive.out create mode 100644 src/test/regress/expected/yb.orig.merge_scan_hash.out create mode 100644 src/test/regress/expected/yb.orig.merge_scan_join.out create mode 100644 src/test/regress/expected/yb.orig.merge_scan_partition.out create mode 100644 src/test/regress/expected/yb.orig.merge_scan_range.out create mode 100644 src/test/regress/expected/yb.orig.merge_scan_setup.out create mode 100644 src/test/regress/expected/yb.orig.merge_scan_temp.out create mode 100644 src/test/regress/expected/yb.orig.money.out create mode 100644 src/test/regress/expected/yb.orig.no_explicit_target.out create mode 100644 src/test/regress/expected/yb.orig.non_txn_copy.out create mode 100644 src/test/regress/expected/yb.orig.numeric.out create mode 100644 src/test/regress/expected/yb.orig.obj_properties_functions.out create mode 100644 src/test/regress/expected/yb.orig.oid.out create mode 100644 src/test/regress/expected/yb.orig.operator_class.out create mode 100644 src/test/regress/expected/yb.orig.optimizer_guc.out create mode 100644 src/test/regress/expected/yb.orig.parallel_append.out create mode 100644 src/test/regress/expected/yb.orig.parallel_big_colocated.out create mode 100644 src/test/regress/expected/yb.orig.parallel_colocated.out create mode 100644 src/test/regress/expected/yb.orig.parallel_hashed.out create mode 100644 src/test/regress/expected/yb.orig.parallel_plans.out create mode 100644 src/test/regress/expected/yb.orig.parallel_ranged.out create mode 100644 src/test/regress/expected/yb.orig.parallel_snapshot_switching.out create mode 100644 src/test/regress/expected/yb.orig.partition_aggregate.out create mode 100644 src/test/regress/expected/yb.orig.partition_alter.out create mode 100644 src/test/regress/expected/yb.orig.partition_fk.out create mode 100644 src/test/regress/expected/yb.orig.partition_out_of_order.out create mode 100644 src/test/regress/expected/yb.orig.partition_pk.out create mode 100644 src/test/regress/expected/yb.orig.partition_prune.out create mode 100644 src/test/regress/expected/yb.orig.partition_prune_plancache.out create mode 100644 src/test/regress/expected/yb.orig.perf_secondary_index_scan.out create mode 100644 src/test/regress/expected/yb.orig.pg_stat.out create mode 100644 src/test/regress/expected/yb.orig.pg_stat_backend.out create mode 100644 src/test/regress/expected/yb.orig.planner_base_scans_cost_model.out create mode 100644 src/test/regress/expected/yb.orig.planner_base_scans_cost_model_colocated.out create mode 100644 src/test/regress/expected/yb.orig.planner_heuristic_selectivity.out create mode 100644 src/test/regress/expected/yb.orig.planner_indexes.out create mode 100644 src/test/regress/expected/yb.orig.planner_join_order.out create mode 100644 src/test/regress/expected/yb.orig.planner_joins.out create mode 100644 src/test/regress/expected/yb.orig.planner_size_estimates.out create mode 100644 src/test/regress/expected/yb.orig.planner_taqo_seek_next_estimation.out create mode 100644 src/test/regress/expected/yb.orig.planner_taqo_tuning_tests.out create mode 100644 src/test/regress/expected/yb.orig.plpgsql.out create mode 100644 src/test/regress/expected/yb.orig.profile.out create mode 100644 src/test/regress/expected/yb.orig.profile_permissions.out create mode 100644 src/test/regress/expected/yb.orig.publication.out create mode 100644 src/test/regress/expected/yb.orig.query_consistent_snapshot.out create mode 100644 src/test/regress/expected/yb.orig.reindex.out create mode 100644 src/test/regress/expected/yb.orig.replica_identity.out create mode 100644 src/test/regress/expected/yb.orig.replication_slot.out create mode 100644 src/test/regress/expected/yb.orig.reset_analyze.out create mode 100644 src/test/regress/expected/yb.orig.role_profile.out create mode 100644 src/test/regress/expected/yb.orig.roles.out create mode 100644 src/test/regress/expected/yb.orig.row_estimates.out create mode 100644 src/test/regress/expected/yb.orig.rowsecurity.out create mode 100644 src/test/regress/expected/yb.orig.schema.out create mode 100644 src/test/regress/expected/yb.orig.secondary_index_scan.out create mode 100644 src/test/regress/expected/yb.orig.select.out create mode 100644 src/test/regress/expected/yb.orig.select_large.out create mode 100644 src/test/regress/expected/yb.orig.select_large_abort.out create mode 100644 src/test/regress/expected/yb.orig.select_large_ddl.out create mode 100644 src/test/regress/expected/yb.orig.select_large_index_backfill.out create mode 100644 src/test/regress/expected/yb.orig.select_no_pushdown.out create mode 100644 src/test/regress/expected/yb.orig.select_parallel.out create mode 100644 src/test/regress/expected/yb.orig.stat.out create mode 100644 src/test/regress/expected/yb.orig.system_targets.out create mode 100644 src/test/regress/expected/yb.orig.tablegroup.out create mode 100644 src/test/regress/expected/yb.orig.tablegroup_alter.out create mode 100644 src/test/regress/expected/yb.orig.tablegroup_colocation_id.out create mode 100644 src/test/regress/expected/yb.orig.tablegroup_deprecation.out create mode 100644 src/test/regress/expected/yb.orig.tablegroup_dml.out create mode 100644 src/test/regress/expected/yb.orig.tablegroup_partitioned_table.out create mode 100644 src/test/regress/expected/yb.orig.tablegroup_permissions.out create mode 100644 src/test/regress/expected/yb.orig.tablespaces.out create mode 100644 src/test/regress/expected/yb.orig.tablet_metadata.out create mode 100644 src/test/regress/expected/yb.orig.time.out create mode 100644 src/test/regress/expected/yb.orig.transactions.out create mode 100644 src/test/regress/expected/yb.orig.triggers.out create mode 100644 src/test/regress/expected/yb.orig.truncate.out create mode 100644 src/test/regress/expected/yb.orig.ts_remote_filter.out create mode 100644 src/test/regress/expected/yb.orig.tsdicts.out create mode 100644 src/test/regress/expected/yb.orig.update_large.out create mode 100644 src/test/regress/expected/yb.orig.update_optimize_base.out create mode 100644 src/test/regress/expected/yb.orig.update_optimize_index_updates.out create mode 100644 src/test/regress/expected/yb.orig.update_optimize_indices.out create mode 100644 src/test/regress/expected/yb.orig.update_optimize_triggers.out create mode 100644 src/test/regress/expected/yb.orig.util.out create mode 100644 src/test/regress/expected/yb.orig.view.out create mode 100644 src/test/regress/expected/yb.orig.with.out create mode 100644 src/test/regress/expected/yb.orig.yb_get_tablet_for_key.out create mode 100644 src/test/regress/expected/yb.orig.yb_hash_code.out create mode 100644 src/test/regress/expected/yb.orig.yb_index_check.out create mode 100644 src/test/regress/expected/yb.orig.yb_index_check_1.out create mode 100644 src/test/regress/expected/yb.orig.ybgin.out create mode 100644 src/test/regress/expected/yb.orig.ybgin_cost_estimate.out create mode 100644 src/test/regress/expected/yb.orig.ybgin_misc.out create mode 100644 src/test/regress/expected/yb.orig.ybgin_nulls.out create mode 100644 src/test/regress/expected/yb.orig.ybgin_operators.out create mode 100644 src/test/regress/expected/yb.orig.ybgin_pushdown.out create mode 100644 src/test/regress/expected/yb.orig.ybgin_search_modes.out create mode 100644 src/test/regress/expected/yb.port.advisory_lock.out create mode 100644 src/test/regress/expected/yb.port.aggregates.out create mode 100644 src/test/regress/expected/yb.port.alter_generic.out create mode 100644 src/test/regress/expected/yb.port.alter_table.out create mode 100644 src/test/regress/expected/yb.port.arrays.out create mode 100644 src/test/regress/expected/yb.port.boolean.out create mode 100644 src/test/regress/expected/yb.port.box.out create mode 100644 src/test/regress/expected/yb.port.case.out create mode 100644 src/test/regress/expected/yb.port.collate.icu.utf8.out create mode 100644 src/test/regress/expected/yb.port.collate.out create mode 100644 src/test/regress/expected/yb.port.combocid.out create mode 100644 src/test/regress/expected/yb.port.compression.out create mode 100644 src/test/regress/expected/yb.port.constraints.out create mode 100644 src/test/regress/expected/yb.port.conversion.out create mode 100644 src/test/regress/expected/yb.port.copy.out create mode 100644 src/test/regress/expected/yb.port.copy2.out create mode 100644 src/test/regress/expected/yb.port.create_am.out create mode 100644 src/test/regress/expected/yb.port.create_function_c.out create mode 100644 src/test/regress/expected/yb.port.create_function_sql.out create mode 100644 src/test/regress/expected/yb.port.create_index.out create mode 100644 src/test/regress/expected/yb.port.create_misc.out create mode 100644 src/test/regress/expected/yb.port.create_role.out create mode 100644 src/test/regress/expected/yb.port.create_table.out create mode 100644 src/test/regress/expected/yb.port.create_table_like.out create mode 100644 src/test/regress/expected/yb.port.create_type.out create mode 100644 src/test/regress/expected/yb.port.create_view.out create mode 100644 src/test/regress/expected/yb.port.drop_if_exists.out create mode 100644 src/test/regress/expected/yb.port.drop_operator.out create mode 100644 src/test/regress/expected/yb.port.enum.out create mode 100644 src/test/regress/expected/yb.port.errors.out create mode 100644 src/test/regress/expected/yb.port.event_trigger.out create mode 100644 src/test/regress/expected/yb.port.expressions.out create mode 100644 src/test/regress/expected/yb.port.fast_default.out create mode 100644 src/test/regress/expected/yb.port.float4.out create mode 100644 src/test/regress/expected/yb.port.float8.out create mode 100644 src/test/regress/expected/yb.port.foreign_data.out create mode 100644 src/test/regress/expected/yb.port.foreign_key.out create mode 100644 src/test/regress/expected/yb.port.generated.out create mode 100644 src/test/regress/expected/yb.port.geometry.out create mode 100644 src/test/regress/expected/yb.port.gin.out create mode 100644 src/test/regress/expected/yb.port.groupingsets.out create mode 100644 src/test/regress/expected/yb.port.horology.out create mode 100644 src/test/regress/expected/yb.port.identity.out create mode 100644 src/test/regress/expected/yb.port.incremental_sort.out create mode 100644 src/test/regress/expected/yb.port.index_including.out create mode 100644 src/test/regress/expected/yb.port.indexing.out create mode 100644 src/test/regress/expected/yb.port.indirect_toast.out create mode 100644 src/test/regress/expected/yb.port.inet.out create mode 100644 src/test/regress/expected/yb.port.inherit.out create mode 100644 src/test/regress/expected/yb.port.insert.out create mode 100644 src/test/regress/expected/yb.port.insert_conflict.out create mode 100644 src/test/regress/expected/yb.port.insert_conflict_1.out create mode 100644 src/test/regress/expected/yb.port.int8.out create mode 100644 src/test/regress/expected/yb.port.join.out create mode 100644 src/test/regress/expected/yb.port.jsonb.out create mode 100644 src/test/regress/expected/yb.port.limit.out create mode 100644 src/test/regress/expected/yb.port.lock.out create mode 100644 src/test/regress/expected/yb.port.macaddr.out create mode 100644 src/test/regress/expected/yb.port.macaddr8.out create mode 100644 src/test/regress/expected/yb.port.matview.out create mode 100644 src/test/regress/expected/yb.port.misc.out create mode 100644 src/test/regress/expected/yb.port.misc_functions.out create mode 100644 src/test/regress/expected/yb.port.multirangetypes.out create mode 100644 src/test/regress/expected/yb.port.namespace.out create mode 100644 src/test/regress/expected/yb.port.numeric.out create mode 100644 src/test/regress/expected/yb.port.numeric_big.out create mode 100644 src/test/regress/expected/yb.port.partition_aggregate.out create mode 100644 src/test/regress/expected/yb.port.partition_join.out create mode 100644 src/test/regress/expected/yb.port.partition_prune.out create mode 100644 src/test/regress/expected/yb.port.pg_lsn.out create mode 100644 src/test/regress/expected/yb.port.plpgsql.out create mode 100644 src/test/regress/expected/yb.port.polygon.out create mode 100644 src/test/regress/expected/yb.port.portals.out create mode 100644 src/test/regress/expected/yb.port.prepare.out create mode 100644 src/test/regress/expected/yb.port.privileges.out create mode 100644 src/test/regress/expected/yb.port.publication.out create mode 100644 src/test/regress/expected/yb.port.rangetypes.out create mode 100644 src/test/regress/expected/yb.port.replica_identity.out create mode 100644 src/test/regress/expected/yb.port.rowsecurity.out create mode 100644 src/test/regress/expected/yb.port.rowtypes.out create mode 100644 src/test/regress/expected/yb.port.rules.out create mode 100644 src/test/regress/expected/yb.port.select.out create mode 100644 src/test/regress/expected/yb.port.select_distinct.out create mode 100644 src/test/regress/expected/yb.port.sequence.out create mode 100644 src/test/regress/expected/yb.port.stats_ext.out create mode 100644 src/test/regress/expected/yb.port.stats_import.out create mode 100644 src/test/regress/expected/yb.port.strings.out create mode 100644 src/test/regress/expected/yb.port.subselect.out create mode 100644 src/test/regress/expected/yb.port.test_setup.out create mode 100644 src/test/regress/expected/yb.port.test_setup_1.out create mode 100644 src/test/regress/expected/yb.port.text.out create mode 100644 src/test/regress/expected/yb.port.transactions.out create mode 100644 src/test/regress/expected/yb.port.triggers.out create mode 100644 src/test/regress/expected/yb.port.truncate.out create mode 100644 src/test/regress/expected/yb.port.tsdicts.out create mode 100644 src/test/regress/expected/yb.port.tsearch.out create mode 100644 src/test/regress/expected/yb.port.update.out create mode 100644 src/test/regress/expected/yb.port.uuid.out create mode 100644 src/test/regress/expected/yb.port.varchar.out create mode 100644 src/test/regress/expected/yb.port.window.out create mode 100644 src/test/regress/expected/yb.port.with.out create mode 100644 src/test/regress/expected/yb.port.xmlmap.out create mode 100644 src/test/regress/sql/yb.depd.create_index.sql create mode 100644 src/test/regress/sql/yb.depd.float8_tbl.sql create mode 100644 src/test/regress/sql/yb.depd.int4_tbl.sql create mode 100644 src/test/regress/sql/yb.depd.int8_tbl.sql create mode 100644 src/test/regress/sql/yb.depd.onek.sql create mode 100644 src/test/regress/sql/yb.depd.onek2.sql create mode 100644 src/test/regress/sql/yb.depd.part_test_int4_ops.sql create mode 100644 src/test/regress/sql/yb.depd.person.sql create mode 100644 src/test/regress/sql/yb.depd.tenk1.sql create mode 100644 src/test/regress/sql/yb.depd.tenk2.sql create mode 100644 src/test/regress/sql/yb.orig.aggregates.sql create mode 100644 src/test/regress/sql/yb.orig.alter_operator.sql create mode 100644 src/test/regress/sql/yb.orig.alter_table.sql create mode 100644 src/test/regress/sql/yb.orig.alter_table_rewrite.sql create mode 100644 src/test/regress/sql/yb.orig.alter_type.sql create mode 100644 src/test/regress/sql/yb.orig.analyze.sql create mode 100644 src/test/regress/sql/yb.orig.analyze_system_tables.sql create mode 100644 src/test/regress/sql/yb.orig.async.sql create mode 100644 src/test/regress/sql/yb.orig.base_type.sql create mode 100644 src/test/regress/sql/yb.orig.beta_extensions.sql create mode 100644 src/test/regress/sql/yb.orig.bitmap_scan_plan.sql create mode 100644 src/test/regress/sql/yb.orig.bitmap_scans.sql create mode 100644 src/test/regress/sql/yb.orig.bitmap_scans_colo.sql create mode 100644 src/test/regress/sql/yb.orig.bitmap_scans_distinct.sql create mode 100644 src/test/regress/sql/yb.orig.bitmap_scans_flags.sql create mode 100644 src/test/regress/sql/yb.orig.bitmap_scans_joins.sql create mode 100644 src/test/regress/sql/yb.orig.bitmap_scans_system.sql create mode 100644 src/test/regress/sql/yb.orig.boolean.sql create mode 100644 src/test/regress/sql/yb.orig.buffering_with_sql_errors.sql create mode 100644 src/test/regress/sql/yb.orig.bytea.sql create mode 100644 src/test/regress/sql/yb.orig.cacheinfo.sql create mode 100644 src/test/regress/sql/yb.orig.catalog_cache_hashing.sql create mode 100644 src/test/regress/sql/yb.orig.catalog_version.sql create mode 100644 src/test/regress/sql/yb.orig.catalog_version_increment.sql create mode 100644 src/test/regress/sql/yb.orig.char.sql create mode 100644 src/test/regress/sql/yb.orig.collate.icu.utf8.sql create mode 100644 src/test/regress/sql/yb.orig.colocated_tables_with_tablespaces.sql create mode 100644 src/test/regress/sql/yb.orig.compound_key.sql create mode 100644 src/test/regress/sql/yb.orig.copy_55k_rows.sql create mode 100644 src/test/regress/sql/yb.orig.create_function.sql create mode 100644 src/test/regress/sql/yb.orig.create_index.sql create mode 100644 src/test/regress/sql/yb.orig.create_index_large_table_backfill_index.sql create mode 100644 src/test/regress/sql/yb.orig.create_index_update_reltuples.sql create mode 100644 src/test/regress/sql/yb.orig.create_language.sql create mode 100644 src/test/regress/sql/yb.orig.create_large_table.sql create mode 100644 src/test/regress/sql/yb.orig.create_large_table_indexes.sql create mode 100644 src/test/regress/sql/yb.orig.create_large_table_tablet_split.sql create mode 100644 src/test/regress/sql/yb.orig.create_table.sql create mode 100644 src/test/regress/sql/yb.orig.create_table_55k_secondary_index.sql create mode 100644 src/test/regress/sql/yb.orig.create_table_like.sql create mode 100644 src/test/regress/sql/yb.orig.create_table_scattering_keys.sql create mode 100644 src/test/regress/sql/yb.orig.create_type.sql create mode 100644 src/test/regress/sql/yb.orig.ctid.sql create mode 100644 src/test/regress/sql/yb.orig.cursor.sql create mode 100644 src/test/regress/sql/yb.orig.cursor_preserve_in_txn_limit.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_exceptions.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_rc_rollback.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_savepoint.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_savepoint_toggle.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_txn_block.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_txn_copy.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_txn_event_triggers.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_txn_fk.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_txn_indices.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_txn_isolation.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_txn_procedures.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_txn_recursive_functions.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_txn_triggers.sql create mode 100644 src/test/regress/sql/yb.orig.ddl_txn_visibility.sql create mode 100644 src/test/regress/sql/yb.orig.depend.sql create mode 100644 src/test/regress/sql/yb.orig.dependency.sql create mode 100644 src/test/regress/sql/yb.orig.distinct_pushdown_base.sql create mode 100644 src/test/regress/sql/yb.orig.distinct_pushdown_join.sql create mode 100644 src/test/regress/sql/yb.orig.distinct_pushdown_pred.sql create mode 100644 src/test/regress/sql/yb.orig.dml.sql create mode 100644 src/test/regress/sql/yb.orig.dml_derived_equalities.sql create mode 100644 src/test/regress/sql/yb.orig.dml_insert_conflict.sql create mode 100644 src/test/regress/sql/yb.orig.dml_key_pushdown.sql create mode 100644 src/test/regress/sql/yb.orig.dml_pushdown.sql create mode 100644 src/test/regress/sql/yb.orig.dml_read_time.sql create mode 100644 src/test/regress/sql/yb.orig.dml_scankey.sql create mode 100644 src/test/regress/sql/yb.orig.dml_single_row.sql create mode 100644 src/test/regress/sql/yb.orig.dml_systable_scan.sql create mode 100644 src/test/regress/sql/yb.orig.drop_table.sql create mode 100644 src/test/regress/sql/yb.orig.errors.sql create mode 100644 src/test/regress/sql/yb.orig.event_trigger.sql create mode 100644 src/test/regress/sql/yb.orig.explain.sql create mode 100644 src/test/regress/sql/yb.orig.explicit_row_lock_batching.sql create mode 100644 src/test/regress/sql/yb.orig.explicit_row_lock_planning.sql create mode 100644 src/test/regress/sql/yb.orig.extensions.sql create mode 100644 src/test/regress/sql/yb.orig.feature_alter_rename.sql create mode 100644 src/test/regress/sql/yb.orig.feature_alter_table.sql create mode 100644 src/test/regress/sql/yb.orig.feature_colocation.sql create mode 100644 src/test/regress/sql/yb.orig.feature_config.sql create mode 100644 src/test/regress/sql/yb.orig.feature_copy.sql create mode 100644 src/test/regress/sql/yb.orig.feature_copyselect.sql create mode 100644 src/test/regress/sql/yb.orig.feature_db.sql create mode 100644 src/test/regress/sql/yb.orig.feature_delete.sql create mode 100644 src/test/regress/sql/yb.orig.feature_hash_types.sql create mode 100644 src/test/regress/sql/yb.orig.feature_insert.sql create mode 100644 src/test/regress/sql/yb.orig.feature_legacy_colocation.sql create mode 100644 src/test/regress/sql/yb.orig.feature_pk_split.sql create mode 100644 src/test/regress/sql/yb.orig.feature_pk_split_desc.sql create mode 100644 src/test/regress/sql/yb.orig.feature_range_types.sql create mode 100644 src/test/regress/sql/yb.orig.feature_select.sql create mode 100644 src/test/regress/sql/yb.orig.feature_temp.sql create mode 100644 src/test/regress/sql/yb.orig.feature_types.sql create mode 100644 src/test/regress/sql/yb.orig.feature_update.sql create mode 100644 src/test/regress/sql/yb.orig.fetch_limits.sql create mode 100644 src/test/regress/sql/yb.orig.fetch_limits_joins.sql create mode 100644 src/test/regress/sql/yb.orig.foreign_data.sql create mode 100644 src/test/regress/sql/yb.orig.foreign_key.sql create mode 100644 src/test/regress/sql/yb.orig.function.sql create mode 100644 src/test/regress/sql/yb.orig.functional_index_multiple_column.sql create mode 100644 src/test/regress/sql/yb.orig.functional_indexes.sql create mode 100644 src/test/regress/sql/yb.orig.generated.sql create mode 100644 src/test/regress/sql/yb.orig.get_current_transaction_priority.sql create mode 100644 src/test/regress/sql/yb.orig.get_range_split_clause.sql create mode 100644 src/test/regress/sql/yb.orig.gin.sql create mode 100644 src/test/regress/sql/yb.orig.guc.sql create mode 100644 src/test/regress/sql/yb.orig.hash_func.sql create mode 100644 src/test/regress/sql/yb.orig.hash_in_queries.sql create mode 100644 src/test/regress/sql/yb.orig.hdr_percentile.sql create mode 100644 src/test/regress/sql/yb.orig.hints.sql create mode 100644 src/test/regress/sql/yb.orig.index_including.sql create mode 100644 src/test/regress/sql/yb.orig.index_only_scan_decoded_pk.sql create mode 100644 src/test/regress/sql/yb.orig.index_recheck.sql create mode 100644 src/test/regress/sql/yb.orig.index_scan.sql create mode 100644 src/test/regress/sql/yb.orig.index_scan_null_asc.sql create mode 100644 src/test/regress/sql/yb.orig.index_scan_null_create.sql create mode 100644 src/test/regress/sql/yb.orig.index_scan_null_hash.sql create mode 100644 src/test/regress/sql/yb.orig.index_selectivity.sql create mode 100644 src/test/regress/sql/yb.orig.indexing_order.sql create mode 100644 src/test/regress/sql/yb.orig.inherit.sql create mode 100644 src/test/regress/sql/yb.orig.insert_on_conflict.sql create mode 100644 src/test/regress/sql/yb.orig.insert_on_conflict_multiplan.sql create mode 100644 src/test/regress/sql/yb.orig.insert_on_conflict_with.sql create mode 100644 src/test/regress/sql/yb.orig.insert_on_conflict_with_explain.sql create mode 100644 src/test/regress/sql/yb.orig.insert_on_conflict_with_spi.sql create mode 100644 src/test/regress/sql/yb.orig.int2_array.sql create mode 100644 src/test/regress/sql/yb.orig.int4_array.sql create mode 100644 src/test/regress/sql/yb.orig.internal_subtransactions.sql create mode 100644 src/test/regress/sql/yb.orig.interval.sql create mode 100644 src/test/regress/sql/yb.orig.is_local_table.sql create mode 100644 src/test/regress/sql/yb.orig.join_batching.sql create mode 100644 src/test/regress/sql/yb.orig.join_batching_parallel.sql create mode 100644 src/test/regress/sql/yb.orig.join_batching_plans.sql create mode 100644 src/test/regress/sql/yb.orig.large_copy.sql create mode 100644 src/test/regress/sql/yb.orig.large_copy_abort.sql create mode 100644 src/test/regress/sql/yb.orig.large_copy_ddl.sql create mode 100644 src/test/regress/sql/yb.orig.legacy_join_plans.sql create mode 100644 src/test/regress/sql/yb.orig.levenshtein_pushdown.sql create mode 100644 src/test/regress/sql/yb.orig.local_tablets.sql create mode 100644 src/test/regress/sql/yb.orig.lock_row_for_update.sql create mode 100644 src/test/regress/sql/yb.orig.lock_status.sql create mode 100644 src/test/regress/sql/yb.orig.matview.sql create mode 100644 src/test/regress/sql/yb.orig.merge_scan_colocated.sql create mode 100644 src/test/regress/sql/yb.orig.merge_scan_derive.sql create mode 100644 src/test/regress/sql/yb.orig.merge_scan_hash.sql create mode 100644 src/test/regress/sql/yb.orig.merge_scan_join.sql create mode 100644 src/test/regress/sql/yb.orig.merge_scan_partition.sql create mode 100644 src/test/regress/sql/yb.orig.merge_scan_range.sql create mode 100644 src/test/regress/sql/yb.orig.merge_scan_setup.sql create mode 100644 src/test/regress/sql/yb.orig.merge_scan_temp.sql create mode 100644 src/test/regress/sql/yb.orig.money.sql create mode 100644 src/test/regress/sql/yb.orig.no_explicit_target.sql create mode 100644 src/test/regress/sql/yb.orig.non_txn_copy.sql create mode 100644 src/test/regress/sql/yb.orig.numeric.sql create mode 100644 src/test/regress/sql/yb.orig.obj_properties_functions.sql create mode 100644 src/test/regress/sql/yb.orig.oid.sql create mode 100644 src/test/regress/sql/yb.orig.operator_class.sql create mode 100644 src/test/regress/sql/yb.orig.optimizer_guc.sql create mode 100644 src/test/regress/sql/yb.orig.parallel_append.sql create mode 100644 src/test/regress/sql/yb.orig.parallel_big_colocated.sql create mode 100644 src/test/regress/sql/yb.orig.parallel_colocated.sql create mode 100644 src/test/regress/sql/yb.orig.parallel_hashed.sql create mode 100644 src/test/regress/sql/yb.orig.parallel_plans.sql create mode 100644 src/test/regress/sql/yb.orig.parallel_ranged.sql create mode 100644 src/test/regress/sql/yb.orig.parallel_snapshot_switching.sql create mode 100644 src/test/regress/sql/yb.orig.partition_aggregate.sql create mode 100644 src/test/regress/sql/yb.orig.partition_alter.sql create mode 100644 src/test/regress/sql/yb.orig.partition_fk.sql create mode 100644 src/test/regress/sql/yb.orig.partition_out_of_order.sql create mode 100644 src/test/regress/sql/yb.orig.partition_pk.sql create mode 100644 src/test/regress/sql/yb.orig.partition_prune.sql create mode 100644 src/test/regress/sql/yb.orig.partition_prune_plancache.sql create mode 100644 src/test/regress/sql/yb.orig.perf_secondary_index_scan.sql create mode 100644 src/test/regress/sql/yb.orig.pg_stat.sql create mode 100644 src/test/regress/sql/yb.orig.pg_stat_backend.sql create mode 100644 src/test/regress/sql/yb.orig.planner_base_scans_cost_model.sql create mode 100644 src/test/regress/sql/yb.orig.planner_base_scans_cost_model_colocated.sql create mode 100644 src/test/regress/sql/yb.orig.planner_heuristic_selectivity.sql create mode 100644 src/test/regress/sql/yb.orig.planner_indexes.sql create mode 100644 src/test/regress/sql/yb.orig.planner_join_order.sql create mode 100644 src/test/regress/sql/yb.orig.planner_joins.sql create mode 100644 src/test/regress/sql/yb.orig.planner_size_estimates.sql create mode 100644 src/test/regress/sql/yb.orig.planner_taqo_seek_next_estimation.sql create mode 100644 src/test/regress/sql/yb.orig.planner_taqo_tuning_tests.sql create mode 100644 src/test/regress/sql/yb.orig.plpgsql.sql create mode 100644 src/test/regress/sql/yb.orig.profile.sql create mode 100644 src/test/regress/sql/yb.orig.profile_permissions.sql create mode 100644 src/test/regress/sql/yb.orig.publication.sql create mode 100644 src/test/regress/sql/yb.orig.query_consistent_snapshot.sql create mode 100644 src/test/regress/sql/yb.orig.reindex.sql create mode 100644 src/test/regress/sql/yb.orig.replica_identity.sql create mode 100644 src/test/regress/sql/yb.orig.replication_slot.sql create mode 100644 src/test/regress/sql/yb.orig.reset_analyze.sql create mode 100644 src/test/regress/sql/yb.orig.role_profile.sql create mode 100644 src/test/regress/sql/yb.orig.roles.sql create mode 100644 src/test/regress/sql/yb.orig.row_estimates.sql create mode 100644 src/test/regress/sql/yb.orig.rowsecurity.sql create mode 100644 src/test/regress/sql/yb.orig.schema.sql create mode 100644 src/test/regress/sql/yb.orig.secondary_index_scan.sql create mode 100644 src/test/regress/sql/yb.orig.select.sql create mode 100644 src/test/regress/sql/yb.orig.select_large.sql create mode 100644 src/test/regress/sql/yb.orig.select_large_abort.sql create mode 100644 src/test/regress/sql/yb.orig.select_large_ddl.sql create mode 100644 src/test/regress/sql/yb.orig.select_large_index_backfill.sql create mode 100644 src/test/regress/sql/yb.orig.select_no_pushdown.sql create mode 100644 src/test/regress/sql/yb.orig.select_parallel.sql create mode 100644 src/test/regress/sql/yb.orig.stat.sql create mode 100644 src/test/regress/sql/yb.orig.system_targets.sql create mode 100644 src/test/regress/sql/yb.orig.tablegroup.sql create mode 100644 src/test/regress/sql/yb.orig.tablegroup_alter.sql create mode 100644 src/test/regress/sql/yb.orig.tablegroup_colocation_id.sql create mode 100644 src/test/regress/sql/yb.orig.tablegroup_deprecation.sql create mode 100644 src/test/regress/sql/yb.orig.tablegroup_dml.sql create mode 100644 src/test/regress/sql/yb.orig.tablegroup_partitioned_table.sql create mode 100644 src/test/regress/sql/yb.orig.tablegroup_permissions.sql create mode 100644 src/test/regress/sql/yb.orig.tablespaces.sql create mode 100644 src/test/regress/sql/yb.orig.tablet_metadata.sql create mode 100644 src/test/regress/sql/yb.orig.time.sql create mode 100644 src/test/regress/sql/yb.orig.transactions.sql create mode 100644 src/test/regress/sql/yb.orig.triggers.sql create mode 100644 src/test/regress/sql/yb.orig.truncate.sql create mode 100644 src/test/regress/sql/yb.orig.ts_remote_filter.sql create mode 100644 src/test/regress/sql/yb.orig.tsdicts.sql create mode 100644 src/test/regress/sql/yb.orig.update_large.sql create mode 100644 src/test/regress/sql/yb.orig.update_optimize_base.sql create mode 100644 src/test/regress/sql/yb.orig.update_optimize_index_updates.sql create mode 100644 src/test/regress/sql/yb.orig.update_optimize_indices.sql create mode 100644 src/test/regress/sql/yb.orig.update_optimize_triggers.sql create mode 100644 src/test/regress/sql/yb.orig.util.sql create mode 100644 src/test/regress/sql/yb.orig.view.sql create mode 100644 src/test/regress/sql/yb.orig.with.sql create mode 100644 src/test/regress/sql/yb.orig.yb_get_tablet_for_key.sql create mode 100644 src/test/regress/sql/yb.orig.yb_hash_code.sql create mode 100644 src/test/regress/sql/yb.orig.yb_index_check.sql create mode 100644 src/test/regress/sql/yb.orig.ybgin.sql create mode 100644 src/test/regress/sql/yb.orig.ybgin_cost_estimate.sql create mode 100644 src/test/regress/sql/yb.orig.ybgin_misc.sql create mode 100644 src/test/regress/sql/yb.orig.ybgin_nulls.sql create mode 100644 src/test/regress/sql/yb.orig.ybgin_operators.sql create mode 100644 src/test/regress/sql/yb.orig.ybgin_pushdown.sql create mode 100644 src/test/regress/sql/yb.orig.ybgin_search_modes.sql create mode 100644 src/test/regress/sql/yb.port.advisory_lock.sql create mode 100644 src/test/regress/sql/yb.port.aggregates.sql create mode 100644 src/test/regress/sql/yb.port.alter_generic.sql create mode 100644 src/test/regress/sql/yb.port.alter_table.sql create mode 100644 src/test/regress/sql/yb.port.arrays.sql create mode 100644 src/test/regress/sql/yb.port.boolean.sql create mode 100644 src/test/regress/sql/yb.port.box.sql create mode 100644 src/test/regress/sql/yb.port.case.sql create mode 100644 src/test/regress/sql/yb.port.collate.icu.utf8.sql create mode 100644 src/test/regress/sql/yb.port.collate.sql create mode 100644 src/test/regress/sql/yb.port.combocid.sql create mode 100644 src/test/regress/sql/yb.port.compression.sql create mode 100644 src/test/regress/sql/yb.port.constraints.sql create mode 100644 src/test/regress/sql/yb.port.conversion.sql create mode 100644 src/test/regress/sql/yb.port.copy.sql create mode 100644 src/test/regress/sql/yb.port.copy2.sql create mode 100644 src/test/regress/sql/yb.port.create_am.sql create mode 100644 src/test/regress/sql/yb.port.create_function_c.sql create mode 100644 src/test/regress/sql/yb.port.create_function_sql.sql create mode 100644 src/test/regress/sql/yb.port.create_index.sql create mode 100644 src/test/regress/sql/yb.port.create_misc.sql create mode 100644 src/test/regress/sql/yb.port.create_role.sql create mode 100644 src/test/regress/sql/yb.port.create_table.sql create mode 100644 src/test/regress/sql/yb.port.create_table_like.sql create mode 100644 src/test/regress/sql/yb.port.create_type.sql create mode 100644 src/test/regress/sql/yb.port.create_view.sql create mode 100644 src/test/regress/sql/yb.port.drop_if_exists.sql create mode 100644 src/test/regress/sql/yb.port.drop_operator.sql create mode 100644 src/test/regress/sql/yb.port.enum.sql create mode 100644 src/test/regress/sql/yb.port.errors.sql create mode 100644 src/test/regress/sql/yb.port.event_trigger.sql create mode 100644 src/test/regress/sql/yb.port.expressions.sql create mode 100644 src/test/regress/sql/yb.port.fast_default.sql create mode 100644 src/test/regress/sql/yb.port.float4.sql create mode 100644 src/test/regress/sql/yb.port.float8.sql create mode 100644 src/test/regress/sql/yb.port.foreign_data.sql create mode 100644 src/test/regress/sql/yb.port.foreign_key.sql create mode 100644 src/test/regress/sql/yb.port.generated.sql create mode 100644 src/test/regress/sql/yb.port.geometry.sql create mode 100644 src/test/regress/sql/yb.port.gin.sql create mode 100644 src/test/regress/sql/yb.port.groupingsets.sql create mode 100644 src/test/regress/sql/yb.port.horology.sql create mode 100644 src/test/regress/sql/yb.port.identity.sql create mode 100644 src/test/regress/sql/yb.port.incremental_sort.sql create mode 100644 src/test/regress/sql/yb.port.index_including.sql create mode 100644 src/test/regress/sql/yb.port.indexing.sql create mode 100644 src/test/regress/sql/yb.port.indirect_toast.sql create mode 100644 src/test/regress/sql/yb.port.inet.sql create mode 100644 src/test/regress/sql/yb.port.inherit.sql create mode 100644 src/test/regress/sql/yb.port.insert.sql create mode 100644 src/test/regress/sql/yb.port.insert_conflict.sql create mode 100644 src/test/regress/sql/yb.port.int8.sql create mode 100644 src/test/regress/sql/yb.port.join.sql create mode 100644 src/test/regress/sql/yb.port.jsonb.sql create mode 100644 src/test/regress/sql/yb.port.limit.sql create mode 100644 src/test/regress/sql/yb.port.lock.sql create mode 100644 src/test/regress/sql/yb.port.macaddr.sql create mode 100644 src/test/regress/sql/yb.port.macaddr8.sql create mode 100644 src/test/regress/sql/yb.port.matview.sql create mode 100644 src/test/regress/sql/yb.port.misc.sql create mode 100644 src/test/regress/sql/yb.port.misc_functions.sql create mode 100644 src/test/regress/sql/yb.port.multirangetypes.sql create mode 100644 src/test/regress/sql/yb.port.namespace.sql create mode 100644 src/test/regress/sql/yb.port.numeric.sql create mode 100644 src/test/regress/sql/yb.port.numeric_big.sql create mode 100644 src/test/regress/sql/yb.port.partition_aggregate.sql create mode 100644 src/test/regress/sql/yb.port.partition_join.sql create mode 100644 src/test/regress/sql/yb.port.partition_prune.sql create mode 100644 src/test/regress/sql/yb.port.pg_lsn.sql create mode 100644 src/test/regress/sql/yb.port.plpgsql.sql create mode 100644 src/test/regress/sql/yb.port.polygon.sql create mode 100644 src/test/regress/sql/yb.port.portals.sql create mode 100644 src/test/regress/sql/yb.port.prepare.sql create mode 100644 src/test/regress/sql/yb.port.privileges.sql create mode 100644 src/test/regress/sql/yb.port.publication.sql create mode 100644 src/test/regress/sql/yb.port.rangetypes.sql create mode 100644 src/test/regress/sql/yb.port.replica_identity.sql create mode 100644 src/test/regress/sql/yb.port.rowsecurity.sql create mode 100644 src/test/regress/sql/yb.port.rowtypes.sql create mode 100644 src/test/regress/sql/yb.port.rules.sql create mode 100644 src/test/regress/sql/yb.port.select.sql create mode 100644 src/test/regress/sql/yb.port.select_distinct.sql create mode 100644 src/test/regress/sql/yb.port.sequence.sql create mode 100644 src/test/regress/sql/yb.port.stats_ext.sql create mode 100644 src/test/regress/sql/yb.port.stats_import.sql create mode 100644 src/test/regress/sql/yb.port.strings.sql create mode 100644 src/test/regress/sql/yb.port.subselect.sql create mode 100644 src/test/regress/sql/yb.port.test_setup.sql create mode 100644 src/test/regress/sql/yb.port.text.sql create mode 100644 src/test/regress/sql/yb.port.transactions.sql create mode 100644 src/test/regress/sql/yb.port.triggers.sql create mode 100644 src/test/regress/sql/yb.port.truncate.sql create mode 100644 src/test/regress/sql/yb.port.tsdicts.sql create mode 100644 src/test/regress/sql/yb.port.tsearch.sql create mode 100644 src/test/regress/sql/yb.port.update.sql create mode 100644 src/test/regress/sql/yb.port.uuid.sql create mode 100644 src/test/regress/sql/yb.port.varchar.sql create mode 100644 src/test/regress/sql/yb.port.window.sql create mode 100644 src/test/regress/sql/yb.port.with.sql create mode 100644 src/test/regress/sql/yb.port.xmlmap.sql create mode 100644 src/test/regress/yb_analyze_schedule create mode 100644 src/test/regress/yb_arrays_schedule create mode 100644 src/test/regress/yb_async_schedule create mode 100644 src/test/regress/yb_auth_schedule create mode 100644 src/test/regress/yb_beta_extensions_schedule create mode 100644 src/test/regress/yb_big_parallelquery_serial_schedule create mode 100644 src/test/regress/yb_bitmap_plan_schedule create mode 100644 src/test/regress/yb_bitmap_scans_schedule create mode 100644 src/test/regress/yb_buffering_with_sql_errors_schedule create mode 100644 src/test/regress/yb_catalog_cache_schedule create mode 100644 src/test/regress/yb_colocated_tables_with_tablespaces_schedule create mode 100644 src/test/regress/yb_commands/bitmap_hint_iter_Q2.sql create mode 100644 src/test/regress/yb_commands/index_scan_null_queries.sql create mode 100644 src/test/regress/yb_commands/iter_P2.sql create mode 100644 src/test/regress/yb_commands/iter_P3.sql create mode 100644 src/test/regress/yb_commands/iter_P4.sql create mode 100644 src/test/regress/yb_commands/iter_P5.sql create mode 100644 src/test/regress/yb_commands/iter_Q2.sql create mode 100644 src/test/regress/yb_commands/iter_Q3.sql create mode 100644 src/test/regress/yb_commands/iter_Q4.sql create mode 100644 src/test/regress/yb_commands/iter_Q5.sql create mode 100644 src/test/regress/yb_commands/iter_R2.sql create mode 100644 src/test/regress/yb_commands/iter_R3.sql create mode 100644 src/test/regress/yb_commands/iter_R4.sql create mode 100644 src/test/regress/yb_commands/iter_R5.sql create mode 100644 src/test/regress/yb_commands/iter_query.sql create mode 100644 src/test/regress/yb_commands/legacy_join_plan_queries.sql create mode 100644 src/test/regress/yb_commands/legacy_join_plan_queries_with_costs.sql create mode 100644 src/test/regress/yb_commands/merge_scan_setup.sql create mode 100644 src/test/regress/yb_commands/parallel_snapshot_switching_query.sql create mode 100644 src/test/regress/yb_commands/parallel_snapshot_switching_query_with_gather_merge.sql create mode 100644 src/test/regress/yb_commands/parameterized_query.sql create mode 100644 src/test/regress/yb_commands/planner_size_estimates_cmds.sql create mode 100644 src/test/regress/yb_ctid_schedule create mode 100644 src/test/regress/yb_cursor_low_prefetching_schedule create mode 100644 src/test/regress/yb_cursor_schedule create mode 100644 src/test/regress/yb_ddl_savepoint_schedule create mode 100644 src/test/regress/yb_ddl_txn_block_schedule create mode 100644 src/test/regress/yb_distinct_pushdown_schedule create mode 100644 src/test/regress/yb_dml_serial_schedule create mode 100644 src/test/regress/yb_extensions_schedule create mode 100644 src/test/regress/yb_feature_colocation_schedule create mode 100644 src/test/regress/yb_feature_database_serial_schedule create mode 100644 src/test/regress/yb_feature_legacy_colocation_schedule create mode 100644 src/test/regress/yb_feature_partition_serial_schedule create mode 100644 src/test/regress/yb_feature_serial_schedule create mode 100644 src/test/regress/yb_fetch_limit_schedule create mode 100644 src/test/regress/yb_foreign_data_schedule create mode 100644 src/test/regress/yb_foreign_key_schedule create mode 100644 src/test/regress/yb_gin_schedule create mode 100644 src/test/regress/yb_hash_in_schedule create mode 100644 src/test/regress/yb_hash_index_serial_schedule create mode 100644 src/test/regress/yb_index_check_schedule create mode 100644 src/test/regress/yb_index_schedule create mode 100644 src/test/regress/yb_inherit_schedule create mode 100644 src/test/regress/yb_insert_on_conflict_schedule create mode 100644 src/test/regress/yb_join_plans_schedule create mode 100644 src/test/regress/yb_join_schedule create mode 100644 src/test/regress/yb_large_table_backfill_index_schedule create mode 100644 src/test/regress/yb_large_table_serial_schedule create mode 100644 src/test/regress/yb_large_table_tablet_split_schedule create mode 100644 src/test/regress/yb_levenshtein_pushdown_schedule create mode 100644 src/test/regress/yb_limit_schedule create mode 100644 src/test/regress/yb_matview_schedule create mode 100644 src/test/regress/yb_merge_scan_schedule create mode 100644 src/test/regress/yb_misc_catalog_version_increment_schedule create mode 100644 src/test/regress/yb_misc_independent_schedule create mode 100644 src/test/regress/yb_misc_serial2_schedule create mode 100644 src/test/regress/yb_misc_serial3_schedule create mode 100644 src/test/regress/yb_misc_serial4_schedule create mode 100644 src/test/regress/yb_misc_serial5_schedule create mode 100644 src/test/regress/yb_misc_serial_schedule create mode 100644 src/test/regress/yb_non_txn_copy_schedule create mode 100644 src/test/regress/yb_parallel_plans_schedule create mode 100644 src/test/regress/yb_parallelquery_serial_schedule create mode 100644 src/test/regress/yb_partition_prune_schedule create mode 100644 src/test/regress/yb_partitions_schedule create mode 100644 src/test/regress/yb_percentile_schedule create mode 100644 src/test/regress/yb_pg_advisory_lock_schedule create mode 100644 src/test/regress/yb_pg_aggregates_schedule create mode 100644 src/test/regress/yb_pg_async_schedule create mode 100644 src/test/regress/yb_pg_auth_schedule create mode 100644 src/test/regress/yb_pg_constraints_schedule create mode 100644 src/test/regress/yb_pg_copy_schedule create mode 100644 src/test/regress/yb_pg_dml_serial_schedule create mode 100644 src/test/regress/yb_pg_foreign_data_schedule create mode 100644 src/test/regress/yb_pg_foreign_key_schedule create mode 100644 src/test/regress/yb_pg_generated_schedule create mode 100644 src/test/regress/yb_pg_index_schedule create mode 100644 src/test/regress/yb_pg_inherit_schedule create mode 100644 src/test/regress/yb_pg_join_schedule create mode 100644 src/test/regress/yb_pg_json_schedule create mode 100644 src/test/regress/yb_pg_lock_schedule create mode 100644 src/test/regress/yb_pg_matview_schedule create mode 100644 src/test/regress/yb_pg_misc_independent_1_schedule create mode 100644 src/test/regress/yb_pg_misc_independent_2_schedule create mode 100644 src/test/regress/yb_pg_misc_serial_schedule create mode 100644 src/test/regress/yb_pg_partition_join_schedule create mode 100644 src/test/regress/yb_pg_partitions_misc_schedule create mode 100644 src/test/regress/yb_pg_plpgsql_schedule create mode 100644 src/test/regress/yb_pg_pushdown_serial_schedule create mode 100644 src/test/regress/yb_pg_rules_schedule create mode 100644 src/test/regress/yb_pg_select_schedule create mode 100644 src/test/regress/yb_pg_stat_schedule create mode 100644 src/test/regress/yb_pg_stats_ext_schedule create mode 100644 src/test/regress/yb_pg_stats_import_schedule create mode 100644 src/test/regress/yb_pg_table_schedule create mode 100644 src/test/regress/yb_pg_transactions_schedule create mode 100644 src/test/regress/yb_pg_types_datetime_schedule create mode 100644 src/test/regress/yb_pg_types_geo_serial_schedule create mode 100644 src/test/regress/yb_pg_types_misc_serial_schedule create mode 100644 src/test/regress/yb_pg_types_numeric_schedule create mode 100644 src/test/regress/yb_pg_types_string_schedule create mode 100644 src/test/regress/yb_pg_types_udt_serial_schedule create mode 100644 src/test/regress/yb_pg_window_schedule create mode 100644 src/test/regress/yb_pg_xml_schedule create mode 100644 src/test/regress/yb_planner_serial_schedule create mode 100644 src/test/regress/yb_planner_taqo_schedule create mode 100644 src/test/regress/yb_proc_schedule create mode 100644 src/test/regress/yb_profile_schedule create mode 100644 src/test/regress/yb_publication_schedule create mode 100644 src/test/regress/yb_pushdown_serial_schedule create mode 100644 src/test/regress/yb_replica_identity_schedule create mode 100644 src/test/regress/yb_replication_slot_schedule create mode 100644 src/test/regress/yb_reset_analyze_schedule create mode 100644 src/test/regress/yb_scan_target_schedule create mode 100644 src/test/regress/yb_secondary_index_scan_serial_schedule create mode 100644 src/test/regress/yb_stat_schedule create mode 100644 src/test/regress/yb_table_schedule create mode 100644 src/test/regress/yb_tablegroup_deprecation_schedule create mode 100644 src/test/regress/yb_tablegroups_schedule create mode 100644 src/test/regress/yb_tablespaces_schedule create mode 100644 src/test/regress/yb_transactions_schedule create mode 100644 src/test/regress/yb_triggers_schedule create mode 100644 src/test/regress/yb_types_datetime_schedule create mode 100644 src/test/regress/yb_types_numeric_schedule create mode 100644 src/test/regress/yb_types_string_schedule create mode 100644 src/test/regress/yb_types_udt_serial_schedule create mode 100644 src/test/regress/yb_update_optimized_schedule create mode 100644 src/tools/pgindent/yb_exclude_file_patterns create mode 100755 src/tools/pgindent/yb_pgindent create mode 100644 src/tools/pgindent/yb_thirdparty_typedefs.list create mode 100644 src/tools/pgindent/yb_typedefs.list create mode 100644 yb-extensions/Makefile create mode 100644 yb-extensions/README create mode 100644 yb-extensions/pg_dist_rag/Makefile create mode 100644 yb-extensions/pg_dist_rag/README.md create mode 100644 yb-extensions/pg_dist_rag/pg_dist_rag--0.0.1.sql create mode 100644 yb-extensions/pg_dist_rag/pg_dist_rag.control create mode 100644 yb-extensions/pg_dist_rag/sql/pg_dist_rag_test.sql create mode 100644 yb-extensions/pg_dist_rag/yb_schedule create mode 100644 yb-extensions/yb_pg_metrics/Makefile create mode 100644 yb-extensions/yb_pg_metrics/yb_pg_metrics.c create mode 100644 yb-extensions/yb_pg_metrics/yb_pg_metrics.control create mode 100644 yb-extensions/yb_test_extension/Makefile create mode 100644 yb-extensions/yb_test_extension/yb_test_extension--1.0.sql create mode 100644 yb-extensions/yb_test_extension/yb_test_extension.control create mode 100644 yb-extensions/yb_xcluster_ddl_replication/Makefile create mode 100644 yb-extensions/yb_xcluster_ddl_replication/expected/colocated_setup.out create mode 100644 yb-extensions/yb_xcluster_ddl_replication/expected/create_colocated_table.out create mode 100644 yb-extensions/yb_xcluster_ddl_replication/expected/create_drop_index.out create mode 100644 yb-extensions/yb_xcluster_ddl_replication/expected/create_drop_table.out create mode 100644 yb-extensions/yb_xcluster_ddl_replication/expected/materialized_views.out create mode 100644 yb-extensions/yb_xcluster_ddl_replication/expected/mixed_temporary_and_persistent.out create mode 100644 yb-extensions/yb_xcluster_ddl_replication/expected/routines.out create mode 100644 yb-extensions/yb_xcluster_ddl_replication/expected/setup.out create mode 100644 yb-extensions/yb_xcluster_ddl_replication/expected/table_rewrite.out create mode 100644 yb-extensions/yb_xcluster_ddl_replication/expected/truncate.out create mode 100644 yb-extensions/yb_xcluster_ddl_replication/extension_util.c create mode 100644 yb-extensions/yb_xcluster_ddl_replication/extension_util.h create mode 100644 yb-extensions/yb_xcluster_ddl_replication/json_util.c create mode 100644 yb-extensions/yb_xcluster_ddl_replication/json_util.h create mode 100644 yb-extensions/yb_xcluster_ddl_replication/source_ddl_end_handler.c create mode 100644 yb-extensions/yb_xcluster_ddl_replication/source_ddl_end_handler.h create mode 100644 yb-extensions/yb_xcluster_ddl_replication/sql/colocated_setup.sql create mode 100644 yb-extensions/yb_xcluster_ddl_replication/sql/create_colocated_table.sql create mode 100644 yb-extensions/yb_xcluster_ddl_replication/sql/create_drop_index.sql create mode 100644 yb-extensions/yb_xcluster_ddl_replication/sql/create_drop_table.sql create mode 100644 yb-extensions/yb_xcluster_ddl_replication/sql/materialized_views.sql create mode 100644 yb-extensions/yb_xcluster_ddl_replication/sql/mixed_temporary_and_persistent.sql create mode 100644 yb-extensions/yb_xcluster_ddl_replication/sql/routines.sql create mode 100644 yb-extensions/yb_xcluster_ddl_replication/sql/setup.sql create mode 100644 yb-extensions/yb_xcluster_ddl_replication/sql/table_rewrite.sql create mode 100644 yb-extensions/yb_xcluster_ddl_replication/sql/truncate.sql create mode 100644 yb-extensions/yb_xcluster_ddl_replication/xcluster_ddl_replication_gucs.def create mode 100644 yb-extensions/yb_xcluster_ddl_replication/yb_schedule create mode 100644 yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication--1.0.sql create mode 100644 yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication.c create mode 100644 yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication.control create mode 100644 yb-extensions/yb_ycql_utils/Makefile create mode 100644 yb-extensions/yb_ycql_utils/expected/basic.out create mode 100644 yb-extensions/yb_ycql_utils/sql/basic.sql create mode 100644 yb-extensions/yb_ycql_utils/yb_cql_utils.conf create mode 100644 yb-extensions/yb_ycql_utils/yb_schedule create mode 100644 yb-extensions/yb_ycql_utils/yb_ycql_utils--1.0--1.1.sql create mode 100644 yb-extensions/yb_ycql_utils/yb_ycql_utils--1.0.sql create mode 100644 yb-extensions/yb_ycql_utils/yb_ycql_utils.c create mode 100644 yb-extensions/yb_ycql_utils/yb_ycql_utils.control diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000000..84189bfa8c2a0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,115 @@ +--- +Language: Cpp +# BasedOnStyle: GNU +# This clang-format file is intended to use for formatting *new changes* for +# Postgres C code base under Yugabyte. +# Use this file for formatting with your own judgement. +# The existing PG code base has an inconsistent format with its long history +# plus Yugabyte's extension. +# Please update this file if you identify any formats. +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: false +AlignConsecutiveBitFields: false +# Leave AlignConsecutiveDeclarations to default as YB team can use either of the +# styles. Not aligned is preferred in most cases but this is not enforced. +# AlignConsecutiveDeclarations: false +AlignEscapedNewlines: DontAlign +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: AllDefinitions +AlwaysBreakBeforeMultilineStrings: false +BinPackArguments: true +BinPackParameters: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterStruct: true + AfterUnion: true + BeforeElse: true + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false +BreakBeforeBinaryOperators: None +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: false +BreakStringLiterals: true +ColumnLimit: 80 +ContinuationIndentWidth: 4 +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: + - foreach +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '"postgres.h"' + Priority: 0 + SortPriority: 0 + # system headers + - Regex: '^<.+>$' + Priority: 1 + SortPriority: 0 + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 1 + SortPriority: 1 + # headers for PG + - Regex: '^"[^(yb\/)].*' + Priority: 2 + SortPriority: 0 + # headers for YB + - Regex: '^"pg_yb_.*' + Priority: 2 + SortPriority: 0 + - Regex: '^"yb\/.*' + Priority: 2 + SortPriority: 1 +IndentCaseLabels: true +IndentCaseBlocks: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentExternBlock: NoIndent +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PointerAlignment: Right +SortIncludes: true +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceBeforeAssignmentOperators: true +# clang-format v12 +#SpaceBeforeCaseColon: false +SpaceBeforeParens: ControlStatements +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInConditionalStatement: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +TabWidth: 4 +UseCRLF: false +UseTab: Always diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000000..1407e09c68fd3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,233 @@ +# Copyright (c) YugabyteDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations +# under the License. + +# YB change: Enable catcache stats. +set(POSTGRES_EXTRA_C_CXX_FLAGS "-DCATCACHE_STATS") +set(POSTGRES_EXTRA_LD_FLAGS "") + +get_property(yb_cmake_include_dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + PROPERTY INCLUDE_DIRECTORIES) + +macro(add_pg_c_cxx_flags) + foreach(_tmp_pg_flag IN ITEMS ${ARGN}) + if(NOT POSTGRES_EXTRA_C_CXX_FLAGS MATCHES "[ ]$") + string(APPEND POSTGRES_EXTRA_C_CXX_FLAGS " ") + endif() + string(APPEND POSTGRES_EXTRA_C_CXX_FLAGS "${_tmp_pg_flag}") + endforeach() +endmacro() + +macro(add_pg_ld_flags) + foreach(_tmp_pg_flag IN ITEMS ${ARGN}) + if(NOT POSTGRES_EXTRA_LD_FLAGS MATCHES "[ ]$") + string(APPEND POSTGRES_EXTRA_LD_FLAGS " ") + endif() + string(APPEND POSTGRES_EXTRA_LD_FLAGS "${_tmp_pg_flag}") + endforeach() +endmacro() + +set(added_pg_include_directories "") + +macro(add_pg_include_directory include_dir) + set(_tmp_duplicate_include_dir OFF) + foreach(_tmp_existing_include_dir IN LISTS added_pg_include_directories) + if("${_tmp_existing_include_dir}" STREQUAL "${include_dir}") + set(_tmp_duplicate_include_dir ON) + endif() + endforeach() + list(APPEND added_pg_include_directories "${include_dir}") + if(NOT _tmp_duplicate_include_dir) + add_pg_c_cxx_flags("-I${include_dir}") + endif() +endmacro() + +foreach(include_dir ${yb_cmake_include_dirs}) + add_pg_include_directory("${include_dir}") +endforeach(include_dir) + +if(IS_GCC) + add_pg_c_cxx_flags( + -Wno-format-truncation + -Wno-maybe-uninitialized + -Wno-stringop-truncation + ) +endif() + +if(IS_CLANG) + if("${COMPILER_VERSION}" VERSION_GREATER_EQUAL "14.0.0") + add_pg_c_cxx_flags(-Wno-error=unused-but-set-variable) + endif() + + if(("${COMPILER_VERSION}" VERSION_GREATER_EQUAL "15.0.0") OR + (IS_APPLE_CLANG AND "${COMPILER_VERSION}" VERSION_GREATER_EQUAL "14.0.3")) + add_pg_c_cxx_flags(-Wno-error=deprecated-non-prototype) + endif() +endif() + +if(APPLE) + # To silence warning from stdio.h + # https://gist.github.com/hari90/3480c6073dd5a92ff1e7027295bc2bf3 + # and malloc.h + # https://gist.github.com/hari90/11c8d5cf89056b3c730eab62d216023b + add_pg_c_cxx_flags( + -Wno-nullability-completeness + -Wno-availability + ) +endif() + +if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") + add_pg_c_cxx_flags(-DUSE_SSE42_CRC32C=1) +endif() + +if(NOT APPLE AND IS_ASAN AND IS_CLANG AND "${COMPILER_VERSION}" VERSION_GREATER_EQUAL "10.0.0") + add_pg_ld_flags(-ldl) +endif() + +get_filename_component(OPENSSL_LIB_DIR "${OPENSSL_CRYPTO_LIBRARY}" DIRECTORY) +message("OPENSSL_LIB_DIR=${OPENSSL_LIB_DIR}") + +set(build_postgres_args "") + +if (APPLE AND "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64") + list(APPEND build_postgres_args arch -arm64) +endif() + +set(POSTGRES_EXTRA_PREPROCESSOR_FLAGS "") +if(APPLE AND NOT "${MACOS_SDK_INCLUDE_DIR}" STREQUAL "") + string(APPEND POSTGRES_EXTRA_PREPROCESSOR_FLAGS + " -isystem ${MACOS_SDK_INCLUDE_DIR}") +endif() + +if(NOT "$ENV{YB_VALGRIND_PATH}" STREQUAL "") + + string(APPEND POSTGRES_EXTRA_PREPROCESSOR_FLAGS " -DUSE_VALGRIND") + + find_path(VALGRIND_INCLUDE_PATH "valgrind/valgrind.h" + "/usr/include" + "/usr/local/include" + $ENV{CPATH}) + + if (${VALGRIND_INCLUDE_PATH} STREQUAL "VALGRIND_INCLUDE_PATH-NOTFOUND") + message(FATAL_ERROR "Compiling with Valgrind support, but could not find Valgrind's header" + " files. If you have Valgrind installed in an atypical location, add the include directory" + " to CPATH and retry.") + endif() +endif() + +concat_thirdparty_prefix_dirs_with_suffix(PG_INCLUDE_DIRS " " "/include") +concat_thirdparty_prefix_dirs_with_suffix(PG_LIB_DIRS " " "/lib") + +# We only use YB_ADD_LIB_DIR("${YB_THIRDPARTY_MAYBE_INSTRUMENTED_DIR}/lib") in the top-level +# CMakeLists because the corresponding RPATH ends up on the linker command line in some other way, +# presumably, due to library dependencies. However, for Postgres, we have to add this RPATH +# explicitly. +add_pg_ld_flags("${YB_THIRDPARTY_MAYBE_INSTRUMENTED_RPATH_ARG}") + +if(APPLE AND NOT IS_APPLE_CLANG) + add_pg_ld_flags("-L${CMAKE_OSX_SYSROOT}/usr/lib") + add_pg_c_cxx_flags("-isysroot ${CMAKE_OSX_SYSROOT}") + # This is needed for PostgreSQL's configure to find headers such as uuid.h. + string(APPEND PG_INCLUDE_DIRS " ${CMAKE_OSX_SYSROOT}/usr/include") +endif() +set(POSTGRES_FINAL_C_FLAGS "${POSTGRES_EXTRA_C_CXX_FLAGS} ${CMAKE_C_FLAGS}") +yb_deduplicate_arguments(POSTGRES_FINAL_C_FLAGS) + +set(POSTGRES_FINAL_CXX_FLAGS "${POSTGRES_EXTRA_C_CXX_FLAGS} ${CMAKE_CXX_FLAGS}") +yb_deduplicate_arguments(POSTGRES_FINAL_CXX_FLAGS) + +set(POSTGRES_FINAL_LD_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${POSTGRES_EXTRA_LD_FLAGS}") +yb_deduplicate_arguments(POSTGRES_FINAL_LD_FLAGS) + +set(POSTGRES_FINAL_EXE_LD_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${POSTGRES_EXTRA_LD_FLAGS}") +yb_deduplicate_arguments(POSTGRES_FINAL_EXE_LD_FLAGS) + +set(PG_EXE_LD_FLAGS_AFTER_YB_LIBS "") +if("${YB_TCMALLOC_ENABLED}" STREQUAL "1") + # We link with tcmalloc statically, but tcmalloc depends on some other shared libraries being + # present on the linker command line. + # + # The libraries we add here should be kept consistent with the libraries added in + # the add_executable wrapper in YugabyteFunctions.cmake. + # + # We don't add libtcmalloc and libprofiler to POSTGRES_FINAL_EXE_LD_FLAGS because when building + # with GCC we use the standard ld linker, which cares about the order of libraries when using + # static libraries. Symbols from libtcmalloc and libprofiler are needed by e.g. libyb_util, so + # libtcmalloc must appear after other YB libraries on the linker command line. + # + # The special variable PG_EXE_LD_FLAGS_AFTER_YB_LIBS eventually gets propagated to a + # YB_PG_EXE_LD_FLAGS_AFTER_YB_LIBS environment variable used in Postgres makefiles that build + # an executable. It gets included after all C++ YB libs that the relevant executable depends on. + if ("${YB_GOOGLE_TCMALLOC}" STREQUAL "1") + set(PG_EXE_LD_FLAGS_AFTER_YB_LIBS "${TCMALLOC_STATIC_LIB_LD_FLAGS} -labsl") + else() + set(PG_EXE_LD_FLAGS_AFTER_YB_LIBS "${TCMALLOC_STATIC_LIB_LD_FLAGS} -lprofiler") + endif() + + if(IS_CLANG AND NOT IS_ASAN) + # Skip adding -lc++ when ASAN is enabled, as it's already added globally via ADD_LINKER_FLAGS + # in YugabyteFunctions.cmake to avoid duplicate library warnings. + string(APPEND PG_EXE_LD_FLAGS_AFTER_YB_LIBS " -lc++") + endif() + if(IS_GCC) + # To avoid these errors when linking postgres executables: + # https://gist.githubusercontent.com/mbautin/c6f0e741de57e671e78b78e9a8508995/raw + # https://gist.githubusercontent.com/mbautin/6cbef12e6bbab79d1f87964f571d2d46/raw + string(APPEND PG_EXE_LD_FLAGS_AFTER_YB_LIBS " -lstdc++ -lunwind") + endif() +endif() + +yb_put_string_vars_into_cache( + POSTGRES_FINAL_C_FLAGS + POSTGRES_FINAL_CXX_FLAGS + POSTGRES_FINAL_LD_FLAGS + POSTGRES_FINAL_EXE_LD_FLAGS + POSTGRES_EXTRA_PREPROCESSOR_FLAGS + PG_EXE_LD_FLAGS_AFTER_YB_LIBS + PG_INCLUDE_DIRS + PG_LIB_DIRS +) + +list(APPEND build_postgres_args + "${BUILD_SUPPORT_DIR}/build_postgres" --build_root "${YB_BUILD_ROOT}") + +if (NOT DEFINED LIBPQ_SHARED_LIB) + message(FATAL_ERROR "LIBPQ_SHARED_LIB must be defined") +endif() +if (NOT DEFINED YB_PGBACKEND_SHARED_LIB) + message(FATAL_ERROR "YB_PGBACKEND_SHARED_LIB must be defined") +endif() + +# Invoke our PostgreSQL build script in two steps: "configure" and "make". The "configure" step +# does not have any dependencies so it can run in parallel with e.g. C++ code compilation. +# +# We add spaces before every "flags" parameter so that Python's argparse does not interpret them as +# its own flags. +add_custom_target(configure_postgres ALL COMMAND ${build_postgres_args} --step configure) +add_custom_target(postgres_genbki ALL COMMAND ${build_postgres_args} --step genbki) +add_custom_target( + postgres ALL + COMMAND ${build_postgres_args} --step make + BYPRODUCTS + "${LIBPQ_SHARED_LIB}" + "${YB_PGBACKEND_SHARED_LIB}" + "${PG_COMMON_STATIC_LIB}" + "${PG_PORT_STATIC_LIB}") + +add_dependencies(postgres_genbki configure_postgres) +add_dependencies(postgres configure_postgres postgres_genbki) + +# ------------------------------------------------------------------------------------------------ +# Dependencies between postgres and other targets +# ------------------------------------------------------------------------------------------------ + +# Libraries that we link into the postgres binary. +add_dependencies(postgres yb_pggate server_process yb_pggate_webserver ysql_bench_metrics_handler hdr_histogram yb_pgresult_util) diff --git a/contrib/adminpack/Makefile b/contrib/adminpack/Makefile index 851504f4aefba..77fd9f92bc1e6 100644 --- a/contrib/adminpack/Makefile +++ b/contrib/adminpack/Makefile @@ -12,6 +12,8 @@ PGFILEDESC = "adminpack - support functions for pgAdmin" REGRESS = adminpack +SHLIB_LINK = -L$(YB_BUILD_ROOT)/lib -lyb_pggate + ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c index 03addf1dc5fb0..3d9341951e093 100644 --- a/contrib/adminpack/adminpack.c +++ b/contrib/adminpack/adminpack.c @@ -28,6 +28,10 @@ #include "utils/builtins.h" #include "utils/datetime.h" +/* YB includes */ +#include "pg_yb_utils.h" +#include "yb/yql/pggate/ybc_pggate.h" + #ifdef WIN32 @@ -72,6 +76,8 @@ convert_and_check_filename(text *arg) { char *filename = text_to_cstring(arg); + YBCheckServerAccessIsAllowed(); + canonicalize_path(filename); /* filename can change length here */ /* @@ -505,6 +511,8 @@ pg_logdir_ls_internal(FunctionCallInfo fcinfo) struct dirent *de; MemoryContext oldcontext; + YBCheckServerAccessIsAllowed(); + if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), diff --git a/contrib/adminpack/yb_schedule b/contrib/adminpack/yb_schedule new file mode 100644 index 0000000000000..eb1e58c7b0743 --- /dev/null +++ b/contrib/adminpack/yb_schedule @@ -0,0 +1,5 @@ +# contrib/adminpack/yb_schedule + +# Since the upstream postgres test works fine without modifications +# (currently), don't bother porting it to yb_pg_adminpack. +test: adminpack diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c index c08f19fa89496..c0fbf51dfa61a 100644 --- a/contrib/amcheck/verify_nbtree.c +++ b/contrib/amcheck/verify_nbtree.c @@ -42,6 +42,9 @@ #include "utils/memutils.h" #include "utils/snapmgr.h" +/* YB includes */ +#include "utils/builtins.h" +#include "utils/guc.h" /* TODO: is needed? */ PG_MODULE_MAGIC; diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index c9a0d947c8314..2e4742c59553c 100644 --- a/contrib/auto_explain/auto_explain.c +++ b/contrib/auto_explain/auto_explain.c @@ -21,6 +21,9 @@ #include "jit/jit.h" #include "utils/guc.h" +/* YB includes */ +#include "pg_yb_utils.h" + PG_MODULE_MAGIC; /* GUC variables */ @@ -36,6 +39,11 @@ static int auto_explain_log_format = EXPLAIN_FORMAT_TEXT; static int auto_explain_log_level = LOG; static bool auto_explain_log_nested_statements = false; static double auto_explain_sample_rate = 1; +static bool auto_explain_log_dist = true; /* option = auto_explain.log_dist */ +static bool auto_explain_log_debug = false; /* option = auto_explain.log_debug */ + +/* YB variables */ +static bool yb_auto_explain_debug_metrics_needed = false; static const struct config_enum_entry format_options[] = { {"text", EXPLAIN_FORMAT_TEXT, false}, @@ -230,6 +238,30 @@ _PG_init(void) NULL, NULL); + /* No effect when log_analyze is switched off */ + DefineCustomBoolVariable("auto_explain.log_dist", + "Set log_dist=false to disable distributed metrics for explain analyze.", + NULL, + &auto_explain_log_dist, + true, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + /* No effect when log_analyze is switched off */ + DefineCustomBoolVariable("auto_explain.log_debug", + "Set log_debug=true to enable debug metrics in explain analyze output.", + NULL, + &auto_explain_log_debug, + false, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + MarkGUCPrefixReserved("auto_explain"); /* Install hooks. */ @@ -280,6 +312,15 @@ explain_ExecutorStart(QueryDesc *queryDesc, int eflags) queryDesc->instrument_options |= INSTRUMENT_BUFFERS; if (auto_explain_log_wal) queryDesc->instrument_options |= INSTRUMENT_WAL; + + yb_auto_explain_debug_metrics_needed = + YbIsDebugMetricsCollectionNeeded(auto_explain_log_debug, + auto_explain_log_dist); + if (yb_auto_explain_debug_metrics_needed) + YbSetMetricsCaptureType(YB_YQL_METRICS_CAPTURE_ALL); + + if (auto_explain_log_timing) + YbToggleSessionStatsTimer(true); } } @@ -386,6 +427,8 @@ explain_ExecutorEnd(QueryDesc *queryDesc) es->summary = es->analyze; es->format = auto_explain_log_format; es->settings = auto_explain_log_settings; + es->rpc = (es->analyze && auto_explain_log_dist); + es->yb_debug = yb_auto_explain_debug_metrics_needed; ExplainBeginOutput(es); ExplainQueryText(es, queryDesc); @@ -419,6 +462,15 @@ explain_ExecutorEnd(QueryDesc *queryDesc) errhidestmt(true))); } + /* + * YB: Turn off timing and metrics capture. These will be + * restored by YbToggleSessionStatsTimer() in the main + * query loop before the next query is executed. + */ + YbSetMetricsCaptureType(YB_YQL_METRICS_CAPTURE_NONE); + YbToggleSessionStatsTimer(false); + + yb_auto_explain_debug_metrics_needed = false; MemoryContextSwitchTo(oldcxt); } diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c index b09bb8df964c2..008bdb573f1e9 100644 --- a/contrib/btree_gin/btree_gin.c +++ b/contrib/btree_gin/btree_gin.c @@ -90,6 +90,7 @@ gin_btree_extract_query(FunctionCallInfo fcinfo, case BTGreaterStrategyNumber: *ptr_partialmatch = true; /* FALLTHROUGH */ + yb_switch_fallthrough(); case BTEqualStrategyNumber: entries[0] = datum; break; diff --git a/contrib/cube/expected/yb.port.cube.out b/contrib/cube/expected/yb.port.cube.out new file mode 100644 index 0000000000000..8481b94230acc --- /dev/null +++ b/contrib/cube/expected/yb.port.cube.out @@ -0,0 +1,1907 @@ +-- +-- Test cube datatype +-- +CREATE EXTENSION cube; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); +ERROR: cannot validate operator family without ordered data +-- +-- testing the input and output functions +-- +-- Any number (a one-dimensional point) +SELECT '1'::cube AS cube; + cube +------ + (1) +(1 row) + +SELECT '-1'::cube AS cube; + cube +------ + (-1) +(1 row) + +SELECT '1.'::cube AS cube; + cube +------ + (1) +(1 row) + +SELECT '-1.'::cube AS cube; + cube +------ + (-1) +(1 row) + +SELECT '.1'::cube AS cube; + cube +------- + (0.1) +(1 row) + +SELECT '-.1'::cube AS cube; + cube +-------- + (-0.1) +(1 row) + +SELECT '1.0'::cube AS cube; + cube +------ + (1) +(1 row) + +SELECT '-1.0'::cube AS cube; + cube +------ + (-1) +(1 row) + +SELECT 'infinity'::cube AS cube; + cube +------------ + (Infinity) +(1 row) + +SELECT '-infinity'::cube AS cube; + cube +------------- + (-Infinity) +(1 row) + +SELECT 'NaN'::cube AS cube; + cube +------- + (NaN) +(1 row) + +SELECT '.1234567890123456'::cube AS cube; + cube +---------------------- + (0.1234567890123456) +(1 row) + +SELECT '+.1234567890123456'::cube AS cube; + cube +---------------------- + (0.1234567890123456) +(1 row) + +SELECT '-.1234567890123456'::cube AS cube; + cube +----------------------- + (-0.1234567890123456) +(1 row) + +-- simple lists (points) +SELECT '()'::cube AS cube; + cube +------ + () +(1 row) + +SELECT '1,2'::cube AS cube; + cube +-------- + (1, 2) +(1 row) + +SELECT '(1,2)'::cube AS cube; + cube +-------- + (1, 2) +(1 row) + +SELECT '1,2,3,4,5'::cube AS cube; + cube +----------------- + (1, 2, 3, 4, 5) +(1 row) + +SELECT '(1,2,3,4,5)'::cube AS cube; + cube +----------------- + (1, 2, 3, 4, 5) +(1 row) + +-- double lists (cubes) +SELECT '(),()'::cube AS cube; + cube +------ + () +(1 row) + +SELECT '(0),(0)'::cube AS cube; + cube +------ + (0) +(1 row) + +SELECT '(0),(1)'::cube AS cube; + cube +--------- + (0),(1) +(1 row) + +SELECT '[(0),(0)]'::cube AS cube; + cube +------ + (0) +(1 row) + +SELECT '[(0),(1)]'::cube AS cube; + cube +--------- + (0),(1) +(1 row) + +SELECT '(0,0,0,0),(0,0,0,0)'::cube AS cube; + cube +-------------- + (0, 0, 0, 0) +(1 row) + +SELECT '(0,0,0,0),(1,0,0,0)'::cube AS cube; + cube +--------------------------- + (0, 0, 0, 0),(1, 0, 0, 0) +(1 row) + +SELECT '[(0,0,0,0),(0,0,0,0)]'::cube AS cube; + cube +-------------- + (0, 0, 0, 0) +(1 row) + +SELECT '[(0,0,0,0),(1,0,0,0)]'::cube AS cube; + cube +--------------------------- + (0, 0, 0, 0),(1, 0, 0, 0) +(1 row) + +-- invalid input: parse errors +SELECT ''::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT ''::cube AS cube; + ^ +DETAIL: syntax error at end of input +SELECT 'ABC'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT 'ABC'::cube AS cube; + ^ +DETAIL: syntax error at or near "A" +SELECT '[]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[]'::cube AS cube; + ^ +DETAIL: syntax error at or near "]" +SELECT '[()]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[()]'::cube AS cube; + ^ +DETAIL: syntax error at or near "]" +SELECT '[(1)]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1)]'::cube AS cube; + ^ +DETAIL: syntax error at or near "]" +SELECT '[(1),]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1),]'::cube AS cube; + ^ +DETAIL: syntax error at or near "]" +SELECT '[(1),2]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1),2]'::cube AS cube; + ^ +DETAIL: syntax error at or near "2" +SELECT '[(1),(2),(3)]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1),(2),(3)]'::cube AS cube; + ^ +DETAIL: syntax error at or near "," +SELECT '1,'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '1,'::cube AS cube; + ^ +DETAIL: syntax error at end of input +SELECT '1,2,'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '1,2,'::cube AS cube; + ^ +DETAIL: syntax error at end of input +SELECT '1,,2'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '1,,2'::cube AS cube; + ^ +DETAIL: syntax error at or near "," +SELECT '(1,)'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,)'::cube AS cube; + ^ +DETAIL: syntax error at or near ")" +SELECT '(1,2,)'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2,)'::cube AS cube; + ^ +DETAIL: syntax error at or near ")" +SELECT '(1,,2)'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,,2)'::cube AS cube; + ^ +DETAIL: syntax error at or near "," +-- invalid input: semantic errors and trailing garbage +SELECT '[(1),(2)],'::cube AS cube; -- 0 +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1),(2)],'::cube AS cube; + ^ +DETAIL: syntax error at or near "," +SELECT '[(1,2,3),(2,3)]'::cube AS cube; -- 1 +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1,2,3),(2,3)]'::cube AS cube; + ^ +DETAIL: Different point dimensions in (1,2,3) and (2,3). +SELECT '[(1,2),(1,2,3)]'::cube AS cube; -- 1 +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1,2),(1,2,3)]'::cube AS cube; + ^ +DETAIL: Different point dimensions in (1,2) and (1,2,3). +SELECT '(1),(2),'::cube AS cube; -- 2 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1),(2),'::cube AS cube; + ^ +DETAIL: syntax error at or near "," +SELECT '(1,2,3),(2,3)'::cube AS cube; -- 3 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2,3),(2,3)'::cube AS cube; + ^ +DETAIL: Different point dimensions in (1,2,3) and (2,3). +SELECT '(1,2),(1,2,3)'::cube AS cube; -- 3 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2),(1,2,3)'::cube AS cube; + ^ +DETAIL: Different point dimensions in (1,2) and (1,2,3). +SELECT '(1,2,3)ab'::cube AS cube; -- 4 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2,3)ab'::cube AS cube; + ^ +DETAIL: syntax error at or near "a" +SELECT '(1,2,3)a'::cube AS cube; -- 5 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2,3)a'::cube AS cube; + ^ +DETAIL: syntax error at or near "a" +SELECT '(1,2)('::cube AS cube; -- 5 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2)('::cube AS cube; + ^ +DETAIL: syntax error at or near "(" +SELECT '1,2ab'::cube AS cube; -- 6 +ERROR: invalid input syntax for cube +LINE 1: SELECT '1,2ab'::cube AS cube; + ^ +DETAIL: syntax error at or near "a" +SELECT '1 e7'::cube AS cube; -- 6 +ERROR: invalid input syntax for cube +LINE 1: SELECT '1 e7'::cube AS cube; + ^ +DETAIL: syntax error at or near "e" +SELECT '1,2a'::cube AS cube; -- 7 +ERROR: invalid input syntax for cube +LINE 1: SELECT '1,2a'::cube AS cube; + ^ +DETAIL: syntax error at or near "a" +SELECT '1..2'::cube AS cube; -- 7 +ERROR: invalid input syntax for cube +LINE 1: SELECT '1..2'::cube AS cube; + ^ +DETAIL: syntax error at or near ".2" +SELECT '-1e-700'::cube AS cube; -- out of range +ERROR: "-1e-700" is out of range for type double precision +LINE 1: SELECT '-1e-700'::cube AS cube; + ^ +-- +-- Testing building cubes from float8 values +-- +SELECT cube(0::float8); + cube +------ + (0) +(1 row) + +SELECT cube(1::float8); + cube +------ + (1) +(1 row) + +SELECT cube(1,2); + cube +--------- + (1),(2) +(1 row) + +SELECT cube(cube(1,2),3); + cube +--------------- + (1, 3),(2, 3) +(1 row) + +SELECT cube(cube(1,2),3,4); + cube +--------------- + (1, 3),(2, 4) +(1 row) + +SELECT cube(cube(cube(1,2),3,4),5); + cube +--------------------- + (1, 3, 5),(2, 4, 5) +(1 row) + +SELECT cube(cube(cube(1,2),3,4),5,6); + cube +--------------------- + (1, 3, 5),(2, 4, 6) +(1 row) + +-- +-- Test that the text -> cube cast was installed. +-- +SELECT '(0)'::text::cube; + cube +------ + (0) +(1 row) + +-- +-- Test the float[] -> cube cast +-- +SELECT cube('{0,1,2}'::float[], '{3,4,5}'::float[]); + cube +--------------------- + (0, 1, 2),(3, 4, 5) +(1 row) + +SELECT cube('{0,1,2}'::float[], '{3}'::float[]); +ERROR: UR and LL arrays must be of same length +SELECT cube(NULL::float[], '{3}'::float[]); + cube +------ + +(1 row) + +SELECT cube('{0,1,2}'::float[]); + cube +----------- + (0, 1, 2) +(1 row) + +SELECT cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[3,2,1,1]); + cube_subset +--------------------------- + (5, 3, 1, 1),(8, 7, 6, 6) +(1 row) + +SELECT cube_subset(cube('(1,3,5),(1,3,5)'), ARRAY[3,2,1,1]); + cube_subset +-------------- + (5, 3, 1, 1) +(1 row) + +SELECT cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[4,0]); +ERROR: Index out of bounds +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), ARRAY[4,0]); +ERROR: Index out of bounds +-- test for limits: this should pass +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), array(SELECT 1 as a FROM generate_series(1,100))); + cube_subset +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + (6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6) +(1 row) + +-- and this should fail +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), array(SELECT 1 as a FROM generate_series(1,101))); +ERROR: array is too long +DETAIL: A cube cannot have more than 100 dimensions. +-- +-- Test point processing +-- +SELECT cube('(1,2),(1,2)'); -- cube_in + cube +-------- + (1, 2) +(1 row) + +SELECT cube('{0,1,2}'::float[], '{0,1,2}'::float[]); -- cube_a_f8_f8 + cube +----------- + (0, 1, 2) +(1 row) + +SELECT cube('{5,6,7,8}'::float[]); -- cube_a_f8 + cube +-------------- + (5, 6, 7, 8) +(1 row) + +SELECT cube(1.37); -- cube_f8 + cube +-------- + (1.37) +(1 row) + +SELECT cube(1.37, 1.37); -- cube_f8_f8 + cube +-------- + (1.37) +(1 row) + +SELECT cube(cube(1,1), 42); -- cube_c_f8 + cube +--------- + (1, 42) +(1 row) + +SELECT cube(cube(1,2), 42); -- cube_c_f8 + cube +----------------- + (1, 42),(2, 42) +(1 row) + +SELECT cube(cube(1,1), 42, 42); -- cube_c_f8_f8 + cube +--------- + (1, 42) +(1 row) + +SELECT cube(cube(1,1), 42, 24); -- cube_c_f8_f8 + cube +----------------- + (1, 42),(1, 24) +(1 row) + +SELECT cube(cube(1,2), 42, 42); -- cube_c_f8_f8 + cube +----------------- + (1, 42),(2, 42) +(1 row) + +SELECT cube(cube(1,2), 42, 24); -- cube_c_f8_f8 + cube +----------------- + (1, 42),(2, 24) +(1 row) + +-- +-- Testing limit of CUBE_MAX_DIM dimensions check in cube_in. +-- +-- create too big cube from literal +select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)'::cube; +ERROR: invalid input syntax for cube +LINE 1: select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0... + ^ +DETAIL: A cube cannot have more than 100 dimensions. +select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)'::cube; +ERROR: invalid input syntax for cube +LINE 1: select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0... + ^ +DETAIL: A cube cannot have more than 100 dimensions. +-- from an array +select cube(array(SELECT 0 as a FROM generate_series(1,101))); +ERROR: array is too long +DETAIL: A cube cannot have more than 100 dimensions. +select cube(array(SELECT 0 as a FROM generate_series(1,101)),array(SELECT 0 as a FROM generate_series(1,101))); +ERROR: can't extend cube +DETAIL: A cube cannot have more than 100 dimensions. +-- extend cube beyond limit +-- this should work +select cube(array(SELECT 0 as a FROM generate_series(1,100))); + cube +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +(1 row) + +select cube(array(SELECT 0 as a FROM generate_series(1,100)),array(SELECT 0 as a FROM generate_series(1,100))); + cube +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +(1 row) + +-- this should fail +select cube(cube(array(SELECT 0 as a FROM generate_series(1,100))), 0); +ERROR: can't extend cube +DETAIL: A cube cannot have more than 100 dimensions. +select cube(cube(array(SELECT 0 as a FROM generate_series(1,100)),array(SELECT 0 as a FROM generate_series(1,100))), 0, 0); +ERROR: can't extend cube +DETAIL: A cube cannot have more than 100 dimensions. +-- +-- testing the operators +-- +-- equality/inequality: +-- +SELECT '24, 33.20'::cube = '24, 33.20'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '24, 33.20'::cube != '24, 33.20'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '24, 33.20'::cube = '24, 33.21'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '24, 33.20'::cube != '24, 33.21'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0),(3,1)'::cube = '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0),(3,1)'::cube = '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; + bool +------ + f +(1 row) + +-- "lower than" / "greater than" +-- (these operators are not useful for anything but ordering) +-- +SELECT '1'::cube > '2'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '1'::cube < '2'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '1,1'::cube > '1,2'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '1,1'::cube < '1,2'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,1),(3,1,0,0,0)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,1),(3,1,0,0,0)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0,0,0,0),(3,1,0,0,1)'::cube > '(2,0),(3,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0,0,0,0),(3,1,0,0,1)'::cube < '(2,0),(3,1)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0,0,0,1),(3,1,0,0,0)'::cube > '(2,0),(3,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0,0,0,1),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube > '(2,0),(3,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool; + bool +------ + f +(1 row) + +-- "overlap" +-- +SELECT '1'::cube && '1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '1'::cube && '2'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '1,1,1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(1,1,1),(2,2,2)]'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(1,1),(2,2)]'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(2,1,1),(2,2,2)]'::cube AS bool; + bool +------ + f +(1 row) + +-- "contained in" (the left operand is the cube entirely enclosed by +-- the right operand): +-- +SELECT '0'::cube <@ '0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0,0'::cube <@ '0,0,0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0'::cube <@ '0,0,1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0,0'::cube <@ '0,0,1'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '1,0,0'::cube <@ '0,0,1'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(1,0,0),(0,0,1)'::cube <@ '(1,0,0),(0,0,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(1,0,0),(0,0,1)'::cube <@ '(-1,-1,-1),(1,1,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(1,0,0),(0,0,1)'::cube <@ '(-1,-1,-1,-1),(1,1,1,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0'::cube <@ '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '1'::cube <@ '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '-1'::cube <@ '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube <@ '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube <@ '(-1,-1),(1,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-2),(1)'::cube <@ '(-1),(1)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(-2),(1)'::cube <@ '(-1,-1),(1,1)'::cube AS bool; + bool +------ + f +(1 row) + +-- "contains" (the left operand is the cube that entirely encloses the +-- right operand) +-- +SELECT '0'::cube @> '0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0,0'::cube @> '0,0,0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0,1'::cube @> '0,0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0,1'::cube @> '0,0,0'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '0,0,1'::cube @> '1,0,0'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(1,0,0),(0,0,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1,-1,-1),(1,1,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1,-1,-1,-1),(1,1,1,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube @> '0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube @> '1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube @> '-1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube @> '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1,-1),(1,1)'::cube @> '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube @> '(-2),(1)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(-1,-1),(1,1)'::cube @> '(-2),(1)'::cube AS bool; + bool +------ + f +(1 row) + +-- Test of distance function +-- +SELECT cube_distance('(0)'::cube,'(2,2,2,2)'::cube); + cube_distance +--------------- + 4 +(1 row) + +SELECT cube_distance('(0)'::cube,'(.3,.4)'::cube); + cube_distance +--------------- + 0.5 +(1 row) + +SELECT cube_distance('(2,3,4)'::cube,'(2,3,4)'::cube); + cube_distance +--------------- + 0 +(1 row) + +SELECT cube_distance('(42,42,42,42)'::cube,'(137,137,137,137)'::cube); + cube_distance +--------------- + 190 +(1 row) + +SELECT cube_distance('(42,42,42)'::cube,'(137,137)'::cube); + cube_distance +-------------------- + 140.76221083799445 +(1 row) + +-- Test of cube function (text to cube) +-- +SELECT cube('(1,1.2)'::text); + cube +---------- + (1, 1.2) +(1 row) + +SELECT cube(NULL); + cube +------ + +(1 row) + +-- Test of cube_dim function (dimensions stored in cube) +-- +SELECT cube_dim('(0)'::cube); + cube_dim +---------- + 1 +(1 row) + +SELECT cube_dim('(0,0)'::cube); + cube_dim +---------- + 2 +(1 row) + +SELECT cube_dim('(0,0,0)'::cube); + cube_dim +---------- + 3 +(1 row) + +SELECT cube_dim('(42,42,42),(42,42,42)'::cube); + cube_dim +---------- + 3 +(1 row) + +SELECT cube_dim('(4,8,15,16,23),(4,8,15,16,23)'::cube); + cube_dim +---------- + 5 +(1 row) + +-- Test of cube_ll_coord function (retrieves LL coordinate values) +-- +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 1); + cube_ll_coord +--------------- + -1 +(1 row) + +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 2); + cube_ll_coord +--------------- + -2 +(1 row) + +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 3); + cube_ll_coord +--------------- + 0 +(1 row) + +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 1); + cube_ll_coord +--------------- + 1 +(1 row) + +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 2); + cube_ll_coord +--------------- + 2 +(1 row) + +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 3); + cube_ll_coord +--------------- + 0 +(1 row) + +SELECT cube_ll_coord('(42,137)'::cube, 1); + cube_ll_coord +--------------- + 42 +(1 row) + +SELECT cube_ll_coord('(42,137)'::cube, 2); + cube_ll_coord +--------------- + 137 +(1 row) + +SELECT cube_ll_coord('(42,137)'::cube, 3); + cube_ll_coord +--------------- + 0 +(1 row) + +-- Test of cube_ur_coord function (retrieves UR coordinate values) +-- +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 1); + cube_ur_coord +--------------- + 2 +(1 row) + +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 2); + cube_ur_coord +--------------- + 1 +(1 row) + +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 3); + cube_ur_coord +--------------- + 0 +(1 row) + +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 1); + cube_ur_coord +--------------- + 1 +(1 row) + +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 2); + cube_ur_coord +--------------- + 2 +(1 row) + +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 3); + cube_ur_coord +--------------- + 0 +(1 row) + +SELECT cube_ur_coord('(42,137)'::cube, 1); + cube_ur_coord +--------------- + 42 +(1 row) + +SELECT cube_ur_coord('(42,137)'::cube, 2); + cube_ur_coord +--------------- + 137 +(1 row) + +SELECT cube_ur_coord('(42,137)'::cube, 3); + cube_ur_coord +--------------- + 0 +(1 row) + +-- Test of cube_is_point +-- +SELECT cube_is_point('(0)'::cube); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_is_point('(0,1,2)'::cube); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_is_point('(0,1,2),(0,1,2)'::cube); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_is_point('(0,1,2),(-1,1,2)'::cube); + cube_is_point +--------------- + f +(1 row) + +SELECT cube_is_point('(0,1,2),(0,-1,2)'::cube); + cube_is_point +--------------- + f +(1 row) + +SELECT cube_is_point('(0,1,2),(0,1,-2)'::cube); + cube_is_point +--------------- + f +(1 row) + +-- Test of cube_enlarge (enlarging and shrinking cubes) +-- +SELECT cube_enlarge('(0)'::cube, 0, 0); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(0)'::cube, 0, 1); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(0)'::cube, 0, 2); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(2),(-2)'::cube, 0, 4); + cube_enlarge +-------------- + (-2),(2) +(1 row) + +SELECT cube_enlarge('(0)'::cube, 1, 0); + cube_enlarge +-------------- + (-1),(1) +(1 row) + +SELECT cube_enlarge('(0)'::cube, 1, 1); + cube_enlarge +-------------- + (-1),(1) +(1 row) + +SELECT cube_enlarge('(0)'::cube, 1, 2); + cube_enlarge +----------------- + (-1, -1),(1, 1) +(1 row) + +SELECT cube_enlarge('(2),(-2)'::cube, 1, 4); + cube_enlarge +------------------------------- + (-3, -1, -1, -1),(3, 1, 1, 1) +(1 row) + +SELECT cube_enlarge('(0)'::cube, -1, 0); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(0)'::cube, -1, 1); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(0)'::cube, -1, 2); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(2),(-2)'::cube, -1, 4); + cube_enlarge +-------------- + (-1),(1) +(1 row) + +SELECT cube_enlarge('(0,0,0)'::cube, 1, 0); + cube_enlarge +------------------------ + (-1, -1, -1),(1, 1, 1) +(1 row) + +SELECT cube_enlarge('(0,0,0)'::cube, 1, 2); + cube_enlarge +------------------------ + (-1, -1, -1),(1, 1, 1) +(1 row) + +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, 1, 2); + cube_enlarge +----------------- + (-4, -3),(3, 8) +(1 row) + +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, 3, 2); + cube_enlarge +------------------ + (-6, -5),(5, 10) +(1 row) + +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, -1, 2); + cube_enlarge +----------------- + (-2, -1),(1, 6) +(1 row) + +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, -3, 2); + cube_enlarge +--------------------- + (-0.5, 1),(-0.5, 4) +(1 row) + +SELECT cube_enlarge('(42,-23,-23),(42,23,23)'::cube, -23, 5); + cube_enlarge +-------------- + (42, 0, 0) +(1 row) + +SELECT cube_enlarge('(42,-23,-23),(42,23,23)'::cube, -24, 5); + cube_enlarge +-------------- + (42, 0, 0) +(1 row) + +-- Test of cube_union (MBR for two cubes) +-- +SELECT cube_union('(1,2),(3,4)'::cube, '(5,6,7),(8,9,10)'::cube); + cube_union +---------------------- + (1, 2, 0),(8, 9, 10) +(1 row) + +SELECT cube_union('(1,2)'::cube, '(4,2,0,0)'::cube); + cube_union +--------------------------- + (1, 2, 0, 0),(4, 2, 0, 0) +(1 row) + +SELECT cube_union('(1,2),(1,2)'::cube, '(4,2),(4,2)'::cube); + cube_union +--------------- + (1, 2),(4, 2) +(1 row) + +SELECT cube_union('(1,2),(1,2)'::cube, '(1,2),(1,2)'::cube); + cube_union +------------ + (1, 2) +(1 row) + +SELECT cube_union('(1,2),(1,2)'::cube, '(1,2,0),(1,2,0)'::cube); + cube_union +------------ + (1, 2, 0) +(1 row) + +-- Test of cube_inter +-- +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,4), (16,15)'::cube); -- intersects + cube_inter +----------------- + (3, 4),(10, 11) +(1 row) + +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,4), (6,5)'::cube); -- includes + cube_inter +--------------- + (3, 4),(6, 5) +(1 row) + +SELECT cube_inter('(1,2),(10,11)'::cube, '(13,14), (16,15)'::cube); -- no intersection + cube_inter +------------------- + (13, 14),(10, 11) +(1 row) + +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,14), (16,15)'::cube); -- no intersection, but one dimension intersects + cube_inter +------------------ + (3, 14),(10, 11) +(1 row) + +SELECT cube_inter('(1,2),(10,11)'::cube, '(10,11), (16,15)'::cube); -- point intersection + cube_inter +------------ + (10, 11) +(1 row) + +SELECT cube_inter('(1,2,3)'::cube, '(1,2,3)'::cube); -- point args + cube_inter +------------ + (1, 2, 3) +(1 row) + +SELECT cube_inter('(1,2,3)'::cube, '(5,6,3)'::cube); -- point args + cube_inter +--------------------- + (5, 6, 3),(1, 2, 3) +(1 row) + +-- Test of cube_size +-- +SELECT cube_size('(4,8),(15,16)'::cube); + cube_size +----------- + 88 +(1 row) + +SELECT cube_size('(42,137)'::cube); + cube_size +----------- + 0 +(1 row) + +-- Test of distances (euclidean distance may not be bit-exact) +-- +SET extra_float_digits = 0; +SELECT cube_distance('(1,1)'::cube, '(4,5)'::cube); + cube_distance +--------------- + 5 +(1 row) + +SELECT '(1,1)'::cube <-> '(4,5)'::cube as d_e; + d_e +----- + 5 +(1 row) + +RESET extra_float_digits; +SELECT distance_chebyshev('(1,1)'::cube, '(4,5)'::cube); + distance_chebyshev +-------------------- + 4 +(1 row) + +SELECT '(1,1)'::cube <=> '(4,5)'::cube as d_c; + d_c +----- + 4 +(1 row) + +SELECT distance_taxicab('(1,1)'::cube, '(4,5)'::cube); + distance_taxicab +------------------ + 7 +(1 row) + +SELECT '(1,1)'::cube <#> '(4,5)'::cube as d_t; + d_t +----- + 7 +(1 row) + +-- zero for overlapping +SELECT cube_distance('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); + cube_distance +--------------- + 0 +(1 row) + +SELECT distance_chebyshev('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); + distance_chebyshev +-------------------- + 0 +(1 row) + +SELECT distance_taxicab('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); + distance_taxicab +------------------ + 0 +(1 row) + +-- coordinate access +SELECT cube(array[10,20,30], array[40,50,60])->1; + ?column? +---------- + 10 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])->1; + ?column? +---------- + 40 +(1 row) + +SELECT cube(array[10,20,30], array[40,50,60])->6; + ?column? +---------- + 60 +(1 row) + +SELECT cube(array[10,20,30], array[40,50,60])->0; +ERROR: cube index 0 is out of bounds +SELECT cube(array[10,20,30], array[40,50,60])->7; +ERROR: cube index 7 is out of bounds +SELECT cube(array[10,20,30], array[40,50,60])->-1; +ERROR: cube index -1 is out of bounds +SELECT cube(array[10,20,30], array[40,50,60])->-6; +ERROR: cube index -6 is out of bounds +SELECT cube(array[10,20,30])->3; + ?column? +---------- + 30 +(1 row) + +SELECT cube(array[10,20,30])->6; + ?column? +---------- + 30 +(1 row) + +SELECT cube(array[10,20,30])->-6; +ERROR: cube index -6 is out of bounds +-- "normalized" coordinate access +SELECT cube(array[10,20,30], array[40,50,60])~>1; + ?column? +---------- + 10 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])~>1; + ?column? +---------- + 10 +(1 row) + +SELECT cube(array[10,20,30], array[40,50,60])~>2; + ?column? +---------- + 40 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])~>2; + ?column? +---------- + 40 +(1 row) + +SELECT cube(array[10,20,30], array[40,50,60])~>3; + ?column? +---------- + 20 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])~>3; + ?column? +---------- + 20 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])~>0; +ERROR: zero cube index is not defined +SELECT cube(array[40,50,60], array[10,20,30])~>4; + ?column? +---------- + 50 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])~>(-1); + ?column? +---------- + -10 +(1 row) + +-- Load some example data and build the index +-- +-- YB Note: Add ordering +CREATE TABLE test_cube (c cube, ybsort serial, PRIMARY KEY (ybsort ASC)); +\copy test_cube(c) from 'data/test_cube.data' +CREATE INDEX test_cube_ix ON test_cube USING gist (c); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +-- Test kNN +INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases +SET enable_seqscan = false; +-- Test different metrics +SET extra_float_digits = 0; +SELECT c, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------------------ + (337, 455),(240, 359) | 0 + (1, 1) | 140.007142674936 + (759, 187),(662, 163) | 162 + (948, 1201),(907, 1156) | 772.000647668122 + (1444, 403),(1346, 344) | 846 +(5 rows) + +RESET extra_float_digits; +SELECT c, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------ + (337, 455),(240, 359) | 0 + (1, 1) | 99 + (759, 187),(662, 163) | 162 + (948, 1201),(907, 1156) | 656 + (1444, 403),(1346, 344) | 846 +(5 rows) + +SELECT c, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------ + (337, 455),(240, 359) | 0 + (759, 187),(662, 163) | 162 + (1, 1) | 198 + (1444, 403),(1346, 344) | 846 + (369, 1457),(278, 1409) | 909 +(5 rows) + +-- Test sorting by coordinates +SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound + ?column? | c +----------+--------------------------- + 0 | (0, 100000) + 1 | (1, 1) + 3 | (54, 38679),(3, 38602) + 15 | (83, 10271),(15, 10265) + 64 | (122, 46832),(64, 46762) + 92 | (167, 17214),(92, 17184) + 107 | (161, 24465),(107, 24374) + 120 | (162, 26040),(120, 25963) + 138 | (154, 4019),(138, 3990) + 175 | (259, 1850),(175, 1820) + 179 | (207, 40886),(179, 40879) + 204 | (288, 49588),(204, 49571) + 226 | (270, 32616),(226, 32607) + 235 | (318, 31489),(235, 31404) + 240 | (337, 455),(240, 359) +(15 rows) + +SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound + ?column? | c +----------+--------------------------- + 0 | (0, 100000) + 1 | (1, 1) + 54 | (54, 38679),(3, 38602) + 83 | (83, 10271),(15, 10265) + 122 | (122, 46832),(64, 46762) + 154 | (154, 4019),(138, 3990) + 161 | (161, 24465),(107, 24374) + 162 | (162, 26040),(120, 25963) + 167 | (167, 17214),(92, 17184) + 207 | (207, 40886),(179, 40879) + 259 | (259, 1850),(175, 1820) + 270 | (270, 29508),(264, 29440) + 270 | (270, 32616),(226, 32607) + 288 | (288, 49588),(204, 49571) + 318 | (318, 31489),(235, 31404) +(15 rows) + +SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound + ?column? | c +----------+--------------------------- + 0 | (100000) + 1 | (1, 1) + 6 | (30333, 50),(30273, 6) + 43 | (43301, 75),(43227, 43) + 51 | (19650, 142),(19630, 51) + 81 | (2424, 160),(2424, 81) + 108 | (3449, 171),(3354, 108) + 109 | (18037, 155),(17941, 109) + 114 | (28511, 208),(28479, 114) + 118 | (19946, 217),(19941, 118) + 139 | (16906, 191),(16816, 139) + 163 | (759, 187),(662, 163) + 181 | (22684, 266),(22656, 181) + 213 | (24423, 255),(24360, 213) + 222 | (45989, 249),(45910, 222) +(15 rows) + +SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound + ?column? | c +----------+--------------------------- + 0 | (100000) + 1 | (1, 1) + 50 | (30333, 50),(30273, 6) + 75 | (43301, 75),(43227, 43) + 142 | (19650, 142),(19630, 51) + 155 | (18037, 155),(17941, 109) + 160 | (2424, 160),(2424, 81) + 171 | (3449, 171),(3354, 108) + 187 | (759, 187),(662, 163) + 191 | (16906, 191),(16816, 139) + 208 | (28511, 208),(28479, 114) + 217 | (19946, 217),(19941, 118) + 249 | (45989, 249),(45910, 222) + 255 | (24423, 255),(24360, 213) + 266 | (22684, 266),(22656, 181) +(15 rows) + +SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound + ?column? | c +----------+------------------------------- + -100000 | (100000) + -49951 | (50027, 49230),(49951, 49214) + -49937 | (49980, 35004),(49937, 34963) + -49927 | (49985, 6436),(49927, 6338) + -49908 | (49999, 27218),(49908, 27176) + -49905 | (49954, 1340),(49905, 1294) + -49902 | (49944, 25163),(49902, 25153) + -49898 | (49981, 34876),(49898, 34786) + -49897 | (49957, 43390),(49897, 43384) + -49848 | (49853, 18504),(49848, 18503) + -49818 | (49902, 41752),(49818, 41746) + -49810 | (49907, 30225),(49810, 30158) + -49808 | (49843, 5175),(49808, 5145) + -49805 | (49887, 24274),(49805, 24184) + -49798 | (49847, 7128),(49798, 7067) +(15 rows) + +SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound + ?column? | c +----------+------------------------------- + -100000 | (100000) + -50027 | (50027, 49230),(49951, 49214) + -49999 | (49999, 27218),(49908, 27176) + -49985 | (49985, 6436),(49927, 6338) + -49981 | (49981, 34876),(49898, 34786) + -49980 | (49980, 35004),(49937, 34963) + -49957 | (49957, 43390),(49897, 43384) + -49954 | (49954, 1340),(49905, 1294) + -49944 | (49944, 25163),(49902, 25153) + -49907 | (49907, 30225),(49810, 30158) + -49902 | (49902, 41752),(49818, 41746) + -49887 | (49887, 24274),(49805, 24184) + -49853 | (49853, 18504),(49848, 18503) + -49847 | (49847, 7128),(49798, 7067) + -49843 | (49843, 5175),(49808, 5145) +(15 rows) + +SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound + ?column? | c +----------+------------------------------- + -100000 | (0, 100000) + -49992 | (30746, 50040),(30727, 49992) + -49987 | (36311, 50073),(36258, 49987) + -49934 | (3531, 49962),(3463, 49934) + -49915 | (17954, 49975),(17865, 49915) + -49914 | (2168, 50012),(2108, 49914) + -49913 | (31287, 49923),(31236, 49913) + -49885 | (21551, 49983),(21492, 49885) + -49878 | (43925, 49912),(43888, 49878) + -49849 | (19128, 49932),(19112, 49849) + -49844 | (38266, 49852),(38233, 49844) + -49836 | (14913, 49873),(14849, 49836) + -49834 | (37595, 49849),(37581, 49834) + -49830 | (46151, 49848),(46058, 49830) + -49818 | (29261, 49910),(29247, 49818) +(15 rows) + +SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound + ?column? | c +----------+------------------------------- + -100000 | (0, 100000) + -50073 | (36311, 50073),(36258, 49987) + -50040 | (30746, 50040),(30727, 49992) + -50012 | (2168, 50012),(2108, 49914) + -49983 | (21551, 49983),(21492, 49885) + -49975 | (17954, 49975),(17865, 49915) + -49962 | (3531, 49962),(3463, 49934) + -49932 | (19128, 49932),(19112, 49849) + -49923 | (31287, 49923),(31236, 49913) + -49912 | (43925, 49912),(43888, 49878) + -49910 | (29261, 49910),(29247, 49818) + -49873 | (14913, 49873),(14849, 49836) + -49858 | (20007, 49858),(19921, 49778) + -49852 | (38266, 49852),(38233, 49844) + -49849 | (37595, 49849),(37581, 49834) +(15 rows) + +-- Same queries with sequential scan (should give the same results as above) +RESET enable_seqscan; +SET enable_indexscan = OFF; +SET extra_float_digits = 0; +SELECT c, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------------------ + (337, 455),(240, 359) | 0 + (1, 1) | 140.007142674936 + (759, 187),(662, 163) | 162 + (948, 1201),(907, 1156) | 772.000647668122 + (1444, 403),(1346, 344) | 846 +(5 rows) + +RESET extra_float_digits; +SELECT c, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------ + (337, 455),(240, 359) | 0 + (1, 1) | 99 + (759, 187),(662, 163) | 162 + (948, 1201),(907, 1156) | 656 + (1444, 403),(1346, 344) | 846 +(5 rows) + +SELECT c, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------ + (337, 455),(240, 359) | 0 + (759, 187),(662, 163) | 162 + (1, 1) | 198 + (1444, 403),(1346, 344) | 846 + (369, 1457),(278, 1409) | 909 +(5 rows) + +SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound + ?column? | c +----------+--------------------------- + 0 | (0, 100000) + 1 | (1, 1) + 3 | (54, 38679),(3, 38602) + 15 | (83, 10271),(15, 10265) + 64 | (122, 46832),(64, 46762) + 92 | (167, 17214),(92, 17184) + 107 | (161, 24465),(107, 24374) + 120 | (162, 26040),(120, 25963) + 138 | (154, 4019),(138, 3990) + 175 | (259, 1850),(175, 1820) + 179 | (207, 40886),(179, 40879) + 204 | (288, 49588),(204, 49571) + 226 | (270, 32616),(226, 32607) + 235 | (318, 31489),(235, 31404) + 240 | (337, 455),(240, 359) +(15 rows) + +SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound + ?column? | c +----------+--------------------------- + 0 | (0, 100000) + 1 | (1, 1) + 54 | (54, 38679),(3, 38602) + 83 | (83, 10271),(15, 10265) + 122 | (122, 46832),(64, 46762) + 154 | (154, 4019),(138, 3990) + 161 | (161, 24465),(107, 24374) + 162 | (162, 26040),(120, 25963) + 167 | (167, 17214),(92, 17184) + 207 | (207, 40886),(179, 40879) + 259 | (259, 1850),(175, 1820) + 270 | (270, 29508),(264, 29440) + 270 | (270, 32616),(226, 32607) + 288 | (288, 49588),(204, 49571) + 318 | (318, 31489),(235, 31404) +(15 rows) + +SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound + ?column? | c +----------+--------------------------- + 0 | (100000) + 1 | (1, 1) + 6 | (30333, 50),(30273, 6) + 43 | (43301, 75),(43227, 43) + 51 | (19650, 142),(19630, 51) + 81 | (2424, 160),(2424, 81) + 108 | (3449, 171),(3354, 108) + 109 | (18037, 155),(17941, 109) + 114 | (28511, 208),(28479, 114) + 118 | (19946, 217),(19941, 118) + 139 | (16906, 191),(16816, 139) + 163 | (759, 187),(662, 163) + 181 | (22684, 266),(22656, 181) + 213 | (24423, 255),(24360, 213) + 222 | (45989, 249),(45910, 222) +(15 rows) + +SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound + ?column? | c +----------+--------------------------- + 0 | (100000) + 1 | (1, 1) + 50 | (30333, 50),(30273, 6) + 75 | (43301, 75),(43227, 43) + 142 | (19650, 142),(19630, 51) + 155 | (18037, 155),(17941, 109) + 160 | (2424, 160),(2424, 81) + 171 | (3449, 171),(3354, 108) + 187 | (759, 187),(662, 163) + 191 | (16906, 191),(16816, 139) + 208 | (28511, 208),(28479, 114) + 217 | (19946, 217),(19941, 118) + 249 | (45989, 249),(45910, 222) + 255 | (24423, 255),(24360, 213) + 266 | (22684, 266),(22656, 181) +(15 rows) + +SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound + ?column? | c +----------+------------------------------- + -100000 | (100000) + -49951 | (50027, 49230),(49951, 49214) + -49937 | (49980, 35004),(49937, 34963) + -49927 | (49985, 6436),(49927, 6338) + -49908 | (49999, 27218),(49908, 27176) + -49905 | (49954, 1340),(49905, 1294) + -49902 | (49944, 25163),(49902, 25153) + -49898 | (49981, 34876),(49898, 34786) + -49897 | (49957, 43390),(49897, 43384) + -49848 | (49853, 18504),(49848, 18503) + -49818 | (49902, 41752),(49818, 41746) + -49810 | (49907, 30225),(49810, 30158) + -49808 | (49843, 5175),(49808, 5145) + -49805 | (49887, 24274),(49805, 24184) + -49798 | (49847, 7128),(49798, 7067) +(15 rows) + +SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound + ?column? | c +----------+------------------------------- + -100000 | (100000) + -50027 | (50027, 49230),(49951, 49214) + -49999 | (49999, 27218),(49908, 27176) + -49985 | (49985, 6436),(49927, 6338) + -49981 | (49981, 34876),(49898, 34786) + -49980 | (49980, 35004),(49937, 34963) + -49957 | (49957, 43390),(49897, 43384) + -49954 | (49954, 1340),(49905, 1294) + -49944 | (49944, 25163),(49902, 25153) + -49907 | (49907, 30225),(49810, 30158) + -49902 | (49902, 41752),(49818, 41746) + -49887 | (49887, 24274),(49805, 24184) + -49853 | (49853, 18504),(49848, 18503) + -49847 | (49847, 7128),(49798, 7067) + -49843 | (49843, 5175),(49808, 5145) +(15 rows) + +SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound + ?column? | c +----------+------------------------------- + -100000 | (0, 100000) + -49992 | (30746, 50040),(30727, 49992) + -49987 | (36311, 50073),(36258, 49987) + -49934 | (3531, 49962),(3463, 49934) + -49915 | (17954, 49975),(17865, 49915) + -49914 | (2168, 50012),(2108, 49914) + -49913 | (31287, 49923),(31236, 49913) + -49885 | (21551, 49983),(21492, 49885) + -49878 | (43925, 49912),(43888, 49878) + -49849 | (19128, 49932),(19112, 49849) + -49844 | (38266, 49852),(38233, 49844) + -49836 | (14913, 49873),(14849, 49836) + -49834 | (37595, 49849),(37581, 49834) + -49830 | (46151, 49848),(46058, 49830) + -49818 | (29261, 49910),(29247, 49818) +(15 rows) + +SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound + ?column? | c +----------+------------------------------- + -100000 | (0, 100000) + -50073 | (36311, 50073),(36258, 49987) + -50040 | (30746, 50040),(30727, 49992) + -50012 | (2168, 50012),(2108, 49914) + -49983 | (21551, 49983),(21492, 49885) + -49975 | (17954, 49975),(17865, 49915) + -49962 | (3531, 49962),(3463, 49934) + -49932 | (19128, 49932),(19112, 49849) + -49923 | (31287, 49923),(31236, 49913) + -49912 | (43925, 49912),(43888, 49878) + -49910 | (29261, 49910),(29247, 49818) + -49873 | (14913, 49873),(14849, 49836) + -49858 | (20007, 49858),(19921, 49778) + -49852 | (38266, 49852),(38233, 49844) + -49849 | (37595, 49849),(37581, 49834) +(15 rows) + +RESET enable_indexscan; diff --git a/contrib/cube/sql/yb.port.cube.sql b/contrib/cube/sql/yb.port.cube.sql new file mode 100644 index 0000000000000..94cfb56b15f73 --- /dev/null +++ b/contrib/cube/sql/yb.port.cube.sql @@ -0,0 +1,422 @@ +-- +-- Test cube datatype +-- + +CREATE EXTENSION cube; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + +-- +-- testing the input and output functions +-- + +-- Any number (a one-dimensional point) +SELECT '1'::cube AS cube; +SELECT '-1'::cube AS cube; +SELECT '1.'::cube AS cube; +SELECT '-1.'::cube AS cube; +SELECT '.1'::cube AS cube; +SELECT '-.1'::cube AS cube; +SELECT '1.0'::cube AS cube; +SELECT '-1.0'::cube AS cube; +SELECT 'infinity'::cube AS cube; +SELECT '-infinity'::cube AS cube; +SELECT 'NaN'::cube AS cube; +SELECT '.1234567890123456'::cube AS cube; +SELECT '+.1234567890123456'::cube AS cube; +SELECT '-.1234567890123456'::cube AS cube; + +-- simple lists (points) +SELECT '()'::cube AS cube; +SELECT '1,2'::cube AS cube; +SELECT '(1,2)'::cube AS cube; +SELECT '1,2,3,4,5'::cube AS cube; +SELECT '(1,2,3,4,5)'::cube AS cube; + +-- double lists (cubes) +SELECT '(),()'::cube AS cube; +SELECT '(0),(0)'::cube AS cube; +SELECT '(0),(1)'::cube AS cube; +SELECT '[(0),(0)]'::cube AS cube; +SELECT '[(0),(1)]'::cube AS cube; +SELECT '(0,0,0,0),(0,0,0,0)'::cube AS cube; +SELECT '(0,0,0,0),(1,0,0,0)'::cube AS cube; +SELECT '[(0,0,0,0),(0,0,0,0)]'::cube AS cube; +SELECT '[(0,0,0,0),(1,0,0,0)]'::cube AS cube; + +-- invalid input: parse errors +SELECT ''::cube AS cube; +SELECT 'ABC'::cube AS cube; +SELECT '[]'::cube AS cube; +SELECT '[()]'::cube AS cube; +SELECT '[(1)]'::cube AS cube; +SELECT '[(1),]'::cube AS cube; +SELECT '[(1),2]'::cube AS cube; +SELECT '[(1),(2),(3)]'::cube AS cube; +SELECT '1,'::cube AS cube; +SELECT '1,2,'::cube AS cube; +SELECT '1,,2'::cube AS cube; +SELECT '(1,)'::cube AS cube; +SELECT '(1,2,)'::cube AS cube; +SELECT '(1,,2)'::cube AS cube; + +-- invalid input: semantic errors and trailing garbage +SELECT '[(1),(2)],'::cube AS cube; -- 0 +SELECT '[(1,2,3),(2,3)]'::cube AS cube; -- 1 +SELECT '[(1,2),(1,2,3)]'::cube AS cube; -- 1 +SELECT '(1),(2),'::cube AS cube; -- 2 +SELECT '(1,2,3),(2,3)'::cube AS cube; -- 3 +SELECT '(1,2),(1,2,3)'::cube AS cube; -- 3 +SELECT '(1,2,3)ab'::cube AS cube; -- 4 +SELECT '(1,2,3)a'::cube AS cube; -- 5 +SELECT '(1,2)('::cube AS cube; -- 5 +SELECT '1,2ab'::cube AS cube; -- 6 +SELECT '1 e7'::cube AS cube; -- 6 +SELECT '1,2a'::cube AS cube; -- 7 +SELECT '1..2'::cube AS cube; -- 7 +SELECT '-1e-700'::cube AS cube; -- out of range + +-- +-- Testing building cubes from float8 values +-- + +SELECT cube(0::float8); +SELECT cube(1::float8); +SELECT cube(1,2); +SELECT cube(cube(1,2),3); +SELECT cube(cube(1,2),3,4); +SELECT cube(cube(cube(1,2),3,4),5); +SELECT cube(cube(cube(1,2),3,4),5,6); + +-- +-- Test that the text -> cube cast was installed. +-- + +SELECT '(0)'::text::cube; + +-- +-- Test the float[] -> cube cast +-- +SELECT cube('{0,1,2}'::float[], '{3,4,5}'::float[]); +SELECT cube('{0,1,2}'::float[], '{3}'::float[]); +SELECT cube(NULL::float[], '{3}'::float[]); +SELECT cube('{0,1,2}'::float[]); +SELECT cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[3,2,1,1]); +SELECT cube_subset(cube('(1,3,5),(1,3,5)'), ARRAY[3,2,1,1]); +SELECT cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[4,0]); +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), ARRAY[4,0]); +-- test for limits: this should pass +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), array(SELECT 1 as a FROM generate_series(1,100))); +-- and this should fail +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), array(SELECT 1 as a FROM generate_series(1,101))); + + + +-- +-- Test point processing +-- +SELECT cube('(1,2),(1,2)'); -- cube_in +SELECT cube('{0,1,2}'::float[], '{0,1,2}'::float[]); -- cube_a_f8_f8 +SELECT cube('{5,6,7,8}'::float[]); -- cube_a_f8 +SELECT cube(1.37); -- cube_f8 +SELECT cube(1.37, 1.37); -- cube_f8_f8 +SELECT cube(cube(1,1), 42); -- cube_c_f8 +SELECT cube(cube(1,2), 42); -- cube_c_f8 +SELECT cube(cube(1,1), 42, 42); -- cube_c_f8_f8 +SELECT cube(cube(1,1), 42, 24); -- cube_c_f8_f8 +SELECT cube(cube(1,2), 42, 42); -- cube_c_f8_f8 +SELECT cube(cube(1,2), 42, 24); -- cube_c_f8_f8 + +-- +-- Testing limit of CUBE_MAX_DIM dimensions check in cube_in. +-- +-- create too big cube from literal +select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)'::cube; +select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)'::cube; +-- from an array +select cube(array(SELECT 0 as a FROM generate_series(1,101))); +select cube(array(SELECT 0 as a FROM generate_series(1,101)),array(SELECT 0 as a FROM generate_series(1,101))); + +-- extend cube beyond limit +-- this should work +select cube(array(SELECT 0 as a FROM generate_series(1,100))); +select cube(array(SELECT 0 as a FROM generate_series(1,100)),array(SELECT 0 as a FROM generate_series(1,100))); +-- this should fail +select cube(cube(array(SELECT 0 as a FROM generate_series(1,100))), 0); +select cube(cube(array(SELECT 0 as a FROM generate_series(1,100)),array(SELECT 0 as a FROM generate_series(1,100))), 0, 0); + + +-- +-- testing the operators +-- + +-- equality/inequality: +-- +SELECT '24, 33.20'::cube = '24, 33.20'::cube AS bool; +SELECT '24, 33.20'::cube != '24, 33.20'::cube AS bool; +SELECT '24, 33.20'::cube = '24, 33.21'::cube AS bool; +SELECT '24, 33.20'::cube != '24, 33.21'::cube AS bool; +SELECT '(2,0),(3,1)'::cube = '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube = '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; + +-- "lower than" / "greater than" +-- (these operators are not useful for anything but ordering) +-- +SELECT '1'::cube > '2'::cube AS bool; +SELECT '1'::cube < '2'::cube AS bool; +SELECT '1,1'::cube > '1,2'::cube AS bool; +SELECT '1,1'::cube < '1,2'::cube AS bool; + +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,1),(3,1,0,0,0)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,1),(3,1,0,0,0)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; +SELECT '(2,0,0,0,0),(3,1,0,0,1)'::cube > '(2,0),(3,1)'::cube AS bool; +SELECT '(2,0,0,0,0),(3,1,0,0,1)'::cube < '(2,0),(3,1)'::cube AS bool; +SELECT '(2,0,0,0,1),(3,1,0,0,0)'::cube > '(2,0),(3,1)'::cube AS bool; +SELECT '(2,0,0,0,1),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool; +SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube > '(2,0),(3,1)'::cube AS bool; +SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool; + + +-- "overlap" +-- +SELECT '1'::cube && '1'::cube AS bool; +SELECT '1'::cube && '2'::cube AS bool; + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '0'::cube AS bool; +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '1'::cube AS bool; +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '1,1,1'::cube AS bool; +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(1,1,1),(2,2,2)]'::cube AS bool; +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(1,1),(2,2)]'::cube AS bool; +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(2,1,1),(2,2,2)]'::cube AS bool; + + +-- "contained in" (the left operand is the cube entirely enclosed by +-- the right operand): +-- +SELECT '0'::cube <@ '0'::cube AS bool; +SELECT '0,0,0'::cube <@ '0,0,0'::cube AS bool; +SELECT '0,0'::cube <@ '0,0,1'::cube AS bool; +SELECT '0,0,0'::cube <@ '0,0,1'::cube AS bool; +SELECT '1,0,0'::cube <@ '0,0,1'::cube AS bool; +SELECT '(1,0,0),(0,0,1)'::cube <@ '(1,0,0),(0,0,1)'::cube AS bool; +SELECT '(1,0,0),(0,0,1)'::cube <@ '(-1,-1,-1),(1,1,1)'::cube AS bool; +SELECT '(1,0,0),(0,0,1)'::cube <@ '(-1,-1,-1,-1),(1,1,1,1)'::cube AS bool; +SELECT '0'::cube <@ '(-1),(1)'::cube AS bool; +SELECT '1'::cube <@ '(-1),(1)'::cube AS bool; +SELECT '-1'::cube <@ '(-1),(1)'::cube AS bool; +SELECT '(-1),(1)'::cube <@ '(-1),(1)'::cube AS bool; +SELECT '(-1),(1)'::cube <@ '(-1,-1),(1,1)'::cube AS bool; +SELECT '(-2),(1)'::cube <@ '(-1),(1)'::cube AS bool; +SELECT '(-2),(1)'::cube <@ '(-1,-1),(1,1)'::cube AS bool; + + +-- "contains" (the left operand is the cube that entirely encloses the +-- right operand) +-- +SELECT '0'::cube @> '0'::cube AS bool; +SELECT '0,0,0'::cube @> '0,0,0'::cube AS bool; +SELECT '0,0,1'::cube @> '0,0'::cube AS bool; +SELECT '0,0,1'::cube @> '0,0,0'::cube AS bool; +SELECT '0,0,1'::cube @> '1,0,0'::cube AS bool; +SELECT '(1,0,0),(0,0,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; +SELECT '(-1,-1,-1),(1,1,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; +SELECT '(-1,-1,-1,-1),(1,1,1,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; +SELECT '(-1),(1)'::cube @> '0'::cube AS bool; +SELECT '(-1),(1)'::cube @> '1'::cube AS bool; +SELECT '(-1),(1)'::cube @> '-1'::cube AS bool; +SELECT '(-1),(1)'::cube @> '(-1),(1)'::cube AS bool; +SELECT '(-1,-1),(1,1)'::cube @> '(-1),(1)'::cube AS bool; +SELECT '(-1),(1)'::cube @> '(-2),(1)'::cube AS bool; +SELECT '(-1,-1),(1,1)'::cube @> '(-2),(1)'::cube AS bool; + +-- Test of distance function +-- +SELECT cube_distance('(0)'::cube,'(2,2,2,2)'::cube); +SELECT cube_distance('(0)'::cube,'(.3,.4)'::cube); +SELECT cube_distance('(2,3,4)'::cube,'(2,3,4)'::cube); +SELECT cube_distance('(42,42,42,42)'::cube,'(137,137,137,137)'::cube); +SELECT cube_distance('(42,42,42)'::cube,'(137,137)'::cube); + +-- Test of cube function (text to cube) +-- +SELECT cube('(1,1.2)'::text); +SELECT cube(NULL); + +-- Test of cube_dim function (dimensions stored in cube) +-- +SELECT cube_dim('(0)'::cube); +SELECT cube_dim('(0,0)'::cube); +SELECT cube_dim('(0,0,0)'::cube); +SELECT cube_dim('(42,42,42),(42,42,42)'::cube); +SELECT cube_dim('(4,8,15,16,23),(4,8,15,16,23)'::cube); + +-- Test of cube_ll_coord function (retrieves LL coordinate values) +-- +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 1); +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 2); +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 3); +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 1); +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 2); +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 3); +SELECT cube_ll_coord('(42,137)'::cube, 1); +SELECT cube_ll_coord('(42,137)'::cube, 2); +SELECT cube_ll_coord('(42,137)'::cube, 3); + +-- Test of cube_ur_coord function (retrieves UR coordinate values) +-- +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 1); +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 2); +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 3); +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 1); +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 2); +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 3); +SELECT cube_ur_coord('(42,137)'::cube, 1); +SELECT cube_ur_coord('(42,137)'::cube, 2); +SELECT cube_ur_coord('(42,137)'::cube, 3); + +-- Test of cube_is_point +-- +SELECT cube_is_point('(0)'::cube); +SELECT cube_is_point('(0,1,2)'::cube); +SELECT cube_is_point('(0,1,2),(0,1,2)'::cube); +SELECT cube_is_point('(0,1,2),(-1,1,2)'::cube); +SELECT cube_is_point('(0,1,2),(0,-1,2)'::cube); +SELECT cube_is_point('(0,1,2),(0,1,-2)'::cube); + +-- Test of cube_enlarge (enlarging and shrinking cubes) +-- +SELECT cube_enlarge('(0)'::cube, 0, 0); +SELECT cube_enlarge('(0)'::cube, 0, 1); +SELECT cube_enlarge('(0)'::cube, 0, 2); +SELECT cube_enlarge('(2),(-2)'::cube, 0, 4); +SELECT cube_enlarge('(0)'::cube, 1, 0); +SELECT cube_enlarge('(0)'::cube, 1, 1); +SELECT cube_enlarge('(0)'::cube, 1, 2); +SELECT cube_enlarge('(2),(-2)'::cube, 1, 4); +SELECT cube_enlarge('(0)'::cube, -1, 0); +SELECT cube_enlarge('(0)'::cube, -1, 1); +SELECT cube_enlarge('(0)'::cube, -1, 2); +SELECT cube_enlarge('(2),(-2)'::cube, -1, 4); +SELECT cube_enlarge('(0,0,0)'::cube, 1, 0); +SELECT cube_enlarge('(0,0,0)'::cube, 1, 2); +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, 1, 2); +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, 3, 2); +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, -1, 2); +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, -3, 2); +SELECT cube_enlarge('(42,-23,-23),(42,23,23)'::cube, -23, 5); +SELECT cube_enlarge('(42,-23,-23),(42,23,23)'::cube, -24, 5); + +-- Test of cube_union (MBR for two cubes) +-- +SELECT cube_union('(1,2),(3,4)'::cube, '(5,6,7),(8,9,10)'::cube); +SELECT cube_union('(1,2)'::cube, '(4,2,0,0)'::cube); +SELECT cube_union('(1,2),(1,2)'::cube, '(4,2),(4,2)'::cube); +SELECT cube_union('(1,2),(1,2)'::cube, '(1,2),(1,2)'::cube); +SELECT cube_union('(1,2),(1,2)'::cube, '(1,2,0),(1,2,0)'::cube); + +-- Test of cube_inter +-- +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,4), (16,15)'::cube); -- intersects +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,4), (6,5)'::cube); -- includes +SELECT cube_inter('(1,2),(10,11)'::cube, '(13,14), (16,15)'::cube); -- no intersection +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,14), (16,15)'::cube); -- no intersection, but one dimension intersects +SELECT cube_inter('(1,2),(10,11)'::cube, '(10,11), (16,15)'::cube); -- point intersection +SELECT cube_inter('(1,2,3)'::cube, '(1,2,3)'::cube); -- point args +SELECT cube_inter('(1,2,3)'::cube, '(5,6,3)'::cube); -- point args + +-- Test of cube_size +-- +SELECT cube_size('(4,8),(15,16)'::cube); +SELECT cube_size('(42,137)'::cube); + +-- Test of distances (euclidean distance may not be bit-exact) +-- +SET extra_float_digits = 0; +SELECT cube_distance('(1,1)'::cube, '(4,5)'::cube); +SELECT '(1,1)'::cube <-> '(4,5)'::cube as d_e; +RESET extra_float_digits; +SELECT distance_chebyshev('(1,1)'::cube, '(4,5)'::cube); +SELECT '(1,1)'::cube <=> '(4,5)'::cube as d_c; +SELECT distance_taxicab('(1,1)'::cube, '(4,5)'::cube); +SELECT '(1,1)'::cube <#> '(4,5)'::cube as d_t; +-- zero for overlapping +SELECT cube_distance('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); +SELECT distance_chebyshev('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); +SELECT distance_taxicab('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); +-- coordinate access +SELECT cube(array[10,20,30], array[40,50,60])->1; +SELECT cube(array[40,50,60], array[10,20,30])->1; +SELECT cube(array[10,20,30], array[40,50,60])->6; +SELECT cube(array[10,20,30], array[40,50,60])->0; +SELECT cube(array[10,20,30], array[40,50,60])->7; +SELECT cube(array[10,20,30], array[40,50,60])->-1; +SELECT cube(array[10,20,30], array[40,50,60])->-6; +SELECT cube(array[10,20,30])->3; +SELECT cube(array[10,20,30])->6; +SELECT cube(array[10,20,30])->-6; +-- "normalized" coordinate access +SELECT cube(array[10,20,30], array[40,50,60])~>1; +SELECT cube(array[40,50,60], array[10,20,30])~>1; +SELECT cube(array[10,20,30], array[40,50,60])~>2; +SELECT cube(array[40,50,60], array[10,20,30])~>2; +SELECT cube(array[10,20,30], array[40,50,60])~>3; +SELECT cube(array[40,50,60], array[10,20,30])~>3; + +SELECT cube(array[40,50,60], array[10,20,30])~>0; +SELECT cube(array[40,50,60], array[10,20,30])~>4; +SELECT cube(array[40,50,60], array[10,20,30])~>(-1); + +-- Load some example data and build the index +-- +-- YB Note: Add ordering +CREATE TABLE test_cube (c cube, ybsort serial, PRIMARY KEY (ybsort ASC)); + +\copy test_cube(c) from 'data/test_cube.data' + +CREATE INDEX test_cube_ix ON test_cube USING gist (c); + +-- Test kNN +INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases +SET enable_seqscan = false; + +-- Test different metrics +SET extra_float_digits = 0; +SELECT c, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5; +RESET extra_float_digits; +SELECT c, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5; +SELECT c, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5; + +-- Test sorting by coordinates +SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound +SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound +SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound +SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound +SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound +SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound +SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound +SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound + +-- Same queries with sequential scan (should give the same results as above) +RESET enable_seqscan; +SET enable_indexscan = OFF; +SET extra_float_digits = 0; +SELECT c, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5; +RESET extra_float_digits; +SELECT c, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5; +SELECT c, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5; +SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound +SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound +SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound +SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound +SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound +SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound +SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound +SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound +RESET enable_indexscan; diff --git a/contrib/cube/yb_schedule b/contrib/cube/yb_schedule new file mode 100644 index 0000000000000..3bbea72e78a06 --- /dev/null +++ b/contrib/cube/yb_schedule @@ -0,0 +1,6 @@ +# contrib/cube/yb_schedule + +test: yb.port.cube + +# No need to port this one. +test: cube_sci diff --git a/contrib/earthdistance/expected/yb.port.earthdistance.out b/contrib/earthdistance/expected/yb.port.earthdistance.out new file mode 100644 index 0000000000000..c36a24096d6a8 --- /dev/null +++ b/contrib/earthdistance/expected/yb.port.earthdistance.out @@ -0,0 +1,1100 @@ +-- +-- Test earthdistance extension +-- +-- In this file we also do some testing of extension create/drop scenarios. +-- That's really exercising the core database's dependency logic, so ideally +-- we'd do it in the core regression tests, but we can't for lack of suitable +-- guaranteed-available extensions. earthdistance is a good test case because +-- it has a dependency on the cube extension. +-- +CREATE EXTENSION earthdistance; -- fail, must install cube first +ERROR: required extension "cube" is not installed +HINT: Use CREATE EXTENSION ... CASCADE to install required extensions too. +CREATE EXTENSION cube; +CREATE EXTENSION earthdistance; +-- +-- The radius of the Earth we are using. +-- +SELECT earth()::numeric(20,5); + earth +--------------- + 6378168.00000 +(1 row) + +-- +-- Convert straight line distances to great circle distances. +-- +SELECT (pi()*earth())::numeric(20,5); + numeric +---------------- + 20037605.73216 +(1 row) + +SELECT sec_to_gc(0)::numeric(20,5); + sec_to_gc +----------- + 0.00000 +(1 row) + +SELECT sec_to_gc(2*earth())::numeric(20,5); + sec_to_gc +---------------- + 20037605.73216 +(1 row) + +SELECT sec_to_gc(10*earth())::numeric(20,5); + sec_to_gc +---------------- + 20037605.73216 +(1 row) + +SELECT sec_to_gc(-earth())::numeric(20,5); + sec_to_gc +----------- + 0.00000 +(1 row) + +SELECT sec_to_gc(1000)::numeric(20,5); + sec_to_gc +------------ + 1000.00000 +(1 row) + +SELECT sec_to_gc(10000)::numeric(20,5); + sec_to_gc +------------- + 10000.00102 +(1 row) + +SELECT sec_to_gc(100000)::numeric(20,5); + sec_to_gc +-------------- + 100001.02426 +(1 row) + +SELECT sec_to_gc(1000000)::numeric(20,5); + sec_to_gc +--------------- + 1001027.07131 +(1 row) + +-- +-- Convert great circle distances to straight line distances. +-- +SELECT gc_to_sec(0)::numeric(20,5); + gc_to_sec +----------- + 0.00000 +(1 row) + +SELECT gc_to_sec(sec_to_gc(2*earth()))::numeric(20,5); + gc_to_sec +---------------- + 12756336.00000 +(1 row) + +SELECT gc_to_sec(10*earth())::numeric(20,5); + gc_to_sec +---------------- + 12756336.00000 +(1 row) + +SELECT gc_to_sec(pi()*earth())::numeric(20,5); + gc_to_sec +---------------- + 12756336.00000 +(1 row) + +SELECT gc_to_sec(-1000)::numeric(20,5); + gc_to_sec +----------- + 0.00000 +(1 row) + +SELECT gc_to_sec(1000)::numeric(20,5); + gc_to_sec +------------ + 1000.00000 +(1 row) + +SELECT gc_to_sec(10000)::numeric(20,5); + gc_to_sec +------------ + 9999.99898 +(1 row) + +SELECT gc_to_sec(100000)::numeric(20,5); + gc_to_sec +------------- + 99998.97577 +(1 row) + +SELECT gc_to_sec(1000000)::numeric(20,5); + gc_to_sec +-------------- + 998976.08618 +(1 row) + +-- +-- Set coordinates using latitude and longitude. +-- Extract each coordinate separately so we can round them. +-- +SELECT cube_ll_coord(ll_to_earth(0,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,0),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+--------------- + 6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(360,360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(360,360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(360,360),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+--------------- + 6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(180,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,180),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+--------------- + 6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(180,360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,360),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +----------------+---------------+--------------- + -6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(-180,-360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-180,-360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-180,-360),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +----------------+---------------+--------------- + -6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(0,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,180),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +----------------+---------------+--------------- + -6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(0,-180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,-180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,-180),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +----------------+---------------+--------------- + -6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(90,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,0),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+--------------- + 0.00000 | 0.00000 | 6378168.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(90,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,180),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+--------------- + 0.00000 | 0.00000 | 6378168.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(-90,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,0),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+---------------- + 0.00000 | 0.00000 | -6378168.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(-90,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,180),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+---------------- + 0.00000 | 0.00000 | -6378168.00000 +(1 row) + +-- +-- Test getting the latitude of a location. +-- +SELECT latitude(ll_to_earth(0,0))::numeric(20,10); + latitude +-------------- + 0.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(45,0))::numeric(20,10); + latitude +--------------- + 45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(90,0))::numeric(20,10); + latitude +--------------- + 90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-45,0))::numeric(20,10); + latitude +---------------- + -45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-90,0))::numeric(20,10); + latitude +---------------- + -90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(0,90))::numeric(20,10); + latitude +-------------- + 0.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(45,90))::numeric(20,10); + latitude +--------------- + 45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(90,90))::numeric(20,10); + latitude +--------------- + 90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-45,90))::numeric(20,10); + latitude +---------------- + -45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-90,90))::numeric(20,10); + latitude +---------------- + -90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(0,180))::numeric(20,10); + latitude +-------------- + 0.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(45,180))::numeric(20,10); + latitude +--------------- + 45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(90,180))::numeric(20,10); + latitude +--------------- + 90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-45,180))::numeric(20,10); + latitude +---------------- + -45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-90,180))::numeric(20,10); + latitude +---------------- + -90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(0,-90))::numeric(20,10); + latitude +-------------- + 0.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(45,-90))::numeric(20,10); + latitude +--------------- + 45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(90,-90))::numeric(20,10); + latitude +--------------- + 90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-45,-90))::numeric(20,10); + latitude +---------------- + -45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-90,-90))::numeric(20,10); + latitude +---------------- + -90.0000000000 +(1 row) + +-- +-- Test getting the longitude of a location. +-- +SELECT longitude(ll_to_earth(0,0))::numeric(20,10); + longitude +-------------- + 0.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(45,0))::numeric(20,10); + longitude +-------------- + 0.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(90,0))::numeric(20,10); + longitude +-------------- + 0.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-45,0))::numeric(20,10); + longitude +-------------- + 0.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-90,0))::numeric(20,10); + longitude +-------------- + 0.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(0,90))::numeric(20,10); + longitude +--------------- + 90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(45,90))::numeric(20,10); + longitude +--------------- + 90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(90,90))::numeric(20,10); + longitude +--------------- + 90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-45,90))::numeric(20,10); + longitude +--------------- + 90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-90,90))::numeric(20,10); + longitude +--------------- + 90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(0,180))::numeric(20,10); + longitude +---------------- + 180.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(45,180))::numeric(20,10); + longitude +---------------- + 180.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(90,180))::numeric(20,10); + longitude +---------------- + 180.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-45,180))::numeric(20,10); + longitude +---------------- + 180.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-90,180))::numeric(20,10); + longitude +---------------- + 180.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(0,-90))::numeric(20,10); + longitude +---------------- + -90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(45,-90))::numeric(20,10); + longitude +---------------- + -90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(90,-90))::numeric(20,10); + longitude +---------------- + -90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-45,-90))::numeric(20,10); + longitude +---------------- + -90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-90,-90))::numeric(20,10); + longitude +---------------- + -90.0000000000 +(1 row) + +-- +-- For the distance tests the following is some real life data. +-- +-- Chicago has a latitude of 41.8 and a longitude of 87.6. +-- Albuquerque has a latitude of 35.1 and a longitude of 106.7. +-- (Note that latitude and longitude are specified differently +-- in the cube based functions than for the point based functions.) +-- +-- +-- Test getting the distance between two points using earth_distance. +-- +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,0))::numeric(20,5); + earth_distance +---------------- + 0.00000 +(1 row) + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,180))::numeric(20,5); + earth_distance +---------------- + 20037605.73216 +(1 row) + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(90,0))::numeric(20,5); + earth_distance +---------------- + 10018802.86608 +(1 row) + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,90))::numeric(20,5); + earth_distance +---------------- + 10018802.86608 +(1 row) + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))::numeric(20,5); + earth_distance +---------------- + 111320.03185 +(1 row) + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(1,0))::numeric(20,5); + earth_distance +---------------- + 111320.03185 +(1 row) + +SELECT earth_distance(ll_to_earth(30,0),ll_to_earth(30,1))::numeric(20,5); + earth_distance +---------------- + 96405.66962 +(1 row) + +SELECT earth_distance(ll_to_earth(30,0),ll_to_earth(31,0))::numeric(20,5); + earth_distance +---------------- + 111320.03185 +(1 row) + +SELECT earth_distance(ll_to_earth(60,0),ll_to_earth(60,1))::numeric(20,5); + earth_distance +---------------- + 55659.48608 +(1 row) + +SELECT earth_distance(ll_to_earth(60,0),ll_to_earth(61,0))::numeric(20,5); + earth_distance +---------------- + 111320.03185 +(1 row) + +SELECT earth_distance(ll_to_earth(41.8,87.6),ll_to_earth(35.1,106.7))::numeric(20,5); + earth_distance +---------------- + 1819303.21265 +(1 row) + +SELECT (earth_distance(ll_to_earth(41.8,87.6),ll_to_earth(35.1,106.7))* + 100./2.54/12./5280.)::numeric(20,5); + numeric +------------ + 1130.46261 +(1 row) + +-- +-- Test getting the distance between two points using geo_distance. +-- +SELECT geo_distance('(0,0)'::point,'(0,0)'::point)::numeric(20,5); + geo_distance +-------------- + 0.00000 +(1 row) + +SELECT geo_distance('(0,0)'::point,'(180,0)'::point)::numeric(20,5); + geo_distance +-------------- + 12436.77274 +(1 row) + +SELECT geo_distance('(0,0)'::point,'(0,90)'::point)::numeric(20,5); + geo_distance +-------------- + 6218.38637 +(1 row) + +SELECT geo_distance('(0,0)'::point,'(90,0)'::point)::numeric(20,5); + geo_distance +-------------- + 6218.38637 +(1 row) + +SELECT geo_distance('(0,0)'::point,'(1,0)'::point)::numeric(20,5); + geo_distance +-------------- + 69.09318 +(1 row) + +SELECT geo_distance('(0,0)'::point,'(0,1)'::point)::numeric(20,5); + geo_distance +-------------- + 69.09318 +(1 row) + +SELECT geo_distance('(0,30)'::point,'(1,30)'::point)::numeric(20,5); + geo_distance +-------------- + 59.83626 +(1 row) + +SELECT geo_distance('(0,30)'::point,'(0,31)'::point)::numeric(20,5); + geo_distance +-------------- + 69.09318 +(1 row) + +SELECT geo_distance('(0,60)'::point,'(1,60)'::point)::numeric(20,5); + geo_distance +-------------- + 34.54626 +(1 row) + +SELECT geo_distance('(0,60)'::point,'(0,61)'::point)::numeric(20,5); + geo_distance +-------------- + 69.09318 +(1 row) + +SELECT geo_distance('(87.6,41.8)'::point,'(106.7,35.1)'::point)::numeric(20,5); + geo_distance +-------------- + 1129.18983 +(1 row) + +SELECT (geo_distance('(87.6,41.8)'::point,'(106.7,35.1)'::point)*5280.*12.*2.54/100.)::numeric(20,5); + numeric +--------------- + 1817254.87730 +(1 row) + +-- +-- Test getting the distance between two points using the <@> operator. +-- +SELECT ('(0,0)'::point <@> '(0,0)'::point)::numeric(20,5); + numeric +--------- + 0.00000 +(1 row) + +SELECT ('(0,0)'::point <@> '(180,0)'::point)::numeric(20,5); + numeric +------------- + 12436.77274 +(1 row) + +SELECT ('(0,0)'::point <@> '(0,90)'::point)::numeric(20,5); + numeric +------------ + 6218.38637 +(1 row) + +SELECT ('(0,0)'::point <@> '(90,0)'::point)::numeric(20,5); + numeric +------------ + 6218.38637 +(1 row) + +SELECT ('(0,0)'::point <@> '(1,0)'::point)::numeric(20,5); + numeric +---------- + 69.09318 +(1 row) + +SELECT ('(0,0)'::point <@> '(0,1)'::point)::numeric(20,5); + numeric +---------- + 69.09318 +(1 row) + +SELECT ('(0,30)'::point <@> '(1,30)'::point)::numeric(20,5); + numeric +---------- + 59.83626 +(1 row) + +SELECT ('(0,30)'::point <@> '(0,31)'::point)::numeric(20,5); + numeric +---------- + 69.09318 +(1 row) + +SELECT ('(0,60)'::point <@> '(1,60)'::point)::numeric(20,5); + numeric +---------- + 34.54626 +(1 row) + +SELECT ('(0,60)'::point <@> '(0,61)'::point)::numeric(20,5); + numeric +---------- + 69.09318 +(1 row) + +SELECT ('(87.6,41.8)'::point <@> '(106.7,35.1)'::point)::numeric(20,5); + numeric +------------ + 1129.18983 +(1 row) + +SELECT (('(87.6,41.8)'::point <@> '(106.7,35.1)'::point)*5280.*12.*2.54/100.)::numeric(20,5); + numeric +--------------- + 1817254.87730 +(1 row) + +-- +-- Test getting a bounding box around points. +-- +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),112000),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),112000),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),112000),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord | cube_ur_coord | cube_ur_coord | cube_ur_coord +---------------+---------------+---------------+---------------+---------------+--------------- + 6266169.43896 | -111998.56104 | -111998.56104 | 6490166.56104 | 111998.56104 | 111998.56104 +(1 row) + +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord | cube_ur_coord | cube_ur_coord | cube_ur_coord +----------------+-----------------+-----------------+----------------+----------------+---------------- + -6378168.00000 | -12756336.00000 | -12756336.00000 | 19134504.00000 | 12756336.00000 | 12756336.00000 +(1 row) + +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord | cube_ur_coord | cube_ur_coord | cube_ur_coord +----------------+-----------------+-----------------+----------------+----------------+---------------- + -6378168.00000 | -12756336.00000 | -12756336.00000 | 19134504.00000 | 12756336.00000 | 12756336.00000 +(1 row) + +-- +-- Test for points that should be in bounding boxes. +-- +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*1.00001) @> + ll_to_earth(0,1); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*1.00001) @> + ll_to_earth(0,0.1); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.01))*1.00001) @> + ll_to_earth(0,0.01); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.001))*1.00001) @> + ll_to_earth(0,0.001); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.0001))*1.00001) @> + ll_to_earth(0,0.0001); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0.0001,0.0001))*1.00001) @> + ll_to_earth(0.0001,0.0001); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(45,45), + earth_distance(ll_to_earth(45,45),ll_to_earth(45.0001,45.0001))*1.00001) @> + ll_to_earth(45.0001,45.0001); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(90,180), + earth_distance(ll_to_earth(90,180),ll_to_earth(90.0001,180.0001))*1.00001) @> + ll_to_earth(90.0001,180.0001); + ?column? +---------- + t +(1 row) + +-- +-- Test for points that shouldn't be in bounding boxes. Note that we need +-- to make points way outside, since some points close may be in the box +-- but further away than the distance we are testing. +-- +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*.57735) @> + ll_to_earth(0,1); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*.57735) @> + ll_to_earth(0,0.1); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.01))*.57735) @> + ll_to_earth(0,0.01); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.001))*.57735) @> + ll_to_earth(0,0.001); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.0001))*.57735) @> + ll_to_earth(0,0.0001); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0.0001,0.0001))*.57735) @> + ll_to_earth(0.0001,0.0001); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(45,45), + earth_distance(ll_to_earth(45,45),ll_to_earth(45.0001,45.0001))*.57735) @> + ll_to_earth(45.0001,45.0001); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(90,180), + earth_distance(ll_to_earth(90,180),ll_to_earth(90.0001,180.0001))*.57735) @> + ll_to_earth(90.0001,180.0001); + ?column? +---------- + f +(1 row) + +-- +-- Test the recommended constraints. +-- +SELECT cube_is_point(ll_to_earth(0,0)); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_dim(ll_to_earth(0,0)) <= 3; + ?column? +---------- + t +(1 row) + +SELECT abs(cube_distance(ll_to_earth(0,0), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; + ?column? +---------- + t +(1 row) + +SELECT cube_is_point(ll_to_earth(30,60)); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_dim(ll_to_earth(30,60)) <= 3; + ?column? +---------- + t +(1 row) + +SELECT abs(cube_distance(ll_to_earth(30,60), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; + ?column? +---------- + t +(1 row) + +SELECT cube_is_point(ll_to_earth(60,90)); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_dim(ll_to_earth(60,90)) <= 3; + ?column? +---------- + t +(1 row) + +SELECT abs(cube_distance(ll_to_earth(60,90), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; + ?column? +---------- + t +(1 row) + +SELECT cube_is_point(ll_to_earth(-30,-90)); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_dim(ll_to_earth(-30,-90)) <= 3; + ?column? +---------- + t +(1 row) + +SELECT abs(cube_distance(ll_to_earth(-30,-90), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; + ?column? +---------- + t +(1 row) + +-- +-- Now we are going to test extension create/drop scenarios. +-- +-- list what's installed +\dT + List of data types + Schema | Name | Description +--------+-------+--------------------------------------------------------------------------------------------- + public | cube | multi-dimensional cube '(FLOAT-1, FLOAT-2, ..., FLOAT-N), (FLOAT-1, FLOAT-2, ..., FLOAT-N)' + public | earth | +(2 rows) + +-- YB note: the errdetail of the following command varies in YB for some +-- reason, like "extension earthdistance depends on type cube" or "...depends +-- on extension cube" or "...depends on function cube_out(cube)". It also +-- can't be suppressed using client_min_messages since it's an ERROR message, +-- the highest level. Therefore, comment it out. +-- drop extension cube; -- fail, earthdistance requires it +drop extension earthdistance; +drop type cube; -- fail, extension cube requires it +ERROR: cannot drop type cube because extension cube requires it +HINT: You can drop extension cube instead. +-- list what's installed +\dT + List of data types + Schema | Name | Description +--------+------+--------------------------------------------------------------------------------------------- + public | cube | multi-dimensional cube '(FLOAT-1, FLOAT-2, ..., FLOAT-N), (FLOAT-1, FLOAT-2, ..., FLOAT-N)' +(1 row) + +create table foo (f1 cube, f2 int); +drop extension cube; -- fail, foo.f1 requires it +ERROR: cannot drop extension cube because other objects depend on it +DETAIL: column f1 of table foo depends on type cube +HINT: Use DROP ... CASCADE to drop the dependent objects too. +drop table foo; +drop extension cube; +-- list what's installed +\dT + List of data types + Schema | Name | Description +--------+------+------------- +(0 rows) + +\df + List of functions + Schema | Name | Result data type | Argument data types | Type +--------+------+------------------+---------------------+------ +(0 rows) + +\do + List of operators + Schema | Name | Left arg type | Right arg type | Result type | Description +--------+------+---------------+----------------+-------------+------------- +(0 rows) + +create schema c; +create extension cube with schema c; +-- list what's installed +\dT public.* + List of data types + Schema | Name | Description +--------+------+------------- +(0 rows) + +\df public.* + List of functions + Schema | Name | Result data type | Argument data types | Type +--------+------+------------------+---------------------+------ +(0 rows) + +\do public.* + List of operators + Schema | Name | Left arg type | Right arg type | Result type | Description +--------+------+---------------+----------------+-------------+------------- +(0 rows) + +\dT c.* + List of data types + Schema | Name | Description +--------+--------+--------------------------------------------------------------------------------------------- + c | c.cube | multi-dimensional cube '(FLOAT-1, FLOAT-2, ..., FLOAT-N), (FLOAT-1, FLOAT-2, ..., FLOAT-N)' +(1 row) + +create table foo (f1 c.cube, f2 int); +drop extension cube; -- fail, foo.f1 requires it +ERROR: cannot drop extension cube because other objects depend on it +DETAIL: column f1 of table foo depends on type c.cube +HINT: Use DROP ... CASCADE to drop the dependent objects too. +drop schema c; -- fail, cube requires it +ERROR: cannot drop schema c because other objects depend on it +DETAIL: column f1 of table foo depends on type c.cube +extension cube depends on schema c +HINT: Use DROP ... CASCADE to drop the dependent objects too. +drop extension cube cascade; +NOTICE: drop cascades to column f1 of table foo +\d foo + Table "public.foo" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + f2 | integer | | | + +-- list what's installed +\dT public.* + List of data types + Schema | Name | Description +--------+------+------------- +(0 rows) + +\df public.* + List of functions + Schema | Name | Result data type | Argument data types | Type +--------+------+------------------+---------------------+------ +(0 rows) + +\do public.* + List of operators + Schema | Name | Left arg type | Right arg type | Result type | Description +--------+------+---------------+----------------+-------------+------------- +(0 rows) + +\dT c.* + List of data types + Schema | Name | Description +--------+------+------------- +(0 rows) + +\df c.* + List of functions + Schema | Name | Result data type | Argument data types | Type +--------+------+------------------+---------------------+------ +(0 rows) + +\do c.* + List of operators + Schema | Name | Left arg type | Right arg type | Result type | Description +--------+------+---------------+----------------+-------------+------------- +(0 rows) + +drop schema c; diff --git a/contrib/earthdistance/sql/yb.port.earthdistance.sql b/contrib/earthdistance/sql/yb.port.earthdistance.sql new file mode 100644 index 0000000000000..cf030126099f8 --- /dev/null +++ b/contrib/earthdistance/sql/yb.port.earthdistance.sql @@ -0,0 +1,364 @@ +-- +-- Test earthdistance extension +-- +-- In this file we also do some testing of extension create/drop scenarios. +-- That's really exercising the core database's dependency logic, so ideally +-- we'd do it in the core regression tests, but we can't for lack of suitable +-- guaranteed-available extensions. earthdistance is a good test case because +-- it has a dependency on the cube extension. +-- + +CREATE EXTENSION earthdistance; -- fail, must install cube first +CREATE EXTENSION cube; +CREATE EXTENSION earthdistance; + +-- +-- The radius of the Earth we are using. +-- + +SELECT earth()::numeric(20,5); + +-- +-- Convert straight line distances to great circle distances. +-- +SELECT (pi()*earth())::numeric(20,5); +SELECT sec_to_gc(0)::numeric(20,5); +SELECT sec_to_gc(2*earth())::numeric(20,5); +SELECT sec_to_gc(10*earth())::numeric(20,5); +SELECT sec_to_gc(-earth())::numeric(20,5); +SELECT sec_to_gc(1000)::numeric(20,5); +SELECT sec_to_gc(10000)::numeric(20,5); +SELECT sec_to_gc(100000)::numeric(20,5); +SELECT sec_to_gc(1000000)::numeric(20,5); + +-- +-- Convert great circle distances to straight line distances. +-- + +SELECT gc_to_sec(0)::numeric(20,5); +SELECT gc_to_sec(sec_to_gc(2*earth()))::numeric(20,5); +SELECT gc_to_sec(10*earth())::numeric(20,5); +SELECT gc_to_sec(pi()*earth())::numeric(20,5); +SELECT gc_to_sec(-1000)::numeric(20,5); +SELECT gc_to_sec(1000)::numeric(20,5); +SELECT gc_to_sec(10000)::numeric(20,5); +SELECT gc_to_sec(100000)::numeric(20,5); +SELECT gc_to_sec(1000000)::numeric(20,5); + +-- +-- Set coordinates using latitude and longitude. +-- Extract each coordinate separately so we can round them. +-- + +SELECT cube_ll_coord(ll_to_earth(0,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,0),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(360,360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(360,360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(360,360),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(180,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,180),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(180,360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,360),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(-180,-360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-180,-360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-180,-360),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(0,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,180),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(0,-180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,-180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,-180),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(90,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,0),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(90,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,180),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(-90,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,0),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(-90,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,180),3)::numeric(20,5); + +-- +-- Test getting the latitude of a location. +-- + +SELECT latitude(ll_to_earth(0,0))::numeric(20,10); +SELECT latitude(ll_to_earth(45,0))::numeric(20,10); +SELECT latitude(ll_to_earth(90,0))::numeric(20,10); +SELECT latitude(ll_to_earth(-45,0))::numeric(20,10); +SELECT latitude(ll_to_earth(-90,0))::numeric(20,10); +SELECT latitude(ll_to_earth(0,90))::numeric(20,10); +SELECT latitude(ll_to_earth(45,90))::numeric(20,10); +SELECT latitude(ll_to_earth(90,90))::numeric(20,10); +SELECT latitude(ll_to_earth(-45,90))::numeric(20,10); +SELECT latitude(ll_to_earth(-90,90))::numeric(20,10); +SELECT latitude(ll_to_earth(0,180))::numeric(20,10); +SELECT latitude(ll_to_earth(45,180))::numeric(20,10); +SELECT latitude(ll_to_earth(90,180))::numeric(20,10); +SELECT latitude(ll_to_earth(-45,180))::numeric(20,10); +SELECT latitude(ll_to_earth(-90,180))::numeric(20,10); +SELECT latitude(ll_to_earth(0,-90))::numeric(20,10); +SELECT latitude(ll_to_earth(45,-90))::numeric(20,10); +SELECT latitude(ll_to_earth(90,-90))::numeric(20,10); +SELECT latitude(ll_to_earth(-45,-90))::numeric(20,10); +SELECT latitude(ll_to_earth(-90,-90))::numeric(20,10); + +-- +-- Test getting the longitude of a location. +-- + +SELECT longitude(ll_to_earth(0,0))::numeric(20,10); +SELECT longitude(ll_to_earth(45,0))::numeric(20,10); +SELECT longitude(ll_to_earth(90,0))::numeric(20,10); +SELECT longitude(ll_to_earth(-45,0))::numeric(20,10); +SELECT longitude(ll_to_earth(-90,0))::numeric(20,10); +SELECT longitude(ll_to_earth(0,90))::numeric(20,10); +SELECT longitude(ll_to_earth(45,90))::numeric(20,10); +SELECT longitude(ll_to_earth(90,90))::numeric(20,10); +SELECT longitude(ll_to_earth(-45,90))::numeric(20,10); +SELECT longitude(ll_to_earth(-90,90))::numeric(20,10); +SELECT longitude(ll_to_earth(0,180))::numeric(20,10); +SELECT longitude(ll_to_earth(45,180))::numeric(20,10); +SELECT longitude(ll_to_earth(90,180))::numeric(20,10); +SELECT longitude(ll_to_earth(-45,180))::numeric(20,10); +SELECT longitude(ll_to_earth(-90,180))::numeric(20,10); +SELECT longitude(ll_to_earth(0,-90))::numeric(20,10); +SELECT longitude(ll_to_earth(45,-90))::numeric(20,10); +SELECT longitude(ll_to_earth(90,-90))::numeric(20,10); +SELECT longitude(ll_to_earth(-45,-90))::numeric(20,10); +SELECT longitude(ll_to_earth(-90,-90))::numeric(20,10); + +-- +-- For the distance tests the following is some real life data. +-- +-- Chicago has a latitude of 41.8 and a longitude of 87.6. +-- Albuquerque has a latitude of 35.1 and a longitude of 106.7. +-- (Note that latitude and longitude are specified differently +-- in the cube based functions than for the point based functions.) +-- + +-- +-- Test getting the distance between two points using earth_distance. +-- + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,0))::numeric(20,5); +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,180))::numeric(20,5); +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(90,0))::numeric(20,5); +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,90))::numeric(20,5); +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))::numeric(20,5); +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(1,0))::numeric(20,5); +SELECT earth_distance(ll_to_earth(30,0),ll_to_earth(30,1))::numeric(20,5); +SELECT earth_distance(ll_to_earth(30,0),ll_to_earth(31,0))::numeric(20,5); +SELECT earth_distance(ll_to_earth(60,0),ll_to_earth(60,1))::numeric(20,5); +SELECT earth_distance(ll_to_earth(60,0),ll_to_earth(61,0))::numeric(20,5); +SELECT earth_distance(ll_to_earth(41.8,87.6),ll_to_earth(35.1,106.7))::numeric(20,5); +SELECT (earth_distance(ll_to_earth(41.8,87.6),ll_to_earth(35.1,106.7))* + 100./2.54/12./5280.)::numeric(20,5); + +-- +-- Test getting the distance between two points using geo_distance. +-- + +SELECT geo_distance('(0,0)'::point,'(0,0)'::point)::numeric(20,5); +SELECT geo_distance('(0,0)'::point,'(180,0)'::point)::numeric(20,5); +SELECT geo_distance('(0,0)'::point,'(0,90)'::point)::numeric(20,5); +SELECT geo_distance('(0,0)'::point,'(90,0)'::point)::numeric(20,5); +SELECT geo_distance('(0,0)'::point,'(1,0)'::point)::numeric(20,5); +SELECT geo_distance('(0,0)'::point,'(0,1)'::point)::numeric(20,5); +SELECT geo_distance('(0,30)'::point,'(1,30)'::point)::numeric(20,5); +SELECT geo_distance('(0,30)'::point,'(0,31)'::point)::numeric(20,5); +SELECT geo_distance('(0,60)'::point,'(1,60)'::point)::numeric(20,5); +SELECT geo_distance('(0,60)'::point,'(0,61)'::point)::numeric(20,5); +SELECT geo_distance('(87.6,41.8)'::point,'(106.7,35.1)'::point)::numeric(20,5); +SELECT (geo_distance('(87.6,41.8)'::point,'(106.7,35.1)'::point)*5280.*12.*2.54/100.)::numeric(20,5); + +-- +-- Test getting the distance between two points using the <@> operator. +-- + +SELECT ('(0,0)'::point <@> '(0,0)'::point)::numeric(20,5); +SELECT ('(0,0)'::point <@> '(180,0)'::point)::numeric(20,5); +SELECT ('(0,0)'::point <@> '(0,90)'::point)::numeric(20,5); +SELECT ('(0,0)'::point <@> '(90,0)'::point)::numeric(20,5); +SELECT ('(0,0)'::point <@> '(1,0)'::point)::numeric(20,5); +SELECT ('(0,0)'::point <@> '(0,1)'::point)::numeric(20,5); +SELECT ('(0,30)'::point <@> '(1,30)'::point)::numeric(20,5); +SELECT ('(0,30)'::point <@> '(0,31)'::point)::numeric(20,5); +SELECT ('(0,60)'::point <@> '(1,60)'::point)::numeric(20,5); +SELECT ('(0,60)'::point <@> '(0,61)'::point)::numeric(20,5); +SELECT ('(87.6,41.8)'::point <@> '(106.7,35.1)'::point)::numeric(20,5); +SELECT (('(87.6,41.8)'::point <@> '(106.7,35.1)'::point)*5280.*12.*2.54/100.)::numeric(20,5); + +-- +-- Test getting a bounding box around points. +-- + +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),112000),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),112000),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),112000),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),3)::numeric(20,5); +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),3)::numeric(20,5); +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),3)::numeric(20,5); + +-- +-- Test for points that should be in bounding boxes. +-- + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*1.00001) @> + ll_to_earth(0,1); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*1.00001) @> + ll_to_earth(0,0.1); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.01))*1.00001) @> + ll_to_earth(0,0.01); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.001))*1.00001) @> + ll_to_earth(0,0.001); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.0001))*1.00001) @> + ll_to_earth(0,0.0001); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0.0001,0.0001))*1.00001) @> + ll_to_earth(0.0001,0.0001); +SELECT earth_box(ll_to_earth(45,45), + earth_distance(ll_to_earth(45,45),ll_to_earth(45.0001,45.0001))*1.00001) @> + ll_to_earth(45.0001,45.0001); +SELECT earth_box(ll_to_earth(90,180), + earth_distance(ll_to_earth(90,180),ll_to_earth(90.0001,180.0001))*1.00001) @> + ll_to_earth(90.0001,180.0001); + +-- +-- Test for points that shouldn't be in bounding boxes. Note that we need +-- to make points way outside, since some points close may be in the box +-- but further away than the distance we are testing. +-- + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*.57735) @> + ll_to_earth(0,1); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*.57735) @> + ll_to_earth(0,0.1); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.01))*.57735) @> + ll_to_earth(0,0.01); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.001))*.57735) @> + ll_to_earth(0,0.001); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.0001))*.57735) @> + ll_to_earth(0,0.0001); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0.0001,0.0001))*.57735) @> + ll_to_earth(0.0001,0.0001); +SELECT earth_box(ll_to_earth(45,45), + earth_distance(ll_to_earth(45,45),ll_to_earth(45.0001,45.0001))*.57735) @> + ll_to_earth(45.0001,45.0001); +SELECT earth_box(ll_to_earth(90,180), + earth_distance(ll_to_earth(90,180),ll_to_earth(90.0001,180.0001))*.57735) @> + ll_to_earth(90.0001,180.0001); + +-- +-- Test the recommended constraints. +-- + +SELECT cube_is_point(ll_to_earth(0,0)); +SELECT cube_dim(ll_to_earth(0,0)) <= 3; +SELECT abs(cube_distance(ll_to_earth(0,0), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; +SELECT cube_is_point(ll_to_earth(30,60)); +SELECT cube_dim(ll_to_earth(30,60)) <= 3; +SELECT abs(cube_distance(ll_to_earth(30,60), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; +SELECT cube_is_point(ll_to_earth(60,90)); +SELECT cube_dim(ll_to_earth(60,90)) <= 3; +SELECT abs(cube_distance(ll_to_earth(60,90), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; +SELECT cube_is_point(ll_to_earth(-30,-90)); +SELECT cube_dim(ll_to_earth(-30,-90)) <= 3; +SELECT abs(cube_distance(ll_to_earth(-30,-90), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; + +-- +-- Now we are going to test extension create/drop scenarios. +-- + +-- list what's installed +\dT + +-- YB note: the errdetail of the following command varies in YB for some +-- reason, like "extension earthdistance depends on type cube" or "...depends +-- on extension cube" or "...depends on function cube_out(cube)". It also +-- can't be suppressed using client_min_messages since it's an ERROR message, +-- the highest level. Therefore, comment it out. +-- drop extension cube; -- fail, earthdistance requires it + +drop extension earthdistance; + +drop type cube; -- fail, extension cube requires it + +-- list what's installed +\dT + +create table foo (f1 cube, f2 int); + +drop extension cube; -- fail, foo.f1 requires it + +drop table foo; + +drop extension cube; + +-- list what's installed +\dT +\df +\do + +create schema c; + +create extension cube with schema c; + +-- list what's installed +\dT public.* +\df public.* +\do public.* +\dT c.* + +create table foo (f1 c.cube, f2 int); + +drop extension cube; -- fail, foo.f1 requires it + +drop schema c; -- fail, cube requires it + +drop extension cube cascade; + +\d foo + +-- list what's installed +\dT public.* +\df public.* +\do public.* +\dT c.* +\df c.* +\do c.* + +drop schema c; diff --git a/contrib/earthdistance/yb_schedule b/contrib/earthdistance/yb_schedule new file mode 100644 index 0000000000000..4a961ecd2280a --- /dev/null +++ b/contrib/earthdistance/yb_schedule @@ -0,0 +1,3 @@ +# contrib/earthdistance/yb_schedule + +test: yb.port.earthdistance diff --git a/contrib/file_fdw/Makefile b/contrib/file_fdw/Makefile index 885459d3c16e9..5f5296e0701b7 100644 --- a/contrib/file_fdw/Makefile +++ b/contrib/file_fdw/Makefile @@ -1,6 +1,8 @@ # contrib/file_fdw/Makefile -MODULES = file_fdw +MODULE_big = file_fdw + +OBJS = file_fdw.o $(WIN32RES) EXTENSION = file_fdw DATA = file_fdw--1.0.sql @@ -8,6 +10,8 @@ PGFILEDESC = "file_fdw - foreign data wrapper for files" REGRESS = file_fdw +SHLIB_LINK = -L$(YB_BUILD_ROOT)/lib -lyb_pggate + ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) diff --git a/contrib/file_fdw/expected/yb.port.file_fdw.out b/contrib/file_fdw/expected/yb.port.file_fdw.out new file mode 100644 index 0000000000000..a5fc7785f1357 --- /dev/null +++ b/contrib/file_fdw/expected/yb.port.file_fdw.out @@ -0,0 +1,496 @@ +-- +-- Test foreign-data wrapper file_fdw. +-- +SET compute_query_id to regress; -- YB: hide query id for EXPLAIN VERBOSE +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +-- Clean up in case a prior regression run failed +SET client_min_messages TO 'warning'; +DROP ROLE IF EXISTS regress_file_fdw_superuser, regress_file_fdw_user, regress_no_priv_user; +RESET client_min_messages; +CREATE ROLE regress_file_fdw_superuser LOGIN SUPERUSER; -- is a superuser +CREATE ROLE regress_file_fdw_user LOGIN; -- has priv and user mapping +CREATE ROLE regress_no_priv_user LOGIN; -- has priv but no user mapping +-- Install file_fdw +CREATE EXTENSION file_fdw; +-- create function to filter unstable results of EXPLAIN +CREATE FUNCTION explain_filter(text) RETURNS setof text +LANGUAGE plpgsql AS +$$ +declare + ln text; +begin + for ln in execute $1 + loop + -- Remove the path portion of foreign file names + ln := regexp_replace(ln, 'Foreign File: .*/([a-z.]+)$', 'Foreign File: .../\1'); + return next ln; + end loop; +end; +$$; +-- regress_file_fdw_superuser owns fdw-related objects +SET ROLE regress_file_fdw_superuser; +CREATE SERVER file_server FOREIGN DATA WRAPPER file_fdw; +-- privilege tests +SET ROLE regress_file_fdw_user; +CREATE FOREIGN DATA WRAPPER file_fdw2 HANDLER file_fdw_handler VALIDATOR file_fdw_validator; -- ERROR +ERROR: permission denied to create foreign-data wrapper "file_fdw2" +HINT: Must be superuser or a member of the yb_fdw role to create a foreign-data wrapper. +CREATE SERVER file_server2 FOREIGN DATA WRAPPER file_fdw; -- ERROR +ERROR: permission denied for foreign-data wrapper file_fdw +CREATE USER MAPPING FOR regress_file_fdw_user SERVER file_server; -- ERROR +ERROR: permission denied for foreign server file_server +SET ROLE regress_file_fdw_superuser; +GRANT USAGE ON FOREIGN SERVER file_server TO regress_file_fdw_user; +SET ROLE regress_file_fdw_user; +CREATE USER MAPPING FOR regress_file_fdw_user SERVER file_server; +-- create user mappings and grant privilege to test users +SET ROLE regress_file_fdw_superuser; +CREATE USER MAPPING FOR regress_file_fdw_superuser SERVER file_server; +CREATE USER MAPPING FOR regress_no_priv_user SERVER file_server; +-- validator tests +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'xml'); -- ERROR +ERROR: COPY format "xml" not recognized +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', quote ':'); -- ERROR +ERROR: COPY quote available only in CSV mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape ':'); -- ERROR +ERROR: COPY escape available only in CSV mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', header 'true'); -- ERROR +ERROR: cannot specify HEADER in BINARY mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', quote ':'); -- ERROR +ERROR: COPY quote available only in CSV mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', escape ':'); -- ERROR +ERROR: COPY escape available only in CSV mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter 'a'); -- ERROR +ERROR: COPY delimiter cannot be "a" +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape '-'); -- ERROR +ERROR: COPY escape available only in CSV mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '-', null '=-='); -- ERROR +ERROR: CSV quote character must not appear in the NULL specification +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '-', null '=-='); -- ERROR +ERROR: COPY delimiter must not appear in the NULL specification +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '-', quote '-'); -- ERROR +ERROR: COPY delimiter and quote must be different +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '---'); -- ERROR +ERROR: COPY delimiter must be a single one-byte character +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '---'); -- ERROR +ERROR: COPY quote must be a single one-byte character +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', escape '---'); -- ERROR +ERROR: COPY escape must be a single one-byte character +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\'); -- ERROR +ERROR: COPY delimiter cannot be "\" +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '.'); -- ERROR +ERROR: COPY delimiter cannot be "." +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '1'); -- ERROR +ERROR: COPY delimiter cannot be "1" +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter 'a'); -- ERROR +ERROR: COPY delimiter cannot be "a" +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter ' +'); -- ERROR +ERROR: COPY delimiter cannot be newline or carriage return +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', null ' +'); -- ERROR +ERROR: COPY null representation cannot use newline or carriage return +CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR +ERROR: either filename or program is required for file_fdw foreign tables +\set filename :abs_srcdir '/data/agg.data' +CREATE FOREIGN TABLE agg_text ( + a int2 CHECK (a >= 0), + b float4 +) SERVER file_server +OPTIONS (format 'text', filename :'filename', delimiter ' ', null '\N'); +GRANT SELECT ON agg_text TO regress_file_fdw_user; +\set filename :abs_srcdir '/data/agg.csv' +CREATE FOREIGN TABLE agg_csv ( + a int2, + b float4 +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', header 'true', delimiter ';', quote '@', escape '"', null ''); +ALTER FOREIGN TABLE agg_csv ADD CHECK (a >= 0); +\set filename :abs_srcdir '/data/agg.bad' +CREATE FOREIGN TABLE agg_bad ( + a int2, + b float4 +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', header 'true', delimiter ';', quote '@', escape '"', null ''); +ALTER FOREIGN TABLE agg_bad ADD CHECK (a >= 0); +-- test header matching +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE header_match ("1" int, foo text) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ',', header 'match'); +SELECT * FROM header_match; + 1 | foo +---+----- + 1 | bar +(1 row) + +CREATE FOREIGN TABLE header_doesnt_match (a int, foo text) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ',', header 'match'); +SELECT * FROM header_doesnt_match; -- ERROR +ERROR: column name mismatch in header line field 1: got "1", expected "a" +CONTEXT: COPY header_doesnt_match, line 1: "1,foo" +-- per-column options tests +\set filename :abs_srcdir '/data/text.csv' +CREATE FOREIGN TABLE text_csv ( + word1 text OPTIONS (force_not_null 'true'), + word2 text OPTIONS (force_not_null 'off'), + word3 text OPTIONS (force_null 'true'), + word4 text OPTIONS (force_null 'off') +) SERVER file_server +OPTIONS (format 'text', filename :'filename', null 'NULL'); +SELECT * FROM text_csv; -- ERROR +ERROR: COPY force not null available only in CSV mode +ALTER FOREIGN TABLE text_csv OPTIONS (SET format 'csv'); +DROP FOREIGN TABLE text_csv; +CREATE FOREIGN TABLE text_csv ( + word1 text OPTIONS (force_not_null 'true'), + word2 text OPTIONS (force_not_null 'off'), + word3 text OPTIONS (force_null 'true'), + word4 text OPTIONS (force_null 'off') +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', null 'NULL'); +\pset null _null_ +SELECT * FROM text_csv; + word1 | word2 | word3 | word4 +-------+--------+--------+-------- + AAA | aaa | 123 | + XYZ | xyz | | 321 + NULL | _null_ | _null_ | _null_ + NULL | _null_ | _null_ | _null_ + ABC | abc | | +(5 rows) + +-- force_not_null and force_null can be used together on the same column +ALTER FOREIGN TABLE text_csv ALTER COLUMN word1 OPTIONS (force_null 'true'); +ALTER FOREIGN TABLE text_csv ALTER COLUMN word3 OPTIONS (force_not_null 'true'); +-- force_not_null is not allowed to be specified at any foreign object level: +ALTER FOREIGN DATA WRAPPER file_fdw OPTIONS (ADD force_not_null '*'); -- ERROR +ERROR: invalid option "force_not_null" +HINT: There are no valid options in this context. +ALTER SERVER file_server OPTIONS (ADD force_not_null '*'); -- ERROR +ERROR: invalid option "force_not_null" +HINT: There are no valid options in this context. +CREATE USER MAPPING FOR public SERVER file_server OPTIONS (force_not_null '*'); -- ERROR +ERROR: invalid option "force_not_null" +HINT: There are no valid options in this context. +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_not_null '*'); -- ERROR +ERROR: invalid option "force_not_null" +HINT: Valid options in this context are: filename, program, format, header, delimiter, quote, escape, null, encoding +-- force_null is not allowed to be specified at any foreign object level: +ALTER FOREIGN DATA WRAPPER file_fdw OPTIONS (ADD force_null '*'); -- ERROR +ERROR: invalid option "force_null" +HINT: There are no valid options in this context. +ALTER SERVER file_server OPTIONS (ADD force_null '*'); -- ERROR +ERROR: invalid option "force_null" +HINT: There are no valid options in this context. +CREATE USER MAPPING FOR public SERVER file_server OPTIONS (force_null '*'); -- ERROR +ERROR: invalid option "force_null" +HINT: There are no valid options in this context. +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_null '*'); -- ERROR +ERROR: invalid option "force_null" +HINT: Valid options in this context are: filename, program, format, header, delimiter, quote, escape, null, encoding +-- basic query tests +SELECT * FROM agg_text WHERE b > 10.0 ORDER BY a; + a | b +-----+-------- + 42 | 324.78 + 100 | 99.097 +(2 rows) + +SELECT * FROM agg_csv ORDER BY a; + a | b +-----+--------- + 0 | 0.09561 + 42 | 324.78 + 100 | 99.097 +(3 rows) + +SELECT * FROM agg_csv c JOIN agg_text t ON (t.a = c.a) ORDER BY c.a; + a | b | a | b +-----+---------+-----+--------- + 0 | 0.09561 | 0 | 0.09561 + 42 | 324.78 | 42 | 324.78 + 100 | 99.097 | 100 | 99.097 +(3 rows) + +-- error context report tests +SELECT * FROM agg_bad; -- ERROR +ERROR: invalid input syntax for type real: "aaa" +CONTEXT: COPY agg_bad, line 3, column b: "aaa" +-- misc query tests +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv'); + Foreign Scan on public.agg_csv + Output: a, b + Foreign File: .../agg.csv + +\t off +PREPARE st(int) AS SELECT * FROM agg_csv WHERE a = $1; +EXECUTE st(100); + a | b +-----+-------- + 100 | 99.097 +(1 row) + +EXECUTE st(100); + a | b +-----+-------- + 100 | 99.097 +(1 row) + +DEALLOCATE st; +-- tableoid +SELECT tableoid::regclass, b FROM agg_csv; + tableoid | b +----------+--------- + agg_csv | 99.097 + agg_csv | 0.09561 + agg_csv | 324.78 +(3 rows) + +-- updates aren't supported +INSERT INTO agg_csv VALUES(1,2.0); +ERROR: cannot insert into foreign table "agg_csv" +UPDATE agg_csv SET a = 1; +ERROR: cannot update foreign table "agg_csv" +DELETE FROM agg_csv WHERE a = 100; +ERROR: cannot delete from foreign table "agg_csv" +-- but this should be allowed +SELECT * FROM agg_csv FOR UPDATE; + a | b +-----+--------- + 100 | 99.097 + 0 | 0.09561 + 42 | 324.78 +(3 rows) + +-- copy from isn't supported either +COPY agg_csv FROM STDIN; +ERROR: cannot insert into foreign table "agg_csv" +-- constraint exclusion tests +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0'); + Foreign Scan on public.agg_csv + Output: a, b + Filter: (agg_csv.a < 0) + Foreign File: .../agg.csv + +\t off +SELECT * FROM agg_csv WHERE a < 0; + a | b +---+--- +(0 rows) + +SET constraint_exclusion = 'on'; +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0'); + Result agg_csv + Output: a, b + One-Time Filter: false + +\t off +SELECT * FROM agg_csv WHERE a < 0; + a | b +---+--- +(0 rows) + +RESET constraint_exclusion; +-- table inheritance tests +CREATE TABLE agg (a int2, b float4); +-- YB note: Enable when ALTER TABLE INHERIT from #1124 is supported +ALTER FOREIGN TABLE agg_csv INHERIT agg; +SELECT tableoid::regclass, * FROM agg; + tableoid | a | b +----------+-----+--------- + agg_csv | 100 | 99.097 + agg_csv | 0 | 0.09561 + agg_csv | 42 | 324.78 +(3 rows) + +SELECT tableoid::regclass, * FROM agg_csv; + tableoid | a | b +----------+-----+--------- + agg_csv | 100 | 99.097 + agg_csv | 0 | 0.09561 + agg_csv | 42 | 324.78 +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY agg; + tableoid | a | b +----------+---+--- +(0 rows) + +-- updates aren't supported +UPDATE agg SET a = 1; +ERROR: cannot update foreign table "agg_csv" +DELETE FROM agg WHERE a = 100; +ERROR: cannot delete from foreign table "agg_csv" +-- but this should be allowed +--SELECT tableoid::regclass, * FROM agg FOR UPDATE; -- YB: triggers a crash #27105 +ALTER FOREIGN TABLE agg_csv NO INHERIT agg; +DROP TABLE agg; +-- declarative partitioning tests +SET ROLE regress_file_fdw_superuser; +CREATE TABLE pt (a int, b text) partition by list (a); +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE p1 partition of pt for values in (1) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ','); +CREATE TABLE p2 partition of pt for values in (2); +-- YB note: ORDER BYs added for consistent results +SELECT tableoid::regclass, * FROM pt ORDER BY 1, 2, 3; + tableoid | a | b +----------+---+----- + p1 | 1 | bar + p1 | 1 | foo +(2 rows) + +SELECT tableoid::regclass, * FROM p1 ORDER BY 1, 2, 3; + tableoid | a | b +----------+---+----- + p1 | 1 | bar + p1 | 1 | foo +(2 rows) + +SELECT tableoid::regclass, * FROM p2 ORDER BY 1, 2, 3; + tableoid | a | b +----------+---+--- +(0 rows) + +\set filename :abs_srcdir '/data/list2.bad' +COPY pt FROM :'filename' with (format 'csv', delimiter ','); -- ERROR +ERROR: cannot insert into foreign table "p1" +CONTEXT: COPY pt, line 2: "1,qux" +\set filename :abs_srcdir '/data/list2.csv' +COPY pt FROM :'filename' with (format 'csv', delimiter ','); +SELECT tableoid::regclass, * FROM pt ORDER BY 1, 2, 3; + tableoid | a | b +----------+---+----- + p1 | 1 | bar + p1 | 1 | foo + p2 | 2 | baz + p2 | 2 | qux +(4 rows) + +SELECT tableoid::regclass, * FROM p1 ORDER BY 1, 2, 3; + tableoid | a | b +----------+---+----- + p1 | 1 | bar + p1 | 1 | foo +(2 rows) + +SELECT tableoid::regclass, * FROM p2 ORDER BY 1, 2, 3; + tableoid | a | b +----------+---+----- + p2 | 2 | baz + p2 | 2 | qux +(2 rows) + +INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR +ERROR: cannot insert into foreign table "p1" +INSERT INTO pt VALUES (2, 'xyzzy'); +UPDATE pt set a = 1 where a = 2; -- ERROR +ERROR: cannot insert into foreign table "p1" +SELECT tableoid::regclass, * FROM pt ORDER BY 1, 2, 3; + tableoid | a | b +----------+---+------- + p1 | 1 | bar + p1 | 1 | foo + p2 | 2 | baz + p2 | 2 | qux + p2 | 2 | xyzzy +(5 rows) + +SELECT tableoid::regclass, * FROM p1 ORDER BY 1, 2, 3; + tableoid | a | b +----------+---+----- + p1 | 1 | bar + p1 | 1 | foo +(2 rows) + +SELECT tableoid::regclass, * FROM p2 ORDER BY 1, 2, 3; + tableoid | a | b +----------+---+------- + p2 | 2 | baz + p2 | 2 | qux + p2 | 2 | xyzzy +(3 rows) + +DROP TABLE pt; +-- generated column tests +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE gft1 (a int, b text, c text GENERATED ALWAYS AS ('foo') STORED) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ','); +SELECT a, c FROM gft1; + a | c +---+-------- + 1 | _null_ + 1 | _null_ +(2 rows) + +DROP FOREIGN TABLE gft1; +-- privilege tests +SET ROLE regress_file_fdw_superuser; +SELECT * FROM agg_text ORDER BY a; + a | b +-----+--------- + 0 | 0.09561 + 42 | 324.78 + 56 | 7.8 + 100 | 99.097 +(4 rows) + +SET ROLE regress_file_fdw_user; +SELECT * FROM agg_text ORDER BY a; + a | b +-----+--------- + 0 | 0.09561 + 42 | 324.78 + 56 | 7.8 + 100 | 99.097 +(4 rows) + +SET ROLE regress_no_priv_user; +SELECT * FROM agg_text ORDER BY a; -- ERROR +ERROR: permission denied for foreign table agg_text +SET ROLE regress_file_fdw_user; +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_text WHERE a > 0'); + Foreign Scan on public.agg_text + Output: a, b + Filter: (agg_text.a > 0) + Foreign File: .../agg.data + +\t off +-- file FDW allows foreign tables to be accessed without user mapping +DROP USER MAPPING FOR regress_file_fdw_user SERVER file_server; +SELECT * FROM agg_text ORDER BY a; + a | b +-----+--------- + 0 | 0.09561 + 42 | 324.78 + 56 | 7.8 + 100 | 99.097 +(4 rows) + +-- privilege tests for object +SET ROLE regress_file_fdw_superuser; +ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user; +ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text'); +SET ROLE regress_file_fdw_user; +ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text'); +ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table +SET ROLE regress_file_fdw_superuser; +-- cleanup +RESET ROLE; +DROP EXTENSION file_fdw CASCADE; +NOTICE: drop cascades to 9 other objects +DETAIL: drop cascades to foreign table agg_bad +drop cascades to foreign table agg_csv +drop cascades to foreign table agg_text +drop cascades to foreign table header_doesnt_match +drop cascades to foreign table header_match +drop cascades to foreign table text_csv +drop cascades to server file_server +drop cascades to user mapping for regress_file_fdw_superuser on server file_server +drop cascades to user mapping for regress_no_priv_user on server file_server +DROP ROLE regress_file_fdw_superuser, regress_file_fdw_user, regress_no_priv_user; diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c index 4773cadec09d3..614e55bec29e4 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -38,6 +38,10 @@ #include "utils/rel.h" #include "utils/sampling.h" +/* YB includes */ +#include "pg_yb_utils.h" +#include "yb/yql/pggate/ybc_pggate.h" + PG_MODULE_MAGIC; /* @@ -244,6 +248,8 @@ file_fdw_validator(PG_FUNCTION_ARGS) if (strcmp(def->defname, "filename") == 0 || strcmp(def->defname, "program") == 0) { + YBCheckServerAccessIsAllowed(); + if (filename) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -727,7 +733,8 @@ fileIterateForeignScan(ForeignScanState *node) */ ExecClearTuple(slot); found = NextCopyFrom(festate->cstate, NULL, - slot->tts_values, slot->tts_isnull); + slot->tts_values, slot->tts_isnull, + false /* skip_row */ ); if (found) ExecStoreVirtualTuple(slot); @@ -1155,7 +1162,7 @@ file_acquire_sample_rows(Relation onerel, int elevel, MemoryContextReset(tupcontext); MemoryContextSwitchTo(tupcontext); - found = NextCopyFrom(cstate, NULL, values, nulls); + found = NextCopyFrom(cstate, NULL, values, nulls, false /* skip_row */ ); MemoryContextSwitchTo(oldcontext); diff --git a/contrib/file_fdw/sql/yb.port.file_fdw.sql b/contrib/file_fdw/sql/yb.port.file_fdw.sql new file mode 100644 index 0000000000000..4251bb4e862ee --- /dev/null +++ b/contrib/file_fdw/sql/yb.port.file_fdw.sql @@ -0,0 +1,272 @@ +-- +-- Test foreign-data wrapper file_fdw. +-- + +SET compute_query_id to regress; -- YB: hide query id for EXPLAIN VERBOSE +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +-- Clean up in case a prior regression run failed +SET client_min_messages TO 'warning'; +DROP ROLE IF EXISTS regress_file_fdw_superuser, regress_file_fdw_user, regress_no_priv_user; +RESET client_min_messages; + +CREATE ROLE regress_file_fdw_superuser LOGIN SUPERUSER; -- is a superuser +CREATE ROLE regress_file_fdw_user LOGIN; -- has priv and user mapping +CREATE ROLE regress_no_priv_user LOGIN; -- has priv but no user mapping + +-- Install file_fdw +CREATE EXTENSION file_fdw; + +-- create function to filter unstable results of EXPLAIN +CREATE FUNCTION explain_filter(text) RETURNS setof text +LANGUAGE plpgsql AS +$$ +declare + ln text; +begin + for ln in execute $1 + loop + -- Remove the path portion of foreign file names + ln := regexp_replace(ln, 'Foreign File: .*/([a-z.]+)$', 'Foreign File: .../\1'); + return next ln; + end loop; +end; +$$; + +-- regress_file_fdw_superuser owns fdw-related objects +SET ROLE regress_file_fdw_superuser; +CREATE SERVER file_server FOREIGN DATA WRAPPER file_fdw; + +-- privilege tests +SET ROLE regress_file_fdw_user; +CREATE FOREIGN DATA WRAPPER file_fdw2 HANDLER file_fdw_handler VALIDATOR file_fdw_validator; -- ERROR +CREATE SERVER file_server2 FOREIGN DATA WRAPPER file_fdw; -- ERROR +CREATE USER MAPPING FOR regress_file_fdw_user SERVER file_server; -- ERROR + +SET ROLE regress_file_fdw_superuser; +GRANT USAGE ON FOREIGN SERVER file_server TO regress_file_fdw_user; + +SET ROLE regress_file_fdw_user; +CREATE USER MAPPING FOR regress_file_fdw_user SERVER file_server; + +-- create user mappings and grant privilege to test users +SET ROLE regress_file_fdw_superuser; +CREATE USER MAPPING FOR regress_file_fdw_superuser SERVER file_server; +CREATE USER MAPPING FOR regress_no_priv_user SERVER file_server; + +-- validator tests +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'xml'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', quote ':'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape ':'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', header 'true'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', quote ':'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', escape ':'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter 'a'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape '-'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '-', null '=-='); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '-', null '=-='); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '-', quote '-'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '---'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '---'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', escape '---'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '.'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '1'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter 'a'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter ' +'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', null ' +'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR + +\set filename :abs_srcdir '/data/agg.data' +CREATE FOREIGN TABLE agg_text ( + a int2 CHECK (a >= 0), + b float4 +) SERVER file_server +OPTIONS (format 'text', filename :'filename', delimiter ' ', null '\N'); +GRANT SELECT ON agg_text TO regress_file_fdw_user; + +\set filename :abs_srcdir '/data/agg.csv' +CREATE FOREIGN TABLE agg_csv ( + a int2, + b float4 +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', header 'true', delimiter ';', quote '@', escape '"', null ''); +ALTER FOREIGN TABLE agg_csv ADD CHECK (a >= 0); + +\set filename :abs_srcdir '/data/agg.bad' +CREATE FOREIGN TABLE agg_bad ( + a int2, + b float4 +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', header 'true', delimiter ';', quote '@', escape '"', null ''); +ALTER FOREIGN TABLE agg_bad ADD CHECK (a >= 0); + +-- test header matching +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE header_match ("1" int, foo text) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ',', header 'match'); +SELECT * FROM header_match; +CREATE FOREIGN TABLE header_doesnt_match (a int, foo text) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ',', header 'match'); +SELECT * FROM header_doesnt_match; -- ERROR + +-- per-column options tests +\set filename :abs_srcdir '/data/text.csv' +CREATE FOREIGN TABLE text_csv ( + word1 text OPTIONS (force_not_null 'true'), + word2 text OPTIONS (force_not_null 'off'), + word3 text OPTIONS (force_null 'true'), + word4 text OPTIONS (force_null 'off') +) SERVER file_server +OPTIONS (format 'text', filename :'filename', null 'NULL'); +SELECT * FROM text_csv; -- ERROR +ALTER FOREIGN TABLE text_csv OPTIONS (SET format 'csv'); +DROP FOREIGN TABLE text_csv; +CREATE FOREIGN TABLE text_csv ( + word1 text OPTIONS (force_not_null 'true'), + word2 text OPTIONS (force_not_null 'off'), + word3 text OPTIONS (force_null 'true'), + word4 text OPTIONS (force_null 'off') +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', null 'NULL'); +\pset null _null_ +SELECT * FROM text_csv; + +-- force_not_null and force_null can be used together on the same column +ALTER FOREIGN TABLE text_csv ALTER COLUMN word1 OPTIONS (force_null 'true'); +ALTER FOREIGN TABLE text_csv ALTER COLUMN word3 OPTIONS (force_not_null 'true'); + +-- force_not_null is not allowed to be specified at any foreign object level: +ALTER FOREIGN DATA WRAPPER file_fdw OPTIONS (ADD force_not_null '*'); -- ERROR +ALTER SERVER file_server OPTIONS (ADD force_not_null '*'); -- ERROR +CREATE USER MAPPING FOR public SERVER file_server OPTIONS (force_not_null '*'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_not_null '*'); -- ERROR + +-- force_null is not allowed to be specified at any foreign object level: +ALTER FOREIGN DATA WRAPPER file_fdw OPTIONS (ADD force_null '*'); -- ERROR +ALTER SERVER file_server OPTIONS (ADD force_null '*'); -- ERROR +CREATE USER MAPPING FOR public SERVER file_server OPTIONS (force_null '*'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_null '*'); -- ERROR + +-- basic query tests +SELECT * FROM agg_text WHERE b > 10.0 ORDER BY a; +SELECT * FROM agg_csv ORDER BY a; +SELECT * FROM agg_csv c JOIN agg_text t ON (t.a = c.a) ORDER BY c.a; + +-- error context report tests +SELECT * FROM agg_bad; -- ERROR + +-- misc query tests +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv'); +\t off +PREPARE st(int) AS SELECT * FROM agg_csv WHERE a = $1; +EXECUTE st(100); +EXECUTE st(100); +DEALLOCATE st; + +-- tableoid +SELECT tableoid::regclass, b FROM agg_csv; + +-- updates aren't supported +INSERT INTO agg_csv VALUES(1,2.0); +UPDATE agg_csv SET a = 1; +DELETE FROM agg_csv WHERE a = 100; +-- but this should be allowed +SELECT * FROM agg_csv FOR UPDATE; + +-- copy from isn't supported either +COPY agg_csv FROM STDIN; +12 3.4 +\. + +-- constraint exclusion tests +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0'); +\t off +SELECT * FROM agg_csv WHERE a < 0; +SET constraint_exclusion = 'on'; +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0'); +\t off +SELECT * FROM agg_csv WHERE a < 0; +RESET constraint_exclusion; + +-- table inheritance tests +CREATE TABLE agg (a int2, b float4); +-- YB note: Enable when ALTER TABLE INHERIT from #1124 is supported +ALTER FOREIGN TABLE agg_csv INHERIT agg; +SELECT tableoid::regclass, * FROM agg; +SELECT tableoid::regclass, * FROM agg_csv; +SELECT tableoid::regclass, * FROM ONLY agg; +-- updates aren't supported +UPDATE agg SET a = 1; +DELETE FROM agg WHERE a = 100; +-- but this should be allowed +--SELECT tableoid::regclass, * FROM agg FOR UPDATE; -- YB: triggers a crash #27105 +ALTER FOREIGN TABLE agg_csv NO INHERIT agg; +DROP TABLE agg; + +-- declarative partitioning tests +SET ROLE regress_file_fdw_superuser; +CREATE TABLE pt (a int, b text) partition by list (a); +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE p1 partition of pt for values in (1) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ','); +CREATE TABLE p2 partition of pt for values in (2); +-- YB note: ORDER BYs added for consistent results +SELECT tableoid::regclass, * FROM pt ORDER BY 1, 2, 3; +SELECT tableoid::regclass, * FROM p1 ORDER BY 1, 2, 3; +SELECT tableoid::regclass, * FROM p2 ORDER BY 1, 2, 3; +\set filename :abs_srcdir '/data/list2.bad' +COPY pt FROM :'filename' with (format 'csv', delimiter ','); -- ERROR +\set filename :abs_srcdir '/data/list2.csv' +COPY pt FROM :'filename' with (format 'csv', delimiter ','); +SELECT tableoid::regclass, * FROM pt ORDER BY 1, 2, 3; +SELECT tableoid::regclass, * FROM p1 ORDER BY 1, 2, 3; +SELECT tableoid::regclass, * FROM p2 ORDER BY 1, 2, 3; +INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR +INSERT INTO pt VALUES (2, 'xyzzy'); +UPDATE pt set a = 1 where a = 2; -- ERROR +SELECT tableoid::regclass, * FROM pt ORDER BY 1, 2, 3; +SELECT tableoid::regclass, * FROM p1 ORDER BY 1, 2, 3; +SELECT tableoid::regclass, * FROM p2 ORDER BY 1, 2, 3; +DROP TABLE pt; + +-- generated column tests +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE gft1 (a int, b text, c text GENERATED ALWAYS AS ('foo') STORED) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ','); +SELECT a, c FROM gft1; +DROP FOREIGN TABLE gft1; + +-- privilege tests +SET ROLE regress_file_fdw_superuser; +SELECT * FROM agg_text ORDER BY a; +SET ROLE regress_file_fdw_user; +SELECT * FROM agg_text ORDER BY a; +SET ROLE regress_no_priv_user; +SELECT * FROM agg_text ORDER BY a; -- ERROR +SET ROLE regress_file_fdw_user; +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_text WHERE a > 0'); +\t off +-- file FDW allows foreign tables to be accessed without user mapping +DROP USER MAPPING FOR regress_file_fdw_user SERVER file_server; +SELECT * FROM agg_text ORDER BY a; + +-- privilege tests for object +SET ROLE regress_file_fdw_superuser; +ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user; +ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text'); +SET ROLE regress_file_fdw_user; +ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text'); +SET ROLE regress_file_fdw_superuser; + +-- cleanup +RESET ROLE; +DROP EXTENSION file_fdw CASCADE; +DROP ROLE regress_file_fdw_superuser, regress_file_fdw_user, regress_no_priv_user; diff --git a/contrib/file_fdw/yb_schedule b/contrib/file_fdw/yb_schedule new file mode 100644 index 0000000000000..e87095a198252 --- /dev/null +++ b/contrib/file_fdw/yb_schedule @@ -0,0 +1,3 @@ +# contrib/file_fdw/yb_schedule + +test: yb.port.file_fdw diff --git a/contrib/fuzzystrmatch/dmetaphone.c b/contrib/fuzzystrmatch/dmetaphone.c index 6f4d2b730e92c..15de859a789b6 100644 --- a/contrib/fuzzystrmatch/dmetaphone.c +++ b/contrib/fuzzystrmatch/dmetaphone.c @@ -101,7 +101,10 @@ The remaining code is authored by Andrew Dunstan and #include "utils/builtins.h" /* turn off assertions for embedded function */ +#if 0 /* Commented out by Yugabyte: NDEBUG should + * not be redefined! */ #define NDEBUG +#endif /* YB */ #else /* DMETAPHONE_MAIN */ diff --git a/contrib/fuzzystrmatch/fuzzystrmatch.c b/contrib/fuzzystrmatch/fuzzystrmatch.c index 18177d14db23c..4d36ba2551e1b 100644 --- a/contrib/fuzzystrmatch/fuzzystrmatch.c +++ b/contrib/fuzzystrmatch/fuzzystrmatch.c @@ -44,6 +44,9 @@ #include "utils/builtins.h" #include "utils/varlena.h" +/* YB includes */ +#include "common/pg_yb_common.h" + PG_MODULE_MAGIC; /* @@ -147,6 +150,9 @@ PG_FUNCTION_INFO_V1(levenshtein_with_costs); Datum levenshtein_with_costs(PG_FUNCTION_ARGS) { + if (YBIsEnabledInPostgresEnvVar()) + return yb_levenshtein_with_costs(fcinfo); + text *src = PG_GETARG_TEXT_PP(0); text *dst = PG_GETARG_TEXT_PP(1); int ins_c = PG_GETARG_INT32(2); @@ -173,6 +179,9 @@ PG_FUNCTION_INFO_V1(levenshtein); Datum levenshtein(PG_FUNCTION_ARGS) { + if (YBIsEnabledInPostgresEnvVar()) + return yb_levenshtein(fcinfo); + text *src = PG_GETARG_TEXT_PP(0); text *dst = PG_GETARG_TEXT_PP(1); const char *s_data; @@ -196,6 +205,9 @@ PG_FUNCTION_INFO_V1(levenshtein_less_equal_with_costs); Datum levenshtein_less_equal_with_costs(PG_FUNCTION_ARGS) { + if (YBIsEnabledInPostgresEnvVar()) + return yb_levenshtein_less_equal_with_costs(fcinfo); + text *src = PG_GETARG_TEXT_PP(0); text *dst = PG_GETARG_TEXT_PP(1); int ins_c = PG_GETARG_INT32(2); @@ -225,6 +237,9 @@ PG_FUNCTION_INFO_V1(levenshtein_less_equal); Datum levenshtein_less_equal(PG_FUNCTION_ARGS) { + if (YBIsEnabledInPostgresEnvVar()) + return yb_levenshtein_less_equal(fcinfo); + text *src = PG_GETARG_TEXT_PP(0); text *dst = PG_GETARG_TEXT_PP(1); int max_d = PG_GETARG_INT32(2); diff --git a/contrib/fuzzystrmatch/yb_schedule b/contrib/fuzzystrmatch/yb_schedule new file mode 100644 index 0000000000000..022b8e807ae99 --- /dev/null +++ b/contrib/fuzzystrmatch/yb_schedule @@ -0,0 +1,5 @@ +# contrib/fuzzystrmatch/yb_schedule + +# Since the upstream postgres test works fine without modifications +# (currently), don't bother porting it to yb_pg_fuzzystrmatch. +test: fuzzystrmatch diff --git a/contrib/hstore/Makefile b/contrib/hstore/Makefile index 48ee98f0d5c6f..8a4add0141c05 100644 --- a/contrib/hstore/Makefile +++ b/contrib/hstore/Makefile @@ -11,10 +11,9 @@ OBJS = \ hstore_subs.o EXTENSION = hstore -DATA = hstore--1.4.sql \ - hstore--1.7--1.8.sql \ - hstore--1.6--1.7.sql \ - hstore--1.5--1.6.sql \ +DATA = hstore--1.5-yb-1.0--1.8-yb-1.0.sql \ + hstore--1.5--1.5-yb-1.0.sql \ + hstore--1.4.sql \ hstore--1.4--1.5.sql \ hstore--1.3--1.4.sql hstore--1.2--1.3.sql \ hstore--1.1--1.2.sql diff --git a/contrib/hstore/expected/yb.port.hstore.out b/contrib/hstore/expected/yb.port.hstore.out new file mode 100644 index 0000000000000..93d823bc3ec70 --- /dev/null +++ b/contrib/hstore/expected/yb.port.hstore.out @@ -0,0 +1,1518 @@ +CREATE EXTENSION hstore; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); +ERROR: cannot validate operator family without ordered data +set escape_string_warning=off; +--hstore; +select ''::hstore; + hstore +-------- + +(1 row) + +select 'a=>b'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select ' a=>b'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select 'a =>b'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select 'a=>b '::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select 'a=> b'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select '"a"=>"b"'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select ' "a"=>"b"'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select '"a" =>"b"'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select '"a"=>"b" '::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select '"a"=> "b"'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select 'aa=>bb'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select ' aa=>bb'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select 'aa =>bb'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select 'aa=>bb '::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select 'aa=> bb'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select '"aa"=>"bb"'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select ' "aa"=>"bb"'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select '"aa" =>"bb"'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select '"aa"=>"bb" '::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select '"aa"=> "bb"'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select 'aa=>bb, cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>bb , cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>bb ,cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>bb, "cc"=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>bb , "cc"=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>bb ,"cc"=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>"bb", cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>"bb" , cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>"bb" ,cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>null'::hstore; + hstore +------------ + "aa"=>NULL +(1 row) + +select 'aa=>NuLl'::hstore; + hstore +------------ + "aa"=>NULL +(1 row) + +select 'aa=>"NuLl"'::hstore; + hstore +-------------- + "aa"=>"NuLl" +(1 row) + +select e'\\=a=>q=w'::hstore; + hstore +------------- + "=a"=>"q=w" +(1 row) + +select e'"=a"=>q\\=w'::hstore; + hstore +------------- + "=a"=>"q=w" +(1 row) + +select e'"\\"a"=>q>w'::hstore; + hstore +-------------- + "\"a"=>"q>w" +(1 row) + +select e'\\"a=>q"w'::hstore; + hstore +--------------- + "\"a"=>"q\"w" +(1 row) + +select ''::hstore; + hstore +-------- + +(1 row) + +select ' '::hstore; + hstore +-------- + +(1 row) + +-- -> operator +select 'aa=>b, c=>d , b=>16'::hstore->'c'; + ?column? +---------- + d +(1 row) + +select 'aa=>b, c=>d , b=>16'::hstore->'b'; + ?column? +---------- + 16 +(1 row) + +select 'aa=>b, c=>d , b=>16'::hstore->'aa'; + ?column? +---------- + b +(1 row) + +select ('aa=>b, c=>d , b=>16'::hstore->'gg') is null; + ?column? +---------- + t +(1 row) + +select ('aa=>NULL, c=>d , b=>16'::hstore->'aa') is null; + ?column? +---------- + t +(1 row) + +select ('aa=>"NULL", c=>d , b=>16'::hstore->'aa') is null; + ?column? +---------- + f +(1 row) + +-- -> array operator +select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['aa','c']; + ?column? +------------ + {"NULL",d} +(1 row) + +select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['c','aa']; + ?column? +------------ + {d,"NULL"} +(1 row) + +select 'aa=>NULL, c=>d , b=>16'::hstore -> ARRAY['aa','c',null]; + ?column? +--------------- + {NULL,d,NULL} +(1 row) + +select 'aa=>1, c=>3, b=>2, d=>4'::hstore -> ARRAY[['b','d'],['aa','c']]; + ?column? +--------------- + {{2,4},{1,3}} +(1 row) + +-- exists/defined +select exist('a=>NULL, b=>qq', 'a'); + exist +------- + t +(1 row) + +select exist('a=>NULL, b=>qq', 'b'); + exist +------- + t +(1 row) + +select exist('a=>NULL, b=>qq', 'c'); + exist +------- + f +(1 row) + +select exist('a=>"NULL", b=>qq', 'a'); + exist +------- + t +(1 row) + +select defined('a=>NULL, b=>qq', 'a'); + defined +--------- + f +(1 row) + +select defined('a=>NULL, b=>qq', 'b'); + defined +--------- + t +(1 row) + +select defined('a=>NULL, b=>qq', 'c'); + defined +--------- + f +(1 row) + +select defined('a=>"NULL", b=>qq', 'a'); + defined +--------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ? 'a'; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ? 'b'; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ? 'c'; + ?column? +---------- + f +(1 row) + +select hstore 'a=>"NULL", b=>qq' ? 'a'; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?| ARRAY['a','b']; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?| ARRAY['b','a']; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','a']; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','d']; + ?column? +---------- + f +(1 row) + +select hstore 'a=>NULL, b=>qq' ?| '{}'::text[]; + ?column? +---------- + f +(1 row) + +select hstore 'a=>NULL, b=>qq' ?& ARRAY['a','b']; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?& ARRAY['b','a']; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','a']; + ?column? +---------- + f +(1 row) + +select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','d']; + ?column? +---------- + f +(1 row) + +select hstore 'a=>NULL, b=>qq' ?& '{}'::text[]; + ?column? +---------- + t +(1 row) + +-- delete +select delete('a=>1 , b=>2, c=>3'::hstore, 'a'); + delete +-------------------- + "b"=>"2", "c"=>"3" +(1 row) + +select delete('a=>null , b=>2, c=>3'::hstore, 'a'); + delete +-------------------- + "b"=>"2", "c"=>"3" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, 'b'); + delete +-------------------- + "a"=>"1", "c"=>"3" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, 'c'); + delete +-------------------- + "a"=>"1", "b"=>"2" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, 'd'); + delete +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - 'a'::text; + ?column? +-------------------- + "b"=>"2", "c"=>"3" +(1 row) + +select 'a=>null , b=>2, c=>3'::hstore - 'a'::text; + ?column? +-------------------- + "b"=>"2", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - 'b'::text; + ?column? +-------------------- + "a"=>"1", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - 'c'::text; + ?column? +-------------------- + "a"=>"1", "b"=>"2" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - 'd'::text; + ?column? +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b'::text) + = pg_column_size('a=>1, b=>2'::hstore); + ?column? +---------- + t +(1 row) + +-- delete (array) +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','e']); + delete +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','b']); + delete +-------------------- + "a"=>"1", "c"=>"3" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['a','c']); + delete +---------- + "b"=>"2" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY[['b'],['c'],['a']]); + delete +-------- + +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, '{}'::text[]); + delete +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','e']; + ?column? +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','b']; + ?column? +-------------------- + "a"=>"1", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c']; + ?column? +---------- + "b"=>"2" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY[['b'],['c'],['a']]; + ?column? +---------- + +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - '{}'::text[]; + ?column? +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c']) + = pg_column_size('b=>2'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - '{}'::text[]) + = pg_column_size('a=>1, b=>2, c=>3'::hstore); + ?column? +---------- + t +(1 row) + +-- delete (hstore) +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>4, b=>2'::hstore); + delete +--------------------- + "c"=>"3", "aa"=>"1" +(1 row) + +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>NULL, c=>3'::hstore); + delete +--------------------- + "b"=>"2", "aa"=>"1" +(1 row) + +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>1, b=>2, c=>3'::hstore); + delete +-------- + +(1 row) + +select delete('aa=>1 , b=>2, c=>3'::hstore, 'b=>2'::hstore); + delete +--------------------- + "c"=>"3", "aa"=>"1" +(1 row) + +select delete('aa=>1 , b=>2, c=>3'::hstore, ''::hstore); + delete +------------------------------- + "b"=>"2", "c"=>"3", "aa"=>"1" +(1 row) + +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>4, b=>2'::hstore; + ?column? +--------------------- + "c"=>"3", "aa"=>"1" +(1 row) + +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>NULL, c=>3'::hstore; + ?column? +--------------------- + "b"=>"2", "aa"=>"1" +(1 row) + +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>1, b=>2, c=>3'::hstore; + ?column? +---------- + +(1 row) + +select 'aa=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore; + ?column? +--------------------- + "c"=>"3", "aa"=>"1" +(1 row) + +select 'aa=>1 , b=>2, c=>3'::hstore - ''::hstore; + ?column? +------------------------------- + "b"=>"2", "c"=>"3", "aa"=>"1" +(1 row) + +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore) + = pg_column_size('a=>1, c=>3'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ''::hstore) + = pg_column_size('a=>1, b=>2, c=>3'::hstore); + ?column? +---------- + t +(1 row) + +-- || +select 'aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'; + ?column? +------------------------------------------- + "b"=>"g", "aa"=>"1", "cq"=>"l", "fg"=>"f" +(1 row) + +select 'aa=>1 , b=>2, cq=>3'::hstore || 'aq=>l'; + ?column? +------------------------------------------- + "b"=>"2", "aa"=>"1", "aq"=>"l", "cq"=>"3" +(1 row) + +select 'aa=>1 , b=>2, cq=>3'::hstore || 'aa=>l'; + ?column? +-------------------------------- + "b"=>"2", "aa"=>"l", "cq"=>"3" +(1 row) + +select 'aa=>1 , b=>2, cq=>3'::hstore || ''; + ?column? +-------------------------------- + "b"=>"2", "aa"=>"1", "cq"=>"3" +(1 row) + +select ''::hstore || 'cq=>l, b=>g, fg=>f'; + ?column? +-------------------------------- + "b"=>"g", "cq"=>"l", "fg"=>"f" +(1 row) + +select pg_column_size(''::hstore || ''::hstore) = pg_column_size(''::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size('aa=>1'::hstore || 'b=>2'::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size('aa=>1, b=>2'::hstore || ''::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size(''::hstore || 'aa=>1, b=>2'::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); + ?column? +---------- + t +(1 row) + +-- hstore(text,text) +select 'a=>g, b=>c'::hstore || hstore('asd', 'gf'); + ?column? +--------------------------------- + "a"=>"g", "b"=>"c", "asd"=>"gf" +(1 row) + +select 'a=>g, b=>c'::hstore || hstore('b', 'gf'); + ?column? +--------------------- + "a"=>"g", "b"=>"gf" +(1 row) + +select 'a=>g, b=>c'::hstore || hstore('b', 'NULL'); + ?column? +----------------------- + "a"=>"g", "b"=>"NULL" +(1 row) + +select 'a=>g, b=>c'::hstore || hstore('b', NULL); + ?column? +--------------------- + "a"=>"g", "b"=>NULL +(1 row) + +select ('a=>g, b=>c'::hstore || hstore(NULL, 'b')) is null; + ?column? +---------- + t +(1 row) + +select pg_column_size(hstore('b', 'gf')) + = pg_column_size('b=>gf'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size('a=>g, b=>c'::hstore || hstore('b', 'gf')) + = pg_column_size('a=>g, b=>gf'::hstore); + ?column? +---------- + t +(1 row) + +-- slice() +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['g','h','i']); + slice +------- + +(1 row) + +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b']); + slice +-------------------- + "b"=>"2", "c"=>"3" +(1 row) + +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['aa','b']); + slice +--------------------- + "b"=>"2", "aa"=>"1" +(1 row) + +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa']); + slice +------------------------------- + "b"=>"2", "c"=>"3", "aa"=>"1" +(1 row) + +select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b'])) + = pg_column_size('b=>2, c=>3'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa'])) + = pg_column_size('aa=>1, b=>2, c=>3'::hstore); + ?column? +---------- + t +(1 row) + +-- array input +select '{}'::text[]::hstore; + hstore +-------- + +(1 row) + +select ARRAY['a','g','b','h','asd']::hstore; +ERROR: array must have even number of elements +select ARRAY['a','g','b','h','asd','i']::hstore; + array +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore; + array +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select ARRAY[['a','g','b'],['h','asd','i']]::hstore; +ERROR: array must have two columns +select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore; +ERROR: wrong number of array subscripts +select hstore('{}'::text[]); + hstore +-------- + +(1 row) + +select hstore(ARRAY['a','g','b','h','asd']); +ERROR: array must have even number of elements +select hstore(ARRAY['a','g','b','h','asd','i']); + hstore +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select hstore(ARRAY[['a','g'],['b','h'],['asd','i']]); + hstore +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select hstore(ARRAY[['a','g','b'],['h','asd','i']]); +ERROR: array must have two columns +select hstore(ARRAY[[['a','g'],['b','h'],['asd','i']]]); +ERROR: wrong number of array subscripts +select hstore('[0:5]={a,g,b,h,asd,i}'::text[]); + hstore +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select hstore('[0:2][1:2]={{a,g},{b,h},{asd,i}}'::text[]); + hstore +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +-- pairs of arrays +select hstore(ARRAY['a','b','asd'], ARRAY['g','h','i']); + hstore +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select hstore(ARRAY['a','b','asd'], ARRAY['g','h',NULL]); + hstore +--------------------------------- + "a"=>"g", "b"=>"h", "asd"=>NULL +(1 row) + +select hstore(ARRAY['z','y','x'], ARRAY['1','2','3']); + hstore +------------------------------ + "x"=>"3", "y"=>"2", "z"=>"1" +(1 row) + +select hstore(ARRAY['aaa','bb','c','d'], ARRAY[null::text,null,null,null]); + hstore +----------------------------------------------- + "c"=>NULL, "d"=>NULL, "bb"=>NULL, "aaa"=>NULL +(1 row) + +select hstore(ARRAY['aaa','bb','c','d'], null); + hstore +----------------------------------------------- + "c"=>NULL, "d"=>NULL, "bb"=>NULL, "aaa"=>NULL +(1 row) + +select quote_literal(hstore('{}'::text[], '{}'::text[])); + quote_literal +--------------- + '' +(1 row) + +select quote_literal(hstore('{}'::text[], null)); + quote_literal +--------------- + '' +(1 row) + +select hstore(ARRAY['a'], '{}'::text[]); -- error +ERROR: arrays must have same bounds +select hstore('{}'::text[], ARRAY['a']); -- error +ERROR: arrays must have same bounds +select pg_column_size(hstore(ARRAY['a','b','asd'], ARRAY['g','h','i'])) + = pg_column_size('a=>g, b=>h, asd=>i'::hstore); + ?column? +---------- + t +(1 row) + +-- records +select hstore(v) from (values (1, 'foo', 1.2, 3::float8)) v(a,b,c,d); + hstore +-------------------------------------------- + "a"=>"1", "b"=>"foo", "c"=>"1.2", "d"=>"3" +(1 row) + +create domain hstestdom1 as integer not null default 0; +create table testhstore0 (a integer, b text, c numeric, d float8); +create table testhstore1 (a integer, b text, c numeric, d float8, e hstestdom1); +insert into testhstore0 values (1, 'foo', 1.2, 3::float8); +insert into testhstore1 values (1, 'foo', 1.2, 3::float8); +select hstore(v) from testhstore1 v; + hstore +------------------------------------------------------ + "a"=>"1", "b"=>"foo", "c"=>"1.2", "d"=>"3", "e"=>"0" +(1 row) + +select hstore(null::testhstore0); + hstore +-------------------------------------------- + "a"=>NULL, "b"=>NULL, "c"=>NULL, "d"=>NULL +(1 row) + +select hstore(null::testhstore1); + hstore +------------------------------------------------------- + "a"=>NULL, "b"=>NULL, "c"=>NULL, "d"=>NULL, "e"=>NULL +(1 row) + +select pg_column_size(hstore(v)) + = pg_column_size('a=>1, b=>"foo", c=>"1.2", d=>"3", e=>"0"'::hstore) + from testhstore1 v; + ?column? +---------- + t +(1 row) + +select populate_record(v, hstore('c', '3.45')) from testhstore1 v; + populate_record +------------------ + (1,foo,3.45,3,0) +(1 row) + +select populate_record(v, hstore('d', '3.45')) from testhstore1 v; + populate_record +-------------------- + (1,foo,1.2,3.45,0) +(1 row) + +select populate_record(v, hstore('e', '123')) from testhstore1 v; + populate_record +------------------- + (1,foo,1.2,3,123) +(1 row) + +select populate_record(v, hstore('e', null)) from testhstore1 v; +ERROR: domain hstestdom1 does not allow null values +select populate_record(v, hstore('c', null)) from testhstore1 v; + populate_record +----------------- + (1,foo,,3,0) +(1 row) + +select populate_record(v, hstore('b', 'foo') || hstore('a', '123')) from testhstore1 v; + populate_record +------------------- + (123,foo,1.2,3,0) +(1 row) + +select populate_record(v, hstore('b', 'foo') || hstore('e', null)) from testhstore0 v; + populate_record +----------------- + (1,foo,1.2,3) +(1 row) + +select populate_record(v, hstore('b', 'foo') || hstore('e', null)) from testhstore1 v; +ERROR: domain hstestdom1 does not allow null values +select populate_record(v, '') from testhstore0 v; + populate_record +----------------- + (1,foo,1.2,3) +(1 row) + +select populate_record(v, '') from testhstore1 v; + populate_record +----------------- + (1,foo,1.2,3,0) +(1 row) + +select populate_record(null::testhstore1, hstore('c', '3.45') || hstore('a', '123')); +ERROR: domain hstestdom1 does not allow null values +select populate_record(null::testhstore1, hstore('c', '3.45') || hstore('e', '123')); + populate_record +----------------- + (,,3.45,,123) +(1 row) + +select populate_record(null::testhstore0, ''); + populate_record +----------------- + (,,,) +(1 row) + +select populate_record(null::testhstore1, ''); +ERROR: domain hstestdom1 does not allow null values +select v #= hstore('c', '3.45') from testhstore1 v; + ?column? +------------------ + (1,foo,3.45,3,0) +(1 row) + +select v #= hstore('d', '3.45') from testhstore1 v; + ?column? +-------------------- + (1,foo,1.2,3.45,0) +(1 row) + +select v #= hstore('e', '123') from testhstore1 v; + ?column? +------------------- + (1,foo,1.2,3,123) +(1 row) + +select v #= hstore('c', null) from testhstore1 v; + ?column? +-------------- + (1,foo,,3,0) +(1 row) + +select v #= hstore('e', null) from testhstore0 v; + ?column? +--------------- + (1,foo,1.2,3) +(1 row) + +select v #= hstore('e', null) from testhstore1 v; +ERROR: domain hstestdom1 does not allow null values +select v #= (hstore('b', 'foo') || hstore('a', '123')) from testhstore1 v; + ?column? +------------------- + (123,foo,1.2,3,0) +(1 row) + +select v #= (hstore('b', 'foo') || hstore('e', '123')) from testhstore1 v; + ?column? +------------------- + (1,foo,1.2,3,123) +(1 row) + +select v #= hstore '' from testhstore0 v; + ?column? +--------------- + (1,foo,1.2,3) +(1 row) + +select v #= hstore '' from testhstore1 v; + ?column? +----------------- + (1,foo,1.2,3,0) +(1 row) + +select null::testhstore1 #= (hstore('c', '3.45') || hstore('a', '123')); +ERROR: domain hstestdom1 does not allow null values +select null::testhstore1 #= (hstore('c', '3.45') || hstore('e', '123')); + ?column? +--------------- + (,,3.45,,123) +(1 row) + +select null::testhstore0 #= hstore ''; + ?column? +---------- + (,,,) +(1 row) + +select null::testhstore1 #= hstore ''; +ERROR: domain hstestdom1 does not allow null values +select v #= h from testhstore1 v, (values (hstore 'a=>123',1),('b=>foo,c=>3.21',2),('a=>null',3),('e=>123',4),('f=>blah',5)) x(h,i) order by i; + ?column? +------------------- + (123,foo,1.2,3,0) + (1,foo,3.21,3,0) + (,foo,1.2,3,0) + (1,foo,1.2,3,123) + (1,foo,1.2,3,0) +(5 rows) + +-- keys/values +select akeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); + akeys +-------------- + {b,aa,cq,fg} +(1 row) + +select akeys('""=>1'); + akeys +------- + {""} +(1 row) + +select akeys(''); + akeys +------- + {} +(1 row) + +select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); + avals +----------- + {g,1,l,f} +(1 row) + +select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL'); + avals +-------------- + {g,1,l,NULL} +(1 row) + +select avals('""=>1'); + avals +------- + {1} +(1 row) + +select avals(''); + avals +------- + {} +(1 row) + +select hstore_to_array('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore); + hstore_to_array +------------------------- + {b,g,aa,1,cq,l,fg,NULL} +(1 row) + +select %% 'aa=>1, cq=>l, b=>g, fg=>NULL'; + ?column? +------------------------- + {b,g,aa,1,cq,l,fg,NULL} +(1 row) + +select hstore_to_matrix('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore); + hstore_to_matrix +--------------------------------- + {{b,g},{aa,1},{cq,l},{fg,NULL}} +(1 row) + +select %# 'aa=>1, cq=>l, b=>g, fg=>NULL'; + ?column? +--------------------------------- + {{b,g},{aa,1},{cq,l},{fg,NULL}} +(1 row) + +select * from skeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); + skeys +------- + b + aa + cq + fg +(4 rows) + +select * from skeys('""=>1'); + skeys +------- + +(1 row) + +select * from skeys(''); + skeys +------- +(0 rows) + +select * from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); + svals +------- + g + 1 + l + f +(4 rows) + +select *, svals is null from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL'); + svals | ?column? +-------+---------- + g | f + 1 | f + l | f + | t +(4 rows) + +select * from svals('""=>1'); + svals +------- + 1 +(1 row) + +select * from svals(''); + svals +------- +(0 rows) + +select * from each('aaa=>bq, b=>NULL, ""=>1 '); + key | value +-----+------- + | 1 + b | + aaa | bq +(3 rows) + +-- @> +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b'; + ?column? +---------- + t +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>NULL'; + ?column? +---------- + t +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, g=>NULL'; + ?column? +---------- + f +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'g=>NULL'; + ?column? +---------- + f +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>c'; + ?column? +---------- + f +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b'; + ?column? +---------- + t +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>q'; + ?column? +---------- + f +(1 row) + +CREATE TABLE testhstore (h hstore); +\copy testhstore from 'data/hstore.data' +select count(*) from testhstore where h @> 'wait=>NULL'; + count +------- + 1 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC'; + count +------- + 15 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; + count +------- + 2 +(1 row) + +select count(*) from testhstore where h ? 'public'; + count +------- + 194 +(1 row) + +select count(*) from testhstore where h ?| ARRAY['public','disabled']; + count +------- + 337 +(1 row) + +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +create index hidx on testhstore using gist(h); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +create index hidx on testhstore using gin (h); +set enable_seqscan=off; +select count(*) from testhstore where h @> 'wait=>NULL'; + count +------- + 1 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC'; + count +------- + 15 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; + count +------- + 2 +(1 row) + +select count(*) from testhstore where h ? 'public'; + count +------- + 194 +(1 row) + +select count(*) from testhstore where h ?| ARRAY['public','disabled']; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 2. +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +select count(*) from (select (each(h)).key from testhstore) as wow ; + count +------- + 4781 +(1 row) + +select key, count(*) from (select (each(h)).key from testhstore) as wow group by key order by count desc, key; + key | count +-----------+------- + line | 884 + query | 207 + pos | 203 + node | 202 + space | 197 + status | 195 + public | 194 + title | 190 + wait | 190 + org | 189 + user | 189 + coauthors | 188 + disabled | 185 + indexed | 184 + cleaned | 180 + bad | 179 + date | 179 + world | 176 + state | 172 + subtitle | 169 + auth | 168 + abstract | 161 +(22 rows) + +-- sort/hash +-- TODO(#10441): fix output +select count(distinct h) from testhstore; +ERROR: could not identify an ordering operator for type hstore +LINE 1: select count(distinct h) from testhstore; + ^ +DETAIL: Aggregates with DISTINCT must be able to sort their inputs. +set enable_hashagg = false; +select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2; + count +------- + 885 +(1 row) + +set enable_hashagg = true; +set enable_sort = false; +select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2; + count +------- + 885 +(1 row) + +select distinct * from (values (hstore '' || ''),('')) v(h); + h +--- + +(1 row) + +set enable_sort = true; +-- btree +drop index hidx; +create index hidx on testhstore using btree (h); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +ERROR: data type hstore has no default operator class for access method "lsm" +HINT: You must specify an operator class for the index or define a default operator class for the data type. +set enable_seqscan=off; +select count(*) from testhstore where h #># 'p=>1'; + count +------- + 125 +(1 row) + +select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexed=>t'; + count +------- + 1 +(1 row) + +-- json and jsonb +select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); + hstore_to_json +------------------------------------------------------------------------------------------------- + {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"} +(1 row) + +select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json); + json +------------------------------------------------------------------------------------------------- + {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"} +(1 row) + +select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"'); + hstore_to_json_loose +------------------------------------------------------------------------------------------------------------- + {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "h": "2016-01-01", "a key": 1} +(1 row) + +select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); + hstore_to_jsonb +------------------------------------------------------------------------------------------------- + {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"} +(1 row) + +select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb); + jsonb +------------------------------------------------------------------------------------------------- + {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"} +(1 row) + +select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"'); + hstore_to_jsonb_loose +---------------------------------------------------------------------------------------------------------- + {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 23450, "h": "2016-01-01", "a key": 1} +(1 row) + +-- YB note: add ordering. +create table test_json_agg (f1 text, f2 hstore, ybsort serial, PRIMARY KEY (ybsort ASC)); +insert into test_json_agg values ('rec1','"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'), + ('rec2','"a key" =>2, b => f, c => "null", d=> -12345, e => 012345.6, f=> -1.234, g=> 0.345e-4'); +select json_agg(q) from test_json_agg q; + json_agg +--------------------------------------------------------------------------------------------------------------------------------------- + [{"f1":"rec1","f2":{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"},"ybsort":1}, + + {"f1":"rec2","f2":{"b": "f", "c": "null", "d": "-12345", "e": "012345.6", "f": "-1.234", "g": "0.345e-4", "a key": "2"},"ybsort":2}] +(1 row) + +select json_agg(q) from (select f1, hstore_to_json_loose(f2) as f2 from test_json_agg) q; + json_agg +---------------------------------------------------------------------------------------------------------------------- + [{"f1":"rec1","f2":{"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "a key": 1}}, + + {"f1":"rec2","f2":{"b": false, "c": "null", "d": -12345, "e": "012345.6", "f": -1.234, "g": 0.345e-4, "a key": 2}}] +(1 row) + +-- Test subscripting +insert into test_json_agg default values; +select f2['d'], f2['x'] is null as x_isnull from test_json_agg; + f2 | x_isnull +--------+---------- + 12345 | t + -12345 | t + | t +(3 rows) + +select f2['d']['e'] from test_json_agg; -- error +ERROR: hstore allows only one subscript +select f2['d':'e'] from test_json_agg; -- error +ERROR: hstore allows only one subscript +update test_json_agg set f2['d'] = f2['e'], f2['x'] = 'xyzzy'; +select f2 from test_json_agg; + f2 +--------------------------------------------------------------------------------------------------------------------- + "b"=>"t", "c"=>NULL, "d"=>"012345", "e"=>"012345", "f"=>"1.234", "g"=>"2.345e+4", "x"=>"xyzzy", "a key"=>"1" + "b"=>"f", "c"=>"null", "d"=>"012345.6", "e"=>"012345.6", "f"=>"-1.234", "g"=>"0.345e-4", "x"=>"xyzzy", "a key"=>"2" + "d"=>NULL, "x"=>"xyzzy" +(3 rows) + +-- Test subscripting in plpgsql +do $$ declare h hstore; +begin h['a'] := 'b'; raise notice 'h = %, h[a] = %', h, h['a']; end $$; +NOTICE: h = "a"=>"b", h[a] = b +-- Check the hstore_hash() and hstore_hash_extended() function explicitly. +SELECT v as value, hstore_hash(v)::bit(32) as standard, + hstore_hash_extended(v, 0)::bit(32) as extended0, + hstore_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::hstore), (''), ('"a key" =>1'), ('c => null'), + ('e => 012345'), ('g => 2.345e+4')) x(v) +WHERE hstore_hash(v)::bit(32) != hstore_hash_extended(v, 0)::bit(32) + OR hstore_hash(v)::bit(32) = hstore_hash_extended(v, 1)::bit(32); + value | standard | extended0 | extended1 +-------+----------+-----------+----------- +(0 rows) + diff --git a/contrib/hstore/hstore--1.5--1.5-yb-1.0.sql b/contrib/hstore/hstore--1.5--1.5-yb-1.0.sql new file mode 100644 index 0000000000000..2c034bfbbfec5 --- /dev/null +++ b/contrib/hstore/hstore--1.5--1.5-yb-1.0.sql @@ -0,0 +1,23 @@ +/* contrib/hstore/hstore--1.5--1.5-yb-1.0.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION hstore UPDATE TO '1.5-yb-1.0'" to load this file. \quit + +/* + * Translate gin operator class to ybgin operator class. + */ + +/* Adopted from contrib/hstore/hstore--1.4.sql */ + +CREATE OPERATOR CLASS gin_hstore_ops +DEFAULT FOR TYPE hstore USING ybgin +AS + OPERATOR 7 @>, + OPERATOR 9 ?(hstore,text), + OPERATOR 10 ?|(hstore,text[]), + OPERATOR 11 ?&(hstore,text[]), + FUNCTION 1 bttextcmp(text,text), + FUNCTION 2 gin_extract_hstore(hstore, internal), + FUNCTION 3 gin_extract_hstore_query(hstore, internal, int2, internal, internal), + FUNCTION 4 gin_consistent_hstore(internal, int2, hstore, int4, internal, internal), + STORAGE text; diff --git a/contrib/hstore/hstore--1.5-yb-1.0--1.8-yb-1.0.sql b/contrib/hstore/hstore--1.5-yb-1.0--1.8-yb-1.0.sql new file mode 100644 index 0000000000000..2dcc2ffb87608 --- /dev/null +++ b/contrib/hstore/hstore--1.5-yb-1.0--1.8-yb-1.0.sql @@ -0,0 +1,52 @@ +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION hstore UPDATE TO '1.8-yb-1.0'" to load this file. \quit + +/* Sourced from contrib/hstore/hstore--1.5--1.6.sql */ + +CREATE FUNCTION hstore_hash_extended(hstore, int8) +RETURNS int8 +AS 'MODULE_PATHNAME','hstore_hash_extended' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +ALTER OPERATOR FAMILY hash_hstore_ops USING hash ADD + FUNCTION 2 hstore_hash_extended(hstore, int8); + +/* Sourced from contrib/hstore/hstore--1.6--1.7.sql */ + +CREATE FUNCTION ghstore_options(internal) +RETURNS void +AS 'MODULE_PATHNAME', 'ghstore_options' +LANGUAGE C IMMUTABLE PARALLEL SAFE; + +ALTER OPERATOR FAMILY gist_hstore_ops USING gist +ADD FUNCTION 10 (hstore) ghstore_options (internal); + +ALTER OPERATOR ? (hstore, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ?| (hstore, text[]) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ?& (hstore, text[]) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR @> (hstore, hstore) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR <@ (hstore, hstore) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR @ (hstore, hstore) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ~ (hstore, hstore) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); + +/* Sourced from contrib/hstore/hstore--1.7--1.8.sql */ + +CREATE FUNCTION hstore_subscript_handler(internal) +RETURNS internal +AS 'MODULE_PATHNAME', 'hstore_subscript_handler' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +ALTER TYPE hstore SET ( + SUBSCRIPT = hstore_subscript_handler +); + +-- Remove @ and ~ +DROP OPERATOR @ (hstore, hstore); +DROP OPERATOR ~ (hstore, hstore); diff --git a/contrib/hstore/hstore.control b/contrib/hstore/hstore.control index 89e3c746c461d..758564854bc4f 100644 --- a/contrib/hstore/hstore.control +++ b/contrib/hstore/hstore.control @@ -1,6 +1,6 @@ # hstore extension comment = 'data type for storing sets of (key, value) pairs' -default_version = '1.8' +default_version = '1.8-yb-1.0' module_pathname = '$libdir/hstore' relocatable = true trusted = true diff --git a/contrib/hstore/sql/yb.port.hstore.sql b/contrib/hstore/sql/yb.port.hstore.sql new file mode 100644 index 0000000000000..7d0e4a52cf6e8 --- /dev/null +++ b/contrib/hstore/sql/yb.port.hstore.sql @@ -0,0 +1,366 @@ +CREATE EXTENSION hstore; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + +set escape_string_warning=off; + +--hstore; + +select ''::hstore; +select 'a=>b'::hstore; +select ' a=>b'::hstore; +select 'a =>b'::hstore; +select 'a=>b '::hstore; +select 'a=> b'::hstore; +select '"a"=>"b"'::hstore; +select ' "a"=>"b"'::hstore; +select '"a" =>"b"'::hstore; +select '"a"=>"b" '::hstore; +select '"a"=> "b"'::hstore; +select 'aa=>bb'::hstore; +select ' aa=>bb'::hstore; +select 'aa =>bb'::hstore; +select 'aa=>bb '::hstore; +select 'aa=> bb'::hstore; +select '"aa"=>"bb"'::hstore; +select ' "aa"=>"bb"'::hstore; +select '"aa" =>"bb"'::hstore; +select '"aa"=>"bb" '::hstore; +select '"aa"=> "bb"'::hstore; + +select 'aa=>bb, cc=>dd'::hstore; +select 'aa=>bb , cc=>dd'::hstore; +select 'aa=>bb ,cc=>dd'::hstore; +select 'aa=>bb, "cc"=>dd'::hstore; +select 'aa=>bb , "cc"=>dd'::hstore; +select 'aa=>bb ,"cc"=>dd'::hstore; +select 'aa=>"bb", cc=>dd'::hstore; +select 'aa=>"bb" , cc=>dd'::hstore; +select 'aa=>"bb" ,cc=>dd'::hstore; + +select 'aa=>null'::hstore; +select 'aa=>NuLl'::hstore; +select 'aa=>"NuLl"'::hstore; + +select e'\\=a=>q=w'::hstore; +select e'"=a"=>q\\=w'::hstore; +select e'"\\"a"=>q>w'::hstore; +select e'\\"a=>q"w'::hstore; + +select ''::hstore; +select ' '::hstore; + +-- -> operator + +select 'aa=>b, c=>d , b=>16'::hstore->'c'; +select 'aa=>b, c=>d , b=>16'::hstore->'b'; +select 'aa=>b, c=>d , b=>16'::hstore->'aa'; +select ('aa=>b, c=>d , b=>16'::hstore->'gg') is null; +select ('aa=>NULL, c=>d , b=>16'::hstore->'aa') is null; +select ('aa=>"NULL", c=>d , b=>16'::hstore->'aa') is null; + +-- -> array operator + +select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['aa','c']; +select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['c','aa']; +select 'aa=>NULL, c=>d , b=>16'::hstore -> ARRAY['aa','c',null]; +select 'aa=>1, c=>3, b=>2, d=>4'::hstore -> ARRAY[['b','d'],['aa','c']]; + +-- exists/defined + +select exist('a=>NULL, b=>qq', 'a'); +select exist('a=>NULL, b=>qq', 'b'); +select exist('a=>NULL, b=>qq', 'c'); +select exist('a=>"NULL", b=>qq', 'a'); +select defined('a=>NULL, b=>qq', 'a'); +select defined('a=>NULL, b=>qq', 'b'); +select defined('a=>NULL, b=>qq', 'c'); +select defined('a=>"NULL", b=>qq', 'a'); +select hstore 'a=>NULL, b=>qq' ? 'a'; +select hstore 'a=>NULL, b=>qq' ? 'b'; +select hstore 'a=>NULL, b=>qq' ? 'c'; +select hstore 'a=>"NULL", b=>qq' ? 'a'; +select hstore 'a=>NULL, b=>qq' ?| ARRAY['a','b']; +select hstore 'a=>NULL, b=>qq' ?| ARRAY['b','a']; +select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','a']; +select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','d']; +select hstore 'a=>NULL, b=>qq' ?| '{}'::text[]; +select hstore 'a=>NULL, b=>qq' ?& ARRAY['a','b']; +select hstore 'a=>NULL, b=>qq' ?& ARRAY['b','a']; +select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','a']; +select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','d']; +select hstore 'a=>NULL, b=>qq' ?& '{}'::text[]; + +-- delete + +select delete('a=>1 , b=>2, c=>3'::hstore, 'a'); +select delete('a=>null , b=>2, c=>3'::hstore, 'a'); +select delete('a=>1 , b=>2, c=>3'::hstore, 'b'); +select delete('a=>1 , b=>2, c=>3'::hstore, 'c'); +select delete('a=>1 , b=>2, c=>3'::hstore, 'd'); +select 'a=>1 , b=>2, c=>3'::hstore - 'a'::text; +select 'a=>null , b=>2, c=>3'::hstore - 'a'::text; +select 'a=>1 , b=>2, c=>3'::hstore - 'b'::text; +select 'a=>1 , b=>2, c=>3'::hstore - 'c'::text; +select 'a=>1 , b=>2, c=>3'::hstore - 'd'::text; +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b'::text) + = pg_column_size('a=>1, b=>2'::hstore); + +-- delete (array) + +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','e']); +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','b']); +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['a','c']); +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY[['b'],['c'],['a']]); +select delete('a=>1 , b=>2, c=>3'::hstore, '{}'::text[]); +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','e']; +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','b']; +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c']; +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY[['b'],['c'],['a']]; +select 'a=>1 , b=>2, c=>3'::hstore - '{}'::text[]; +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c']) + = pg_column_size('b=>2'::hstore); +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - '{}'::text[]) + = pg_column_size('a=>1, b=>2, c=>3'::hstore); + +-- delete (hstore) + +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>4, b=>2'::hstore); +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>NULL, c=>3'::hstore); +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>1, b=>2, c=>3'::hstore); +select delete('aa=>1 , b=>2, c=>3'::hstore, 'b=>2'::hstore); +select delete('aa=>1 , b=>2, c=>3'::hstore, ''::hstore); +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>4, b=>2'::hstore; +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>NULL, c=>3'::hstore; +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>1, b=>2, c=>3'::hstore; +select 'aa=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore; +select 'aa=>1 , b=>2, c=>3'::hstore - ''::hstore; +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore) + = pg_column_size('a=>1, c=>3'::hstore); +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ''::hstore) + = pg_column_size('a=>1, b=>2, c=>3'::hstore); + +-- || +select 'aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'; +select 'aa=>1 , b=>2, cq=>3'::hstore || 'aq=>l'; +select 'aa=>1 , b=>2, cq=>3'::hstore || 'aa=>l'; +select 'aa=>1 , b=>2, cq=>3'::hstore || ''; +select ''::hstore || 'cq=>l, b=>g, fg=>f'; +select pg_column_size(''::hstore || ''::hstore) = pg_column_size(''::hstore); +select pg_column_size('aa=>1'::hstore || 'b=>2'::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); +select pg_column_size('aa=>1, b=>2'::hstore || ''::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); +select pg_column_size(''::hstore || 'aa=>1, b=>2'::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); + +-- hstore(text,text) +select 'a=>g, b=>c'::hstore || hstore('asd', 'gf'); +select 'a=>g, b=>c'::hstore || hstore('b', 'gf'); +select 'a=>g, b=>c'::hstore || hstore('b', 'NULL'); +select 'a=>g, b=>c'::hstore || hstore('b', NULL); +select ('a=>g, b=>c'::hstore || hstore(NULL, 'b')) is null; +select pg_column_size(hstore('b', 'gf')) + = pg_column_size('b=>gf'::hstore); +select pg_column_size('a=>g, b=>c'::hstore || hstore('b', 'gf')) + = pg_column_size('a=>g, b=>gf'::hstore); + +-- slice() +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['g','h','i']); +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b']); +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['aa','b']); +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa']); +select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b'])) + = pg_column_size('b=>2, c=>3'::hstore); +select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa'])) + = pg_column_size('aa=>1, b=>2, c=>3'::hstore); + +-- array input +select '{}'::text[]::hstore; +select ARRAY['a','g','b','h','asd']::hstore; +select ARRAY['a','g','b','h','asd','i']::hstore; +select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore; +select ARRAY[['a','g','b'],['h','asd','i']]::hstore; +select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore; +select hstore('{}'::text[]); +select hstore(ARRAY['a','g','b','h','asd']); +select hstore(ARRAY['a','g','b','h','asd','i']); +select hstore(ARRAY[['a','g'],['b','h'],['asd','i']]); +select hstore(ARRAY[['a','g','b'],['h','asd','i']]); +select hstore(ARRAY[[['a','g'],['b','h'],['asd','i']]]); +select hstore('[0:5]={a,g,b,h,asd,i}'::text[]); +select hstore('[0:2][1:2]={{a,g},{b,h},{asd,i}}'::text[]); + +-- pairs of arrays +select hstore(ARRAY['a','b','asd'], ARRAY['g','h','i']); +select hstore(ARRAY['a','b','asd'], ARRAY['g','h',NULL]); +select hstore(ARRAY['z','y','x'], ARRAY['1','2','3']); +select hstore(ARRAY['aaa','bb','c','d'], ARRAY[null::text,null,null,null]); +select hstore(ARRAY['aaa','bb','c','d'], null); +select quote_literal(hstore('{}'::text[], '{}'::text[])); +select quote_literal(hstore('{}'::text[], null)); +select hstore(ARRAY['a'], '{}'::text[]); -- error +select hstore('{}'::text[], ARRAY['a']); -- error +select pg_column_size(hstore(ARRAY['a','b','asd'], ARRAY['g','h','i'])) + = pg_column_size('a=>g, b=>h, asd=>i'::hstore); + +-- records +select hstore(v) from (values (1, 'foo', 1.2, 3::float8)) v(a,b,c,d); +create domain hstestdom1 as integer not null default 0; +create table testhstore0 (a integer, b text, c numeric, d float8); +create table testhstore1 (a integer, b text, c numeric, d float8, e hstestdom1); +insert into testhstore0 values (1, 'foo', 1.2, 3::float8); +insert into testhstore1 values (1, 'foo', 1.2, 3::float8); +select hstore(v) from testhstore1 v; +select hstore(null::testhstore0); +select hstore(null::testhstore1); +select pg_column_size(hstore(v)) + = pg_column_size('a=>1, b=>"foo", c=>"1.2", d=>"3", e=>"0"'::hstore) + from testhstore1 v; +select populate_record(v, hstore('c', '3.45')) from testhstore1 v; +select populate_record(v, hstore('d', '3.45')) from testhstore1 v; +select populate_record(v, hstore('e', '123')) from testhstore1 v; +select populate_record(v, hstore('e', null)) from testhstore1 v; +select populate_record(v, hstore('c', null)) from testhstore1 v; +select populate_record(v, hstore('b', 'foo') || hstore('a', '123')) from testhstore1 v; +select populate_record(v, hstore('b', 'foo') || hstore('e', null)) from testhstore0 v; +select populate_record(v, hstore('b', 'foo') || hstore('e', null)) from testhstore1 v; +select populate_record(v, '') from testhstore0 v; +select populate_record(v, '') from testhstore1 v; +select populate_record(null::testhstore1, hstore('c', '3.45') || hstore('a', '123')); +select populate_record(null::testhstore1, hstore('c', '3.45') || hstore('e', '123')); +select populate_record(null::testhstore0, ''); +select populate_record(null::testhstore1, ''); +select v #= hstore('c', '3.45') from testhstore1 v; +select v #= hstore('d', '3.45') from testhstore1 v; +select v #= hstore('e', '123') from testhstore1 v; +select v #= hstore('c', null) from testhstore1 v; +select v #= hstore('e', null) from testhstore0 v; +select v #= hstore('e', null) from testhstore1 v; +select v #= (hstore('b', 'foo') || hstore('a', '123')) from testhstore1 v; +select v #= (hstore('b', 'foo') || hstore('e', '123')) from testhstore1 v; +select v #= hstore '' from testhstore0 v; +select v #= hstore '' from testhstore1 v; +select null::testhstore1 #= (hstore('c', '3.45') || hstore('a', '123')); +select null::testhstore1 #= (hstore('c', '3.45') || hstore('e', '123')); +select null::testhstore0 #= hstore ''; +select null::testhstore1 #= hstore ''; +select v #= h from testhstore1 v, (values (hstore 'a=>123',1),('b=>foo,c=>3.21',2),('a=>null',3),('e=>123',4),('f=>blah',5)) x(h,i) order by i; + +-- keys/values +select akeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); +select akeys('""=>1'); +select akeys(''); +select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); +select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL'); +select avals('""=>1'); +select avals(''); + +select hstore_to_array('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore); +select %% 'aa=>1, cq=>l, b=>g, fg=>NULL'; + +select hstore_to_matrix('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore); +select %# 'aa=>1, cq=>l, b=>g, fg=>NULL'; + +select * from skeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); +select * from skeys('""=>1'); +select * from skeys(''); +select * from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); +select *, svals is null from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL'); +select * from svals('""=>1'); +select * from svals(''); + +select * from each('aaa=>bq, b=>NULL, ""=>1 '); + +-- @> +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>NULL'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, g=>NULL'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'g=>NULL'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>c'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>q'; + +CREATE TABLE testhstore (h hstore); +\copy testhstore from 'data/hstore.data' + +select count(*) from testhstore where h @> 'wait=>NULL'; +select count(*) from testhstore where h @> 'wait=>CC'; +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; +select count(*) from testhstore where h ? 'public'; +select count(*) from testhstore where h ?| ARRAY['public','disabled']; +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + +create index hidx on testhstore using gist(h); + +create index hidx on testhstore using gin (h); +set enable_seqscan=off; + +select count(*) from testhstore where h @> 'wait=>NULL'; +select count(*) from testhstore where h @> 'wait=>CC'; +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; +select count(*) from testhstore where h ? 'public'; +select count(*) from testhstore where h ?| ARRAY['public','disabled']; +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + +select count(*) from (select (each(h)).key from testhstore) as wow ; +select key, count(*) from (select (each(h)).key from testhstore) as wow group by key order by count desc, key; + +-- sort/hash +-- TODO(#10441): fix output +select count(distinct h) from testhstore; +set enable_hashagg = false; +select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2; +set enable_hashagg = true; +set enable_sort = false; +select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2; +select distinct * from (values (hstore '' || ''),('')) v(h); +set enable_sort = true; + +-- btree +drop index hidx; +create index hidx on testhstore using btree (h); +set enable_seqscan=off; + +select count(*) from testhstore where h #># 'p=>1'; +select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexed=>t'; + +-- json and jsonb +select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); +select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json); +select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"'); + +select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); +select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb); +select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"'); + +-- YB note: add ordering. +create table test_json_agg (f1 text, f2 hstore, ybsort serial, PRIMARY KEY (ybsort ASC)); +insert into test_json_agg values ('rec1','"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'), + ('rec2','"a key" =>2, b => f, c => "null", d=> -12345, e => 012345.6, f=> -1.234, g=> 0.345e-4'); +select json_agg(q) from test_json_agg q; +select json_agg(q) from (select f1, hstore_to_json_loose(f2) as f2 from test_json_agg) q; + +-- Test subscripting +insert into test_json_agg default values; +select f2['d'], f2['x'] is null as x_isnull from test_json_agg; +select f2['d']['e'] from test_json_agg; -- error +select f2['d':'e'] from test_json_agg; -- error +update test_json_agg set f2['d'] = f2['e'], f2['x'] = 'xyzzy'; +select f2 from test_json_agg; + +-- Test subscripting in plpgsql +do $$ declare h hstore; +begin h['a'] := 'b'; raise notice 'h = %, h[a] = %', h, h['a']; end $$; + +-- Check the hstore_hash() and hstore_hash_extended() function explicitly. +SELECT v as value, hstore_hash(v)::bit(32) as standard, + hstore_hash_extended(v, 0)::bit(32) as extended0, + hstore_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::hstore), (''), ('"a key" =>1'), ('c => null'), + ('e => 012345'), ('g => 2.345e+4')) x(v) +WHERE hstore_hash(v)::bit(32) != hstore_hash_extended(v, 0)::bit(32) + OR hstore_hash(v)::bit(32) = hstore_hash_extended(v, 1)::bit(32); diff --git a/contrib/hstore/yb_schedule b/contrib/hstore/yb_schedule new file mode 100644 index 0000000000000..6b2d1e820ebe0 --- /dev/null +++ b/contrib/hstore/yb_schedule @@ -0,0 +1,3 @@ +# contrib/hstore/yb_schedule + +test: yb.port.hstore diff --git a/contrib/intarray/expected/yb.port._int.out b/contrib/intarray/expected/yb.port._int.out new file mode 100644 index 0000000000000..d2f70f6cf348c --- /dev/null +++ b/contrib/intarray/expected/yb.port._int.out @@ -0,0 +1,482 @@ +CREATE EXTENSION intarray; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); +ERROR: cannot validate operator family without ordered data +SELECT intset(1234); + intset +-------- + {1234} +(1 row) + +SELECT icount('{1234234,234234}'); + icount +-------- + 2 +(1 row) + +SELECT sort('{1234234,-30,234234}'); + sort +---------------------- + {-30,234234,1234234} +(1 row) + +SELECT sort('{1234234,-30,234234}','asc'); + sort +---------------------- + {-30,234234,1234234} +(1 row) + +SELECT sort('{1234234,-30,234234}','desc'); + sort +---------------------- + {1234234,234234,-30} +(1 row) + +SELECT sort_asc('{1234234,-30,234234}'); + sort_asc +---------------------- + {-30,234234,1234234} +(1 row) + +SELECT sort_desc('{1234234,-30,234234}'); + sort_desc +---------------------- + {1234234,234234,-30} +(1 row) + +SELECT uniq('{1234234,-30,-30,234234,-30}'); + uniq +-------------------------- + {1234234,-30,234234,-30} +(1 row) + +SELECT uniq(sort_asc('{1234234,-30,-30,234234,-30}')); + uniq +---------------------- + {-30,234234,1234234} +(1 row) + +SELECT idx('{1234234,-30,-30,234234,-30}',-30); + idx +----- + 2 +(1 row) + +SELECT subarray('{1234234,-30,-30,234234,-30}',2,3); + subarray +------------------ + {-30,-30,234234} +(1 row) + +SELECT subarray('{1234234,-30,-30,234234,-30}',-1,1); + subarray +---------- + {-30} +(1 row) + +SELECT subarray('{1234234,-30,-30,234234,-30}',0,-1); + subarray +-------------------------- + {1234234,-30,-30,234234} +(1 row) + +SELECT #'{1234234,234234}'::int[]; + ?column? +---------- + 2 +(1 row) + +SELECT '{123,623,445}'::int[] + 1245; + ?column? +-------------------- + {123,623,445,1245} +(1 row) + +SELECT '{123,623,445}'::int[] + 445; + ?column? +------------------- + {123,623,445,445} +(1 row) + +SELECT '{123,623,445}'::int[] + '{1245,87,445}'; + ?column? +--------------------------- + {123,623,445,1245,87,445} +(1 row) + +SELECT '{123,623,445}'::int[] - 623; + ?column? +----------- + {123,445} +(1 row) + +SELECT '{123,623,445}'::int[] - '{1623,623}'; + ?column? +----------- + {123,445} +(1 row) + +SELECT '{123,623,445}'::int[] | 623; + ?column? +--------------- + {123,445,623} +(1 row) + +SELECT '{123,623,445}'::int[] | 1623; + ?column? +-------------------- + {123,445,623,1623} +(1 row) + +SELECT '{123,623,445}'::int[] | '{1623,623}'; + ?column? +-------------------- + {123,445,623,1623} +(1 row) + +SELECT '{123,623,445}'::int[] & '{1623,623}'; + ?column? +---------- + {623} +(1 row) + +SELECT '{-1,3,1}'::int[] & '{1,2}'; + ?column? +---------- + {1} +(1 row) + +SELECT '{1}'::int[] & '{2}'::int[]; + ?column? +---------- + {} +(1 row) + +SELECT array_dims('{1}'::int[] & '{2}'::int[]); + array_dims +------------ + +(1 row) + +SELECT ('{1}'::int[] & '{2}'::int[]) = '{}'::int[]; + ?column? +---------- + t +(1 row) + +SELECT ('{}'::int[] & '{}'::int[]) = '{}'::int[]; + ?column? +---------- + t +(1 row) + +--test query_int +SELECT '1'::query_int; + query_int +----------- + 1 +(1 row) + +SELECT ' 1'::query_int; + query_int +----------- + 1 +(1 row) + +SELECT '1 '::query_int; + query_int +----------- + 1 +(1 row) + +SELECT ' 1 '::query_int; + query_int +----------- + 1 +(1 row) + +SELECT ' ! 1 '::query_int; + query_int +----------- + !1 +(1 row) + +SELECT '!1'::query_int; + query_int +----------- + !1 +(1 row) + +SELECT '1|2'::query_int; + query_int +----------- + 1 | 2 +(1 row) + +SELECT '1|!2'::query_int; + query_int +----------- + 1 | !2 +(1 row) + +SELECT '!1|2'::query_int; + query_int +----------- + !1 | 2 +(1 row) + +SELECT '!1|!2'::query_int; + query_int +----------- + !1 | !2 +(1 row) + +SELECT '!(!1|!2)'::query_int; + query_int +-------------- + !( !1 | !2 ) +(1 row) + +SELECT '!(!1|2)'::query_int; + query_int +------------- + !( !1 | 2 ) +(1 row) + +SELECT '!(1|!2)'::query_int; + query_int +------------- + !( 1 | !2 ) +(1 row) + +SELECT '!(1|2)'::query_int; + query_int +------------ + !( 1 | 2 ) +(1 row) + +SELECT '1&2'::query_int; + query_int +----------- + 1 & 2 +(1 row) + +SELECT '!1&2'::query_int; + query_int +----------- + !1 & 2 +(1 row) + +SELECT '1&!2'::query_int; + query_int +----------- + 1 & !2 +(1 row) + +SELECT '!1&!2'::query_int; + query_int +----------- + !1 & !2 +(1 row) + +SELECT '(1&2)'::query_int; + query_int +----------- + 1 & 2 +(1 row) + +SELECT '1&(2)'::query_int; + query_int +----------- + 1 & 2 +(1 row) + +SELECT '!(1)&2'::query_int; + query_int +----------- + !1 & 2 +(1 row) + +SELECT '!(1&2)'::query_int; + query_int +------------ + !( 1 & 2 ) +(1 row) + +SELECT '1|2&3'::query_int; + query_int +----------- + 1 | 2 & 3 +(1 row) + +SELECT '1|(2&3)'::query_int; + query_int +----------- + 1 | 2 & 3 +(1 row) + +SELECT '(1|2)&3'::query_int; + query_int +--------------- + ( 1 | 2 ) & 3 +(1 row) + +SELECT '1|2&!3'::query_int; + query_int +------------ + 1 | 2 & !3 +(1 row) + +SELECT '1|!2&3'::query_int; + query_int +------------ + 1 | !2 & 3 +(1 row) + +SELECT '!1|2&3'::query_int; + query_int +------------ + !1 | 2 & 3 +(1 row) + +SELECT '!1|(2&3)'::query_int; + query_int +------------ + !1 | 2 & 3 +(1 row) + +SELECT '!(1|2)&3'::query_int; + query_int +---------------- + !( 1 | 2 ) & 3 +(1 row) + +SELECT '(!1|2)&3'::query_int; + query_int +---------------- + ( !1 | 2 ) & 3 +(1 row) + +SELECT '1|(2|(4|(5|6)))'::query_int; + query_int +------------------------------- + 1 | ( 2 | ( 4 | ( 5 | 6 ) ) ) +(1 row) + +SELECT '1|2|4|5|6'::query_int; + query_int +------------------------------- + ( ( ( 1 | 2 ) | 4 ) | 5 ) | 6 +(1 row) + +SELECT '1&(2&(4&(5&6)))'::query_int; + query_int +------------------- + 1 & 2 & 4 & 5 & 6 +(1 row) + +SELECT '1&2&4&5&6'::query_int; + query_int +------------------- + 1 & 2 & 4 & 5 & 6 +(1 row) + +SELECT '1&(2&(4&(5|6)))'::query_int; + query_int +----------------------- + 1 & 2 & 4 & ( 5 | 6 ) +(1 row) + +SELECT '1&(2&(4&(5|!6)))'::query_int; + query_int +------------------------ + 1 & 2 & 4 & ( 5 | !6 ) +(1 row) + +CREATE TABLE test__int( a int[] ); +\copy test__int from 'data/test__int.data' +ANALYZE test__int; +SELECT count(*) from test__int WHERE a && '{23,50}'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23|50'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{23,50}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23&50'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; + count +------- + 10 +(1 row) + +SELECT count(*) from test__int WHERE a = '{73,23,20}'; + count +------- + 1 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '50&68'; + count +------- + 9 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '20 | !21'; + count +------- + 6567 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + count +------- + 6344 +(1 row) + +SET enable_seqscan = off; -- not all of these would use index by default +CREATE INDEX text_idx on test__int using gist ( a gist__int_ops ); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +CREATE INDEX text_idx on test__int using gist ( a gist__intbig_ops ); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +CREATE INDEX text_idx on test__int using gin ( a gin__int_ops ); +ERROR: operator class "gin__int_ops" does not exist for access method "ybgin" +RESET enable_seqscan; diff --git a/contrib/intarray/sql/yb.port._int.sql b/contrib/intarray/sql/yb.port._int.sql new file mode 100644 index 0000000000000..1882a8deb7cb4 --- /dev/null +++ b/contrib/intarray/sql/yb.port._int.sql @@ -0,0 +1,103 @@ +CREATE EXTENSION intarray; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + +SELECT intset(1234); +SELECT icount('{1234234,234234}'); +SELECT sort('{1234234,-30,234234}'); +SELECT sort('{1234234,-30,234234}','asc'); +SELECT sort('{1234234,-30,234234}','desc'); +SELECT sort_asc('{1234234,-30,234234}'); +SELECT sort_desc('{1234234,-30,234234}'); +SELECT uniq('{1234234,-30,-30,234234,-30}'); +SELECT uniq(sort_asc('{1234234,-30,-30,234234,-30}')); +SELECT idx('{1234234,-30,-30,234234,-30}',-30); +SELECT subarray('{1234234,-30,-30,234234,-30}',2,3); +SELECT subarray('{1234234,-30,-30,234234,-30}',-1,1); +SELECT subarray('{1234234,-30,-30,234234,-30}',0,-1); + +SELECT #'{1234234,234234}'::int[]; +SELECT '{123,623,445}'::int[] + 1245; +SELECT '{123,623,445}'::int[] + 445; +SELECT '{123,623,445}'::int[] + '{1245,87,445}'; +SELECT '{123,623,445}'::int[] - 623; +SELECT '{123,623,445}'::int[] - '{1623,623}'; +SELECT '{123,623,445}'::int[] | 623; +SELECT '{123,623,445}'::int[] | 1623; +SELECT '{123,623,445}'::int[] | '{1623,623}'; +SELECT '{123,623,445}'::int[] & '{1623,623}'; +SELECT '{-1,3,1}'::int[] & '{1,2}'; +SELECT '{1}'::int[] & '{2}'::int[]; +SELECT array_dims('{1}'::int[] & '{2}'::int[]); +SELECT ('{1}'::int[] & '{2}'::int[]) = '{}'::int[]; +SELECT ('{}'::int[] & '{}'::int[]) = '{}'::int[]; + + +--test query_int +SELECT '1'::query_int; +SELECT ' 1'::query_int; +SELECT '1 '::query_int; +SELECT ' 1 '::query_int; +SELECT ' ! 1 '::query_int; +SELECT '!1'::query_int; +SELECT '1|2'::query_int; +SELECT '1|!2'::query_int; +SELECT '!1|2'::query_int; +SELECT '!1|!2'::query_int; +SELECT '!(!1|!2)'::query_int; +SELECT '!(!1|2)'::query_int; +SELECT '!(1|!2)'::query_int; +SELECT '!(1|2)'::query_int; +SELECT '1&2'::query_int; +SELECT '!1&2'::query_int; +SELECT '1&!2'::query_int; +SELECT '!1&!2'::query_int; +SELECT '(1&2)'::query_int; +SELECT '1&(2)'::query_int; +SELECT '!(1)&2'::query_int; +SELECT '!(1&2)'::query_int; +SELECT '1|2&3'::query_int; +SELECT '1|(2&3)'::query_int; +SELECT '(1|2)&3'::query_int; +SELECT '1|2&!3'::query_int; +SELECT '1|!2&3'::query_int; +SELECT '!1|2&3'::query_int; +SELECT '!1|(2&3)'::query_int; +SELECT '!(1|2)&3'::query_int; +SELECT '(!1|2)&3'::query_int; +SELECT '1|(2|(4|(5|6)))'::query_int; +SELECT '1|2|4|5|6'::query_int; +SELECT '1&(2&(4&(5&6)))'::query_int; +SELECT '1&2&4&5&6'::query_int; +SELECT '1&(2&(4&(5|6)))'::query_int; +SELECT '1&(2&(4&(5|!6)))'::query_int; + + +CREATE TABLE test__int( a int[] ); +\copy test__int from 'data/test__int.data' +ANALYZE test__int; + +SELECT count(*) from test__int WHERE a && '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23|50'; +SELECT count(*) from test__int WHERE a @> '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23&50'; +SELECT count(*) from test__int WHERE a @> '{20,23}'; +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; +SELECT count(*) from test__int WHERE a = '{73,23,20}'; +SELECT count(*) from test__int WHERE a @@ '50&68'; +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; +SELECT count(*) from test__int WHERE a @@ '20 | !21'; +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + +SET enable_seqscan = off; -- not all of these would use index by default + +CREATE INDEX text_idx on test__int using gist ( a gist__int_ops ); + +CREATE INDEX text_idx on test__int using gist ( a gist__intbig_ops ); + +CREATE INDEX text_idx on test__int using gin ( a gin__int_ops ); +RESET enable_seqscan; diff --git a/contrib/intarray/yb_schedule b/contrib/intarray/yb_schedule new file mode 100644 index 0000000000000..5650ef8f995af --- /dev/null +++ b/contrib/intarray/yb_schedule @@ -0,0 +1,3 @@ +# contrib/intarray/yb_schedule + +test: yb.port._int diff --git a/contrib/isn/expected/yb.port.isn.out b/contrib/isn/expected/yb.port.isn.out new file mode 100644 index 0000000000000..4c5b48e5d5993 --- /dev/null +++ b/contrib/isn/expected/yb.port.isn.out @@ -0,0 +1,231 @@ +-- +-- Test ISN extension +-- +CREATE EXTENSION isn; +-- Check whether any of our opclasses fail amvalidate +-- ... they will, because of missing cross-type operators +SELECT amname, opcname +FROM (SELECT amname, opcname, opc.oid + FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod + WHERE opc.oid >= 16384 + ORDER BY 1, 2 OFFSET 0) ss +WHERE NOT amvalidate(oid); +ERROR: cannot validate operator family without ordered data +-- +-- test valid conversions +-- +SELECT '9780123456786'::EAN13, -- old book + '9790123456785'::EAN13, -- music + '9791234567896'::EAN13, -- new book + '9771234567898'::EAN13, -- serial + '0123456789012'::EAN13, -- upc + '1234567890128'::EAN13; + ean13 | ean13 | ean13 | ean13 | ean13 | ean13 +-------------------+-------------------+-----------------+-------------------+-----------------+----------------- + 978-0-12-345678-6 | 979-0-1234-5678-5 | 979-123456789-6 | 977-1234-567-89-8 | 012-345678901-2 | 123-456789012-8 +(1 row) + +SELECT '9780123456786'::ISBN, + '123456789X'::ISBN, + '9780123456786'::ISBN13::ISBN, + '9780123456786'::EAN13::ISBN; + isbn | isbn | isbn | isbn +---------------+---------------+---------------+--------------- + 0-12-345678-9 | 1-234-56789-X | 0-12-345678-9 | 0-12-345678-9 +(1 row) + +SELECT -- new books, shown as ISBN13 even for ISBN... + '9791234567896'::ISBN, + '9791234567896'::ISBN13::ISBN, + '9791234567896'::EAN13::ISBN; + isbn | isbn | isbn +-----------------+-----------------+----------------- + 979-123456789-6 | 979-123456789-6 | 979-123456789-6 +(1 row) + +SELECT '9780123456786'::ISBN13, + '123456789X'::ISBN13, + '9791234567896'::ISBN13, + '9791234567896'::EAN13::ISBN13; + isbn13 | isbn13 | isbn13 | isbn13 +-------------------+-------------------+-----------------+----------------- + 978-0-12-345678-6 | 978-1-234-56789-7 | 979-123456789-6 | 979-123456789-6 +(1 row) + +SELECT '9790123456785'::ISMN, + '9790123456785'::EAN13::ISMN, + 'M123456785'::ISMN, + 'M-1234-5678-5'::ISMN; + ismn | ismn | ismn | ismn +---------------+---------------+---------------+--------------- + M-1234-5678-5 | M-1234-5678-5 | M-1234-5678-5 | M-1234-5678-5 +(1 row) + +SELECT '9790123456785'::ISMN13, + 'M123456785'::ISMN13, + 'M-1234-5678-5'::ISMN13; + ismn13 | ismn13 | ismn13 +-------------------+-------------------+------------------- + 979-0-1234-5678-5 | 979-0-1234-5678-5 | 979-0-1234-5678-5 +(1 row) + +SELECT '9771234567003'::ISSN, + '12345679'::ISSN; + issn | issn +-----------+----------- + 1234-5679 | 1234-5679 +(1 row) + +SELECT '9771234567003'::ISSN13, + '12345679'::ISSN13, + '9771234567898'::ISSN13, + '9771234567898'::EAN13::ISSN13; + issn13 | issn13 | issn13 | issn13 +-------------------+-------------------+-------------------+------------------- + 977-1234-567-00-3 | 977-1234-567-00-3 | 977-1234-567-89-8 | 977-1234-567-89-8 +(1 row) + +SELECT '0123456789012'::UPC, + '0123456789012'::EAN13::UPC; + upc | upc +--------------+-------------- + 123456789012 | 123456789012 +(1 row) + +-- +-- test invalid checksums +-- +SELECT '1234567890'::ISBN; +ERROR: invalid check digit for ISBN number: "1234567890", should be X +LINE 1: SELECT '1234567890'::ISBN; + ^ +SELECT 'M123456780'::ISMN; +ERROR: invalid check digit for ISMN number: "M123456780", should be 5 +LINE 1: SELECT 'M123456780'::ISMN; + ^ +SELECT '12345670'::ISSN; +ERROR: invalid check digit for ISSN number: "12345670", should be 9 +LINE 1: SELECT '12345670'::ISSN; + ^ +SELECT '9780123456780'::ISBN; +ERROR: invalid check digit for ISBN number: "9780123456780", should be 6 +LINE 1: SELECT '9780123456780'::ISBN; + ^ +SELECT '9791234567890'::ISBN13; +ERROR: invalid check digit for ISBN number: "9791234567890", should be 6 +LINE 1: SELECT '9791234567890'::ISBN13; + ^ +SELECT '0123456789010'::UPC; +ERROR: invalid check digit for UPC number: "0123456789010", should be 2 +LINE 1: SELECT '0123456789010'::UPC; + ^ +SELECT '1234567890120'::EAN13; +ERROR: invalid check digit for EAN13 number: "1234567890120", should be 8 +LINE 1: SELECT '1234567890120'::EAN13; + ^ +-- +-- test invalid conversions +-- +SELECT '9790123456785'::ISBN; -- not a book +ERROR: cannot cast ISMN to ISBN for number: "9790123456785" +LINE 1: SELECT '9790123456785'::ISBN; + ^ +SELECT '9771234567898'::ISBN; -- not a book +ERROR: cannot cast ISSN to ISBN for number: "9771234567898" +LINE 1: SELECT '9771234567898'::ISBN; + ^ +SELECT '0123456789012'::ISBN; -- not a book +ERROR: cannot cast UPC to ISBN for number: "0123456789012" +LINE 1: SELECT '0123456789012'::ISBN; + ^ +SELECT '9790123456785'::ISBN13; -- not a book +ERROR: cannot cast ISMN to ISBN for number: "9790123456785" +LINE 1: SELECT '9790123456785'::ISBN13; + ^ +SELECT '9771234567898'::ISBN13; -- not a book +ERROR: cannot cast ISSN to ISBN for number: "9771234567898" +LINE 1: SELECT '9771234567898'::ISBN13; + ^ +SELECT '0123456789012'::ISBN13; -- not a book +ERROR: cannot cast UPC to ISBN for number: "0123456789012" +LINE 1: SELECT '0123456789012'::ISBN13; + ^ +SELECT '9780123456786'::ISMN; -- not music +ERROR: cannot cast ISBN to ISMN for number: "9780123456786" +LINE 1: SELECT '9780123456786'::ISMN; + ^ +SELECT '9771234567898'::ISMN; -- not music +ERROR: cannot cast ISSN to ISMN for number: "9771234567898" +LINE 1: SELECT '9771234567898'::ISMN; + ^ +SELECT '9791234567896'::ISMN; -- not music +ERROR: cannot cast ISBN to ISMN for number: "9791234567896" +LINE 1: SELECT '9791234567896'::ISMN; + ^ +SELECT '0123456789012'::ISMN; -- not music +ERROR: cannot cast UPC to ISMN for number: "0123456789012" +LINE 1: SELECT '0123456789012'::ISMN; + ^ +SELECT '9780123456786'::ISSN; -- not serial +ERROR: cannot cast ISBN to ISSN for number: "9780123456786" +LINE 1: SELECT '9780123456786'::ISSN; + ^ +SELECT '9790123456785'::ISSN; -- not serial +ERROR: cannot cast ISMN to ISSN for number: "9790123456785" +LINE 1: SELECT '9790123456785'::ISSN; + ^ +SELECT '9791234567896'::ISSN; -- not serial +ERROR: cannot cast ISBN to ISSN for number: "9791234567896" +LINE 1: SELECT '9791234567896'::ISSN; + ^ +SELECT '0123456789012'::ISSN; -- not serial +ERROR: cannot cast UPC to ISSN for number: "0123456789012" +LINE 1: SELECT '0123456789012'::ISSN; + ^ +SELECT '9780123456786'::UPC; -- not a product +ERROR: cannot cast ISBN to UPC for number: "9780123456786" +LINE 1: SELECT '9780123456786'::UPC; + ^ +SELECT '9771234567898'::UPC; -- not a product +ERROR: cannot cast ISSN to UPC for number: "9771234567898" +LINE 1: SELECT '9771234567898'::UPC; + ^ +SELECT '9790123456785'::UPC; -- not a product +ERROR: cannot cast ISMN to UPC for number: "9790123456785" +LINE 1: SELECT '9790123456785'::UPC; + ^ +SELECT '9791234567896'::UPC; -- not a product +ERROR: cannot cast ISBN to UPC for number: "9791234567896" +LINE 1: SELECT '9791234567896'::UPC; + ^ +SELECT 'postgresql...'::EAN13; +ERROR: invalid input syntax for EAN13 number: "postgresql..." +LINE 1: SELECT 'postgresql...'::EAN13; + ^ +SELECT 'postgresql...'::ISBN; +ERROR: invalid input syntax for ISBN number: "postgresql..." +LINE 1: SELECT 'postgresql...'::ISBN; + ^ +SELECT 9780123456786::EAN13; +ERROR: cannot cast type bigint to ean13 +LINE 1: SELECT 9780123456786::EAN13; + ^ +SELECT 9780123456786::ISBN; +ERROR: cannot cast type bigint to isbn +LINE 1: SELECT 9780123456786::ISBN; + ^ +-- +-- test some comparisons, must yield true +-- +SELECT '12345679'::ISSN = '9771234567003'::EAN13 AS "ok", + 'M-1234-5678-5'::ISMN = '9790123456785'::EAN13 AS "ok", + '9791234567896'::EAN13 != '123456789X'::ISBN AS "nope"; + ok | ok | nope +----+----+------ + t | t | t +(1 row) + +-- +-- cleanup +-- +DROP EXTENSION isn; diff --git a/contrib/isn/isn.c b/contrib/isn/isn.c index 7493a2c60fa19..74d752b381e9f 100644 --- a/contrib/isn/isn.c +++ b/contrib/isn/isn.c @@ -851,6 +851,7 @@ string2ean(const char *str, bool errorOK, ean13 *result, case UPC: buf[2] = '0'; valid = (valid && ((rcheck = checkdig(buf + 2, 13)) == check || magic)); + yb_switch_fallthrough(); default: break; } diff --git a/contrib/isn/sql/yb.port.isn.sql b/contrib/isn/sql/yb.port.isn.sql new file mode 100644 index 0000000000000..71577d5590f87 --- /dev/null +++ b/contrib/isn/sql/yb.port.isn.sql @@ -0,0 +1,113 @@ +-- +-- Test ISN extension +-- + +CREATE EXTENSION isn; + +-- Check whether any of our opclasses fail amvalidate +-- ... they will, because of missing cross-type operators +SELECT amname, opcname +FROM (SELECT amname, opcname, opc.oid + FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod + WHERE opc.oid >= 16384 + ORDER BY 1, 2 OFFSET 0) ss +WHERE NOT amvalidate(oid); + +-- +-- test valid conversions +-- +SELECT '9780123456786'::EAN13, -- old book + '9790123456785'::EAN13, -- music + '9791234567896'::EAN13, -- new book + '9771234567898'::EAN13, -- serial + '0123456789012'::EAN13, -- upc + '1234567890128'::EAN13; + +SELECT '9780123456786'::ISBN, + '123456789X'::ISBN, + '9780123456786'::ISBN13::ISBN, + '9780123456786'::EAN13::ISBN; + +SELECT -- new books, shown as ISBN13 even for ISBN... + '9791234567896'::ISBN, + '9791234567896'::ISBN13::ISBN, + '9791234567896'::EAN13::ISBN; + +SELECT '9780123456786'::ISBN13, + '123456789X'::ISBN13, + '9791234567896'::ISBN13, + '9791234567896'::EAN13::ISBN13; + +SELECT '9790123456785'::ISMN, + '9790123456785'::EAN13::ISMN, + 'M123456785'::ISMN, + 'M-1234-5678-5'::ISMN; + +SELECT '9790123456785'::ISMN13, + 'M123456785'::ISMN13, + 'M-1234-5678-5'::ISMN13; + +SELECT '9771234567003'::ISSN, + '12345679'::ISSN; + +SELECT '9771234567003'::ISSN13, + '12345679'::ISSN13, + '9771234567898'::ISSN13, + '9771234567898'::EAN13::ISSN13; + +SELECT '0123456789012'::UPC, + '0123456789012'::EAN13::UPC; + +-- +-- test invalid checksums +-- +SELECT '1234567890'::ISBN; +SELECT 'M123456780'::ISMN; +SELECT '12345670'::ISSN; +SELECT '9780123456780'::ISBN; +SELECT '9791234567890'::ISBN13; +SELECT '0123456789010'::UPC; +SELECT '1234567890120'::EAN13; + +-- +-- test invalid conversions +-- +SELECT '9790123456785'::ISBN; -- not a book +SELECT '9771234567898'::ISBN; -- not a book +SELECT '0123456789012'::ISBN; -- not a book + +SELECT '9790123456785'::ISBN13; -- not a book +SELECT '9771234567898'::ISBN13; -- not a book +SELECT '0123456789012'::ISBN13; -- not a book + +SELECT '9780123456786'::ISMN; -- not music +SELECT '9771234567898'::ISMN; -- not music +SELECT '9791234567896'::ISMN; -- not music +SELECT '0123456789012'::ISMN; -- not music + +SELECT '9780123456786'::ISSN; -- not serial +SELECT '9790123456785'::ISSN; -- not serial +SELECT '9791234567896'::ISSN; -- not serial +SELECT '0123456789012'::ISSN; -- not serial + +SELECT '9780123456786'::UPC; -- not a product +SELECT '9771234567898'::UPC; -- not a product +SELECT '9790123456785'::UPC; -- not a product +SELECT '9791234567896'::UPC; -- not a product + +SELECT 'postgresql...'::EAN13; +SELECT 'postgresql...'::ISBN; +SELECT 9780123456786::EAN13; +SELECT 9780123456786::ISBN; + +-- +-- test some comparisons, must yield true +-- +SELECT '12345679'::ISSN = '9771234567003'::EAN13 AS "ok", + 'M-1234-5678-5'::ISMN = '9790123456785'::EAN13 AS "ok", + '9791234567896'::EAN13 != '123456789X'::ISBN AS "nope"; + +-- +-- cleanup +-- +DROP EXTENSION isn; diff --git a/contrib/isn/yb_schedule b/contrib/isn/yb_schedule new file mode 100644 index 0000000000000..bfdbdae05cdc0 --- /dev/null +++ b/contrib/isn/yb_schedule @@ -0,0 +1,3 @@ +# contrib/isn/yb_schedule + +test: yb.port.isn diff --git a/contrib/lo/lo.c b/contrib/lo/lo.c index 457be26c4edfe..85956e581945b 100644 --- a/contrib/lo/lo.c +++ b/contrib/lo/lo.c @@ -74,7 +74,8 @@ lo_manage(PG_FUNCTION_ARGS) * object associated with the original value is unlinked. */ if (newtuple != NULL && - bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, trigdata->tg_updatedcols)) + bms_is_member(attnum - YBGetFirstLowInvalidAttributeNumber(trigdata->tg_relation), + trigdata->tg_updatedcols)) { char *orig = SPI_getvalue(trigtuple, tupdesc, attnum); char *newv = SPI_getvalue(newtuple, tupdesc, attnum); diff --git a/contrib/passwordcheck/.gitignore b/contrib/passwordcheck/.gitignore index 5dcb3ff972350..491e4120e4311 100644 --- a/contrib/passwordcheck/.gitignore +++ b/contrib/passwordcheck/.gitignore @@ -1,4 +1,2 @@ -# Generated subdirectories -/log/ +# Ignore test paths /results/ -/tmp_check/ diff --git a/contrib/passwordcheck/Makefile b/contrib/passwordcheck/Makefile index 006735a9690ff..aacc44c481e4e 100644 --- a/contrib/passwordcheck/Makefile +++ b/contrib/passwordcheck/Makefile @@ -1,17 +1,16 @@ # contrib/passwordcheck/Makefile MODULE_big = passwordcheck -OBJS = \ - $(WIN32RES) \ - passwordcheck.o +OBJS = passwordcheck.o $(WIN32RES) PGFILEDESC = "passwordcheck - strengthen user password checks" +REGRESS = passwordcheck + # uncomment the following two lines to enable cracklib support # PG_CPPFLAGS = -DUSE_CRACKLIB '-DCRACKLIB_DICTPATH="/usr/lib/cracklib_dict"' # SHLIB_LINK = -lcrack -REGRESS = passwordcheck - +# YB note: USE_PGXS is by default 0 in contrib hence use Makefile.global ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) diff --git a/contrib/passwordcheck/README b/contrib/passwordcheck/README new file mode 100644 index 0000000000000..d1b57a0dc8b43 --- /dev/null +++ b/contrib/passwordcheck/README @@ -0,0 +1,62 @@ +passwordcheck_extra +=================== + +Development +----------- + +passwordcheck_extra is a fork of passwordcheck, contrib module to check +a password quality using a fork of PostgreSQL core. + +There are two ways to compile and install the code: +1) Copy it as contrib/passwordcheck_extra in PostgreSQL code and use the +following command: +make install +2) Include PostgreSQL libraries in LD_LIBRARY_PATH and use the following +command: +make USE_PGXS=1 +make USE_PGXS=1 install + +Regression tests can be run as follows: +1) Module copied in contrib of PostgreSQL: +make check +make installcheck # Run on existing server +2) Module managed independently, needs a server already running. +make installcheck USE_PGXS=1 # Run on existing server + +In order to install it, install the library to server, add the following +parameter value to postgresql.conf and restart server. +shared_preload_libraries = '$libdir/passwordcheck_extra' + +Features +-------- + +This module strengthens the minimum password requirement it should have +at creation with a user-defined policy: +- passwordcheck_extra.special_chars, to define a list of special characters + with the password needing at least one. Default is "!@#$%^&*()_+{}|<>?=". +- passwordcheck_extra.restrict_lower, to enforce the use of at least one + lower-case character. +- passwordcheck_extra.restrict_upper, to enforce the use of at least one + upper-case character. +- passwordcheck_extra.restrict_numbers, to enforce the use of at least + one number. +- passwordcheck_extra.restrict_special, to enforce the use of at least + one special character listed in \"passwordcheck_extra.special_chars\". +- passwordcheck_extra.minimum_length, minimum length of password allowed. + Default is 8, which likely sucks. +- passwordcheck_extra.maximum_length, maximum length of password allowed. + Default is 15, which definitely sucks, but it is useful for tests. + +YB note: +passwordcheck has been replaced by passwordcheck_extra. +However the name of the plugin continues to be passwordcheck because the team +decided that it will be confusing to have two plugins for the same function. + +The steps followed to replace the plugin are: + +* Choose commit referred below as subsequent commits add support for PG15. +* Overwrite passwordcheck.c with passwordcheck_extra.c (using cp) +* Remove all occurrences of "_extra" as the name of the plugin is unchanged. + +Tests from passwordcheck (with some changes) and passwordcheck_extra are also +run. diff --git a/contrib/passwordcheck/expected/passwordcheck_extra.out b/contrib/passwordcheck/expected/passwordcheck_extra.out new file mode 100644 index 0000000000000..9da4282987887 --- /dev/null +++ b/contrib/passwordcheck/expected/passwordcheck_extra.out @@ -0,0 +1,84 @@ +-- Load the extension to enable the tests +LOAD 'passwordcheck_extra'; +-- Restrictive policy +SET passwordcheck_extra.minimum_length TO 8; +SET passwordcheck_extra.maximum_length TO 15; +SET passwordcheck_extra.special_chars TO '%$?@'; +SET passwordcheck_extra.restrict_lower TO true; +SET passwordcheck_extra.restrict_upper TO true; +SET passwordcheck_extra.restrict_numbers TO true; +-- Check password policy in place +-- Password too short +CREATE ROLE regress_pwd_foo PASSWORD '01234'; +ERROR: password is too short +-- Password too long +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123456'; +ERROR: password is too long +-- Invalid characters +CREATE ROLE regress_pwd_foo PASSWORD '```````````````'; +ERROR: password contains invalid characters +-- Three categories missing +-- Lower-case, upper-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '012345678901234'; +ERROR: Incorrect password format: lower-case character missing, upper-case character missing, special character missing (needs to be one listed in "%$?@") +-- Number, upper-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmno'; +ERROR: Incorrect password format: upper-case character missing, number missing, special character missing (needs to be one listed in "%$?@") +-- Number, lower-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMNO'; +ERROR: Incorrect password format: lower-case character missing, number missing, special character missing (needs to be one listed in "%$?@") +-- Number, lower-case, upper-case character missing +CREATE ROLE regress_pwd_foo PASSWORD '%%%%%%%%%%%%%%%'; +ERROR: Incorrect password format: lower-case character missing, upper-case character missing, number missing +-- Two categories missing +-- Number, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmnA'; +ERROR: Incorrect password format: number missing, special character missing (needs to be one listed in "%$?@") +-- Upper-case character, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123a'; +ERROR: Incorrect password format: upper-case character missing, special character missing (needs to be one listed in "%$?@") +-- Lower-case character, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123A'; +ERROR: Incorrect password format: lower-case character missing, special character missing (needs to be one listed in "%$?@") +-- Number, upper case missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmn%'; +ERROR: Incorrect password format: upper-case character missing, number missing +-- Number, lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMN%'; +ERROR: Incorrect password format: lower-case character missing, number missing +-- Upper-case, lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123%'; +ERROR: Incorrect password format: lower-case character missing, upper-case character missing +-- One category missing +-- Special character missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012aA'; +ERROR: Incorrect password format: special character missing (needs to be one listed in "%$?@") +-- Upper-case missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012a%'; +ERROR: Incorrect password format: upper-case character missing +-- Lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012A%'; +ERROR: Incorrect password format: lower-case character missing +-- Number missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMa%'; +ERROR: Incorrect password format: number missing +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD '012345678901Aa%'; +DROP ROLE regress_pwd_foo; +-- Policy less restrictive +SET passwordcheck_extra.restrict_lower TO false; +SET passwordcheck_extra.restrict_upper TO false; +SET passwordcheck_extra.restrict_numbers TO false; +SET passwordcheck_extra.minimum_length TO 1; +SET passwordcheck_extra.maximum_length TO 100; +-- Special character missing +CREATE ROLE regress_pwd_foo PASSWORD '012345678901Aa'; +ERROR: Incorrect password format: special character missing (needs to be one listed in "%$?@") +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD '@%'; +DROP ROLE regress_pwd_foo; +-- Even less restrictive policy +SET passwordcheck_extra.restrict_special TO false; +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD 'A'; +DROP ROLE regress_pwd_foo; diff --git a/contrib/passwordcheck/expected/yb.port.passwordcheck.out b/contrib/passwordcheck/expected/yb.port.passwordcheck.out new file mode 100644 index 0000000000000..efcd057c7a1e8 --- /dev/null +++ b/contrib/passwordcheck/expected/yb.port.passwordcheck.out @@ -0,0 +1,24 @@ +-- YB note: LOAD is not ported because The extension is loaded using cmd line flag. +CREATE USER regress_user1; +-- YB Note: In passwordcheck_extra, the default length is 15. However in passwordcheck there was +-- no check for max length. Increase max length to accomodate the tests in this file. +SET passwordcheck.maximum_length TO 30; +-- ok +-- YB Note: The test has changed from upstream because passwordcheck_extra expects +-- an upper case and a number by default. +ALTER USER regress_user1 PASSWORD 'a_N1ce_long_password'; +-- error: too short +ALTER USER regress_user1 PASSWORD 'tooshrt'; +ERROR: password is too short +-- error: contains user name +ALTER USER regress_user1 PASSWORD 'xyzregress_user1'; +ERROR: password must not contain user name +-- error: contains only letters +ALTER USER regress_user1 PASSWORD 'alessnicelongpassword'; +ERROR: Incorrect password format: upper-case character missing, number missing, special character missing (needs to be one listed in "!@#$%^&*()_+{}|<>?=") +-- encrypted ok (password is "secret") +ALTER USER regress_user1 PASSWORD 'md51a44d829a20a23eac686d9f0d258af13'; +-- error: password is user name +ALTER USER regress_user1 PASSWORD 'md5e589150ae7d28f93333afae92b36ef48'; +ERROR: password must not contain user name +DROP USER regress_user1; diff --git a/contrib/passwordcheck/expected/yb.port.passwordcheck_extra.out b/contrib/passwordcheck/expected/yb.port.passwordcheck_extra.out new file mode 100644 index 0000000000000..cfd4004b11ea0 --- /dev/null +++ b/contrib/passwordcheck/expected/yb.port.passwordcheck_extra.out @@ -0,0 +1,83 @@ +-- YB note: LOAD is not ported because The extension is loaded using cmd line flag. +-- Restrictive policy +SET passwordcheck.minimum_length TO 8; +SET passwordcheck.maximum_length TO 15; +SET passwordcheck.special_chars TO '%$?@'; +SET passwordcheck.restrict_lower TO true; +SET passwordcheck.restrict_upper TO true; +SET passwordcheck.restrict_numbers TO true; +-- Check password policy in place +-- Password too short +CREATE ROLE regress_pwd_foo PASSWORD '01234'; +ERROR: password is too short +-- Password too long +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123456'; +ERROR: password is too long +-- Invalid characters +CREATE ROLE regress_pwd_foo PASSWORD '```````````````'; +ERROR: password contains invalid characters +-- Three categories missing +-- Lower-case, upper-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '012345678901234'; +ERROR: Incorrect password format: lower-case character missing, upper-case character missing, special character missing (needs to be one listed in "%$?@") +-- Number, upper-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmno'; +ERROR: Incorrect password format: upper-case character missing, number missing, special character missing (needs to be one listed in "%$?@") +-- Number, lower-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMNO'; +ERROR: Incorrect password format: lower-case character missing, number missing, special character missing (needs to be one listed in "%$?@") +-- Number, lower-case, upper-case character missing +CREATE ROLE regress_pwd_foo PASSWORD '%%%%%%%%%%%%%%%'; +ERROR: Incorrect password format: lower-case character missing, upper-case character missing, number missing +-- Two categories missing +-- Number, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmnA'; +ERROR: Incorrect password format: number missing, special character missing (needs to be one listed in "%$?@") +-- Upper-case character, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123a'; +ERROR: Incorrect password format: upper-case character missing, special character missing (needs to be one listed in "%$?@") +-- Lower-case character, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123A'; +ERROR: Incorrect password format: lower-case character missing, special character missing (needs to be one listed in "%$?@") +-- Number, upper case missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmn%'; +ERROR: Incorrect password format: upper-case character missing, number missing +-- Number, lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMN%'; +ERROR: Incorrect password format: lower-case character missing, number missing +-- Upper-case, lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123%'; +ERROR: Incorrect password format: lower-case character missing, upper-case character missing +-- One category missing +-- Special character missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012aA'; +ERROR: Incorrect password format: special character missing (needs to be one listed in "%$?@") +-- Upper-case missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012a%'; +ERROR: Incorrect password format: upper-case character missing +-- Lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012A%'; +ERROR: Incorrect password format: lower-case character missing +-- Number missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMa%'; +ERROR: Incorrect password format: number missing +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD '012345678901Aa%'; +DROP ROLE regress_pwd_foo; +-- Policy less restrictive +SET passwordcheck.restrict_lower TO false; +SET passwordcheck.restrict_upper TO false; +SET passwordcheck.restrict_numbers TO false; +SET passwordcheck.minimum_length TO 1; +SET passwordcheck.maximum_length TO 100; +-- Special character missing +CREATE ROLE regress_pwd_foo PASSWORD '012345678901Aa'; +ERROR: Incorrect password format: special character missing (needs to be one listed in "%$?@") +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD '@%'; +DROP ROLE regress_pwd_foo; +-- Even less restrictive policy +SET passwordcheck.restrict_special TO false; +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD 'A'; +DROP ROLE regress_pwd_foo; diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c deleted file mode 100644 index 9d8c58ded0922..0000000000000 --- a/contrib/passwordcheck/passwordcheck.c +++ /dev/null @@ -1,150 +0,0 @@ -/*------------------------------------------------------------------------- - * - * passwordcheck.c - * - * - * Copyright (c) 2009-2022, PostgreSQL Global Development Group - * - * Author: Laurenz Albe - * - * IDENTIFICATION - * contrib/passwordcheck/passwordcheck.c - * - *------------------------------------------------------------------------- - */ -#include "postgres.h" - -#include - -#ifdef USE_CRACKLIB -#include -#endif - -#include "commands/user.h" -#include "fmgr.h" -#include "libpq/crypt.h" - -PG_MODULE_MAGIC; - -/* Saved hook value in case of unload */ -static check_password_hook_type prev_check_password_hook = NULL; - -/* passwords shorter than this will be rejected */ -#define MIN_PWD_LENGTH 8 - -extern void _PG_init(void); - -/* - * check_password - * - * performs checks on an encrypted or unencrypted password - * ereport's if not acceptable - * - * username: name of role being created or changed - * password: new password (possibly already encrypted) - * password_type: PASSWORD_TYPE_* code, to indicate if the password is - * in plaintext or encrypted form. - * validuntil_time: password expiration time, as a timestamptz Datum - * validuntil_null: true if password expiration time is NULL - * - * This sample implementation doesn't pay any attention to the password - * expiration time, but you might wish to insist that it be non-null and - * not too far in the future. - */ -static void -check_password(const char *username, - const char *shadow_pass, - PasswordType password_type, - Datum validuntil_time, - bool validuntil_null) -{ - if (prev_check_password_hook) - prev_check_password_hook(username, shadow_pass, - password_type, validuntil_time, - validuntil_null); - - if (password_type != PASSWORD_TYPE_PLAINTEXT) - { - /* - * Unfortunately we cannot perform exhaustive checks on encrypted - * passwords - we are restricted to guessing. (Alternatively, we could - * insist on the password being presented non-encrypted, but that has - * its own security disadvantages.) - * - * We only check for username = password. - */ - const char *logdetail = NULL; - - if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("password must not equal user name"))); - } - else - { - /* - * For unencrypted passwords we can perform better checks - */ - const char *password = shadow_pass; - int pwdlen = strlen(password); - int i; - bool pwd_has_letter, - pwd_has_nonletter; -#ifdef USE_CRACKLIB - const char *reason; -#endif - - /* enforce minimum length */ - if (pwdlen < MIN_PWD_LENGTH) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("password is too short"))); - - /* check if the password contains the username */ - if (strstr(password, username)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("password must not contain user name"))); - - /* check if the password contains both letters and non-letters */ - pwd_has_letter = false; - pwd_has_nonletter = false; - for (i = 0; i < pwdlen; i++) - { - /* - * isalpha() does not work for multibyte encodings but let's - * consider non-ASCII characters non-letters - */ - if (isalpha((unsigned char) password[i])) - pwd_has_letter = true; - else - pwd_has_nonletter = true; - } - if (!pwd_has_letter || !pwd_has_nonletter) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("password must contain both letters and nonletters"))); - -#ifdef USE_CRACKLIB - /* call cracklib to check password */ - if ((reason = FascistCheck(password, CRACKLIB_DICTPATH))) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("password is easily cracked"), - errdetail_log("cracklib diagnostic: %s", reason))); -#endif - } - - /* all checks passed, password is ok */ -} - -/* - * Module initialization function - */ -void -_PG_init(void) -{ - /* activate password checks when the module is loaded */ - prev_check_password_hook = check_password_hook; - check_password_hook = check_password; -} diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c new file mode 120000 index 0000000000000..99e747c4c782c --- /dev/null +++ b/contrib/passwordcheck/passwordcheck.c @@ -0,0 +1 @@ +passwordcheck_extra.c \ No newline at end of file diff --git a/contrib/passwordcheck/passwordcheck_extra.c b/contrib/passwordcheck/passwordcheck_extra.c new file mode 100644 index 0000000000000..9476e076c96d9 --- /dev/null +++ b/contrib/passwordcheck/passwordcheck_extra.c @@ -0,0 +1,299 @@ +/*------------------------------------------------------------------------- + * + * passwordcheck.c + * + * Copyright (c) 2009-2022, PostgreSQL Global Development Group + * + * Author: Laurenz Albe + * + * IDENTIFICATION + * contrib/passwordcheck/passwordcheck.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#ifdef USE_CRACKLIB +#include +#endif + +#include "commands/user.h" +#include "fmgr.h" +#include "common/md5.h" +#include "lib/stringinfo.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC; + +/* GUC variables */ +static int password_min_len = 8; +static int password_max_len = 15; +static bool password_lower_case = true; +static bool password_upper_case = true; +static bool password_special = true; +static bool password_numbers = true; +static char *password_special_chars = "!@#$%^&*()_+{}|<>?="; + +/* + * Flags to check contents of plain text passwords, which allow tracking + * of restrictions more easily. + */ +#define PASSWORD_HAS_LOWER 0x0001 /* Lower-case character */ +#define PASSWORD_HAS_UPPER 0x0002 /* Upper-case character */ +#define PASSWORD_HAS_SPECIAL 0x0004 /* Special character */ +#define PASSWORD_HAS_NUMBER 0x0008 /* Number */ + +extern void _PG_init(void); + +/* + * check_password + * + * performs checks on an encrypted or unencrypted password + * ereport's if not acceptable + * + * username: name of role being created or changed + * password: new password (possibly already encrypted) + * password_type: PASSWORD_TYPE_PLAINTEXT or PASSWORD_TYPE_MD5 (there + * could be other encryption schemes in future) + * validuntil_time: password expiration time, as a timestamptz Datum + * validuntil_null: true if password expiration time is NULL + * + * This sample implementation doesn't pay any attention to the password + * expiration time, but you might wish to insist that it be non-null and + * not too far in the future. + */ +static void +check_password(const char *username, + const char *password, + PasswordType password_type, + Datum validuntil_time, + bool validuntil_null) +{ + int namelen = strlen(username); + int pwdlen = strlen(password); + char encrypted[MD5_PASSWD_LEN + 1]; + int i; + int password_flag = 0; + const char *errstr = NULL; + + switch (password_type) + { + case PASSWORD_TYPE_MD5: + + /* + * Unfortunately we cannot perform exhaustive checks on encrypted + * passwords - we are restricted to guessing. (Alternatively, we + * could insist on the password being presented non-encrypted, but + * that has its own security disadvantages.) + * + * We only check for username = password. + */ + if (!pg_md5_encrypt(username, username, namelen, encrypted, &errstr)) + elog(ERROR, "password encryption failed: %s", errstr); + if (strcmp(password, encrypted) == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must not contain user name"))); + break; + + case PASSWORD_TYPE_PLAINTEXT: + { + StringInfoData buf; + bool set_comma = false; + + /* + * For unencrypted passwords we can perform better checks. + */ + + /* enforce minimum length */ + if (pwdlen < password_min_len) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password is too short"))); + + /* enforce maximum length */ + if (pwdlen > password_max_len) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password is too long"))); + + /* check if the password contains the username */ + if (strstr(password, username)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must not contain user name"))); + + /* Scan password characters and check contents */ + for (i = 0; i < pwdlen; i++) + { + /* Check character validity */ + if (isupper((unsigned char) password[i])) + password_flag |= PASSWORD_HAS_UPPER; + else if (islower((unsigned char) password[i])) + password_flag |= PASSWORD_HAS_LOWER; + else if (isdigit((unsigned char) password[i])) + password_flag |= PASSWORD_HAS_NUMBER; + else if (strchr(password_special_chars, + (unsigned char) password[i]) != NULL) + password_flag |= PASSWORD_HAS_SPECIAL; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password contains invalid characters"))); + } + + /* Initialize error message */ + initStringInfo(&buf); + + /* Lower-case character missing? */ + if ((password_flag & PASSWORD_HAS_LOWER) == 0 && + password_lower_case) + { + appendStringInfo(&buf, "lower-case character missing"); + set_comma = true; + } + + /* Upper-case character missing? */ + if ((password_flag & PASSWORD_HAS_UPPER) == 0 && + password_upper_case) + { + if (set_comma) + appendStringInfo(&buf, ", "); + appendStringInfo(&buf, "upper-case character missing"); + set_comma = true; + } + + /* Number missing? */ + if ((password_flag & PASSWORD_HAS_NUMBER) == 0 && + password_numbers) + { + if (set_comma) + appendStringInfo(&buf, ", "); + appendStringInfo(&buf, "number missing"); + set_comma = true; + } + + /* Special character missing */ + if ((password_flag & PASSWORD_HAS_SPECIAL) == 0 && + password_special) + { + if (set_comma) + appendStringInfo(&buf, ", "); + appendStringInfo(&buf, "special character missing " + "(needs to be one listed in \"%s\")", + password_special_chars); + } + + /* + * Complain with everything lacking if anything has been found. + */ + if (buf.len != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Incorrect password format: %s", + buf.data))); + +#ifdef USE_CRACKLIB + /* call cracklib to check password */ + if (FascistCheck(password, CRACKLIB_DICTPATH)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password is easily cracked"))); +#endif + } + break; + + default: + elog(ERROR, "unrecognized password type: %d", password_type); + break; + } + + /* all checks passed, password is ok */ +} + +/* + * Entry point for parameter loading + */ +static void +passwordcheck_load_params(void) +{ + /* Special character set */ + DefineCustomStringVariable("passwordcheck.special_chars", + "Special characters defined.", + "Default value is \"!@#$%^&*()_+{}|<>?=\".", + &password_special_chars, + "!@#$%^&*()_+{}|<>?=", + PGC_SUSET, + 0, NULL, NULL, NULL); + + /* Restrict use of lower-case characters */ + DefineCustomBoolVariable("passwordcheck.restrict_lower", + "Enforce use of lower-case characters.", + NULL, + &password_lower_case, + true, + PGC_SUSET, + 0, NULL, NULL, NULL); + + /* Restrict use of upper-case characters */ + DefineCustomBoolVariable("passwordcheck.restrict_upper", + "Enforce use of upper-case characters.", + NULL, + &password_upper_case, + true, + PGC_SUSET, + 0, NULL, NULL, NULL); + + /* Restrict use of numbers */ + DefineCustomBoolVariable("passwordcheck.restrict_numbers", + "Enforce use of numbers.", + NULL, + &password_numbers, + true, + PGC_SUSET, + 0, NULL, NULL, NULL); + + /* Restrict use of special characters */ + DefineCustomBoolVariable("passwordcheck.restrict_special", + "Enforce use of special characters.", + NULL, + &password_special, + true, + PGC_SUSET, + 0, NULL, NULL, NULL); + + /* Minimum password length */ + DefineCustomIntVariable("passwordcheck.minimum_length", + "Minimum length of password allowed", + "Default value set to 8.", + &password_min_len, + 8, 1, 10000, + PGC_SUSET, + 0, NULL, NULL, NULL); + + /* Maximum password length */ + DefineCustomIntVariable("passwordcheck.maximum_length", + "Maximum length of password allowed", + "Default value set to 15, which actually sucks.", + &password_max_len, + 15, 1, 10000, + PGC_SUSET, + 0, NULL, NULL, NULL); + + MarkGUCPrefixReserved("passwordcheck_extra"); +} + +/* + * Module initialization function + */ +void +_PG_init(void) +{ + /* Load library parameters */ + passwordcheck_load_params(); + + /* activate password checks when the module is loaded */ + check_password_hook = check_password; +} diff --git a/contrib/passwordcheck/sql/passwordcheck_extra.sql b/contrib/passwordcheck/sql/passwordcheck_extra.sql new file mode 100644 index 0000000000000..30d3dc1914779 --- /dev/null +++ b/contrib/passwordcheck/sql/passwordcheck_extra.sql @@ -0,0 +1,75 @@ +-- Load the extension to enable the tests +LOAD 'passwordcheck_extra'; + +-- Restrictive policy +SET passwordcheck_extra.minimum_length TO 8; +SET passwordcheck_extra.maximum_length TO 15; +SET passwordcheck_extra.special_chars TO '%$?@'; +SET passwordcheck_extra.restrict_lower TO true; +SET passwordcheck_extra.restrict_upper TO true; +SET passwordcheck_extra.restrict_numbers TO true; + +-- Check password policy in place +-- Password too short +CREATE ROLE regress_pwd_foo PASSWORD '01234'; +-- Password too long +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123456'; +-- Invalid characters +CREATE ROLE regress_pwd_foo PASSWORD '```````````````'; + +-- Three categories missing +-- Lower-case, upper-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '012345678901234'; +-- Number, upper-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmno'; +-- Number, lower-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMNO'; +-- Number, lower-case, upper-case character missing +CREATE ROLE regress_pwd_foo PASSWORD '%%%%%%%%%%%%%%%'; + +-- Two categories missing +-- Number, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmnA'; +-- Upper-case character, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123a'; +-- Lower-case character, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123A'; +-- Number, upper case missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmn%'; +-- Number, lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMN%'; +-- Upper-case, lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123%'; + +-- One category missing +-- Special character missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012aA'; +-- Upper-case missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012a%'; +-- Lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012A%'; +-- Number missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMa%'; + +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD '012345678901Aa%'; +DROP ROLE regress_pwd_foo; + +-- Policy less restrictive +SET passwordcheck_extra.restrict_lower TO false; +SET passwordcheck_extra.restrict_upper TO false; +SET passwordcheck_extra.restrict_numbers TO false; +SET passwordcheck_extra.minimum_length TO 1; +SET passwordcheck_extra.maximum_length TO 100; + +-- Special character missing +CREATE ROLE regress_pwd_foo PASSWORD '012345678901Aa'; +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD '@%'; +DROP ROLE regress_pwd_foo; + +-- Even less restrictive policy +SET passwordcheck_extra.restrict_special TO false; +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD 'A'; +DROP ROLE regress_pwd_foo; diff --git a/contrib/passwordcheck/sql/yb.port.passwordcheck.sql b/contrib/passwordcheck/sql/yb.port.passwordcheck.sql new file mode 100644 index 0000000000000..0d89d4f391d4b --- /dev/null +++ b/contrib/passwordcheck/sql/yb.port.passwordcheck.sql @@ -0,0 +1,27 @@ +-- YB note: LOAD is not ported because The extension is loaded using cmd line flag. +CREATE USER regress_user1; +-- YB Note: In passwordcheck_extra, the default length is 15. However in passwordcheck there was +-- no check for max length. Increase max length to accomodate the tests in this file. +SET passwordcheck.maximum_length TO 30; + +-- ok +-- YB Note: The test has changed from upstream because passwordcheck_extra expects +-- an upper case and a number by default. +ALTER USER regress_user1 PASSWORD 'a_N1ce_long_password'; + +-- error: too short +ALTER USER regress_user1 PASSWORD 'tooshrt'; + +-- error: contains user name +ALTER USER regress_user1 PASSWORD 'xyzregress_user1'; + +-- error: contains only letters +ALTER USER regress_user1 PASSWORD 'alessnicelongpassword'; + +-- encrypted ok (password is "secret") +ALTER USER regress_user1 PASSWORD 'md51a44d829a20a23eac686d9f0d258af13'; + +-- error: password is user name +ALTER USER regress_user1 PASSWORD 'md5e589150ae7d28f93333afae92b36ef48'; + +DROP USER regress_user1; diff --git a/contrib/passwordcheck/sql/yb.port.passwordcheck_extra.sql b/contrib/passwordcheck/sql/yb.port.passwordcheck_extra.sql new file mode 100644 index 0000000000000..168fa0de67c77 --- /dev/null +++ b/contrib/passwordcheck/sql/yb.port.passwordcheck_extra.sql @@ -0,0 +1,73 @@ +-- YB note: LOAD is not ported because The extension is loaded using cmd line flag. +-- Restrictive policy +SET passwordcheck.minimum_length TO 8; +SET passwordcheck.maximum_length TO 15; +SET passwordcheck.special_chars TO '%$?@'; +SET passwordcheck.restrict_lower TO true; +SET passwordcheck.restrict_upper TO true; +SET passwordcheck.restrict_numbers TO true; + +-- Check password policy in place +-- Password too short +CREATE ROLE regress_pwd_foo PASSWORD '01234'; +-- Password too long +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123456'; +-- Invalid characters +CREATE ROLE regress_pwd_foo PASSWORD '```````````````'; + +-- Three categories missing +-- Lower-case, upper-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '012345678901234'; +-- Number, upper-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmno'; +-- Number, lower-case, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMNO'; +-- Number, lower-case, upper-case character missing +CREATE ROLE regress_pwd_foo PASSWORD '%%%%%%%%%%%%%%%'; + +-- Two categories missing +-- Number, special character missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmnA'; +-- Upper-case character, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123a'; +-- Lower-case character, special character missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123A'; +-- Number, upper case missing +CREATE ROLE regress_pwd_foo PASSWORD 'abcdefghijklmn%'; +-- Number, lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMN%'; +-- Upper-case, lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD '01234567890123%'; + +-- One category missing +-- Special character missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012aA'; +-- Upper-case missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012a%'; +-- Lower-case missing +CREATE ROLE regress_pwd_foo PASSWORD '0123456789012A%'; +-- Number missing +CREATE ROLE regress_pwd_foo PASSWORD 'ABCDEFGHIJKLMa%'; + +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD '012345678901Aa%'; +DROP ROLE regress_pwd_foo; + +-- Policy less restrictive +SET passwordcheck.restrict_lower TO false; +SET passwordcheck.restrict_upper TO false; +SET passwordcheck.restrict_numbers TO false; +SET passwordcheck.minimum_length TO 1; +SET passwordcheck.maximum_length TO 100; + +-- Special character missing +CREATE ROLE regress_pwd_foo PASSWORD '012345678901Aa'; +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD '@%'; +DROP ROLE regress_pwd_foo; + +-- Even less restrictive policy +SET passwordcheck.restrict_special TO false; +-- Valid password +CREATE ROLE regress_pwd_foo PASSWORD 'A'; +DROP ROLE regress_pwd_foo; diff --git a/contrib/passwordcheck/yb_schedule b/contrib/passwordcheck/yb_schedule new file mode 100644 index 0000000000000..98c6b64e05360 --- /dev/null +++ b/contrib/passwordcheck/yb_schedule @@ -0,0 +1,4 @@ +# contrib/passwordcheck/yb_schedule + +test: yb.port.passwordcheck +test: yb.port.passwordcheck_extra diff --git a/contrib/pg_stat_statements/Makefile b/contrib/pg_stat_statements/Makefile index edc40c8bbfb56..9a7b91efdd351 100644 --- a/contrib/pg_stat_statements/Makefile +++ b/contrib/pg_stat_statements/Makefile @@ -6,13 +6,13 @@ OBJS = \ pg_stat_statements.o EXTENSION = pg_stat_statements -DATA = pg_stat_statements--1.4.sql \ - pg_stat_statements--1.9--1.10.sql pg_stat_statements--1.8--1.9.sql \ - pg_stat_statements--1.7--1.8.sql pg_stat_statements--1.6--1.7.sql \ - pg_stat_statements--1.5--1.6.sql pg_stat_statements--1.4--1.5.sql \ - pg_stat_statements--1.3--1.4.sql pg_stat_statements--1.2--1.3.sql \ - pg_stat_statements--1.1--1.2.sql pg_stat_statements--1.0--1.1.sql +DATA = pg_stat_statements--1.10-yb-2.1.sql \ + pg_stat_statements--1.10-yb-2.0--1.10-yb-2.1.sql \ + pg_stat_statements--1.10-yb-1.0--1.10-yb-2.0.sql \ + pg_stat_statements--1.6-yb-1.0--1.10-yb-1.0.sql \ + pg_stat_statements--1.6--1.6-yb-1.0.sql PGFILEDESC = "pg_stat_statements - execution statistics of SQL statements" +SHLIB_LINK += -L$(YB_BUILD_ROOT)/lib -lserver_process -lyb_pggate_webserver -lhdr_histogram LDFLAGS_SL += $(filter -lm, $(LIBS)) diff --git a/contrib/pg_stat_statements/expected/yb.orig.backfill_queryid.out b/contrib/pg_stat_statements/expected/yb.orig.backfill_queryid.out new file mode 100644 index 0000000000000..85944fa21873a --- /dev/null +++ b/contrib/pg_stat_statements/expected/yb.orig.backfill_queryid.out @@ -0,0 +1,78 @@ +-- Test: BACKFILL INDEX commands are aggregated into a single pg_stat_statements entry per index. +CREATE EXTENSION pg_stat_statements; +SET pg_stat_statements.track_utility = TRUE; +CREATE TABLE backfill_test(k INT PRIMARY KEY, v INT); +INSERT INTO backfill_test SELECT i, i FROM generate_series(1, 5000) AS i; +--Test: there should be exactly one BACKFILL entry. +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +CREATE INDEX ON backfill_test(v); +SELECT COUNT(*) AS num_backfill_entries +FROM pg_stat_statements +WHERE query LIKE 'BACKFILL%'; + num_backfill_entries +---------------------- + 1 +(1 row) + +--Test: query text should be normalized with $N placeholders. +SELECT + query LIKE 'BACKFILL INDEX % WITH $1 READ TIME $2 PARTITION $3' + AS query_is_normalized +FROM pg_stat_statements +WHERE query LIKE 'BACKFILL%'; + query_is_normalized +--------------------- + t +(1 row) + +--Test: each index should have its own separate entry. +CREATE INDEX ON backfill_test(k, v); +SELECT COUNT(*) AS num_backfill_entries +FROM pg_stat_statements +WHERE query LIKE 'BACKFILL%'; + num_backfill_entries +---------------------- + 2 +(1 row) + +--Verify that normal DML normalization is unaffected. +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT * FROM backfill_test WHERE k = 1; + k | v +---+--- + 1 | 1 +(1 row) + +SELECT * FROM backfill_test WHERE k = 2; + k | v +---+--- + 2 | 2 +(1 row) + +SELECT * FROM backfill_test WHERE k = 3; + k | v +---+--- + 3 | 3 +(1 row) + +SELECT COUNT(*), SUM(calls) +FROM pg_stat_statements +WHERE query LIKE 'SELECT %FROM backfill_test WHERE k%'; + count | sum +-------+----- + 1 | 3 +(1 row) + +-- Cleanup. +DROP TABLE backfill_test; +DROP EXTENSION pg_stat_statements; diff --git a/contrib/pg_stat_statements/expected/yb.orig.pg_stat_statements.out b/contrib/pg_stat_statements/expected/yb.orig.pg_stat_statements.out new file mode 100644 index 0000000000000..7ca830ba735b9 --- /dev/null +++ b/contrib/pg_stat_statements/expected/yb.orig.pg_stat_statements.out @@ -0,0 +1,78 @@ +CREATE EXTENSION pg_stat_statements; +-- +-- simple and compound statements +-- +SET pg_stat_statements.track_utility = TRUE; +SET pg_stat_statements.track_planning = TRUE; +-- +-- create / alter user +-- +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +CREATE USER foo PASSWORD 'fooooooo'; +ALTER USER foo PASSWORD 'foo2'; +CREATE ROLE fizzbuzz PASSWORD 'barrr'; +ALTER ROLE fizzbuzz PASSWORD 'barrr2'; +-- Passwords of all lengths must be redacted. +CREATE ROLE minimalpass PASSWORD '1'; +CREATE ROLE smallpass PASSWORD '123'; +CREATE ROLE largepass PASSWORD '123456790123456'; +-- A NULL password must also be redacted. +ALTER ROLE fizzbuzz PASSWORD NULL; +-- Password must be redacted irrespective of its position in the query. +ALTER ROLE fizzbuzz SUPERUSER PASSWORD 'test' NOLOGIN; +-- Test that pgss handles pl/pgSQL statements well. +CREATE FUNCTION returnArg(variadic NUMERIC[]) RETURNS NUMERIC LANGUAGE plpgsql AS $$ +DECLARE + r INTEGER := $1[1]; +BEGIN + RETURN r; +END; +$$; +SELECT returnArg(10, 2); + returnarg +----------- + 10 +(1 row) + +SELECT returnArg(1, 4, 7); + returnarg +----------- + 1 +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +--------------------------------------------------------------------------------------+-------+------ + ALTER ROLE fizzbuzz PASSWORD | 1 | 0 + ALTER ROLE fizzbuzz PASSWORD | 1 | 0 + ALTER ROLE fizzbuzz SUPERUSER PASSWORD | 1 | 0 + ALTER USER foo PASSWORD | 1 | 0 + CREATE FUNCTION returnArg(variadic NUMERIC[]) RETURNS NUMERIC LANGUAGE plpgsql AS $$+| 1 | 0 + DECLARE +| | + r INTEGER := $1[1]; +| | + BEGIN +| | + RETURN r; +| | + END; +| | + $$ | | + CREATE ROLE fizzbuzz PASSWORD | 1 | 0 + CREATE ROLE largepass PASSWORD | 1 | 0 + CREATE ROLE minimalpass PASSWORD | 1 | 0 + CREATE ROLE smallpass PASSWORD | 1 | 0 + CREATE USER foo PASSWORD | 1 | 0 + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0 + SELECT returnArg($1, $2) | 1 | 1 + SELECT returnArg($1, $2, $3) | 1 | 1 +(14 rows) + +DROP USER foo; +DROP ROLE fizzbuzz; +DROP ROLE minimalpass; +DROP ROLE smallpass; +DROP ROLE largepass; +DROP EXTENSION pg_stat_statements; diff --git a/contrib/pg_stat_statements/expected/yb.port.pg_stat_statements.out b/contrib/pg_stat_statements/expected/yb.port.pg_stat_statements.out new file mode 100644 index 0000000000000..37cda5e72ed0e --- /dev/null +++ b/contrib/pg_stat_statements/expected/yb.port.pg_stat_statements.out @@ -0,0 +1,1114 @@ +DROP EXTENSION IF EXISTS pg_stat_statements; +CREATE EXTENSION pg_stat_statements; +-- +-- simple and compound statements +-- +SET pg_stat_statements.track_utility = FALSE; +SET pg_stat_statements.track_planning = TRUE; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT 1 AS "int"; + int +----- + 1 +(1 row) + +SELECT 'hello' + -- multiline + AS "text"; + text +------- + hello +(1 row) + +SELECT 'world' AS "text"; + text +------- + world +(1 row) + +-- transaction +BEGIN; +SELECT 1 AS "int"; + int +----- + 1 +(1 row) + +SELECT 'hello' AS "text"; + text +------- + hello +(1 row) + +COMMIT; +-- compound transaction +BEGIN \; +SELECT 2.0 AS "float" \; +SELECT 'world' AS "text" \; +COMMIT; + float +------- + 2.0 +(1 row) + + text +------- + world +(1 row) + +-- compound with empty statements and spurious leading spacing +\;\; SELECT 3 + 3 \;\;\; SELECT ' ' || ' !' \;\; SELECT 1 + 4 \;; + ?column? +---------- + 6 +(1 row) + + ?column? +---------- + ! +(1 row) + + ?column? +---------- + 5 +(1 row) + +-- non ;-terminated statements +SELECT 1 + 1 + 1 AS "add" \gset +SELECT :add + 1 + 1 AS "add" \; +SELECT :add + 1 + 1 AS "add" \gset + add +----- + 5 +(1 row) + +-- set operator +SELECT 1 AS i UNION SELECT 2 ORDER BY i; + i +--- + 1 + 2 +(2 rows) + +-- ? operator +select '{"a":1, "b":2}'::jsonb ? 'b'; + ?column? +---------- + t +(1 row) + +-- cte +WITH t(f) AS ( + VALUES (1.0), (2.0) +) + SELECT f FROM t ORDER BY f; + f +----- + 1.0 + 2.0 +(2 rows) + +-- prepared statement with parameter +PREPARE pgss_test (int) AS SELECT $1, 'test' LIMIT 1; +EXECUTE pgss_test(1); + ?column? | ?column? +----------+---------- + 1 | test +(1 row) + +DEALLOCATE pgss_test; +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +------------------------------------------------------------------------------+-------+------ + PREPARE pgss_test (int) AS SELECT $1, $2 LIMIT $3 | 1 | 1 + SELECT $1 +| 4 | 4 + -- multiline +| | + AS "text" | | + SELECT $1 + $2 | 2 | 2 + SELECT $1 + $2 + $3 AS "add" | 3 | 3 + SELECT $1 AS "float" | 1 | 1 + SELECT $1 AS "int" | 2 | 2 + SELECT $1 AS i UNION SELECT $2 ORDER BY i | 1 | 2 + SELECT $1 || $2 | 1 | 1 + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0 + WITH t(f) AS ( +| 1 | 2 + VALUES ($1), ($2) +| | + ) +| | + SELECT f FROM t ORDER BY f | | + select $1::jsonb ? $2 | 1 | 1 +(12 rows) + +-- +-- CRUD: INSERT SELECT UPDATE DELETE on test table +-- +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- utility "create table" should not be shown +CREATE TEMP TABLE test (a int, b char(20)); +INSERT INTO test VALUES(generate_series(1, 10), 'aaa'); +UPDATE test SET b = 'bbb' WHERE a > 7; +DELETE FROM test WHERE a > 9; +-- explicit transaction +BEGIN; +UPDATE test SET b = '111' WHERE a = 1 ; +COMMIT; +BEGIN \; +UPDATE test SET b = '222' WHERE a = 2 \; +COMMIT ; +UPDATE test SET b = '333' WHERE a = 3 \; +UPDATE test SET b = '444' WHERE a = 4 ; +BEGIN \; +UPDATE test SET b = '555' WHERE a = 5 \; +UPDATE test SET b = '666' WHERE a = 6 \; +COMMIT ; +-- many INSERT values +INSERT INTO test (a, b) VALUES (1, 'a'), (2, 'b'), (3, 'c'); +-- SELECT with constants +SELECT * FROM test WHERE a > 5 ORDER BY a ; + a | b +---+---------------------- + 6 | 666 + 7 | aaa + 8 | bbb + 9 | bbb +(4 rows) + +SELECT * + FROM test + WHERE a > 9 + ORDER BY a ; + a | b +---+--- +(0 rows) + +-- SELECT without constants +SELECT * FROM test ORDER BY a; + a | b +---+---------------------- + 1 | a + 1 | 111 + 2 | b + 2 | 222 + 3 | c + 3 | 333 + 4 | 444 + 5 | 555 + 6 | 666 + 7 | aaa + 8 | bbb + 9 | bbb +(12 rows) + +-- SELECT with IN clause +SELECT * FROM test WHERE a IN (1, 2, 3, 4, 5); + a | b +---+---------------------- + 1 | 111 + 2 | 222 + 3 | 333 + 4 | 444 + 5 | 555 + 1 | a + 2 | b + 3 | c +(8 rows) + +-- MERGE +-- YB note: ERROR: MERGE not supported yet hence failing. Check after support added. +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED THEN UPDATE SET b = st.b || st.a::text; +ERROR: This statement not supported yet +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +------------------------------------------------------------------------------+-------+------ + DELETE FROM test WHERE a > $1 | 1 | 1 + INSERT INTO test (a, b) VALUES ($1, $2), ($3, $4), ($5, $6) | 1 | 3 + INSERT INTO test VALUES(generate_series($1, $2), $3) | 1 | 10 + SELECT * FROM test ORDER BY a | 1 | 12 + SELECT * FROM test WHERE a > $1 ORDER BY a | 2 | 4 + SELECT * FROM test WHERE a IN ($1, $2, $3, $4, $5) | 1 | 8 + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0 + UPDATE test SET b = $1 WHERE a = $2 | 6 | 6 + UPDATE test SET b = $1 WHERE a > $2 | 1 | 3 +(10 rows) + +-- +-- INSERT, UPDATE, DELETE on test table to validate WAL generation metrics +-- +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- utility "create table" should not be shown +CREATE TABLE pgss_test (a int, b char(20)); +INSERT INTO pgss_test VALUES(generate_series(1, 10), 'aaa'); +UPDATE pgss_test SET b = 'bbb' WHERE a > 7; +DELETE FROM pgss_test WHERE a > 9; +-- DROP test table +SET pg_stat_statements.track_utility = TRUE; +DROP TABLE pgss_test; +SET pg_stat_statements.track_utility = FALSE; +-- Check WAL is generated for the above statements +-- YB Note: PG WAL not used in YB, out might differ from correct one +-- in pg_stat_statements.out. +SELECT query, calls, rows, +wal_bytes > 0 as wal_bytes_generated, +wal_records > 0 as wal_records_generated, +wal_records = rows as wal_records_as_rows +FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows | wal_bytes_generated | wal_records_generated | wal_records_as_rows +-----------------------------------------------------------+-------+------+---------------------+-----------------------+--------------------- + DELETE FROM pgss_test WHERE a > $1 | 1 | 1 | f | f | f + DROP TABLE pgss_test | 1 | 0 | t | t | f + INSERT INTO pgss_test VALUES(generate_series($1, $2), $3) | 1 | 10 | f | f | f + SELECT pg_stat_statements_reset() | 1 | 1 | f | f | f + SELECT query, calls, rows, +| 0 | 0 | f | f | t + wal_bytes > $1 as wal_bytes_generated, +| | | | | + wal_records > $2 as wal_records_generated, +| | | | | + wal_records = rows as wal_records_as_rows +| | | | | + FROM pg_stat_statements ORDER BY query COLLATE "C" | | | | | + SET pg_stat_statements.track_utility = FALSE | 1 | 0 | f | f | t + UPDATE pgss_test SET b = $1 WHERE a > $2 | 1 | 3 | f | f | f +(7 rows) + +-- +-- pg_stat_statements.track = none +-- +SET pg_stat_statements.track = 'none'; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT 1 AS "one"; + one +----- + 1 +(1 row) + +SELECT 1 + 1 AS "two"; + two +----- + 2 +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +-------+-------+------ +(0 rows) + +-- +-- pg_stat_statements.track = top +-- +SET pg_stat_statements.track = 'top'; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +DO LANGUAGE plpgsql $$ +BEGIN + -- this is a SELECT + PERFORM 'hello world'::TEXT; +END; +$$; +-- PL/pgSQL function +CREATE FUNCTION PLUS_TWO(i INTEGER) RETURNS INTEGER AS $$ +DECLARE + r INTEGER; +BEGIN + SELECT (i + 1 + 1.0)::INTEGER INTO r; + RETURN r; +END; $$ LANGUAGE plpgsql; +SELECT PLUS_TWO(3); + plus_two +---------- + 5 +(1 row) + +SELECT PLUS_TWO(7); + plus_two +---------- + 9 +(1 row) + +-- SQL function --- use LIMIT to keep it from being inlined +CREATE FUNCTION PLUS_ONE(i INTEGER) RETURNS INTEGER AS +$$ SELECT (i + 1.0)::INTEGER LIMIT 1 $$ LANGUAGE SQL; +SELECT PLUS_ONE(8); + plus_one +---------- + 9 +(1 row) + +SELECT PLUS_ONE(10); + plus_one +---------- + 11 +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +------------------------------------------------------------------------------+-------+------ + SELECT $1::TEXT | 1 | 1 + SELECT PLUS_ONE($1) | 2 | 2 + SELECT PLUS_TWO($1) | 2 | 2 + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0 +(5 rows) + +-- +-- pg_stat_statements.track = all +-- +SET pg_stat_statements.track = 'all'; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- we drop and recreate the functions to avoid any caching funnies +DROP FUNCTION PLUS_ONE(INTEGER); +DROP FUNCTION PLUS_TWO(INTEGER); +-- PL/pgSQL function +CREATE FUNCTION PLUS_TWO(i INTEGER) RETURNS INTEGER AS $$ +DECLARE + r INTEGER; +BEGIN + SELECT (i + 1 + 1.0)::INTEGER INTO r; + RETURN r; +END; $$ LANGUAGE plpgsql; +SELECT PLUS_TWO(-1); + plus_two +---------- + 1 +(1 row) + +SELECT PLUS_TWO(2); + plus_two +---------- + 4 +(1 row) + +-- SQL function --- use LIMIT to keep it from being inlined +CREATE FUNCTION PLUS_ONE(i INTEGER) RETURNS INTEGER AS +$$ SELECT (i + 1.0)::INTEGER LIMIT 1 $$ LANGUAGE SQL; +SELECT PLUS_ONE(3); + plus_one +---------- + 4 +(1 row) + +SELECT PLUS_ONE(1); + plus_one +---------- + 2 +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +------------------------------------------------------------------------------+-------+------ + SELECT (i + $2 + $3)::INTEGER | 2 | 2 + SELECT (i + $2)::INTEGER LIMIT $3 | 2 | 2 + SELECT PLUS_ONE($1) | 2 | 2 + SELECT PLUS_TWO($1) | 2 | 2 + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0 +(6 rows) + +-- +-- queries with locking clauses +-- +CREATE TABLE pgss_a (id integer PRIMARY KEY); +CREATE TABLE pgss_b (id integer PRIMARY KEY, a_id integer REFERENCES pgss_a); +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- control query +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id; + id | id | a_id +----+----+------ +(0 rows) + +-- test range tables +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_a; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_a, pgss_b; -- matches plain "FOR UPDATE" + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b, pgss_a; + id | id | a_id +----+----+------ +(0 rows) + +-- test strengths +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR NO KEY UPDATE; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR SHARE; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR KEY SHARE; + id | id | a_id +----+----+------ +(0 rows) + +-- test wait policies +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE NOWAIT; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE SKIP LOCKED; + id | id | a_id +----+----+------ +(0 rows) + +SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | query +-------+------------------------------------------------------------------------------------------ + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR KEY SHARE + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR NO KEY UPDATE + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR SHARE + 2 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE NOWAIT + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_a + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b, pgss_a + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE SKIP LOCKED + 0 | SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C" + 1 | SELECT pg_stat_statements_reset() +(12 rows) + +DROP TABLE pgss_a, pgss_b CASCADE; +-- +-- utility commands +-- +SET pg_stat_statements.track_utility = TRUE; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT 1; + ?column? +---------- + 1 +(1 row) + +CREATE INDEX test_b ON test(b); +DROP TABLE test \; +DROP TABLE IF EXISTS test \; +DROP FUNCTION PLUS_ONE(INTEGER); +NOTICE: table "test" does not exist, skipping +DROP TABLE IF EXISTS test \; +DROP TABLE IF EXISTS test \; +DROP FUNCTION IF EXISTS PLUS_ONE(INTEGER); +NOTICE: table "test" does not exist, skipping +NOTICE: table "test" does not exist, skipping +NOTICE: function plus_one(pg_catalog.int4) does not exist, skipping +DROP FUNCTION PLUS_TWO(INTEGER); +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +------------------------------------------------------------------------------+-------+------ + CREATE INDEX test_b ON test(b) | 1 | 0 + DROP FUNCTION IF EXISTS PLUS_ONE(INTEGER) | 1 | 0 + DROP FUNCTION PLUS_ONE(INTEGER) | 1 | 0 + DROP FUNCTION PLUS_TWO(INTEGER) | 1 | 0 + DROP TABLE IF EXISTS test | 3 | 0 + DROP TABLE test | 1 | 0 + SELECT $1 | 1 | 1 + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0 +(9 rows) + +-- +-- Track the total number of rows retrieved or affected by the utility +-- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW, +-- REFRESH MATERIALIZED VIEW and SELECT INTO +-- +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +CREATE TABLE pgss_ctas AS SELECT a, 'ctas' b FROM generate_series(1, 10) a; +SELECT generate_series(1, 10) c INTO pgss_select_into; +COPY pgss_ctas (a, b) FROM STDIN; +CREATE MATERIALIZED VIEW pgss_matv AS SELECT * FROM pgss_ctas; +REFRESH MATERIALIZED VIEW pgss_matv; +BEGIN; +-- YB note: added order by a to correct sorted order for cursor. +DECLARE pgss_cursor CURSOR FOR SELECT * FROM pgss_matv ORDER BY a; +FETCH NEXT pgss_cursor; + a | b +---+------ + 1 | ctas +(1 row) + +FETCH FORWARD 5 pgss_cursor; + a | b +---+------ + 2 | ctas + 3 | ctas + 4 | ctas + 5 | ctas + 6 | ctas +(5 rows) + +FETCH FORWARD ALL pgss_cursor; + a | b +----+------ + 7 | ctas + 8 | ctas + 9 | ctas + 10 | ctas + 11 | copy + 12 | copy + 13 | copy +(7 rows) + +COMMIT; +SELECT query, plans, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | plans | calls | rows +-------------------------------------------------------------------------------------+-------+-------+------ + BEGIN | 0 | 1 | 0 + COMMIT | 0 | 1 | 0 + COPY pgss_ctas (a, b) FROM STDIN | 0 | 1 | 3 + CREATE MATERIALIZED VIEW pgss_matv AS SELECT * FROM pgss_ctas | 0 | 1 | 13 + CREATE TABLE pgss_ctas AS SELECT a, 'ctas' b FROM generate_series(1, 10) a | 0 | 1 | 10 + DECLARE pgss_cursor CURSOR FOR SELECT * FROM pgss_matv ORDER BY a | 0 | 1 | 0 + FETCH FORWARD 5 pgss_cursor | 0 | 1 | 5 + FETCH FORWARD ALL pgss_cursor | 0 | 1 | 7 + FETCH NEXT pgss_cursor | 0 | 1 | 1 + REFRESH MATERIALIZED VIEW pgss_matv | 0 | 1 | 13 + SELECT generate_series(1, 10) c INTO pgss_select_into | 0 | 1 | 10 + SELECT pg_stat_statements_reset() | 0 | 1 | 1 + SELECT query, plans, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 1 | 0 | 0 +(13 rows) + +-- +-- Track user activity and reset them +-- +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +CREATE ROLE regress_stats_user1; +CREATE ROLE regress_stats_user2; +SET ROLE regress_stats_user1; +SELECT 1 AS "ONE"; + ONE +----- + 1 +(1 row) + +SELECT 1+1 AS "TWO"; + TWO +----- + 2 +(1 row) + +RESET ROLE; +SET ROLE regress_stats_user2; +SELECT 1 AS "ONE"; + ONE +----- + 1 +(1 row) + +SELECT 1+1 AS "TWO"; + TWO +----- + 2 +(1 row) + +RESET ROLE; +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +------------------------------------------------------------------------------+-------+------ + CREATE ROLE regress_stats_user1 | 1 | 0 + CREATE ROLE regress_stats_user2 | 1 | 0 + RESET ROLE | 2 | 0 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0 + SET ROLE regress_stats_user1 | 1 | 0 + SET ROLE regress_stats_user2 | 1 | 0 +(11 rows) + +-- +-- Don't reset anything if any of the parameter is NULL +-- +SELECT pg_stat_statements_reset(NULL); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +------------------------------------------------------------------------------+-------+------ + CREATE ROLE regress_stats_user1 | 1 | 0 + CREATE ROLE regress_stats_user2 | 1 | 0 + RESET ROLE | 2 | 0 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT pg_stat_statements_reset($1) | 1 | 1 + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 1 | 11 + SET ROLE regress_stats_user1 | 1 | 0 + SET ROLE regress_stats_user2 | 1 | 0 +(12 rows) + +-- +-- remove query ('SELECT $1+$2 AS "TWO"') executed by regress_stats_user2 +-- in the current_database +-- +SELECT pg_stat_statements_reset( + (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user2'), + (SELECT d.oid FROM pg_database As d where datname = current_database()), + (SELECT s.queryid FROM pg_stat_statements AS s + WHERE s.query = 'SELECT $1+$2 AS "TWO"' LIMIT 1)); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +----------------------------------------------------------------------------------+-------+------ + CREATE ROLE regress_stats_user1 | 1 | 0 + CREATE ROLE regress_stats_user2 | 1 | 0 + RESET ROLE | 2 | 0 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT pg_stat_statements_reset( +| 1 | 1 + (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = $1), +| | + (SELECT d.oid FROM pg_database As d where datname = current_database()),+| | + (SELECT s.queryid FROM pg_stat_statements AS s +| | + WHERE s.query = $2 LIMIT $3)) | | + SELECT pg_stat_statements_reset($1) | 1 | 1 + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 2 | 23 + SET ROLE regress_stats_user1 | 1 | 0 + SET ROLE regress_stats_user2 | 1 | 0 +(12 rows) + +-- +-- remove query ('SELECT $1 AS "ONE"') executed by two users +-- +SELECT pg_stat_statements_reset(0,0,s.queryid) + FROM pg_stat_statements AS s WHERE s.query = 'SELECT $1 AS "ONE"'; + pg_stat_statements_reset +-------------------------- + + +(2 rows) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +----------------------------------------------------------------------------------+-------+------ + CREATE ROLE regress_stats_user1 | 1 | 0 + CREATE ROLE regress_stats_user2 | 1 | 0 + RESET ROLE | 2 | 0 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT pg_stat_statements_reset( +| 1 | 1 + (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = $1), +| | + (SELECT d.oid FROM pg_database As d where datname = current_database()),+| | + (SELECT s.queryid FROM pg_stat_statements AS s +| | + WHERE s.query = $2 LIMIT $3)) | | + SELECT pg_stat_statements_reset($1) | 1 | 1 + SELECT pg_stat_statements_reset($1,$2,s.queryid) +| 1 | 2 + FROM pg_stat_statements AS s WHERE s.query = $3 | | + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 3 | 35 + SET ROLE regress_stats_user1 | 1 | 0 + SET ROLE regress_stats_user2 | 1 | 0 +(11 rows) + +-- +-- remove query of a user (regress_stats_user1) +-- +SELECT pg_stat_statements_reset(r.oid) + FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user1'; + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +----------------------------------------------------------------------------------+-------+------ + CREATE ROLE regress_stats_user1 | 1 | 0 + CREATE ROLE regress_stats_user2 | 1 | 0 + RESET ROLE | 2 | 0 + SELECT pg_stat_statements_reset( +| 1 | 1 + (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = $1), +| | + (SELECT d.oid FROM pg_database As d where datname = current_database()),+| | + (SELECT s.queryid FROM pg_stat_statements AS s +| | + WHERE s.query = $2 LIMIT $3)) | | + SELECT pg_stat_statements_reset($1) | 1 | 1 + SELECT pg_stat_statements_reset($1,$2,s.queryid) +| 1 | 2 + FROM pg_stat_statements AS s WHERE s.query = $3 | | + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT pg_stat_statements_reset(r.oid) +| 1 | 1 + FROM pg_roles AS r WHERE r.rolname = $1 | | + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 4 | 46 + SET ROLE regress_stats_user2 | 1 | 0 +(10 rows) + +-- +-- reset all +-- +SELECT pg_stat_statements_reset(0,0,0); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +------------------------------------------------------------------------------+-------+------ + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0 +(1 row) + +-- +-- cleanup +-- +DROP ROLE regress_stats_user1; +DROP ROLE regress_stats_user2; +DROP MATERIALIZED VIEW pgss_matv; +DROP TABLE pgss_ctas; +DROP TABLE pgss_select_into; +-- +-- [re]plan counting +-- +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +CREATE TABLE test (); +PREPARE prep1 AS SELECT COUNT(*) FROM test; +EXECUTE prep1; + count +------- + 0 +(1 row) + +EXECUTE prep1; + count +------- + 0 +(1 row) + +EXECUTE prep1; + count +------- + 0 +(1 row) + +ALTER TABLE test ADD COLUMN x int; +EXECUTE prep1; + count +------- + 0 +(1 row) + +SELECT 42; + ?column? +---------- + 42 +(1 row) + +SELECT 42; + ?column? +---------- + 42 +(1 row) + +SELECT 42; + ?column? +---------- + 42 +(1 row) + +SELECT query, plans, calls, rows FROM pg_stat_statements + WHERE query NOT LIKE 'PREPARE%' ORDER BY query COLLATE "C"; + query | plans | calls | rows +----------------------------------------------------------+-------+-------+------ + ALTER TABLE test ADD COLUMN x int | 0 | 1 | 0 + CREATE TABLE test () | 0 | 1 | 0 + SELECT $1 | 3 | 3 | 3 + SELECT pg_stat_statements_reset() | 0 | 1 | 1 + SELECT query, plans, calls, rows FROM pg_stat_statements+| 1 | 0 | 0 + WHERE query NOT LIKE $1 ORDER BY query COLLATE "C" | | | +(5 rows) + +-- for the prepared statement we expect at least one replan, but cache +-- invalidations could force more +SELECT query, plans >= 2 AND plans <= calls AS plans_ok, calls, rows FROM pg_stat_statements + WHERE query LIKE 'PREPARE%' ORDER BY query COLLATE "C"; + query | plans_ok | calls | rows +--------------------------------------------+----------+-------+------ + PREPARE prep1 AS SELECT COUNT(*) FROM test | t | 4 | 4 +(1 row) + +-- +-- access to pg_stat_statements_info view +-- +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT dealloc FROM pg_stat_statements_info; + dealloc +--------- + 0 +(1 row) + +-- +-- top level handling +-- +SET pg_stat_statements.track = 'top'; +DELETE FROM test; +DO $$ +BEGIN + DELETE FROM test; +END; +$$ LANGUAGE plpgsql; +SELECT query, toplevel, plans, calls FROM pg_stat_statements WHERE query LIKE '%DELETE%' ORDER BY query COLLATE "C", toplevel; + query | toplevel | plans | calls +-----------------------+----------+-------+------- + DELETE FROM test | t | 1 | 1 + DO $$ +| t | 0 | 1 + BEGIN +| | | + DELETE FROM test;+| | | + END; +| | | + $$ LANGUAGE plpgsql | | | +(2 rows) + +SET pg_stat_statements.track = 'all'; +DELETE FROM test; +DO $$ +BEGIN + DELETE FROM test; +END; +$$ LANGUAGE plpgsql; +SELECT query, toplevel, plans, calls FROM pg_stat_statements WHERE query LIKE '%DELETE%' ORDER BY query COLLATE "C", toplevel; + query | toplevel | plans | calls +-----------------------+----------+-------+------- + DELETE FROM test | f | 1 | 1 + DELETE FROM test | t | 2 | 2 + DO $$ +| t | 0 | 2 + BEGIN +| | | + DELETE FROM test;+| | | + END; +| | | + $$ LANGUAGE plpgsql | | | +(3 rows) + +-- FROM [ONLY] +CREATE TABLE tbl_inh(id integer); +CREATE TABLE tbl_inh_1() INHERITS (tbl_inh); +INSERT INTO tbl_inh_1 SELECT 1; +SELECT * FROM tbl_inh; + id +---- + 1 +(1 row) + +SELECT * FROM ONLY tbl_inh; + id +---- +(0 rows) + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%FROM%tbl_inh%'; + count +------- + 2 +(1 row) + +-- WITH TIES +CREATE TABLE limitoption AS SELECT 0 AS val FROM generate_series(1, 10); +SELECT * +FROM limitoption +WHERE val < 2 +ORDER BY val +FETCH FIRST 2 ROWS WITH TIES; + val +----- + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 +(10 rows) + +SELECT * +FROM limitoption +WHERE val < 2 +ORDER BY val +FETCH FIRST 2 ROW ONLY; + val +----- + 0 + 0 +(2 rows) + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%FETCH FIRST%'; + count +------- + 2 +(1 row) + +-- GROUP BY [DISTINCT] +SELECT a, b, c +FROM (VALUES (1, 2, 3), (4, NULL, 6), (7, 8, 9)) AS t (a, b, c) +GROUP BY ROLLUP(a, b), rollup(a, c) +ORDER BY a, b, c; + a | b | c +---+---+--- + 1 | 2 | 3 + 1 | 2 | + 1 | 2 | + 1 | | 3 + 1 | | 3 + 1 | | + 1 | | + 1 | | + 4 | | 6 + 4 | | 6 + 4 | | 6 + 4 | | + 4 | | + 4 | | + 4 | | + 4 | | + 7 | 8 | 9 + 7 | 8 | + 7 | 8 | + 7 | | 9 + 7 | | 9 + 7 | | + 7 | | + 7 | | + | | +(25 rows) + +SELECT a, b, c +FROM (VALUES (1, 2, 3), (4, NULL, 6), (7, 8, 9)) AS t (a, b, c) +GROUP BY DISTINCT ROLLUP(a, b), rollup(a, c) +ORDER BY a, b, c; + a | b | c +---+---+--- + 1 | 2 | 3 + 1 | 2 | + 1 | | 3 + 1 | | + 4 | | 6 + 4 | | 6 + 4 | | + 4 | | + 7 | 8 | 9 + 7 | 8 | + 7 | | 9 + 7 | | + | | +(13 rows) + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%GROUP BY%ROLLUP%'; + count +------- + 2 +(1 row) + +-- GROUPING SET agglevelsup +SELECT ( + SELECT ( + SELECT GROUPING(a,b) FROM (VALUES (1)) v2(c) + ) FROM (VALUES (1,2)) v1(a,b) GROUP BY (a,b) +) FROM (VALUES(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); + grouping +---------- + 0 + 0 + 0 +(3 rows) + +SELECT ( + SELECT ( + SELECT GROUPING(e,f) FROM (VALUES (1)) v2(c) + ) FROM (VALUES (1,2)) v1(a,b) GROUP BY (a,b) +) FROM (VALUES(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); + grouping +---------- + 3 + 0 + 1 +(3 rows) + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%SELECT GROUPING%'; + count +------- + 2 +(1 row) + +DROP EXTENSION pg_stat_statements; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.10--1.10-yb-1.0.sql b/contrib/pg_stat_statements/pg_stat_statements--1.10--1.10-yb-1.0.sql new file mode 100644 index 0000000000000..28e8c1f194918 --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.10--1.10-yb-1.0.sql @@ -0,0 +1,71 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.10--1.10-yb-1.0.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.10-yb-1.0'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8, + OUT yb_latency_histogram jsonb +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-1.0--1.10-yb-2.0.sql b/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-1.0--1.10-yb-2.0.sql new file mode 100644 index 0000000000000..06b5decc81eb2 --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-1.0--1.10-yb-2.0.sql @@ -0,0 +1,82 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.10-yb-1.0--1.10-yb-2.0.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.10-yb-2.0'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8, + OUT yb_latency_histogram jsonb, + OUT docdb_read_rpcs int8, + OUT docdb_write_rpcs int8, + OUT catalog_wait_time float8, + OUT docdb_read_operations int8, + OUT docdb_write_operations int8, + OUT docdb_rows_scanned int8, + OUT docdb_rows_returned int8, + OUT docdb_wait_time float8, + OUT conflict_retries int8, + OUT read_restart_retries int8, + OUT total_retries int8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_2_0_pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-1.0.sql b/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-1.0.sql new file mode 100644 index 0000000000000..21559d5f678aa --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-1.0.sql @@ -0,0 +1,390 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_stat_statements" to load this file. \quit + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.4.sql */ + +-- Register functions. +CREATE FUNCTION pg_stat_statements_reset() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_3' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_stat_statements_reset() FROM PUBLIC; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.4--1.5.sql */ + +GRANT EXECUTE ON FUNCTION pg_stat_statements_reset() TO pg_read_all_stats; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.5--1.6.sql */ + +-- Execution is only allowed for superusers, fixing issue with 1.5. +REVOKE EXECUTE ON FUNCTION pg_stat_statements_reset() FROM pg_read_all_stats; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.6--1.6-yb-1.0.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT yb_latency_histogram jsonb +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_pg_stat_statements_1_4' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.6-yb-1.0--1.10-yb-1.0.sql */ + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.6--1.7.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements_reset(); + +/* Then we can drop them */ +DROP FUNCTION pg_stat_statements_reset(); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements_reset(IN userid Oid DEFAULT 0, + IN dbid Oid DEFAULT 0, + IN queryid bigint DEFAULT 0 +) +RETURNS void +AS 'MODULE_PATHNAME', 'pg_stat_statements_reset_1_7' +LANGUAGE C STRICT PARALLEL SAFE; + +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_stat_statements_reset(Oid, Oid, bigint) FROM PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_8' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql */ + +--- Define pg_stat_statements_info +CREATE FUNCTION pg_stat_statements_info( + OUT dealloc bigint, + OUT stats_reset timestamp with time zone +) +RETURNS record +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements_info AS + SELECT * FROM pg_stat_statements_info(); + +GRANT SELECT ON pg_stat_statements_info TO PUBLIC; + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_9' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.9--1.10.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.10--1.10-yb-1.0.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8, + OUT yb_latency_histogram jsonb +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.0--1.10-yb-2.1.sql b/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.0--1.10-yb-2.1.sql new file mode 100644 index 0000000000000..5d2c8c123931c --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.0--1.10-yb-2.1.sql @@ -0,0 +1,88 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.0--1.10-yb-2.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.10-yb-2.1'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8, + OUT yb_latency_histogram jsonb, + OUT docdb_read_rpcs int8, + OUT docdb_write_rpcs int8, + OUT catalog_wait_time float8, + OUT docdb_read_operations int8, + OUT docdb_write_operations int8, + OUT docdb_rows_scanned int8, + OUT docdb_rows_returned int8, + OUT docdb_wait_time float8, + OUT conflict_retries int8, + OUT read_restart_retries int8, + OUT total_retries int8, + OUT docdb_obsolete_rows_scanned int8, + OUT docdb_seeks int8, + OUT docdb_nexts int8, + OUT docdb_prevs int8, + OUT docdb_read_time float8, + OUT docdb_write_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_2_1_pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.0.sql b/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.0.sql new file mode 100644 index 0000000000000..f6f4c17c91a9c --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.0.sql @@ -0,0 +1,471 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_stat_statements" to load this file. \quit + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.4.sql */ + +-- Register functions. +CREATE FUNCTION pg_stat_statements_reset() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_3' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_stat_statements_reset() FROM PUBLIC; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.4--1.5.sql */ + +GRANT EXECUTE ON FUNCTION pg_stat_statements_reset() TO pg_read_all_stats; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.5--1.6.sql */ + +-- Execution is only allowed for superusers, fixing issue with 1.5. +REVOKE EXECUTE ON FUNCTION pg_stat_statements_reset() FROM pg_read_all_stats; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.6--1.6-yb-1.0.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT yb_latency_histogram jsonb +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_pg_stat_statements_1_4' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.6-yb-1.0--1.10-yb-1.0.sql */ + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.6--1.7.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements_reset(); + +/* Then we can drop them */ +DROP FUNCTION pg_stat_statements_reset(); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements_reset(IN userid Oid DEFAULT 0, + IN dbid Oid DEFAULT 0, + IN queryid bigint DEFAULT 0 +) +RETURNS void +AS 'MODULE_PATHNAME', 'pg_stat_statements_reset_1_7' +LANGUAGE C STRICT PARALLEL SAFE; + +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_stat_statements_reset(Oid, Oid, bigint) FROM PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_8' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql */ + +--- Define pg_stat_statements_info +CREATE FUNCTION pg_stat_statements_info( + OUT dealloc bigint, + OUT stats_reset timestamp with time zone +) +RETURNS record +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements_info AS + SELECT * FROM pg_stat_statements_info(); + +GRANT SELECT ON pg_stat_statements_info TO PUBLIC; + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_9' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.9--1.10.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.10--1.10-yb-1.0.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8, + OUT yb_latency_histogram jsonb +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.10-yb-1.0--1.10-yb-2.0.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8, + OUT yb_latency_histogram jsonb, + OUT docdb_read_rpcs int8, + OUT docdb_write_rpcs int8, + OUT catalog_wait_time float8, + OUT docdb_read_operations int8, + OUT docdb_write_operations int8, + OUT docdb_rows_scanned int8, + OUT docdb_rows_returned int8, + OUT docdb_wait_time float8, + OUT conflict_retries int8, + OUT read_restart_retries int8, + OUT total_retries int8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_2_0_pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.1.sql b/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.1.sql new file mode 100644 index 0000000000000..6a7d9ebd81e02 --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.1.sql @@ -0,0 +1,558 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_stat_statements" to load this file. \quit + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.4.sql */ + +-- Register functions. +CREATE FUNCTION pg_stat_statements_reset() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_3' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_stat_statements_reset() FROM PUBLIC; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.4--1.5.sql */ + +GRANT EXECUTE ON FUNCTION pg_stat_statements_reset() TO pg_read_all_stats; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.5--1.6.sql */ + +-- Execution is only allowed for superusers, fixing issue with 1.5. +REVOKE EXECUTE ON FUNCTION pg_stat_statements_reset() FROM pg_read_all_stats; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.6--1.6-yb-1.0.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT yb_latency_histogram jsonb +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_pg_stat_statements_1_4' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.6-yb-1.0--1.10-yb-1.0.sql */ + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.6--1.7.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements_reset(); + +/* Then we can drop them */ +DROP FUNCTION pg_stat_statements_reset(); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements_reset(IN userid Oid DEFAULT 0, + IN dbid Oid DEFAULT 0, + IN queryid bigint DEFAULT 0 +) +RETURNS void +AS 'MODULE_PATHNAME', 'pg_stat_statements_reset_1_7' +LANGUAGE C STRICT PARALLEL SAFE; + +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_stat_statements_reset(Oid, Oid, bigint) FROM PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_8' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql */ + +--- Define pg_stat_statements_info +CREATE FUNCTION pg_stat_statements_info( + OUT dealloc bigint, + OUT stats_reset timestamp with time zone +) +RETURNS record +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements_info AS + SELECT * FROM pg_stat_statements_info(); + +GRANT SELECT ON pg_stat_statements_info TO PUBLIC; + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_9' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.9--1.10.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.10--1.10-yb-1.0.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8, + OUT yb_latency_histogram jsonb +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.10-yb-1.0--1.10-yb-2.0.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8, + OUT yb_latency_histogram jsonb, + OUT docdb_read_rpcs int8, + OUT docdb_write_rpcs int8, + OUT catalog_wait_time float8, + OUT docdb_read_operations int8, + OUT docdb_write_operations int8, + OUT docdb_rows_scanned int8, + OUT docdb_rows_returned int8, + OUT docdb_wait_time float8, + OUT conflict_retries int8, + OUT read_restart_retries int8, + OUT total_retries int8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_2_0_pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/*NESTED*/ +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.10-yb-2.0--1.10-yb-2.1.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8, + OUT yb_latency_histogram jsonb, + OUT docdb_read_rpcs int8, + OUT docdb_write_rpcs int8, + OUT catalog_wait_time float8, + OUT docdb_read_operations int8, + OUT docdb_write_operations int8, + OUT docdb_rows_scanned int8, + OUT docdb_rows_returned int8, + OUT docdb_wait_time float8, + OUT conflict_retries int8, + OUT read_restart_retries int8, + OUT total_retries int8, + OUT docdb_obsolete_rows_scanned int8, + OUT docdb_seeks int8, + OUT docdb_nexts int8, + OUT docdb_prevs int8, + OUT docdb_read_time float8, + OUT docdb_write_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_2_1_pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.6--1.6-yb-1.0.sql b/contrib/pg_stat_statements/pg_stat_statements--1.6--1.6-yb-1.0.sql new file mode 100644 index 0000000000000..225775d90c14a --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.6--1.6-yb-1.0.sql @@ -0,0 +1,51 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.6--1.6-yb-1.0.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.6-yb-1.0'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT yb_latency_histogram jsonb +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_pg_stat_statements_1_4' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.6-yb-1.0--1.10-yb-1.0.sql b/contrib/pg_stat_statements/pg_stat_statements--1.6-yb-1.0--1.10-yb-1.0.sql new file mode 100644 index 0000000000000..f6c667e63288d --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.6-yb-1.0--1.10-yb-1.0.sql @@ -0,0 +1,277 @@ +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.10-yb-1.0'" to load this file. \quit + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.6--1.7.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements_reset(); + +/* Then we can drop them */ +DROP FUNCTION pg_stat_statements_reset(); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements_reset(IN userid Oid DEFAULT 0, + IN dbid Oid DEFAULT 0, + IN queryid bigint DEFAULT 0 +) +RETURNS void +AS 'MODULE_PATHNAME', 'pg_stat_statements_reset_1_7' +LANGUAGE C STRICT PARALLEL SAFE; + +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_stat_statements_reset(Oid, Oid, bigint) FROM PUBLIC; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_8' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql */ + +--- Define pg_stat_statements_info +CREATE FUNCTION pg_stat_statements_info( + OUT dealloc bigint, + OUT stats_reset timestamp with time zone +) +RETURNS record +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements_info AS + SELECT * FROM pg_stat_statements_info(); + +GRANT SELECT ON pg_stat_statements_info TO PUBLIC; + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_9' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.9--1.10.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +/* Sourced from contrib/pg_stat_statements/pg_stat_statements--1.10--1.10-yb-1.0.sql */ + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW IF EXISTS pg_stat_statements; +DROP FUNCTION IF EXISTS pg_stat_statements(boolean); + +-- Update the view pg_stat_statements. +-- Register functions. + +CREATE OR REPLACE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8, + OUT yb_latency_histogram jsonb +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'yb_pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE OR REPLACE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index ae4f6e0a156d2..9d6575e4fad49 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -73,6 +73,19 @@ #include "utils/memutils.h" #include "utils/timestamp.h" +/* YB includes */ +#include "common/jsonapi.h" +#include "common/pg_yb_common.h" +#include "hdr/hdr_histogram.h" +#include "lib/stringinfo.h" +#include "pg_yb_utils.h" +#include "utils/json.h" +#include "utils/jsonb.h" +#include "yb/yql/pggate/webserver/ybc_pg_webserver_wrapper.h" +#include "yb_query_diagnostics.h" +#include +#include + PG_MODULE_MAGIC; /* Location of permanent stats file (valid when database is shut down) */ @@ -84,7 +97,10 @@ PG_MODULE_MAGIC; #define PGSS_TEXT_FILE PG_STAT_TMP_DIR "/pgss_query_texts.stat" /* Magic number identifying the stats file format */ +/* YB: Postgres 15 uses the following number. static const uint32 PGSS_FILE_HEADER = 0x20220408; +*/ +static const uint32 PGSS_FILE_HEADER = 0x20250425; /* PostgreSQL major version number, changes in which invalidate all entries */ static const uint32 PGSS_PG_MAJOR_VERSION = PG_VERSION_NUM / 100; @@ -107,6 +123,9 @@ static const uint32 PGSS_PG_MAJOR_VERSION = PG_VERSION_NUM / 100; !IsA(n, PrepareStmt) && \ !IsA(n, DeallocateStmt)) +#define YB_NUM_COUNTERS_INT 60 +#define YB_NUM_COUNTERS_DBL 40 + /* * Extension version number, for supporting older extension versions' objects */ @@ -116,11 +135,26 @@ typedef enum pgssVersion PGSS_V1_1, PGSS_V1_2, PGSS_V1_3, + YB_PGSS_V1_4, PGSS_V1_8, PGSS_V1_9, - PGSS_V1_10 + PGSS_V1_10, + YB_PGSS_V1_10, + YB_V2_0_PGSS_V1_10, + YB_V2_1_PGSS_V1_10, } pgssVersion; +/* + * yb change: Added YB_PGSS_V1_4 which adds a the column yb_latency_histogram to + * pg_stat_statements. This necessitates new pg_stat_statements_internal changes + * because the number of columns in the table has changed. Considering upgrades, + * we use the PGSS_FILE_HEADER variable to determine whether a saved-to-disk + * pg_stat_statements file contains a histogram or not. If not, we read in the + * other pg_stat_statements information and create a fresh histogram. Histogram + * data is tracked regardless of pgssVersion but the yb_latency_histogram column + * only gets added to pg_stat_statements if api_version >= YB_PGSS_V1_4. + */ + typedef enum pgssStoreKind { PGSS_INVALID = -1, @@ -153,6 +187,50 @@ typedef struct pgssHashKey bool toplevel; /* query executed at top level */ } pgssHashKey; +/* + * Enum for YbCounters::counters + */ +typedef enum YbIntCounters +{ + YB_INT_DOCDB_READ_RPCS, + YB_INT_DOCDB_WRITE_RPCS, + YB_INT_DOCDB_READ_OPS, + YB_INT_DOCDB_WRITE_OPS, + YB_INT_DOCDB_ROWS_SCANNED, + YB_INT_CONFLICT_RETRIES, + YB_INT_READ_RESTART_RETRIES, + YB_INT_TOTAL_RETRIES, + YB_INT_DOCDB_ROWS_RETURNED, + YB_INT_DOCDB_OBSOLETE_ROWS_SCANNED, + YB_INT_DOCDB_SEEKS, + YB_INT_DOCDB_NEXTS, + YB_INT_DOCDB_PREVS, + + YB_INT_COUNTERS_LAST = YB_NUM_COUNTERS_INT +} YbIntCounters; + +/* + * Enum for YbCounters::counters_dbl + */ +typedef enum YbDoubleCounters +{ + YB_DBL_CATALOG_WAIT_TIME_MS, + YB_DBL_DOCDB_WAIT_TIME_MS, + YB_DBL_DOCDB_READ_TIME_MS, + YB_DBL_DOCDB_WRITE_TIME_MS, + + YB_DBL_COUNTERS_LAST = YB_NUM_COUNTERS_DBL +} YbDoubleCounters; + +/* + * Struct for YB-specific counters. Currently, all counters are unreserved. + */ +typedef struct YbCounters +{ + int64 counters[YB_NUM_COUNTERS_INT]; + double counters_dbl[YB_NUM_COUNTERS_DBL]; +} YbCounters; + /* * The actual stats counters kept within pgssEntry. */ @@ -200,8 +278,15 @@ typedef struct Counters int64 jit_emission_count; /* number of times emission time has been * > 0 */ double jit_emission_time; /* total time to emit jit code */ + YbCounters yb_counters; /* YB specific counters */ } Counters; +/* + * hdr_histogram + * defined in third party hdr_histogram.h + */ +typedef struct hdr_iter yb_hdr_iter; + /* * Global statistics for pg_stat_statements */ @@ -226,8 +311,24 @@ typedef struct pgssEntry int query_len; /* # of valid bytes in query string, or -1 */ int encoding; /* query text encoding */ slock_t mutex; /* protects the counters only */ + size_t yb_slow_executions; /* # of executions >= yb_hdr_max_value * + * yb_hdr_latency_res_ms */ + hdr_histogram yb_hdr_histogram; /* flexible array member at end - MUST BE + * LAST */ } pgssEntry; +typedef struct +{ + int32 num; + int *buffer_size; + char **buffer; + + /* hdr specific */ + int temp_yb_hdr_count_bytes; + bool hdr_config_match; + +} pgssYbReaderContext; + /* * Global shared state */ @@ -251,6 +352,37 @@ static int exec_nested_level = 0; /* Current nesting depth of planner calls */ static int plan_nested_level = 0; +/* + * YB: Session-local set of queryIds whose queries contain constants that + * require normalization (i.e. clocations_count > 0 at parse time). + * + * An entry is added by pgss_post_parse_analyze when the query has constants + * needing normalization. + * + * pgss_store consults this set when the shared hash table entry is missing and + * jstate is NULL: if the queryId is present, the entry was evicted or reset + * and we discard stats rather than storing unnormalized text; if absent, the + * query never needed normalization and raw text is stored normally. + * + * A hash set keyed on queryId (rather than a single boolean) is needed so that + * the extended query protocol — where multiple queries with different + * normalization needs can be parsed before any are executed — is handled + * correctly. + * + * The set is session-local because it tracks what this backend has parsed; + * every backend must parse a query before executing it, so the set is always + * populated before it is read. Session-local storage also avoids the need for + * shared-memory sizing, locking, and cleanup on backend exit. + * + * queryIds are never removed from this set; once recorded, an id remains until + * backend exit. That is safe: at worst a recycled queryId is treated as + * needing normalization and we discard a few stats, never the reverse error of + * storing unnormalized text. + */ +typedef uint64 YbPgssQueryNeedingNormalization; + +static HTAB *yb_pgss_queries_needing_normalization = NULL; + /* Saved hook values in case of unload */ static shmem_request_hook_type prev_shmem_request_hook = NULL; static shmem_startup_hook_type prev_shmem_startup_hook = NULL; @@ -283,12 +415,41 @@ static const struct config_enum_entry track_options[] = {NULL, 0, false} }; +#define YB_HDR_DEFAULT_MAX_LATENCY_MS 1677721.6 +#define YB_HDR_DEFAULT_LATENCY_RES_MS 0.1 +#define YB_HDR_DEFAULT_BUCKET_FACTOR 16 +#define YB_HDR_DEFAULT_MAX_VALUE YB_HDR_DEFAULT_MAX_LATENCY_MS / YB_HDR_DEFAULT_LATENCY_RES_MS +#define YB_DEFAULT_QTEXT_LIMIT_KB -1 + static int pgss_max; /* max # statements to track */ static int pgss_track; /* tracking level */ static bool pgss_track_utility; /* whether to track utility commands */ static bool pgss_track_planning; /* whether to track planning duration */ static bool pgss_save; /* whether to save stats across shutdown */ +static float yb_hdr_max_latency_ms = YB_HDR_DEFAULT_MAX_LATENCY_MS; /* hardcoded in phase 1, + * max query latency + * tracked by histogram */ +static float yb_hdr_latency_res_ms = YB_HDR_DEFAULT_LATENCY_RES_MS; /* hardcoded in phase 1, + * starting query + * latency resolution + * tracked by histogram */ +static int yb_hdr_bucket_factor; /* subbuckets per bucket for histogram */ +static int64_t yb_hdr_max_value = YB_HDR_DEFAULT_MAX_VALUE; /* default hardcode for + * phase 1, will need to + * be adjusted against + * latency_res */ +static struct hdr_histogram_bucket_config cfg; +static int yb_qtext_size_limit = YB_DEFAULT_QTEXT_LIMIT_KB; +/* + * yb_hdr_max_value is the integer representation of the max query latency we + * want to track. Currently with a latency resolution of 0.1ms, + * 16777216 * 0.1 = 1677721.6ms ~ 28min, our default max query latency. Max + * query latency and latency resolution have not been discretely implemented as + * GUC variables yet. In future phases, we will modify this value and + * yb_hdr_max_latency_ms accordingly based on the histogram-supported max and + * latency resolution. + */ #define pgss_enabled(level) \ (!IsParallelWorker() && \ @@ -317,6 +478,11 @@ PG_FUNCTION_INFO_V1(pg_stat_statements_1_10); PG_FUNCTION_INFO_V1(pg_stat_statements); PG_FUNCTION_INFO_V1(pg_stat_statements_info); +PG_FUNCTION_INFO_V1(yb_pg_stat_statements_1_4); +PG_FUNCTION_INFO_V1(yb_pg_stat_statements_1_10); +PG_FUNCTION_INFO_V1(yb_2_0_pg_stat_statements_1_10); +PG_FUNCTION_INFO_V1(yb_2_1_pg_stat_statements_1_10); + static void pgss_shmem_request(void); static void pgss_shmem_startup(void); static void pgss_shmem_shutdown(int code, Datum arg); @@ -344,7 +510,8 @@ static void pgss_store(const char *query, uint64 queryId, const BufferUsage *bufusage, const WalUsage *walusage, const struct JitInstrumentation *jitusage, - JumbleState *jstate); + JumbleState *jstate, + bool yb_is_sensitive_stmt); static void pg_stat_statements_internal(FunctionCallInfo fcinfo, pgssVersion api_version, bool showtext); @@ -366,6 +533,31 @@ static void fill_in_constant_lengths(JumbleState *jstate, const char *query, int query_loc); static int comp_location(const void *a, const void *b); +/* YB functions */ +PG_FUNCTION_INFO_V1(yb_get_histogram_jsonb); +static Datum yb_get_histogram_jsonb_args(uint64 queryid, Oid userid, Oid dbid, + bool top_level); +static void yb_add_hdr_jsonb_object(JsonbParseState *state, char *buf, + count_t count, JsonbPair *pair); +static Datum yb_add_histogram_jsonb(JsonbParseState *state, + hdr_histogram *h, size_t yb_slow_executions); +static void yb_hdr_reset(hdr_histogram *h); +static int read_entry_original(int header, FILE *file, FILE *qfile, + pgssYbReaderContext *context); +static int read_entry_hdr(int header, FILE *file, FILE *qfile, + pgssYbReaderContext *context); +static int extended_header_reader(int header, FILE *file, + pgssYbReaderContext *context); +static int query_buffer_helper(FILE *file, FILE *qfile, int qlen, + Size *query_offset, int encoding, Counters *counters, + pgssYbReaderContext *context); +static void enforce_bucket_factor(int *value); +static bool yb_track_nested_queries(void); +static int YbGetPgssNormalizedQueryText(Size query_offset, int actual_query_len, char *normalized_query); +static char *yb_generate_normalized_backfill_query(YbBackfillIndexStmt *stmt, + int *query_len_p); +static void yb_pgss_mark_needs_normalization(uint64 queryId); +static bool yb_pgss_needs_normalization(uint64 queryId); /* * Module load callback @@ -433,7 +625,7 @@ _PG_init(void) "Selects whether planning duration is tracked by pg_stat_statements.", NULL, &pgss_track_planning, - false, + true, /* YB: change default */ PGC_SUSET, 0, NULL, @@ -451,8 +643,65 @@ _PG_init(void) NULL, NULL); + DefineCustomIntVariable("pg_stat_statements.yb_hdr_bucket_factor", + "Sets the number of subbuckets per bucket in pgss histogram stat tracking.", + NULL, + &yb_hdr_bucket_factor, + YB_HDR_DEFAULT_BUCKET_FACTOR, + 8, + 32, + PGC_POSTMASTER, + 0, + NULL, + NULL, + NULL); + + DefineCustomIntVariable("pg_stat_statements.yb_qtext_size_limit", + "Sets the max size of the pg_stat_statements query text file.", + NULL, + &yb_qtext_size_limit, + YB_DEFAULT_QTEXT_LIMIT_KB, + -1, + MAX_KILOBYTES, + PGC_SUSET, + GUC_UNIT_KB, + NULL, + NULL, + NULL); + MarkGUCPrefixReserved("pg_stat_statements"); + /* + * Calculate actual max = 2^(sub_bucket_half_count_magnitude + bucket_count) + * So hdr's max val should be actual max - 1 + * yb_hdr_max_latency_ms and yb_hdr_latency_res_ms will be made GUC vars + * Adjust yb_hdr_max_latency_ms accordingly + */ + int prelim_max_value = yb_hdr_max_latency_ms / yb_hdr_latency_res_ms; + + enforce_bucket_factor(&yb_hdr_bucket_factor); + + if (yb_hdr_calculate_bucket_config(1, prelim_max_value - 1, + yb_hdr_bucket_factor, &cfg)) + { + elog(LOG, "Cannot configure custom hdr values, reverting to default"); + yb_hdr_max_latency_ms = YB_HDR_DEFAULT_MAX_LATENCY_MS; + yb_hdr_latency_res_ms = YB_HDR_DEFAULT_LATENCY_RES_MS; + yb_hdr_bucket_factor = YB_HDR_DEFAULT_BUCKET_FACTOR; + prelim_max_value = yb_hdr_max_latency_ms / yb_hdr_latency_res_ms; + yb_hdr_calculate_bucket_config(1, prelim_max_value - 1, + yb_hdr_bucket_factor, &cfg); + } + int derived_max_magnitude = (cfg.sub_bucket_half_count_magnitude + + cfg.bucket_count); + + yb_hdr_max_value = pow(2, derived_max_magnitude); + /* cfg retains prelim_max_value, update for use in hdr_init_preallocated */ + if (prelim_max_value != yb_hdr_max_value) + yb_hdr_calculate_bucket_config(1, yb_hdr_max_value - 1, + yb_hdr_bucket_factor, &cfg); + yb_hdr_max_latency_ms = yb_hdr_max_value * yb_hdr_latency_res_ms; + /* * Install hooks. */ @@ -474,6 +723,21 @@ _PG_init(void) ExecutorEnd_hook = pgss_ExecutorEnd; prev_ProcessUtility = ProcessUtility_hook; ProcessUtility_hook = pgss_ProcessUtility; + + /* Function pointer to check if nested queries should be tracked in ASH */ + yb_ash_track_nested_queries = yb_track_nested_queries; + + /* + * Initializing the function pointer required by yb_query_diagnostics.c + * to get normalized query text. + */ + yb_get_normalized_query = &YbGetPgssNormalizedQueryText; + + /* + * Initialize the function pointer required by yb_query_diagnostics.c + * to get constant lengths in the query text. + */ + yb_qd_fill_in_constant_lengths = &fill_in_constant_lengths; } /* @@ -490,6 +754,336 @@ pgss_shmem_request(void) RequestNamedLWLockTranche("pg_stat_statements", 1); } +static void +resetYsqlStatementStats() +{ + pg_stat_statements_reset(NULL); +} + +static void +yb_add_hist_json(void *cb_arg, hdr_histogram *h, size_t yb_slow_executions) +{ + yb_hdr_iter iter; + StringInfoData buf; + + initStringInfo(&buf); + + hdr_iter_init(&iter, h); + while (hdr_iter_next(&iter)) + { + if (iter.count > 0) + { + resetStringInfo(&buf); + appendStringInfo(&buf, "[%.1f,%.1f)", + (iter.value_iterated_to) * yb_hdr_latency_res_ms, + (iter.highest_equivalent_value + 1) * yb_hdr_latency_res_ms); + WriteIntValueObjectToJson(cb_arg, buf.data, &iter.count); + } + } + + if (yb_slow_executions > 0) + { + resetStringInfo(&buf); + appendStringInfo(&buf, "[%.1f,)", yb_hdr_max_value * yb_hdr_latency_res_ms); + WriteIntValueObjectToJson(cb_arg, buf.data, &yb_slow_executions); + } + pfree(buf.data); +} + +static double +calc_stddev(int64 calls, double sum_var_time) +{ + return (calls > 1) ? (sqrt(sum_var_time / calls)) : 0.0; +} + +static void +getYsqlStatementStats(void *cb_arg) +{ + HASH_SEQ_STATUS hash_seq; + char *qbuffer = NULL; + Size qbuffer_size = 0; + pgssEntry *entry; + + /* + * TODO(gauravsingh): Add memory context cleanup to prevent memory leak. + */ + qbuffer = qtext_load_file(&qbuffer_size); + if (qbuffer == NULL) + return; + + LWLockAcquire(pgss->lock, LW_SHARED); + + hash_seq_init(&hash_seq, pgss_hash); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + /* some entries have 0 calls and strange query text - ignore them */ + if (IS_STICKY(entry->counters)) + continue; + + char *qry = qtext_fetch(entry->query_offset, entry->query_len, qbuffer, qbuffer_size); + + if (qry != NULL) + { + WriteStartObjectToJson(cb_arg); + + WriteIntToJson(cb_arg, "userid", entry->key.userid); + WriteIntToJson(cb_arg, "dbid", entry->key.dbid); + WriteIntToJson(cb_arg, "query_id", entry->key.queryid); + WriteStringToJson(cb_arg, "query", qry); + WriteIntToJson(cb_arg, "calls", entry->counters.calls[PGSS_EXEC]); + + WriteDoubleToJson(cb_arg, "total_plan_time", entry->counters.total_time[PGSS_PLAN]); + WriteDoubleToJson(cb_arg, "total_exec_time", entry->counters.total_time[PGSS_EXEC]); + + WriteDoubleToJson(cb_arg, "min_plan_time", entry->counters.min_time[PGSS_PLAN]); + WriteDoubleToJson(cb_arg, "min_exec_time", entry->counters.min_time[PGSS_EXEC]); + + WriteDoubleToJson(cb_arg, "max_plan_time", entry->counters.max_time[PGSS_PLAN]); + WriteDoubleToJson(cb_arg, "max_exec_time", entry->counters.max_time[PGSS_EXEC]); + + WriteDoubleToJson(cb_arg, "mean_plan_time", entry->counters.mean_time[PGSS_PLAN]); + WriteDoubleToJson(cb_arg, "mean_exec_time", entry->counters.mean_time[PGSS_EXEC]); + + WriteDoubleToJson(cb_arg, "stddev_plan_time", + calc_stddev(entry->counters.calls[PGSS_PLAN], + entry->counters.sum_var_time[PGSS_PLAN])); + WriteDoubleToJson(cb_arg, "stddev_exec_time", + calc_stddev(entry->counters.calls[PGSS_EXEC], + entry->counters.sum_var_time[PGSS_EXEC])); + + WriteIntToJson(cb_arg, "rows", entry->counters.rows); + + WriteIntToJson(cb_arg, "local_blks_hit", entry->counters.local_blks_hit); + WriteIntToJson(cb_arg, "local_blks_read", entry->counters.local_blks_read); + WriteIntToJson(cb_arg, "local_blks_dirtied", entry->counters.local_blks_dirtied); + WriteIntToJson(cb_arg, "local_blks_written", entry->counters.local_blks_written); + WriteIntToJson(cb_arg, "temp_blks_read", entry->counters.temp_blks_read); + WriteIntToJson(cb_arg, "temp_blks_written", entry->counters.temp_blks_written); + + WriteArrayBeginToJson(cb_arg, "yb_latency_histogram"); + yb_add_hist_json(cb_arg, &entry->yb_hdr_histogram, entry->yb_slow_executions); + WriteArrayEndToJson(cb_arg); + WriteEndObjectToJson(cb_arg); + } + } + + LWLockRelease(pgss->lock); + + pfree(qbuffer); +} + +/* + * Parse in query text from disk during shared memory startup, and write that + * text into the query dump file pointed to by qfile. + */ +static int +query_buffer_helper(FILE *file, FILE *qfile, int qlen, + Size *query_offset, int encoding, Counters *counters, + pgssYbReaderContext *context) +{ + /* Encoding is the only field we can easily sanity-check */ + if (!PG_VALID_BE_ENCODING(encoding)) + return -1; + + /* Resize buffer as needed */ + if (qlen >= *context->buffer_size) + { + *context->buffer_size = Max(*context->buffer_size * 2, qlen + 1); + *context->buffer = repalloc(*context->buffer, *context->buffer_size); + } + + if (fread(*context->buffer, 1, qlen + 1, file) != qlen + 1) + return -1; + + /* Should have a trailing null, but let's make sure */ + (*context->buffer)[qlen] = '\0'; + + /* Skip loading "sticky" entries */ + if ((counters->calls[PGSS_EXEC] + counters->calls[PGSS_PLAN]) == 0) + return 0; + + /* Store the query text */ + *query_offset = pgss->extent; + if (fwrite(*context->buffer, 1, qlen + 1, qfile) != qlen + 1) + return -1; + pgss->extent += qlen + 1; + return 0; +} + +/* + * Parse in pgssEntries from disk during shared memory startup, for .stat files + * generated by pre-histogram code. + */ +static int +read_entry_original(int header, FILE *file, FILE *qfile, + pgssYbReaderContext *context) +{ + struct pgssEntry_original + { + pgssHashKey key; /* hash key of entry - MUST BE FIRST */ + Counters counters; /* the statistics for this query */ + Size query_offset; /* query text offset in external file */ + int query_len; /* # of valid bytes in query string, or -1 */ + int encoding; /* query text encoding */ + slock_t mutex; /* protects the counters only */ + }; + + Assert(header == 0x20171004); + + size_t pgssEntry_size = sizeof(struct pgssEntry_original); + + for (int i = 0; i < context->num; i++) + { + struct pgssEntry_original temp; + pgssEntry *entry; + Size query_offset; + + int qlen = 0; + int encoding = 0; + pgssHashKey key; + Counters counters; + + if (fread(&temp, pgssEntry_size, 1, file) != 1) + return -1; + qlen = temp.query_len; + encoding = temp.encoding; + key = temp.key; + counters = temp.counters; + + if (query_buffer_helper(file, qfile, qlen, &query_offset, encoding, + &counters, context)) + return -1; + + entry = entry_alloc(&key, query_offset, qlen, + encoding, false); + + /* copy in the actual stats */ + entry->counters = counters; + } + return 0; +} + +/* + * Parse in post-histogram pgssEntries from disk, throw out histogram parts if + * config variables have changed between restarts. + * File header version 0x20230330 can no longer be read as the file format has + * changed between 0x20230330 and 0x20250425. The stats file is discarded in + * pgss_shmem_startup() if the header is not 0x20250425. + */ +static int +read_entry_hdr(int header, FILE *file, FILE *qfile, + pgssYbReaderContext *context) +{ + Assert(header == 0x20250425); + + /* TODO: address case where hdr_histogram size changes due to 3p update */ + int prev_entry_total_size = (sizeof(pgssEntry) + + context->temp_yb_hdr_count_bytes); + char prev_entry[prev_entry_total_size]; + + for (int i = 0; i < context->num; i++) + { + pgssEntry *temp = (pgssEntry *) prev_entry; + pgssEntry *entry; + Size query_offset; + + int qlen = 0; + int encoding = 0; + pgssHashKey key; + Counters counters; + + if (fread(prev_entry, prev_entry_total_size, 1, file) != 1) + return -1; + qlen = temp->query_len; + encoding = temp->encoding; + key = temp->key; + counters = temp->counters; + + if (query_buffer_helper(file, qfile, qlen, &query_offset, encoding, + &counters, context)) + return -1; + + entry = entry_alloc(&key, query_offset, qlen, + encoding, false); + + /* + * only pass in old slow exec counts and old histograms if configs match + */ + if (context->hdr_config_match) + { + memcpy(&entry->yb_slow_executions, + &temp->yb_slow_executions, + sizeof(size_t) + sizeof(hdr_histogram) + + cfg.counts_len * sizeof(count_t)); + } + /* copy in the actual stats */ + entry->counters = counters; + } + return 0; +} + +/* + * Post-histogram pg_stat_statements.stat files will have histogram config + * variables to parse following the standard file header. + */ +static int +extended_header_reader(int header, FILE *file, + pgssYbReaderContext *context) +{ + if (header != 0x20250425) + return -1; + + int64_t temp_yb_hdr_max_value; + int temp_yb_hdr_count_bytes; + int temp_yb_hdr_bucket_factor; + + if (fread(&temp_yb_hdr_max_value, sizeof(int64_t), 1, file) != 1 || + fread(&temp_yb_hdr_count_bytes, sizeof(int), 1, file) != 1 || + fread(&temp_yb_hdr_bucket_factor, sizeof(int), 1, file) != 1) + return -1; + + context->temp_yb_hdr_count_bytes = temp_yb_hdr_count_bytes; + context->hdr_config_match = (temp_yb_hdr_max_value == yb_hdr_max_value && + temp_yb_hdr_count_bytes == cfg.counts_len * sizeof(count_t) && + temp_yb_hdr_bucket_factor == yb_hdr_bucket_factor); + return 0; +} + +typedef struct pgssYbReader +{ + uint32 header; + int (*extended_header_reader) (int, FILE *, pgssYbReaderContext *context); + int (*entry_reader) (int, FILE *, FILE *, pgssYbReaderContext *context); +} pgssYbReader; + +/* + * Array of pgssEntry deserialization functions, organized by header member. + * Future contributors should add a row here and write a read_entry_[feature]() + * if they want to parse in their new versions of pgssEntry. + * IMPORTANT: this approach has no resilience against changes to Counters or + * pgssHashKey. If those structures changed between pgss upgrades, the proper + * offsets would be lost. + */ +static const int pgssReaderEndMarker = -1; +pgssYbReader pgssReaderList[] = +{ + {0x20171004, NULL, read_entry_original}, + {0x20230330, extended_header_reader, read_entry_hdr}, + {0x20250425, extended_header_reader, read_entry_hdr}, + {pgssReaderEndMarker, NULL, NULL} +}; + +static void +enforce_bucket_factor(int *value) +{ + if (*value != 8 && *value != 16 && *value != 32) + { + elog(LOG, "Unsupported yb_hdr_bucket_factor, defaulting to %d", + YB_HDR_DEFAULT_BUCKET_FACTOR); + *value = YB_HDR_DEFAULT_BUCKET_FACTOR; + } +} + /* * shmem_startup hook: allocate or attach to shared memory, * then load any pre-existing statistics from file. @@ -506,13 +1100,16 @@ pgss_shmem_startup(void) uint32 header; int32 num; int32 pgver; - int32 i; + int buffer_size; char *buffer = NULL; if (prev_shmem_startup_hook) prev_shmem_startup_hook(); + RegisterGetYsqlStatStatements(&getYsqlStatementStats); + RegisterResetYsqlStatStatements(&resetYsqlStatementStats); + /* reset in case this is a restart within the postmaster */ pgss = NULL; pgss_hash = NULL; @@ -541,7 +1138,7 @@ pgss_shmem_startup(void) } info.keysize = sizeof(pgssHashKey); - info.entrysize = sizeof(pgssEntry); + info.entrysize = sizeof(pgssEntry) + cfg.counts_len * sizeof(count_t); pgss_hash = ShmemInitHash("pg_stat_statements hash", pgss_max, pgss_max, &info, @@ -611,50 +1208,35 @@ pgss_shmem_startup(void) pgver != PGSS_PG_MAJOR_VERSION) goto data_error; - for (i = 0; i < num; i++) - { - pgssEntry temp; - pgssEntry *entry; - Size query_offset; + pgssYbReader *version_reader = NULL; + int i = 0; - if (fread(&temp, sizeof(pgssEntry), 1, file) != 1) - goto read_error; - - /* Encoding is the only field we can easily sanity-check */ - if (!PG_VALID_BE_ENCODING(temp.encoding)) - goto data_error; - - /* Resize buffer as needed */ - if (temp.query_len >= buffer_size) + while (pgssReaderList[i].header != pgssReaderEndMarker) + { + if (header == pgssReaderList[i].header) { - buffer_size = Max(buffer_size * 2, temp.query_len + 1); - buffer = repalloc(buffer, buffer_size); + version_reader = &pgssReaderList[i]; + break; } + i++; + } - if (fread(buffer, 1, temp.query_len + 1, file) != temp.query_len + 1) - goto read_error; - - /* Should have a trailing null, but let's make sure */ - buffer[temp.query_len] = '\0'; - - /* Skip loading "sticky" entries */ - if (IS_STICKY(temp.counters)) - continue; - - /* Store the query text */ - query_offset = pgss->extent; - if (fwrite(buffer, 1, temp.query_len + 1, qfile) != temp.query_len + 1) - goto write_error; - pgss->extent += temp.query_len + 1; + if (version_reader == NULL) + goto read_error; - /* make the hashtable entry (discards old entries if too many) */ - entry = entry_alloc(&temp.key, query_offset, temp.query_len, - temp.encoding, - false); + pgssYbReaderContext context; - /* copy in the actual stats */ - entry->counters = temp.counters; + context.num = num; + context.buffer_size = &buffer_size; + context.buffer = &buffer; + context.hdr_config_match = false; + if (version_reader->extended_header_reader != NULL) + { + if (version_reader->extended_header_reader(header, file, &context)) + goto read_error; } + if (version_reader->entry_reader(header, file, qfile, &context)) + goto read_error; /* Read global statistics for pg_stat_statements */ if (fread(&pgss->stats, sizeof(pgssGlobalStats), 1, file) != 1) @@ -753,6 +1335,14 @@ pgss_shmem_shutdown(int code, Datum arg) num_entries = hash_get_num_entries(pgss_hash); if (fwrite(&num_entries, sizeof(int32), 1, file) != 1) goto error; + int yb_hdr_count_bytes = cfg.counts_len * sizeof(count_t); + + if (fwrite(&yb_hdr_max_value, sizeof(int64_t), 1, file) != 1) + goto error; + if (fwrite(&yb_hdr_count_bytes, sizeof(int), 1, file) != 1) + goto error; + if (fwrite(&yb_hdr_bucket_factor, sizeof(int), 1, file) != 1) + goto error; qbuffer = qtext_load_file(&qbuffer_size); if (qbuffer == NULL) @@ -772,7 +1362,8 @@ pgss_shmem_shutdown(int code, Datum arg) if (qstr == NULL) continue; /* Ignore any entries with bogus texts */ - if (fwrite(entry, sizeof(pgssEntry), 1, file) != 1 || + if (fwrite(entry, sizeof(pgssEntry) + cfg.counts_len * sizeof(count_t), + 1, file) != 1 || fwrite(qstr, 1, len + 1, file) != len + 1) { /* note: we assume hash_seq_term won't change errno */ @@ -817,6 +1408,46 @@ pgss_shmem_shutdown(int code, Datum arg) unlink(PGSS_TEXT_FILE); } +/* + * YB: Lazily initialize the session-local hash set that tracks which queryIds + * need normalization. + * + * There is no extension hook at connection startup to allocate this table, and + * many backends never execute a query with normalizable constants, so we create + * the hash on first use rather than at shared-memory attach time. + */ +static void +yb_pgss_init_normalization_set(void) +{ + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(YbPgssQueryNeedingNormalization); + ctl.entrysize = sizeof(YbPgssQueryNeedingNormalization); + ctl.hcxt = TopMemoryContext; + yb_pgss_queries_needing_normalization = hash_create("YB pgss queries needing normalization", + 16, + &ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +static void +yb_pgss_mark_needs_normalization(uint64 queryId) +{ + if (!yb_pgss_queries_needing_normalization) + yb_pgss_init_normalization_set(); + hash_search(yb_pgss_queries_needing_normalization, &queryId, HASH_ENTER, NULL); +} + +static bool +yb_pgss_needs_normalization(uint64 queryId) +{ + if (!yb_pgss_queries_needing_normalization) + return false; + return hash_search(yb_pgss_queries_needing_normalization, &queryId, + HASH_FIND, NULL) != NULL; +} + /* * Post-parse-analysis hook: mark query with a queryId */ @@ -850,6 +1481,8 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate) * anyway, so there's no need for an early entry. */ if (jstate && jstate->clocations_count > 0) + { + yb_pgss_mark_needs_normalization(query->queryId); pgss_store(pstate->p_sourcetext, query->queryId, query->stmt_location, @@ -860,7 +1493,9 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate) NULL, NULL, NULL, - jstate); + jstate, + false /* yb_is_sensitive_stmt */ ); + } } /* @@ -945,7 +1580,8 @@ pgss_planner(Query *parse, &bufusage, &walusage, NULL, - NULL); + NULL, + false /* yb_is_sensitive_stmt */ ); } else { @@ -1064,7 +1700,8 @@ pgss_ExecutorEnd(QueryDesc *queryDesc) &queryDesc->totaltime->bufusage, &queryDesc->totaltime->walusage, queryDesc->estate->es_jit ? &queryDesc->estate->es_jit->instr : NULL, - NULL); + NULL, + false /* yb_is_sensitive_stmt */ ); } if (prev_ExecutorEnd) @@ -1184,17 +1821,51 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, memset(&walusage, 0, sizeof(WalUsage)); WalUsageAccumDiff(&walusage, &pgWalUsage, &walusage_start); - pgss_store(queryString, - saved_queryId, - saved_stmt_location, - saved_stmt_len, - PGSS_EXEC, - INSTR_TIME_GET_MILLISEC(duration), - rows, - &bufusage, - &walusage, - NULL, - NULL); + /* + * YB note: UTILITY statements are the only kind that are treated as + * sensitive. The other pgss hooks (planner, analyze, end-of-execution) + * are not invoked for utility statements. + * + * For BACKFILL INDEX commands, generate a normalized query string so + * that all backfill calls for the same index are aggregated. + */ + if (IsA(parsetree, YbBackfillIndexStmt)) + { + char *norm_query; + int norm_query_len; + + YbBackfillIndexStmt *bf_stmt = + (YbBackfillIndexStmt *) parsetree; + norm_query = yb_generate_normalized_backfill_query(bf_stmt, &norm_query_len); + pgss_store(norm_query, + saved_queryId, + 0, + norm_query_len, + PGSS_EXEC, + INSTR_TIME_GET_MILLISEC(duration), + rows, + &bufusage, + &walusage, + NULL, + NULL, + false /* yb_is_sensitive_stmt */ ); + pfree(norm_query); + } + else + { + pgss_store(queryString, + saved_queryId, + saved_stmt_location, + saved_stmt_len, + PGSS_EXEC, + INSTR_TIME_GET_MILLISEC(duration), + rows, + &bufusage, + &walusage, + NULL, + NULL, + true /* yb_is_sensitive_stmt */ ); + } } else { @@ -1228,7 +1899,8 @@ pgss_store(const char *query, uint64 queryId, const BufferUsage *bufusage, const WalUsage *walusage, const struct JitInstrumentation *jitusage, - JumbleState *jstate) + JumbleState *jstate, + bool yb_is_sensitive_stmt) { pgssHashKey key; pgssEntry *entry; @@ -1241,12 +1913,17 @@ pgss_store(const char *query, uint64 queryId, if (!pgss || !pgss_hash) return; + if (yb_is_calling_internal_sql_for_ddl) + return; + /* * Nothing to do if compute_query_id isn't enabled and no other module * computed a query identifier. */ if (queryId == UINT64CONST(0)) + { return; + } /* * Confine our attention to the relevant part of the string, if the query @@ -1255,6 +1932,18 @@ pgss_store(const char *query, uint64 queryId, */ query = CleanQuerytext(query, &query_location, &query_len); + /* + * The query string may include multiple statements, so consider only the + * substring that we are interested in for redaction. Note that the + * substring in question does not contain a semi-colon at the end. + */ + if (yb_is_sensitive_stmt) + query = YbGetRedactedQueryString(pnstrdup(query, query_len), &query_len); + + if (yb_enable_query_diagnostics && !jstate) + YbQueryDiagnosticsAccumulatePgss(queryId, (YbQdPgssStoreKind) kind, + total_time, rows, bufusage, walusage, jitusage); + /* Set up key for hashtable search */ /* clear padding */ @@ -1293,6 +1982,17 @@ pgss_store(const char *query, uint64 queryId, &query_len); LWLockAcquire(pgss->lock, LW_SHARED); } + else if (yb_pgss_needs_normalization(queryId)) + { + /* + * YB: This query has constants that require normalization, but its + * shared hash table entry has been evicted or reset. We can't + * normalize without jstate, so discard the execution stats rather + * than storing unnormalized literals. The entry will be re-created + * with proper normalization on the next full parse cycle. + */ + goto done; + } /* Append new query text to file with only shared lock held */ stored = qtext_store(norm_query ? norm_query : query, query_len, @@ -1333,6 +2033,10 @@ pgss_store(const char *query, uint64 queryId, gc_qtexts(); } + if (yb_enable_query_diagnostics) + YbSetPgssNormalizedQueryText(queryId, entry->query_offset, + entry->query_len); + /* Increment the counts, except when jstate is not NULL */ if (!jstate) { @@ -1353,6 +2057,21 @@ pgss_store(const char *query, uint64 queryId, e->counters.calls[kind] += 1; e->counters.total_time[kind] += total_time; + /* + * Add record for only execution time for queries in latency + * histograms + */ + if (IsYugaByteEnabled() && kind == PGSS_EXEC) + { + int64_t tt_int = (int64_t) (total_time / yb_hdr_latency_res_ms); + + if (tt_int < yb_hdr_max_value) + hdr_record_value((hdr_histogram *) &e->yb_hdr_histogram, tt_int); + else + e->yb_slow_executions++; + } + + if (e->counters.calls[kind] == 1) { e->counters.min_time[kind] = total_time; @@ -1415,6 +2134,70 @@ pgss_store(const char *query, uint64 queryId, e->counters.jit_emission_time += INSTR_TIME_GET_MILLISEC(jitusage->emission_counter); } + if (kind == PGSS_EXEC) + { + /* + * YB: These stats are collected for both regular and utility + * statements, unlike EXPLAIN + */ + YbInstrumentation yb_instr = {0}; + + YbUpdateSessionStats((YbInstrumentation *) &yb_instr); + e->counters.yb_counters.counters[YB_INT_DOCDB_READ_RPCS] += + yb_instr.tbl_reads.count + yb_instr.index_reads.count; + e->counters.yb_counters.counters[YB_INT_DOCDB_WRITE_RPCS] += + yb_instr.write_flushes.count; + e->counters.yb_counters.counters[YB_INT_DOCDB_READ_OPS] += + yb_instr.tbl_read_ops + yb_instr.index_read_ops; + e->counters.yb_counters.counters[YB_INT_DOCDB_WRITE_OPS] += + yb_instr.tbl_writes + yb_instr.index_writes; + e->counters.yb_counters.counters[YB_INT_DOCDB_ROWS_SCANNED] += + yb_instr.tbl_reads.rows_scanned + yb_instr.index_reads.rows_scanned; + e->counters.yb_counters.counters[YB_INT_DOCDB_ROWS_RETURNED] += + yb_instr.tbl_reads.rows_received + yb_instr.index_reads.rows_received; + + e->counters.yb_counters.counters_dbl[YB_DBL_CATALOG_WAIT_TIME_MS] += + (yb_instr.catalog_reads.wait_time) / 1000000.0; + e->counters.yb_counters.counters_dbl[YB_DBL_DOCDB_WAIT_TIME_MS] += + (yb_instr.tbl_reads.wait_time + yb_instr.write_flushes.wait_time + + yb_instr.index_reads.wait_time) / 1000000.0; + + /* Update retry statistics, only after the statement has completed */ + e->counters.yb_counters.counters[YB_INT_CONFLICT_RETRIES] += + YbGetRetryCount(YB_TXN_CONFLICT); + e->counters.yb_counters.counters[YB_INT_READ_RESTART_RETRIES] += + YbGetRetryCount(YB_TXN_RESTART_READ); + e->counters.yb_counters.counters[YB_INT_TOTAL_RETRIES] += YbGetTotalRetryCount(); + + if (yb_instr.read_metrics.version) + { + e->counters.yb_counters.counters[YB_INT_DOCDB_OBSOLETE_ROWS_SCANNED] += + yb_instr.read_metrics.counters[YB_STORAGE_COUNTER_DOCDB_OBSOLETE_KEYS_FOUND]; + e->counters.yb_counters.counters[YB_INT_DOCDB_SEEKS] += + yb_instr.read_metrics.gauges[YB_STORAGE_GAUGE_REGULARDB_NUMBER_DB_SEEK]; + e->counters.yb_counters.counters[YB_INT_DOCDB_NEXTS] += + yb_instr.read_metrics.gauges[YB_STORAGE_GAUGE_REGULARDB_NUMBER_DB_NEXT]; + e->counters.yb_counters.counters[YB_INT_DOCDB_PREVS] += + yb_instr.read_metrics.gauges[YB_STORAGE_GAUGE_REGULARDB_NUMBER_DB_PREV]; + e->counters.yb_counters.counters_dbl[YB_DBL_DOCDB_READ_TIME_MS] += + yb_instr.read_metrics.events[YB_STORAGE_EVENT_QL_READ_LATENCY].sum / 1000.0; + } + + if (yb_instr.write_metrics.version) + { + e->counters.yb_counters.counters[YB_INT_DOCDB_OBSOLETE_ROWS_SCANNED] += + yb_instr.write_metrics.counters[YB_STORAGE_COUNTER_DOCDB_OBSOLETE_KEYS_FOUND]; + e->counters.yb_counters.counters[YB_INT_DOCDB_SEEKS] += + yb_instr.write_metrics.gauges[YB_STORAGE_GAUGE_REGULARDB_NUMBER_DB_SEEK]; + e->counters.yb_counters.counters[YB_INT_DOCDB_NEXTS] += + yb_instr.write_metrics.gauges[YB_STORAGE_GAUGE_REGULARDB_NUMBER_DB_NEXT]; + e->counters.yb_counters.counters[YB_INT_DOCDB_PREVS] += + yb_instr.write_metrics.gauges[YB_STORAGE_GAUGE_REGULARDB_NUMBER_DB_PREV]; + e->counters.yb_counters.counters_dbl[YB_DBL_DOCDB_WRITE_TIME_MS] += + yb_instr.write_metrics.events[YB_STORAGE_EVENT_QL_WRITE_LATENCY].sum / 1000.0; + } + } + SpinLockRelease(&e->mutex); } @@ -1432,6 +2215,9 @@ pgss_store(const char *query, uint64 queryId, Datum pg_stat_statements_reset_1_7(PG_FUNCTION_ARGS) { + if (yb_enable_query_diagnostics) + *yb_pgss_last_reset_time = GetCurrentTimestamp(); + Oid userid; Oid dbid; uint64 queryid; @@ -1451,6 +2237,9 @@ pg_stat_statements_reset_1_7(PG_FUNCTION_ARGS) Datum pg_stat_statements_reset(PG_FUNCTION_ARGS) { + if (yb_enable_query_diagnostics) + *yb_pgss_last_reset_time = GetCurrentTimestamp(); + entry_reset(0, 0, 0); PG_RETURN_VOID(); @@ -1461,10 +2250,14 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS) #define PG_STAT_STATEMENTS_COLS_V1_1 18 #define PG_STAT_STATEMENTS_COLS_V1_2 19 #define PG_STAT_STATEMENTS_COLS_V1_3 23 +#define YB_PG_STAT_STATEMENTS_COLS_V1_4 24 #define PG_STAT_STATEMENTS_COLS_V1_8 32 #define PG_STAT_STATEMENTS_COLS_V1_9 33 #define PG_STAT_STATEMENTS_COLS_V1_10 43 -#define PG_STAT_STATEMENTS_COLS 43 /* maximum of above */ +#define YB_PG_STAT_STATEMENTS_COLS_V1_10 44 +#define YB_V2_0_PG_STAT_STATEMENTS_COLS_V1_10 55 +#define YB_V2_1_PG_STAT_STATEMENTS_COLS_V1_10 61 +#define PG_STAT_STATEMENTS_COLS 61 /* maximum of above */ /* * Retrieve statement statistics. @@ -1476,6 +2269,46 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS) * expected API version is identified by embedding it in the C name of the * function. Unfortunately we weren't bright enough to do that for 1.1. */ +Datum +yb_pg_stat_statements_1_4(PG_FUNCTION_ARGS) +{ + bool showtext = PG_GETARG_BOOL(0); + + pg_stat_statements_internal(fcinfo, YB_PGSS_V1_4, showtext); + + return (Datum) 0; +} + +Datum +yb_2_0_pg_stat_statements_1_10(PG_FUNCTION_ARGS) +{ + bool showtext = PG_GETARG_BOOL(0); + + pg_stat_statements_internal(fcinfo, YB_V2_0_PGSS_V1_10, showtext); + + return (Datum) 0; +} + +Datum +yb_2_1_pg_stat_statements_1_10(PG_FUNCTION_ARGS) +{ + bool showtext = PG_GETARG_BOOL(0); + + pg_stat_statements_internal(fcinfo, YB_V2_1_PGSS_V1_10, showtext); + + return (Datum) 0; +} + +Datum +yb_pg_stat_statements_1_10(PG_FUNCTION_ARGS) +{ + bool showtext = PG_GETARG_BOOL(0); + + pg_stat_statements_internal(fcinfo, YB_PGSS_V1_10, showtext); + + return (Datum) 0; +} + Datum pg_stat_statements_1_10(PG_FUNCTION_ARGS) { @@ -1606,6 +2439,22 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo, if (api_version != PGSS_V1_10) elog(ERROR, "incorrect number of output arguments"); break; + case YB_PG_STAT_STATEMENTS_COLS_V1_4: + if (api_version != YB_PGSS_V1_4) + elog(ERROR, "incorrect number of output arguments"); + break; + case YB_PG_STAT_STATEMENTS_COLS_V1_10: + if (api_version != YB_PGSS_V1_10) + elog(ERROR, "incorrect number of output arguments"); + break; + case YB_V2_0_PG_STAT_STATEMENTS_COLS_V1_10: + if (api_version != YB_V2_0_PGSS_V1_10) + elog(ERROR, "incorrect number of output arguments"); + break; + case YB_V2_1_PG_STAT_STATEMENTS_COLS_V1_10: + if (api_version != YB_V2_1_PGSS_V1_10) + elog(ERROR, "incorrect number of output arguments"); + break; default: elog(ERROR, "incorrect number of output arguments"); } @@ -1840,6 +2689,40 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo, values[i++] = Float8GetDatumFast(tmp.jit_emission_time); } + if (api_version == YB_PGSS_V1_4 || api_version == YB_PGSS_V1_10 || + api_version == YB_V2_0_PGSS_V1_10 || api_version == YB_V2_1_PGSS_V1_10) + { + values[i++] = yb_get_histogram_jsonb_args(queryid, + entry->key.userid, + entry->key.dbid, + entry->key.toplevel); + } + + if (api_version >= YB_V2_0_PGSS_V1_10) + { + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_DOCDB_READ_RPCS]); + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_DOCDB_WRITE_RPCS]); + values[i++] = Float8GetDatumFast(tmp.yb_counters.counters_dbl[YB_DBL_CATALOG_WAIT_TIME_MS]); + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_DOCDB_READ_OPS]); + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_DOCDB_WRITE_OPS]); + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_DOCDB_ROWS_SCANNED]); + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_DOCDB_ROWS_RETURNED]); + values[i++] = Float8GetDatumFast(tmp.yb_counters.counters_dbl[YB_DBL_DOCDB_WAIT_TIME_MS]); + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_CONFLICT_RETRIES]); + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_READ_RESTART_RETRIES]); + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_TOTAL_RETRIES]); + } + + if (api_version >= YB_V2_1_PGSS_V1_10) + { + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_DOCDB_OBSOLETE_ROWS_SCANNED]); + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_DOCDB_SEEKS]); + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_DOCDB_NEXTS]); + values[i++] = Int64GetDatumFast(tmp.yb_counters.counters[YB_INT_DOCDB_PREVS]); + values[i++] = Float8GetDatumFast(tmp.yb_counters.counters_dbl[YB_DBL_DOCDB_READ_TIME_MS]); + values[i++] = Float8GetDatumFast(tmp.yb_counters.counters_dbl[YB_DBL_DOCDB_WRITE_TIME_MS]); + } + Assert(i == (api_version == PGSS_V1_0 ? PG_STAT_STATEMENTS_COLS_V1_0 : api_version == PGSS_V1_1 ? PG_STAT_STATEMENTS_COLS_V1_1 : api_version == PGSS_V1_2 ? PG_STAT_STATEMENTS_COLS_V1_2 : @@ -1847,6 +2730,10 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo, api_version == PGSS_V1_8 ? PG_STAT_STATEMENTS_COLS_V1_8 : api_version == PGSS_V1_9 ? PG_STAT_STATEMENTS_COLS_V1_9 : api_version == PGSS_V1_10 ? PG_STAT_STATEMENTS_COLS_V1_10 : + api_version == YB_PGSS_V1_4 ? YB_PG_STAT_STATEMENTS_COLS_V1_4 : + api_version == YB_PGSS_V1_10 ? YB_PG_STAT_STATEMENTS_COLS_V1_10 : + api_version == YB_V2_0_PGSS_V1_10 ? YB_V2_0_PG_STAT_STATEMENTS_COLS_V1_10 : + api_version == YB_V2_1_PGSS_V1_10 ? YB_V2_1_PG_STAT_STATEMENTS_COLS_V1_10 : -1 /* fail if you forget to update this assert */ )); tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); @@ -1908,7 +2795,8 @@ pgss_memsize(void) Size size; size = MAXALIGN(sizeof(pgssSharedState)); - size = add_size(size, hash_estimate_size(pgss_max, sizeof(pgssEntry))); + size = add_size(size, hash_estimate_size(pgss_max, + sizeof(pgssEntry) + cfg.counts_len * sizeof(count_t))); return size; } @@ -1959,6 +2847,10 @@ entry_alloc(pgssHashKey *key, Size query_offset, int query_len, int encoding, entry->query_offset = query_offset; entry->query_len = query_len; entry->encoding = encoding; + entry->yb_slow_executions = 0; + /* zero out histogram space when new entry is created */ + yb_hdr_reset(&entry->yb_hdr_histogram); + hdr_init_preallocated(&entry->yb_hdr_histogram, &cfg); } return entry; @@ -2204,17 +3096,19 @@ qtext_load_file(Size *buffer_size) } /* Allocate buffer; beware that off_t might be wider than size_t */ - if (stat.st_size <= MaxAllocHugeSize) - buf = (char *) palloc_extended(stat.st_size, MCXT_ALLOC_HUGE | MCXT_ALLOC_NO_OOM); - else + if ((yb_qtext_size_limit >= 0 && stat.st_size > yb_qtext_size_limit * 1024) || + !AllocHugeSizeIsValid(stat.st_size)) buf = NULL; + else + buf = (char *) palloc_extended(stat.st_size, MCXT_ALLOC_HUGE | MCXT_ALLOC_NO_OOM); + if (buf == NULL) { ereport(LOG, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"), - errdetail("Could not allocate enough memory to read file \"%s\".", - PGSS_TEXT_FILE))); + errdetail("Could not allocate %lld bytes needed to read file \"%s\".", + (long long) stat.st_size, PGSS_TEXT_FILE))); CloseTransientFile(fd); return NULL; } @@ -2523,6 +3417,26 @@ gc_qtexts(void) record_gc_qtexts(); } +/* + * Function that caches environmental variable + * FLAGS_TEST_yb_lwlock_crash_after_acquire_pg_stat_statements_reset. + * + * This avoids the process of checking the value of the environmental variable + * time and again. + */ +bool +yb_lwlock_crash_after_acquire_pg_stat_statements_reset() +{ + static int cached_value = -1; + + if (cached_value == -1) + { + cached_value = YBCIsEnvVarTrue("FLAGS_TEST_yb_lwlock_crash_after_acquire_pg_stat_statements_reset"); + } + return cached_value; + +} + /* * Release entries corresponding to parameters passed. */ @@ -2610,6 +3524,8 @@ entry_reset(Oid userid, Oid dbid, uint64 queryid) SpinLockRelease(&s->mutex); } + if (yb_lwlock_crash_after_acquire_pg_stat_statements_reset()) + kill(getpid(), 9); /* * Write new empty query file, perhaps even creating a new one to recover * if the file was missing. @@ -2881,3 +3797,264 @@ comp_location(const void *a, const void *b) else return 0; } + +/* + * DIRECT CALL IN PG_STAT_STATEMENTS.C + * Given queryid, userid, dbid and top_level, returns jsonb histogram of execution time + * of that query. Userid and dbid can be passed as NULL or 0 to use the + * currently connected userid and dbid. + */ +static Datum +yb_get_histogram_jsonb_args(uint64 queryid, Oid userid, Oid dbid, bool top_level) +{ + userid = userid != InvalidOid ? userid : GetUserId(); + dbid = dbid != InvalidOid ? dbid : MyDatabaseId; + pgssHashKey key; + pgssEntry *entry; + bool is_allowed_role = has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS); + + memset(&key, 0, sizeof(pgssHashKey)); + key.queryid = queryid; + key.userid = userid; + key.dbid = dbid; + key.toplevel = top_level; + + if (!is_allowed_role && userid != GetUserId()) + { + ereport(ERROR, + (errmsg("insufficient privilege to read this query detail"))); + PG_RETURN_DATUM(0); + } + + entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL); + + if (!entry) + { + ereport(ERROR, + (errmsg("invalid combination of queryid, userid, dbid, and top_level"), + errhint("Please refer to pg_stat_statements for the correct details."))); + PG_RETURN_DATUM(0); + } + + JsonbParseState *state = NULL; + + return yb_add_histogram_jsonb(state, &entry->yb_hdr_histogram, + entry->yb_slow_executions); +} + +/* + * Add histogram entries to the JsonbParseState. + */ +static Datum +yb_add_histogram_jsonb(JsonbParseState *state, hdr_histogram *h, + size_t yb_slow_executions) +{ + yb_hdr_iter iter; + + StringInfoData buf; + + initStringInfo(&buf); + + JsonbValue *res; + JsonbPair pair; + + pair.key.type = jbvString; + pair.value.type = jbvNumeric; + + MemoryContext tempContext = AllocSetContextCreate(CurrentMemoryContext, + "JSONB processing temporary context", + ALLOCSET_DEFAULT_SIZES); + MemoryContext oldContext = MemoryContextSwitchTo(tempContext); + + pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + hdr_iter_init(&iter, h); + while (hdr_iter_next(&iter)) + { + if (iter.count > 0) + { + resetStringInfo(&buf); + appendStringInfo(&buf, "[%.1f,%.1f)", + (iter.value_iterated_to) * yb_hdr_latency_res_ms, + (iter.highest_equivalent_value + 1) * yb_hdr_latency_res_ms); + yb_add_hdr_jsonb_object(state, buf.data, iter.count, &pair); + } + } + + if (yb_slow_executions > 0) + { + resetStringInfo(&buf); + appendStringInfo(&buf, "[%.1f,)", yb_hdr_max_value * yb_hdr_latency_res_ms); + yb_add_hdr_jsonb_object(state, buf.data, yb_slow_executions, &pair); + } + + res = pushJsonbValue(&state, WJB_END_ARRAY, NULL); + MemoryContextSwitchTo(oldContext); + Jsonb *ret = JsonbValueToJsonb(res); + + MemoryContextDelete(tempContext); + pfree(buf.data); + + PG_RETURN_POINTER(ret); +} + +static void +yb_add_hdr_jsonb_object(JsonbParseState *state, char *buf, + count_t count, JsonbPair *pair) +{ + pair->key.val.string.len = strlen(buf); + pair->key.val.string.val = pstrdup(buf); + pair->value.val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric, + (int64_t) count)); + + (void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + (void) pushJsonbValue(&state, WJB_KEY, &pair->key); + (void) pushJsonbValue(&state, WJB_VALUE, &pair->value); + (void) pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} + +/* + * Initialized as callable function in Postgres (kept for debugging) + * Given queryid, userid, and dbid, returns jsonb histogram of execution time of + * that query. Userid and dbid can be passed as NULL, 0, or empty parameters to + * use the currently connected userid and dbid. + */ +Datum +yb_get_histogram_jsonb(PG_FUNCTION_ARGS) +{ + uint64 queryid = PG_GETARG_INT64(0); + Oid userid = (PG_NARGS() >= 2 && PG_GETARG_OID(1) != InvalidOid ? + PG_GETARG_OID(1) : + InvalidOid); + Oid dbid = (PG_NARGS() >= 3 && PG_GETARG_OID(2) != InvalidOid ? + PG_GETARG_OID(2) : + InvalidOid); + bool top_level = PG_NARGS() == 4 ? PG_GETARG_BOOL(3) : false; + + /* + * Check for null queryid + */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + return yb_get_histogram_jsonb_args(queryid, userid, dbid, top_level); +} + +/* +* Wipes histogram counts array only, expects pgssEntry memory cleanup to +* handle deallocating the actual histogram struct +*/ +static void +yb_hdr_reset(hdr_histogram *h) +{ + memset(h, 0, sizeof(hdr_histogram) + (sizeof(count_t) * h->counts_len)); +} + +static bool +yb_track_nested_queries(void) +{ + return pgss_track == PGSS_TRACK_ALL; +} + +/* + * Get the normalized query text from the pgss_query_texts.stat file + * and copy it to the normalized_query buffer. + * Note that normalized_query is expected to be a buffer of size at max + * 1024 bytes. + * + * Returns: + * YB_DIAGNOSTICS_SUCCESS if normalized query fetched successfully, + * YB_DIAGNOSTICS_ERROR otherwise. + */ +static int +YbGetPgssNormalizedQueryText(Size query_offset, int actual_query_len, char *normalized_query) +{ + char *qbuffer = NULL; + Size qbuffer_size = 0; + int query_len = Min(actual_query_len, YB_QD_MAX_PGSS_QUERY_LEN); + + qbuffer = qtext_load_file(&qbuffer_size); + + if (qbuffer == NULL) + { + ereport(LOG, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("could not load pgss query text file"))); + return YB_DIAGNOSTICS_ERROR; + } + + /* + * Its important to fetch the query and then memcpy to normalized_query, + * as qtext_fetch requires query length to be exact length of the query, + * whereas query_len can be 1024 bytes in case pgss_query_len >= 1024. + */ + char *fetched_query = qtext_fetch(query_offset, actual_query_len, + qbuffer, qbuffer_size); + + if (fetched_query == NULL) + { + ereport(LOG, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("could not load pgss query text file"))); + pfree(qbuffer); + return YB_DIAGNOSTICS_ERROR; + } + + memcpy(normalized_query, fetched_query, query_len); + normalized_query[query_len] = '\0'; /* Ensure null-termination */ + + pfree(qbuffer); + return YB_DIAGNOSTICS_SUCCESS; +} + +/* + * YB: Generate a normalized query string for a BACKFILL command. + * + * Replaces varying parameters (bfinstr, read_time, partition key, row keys) + * with $N parameter placeholders so that all BACKFILL calls for the same + * index are represented by a single normalized query in pg_stat_statements. + * + * Returns a palloc'd string. + */ +static char * +yb_generate_normalized_backfill_query(YbBackfillIndexStmt *stmt, + int *query_len_p) +{ + StringInfoData buf; + ListCell *lc; + bool first = true; + int param_num = 1; + + initStringInfo(&buf); + + appendStringInfoString(&buf, "BACKFILL INDEX "); + + /* Append OID list (these are part of the query identity, not abstracted) */ + foreach(lc, stmt->oid_list) + { + if (!first) + appendStringInfoString(&buf, ", "); + appendStringInfo(&buf, "%u", lfirst_oid(lc)); + first = false; + } + + /* Append normalized parameters */ + if (stmt->bfinfo->bfinstr) + appendStringInfo(&buf, " WITH $%d", param_num++); + + appendStringInfo(&buf, " READ TIME $%d", param_num++); + + if (stmt->bfinfo->row_bounds) + { + if (stmt->bfinfo->row_bounds->partition_key) + appendStringInfo(&buf, " PARTITION $%d", param_num++); + + if (stmt->bfinfo->row_bounds->row_key_start) + appendStringInfo(&buf, " FROM $%d", param_num++); + + if (stmt->bfinfo->row_bounds->row_key_end) + appendStringInfo(&buf, " TO $%d", param_num++); + } + + *query_len_p = buf.len; + return buf.data; +} diff --git a/contrib/pg_stat_statements/pg_stat_statements.control b/contrib/pg_stat_statements/pg_stat_statements.control index 0747e48138373..14c414fe165ff 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.control +++ b/contrib/pg_stat_statements/pg_stat_statements.control @@ -1,5 +1,5 @@ # pg_stat_statements extension comment = 'track planning and execution statistics of all SQL statements executed' -default_version = '1.10' +default_version = '1.10-yb-2.1' module_pathname = '$libdir/pg_stat_statements' relocatable = true diff --git a/contrib/pg_stat_statements/sql/yb.orig.backfill_queryid.sql b/contrib/pg_stat_statements/sql/yb.orig.backfill_queryid.sql new file mode 100644 index 0000000000000..64651c7861da9 --- /dev/null +++ b/contrib/pg_stat_statements/sql/yb.orig.backfill_queryid.sql @@ -0,0 +1,44 @@ +-- Test: BACKFILL INDEX commands are aggregated into a single pg_stat_statements entry per index. +CREATE EXTENSION pg_stat_statements; + +SET pg_stat_statements.track_utility = TRUE; + +CREATE TABLE backfill_test(k INT PRIMARY KEY, v INT); +INSERT INTO backfill_test SELECT i, i FROM generate_series(1, 5000) AS i; + +--Test: there should be exactly one BACKFILL entry. +SELECT pg_stat_statements_reset(); +CREATE INDEX ON backfill_test(v); + +SELECT COUNT(*) AS num_backfill_entries +FROM pg_stat_statements +WHERE query LIKE 'BACKFILL%'; + +--Test: query text should be normalized with $N placeholders. +SELECT + query LIKE 'BACKFILL INDEX % WITH $1 READ TIME $2 PARTITION $3' + AS query_is_normalized +FROM pg_stat_statements +WHERE query LIKE 'BACKFILL%'; + +--Test: each index should have its own separate entry. +CREATE INDEX ON backfill_test(k, v); + +SELECT COUNT(*) AS num_backfill_entries +FROM pg_stat_statements +WHERE query LIKE 'BACKFILL%'; + +--Verify that normal DML normalization is unaffected. +SELECT pg_stat_statements_reset(); + +SELECT * FROM backfill_test WHERE k = 1; +SELECT * FROM backfill_test WHERE k = 2; +SELECT * FROM backfill_test WHERE k = 3; + +SELECT COUNT(*), SUM(calls) +FROM pg_stat_statements +WHERE query LIKE 'SELECT %FROM backfill_test WHERE k%'; + +-- Cleanup. +DROP TABLE backfill_test; +DROP EXTENSION pg_stat_statements; diff --git a/contrib/pg_stat_statements/sql/yb.orig.pg_stat_statements.sql b/contrib/pg_stat_statements/sql/yb.orig.pg_stat_statements.sql new file mode 100644 index 0000000000000..f05772b43f460 --- /dev/null +++ b/contrib/pg_stat_statements/sql/yb.orig.pg_stat_statements.sql @@ -0,0 +1,44 @@ +CREATE EXTENSION pg_stat_statements; + +-- +-- simple and compound statements +-- +SET pg_stat_statements.track_utility = TRUE; +SET pg_stat_statements.track_planning = TRUE; + +-- +-- create / alter user +-- +SELECT pg_stat_statements_reset(); +CREATE USER foo PASSWORD 'fooooooo'; +ALTER USER foo PASSWORD 'foo2'; +CREATE ROLE fizzbuzz PASSWORD 'barrr'; +ALTER ROLE fizzbuzz PASSWORD 'barrr2'; +-- Passwords of all lengths must be redacted. +CREATE ROLE minimalpass PASSWORD '1'; +CREATE ROLE smallpass PASSWORD '123'; +CREATE ROLE largepass PASSWORD '123456790123456'; +-- A NULL password must also be redacted. +ALTER ROLE fizzbuzz PASSWORD NULL; +-- Password must be redacted irrespective of its position in the query. +ALTER ROLE fizzbuzz SUPERUSER PASSWORD 'test' NOLOGIN; + +-- Test that pgss handles pl/pgSQL statements well. +CREATE FUNCTION returnArg(variadic NUMERIC[]) RETURNS NUMERIC LANGUAGE plpgsql AS $$ +DECLARE + r INTEGER := $1[1]; +BEGIN + RETURN r; +END; +$$; +SELECT returnArg(10, 2); +SELECT returnArg(1, 4, 7); + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; +DROP USER foo; +DROP ROLE fizzbuzz; +DROP ROLE minimalpass; +DROP ROLE smallpass; +DROP ROLE largepass; + +DROP EXTENSION pg_stat_statements; diff --git a/contrib/pg_stat_statements/sql/yb.port.pg_stat_statements.sql b/contrib/pg_stat_statements/sql/yb.port.pg_stat_statements.sql new file mode 100644 index 0000000000000..abdf74f60c4b8 --- /dev/null +++ b/contrib/pg_stat_statements/sql/yb.port.pg_stat_statements.sql @@ -0,0 +1,454 @@ +DROP EXTENSION IF EXISTS pg_stat_statements; +CREATE EXTENSION pg_stat_statements; + +-- +-- simple and compound statements +-- +SET pg_stat_statements.track_utility = FALSE; +SET pg_stat_statements.track_planning = TRUE; +SELECT pg_stat_statements_reset(); + +SELECT 1 AS "int"; + +SELECT 'hello' + -- multiline + AS "text"; + +SELECT 'world' AS "text"; + +-- transaction +BEGIN; +SELECT 1 AS "int"; +SELECT 'hello' AS "text"; +COMMIT; + +-- compound transaction +BEGIN \; +SELECT 2.0 AS "float" \; +SELECT 'world' AS "text" \; +COMMIT; + +-- compound with empty statements and spurious leading spacing +\;\; SELECT 3 + 3 \;\;\; SELECT ' ' || ' !' \;\; SELECT 1 + 4 \;; + +-- non ;-terminated statements +SELECT 1 + 1 + 1 AS "add" \gset +SELECT :add + 1 + 1 AS "add" \; +SELECT :add + 1 + 1 AS "add" \gset + +-- set operator +SELECT 1 AS i UNION SELECT 2 ORDER BY i; + +-- ? operator +select '{"a":1, "b":2}'::jsonb ? 'b'; + +-- cte +WITH t(f) AS ( + VALUES (1.0), (2.0) +) + SELECT f FROM t ORDER BY f; + +-- prepared statement with parameter +PREPARE pgss_test (int) AS SELECT $1, 'test' LIMIT 1; +EXECUTE pgss_test(1); +DEALLOCATE pgss_test; + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- CRUD: INSERT SELECT UPDATE DELETE on test table +-- +SELECT pg_stat_statements_reset(); + +-- utility "create table" should not be shown +CREATE TEMP TABLE test (a int, b char(20)); + +INSERT INTO test VALUES(generate_series(1, 10), 'aaa'); +UPDATE test SET b = 'bbb' WHERE a > 7; +DELETE FROM test WHERE a > 9; + +-- explicit transaction +BEGIN; +UPDATE test SET b = '111' WHERE a = 1 ; +COMMIT; + +BEGIN \; +UPDATE test SET b = '222' WHERE a = 2 \; +COMMIT ; + +UPDATE test SET b = '333' WHERE a = 3 \; +UPDATE test SET b = '444' WHERE a = 4 ; + +BEGIN \; +UPDATE test SET b = '555' WHERE a = 5 \; +UPDATE test SET b = '666' WHERE a = 6 \; +COMMIT ; + +-- many INSERT values +INSERT INTO test (a, b) VALUES (1, 'a'), (2, 'b'), (3, 'c'); + +-- SELECT with constants +SELECT * FROM test WHERE a > 5 ORDER BY a ; + +SELECT * + FROM test + WHERE a > 9 + ORDER BY a ; + +-- SELECT without constants +SELECT * FROM test ORDER BY a; + +-- SELECT with IN clause +SELECT * FROM test WHERE a IN (1, 2, 3, 4, 5); + +-- MERGE +-- YB note: ERROR: MERGE not supported yet hence failing. Check after support added. +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED THEN UPDATE SET b = st.b || st.a::text; + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- INSERT, UPDATE, DELETE on test table to validate WAL generation metrics +-- +SELECT pg_stat_statements_reset(); + +-- utility "create table" should not be shown +CREATE TABLE pgss_test (a int, b char(20)); + +INSERT INTO pgss_test VALUES(generate_series(1, 10), 'aaa'); +UPDATE pgss_test SET b = 'bbb' WHERE a > 7; +DELETE FROM pgss_test WHERE a > 9; +-- DROP test table +SET pg_stat_statements.track_utility = TRUE; +DROP TABLE pgss_test; +SET pg_stat_statements.track_utility = FALSE; + +-- Check WAL is generated for the above statements +-- YB Note: PG WAL not used in YB, out might differ from correct one +-- in pg_stat_statements.out. +SELECT query, calls, rows, +wal_bytes > 0 as wal_bytes_generated, +wal_records > 0 as wal_records_generated, +wal_records = rows as wal_records_as_rows +FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- pg_stat_statements.track = none +-- +SET pg_stat_statements.track = 'none'; +SELECT pg_stat_statements_reset(); + +SELECT 1 AS "one"; +SELECT 1 + 1 AS "two"; + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- pg_stat_statements.track = top +-- +SET pg_stat_statements.track = 'top'; +SELECT pg_stat_statements_reset(); + +DO LANGUAGE plpgsql $$ +BEGIN + -- this is a SELECT + PERFORM 'hello world'::TEXT; +END; +$$; + +-- PL/pgSQL function +CREATE FUNCTION PLUS_TWO(i INTEGER) RETURNS INTEGER AS $$ +DECLARE + r INTEGER; +BEGIN + SELECT (i + 1 + 1.0)::INTEGER INTO r; + RETURN r; +END; $$ LANGUAGE plpgsql; + +SELECT PLUS_TWO(3); +SELECT PLUS_TWO(7); + +-- SQL function --- use LIMIT to keep it from being inlined +CREATE FUNCTION PLUS_ONE(i INTEGER) RETURNS INTEGER AS +$$ SELECT (i + 1.0)::INTEGER LIMIT 1 $$ LANGUAGE SQL; + +SELECT PLUS_ONE(8); +SELECT PLUS_ONE(10); + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- pg_stat_statements.track = all +-- +SET pg_stat_statements.track = 'all'; +SELECT pg_stat_statements_reset(); + +-- we drop and recreate the functions to avoid any caching funnies +DROP FUNCTION PLUS_ONE(INTEGER); +DROP FUNCTION PLUS_TWO(INTEGER); + +-- PL/pgSQL function +CREATE FUNCTION PLUS_TWO(i INTEGER) RETURNS INTEGER AS $$ +DECLARE + r INTEGER; +BEGIN + SELECT (i + 1 + 1.0)::INTEGER INTO r; + RETURN r; +END; $$ LANGUAGE plpgsql; + +SELECT PLUS_TWO(-1); +SELECT PLUS_TWO(2); + +-- SQL function --- use LIMIT to keep it from being inlined +CREATE FUNCTION PLUS_ONE(i INTEGER) RETURNS INTEGER AS +$$ SELECT (i + 1.0)::INTEGER LIMIT 1 $$ LANGUAGE SQL; + +SELECT PLUS_ONE(3); +SELECT PLUS_ONE(1); + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- queries with locking clauses +-- +CREATE TABLE pgss_a (id integer PRIMARY KEY); +CREATE TABLE pgss_b (id integer PRIMARY KEY, a_id integer REFERENCES pgss_a); + +SELECT pg_stat_statements_reset(); + +-- control query +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id; + +-- test range tables +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_a; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_a, pgss_b; -- matches plain "FOR UPDATE" +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b, pgss_a; + +-- test strengths +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR NO KEY UPDATE; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR SHARE; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR KEY SHARE; + +-- test wait policies +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE NOWAIT; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE SKIP LOCKED; + +SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + +DROP TABLE pgss_a, pgss_b CASCADE; + +-- +-- utility commands +-- +SET pg_stat_statements.track_utility = TRUE; +SELECT pg_stat_statements_reset(); + +SELECT 1; +CREATE INDEX test_b ON test(b); +DROP TABLE test \; +DROP TABLE IF EXISTS test \; +DROP FUNCTION PLUS_ONE(INTEGER); +DROP TABLE IF EXISTS test \; +DROP TABLE IF EXISTS test \; +DROP FUNCTION IF EXISTS PLUS_ONE(INTEGER); +DROP FUNCTION PLUS_TWO(INTEGER); + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- Track the total number of rows retrieved or affected by the utility +-- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW, +-- REFRESH MATERIALIZED VIEW and SELECT INTO +-- +SELECT pg_stat_statements_reset(); + +CREATE TABLE pgss_ctas AS SELECT a, 'ctas' b FROM generate_series(1, 10) a; +SELECT generate_series(1, 10) c INTO pgss_select_into; +COPY pgss_ctas (a, b) FROM STDIN; +11 copy +12 copy +13 copy +\. +CREATE MATERIALIZED VIEW pgss_matv AS SELECT * FROM pgss_ctas; +REFRESH MATERIALIZED VIEW pgss_matv; +BEGIN; +-- YB note: added order by a to correct sorted order for cursor. +DECLARE pgss_cursor CURSOR FOR SELECT * FROM pgss_matv ORDER BY a; +FETCH NEXT pgss_cursor; +FETCH FORWARD 5 pgss_cursor; +FETCH FORWARD ALL pgss_cursor; +COMMIT; + +SELECT query, plans, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- Track user activity and reset them +-- +SELECT pg_stat_statements_reset(); +CREATE ROLE regress_stats_user1; +CREATE ROLE regress_stats_user2; + +SET ROLE regress_stats_user1; + +SELECT 1 AS "ONE"; +SELECT 1+1 AS "TWO"; + +RESET ROLE; +SET ROLE regress_stats_user2; + +SELECT 1 AS "ONE"; +SELECT 1+1 AS "TWO"; + +RESET ROLE; +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- Don't reset anything if any of the parameter is NULL +-- +SELECT pg_stat_statements_reset(NULL); +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- remove query ('SELECT $1+$2 AS "TWO"') executed by regress_stats_user2 +-- in the current_database +-- +SELECT pg_stat_statements_reset( + (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user2'), + (SELECT d.oid FROM pg_database As d where datname = current_database()), + (SELECT s.queryid FROM pg_stat_statements AS s + WHERE s.query = 'SELECT $1+$2 AS "TWO"' LIMIT 1)); +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- remove query ('SELECT $1 AS "ONE"') executed by two users +-- +SELECT pg_stat_statements_reset(0,0,s.queryid) + FROM pg_stat_statements AS s WHERE s.query = 'SELECT $1 AS "ONE"'; +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- remove query of a user (regress_stats_user1) +-- +SELECT pg_stat_statements_reset(r.oid) + FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user1'; +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- reset all +-- +SELECT pg_stat_statements_reset(0,0,0); +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- cleanup +-- +DROP ROLE regress_stats_user1; +DROP ROLE regress_stats_user2; +DROP MATERIALIZED VIEW pgss_matv; +DROP TABLE pgss_ctas; +DROP TABLE pgss_select_into; + +-- +-- [re]plan counting +-- +SELECT pg_stat_statements_reset(); +CREATE TABLE test (); +PREPARE prep1 AS SELECT COUNT(*) FROM test; +EXECUTE prep1; +EXECUTE prep1; +EXECUTE prep1; +ALTER TABLE test ADD COLUMN x int; +EXECUTE prep1; +SELECT 42; +SELECT 42; +SELECT 42; +SELECT query, plans, calls, rows FROM pg_stat_statements + WHERE query NOT LIKE 'PREPARE%' ORDER BY query COLLATE "C"; +-- for the prepared statement we expect at least one replan, but cache +-- invalidations could force more +SELECT query, plans >= 2 AND plans <= calls AS plans_ok, calls, rows FROM pg_stat_statements + WHERE query LIKE 'PREPARE%' ORDER BY query COLLATE "C"; + +-- +-- access to pg_stat_statements_info view +-- +SELECT pg_stat_statements_reset(); +SELECT dealloc FROM pg_stat_statements_info; + +-- +-- top level handling +-- +SET pg_stat_statements.track = 'top'; +DELETE FROM test; +DO $$ +BEGIN + DELETE FROM test; +END; +$$ LANGUAGE plpgsql; +SELECT query, toplevel, plans, calls FROM pg_stat_statements WHERE query LIKE '%DELETE%' ORDER BY query COLLATE "C", toplevel; + +SET pg_stat_statements.track = 'all'; +DELETE FROM test; +DO $$ +BEGIN + DELETE FROM test; +END; +$$ LANGUAGE plpgsql; +SELECT query, toplevel, plans, calls FROM pg_stat_statements WHERE query LIKE '%DELETE%' ORDER BY query COLLATE "C", toplevel; + +-- FROM [ONLY] +CREATE TABLE tbl_inh(id integer); +CREATE TABLE tbl_inh_1() INHERITS (tbl_inh); +INSERT INTO tbl_inh_1 SELECT 1; + +SELECT * FROM tbl_inh; +SELECT * FROM ONLY tbl_inh; + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%FROM%tbl_inh%'; + +-- WITH TIES +CREATE TABLE limitoption AS SELECT 0 AS val FROM generate_series(1, 10); +SELECT * +FROM limitoption +WHERE val < 2 +ORDER BY val +FETCH FIRST 2 ROWS WITH TIES; + +SELECT * +FROM limitoption +WHERE val < 2 +ORDER BY val +FETCH FIRST 2 ROW ONLY; + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%FETCH FIRST%'; + +-- GROUP BY [DISTINCT] +SELECT a, b, c +FROM (VALUES (1, 2, 3), (4, NULL, 6), (7, 8, 9)) AS t (a, b, c) +GROUP BY ROLLUP(a, b), rollup(a, c) +ORDER BY a, b, c; +SELECT a, b, c +FROM (VALUES (1, 2, 3), (4, NULL, 6), (7, 8, 9)) AS t (a, b, c) +GROUP BY DISTINCT ROLLUP(a, b), rollup(a, c) +ORDER BY a, b, c; + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%GROUP BY%ROLLUP%'; + +-- GROUPING SET agglevelsup +SELECT ( + SELECT ( + SELECT GROUPING(a,b) FROM (VALUES (1)) v2(c) + ) FROM (VALUES (1,2)) v1(a,b) GROUP BY (a,b) +) FROM (VALUES(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); +SELECT ( + SELECT ( + SELECT GROUPING(e,f) FROM (VALUES (1)) v2(c) + ) FROM (VALUES (1,2)) v1(a,b) GROUP BY (a,b) +) FROM (VALUES(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%SELECT GROUPING%'; + +DROP EXTENSION pg_stat_statements; diff --git a/contrib/pg_stat_statements/yb_schedule b/contrib/pg_stat_statements/yb_schedule new file mode 100644 index 0000000000000..af067281fe734 --- /dev/null +++ b/contrib/pg_stat_statements/yb_schedule @@ -0,0 +1,5 @@ +# contrib/pg_stat_statements/yb_schedule + +test: yb.port.pg_stat_statements +test: yb.orig.pg_stat_statements +test: yb.orig.backfill_queryid diff --git a/contrib/pg_trgm/Makefile b/contrib/pg_trgm/Makefile index 1fbdc9ec1ef43..9e451e3f1e8f0 100644 --- a/contrib/pg_trgm/Makefile +++ b/contrib/pg_trgm/Makefile @@ -9,7 +9,7 @@ OBJS = \ trgm_regexp.o EXTENSION = pg_trgm -DATA = pg_trgm--1.5--1.6.sql pg_trgm--1.4--1.5.sql pg_trgm--1.3--1.4.sql \ +DATA = pg_trgm--1.4-yb-1.0--1.6-yb-1.0.sql pg_trgm--1.4--1.4-yb-1.0.sql pg_trgm--1.3--1.4.sql \ pg_trgm--1.3.sql pg_trgm--1.2--1.3.sql pg_trgm--1.1--1.2.sql \ pg_trgm--1.0--1.1.sql PGFILEDESC = "pg_trgm - trigram matching" diff --git a/contrib/pg_trgm/expected/yb.port.pg_strict_word_trgm.out b/contrib/pg_trgm/expected/yb.port.pg_strict_word_trgm.out new file mode 100644 index 0000000000000..77b631ccc9dfa --- /dev/null +++ b/contrib/pg_trgm/expected/yb.port.pg_strict_word_trgm.out @@ -0,0 +1,122 @@ +DROP INDEX trgm_idx2; +\copy test_trgm3 from 'data/trgm2.data' +ERROR: relation "test_trgm3" does not exist +-- reduce noise +set extra_float_digits = 0; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 +(17 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; + t | sml +------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 +(4 rows) + +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 +(17 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; + t | sml +------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 +(4 rows) + +select t <->>> 'Alaikallupoddakulam', t from test_trgm2 order by t <->>> 'Alaikallupoddakulam' limit 7; + ?column? | t +----------+-------------------------- + 0 | Alaikallupoddakulam + 0.25 | Alaikallupodda Alankulam + 0.32 | Alaikalluppodda Kulam + 0.615385 | Mulaikallu Kulam + 0.724138 | Koraikalapu Kulam + 0.75 | Vaikaliththevakulam + 0.766667 | Karaivaikal Kulam +(7 rows) + +create index trgm_idx2 on test_trgm2 using gist (t gist_trgm_ops); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +create index trgm_idx2 on test_trgm2 using gin (t gin_trgm_ops); +set enable_seqscan=off; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 4. +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 6. +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 4. +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 6. +set "pg_trgm.strict_word_similarity_threshold" to 0.4; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 5. +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 7. +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 5. +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 7. +set "pg_trgm.strict_word_similarity_threshold" to 0.2; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 6. +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 9. +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 6. +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 9. diff --git a/contrib/pg_trgm/expected/yb.port.pg_trgm.out b/contrib/pg_trgm/expected/yb.port.pg_trgm.out new file mode 100644 index 0000000000000..f6d5e544ccc25 --- /dev/null +++ b/contrib/pg_trgm/expected/yb.port.pg_trgm.out @@ -0,0 +1,1636 @@ +CREATE EXTENSION pg_trgm; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); +ERROR: cannot validate operator family without ordered data +--backslash is used in tests below, installcheck will fail if +--standard_conforming_string is off +set standard_conforming_strings=on; +-- reduce noise +set extra_float_digits = 0; +select show_trgm(''); + show_trgm +----------- + {} +(1 row) + +select show_trgm('(*&^$@%@'); + show_trgm +----------- + {} +(1 row) + +select show_trgm('a b c'); + show_trgm +--------------------------------------- + {" a"," b"," c"," a "," b "," c "} +(1 row) + +select show_trgm(' a b c '); + show_trgm +--------------------------------------- + {" a"," b"," c"," a "," b "," c "} +(1 row) + +select show_trgm('aA bB cC'); + show_trgm +--------------------------------------------------------- + {" a"," b"," c"," aa"," bb"," cc","aa ","bb ","cc "} +(1 row) + +select show_trgm(' aA bB cC '); + show_trgm +--------------------------------------------------------- + {" a"," b"," c"," aa"," bb"," cc","aa ","bb ","cc "} +(1 row) + +select show_trgm('a b C0*%^'); + show_trgm +--------------------------------------------- + {" a"," b"," c"," a "," b "," c0","c0 "} +(1 row) + +select similarity('wow','WOWa '); + similarity +------------ + 0.5 +(1 row) + +select similarity('wow',' WOW '); + similarity +------------ + 1 +(1 row) + +select similarity('---', '####---'); + similarity +------------ + 0 +(1 row) + +CREATE TABLE test_trgm(t text COLLATE "C"); +\copy test_trgm from 'data/trgm.data' +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 1 + qwertyu0980 | 0.714286 + qwertyu0981 | 0.714286 + qwertyu0982 | 0.714286 + qwertyu0983 | 0.714286 + qwertyu0984 | 0.714286 + qwertyu0985 | 0.714286 + qwertyu0986 | 0.714286 + qwertyu0987 | 0.714286 + qwertyu0989 | 0.714286 + qwertyu0088 | 0.6 + qwertyu0098 | 0.6 + qwertyu0188 | 0.6 + qwertyu0288 | 0.6 + qwertyu0388 | 0.6 + qwertyu0488 | 0.6 + qwertyu0588 | 0.6 + qwertyu0688 | 0.6 + qwertyu0788 | 0.6 + qwertyu0888 | 0.6 + qwertyu0900 | 0.6 + qwertyu0901 | 0.6 + qwertyu0902 | 0.6 + qwertyu0903 | 0.6 + qwertyu0904 | 0.6 + qwertyu0905 | 0.6 + qwertyu0906 | 0.6 + qwertyu0907 | 0.6 + qwertyu0908 | 0.6 + qwertyu0909 | 0.6 + qwertyu0910 | 0.6 + qwertyu0911 | 0.6 + qwertyu0912 | 0.6 + qwertyu0913 | 0.6 + qwertyu0914 | 0.6 + qwertyu0915 | 0.6 + qwertyu0916 | 0.6 + qwertyu0917 | 0.6 + qwertyu0918 | 0.6 + qwertyu0919 | 0.6 + qwertyu0920 | 0.6 + qwertyu0921 | 0.6 + qwertyu0922 | 0.6 + qwertyu0923 | 0.6 + qwertyu0924 | 0.6 + qwertyu0925 | 0.6 + qwertyu0926 | 0.6 + qwertyu0927 | 0.6 + qwertyu0928 | 0.6 + qwertyu0929 | 0.6 + qwertyu0930 | 0.6 + qwertyu0931 | 0.6 + qwertyu0932 | 0.6 + qwertyu0933 | 0.6 + qwertyu0934 | 0.6 + qwertyu0935 | 0.6 + qwertyu0936 | 0.6 + qwertyu0937 | 0.6 + qwertyu0938 | 0.6 + qwertyu0939 | 0.6 + qwertyu0940 | 0.6 + qwertyu0941 | 0.6 + qwertyu0942 | 0.6 + qwertyu0943 | 0.6 + qwertyu0944 | 0.6 + qwertyu0945 | 0.6 + qwertyu0946 | 0.6 + qwertyu0947 | 0.6 + qwertyu0948 | 0.6 + qwertyu0949 | 0.6 + qwertyu0950 | 0.6 + qwertyu0951 | 0.6 + qwertyu0952 | 0.6 + qwertyu0953 | 0.6 + qwertyu0954 | 0.6 + qwertyu0955 | 0.6 + qwertyu0956 | 0.6 + qwertyu0957 | 0.6 + qwertyu0958 | 0.6 + qwertyu0959 | 0.6 + qwertyu0960 | 0.6 + qwertyu0961 | 0.6 + qwertyu0962 | 0.6 + qwertyu0963 | 0.6 + qwertyu0964 | 0.6 + qwertyu0965 | 0.6 + qwertyu0966 | 0.6 + qwertyu0967 | 0.6 + qwertyu0968 | 0.6 + qwertyu0969 | 0.6 + qwertyu0970 | 0.6 + qwertyu0971 | 0.6 + qwertyu0972 | 0.6 + qwertyu0973 | 0.6 + qwertyu0974 | 0.6 + qwertyu0975 | 0.6 + qwertyu0976 | 0.6 + qwertyu0977 | 0.6 + qwertyu0978 | 0.6 + qwertyu0979 | 0.6 + qwertyu0990 | 0.6 + qwertyu0991 | 0.6 + qwertyu0992 | 0.6 + qwertyu0993 | 0.6 + qwertyu0994 | 0.6 + qwertyu0995 | 0.6 + qwertyu0996 | 0.6 + qwertyu0997 | 0.6 + qwertyu0998 | 0.6 + qwertyu0999 | 0.6 + qwertyu0001 | 0.5 + qwertyu0002 | 0.5 + qwertyu0003 | 0.5 + qwertyu0004 | 0.5 + qwertyu0005 | 0.5 + qwertyu0006 | 0.5 + qwertyu0007 | 0.5 + qwertyu0008 | 0.5 + qwertyu0009 | 0.5 + qwertyu0010 | 0.5 + qwertyu0011 | 0.5 + qwertyu0012 | 0.5 + qwertyu0013 | 0.5 + qwertyu0014 | 0.5 + qwertyu0015 | 0.5 + qwertyu0016 | 0.5 + qwertyu0017 | 0.5 + qwertyu0018 | 0.5 + qwertyu0019 | 0.5 + qwertyu0020 | 0.5 + qwertyu0021 | 0.5 + qwertyu0022 | 0.5 + qwertyu0023 | 0.5 + qwertyu0024 | 0.5 + qwertyu0025 | 0.5 + qwertyu0026 | 0.5 + qwertyu0027 | 0.5 + qwertyu0028 | 0.5 + qwertyu0029 | 0.5 + qwertyu0030 | 0.5 + qwertyu0031 | 0.5 + qwertyu0032 | 0.5 + qwertyu0033 | 0.5 + qwertyu0034 | 0.5 + qwertyu0035 | 0.5 + qwertyu0036 | 0.5 + qwertyu0037 | 0.5 + qwertyu0038 | 0.5 + qwertyu0039 | 0.5 + qwertyu0040 | 0.5 + qwertyu0041 | 0.5 + qwertyu0042 | 0.5 + qwertyu0043 | 0.5 + qwertyu0044 | 0.5 + qwertyu0045 | 0.5 + qwertyu0046 | 0.5 + qwertyu0047 | 0.5 + qwertyu0048 | 0.5 + qwertyu0049 | 0.5 + qwertyu0050 | 0.5 + qwertyu0051 | 0.5 + qwertyu0052 | 0.5 + qwertyu0053 | 0.5 + qwertyu0054 | 0.5 + qwertyu0055 | 0.5 + qwertyu0056 | 0.5 + qwertyu0057 | 0.5 + qwertyu0058 | 0.5 + qwertyu0059 | 0.5 + qwertyu0060 | 0.5 + qwertyu0061 | 0.5 + qwertyu0062 | 0.5 + qwertyu0063 | 0.5 + qwertyu0064 | 0.5 + qwertyu0065 | 0.5 + qwertyu0066 | 0.5 + qwertyu0067 | 0.5 + qwertyu0068 | 0.5 + qwertyu0069 | 0.5 + qwertyu0070 | 0.5 + qwertyu0071 | 0.5 + qwertyu0072 | 0.5 + qwertyu0073 | 0.5 + qwertyu0074 | 0.5 + qwertyu0075 | 0.5 + qwertyu0076 | 0.5 + qwertyu0077 | 0.5 + qwertyu0078 | 0.5 + qwertyu0079 | 0.5 + qwertyu0080 | 0.5 + qwertyu0081 | 0.5 + qwertyu0082 | 0.5 + qwertyu0083 | 0.5 + qwertyu0084 | 0.5 + qwertyu0085 | 0.5 + qwertyu0086 | 0.5 + qwertyu0087 | 0.5 + qwertyu0089 | 0.5 + qwertyu0090 | 0.5 + qwertyu0091 | 0.5 + qwertyu0092 | 0.5 + qwertyu0093 | 0.5 + qwertyu0094 | 0.5 + qwertyu0095 | 0.5 + qwertyu0096 | 0.5 + qwertyu0097 | 0.5 + qwertyu0099 | 0.5 + qwertyu0100 | 0.5 + qwertyu0101 | 0.5 + qwertyu0102 | 0.5 + qwertyu0103 | 0.5 + qwertyu0104 | 0.5 + qwertyu0105 | 0.5 + qwertyu0106 | 0.5 + qwertyu0107 | 0.5 + qwertyu0108 | 0.5 + qwertyu0109 | 0.5 + qwertyu0110 | 0.5 + qwertyu0111 | 0.5 + qwertyu0112 | 0.5 + qwertyu0113 | 0.5 + qwertyu0114 | 0.5 + qwertyu0115 | 0.5 + qwertyu0116 | 0.5 + qwertyu0117 | 0.5 + qwertyu0118 | 0.5 + qwertyu0119 | 0.5 + qwertyu0120 | 0.5 + qwertyu0121 | 0.5 + qwertyu0122 | 0.5 + qwertyu0123 | 0.5 + qwertyu0124 | 0.5 + qwertyu0125 | 0.5 + qwertyu0126 | 0.5 + qwertyu0127 | 0.5 + qwertyu0128 | 0.5 + qwertyu0129 | 0.5 + qwertyu0130 | 0.5 + qwertyu0131 | 0.5 + qwertyu0132 | 0.5 + qwertyu0133 | 0.5 + qwertyu0134 | 0.5 + qwertyu0135 | 0.5 + qwertyu0136 | 0.5 + qwertyu0137 | 0.5 + qwertyu0138 | 0.5 + qwertyu0139 | 0.5 + qwertyu0140 | 0.5 + qwertyu0141 | 0.5 + qwertyu0142 | 0.5 + qwertyu0143 | 0.5 + qwertyu0144 | 0.5 + qwertyu0145 | 0.5 + qwertyu0146 | 0.5 + qwertyu0147 | 0.5 + qwertyu0148 | 0.5 + qwertyu0149 | 0.5 + qwertyu0150 | 0.5 + qwertyu0151 | 0.5 + qwertyu0152 | 0.5 + qwertyu0153 | 0.5 + qwertyu0154 | 0.5 + qwertyu0155 | 0.5 + qwertyu0156 | 0.5 + qwertyu0157 | 0.5 + qwertyu0158 | 0.5 + qwertyu0159 | 0.5 + qwertyu0160 | 0.5 + qwertyu0161 | 0.5 + qwertyu0162 | 0.5 + qwertyu0163 | 0.5 + qwertyu0164 | 0.5 + qwertyu0165 | 0.5 + qwertyu0166 | 0.5 + qwertyu0167 | 0.5 + qwertyu0168 | 0.5 + qwertyu0169 | 0.5 + qwertyu0170 | 0.5 + qwertyu0171 | 0.5 + qwertyu0172 | 0.5 + qwertyu0173 | 0.5 + qwertyu0174 | 0.5 + qwertyu0175 | 0.5 + qwertyu0176 | 0.5 + qwertyu0177 | 0.5 + qwertyu0178 | 0.5 + qwertyu0179 | 0.5 + qwertyu0180 | 0.5 + qwertyu0181 | 0.5 + qwertyu0182 | 0.5 + qwertyu0183 | 0.5 + qwertyu0184 | 0.5 + qwertyu0185 | 0.5 + qwertyu0186 | 0.5 + qwertyu0187 | 0.5 + qwertyu0189 | 0.5 + qwertyu0190 | 0.5 + qwertyu0191 | 0.5 + qwertyu0192 | 0.5 + qwertyu0193 | 0.5 + qwertyu0194 | 0.5 + qwertyu0195 | 0.5 + qwertyu0196 | 0.5 + qwertyu0197 | 0.5 + qwertyu0198 | 0.5 + qwertyu0199 | 0.5 + qwertyu0200 | 0.5 + qwertyu0201 | 0.5 + qwertyu0202 | 0.5 + qwertyu0203 | 0.5 + qwertyu0204 | 0.5 + qwertyu0205 | 0.5 + qwertyu0206 | 0.5 + qwertyu0207 | 0.5 + qwertyu0208 | 0.5 + qwertyu0209 | 0.5 + qwertyu0210 | 0.5 + qwertyu0211 | 0.5 + qwertyu0212 | 0.5 + qwertyu0213 | 0.5 + qwertyu0214 | 0.5 + qwertyu0215 | 0.5 + qwertyu0216 | 0.5 + qwertyu0217 | 0.5 + qwertyu0218 | 0.5 + qwertyu0219 | 0.5 + qwertyu0220 | 0.5 + qwertyu0221 | 0.5 + qwertyu0222 | 0.5 + qwertyu0223 | 0.5 + qwertyu0224 | 0.5 + qwertyu0225 | 0.5 + qwertyu0226 | 0.5 + qwertyu0227 | 0.5 + qwertyu0228 | 0.5 + qwertyu0229 | 0.5 + qwertyu0230 | 0.5 + qwertyu0231 | 0.5 + qwertyu0232 | 0.5 + qwertyu0233 | 0.5 + qwertyu0234 | 0.5 + qwertyu0235 | 0.5 + qwertyu0236 | 0.5 + qwertyu0237 | 0.5 + qwertyu0238 | 0.5 + qwertyu0239 | 0.5 + qwertyu0240 | 0.5 + qwertyu0241 | 0.5 + qwertyu0242 | 0.5 + qwertyu0243 | 0.5 + qwertyu0244 | 0.5 + qwertyu0245 | 0.5 + qwertyu0246 | 0.5 + qwertyu0247 | 0.5 + qwertyu0248 | 0.5 + qwertyu0249 | 0.5 + qwertyu0250 | 0.5 + qwertyu0251 | 0.5 + qwertyu0252 | 0.5 + qwertyu0253 | 0.5 + qwertyu0254 | 0.5 + qwertyu0255 | 0.5 + qwertyu0256 | 0.5 + qwertyu0257 | 0.5 + qwertyu0258 | 0.5 + qwertyu0259 | 0.5 + qwertyu0260 | 0.5 + qwertyu0261 | 0.5 + qwertyu0262 | 0.5 + qwertyu0263 | 0.5 + qwertyu0264 | 0.5 + qwertyu0265 | 0.5 + qwertyu0266 | 0.5 + qwertyu0267 | 0.5 + qwertyu0268 | 0.5 + qwertyu0269 | 0.5 + qwertyu0270 | 0.5 + qwertyu0271 | 0.5 + qwertyu0272 | 0.5 + qwertyu0273 | 0.5 + qwertyu0274 | 0.5 + qwertyu0275 | 0.5 + qwertyu0276 | 0.5 + qwertyu0277 | 0.5 + qwertyu0278 | 0.5 + qwertyu0279 | 0.5 + qwertyu0280 | 0.5 + qwertyu0281 | 0.5 + qwertyu0282 | 0.5 + qwertyu0283 | 0.5 + qwertyu0284 | 0.5 + qwertyu0285 | 0.5 + qwertyu0286 | 0.5 + qwertyu0287 | 0.5 + qwertyu0289 | 0.5 + qwertyu0290 | 0.5 + qwertyu0291 | 0.5 + qwertyu0292 | 0.5 + qwertyu0293 | 0.5 + qwertyu0294 | 0.5 + qwertyu0295 | 0.5 + qwertyu0296 | 0.5 + qwertyu0297 | 0.5 + qwertyu0298 | 0.5 + qwertyu0299 | 0.5 + qwertyu0300 | 0.5 + qwertyu0301 | 0.5 + qwertyu0302 | 0.5 + qwertyu0303 | 0.5 + qwertyu0304 | 0.5 + qwertyu0305 | 0.5 + qwertyu0306 | 0.5 + qwertyu0307 | 0.5 + qwertyu0308 | 0.5 + qwertyu0309 | 0.5 + qwertyu0310 | 0.5 + qwertyu0311 | 0.5 + qwertyu0312 | 0.5 + qwertyu0313 | 0.5 + qwertyu0314 | 0.5 + qwertyu0315 | 0.5 + qwertyu0316 | 0.5 + qwertyu0317 | 0.5 + qwertyu0318 | 0.5 + qwertyu0319 | 0.5 + qwertyu0320 | 0.5 + qwertyu0321 | 0.5 + qwertyu0322 | 0.5 + qwertyu0323 | 0.5 + qwertyu0324 | 0.5 + qwertyu0325 | 0.5 + qwertyu0326 | 0.5 + qwertyu0327 | 0.5 + qwertyu0328 | 0.5 + qwertyu0329 | 0.5 + qwertyu0330 | 0.5 + qwertyu0331 | 0.5 + qwertyu0332 | 0.5 + qwertyu0333 | 0.5 + qwertyu0334 | 0.5 + qwertyu0335 | 0.5 + qwertyu0336 | 0.5 + qwertyu0337 | 0.5 + qwertyu0338 | 0.5 + qwertyu0339 | 0.5 + qwertyu0340 | 0.5 + qwertyu0341 | 0.5 + qwertyu0342 | 0.5 + qwertyu0343 | 0.5 + qwertyu0344 | 0.5 + qwertyu0345 | 0.5 + qwertyu0346 | 0.5 + qwertyu0347 | 0.5 + qwertyu0348 | 0.5 + qwertyu0349 | 0.5 + qwertyu0350 | 0.5 + qwertyu0351 | 0.5 + qwertyu0352 | 0.5 + qwertyu0353 | 0.5 + qwertyu0354 | 0.5 + qwertyu0355 | 0.5 + qwertyu0356 | 0.5 + qwertyu0357 | 0.5 + qwertyu0358 | 0.5 + qwertyu0359 | 0.5 + qwertyu0360 | 0.5 + qwertyu0361 | 0.5 + qwertyu0362 | 0.5 + qwertyu0363 | 0.5 + qwertyu0364 | 0.5 + qwertyu0365 | 0.5 + qwertyu0366 | 0.5 + qwertyu0367 | 0.5 + qwertyu0368 | 0.5 + qwertyu0369 | 0.5 + qwertyu0370 | 0.5 + qwertyu0371 | 0.5 + qwertyu0372 | 0.5 + qwertyu0373 | 0.5 + qwertyu0374 | 0.5 + qwertyu0375 | 0.5 + qwertyu0376 | 0.5 + qwertyu0377 | 0.5 + qwertyu0378 | 0.5 + qwertyu0379 | 0.5 + qwertyu0380 | 0.5 + qwertyu0381 | 0.5 + qwertyu0382 | 0.5 + qwertyu0383 | 0.5 + qwertyu0384 | 0.5 + qwertyu0385 | 0.5 + qwertyu0386 | 0.5 + qwertyu0387 | 0.5 + qwertyu0389 | 0.5 + qwertyu0390 | 0.5 + qwertyu0391 | 0.5 + qwertyu0392 | 0.5 + qwertyu0393 | 0.5 + qwertyu0394 | 0.5 + qwertyu0395 | 0.5 + qwertyu0396 | 0.5 + qwertyu0397 | 0.5 + qwertyu0398 | 0.5 + qwertyu0399 | 0.5 + qwertyu0400 | 0.5 + qwertyu0401 | 0.5 + qwertyu0402 | 0.5 + qwertyu0403 | 0.5 + qwertyu0404 | 0.5 + qwertyu0405 | 0.5 + qwertyu0406 | 0.5 + qwertyu0407 | 0.5 + qwertyu0408 | 0.5 + qwertyu0409 | 0.5 + qwertyu0410 | 0.5 + qwertyu0411 | 0.5 + qwertyu0412 | 0.5 + qwertyu0413 | 0.5 + qwertyu0414 | 0.5 + qwertyu0415 | 0.5 + qwertyu0416 | 0.5 + qwertyu0417 | 0.5 + qwertyu0418 | 0.5 + qwertyu0419 | 0.5 + qwertyu0420 | 0.5 + qwertyu0421 | 0.5 + qwertyu0422 | 0.5 + qwertyu0423 | 0.5 + qwertyu0424 | 0.5 + qwertyu0425 | 0.5 + qwertyu0426 | 0.5 + qwertyu0427 | 0.5 + qwertyu0428 | 0.5 + qwertyu0429 | 0.5 + qwertyu0430 | 0.5 + qwertyu0431 | 0.5 + qwertyu0432 | 0.5 + qwertyu0433 | 0.5 + qwertyu0434 | 0.5 + qwertyu0435 | 0.5 + qwertyu0436 | 0.5 + qwertyu0437 | 0.5 + qwertyu0438 | 0.5 + qwertyu0439 | 0.5 + qwertyu0440 | 0.5 + qwertyu0441 | 0.5 + qwertyu0442 | 0.5 + qwertyu0443 | 0.5 + qwertyu0444 | 0.5 + qwertyu0445 | 0.5 + qwertyu0446 | 0.5 + qwertyu0447 | 0.5 + qwertyu0448 | 0.5 + qwertyu0449 | 0.5 + qwertyu0450 | 0.5 + qwertyu0451 | 0.5 + qwertyu0452 | 0.5 + qwertyu0453 | 0.5 + qwertyu0454 | 0.5 + qwertyu0455 | 0.5 + qwertyu0456 | 0.5 + qwertyu0457 | 0.5 + qwertyu0458 | 0.5 + qwertyu0459 | 0.5 + qwertyu0460 | 0.5 + qwertyu0461 | 0.5 + qwertyu0462 | 0.5 + qwertyu0463 | 0.5 + qwertyu0464 | 0.5 + qwertyu0465 | 0.5 + qwertyu0466 | 0.5 + qwertyu0467 | 0.5 + qwertyu0468 | 0.5 + qwertyu0469 | 0.5 + qwertyu0470 | 0.5 + qwertyu0471 | 0.5 + qwertyu0472 | 0.5 + qwertyu0473 | 0.5 + qwertyu0474 | 0.5 + qwertyu0475 | 0.5 + qwertyu0476 | 0.5 + qwertyu0477 | 0.5 + qwertyu0478 | 0.5 + qwertyu0479 | 0.5 + qwertyu0480 | 0.5 + qwertyu0481 | 0.5 + qwertyu0482 | 0.5 + qwertyu0483 | 0.5 + qwertyu0484 | 0.5 + qwertyu0485 | 0.5 + qwertyu0486 | 0.5 + qwertyu0487 | 0.5 + qwertyu0489 | 0.5 + qwertyu0490 | 0.5 + qwertyu0491 | 0.5 + qwertyu0492 | 0.5 + qwertyu0493 | 0.5 + qwertyu0494 | 0.5 + qwertyu0495 | 0.5 + qwertyu0496 | 0.5 + qwertyu0497 | 0.5 + qwertyu0498 | 0.5 + qwertyu0499 | 0.5 + qwertyu0500 | 0.5 + qwertyu0501 | 0.5 + qwertyu0502 | 0.5 + qwertyu0503 | 0.5 + qwertyu0504 | 0.5 + qwertyu0505 | 0.5 + qwertyu0506 | 0.5 + qwertyu0507 | 0.5 + qwertyu0508 | 0.5 + qwertyu0509 | 0.5 + qwertyu0510 | 0.5 + qwertyu0511 | 0.5 + qwertyu0512 | 0.5 + qwertyu0513 | 0.5 + qwertyu0514 | 0.5 + qwertyu0515 | 0.5 + qwertyu0516 | 0.5 + qwertyu0517 | 0.5 + qwertyu0518 | 0.5 + qwertyu0519 | 0.5 + qwertyu0520 | 0.5 + qwertyu0521 | 0.5 + qwertyu0522 | 0.5 + qwertyu0523 | 0.5 + qwertyu0524 | 0.5 + qwertyu0525 | 0.5 + qwertyu0526 | 0.5 + qwertyu0527 | 0.5 + qwertyu0528 | 0.5 + qwertyu0529 | 0.5 + qwertyu0530 | 0.5 + qwertyu0531 | 0.5 + qwertyu0532 | 0.5 + qwertyu0533 | 0.5 + qwertyu0534 | 0.5 + qwertyu0535 | 0.5 + qwertyu0536 | 0.5 + qwertyu0537 | 0.5 + qwertyu0538 | 0.5 + qwertyu0539 | 0.5 + qwertyu0540 | 0.5 + qwertyu0541 | 0.5 + qwertyu0542 | 0.5 + qwertyu0543 | 0.5 + qwertyu0544 | 0.5 + qwertyu0545 | 0.5 + qwertyu0546 | 0.5 + qwertyu0547 | 0.5 + qwertyu0548 | 0.5 + qwertyu0549 | 0.5 + qwertyu0550 | 0.5 + qwertyu0551 | 0.5 + qwertyu0552 | 0.5 + qwertyu0553 | 0.5 + qwertyu0554 | 0.5 + qwertyu0555 | 0.5 + qwertyu0556 | 0.5 + qwertyu0557 | 0.5 + qwertyu0558 | 0.5 + qwertyu0559 | 0.5 + qwertyu0560 | 0.5 + qwertyu0561 | 0.5 + qwertyu0562 | 0.5 + qwertyu0563 | 0.5 + qwertyu0564 | 0.5 + qwertyu0565 | 0.5 + qwertyu0566 | 0.5 + qwertyu0567 | 0.5 + qwertyu0568 | 0.5 + qwertyu0569 | 0.5 + qwertyu0570 | 0.5 + qwertyu0571 | 0.5 + qwertyu0572 | 0.5 + qwertyu0573 | 0.5 + qwertyu0574 | 0.5 + qwertyu0575 | 0.5 + qwertyu0576 | 0.5 + qwertyu0577 | 0.5 + qwertyu0578 | 0.5 + qwertyu0579 | 0.5 + qwertyu0580 | 0.5 + qwertyu0581 | 0.5 + qwertyu0582 | 0.5 + qwertyu0583 | 0.5 + qwertyu0584 | 0.5 + qwertyu0585 | 0.5 + qwertyu0586 | 0.5 + qwertyu0587 | 0.5 + qwertyu0589 | 0.5 + qwertyu0590 | 0.5 + qwertyu0591 | 0.5 + qwertyu0592 | 0.5 + qwertyu0593 | 0.5 + qwertyu0594 | 0.5 + qwertyu0595 | 0.5 + qwertyu0596 | 0.5 + qwertyu0597 | 0.5 + qwertyu0598 | 0.5 + qwertyu0599 | 0.5 + qwertyu0600 | 0.5 + qwertyu0601 | 0.5 + qwertyu0602 | 0.5 + qwertyu0603 | 0.5 + qwertyu0604 | 0.5 + qwertyu0605 | 0.5 + qwertyu0606 | 0.5 + qwertyu0607 | 0.5 + qwertyu0608 | 0.5 + qwertyu0609 | 0.5 + qwertyu0610 | 0.5 + qwertyu0611 | 0.5 + qwertyu0612 | 0.5 + qwertyu0613 | 0.5 + qwertyu0614 | 0.5 + qwertyu0615 | 0.5 + qwertyu0616 | 0.5 + qwertyu0617 | 0.5 + qwertyu0618 | 0.5 + qwertyu0619 | 0.5 + qwertyu0620 | 0.5 + qwertyu0621 | 0.5 + qwertyu0622 | 0.5 + qwertyu0623 | 0.5 + qwertyu0624 | 0.5 + qwertyu0625 | 0.5 + qwertyu0626 | 0.5 + qwertyu0627 | 0.5 + qwertyu0628 | 0.5 + qwertyu0629 | 0.5 + qwertyu0630 | 0.5 + qwertyu0631 | 0.5 + qwertyu0632 | 0.5 + qwertyu0633 | 0.5 + qwertyu0634 | 0.5 + qwertyu0635 | 0.5 + qwertyu0636 | 0.5 + qwertyu0637 | 0.5 + qwertyu0638 | 0.5 + qwertyu0639 | 0.5 + qwertyu0640 | 0.5 + qwertyu0641 | 0.5 + qwertyu0642 | 0.5 + qwertyu0643 | 0.5 + qwertyu0644 | 0.5 + qwertyu0645 | 0.5 + qwertyu0646 | 0.5 + qwertyu0647 | 0.5 + qwertyu0648 | 0.5 + qwertyu0649 | 0.5 + qwertyu0650 | 0.5 + qwertyu0651 | 0.5 + qwertyu0652 | 0.5 + qwertyu0653 | 0.5 + qwertyu0654 | 0.5 + qwertyu0655 | 0.5 + qwertyu0656 | 0.5 + qwertyu0657 | 0.5 + qwertyu0658 | 0.5 + qwertyu0659 | 0.5 + qwertyu0660 | 0.5 + qwertyu0661 | 0.5 + qwertyu0662 | 0.5 + qwertyu0663 | 0.5 + qwertyu0664 | 0.5 + qwertyu0665 | 0.5 + qwertyu0666 | 0.5 + qwertyu0667 | 0.5 + qwertyu0668 | 0.5 + qwertyu0669 | 0.5 + qwertyu0670 | 0.5 + qwertyu0671 | 0.5 + qwertyu0672 | 0.5 + qwertyu0673 | 0.5 + qwertyu0674 | 0.5 + qwertyu0675 | 0.5 + qwertyu0676 | 0.5 + qwertyu0677 | 0.5 + qwertyu0678 | 0.5 + qwertyu0679 | 0.5 + qwertyu0680 | 0.5 + qwertyu0681 | 0.5 + qwertyu0682 | 0.5 + qwertyu0683 | 0.5 + qwertyu0684 | 0.5 + qwertyu0685 | 0.5 + qwertyu0686 | 0.5 + qwertyu0687 | 0.5 + qwertyu0689 | 0.5 + qwertyu0690 | 0.5 + qwertyu0691 | 0.5 + qwertyu0692 | 0.5 + qwertyu0693 | 0.5 + qwertyu0694 | 0.5 + qwertyu0695 | 0.5 + qwertyu0696 | 0.5 + qwertyu0697 | 0.5 + qwertyu0698 | 0.5 + qwertyu0699 | 0.5 + qwertyu0700 | 0.5 + qwertyu0701 | 0.5 + qwertyu0702 | 0.5 + qwertyu0703 | 0.5 + qwertyu0704 | 0.5 + qwertyu0705 | 0.5 + qwertyu0706 | 0.5 + qwertyu0707 | 0.5 + qwertyu0708 | 0.5 + qwertyu0709 | 0.5 + qwertyu0710 | 0.5 + qwertyu0711 | 0.5 + qwertyu0712 | 0.5 + qwertyu0713 | 0.5 + qwertyu0714 | 0.5 + qwertyu0715 | 0.5 + qwertyu0716 | 0.5 + qwertyu0717 | 0.5 + qwertyu0718 | 0.5 + qwertyu0719 | 0.5 + qwertyu0720 | 0.5 + qwertyu0721 | 0.5 + qwertyu0722 | 0.5 + qwertyu0723 | 0.5 + qwertyu0724 | 0.5 + qwertyu0725 | 0.5 + qwertyu0726 | 0.5 + qwertyu0727 | 0.5 + qwertyu0728 | 0.5 + qwertyu0729 | 0.5 + qwertyu0730 | 0.5 + qwertyu0731 | 0.5 + qwertyu0732 | 0.5 + qwertyu0733 | 0.5 + qwertyu0734 | 0.5 + qwertyu0735 | 0.5 + qwertyu0736 | 0.5 + qwertyu0737 | 0.5 + qwertyu0738 | 0.5 + qwertyu0739 | 0.5 + qwertyu0740 | 0.5 + qwertyu0741 | 0.5 + qwertyu0742 | 0.5 + qwertyu0743 | 0.5 + qwertyu0744 | 0.5 + qwertyu0745 | 0.5 + qwertyu0746 | 0.5 + qwertyu0747 | 0.5 + qwertyu0748 | 0.5 + qwertyu0749 | 0.5 + qwertyu0750 | 0.5 + qwertyu0751 | 0.5 + qwertyu0752 | 0.5 + qwertyu0753 | 0.5 + qwertyu0754 | 0.5 + qwertyu0755 | 0.5 + qwertyu0756 | 0.5 + qwertyu0757 | 0.5 + qwertyu0758 | 0.5 + qwertyu0759 | 0.5 + qwertyu0760 | 0.5 + qwertyu0761 | 0.5 + qwertyu0762 | 0.5 + qwertyu0763 | 0.5 + qwertyu0764 | 0.5 + qwertyu0765 | 0.5 + qwertyu0766 | 0.5 + qwertyu0767 | 0.5 + qwertyu0768 | 0.5 + qwertyu0769 | 0.5 + qwertyu0770 | 0.5 + qwertyu0771 | 0.5 + qwertyu0772 | 0.5 + qwertyu0773 | 0.5 + qwertyu0774 | 0.5 + qwertyu0775 | 0.5 + qwertyu0776 | 0.5 + qwertyu0777 | 0.5 + qwertyu0778 | 0.5 + qwertyu0779 | 0.5 + qwertyu0780 | 0.5 + qwertyu0781 | 0.5 + qwertyu0782 | 0.5 + qwertyu0783 | 0.5 + qwertyu0784 | 0.5 + qwertyu0785 | 0.5 + qwertyu0786 | 0.5 + qwertyu0787 | 0.5 + qwertyu0789 | 0.5 + qwertyu0790 | 0.5 + qwertyu0791 | 0.5 + qwertyu0792 | 0.5 + qwertyu0793 | 0.5 + qwertyu0794 | 0.5 + qwertyu0795 | 0.5 + qwertyu0796 | 0.5 + qwertyu0797 | 0.5 + qwertyu0798 | 0.5 + qwertyu0799 | 0.5 + qwertyu0800 | 0.5 + qwertyu0801 | 0.5 + qwertyu0802 | 0.5 + qwertyu0803 | 0.5 + qwertyu0804 | 0.5 + qwertyu0805 | 0.5 + qwertyu0806 | 0.5 + qwertyu0807 | 0.5 + qwertyu0808 | 0.5 + qwertyu0809 | 0.5 + qwertyu0810 | 0.5 + qwertyu0811 | 0.5 + qwertyu0812 | 0.5 + qwertyu0813 | 0.5 + qwertyu0814 | 0.5 + qwertyu0815 | 0.5 + qwertyu0816 | 0.5 + qwertyu0817 | 0.5 + qwertyu0818 | 0.5 + qwertyu0819 | 0.5 + qwertyu0820 | 0.5 + qwertyu0821 | 0.5 + qwertyu0822 | 0.5 + qwertyu0823 | 0.5 + qwertyu0824 | 0.5 + qwertyu0825 | 0.5 + qwertyu0826 | 0.5 + qwertyu0827 | 0.5 + qwertyu0828 | 0.5 + qwertyu0829 | 0.5 + qwertyu0830 | 0.5 + qwertyu0831 | 0.5 + qwertyu0832 | 0.5 + qwertyu0833 | 0.5 + qwertyu0834 | 0.5 + qwertyu0835 | 0.5 + qwertyu0836 | 0.5 + qwertyu0837 | 0.5 + qwertyu0838 | 0.5 + qwertyu0839 | 0.5 + qwertyu0840 | 0.5 + qwertyu0841 | 0.5 + qwertyu0842 | 0.5 + qwertyu0843 | 0.5 + qwertyu0844 | 0.5 + qwertyu0845 | 0.5 + qwertyu0846 | 0.5 + qwertyu0847 | 0.5 + qwertyu0848 | 0.5 + qwertyu0849 | 0.5 + qwertyu0850 | 0.5 + qwertyu0851 | 0.5 + qwertyu0852 | 0.5 + qwertyu0853 | 0.5 + qwertyu0854 | 0.5 + qwertyu0855 | 0.5 + qwertyu0856 | 0.5 + qwertyu0857 | 0.5 + qwertyu0858 | 0.5 + qwertyu0859 | 0.5 + qwertyu0860 | 0.5 + qwertyu0861 | 0.5 + qwertyu0862 | 0.5 + qwertyu0863 | 0.5 + qwertyu0864 | 0.5 + qwertyu0865 | 0.5 + qwertyu0866 | 0.5 + qwertyu0867 | 0.5 + qwertyu0868 | 0.5 + qwertyu0869 | 0.5 + qwertyu0870 | 0.5 + qwertyu0871 | 0.5 + qwertyu0872 | 0.5 + qwertyu0873 | 0.5 + qwertyu0874 | 0.5 + qwertyu0875 | 0.5 + qwertyu0876 | 0.5 + qwertyu0877 | 0.5 + qwertyu0878 | 0.5 + qwertyu0879 | 0.5 + qwertyu0880 | 0.5 + qwertyu0881 | 0.5 + qwertyu0882 | 0.5 + qwertyu0883 | 0.5 + qwertyu0884 | 0.5 + qwertyu0885 | 0.5 + qwertyu0886 | 0.5 + qwertyu0887 | 0.5 + qwertyu0889 | 0.5 + qwertyu0890 | 0.5 + qwertyu0891 | 0.5 + qwertyu0892 | 0.5 + qwertyu0893 | 0.5 + qwertyu0894 | 0.5 + qwertyu0895 | 0.5 + qwertyu0896 | 0.5 + qwertyu0897 | 0.5 + qwertyu0898 | 0.5 + qwertyu0899 | 0.5 + qwertyu1000 | 0.411765 +(1000 rows) + +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 0.6 + qwertyu0980 | 0.411765 + qwertyu0981 | 0.411765 + qwertyu0982 | 0.411765 + qwertyu0983 | 0.411765 + qwertyu0984 | 0.411765 + qwertyu0985 | 0.411765 + qwertyu0986 | 0.411765 + qwertyu0987 | 0.411765 + qwertyu0989 | 0.411765 + qwertyu0088 | 0.333333 + qwertyu0098 | 0.333333 + qwertyu0188 | 0.333333 + qwertyu0288 | 0.333333 + qwertyu0388 | 0.333333 + qwertyu0488 | 0.333333 + qwertyu0588 | 0.333333 + qwertyu0688 | 0.333333 + qwertyu0788 | 0.333333 + qwertyu0888 | 0.333333 + qwertyu0900 | 0.333333 + qwertyu0901 | 0.333333 + qwertyu0902 | 0.333333 + qwertyu0903 | 0.333333 + qwertyu0904 | 0.333333 + qwertyu0905 | 0.333333 + qwertyu0906 | 0.333333 + qwertyu0907 | 0.333333 + qwertyu0908 | 0.333333 + qwertyu0909 | 0.333333 + qwertyu0910 | 0.333333 + qwertyu0911 | 0.333333 + qwertyu0912 | 0.333333 + qwertyu0913 | 0.333333 + qwertyu0914 | 0.333333 + qwertyu0915 | 0.333333 + qwertyu0916 | 0.333333 + qwertyu0917 | 0.333333 + qwertyu0918 | 0.333333 + qwertyu0919 | 0.333333 + qwertyu0920 | 0.333333 + qwertyu0921 | 0.333333 + qwertyu0922 | 0.333333 + qwertyu0923 | 0.333333 + qwertyu0924 | 0.333333 + qwertyu0925 | 0.333333 + qwertyu0926 | 0.333333 + qwertyu0927 | 0.333333 + qwertyu0928 | 0.333333 + qwertyu0929 | 0.333333 + qwertyu0930 | 0.333333 + qwertyu0931 | 0.333333 + qwertyu0932 | 0.333333 + qwertyu0933 | 0.333333 + qwertyu0934 | 0.333333 + qwertyu0935 | 0.333333 + qwertyu0936 | 0.333333 + qwertyu0937 | 0.333333 + qwertyu0938 | 0.333333 + qwertyu0939 | 0.333333 + qwertyu0940 | 0.333333 + qwertyu0941 | 0.333333 + qwertyu0942 | 0.333333 + qwertyu0943 | 0.333333 + qwertyu0944 | 0.333333 + qwertyu0945 | 0.333333 + qwertyu0946 | 0.333333 + qwertyu0947 | 0.333333 + qwertyu0948 | 0.333333 + qwertyu0949 | 0.333333 + qwertyu0950 | 0.333333 + qwertyu0951 | 0.333333 + qwertyu0952 | 0.333333 + qwertyu0953 | 0.333333 + qwertyu0954 | 0.333333 + qwertyu0955 | 0.333333 + qwertyu0956 | 0.333333 + qwertyu0957 | 0.333333 + qwertyu0958 | 0.333333 + qwertyu0959 | 0.333333 + qwertyu0960 | 0.333333 + qwertyu0961 | 0.333333 + qwertyu0962 | 0.333333 + qwertyu0963 | 0.333333 + qwertyu0964 | 0.333333 + qwertyu0965 | 0.333333 + qwertyu0966 | 0.333333 + qwertyu0967 | 0.333333 + qwertyu0968 | 0.333333 + qwertyu0969 | 0.333333 + qwertyu0970 | 0.333333 + qwertyu0971 | 0.333333 + qwertyu0972 | 0.333333 + qwertyu0973 | 0.333333 + qwertyu0974 | 0.333333 + qwertyu0975 | 0.333333 + qwertyu0976 | 0.333333 + qwertyu0977 | 0.333333 + qwertyu0978 | 0.333333 + qwertyu0979 | 0.333333 + qwertyu0990 | 0.333333 + qwertyu0991 | 0.333333 + qwertyu0992 | 0.333333 + qwertyu0993 | 0.333333 + qwertyu0994 | 0.333333 + qwertyu0995 | 0.333333 + qwertyu0996 | 0.333333 + qwertyu0997 | 0.333333 + qwertyu0998 | 0.333333 + qwertyu0999 | 0.333333 +(110 rows) + +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 0.333333 +(1 row) + +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; + ?column? | t +----------+------------- + 0.411765 | qwertyu0988 + 0.5 | qwertyu0987 +(2 rows) + +select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; + count +------- + 1000 +(1 row) + +create index trgm_idx on test_trgm using gist (t gist_trgm_ops); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +create index trgm_idx on test_trgm using gin (t gin_trgm_ops); +set enable_seqscan=off; +-- YB note: yb_test_ybgin_disable_cost_factor setting is needed to really force +-- index scan even if it is detected to be not supported. +set yb_test_ybgin_disable_cost_factor = 0.5; +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 9. +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 9. +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 9. +select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 252. +-- 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 +----------------------------------------------------------------------- + Aggregate + -> Index Scan using trgm_idx on test_trgm + Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) +(3 rows) + +select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one scan key: got 2. +HINT: Consider rewriting the query with INTERSECT and UNION. +explain (costs off) +select count(*) from test_trgm where t like '%99%' and t like '%qw%'; + QUERY PLAN +------------------------------------------------------------------- + Aggregate + -> Index Scan using trgm_idx on test_trgm + Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) +(3 rows) + +select count(*) from test_trgm where t like '%99%' and t like '%qw%'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one scan key: got 2. +HINT: Consider rewriting the query with INTERSECT and UNION. +-- ensure that pending-list items are handled correctly, too +create temp table t_test_trgm(t text COLLATE "C"); +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 +----------------------------------------------------------------------------- + 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)) +(5 rows) + +select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; + count +------- + 1 +(1 row) + +explain (costs off) +select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; + QUERY PLAN +------------------------------------------------------------------------- + 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)) +(5 rows) + +select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; + count +------- + 1 +(1 row) + +-- run the same queries with sequential scan to check the results +set enable_bitmapscan=off; +set enable_seqscan=on; +select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; + count +------- + 19 +(1 row) + +select count(*) from test_trgm where t like '%99%' and t like '%qw%'; + count +------- + 19 +(1 row) + +select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; + count +------- + 1 +(1 row) + +select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; + count +------- + 1 +(1 row) + +reset enable_bitmapscan; +create table test2(t text COLLATE "C"); +insert into test2 values ('abcdef'); +insert into test2 values ('quark'); +insert into test2 values (' z foo bar'); +insert into test2 values ('/123/-45/'); +insert into test2 values ('line 1'); +insert into test2 values ('%line 2'); +insert into test2 values ('line 3%'); +insert into test2 values ('%line 4%'); +insert into test2 values ('%li%ne 5%'); +insert into test2 values ('li_e 6'); +create index test2_idx_gin on test2 using gin (t gin_trgm_ops); +set enable_seqscan=off; +-- YB note: yb_test_ybgin_disable_cost_factor setting is needed to really force +-- index scan even if it is detected to be not supported. +set yb_test_ybgin_disable_cost_factor = 0.5; +explain (costs off) + select * from test2 where t like '%BCD%'; + QUERY PLAN +----------------------------------------- + Index Scan using test2_idx_gin on test2 + Index Cond: (t ~~ '%BCD%'::text) +(2 rows) + +explain (costs off) + select * from test2 where t ilike '%BCD%'; + QUERY PLAN +----------------------------------------- + Index Scan using test2_idx_gin on test2 + Index Cond: (t ~~* '%BCD%'::text) +(2 rows) + +select * from test2 where t like '%BCD%'; + t +--- +(0 rows) + +select * from test2 where t like '%bcd%'; + t +-------- + abcdef +(1 row) + +select * from test2 where t like E'%\\bcd%'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ilike '%BCD%'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ilike 'qua%'; + t +------- + quark +(1 row) + +select * from test2 where t like '%z foo bar%'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t like ' z foo%'; + t +------------- + z foo bar +(1 row) + +explain (costs off) + select * from test2 where t ~ '[abc]{3}'; + QUERY PLAN +----------------------------------------- + Index Scan using test2_idx_gin on test2 + Index Cond: (t ~ '[abc]{3}'::text) +(2 rows) + +explain (costs off) + select * from test2 where t ~* 'DEF'; + QUERY PLAN +----------------------------------------- + Index Scan using test2_idx_gin on test2 + Index Cond: (t ~* 'DEF'::text) +(2 rows) + +select * from test2 where t ~ '[abc]{3}'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 27. +select * from test2 where t ~ 'a[bc]+d'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 6. +select * from test2 where t ~ '(abc)*$'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +select * from test2 where t ~* 'DEF'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ~ 'dEf'; + t +--- +(0 rows) + +select * from test2 where t ~* '^q'; + t +------- + quark +(1 row) + +select * from test2 where t ~* '[abc]{3}[def]{3}'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 27. +select * from test2 where t ~* 'ab[a-z]{3}'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 26. +select * from test2 where t ~* '(^| )qua'; + t +------- + quark +(1 row) + +select * from test2 where t ~ 'q.*rk$'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +select * from test2 where t ~ 'q'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +select * from test2 where t ~ '[a-z]{3}'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +select * from test2 where t ~* '(a{10}|b{10}|c{10}){10}'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 15. +select * from test2 where t ~ 'z foo bar'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ ' z foo bar'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ ' z foo bar'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ ' z foo'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ 'qua(?!foo)'; + t +------- + quark +(1 row) + +select * from test2 where t ~ '/\d+/-\d'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 10. +-- test = operator +explain (costs off) + select * from test2 where t = 'abcdef'; + QUERY PLAN +----------------------------------------- + Index Scan using test2_idx_gin on test2 + Index Cond: (t = 'abcdef'::text) +(2 rows) + +select * from test2 where t = 'abcdef'; + t +-------- + abcdef +(1 row) + +explain (costs off) + select * from test2 where t = '%line%'; + QUERY PLAN +----------------------------------------- + Index Scan using test2_idx_gin on test2 + Index Cond: (t = '%line%'::text) +(2 rows) + +select * from test2 where t = '%line%'; + t +--- +(0 rows) + +select * from test2 where t = 'li_e 1'; + t +--- +(0 rows) + +select * from test2 where t = '%line 2'; + t +--------- + %line 2 +(1 row) + +select * from test2 where t = 'line 3%'; + t +--------- + line 3% +(1 row) + +select * from test2 where t = '%line 3%'; + t +--- +(0 rows) + +select * from test2 where t = '%line 4%'; + t +---------- + %line 4% +(1 row) + +select * from test2 where t = '%line 5%'; + t +--- +(0 rows) + +select * from test2 where t = '%li_ne 5%'; + t +--- +(0 rows) + +select * from test2 where t = '%li%ne 5%'; + t +----------- + %li%ne 5% +(1 row) + +select * from test2 where t = 'line 6'; + t +--- +(0 rows) + +select * from test2 where t = 'li_e 6'; + t +-------- + li_e 6 +(1 row) + +drop index test2_idx_gin; +create index test2_idx_gist on test2 using gist (t gist_trgm_ops); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +-- Check similarity threshold (bug #14202) +CREATE TEMP TABLE restaurants (city text); +INSERT INTO restaurants SELECT 'Warsaw' FROM generate_series(1, 10000); +INSERT INTO restaurants SELECT 'Szczecin' FROM generate_series(1, 10000); +CREATE INDEX ON restaurants USING gist(city gist_trgm_ops); +-- Similarity of the two names (for reference). +SELECT similarity('Szczecin', 'Warsaw'); + similarity +------------ + 0 +(1 row) + +-- Should get only 'Warsaw' for either setting of set_limit. +EXPLAIN (COSTS OFF) +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; + QUERY PLAN +------------------------------------------------------------------- + HashAggregate + Group Key: city, similarity(city, 'Warsaw'::text), show_limit() + -> Bitmap Heap Scan on restaurants + Recheck Cond: (city % 'Warsaw'::text) + -> Bitmap Index Scan on restaurants_city_idx + Index Cond: (city % 'Warsaw'::text) +(6 rows) + +SELECT set_limit(0.3); + set_limit +----------- + 0.3 +(1 row) + +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; + city | similarity | show_limit +--------+------------+------------ + Warsaw | 1 | 0.3 +(1 row) + +SELECT set_limit(0.5); + set_limit +----------- + 0.5 +(1 row) + +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; + city | similarity | show_limit +--------+------------+------------ + Warsaw | 1 | 0.5 +(1 row) + diff --git a/contrib/pg_trgm/expected/yb.port.pg_word_trgm.out b/contrib/pg_trgm/expected/yb.port.pg_word_trgm.out new file mode 100644 index 0000000000000..6d3507491af62 --- /dev/null +++ b/contrib/pg_trgm/expected/yb.port.pg_word_trgm.out @@ -0,0 +1,127 @@ +CREATE TABLE test_trgm2(t text COLLATE "C"); +\copy test_trgm2 from 'data/trgm2.data' +-- reduce noise +set extra_float_digits = 0; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 +(20 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; + t | sml +------------------------------+----- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 +(4 rows) + +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 +(20 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + t | sml +------------------------------+----- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 +(4 rows) + +select t <->> 'Kabankala', t from test_trgm2 order by t <->> 'Kabankala' limit 7; + ?column? | t +----------+---------------------------------- + 0 | Kabankala + 0.1 | Kabankalan City Public Plaza + 0.3 | Abankala + 0.4 | Ntombankala School + 0.416667 | Kabakala + 0.5 | Nehalla Bankalah Reserved Forest + 0.538462 | Kabikala +(7 rows) + +create index trgm_idx2 on test_trgm2 using gist (t gist_trgm_ops); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +create index trgm_idx2 on test_trgm2 using gin (t gin_trgm_ops); +set enable_seqscan=off; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 3. +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 5. +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 3. +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 5. +set "pg_trgm.word_similarity_threshold" to 0.5; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 4. +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 6. +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 4. +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 6. +set "pg_trgm.word_similarity_threshold" to 0.3; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 5. +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 8. +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 5. +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 8. diff --git a/contrib/pg_trgm/pg_trgm--1.4--1.4-yb-1.0.sql b/contrib/pg_trgm/pg_trgm--1.4--1.4-yb-1.0.sql new file mode 100644 index 0000000000000..40aeb1471ae60 --- /dev/null +++ b/contrib/pg_trgm/pg_trgm--1.4--1.4-yb-1.0.sql @@ -0,0 +1,45 @@ +/* contrib/pg_trgm/pg_trgm--1.4--1.4-yb-1.0.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_trgm UPDATE TO '1.4-yb-1.0'" to load this file. \quit + +/* + * Translate gin_trgm_ops to ybgin_trgm_ops. Ignore gist because ybgist index + * access method does not exist, yet. + */ + +/* Adopted from contrib/pg_trgm/pg_trgm--1.3.sql */ + +-- create the operator class for ybgin +CREATE OPERATOR CLASS gin_trgm_ops +FOR TYPE text USING ybgin +AS + OPERATOR 1 % (text, text), + FUNCTION 1 btint4cmp (int4, int4), + FUNCTION 2 gin_extract_value_trgm (text, internal), + FUNCTION 3 gin_extract_query_trgm (text, internal, int2, internal, internal, internal, internal), + FUNCTION 4 gin_trgm_consistent (internal, int2, text, int4, internal, internal, internal, internal), + STORAGE int4; + +-- Add operators that are new in 9.1. + +ALTER OPERATOR FAMILY gin_trgm_ops USING ybgin ADD + OPERATOR 3 pg_catalog.~~ (text, text), + OPERATOR 4 pg_catalog.~~* (text, text); + +-- Add operators that are new in 9.3. + +ALTER OPERATOR FAMILY gin_trgm_ops USING ybgin ADD + OPERATOR 5 pg_catalog.~ (text, text), + OPERATOR 6 pg_catalog.~* (text, text); + +-- Add functions that are new in 9.6 (pg_trgm 1.2). + +ALTER OPERATOR FAMILY gin_trgm_ops USING ybgin ADD + OPERATOR 7 %> (text, text), + FUNCTION 6 (text,text) gin_trgm_triconsistent (internal, int2, text, int4, internal, internal, internal); + +/* Adopted from contrib/pg_trgm/pg_trgm--1.3--1.4.sql */ + +ALTER OPERATOR FAMILY gin_trgm_ops USING ybgin ADD + OPERATOR 9 %>> (text, text); diff --git a/contrib/pg_trgm/pg_trgm--1.4-yb-1.0--1.6-yb-1.0.sql b/contrib/pg_trgm/pg_trgm--1.4-yb-1.0--1.6-yb-1.0.sql new file mode 100644 index 0000000000000..85143352b8196 --- /dev/null +++ b/contrib/pg_trgm/pg_trgm--1.4-yb-1.0--1.6-yb-1.0.sql @@ -0,0 +1,40 @@ +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_trgm UPDATE TO '1.6-yb-1.0'" to load this file. \quit + +/* Sourced from contrib/pg_trgm/pg_trgm--1.4--1.5.sql */ + +CREATE FUNCTION gtrgm_options(internal) +RETURNS void +AS 'MODULE_PATHNAME', 'gtrgm_options' +LANGUAGE C IMMUTABLE PARALLEL SAFE; + +ALTER OPERATOR FAMILY gist_trgm_ops USING gist +ADD FUNCTION 10 (text) gtrgm_options (internal); + +ALTER OPERATOR % (text, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR <% (text, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR %> (text, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR <<% (text, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR %>> (text, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); + +/* Sourced from contrib/pg_trgm/pg_trgm--1.5--1.6.sql */ + +ALTER OPERATOR FAMILY gin_trgm_ops USING gin ADD + OPERATOR 11 pg_catalog.= (text, text); + +ALTER OPERATOR FAMILY gist_trgm_ops USING gist ADD + OPERATOR 11 pg_catalog.= (text, text); + +/* + * Translate gin operator class to ybgin operator class. + */ + +/* Adopted from contrib/pg_trgm/pg_trgm--1.5--1.6.sql */ + +ALTER OPERATOR FAMILY gin_trgm_ops USING ybgin ADD + OPERATOR 11 pg_catalog.= (text, text); diff --git a/contrib/pg_trgm/pg_trgm.control b/contrib/pg_trgm/pg_trgm.control index 1d6a9ddf25994..3ec28e6bd8705 100644 --- a/contrib/pg_trgm/pg_trgm.control +++ b/contrib/pg_trgm/pg_trgm.control @@ -1,6 +1,6 @@ # pg_trgm extension comment = 'text similarity measurement and index searching based on trigrams' -default_version = '1.6' +default_version = '1.6-yb-1.0' module_pathname = '$libdir/pg_trgm' relocatable = true trusted = true diff --git a/contrib/pg_trgm/sql/yb.port.pg_strict_word_trgm.sql b/contrib/pg_trgm/sql/yb.port.pg_strict_word_trgm.sql new file mode 100644 index 0000000000000..c71426303d306 --- /dev/null +++ b/contrib/pg_trgm/sql/yb.port.pg_strict_word_trgm.sql @@ -0,0 +1,34 @@ +DROP INDEX trgm_idx2; + +\copy test_trgm3 from 'data/trgm2.data' + +-- reduce noise +set extra_float_digits = 0; + +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; +select t <->>> 'Alaikallupoddakulam', t from test_trgm2 order by t <->>> 'Alaikallupoddakulam' limit 7; + +create index trgm_idx2 on test_trgm2 using gist (t gist_trgm_ops); + +create index trgm_idx2 on test_trgm2 using gin (t gin_trgm_ops); +set enable_seqscan=off; + +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; + +set "pg_trgm.strict_word_similarity_threshold" to 0.4; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; + +set "pg_trgm.strict_word_similarity_threshold" to 0.2; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; diff --git a/contrib/pg_trgm/sql/yb.port.pg_trgm.sql b/contrib/pg_trgm/sql/yb.port.pg_trgm.sql new file mode 100644 index 0000000000000..5cd946395aecc --- /dev/null +++ b/contrib/pg_trgm/sql/yb.port.pg_trgm.sql @@ -0,0 +1,168 @@ +CREATE EXTENSION pg_trgm; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + +--backslash is used in tests below, installcheck will fail if +--standard_conforming_string is off +set standard_conforming_strings=on; + +-- reduce noise +set extra_float_digits = 0; + +select show_trgm(''); +select show_trgm('(*&^$@%@'); +select show_trgm('a b c'); +select show_trgm(' a b c '); +select show_trgm('aA bB cC'); +select show_trgm(' aA bB cC '); +select show_trgm('a b C0*%^'); + +select similarity('wow','WOWa '); +select similarity('wow',' WOW '); + +select similarity('---', '####---'); + +CREATE TABLE test_trgm(t text COLLATE "C"); + +\copy test_trgm from 'data/trgm.data' + +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; +select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; + +create index trgm_idx on test_trgm using gist (t gist_trgm_ops); + +create index trgm_idx on test_trgm using gin (t gin_trgm_ops); +set enable_seqscan=off; +-- YB note: yb_test_ybgin_disable_cost_factor setting is needed to really force +-- index scan even if it is detected to be not supported. +set yb_test_ybgin_disable_cost_factor = 0.5; + +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; +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%'; +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%'; +select count(*) from test_trgm where t like '%99%' and t like '%qw%'; +-- ensure that pending-list items are handled correctly, too +create temp table t_test_trgm(t text COLLATE "C"); +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%'; +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%'; +select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; + +-- run the same queries with sequential scan to check the results +set enable_bitmapscan=off; +set enable_seqscan=on; +select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; +select count(*) from test_trgm where t like '%99%' and t like '%qw%'; +select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; +select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; +reset enable_bitmapscan; + +create table test2(t text COLLATE "C"); +insert into test2 values ('abcdef'); +insert into test2 values ('quark'); +insert into test2 values (' z foo bar'); +insert into test2 values ('/123/-45/'); +insert into test2 values ('line 1'); +insert into test2 values ('%line 2'); +insert into test2 values ('line 3%'); +insert into test2 values ('%line 4%'); +insert into test2 values ('%li%ne 5%'); +insert into test2 values ('li_e 6'); +create index test2_idx_gin on test2 using gin (t gin_trgm_ops); +set enable_seqscan=off; +-- YB note: yb_test_ybgin_disable_cost_factor setting is needed to really force +-- index scan even if it is detected to be not supported. +set yb_test_ybgin_disable_cost_factor = 0.5; +explain (costs off) + select * from test2 where t like '%BCD%'; +explain (costs off) + select * from test2 where t ilike '%BCD%'; +select * from test2 where t like '%BCD%'; +select * from test2 where t like '%bcd%'; +select * from test2 where t like E'%\\bcd%'; +select * from test2 where t ilike '%BCD%'; +select * from test2 where t ilike 'qua%'; +select * from test2 where t like '%z foo bar%'; +select * from test2 where t like ' z foo%'; +explain (costs off) + select * from test2 where t ~ '[abc]{3}'; +explain (costs off) + select * from test2 where t ~* 'DEF'; +select * from test2 where t ~ '[abc]{3}'; +select * from test2 where t ~ 'a[bc]+d'; +select * from test2 where t ~ '(abc)*$'; +select * from test2 where t ~* 'DEF'; +select * from test2 where t ~ 'dEf'; +select * from test2 where t ~* '^q'; +select * from test2 where t ~* '[abc]{3}[def]{3}'; +select * from test2 where t ~* 'ab[a-z]{3}'; +select * from test2 where t ~* '(^| )qua'; +select * from test2 where t ~ 'q.*rk$'; +select * from test2 where t ~ 'q'; +select * from test2 where t ~ '[a-z]{3}'; +select * from test2 where t ~* '(a{10}|b{10}|c{10}){10}'; +select * from test2 where t ~ 'z foo bar'; +select * from test2 where t ~ ' z foo bar'; +select * from test2 where t ~ ' z foo bar'; +select * from test2 where t ~ ' z foo'; +select * from test2 where t ~ 'qua(?!foo)'; +select * from test2 where t ~ '/\d+/-\d'; +-- test = operator +explain (costs off) + select * from test2 where t = 'abcdef'; +select * from test2 where t = 'abcdef'; +explain (costs off) + select * from test2 where t = '%line%'; +select * from test2 where t = '%line%'; +select * from test2 where t = 'li_e 1'; +select * from test2 where t = '%line 2'; +select * from test2 where t = 'line 3%'; +select * from test2 where t = '%line 3%'; +select * from test2 where t = '%line 4%'; +select * from test2 where t = '%line 5%'; +select * from test2 where t = '%li_ne 5%'; +select * from test2 where t = '%li%ne 5%'; +select * from test2 where t = 'line 6'; +select * from test2 where t = 'li_e 6'; +drop index test2_idx_gin; + +create index test2_idx_gist on test2 using gist (t gist_trgm_ops); + +-- Check similarity threshold (bug #14202) + +CREATE TEMP TABLE restaurants (city text); +INSERT INTO restaurants SELECT 'Warsaw' FROM generate_series(1, 10000); +INSERT INTO restaurants SELECT 'Szczecin' FROM generate_series(1, 10000); +CREATE INDEX ON restaurants USING gist(city gist_trgm_ops); + +-- Similarity of the two names (for reference). +SELECT similarity('Szczecin', 'Warsaw'); + +-- Should get only 'Warsaw' for either setting of set_limit. +EXPLAIN (COSTS OFF) +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; +SELECT set_limit(0.3); +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; +SELECT set_limit(0.5); +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; diff --git a/contrib/pg_trgm/sql/yb.port.pg_word_trgm.sql b/contrib/pg_trgm/sql/yb.port.pg_word_trgm.sql new file mode 100644 index 0000000000000..0296695683fab --- /dev/null +++ b/contrib/pg_trgm/sql/yb.port.pg_word_trgm.sql @@ -0,0 +1,34 @@ +CREATE TABLE test_trgm2(t text COLLATE "C"); + +\copy test_trgm2 from 'data/trgm2.data' + +-- reduce noise +set extra_float_digits = 0; + +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; +select t <->> 'Kabankala', t from test_trgm2 order by t <->> 'Kabankala' limit 7; + +create index trgm_idx2 on test_trgm2 using gist (t gist_trgm_ops); + +create index trgm_idx2 on test_trgm2 using gin (t gin_trgm_ops); +set enable_seqscan=off; + +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + +set "pg_trgm.word_similarity_threshold" to 0.5; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + +set "pg_trgm.word_similarity_threshold" to 0.3; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; diff --git a/contrib/pg_trgm/trgm_gin.c b/contrib/pg_trgm/trgm_gin.c index 32fafef203f50..4f512cdf60e75 100644 --- a/contrib/pg_trgm/trgm_gin.c +++ b/contrib/pg_trgm/trgm_gin.c @@ -97,6 +97,7 @@ gin_extract_query_trgm(PG_FUNCTION_ARGS) elog(ERROR, "cannot handle ~~* with case-sensitive trigrams"); #endif /* FALL THRU */ + yb_switch_fallthrough(); case LikeStrategyNumber: /* @@ -111,6 +112,7 @@ gin_extract_query_trgm(PG_FUNCTION_ARGS) elog(ERROR, "cannot handle ~* with case-sensitive trigrams"); #endif /* FALL THRU */ + yb_switch_fallthrough(); case RegExpStrategyNumber: trg = createTrgmNFA(val, PG_GET_COLLATION(), &graph, CurrentMemoryContext); @@ -221,6 +223,7 @@ gin_trgm_consistent(PG_FUNCTION_ARGS) elog(ERROR, "cannot handle ~~* with case-sensitive trigrams"); #endif /* FALL THRU */ + yb_switch_fallthrough(); case LikeStrategyNumber: case EqualStrategyNumber: /* Check if all extracted trigrams are presented. */ @@ -239,6 +242,7 @@ gin_trgm_consistent(PG_FUNCTION_ARGS) elog(ERROR, "cannot handle ~* with case-sensitive trigrams"); #endif /* FALL THRU */ + yb_switch_fallthrough(); case RegExpStrategyNumber: if (nkeys < 1) { @@ -307,6 +311,7 @@ gin_trgm_triconsistent(PG_FUNCTION_ARGS) elog(ERROR, "cannot handle ~~* with case-sensitive trigrams"); #endif /* FALL THRU */ + yb_switch_fallthrough(); case LikeStrategyNumber: case EqualStrategyNumber: /* Check if all extracted trigrams are presented. */ @@ -325,6 +330,7 @@ gin_trgm_triconsistent(PG_FUNCTION_ARGS) elog(ERROR, "cannot handle ~* with case-sensitive trigrams"); #endif /* FALL THRU */ + yb_switch_fallthrough(); case RegExpStrategyNumber: if (nkeys < 1) { diff --git a/contrib/pg_trgm/trgm_gist.c b/contrib/pg_trgm/trgm_gist.c index 711413df491b0..a7baa70cb83fa 100644 --- a/contrib/pg_trgm/trgm_gist.c +++ b/contrib/pg_trgm/trgm_gist.c @@ -241,6 +241,7 @@ gtrgm_consistent(PG_FUNCTION_ARGS) elog(ERROR, "cannot handle ~~* with case-sensitive trigrams"); #endif /* FALL THRU */ + yb_switch_fallthrough(); case LikeStrategyNumber: qtrg = generate_wildcard_trgm(VARDATA(query), querysize - VARHDRSZ); @@ -250,6 +251,7 @@ gtrgm_consistent(PG_FUNCTION_ARGS) elog(ERROR, "cannot handle ~* with case-sensitive trigrams"); #endif /* FALL THRU */ + yb_switch_fallthrough(); case RegExpStrategyNumber: qtrg = createTrgmNFA(query, PG_GET_COLLATION(), &graph, fcinfo->flinfo->fn_mcxt); @@ -338,6 +340,7 @@ gtrgm_consistent(PG_FUNCTION_ARGS) elog(ERROR, "cannot handle ~~* with case-sensitive trigrams"); #endif /* FALL THRU */ + yb_switch_fallthrough(); case LikeStrategyNumber: case EqualStrategyNumber: /* Wildcard and equal search are inexact */ @@ -380,6 +383,7 @@ gtrgm_consistent(PG_FUNCTION_ARGS) elog(ERROR, "cannot handle ~* with case-sensitive trigrams"); #endif /* FALL THRU */ + yb_switch_fallthrough(); case RegExpStrategyNumber: /* Regexp search is inexact */ *recheck = true; diff --git a/contrib/pg_trgm/trgm_regexp.c b/contrib/pg_trgm/trgm_regexp.c index 3fc8a9ec6f0d6..406a021352ba8 100644 --- a/contrib/pg_trgm/trgm_regexp.c +++ b/contrib/pg_trgm/trgm_regexp.c @@ -197,6 +197,9 @@ #include "utils/hsearch.h" #include "utils/memutils.h" +/* YB includes */ +#include "common/pg_yb_common.h" + /* * Uncomment (or use -DTRGM_REGEXP_DEBUG) to print debug info, * for exploring and debugging the algorithm implementation. @@ -2140,6 +2143,7 @@ printSourceNFA(regex_t *regex, TrgmColorInfo *colors, int ncolors) int nstates = pg_reg_getnumstates(regex); int state; int i; + const char *yb_tmp_dir = YbGetTmpDir(); initStringInfo(&buf); @@ -2205,9 +2209,19 @@ printSourceNFA(regex_t *regex, TrgmColorInfo *colors, int ncolors) { /* dot -Tpng -o /tmp/source.png < /tmp/source.gv */ - FILE *fp = fopen("/tmp/source.gv", "w"); + FILE *fp = NULL; + char *yb_custom_path = palloc0(MAX_STRING_LEN); + + if (yb_tmp_dir) + { + snprintf(yb_custom_path, MAX_STRING_LEN, "%s/source.gv", yb_tmp_dir); + fp = fopen(yb_custom_path, "w"); + } + else + fp = fopen("/tmp/source.gv", "w"); fprintf(fp, "%s", buf.data); + pfree(yb_custom_path); fclose(fp); } @@ -2224,6 +2238,7 @@ printTrgmNFA(TrgmNFA *trgmNFA) HASH_SEQ_STATUS scan_status; TrgmState *state; TrgmState *initstate = NULL; + const char *yb_tmp_dir = YbGetTmpDir(); initStringInfo(&buf); @@ -2267,9 +2282,20 @@ printTrgmNFA(TrgmNFA *trgmNFA) { /* dot -Tpng -o /tmp/transformed.png < /tmp/transformed.gv */ - FILE *fp = fopen("/tmp/transformed.gv", "w"); + char *yb_custom_path = palloc0(MAX_STRING_LEN); + FILE *fp = NULL; + + if (yb_tmp_dir) + { + snprintf(yb_custom_path, MAX_STRING_LEN, "%s/transformed.gv", + yb_tmp_dir); + fp = fopen(yb_custom_path, "w"); + } + else + fp = fopen("/tmp/transformed.gv", "w"); fprintf(fp, "%s", buf.data); + pfree(yb_custom_path); fclose(fp); } @@ -2358,10 +2384,21 @@ printTrgmPackedGraph(TrgmPackedGraph *packedGraph, TRGM *trigrams) { /* dot -Tpng -o /tmp/packed.png < /tmp/packed.gv */ - FILE *fp = fopen("/tmp/packed.gv", "w"); + FILE *fp = NULL; + char *yb_custom_path = palloc0(MAX_STRING_LEN); + const char *yb_tmp_dir = YbGetTmpDir(); + + if (yb_tmp_dir) + { + snprintf(yb_custom_path, MAX_STRING_LEN, "%s/packed.gv", yb_tmp_dir); + fp = fopen(yb_custom_path, "w"); + } + else + fp = fopen("/tmp/packed.gv", "w"); fprintf(fp, "%s", buf.data); fclose(fp); + pfree(yb_custom_path); } pfree(buf.data); diff --git a/contrib/pg_trgm/yb_schedule b/contrib/pg_trgm/yb_schedule new file mode 100644 index 0000000000000..6f202820e15f1 --- /dev/null +++ b/contrib/pg_trgm/yb_schedule @@ -0,0 +1,6 @@ +# contrib/pg_trgm/yb_schedule + +# Ported. Use the same order as REGRESS in Makefile. +test: yb.port.pg_trgm +test: yb.port.pg_word_trgm +test: yb.port.pg_strict_word_trgm diff --git a/contrib/pgcrypto/crypt-blowfish.c b/contrib/pgcrypto/crypt-blowfish.c index a663852ccf90c..5b5df306f84bd 100644 --- a/contrib/pgcrypto/crypt-blowfish.c +++ b/contrib/pgcrypto/crypt-blowfish.c @@ -579,8 +579,8 @@ BF_set_key(const char *key, BF_key expanded, BF_key initial, } char * -_crypt_blowfish_rn(const char *key, const char *setting, - char *output, int size) +yb_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size) { struct { diff --git a/contrib/pgcrypto/crypt-gensalt.c b/contrib/pgcrypto/crypt-gensalt.c index 740f3612532b0..0c1bd752bcaf7 100644 --- a/contrib/pgcrypto/crypt-gensalt.c +++ b/contrib/pgcrypto/crypt-gensalt.c @@ -22,8 +22,8 @@ static unsigned char _crypt_itoa64[64 + 1] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; char * -_crypt_gensalt_traditional_rn(unsigned long count, - const char *input, int size, char *output, int output_size) +yb_crypt_gensalt_traditional_rn(unsigned long count, + const char *input, int size, char *output, int output_size) { if (size < 2 || output_size < 2 + 1 || (count && count != 25)) { @@ -40,8 +40,8 @@ _crypt_gensalt_traditional_rn(unsigned long count, } char * -_crypt_gensalt_extended_rn(unsigned long count, - const char *input, int size, char *output, int output_size) +yb_crypt_gensalt_extended_rn(unsigned long count, + const char *input, int size, char *output, int output_size) { unsigned long value; @@ -76,8 +76,8 @@ _crypt_gensalt_extended_rn(unsigned long count, } char * -_crypt_gensalt_md5_rn(unsigned long count, - const char *input, int size, char *output, int output_size) +yb_crypt_gensalt_md5_rn(unsigned long count, + const char *input, int size, char *output, int output_size) { unsigned long value; @@ -158,8 +158,8 @@ BF_encode(char *dst, const BF_word *src, int size) } char * -_crypt_gensalt_blowfish_rn(unsigned long count, - const char *input, int size, char *output, int output_size) +yb_crypt_gensalt_blowfish_rn(unsigned long count, + const char *input, int size, char *output, int output_size) { if (size < 16 || output_size < 7 + 22 + 1 || (count && (count < 4 || count > 31))) diff --git a/contrib/pgcrypto/pgp-info.c b/contrib/pgcrypto/pgp-info.c index 83dc60486bd98..c62620f5f3c59 100644 --- a/contrib/pgcrypto/pgp-info.c +++ b/contrib/pgcrypto/pgp-info.c @@ -170,6 +170,7 @@ pgp_get_keyid(MBuf *pgp_data, char *dst) case PGP_PKT_SYMENCRYPTED_SESSKEY: got_symenc_key++; /* fall through */ + yb_switch_fallthrough(); case PGP_PKT_SIGNATURE: case PGP_PKT_MARKER: case PGP_PKT_TRUST: diff --git a/contrib/pgcrypto/px-crypt.c b/contrib/pgcrypto/px-crypt.c index 0913ff2c1bc2e..3f5984a0d9632 100644 --- a/contrib/pgcrypto/px-crypt.c +++ b/contrib/pgcrypto/px-crypt.c @@ -63,7 +63,7 @@ run_crypt_bf(const char *psw, const char *salt, { char *res; - res = _crypt_blowfish_rn(psw, salt, buf, len); + res = yb_crypt_blowfish_rn(psw, salt, buf, len); return res; } @@ -121,10 +121,10 @@ struct generator }; static struct generator gen_list[] = { - {"des", _crypt_gensalt_traditional_rn, 2, 0, 0, 0}, - {"md5", _crypt_gensalt_md5_rn, 6, 0, 0, 0}, - {"xdes", _crypt_gensalt_extended_rn, 3, PX_XDES_ROUNDS, 1, 0xFFFFFF}, - {"bf", _crypt_gensalt_blowfish_rn, 16, PX_BF_ROUNDS, 4, 31}, + {"des", yb_crypt_gensalt_traditional_rn, 2, 0, 0, 0}, + {"md5", yb_crypt_gensalt_md5_rn, 6, 0, 0, 0}, + {"xdes", yb_crypt_gensalt_extended_rn, 3, PX_XDES_ROUNDS, 1, 0xFFFFFF}, + {"bf", yb_crypt_gensalt_blowfish_rn, 16, PX_BF_ROUNDS, 4, 31}, {NULL, NULL, 0, 0, 0, 0} }; diff --git a/contrib/pgcrypto/px-crypt.h b/contrib/pgcrypto/px-crypt.h index 08001a81f5e53..7765d817a4542 100644 --- a/contrib/pgcrypto/px-crypt.h +++ b/contrib/pgcrypto/px-crypt.h @@ -56,21 +56,21 @@ int px_gen_salt(const char *salt_type, char *dst, int rounds); */ /* crypt-gensalt.c */ -char *_crypt_gensalt_traditional_rn(unsigned long count, - const char *input, int size, char *output, int output_size); -char *_crypt_gensalt_extended_rn(unsigned long count, - const char *input, int size, char *output, int output_size); -char *_crypt_gensalt_md5_rn(unsigned long count, - const char *input, int size, char *output, int output_size); -char *_crypt_gensalt_blowfish_rn(unsigned long count, - const char *input, int size, char *output, int output_size); +char *yb_crypt_gensalt_traditional_rn(unsigned long count, + const char *input, int size, char *output, int output_size); +char *yb_crypt_gensalt_extended_rn(unsigned long count, + const char *input, int size, char *output, int output_size); +char *yb_crypt_gensalt_md5_rn(unsigned long count, + const char *input, int size, char *output, int output_size); +char *yb_crypt_gensalt_blowfish_rn(unsigned long count, + const char *input, int size, char *output, int output_size); /* disable 'extended DES crypt' */ /* #define DISABLE_XDES */ /* crypt-blowfish.c */ -char *_crypt_blowfish_rn(const char *key, const char *setting, - char *output, int size); +char *yb_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size); /* crypt-des.c */ char *px_crypt_des(const char *key, const char *setting); diff --git a/contrib/pgcrypto/yb_schedule b/contrib/pgcrypto/yb_schedule new file mode 100644 index 0000000000000..fe7c975e7727c --- /dev/null +++ b/contrib/pgcrypto/yb_schedule @@ -0,0 +1,27 @@ +#contrib/pgcrypto/yb_schedule +# Since the upstream postgres test works fine without modifications +# (currently), don't bother porting it to yb_pg_ tests + +test: init +test: 3des +test: cast5 +test: crypt-des +test: crypt-xdes +test: hmac-md5 +test: sha2 +test: blowfish +test: crypt-blowfish +test: crypt-md5 +test: des +test: hmac-sha1 +test: md5 +test: sha1 +test: pgp-pubkey-decrypt +test: pgp-pubkey-encrypt +test: pgp-info +test: pgp-encrypt +test: pgp-decrypt +test: pgp-compression +test: pgp-armor +test: pgp-zlib-DISABLED +test: rijndael diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile index c1b0cad453f25..001649a8cb93a 100644 --- a/contrib/postgres_fdw/Makefile +++ b/contrib/postgres_fdw/Makefile @@ -11,7 +11,7 @@ OBJS = \ PGFILEDESC = "postgres_fdw - foreign data wrapper for PostgreSQL" PG_CPPFLAGS = -I$(libpq_srcdir) -SHLIB_LINK_INTERNAL = $(libpq) +SHLIB_LINK_INTERNAL = $(libpq) -L$(YB_BUILD_ROOT)/lib -lyb_pggate -lyb_pggate_util -lyb_pgresult_util EXTENSION = postgres_fdw DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index d7235a50bfe93..6aefb82ac8b82 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -102,6 +102,7 @@ typedef struct deparse_expr_cxt * a base relation. */ StringInfo buf; /* output buffer to append to */ List **params_list; /* exprs that will become remote Params */ + AttrNumber yb_min_attr; /* FirstLowInvalidHeapNumber for Vars */ } deparse_expr_cxt; #define REL_ALIAS_PREFIX "r" @@ -131,6 +132,7 @@ static void deparseTargetList(StringInfo buf, bool is_returning, Bitmapset *attrs_used, bool qualify_col, + AttrNumber yb_min_attr, List **retrieved_attrs); static void deparseExplicitTargetList(List *tlist, bool is_returning, @@ -142,12 +144,16 @@ static void deparseReturningList(StringInfo buf, RangeTblEntry *rte, bool trig_after_row, List *withCheckOptionList, List *returningList, + AttrNumber yb_min_attr, List **retrieved_attrs); static void deparseColumnRef(StringInfo buf, int varno, int varattno, - RangeTblEntry *rte, bool qualify_col); + RangeTblEntry *rte, bool qualify_col, + AttrNumber yb_min_attr); static void deparseRelation(StringInfo buf, Relation rel); static void deparseExpr(Expr *expr, deparse_expr_cxt *context); static void deparseVar(Var *node, deparse_expr_cxt *context); +static void deparseYbBatchedExpr(YbBatchedExpr *node, + deparse_expr_cxt *context); static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype); static void deparseParam(Param *node, deparse_expr_cxt *context); static void deparseSubscriptingRef(SubscriptingRef *node, deparse_expr_cxt *context); @@ -349,7 +355,7 @@ foreign_expr_walker(Node *node, * particular, almost certainly doesn't match). */ if (var->varattno < 0 && - var->varattno != SelfItemPointerAttributeNumber) + var->varattno != fpinfo->yb_min_attr) return false; /* Else check the collation */ @@ -1177,6 +1183,7 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, context.foreignrel = rel; context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel; context.params_list = params_list; + context.yb_min_attr = fpinfo->yb_min_attr; /* Construct SELECT clause */ deparseSelectSql(tlist, is_subquery, retrieved_attrs, &context); @@ -1283,7 +1290,8 @@ deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs, Relation rel = table_open(rte->relid, NoLock); deparseTargetList(buf, rte, foreignrel->relid, rel, false, - fpinfo->attrs_used, false, retrieved_attrs); + fpinfo->attrs_used, false, fpinfo->yb_min_attr, + retrieved_attrs); table_close(rel, NoLock); } } @@ -1337,6 +1345,7 @@ deparseTargetList(StringInfo buf, bool is_returning, Bitmapset *attrs_used, bool qualify_col, + AttrNumber yb_min_attr, List **retrieved_attrs) { TupleDesc tupdesc = RelationGetDescr(rel); @@ -1347,7 +1356,7 @@ deparseTargetList(StringInfo buf, *retrieved_attrs = NIL; /* If there's a whole-row reference, we'll need all the columns. */ - have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, + have_wholerow = bms_is_member(0 - yb_min_attr, attrs_used); first = true; @@ -1360,7 +1369,7 @@ deparseTargetList(StringInfo buf, continue; if (have_wholerow || - bms_is_member(i - FirstLowInvalidHeapAttributeNumber, + bms_is_member(i - yb_min_attr, attrs_used)) { if (!first) @@ -1369,7 +1378,7 @@ deparseTargetList(StringInfo buf, appendStringInfoString(buf, " RETURNING "); first = false; - deparseColumnRef(buf, rtindex, i, rte, qualify_col); + deparseColumnRef(buf, rtindex, i, rte, qualify_col, yb_min_attr); *retrieved_attrs = lappend_int(*retrieved_attrs, i); } @@ -1378,8 +1387,10 @@ deparseTargetList(StringInfo buf, /* * Add ctid if needed. We currently don't support retrieving any other * system columns. + * YB note: The target list will contain CTID only when the server is of + * type postgres. */ - if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber, + if (bms_is_member(SelfItemPointerAttributeNumber - yb_min_attr, attrs_used)) { if (!first) @@ -1395,6 +1406,23 @@ deparseTargetList(StringInfo buf, *retrieved_attrs = lappend_int(*retrieved_attrs, SelfItemPointerAttributeNumber); } + else if (bms_is_member(YBTupleIdAttributeNumber - yb_min_attr, + attrs_used)) + { + if (!first) + appendStringInfoString(buf, ", "); + else if (is_returning) + appendStringInfoString(buf, " RETURNING "); + first = false; + + if (qualify_col) + ADD_REL_QUALIFIER(buf, rtindex); + appendStringInfoString(buf, "ybctid"); + + *retrieved_attrs = lappend_int(*retrieved_attrs, + YBTupleIdAttributeNumber); + } + /* Don't generate bad syntax if no undropped columns */ if (first && !is_returning) @@ -1756,6 +1784,7 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, context.scanrel = foreignrel; context.root = root; context.params_list = params_list; + context.yb_min_attr = fpinfo->yb_min_attr; appendStringInfoChar(buf, '('); appendConditions(fpinfo->joinclauses, &context); @@ -1879,6 +1908,7 @@ deparseInsertSql(StringInfo buf, RangeTblEntry *rte, AttrNumber pindex; bool first; ListCell *lc; + AttrNumber yb_min_attr = yb_get_min_attr_from_ftrelid(RelationGetRelid(rel)); appendStringInfoString(buf, "INSERT INTO "); deparseRelation(buf, rel); @@ -1896,7 +1926,7 @@ deparseInsertSql(StringInfo buf, RangeTblEntry *rte, appendStringInfoString(buf, ", "); first = false; - deparseColumnRef(buf, rtindex, attnum, rte, false); + deparseColumnRef(buf, rtindex, attnum, rte, false, yb_min_attr); } appendStringInfoString(buf, ") VALUES ("); @@ -1932,7 +1962,8 @@ deparseInsertSql(StringInfo buf, RangeTblEntry *rte, deparseReturningList(buf, rte, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_insert_after_row, - withCheckOptionList, returningList, retrieved_attrs); + withCheckOptionList, returningList, yb_min_attr, + retrieved_attrs); } /* @@ -2012,6 +2043,7 @@ deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, AttrNumber pindex; bool first; ListCell *lc; + AttrNumber yb_min_attr = yb_get_min_attr_from_ftrelid(RelationGetRelid(rel)); appendStringInfoString(buf, "UPDATE "); deparseRelation(buf, rel); @@ -2028,7 +2060,7 @@ deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, appendStringInfoString(buf, ", "); first = false; - deparseColumnRef(buf, rtindex, attnum, rte, false); + deparseColumnRef(buf, rtindex, attnum, rte, false, yb_min_attr); if (attr->attgenerated) appendStringInfoString(buf, " = DEFAULT"); else @@ -2037,11 +2069,13 @@ deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, pindex++; } } - appendStringInfoString(buf, " WHERE ctid = $1"); + appendStringInfo(buf, " WHERE %s = $1", + (yb_min_attr == YBFirstLowInvalidAttributeNumber) ? "ybctid" : "ctid"); deparseReturningList(buf, rte, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_update_after_row, - withCheckOptionList, returningList, retrieved_attrs); + withCheckOptionList, returningList, yb_min_attr, + retrieved_attrs); } /* @@ -2078,6 +2112,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, RangeTblEntry *rte = planner_rt_fetch(rtindex, root); ListCell *lc, *lc2; + AttrNumber yb_min_attr = yb_get_min_attr_from_ftrelid(RelationGetRelid(rel)); /* Set up context struct for recursion */ context.root = root; @@ -2085,6 +2120,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, context.scanrel = foreignrel; context.buf = buf; context.params_list = params_list; + context.yb_min_attr = yb_min_attr; appendStringInfoString(buf, "UPDATE "); deparseRelation(buf, rel); @@ -2108,7 +2144,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, appendStringInfoString(buf, ", "); first = false; - deparseColumnRef(buf, rtindex, attnum, rte, false); + deparseColumnRef(buf, rtindex, attnum, rte, false, yb_min_attr); appendStringInfoString(buf, " = "); deparseExpr((Expr *) tle->expr, &context); } @@ -2136,7 +2172,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, &context); else deparseReturningList(buf, rte, rtindex, rel, false, - NIL, returningList, retrieved_attrs); + NIL, returningList, yb_min_attr, retrieved_attrs); } /* @@ -2152,13 +2188,16 @@ deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, List *returningList, List **retrieved_attrs) { + AttrNumber yb_min_attr = yb_get_min_attr_from_ftrelid(RelationGetRelid(rel)); + appendStringInfoString(buf, "DELETE FROM "); deparseRelation(buf, rel); - appendStringInfoString(buf, " WHERE ctid = $1"); + appendStringInfo(buf, " WHERE %s = $1", + (yb_min_attr == YBFirstLowInvalidAttributeNumber) ? "ybctid" : "ctid"); deparseReturningList(buf, rte, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_delete_after_row, - NIL, returningList, retrieved_attrs); + NIL, returningList, yb_min_attr, retrieved_attrs); } /* @@ -2185,6 +2224,7 @@ deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, List **retrieved_attrs) { deparse_expr_cxt context; + AttrNumber yb_min_attr = yb_get_min_attr_from_ftrelid(RelationGetRelid(rel)); /* Set up context struct for recursion */ context.root = root; @@ -2192,6 +2232,7 @@ deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, context.scanrel = foreignrel; context.buf = buf; context.params_list = params_list; + context.yb_min_attr = yb_min_attr; appendStringInfoString(buf, "DELETE FROM "); deparseRelation(buf, rel); @@ -2220,7 +2261,7 @@ deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, else deparseReturningList(buf, planner_rt_fetch(rtindex, root), rtindex, rel, false, - NIL, returningList, retrieved_attrs); + NIL, returningList, yb_min_attr, retrieved_attrs); } /* @@ -2232,6 +2273,7 @@ deparseReturningList(StringInfo buf, RangeTblEntry *rte, bool trig_after_row, List *withCheckOptionList, List *returningList, + AttrNumber yb_min_attr, List **retrieved_attrs) { Bitmapset *attrs_used = NULL; @@ -2240,7 +2282,7 @@ deparseReturningList(StringInfo buf, RangeTblEntry *rte, { /* whole-row reference acquires all non-system columns */ attrs_used = - bms_make_singleton(0 - FirstLowInvalidHeapAttributeNumber); + bms_make_singleton(0 - yb_min_attr); } if (withCheckOptionList != NIL) @@ -2254,8 +2296,8 @@ deparseReturningList(StringInfo buf, RangeTblEntry *rte, * might differ from the data supplied by the core code, for example * as a result of remote triggers. */ - pull_varattnos((Node *) withCheckOptionList, rtindex, - &attrs_used); + pull_varattnos_min_attr((Node *) withCheckOptionList, rtindex, + &attrs_used, yb_min_attr + 1); } if (returningList != NIL) @@ -2264,13 +2306,27 @@ deparseReturningList(StringInfo buf, RangeTblEntry *rte, * We need the attrs, non-system and system, mentioned in the local * query's RETURNING list. */ - pull_varattnos((Node *) returningList, rtindex, - &attrs_used); + pull_varattnos_min_attr((Node *) returningList, rtindex, + &attrs_used, yb_min_attr + 1); } if (attrs_used != NULL) + { + /* + * RETURNING projections assume that the ybctid of the tuple is + * populated. For postgres_fdw, the ybctid is lost when the tuple is + * returned by libpq from the remote server, unless explicitly requested. + * Add ybctid to the returning list to work around this. Remove once + * GH-27762 is fixed. + */ + if (yb_min_attr == YBFirstLowInvalidAttributeNumber) + attrs_used = bms_add_member(attrs_used, + YBTupleIdAttributeNumber - yb_min_attr); + deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, false, - retrieved_attrs); + yb_min_attr, retrieved_attrs); + } + else *retrieved_attrs = NIL; } @@ -2398,7 +2454,7 @@ deparseTruncateSql(StringInfo buf, */ static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, - bool qualify_col) + bool qualify_col, AttrNumber yb_min_attr) { /* We support fetching the remote side's CTID and OID. */ if (varattno == SelfItemPointerAttributeNumber) @@ -2407,6 +2463,12 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, ADD_REL_QUALIFIER(buf, varno); appendStringInfoString(buf, "ctid"); } + else if (varattno == YBTupleIdAttributeNumber) + { + if (qualify_col) + ADD_REL_QUALIFIER(buf, varno); + appendStringInfoString(buf, "ybctid"); + } else if (varattno < 0) { /* @@ -2453,7 +2515,7 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, * "whole row" attribute. */ attrs_used = bms_add_member(NULL, - 0 - FirstLowInvalidHeapAttributeNumber); + 0 - yb_min_attr); /* * In case the whole-row reference is under an outer join then it has @@ -2470,7 +2532,7 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, appendStringInfoString(buf, "ROW("); deparseTargetList(buf, rte, varno, rel, false, attrs_used, qualify_col, - &retrieved_attrs); + yb_min_attr, &retrieved_attrs); appendStringInfoChar(buf, ')'); /* Complete the CASE WHEN statement started above. */ @@ -2616,6 +2678,9 @@ deparseExpr(Expr *node, deparse_expr_cxt *context) case T_Param: deparseParam((Param *) node, context); break; + case T_YbBatchedExpr: + deparseYbBatchedExpr((YbBatchedExpr *) node, context); + break; case T_SubscriptingRef: deparseSubscriptingRef((SubscriptingRef *) node, context); break; @@ -2690,7 +2755,7 @@ deparseVar(Var *node, deparse_expr_cxt *context) if (bms_is_member(node->varno, relids) && node->varlevelsup == 0) deparseColumnRef(context->buf, node->varno, node->varattno, planner_rt_fetch(node->varno, context->root), - qualify_col); + qualify_col, context->yb_min_attr); else { /* Treat like a Param */ @@ -2722,6 +2787,18 @@ deparseVar(Var *node, deparse_expr_cxt *context) } } +/* + * Deparse given YbBatchedExpr node into context->buf. + * + * We simply deparse the original expression contained in this batched + * expression. + */ +static void +deparseYbBatchedExpr(YbBatchedExpr *node, deparse_expr_cxt *context) +{ + deparseExpr(node->orig_expr, context); +} + /* * Deparse given constant value into context->buf. * diff --git a/contrib/postgres_fdw/expected/yb.orig.global_views.out b/contrib/postgres_fdw/expected/yb.orig.global_views.out new file mode 100644 index 0000000000000..22d4347011e5a --- /dev/null +++ b/contrib/postgres_fdw/expected/yb.orig.global_views.out @@ -0,0 +1,1327 @@ +-- +-- Tests for global views using file_fdw as the underlying data source. +-- Each tserver has a different CSV file placed in its data directory +-- by the test framework, simulating per-node data like pg_stat_statements. +-- +-- The global views RPC handler connects to template1 on remote tservers, +-- so all objects must be created in template1. +\c template1 +-- Install required extensions +CREATE EXTENSION file_fdw; +CREATE EXTENSION postgres_fdw; +-- Create enum type for the status column +CREATE TYPE metric_status AS ENUM ('active', 'warning', 'critical'); +-- Create file_fdw server for reading local CSV files +CREATE SERVER file_server FOREIGN DATA WRAPPER file_fdw; +-- Create file_fdw foreign table reading the tserver-local CSV. +-- The relative path resolves against each tserver's data directory, +-- so each tserver reads its own gv_test_data.csv with unique data. +CREATE FOREIGN TABLE local_node_metrics ( + node_id int, + metric_name varchar(30), + metric_value real, + event_count int, + total_bytes bigint, + ratio double precision, + is_healthy boolean, + tag char(5), + status metric_status, + created_at timestamp, + updated_at timestamptz +) SERVER file_server +OPTIONS (format 'csv', filename 'gv_test_data.csv'); +-- Verify local data is readable (should show only this node's data) +SELECT count(*) > 0 AS has_local_data FROM local_node_metrics; + has_local_data +---------------- + t +(1 row) + +-- Create global views server +CREATE SERVER IF NOT EXISTS gv_server FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (server_type 'federatedYugabyteDB'); +-- Create global view foreign table +CREATE FOREIGN TABLE "gv$node_metrics" ( + node_id int, + metric_name varchar(30), + metric_value real, + event_count int, + total_bytes bigint, + ratio double precision, + is_healthy boolean, + tag char(5), + status metric_status, + created_at timestamp, + updated_at timestamptz +) SERVER gv_server +OPTIONS (schema_name 'public', table_name 'local_node_metrics'); +-- +-- Basic SELECT: verify data from all 3 nodes (11 rows total) +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM "gv$node_metrics" ORDER BY node_id, metric_name; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, metric_value, event_count, total_bytes, ratio, is_healthy, tag, status, created_at, updated_at + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value, event_count, total_bytes, ratio, is_healthy, tag, status, created_at, updated_at + Remote SQL: SELECT node_id, metric_name, metric_value, event_count, total_bytes, ratio, is_healthy, tag, status, created_at, updated_at FROM public.local_node_metrics +(6 rows) + +SELECT * FROM "gv$node_metrics" ORDER BY node_id, metric_name; + node_id | metric_name | metric_value | event_count | total_bytes | ratio | is_healthy | tag | status | created_at | updated_at +---------+--------------+--------------+-------------+-------------+--------+------------+-------+----------+--------------------------+------------------------------ + 1 | | 0.1 | 77 | 100 | 0.001 | t | EMPTY | active | Wed Jan 15 12:00:00 2025 | Wed Jan 15 04:00:00 2025 PST + 1 | cpu_usage | 45.5 | 100 | 5000000000 | 0.4551 | t | CPU01 | active | Wed Jan 15 10:30:00 2025 | Wed Jan 15 02:30:00 2025 PST + 1 | disk_io | 120 | 50 | 12000000000 | 0.6001 | t | DSK01 | active | Wed Jan 15 11:30:00 2025 | Wed Jan 15 08:30:00 2025 PST + 1 | memory_usage | 72.5 | 200 | 8500000000 | 0.726 | f | MEM01 | warning | Wed Jan 15 11:00:00 2025 | Tue Jan 14 21:30:00 2025 PST + 2 | cpu_usage | 62.5 | 150 | 6200000000 | 0.6254 | t | CPU02 | active | Thu Feb 20 14:00:00 2025 | Thu Feb 20 06:00:00 2025 PST + 2 | disk_io | 89.5 | 75 | 3500000000 | 0.895 | f | DSK02 | critical | Thu Feb 20 15:00:00 2025 | Thu Feb 20 15:00:00 2025 PST + 2 | memory_usage | 55.5 | 180 | 9800000000 | 0.5558 | t | MEM02 | active | Thu Feb 20 14:30:00 2025 | Wed Feb 19 21:30:00 2025 PST + 2 | | 0.1 | 83 | 100 | 0.001 | t | | active | Thu Feb 20 16:00:00 2025 | Thu Feb 20 08:00:00 2025 PST + 3 | cpu_usage | 38.5 | 90 | 4100000000 | 0.3853 | t | CPU03 | active | Mon Mar 10 09:00:00 2025 | Mon Mar 10 02:00:00 2025 PDT + 3 | disk_io | 67.5 | 60 | 7700000000 | 0.676 | t | DSK03 | active | Mon Mar 10 10:00:00 2025 | Mon Mar 10 08:00:00 2025 PDT + 3 | memory_usage | 91.5 | 250 | 11000000000 | 0.9157 | f | MEM03 | critical | Mon Mar 10 09:30:00 2025 | Sun Mar 09 21:00:00 2025 PDT +(11 rows) + +-- +-- WHERE clause tests: one per type +-- +-- int4 +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name FROM "gv$node_metrics" +WHERE node_id IN (1, 3) +ORDER BY node_id, metric_name; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name + Remote Filter: ("gv$node_metrics".node_id = ANY ('{1,3}'::integer[])) + Remote SQL: SELECT node_id, metric_name FROM public.local_node_metrics WHERE ((node_id = ANY ('{1,3}'::integer[]))) +(7 rows) + +SELECT node_id, metric_name FROM "gv$node_metrics" +WHERE node_id IN (1, 3) +ORDER BY node_id, metric_name; + node_id | metric_name +---------+-------------- + 1 | + 1 | cpu_usage + 1 | disk_io + 1 | memory_usage + 3 | cpu_usage + 3 | disk_io + 3 | memory_usage +(7 rows) + +-- float4 (real) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +WHERE metric_value > 70 +ORDER BY node_id, metric_name; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, metric_value + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value + Remote Filter: ("gv$node_metrics".metric_value > '70'::double precision) + Remote SQL: SELECT node_id, metric_name, metric_value FROM public.local_node_metrics WHERE ((metric_value > 70::double precision)) +(7 rows) + +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +WHERE metric_value > 70 +ORDER BY node_id, metric_name; + node_id | metric_name | metric_value +---------+--------------+-------------- + 1 | disk_io | 120 + 1 | memory_usage | 72.5 + 2 | disk_io | 89.5 + 3 | memory_usage | 91.5 +(4 rows) + +-- varchar (variable-length string) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, status FROM "gv$node_metrics" +WHERE status = 'active' +ORDER BY node_id, metric_name; + QUERY PLAN +---------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, status + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, status + Filter: ("gv$node_metrics".status = 'active'::metric_status) + Remote SQL: SELECT node_id, metric_name, status FROM public.local_node_metrics +(7 rows) + +SELECT node_id, metric_name, status FROM "gv$node_metrics" +WHERE status = 'active' +ORDER BY node_id, metric_name; + node_id | metric_name | status +---------+--------------+-------- + 1 | | active + 1 | cpu_usage | active + 1 | disk_io | active + 2 | cpu_usage | active + 2 | memory_usage | active + 2 | | active + 3 | cpu_usage | active + 3 | disk_io | active +(8 rows) + +-- boolean +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, is_healthy FROM "gv$node_metrics" +WHERE is_healthy = false +ORDER BY node_id, metric_name; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, is_healthy + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, is_healthy + Remote Filter: (NOT "gv$node_metrics".is_healthy) + Remote SQL: SELECT node_id, metric_name, is_healthy FROM public.local_node_metrics WHERE ((NOT is_healthy)) +(7 rows) + +SELECT node_id, metric_name, is_healthy FROM "gv$node_metrics" +WHERE is_healthy = false +ORDER BY node_id, metric_name; + node_id | metric_name | is_healthy +---------+--------------+------------ + 1 | memory_usage | f + 2 | disk_io | f + 3 | memory_usage | f +(3 rows) + +-- int8 (bigint) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, total_bytes FROM "gv$node_metrics" +WHERE total_bytes > 8000000000 +ORDER BY node_id, metric_name; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, total_bytes + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, total_bytes + Remote Filter: ("gv$node_metrics".total_bytes > '8000000000'::bigint) + Remote SQL: SELECT node_id, metric_name, total_bytes FROM public.local_node_metrics WHERE ((total_bytes > 8000000000::bigint)) +(7 rows) + +SELECT node_id, metric_name, total_bytes FROM "gv$node_metrics" +WHERE total_bytes > 8000000000 +ORDER BY node_id, metric_name; + node_id | metric_name | total_bytes +---------+--------------+------------- + 1 | disk_io | 12000000000 + 1 | memory_usage | 8500000000 + 2 | memory_usage | 9800000000 + 3 | memory_usage | 11000000000 +(4 rows) + +-- float8 (double precision) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, ratio FROM "gv$node_metrics" +WHERE ratio > 0.7 +ORDER BY node_id, metric_name; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, ratio + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, ratio + Remote Filter: ("gv$node_metrics".ratio > '0.7'::double precision) + Remote SQL: SELECT node_id, metric_name, ratio FROM public.local_node_metrics WHERE ((ratio > 0.7::double precision)) +(7 rows) + +SELECT node_id, metric_name, ratio FROM "gv$node_metrics" +WHERE ratio > 0.7 +ORDER BY node_id, metric_name; + node_id | metric_name | ratio +---------+--------------+-------- + 1 | memory_usage | 0.726 + 2 | disk_io | 0.895 + 3 | memory_usage | 0.9157 +(3 rows) + +-- char(5) (fixed-length string) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, tag FROM "gv$node_metrics" +WHERE tag LIKE 'CPU%' +ORDER BY node_id; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, tag + Sort Key: "gv$node_metrics".node_id + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, tag + Remote Filter: ("gv$node_metrics".tag ~~ 'CPU%'::text) + Remote SQL: SELECT node_id, metric_name, tag FROM public.local_node_metrics WHERE ((tag ~~ 'CPU%'::text)) +(7 rows) + +SELECT node_id, metric_name, tag FROM "gv$node_metrics" +WHERE tag LIKE 'CPU%' +ORDER BY node_id; + node_id | metric_name | tag +---------+-------------+------- + 1 | cpu_usage | CPU01 + 2 | cpu_usage | CPU02 + 3 | cpu_usage | CPU03 +(3 rows) + +-- enum +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, status FROM "gv$node_metrics" +WHERE status = 'critical'::metric_status +ORDER BY node_id, metric_name; + QUERY PLAN +---------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, status + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, status + Filter: ("gv$node_metrics".status = 'critical'::metric_status) + Remote SQL: SELECT node_id, metric_name, status FROM public.local_node_metrics +(7 rows) + +SELECT node_id, metric_name, status FROM "gv$node_metrics" +WHERE status = 'critical'::metric_status +ORDER BY node_id, metric_name; + node_id | metric_name | status +---------+--------------+---------- + 2 | disk_io | critical + 3 | memory_usage | critical +(2 rows) + +-- timestamp (without TZ) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, created_at FROM "gv$node_metrics" +WHERE created_at >= '2025-02-01'::timestamp +ORDER BY node_id, metric_name; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------ + Sort + Output: node_id, metric_name, created_at + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, created_at + Remote Filter: ("gv$node_metrics".created_at >= 'Sat Feb 01 00:00:00 2025'::timestamp without time zone) + Remote SQL: SELECT node_id, metric_name, created_at FROM public.local_node_metrics WHERE ((created_at >= '2025-02-01 00:00:00')) +(7 rows) + +SELECT node_id, metric_name, created_at FROM "gv$node_metrics" +WHERE created_at >= '2025-02-01'::timestamp +ORDER BY node_id, metric_name; + node_id | metric_name | created_at +---------+--------------+-------------------------- + 2 | cpu_usage | Thu Feb 20 14:00:00 2025 + 2 | disk_io | Thu Feb 20 15:00:00 2025 + 2 | memory_usage | Thu Feb 20 14:30:00 2025 + 2 | | Thu Feb 20 16:00:00 2025 + 3 | cpu_usage | Mon Mar 10 09:00:00 2025 + 3 | disk_io | Mon Mar 10 10:00:00 2025 + 3 | memory_usage | Mon Mar 10 09:30:00 2025 +(7 rows) + +-- timestamptz (with TZ) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, updated_at FROM "gv$node_metrics" +WHERE updated_at < '2025-02-01 00:00:00+00'::timestamptz +ORDER BY node_id, updated_at; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, updated_at + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".updated_at + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, updated_at + Remote Filter: ("gv$node_metrics".updated_at < 'Fri Jan 31 16:00:00 2025 PST'::timestamp with time zone) + Remote SQL: SELECT node_id, metric_name, updated_at FROM public.local_node_metrics WHERE ((updated_at < '2025-01-31 16:00:00-08')) +(7 rows) + +SELECT node_id, metric_name, updated_at FROM "gv$node_metrics" +WHERE updated_at < '2025-02-01 00:00:00+00'::timestamptz +ORDER BY node_id, updated_at; + node_id | metric_name | updated_at +---------+--------------+------------------------------ + 1 | memory_usage | Tue Jan 14 21:30:00 2025 PST + 1 | cpu_usage | Wed Jan 15 02:30:00 2025 PST + 1 | | Wed Jan 15 04:00:00 2025 PST + 1 | disk_io | Wed Jan 15 08:30:00 2025 PST +(4 rows) + +-- +-- Aggregate queries +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT COUNT(*) FROM "gv$node_metrics"; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value, event_count, total_bytes, ratio, is_healthy, tag, status, created_at, updated_at + Remote SQL: SELECT NULL FROM public.local_node_metrics +(5 rows) + +SELECT COUNT(*) FROM "gv$node_metrics"; + count +------- + 11 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT metric_name, + COUNT(*) AS node_count, + SUM(event_count) AS total_events, + SUM(total_bytes) AS total_bytes_sum +FROM "gv$node_metrics" +GROUP BY metric_name +ORDER BY metric_name; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: metric_name, (count(*)), (sum(event_count)), (sum(total_bytes)) + Sort Key: "gv$node_metrics".metric_name + -> HashAggregate + Output: metric_name, count(*), sum(event_count), sum(total_bytes) + Group Key: "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value, event_count, total_bytes, ratio, is_healthy, tag, status, created_at, updated_at + Remote SQL: SELECT metric_name, event_count, total_bytes FROM public.local_node_metrics +(9 rows) + +SELECT metric_name, + COUNT(*) AS node_count, + SUM(event_count) AS total_events, + SUM(total_bytes) AS total_bytes_sum +FROM "gv$node_metrics" +GROUP BY metric_name +ORDER BY metric_name; + metric_name | node_count | total_events | total_bytes_sum +--------------+------------+--------------+----------------- + | 1 | 77 | 100 + cpu_usage | 3 | 340 | 15300000000 + disk_io | 3 | 185 | 23200000000 + memory_usage | 3 | 630 | 29300000000 + | 1 | 83 | 100 +(5 rows) + +-- Aggregate on boolean +EXPLAIN (VERBOSE, COSTS OFF) +SELECT is_healthy, COUNT(*) AS cnt +FROM "gv$node_metrics" +GROUP BY is_healthy +ORDER BY is_healthy; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: is_healthy, (count(*)) + Sort Key: "gv$node_metrics".is_healthy + -> HashAggregate + Output: is_healthy, count(*) + Group Key: "gv$node_metrics".is_healthy + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value, event_count, total_bytes, ratio, is_healthy, tag, status, created_at, updated_at + Remote SQL: SELECT is_healthy FROM public.local_node_metrics +(9 rows) + +SELECT is_healthy, COUNT(*) AS cnt +FROM "gv$node_metrics" +GROUP BY is_healthy +ORDER BY is_healthy; + is_healthy | cnt +------------+----- + f | 3 + t | 8 +(2 rows) + +-- Aggregate on timestamp +EXPLAIN (VERBOSE, COSTS OFF) +SELECT MIN(created_at) AS earliest, MAX(created_at) AS latest +FROM "gv$node_metrics"; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: min(created_at), max(created_at) + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value, event_count, total_bytes, ratio, is_healthy, tag, status, created_at, updated_at + Remote SQL: SELECT created_at FROM public.local_node_metrics +(5 rows) + +SELECT MIN(created_at) AS earliest, MAX(created_at) AS latest +FROM "gv$node_metrics"; + earliest | latest +--------------------------+-------------------------- + Wed Jan 15 10:30:00 2025 | Mon Mar 10 10:00:00 2025 +(1 row) + +-- +-- HAVING clause +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT status, COUNT(*) AS cnt +FROM "gv$node_metrics" +GROUP BY status +HAVING COUNT(*) >= 2 +ORDER BY status; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: status, (count(*)) + Sort Key: "gv$node_metrics".status + -> HashAggregate + Output: status, count(*) + Group Key: "gv$node_metrics".status + Filter: (count(*) >= 2) + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value, event_count, total_bytes, ratio, is_healthy, tag, status, created_at, updated_at + Remote SQL: SELECT status FROM public.local_node_metrics +(10 rows) + +SELECT status, COUNT(*) AS cnt +FROM "gv$node_metrics" +GROUP BY status +HAVING COUNT(*) >= 2 +ORDER BY status; + status | cnt +----------+----- + active | 8 + critical | 2 +(2 rows) + +-- +-- DISTINCT +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT DISTINCT metric_name FROM "gv$node_metrics" ORDER BY metric_name; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: metric_name + Sort Key: "gv$node_metrics".metric_name + -> HashAggregate + Output: metric_name + Group Key: "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value, event_count, total_bytes, ratio, is_healthy, tag, status, created_at, updated_at + Remote SQL: SELECT metric_name FROM public.local_node_metrics +(9 rows) + +SELECT DISTINCT metric_name FROM "gv$node_metrics" ORDER BY metric_name; + metric_name +-------------- + + cpu_usage + disk_io + memory_usage + +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT DISTINCT status FROM "gv$node_metrics" ORDER BY status; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: status + Sort Key: "gv$node_metrics".status + -> HashAggregate + Output: status + Group Key: "gv$node_metrics".status + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value, event_count, total_bytes, ratio, is_healthy, tag, status, created_at, updated_at + Remote SQL: SELECT status FROM public.local_node_metrics +(9 rows) + +SELECT DISTINCT status FROM "gv$node_metrics" ORDER BY status; + status +---------- + active + warning + critical +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT DISTINCT is_healthy FROM "gv$node_metrics" ORDER BY is_healthy; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: is_healthy + Sort Key: "gv$node_metrics".is_healthy + -> HashAggregate + Output: is_healthy + Group Key: "gv$node_metrics".is_healthy + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value, event_count, total_bytes, ratio, is_healthy, tag, status, created_at, updated_at + Remote SQL: SELECT is_healthy FROM public.local_node_metrics +(9 rows) + +SELECT DISTINCT is_healthy FROM "gv$node_metrics" ORDER BY is_healthy; + is_healthy +------------ + f + t +(2 rows) + +-- +-- LIMIT +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +ORDER BY metric_value DESC LIMIT 3; + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Limit + Output: node_id, metric_name, metric_value + -> Sort + Output: node_id, metric_name, metric_value + Sort Key: "gv$node_metrics".metric_value DESC + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value + Remote SQL: SELECT node_id, metric_name, metric_value FROM public.local_node_metrics +(8 rows) + +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +ORDER BY metric_value DESC LIMIT 3; + node_id | metric_name | metric_value +---------+--------------+-------------- + 1 | disk_io | 120 + 3 | memory_usage | 91.5 + 2 | disk_io | 89.5 +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, event_count FROM "gv$node_metrics" +ORDER BY event_count ASC LIMIT 5; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Limit + Output: node_id, metric_name, event_count + -> Sort + Output: node_id, metric_name, event_count + Sort Key: "gv$node_metrics".event_count + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, event_count + Remote SQL: SELECT node_id, metric_name, event_count FROM public.local_node_metrics +(8 rows) + +SELECT node_id, metric_name, event_count FROM "gv$node_metrics" +ORDER BY event_count ASC LIMIT 5; + node_id | metric_name | event_count +---------+-------------+------------- + 1 | disk_io | 50 + 3 | disk_io | 60 + 2 | disk_io | 75 + 1 | | 77 + 2 | | 83 +(5 rows) + +-- +-- Subqueries +-- +-- Single parameter: scalar subquery result bound as $1 +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") +ORDER BY node_id, metric_name; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: "gv$node_metrics_1".node_id, "gv$node_metrics_1".metric_name, "gv$node_metrics_1".metric_value + Sort Key: "gv$node_metrics_1".node_id, "gv$node_metrics_1".metric_name + InitPlan 1 (returns $0) + -> Aggregate + Output: avg("gv$node_metrics".metric_value) + -> Foreign Scan on public."gv$node_metrics" + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name, "gv$node_metrics".metric_value, "gv$node_metrics".event_count, "gv$node_metrics".total_bytes, "gv$node_metrics".ratio, "gv$node_metrics".is_healthy, "gv$node_metrics".tag, "gv$node_metrics".status, "gv$node_metrics".created_at, "gv$node_metrics".updated_at + Remote SQL: SELECT metric_value FROM public.local_node_metrics + -> Foreign Scan on public."gv$node_metrics" "gv$node_metrics_1" + Output: "gv$node_metrics_1".node_id, "gv$node_metrics_1".metric_name, "gv$node_metrics_1".metric_value + Remote Filter: ("gv$node_metrics_1".metric_value > $0) + Remote SQL: SELECT node_id, metric_name, metric_value FROM public.local_node_metrics WHERE ((metric_value > $1::double precision)) +(13 rows) + +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") +ORDER BY node_id, metric_name; + node_id | metric_name | metric_value +---------+--------------+-------------- + 1 | disk_io | 120 + 1 | memory_usage | 72.5 + 2 | cpu_usage | 62.5 + 2 | disk_io | 89.5 + 3 | disk_io | 67.5 + 3 | memory_usage | 91.5 +(6 rows) + +-- Two parameters: two scalar subquery results bound as $1 and $2 +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") + AND event_count > (SELECT MIN(event_count) FROM "gv$node_metrics") +ORDER BY node_id, metric_name; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: "gv$node_metrics_2".node_id, "gv$node_metrics_2".metric_name, "gv$node_metrics_2".metric_value + Sort Key: "gv$node_metrics_2".node_id, "gv$node_metrics_2".metric_name + InitPlan 1 (returns $0) + -> Aggregate + Output: avg("gv$node_metrics".metric_value) + -> Foreign Scan on public."gv$node_metrics" + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name, "gv$node_metrics".metric_value, "gv$node_metrics".event_count, "gv$node_metrics".total_bytes, "gv$node_metrics".ratio, "gv$node_metrics".is_healthy, "gv$node_metrics".tag, "gv$node_metrics".status, "gv$node_metrics".created_at, "gv$node_metrics".updated_at + Remote SQL: SELECT metric_value FROM public.local_node_metrics + InitPlan 2 (returns $1) + -> Aggregate + Output: min("gv$node_metrics_1".event_count) + -> Foreign Scan on public."gv$node_metrics" "gv$node_metrics_1" + Output: "gv$node_metrics_1".node_id, "gv$node_metrics_1".metric_name, "gv$node_metrics_1".metric_value, "gv$node_metrics_1".event_count, "gv$node_metrics_1".total_bytes, "gv$node_metrics_1".ratio, "gv$node_metrics_1".is_healthy, "gv$node_metrics_1".tag, "gv$node_metrics_1".status, "gv$node_metrics_1".created_at, "gv$node_metrics_1".updated_at + Remote SQL: SELECT event_count FROM public.local_node_metrics + -> Foreign Scan on public."gv$node_metrics" "gv$node_metrics_2" + Output: "gv$node_metrics_2".node_id, "gv$node_metrics_2".metric_name, "gv$node_metrics_2".metric_value + Remote Filter: (("gv$node_metrics_2".metric_value > $0) AND ("gv$node_metrics_2".event_count > $1)) + Remote SQL: SELECT node_id, metric_name, metric_value FROM public.local_node_metrics WHERE ((metric_value > $1::double precision)) AND ((event_count > $2::integer)) +(19 rows) + +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") + AND event_count > (SELECT MIN(event_count) FROM "gv$node_metrics") +ORDER BY node_id, metric_name; + node_id | metric_name | metric_value +---------+--------------+-------------- + 1 | memory_usage | 72.5 + 2 | cpu_usage | 62.5 + 2 | disk_io | 89.5 + 3 | disk_io | 67.5 + 3 | memory_usage | 91.5 +(5 rows) + +-- NULL parameter: subquery over empty result set returns NULL +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics" + WHERE node_id = 999) +ORDER BY node_id, metric_name; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: "gv$node_metrics_1".node_id, "gv$node_metrics_1".metric_name, "gv$node_metrics_1".metric_value + Sort Key: "gv$node_metrics_1".node_id, "gv$node_metrics_1".metric_name + InitPlan 1 (returns $0) + -> Aggregate + Output: avg("gv$node_metrics".metric_value) + -> Foreign Scan on public."gv$node_metrics" + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name, "gv$node_metrics".metric_value, "gv$node_metrics".event_count, "gv$node_metrics".total_bytes, "gv$node_metrics".ratio, "gv$node_metrics".is_healthy, "gv$node_metrics".tag, "gv$node_metrics".status, "gv$node_metrics".created_at, "gv$node_metrics".updated_at + Remote Filter: ("gv$node_metrics".node_id = 999) + Remote SQL: SELECT metric_value FROM public.local_node_metrics WHERE ((node_id = 999)) + -> Foreign Scan on public."gv$node_metrics" "gv$node_metrics_1" + Output: "gv$node_metrics_1".node_id, "gv$node_metrics_1".metric_name, "gv$node_metrics_1".metric_value + Remote Filter: ("gv$node_metrics_1".metric_value > $0) + Remote SQL: SELECT node_id, metric_name, metric_value FROM public.local_node_metrics WHERE ((metric_value > $1::double precision)) +(14 rows) + +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics" + WHERE node_id = 999) +ORDER BY node_id, metric_name; + node_id | metric_name | metric_value +---------+-------------+-------------- +(0 rows) + +-- Mixed NULL and non-NULL parameters: $1 is a real value, $2 is NULL +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") + AND event_count > (SELECT MIN(event_count) FROM "gv$node_metrics" + WHERE node_id = 999) +ORDER BY node_id, metric_name; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: "gv$node_metrics_2".node_id, "gv$node_metrics_2".metric_name, "gv$node_metrics_2".metric_value + Sort Key: "gv$node_metrics_2".node_id, "gv$node_metrics_2".metric_name + InitPlan 1 (returns $0) + -> Aggregate + Output: avg("gv$node_metrics".metric_value) + -> Foreign Scan on public."gv$node_metrics" + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name, "gv$node_metrics".metric_value, "gv$node_metrics".event_count, "gv$node_metrics".total_bytes, "gv$node_metrics".ratio, "gv$node_metrics".is_healthy, "gv$node_metrics".tag, "gv$node_metrics".status, "gv$node_metrics".created_at, "gv$node_metrics".updated_at + Remote SQL: SELECT metric_value FROM public.local_node_metrics + InitPlan 2 (returns $1) + -> Aggregate + Output: min("gv$node_metrics_1".event_count) + -> Foreign Scan on public."gv$node_metrics" "gv$node_metrics_1" + Output: "gv$node_metrics_1".node_id, "gv$node_metrics_1".metric_name, "gv$node_metrics_1".metric_value, "gv$node_metrics_1".event_count, "gv$node_metrics_1".total_bytes, "gv$node_metrics_1".ratio, "gv$node_metrics_1".is_healthy, "gv$node_metrics_1".tag, "gv$node_metrics_1".status, "gv$node_metrics_1".created_at, "gv$node_metrics_1".updated_at + Remote Filter: ("gv$node_metrics_1".node_id = 999) + Remote SQL: SELECT event_count FROM public.local_node_metrics WHERE ((node_id = 999)) + -> Foreign Scan on public."gv$node_metrics" "gv$node_metrics_2" + Output: "gv$node_metrics_2".node_id, "gv$node_metrics_2".metric_name, "gv$node_metrics_2".metric_value + Remote Filter: (("gv$node_metrics_2".metric_value > $0) AND ("gv$node_metrics_2".event_count > $1)) + Remote SQL: SELECT node_id, metric_name, metric_value FROM public.local_node_metrics WHERE ((metric_value > $1::double precision)) AND ((event_count > $2::integer)) +(20 rows) + +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") + AND event_count > (SELECT MIN(event_count) FROM "gv$node_metrics" + WHERE node_id = 999) +ORDER BY node_id, metric_name; + node_id | metric_name | metric_value +---------+-------------+-------------- +(0 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name +FROM "gv$node_metrics" +WHERE status IN (SELECT DISTINCT status FROM "gv$node_metrics" WHERE status != 'active') +ORDER BY node_id, metric_name; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Sort + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Hash Join + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + Inner Unique: true + Hash Cond: ("gv$node_metrics".status = "gv$node_metrics_1".status) + -> Foreign Scan on public."gv$node_metrics" + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name, "gv$node_metrics".metric_value, "gv$node_metrics".event_count, "gv$node_metrics".total_bytes, "gv$node_metrics".ratio, "gv$node_metrics".is_healthy, "gv$node_metrics".tag, "gv$node_metrics".status, "gv$node_metrics".created_at, "gv$node_metrics".updated_at + Remote SQL: SELECT node_id, metric_name, status FROM public.local_node_metrics + -> Hash + Output: "gv$node_metrics_1".status + -> HashAggregate + Output: "gv$node_metrics_1".status + Group Key: "gv$node_metrics_1".status + -> Foreign Scan on public."gv$node_metrics" "gv$node_metrics_1" + Output: "gv$node_metrics_1".node_id, "gv$node_metrics_1".metric_name, "gv$node_metrics_1".metric_value, "gv$node_metrics_1".event_count, "gv$node_metrics_1".total_bytes, "gv$node_metrics_1".ratio, "gv$node_metrics_1".is_healthy, "gv$node_metrics_1".tag, "gv$node_metrics_1".status, "gv$node_metrics_1".created_at, "gv$node_metrics_1".updated_at + Filter: ("gv$node_metrics_1".status <> 'active'::metric_status) + Remote SQL: SELECT status FROM public.local_node_metrics +(19 rows) + +SELECT node_id, metric_name +FROM "gv$node_metrics" +WHERE status IN (SELECT DISTINCT status FROM "gv$node_metrics" WHERE status != 'active') +ORDER BY node_id, metric_name; + node_id | metric_name +---------+-------------- + 1 | memory_usage + 2 | disk_io + 3 | memory_usage +(3 rows) + +-- Empty string subquery parameter +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, tag +FROM "gv$node_metrics" +WHERE metric_name = (SELECT ''::varchar) +ORDER BY node_id, metric_name; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name, "gv$node_metrics".tag + Sort Key: "gv$node_metrics".node_id + InitPlan 1 (returns $0) + -> Result *RESULT* + Output: ''::character varying + -> Foreign Scan on public."gv$node_metrics" + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name, "gv$node_metrics".tag + Remote Filter: (("gv$node_metrics".metric_name)::text = ($0)::text) + Remote SQL: SELECT node_id, metric_name, tag FROM public.local_node_metrics WHERE ((metric_name = $1::character varying)) +(10 rows) + +SELECT node_id, metric_name, tag +FROM "gv$node_metrics" +WHERE metric_name = (SELECT ''::varchar) +ORDER BY node_id, metric_name; + node_id | metric_name | tag +---------+-------------+------- + 1 | | EMPTY +(1 row) + +-- NULL subquery parameter +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, tag +FROM "gv$node_metrics" +WHERE tag IS NOT DISTINCT FROM (SELECT NULL::char(5)) +ORDER BY node_id, metric_name; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name, "gv$node_metrics".tag + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + InitPlan 1 (returns $0) + -> Result *RESULT* + Output: NULL::character(5) + -> Foreign Scan on public."gv$node_metrics" + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name, "gv$node_metrics".tag + Remote Filter: (NOT ("gv$node_metrics".tag IS DISTINCT FROM $0)) + Remote SQL: SELECT node_id, metric_name, tag FROM public.local_node_metrics WHERE ((NOT (tag IS DISTINCT FROM $1::character(5)))) +(10 rows) + +SELECT node_id, metric_name, tag +FROM "gv$node_metrics" +WHERE tag IS NOT DISTINCT FROM (SELECT NULL::char(5)) +ORDER BY node_id, metric_name; + node_id | metric_name | tag +---------+-------------+----- + 2 | | +(1 row) + +-- +-- JOIN with an inline VALUES list +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ni.node_name, gv.metric_name, gv.event_count +FROM "gv$node_metrics" gv +JOIN (VALUES (1, 'alpha'), (2, 'beta'), (3, 'gamma')) + AS ni(node_id, node_name) ON gv.node_id = ni.node_id +ORDER BY ni.node_name, gv.metric_name; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: "*VALUES*".column2, gv.metric_name, gv.event_count + Sort Key: "*VALUES*".column2, gv.metric_name + -> Hash Join + Output: "*VALUES*".column2, gv.metric_name, gv.event_count + Hash Cond: (gv.node_id = "*VALUES*".column1) + -> Foreign Scan on public."gv$node_metrics" gv + Output: gv.node_id, gv.metric_name, gv.metric_value, gv.event_count, gv.total_bytes, gv.ratio, gv.is_healthy, gv.tag, gv.status, gv.created_at, gv.updated_at + Remote SQL: SELECT node_id, metric_name, event_count FROM public.local_node_metrics + -> Hash + Output: "*VALUES*".column2, "*VALUES*".column1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column2, "*VALUES*".column1 +(13 rows) + +SELECT ni.node_name, gv.metric_name, gv.event_count +FROM "gv$node_metrics" gv +JOIN (VALUES (1, 'alpha'), (2, 'beta'), (3, 'gamma')) + AS ni(node_id, node_name) ON gv.node_id = ni.node_id +ORDER BY ni.node_name, gv.metric_name; + node_name | metric_name | event_count +-----------+--------------+------------- + alpha | | 77 + alpha | cpu_usage | 100 + alpha | disk_io | 50 + alpha | memory_usage | 200 + beta | cpu_usage | 150 + beta | disk_io | 75 + beta | memory_usage | 180 + beta | | 83 + gamma | cpu_usage | 90 + gamma | disk_io | 60 + gamma | memory_usage | 250 +(11 rows) + +-- +-- UNION of filtered results +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, status FROM "gv$node_metrics" WHERE status = 'critical' +UNION ALL +SELECT node_id, metric_name, status FROM "gv$node_metrics" WHERE status = 'warning' +ORDER BY node_id, metric_name; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- + Sort + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name, "gv$node_metrics".status + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Append + -> Foreign Scan on public."gv$node_metrics" + Output: "gv$node_metrics".node_id, "gv$node_metrics".metric_name, "gv$node_metrics".status + Filter: ("gv$node_metrics".status = 'critical'::metric_status) + Remote SQL: SELECT node_id, metric_name, status FROM public.local_node_metrics + -> Foreign Scan on public."gv$node_metrics" "gv$node_metrics_1" + Output: "gv$node_metrics_1".node_id, "gv$node_metrics_1".metric_name, "gv$node_metrics_1".status + Filter: ("gv$node_metrics_1".status = 'warning'::metric_status) + Remote SQL: SELECT node_id, metric_name, status FROM public.local_node_metrics +(12 rows) + +SELECT node_id, metric_name, status FROM "gv$node_metrics" WHERE status = 'critical' +UNION ALL +SELECT node_id, metric_name, status FROM "gv$node_metrics" WHERE status = 'warning' +ORDER BY node_id, metric_name; + node_id | metric_name | status +---------+--------------+---------- + 1 | memory_usage | warning + 2 | disk_io | critical + 3 | memory_usage | critical +(3 rows) + +-- +-- ORDER BY with expressions +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + (metric_value * event_count)::int AS weighted_value +FROM "gv$node_metrics" +ORDER BY weighted_value DESC +LIMIT 5; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------- + Limit + Output: node_id, metric_name, (((metric_value * (event_count)::double precision))::integer) + -> Sort + Output: node_id, metric_name, (((metric_value * (event_count)::double precision))::integer) + Sort Key: ((("gv$node_metrics".metric_value * ("gv$node_metrics".event_count)::double precision))::integer) DESC + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, ((metric_value * (event_count)::double precision))::integer + Remote SQL: SELECT node_id, metric_name, metric_value, event_count FROM public.local_node_metrics +(8 rows) + +SELECT node_id, metric_name, + (metric_value * event_count)::int AS weighted_value +FROM "gv$node_metrics" +ORDER BY weighted_value DESC +LIMIT 5; + node_id | metric_name | weighted_value +---------+--------------+---------------- + 3 | memory_usage | 22875 + 1 | memory_usage | 14500 + 2 | memory_usage | 9990 + 2 | cpu_usage | 9375 + 2 | disk_io | 6712 +(5 rows) + +-- +-- Type-specific queries +-- +-- Bigint arithmetic +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + total_bytes, + (total_bytes / 1000000000) AS total_gb +FROM "gv$node_metrics" +ORDER BY total_bytes DESC +LIMIT 3; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Limit + Output: node_id, metric_name, total_bytes, ((total_bytes / 1000000000)) + -> Sort + Output: node_id, metric_name, total_bytes, ((total_bytes / 1000000000)) + Sort Key: "gv$node_metrics".total_bytes DESC + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, total_bytes, (total_bytes / 1000000000) + Remote SQL: SELECT node_id, metric_name, total_bytes FROM public.local_node_metrics +(8 rows) + +SELECT node_id, metric_name, + total_bytes, + (total_bytes / 1000000000) AS total_gb +FROM "gv$node_metrics" +ORDER BY total_bytes DESC +LIMIT 3; + node_id | metric_name | total_bytes | total_gb +---------+--------------+-------------+---------- + 1 | disk_io | 12000000000 | 12 + 3 | memory_usage | 11000000000 | 11 + 2 | memory_usage | 9800000000 | 9 +(3 rows) + +-- Float8 rounding +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + ratio, + ROUND(ratio::numeric, 2) AS ratio_rounded +FROM "gv$node_metrics" +ORDER BY node_id, metric_name; + QUERY PLAN +--------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, ratio, (round((ratio)::numeric, 2)) + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, ratio, round((ratio)::numeric, 2) + Remote SQL: SELECT node_id, metric_name, ratio FROM public.local_node_metrics +(6 rows) + +SELECT node_id, metric_name, + ratio, + ROUND(ratio::numeric, 2) AS ratio_rounded +FROM "gv$node_metrics" +ORDER BY node_id, metric_name; + node_id | metric_name | ratio | ratio_rounded +---------+--------------+--------+--------------- + 1 | | 0.001 | 0.00 + 1 | cpu_usage | 0.4551 | 0.46 + 1 | disk_io | 0.6001 | 0.60 + 1 | memory_usage | 0.726 | 0.73 + 2 | cpu_usage | 0.6254 | 0.63 + 2 | disk_io | 0.895 | 0.90 + 2 | memory_usage | 0.5558 | 0.56 + 2 | | 0.001 | 0.00 + 3 | cpu_usage | 0.3853 | 0.39 + 3 | disk_io | 0.676 | 0.68 + 3 | memory_usage | 0.9157 | 0.92 +(11 rows) + +-- Boolean logic +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + is_healthy, + NOT is_healthy AS is_unhealthy +FROM "gv$node_metrics" +ORDER BY node_id, metric_name; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, is_healthy, ((NOT is_healthy)) + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, is_healthy, (NOT is_healthy) + Remote SQL: SELECT node_id, metric_name, is_healthy FROM public.local_node_metrics +(6 rows) + +SELECT node_id, metric_name, + is_healthy, + NOT is_healthy AS is_unhealthy +FROM "gv$node_metrics" +ORDER BY node_id, metric_name; + node_id | metric_name | is_healthy | is_unhealthy +---------+--------------+------------+-------------- + 1 | | t | f + 1 | cpu_usage | t | f + 1 | disk_io | t | f + 1 | memory_usage | f | t + 2 | cpu_usage | t | f + 2 | disk_io | f | t + 2 | memory_usage | t | f + 2 | | t | f + 3 | cpu_usage | t | f + 3 | disk_io | t | f + 3 | memory_usage | f | t +(11 rows) + +-- Char padding +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, tag, + LENGTH(tag) AS tag_len +FROM "gv$node_metrics" +ORDER BY tag; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Output: node_id, tag, (length(tag)) + Sort Key: "gv$node_metrics".tag + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, tag, length(tag) + Remote SQL: SELECT node_id, tag FROM public.local_node_metrics +(6 rows) + +SELECT node_id, tag, + LENGTH(tag) AS tag_len +FROM "gv$node_metrics" +ORDER BY tag; + node_id | tag | tag_len +---------+-------+--------- + 1 | CPU01 | 5 + 2 | CPU02 | 5 + 3 | CPU03 | 5 + 1 | DSK01 | 5 + 2 | DSK02 | 5 + 3 | DSK03 | 5 + 1 | EMPTY | 5 + 1 | MEM01 | 5 + 2 | MEM02 | 5 + 3 | MEM03 | 5 + 2 | | +(11 rows) + +-- Timestamp extract +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + EXTRACT(MONTH FROM created_at) AS month, + EXTRACT(YEAR FROM created_at) AS year +FROM "gv$node_metrics" +ORDER BY created_at +LIMIT 3; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Limit + Output: node_id, metric_name, (EXTRACT(month FROM created_at)), (EXTRACT(year FROM created_at)), created_at + -> Sort + Output: node_id, metric_name, (EXTRACT(month FROM created_at)), (EXTRACT(year FROM created_at)), created_at + Sort Key: "gv$node_metrics".created_at + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, EXTRACT(month FROM created_at), EXTRACT(year FROM created_at), created_at + Remote SQL: SELECT node_id, metric_name, created_at FROM public.local_node_metrics +(8 rows) + +SELECT node_id, metric_name, + EXTRACT(MONTH FROM created_at) AS month, + EXTRACT(YEAR FROM created_at) AS year +FROM "gv$node_metrics" +ORDER BY created_at +LIMIT 3; + node_id | metric_name | month | year +---------+--------------+-------+------ + 1 | cpu_usage | 1 | 2025 + 1 | memory_usage | 1 | 2025 + 1 | disk_io | 1 | 2025 +(3 rows) + +-- Timestamptz arithmetic +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + updated_at, + updated_at + interval '1 hour' AS updated_at_plus_1h +FROM "gv$node_metrics" +ORDER BY updated_at +LIMIT 3; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Limit + Output: node_id, metric_name, updated_at, ((updated_at + '@ 1 hour'::interval)) + -> Sort + Output: node_id, metric_name, updated_at, ((updated_at + '@ 1 hour'::interval)) + Sort Key: "gv$node_metrics".updated_at + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, updated_at, (updated_at + '@ 1 hour'::interval) + Remote SQL: SELECT node_id, metric_name, updated_at FROM public.local_node_metrics +(8 rows) + +SELECT node_id, metric_name, + updated_at, + updated_at + interval '1 hour' AS updated_at_plus_1h +FROM "gv$node_metrics" +ORDER BY updated_at +LIMIT 3; + node_id | metric_name | updated_at | updated_at_plus_1h +---------+--------------+------------------------------+------------------------------ + 1 | memory_usage | Tue Jan 14 21:30:00 2025 PST | Tue Jan 14 22:30:00 2025 PST + 1 | cpu_usage | Wed Jan 15 02:30:00 2025 PST | Wed Jan 15 03:30:00 2025 PST + 1 | | Wed Jan 15 04:00:00 2025 PST | Wed Jan 15 05:00:00 2025 PST +(3 rows) + +-- +-- Function pushdown behavior +-- +-- Stable function (NOW()) in predicate: not pushed down because +-- contain_mutable_functions() rejects non-immutable expressions. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, updated_at FROM "gv$node_metrics" +WHERE updated_at < NOW() +ORDER BY node_id, metric_name; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, updated_at + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, updated_at + Filter: ("gv$node_metrics".updated_at < now()) + Remote SQL: SELECT node_id, metric_name, updated_at FROM public.local_node_metrics +(7 rows) + +SELECT node_id, metric_name, updated_at FROM "gv$node_metrics" +WHERE updated_at < NOW() +ORDER BY node_id, metric_name; + node_id | metric_name | updated_at +---------+--------------+------------------------------ + 1 | | Wed Jan 15 04:00:00 2025 PST + 1 | cpu_usage | Wed Jan 15 02:30:00 2025 PST + 1 | disk_io | Wed Jan 15 08:30:00 2025 PST + 1 | memory_usage | Tue Jan 14 21:30:00 2025 PST + 2 | cpu_usage | Thu Feb 20 06:00:00 2025 PST + 2 | disk_io | Thu Feb 20 15:00:00 2025 PST + 2 | memory_usage | Wed Feb 19 21:30:00 2025 PST + 2 | | Thu Feb 20 08:00:00 2025 PST + 3 | cpu_usage | Mon Mar 10 02:00:00 2025 PDT + 3 | disk_io | Mon Mar 10 08:00:00 2025 PDT + 3 | memory_usage | Sun Mar 09 21:00:00 2025 PDT +(11 rows) + +-- Immutable built-in function (ABS) in predicate: pushed down because +-- the function is built-in and immutable. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, event_count FROM "gv$node_metrics" +WHERE ABS(event_count) > 100 +ORDER BY node_id, metric_name; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------ + Sort + Output: node_id, metric_name, event_count + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, event_count + Remote Filter: (abs("gv$node_metrics".event_count) > 100) + Remote SQL: SELECT node_id, metric_name, event_count FROM public.local_node_metrics WHERE ((abs(event_count) > 100)) +(7 rows) + +SELECT node_id, metric_name, event_count FROM "gv$node_metrics" +WHERE ABS(event_count) > 100 +ORDER BY node_id, metric_name; + node_id | metric_name | event_count +---------+--------------+------------- + 1 | memory_usage | 200 + 2 | cpu_usage | 150 + 2 | memory_usage | 180 + 3 | memory_usage | 250 +(4 rows) + +-- User-defined function in predicate: not pushed down because +-- postgres_fdw only considers built-in functions shippable. +CREATE FUNCTION is_high_value(val real) RETURNS boolean + LANGUAGE plpgsql IMMUTABLE AS $$ +BEGIN + RETURN val > 70::real; +END; +$$; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +WHERE is_high_value(metric_value) +ORDER BY node_id, metric_name; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Sort + Output: node_id, metric_name, metric_value + Sort Key: "gv$node_metrics".node_id, "gv$node_metrics".metric_name + -> Foreign Scan on public."gv$node_metrics" + Output: node_id, metric_name, metric_value + Filter: is_high_value("gv$node_metrics".metric_value) + Remote SQL: SELECT node_id, metric_name, metric_value FROM public.local_node_metrics +(7 rows) + +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +WHERE is_high_value(metric_value) +ORDER BY node_id, metric_name; + node_id | metric_name | metric_value +---------+--------------+-------------- + 1 | disk_io | 120 + 1 | memory_usage | 72.5 + 2 | disk_io | 89.5 + 3 | memory_usage | 91.5 +(4 rows) + +-- +-- Cleanup +-- +DROP FOREIGN TABLE "gv$node_metrics"; +DROP FOREIGN TABLE local_node_metrics; +DROP SERVER gv_server CASCADE; +DROP SERVER file_server CASCADE; +DROP EXTENSION postgres_fdw; +DROP EXTENSION file_fdw; +DROP TYPE metric_status; +DROP FUNCTION is_high_value(real); diff --git a/contrib/postgres_fdw/expected/yb.orig.postgres_fdw.out b/contrib/postgres_fdw/expected/yb.orig.postgres_fdw.out new file mode 100644 index 0000000000000..3c4a4b90c414b --- /dev/null +++ b/contrib/postgres_fdw/expected/yb.orig.postgres_fdw.out @@ -0,0 +1,667 @@ +CREATE EXTENSION postgres_fdw; +-- +-- Test to validate behavior of 'server_type' option in foreign server. +-- +CREATE SERVER s_yugabytedb FOREIGN DATA WRAPPER postgres_fdw OPTIONS (server_type 'yugabytedb'); +CREATE SERVER s_postgres FOREIGN DATA WRAPPER postgres_fdw OPTIONS (server_type 'postgresql'); +CREATE SERVER s_yugabytedb2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname 'yugabyte', server_type 'yugabytedb'); +CREATE SERVER s_nosrvtype FOREIGN DATA WRAPPER postgres_fdw; +NOTICE: no server_type specified. Defaulting to PostgreSQL. +HINT: Use "ALTER SERVER ... OPTIONS (ADD server_type '')" to explicitly set server_type. +CREATE SERVER s_nosrvtype2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname 'yugabyte', host '127.0.0.1'); +NOTICE: no server_type specified. Defaulting to PostgreSQL. +HINT: Use "ALTER SERVER ... OPTIONS (ADD server_type '')" to explicitly set server_type. +CREATE SERVER s_invalidsrvtype FOREIGN DATA WRAPPER postgres_fdw OPTIONS (server_type 'something_invalid'); +ERROR: invalid server_type 'something_invalid' +HINT: Supported server types: [postgreSQL, yugabyteDB] +CREATE SERVER s_invalidsrvtype2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (server_type ''); +ERROR: invalid server_type '' +HINT: Supported server types: [postgreSQL, yugabyteDB] +SELECT srv.srvname, srv.srvoptions +FROM pg_foreign_server srv JOIN pg_foreign_data_wrapper fdw ON srv.srvfdw = fdw.oid +WHERE fdw.fdwname = 'postgres_fdw'; + srvname | srvoptions +---------------+------------------------------------------ + s_yugabytedb | {server_type=yugabytedb} + s_postgres | {server_type=postgresql} + s_yugabytedb2 | {dbname=yugabyte,server_type=yugabytedb} + s_nosrvtype | + s_nosrvtype2 | {dbname=yugabyte,host=127.0.0.1} +(5 rows) + +-- Adding a 'server_type' option where none exists. +ALTER SERVER s_nosrvtype2 OPTIONS (ADD server_type 'yugabytedb'); +-- Adding a 'server_type' option where one already exists should be disallowed. +ALTER SERVER s_nosrvtype2 OPTIONS (ADD server_type 'postgresql'); +ERROR: option "server_type" provided more than once +-- Modifying 'server_type' option where one already exists should be disallowed. +ALTER SERVER s_nosrvtype2 OPTIONS (SET server_type 'postgresql'); +ERROR: changing server_type is not supported. +HINT: Use "DROP SERVER " followed by "CREATE SERVER ... OPTIONS (server_type 'postgresql')" to change server_type. +-- Modifying 'server_type' option where one already exists but with an invalid value should be disallowed. +ALTER SERVER s_nosrvtype2 OPTIONS (SET server_type 'something_invalid'); +ERROR: invalid server_type 'something_invalid' +HINT: Supported server types: [postgreSQL, yugabyteDB] +-- Dropping 'server_type' option should be disallowed. +ALTER SERVER s_nosrvtype2 OPTIONS (DROP server_type); +ERROR: dropping server_type is not supported. +-- Modifying or dropping 'server_type' option where one does not exist should be disallowed. +ALTER SERVER s_nosrvtype OPTIONS (SET server_type 'postgresql'); +ERROR: option "server_type" not found +ALTER SERVER s_nosrvtype OPTIONS (DROP server_type); +ERROR: option "server_type" not found +SELECT srv.srvname, srv.srvoptions +FROM pg_foreign_server srv JOIN pg_foreign_data_wrapper fdw ON srv.srvfdw = fdw.oid +WHERE fdw.fdwname = 'postgres_fdw'; + srvname | srvoptions +---------------+--------------------------------------------------------- + s_yugabytedb | {server_type=yugabytedb} + s_postgres | {server_type=postgresql} + s_yugabytedb2 | {dbname=yugabyte,server_type=yugabytedb} + s_nosrvtype | + s_nosrvtype2 | {dbname=yugabyte,host=127.0.0.1,server_type=yugabytedb} +(5 rows) + +DROP SERVER s_yugabytedb2; +DROP SERVER s_nosrvtype2; +-- Test to validate that 'server_type' option is not applicable to foreign table. +CREATE FOREIGN TABLE ft_test (v INT) SERVER s_yugabytedb OPTIONS (table_name 't_test', server_type 'yugabytedb'); +ERROR: invalid option "server_type" +HINT: Valid options in this context are: schema_name, table_name, use_remote_estimate, updatable, truncatable, fetch_size, batch_size, async_capable +CREATE TABLE t_test (v INT); +INSERT INTO t_test (SELECT i FROM generate_series(1, 10) AS i); +-- Sanity test to assert that the server has indeed been configured. +DO $d$ + BEGIN + EXECUTE $$ALTER SERVER s_yugabytedb + OPTIONS (dbname '$$||current_database()||$$', + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$ALTER SERVER s_postgres + OPTIONS (dbname '$$||current_database()||$$', + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$ALTER SERVER s_nosrvtype + OPTIONS (dbname '$$||current_database()||$$', + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; +NOTICE: no server_type specified. Defaulting to PostgreSQL. +HINT: Use "ALTER SERVER ... OPTIONS (ADD server_type '')" to explicitly set server_type. +CREATE USER MAPPING FOR CURRENT_USER SERVER s_yugabytedb; +CREATE USER MAPPING FOR CURRENT_USER SERVER s_postgres; +CREATE USER MAPPING FOR CURRENT_USER SERVER s_nosrvtype; +CREATE FOREIGN TABLE ft_test1 (v INT) SERVER s_yugabytedb OPTIONS (table_name 't_test'); +SELECT * FROM ft_test1 ORDER BY v; + v +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +CREATE FOREIGN TABLE ft_test2 (v INT) SERVER s_postgres OPTIONS (table_name 't_test'); +SELECT * FROM ft_test2 ORDER BY v; + v +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +CREATE FOREIGN TABLE ft_test3 (v INT) SERVER s_nosrvtype OPTIONS (table_name 't_test'); +SELECT * FROM ft_test3 ORDER BY v; + v +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +-- A join involving tables of different server types must not be pushed down. +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_test1, ft_test2, ft_test3 WHERE ft_test1.v = ft_test2.v AND ft_test1.v = ft_test3.v; + QUERY PLAN +------------------------------------------------------------------- + Merge Join + Output: ft_test1.v, ft_test2.v, ft_test3.v + Merge Cond: (ft_test3.v = ft_test1.v) + -> Sort + Output: ft_test3.v + Sort Key: ft_test3.v + -> Foreign Scan on public.ft_test3 + Output: ft_test3.v + Remote SQL: SELECT v FROM public.t_test + -> Materialize + Output: ft_test1.v, ft_test2.v + -> Merge Join + Output: ft_test1.v, ft_test2.v + Merge Cond: (ft_test1.v = ft_test2.v) + -> Sort + Output: ft_test1.v + Sort Key: ft_test1.v + -> Foreign Scan on public.ft_test1 + Output: ft_test1.v + Remote SQL: SELECT v FROM public.t_test + -> Sort + Output: ft_test2.v + Sort Key: ft_test2.v + -> Foreign Scan on public.ft_test2 + Output: ft_test2.v + Remote SQL: SELECT v FROM public.t_test +(26 rows) + +SELECT * FROM ft_test1, ft_test2, ft_test3 WHERE ft_test1.v = ft_test2.v AND ft_test1.v = ft_test3.v; + v | v | v +----+----+---- + 1 | 1 | 1 + 2 | 2 | 2 + 3 | 3 | 3 + 4 | 4 | 4 + 5 | 5 | 5 + 6 | 6 | 6 + 7 | 7 | 7 + 8 | 8 | 8 + 9 | 9 | 9 + 10 | 10 | 10 +(10 rows) + +-- Modifying table level options should be allowed. +ALTER FOREIGN TABLE ft_test1 OPTIONS (SET table_name 't_othertest'); +ALTER FOREIGN TABLE ft_test2 OPTIONS (DROP table_name, ADD batch_size '1000'); +SELECT * FROM pg_foreign_table; + ftrelid | ftserver | ftoptions +---------+----------+-------------------------- + 16407 | 16391 | {table_name=t_othertest} + 16410 | 16392 | {batch_size=1000} + 16413 | 16394 | {table_name=t_test} +(3 rows) + +-- Setting an option should be disallowed if it doesn't already exist. +ALTER FOREIGN TABLE ft_test3 OPTIONS (SET batch_size '1000', SET table_name 't_othertest'); +ERROR: option "batch_size" not found +-- Dropping an option should be disallowed if it doesn't already exist. +ALTER FOREIGN TABLE ft_test2 OPTIONS (DROP table_name); +ERROR: option "table_name" not found +-- Adding an option should be disallowed if it already exists. +ALTER FOREIGN TABLE ft_test1 OPTIONS (ADD table_name 't_test'); +ERROR: option "table_name" provided more than once +SELECT * FROM pg_foreign_table; + ftrelid | ftserver | ftoptions +---------+----------+-------------------------- + 16407 | 16391 | {table_name=t_othertest} + 16410 | 16392 | {batch_size=1000} + 16413 | 16394 | {table_name=t_test} +(3 rows) + +-- Restore the original table name. +ALTER FOREIGN TABLE ft_test1 OPTIONS (SET table_name 't_test'); +ALTER FOREIGN TABLE ft_test2 OPTIONS (ADD table_name 't_test', DROP batch_size); +SELECT * FROM pg_foreign_table; + ftrelid | ftserver | ftoptions +---------+----------+--------------------- + 16407 | 16391 | {table_name=t_test} + 16410 | 16392 | {table_name=t_test} + 16413 | 16394 | {table_name=t_test} +(3 rows) + +DROP FOREIGN TABLE ft_test1; +DROP FOREIGN TABLE ft_test2; +DROP FOREIGN TABLE ft_test3; +-- +-- Test to validate behavior of DML queries on foreign tables +-- +CREATE TABLE t_simple(k INT, v INT, PRIMARY KEY (k ASC)); +CREATE TABLE t_other (k INT, v INT, PRIMARY KEY (k ASC)); +CREATE FOREIGN TABLE ft_simple (k INT, v INT) SERVER s_yugabytedb OPTIONS (table_name 't_simple'); +CREATE FOREIGN TABLE ft_other (k INT, v INT) SERVER s_yugabytedb OPTIONS (table_name 't_other'); +-- Test various types of inserts. +-- In all DMLs with a RETURNING clause, the ybctid column must be requested either +-- explicitly or implicitly. +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ft_simple VALUES (1, 1) RETURNING k, v; + QUERY PLAN +---------------------------------------------------------------------------------------- + Insert on public.ft_simple + Output: ft_simple.k, ft_simple.v + Remote SQL: INSERT INTO public.t_simple(k, v) VALUES ($1, $2) RETURNING k, v, ybctid + Batch Size: 1 + -> Result *RESULT* + Output: 1, 1 +(6 rows) + +INSERT INTO ft_simple VALUES (0, 0); +INSERT INTO ft_simple VALUES (1, 1) RETURNING k, v; + k | v +---+--- + 1 | 1 +(1 row) + +INSERT INTO ft_simple VALUES (2, 2) RETURNING *; + k | v +---+--- + 2 | 2 +(1 row) + +INSERT INTO ft_simple (v, k) VALUES (3, 3) RETURNING tableoid::regclass, *; + tableoid | k | v +-----------+---+--- + ft_simple | 3 | 3 +(1 row) + +INSERT INTO ft_simple VALUES (4, 4) RETURNING ybctid, *; + ybctid | k | v +----------------+---+--- + \x488000000421 | 4 | 4 +(1 row) + +SELECT * FROM ft_simple ORDER BY k; + k | v +---+--- + 0 | 0 + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 +(5 rows) + +-- Test "direct" updates and deletes. These are operations that can be pushed down +-- to the foreign server. +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple SET k = k + 10, v = v + 1 WHERE k = 1; + QUERY PLAN +------------------------------------------------------------------------------------------ + Update on public.ft_simple + -> Foreign Update on public.ft_simple + Remote Filter: (ft_simple.k = 1) + Remote SQL: UPDATE public.t_simple SET k = (k + 10), v = (v + 1) WHERE ((k = 1)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple SET v = v + 1 WHERE k < 10 RETURNING k, v; + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Update on public.ft_simple + Output: k, v + -> Foreign Update on public.ft_simple + Remote Filter: (ft_simple.k < 10) + Remote SQL: UPDATE public.t_simple SET v = (v + 1) WHERE ((k < 10)) RETURNING k, v, ybctid +(5 rows) + +UPDATE ft_simple SET v = v + 1 WHERE k = 0; +UPDATE ft_simple SET k = k + 10, v = v + 1 WHERE k = 1 RETURNING k, v; + k | v +----+--- + 11 | 2 +(1 row) + +UPDATE ft_simple SET v = v + 1 WHERE k < 10 RETURNING *; + k | v +---+--- + 0 | 2 + 2 | 3 + 3 | 4 + 4 | 5 +(4 rows) + +UPDATE ft_simple SET k = k + 10 WHERE v = 3 RETURNING ybctid, *; + ybctid | k | v +----------------+----+--- + \x488000000c21 | 12 | 3 +(1 row) + +SELECT * FROM ft_simple ORDER BY k; + k | v +----+--- + 0 | 2 + 3 | 4 + 4 | 5 + 11 | 2 + 12 | 3 +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple WHERE k = 0; + QUERY PLAN +----------------------------------------------------------------- + Delete on public.ft_simple + -> Foreign Delete on public.ft_simple + Remote Filter: (ft_simple.k = 0) + Remote SQL: DELETE FROM public.t_simple WHERE ((k = 0)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple WHERE k < 10 RETURNING k, v; + QUERY PLAN +----------------------------------------------------------------------------------------- + Delete on public.ft_simple + Output: k, v + -> Foreign Delete on public.ft_simple + Remote Filter: (ft_simple.k < 10) + Remote SQL: DELETE FROM public.t_simple WHERE ((k < 10)) RETURNING k, v, ybctid +(5 rows) + +DELETE FROM ft_simple WHERE k = 0; +DELETE FROM ft_simple WHERE k < 10 RETURNING k, v; + k | v +---+--- + 3 | 4 + 4 | 5 +(2 rows) + +DELETE FROM ft_simple WHERE k = 11 RETURNING *; + k | v +----+--- + 11 | 2 +(1 row) + +DELETE FROM ft_simple WHERE v = 3 RETURNING ybctid, *; + ybctid | k | v +----------------+----+--- + \x488000000c21 | 12 | 3 +(1 row) + +SELECT * FROM ft_simple ORDER BY k; + k | v +---+--- +(0 rows) + +-- Test "two-pass" updates and deletes. These are operations that cannot be pushed down +-- to the foreign server, and require a foreign scan to return the ctid (ybctid) of +-- the row being deleted. +-- Create a "local" function. postgres_fdw will not push down function calls +-- because it cannot determine if the same implementation of the function exists +-- on the foreign server. +CREATE FUNCTION fdw(a INT) RETURNS INT +LANGUAGE PLPGSQL AS $$ +BEGIN + RETURN a; +END; +$$; +INSERT INTO t_simple (SELECT i, i FROM generate_series(1, 10) AS i); +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple SET v = fdw(v) WHERE k = 1; + QUERY PLAN +----------------------------------------------------------------------------------------- + Update on public.ft_simple + Remote SQL: UPDATE public.t_simple SET v = $2 WHERE ybctid = $1 + -> Foreign Scan on public.ft_simple + Output: fdw(v), ybctid, ft_simple.* + Remote Filter: (ft_simple.k = 1) + Remote SQL: SELECT k, v, ybctid FROM public.t_simple WHERE ((k = 1)) FOR UPDATE +(6 rows) + +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple SET v = v + 1 WHERE fdw(k) = 1 RETURNING *; + QUERY PLAN +------------------------------------------------------------------------------------------ + Update on public.ft_simple + Output: k, v + Remote SQL: UPDATE public.t_simple SET v = $2 WHERE ybctid = $1 RETURNING k, v, ybctid + -> Foreign Scan on public.ft_simple + Output: (v + 1), ybctid, ft_simple.* + Filter: (fdw(ft_simple.k) = 1) + Remote SQL: SELECT k, v, ybctid FROM public.t_simple FOR UPDATE +(7 rows) + +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple WHERE fdw(k) = 1 RETURNING *; + QUERY PLAN +------------------------------------------------------------------------------------ + Delete on public.ft_simple + Output: k, v + Remote SQL: DELETE FROM public.t_simple WHERE ybctid = $1 RETURNING k, v, ybctid + -> Foreign Scan on public.ft_simple + Output: ybctid + Filter: (fdw(ft_simple.k) = 1) + Remote SQL: SELECT k, ybctid FROM public.t_simple FOR UPDATE +(7 rows) + +-- This query should be capable of push down. +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple WHERE k = 1 RETURNING fdw(k), fdw(v); + QUERY PLAN +---------------------------------------------------------------------------------------- + Delete on public.ft_simple + Output: fdw(k), fdw(v) + -> Foreign Delete on public.ft_simple + Remote Filter: (ft_simple.k = 1) + Remote SQL: DELETE FROM public.t_simple WHERE ((k = 1)) RETURNING k, v, ybctid +(5 rows) + +UPDATE ft_simple SET v = v + 1 WHERE fdw(k) = 1; +UPDATE ft_simple SET k = fdw(k) + 10, v = fdw(v) + 1 WHERE k = 2 RETURNING k, v; + k | v +----+--- + 12 | 3 +(1 row) + +UPDATE ft_simple SET v = fdw(v) + 1 WHERE fdw(k) < 10 RETURNING *; + k | v +---+---- + 1 | 3 + 3 | 4 + 4 | 5 + 5 | 6 + 6 | 7 + 7 | 8 + 8 | 9 + 9 | 10 +(8 rows) + +SELECT * FROM ft_simple ORDER BY k; + k | v +----+---- + 1 | 3 + 3 | 4 + 4 | 5 + 5 | 6 + 6 | 7 + 7 | 8 + 8 | 9 + 9 | 10 + 10 | 10 + 12 | 3 +(10 rows) + +DELETE FROM ft_simple WHERE fdw(k) = 1; +DELETE FROM ft_simple WHERE fdw(k) = 12 RETURNING k, v; + k | v +----+--- + 12 | 3 +(1 row) + +DELETE FROM ft_simple WHERE fdw(v) < 10 RETURNING *; + k | v +---+--- + 3 | 4 + 4 | 5 + 5 | 6 + 6 | 7 + 7 | 8 + 8 | 9 +(6 rows) + +SELECT * FROM ft_simple ORDER BY k; + k | v +----+---- + 9 | 10 + 10 | 10 +(2 rows) + +INSERT INTO t_simple (SELECT i, i FROM generate_series(1, 5) AS i); +INSERT INTO t_other (SELECT i, i FROM generate_series(1, 5) AS i); +-- Updates and deletes involving multiple foreign tables cannot be pushed down +-- when the conditions are non-trivial. +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple s SET v = s.v + 1 +FROM ft_other t +WHERE s.k < 2 AND t.v = s.v; + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Update on public.ft_simple s + Remote SQL: UPDATE public.t_simple SET v = $2 WHERE ybctid = $1 + -> Hash Join + Output: (s.v + 1), s.ybctid, s.*, t.* + Hash Cond: (t.v = s.v) + -> Foreign Scan on public.ft_other t + Output: t.*, t.v + Remote SQL: SELECT k, v FROM public.t_other + -> Hash + Output: s.v, s.ybctid, s.* + -> Foreign Scan on public.ft_simple s + Output: s.v, s.ybctid, s.* + Remote Filter: (s.k < 2) + Remote SQL: SELECT k, v, ybctid FROM public.t_simple WHERE ((k < 2)) FOR UPDATE +(14 rows) + +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple s SET v = s.v + 1 +FROM ft_other t1 JOIN ft_other t2 ON t1.k = t2.k +WHERE s.k = 2 AND t1.v = s.v; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft_simple s + -> Foreign Update + Remote SQL: UPDATE public.t_simple r1 SET v = (r1.v + 1) FROM (public.t_other r2 INNER JOIN public.t_other r3 ON (TRUE)) WHERE ((r2.k = r3.k)) AND ((r1.v = r2.v)) AND ((r1.k = 2)) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple s +USING ft_other t +WHERE s.k < 2 AND t.v = s.v; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Delete on public.ft_simple s + Remote SQL: DELETE FROM public.t_simple WHERE ybctid = $1 + -> Hash Join + Output: s.ybctid, t.* + Hash Cond: (t.v = s.v) + -> Foreign Scan on public.ft_other t + Output: t.*, t.v + Remote SQL: SELECT k, v FROM public.t_other + -> Hash + Output: s.ybctid, s.v + -> Foreign Scan on public.ft_simple s + Output: s.ybctid, s.v + Remote Filter: (s.k < 2) + Remote SQL: SELECT v, ybctid FROM public.t_simple WHERE ((k < 2)) FOR UPDATE +(14 rows) + +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple s +USING ft_other t1 JOIN ft_other t2 ON t1.k = t2.k +WHERE s.k = 2 AND t1.v = s.v; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Delete on public.ft_simple s + Remote SQL: DELETE FROM public.t_simple WHERE ybctid = $1 + -> Hash Join + Output: s.ybctid, t1.*, t2.* + Hash Cond: (t2.k = t1.k) + -> Foreign Scan on public.ft_other t2 + Output: t2.*, t2.k + Remote SQL: SELECT k, v FROM public.t_other + -> Hash + Output: s.ybctid, t1.*, t1.k + -> Foreign Scan + Output: s.ybctid, t1.*, t1.k + Relations: (public.ft_simple s) INNER JOIN (public.ft_other t1) + Remote SQL: SELECT r1.ybctid, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.k, r2.v) END, r2.k FROM (public.t_simple r1 INNER JOIN public.t_other r2 ON (((r1.v = r2.v)) AND ((r1.k = 2)))) FOR UPDATE OF r1 + -> Hash Join + Output: s.ybctid, t1.*, t1.k + Hash Cond: (s.v = t1.v) + -> Foreign Scan on public.ft_simple s + Output: s.ybctid, s.v + Remote Filter: (s.k = 2) + Remote SQL: SELECT v, ybctid FROM public.t_simple WHERE ((k = 2)) FOR UPDATE + -> Hash + Output: t1.*, t1.k, t1.v + -> Foreign Scan on public.ft_other t1 + Output: t1.*, t1.k, t1.v + Remote SQL: SELECT k, v FROM public.t_other +(26 rows) + +UPDATE ft_simple s SET v = s.v + 1 +FROM ft_other t +WHERE s.k < 2 AND t.v = s.v +RETURNING *; + k | v | k | v +---+---+---+--- + 1 | 2 | 1 | 1 +(1 row) + +UPDATE ft_simple s SET v = s.v + 1 +FROM ft_other t1 JOIN ft_other t2 ON t1.k = t2.k +WHERE s.k = 2 AND t1.v = s.v +RETURNING *; + k | v | k | v | k | v +---+---+---+---+---+--- + 2 | 3 | 2 | 2 | 2 | 2 +(1 row) + +DELETE FROM ft_simple s +USING ft_other t +WHERE s.k < 2 AND t.v = s.v +RETURNING *; + k | v | k | v +---+---+---+--- + 1 | 2 | 2 | 2 +(1 row) + +DELETE FROM ft_simple s +USING ft_other t1 JOIN ft_other t2 ON t1.k = t2.k +WHERE s.k = 2 AND t1.v = s.v +RETURNING *; + k | v | k | v | k | v +---+---+---+---+---+--- + 2 | 3 | 3 | 3 | 3 | 3 +(1 row) + +SELECT * FROM ft_simple ORDER BY k; + k | v +----+---- + 3 | 3 + 4 | 4 + 5 | 5 + 9 | 10 + 10 | 10 +(5 rows) + +CREATE SERVER IF NOT EXISTS gv_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (server_type 'federatedYugabyteDB'); +CREATE FOREIGN TABLE IF NOT EXISTS "gv$yb_active_session_history" ( + sample_time TIMESTAMPTZ, + root_request_id UUID, + rpc_request_id BIGINT, + wait_event_component TEXT, + wait_event_class TEXT, + wait_event TEXT, + top_level_node_id UUID, + query_id BIGINT, + pid INT, + client_node_ip TEXT, + wait_event_aux TEXT, + sample_weight REAL, + wait_event_type TEXT, + ysql_dbid OID, + wait_event_code BIGINT, + pss_mem_bytes BIGINT, + ysql_userid OID, + plan_id BIGINT +) +SERVER gv_server +OPTIONS (schema_name 'pg_catalog', table_name 'yb_active_session_history'); +SELECT * FROM gv$yb_active_session_history; +ERROR: federatedYB queries are not supported +HINT: Must enable the GUC yb_enable_global_views diff --git a/contrib/postgres_fdw/expected/yb.port.postgres_fdw.out b/contrib/postgres_fdw/expected/yb.port.postgres_fdw.out new file mode 100644 index 0000000000000..9a86bf9a93eaa --- /dev/null +++ b/contrib/postgres_fdw/expected/yb.port.postgres_fdw.out @@ -0,0 +1,11827 @@ +-- =================================================================== +-- create FDW objects +-- =================================================================== +CREATE EXTENSION postgres_fdw; +CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw; +NOTICE: no server_type specified. Defaulting to PostgreSQL. +HINT: Use "ALTER SERVER ... OPTIONS (ADD server_type '')" to explicitly set server_type. +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + -- YB note: In YugabyteDB regress tests, the host and port vary on a run-to-run basis. + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$', + -- YB note: 'server_type' is a YB-specific option which defaults to 'postgresql'. + server_type 'yugabytedb' + )$$; + EXECUTE $$CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + -- YB note: In YugabyteDB regress tests, the host and port vary on a run-to-run basis. + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$', + -- YB note: 'server_type' is a YB-specific option which defaults to 'postgresql'. + server_type 'yugabytedb' + )$$; + EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + -- YB note: In YugabyteDB regress tests, the host and port vary on a run-to-run basis. + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$', + -- YB note: 'server_type' is a YB-specific option which defaults to 'postgresql'. + server_type 'yugabytedb' + )$$; + END; +$d$; +CREATE USER MAPPING FOR public SERVER testserver1 + OPTIONS (user 'value', password 'value'); +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2; +CREATE USER MAPPING FOR public SERVER loopback3; +-- =================================================================== +-- create objects used through FDW loopback server +-- =================================================================== +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); +CREATE SCHEMA "S 1"; +CREATE TABLE "S 1"."T 1" ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10), + c8 user_enum, + CONSTRAINT t1_pkey PRIMARY KEY ("C 1") +); +CREATE TABLE "S 1"."T 2" ( + c1 int NOT NULL, + c2 text, + CONSTRAINT t2_pkey PRIMARY KEY (c1) +); +CREATE TABLE "S 1"."T 3" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t3_pkey PRIMARY KEY (c1) +); +CREATE TABLE "S 1"."T 4" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t4_pkey PRIMARY KEY (c1) +); +-- Disable autovacuum for these tables to avoid unexpected effects of that +ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +INSERT INTO "S 1"."T 1" + SELECT id, + id % 10, + to_char(id, 'FM00000'), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, + '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, + id % 10, + id % 10, + 'foo'::user_enum + FROM generate_series(1, 1000) id; +INSERT INTO "S 1"."T 2" + SELECT id, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +INSERT INTO "S 1"."T 3" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 3" WHERE c1 % 2 != 0; -- delete for outer join tests +INSERT INTO "S 1"."T 4" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests +ANALYZE "S 1"."T 1"; +ANALYZE "S 1"."T 2"; +ANALYZE "S 1"."T 3"; +ANALYZE "S 1"."T 4"; +-- =================================================================== +-- create foreign tables +-- =================================================================== +CREATE FOREIGN TABLE ft1 ( + c0 int, + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft1 DROP COLUMN c0; +CREATE FOREIGN TABLE ft2 ( + c1 int NOT NULL, + c2 int NOT NULL, + cx int, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft2', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft2 DROP COLUMN cx; +CREATE FOREIGN TABLE ft4 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3'); +CREATE FOREIGN TABLE ft5 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); +CREATE FOREIGN TABLE ft6 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4'); +CREATE FOREIGN TABLE ft7 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4'); +-- =================================================================== +-- tests for validator +-- =================================================================== +-- requiressl and some other parameters are omitted because +-- valid values for them depend on configure options +ALTER SERVER testserver1 OPTIONS ( + use_remote_estimate 'false', + updatable 'true', + fdw_startup_cost '123.456', + fdw_tuple_cost '0.123', + service 'value', + connect_timeout 'value', + dbname 'value', + host 'value', + hostaddr 'value', + port 'value', + --client_encoding 'value', + application_name 'value', + --fallback_application_name 'value', + keepalives 'value', + keepalives_idle 'value', + keepalives_interval 'value', + tcp_user_timeout 'value', + -- requiressl 'value', + sslcompression 'value', + sslmode 'value', + sslcert 'value', + sslkey 'value', + sslrootcert 'value', + sslcrl 'value', + --requirepeer 'value', + krbsrvname 'value', + gsslib 'value' + --replication 'value' +); +NOTICE: no server_type specified. Defaulting to PostgreSQL. +HINT: Use "ALTER SERVER ... OPTIONS (ADD server_type '')" to explicitly set server_type. +-- Error, invalid list syntax +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo; bar'); +ERROR: parameter "extensions" must be a list of extension names +-- OK but gets a warning +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo, bar'); +WARNING: extension "foo" is not installed +WARNING: extension "bar" is not installed +NOTICE: no server_type specified. Defaulting to PostgreSQL. +HINT: Use "ALTER SERVER ... OPTIONS (ADD server_type '')" to explicitly set server_type. +ALTER SERVER testserver1 OPTIONS (DROP extensions); +NOTICE: no server_type specified. Defaulting to PostgreSQL. +HINT: Use "ALTER SERVER ... OPTIONS (ADD server_type '')" to explicitly set server_type. +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (DROP user, DROP password); +-- Attempt to add a valid option that's not allowed in a user mapping +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslmode 'require'); +ERROR: invalid option "sslmode" +HINT: Valid options in this context are: user, password, sslpassword, password_required, sslcert, sslkey +-- But we can add valid ones fine +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslpassword 'dummy'); +-- Ensure valid options we haven't used in a user mapping yet are +-- permitted to check validation. +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslkey 'value', ADD sslcert 'value'); +ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +\det+ + List of foreign tables + Schema | Table | Server | FDW options | Description +--------+-------+-----------+---------------------------------------+------------- + public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') | + public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') | + public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') | + public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') | + public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') | + public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') | +(6 rows) + +-- Test that alteration of server options causes reconnection +-- Remote's errors might be non-English, so hide them to ensure stable results +\set VERBOSITY terse +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work + c3 | c4 +-------+------------------------------ + 00001 | Fri Jan 02 00:00:00 1970 PST +(1 row) + +ALTER SERVER loopback OPTIONS (SET dbname 'no such database'); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail +ERROR: could not connect to server "loopback" +DO $d$ + BEGIN + EXECUTE $$ALTER SERVER loopback + OPTIONS (SET dbname '$$||current_database()||$$')$$; + END; +$d$; +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again + c3 | c4 +-------+------------------------------ + 00001 | Fri Jan 02 00:00:00 1970 PST +(1 row) + +-- Test that alteration of user mapping options causes reconnection +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback + OPTIONS (ADD user 'no such user'); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail +ERROR: could not connect to server "loopback" +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback + OPTIONS (DROP user); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again + c3 | c4 +-------+------------------------------ + 00001 | Fri Jan 02 00:00:00 1970 PST +(1 row) + +\set VERBOSITY default +-- Now we should be able to run ANALYZE. +-- To exercise multiple code paths, we use local stats on ft1 +-- and remote-estimate mode on ft2. +ANALYZE ft1; +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); +-- =================================================================== +-- test error case for create publication on foreign table +-- =================================================================== +CREATE PUBLICATION testpub_ftbl FOR TABLE ft1; -- should fail +ERROR: cannot add relation "ft1" to publication +DETAIL: This operation is not supported for foreign tables. +-- =================================================================== +-- simple queries +-- =================================================================== +-- single table without alias +EXPLAIN (COSTS OFF) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------- + Foreign Scan on ft1 +(1 row) + +SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 103 | 3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 107 | 7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 109 | 9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo + 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- single table with alias - also test that tableoid sort is not pushed to remote side +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------- + Limit + Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid + -> Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid + Sort Key: t1.c3, t1.c1, t1.tableoid + -> Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(8 rows) + +SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 103 | 3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 107 | 7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 109 | 9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo + 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- whole-row reference +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: t1.*, c3, c1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint +(3 rows) + +SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + t1 +-------------------------------------------------------------------------------------------- + (101,1,00101,"Fri Jan 02 00:00:00 1970 PST","Fri Jan 02 00:00:00 1970",1,"1 ",foo) + (102,2,00102,"Sat Jan 03 00:00:00 1970 PST","Sat Jan 03 00:00:00 1970",2,"2 ",foo) + (103,3,00103,"Sun Jan 04 00:00:00 1970 PST","Sun Jan 04 00:00:00 1970",3,"3 ",foo) + (104,4,00104,"Mon Jan 05 00:00:00 1970 PST","Mon Jan 05 00:00:00 1970",4,"4 ",foo) + (105,5,00105,"Tue Jan 06 00:00:00 1970 PST","Tue Jan 06 00:00:00 1970",5,"5 ",foo) + (106,6,00106,"Wed Jan 07 00:00:00 1970 PST","Wed Jan 07 00:00:00 1970",6,"6 ",foo) + (107,7,00107,"Thu Jan 08 00:00:00 1970 PST","Thu Jan 08 00:00:00 1970",7,"7 ",foo) + (108,8,00108,"Fri Jan 09 00:00:00 1970 PST","Fri Jan 09 00:00:00 1970",8,"8 ",foo) + (109,9,00109,"Sat Jan 10 00:00:00 1970 PST","Sat Jan 10 00:00:00 1970",9,"9 ",foo) + (110,0,00110,"Sun Jan 11 00:00:00 1970 PST","Sun Jan 11 00:00:00 1970",0,"0 ",foo) +(10 rows) + +-- empty result +SELECT * FROM ft1 WHERE false; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+----+----+----+----+----+---- +(0 rows) + +-- with WHERE clause +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: ((t1.c7 >= '1'::bpchar) AND (t1.c1 = 101) AND ((t1.c6)::text = '1'::text)) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c7 >= '1')) AND (("C 1" = 101)) AND ((c6 = '1')) +(4 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- with FOR UPDATE/SHARE +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* + Remote Filter: (t1.c1 = 101) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE +(4 rows) + +SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* + Remote Filter: (t1.c1 = 102) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE +(4 rows) + +SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo +(1 row) + +-- aggregate +SELECT COUNT(*) FROM ft1 t1; + count +------- + 1000 +(1 row) + +-- subquery +SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- subquery+MAX +SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+----+-------+------------------------------+--------------------------+----+------------+----- + 1000 | 0 | 01000 | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0 | 0 | foo +(1 row) + +-- used in CTE +WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1; + c1 | c2 | c3 | c4 +----+----+-------+------------------------------ + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST +(10 rows) + +-- fixed values +SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1; + ?column? | ?column? +----------+---------- + fixed | +(1 row) + +-- Test forcing the remote server to produce sorted data for a merge join. +SET enable_hashjoin TO false; +SET enable_nestloop TO false; +SET yb_enable_batchednl TO false; +-- inner join; expressions in the clauses appear in the equivalence class list +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2."C 1" + -> Merge Join + Output: t1.c1, t2."C 1" + Inner Unique: true + Merge Cond: (t1.c1 = t2."C 1") + -> Foreign Scan on public.ft2 t1 + Output: t1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Index Scan using t1_pkey on "S 1"."T 1" t2 + Output: t2."C 1" +(11 rows) + +SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + c1 | C 1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2."C 1" + -> Merge Left Join + Output: t1.c1, t2."C 1" + Inner Unique: true + Merge Cond: (t1.c1 = t2."C 1") + -> Foreign Scan on public.ft2 t1 + Output: t1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Index Scan using t1_pkey on "S 1"."T 1" t2 + Output: t2."C 1" +(11 rows) + +SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + c1 | C 1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- A join between local table and foreign join. ORDER BY clause is added to the +-- foreign join so that the local table can be joined using merge join strategy. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1."C 1" + -> Merge Right Join + Output: t1."C 1" + Inner Unique: true + Merge Cond: (t3.c1 = t1."C 1") + -> Foreign Scan + Output: t3.c1 + Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3) + Remote SQL: SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r2."C 1" ASC NULLS LAST + -> Index Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" +(12 rows) + +SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 +----- + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 +(10 rows) + +-- Test similar to above, except that the full join prevents any equivalence +-- classes from being merged. This produces single relation equivalence classes +-- included in join restrictions. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Limit + Output: t1."C 1", t2.c1, t3.c1 + -> Merge Right Join + Output: t1."C 1", t2.c1, t3.c1 + Inner Unique: true + Merge Cond: (t3.c1 = t1."C 1") + -> Foreign Scan + Output: t3.c1, t2.c1 + Relations: (public.ft2 t3) LEFT JOIN (public.ft1 t2) + Remote SQL: SELECT r3."C 1", r2."C 1" FROM ("S 1"."T 1" r3 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + -> Index Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" +(12 rows) + +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 | c1 | c1 +-----+-----+----- + 101 | 101 | 101 + 102 | 102 | 102 + 103 | 103 | 103 + 104 | 104 | 104 + 105 | 105 | 105 + 106 | 106 | 106 + 107 | 107 | 107 + 108 | 108 | 108 + 109 | 109 | 109 + 110 | 110 | 110 +(10 rows) + +-- Test similar to above with all full outer joins +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Limit + Output: t1."C 1", t2.c1, t3.c1 + -> Merge Full Join + Output: t1."C 1", t2.c1, t3.c1 + Inner Unique: true + Merge Cond: (t3.c1 = t1."C 1") + -> Foreign Scan + Output: t2.c1, t3.c1 + Relations: (public.ft1 t2) FULL JOIN (public.ft2 t3) + Remote SQL: SELECT r2."C 1", r3."C 1" FROM ("S 1"."T 1" r2 FULL JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + -> Index Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" +(12 rows) + +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 | c1 | c1 +-----+-----+----- + 101 | 101 | 101 + 102 | 102 | 102 + 103 | 103 | 103 + 104 | 104 | 104 + 105 | 105 | 105 + 106 | 106 | 106 + 107 | 107 | 107 + 108 | 108 | 108 + 109 | 109 | 109 + 110 | 110 | 110 +(10 rows) + +RESET enable_hashjoin; +RESET enable_nestloop; +-- Test executing assertion in estimate_path_cost_size() that makes sure that +-- retrieved_rows for foreign rel re-used to cost pre-sorted foreign paths is +-- a sensible value even when the rel has tuples=0 +CREATE TABLE loct_empty (c1 int NOT NULL, c2 text); +CREATE FOREIGN TABLE ft_empty (c1 int NOT NULL, c2 text) + SERVER loopback OPTIONS (table_name 'loct_empty'); +INSERT INTO loct_empty + SELECT id, 'AAA' || to_char(id, 'FM000') FROM generate_series(1, 100) id; +DELETE FROM loct_empty; +ANALYZE ft_empty; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; + QUERY PLAN +------------------------------------------------------------------------------- + Foreign Scan on public.ft_empty + Output: c1, c2 + Remote SQL: SELECT c1, c2 FROM public.loct_empty ORDER BY c1 ASC NULLS LAST +(3 rows) + +-- test restriction on non-system foreign tables. +SET restrict_nonsystem_relation_kind TO 'foreign-table'; +SELECT * from ft1 where c1 < 1; -- ERROR +ERROR: access to non-system foreign table is restricted +INSERT INTO ft1 (c1) VALUES (1); -- ERROR +ERROR: access to non-system foreign table is restricted +DELETE FROM ft1 WHERE c1 = 1; -- ERROR +ERROR: access to non-system foreign table is restricted +TRUNCATE ft1; -- ERROR +ERROR: access to non-system foreign table is restricted +RESET restrict_nonsystem_relation_kind; +-- =================================================================== +-- WHERE with remotely-executable conditions +-- =================================================================== +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = 1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr + QUERY PLAN +-------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: ((t1.c1 = 100) AND (t1.c2 = 0)) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 100)) AND ((c2 = 0)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest + QUERY PLAN +------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 IS NULL) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NULL)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 IS NOT NULL) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NOT NULL)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (round((abs(t1.c1))::numeric, 0) = '1'::numeric) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((round(abs("C 1"), 0) = 1::numeric)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = (- t1.c1)) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1"))) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: ((t1.c1 IS NOT NULL) IS DISTINCT FROM (t1.c1 IS NOT NULL)) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL))) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = ANY (ARRAY[t1.c2, 1, (t1.c1 + 0)])) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ANY (ARRAY[c2, 1, ("C 1" + 0)]))) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = (ARRAY[t1.c1, t1.c2, 3])[1]) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ((ARRAY["C 1", c2, 3])[1]))) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: ((t1.c6)::text = 'foo''s\bar'::text) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c6 = E'foo''s\\bar')) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote + QUERY PLAN +------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(4 rows) + +-- parameterized remote path for foreign table +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Nested Loop + Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + -> Index Scan using t1_pkey on "S 1"."T 1" a + Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + Index Cond: (a."C 1" = 47) + -> Foreign Scan on public.ft2 b + Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Remote Filter: (a.c2 = b.c1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) +(9 rows) + +SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; + C 1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+----- + 47 | 7 | 00047 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo | 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo +(1 row) + +-- check both safe and unsafe join conditions +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 a, ft2 b + WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Hash Join + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Hash Cond: ((b.c1 = a.c1) AND ((b.c7)::text = upper((a.c7)::text))) + -> Foreign Scan on public.ft2 b + Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + -> Hash + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + -> Foreign Scan on public.ft2 a + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + Filter: (a.c8 = 'foo'::user_enum) + Remote Filter: (a.c2 = 6) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 = 6)) +(13 rows) + +SELECT * FROM ft2 a, ft2 b +WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+-----+-----+----+-------+------------------------------+--------------------------+----+------------+----- + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo +(100 rows) + +-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters +SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5)); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo +(4 rows) + +SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5)); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo +(4 rows) + +-- user-defined operator/function +CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$ +BEGIN +RETURN abs($1); +END +$$ LANGUAGE plpgsql IMMUTABLE; +CREATE OPERATOR === ( + LEFTARG = int, + RIGHTARG = int, + PROCEDURE = int4eq, + COMMUTATOR = === +); +-- built-in operators and functions can be shipped for remote execution +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); + QUERY PLAN +--------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = abs(c2))) +(4 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + QUERY PLAN +---------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = c2)) +(4 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + count +------- + 9 +(1 row) + +-- by default, user-defined ones cannot +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + QUERY PLAN +----------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 = postgres_fdw_abs(t1.c2)) + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" +(6 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + QUERY PLAN +----------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" +(6 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + count +------- + 9 +(1 row) + +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Limit + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c1 === t1.c2) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST +(6 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- but let's put them in an extension ... +ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int); +ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +-- ... now they can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + QUERY PLAN +----------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = public.postgres_fdw_abs(c2))) +(4 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + QUERY PLAN +----------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2)) +(4 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + count +------- + 9 +(1 row) + +-- and both ORDER BY and LIMIT can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 === t1.c2) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2)) ORDER BY c2 ASC NULLS LAST LIMIT 1::bigint +(4 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- Ensure we don't ship FETCH FIRST .. WITH TIES +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c2 FROM ft1 t1 WHERE t1.c1 > 960 ORDER BY t1.c2 FETCH FIRST 2 ROWS WITH TIES; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Limit + Output: c2 + -> Foreign Scan on public.ft1 t1 + Output: c2 + Remote Filter: (t1.c1 > 960) + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE (("C 1" > 960)) ORDER BY c2 ASC NULLS LAST +(6 rows) + +SELECT t1.c2 FROM ft1 t1 WHERE t1.c1 > 960 ORDER BY t1.c2 FETCH FIRST 2 ROWS WITH TIES; + c2 +---- + 0 + 0 + 0 + 0 +(4 rows) + +-- Test CASE pushdown +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1,c2,c3 FROM ft2 WHERE CASE WHEN c1 > 990 THEN c1 END < 1000 ORDER BY c1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft2 + Output: c1, c2, c3 + Remote Filter: (CASE WHEN (ft2.c1 > 990) THEN ft2.c1 ELSE NULL::integer END < 1000) + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" WHERE (((CASE WHEN ("C 1" > 990) THEN "C 1" ELSE NULL::integer END) < 1000)) ORDER BY "C 1" ASC NULLS LAST +(4 rows) + +SELECT c1,c2,c3 FROM ft2 WHERE CASE WHEN c1 > 990 THEN c1 END < 1000 ORDER BY c1; + c1 | c2 | c3 +-----+----+------- + 991 | 1 | 00991 + 992 | 2 | 00992 + 993 | 3 | 00993 + 994 | 4 | 00994 + 995 | 5 | 00995 + 996 | 6 | 00996 + 997 | 7 | 00997 + 998 | 8 | 00998 + 999 | 9 | 00999 +(9 rows) + +-- Nested CASE +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1,c2,c3 FROM ft2 WHERE CASE CASE WHEN c2 > 0 THEN c2 END WHEN 100 THEN 601 WHEN c2 THEN c2 ELSE 0 END > 600 ORDER BY c1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft2 + Output: c1, c2, c3 + Remote Filter: (CASE CASE WHEN (ft2.c2 > 0) THEN ft2.c2 ELSE NULL::integer END WHEN 100 THEN 601 WHEN ft2.c2 THEN ft2.c2 ELSE 0 END > 600) + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" WHERE (((CASE (CASE WHEN (c2 > 0) THEN c2 ELSE NULL::integer END) WHEN 100 THEN 601 WHEN c2 THEN c2 ELSE 0 END) > 600)) ORDER BY "C 1" ASC NULLS LAST +(4 rows) + +SELECT c1,c2,c3 FROM ft2 WHERE CASE CASE WHEN c2 > 0 THEN c2 END WHEN 100 THEN 601 WHEN c2 THEN c2 ELSE 0 END > 600 ORDER BY c1; + c1 | c2 | c3 +----+----+---- +(0 rows) + +-- CASE arg WHEN +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c1 > (CASE mod(c1, 4) WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (ft1.c1 > CASE mod(ft1.c1, 4) WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > (CASE mod("C 1", 4) WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END))) +(4 rows) + +-- CASE cannot be pushed down because of unshippable arg clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c1 > (CASE random()::integer WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END); + QUERY PLAN +----------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (ft1.c1 > CASE (random())::integer WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(4 rows) + +-- these are shippable +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c6 WHEN 'foo' THEN true ELSE c3 < 'bar' END; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: CASE ft1.c6 WHEN 'foo'::text THEN true ELSE (ft1.c3 < 'bar'::text) END + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((CASE c6 WHEN 'foo'::text THEN true ELSE (c3 < 'bar') END)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c3 WHEN c6 THEN true ELSE c3 < 'bar' END; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: CASE ft1.c3 WHEN ft1.c6 THEN true ELSE (ft1.c3 < 'bar'::text) END + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((CASE c3 WHEN c6 THEN true ELSE (c3 < 'bar') END)) +(4 rows) + +-- but this is not because of collation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c3 COLLATE "C" WHEN c6 THEN true ELSE c3 < 'bar' END; + QUERY PLAN +------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: CASE (ft1.c3)::text WHEN ft1.c6 THEN true ELSE (ft1.c3 < 'bar'::text) END + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(4 rows) + +-- check schema-qualification of regconfig constant +CREATE TEXT SEARCH CONFIGURATION public.custom_search + (COPY = pg_catalog.english); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, to_tsvector('custom_search'::regconfig, c3) + Remote Filter: ((ft1.c1 = 642) AND (length(to_tsvector('custom_search'::regconfig, ft1.c3)) > 0)) + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" WHERE (("C 1" = 642)) AND ((length(to_tsvector('public.custom_search'::regconfig, c3)) > 0)) +(4 rows) + +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; + c1 | to_tsvector +-----+------------- + 642 | '00642':1 +(1 row) + +-- =================================================================== +-- ORDER BY queries +-- =================================================================== +-- we should not push order by clause with volatile expressions or unsafe +-- collations +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, random(); + QUERY PLAN +------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, (random()) + Sort Key: ft2.c1, (random()) + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8, random() + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(6 rows) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C"; + QUERY PLAN +------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, ((c3)::text) + Sort Key: ft2.c1, ft2.c3 COLLATE "C" + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8, c3 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(6 rows) + +-- Ensure we don't push ORDER BY expressions which are Consts at the UNION +-- child level to the foreign server. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ( + SELECT 1 AS type,c1 FROM ft1 + UNION ALL + SELECT 2 AS type,c1 FROM ft2 +) a ORDER BY type,c1; + QUERY PLAN +--------------------------------------------------------------------------------- + Merge Append + Sort Key: (1), ft1.c1 + -> Foreign Scan on public.ft1 + Output: 1, ft1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Foreign Scan on public.ft2 + Output: 2, ft2.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST +(8 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ( + SELECT 1 AS type,c1 FROM ft1 + UNION ALL + SELECT 2 AS type,c1 FROM ft2 +) a ORDER BY type; + QUERY PLAN +--------------------------------------------------- + Merge Append + Sort Key: (1) + -> Foreign Scan on public.ft1 + Output: 1, ft1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Foreign Scan on public.ft2 + Output: 2, ft2.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" +(8 rows) + +-- =================================================================== +-- JOIN queries +-- =================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +ANALYZE ft5; +-- join two tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c1, t1.c3 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3, t1.c3 + Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3, r1.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + 22 | + 24 | 24 + 26 | + 28 | + 30 | 30 + 32 | + 34 | + 36 | 36 + 38 | + 40 | +(10 rows) + +-- left outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- left outer join + placement of clauses. +-- clauses within the nullable side are not pulled up, but top level clause on +-- non-nullable side is pushed into non-nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t1.c2, ft5.c1, ft5.c2 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5) + Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE ((r1.c1 < 10)) +(4 rows) + +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; + c1 | c2 | c1 | c2 +----+----+----+---- + 2 | 3 | | + 4 | 5 | | + 6 | 7 | 6 | 7 + 8 | 9 | | +(4 rows) + +-- clauses within the nullable side are not pulled up, but the top level clause +-- on nullable side is not pushed down into nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t1.c2, ft5.c1, ft5.c2 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5) + Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE (((r4.c1 < 10) OR (r4.c1 IS NULL))) AND ((r1.c1 < 10)) +(4 rows) + +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; + c1 | c2 | c1 | c2 +----+----+----+---- + 2 | 3 | | + 4 | 5 | | + 6 | 7 | 6 | 7 + 8 | 9 | | +(4 rows) + +-- right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft4 t2) LEFT JOIN (public.ft5 t1) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r1 ON (((r1.c1 = r2.c1)))) ORDER BY r2.c1 ASC NULLS LAST, r1.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + | 22 + 24 | 24 + | 26 + | 28 + 30 | 30 + | 32 + | 34 + 36 | 36 + | 38 + | 40 +(10 rows) + +-- right outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 45::bigint +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + c1 | c1 +-----+---- + 92 | + 94 | + 96 | 96 + 98 | + 100 | + | 3 + | 9 + | 15 + | 21 + | 27 +(10 rows) + +-- full outer join with restrictions on the joining relations +-- a. the joining relations are both base relations +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, ft5.c1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT s4.c1, s5.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s5.c1 ASC NULLS LAST +(4 rows) + +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; + c1 | c1 +----+---- + 50 | + 52 | + 54 | 54 + 56 | + 58 | + 60 | 60 + | 51 + | 57 +(8 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: 1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(10 rows) + +-- b. one of the joining relations is a base relation and the other is a join +-- relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, t2.c1, t3.c1 + Relations: (public.ft4) FULL JOIN ((public.ft4 t2) LEFT JOIN (public.ft5 t3)) + Remote SQL: SELECT s4.c1, s8.c1, s8.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT r5.c1, r6.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r6 ON (((r5.c1 = r6.c1)))) WHERE ((r5.c1 >= 50)) AND ((r5.c1 <= 60))) s8(c1, c2) ON (((s4.c1 = s8.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s8.c1 ASC NULLS LAST, s8.c2 ASC NULLS LAST +(4 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + c1 | a | b +----+----+---- + 50 | 50 | + 52 | 52 | + 54 | 54 | 54 + 56 | 56 | + 58 | 58 | + 60 | 60 | 60 +(6 rows) + +-- c. test deparsing the remote query as nested subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, ft4_1.c1, ft5.c1 + Relations: (public.ft4) FULL JOIN ((public.ft4 ft4_1) FULL JOIN (public.ft5)) + Remote SQL: SELECT s4.c1, s10.c1, s10.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT s8.c1, s9.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL)))) s10(c1, c2) ON (((s4.c1 = s10.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s10.c1 ASC NULLS LAST, s10.c2 ASC NULLS LAST +(4 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + c1 | a | b +----+----+---- + 50 | 50 | + 52 | 52 | + 54 | 54 | 54 + 56 | 56 | + 58 | 58 | + 60 | 60 | 60 + | | 51 + | | 57 +(8 rows) + +-- d. test deparsing rowmarked relations as subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ybctid, ft4.*, ft5.* + -> Nested Loop + Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ybctid, ft4.*, ft5.* + -> Foreign Scan + Output: ft4.c1, ft4.*, ft5.c1, ft5.* + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT s8.c1, s8.c2, s9.c1, s9.c2 FROM ((SELECT c1, ROW(c1, c2, c3) FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1, c2) FULL JOIN (SELECT c1, ROW(c1, c2, c3) FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1, c2) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL))) ORDER BY s8.c1 ASC NULLS LAST, s9.c1 ASC NULLS LAST + -> Sort + Output: ft4.c1, ft4.*, ft5.c1, ft5.* + Sort Key: ft4.c1, ft5.c1 + -> Hash Full Join + Output: ft4.c1, ft4.*, ft5.c1, ft5.* + Hash Cond: (ft4.c1 = ft5.c1) + Filter: ((ft4.c1 IS NULL) OR (ft4.c1 IS NOT NULL)) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.* + Remote Filter: ((ft4.c1 >= 50) AND (ft4.c1 <= 60)) + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + -> Hash + Output: ft5.c1, ft5.* + -> Foreign Scan on public.ft5 + Output: ft5.c1, ft5.* + Remote Filter: ((ft5.c1 >= 50) AND (ft5.c1 <= 60)) + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + -> Materialize + Output: "T 3".c1, "T 3".ybctid + -> Index Scan using t3_pkey on "S 1"."T 3" + Output: "T 3".c1, "T 3".ybctid + Index Cond: ("T 3".c1 = 50) +(30 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; + c1 | a | b +----+----+---- + 50 | 50 | + 50 | 52 | + 50 | 54 | 54 + 50 | 56 | + 50 | 58 | + 50 | 60 | 60 + 50 | | 51 + 50 | | 57 +(8 rows) + +-- full outer join + inner join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c1, t3.c1 + Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint +(4 rows) + +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; + c1 | c1 | c1 +----+----+---- + 52 | 51 | + 58 | 57 | + | | 2 + | | 4 + | | 6 + | | 8 + | | 10 + | | 12 + | | 14 + | | 16 +(10 rows) + +-- full outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- full outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- right outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- full outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +-- YB note: Vanilla PG uses Hash Join while YB uses Batched NL + Merge Join. Use ORDER BY to get same output +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1 OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- left outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +SET enable_memoize TO off; +-- right outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +RESET enable_memoize; +-- left outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: (public.ft4 t3) LEFT JOIN ((public.ft2 t1) INNER JOIN (public.ft2 t2)) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- full outer join + WHERE clause, only matched rows +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 = r2.c1) OR (r1.c1 IS NULL))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + 66 | 66 + 72 | 72 + 78 | 78 + 84 | 84 + 90 | 90 + 96 | 96 + | 3 + | 9 + | 15 + | 21 +(10 rows) + +-- full outer join + WHERE clause with shippable extensions set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c2, t1.c3 + Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2.c2, r1.c3 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) WHERE ((public.postgres_fdw_abs(r1."C 1") > 0)) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +-- YB note: The following line is a YB test. This should ideally be in a yb.orig file. +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+------- + 11 | 1 | 00011 + 12 | 2 | 00012 + 13 | 3 | 00013 + 14 | 4 | 00014 + 15 | 5 | 00015 + 16 | 6 | 00016 + 17 | 7 | 00017 + 18 | 8 | 00018 + 19 | 9 | 00019 + 20 | 0 | 00020 +(10 rows) + +ALTER SERVER loopback OPTIONS (DROP extensions); +-- full outer join + WHERE clause with shippable extensions not set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c2, t1.c3 + -> Foreign Scan + Output: t1.c1, t2.c2, t1.c3 + Filter: (postgres_fdw_abs(t1.c1) > 0) + Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2.c2, r1.c3 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) +(7 rows) + +-- YB note: The following line is a YB test. This should ideally be in a yb.orig file. +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+------- + 11 | 1 | 00011 + 12 | 2 | 00012 + 13 | 3 | 00013 + 14 | 4 | 00014 + 15 | 5 | 00015 + 16 | 6 | 00016 + 17 | 7 | 00017 + 18 | 8 | 00018 + 19 | 9 | 00019 + 20 | 0 | 00020 +(10 rows) + +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +-- join two tables with FOR UPDATE clause +-- tests whole-row reference for row marks +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR UPDATE OF r1 +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR UPDATE OF r1 FOR UPDATE OF r2 +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join two tables with FOR SHARE clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR SHARE OF r1 +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR SHARE OF r1 FOR SHARE OF r2 +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join in CTE +EXPLAIN (VERBOSE, COSTS OFF) +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t.c1_1, t.c2_1, t.c1_3 + CTE t + -> Foreign Scan + Output: t1.c1, t1.c3, t2.c1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + -> Sort + Output: t.c1_1, t.c2_1, t.c1_3 + Sort Key: t.c1_3, t.c1_1 + -> CTE Scan on t + Output: t.c1_1, t.c2_1, t.c1_3 +(12 rows) + +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; + c1_1 | c2_1 +------+------ + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- ctid with whole-row reference +-- YB note: YugabyteDB uses ybctid as the tuple identifier, not ctid +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.ybctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; -- YB note: see above + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.ybctid, t1.*, t2.*, t1.c1, t1.c3 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1.ybctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r1."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint +(4 rows) + +-- YB note: The following line is a YB test. This should ideally be in a yb.orig file. +SELECT t1.ybctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + ybctid | t1 | t2 | c1 +----------------+--------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+----- + \x488000006521 | (101,1,00101,"Fri Jan 02 00:00:00 1970 PST","Fri Jan 02 00:00:00 1970",1,"1 ",foo) | (101,1,00101,"Fri Jan 02 00:00:00 1970 PST","Fri Jan 02 00:00:00 1970",1,"1 ",foo) | 101 + \x488000006621 | (102,2,00102,"Sat Jan 03 00:00:00 1970 PST","Sat Jan 03 00:00:00 1970",2,"2 ",foo) | (102,2,00102,"Sat Jan 03 00:00:00 1970 PST","Sat Jan 03 00:00:00 1970",2,"2 ",foo) | 102 + \x488000006721 | (103,3,00103,"Sun Jan 04 00:00:00 1970 PST","Sun Jan 04 00:00:00 1970",3,"3 ",foo) | (103,3,00103,"Sun Jan 04 00:00:00 1970 PST","Sun Jan 04 00:00:00 1970",3,"3 ",foo) | 103 + \x488000006821 | (104,4,00104,"Mon Jan 05 00:00:00 1970 PST","Mon Jan 05 00:00:00 1970",4,"4 ",foo) | (104,4,00104,"Mon Jan 05 00:00:00 1970 PST","Mon Jan 05 00:00:00 1970",4,"4 ",foo) | 104 + \x488000006921 | (105,5,00105,"Tue Jan 06 00:00:00 1970 PST","Tue Jan 06 00:00:00 1970",5,"5 ",foo) | (105,5,00105,"Tue Jan 06 00:00:00 1970 PST","Tue Jan 06 00:00:00 1970",5,"5 ",foo) | 105 + \x488000006a21 | (106,6,00106,"Wed Jan 07 00:00:00 1970 PST","Wed Jan 07 00:00:00 1970",6,"6 ",foo) | (106,6,00106,"Wed Jan 07 00:00:00 1970 PST","Wed Jan 07 00:00:00 1970",6,"6 ",foo) | 106 + \x488000006b21 | (107,7,00107,"Thu Jan 08 00:00:00 1970 PST","Thu Jan 08 00:00:00 1970",7,"7 ",foo) | (107,7,00107,"Thu Jan 08 00:00:00 1970 PST","Thu Jan 08 00:00:00 1970",7,"7 ",foo) | 107 + \x488000006c21 | (108,8,00108,"Fri Jan 09 00:00:00 1970 PST","Fri Jan 09 00:00:00 1970",8,"8 ",foo) | (108,8,00108,"Fri Jan 09 00:00:00 1970 PST","Fri Jan 09 00:00:00 1970",8,"8 ",foo) | 108 + \x488000006d21 | (109,9,00109,"Sat Jan 10 00:00:00 1970 PST","Sat Jan 10 00:00:00 1970",9,"9 ",foo) | (109,9,00109,"Sat Jan 10 00:00:00 1970 PST","Sat Jan 10 00:00:00 1970",9,"9 ",foo) | 109 + \x488000006e21 | (110,0,00110,"Sun Jan 11 00:00:00 1970 PST","Sun Jan 11 00:00:00 1970",0,"0 ",foo) | (110,0,00110,"Sun Jan 11 00:00:00 1970 PST","Sun Jan 11 00:00:00 1970",0,"0 ",foo) | 110 +(10 rows) + +-- SEMI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------- + Limit + Output: t1.c1 + -> Merge Semi Join + Output: t1.c1 + Merge Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST +(11 rows) + +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + c1 +----- + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 +(10 rows) + +-- ANTI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------- + Limit + Output: t1.c1 + -> Merge Anti Join + Output: t1.c1 + Merge Cond: (t1.c1 = t2.c2) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Foreign Scan on public.ft2 t2 + Output: t2.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST +(11 rows) + +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + c1 +----- + 110 + 111 + 112 + 113 + 114 + 115 + 116 + 117 + 118 + 119 +(10 rows) + +-- CROSS JOIN can be pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ORDER BY r1."C 1" ASC NULLS LAST, r2."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+----- + 1 | 101 + 1 | 102 + 1 | 103 + 1 | 104 + 1 | 105 + 1 | 106 + 1 | 107 + 1 | 108 + 1 | 109 + 1 | 110 +(10 rows) + +-- different server, not pushed down. No result expected. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1 + -> Merge Join + Output: t1.c1, t2.c1 + Merge Cond: (t2.c1 = t1.c1) + -> Foreign Scan on public.ft6 t2 + Output: t2.c1, t2.c2, t2.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + -> Materialize + Output: t1.c1, t1.c2, t1.c3 + -> Foreign Scan on public.ft5 t1 + Output: t1.c1, t1.c2, t1.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST +(13 rows) + +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+---- +(0 rows) + +-- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS +-- JOIN since c8 in both tables has same value. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1 + -> Sort + Output: t1.c1, t2.c1 + Sort Key: t1.c1, t2.c1 + -> Merge Left Join + Output: t1.c1, t2.c1 + Merge Cond: (t1.c8 = t2.c8) + -> Sort + Output: t1.c1, t1.c8 + Sort Key: t1.c8 + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c8 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" + -> Sort + Output: t2.c1, t2.c8 + Sort Key: t2.c8 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.c8 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" +(20 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+----- + 1 | 101 + 1 | 102 + 1 | 103 + 1 | 104 + 1 | 105 + 1 | 106 + 1 | 107 + 1 | 108 + 1 | 109 + 1 | 110 +(10 rows) + +-- unsafe conditions on one side (c8 has a UDT), not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Hash Right Join + Output: t1.c1, t2.c1, t1.c3 + Hash Cond: (t2.c1 = t1.c1) + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash + Output: t1.c1, t1.c3 + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3 + Filter: (t1.c8 = 'foo'::user_enum) + Remote SQL: SELECT "C 1", c3, c8 FROM "S 1"."T 1" +(17 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join where unsafe to pushdown condition in WHERE clause has a column not +-- in the SELECT clause. In this test unsafe clause needs to have column +-- references from both joining sides so that the clause is not pushed down +-- into one of the joining sides. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Foreign Scan + Output: t1.c1, t2.c1, t1.c3 + Filter: (t1.c8 = t2.c8) + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) +(10 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- Aggregate after UNION, for testing setrefs +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, (avg((t1.c1 + t2.c1))) + -> Sort + Output: t1.c1, (avg((t1.c1 + t2.c1))) + Sort Key: t1.c1 + -> HashAggregate + Output: t1.c1, avg((t1.c1 + t2.c1)) + Group Key: t1.c1 + -> HashAggregate + Output: t1.c1, t2.c1 + Group Key: t1.c1, t2.c1 + -> Append + -> Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + -> Foreign Scan + Output: t1_1.c1, t2_1.c1 + Relations: (public.ft1 t1_1) INNER JOIN (public.ft2 t2_1) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) +(20 rows) + +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; + t1c1 | avg +------+---------------------- + 101 | 202.0000000000000000 + 102 | 204.0000000000000000 + 103 | 206.0000000000000000 + 104 | 208.0000000000000000 + 105 | 210.0000000000000000 + 106 | 212.0000000000000000 + 107 | 214.0000000000000000 + 108 | 216.0000000000000000 + 109 | 218.0000000000000000 + 110 | 220.0000000000000000 +(10 rows) + +-- join with lateral reference +-- YB note: This is commented out as it ends up doing rescans on a remote relation +-- YB note: which uses an unsupported MOVE BACKWARD ALL IN command. See issue #11422. +/* +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +*/ -- YB +-- join with pseudoconstant quals, not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1 AND CURRENT_USER = SESSION_USER) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Result + Output: t1.c1, t2.c1, t1.c3 + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Hash Join + Output: t1.c1, t1.c3, t2.c1 + Hash Cond: (t2.c1 = t1.c1) + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash + Output: t1.c1, t1.c3 + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3 + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" +(19 rows) + +-- non-Var items in targetlist of the nullable rel of a join preventing +-- push-down in some cases +-- unable to push {ft1, ft2} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: (13), ft2.c1 + Join Filter: (13 = ft2.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1 + Remote Filter: ((ft2.c1 >= 10) AND (ft2.c1 <= 15)) + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST + -> Foreign Scan on public.ft1 + Output: 13 + Remote Filter: (ft1.c1 = 13) + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13)) +(11 rows) + +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + a | c1 +----+---- + | 10 + | 11 + | 12 + 13 | 13 + | 14 + | 15 +(6 rows) + +-- ok to push {ft1, ft2} but not {ft1, ft2, ft4} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: ft4.c1, (13), ft1.c1, ft2.c1 + Join Filter: (ft4.c1 = ft1.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3 + Remote Filter: ((ft4.c1 >= 10) AND (ft4.c1 <= 15)) + Remote SQL: SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15)) + -> Materialize + Output: ft1.c1, ft2.c1, (13) + -> Foreign Scan + Output: ft1.c1, ft2.c1, 13 + Relations: (public.ft1) INNER JOIN (public.ft2) + Remote SQL: SELECT r4."C 1", r5."C 1" FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r5."C 1" = 12)) AND ((r4."C 1" = 12)))) ORDER BY r4."C 1" ASC NULLS LAST +(13 rows) + +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + c1 | a | b | c +----+----+----+---- + 10 | | | + 12 | 13 | 12 | 12 + 14 | | | +(3 rows) + +-- join with nullable side with some columns with null values +UPDATE ft5 SET c3 = null where c1 % 9 = 0; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 + Relations: (public.ft5) INNER JOIN (public.ft4) + Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST +(4 rows) + +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + ft5 | c1 | c2 | c3 | c1 | c2 +----------------+----+----+--------+----+---- + (12,13,AAA012) | 12 | 13 | AAA012 | 12 | 13 + (18,19,) | 18 | 19 | | 18 | 19 + (24,25,AAA024) | 24 | 25 | AAA024 | 24 | 25 + (30,31,AAA030) | 30 | 31 | AAA030 | 30 | 31 +(4 rows) + +-- multi-way join involving multiple merge joins +-- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)); +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; +SET enable_nestloop TO false; +SET yb_enable_batchednl TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ybctid + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ybctid + Inner Unique: true + Merge Cond: (ft1.c2 = local_tbl.c1) + -> Foreign Scan + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.* + Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5) + Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4 + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.* + Merge Cond: (ft1.c2 = ft5.c1) + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.* + Merge Cond: (ft1.c2 = ft4.c1) + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + Sort Key: ft1.c2 + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + Merge Cond: (ft1.c1 = ft2.c1) + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Sort Key: ft1.c1 + -> Foreign Scan on public.ft1 + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Remote Filter: (ft1.c1 < 100) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE + -> Materialize + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + -> Foreign Scan on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + Remote Filter: (ft2.c1 < 100) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + -> Sort + Output: ft4.c1, ft4.c2, ft4.c3, ft4.* + Sort Key: ft4.c1 + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3, ft4.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE + -> Sort + Output: ft5.c1, ft5.c2, ft5.c3, ft5.* + Sort Key: ft5.c1 + -> Foreign Scan on public.ft5 + Output: ft5.c1, ft5.c2, ft5.c3, ft5.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE + -> Index Scan using local_tbl_pkey on public.local_tbl + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ybctid +(49 rows) + +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 | c1 | c2 | c3 +----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+--------+----+----+--------+----+----+------ + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 +(10 rows) + +RESET enable_nestloop; +RESET enable_hashjoin; +-- test that add_paths_with_pathkeys_for_rel() arranges for the epq_path to +-- return columns needed by the parent ForeignScan node +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.*, COALESCE(ft1.c3 || ft2.c3, 'foobar') FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100)) ss ON (local_tbl.c1 = ss.c1) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)), local_tbl.ybctid, ft1.*, ft2.* + -> Merge Left Join + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)), local_tbl.ybctid, ft1.*, ft2.* + Merge Cond: (local_tbl.c1 = ft1.c1) + -> Index Scan using local_tbl_pkey on public.local_tbl + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ybctid + -> Materialize + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)) + -> Foreign Scan + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, COALESCE((ft1.c3 || ft2.c3), 'foobar'::text) + Relations: (public.ft1) INNER JOIN (public.ft2) + Remote SQL: SELECT r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8) END, CASE WHEN (r5.*)::text IS NOT NULL THEN ROW(r5."C 1", r5.c2, r5.c3, r5.c4, r5.c5, r5.c6, r5.c7, r5.c8) END, r5.c3 FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r4."C 1" = r5."C 1")) AND ((r4."C 1" < 100)))) ORDER BY r4."C 1" ASC NULLS LAST + -> Result + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, ft2.c3 + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)), ft2.c3 + Sort Key: ft1.c1 + -> Hash Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, COALESCE((ft1.c3 || ft2.c3), 'foobar'::text), ft2.c3 + Hash Cond: (ft1.c1 = ft2.c1) + -> Foreign Scan on public.ft1 + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Remote Filter: (ft1.c1 < 100) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) + -> Hash + Output: ft2.*, ft2.c1, ft2.c3 + -> Foreign Scan on public.ft2 + Output: ft2.*, ft2.c1, ft2.c3 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(30 rows) + +ALTER SERVER loopback OPTIONS (DROP extensions); +ALTER SERVER loopback OPTIONS (ADD fdw_startup_cost '10000.0'); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.* FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100 AND ft1.c1 = postgres_fdw_abs(ft2.c2))) ss ON (local_tbl.c3 = ss.c3) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, local_tbl.ybctid, ft1.*, ft2.* + -> Nested Loop Left Join + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, local_tbl.ybctid, ft1.*, ft2.* + Join Filter: (local_tbl.c3 = ft1.c3) + -> Index Scan using local_tbl_pkey on public.local_tbl + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ybctid + -> Materialize + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.* + -> Foreign Scan + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.* + Filter: (ft1.c1 = postgres_fdw_abs(ft2.c2)) + Relations: (public.ft1) INNER JOIN (public.ft2) + Remote SQL: SELECT r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8) END, CASE WHEN (r5.*)::text IS NOT NULL THEN ROW(r5."C 1", r5.c2, r5.c3, r5.c4, r5.c5, r5.c6, r5.c7, r5.c8) END, r5.c2 FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r4."C 1" = r5."C 1")) AND ((r4."C 1" < 100)))) ORDER BY r4.c3 ASC NULLS LAST + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, ft2.c2 + Sort Key: ft1.c3 + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, ft2.c2 + Merge Cond: ((ft1.c1 = (postgres_fdw_abs(ft2.c2))) AND (ft1.c1 = ft2.c1)) + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Sort Key: ft1.c1 + -> Foreign Scan on public.ft1 + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Remote Filter: (ft1.c1 < 100) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) + -> Sort + Output: ft2.*, ft2.c1, ft2.c2, (postgres_fdw_abs(ft2.c2)) + Sort Key: (postgres_fdw_abs(ft2.c2)), ft2.c1 + -> Foreign Scan on public.ft2 + Output: ft2.*, ft2.c1, ft2.c2, postgres_fdw_abs(ft2.c2) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST +(33 rows) + +ALTER SERVER loopback OPTIONS (DROP fdw_startup_cost); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +DROP TABLE local_tbl; +-- check join pushdown in situations where multiple userids are involved +CREATE ROLE regress_view_owner SUPERUSER; +CREATE USER MAPPING FOR regress_view_owner SERVER loopback; +GRANT SELECT ON ft4 TO regress_view_owner; +GRANT SELECT ON ft5 TO regress_view_owner; +CREATE VIEW v4 AS SELECT * FROM ft4; +CREATE VIEW v5 AS SELECT * FROM ft5; +ALTER VIEW v5 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, different view owners + QUERY PLAN +---------------------------------------------------------------------- + Limit + Output: ft4.c1, ft5.c2, ft5.c1 + -> Sort + Output: ft4.c1, ft5.c2, ft5.c1 + Sort Key: ft4.c1, ft5.c1 + -> Hash Left Join + Output: ft4.c1, ft5.c2, ft5.c1 + Hash Cond: (ft4.c1 = ft5.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Hash + Output: ft5.c2, ft5.c1 + -> Foreign Scan on public.ft5 + Output: ft5.c2, ft5.c1 + Remote SQL: SELECT c1, c2 FROM "S 1"."T 4" +(16 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, ft5.c2, ft5.c1 + Relations: (public.ft4) LEFT JOIN (public.ft5) + Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, view owner not current user + QUERY PLAN +---------------------------------------------------------------------- + Limit + Output: ft4.c1, t2.c2, t2.c1 + -> Sort + Output: ft4.c1, t2.c2, t2.c1 + Sort Key: ft4.c1, t2.c1 + -> Hash Left Join + Output: ft4.c1, t2.c2, t2.c1 + Hash Cond: (ft4.c1 = t2.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Hash + Output: t2.c2, t2.c1 + -> Foreign Scan on public.ft5 t2 + Output: t2.c2, t2.c1 + Remote SQL: SELECT c1, c2 FROM "S 1"."T 4" +(16 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO CURRENT_USER; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, t2.c2, t2.c1 + Relations: (public.ft4) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO regress_view_owner; +-- cleanup +DROP OWNED BY regress_view_owner; +DROP ROLE regress_view_owner; +-- =================================================================== +-- Aggregate and grouping queries +-- =================================================================== +-- Simple aggregates +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST +(4 rows) + +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + count | sum | avg | min | max | stddev | sum2 +-------+-------+----------------------+-----+------+--------+------- + 100 | 49600 | 496.0000000000000000 | 1 | 991 | 0 | 49600 + 100 | 49700 | 497.0000000000000000 | 2 | 992 | 0 | 49700 + 100 | 49800 | 498.0000000000000000 | 3 | 993 | 0 | 49800 + 100 | 49900 | 499.0000000000000000 | 4 | 994 | 0 | 49900 + 100 | 50500 | 505.0000000000000000 | 0 | 1000 | 0 | 50500 +(5 rows) + +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST LIMIT 1::bigint +(4 rows) + +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + count | sum | avg | min | max | stddev | sum2 +-------+-------+----------------------+-----+-----+--------+------- + 100 | 49600 | 496.0000000000000000 | 1 | 991 | 0 | 49600 +(1 row) + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; + QUERY PLAN +------------------------------------------------------------------------------- + Aggregate + Output: sum((c1 * ((random() <= '1'::double precision))::integer)), avg(c1) + -> Foreign Scan on public.ft1 + Output: c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" +(5 rows) + +-- Aggregate over join query +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)), (sum(t1.c1)), (avg(t2.c1)) + Relations: Aggregate on ((public.ft1 t1) INNER JOIN (public.ft1 t2)) + Remote SQL: SELECT count(*), sum(r1."C 1"), avg(r2."C 1") FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2.c2 = 6)) AND ((r1.c2 = 6)))) +(4 rows) + +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + count | sum | avg +-------+---------+---------------------- + 10000 | 5010000 | 501.0000000000000000 +(1 row) + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) +select sum(t1.c1), count(t2.c1) from ft1 t1 inner join ft2 t2 on (t1.c1 = t2.c1) where ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: sum(t1.c1), count(t2.c1) + -> Foreign Scan + Output: t1.c1, t2.c1 + Filter: (((((t1.c1 * t2.c1) / (t1.c1 * t2.c1)))::double precision * random()) <= '1'::double precision) + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) +(7 rows) + +-- GROUP BY clause having expressions +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: ((c2 / 2)), ((sum(c2) * (c2 / 2))) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT (c2 / 2), (sum(c2) * (c2 / 2)) FROM "S 1"."T 1" GROUP BY 1 ORDER BY (c2 / 2) ASC NULLS LAST +(4 rows) + +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + ?column? | ?column? +----------+---------- + 0 | 0 + 1 | 500 + 2 | 1800 + 3 | 3900 + 4 | 6800 +(5 rows) + +-- Aggregates in subquery are pushed down. +explain (verbose, costs off) +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: count(ft1.c2), sum(ft1.c2) + -> Foreign Scan + Output: ft1.c2, (sum(ft1.c1)), (sqrt((ft1.c1)::double precision)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum("C 1"), sqrt("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 ORDER BY c2 ASC NULLS LAST, sum("C 1") ASC NULLS LAST +(6 rows) + +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + count | sum +-------+------ + 1000 | 4500 +(1 row) + +-- Aggregate is still pushed down by taking unshippable expression out +explain (verbose, costs off) +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Sort + Output: ((c2 * ((random() <= '1'::double precision))::integer)), ((sum(c1) * c2)), c2 + Sort Key: ((ft1.c2 * ((random() <= '1'::double precision))::integer)), ((sum(ft1.c1) * ft1.c2)) + -> Foreign Scan + Output: (c2 * ((random() <= '1'::double precision))::integer), ((sum(c1) * c2)), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT (sum("C 1") * c2), c2 FROM "S 1"."T 1" GROUP BY 2 +(7 rows) + +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + sum1 | sum2 +------+-------- + 0 | 0 + 1 | 49600 + 2 | 99400 + 3 | 149400 + 4 | 199600 + 5 | 250000 + 6 | 300600 + 7 | 351400 + 8 | 402400 + 9 | 453600 +(10 rows) + +-- Aggregate with unshippable GROUP BY clause are not pushed +explain (verbose, costs off) +select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::int order by 1; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Output: ((c2 * ((random() <= '1'::double precision))::integer)) + Sort Key: ((ft2.c2 * ((random() <= '1'::double precision))::integer)) + -> HashAggregate + Output: ((c2 * ((random() <= '1'::double precision))::integer)) + Group Key: (ft2.c2 * ((random() <= '1'::double precision))::integer) + -> Foreign Scan on public.ft2 + Output: (c2 * ((random() <= '1'::double precision))::integer) + Remote SQL: SELECT c2 FROM "S 1"."T 1" +(9 rows) + +-- GROUP BY clause in various forms, cardinal, alias and constant expression +explain (verbose, costs off) +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (count(c2)), c2, 5, 7.0, 9 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 ORDER BY c2 ASC NULLS LAST +(4 rows) + +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + w | x | y | z +-----+---+---+----- + 100 | 0 | 5 | 7.0 + 100 | 1 | 5 | 7.0 + 100 | 2 | 5 | 7.0 + 100 | 3 | 5 | 7.0 + 100 | 4 | 5 | 7.0 + 100 | 5 | 5 | 7.0 + 100 | 6 | 5 | 7.0 + 100 | 7 | 5 | 7.0 + 100 | 8 | 5 | 7.0 + 100 | 9 | 5 | 7.0 +(10 rows) + +-- GROUP BY clause referring to same column multiple times +-- Also, ORDER BY contains an aggregate function +explain (verbose, costs off) +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c2, c2, (sum(c1)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, c2, sum("C 1") FROM "S 1"."T 1" WHERE ((c2 > 6)) GROUP BY 1, 2 ORDER BY sum("C 1") ASC NULLS LAST +(4 rows) + +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + c2 | c2 +----+---- + 7 | 7 + 8 | 8 + 9 | 9 +(3 rows) + +-- Testing HAVING clause shippability +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (sum(c1)) + Sort Key: ft2.c2 + -> Foreign Scan + Output: c2, (sum(c1)) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 HAVING ((avg("C 1") < 500::numeric)) AND ((sum("C 1") < 49800)) +(7 rows) + +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + c2 | sum +----+------- + 1 | 49600 + 2 | 49700 +(2 rows) + +-- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Foreign Scan + Output: ft1.c5, NULL::bigint, (sqrt((ft1.c2)::double precision)) + Filter: (((((avg(ft1.c1)) / (avg(ft1.c1))))::double precision * random()) <= '1'::double precision) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c5, NULL::bigint, sqrt(c2), avg("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 HAVING ((avg("C 1") < 500::numeric)) +(7 rows) + +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + count +------- + 49 +(1 row) + +-- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Sort + Output: (sum(c1)), c2 + Sort Key: (sum(ft1.c1)) + -> HashAggregate + Output: sum(c1), c2 + Group Key: ft1.c2 + Filter: (avg((ft1.c1 * ((random() <= '1'::double precision))::integer)) > '100'::numeric) + -> Foreign Scan on public.ft1 + Output: c1, c2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" +(10 rows) + +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; + QUERY PLAN +-------------------------------------------------- + Foreign Scan + Output: $0, (sum(ft1.c1)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1") FROM "S 1"."T 1" + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum +(6 rows) + +select exists(select 1 from pg_enum), sum(c1) from ft1; + exists | sum +--------+-------- + t | 500500 +(1 row) + +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + QUERY PLAN +--------------------------------------------------- + GroupAggregate + Output: ($0), sum(ft1.c1) + Group Key: $0 + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum + -> Foreign Scan on public.ft1 + Output: $0, ft1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" +(8 rows) + +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + exists | sum +--------+-------- + t | 500500 +(1 row) + +-- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates +-- ORDER BY within aggregate, same column used to order +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c1 ORDER BY c1)), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) GROUP BY 2 ORDER BY array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST) ASC NULLS LAST +(4 rows) + +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + array_agg +-------------------------------- + {1,11,21,31,41,51,61,71,81,91} + {2,12,22,32,42,52,62,72,82,92} + {3,13,23,33,43,53,63,73,83,93} + {4,14,24,34,44,54,64,74,84,94} + {5,15,25,35,45,55,65,75,85,95} + {6,16,26,36,46,56,66,76,86,96} + {7,17,27,37,47,57,67,77,87,97} + {8,18,28,38,48,58,68,78,88,98} + {9,19,29,39,49,59,69,79,89,99} + {10,20,30,40,50,60,70,80,90} +(10 rows) + +-- ORDER BY within aggregate, different column used to order also using DESC +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c5 ORDER BY c1 DESC)) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT array_agg(c5 ORDER BY "C 1" DESC NULLS FIRST) FROM "S 1"."T 1" WHERE (("C 1" < 50)) AND ((c2 = 6)) +(4 rows) + +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + array_agg +------------------------------------------------------------------------------------------------------------------------------------------ + {"Mon Feb 16 00:00:00 1970","Fri Feb 06 00:00:00 1970","Tue Jan 27 00:00:00 1970","Sat Jan 17 00:00:00 1970","Wed Jan 07 00:00:00 1970"} +(1 row) + +-- DISTINCT within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5)), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5)) ASC NULLS LAST +(4 rows) + +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +-- DISTINCT combined with ORDER BY within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST) ASC NULLS LAST +(4 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC NULLS LAST)), ((t2.c1 % 3)) + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST) ASC NULLS LAST +(4 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {3,2,1,NULL} + {4,3,2,1,0} +(2 rows) + +-- FILTER within aggregate +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5)))), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1") FILTER (WHERE (("C 1" < 100) AND (c2 > 5))), c2 FROM "S 1"."T 1" GROUP BY 2 ORDER BY sum("C 1") FILTER (WHERE (("C 1" < 100) AND (c2 > 5))) ASC NULLS LAST +(4 rows) + +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; + sum +----- + 510 + 520 + 530 + 540 + + + + + + +(10 rows) + +-- DISTINCT, ORDER BY and FILTER within aggregate +explain (verbose, costs off) +select sum(c1%3), sum(distinct c1%3 order by c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (sum((c1 % 3))), (sum(DISTINCT (c1 % 3) ORDER BY (c1 % 3)) FILTER (WHERE ((c1 % 3) < 2))), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum(("C 1" % 3)), sum(DISTINCT ("C 1" % 3) ORDER BY (("C 1" % 3)) ASC NULLS LAST) FILTER (WHERE (("C 1" % 3) < 2)), c2 FROM "S 1"."T 1" WHERE ((c2 = 6)) GROUP BY 3 +(4 rows) + +select sum(c1%3), sum(distinct c1%3 order by c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; + sum | sum | c2 +-----+-----+---- + 99 | 1 | 6 +(1 row) + +-- Outer query is aggregation query +explain (verbose, costs off) +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------ + Unique + Output: ((SubPlan 1)) + -> Sort + Output: ((SubPlan 1)) + Sort Key: ((SubPlan 1)) + -> Foreign Scan + Output: (SubPlan 1) + Relations: Aggregate on (public.ft2 t2) + Remote SQL: SELECT count(*) FILTER (WHERE ((c2 = 6) AND ("C 1" < 10))) FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + SubPlan 1 + -> Foreign Scan on public.ft1 t1 + Output: (count(*) FILTER (WHERE ((t2.c2 = 6) AND (t2.c1 < 10)))) + Remote Filter: (t1.c1 = 6) + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 6)) +(14 rows) + +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + count +------- + 1 +(1 row) + +-- Inner query is aggregation query +explain (verbose, costs off) +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------ + Unique + Output: ((SubPlan 1)) + -> Sort + Output: ((SubPlan 1)) + Sort Key: ((SubPlan 1)) + -> Foreign Scan on public.ft2 t2 + Output: (SubPlan 1) + Remote Filter: ((t2.c2 % 6) = 0) + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + SubPlan 1 + -> Foreign Scan + Output: (count(t1.c1) FILTER (WHERE ((t2.c2 = 6) AND (t2.c1 < 10)))) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count("C 1") FILTER (WHERE (($1::integer = 6) AND ($2::integer < 10))) FROM "S 1"."T 1" WHERE (("C 1" = 6)) +(14 rows) + +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + count +------- + 0 + 1 +(2 rows) + +-- Aggregate not pushed down as FILTER condition is not pushable +explain (verbose, costs off) +select sum(c1) filter (where (c1 / c1) * random() <= 1) from ft1 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Sort + Output: (sum(c1) FILTER (WHERE ((((c1 / c1))::double precision * random()) <= '1'::double precision))), c2 + Sort Key: (sum(ft1.c1) FILTER (WHERE ((((ft1.c1 / ft1.c1))::double precision * random()) <= '1'::double precision))) + -> HashAggregate + Output: sum(c1) FILTER (WHERE ((((c1 / c1))::double precision * random()) <= '1'::double precision)), c2 + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c1, c2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" +(9 rows) + +explain (verbose, costs off) +select sum(c2) filter (where c2 in (select c2 from ft1 where c2 < 5)) from ft1; + QUERY PLAN +------------------------------------------------------------------- + Aggregate + Output: sum(ft1_1.c2) FILTER (WHERE (hashed SubPlan 1)) + -> Foreign Scan on public.ft1 ft1_1 + Output: ft1_1.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + SubPlan 1 + -> Foreign Scan on public.ft1 + Output: ft1.c2 + Remote Filter: (ft1.c2 < 5) + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) +(10 rows) + +-- Ordered-sets within aggregate +explain (verbose, costs off) +select c2, rank('10'::varchar) within group (order by c6), percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c2, (rank('10'::character varying) WITHIN GROUP (ORDER BY c6)), (percentile_cont((((c2)::numeric / '10'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision))) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, rank('10'::character varying) WITHIN GROUP (ORDER BY c6 ASC NULLS LAST), percentile_cont((c2 / 10::numeric)) WITHIN GROUP (ORDER BY ("C 1") ASC NULLS LAST) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1 HAVING ((percentile_cont((c2 / 10::numeric)) WITHIN GROUP (ORDER BY ("C 1") ASC NULLS LAST) < 500::double precision)) ORDER BY c2 ASC NULLS LAST +(4 rows) + +select c2, rank('10'::varchar) within group (order by c6), percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; + c2 | rank | percentile_cont +----+------+----------------- + 0 | 101 | 10 + 1 | 101 | 100 + 2 | 1 | 200 + 3 | 1 | 300 + 4 | 1 | 400 +(5 rows) + +-- Using multiple arguments within aggregates +explain (verbose, costs off) +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c1, (rank(c1, c2) WITHIN GROUP (ORDER BY c1, c2)), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT "C 1", rank("C 1", c2) WITHIN GROUP (ORDER BY "C 1" ASC NULLS LAST, c2 ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" = 6)) GROUP BY 1, 3 +(4 rows) + +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; + c1 | rank +----+------ + 6 | 1 +(1 row) + +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); +-- Disable hash aggregation for plan stability. +set enable_hashagg to false; +-- Not pushed down due to user defined aggregate +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + QUERY PLAN +---------------------------------------------------------------------------------- + GroupAggregate + Output: c2, least_agg(VARIADIC ARRAY[c1]) + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST +(6 rows) + +-- Add function and aggregate into extension +alter extension postgres_fdw add function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw add aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); +-- Now aggregate will be pushed. Aggregate will display VARIADIC argument. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (least_agg(VARIADIC ARRAY[c1])) + Sort Key: ft1.c2 + -> Foreign Scan + Output: c2, (least_agg(VARIADIC ARRAY[c1])) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, public.least_agg(VARIADIC ARRAY["C 1"]) FROM "S 1"."T 1" WHERE ((c2 < 100)) GROUP BY 1 +(7 rows) + +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; + c2 | least_agg +----+----------- + 0 | 10 + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 + 7 | 7 + 8 | 8 + 9 | 9 +(10 rows) + +-- Remove function and aggregate from extension +alter extension postgres_fdw drop function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw drop aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); +-- Not pushed down as we have dropped objects from extension. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + QUERY PLAN +---------------------------------------------------------------------------------- + GroupAggregate + Output: c2, least_agg(VARIADIC ARRAY[c1]) + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST +(6 rows) + +-- Cleanup +reset enable_hashagg; +drop aggregate least_agg(variadic items anyarray); +drop function least_accum(anyelement, variadic anyarray); +-- Testing USING OPERATOR() in ORDER BY within aggregate. +-- For this, we need user defined operators along with operator family and +-- operator class. Create those and then add them in extension. Note that +-- user defined objects are considered unshippable unless they are part of +-- the extension. +create operator public.<^ ( + leftarg = int4, + rightarg = int4, + procedure = int4eq +); +create operator public.=^ ( + leftarg = int4, + rightarg = int4, + procedure = int4lt +); +create operator public.>^ ( + leftarg = int4, + rightarg = int4, + procedure = int4gt +); +create operator family my_op_family using btree; +create function my_op_cmp(a int, b int) returns int as + $$begin return btint4cmp(a, b); end $$ language plpgsql; +create operator class my_op_class for type int using btree family my_op_family as + operator 1 public.<^, + operator 3 public.=^, + operator 5 public.>^, + function 1 my_op_cmp(int, int); +-- This will not be pushed as user defined sort operator is not part of the +-- extension yet. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + QUERY PLAN +-------------------------------------------------------------------------------------------- + GroupAggregate + Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2 + Group Key: ft2.c2 + -> Foreign Scan on public.ft2 + Output: c1, c2 + Remote Filter: ((ft2.c1 < 100) AND (ft2.c2 = 6)) + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) +(7 rows) + +-- This should not be pushed either. +explain (verbose, costs off) +select * from ft2 order by c1 using operator(public.<^); + QUERY PLAN +------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Sort Key: ft2.c1 USING <^ + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(6 rows) + +-- Update local stats on ft2 +ANALYZE ft2; +-- Add into extension +alter extension postgres_fdw add operator class my_op_class using btree; +alter extension postgres_fdw add function my_op_cmp(a int, b int); +alter extension postgres_fdw add operator family my_op_family using btree; +alter extension postgres_fdw add operator public.<^(int, int); +alter extension postgres_fdw add operator public.=^(int, int); +alter extension postgres_fdw add operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); +-- Now this will be pushed as sort operator is part of the extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + QUERY PLAN +-------------------------------------------------------------------------------------------- + GroupAggregate + Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2 + Group Key: ft2.c2 + -> Foreign Scan on public.ft2 + Output: c1, c2 + Remote Filter: ((ft2.c1 < 100) AND (ft2.c2 = 6)) + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) +(7 rows) + +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + array_agg +-------------------------------- + {6,16,26,36,46,56,66,76,86,96} +(1 row) + +-- This should be pushed too. +explain (verbose, costs off) +select * from ft2 order by c1 using operator(public.<^); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY "C 1" USING OPERATOR(public.<^) NULLS LAST +(3 rows) + +-- Remove from extension +alter extension postgres_fdw drop operator class my_op_class using btree; +alter extension postgres_fdw drop function my_op_cmp(a int, b int); +alter extension postgres_fdw drop operator family my_op_family using btree; +alter extension postgres_fdw drop operator public.<^(int, int); +alter extension postgres_fdw drop operator public.=^(int, int); +alter extension postgres_fdw drop operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); +-- This will not be pushed as sort operator is now removed from the extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + QUERY PLAN +-------------------------------------------------------------------------------------------- + GroupAggregate + Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2 + Group Key: ft2.c2 + -> Foreign Scan on public.ft2 + Output: c1, c2 + Remote Filter: ((ft2.c1 < 100) AND (ft2.c2 = 6)) + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) +(7 rows) + +-- Cleanup +drop operator class my_op_class using btree; +drop function my_op_cmp(a int, b int); +drop operator family my_op_family using btree; +drop operator public.>^(int, int); +drop operator public.=^(int, int); +drop operator public.<^(int, int); +-- Input relation to aggregate push down hook is not safe to pushdown and thus +-- the aggregate cannot be pushed down to foreign server. +explain (verbose, costs off) +select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2); + QUERY PLAN +------------------------------------------------------------------------------------------- + Aggregate + Output: count(t1.c3) + -> Nested Loop Left Join + Output: t1.c3 + Join Filter: ((t1.c1)::double precision = (random() * (t2.c2)::double precision)) + -> Foreign Scan on public.ft2 t1 + Output: t1.c3, t1.c1 + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" + -> Materialize + Output: t2.c2 + -> Foreign Scan on public.ft2 t2 + Output: t2.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" +(13 rows) + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Sort + Output: (count(*)), x.b + Sort Key: (count(*)), x.b + -> HashAggregate + Output: count(*), x.b + Group Key: x.b + -> Hash Join + Output: x.b + Inner Unique: true + Hash Cond: (ft1.c2 = x.a) + -> Foreign Scan on public.ft1 + Output: ft1.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + -> Hash + Output: x.b, x.a + -> Subquery Scan on x + Output: x.b, x.a + -> Foreign Scan + Output: ft1_1.c2, (sum(ft1_1.c1)) + Relations: Aggregate on (public.ft1 ft1_1) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 +(21 rows) + +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + count | b +-------+------- + 100 | 49600 + 100 | 49700 + 100 | 49800 + 100 | 49900 + 100 | 50000 + 100 | 50100 + 100 | 50200 + 100 | 50300 + 100 | 50400 + 100 | 50500 +(10 rows) + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT avg(r1.c1), sum(r2.c1), r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) GROUP BY 3 HAVING ((((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL))) ORDER BY avg(r1.c1) ASC NULLS LAST, sum(r2.c1) ASC NULLS LAST +(4 rows) + +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + avg | sum +---------------------+----- + 51.0000000000000000 | + | 3 + | 9 +(3 rows) + +-- Aggregate over FULL join needing to deparse the joining relations as +-- subqueries. +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)), (sum(ft4.c1)), (avg(ft5.c1)) + Relations: Aggregate on ((public.ft4) FULL JOIN (public.ft5)) + Remote SQL: SELECT count(*), sum(s4.c1), avg(s5.c1) FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) +(4 rows) + +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + count | sum | avg +-------+-----+--------------------- + 8 | 330 | 55.5000000000000000 +(1 row) + +-- ORDER BY expression is part of the target list but not pushed down to +-- foreign server. +explain (verbose, costs off) +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + QUERY PLAN +-------------------------------------------------------------------------------- + Sort + Output: (((sum(c2)) * ((random() <= '1'::double precision))::integer)) + Sort Key: (((sum(ft1.c2)) * ((random() <= '1'::double precision))::integer)) + -> Foreign Scan + Output: ((sum(c2)) * ((random() <= '1'::double precision))::integer) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum(c2) FROM "S 1"."T 1" +(7 rows) + +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + sum +------ + 4500 +(1 row) + +-- LATERAL join, with parameterization +set enable_hashagg to false; +/* YB note: ERROR: FETCH BACKWARD not supported yet, see issue #11422 +explain (verbose, costs off) +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1; +*/ -- YB +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1; + c2 | sum +----+----- + 1 | 2 + 2 | 4 +(2 rows) + +reset enable_hashagg; +-- bug #15613: bad plan for foreign table scan with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T 1" AS ref_0, + LATERAL ( + SELECT ref_0."C 1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C 1"; + QUERY PLAN +----------------------------------------------------------------------------- + Nested Loop + Output: ref_0.c2, ref_0."C 1", (ref_0.c2), ref_1.c3, ref_0."C 1" + -> Nested Loop + Output: ref_0.c2, ref_0."C 1", ref_1.c3, (ref_0.c2) + -> Index Scan using t1_pkey on "S 1"."T 1" ref_0 + Output: ref_0.c2, ref_0."C 1" + Index Cond: (ref_0."C 1" < 10) + -> Foreign Scan on public.ft1 ref_1 + Output: ref_1.c3, ref_0.c2 + Remote Filter: (ref_1.c3 = '00001'::text) + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001')) + -> Foreign Scan on public.ft2 ref_3 + Output: ref_3.c3 + Remote Filter: (ref_3.c3 = '00001'::text) + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001')) +(15 rows) + +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T 1" AS ref_0, + LATERAL ( + SELECT ref_0."C 1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C 1"; + c2 | c1 | c2 | c3 +----+----+----+------- + 1 | 1 | 1 | 00001 + 2 | 2 | 2 | 00001 + 3 | 3 | 3 | 00001 + 4 | 4 | 4 | 00001 + 5 | 5 | 5 | 00001 + 6 | 6 | 6 | 00001 + 7 | 7 | 7 | 00001 + 8 | 8 | 8 | 00001 + 9 | 9 | 9 | 00001 +(9 rows) + +-- Check with placeHolderVars +explain (verbose, costs off) +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: sum(q.a), count(q.b) + -> Nested Loop Left Join + Output: q.a, q.b + Inner Unique: true + Join Filter: ((ft4.c1)::numeric <= q.b) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Materialize + Output: q.a, q.b + -> Subquery Scan on q + Output: q.a, q.b + -> Foreign Scan + Output: 13, (avg(ft1.c1)), NULL::bigint + Relations: Aggregate on ((public.ft2) LEFT JOIN (public.ft1)) + Remote SQL: SELECT 13, avg(r1."C 1"), NULL::bigint FROM ("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) +(17 rows) + +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + sum | count +-----+------- + 650 | 50 +(1 row) + +-- Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)) + Sort Key: ft1.c2 + -> MixedAggregate + Output: c2, sum(c1) + Hash Key: ft1.c2 + Group Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote Filter: (ft1.c2 < 3) + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) +(11 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + c2 | sum +----+-------- + 0 | 50500 + 1 | 49600 + 2 | 49700 + | 149800 +(4 rows) + +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)) + Sort Key: ft1.c2 + -> MixedAggregate + Output: c2, sum(c1) + Hash Key: ft1.c2 + Group Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote Filter: (ft1.c2 < 3) + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) +(11 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + c2 | sum +----+-------- + 0 | 50500 + 1 | 49600 + 2 | 49700 + | 149800 +(4 rows) + +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + QUERY PLAN +---------------------------------------------------------------------------------- + Sort + Output: c2, c6, (sum(c1)) + Sort Key: ft1.c2, ft1.c6 + -> HashAggregate + Output: c2, c6, sum(c1) + Hash Key: ft1.c2 + Hash Key: ft1.c6 + -> Foreign Scan on public.ft1 + Output: c2, c6, c1 + Remote Filter: (ft1.c2 < 3) + Remote SQL: SELECT "C 1", c2, c6 FROM "S 1"."T 1" WHERE ((c2 < 3)) +(11 rows) + +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + c2 | c6 | sum +----+----+------- + 0 | | 50500 + 1 | | 49600 + 2 | | 49700 + | 0 | 50500 + | 1 | 49600 + | 2 | 49700 +(6 rows) + +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)), (GROUPING(c2)) + Sort Key: ft1.c2 + -> HashAggregate + Output: c2, sum(c1), GROUPING(c2) + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote Filter: (ft1.c2 < 3) + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) +(10 rows) + +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + c2 | sum | grouping +----+-------+---------- + 0 | 50500 | 0 + 1 | 49600 | 0 + 2 | 49700 | 0 +(3 rows) + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Unique + Output: ((sum(c1) / 1000)), c2 + -> Sort + Output: ((sum(c1) / 1000)), c2 + Sort Key: ((sum(ft2.c1) / 1000)) + -> Foreign Scan + Output: ((sum(c1) / 1000)), c2 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT (sum("C 1") / 1000), c2 FROM "S 1"."T 1" WHERE ((c2 < 6)) GROUP BY 2 +(9 rows) + +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + s +---- + 49 + 50 +(2 rows) + +-- WindowAgg +explain (verbose, costs off) +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c2)), (count(c2) OVER (?)), ((c2 % 2)) + Sort Key: ft2.c2 + -> WindowAgg + Output: c2, (sum(c2)), count(c2) OVER (?), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)), (sum(c2)) + Sort Key: ((ft2.c2 % 2)) + -> Foreign Scan + Output: c2, ((c2 % 2)), (sum(c2)) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT c2, (c2 % 2), sum(c2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1 +(12 rows) + +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + c2 | sum | count +----+-----+------- + 0 | 0 | 5 + 1 | 100 | 5 + 2 | 200 | 5 + 3 | 300 | 5 + 4 | 400 | 5 + 5 | 500 | 5 + 6 | 600 | 5 + 7 | 700 | 5 + 8 | 800 | 5 + 9 | 900 | 5 +(10 rows) + +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Sort + Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 DESC + -> Foreign Scan + Output: c2, ((c2 % 2)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, (c2 % 2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1 +(12 rows) + +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; + c2 | array_agg +----+------------- + 0 | {8,6,4,2,0} + 1 | {9,7,5,3,1} + 2 | {8,6,4,2} + 3 | {9,7,5,3} + 4 | {8,6,4} + 5 | {9,7,5} + 6 | {8,6} + 7 | {9,7} + 8 | {8} + 9 | {9} +(10 rows) + +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Sort + Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 + -> Foreign Scan + Output: c2, ((c2 % 2)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, (c2 % 2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1 +(12 rows) + +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + c2 | array_agg +----+------------- + 0 | {0,2,4,6,8} + 1 | {1,3,5,7,9} + 2 | {2,4,6,8} + 3 | {3,5,7,9} + 4 | {4,6,8} + 5 | {5,7,9} + 6 | {6,8} + 7 | {7,9} + 8 | {8} + 9 | {9} +(10 rows) + +-- =================================================================== +-- parameterized queries +-- =================================================================== +-- simple join +PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c3, t2.c3 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = 2)) AND ((r1."C 1" = 1)))) +(4 rows) + +EXECUTE st1(1, 1); + c3 | c3 +-------+------- + 00001 | 00001 +(1 row) + +EXECUTE st1(101, 101); + c3 | c3 +-------+------- + 00101 | 00101 +(1 row) + +-- subquery using stable function (can't be sent to remote) +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st2(10, 20); + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Sort + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Sort Key: t1.c1 + -> Nested Loop Semi Join + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Join Filter: (t1.c3 = t2.c3) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Remote Filter: (t1.c1 < 20) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 20)) + -> Materialize + Output: t2.c3 + -> Foreign Scan on public.ft2 t2 + Output: t2.c3 + Filter: (date(t2.c4) = '01-17-1970'::date) + Remote Filter: (t2.c1 > 10) + Remote SQL: SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > 10)) +(17 rows) + +EXECUTE st2(10, 20); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +EXECUTE st2(101, 121); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +-- subquery using immutable function (can be sent to remote) +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st3(10, 20); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Sort + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Sort Key: t1.c1 + -> Nested Loop Semi Join + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Join Filter: (t1.c3 = t2.c3) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Remote Filter: (t1.c1 < 20) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 20)) + -> Materialize + Output: t2.c3 + -> Foreign Scan on public.ft2 t2 + Output: t2.c3 + Remote Filter: ((t2.c1 > 10) AND (date(t2.c5) = '01-17-1970'::date)) + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > 10)) AND ((date(c5) = '1970-01-17'::date)) +(16 rows) + +EXECUTE st3(10, 20); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +EXECUTE st3(20, 30); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+----+----+----+----+----+---- +(0 rows) + +-- custom plan should be chosen initially +PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = 1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = 1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = 1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = 1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = 1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(4 rows) + +-- once we try it enough times, should switch to generic plan +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = $1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) +(4 rows) + +-- value of $1 should not be sent to remote +PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote Filter: (t1.c1 = 1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote Filter: (t1.c1 = 1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote Filter: (t1.c1 = 1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote Filter: (t1.c1 = 1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote Filter: (t1.c1 = 1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Remote Filter: (t1.c1 = $2) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) +(5 rows) + +EXECUTE st5('foo', 1); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- altering FDW options requires replanning +PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = t1.c2) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = c2)) +(4 rows) + +PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft1 + Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + Batch Size: 1 + -> Result *RESULT* + Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft1 '::character(10), NULL::user_enum +(5 rows) + +ALTER TABLE "S 1"."T 1" RENAME TO "T 0"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (t1.c1 = t1.c2) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 0" WHERE (("C 1" = c2)) +(4 rows) + +EXECUTE st6; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo +(9 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft1 + Remote SQL: INSERT INTO "S 1"."T 0"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + Batch Size: 1 + -> Result *RESULT* + Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft1 '::character(10), NULL::user_enum +(5 rows) + +ALTER TABLE "S 1"."T 0" RENAME TO "T 1"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1'); +PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN +----------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2)) +(4 rows) + +ALTER SERVER loopback OPTIONS (DROP extensions); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN +----------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" +(6 rows) + +EXECUTE st8; + count +------- + 9 +(1 row) + +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +-- cleanup +DEALLOCATE st1; +DEALLOCATE st2; +DEALLOCATE st3; +DEALLOCATE st4; +DEALLOCATE st5; +DEALLOCATE st6; +DEALLOCATE st7; +DEALLOCATE st8; +-- System columns, except ctid and oid, should not be sent to remote +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1; + QUERY PLAN +------------------------------------------------------------------------------- + Limit + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.tableoid = '1259'::oid) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(6 rows) + +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; + QUERY PLAN +----------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: (tableoid)::regclass, c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" LIMIT 1::bigint +(3 rows) + +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; + tableoid | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----------+----+----+-------+------------------------------+--------------------------+----+------------+----- + ft1 | 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- YB note: YugabyteDB uses ybctid as the tuple identifier, not ctid +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.ybctid = '\x488000000221'; -- YB note: see above + QUERY PLAN +--------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.ybctid = '\x488000000221'::bytea) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ybctid FROM "S 1"."T 1" +(4 rows) + +SELECT * FROM ft1 t1 WHERE t1.ybctid = '\x488000000221'; -- YB note: see above + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo +(1 row) + +-- YB note: YugabyteDB uses ybctid as the tuple identifier, not ctid +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ybctid, * FROM ft1 t1 LIMIT 1; -- YB note: see above + QUERY PLAN +------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: ybctid, c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ybctid FROM "S 1"."T 1" LIMIT 1::bigint +(3 rows) + +SELECT ybctid, * FROM ft1 t1 LIMIT 1; -- YB note: see above + ybctid | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----------------+----+----+-------+------------------------------+--------------------------+----+------------+----- + \x488000000121 | 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- =================================================================== +-- used in PL/pgSQL function +-- =================================================================== +CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$ +DECLARE + v_c1 int; +BEGIN + SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1; + PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1; + RETURN v_c1; +END; +$$ LANGUAGE plpgsql; +SELECT f_test(100); + f_test +-------- + 100 +(1 row) + +DROP FUNCTION f_test(int); +-- =================================================================== +-- REINDEX +-- =================================================================== +-- remote table is not created here +CREATE FOREIGN TABLE reindex_foreign (c1 int, c2 int) + SERVER loopback2 OPTIONS (table_name 'reindex_local'); +REINDEX TABLE reindex_foreign; -- error +ERROR: REINDEX TABLE not supported yet +LINE 1: REINDEX TABLE reindex_foreign; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +REINDEX TABLE CONCURRENTLY reindex_foreign; -- error +ERROR: REINDEX CONCURRENTLY not supported yet +LINE 1: REINDEX TABLE CONCURRENTLY reindex_foreign; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +DROP FOREIGN TABLE reindex_foreign; +-- partitions and foreign tables +CREATE TABLE reind_fdw_parent (c1 int) PARTITION BY RANGE (c1); +CREATE TABLE reind_fdw_0_10 PARTITION OF reind_fdw_parent + FOR VALUES FROM (0) TO (10); +CREATE FOREIGN TABLE reind_fdw_10_20 PARTITION OF reind_fdw_parent + FOR VALUES FROM (10) TO (20) + SERVER loopback OPTIONS (table_name 'reind_local_10_20'); +REINDEX TABLE reind_fdw_parent; -- ok +ERROR: REINDEX TABLE not supported yet +LINE 1: REINDEX TABLE reind_fdw_parent; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +REINDEX TABLE CONCURRENTLY reind_fdw_parent; -- ok +ERROR: REINDEX CONCURRENTLY not supported yet +LINE 1: REINDEX TABLE CONCURRENTLY reind_fdw_parent; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +DROP TABLE reind_fdw_parent; +-- =================================================================== +-- conversion error +-- =================================================================== +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int; +SELECT * FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8) WHERE x1 = 1; -- ERROR +ERROR: invalid input syntax for type integer: "foo" +CONTEXT: column "x8" of foreign table "ftx" +SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +ERROR: invalid input syntax for type integer: "foo" +CONTEXT: column "x8" of foreign table "ftx" +SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +ERROR: invalid input syntax for type integer: "foo" +CONTEXT: whole-row reference to foreign table "ftx" +SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR +ERROR: invalid input syntax for type integer: "foo" +CONTEXT: processing expression at position 2 in select list +ANALYZE ft1; -- ERROR +ERROR: invalid input syntax for type integer: "foo" +CONTEXT: column "c8" of foreign table "ft1" +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; +-- =================================================================== +-- local type can be different from remote type in some cases, +-- in particular if similarly-named operators do equivalent things +-- =================================================================== +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE text; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: (ft1.c8 = 'foo'::text) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c8 = 'foo')) LIMIT 1::bigint +(4 rows) + +SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE 'foo' = c8 LIMIT 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote Filter: ('foo'::text = ft1.c8) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (('foo' = c8)) LIMIT 1::bigint +(4 rows) + +SELECT * FROM ft1 WHERE 'foo' = c8 LIMIT 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- we declared c8 to be text locally, but it's still the same type on +-- the remote which will balk if we try to do anything incompatible +-- with that remote type +SELECT * FROM ft1 WHERE c8 LIKE 'foo' LIMIT 1; -- ERROR +ERROR: operator does not exist: public.user_enum ~~ unknown +HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c8 ~~ 'foo')) LIMIT 1::bigint +SELECT * FROM ft1 WHERE c8::text LIKE 'foo' LIMIT 1; -- ERROR; cast not pushed down +ERROR: operator does not exist: public.user_enum ~~ unknown +HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c8 ~~ 'foo')) LIMIT 1::bigint +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; +-- =================================================================== +-- subtransaction +-- + local/remote error doesn't break cursor +-- =================================================================== +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +FETCH c; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +SAVEPOINT s; +ERROR OUT; -- ERROR +ERROR: syntax error at or near "ERROR" +LINE 1: ERROR OUT; + ^ +ROLLBACK TO s; +FETCH c; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo +(1 row) + +SAVEPOINT s; +SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0; -- ERROR +ERROR: division by zero +CONTEXT: remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((1 / ("C 1" - 1)) > 0)) +ROLLBACK TO s; +FETCH c; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo +(1 row) + +SELECT * FROM ft1 ORDER BY c1 LIMIT 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +COMMIT; +-- =================================================================== +-- test handling of collations +-- =================================================================== +create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique); +create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10)) + server loopback options (table_name 'loct3', use_remote_estimate 'true'); +-- can be sent to remote +explain (verbose, costs off) select * from ft3 where f1 = 'foo'; + QUERY PLAN +------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Remote Filter: (ft3.f1 = 'foo'::text) + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo')) +(4 rows) + +explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo'; + QUERY PLAN +------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Remote Filter: (ft3.f1 = 'foo'::text) + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo')) +(4 rows) + +explain (verbose, costs off) select * from ft3 where f2 = 'foo'; + QUERY PLAN +------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Remote Filter: (ft3.f2 = 'foo'::text) + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f2 = 'foo')) +(4 rows) + +explain (verbose, costs off) select * from ft3 where f3 = 'foo'; + QUERY PLAN +------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Remote Filter: ((ft3.f3)::text = 'foo'::text) + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f3 = 'foo')) +(4 rows) + +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 and l.f1 = 'foo'; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Nested Loop + Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3 + -> Index Scan using loct3_f1_key on public.loct3 l + Output: l.f1, l.f2, l.f3 + Index Cond: (l.f1 = 'foo'::text) + -> Foreign Scan on public.ft3 f + Output: f.f1, f.f2, f.f3 + Remote Filter: ((l.f3)::text = (f.f3)::text) + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE (($1::character varying(10) = f3)) +(9 rows) + +-- can't be sent to remote +explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo'; + QUERY PLAN +--------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: ((ft3.f1)::text = 'foo'::text) + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 +(4 rows) + +explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C"; + QUERY PLAN +--------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: (ft3.f1 = 'foo'::text COLLATE "C") + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 +(4 rows) + +explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo'; + QUERY PLAN +--------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: ((ft3.f2)::text = 'foo'::text) + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 +(4 rows) + +explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C"; + QUERY PLAN +--------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: (ft3.f2 = 'foo'::text COLLATE "C") + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 +(4 rows) + +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo'; + QUERY PLAN +------------------------------------------------------------- + Hash Join + Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3 + Inner Unique: true + Hash Cond: ((f.f3)::text = (l.f3)::text) + -> Foreign Scan on public.ft3 f + Output: f.f1, f.f2, f.f3 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 + -> Hash + Output: l.f1, l.f2, l.f3 + -> Index Scan using loct3_f1_key on public.loct3 l + Output: l.f1, l.f2, l.f3 + Index Cond: (l.f1 = 'foo'::text) +(12 rows) + +-- =================================================================== +-- test writable foreign table stuff +-- =================================================================== +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Insert on public.ft2 + Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + Batch Size: 1 + -> Subquery Scan on "*SELECT*" + Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1", NULL::integer, "*SELECT*"."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft2 '::character(10), NULL::user_enum + -> Foreign Scan on public.ft2 ft2_1 + Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3) + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint +(8 rows) + +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) + VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----+----+----+----+------------+---- + 1101 | 201 | aaa | | | | ft2 | + 1102 | 202 | bbb | | | | ft2 | + 1103 | 203 | ccc | | | | ft2 | +(3 rows) + +INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + -> Foreign Update on public.ft2 + Remote Filter: ((ft2.c1 % 10) = 3) + Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 300), c3 = (c3 || '_update3') WHERE ((("C 1" % 10) = 3)) +(4 rows) + +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Update on public.ft2 + Remote Filter: ((ft2.c1 % 10) = 7) + Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 400), c3 = (c3 || '_update7') WHERE ((("C 1" % 10) = 7)) RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8, ybctid +(5 rows) + +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+--------------------+------------------------------+--------------------------+----+------------+----- + 7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 17 | 407 | 00017_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 27 | 407 | 00027_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 37 | 407 | 00037_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 47 | 407 | 00047_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 57 | 407 | 00057_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 67 | 407 | 00067_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 77 | 407 | 00077_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 87 | 407 | 00087_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 97 | 407 | 00097_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 107 | 407 | 00107_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 117 | 407 | 00117_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 127 | 407 | 00127_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 137 | 407 | 00137_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 147 | 407 | 00147_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 157 | 407 | 00157_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 167 | 407 | 00167_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 177 | 407 | 00177_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 187 | 407 | 00187_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 197 | 407 | 00197_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 207 | 407 | 00207_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 217 | 407 | 00217_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 227 | 407 | 00227_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 237 | 407 | 00237_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 247 | 407 | 00247_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 257 | 407 | 00257_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 267 | 407 | 00267_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 277 | 407 | 00277_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 287 | 407 | 00287_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 297 | 407 | 00297_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 307 | 407 | 00307_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 317 | 407 | 00317_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 327 | 407 | 00327_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 337 | 407 | 00337_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 347 | 407 | 00347_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 357 | 407 | 00357_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 367 | 407 | 00367_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 377 | 407 | 00377_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 387 | 407 | 00387_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 397 | 407 | 00397_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 407 | 407 | 00407_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 417 | 407 | 00417_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 427 | 407 | 00427_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 437 | 407 | 00437_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 447 | 407 | 00447_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 457 | 407 | 00457_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 467 | 407 | 00467_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 477 | 407 | 00477_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 487 | 407 | 00487_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 497 | 407 | 00497_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 507 | 407 | 00507_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 517 | 407 | 00517_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 527 | 407 | 00527_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 537 | 407 | 00537_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 547 | 407 | 00547_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 557 | 407 | 00557_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 567 | 407 | 00567_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 577 | 407 | 00577_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 587 | 407 | 00587_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 597 | 407 | 00597_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 607 | 407 | 00607_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 617 | 407 | 00617_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 627 | 407 | 00627_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 637 | 407 | 00637_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 647 | 407 | 00647_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 657 | 407 | 00657_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 667 | 407 | 00667_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 677 | 407 | 00677_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 687 | 407 | 00687_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 697 | 407 | 00697_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 707 | 407 | 00707_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 717 | 407 | 00717_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 727 | 407 | 00727_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 737 | 407 | 00737_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 747 | 407 | 00747_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 757 | 407 | 00757_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 767 | 407 | 00767_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 777 | 407 | 00777_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 787 | 407 | 00787_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 797 | 407 | 00797_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 807 | 407 | 00807_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 817 | 407 | 00817_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 827 | 407 | 00827_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 837 | 407 | 00837_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 847 | 407 | 00847_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 857 | 407 | 00857_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 867 | 407 | 00867_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 877 | 407 | 00877_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 887 | 407 | 00887_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 897 | 407 | 00897_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 907 | 407 | 00907_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 917 | 407 | 00917_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 927 | 407 | 00927_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 937 | 407 | 00937_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 947 | 407 | 00947_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 957 | 407 | 00957_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 967 | 407 | 00967_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 977 | 407 | 00977_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 987 | 407 | 00987_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 997 | 407 | 00997_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 1007 | 507 | 0000700007_update7 | | | | ft2 | + 1017 | 507 | 0001700017_update7 | | | | ft2 | +(102 rows) + +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + -> Foreign Update + Remote SQL: UPDATE "S 1"."T 1" r1 SET c2 = (r1.c2 + 500), c3 = (r1.c3 || '_update9'), c7 = 'ft2 '::character(10) FROM "S 1"."T 1" r2 WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 9)) +(3 rows) + +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +EXPLAIN (verbose, costs off) + DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Delete on public.ft2 + Output: c1, c4 + -> Foreign Delete on public.ft2 + Remote Filter: ((ft2.c1 % 10) = 5) + Remote SQL: DELETE FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) RETURNING "C 1", c4, ybctid +(5 rows) + +DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; + c1 | c4 +------+------------------------------ + 5 | Tue Jan 06 00:00:00 1970 PST + 15 | Fri Jan 16 00:00:00 1970 PST + 25 | Mon Jan 26 00:00:00 1970 PST + 35 | Thu Feb 05 00:00:00 1970 PST + 45 | Sun Feb 15 00:00:00 1970 PST + 55 | Wed Feb 25 00:00:00 1970 PST + 65 | Sat Mar 07 00:00:00 1970 PST + 75 | Tue Mar 17 00:00:00 1970 PST + 85 | Fri Mar 27 00:00:00 1970 PST + 95 | Mon Apr 06 00:00:00 1970 PST + 105 | Tue Jan 06 00:00:00 1970 PST + 115 | Fri Jan 16 00:00:00 1970 PST + 125 | Mon Jan 26 00:00:00 1970 PST + 135 | Thu Feb 05 00:00:00 1970 PST + 145 | Sun Feb 15 00:00:00 1970 PST + 155 | Wed Feb 25 00:00:00 1970 PST + 165 | Sat Mar 07 00:00:00 1970 PST + 175 | Tue Mar 17 00:00:00 1970 PST + 185 | Fri Mar 27 00:00:00 1970 PST + 195 | Mon Apr 06 00:00:00 1970 PST + 205 | Tue Jan 06 00:00:00 1970 PST + 215 | Fri Jan 16 00:00:00 1970 PST + 225 | Mon Jan 26 00:00:00 1970 PST + 235 | Thu Feb 05 00:00:00 1970 PST + 245 | Sun Feb 15 00:00:00 1970 PST + 255 | Wed Feb 25 00:00:00 1970 PST + 265 | Sat Mar 07 00:00:00 1970 PST + 275 | Tue Mar 17 00:00:00 1970 PST + 285 | Fri Mar 27 00:00:00 1970 PST + 295 | Mon Apr 06 00:00:00 1970 PST + 305 | Tue Jan 06 00:00:00 1970 PST + 315 | Fri Jan 16 00:00:00 1970 PST + 325 | Mon Jan 26 00:00:00 1970 PST + 335 | Thu Feb 05 00:00:00 1970 PST + 345 | Sun Feb 15 00:00:00 1970 PST + 355 | Wed Feb 25 00:00:00 1970 PST + 365 | Sat Mar 07 00:00:00 1970 PST + 375 | Tue Mar 17 00:00:00 1970 PST + 385 | Fri Mar 27 00:00:00 1970 PST + 395 | Mon Apr 06 00:00:00 1970 PST + 405 | Tue Jan 06 00:00:00 1970 PST + 415 | Fri Jan 16 00:00:00 1970 PST + 425 | Mon Jan 26 00:00:00 1970 PST + 435 | Thu Feb 05 00:00:00 1970 PST + 445 | Sun Feb 15 00:00:00 1970 PST + 455 | Wed Feb 25 00:00:00 1970 PST + 465 | Sat Mar 07 00:00:00 1970 PST + 475 | Tue Mar 17 00:00:00 1970 PST + 485 | Fri Mar 27 00:00:00 1970 PST + 495 | Mon Apr 06 00:00:00 1970 PST + 505 | Tue Jan 06 00:00:00 1970 PST + 515 | Fri Jan 16 00:00:00 1970 PST + 525 | Mon Jan 26 00:00:00 1970 PST + 535 | Thu Feb 05 00:00:00 1970 PST + 545 | Sun Feb 15 00:00:00 1970 PST + 555 | Wed Feb 25 00:00:00 1970 PST + 565 | Sat Mar 07 00:00:00 1970 PST + 575 | Tue Mar 17 00:00:00 1970 PST + 585 | Fri Mar 27 00:00:00 1970 PST + 595 | Mon Apr 06 00:00:00 1970 PST + 605 | Tue Jan 06 00:00:00 1970 PST + 615 | Fri Jan 16 00:00:00 1970 PST + 625 | Mon Jan 26 00:00:00 1970 PST + 635 | Thu Feb 05 00:00:00 1970 PST + 645 | Sun Feb 15 00:00:00 1970 PST + 655 | Wed Feb 25 00:00:00 1970 PST + 665 | Sat Mar 07 00:00:00 1970 PST + 675 | Tue Mar 17 00:00:00 1970 PST + 685 | Fri Mar 27 00:00:00 1970 PST + 695 | Mon Apr 06 00:00:00 1970 PST + 705 | Tue Jan 06 00:00:00 1970 PST + 715 | Fri Jan 16 00:00:00 1970 PST + 725 | Mon Jan 26 00:00:00 1970 PST + 735 | Thu Feb 05 00:00:00 1970 PST + 745 | Sun Feb 15 00:00:00 1970 PST + 755 | Wed Feb 25 00:00:00 1970 PST + 765 | Sat Mar 07 00:00:00 1970 PST + 775 | Tue Mar 17 00:00:00 1970 PST + 785 | Fri Mar 27 00:00:00 1970 PST + 795 | Mon Apr 06 00:00:00 1970 PST + 805 | Tue Jan 06 00:00:00 1970 PST + 815 | Fri Jan 16 00:00:00 1970 PST + 825 | Mon Jan 26 00:00:00 1970 PST + 835 | Thu Feb 05 00:00:00 1970 PST + 845 | Sun Feb 15 00:00:00 1970 PST + 855 | Wed Feb 25 00:00:00 1970 PST + 865 | Sat Mar 07 00:00:00 1970 PST + 875 | Tue Mar 17 00:00:00 1970 PST + 885 | Fri Mar 27 00:00:00 1970 PST + 895 | Mon Apr 06 00:00:00 1970 PST + 905 | Tue Jan 06 00:00:00 1970 PST + 915 | Fri Jan 16 00:00:00 1970 PST + 925 | Mon Jan 26 00:00:00 1970 PST + 935 | Thu Feb 05 00:00:00 1970 PST + 945 | Sun Feb 15 00:00:00 1970 PST + 955 | Wed Feb 25 00:00:00 1970 PST + 965 | Sat Mar 07 00:00:00 1970 PST + 975 | Tue Mar 17 00:00:00 1970 PST + 985 | Fri Mar 27 00:00:00 1970 PST + 995 | Mon Apr 06 00:00:00 1970 PST + 1005 | + 1015 | + 1105 | +(103 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Delete on public.ft2 + -> Foreign Delete + Remote SQL: DELETE FROM "S 1"."T 1" r1 USING "S 1"."T 1" r2 WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 2)) +(3 rows) + +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; + c1 | c2 | c3 | c4 +------+-----+--------------------+------------------------------ + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST + 3 | 303 | 00003_update3 | Sun Jan 04 00:00:00 1970 PST + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST + 7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST + 9 | 509 | 00009_update9 | Sat Jan 10 00:00:00 1970 PST + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST + 11 | 1 | 00011 | Mon Jan 12 00:00:00 1970 PST + 13 | 303 | 00013_update3 | Wed Jan 14 00:00:00 1970 PST + 14 | 4 | 00014 | Thu Jan 15 00:00:00 1970 PST + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST + 17 | 407 | 00017_update7 | Sun Jan 18 00:00:00 1970 PST + 18 | 8 | 00018 | Mon Jan 19 00:00:00 1970 PST + 19 | 509 | 00019_update9 | Tue Jan 20 00:00:00 1970 PST + 20 | 0 | 00020 | Wed Jan 21 00:00:00 1970 PST + 21 | 1 | 00021 | Thu Jan 22 00:00:00 1970 PST + 23 | 303 | 00023_update3 | Sat Jan 24 00:00:00 1970 PST + 24 | 4 | 00024 | Sun Jan 25 00:00:00 1970 PST + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST + 27 | 407 | 00027_update7 | Wed Jan 28 00:00:00 1970 PST + 28 | 8 | 00028 | Thu Jan 29 00:00:00 1970 PST + 29 | 509 | 00029_update9 | Fri Jan 30 00:00:00 1970 PST + 30 | 0 | 00030 | Sat Jan 31 00:00:00 1970 PST + 31 | 1 | 00031 | Sun Feb 01 00:00:00 1970 PST + 33 | 303 | 00033_update3 | Tue Feb 03 00:00:00 1970 PST + 34 | 4 | 00034 | Wed Feb 04 00:00:00 1970 PST + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST + 37 | 407 | 00037_update7 | Sat Feb 07 00:00:00 1970 PST + 38 | 8 | 00038 | Sun Feb 08 00:00:00 1970 PST + 39 | 509 | 00039_update9 | Mon Feb 09 00:00:00 1970 PST + 40 | 0 | 00040 | Tue Feb 10 00:00:00 1970 PST + 41 | 1 | 00041 | Wed Feb 11 00:00:00 1970 PST + 43 | 303 | 00043_update3 | Fri Feb 13 00:00:00 1970 PST + 44 | 4 | 00044 | Sat Feb 14 00:00:00 1970 PST + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST + 47 | 407 | 00047_update7 | Tue Feb 17 00:00:00 1970 PST + 48 | 8 | 00048 | Wed Feb 18 00:00:00 1970 PST + 49 | 509 | 00049_update9 | Thu Feb 19 00:00:00 1970 PST + 50 | 0 | 00050 | Fri Feb 20 00:00:00 1970 PST + 51 | 1 | 00051 | Sat Feb 21 00:00:00 1970 PST + 53 | 303 | 00053_update3 | Mon Feb 23 00:00:00 1970 PST + 54 | 4 | 00054 | Tue Feb 24 00:00:00 1970 PST + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST + 57 | 407 | 00057_update7 | Fri Feb 27 00:00:00 1970 PST + 58 | 8 | 00058 | Sat Feb 28 00:00:00 1970 PST + 59 | 509 | 00059_update9 | Sun Mar 01 00:00:00 1970 PST + 60 | 0 | 00060 | Mon Mar 02 00:00:00 1970 PST + 61 | 1 | 00061 | Tue Mar 03 00:00:00 1970 PST + 63 | 303 | 00063_update3 | Thu Mar 05 00:00:00 1970 PST + 64 | 4 | 00064 | Fri Mar 06 00:00:00 1970 PST + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST + 67 | 407 | 00067_update7 | Mon Mar 09 00:00:00 1970 PST + 68 | 8 | 00068 | Tue Mar 10 00:00:00 1970 PST + 69 | 509 | 00069_update9 | Wed Mar 11 00:00:00 1970 PST + 70 | 0 | 00070 | Thu Mar 12 00:00:00 1970 PST + 71 | 1 | 00071 | Fri Mar 13 00:00:00 1970 PST + 73 | 303 | 00073_update3 | Sun Mar 15 00:00:00 1970 PST + 74 | 4 | 00074 | Mon Mar 16 00:00:00 1970 PST + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST + 77 | 407 | 00077_update7 | Thu Mar 19 00:00:00 1970 PST + 78 | 8 | 00078 | Fri Mar 20 00:00:00 1970 PST + 79 | 509 | 00079_update9 | Sat Mar 21 00:00:00 1970 PST + 80 | 0 | 00080 | Sun Mar 22 00:00:00 1970 PST + 81 | 1 | 00081 | Mon Mar 23 00:00:00 1970 PST + 83 | 303 | 00083_update3 | Wed Mar 25 00:00:00 1970 PST + 84 | 4 | 00084 | Thu Mar 26 00:00:00 1970 PST + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST + 87 | 407 | 00087_update7 | Sun Mar 29 00:00:00 1970 PST + 88 | 8 | 00088 | Mon Mar 30 00:00:00 1970 PST + 89 | 509 | 00089_update9 | Tue Mar 31 00:00:00 1970 PST + 90 | 0 | 00090 | Wed Apr 01 00:00:00 1970 PST + 91 | 1 | 00091 | Thu Apr 02 00:00:00 1970 PST + 93 | 303 | 00093_update3 | Sat Apr 04 00:00:00 1970 PST + 94 | 4 | 00094 | Sun Apr 05 00:00:00 1970 PST + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST + 97 | 407 | 00097_update7 | Wed Apr 08 00:00:00 1970 PST + 98 | 8 | 00098 | Thu Apr 09 00:00:00 1970 PST + 99 | 509 | 00099_update9 | Fri Apr 10 00:00:00 1970 PST + 100 | 0 | 00100 | Thu Jan 01 00:00:00 1970 PST + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST + 103 | 303 | 00103_update3 | Sun Jan 04 00:00:00 1970 PST + 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST + 107 | 407 | 00107_update7 | Thu Jan 08 00:00:00 1970 PST + 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST + 109 | 509 | 00109_update9 | Sat Jan 10 00:00:00 1970 PST + 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST + 111 | 1 | 00111 | Mon Jan 12 00:00:00 1970 PST + 113 | 303 | 00113_update3 | Wed Jan 14 00:00:00 1970 PST + 114 | 4 | 00114 | Thu Jan 15 00:00:00 1970 PST + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST + 117 | 407 | 00117_update7 | Sun Jan 18 00:00:00 1970 PST + 118 | 8 | 00118 | Mon Jan 19 00:00:00 1970 PST + 119 | 509 | 00119_update9 | Tue Jan 20 00:00:00 1970 PST + 120 | 0 | 00120 | Wed Jan 21 00:00:00 1970 PST + 121 | 1 | 00121 | Thu Jan 22 00:00:00 1970 PST + 123 | 303 | 00123_update3 | Sat Jan 24 00:00:00 1970 PST + 124 | 4 | 00124 | Sun Jan 25 00:00:00 1970 PST + 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST + 127 | 407 | 00127_update7 | Wed Jan 28 00:00:00 1970 PST + 128 | 8 | 00128 | Thu Jan 29 00:00:00 1970 PST + 129 | 509 | 00129_update9 | Fri Jan 30 00:00:00 1970 PST + 130 | 0 | 00130 | Sat Jan 31 00:00:00 1970 PST + 131 | 1 | 00131 | Sun Feb 01 00:00:00 1970 PST + 133 | 303 | 00133_update3 | Tue Feb 03 00:00:00 1970 PST + 134 | 4 | 00134 | Wed Feb 04 00:00:00 1970 PST + 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST + 137 | 407 | 00137_update7 | Sat Feb 07 00:00:00 1970 PST + 138 | 8 | 00138 | Sun Feb 08 00:00:00 1970 PST + 139 | 509 | 00139_update9 | Mon Feb 09 00:00:00 1970 PST + 140 | 0 | 00140 | Tue Feb 10 00:00:00 1970 PST + 141 | 1 | 00141 | Wed Feb 11 00:00:00 1970 PST + 143 | 303 | 00143_update3 | Fri Feb 13 00:00:00 1970 PST + 144 | 4 | 00144 | Sat Feb 14 00:00:00 1970 PST + 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST + 147 | 407 | 00147_update7 | Tue Feb 17 00:00:00 1970 PST + 148 | 8 | 00148 | Wed Feb 18 00:00:00 1970 PST + 149 | 509 | 00149_update9 | Thu Feb 19 00:00:00 1970 PST + 150 | 0 | 00150 | Fri Feb 20 00:00:00 1970 PST + 151 | 1 | 00151 | Sat Feb 21 00:00:00 1970 PST + 153 | 303 | 00153_update3 | Mon Feb 23 00:00:00 1970 PST + 154 | 4 | 00154 | Tue Feb 24 00:00:00 1970 PST + 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST + 157 | 407 | 00157_update7 | Fri Feb 27 00:00:00 1970 PST + 158 | 8 | 00158 | Sat Feb 28 00:00:00 1970 PST + 159 | 509 | 00159_update9 | Sun Mar 01 00:00:00 1970 PST + 160 | 0 | 00160 | Mon Mar 02 00:00:00 1970 PST + 161 | 1 | 00161 | Tue Mar 03 00:00:00 1970 PST + 163 | 303 | 00163_update3 | Thu Mar 05 00:00:00 1970 PST + 164 | 4 | 00164 | Fri Mar 06 00:00:00 1970 PST + 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST + 167 | 407 | 00167_update7 | Mon Mar 09 00:00:00 1970 PST + 168 | 8 | 00168 | Tue Mar 10 00:00:00 1970 PST + 169 | 509 | 00169_update9 | Wed Mar 11 00:00:00 1970 PST + 170 | 0 | 00170 | Thu Mar 12 00:00:00 1970 PST + 171 | 1 | 00171 | Fri Mar 13 00:00:00 1970 PST + 173 | 303 | 00173_update3 | Sun Mar 15 00:00:00 1970 PST + 174 | 4 | 00174 | Mon Mar 16 00:00:00 1970 PST + 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST + 177 | 407 | 00177_update7 | Thu Mar 19 00:00:00 1970 PST + 178 | 8 | 00178 | Fri Mar 20 00:00:00 1970 PST + 179 | 509 | 00179_update9 | Sat Mar 21 00:00:00 1970 PST + 180 | 0 | 00180 | Sun Mar 22 00:00:00 1970 PST + 181 | 1 | 00181 | Mon Mar 23 00:00:00 1970 PST + 183 | 303 | 00183_update3 | Wed Mar 25 00:00:00 1970 PST + 184 | 4 | 00184 | Thu Mar 26 00:00:00 1970 PST + 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST + 187 | 407 | 00187_update7 | Sun Mar 29 00:00:00 1970 PST + 188 | 8 | 00188 | Mon Mar 30 00:00:00 1970 PST + 189 | 509 | 00189_update9 | Tue Mar 31 00:00:00 1970 PST + 190 | 0 | 00190 | Wed Apr 01 00:00:00 1970 PST + 191 | 1 | 00191 | Thu Apr 02 00:00:00 1970 PST + 193 | 303 | 00193_update3 | Sat Apr 04 00:00:00 1970 PST + 194 | 4 | 00194 | Sun Apr 05 00:00:00 1970 PST + 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST + 197 | 407 | 00197_update7 | Wed Apr 08 00:00:00 1970 PST + 198 | 8 | 00198 | Thu Apr 09 00:00:00 1970 PST + 199 | 509 | 00199_update9 | Fri Apr 10 00:00:00 1970 PST + 200 | 0 | 00200 | Thu Jan 01 00:00:00 1970 PST + 201 | 1 | 00201 | Fri Jan 02 00:00:00 1970 PST + 203 | 303 | 00203_update3 | Sun Jan 04 00:00:00 1970 PST + 204 | 4 | 00204 | Mon Jan 05 00:00:00 1970 PST + 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST + 207 | 407 | 00207_update7 | Thu Jan 08 00:00:00 1970 PST + 208 | 8 | 00208 | Fri Jan 09 00:00:00 1970 PST + 209 | 509 | 00209_update9 | Sat Jan 10 00:00:00 1970 PST + 210 | 0 | 00210 | Sun Jan 11 00:00:00 1970 PST + 211 | 1 | 00211 | Mon Jan 12 00:00:00 1970 PST + 213 | 303 | 00213_update3 | Wed Jan 14 00:00:00 1970 PST + 214 | 4 | 00214 | Thu Jan 15 00:00:00 1970 PST + 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST + 217 | 407 | 00217_update7 | Sun Jan 18 00:00:00 1970 PST + 218 | 8 | 00218 | Mon Jan 19 00:00:00 1970 PST + 219 | 509 | 00219_update9 | Tue Jan 20 00:00:00 1970 PST + 220 | 0 | 00220 | Wed Jan 21 00:00:00 1970 PST + 221 | 1 | 00221 | Thu Jan 22 00:00:00 1970 PST + 223 | 303 | 00223_update3 | Sat Jan 24 00:00:00 1970 PST + 224 | 4 | 00224 | Sun Jan 25 00:00:00 1970 PST + 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST + 227 | 407 | 00227_update7 | Wed Jan 28 00:00:00 1970 PST + 228 | 8 | 00228 | Thu Jan 29 00:00:00 1970 PST + 229 | 509 | 00229_update9 | Fri Jan 30 00:00:00 1970 PST + 230 | 0 | 00230 | Sat Jan 31 00:00:00 1970 PST + 231 | 1 | 00231 | Sun Feb 01 00:00:00 1970 PST + 233 | 303 | 00233_update3 | Tue Feb 03 00:00:00 1970 PST + 234 | 4 | 00234 | Wed Feb 04 00:00:00 1970 PST + 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST + 237 | 407 | 00237_update7 | Sat Feb 07 00:00:00 1970 PST + 238 | 8 | 00238 | Sun Feb 08 00:00:00 1970 PST + 239 | 509 | 00239_update9 | Mon Feb 09 00:00:00 1970 PST + 240 | 0 | 00240 | Tue Feb 10 00:00:00 1970 PST + 241 | 1 | 00241 | Wed Feb 11 00:00:00 1970 PST + 243 | 303 | 00243_update3 | Fri Feb 13 00:00:00 1970 PST + 244 | 4 | 00244 | Sat Feb 14 00:00:00 1970 PST + 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST + 247 | 407 | 00247_update7 | Tue Feb 17 00:00:00 1970 PST + 248 | 8 | 00248 | Wed Feb 18 00:00:00 1970 PST + 249 | 509 | 00249_update9 | Thu Feb 19 00:00:00 1970 PST + 250 | 0 | 00250 | Fri Feb 20 00:00:00 1970 PST + 251 | 1 | 00251 | Sat Feb 21 00:00:00 1970 PST + 253 | 303 | 00253_update3 | Mon Feb 23 00:00:00 1970 PST + 254 | 4 | 00254 | Tue Feb 24 00:00:00 1970 PST + 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST + 257 | 407 | 00257_update7 | Fri Feb 27 00:00:00 1970 PST + 258 | 8 | 00258 | Sat Feb 28 00:00:00 1970 PST + 259 | 509 | 00259_update9 | Sun Mar 01 00:00:00 1970 PST + 260 | 0 | 00260 | Mon Mar 02 00:00:00 1970 PST + 261 | 1 | 00261 | Tue Mar 03 00:00:00 1970 PST + 263 | 303 | 00263_update3 | Thu Mar 05 00:00:00 1970 PST + 264 | 4 | 00264 | Fri Mar 06 00:00:00 1970 PST + 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST + 267 | 407 | 00267_update7 | Mon Mar 09 00:00:00 1970 PST + 268 | 8 | 00268 | Tue Mar 10 00:00:00 1970 PST + 269 | 509 | 00269_update9 | Wed Mar 11 00:00:00 1970 PST + 270 | 0 | 00270 | Thu Mar 12 00:00:00 1970 PST + 271 | 1 | 00271 | Fri Mar 13 00:00:00 1970 PST + 273 | 303 | 00273_update3 | Sun Mar 15 00:00:00 1970 PST + 274 | 4 | 00274 | Mon Mar 16 00:00:00 1970 PST + 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST + 277 | 407 | 00277_update7 | Thu Mar 19 00:00:00 1970 PST + 278 | 8 | 00278 | Fri Mar 20 00:00:00 1970 PST + 279 | 509 | 00279_update9 | Sat Mar 21 00:00:00 1970 PST + 280 | 0 | 00280 | Sun Mar 22 00:00:00 1970 PST + 281 | 1 | 00281 | Mon Mar 23 00:00:00 1970 PST + 283 | 303 | 00283_update3 | Wed Mar 25 00:00:00 1970 PST + 284 | 4 | 00284 | Thu Mar 26 00:00:00 1970 PST + 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST + 287 | 407 | 00287_update7 | Sun Mar 29 00:00:00 1970 PST + 288 | 8 | 00288 | Mon Mar 30 00:00:00 1970 PST + 289 | 509 | 00289_update9 | Tue Mar 31 00:00:00 1970 PST + 290 | 0 | 00290 | Wed Apr 01 00:00:00 1970 PST + 291 | 1 | 00291 | Thu Apr 02 00:00:00 1970 PST + 293 | 303 | 00293_update3 | Sat Apr 04 00:00:00 1970 PST + 294 | 4 | 00294 | Sun Apr 05 00:00:00 1970 PST + 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST + 297 | 407 | 00297_update7 | Wed Apr 08 00:00:00 1970 PST + 298 | 8 | 00298 | Thu Apr 09 00:00:00 1970 PST + 299 | 509 | 00299_update9 | Fri Apr 10 00:00:00 1970 PST + 300 | 0 | 00300 | Thu Jan 01 00:00:00 1970 PST + 301 | 1 | 00301 | Fri Jan 02 00:00:00 1970 PST + 303 | 303 | 00303_update3 | Sun Jan 04 00:00:00 1970 PST + 304 | 4 | 00304 | Mon Jan 05 00:00:00 1970 PST + 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST + 307 | 407 | 00307_update7 | Thu Jan 08 00:00:00 1970 PST + 308 | 8 | 00308 | Fri Jan 09 00:00:00 1970 PST + 309 | 509 | 00309_update9 | Sat Jan 10 00:00:00 1970 PST + 310 | 0 | 00310 | Sun Jan 11 00:00:00 1970 PST + 311 | 1 | 00311 | Mon Jan 12 00:00:00 1970 PST + 313 | 303 | 00313_update3 | Wed Jan 14 00:00:00 1970 PST + 314 | 4 | 00314 | Thu Jan 15 00:00:00 1970 PST + 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST + 317 | 407 | 00317_update7 | Sun Jan 18 00:00:00 1970 PST + 318 | 8 | 00318 | Mon Jan 19 00:00:00 1970 PST + 319 | 509 | 00319_update9 | Tue Jan 20 00:00:00 1970 PST + 320 | 0 | 00320 | Wed Jan 21 00:00:00 1970 PST + 321 | 1 | 00321 | Thu Jan 22 00:00:00 1970 PST + 323 | 303 | 00323_update3 | Sat Jan 24 00:00:00 1970 PST + 324 | 4 | 00324 | Sun Jan 25 00:00:00 1970 PST + 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST + 327 | 407 | 00327_update7 | Wed Jan 28 00:00:00 1970 PST + 328 | 8 | 00328 | Thu Jan 29 00:00:00 1970 PST + 329 | 509 | 00329_update9 | Fri Jan 30 00:00:00 1970 PST + 330 | 0 | 00330 | Sat Jan 31 00:00:00 1970 PST + 331 | 1 | 00331 | Sun Feb 01 00:00:00 1970 PST + 333 | 303 | 00333_update3 | Tue Feb 03 00:00:00 1970 PST + 334 | 4 | 00334 | Wed Feb 04 00:00:00 1970 PST + 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST + 337 | 407 | 00337_update7 | Sat Feb 07 00:00:00 1970 PST + 338 | 8 | 00338 | Sun Feb 08 00:00:00 1970 PST + 339 | 509 | 00339_update9 | Mon Feb 09 00:00:00 1970 PST + 340 | 0 | 00340 | Tue Feb 10 00:00:00 1970 PST + 341 | 1 | 00341 | Wed Feb 11 00:00:00 1970 PST + 343 | 303 | 00343_update3 | Fri Feb 13 00:00:00 1970 PST + 344 | 4 | 00344 | Sat Feb 14 00:00:00 1970 PST + 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST + 347 | 407 | 00347_update7 | Tue Feb 17 00:00:00 1970 PST + 348 | 8 | 00348 | Wed Feb 18 00:00:00 1970 PST + 349 | 509 | 00349_update9 | Thu Feb 19 00:00:00 1970 PST + 350 | 0 | 00350 | Fri Feb 20 00:00:00 1970 PST + 351 | 1 | 00351 | Sat Feb 21 00:00:00 1970 PST + 353 | 303 | 00353_update3 | Mon Feb 23 00:00:00 1970 PST + 354 | 4 | 00354 | Tue Feb 24 00:00:00 1970 PST + 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST + 357 | 407 | 00357_update7 | Fri Feb 27 00:00:00 1970 PST + 358 | 8 | 00358 | Sat Feb 28 00:00:00 1970 PST + 359 | 509 | 00359_update9 | Sun Mar 01 00:00:00 1970 PST + 360 | 0 | 00360 | Mon Mar 02 00:00:00 1970 PST + 361 | 1 | 00361 | Tue Mar 03 00:00:00 1970 PST + 363 | 303 | 00363_update3 | Thu Mar 05 00:00:00 1970 PST + 364 | 4 | 00364 | Fri Mar 06 00:00:00 1970 PST + 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST + 367 | 407 | 00367_update7 | Mon Mar 09 00:00:00 1970 PST + 368 | 8 | 00368 | Tue Mar 10 00:00:00 1970 PST + 369 | 509 | 00369_update9 | Wed Mar 11 00:00:00 1970 PST + 370 | 0 | 00370 | Thu Mar 12 00:00:00 1970 PST + 371 | 1 | 00371 | Fri Mar 13 00:00:00 1970 PST + 373 | 303 | 00373_update3 | Sun Mar 15 00:00:00 1970 PST + 374 | 4 | 00374 | Mon Mar 16 00:00:00 1970 PST + 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST + 377 | 407 | 00377_update7 | Thu Mar 19 00:00:00 1970 PST + 378 | 8 | 00378 | Fri Mar 20 00:00:00 1970 PST + 379 | 509 | 00379_update9 | Sat Mar 21 00:00:00 1970 PST + 380 | 0 | 00380 | Sun Mar 22 00:00:00 1970 PST + 381 | 1 | 00381 | Mon Mar 23 00:00:00 1970 PST + 383 | 303 | 00383_update3 | Wed Mar 25 00:00:00 1970 PST + 384 | 4 | 00384 | Thu Mar 26 00:00:00 1970 PST + 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST + 387 | 407 | 00387_update7 | Sun Mar 29 00:00:00 1970 PST + 388 | 8 | 00388 | Mon Mar 30 00:00:00 1970 PST + 389 | 509 | 00389_update9 | Tue Mar 31 00:00:00 1970 PST + 390 | 0 | 00390 | Wed Apr 01 00:00:00 1970 PST + 391 | 1 | 00391 | Thu Apr 02 00:00:00 1970 PST + 393 | 303 | 00393_update3 | Sat Apr 04 00:00:00 1970 PST + 394 | 4 | 00394 | Sun Apr 05 00:00:00 1970 PST + 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST + 397 | 407 | 00397_update7 | Wed Apr 08 00:00:00 1970 PST + 398 | 8 | 00398 | Thu Apr 09 00:00:00 1970 PST + 399 | 509 | 00399_update9 | Fri Apr 10 00:00:00 1970 PST + 400 | 0 | 00400 | Thu Jan 01 00:00:00 1970 PST + 401 | 1 | 00401 | Fri Jan 02 00:00:00 1970 PST + 403 | 303 | 00403_update3 | Sun Jan 04 00:00:00 1970 PST + 404 | 4 | 00404 | Mon Jan 05 00:00:00 1970 PST + 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST + 407 | 407 | 00407_update7 | Thu Jan 08 00:00:00 1970 PST + 408 | 8 | 00408 | Fri Jan 09 00:00:00 1970 PST + 409 | 509 | 00409_update9 | Sat Jan 10 00:00:00 1970 PST + 410 | 0 | 00410 | Sun Jan 11 00:00:00 1970 PST + 411 | 1 | 00411 | Mon Jan 12 00:00:00 1970 PST + 413 | 303 | 00413_update3 | Wed Jan 14 00:00:00 1970 PST + 414 | 4 | 00414 | Thu Jan 15 00:00:00 1970 PST + 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST + 417 | 407 | 00417_update7 | Sun Jan 18 00:00:00 1970 PST + 418 | 8 | 00418 | Mon Jan 19 00:00:00 1970 PST + 419 | 509 | 00419_update9 | Tue Jan 20 00:00:00 1970 PST + 420 | 0 | 00420 | Wed Jan 21 00:00:00 1970 PST + 421 | 1 | 00421 | Thu Jan 22 00:00:00 1970 PST + 423 | 303 | 00423_update3 | Sat Jan 24 00:00:00 1970 PST + 424 | 4 | 00424 | Sun Jan 25 00:00:00 1970 PST + 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST + 427 | 407 | 00427_update7 | Wed Jan 28 00:00:00 1970 PST + 428 | 8 | 00428 | Thu Jan 29 00:00:00 1970 PST + 429 | 509 | 00429_update9 | Fri Jan 30 00:00:00 1970 PST + 430 | 0 | 00430 | Sat Jan 31 00:00:00 1970 PST + 431 | 1 | 00431 | Sun Feb 01 00:00:00 1970 PST + 433 | 303 | 00433_update3 | Tue Feb 03 00:00:00 1970 PST + 434 | 4 | 00434 | Wed Feb 04 00:00:00 1970 PST + 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST + 437 | 407 | 00437_update7 | Sat Feb 07 00:00:00 1970 PST + 438 | 8 | 00438 | Sun Feb 08 00:00:00 1970 PST + 439 | 509 | 00439_update9 | Mon Feb 09 00:00:00 1970 PST + 440 | 0 | 00440 | Tue Feb 10 00:00:00 1970 PST + 441 | 1 | 00441 | Wed Feb 11 00:00:00 1970 PST + 443 | 303 | 00443_update3 | Fri Feb 13 00:00:00 1970 PST + 444 | 4 | 00444 | Sat Feb 14 00:00:00 1970 PST + 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST + 447 | 407 | 00447_update7 | Tue Feb 17 00:00:00 1970 PST + 448 | 8 | 00448 | Wed Feb 18 00:00:00 1970 PST + 449 | 509 | 00449_update9 | Thu Feb 19 00:00:00 1970 PST + 450 | 0 | 00450 | Fri Feb 20 00:00:00 1970 PST + 451 | 1 | 00451 | Sat Feb 21 00:00:00 1970 PST + 453 | 303 | 00453_update3 | Mon Feb 23 00:00:00 1970 PST + 454 | 4 | 00454 | Tue Feb 24 00:00:00 1970 PST + 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST + 457 | 407 | 00457_update7 | Fri Feb 27 00:00:00 1970 PST + 458 | 8 | 00458 | Sat Feb 28 00:00:00 1970 PST + 459 | 509 | 00459_update9 | Sun Mar 01 00:00:00 1970 PST + 460 | 0 | 00460 | Mon Mar 02 00:00:00 1970 PST + 461 | 1 | 00461 | Tue Mar 03 00:00:00 1970 PST + 463 | 303 | 00463_update3 | Thu Mar 05 00:00:00 1970 PST + 464 | 4 | 00464 | Fri Mar 06 00:00:00 1970 PST + 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST + 467 | 407 | 00467_update7 | Mon Mar 09 00:00:00 1970 PST + 468 | 8 | 00468 | Tue Mar 10 00:00:00 1970 PST + 469 | 509 | 00469_update9 | Wed Mar 11 00:00:00 1970 PST + 470 | 0 | 00470 | Thu Mar 12 00:00:00 1970 PST + 471 | 1 | 00471 | Fri Mar 13 00:00:00 1970 PST + 473 | 303 | 00473_update3 | Sun Mar 15 00:00:00 1970 PST + 474 | 4 | 00474 | Mon Mar 16 00:00:00 1970 PST + 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST + 477 | 407 | 00477_update7 | Thu Mar 19 00:00:00 1970 PST + 478 | 8 | 00478 | Fri Mar 20 00:00:00 1970 PST + 479 | 509 | 00479_update9 | Sat Mar 21 00:00:00 1970 PST + 480 | 0 | 00480 | Sun Mar 22 00:00:00 1970 PST + 481 | 1 | 00481 | Mon Mar 23 00:00:00 1970 PST + 483 | 303 | 00483_update3 | Wed Mar 25 00:00:00 1970 PST + 484 | 4 | 00484 | Thu Mar 26 00:00:00 1970 PST + 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST + 487 | 407 | 00487_update7 | Sun Mar 29 00:00:00 1970 PST + 488 | 8 | 00488 | Mon Mar 30 00:00:00 1970 PST + 489 | 509 | 00489_update9 | Tue Mar 31 00:00:00 1970 PST + 490 | 0 | 00490 | Wed Apr 01 00:00:00 1970 PST + 491 | 1 | 00491 | Thu Apr 02 00:00:00 1970 PST + 493 | 303 | 00493_update3 | Sat Apr 04 00:00:00 1970 PST + 494 | 4 | 00494 | Sun Apr 05 00:00:00 1970 PST + 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST + 497 | 407 | 00497_update7 | Wed Apr 08 00:00:00 1970 PST + 498 | 8 | 00498 | Thu Apr 09 00:00:00 1970 PST + 499 | 509 | 00499_update9 | Fri Apr 10 00:00:00 1970 PST + 500 | 0 | 00500 | Thu Jan 01 00:00:00 1970 PST + 501 | 1 | 00501 | Fri Jan 02 00:00:00 1970 PST + 503 | 303 | 00503_update3 | Sun Jan 04 00:00:00 1970 PST + 504 | 4 | 00504 | Mon Jan 05 00:00:00 1970 PST + 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST + 507 | 407 | 00507_update7 | Thu Jan 08 00:00:00 1970 PST + 508 | 8 | 00508 | Fri Jan 09 00:00:00 1970 PST + 509 | 509 | 00509_update9 | Sat Jan 10 00:00:00 1970 PST + 510 | 0 | 00510 | Sun Jan 11 00:00:00 1970 PST + 511 | 1 | 00511 | Mon Jan 12 00:00:00 1970 PST + 513 | 303 | 00513_update3 | Wed Jan 14 00:00:00 1970 PST + 514 | 4 | 00514 | Thu Jan 15 00:00:00 1970 PST + 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST + 517 | 407 | 00517_update7 | Sun Jan 18 00:00:00 1970 PST + 518 | 8 | 00518 | Mon Jan 19 00:00:00 1970 PST + 519 | 509 | 00519_update9 | Tue Jan 20 00:00:00 1970 PST + 520 | 0 | 00520 | Wed Jan 21 00:00:00 1970 PST + 521 | 1 | 00521 | Thu Jan 22 00:00:00 1970 PST + 523 | 303 | 00523_update3 | Sat Jan 24 00:00:00 1970 PST + 524 | 4 | 00524 | Sun Jan 25 00:00:00 1970 PST + 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST + 527 | 407 | 00527_update7 | Wed Jan 28 00:00:00 1970 PST + 528 | 8 | 00528 | Thu Jan 29 00:00:00 1970 PST + 529 | 509 | 00529_update9 | Fri Jan 30 00:00:00 1970 PST + 530 | 0 | 00530 | Sat Jan 31 00:00:00 1970 PST + 531 | 1 | 00531 | Sun Feb 01 00:00:00 1970 PST + 533 | 303 | 00533_update3 | Tue Feb 03 00:00:00 1970 PST + 534 | 4 | 00534 | Wed Feb 04 00:00:00 1970 PST + 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST + 537 | 407 | 00537_update7 | Sat Feb 07 00:00:00 1970 PST + 538 | 8 | 00538 | Sun Feb 08 00:00:00 1970 PST + 539 | 509 | 00539_update9 | Mon Feb 09 00:00:00 1970 PST + 540 | 0 | 00540 | Tue Feb 10 00:00:00 1970 PST + 541 | 1 | 00541 | Wed Feb 11 00:00:00 1970 PST + 543 | 303 | 00543_update3 | Fri Feb 13 00:00:00 1970 PST + 544 | 4 | 00544 | Sat Feb 14 00:00:00 1970 PST + 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST + 547 | 407 | 00547_update7 | Tue Feb 17 00:00:00 1970 PST + 548 | 8 | 00548 | Wed Feb 18 00:00:00 1970 PST + 549 | 509 | 00549_update9 | Thu Feb 19 00:00:00 1970 PST + 550 | 0 | 00550 | Fri Feb 20 00:00:00 1970 PST + 551 | 1 | 00551 | Sat Feb 21 00:00:00 1970 PST + 553 | 303 | 00553_update3 | Mon Feb 23 00:00:00 1970 PST + 554 | 4 | 00554 | Tue Feb 24 00:00:00 1970 PST + 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST + 557 | 407 | 00557_update7 | Fri Feb 27 00:00:00 1970 PST + 558 | 8 | 00558 | Sat Feb 28 00:00:00 1970 PST + 559 | 509 | 00559_update9 | Sun Mar 01 00:00:00 1970 PST + 560 | 0 | 00560 | Mon Mar 02 00:00:00 1970 PST + 561 | 1 | 00561 | Tue Mar 03 00:00:00 1970 PST + 563 | 303 | 00563_update3 | Thu Mar 05 00:00:00 1970 PST + 564 | 4 | 00564 | Fri Mar 06 00:00:00 1970 PST + 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST + 567 | 407 | 00567_update7 | Mon Mar 09 00:00:00 1970 PST + 568 | 8 | 00568 | Tue Mar 10 00:00:00 1970 PST + 569 | 509 | 00569_update9 | Wed Mar 11 00:00:00 1970 PST + 570 | 0 | 00570 | Thu Mar 12 00:00:00 1970 PST + 571 | 1 | 00571 | Fri Mar 13 00:00:00 1970 PST + 573 | 303 | 00573_update3 | Sun Mar 15 00:00:00 1970 PST + 574 | 4 | 00574 | Mon Mar 16 00:00:00 1970 PST + 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST + 577 | 407 | 00577_update7 | Thu Mar 19 00:00:00 1970 PST + 578 | 8 | 00578 | Fri Mar 20 00:00:00 1970 PST + 579 | 509 | 00579_update9 | Sat Mar 21 00:00:00 1970 PST + 580 | 0 | 00580 | Sun Mar 22 00:00:00 1970 PST + 581 | 1 | 00581 | Mon Mar 23 00:00:00 1970 PST + 583 | 303 | 00583_update3 | Wed Mar 25 00:00:00 1970 PST + 584 | 4 | 00584 | Thu Mar 26 00:00:00 1970 PST + 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST + 587 | 407 | 00587_update7 | Sun Mar 29 00:00:00 1970 PST + 588 | 8 | 00588 | Mon Mar 30 00:00:00 1970 PST + 589 | 509 | 00589_update9 | Tue Mar 31 00:00:00 1970 PST + 590 | 0 | 00590 | Wed Apr 01 00:00:00 1970 PST + 591 | 1 | 00591 | Thu Apr 02 00:00:00 1970 PST + 593 | 303 | 00593_update3 | Sat Apr 04 00:00:00 1970 PST + 594 | 4 | 00594 | Sun Apr 05 00:00:00 1970 PST + 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST + 597 | 407 | 00597_update7 | Wed Apr 08 00:00:00 1970 PST + 598 | 8 | 00598 | Thu Apr 09 00:00:00 1970 PST + 599 | 509 | 00599_update9 | Fri Apr 10 00:00:00 1970 PST + 600 | 0 | 00600 | Thu Jan 01 00:00:00 1970 PST + 601 | 1 | 00601 | Fri Jan 02 00:00:00 1970 PST + 603 | 303 | 00603_update3 | Sun Jan 04 00:00:00 1970 PST + 604 | 4 | 00604 | Mon Jan 05 00:00:00 1970 PST + 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST + 607 | 407 | 00607_update7 | Thu Jan 08 00:00:00 1970 PST + 608 | 8 | 00608 | Fri Jan 09 00:00:00 1970 PST + 609 | 509 | 00609_update9 | Sat Jan 10 00:00:00 1970 PST + 610 | 0 | 00610 | Sun Jan 11 00:00:00 1970 PST + 611 | 1 | 00611 | Mon Jan 12 00:00:00 1970 PST + 613 | 303 | 00613_update3 | Wed Jan 14 00:00:00 1970 PST + 614 | 4 | 00614 | Thu Jan 15 00:00:00 1970 PST + 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST + 617 | 407 | 00617_update7 | Sun Jan 18 00:00:00 1970 PST + 618 | 8 | 00618 | Mon Jan 19 00:00:00 1970 PST + 619 | 509 | 00619_update9 | Tue Jan 20 00:00:00 1970 PST + 620 | 0 | 00620 | Wed Jan 21 00:00:00 1970 PST + 621 | 1 | 00621 | Thu Jan 22 00:00:00 1970 PST + 623 | 303 | 00623_update3 | Sat Jan 24 00:00:00 1970 PST + 624 | 4 | 00624 | Sun Jan 25 00:00:00 1970 PST + 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST + 627 | 407 | 00627_update7 | Wed Jan 28 00:00:00 1970 PST + 628 | 8 | 00628 | Thu Jan 29 00:00:00 1970 PST + 629 | 509 | 00629_update9 | Fri Jan 30 00:00:00 1970 PST + 630 | 0 | 00630 | Sat Jan 31 00:00:00 1970 PST + 631 | 1 | 00631 | Sun Feb 01 00:00:00 1970 PST + 633 | 303 | 00633_update3 | Tue Feb 03 00:00:00 1970 PST + 634 | 4 | 00634 | Wed Feb 04 00:00:00 1970 PST + 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST + 637 | 407 | 00637_update7 | Sat Feb 07 00:00:00 1970 PST + 638 | 8 | 00638 | Sun Feb 08 00:00:00 1970 PST + 639 | 509 | 00639_update9 | Mon Feb 09 00:00:00 1970 PST + 640 | 0 | 00640 | Tue Feb 10 00:00:00 1970 PST + 641 | 1 | 00641 | Wed Feb 11 00:00:00 1970 PST + 643 | 303 | 00643_update3 | Fri Feb 13 00:00:00 1970 PST + 644 | 4 | 00644 | Sat Feb 14 00:00:00 1970 PST + 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST + 647 | 407 | 00647_update7 | Tue Feb 17 00:00:00 1970 PST + 648 | 8 | 00648 | Wed Feb 18 00:00:00 1970 PST + 649 | 509 | 00649_update9 | Thu Feb 19 00:00:00 1970 PST + 650 | 0 | 00650 | Fri Feb 20 00:00:00 1970 PST + 651 | 1 | 00651 | Sat Feb 21 00:00:00 1970 PST + 653 | 303 | 00653_update3 | Mon Feb 23 00:00:00 1970 PST + 654 | 4 | 00654 | Tue Feb 24 00:00:00 1970 PST + 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST + 657 | 407 | 00657_update7 | Fri Feb 27 00:00:00 1970 PST + 658 | 8 | 00658 | Sat Feb 28 00:00:00 1970 PST + 659 | 509 | 00659_update9 | Sun Mar 01 00:00:00 1970 PST + 660 | 0 | 00660 | Mon Mar 02 00:00:00 1970 PST + 661 | 1 | 00661 | Tue Mar 03 00:00:00 1970 PST + 663 | 303 | 00663_update3 | Thu Mar 05 00:00:00 1970 PST + 664 | 4 | 00664 | Fri Mar 06 00:00:00 1970 PST + 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST + 667 | 407 | 00667_update7 | Mon Mar 09 00:00:00 1970 PST + 668 | 8 | 00668 | Tue Mar 10 00:00:00 1970 PST + 669 | 509 | 00669_update9 | Wed Mar 11 00:00:00 1970 PST + 670 | 0 | 00670 | Thu Mar 12 00:00:00 1970 PST + 671 | 1 | 00671 | Fri Mar 13 00:00:00 1970 PST + 673 | 303 | 00673_update3 | Sun Mar 15 00:00:00 1970 PST + 674 | 4 | 00674 | Mon Mar 16 00:00:00 1970 PST + 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST + 677 | 407 | 00677_update7 | Thu Mar 19 00:00:00 1970 PST + 678 | 8 | 00678 | Fri Mar 20 00:00:00 1970 PST + 679 | 509 | 00679_update9 | Sat Mar 21 00:00:00 1970 PST + 680 | 0 | 00680 | Sun Mar 22 00:00:00 1970 PST + 681 | 1 | 00681 | Mon Mar 23 00:00:00 1970 PST + 683 | 303 | 00683_update3 | Wed Mar 25 00:00:00 1970 PST + 684 | 4 | 00684 | Thu Mar 26 00:00:00 1970 PST + 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST + 687 | 407 | 00687_update7 | Sun Mar 29 00:00:00 1970 PST + 688 | 8 | 00688 | Mon Mar 30 00:00:00 1970 PST + 689 | 509 | 00689_update9 | Tue Mar 31 00:00:00 1970 PST + 690 | 0 | 00690 | Wed Apr 01 00:00:00 1970 PST + 691 | 1 | 00691 | Thu Apr 02 00:00:00 1970 PST + 693 | 303 | 00693_update3 | Sat Apr 04 00:00:00 1970 PST + 694 | 4 | 00694 | Sun Apr 05 00:00:00 1970 PST + 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST + 697 | 407 | 00697_update7 | Wed Apr 08 00:00:00 1970 PST + 698 | 8 | 00698 | Thu Apr 09 00:00:00 1970 PST + 699 | 509 | 00699_update9 | Fri Apr 10 00:00:00 1970 PST + 700 | 0 | 00700 | Thu Jan 01 00:00:00 1970 PST + 701 | 1 | 00701 | Fri Jan 02 00:00:00 1970 PST + 703 | 303 | 00703_update3 | Sun Jan 04 00:00:00 1970 PST + 704 | 4 | 00704 | Mon Jan 05 00:00:00 1970 PST + 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST + 707 | 407 | 00707_update7 | Thu Jan 08 00:00:00 1970 PST + 708 | 8 | 00708 | Fri Jan 09 00:00:00 1970 PST + 709 | 509 | 00709_update9 | Sat Jan 10 00:00:00 1970 PST + 710 | 0 | 00710 | Sun Jan 11 00:00:00 1970 PST + 711 | 1 | 00711 | Mon Jan 12 00:00:00 1970 PST + 713 | 303 | 00713_update3 | Wed Jan 14 00:00:00 1970 PST + 714 | 4 | 00714 | Thu Jan 15 00:00:00 1970 PST + 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST + 717 | 407 | 00717_update7 | Sun Jan 18 00:00:00 1970 PST + 718 | 8 | 00718 | Mon Jan 19 00:00:00 1970 PST + 719 | 509 | 00719_update9 | Tue Jan 20 00:00:00 1970 PST + 720 | 0 | 00720 | Wed Jan 21 00:00:00 1970 PST + 721 | 1 | 00721 | Thu Jan 22 00:00:00 1970 PST + 723 | 303 | 00723_update3 | Sat Jan 24 00:00:00 1970 PST + 724 | 4 | 00724 | Sun Jan 25 00:00:00 1970 PST + 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST + 727 | 407 | 00727_update7 | Wed Jan 28 00:00:00 1970 PST + 728 | 8 | 00728 | Thu Jan 29 00:00:00 1970 PST + 729 | 509 | 00729_update9 | Fri Jan 30 00:00:00 1970 PST + 730 | 0 | 00730 | Sat Jan 31 00:00:00 1970 PST + 731 | 1 | 00731 | Sun Feb 01 00:00:00 1970 PST + 733 | 303 | 00733_update3 | Tue Feb 03 00:00:00 1970 PST + 734 | 4 | 00734 | Wed Feb 04 00:00:00 1970 PST + 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST + 737 | 407 | 00737_update7 | Sat Feb 07 00:00:00 1970 PST + 738 | 8 | 00738 | Sun Feb 08 00:00:00 1970 PST + 739 | 509 | 00739_update9 | Mon Feb 09 00:00:00 1970 PST + 740 | 0 | 00740 | Tue Feb 10 00:00:00 1970 PST + 741 | 1 | 00741 | Wed Feb 11 00:00:00 1970 PST + 743 | 303 | 00743_update3 | Fri Feb 13 00:00:00 1970 PST + 744 | 4 | 00744 | Sat Feb 14 00:00:00 1970 PST + 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST + 747 | 407 | 00747_update7 | Tue Feb 17 00:00:00 1970 PST + 748 | 8 | 00748 | Wed Feb 18 00:00:00 1970 PST + 749 | 509 | 00749_update9 | Thu Feb 19 00:00:00 1970 PST + 750 | 0 | 00750 | Fri Feb 20 00:00:00 1970 PST + 751 | 1 | 00751 | Sat Feb 21 00:00:00 1970 PST + 753 | 303 | 00753_update3 | Mon Feb 23 00:00:00 1970 PST + 754 | 4 | 00754 | Tue Feb 24 00:00:00 1970 PST + 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST + 757 | 407 | 00757_update7 | Fri Feb 27 00:00:00 1970 PST + 758 | 8 | 00758 | Sat Feb 28 00:00:00 1970 PST + 759 | 509 | 00759_update9 | Sun Mar 01 00:00:00 1970 PST + 760 | 0 | 00760 | Mon Mar 02 00:00:00 1970 PST + 761 | 1 | 00761 | Tue Mar 03 00:00:00 1970 PST + 763 | 303 | 00763_update3 | Thu Mar 05 00:00:00 1970 PST + 764 | 4 | 00764 | Fri Mar 06 00:00:00 1970 PST + 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST + 767 | 407 | 00767_update7 | Mon Mar 09 00:00:00 1970 PST + 768 | 8 | 00768 | Tue Mar 10 00:00:00 1970 PST + 769 | 509 | 00769_update9 | Wed Mar 11 00:00:00 1970 PST + 770 | 0 | 00770 | Thu Mar 12 00:00:00 1970 PST + 771 | 1 | 00771 | Fri Mar 13 00:00:00 1970 PST + 773 | 303 | 00773_update3 | Sun Mar 15 00:00:00 1970 PST + 774 | 4 | 00774 | Mon Mar 16 00:00:00 1970 PST + 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST + 777 | 407 | 00777_update7 | Thu Mar 19 00:00:00 1970 PST + 778 | 8 | 00778 | Fri Mar 20 00:00:00 1970 PST + 779 | 509 | 00779_update9 | Sat Mar 21 00:00:00 1970 PST + 780 | 0 | 00780 | Sun Mar 22 00:00:00 1970 PST + 781 | 1 | 00781 | Mon Mar 23 00:00:00 1970 PST + 783 | 303 | 00783_update3 | Wed Mar 25 00:00:00 1970 PST + 784 | 4 | 00784 | Thu Mar 26 00:00:00 1970 PST + 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST + 787 | 407 | 00787_update7 | Sun Mar 29 00:00:00 1970 PST + 788 | 8 | 00788 | Mon Mar 30 00:00:00 1970 PST + 789 | 509 | 00789_update9 | Tue Mar 31 00:00:00 1970 PST + 790 | 0 | 00790 | Wed Apr 01 00:00:00 1970 PST + 791 | 1 | 00791 | Thu Apr 02 00:00:00 1970 PST + 793 | 303 | 00793_update3 | Sat Apr 04 00:00:00 1970 PST + 794 | 4 | 00794 | Sun Apr 05 00:00:00 1970 PST + 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST + 797 | 407 | 00797_update7 | Wed Apr 08 00:00:00 1970 PST + 798 | 8 | 00798 | Thu Apr 09 00:00:00 1970 PST + 799 | 509 | 00799_update9 | Fri Apr 10 00:00:00 1970 PST + 800 | 0 | 00800 | Thu Jan 01 00:00:00 1970 PST + 801 | 1 | 00801 | Fri Jan 02 00:00:00 1970 PST + 803 | 303 | 00803_update3 | Sun Jan 04 00:00:00 1970 PST + 804 | 4 | 00804 | Mon Jan 05 00:00:00 1970 PST + 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST + 807 | 407 | 00807_update7 | Thu Jan 08 00:00:00 1970 PST + 808 | 8 | 00808 | Fri Jan 09 00:00:00 1970 PST + 809 | 509 | 00809_update9 | Sat Jan 10 00:00:00 1970 PST + 810 | 0 | 00810 | Sun Jan 11 00:00:00 1970 PST + 811 | 1 | 00811 | Mon Jan 12 00:00:00 1970 PST + 813 | 303 | 00813_update3 | Wed Jan 14 00:00:00 1970 PST + 814 | 4 | 00814 | Thu Jan 15 00:00:00 1970 PST + 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST + 817 | 407 | 00817_update7 | Sun Jan 18 00:00:00 1970 PST + 818 | 8 | 00818 | Mon Jan 19 00:00:00 1970 PST + 819 | 509 | 00819_update9 | Tue Jan 20 00:00:00 1970 PST + 820 | 0 | 00820 | Wed Jan 21 00:00:00 1970 PST + 821 | 1 | 00821 | Thu Jan 22 00:00:00 1970 PST + 823 | 303 | 00823_update3 | Sat Jan 24 00:00:00 1970 PST + 824 | 4 | 00824 | Sun Jan 25 00:00:00 1970 PST + 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST + 827 | 407 | 00827_update7 | Wed Jan 28 00:00:00 1970 PST + 828 | 8 | 00828 | Thu Jan 29 00:00:00 1970 PST + 829 | 509 | 00829_update9 | Fri Jan 30 00:00:00 1970 PST + 830 | 0 | 00830 | Sat Jan 31 00:00:00 1970 PST + 831 | 1 | 00831 | Sun Feb 01 00:00:00 1970 PST + 833 | 303 | 00833_update3 | Tue Feb 03 00:00:00 1970 PST + 834 | 4 | 00834 | Wed Feb 04 00:00:00 1970 PST + 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST + 837 | 407 | 00837_update7 | Sat Feb 07 00:00:00 1970 PST + 838 | 8 | 00838 | Sun Feb 08 00:00:00 1970 PST + 839 | 509 | 00839_update9 | Mon Feb 09 00:00:00 1970 PST + 840 | 0 | 00840 | Tue Feb 10 00:00:00 1970 PST + 841 | 1 | 00841 | Wed Feb 11 00:00:00 1970 PST + 843 | 303 | 00843_update3 | Fri Feb 13 00:00:00 1970 PST + 844 | 4 | 00844 | Sat Feb 14 00:00:00 1970 PST + 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST + 847 | 407 | 00847_update7 | Tue Feb 17 00:00:00 1970 PST + 848 | 8 | 00848 | Wed Feb 18 00:00:00 1970 PST + 849 | 509 | 00849_update9 | Thu Feb 19 00:00:00 1970 PST + 850 | 0 | 00850 | Fri Feb 20 00:00:00 1970 PST + 851 | 1 | 00851 | Sat Feb 21 00:00:00 1970 PST + 853 | 303 | 00853_update3 | Mon Feb 23 00:00:00 1970 PST + 854 | 4 | 00854 | Tue Feb 24 00:00:00 1970 PST + 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST + 857 | 407 | 00857_update7 | Fri Feb 27 00:00:00 1970 PST + 858 | 8 | 00858 | Sat Feb 28 00:00:00 1970 PST + 859 | 509 | 00859_update9 | Sun Mar 01 00:00:00 1970 PST + 860 | 0 | 00860 | Mon Mar 02 00:00:00 1970 PST + 861 | 1 | 00861 | Tue Mar 03 00:00:00 1970 PST + 863 | 303 | 00863_update3 | Thu Mar 05 00:00:00 1970 PST + 864 | 4 | 00864 | Fri Mar 06 00:00:00 1970 PST + 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST + 867 | 407 | 00867_update7 | Mon Mar 09 00:00:00 1970 PST + 868 | 8 | 00868 | Tue Mar 10 00:00:00 1970 PST + 869 | 509 | 00869_update9 | Wed Mar 11 00:00:00 1970 PST + 870 | 0 | 00870 | Thu Mar 12 00:00:00 1970 PST + 871 | 1 | 00871 | Fri Mar 13 00:00:00 1970 PST + 873 | 303 | 00873_update3 | Sun Mar 15 00:00:00 1970 PST + 874 | 4 | 00874 | Mon Mar 16 00:00:00 1970 PST + 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST + 877 | 407 | 00877_update7 | Thu Mar 19 00:00:00 1970 PST + 878 | 8 | 00878 | Fri Mar 20 00:00:00 1970 PST + 879 | 509 | 00879_update9 | Sat Mar 21 00:00:00 1970 PST + 880 | 0 | 00880 | Sun Mar 22 00:00:00 1970 PST + 881 | 1 | 00881 | Mon Mar 23 00:00:00 1970 PST + 883 | 303 | 00883_update3 | Wed Mar 25 00:00:00 1970 PST + 884 | 4 | 00884 | Thu Mar 26 00:00:00 1970 PST + 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST + 887 | 407 | 00887_update7 | Sun Mar 29 00:00:00 1970 PST + 888 | 8 | 00888 | Mon Mar 30 00:00:00 1970 PST + 889 | 509 | 00889_update9 | Tue Mar 31 00:00:00 1970 PST + 890 | 0 | 00890 | Wed Apr 01 00:00:00 1970 PST + 891 | 1 | 00891 | Thu Apr 02 00:00:00 1970 PST + 893 | 303 | 00893_update3 | Sat Apr 04 00:00:00 1970 PST + 894 | 4 | 00894 | Sun Apr 05 00:00:00 1970 PST + 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST + 897 | 407 | 00897_update7 | Wed Apr 08 00:00:00 1970 PST + 898 | 8 | 00898 | Thu Apr 09 00:00:00 1970 PST + 899 | 509 | 00899_update9 | Fri Apr 10 00:00:00 1970 PST + 900 | 0 | 00900 | Thu Jan 01 00:00:00 1970 PST + 901 | 1 | 00901 | Fri Jan 02 00:00:00 1970 PST + 903 | 303 | 00903_update3 | Sun Jan 04 00:00:00 1970 PST + 904 | 4 | 00904 | Mon Jan 05 00:00:00 1970 PST + 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST + 907 | 407 | 00907_update7 | Thu Jan 08 00:00:00 1970 PST + 908 | 8 | 00908 | Fri Jan 09 00:00:00 1970 PST + 909 | 509 | 00909_update9 | Sat Jan 10 00:00:00 1970 PST + 910 | 0 | 00910 | Sun Jan 11 00:00:00 1970 PST + 911 | 1 | 00911 | Mon Jan 12 00:00:00 1970 PST + 913 | 303 | 00913_update3 | Wed Jan 14 00:00:00 1970 PST + 914 | 4 | 00914 | Thu Jan 15 00:00:00 1970 PST + 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST + 917 | 407 | 00917_update7 | Sun Jan 18 00:00:00 1970 PST + 918 | 8 | 00918 | Mon Jan 19 00:00:00 1970 PST + 919 | 509 | 00919_update9 | Tue Jan 20 00:00:00 1970 PST + 920 | 0 | 00920 | Wed Jan 21 00:00:00 1970 PST + 921 | 1 | 00921 | Thu Jan 22 00:00:00 1970 PST + 923 | 303 | 00923_update3 | Sat Jan 24 00:00:00 1970 PST + 924 | 4 | 00924 | Sun Jan 25 00:00:00 1970 PST + 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST + 927 | 407 | 00927_update7 | Wed Jan 28 00:00:00 1970 PST + 928 | 8 | 00928 | Thu Jan 29 00:00:00 1970 PST + 929 | 509 | 00929_update9 | Fri Jan 30 00:00:00 1970 PST + 930 | 0 | 00930 | Sat Jan 31 00:00:00 1970 PST + 931 | 1 | 00931 | Sun Feb 01 00:00:00 1970 PST + 933 | 303 | 00933_update3 | Tue Feb 03 00:00:00 1970 PST + 934 | 4 | 00934 | Wed Feb 04 00:00:00 1970 PST + 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST + 937 | 407 | 00937_update7 | Sat Feb 07 00:00:00 1970 PST + 938 | 8 | 00938 | Sun Feb 08 00:00:00 1970 PST + 939 | 509 | 00939_update9 | Mon Feb 09 00:00:00 1970 PST + 940 | 0 | 00940 | Tue Feb 10 00:00:00 1970 PST + 941 | 1 | 00941 | Wed Feb 11 00:00:00 1970 PST + 943 | 303 | 00943_update3 | Fri Feb 13 00:00:00 1970 PST + 944 | 4 | 00944 | Sat Feb 14 00:00:00 1970 PST + 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST + 947 | 407 | 00947_update7 | Tue Feb 17 00:00:00 1970 PST + 948 | 8 | 00948 | Wed Feb 18 00:00:00 1970 PST + 949 | 509 | 00949_update9 | Thu Feb 19 00:00:00 1970 PST + 950 | 0 | 00950 | Fri Feb 20 00:00:00 1970 PST + 951 | 1 | 00951 | Sat Feb 21 00:00:00 1970 PST + 953 | 303 | 00953_update3 | Mon Feb 23 00:00:00 1970 PST + 954 | 4 | 00954 | Tue Feb 24 00:00:00 1970 PST + 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST + 957 | 407 | 00957_update7 | Fri Feb 27 00:00:00 1970 PST + 958 | 8 | 00958 | Sat Feb 28 00:00:00 1970 PST + 959 | 509 | 00959_update9 | Sun Mar 01 00:00:00 1970 PST + 960 | 0 | 00960 | Mon Mar 02 00:00:00 1970 PST + 961 | 1 | 00961 | Tue Mar 03 00:00:00 1970 PST + 963 | 303 | 00963_update3 | Thu Mar 05 00:00:00 1970 PST + 964 | 4 | 00964 | Fri Mar 06 00:00:00 1970 PST + 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST + 967 | 407 | 00967_update7 | Mon Mar 09 00:00:00 1970 PST + 968 | 8 | 00968 | Tue Mar 10 00:00:00 1970 PST + 969 | 509 | 00969_update9 | Wed Mar 11 00:00:00 1970 PST + 970 | 0 | 00970 | Thu Mar 12 00:00:00 1970 PST + 971 | 1 | 00971 | Fri Mar 13 00:00:00 1970 PST + 973 | 303 | 00973_update3 | Sun Mar 15 00:00:00 1970 PST + 974 | 4 | 00974 | Mon Mar 16 00:00:00 1970 PST + 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST + 977 | 407 | 00977_update7 | Thu Mar 19 00:00:00 1970 PST + 978 | 8 | 00978 | Fri Mar 20 00:00:00 1970 PST + 979 | 509 | 00979_update9 | Sat Mar 21 00:00:00 1970 PST + 980 | 0 | 00980 | Sun Mar 22 00:00:00 1970 PST + 981 | 1 | 00981 | Mon Mar 23 00:00:00 1970 PST + 983 | 303 | 00983_update3 | Wed Mar 25 00:00:00 1970 PST + 984 | 4 | 00984 | Thu Mar 26 00:00:00 1970 PST + 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST + 987 | 407 | 00987_update7 | Sun Mar 29 00:00:00 1970 PST + 988 | 8 | 00988 | Mon Mar 30 00:00:00 1970 PST + 989 | 509 | 00989_update9 | Tue Mar 31 00:00:00 1970 PST + 990 | 0 | 00990 | Wed Apr 01 00:00:00 1970 PST + 991 | 1 | 00991 | Thu Apr 02 00:00:00 1970 PST + 993 | 303 | 00993_update3 | Sat Apr 04 00:00:00 1970 PST + 994 | 4 | 00994 | Sun Apr 05 00:00:00 1970 PST + 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST + 997 | 407 | 00997_update7 | Wed Apr 08 00:00:00 1970 PST + 998 | 8 | 00998 | Thu Apr 09 00:00:00 1970 PST + 999 | 509 | 00999_update9 | Fri Apr 10 00:00:00 1970 PST + 1000 | 0 | 01000 | Thu Jan 01 00:00:00 1970 PST + 1001 | 101 | 0000100001 | + 1003 | 403 | 0000300003_update3 | + 1004 | 104 | 0000400004 | + 1006 | 106 | 0000600006 | + 1007 | 507 | 0000700007_update7 | + 1008 | 108 | 0000800008 | + 1009 | 609 | 0000900009_update9 | + 1010 | 100 | 0001000010 | + 1011 | 101 | 0001100011 | + 1013 | 403 | 0001300013_update3 | + 1014 | 104 | 0001400014 | + 1016 | 106 | 0001600016 | + 1017 | 507 | 0001700017_update7 | + 1018 | 108 | 0001800018 | + 1019 | 609 | 0001900019_update9 | + 1020 | 100 | 0002000020 | + 1101 | 201 | aaa | + 1103 | 503 | ccc_update3 | + 1104 | 204 | ddd | +(819 rows) + +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft2 + Output: (ft2.tableoid)::regclass + Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING ybctid + Batch Size: 1 + -> Result *RESULT* + Output: 1200, 999, NULL::integer, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft2 '::character(10), NULL::user_enum +(6 rows) + +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; + tableoid +---------- + ft2 +(1 row) + +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: (tableoid)::regclass + -> Foreign Update on public.ft2 + Remote Filter: (ft2.c1 = 1200) + Remote SQL: UPDATE "S 1"."T 1" SET c3 = 'bar'::text WHERE (("C 1" = 1200)) RETURNING ybctid +(5 rows) + +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; + tableoid +---------- + ft2 +(1 row) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------- + Delete on public.ft2 + Output: (tableoid)::regclass + -> Foreign Delete on public.ft2 + Remote Filter: (ft2.c1 = 1200) + Remote SQL: DELETE FROM "S 1"."T 1" WHERE (("C 1" = 1200)) RETURNING ybctid +(5 rows) + +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; + tableoid +---------- + ft2 +(1 row) + +-- Test UPDATE/DELETE with RETURNING on a three-table join +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; -- can be pushed down + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: ft2.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.*, ft4.c1, ft4.c2, ft4.c3 + -> Foreign Update + Remote SQL: UPDATE "S 1"."T 1" r1 SET c3 = 'foo'::text FROM ("S 1"."T 3" r2 INNER JOIN "S 1"."T 4" r3 ON (TRUE)) WHERE ((r2.c1 = r3.c1)) AND ((r1.c2 = r2.c1)) AND ((r1."C 1" > 1200)) RETURNING r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r2.c1, r2.c2, r2.c3 +(4 rows) + +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; + ft2 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | ft4 | c1 | c2 | c3 +--------------------------------+------+----+-----+----+----+----+------------+----+----------------+----+----+-------- + (1206,6,foo,,,,"ft2 ",) | 1206 | 6 | foo | | | | ft2 | | (6,7,AAA006) | 6 | 7 | AAA006 + (1212,12,foo,,,,"ft2 ",) | 1212 | 12 | foo | | | | ft2 | | (12,13,AAA012) | 12 | 13 | AAA012 + (1218,18,foo,,,,"ft2 ",) | 1218 | 18 | foo | | | | ft2 | | (18,19,AAA018) | 18 | 19 | AAA018 + (1224,24,foo,,,,"ft2 ",) | 1224 | 24 | foo | | | | ft2 | | (24,25,AAA024) | 24 | 25 | AAA024 + (1230,30,foo,,,,"ft2 ",) | 1230 | 30 | foo | | | | ft2 | | (30,31,AAA030) | 30 | 31 | AAA030 + (1236,36,foo,,,,"ft2 ",) | 1236 | 36 | foo | | | | ft2 | | (36,37,AAA036) | 36 | 37 | AAA036 + (1242,42,foo,,,,"ft2 ",) | 1242 | 42 | foo | | | | ft2 | | (42,43,AAA042) | 42 | 43 | AAA042 + (1248,48,foo,,,,"ft2 ",) | 1248 | 48 | foo | | | | ft2 | | (48,49,AAA048) | 48 | 49 | AAA048 + (1254,54,foo,,,,"ft2 ",) | 1254 | 54 | foo | | | | ft2 | | (54,55,AAA054) | 54 | 55 | AAA054 + (1260,60,foo,,,,"ft2 ",) | 1260 | 60 | foo | | | | ft2 | | (60,61,AAA060) | 60 | 61 | AAA060 + (1266,66,foo,,,,"ft2 ",) | 1266 | 66 | foo | | | | ft2 | | (66,67,AAA066) | 66 | 67 | AAA066 + (1272,72,foo,,,,"ft2 ",) | 1272 | 72 | foo | | | | ft2 | | (72,73,AAA072) | 72 | 73 | AAA072 + (1278,78,foo,,,,"ft2 ",) | 1278 | 78 | foo | | | | ft2 | | (78,79,AAA078) | 78 | 79 | AAA078 + (1284,84,foo,,,,"ft2 ",) | 1284 | 84 | foo | | | | ft2 | | (84,85,AAA084) | 84 | 85 | AAA084 + (1290,90,foo,,,,"ft2 ",) | 1290 | 90 | foo | | | | ft2 | | (90,91,AAA090) | 90 | 91 | AAA090 + (1296,96,foo,,,,"ft2 ",) | 1296 | 96 | foo | | | | ft2 | | (96,97,AAA096) | 96 | 97 | AAA096 +(16 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; -- can be pushed down + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Delete on public.ft2 + Output: 100 + -> Foreign Delete + Remote SQL: DELETE FROM "S 1"."T 1" r1 USING ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r3 ON (((r2.c1 = r3.c1)))) WHERE ((r1.c2 = r2.c1)) AND ((r1."C 1" > 1200)) AND (((r1."C 1" % 10) = 0)) +(4 rows) + +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; + ?column? +---------- + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 +(10 rows) + +DELETE FROM ft2 WHERE ft2.c1 > 1200; +-- Test UPDATE with a MULTIEXPR sub-select +-- (maybe someday this'll be remotely executable, but not today) +EXPLAIN (verbose, costs off) +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 target + Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c7 = $3 WHERE ybctid = $1 + -> Foreign Scan on public.ft2 target + Output: $1, $2, (SubPlan 1 (returns $1,$2)), target.ybctid, target.* + Remote Filter: (target.c1 > 1100) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ybctid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE + SubPlan 1 (returns $1,$2) + -> Foreign Scan on public.ft2 src + Output: (src.c2 * 10), src.c7 + Remote Filter: (target.c1 = src.c1) + Remote SQL: SELECT c2, c7 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) +(11 rows) + +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +UPDATE ft2 AS target SET (c2) = ( + SELECT c2 / 10 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +-- Test UPDATE involving a join that can be pushed down, +-- but a SET clause that can't be +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 d + Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ybctid = $1 + -> Foreign Scan + Output: CASE WHEN (random() >= '0'::double precision) THEN d.c2 ELSE 0 END, d.ybctid, d.*, t.* + Relations: (public.ft2 d) INNER JOIN (public.ft2 t) + Remote SQL: SELECT r1.c2, r1.ybctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1 + -> Nested Loop + Output: d.c2, d.ybctid, d.*, t.* + -> Foreign Scan on public.ft2 d + Output: d.c2, d.ybctid, d.*, d.c1 + Remote Filter: (d.c1 > 1000) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ybctid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + -> Foreign Scan on public.ft2 t + Output: t.*, t.c1 + Remote Filter: (d.c1 = t.c1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) +(16 rows) + +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; +-- Test UPDATE/DELETE with WHERE or JOIN/ON conditions containing +-- user-defined operators/functions +ALTER SERVER loopback OPTIONS (DROP extensions); +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ybctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8, ybctid + -> Foreign Scan on public.ft2 + Output: 'bar'::text, ybctid, ft2.* + Filter: (postgres_fdw_abs(ft2.c1) > 2000) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ybctid FROM "S 1"."T 1" FOR UPDATE +(7 rows) + +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+----+-----+----+----+----+------------+---- + 2001 | 1 | bar | | | | ft2 | + 2002 | 2 | bar | | | | ft2 | + 2003 | 3 | bar | | | | ft2 | + 2004 | 4 | bar | | | | ft2 | + 2005 | 5 | bar | | | | ft2 | + 2006 | 6 | bar | | | | ft2 | + 2007 | 7 | bar | | | | ft2 | + 2008 | 8 | bar | | | | ft2 | + 2009 | 9 | bar | | | | ft2 | + 2010 | 0 | bar | | | | ft2 | +(10 rows) + +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; -- can't be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3 + Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ybctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8, ybctid + -> Nested Loop + Output: 'baz'::text, ft2.ybctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3 + Join Filter: (ft2.c2 === ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.ybctid, ft2.*, ft2.c2 + Remote Filter: (ft2.c1 > 2000) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ybctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + -> Foreign Scan + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3 + Relations: (public.ft4) INNER JOIN (public.ft5) + Remote SQL: SELECT CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r2.c1, r2.c2, r2.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r3.c1, r3.c2, r3.c3 FROM ("S 1"."T 3" r2 INNER JOIN "S 1"."T 4" r3 ON (((r2.c1 = r3.c1)))) + -> Hash Join + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3 + Hash Cond: (ft4.c1 = ft5.c1) + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Hash + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3 + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" +(25 rows) + +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 +------+----+-----+----+----+----+------------+----+----+----+--------+----+----+-------- + 2006 | 6 | baz | | | | ft2 | | 6 | 7 | AAA006 | 6 | 7 | AAA006 +(1 row) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Delete on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3 + Remote SQL: DELETE FROM "S 1"."T 1" WHERE ybctid = $1 RETURNING "C 1", c2, c3, ybctid + -> Foreign Scan + Output: ft2.ybctid, ft4.*, ft5.* + Filter: (ft4.c1 === ft5.c1) + Relations: ((public.ft2) INNER JOIN (public.ft4)) INNER JOIN (public.ft5) + Remote SQL: SELECT r1.ybctid, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r2.c1, r3.c1 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c2 = r2.c1)) AND ((r1."C 1" > 2000)))) INNER JOIN "S 1"."T 4" r3 ON (TRUE)) FOR UPDATE OF r1 + -> Nested Loop + Output: ft2.ybctid, ft4.*, ft5.*, ft4.c1, ft5.c1 + -> Nested Loop + Output: ft2.ybctid, ft4.*, ft4.c1 + Join Filter: (ft2.c2 = ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.ybctid, ft2.c2 + Remote Filter: (ft2.c1 > 2000) + Remote SQL: SELECT c2, ybctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" +(23 rows) + +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; + c1 | c2 | c3 +------+----+----- + 2006 | 6 | baz +(1 row) + +DELETE FROM ft2 WHERE ft2.c1 > 2000; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +-- Test that trigger on remote table works as expected +CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ +BEGIN + NEW.c3 = NEW.c3 || '_trig_update'; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE + ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG(); +INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----------------+----+----+----+------------+---- + 1208 | 818 | fff_trig_update | | | | ft2 | +(1 row) + +INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----------------+----+----+------+------------+---- + 1218 | 818 | ggg_trig_update | | | (--; | ft2 | +(1 row) + +UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+------------------------+------------------------------+--------------------------+----+------------+----- + 8 | 608 | 00008_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 18 | 608 | 00018_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 28 | 608 | 00028_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 38 | 608 | 00038_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 48 | 608 | 00048_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 58 | 608 | 00058_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 68 | 608 | 00068_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 78 | 608 | 00078_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 88 | 608 | 00088_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 98 | 608 | 00098_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 108 | 608 | 00108_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 118 | 608 | 00118_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 128 | 608 | 00128_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 138 | 608 | 00138_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 148 | 608 | 00148_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 158 | 608 | 00158_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 168 | 608 | 00168_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 178 | 608 | 00178_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 188 | 608 | 00188_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 198 | 608 | 00198_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 208 | 608 | 00208_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 218 | 608 | 00218_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 228 | 608 | 00228_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 238 | 608 | 00238_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 248 | 608 | 00248_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 258 | 608 | 00258_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 268 | 608 | 00268_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 278 | 608 | 00278_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 288 | 608 | 00288_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 298 | 608 | 00298_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 308 | 608 | 00308_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 318 | 608 | 00318_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 328 | 608 | 00328_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 338 | 608 | 00338_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 348 | 608 | 00348_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 358 | 608 | 00358_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 368 | 608 | 00368_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 378 | 608 | 00378_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 388 | 608 | 00388_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 398 | 608 | 00398_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 408 | 608 | 00408_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 418 | 608 | 00418_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 428 | 608 | 00428_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 438 | 608 | 00438_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 448 | 608 | 00448_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 458 | 608 | 00458_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 468 | 608 | 00468_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 478 | 608 | 00478_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 488 | 608 | 00488_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 498 | 608 | 00498_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 508 | 608 | 00508_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 518 | 608 | 00518_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 528 | 608 | 00528_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 538 | 608 | 00538_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 548 | 608 | 00548_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 558 | 608 | 00558_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 568 | 608 | 00568_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 578 | 608 | 00578_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 588 | 608 | 00588_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 598 | 608 | 00598_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 608 | 608 | 00608_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 618 | 608 | 00618_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 628 | 608 | 00628_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 638 | 608 | 00638_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 648 | 608 | 00648_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 658 | 608 | 00658_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 668 | 608 | 00668_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 678 | 608 | 00678_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 688 | 608 | 00688_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 698 | 608 | 00698_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 708 | 608 | 00708_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 718 | 608 | 00718_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 728 | 608 | 00728_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 738 | 608 | 00738_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 748 | 608 | 00748_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 758 | 608 | 00758_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 768 | 608 | 00768_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 778 | 608 | 00778_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 788 | 608 | 00788_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 798 | 608 | 00798_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 808 | 608 | 00808_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 818 | 608 | 00818_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 828 | 608 | 00828_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 838 | 608 | 00838_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 848 | 608 | 00848_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 858 | 608 | 00858_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 868 | 608 | 00868_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 878 | 608 | 00878_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 888 | 608 | 00888_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 898 | 608 | 00898_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 908 | 608 | 00908_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 918 | 608 | 00918_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 928 | 608 | 00928_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 938 | 608 | 00938_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 948 | 608 | 00948_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 958 | 608 | 00958_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 968 | 608 | 00968_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 978 | 608 | 00978_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 988 | 608 | 00988_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 998 | 608 | 00998_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 1008 | 708 | 0000800008_trig_update | | | | ft2 | + 1018 | 708 | 0001800018_trig_update | | | | ft2 | +(102 rows) + +-- Test errors thrown on remote side during update +ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0); +INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key +ERROR: duplicate key value violates unique constraint "t1_pkey" +CONTEXT: remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT DO NOTHING; -- works +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO NOTHING; -- unsupported +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO UPDATE SET c3 = 'ffg'; -- unsupported +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: Failing row contains (1111, -2, null, null, null, null, ft1 , null). +CONTEXT: remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo). +CONTEXT: remote SQL command: UPDATE "S 1"."T 1" SET c2 = (- c2) WHERE (("C 1" = 1)) +-- Test savepoint/rollback behavior +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 0 | 100 + 1 | 100 + 4 | 100 + 6 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 0 | 100 + 1 | 100 + 4 | 100 + 6 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +begin; +update ft2 set c2 = 42 where c2 = 0; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 4 | 100 + 6 | 100 + 42 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +savepoint s1; +update ft2 set c2 = 44 where c2 = 4; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +release savepoint s1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +savepoint s2; +update ft2 set c2 = 46 where c2 = 6; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 42 | 100 + 44 | 100 + 46 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +rollback to savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +release savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +savepoint s3; +update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: Failing row contains (10, -2, 00010_trig_update_trig_update, 1970-01-11 08:00:00+00, 1970-01-11 00:00:00, 0, 0 , foo). +CONTEXT: remote SQL command: UPDATE "S 1"."T 1" SET c2 = (-2) WHERE ((c2 = 42)) AND (("C 1" = 10)) +rollback to savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +release savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +-- none of the above is committed yet remotely +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 0 | 100 + 1 | 100 + 4 | 100 + 6 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +commit; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +VACUUM ANALYZE "S 1"."T 1"; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +-- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs +-- FIRST behavior here. +-- ORDER BY DESC NULLS LAST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 DESC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 795::bigint +(3 rows) + +SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+--------------------+------------------------------+--------------------------+------+------------+----- + 960 | 42 | 00960_trig_update | Mon Mar 02 00:00:00 1970 PST | Mon Mar 02 00:00:00 1970 | 0 | 0 | foo + 970 | 42 | 00970_trig_update | Thu Mar 12 00:00:00 1970 PST | Thu Mar 12 00:00:00 1970 | 0 | 0 | foo + 980 | 42 | 00980_trig_update | Sun Mar 22 00:00:00 1970 PST | Sun Mar 22 00:00:00 1970 | 0 | 0 | foo + 990 | 42 | 00990_trig_update | Wed Apr 01 00:00:00 1970 PST | Wed Apr 01 00:00:00 1970 | 0 | 0 | foo + 1000 | 42 | 01000_trig_update | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0 | 0 | foo + 1218 | 818 | ggg_trig_update | | | (--; | ft2 | + 1001 | 101 | 0000100001 | | | | ft2 | + 1003 | 403 | 0000300003_update3 | | | | ft2 | + 1004 | 104 | 0000400004 | | | | ft2 | + 1006 | 106 | 0000600006 | | | | ft2 | +(10 rows) + +-- ORDER BY DESC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 DESC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint +(3 rows) + +SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----------------+------------------------------+--------------------------+----+------------+----- + 1020 | 100 | 0002000020 | | | | ft2 | + 1101 | 201 | aaa | | | | ft2 | + 1103 | 503 | ccc_update3 | | | | ft2 | + 1104 | 204 | ddd | | | | ft2 | + 1208 | 818 | fff_trig_update | | | | ft2 | + 9 | 509 | 00009_update9 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | ft2 | foo + 19 | 509 | 00019_update9 | Tue Jan 20 00:00:00 1970 PST | Tue Jan 20 00:00:00 1970 | 9 | ft2 | foo + 29 | 509 | 00029_update9 | Fri Jan 30 00:00:00 1970 PST | Fri Jan 30 00:00:00 1970 | 9 | ft2 | foo + 39 | 509 | 00039_update9 | Mon Feb 09 00:00:00 1970 PST | Mon Feb 09 00:00:00 1970 | 9 | ft2 | foo + 49 | 509 | 00049_update9 | Thu Feb 19 00:00:00 1970 PST | Thu Feb 19 00:00:00 1970 | 9 | ft2 | foo +(10 rows) + +-- ORDER BY ASC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 ASC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint +(3 rows) + +SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-------------------+------------------------------+--------------------------+------+------------+----- + 1020 | 100 | 0002000020 | | | | ft2 | + 1101 | 201 | aaa | | | | ft2 | + 1103 | 503 | ccc_update3 | | | | ft2 | + 1104 | 204 | ddd | | | | ft2 | + 1208 | 818 | fff_trig_update | | | | ft2 | + 1218 | 818 | ggg_trig_update | | | (--; | ft2 | + 10 | 42 | 00010_trig_update | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo + 20 | 42 | 00020_trig_update | Wed Jan 21 00:00:00 1970 PST | Wed Jan 21 00:00:00 1970 | 0 | 0 | foo + 30 | 42 | 00030_trig_update | Sat Jan 31 00:00:00 1970 PST | Sat Jan 31 00:00:00 1970 | 0 | 0 | foo + 40 | 42 | 00040_trig_update | Tue Feb 10 00:00:00 1970 PST | Tue Feb 10 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- Test ReScan code path that recreates the cursor even when no parameters +-- change (bug #17889) +CREATE TABLE loct1 (c1 int); +CREATE TABLE loct2 (c1 int, c2 text); +INSERT INTO loct1 VALUES (1001); +INSERT INTO loct1 VALUES (1002); +INSERT INTO loct2 SELECT id, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +INSERT INTO loct2 VALUES (1001, 'foo'); +INSERT INTO loct2 VALUES (1002, 'bar'); +CREATE FOREIGN TABLE remt2 (c1 int, c2 text) SERVER loopback OPTIONS (table_name 'loct2'); +ANALYZE loct1; +ANALYZE remt2; +SET enable_mergejoin TO false; +SET enable_hashjoin TO false; +SET enable_material TO false; +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE remt2 SET c2 = remt2.c2 || remt2.c2 FROM loct1 WHERE loct1.c1 = remt2.c1 RETURNING remt2.*; + QUERY PLAN +------------------------------------------------------------------------------------------ + Update on public.remt2 + Output: remt2.c1, remt2.c2 + Remote SQL: UPDATE public.loct2 SET c2 = $2 WHERE ybctid = $1 RETURNING c1, c2, ybctid + -> Nested Loop + Output: (remt2.c2 || remt2.c2), remt2.ybctid, remt2.*, loct1.ybctid + Join Filter: (remt2.c1 = loct1.c1) + -> Seq Scan on public.loct1 + Output: loct1.ybctid, loct1.c1 + -> Foreign Scan on public.remt2 + Output: remt2.c2, remt2.ybctid, remt2.*, remt2.c1 + Remote SQL: SELECT c1, c2, ybctid FROM public.loct2 FOR UPDATE +(11 rows) + +UPDATE remt2 SET c2 = remt2.c2 || remt2.c2 FROM loct1 WHERE loct1.c1 = remt2.c1 RETURNING remt2.*; + c1 | c2 +------+-------- + 1001 | foofoo + 1002 | barbar +(2 rows) + +RESET enable_mergejoin; +RESET enable_hashjoin; +RESET enable_material; +DROP FOREIGN TABLE remt2; +DROP TABLE loct1; +DROP TABLE loct2; +-- =================================================================== +-- test check constraints +-- =================================================================== +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +----------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 < 0)) +(4 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- + 0 +(1 row) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +-------------------------------- + Aggregate + Output: count(*) + -> Result ft1 + One-Time Filter: false +(4 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- + 0 +(1 row) + +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: Failing row contains (1111, -2, null, null, null, null, ft1 , null). +CONTEXT: remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo). +CONTEXT: remote SQL command: UPDATE "S 1"."T 1" SET c2 = (- c2) WHERE (("C 1" = 1)) +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +------------------------------------------------------------------ + Foreign Scan + Output: (count(*)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) +(4 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 821 +(1 row) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +-------------------------------- + Aggregate + Output: count(*) + -> Result ft1 + One-Time Filter: false +(4 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 0 +(1 row) + +RESET constraint_exclusion; +-- local check constraint is not actually enforced +INSERT INTO ft1(c1, c2) VALUES(1111, 2); +UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; +-- =================================================================== +-- test WITH CHECK OPTION constraints +-- =================================================================== +CREATE FUNCTION row_before_insupd_trigfunc() RETURNS trigger AS $$BEGIN NEW.a := NEW.a + 10; RETURN NEW; END$$ LANGUAGE plpgsql; +CREATE TABLE base_tbl (a int, b int); +ALTER TABLE base_tbl SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON base_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +-- YB note: ERROR: VIEW WITH CHECK OPTION not supported yet, reenable when supported +CREATE VIEW rw_view AS SELECT * FROM foreign_tbl + WHERE a < b WITH CHECK OPTION; +ERROR: VIEW WITH CHECK OPTION not supported yet +LINE 2: WHERE a < b WITH CHECK OPTION; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +/* YB note: ERROR: VIEW WITH CHECK OPTION not supported yet, reenable when supported +\d+ rw_view + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); +INSERT INTO rw_view VALUES (0, 5); -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM foreign_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; +UPDATE rw_view SET b = b + 5; -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM foreign_tbl; + +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +SELECT * FROM foreign_tbl; +ALTER SERVER loopback OPTIONS (DROP batch_size); +*/ -- YB +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON base_tbl; +DROP TABLE base_tbl; +-- test WCO for partitions +CREATE TABLE child_tbl (a int, b int); +ALTER TABLE child_tbl SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON child_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'child_tbl'); +CREATE TABLE parent_tbl (a int, b int) PARTITION BY RANGE(a); +ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES FROM (0) TO (100); +-- Detach and re-attach once, to stress the concurrent detach case. +/* YB note: DETACH PARTITION CONCURRENTLY produces a crash. Uncomment when #28152 is fixed. +ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl CONCURRENTLY; +ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES FROM (0) TO (100); +*/ -- YB +CREATE VIEW rw_view AS SELECT * FROM parent_tbl + WHERE a < b WITH CHECK OPTION; +ERROR: VIEW WITH CHECK OPTION not supported yet +LINE 6: WHERE a < b WITH CHECK OPTION; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +/* YB note: ERROR: VIEW WITH CHECK OPTION not supported yet, reenable when supported +\d+ rw_view + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); +INSERT INTO rw_view VALUES (0, 5); -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM foreign_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; +UPDATE rw_view SET b = b + 5; -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM foreign_tbl; + +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +SELECT * FROM foreign_tbl; +ALTER SERVER loopback OPTIONS (DROP batch_size); +*/ -- YB +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON child_tbl; +DROP TABLE parent_tbl CASCADE; +DROP FUNCTION row_before_insupd_trigfunc; +-- =================================================================== +-- test serial columns (ie, sequence-based defaults) +-- =================================================================== +create table loc1 (f1 serial, f2 text); +alter table loc1 set (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +create foreign table rem1 (f1 serial, f2 text) + server loopback options(table_name 'loc1'); +select pg_catalog.setval('rem1_f1_seq', 10, false); + setval +-------- + 10 +(1 row) + +insert into loc1(f2) values('hi'); +insert into rem1(f2) values('hi remote'); +insert into loc1(f2) values('bye'); +insert into rem1(f2) values('bye remote'); +select * from loc1; + f1 | f2 +----+------------ + 1 | hi + 10 | hi remote + 2 | bye + 11 | bye remote +(4 rows) + +select * from rem1; + f1 | f2 +----+------------ + 1 | hi + 10 | hi remote + 2 | bye + 11 | bye remote +(4 rows) + +-- =================================================================== +-- test generated columns +-- =================================================================== +create table gloc1 ( + a int, + b int generated always as (a * 2) stored); +alter table gloc1 set (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +create foreign table grem1 ( + a int, + b int generated always as (a * 2) stored) + server loopback options(table_name 'gloc1'); +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); + QUERY PLAN +------------------------------------------------------------------- + Insert on public.grem1 + Remote SQL: INSERT INTO public.gloc1(a, b) VALUES ($1, DEFAULT) + Batch Size: 1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, NULL::integer +(5 rows) + +insert into grem1 (a) values (1), (2); +explain (verbose, costs off) +update grem1 set a = 22 where a = 2; + QUERY PLAN +-------------------------------------------------------------------------------------- + Update on public.grem1 + Remote SQL: UPDATE public.gloc1 SET a = $2, b = DEFAULT WHERE ybctid = $1 + -> Foreign Scan on public.grem1 + Output: 22, ybctid, grem1.* + Remote Filter: (grem1.a = 2) + Remote SQL: SELECT a, b, ybctid FROM public.gloc1 WHERE ((a = 2)) FOR UPDATE +(6 rows) + +update grem1 set a = 22 where a = 2; +select * from gloc1; + a | b +----+---- + 1 | 2 + 22 | 44 +(2 rows) + +select * from grem1; + a | b +----+---- + 1 | 2 + 22 | 44 +(2 rows) + +delete from grem1; +-- test copy from +copy grem1 from stdin; +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +select * from gloc1; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +select * from grem1; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +delete from grem1; +-- test batch insert +alter server loopback options (add batch_size '10'); +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); + QUERY PLAN +------------------------------------------------------------------- + Insert on public.grem1 + Remote SQL: INSERT INTO public.gloc1(a, b) VALUES ($1, DEFAULT) + Batch Size: 10 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, NULL::integer +(5 rows) + +insert into grem1 (a) values (1), (2); +select * from gloc1; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +select * from grem1; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +delete from grem1; +-- batch insert with foreign partitions. +-- This schema uses two partitions, one local and one remote with a modulo +-- to loop across all of them in batches. +create table tab_batch_local (id int, data text); +insert into tab_batch_local select i, 'test'|| i from generate_series(1, 45) i; +create table tab_batch_sharded (id int, data text) partition by hash(id); +create table tab_batch_sharded_p0 partition of tab_batch_sharded + for values with (modulus 2, remainder 0); +create table tab_batch_sharded_p1_remote (id int, data text); +create foreign table tab_batch_sharded_p1 partition of tab_batch_sharded + for values with (modulus 2, remainder 1) + server loopback options (table_name 'tab_batch_sharded_p1_remote'); +insert into tab_batch_sharded select * from tab_batch_local; +select count(*) from tab_batch_sharded; + count +------- + 45 +(1 row) + +drop table tab_batch_local; +drop table tab_batch_sharded; +drop table tab_batch_sharded_p1_remote; +alter server loopback options (drop batch_size); +-- =================================================================== +-- test local triggers +-- =================================================================== +-- Trigger functions "borrowed" from triggers regress test. +CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %', + TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; + RETURN NULL; +END;$$; +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger +LANGUAGE plpgsql AS $$ + +declare + oldnew text[]; + relid text; + argstr text; +begin + + relid := TG_relid::regclass; + argstr := ''; + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; + + RAISE NOTICE '%(%) % % % ON %', + tg_name, argstr, TG_when, TG_level, TG_OP, relid; + oldnew := '{}'::text[]; + if TG_OP != 'INSERT' then + oldnew := array_append(oldnew, format('OLD: %s', OLD)); + end if; + + if TG_OP != 'DELETE' then + oldnew := array_append(oldnew, format('NEW: %s', NEW)); + end if; + + RAISE NOTICE '%', array_to_string(oldnew, ','); + + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; +end; +$$; +-- Test basic functionality +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +delete from rem1; +NOTICE: trigger_func() called: action = DELETE, when = BEFORE, level = STATEMENT +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 +NOTICE: OLD: (1,hi) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 +NOTICE: OLD: (10,"hi remote") +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 +NOTICE: OLD: (2,bye) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 +NOTICE: OLD: (11,"bye remote") +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (1,hi) +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (10,"hi remote") +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (2,bye) +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (11,"bye remote") +NOTICE: trigger_func() called: action = DELETE, when = AFTER, level = STATEMENT +insert into rem1 values(1,'insert'); +NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1 +NOTICE: NEW: (1,insert) +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (1,insert) +NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT +update rem1 set f2 = 'update' where f1 = 1; +NOTICE: trigger_func() called: action = UPDATE, when = BEFORE, level = STATEMENT +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1 +NOTICE: OLD: (1,insert),NEW: (1,update) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,insert),NEW: (1,update) +NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT +update rem1 set f2 = f2 || f2; +NOTICE: trigger_func() called: action = UPDATE, when = BEFORE, level = STATEMENT +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1 +NOTICE: OLD: (1,update),NEW: (1,updateupdate) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,update),NEW: (1,updateupdate) +NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT +-- cleanup +DROP TRIGGER trig_row_before ON rem1; +DROP TRIGGER trig_row_after ON rem1; +DROP TRIGGER trig_stmt_before ON rem1; +DROP TRIGGER trig_stmt_after ON rem1; +DELETE from rem1; +-- Test multiple AFTER ROW triggers on a foreign table +CREATE TRIGGER trig_row_after1 +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after2 +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +insert into rem1 values(1,'insert'); +NOTICE: trig_row_after1(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (1,insert) +NOTICE: trig_row_after2(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (1,insert) +update rem1 set f2 = 'update' where f1 = 1; +NOTICE: trig_row_after1(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,insert),NEW: (1,update) +NOTICE: trig_row_after2(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,insert),NEW: (1,update) +update rem1 set f2 = f2 || f2; +NOTICE: trig_row_after1(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,update),NEW: (1,updateupdate) +NOTICE: trig_row_after2(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,update),NEW: (1,updateupdate) +delete from rem1; +NOTICE: trig_row_after1(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (1,updateupdate) +NOTICE: trig_row_after2(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (1,updateupdate) +-- cleanup +DROP TRIGGER trig_row_after1 ON rem1; +DROP TRIGGER trig_row_after2 ON rem1; +-- Test WHEN conditions +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after_insupd +AFTER INSERT OR UPDATE ON rem1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +-- Insert or update not matching: nothing happens +INSERT INTO rem1 values(1, 'insert'); +UPDATE rem1 set f2 = 'test'; +-- Insert or update matching: triggers are fired +INSERT INTO rem1 values(2, 'update'); +NOTICE: trig_row_before_insupd(23, skidoo) BEFORE ROW INSERT ON rem1 +NOTICE: NEW: (2,update) +NOTICE: trig_row_after_insupd(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (2,update) +UPDATE rem1 set f2 = 'update update' where f1 = '2'; +NOTICE: trig_row_before_insupd(23, skidoo) BEFORE ROW UPDATE ON rem1 +NOTICE: OLD: (2,update),NEW: (2,"update update") +NOTICE: trig_row_after_insupd(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (2,update),NEW: (2,"update update") +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON rem1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON rem1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +-- Trigger is fired for f1=2, not for f1=1 +DELETE FROM rem1; +NOTICE: trig_row_before_delete(23, skidoo) BEFORE ROW DELETE ON rem1 +NOTICE: OLD: (2,"update update") +NOTICE: trig_row_after_delete(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (2,"update update") +-- cleanup +DROP TRIGGER trig_row_before_insupd ON rem1; +DROP TRIGGER trig_row_after_insupd ON rem1; +DROP TRIGGER trig_row_before_delete ON rem1; +DROP TRIGGER trig_row_after_delete ON rem1; +-- Test various RETURN statements in BEFORE triggers. +CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$ + BEGIN + NEW.f2 := NEW.f2 || ' triggered !'; + RETURN NEW; + END +$$ language plpgsql; +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); +-- The new values should have 'triggered' appended +INSERT INTO rem1 values(1, 'insert'); +SELECT * from loc1; + f1 | f2 +----+-------------------- + 1 | insert triggered ! +(1 row) + +INSERT INTO rem1 values(2, 'insert') RETURNING f2; + f2 +-------------------- + insert triggered ! +(1 row) + +SELECT * from loc1; + f1 | f2 +----+-------------------- + 1 | insert triggered ! + 2 | insert triggered ! +(2 rows) + +UPDATE rem1 set f2 = ''; +SELECT * from loc1; + f1 | f2 +----+-------------- + 1 | triggered ! + 2 | triggered ! +(2 rows) + +UPDATE rem1 set f2 = 'skidoo' RETURNING f2; + f2 +-------------------- + skidoo triggered ! + skidoo triggered ! +(2 rows) + +SELECT * from loc1; + f1 | f2 +----+-------------------- + 1 | skidoo triggered ! + 2 | skidoo triggered ! +(2 rows) + +EXPLAIN (verbose, costs off) +UPDATE rem1 set f1 = 10; -- all columns should be transmitted + QUERY PLAN +------------------------------------------------------------------------- + Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ybctid = $1 + -> Foreign Scan on public.rem1 + Output: 10, ybctid, rem1.* + Remote SQL: SELECT f1, f2, ybctid FROM public.loc1 FOR UPDATE +(5 rows) + +UPDATE rem1 set f1 = 10; +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 10 | skidoo triggered ! triggered ! + 10 | skidoo triggered ! triggered ! +(2 rows) + +DELETE FROM rem1; +-- Add a second trigger, to check that the changes are propagated correctly +-- from trigger to trigger +CREATE TRIGGER trig_row_before_insupd2 +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); +INSERT INTO rem1 values(1, 'insert'); +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 1 | insert triggered ! triggered ! +(1 row) + +INSERT INTO rem1 values(2, 'insert') RETURNING f2; + f2 +-------------------------------- + insert triggered ! triggered ! +(1 row) + +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 1 | insert triggered ! triggered ! + 2 | insert triggered ! triggered ! +(2 rows) + +UPDATE rem1 set f2 = ''; +SELECT * from loc1; + f1 | f2 +----+-------------------------- + 1 | triggered ! triggered ! + 2 | triggered ! triggered ! +(2 rows) + +UPDATE rem1 set f2 = 'skidoo' RETURNING f2; + f2 +-------------------------------- + skidoo triggered ! triggered ! + skidoo triggered ! triggered ! +(2 rows) + +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 1 | skidoo triggered ! triggered ! + 2 | skidoo triggered ! triggered ! +(2 rows) + +DROP TRIGGER trig_row_before_insupd ON rem1; +DROP TRIGGER trig_row_before_insupd2 ON rem1; +DELETE from rem1; +INSERT INTO rem1 VALUES (1, 'test'); +-- Test with a trigger returning NULL +CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$ + BEGIN + RETURN NULL; + END +$$ language plpgsql; +CREATE TRIGGER trig_null +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_null(); +-- Nothing should have changed. +INSERT INTO rem1 VALUES (2, 'test2'); +SELECT * from loc1; + f1 | f2 +----+------ + 1 | test +(1 row) + +UPDATE rem1 SET f2 = 'test2'; +SELECT * from loc1; + f1 | f2 +----+------ + 1 | test +(1 row) + +DELETE from rem1; +SELECT * from loc1; + f1 | f2 +----+------ + 1 | test +(1 row) + +DROP TRIGGER trig_null ON rem1; +DELETE from rem1; +-- Test a combination of local and remote triggers +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); +INSERT INTO rem1(f2) VALUES ('test'); +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1 +NOTICE: NEW: (12,test) +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (12,"test triggered !") +UPDATE rem1 SET f2 = 'testo'; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1 +NOTICE: OLD: (12,"test triggered !"),NEW: (12,testo) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (12,"test triggered !"),NEW: (12,"testo triggered !") +-- Test returning a system attribute +-- YB note: YugabyteDB uses ybctid as the tuple identifier, not ctid. Since +-- rem1 (loc1) does not have a primary key, add one to make the returned ybctid +-- deterministic. +ALTER TABLE loc1 ADD PRIMARY KEY (f1); +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +INSERT INTO rem1(f2) VALUES ('test') RETURNING ybctid; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1 +NOTICE: NEW: (13,test) +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (13,"test triggered !") + ybctid +---------------- + \x488000000d21 +(1 row) + +-- YB note: See above. +ALTER TABLE loc1 DROP CONSTRAINT loc1_pkey; +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +-- cleanup +DROP TRIGGER trig_row_before ON rem1; +DROP TRIGGER trig_row_after ON rem1; +DROP TRIGGER trig_local_before ON loc1; +-- Test direct foreign table modification functionality +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1 WHERE false; -- currently can't be pushed down + QUERY PLAN +--------------------------------------------------------- + Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 WHERE ybctid = $1 + -> Result rem1 + Output: ybctid + One-Time Filter: false +(5 rows) + +-- Test with statement-level triggers +CREATE TRIGGER trig_stmt_before + BEFORE DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_stmt_before ON rem1; +CREATE TRIGGER trig_stmt_after + AFTER DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_stmt_after ON rem1; +-- Test with row-level ON INSERT triggers +CREATE TRIGGER trig_row_before_insert +BEFORE INSERT ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_row_before_insert ON rem1; +CREATE TRIGGER trig_row_after_insert +AFTER INSERT ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_row_after_insert ON rem1; +-- Test with row-level ON UPDATE triggers +CREATE TRIGGER trig_row_before_update +BEFORE UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down + QUERY PLAN +------------------------------------------------------------------------- + Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ybctid = $1 + -> Foreign Scan on public.rem1 + Output: ''::text, ybctid, rem1.* + Remote SQL: SELECT f1, f2, ybctid FROM public.loc1 FOR UPDATE +(5 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_row_before_update ON rem1; +CREATE TRIGGER trig_row_after_update +AFTER UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down + QUERY PLAN +----------------------------------------------------------------------------------------- + Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = $2 WHERE ybctid = $1 RETURNING f1, f2, ybctid + -> Foreign Scan on public.rem1 + Output: ''::text, ybctid, rem1.* + Remote SQL: SELECT f1, f2, ybctid FROM public.loc1 FOR UPDATE +(5 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_row_after_update ON rem1; +-- Test with row-level ON DELETE triggers +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down + QUERY PLAN +----------------------------------------------------------------------- + Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 WHERE ybctid = $1 + -> Foreign Scan on public.rem1 + Output: ybctid, rem1.* + Remote SQL: SELECT f1, f2, ybctid FROM public.loc1 FOR UPDATE +(5 rows) + +DROP TRIGGER trig_row_before_delete ON rem1; +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down + QUERY PLAN +---------------------------------------------------------------------------------- + Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 WHERE ybctid = $1 RETURNING f1, f2, ybctid + -> Foreign Scan on public.rem1 + Output: ybctid, rem1.* + Remote SQL: SELECT f1, f2, ybctid FROM public.loc1 FOR UPDATE +(5 rows) + +DROP TRIGGER trig_row_after_delete ON rem1; +-- =================================================================== +-- test inheritance features +-- =================================================================== +CREATE TABLE a (aa TEXT); +CREATE TABLE loct (aa TEXT, bb TEXT); +ALTER TABLE a SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +ALTER TABLE loct SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a) + SERVER loopback OPTIONS (table_name 'loct'); +INSERT INTO a(aa) VALUES('aaa'); +INSERT INTO a(aa) VALUES('aaaa'); +INSERT INTO a(aa) VALUES('aaaaa'); +INSERT INTO b(aa) VALUES('bbb'); +INSERT INTO b(aa) VALUES('bbbb'); +INSERT INTO b(aa) VALUES('bbbbb'); +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+------- + a | aaa + a | aaaa + a | aaaaa + b | bbb + b | bbbb + b | bbbbb +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+-------+---- + b | bbb | + b | bbbb | + b | bbbbb | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+------- + a | aaa + a | aaaa + a | aaaaa +(3 rows) + +UPDATE a SET aa = 'zzzzzz' WHERE aa LIKE 'aaaa%'; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz + b | bbb + b | bbbb + b | bbbbb +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+-------+---- + b | bbb | + b | bbbb | + b | bbbbb | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz +(3 rows) + +UPDATE b SET aa = 'new'; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz + b | new + b | new + b | new +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+-----+---- + b | new | + b | new | + b | new | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz +(3 rows) + +UPDATE a SET aa = 'newtoo'; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+-------- + a | newtoo + a | newtoo + a | newtoo + b | newtoo + b | newtoo + b | newtoo +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+--------+---- + b | newtoo | + b | newtoo | + b | newtoo | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+-------- + a | newtoo + a | newtoo + a | newtoo +(3 rows) + +DELETE FROM a; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+---- +(0 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+----+---- +(0 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+---- +(0 rows) + +DROP TABLE a CASCADE; +NOTICE: drop cascades to foreign table b +DROP TABLE loct; +-- Check SELECT FOR UPDATE/SHARE with an inherited source table +create table loct1 (f1 int, f2 int, f3 int); +create table loct2 (f1 int, f2 int, f3 int); +alter table loct1 set (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +alter table loct2 set (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +create table foo (f1 int, f2 int); +create foreign table foo2 (f3 int) inherits (foo) + server loopback options (table_name 'loct1'); +create table bar (f1 int, f2 int); +create foreign table bar2 (f3 int) inherits (bar) + server loopback options (table_name 'loct2'); +alter table foo set (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +alter table bar set (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +insert into foo values(1,1); +insert into foo values(3,3); +insert into foo2 values(2,2,2); +insert into foo2 values(4,4,4); +insert into bar values(1,11); +insert into bar values(2,22); +insert into bar values(6,66); +insert into bar2 values(3,33,33); +insert into bar2 values(4,44,44); +insert into bar2 values(7,77,77); +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo) for update; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + LockRows + Output: bar.f1, bar.f2, bar.ybctid, foo.ybctid, bar.*, bar.tableoid, foo.*, foo.tableoid + -> Hash Join + Output: bar.f1, bar.f2, bar.ybctid, foo.ybctid, bar.*, bar.tableoid, foo.*, foo.tableoid + Inner Unique: true + Hash Cond: (bar.f1 = foo.f1) + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f1, bar_1.f2, bar_1.ybctid, bar_1.*, bar_1.tableoid + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f1, bar_2.f2, bar_2.ybctid, bar_2.*, bar_2.tableoid + Remote SQL: SELECT f1, f2, f3, ybctid FROM public.loct2 FOR UPDATE + -> Hash + Output: foo.ybctid, foo.f1, foo.*, foo.tableoid + -> HashAggregate + Output: foo.ybctid, foo.f1, foo.*, foo.tableoid + Group Key: foo.f1 + -> Append + -> Seq Scan on public.foo foo_1 + Output: foo_1.ybctid, foo_1.f1, foo_1.*, foo_1.tableoid + -> Foreign Scan on public.foo2 foo_2 + Output: foo_2.ybctid, foo_2.f1, foo_2.*, foo_2.tableoid + Remote SQL: SELECT f1, f2, f3, ybctid FROM public.loct1 +(23 rows) + +select * from bar where f1 in (select f1 from foo) for update; + f1 | f2 +----+---- + 1 | 11 + 2 | 22 + 3 | 33 + 4 | 44 +(4 rows) + +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo) for share; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + LockRows + Output: bar.f1, bar.f2, bar.ybctid, foo.ybctid, bar.*, bar.tableoid, foo.*, foo.tableoid + -> Hash Join + Output: bar.f1, bar.f2, bar.ybctid, foo.ybctid, bar.*, bar.tableoid, foo.*, foo.tableoid + Inner Unique: true + Hash Cond: (bar.f1 = foo.f1) + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f1, bar_1.f2, bar_1.ybctid, bar_1.*, bar_1.tableoid + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f1, bar_2.f2, bar_2.ybctid, bar_2.*, bar_2.tableoid + Remote SQL: SELECT f1, f2, f3, ybctid FROM public.loct2 FOR SHARE + -> Hash + Output: foo.ybctid, foo.f1, foo.*, foo.tableoid + -> HashAggregate + Output: foo.ybctid, foo.f1, foo.*, foo.tableoid + Group Key: foo.f1 + -> Append + -> Seq Scan on public.foo foo_1 + Output: foo_1.ybctid, foo_1.f1, foo_1.*, foo_1.tableoid + -> Foreign Scan on public.foo2 foo_2 + Output: foo_2.ybctid, foo_2.f1, foo_2.*, foo_2.tableoid + Remote SQL: SELECT f1, f2, f3, ybctid FROM public.loct1 +(23 rows) + +select * from bar where f1 in (select f1 from foo) for share; + f1 | f2 +----+---- + 1 | 11 + 2 | 22 + 3 | 33 + 4 | 44 +(4 rows) + +-- Now check SELECT FOR UPDATE/SHARE with an inherited source table, +-- where the parent is itself a foreign table +create table loct4 (f1 int, f2 int, f3 int); +create foreign table foo2child (f3 int) inherits (foo2) + server loopback options (table_name 'loct4'); +NOTICE: moving and merging column "f3" with inherited definition +DETAIL: User-specified column moved to the position of the inherited column. +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo2) for share; + QUERY PLAN +---------------------------------------------------------------------------------------- + LockRows + Output: bar.f1, bar.f2, bar.ybctid, foo2.*, bar.*, bar.tableoid, foo2.tableoid + -> Hash Join + Output: bar.f1, bar.f2, bar.ybctid, foo2.*, bar.*, bar.tableoid, foo2.tableoid + Inner Unique: true + Hash Cond: (bar.f1 = foo2.f1) + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f1, bar_1.f2, bar_1.ybctid, bar_1.*, bar_1.tableoid + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f1, bar_2.f2, bar_2.ybctid, bar_2.*, bar_2.tableoid + Remote SQL: SELECT f1, f2, f3, ybctid FROM public.loct2 FOR SHARE + -> Hash + Output: foo2.*, foo2.f1, foo2.tableoid + -> HashAggregate + Output: foo2.*, foo2.f1, foo2.tableoid + Group Key: foo2.f1 + -> Append + -> Foreign Scan on public.foo2 foo2_1 + Output: foo2_1.*, foo2_1.f1, foo2_1.tableoid + Remote SQL: SELECT f1, f2, f3 FROM public.loct1 + -> Foreign Scan on public.foo2child foo2_2 + Output: foo2_2.*, foo2_2.f1, foo2_2.tableoid + Remote SQL: SELECT f1, f2, f3 FROM public.loct4 +(24 rows) + +select * from bar where f1 in (select f1 from foo2) for share; + f1 | f2 +----+---- + 2 | 22 + 4 | 44 +(2 rows) + +drop foreign table foo2child; +-- And with a local child relation of the foreign table parent +create table foo2child (f3 int) inherits (foo2); +NOTICE: moving and merging column "f3" with inherited definition +DETAIL: User-specified column moved to the position of the inherited column. +-- YB note: The following queries fail to construct row marks. (#28151) +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo2) for share; +ERROR: could not find junk ybctid2 column +select * from bar where f1 in (select f1 from foo2) for share; +ERROR: could not find junk ybctid2 column +drop table foo2child; +-- Check UPDATE with inherited target and an inherited source table +explain (verbose, costs off) +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Update on public.bar + Update on public.bar bar_1 + Foreign Update on public.bar2 bar_2 + Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ybctid = $1 + -> Hash Join + Output: (bar.f2 + 100), foo.ybctid, bar.tableoid, bar.*, bar.ybctid, foo.*, foo.tableoid + Inner Unique: true + Hash Cond: (bar.f1 = foo.f1) + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f2, bar_1.f1, bar_1.tableoid, bar_1.*, bar_1.ybctid + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f2, bar_2.f1, bar_2.tableoid, bar_2.*, bar_2.ybctid + Remote SQL: SELECT f1, f2, f3, ybctid FROM public.loct2 FOR UPDATE + -> Hash + Output: foo.ybctid, foo.f1, foo.*, foo.tableoid + -> HashAggregate + Output: foo.ybctid, foo.f1, foo.*, foo.tableoid + Group Key: foo.f1 + -> Append + -> Seq Scan on public.foo foo_1 + Output: foo_1.ybctid, foo_1.f1, foo_1.*, foo_1.tableoid + -> Foreign Scan on public.foo2 foo_2 + Output: foo_2.ybctid, foo_2.f1, foo_2.*, foo_2.tableoid + Remote SQL: SELECT f1, f2, f3, ybctid FROM public.loct1 +(25 rows) + +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); +select tableoid::regclass, * from bar order by 1,2; + tableoid | f1 | f2 +----------+----+----- + bar | 1 | 111 + bar | 2 | 122 + bar | 6 | 66 + bar2 | 3 | 133 + bar2 | 4 | 144 + bar2 | 7 | 77 +(6 rows) + +-- Check UPDATE with inherited target and an appendrel subquery +explain (verbose, costs off) +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Update on public.bar + Update on public.bar bar_1 + Foreign Update on public.bar2 bar_2 + Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ybctid = $1 + -> Merge Join + Output: (bar.f2 + 100), (ROW(foo_1.f1)), bar.tableoid, bar.*, bar.ybctid + Merge Cond: (bar.f1 = foo_1.f1) + -> Sort + Output: bar.f2, bar.f1, bar.tableoid, bar.*, bar.ybctid + Sort Key: bar.f1 + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f2, bar_1.f1, bar_1.tableoid, bar_1.*, bar_1.ybctid + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f2, bar_2.f1, bar_2.tableoid, bar_2.*, bar_2.ybctid + Remote SQL: SELECT f1, f2, f3, ybctid FROM public.loct2 FOR UPDATE + -> Sort + Output: (ROW(foo_1.f1)), foo_1.f1 + Sort Key: foo_1.f1 + -> Append + -> Seq Scan on public.foo foo_1 + Output: ROW(foo_1.f1), foo_1.f1 + -> Foreign Scan on public.foo2 foo_2 + Output: ROW(foo_2.f1), foo_2.f1 + Remote SQL: SELECT f1 FROM public.loct1 + -> Seq Scan on public.foo foo_4 + Output: ROW((foo_4.f1 + 3)), (foo_4.f1 + 3) + -> Foreign Scan on public.foo2 foo_5 + Output: ROW((foo_5.f1 + 3)), (foo_5.f1 + 3) + Remote SQL: SELECT f1 FROM public.loct1 +(30 rows) + +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; +select tableoid::regclass, * from bar order by 1,2; + tableoid | f1 | f2 +----------+----+----- + bar | 1 | 211 + bar | 2 | 222 + bar | 6 | 166 + bar2 | 3 | 233 + bar2 | 4 | 244 + bar2 | 7 | 177 +(6 rows) + +-- Test forcing the remote server to produce sorted data for a merge join, +-- but the foreign table is an inheritance child. +truncate table loct1; +truncate table only foo; +\set num_rows_foo 2000 +insert into loct1 select generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2); +insert into foo select generate_series(1, :num_rows_foo, 2), generate_series(1, :num_rows_foo, 2); +SET enable_hashjoin to false; +SET yb_enable_batchednl to false; +SET enable_nestloop to false; +alter foreign table foo2 options (use_remote_estimate 'true'); +create index i_loct1_f1 on loct1(f1); +create index i_foo_f1 on foo(f1); +analyze foo; +analyze loct1; +-- inner join; expressions in the clauses appear in the equivalence class list +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Limit + Output: foo.f1, loct1.f1, foo.f2 + -> Sort + Output: foo.f1, loct1.f1, foo.f2 + Sort Key: foo.f2 + -> Merge Join + Output: foo.f1, loct1.f1, foo.f2 + Merge Cond: (foo.f1 = loct1.f1) + -> Merge Append + Sort Key: foo.f1 + -> Index Scan using i_foo_f1 on public.foo foo_1 + Output: foo_1.f1, foo_1.f2 + -> Foreign Scan on public.foo2 foo_2 + Output: foo_2.f1, foo_2.f2 + Remote SQL: SELECT f1, f2 FROM public.loct1 ORDER BY f1 ASC NULLS LAST + -> Materialize + Output: loct1.f1 + -> Index Only Scan using i_loct1_f1 on public.loct1 + Output: loct1.f1 +(19 rows) + +select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; + f1 | f1 +----+---- + 20 | 20 + 22 | 22 + 24 | 24 + 26 | 26 + 28 | 28 + 30 | 30 + 32 | 32 + 34 | 34 + 36 | 36 + 38 | 38 +(10 rows) + +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Limit + Output: foo.f1, loct1.f1, foo.f2 + -> Sort + Output: foo.f1, loct1.f1, foo.f2 + Sort Key: foo.f2 + -> Merge Left Join + Output: foo.f1, loct1.f1, foo.f2 + Merge Cond: (foo.f1 = loct1.f1) + -> Merge Append + Sort Key: foo.f1 + -> Index Scan using i_foo_f1 on public.foo foo_1 + Output: foo_1.f1, foo_1.f2 + -> Foreign Scan on public.foo2 foo_2 + Output: foo_2.f1, foo_2.f2 + Remote SQL: SELECT f1, f2 FROM public.loct1 ORDER BY f1 ASC NULLS LAST + -> Materialize + Output: loct1.f1 + -> Index Only Scan using i_loct1_f1 on public.loct1 + Output: loct1.f1 +(19 rows) + +select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; + f1 | f1 +----+---- + 10 | 10 + 11 | + 12 | 12 + 13 | + 14 | 14 + 15 | + 16 | 16 + 17 | + 18 | 18 + 19 | +(10 rows) + +RESET enable_hashjoin; +RESET yb_enable_batchednl; +RESET enable_nestloop; +-- Test that WHERE CURRENT OF is not supported +begin; +declare c cursor for select * from bar where f1 = 7; +fetch from c; + f1 | f2 +----+----- + 7 | 177 +(1 row) + +-- YB note: WHERE CURRENT OF is currently unsupported. (#737) +update bar set f2 = null where current of c; +ERROR: WHERE CURRENT OF not supported yet +LINE 1: update bar set f2 = null where current of c; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +rollback; +explain (verbose, costs off) +delete from foo where f1 < 5 returning *; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Delete on public.foo + Output: foo_1.f1, foo_1.f2 + Delete on public.foo foo_1 + Foreign Delete on public.foo2 foo_2 + -> Append + -> Index Scan using i_foo_f1 on public.foo foo_1 + Output: foo_1.f1, foo_1.f2, foo_1.tableoid, foo_1.*, foo_1.ybctid + Index Cond: (foo_1.f1 < 5) + -> Foreign Delete on public.foo2 foo_2 + Remote Filter: (foo_2.f1 < 5) + Remote SQL: DELETE FROM public.loct1 WHERE ((f1 < 5)) RETURNING f1, f2, ybctid +(11 rows) + +delete from foo where f1 < 5 returning *; + f1 | f2 +----+---- + 1 | 1 + 3 | 3 + 0 | 0 + 2 | 2 + 4 | 4 +(5 rows) + +explain (verbose, costs off) +update bar set f2 = f2 + 100 returning *; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Update on public.bar + Output: bar_1.f1, bar_1.f2 + Update on public.bar bar_1 + Foreign Update on public.bar2 bar_2 + -> Result bar + Output: (bar.f2 + 100), bar.tableoid, bar.*, bar.ybctid + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f2, bar_1.tableoid, bar_1.*, bar_1.ybctid + -> Foreign Update on public.bar2 bar_2 + Remote SQL: UPDATE public.loct2 SET f2 = (f2 + 100) RETURNING f1, f2, ybctid +(11 rows) + +update bar set f2 = f2 + 100 returning *; + f1 | f2 +----+----- + 1 | 311 + 2 | 322 + 6 | 266 + 3 | 333 + 4 | 344 + 7 | 277 +(6 rows) + +-- Test that UPDATE/DELETE with inherited target works with row-level triggers +CREATE TRIGGER trig_row_before +BEFORE UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after +AFTER UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +explain (verbose, costs off) +update bar set f2 = f2 + 100; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------ + Update on public.bar + Update on public.bar bar_1 + Foreign Update on public.bar2 bar_2 + Remote SQL: UPDATE public.loct2 SET f1 = $2, f2 = $3, f3 = $4 WHERE ybctid = $1 RETURNING f1, f2, f3, ybctid + -> Result bar + Output: (bar.f2 + 100), bar.tableoid, bar.*, bar.ybctid + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f2, bar_1.tableoid, bar_1.*, bar_1.ybctid + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f2, bar_2.tableoid, bar_2.*, bar_2.ybctid + Remote SQL: SELECT f1, f2, f3, ybctid FROM public.loct2 FOR UPDATE +(12 rows) + +update bar set f2 = f2 + 100; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2 +NOTICE: OLD: (3,333,33),NEW: (3,433,33) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2 +NOTICE: OLD: (4,344,44),NEW: (4,444,44) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2 +NOTICE: OLD: (7,277,77),NEW: (7,377,77) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2 +NOTICE: OLD: (3,333,33),NEW: (3,433,33) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2 +NOTICE: OLD: (4,344,44),NEW: (4,444,44) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2 +NOTICE: OLD: (7,277,77),NEW: (7,377,77) +explain (verbose, costs off) +delete from bar where f2 < 400; + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Delete on public.bar + Delete on public.bar bar_1 + Foreign Delete on public.bar2 bar_2 + Remote SQL: DELETE FROM public.loct2 WHERE ybctid = $1 RETURNING f1, f2, f3, ybctid + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.tableoid, bar_1.ybctid, NULL::record + Storage Filter: (bar_1.f2 < 400) + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.tableoid, bar_2.ybctid, bar_2.* + Remote Filter: (bar_2.f2 < 400) + Remote SQL: SELECT f1, f2, f3, ybctid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE +(12 rows) + +delete from bar where f2 < 400; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON bar2 +NOTICE: OLD: (7,377,77) +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON bar2 +NOTICE: OLD: (7,377,77) +-- cleanup +drop table foo cascade; +NOTICE: drop cascades to foreign table foo2 +drop table bar cascade; +NOTICE: drop cascades to foreign table bar2 +drop table loct1; +drop table loct2; +-- Test pushing down UPDATE/DELETE joins to the remote server +create table parent (a int, b text); +create table loct1 (a int, b text); +create table loct2 (a int, b text); +create foreign table remt1 (a int, b text) + server loopback options (table_name 'loct1'); +create foreign table remt2 (a int, b text) + server loopback options (table_name 'loct2'); +alter foreign table remt1 inherit parent; +insert into remt1 values (1, 'foo'); +insert into remt1 values (2, 'bar'); +insert into remt2 values (1, 'foo'); +insert into remt2 values (2, 'bar'); +analyze remt1; +analyze remt2; +explain (verbose, costs off) +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Update on public.parent + Output: parent_1.a, parent_1.b, remt2.a, remt2.b + Update on public.parent parent_1 + Foreign Update on public.remt1 parent_2 + Remote SQL: UPDATE public.loct1 SET b = $2 WHERE ybctid = $1 RETURNING a, b, ybctid + -> Hash Join + Output: (parent.b || remt2.b), remt2.*, remt2.a, remt2.b, parent.tableoid, parent.*, parent.ybctid + Hash Cond: (parent.a = remt2.a) + -> Append + -> Seq Scan on public.parent parent_1 + Output: parent_1.b, parent_1.a, parent_1.tableoid, parent_1.*, parent_1.ybctid + -> Foreign Scan on public.remt1 parent_2 + Output: parent_2.b, parent_2.a, parent_2.tableoid, parent_2.*, parent_2.ybctid + Remote SQL: SELECT a, b, ybctid FROM public.loct1 FOR UPDATE + -> Hash + Output: remt2.b, remt2.*, remt2.a + -> Foreign Scan on public.remt2 + Output: remt2.b, remt2.*, remt2.a + Remote SQL: SELECT a, b FROM public.loct2 +(19 rows) + +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; + a | b | a | b +---+--------+---+----- + 1 | foofoo | 1 | foo + 2 | barbar | 2 | bar +(2 rows) + +explain (verbose, costs off) +delete from parent using remt2 where parent.a = remt2.a returning parent; + QUERY PLAN +---------------------------------------------------------------------------------------- + Delete on public.parent + Output: parent_1.* + Delete on public.parent parent_1 + Foreign Delete on public.remt1 parent_2 + Remote SQL: DELETE FROM public.loct1 WHERE ybctid = $1 RETURNING a, b, ybctid + -> Hash Join + Output: parent.a, parent.b, remt2.*, parent.tableoid, parent.ybctid + Hash Cond: (parent.a = remt2.a) + -> Append + -> Seq Scan on public.parent parent_1 + Output: parent_1.a, parent_1.b, parent_1.tableoid, parent_1.ybctid + -> Foreign Scan on public.remt1 parent_2 + Output: parent_2.a, parent_2.b, parent_2.tableoid, parent_2.ybctid + Remote SQL: SELECT a, b, ybctid FROM public.loct1 FOR UPDATE + -> Hash + Output: remt2.*, remt2.a + -> Foreign Scan on public.remt2 + Output: remt2.*, remt2.a + Remote SQL: SELECT a, b FROM public.loct2 +(19 rows) + +delete from parent using remt2 where parent.a = remt2.a returning parent; + parent +------------ + (1,foofoo) + (2,barbar) +(2 rows) + +-- cleanup +drop foreign table remt1; +drop foreign table remt2; +drop table loct1; +drop table loct2; +drop table parent; +-- =================================================================== +-- test tuple routing for foreign-table partitions +-- =================================================================== +-- Test insert tuple routing +create table itrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table itrtest attach partition remp1 for values in (1); +alter table itrtest attach partition remp2 for values in (2); +insert into itrtest values (1, 'foo'); +insert into itrtest values (1, 'bar') returning *; + a | b +---+----- + 1 | bar +(1 row) + +insert into itrtest values (2, 'baz'); +insert into itrtest values (2, 'qux') returning *; + a | b +---+----- + 2 | qux +(1 row) + +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; + a | b +---+------- + 1 | test1 + 2 | test2 +(2 rows) + +select tableoid::regclass, * FROM itrtest; + tableoid | a | b +----------+---+------- + remp1 | 1 | foo + remp1 | 1 | bar + remp1 | 1 | test1 + remp2 | 2 | baz + remp2 | 2 | qux + remp2 | 2 | test2 +(6 rows) + +select tableoid::regclass, * FROM remp1; + tableoid | a | b +----------+---+------- + remp1 | 1 | foo + remp1 | 1 | bar + remp1 | 1 | test1 +(3 rows) + +select tableoid::regclass, * FROM remp2; + tableoid | b | a +----------+-------+--- + remp2 | baz | 2 + remp2 | qux | 2 + remp2 | test2 | 2 +(3 rows) + +delete from itrtest; +-- MERGE ought to fail cleanly +-- YB Note: MERGE command is not supported yet (#19064). +-- YB Note: The following query will fail with a different error. +merge into itrtest using (select 1, 'foo') as source on (true) + when matched then do nothing; +ERROR: This statement not supported yet +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +create unique index loct1_idx on loct1 (a); +-- DO NOTHING without an inference specification is supported +insert into itrtest values (1, 'foo') on conflict do nothing returning *; + a | b +---+----- + 1 | foo +(1 row) + +insert into itrtest values (1, 'foo') on conflict do nothing returning *; + a | b +---+--- +(0 rows) + +-- But other cases are not supported +insert into itrtest values (1, 'bar') on conflict (a) do nothing; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +select tableoid::regclass, * FROM itrtest; + tableoid | a | b +----------+---+----- + remp1 | 1 | foo +(1 row) + +delete from itrtest; +drop index loct1_idx; +-- Test that remote triggers work with insert tuple routing +create function br_insert_trigfunc() returns trigger as $$ +begin + new.b := new.b || ' triggered !'; + return new; +end +$$ language plpgsql; +create trigger loct1_br_insert_trigger before insert on loct1 + for each row execute procedure br_insert_trigfunc(); +create trigger loct2_br_insert_trigger before insert on loct2 + for each row execute procedure br_insert_trigfunc(); +-- The new values are concatenated with ' triggered !' +insert into itrtest values (1, 'foo') returning *; + a | b +---+----------------- + 1 | foo triggered ! +(1 row) + +insert into itrtest values (2, 'qux') returning *; + a | b +---+----------------- + 2 | qux triggered ! +(1 row) + +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; + a | b +---+------------------- + 1 | test1 triggered ! + 2 | test2 triggered ! +(2 rows) + +with result as (insert into itrtest values (1, 'test1'), (2, 'test2') returning *) select * from result; + a | b +---+------------------- + 1 | test1 triggered ! + 2 | test2 triggered ! +(2 rows) + +drop trigger loct1_br_insert_trigger on loct1; +drop trigger loct2_br_insert_trigger on loct2; +drop table itrtest; +drop table loct1; +drop table loct2; +-- Test update tuple routing +create table utrtest (a int, b text) partition by list (a); +create table loct (a int check (a in (1)), b text); +create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct'); +create table locp (a int check (a in (2)), b text); +alter table utrtest attach partition remp for values in (1); +alter table utrtest attach partition locp for values in (2); +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); +select tableoid::regclass, * FROM utrtest; + tableoid | a | b +----------+---+----- + remp | 1 | foo + locp | 2 | qux +(2 rows) + +select tableoid::regclass, * FROM remp; + tableoid | a | b +----------+---+----- + remp | 1 | foo +(1 row) + +select tableoid::regclass, * FROM locp; + tableoid | a | b +----------+---+----- + locp | 2 | qux +(1 row) + +-- It's not allowed to move a row from a partition that is foreign to another +update utrtest set a = 2 where b = 'foo' returning *; +ERROR: new row for relation "loct" violates check constraint "loct_a_check" +DETAIL: Failing row contains (2, foo). +CONTEXT: remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo')) RETURNING a, b, ybctid +-- But the reverse is allowed +update utrtest set a = 1 where b = 'qux' returning *; +ERROR: cannot route tuples into foreign table to be updated "remp" +select tableoid::regclass, * FROM utrtest; + tableoid | a | b +----------+---+----- + remp | 1 | foo + locp | 2 | qux +(2 rows) + +select tableoid::regclass, * FROM remp; + tableoid | a | b +----------+---+----- + remp | 1 | foo +(1 row) + +select tableoid::regclass, * FROM locp; + tableoid | a | b +----------+---+----- + locp | 2 | qux +(1 row) + +-- The executor should not let unexercised FDWs shut down +update utrtest set a = 1 where b = 'foo'; +-- Test that remote triggers work with update tuple routing +create trigger loct_br_insert_trigger before insert on loct + for each row execute procedure br_insert_trigfunc(); +delete from utrtest; +insert into utrtest values (2, 'qux'); +-- Check case where the foreign partition is a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 1 or a = 2 returning *; + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b + Foreign Update on public.remp utrtest_1 + Update on public.locp utrtest_2 + -> Append + -> Foreign Update on public.remp utrtest_1 + Remote Filter: ((utrtest_1.a = 1) OR (utrtest_1.a = 2)) + Remote SQL: UPDATE public.loct SET a = 1 WHERE (((a = 1) OR (a = 2))) RETURNING a, b, ybctid + -> Seq Scan on public.locp utrtest_2 + Output: 1, utrtest_2.tableoid, utrtest_2.ybctid, utrtest_2.* + Storage Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2)) +(11 rows) + +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 1 or a = 2 returning *; +ERROR: cannot route tuples into foreign table to be updated "remp" +delete from utrtest; +insert into utrtest values (2, 'qux'); +-- Check case where the foreign partition isn't a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 2 returning *; + QUERY PLAN +---------------------------------------------------------------------- + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b + Update on public.locp utrtest_1 + -> Seq Scan on public.locp utrtest_1 + Output: 1, utrtest_1.tableoid, utrtest_1.*, utrtest_1.ybctid + Storage Filter: (utrtest_1.a = 2) +(6 rows) + +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 2 returning *; + a | b +---+----------------- + 1 | qux triggered ! +(1 row) + +drop trigger loct_br_insert_trigger on loct; +-- We can move rows to a foreign partition that has been updated already, +-- but can't move rows to a foreign partition that hasn't been updated yet +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); +-- Test the former case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 returning *; + QUERY PLAN +------------------------------------------------------------------------------- + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b + Foreign Update on public.remp utrtest_1 + Update on public.locp utrtest_2 + -> Append + -> Foreign Update on public.remp utrtest_1 + Remote SQL: UPDATE public.loct SET a = 1 RETURNING a, b, ybctid + -> Seq Scan on public.locp utrtest_2 + Output: 1, utrtest_2.tableoid, utrtest_2.ybctid, utrtest_2.* +(9 rows) + +update utrtest set a = 1 returning *; +ERROR: cannot route tuples into foreign table to be updated "remp" +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b, "*VALUES*".column1 + Foreign Update on public.remp utrtest_1 + Remote SQL: UPDATE public.loct SET a = $2 WHERE ybctid = $1 RETURNING a, b, ybctid + Update on public.locp utrtest_2 + -> Hash Join + Output: 1, "*VALUES*".*, "*VALUES*".column1, utrtest.tableoid, utrtest.ybctid, utrtest.* + Hash Cond: (utrtest.a = "*VALUES*".column1) + -> Append + -> Foreign Scan on public.remp utrtest_1 + Output: utrtest_1.a, utrtest_1.tableoid, utrtest_1.ybctid, utrtest_1.* + Remote SQL: SELECT a, b, ybctid FROM public.loct FOR UPDATE + -> Seq Scan on public.locp utrtest_2 + Output: utrtest_2.a, utrtest_2.tableoid, utrtest_2.ybctid, utrtest_2.* + -> Hash + Output: "*VALUES*".*, "*VALUES*".column1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".*, "*VALUES*".column1 +(18 rows) + +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; +ERROR: cannot route tuples into foreign table to be updated "remp" +-- Change the definition of utrtest so that the foreign partition get updated +-- after the local partition +delete from utrtest; +alter table utrtest detach partition remp; +drop foreign table remp; +alter table loct drop constraint loct_a_check; +alter table loct add check (a in (3)); +create foreign table remp (a int check (a in (3)), b text) server loopback options (table_name 'loct'); +alter table utrtest attach partition remp for values in (3); +insert into utrtest values (2, 'qux'); +insert into utrtest values (3, 'xyzzy'); +-- Test the latter case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 returning *; + QUERY PLAN +------------------------------------------------------------------------------- + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b + Update on public.locp utrtest_1 + Foreign Update on public.remp utrtest_2 + -> Append + -> Seq Scan on public.locp utrtest_1 + Output: 3, utrtest_1.tableoid, utrtest_1.*, utrtest_1.ybctid + -> Foreign Update on public.remp utrtest_2 + Remote SQL: UPDATE public.loct SET a = 3 RETURNING a, b, ybctid +(9 rows) + +update utrtest set a = 3 returning *; -- ERROR +ERROR: cannot route tuples into foreign table to be updated "remp" +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b, "*VALUES*".column1 + Update on public.locp utrtest_1 + Foreign Update on public.remp utrtest_2 + Remote SQL: UPDATE public.loct SET a = $2 WHERE ybctid = $1 RETURNING a, b, ybctid + -> Hash Join + Output: 3, "*VALUES*".*, "*VALUES*".column1, utrtest.tableoid, utrtest.*, utrtest.ybctid + Hash Cond: (utrtest.a = "*VALUES*".column1) + -> Append + -> Seq Scan on public.locp utrtest_1 + Output: utrtest_1.a, utrtest_1.tableoid, utrtest_1.*, utrtest_1.ybctid + -> Foreign Scan on public.remp utrtest_2 + Output: utrtest_2.a, utrtest_2.tableoid, utrtest_2.*, utrtest_2.ybctid + Remote SQL: SELECT a, b, ybctid FROM public.loct FOR UPDATE + -> Hash + Output: "*VALUES*".*, "*VALUES*".column1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".*, "*VALUES*".column1 +(18 rows) + +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; -- ERROR +ERROR: cannot route tuples into foreign table to be updated "remp" +drop table utrtest; +drop table loct; +-- Test copy tuple routing +create table ctrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table ctrtest attach partition remp1 for values in (1); +alter table ctrtest attach partition remp2 for values in (2); +copy ctrtest from stdin; +select tableoid::regclass, * FROM ctrtest; + tableoid | a | b +----------+---+----- + remp1 | 1 | foo + remp2 | 2 | qux +(2 rows) + +select tableoid::regclass, * FROM remp1; + tableoid | a | b +----------+---+----- + remp1 | 1 | foo +(1 row) + +select tableoid::regclass, * FROM remp2; + tableoid | b | a +----------+-----+--- + remp2 | qux | 2 +(1 row) + +-- Copying into foreign partitions directly should work as well +copy remp1 from stdin; +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +-- YB note: The following line is a YB test. This should ideally be in a yb.orig file. +SELECT relid::regclass, command, yb_status, type, bytes_processed, bytes_total, + tuples_processed, tuples_excluded FROM pg_stat_progress_copy; + relid | command | yb_status | type | bytes_processed | bytes_total | tuples_processed | tuples_excluded +-------+-----------+-----------+------+-----------------+-------------+------------------+----------------- + remp1 | COPY FROM | SUCCESS | PIPE | 9 | 0 | 1 | 0 +(1 row) + +select tableoid::regclass, * FROM remp1; + tableoid | a | b +----------+---+----- + remp1 | 1 | foo + remp1 | 1 | bar +(2 rows) + +drop table ctrtest; +drop table loct1; +drop table loct2; +-- =================================================================== +-- test COPY FROM +-- =================================================================== +create table loc2 (f1 int, f2 text); +alter table loc2 set (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2'); +-- Test basic functionality +copy rem2 from stdin; +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +delete from rem2; +-- Test check constraints +alter table loc2 add constraint loc2_f1positive check (f1 >= 0); +alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0); +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +copy rem2 from stdin; -- ERROR +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +ERROR: new row for relation "loc2" violates check constraint "loc2_f1positive" +DETAIL: Failing row contains (-1, xyzzy). +CONTEXT: remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2) +COPY rem2, line 1: "-1 xyzzy" +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +alter foreign table rem2 drop constraint rem2_f1positive; +alter table loc2 drop constraint loc2_f1positive; +delete from rem2; +-- Test local triggers +create trigger trig_stmt_before before insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_stmt_after after insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +copy rem2 from stdin; +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2 +NOTICE: NEW: (1,foo) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2 +NOTICE: NEW: (2,bar) +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2 +NOTICE: NEW: (1,foo) +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2 +NOTICE: NEW: (2,bar) +NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +drop trigger trig_row_before on rem2; +drop trigger trig_row_after on rem2; +drop trigger trig_stmt_before on rem2; +drop trigger trig_stmt_after on rem2; +delete from rem2; +create trigger trig_row_before_insert before insert on rem2 + for each row execute procedure trig_row_before_insupdate(); +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +select * from rem2; + f1 | f2 +----+----------------- + 1 | foo triggered ! + 2 | bar triggered ! +(2 rows) + +drop trigger trig_row_before_insert on rem2; +delete from rem2; +create trigger trig_null before insert on rem2 + for each row execute procedure trig_null(); +-- Nothing happens +copy rem2 from stdin; +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +select * from rem2; + f1 | f2 +----+---- +(0 rows) + +drop trigger trig_null on rem2; +delete from rem2; +-- Test remote triggers +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +select * from rem2; + f1 | f2 +----+----------------- + 1 | foo triggered ! + 2 | bar triggered ! +(2 rows) + +drop trigger trig_row_before_insert on loc2; +delete from rem2; +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); +-- Nothing happens +copy rem2 from stdin; +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +select * from rem2; + f1 | f2 +----+---- +(0 rows) + +drop trigger trig_null on loc2; +delete from rem2; +-- Test a combination of local and remote triggers +create trigger rem2_trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger rem2_trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger loc2_trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); +copy rem2 from stdin; +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2 +NOTICE: NEW: (1,foo) +NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2 +NOTICE: NEW: (2,bar) +NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2 +NOTICE: NEW: (1,"foo triggered !") +NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2 +NOTICE: NEW: (2,"bar triggered !") +select * from rem2; + f1 | f2 +----+----------------- + 1 | foo triggered ! + 2 | bar triggered ! +(2 rows) + +drop trigger rem2_trig_row_before on rem2; +drop trigger rem2_trig_row_after on rem2; +drop trigger loc2_trig_row_before_insert on loc2; +delete from rem2; +-- test COPY FROM with foreign table created in the same transaction +create table loc3 (f1 int, f2 text); +begin; +create foreign table rem3 (f1 int, f2 text) + server loopback options(table_name 'loc3'); +copy rem3 from stdin; +WARNING: ROWS_PER_TRANSACTION is not supported on foreign tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +commit; +select * from rem3; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +drop foreign table rem3; +drop table loc3; +-- =================================================================== +-- test for TRUNCATE +-- =================================================================== +CREATE TABLE tru_rtable0 (id int primary key); +CREATE FOREIGN TABLE tru_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_rtable0'); +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(1,10) x); +CREATE TABLE tru_ptable (id int) PARTITION BY HASH(id); +CREATE TABLE tru_ptable__p0 PARTITION OF tru_ptable + FOR VALUES WITH (MODULUS 2, REMAINDER 0); +CREATE TABLE tru_rtable1 (id int primary key); +CREATE FOREIGN TABLE tru_ftable__p1 PARTITION OF tru_ptable + FOR VALUES WITH (MODULUS 2, REMAINDER 1) + SERVER loopback OPTIONS (table_name 'tru_rtable1'); +INSERT INTO tru_ptable (SELECT x FROM generate_series(11,20) x); +CREATE TABLE tru_pk_table(id int primary key); +CREATE TABLE tru_fk_table(fkey int references tru_pk_table(id)); +INSERT INTO tru_pk_table (SELECT x FROM generate_series(1,10) x); +INSERT INTO tru_fk_table (SELECT x % 10 + 1 FROM generate_series(5,25) x); +CREATE FOREIGN TABLE tru_pk_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_pk_table'); +CREATE TABLE tru_rtable_parent (id int); +CREATE TABLE tru_rtable_child (id int); +CREATE FOREIGN TABLE tru_ftable_parent (id int) + SERVER loopback OPTIONS (table_name 'tru_rtable_parent'); +CREATE FOREIGN TABLE tru_ftable_child () INHERITS (tru_ftable_parent) + SERVER loopback OPTIONS (table_name 'tru_rtable_child'); +INSERT INTO tru_rtable_parent (SELECT x FROM generate_series(1,8) x); +INSERT INTO tru_rtable_child (SELECT x FROM generate_series(10, 18) x); +-- normal truncate +SELECT sum(id) FROM tru_ftable; -- 55 + sum +----- + 55 +(1 row) + +TRUNCATE tru_ftable; +SELECT count(*) FROM tru_rtable0; -- 0 + count +------- + 0 +(1 row) + +SELECT count(*) FROM tru_ftable; -- 0 + count +------- + 0 +(1 row) + +-- 'truncatable' option +ALTER SERVER loopback OPTIONS (ADD truncatable 'false'); +TRUNCATE tru_ftable; -- error +ERROR: foreign table "tru_ftable" does not allow truncates +ALTER FOREIGN TABLE tru_ftable OPTIONS (ADD truncatable 'true'); +TRUNCATE tru_ftable; -- accepted +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false'); +TRUNCATE tru_ftable; -- error +ERROR: foreign table "tru_ftable" does not allow truncates +ALTER SERVER loopback OPTIONS (DROP truncatable); +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false'); +TRUNCATE tru_ftable; -- error +ERROR: foreign table "tru_ftable" does not allow truncates +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'true'); +TRUNCATE tru_ftable; -- accepted +-- partitioned table with both local and foreign tables as partitions +SELECT sum(id) FROM tru_ptable; -- 155 + sum +----- + 155 +(1 row) + +-- YB note: Table with foreign partitions fails to truncate. Uncomment once #28150 is fixed. +-- As a result the counts below are all expected to be non-zero. +-- TRUNCATE tru_ptable; +SELECT count(*) FROM tru_ptable; -- 0 + count +------- + 10 +(1 row) + +SELECT count(*) FROM tru_ptable__p0; -- 0 + count +------- + 6 +(1 row) + +SELECT count(*) FROM tru_ftable__p1; -- 0 + count +------- + 4 +(1 row) + +SELECT count(*) FROM tru_rtable1; -- 0 + count +------- + 4 +(1 row) + +-- 'CASCADE' option +SELECT sum(id) FROM tru_pk_ftable; -- 55 + sum +----- + 55 +(1 row) + +TRUNCATE tru_pk_ftable; -- failed by FK reference +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "tru_fk_table" references "tru_pk_table". +HINT: Truncate table "tru_fk_table" at the same time, or use TRUNCATE ... CASCADE. +CONTEXT: remote SQL command: TRUNCATE public.tru_pk_table CONTINUE IDENTITY RESTRICT +TRUNCATE tru_pk_ftable CASCADE; +SELECT count(*) FROM tru_pk_ftable; -- 0 + count +------- + 0 +(1 row) + +SELECT count(*) FROM tru_fk_table; -- also truncated,0 + count +------- + 0 +(1 row) + +-- truncate two tables at a command +INSERT INTO tru_ftable (SELECT x FROM generate_series(1,8) x); +INSERT INTO tru_pk_ftable (SELECT x FROM generate_series(3,10) x); +SELECT count(*) from tru_ftable; -- 8 + count +------- + 8 +(1 row) + +SELECT count(*) from tru_pk_ftable; -- 8 + count +------- + 8 +(1 row) + +TRUNCATE tru_ftable, tru_pk_ftable CASCADE; +SELECT count(*) from tru_ftable; -- 0 + count +------- + 0 +(1 row) + +SELECT count(*) from tru_pk_ftable; -- 0 + count +------- + 0 +(1 row) + +-- truncate with ONLY clause +-- Since ONLY is specified, the table tru_ftable_child that inherits +-- tru_ftable_parent locally is not truncated. +TRUNCATE ONLY tru_ftable_parent; +SELECT sum(id) FROM tru_ftable_parent; -- 126 + sum +----- + 126 +(1 row) + +TRUNCATE tru_ftable_parent; +SELECT count(*) FROM tru_ftable_parent; -- 0 + count +------- + 0 +(1 row) + +-- in case when remote table has inherited children +CREATE TABLE tru_rtable0_child () INHERITS (tru_rtable0); +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(5,9) x); +INSERT INTO tru_rtable0_child (SELECT x FROM generate_series(10,14) x); +SELECT sum(id) FROM tru_ftable; -- 95 + sum +----- + 95 +(1 row) + +-- Both parent and child tables in the foreign server are truncated +-- even though ONLY is specified because ONLY has no effect +-- when truncating a foreign table. +TRUNCATE ONLY tru_ftable; +SELECT count(*) FROM tru_ftable; -- 0 + count +------- + 0 +(1 row) + +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(21,25) x); +INSERT INTO tru_rtable0_child (SELECT x FROM generate_series(26,30) x); +SELECT sum(id) FROM tru_ftable; -- 255 + sum +----- + 255 +(1 row) + +TRUNCATE tru_ftable; -- truncate both of parent and child +SELECT count(*) FROM tru_ftable; -- 0 + count +------- + 0 +(1 row) + +-- cleanup +DROP FOREIGN TABLE tru_ftable_parent, tru_ftable_child, tru_pk_ftable,tru_ftable__p1,tru_ftable; +ERROR: DROP multiple objects not supported yet +LINE 1: DROP FOREIGN TABLE tru_ftable_parent, tru_ftable_child, tru_... + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/880. React with thumbs up to raise its priority +DROP TABLE tru_rtable0, tru_rtable1, tru_ptable, tru_ptable__p0, tru_pk_table, tru_fk_table, +tru_rtable_parent,tru_rtable_child, tru_rtable0_child; +-- =================================================================== +-- test IMPORT FOREIGN SCHEMA +-- =================================================================== +CREATE SCHEMA import_source; +CREATE TABLE import_source.t1 (c1 int, c2 varchar NOT NULL); +CREATE TABLE import_source.t2 (c1 int default 42, c2 varchar NULL, c3 text collate "POSIX"); +CREATE TYPE typ1 AS (m1 int, m2 varchar); +CREATE TABLE import_source.t3 (c1 timestamptz default now(), c2 typ1); +CREATE TABLE import_source."x 4" (c1 float8, "C 2" text, c3 varchar(42)); +CREATE TABLE import_source."x 5" (c1 float8); +ALTER TABLE import_source."x 5" DROP COLUMN c1; +CREATE TABLE import_source."x 6" (c1 int, c2 int generated always as (c1 * 2) stored); +CREATE TABLE import_source.t4 (c1 int) PARTITION BY RANGE (c1); +CREATE TABLE import_source.t4_part PARTITION OF import_source.t4 + FOR VALUES FROM (1) TO (100); +CREATE TABLE import_source.t4_part2 PARTITION OF import_source.t4 + FOR VALUES FROM (100) TO (200); +CREATE SCHEMA import_dest1; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest1; +\det+ import_dest1.* + List of foreign tables + Schema | Table | Server | FDW options | Description +--------------+-------+----------+-------------------------------------------------+------------- + import_dest1 | t1 | loopback | (schema_name 'import_source', table_name 't1') | + import_dest1 | t2 | loopback | (schema_name 'import_source', table_name 't2') | + import_dest1 | t3 | loopback | (schema_name 'import_source', table_name 't3') | + import_dest1 | t4 | loopback | (schema_name 'import_source', table_name 't4') | + import_dest1 | x 4 | loopback | (schema_name 'import_source', table_name 'x 4') | + import_dest1 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') | + import_dest1 | x 6 | loopback | (schema_name 'import_source', table_name 'x 6') | +(7 rows) + +\d import_dest1.* + Foreign table "import_dest1.t1" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | character varying | | not null | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't1') + + Foreign table "import_dest1.t2" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | character varying | | | | (column_name 'c2') + c3 | text | POSIX | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't2') + + Foreign table "import_dest1.t3" + Column | Type | Collation | Nullable | Default | FDW options +--------+--------------------------+-----------+----------+---------+-------------------- + c1 | timestamp with time zone | | | | (column_name 'c1') + c2 | typ1 | | | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't3') + + Foreign table "import_dest1.t4" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't4') + + Foreign table "import_dest1.x 4" + Column | Type | Collation | Nullable | Default | FDW options +--------+-----------------------+-----------+----------+---------+--------------------- + c1 | double precision | | | | (column_name 'c1') + C 2 | text | | | | (column_name 'C 2') + c3 | character varying(42) | | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 4') + + Foreign table "import_dest1.x 5" + Column | Type | Collation | Nullable | Default | FDW options +--------+------+-----------+----------+---------+------------- +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 5') + + Foreign table "import_dest1.x 6" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+-------------------------------------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | integer | | | generated always as (c1 * 2) stored | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 6') + +-- Options +CREATE SCHEMA import_dest2; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest2 + OPTIONS (import_default 'true'); +\det+ import_dest2.* + List of foreign tables + Schema | Table | Server | FDW options | Description +--------------+-------+----------+-------------------------------------------------+------------- + import_dest2 | t1 | loopback | (schema_name 'import_source', table_name 't1') | + import_dest2 | t2 | loopback | (schema_name 'import_source', table_name 't2') | + import_dest2 | t3 | loopback | (schema_name 'import_source', table_name 't3') | + import_dest2 | t4 | loopback | (schema_name 'import_source', table_name 't4') | + import_dest2 | x 4 | loopback | (schema_name 'import_source', table_name 'x 4') | + import_dest2 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') | + import_dest2 | x 6 | loopback | (schema_name 'import_source', table_name 'x 6') | +(7 rows) + +\d import_dest2.* + Foreign table "import_dest2.t1" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | character varying | | not null | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't1') + + Foreign table "import_dest2.t2" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | 42 | (column_name 'c1') + c2 | character varying | | | | (column_name 'c2') + c3 | text | POSIX | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't2') + + Foreign table "import_dest2.t3" + Column | Type | Collation | Nullable | Default | FDW options +--------+--------------------------+-----------+----------+---------+-------------------- + c1 | timestamp with time zone | | | now() | (column_name 'c1') + c2 | typ1 | | | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't3') + + Foreign table "import_dest2.t4" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't4') + + Foreign table "import_dest2.x 4" + Column | Type | Collation | Nullable | Default | FDW options +--------+-----------------------+-----------+----------+---------+--------------------- + c1 | double precision | | | | (column_name 'c1') + C 2 | text | | | | (column_name 'C 2') + c3 | character varying(42) | | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 4') + + Foreign table "import_dest2.x 5" + Column | Type | Collation | Nullable | Default | FDW options +--------+------+-----------+----------+---------+------------- +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 5') + + Foreign table "import_dest2.x 6" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+-------------------------------------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | integer | | | generated always as (c1 * 2) stored | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 6') + +CREATE SCHEMA import_dest3; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest3 + OPTIONS (import_collate 'false', import_generated 'false', import_not_null 'false'); +\det+ import_dest3.* + List of foreign tables + Schema | Table | Server | FDW options | Description +--------------+-------+----------+-------------------------------------------------+------------- + import_dest3 | t1 | loopback | (schema_name 'import_source', table_name 't1') | + import_dest3 | t2 | loopback | (schema_name 'import_source', table_name 't2') | + import_dest3 | t3 | loopback | (schema_name 'import_source', table_name 't3') | + import_dest3 | t4 | loopback | (schema_name 'import_source', table_name 't4') | + import_dest3 | x 4 | loopback | (schema_name 'import_source', table_name 'x 4') | + import_dest3 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') | + import_dest3 | x 6 | loopback | (schema_name 'import_source', table_name 'x 6') | +(7 rows) + +\d import_dest3.* + Foreign table "import_dest3.t1" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | character varying | | | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't1') + + Foreign table "import_dest3.t2" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | character varying | | | | (column_name 'c2') + c3 | text | | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't2') + + Foreign table "import_dest3.t3" + Column | Type | Collation | Nullable | Default | FDW options +--------+--------------------------+-----------+----------+---------+-------------------- + c1 | timestamp with time zone | | | | (column_name 'c1') + c2 | typ1 | | | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't3') + + Foreign table "import_dest3.t4" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't4') + + Foreign table "import_dest3.x 4" + Column | Type | Collation | Nullable | Default | FDW options +--------+-----------------------+-----------+----------+---------+--------------------- + c1 | double precision | | | | (column_name 'c1') + C 2 | text | | | | (column_name 'C 2') + c3 | character varying(42) | | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 4') + + Foreign table "import_dest3.x 5" + Column | Type | Collation | Nullable | Default | FDW options +--------+------+-----------+----------+---------+------------- +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 5') + + Foreign table "import_dest3.x 6" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | integer | | | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 6') + +-- Check LIMIT TO and EXCEPT +CREATE SCHEMA import_dest4; +IMPORT FOREIGN SCHEMA import_source LIMIT TO (t1, nonesuch, t4_part) + FROM SERVER loopback INTO import_dest4; +\det+ import_dest4.* + List of foreign tables + Schema | Table | Server | FDW options | Description +--------------+---------+----------+-----------------------------------------------------+------------- + import_dest4 | t1 | loopback | (schema_name 'import_source', table_name 't1') | + import_dest4 | t4_part | loopback | (schema_name 'import_source', table_name 't4_part') | +(2 rows) + +IMPORT FOREIGN SCHEMA import_source EXCEPT (t1, "x 4", nonesuch, t4_part) + FROM SERVER loopback INTO import_dest4; +\det+ import_dest4.* + List of foreign tables + Schema | Table | Server | FDW options | Description +--------------+---------+----------+-----------------------------------------------------+------------- + import_dest4 | t1 | loopback | (schema_name 'import_source', table_name 't1') | + import_dest4 | t2 | loopback | (schema_name 'import_source', table_name 't2') | + import_dest4 | t3 | loopback | (schema_name 'import_source', table_name 't3') | + import_dest4 | t4 | loopback | (schema_name 'import_source', table_name 't4') | + import_dest4 | t4_part | loopback | (schema_name 'import_source', table_name 't4_part') | + import_dest4 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') | + import_dest4 | x 6 | loopback | (schema_name 'import_source', table_name 'x 6') | +(7 rows) + +-- Assorted error cases +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest4; +ERROR: relation "t1" already exists +CONTEXT: importing foreign table "t1" +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO import_dest4; +ERROR: schema "nonesuch" is not present on foreign server "loopback" +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO notthere; +ERROR: schema "notthere" does not exist +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER nowhere INTO notthere; +ERROR: server "nowhere" does not exist +-- Check case of a type present only on the remote server. +-- We can fake this by dropping the type locally in our transaction. +CREATE TYPE "Colors" AS ENUM ('red', 'green', 'blue'); +CREATE TABLE import_source.t5 (c1 int, c2 text collate "C", "Col" "Colors"); +CREATE SCHEMA import_dest5; +BEGIN; +DROP TYPE "Colors" CASCADE; +NOTICE: drop cascades to column Col of table import_source.t5 +IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5) + FROM SERVER loopback INTO import_dest5; -- ERROR +ERROR: type "public.Colors" does not exist +LINE 4: "Col" public."Colors" OPTIONS (column_name 'Col') + ^ +QUERY: CREATE FOREIGN TABLE t5 ( + c1 integer OPTIONS (column_name 'c1'), + c2 text OPTIONS (column_name 'c2') COLLATE pg_catalog."C", + "Col" public."Colors" OPTIONS (column_name 'Col') +) SERVER loopback +OPTIONS (schema_name 'import_source', table_name 't5'); +CONTEXT: importing foreign table "t5" +ROLLBACK; +BEGIN; +CREATE SERVER fetch101 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( fetch_size '101' ); +NOTICE: no server_type specified. Defaulting to PostgreSQL. +HINT: Use "ALTER SERVER ... OPTIONS (ADD server_type '')" to explicitly set server_type. +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=101']; + count +------- + 1 +(1 row) + +ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' ); +NOTICE: no server_type specified. Defaulting to PostgreSQL. +HINT: Use "ALTER SERVER ... OPTIONS (ADD server_type '')" to explicitly set server_type. +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=101']; + count +------- + 0 +(1 row) + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=202']; + count +------- + 1 +(1 row) + +CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' ); +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=30000']; + count +------- + 1 +(1 row) + +ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000'); +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=30000']; + count +------- + 0 +(1 row) + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=60000']; + count +------- + 1 +(1 row) + +ROLLBACK; +-- =================================================================== +-- test partitionwise joins +-- =================================================================== +SET enable_partitionwise_join=on; +CREATE TABLE fprt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE fprt1_p1 (LIKE fprt1); +CREATE TABLE fprt1_p2 (LIKE fprt1); +ALTER TABLE fprt1_p1 SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +ALTER TABLE fprt1_p2 SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +INSERT INTO fprt1_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 2) i; +INSERT INTO fprt1_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 2) i; +CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (0) TO (250) + SERVER loopback OPTIONS (table_name 'fprt1_p1', use_remote_estimate 'true'); +CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (250) TO (500) + SERVER loopback OPTIONS (TABLE_NAME 'fprt1_p2'); +ANALYZE fprt1; +ANALYZE fprt1_p1; +ANALYZE fprt1_p2; +CREATE TABLE fprt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE fprt2_p1 (LIKE fprt2); +CREATE TABLE fprt2_p2 (LIKE fprt2); +ALTER TABLE fprt2_p1 SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false'); +NOTICE: storage parameters are currently ignored in YugabyteDB +INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i; +INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i; +CREATE FOREIGN TABLE ftprt2_p1 (b int, c varchar, a int) + SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true'); +ALTER TABLE fprt2 ATTACH PARTITION ftprt2_p1 FOR VALUES FROM (0) TO (250); +CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500) + SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true'); +ANALYZE fprt2; +ANALYZE fprt2_p1; +ANALYZE fprt2_p2; +-- inner join three tables +EXPLAIN (COSTS OFF) +SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Merge Append + Sort Key: t1.a, t3.c + -> Foreign Scan + Relations: ((ftprt1_p1 t1_1) INNER JOIN (ftprt2_p1 t2_1)) INNER JOIN (ftprt1_p1 t3_1) + -> Foreign Scan + Relations: ((ftprt1_p2 t1_2) INNER JOIN (ftprt2_p2 t2_2)) INNER JOIN (ftprt1_p2 t3_2) +(6 rows) + +SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; + a | b | c +-----+-----+------ + 0 | 0 | 0000 + 150 | 150 | 0003 + 250 | 250 | 0005 + 400 | 400 | 0008 +(4 rows) + +-- left outer join + nullable clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1_1.a, fprt2_1.b, fprt2_1.c + Relations: (public.ftprt1_p1 t1_1) LEFT JOIN (public.ftprt2_p1 fprt2_1) + Remote SQL: SELECT r5.a, r6.b, r6.c FROM (public.fprt1_p1 r5 LEFT JOIN public.fprt2_p1 r6 ON (((r5.a = r6.b)) AND ((r5.b = r6.a)) AND ((r6.a < 10)))) WHERE ((r5.a < 10)) ORDER BY r5.a ASC NULLS LAST, r6.b ASC NULLS LAST, r6.c ASC NULLS LAST +(4 rows) + +SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; + a | b | c +---+---+------ + 0 | 0 | 0000 + 2 | | + 4 | | + 6 | 6 | 0000 + 8 | | +(5 rows) + +-- with whole-row reference; partitionwise join does not apply +EXPLAIN (COSTS OFF) +SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; + QUERY PLAN +--------------------------------------------------------- + Sort + Sort Key: ((t1.*)::fprt1), ((t2.*)::fprt2) + -> Hash Full Join + Hash Cond: (t2.b = t1.a) + -> Append + -> Foreign Scan on ftprt2_p1 t2_1 + Remote Filter: ((b % 25) = 0) + -> Foreign Scan on ftprt2_p2 t2_2 + Remote Filter: ((b % 25) = 0) + -> Hash + -> Append + -> Foreign Scan on ftprt1_p1 t1_1 + Remote Filter: ((a % 25) = 0) + -> Foreign Scan on ftprt1_p2 t1_2 + Remote Filter: ((a % 25) = 0) +(15 rows) + +SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; + wr | wr +----------------+---------------- + (0,0,0000) | (0,0,0000) + (50,50,0001) | + (100,100,0002) | + (150,150,0003) | (150,150,0003) + (200,200,0004) | + (250,250,0005) | (250,250,0005) + (300,300,0006) | + (350,350,0007) | + (400,400,0008) | (400,400,0008) + (450,450,0009) | + | (75,75,0001) + | (225,225,0004) + | (325,325,0006) + | (475,475,0009) +(14 rows) + +-- join with lateral reference +EXPLAIN (COSTS OFF) +SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2; + QUERY PLAN +----------------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.b + -> Append + -> Foreign Scan + Relations: (ftprt1_p1 t1_1) INNER JOIN (ftprt2_p1 t2_1) + -> Foreign Scan + Relations: (ftprt1_p2 t1_2) INNER JOIN (ftprt2_p2 t2_2) +(7 rows) + +SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2; + a | b +-----+----- + 0 | 0 + 150 | 150 + 250 | 250 + 400 | 400 +(4 rows) + +-- with PHVs, partitionwise join selected but no join pushdown +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; + QUERY PLAN +----------------------------------------------------------- + Sort + Sort Key: fprt1.a, fprt2.b + -> Append + -> Hash Full Join + Hash Cond: (fprt1_1.a = fprt2_1.b) + -> Foreign Scan on ftprt1_p1 fprt1_1 + Remote Filter: ((a % 25) = 0) + -> Hash + -> Foreign Scan on ftprt2_p1 fprt2_1 + Remote Filter: ((b % 25) = 0) + -> Hash Full Join + Hash Cond: (fprt2_2.b = fprt1_2.a) + -> Foreign Scan on ftprt2_p2 fprt2_2 + Remote Filter: ((b % 25) = 0) + -> Hash + -> Foreign Scan on ftprt1_p2 fprt1_2 + Remote Filter: ((a % 25) = 0) +(17 rows) + +SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; + a | phv | b | phv +-----+--------+-----+-------- + 0 | t1_phv | 0 | t2_phv + 50 | t1_phv | | + 100 | t1_phv | | + 150 | t1_phv | 150 | t2_phv + 200 | t1_phv | | + 250 | t1_phv | 250 | t2_phv + 300 | t1_phv | | + 350 | t1_phv | | + 400 | t1_phv | 400 | t2_phv + 450 | t1_phv | | + | | 75 | t2_phv + | | 225 | t2_phv + | | 325 | t2_phv + | | 475 | t2_phv +(14 rows) + +-- test FOR UPDATE; partitionwise join does not apply +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; + QUERY PLAN +--------------------------------------------------------------- + LockRows + -> Sort + Sort Key: t1.a + -> Hash Join + Hash Cond: (t2.b = t1.a) + -> Append + -> Foreign Scan on ftprt2_p1 t2_1 + -> Foreign Scan on ftprt2_p2 t2_2 + -> Hash + -> Append + -> Foreign Scan on ftprt1_p1 t1_1 + Remote Filter: ((a % 25) = 0) + -> Foreign Scan on ftprt1_p2 t1_2 + Remote Filter: ((a % 25) = 0) +(14 rows) + +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; + a | b +-----+----- + 0 | 0 + 150 | 150 + 250 | 250 + 400 | 400 +(4 rows) + +RESET enable_partitionwise_join; +-- =================================================================== +-- test partitionwise aggregates +-- =================================================================== +CREATE TABLE pagg_tab (a int, b int, c text) PARTITION BY RANGE(a); +CREATE TABLE pagg_tab_p1 (LIKE pagg_tab); +CREATE TABLE pagg_tab_p2 (LIKE pagg_tab); +CREATE TABLE pagg_tab_p3 (LIKE pagg_tab); +INSERT INTO pagg_tab_p1 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 10; +INSERT INTO pagg_tab_p2 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 20 and (i % 30) >= 10; +INSERT INTO pagg_tab_p3 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 30 and (i % 30) >= 20; +-- Create foreign partitions +CREATE FOREIGN TABLE fpagg_tab_p1 PARTITION OF pagg_tab FOR VALUES FROM (0) TO (10) SERVER loopback OPTIONS (table_name 'pagg_tab_p1'); +CREATE FOREIGN TABLE fpagg_tab_p2 PARTITION OF pagg_tab FOR VALUES FROM (10) TO (20) SERVER loopback OPTIONS (table_name 'pagg_tab_p2'); +CREATE FOREIGN TABLE fpagg_tab_p3 PARTITION OF pagg_tab FOR VALUES FROM (20) TO (30) SERVER loopback OPTIONS (table_name 'pagg_tab_p3'); +ANALYZE pagg_tab; +ANALYZE fpagg_tab_p1; +ANALYZE fpagg_tab_p2; +ANALYZE fpagg_tab_p3; +-- When GROUP BY clause matches with PARTITION KEY. +-- Plan with partitionwise aggregates is disabled +SET enable_partitionwise_aggregate TO false; +EXPLAIN (COSTS OFF) +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + QUERY PLAN +----------------------------------------------------------- + Sort + Sort Key: pagg_tab.a + -> HashAggregate + Group Key: pagg_tab.a + Filter: (avg(pagg_tab.b) < '22'::numeric) + -> Append + -> Foreign Scan on fpagg_tab_p1 pagg_tab_1 + -> Foreign Scan on fpagg_tab_p2 pagg_tab_2 + -> Foreign Scan on fpagg_tab_p3 pagg_tab_3 +(9 rows) + +-- Plan with partitionwise aggregates is enabled +SET enable_partitionwise_aggregate TO true; +EXPLAIN (COSTS OFF) +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + QUERY PLAN +----------------------------------------------------------------- + Sort + Sort Key: pagg_tab_1.a + -> Append + -> Foreign Scan + Relations: Aggregate on (fpagg_tab_p1 pagg_tab_1) + -> Foreign Scan + Relations: Aggregate on (fpagg_tab_p2 pagg_tab_2) + -> Foreign Scan + Relations: Aggregate on (fpagg_tab_p3 pagg_tab_3) +(9 rows) + +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + a | sum | min | count +----+------+-----+------- + 0 | 2000 | 0 | 100 + 1 | 2100 | 1 | 100 + 10 | 2000 | 0 | 100 + 11 | 2100 | 1 | 100 + 20 | 2000 | 0 | 100 + 21 | 2100 | 1 | 100 +(6 rows) + +-- Check with whole-row reference +-- Should have all the columns in the target list for the given relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a, count(t1) FROM pagg_tab t1 GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Output: t1_1.a, (count(((t1_1.*)::pagg_tab))) + Sort Key: t1_1.a + -> Append + -> HashAggregate + Output: t1_1.a, count(((t1_1.*)::pagg_tab)) + Group Key: t1_1.a + Filter: (avg(t1_1.b) < '22'::numeric) + -> Foreign Scan on public.fpagg_tab_p1 t1_1 + Output: t1_1.a, t1_1.*, t1_1.b + Remote SQL: SELECT a, b, c FROM public.pagg_tab_p1 + -> HashAggregate + Output: t1_2.a, count(((t1_2.*)::pagg_tab)) + Group Key: t1_2.a + Filter: (avg(t1_2.b) < '22'::numeric) + -> Foreign Scan on public.fpagg_tab_p2 t1_2 + Output: t1_2.a, t1_2.*, t1_2.b + Remote SQL: SELECT a, b, c FROM public.pagg_tab_p2 + -> HashAggregate + Output: t1_3.a, count(((t1_3.*)::pagg_tab)) + Group Key: t1_3.a + Filter: (avg(t1_3.b) < '22'::numeric) + -> Foreign Scan on public.fpagg_tab_p3 t1_3 + Output: t1_3.a, t1_3.*, t1_3.b + Remote SQL: SELECT a, b, c FROM public.pagg_tab_p3 +(25 rows) + +SELECT a, count(t1) FROM pagg_tab t1 GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + a | count +----+------- + 0 | 100 + 1 | 100 + 10 | 100 + 11 | 100 + 20 | 100 + 21 | 100 +(6 rows) + +-- When GROUP BY clause does not match with PARTITION KEY. +EXPLAIN (COSTS OFF) +SELECT b, avg(a), max(a), count(*) FROM pagg_tab GROUP BY b HAVING sum(a) < 700 ORDER BY 1; + QUERY PLAN +----------------------------------------------------------------- + Sort + Sort Key: pagg_tab_1.b + -> Finalize HashAggregate + Group Key: pagg_tab_1.b + Filter: (sum(pagg_tab_1.a) < 700) + -> Append + -> Partial HashAggregate + Group Key: pagg_tab_1.b + -> Foreign Scan on fpagg_tab_p1 pagg_tab_1 + -> Partial HashAggregate + Group Key: pagg_tab_2.b + -> Foreign Scan on fpagg_tab_p2 pagg_tab_2 + -> Partial HashAggregate + Group Key: pagg_tab_3.b + -> Foreign Scan on fpagg_tab_p3 pagg_tab_3 +(15 rows) + +-- =================================================================== +-- access rights and superuser +-- =================================================================== +-- Non-superuser cannot create a FDW without a password in the connstr +CREATE ROLE regress_nosuper NOSUPERUSER; +GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO regress_nosuper; +SET ROLE regress_nosuper; +SHOW is_superuser; + is_superuser +-------------- + off +(1 row) + +-- This will be OK, we can create the FDW +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback_nopw FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + -- YB note: In YugabyteDB regress tests, the host and port vary on a run-to-run basis. + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$', + -- YB note: 'server_type' is a YB-specific option which defaults to 'postgresql'. + server_type 'yugabytedb' + )$$; + END; +$d$; +-- But creation of user mappings for non-superusers should fail +CREATE USER MAPPING FOR public SERVER loopback_nopw; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; +CREATE FOREIGN TABLE pg_temp.ft1_nopw ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback_nopw OPTIONS (schema_name 'public', table_name 'ft1'); +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password is required +DETAIL: Non-superusers must provide a password in the user mapping. +-- If we add a password to the connstr it'll fail, because we don't allow passwords +-- in connstrs only in user mappings. +DO $d$ + BEGIN + EXECUTE $$ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')$$; + END; +$d$; +ERROR: invalid option "password" +HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, yb_auto_analyze, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, parallel_commit, keep_connections, server_type +CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')" +PL/pgSQL function inline_code_block line 3 at EXECUTE +-- If we add a password for our user mapping instead, we should get a different +-- error because the password wasn't actually *used* when we run with trust auth. +-- +-- This won't work with installcheck, but neither will most of the FDW checks. +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password 'dummypw'); +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password is required +DETAIL: Non-superuser cannot connect if the server does not request a password. +HINT: Target server's authentication method must be changed or password_required=false set in the user mapping attributes. +-- Unpriv user cannot make the mapping passwordless +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password_required 'false'); +ERROR: password_required=false is superuser-only +HINT: User mappings with the password_required option set to false may only be created or modified by the superuser. +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password is required +DETAIL: Non-superuser cannot connect if the server does not request a password. +HINT: Target server's authentication method must be changed or password_required=false set in the user mapping attributes. +RESET ROLE; +-- But the superuser can +ALTER USER MAPPING FOR regress_nosuper SERVER loopback_nopw OPTIONS (ADD password_required 'false'); +SET ROLE regress_nosuper; +-- Should finally work now +SELECT 1 FROM ft1_nopw LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- unpriv user also cannot set sslcert / sslkey on the user mapping +-- first set password_required so we see the right error messages +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (SET password_required 'true'); +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslcert 'foo.crt'); +ERROR: sslcert and sslkey are superuser-only +HINT: User mappings with the sslcert or sslkey options set may only be created or modified by the superuser. +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslkey 'foo.key'); +ERROR: sslcert and sslkey are superuser-only +HINT: User mappings with the sslcert or sslkey options set may only be created or modified by the superuser. +-- We're done with the role named after a specific user and need to check the +-- changes to the public mapping. +DROP USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; +-- This will fail again as it'll resolve the user mapping for public, which +-- lacks password_required=false +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password is required +DETAIL: Non-superusers must provide a password in the user mapping. +RESET ROLE; +-- The user mapping for public is passwordless and lacks the password_required=false +-- mapping option, but will work because the current user is a superuser. +SELECT 1 FROM ft1_nopw LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- cleanup +DROP USER MAPPING FOR public SERVER loopback_nopw; +DROP OWNED BY regress_nosuper; +DROP ROLE regress_nosuper; +-- Clean-up +RESET enable_partitionwise_aggregate; +-- Two-phase transactions are not supported. +BEGIN; +SELECT count(*) FROM ft1; + count +------- + 822 +(1 row) + +-- error here +PREPARE TRANSACTION 'fdw_tpc'; +ERROR: PREPARE TRANSACTION not supported yet +LINE 1: PREPARE TRANSACTION 'fdw_tpc'; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1125. React with thumbs up to raise its priority +ROLLBACK; +-- =================================================================== +-- reestablish new connection +-- =================================================================== +-- Change application_name of remote connection to special one +-- so that we can easily terminate the connection later. +ALTER SERVER loopback OPTIONS (application_name 'fdw_retry_check'); +-- Make sure we have a remote connection. +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- Terminate the remote connection and wait for the termination to complete. +-- (If a cache flush happens, the remote connection might have already been +-- dropped; so code this step in a way that doesn't fail if no connection.) +-- YB: Re-enable this section once GH-29528 is fixed. +-- DO $$ BEGIN +-- PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity +-- WHERE application_name = 'fdw_retry_check'; +-- END $$; +-- This query should detect the broken connection when starting new remote +-- transaction, reestablish new connection, and then succeed. +BEGIN; +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- If we detect the broken connection when starting a new remote +-- subtransaction, we should fail instead of establishing a new connection. +-- Terminate the remote connection and wait for the termination to complete. +-- YB: Re-enable this section once GH-29528 is fixed. +-- DO $$ BEGIN +-- PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity +-- WHERE application_name = 'fdw_retry_check'; +-- END $$; +SAVEPOINT s; +-- The text of the error might vary across platforms, so only show SQLSTATE. +-- YB: Re-enable this section once GH-29528 is fixed. +-- \set VERBOSITY sqlstate +-- SELECT 1 FROM ft1 LIMIT 1; -- should fail +-- \set VERBOSITY default +COMMIT; +-- ============================================================================= +-- test connection invalidation cases and postgres_fdw_get_connections function +-- ============================================================================= +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); + ?column? +---------- + 1 +(1 row) + +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- +(0 rows) + +-- This test case is for closing the connection in pgfdw_xact_callback +BEGIN; +-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact. +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +SELECT 1 FROM ft7 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- List all the existing cached connections. loopback and loopback3 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- + loopback + loopback3 +(2 rows) + +-- Connections are not closed at the end of the alter and drop statements. +-- That's because the connections are in midst of this xact, +-- they are just marked as invalid in pgfdw_inval_callback. +ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off'); +DROP SERVER loopback3 CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to foreign table ft7 +drop cascades to user mapping for public on server loopback3 +-- List all the existing cached connections. loopback and loopback3 +-- should be output as invalid connections. Also the server name for +-- loopback3 should be NULL because the server was dropped. +SELECT * FROM postgres_fdw_get_connections() ORDER BY 1; + server_name | valid +-------------+------- + loopback | f + | f +(2 rows) + +-- The invalid connections get closed in pgfdw_xact_callback during commit. +COMMIT; +-- All cached connections were closed while committing above xact, so no +-- records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- +(0 rows) + +-- ======================================================================= +-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions +-- ======================================================================= +BEGIN; +-- Ensure to cache loopback connection. +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- Ensure to cache loopback2 connection. +SELECT 1 FROM ft6 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- List all the existing cached connections. loopback and loopback2 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- + loopback + loopback2 +(2 rows) + +-- Issue a warning and return false as loopback connection is still in use and +-- can not be closed. +SELECT postgres_fdw_disconnect('loopback'); +WARNING: cannot close connection for server "loopback" because it is still in use + postgres_fdw_disconnect +------------------------- + f +(1 row) + +-- List all the existing cached connections. loopback and loopback2 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- + loopback + loopback2 +(2 rows) + +-- Return false as connections are still in use, warnings are issued. +-- But disable warnings temporarily because the order of them is not stable. +SET client_min_messages = 'ERROR'; +SELECT postgres_fdw_disconnect_all(); + postgres_fdw_disconnect_all +----------------------------- + f +(1 row) + +RESET client_min_messages; +COMMIT; +-- Ensure that loopback2 connection is closed. +SELECT 1 FROM postgres_fdw_disconnect('loopback2'); + ?column? +---------- + 1 +(1 row) + +SELECT server_name FROM postgres_fdw_get_connections() WHERE server_name = 'loopback2'; + server_name +------------- +(0 rows) + +-- Return false as loopback2 connection is closed already. +SELECT postgres_fdw_disconnect('loopback2'); + postgres_fdw_disconnect +------------------------- + f +(1 row) + +-- Return an error as there is no foreign server with given name. +SELECT postgres_fdw_disconnect('unknownserver'); +ERROR: server "unknownserver" does not exist +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); + ?column? +---------- + 1 +(1 row) + +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- +(0 rows) + +-- ============================================================================= +-- test case for having multiple cached connections for a foreign server +-- ============================================================================= +CREATE ROLE regress_multi_conn_user1 SUPERUSER; +CREATE ROLE regress_multi_conn_user2 SUPERUSER; +CREATE USER MAPPING FOR regress_multi_conn_user1 SERVER loopback; +CREATE USER MAPPING FOR regress_multi_conn_user2 SERVER loopback; +BEGIN; +-- Will cache loopback connection with user mapping for regress_multi_conn_user1 +SET ROLE regress_multi_conn_user1; +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +RESET ROLE; +-- Will cache loopback connection with user mapping for regress_multi_conn_user2 +SET ROLE regress_multi_conn_user2; +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +RESET ROLE; +-- Should output two connections for loopback server +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- + loopback + loopback +(2 rows) + +COMMIT; +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); + ?column? +---------- + 1 +(1 row) + +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- +(0 rows) + +-- Clean up +DROP USER MAPPING FOR regress_multi_conn_user1 SERVER loopback; +DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback; +DROP ROLE regress_multi_conn_user1; +DROP ROLE regress_multi_conn_user2; +-- =================================================================== +-- Test foreign server level option keep_connections +-- =================================================================== +-- By default, the connections associated with foreign server are cached i.e. +-- keep_connections option is on. Set it to off. +ALTER SERVER loopback OPTIONS (keep_connections 'off'); +-- connection to loopback server is closed at the end of xact +-- as keep_connections was set to off. +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- +(0 rows) + +ALTER SERVER loopback OPTIONS (SET keep_connections 'on'); +-- =================================================================== +-- batch insert +-- =================================================================== +BEGIN; +CREATE SERVER batch10 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( batch_size '10' ); +NOTICE: no server_type specified. Defaulting to PostgreSQL. +HINT: Use "ALTER SERVER ... OPTIONS (ADD server_type '')" to explicitly set server_type. +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; + count +------- + 1 +(1 row) + +ALTER SERVER batch10 OPTIONS( SET batch_size '20' ); +NOTICE: no server_type specified. Defaulting to PostgreSQL. +HINT: Use "ALTER SERVER ... OPTIONS (ADD server_type '')" to explicitly set server_type. +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; + count +------- + 0 +(1 row) + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=20']; + count +------- + 1 +(1 row) + +CREATE FOREIGN TABLE table30 ( x int ) SERVER batch10 OPTIONS ( batch_size '30' ); +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; + count +------- + 1 +(1 row) + +ALTER FOREIGN TABLE table30 OPTIONS ( SET batch_size '40'); +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; + count +------- + 0 +(1 row) + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=40']; + count +------- + 1 +(1 row) + +ROLLBACK; +CREATE TABLE batch_table ( x int ); +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table', batch_size '10' ); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; + QUERY PLAN +------------------------------------------------------------- + Insert on public.ftable + Remote SQL: INSERT INTO public.batch_table(x) VALUES ($1) + Batch Size: 10 + -> Function Scan on pg_catalog.generate_series i + Output: i.i + Function Call: generate_series(1, 10) +(6 rows) + +INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; +INSERT INTO ftable SELECT * FROM generate_series(11, 31) i; +INSERT INTO ftable VALUES (32); +INSERT INTO ftable VALUES (33), (34); +SELECT COUNT(*) FROM ftable; + count +------- + 34 +(1 row) + +TRUNCATE batch_table; +DROP FOREIGN TABLE ftable; +-- Disable batch insert +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table', batch_size '1' ); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (1), (2); + QUERY PLAN +------------------------------------------------------------- + Insert on public.ftable + Remote SQL: INSERT INTO public.batch_table(x) VALUES ($1) + Batch Size: 1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1 +(5 rows) + +INSERT INTO ftable VALUES (1), (2); +SELECT COUNT(*) FROM ftable; + count +------- + 2 +(1 row) + +-- Disable batch inserting into foreign tables with BEFORE ROW INSERT triggers +-- even if the batch_size option is enabled. +ALTER FOREIGN TABLE ftable OPTIONS ( SET batch_size '10' ); +CREATE TRIGGER trig_row_before BEFORE INSERT ON ftable +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (3), (4); + QUERY PLAN +------------------------------------------------------------- + Insert on public.ftable + Remote SQL: INSERT INTO public.batch_table(x) VALUES ($1) + Batch Size: 1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1 +(5 rows) + +INSERT INTO ftable VALUES (3), (4); +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON ftable +NOTICE: NEW: (3) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON ftable +NOTICE: NEW: (4) +SELECT COUNT(*) FROM ftable; + count +------- + 4 +(1 row) + +-- Clean up +DROP TRIGGER trig_row_before ON ftable; +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; +-- Use partitioning +CREATE TABLE batch_table ( x int ) PARTITION BY HASH (x); +CREATE TABLE batch_table_p0 (LIKE batch_table); +CREATE FOREIGN TABLE batch_table_p0f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 0) + SERVER loopback + OPTIONS (table_name 'batch_table_p0', batch_size '10'); +CREATE TABLE batch_table_p1 (LIKE batch_table); +CREATE FOREIGN TABLE batch_table_p1f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 1) + SERVER loopback + OPTIONS (table_name 'batch_table_p1', batch_size '1'); +CREATE TABLE batch_table_p2 + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 2); +INSERT INTO batch_table SELECT * FROM generate_series(1, 66) i; +SELECT COUNT(*) FROM batch_table; + count +------- + 66 +(1 row) + +-- Check that enabling batched inserts doesn't interfere with cross-partition +-- updates +CREATE TABLE batch_cp_upd_test (a int) PARTITION BY LIST (a); +CREATE TABLE batch_cp_upd_test1 (LIKE batch_cp_upd_test); +CREATE FOREIGN TABLE batch_cp_upd_test1_f + PARTITION OF batch_cp_upd_test + FOR VALUES IN (1) + SERVER loopback + OPTIONS (table_name 'batch_cp_upd_test1', batch_size '10'); +CREATE TABLE batch_cp_up_test1 PARTITION OF batch_cp_upd_test + FOR VALUES IN (2); +INSERT INTO batch_cp_upd_test VALUES (1), (2); +-- The following moves a row from the local partition to the foreign one +UPDATE batch_cp_upd_test t SET a = 1 FROM (VALUES (1), (2)) s(a) WHERE t.a = s.a; +ERROR: cannot route tuples into foreign table to be updated "batch_cp_upd_test1_f" +SELECT tableoid::regclass, * FROM batch_cp_upd_test; + tableoid | a +----------------------+--- + batch_cp_upd_test1_f | 1 + batch_cp_up_test1 | 2 +(2 rows) + +-- Clean up +DROP TABLE batch_table, batch_cp_upd_test, batch_table_p0, batch_table_p1 CASCADE; +-- Use partitioning +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +CREATE TABLE batch_table ( x int, field1 text, field2 text) PARTITION BY HASH (x); +CREATE TABLE batch_table_p0 (LIKE batch_table); +ALTER TABLE batch_table_p0 ADD CONSTRAINT p0_pkey PRIMARY KEY (x); +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +CREATE FOREIGN TABLE batch_table_p0f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 2, REMAINDER 0) + SERVER loopback + OPTIONS (table_name 'batch_table_p0'); +CREATE TABLE batch_table_p1 (LIKE batch_table); +ALTER TABLE batch_table_p1 ADD CONSTRAINT p1_pkey PRIMARY KEY (x); +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +CREATE FOREIGN TABLE batch_table_p1f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 2, REMAINDER 1) + SERVER loopback + OPTIONS (table_name 'batch_table_p1'); +INSERT INTO batch_table SELECT i, 'test'||i, 'test'|| i FROM generate_series(1, 50) i; +SELECT COUNT(*) FROM batch_table; + count +------- + 50 +(1 row) + +SELECT * FROM batch_table ORDER BY x; + x | field1 | field2 +----+--------+-------- + 1 | test1 | test1 + 2 | test2 | test2 + 3 | test3 | test3 + 4 | test4 | test4 + 5 | test5 | test5 + 6 | test6 | test6 + 7 | test7 | test7 + 8 | test8 | test8 + 9 | test9 | test9 + 10 | test10 | test10 + 11 | test11 | test11 + 12 | test12 | test12 + 13 | test13 | test13 + 14 | test14 | test14 + 15 | test15 | test15 + 16 | test16 | test16 + 17 | test17 | test17 + 18 | test18 | test18 + 19 | test19 | test19 + 20 | test20 | test20 + 21 | test21 | test21 + 22 | test22 | test22 + 23 | test23 | test23 + 24 | test24 | test24 + 25 | test25 | test25 + 26 | test26 | test26 + 27 | test27 | test27 + 28 | test28 | test28 + 29 | test29 | test29 + 30 | test30 | test30 + 31 | test31 | test31 + 32 | test32 | test32 + 33 | test33 | test33 + 34 | test34 | test34 + 35 | test35 | test35 + 36 | test36 | test36 + 37 | test37 | test37 + 38 | test38 | test38 + 39 | test39 | test39 + 40 | test40 | test40 + 41 | test41 | test41 + 42 | test42 | test42 + 43 | test43 | test43 + 44 | test44 | test44 + 45 | test45 | test45 + 46 | test46 | test46 + 47 | test47 | test47 + 48 | test48 | test48 + 49 | test49 | test49 + 50 | test50 | test50 +(50 rows) + +-- Clean up +DROP TABLE batch_table; +DROP TABLE batch_table_p0; +DROP TABLE batch_table_p1; +ALTER SERVER loopback OPTIONS (DROP batch_size); +-- Test that pending inserts are handled properly when needed +CREATE TABLE batch_table (a text, b int); +CREATE FOREIGN TABLE ftable (a text, b int) + SERVER loopback + OPTIONS (table_name 'batch_table', batch_size '2'); +CREATE TABLE ltable (a text, b int); +CREATE FUNCTION ftable_rowcount_trigf() RETURNS trigger LANGUAGE plpgsql AS +$$ +begin + raise notice '%: there are % rows in ftable', + TG_NAME, (SELECT count(*) FROM ftable); + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; +end; +$$; +CREATE TRIGGER ftable_rowcount_trigger +BEFORE INSERT OR UPDATE OR DELETE ON ltable +FOR EACH ROW EXECUTE PROCEDURE ftable_rowcount_trigf(); +WITH t AS ( + INSERT INTO ltable VALUES ('AAA', 42), ('BBB', 42) RETURNING * +) +INSERT INTO ftable SELECT * FROM t; +NOTICE: ftable_rowcount_trigger: there are 0 rows in ftable +NOTICE: ftable_rowcount_trigger: there are 1 rows in ftable +SELECT * FROM ltable; + a | b +-----+---- + AAA | 42 + BBB | 42 +(2 rows) + +SELECT * FROM ftable; + a | b +-----+---- + AAA | 42 + BBB | 42 +(2 rows) + +DELETE FROM ftable; +WITH t AS ( + UPDATE ltable SET b = b + 100 RETURNING * +) +INSERT INTO ftable SELECT * FROM t; +NOTICE: ftable_rowcount_trigger: there are 0 rows in ftable +NOTICE: ftable_rowcount_trigger: there are 1 rows in ftable +SELECT * FROM ltable; + a | b +-----+----- + AAA | 142 + BBB | 142 +(2 rows) + +SELECT * FROM ftable; + a | b +-----+----- + AAA | 142 + BBB | 142 +(2 rows) + +DELETE FROM ftable; +WITH t AS ( + DELETE FROM ltable RETURNING * +) +INSERT INTO ftable SELECT * FROM t; +NOTICE: ftable_rowcount_trigger: there are 0 rows in ftable +NOTICE: ftable_rowcount_trigger: there are 1 rows in ftable +SELECT * FROM ltable; + a | b +---+--- +(0 rows) + +SELECT * FROM ftable; + a | b +-----+----- + AAA | 142 + BBB | 142 +(2 rows) + +DELETE FROM ftable; +-- Clean up +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; +DROP TRIGGER ftable_rowcount_trigger ON ltable; +DROP TABLE ltable; +CREATE TABLE parent (a text, b int) PARTITION BY LIST (a); +CREATE TABLE batch_table (a text, b int); +CREATE FOREIGN TABLE ftable + PARTITION OF parent + FOR VALUES IN ('AAA') + SERVER loopback + OPTIONS (table_name 'batch_table', batch_size '2'); +CREATE TABLE ltable + PARTITION OF parent + FOR VALUES IN ('BBB'); +CREATE TRIGGER ftable_rowcount_trigger +BEFORE INSERT ON ltable +FOR EACH ROW EXECUTE PROCEDURE ftable_rowcount_trigf(); +INSERT INTO parent VALUES ('AAA', 42), ('BBB', 42), ('AAA', 42), ('BBB', 42); +NOTICE: ftable_rowcount_trigger: there are 1 rows in ftable +NOTICE: ftable_rowcount_trigger: there are 2 rows in ftable +SELECT tableoid::regclass, * FROM parent; + tableoid | a | b +----------+-----+---- + ftable | AAA | 42 + ftable | AAA | 42 + ltable | BBB | 42 + ltable | BBB | 42 +(4 rows) + +-- Clean up +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; +DROP TRIGGER ftable_rowcount_trigger ON ltable; +DROP TABLE ltable; +DROP TABLE parent; +DROP FUNCTION ftable_rowcount_trigf; +-- =================================================================== +-- test asynchronous execution +-- =================================================================== +ALTER SERVER loopback OPTIONS (DROP extensions); +ALTER SERVER loopback OPTIONS (ADD async_capable 'true'); +ALTER SERVER loopback2 OPTIONS (ADD async_capable 'true'); +CREATE TABLE async_pt (a int, b int, c text) PARTITION BY RANGE (a); +CREATE TABLE base_tbl1 (a int, b int, c text); +CREATE TABLE base_tbl2 (a int, b int, c text); +CREATE FOREIGN TABLE async_p1 PARTITION OF async_pt FOR VALUES FROM (1000) TO (2000) + SERVER loopback OPTIONS (table_name 'base_tbl1'); +CREATE FOREIGN TABLE async_p2 PARTITION OF async_pt FOR VALUES FROM (2000) TO (3000) + SERVER loopback2 OPTIONS (table_name 'base_tbl2'); +INSERT INTO async_p1 SELECT 1000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +INSERT INTO async_p2 SELECT 2000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; +-- simple queries +CREATE TABLE result_tbl (a int, b int, c text); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b % 100 = 0; + QUERY PLAN +---------------------------------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Remote Filter: ((async_pt_1.b % 100) = 0) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE (((b % 100) = 0)) + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Remote Filter: ((async_pt_2.b % 100) = 0) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE (((b % 100) = 0)) +(10 rows) + +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b % 100 = 0; +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1000 | 0 | 0000 + 1100 | 100 | 0100 + 1200 | 200 | 0200 + 1300 | 300 | 0300 + 1400 | 400 | 0400 + 1500 | 500 | 0500 + 1600 | 600 | 0600 + 1700 | 700 | 0700 + 1800 | 800 | 0800 + 1900 | 900 | 0900 + 2000 | 0 | 0000 + 2100 | 100 | 0100 + 2200 | 200 | 0200 + 2300 | 300 | 0300 + 2400 | 400 | 0400 + 2500 | 500 | 0500 + 2600 | 600 | 0600 + 2700 | 700 | 0700 + 2800 | 800 | 0800 + 2900 | 900 | 0900 +(20 rows) + +DELETE FROM result_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + QUERY PLAN +---------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Filter: (async_pt_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 +(10 rows) + +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1505 | 505 | 0505 + 2505 | 505 | 0505 +(2 rows) + +DELETE FROM result_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT a, b, 'AAA' || c FROM async_pt WHERE b === 505; + QUERY PLAN +--------------------------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, ('AAA'::text || async_pt_1.c) + Filter: (async_pt_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, ('AAA'::text || async_pt_2.c) + Filter: (async_pt_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 +(10 rows) + +INSERT INTO result_tbl SELECT a, b, 'AAA' || c FROM async_pt WHERE b === 505; +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+--------- + 1505 | 505 | AAA0505 + 2505 | 505 | AAA0505 +(2 rows) + +DELETE FROM result_tbl; +-- Test error handling, if accessing one of the foreign partitions errors out +CREATE FOREIGN TABLE async_p_broken PARTITION OF async_pt FOR VALUES FROM (10000) TO (10001) + SERVER loopback OPTIONS (table_name 'non_existent_table'); +SELECT * FROM async_pt; +ERROR: relation "public.non_existent_table" does not exist +CONTEXT: remote SQL command: SELECT a, b, c FROM public.non_existent_table +DROP FOREIGN TABLE async_p_broken; +-- Check case where multiple partitions use the same connection +CREATE TABLE base_tbl3 (a int, b int, c text); +CREATE FOREIGN TABLE async_p3 PARTITION OF async_pt FOR VALUES FROM (3000) TO (4000) + SERVER loopback2 OPTIONS (table_name 'base_tbl3'); +INSERT INTO async_p3 SELECT 3000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + QUERY PLAN +---------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Filter: (async_pt_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Async Foreign Scan on public.async_p3 async_pt_3 + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c + Filter: (async_pt_3.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl3 +(14 rows) + +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1505 | 505 | 0505 + 2505 | 505 | 0505 + 3505 | 505 | 0505 +(3 rows) + +DELETE FROM result_tbl; +DROP FOREIGN TABLE async_p3; +DROP TABLE base_tbl3; +-- Check case where the partitioned table has local/remote partitions +CREATE TABLE async_p3 PARTITION OF async_pt FOR VALUES FROM (3000) TO (4000); +INSERT INTO async_p3 SELECT 3000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + QUERY PLAN +---------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Filter: (async_pt_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Seq Scan on public.async_p3 async_pt_3 + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c + Storage Filter: (async_pt_3.b === 505) +(13 rows) + +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1505 | 505 | 0505 + 2505 | 505 | 0505 + 3505 | 505 | 0505 +(3 rows) + +DELETE FROM result_tbl; +-- partitionwise joins +SET enable_partitionwise_join TO true; +CREATE TABLE join_tbl (a1 int, b1 int, c1 text, a2 int, b2 int, c2 text); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.join_tbl + -> Append + -> Async Foreign Scan + Output: t1_1.a, t1_1.b, t1_1.c, t2_1.a, t2_1.b, t2_1.c + Relations: (public.async_p1 t1_1) INNER JOIN (public.async_p1 t2_1) + Remote SQL: SELECT r5.a, r5.b, r5.c, r8.a, r8.b, r8.c FROM (public.base_tbl1 r5 INNER JOIN public.base_tbl1 r8 ON (((r5.a = r8.a)) AND ((r5.b = r8.b)) AND (((r5.b % 100) = 0)))) + -> Async Foreign Scan + Output: t1_2.a, t1_2.b, t1_2.c, t2_2.a, t2_2.b, t2_2.c + Relations: (public.async_p2 t1_2) INNER JOIN (public.async_p2 t2_2) + Remote SQL: SELECT r6.a, r6.b, r6.c, r9.a, r9.b, r9.c FROM (public.base_tbl2 r6 INNER JOIN public.base_tbl2 r9 ON (((r6.a = r9.a)) AND ((r6.b = r9.b)) AND (((r6.b % 100) = 0)))) + -> Hash Join + Output: t1_3.a, t1_3.b, t1_3.c, t2_3.a, t2_3.b, t2_3.c + Hash Cond: ((t1_3.a = t2_3.a) AND (t1_3.b = t2_3.b)) + -> Seq Scan on public.async_p3 t1_3 + Output: t1_3.a, t1_3.b, t1_3.c + Storage Filter: ((t1_3.b % 100) = 0) + -> Hash + Output: t2_3.a, t2_3.b, t2_3.c + -> Seq Scan on public.async_p3 t2_3 + Output: t2_3.a, t2_3.b, t2_3.c +(20 rows) + +INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +SELECT * FROM join_tbl ORDER BY a1; + a1 | b1 | c1 | a2 | b2 | c2 +------+-----+------+------+-----+------ + 1000 | 0 | 0000 | 1000 | 0 | 0000 + 1100 | 100 | 0100 | 1100 | 100 | 0100 + 1200 | 200 | 0200 | 1200 | 200 | 0200 + 1300 | 300 | 0300 | 1300 | 300 | 0300 + 1400 | 400 | 0400 | 1400 | 400 | 0400 + 1500 | 500 | 0500 | 1500 | 500 | 0500 + 1600 | 600 | 0600 | 1600 | 600 | 0600 + 1700 | 700 | 0700 | 1700 | 700 | 0700 + 1800 | 800 | 0800 | 1800 | 800 | 0800 + 1900 | 900 | 0900 | 1900 | 900 | 0900 + 2000 | 0 | 0000 | 2000 | 0 | 0000 + 2100 | 100 | 0100 | 2100 | 100 | 0100 + 2200 | 200 | 0200 | 2200 | 200 | 0200 + 2300 | 300 | 0300 | 2300 | 300 | 0300 + 2400 | 400 | 0400 | 2400 | 400 | 0400 + 2500 | 500 | 0500 | 2500 | 500 | 0500 + 2600 | 600 | 0600 | 2600 | 600 | 0600 + 2700 | 700 | 0700 | 2700 | 700 | 0700 + 2800 | 800 | 0800 | 2800 | 800 | 0800 + 2900 | 900 | 0900 | 2900 | 900 | 0900 + 3000 | 0 | 0000 | 3000 | 0 | 0000 + 3100 | 100 | 0100 | 3100 | 100 | 0100 + 3200 | 200 | 0200 | 3200 | 200 | 0200 + 3300 | 300 | 0300 | 3300 | 300 | 0300 + 3400 | 400 | 0400 | 3400 | 400 | 0400 + 3500 | 500 | 0500 | 3500 | 500 | 0500 + 3600 | 600 | 0600 | 3600 | 600 | 0600 + 3700 | 700 | 0700 | 3700 | 700 | 0700 + 3800 | 800 | 0800 | 3800 | 800 | 0800 + 3900 | 900 | 0900 | 3900 | 900 | 0900 +(30 rows) + +DELETE FROM join_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.join_tbl + -> Append + -> Async Foreign Scan + Output: t1_1.a, t1_1.b, ('AAA'::text || t1_1.c), t2_1.a, t2_1.b, ('AAA'::text || t2_1.c) + Relations: (public.async_p1 t1_1) INNER JOIN (public.async_p1 t2_1) + Remote SQL: SELECT r5.a, r5.b, r5.c, r8.a, r8.b, r8.c FROM (public.base_tbl1 r5 INNER JOIN public.base_tbl1 r8 ON (((r5.a = r8.a)) AND ((r5.b = r8.b)) AND (((r5.b % 100) = 0)))) + -> Async Foreign Scan + Output: t1_2.a, t1_2.b, ('AAA'::text || t1_2.c), t2_2.a, t2_2.b, ('AAA'::text || t2_2.c) + Relations: (public.async_p2 t1_2) INNER JOIN (public.async_p2 t2_2) + Remote SQL: SELECT r6.a, r6.b, r6.c, r9.a, r9.b, r9.c FROM (public.base_tbl2 r6 INNER JOIN public.base_tbl2 r9 ON (((r6.a = r9.a)) AND ((r6.b = r9.b)) AND (((r6.b % 100) = 0)))) + -> Hash Join + Output: t1_3.a, t1_3.b, ('AAA'::text || t1_3.c), t2_3.a, t2_3.b, ('AAA'::text || t2_3.c) + Hash Cond: ((t1_3.a = t2_3.a) AND (t1_3.b = t2_3.b)) + -> Seq Scan on public.async_p3 t1_3 + Output: t1_3.a, t1_3.b, t1_3.c + Storage Filter: ((t1_3.b % 100) = 0) + -> Hash + Output: t2_3.a, t2_3.b, t2_3.c + -> Seq Scan on public.async_p3 t2_3 + Output: t2_3.a, t2_3.b, t2_3.c +(20 rows) + +INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +SELECT * FROM join_tbl ORDER BY a1; + a1 | b1 | c1 | a2 | b2 | c2 +------+-----+---------+------+-----+--------- + 1000 | 0 | AAA0000 | 1000 | 0 | AAA0000 + 1100 | 100 | AAA0100 | 1100 | 100 | AAA0100 + 1200 | 200 | AAA0200 | 1200 | 200 | AAA0200 + 1300 | 300 | AAA0300 | 1300 | 300 | AAA0300 + 1400 | 400 | AAA0400 | 1400 | 400 | AAA0400 + 1500 | 500 | AAA0500 | 1500 | 500 | AAA0500 + 1600 | 600 | AAA0600 | 1600 | 600 | AAA0600 + 1700 | 700 | AAA0700 | 1700 | 700 | AAA0700 + 1800 | 800 | AAA0800 | 1800 | 800 | AAA0800 + 1900 | 900 | AAA0900 | 1900 | 900 | AAA0900 + 2000 | 0 | AAA0000 | 2000 | 0 | AAA0000 + 2100 | 100 | AAA0100 | 2100 | 100 | AAA0100 + 2200 | 200 | AAA0200 | 2200 | 200 | AAA0200 + 2300 | 300 | AAA0300 | 2300 | 300 | AAA0300 + 2400 | 400 | AAA0400 | 2400 | 400 | AAA0400 + 2500 | 500 | AAA0500 | 2500 | 500 | AAA0500 + 2600 | 600 | AAA0600 | 2600 | 600 | AAA0600 + 2700 | 700 | AAA0700 | 2700 | 700 | AAA0700 + 2800 | 800 | AAA0800 | 2800 | 800 | AAA0800 + 2900 | 900 | AAA0900 | 2900 | 900 | AAA0900 + 3000 | 0 | AAA0000 | 3000 | 0 | AAA0000 + 3100 | 100 | AAA0100 | 3100 | 100 | AAA0100 + 3200 | 200 | AAA0200 | 3200 | 200 | AAA0200 + 3300 | 300 | AAA0300 | 3300 | 300 | AAA0300 + 3400 | 400 | AAA0400 | 3400 | 400 | AAA0400 + 3500 | 500 | AAA0500 | 3500 | 500 | AAA0500 + 3600 | 600 | AAA0600 | 3600 | 600 | AAA0600 + 3700 | 700 | AAA0700 | 3700 | 700 | AAA0700 + 3800 | 800 | AAA0800 | 3800 | 800 | AAA0800 + 3900 | 900 | AAA0900 | 3900 | 900 | AAA0900 +(30 rows) + +DELETE FROM join_tbl; +RESET enable_partitionwise_join; +-- Test rescan of an async Append node with do_exec_prune=false +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT * FROM async_p1 t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + QUERY PLAN +---------------------------------------------------------------------------------------- + Insert on public.join_tbl + -> Nested Loop + Output: t1.a, t1.b, t1.c, t2.a, t2.b, t2.c + Join Filter: ((t1.a = t2.a) AND (t1.b = t2.b)) + -> Foreign Scan on public.async_p1 t1 + Output: t1.a, t1.b, t1.c + Remote Filter: ((t1.b % 100) = 0) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE (((b % 100) = 0)) + -> Append + -> Async Foreign Scan on public.async_p1 t2_1 + Output: t2_1.a, t2_1.b, t2_1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 t2_2 + Output: t2_2.a, t2_2.b, t2_2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Seq Scan on public.async_p3 t2_3 + Output: t2_3.a, t2_3.b, t2_3.c +(17 rows) + +INSERT INTO join_tbl SELECT * FROM async_p1 t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +SELECT * FROM join_tbl ORDER BY a1; + a1 | b1 | c1 | a2 | b2 | c2 +------+-----+------+------+-----+------ + 1000 | 0 | 0000 | 1000 | 0 | 0000 + 1100 | 100 | 0100 | 1100 | 100 | 0100 + 1200 | 200 | 0200 | 1200 | 200 | 0200 + 1300 | 300 | 0300 | 1300 | 300 | 0300 + 1400 | 400 | 0400 | 1400 | 400 | 0400 + 1500 | 500 | 0500 | 1500 | 500 | 0500 + 1600 | 600 | 0600 | 1600 | 600 | 0600 + 1700 | 700 | 0700 | 1700 | 700 | 0700 + 1800 | 800 | 0800 | 1800 | 800 | 0800 + 1900 | 900 | 0900 | 1900 | 900 | 0900 +(10 rows) + +DELETE FROM join_tbl; +RESET enable_hashjoin; +-- Test interaction of async execution with plan-time partition pruning +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE a < 3000; + QUERY PLAN +----------------------------------------------------------------------------- + Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Remote Filter: (async_pt_1.a < 3000) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((a < 3000)) + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Remote Filter: (async_pt_2.a < 3000) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((a < 3000)) +(9 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE a < 2000; + QUERY PLAN +----------------------------------------------------------------------- + Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Remote Filter: (async_pt_1.a < 2000) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((a < 2000)) +(4 rows) + +-- Test interaction of async execution with run-time partition pruning +SET plan_cache_mode TO force_generic_plan; +PREPARE async_pt_query (int, int) AS + INSERT INTO result_tbl SELECT * FROM async_pt WHERE a < $1 AND b === $2; +EXPLAIN (VERBOSE, COSTS OFF) +EXECUTE async_pt_query (3000, 505); + QUERY PLAN +------------------------------------------------------------------------------------------ + Insert on public.result_tbl + -> Append + Subplans Removed: 1 + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === $2) + Remote Filter: (async_pt_1.a < $1) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((a < $1::integer)) + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Filter: (async_pt_2.b === $2) + Remote Filter: (async_pt_2.a < $1) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((a < $1::integer)) +(13 rows) + +EXECUTE async_pt_query (3000, 505); +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1505 | 505 | 0505 + 2505 | 505 | 0505 +(2 rows) + +DELETE FROM result_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +EXECUTE async_pt_query (2000, 505); + QUERY PLAN +------------------------------------------------------------------------------------------ + Insert on public.result_tbl + -> Append + Subplans Removed: 2 + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === $2) + Remote Filter: (async_pt_1.a < $1) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((a < $1::integer)) +(8 rows) + +EXECUTE async_pt_query (2000, 505); +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1505 | 505 | 0505 +(1 row) + +DELETE FROM result_tbl; +RESET plan_cache_mode; +CREATE TABLE local_tbl(a int, b int, c text); +INSERT INTO local_tbl VALUES (1505, 505, 'foo'), (2505, 505, 'bar'); +ANALYZE local_tbl; +CREATE INDEX base_tbl1_idx ON base_tbl1 (a); +CREATE INDEX base_tbl2_idx ON base_tbl2 (a); +CREATE INDEX async_p3_idx ON async_p3 (a); +ANALYZE base_tbl1; +ANALYZE base_tbl2; +ANALYZE async_p3; +ALTER FOREIGN TABLE async_p1 OPTIONS (use_remote_estimate 'true'); +ALTER FOREIGN TABLE async_p2 OPTIONS (use_remote_estimate 'true'); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; + QUERY PLAN +------------------------------------------------------------------------------------------ + Merge Join + Output: local_tbl.a, local_tbl.b, local_tbl.c, async_pt.a, async_pt.b, async_pt.c + Merge Cond: (async_pt.a = local_tbl.a) + -> Merge Append + Sort Key: async_pt.a + -> Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 ORDER BY a ASC NULLS LAST + -> Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 ORDER BY a ASC NULLS LAST + -> Index Scan using async_p3_idx on public.async_p3 async_pt_3 + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c + -> Sort + Output: local_tbl.a, local_tbl.b, local_tbl.c + Sort Key: local_tbl.a + -> Seq Scan on public.local_tbl + Output: local_tbl.a, local_tbl.b, local_tbl.c + Storage Filter: (local_tbl.c = 'bar'::text) +(19 rows) + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; + QUERY PLAN +------------------------------------------------------------------------------------------ + Merge Join (actual rows=1 loops=1) + Merge Cond: (async_pt.a = local_tbl.a) + -> Merge Append (actual rows=303 loops=1) + Sort Key: async_pt.a + -> Foreign Scan on async_p1 async_pt_1 (actual rows=200 loops=1) + -> Foreign Scan on async_p2 async_pt_2 (actual rows=103 loops=1) + -> Index Scan using async_p3_idx on async_p3 async_pt_3 (actual rows=1 loops=1) + -> Sort (actual rows=1 loops=1) + Sort Key: local_tbl.a + Sort Method: quicksort Memory: 25kB + -> Seq Scan on local_tbl (actual rows=1 loops=1) + Storage Filter: (c = 'bar'::text) +(12 rows) + +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; + a | b | c | a | b | c +------+-----+-----+------+-----+------ + 2505 | 505 | bar | 2505 | 505 | 0505 +(1 row) + +ALTER FOREIGN TABLE async_p1 OPTIONS (DROP use_remote_estimate); +ALTER FOREIGN TABLE async_p2 OPTIONS (DROP use_remote_estimate); +DROP TABLE local_tbl; +DROP INDEX base_tbl1_idx; +DROP INDEX base_tbl2_idx; +DROP INDEX async_p3_idx; +-- UNION queries +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Insert on public.result_tbl + -> HashAggregate + Output: async_p1.a, async_p1.b, (('AAA'::text || async_p1.c)) + Group Key: async_p1.a, async_p1.b, (('AAA'::text || async_p1.c)) + -> Append + -> Async Foreign Scan on public.async_p1 + Output: async_p1.a, async_p1.b, ('AAA'::text || async_p1.c) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 ORDER BY a ASC NULLS LAST LIMIT 10::bigint + -> Async Foreign Scan on public.async_p2 + Output: async_p2.a, async_p2.b, ('AAA'::text || async_p2.c) + Remote Filter: (async_p2.b < 10) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((b < 10)) +(12 rows) + +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+----+--------- + 1000 | 0 | AAA0000 + 1005 | 5 | AAA0005 + 1010 | 10 | AAA0010 + 1015 | 15 | AAA0015 + 1020 | 20 | AAA0020 + 1025 | 25 | AAA0025 + 1030 | 30 | AAA0030 + 1035 | 35 | AAA0035 + 1040 | 40 | AAA0040 + 1045 | 45 | AAA0045 + 2000 | 0 | AAA0000 + 2005 | 5 | AAA0005 +(12 rows) + +DELETE FROM result_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); + QUERY PLAN +----------------------------------------------------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 + Output: async_p1.a, async_p1.b, ('AAA'::text || async_p1.c) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 ORDER BY a ASC NULLS LAST LIMIT 10::bigint + -> Async Foreign Scan on public.async_p2 + Output: async_p2.a, async_p2.b, ('AAA'::text || async_p2.c) + Remote Filter: (async_p2.b < 10) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((b < 10)) +(9 rows) + +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+----+--------- + 1000 | 0 | AAA0000 + 1005 | 5 | AAA0005 + 1010 | 10 | AAA0010 + 1015 | 15 | AAA0015 + 1020 | 20 | AAA0020 + 1025 | 25 | AAA0025 + 1030 | 30 | AAA0030 + 1035 | 35 | AAA0035 + 1040 | 40 | AAA0040 + 1045 | 45 | AAA0045 + 2000 | 0 | AAA0000 + 2005 | 5 | AAA0005 +(12 rows) + +DELETE FROM result_tbl; +-- Disable async execution if we use gating Result nodes for pseudoconstant +-- quals +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE CURRENT_USER = SESSION_USER; + QUERY PLAN +---------------------------------------------------------------- + Append + -> Result + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Result + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Result + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Seq Scan on public.async_p3 async_pt_3 + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c +(18 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +(SELECT * FROM async_p1 WHERE CURRENT_USER = SESSION_USER) +UNION ALL +(SELECT * FROM async_p2 WHERE CURRENT_USER = SESSION_USER); + QUERY PLAN +---------------------------------------------------------------- + Append + -> Result + Output: async_p1.a, async_p1.b, async_p1.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p1 + Output: async_p1.a, async_p1.b, async_p1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Result + Output: async_p2.a, async_p2.b, async_p2.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p2 + Output: async_p2.a, async_p2.b, async_p2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 +(13 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ((SELECT * FROM async_p1 WHERE b < 10) UNION ALL (SELECT * FROM async_p2 WHERE b < 10)) s WHERE CURRENT_USER = SESSION_USER; + QUERY PLAN +--------------------------------------------------------------------------------- + Append + -> Result + Output: async_p1.a, async_p1.b, async_p1.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p1 + Output: async_p1.a, async_p1.b, async_p1.c + Remote Filter: (async_p1.b < 10) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((b < 10)) + -> Result + Output: async_p2.a, async_p2.b, async_p2.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p2 + Output: async_p2.a, async_p2.b, async_p2.c + Remote Filter: (async_p2.b < 10) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((b < 10)) +(15 rows) + +-- Test that pending requests are processed properly +SET enable_mergejoin TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt t1, async_p2 t2 WHERE t1.a = t2.a AND t1.b === 505; + QUERY PLAN +---------------------------------------------------------------- + Nested Loop + Output: t1.a, t1.b, t1.c, t2.a, t2.b, t2.c + Join Filter: (t1.a = t2.a) + -> Append + -> Async Foreign Scan on public.async_p1 t1_1 + Output: t1_1.a, t1_1.b, t1_1.c + Filter: (t1_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 t1_2 + Output: t1_2.a, t1_2.b, t1_2.c + Filter: (t1_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Seq Scan on public.async_p3 t1_3 + Output: t1_3.a, t1_3.b, t1_3.c + Storage Filter: (t1_3.b === 505) + -> Materialize + Output: t2.a, t2.b, t2.c + -> Foreign Scan on public.async_p2 t2 + Output: t2.a, t2.b, t2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 +(20 rows) + +SELECT * FROM async_pt t1, async_p2 t2 WHERE t1.a = t2.a AND t1.b === 505; + a | b | c | a | b | c +------+-----+------+------+-----+------ + 2505 | 505 | 0505 | 2505 | 505 | 0505 +(1 row) + +CREATE TABLE local_tbl (a int, b int, c text); +INSERT INTO local_tbl VALUES (1505, 505, 'foo'); +ANALYZE local_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: t1.a, t1.b, t1.c, async_pt_3.a, async_pt_3.b, async_pt_3.c, ($0) + Join Filter: (t1.a = async_pt_3.a) + InitPlan 1 (returns $0) + -> Finalize Aggregate + Output: count(*) + -> Append + -> Partial Aggregate + Output: PARTIAL count(*) + -> Foreign Scan on public.async_p1 async_pt_1 + Remote Filter: (async_pt_1.a < 3000) + Remote SQL: SELECT NULL FROM public.base_tbl1 WHERE ((a < 3000)) + -> Partial Aggregate + Output: PARTIAL count(*) + -> Foreign Scan on public.async_p2 async_pt_2 + Remote Filter: (async_pt_2.a < 3000) + Remote SQL: SELECT NULL FROM public.base_tbl2 WHERE ((a < 3000)) + -> Seq Scan on public.local_tbl t1 + Output: t1.a, t1.b, t1.c + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_4 + Output: async_pt_4.a, async_pt_4.b, async_pt_4.c, $0 + Remote Filter: (async_pt_4.a < 3000) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((a < 3000)) + -> Async Foreign Scan on public.async_p2 async_pt_5 + Output: async_pt_5.a, async_pt_5.b, async_pt_5.c, $0 + Remote Filter: (async_pt_5.a < 3000) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((a < 3000)) +(28 rows) + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; + QUERY PLAN +----------------------------------------------------------------------------------------- + Nested Loop Left Join (actual rows=1 loops=1) + Join Filter: (t1.a = async_pt_3.a) + Rows Removed by Join Filter: 399 + InitPlan 1 (returns $0) + -> Finalize Aggregate (actual rows=1 loops=1) + -> Append (actual rows=2 loops=1) + -> Partial Aggregate (actual rows=1 loops=1) + -> Foreign Scan on async_p1 async_pt_1 (actual rows=200 loops=1) + Remote Filter: (a < 3000) + -> Partial Aggregate (actual rows=1 loops=1) + -> Foreign Scan on async_p2 async_pt_2 (actual rows=200 loops=1) + Remote Filter: (a < 3000) + -> Seq Scan on local_tbl t1 (actual rows=1 loops=1) + -> Append (actual rows=400 loops=1) + -> Async Foreign Scan on async_p1 async_pt_4 (actual rows=200 loops=1) + Remote Filter: (a < 3000) + -> Async Foreign Scan on async_p2 async_pt_5 (actual rows=200 loops=1) + Remote Filter: (a < 3000) +(18 rows) + +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; + a | b | c | a | b | c | count +------+-----+-----+------+-----+------+------- + 1505 | 505 | foo | 1505 | 505 | 0505 | 400 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; + QUERY PLAN +---------------------------------------------------------------- + Limit + Output: t1.a, t1.b, t1.c + -> Append + -> Async Foreign Scan on public.async_p1 t1_1 + Output: t1_1.a, t1_1.b, t1_1.c + Filter: (t1_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 t1_2 + Output: t1_2.a, t1_2.b, t1_2.c + Filter: (t1_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Seq Scan on public.async_p3 t1_3 + Output: t1_3.a, t1_3.b, t1_3.c + Storage Filter: (t1_3.b === 505) +(14 rows) + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; + QUERY PLAN +------------------------------------------------------------------------- + Limit (actual rows=1 loops=1) + -> Append (actual rows=1 loops=1) + -> Async Foreign Scan on async_p1 t1_1 (actual rows=0 loops=1) + Filter: (b === 505) + -> Async Foreign Scan on async_p2 t1_2 (actual rows=0 loops=1) + Filter: (b === 505) + -> Seq Scan on async_p3 t1_3 (actual rows=1 loops=1) + Storage Filter: (b === 505) +(8 rows) + +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; + a | b | c +------+-----+------ + 3505 | 505 | 0505 +(1 row) + +-- Check with foreign modify +CREATE TABLE base_tbl3 (a int, b int, c text); +CREATE FOREIGN TABLE remote_tbl (a int, b int, c text) + SERVER loopback OPTIONS (table_name 'base_tbl3'); +INSERT INTO remote_tbl VALUES (2505, 505, 'bar'); +CREATE TABLE base_tbl4 (a int, b int, c text); +CREATE FOREIGN TABLE insert_tbl (a int, b int, c text) + SERVER loopback OPTIONS (table_name 'base_tbl4'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO insert_tbl (SELECT * FROM local_tbl UNION ALL SELECT * FROM remote_tbl); + QUERY PLAN +------------------------------------------------------------------------- + Insert on public.insert_tbl + Remote SQL: INSERT INTO public.base_tbl4(a, b, c) VALUES ($1, $2, $3) + Batch Size: 1 + -> Append + -> Seq Scan on public.local_tbl + Output: local_tbl.a, local_tbl.b, local_tbl.c + -> Async Foreign Scan on public.remote_tbl + Output: remote_tbl.a, remote_tbl.b, remote_tbl.c + Remote SQL: SELECT a, b, c FROM public.base_tbl3 +(9 rows) + +INSERT INTO insert_tbl (SELECT * FROM local_tbl UNION ALL SELECT * FROM remote_tbl); +SELECT * FROM insert_tbl ORDER BY a; + a | b | c +------+-----+----- + 1505 | 505 | foo + 2505 | 505 | bar +(2 rows) + +-- Check with direct modify +EXPLAIN (VERBOSE, COSTS OFF) +WITH t AS (UPDATE remote_tbl SET c = c || c RETURNING *) +INSERT INTO join_tbl SELECT * FROM async_pt LEFT JOIN t ON (async_pt.a = t.a AND async_pt.b = t.b) WHERE async_pt.b === 505; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Insert on public.join_tbl + CTE t + -> Update on public.remote_tbl + Output: remote_tbl.a, remote_tbl.b, remote_tbl.c + -> Foreign Update on public.remote_tbl + Remote SQL: UPDATE public.base_tbl3 SET c = (c || c) RETURNING a, b, c, ybctid + -> Nested Loop Left Join + Output: async_pt.a, async_pt.b, async_pt.c, t.a, t.b, t.c + Join Filter: ((async_pt.a = t.a) AND (async_pt.b = t.b)) + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Filter: (async_pt_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Seq Scan on public.async_p3 async_pt_3 + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c + Storage Filter: (async_pt_3.b === 505) + -> CTE Scan on t + Output: t.a, t.b, t.c +(23 rows) + +WITH t AS (UPDATE remote_tbl SET c = c || c RETURNING *) +INSERT INTO join_tbl SELECT * FROM async_pt LEFT JOIN t ON (async_pt.a = t.a AND async_pt.b = t.b) WHERE async_pt.b === 505; +SELECT * FROM join_tbl ORDER BY a1; + a1 | b1 | c1 | a2 | b2 | c2 +------+-----+------+------+-----+-------- + 1505 | 505 | 0505 | | | + 2505 | 505 | 0505 | 2505 | 505 | barbar + 3505 | 505 | 0505 | | | +(3 rows) + +DELETE FROM join_tbl; +DROP TABLE local_tbl; +DROP FOREIGN TABLE remote_tbl; +DROP FOREIGN TABLE insert_tbl; +DROP TABLE base_tbl3; +DROP TABLE base_tbl4; +RESET enable_mergejoin; +RESET enable_hashjoin; +-- Test that UPDATE/DELETE with inherited target works with async_capable enabled +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE async_pt SET c = c || c WHERE b = 0 RETURNING *; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------- + Update on public.async_pt + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Foreign Update on public.async_p1 async_pt_1 + Foreign Update on public.async_p2 async_pt_2 + Update on public.async_p3 async_pt_3 + -> Append + -> Foreign Update on public.async_p1 async_pt_1 + Remote Filter: (async_pt_1.b = 0) + Remote SQL: UPDATE public.base_tbl1 SET c = (c || c) WHERE ((b = 0)) RETURNING a, b, c, ybctid + -> Foreign Update on public.async_p2 async_pt_2 + Remote Filter: (async_pt_2.b = 0) + Remote SQL: UPDATE public.base_tbl2 SET c = (c || c) WHERE ((b = 0)) RETURNING a, b, c, ybctid + -> Seq Scan on public.async_p3 async_pt_3 + Output: (async_pt_3.c || async_pt_3.c), async_pt_3.tableoid, async_pt_3.ybctid, async_pt_3.* + Storage Filter: (async_pt_3.b = 0) +(15 rows) + +UPDATE async_pt SET c = c || c WHERE b = 0 RETURNING *; + a | b | c +------+---+---------- + 1000 | 0 | 00000000 + 2000 | 0 | 00000000 + 3000 | 0 | 00000000 +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +DELETE FROM async_pt WHERE b = 0 RETURNING *; + QUERY PLAN +-------------------------------------------------------------------------------------------------------- + Delete on public.async_pt + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Foreign Delete on public.async_p1 async_pt_1 + Foreign Delete on public.async_p2 async_pt_2 + Delete on public.async_p3 async_pt_3 + -> Append + -> Foreign Delete on public.async_p1 async_pt_1 + Remote Filter: (async_pt_1.b = 0) + Remote SQL: DELETE FROM public.base_tbl1 WHERE ((b = 0)) RETURNING a, b, c, ybctid + -> Foreign Delete on public.async_p2 async_pt_2 + Remote Filter: (async_pt_2.b = 0) + Remote SQL: DELETE FROM public.base_tbl2 WHERE ((b = 0)) RETURNING a, b, c, ybctid + -> Seq Scan on public.async_p3 async_pt_3 + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c, async_pt_3.tableoid, async_pt_3.ybctid + Storage Filter: (async_pt_3.b = 0) +(15 rows) + +DELETE FROM async_pt WHERE b = 0 RETURNING *; + a | b | c +------+---+---------- + 1000 | 0 | 00000000 + 2000 | 0 | 00000000 + 3000 | 0 | 00000000 +(3 rows) + +-- Check EXPLAIN ANALYZE for a query that scans empty partitions asynchronously +DELETE FROM async_p1; +DELETE FROM async_p2; +DELETE FROM async_p3; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM async_pt; + QUERY PLAN +------------------------------------------------------------------------- + Append (actual rows=0 loops=1) + -> Async Foreign Scan on async_p1 async_pt_1 (actual rows=0 loops=1) + -> Async Foreign Scan on async_p2 async_pt_2 (actual rows=0 loops=1) + -> Seq Scan on async_p3 async_pt_3 (actual rows=0 loops=1) +(4 rows) + +-- Clean up +DROP TABLE async_pt; +DROP TABLE base_tbl1; +DROP TABLE base_tbl2; +DROP TABLE result_tbl; +DROP TABLE join_tbl; +-- Test that an asynchronous fetch is processed before restarting the scan in +-- ReScanForeignScan +CREATE TABLE base_tbl (a int, b int); +INSERT INTO base_tbl VALUES (1, 11), (2, 22), (3, 33); +CREATE FOREIGN TABLE foreign_tbl (b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +CREATE FOREIGN TABLE foreign_tbl2 () INHERITS (foreign_tbl) + SERVER loopback OPTIONS (table_name 'base_tbl'); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); + QUERY PLAN +----------------------------------------------------------------------------- + Seq Scan on public.base_tbl + Output: base_tbl.a + Filter: (SubPlan 1) + SubPlan 1 + -> Result foreign_tbl + Output: base_tbl.a + -> Append + -> Async Foreign Scan on public.foreign_tbl foreign_tbl_1 + Remote SQL: SELECT NULL FROM public.base_tbl + -> Async Foreign Scan on public.foreign_tbl2 foreign_tbl_2 + Remote SQL: SELECT NULL FROM public.base_tbl +(11 rows) + +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); + a +--- + 1 + 2 + 3 +(3 rows) + +-- Clean up +DROP FOREIGN TABLE foreign_tbl CASCADE; +NOTICE: drop cascades to foreign table foreign_tbl2 +DROP TABLE base_tbl; +ALTER SERVER loopback OPTIONS (DROP async_capable); +ALTER SERVER loopback2 OPTIONS (DROP async_capable); +-- =================================================================== +-- test invalid server, foreign table and foreign data wrapper options +-- =================================================================== +-- Invalid fdw_startup_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_startup_cost '100$%$#$#'); +ERROR: invalid value for floating point option "fdw_startup_cost": 100$%$#$# +-- Invalid fdw_tuple_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_tuple_cost '100$%$#$#'); +ERROR: invalid value for floating point option "fdw_tuple_cost": 100$%$#$# +-- Invalid fetch_size option +CREATE FOREIGN TABLE inv_fsz (c1 int ) + SERVER loopback OPTIONS (fetch_size '100$%$#$#'); +ERROR: invalid value for integer option "fetch_size": 100$%$#$# +-- Invalid batch_size option +CREATE FOREIGN TABLE inv_bsz (c1 int ) + SERVER loopback OPTIONS (batch_size '100$%$#$#'); +ERROR: invalid value for integer option "batch_size": 100$%$#$# +-- No option is allowed to be specified at foreign data wrapper level +ALTER FOREIGN DATA WRAPPER postgres_fdw OPTIONS (nonexistent 'fdw'); +ERROR: invalid option "nonexistent" +HINT: There are no valid options in this context. +-- =================================================================== +-- test postgres_fdw.application_name GUC +-- =================================================================== +-- To avoid race conditions in checking the remote session's application_name, +-- use this view to make the remote session itself read its application_name. +CREATE VIEW my_application_name AS + SELECT application_name FROM pg_stat_activity WHERE pid = pg_backend_pid(); +CREATE FOREIGN TABLE remote_application_name (application_name text) + SERVER loopback2 + OPTIONS (schema_name 'public', table_name 'my_application_name'); +SELECT count(*) FROM remote_application_name; + count +------- + 1 +(1 row) + +-- Specify escape sequences in application_name option of a server +-- object so as to test that they are replaced with status information +-- expectedly. Note that we are also relying on ALTER SERVER to force +-- the remote session to be restarted with its new application name. +-- +-- Since pg_stat_activity.application_name may be truncated to less than +-- NAMEDATALEN characters, note that substring() needs to be used +-- at the condition of test query to make sure that the string consisting +-- of database name and process ID is also less than that. +ALTER SERVER loopback2 OPTIONS (application_name 'fdw_%d%p'); +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_database() || pg_backend_pid() for + current_setting('max_identifier_length')::int); + count +------- + 1 +(1 row) + +-- postgres_fdw.application_name overrides application_name option +-- of a server object if both settings are present. +ALTER SERVER loopback2 OPTIONS (SET application_name 'fdw_wrong'); +SET postgres_fdw.application_name TO 'fdw_%a%u%%'; +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_setting('application_name') || + CURRENT_USER || '%' for current_setting('max_identifier_length')::int); + count +------- + 1 +(1 row) + +RESET postgres_fdw.application_name; +-- Test %c (session ID) and %C (cluster name) escape sequences. +ALTER SERVER loopback2 OPTIONS (SET application_name 'fdw_%C%c'); +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_setting('cluster_name') || + to_hex(trunc(EXTRACT(EPOCH FROM (SELECT backend_start FROM + pg_stat_get_activity(pg_backend_pid()))))::integer) || '.' || + to_hex(pg_backend_pid()) + for current_setting('max_identifier_length')::int); + count +------- + 1 +(1 row) + +-- Clean up. +DROP FOREIGN TABLE remote_application_name; +DROP VIEW my_application_name; +-- =================================================================== +-- test parallel commit +-- =================================================================== +ALTER SERVER loopback OPTIONS (ADD parallel_commit 'true'); +ALTER SERVER loopback2 OPTIONS (ADD parallel_commit 'true'); +CREATE TABLE ploc1 (f1 int, f2 text); +CREATE FOREIGN TABLE prem1 (f1 int, f2 text) + SERVER loopback OPTIONS (table_name 'ploc1'); +CREATE TABLE ploc2 (f1 int, f2 text); +CREATE FOREIGN TABLE prem2 (f1 int, f2 text) + SERVER loopback2 OPTIONS (table_name 'ploc2'); +BEGIN; +INSERT INTO prem1 VALUES (101, 'foo'); +INSERT INTO prem2 VALUES (201, 'bar'); +COMMIT; +SELECT * FROM prem1; + f1 | f2 +-----+----- + 101 | foo +(1 row) + +SELECT * FROM prem2; + f1 | f2 +-----+----- + 201 | bar +(1 row) + +BEGIN; +SAVEPOINT s; +INSERT INTO prem1 VALUES (102, 'foofoo'); +INSERT INTO prem2 VALUES (202, 'barbar'); +RELEASE SAVEPOINT s; +COMMIT; +SELECT * FROM prem1; + f1 | f2 +-----+-------- + 101 | foo + 102 | foofoo +(2 rows) + +SELECT * FROM prem2; + f1 | f2 +-----+-------- + 201 | bar + 202 | barbar +(2 rows) + +-- This tests executing DEALLOCATE ALL against foreign servers in parallel +-- during pre-commit +BEGIN; +SAVEPOINT s; +INSERT INTO prem1 VALUES (103, 'baz'); +INSERT INTO prem2 VALUES (203, 'qux'); +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +INSERT INTO prem1 VALUES (104, 'bazbaz'); +INSERT INTO prem2 VALUES (204, 'quxqux'); +COMMIT; +SELECT * FROM prem1; + f1 | f2 +-----+-------- + 101 | foo + 102 | foofoo + 104 | bazbaz +(3 rows) + +SELECT * FROM prem2; + f1 | f2 +-----+-------- + 201 | bar + 202 | barbar + 204 | quxqux +(3 rows) + +ALTER SERVER loopback OPTIONS (DROP parallel_commit); +ALTER SERVER loopback2 OPTIONS (DROP parallel_commit); diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index fa2044b2c5d53..49b5e584e8bbb 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -207,6 +207,22 @@ postgres_fdw_validator(PG_FUNCTION_ARGS) errmsg("sslcert and sslkey are superuser-only"), errhint("User mappings with the sslcert or sslkey options set may only be created or modified by the superuser."))); } + /* YB specific options */ + else if (strcmp(def->defname, "server_type") == 0) + { + /* + * This functions is invoked for both CREATE and ALTER SERVER. In + * case of the latter, no context is available to check if the user + * is changing or dropping the server_type. + */ + if (!yb_is_valid_server_type(defGetString(def))) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid server_type '%s'", defGetString(def)), + errhint("Supported server types: [postgreSQL, yugabyteDB]"))); + } + } } PG_RETURN_VOID(); @@ -262,6 +278,9 @@ InitPgFdwOptions(void) {"sslcert", UserMappingRelationId, true}, {"sslkey", UserMappingRelationId, true}, + /* YB specific options */ + {"server_type", ForeignServerRelationId, false /* is_libpq_opt */ }, + {NULL, InvalidOid, false} }; diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 8aaa3a6a1daee..b5949f44a6262 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -51,6 +51,16 @@ #include "utils/sampling.h" #include "utils/selfuncs.h" +/* YB includes */ +#include "catalog/pg_authid.h" +#include "commands/dbcommands.h" +#include "pg_yb_utils.h" +#include "utils/acl.h" +#include "yb/yql/pggate/util/ybc_pgresult_util.h" +#include "yb/yql/pggate/ybc_pg_typedefs.h" +#include "yb/yql/pggate/ybc_pggate.h" +#include "ybctid.h" + PG_MODULE_MAGIC; /* Default CPU cost to start up a foreign query. */ @@ -62,6 +72,16 @@ PG_MODULE_MAGIC; /* If no remote estimates, assume a sort costs 20% extra */ #define DEFAULT_FDW_SORT_MULTIPLIER 1.2 +/* YB: If no server_type is provided, assume that the underlying server is Postgres */ +#define YB_DEFAULT_FDW_SERVER_TYPE PG_FDW_SERVER_POSTGRES + +const char *yb_server_types[] = { + [PG_FDW_SERVER_UNKNOWN] = "unknown", + [PG_FDW_SERVER_POSTGRES] = "postgreSQL", + [PG_FDW_SERVER_YUGABYTEDB] = "yugabyteDB", + [PG_FDW_SERVER_FEDERATED_YUGABYTEDB] = "federatedYugabyteDB" +}; + /* * Indexes of FDW-private information stored in fdw_private lists. * @@ -173,6 +193,9 @@ typedef struct PgFdwScanState MemoryContext temp_cxt; /* context for per-tuple temporary data */ int fetch_size; /* number of tuples per fetch */ + + /* YB state: opaque handle to PgGlobalViewRead for federatedYugabyteDB scans */ + YbcPgGlobalViewRead yb_gvr; } PgFdwScanState; /* @@ -211,6 +234,9 @@ typedef struct PgFdwModifyState /* for update row movement if subplan result rel */ struct PgFdwModifyState *aux_fmstate; /* foreign-insert state, if * created */ + + /* YB state */ + YbPgFdwServerType yb_server_type; /* type of server hosting the relation */ } PgFdwModifyState; /* @@ -247,6 +273,9 @@ typedef struct PgFdwDirectModifyState /* working memory context */ MemoryContext temp_cxt; /* context for per-tuple temporary data */ + + /* YB state */ + YbPgFdwServerType yb_server_type; /* type of server hosting the relation */ } PgFdwDirectModifyState; /* @@ -471,7 +500,8 @@ static void prepare_foreign_modify(PgFdwModifyState *fmstate); static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate, ItemPointer tupleid, TupleTableSlot **slots, - int numSlots); + int numSlots, + bytea *ybctid); static void store_returning_result(PgFdwModifyState *fmstate, TupleTableSlot *slot, PGresult *res); static void finish_foreign_modify(PgFdwModifyState *fmstate); @@ -543,6 +573,13 @@ static void merge_fdw_options(PgFdwRelationInfo *fpinfo, const PgFdwRelationInfo *fpinfo_i); static int get_batch_size_option(Relation rel); +static const char *yb_server_type_to_string(YbPgFdwServerType server_type); +static YbPgFdwServerType yb_get_server_type(const char *server_type); +static YbPgFdwServerType yb_get_server_type_from_ftrelid(Oid relid); +static const char *yb_get_tuple_identifier_colname(YbPgFdwServerType server_type); +static AttrNumber yb_get_min_attr_from_server_type(YbPgFdwServerType server_type); +static PGresult *YbGlobalViewReadExecScan(YbcPgGlobalViewRead yb_gvr, const char *query); + /* * Foreign-data wrapper handler function: return a struct with pointers @@ -687,14 +724,14 @@ postgresGetForeignRelSize(PlannerInfo *root, * columns used in them. Doesn't seem worth detecting that case though.) */ fpinfo->attrs_used = NULL; - pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, - &fpinfo->attrs_used); + pull_varattnos_min_attr((Node *) baserel->reltarget->exprs, baserel->relid, + &fpinfo->attrs_used, fpinfo->yb_min_attr + 1); foreach(lc, fpinfo->local_conds) { RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); - pull_varattnos((Node *) rinfo->clause, baserel->relid, - &fpinfo->attrs_used); + pull_varattnos_min_attr((Node *) rinfo->clause, baserel->relid, + &fpinfo->attrs_used, fpinfo->yb_min_attr + 1); } /* @@ -1037,8 +1074,12 @@ postgresGetForeignPaths(PlannerInfo *root, NIL); /* no fdw_private list */ add_path(baserel, (Path *) path); - /* Add paths with pathkeys */ - add_paths_with_pathkeys_for_rel(root, baserel, NULL); + /* + * Add paths with pathkeys. + * YB: Do not push down ORDER BY for federated server (global views). + */ + if (fpinfo->yb_server_type != PG_FDW_SERVER_FEDERATED_YUGABYTEDB) + add_paths_with_pathkeys_for_rel(root, baserel, NULL); /* * If we're not using remote estimates, stop here. We have no way to @@ -1522,21 +1563,68 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags) else rtindex = bms_next_member(fsplan->fs_relids, -1); rte = exec_rt_fetch(rtindex, estate); + + YbPgFdwServerType yb_server_type = yb_get_server_type_from_ftrelid(rte->relid); + + if (yb_server_type == PG_FDW_SERVER_FEDERATED_YUGABYTEDB) + { + if (!yb_enable_global_views) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("federatedYB queries are not supported"), + errhint("Must enable the GUC yb_enable_global_views"))); + + if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for federated YugabyteDB query"), + errhint("Must be a member of the pg_read_all_stats role."))); + } + userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); /* Get info about foreign table. */ table = GetForeignTable(rte->relid); - user = GetUserMapping(userid, table->serverid); /* - * Get connection to the foreign server. Connection manager will - * establish new connection if necessary. + * YB: For federatedYugabyteDB server types, the query is passed to the remote + * tservers through RPCs. The remote tserver uses a unix socket connection to + * execute the query. */ - fsstate->conn = GetConnection(user, false, &fsstate->conn_state); + if (yb_server_type != PG_FDW_SERVER_FEDERATED_YUGABYTEDB) + { + user = GetUserMapping(userid, table->serverid); + /* + * Get connection to the foreign server. Connection manager will + * establish new connection if necessary. + */ + fsstate->conn = GetConnection(user, false, &fsstate->conn_state); + + /* + * Assign a unique ID for my cursor + * YB: Retrieval is capped at the rpc_max_message_size per tserver. + * If this limit is exceeded, excess data is truncated, and a warning + * is logged here. + */ + fsstate->cursor_number = GetCursorNumber(fsstate->conn); + } - /* Assign a unique ID for my cursor */ - fsstate->cursor_number = GetCursorNumber(fsstate->conn); + /* + * YB: For federatedYB tables (yb_gvr path), no SQL cursor is used. + * Instead, create_cursor sets cursor_exists = true after binding + * parameters via YBCPgGlobalViewReadSetParams. + * + * postgresIterateForeignScan checks this flag and calls create_cursor on + * the first iterate when it is false. Without this guard, create_cursor + * would be called on every iterate, redundantly re-binding parameters. + * postgresReScanForeignScan resets it to false for yb_gvr scans (which + * cannot be rewound), so that the next iterate re-initializes the scan + * with fresh parameters. postgresEndForeignScan skips the close_cursor + * call for yb_gvr scans entirely, destroying the YBCPg handle directly + * instead. + */ fsstate->cursor_exists = false; + fsstate->yb_gvr = NULL; /* Get private info created by planner functions. */ fsstate->query = strVal(list_nth(fsplan->fdw_private, @@ -1546,6 +1634,11 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags) fsstate->fetch_size = intVal(list_nth(fsplan->fdw_private, FdwScanPrivateFetchSize)); + /* YB: Create and initialize the global view scan. */ + if (yb_server_type == PG_FDW_SERVER_FEDERATED_YUGABYTEDB) + HandleYBStatus(YBCPgNewGlobalViewRead(get_database_name(MyDatabaseId), + &fsstate->yb_gvr)); + /* Create contexts for batches of tuples and per-tuple temp workspace. */ fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt, "postgres_fdw tuple data", @@ -1634,6 +1727,22 @@ postgresIterateForeignScan(ForeignScanState *node) return slot; } +/* + * YbResetForeignScanControlState + * Reset the tuple-fetching control state so the next iteration + * starts fresh. Used by both ReScan and the cursor-rewind paths. + */ +static void +YbResetForeignScanControlState(PgFdwScanState *fsstate) +{ + /* Now force a fresh FETCH. */ + fsstate->tuples = NULL; + fsstate->num_tuples = 0; + fsstate->next_tuple = 0; + fsstate->fetch_ct_2 = 0; + fsstate->eof_reached = false; +} + /* * postgresReScanForeignScan * Restart the scan. @@ -1649,6 +1758,18 @@ postgresReScanForeignScan(ForeignScanState *node) if (!fsstate->cursor_exists) return; + if (fsstate->yb_gvr) + { + YBCPgGlobalViewReadResetScan(fsstate->yb_gvr); + YbResetForeignScanControlState(fsstate); + /* + * YB: yb_gvr scans cannot be rewound, so force create_cursor on the + * next iterate to re-evaluate parameters. + */ + fsstate->cursor_exists = false; + return; + } + /* * If the node is async-capable, and an asynchronous fetch for it has been * begun, the asynchronous fetch might not have yet completed. Check if @@ -1704,12 +1825,7 @@ postgresReScanForeignScan(ForeignScanState *node) pgfdw_report_error(ERROR, res, fsstate->conn, true, sql); PQclear(res); - /* Now force a fresh FETCH. */ - fsstate->tuples = NULL; - fsstate->num_tuples = 0; - fsstate->next_tuple = 0; - fsstate->fetch_ct_2 = 0; - fsstate->eof_reached = false; + YbResetForeignScanControlState(fsstate); } /* @@ -1725,6 +1841,13 @@ postgresEndForeignScan(ForeignScanState *node) if (fsstate == NULL) return; + if (fsstate->yb_gvr) + { + YBCPgGlobalViewReadDestroy(fsstate->yb_gvr); + fsstate->yb_gvr = NULL; + return; + } + /* Close the cursor if open, to prevent accumulation of cursors */ if (fsstate->cursor_exists) close_cursor(fsstate->conn, fsstate->cursor_number, @@ -1749,6 +1872,23 @@ postgresAddForeignUpdateTargets(PlannerInfo *root, { Var *var; + /* + * In YugabyteDB, we need the ybctid to identify the tuple undergoing update/delete. + */ + if (yb_get_server_type_from_ftrelid(RelationGetRelid(target_relation)) == + PG_FDW_SERVER_YUGABYTEDB) + { + var = makeVar(rtindex, + YBTupleIdAttributeNumber, + BYTEAOID, + -1, + InvalidOid, + 0); + + add_row_identity_var(root, var, rtindex, "ybctid"); + return; + } + /* * In postgres_fdw, what we need is the ctid, same as for a regular table. */ @@ -1785,6 +1925,7 @@ postgresPlanForeignModify(PlannerInfo *root, List *retrieved_attrs = NIL; bool doNothing = false; int values_end_len = -1; + AttrNumber yb_min_attr = yb_get_min_attr_from_ftrelid(rte->relid); initStringInfo(&sql); @@ -1831,7 +1972,7 @@ postgresPlanForeignModify(PlannerInfo *root, while ((col = bms_next_member(allUpdatedCols, col)) >= 0) { /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */ - AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber; + AttrNumber attno = col + yb_min_attr; if (attno <= InvalidAttrNumber) /* shouldn't happen */ elog(ERROR, "system-column update is not supported"); @@ -2570,13 +2711,15 @@ postgresPlanDirectModify(PlannerInfo *root, processed_tlist, targetAttrs, remote_exprs, ¶ms_list, - returningList, &retrieved_attrs); + returningList, + &retrieved_attrs); break; case CMD_DELETE: deparseDirectDeleteSql(&sql, root, resultRelation, rel, foreignrel, remote_exprs, ¶ms_list, - returningList, &retrieved_attrs); + returningList, + &retrieved_attrs); break; default: elog(ERROR, "unexpected operation: %d", (int) operation); @@ -2672,6 +2815,9 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags) table = GetForeignTable(RelationGetRelid(dmstate->rel)); user = GetUserMapping(userid, table->serverid); + /* YB: Save server type of relation for future reference */ + dmstate->yb_server_type = yb_get_server_type_from_ftrelid(RelationGetRelid(dmstate->rel)); + /* * Get connection to the foreign server. Connection manager will * establish new connection if necessary. @@ -3723,7 +3869,7 @@ create_cursor(ForeignScanState *node) PGresult *res; /* First, process a pending asynchronous request, if any. */ - if (fsstate->conn_state->pendingAreq) + if (!fsstate->yb_gvr && fsstate->conn_state->pendingAreq) process_pending_request(fsstate->conn_state->pendingAreq); /* @@ -3745,6 +3891,14 @@ create_cursor(ForeignScanState *node) MemoryContextSwitchTo(oldcontext); } + if (fsstate->yb_gvr) + { + if (numParams > 0) + YBCPgGlobalViewReadSetParams(fsstate->yb_gvr, numParams, values); + fsstate->cursor_exists = true; + return; + } + /* Construct the DECLARE CURSOR command */ initStringInfo(&buf); appendStringInfo(&buf, "DECLARE c%u CURSOR FOR\n%s", @@ -3825,6 +3979,8 @@ fetch_more_data(ForeignScanState *node) /* Reset per-connection state */ fsstate->conn_state->pendingAreq = NULL; } + else if (fsstate->yb_gvr) + res = YbGlobalViewReadExecScan(fsstate->yb_gvr, fsstate->query); else { char sql[64]; @@ -3834,11 +3990,15 @@ fetch_more_data(ForeignScanState *node) fsstate->fetch_size, fsstate->cursor_number); res = pgfdw_exec_query(conn, sql, fsstate->conn_state); - /* On error, report the original query, not the FETCH. */ - if (PQresultStatus(res) != PGRES_TUPLES_OK) - pgfdw_report_error(ERROR, res, conn, false, fsstate->query); } + /* + * On error, report the original query, not the FETCH. + * YB: This code is common for all server types. + */ + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pgfdw_report_error(ERROR, res, conn, false, fsstate->query); + /* Convert the data into HeapTuples */ numrows = PQntuples(res); fsstate->tuples = (HeapTuple *) palloc0(numrows * sizeof(HeapTuple)); @@ -3863,7 +4023,10 @@ fetch_more_data(ForeignScanState *node) fsstate->fetch_ct_2++; /* Must be EOF if we didn't get as many tuples as we asked for. */ - fsstate->eof_reached = (numrows < fsstate->fetch_size); + if (fsstate->yb_gvr) + fsstate->eof_reached = YBCPgGlobalViewReadIsEof(fsstate->yb_gvr); + else + fsstate->eof_reached = (numrows < fsstate->fetch_size); } PG_FINALLY(); { @@ -4013,6 +4176,9 @@ create_foreign_modify(EState *estate, fmstate->has_returning = has_returning; fmstate->retrieved_attrs = retrieved_attrs; + /* YB: Save server type of relation for future reference */ + fmstate->yb_server_type = yb_get_server_type_from_ftrelid(RelationGetRelid(rel)); + /* Create context for per-tuple temp workspace. */ fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "postgres_fdw temporary data", @@ -4030,15 +4196,19 @@ create_foreign_modify(EState *estate, if (operation == CMD_UPDATE || operation == CMD_DELETE) { Assert(subplan != NULL); + const char *yb_tupleid_colname = yb_get_tuple_identifier_colname(fmstate->yb_server_type); /* Find the ctid resjunk column in the subplan's result */ fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist, - "ctid"); + yb_tupleid_colname); if (!AttributeNumberIsValid(fmstate->ctidAttno)) - elog(ERROR, "could not find junk ctid column"); + elog(ERROR, "could not find junk %s column", yb_tupleid_colname); /* First transmittable parameter will be ctid */ - getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena); + if (fmstate->yb_server_type == PG_FDW_SERVER_YUGABYTEDB) + getTypeOutputInfo(BYTEAOID, &typefnoid, &isvarlena); + else + getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena); fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]); fmstate->p_nums++; } @@ -4097,6 +4267,7 @@ execute_foreign_modify(EState *estate, PGresult *res; int n_rows; StringInfoData sql; + bytea *ybctid = NULL; /* The operation should be INSERT, UPDATE, or DELETE */ Assert(operation == CMD_INSERT || @@ -4145,12 +4316,16 @@ execute_foreign_modify(EState *estate, &isNull); /* shouldn't ever get a null result... */ if (isNull) - elog(ERROR, "ctid is NULL"); - ctid = (ItemPointer) DatumGetPointer(datum); + elog(ERROR, "%s is NULL", yb_get_tuple_identifier_colname(fmstate->yb_server_type)); + + if (fmstate->yb_server_type == PG_FDW_SERVER_YUGABYTEDB) + ybctid = DatumGetByteaP(datum); + else + ctid = (ItemPointer) DatumGetPointer(datum); } /* Convert parameters needed by prepared statement to text form */ - p_values = convert_prep_stmt_params(fmstate, ctid, slots, *numSlots); + p_values = convert_prep_stmt_params(fmstate, ctid, slots, *numSlots, ybctid); /* * Execute the prepared statement. @@ -4254,6 +4429,8 @@ prepare_foreign_modify(PgFdwModifyState *fmstate) * Create array of text strings representing parameter values * * tupleid is ctid to send, or NULL if none + * YB: ybctid is the tuple identifier for YugabyteDB server types. + * YB: At most one of tupleid or ybctid is non-NULL. * slot is slot to get remaining parameters from, or NULL if none * * Data is constructed in temp_cxt; caller should reset that after use. @@ -4262,7 +4439,8 @@ static const char ** convert_prep_stmt_params(PgFdwModifyState *fmstate, ItemPointer tupleid, TupleTableSlot **slots, - int numSlots) + int numSlots, + bytea *ybctid) { const char **p_values; int i; @@ -4275,7 +4453,7 @@ convert_prep_stmt_params(PgFdwModifyState *fmstate, p_values = (const char **) palloc(sizeof(char *) * fmstate->p_nums * numSlots); /* ctid is provided only for UPDATE/DELETE, which don't allow batching */ - Assert(!(tupleid != NULL && numSlots > 1)); + Assert(!(tupleid != NULL && ybctid && numSlots > 1)); /* 1st parameter should be ctid, if it's in use */ if (tupleid != NULL) @@ -4286,6 +4464,13 @@ convert_prep_stmt_params(PgFdwModifyState *fmstate, PointerGetDatum(tupleid)); pindex++; } + else if (ybctid) + { + Assert(numSlots == 1); + p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex], + PointerGetDatum(ybctid)); + pindex++; + } /* get following parameters from slots */ if (slots != NULL && fmstate->target_attrs != NIL) @@ -4298,7 +4483,7 @@ convert_prep_stmt_params(PgFdwModifyState *fmstate, for (i = 0; i < numSlots; i++) { - j = (tupleid != NULL) ? 1 : 0; + j = (tupleid != NULL || ybctid) ? 1 : 0; foreach(lc, fmstate->target_attrs) { int attnum = lfirst_int(lc); @@ -4489,7 +4674,8 @@ build_remote_returning(Index rtindex, Relation rel, List *returningList) if (IsA(var, Var) && var->varno == rtindex && var->varattno <= InvalidAttrNumber && - var->varattno != SelfItemPointerAttributeNumber) + var->varattno != SelfItemPointerAttributeNumber && + var->varattno != YBTupleIdAttributeNumber) continue; /* don't need it */ if (tlist_member((Expr *) var, tlist)) @@ -4720,10 +4906,12 @@ init_returning_filter(PgFdwDirectModifyState *dmstate, if (attrno < 0) { /* - * We don't retrieve system columns other than ctid and oid. + * We don't retrieve system columns other than ctid, ybctid and oid. */ if (attrno == SelfItemPointerAttributeNumber) dmstate->ctidAttno = i; + else if (attrno == YBTupleIdAttributeNumber) + dmstate->ctidAttno = i; else Assert(false); dmstate->hasSystemCols = true; @@ -4815,10 +5003,20 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate, /* ctid */ if (dmstate->ctidAttno) { - ItemPointer ctid = NULL; + if (dmstate->yb_server_type == PG_FDW_SERVER_YUGABYTEDB) + { + /* YugabyteDB */ + bytea *ybctid = DatumGetByteaP(old_values[dmstate->ctidAttno - 1]); - ctid = (ItemPointer) DatumGetPointer(old_values[dmstate->ctidAttno - 1]); - resultTup->t_self = *ctid; + COPY_YBCTID(PointerGetDatum(ybctid), HEAPTUPLE_YBCTID(resultTup)); + } + else + { + ItemPointer ctid = NULL; + + ctid = (ItemPointer) DatumGetPointer(old_values[dmstate->ctidAttno - 1]); + resultTup->t_self = *ctid; + } } /* @@ -5572,6 +5770,13 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, if (fpinfo_o->local_conds || fpinfo_i->local_conds) return false; + /* + * YB: Do not push down joins for global views scans (federated server). + */ + if (fpinfo_o->yb_server_type == PG_FDW_SERVER_FEDERATED_YUGABYTEDB || + fpinfo_i->yb_server_type == PG_FDW_SERVER_FEDERATED_YUGABYTEDB) + return false; + /* * Merge FDW options. We might be tempted to do this after we have deemed * the foreign join to be OK. But we must do this beforehand so that we @@ -5935,7 +6140,15 @@ apply_server_options(PgFdwRelationInfo *fpinfo) (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL); else if (strcmp(def->defname, "async_capable") == 0) fpinfo->async_capable = defGetBoolean(def); + /* YB-specific server options */ + else if (strcmp(def->defname, "server_type") == 0) + { + fpinfo->yb_server_type = yb_get_server_type(defGetString(def)); + Assert(fpinfo->yb_server_type != PG_FDW_SERVER_UNKNOWN); + } } + + fpinfo->yb_min_attr = yb_get_min_attr_from_server_type(fpinfo->yb_server_type); } /* @@ -5993,6 +6206,8 @@ merge_fdw_options(PgFdwRelationInfo *fpinfo, fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate; fpinfo->fetch_size = fpinfo_o->fetch_size; fpinfo->async_capable = fpinfo_o->async_capable; + fpinfo->yb_server_type = fpinfo_o->yb_server_type; + fpinfo->yb_min_attr = fpinfo_o->yb_min_attr; /* Merge the table level options from either side of the join. */ if (fpinfo_i) @@ -6024,6 +6239,13 @@ merge_fdw_options(PgFdwRelationInfo *fpinfo, */ fpinfo->async_capable = fpinfo_o->async_capable || fpinfo_i->async_capable; + + if (fpinfo_i->yb_server_type != fpinfo_o->yb_server_type) + elog(ERROR, "Mismatched server_type of relations: %s(%s), %s(%s)", + fpinfo_o->relation_name, + yb_server_type_to_string(fpinfo_o->yb_server_type), + fpinfo_i->relation_name, + yb_server_type_to_string(fpinfo_i->yb_server_type)); } } @@ -6423,6 +6645,14 @@ postgresGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, !((PgFdwRelationInfo *) input_rel->fdw_private)->pushdown_safe) return; + /* + * YB: Do not push down GROUP BY, ORDER BY, or LIMIT for federated server + * (global views). Only WHERE clauses should be pushed down. + */ + if (((PgFdwRelationInfo *) input_rel->fdw_private)->yb_server_type == + PG_FDW_SERVER_FEDERATED_YUGABYTEDB) + return; + /* Ignore stages we don't support; and skip any duplicate calls. */ if ((stage != UPPERREL_GROUP_AGG && stage != UPPERREL_ORDERED && @@ -7253,6 +7483,7 @@ make_tuple_from_result_row(PGresult *res, MemoryContext oldcontext; ListCell *lc; int j; + bytea *ybctid = NULL; Assert(row < PQntuples(res)); @@ -7334,8 +7565,18 @@ make_tuple_from_result_row(PGresult *res, ctid = (ItemPointer) DatumGetPointer(datum); } } - errpos.cur_attno = 0; + else if (i == YBTupleIdAttributeNumber) + { + /* ybctid */ + if (valstr != NULL) + { + Datum datum; + datum = DirectFunctionCall1(byteain, CStringGetDatum(valstr)); + ybctid = DatumGetByteaP(datum); + } + } + errpos.cur_attno = 0; j++; } @@ -7365,6 +7606,9 @@ make_tuple_from_result_row(PGresult *res, if (ctid) tuple->t_self = tuple->t_data->t_ctid = *ctid; + if (ybctid) + COPY_YBCTID(PointerGetDatum(ybctid), HEAPTUPLE_YBCTID(tuple)); + /* * Stomp on the xmin, xmax, and cmin fields from the tuple created by * heap_form_tuple. heap_form_tuple actually creates the tuple with @@ -7454,6 +7698,8 @@ conversion_error_callback(void *arg) attname = strVal(list_nth(rte->eref->colnames, colno - 1)); else if (colno == SelfItemPointerAttributeNumber) attname = "ctid"; + else if (colno == YBTupleIdAttributeNumber) + attname = "ybctid"; } } else if (rel) @@ -7471,6 +7717,8 @@ conversion_error_callback(void *arg) } else if (errpos->cur_attno == SelfItemPointerAttributeNumber) attname = "ctid"; + else if (errpos->cur_attno == YBTupleIdAttributeNumber) + attname = "ybctid"; } if (relname && is_wholerow) @@ -7629,3 +7877,116 @@ get_batch_size_option(Relation rel) return batch_size; } + +static const char * +yb_server_type_to_string(YbPgFdwServerType server_type) +{ + switch (server_type) + { + case PG_FDW_SERVER_POSTGRES: + return yb_server_types[PG_FDW_SERVER_POSTGRES]; + case PG_FDW_SERVER_YUGABYTEDB: + return yb_server_types[PG_FDW_SERVER_POSTGRES]; + default: + elog(ERROR, "Unsupported server type: %d", server_type); + } + + return NULL; /* keep compiler happy */ +} + +static YbPgFdwServerType +yb_get_server_type(const char *server_type) +{ + if (pg_strncasecmp(server_type, + yb_server_types[PG_FDW_SERVER_POSTGRES], + strlen(yb_server_types[PG_FDW_SERVER_POSTGRES])) == 0) + return PG_FDW_SERVER_POSTGRES; + else if (pg_strncasecmp(server_type, + yb_server_types[PG_FDW_SERVER_YUGABYTEDB], + strlen(yb_server_types[PG_FDW_SERVER_YUGABYTEDB])) == 0) + return PG_FDW_SERVER_YUGABYTEDB; + else if (pg_strncasecmp(server_type, + yb_server_types[PG_FDW_SERVER_FEDERATED_YUGABYTEDB], + strlen(yb_server_types[PG_FDW_SERVER_FEDERATED_YUGABYTEDB])) == 0) + return PG_FDW_SERVER_FEDERATED_YUGABYTEDB; + + return PG_FDW_SERVER_UNKNOWN; +} + +bool +yb_is_valid_server_type(const char *server_type) +{ + return yb_get_server_type(server_type) != PG_FDW_SERVER_UNKNOWN; +} + +static YbPgFdwServerType +yb_get_server_type_from_ftrelid(Oid relid) +{ + ForeignTable *table = GetForeignTable(relid); + ForeignServer *server = GetForeignServer(table->serverid); + ListCell *lc; + + foreach(lc, server->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "server_type") == 0) + return yb_get_server_type(defGetString(def)); + } + + return PG_FDW_SERVER_UNKNOWN; +} + +static AttrNumber +yb_get_min_attr_from_server_type(YbPgFdwServerType server_type) +{ + switch (server_type) + { + case PG_FDW_SERVER_POSTGRES: + case PG_FDW_SERVER_UNKNOWN: + return FirstLowInvalidHeapAttributeNumber; + case PG_FDW_SERVER_YUGABYTEDB: + case PG_FDW_SERVER_FEDERATED_YUGABYTEDB: + return YBFirstLowInvalidAttributeNumber; + default: + elog(ERROR, "Unsupported server type: %d", server_type); + } + + return InvalidAttrNumber; /* keep compiler happy */ +} + +extern AttrNumber +yb_get_min_attr_from_ftrelid(Oid relid) +{ + return yb_get_min_attr_from_server_type(yb_get_server_type_from_ftrelid(relid)); +} + +static const char * +yb_get_tuple_identifier_colname(YbPgFdwServerType server_type) +{ + switch (server_type) + { + case PG_FDW_SERVER_POSTGRES: + case PG_FDW_SERVER_UNKNOWN: + return "ctid"; + case PG_FDW_SERVER_YUGABYTEDB: + case PG_FDW_SERVER_FEDERATED_YUGABYTEDB: + return "ybctid"; + default: + elog(ERROR, "Unsupported server type: %d", server_type); + } + + return NULL; /* keep compiler happy */ +} + +static PGresult * +YbGlobalViewReadExecScan(YbcPgGlobalViewRead yb_gvr, const char *query) +{ + YbcRemotePgExecResult yb_result = YBCPgGlobalViewReadExecScan(yb_gvr, query); + PGresult *res = YBCPgResultFromPB(yb_result.pgresult, yb_result.pgresult_size); + /* + * This makes sure that we continue to query other tservers even if we get error + * from one tserver due to timeouts / tserver down etc. + */ + return res ? res : PQmakeEmptyPGresult(NULL, PGRES_TUPLES_OK); +} diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h index 21f2b20ce8d23..9dacca833797d 100644 --- a/contrib/postgres_fdw/postgres_fdw.h +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -20,6 +20,15 @@ #include "nodes/pathnodes.h" #include "utils/relcache.h" + +typedef enum YbPgFdwServerType +{ + PG_FDW_SERVER_UNKNOWN = 0, + PG_FDW_SERVER_POSTGRES, + PG_FDW_SERVER_YUGABYTEDB, + PG_FDW_SERVER_FEDERATED_YUGABYTEDB +} YbPgFdwServerType; + /* * FDW-specific planner information kept in RelOptInfo.fdw_private for a * postgres_fdw foreign table. For a baserel, this struct is created by @@ -124,6 +133,10 @@ typedef struct PgFdwRelationInfo * representing the relation. */ int relation_index; + + /* The underlying server that is hosting the relation. */ + YbPgFdwServerType yb_server_type; + AttrNumber yb_min_attr; } PgFdwRelationInfo; /* @@ -139,6 +152,10 @@ extern int set_transmission_modes(void); extern void reset_transmission_modes(int nestlevel); extern void process_pending_request(AsyncRequest *areq); +/* YB functions in postgres_fdw.c */ +extern bool yb_is_valid_server_type(const char *server_type); +extern AttrNumber yb_get_min_attr_from_ftrelid(Oid relid); + /* in connection.c */ extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt, PgFdwConnState **state); diff --git a/contrib/postgres_fdw/sql/yb.orig.global_views.sql b/contrib/postgres_fdw/sql/yb.orig.global_views.sql new file mode 100644 index 0000000000000..025d02c62748f --- /dev/null +++ b/contrib/postgres_fdw/sql/yb.orig.global_views.sql @@ -0,0 +1,516 @@ +-- +-- Tests for global views using file_fdw as the underlying data source. +-- Each tserver has a different CSV file placed in its data directory +-- by the test framework, simulating per-node data like pg_stat_statements. +-- + +-- The global views RPC handler connects to template1 on remote tservers, +-- so all objects must be created in template1. +\c template1 + +-- Install required extensions +CREATE EXTENSION file_fdw; +CREATE EXTENSION postgres_fdw; + +-- Create enum type for the status column +CREATE TYPE metric_status AS ENUM ('active', 'warning', 'critical'); + +-- Create file_fdw server for reading local CSV files +CREATE SERVER file_server FOREIGN DATA WRAPPER file_fdw; + +-- Create file_fdw foreign table reading the tserver-local CSV. +-- The relative path resolves against each tserver's data directory, +-- so each tserver reads its own gv_test_data.csv with unique data. +CREATE FOREIGN TABLE local_node_metrics ( + node_id int, + metric_name varchar(30), + metric_value real, + event_count int, + total_bytes bigint, + ratio double precision, + is_healthy boolean, + tag char(5), + status metric_status, + created_at timestamp, + updated_at timestamptz +) SERVER file_server +OPTIONS (format 'csv', filename 'gv_test_data.csv'); + +-- Verify local data is readable (should show only this node's data) +SELECT count(*) > 0 AS has_local_data FROM local_node_metrics; + +-- Create global views server +CREATE SERVER IF NOT EXISTS gv_server FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (server_type 'federatedYugabyteDB'); + +-- Create global view foreign table +CREATE FOREIGN TABLE "gv$node_metrics" ( + node_id int, + metric_name varchar(30), + metric_value real, + event_count int, + total_bytes bigint, + ratio double precision, + is_healthy boolean, + tag char(5), + status metric_status, + created_at timestamp, + updated_at timestamptz +) SERVER gv_server +OPTIONS (schema_name 'public', table_name 'local_node_metrics'); + +-- +-- Basic SELECT: verify data from all 3 nodes (11 rows total) +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM "gv$node_metrics" ORDER BY node_id, metric_name; +SELECT * FROM "gv$node_metrics" ORDER BY node_id, metric_name; + +-- +-- WHERE clause tests: one per type +-- + +-- int4 +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name FROM "gv$node_metrics" +WHERE node_id IN (1, 3) +ORDER BY node_id, metric_name; +SELECT node_id, metric_name FROM "gv$node_metrics" +WHERE node_id IN (1, 3) +ORDER BY node_id, metric_name; + +-- float4 (real) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +WHERE metric_value > 70 +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +WHERE metric_value > 70 +ORDER BY node_id, metric_name; + +-- varchar (variable-length string) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, status FROM "gv$node_metrics" +WHERE status = 'active' +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, status FROM "gv$node_metrics" +WHERE status = 'active' +ORDER BY node_id, metric_name; + +-- boolean +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, is_healthy FROM "gv$node_metrics" +WHERE is_healthy = false +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, is_healthy FROM "gv$node_metrics" +WHERE is_healthy = false +ORDER BY node_id, metric_name; + +-- int8 (bigint) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, total_bytes FROM "gv$node_metrics" +WHERE total_bytes > 8000000000 +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, total_bytes FROM "gv$node_metrics" +WHERE total_bytes > 8000000000 +ORDER BY node_id, metric_name; + +-- float8 (double precision) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, ratio FROM "gv$node_metrics" +WHERE ratio > 0.7 +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, ratio FROM "gv$node_metrics" +WHERE ratio > 0.7 +ORDER BY node_id, metric_name; + +-- char(5) (fixed-length string) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, tag FROM "gv$node_metrics" +WHERE tag LIKE 'CPU%' +ORDER BY node_id; +SELECT node_id, metric_name, tag FROM "gv$node_metrics" +WHERE tag LIKE 'CPU%' +ORDER BY node_id; + +-- enum +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, status FROM "gv$node_metrics" +WHERE status = 'critical'::metric_status +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, status FROM "gv$node_metrics" +WHERE status = 'critical'::metric_status +ORDER BY node_id, metric_name; + +-- timestamp (without TZ) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, created_at FROM "gv$node_metrics" +WHERE created_at >= '2025-02-01'::timestamp +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, created_at FROM "gv$node_metrics" +WHERE created_at >= '2025-02-01'::timestamp +ORDER BY node_id, metric_name; + +-- timestamptz (with TZ) +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, updated_at FROM "gv$node_metrics" +WHERE updated_at < '2025-02-01 00:00:00+00'::timestamptz +ORDER BY node_id, updated_at; +SELECT node_id, metric_name, updated_at FROM "gv$node_metrics" +WHERE updated_at < '2025-02-01 00:00:00+00'::timestamptz +ORDER BY node_id, updated_at; + +-- +-- Aggregate queries +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT COUNT(*) FROM "gv$node_metrics"; +SELECT COUNT(*) FROM "gv$node_metrics"; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT metric_name, + COUNT(*) AS node_count, + SUM(event_count) AS total_events, + SUM(total_bytes) AS total_bytes_sum +FROM "gv$node_metrics" +GROUP BY metric_name +ORDER BY metric_name; +SELECT metric_name, + COUNT(*) AS node_count, + SUM(event_count) AS total_events, + SUM(total_bytes) AS total_bytes_sum +FROM "gv$node_metrics" +GROUP BY metric_name +ORDER BY metric_name; + +-- Aggregate on boolean +EXPLAIN (VERBOSE, COSTS OFF) +SELECT is_healthy, COUNT(*) AS cnt +FROM "gv$node_metrics" +GROUP BY is_healthy +ORDER BY is_healthy; +SELECT is_healthy, COUNT(*) AS cnt +FROM "gv$node_metrics" +GROUP BY is_healthy +ORDER BY is_healthy; + +-- Aggregate on timestamp +EXPLAIN (VERBOSE, COSTS OFF) +SELECT MIN(created_at) AS earliest, MAX(created_at) AS latest +FROM "gv$node_metrics"; +SELECT MIN(created_at) AS earliest, MAX(created_at) AS latest +FROM "gv$node_metrics"; + +-- +-- HAVING clause +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT status, COUNT(*) AS cnt +FROM "gv$node_metrics" +GROUP BY status +HAVING COUNT(*) >= 2 +ORDER BY status; +SELECT status, COUNT(*) AS cnt +FROM "gv$node_metrics" +GROUP BY status +HAVING COUNT(*) >= 2 +ORDER BY status; + +-- +-- DISTINCT +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT DISTINCT metric_name FROM "gv$node_metrics" ORDER BY metric_name; +SELECT DISTINCT metric_name FROM "gv$node_metrics" ORDER BY metric_name; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT DISTINCT status FROM "gv$node_metrics" ORDER BY status; +SELECT DISTINCT status FROM "gv$node_metrics" ORDER BY status; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT DISTINCT is_healthy FROM "gv$node_metrics" ORDER BY is_healthy; +SELECT DISTINCT is_healthy FROM "gv$node_metrics" ORDER BY is_healthy; + +-- +-- LIMIT +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +ORDER BY metric_value DESC LIMIT 3; +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +ORDER BY metric_value DESC LIMIT 3; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, event_count FROM "gv$node_metrics" +ORDER BY event_count ASC LIMIT 5; +SELECT node_id, metric_name, event_count FROM "gv$node_metrics" +ORDER BY event_count ASC LIMIT 5; + +-- +-- Subqueries +-- +-- Single parameter: scalar subquery result bound as $1 +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") +ORDER BY node_id, metric_name; + +-- Two parameters: two scalar subquery results bound as $1 and $2 +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") + AND event_count > (SELECT MIN(event_count) FROM "gv$node_metrics") +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") + AND event_count > (SELECT MIN(event_count) FROM "gv$node_metrics") +ORDER BY node_id, metric_name; + +-- NULL parameter: subquery over empty result set returns NULL +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics" + WHERE node_id = 999) +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics" + WHERE node_id = 999) +ORDER BY node_id, metric_name; + +-- Mixed NULL and non-NULL parameters: $1 is a real value, $2 is NULL +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") + AND event_count > (SELECT MIN(event_count) FROM "gv$node_metrics" + WHERE node_id = 999) +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, metric_value +FROM "gv$node_metrics" +WHERE metric_value > (SELECT AVG(metric_value) FROM "gv$node_metrics") + AND event_count > (SELECT MIN(event_count) FROM "gv$node_metrics" + WHERE node_id = 999) +ORDER BY node_id, metric_name; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name +FROM "gv$node_metrics" +WHERE status IN (SELECT DISTINCT status FROM "gv$node_metrics" WHERE status != 'active') +ORDER BY node_id, metric_name; +SELECT node_id, metric_name +FROM "gv$node_metrics" +WHERE status IN (SELECT DISTINCT status FROM "gv$node_metrics" WHERE status != 'active') +ORDER BY node_id, metric_name; + +-- Empty string subquery parameter +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, tag +FROM "gv$node_metrics" +WHERE metric_name = (SELECT ''::varchar) +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, tag +FROM "gv$node_metrics" +WHERE metric_name = (SELECT ''::varchar) +ORDER BY node_id, metric_name; + +-- NULL subquery parameter +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, tag +FROM "gv$node_metrics" +WHERE tag IS NOT DISTINCT FROM (SELECT NULL::char(5)) +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, tag +FROM "gv$node_metrics" +WHERE tag IS NOT DISTINCT FROM (SELECT NULL::char(5)) +ORDER BY node_id, metric_name; + +-- +-- JOIN with an inline VALUES list +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ni.node_name, gv.metric_name, gv.event_count +FROM "gv$node_metrics" gv +JOIN (VALUES (1, 'alpha'), (2, 'beta'), (3, 'gamma')) + AS ni(node_id, node_name) ON gv.node_id = ni.node_id +ORDER BY ni.node_name, gv.metric_name; +SELECT ni.node_name, gv.metric_name, gv.event_count +FROM "gv$node_metrics" gv +JOIN (VALUES (1, 'alpha'), (2, 'beta'), (3, 'gamma')) + AS ni(node_id, node_name) ON gv.node_id = ni.node_id +ORDER BY ni.node_name, gv.metric_name; + +-- +-- UNION of filtered results +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, status FROM "gv$node_metrics" WHERE status = 'critical' +UNION ALL +SELECT node_id, metric_name, status FROM "gv$node_metrics" WHERE status = 'warning' +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, status FROM "gv$node_metrics" WHERE status = 'critical' +UNION ALL +SELECT node_id, metric_name, status FROM "gv$node_metrics" WHERE status = 'warning' +ORDER BY node_id, metric_name; + +-- +-- ORDER BY with expressions +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + (metric_value * event_count)::int AS weighted_value +FROM "gv$node_metrics" +ORDER BY weighted_value DESC +LIMIT 5; +SELECT node_id, metric_name, + (metric_value * event_count)::int AS weighted_value +FROM "gv$node_metrics" +ORDER BY weighted_value DESC +LIMIT 5; + +-- +-- Type-specific queries +-- + +-- Bigint arithmetic +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + total_bytes, + (total_bytes / 1000000000) AS total_gb +FROM "gv$node_metrics" +ORDER BY total_bytes DESC +LIMIT 3; +SELECT node_id, metric_name, + total_bytes, + (total_bytes / 1000000000) AS total_gb +FROM "gv$node_metrics" +ORDER BY total_bytes DESC +LIMIT 3; + +-- Float8 rounding +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + ratio, + ROUND(ratio::numeric, 2) AS ratio_rounded +FROM "gv$node_metrics" +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, + ratio, + ROUND(ratio::numeric, 2) AS ratio_rounded +FROM "gv$node_metrics" +ORDER BY node_id, metric_name; + +-- Boolean logic +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + is_healthy, + NOT is_healthy AS is_unhealthy +FROM "gv$node_metrics" +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, + is_healthy, + NOT is_healthy AS is_unhealthy +FROM "gv$node_metrics" +ORDER BY node_id, metric_name; + +-- Char padding +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, tag, + LENGTH(tag) AS tag_len +FROM "gv$node_metrics" +ORDER BY tag; +SELECT node_id, tag, + LENGTH(tag) AS tag_len +FROM "gv$node_metrics" +ORDER BY tag; + +-- Timestamp extract +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + EXTRACT(MONTH FROM created_at) AS month, + EXTRACT(YEAR FROM created_at) AS year +FROM "gv$node_metrics" +ORDER BY created_at +LIMIT 3; +SELECT node_id, metric_name, + EXTRACT(MONTH FROM created_at) AS month, + EXTRACT(YEAR FROM created_at) AS year +FROM "gv$node_metrics" +ORDER BY created_at +LIMIT 3; + +-- Timestamptz arithmetic +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, + updated_at, + updated_at + interval '1 hour' AS updated_at_plus_1h +FROM "gv$node_metrics" +ORDER BY updated_at +LIMIT 3; +SELECT node_id, metric_name, + updated_at, + updated_at + interval '1 hour' AS updated_at_plus_1h +FROM "gv$node_metrics" +ORDER BY updated_at +LIMIT 3; + +-- +-- Function pushdown behavior +-- + +-- Stable function (NOW()) in predicate: not pushed down because +-- contain_mutable_functions() rejects non-immutable expressions. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, updated_at FROM "gv$node_metrics" +WHERE updated_at < NOW() +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, updated_at FROM "gv$node_metrics" +WHERE updated_at < NOW() +ORDER BY node_id, metric_name; + +-- Immutable built-in function (ABS) in predicate: pushed down because +-- the function is built-in and immutable. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, event_count FROM "gv$node_metrics" +WHERE ABS(event_count) > 100 +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, event_count FROM "gv$node_metrics" +WHERE ABS(event_count) > 100 +ORDER BY node_id, metric_name; + +-- User-defined function in predicate: not pushed down because +-- postgres_fdw only considers built-in functions shippable. +CREATE FUNCTION is_high_value(val real) RETURNS boolean + LANGUAGE plpgsql IMMUTABLE AS $$ +BEGIN + RETURN val > 70::real; +END; +$$; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +WHERE is_high_value(metric_value) +ORDER BY node_id, metric_name; +SELECT node_id, metric_name, metric_value FROM "gv$node_metrics" +WHERE is_high_value(metric_value) +ORDER BY node_id, metric_name; + +-- +-- Cleanup +-- +DROP FOREIGN TABLE "gv$node_metrics"; +DROP FOREIGN TABLE local_node_metrics; +DROP SERVER gv_server CASCADE; +DROP SERVER file_server CASCADE; +DROP EXTENSION postgres_fdw; +DROP EXTENSION file_fdw; +DROP TYPE metric_status; +DROP FUNCTION is_high_value(real); diff --git a/contrib/postgres_fdw/sql/yb.orig.postgres_fdw.sql b/contrib/postgres_fdw/sql/yb.orig.postgres_fdw.sql new file mode 100644 index 0000000000000..464c312d559bc --- /dev/null +++ b/contrib/postgres_fdw/sql/yb.orig.postgres_fdw.sql @@ -0,0 +1,240 @@ +CREATE EXTENSION postgres_fdw; + +-- +-- Test to validate behavior of 'server_type' option in foreign server. +-- +CREATE SERVER s_yugabytedb FOREIGN DATA WRAPPER postgres_fdw OPTIONS (server_type 'yugabytedb'); +CREATE SERVER s_postgres FOREIGN DATA WRAPPER postgres_fdw OPTIONS (server_type 'postgresql'); +CREATE SERVER s_yugabytedb2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname 'yugabyte', server_type 'yugabytedb'); +CREATE SERVER s_nosrvtype FOREIGN DATA WRAPPER postgres_fdw; +CREATE SERVER s_nosrvtype2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname 'yugabyte', host '127.0.0.1'); +CREATE SERVER s_invalidsrvtype FOREIGN DATA WRAPPER postgres_fdw OPTIONS (server_type 'something_invalid'); +CREATE SERVER s_invalidsrvtype2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (server_type ''); + +SELECT srv.srvname, srv.srvoptions +FROM pg_foreign_server srv JOIN pg_foreign_data_wrapper fdw ON srv.srvfdw = fdw.oid +WHERE fdw.fdwname = 'postgres_fdw'; + +-- Adding a 'server_type' option where none exists. +ALTER SERVER s_nosrvtype2 OPTIONS (ADD server_type 'yugabytedb'); +-- Adding a 'server_type' option where one already exists should be disallowed. +ALTER SERVER s_nosrvtype2 OPTIONS (ADD server_type 'postgresql'); +-- Modifying 'server_type' option where one already exists should be disallowed. +ALTER SERVER s_nosrvtype2 OPTIONS (SET server_type 'postgresql'); +-- Modifying 'server_type' option where one already exists but with an invalid value should be disallowed. +ALTER SERVER s_nosrvtype2 OPTIONS (SET server_type 'something_invalid'); +-- Dropping 'server_type' option should be disallowed. +ALTER SERVER s_nosrvtype2 OPTIONS (DROP server_type); +-- Modifying or dropping 'server_type' option where one does not exist should be disallowed. +ALTER SERVER s_nosrvtype OPTIONS (SET server_type 'postgresql'); +ALTER SERVER s_nosrvtype OPTIONS (DROP server_type); + +SELECT srv.srvname, srv.srvoptions +FROM pg_foreign_server srv JOIN pg_foreign_data_wrapper fdw ON srv.srvfdw = fdw.oid +WHERE fdw.fdwname = 'postgres_fdw'; + +DROP SERVER s_yugabytedb2; +DROP SERVER s_nosrvtype2; + +-- Test to validate that 'server_type' option is not applicable to foreign table. +CREATE FOREIGN TABLE ft_test (v INT) SERVER s_yugabytedb OPTIONS (table_name 't_test', server_type 'yugabytedb'); + +CREATE TABLE t_test (v INT); +INSERT INTO t_test (SELECT i FROM generate_series(1, 10) AS i); + +-- Sanity test to assert that the server has indeed been configured. +DO $d$ + BEGIN + EXECUTE $$ALTER SERVER s_yugabytedb + OPTIONS (dbname '$$||current_database()||$$', + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$ALTER SERVER s_postgres + OPTIONS (dbname '$$||current_database()||$$', + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$ALTER SERVER s_nosrvtype + OPTIONS (dbname '$$||current_database()||$$', + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; + +CREATE USER MAPPING FOR CURRENT_USER SERVER s_yugabytedb; +CREATE USER MAPPING FOR CURRENT_USER SERVER s_postgres; +CREATE USER MAPPING FOR CURRENT_USER SERVER s_nosrvtype; + +CREATE FOREIGN TABLE ft_test1 (v INT) SERVER s_yugabytedb OPTIONS (table_name 't_test'); +SELECT * FROM ft_test1 ORDER BY v; + +CREATE FOREIGN TABLE ft_test2 (v INT) SERVER s_postgres OPTIONS (table_name 't_test'); +SELECT * FROM ft_test2 ORDER BY v; + +CREATE FOREIGN TABLE ft_test3 (v INT) SERVER s_nosrvtype OPTIONS (table_name 't_test'); +SELECT * FROM ft_test3 ORDER BY v; + +-- A join involving tables of different server types must not be pushed down. +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_test1, ft_test2, ft_test3 WHERE ft_test1.v = ft_test2.v AND ft_test1.v = ft_test3.v; +SELECT * FROM ft_test1, ft_test2, ft_test3 WHERE ft_test1.v = ft_test2.v AND ft_test1.v = ft_test3.v; + +-- Modifying table level options should be allowed. +ALTER FOREIGN TABLE ft_test1 OPTIONS (SET table_name 't_othertest'); +ALTER FOREIGN TABLE ft_test2 OPTIONS (DROP table_name, ADD batch_size '1000'); +SELECT * FROM pg_foreign_table; +-- Setting an option should be disallowed if it doesn't already exist. +ALTER FOREIGN TABLE ft_test3 OPTIONS (SET batch_size '1000', SET table_name 't_othertest'); +-- Dropping an option should be disallowed if it doesn't already exist. +ALTER FOREIGN TABLE ft_test2 OPTIONS (DROP table_name); +-- Adding an option should be disallowed if it already exists. +ALTER FOREIGN TABLE ft_test1 OPTIONS (ADD table_name 't_test'); +SELECT * FROM pg_foreign_table; + +-- Restore the original table name. +ALTER FOREIGN TABLE ft_test1 OPTIONS (SET table_name 't_test'); +ALTER FOREIGN TABLE ft_test2 OPTIONS (ADD table_name 't_test', DROP batch_size); +SELECT * FROM pg_foreign_table; + +DROP FOREIGN TABLE ft_test1; +DROP FOREIGN TABLE ft_test2; +DROP FOREIGN TABLE ft_test3; + +-- +-- Test to validate behavior of DML queries on foreign tables +-- +CREATE TABLE t_simple(k INT, v INT, PRIMARY KEY (k ASC)); +CREATE TABLE t_other (k INT, v INT, PRIMARY KEY (k ASC)); + +CREATE FOREIGN TABLE ft_simple (k INT, v INT) SERVER s_yugabytedb OPTIONS (table_name 't_simple'); +CREATE FOREIGN TABLE ft_other (k INT, v INT) SERVER s_yugabytedb OPTIONS (table_name 't_other'); + +-- Test various types of inserts. +-- In all DMLs with a RETURNING clause, the ybctid column must be requested either +-- explicitly or implicitly. +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ft_simple VALUES (1, 1) RETURNING k, v; +INSERT INTO ft_simple VALUES (0, 0); +INSERT INTO ft_simple VALUES (1, 1) RETURNING k, v; +INSERT INTO ft_simple VALUES (2, 2) RETURNING *; +INSERT INTO ft_simple (v, k) VALUES (3, 3) RETURNING tableoid::regclass, *; +INSERT INTO ft_simple VALUES (4, 4) RETURNING ybctid, *; + +SELECT * FROM ft_simple ORDER BY k; + +-- Test "direct" updates and deletes. These are operations that can be pushed down +-- to the foreign server. +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple SET k = k + 10, v = v + 1 WHERE k = 1; +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple SET v = v + 1 WHERE k < 10 RETURNING k, v; +UPDATE ft_simple SET v = v + 1 WHERE k = 0; +UPDATE ft_simple SET k = k + 10, v = v + 1 WHERE k = 1 RETURNING k, v; +UPDATE ft_simple SET v = v + 1 WHERE k < 10 RETURNING *; +UPDATE ft_simple SET k = k + 10 WHERE v = 3 RETURNING ybctid, *; + +SELECT * FROM ft_simple ORDER BY k; + +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple WHERE k = 0; +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple WHERE k < 10 RETURNING k, v; +DELETE FROM ft_simple WHERE k = 0; +DELETE FROM ft_simple WHERE k < 10 RETURNING k, v; +DELETE FROM ft_simple WHERE k = 11 RETURNING *; +DELETE FROM ft_simple WHERE v = 3 RETURNING ybctid, *; + +SELECT * FROM ft_simple ORDER BY k; + +-- Test "two-pass" updates and deletes. These are operations that cannot be pushed down +-- to the foreign server, and require a foreign scan to return the ctid (ybctid) of +-- the row being deleted. +-- Create a "local" function. postgres_fdw will not push down function calls +-- because it cannot determine if the same implementation of the function exists +-- on the foreign server. +CREATE FUNCTION fdw(a INT) RETURNS INT +LANGUAGE PLPGSQL AS $$ +BEGIN + RETURN a; +END; +$$; + +INSERT INTO t_simple (SELECT i, i FROM generate_series(1, 10) AS i); + +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple SET v = fdw(v) WHERE k = 1; +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple SET v = v + 1 WHERE fdw(k) = 1 RETURNING *; +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple WHERE fdw(k) = 1 RETURNING *; +-- This query should be capable of push down. +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple WHERE k = 1 RETURNING fdw(k), fdw(v); + +UPDATE ft_simple SET v = v + 1 WHERE fdw(k) = 1; +UPDATE ft_simple SET k = fdw(k) + 10, v = fdw(v) + 1 WHERE k = 2 RETURNING k, v; +UPDATE ft_simple SET v = fdw(v) + 1 WHERE fdw(k) < 10 RETURNING *; + +SELECT * FROM ft_simple ORDER BY k; + +DELETE FROM ft_simple WHERE fdw(k) = 1; +DELETE FROM ft_simple WHERE fdw(k) = 12 RETURNING k, v; +DELETE FROM ft_simple WHERE fdw(v) < 10 RETURNING *; + +SELECT * FROM ft_simple ORDER BY k; + +INSERT INTO t_simple (SELECT i, i FROM generate_series(1, 5) AS i); +INSERT INTO t_other (SELECT i, i FROM generate_series(1, 5) AS i); + +-- Updates and deletes involving multiple foreign tables cannot be pushed down +-- when the conditions are non-trivial. +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple s SET v = s.v + 1 +FROM ft_other t +WHERE s.k < 2 AND t.v = s.v; +EXPLAIN (VERBOSE, COSTS OFF) UPDATE ft_simple s SET v = s.v + 1 +FROM ft_other t1 JOIN ft_other t2 ON t1.k = t2.k +WHERE s.k = 2 AND t1.v = s.v; +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple s +USING ft_other t +WHERE s.k < 2 AND t.v = s.v; +EXPLAIN (VERBOSE, COSTS OFF) DELETE FROM ft_simple s +USING ft_other t1 JOIN ft_other t2 ON t1.k = t2.k +WHERE s.k = 2 AND t1.v = s.v; + +UPDATE ft_simple s SET v = s.v + 1 +FROM ft_other t +WHERE s.k < 2 AND t.v = s.v +RETURNING *; +UPDATE ft_simple s SET v = s.v + 1 +FROM ft_other t1 JOIN ft_other t2 ON t1.k = t2.k +WHERE s.k = 2 AND t1.v = s.v +RETURNING *; +DELETE FROM ft_simple s +USING ft_other t +WHERE s.k < 2 AND t.v = s.v +RETURNING *; +DELETE FROM ft_simple s +USING ft_other t1 JOIN ft_other t2 ON t1.k = t2.k +WHERE s.k = 2 AND t1.v = s.v +RETURNING *; + +SELECT * FROM ft_simple ORDER BY k; + +CREATE SERVER IF NOT EXISTS gv_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (server_type 'federatedYugabyteDB'); + +CREATE FOREIGN TABLE IF NOT EXISTS "gv$yb_active_session_history" ( + sample_time TIMESTAMPTZ, + root_request_id UUID, + rpc_request_id BIGINT, + wait_event_component TEXT, + wait_event_class TEXT, + wait_event TEXT, + top_level_node_id UUID, + query_id BIGINT, + pid INT, + client_node_ip TEXT, + wait_event_aux TEXT, + sample_weight REAL, + wait_event_type TEXT, + ysql_dbid OID, + wait_event_code BIGINT, + pss_mem_bytes BIGINT, + ysql_userid OID, + plan_id BIGINT +) +SERVER gv_server +OPTIONS (schema_name 'pg_catalog', table_name 'yb_active_session_history'); + +SELECT * FROM gv$yb_active_session_history; diff --git a/contrib/postgres_fdw/sql/yb.port.postgres_fdw.sql b/contrib/postgres_fdw/sql/yb.port.postgres_fdw.sql new file mode 100644 index 0000000000000..789d8e19f984c --- /dev/null +++ b/contrib/postgres_fdw/sql/yb.port.postgres_fdw.sql @@ -0,0 +1,3909 @@ +-- =================================================================== +-- create FDW objects +-- =================================================================== + +CREATE EXTENSION postgres_fdw; + +CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw; +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + -- YB note: In YugabyteDB regress tests, the host and port vary on a run-to-run basis. + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$', + -- YB note: 'server_type' is a YB-specific option which defaults to 'postgresql'. + server_type 'yugabytedb' + )$$; + EXECUTE $$CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + -- YB note: In YugabyteDB regress tests, the host and port vary on a run-to-run basis. + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$', + -- YB note: 'server_type' is a YB-specific option which defaults to 'postgresql'. + server_type 'yugabytedb' + )$$; + EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + -- YB note: In YugabyteDB regress tests, the host and port vary on a run-to-run basis. + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$', + -- YB note: 'server_type' is a YB-specific option which defaults to 'postgresql'. + server_type 'yugabytedb' + )$$; + END; +$d$; + +CREATE USER MAPPING FOR public SERVER testserver1 + OPTIONS (user 'value', password 'value'); +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2; +CREATE USER MAPPING FOR public SERVER loopback3; + +-- =================================================================== +-- create objects used through FDW loopback server +-- =================================================================== +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); +CREATE SCHEMA "S 1"; +CREATE TABLE "S 1"."T 1" ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10), + c8 user_enum, + CONSTRAINT t1_pkey PRIMARY KEY ("C 1") +); +CREATE TABLE "S 1"."T 2" ( + c1 int NOT NULL, + c2 text, + CONSTRAINT t2_pkey PRIMARY KEY (c1) +); +CREATE TABLE "S 1"."T 3" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t3_pkey PRIMARY KEY (c1) +); +CREATE TABLE "S 1"."T 4" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t4_pkey PRIMARY KEY (c1) +); + +-- Disable autovacuum for these tables to avoid unexpected effects of that +ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false'); + +INSERT INTO "S 1"."T 1" + SELECT id, + id % 10, + to_char(id, 'FM00000'), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, + '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, + id % 10, + id % 10, + 'foo'::user_enum + FROM generate_series(1, 1000) id; +INSERT INTO "S 1"."T 2" + SELECT id, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +INSERT INTO "S 1"."T 3" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 3" WHERE c1 % 2 != 0; -- delete for outer join tests +INSERT INTO "S 1"."T 4" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests + +ANALYZE "S 1"."T 1"; +ANALYZE "S 1"."T 2"; +ANALYZE "S 1"."T 3"; +ANALYZE "S 1"."T 4"; + +-- =================================================================== +-- create foreign tables +-- =================================================================== +CREATE FOREIGN TABLE ft1 ( + c0 int, + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft1 DROP COLUMN c0; + +CREATE FOREIGN TABLE ft2 ( + c1 int NOT NULL, + c2 int NOT NULL, + cx int, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft2', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft2 DROP COLUMN cx; + +CREATE FOREIGN TABLE ft4 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3'); + +CREATE FOREIGN TABLE ft5 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); + +CREATE FOREIGN TABLE ft6 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4'); + +CREATE FOREIGN TABLE ft7 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4'); + +-- =================================================================== +-- tests for validator +-- =================================================================== +-- requiressl and some other parameters are omitted because +-- valid values for them depend on configure options +ALTER SERVER testserver1 OPTIONS ( + use_remote_estimate 'false', + updatable 'true', + fdw_startup_cost '123.456', + fdw_tuple_cost '0.123', + service 'value', + connect_timeout 'value', + dbname 'value', + host 'value', + hostaddr 'value', + port 'value', + --client_encoding 'value', + application_name 'value', + --fallback_application_name 'value', + keepalives 'value', + keepalives_idle 'value', + keepalives_interval 'value', + tcp_user_timeout 'value', + -- requiressl 'value', + sslcompression 'value', + sslmode 'value', + sslcert 'value', + sslkey 'value', + sslrootcert 'value', + sslcrl 'value', + --requirepeer 'value', + krbsrvname 'value', + gsslib 'value' + --replication 'value' +); + +-- Error, invalid list syntax +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo; bar'); + +-- OK but gets a warning +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo, bar'); +ALTER SERVER testserver1 OPTIONS (DROP extensions); + +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (DROP user, DROP password); + +-- Attempt to add a valid option that's not allowed in a user mapping +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslmode 'require'); + +-- But we can add valid ones fine +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslpassword 'dummy'); + +-- Ensure valid options we haven't used in a user mapping yet are +-- permitted to check validation. +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslkey 'value', ADD sslcert 'value'); + +ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +\det+ + +-- Test that alteration of server options causes reconnection +-- Remote's errors might be non-English, so hide them to ensure stable results +\set VERBOSITY terse +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work +ALTER SERVER loopback OPTIONS (SET dbname 'no such database'); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail +DO $d$ + BEGIN + EXECUTE $$ALTER SERVER loopback + OPTIONS (SET dbname '$$||current_database()||$$')$$; + END; +$d$; +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again + +-- Test that alteration of user mapping options causes reconnection +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback + OPTIONS (ADD user 'no such user'); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback + OPTIONS (DROP user); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again +\set VERBOSITY default + +-- Now we should be able to run ANALYZE. +-- To exercise multiple code paths, we use local stats on ft1 +-- and remote-estimate mode on ft2. +ANALYZE ft1; +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); + +-- =================================================================== +-- test error case for create publication on foreign table +-- =================================================================== +CREATE PUBLICATION testpub_ftbl FOR TABLE ft1; -- should fail + +-- =================================================================== +-- simple queries +-- =================================================================== +-- single table without alias +EXPLAIN (COSTS OFF) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; +SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; +-- single table with alias - also test that tableoid sort is not pushed to remote side +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; +SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; +-- whole-row reference +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- empty result +SELECT * FROM ft1 WHERE false; +-- with WHERE clause +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; +SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; +-- with FOR UPDATE/SHARE +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; +SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; +SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; +-- aggregate +SELECT COUNT(*) FROM ft1 t1; +-- subquery +SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1; +-- subquery+MAX +SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1; +-- used in CTE +WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1; +-- fixed values +SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1; +-- Test forcing the remote server to produce sorted data for a merge join. +SET enable_hashjoin TO false; +SET enable_nestloop TO false; +SET yb_enable_batchednl TO false; +-- inner join; expressions in the clauses appear in the equivalence class list +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +-- A join between local table and foreign join. ORDER BY clause is added to the +-- foreign join so that the local table can be joined using merge join strategy. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +-- Test similar to above, except that the full join prevents any equivalence +-- classes from being merged. This produces single relation equivalence classes +-- included in join restrictions. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +-- Test similar to above with all full outer joins +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +RESET enable_hashjoin; +RESET enable_nestloop; + +-- Test executing assertion in estimate_path_cost_size() that makes sure that +-- retrieved_rows for foreign rel re-used to cost pre-sorted foreign paths is +-- a sensible value even when the rel has tuples=0 +CREATE TABLE loct_empty (c1 int NOT NULL, c2 text); +CREATE FOREIGN TABLE ft_empty (c1 int NOT NULL, c2 text) + SERVER loopback OPTIONS (table_name 'loct_empty'); +INSERT INTO loct_empty + SELECT id, 'AAA' || to_char(id, 'FM000') FROM generate_series(1, 100) id; +DELETE FROM loct_empty; +ANALYZE ft_empty; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; + +-- test restriction on non-system foreign tables. +SET restrict_nonsystem_relation_kind TO 'foreign-table'; +SELECT * from ft1 where c1 < 1; -- ERROR +INSERT INTO ft1 (c1) VALUES (1); -- ERROR +DELETE FROM ft1 WHERE c1 = 1; -- ERROR +TRUNCATE ft1; -- ERROR +RESET restrict_nonsystem_relation_kind; + +-- =================================================================== +-- WHERE with remotely-executable conditions +-- =================================================================== +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote +-- parameterized remote path for foreign table +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; +SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; + +-- check both safe and unsafe join conditions +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 a, ft2 b + WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); +SELECT * FROM ft2 a, ft2 b +WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); +-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters +SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5)); +SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5)); + +-- user-defined operator/function +CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$ +BEGIN +RETURN abs($1); +END +$$ LANGUAGE plpgsql IMMUTABLE; +CREATE OPERATOR === ( + LEFTARG = int, + RIGHTARG = int, + PROCEDURE = int4eq, + COMMUTATOR = === +); + +-- built-in operators and functions can be shipped for remote execution +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + +-- by default, user-defined ones cannot +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + +-- but let's put them in an extension ... +ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int); +ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +-- ... now they can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + +-- and both ORDER BY and LIMIT can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + +-- Ensure we don't ship FETCH FIRST .. WITH TIES +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c2 FROM ft1 t1 WHERE t1.c1 > 960 ORDER BY t1.c2 FETCH FIRST 2 ROWS WITH TIES; +SELECT t1.c2 FROM ft1 t1 WHERE t1.c1 > 960 ORDER BY t1.c2 FETCH FIRST 2 ROWS WITH TIES; + +-- Test CASE pushdown +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1,c2,c3 FROM ft2 WHERE CASE WHEN c1 > 990 THEN c1 END < 1000 ORDER BY c1; +SELECT c1,c2,c3 FROM ft2 WHERE CASE WHEN c1 > 990 THEN c1 END < 1000 ORDER BY c1; + +-- Nested CASE +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1,c2,c3 FROM ft2 WHERE CASE CASE WHEN c2 > 0 THEN c2 END WHEN 100 THEN 601 WHEN c2 THEN c2 ELSE 0 END > 600 ORDER BY c1; + +SELECT c1,c2,c3 FROM ft2 WHERE CASE CASE WHEN c2 > 0 THEN c2 END WHEN 100 THEN 601 WHEN c2 THEN c2 ELSE 0 END > 600 ORDER BY c1; + +-- CASE arg WHEN +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c1 > (CASE mod(c1, 4) WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END); + +-- CASE cannot be pushed down because of unshippable arg clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c1 > (CASE random()::integer WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END); + +-- these are shippable +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c6 WHEN 'foo' THEN true ELSE c3 < 'bar' END; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c3 WHEN c6 THEN true ELSE c3 < 'bar' END; + +-- but this is not because of collation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c3 COLLATE "C" WHEN c6 THEN true ELSE c3 < 'bar' END; + +-- check schema-qualification of regconfig constant +CREATE TEXT SEARCH CONFIGURATION public.custom_search + (COPY = pg_catalog.english); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; + +-- =================================================================== +-- ORDER BY queries +-- =================================================================== +-- we should not push order by clause with volatile expressions or unsafe +-- collations +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, random(); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C"; + +-- Ensure we don't push ORDER BY expressions which are Consts at the UNION +-- child level to the foreign server. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ( + SELECT 1 AS type,c1 FROM ft1 + UNION ALL + SELECT 2 AS type,c1 FROM ft2 +) a ORDER BY type,c1; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ( + SELECT 1 AS type,c1 FROM ft1 + UNION ALL + SELECT 2 AS type,c1 FROM ft2 +) a ORDER BY type; + +-- =================================================================== +-- JOIN queries +-- =================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +ANALYZE ft5; + +-- join two tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; +-- left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +-- left outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- left outer join + placement of clauses. +-- clauses within the nullable side are not pulled up, but top level clause on +-- non-nullable side is pushed into non-nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; +-- clauses within the nullable side are not pulled up, but the top level clause +-- on nullable side is not pushed down into nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; +-- right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; +-- right outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +-- full outer join with restrictions on the joining relations +-- a. the joining relations are both base relations +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; +-- b. one of the joining relations is a base relation and the other is a join +-- relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +-- c. test deparsing the remote query as nested subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +-- d. test deparsing rowmarked relations as subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +-- full outer join + inner join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; +-- full outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- right outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- YB note: Vanilla PG uses Hash Join while YB uses Batched NL + Merge Join. Use ORDER BY to get same output +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1 OFFSET 10 LIMIT 10; +-- left outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SET enable_memoize TO off; +-- right outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +RESET enable_memoize; +-- left outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + WHERE clause, only matched rows +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +-- full outer join + WHERE clause with shippable extensions set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +-- YB note: The following line is a YB test. This should ideally be in a yb.orig file. +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +ALTER SERVER loopback OPTIONS (DROP extensions); +-- full outer join + WHERE clause with shippable extensions not set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +-- YB note: The following line is a YB test. This should ideally be in a yb.orig file. +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +-- join two tables with FOR UPDATE clause +-- tests whole-row reference for row marks +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +-- join two tables with FOR SHARE clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +-- join in CTE +EXPLAIN (VERBOSE, COSTS OFF) +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +-- ctid with whole-row reference +-- YB note: YugabyteDB uses ybctid as the tuple identifier, not ctid +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.ybctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; -- YB note: see above +-- YB note: The following line is a YB test. This should ideally be in a yb.orig file. +SELECT t1.ybctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- SEMI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +-- ANTI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +-- CROSS JOIN can be pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- different server, not pushed down. No result expected. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS +-- JOIN since c8 in both tables has same value. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- unsafe conditions on one side (c8 has a UDT), not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- join where unsafe to pushdown condition in WHERE clause has a column not +-- in the SELECT clause. In this test unsafe clause needs to have column +-- references from both joining sides so that the clause is not pushed down +-- into one of the joining sides. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- Aggregate after UNION, for testing setrefs +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; +-- join with lateral reference +-- YB note: This is commented out as it ends up doing rescans on a remote relation +-- YB note: which uses an unsupported MOVE BACKWARD ALL IN command. See issue #11422. +/* +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +*/ -- YB +-- join with pseudoconstant quals, not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1 AND CURRENT_USER = SESSION_USER) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + +-- non-Var items in targetlist of the nullable rel of a join preventing +-- push-down in some cases +-- unable to push {ft1, ft2} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + +-- ok to push {ft1, ft2} but not {ft1, ft2, ft4} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + +-- join with nullable side with some columns with null values +UPDATE ft5 SET c3 = null where c1 % 9 = 0; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + +-- multi-way join involving multiple merge joins +-- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)); +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; +SET enable_nestloop TO false; +SET yb_enable_batchednl TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +RESET enable_nestloop; +RESET enable_hashjoin; + +-- test that add_paths_with_pathkeys_for_rel() arranges for the epq_path to +-- return columns needed by the parent ForeignScan node +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.*, COALESCE(ft1.c3 || ft2.c3, 'foobar') FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100)) ss ON (local_tbl.c1 = ss.c1) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl; + +ALTER SERVER loopback OPTIONS (DROP extensions); +ALTER SERVER loopback OPTIONS (ADD fdw_startup_cost '10000.0'); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.* FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100 AND ft1.c1 = postgres_fdw_abs(ft2.c2))) ss ON (local_tbl.c3 = ss.c3) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl; +ALTER SERVER loopback OPTIONS (DROP fdw_startup_cost); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +DROP TABLE local_tbl; + +-- check join pushdown in situations where multiple userids are involved +CREATE ROLE regress_view_owner SUPERUSER; +CREATE USER MAPPING FOR regress_view_owner SERVER loopback; +GRANT SELECT ON ft4 TO regress_view_owner; +GRANT SELECT ON ft5 TO regress_view_owner; + +CREATE VIEW v4 AS SELECT * FROM ft4; +CREATE VIEW v5 AS SELECT * FROM ft5; +ALTER VIEW v5 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, different view owners +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, view owner not current user +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO CURRENT_USER; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO regress_view_owner; + +-- cleanup +DROP OWNED BY regress_view_owner; +DROP ROLE regress_view_owner; + + +-- =================================================================== +-- Aggregate and grouping queries +-- =================================================================== + +-- Simple aggregates +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; + +-- Aggregate over join query +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) +select sum(t1.c1), count(t2.c1) from ft1 t1 inner join ft2 t2 on (t1.c1 = t2.c1) where ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; + +-- GROUP BY clause having expressions +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + +-- Aggregates in subquery are pushed down. +explain (verbose, costs off) +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + +-- Aggregate is still pushed down by taking unshippable expression out +explain (verbose, costs off) +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + +-- Aggregate with unshippable GROUP BY clause are not pushed +explain (verbose, costs off) +select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::int order by 1; + +-- GROUP BY clause in various forms, cardinal, alias and constant expression +explain (verbose, costs off) +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + +-- GROUP BY clause referring to same column multiple times +-- Also, ORDER BY contains an aggregate function +explain (verbose, costs off) +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + +-- Testing HAVING clause shippability +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + +-- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + +-- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1; + +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; +select exists(select 1 from pg_enum), sum(c1) from ft1; + +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + + +-- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates + +-- ORDER BY within aggregate, same column used to order +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + +-- ORDER BY within aggregate, different column used to order also using DESC +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + +-- DISTINCT within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +-- DISTINCT combined with ORDER BY within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +-- FILTER within aggregate +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; + +-- DISTINCT, ORDER BY and FILTER within aggregate +explain (verbose, costs off) +select sum(c1%3), sum(distinct c1%3 order by c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; +select sum(c1%3), sum(distinct c1%3 order by c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; + +-- Outer query is aggregation query +explain (verbose, costs off) +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +-- Inner query is aggregation query +explain (verbose, costs off) +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + +-- Aggregate not pushed down as FILTER condition is not pushable +explain (verbose, costs off) +select sum(c1) filter (where (c1 / c1) * random() <= 1) from ft1 group by c2 order by 1; +explain (verbose, costs off) +select sum(c2) filter (where c2 in (select c2 from ft1 where c2 < 5)) from ft1; + +-- Ordered-sets within aggregate +explain (verbose, costs off) +select c2, rank('10'::varchar) within group (order by c6), percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; +select c2, rank('10'::varchar) within group (order by c6), percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; + +-- Using multiple arguments within aggregates +explain (verbose, costs off) +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; + +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); + +-- Disable hash aggregation for plan stability. +set enable_hashagg to false; + +-- Not pushed down due to user defined aggregate +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + +-- Add function and aggregate into extension +alter extension postgres_fdw add function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw add aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Now aggregate will be pushed. Aggregate will display VARIADIC argument. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; + +-- Remove function and aggregate from extension +alter extension postgres_fdw drop function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw drop aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Not pushed down as we have dropped objects from extension. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + +-- Cleanup +reset enable_hashagg; +drop aggregate least_agg(variadic items anyarray); +drop function least_accum(anyelement, variadic anyarray); + + +-- Testing USING OPERATOR() in ORDER BY within aggregate. +-- For this, we need user defined operators along with operator family and +-- operator class. Create those and then add them in extension. Note that +-- user defined objects are considered unshippable unless they are part of +-- the extension. +create operator public.<^ ( + leftarg = int4, + rightarg = int4, + procedure = int4eq +); + +create operator public.=^ ( + leftarg = int4, + rightarg = int4, + procedure = int4lt +); + +create operator public.>^ ( + leftarg = int4, + rightarg = int4, + procedure = int4gt +); + +create operator family my_op_family using btree; + +create function my_op_cmp(a int, b int) returns int as + $$begin return btint4cmp(a, b); end $$ language plpgsql; + +create operator class my_op_class for type int using btree family my_op_family as + operator 1 public.<^, + operator 3 public.=^, + operator 5 public.>^, + function 1 my_op_cmp(int, int); + +-- This will not be pushed as user defined sort operator is not part of the +-- extension yet. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + +-- This should not be pushed either. +explain (verbose, costs off) +select * from ft2 order by c1 using operator(public.<^); + +-- Update local stats on ft2 +ANALYZE ft2; + +-- Add into extension +alter extension postgres_fdw add operator class my_op_class using btree; +alter extension postgres_fdw add function my_op_cmp(a int, b int); +alter extension postgres_fdw add operator family my_op_family using btree; +alter extension postgres_fdw add operator public.<^(int, int); +alter extension postgres_fdw add operator public.=^(int, int); +alter extension postgres_fdw add operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Now this will be pushed as sort operator is part of the extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + +-- This should be pushed too. +explain (verbose, costs off) +select * from ft2 order by c1 using operator(public.<^); + +-- Remove from extension +alter extension postgres_fdw drop operator class my_op_class using btree; +alter extension postgres_fdw drop function my_op_cmp(a int, b int); +alter extension postgres_fdw drop operator family my_op_family using btree; +alter extension postgres_fdw drop operator public.<^(int, int); +alter extension postgres_fdw drop operator public.=^(int, int); +alter extension postgres_fdw drop operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); + +-- This will not be pushed as sort operator is now removed from the extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + +-- Cleanup +drop operator class my_op_class using btree; +drop function my_op_cmp(a int, b int); +drop operator family my_op_family using btree; +drop operator public.>^(int, int); +drop operator public.=^(int, int); +drop operator public.<^(int, int); + +-- Input relation to aggregate push down hook is not safe to pushdown and thus +-- the aggregate cannot be pushed down to foreign server. +explain (verbose, costs off) +select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2); + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + +-- Aggregate over FULL join needing to deparse the joining relations as +-- subqueries. +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + +-- ORDER BY expression is part of the target list but not pushed down to +-- foreign server. +explain (verbose, costs off) +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + +-- LATERAL join, with parameterization +set enable_hashagg to false; +/* YB note: ERROR: FETCH BACKWARD not supported yet, see issue #11422 +explain (verbose, costs off) +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1; +*/ -- YB +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1; +reset enable_hashagg; + +-- bug #15613: bad plan for foreign table scan with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T 1" AS ref_0, + LATERAL ( + SELECT ref_0."C 1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C 1"; + +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T 1" AS ref_0, + LATERAL ( + SELECT ref_0."C 1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C 1"; + +-- Check with placeHolderVars +explain (verbose, costs off) +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + + +-- Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + +-- WindowAgg +explain (verbose, costs off) +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + + +-- =================================================================== +-- parameterized queries +-- =================================================================== +-- simple join +PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); +EXECUTE st1(1, 1); +EXECUTE st1(101, 101); +-- subquery using stable function (can't be sent to remote) +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st2(10, 20); +EXECUTE st2(10, 20); +EXECUTE st2(101, 121); +-- subquery using immutable function (can be sent to remote) +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st3(10, 20); +EXECUTE st3(10, 20); +EXECUTE st3(20, 30); +-- custom plan should be chosen initially +PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +-- once we try it enough times, should switch to generic plan +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +-- value of $1 should not be sent to remote +PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXECUTE st5('foo', 1); + +-- altering FDW options requires replanning +PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; +PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; +ALTER TABLE "S 1"."T 1" RENAME TO "T 0"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; +EXECUTE st6; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; +ALTER TABLE "S 1"."T 0" RENAME TO "T 1"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1'); + +PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +ALTER SERVER loopback OPTIONS (DROP extensions); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +EXECUTE st8; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +-- cleanup +DEALLOCATE st1; +DEALLOCATE st2; +DEALLOCATE st3; +DEALLOCATE st4; +DEALLOCATE st5; +DEALLOCATE st6; +DEALLOCATE st7; +DEALLOCATE st8; + +-- System columns, except ctid and oid, should not be sent to remote +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1; +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; +-- YB note: YugabyteDB uses ybctid as the tuple identifier, not ctid +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.ybctid = '\x488000000221'; -- YB note: see above +SELECT * FROM ft1 t1 WHERE t1.ybctid = '\x488000000221'; -- YB note: see above +-- YB note: YugabyteDB uses ybctid as the tuple identifier, not ctid +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ybctid, * FROM ft1 t1 LIMIT 1; -- YB note: see above +SELECT ybctid, * FROM ft1 t1 LIMIT 1; -- YB note: see above + +-- =================================================================== +-- used in PL/pgSQL function +-- =================================================================== +CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$ +DECLARE + v_c1 int; +BEGIN + SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1; + PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1; + RETURN v_c1; +END; +$$ LANGUAGE plpgsql; +SELECT f_test(100); +DROP FUNCTION f_test(int); + +-- =================================================================== +-- REINDEX +-- =================================================================== +-- remote table is not created here +CREATE FOREIGN TABLE reindex_foreign (c1 int, c2 int) + SERVER loopback2 OPTIONS (table_name 'reindex_local'); +REINDEX TABLE reindex_foreign; -- error +REINDEX TABLE CONCURRENTLY reindex_foreign; -- error +DROP FOREIGN TABLE reindex_foreign; +-- partitions and foreign tables +CREATE TABLE reind_fdw_parent (c1 int) PARTITION BY RANGE (c1); +CREATE TABLE reind_fdw_0_10 PARTITION OF reind_fdw_parent + FOR VALUES FROM (0) TO (10); +CREATE FOREIGN TABLE reind_fdw_10_20 PARTITION OF reind_fdw_parent + FOR VALUES FROM (10) TO (20) + SERVER loopback OPTIONS (table_name 'reind_local_10_20'); +REINDEX TABLE reind_fdw_parent; -- ok +REINDEX TABLE CONCURRENTLY reind_fdw_parent; -- ok +DROP TABLE reind_fdw_parent; + +-- =================================================================== +-- conversion error +-- =================================================================== +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int; +SELECT * FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8) WHERE x1 = 1; -- ERROR +SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR +ANALYZE ft1; -- ERROR +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; + +-- =================================================================== +-- local type can be different from remote type in some cases, +-- in particular if similarly-named operators do equivalent things +-- =================================================================== +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE text; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1; +SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE 'foo' = c8 LIMIT 1; +SELECT * FROM ft1 WHERE 'foo' = c8 LIMIT 1; +-- we declared c8 to be text locally, but it's still the same type on +-- the remote which will balk if we try to do anything incompatible +-- with that remote type +SELECT * FROM ft1 WHERE c8 LIKE 'foo' LIMIT 1; -- ERROR +SELECT * FROM ft1 WHERE c8::text LIKE 'foo' LIMIT 1; -- ERROR; cast not pushed down +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; + +-- =================================================================== +-- subtransaction +-- + local/remote error doesn't break cursor +-- =================================================================== +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +FETCH c; +SAVEPOINT s; +ERROR OUT; -- ERROR +ROLLBACK TO s; +FETCH c; +SAVEPOINT s; +SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0; -- ERROR +ROLLBACK TO s; +FETCH c; +SELECT * FROM ft1 ORDER BY c1 LIMIT 1; +COMMIT; + +-- =================================================================== +-- test handling of collations +-- =================================================================== +create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique); +create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10)) + server loopback options (table_name 'loct3', use_remote_estimate 'true'); + +-- can be sent to remote +explain (verbose, costs off) select * from ft3 where f1 = 'foo'; +explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo'; +explain (verbose, costs off) select * from ft3 where f2 = 'foo'; +explain (verbose, costs off) select * from ft3 where f3 = 'foo'; +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 and l.f1 = 'foo'; +-- can't be sent to remote +explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo'; +explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C"; +explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo'; +explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C"; +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo'; + +-- =================================================================== +-- test writable foreign table stuff +-- =================================================================== +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) + VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; +INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +EXPLAIN (verbose, costs off) + DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down +DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; + +-- Test UPDATE/DELETE with RETURNING on a three-table join +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; -- can be pushed down +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; -- can be pushed down +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; +DELETE FROM ft2 WHERE ft2.c1 > 1200; + +-- Test UPDATE with a MULTIEXPR sub-select +-- (maybe someday this'll be remotely executable, but not today) +EXPLAIN (verbose, costs off) +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + +UPDATE ft2 AS target SET (c2) = ( + SELECT c2 / 10 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + +-- Test UPDATE involving a join that can be pushed down, +-- but a SET clause that can't be +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; + +-- Test UPDATE/DELETE with WHERE or JOIN/ON conditions containing +-- user-defined operators/functions +ALTER SERVER loopback OPTIONS (DROP extensions); +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; -- can't be pushed down +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; +DELETE FROM ft2 WHERE ft2.c1 > 2000; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +-- Test that trigger on remote table works as expected +CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ +BEGIN + NEW.c3 = NEW.c3 || '_trig_update'; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE + ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG(); + +INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *; +INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *; +UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *; + +-- Test errors thrown on remote side during update +ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0); + +INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT DO NOTHING; -- works +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO NOTHING; -- unsupported +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO UPDATE SET c3 = 'ffg'; -- unsupported +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive + +-- Test savepoint/rollback behavior +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; +begin; +update ft2 set c2 = 42 where c2 = 0; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +savepoint s1; +update ft2 set c2 = 44 where c2 = 4; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +release savepoint s1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +savepoint s2; +update ft2 set c2 = 46 where c2 = 6; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +rollback to savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +release savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +savepoint s3; +update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side +rollback to savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +release savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- none of the above is committed yet remotely +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; +commit; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + +VACUUM ANALYZE "S 1"."T 1"; + +-- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs +-- FIRST behavior here. +-- ORDER BY DESC NULLS LAST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; +-- ORDER BY DESC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +-- ORDER BY ASC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + +-- Test ReScan code path that recreates the cursor even when no parameters +-- change (bug #17889) +CREATE TABLE loct1 (c1 int); +CREATE TABLE loct2 (c1 int, c2 text); +INSERT INTO loct1 VALUES (1001); +INSERT INTO loct1 VALUES (1002); +INSERT INTO loct2 SELECT id, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +INSERT INTO loct2 VALUES (1001, 'foo'); +INSERT INTO loct2 VALUES (1002, 'bar'); +CREATE FOREIGN TABLE remt2 (c1 int, c2 text) SERVER loopback OPTIONS (table_name 'loct2'); +ANALYZE loct1; +ANALYZE remt2; +SET enable_mergejoin TO false; +SET enable_hashjoin TO false; +SET enable_material TO false; +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE remt2 SET c2 = remt2.c2 || remt2.c2 FROM loct1 WHERE loct1.c1 = remt2.c1 RETURNING remt2.*; +UPDATE remt2 SET c2 = remt2.c2 || remt2.c2 FROM loct1 WHERE loct1.c1 = remt2.c1 RETURNING remt2.*; +RESET enable_mergejoin; +RESET enable_hashjoin; +RESET enable_material; +DROP FOREIGN TABLE remt2; +DROP TABLE loct1; +DROP TABLE loct2; + +-- =================================================================== +-- test check constraints +-- =================================================================== + +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; + +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +RESET constraint_exclusion; +-- local check constraint is not actually enforced +INSERT INTO ft1(c1, c2) VALUES(1111, 2); +UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; + +-- =================================================================== +-- test WITH CHECK OPTION constraints +-- =================================================================== + +CREATE FUNCTION row_before_insupd_trigfunc() RETURNS trigger AS $$BEGIN NEW.a := NEW.a + 10; RETURN NEW; END$$ LANGUAGE plpgsql; + +CREATE TABLE base_tbl (a int, b int); +ALTER TABLE base_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON base_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +-- YB note: ERROR: VIEW WITH CHECK OPTION not supported yet, reenable when supported +CREATE VIEW rw_view AS SELECT * FROM foreign_tbl + WHERE a < b WITH CHECK OPTION; +/* YB note: ERROR: VIEW WITH CHECK OPTION not supported yet, reenable when supported +\d+ rw_view + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); +INSERT INTO rw_view VALUES (0, 5); -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM foreign_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; +UPDATE rw_view SET b = b + 5; -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM foreign_tbl; + +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +SELECT * FROM foreign_tbl; +ALTER SERVER loopback OPTIONS (DROP batch_size); +*/ -- YB + +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON base_tbl; +DROP TABLE base_tbl; + +-- test WCO for partitions +CREATE TABLE child_tbl (a int, b int); +ALTER TABLE child_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON child_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'child_tbl'); + +CREATE TABLE parent_tbl (a int, b int) PARTITION BY RANGE(a); +ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES FROM (0) TO (100); +-- Detach and re-attach once, to stress the concurrent detach case. +/* YB note: DETACH PARTITION CONCURRENTLY produces a crash. Uncomment when #28152 is fixed. +ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl CONCURRENTLY; +ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES FROM (0) TO (100); +*/ -- YB + +CREATE VIEW rw_view AS SELECT * FROM parent_tbl + WHERE a < b WITH CHECK OPTION; +/* YB note: ERROR: VIEW WITH CHECK OPTION not supported yet, reenable when supported +\d+ rw_view + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); +INSERT INTO rw_view VALUES (0, 5); -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM foreign_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; +UPDATE rw_view SET b = b + 5; -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM foreign_tbl; + +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +SELECT * FROM foreign_tbl; +ALTER SERVER loopback OPTIONS (DROP batch_size); +*/ -- YB + +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON child_tbl; +DROP TABLE parent_tbl CASCADE; + +DROP FUNCTION row_before_insupd_trigfunc; + +-- =================================================================== +-- test serial columns (ie, sequence-based defaults) +-- =================================================================== +create table loc1 (f1 serial, f2 text); +alter table loc1 set (autovacuum_enabled = 'false'); +create foreign table rem1 (f1 serial, f2 text) + server loopback options(table_name 'loc1'); +select pg_catalog.setval('rem1_f1_seq', 10, false); +insert into loc1(f2) values('hi'); +insert into rem1(f2) values('hi remote'); +insert into loc1(f2) values('bye'); +insert into rem1(f2) values('bye remote'); +select * from loc1; +select * from rem1; + +-- =================================================================== +-- test generated columns +-- =================================================================== +create table gloc1 ( + a int, + b int generated always as (a * 2) stored); +alter table gloc1 set (autovacuum_enabled = 'false'); +create foreign table grem1 ( + a int, + b int generated always as (a * 2) stored) + server loopback options(table_name 'gloc1'); +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); +insert into grem1 (a) values (1), (2); +explain (verbose, costs off) +update grem1 set a = 22 where a = 2; +update grem1 set a = 22 where a = 2; +select * from gloc1; +select * from grem1; +delete from grem1; + +-- test copy from +copy grem1 from stdin; +1 +2 +\. +select * from gloc1; +select * from grem1; +delete from grem1; + +-- test batch insert +alter server loopback options (add batch_size '10'); +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); +insert into grem1 (a) values (1), (2); +select * from gloc1; +select * from grem1; +delete from grem1; +-- batch insert with foreign partitions. +-- This schema uses two partitions, one local and one remote with a modulo +-- to loop across all of them in batches. +create table tab_batch_local (id int, data text); +insert into tab_batch_local select i, 'test'|| i from generate_series(1, 45) i; +create table tab_batch_sharded (id int, data text) partition by hash(id); +create table tab_batch_sharded_p0 partition of tab_batch_sharded + for values with (modulus 2, remainder 0); +create table tab_batch_sharded_p1_remote (id int, data text); +create foreign table tab_batch_sharded_p1 partition of tab_batch_sharded + for values with (modulus 2, remainder 1) + server loopback options (table_name 'tab_batch_sharded_p1_remote'); +insert into tab_batch_sharded select * from tab_batch_local; +select count(*) from tab_batch_sharded; +drop table tab_batch_local; +drop table tab_batch_sharded; +drop table tab_batch_sharded_p1_remote; + +alter server loopback options (drop batch_size); + +-- =================================================================== +-- test local triggers +-- =================================================================== + +-- Trigger functions "borrowed" from triggers regress test. +CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %', + TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; + RETURN NULL; +END;$$; + +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + +CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger +LANGUAGE plpgsql AS $$ + +declare + oldnew text[]; + relid text; + argstr text; +begin + + relid := TG_relid::regclass; + argstr := ''; + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; + + RAISE NOTICE '%(%) % % % ON %', + tg_name, argstr, TG_when, TG_level, TG_OP, relid; + oldnew := '{}'::text[]; + if TG_OP != 'INSERT' then + oldnew := array_append(oldnew, format('OLD: %s', OLD)); + end if; + + if TG_OP != 'DELETE' then + oldnew := array_append(oldnew, format('NEW: %s', NEW)); + end if; + + RAISE NOTICE '%', array_to_string(oldnew, ','); + + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; +end; +$$; + +-- Test basic functionality +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +delete from rem1; +insert into rem1 values(1,'insert'); +update rem1 set f2 = 'update' where f1 = 1; +update rem1 set f2 = f2 || f2; + + +-- cleanup +DROP TRIGGER trig_row_before ON rem1; +DROP TRIGGER trig_row_after ON rem1; +DROP TRIGGER trig_stmt_before ON rem1; +DROP TRIGGER trig_stmt_after ON rem1; + +DELETE from rem1; + +-- Test multiple AFTER ROW triggers on a foreign table +CREATE TRIGGER trig_row_after1 +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after2 +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +insert into rem1 values(1,'insert'); +update rem1 set f2 = 'update' where f1 = 1; +update rem1 set f2 = f2 || f2; +delete from rem1; + +-- cleanup +DROP TRIGGER trig_row_after1 ON rem1; +DROP TRIGGER trig_row_after2 ON rem1; + +-- Test WHEN conditions + +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after_insupd +AFTER INSERT OR UPDATE ON rem1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +-- Insert or update not matching: nothing happens +INSERT INTO rem1 values(1, 'insert'); +UPDATE rem1 set f2 = 'test'; + +-- Insert or update matching: triggers are fired +INSERT INTO rem1 values(2, 'update'); +UPDATE rem1 set f2 = 'update update' where f1 = '2'; + +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON rem1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON rem1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +-- Trigger is fired for f1=2, not for f1=1 +DELETE FROM rem1; + +-- cleanup +DROP TRIGGER trig_row_before_insupd ON rem1; +DROP TRIGGER trig_row_after_insupd ON rem1; +DROP TRIGGER trig_row_before_delete ON rem1; +DROP TRIGGER trig_row_after_delete ON rem1; + + +-- Test various RETURN statements in BEFORE triggers. + +CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$ + BEGIN + NEW.f2 := NEW.f2 || ' triggered !'; + RETURN NEW; + END +$$ language plpgsql; + +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); + +-- The new values should have 'triggered' appended +INSERT INTO rem1 values(1, 'insert'); +SELECT * from loc1; +INSERT INTO rem1 values(2, 'insert') RETURNING f2; +SELECT * from loc1; +UPDATE rem1 set f2 = ''; +SELECT * from loc1; +UPDATE rem1 set f2 = 'skidoo' RETURNING f2; +SELECT * from loc1; + +EXPLAIN (verbose, costs off) +UPDATE rem1 set f1 = 10; -- all columns should be transmitted +UPDATE rem1 set f1 = 10; +SELECT * from loc1; + +DELETE FROM rem1; + +-- Add a second trigger, to check that the changes are propagated correctly +-- from trigger to trigger +CREATE TRIGGER trig_row_before_insupd2 +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); + +INSERT INTO rem1 values(1, 'insert'); +SELECT * from loc1; +INSERT INTO rem1 values(2, 'insert') RETURNING f2; +SELECT * from loc1; +UPDATE rem1 set f2 = ''; +SELECT * from loc1; +UPDATE rem1 set f2 = 'skidoo' RETURNING f2; +SELECT * from loc1; + +DROP TRIGGER trig_row_before_insupd ON rem1; +DROP TRIGGER trig_row_before_insupd2 ON rem1; + +DELETE from rem1; + +INSERT INTO rem1 VALUES (1, 'test'); + +-- Test with a trigger returning NULL +CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$ + BEGIN + RETURN NULL; + END +$$ language plpgsql; + +CREATE TRIGGER trig_null +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_null(); + +-- Nothing should have changed. +INSERT INTO rem1 VALUES (2, 'test2'); + +SELECT * from loc1; + +UPDATE rem1 SET f2 = 'test2'; + +SELECT * from loc1; + +DELETE from rem1; + +SELECT * from loc1; + +DROP TRIGGER trig_null ON rem1; +DELETE from rem1; + +-- Test a combination of local and remote triggers +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); + +INSERT INTO rem1(f2) VALUES ('test'); +UPDATE rem1 SET f2 = 'testo'; + +-- Test returning a system attribute +-- YB note: YugabyteDB uses ybctid as the tuple identifier, not ctid. Since +-- rem1 (loc1) does not have a primary key, add one to make the returned ybctid +-- deterministic. +ALTER TABLE loc1 ADD PRIMARY KEY (f1); +INSERT INTO rem1(f2) VALUES ('test') RETURNING ybctid; +-- YB note: See above. +ALTER TABLE loc1 DROP CONSTRAINT loc1_pkey; + +-- cleanup +DROP TRIGGER trig_row_before ON rem1; +DROP TRIGGER trig_row_after ON rem1; +DROP TRIGGER trig_local_before ON loc1; + + +-- Test direct foreign table modification functionality +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1 WHERE false; -- currently can't be pushed down + +-- Test with statement-level triggers +CREATE TRIGGER trig_stmt_before + BEFORE DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_stmt_before ON rem1; + +CREATE TRIGGER trig_stmt_after + AFTER DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_stmt_after ON rem1; + +-- Test with row-level ON INSERT triggers +CREATE TRIGGER trig_row_before_insert +BEFORE INSERT ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_before_insert ON rem1; + +CREATE TRIGGER trig_row_after_insert +AFTER INSERT ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_after_insert ON rem1; + +-- Test with row-level ON UPDATE triggers +CREATE TRIGGER trig_row_before_update +BEFORE UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_before_update ON rem1; + +CREATE TRIGGER trig_row_after_update +AFTER UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_after_update ON rem1; + +-- Test with row-level ON DELETE triggers +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down +DROP TRIGGER trig_row_before_delete ON rem1; + +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down +DROP TRIGGER trig_row_after_delete ON rem1; + +-- =================================================================== +-- test inheritance features +-- =================================================================== + +CREATE TABLE a (aa TEXT); +CREATE TABLE loct (aa TEXT, bb TEXT); +ALTER TABLE a SET (autovacuum_enabled = 'false'); +ALTER TABLE loct SET (autovacuum_enabled = 'false'); +CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a) + SERVER loopback OPTIONS (table_name 'loct'); + +INSERT INTO a(aa) VALUES('aaa'); +INSERT INTO a(aa) VALUES('aaaa'); +INSERT INTO a(aa) VALUES('aaaaa'); + +INSERT INTO b(aa) VALUES('bbb'); +INSERT INTO b(aa) VALUES('bbbb'); +INSERT INTO b(aa) VALUES('bbbbb'); + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +UPDATE a SET aa = 'zzzzzz' WHERE aa LIKE 'aaaa%'; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +UPDATE b SET aa = 'new'; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +UPDATE a SET aa = 'newtoo'; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +DELETE FROM a; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +DROP TABLE a CASCADE; +DROP TABLE loct; + +-- Check SELECT FOR UPDATE/SHARE with an inherited source table +create table loct1 (f1 int, f2 int, f3 int); +create table loct2 (f1 int, f2 int, f3 int); + +alter table loct1 set (autovacuum_enabled = 'false'); +alter table loct2 set (autovacuum_enabled = 'false'); + +create table foo (f1 int, f2 int); +create foreign table foo2 (f3 int) inherits (foo) + server loopback options (table_name 'loct1'); +create table bar (f1 int, f2 int); +create foreign table bar2 (f3 int) inherits (bar) + server loopback options (table_name 'loct2'); + +alter table foo set (autovacuum_enabled = 'false'); +alter table bar set (autovacuum_enabled = 'false'); + +insert into foo values(1,1); +insert into foo values(3,3); +insert into foo2 values(2,2,2); +insert into foo2 values(4,4,4); +insert into bar values(1,11); +insert into bar values(2,22); +insert into bar values(6,66); +insert into bar2 values(3,33,33); +insert into bar2 values(4,44,44); +insert into bar2 values(7,77,77); + +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo) for update; +select * from bar where f1 in (select f1 from foo) for update; + +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo) for share; +select * from bar where f1 in (select f1 from foo) for share; + +-- Now check SELECT FOR UPDATE/SHARE with an inherited source table, +-- where the parent is itself a foreign table +create table loct4 (f1 int, f2 int, f3 int); +create foreign table foo2child (f3 int) inherits (foo2) + server loopback options (table_name 'loct4'); + +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo2) for share; +select * from bar where f1 in (select f1 from foo2) for share; + +drop foreign table foo2child; + +-- And with a local child relation of the foreign table parent +create table foo2child (f3 int) inherits (foo2); + +-- YB note: The following queries fail to construct row marks. (#28151) +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo2) for share; +select * from bar where f1 in (select f1 from foo2) for share; + +drop table foo2child; + +-- Check UPDATE with inherited target and an inherited source table +explain (verbose, costs off) +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); + +select tableoid::regclass, * from bar order by 1,2; + +-- Check UPDATE with inherited target and an appendrel subquery +explain (verbose, costs off) +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; + +select tableoid::regclass, * from bar order by 1,2; + +-- Test forcing the remote server to produce sorted data for a merge join, +-- but the foreign table is an inheritance child. +truncate table loct1; +truncate table only foo; +\set num_rows_foo 2000 +insert into loct1 select generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2); +insert into foo select generate_series(1, :num_rows_foo, 2), generate_series(1, :num_rows_foo, 2); +SET enable_hashjoin to false; +SET yb_enable_batchednl to false; +SET enable_nestloop to false; +alter foreign table foo2 options (use_remote_estimate 'true'); +create index i_loct1_f1 on loct1(f1); +create index i_foo_f1 on foo(f1); +analyze foo; +analyze loct1; +-- inner join; expressions in the clauses appear in the equivalence class list +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +RESET enable_hashjoin; +RESET yb_enable_batchednl; +RESET enable_nestloop; + +-- Test that WHERE CURRENT OF is not supported +begin; +declare c cursor for select * from bar where f1 = 7; +fetch from c; +-- YB note: WHERE CURRENT OF is currently unsupported. (#737) +update bar set f2 = null where current of c; +rollback; + +explain (verbose, costs off) +delete from foo where f1 < 5 returning *; +delete from foo where f1 < 5 returning *; +explain (verbose, costs off) +update bar set f2 = f2 + 100 returning *; +update bar set f2 = f2 + 100 returning *; + +-- Test that UPDATE/DELETE with inherited target works with row-level triggers +CREATE TRIGGER trig_row_before +BEFORE UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after +AFTER UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +explain (verbose, costs off) +update bar set f2 = f2 + 100; +update bar set f2 = f2 + 100; + +explain (verbose, costs off) +delete from bar where f2 < 400; +delete from bar where f2 < 400; + +-- cleanup +drop table foo cascade; +drop table bar cascade; +drop table loct1; +drop table loct2; + +-- Test pushing down UPDATE/DELETE joins to the remote server +create table parent (a int, b text); +create table loct1 (a int, b text); +create table loct2 (a int, b text); +create foreign table remt1 (a int, b text) + server loopback options (table_name 'loct1'); +create foreign table remt2 (a int, b text) + server loopback options (table_name 'loct2'); +alter foreign table remt1 inherit parent; + +insert into remt1 values (1, 'foo'); +insert into remt1 values (2, 'bar'); +insert into remt2 values (1, 'foo'); +insert into remt2 values (2, 'bar'); + +analyze remt1; +analyze remt2; + +explain (verbose, costs off) +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; +explain (verbose, costs off) +delete from parent using remt2 where parent.a = remt2.a returning parent; +delete from parent using remt2 where parent.a = remt2.a returning parent; + +-- cleanup +drop foreign table remt1; +drop foreign table remt2; +drop table loct1; +drop table loct2; +drop table parent; + +-- =================================================================== +-- test tuple routing for foreign-table partitions +-- =================================================================== + +-- Test insert tuple routing +create table itrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table itrtest attach partition remp1 for values in (1); +alter table itrtest attach partition remp2 for values in (2); + +insert into itrtest values (1, 'foo'); +insert into itrtest values (1, 'bar') returning *; +insert into itrtest values (2, 'baz'); +insert into itrtest values (2, 'qux') returning *; +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; + +select tableoid::regclass, * FROM itrtest; +select tableoid::regclass, * FROM remp1; +select tableoid::regclass, * FROM remp2; + +delete from itrtest; + +-- MERGE ought to fail cleanly +-- YB Note: MERGE command is not supported yet (#19064). +-- YB Note: The following query will fail with a different error. +merge into itrtest using (select 1, 'foo') as source on (true) + when matched then do nothing; + +create unique index loct1_idx on loct1 (a); + +-- DO NOTHING without an inference specification is supported +insert into itrtest values (1, 'foo') on conflict do nothing returning *; +insert into itrtest values (1, 'foo') on conflict do nothing returning *; + +-- But other cases are not supported +insert into itrtest values (1, 'bar') on conflict (a) do nothing; +insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b; + +select tableoid::regclass, * FROM itrtest; + +delete from itrtest; + +drop index loct1_idx; + +-- Test that remote triggers work with insert tuple routing +create function br_insert_trigfunc() returns trigger as $$ +begin + new.b := new.b || ' triggered !'; + return new; +end +$$ language plpgsql; +create trigger loct1_br_insert_trigger before insert on loct1 + for each row execute procedure br_insert_trigfunc(); +create trigger loct2_br_insert_trigger before insert on loct2 + for each row execute procedure br_insert_trigfunc(); + +-- The new values are concatenated with ' triggered !' +insert into itrtest values (1, 'foo') returning *; +insert into itrtest values (2, 'qux') returning *; +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; +with result as (insert into itrtest values (1, 'test1'), (2, 'test2') returning *) select * from result; + +drop trigger loct1_br_insert_trigger on loct1; +drop trigger loct2_br_insert_trigger on loct2; + +drop table itrtest; +drop table loct1; +drop table loct2; + +-- Test update tuple routing +create table utrtest (a int, b text) partition by list (a); +create table loct (a int check (a in (1)), b text); +create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct'); +create table locp (a int check (a in (2)), b text); +alter table utrtest attach partition remp for values in (1); +alter table utrtest attach partition locp for values in (2); + +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +select tableoid::regclass, * FROM utrtest; +select tableoid::regclass, * FROM remp; +select tableoid::regclass, * FROM locp; + +-- It's not allowed to move a row from a partition that is foreign to another +update utrtest set a = 2 where b = 'foo' returning *; + +-- But the reverse is allowed +update utrtest set a = 1 where b = 'qux' returning *; + +select tableoid::regclass, * FROM utrtest; +select tableoid::regclass, * FROM remp; +select tableoid::regclass, * FROM locp; + +-- The executor should not let unexercised FDWs shut down +update utrtest set a = 1 where b = 'foo'; + +-- Test that remote triggers work with update tuple routing +create trigger loct_br_insert_trigger before insert on loct + for each row execute procedure br_insert_trigfunc(); + +delete from utrtest; +insert into utrtest values (2, 'qux'); + +-- Check case where the foreign partition is a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 1 or a = 2 returning *; +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 1 or a = 2 returning *; + +delete from utrtest; +insert into utrtest values (2, 'qux'); + +-- Check case where the foreign partition isn't a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 2 returning *; +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 2 returning *; + +drop trigger loct_br_insert_trigger on loct; + +-- We can move rows to a foreign partition that has been updated already, +-- but can't move rows to a foreign partition that hasn't been updated yet + +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +-- Test the former case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 returning *; +update utrtest set a = 1 returning *; + +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; + +-- Change the definition of utrtest so that the foreign partition get updated +-- after the local partition +delete from utrtest; +alter table utrtest detach partition remp; +drop foreign table remp; +alter table loct drop constraint loct_a_check; +alter table loct add check (a in (3)); +create foreign table remp (a int check (a in (3)), b text) server loopback options (table_name 'loct'); +alter table utrtest attach partition remp for values in (3); +insert into utrtest values (2, 'qux'); +insert into utrtest values (3, 'xyzzy'); + +-- Test the latter case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 returning *; +update utrtest set a = 3 returning *; -- ERROR + +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; -- ERROR + +drop table utrtest; +drop table loct; + +-- Test copy tuple routing +create table ctrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table ctrtest attach partition remp1 for values in (1); +alter table ctrtest attach partition remp2 for values in (2); + +copy ctrtest from stdin; +1 foo +2 qux +\. + +select tableoid::regclass, * FROM ctrtest; +select tableoid::regclass, * FROM remp1; +select tableoid::regclass, * FROM remp2; + +-- Copying into foreign partitions directly should work as well +copy remp1 from stdin; +1 bar +\. + +-- YB note: The following line is a YB test. This should ideally be in a yb.orig file. +SELECT relid::regclass, command, yb_status, type, bytes_processed, bytes_total, + tuples_processed, tuples_excluded FROM pg_stat_progress_copy; + +select tableoid::regclass, * FROM remp1; + +drop table ctrtest; +drop table loct1; +drop table loct2; + +-- =================================================================== +-- test COPY FROM +-- =================================================================== + +create table loc2 (f1 int, f2 text); +alter table loc2 set (autovacuum_enabled = 'false'); +create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2'); + +-- Test basic functionality +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +delete from rem2; + +-- Test check constraints +alter table loc2 add constraint loc2_f1positive check (f1 >= 0); +alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0); + +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +1 foo +2 bar +\. +copy rem2 from stdin; -- ERROR +-1 xyzzy +\. +select * from rem2; + +alter foreign table rem2 drop constraint rem2_f1positive; +alter table loc2 drop constraint loc2_f1positive; + +delete from rem2; + +-- Test local triggers +create trigger trig_stmt_before before insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_stmt_after after insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); + +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before on rem2; +drop trigger trig_row_after on rem2; +drop trigger trig_stmt_before on rem2; +drop trigger trig_stmt_after on rem2; + +delete from rem2; + +create trigger trig_row_before_insert before insert on rem2 + for each row execute procedure trig_row_before_insupdate(); + +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before_insert on rem2; + +delete from rem2; + +create trigger trig_null before insert on rem2 + for each row execute procedure trig_null(); + +-- Nothing happens +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_null on rem2; + +delete from rem2; + +-- Test remote triggers +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before_insert on loc2; + +delete from rem2; + +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); + +-- Nothing happens +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_null on loc2; + +delete from rem2; + +-- Test a combination of local and remote triggers +create trigger rem2_trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger rem2_trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger loc2_trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger rem2_trig_row_before on rem2; +drop trigger rem2_trig_row_after on rem2; +drop trigger loc2_trig_row_before_insert on loc2; + +delete from rem2; + +-- test COPY FROM with foreign table created in the same transaction +create table loc3 (f1 int, f2 text); +begin; +create foreign table rem3 (f1 int, f2 text) + server loopback options(table_name 'loc3'); +copy rem3 from stdin; +1 foo +2 bar +\. +commit; +select * from rem3; +drop foreign table rem3; +drop table loc3; + +-- =================================================================== +-- test for TRUNCATE +-- =================================================================== +CREATE TABLE tru_rtable0 (id int primary key); +CREATE FOREIGN TABLE tru_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_rtable0'); +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(1,10) x); + +CREATE TABLE tru_ptable (id int) PARTITION BY HASH(id); +CREATE TABLE tru_ptable__p0 PARTITION OF tru_ptable + FOR VALUES WITH (MODULUS 2, REMAINDER 0); +CREATE TABLE tru_rtable1 (id int primary key); +CREATE FOREIGN TABLE tru_ftable__p1 PARTITION OF tru_ptable + FOR VALUES WITH (MODULUS 2, REMAINDER 1) + SERVER loopback OPTIONS (table_name 'tru_rtable1'); +INSERT INTO tru_ptable (SELECT x FROM generate_series(11,20) x); + +CREATE TABLE tru_pk_table(id int primary key); +CREATE TABLE tru_fk_table(fkey int references tru_pk_table(id)); +INSERT INTO tru_pk_table (SELECT x FROM generate_series(1,10) x); +INSERT INTO tru_fk_table (SELECT x % 10 + 1 FROM generate_series(5,25) x); +CREATE FOREIGN TABLE tru_pk_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_pk_table'); + +CREATE TABLE tru_rtable_parent (id int); +CREATE TABLE tru_rtable_child (id int); +CREATE FOREIGN TABLE tru_ftable_parent (id int) + SERVER loopback OPTIONS (table_name 'tru_rtable_parent'); +CREATE FOREIGN TABLE tru_ftable_child () INHERITS (tru_ftable_parent) + SERVER loopback OPTIONS (table_name 'tru_rtable_child'); +INSERT INTO tru_rtable_parent (SELECT x FROM generate_series(1,8) x); +INSERT INTO tru_rtable_child (SELECT x FROM generate_series(10, 18) x); + +-- normal truncate +SELECT sum(id) FROM tru_ftable; -- 55 +TRUNCATE tru_ftable; +SELECT count(*) FROM tru_rtable0; -- 0 +SELECT count(*) FROM tru_ftable; -- 0 + +-- 'truncatable' option +ALTER SERVER loopback OPTIONS (ADD truncatable 'false'); +TRUNCATE tru_ftable; -- error +ALTER FOREIGN TABLE tru_ftable OPTIONS (ADD truncatable 'true'); +TRUNCATE tru_ftable; -- accepted +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false'); +TRUNCATE tru_ftable; -- error +ALTER SERVER loopback OPTIONS (DROP truncatable); +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false'); +TRUNCATE tru_ftable; -- error +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'true'); +TRUNCATE tru_ftable; -- accepted + +-- partitioned table with both local and foreign tables as partitions +SELECT sum(id) FROM tru_ptable; -- 155 +-- YB note: Table with foreign partitions fails to truncate. Uncomment once #28150 is fixed. +-- As a result the counts below are all expected to be non-zero. +-- TRUNCATE tru_ptable; +SELECT count(*) FROM tru_ptable; -- 0 +SELECT count(*) FROM tru_ptable__p0; -- 0 +SELECT count(*) FROM tru_ftable__p1; -- 0 +SELECT count(*) FROM tru_rtable1; -- 0 + +-- 'CASCADE' option +SELECT sum(id) FROM tru_pk_ftable; -- 55 +TRUNCATE tru_pk_ftable; -- failed by FK reference +TRUNCATE tru_pk_ftable CASCADE; +SELECT count(*) FROM tru_pk_ftable; -- 0 +SELECT count(*) FROM tru_fk_table; -- also truncated,0 + +-- truncate two tables at a command +INSERT INTO tru_ftable (SELECT x FROM generate_series(1,8) x); +INSERT INTO tru_pk_ftable (SELECT x FROM generate_series(3,10) x); +SELECT count(*) from tru_ftable; -- 8 +SELECT count(*) from tru_pk_ftable; -- 8 +TRUNCATE tru_ftable, tru_pk_ftable CASCADE; +SELECT count(*) from tru_ftable; -- 0 +SELECT count(*) from tru_pk_ftable; -- 0 + +-- truncate with ONLY clause +-- Since ONLY is specified, the table tru_ftable_child that inherits +-- tru_ftable_parent locally is not truncated. +TRUNCATE ONLY tru_ftable_parent; +SELECT sum(id) FROM tru_ftable_parent; -- 126 +TRUNCATE tru_ftable_parent; +SELECT count(*) FROM tru_ftable_parent; -- 0 + +-- in case when remote table has inherited children +CREATE TABLE tru_rtable0_child () INHERITS (tru_rtable0); +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(5,9) x); +INSERT INTO tru_rtable0_child (SELECT x FROM generate_series(10,14) x); +SELECT sum(id) FROM tru_ftable; -- 95 + +-- Both parent and child tables in the foreign server are truncated +-- even though ONLY is specified because ONLY has no effect +-- when truncating a foreign table. +TRUNCATE ONLY tru_ftable; +SELECT count(*) FROM tru_ftable; -- 0 + +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(21,25) x); +INSERT INTO tru_rtable0_child (SELECT x FROM generate_series(26,30) x); +SELECT sum(id) FROM tru_ftable; -- 255 +TRUNCATE tru_ftable; -- truncate both of parent and child +SELECT count(*) FROM tru_ftable; -- 0 + +-- cleanup +DROP FOREIGN TABLE tru_ftable_parent, tru_ftable_child, tru_pk_ftable,tru_ftable__p1,tru_ftable; +DROP TABLE tru_rtable0, tru_rtable1, tru_ptable, tru_ptable__p0, tru_pk_table, tru_fk_table, +tru_rtable_parent,tru_rtable_child, tru_rtable0_child; + +-- =================================================================== +-- test IMPORT FOREIGN SCHEMA +-- =================================================================== + +CREATE SCHEMA import_source; +CREATE TABLE import_source.t1 (c1 int, c2 varchar NOT NULL); +CREATE TABLE import_source.t2 (c1 int default 42, c2 varchar NULL, c3 text collate "POSIX"); +CREATE TYPE typ1 AS (m1 int, m2 varchar); +CREATE TABLE import_source.t3 (c1 timestamptz default now(), c2 typ1); +CREATE TABLE import_source."x 4" (c1 float8, "C 2" text, c3 varchar(42)); +CREATE TABLE import_source."x 5" (c1 float8); +ALTER TABLE import_source."x 5" DROP COLUMN c1; +CREATE TABLE import_source."x 6" (c1 int, c2 int generated always as (c1 * 2) stored); +CREATE TABLE import_source.t4 (c1 int) PARTITION BY RANGE (c1); +CREATE TABLE import_source.t4_part PARTITION OF import_source.t4 + FOR VALUES FROM (1) TO (100); +CREATE TABLE import_source.t4_part2 PARTITION OF import_source.t4 + FOR VALUES FROM (100) TO (200); + +CREATE SCHEMA import_dest1; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest1; +\det+ import_dest1.* +\d import_dest1.* + +-- Options +CREATE SCHEMA import_dest2; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest2 + OPTIONS (import_default 'true'); +\det+ import_dest2.* +\d import_dest2.* +CREATE SCHEMA import_dest3; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest3 + OPTIONS (import_collate 'false', import_generated 'false', import_not_null 'false'); +\det+ import_dest3.* +\d import_dest3.* + +-- Check LIMIT TO and EXCEPT +CREATE SCHEMA import_dest4; +IMPORT FOREIGN SCHEMA import_source LIMIT TO (t1, nonesuch, t4_part) + FROM SERVER loopback INTO import_dest4; +\det+ import_dest4.* +IMPORT FOREIGN SCHEMA import_source EXCEPT (t1, "x 4", nonesuch, t4_part) + FROM SERVER loopback INTO import_dest4; +\det+ import_dest4.* + +-- Assorted error cases +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest4; +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO import_dest4; +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO notthere; +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER nowhere INTO notthere; + +-- Check case of a type present only on the remote server. +-- We can fake this by dropping the type locally in our transaction. +CREATE TYPE "Colors" AS ENUM ('red', 'green', 'blue'); +CREATE TABLE import_source.t5 (c1 int, c2 text collate "C", "Col" "Colors"); + +CREATE SCHEMA import_dest5; +BEGIN; +DROP TYPE "Colors" CASCADE; +IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5) + FROM SERVER loopback INTO import_dest5; -- ERROR + +ROLLBACK; + +BEGIN; + + +CREATE SERVER fetch101 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( fetch_size '101' ); + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=101']; + +ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' ); + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=101']; + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=202']; + +CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' ); + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=30000']; + +ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000'); + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=30000']; + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=60000']; + +ROLLBACK; + +-- =================================================================== +-- test partitionwise joins +-- =================================================================== +SET enable_partitionwise_join=on; + +CREATE TABLE fprt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE fprt1_p1 (LIKE fprt1); +CREATE TABLE fprt1_p2 (LIKE fprt1); +ALTER TABLE fprt1_p1 SET (autovacuum_enabled = 'false'); +ALTER TABLE fprt1_p2 SET (autovacuum_enabled = 'false'); +INSERT INTO fprt1_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 2) i; +INSERT INTO fprt1_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 2) i; +CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (0) TO (250) + SERVER loopback OPTIONS (table_name 'fprt1_p1', use_remote_estimate 'true'); +CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (250) TO (500) + SERVER loopback OPTIONS (TABLE_NAME 'fprt1_p2'); +ANALYZE fprt1; +ANALYZE fprt1_p1; +ANALYZE fprt1_p2; + +CREATE TABLE fprt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE fprt2_p1 (LIKE fprt2); +CREATE TABLE fprt2_p2 (LIKE fprt2); +ALTER TABLE fprt2_p1 SET (autovacuum_enabled = 'false'); +ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false'); +INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i; +INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i; +CREATE FOREIGN TABLE ftprt2_p1 (b int, c varchar, a int) + SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true'); +ALTER TABLE fprt2 ATTACH PARTITION ftprt2_p1 FOR VALUES FROM (0) TO (250); +CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500) + SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true'); +ANALYZE fprt2; +ANALYZE fprt2_p1; +ANALYZE fprt2_p2; + +-- inner join three tables +EXPLAIN (COSTS OFF) +SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; +SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; + +-- left outer join + nullable clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; +SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; + +-- with whole-row reference; partitionwise join does not apply +EXPLAIN (COSTS OFF) +SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; +SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; + +-- join with lateral reference +EXPLAIN (COSTS OFF) +SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2; +SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2; + +-- with PHVs, partitionwise join selected but no join pushdown +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; +SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; + +-- test FOR UPDATE; partitionwise join does not apply +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; + +RESET enable_partitionwise_join; + + +-- =================================================================== +-- test partitionwise aggregates +-- =================================================================== + +CREATE TABLE pagg_tab (a int, b int, c text) PARTITION BY RANGE(a); + +CREATE TABLE pagg_tab_p1 (LIKE pagg_tab); +CREATE TABLE pagg_tab_p2 (LIKE pagg_tab); +CREATE TABLE pagg_tab_p3 (LIKE pagg_tab); + +INSERT INTO pagg_tab_p1 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 10; +INSERT INTO pagg_tab_p2 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 20 and (i % 30) >= 10; +INSERT INTO pagg_tab_p3 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 30 and (i % 30) >= 20; + +-- Create foreign partitions +CREATE FOREIGN TABLE fpagg_tab_p1 PARTITION OF pagg_tab FOR VALUES FROM (0) TO (10) SERVER loopback OPTIONS (table_name 'pagg_tab_p1'); +CREATE FOREIGN TABLE fpagg_tab_p2 PARTITION OF pagg_tab FOR VALUES FROM (10) TO (20) SERVER loopback OPTIONS (table_name 'pagg_tab_p2'); +CREATE FOREIGN TABLE fpagg_tab_p3 PARTITION OF pagg_tab FOR VALUES FROM (20) TO (30) SERVER loopback OPTIONS (table_name 'pagg_tab_p3'); + +ANALYZE pagg_tab; +ANALYZE fpagg_tab_p1; +ANALYZE fpagg_tab_p2; +ANALYZE fpagg_tab_p3; + +-- When GROUP BY clause matches with PARTITION KEY. +-- Plan with partitionwise aggregates is disabled +SET enable_partitionwise_aggregate TO false; +EXPLAIN (COSTS OFF) +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + +-- Plan with partitionwise aggregates is enabled +SET enable_partitionwise_aggregate TO true; +EXPLAIN (COSTS OFF) +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + +-- Check with whole-row reference +-- Should have all the columns in the target list for the given relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a, count(t1) FROM pagg_tab t1 GROUP BY a HAVING avg(b) < 22 ORDER BY 1; +SELECT a, count(t1) FROM pagg_tab t1 GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + +-- When GROUP BY clause does not match with PARTITION KEY. +EXPLAIN (COSTS OFF) +SELECT b, avg(a), max(a), count(*) FROM pagg_tab GROUP BY b HAVING sum(a) < 700 ORDER BY 1; + +-- =================================================================== +-- access rights and superuser +-- =================================================================== + +-- Non-superuser cannot create a FDW without a password in the connstr +CREATE ROLE regress_nosuper NOSUPERUSER; + +GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO regress_nosuper; + +SET ROLE regress_nosuper; + +SHOW is_superuser; + +-- This will be OK, we can create the FDW +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback_nopw FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + -- YB note: In YugabyteDB regress tests, the host and port vary on a run-to-run basis. + host '$$||current_setting('listen_addresses')||$$', + port '$$||current_setting('port')||$$', + -- YB note: 'server_type' is a YB-specific option which defaults to 'postgresql'. + server_type 'yugabytedb' + )$$; + END; +$d$; + +-- But creation of user mappings for non-superusers should fail +CREATE USER MAPPING FOR public SERVER loopback_nopw; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; + +CREATE FOREIGN TABLE pg_temp.ft1_nopw ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback_nopw OPTIONS (schema_name 'public', table_name 'ft1'); + +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- If we add a password to the connstr it'll fail, because we don't allow passwords +-- in connstrs only in user mappings. + +DO $d$ + BEGIN + EXECUTE $$ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')$$; + END; +$d$; + +-- If we add a password for our user mapping instead, we should get a different +-- error because the password wasn't actually *used* when we run with trust auth. +-- +-- This won't work with installcheck, but neither will most of the FDW checks. + +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password 'dummypw'); + +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- Unpriv user cannot make the mapping passwordless +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password_required 'false'); + + +SELECT 1 FROM ft1_nopw LIMIT 1; + +RESET ROLE; + +-- But the superuser can +ALTER USER MAPPING FOR regress_nosuper SERVER loopback_nopw OPTIONS (ADD password_required 'false'); + +SET ROLE regress_nosuper; + +-- Should finally work now +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- unpriv user also cannot set sslcert / sslkey on the user mapping +-- first set password_required so we see the right error messages +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (SET password_required 'true'); +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslcert 'foo.crt'); +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslkey 'foo.key'); + +-- We're done with the role named after a specific user and need to check the +-- changes to the public mapping. +DROP USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; + +-- This will fail again as it'll resolve the user mapping for public, which +-- lacks password_required=false +SELECT 1 FROM ft1_nopw LIMIT 1; + +RESET ROLE; + +-- The user mapping for public is passwordless and lacks the password_required=false +-- mapping option, but will work because the current user is a superuser. +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- cleanup +DROP USER MAPPING FOR public SERVER loopback_nopw; +DROP OWNED BY regress_nosuper; +DROP ROLE regress_nosuper; + +-- Clean-up +RESET enable_partitionwise_aggregate; + +-- Two-phase transactions are not supported. +BEGIN; +SELECT count(*) FROM ft1; +-- error here +PREPARE TRANSACTION 'fdw_tpc'; +ROLLBACK; + +-- =================================================================== +-- reestablish new connection +-- =================================================================== + +-- Change application_name of remote connection to special one +-- so that we can easily terminate the connection later. +ALTER SERVER loopback OPTIONS (application_name 'fdw_retry_check'); + +-- Make sure we have a remote connection. +SELECT 1 FROM ft1 LIMIT 1; + +-- Terminate the remote connection and wait for the termination to complete. +-- (If a cache flush happens, the remote connection might have already been +-- dropped; so code this step in a way that doesn't fail if no connection.) +-- YB: Re-enable this section once GH-29528 is fixed. +-- DO $$ BEGIN +-- PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity +-- WHERE application_name = 'fdw_retry_check'; +-- END $$; + +-- This query should detect the broken connection when starting new remote +-- transaction, reestablish new connection, and then succeed. +BEGIN; +SELECT 1 FROM ft1 LIMIT 1; + +-- If we detect the broken connection when starting a new remote +-- subtransaction, we should fail instead of establishing a new connection. +-- Terminate the remote connection and wait for the termination to complete. +-- YB: Re-enable this section once GH-29528 is fixed. +-- DO $$ BEGIN +-- PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity +-- WHERE application_name = 'fdw_retry_check'; +-- END $$; +SAVEPOINT s; +-- The text of the error might vary across platforms, so only show SQLSTATE. +-- YB: Re-enable this section once GH-29528 is fixed. +-- \set VERBOSITY sqlstate +-- SELECT 1 FROM ft1 LIMIT 1; -- should fail +-- \set VERBOSITY default +COMMIT; + +-- ============================================================================= +-- test connection invalidation cases and postgres_fdw_get_connections function +-- ============================================================================= +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +-- This test case is for closing the connection in pgfdw_xact_callback +BEGIN; +-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact. +SELECT 1 FROM ft1 LIMIT 1; +SELECT 1 FROM ft7 LIMIT 1; +-- List all the existing cached connections. loopback and loopback3 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +-- Connections are not closed at the end of the alter and drop statements. +-- That's because the connections are in midst of this xact, +-- they are just marked as invalid in pgfdw_inval_callback. +ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off'); +DROP SERVER loopback3 CASCADE; +-- List all the existing cached connections. loopback and loopback3 +-- should be output as invalid connections. Also the server name for +-- loopback3 should be NULL because the server was dropped. +SELECT * FROM postgres_fdw_get_connections() ORDER BY 1; +-- The invalid connections get closed in pgfdw_xact_callback during commit. +COMMIT; +-- All cached connections were closed while committing above xact, so no +-- records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + +-- ======================================================================= +-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions +-- ======================================================================= +BEGIN; +-- Ensure to cache loopback connection. +SELECT 1 FROM ft1 LIMIT 1; +-- Ensure to cache loopback2 connection. +SELECT 1 FROM ft6 LIMIT 1; +-- List all the existing cached connections. loopback and loopback2 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +-- Issue a warning and return false as loopback connection is still in use and +-- can not be closed. +SELECT postgres_fdw_disconnect('loopback'); +-- List all the existing cached connections. loopback and loopback2 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +-- Return false as connections are still in use, warnings are issued. +-- But disable warnings temporarily because the order of them is not stable. +SET client_min_messages = 'ERROR'; +SELECT postgres_fdw_disconnect_all(); +RESET client_min_messages; +COMMIT; +-- Ensure that loopback2 connection is closed. +SELECT 1 FROM postgres_fdw_disconnect('loopback2'); +SELECT server_name FROM postgres_fdw_get_connections() WHERE server_name = 'loopback2'; +-- Return false as loopback2 connection is closed already. +SELECT postgres_fdw_disconnect('loopback2'); +-- Return an error as there is no foreign server with given name. +SELECT postgres_fdw_disconnect('unknownserver'); +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + +-- ============================================================================= +-- test case for having multiple cached connections for a foreign server +-- ============================================================================= +CREATE ROLE regress_multi_conn_user1 SUPERUSER; +CREATE ROLE regress_multi_conn_user2 SUPERUSER; +CREATE USER MAPPING FOR regress_multi_conn_user1 SERVER loopback; +CREATE USER MAPPING FOR regress_multi_conn_user2 SERVER loopback; + +BEGIN; +-- Will cache loopback connection with user mapping for regress_multi_conn_user1 +SET ROLE regress_multi_conn_user1; +SELECT 1 FROM ft1 LIMIT 1; +RESET ROLE; + +-- Will cache loopback connection with user mapping for regress_multi_conn_user2 +SET ROLE regress_multi_conn_user2; +SELECT 1 FROM ft1 LIMIT 1; +RESET ROLE; + +-- Should output two connections for loopback server +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +COMMIT; +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + +-- Clean up +DROP USER MAPPING FOR regress_multi_conn_user1 SERVER loopback; +DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback; +DROP ROLE regress_multi_conn_user1; +DROP ROLE regress_multi_conn_user2; + +-- =================================================================== +-- Test foreign server level option keep_connections +-- =================================================================== +-- By default, the connections associated with foreign server are cached i.e. +-- keep_connections option is on. Set it to off. +ALTER SERVER loopback OPTIONS (keep_connections 'off'); +-- connection to loopback server is closed at the end of xact +-- as keep_connections was set to off. +SELECT 1 FROM ft1 LIMIT 1; +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +ALTER SERVER loopback OPTIONS (SET keep_connections 'on'); + +-- =================================================================== +-- batch insert +-- =================================================================== + +BEGIN; + +CREATE SERVER batch10 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( batch_size '10' ); + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; + +ALTER SERVER batch10 OPTIONS( SET batch_size '20' ); + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=20']; + +CREATE FOREIGN TABLE table30 ( x int ) SERVER batch10 OPTIONS ( batch_size '30' ); + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; + +ALTER FOREIGN TABLE table30 OPTIONS ( SET batch_size '40'); + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=40']; + +ROLLBACK; + +CREATE TABLE batch_table ( x int ); + +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table', batch_size '10' ); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; +INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; +INSERT INTO ftable SELECT * FROM generate_series(11, 31) i; +INSERT INTO ftable VALUES (32); +INSERT INTO ftable VALUES (33), (34); +SELECT COUNT(*) FROM ftable; +TRUNCATE batch_table; +DROP FOREIGN TABLE ftable; + +-- Disable batch insert +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table', batch_size '1' ); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (1), (2); +INSERT INTO ftable VALUES (1), (2); +SELECT COUNT(*) FROM ftable; + +-- Disable batch inserting into foreign tables with BEFORE ROW INSERT triggers +-- even if the batch_size option is enabled. +ALTER FOREIGN TABLE ftable OPTIONS ( SET batch_size '10' ); +CREATE TRIGGER trig_row_before BEFORE INSERT ON ftable +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (3), (4); +INSERT INTO ftable VALUES (3), (4); +SELECT COUNT(*) FROM ftable; + +-- Clean up +DROP TRIGGER trig_row_before ON ftable; +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; + +-- Use partitioning +CREATE TABLE batch_table ( x int ) PARTITION BY HASH (x); + +CREATE TABLE batch_table_p0 (LIKE batch_table); +CREATE FOREIGN TABLE batch_table_p0f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 0) + SERVER loopback + OPTIONS (table_name 'batch_table_p0', batch_size '10'); + +CREATE TABLE batch_table_p1 (LIKE batch_table); +CREATE FOREIGN TABLE batch_table_p1f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 1) + SERVER loopback + OPTIONS (table_name 'batch_table_p1', batch_size '1'); + +CREATE TABLE batch_table_p2 + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 2); + +INSERT INTO batch_table SELECT * FROM generate_series(1, 66) i; +SELECT COUNT(*) FROM batch_table; + +-- Check that enabling batched inserts doesn't interfere with cross-partition +-- updates +CREATE TABLE batch_cp_upd_test (a int) PARTITION BY LIST (a); +CREATE TABLE batch_cp_upd_test1 (LIKE batch_cp_upd_test); +CREATE FOREIGN TABLE batch_cp_upd_test1_f + PARTITION OF batch_cp_upd_test + FOR VALUES IN (1) + SERVER loopback + OPTIONS (table_name 'batch_cp_upd_test1', batch_size '10'); +CREATE TABLE batch_cp_up_test1 PARTITION OF batch_cp_upd_test + FOR VALUES IN (2); +INSERT INTO batch_cp_upd_test VALUES (1), (2); + +-- The following moves a row from the local partition to the foreign one +UPDATE batch_cp_upd_test t SET a = 1 FROM (VALUES (1), (2)) s(a) WHERE t.a = s.a; +SELECT tableoid::regclass, * FROM batch_cp_upd_test; + +-- Clean up +DROP TABLE batch_table, batch_cp_upd_test, batch_table_p0, batch_table_p1 CASCADE; + +-- Use partitioning +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); + +CREATE TABLE batch_table ( x int, field1 text, field2 text) PARTITION BY HASH (x); + +CREATE TABLE batch_table_p0 (LIKE batch_table); +ALTER TABLE batch_table_p0 ADD CONSTRAINT p0_pkey PRIMARY KEY (x); +CREATE FOREIGN TABLE batch_table_p0f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 2, REMAINDER 0) + SERVER loopback + OPTIONS (table_name 'batch_table_p0'); + +CREATE TABLE batch_table_p1 (LIKE batch_table); +ALTER TABLE batch_table_p1 ADD CONSTRAINT p1_pkey PRIMARY KEY (x); +CREATE FOREIGN TABLE batch_table_p1f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 2, REMAINDER 1) + SERVER loopback + OPTIONS (table_name 'batch_table_p1'); + +INSERT INTO batch_table SELECT i, 'test'||i, 'test'|| i FROM generate_series(1, 50) i; +SELECT COUNT(*) FROM batch_table; +SELECT * FROM batch_table ORDER BY x; + +-- Clean up +DROP TABLE batch_table; +DROP TABLE batch_table_p0; +DROP TABLE batch_table_p1; + +ALTER SERVER loopback OPTIONS (DROP batch_size); + +-- Test that pending inserts are handled properly when needed +CREATE TABLE batch_table (a text, b int); +CREATE FOREIGN TABLE ftable (a text, b int) + SERVER loopback + OPTIONS (table_name 'batch_table', batch_size '2'); +CREATE TABLE ltable (a text, b int); +CREATE FUNCTION ftable_rowcount_trigf() RETURNS trigger LANGUAGE plpgsql AS +$$ +begin + raise notice '%: there are % rows in ftable', + TG_NAME, (SELECT count(*) FROM ftable); + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; +end; +$$; +CREATE TRIGGER ftable_rowcount_trigger +BEFORE INSERT OR UPDATE OR DELETE ON ltable +FOR EACH ROW EXECUTE PROCEDURE ftable_rowcount_trigf(); + +WITH t AS ( + INSERT INTO ltable VALUES ('AAA', 42), ('BBB', 42) RETURNING * +) +INSERT INTO ftable SELECT * FROM t; + +SELECT * FROM ltable; +SELECT * FROM ftable; +DELETE FROM ftable; + +WITH t AS ( + UPDATE ltable SET b = b + 100 RETURNING * +) +INSERT INTO ftable SELECT * FROM t; + +SELECT * FROM ltable; +SELECT * FROM ftable; +DELETE FROM ftable; + +WITH t AS ( + DELETE FROM ltable RETURNING * +) +INSERT INTO ftable SELECT * FROM t; + +SELECT * FROM ltable; +SELECT * FROM ftable; +DELETE FROM ftable; + +-- Clean up +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; +DROP TRIGGER ftable_rowcount_trigger ON ltable; +DROP TABLE ltable; + +CREATE TABLE parent (a text, b int) PARTITION BY LIST (a); +CREATE TABLE batch_table (a text, b int); +CREATE FOREIGN TABLE ftable + PARTITION OF parent + FOR VALUES IN ('AAA') + SERVER loopback + OPTIONS (table_name 'batch_table', batch_size '2'); +CREATE TABLE ltable + PARTITION OF parent + FOR VALUES IN ('BBB'); +CREATE TRIGGER ftable_rowcount_trigger +BEFORE INSERT ON ltable +FOR EACH ROW EXECUTE PROCEDURE ftable_rowcount_trigf(); + +INSERT INTO parent VALUES ('AAA', 42), ('BBB', 42), ('AAA', 42), ('BBB', 42); + +SELECT tableoid::regclass, * FROM parent; + +-- Clean up +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; +DROP TRIGGER ftable_rowcount_trigger ON ltable; +DROP TABLE ltable; +DROP TABLE parent; +DROP FUNCTION ftable_rowcount_trigf; + +-- =================================================================== +-- test asynchronous execution +-- =================================================================== + +ALTER SERVER loopback OPTIONS (DROP extensions); +ALTER SERVER loopback OPTIONS (ADD async_capable 'true'); +ALTER SERVER loopback2 OPTIONS (ADD async_capable 'true'); + +CREATE TABLE async_pt (a int, b int, c text) PARTITION BY RANGE (a); +CREATE TABLE base_tbl1 (a int, b int, c text); +CREATE TABLE base_tbl2 (a int, b int, c text); +CREATE FOREIGN TABLE async_p1 PARTITION OF async_pt FOR VALUES FROM (1000) TO (2000) + SERVER loopback OPTIONS (table_name 'base_tbl1'); +CREATE FOREIGN TABLE async_p2 PARTITION OF async_pt FOR VALUES FROM (2000) TO (3000) + SERVER loopback2 OPTIONS (table_name 'base_tbl2'); +INSERT INTO async_p1 SELECT 1000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +INSERT INTO async_p2 SELECT 2000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; + +-- simple queries +CREATE TABLE result_tbl (a int, b int, c text); + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b % 100 = 0; +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b % 100 = 0; + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT a, b, 'AAA' || c FROM async_pt WHERE b === 505; +INSERT INTO result_tbl SELECT a, b, 'AAA' || c FROM async_pt WHERE b === 505; + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +-- Test error handling, if accessing one of the foreign partitions errors out +CREATE FOREIGN TABLE async_p_broken PARTITION OF async_pt FOR VALUES FROM (10000) TO (10001) + SERVER loopback OPTIONS (table_name 'non_existent_table'); +SELECT * FROM async_pt; +DROP FOREIGN TABLE async_p_broken; + +-- Check case where multiple partitions use the same connection +CREATE TABLE base_tbl3 (a int, b int, c text); +CREATE FOREIGN TABLE async_p3 PARTITION OF async_pt FOR VALUES FROM (3000) TO (4000) + SERVER loopback2 OPTIONS (table_name 'base_tbl3'); +INSERT INTO async_p3 SELECT 3000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +DROP FOREIGN TABLE async_p3; +DROP TABLE base_tbl3; + +-- Check case where the partitioned table has local/remote partitions +CREATE TABLE async_p3 PARTITION OF async_pt FOR VALUES FROM (3000) TO (4000); +INSERT INTO async_p3 SELECT 3000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +-- partitionwise joins +SET enable_partitionwise_join TO true; + +CREATE TABLE join_tbl (a1 int, b1 int, c1 text, a2 int, b2 int, c2 text); + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + +SELECT * FROM join_tbl ORDER BY a1; +DELETE FROM join_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + +SELECT * FROM join_tbl ORDER BY a1; +DELETE FROM join_tbl; + +RESET enable_partitionwise_join; + +-- Test rescan of an async Append node with do_exec_prune=false +SET enable_hashjoin TO false; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT * FROM async_p1 t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +INSERT INTO join_tbl SELECT * FROM async_p1 t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + +SELECT * FROM join_tbl ORDER BY a1; +DELETE FROM join_tbl; + +RESET enable_hashjoin; + +-- Test interaction of async execution with plan-time partition pruning +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE a < 3000; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE a < 2000; + +-- Test interaction of async execution with run-time partition pruning +SET plan_cache_mode TO force_generic_plan; + +PREPARE async_pt_query (int, int) AS + INSERT INTO result_tbl SELECT * FROM async_pt WHERE a < $1 AND b === $2; + +EXPLAIN (VERBOSE, COSTS OFF) +EXECUTE async_pt_query (3000, 505); +EXECUTE async_pt_query (3000, 505); + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +EXECUTE async_pt_query (2000, 505); +EXECUTE async_pt_query (2000, 505); + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +RESET plan_cache_mode; + +CREATE TABLE local_tbl(a int, b int, c text); +INSERT INTO local_tbl VALUES (1505, 505, 'foo'), (2505, 505, 'bar'); +ANALYZE local_tbl; + +CREATE INDEX base_tbl1_idx ON base_tbl1 (a); +CREATE INDEX base_tbl2_idx ON base_tbl2 (a); +CREATE INDEX async_p3_idx ON async_p3 (a); +ANALYZE base_tbl1; +ANALYZE base_tbl2; +ANALYZE async_p3; + +ALTER FOREIGN TABLE async_p1 OPTIONS (use_remote_estimate 'true'); +ALTER FOREIGN TABLE async_p2 OPTIONS (use_remote_estimate 'true'); + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; + +ALTER FOREIGN TABLE async_p1 OPTIONS (DROP use_remote_estimate); +ALTER FOREIGN TABLE async_p2 OPTIONS (DROP use_remote_estimate); + +DROP TABLE local_tbl; +DROP INDEX base_tbl1_idx; +DROP INDEX base_tbl2_idx; +DROP INDEX async_p3_idx; + +-- UNION queries +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +-- Disable async execution if we use gating Result nodes for pseudoconstant +-- quals +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE CURRENT_USER = SESSION_USER; + +EXPLAIN (VERBOSE, COSTS OFF) +(SELECT * FROM async_p1 WHERE CURRENT_USER = SESSION_USER) +UNION ALL +(SELECT * FROM async_p2 WHERE CURRENT_USER = SESSION_USER); + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ((SELECT * FROM async_p1 WHERE b < 10) UNION ALL (SELECT * FROM async_p2 WHERE b < 10)) s WHERE CURRENT_USER = SESSION_USER; + +-- Test that pending requests are processed properly +SET enable_mergejoin TO false; +SET enable_hashjoin TO false; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt t1, async_p2 t2 WHERE t1.a = t2.a AND t1.b === 505; +SELECT * FROM async_pt t1, async_p2 t2 WHERE t1.a = t2.a AND t1.b === 505; + +CREATE TABLE local_tbl (a int, b int, c text); +INSERT INTO local_tbl VALUES (1505, 505, 'foo'); +ANALYZE local_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; + +-- Check with foreign modify +CREATE TABLE base_tbl3 (a int, b int, c text); +CREATE FOREIGN TABLE remote_tbl (a int, b int, c text) + SERVER loopback OPTIONS (table_name 'base_tbl3'); +INSERT INTO remote_tbl VALUES (2505, 505, 'bar'); + +CREATE TABLE base_tbl4 (a int, b int, c text); +CREATE FOREIGN TABLE insert_tbl (a int, b int, c text) + SERVER loopback OPTIONS (table_name 'base_tbl4'); + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO insert_tbl (SELECT * FROM local_tbl UNION ALL SELECT * FROM remote_tbl); +INSERT INTO insert_tbl (SELECT * FROM local_tbl UNION ALL SELECT * FROM remote_tbl); + +SELECT * FROM insert_tbl ORDER BY a; + +-- Check with direct modify +EXPLAIN (VERBOSE, COSTS OFF) +WITH t AS (UPDATE remote_tbl SET c = c || c RETURNING *) +INSERT INTO join_tbl SELECT * FROM async_pt LEFT JOIN t ON (async_pt.a = t.a AND async_pt.b = t.b) WHERE async_pt.b === 505; +WITH t AS (UPDATE remote_tbl SET c = c || c RETURNING *) +INSERT INTO join_tbl SELECT * FROM async_pt LEFT JOIN t ON (async_pt.a = t.a AND async_pt.b = t.b) WHERE async_pt.b === 505; + +SELECT * FROM join_tbl ORDER BY a1; +DELETE FROM join_tbl; + +DROP TABLE local_tbl; +DROP FOREIGN TABLE remote_tbl; +DROP FOREIGN TABLE insert_tbl; +DROP TABLE base_tbl3; +DROP TABLE base_tbl4; + +RESET enable_mergejoin; +RESET enable_hashjoin; + +-- Test that UPDATE/DELETE with inherited target works with async_capable enabled +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE async_pt SET c = c || c WHERE b = 0 RETURNING *; +UPDATE async_pt SET c = c || c WHERE b = 0 RETURNING *; +EXPLAIN (VERBOSE, COSTS OFF) +DELETE FROM async_pt WHERE b = 0 RETURNING *; +DELETE FROM async_pt WHERE b = 0 RETURNING *; + +-- Check EXPLAIN ANALYZE for a query that scans empty partitions asynchronously +DELETE FROM async_p1; +DELETE FROM async_p2; +DELETE FROM async_p3; + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM async_pt; + +-- Clean up +DROP TABLE async_pt; +DROP TABLE base_tbl1; +DROP TABLE base_tbl2; +DROP TABLE result_tbl; +DROP TABLE join_tbl; + +-- Test that an asynchronous fetch is processed before restarting the scan in +-- ReScanForeignScan +CREATE TABLE base_tbl (a int, b int); +INSERT INTO base_tbl VALUES (1, 11), (2, 22), (3, 33); +CREATE FOREIGN TABLE foreign_tbl (b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +CREATE FOREIGN TABLE foreign_tbl2 () INHERITS (foreign_tbl) + SERVER loopback OPTIONS (table_name 'base_tbl'); + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); + +-- Clean up +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TABLE base_tbl; + +ALTER SERVER loopback OPTIONS (DROP async_capable); +ALTER SERVER loopback2 OPTIONS (DROP async_capable); + +-- =================================================================== +-- test invalid server, foreign table and foreign data wrapper options +-- =================================================================== +-- Invalid fdw_startup_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_startup_cost '100$%$#$#'); +-- Invalid fdw_tuple_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_tuple_cost '100$%$#$#'); +-- Invalid fetch_size option +CREATE FOREIGN TABLE inv_fsz (c1 int ) + SERVER loopback OPTIONS (fetch_size '100$%$#$#'); +-- Invalid batch_size option +CREATE FOREIGN TABLE inv_bsz (c1 int ) + SERVER loopback OPTIONS (batch_size '100$%$#$#'); + +-- No option is allowed to be specified at foreign data wrapper level +ALTER FOREIGN DATA WRAPPER postgres_fdw OPTIONS (nonexistent 'fdw'); + +-- =================================================================== +-- test postgres_fdw.application_name GUC +-- =================================================================== +-- To avoid race conditions in checking the remote session's application_name, +-- use this view to make the remote session itself read its application_name. +CREATE VIEW my_application_name AS + SELECT application_name FROM pg_stat_activity WHERE pid = pg_backend_pid(); + +CREATE FOREIGN TABLE remote_application_name (application_name text) + SERVER loopback2 + OPTIONS (schema_name 'public', table_name 'my_application_name'); + +SELECT count(*) FROM remote_application_name; + +-- Specify escape sequences in application_name option of a server +-- object so as to test that they are replaced with status information +-- expectedly. Note that we are also relying on ALTER SERVER to force +-- the remote session to be restarted with its new application name. +-- +-- Since pg_stat_activity.application_name may be truncated to less than +-- NAMEDATALEN characters, note that substring() needs to be used +-- at the condition of test query to make sure that the string consisting +-- of database name and process ID is also less than that. +ALTER SERVER loopback2 OPTIONS (application_name 'fdw_%d%p'); +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_database() || pg_backend_pid() for + current_setting('max_identifier_length')::int); + +-- postgres_fdw.application_name overrides application_name option +-- of a server object if both settings are present. +ALTER SERVER loopback2 OPTIONS (SET application_name 'fdw_wrong'); +SET postgres_fdw.application_name TO 'fdw_%a%u%%'; +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_setting('application_name') || + CURRENT_USER || '%' for current_setting('max_identifier_length')::int); +RESET postgres_fdw.application_name; + +-- Test %c (session ID) and %C (cluster name) escape sequences. +ALTER SERVER loopback2 OPTIONS (SET application_name 'fdw_%C%c'); +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_setting('cluster_name') || + to_hex(trunc(EXTRACT(EPOCH FROM (SELECT backend_start FROM + pg_stat_get_activity(pg_backend_pid()))))::integer) || '.' || + to_hex(pg_backend_pid()) + for current_setting('max_identifier_length')::int); + +-- Clean up. +DROP FOREIGN TABLE remote_application_name; +DROP VIEW my_application_name; + +-- =================================================================== +-- test parallel commit +-- =================================================================== +ALTER SERVER loopback OPTIONS (ADD parallel_commit 'true'); +ALTER SERVER loopback2 OPTIONS (ADD parallel_commit 'true'); + +CREATE TABLE ploc1 (f1 int, f2 text); +CREATE FOREIGN TABLE prem1 (f1 int, f2 text) + SERVER loopback OPTIONS (table_name 'ploc1'); +CREATE TABLE ploc2 (f1 int, f2 text); +CREATE FOREIGN TABLE prem2 (f1 int, f2 text) + SERVER loopback2 OPTIONS (table_name 'ploc2'); + +BEGIN; +INSERT INTO prem1 VALUES (101, 'foo'); +INSERT INTO prem2 VALUES (201, 'bar'); +COMMIT; +SELECT * FROM prem1; +SELECT * FROM prem2; + +BEGIN; +SAVEPOINT s; +INSERT INTO prem1 VALUES (102, 'foofoo'); +INSERT INTO prem2 VALUES (202, 'barbar'); +RELEASE SAVEPOINT s; +COMMIT; +SELECT * FROM prem1; +SELECT * FROM prem2; + +-- This tests executing DEALLOCATE ALL against foreign servers in parallel +-- during pre-commit +BEGIN; +SAVEPOINT s; +INSERT INTO prem1 VALUES (103, 'baz'); +INSERT INTO prem2 VALUES (203, 'qux'); +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +INSERT INTO prem1 VALUES (104, 'bazbaz'); +INSERT INTO prem2 VALUES (204, 'quxqux'); +COMMIT; +SELECT * FROM prem1; +SELECT * FROM prem2; + +ALTER SERVER loopback OPTIONS (DROP parallel_commit); +ALTER SERVER loopback2 OPTIONS (DROP parallel_commit); diff --git a/contrib/postgres_fdw/yb_gv_schedule b/contrib/postgres_fdw/yb_gv_schedule new file mode 100644 index 0000000000000..a76413e12cec0 --- /dev/null +++ b/contrib/postgres_fdw/yb_gv_schedule @@ -0,0 +1,4 @@ +# contrib/postgres_fdw/yb_gv_schedule +# Global views regression tests + +test: yb.orig.global_views diff --git a/contrib/postgres_fdw/yb_pg_schedule b/contrib/postgres_fdw/yb_pg_schedule new file mode 100644 index 0000000000000..5622e11bcc088 --- /dev/null +++ b/contrib/postgres_fdw/yb_pg_schedule @@ -0,0 +1,3 @@ +# contrib/postgres_fdw/yb_pg_schedule + +test: yb.port.postgres_fdw diff --git a/contrib/postgres_fdw/yb_schedule b/contrib/postgres_fdw/yb_schedule new file mode 100644 index 0000000000000..c537aef5e2fc4 --- /dev/null +++ b/contrib/postgres_fdw/yb_schedule @@ -0,0 +1,3 @@ +# contrib/postgres_fdw/yb_schedule + +test: yb.orig.postgres_fdw diff --git a/contrib/seg/Makefile b/contrib/seg/Makefile index aee8826d8be82..bb63e835067f4 100644 --- a/contrib/seg/Makefile +++ b/contrib/seg/Makefile @@ -13,7 +13,7 @@ PGFILEDESC = "seg - line segment data type" HEADERS = segdata.h -REGRESS = security seg +REGRESS = seg EXTRA_CLEAN = y.tab.c y.tab.h diff --git a/contrib/seg/seg.c b/contrib/seg/seg.c index 91b8a79600477..941337a05ab73 100644 --- a/contrib/seg/seg.c +++ b/contrib/seg/seg.c @@ -912,12 +912,20 @@ seg_different(PG_FUNCTION_ARGS) static int restore(char *result, float val, int n) { - char buf[25] = { + /* + * YB: To avoid the following error when building with GCC 12: + * bit.ly/seg_cc_gcc12_format_overflow + * + * The fix increased the array size from 25 to 32 and added 7 bytes, + * fixing the compilation error without changing behavior. + */ + char buf[32] = { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '\0' + '0', '0', '0', '0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0' }; char *p; int exp; diff --git a/contrib/spi/expected/yb.orig.spi.out b/contrib/spi/expected/yb.orig.spi.out new file mode 100644 index 0000000000000..ce323ff446bc1 --- /dev/null +++ b/contrib/spi/expected/yb.orig.spi.out @@ -0,0 +1,215 @@ +-- Test autoinc EXTENSION +CREATE EXTENSION autoinc; +CREATE SEQUENCE next_id START -2 MINVALUE -2; +CREATE TABLE ids (id int4, idesc text); +CREATE TRIGGER ids_nextid + BEFORE INSERT OR UPDATE ON ids + FOR EACH ROW + EXECUTE PROCEDURE autoinc (id, next_id); +INSERT INTO ids VALUES (0, 'first (-2 ?)'); +INSERT INTO ids VALUES (null, 'second (-1 ?)'); +INSERT INTO ids(idesc) VALUES ('third (1 ?!)'); +SELECT * FROM ids ORDER BY idesc ASC; + id | idesc +----+--------------- + -2 | first (-2 ?) + -1 | second (-1 ?) + 1 | third (1 ?!) +(3 rows) + +UPDATE ids SET id = null, idesc = 'first: -2 --> 2' WHERE idesc = 'first (-2 ?)'; +UPDATE ids SET id = 0, idesc = 'second: -1 --> 3' WHERE id = -1; +UPDATE ids SET id = 4, idesc = 'third: 1 --> 4' WHERE id = 1; +SELECT * FROM ids ORDER BY idesc ASC; + id | idesc +----+------------------ + 2 | first: -2 --> 2 + 3 | second: -1 --> 3 + 4 | third: 1 --> 4 +(3 rows) + +SELECT 'Wasn''t it 4 ?' as nextval, nextval ('next_id'); + nextval | nextval +---------------+--------- + Wasn't it 4 ? | 4 +(1 row) + +DROP SEQUENCE next_id; +DROP TABLE ids; +-- Test insert_username EXTENSION +CREATE EXTENSION insert_username; +CREATE TABLE username_test (name text, username text not null); +CREATE TRIGGER insert_usernames + BEFORE INSERT OR UPDATE ON username_test + FOR EACH ROW + EXECUTE PROCEDURE insert_username (username); +INSERT INTO username_test VALUES ('nothing'); +INSERT INTO username_test VALUES ('null', null); +INSERT INTO username_test VALUES ('empty string', ''); +INSERT INTO username_test VALUES ('space', ' '); +INSERT INTO username_test VALUES ('tab', ' '); +INSERT INTO username_test VALUES ('name', 'name'); +SELECT * FROM username_test ORDER BY name ASC; + name | username +--------------+---------- + empty string | yugabyte + name | yugabyte + nothing | yugabyte + null | yugabyte + space | yugabyte + tab | yugabyte +(6 rows) + +DROP TABLE username_test; +-- Test moddatetime EXTENSION +CREATE EXTENSION moddatetime; +CREATE TABLE mdt (id int4, idesc text, moddate timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL); +CREATE TRIGGER mdt_moddatetime + BEFORE UPDATE ON mdt + FOR EACH ROW + EXECUTE PROCEDURE moddatetime (moddate); +INSERT INTO mdt VALUES (1, 'first'); +INSERT INTO mdt VALUES (2, 'second'); +INSERT INTO mdt VALUES (3, 'third'); +SELECT id, idesc FROM mdt ORDER BY moddate; + id | idesc +----+-------- + 1 | first + 2 | second + 3 | third +(3 rows) + +UPDATE mdt SET id = 4 WHERE id = 1; +UPDATE mdt SET id = 5 WHERE id = 2; +UPDATE mdt SET id = 6 WHERE id = 3; +SELECT id, idesc FROM mdt ORDER BY moddate ASC; + id | idesc +----+-------- + 4 | first + 5 | second + 6 | third +(3 rows) + +DROP TABLE mdt; +CREATE TABLE spi_test ( + id int primary key, + content text, + username text not null, + moddate timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL +); +CREATE TRIGGER insert_usernames + BEFORE INSERT OR UPDATE ON spi_test + FOR EACH ROW + EXECUTE PROCEDURE insert_username (username); +CREATE TRIGGER update_moddatetime + BEFORE UPDATE ON spi_test + FOR EACH ROW + EXECUTE PROCEDURE moddatetime (moddate); +SET ROLE yugabyte; +INSERT INTO spi_test VALUES(1, 'desc1'); +SET ROLE postgres; +INSERT INTO spi_test VALUES(2, 'desc2'); +INSERT INTO spi_test VALUES(3, 'desc3'); +SET ROLE yugabyte; +INSERT INTO spi_test VALUES(4, 'desc4'); +SELECT id, content, username FROM spi_test ORDER BY moddate ASC; + id | content | username +----+---------+---------- + 1 | desc1 | yugabyte + 2 | desc2 | postgres + 3 | desc3 | postgres + 4 | desc4 | yugabyte +(4 rows) + +UPDATE spi_test SET content = 'desc1_updated' WHERE id = 1; +UPDATE spi_test SET content = 'desc3_updated' WHERE id = 3; +SELECT id, content, username FROM spi_test ORDER BY moddate ASC; + id | content | username +----+---------------+---------- + 2 | desc2 | postgres + 4 | desc4 | yugabyte + 1 | desc1_updated | yugabyte + 3 | desc3_updated | yugabyte +(4 rows) + +DROP TABLE username_test; +ERROR: table "username_test" does not exist +-- Test refint EXTENSION +CREATE EXTENSION refint; +--Column ID of table A is primary key: +CREATE TABLE A ( id int4 not null); +CREATE UNIQUE INDEX AI ON A (id); +--Columns REFB of table B and REFC of C are foreign keys referencing ID of A: +CREATE TABLE B (refb int4); +CREATE INDEX BI ON B (refb); +CREATE TABLE C (refc int4); +CREATE INDEX CI ON C (refc); +--Trigger for table A: +CREATE TRIGGER AT BEFORE DELETE OR UPDATE ON A FOR EACH ROW +EXECUTE PROCEDURE check_foreign_key (2, 'cascade', 'id', 'B', 'refb', 'C', 'refc'); +-- 2 - means that check must be performed for foreign keys of 2 tables. +-- cascade - defines that corresponding keys must be deleted. +-- ID - name of primary key column in triggered table (A). You may use as many columns as you need. +-- B - name of (first) table with foreign keys. +-- REFB - name of foreign key column in this table. You may use as many columns as you need, but +-- number of key columns in referenced table (A) must be the same. +-- C - name of second table with foreign keys. +-- REFC - name of foreign key column in this table. +--Trigger for table B: +CREATE TRIGGER BT BEFORE INSERT OR UPDATE ON B FOR EACH ROW +EXECUTE PROCEDURE check_primary_key ('refb', 'A', 'id'); +-- REFB - name of foreign key column in triggered (B) table. You may use as many columns as you need, +-- but number of key columns in referenced table must be the same. +-- A - referenced table name. +-- ID - name of primary key column in referenced table. +--Trigger for table C: +CREATE TRIGGER CT BEFORE INSERT OR UPDATE ON C FOR EACH ROW +EXECUTE PROCEDURE check_primary_key ('refc', 'A', 'id'); +INSERT INTO A VALUES (10); +INSERT INTO A VALUES (20); +INSERT INTO A VALUES (30); +INSERT INTO A VALUES (40); +INSERT INTO A VALUES (50); +-- invalid reference +INSERT INTO B VALUES (1); +ERROR: tuple references non-existent key +DETAIL: Trigger "bt" found tuple referencing non-existent key in "A". +INSERT INTO B VALUES (10); +INSERT INTO B VALUES (30); +INSERT INTO B VALUES (30); +-- invalid reference +INSERT INTO C VALUES (11); +ERROR: tuple references non-existent key +DETAIL: Trigger "ct" found tuple referencing non-existent key in "A". +INSERT INTO C VALUES (20); +INSERT INTO C VALUES (20); +INSERT INTO C VALUES (30); +DELETE FROM A WHERE id = 10; +NOTICE: at: 1 tuple(s) of B are deleted +NOTICE: at: 0 tuple(s) of C are deleted +DELETE FROM A WHERE id = 20; +NOTICE: at: 0 tuple(s) of B are deleted +NOTICE: at: 2 tuple(s) of C are deleted +DELETE FROM A WHERE id = 30; +NOTICE: at: 2 tuple(s) of B are deleted +NOTICE: at: 1 tuple(s) of C are deleted +SELECT * FROM A ORDER BY id ASC; + id +---- + 40 + 50 +(2 rows) + +SELECT * FROM B ORDER BY refb ASC; + refb +------ +(0 rows) + +SELECT * FROM C ORDER BY refc ASC; + refc +------ +(0 rows) + +DROP TABLE A; +DROP TABLE B; +DROP TABLE C; diff --git a/contrib/spi/sql/yb.orig.spi.sql b/contrib/spi/sql/yb.orig.spi.sql new file mode 100644 index 0000000000000..60f0dd08effdf --- /dev/null +++ b/contrib/spi/sql/yb.orig.spi.sql @@ -0,0 +1,190 @@ +-- Test autoinc EXTENSION + +CREATE EXTENSION autoinc; + +CREATE SEQUENCE next_id START -2 MINVALUE -2; + +CREATE TABLE ids (id int4, idesc text); + +CREATE TRIGGER ids_nextid + BEFORE INSERT OR UPDATE ON ids + FOR EACH ROW + EXECUTE PROCEDURE autoinc (id, next_id); + +INSERT INTO ids VALUES (0, 'first (-2 ?)'); +INSERT INTO ids VALUES (null, 'second (-1 ?)'); +INSERT INTO ids(idesc) VALUES ('third (1 ?!)'); + +SELECT * FROM ids ORDER BY idesc ASC; + +UPDATE ids SET id = null, idesc = 'first: -2 --> 2' WHERE idesc = 'first (-2 ?)'; +UPDATE ids SET id = 0, idesc = 'second: -1 --> 3' WHERE id = -1; +UPDATE ids SET id = 4, idesc = 'third: 1 --> 4' WHERE id = 1; + +SELECT * FROM ids ORDER BY idesc ASC; + +SELECT 'Wasn''t it 4 ?' as nextval, nextval ('next_id'); + +DROP SEQUENCE next_id; +DROP TABLE ids; + +-- Test insert_username EXTENSION + +CREATE EXTENSION insert_username; + +CREATE TABLE username_test (name text, username text not null); + +CREATE TRIGGER insert_usernames + BEFORE INSERT OR UPDATE ON username_test + FOR EACH ROW + EXECUTE PROCEDURE insert_username (username); + +INSERT INTO username_test VALUES ('nothing'); +INSERT INTO username_test VALUES ('null', null); +INSERT INTO username_test VALUES ('empty string', ''); +INSERT INTO username_test VALUES ('space', ' '); +INSERT INTO username_test VALUES ('tab', ' '); +INSERT INTO username_test VALUES ('name', 'name'); + +SELECT * FROM username_test ORDER BY name ASC; + +DROP TABLE username_test; + +-- Test moddatetime EXTENSION + +CREATE EXTENSION moddatetime; + +CREATE TABLE mdt (id int4, idesc text, moddate timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL); + +CREATE TRIGGER mdt_moddatetime + BEFORE UPDATE ON mdt + FOR EACH ROW + EXECUTE PROCEDURE moddatetime (moddate); + +INSERT INTO mdt VALUES (1, 'first'); +INSERT INTO mdt VALUES (2, 'second'); +INSERT INTO mdt VALUES (3, 'third'); + +SELECT id, idesc FROM mdt ORDER BY moddate; + +UPDATE mdt SET id = 4 WHERE id = 1; +UPDATE mdt SET id = 5 WHERE id = 2; +UPDATE mdt SET id = 6 WHERE id = 3; + +SELECT id, idesc FROM mdt ORDER BY moddate ASC; + +DROP TABLE mdt; + +CREATE TABLE spi_test ( + id int primary key, + content text, + username text not null, + moddate timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +CREATE TRIGGER insert_usernames + BEFORE INSERT OR UPDATE ON spi_test + FOR EACH ROW + EXECUTE PROCEDURE insert_username (username); + +CREATE TRIGGER update_moddatetime + BEFORE UPDATE ON spi_test + FOR EACH ROW + EXECUTE PROCEDURE moddatetime (moddate); + +SET ROLE yugabyte; +INSERT INTO spi_test VALUES(1, 'desc1'); + +SET ROLE postgres; +INSERT INTO spi_test VALUES(2, 'desc2'); +INSERT INTO spi_test VALUES(3, 'desc3'); + +SET ROLE yugabyte; +INSERT INTO spi_test VALUES(4, 'desc4'); + +SELECT id, content, username FROM spi_test ORDER BY moddate ASC; + +UPDATE spi_test SET content = 'desc1_updated' WHERE id = 1; +UPDATE spi_test SET content = 'desc3_updated' WHERE id = 3; + +SELECT id, content, username FROM spi_test ORDER BY moddate ASC; + +DROP TABLE username_test; + +-- Test refint EXTENSION + +CREATE EXTENSION refint; + +--Column ID of table A is primary key: + +CREATE TABLE A ( id int4 not null); +CREATE UNIQUE INDEX AI ON A (id); + +--Columns REFB of table B and REFC of C are foreign keys referencing ID of A: + +CREATE TABLE B (refb int4); +CREATE INDEX BI ON B (refb); + +CREATE TABLE C (refc int4); +CREATE INDEX CI ON C (refc); + +--Trigger for table A: + +CREATE TRIGGER AT BEFORE DELETE OR UPDATE ON A FOR EACH ROW +EXECUTE PROCEDURE check_foreign_key (2, 'cascade', 'id', 'B', 'refb', 'C', 'refc'); + +-- 2 - means that check must be performed for foreign keys of 2 tables. +-- cascade - defines that corresponding keys must be deleted. +-- ID - name of primary key column in triggered table (A). You may use as many columns as you need. +-- B - name of (first) table with foreign keys. +-- REFB - name of foreign key column in this table. You may use as many columns as you need, but +-- number of key columns in referenced table (A) must be the same. +-- C - name of second table with foreign keys. +-- REFC - name of foreign key column in this table. + +--Trigger for table B: + +CREATE TRIGGER BT BEFORE INSERT OR UPDATE ON B FOR EACH ROW +EXECUTE PROCEDURE check_primary_key ('refb', 'A', 'id'); + +-- REFB - name of foreign key column in triggered (B) table. You may use as many columns as you need, +-- but number of key columns in referenced table must be the same. +-- A - referenced table name. +-- ID - name of primary key column in referenced table. + +--Trigger for table C: + +CREATE TRIGGER CT BEFORE INSERT OR UPDATE ON C FOR EACH ROW +EXECUTE PROCEDURE check_primary_key ('refc', 'A', 'id'); + +INSERT INTO A VALUES (10); +INSERT INTO A VALUES (20); +INSERT INTO A VALUES (30); +INSERT INTO A VALUES (40); +INSERT INTO A VALUES (50); + +-- invalid reference +INSERT INTO B VALUES (1); + +INSERT INTO B VALUES (10); +INSERT INTO B VALUES (30); +INSERT INTO B VALUES (30); + +-- invalid reference +INSERT INTO C VALUES (11); + +INSERT INTO C VALUES (20); +INSERT INTO C VALUES (20); +INSERT INTO C VALUES (30); + +DELETE FROM A WHERE id = 10; +DELETE FROM A WHERE id = 20; +DELETE FROM A WHERE id = 30; + +SELECT * FROM A ORDER BY id ASC; +SELECT * FROM B ORDER BY refb ASC; +SELECT * FROM C ORDER BY refc ASC; + +DROP TABLE A; +DROP TABLE B; +DROP TABLE C; diff --git a/contrib/spi/yb_schedule b/contrib/spi/yb_schedule new file mode 100644 index 0000000000000..fe52875db3afd --- /dev/null +++ b/contrib/spi/yb_schedule @@ -0,0 +1,3 @@ +# contrib/spi/yb_schedule + +test: yb.orig.spi diff --git a/contrib/tablefunc/expected/yb.port.tablefunc.out b/contrib/tablefunc/expected/yb.port.tablefunc.out new file mode 100644 index 0000000000000..d9a00c76710cb --- /dev/null +++ b/contrib/tablefunc/expected/yb.port.tablefunc.out @@ -0,0 +1,430 @@ +CREATE EXTENSION tablefunc; +-- +-- normal_rand() +-- no easy way to do this for regression testing +-- +SELECT avg(normal_rand)::int, count(*) FROM normal_rand(100, 250, 0.2); + avg | count +-----+------- + 250 | 100 +(1 row) + +-- negative number of tuples +SELECT avg(normal_rand)::int, count(*) FROM normal_rand(-1, 250, 0.2); +ERROR: number of rows cannot be negative +-- +-- crosstab() +-- +CREATE TABLE ct(id int, rowclass text, rowid text, attribute text, val text); +\copy ct from 'data/ct.data' +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); + row_name | category_1 | category_2 +----------+------------+------------ + test1 | val2 | val3 + test2 | val6 | val7 + | val10 | val11 +(3 rows) + +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 +----------+------------+------------+------------ + test1 | val2 | val3 | + test2 | val6 | val7 | + | val10 | val11 | +(3 rows) + +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 | category_4 +----------+------------+------------+------------+------------ + test1 | val2 | val3 | | + test2 | val6 | val7 | | + | val10 | val11 | | +(3 rows) + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + row_name | category_1 | category_2 +----------+------------+------------ + test1 | val1 | val2 + test2 | val5 | val6 + | val9 | val10 +(3 rows) + +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 +----------+------------+------------+------------ + test1 | val1 | val2 | val3 + test2 | val5 | val6 | val7 + | val9 | val10 | val11 +(3 rows) + +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 | category_4 +----------+------------+------------+------------+------------ + test1 | val1 | val2 | val3 | val4 + test2 | val5 | val6 | val7 | val8 + | val9 | val10 | val11 | val12 +(3 rows) + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); + row_name | category_1 | category_2 +----------+------------+------------ + test3 | val1 | val2 + test4 | val4 | val5 +(2 rows) + +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 +----------+------------+------------+------------ + test3 | val1 | val2 | + test4 | val4 | val5 | +(2 rows) + +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 | category_4 +----------+------------+------------+------------+------------ + test3 | val1 | val2 | | + test4 | val4 | val5 | | +(2 rows) + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); + row_name | category_1 | category_2 +----------+------------+------------ + test3 | val1 | val2 + test4 | val4 | val5 +(2 rows) + +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 +----------+------------+------------+------------ + test3 | val1 | val2 | val3 + test4 | val4 | val5 | val6 +(2 rows) + +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 | category_4 +----------+------------+------------+------------+------------ + test3 | val1 | val2 | val3 | + test4 | val4 | val5 | val6 | +(2 rows) + +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text); + rowid | att1 | att2 +-------+------+------- + test1 | val1 | val2 + test2 | val5 | val6 + | val9 | val10 +(3 rows) + +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text); + rowid | att1 | att2 | att3 +-------+------+-------+------- + test1 | val1 | val2 | val3 + test2 | val5 | val6 | val7 + | val9 | val10 | val11 +(3 rows) + +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text, att4 text); + rowid | att1 | att2 | att3 | att4 +-------+------+-------+-------+------- + test1 | val1 | val2 | val3 | val4 + test2 | val5 | val6 | val7 | val8 + | val9 | val10 | val11 | val12 +(3 rows) + +-- check it works with OUT parameters, too +CREATE FUNCTION crosstab_out(text, + OUT rowid text, OUT att1 text, OUT att2 text, OUT att3 text) +RETURNS setof record +AS '$libdir/tablefunc','crosstab' +LANGUAGE C STABLE STRICT; +SELECT * FROM crosstab_out('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + rowid | att1 | att2 | att3 +-------+------+-------+------- + test1 | val1 | val2 | val3 + test2 | val5 | val6 | val7 + | val9 | val10 | val11 +(3 rows) + +-- +-- hash based crosstab +-- +create table cth(id serial, rowid text, rowdt timestamp, attribute text, val text); +insert into cth values(DEFAULT,'test1','01 March 2003','temperature','42'); +insert into cth values(DEFAULT,'test1','01 March 2003','test_result','PASS'); +-- the next line is intentionally left commented and is therefore a "missing" attribute +-- insert into cth values(DEFAULT,'test1','01 March 2003','test_startdate','28 February 2003'); +insert into cth values(DEFAULT,'test1','01 March 2003','volts','2.6987'); +insert into cth values(DEFAULT,'test2','02 March 2003','temperature','53'); +insert into cth values(DEFAULT,'test2','02 March 2003','test_result','FAIL'); +insert into cth values(DEFAULT,'test2','02 March 2003','test_startdate','01 March 2003'); +insert into cth values(DEFAULT,'test2','02 March 2003','volts','3.1234'); +-- next group tests for NULL rowids +insert into cth values(DEFAULT,NULL,'25 October 2007','temperature','57'); +insert into cth values(DEFAULT,NULL,'25 October 2007','test_result','PASS'); +insert into cth values(DEFAULT,NULL,'25 October 2007','test_startdate','24 October 2007'); +insert into cth values(DEFAULT,NULL,'25 October 2007','volts','1.41234'); +-- return attributes as plain text +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+--------------------------+-------------+-------------+-----------------+--------- + test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987 + test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | 01 March 2003 | 3.1234 + | Thu Oct 25 00:00:00 2007 | 57 | PASS | 24 October 2007 | 1.41234 +(3 rows) + +-- this time without rowdt +SELECT * FROM crosstab( + 'SELECT rowid, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, temperature text, test_result text, test_startdate text, volts text); + rowid | temperature | test_result | test_startdate | volts +-------+-------------+-------------+-----------------+--------- + test1 | 42 | PASS | | 2.6987 + test2 | 53 | FAIL | 01 March 2003 | 3.1234 + | 57 | PASS | 24 October 2007 | 1.41234 +(3 rows) + +-- convert attributes to specific datatypes +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+--------------------------+-------------+-------------+--------------------------+--------- + test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987 + test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234 + | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007 | 1.41234 +(3 rows) + +-- source query and category query out of sync +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE attribute IN (''temperature'',''test_result'',''test_startdate'') ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp); + rowid | rowdt | temperature | test_result | test_startdate +-------+--------------------------+-------------+-------------+-------------------------- + test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | + test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 + | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007 +(3 rows) + +-- if category query generates no rows, get expected error +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE attribute = ''a'' ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); +ERROR: provided "categories" SQL must return 1 column of at least one row +-- if category query generates more than one column, get expected error +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT rowdt, attribute FROM cth ORDER BY 2') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); +ERROR: provided "categories" SQL must return 1 column of at least one row +-- if source query returns zero rows, get zero rows returned +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+-------+-------------+-------------+----------------+------- +(0 rows) + +-- if source query returns zero rows, get zero rows returned even if category query generates no rows +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE false ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+-------+-------------+-------------+----------------+------- +(0 rows) + +-- check it works with a named result rowtype +create type my_crosstab_result as ( + rowid text, rowdt timestamp, + temperature int4, test_result text, test_startdate timestamp, volts float8); +CREATE FUNCTION crosstab_named(text, text) +RETURNS setof my_crosstab_result +AS '$libdir/tablefunc','crosstab_hash' +LANGUAGE C STABLE STRICT; +SELECT * FROM crosstab_named( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1'); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+--------------------------+-------------+-------------+--------------------------+--------- + test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987 + test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234 + | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007 | 1.41234 +(3 rows) + +-- check it works with OUT parameters +CREATE FUNCTION crosstab_out(text, text, + OUT rowid text, OUT rowdt timestamp, + OUT temperature int4, OUT test_result text, + OUT test_startdate timestamp, OUT volts float8) +RETURNS setof record +AS '$libdir/tablefunc','crosstab_hash' +LANGUAGE C STABLE STRICT; +SELECT * FROM crosstab_out( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1'); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+--------------------------+-------------+-------------+--------------------------+--------- + test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987 + test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234 + | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007 | 1.41234 +(3 rows) + +-- +-- connectby +-- +-- test connectby with text based hierarchy +CREATE TABLE connectby_text(keyid text, parent_keyid text, pos int, ybsort serial, PRIMARY KEY (ybsort ASC)); +\copy connectby_text(keyid, parent_keyid, pos) from 'data/connectby_text.data' +-- with branch, without orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text); + keyid | parent_keyid | level | branch +-------+--------------+-------+--------------------- + row2 | | 0 | row2 + row4 | row2 | 1 | row2~row4 + row6 | row4 | 2 | row2~row4~row6 + row8 | row6 | 3 | row2~row4~row6~row8 + row5 | row2 | 1 | row2~row5 + row9 | row5 | 2 | row2~row5~row9 +(6 rows) + +-- without branch, without orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0) AS t(keyid text, parent_keyid text, level int); + keyid | parent_keyid | level +-------+--------------+------- + row2 | | 0 + row4 | row2 | 1 + row6 | row4 | 2 + row8 | row6 | 3 + row5 | row2 | 1 + row9 | row5 | 2 +(6 rows) + +-- with branch, with orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text, pos int) ORDER BY t.pos; + keyid | parent_keyid | level | branch | pos +-------+--------------+-------+---------------------+----- + row2 | | 0 | row2 | 1 + row5 | row2 | 1 | row2~row5 | 2 + row9 | row5 | 2 | row2~row5~row9 | 3 + row4 | row2 | 1 | row2~row4 | 4 + row6 | row4 | 2 | row2~row4~row6 | 5 + row8 | row6 | 3 | row2~row4~row6~row8 | 6 +(6 rows) + +-- without branch, with orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0) AS t(keyid text, parent_keyid text, level int, pos int) ORDER BY t.pos; + keyid | parent_keyid | level | pos +-------+--------------+-------+----- + row2 | | 0 | 1 + row5 | row2 | 1 | 2 + row9 | row5 | 2 | 3 + row4 | row2 | 1 | 4 + row6 | row4 | 2 | 5 + row8 | row6 | 3 | 6 +(6 rows) + +-- test connectby with int based hierarchy +CREATE TABLE connectby_int(keyid int, parent_keyid int, ybsort serial, PRIMARY KEY (ybsort ASC)); +\copy connectby_int(keyid, parent_keyid) from 'data/connectby_int.data' +-- with branch +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch text); + keyid | parent_keyid | level | branch +-------+--------------+-------+--------- + 2 | | 0 | 2 + 4 | 2 | 1 | 2~4 + 6 | 4 | 2 | 2~4~6 + 8 | 6 | 3 | 2~4~6~8 + 5 | 2 | 1 | 2~5 + 9 | 5 | 2 | 2~5~9 +(6 rows) + +-- without branch +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); + keyid | parent_keyid | level +-------+--------------+------- + 2 | | 0 + 4 | 2 | 1 + 6 | 4 | 2 + 8 | 6 | 3 + 5 | 2 | 1 + 9 | 5 | 2 +(6 rows) + +-- recursion detection +INSERT INTO connectby_int VALUES(10,9); +INSERT INTO connectby_int VALUES(11,10); +INSERT INTO connectby_int VALUES(9,11); +-- should fail due to infinite recursion +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch text); +ERROR: infinite recursion detected +-- infinite recursion failure avoided by depth limit +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 4, '~') AS t(keyid int, parent_keyid int, level int, branch text); + keyid | parent_keyid | level | branch +-------+--------------+-------+------------- + 2 | | 0 | 2 + 4 | 2 | 1 | 2~4 + 6 | 4 | 2 | 2~4~6 + 8 | 6 | 3 | 2~4~6~8 + 5 | 2 | 1 | 2~5 + 9 | 5 | 2 | 2~5~9 + 10 | 9 | 3 | 2~5~9~10 + 11 | 10 | 4 | 2~5~9~10~11 +(8 rows) + +-- should fail as first two columns must have the same type +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid text, parent_keyid int, level int, branch text); +ERROR: invalid return type +DETAIL: First two columns must be the same type. +-- should fail as key field datatype should match return datatype +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid float8, parent_keyid float8, level int, branch text); +ERROR: invalid return type +DETAIL: SQL key field type double precision does not match return key field type integer. +-- tests for values using custom queries +-- query with one column - failed +SELECT * FROM connectby('connectby_int', '1; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); +ERROR: invalid return type +DETAIL: Query must return at least two columns. +-- query with two columns first value as NULL +SELECT * FROM connectby('connectby_int', 'NULL::int, 1::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); + keyid | parent_keyid | level +-------+--------------+------- + 2 | | 0 + | 1 | 1 +(2 rows) + +-- query with two columns second value as NULL +SELECT * FROM connectby('connectby_int', '1::int, NULL::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); +ERROR: infinite recursion detected +-- query with two columns, both values as NULL +SELECT * FROM connectby('connectby_int', 'NULL::int, NULL::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); + keyid | parent_keyid | level +-------+--------------+------- + 2 | | 0 + | | 1 +(2 rows) + +-- test for falsely detected recursion +DROP TABLE connectby_int; +CREATE TABLE connectby_int(keyid int, parent_keyid int, ybsort serial, PRIMARY KEY (ybsort ASC)); +INSERT INTO connectby_int VALUES(11,NULL); +INSERT INTO connectby_int VALUES(10,11); +INSERT INTO connectby_int VALUES(111,11); +INSERT INTO connectby_int VALUES(1,111); +-- this should not fail due to recursion detection +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '11', 0, '-') AS t(keyid int, parent_keyid int, level int, branch text); + keyid | parent_keyid | level | branch +-------+--------------+-------+---------- + 11 | | 0 | 11 + 10 | 11 | 1 | 11-10 + 111 | 11 | 1 | 11-111 + 1 | 111 | 2 | 11-111-1 +(4 rows) + diff --git a/contrib/tablefunc/sql/yb.port.tablefunc.sql b/contrib/tablefunc/sql/yb.port.tablefunc.sql new file mode 100644 index 0000000000000..8c7182bbe12da --- /dev/null +++ b/contrib/tablefunc/sql/yb.port.tablefunc.sql @@ -0,0 +1,208 @@ +CREATE EXTENSION tablefunc; + +-- +-- normal_rand() +-- no easy way to do this for regression testing +-- +SELECT avg(normal_rand)::int, count(*) FROM normal_rand(100, 250, 0.2); +-- negative number of tuples +SELECT avg(normal_rand)::int, count(*) FROM normal_rand(-1, 250, 0.2); + +-- +-- crosstab() +-- +CREATE TABLE ct(id int, rowclass text, rowid text, attribute text, val text); +\copy ct from 'data/ct.data' + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); + +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text); +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text); +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text, att4 text); + +-- check it works with OUT parameters, too + +CREATE FUNCTION crosstab_out(text, + OUT rowid text, OUT att1 text, OUT att2 text, OUT att3 text) +RETURNS setof record +AS '$libdir/tablefunc','crosstab' +LANGUAGE C STABLE STRICT; + +SELECT * FROM crosstab_out('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + +-- +-- hash based crosstab +-- +create table cth(id serial, rowid text, rowdt timestamp, attribute text, val text); +insert into cth values(DEFAULT,'test1','01 March 2003','temperature','42'); +insert into cth values(DEFAULT,'test1','01 March 2003','test_result','PASS'); +-- the next line is intentionally left commented and is therefore a "missing" attribute +-- insert into cth values(DEFAULT,'test1','01 March 2003','test_startdate','28 February 2003'); +insert into cth values(DEFAULT,'test1','01 March 2003','volts','2.6987'); +insert into cth values(DEFAULT,'test2','02 March 2003','temperature','53'); +insert into cth values(DEFAULT,'test2','02 March 2003','test_result','FAIL'); +insert into cth values(DEFAULT,'test2','02 March 2003','test_startdate','01 March 2003'); +insert into cth values(DEFAULT,'test2','02 March 2003','volts','3.1234'); +-- next group tests for NULL rowids +insert into cth values(DEFAULT,NULL,'25 October 2007','temperature','57'); +insert into cth values(DEFAULT,NULL,'25 October 2007','test_result','PASS'); +insert into cth values(DEFAULT,NULL,'25 October 2007','test_startdate','24 October 2007'); +insert into cth values(DEFAULT,NULL,'25 October 2007','volts','1.41234'); + +-- return attributes as plain text +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + +-- this time without rowdt +SELECT * FROM crosstab( + 'SELECT rowid, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, temperature text, test_result text, test_startdate text, volts text); + +-- convert attributes to specific datatypes +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); + +-- source query and category query out of sync +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE attribute IN (''temperature'',''test_result'',''test_startdate'') ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp); + +-- if category query generates no rows, get expected error +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE attribute = ''a'' ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); + +-- if category query generates more than one column, get expected error +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT rowdt, attribute FROM cth ORDER BY 2') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); + +-- if source query returns zero rows, get zero rows returned +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + +-- if source query returns zero rows, get zero rows returned even if category query generates no rows +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE false ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + +-- check it works with a named result rowtype + +create type my_crosstab_result as ( + rowid text, rowdt timestamp, + temperature int4, test_result text, test_startdate timestamp, volts float8); + +CREATE FUNCTION crosstab_named(text, text) +RETURNS setof my_crosstab_result +AS '$libdir/tablefunc','crosstab_hash' +LANGUAGE C STABLE STRICT; + +SELECT * FROM crosstab_named( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1'); + +-- check it works with OUT parameters + +CREATE FUNCTION crosstab_out(text, text, + OUT rowid text, OUT rowdt timestamp, + OUT temperature int4, OUT test_result text, + OUT test_startdate timestamp, OUT volts float8) +RETURNS setof record +AS '$libdir/tablefunc','crosstab_hash' +LANGUAGE C STABLE STRICT; + +SELECT * FROM crosstab_out( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1'); + +-- +-- connectby +-- + +-- test connectby with text based hierarchy +CREATE TABLE connectby_text(keyid text, parent_keyid text, pos int, ybsort serial, PRIMARY KEY (ybsort ASC)); +\copy connectby_text(keyid, parent_keyid, pos) from 'data/connectby_text.data' + +-- with branch, without orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text); + +-- without branch, without orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0) AS t(keyid text, parent_keyid text, level int); + +-- with branch, with orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text, pos int) ORDER BY t.pos; + +-- without branch, with orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0) AS t(keyid text, parent_keyid text, level int, pos int) ORDER BY t.pos; + +-- test connectby with int based hierarchy +CREATE TABLE connectby_int(keyid int, parent_keyid int, ybsort serial, PRIMARY KEY (ybsort ASC)); +\copy connectby_int(keyid, parent_keyid) from 'data/connectby_int.data' + +-- with branch +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch text); + +-- without branch +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); + +-- recursion detection +INSERT INTO connectby_int VALUES(10,9); +INSERT INTO connectby_int VALUES(11,10); +INSERT INTO connectby_int VALUES(9,11); + +-- should fail due to infinite recursion +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch text); + +-- infinite recursion failure avoided by depth limit +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 4, '~') AS t(keyid int, parent_keyid int, level int, branch text); + +-- should fail as first two columns must have the same type +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid text, parent_keyid int, level int, branch text); + +-- should fail as key field datatype should match return datatype +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid float8, parent_keyid float8, level int, branch text); + +-- tests for values using custom queries +-- query with one column - failed +SELECT * FROM connectby('connectby_int', '1; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); +-- query with two columns first value as NULL +SELECT * FROM connectby('connectby_int', 'NULL::int, 1::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); +-- query with two columns second value as NULL +SELECT * FROM connectby('connectby_int', '1::int, NULL::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); +-- query with two columns, both values as NULL +SELECT * FROM connectby('connectby_int', 'NULL::int, NULL::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); + +-- test for falsely detected recursion +DROP TABLE connectby_int; +CREATE TABLE connectby_int(keyid int, parent_keyid int, ybsort serial, PRIMARY KEY (ybsort ASC)); +INSERT INTO connectby_int VALUES(11,NULL); +INSERT INTO connectby_int VALUES(10,11); +INSERT INTO connectby_int VALUES(111,11); +INSERT INTO connectby_int VALUES(1,111); +-- this should not fail due to recursion detection +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '11', 0, '-') AS t(keyid int, parent_keyid int, level int, branch text); diff --git a/contrib/tablefunc/yb_schedule b/contrib/tablefunc/yb_schedule new file mode 100644 index 0000000000000..ca769ae228c20 --- /dev/null +++ b/contrib/tablefunc/yb_schedule @@ -0,0 +1,3 @@ +# contrib/tablefunc/yb_schedule + +test: yb.port.tablefunc diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c index 296b25ad31bd2..a66b1a4c4d15e 100644 --- a/contrib/test_decoding/test_decoding.c +++ b/contrib/test_decoding/test_decoding.c @@ -22,6 +22,9 @@ #include "utils/memutils.h" #include "utils/rel.h" +/* YB includes */ +#include "pg_yb_utils.h" + PG_MODULE_MAGIC; /* These must be available to dlsym() */ @@ -121,6 +124,11 @@ static void pg_decode_stream_truncate(LogicalDecodingContext *ctx, int nrelations, Relation relations[], ReorderBufferChange *change); + +static void yb_pgoutput_schema_change(LogicalDecodingContext *ctx, Oid relid); + +static void yb_support_yb_specific_replica_identity(bool support_yb_specific_replica_identity); + void _PG_init(void) { @@ -154,6 +162,12 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb) cb->stream_change_cb = pg_decode_stream_change; cb->stream_message_cb = pg_decode_stream_message; cb->stream_truncate_cb = pg_decode_stream_truncate; + + if (IsYugaByteEnabled()) + { + cb->yb_schema_change_cb = yb_pgoutput_schema_change; + cb->yb_support_yb_specifc_replica_identity_cb = yb_support_yb_specific_replica_identity; + } } @@ -527,7 +541,8 @@ print_literal(StringInfo s, Oid typid, char *outputstr) /* print the tuple 'tuple' into the StringInfo s */ static void -tuple_to_stringinfo(StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool skip_nulls) +tuple_to_stringinfo(StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool skip_nulls, + bool *yb_is_omitted) { int natt; @@ -541,6 +556,8 @@ tuple_to_stringinfo(StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool skip_ Datum origval; /* possibly toasted Datum */ bool isnull; /* column is null? */ + bool yb_send_unchanged_toasted = false; + attr = TupleDescAttr(tupdesc, natt); /* @@ -581,10 +598,14 @@ tuple_to_stringinfo(StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool skip_ /* print separator */ appendStringInfoChar(s, ':'); + if (IsYugaByteEnabled()) + yb_send_unchanged_toasted = yb_is_omitted && yb_is_omitted[natt]; + /* print data */ - if (isnull) + if (isnull && !yb_send_unchanged_toasted) appendStringInfoString(s, "null"); - else if (typisvarlena && VARATT_IS_EXTERNAL_ONDISK(origval)) + else if ((yb_send_unchanged_toasted) || + (typisvarlena && VARATT_IS_EXTERNAL_ONDISK(origval))) appendStringInfoString(s, "unchanged-toast-datum"); else if (!typisvarlena) print_literal(s, typid, @@ -647,7 +668,8 @@ pg_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, else tuple_to_stringinfo(ctx->out, tupdesc, &change->data.tp.newtuple->tuple, - false); + false, + change->data.tp.newtuple->yb_is_omitted); break; case REORDER_BUFFER_CHANGE_UPDATE: appendStringInfoString(ctx->out, " UPDATE:"); @@ -656,7 +678,8 @@ pg_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, appendStringInfoString(ctx->out, " old-key:"); tuple_to_stringinfo(ctx->out, tupdesc, &change->data.tp.oldtuple->tuple, - true); + true, + change->data.tp.oldtuple->yb_is_omitted); appendStringInfoString(ctx->out, " new-tuple:"); } @@ -665,7 +688,8 @@ pg_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, else tuple_to_stringinfo(ctx->out, tupdesc, &change->data.tp.newtuple->tuple, - false); + false, + change->data.tp.newtuple->yb_is_omitted); break; case REORDER_BUFFER_CHANGE_DELETE: appendStringInfoString(ctx->out, " DELETE:"); @@ -677,7 +701,8 @@ pg_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, else tuple_to_stringinfo(ctx->out, tupdesc, &change->data.tp.oldtuple->tuple, - true); + true, + change->data.tp.oldtuple->yb_is_omitted); break; default: Assert(false); @@ -980,3 +1005,15 @@ pg_decode_stream_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, appendStringInfoString(ctx->out, "streaming truncate for transaction"); OutputPluginWrite(ctx, true); } + +static void +yb_pgoutput_schema_change(LogicalDecodingContext *ctx, Oid relid) +{ + /* NOOP. */ +} + +static void +yb_support_yb_specific_replica_identity(bool support_yb_specific_replica_identity) +{ + /* NOOP. */ +} diff --git a/contrib/unaccent/generate_unaccent_rules.py b/contrib/unaccent/generate_unaccent_rules.py index c405e231b39a6..6cd186fadee5b 100644 --- a/contrib/unaccent/generate_unaccent_rules.py +++ b/contrib/unaccent/generate_unaccent_rules.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # This script builds unaccent.rules on standard output when given the @@ -32,6 +32,9 @@ import sys import xml.etree.ElementTree as ET +yb_original_stdout = sys.stdout + +# yb_original_stdout = sys.stdout sys.stdout = codecs.getwriter('utf8')(sys.stdout.buffer) # The ranges of Unicode characters that we consider to be "plain letters". @@ -284,3 +287,9 @@ def main(args): sys.exit(1) main(args) + +# Doctest throws the following error unless sys.stdout is restored: +# AttributeError: '_io.BufferedWriter' object has no attribute 'encoding' +# original commit: https://github.com/yugabyte/yugabyte-db/commit/7526c698410dc7ba1c21ce0041357eb4444e5abb + +sys.stdout = yb_original_stdout diff --git a/contrib/uuid-ossp/yb_schedule b/contrib/uuid-ossp/yb_schedule new file mode 100644 index 0000000000000..c27d6a6b0835c --- /dev/null +++ b/contrib/uuid-ossp/yb_schedule @@ -0,0 +1,5 @@ +# contrib/uuid-ossp/yb_schedule + +# Since the upstream postgres test works fine without modifications +# (currently), don't bother porting it to yb_pg_uuid_ossp. +test: uuid_ossp diff --git a/doc/bug.template b/doc/bug.template new file mode 100644 index 0000000000000..843624818aa6b --- /dev/null +++ b/doc/bug.template @@ -0,0 +1,53 @@ +If PostgreSQL failed to compile on your computer or you found a bug, +please fill out this form and e-mail it to pgsql-bugs@postgresql.org. + +If your bug report has security implications and you'd prefer that it not +become immediately visible in public archives, don't send it to pgsql-bugs. +Security issues can be reported privately to security@postgresql.org. + +If you not only found the problem but solved it and generated a patch +then e-mail it to pgsql-hackers@postgresql.org instead. Please use the +command "diff -c" to generate the patch. + +You may also enter a bug report at https://www.postgresql.org/ instead of +e-mailing this form. + +============================================================================ + POSTGRESQL BUG REPORT TEMPLATE +============================================================================ + + +Your name : +Your email address : + + +System Configuration: +--------------------- + Architecture (example: Intel Pentium) : + + Operating System (example: Linux 2.4.18) : + + PostgreSQL version (example: PostgreSQL 11.2): PostgreSQL 11.2 + + Compiler used (example: gcc 3.3.5) : + + +Please enter a FULL description of your problem: +------------------------------------------------ + + + + + +Please describe a way to repeat the problem. Please try to provide a +concise reproducible example, if at all possible: +---------------------------------------------------------------------- + + + + + +If you know how this problem might be fixed, list the solution below: +--------------------------------------------------------------------- + + diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index e90a0e1f83725..61f6336f6f63d 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -79,6 +79,7 @@ Complete list of usable sgml source files in this directory. + @@ -127,6 +128,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml index 922686e5ced2a..b83097d53b5c1 100644 --- a/doc/src/sgml/ref/alter_role.sgml +++ b/doc/src/sgml/ref/alter_role.sgml @@ -35,6 +35,8 @@ ALTER ROLE role_specification [ WIT | CONNECTION LIMIT connlimit | [ ENCRYPTED ] PASSWORD 'password' | PASSWORD NULL | VALID UNTIL 'timestamp' + | PROFILE profile_name | NOPROFILE + | ACCOUNT LOCK | ACCOUNT UNLOCK ALTER ROLE name RENAME TO new_name diff --git a/doc/src/sgml/ref/alter_tablegroup.sgml b/doc/src/sgml/ref/alter_tablegroup.sgml new file mode 100644 index 0000000000000..8932269ed48e7 --- /dev/null +++ b/doc/src/sgml/ref/alter_tablegroup.sgml @@ -0,0 +1,114 @@ + + + + + ALTER TABLEGROUP + + + + ALTER TABLEGROUP + 7 + SQL - Language Statements + + + + ALTER TABLEGROUP + change the definition of a tablegroup + + + + +ALTER TABLEGROUP name RENAME TO new_name +ALTER TABLEGROUP name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } + + + + + Description + + + ALTER TABLEGROUP can be used to change the definition of + a tablegroup. + + + + You must own the tablegroup to change the definition of a tablegroup. + To alter the owner, you must also be a direct or indirect member of the new + owning role. + (Note that superusers have these privileges automatically.) + + + + + + Parameters + + + + name + + + The name of an existing tablegroup. + + + + + + new_name + + + The new name of the tablegroup. + + + + + + new_owner + + + The new owner of the tablegroup. + + + + + + + + Examples + + + Rename tablegroup tbl_grp to tgroup: + + ALTER TABLEGROUP tbl_grp RENAME TO tgroup; + + + + + Change the owner of tablegroup tbl_grp: + + ALTER TABLEGROUP tbl_grp OWNER TO mary; + + + + + + Compatibility + + + There is no ALTER TABLEGROUP statement in + the SQL standard. + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/ref/alter_user.sgml b/doc/src/sgml/ref/alter_user.sgml index 0ee89f54c5ce0..0592d29872489 100644 --- a/doc/src/sgml/ref/alter_user.sgml +++ b/doc/src/sgml/ref/alter_user.sgml @@ -35,6 +35,8 @@ ALTER USER role_specification [ WIT | CONNECTION LIMIT connlimit | [ ENCRYPTED ] PASSWORD 'password' | PASSWORD NULL | VALID UNTIL 'timestamp' + | PROFILE profile_name | NOPROFILE + | ACCOUNT LOCK | ACCOUNT UNLOCK ALTER USER name RENAME TO new_name diff --git a/doc/src/sgml/ref/create_profile.sgml b/doc/src/sgml/ref/create_profile.sgml new file mode 100644 index 0000000000000..8dce332929aed --- /dev/null +++ b/doc/src/sgml/ref/create_profile.sgml @@ -0,0 +1,31 @@ + + + + + CREATE PROFILE + + + + CREATE PROFILE + 7 + SQL - Language Statements + + + + CREATE PROFILE + define a new login profile + + + + + +CREATE PROFILE profile_name LIMIT + FAILED_LOGIN_ATTEMPTS number; + [PASSWORD_LOCK_TIME days]; + + + + \ No newline at end of file diff --git a/doc/src/sgml/ref/create_tablegroup.sgml b/doc/src/sgml/ref/create_tablegroup.sgml new file mode 100644 index 0000000000000..a4e03b14ee44b --- /dev/null +++ b/doc/src/sgml/ref/create_tablegroup.sgml @@ -0,0 +1,107 @@ + + + + + CREATE TABLEGROUP + + + + CREATE TABLEGROUP + 7 + SQL - Language Statements + + + + CREATE TABLEGROUP + define a new tablegroup + + + + + CREATE TABLEGROUP tablegroup_name + + + + + Description + + + CREATE TABLEGROUP registers a new cluster-wide + tablegroup. The tablegroup name must be distinct from the name of any + existing tablegroup in the database cluster. + + + + A tablegroup is a group of tables that will be colocated together on a + single colocation tablet. + + + + + + Parameters + + + + tablegroup_name + + + The name of a tablegroup to be created. + + + + + + + + Notes + + + Tablegroups cannot be mixed with colocated databases or with + COLOCATED = true / false as part of the + CREATE TABLE DDL, to avoid confusion between colocated + DB and tablegroups. + + + + + Examples + + + Create tablegrouptbl_grp: + + CREATE TABLEGROUP tbl_grp; + + + + + + Create tablegroup tbl_grp2 with owner mary: + + CREATE TABLEGROUP tbl_grp2 OWNER mary; + + + + + + + Compatibility + + + There is no CREATE TABLEGROUP statement in + the SQL standard. + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/ref/drop_profile.sgml b/doc/src/sgml/ref/drop_profile.sgml new file mode 100644 index 0000000000000..3dab18bdb7291 --- /dev/null +++ b/doc/src/sgml/ref/drop_profile.sgml @@ -0,0 +1,29 @@ + + + + + DROP PROFILE + + + + DROP PROFILE + 7 + SQL - Language Statements + + + + DROP PROFILE + remove a login profile + + + + + +DROP PROFILE profile_name; + + + + \ No newline at end of file diff --git a/doc/src/sgml/ref/drop_tablegroup.sgml b/doc/src/sgml/ref/drop_tablegroup.sgml new file mode 100644 index 0000000000000..87c47a39ac8ec --- /dev/null +++ b/doc/src/sgml/ref/drop_tablegroup.sgml @@ -0,0 +1,85 @@ + + + + + DROP TABLEGROUP + + + + DROP TABLEGROUP + 7 + SQL - Language Statements + + + + DROP TABLEGROUP + remove a tablegroup + + + + + DROP TABLEGROUP name + + + + + Description + + + DROP TABLEGROUP removes a tablegroup from the system. + + + + A tablegroup can only be dropped by its owner or a superuser. + The tablegroup must be empty of all database objects before it can be + dropped. + + + + + Parameters + + + + name + + + The name of a tablegroup. + + + + + + + + Examples + + + To remove tablegroup tblgrp from the system: + + DROP TABLEGROUP tblgrp; + + + + + + Compatibility + + + There is no DROP TABLEGROUP statement in + the SQL standard. + + + + + See Also + + + + + + + diff --git a/src/Makefile b/src/Makefile index 94649c36c7a46..05f84dc23a4fa 100644 --- a/src/Makefile +++ b/src/Makefile @@ -12,6 +12,9 @@ subdir = src top_builddir = .. include Makefile.global +# YB: TODO(#25892): YB currently has a single way of building postgres, so any +# non-default subdirs we want to build are added here. YB should be more +# flexible and be able to build subdirs at will. SUBDIRS = \ common \ port \ diff --git a/src/Makefile.global.in b/src/Makefile.global.in index eeefc73b9f719..e17076395043e 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -242,7 +242,11 @@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ PG_SYSROOT = @PG_SYSROOT@ -override CPPFLAGS := $(ICU_CFLAGS) $(CPPFLAGS) +ifndef YB_SRC_ROOT +$(error YB_SRC_ROOT is not set) +endif + +override CPPFLAGS := $(ICU_CFLAGS) $(CPPFLAGS) -I$(YB_SRC_ROOT)/src ifdef PGXS override CPPFLAGS := -I$(includedir_server) -I$(includedir_internal) $(CPPFLAGS) @@ -270,6 +274,8 @@ LLVM_CPPFLAGS = @LLVM_CPPFLAGS@ LLVM_CFLAGS = @LLVM_CFLAGS@ LLVM_CXXFLAGS = @LLVM_CXXFLAGS@ +YB_CCLD = @CXX@ + # Kind-of compilers BISON = @BISON@ @@ -312,6 +318,18 @@ ifdef PGXS else LDFLAGS_INTERNAL = -L$(top_builddir)/src/port -L$(top_builddir)/src/common endif + +# YugaByte-specific change: we want rpath for postgres libs to go before rpath from external +# LDFLAGS. + +# Set up rpath if enabled. By default it will point to our libdir, +# but individual Makefiles can force other rpath paths if needed. +rpathdir = $(libdir) + +ifeq ($(enable_rpath), yes) +LDFLAGS_INTERNAL += $(rpath) +endif + LDFLAGS = $(LDFLAGS_INTERNAL) @LDFLAGS@ LDFLAGS_EX = @LDFLAGS_EX@ @@ -455,6 +473,7 @@ cd $(srcdir) && \ TESTDIR='$(CURDIR)' PATH="$(bindir):$(CURDIR):$$PATH" \ PGPORT='6$(DEF_PGPORT)' top_builddir='$(CURDIR)/$(top_builddir)' \ PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' \ + REGRESS_SHLIB='$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)' \ $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl) endef else # PGXS case @@ -466,6 +485,7 @@ cd $(srcdir) && \ TESTDIR='$(CURDIR)' PATH="$(bindir):$(CURDIR):$$PATH" \ PGPORT='6$(DEF_PGPORT)' \ PG_REGRESS='$(top_builddir)/src/test/regress/pg_regress' \ + REGRESS_SHLIB='$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)' \ $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl) endef endif # PGXS @@ -554,13 +574,7 @@ DLSUFFIX = @DLSUFFIX@ # Pull in platform-specific magic include $(top_builddir)/src/Makefile.port -# Set up rpath if enabled. By default it will point to our libdir, -# but individual Makefiles can force other rpath paths if needed. -rpathdir = $(libdir) - -ifeq ($(enable_rpath), yes) -LDFLAGS += $(rpath) -endif +# YugaByte-specific change: moved rpath setup to happen before @LDFLAGS@ appending. # Show the DLSUFFIX to build scripts (e.g. buildfarm) .PHONY: show_dl_suffix @@ -816,7 +830,7 @@ endif # since we put -l switches into LDFLAGS and those are order-sensitive. # In addition, include CFLAGS and LDFLAGS_EX per project conventions. %: %.o - $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) + $(YB_CCLD) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) ifndef PGXS @@ -825,7 +839,9 @@ ifndef PGXS # write `include $(top_builddir)/src/Makefile.global', not some # shortcut thereof. $(top_builddir)/src/Makefile.global: $(top_srcdir)/src/Makefile.global.in $(top_builddir)/config.status - cd $(top_builddir) && ./config.status src/Makefile.global + if test -z "$YB_PG_SKIP_CONFIG_STATUS"; then \ + cd $(top_builddir) && ./config.status src/Makefile.global; \ + fi # Remake pg_config.h from pg_config.h.in if the latter changed. # config.status will not change the timestamp on pg_config.h if it @@ -840,25 +856,33 @@ $(top_builddir)/src/Makefile.global: $(top_srcdir)/src/Makefile.global.in $(top_ $(top_builddir)/src/include/pg_config.h: $(top_builddir)/src/include/stamp-h ; $(top_builddir)/src/include/stamp-h: $(top_srcdir)/src/include/pg_config.h.in $(top_builddir)/config.status - cd $(top_builddir) && ./config.status src/include/pg_config.h + if test -z "$YB_PG_SKIP_CONFIG_STATUS"; then \ + cd $(top_builddir) && ./config.status src/include/pg_config.h; \ + fi # Also remake pg_config_ext.h from pg_config_ext.h.in, same logic as above. $(top_builddir)/src/include/pg_config_ext.h: $(top_builddir)/src/include/stamp-ext-h ; $(top_builddir)/src/include/stamp-ext-h: $(top_srcdir)/src/include/pg_config_ext.h.in $(top_builddir)/config.status - cd $(top_builddir) && ./config.status src/include/pg_config_ext.h + if test -z "$YB_PG_SKIP_CONFIG_STATUS"; then \ + cd $(top_builddir) && ./config.status src/include/pg_config_ext.h; \ + fi # Also remake ecpg_config.h from ecpg_config.h.in if the latter changed, same # logic as above. $(top_builddir)/src/interfaces/ecpg/include/ecpg_config.h: $(top_builddir)/src/interfaces/ecpg/include/stamp-h ; $(top_builddir)/src/interfaces/ecpg/include/stamp-h: $(top_builddir)/src/interfaces/ecpg/include/ecpg_config.h.in $(top_builddir)/config.status - cd $(top_builddir) && ./config.status src/interfaces/ecpg/include/ecpg_config.h + if test -z "$YB_PG_SKIP_CONFIG_STATUS"; then \ + cd $(top_builddir) && ./config.status src/interfaces/ecpg/include/ecpg_config.h; \ + fi # When configure changes, rerun configure with the same options as # last time. To change configure, you need to run autoconf manually. $(top_builddir)/config.status: $(top_srcdir)/configure - cd $(top_builddir) && ./config.status --recheck + if test -z "$YB_PG_SKIP_CONFIG_STATUS"; then \ + cd $(top_builddir) && ./config.status --recheck; \ + fi endif # not PGXS diff --git a/src/Makefile_ybpostgres.shlib b/src/Makefile_ybpostgres.shlib new file mode 100644 index 0000000000000..6025e967b8778 --- /dev/null +++ b/src/Makefile_ybpostgres.shlib @@ -0,0 +1,540 @@ +#------------------------------------------------------------------------- +# +# Makefile_ybpostgres.shlib +# +# Yugabyte-specific common rules for building shared libraries +# This file is copied from the Makefile.shlib file in the same +# directory, with minor modifications for Yugabyte-specific +# build tasks. +# +# Currently it is used to build the postgres shared library used +# in DocDB to evaluate Postgres/YSQL expressions. +# Current changes: +# - replace OBJS with 'call expand_subsys,$(OBJS)' for +# compatibility with common.mk +# +# Copyright (c) 1998, Regents of the University of California +# +# IDENTIFICATION +# src/Makefile_ybpostgres.shlib +# +#------------------------------------------------------------------------- + +# This file should be included by any Postgres module Makefile that +# wants to build a shared library (if possible for the current +# platform). A static library is also built from the same object +# files. Only one library can be built per makefile. +# +# Before including this file, the module Makefile must define these +# variables: +# +# NAME Name of library to build (no suffix nor "lib" prefix) +# SHLIB_LINK Stuff to append to library's link command +# (typically, -L and -l switches for external libraries) +# SHLIB_LINK_INTERNAL -L and -l switches for Postgres-supplied libraries +# SHLIB_PREREQS Order-only prerequisites for library build target +# SHLIB_EXPORTS (optional) Name of file containing list of symbols to +# export, in the format "function_name number" +# +# Don't use SHLIB_LINK for references to files in the build tree, or the +# wrong things will happen --- use SHLIB_LINK_INTERNAL for those! +# +# When building a shared library, the following version information +# must also be set. It should be omitted when building a dynamically +# loadable module. +# +# YB_SO_MAJOR_VERSION Major version number to use for shared library +# YB_SO_MINOR_VERSION Minor version number to use for shared library +# (If you want a patchlevel, include it in YB_SO_MINOR_VERSION, e.g., "6.2".) +# +# The module Makefile must also include +# $(top_builddir)/src/Makefile.global before including this file. +# (Makefile.global sets PORTNAME and other needed symbols.) +# +# This makefile provides the following (phony) targets: +# +# all-lib build the static and shared (if applicable) libraries +# install-lib install the libraries into $(libdir) +# installdirs-lib create installation directory $(libdir) +# uninstall-lib remove the libraries from $(libdir) +# clean-lib delete the static and shared libraries from the build dir +# maintainer-clean-lib delete .def files built for win32 +# +# Typically you would add `all-lib' to the `all' target so that `make all' +# builds the libraries. In the most simple case it would look like this: +# +# all: all-lib +# +# Similarly, the install rule might look like +# +# install: install-lib +# +# plus any additional things you want to install. Et cetera. +# +# Got that? Look at src/interfaces/libpq/Makefile for an example. +# + + +COMPILER = $(CC) $(CFLAGS) +LINK.static = $(AR) $(AROPT) + +LDFLAGS_INTERNAL += $(SHLIB_LINK_INTERNAL) + + + +ifdef YB_SO_MAJOR_VERSION +# Default library naming convention used by the majority of platforms +shlib = lib$(NAME)$(DLSUFFIX).$(YB_SO_MAJOR_VERSION).$(YB_SO_MINOR_VERSION) +shlib_major = lib$(NAME)$(DLSUFFIX).$(YB_SO_MAJOR_VERSION) +shlib_bare = lib$(NAME)$(DLSUFFIX) +# Testing the soname variable is a reliable way to determine whether a +# linkable library is being built. +soname = $(shlib_major) +pkgconfigdir = $(libdir)/pkgconfig +else +# Naming convention for dynamically loadable modules +shlib = $(NAME)$(DLSUFFIX) +endif +stlib = lib$(NAME).a + +ifndef soname +# additional flags for backend modules +SHLIB_LINK += $(BE_DLLLIBS) +endif + +# For each platform we support shared libraries on, set shlib to the +# name of the library (if default above is not right), set +# LINK.shared to the command to link the library, +# and adjust SHLIB_LINK if necessary. + +# Try to keep the sections in some kind of order, folks... + +override CFLAGS += $(CFLAGS_SL) +override CXXFLAGS += $(CFLAGS_SL) +ifdef YB_SO_MAJOR_VERSION +# libraries ought to use this to refer to versioned gettext domain names +override CPPFLAGS += -DYB_SO_MAJOR_VERSION=$(YB_SO_MAJOR_VERSION) +endif + +ifeq ($(PORTNAME), aix) + ifdef YB_SO_MAJOR_VERSION + shlib = lib$(NAME)$(DLSUFFIX).$(YB_SO_MAJOR_VERSION) + endif + haslibarule = yes + # $(exports_file) is also usable as an import file + exports_file = lib$(NAME).exp +endif + +ifeq ($(PORTNAME), darwin) + ifdef soname + # linkable library + DLSUFFIX = .dylib + ifneq ($(YB_SO_MAJOR_VERSION), 0) + version_link = -compatibility_version $(YB_SO_MAJOR_VERSION) -current_version $(YB_SO_MAJOR_VERSION).$(YB_SO_MINOR_VERSION) + endif + LINK.shared = $(COMPILER) -dynamiclib -install_name '$(libdir)/lib$(NAME).$(YB_SO_MAJOR_VERSION)$(DLSUFFIX)' $(version_link) $(exported_symbols_list) -multiply_defined suppress + shlib = lib$(NAME).$(YB_SO_MAJOR_VERSION).$(YB_SO_MINOR_VERSION)$(DLSUFFIX) + shlib_major = lib$(NAME).$(YB_SO_MAJOR_VERSION)$(DLSUFFIX) + else + # loadable module + DLSUFFIX = .so + LINK.shared = $(COMPILER) -bundle -multiply_defined suppress + endif + BUILD.exports = $(AWK) '/^[^\#]/ {printf "_%s\n",$$1}' $< >$@ + exports_file = $(SHLIB_EXPORTS:%.txt=%.list) + ifneq (,$(exports_file)) + exported_symbols_list = -exported_symbols_list $(exports_file) + endif +endif + +ifeq ($(PORTNAME), openbsd) + ifdef ELF_SYSTEM + LINK.shared = $(COMPILER) -shared + ifdef soname + LINK.shared += -Wl,-x,-soname,$(soname) + endif + BUILD.exports = ( echo '{ global:'; $(AWK) '/^[^\#]/ {printf "%s;\n",$$1}' $<; echo ' local: *; };' ) >$@ + exports_file = $(SHLIB_EXPORTS:%.txt=%.list) + ifneq (,$(exports_file)) + LINK.shared += -Wl,--version-script=$(exports_file) + endif + SHLIB_LINK += -lc + else + LINK.shared = $(LD) -x -Bshareable -Bforcearchive + endif +endif + +ifeq ($(PORTNAME), freebsd) + ifdef ELF_SYSTEM + ifdef YB_SO_MAJOR_VERSION + shlib = lib$(NAME)$(DLSUFFIX).$(YB_SO_MAJOR_VERSION) + endif + LINK.shared = $(COMPILER) -shared + ifdef soname + LINK.shared += -Wl,-x,-soname,$(soname) + endif + BUILD.exports = ( echo '{ global:'; $(AWK) '/^[^\#]/ {printf "%s;\n",$$1}' $<; echo ' local: *; };' ) >$@ + exports_file = $(SHLIB_EXPORTS:%.txt=%.list) + ifneq (,$(exports_file)) + LINK.shared += -Wl,--version-script=$(exports_file) + endif + else + ifdef YB_SO_MAJOR_VERSION + shlib = lib$(NAME)$(DLSUFFIX).$(YB_SO_MAJOR_VERSION).$(YB_SO_MINOR_VERSION) + endif + LINK.shared = $(LD) -x -Bshareable -Bforcearchive + endif +endif + +ifeq ($(PORTNAME), netbsd) + ifdef ELF_SYSTEM + LINK.shared = $(COMPILER) -shared + ifdef soname + LINK.shared += -Wl,-x,-soname,$(soname) + endif + BUILD.exports = ( echo '{ global:'; $(AWK) '/^[^\#]/ {printf "%s;\n",$$1}' $<; echo ' local: *; };' ) >$@ + exports_file = $(SHLIB_EXPORTS:%.txt=%.list) + ifneq (,$(exports_file)) + LINK.shared += -Wl,--version-script=$(exports_file) + endif + else + LINK.shared = $(LD) -x -Bshareable -Bforcearchive + endif +endif + +ifeq ($(PORTNAME), hpux) + ifdef YB_SO_MAJOR_VERSION + shlib = lib$(NAME)$(DLSUFFIX).$(YB_SO_MAJOR_VERSION) + endif + ifeq ($(with_gnu_ld), yes) + LINK.shared = $(CC) -shared -Wl,-Bsymbolic + ifdef soname + LINK.shared += -Wl,-h -Wl,$(soname) + endif + else + LINK.shared = $(LD) -b -Bsymbolic + ifdef soname + LINK.shared += +h $(soname) + endif + # can't use the CC-syntax rpath pattern here, so instead: + rpath = + ifeq ($(enable_rpath), yes) + LINK.shared += +s +b '$(rpathdir)' + endif + # On HPUX platforms, gcc is usually configured to search for libraries + # in /usr/local/lib, but ld won't do so. Add an explicit -L switch so + # ld can find the same libraries gcc does. Make sure it goes after any + # -L switches provided explicitly. + ifeq ($(GCC), yes) + SHLIB_LINK += -L/usr/local/lib + endif + endif + # And we need to link with libgcc, too + ifeq ($(GCC), yes) + SHLIB_LINK += `$(CC) $(LDFLAGS) -print-libgcc-file-name` + endif +endif + +ifeq ($(PORTNAME), linux) + LINK.shared = $(COMPILER) -shared + ifdef soname + LINK.shared += -Wl,-soname,$(soname) + endif + BUILD.exports = ( echo '{ global:'; $(AWK) '/^[^\#]/ {printf "%s;\n",$$1}' $<; echo ' local: *; };' ) >$@ + exports_file = $(SHLIB_EXPORTS:%.txt=%.list) + ifneq (,$(exports_file)) + LINK.shared += -Wl,--version-script=$(exports_file) + endif +endif + +ifeq ($(PORTNAME), solaris) + ifeq ($(GCC), yes) + LINK.shared = $(COMPILER) -shared -Wl,-Bsymbolic + else + LINK.shared = $(COMPILER) -G -Bsymbolic + endif + ifdef soname + ifeq ($(with_gnu_ld), yes) + LINK.shared += -Wl,-soname,$(soname) + else + LINK.shared += -h $(soname) + endif + endif +endif + +ifeq ($(PORTNAME), cygwin) + LINK.shared = $(CC) -shared + ifdef YB_SO_MAJOR_VERSION + shlib = cyg$(NAME)$(DLSUFFIX) + endif + haslibarule = yes +endif + +ifeq ($(PORTNAME), win32) + ifdef YB_SO_MAJOR_VERSION + shlib = lib$(NAME)$(DLSUFFIX) + endif + haslibarule = yes +endif + + + +## +## BUILD +## + +.PHONY: all-lib all-static-lib all-shared-lib + +all-lib: all-shared-lib +ifdef soname +# no static library when building a dynamically loadable module +all-lib: all-static-lib +all-lib: lib$(NAME).pc +endif + +all-static-lib: $(stlib) + +all-shared-lib: $(shlib) + +# In this rule, "touch $@" works around a problem on some platforms wherein +# ranlib updates the library file's mod time with a value calculated to +# seconds precision. If the filesystem has sub-second timestamps, this can +# cause the library file to appear older than its input files, triggering +# parallel-make problems. +ifndef haslibarule +$(stlib): $(call expand_subsys,$(OBJS)) | $(SHLIB_PREREQS) + rm -f $@ + $(LINK.static) $@ $^ + $(RANLIB) $@ + touch $@ +endif #haslibarule + + +ifeq (,$(filter cygwin win32,$(PORTNAME))) +ifneq ($(PORTNAME), aix) + +# Normal case +$(shlib): $(call expand_subsys,$(OBJS)) | $(SHLIB_PREREQS) + $(LINK.shared) -o $@ $(call expand_subsys,$(OBJS)) $(LDFLAGS) $(LDFLAGS_SL) $(SHLIB_LINK) +ifdef shlib_major +# If we're using major and minor versions, then make a symlink to major-version-only. +ifneq ($(shlib), $(shlib_major)) + rm -f $(shlib_major) + $(LN_S) $(shlib) $(shlib_major) +endif +# Make sure we have a link to a name without any version numbers +ifneq ($(shlib), $(shlib_bare)) + rm -f $(shlib_bare) + $(LN_S) $(shlib) $(shlib_bare) +endif +endif # shlib_major + +# Where possible, restrict the symbols exported by the library to just the +# official list, so as to avoid unintentional ABI changes. On recent macOS +# this also quiets multiply-defined-symbol warnings in programs that use +# libpgport along with libpq. +ifneq (,$(SHLIB_EXPORTS)) +ifdef BUILD.exports +$(shlib): $(SHLIB_EXPORTS:%.txt=%.list) + +$(SHLIB_EXPORTS:%.txt=%.list): %.list: %.txt + $(BUILD.exports) +endif +endif + +else # PORTNAME == aix + +# AIX case + +# See notes in src/backend/parser/Makefile about the following two rules +$(stlib): $(shlib) + touch $@ + +$(shlib): $(call expand_subsys,$(OBJS)) | $(SHLIB_PREREQS) + rm -f $(stlib) + $(LINK.static) $(stlib) $^ + $(RANLIB) $(stlib) + $(MKLDEXPORT) $(stlib) $(shlib) >$(exports_file) + $(COMPILER) -o $(shlib) $(stlib) -Wl,-bE:$(exports_file) $(LDFLAGS) $(LDFLAGS_SL) $(SHLIB_LINK) + rm -f $(stlib) + $(AR) $(AROPT) $(stlib) $(shlib) + +endif # PORTNAME == aix + +else # PORTNAME == cygwin || PORTNAME == win32 + +ifeq ($(PORTNAME), cygwin) + +# Cygwin case + +$(shlib): $(call expand_subsys,$(OBJS)) | $(SHLIB_PREREQS) + $(CC) $(CFLAGS) -shared -o $@ $(call expand_subsys,$(OBJS)) $(LDFLAGS) $(LDFLAGS_SL) $(SHLIB_LINK) $(LIBS) $(LDAP_LIBS_BE) + +$(stlib): $(call expand_subsys,$(OBJS)) | $(SHLIB_PREREQS) + rm -f $@ + $(LINK.static) $@ $^ + $(RANLIB) $@ + +else + +# Win32 case + +# See notes in src/backend/parser/Makefile about the following two rules +$(stlib): $(shlib) + touch $@ + +# XXX A backend that loads a module linked with libgcc_s_dw2-1.dll will exit +# uncleanly, hence -static-libgcc. (Last verified with MinGW-w64 compilers +# from i686-4.9.1-release-win32-dwarf-rt_v3-rev1.) Shared libgcc has better +# support for C++/Java exceptions; while core PostgreSQL does not use them, it +# would be nice to support shared libgcc for the benefit of extensions. +# +# If SHLIB_EXPORTS is set, the rules below will build a .def file from that. +# Else we just use --export-all-symbols. +ifeq (,$(SHLIB_EXPORTS)) +$(shlib): $(call expand_subsys,$(OBJS)) | $(SHLIB_PREREQS) + $(CC) $(CFLAGS) -shared -static-libgcc -o $@ $(call expand_subsys,$(OBJS)) $(LDFLAGS) $(LDFLAGS_SL) $(SHLIB_LINK) $(LIBS) -Wl,--export-all-symbols -Wl,--out-implib=$(stlib) +else +DLL_DEFFILE = lib$(NAME)dll.def + +# It is important to not include $(LIBS) here, because it may contain libtcmalloc.a, which we are +# not allowed to link into a shared library. All other libraries should already be included in +# $(SHLIB_LINK). +$(shlib): $(call expand_subsys,$(OBJS)) $(DLL_DEFFILE) | $(SHLIB_PREREQS) + $(CC) $(CFLAGS) -shared -static-libgcc -o $@ $(call expand_subsys,$(OBJS)) $(DLL_DEFFILE) $(LDFLAGS) $(LDFLAGS_SL) $(SHLIB_LINK) -Wl,--out-implib=$(stlib) +endif + +endif # PORTNAME == cygwin +endif # PORTNAME == cygwin || PORTNAME == win32 + + +%.pc: $(MAKEFILE_LIST) + echo 'Name: lib$(NAME)' >$@ + echo 'Description: PostgreSQL lib$(NAME) library' >>$@ + echo 'Url: http://www.postgresql.org/' >>$@ + echo 'Version: $(VERSION)' >>$@ + echo 'Requires: ' >>$@ + echo 'Requires.private: $(PKG_CONFIG_REQUIRES_PRIVATE)' >>$@ + echo 'Cflags: -I$(includedir)' >>$@ + echo 'Libs: -L$(libdir) -l$(NAME)' >>$@ +# Record -L flags that the user might have passed in to the PostgreSQL +# build to locate third-party libraries (e.g., ldap, ssl). Filter out +# those that point inside the build or source tree. Use sort to +# remove duplicates. Also record the -l flags necessary for static +# linking, but not those already covered by Requires.private. + echo 'Libs.private: $(sort $(filter-out -L.% -L$(top_srcdir)/%,$(filter -L%,$(LDFLAGS) $(SHLIB_LINK)))) $(filter-out $(PKG_CONFIG_REQUIRES_PRIVATE:lib%=-l%),$(filter -l%,$(SHLIB_LINK)))' >>$@ + + +# We need several not-quite-identical variants of .DEF files to build +# DLLs for Windows. These are made from the single source file +# exports.txt. Since we can't assume that Windows boxes will have +# sed, the .DEF files are always built and included in distribution +# tarballs. + +ifneq (,$(SHLIB_EXPORTS)) +distprep: lib$(NAME)dll.def lib$(NAME)ddll.def + +UC_NAME = $(shell echo $(NAME) | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') + +lib$(NAME)dll.def: $(SHLIB_EXPORTS) + echo '; DEF file for Makefile.shlib (MinGW)' >$@ + echo 'LIBRARY LIB$(UC_NAME).dll' >>$@ + echo 'EXPORTS' >>$@ + sed -e '/^#/d' -e 's/^\(.*[ ]\)\([0-9][0-9]*\)/ \1@ \2/' $< >>$@ + +lib$(NAME)ddll.def: $(SHLIB_EXPORTS) + echo '; DEF file for Makefile.shlib (MinGW)' >$@ + echo 'LIBRARY LIB$(UC_NAME)D.dll' >>$@ + echo 'EXPORTS' >>$@ + sed -e '/^#/d' -e 's/^\(.*[ ]\)\([0-9][0-9]*\)/ \1@ \2/' $< >>$@ + +endif # SHLIB_EXPORTS + + +## +## INSTALL +## + +.PHONY: install-lib install-lib-static install-lib-shared installdirs-lib +install-lib: install-lib-shared +ifdef soname +install-lib: install-lib-static +install-lib: install-lib-pc +endif + +install-lib-pc: lib$(NAME).pc installdirs-lib + $(INSTALL_DATA) $< '$(DESTDIR)$(pkgconfigdir)/lib$(NAME).pc' + +install-lib-static: $(stlib) installdirs-lib + $(INSTALL_STLIB) $< '$(DESTDIR)$(libdir)/$(stlib)' +ifeq ($(PORTNAME), darwin) + cd '$(DESTDIR)$(libdir)' && \ + $(RANLIB) $(stlib) +endif + +install-lib-shared: $(shlib) installdirs-lib +ifdef soname +# we don't install $(shlib) on AIX +# (see http://archives.postgresql.org/message-id/52EF20B2E3209443BC37736D00C3C1380A6E79FE@EXADV1.host.magwien.gv.at) +ifneq ($(PORTNAME), aix) + $(INSTALL_SHLIB) $< '$(DESTDIR)$(libdir)/$(shlib)' +ifneq ($(PORTNAME), cygwin) +ifneq ($(PORTNAME), win32) +ifneq ($(shlib), $(shlib_major)) + cd '$(DESTDIR)$(libdir)' && \ + rm -f $(shlib_major) && \ + $(LN_S) $(shlib) $(shlib_major) +endif +ifneq ($(shlib), $(shlib_bare)) + cd '$(DESTDIR)$(libdir)' && \ + rm -f $(shlib_bare) && \ + $(LN_S) $(shlib) $(shlib_bare) +endif +endif # not win32 +endif # not cygwin +endif # not aix +ifneq (,$(findstring $(PORTNAME),win32 cygwin)) + $(INSTALL_SHLIB) $< '$(DESTDIR)$(bindir)/$(shlib)' +endif +else # no soname + $(INSTALL_SHLIB) $< '$(DESTDIR)$(pkglibdir)/$(shlib)' +endif + + +installdirs-lib: +ifdef soname + $(MKDIR_P) '$(DESTDIR)$(libdir)' '$(DESTDIR)$(pkgconfigdir)' $(if $(findstring $(PORTNAME),win32 cygwin),'$(DESTDIR)$(bindir)') +else + $(MKDIR_P) '$(DESTDIR)$(pkglibdir)' +endif + + +## +## UNINSTALL +## + +.PHONY: uninstall-lib +uninstall-lib: +ifdef soname + rm -f '$(DESTDIR)$(libdir)/$(stlib)' + rm -f '$(DESTDIR)$(libdir)/$(shlib_bare)' \ + '$(DESTDIR)$(libdir)/$(shlib_major)' \ + '$(DESTDIR)$(libdir)/$(shlib)' $(if $(findstring $(PORTNAME),win32 cygwin),'$(DESTDIR)$(bindir)/$(shlib)') \ + '$(DESTDIR)$(pkgconfigdir)/lib$(NAME).pc' +else # no soname + rm -f '$(DESTDIR)$(pkglibdir)/$(shlib)' +endif # no soname + + +## +## CLEAN +## + +.PHONY: clean-lib +clean-lib: + rm -f $(shlib) $(shlib_bare) $(shlib_major) $(stlib) $(exports_file) lib$(NAME).pc + +ifneq (,$(SHLIB_EXPORTS)) +maintainer-clean-lib: + rm -f lib$(NAME)dll.def lib$(NAME)ddll.def +endif diff --git a/src/backend/Makefile b/src/backend/Makefile index 0231841a0fa28..d4c78ac832395 100644 --- a/src/backend/Makefile +++ b/src/backend/Makefile @@ -22,7 +22,7 @@ SUBDIRS = access backup bootstrap catalog parser commands executor \ main nodes optimizer partitioning port postmaster \ regex replication rewrite \ statistics storage tcop tsearch utils $(top_builddir)/src/timezone \ - jit + jit ybgate include $(srcdir)/common.mk @@ -55,16 +55,53 @@ ifeq ($(with_systemd),yes) LIBS += -lsystemd endif +ifndef YB_BUILD_ROOT +$(error YB_BUILD_ROOT is not set) +endif +# Do not append $(YB_PG_EXE_LD_FLAGS_AFTER_YB_LIBS) here. We will append it +# later, after initializing SHLIB_LINK. +LIBS += -L$(YB_BUILD_ROOT)/lib -lyb_util -lyb_pggate -lyb_pggate_util + +# Shared library configuration (see Makefile_ybpostgres.shlib -- included below) +PGFILEDESC = "Postgres shared library" +NAME = yb_pgbackend +YB_SO_MAJOR_VERSION= 1 +YB_SO_MINOR_VERSION= $(MAJORVERSION) + +# Use := for immediate variable expansion. Further changes to $(LIBS) must not affect +# $(SHLIB_LINK). +SHLIB_LINK := $(LIBS) + +# YB_PG_EXE_LD_FLAGS_AFTER_YB_LIBS should only be used for linking executables. +LIBS += $(YB_PG_EXE_LD_FLAGS_AFTER_YB_LIBS) + +SHLIB_EXPORTS = exports.txt + +.NOTPARALLEL: + ########################################################################## -all: submake-libpgport submake-catalog-headers submake-utils-headers postgres $(POSTGRES_IMP) +all: submake-libpgport submake-catalog-headers submake-utils-headers postgres $(POSTGRES_IMP) all-lib + +exports.txt: exports-common.txt exports-test.txt + cat exports-common.txt > $@ +ifneq ($(YB_DO_NOT_BUILD_TESTS), 1) + cat exports-test.txt >> $@ +endif + +# Shared-library target all-lib. +# Depends on postgres build, and Makefile_ybpostgres.shlib will add the other +# deps (e.g. all-shared-lib and/or all-static-lib). +all-lib: postgres all-lib exports.txt +include $(top_srcdir)/src/Makefile_ybpostgres.shlib + ifneq ($(PORTNAME), cygwin) ifneq ($(PORTNAME), win32) ifneq ($(PORTNAME), aix) postgres: $(OBJS) - $(CC) $(CFLAGS) $(call expand_subsys,$^) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(LIBS) -o $@ + $(YB_CCLD) $(CFLAGS) $(call expand_subsys,$^) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(LIBS) -o $@ endif endif @@ -73,7 +110,7 @@ endif ifeq ($(PORTNAME), cygwin) postgres: $(OBJS) - $(CC) $(CFLAGS) $(call expand_subsys,$^) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) -Wl,--stack,$(WIN32_STACK_RLIMIT) -Wl,--export-all-symbols -Wl,--out-implib=libpostgres.a $(LIBS) -o $@ + $(YB_CCLD) $(CFLAGS) $(call expand_subsys,$^) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) -Wl,--stack,$(WIN32_STACK_RLIMIT) -Wl,--export-all-symbols -Wl,--out-implib=libpostgres.a $(LIBS) -o $@ # libpostgres.a is actually built in the preceding rule, but we need this to # ensure it's newer than postgres; see notes in src/backend/parser/Makefile @@ -86,7 +123,7 @@ ifeq ($(PORTNAME), win32) LIBS += -lsecur32 postgres: $(OBJS) $(WIN32RES) - $(CC) $(CFLAGS) $(call expand_subsys,$(OBJS)) $(WIN32RES) $(LDFLAGS) $(LDFLAGS_EX) -Wl,--stack=$(WIN32_STACK_RLIMIT) -Wl,--export-all-symbols -Wl,--out-implib=libpostgres.a $(LIBS) -o $@$(X) + $(YB_CCLD) $(CFLAGS) $(call expand_subsys,$(OBJS)) $(WIN32RES) $(LDFLAGS) $(LDFLAGS_EX) -Wl,--stack=$(WIN32_STACK_RLIMIT) -Wl,--export-all-symbols -Wl,--out-implib=libpostgres.a $(LIBS) -o $@$(X) # libpostgres.a is actually built in the preceding rule, but we need this to # ensure it's newer than postgres; see notes in src/backend/parser/Makefile @@ -98,7 +135,7 @@ endif # win32 ifeq ($(PORTNAME), aix) postgres: $(POSTGRES_IMP) - $(CC) $(CFLAGS) $(call expand_subsys,$(OBJS)) $(LDFLAGS) $(LDFLAGS_EX) -Wl,-bE:$(top_builddir)/src/backend/$(POSTGRES_IMP) $(LIBS) -Wl,-brtllib -o $@ + $(YB_CCLD) $(CFLAGS) $(call expand_subsys,$(OBJS)) $(LDFLAGS) $(LDFLAGS_EX) -Wl,-bE:$(top_builddir)/src/backend/$(POSTGRES_IMP) $(LIBS) -Wl,-brtllib -o $@ $(POSTGRES_IMP): $(OBJS) $(LD) $(LDREL) $(LDOUT) SUBSYS.o $(call expand_subsys,$^) @@ -195,7 +232,7 @@ distprep: ########################################################################## -install: all installdirs install-bin +install: all installdirs install-bin install-lib ifeq ($(PORTNAME), cygwin) ifeq ($(MAKE_DLL), true) $(INSTALL_DATA) libpostgres.a '$(DESTDIR)$(libdir)/libpostgres.a' diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile index 0880e0a8bbb63..68f16d02a8dd1 100644 --- a/src/backend/access/Makefile +++ b/src/backend/access/Makefile @@ -9,6 +9,6 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global SUBDIRS = brin common gin gist hash heap index nbtree rmgrdesc spgist \ - table tablesample transam + table tablesample transam yb_access ybgin include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 4d228a6cce697..cdfb1456dc8ef 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -40,6 +40,9 @@ #include "utils/memutils.h" #include "utils/rel.h" +/* YB includes */ +#include "utils/guc.h" + /* * We use a BrinBuildState during initial construction of a BRIN index. @@ -1431,7 +1434,8 @@ summarize_range(IndexInfo *indexInfo, BrinBuildState *state, Relation heapRel, state->bs_currRangeStart = heapBlk; table_index_build_range_scan(heapRel, state->bs_irel, indexInfo, false, true, false, heapBlk, scanNumBlks, - brinbuildCallback, (void *) state, NULL); + brinbuildCallback, (void *) state, NULL, + NULL /* bfinfo */ , NULL /* bfresult */ ); /* * Now we update the values obtained by the scan with the placeholder diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c index 896f82a22b48b..0da05f029e895 100644 --- a/src/backend/access/common/attmap.c +++ b/src/backend/access/common/attmap.c @@ -26,6 +26,9 @@ #include "access/htup_details.h" #include "utils/builtins.h" +/* YB includes */ +#include "pg_yb_utils.h" + static bool check_attrmap_match(TupleDesc indesc, TupleDesc outdesc, @@ -172,7 +175,8 @@ build_attrmap_by_position(TupleDesc indesc, */ AttrMap * build_attrmap_by_name(TupleDesc indesc, - TupleDesc outdesc) + TupleDesc outdesc, + bool yb_ignore_type_mismatch) { AttrMap *attrMap; int outnatts; @@ -223,7 +227,8 @@ build_attrmap_by_name(TupleDesc indesc, if (strcmp(attname, NameStr(inatt->attname)) == 0) { /* Found it, check type */ - if (atttypid != inatt->atttypid || atttypmod != inatt->atttypmod) + if ((atttypid != inatt->atttypid || atttypmod != inatt->atttypmod) && + !(IsYugaByteEnabled() && yb_ignore_type_mismatch)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("could not convert row type"), @@ -262,7 +267,8 @@ build_attrmap_by_name_if_req(TupleDesc indesc, AttrMap *attrMap; /* Verify compatibility and prepare attribute-number map */ - attrMap = build_attrmap_by_name(indesc, outdesc); + attrMap = build_attrmap_by_name(indesc, outdesc, + false /* yb_ignore_type_mismatch */ ); /* Check if the map has a one-to-one match */ if (check_attrmap_match(indesc, outdesc, attrMap)) diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index c1cbaab9ecbda..e590c9581ef0c 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -67,6 +67,10 @@ #include "utils/hsearch.h" #include "utils/memutils.h" +/* YB includes */ +#include "pg_yb_utils.h" +#include "utils/builtins.h" + /* * Does att's datatype allow packing into the 1-byte-header varlena format? @@ -486,6 +490,10 @@ heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc) /* these are never null */ break; + case YBTupleIdAttributeNumber: + /* If selected, virtual YB columns are never null */ + break; + default: elog(ERROR, "invalid attnum: %d", attnum); } @@ -756,6 +764,11 @@ heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull) case TableOidAttributeNumber: result = ObjectIdGetDatum(tup->t_tableOid); break; + + case YBTupleIdAttributeNumber: + result = HEAPTUPLE_YBCTID(tup); + break; + default: elog(ERROR, "invalid attnum: %d", attnum); result = 0; /* keep compiler quiet */ @@ -784,6 +797,7 @@ heap_copytuple(HeapTuple tuple) newTuple = (HeapTuple) palloc(HEAPTUPLESIZE + tuple->t_len); newTuple->t_len = tuple->t_len; newTuple->t_self = tuple->t_self; + HEAPTUPLE_COPY_YBCTID(tuple, newTuple); newTuple->t_tableOid = tuple->t_tableOid; newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE); memcpy((char *) newTuple->t_data, (char *) tuple->t_data, tuple->t_len); @@ -810,11 +824,38 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest) dest->t_len = src->t_len; dest->t_self = src->t_self; + HEAPTUPLE_COPY_YBCTID(src, dest); dest->t_tableOid = src->t_tableOid; dest->t_data = (HeapTupleHeader) palloc(src->t_len); memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len); } +/* ---------------- + * yb_heap_copytuple_with_tuple + * + * copy a tuple into a caller-supplied HeapTuple management struct assuming + * that the dest heap tuple's t_data already has *sufficient* memory + * pre-allocated. + * + * ---------------- + */ +void +yb_heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest) +{ + if (!HeapTupleIsValid(src) || src->t_data == NULL) + { + dest->t_data = NULL; + return; + } + + dest->t_len = src->t_len; + dest->t_self = src->t_self; + HEAPTUPLE_COPY_YBCTID(src, dest); + dest->t_tableOid = src->t_tableOid; + /* assumes that dest->t_data is pre-allocated with enough memory. */ + memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len); +} + /* * Expand a tuple which has fewer attributes than required. For each attribute * not present in the sourceTuple, if there is a missing value that will be @@ -1174,6 +1215,7 @@ heap_form_tuple(TupleDesc tupleDescriptor, tuple->t_len = len; ItemPointerSetInvalid(&(tuple->t_self)); tuple->t_tableOid = InvalidOid; + HEAPTUPLE_YBCTID(tuple) = 0; HeapTupleHeaderSetDatumLength(td, len); HeapTupleHeaderSetTypeId(td, tupleDescriptor->tdtypeid); @@ -1257,6 +1299,7 @@ heap_modify_tuple(HeapTuple tuple, */ newTuple->t_data->t_ctid = tuple->t_data->t_ctid; newTuple->t_self = tuple->t_self; + HEAPTUPLE_COPY_YBCTID(tuple, newTuple); newTuple->t_tableOid = tuple->t_tableOid; return newTuple; @@ -1320,6 +1363,7 @@ heap_modify_tuple_by_cols(HeapTuple tuple, */ newTuple->t_data->t_ctid = tuple->t_data->t_ctid; newTuple->t_self = tuple->t_self; + HEAPTUPLE_COPY_YBCTID(tuple, newTuple); newTuple->t_tableOid = tuple->t_tableOid; return newTuple; @@ -1561,6 +1605,7 @@ heap_tuple_from_minimal_tuple(MinimalTuple mtup) result->t_len = len; ItemPointerSetInvalid(&(result->t_self)); result->t_tableOid = InvalidOid; + HEAPTUPLE_YBCTID(result) = 0; result->t_data = (HeapTupleHeader) ((char *) result + HEAPTUPLESIZE); memcpy((char *) result->t_data + MINIMAL_TUPLE_OFFSET, mtup, mtup->t_len); memset(result->t_data, 0, offsetof(HeapTupleHeaderData, t_infomask2)); diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c index c0bad3cd9574e..e7375791fa184 100644 --- a/src/backend/access/common/indextuple.c +++ b/src/backend/access/common/indextuple.c @@ -22,6 +22,9 @@ #include "access/itup.h" #include "access/toast_internals.h" +/* YB includes */ +#include "pg_yb_utils.h" + /* * This enables de-toasting of index entries. Needed until VACUUM is * smart enough to rebuild indexes from scratch. @@ -73,10 +76,11 @@ index_form_tuple_context(TupleDesc tupleDescriptor, data_size, hoff; int i; - unsigned short infomask = 0; + uint32 infomask = 0; /* YB: changed type from unsigned short */ bool hasnull = false; uint16 tupmask = 0; int numberOfAttributes = tupleDescriptor->natts; + bool is_yb_relation = false; #ifdef TOAST_INDEX_HACK Datum untoasted_values[INDEX_MAX_KEYS]; @@ -117,7 +121,29 @@ index_form_tuple_context(TupleDesc tupleDescriptor, * If value is above size target, and is of a compressible datatype, * try to compress it in-line. */ - if (!VARATT_IS_EXTENDED(DatumGetPointer(untoasted_values[i])) && + + /* + * Some indexes (such as GIN index) might dynamically create attribute + * descriptors via calling TupleDescInitEntry(). These OIDs would be + * invalid, and passing such OID to IsYBRelationById() might result + * in a crash. We therefore first check if the OID is valid. + */ + is_yb_relation = is_yb_relation || + (OidIsValid(att->attrelid) && IsYBRelationById(att->attrelid)); + + /* + * In YugabyteDB, for regular tables, since it stores the tuple in + * DocDB, it is not necessary to have TOAST compression. + * Disabling compression would improve query latency as one does not + * need to compress the resulting index to fit in the IndexTuple + * structure. + + * For temporary tables and temporary indices, since they are not + * stored in DocDB, TOAST compression might still be necessary to fit + * the tuple in the IndexTuple structure. + */ + if (!is_yb_relation && + !VARATT_IS_EXTENDED(DatumGetPointer(untoasted_values[i])) && VARSIZE(DatumGetPointer(untoasted_values[i])) > TOAST_INDEX_TARGET && (att->attstorage == TYPSTORAGE_EXTENDED || att->attstorage == TYPSTORAGE_MAIN)) @@ -199,15 +225,23 @@ index_form_tuple_context(TupleDesc tupleDescriptor, Assert((tupmask & HEAP_HASEXTERNAL) == 0); #endif + /* + * YB NOTE (#2003): use the same limit for temp table after adding support + * for wide column in temp table. + */ + uint32 index_size_mask = (is_yb_relation ? + YB_INDEX_SIZE_MASK : + INDEX_SIZE_MASK); + /* * Here we make sure that the size will fit in the field reserved for it * in t_info. */ - if ((size & INDEX_SIZE_MASK) != size) + if ((size & index_size_mask) != size) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("index row requires %zu bytes, maximum size is %zu", - size, (Size) INDEX_SIZE_MASK))); + size, (Size) index_size_mask))); infomask |= size; diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c index d2f3b5728846d..af868bb07c185 100644 --- a/src/backend/access/common/printtup.c +++ b/src/backend/access/common/printtup.c @@ -23,6 +23,9 @@ #include "utils/memdebug.h" #include "utils/memutils.h" +/* YB includes */ +#include "common/pg_yb_common.h" + static void printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo); @@ -360,12 +363,7 @@ printtup(TupleTableSlot *slot, DestReceiver *self) else { /* Binary output */ - bytea *outputbytes; - - outputbytes = SendFunctionCall(&thisState->finfo, attr); - pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ); - pq_sendbytes(buf, VARDATA(outputbytes), - VARSIZE(outputbytes) - VARHDRSZ); + StringInfoSendFunctionCall(buf, &thisState->finfo, attr); } } diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index ec248ad682e99..ec7f1a6aeafde 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -37,6 +37,11 @@ #include "utils/memutils.h" #include "utils/rel.h" +/* YB includes */ +#include "access/transam.h" +#include "commands/yb_tablegroup.h" +#include "miscadmin.h" + /* * Contents of pg_class.reloptions * @@ -168,6 +173,43 @@ static relopt_bool boolRelOpts[] = }, true }, + { + { + "colocated", + "opt-out of using colocated tablet if set to false", + RELOPT_KIND_HEAP, + AccessExclusiveLock + }, + + /* + * true by default so that table created in colocated database will be + * colocated. This option will be ignored in non-colocated database. + */ + true + }, + { + { + "colocation", + "opt-out of colocation if set to false", + RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED, + AccessExclusiveLock + }, + + /* + * true by default so that table created in colocated database will be + * colocated. This option will be ignored in non-colocation database. + */ + true + }, + { + { + "use_initdb_acl", + "Initialize view's permissions as if it was created by initdb via yb_system_views.sql", + RELOPT_KIND_VIEW, + AccessExclusiveLock + }, + false + }, /* list terminator */ {{NULL}} }; @@ -382,7 +424,47 @@ static relopt_int intRelOpts[] = }, -1, 0, 1024 }, + /* list terminator */ + {{NULL}} +}; +static yb_relopt_oid oidRelOpts[] = +{ + { + { + "colocation_id", + "Colocation ID to distinguish a table within a colocation group. Used during backup/restore.", + RELOPT_KIND_HEAP | RELOPT_KIND_INDEX | RELOPT_KIND_PARTITIONED, + AccessExclusiveLock + }, + InvalidOid, + FirstNormalObjectId, + OID_MAX + }, + { + { + "table_oid", + "Postgres table oid for this relation.", + RELOPT_KIND_HEAP | RELOPT_KIND_INDEX, + AccessExclusiveLock + }, + InvalidOid, + 1, /* parse_utilcmd takes care of OID >= + * FirstNormalObjectId for user tables */ + OID_MAX + }, + { + { + "row_type_oid", + "Postgres type oid for the new row type defined for this relation.", + RELOPT_KIND_HEAP | RELOPT_KIND_INDEX, + AccessExclusiveLock + }, + InvalidOid, + 1, /* parse_utilcmd takes care of OID >= + * FirstNormalObjectId for user tables */ + OID_MAX + }, /* list terminator */ {{NULL}} }; @@ -548,6 +630,32 @@ static relopt_enum enumRelOpts[] = static relopt_string stringRelOpts[] = { + { + { + "replica_placement", + "Json formatted string with array of placement policies", + RELOPT_KIND_YB_TABLESPACE, + AccessExclusiveLock + }, + 0, /* default_len */ + true, /* default_isnull */ + NULL, /* validate_cb */ + NULL, /* fill_cb */ + NULL /* default_val */ + }, + { + { + "read_replica_placement", + "Json formatted string with array of placement policies", + RELOPT_KIND_YB_TABLESPACE, + AccessExclusiveLock + }, + 0, /* default_len */ + true, /* default_isnull */ + NULL, /* validate_cb */ + NULL, /* fill_cb */ + NULL /* default_val */ + }, /* list terminator */ {{NULL}} }; @@ -597,6 +705,12 @@ initialize_reloptions(void) intRelOpts[i].gen.lockmode)); j++; } + for (i = 0; oidRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(oidRelOpts[i].gen.lockmode, + oidRelOpts[i].gen.lockmode)); + j++; + } for (i = 0; realRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(realRelOpts[i].gen.lockmode, @@ -639,6 +753,14 @@ initialize_reloptions(void) j++; } + for (i = 0; oidRelOpts[i].gen.name; i++) + { + relOpts[j] = &oidRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_OID; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; realRelOpts[i].gen.name; i++) { relOpts[j] = &realRelOpts[i].gen; @@ -794,6 +916,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; + case RELOPT_TYPE_OID: + size = sizeof(yb_relopt_oid); + break; case RELOPT_TYPE_REAL: size = sizeof(relopt_real); break; @@ -1325,6 +1450,69 @@ transformRelOptions(Datum oldOptions, List *defList, const char *namspace, return result; } +/* + * Create a new reloptions list without YB-specific utility reloptions which + * we don't want to be persisted. + */ +Datum +ybExcludeNonPersistentReloptions(Datum options) +{ + Datum result = (Datum) 0; + + /* Nothing to do if no options */ + if (!PointerIsValid(DatumGetPointer(options))) + return result; + + ArrayType *array = DatumGetArrayTypeP(options); + + Datum *optiondatums; + int noptions; + + deconstruct_array(array, TEXTOID, -1, false, 'i', + &optiondatums, NULL, &noptions); + + /* We build new array using accumArrayResult */ + ArrayBuildState *astate = NULL; + + for (int i = 0; i < noptions; i++) + { + char *s = TextDatumGetCString(optiondatums[i]); + + char *p = strchr(s, '='); + + if (p) + *p = '\0'; + + /* + * These options serve as temporary markers during YSQL upgrade, + * but they might also be used for other purposes (e.g. table_oid + * was used to backup/restore colocated tables). + */ + if (IsYsqlUpgrade && + (strcmp(s, "table_oid") == 0 || + strcmp(s, "row_type_oid") == 0 || + strcmp(s, "use_initdb_acl") == 0)) + continue; + + /* + * We do not want colocation ID to be persisted in reloptions, + * we can get it via YbGetTableProperties. + */ + if (strcmp(s, "colocation_id") == 0) + continue; + + astate = accumArrayResult(astate, optiondatums[i], + false, TEXTOID, + CurrentMemoryContext); + } + + if (astate) + result = makeArrayResult(astate, CurrentMemoryContext); + else + result = (Datum) 0; + + return result; +} /* * Convert the text-array format of reloptions into a List of DefElem. @@ -1362,6 +1550,7 @@ untransformRelOptions(Datum options) val = (Node *) makeString(pstrdup(p)); } result = lappend(result, makeDefElem(pstrdup(s), val, -1)); + pfree(s); /* YB-added */ } return result; @@ -1500,7 +1689,7 @@ parseRelOptionsInternal(Datum options, bool validate, * returned array. Values of type string are allocated separately and must * be freed by the caller. */ -static relopt_value * +relopt_value * parseRelOptions(Datum options, bool validate, relopt_kind kind, int *numrelopts) { @@ -1622,6 +1811,26 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, optint->min, optint->max))); } break; + case RELOPT_TYPE_OID: + { + yb_relopt_oid *optoid = (yb_relopt_oid *) option->gen; + + parsed = parse_oid(value, &option->values.oid_val, NULL); + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for OID option \"%s\": %s", + option->gen->name, value))); + if (validate && (option->values.oid_val < optoid->min || + option->values.oid_val > optoid->max)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("value %s out of bounds for option \"%s\"", + value, option->gen->name), + errdetail("Valid values are between \"%u\" and \"%u\".", + optoid->min, optoid->max))); + } + break; case RELOPT_TYPE_REAL: { relopt_real *optreal = (relopt_real *) option->gen; @@ -1777,6 +1986,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.int_val : ((relopt_int *) options[i].gen)->default_val; break; + case RELOPT_TYPE_OID: + *(Oid *) itempos = options[i].isset ? + options[i].values.oid_val : + ((yb_relopt_oid *) options[i].gen)->default_val; + break; case RELOPT_TYPE_REAL: *(double *) itempos = options[i].isset ? options[i].values.real_val : @@ -1885,7 +2099,14 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate)} + offsetof(StdRdOptions, vacuum_truncate)}, + {"colocated", RELOPT_TYPE_BOOL, + offsetof(StdRdOptions, colocated)}, + /* Reuse colocated field in StdRdOptions. */ + {"colocation", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, colocated)}, + {"colocation_id", RELOPT_TYPE_OID, offsetof(StdRdOptions, colocation_id)}, + {"table_oid", RELOPT_TYPE_OID, offsetof(StdRdOptions, table_oid)}, + {"row_type_oid", RELOPT_TYPE_OID, offsetof(StdRdOptions, row_type_oid)}, }; return (bytea *) build_reloptions(reloptions, validate, kind, @@ -1988,6 +2209,21 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) bytea * partitioned_table_reloptions(Datum reloptions, bool validate) { + if (IsYugaByteEnabled()) + { + /* YB supports colocation_id option for partioned tables. */ + static const relopt_parse_elt tab[] = { + {"colocation_id", RELOPT_TYPE_OID, + offsetof(YbParitionedTableOptions, colocation_id)}, + {"colocation", RELOPT_TYPE_BOOL, + offsetof(YbParitionedTableOptions, colocation)}, + }; + + return (bytea *) build_reloptions(reloptions, validate, + RELOPT_KIND_PARTITIONED, + sizeof(YbParitionedTableOptions), + tab, lengthof(tab)); + } /* * There are no options for partitioned tables yet, but this is able to do * some validation. @@ -2009,7 +2245,9 @@ view_reloptions(Datum reloptions, bool validate) {"security_invoker", RELOPT_TYPE_BOOL, offsetof(ViewOptions, security_invoker)}, {"check_option", RELOPT_TYPE_ENUM, - offsetof(ViewOptions, check_option)} + offsetof(ViewOptions, check_option)}, + {"use_initdb_acl", RELOPT_TYPE_BOOL, + offsetof(ViewOptions, yb_use_initdb_acl)} }; return (bytea *) build_reloptions(reloptions, validate, @@ -2104,6 +2342,53 @@ tablespace_reloptions(Datum reloptions, bool validate) tab, lengthof(tab)); } +/* + * Option parser for yugabyte tablespace reloptions + */ +bytea * +yb_tablespace_reloptions(Datum reloptions, bool validate) +{ + relopt_value *options; + YBTableSpaceOpts *tsopts; + int numoptions; + static const relopt_parse_elt yb_tab[] = { + {"replica_placement", RELOPT_TYPE_STRING, + offsetof(YBTableSpaceOpts, placement_offset)}, + {"read_replica_placement", RELOPT_TYPE_STRING, + offsetof(YBTableSpaceOpts, read_replica_placement_offset)} + }; + + options = parseRelOptions(reloptions, validate, RELOPT_KIND_YB_TABLESPACE, &numoptions); + + /* if none set, we're done */ + if (numoptions == 0) + { + return NULL; + } + + tsopts = allocateReloptStruct(sizeof(YBTableSpaceOpts), options, numoptions); + + fillRelOptions((void *) tsopts, sizeof(YBTableSpaceOpts), options, numoptions, + validate, yb_tab, lengthof(yb_tab)); + + if (validate) + { + const char *live_replicas_str = NULL; + const char *read_replicas_str = NULL; + + if (tsopts->placement_offset > 0) + live_replicas_str = (char *) tsopts + tsopts->placement_offset; + if (tsopts->read_replica_placement_offset > 0) + read_replicas_str = (char *) tsopts + tsopts->read_replica_placement_offset; + + validatePlacementConfigurations(live_replicas_str, read_replicas_str); + } + + pfree(options); + + return (bytea *) tsopts; +} + /* * Determine the required LOCKMODE from an option list. * diff --git a/src/backend/access/common/toast_compression.c b/src/backend/access/common/toast_compression.c index 7156ae9c47e72..fb17f82243b0d 100644 --- a/src/backend/access/common/toast_compression.c +++ b/src/backend/access/common/toast_compression.c @@ -24,7 +24,7 @@ #include "utils/builtins.h" /* GUC */ -int default_toast_compression = TOAST_PGLZ_COMPRESSION; +int default_toast_compression = TOAST_LZ4_COMPRESSION; #define NO_LZ4_SUPPORT() \ ereport(ERROR, \ @@ -44,7 +44,7 @@ pglz_compress_datum(const struct varlena *value) len; struct varlena *tmp = NULL; - valsize = VARSIZE_ANY_EXHDR(value); + valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value)); /* * No point in wasting a palloc cycle if value size is outside the allowed diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c index 576e585a89fc2..d58df3a91635d 100644 --- a/src/backend/access/common/toast_internals.c +++ b/src/backend/access/common/toast_internals.c @@ -340,10 +340,11 @@ toast_save_datum(Relation rel, Datum value, if (toastidxs[i]->rd_index->indisready) index_insert(toastidxs[i], t_values, t_isnull, &(toasttup->t_self), + toasttup->t_ybctid, toastrel, toastidxs[i]->rd_index->indisunique ? UNIQUE_CHECK_YES : UNIQUE_CHECK_NO, - false, NULL); + false, NULL, false /* yb_shared_insert */ ); } /* diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c index 4010e20cfbc26..29dd5465a1f61 100644 --- a/src/backend/access/common/tupconvert.c +++ b/src/backend/access/common/tupconvert.c @@ -21,6 +21,10 @@ #include "access/tupconvert.h" #include "executor/tuptable.h" +/* YB includes */ +#include "access/sysattr.h" +#include "pg_yb_utils.h" + /* * The conversion setup routines have the following common API: @@ -232,9 +236,12 @@ execute_attr_map_slot(AttrMap *attrMap, * The input and output bitmaps are offset by * FirstLowInvalidHeapAttributeNumber to accommodate system cols, like the * column-bitmaps in RangeTblEntry. + * + * YB uses YBGetFirstLowInvalidAttributeNumber instead of + * FirstLowInvalidHeapAttributeNumber. */ Bitmapset * -execute_attr_map_cols(AttrMap *attrMap, Bitmapset *in_cols) +execute_attr_map_cols(AttrMap *attrMap, Bitmapset *in_cols, Relation rel) { Bitmapset *out_cols; int out_attnum; @@ -248,7 +255,7 @@ execute_attr_map_cols(AttrMap *attrMap, Bitmapset *in_cols) */ out_cols = NULL; - for (out_attnum = FirstLowInvalidHeapAttributeNumber; + for (out_attnum = YBGetFirstLowInvalidAttributeNumber(rel) + 1; out_attnum <= attrMap->maplen; out_attnum++) { @@ -270,8 +277,9 @@ execute_attr_map_cols(AttrMap *attrMap, Bitmapset *in_cols) continue; } - if (bms_is_member(in_attnum - FirstLowInvalidHeapAttributeNumber, in_cols)) - out_cols = bms_add_member(out_cols, out_attnum - FirstLowInvalidHeapAttributeNumber); + if (bms_is_member(in_attnum - YBGetFirstLowInvalidAttributeNumber(rel), in_cols)) + out_cols = bms_add_member(out_cols, + out_attnum - YBGetFirstLowInvalidAttributeNumber(rel)); } return out_cols; diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c index b776d04459315..b346b0e35fe60 100644 --- a/src/backend/access/gin/ginscan.c +++ b/src/backend/access/gin/ginscan.c @@ -419,8 +419,11 @@ ginNewScanKey(IndexScanDesc scan) * If the index is version 0, it may be missing null and placeholder * entries, which would render searches for nulls and full-index scans * unreliable. Throw an error if so. + * + * This doesn't apply to Yugabyte-backed indexes. */ - if (hasNullQuery && !so->isVoidRes) + if (hasNullQuery && !so->isVoidRes + && !IsYBBackedRelation(scan->indexRelation)) { GinStatsData ginStats; diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index 20f470648bec7..24a03e224fccf 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -29,6 +29,10 @@ #include "utils/index_selfuncs.h" #include "utils/typcache.h" +/* YB includes */ +#include "pg_yb_utils.h" +#include "utils/lsyscache.h" + /* * GIN handler function: return IndexAmRoutine with access method parameters @@ -209,9 +213,27 @@ initGinState(GinState *state, Relation index) * alternatively call get_typcollation, but that seems like expensive * overkill --- there aren't going to be any cases where a GIN storage * type has a nondefault collation.) + * + * For Yugabyte, _do_ use get_typcollation (through type_is_collatable) + * because we do care that collation is invalid rather than default for + * later assertion purposes. Assuming the index is not created with + * explicit collation, expect the following: + * - int array: indexed type is _int and index type is int. Since both + * aren't collatable, set to InvalidOid (else if). + * - text array: indexed type is _text and index type is text. Since + * _text is collatable, set to collation of the indexed column (if). + * - yb_hstore: indexed type is hstore and index type is text. Since + * only text is collatable, set to default collation (else). + * - yb_pg_trgm: indexed type is text and index type is int. Since + * text is collatable, set to collation of the indexed column (if). + * This valid collation is needed later for regex, for example. + * TODO(#9595): fix interaction between this and the "if != STRING" + * Assert in YBGetCollationInfo. */ if (OidIsValid(index->rd_indcollation[i])) state->supportCollation[i] = index->rd_indcollation[i]; + else if (IsYBRelation(index) && !type_is_collatable(attr->atttypid)) + state->supportCollation[i] = InvalidOid; else state->supportCollation[i] = DEFAULT_COLLATION_OID; } @@ -632,6 +654,11 @@ ginGetStats(Relation index, GinStatsData *stats) Page metapage; GinMetaPageData *metadata; + if (IsYBBackedRelation(index)) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("cannot get stats on Yugabyte-backed gin index"))); + metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO); LockBuffer(metabuffer, GIN_SHARE); metapage = BufferGetPage(metabuffer); diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 13f5971cb2cb0..ff07a0f4dc1ea 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -74,6 +74,12 @@ #include "utils/spccache.h" #include "utils/syscache.h" +/* YB includes */ +#include "access/yb_scan.h" +#include "executor/ybModifyTable.h" +#include "pg_yb_utils.h" +#include "utils/builtins.h" + static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid, CommandId cid, int options); @@ -120,7 +126,6 @@ static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_required, bool *copy); - /* * Each tuple lock mode has a corresponding heavyweight lock, and one or two * corresponding MultiXactStatuses (one to merely lock tuples, another one to @@ -259,6 +264,8 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock) bpscan = (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel; scan->rs_nblocks = bpscan->phs_nblocks; } + else if (RelationGetForm(scan->rs_base.rs_rd)->relkind == RELKIND_SEQUENCE) + scan->rs_nblocks = 1; else scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_base.rs_rd); @@ -331,6 +338,7 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock) scan->rs_inited = false; scan->rs_ctup.t_data = NULL; ItemPointerSetInvalid(&scan->rs_ctup.t_self); + HEAPTUPLE_YBCTID(&scan->rs_ctup) = (Datum) 0; scan->rs_cbuf = InvalidBuffer; scan->rs_cblock = InvalidBlockNumber; @@ -1156,6 +1164,15 @@ heap_beginscan(Relation relation, Snapshot snapshot, { HeapScanDesc scan; + /* + * YB scan methods should only be used for tables that are handled by + * YugaByte. + */ + if (IsYBRelation(relation)) + { + return ybc_heap_beginscan(relation, snapshot, nkeys, key, flags); + } + /* * increment relation ref count while scanning relation * @@ -1273,6 +1290,11 @@ heap_rescan(TableScanDesc sscan, ScanKey key, bool set_params, void heap_endscan(TableScanDesc sscan) { + if (IsYBRelation(sscan->rs_rd)) + { + return ybc_heap_endscan(sscan); + } + HeapScanDesc scan = (HeapScanDesc) sscan; /* Note: no locking manipulations needed */ @@ -1306,6 +1328,11 @@ heap_endscan(TableScanDesc sscan) HeapTuple heap_getnext(TableScanDesc sscan, ScanDirection direction) { + if (IsYBRelation(sscan->rs_rd)) + { + return ybc_heap_getnext(sscan); + } + HeapScanDesc scan = (HeapScanDesc) sscan; /* @@ -1355,6 +1382,19 @@ heap_getnext(TableScanDesc sscan, ScanDirection direction) bool heap_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot) { + if (IsYBRelation(sscan->rs_rd)) + { + HeapTuple tuple = ybc_heap_getnext(sscan); + + if (!tuple) + { + ExecClearTuple(slot); + return false; + } + ExecStoreHeapTuple(tuple, slot, false /* shouldFree */ ); + return true; + } + HeapScanDesc scan = (HeapScanDesc) sscan; /* Note: no locking manipulations needed */ @@ -2043,6 +2083,12 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, Buffer vmbuffer = InvalidBuffer; bool all_visible_cleared = false; + if (IsYBRelation(relation)) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("operation not allowed in Yugabyte mode %s", + __func__))); + /* Cheap, simplistic check that the tuple matches the rel's rowtype. */ Assert(HeapTupleHeaderGetNatts(tup->t_data) <= RelationGetNumberOfAttributes(relation)); @@ -2291,6 +2337,11 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, bool need_tuple_data = RelationIsLogicallyLogged(relation); bool need_cids = RelationIsAccessibleInLogicalDecoding(relation); + if (IsYBRelation(relation)) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("operation not allowed in Yugabyte mode"))); + /* currently not needed (thus unsupported) for heap_multi_insert() */ AssertArg(!(options & HEAP_INSERT_NO_LOGICAL)); @@ -2695,6 +2746,13 @@ heap_delete(Relation relation, ItemPointer tid, HeapTuple old_key_tuple = NULL; /* replica identity of the tuple */ bool old_key_copied = false; + if (IsYBRelation(relation)) + { + YBC_LOG_WARNING("Ignoring unsupported tuple delete for rel %s", + RelationGetRelationName(relation)); + return TM_Ok; /* HeapTupleMayBeUpdated; */ + } + Assert(ItemPointerIsValid(tid)); /* @@ -4480,6 +4538,12 @@ heap_lock_tuple(Relation relation, HeapTuple tuple, tuple->t_len = ItemIdGetLength(lp); tuple->t_tableOid = RelationGetRelid(relation); + /* + * This will only be used for non-YB tuples (e.g. Temp tables) so we just + * need to set the ybctid to 0 (NULL) here. + */ + HEAPTUPLE_YBCTID(tuple) = (Datum) 0; + l3: result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer); @@ -6425,9 +6489,15 @@ heap_inplace_unlock(Relation relation, * * This exists only to keep modules working in back branches. Affected * modules should migrate to systable_inplace_update_begin(). + * + * If yb_shared_update is specified, this update will be done in every + * database (including template0 and template1). Such operation will assume + * the tuple is exactly the same in all databases. + * This is needed when creating shared relations. + * This flag should not be used during initdb bootstrap. */ void -heap_inplace_update(Relation relation, HeapTuple tuple) +heap_inplace_update(Relation relation, HeapTuple tuple, bool yb_shared_update) { Buffer buffer; Page page; @@ -6437,6 +6507,29 @@ heap_inplace_update(Relation relation, HeapTuple tuple) uint32 oldlen; uint32 newlen; + if (IsYBRelation(relation)) + { + if (yb_shared_update) + { + if (!IsYsqlUpgrade) + elog(ERROR, "shared update cannot be done outside of YSQL upgrade"); + + YB_FOR_EACH_DB(pg_db_tuple) + { + Oid dboid = ((Form_pg_database) GETSTRUCT(pg_db_tuple))->oid; + + /* YB doesn't use PG locks so it's okay not to take them. */ + YBCUpdateSysCatalogTupleForDb(dboid, relation, NULL /* oldtuple */ , tuple); + } + YB_FOR_EACH_DB_END; + } + else + { + YBCUpdateSysCatalogTuple(relation, NULL /* oldtuple */ , tuple); + } + return; + } + /* * For now, we don't allow parallel updates. Unlike a regular update, * this should never create a combo CID, so it might be possible to relax diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 21682592478df..f762270fc7a0f 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -45,6 +45,10 @@ #include "utils/builtins.h" #include "utils/rel.h" +/* YB includes */ +#include "access/yb_scan.h" +#include "pg_yb_utils.h" + static void reform_and_rewrite_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap, Datum *values, bool *isnull, RewriteState rwstate); @@ -66,6 +70,8 @@ static const TableAmRoutine heapam_methods; static const TupleTableSlotOps * heapam_slot_callbacks(Relation relation) { + if (IsYBRelation(relation)) + return &TTSOpsHeapTuple; return &TTSOpsBufferHeapTuple; } @@ -843,6 +849,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, case HEAPTUPLE_RECENTLY_DEAD: *tups_recently_dead += 1; /* fall through */ + yb_switch_fallthrough(); case HEAPTUPLE_LIVE: /* Live or recently dead, must copy it */ isdead = false; @@ -1166,7 +1173,10 @@ heapam_index_build_range_scan(Relation heapRelation, BlockNumber numblocks, IndexBuildCallback callback, void *callback_state, - TableScanDesc scan) + TableScanDesc scan, + YbBackfillInfo *bfinfo, + YbPgExecOutParam *bfresult, + YbIndexBuildCallback ybcallback) { HeapScanDesc hscan; bool is_system_catalog; @@ -1185,12 +1195,17 @@ heapam_index_build_range_scan(Relation heapRelation, BlockNumber previous_blkno = InvalidBlockNumber; BlockNumber root_blkno = InvalidBlockNumber; OffsetNumber root_offsets[MaxHeapTuplesPerPage]; + MemoryContext oldcontext = CurrentMemoryContext; + int yb_tuples_done = 0; /* * sanity checks */ Assert(OidIsValid(indexRelation->rd_rel->relam)); + /* YB note: progress flag must not be set for YB relations. */ + Assert(!IsYBRelation(heapRelation) || !progress); + /* Remember if it's a system catalog */ is_system_catalog = IsSystemRelation(heapRelation); @@ -1247,12 +1262,39 @@ heapam_index_build_range_scan(Relation heapRelation, else snapshot = SnapshotAny; - scan = table_beginscan_strat(heapRelation, /* relation */ - snapshot, /* snapshot */ - 0, /* number of keys */ - NULL, /* scan key */ - true, /* buffer access strategy OK */ - allow_sync); /* syncscan OK? */ + if (IsYBRelation(heapRelation) && yb_enable_index_backfill_column_projection && !is_system_catalog) + { + scan = ybc_heap_beginscan_for_index_build(heapRelation, + snapshot, + indexInfo); + } + else + { + scan = table_beginscan_strat(heapRelation, /* relation */ + snapshot, /* snapshot */ + 0, /* number of keys */ + NULL, /* scan key */ + true, /* buffer access strategy OK */ + allow_sync); /* syncscan OK? */ + } + + if (IsYBRelation(heapRelation)) + { + YbcPgExecParameters *exec_params = &estate->yb_exec_params; + + if (bfinfo) + { + if (bfinfo->bfinstr) + exec_params->bfinstr = pstrdup(bfinfo->bfinstr); + + exec_params->backfill_read_time = bfinfo->read_time; + exec_params->partition_key = + pstrdup(bfinfo->row_bounds->partition_key); + exec_params->out_param = bfresult; + exec_params->is_index_backfill = true; + } + scan->ybscan->exec_params = exec_params; + } } else { @@ -1268,7 +1310,8 @@ heapam_index_build_range_scan(Relation heapRelation, snapshot = scan->rs_snapshot; } - hscan = (HeapScanDesc) scan; + if (!IsYBRelation(heapRelation)) + hscan = (HeapScanDesc) scan; /* * Must have called GetOldestNonRemovableTransactionId() if using @@ -1312,6 +1355,9 @@ heapam_index_build_range_scan(Relation heapRelation, reltuples = 0; + if (IsYBRelation(indexRelation)) + MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + /* * Scan all tuples in the base relation. */ @@ -1334,290 +1380,302 @@ heapam_index_build_range_scan(Relation heapRelation, } } - /* - * When dealing with a HOT-chain of updated tuples, we want to index - * the values of the live tuple (if any), but index it under the TID - * of the chain's root tuple. This approach is necessary to preserve - * the HOT-chain structure in the heap. So we need to be able to find - * the root item offset for every tuple that's in a HOT-chain. When - * first reaching a new page of the relation, call - * heap_get_root_tuples() to build a map of root item offsets on the - * page. - * - * It might look unsafe to use this information across buffer - * lock/unlock. However, we hold ShareLock on the table so no - * ordinary insert/update/delete should occur; and we hold pin on the - * buffer continuously while visiting the page, so no pruning - * operation can occur either. - * - * In cases with only ShareUpdateExclusiveLock on the table, it's - * possible for some HOT tuples to appear that we didn't know about - * when we first read the page. To handle that case, we re-obtain the - * list of root offsets when a HOT tuple points to a root item that we - * don't know about. - * - * Also, although our opinions about tuple liveness could change while - * we scan the page (due to concurrent transaction commits/aborts), - * the chain root locations won't, so this info doesn't need to be - * rebuilt after waiting for another transaction. - * - * Note the implied assumption that there is no more than one live - * tuple per HOT-chain --- else we could create more than one index - * entry pointing to the same root tuple. - */ - if (hscan->rs_cblock != root_blkno) + if (!IsYBRelation(heapRelation)) { - Page page = BufferGetPage(hscan->rs_cbuf); - - LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE); - heap_get_root_tuples(page, root_offsets); - LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK); + /* + * When dealing with a HOT-chain of updated tuples, we want to index + * the values of the live tuple (if any), but index it under the TID + * of the chain's root tuple. This approach is necessary to preserve + * the HOT-chain structure in the heap. So we need to be able to find + * the root item offset for every tuple that's in a HOT-chain. When + * first reaching a new page of the relation, call + * heap_get_root_tuples() to build a map of root item offsets on the + * page. + * + * It might look unsafe to use this information across buffer + * lock/unlock. However, we hold ShareLock on the table so no + * ordinary insert/update/delete should occur; and we hold pin on the + * buffer continuously while visiting the page, so no pruning + * operation can occur either. + * + * In cases with only ShareUpdateExclusiveLock on the table, it's + * possible for some HOT tuples to appear that we didn't know about + * when we first read the page. To handle that case, we re-obtain the + * list of root offsets when a HOT tuple points to a root item that we + * don't know about. + * + * Also, although our opinions about tuple liveness could change while + * we scan the page (due to concurrent transaction commits/aborts), + * the chain root locations won't, so this info doesn't need to be + * rebuilt after waiting for another transaction. + * + * Note the implied assumption that there is no more than one live + * tuple per HOT-chain --- else we could create more than one index + * entry pointing to the same root tuple. + */ + if (hscan->rs_cblock != root_blkno) + { + Page page = BufferGetPage(hscan->rs_cbuf); - root_blkno = hscan->rs_cblock; - } + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE); + heap_get_root_tuples(page, root_offsets); + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK); - if (snapshot == SnapshotAny) - { - /* do our own time qual check */ - bool indexIt; - TransactionId xwait; + root_blkno = hscan->rs_cblock; + } - recheck: + if (snapshot == SnapshotAny) + { + /* do our own time qual check */ + bool indexIt; + TransactionId xwait; - /* - * We could possibly get away with not locking the buffer here, - * since caller should hold ShareLock on the relation, but let's - * be conservative about it. (This remark is still correct even - * with HOT-pruning: our pin on the buffer prevents pruning.) - */ - LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE); + recheck: - /* - * The criteria for counting a tuple as live in this block need to - * match what analyze.c's heapam_scan_analyze_next_tuple() does, - * otherwise CREATE INDEX and ANALYZE may produce wildly different - * reltuples values, e.g. when there are many recently-dead - * tuples. - */ - switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin, - hscan->rs_cbuf)) - { - case HEAPTUPLE_DEAD: - /* Definitely dead, we can ignore it */ - indexIt = false; - tupleIsAlive = false; - break; - case HEAPTUPLE_LIVE: - /* Normal case, index and unique-check it */ - indexIt = true; - tupleIsAlive = true; - /* Count it as live, too */ - reltuples += 1; - break; - case HEAPTUPLE_RECENTLY_DEAD: + /* + * We could possibly get away with not locking the buffer here, + * since caller should hold ShareLock on the relation, but let's + * be conservative about it. (This remark is still correct even + * with HOT-pruning: our pin on the buffer prevents pruning.) + */ + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE); - /* - * If tuple is recently deleted then we must index it - * anyway to preserve MVCC semantics. (Pre-existing - * transactions could try to use the index after we finish - * building it, and may need to see such tuples.) - * - * However, if it was HOT-updated then we must only index - * the live tuple at the end of the HOT-chain. Since this - * breaks semantics for pre-existing snapshots, mark the - * index as unusable for them. - * - * We don't count recently-dead tuples in reltuples, even - * if we index them; see heapam_scan_analyze_next_tuple(). - */ - if (HeapTupleIsHotUpdated(heapTuple)) - { + /* + * The criteria for counting a tuple as live in this block need to + * match what analyze.c's heapam_scan_analyze_next_tuple() does, + * otherwise CREATE INDEX and ANALYZE may produce wildly different + * reltuples values, e.g. when there are many recently-dead + * tuples. + */ + switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin, + hscan->rs_cbuf)) + { + case HEAPTUPLE_DEAD: + /* Definitely dead, we can ignore it */ indexIt = false; - /* mark the index as unsafe for old snapshots */ - indexInfo->ii_BrokenHotChain = true; - } - else - indexIt = true; - /* In any case, exclude the tuple from unique-checking */ - tupleIsAlive = false; - break; - case HEAPTUPLE_INSERT_IN_PROGRESS: - - /* - * In "anyvisible" mode, this tuple is visible and we - * don't need any further checks. - */ - if (anyvisible) - { + tupleIsAlive = false; + break; + case HEAPTUPLE_LIVE: + /* Normal case, index and unique-check it */ indexIt = true; tupleIsAlive = true; + /* Count it as live, too */ reltuples += 1; break; - } - - /* - * Since caller should hold ShareLock or better, normally - * the only way to see this is if it was inserted earlier - * in our own transaction. However, it can happen in - * system catalogs, since we tend to release write lock - * before commit there. Give a warning if neither case - * applies. - */ - xwait = HeapTupleHeaderGetXmin(heapTuple->t_data); - if (!TransactionIdIsCurrentTransactionId(xwait)) - { - if (!is_system_catalog) - elog(WARNING, "concurrent insert in progress within table \"%s\"", - RelationGetRelationName(heapRelation)); + case HEAPTUPLE_RECENTLY_DEAD: /* - * If we are performing uniqueness checks, indexing - * such a tuple could lead to a bogus uniqueness - * failure. In that case we wait for the inserting - * transaction to finish and check again. - */ - if (checking_uniqueness) + * If tuple is recently deleted then we must index it + * anyway to preserve MVCC semantics. (Pre-existing + * transactions could try to use the index after we finish + * building it, and may need to see such tuples.) + * + * However, if it was HOT-updated then we must only index + * the live tuple at the end of the HOT-chain. Since this + * breaks semantics for pre-existing snapshots, mark the + * index as unusable for them. + * + * We don't count recently-dead tuples in reltuples, even + * if we index them; see heapam_scan_analyze_next_tuple(). + */ + if (HeapTupleIsHotUpdated(heapTuple)) { - /* - * Must drop the lock on the buffer before we wait - */ - LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK); - XactLockTableWait(xwait, heapRelation, - &heapTuple->t_self, - XLTW_InsertIndexUnique); - CHECK_FOR_INTERRUPTS(); - goto recheck; + indexIt = false; + /* mark the index as unsafe for old snapshots */ + indexInfo->ii_BrokenHotChain = true; } - } - else - { - /* - * For consistency with - * heapam_scan_analyze_next_tuple(), count - * HEAPTUPLE_INSERT_IN_PROGRESS tuples as live only - * when inserted by our own transaction. - */ - reltuples += 1; - } - - /* - * We must index such tuples, since if the index build - * commits then they're good. - */ - indexIt = true; - tupleIsAlive = true; - break; - case HEAPTUPLE_DELETE_IN_PROGRESS: - - /* - * As with INSERT_IN_PROGRESS case, this is unexpected - * unless it's our own deletion or a system catalog; but - * in anyvisible mode, this tuple is visible. - */ - if (anyvisible) - { - indexIt = true; + else + indexIt = true; + /* In any case, exclude the tuple from unique-checking */ tupleIsAlive = false; - reltuples += 1; break; - } + case HEAPTUPLE_INSERT_IN_PROGRESS: - xwait = HeapTupleHeaderGetUpdateXid(heapTuple->t_data); - if (!TransactionIdIsCurrentTransactionId(xwait)) - { - if (!is_system_catalog) - elog(WARNING, "concurrent delete in progress within table \"%s\"", - RelationGetRelationName(heapRelation)); + /* + * In "anyvisible" mode, this tuple is visible and we + * don't need any further checks. + */ + if (anyvisible) + { + indexIt = true; + tupleIsAlive = true; + reltuples += 1; + break; + } /* - * If we are performing uniqueness checks, assuming - * the tuple is dead could lead to missing a - * uniqueness violation. In that case we wait for the - * deleting transaction to finish and check again. - * - * Also, if it's a HOT-updated tuple, we should not - * index it but rather the live tuple at the end of - * the HOT-chain. However, the deleting transaction - * could abort, possibly leaving this tuple as live - * after all, in which case it has to be indexed. The - * only way to know what to do is to wait for the - * deleting transaction to finish and check again. - */ - if (checking_uniqueness || - HeapTupleIsHotUpdated(heapTuple)) + * Since caller should hold ShareLock or better, normally + * the only way to see this is if it was inserted earlier + * in our own transaction. However, it can happen in + * system catalogs, since we tend to release write lock + * before commit there. Give a warning if neither case + * applies. + */ + xwait = HeapTupleHeaderGetXmin(heapTuple->t_data); + if (!TransactionIdIsCurrentTransactionId(xwait)) + { + if (!is_system_catalog) + elog(WARNING, "concurrent insert in progress within table \"%s\"", + RelationGetRelationName(heapRelation)); + + /* + * If we are performing uniqueness checks, indexing + * such a tuple could lead to a bogus uniqueness + * failure. In that case we wait for the inserting + * transaction to finish and check again. + */ + if (checking_uniqueness) + { + /* + * Must drop the lock on the buffer before we wait + */ + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK); + XactLockTableWait(xwait, heapRelation, + &heapTuple->t_self, + XLTW_InsertIndexUnique); + CHECK_FOR_INTERRUPTS(); + goto recheck; + } + } + else { /* - * Must drop the lock on the buffer before we wait - */ - LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK); - XactLockTableWait(xwait, heapRelation, - &heapTuple->t_self, - XLTW_InsertIndexUnique); - CHECK_FOR_INTERRUPTS(); - goto recheck; + * For consistency with + * heapam_scan_analyze_next_tuple(), count + * HEAPTUPLE_INSERT_IN_PROGRESS tuples as live only + * when inserted by our own transaction. + */ + reltuples += 1; } /* - * Otherwise index it but don't check for uniqueness, - * the same as a RECENTLY_DEAD tuple. - */ + * We must index such tuples, since if the index build + * commits then they're good. + */ indexIt = true; + tupleIsAlive = true; + break; + case HEAPTUPLE_DELETE_IN_PROGRESS: /* - * Count HEAPTUPLE_DELETE_IN_PROGRESS tuples as live, - * if they were not deleted by the current - * transaction. That's what - * heapam_scan_analyze_next_tuple() does, and we want - * the behavior to be consistent. - */ - reltuples += 1; - } - else if (HeapTupleIsHotUpdated(heapTuple)) - { - /* - * It's a HOT-updated tuple deleted by our own xact. - * We can assume the deletion will commit (else the - * index contents don't matter), so treat the same as - * RECENTLY_DEAD HOT-updated tuples. - */ - indexIt = false; - /* mark the index as unsafe for old snapshots */ - indexInfo->ii_BrokenHotChain = true; - } - else - { - /* - * It's a regular tuple deleted by our own xact. Index - * it, but don't check for uniqueness nor count in - * reltuples, the same as a RECENTLY_DEAD tuple. - */ - indexIt = true; - } - /* In any case, exclude the tuple from unique-checking */ - tupleIsAlive = false; - break; - default: - elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result"); - indexIt = tupleIsAlive = false; /* keep compiler quiet */ - break; + * As with INSERT_IN_PROGRESS case, this is unexpected + * unless it's our own deletion or a system catalog; but + * in anyvisible mode, this tuple is visible. + */ + if (anyvisible) + { + indexIt = true; + tupleIsAlive = false; + reltuples += 1; + break; + } + + xwait = HeapTupleHeaderGetUpdateXid(heapTuple->t_data); + if (!TransactionIdIsCurrentTransactionId(xwait)) + { + if (!is_system_catalog) + elog(WARNING, "concurrent delete in progress within table \"%s\"", + RelationGetRelationName(heapRelation)); + + /* + * If we are performing uniqueness checks, assuming + * the tuple is dead could lead to missing a + * uniqueness violation. In that case we wait for the + * deleting transaction to finish and check again. + * + * Also, if it's a HOT-updated tuple, we should not + * index it but rather the live tuple at the end of + * the HOT-chain. However, the deleting transaction + * could abort, possibly leaving this tuple as live + * after all, in which case it has to be indexed. The + * only way to know what to do is to wait for the + * deleting transaction to finish and check again. + */ + if (checking_uniqueness || + HeapTupleIsHotUpdated(heapTuple)) + { + /* + * Must drop the lock on the buffer before we wait + */ + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK); + XactLockTableWait(xwait, heapRelation, + &heapTuple->t_self, + XLTW_InsertIndexUnique); + CHECK_FOR_INTERRUPTS(); + goto recheck; + } + + /* + * Otherwise index it but don't check for uniqueness, + * the same as a RECENTLY_DEAD tuple. + */ + indexIt = true; + + /* + * Count HEAPTUPLE_DELETE_IN_PROGRESS tuples as live, + * if they were not deleted by the current + * transaction. That's what + * heapam_scan_analyze_next_tuple() does, and we want + * the behavior to be consistent. + */ + reltuples += 1; + } + else if (HeapTupleIsHotUpdated(heapTuple)) + { + /* + * It's a HOT-updated tuple deleted by our own xact. + * We can assume the deletion will commit (else the + * index contents don't matter), so treat the same as + * RECENTLY_DEAD HOT-updated tuples. + */ + indexIt = false; + /* mark the index as unsafe for old snapshots */ + indexInfo->ii_BrokenHotChain = true; + } + else + { + /* + * It's a regular tuple deleted by our own xact. Index + * it, but don't check for uniqueness nor count in + * reltuples, the same as a RECENTLY_DEAD tuple. + */ + indexIt = true; + } + /* In any case, exclude the tuple from unique-checking */ + tupleIsAlive = false; + break; + default: + elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result"); + indexIt = tupleIsAlive = false; /* keep compiler quiet */ + break; + } + + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK); + + if (!indexIt) + continue; + } + else + { + /* heap_getnext did the time qual check */ + tupleIsAlive = true; + reltuples += 1; } - LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK); + MemoryContextReset(econtext->ecxt_per_tuple_memory); - if (!indexIt) - continue; + /* Set up for predicate or expression evaluation */ + ExecStoreBufferHeapTuple(heapTuple, slot, hscan->rs_cbuf); } else { - /* heap_getnext did the time qual check */ + /* In YugaByte mode DocDB will only send live tuples. */ tupleIsAlive = true; reltuples += 1; - } - - MemoryContextReset(econtext->ecxt_per_tuple_memory); - /* Set up for predicate or expression evaluation */ - ExecStoreBufferHeapTuple(heapTuple, slot, hscan->rs_cbuf); + /* Set up for predicate or expression evaluation */ + ExecStoreHeapTuple(heapTuple, slot, false /* shouldFree */ ); + } /* * In a partial index, discard tuples that don't satisfy the @@ -1626,7 +1684,12 @@ heapam_index_build_range_scan(Relation heapRelation, if (predicate != NULL) { if (!ExecQual(predicate, econtext)) + { + if (IsYBRelation(indexRelation) && !indexInfo->ii_Concurrent) + pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE, + ++yb_tuples_done); continue; + } } /* @@ -1646,7 +1709,7 @@ heapam_index_build_range_scan(Relation heapRelation, * pass the values[] and isnull[] arrays, instead. */ - if (HeapTupleIsHeapOnly(heapTuple)) + if (!IsYugaByteEnabled() && HeapTupleIsHeapOnly(heapTuple)) { /* * For a heap-only tuple, pretend its TID is that of the root. See @@ -1686,12 +1749,24 @@ heapam_index_build_range_scan(Relation heapRelation, callback(indexRelation, &tid, values, isnull, tupleIsAlive, callback_state); } + else if (IsYBRelation(indexRelation)) + { + ybcallback(indexRelation, heapTuple->t_ybctid, values, isnull, + tupleIsAlive, callback_state); + } else { /* Call the AM's callback routine to process the tuple */ callback(indexRelation, &heapTuple->t_self, values, isnull, tupleIsAlive, callback_state); } + if (IsYBRelation(indexRelation)) + { + MemoryContextReset(econtext->ecxt_per_tuple_memory); + if (!indexInfo->ii_Concurrent) + pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE, + ++yb_tuples_done); + } } /* Report scan progress one last time. */ @@ -1713,6 +1788,9 @@ heapam_index_build_range_scan(Relation heapRelation, blks_done); } + if (IsYBRelation(indexRelation)) + MemoryContextSwitchTo(oldcontext); + table_endscan(scan); /* we can now forget our snapshot, if set and registered by us */ @@ -1954,11 +2032,13 @@ heapam_index_validate_scan(Relation heapRelation, values, isnull, &rootTuple, + heapTuple->t_ybctid, heapRelation, indexInfo->ii_Unique ? UNIQUE_CHECK_YES : UNIQUE_CHECK_NO, false, - indexInfo); + indexInfo, + false /* yb_shared_insert */ ); state->tups_inserted += 1; } diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c index 4e88295df772f..ae81f61631765 100644 --- a/src/backend/access/heap/heaptoast.c +++ b/src/backend/access/heap/heaptoast.c @@ -32,6 +32,10 @@ #include "access/toast_internals.h" #include "utils/fmgroids.h" +/* YB includes */ +#include "pg_yb_utils.h" +#include "utils/builtins.h" + /* ---------- * heap_toast_delete - @@ -415,6 +419,91 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc) return new_tuple; } +/* ---------- + * yb_toast_compress_tuple -- given an uncompressed tuple, + * try to compress the varlena attributes that are above + * the threshold. Returns a new tuple with the compressed + * attributes. + * ---------- + */ +HeapTuple +yb_toast_compress_tuple(HeapTuple tup, TupleDesc tupleDesc) +{ + HeapTuple new_tuple; + int numAttrs = tupleDesc->natts; + int i; + Datum toast_values[MaxTupleAttributeNumber]; + bool toast_isnull[MaxTupleAttributeNumber]; + bool toast_free[MaxTupleAttributeNumber]; + + /* + * Break down the tuple into fields. + */ + Assert(numAttrs <= MaxTupleAttributeNumber); + heap_deform_tuple(tup, tupleDesc, toast_values, toast_isnull); + + memset(toast_free, 0, numAttrs * sizeof(bool)); + + for (i = 0; i < numAttrs; i++) + { + /* + * Look at non-null varlena attributes that are over the toast threshold in size + * and whose attstorage is marked for compression. + */ + if (!toast_isnull[i] && + TupleDescAttr(tupleDesc, i)->attlen == -1 && + (TupleDescAttr(tupleDesc, i)->attstorage == 'm' || + TupleDescAttr(tupleDesc, i)->attstorage == 'x')) + { + struct varlena *old_value; + Datum compressed_value; + + old_value = (struct varlena *) DatumGetPointer(toast_values[i]); + if (VARATT_IS_COMPRESSED(old_value)) + continue; + + compressed_value = toast_compress_datum(PointerGetDatum(old_value), + TupleDescAttr(tupleDesc, i)->attcompression); + if (DatumGetPointer(compressed_value) != NULL) + { + toast_values[i] = compressed_value; + toast_free[i] = true; + } + } + } + + /* + * Form the reconfigured tuple. + */ + new_tuple = heap_form_tuple(tupleDesc, toast_values, toast_isnull); + + /* + * The following block of code is copied from toast_flatten_tuple in PG, any future changes should + * be sync'ed up between the two. + */ + new_tuple->t_self = tup->t_self; + new_tuple->t_tableOid = tup->t_tableOid; + + new_tuple->t_data->t_choice = tup->t_data->t_choice; + new_tuple->t_data->t_ctid = tup->t_data->t_ctid; + new_tuple->t_data->t_infomask &= ~HEAP_XACT_MASK; + new_tuple->t_data->t_infomask |= + tup->t_data->t_infomask & HEAP_XACT_MASK; + new_tuple->t_data->t_infomask2 &= ~HEAP2_XACT_MASK; + new_tuple->t_data->t_infomask2 |= + tup->t_data->t_infomask2 & HEAP2_XACT_MASK; + + HEAPTUPLE_COPY_YBCTID(tup, new_tuple); + + /* + * Free allocated temp values + */ + for (i = 0; i < numAttrs; i++) + if (toast_free[i]) + pfree(DatumGetPointer(toast_values[i])); + + return new_tuple; +} /* ---------- * toast_flatten_tuple_to_datum - diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c index 6dd05e01be895..8ee81860e1a3c 100644 --- a/src/backend/access/index/genam.c +++ b/src/backend/access/index/genam.c @@ -39,6 +39,11 @@ #include "utils/snapmgr.h" #include "utils/syscache.h" +/* YB includes */ +#include "access/yb_scan.h" +#include "executor/ybModifyTable.h" +#include + /* ---------------------------------------------------------------- * general access method routines @@ -127,6 +132,21 @@ RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys) scan->xs_hitup = NULL; scan->xs_hitupdesc = NULL; + /* + * Upstream PG commit c2fe139c201c48f1133e9fbea2dd99b8efe2fadd removes + * setting the item pointer invalid. Bring that back for the sake of YB + * asserts that that PG field is not changed in YB logic. + */ + ItemPointerSetInvalid(&scan->xs_heaptid); + scan->yb_exec_params = NULL; + scan->yb_scan_plan = NULL; + scan->yb_rel_pushdown = NULL; + scan->yb_idx_pushdown = NULL; + scan->yb_aggrefs = NIL; + scan->yb_agg_slot = NULL; + scan->yb_distinct_prefixlen = 0; + scan->fetch_ybctids_only = false; + return scan; } @@ -393,6 +413,19 @@ systable_beginscan(Relation heapRelation, SysScanDesc sysscan; Relation irel; + /* + * PG sometimes calls systable_beginscan on non-YB tables. + * e.g. toast_delete_datum, which uses systable_beginscan + * on TOAST tables, which can be temporary (and thus non-YB). + */ + if (IsYBRelation(heapRelation)) + return ybc_systable_beginscan(heapRelation, + indexId, + indexOK, + snapshot, + nkeys, + key); + if (indexOK && !IgnoreSystemIndexes && !ReindexIsProcessingIndex(indexId)) @@ -402,6 +435,7 @@ systable_beginscan(Relation heapRelation, sysscan = (SysScanDesc) palloc(sizeof(SysScanDescData)); + sysscan->ybscan = NULL; sysscan->heap_rel = heapRelation; sysscan->irel = irel; sysscan->slot = table_slot_create(heapRelation, NULL); @@ -507,6 +541,11 @@ systable_getnext(SysScanDesc sysscan) { HeapTuple htup = NULL; + YbSysScanBase ybscan = sysscan->ybscan; + + if (ybscan) + return ybscan->vtable->next(ybscan); + if (sysscan->irel) { if (index_getnext_slot(sysscan->iscan, ForwardScanDirection, sysscan->slot)) @@ -564,6 +603,13 @@ systable_getnext(SysScanDesc sysscan) bool systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup) { + /* + * If YugaByte is enabled, systable_recheck_tuple doesn't work + * since the function uses the buffer to determine the tuple's visibility. + */ + if (IsYugaByteEnabled()) + return true; + Snapshot freshsnap; bool result; @@ -598,6 +644,11 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup) void systable_endscan(SysScanDesc sysscan) { + YbSysScanBase ybscan = sysscan->ybscan; + + if (ybscan) + return ybscan->vtable->end(ybscan); + if (sysscan->slot) { ExecDropSingleTupleTableSlot(sysscan->slot); @@ -641,6 +692,8 @@ systable_endscan(SysScanDesc sysscan) * wrappers around index_beginscan/index_getnext_slot. The main reason for * their existence is to centralize possible future support of lossy operators * in catalog scans. + * TODO: This is not yet formally supported in YB, but cannot disable it + * because it is used for enum types (see issues #6259). */ SysScanDesc systable_beginscan_ordered(Relation heapRelation, @@ -852,6 +905,14 @@ systable_inplace_update_begin(Relation relation, return; } + /* + * TODO: cover this case if we intend to set TEST_enable_obj_tuple_locks to true. + */ + if (IsYBRelation(relation)) + { + break; + } + slot = scan->slot; Assert(TTS_IS_BUFFERTUPLE(slot)); bslot = (BufferHeapTupleTableSlot *) slot; @@ -868,13 +929,51 @@ systable_inplace_update_begin(Relation relation, * * The tuple cannot change size, and therefore its header fields and null * bitmap (if any) don't change either. + * + * If yb_shared_update is specified, this update will be done in every + * database (including template0 and template1). Such operation will assume + * the tuple is exactly the same in all databases. + * This is needed when creating shared relations. + * This flag should not be used during initdb bootstrap. */ void -systable_inplace_update_finish(void *state, HeapTuple tuple) +systable_inplace_update_finish(void *state, HeapTuple tuple, + bool yb_shared_update) { SysScanDesc scan = (SysScanDesc) state; Relation relation = scan->heap_rel; TupleTableSlot *slot = scan->slot; + + if (IsYBRelation(relation)) + { + /* Note: none of the variables above except "relation" are valid. */ + + if (yb_shared_update) + { + if (!IsYsqlUpgrade) + elog(ERROR, "shared update cannot be done outside of YSQL upgrade"); + + YB_FOR_EACH_DB(pg_db_tuple) + { + Oid dboid = ((Form_pg_database) GETSTRUCT(pg_db_tuple))->oid; + + /* YB doesn't use PG locks so it's okay not to take them. */ + YBCUpdateSysCatalogTupleForDb(dboid, relation, NULL /* oldtuple */ , tuple); + } + YB_FOR_EACH_DB_END; + } + else + { + YBCUpdateSysCatalogTuple(relation, NULL /* oldtuple */ , tuple); + } + + /* + * TODO: cover this case if we intend to set TEST_enable_obj_tuple_locks to true. + */ + systable_endscan(scan); + return; + } + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; HeapTuple oldtup = bslot->base.tuple; Buffer buffer = bslot->buffer; @@ -894,6 +993,16 @@ systable_inplace_update_cancel(void *state) SysScanDesc scan = (SysScanDesc) state; Relation relation = scan->heap_rel; TupleTableSlot *slot = scan->slot; + + if (IsYBRelation(relation)) + { + /* + * TODO: cover this case if we intend to set TEST_enable_obj_tuple_locks to true. + */ + systable_endscan(scan); + return; + } + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; HeapTuple oldtup = bslot->base.tuple; Buffer buffer = bslot->buffer; diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index 15ed66011314f..24546d5a2ea15 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -34,6 +34,9 @@ * index_getprocid - get a support procedure OID * index_getprocinfo - get a support procedure's lookup info * + * yb_index_might_recheck - could the scan possibly recheck indexquals? + * yb_index_getbitmap - get all ybctids from a scan + * * NOTES * This file contains the index_ routines which used * to be a scattered collection of stuff in access/genam. @@ -63,6 +66,12 @@ #include "utils/snapmgr.h" #include "utils/syscache.h" +/* YB includes */ +#include "access/yb_scan.h" +#include "catalog/pg_am.h" +#include "catalog/pg_opfamily.h" +#include "pg_yb_utils.h" + /* ---------------------------------------------------------------- * macros used in index_ routines @@ -114,6 +123,75 @@ static IndexScanDesc index_beginscan_internal(Relation indexRelation, ParallelIndexScanDesc pscan, bool temp_snap); static inline void validate_relation_kind(Relation r); +/* + * Wrapper on the top of index_open(), used during yb_index_check(). Given a + * base relation id, it creates a dummy primary key index object such + * that: + * - indexrelid and indrelid both point to the base relation + * - index key: ybctid column + */ +Relation +yb_dummy_baserel_index_open(Oid relationId, LOCKMODE lockmode) +{ + Relation relation; + + relation = relation_open(relationId, lockmode); + + if (relation->rd_rel->relkind == RELKIND_RELATION || + relation->rd_rel->relkind == RELKIND_MATVIEW) + { + Assert(!relation->rd_index); + Assert(!relation->rd_indam); + Assert(!relation->rd_opfamily); + int natts = 1; + Form_pg_index pg_index = + palloc0(sizeof(FormData_pg_index) + natts * sizeof(int16)); + + pg_index->indexrelid = RelationGetRelid(relation); + pg_index->indrelid = RelationGetRelid(relation); + pg_index->indnatts = natts; + pg_index->indnkeyatts = natts; + pg_index->indisunique = true; + pg_index->indisprimary = true; + pg_index->indimmediate = true; + pg_index->indisvalid = true; + pg_index->indisready = true; + pg_index->indislive = true; + pg_index->indkey.ndim = 1; + pg_index->indkey.dataoffset = 0; /* never any nulls */ + pg_index->indkey.elemtype = INT2OID; + pg_index->indkey.dim1 = natts; + pg_index->indkey.lbound1 = 0; + pg_index->indkey.values[0] = YBTupleIdAttributeNumber; + + relation->rd_index = pg_index; + relation->rd_indam = GetIndexAmRoutineByAmId(LSM_AM_OID, false); + relation->rd_opfamily = palloc0(sizeof(Oid) * pg_index->indnkeyatts); + relation->rd_opfamily[0] = BYTEA_LSM_FAM_OID; + } + return relation; +} + +/* + * Free the dummy index object created for yb_index_check(). + */ +void +yb_free_dummy_baserel_index(Relation relation) +{ + if (!(relation->rd_rel->relkind == RELKIND_RELATION || + relation->rd_rel->relkind == RELKIND_MATVIEW)) + return; + + Assert(relation->rd_index); + Assert(relation->rd_indam); + Assert(relation->rd_opfamily); + pfree(relation->rd_index); + pfree(relation->rd_indam); + pfree(relation->rd_opfamily); + relation->rd_index = NULL; + relation->rd_indam = NULL; + relation->rd_opfamily = NULL; +} /* ---------------------------------------------------------------- * index_ interface functions @@ -219,25 +297,85 @@ index_insert(Relation indexRelation, Datum *values, bool *isnull, ItemPointer heap_t_ctid, + Datum ybctid, Relation heapRelation, IndexUniqueCheck checkUnique, bool indexUnchanged, - IndexInfo *indexInfo) + IndexInfo *indexInfo, + bool yb_shared_insert) { RELATION_CHECKS; - CHECK_REL_PROCEDURE(aminsert); + if (IsYugaByteEnabled() && IsYBRelation(indexRelation)) + CHECK_REL_PROCEDURE(yb_aminsert); + else + CHECK_REL_PROCEDURE(aminsert); if (!(indexRelation->rd_indam->ampredlocks)) CheckForSerializableConflictIn(indexRelation, (ItemPointer) NULL, InvalidBlockNumber); + /* + * For YugaByte-based index, call the variant of aminsert that takes the full tuple instead of + * the tuple id. + */ + if (IsYugaByteEnabled() && IsYBRelation(indexRelation)) + { + return indexRelation->rd_indam->yb_aminsert(indexRelation, values, isnull, + ybctid, heapRelation, + checkUnique, indexInfo, + yb_shared_insert); + } + return indexRelation->rd_indam->aminsert(indexRelation, values, isnull, heap_t_ctid, heapRelation, checkUnique, indexUnchanged, indexInfo); } +/* ---------------- + * yb_index_delete - delete an index tuple from a relation. + * This is used only for indexes backed by YugabyteDB. For Postgres, when a tuple is updated, + * the ctid of the original tuple will be invalid (except for heap-only tuple (HOT)). Because + * of this, index entries of the original tuple do not need to be deleted in UPDATE. For + * YugaByte-based tables, the ybctid is the primary key of the tuple and will remain valid + * after UPDATE. So when a tuple is updated, we need to delete all index entries associated + * explicitly. + * ---------------- + */ +void +yb_index_delete(Relation indexRelation, + Datum *values, + bool *isnull, + Datum ybctid, + Relation heapRelation, + IndexInfo *indexInfo) +{ + RELATION_CHECKS; + CHECK_REL_PROCEDURE(yb_amdelete); + + indexRelation->rd_indam->yb_amdelete(indexRelation, values, isnull, + ybctid, heapRelation, + indexInfo); +} + +void +yb_index_update(Relation indexRelation, + Datum *values, + bool *isnull, + Datum oldYbctid, + Datum newYbctid, + Relation heapRelation, + struct IndexInfo *indexInfo) +{ + RELATION_CHECKS; + CHECK_REL_PROCEDURE(yb_amupdate); + + indexRelation->rd_indam->yb_amupdate(indexRelation, values, isnull, + oldYbctid, newYbctid, + heapRelation, indexInfo); +} + /* * index_beginscan - start a scan of an index with amgettuple * @@ -571,6 +709,16 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction) * keys, and puts the TID into scan->xs_heaptid. It should also set * scan->xs_recheck and possibly scan->xs_itup/scan->xs_hitup, though we * pay no attention to those fields here. + * + * YB note: + * TID should not be set for YB relations since it is not used. The slot + * is filled from other sources: + * - aggregate pushdown: yb_agg_slot + * - Index Scan: xs_hitup + * - Index Only Scan: xs_itup + * Upstream PG needs TID in these cases: + * - Index Only Scan: heap fetch using TID in case of invisible tuples + * - Index Scan: heap fetch using TID */ found = scan->indexRelation->rd_indam->amgettuple(scan, direction); @@ -587,7 +735,16 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction) return NULL; } - Assert(ItemPointerIsValid(&scan->xs_heaptid)); + /* + * YB should not set TID and should instead set at least one of + * yb_agg_slot, xs_hitup, or xs_itup. (See above note.) + */ + Assert(IsYBRelation(scan->indexRelation) ? + (!ItemPointerIsValid(&scan->xs_heaptid) && + (!TupIsNull(scan->yb_agg_slot) || + scan->xs_hitup != NULL || + scan->xs_itup != NULL)) : + ItemPointerIsValid(&scan->xs_heaptid)); pgstat_count_index_tuples(scan->indexRelation, 1); @@ -616,6 +773,18 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction) bool index_fetch_heap(IndexScanDesc scan, TupleTableSlot *slot) { + if (IsYBRelation(scan->heapRelation)) + { + /* + * For aggregate pushdown, slot should have already been updated by YB + * amgettuple. + */ + if (!scan->yb_aggrefs) + ExecStoreHeapTuple(scan->xs_hitup, slot, false /* shouldFree */ ); + + return true; + } + bool all_dead = false; bool found; @@ -670,15 +839,30 @@ index_getnext_slot(IndexScanDesc scan, ScanDirection direction, TupleTableSlot * if (tid == NULL) break; - Assert(ItemPointerEquals(tid, &scan->xs_heaptid)); + /* + * YB should not set TID and should instead set at least one of + * yb_agg_slot or xs_hitup. (See index_getnext_tid note.) + */ + Assert(IsYBRelation(scan->indexRelation) ? + (!ItemPointerIsValid(&scan->xs_heaptid) && + (!TupIsNull(scan->yb_agg_slot) || + scan->xs_hitup != NULL)) : + ItemPointerIsValid(&scan->xs_heaptid)); } /* * Fetch the next (or only) visible heap tuple for this index entry. * If we don't find anything, loop around and grab the next TID from * the index. + * + * YB should not set TID and should instead set at least one of + * yb_agg_slot or xs_hitup. (See index_getnext_tid note.) */ - Assert(ItemPointerIsValid(&scan->xs_heaptid)); + Assert(IsYBRelation(scan->indexRelation) ? + (!ItemPointerIsValid(&scan->xs_heaptid) && + (!TupIsNull(scan->yb_agg_slot) || + scan->xs_hitup != NULL)) : + ItemPointerIsValid(&scan->xs_heaptid)); if (index_fetch_heap(scan, slot)) return true; } @@ -720,6 +904,33 @@ index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap) return ntids; } +/* ---------------- + * yb_index_getbitmap - get all tuples at once from an index scan + * + * Adds the ybctids of all tuples satisfying the scan keys to a bitmap. + * + * Returns the number of matching tuples found. (Note: this might be only + * approximate, so it should only be used for statistical purposes.) + * ---------------- + */ +int64 +yb_index_getbitmap(IndexScanDesc scan, YbTIDBitmap *ybtbm) +{ + int64 ntids; + + SCAN_CHECKS; + CHECK_SCAN_PROCEDURE(yb_amgetbitmap); + + /* + * have the am's getbitmap proc do all the work. + */ + ntids = scan->indexRelation->rd_indam->yb_amgetbitmap(scan, ybtbm); + + pgstat_count_index_tuples(scan->indexRelation, ntids); + + return ntids; +} + /* ---------------- * index_bulk_delete - do mass deletion of index entries * @@ -1024,3 +1235,18 @@ index_opclass_options(Relation indrel, AttrNumber attnum, Datum attoptions, return build_local_reloptions(&relopts, attoptions, validate); } + +bool +yb_index_might_recheck(Scan *scan, + Relation heapRelation, Relation indexRelation, + bool xs_want_itup, ScanKey keys, int nkeys) +{ + RELATION_CHECKS; + CHECK_REL_PROCEDURE(yb_ammightrecheck); + + return indexRelation->rd_indam->yb_ammightrecheck(scan, + heapRelation, + indexRelation, + xs_want_itup, keys, + nkeys); +} diff --git a/src/backend/access/nbtree/nbtdedup.c b/src/backend/access/nbtree/nbtdedup.c index 0207421a5d977..2292ee7506d35 100644 --- a/src/backend/access/nbtree/nbtdedup.c +++ b/src/backend/access/nbtree/nbtdedup.c @@ -20,6 +20,9 @@ #include "miscadmin.h" #include "utils/rel.h" +/* YB includes */ +#include "utils/builtins.h" + static void _bt_bottomupdel_finish_pending(Page page, BTDedupState state, TM_IndexDeleteOp *delstate); static bool _bt_do_singleval(Relation rel, Page page, BTDedupState state, diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index 63976888afdbf..891922fb9b60d 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -30,6 +30,10 @@ #include "utils/memutils.h" #include "utils/rel.h" +/* YB includes */ +#include "utils/builtins.h" +#include "utils/typcache.h" + typedef struct BTSortArrayContext { @@ -41,9 +45,6 @@ typedef struct BTSortArrayContext static Datum _bt_find_extreme_element(IndexScanDesc scan, ScanKey skey, StrategyNumber strat, Datum *elems, int nelems); -static int _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey, - bool reverse, - Datum *elems, int nelems); static int _bt_compare_array_elements(const void *a, const void *b, void *arg); static bool _bt_compare_scankey_args(IndexScanDesc scan, ScanKey op, ScanKey leftarg, ScanKey rightarg, @@ -445,7 +446,7 @@ _bt_find_extreme_element(IndexScanDesc scan, ScanKey skey, * scan and skey identify the index column, whose opfamily determines the * comparison semantics. If reverse is true, we sort in descending order. */ -static int +int _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey, bool reverse, Datum *elems, int nelems) @@ -479,6 +480,25 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey, elemtype, elemtype, BTORDER_PROC); + if (IsYugaByteEnabled() && !RegProcedureIsValid(cmp_proc)) + { + /* + * YB: It can be possible that the opfamily for the requested attno + * rightfully does not have the requested elemtype yet we should be + * able to find a valid comparison function for it. For example, if + * elemtype is a Record type in the case that skey is a SAOP on Row + * expressions, we might run into this case. + * We fall back to finding the column data-type specific comparator + * in this case. The original PG code above looks for BTORDER_PROC + * in what might potentially be a user-defined opclass but we don't + * support that in YB yet. Until then, it's ok to fallback to the + * column data-type specific comparator. + */ + TypeCacheEntry *typentry = lookup_type_cache(elemtype, + TYPECACHE_CMP_PROC); + + cmp_proc = typentry->cmp_proc; + } if (!RegProcedureIsValid(cmp_proc)) elog(ERROR, "missing support function %d(%u,%u) in opfamily %u", BTORDER_PROC, elemtype, elemtype, diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c index 74ba2189f0a26..0cc5a30f9b491 100644 --- a/src/backend/access/table/toast_helper.c +++ b/src/backend/access/table/toast_helper.c @@ -330,7 +330,7 @@ toast_delete_external(Relation rel, Datum *values, bool *isnull, if (isnull[i]) continue; - else if (VARATT_IS_EXTERNAL_ONDISK(value)) + else if (VARATT_IS_EXTERNAL_ONDISK(PointerGetDatum(value))) toast_delete_datum(rel, value, is_speculative); } } diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c index 0ea06f360e27b..cc9400ba65fe4 100644 --- a/src/backend/access/transam/parallel.c +++ b/src/backend/access/transam/parallel.c @@ -45,6 +45,10 @@ #include "utils/snapmgr.h" #include "utils/typcache.h" +/* YB includes */ +#include "catalog/yb_catalog_version.h" +#include "pg_yb_utils.h" + /* * We don't want to waste a lot of memory on an error queue which, most of * the time, will process only a handful of small messages. However, it is @@ -95,6 +99,8 @@ typedef struct FixedParallelState PGPROC *parallel_leader_pgproc; pid_t parallel_leader_pid; BackendId parallel_leader_backend_id; + bool parallel_master_is_yb_session; + YbcPgSessionState parallel_master_yb_session_state; TimestampTz xact_ts; TimestampTz stmt_ts; SerializableXactHandle serializable_xact_handle; @@ -176,6 +182,10 @@ CreateParallelContext(const char *library_name, const char *function_name, /* Number of workers should be non-negative. */ Assert(nworkers >= 0); + /* YB: TODO GHI 23549 */ + if (IsolationIsSerializable()) + nworkers = 0; + /* We might be running in a short-lived memory context. */ oldcontext = MemoryContextSwitchTo(TopTransactionContext); @@ -219,7 +229,20 @@ InitializeParallelDSM(ParallelContext *pcxt) int i; FixedParallelState *fps; dsm_handle session_dsm_handle = DSM_HANDLE_INVALID; - Snapshot transaction_snapshot = GetTransactionSnapshot(); + Snapshot transaction_snapshot; + + /* + * Postgres unconditionally takes the snapshot, however Yugabyte has + * undesired side effect if transaction isolation is READ COMMITTED: it + * resets the read point, so the next DocDB request picks new read time. + * This can result in a change of read snapshot in the middle of the query. + * Fortunately we can skip that call in READ COMMITTED mode, because + * the transaction_snapshot is only used if the isolation level is + * REPEATABLE READ or SERIALIZABLE. + * For safety, keep original behavior if Yugabyte is not enabled. + */ + if (IsolationUsesXactSnapshot() || !IsYugaByteEnabled()) + transaction_snapshot = GetTransactionSnapshot(); Snapshot active_snapshot = GetActiveSnapshot(); /* We might be running in a very short-lived memory context. */ @@ -344,6 +367,10 @@ InitializeParallelDSM(ParallelContext *pcxt) fps->parallel_leader_pgproc = MyProc; fps->parallel_leader_pid = MyProcPid; fps->parallel_leader_backend_id = MyBackendId; + /* Capture our Session ID to share with the background workers. */ + fps->parallel_master_is_yb_session = IsYugaByteEnabled(); + if (fps->parallel_master_is_yb_session) + YBCDumpCurrentPgSessionState(&fps->parallel_master_yb_session_state); fps->xact_ts = GetCurrentTransactionStartTimestamp(); fps->stmt_ts = GetCurrentStatementStartTimestamp(); fps->serializable_xact_handle = ShareSerializableXact(); @@ -531,6 +558,11 @@ ReinitializeParallelDSM(ParallelContext *pcxt) pcxt->worker[i].error_mqh = shm_mq_attach(mq, pcxt->seg, NULL); } } + + /* + * YB TODO(#30936): Dump and restore transaction state similar to the way it + * is done in InitializeParallelDSM. + */ } /* @@ -566,6 +598,17 @@ LaunchParallelWorkers(ParallelContext *pcxt) if (pcxt->nworkers == 0 || pcxt->nworkers_to_launch == 0) return; + if (IsYugaByteEnabled()) + { + /* + * Semantics of the "EnsureReadPoint" contradicts "RestartReadPoint". + * Hence, if we are restarting, proceed without parallel workers. + */ + if (YBCIsRestartReadPointRequested()) + return; + HandleYBStatus(YBCPgEnsureReadPoint()); + } + /* We need to be a lock group leader. */ BecomeLockGroupLeader(); @@ -1297,6 +1340,9 @@ ParallelWorkerMain(Datum main_arg) /* Set flag to indicate that we're initializing a parallel worker. */ InitializingParallelWorker = true; + if (YBIsEnabledInPostgresEnvVar()) + YbSetParallelWorker(); + /* Establish signal handlers. */ pqsignal(SIGTERM, die); BackgroundWorkerUnblockSignals(); @@ -1306,9 +1352,9 @@ ParallelWorkerMain(Datum main_arg) memcpy(&ParallelWorkerNumber, MyBgworkerEntry->bgw_extra, sizeof(int)); /* Set up a memory context to work in, just for cleanliness. */ - CurrentMemoryContext = AllocSetContextCreate(TopMemoryContext, - "Parallel worker", - ALLOCSET_DEFAULT_SIZES); + YbCurrentMemoryContext = AllocSetContextCreate(TopMemoryContext, + "Parallel worker", + ALLOCSET_DEFAULT_SIZES); /* * Attach to the dynamic shared memory segment for the parallel query, and @@ -1420,9 +1466,24 @@ ParallelWorkerMain(Datum main_arg) * (b) we do not want parallel mode to cause these failures, because that * would make use of parallel query plans not transparent to applications. */ - BackgroundWorkerInitializeConnectionByOid(fps->database_id, - fps->authenticated_user_id, - BGWORKER_BYPASS_ALLOWCONN); + if (fps->parallel_master_is_yb_session) + { + YbcPgInitPostgresInfo yb_init_info = { + .parallel_leader_session_id = &fps->parallel_master_yb_session_state.session_id, + .shared_data = &fps->parallel_leader_pgproc->yb_shared_data + }; + + YbBackgroundWorkerInitializeConnectionByOid(fps->database_id, + fps->authenticated_user_id, + BGWORKER_BYPASS_ALLOWCONN, + &yb_init_info); + } + else + { + BackgroundWorkerInitializeConnectionByOid(fps->database_id, + fps->authenticated_user_id, + BGWORKER_BYPASS_ALLOWCONN); + } /* * Set the client encoding to the database encoding, since that is what @@ -1457,6 +1518,9 @@ ParallelWorkerMain(Datum main_arg) shm_toc_lookup(toc, PARALLEL_KEY_SESSION_DSM, false); AttachSession(*(dsm_handle *) session_dsm_handle_space); + if (fps->parallel_master_is_yb_session) + YBCRestorePgSessionState(&fps->parallel_master_yb_session_state); + /* * If the transaction isolation level is REPEATABLE READ or SERIALIZABLE, * the leader has serialized the transaction snapshot and we must restore @@ -1475,6 +1539,8 @@ ParallelWorkerMain(Datum main_arg) tsnapshot = tsnapspace ? RestoreSnapshot(tsnapspace) : asnapshot; RestoreTransactionSnapshot(tsnapshot, fps->parallel_leader_pgproc); + if (IsYugaByteEnabled()) + YBCPgEnsureReadPoint(); PushActiveSnapshot(asnapshot); /* @@ -1518,6 +1584,15 @@ ParallelWorkerMain(Datum main_arg) /* Attach to the leader's serializable transaction, if SERIALIZABLE. */ AttachSerializableXact(fps->serializable_xact_handle); + /* + * TODO Revisit initialization of the catalog cache version. + * DocDB scans running in background workers need a catalog cache version + * to put into the request. However, I'm not sure what is the right way to + * obtain it. Perhaps master scan should share the value it has. + */ + if (IsYugaByteEnabled()) + YbUpdateCatalogCacheVersion(YbGetMasterCatalogVersion()); + /* * We've initialized all of our state now; nothing should change * hereafter. diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index 748120a012561..76b45e7024885 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -26,13 +26,37 @@ #include "storage/proc.h" #include "utils/syscache.h" +/* YB includes */ +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "access/tableam.h" +#include "catalog/pg_class.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_namespace.h" +#include "commands/yb_cmds.h" +#include "utils/fmgroids.h" +#include "yb/yql/pggate/ybc_gflags.h" + /* Number of OIDs to prefetch (preallocate) per XLOG write */ #define VAR_OID_PREFETCH 8192 +/* + * Number of OIDs to prefetch (preallocate) in YugabyteDB setup. + * Given there are multiple Postgres nodes, each node should prefetch + * in smaller chunks. + */ +#define YB_OID_PREFETCH 256 + /* pointer to "variable cache" in shared memory (set up by shmem.c) */ VariableCache ShmemVariableCache = NULL; +/* next OID to assign during YSQL upgrade */ +Oid ysql_upgrade_next_oid = InvalidOid; + /* * Allocate the next FullTransactionId for a new transaction or @@ -513,6 +537,133 @@ ForceTransactionIdLimitUpdate(void) return false; } +/* + * Scan all system tables with OIDs to determine the maximum + * system-allocated OID. + * Naturally, this function is expensive. + */ +static Oid +YbGetMaxAllocatedSystemOid() +{ + Oid result = InvalidOid; + + Relation pg_class, + pg_attribute, + sys_rel; + + ScanKeyData key[2]; + TableScanDesc scan; + HeapTuple tuple; + + List *sys_rel_oids = NIL; + ListCell *lc, + *lc2; + List *attrelids = NIL; + List *attnums = NIL; + ArrayType *oids_array_type; + SysScanDesc sys_scan; + bool is_null; + + pg_class = table_open(RelationRelationId, AccessShareLock); + + /* + * SELECT * FROM pg_class + * WHERE relnamespace = 'pg_catalog'::regnamespace + * AND relkind = 'r'; + */ + ScanKeyInit(&key[0], + Anum_pg_class_relnamespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(PG_CATALOG_NAMESPACE)); + ScanKeyInit(&key[1], + Anum_pg_class_relkind, + BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(RELKIND_RELATION)); + + scan = table_beginscan_catalog(pg_class, 2, key); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + sys_rel_oids = + lappend_oid(sys_rel_oids, ((Form_pg_class) GETSTRUCT(tuple))->oid); + + table_endscan(scan); + table_close(pg_class, AccessShareLock); + + /* + * SELECT attrelid, attnum FROM pg_attribute WHERE attrelid in + * AND attname = 'oid'; + */ + Datum oids_datum_array[list_length(sys_rel_oids)]; + int num_oids = 0; + + foreach(lc, sys_rel_oids) + oids_datum_array[num_oids++] = ObjectIdGetDatum(lfirst_oid(lc)); + + oids_array_type = construct_array(oids_datum_array, num_oids, OIDOID, + sizeof(Oid), true, TYPALIGN_INT); + + ScanKeyEntryInitialize(&key[0], + SK_SEARCHARRAY, + Anum_pg_attribute_attrelid, + BTEqualStrategyNumber, OIDOID, + C_COLLATION_OID, F_OIDEQ, + PointerGetDatum(oids_array_type)); + ScanKeyInit(&key[1], + Anum_pg_attribute_attname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum("oid")); + + pg_attribute = table_open(AttributeRelationId, AccessShareLock); + + sys_scan = systable_beginscan(pg_attribute, AttributeRelidNameIndexId, + true, NULL, 2, key); + + while (HeapTupleIsValid(tuple = systable_getnext(sys_scan))) + { + Form_pg_attribute pg_att = (Form_pg_attribute) GETSTRUCT(tuple); + + attrelids = lappend_oid(attrelids, pg_att->attrelid); + attnums = lappend_int(attnums, pg_att->attnum); + } + systable_endscan(sys_scan); + table_close(pg_attribute, AccessShareLock); + pfree(oids_array_type); + + forboth(lc, attrelids, lc2, attnums) + { + int attnum = lfirst_int(lc2); + + /* SELECT * FROM x WHERE oid >= 12000 AND oid < 16384 */ + ScanKeyInit(&key[0], + attnum, + BTGreaterEqualStrategyNumber, F_OIDGE, + ObjectIdGetDatum((Oid) FirstUnpinnedObjectId)); + ScanKeyInit(&key[1], + attnum, + BTLessStrategyNumber, F_OIDLT, + ObjectIdGetDatum((Oid) FirstNormalObjectId)); + + sys_rel = table_open(lfirst_oid(lc), AccessShareLock); + scan = table_beginscan_catalog(sys_rel, 2, key); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Oid oid = DatumGetObjectId(heap_getattr(tuple, attnum, + RelationGetDescr(sys_rel), + &is_null)); + + Assert(!is_null); + + if (result < oid) + result = oid; + } + + table_endscan(scan); + table_close(sys_rel, AccessShareLock); + } + + return result; +} /* * GetNewObjectId -- allocate a new OID @@ -535,6 +686,43 @@ GetNewObjectId(void) LWLockAcquire(OidGenLock, LW_EXCLUSIVE); + /* + * In YSQL upgrade mode, we continue OID sequence where initdb left off. + * We don't expect concurrent upgrade, so we don't need to reserve OIDs. + */ + if (IsYsqlUpgrade) + { + if (!OidIsValid(ysql_upgrade_next_oid)) + ysql_upgrade_next_oid = YbGetMaxAllocatedSystemOid() + 1; + + result = ysql_upgrade_next_oid; + ysql_upgrade_next_oid++; + + LWLockRelease(OidGenLock); + return result; + } + + const bool ysql_enable_pg_per_database_oid_allocator = + *YBCGetGFlags()->ysql_enable_pg_per_database_oid_allocator; + + if (ysql_enable_pg_per_database_oid_allocator && + IsYugaByteEnabled() && !YBCIsInitDbModeEnvVarSet()) + { + /* + * As of 2023-10-16, docdb does not allow OID wraparound so we do not + * need to handle OID wraparound here. + */ + YbcStatus status = YBCGetNewObjectId(YbDatabaseIdForNewObjectId, + &result); + + if (*YBCGetGFlags()->TEST_ysql_log_perdb_allocated_new_objectid) + YBC_LOG_INFO("allocated new object id %u in database %u", + result, YbDatabaseIdForNewObjectId); + LWLockRelease(OidGenLock); + HandleYBStatus(status); + return result; + } + /* * Check for wraparound of the OID counter. We *must* not return 0 * (InvalidOid), and in normal operation we mustn't return anything below @@ -572,8 +760,25 @@ GetNewObjectId(void) /* If we run out of logged for use oids then we must log more */ if (ShmemVariableCache->oidCount == 0) { - XLogPutNextOid(ShmemVariableCache->nextOid + VAR_OID_PREFETCH); - ShmemVariableCache->oidCount = VAR_OID_PREFETCH; + if (IsYugaByteEnabled() && + !ysql_enable_pg_per_database_oid_allocator) + { + Oid begin_oid = InvalidOid; + Oid end_oid = InvalidOid; + + YBCReserveOids(MyDatabaseId, + ShmemVariableCache->nextOid, + YB_OID_PREFETCH, + &begin_oid, + &end_oid); + ShmemVariableCache->nextOid = begin_oid; + ShmemVariableCache->oidCount = end_oid - begin_oid; + } + else + { + XLogPutNextOid(ShmemVariableCache->nextOid + VAR_OID_PREFETCH); + ShmemVariableCache->oidCount = VAR_OID_PREFETCH; + } } result = ShmemVariableCache->nextOid; diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 7a3d9b4b01269..3e78131baf113 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -71,6 +71,11 @@ #include "utils/timeout.h" #include "utils/timestamp.h" +/* YB includes */ +#include "pg_yb_utils.h" +#include "yb/yql/pggate/ybc_dist_trace.h" +#include "yb/yql/pggate/ybc_pg_typedefs.h" + /* * User-tweakable parameters */ @@ -209,6 +214,23 @@ typedef struct TransactionStateData bool chain; /* start a new block after this one */ bool topXidLogged; /* for a subxact: is top-level XID logged? */ struct TransactionStateData *parent; /* back link to parent */ + + bool ybDataSent; /* Whether some tuples have been transmitted + * to frontend as part of this execution */ + bool ybDataSentForCurrQuery; /* Whether any data has been sent to + * frontend as part of current query's + * execution */ + bool ybTxnUsesTempRel; /* True if the transaction operates on a + * temporary table */ + List *YBPostponedDdlOps; /* We postpone execution of non-revertable + * DocDB operations (e.g. drop + * table/index) until the rest of the txn + * succeeds */ + int ybUncommittedStickyObjectCount; /* Count of objects that + * require stickiness within a + * certain transaction (e.g. + * TEMP TABLES/WITH HOLD + * CURSORS) */ } TransactionStateData; typedef TransactionStateData *TransactionState; @@ -241,6 +263,10 @@ static TransactionStateData TopTransactionStateData = { .state = TRANS_DEFAULT, .blockState = TBLOCK_DEFAULT, .topXidLogged = false, + .ybDataSent = false, + .ybDataSentForCurrQuery = false, + .ybTxnUsesTempRel = false, + .YBPostponedDdlOps = NULL, }; /* @@ -316,7 +342,6 @@ typedef struct SubXactCallbackItem static SubXactCallbackItem *SubXact_callbacks = NULL; - /* local function prototypes */ static void AssignTransactionId(TransactionState s); static void AbortTransaction(void); @@ -914,6 +939,14 @@ GetCurrentTransactionNestLevel(void) return s->nestingLevel; } +const char * +GetCurrentTransactionName(void) +{ + TransactionState s = CurrentTransactionState; + + return s->name; +} + /* * TransactionIdIsCurrentTransactionId @@ -1126,6 +1159,67 @@ ForceSyncCommit(void) forceSyncCommit = true; } +/* + * Mark current transaction as having sent some data back to the client. + * This prevents automatic transaction restart. + */ +void +YBMarkDataSent(void) +{ + TransactionState s = CurrentTransactionState; + + s->ybDataSent = true; + s->ybDataSentForCurrQuery = true; +} + +/* + * Mark current transaction as having no data sent to the client. + */ +void +YBMarkDataNotSent(void) +{ + TransactionState s = CurrentTransactionState; + + s->ybDataSent = false; +} + +void +YBMarkDataNotSentForCurrQuery(void) +{ + TransactionState s = CurrentTransactionState; + + s->ybDataSentForCurrQuery = false; +} + +/* + * Whether some data has been transmitted to frontend as part of this transaction. + */ +bool +YBIsDataSent(void) +{ + /* + * Note: we don't support nested transactions (savepoints) yet, but once + * we do - we have to make sure this works as intended. + */ + TransactionState s = CurrentTransactionState; + + /* + * Ignoring "idle" transaction state, a leftover from a previous + * transaction + */ + return s->blockState != TBLOCK_DEFAULT && s->ybDataSent; +} + +/* + * Whether some data has been transmitted to frontend as part of this query. + */ +bool +YBIsDataSentForCurrQuery(void) +{ + TransactionState s = CurrentTransactionState; + + return s->ybDataSentForCurrQuery; +} /* ---------------------------------------------------------------- * StartTransaction stuff @@ -1292,6 +1386,9 @@ RecordTransactionCommit(void) bool RelcacheInitFileInval = false; bool wrote_xlog; + if (IsYugaByteEnabled() && !YbCurrentTxnUsesTempRel()) + return InvalidTransactionId; + /* * Log pending invalidations for logical decoding of in-progress * transactions. Normally for DDLs, we log this at each command end, @@ -1975,6 +2072,98 @@ AtSubCleanup_Memory(void) * ---------------------------------------------------------------- */ +static void +YBUpdateActiveSubTransaction(TransactionState s) +{ + YBCSetActiveSubTransaction(s->subTransactionId); +} + +/* + * Do a Yugabyte-specific initialization of transaction when it starts, + * called as a part of StartTransaction + */ +static void +YBStartTransaction(TransactionState s) +{ + elog(DEBUG2, "YBStartTransaction"); + s->ybTxnUsesTempRel = false; + s->ybDataSent = false; + s->ybDataSentForCurrQuery = false; + s->YBPostponedDdlOps = NULL; + + if (IsYugaByteEnabled()) + { + YBInitializeTransaction(); + } +} + +/* + * The isolation level in Postgres code (i.e., XactIsoLevel) maps to a certain + * isolation level as seen by pggate. This function returns the mapped isolation + * level that pggate layer is supposed to see. + */ +int +YBGetEffectivePggateIsolationLevel() +{ + int mapped_pg_isolation_level = XactIsoLevel; + + /* + * For the txn manager, logic for XACT_READ_UNCOMMITTED is same as + * XACT_READ_COMMITTED. + */ + if (mapped_pg_isolation_level == XACT_READ_UNCOMMITTED) + mapped_pg_isolation_level = XACT_READ_COMMITTED; + + /* + * If READ COMMITTED mode is not on, XACT_READ_COMMITTED maps to + * XACT_REPEATABLE_READ. + */ + if ((mapped_pg_isolation_level == XACT_READ_COMMITTED) && + !IsYBReadCommitted()) + mapped_pg_isolation_level = XACT_REPEATABLE_READ; + + return mapped_pg_isolation_level; +} + +static TimestampTz +ToUnixEpochUs(TimestampTz pg_timestamp) +{ + return pg_timestamp + + ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC); +} + +static void +YBRunWithInitTransactionData(YbcStatus (*Callback) (const YbcPgInitTransactionData *)) +{ + if (YBTransactionsEnabled()) + { + const YbcPgInitTransactionData data = + { + .xact_start_timestamp = ToUnixEpochUs(xactStartTimestamp), + .xact_read_only = XactReadOnly, + .xact_deferrable = XactDeferrable, + .enable_tracing = YBEnableTracing(), + .effective_pggate_isolation_level = YBGetEffectivePggateIsolationLevel(), + .read_from_followers_enabled = YBReadFromFollowersEnabled(), + .follower_read_staleness_ms = YBFollowerReadStalenessMs() + }; + + HandleYBStatus((*Callback) (&data)); + } +} + +void +YBInitializeTransaction(void) +{ + YBRunWithInitTransactionData(&YBCInitTransaction); +} + +void +YBCommitTransactionIntermediate(void) +{ + YBRunWithInitTransactionData(&YBCCommitTransactionIntermediate); +} + /* * StartTransaction */ @@ -2128,9 +2317,52 @@ StartTransaction(void) */ s->state = TRANS_INPROGRESS; + YBStartTransaction(s); + + s->ybUncommittedStickyObjectCount = 0; + + /* Check for superuser history to determine stickiness of connection */ + if (YbIsClientYsqlConnMgr() && OidIsValid(s->prevUser)) + yb_ysql_conn_mgr_superuser_existed = yb_ysql_conn_mgr_superuser_existed || superuser(); ShowTransactionState("StartTransaction"); } +/* + * Recreates the state required to restart the write that received a transaction + * conflict. + */ +void +YBCRestartWriteTransaction() +{ + /* + * Presence of triggers pushes additional snapshots. Pop all of them. Given + * that we restart the writes only when we haven't sent any data back to the + * user, removing all snapshots is safe. + */ + PopAllActiveSnapshots(); + + if (TopTransactionResourceOwner != NULL) + { + ResourceOwnerRelease(TopTransactionResourceOwner, + RESOURCE_RELEASE_BEFORE_LOCKS, + false, true); + ResourceOwnerRelease(TopTransactionResourceOwner, + RESOURCE_RELEASE_LOCKS, + false, true); + ResourceOwnerRelease(TopTransactionResourceOwner, + RESOURCE_RELEASE_AFTER_LOCKS, + false, true); + } + AtEOXact_SPI(false /* isCommit */ ); + AtEOXact_Snapshot(false, true); /* and release the transaction's snapshots */ + + /* + * Recreate the global state present for triggers that would have changed + * during the execution of the failed write. + */ + AfterTriggerEndXact(false /* isCommit */ ); + AfterTriggerBeginXact(); +} /* * CommitTransaction @@ -2143,6 +2375,12 @@ CommitTransaction(void) TransactionState s = CurrentTransactionState; TransactionId latestXid; bool is_parallel_worker; + instr_time yb_commit_starttime; + instr_time yb_commit_endtime; + uint64 elapsed_time; + + if (YbIsCommitStatsCollectionEnabled() && YbIsSessionStatsTimerEnabled()) + INSTR_TIME_SET_CURRENT(yb_commit_starttime); is_parallel_worker = (s->blockState == TBLOCK_PARALLEL_INPROGRESS); @@ -2160,6 +2398,9 @@ CommitTransaction(void) TransStateAsString(s->state)); Assert(s->parent == NULL); + if (IsYugaByteEnabled()) + YBCNotifyDeferredTriggersProcessingStarted(); + /* * Do pre-commit processing that involves calling user-defined code, such * as triggers. SECURITY_RESTRICTED_OPERATION contexts must not queue an @@ -2240,6 +2481,25 @@ CommitTransaction(void) /* Commit updates to the relation map --- do this as late as possible */ AtEOXact_RelationMap(true, is_parallel_worker); + YB_DIST_TRACE_START_SPAN("commit"); + if (IsYugaByteEnabled()) + { + bool increment_pg_txns = YbTrackPgTxnInvalMessagesForAnalyze(); + + /* + * Firing the triggers may abort current transaction. + * At this point all the them has been fired already. + * Also, we have executed the ON COMMIT actions which may include + * truncating temp tables. + * It is time to commit YB transaction. + * Postgres transaction can be aborted at this point without an issue + * in case of YBCCommitTransaction failure. + */ + YBCCommitTransaction(); + if (increment_pg_txns) + YbIncrementPgTxnsCommitted(); + } + /* * set the current transaction state information appropriately during * commit processing @@ -2270,6 +2530,7 @@ CommitTransaction(void) ParallelWorkerReportLastRecEnd(XactLastRecEnd); } + YB_DIST_TRACE_END_SPAN(); TRACE_POSTGRESQL_TRANSACTION_COMMIT(MyProc->lxid); /* @@ -2387,6 +2648,14 @@ CommitTransaction(void) */ s->state = TRANS_DEFAULT; + if (YbIsCommitStatsCollectionEnabled() && YbIsSessionStatsTimerEnabled()) + { + INSTR_TIME_SET_CURRENT(yb_commit_endtime); + INSTR_TIME_SUBTRACT(yb_commit_endtime, yb_commit_starttime); + elapsed_time = INSTR_TIME_GET_MICROSEC(yb_commit_endtime); + YbRecordCommitLatency(elapsed_time); + } + RESUME_INTERRUPTS(); } @@ -2551,8 +2820,11 @@ PrepareTransaction(void) StartPrepare(gxact); AtPrepare_Notify(); - AtPrepare_Locks(); - AtPrepare_PredicateLocks(); + if (YBGetObjectLockMode() == PG_OBJECT_LOCK_MODE) + { + AtPrepare_Locks(); + AtPrepare_PredicateLocks(); + } AtPrepare_PgStat(); AtPrepare_MultiXact(); AtPrepare_RelationMap(); @@ -2578,7 +2850,8 @@ PrepareTransaction(void) * ProcArrayClearTransaction(). Otherwise, a GetLockConflicts() would * conclude "xact already committed or aborted" for our locks. */ - PostPrepare_Locks(xid); + if (YBGetObjectLockMode() == PG_OBJECT_LOCK_MODE) + PostPrepare_Locks(xid); /* * Let others know about no transaction in progress by me. This has to be @@ -2619,7 +2892,8 @@ PrepareTransaction(void) PostPrepare_MultiXact(xid); - PostPrepare_PredicateLocks(xid); + if (YBGetObjectLockMode() == PG_OBJECT_LOCK_MODE) + PostPrepare_PredicateLocks(xid); ResourceOwnerRelease(TopTransactionResourceOwner, RESOURCE_RELEASE_LOCKS, @@ -2717,11 +2991,14 @@ AbortTransaction(void) /* Cancel condition variable sleep */ ConditionVariableCancelSleep(); - /* - * Also clean up any open wait for lock, since the lock manager will choke - * if we try to wait for another lock before doing this. - */ - LockErrorCleanup(); + if (YBGetObjectLockMode() == PG_OBJECT_LOCK_MODE) + { + /* + * Also clean up any open wait for lock, since the lock manager will choke + * if we try to wait for another lock before doing this. + */ + LockErrorCleanup(); + } /* * If any timeout events are still active, make sure the timeout interrupt @@ -2748,6 +3025,15 @@ AbortTransaction(void) TransStateAsString(s->state)); Assert(s->parent == NULL); + /* + * Invalidate the table cache for any tables which have been altered as part + * of the transaction. We do this before setting the transaction state to + * TRANS_ABORT since the invalidation requires us to fetch the Relation + * descriptor which requires us to be in a valid PG transaction block. + */ + if (IsYugaByteEnabled()) + YbInvalidateTableCacheForAlteredTables(); + /* * set the current transaction state information appropriately during the * abort processing @@ -2813,6 +3099,7 @@ AbortTransaction(void) XLogSetAsyncXactLSN(XactLastRecEnd); } + YB_DIST_TRACE_START_SPAN("abort"); TRACE_POSTGRESQL_TRANSACTION_ABORT(MyProc->lxid); /* @@ -2863,6 +3150,12 @@ AbortTransaction(void) pgstat_report_xact_timestamp(0); } + YBCAbortTransaction(); + YB_DIST_TRACE_END_SPAN(); + + /* Reset the value of the sticky connection */ + s->ybUncommittedStickyObjectCount = 0; + /* * State remains TRANS_ABORT until CleanupTransaction(). */ @@ -2919,11 +3212,12 @@ CleanupTransaction(void) } /* - * StartTransactionCommand + * YBStartTransactionCommandInternal */ void -StartTransactionCommand(void) +YBStartTransactionCommandInternal(bool yb_skip_read_committed_internal_savepoint) { + elog(DEBUG2, "YBStartTransactionCommandInternal"); TransactionState s = CurrentTransactionState; switch (s->blockState) @@ -2947,6 +3241,47 @@ StartTransactionCommand(void) case TBLOCK_INPROGRESS: case TBLOCK_IMPLICIT_INPROGRESS: case TBLOCK_SUBINPROGRESS: + /* + * YB specific logic. + * + * For READ COMMITTED isolation, we want to reset the read point to current ht time so that + * the query works on a newer snapshot that will include all txns committed before this + * command. + * + * Read restart handling per statement + * ----------------------------------- + * Note that by "all txns committed before this command" we intend to include any txn that + * might have been committed before the statement was issued, as per real time (i.e., as + * perceived by any client). + * + * Since there might be clock skew, during a read, if a txn participant finds committed + * records with ht after the chosen read ht and is unsure if the records were committed before + * the client issued read (as per real time), a kReadRestart will be received by postgres. + * + * Read restart retries are handled transparently for every statement in the txn in + * yb_attempt_to_restart_on_error(). + */ + if (YBTransactionsEnabled() && IsYBReadCommitted() && !yb_skip_read_committed_internal_savepoint) + { + /* + * Create a new internal sub txn before any execution. This aids in rolling back any changes + * before restarting the statement. + * + * We don't rely on the name of the internal sub transaction for rolling back to it in + * yb_attempt_to_restart_on_error(). We just assert that the name of the current sub txn + * matches before calling RollbackAndReleaseCurrentSubTransaction() to restart the + * statement. + * + * Instead of calling BeginInternalSubTransaction(), we have copy-pasted necessary logic + * into a new function since BeginInternalSubTransaction() again calls + * CommitTransactionCommand() and StartTransactionCommand() which will result in recursion. + * We could have solved the recursion problem by plumbing a flag to skip calling + * BeginInternalSubTransaction() again, but it is simpler and less error-prone to just copy + * the minimal required logic. + */ + YbBeginInternalSubTransactionForReadCommittedStatement(); + } + break; /* @@ -2989,6 +3324,49 @@ StartTransactionCommand(void) MemoryContextSwitchTo(CurTransactionContext); } +void +YbCommitTransactionCommandIntermediate(void) +{ + NodeTag yb_node_tag; + CommandTag yb_command_tag; + bool is_ddl_mode = YBCPgIsDdlMode(); + YbDdlMode ddl_mode; + + elog(DEBUG2, "YbCommitTransactionCommandIntermediate"); + + /* + * Remember the NodeTag and the CommandTag of the DDL currently being + * executed so that we can set it into the next transaction. + */ + if (YBIsDdlTransactionBlockEnabled() && is_ddl_mode) + { + yb_node_tag = YBGetCurrentStmtDdlNodeTag(); + yb_command_tag = YBGetCurrentStmtDdlCommandTag(); + ddl_mode = YBGetCurrentDdlMode(); + } + + if (ActiveSnapshotSet()) + PopActiveSnapshot(); + + CommitTransactionCommand(); + StartTransactionCommand(); + + if (YBIsDdlTransactionBlockEnabled() && is_ddl_mode) + { + YBAddDdlTxnState(ddl_mode); + YBSetDdlOriginalNodeAndCommandTag(yb_node_tag, yb_command_tag); + } +} + +/* + * StartTransactionCommand + */ +void +StartTransactionCommand(void) +{ + elog(DEBUG2, "StartTransactionCommand"); + YBStartTransactionCommandInternal(false /* yb_skip_read_committed_internal_savepoint */ ); +} /* * Simple system for saving and restoring transaction characteristics @@ -3014,6 +3392,40 @@ RestoreTransactionCharacteristics(const SavedTransactionCharacteristics *s) XactDeferrable = s->save_XactDeferrable; } +void +YbSetTxnUsesTempRel() +{ + TransactionState s = CurrentTransactionState; + + /* + * TODO(kramanathan): This flag needs to be rolled back appropriately when + * rolling back a sub-transaction. Currently, the flag(s) being set is + * persisted until commit/abort, even if the transaction at commit does not + * end up performing the operation whose flag is being set. + */ + while (s != NULL) + { + s->ybTxnUsesTempRel = true; + s = s->parent; + } +} + +void +YBMarkTxnUsesTempRelAndSetTxnId() +{ + YbSetTxnUsesTempRel(); + /* + * Invoke GetCurrentTransactionId() to assign a txn id + * as we we want to use PG txn code paths for txns that use temp relations. + */ + GetCurrentTransactionId(); +} + +bool +YbCurrentTxnUsesTempRel(void) +{ + return CurrentTransactionState->ybTxnUsesTempRel; +} /* * CommitTransactionCommand @@ -3021,12 +3433,16 @@ RestoreTransactionCharacteristics(const SavedTransactionCharacteristics *s) void CommitTransactionCommand(void) { + elog(DEBUG2, "CommitTransactionCommand"); TransactionState s = CurrentTransactionState; SavedTransactionCharacteristics savetc; /* Must save in case we need to restore below */ SaveTransactionCharacteristics(&savetc); + /* TODO(jayant): add YB prefix to prevState. */ + TBlockState prevState = s->blockState; + switch (s->blockState) { /* @@ -3285,6 +3701,55 @@ CommitTransactionCommand(void) } break; } + + /* Update the session parameter values in the shared memory */ + if (YbIsClientYsqlConnMgr()) + { + /* + * At the end of a single query transaction (when autocommit is enabled) + * the blockState will be TBLOCK_STARTED. + * At the end of a normal transaction (when autocommit is disabled) + * the blockState will be TBLOCK_END. + * So in the case of TBLOCK_ENDand TBLOCK_STARTED, + * + * UpdateSharedMemory is called at the end of a transaction. + * i.e. TBLOCK_END and TBLOCK_STARTED, not TBLOCK_BEGIN. + * This is done to update the shared memory in case any + * session parameter might have changed. + * + * YbCleanChangedSessionParameter is called both at the beginning + * and at the end of the transaction (after updating shared memory) . + * YbCleanChangedSessionParameter basically cleans the local cach, so + * when a logical connection is attached to a new physical connection, + * the cach needs to be cleaned. Also once this cach has been used to + * update the shared memory (YbUpdateSharedMemory) this cach should be + * cleaned. + */ + switch (prevState) + { + case TBLOCK_END: /* COMMIT received */ + case TBLOCK_STARTED: /* running single-query transaction */ + + /* + * Copy the session parameter from the local memory to the + * shared memory + */ + YbUpdateSharedMemory(); + + YbCleanChangedSessionParameters(); + break; + case TBLOCK_BEGIN: + YbCleanChangedSessionParameters(); + break; + default: + + /* + * do nothing for sub transaction, process changed session + * parameters only at the end of the transaction. + */ + break; + } + } } /* @@ -3616,7 +4081,8 @@ IsInTransactionBlock(bool isTopLevel) if (!isTopLevel) return true; - if (CurrentTransactionState->blockState != TBLOCK_DEFAULT && + if (!IsYugaByteEnabled() && + CurrentTransactionState->blockState != TBLOCK_DEFAULT && CurrentTransactionState->blockState != TBLOCK_STARTED) return true; @@ -3748,6 +4214,7 @@ CallSubXactCallbacks(SubXactEvent event, void BeginTransactionBlock(void) { + elog(DEBUG2, "BeginTransactionBlock"); TransactionState s = CurrentTransactionState; switch (s->blockState) @@ -3799,6 +4266,10 @@ BeginTransactionBlock(void) BlockStateAsString(s->blockState)); break; } + + /* YB: Notify pggate that we are within a txn block. */ + if (IsYugaByteEnabled()) + HandleYBStatus(YBCPgSetInTxnBlock(true)); } /* @@ -4163,6 +4634,10 @@ BeginImplicitTransactionBlock(void) */ if (s->blockState == TBLOCK_STARTED) s->blockState = TBLOCK_IMPLICIT_INPROGRESS; + + /* YB: Notify pggate that we are within an (implicit) txn block. */ + if (IsYugaByteEnabled()) + HandleYBStatus(YBCPgSetInTxnBlock(true)); } /* @@ -4218,6 +4693,8 @@ DefineSavepoint(const char *name) /* Normal subtransaction start */ PushTransaction(); s = CurrentTransactionState; /* changed by push */ + elog(DEBUG2, "YB: new sub txn created by savepoint, subtxn_id: %d", + s->subTransactionId); /* * Savepoint names, like the TransactionState block itself, live @@ -4503,6 +4980,8 @@ RollbackToSavepoint(const char *name) else elog(FATAL, "RollbackToSavepoint: unexpected state %s", BlockStateAsString(xact->blockState)); + + YBCRollbackToSubTransaction(target->subTransactionId); } /* @@ -4518,6 +4997,13 @@ RollbackToSavepoint(const char *name) void BeginInternalSubTransaction(const char *name) { + /* + * YB: The subtransaction corresponding to the buffered operations must be + * current and in the INPROGRESS state for correct error handling. + * An error thrown while/after switching over to a new subtransaction + * would lead to a fatal error or unpredictable behavior. + */ + YBFlushBufferedOperations(YBCMakeFlushDebugContextBeginSubTxn(CurrentTransactionState->subTransactionId, name)); TransactionState s = CurrentTransactionState; /* @@ -4546,6 +5032,8 @@ BeginInternalSubTransaction(const char *name) /* Normal subtransaction start */ PushTransaction(); s = CurrentTransactionState; /* changed by push */ + elog(DEBUG2, "YB: new sub txn created internally, subtxn_id: %d", + s->subTransactionId); /* * Savepoint names, like the TransactionState block itself, live @@ -4576,7 +5064,66 @@ BeginInternalSubTransaction(const char *name) } CommitTransactionCommand(); - StartTransactionCommand(); + YBStartTransactionCommandInternal(true /* yb_skip_read_committed_internal_savepoint */ ); +} + +/* + * YbBeginInternalSubTransactionForReadCommittedStatement + * This is similar to BeginInternalSubTransaction() but doesn't call CommitTransactionCommand() + * and StartTransactionCommand(). It is okay to not call those since this method is called only + * in 2 specific cases (i.e., when starting a new statement in an already existing txn in + * READ COMMITED mode, or when rolling back to the internal sub txn while restarting a + * statement) and both cases satisfy the following property - + * CurrentTransactionState->blockState is TBLOCK_INPROGRESS, TBLOCK_IMPLICIT_INPROGRESS or + * TBLOCK_SUBINPROGRESS. + */ +void +YbBeginInternalSubTransactionForReadCommittedStatement() +{ + YBFlushBufferedOperations(YBCMakeFlushDebugContextBeginSubTxn(CurrentTransactionState->subTransactionId, "read committed transaction")); + TransactionState s = CurrentTransactionState; + + Assert(s->blockState == TBLOCK_SUBINPROGRESS || + s->blockState == TBLOCK_IMPLICIT_INPROGRESS || + s->blockState == TBLOCK_INPROGRESS); + + if (IsInParallelMode()) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TRANSACTION_STATE), + errmsg("cannot start subtransactions during a parallel operation"))); + + /* Normal subtransaction start */ + PushTransaction(); + s = CurrentTransactionState; /* changed by push */ + elog(DEBUG2, "new internal sub txn in READ COMMITTED subtxn_id: %d", s->subTransactionId); + + s->name = MemoryContextStrdup(TopTransactionContext, YB_READ_COMMITTED_INTERNAL_SUB_TXN_NAME); + + StartSubTransaction(); + s->blockState = TBLOCK_SUBINPROGRESS; +} + +bool +YBTransactionContainsNonReadCommittedSavepoint(void) +{ + if (!IsTransactionBlock()) + return false; + + if (!IsSubTransaction()) + return false; + + TransactionState s = CurrentTransactionState; + + while (s != NULL) + { + if (s->name != NULL && + strcmp(s->name, YB_READ_COMMITTED_INTERNAL_SUB_TXN_NAME) != 0) + return true; + + s = s->parent; + } + + return false; } /* @@ -4589,6 +5136,13 @@ BeginInternalSubTransaction(const char *name) void ReleaseCurrentSubTransaction(void) { + /* + * YB: The subtransaction corresponding to the buffered operations must be + * current and in the INPROGRESS state for correct error handling. + * An error thrown while/after commiting/releasing it would lead to a + * fatal error or unpredictable behavior. + */ + YBFlushBufferedOperations(YBCMakeFlushDebugContextEndSubTxn(CurrentTransactionState->subTransactionId)); TransactionState s = CurrentTransactionState; /* @@ -4690,6 +5244,7 @@ RollbackAndReleaseCurrentSubTransaction(void) void AbortOutOfAnyTransaction(void) { + elog(DEBUG2, "YB: AbortOutOfAnyTransaction"); TransactionState s = CurrentTransactionState; /* Ensure we're not running in a doomed memory context */ @@ -4832,7 +5387,9 @@ TransactionBlockStatusCode(void) { case TBLOCK_DEFAULT: case TBLOCK_STARTED: - return 'I'; /* idle --- not in transaction */ + return ((YbIsClientYsqlConnMgr() && + YbIsStickyConnection(&(s->ybUncommittedStickyObjectCount))) + ? 'i' : 'I'); /* idle --- not in transaction */ case TBLOCK_BEGIN: case TBLOCK_SUBBEGIN: case TBLOCK_INPROGRESS: @@ -4916,6 +5473,13 @@ StartSubTransaction(void) s->parent->subTransactionId); ShowTransactionState("StartSubTransaction"); + + /* + * YB: Update the value of the sticky objects from parent transaction + */ + if (CurrentTransactionState->parent) + CurrentTransactionState->ybUncommittedStickyObjectCount = + CurrentTransactionState->parent->ybUncommittedStickyObjectCount; } /* @@ -5027,6 +5591,9 @@ CommitSubTransaction(void) s->state = TRANS_DEFAULT; + /* YB: Conserve sticky object count before popping transaction state. */ + s->parent->ybUncommittedStickyObjectCount = s->ybUncommittedStickyObjectCount; + PopTransaction(); } @@ -5176,6 +5743,8 @@ AbortSubTransaction(void) AtSubAbort_Snapshot(s->nestingLevel); } + YBCRollbackToSubTransaction(s->subTransactionId); + /* * Restore the upper transaction's read-only state, too. This should be * redundant with GUC's cleanup but we may as well do it for consistency @@ -5228,6 +5797,8 @@ CleanupSubTransaction(void) static void PushTransaction(void) { + elog(DEBUG2, "YB: PushTransaction increment sub-txn id from %d -> %d", + currentSubTransactionId, currentSubTransactionId + 1); TransactionState p = CurrentTransactionState; TransactionState s; @@ -5269,8 +5840,13 @@ PushTransaction(void) s->parallelModeLevel = 0; s->topXidLogged = false; + s->ybDataSentForCurrQuery = p->ybDataSentForCurrQuery; + s->ybDataSent = p->ybDataSent; + CurrentTransactionState = s; + YBUpdateActiveSubTransaction(CurrentTransactionState); + /* * AbortSubTransaction and CleanupSubTransaction have to be able to cope * with the subtransaction from here on out; in particular they should not @@ -5289,6 +5865,7 @@ PushTransaction(void) static void PopTransaction(void) { + elog(DEBUG2, "YB: PopTransaction sub-txn id %d", currentSubTransactionId); TransactionState s = CurrentTransactionState; if (s->state != TRANS_DEFAULT) @@ -5298,7 +5875,13 @@ PopTransaction(void) if (s->parent == NULL) elog(FATAL, "PopTransaction with no parent"); + /* Propagate the data sent information to the parent. */ + s->parent->ybDataSent = s->parent->ybDataSent || s->ybDataSent; + s->parent->ybDataSentForCurrQuery = (s->parent->ybDataSentForCurrQuery || + s->ybDataSentForCurrQuery); + CurrentTransactionState = s->parent; + YBUpdateActiveSubTransaction(CurrentTransactionState); /* Let's just make sure CurTransactionContext is good */ CurTransactionContext = s->parent->curTransactionContext; @@ -5488,11 +6071,14 @@ ShowTransactionStateRec(const char *str, TransactionState s) ShowTransactionStateRec(str, s->parent); ereport(DEBUG5, - (errmsg_internal("%s(%d) name: %s; blockState: %s; state: %s, xid/subid/cid: %u/%u/%u%s%s", + (errmsg_internal("%s(%d) name: %s; blockState: %s; " + "state: %s, ybDataSent: %s, ybDataSentForCurrQuery: %s, xid/subid/cid: %u/%u/%u%s%s", str, s->nestingLevel, PointerIsValid(s->name) ? s->name : "unnamed", BlockStateAsString(s->blockState), TransStateAsString(s->state), + s->ybDataSent ? "Y" : "N", + s->ybDataSentForCurrQuery ? "Y" : "N", (unsigned int) XidFromFullTransactionId(s->fullTransactionId), (unsigned int) s->subTransactionId, (unsigned int) currentCommandId, @@ -6248,3 +6834,58 @@ xact_redo(XLogReaderState *record) else elog(PANIC, "xact_redo: unknown op code %u", info); } + +void +YBSaveDdlHandle(YbcPgStatement handle) +{ + CurrentTransactionState->YBPostponedDdlOps = lappend(CurrentTransactionState->YBPostponedDdlOps, handle); +} + +List * +YBGetDdlHandles() +{ + return CurrentTransactionState->YBPostponedDdlOps; +} + +void +YBClearDdlHandles() +{ + CurrentTransactionState->YBPostponedDdlOps = NULL; +} + +/* + * YbClearParallelContexts + * Clean up parallel contexts as a part of transparent query restart. + */ +void +YbClearParallelContexts() +{ + TransactionState s = CurrentTransactionState; + + Assert(IsInParallelMode()); + if (s->subTransactionId == InvalidSubTransactionId) + AtEOXact_Parallel(false); + else + AtEOSubXact_Parallel(false, s->subTransactionId); + ExitParallelMode(); +} + +/* + * ```increment_sticky_object_count()``` is called when any database object which requires + * stickiness is created. + */ +void +increment_sticky_object_count() +{ + CurrentTransactionState->ybUncommittedStickyObjectCount++; +} + +/* + * ```decrement_sticky_object_count()``` is called when any database object which required + * stickiness is deleted. + */ +void +decrement_sticky_object_count() +{ + CurrentTransactionState->ybUncommittedStickyObjectCount--; +} diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 99da2545202d2..1adc67d5d530d 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -130,7 +130,13 @@ bool wal_init_zero = true; bool wal_recycle = true; bool log_checkpoints = true; int sync_method = DEFAULT_SYNC_METHOD; -int wal_level = WAL_LEVEL_MINIMAL; + +/* + * YB NOTE: wal_level is not applicable to YB. So for user experience, we set + * the default to logical, so that any logical replication client doesn't throw + * any errors based on the value of the wal_level. + */ +int wal_level = WAL_LEVEL_LOGICAL; int CommitDelay = 0; /* precommit delay in microseconds */ int CommitSiblings = 5; /* # concurrent xacts needed to sleep */ int wal_retrieve_retry_interval = 5000; diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index bec89665836af..6d9638da03e79 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -38,6 +38,9 @@ #include "utils/timestamp.h" #include "utils/tuplestore.h" +/* YB includes */ +#include "pg_yb_utils.h" + /* * Store label file and tablespace map during backups. */ @@ -231,6 +234,13 @@ pg_current_wal_lsn(PG_FUNCTION_ARGS) errmsg("recovery is in progress"), errhint("WAL control functions cannot be executed during recovery."))); + if (IsYugaByteEnabled()) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("pg_current_wal_lsn() is not yet supported"), + errhint("See https://github.com/yugabyte/yugabyte-db/issues/" + "30243. React with thumbs up to raise its priority."))); + current_recptr = GetXLogWriteRecPtr(); PG_RETURN_LSN(current_recptr); diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index 2f1b078937e2f..8e629bb1bd958 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -41,6 +41,11 @@ #include "common/logging.h" #endif +/* YB includes */ +#ifndef FRONTEND +#include "pg_yb_utils.h" +#endif + static void report_invalid_record(XLogReaderState *state, const char *fmt,...) pg_attribute_printf(2, 3); static void allocate_recordbuf(XLogReaderState *state, uint32 reclength); @@ -151,6 +156,11 @@ XLogReaderAllocate(int wal_segment_size, const char *waldir, } state->errormsg_buf[0] = '\0'; +#ifndef FRONTEND + if (IsYugaByteEnabled()) + state->yb_virtual_wal_record = NULL; +#endif + /* * Allocate an initial readRecordBuf of minimal size, which can later be * enlarged if necessary. @@ -1617,6 +1627,12 @@ ResetDecoder(XLogReaderState *state) state->errormsg_deferred = false; } +void +YBResetDecoder(XLogReaderState *state) +{ + ResetDecoder(state); +} + /* * Compute the maximum possible amount of padding that could be required to * decode a record, given xl_tot_len from the record's header. This is the diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c index 1fa1547115a08..d0b1ba1c32c3b 100644 --- a/src/backend/access/transam/xlogrecovery.c +++ b/src/backend/access/transam/xlogrecovery.c @@ -1811,6 +1811,7 @@ PerformWalRecovery(void) recoveryPausesHere(true); /* drop into promote */ + yb_switch_fallthrough(); case RECOVERY_TARGET_ACTION_PROMOTE: break; diff --git a/src/backend/access/yb_access/Makefile b/src/backend/access/yb_access/Makefile new file mode 100644 index 0000000000000..7d5300a61c416 --- /dev/null +++ b/src/backend/access/yb_access/Makefile @@ -0,0 +1,20 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for access/yb_access +# +# IDENTIFICATION +# src/backend/access/yb_access/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/backend/access/yb_access +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = \ + yb_scan.o \ + yb_lsm.o \ + yb_pg_inherits_scan.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/yb_access/yb_lsm.c b/src/backend/access/yb_access/yb_lsm.c new file mode 100644 index 0000000000000..6c414f07101eb --- /dev/null +++ b/src/backend/access/yb_access/yb_lsm.c @@ -0,0 +1,815 @@ +/*-------------------------------------------------------------------------------------------------- + * + * yb_lsm.c + * Implementation of YugaByte indexes. + * + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations + * under the License. + * + * src/backend/access/yb_access/yb_lsm.c + *-------------------------------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/nbtree.h" +#include "access/reloptions.h" +#include "access/relscan.h" +#include "access/sysattr.h" +#include "access/yb_scan.h" +#include "catalog/index.h" +#include "catalog/pg_type.h" +#include "commands/yb_cmds.h" +#include "executor/ybModifyTable.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "utils/rel.h" + +static void ybcinendscan(IndexScanDesc scan); + +/* --------------------------------------------------------------------------------------------- */ + +/* Working state for ybcinbuild and its callback */ +typedef struct +{ + bool isprimary; /* are we building a primary index? */ + double index_tuples; /* # of tuples inserted into index */ + /* + * Write time for rows written to index as part of online index backfill. + * This field being non-null signifies that we are doing online index + * backfill. + */ + const uint64_t *backfill_write_time; +} YbLsmBuildState; + + +/* + * Utility method to set binds for index write statement. + */ +static void +doBindsForIdxWrite(YbcPgStatement stmt, + void *indexstate, + Relation index, + Datum *values, + bool *isnull, + int n_bound_atts, + Datum ybbasectid, + bool ybctid_as_value) +{ + TupleDesc tupdesc = RelationGetDescr(index); + int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index); + + if (ybbasectid == 0) + { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("missing base table ybctid in index write request"))); + } + + bool has_null_attr = false; + + for (AttrNumber attnum = 1; attnum <= n_bound_atts; ++attnum) + { + Oid type_id = GetTypeId(attnum, tupdesc); + Oid collation_id = YBEncodingCollation(stmt, attnum, + ybc_get_attcollation(tupdesc, attnum)); + Datum value = values[attnum - 1]; + bool is_null = isnull[attnum - 1]; + + YbBindDatumToColumn(stmt, + attnum, + type_id, + collation_id, + value, + is_null, + NULL /* null_type_entity */ ); + + + /* + * If any of the indexed columns is null, we need to take case of + * SQL null != null semantics. + * For details, see comment on kYBUniqueIdxKeySuffix. + */ + has_null_attr = has_null_attr || (is_null && attnum <= indnkeyatts); + } + + const bool unique_index = index->rd_index->indisunique; + + /* + * For unique indexes we need to set the key suffix system column: + * - to ybbasectid if the index uses nulls-are-distinct mode and at least + * one index key column is null. + * - to NULL otherwise (setting is_null to true is enough). + */ + if (unique_index) + YbBindDatumToColumn(stmt, + YBUniqueIdxKeySuffixAttributeNumber, + BYTEAOID, + InvalidOid, + ybbasectid, + index->rd_index->indnullsnotdistinct || !has_null_attr /* is_null */ , + NULL /* null_type_entity */ ); + + /* + * We may need to set the base ctid column: + * - for unique indexes only if we need it as a value (i.e. for inserts) + * - for non-unique indexes always (it is a key column). + */ + if (ybctid_as_value || !unique_index) + YbBindDatumToColumn(stmt, + YBIdxBaseTupleIdAttributeNumber, + BYTEAOID, + InvalidOid, + ybbasectid, + false /* is_null */ , + NULL /* null_type_entity */ ); + +} + +static void +doAssignForIdxUpdate(YbcPgStatement stmt, + Relation index, + Datum *values, + bool *isnull, + int n_atts, + Datum old_ybbasectid, + Datum new_ybbasectid) +{ + TupleDesc tupdesc = RelationGetDescr(index); + int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index); + + if (old_ybbasectid == 0) + { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("missing base table ybctid in index write request"))); + } + + bool has_null_attr = false; + + for (AttrNumber attnum = 1; attnum <= n_atts; ++attnum) + { + Oid type_id = GetTypeId(attnum, tupdesc); + Oid collation_id = YBEncodingCollation(stmt, attnum, + ybc_get_attcollation(tupdesc, attnum)); + Datum value = values[attnum - 1]; + bool is_null = isnull[attnum - 1]; + + YbcPgExpr ybc_expr = YBCNewConstant(stmt, type_id, collation_id, value, is_null); + + /* + * Attrs that are a part of the index key are 'bound' to their values. + * It is guaranteed by YbExecUpdateIndexTuples that the values of these + * attrs are unmodified. The non-key attrs are 'assigned' to their + * new (updated) values. These represent the values that are undergoing + * the update. + */ + if (attnum <= indnkeyatts) + { + HandleYBStatus(YBCPgDmlBindColumn(stmt, attnum, ybc_expr)); + + /* + * In a unique index, if any of the key columns are NULL, we need + * to handle NULL != NULL semantics. + */ + has_null_attr = has_null_attr || is_null; + } + else + HandleYBStatus(YBCPgDmlAssignColumn(stmt, attnum, ybc_expr)); + } + + bool unique_index = index->rd_index->indisunique; + + /* + * For a non-unique index, the base table CTID attribute is a part of the + * index key. Therefore, updates to the primary key require the index + * tuple to be deleted and re-inserted, and will not utilize this function. + * For a unique index, the base table CTID attribute is not a part of the + * index key and can be updated in place. Handle that here. + */ + if (new_ybbasectid != (Datum) NULL) + { + Assert(unique_index); + + YbcPgExpr ybc_expr = YBCNewConstant(stmt, BYTEAOID, InvalidOid, new_ybbasectid, false); + + HandleYBStatus(YBCPgDmlAssignColumn(stmt, YBIdxBaseTupleIdAttributeNumber, ybc_expr)); + } + + /* + * Bind to key columns that do not have an attnum in postgres: + * - For non-unique indexes, this is the base table CTID. + * - For unique indexes, this is the unique key suffix. + */ + if (!unique_index) + YbBindDatumToColumn(stmt, + YBIdxBaseTupleIdAttributeNumber, + BYTEAOID, + InvalidOid, + old_ybbasectid, + false, + NULL /* null_type_entity */ ); + + else + YbBindDatumToColumn(stmt, + YBUniqueIdxKeySuffixAttributeNumber, + BYTEAOID, + InvalidOid, + old_ybbasectid, + index->rd_index->indnullsnotdistinct || !has_null_attr /* is_null */ , + NULL /* null_type_entity */ ); +} + +static void +ybcinbuildCallback(Relation index, Datum ybctid, Datum *values, + bool *isnull, bool tupleIsAlive, void *state) +{ + YbLsmBuildState *buildstate = (YbLsmBuildState *) state; + + if (!buildstate->isprimary) + YBCExecuteInsertIndex(index, + values, + isnull, + ybctid, + buildstate->backfill_write_time, + doBindsForIdxWrite, + NULL /* indexstate */ ); + + buildstate->index_tuples += 1; +} + +static IndexBuildResult * +ybcinbuild(Relation heap, Relation index, struct IndexInfo *indexInfo) +{ + YbLsmBuildState buildstate; + double heap_tuples = 0; + + /* Do the heap scan */ + buildstate.isprimary = index->rd_index->indisprimary; + buildstate.index_tuples = 0; + buildstate.backfill_write_time = NULL; + /* + * YB: Primary key index is an implicit part of the base table in Yugabyte. + * We don't need to scan the base table to build a primary key index. (#8024) + * We also don't need to build YB indexes during a major version upgrade, + * as we simply link the old DocDB table on master. + */ + if (!index->rd_index->indisprimary && + !(IsYugaByteEnabled() && IsBinaryUpgrade)) + { + heap_tuples = yb_table_index_build_scan(heap, index, indexInfo, true, + ybcinbuildCallback, &buildstate, + NULL); + } + /* + * Return statistics + */ + IndexBuildResult *result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult)); + + result->heap_tuples = heap_tuples; + result->index_tuples = buildstate.index_tuples; + return result; +} + +static IndexBuildResult * +ybcinbackfill(Relation heap, + Relation index, + struct IndexInfo *indexInfo, + YbBackfillInfo *bfinfo, + YbPgExecOutParam *bfresult) +{ + YbLsmBuildState buildstate; + double heap_tuples = 0; + + /* Do the heap scan */ + buildstate.isprimary = index->rd_index->indisprimary; + buildstate.index_tuples = 0; + /* Backfilled rows should be as if they happened at the time of backfill */ + buildstate.backfill_write_time = &bfinfo->read_time; + heap_tuples = IndexBackfillHeapRangeScan(heap, + index, + indexInfo, + ybcinbuildCallback, + &buildstate, + bfinfo, + bfresult); + + /* + * Return statistics + */ + IndexBuildResult *result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult)); + + result->heap_tuples = heap_tuples; + result->index_tuples = buildstate.index_tuples; + return result; +} + +static void +ybcinbuildempty(Relation index) +{ + YBC_LOG_WARNING("Unexpected building of empty unlogged index"); +} + +static bool +ybcininsert(Relation index, Datum *values, bool *isnull, Datum ybctid, Relation heap, + IndexUniqueCheck checkUnique, struct IndexInfo *indexInfo, bool sharedInsert) +{ + if (!index->rd_index->indisprimary) + { + if (sharedInsert) + { + if (!IsYsqlUpgrade) + elog(ERROR, "shared insert cannot be done outside of YSQL upgrade"); + + YB_FOR_EACH_DB(pg_db_tuple) + { + Oid dboid = ((Form_pg_database) GETSTRUCT(pg_db_tuple))->oid; + + /* + * Since this is a catalog index, we assume it exists in all databases. + * YB doesn't use PG locks so it's okay not to take them. + */ + YBCExecuteInsertIndexForDb(dboid, + index, + values, + isnull, + ybctid, + NULL /* backfill_write_time */ , + doBindsForIdxWrite, + NULL /* indexstate */ ); + } + YB_FOR_EACH_DB_END; + } + else + YBCExecuteInsertIndex(index, + values, + isnull, + ybctid, + NULL /* backfill_write_time */ , + doBindsForIdxWrite, + NULL /* indexstate */ ); + } + + return index->rd_index->indisunique ? true : false; +} + +static void +ybcindelete(Relation index, Datum *values, bool *isnull, Datum ybctid, Relation heap, + struct IndexInfo *indexInfo) +{ + if (!index->rd_index->indisprimary) + YBCExecuteDeleteIndex(index, values, isnull, ybctid, + doBindsForIdxWrite, NULL /* indexstate */ ); +} + +static void +ybcinupdate(Relation index, Datum *values, bool *isnull, Datum oldYbctid, + Datum newYbctid, Relation heap, struct IndexInfo *indexInfo) +{ + Assert(!index->rd_index->indisprimary); + YBCExecuteUpdateIndex(index, values, isnull, oldYbctid, newYbctid, + doAssignForIdxUpdate); +} + +static IndexBulkDeleteResult * +ybcinbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, + IndexBulkDeleteCallback callback, void *callback_state) +{ + YBC_LOG_WARNING("Unexpected bulk delete of index via vacuum"); + return NULL; +} + +/* + * Post-VACUUM cleanup. + * + * Result: a palloc'd struct containing statistical info for VACUUM displays. + * + * YB: this function is based on btvacuumcleanup implementation. + * We only support ANALYZE and don't support VACUUM as of 04/05/2024. + */ +static IndexBulkDeleteResult * +ybcinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) +{ + /* No-op in ANALYZE ONLY mode */ + if (info->analyze_only) + return stats; + + YBC_LOG_WARNING("Unexpected index cleanup via vacuum"); + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +static bool +ybcincanreturn(Relation index, int attno) +{ + /* + * If "canreturn" is true, Postgres will attempt to perform index-only scan on the indexed + * columns and expect us to return the column values as an IndexTuple. This will be the case + * for secondary index. + * + * For indexes which are primary keys, we will return the table row as a HeapTuple instead. + * For this reason, we set "canreturn" to false for primary keys. + */ + return !index->rd_index->indisprimary; +} + +static bool +ybcinmightrecheck(Scan *scan, Relation heap, Relation index, bool xs_want_itup, + ScanKey keys, int nkeys) +{ + return YbPredetermineNeedsRecheck(scan, heap, index, xs_want_itup, keys, nkeys); +} + +static int64 +ybcgetbitmap(IndexScanDesc scan, YbTIDBitmap *ybtbm) +{ + size_t new_tuples = 0; + YbcSliceVector ybctids; + YbScanDesc ybscan = (YbScanDesc) scan->opaque; + bool exceeded_work_mem = false; + + ybscan->exec_params = scan->yb_exec_params; + /* exec_params can be NULL in case of systable_getnext, for example. */ + if (ybscan->exec_params) + ybscan->exec_params->work_mem = work_mem; + + if (!ybscan->is_exec_done) + pgstat_count_index_scan(scan->indexRelation); + + if (ybscan->quit_scan || ybtbm->work_mem_exceeded) + return 0; + + HandleYBStatus(YBCPgRetrieveYbctids(ybscan->handle, ybscan->exec_params, + ybscan->target_desc->natts, &ybctids, &new_tuples, + &exceeded_work_mem)); + if (!exceeded_work_mem) + { + yb_tbm_add_tuples(ybtbm, ybctids); + /* Got some row that may be actually not matching */ + if (scan->xs_recheck && new_tuples > 0) + ybtbm->recheck_required = true; + } + else + yb_tbm_set_work_mem_exceeded(ybtbm); + + YBCBitmapShallowDeleteVector(ybctids); + + return ybtbm->work_mem_exceeded ? 0 : new_tuples; +} + +static void +ybcincostestimate(struct PlannerInfo *root, struct IndexPath *path, double loop_count, + Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, + double *indexCorrelation, double *indexPages) +{ + ybcIndexCostEstimate(root, + path, + indexSelectivity, + indexStartupCost, + indexTotalCost); + *indexCorrelation = 0.0; + *indexPages = 0.0; +} + +static bytea * +ybcinoptions(Datum reloptions, bool validate) +{ + return default_reloptions(reloptions, validate, RELOPT_KIND_YB_LSM); +} + +static bool +ybcinproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, + bool *res, bool *isnull) +{ + return false; +} + +static bool +ybcinvalidate(Oid opclassoid) +{ + return true; +} + +/* --------------------------------------------------------------------------------------------- */ + +static IndexScanDesc +ybcinbeginscan(Relation rel, int nkeys, int norderbys) +{ + IndexScanDesc scan; + + /* no order by operators allowed */ + Assert(norderbys == 0); + + /* get the scan */ + scan = RelationGetIndexScan(rel, nkeys, norderbys); + scan->opaque = NULL; + + return scan; +} + +/* + * ybcinparallel_prepare + * + * Prepare the YB parallel scan state to fetch the key ranges from DocDB: + * make sure the relation and directions are set correctly. + * Returns the YBParallelPartitionKeys, which is expected to be a part of the + * parallel index scan state, and already initialized. + */ +static YBParallelPartitionKeys +ybcinparallel_prepare(IndexScanDesc scan) +{ + Assert(scan->parallel_scan); + YBParallelPartitionKeys pscan; + ParallelIndexScanDesc target = scan->parallel_scan; + ScanDirection direction = ForwardScanDirection; + + pscan = (YBParallelPartitionKeys) OffsetToPointer(target, target->ps_offset); + Relation rel = scan->indexRelation; + + /* If scan is by the PK, use the main relation instead */ + if (rel->rd_index && rel->rd_index->indisprimary) + { + Assert(scan->heapRelation); + elog(LOG, "Scan is by PK, get parallel ranges from the main table"); + rel = scan->heapRelation; + } + if (scan->yb_scan_plan) + { + if (IsA(scan->yb_scan_plan, IndexScan)) + direction = ((IndexScan *) scan->yb_scan_plan)->indexorderdir; + else if (IsA(scan->yb_scan_plan, IndexOnlyScan)) + direction = ((IndexOnlyScan *) scan->yb_scan_plan)->indexorderdir; + } + ybParallelPrepare(pscan, rel, scan->yb_exec_params, !ScanDirectionIsBackward(direction)); + return pscan; +} + +static void +ybcinrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, ScanKey orderbys, int norderbys) +{ + if (scan->opaque) + { + ybcinendscan(scan); + scan->opaque = NULL; + } + + YbScanDesc ybScan = YbBeginScan(scan->heapRelation, + scan->indexRelation, + scan->xs_want_itup, + nscankeys, + scankey, + scan->yb_scan_plan, + scan->yb_rel_pushdown, + scan->yb_idx_pushdown, + scan->yb_aggrefs, + scan->yb_distinct_prefixlen, + scan->yb_exec_params, + false, /* is_internal_scan */ + scan->fetch_ybctids_only); + + /* For LSM indexes, we either recheck all rows or no rows. */ + scan->xs_recheck = YbNeedsPgRecheck(ybScan); + + scan->opaque = ybScan; + if (scan->parallel_scan) + ybScan->pscan = ybcinparallel_prepare(scan); +} + +static void +ybcinparallelrescan(IndexScanDesc scan) +{ + Assert(scan->opaque); + YbScanDesc ybScan = (YbScanDesc) scan->opaque; + yb_rescan_partition_key_data(ybScan->pscan); + YBParallelPartitionKeys pscan PG_USED_FOR_ASSERTS_ONLY = ybcinparallel_prepare(scan); + Assert(pscan == ybScan->pscan); +} + +/* + * Processing the following SELECT. + * SELECT data FROM heapRelation WHERE rowid IN + * ( SELECT rowid FROM indexRelation WHERE key = given_value ) + * + * TODO(neil) Postgres layer should make just one request for IndexScan. + * - Query ROWID from IndexTable using key. + * - Query data from Table (relation) using ROWID. + */ +static bool +ybcingettuple(IndexScanDesc scan, ScanDirection dir) +{ + YbScanDesc ybscan = (YbScanDesc) scan->opaque; + + ybscan->exec_params = scan->yb_exec_params; + /* exec_params can be NULL in case of systable_getnext, for example. */ + if (ybscan->exec_params) + ybscan->exec_params->work_mem = work_mem; + + if (!ybscan->is_exec_done) + pgstat_count_index_scan(scan->indexRelation); + + /* Special case: aggregate pushdown. */ + if (scan->yb_aggrefs) + { + /* + * TODO(jason): deduplicate with ybc_getnext_heaptuple, + * ybc_getnext_indextuple. + */ + if (ybscan->quit_scan) + return false; + + /* + * In the case of parallel scan we need to obtain boundaries from the + * pscan before the scan is executed. Also empty row from parallel range + * scan does not mean scan is done, it means the range is done and we + * need to pick up next. No rows from parallel range is possible, hence + * the loop. + */ + while (true) + { + /* Need to execute the request */ + if (!ybscan->is_exec_done) + { + /* Parallel mode: pick up parallel block first */ + if (ybscan->pscan != NULL) + { + YBParallelPartitionKeys parallel_scan = ybscan->pscan; + const char *low_bound; + size_t low_bound_size; + const char *high_bound; + size_t high_bound_size; + + /* + * If range is found, apply the boundaries, false means the + * scan * is done for that worker. + */ + if (ybParallelNextRange(parallel_scan, + &low_bound, &low_bound_size, + &high_bound, &high_bound_size)) + { + HandleYBStatus(YBCPgDmlApplyParallelRange(ybscan->handle, + low_bound, + low_bound_size, + high_bound, + high_bound_size)); + if (low_bound) + pfree((void *) low_bound); + if (high_bound) + pfree((void *) high_bound); + } + else + return false; + /* + * Use unlimited fetch. + * Parallel scan range is already of limited size, it is + * unlikely to exceed the + * message size, but may save some RPCs. + */ + ybscan->exec_params->limit_use_default = true; + ybscan->exec_params->yb_fetch_row_limit = 0; + ybscan->exec_params->yb_fetch_size_limit = 0; + } + /* Request with aggregates does not care of scan direction */ + HandleYBStatus(YBCPgExecSelect(ybscan->handle, + ybscan->exec_params)); + ybscan->is_exec_done = true; + } + + /* + * Aggregate pushdown directly modifies the scan slot rather than + * passing it through xs_hitup or xs_itup. + */ + if (ybc_getnext_aggslot(scan, ybscan->handle, + ybscan->prepare_params.index_only_scan)) + return true; + /* + * Parallel scan needs to pick next range and reexecute, done + * otherwise. + */ + if (ybscan->pscan) + ybscan->is_exec_done = false; + else + return false; + } + } + + /* + * IndexScan(SysTable, Index) --> HeapTuple. + */ + bool has_tuple = false; + + if (ybscan->prepare_params.index_only_scan) + { + IndexTuple tuple = ybc_getnext_indextuple(ybscan, dir); + + if (tuple) + { + scan->xs_itup = tuple; + scan->xs_itupdesc = RelationGetDescr(scan->indexRelation); + has_tuple = true; + } + } + else + { + HeapTuple tuple = ybc_getnext_heaptuple(ybscan, dir); + + if (tuple) + { + scan->xs_hitup = tuple; + scan->xs_hitupdesc = RelationGetDescr(scan->heapRelation); + has_tuple = true; + } + } + + return has_tuple; +} + +static void +ybcinendscan(IndexScanDesc scan) +{ + ybc_free_ybscan((YbScanDesc) scan->opaque); +} + +static void +ybcinbindschema(YbcPgStatement handle, + struct IndexInfo *indexInfo, + TupleDesc indexTupleDesc, + int16 *coloptions, + Oid *opclassOids, + Datum reloptions) +{ + YBCBindCreateIndexColumns(handle, + indexInfo, + indexTupleDesc, + coloptions, + indexInfo->ii_NumIndexKeyAttrs); +} + +/* + * LSM handler function: return IndexAmRoutine with access method parameters + * and callbacks. + */ +Datum +ybcinhandler(PG_FUNCTION_ARGS) +{ + IndexAmRoutine *amroutine = makeNode(IndexAmRoutine); + + amroutine->amstrategies = BTMaxStrategyNumber; + amroutine->amsupport = BTNProcs; + amroutine->amcanorder = true; + amroutine->amcanorderbyop = false; + amroutine->amcanbackward = true; + amroutine->amcanunique = true; + amroutine->amcanmulticol = true; + amroutine->amoptionalkey = true; + amroutine->amsearcharray = true; + amroutine->amsearchnulls = true; + amroutine->amstorage = false; + amroutine->amclusterable = true; + amroutine->ampredlocks = true; + amroutine->amcanparallel = true; + amroutine->amcaninclude = true; + amroutine->ybamcanupdatetupleinplace = true; + amroutine->amkeytype = InvalidOid; + + amroutine->ambuild = ybcinbuild; + amroutine->ambuildempty = ybcinbuildempty; + amroutine->aminsert = NULL; /* use yb_aminsert below instead */ + amroutine->ambulkdelete = ybcinbulkdelete; + amroutine->amvacuumcleanup = ybcinvacuumcleanup; + amroutine->amcanreturn = ybcincanreturn; + amroutine->amcostestimate = ybcincostestimate; + amroutine->amoptions = ybcinoptions; + amroutine->amproperty = ybcinproperty; + amroutine->amvalidate = ybcinvalidate; + amroutine->ambeginscan = ybcinbeginscan; + amroutine->amrescan = ybcinrescan; + amroutine->amgettuple = ybcingettuple; + amroutine->amgetbitmap = NULL; /* use yb_amgetbitmap below instead */ + amroutine->amendscan = ybcinendscan; + amroutine->ammarkpos = NULL; /* TODO: support mark/restore pos with + * ordering */ + amroutine->amrestrpos = NULL; + amroutine->amestimateparallelscan = yb_estimate_parallel_size; + amroutine->aminitparallelscan = yb_init_partition_key_data; + amroutine->amparallelrescan = ybcinparallelrescan; + amroutine->yb_amisforybrelation = true; + amroutine->yb_aminsert = ybcininsert; + amroutine->yb_amdelete = ybcindelete; + amroutine->yb_amupdate = ybcinupdate; + amroutine->yb_ambackfill = ybcinbackfill; + amroutine->yb_ammightrecheck = ybcinmightrecheck; + amroutine->yb_amgetbitmap = ybcgetbitmap; + amroutine->yb_ambindschema = ybcinbindschema; + + PG_RETURN_POINTER(amroutine); +} diff --git a/src/backend/access/yb_access/yb_pg_inherits_scan.c b/src/backend/access/yb_access/yb_pg_inherits_scan.c new file mode 100644 index 0000000000000..345cf839e6d98 --- /dev/null +++ b/src/backend/access/yb_access/yb_pg_inherits_scan.c @@ -0,0 +1,235 @@ +/*------------------------------------------------------------------------- + * + * yb_pg_inherits_scan.c + * This is an abstraction used for scanning the pg_inherits sys catalog + * table. We use a custom cache for pg_inherits, so that we can avoid trips + * to the YB-Master for every lookup. This cache lookup is abstracted under + * an interface similar to the system catalog scan interface. + * + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * IDENTIFICATION + * src/backend/access/yb_access/yb_pg_inherits_scan.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/heapam.h" +#include "access/relscan.h" +#include "access/yb_pg_inherits_scan.h" +#include "access/yb_scan.h" +#include "catalog/indexing.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_inherits_d.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/fmgroids.h" +#include "utils/memutils.h" +#include "utils/syscache.h" +#include "utils/yb_inheritscache.h" + +typedef struct YbChildScanData +{ + YbSysScanBaseData base; + YbPgInheritsCacheEntry child_cache_entry; + /* + * If seqno > 0, it is a scan for a specific seqno + * otherwise it is a general scan by child rel oid. + */ + int seqno; + ListCell *current_tuple; +} YbChildScanData; + +typedef struct YbChildScanData *YbChildScan; + +typedef struct YbParentScanData +{ + YbSysScanBaseData base; + YbPgInheritsCacheEntry parent_cache_entry; + List *tuples; + ListCell *current_tuple; +} YbParentScanData; + +typedef struct YbParentScanData *YbParentScan; + +static HeapTuple +yb_lookup_cache_get_next(YbSysScanBase child_scan) +{ + YbChildScan scan = (void *) child_scan; + + if (!scan->child_cache_entry || scan->seqno == -1) + return NULL; + + if (scan->seqno > 0) + { + /* + * Scan for a specific entry (childoid, seqno) over all entries for childoid + */ + ListCell *lc = NULL; + + foreach(lc, scan->child_cache_entry->tuples) + { + HeapTuple tuple = (HeapTuple) lfirst(lc); + Form_pg_inherits form = (Form_pg_inherits) GETSTRUCT(tuple); + + if (form->inhseqno == scan->seqno) + { + scan->seqno = -1; /* return NULL next time */ + return tuple; + } + } + scan->seqno = -1; + return NULL; + } + + if (scan->seqno == 0) + { + ListCell *ret = scan->current_tuple; + + if (ret == NULL) + return NULL; + HeapTuple tuple = (HeapTuple) lfirst(ret); + Form_pg_inherits form = (Form_pg_inherits) GETSTRUCT(tuple); + + elog(DEBUG3, + "child scan for all seqno returns list tuple parent %d child %d seqno %d", + form->inhparent, form->inhrelid, form->inhseqno); + scan->current_tuple = lnext(scan->child_cache_entry->tuples, scan->current_tuple); + return tuple; + } + + return NULL; +} + +static void +yb_lookup_cache_end_scan(YbSysScanBase child_scan) +{ + YbChildScan scan = (void *) child_scan; + + if (scan->child_cache_entry) + ReleaseYbPgInheritsCacheEntry(scan->child_cache_entry); +} + + +static HeapTuple +yb_parent_get_next(YbSysScanBase parent_scan) +{ + YbParentScan scan = (void *) parent_scan; + ListCell *ret = scan->current_tuple; + + if (ret == NULL) + return NULL; + scan->current_tuple = lnext(scan->tuples, scan->current_tuple); + return lfirst(ret); +} + +static void +yb_parent_end_scan(YbSysScanBase parent_scan) +{ + YbParentScan scan = (void *) parent_scan; + + Assert(scan->parent_cache_entry); + ReleaseYbPgInheritsCacheEntry(scan->parent_cache_entry); +} + +static YbSysScanVirtualTable yb_parent_scan = { + .next = &yb_parent_get_next, + .end = &yb_parent_end_scan +}; +static YbSysScanVirtualTable yb_child_scan = { + .next = &yb_lookup_cache_get_next, + .end = &yb_lookup_cache_end_scan +}; + +static YbSysScanBase +YbInitSysScanDesc(YbSysScanBase scan, YbSysScanVirtualTable *vtable) +{ + scan->vtable = vtable; + return scan; +} + +YbSysScanBase +yb_pg_inherits_beginscan(Relation inhrel, ScanKey key, int nkeys, Oid indexId) +{ + /* + * We only expect that this is a cache lookup based on the + * or or. Verify that the + * keys match our expectations. + */ + Assert(key->sk_strategy == BTEqualStrategyNumber); + Assert(key->sk_func.fn_oid == F_OIDEQ); + + if (key[0].sk_attno == Anum_pg_inherits_inhparent) + { + elog(DEBUG3, "lookup by parent for oid %d ", + DatumGetObjectId(key[0].sk_argument)); + + /* + * This is a lookup based on the parentrelid (inhparent). + */ + Assert(nkeys == 1); + YbParentScan scan = palloc0(sizeof(YbParentScanData)); + + scan->parent_cache_entry = + GetYbPgInheritsCacheEntryByParent(DatumGetObjectId(key[0].sk_argument)); + scan->tuples = scan->parent_cache_entry->tuples; + scan->current_tuple = list_head(scan->tuples); + return YbInitSysScanDesc(&scan->base, &yb_parent_scan); + } + + /* + * If this is not a lookup request based on the parent relid, it must be + * based on the child (inhrelid). + */ + Assert(key[0].sk_attno == Anum_pg_inherits_inhrelid); + + YbChildScan scan = palloc0(sizeof(YbChildScanData)); + + scan->seqno = -1; + scan->child_cache_entry = NULL; + scan->current_tuple = NULL; + if (nkeys == 2) + { + /* + * This should be a lookup for tuples with inhlreid and inhseqno set + * to these two values + */ + Assert(key[1].sk_attno == Anum_pg_inherits_inhseqno); + scan->seqno = DatumGetInt32(key[1].sk_argument); + elog(DEBUG3, "lookup by child oid %d and seqno %d", + DatumGetObjectId(key[0].sk_argument), + scan->seqno); + + } + else + { + + elog(DEBUG3, "lookup by child oid %d", DatumGetObjectId(key[0].sk_argument)); + /* + * This is a request to lookup all tuples in pg_inherits matching a given + * child relid. + */ + Assert(nkeys == 1); + scan->seqno = 0; + } + + scan->child_cache_entry = GetYbPgInheritsCacheEntryByChild(DatumGetObjectId(key[0].sk_argument)); + if (scan->child_cache_entry && scan->child_cache_entry->tuples && scan->seqno == 0) + { + scan->current_tuple = list_head(scan->child_cache_entry->tuples); + } + return YbInitSysScanDesc(&scan->base, &yb_child_scan); +} diff --git a/src/backend/access/yb_access/yb_scan.c b/src/backend/access/yb_access/yb_scan.c new file mode 100644 index 0000000000000..103a473146555 --- /dev/null +++ b/src/backend/access/yb_access/yb_scan.c @@ -0,0 +1,5522 @@ +/*-------------------------------------------------------------------------------------------------- + * + * yb_scan.c + * YugaByte catalog scan API. + * This is used to access data from YugaByte's system catalog tables. + * + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations + * under the License. + * + * src/backend/access/yb_access/yb_scan.c + * + *-------------------------------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include +#include +#include +#include + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/nbtree.h" +#include "access/relation.h" +#include "access/relscan.h" +#include "access/sysattr.h" +#include "access/xact.h" +#include "access/yb_pg_inherits_scan.h" +#include "access/yb_scan.h" +#include "catalog/catalog.h" +#include "catalog/heap.h" +#include "catalog/index.h" +#include "catalog/indexing.h" +#include "catalog/pg_database.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_type.h" +#include "catalog/yb_type.h" +#include "commands/dbcommands.h" +#include "commands/yb_tablegroup.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/pathnodes.h" +#include "optimizer/cost.h" +#include "optimizer/optimizer.h" +#include "optimizer/paths.h" +#include "parser/parsetree.h" +#include "pg_yb_utils.h" +#include "pgstat.h" +#include "postmaster/bgworker_internals.h" /* for MAX_PARALLEL_WORKER_LIMIT */ +#include "utils/datum.h" +#include "utils/elog.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/resowner_private.h" +#include "utils/selfuncs.h" +#include "utils/snapmgr.h" +#include "utils/spccache.h" +#include "utils/syscache.h" +#include "utils/typcache.h" +#include "yb/yql/pggate/util/ybc_guc.h" +#include "yb/yql/pggate/ybc_gflags.h" +#include "ybgate/ybgate_api.h" + +typedef struct YbAttnumBmsState +{ + Bitmapset *bms; + const AttrNumber min_attr; +} YbAttnumBmsState; + +static inline YbAttnumBmsState +ybcAttnumBmsConstruct() +{ + return (YbAttnumBmsState) + { + .bms = NULL, + .min_attr = YBSystemFirstLowInvalidAttributeNumber, + }; +} + +static inline void +ybcAttnumBmsDestroy(YbAttnumBmsState *state) +{ + bms_free(state->bms); + state->bms = NULL; +} + +static inline int +ybcAttnumBmsIndex(const YbAttnumBmsState *state, AttrNumber attnum) +{ + return YBAttnumToBmsIndexWithMinAttr(state->min_attr, attnum); +} + +static inline AttrNumber +ybcAttnumBmsAttnum(const YbAttnumBmsState *state, int idx) +{ + return YBBmsIndexToAttnumWithMinAttr(state->min_attr, idx); +} + +static inline void +ybcAttnumBmsAdd(YbAttnumBmsState *state, AttrNumber attnum) +{ + state->bms = bms_add_member(state->bms, ybcAttnumBmsIndex(state, attnum)); +} + +static inline void +ybcAttnumBmsDel(YbAttnumBmsState *state, AttrNumber attnum) +{ + state->bms = bms_del_member(state->bms, ybcAttnumBmsIndex(state, attnum)); +} + +static inline bool +ybcAttnumBmsExists(const YbAttnumBmsState *state, AttrNumber attnum) +{ + return bms_is_member(ybcAttnumBmsIndex(state, attnum), state->bms); +} + +static inline bool +ybcAttnumBmsDelIfExists(YbAttnumBmsState *state, AttrNumber attnum) +{ + if (!ybcAttnumBmsExists(state, attnum)) + return false; + + ybcAttnumBmsDel(state, attnum); + return true; +} + +static inline bool +ybcAttnumBmsIsEmpty(const YbAttnumBmsState *state) +{ + return bms_is_empty(state->bms); +} + +typedef struct YbScanPlanData +{ + /* The relation where to read data from */ + Relation target_relation; + + /* + * Key columns of the bind relation (i.e. the relation where cols are + * bound). For a sequential scan or primary index scan, this is the base + * table's primary key columns. For a secondary index [only] scan, this is + * the secondary index's key columns. + * - key_cols: all of them + * - hash_key_cols: the subset that are HASH columns + * - qualified_scan_key_cols: the subset qualified from scan keys + */ + Bitmapset *key_cols; + Bitmapset *hash_key_cols; + Bitmapset *qualified_scan_key_cols; + + /* Description and attnums of the columns to bind */ + TupleDesc bind_desc; + AttrNumber bind_key_attnums[YB_MAX_SCAN_KEYS]; +} YbScanPlanData; + +typedef YbScanPlanData *YbScanPlan; + +typedef struct YbDefaultSysScanData +{ + YbSysScanBaseData base; + YbScanDesc ybscan; +} YbDefaultSysScanData; + +typedef struct YbDefaultSysScanData *YbDefaultSysScan; + +static void +ybcAddAttributeColumn(YbScanPlan scan_plan, AttrNumber attnum) +{ + const int idx = YBAttnumToBmsIndex(scan_plan->target_relation, attnum); + + if (bms_is_member(idx, scan_plan->key_cols)) + scan_plan->qualified_scan_key_cols = + bms_add_member(scan_plan->qualified_scan_key_cols, idx); +} + +/* + * Checks if an attribute is a hash or key column and note it in the scan plan. + */ +static void +ybcCheckKeyAttribute(YbScanPlan scan_plan, + YbcPgTableDesc ybc_table_desc, + AttrNumber attnum) +{ + YbcPgColumnInfo column_info; + + HandleYBTableDescStatus(YBCPgGetColumnInfo(ybc_table_desc, + attnum, + &column_info), ybc_table_desc); + + int idx = YBAttnumToBmsIndex(scan_plan->target_relation, attnum); + + if (column_info.is_hash) + scan_plan->hash_key_cols = bms_add_member(scan_plan->hash_key_cols, idx); + if (column_info.is_key) + scan_plan->key_cols = bms_add_member(scan_plan->key_cols, idx); +} + +/* + * Get Yugabyte-specific table metadata and load it into the scan_plan. + * Currently only key info. + */ +static void +ybcLoadTableInfo(Relation relation, YbScanPlan scan_plan) +{ + Oid dboid = YBCGetDatabaseOid(relation); + YbcPgTableDesc ybc_table_desc = NULL; + + HandleYBStatus(YBCPgGetTableDesc(dboid, YbGetRelfileNodeId(relation), + &ybc_table_desc)); + + for (AttrNumber attnum = 1; attnum <= relation->rd_att->natts; attnum++) + ybcCheckKeyAttribute(scan_plan, ybc_table_desc, attnum); +} + +static Oid +ybc_get_atttypid(TupleDesc bind_desc, AttrNumber attnum) +{ + return attnum > 0 ? TupleDescAttr(bind_desc, attnum - 1)->atttypid : + SystemAttributeDefinition(attnum)->atttypid; +} + +/* + * Bind a scan key. + */ +static void +YbBindColumn(YbScanDesc ybScan, TupleDesc bind_desc, + AttrNumber attnum, Datum value, bool is_null) +{ + Oid atttypid = ybc_get_atttypid(bind_desc, attnum); + Oid attcollation = YBEncodingCollation(ybScan->handle, attnum, + ybc_get_attcollation(bind_desc, + attnum)); + + YbcPgExpr ybc_expr = YBCNewConstant(ybScan->handle, atttypid, attcollation, + value, is_null); + + HandleYBStatus(YBCPgDmlBindColumn(ybScan->handle, attnum, ybc_expr)); +} + +static void +YbBindColumnCondBetween(YbScanDesc ybScan, + TupleDesc bind_desc, AttrNumber attnum, + bool start_valid, bool start_inclusive, Datum value, + bool end_valid, bool end_inclusive, Datum value_end) +{ + /* Special handling of quals on ybctid column. */ + if (attnum == YBTupleIdAttributeNumber) + { + HandleYBStatus(YBCPgDmlBindBounds(ybScan->handle, + start_valid ? value : 0, + start_inclusive, + end_valid ? value_end : 0, + end_inclusive)); + return; + } + + Oid atttypid = ybc_get_atttypid(bind_desc, attnum); + Oid attcollation = YBEncodingCollation(ybScan->handle, attnum, + ybc_get_attcollation(bind_desc, + attnum)); + + YbcPgExpr ybc_expr = (start_valid ? + YBCNewConstant(ybScan->handle, + atttypid, + attcollation, + value, + false /* isnull */ ) : + NULL); + YbcPgExpr ybc_expr_end = (end_valid ? + YBCNewConstant(ybScan->handle, + atttypid, + attcollation, + value_end, + false /* isnull */ ) : + NULL); + + HandleYBStatus(YBCPgDmlBindColumnCondBetween(ybScan->handle, attnum, + ybc_expr, start_inclusive, + ybc_expr_end, end_inclusive)); +} + +static void +YbBindColumnNotNull(YbScanDesc ybScan, TupleDesc bind_desc, AttrNumber attnum) +{ + HandleYBStatus(YBCPgDmlBindColumnCondIsNotNull(ybScan->handle, attnum)); +} + +/* + * Bind an array of scan keys for a column. + */ +static void +ybcBindColumnCondIn(YbScanDesc ybScan, TupleDesc bind_desc, AttrNumber attnum, + int nvalues, Datum *values, bool bind_to_null) +{ + Oid atttypid = ybc_get_atttypid(bind_desc, attnum); + Oid attcollation = YBEncodingCollation(ybScan->handle, attnum, + ybc_get_attcollation(bind_desc, + attnum)); + + YbcPgExpr colref = YBCNewColumnRef(ybScan->handle, attnum, atttypid, + attcollation, NULL); + + int total_num_values = nvalues + (bind_to_null ? 1 : 0); + YbcPgExpr ybc_exprs[total_num_values]; /* VLA - scratch space */ + + /* First, create expr for non-null values. */ + for (int i = 0; i < nvalues; i++) + ybc_exprs[i] = YBCNewConstant(ybScan->handle, atttypid, attcollation, + values[i], false /* is_null */ ); + + /* Create expr for NULL if bind_to_null is set. */ + if (bind_to_null) + ybc_exprs[nvalues] = YBCNewConstant(ybScan->handle, atttypid, + attcollation, (Datum) 0, + true /* is_null */ ); + + HandleYBStatus(YBCPgDmlBindColumnCondIn(ybScan->handle, colref, + total_num_values, ybc_exprs)); +} + +/* + * Bind an array of scan keys for a tuple of columns. + */ +static void +ybcBindTupleExprCondIn(YbScanDesc ybScan, + TupleDesc bind_desc, + int n_attnum_values, + AttrNumber *attnum, + int nvalues, + Datum *values) +{ + Assert(nvalues > 0); + YbcPgExpr ybc_rhs_exprs[nvalues]; + + YbcPgExpr ybc_elems_exprs[n_attnum_values]; /* VLA - scratch space */ + Datum datum_values[n_attnum_values]; + bool is_null[n_attnum_values]; + + Oid tupType = + HeapTupleHeaderGetTypeId(DatumGetHeapTupleHeader(values[0])); + Oid tupTypmod = + HeapTupleHeaderGetTypMod(DatumGetHeapTupleHeader(values[0])); + YbcPgTypeAttrs type_attrs = {tupTypmod}; + + /* Form the lhs tuple. */ + for (int i = 0; i < n_attnum_values; i++) + { + Oid atttypid = ybc_get_atttypid(bind_desc, attnum[i]); + Oid attcollation = YBEncodingCollation(ybScan->handle, attnum[i], + ybc_get_attcollation(bind_desc, + attnum[i])); + + ybc_elems_exprs[i] = YBCNewColumnRef(ybScan->handle, attnum[i], + atttypid, attcollation, NULL); + } + + YbcPgExpr lhs = YBCNewTupleExpr(ybScan->handle, &type_attrs, + n_attnum_values, ybc_elems_exprs); + + TupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + HeapTupleData tuple; + + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + + /* Form the list of tuples for the RHS. */ + for (int i = 0; i < nvalues; i++) + { + tuple.t_len = HeapTupleHeaderGetDatumLength(values[i]); + tuple.t_data = DatumGetHeapTupleHeader(values[i]); + heap_deform_tuple(&tuple, tupdesc, datum_values, is_null); + for (int j = 0; j < n_attnum_values; j++) + { + Oid atttypid = ybc_get_atttypid(bind_desc, attnum[j]); + Oid attcollation = YBEncodingCollation(ybScan->handle, attnum[j], + ybc_get_attcollation(bind_desc, + attnum[j])); + + ybc_elems_exprs[j] = YBCNewConstant(ybScan->handle, atttypid, + attcollation, datum_values[j], + is_null[j]); + } + + ybc_rhs_exprs[i] = YBCNewTupleExpr(ybScan->handle, &type_attrs, + n_attnum_values, ybc_elems_exprs); + } + + HandleYBStatus(YBCPgDmlBindColumnCondIn(ybScan->handle, lhs, nvalues, + ybc_rhs_exprs)); + + ReleaseTupleDesc(tupdesc); +} + +/* + * Utility method to bind const to column. + */ +void +YbBindDatumToColumn(YbcPgStatement stmt, + int attr_num, + Oid type_id, + Oid collation_id, + Datum datum, + bool is_null, + const YbcPgTypeEntity *null_type_entity) +{ + YbcPgExpr expr; + const YbcPgTypeEntity *type_entity; + + if (is_null && null_type_entity) + type_entity = null_type_entity; + else + type_entity = YbDataTypeFromOidMod(InvalidAttrNumber, type_id); + + YbcPgCollationInfo collation_info; + + YBGetCollationInfo(collation_id, type_entity, datum, is_null, + &collation_info); + + HandleYBStatus(YBCPgNewConstant(stmt, type_entity, + collation_info.collate_is_valid_non_c, + collation_info.sortkey, + datum, is_null, &expr)); + + HandleYBStatus(YBCPgDmlBindColumn(stmt, attr_num, expr)); +} + +static void +YbDmlAppendTargetImpl(YbcPgStatement handle, AttrNumber attnum, Oid typid, Oid collation, Oid typmod) +{ + const YbcPgTypeAttrs type_attrs = {.typmod = typmod}; + + HandleYBStatus(YBCPgDmlAppendTarget(handle, + YBCNewColumnRef(handle, attnum, typid, + collation, &type_attrs), + false /* is_for_secondary_index */ )); +} + +/* + * Add a system column as target to the given statement handle. + */ +void +YbDmlAppendTargetSystem(AttrNumber attnum, YbcPgStatement handle) +{ + Assert(attnum < 0); + YbDmlAppendTargetImpl(handle, attnum, InvalidOid /* typid */ , + InvalidOid /* collation */ , -1 /* typmod */ ); +} + +void +YbDmlAppendTargetRegularAttr(const FormData_pg_attribute *attr, + YbcPgStatement handle) +{ + Assert(attr->attnum > 0); + Assert(!attr->attisdropped); + + YbDmlAppendTargetImpl(handle, attr->attnum, attr->atttypid, + attr->attcollation, attr->atttypmod); + +} + +/* + * Add a regular column as target to the given statement handle. + * Assume tupdesc's relation is the same as handle's target relation. + */ +void +YbDmlAppendTargetRegular(TupleDesc tupdesc, AttrNumber attnum, + YbcPgStatement handle) +{ + Assert(attnum > 0); + YbDmlAppendTargetRegularAttr(TupleDescAttr(tupdesc, attnum - 1), handle); +} + +static void +ybcUpdateFKCache(YbScanDesc ybScan, Datum ybctid) +{ + if (!ybScan->exec_params) + return; + + switch (ybScan->exec_params->rowmark) + { + case ROW_MARK_EXCLUSIVE: + case ROW_MARK_NOKEYEXCLUSIVE: + case ROW_MARK_SHARE: + case ROW_MARK_KEYSHARE: + YBCPgAddIntoForeignKeyReferenceCache(YbGetRelfileNodeId(ybScan->table), + ybctid); + break; + case ROW_MARK_REFERENCE: + case ROW_MARK_COPY: + break; + } +} + +static HeapTuple +ybcFetchNextHeapTuple(YbScanDesc ybScan, ScanDirection dir) +{ + HeapTuple tuple = NULL; + bool has_data = false; + TupleDesc tupdesc = ybScan->target_desc; + Datum *values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum)); + bool *nulls = (bool *) palloc(tupdesc->natts * sizeof(bool)); + YbcPgSysColumns syscols; + + /* + * In the case of parallel scan we need to obtain boundaries from the pscan + * before the scan is executed. Also empty row from parallel range scan does + * not mean scan is done, it means the range is done and we need to pick up + * next. No rows from parallel range is possible, hence the loop. + */ + while (true) + { + /* Need to execute the request */ + if (!ybScan->is_exec_done) + { + /* Parallel mode: pick up parallel block first */ + if (ybScan->pscan != NULL) + { + YBParallelPartitionKeys parallel_scan = ybScan->pscan; + const char *low_bound; + size_t low_bound_size; + const char *high_bound; + size_t high_bound_size; + + /* + * If range is found, apply the boundaries, false means the scan + * is done for that worker. + */ + if (ybParallelNextRange(parallel_scan, + &low_bound, &low_bound_size, + &high_bound, &high_bound_size)) + { + HandleYBStatus(YBCPgDmlApplyParallelRange(ybScan->handle, + low_bound, + low_bound_size, + high_bound, + high_bound_size)); + if (low_bound) + pfree((void *) low_bound); + if (high_bound) + pfree((void *) high_bound); + } + else + return NULL; + /* + * Use unlimited fetch. + * Parallel scan range is already of limited size, it is + * unlikely to exceed the message size, but may save some RPCs. + */ + ybScan->exec_params->limit_use_default = true; + ybScan->exec_params->yb_fetch_row_limit = 0; + ybScan->exec_params->yb_fetch_size_limit = 0; + } + /* Set scan direction, if matters */ + if (ScanDirectionIsForward(dir)) + HandleYBStatus(YBCPgSetForwardScan(ybScan->handle, true)); + else if (ScanDirectionIsBackward(dir)) + HandleYBStatus(YBCPgSetForwardScan(ybScan->handle, false)); + + HandleYBStatus(YBCPgExecSelect(ybScan->handle, + ybScan->exec_params)); + ybScan->is_exec_done = true; + } + + /* Fetch one row. */ + YbcStatus status = YBCPgDmlFetch(ybScan->handle, + tupdesc->natts, + (uint64_t *) values, + nulls, + &syscols, + &has_data); + + if (status && !IsolationIsSerializable()) + { + const uint32_t err_code = YBCStatusPgsqlError(status); + + if (ybScan->exec_params != NULL && err_code == ERRCODE_YB_TXN_CONFLICT) + { + elog(DEBUG2, "Error when trying to lock row. " + "pg_wait_policy=%d docdb_wait_policy=%d message=%s", + ybScan->exec_params->pg_wait_policy, + ybScan->exec_params->docdb_wait_policy, + YBCStatusMessageBegin(status)); + YBCFreeStatus(status); + status = NULL; + if (ybScan->exec_params->pg_wait_policy == LockWaitError) + ereport(ERROR, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on row in relation \"%s\"", + RelationGetRelationName(ybScan->table)))); + else + ereport(ERROR, + (errcode(ERRCODE_YB_TXN_CONFLICT), + errmsg("could not serialize access due to concurrent update"))); + } + else if (err_code == ERRCODE_YB_TXN_SKIP_LOCKING) + { + /* For skip locking, it's correct to simply return no results. */ + has_data = false; + YBCFreeStatus(status); + status = NULL; + } + } + + HandleYBStatus(status); + + if (has_data) + { + tuple = heap_form_tuple(tupdesc, values, nulls); + + if (syscols.ybctid != NULL) + { + HEAPTUPLE_YBCTID(tuple) = PointerGetDatum(syscols.ybctid); + ybcUpdateFKCache(ybScan, HEAPTUPLE_YBCTID(tuple)); + } + tuple->t_tableOid = RelationGetRelid(ybScan->table); + break; + } + else if (ybScan->pscan != NULL) + ybScan->is_exec_done = false; + else + break; + } + pfree(values); + pfree(nulls); + + return tuple; +} + +static IndexTuple +ybcFetchNextIndexTuple(YbScanDesc ybScan, ScanDirection dir) +{ + IndexTuple tuple = NULL; + bool has_data = false; + Relation index = ybScan->index; + TupleDesc tupdesc = ybScan->target_desc; + Datum *values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum)); + bool *nulls = (bool *) palloc(tupdesc->natts * sizeof(bool)); + YbcPgSysColumns syscols; + + /* + * In the case of parallel scan we need to obtain boundaries from the pscan + * before the scan is executed. Also empty row from parallel range scan does + * not mean scan is done, it means the range is done and we need to pick up + * next. No rows from parallel range is possible, hence the loop. + */ + while (true) + { + /* Need to execute the request */ + if (!ybScan->is_exec_done) + { + /* Parallel mode: pick up parallel block first */ + if (ybScan->pscan != NULL) + { + YBParallelPartitionKeys parallel_scan = ybScan->pscan; + const char *low_bound; + size_t low_bound_size; + const char *high_bound; + size_t high_bound_size; + + /* + * If range is found, apply the boundaries, false means the scan + * is done for that worker. + */ + if (ybParallelNextRange(parallel_scan, + &low_bound, &low_bound_size, + &high_bound, &high_bound_size)) + { + HandleYBStatus(YBCPgDmlApplyParallelRange(ybScan->handle, + low_bound, + low_bound_size, + high_bound, + high_bound_size)); + if (low_bound) + pfree((void *) low_bound); + if (high_bound) + pfree((void *) high_bound); + } + else + return NULL; + /* + * Use unlimited fetch. + * Parallel scan range is already of limited size, it is + * unlikely to exceed the message size, but may save some RPCs. + */ + ybScan->exec_params->limit_use_default = true; + ybScan->exec_params->yb_fetch_row_limit = 0; + ybScan->exec_params->yb_fetch_size_limit = 0; + } + /* Set scan direction, if matters */ + if (ScanDirectionIsForward(dir)) + HandleYBStatus(YBCPgSetForwardScan(ybScan->handle, true)); + else if (ScanDirectionIsBackward(dir)) + HandleYBStatus(YBCPgSetForwardScan(ybScan->handle, false)); + + HandleYBStatus(YBCPgExecSelect(ybScan->handle, + ybScan->exec_params)); + ybScan->is_exec_done = true; + } + + /* Fetch one row. */ + HandleYBStatus(YBCPgDmlFetch(ybScan->handle, + tupdesc->natts, + (uint64_t *) values, + nulls, + &syscols, + &has_data)); + + if (has_data) + { + /* + * Return the IndexTuple. If this is a primary key, reorder the + * values first as expected in the index's column order first. + */ + if (index->rd_index->indisprimary) + { + Assert(index->rd_index->indnatts <= INDEX_MAX_KEYS); + + Datum ivalues[INDEX_MAX_KEYS]; + bool inulls[INDEX_MAX_KEYS]; + + for (int i = 0; i < index->rd_index->indnatts; i++) + { + AttrNumber attno = index->rd_index->indkey.values[i]; + + ivalues[i] = values[attno - 1]; + inulls[i] = nulls[attno - 1]; + } + + tuple = index_form_tuple(RelationGetDescr(index), ivalues, inulls); + if (syscols.ybctid != NULL) + { + INDEXTUPLE_BASECTID(tuple) = PointerGetDatum(syscols.ybctid); + ybcUpdateFKCache(ybScan, INDEXTUPLE_BASECTID(tuple)); + } + } + else + { + tuple = index_form_tuple(tupdesc, values, nulls); + if (syscols.ybbasectid != NULL) + { + INDEXTUPLE_BASECTID(tuple) = PointerGetDatum(syscols.ybbasectid); + ybcUpdateFKCache(ybScan, INDEXTUPLE_BASECTID(tuple)); + } + + /* Fields used by yb_index_check() */ + if (syscols.ybuniqueidxkeysuffix != NULL) + tuple->t_ybuniqueidxkeysuffix = + PointerGetDatum(syscols.ybuniqueidxkeysuffix); + if (syscols.ybctid != NULL) + tuple->t_ybindexrowybctid = PointerGetDatum(syscols.ybctid); + } + break; + } + else if (ybScan->pscan != NULL) + ybScan->is_exec_done = false; + else + break; + } + pfree(values); + pfree(nulls); + + return tuple; +} + +static Oid +ybcCalculateIndexRelfileNodeId(Relation rel, Relation index, + const YbcPgPrepareParameters *params) +{ + Assert(index); + if (!index->rd_index->indisprimary) + return YbGetRelfileNodeId(index); + else if (params->index_only_scan) + return YbGetRelfileNodeId(rel); + return InvalidOid; +} + +/* + * True if the scan involving table and index is such that the index data is + * sharded together with the table. + * TODO(#25940): this logic is not completely clean, as indicated by the + * todos below. + */ +bool +YbIsScanningEmbeddedIdx(Relation table, Relation index) +{ + YbcTableProperties yb_table_properties_table; + bool is_embedded; + + yb_table_properties_table = YbGetTableProperties(table); + + /* + * There are a few cases where embedding happens. + * 1. System table: all system tables and indexes are specially colocated + * to the sys catalog tablet. + * 2. Copartitioning: some indexes may use copartitioning, which shards the + * table and index together. + */ + is_embedded = (IsSystemRelation(table) || + (index && index->rd_indam->yb_amiscopartitioned)); + + /* + * 3. Colocation: the table and index may be colocated to the same tablet: + * - If ysql_enable_colocated_tables_with_tablespaces, check that the + * table and index are in the same colocation tablet using + * tablegroup_oid. + * - TODO(#25940): index->rd_index->indisprimary seems irrelevant and + * should not be a pass condition. + * - Else, simply check for colocation of the table because the index + * should follow the table. + * - TODO(#25940): index being NULL or pk index should not be a pass + * condition. + * - TODO(#25940): the gflag could be turned on/off in the lifetime of + * a cluster, so it shouldn't even be involved in this logic. + * Everything should be validated, likely using the tablegroup_oid + * check, assuming that holds even when indexes are created when the + * flag is false. + */ + if (*YBCGetGFlags()->ysql_enable_colocated_tables_with_tablespaces) + is_embedded |= (yb_table_properties_table->is_colocated && + ((index && index->rd_index->indisprimary) || + (index && + (YbGetTableProperties(index)->tablegroup_oid == + yb_table_properties_table->tablegroup_oid)))); + else + is_embedded |= yb_table_properties_table->is_colocated; + + return is_embedded; +} + +/* + * Set up scan plan. + * This function sets up target and bind columns for each type of scans. + * SELECT FROM WHERE + * + * 1. SequentialScan(Table) and PrimaryIndexScan(Table): index = 0 + * - Table can be systable or usertable. + * - YugaByte doesn't have a separate PrimaryIndexTable. It's a special case. + * - Both target and bind descriptors are specified by the
+ * + * 2. IndexScan(SysTable, Index). + * - Target descriptor is specifed by the SysTable. + * - Bind descriptor is specified by the IndexTable. + * - For this scan, YugaByte returns a heap-tuple, which has all user's requested data. + * + * 3. IndexScan(UserTable, Index) + * - Both target and bind descriptors are specifed by the IndexTable. + * - For this scan, YugaByte returns an index-tuple, which has a ybctid (ROWID) to be used for + * querying data from the UserTable. + * - TODO(neil) By batching ybctid and processing it on YugaByte for all index-scans, the target + * for index-scan on regular table should also be the table itself (relation). + * + * 4. IndexOnlyScan(Table, Index) + * - Table can be systable or usertable. + * - Both target and bind descriptors are specifed by the IndexTable. + * - For this scan, YugaByte ALWAYS return index-tuple, which is expected by Postgres layer. + * 5. BitmapIndexScan(Index) + * - Table is null because we are only interested in getting ybctids from the index. + */ +static void +ybcSetupScanPlan(bool xs_want_itup, YbScanDesc ybScan, YbScanPlan scan_plan) +{ + Relation relation = ybScan->table; + Relation index = ybScan->index; + int i; + + memset(scan_plan, 0, sizeof(*scan_plan)); + + ybScan->prepare_params.embedded_idx = YbIsScanningEmbeddedIdx(relation, + index); + + if (index) + { + YbcPgPrepareParameters *params = &ybScan->prepare_params; + + params->index_only_scan = xs_want_itup; + params->index_relfilenode_oid = + ybcCalculateIndexRelfileNodeId(relation, index, params); + } + + /* Setup descriptors for target and bind. */ + if (!index || index->rd_index->indisprimary) + { + /* + * SequentialScan or PrimaryIndexScan or BitmapIndexScan on the primary index + * - YugaByte does not have a separate table for PrimaryIndex. + * - The target table descriptor, where data is read and returned, is the main table. + * - The binding table descriptor, whose column is bound to values, is also the main table. + */ + scan_plan->target_relation = relation; + ybcLoadTableInfo(relation, scan_plan); + ybScan->target_desc = RelationGetDescr(relation); + scan_plan->bind_desc = RelationGetDescr(relation); + } + else + { + /* + * Index-Scan: SELECT data FROM UserTable WHERE rowid IN (SELECT ybctid FROM indexTable) + * + */ + + if (ybScan->prepare_params.fetch_ybctids_only) + { + /* + * BitmapIndexScan + * - A BitmapIndexScan accesses only the index, not the main table. + */ + scan_plan->target_relation = index; + ybScan->target_desc = RelationGetDescr(index); + } + else if (ybScan->prepare_params.index_only_scan || relation == NULL) + { + /* + * IndexOnlyScan + * - This special case is optimized where data is read from index table. + * - The target table descriptor, where data is read and returned, is the index table. + * - The binding table descriptor, whose column is bound to values, is also the index table. + */ + scan_plan->target_relation = index; + ybScan->target_desc = RelationGetDescr(index); + } + else + { + /* + * IndexScan ( SysTable / UserTable) + * - YugaByte will use the binds to query base-ybctid in the index table, which is then used + * to query data from the main table. + * - The target table descriptor, where data is read and returned, is the main table. + * - The binding table descriptor, whose column is bound to values, is the index table. + */ + scan_plan->target_relation = relation; + ybScan->target_desc = RelationGetDescr(relation); + } + + ybcLoadTableInfo(index, scan_plan); + scan_plan->bind_desc = RelationGetDescr(index); + } + + /* + * Setup bind and target attnum of ScanKey. + * - The target-attnum comes from the table that is being read by the scan + * - The bind-attnum comes from the table that is being scan by the scan. + * + * Examples: + * - For IndexScan(Table, Index), Table is used for targets, but Index is for binds. + * - For IndexOnlyScan(Table, Index), only Index is used to setup both target and bind. + */ + for (i = 0; i < ybScan->nkeys; i++) + { + ScanKey key = ybScan->keys[i]; + + if (!index) + { + /* Sequential scan */ + ybScan->target_key_attnums[i] = key->sk_attno; + scan_plan->bind_key_attnums[i] = key->sk_attno; + } + else if (index->rd_index->indisprimary) + { + /* + * PrimaryIndex scan: This is a special case in YugaByte. There is no PrimaryIndexTable. + * The table itself will be scanned. + */ + ybScan->target_key_attnums[i] = scan_plan->bind_key_attnums[i] = + index->rd_index->indkey.values[key->sk_attno - 1]; + } + else if (ybScan->prepare_params.index_only_scan) + { + /* + * IndexOnlyScan(Table, Index) returns IndexTuple. + * Use the index attnum for both targets and binds. + */ + scan_plan->bind_key_attnums[i] = key->sk_attno; + ybScan->target_key_attnums[i] = key->sk_attno; + } + else + { + /* + * IndexScan(Table, Index) returns HeapTuple. + * Use Table attnum for targets. Use its Index attnum for binds. + */ + scan_plan->bind_key_attnums[i] = key->sk_attno; + ybScan->target_key_attnums[i] = + index->rd_index->indkey.values[key->sk_attno - 1]; + } + } +} + +static bool +ybc_should_pushdown_op(YbScanPlan scan_plan, AttrNumber attnum, int op_strategy) +{ + const int idx = YBAttnumToBmsIndex(scan_plan->target_relation, attnum); + + switch (op_strategy) + { + case BTEqualStrategyNumber: + return bms_is_member(idx, scan_plan->key_cols); + + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + case BTGreaterEqualStrategyNumber: + case BTGreaterStrategyNumber: + /* range key */ + return (!bms_is_member(idx, scan_plan->hash_key_cols) && + bms_is_member(idx, scan_plan->key_cols)); + + default: + /* TODO: support other logical operators */ + return false; + } +} + +static bool +YbIsHashCodeSearch(ScanKey key) +{ + bool is_hash_search = (key->sk_flags & YB_SK_SEARCHHASHCODE) != 0; + + /* We currently don't support hash code search with any other flags */ + Assert(!is_hash_search || key->sk_flags == YB_SK_SEARCHHASHCODE); + return is_hash_search; +} + +/* + * Is this a basic (c =/=/> value) (in)equality condition possibly on + * hashed values? + * TODO: The null value case (SK_ISNULL) should always evaluate to false + * per SQL semantics but in DocDB it will be true. So this case + * will require PG filtering (for null values only). + */ +static bool +YbIsBasicOpSearch(ScanKey key) +{ + return (key->sk_flags == 0 || + key->sk_flags == SK_ISNULL || + YbIsHashCodeSearch(key)); +} + +/* + * Is this a null search (c IS NULL) -- same as equality cond for DocDB. + */ +static bool +YbIsSearchNull(ScanKey key) +{ + return key->sk_flags == (SK_ISNULL | SK_SEARCHNULL); +} + +/* + * Is this a not-null search (c IS NOT NULL). + */ +static bool +YbIsSearchNotNull(ScanKey key) +{ + return key->sk_flags == (SK_ISNULL | SK_SEARCHNOTNULL); +} + +/* + * Is this an array search (c = ANY(..) or c IN ..). + */ +static bool +YbIsSearchArray(ScanKey key) +{ + return key->sk_flags & SK_SEARCHARRAY; +} + +static bool +YbIsRowHeader(ScanKey key) +{ + return key->sk_flags & SK_ROW_HEADER; +} + +static bool +YbSearchArrayRetainNulls(ScanKey key) +{ + return key->sk_flags & YB_SK_SEARCHARRAY_RETAIN_NULLS; +} + +/* + * Is the condition never TRUE because of c {=|<|<=|>=|>} NULL, etc.? + */ +static bool +YbIsNeverTrueNullCond(ScanKey key) +{ + return ((key->sk_flags & SK_ISNULL) != 0 && + (key->sk_flags & (SK_SEARCHNULL | SK_SEARCHNOTNULL)) == 0); +} + +static int +YbGetLengthOfKey(const ScanKey *key_ptr) +{ + if (!YbIsRowHeader(key_ptr[0])) + return 1; + + int length_of_key = 0; + + while (!(key_ptr[length_of_key]->sk_flags & SK_ROW_END)) + length_of_key++; + + /* We also want to include the last element. */ + length_of_key++; + return length_of_key; +} + +/* + * Add ordinary key to ybScan. + */ +static void +ybAddOrdinaryScanKey(ScanKey key, YbScanDesc ybScan) +{ + if (ybScan->nkeys >= YB_MAX_SCAN_KEYS) + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_COLUMNS), + errmsg("cannot use more than %d predicates in a table or index scan", + YB_MAX_SCAN_KEYS))); + ybScan->keys[ybScan->nkeys++] = key; +} + +/* + * Extract keys and store to ybScan. + */ +static void +ybExtractScanKeys(ScanKey keys, int nkeys, YbScanDesc ybScan) +{ + for (int i = 0; i < nkeys; ++i) + { + ScanKey key = &keys[i]; + + if (YbIsHashCodeSearch(key)) + { + Assert(!YbIsRowHeader(key)); + ybScan->hash_code_keys = lappend(ybScan->hash_code_keys, key); + } + else + { + ybAddOrdinaryScanKey(key, ybScan); + + /* Extract subkeys in case of row comparison. */ + if (YbIsRowHeader(key)) + { + ScanKey subkey = (ScanKey) key->sk_argument; + + do + { + ybAddOrdinaryScanKey(subkey, ybScan); + } + while (((subkey++)->sk_flags & SK_ROW_END) == 0); + } + } + } +} + +/* + * Return whether the given conditions are unsatisfiable regardless of the + * values in the index because of always FALSE or UNKNOWN conditions. + */ +static bool +YbIsUnsatisfiableCondition(int nkeys, ScanKey keys[]) +{ + for (int i = 0; i < nkeys; ++i) + { + ScanKey key = keys[i]; + + /* + * Look for two cases: + * - op null + * - row(a, b, c) op row(null, e, f) + */ + if (((key->sk_strategy != InvalidStrategy && + (key->sk_flags & SK_ROW_MEMBER) == 0) || + (i > 0 && YbIsRowHeader(keys[i - 1]) && + key->sk_flags & SK_ROW_MEMBER)) && + YbIsNeverTrueNullCond(key)) + { + elog(DEBUG1, "skipping a scan due to unsatisfiable condition"); + return true; + } + } + return false; +} + +static bool +YbShouldPushdownScanKey(YbScanPlan scan_plan, AttrNumber attnum, + ScanKey key) +{ + if (YbIsHashCodeSearch(key)) + return true; + + if (YbIsBasicOpSearch(key)) + { + /* Eq strategy for hash key, eq + ineq for range key. */ + return ybc_should_pushdown_op(scan_plan, attnum, key->sk_strategy); + } + + if (YbIsSearchNull(key)) + { + /* Always expect InvalidStrategy for NULL search. */ + Assert(key->sk_strategy == InvalidStrategy); + return true; + } + + if (YbIsSearchNotNull(key)) + { + /* Always expect InvalidStrategy for IS NOT NULL search. */ + Assert(key->sk_strategy == InvalidStrategy); + return true; + } + + if (YbIsSearchArray(key)) + { + /* + * Only allow equal strategy here (i.e. IN .. or = ANY(..) conditions, + * NOT IN will generate <> which is not a supported LSM/BTREE + * operator, so it should not get to this point. + */ + return key->sk_strategy == BTEqualStrategyNumber; + } + + if (key->sk_flags & SK_ROW_MEMBER) + { + /* We'll recheck if this is a valid row comparison key later. */ + return true; + } + + /* No other operators are supported. */ + return false; +} + +/* int comparator for qsort() */ +static int +int_compar_cb(const void *v1, const void *v2) +{ + const int *k1 = v1; + const int *k2 = v2; + + if (*k1 < *k2) + return -1; + + if (*k1 > *k2) + return 1; + + return 0; +} + +/* Use the scan-descriptor and scan-plan to setup scan key for filtering */ +static void +ybcSetupScanKeys(YbScanDesc ybScan, YbScanPlan scan_plan) +{ + bool qualified_scan_key_cols_has_ybctid = false; + + for (int i = 0; i < ybScan->nkeys; i++) + { + const AttrNumber attnum = scan_plan->bind_key_attnums[i]; + + if (attnum == InvalidAttrNumber) + break; + + int idx = YBAttnumToBmsIndex(scan_plan->target_relation, attnum); + + if (attnum == YBTupleIdAttributeNumber) + { + qualified_scan_key_cols_has_ybctid = true; + scan_plan->qualified_scan_key_cols = + bms_add_member(scan_plan->qualified_scan_key_cols, idx); + } + + /* SeqScan may give scan keys that are not key columns. */ + bool is_key_column = bms_is_member(idx, scan_plan->key_cols); + + if (is_key_column && + YbShouldPushdownScanKey(scan_plan, attnum, ybScan->keys[i])) + { + scan_plan->qualified_scan_key_cols = + bms_add_member(scan_plan->qualified_scan_key_cols, idx); + } + } + + /* + * If hash key is not fully set and ybctid is not set either, we must do a + * full-table scan so clear all the scan keys if the hash code was + * explicitly specified as a scan key then we also shouldn't be clearing the + * scan keys. + */ + if (ybScan->hash_code_keys == NIL && + !bms_is_subset(scan_plan->hash_key_cols, + scan_plan->qualified_scan_key_cols) && + !qualified_scan_key_cols_has_ybctid) + { + bms_free(scan_plan->qualified_scan_key_cols); + scan_plan->qualified_scan_key_cols = NULL; + } +} + +/* Return true if typid is one of the Object Identifier Types */ +static bool +YbIsOidType(Oid typid) +{ + switch (typid) + { + case OIDOID: + case REGPROCOID: + case REGPROCEDUREOID: + case REGOPEROID: + case REGOPERATOROID: + case REGCLASSOID: + case REGTYPEOID: + case REGROLEOID: + case REGNAMESPACEOID: + case REGCONFIGOID: + case REGDICTIONARYOID: + return true; + default: + return false; + } +} + +static int64 +YbDatumGetInt64(Datum value, Oid value_typid) +{ + switch (value_typid) + { + case INT2OID: + return DatumGetInt16(value); + case INT4OID: + return DatumGetInt32(value); + case INT8OID: + return DatumGetInt64(value); + default: + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("not an integer type"))); + } +} + +static bool +YbIsIntegerInRange(Datum value, Oid value_typid, int min, int max) +{ + int64 val = YbDatumGetInt64(value, value_typid); + + return val >= min && val <= max; +} + +/* + * Return true if a scan key column type is compatible with value type. + */ +static bool +YbIsScanCompatible(Oid column_typid, + Oid value_typid, + bool is_value_scalar, + Datum value) +{ + if (column_typid == value_typid) + return true; + + switch (column_typid) + { + case INT2OID: + + /* + * If column c0 has INT2OID type and value type is INT4OID, the + * value may overflow INT2OID. For example, where clause condition + * "c0 = 65539" would become "c0 = 3" and will unnecessarily fetch + * a row with key of 3. This will not affect correctness + * because at upper Postgres layer filtering will be subsequently + * applied for equality/inequality conditions. For example, "c0 = + * 65539" will be applied again to filter out this row. + * We prefer to bind scan key c0 to account for the + * common case where INT4OID value does not overflow INT2OID, + * which happens in some system relation scan queries. + * + * For this purpose, specifically for when the value is scalar, + * we return true when we are sure that + * there isn't a data overflow. For instance, if column c0 has + * INT2OID and value type is INT4OID, and its an inequality + * strategy, we check if the actual value is within the + * bounds of INT2OID. If yes, then we return true, otherwise false. + */ + return (!is_value_scalar ? + (value_typid == INT4OID || value_typid == INT8OID) : + YbIsIntegerInRange(value, value_typid, SHRT_MIN, SHRT_MAX)); + case INT4OID: + return (!is_value_scalar ? + (value_typid == INT2OID || value_typid == INT8OID) : + YbIsIntegerInRange(value, value_typid, INT_MIN, INT_MAX)); + case INT8OID: + return value_typid == INT2OID || value_typid == INT4OID; + + case TEXTOID: + case BPCHAROID: + case VARCHAROID: + return (value_typid == TEXTOID || value_typid == BPCHAROID || + value_typid == VARCHAROID); + + default: + if (YbIsOidType(column_typid) && YbIsOidType(value_typid)) + return true; + /* Conservatively return false. */ + return false; + } +} + +/* + * Determine whether an equality between two types needs further recheck. For + * now, this only flags cases where the storage column type is smaller than the + * value type. + * + * TODO(jason): check if any other type combos need checking. float4 and + * float8 look suspicious. + * + * SELECT a.typname, b.typname + * FROM pg_operator + * JOIN pg_type a ON oprleft = a.oid + * JOIN pg_type b ON oprright = b.oid + * WHERE oprname = '=' AND oprleft != oprright; + * typname | typname + * -------------+------------- + * name | text + * int8 | int2 + * int8 | int4 + * int2 | int8 + * int2 | int4 + * int4 | int8 + * int4 | int2 + * text | name + * xid | int4 + * float4 | float8 + * float8 | float4 + * date | timestamp + * date | timestamptz + * timestamp | date + * timestamp | timestamptz + * timestamptz | date + * timestamptz | timestamp + * (17 rows) + */ +static bool +YbShouldRecheckEquality(Oid column_typid, Oid value_typid) +{ + switch (column_typid) + { + case INT2OID: + if (value_typid == INT4OID) + return true; + yb_switch_fallthrough(); + case INT4OID: + if (value_typid == INT8OID) + return true; + break; + default: + break; + } + + return false; +} + +static bool +YbIsValueOutOfRange(Oid col_typid, Oid val_typid, Datum val) +{ + return (YbShouldRecheckEquality(col_typid, val_typid) && + !YbIsIntegerInRange(val, val_typid, + col_typid == INT2OID ? SHRT_MIN : INT_MIN, + col_typid == INT2OID ? SHRT_MAX : INT_MAX)); +} + +static bool +YbNeedTupleRangeCheck(Datum value, TupleDesc bind_desc, + int key_length, AttrNumber bind_key_attnums[]) +{ + Oid tupType = HeapTupleHeaderGetTypeId(DatumGetHeapTupleHeader(value)); + Oid tupTypmod = HeapTupleHeaderGetTypMod(DatumGetHeapTupleHeader(value)); + TupleDesc val_tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + bool need_check = false; + + for (int i = 0; i < key_length; i++) + { + Oid val_type = ybc_get_atttypid(val_tupdesc, i + 1); + Oid column_type = ybc_get_atttypid(bind_desc, + bind_key_attnums[i]); + + if (YbShouldRecheckEquality(column_type, val_type)) + { + need_check = true; + break; + } + } + ReleaseTupleDesc(val_tupdesc); + return need_check; +} + +static bool +YbIsTupleInRange(Datum value, TupleDesc bind_desc, + int key_length, AttrNumber bind_key_attnums[]) +{ + Oid tupType = HeapTupleHeaderGetTypeId(DatumGetHeapTupleHeader(value)); + Oid tupTypmod = HeapTupleHeaderGetTypMod(DatumGetHeapTupleHeader(value)); + TupleDesc val_tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + Datum datum_values[key_length]; + bool datum_nulls[key_length]; + + HeapTupleData tuple; + + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_len = HeapTupleHeaderGetDatumLength(value); + tuple.t_data = DatumGetHeapTupleHeader(value); + heap_deform_tuple(&tuple, val_tupdesc, + datum_values, datum_nulls); + bool is_in_range = true; + + for (int i = 0; i < key_length; i++) + { + Datum val = datum_values[i]; + Oid val_type = ybc_get_atttypid(val_tupdesc, i + 1); + Oid column_type = ybc_get_atttypid(bind_desc, + bind_key_attnums[i]); + + if (YbIsValueOutOfRange(column_type, val_type, val)) + { + is_in_range = false; + break; + } + } + ReleaseTupleDesc(val_tupdesc); + return is_in_range; +} + +/* + * We require compatible column type and value type to avoid misinterpreting the value Datum + * using a column type that can cause wrong scan results. Returns true if the column type + * and value type are compatible. + */ +static bool +YbCheckScanTypes(YbScanDesc ybScan, YbScanPlan scan_plan, int i) +{ + ScanKey key = ybScan->keys[i]; + Oid valtypid = key->sk_subtype; + + /* + * TODO(jason): this RECORDOID logic is hacky. It essentially skips the + * whole check. This should only arise from the (SK_ROW_HEADER | + * SK_SEARCHARRAY) case, and in that case, this call should be avoided to + * begin with since scan_plan->bind_key_attnums is irrelevant. Or rather, + * bind_key_attnums usage should be completely reworked to not be used in + * all cases where SK_ROW_HEADER is involved. + */ + Oid atttypid = (valtypid == RECORDOID ? + RECORDOID : + ybc_get_atttypid(scan_plan->bind_desc, + scan_plan->bind_key_attnums[i])); + + Assert(OidIsValid(atttypid)); + + /* + * Example: CREATE TABLE t1(c0 REAL, c1 TEXT, PRIMARY KEY(c0 asc)); + * INSERT INTO t1(c0, c1) VALUES(0.4, 'SHOULD BE IN RESULT'); + * SELECT ALL t1.c1 FROM t1 WHERE ((0.6)>(t1.c0)); + * Internally, c0 has float4 type, 0.6 has float8 type. If we bind 0.6 directly with + * column c0, float8 0.6 will be misinterpreted as float4. However, casting to float4 + * may lose precision. Here we simply do not bind a key when there is a type mismatch + * by leaving start_valid[idx] and end_valid[idx] as false. For the following cases + * we assume that Postgres ensures there is no concern for type mismatch. + * (1) value type is not a valid type id + * (2) InvalidStrategy (for IS NULL) + * (3) value type is a polymorphic pseudotype + */ + return (!OidIsValid(valtypid) || + key->sk_strategy == InvalidStrategy || + YbIsScanCompatible(atttypid, valtypid, + !YbIsRowHeader(key) && !YbIsSearchArray(key), + key->sk_argument) || + IsPolymorphicType(valtypid)); +} + +static bool +YbBindRowComparisonKeys(YbScanDesc ybScan, YbScanPlan scan_plan, + int skey_index, bool is_not_null[], + bool is_for_precheck) +{ + int last_att_no = YBFirstLowInvalidAttributeNumber; + Relation index = ybScan->index; + int length_of_key = YbGetLengthOfKey(&ybScan->keys[skey_index]); + + ScanKey header_key = ybScan->keys[skey_index]; + + ScanKey *subkeys = &ybScan->keys[skey_index + 1]; + + /* + * We can only push down right now if the key columns are specified in the + * correct order and the key has no hashed columns. We also need to ensure + * that the same comparison operation is done to all subkeys. + */ + bool can_pushdown = true; + + int strategy = header_key->sk_strategy; + int subkey_count = length_of_key - 1; + + for (int j = 0; j < subkey_count; j++) + { + ScanKey key = subkeys[j]; + + /* Make sure that the specified keys are in the right order. */ + if (key->sk_attno <= last_att_no) + { + can_pushdown = false; + break; + } + + /* + * Make sure that the same comparator is applied to + * all subkeys. + */ + if (strategy != key->sk_strategy) + { + can_pushdown = false; + break; + } + last_att_no = key->sk_attno; + + /* Make sure that there are no hash key columns. */ + if (index->rd_indoption[key->sk_attno - 1] + & INDOPTION_HASH) + { + can_pushdown = false; + break; + } + } + + /* Make sure that there are no hash keys in order to push down. */ + for (int i = 0; (i < index->rd_index->indnkeyatts) && can_pushdown; i++) + { + if (index->rd_indoption[i] & INDOPTION_HASH) + can_pushdown = false; + } + + bool needs_recheck = true; + + if (can_pushdown) + { + + YbcPgExpr *col_values = palloc(sizeof(YbcPgExpr) * + index->rd_index->indnkeyatts); + + /* + * Prepare upper/lower bound tuples determined from this + * clause for bind. Care must be taken in the case + * that key columns in the index are ordered + * differently from each other. For example, consider + * if the underlying index has key + * (r1 ASC, r2 DESC, r3 ASC) and we are dealing with + * a clause like (r1, r2, r3) <= (40, 35, 12). + * We cannot simply bind (40, 35, 12) as an upper bound + * as that will miss tuples such as (40, 32, 0). + * Instead we must push down (40, Inf, 12) in this case + * for correctness. (Note that +Inf in this context + * is higher in STORAGE order than all other values not + * necessarily logical order, similar to the role of + * docdb::ValueType::kHighest. + */ + + /* + * Is the first column in ascending order in the index? + * This is important because whether or not the RHS of a + * (row key) >= (row key values) expression is + * considered an upper bound is dependent on the answer + * to this question. The RHS of such an expression will + * be the scan upper bound if the first column is in + * descending order and lower if else. Similar logic + * applies to the RHS of (row key) <= (row key values) + * expressions. + */ + bool is_direction_asc = !(index->rd_indoption[subkeys[0]->sk_attno - 1] & + INDOPTION_DESC); + + bool gt = (strategy == BTGreaterEqualStrategyNumber || + strategy == BTGreaterStrategyNumber); + + bool is_inclusive = (strategy != BTGreaterStrategyNumber && + strategy != BTLessStrategyNumber); + + bool is_point_scan = ((subkey_count == index->rd_index->indnatts) && + (strategy == BTEqualStrategyNumber)); + + /* Whether or not the RHS values make up a DocDB upper bound */ + bool is_upper_bound = gt ^ is_direction_asc; + size_t subkey_index = 0; + + for (int j = 0; j < index->rd_index->indnkeyatts; j++) + { + bool is_column_specified = (subkey_index < subkey_count && + (subkeys[subkey_index]->sk_attno - 1) == j); + + /* + * Is the current column stored in ascending order in the + * underlying index? + */ + bool asc = (index->rd_indoption[j] & INDOPTION_DESC) == 0; + + /* + * If this column has different directionality than the + * first column then we have to adjust the bounds on this + * column. + */ + if (!is_column_specified || + (asc != is_direction_asc && !is_point_scan)) + { + col_values[j] = NULL; + needs_recheck = true; + + /* + * If this is just for precheck, we can return that recheck + * is needed. + */ + if (is_for_precheck) + return true; + } + else if (!is_for_precheck) + { + ScanKey current = subkeys[subkey_index]; + AttrNumber attnum = + scan_plan->bind_key_attnums[skey_index + 1 + subkey_index]; + + col_values[j] = YBCNewConstant(ybScan->handle, + ybc_get_atttypid(scan_plan->bind_desc, + attnum), + current->sk_collation, + current->sk_argument, + false); + } + + if (is_column_specified) + { + int att_idx = YBAttnumToBmsIndex(ybScan->table, + subkeys[subkey_index]->sk_attno); + + /* Set the first column in this RC to not null. */ + is_not_null[att_idx] |= subkey_index == 0; + + subkey_index++; + } + } + + if (is_for_precheck) + return needs_recheck; + + if (is_upper_bound || strategy == BTEqualStrategyNumber) + { + HandleYBStatus(YBCPgDmlAddRowUpperBound(ybScan->handle, + index->rd_index->indnkeyatts, + col_values, + is_inclusive)); + } + + if (!is_upper_bound || strategy == BTEqualStrategyNumber) + { + HandleYBStatus(YBCPgDmlAddRowLowerBound(ybScan->handle, + index->rd_index->indnkeyatts, + col_values, + is_inclusive)); + } + } + + return needs_recheck; +} + +static Datum +YbGetArrayConst(ScanKey *keys) +{ + /* + * Get array from keys. See skey.h and ybExtractScanKeys for layout + * details. + */ + if (YbIsRowHeader(*keys)) + return (*(++keys))->sk_argument; + return (*keys)->sk_argument; +} + +/* + * Given an array, cull it by removing unsatisfiable and duplicate elements. + * + * Out params: + * - scalar_null_bound: for scalar (non-row) arrays, whether a null element was + * found and NULLs are to be retained + * - culled_elem_values: palloc'd culled array as a C-array of Datums + * - culled_num_elems: culled array size + * + * Returns false if the array is culled to zero elements and NULL is not bound. + * Caller is still expected to pfree culled_elem_values in this case. + */ +static bool +YbCullArray(ArrayType *arrayval, + ScanKey key, + TupleDesc bind_desc, + Relation index, + bool is_row, + int row_nkeys, AttrNumber *row_attnums, + Oid scalar_col_typid, Oid scalar_val_typid, + bool *scalar_null_bound, + Datum **culled_elem_values, + int *culled_num_elems) +{ + int16 elmlen; + bool elmbyval; + char elmalign; + Datum *elem_values; + bool *elem_nulls; + int num_elems; + + get_typlenbyvalalign(ARR_ELEMTYPE(arrayval), + &elmlen, &elmbyval, &elmalign); + + deconstruct_array(arrayval, + ARR_ELEMTYPE(arrayval), + elmlen, elmbyval, elmalign, + &elem_values, &elem_nulls, &num_elems); + *culled_elem_values = elem_values; + + int num_valid = 0; + bool retain_nulls = YbSearchArrayRetainNulls(key); + + /* + * Filter out nulls and out-of-range elements since they'll never match. + * + * Four cases for nulls: + * - is_row=t, retain_nulls=t: save the value (which is a row that contains + * null(s)) + * - is_row=t, retain_nulls=f: ignore (this value cannot match) + * - is_row=f, retain_nulls=t: save the fact that a null was encountered + * (as any further nulls encoutered are just duplicates) + * - is_row=f, retain_nulls=f: ignore (this value cannot match) + */ + if (is_row) + { + /* + * To speed up the common case, cache whether we need to do any value + * out-of-range checks on the elements. Each value row has the same + * types as the first value row, so looking at the first value row is + * sufficient. + */ + bool row_should_check_range = (num_elems > 0 && + YbNeedTupleRangeCheck(elem_values[0], + bind_desc, + row_nkeys, + row_attnums)); + + for (int i = 0; i < num_elems; i++) + { + bool row_has_nulls = + HeapTupleHeaderHasNulls(DatumGetHeapTupleHeader(elem_values[i])); + + /* + * For rows, we use row_has_nulls instead of elem_nulls. + */ + Assert(!elem_nulls[i]); + + if (!retain_nulls && row_has_nulls) + continue; + + if (row_should_check_range) + { + /* + * is_row has two cases: + * - BNL: never sets YB_SK_SEARCHARRAY_RETAIN_NULLS + * - INSERT ON CONFLICT batching: never needs tuple in range + * check + * Hence, the following assert. + */ + Assert(!row_has_nulls); + + if (!YbIsTupleInRange(elem_values[i], + bind_desc, + row_nkeys, + row_attnums)) + continue; + } + + elem_values[num_valid++] = elem_values[i]; + } + } + else + { + for (int i = 0; i < num_elems; i++) + { + if (elem_nulls[i]) + { + if (retain_nulls) + *scalar_null_bound = true; + continue; + } + + if (YbIsValueOutOfRange(scalar_col_typid, scalar_val_typid, + elem_values[i])) + continue; + + elem_values[num_valid++] = elem_values[i]; + } + } + + pfree(elem_nulls); + + /* + * If there are no non-nulls, and binding to NULL is not required, the scan + * qual is unsatisfiable. + */ + if (num_valid == 0 && !(!is_row && *scalar_null_bound)) + return false; + + /* Build temporary vars */ + IndexScanDescData tmp_scan_desc; + + memset(&tmp_scan_desc, 0, sizeof(IndexScanDescData)); + tmp_scan_desc.indexRelation = index; + + /* + * Sort the non-null elements and eliminate any duplicates. We must + * sort in the same ordering used by the index column, so that the + * successive primitive indexscans produce data in index order. + */ + *culled_num_elems = _bt_sort_array_elements(&tmp_scan_desc, key, + false, /* reverse */ + elem_values, num_valid); + + return true; +} + +/* + * Bind scalar array ops and row array ops. + * + * skey_index represents the scan key index we are focusing on. + */ +static void +YbBindSearchArray(YbScanDesc ybScan, YbScanPlan scan_plan, + int skey_index, bool is_for_precheck, bool is_column_bound[], + bool *bail_out) +{ + /* based on _bt_preprocess_array_keys() */ + ArrayType *arrayval; + int num_elems; + Datum *elem_values; + ScanKey key = ybScan->keys[skey_index]; + AttrNumber scalar_attnum = scan_plan->bind_key_attnums[skey_index]; + Relation relation = ybScan->table; + + *bail_out = false; + + bool is_row = false; + int row_nkeys; + AttrNumber *row_attnums; + Oid scalar_col_typid; + Oid scalar_val_typid; + bool scalar_null_bound; + + if (YbIsRowHeader(key)) + { + Bitmapset *newly_bound_idxs = NULL; + int bound_idx; + + /* + * Get num subkeys and their attnums in this rowkey (exclude header). + * See skey.h and ybExtractScanKeys for layout details. + */ + is_row = true; + row_nkeys = YbGetLengthOfKey(&ybScan->keys[skey_index]) - 1; + row_attnums = &scan_plan->bind_key_attnums[skey_index + 1]; + + /* If any column is already bound, give up. */ + for (int row_idx = 0; row_idx < row_nkeys; row_idx++) + { + bound_idx = YBAttnumToBmsIndex(relation, row_attnums[row_idx]); + + if (is_column_bound[bound_idx]) + { + ybScan->needs_recheck = true; + return; + } + + newly_bound_idxs = bms_add_member(newly_bound_idxs, bound_idx); + } + + /* + * All columns are bindable: mark them as bound before proceeding to + * bind them. + */ + while ((bound_idx = bms_first_member(newly_bound_idxs)) >= 0) + { + is_column_bound[bound_idx] = true; + } + + bms_free(newly_bound_idxs); + } + else + { + scalar_col_typid = ybc_get_atttypid(scan_plan->bind_desc, + scalar_attnum); + scalar_val_typid = key->sk_subtype; + scalar_null_bound = false; + Assert(!is_column_bound[YBAttnumToBmsIndex(relation, scalar_attnum)]); + is_column_bound[YBAttnumToBmsIndex(relation, scalar_attnum)] = true; + } + + if (is_for_precheck) + return; + + arrayval = DatumGetArrayTypeP(YbGetArrayConst(&ybScan->keys[skey_index])); + Assert(key->sk_subtype == ARR_ELEMTYPE(arrayval)); + if (!YbCullArray(arrayval, + key, + scan_plan->bind_desc, + ybScan->index, + is_row, + row_nkeys, row_attnums, + scalar_col_typid, scalar_val_typid, &scalar_null_bound, + &elem_values, + &num_elems)) + { + *bail_out = true; + pfree(elem_values); + return; + } + + if (is_row) + { + ybcBindTupleExprCondIn(ybScan, scan_plan->bind_desc, + row_nkeys, row_attnums, + num_elems, elem_values); + } + else if (scalar_attnum == YBTupleIdAttributeNumber) + YBCPgBindYbctids(ybScan->handle, num_elems, elem_values); + else + ybcBindColumnCondIn(ybScan, scan_plan->bind_desc, + scalar_attnum, num_elems, + elem_values, scalar_null_bound); + + pfree(elem_values); +} + +/* + * Use the scan-descriptor and scan-plan to setup binds for the queryplan. + */ +static bool +ybBindOrdinaryScanKeys(YbScanDesc ybScan, YbScanPlan scan_plan, Scan *scan, + bool is_for_precheck) +{ + Relation relation = scan_plan->target_relation; + + /* + * Set up the arrays to store the search intervals for each PG/YSQL + * attribute (i.e. DocDB column). + * The size of the arrays will be based on the max attribute + * number used in the query but, as usual, offset to account for the + * negative attribute numbers of system attributes. + */ + int max_idx = 0; + + for (int i = 0; i < ybScan->nkeys; i++) + { + int idx = YBAttnumToBmsIndex(relation, scan_plan->bind_key_attnums[i]); + + if (max_idx < idx && bms_is_member(idx, scan_plan->qualified_scan_key_cols)) + max_idx = idx; + } + max_idx++; + + /* Find intervals for columns */ + + bool is_column_bound[max_idx]; /* VLA - scratch space */ + + memset(is_column_bound, 0, sizeof(bool) * max_idx); + + bool start_valid[max_idx]; /* VLA - scratch space */ + + memset(start_valid, 0, sizeof(bool) * max_idx); + + bool end_valid[max_idx]; /* VLA - scratch space */ + + memset(end_valid, 0, sizeof(bool) * max_idx); + + bool is_not_null[max_idx]; /* VLA - scratch space */ + + memset(is_not_null, 0, sizeof(bool) * max_idx); + + Datum start[max_idx]; /* VLA - scratch space */ + Datum end[max_idx]; /* VLA - scratch space */ + + bool start_inclusive[max_idx]; /* VLA - scratch space */ + + memset(start_inclusive, 0, sizeof(bool) * max_idx); + + bool end_inclusive[max_idx]; /* VLA - scratch space */ + + memset(end_inclusive, 0, sizeof(bool) * max_idx); + + FmgrInfo *start_cmp_fn[max_idx]; + FmgrInfo *end_cmp_fn[max_idx]; + + /* + * Find an order of relevant keys such that for the same column, an EQUAL + * condition is encountered before IN or BETWEEN. is_column_bound is then + * used to establish priority order ROW IN > EQUAL > IN > BETWEEN. + * IS NOT NULL is treated as a special case of BETWEEN. + */ + int noffsets = 0; + int offsets[ybScan->nkeys + 1]; /* VLA - scratch space: +1 to + * avoid zero elements */ + int length_of_key = 0; + + for (int i = 0; i < ybScan->nkeys; i += length_of_key) + { + length_of_key = YbGetLengthOfKey(&ybScan->keys[i]); + ScanKey key = ybScan->keys[i]; + + /* Prioritize binding SAOPs that are pinned by merge scan. */ + if (YbIsSearchArray(key)) + { + Datum this_array_const; + ListCell *lc; + YbMergeScanInfo *yb_merge_scan_info = NULL; + + this_array_const = YbGetArrayConst(&ybScan->keys[i]); + + if (scan) + { + if (IsA(scan, IndexScan)) + yb_merge_scan_info = + ((IndexScan *) scan)->yb_merge_scan_info; + else if (IsA(scan, IndexOnlyScan)) + yb_merge_scan_info = + ((IndexOnlyScan *) scan)->yb_merge_scan_info; + } + + if (yb_merge_scan_info) + { + foreach(lc, yb_merge_scan_info->saop_cols) + { + ScalarArrayOpExpr *pinned_saop = + ((YbMergeScanSaopColInfo *) lfirst(lc))->saop; + Datum pinned_array_const = + ((Const *) lsecond(pinned_saop->args))->constvalue; + + /* + * Direct datum comparison (compared to datumIsEqual) is + * safe because yb_match_in_index_clause and + * ExecIndexBuildScanKeys set pinned_array_const and + * this_array_const, respectively, to the same field in + * memory. + */ + if (this_array_const == pinned_array_const) + { + bool bail_out = false; + + /* YbBindSearchArray updates is_column_bound. */ + YbBindSearchArray(ybScan, scan_plan, i, + is_for_precheck, + is_column_bound, + &bail_out); + + if (bail_out) + return false; + + continue; + } + } + } + } + + /* Check if this is full key row comparison expression */ + if (YbIsRowHeader(key) && + !YbIsSearchArray(key)) + { + bool needs_recheck = YbBindRowComparisonKeys(ybScan, scan_plan, i, + is_not_null, + is_for_precheck); + + ybScan->needs_recheck |= needs_recheck; + /* + * Full key RowComparison bindings don't interact + * or interfere too much with other bindings to the same columns. + * They set the upper/lower bounds of the requested scan and also + * apply IS NOT NULL filters on the bound LHS columns. + */ + continue; + } + + int bind_key_attnum = scan_plan->bind_key_attnums[i]; + int idx = YBAttnumToBmsIndex(relation, bind_key_attnum); + + /* Check if this is a qualified key column. */ + if (!bms_is_member(idx, scan_plan->qualified_scan_key_cols)) + { + ybScan->needs_recheck = true; + continue; + } + + /* + * Assign key offsets. Where n is the number of keys, and i is the + * clause's index in the list (i < n): + * Clause Type | Value + * ----------------------+-------------- + * ROW IN | -(n * 2 + i) + * EQUAL, IS NULL | -(n + i) + * IN | -i + * BETWEEN, IS NOT NULL | i + * + * qsort will place the larger negative values first, and a modulo + * operation will return the clause's original index. + */ + switch (key->sk_strategy) + { + case InvalidStrategy: + if (YbIsSearchNotNull(key)) + { + offsets[noffsets++] = i; + break; + } + /* Should be ensured during planning. */ + Assert(YbIsSearchNull(key)); + /* fallthrough -- treating IS NULL as (DocDB) = (null) */ + yb_switch_fallthrough(); + case BTEqualStrategyNumber: + if (YbIsBasicOpSearch(key) || YbIsSearchNull(key)) + { + /* + * Use a -ve value so that qsort places EQUAL before + * others + */ + offsets[noffsets++] = -(ybScan->nkeys + i); + } + else if (YbIsSearchArray(key)) + { + /* Row IN expressions take priority over all. */ + offsets[noffsets++] = (length_of_key > 1 ? + -(ybScan->nkeys * 2 + i) : + -i); + } + break; + case BTGreaterEqualStrategyNumber: + case BTGreaterStrategyNumber: + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + offsets[noffsets++] = i; + yb_switch_fallthrough(); + + default: + break; /* unreachable */ + } + } + + qsort(offsets, noffsets, sizeof(int), int_compar_cb); + /* restore -ve offsets to +ve */ + for (int i = 0; i < noffsets; i++) + if (offsets[i] < 0) + offsets[i] = (-offsets[i]) % (ybScan->nkeys); + else + break; + + /* + * Bind keys for EQUALS and IN, collecting info for ranges and IS NOT NULL + */ + for (int k = 0; k < noffsets; k++) + { + int i = offsets[k]; + ScanKey key = ybScan->keys[i]; + int idx = YBAttnumToBmsIndex(relation, scan_plan->bind_key_attnums[i]); + + /* + * YBAttnumToBmsIndex should guarantee that index is positive + * -- needed for hash code search below. + */ + Assert(idx > 0); + + /* Do not bind more than one condition to a column */ + if (is_column_bound[idx] || + !YbCheckScanTypes(ybScan, scan_plan, i)) + { + ybScan->needs_recheck = true; + continue; + } + + bool bound_inclusive = false; + + switch (key->sk_strategy) + { + case InvalidStrategy: + if (YbIsSearchNotNull(key)) + { + is_not_null[idx] = true; + break; + } + + /* + * Otherwise this is an IS NULL search. c IS NULL -> c = NULL + * (checked above) + */ + yb_switch_fallthrough(); + case BTEqualStrategyNumber: + /* Bind the scan keys */ + if (YbIsBasicOpSearch(key) || YbIsSearchNull(key)) + { + /* Either c = NULL or c IS NULL. */ + if (!is_for_precheck) + { + bool is_null = (key->sk_flags & SK_ISNULL) == SK_ISNULL; + + YbBindColumn(ybScan, scan_plan->bind_desc, + scan_plan->bind_key_attnums[i], + key->sk_argument, is_null); + } + is_column_bound[idx] = true; + } + else if (YbIsSearchArray(key)) + { + bool bail_out = false; + + /* YbBindSearchArray updates is_column_bound. */ + YbBindSearchArray(ybScan, scan_plan, i, + is_for_precheck, + is_column_bound, + &bail_out); + + if (bail_out) + return false; + } + break; + + case BTGreaterEqualStrategyNumber: + bound_inclusive = true; + yb_switch_fallthrough(); + case BTGreaterStrategyNumber: + /* + * For prechecks, we skip computation of the range bounds as we + * are interested in only knowing if the keys can be bound, and + * not what they bind to. Further, in some cases such as nested + * subqueries, the value datums may not yet be available during + * the precheck. + */ + if (is_for_precheck) + break; + + if (start_valid[idx]) + { + /* take max of old value and new value */ + bool is_gt = DatumGetBool(FunctionCall2Coll(&key->sk_func, + key->sk_collation, + start[idx], + key->sk_argument)); + + if (!is_gt) + { + start[idx] = key->sk_argument; + start_inclusive[idx] = bound_inclusive; + start_cmp_fn[idx] = &key->sk_func; + } + } + else + { + start[idx] = key->sk_argument; + start_inclusive[idx] = bound_inclusive; + start_valid[idx] = true; + start_cmp_fn[idx] = &key->sk_func; + } + + if (end_valid[idx]) + { + bool is_lt = DatumGetBool(FunctionCall2Coll(end_cmp_fn[idx], + key->sk_collation, + start[idx], + end[idx])); + + if (!is_lt) + return false; + + bool is_gt = DatumGetBool(FunctionCall2Coll(start_cmp_fn[idx], + key->sk_collation, + end[idx], + start[idx])); + + if (!is_gt) + return false; + } + break; + + case BTLessEqualStrategyNumber: + bound_inclusive = true; + yb_switch_fallthrough(); + case BTLessStrategyNumber: + /* + * For prechecks, we skip computation of the range bounds as we + * are interested in only knowing if the keys can be bound, and + * not what they bind to. Further, in some cases such as nested + * subqueries, the value datums may not yet be available during + * the precheck. + */ + if (is_for_precheck) + break; + + if (end_valid[idx]) + { + /* take min of old value and new value */ + bool is_lt = DatumGetBool(FunctionCall2Coll(&key->sk_func, + key->sk_collation, + end[idx], + key->sk_argument)); + + if (!is_lt) + { + end[idx] = key->sk_argument; + end_inclusive[idx] = bound_inclusive; + end_cmp_fn[idx] = &key->sk_func; + } + } + else + { + end[idx] = key->sk_argument; + end_inclusive[idx] = bound_inclusive; + end_valid[idx] = true; + end_cmp_fn[idx] = &key->sk_func; + } + + if (start_valid[idx]) + { + bool is_gt = DatumGetBool(FunctionCall2Coll(start_cmp_fn[idx], + key->sk_collation, + end[idx], + start[idx])); + + if (!is_gt) + return false; + + bool is_lt = DatumGetBool(FunctionCall2Coll(end_cmp_fn[idx], + key->sk_collation, + start[idx], + end[idx])); + + if (!is_lt) + return false; + } + break; + + default: + break; /* unreachable */ + } + } + + /* Bind keys for BETWEEN and IS NOT NULL */ + int min_idx = bms_first_member(scan_plan->qualified_scan_key_cols); + + min_idx = min_idx < 0 ? 0 : min_idx; + for (int idx = min_idx; idx < max_idx; idx++) + { + /* There's no range key or IS NOT NULL for this query */ + if (!start_valid[idx] && !end_valid[idx] && !is_not_null[idx]) + continue; + + /* Do not bind more than one condition to a column */ + if (is_column_bound[idx]) + { + ybScan->needs_recheck = true; + continue; + } + + if (!is_for_precheck) + { + if (start_valid[idx] || end_valid[idx]) + { + YbBindColumnCondBetween(ybScan, scan_plan->bind_desc, + YBBmsIndexToAttnum(relation, idx), + start_valid[idx], start_inclusive[idx], + start[idx], end_valid[idx], + end_inclusive[idx], end[idx]); + } + else + { + Assert(is_not_null[idx]); + YbBindColumnNotNull(ybScan, scan_plan->bind_desc, + YBBmsIndexToAttnum(relation, idx)); + } + } + } + return true; +} + +typedef struct +{ + YbcPgBoundType type; + int64_t value; +} YbBound; + +static inline bool +YbBoundEqual(const YbBound *lhs, const YbBound *rhs) +{ + return lhs->type == rhs->type && lhs->value == rhs->value; +} + +typedef struct +{ + YbBound start; + YbBound end; +} YbRange; + +static inline bool +YbBoundValid(const YbBound *bound) +{ + return bound->type != YB_YQL_BOUND_INVALID; +} + +static inline bool +YbBoundInclusive(const YbBound *bound) +{ + return bound->type == YB_YQL_BOUND_VALID_INCLUSIVE; +} + +static bool +YbIsValidRange(const YbBound *start, const YbBound *end) +{ + Assert(YbBoundValid(start) && YbBoundValid(end)); + return (start->value < end->value || + (start->value == end->value && + YbBoundInclusive(start) && + YbBoundInclusive(end))); +} + +static bool +YbApplyStartBound(YbRange *range, const YbBound *start) +{ + Assert(YbIsValidRange(&range->start, &range->end)); + Assert(YbBoundValid(start)); + + if (!YbIsValidRange(start, &range->end)) + return false; + + if ((range->start.value < start->value) || + (range->start.value == start->value && !YbBoundInclusive(start))) + { + range->start = *start; + } + return true; +} + +static bool +YbApplyEndBound(YbRange *range, const YbBound *end) +{ + Assert(YbIsValidRange(&range->start, &range->end)); + Assert(YbBoundValid(end)); + + if (!YbIsValidRange(&range->start, end)) + return false; + + if ((range->end.value > end->value) || + (range->end.value == end->value && !YbBoundInclusive(end))) + { + range->end = *end; + } + return true; +} + +static inline uint16_t +YbBoundUint16Value(const YbBound *bound) +{ + Assert(bound->type == YB_YQL_BOUND_INVALID || + (bound->value >= 0 && bound->value <= UINT16_MAX)); + return bound->value; +} + +static bool +ybBindSearchHashCodeScanKeys(YbScanDesc ybScan, bool is_for_precheck) +{ + static const YbBound YB_MIN_HASH_BOUND = { + .type = YB_YQL_BOUND_VALID_INCLUSIVE, + .value = 0, + }; + static const YbBound YB_MAX_HASH_BOUND = { + .type = YB_YQL_BOUND_VALID_INCLUSIVE, + .value = UINT16_MAX, + }; + YbRange range = { + .start = YB_MIN_HASH_BOUND, + .end = YB_MAX_HASH_BOUND, + }; + ListCell *lc; + + foreach(lc, ybScan->hash_code_keys) + { + ScanKey key = (ScanKey) lfirst(lc); + + Assert(YbIsHashCodeSearch(key)); + YbBound bound = { + .type = YB_YQL_BOUND_VALID, + .value = YbDatumGetInt64(key->sk_argument, key->sk_subtype) + }; + + switch (key->sk_strategy) + { + case BTEqualStrategyNumber: + bound.type = YB_YQL_BOUND_VALID_INCLUSIVE; + if (!YbApplyStartBound(&range, &bound) || + !YbApplyEndBound(&range, &bound)) + return false; + break; + + case BTGreaterEqualStrategyNumber: + bound.type = YB_YQL_BOUND_VALID_INCLUSIVE; + yb_switch_fallthrough(); + case BTGreaterStrategyNumber: + if (!YbApplyStartBound(&range, &bound)) + return false; + break; + + case BTLessEqualStrategyNumber: + bound.type = YB_YQL_BOUND_VALID_INCLUSIVE; + yb_switch_fallthrough(); + case BTLessStrategyNumber: + if (!YbApplyEndBound(&range, &bound)) + return false; + break; + + default: + break; /* unreachable */ + } + } + + if (is_for_precheck) + return true; + + if (YbBoundEqual(&range.start, &YB_MIN_HASH_BOUND)) + range.start.type = YB_YQL_BOUND_INVALID; + if (YbBoundEqual(&range.end, &YB_MAX_HASH_BOUND)) + range.end.type = YB_YQL_BOUND_INVALID; + if (YbBoundValid(&range.start) || YbBoundValid(&range.end)) + YBCPgDmlBindHashCodes(ybScan->handle, range.start.type, + YbBoundUint16Value(&range.start), range.end.type, + YbBoundUint16Value(&range.end)); + + return true; +} + +/* + * Two modes: + * - is_for_precheck=true: return whether unsatisfiable conditions were found + * and set field needs_recheck in the given ybScan. Both results are not + * necessarily exact and err on the side of caution: + * - may return false (signifying no unsatisfiable conditions) even if there + * are unsatisfiable conditions. + * - may set needs_recheck true even if recheck is not actually needed. + * - is_for_precheck=false: same as above case but also actually performs binds + * to the ybScan->handle. + * + * TODO(jason): needs_recheck initialization should be made clearer by only + * setting it once such as by passing it as an out-param to this function. + * This only makes more sense if the constant params are eliminated from + * YbScanDesc and put in some different structure. In general, this function + * deserves three cases of return information: bail-out, no-bail-out+recheck, + * no-bail-out+no-recheck. + */ +static bool +ybBindScanKeys(YbScanDesc ybScan, YbScanPlan scan_plan, Scan *scan, + bool is_for_precheck) +{ + /* + * Best-effort try to determine if all keys are bound. + * - YBCIsSysTablePrefetchingStarted: if this scan is for system table + * prefetching, it is a special case that doesn't push down conditions, + * so assume the worst. + */ + ybScan->needs_recheck = YBCIsSysTablePrefetchingStarted(); + + /* + * For testing, skip doing any scan key binds if + * yb_test_skip_binding_scan_keys=true. Internal scans (signified by + * "scan" variable being NULL) do not have additional recheck mechanisms + * besides YB recheck, so avoid skipping binds for those as that may + * possibly lead to incorrect results. + */ + if (yb_test_skip_binding_scan_keys && scan) + { + /* + * NOTE: We set needs_recheck to true even in case there are zero scan + * keys. + */ + ybScan->needs_recheck = true; + return true; + } + + if (!ybBindOrdinaryScanKeys(ybScan, scan_plan, scan, is_for_precheck)) + { + /* In case of unsatisfiable scan, recheck is trivially not needed. */ + ybScan->needs_recheck = false; + return false; + } + + if (!ybBindSearchHashCodeScanKeys(ybScan, is_for_precheck)) + { + /* In case of unsatisfiable scan, recheck is trivially not needed. */ + ybScan->needs_recheck = false; + return false; + } + + return true; +} + +static inline void +ybcPullVarattnosIntoAttnumBms(List *list, Index varno, YbAttnumBmsState *state) +{ + if (list) + pull_varattnos_min_attr((Node *) list, varno, &state->bms, + state->min_attr); +} + +/* + * Adds any columns referenced by the bitmap scan local quals to the + * required_attrs bitmap. + * + * If the local quals will not be used, the caller is responsible for ensuring + * that they are removed from the YbBitmapTableScan node before calling this. + */ +static void +YbAddBitmapScanRecheckColumns(YbBitmapTableScan *plan, Index target_relid, + YbAttnumBmsState *required_attrs) +{ + ybcPullVarattnosIntoAttnumBms(plan->fallback_local_quals, target_relid, + required_attrs); + ybcPullVarattnosIntoAttnumBms(plan->recheck_local_quals, target_relid, + required_attrs); +} + +static void +ybcAddNonDroppedAttr(const TupleDesc tup_desc, + AttrNumber attnum, + YbAttnumBmsState *attnums) +{ + if (!TupleDescAttr(tup_desc, attnum - 1)->attisdropped) + ybcAttnumBmsAdd(attnums, attnum); +} + +/* + * Replace decoded PK column requests with a request for ybidxbasectid. + */ +static void +ybcReplacePkReqWithBasectid(IndexOnlyScan *ios_plan, + YbAttnumBmsState *result) +{ + if (ios_plan->yb_num_decoded_pk_cols > 0) + { + int phys_natts = list_length(ios_plan->indextlist) - + ios_plan->yb_num_decoded_pk_cols; + bool removed = false; + int bms_idx = ybcAttnumBmsIndex(result, phys_natts); + + while ((bms_idx = bms_next_member(result->bms, bms_idx)) >= 0) + { + bms_del_member(result->bms, bms_idx); + removed = true; + } + if (removed) + ybcAttnumBmsAdd(result, YBIdxBaseTupleIdAttributeNumber); + } +} + +/* + * Returns list of target columns required by scan plan. + */ +static YbAttnumBmsState +ybcBuildRequiredAttrs(YbScanDesc yb_scan, YbScanPlan scan_plan, + Scan *pg_scan_plan) +{ + const YbcPgPrepareParameters *params = &yb_scan->prepare_params; + const bool is_index_only_scan = params->index_only_scan; + bool all_attrs_required = !params->fetch_ybctids_only; + Relation index = yb_scan->index; + + Assert(!is_index_only_scan || index); + + YbAttnumBmsState result = ybcAttnumBmsConstruct(); + + if (params->fetch_ybctids_only) + { + Assert(index); + ybcAttnumBmsAdd(&result, + index->rd_index->indisprimary ? + YBTupleIdAttributeNumber : + YBIdxBaseTupleIdAttributeNumber); + return result; + } + + /* Catalog requests do not have a pg_scan_plan and require ybctid */ + if (!pg_scan_plan) + ybcAttnumBmsAdd(&result, YBTupleIdAttributeNumber); + else + { + Index target_relid = (is_index_only_scan ? + INDEX_VAR : + pg_scan_plan->scanrelid); + + /* Collect target attributes */ + ybcPullVarattnosIntoAttnumBms(pg_scan_plan->plan.targetlist, + target_relid, &result); + + /* Collect local table filter attributes */ + ybcPullVarattnosIntoAttnumBms(pg_scan_plan->plan.qual, target_relid, + &result); + + /* + * Collect local YB/PG recheck attributes + * + * TODO(jason): only do this if YB/PG recheck is needed. + */ + if (IsA(pg_scan_plan, YbBitmapTableScan)) + YbAddBitmapScanRecheckColumns((YbBitmapTableScan *) pg_scan_plan, + target_relid, + &result); + else if (IsA(pg_scan_plan, IndexOnlyScan)) + ybcPullVarattnosIntoAttnumBms(((IndexOnlyScan *) pg_scan_plan)->recheckqual, + target_relid, + &result); + else if (IsA(pg_scan_plan, IndexScan)) + ybcPullVarattnosIntoAttnumBms(((IndexScan *) pg_scan_plan)->indexqualorig, + target_relid, + &result); + + /* TableOidAttrNumber is a virtual column, do not send it */ + if (ybcAttnumBmsDelIfExists(&result, TableOidAttributeNumber)) + { + /* + * TODO(#18870): A HeapTuple is required to store + * TableOidAttrNumber. Force its creation by including ybctid. + */ + ybcAttnumBmsAdd(&result, YBTupleIdAttributeNumber); + } + + /* + * TODO(#16717): Such placeholder target can be removed once the pg_dml + * fetcher can recognize empty rows in a response with no explict + * targets. + * + * TODO(#18870): ybctid can be large. Can we use a smaller placeholder + * than this? (e.g. NULL) + */ + if (ybcAttnumBmsIsEmpty(&result)) + ybcAttnumBmsAdd(&result, YBTupleIdAttributeNumber); + + /* + * Postgres uses InvalidAttrNumber as a marker that all columns are + * required. It must not be set as a target. + */ + all_attrs_required = ybcAttnumBmsDelIfExists(&result, + InvalidAttrNumber); + } + + if (all_attrs_required) + { + TupleDesc target_desc = yb_scan->target_desc; + + if (is_index_only_scan && index->rd_index->indisprimary) + { + /* + * Special case: For Primary-Key-ONLY-Scan, we select ONLY the + * primary key from the target table instead of the whole target + * table. + */ + for (int i = 0; i < index->rd_index->indnatts; ++i) + ybcAddNonDroppedAttr(target_desc, + index->rd_index->indkey.values[i], + &result); + } + else + for (AttrNumber attnum = 1; attnum <= target_desc->natts; ++attnum) + ybcAddNonDroppedAttr(target_desc, attnum, &result); + } + + /* + * For index-only scans with decoded primary key columns, do not request + * their values from DocDB. Instead, request ybidxbasectid for decoding. + */ + if (pg_scan_plan && IsA(pg_scan_plan, IndexOnlyScan)) + ybcReplacePkReqWithBasectid((IndexOnlyScan *) pg_scan_plan, &result); + + return result; +} + +static void +ybcSetupTargets(YbScanDesc yb_scan, YbScanPlan scan_plan, Scan *pg_scan_plan) +{ + YbAttnumBmsState required_attrs = ybcBuildRequiredAttrs(yb_scan, + scan_plan, + pg_scan_plan); + + Assert(!ybcAttnumBmsIsEmpty(&required_attrs)); + int idx = -1; + + while ((idx = bms_next_member(required_attrs.bms, idx)) >= 0) + { + const AttrNumber attnum = ybcAttnumBmsAttnum(&required_attrs, idx); + + Assert(attnum != InvalidAttrNumber); + if (attnum > 0) + YbDmlAppendTargetRegular(yb_scan->target_desc, attnum, + yb_scan->handle); + else + YbDmlAppendTargetSystem(attnum, yb_scan->handle); + } + ybcAttnumBmsDestroy(&required_attrs); +} + +/* + * Set aggregate targets into handle. If index is not null, convert column + * attribute numbers from table-based numbers to index-based ones. + */ +void +YbDmlAppendTargetsAggregate(List *aggrefs, Scan *outer_plan, + TupleDesc tupdesc, Relation index, + bool xs_want_itup, YbcPgStatement handle) +{ + ListCell *lc; + + /* Set aggregate scan targets. */ + foreach(lc, aggrefs) + { + Aggref *aggref = lfirst_node(Aggref, lc); + char *func_name = get_func_name(aggref->aggfnoid); + ListCell *lc_arg; + YbcPgExpr op_handle; + const YbcPgTypeEntity *type_entity; + + /* Get type entity for the operator from the aggref. */ + type_entity = YbDataTypeFromOidMod(InvalidAttrNumber, + aggref->aggtranstype); + + /* Create operator. */ + HandleYBStatus(YBCPgNewOperator(handle, func_name, type_entity, + aggref->aggcollid, &op_handle)); + + /* Handle arguments. */ + if (aggref->aggstar) + { + /* + * Add dummy argument for COUNT(*) case, turning it into COUNT(0). + * We don't use a column reference as we want to count rows + * even if all column values are NULL. + */ + YbcPgExpr const_handle; + + HandleYBStatus(YBCPgNewConstant(handle, + type_entity, + false /* collate_is_valid_non_c */ , + NULL /* collation_sortkey */ , + 0 /* datum */ , + false /* is_null */ , + &const_handle)); + HandleYBStatus(YBCPgOperatorAppendArg(op_handle, const_handle)); + } + else + { + /* Add aggregate arguments to operator. */ + foreach(lc_arg, aggref->args) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc_arg); + + if (IsA(tle->expr, Const)) + { + Const *const_node = castNode(Const, tle->expr); + + /* Already checked by yb_agg_pushdown_supported */ + Assert(const_node->constisnull || const_node->constbyval); + + YbcPgExpr const_handle; + + HandleYBStatus(YBCPgNewConstant(handle, + type_entity, + false /* collate_is_valid_non_c */ , + NULL /* collation_sortkey */ , + const_node->constvalue, + const_node->constisnull, + &const_handle)); + HandleYBStatus(YBCPgOperatorAppendArg(op_handle, + const_handle)); + } + else if (IsA(tle->expr, Var)) + { + Var *var = castNode(Var, tle->expr); + int attno = var->varattno; + + /* + * Change column reference in an aggregate to attribute + * number. Given limited number of cases we support, we + * take a number of assumptions here: the outer plan is a + * plain Scan, and the scan's target list contains only + * simple Vars. + * Support for more generic plan shapes would require + * deep rework of Postgres/PgGate interactions. + */ + if (outer_plan) + { + List *tlist = outer_plan->plan.targetlist; + + Assert(var->varno == OUTER_VAR); + Assert(attno > 0); + Assert(attno <= list_length(tlist)); + TargetEntry *scan_tle = list_nth_node(TargetEntry, tlist, attno - 1); + + Assert(IsA(scan_tle->expr, Var)); + attno = castNode(Var, scan_tle->expr)->varattno; + } + Form_pg_attribute attr = TupleDescAttr(tupdesc, attno - 1); + YbcPgTypeAttrs type_attrs = {attr->atttypmod}; + + YbcPgExpr arg = YBCNewColumnRef(handle, + attno, + attr->atttypid, + attr->attcollation, + &type_attrs); + + HandleYBStatus(YBCPgOperatorAppendArg(op_handle, arg)); + } + else + { + /* Should never happen. */ + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("unsupported aggregate function argument type"))); + } + } + } + + /* Add aggregate operator as scan target. */ + HandleYBStatus(YBCPgDmlAppendTarget(handle, op_handle, + false /* is_for_secondary_index */ )); + } +} + +/* + * YbDmlAppendTargets + * + * Add targets to the statement. The colref list is expected to be made up of + * YbExprColrefDesc nodes. Unlike YbDmlAppendTargetRegular, it does not do any + * dropped-columns checking. + */ +void +YbDmlAppendTargets(List *colrefs, YbcPgStatement handle) +{ + ListCell *lc; + YbcPgExpr expr; + YbcPgTypeAttrs type_attrs; + YbExprColrefDesc *colref; + + foreach(lc, colrefs) + { + colref = lfirst_node(YbExprColrefDesc, lc); + type_attrs.typmod = colref->typmod; + expr = YBCNewColumnRef(handle, + colref->attno, + colref->typid, + colref->collid, + &type_attrs); + HandleYBStatus(YBCPgDmlAppendTarget(handle, expr, false /* is_for_secondary_index */ )); + } +} + +void +YbAppendPrimaryColumnRef(YbcPgStatement dml, YbcPgExpr colref) +{ + HandleYBStatus(YbPgDmlAppendColumnRef(dml, colref, + false /* is_for_secondary_index */ )); +} + +/* + * YbDmlAppendColumnRefsImpl + * + * Add the list of column references used by pushed down expressions to the + * statement. + * The colref list is expected to be the list of YbExprColrefDesc nodes. + */ +static void +YbAppendColumnRefsImpl(YbcPgStatement dml, List *colrefs, + bool is_for_secondary_index) +{ + ListCell *lc; + + foreach(lc, colrefs) + { + YbExprColrefDesc *param = lfirst_node(YbExprColrefDesc, lc); + YbcPgTypeAttrs type_attrs = {param->typmod}; + + HandleYBStatus(YbPgDmlAppendColumnRef(dml, + YBCNewColumnRef(dml, + param->attno, + param->typid, + param->collid, + &type_attrs), + is_for_secondary_index)); + } +} + +void +YbAppendPrimaryColumnRefs(YbcPgStatement dml, List *colrefs) +{ + YbAppendColumnRefsImpl(dml, colrefs, false /* is_for_secondary_index */ ); +} + +static void +YbApplyPushdownImpl(YbcPgStatement dml, const YbPushdownExprs *pushdown, + bool is_for_secondary_index) +{ + if (!pushdown) + return; + + YbAppendColumnRefsImpl(dml, pushdown->colrefs, is_for_secondary_index); + const uint32_t serialization_version = yb_major_version_upgrade_compatibility > 0 + ? yb_major_version_upgrade_compatibility : YbgGetPgVersion(); + + ListCell *lc; + + foreach(lc, pushdown->quals) + { + Expr *expr = lfirst(lc); + + HandleYBStatus(YbPgDmlAppendQual(dml, YBCNewEvalExprCall(dml, expr), serialization_version, + is_for_secondary_index)); + } +} + +void +YbApplyPrimaryPushdown(YbcPgStatement dml, const YbPushdownExprs *pushdown) +{ + YbApplyPushdownImpl(dml, pushdown, false /* is_for_secondary_index */ ); +} + +void +YbApplySecondaryIndexPushdown(YbcPgStatement dml, const YbPushdownExprs *pushdown) +{ + YbApplyPushdownImpl(dml, pushdown, true /* is_for_secondary_index */ ); +} + +/* + * Allows to call ApplySortComparator from the PgGate, which does not know + * Datum, SortSupport data types. + */ +static inline int +yb_sort_comparator_adapter(uint64_t datum1, bool isnull1, + uint64_t datum2, bool isnull2, void *state) +{ + return ApplySortComparator((Datum) datum1, isnull1, + (Datum) datum2, isnull2, (SortSupport) state); +} + +/* + * YbAddSortTarget - add specified attribute to the secondary index scan as a target + * + * Typically the only target on the secondary index scan is the base table ybctid. + * However, if the secondary index scan performs merge sort of multiple streams, + * it also needs to fetch and parse values of the sort keys, hence they are added + * as the targets. + */ +static void +YbAddSortTarget(YbcPgStatement stmt, TupleDesc tupdesc, AttrNumber attno) +{ + Form_pg_attribute attr = TupleDescAttr(tupdesc, attno - 1); + YbcPgTypeAttrs type_attrs = {attr->atttypmod}; + Oid attcollation = YBEncodingCollation(stmt, attno, + ybc_get_attcollation(tupdesc, attno)); + YbcPgExpr colref = YBCNewColumnRef(stmt, attno, attr->atttypid, attcollation, &type_attrs); + + HandleYBStatus(YBCPgDmlAppendTarget(stmt, colref, true /* is_for_secondary_index */ )); +} + +/* + * YbApplyMergeSortKeys - set up planned merge sort in PgGate + * + * Apply merge sort info to the scan. merge stream conditions are expected to be applied separately. + * Merge sort assumes ordered data, so it is applicable to Index and IndexOnly scan with defined + * scan order. + */ +static void +YbApplyMergeSortKeys(YbScanDesc ybScan, Scan *pg_scan_plan) +{ + YbSortInfo *sort_info = NULL; + bool reverse = false; + bool yb_add_sort_targets = false; + int16 *indkey_values = NULL; + + if (IsA(pg_scan_plan, IndexScan)) + { + IndexScan *plan = (IndexScan *) pg_scan_plan; + + if (plan->yb_merge_scan_info) + { + sort_info = plan->yb_merge_scan_info->sort_cols; + Assert(!ScanDirectionIsNoMovement(plan->indexorderdir)); + reverse = ScanDirectionIsBackward(plan->indexorderdir); + /* + * Key columns of a primary or embedded index may have different attribute numbers than + * the respective columns of the base table. So we need to provide their positions in + * the DocDB tuple. On the other hand, when we make separate request to the secondary + * index, we need to set up key column data retrieval, in addition to the ybctid. + */ + if (ybScan->index->rd_index->indisprimary || ybScan->prepare_params.embedded_idx) + { + indkey_values = ybScan->index->rd_index->indkey.values; + } + else + { + yb_add_sort_targets = true; + } + } + } + else if (IsA(pg_scan_plan, IndexOnlyScan)) + { + IndexOnlyScan *plan = (IndexOnlyScan *) pg_scan_plan; + + if (plan->yb_merge_scan_info) + { + sort_info = plan->yb_merge_scan_info->sort_cols; + Assert(!ScanDirectionIsNoMovement(plan->indexorderdir)); + reverse = ScanDirectionIsBackward(plan->indexorderdir); + } + } + if (!sort_info) + return; + + /* Create and apply sort keys */ + YbcPgStatement stmt = ybScan->handle; + YbcSortKey *yb_sort_keys = (YbcSortKey *) palloc(sort_info->numCols * sizeof(YbcSortKey)); + + for (int i = 0; i < sort_info->numCols; ++i) + { + YbcSortKey *key = &yb_sort_keys[i]; + + key->att_idx = sort_info->sortColIdx[i] - 1; + key->value_idx = indkey_values ? indkey_values[key->att_idx] - 1 : key->att_idx; + key->comparator = yb_sort_comparator_adapter; + key->sortstate = palloc0(sizeof(SortSupportData)); + SortSupport sort_support = (SortSupport) key->sortstate; + + sort_support->ssup_cxt = CurrentMemoryContext; + sort_support->ssup_collation = sort_info->collations[i]; + sort_support->ssup_nulls_first = sort_info->nullsFirst[i]; + sort_support->ssup_reverse = reverse; + sort_support->abbreviate = false; + PrepareSortSupportFromOrderingOp(sort_info->sortOperators[i], sort_support); + if (yb_add_sort_targets) + YbAddSortTarget(stmt, RelationGetDescr(ybScan->index), sort_info->sortColIdx[i]); + } + HandleYBStatus(YBCPgDmlSetMergeSortKeys(stmt, sort_info->numCols, yb_sort_keys)); +} + +/* + * Before beginning execution, determine whether any kind of recheck is needed: + * - YB recheck + * - PG recheck + * There is only one condition to avoid both of those: needs_recheck. Use as + * little resources as possible to make this determination. This is largely a + * dup of YbBeginScan minus the unessential parts. + * TODO(jason): there may be room for further cleanup/optimization. + */ +bool +YbPredetermineNeedsRecheck(Scan *scan, + Relation relation, + Relation index, + bool xs_want_itup, + ScanKey keys, + int nkeys) +{ + YbScanDescData ybscan = {0}; + + ybExtractScanKeys(keys, nkeys, &ybscan); + + if (YbIsUnsatisfiableCondition(ybscan.nkeys, ybscan.keys)) + return false; + + ybscan.table = relation; + ybscan.index = index; + + /* Set up the scan plan */ + YbScanPlanData scan_plan; + + ybcSetupScanPlan(xs_want_itup, &ybscan, &scan_plan); + ybcSetupScanKeys(&ybscan, &scan_plan); + + /* Determine needs_recheck. */ + (void) ybBindScanKeys(&ybscan, &scan_plan, scan, + true); /* is_for_precheck */ + + bms_free(scan_plan.hash_key_cols); + bms_free(scan_plan.key_cols); + bms_free(scan_plan.qualified_scan_key_cols); + return ybscan.needs_recheck; +} + +/* + * Begin a scan for + * SELECT FROM USING WHERE + * NOTES: + * - "table" is the table (not index). Must always be specified. + * - "index" is the index, if applicable. NULL otherwise. + * - "nkeys" and "keys" identify which key columns are provided in the SELECT + * WHERE clause. Can be 0/NULL. + * - nkeys = Number of keys. + * - keys[].sk_attno = the column's attribute number with respect to + * - "table" if sequential scan + * - "index" if index (only) scan + * Easy way to tell between the two cases is whether index is NULL. + * Note: ybc_systable_beginscan can call for either case. + * - If "xs_want_itup" is true, Postgres layer is expecting an IndexTuple. + * - "rel_pushdown" defines expressions to push down to the targeted relation. + * - sequential scan: table. + * - index scan: table (not index). + * - index only scan: index. + * - "idx_pushdown" defines expressions to push down to the index in case of an + * index scan. + */ +YbScanDesc +YbBeginScan(Relation table, + Relation index, + bool xs_want_itup, + int nkeys, + ScanKey keys, + Scan *pg_scan_plan, + YbPushdownExprs *rel_pushdown, + YbPushdownExprs *idx_pushdown, + List *aggrefs, + int distinct_prefixlen, + YbcPgExecParameters *exec_params, + bool is_internal_scan, + bool fetch_ybctids_only) +{ + /* Set up Yugabyte scan description */ + YbScanDesc ybScan = (YbScanDesc) palloc0(sizeof(YbScanDescData)); + + /* Flatten keys and store the results in ybScan. */ + ybExtractScanKeys(keys, nkeys, ybScan); + + if (YbIsUnsatisfiableCondition(ybScan->nkeys, ybScan->keys)) + { + ybScan->quit_scan = true; + return ybScan; + } + ybScan->exec_params = exec_params; + ybScan->table = table; + ybScan->index = index; + ybScan->quit_scan = false; + ybScan->prepare_params.fetch_ybctids_only = fetch_ybctids_only; + + /* Set up the scan plan */ + YbScanPlanData scan_plan; + + ybcSetupScanPlan(xs_want_itup, ybScan, &scan_plan); + ybcSetupScanKeys(ybScan, &scan_plan); + + ybScan->handle = YbNewSelect(table, &ybScan->prepare_params); + + /* Set up binds */ + if (!ybBindScanKeys(ybScan, &scan_plan, pg_scan_plan, + false /* is_for_precheck */ )) + { + ybScan->quit_scan = true; + bms_free(scan_plan.hash_key_cols); + bms_free(scan_plan.key_cols); + bms_free(scan_plan.qualified_scan_key_cols); + return ybScan; + } + + /* + * Set up targets. There are two separate cases: + * - aggregate pushdown + * - not aggregate pushdown + * This ought to be reworked once aggregate pushdown supports a mix of + * non-aggregate and aggregate targets. + */ + if (aggrefs != NIL) + YbDmlAppendTargetsAggregate(aggrefs, pg_scan_plan, ybScan->target_desc, + index, xs_want_itup, ybScan->handle); + else + ybcSetupTargets(ybScan, &scan_plan, pg_scan_plan); + + YbApplyPrimaryPushdown(ybScan->handle, rel_pushdown); + YbApplySecondaryIndexPushdown(ybScan->handle, idx_pushdown); + + /* + * Set the current syscatalog version (will check that we are up to + * date). Avoid it for internal syscatalog requests because that is the way + * it has been since the early days of YSQL. For tighter correctness, it + * should be sent for syscatalog requests, but this will result in more + * cases of catalog version mismatch. + * TODO(jason): revisit this for #15080. + * + * Initdb and walsender don't have local catalog version, so ignore for + * those cases as well. + */ + if (!(is_internal_scan && IsSystemRelation(table)) && + !IsBootstrapProcessingMode() && + MyBackendType != B_WAL_SENDER && + MyBackendType != YB_YSQL_CONN_MGR_WAL_SENDER) + YbSetCatalogCacheVersion(ybScan->handle, + YbGetCatalogCacheVersion()); + + /* Set distinct prefix length. */ + if (distinct_prefixlen > 0) + YBCPgSetDistinctPrefixLength(ybScan->handle, distinct_prefixlen); + + if (pg_scan_plan) + YbApplyMergeSortKeys(ybScan, pg_scan_plan); + + bms_free(scan_plan.hash_key_cols); + bms_free(scan_plan.key_cols); + bms_free(scan_plan.qualified_scan_key_cols); + return ybScan; +} + +/* + * There are two levels of recheck: + * - YB recheck + * - PG recheck + * If YB recheck can make perfect yes/no decisions, then PG recheck is not + * needed. + * + * Since the logic for determining whether YB recheck can make perfect + * decisions and for actually performing the YB recheck are tightly coupled, + * this function shares the code for both modes: + * - For determining whether PG recheck may be needed: tup is NULL, and + * return... + * - false: tuples definitely will not need PG recheck + * - true: tuples might perform PG recheck + * - For actually executing YB recheck: tup is not NULL, and return... + * - false: in case PG recheck is... + * - needed: tup needs PG recheck + * - not needed: tup is a valid match + * - true: tup definitely mismatches + */ +static bool +ybRecheck(HeapTuple tup, YbScanDesc ybScan) +{ + ScanKey *keys = ybScan->keys; + AttrNumber *sk_attno = ybScan->target_key_attnums; + bool is_determining_pg_recheck_mode = !tup; + + /* + * Neither YB recheck nor PG recheck is needed if all scan keys are bound. + * The caller is expected to avoid calling this function in that case. + */ + Assert(ybScan->needs_recheck); + + /* + * Index Only Scan never goes through YB recheck, so it makes no sense to + * call this function in that case. + */ + Assert(!ybScan->prepare_params.index_only_scan); + + for (int i = 0; i < ybScan->nkeys; i += YbGetLengthOfKey(&keys[i])) + { + /* TODO: support expressions */ + if (sk_attno[i] == InvalidAttrNumber) + { + if (is_determining_pg_recheck_mode) + return true; + continue; + } + + /* + * res_datum is ill-defined when length of key is not 1, but such cases + * would continue/return before we get to read res_datum. + * + * Both res_datum and is_null are ill-defined when + * is_determining_pg_recheck_mode, but we don't read those values in + * that case. + */ + ScanKey key = keys[i]; + bool is_null = false; + Datum res_datum = (tup ? + heap_getattr(tup, sk_attno[i], + ybScan->target_desc, &is_null) : + PointerGetDatum(NULL)); + + if (key->sk_flags & SK_SEARCHNULL) + { + if (is_determining_pg_recheck_mode || is_null) + continue; + else + return true; + } + + if (key->sk_flags & SK_SEARCHNOTNULL) + { + if (is_determining_pg_recheck_mode || !is_null) + continue; + else + return true; + } + + if (key->sk_flags == 0) + { + bool matches; + + if (is_determining_pg_recheck_mode) + continue; + if (is_null) + return true; + + matches = DatumGetBool(FunctionCall2Coll(&key->sk_func, + key->sk_collation, + res_datum, + key->sk_argument)); + + if (!matches) + return true; + } + + /* TODO: support the different search options like SK_SEARCHARRAY. */ + if (is_determining_pg_recheck_mode) + return true; + } + + return false; +} + +/* + * Whether rows returned by DocDB may need to go through PG recheck. This + * function is ready to be called after calling ybBindScanKeys, which sets some + * variables that are read here. There is an implicit assumption that this + * returns false for heap/system scans. + */ +inline bool +YbNeedsPgRecheck(YbScanDesc yb_scan) +{ + if (!yb_scan->needs_recheck) + return false; + + /* + * Index Only Scan does not go through YB recheck like the other scans. So + * needs_recheck means we need PG recheck. + */ + if (yb_scan->prepare_params.index_only_scan) + return true; + + /* + * If YB recheck cannot always make a clear decision, we need to fall back + * to PG recheck. + */ + return ybRecheck(NULL /* tup */ , yb_scan); +} + +HeapTuple +ybc_getnext_heaptuple(YbScanDesc ybScan, ScanDirection dir) +{ + HeapTuple tup = NULL; + + if (ybScan->quit_scan) + return NULL; + + /* Loop over rows from pggate. */ + while (HeapTupleIsValid(tup = ybcFetchNextHeapTuple(ybScan, dir))) + { + if (!ybScan->needs_recheck) + return tup; + + /* + * Do a YB recheck first before deferring to a PG recheck if needed. + * In case of heap/system scans, this is the main check, and it is + * unexpected to need a PG recheck as there is no such code to do that. + */ + if (ybRecheck(tup, ybScan)) + { + YBCPgIncrementIndexRecheckCount(); + heap_freetuple(tup); + continue; + } + break; + } + return tup; +} + +IndexTuple +ybc_getnext_indextuple(YbScanDesc ybScan, ScanDirection dir) +{ + if (ybScan->quit_scan) + return NULL; + return ybcFetchNextIndexTuple(ybScan, dir); +} + +bool +ybc_getnext_aggslot(IndexScanDesc scan, YbcPgStatement handle, + bool index_only_scan) +{ + Assert(scan->yb_agg_slot); + + /* + * As of 2023-08-10, the relid passed into ybFetchNext is not going to + * be used as it is only used when there are system targets, not + * counting the internal ybbasectid lookup to the index. + * YbDmlAppendTargetsAggregate only adds that ybbasectid plus operator + * targets. + * TODO(jason): this may need to be revisited when supporting GROUP BY + * aggregate pushdown where system columns are directly targeted. + */ + ybFetchNext(handle, scan->yb_agg_slot, InvalidOid /* relid */ ); + /* For IndexScan, hack to make index_getnext think there are tuples. */ + if (!index_only_scan) + scan->xs_hitup = (HeapTuple) 1; + return !TTS_EMPTY(scan->yb_agg_slot); +} + +void +ybc_free_ybscan(YbScanDesc ybscan) +{ + /* + * YB Bitmap Table Scans instantiate the biss_ScanDesc of their children + * Bitmap Index Scans, even if it will not be executed. Other nodes + * (like Index Scan) have their ScanDesc set when ExecInitIndexScan is + * called. + * + * If the index scan is never executed, then we never even reach this point. + * + * If the bitmap scan is never executed, it still has a valid biss_ScanDesc, + * even though it's YbScanDesc was not set. We need to cleanup after the + * biss_ScanDesc but not the YbScanDesc. + */ + if (PointerIsValid(ybscan)) + { + YBCPgDeleteStatement(ybscan->handle); + pfree(ybscan); + } +} + +static SysScanDesc +YbBuildSysScanDesc(Relation relation, Snapshot snapshot, YbSysScanBase ybscan) +{ + SysScanDesc scan_desc = palloc0(sizeof(SysScanDescData)); + + scan_desc->heap_rel = relation; + scan_desc->snapshot = snapshot; + scan_desc->ybscan = ybscan; + return scan_desc; +} + +static SysScanDesc +YbBuildOptimizedSysTableScan(Relation relation, + Oid indexId, + bool indexOK, + Snapshot snapshot, + int nkeys, + ScanKey key) +{ + if (relation->rd_id == InheritsRelationId) + return YbBuildSysScanDesc(relation, + snapshot, + yb_pg_inherits_beginscan(relation, key, + nkeys, indexId)); + return NULL; +} + +SysScanDesc +ybc_systable_beginscan(Relation relation, + Oid indexId, + bool indexOK, + Snapshot snapshot, + int nkeys, + ScanKey key) +{ + SysScanDesc scan = (IsBootstrapProcessingMode() ? + NULL : + YbBuildOptimizedSysTableScan(relation, indexId, + indexOK, snapshot, nkeys, + key)); + + return (scan ? + scan : + ybc_systable_begin_default_scan(relation, indexId, indexOK, + snapshot, nkeys, key)); +} + +static HeapTuple +ybc_systable_getnext(YbSysScanBase default_scan) +{ + YbDefaultSysScan scan = (void *) default_scan; + + Assert(PointerIsValid(scan->ybscan)); + + HeapTuple tuple = ybc_getnext_heaptuple(scan->ybscan, + true); /* is_forward_scan */ + + return tuple; +} + +static void +ybc_systable_endscan(YbSysScanBaseData *default_scan) +{ + YbDefaultSysScan scan = (void *) default_scan; + + ybc_free_ybscan(scan->ybscan); + pfree(scan); +} + +static YbSysScanVirtualTable yb_default_scan = { + .next = &ybc_systable_getnext, + .end = &ybc_systable_endscan +}; + +SysScanDesc +ybc_systable_begin_default_scan(Relation relation, + Oid indexId, + bool indexOK, + Snapshot snapshot, + int nkeys, + ScanKey keys) +{ + Relation index = NULL; + + /* + * Look up the index to scan with if we can. If the index is the primary key which is part + * of the table in YugaByte, we should scan the table directly. + */ + if (indexOK && !IgnoreSystemIndexes && !ReindexIsProcessingIndex(indexId)) + { + index = RelationIdGetRelation(indexId); + if (index->rd_index->indisprimary) + { + RelationClose(index); + index = NULL; + } + + if (index) + { + /* + * Change attribute numbers to be index column numbers. + * - This conversion is the same as function systable_beginscan() in file "genam.c". If we + * ever reuse Postgres index code, this conversion is a must because the key entries must + * match what Postgres code expects. + * + * - When selecting using INDEX, the key values are bound to the IndexTable, so index attnum + * must be used for bindings. + */ + for (int i = 0; i < nkeys; ++i) + keys[i].sk_attno = YbGetIndexAttnum(index, keys[i].sk_attno); + } + } + + YbDefaultSysScan scan = palloc0(sizeof(YbDefaultSysScanData)); + + /* + * In case of an index scan here, it would do a full scan and use local + * filtering. + */ + scan->ybscan = YbBeginScan(relation, + index, + false, /* xs_want_itup */ + nkeys, + keys, + NULL, /* pg_scan_plan */ + NULL, /* rel_pushdown */ + NULL, /* idx_pushdown */ + NULL, /* aggrefs */ + 0, /* distinct_prefixlen */ + NULL, /* exec_params */ + true, /* is_internal_scan */ + false); /* fetch_ybctids_only */ + Assert(!YbNeedsPgRecheck(scan->ybscan)); + + scan->base.vtable = &yb_default_scan; + + if (index) + RelationClose(index); + + return YbBuildSysScanDesc(relation, snapshot, &scan->base); +} + +TableScanDesc +ybc_heap_beginscan(Relation relation, + Snapshot snapshot, + int nkeys, + ScanKey keys, + uint32 flags) +{ + TableScanDesc tsdesc = palloc(sizeof(TableScanDescData)); + + tsdesc->rs_rd = relation; + tsdesc->rs_snapshot = snapshot; + tsdesc->rs_nkeys = nkeys; + tsdesc->rs_key = keys; + tsdesc->rs_flags = flags; + tsdesc->rs_parallel = NULL; + tsdesc->ybscan = YbBeginScan(tsdesc->rs_rd, + NULL, /* index */ + false, /* xs_want_itup */ + tsdesc->rs_nkeys, + tsdesc->rs_key, + NULL, /* pg_scan_plan */ + NULL, /* rel_pushdown */ + NULL, /* idx_pushdown */ + NULL, /* aggrefs */ + 0, /* distinct_prefixlen */ + NULL, /* exec_params */ + false, /* is_internal_scan */ + false); /* fetch_ybctids_only */ + + Assert(!YbNeedsPgRecheck(tsdesc->ybscan)); + + return tsdesc; +} + +HeapTuple +ybc_heap_getnext(TableScanDesc tsdesc) +{ + HeapTuple tuple; + + Assert(PointerIsValid(tsdesc->ybscan)); + tuple = ybc_getnext_heaptuple(tsdesc->ybscan, true /* is_forward_scan */ ); + + return tuple; +} + +void +ybc_heap_endscan(TableScanDesc tsdesc) +{ + if (tsdesc->rs_flags & SO_TEMP_SNAPSHOT) + UnregisterSnapshot(tsdesc->rs_snapshot); + ybc_free_ybscan(tsdesc->ybscan); +} + +/* + * Build a Scan node with a targetlist containing the columns required for + * index backfill. This creates Var nodes for each column needed by the index, + * allowing YbBeginScan to use its standard column projection logic. + */ +static Scan * +ybcBuildScanPlanForIndexBuild(Relation relation, IndexInfo *indexInfo) +{ + Scan *scan_plan; + TupleDesc tupdesc = RelationGetDescr(relation); + List *targetlist = NIL; + Var *ybctid_var; + TargetEntry *ybctid_tle; + int i; + int idx; + int resno = 1; + /* + * Use varno=1 since this is always scanning the base relation. + * Concurrent index creation in postgres is restricted to one index + * per table/statement (unlike the non-concurrent index creation process). + * As a result, we're guaranteed that only one table is involved in the process, + * and said table is opened for inspection first, leading to it being varno=1. + */ + const Index varno = 1; + + /* This function is only for Yugabyte relations */ + Assert(IsYBRelation(relation)); + + /* + * Use YbAttnumBmsState for bitmapset operations. This handles the + * min_attr offset correctly for Yugabyte relations which may have + * different system columns than standard Postgres relations. + * Initialize at declaration since min_attr is const. + */ + YbAttnumBmsState required_attrs = ybcAttnumBmsConstruct(); + + /* + * Always need ybctid for index entry construction. Add it directly to + * targetlist since it's the only system column allowed in indexes. + */ + ybctid_var = makeVar(varno, + YBTupleIdAttributeNumber, + BYTEAOID, + -1, /* typmod */ + InvalidOid, /* collation */ + 0); /* varlevelsup */ + ybctid_tle = makeTargetEntry((Expr *) ybctid_var, + resno++, + NULL, /* resname */ + false); /* resjunk */ + targetlist = lappend(targetlist, ybctid_tle); + + /* Add columns directly referenced in the index */ + for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) + { + AttrNumber attnum = indexInfo->ii_IndexAttrNumbers[i]; + + /* + * attnum == 0 means this is an expression index column, + * which will be handled by extracting vars from ii_Expressions. + */ + if (attnum > 0) + ybcAttnumBmsAdd(&required_attrs, attnum); + } + + /* + * Add columns referenced in index expressions. + * Use ybcPullVarattnosIntoAttnumBms which handles the min_attr offset. + */ + ybcPullVarattnosIntoAttnumBms(indexInfo->ii_Expressions, varno, + &required_attrs); + + /* + * Add columns referenced in partial index predicate. + */ + ybcPullVarattnosIntoAttnumBms(indexInfo->ii_Predicate, varno, + &required_attrs); + + /* + * Build targetlist with Var nodes for each required column. + */ + idx = -1; + while ((idx = bms_next_member(required_attrs.bms, idx)) >= 0) + { + AttrNumber attnum = ybcAttnumBmsAttnum(&required_attrs, idx); + Var *var; + TargetEntry *tle; + Form_pg_attribute attr; + + /* + * Only non-system columns should be in required_attrs. ybctid is + * handled separately above, and other system columns cannot be indexed. + */ + Assert(attnum > 0); + + /* + * Verify the column exists and is not dropped. The parser should have + * already rejected any attempt to create an index on a dropped or + * non-existent column, so this is just a sanity check. + */ + Assert(attnum <= tupdesc->natts); + attr = TupleDescAttr(tupdesc, attnum - 1); + Assert(!attr->attisdropped); + + var = makeVar(varno, + attnum, + attr->atttypid, + attr->atttypmod, + attr->attcollation, + 0); /* varlevelsup */ + + tle = makeTargetEntry((Expr *) var, + resno++, + NULL, /* resname */ + false); /* resjunk */ + targetlist = lappend(targetlist, tle); + } + + ybcAttnumBmsDestroy(&required_attrs); + + /* Create the Scan node */ + scan_plan = makeNode(Scan); + scan_plan->scanrelid = varno; + scan_plan->plan.targetlist = targetlist; + scan_plan->plan.qual = NIL; /* No quals for backfill scan */ + + return scan_plan; +} + +/* + * ybc_heap_beginscan_for_index_build + * Begin a heap scan specifically for index build/backfill operations. + * + * Unlike the regular heap scan which fetches all columns, this function + * only requests the columns needed for the index: + * - Columns referenced in ii_IndexAttrNumbers (index key and non-key columns) + * - Columns referenced in ii_Expressions (expression index columns) + * - Columns referenced in ii_Predicate (partial index predicate) + * - ybctid (always needed for index entry construction) + * + * This optimization significantly reduces the amount of data read from + * DocDB during index backfill, especially for tables especially for tables + * with many columns where a given index references only a few. + */ +TableScanDesc +ybc_heap_beginscan_for_index_build(Relation relation, + Snapshot snapshot, + IndexInfo *indexInfo) +{ + TableScanDesc tsdesc = palloc(sizeof(TableScanDescData)); + Scan *pg_scan_plan; + + pg_scan_plan = ybcBuildScanPlanForIndexBuild(relation, indexInfo); + + tsdesc->rs_rd = relation; + tsdesc->rs_snapshot = snapshot; + tsdesc->rs_nkeys = 0; + tsdesc->rs_key = NULL; + tsdesc->rs_flags = SO_TYPE_SEQSCAN; + tsdesc->rs_parallel = NULL; + tsdesc->ybscan = YbBeginScan(tsdesc->rs_rd, + NULL, /* index */ + false, /* xs_want_itup */ + tsdesc->rs_nkeys, + tsdesc->rs_key, + pg_scan_plan, + NULL, /* rel_pushdown */ + NULL, /* idx_pushdown */ + NIL, /* aggrefs */ + 0, /* distinct_prefixlen */ + NULL, /* exec_params */ + false, /* is_internal_scan */ + false); /* fetch_ybctids_only */ + + return tsdesc; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* + * ybcGetForeignRelSize + * Obtain relation size estimates for a foreign table + */ +void +ybcGetForeignRelSize(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid) +{ + if (baserel->tuples < 0) + baserel->tuples = YBC_DEFAULT_NUM_ROWS; + + /* Set the estimate for the total number of rows (tuples) in this table. */ + if (yb_enable_base_scans_cost_model || + yb_enable_optimizer_statistics) + { + set_baserel_size_estimates(root, baserel); + } + else + { + /* + * Initialize the estimate for the number of rows returned by this + * query. This does not yet take into account the restriction clauses, + * but it will be updated later by ybcIndexCostEstimate once it + * inspects the clauses. + */ + baserel->rows = baserel->tuples; + } + + /* + * Test any indexes of rel for applicability also. + */ + check_index_predicates(root, baserel); +} + +void +ybcCostEstimate(RelOptInfo *baserel, Selectivity selectivity, + bool is_backwards_scan, bool is_seq_scan, + bool is_uncovered_idx_scan, + Cost *startup_cost, Cost *total_cost, + Oid index_tablespace_oid) +{ + if (is_seq_scan && !enable_seqscan) + *startup_cost = disable_cost; + else + *startup_cost = (yb_enable_optimizer_statistics ? + yb_network_fetch_cost : + 0); + + /* + * Yugabyte-specific per-tuple cost considerations: + * - 10x the regular CPU cost to account for network/RPC + DocDB overhead. + * - backwards scan scale factor as it will need that many more fetches + * to get all rows/tuples. + * - uncovered index scan is more costly than index-only or seq scan because + * it requires extra request to the main table. + */ + double tsp_cost = 0.0; + bool is_valid_tsp_cost = (!is_uncovered_idx_scan && + get_yb_tablespace_cost(index_tablespace_oid, + &tsp_cost)); + Cost yb_per_tuple_cost_factor = YB_DEFAULT_PER_TUPLE_COST; + + if (is_valid_tsp_cost && yb_per_tuple_cost_factor > tsp_cost) + yb_per_tuple_cost_factor = tsp_cost; + + Assert(!is_valid_tsp_cost || tsp_cost != 0); + if (is_backwards_scan) + yb_per_tuple_cost_factor *= YBC_BACKWARDS_SCAN_COST_FACTOR; + if (is_uncovered_idx_scan) + yb_per_tuple_cost_factor *= YBC_UNCOVERED_INDEX_COST_FACTOR; + + Cost cost_per_tuple = (cpu_tuple_cost * yb_per_tuple_cost_factor + + baserel->baserestrictcost.per_tuple); + + *startup_cost += baserel->baserestrictcost.startup; + + *total_cost = (*startup_cost + cost_per_tuple * baserel->tuples * + selectivity); +} + +/* + * Evaluate the selectivity for yb_hash_code qualifiers. + * Returns 1.0 if there are no yb_hash_code comparison expressions for this + * index. + */ +static double +ybcEvalHashSelectivity(List *hashed_rinfos) +{ + bool greatest_set = false; + int greatest = 0; + + bool lowest_set = false; + int lowest = USHRT_MAX; + double selectivity; + ListCell *lc; + + foreach(lc, hashed_rinfos) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Expr *clause = rinfo->clause; + + Assert(IsA(clause, OpExpr)); + OpExpr *op = (OpExpr *) clause; + Node *other_operand = (Node *) lsecond(op->args); + + if (!IsA(other_operand, Const)) + continue; + + int strategy; + Oid lefttype; + Oid righttype; + + get_op_opfamily_properties(((OpExpr *) clause)->opno, + INTEGER_LSM_FAM_OID, + false, + &strategy, + &lefttype, + &righttype); + + int signed_val = ((Const *) other_operand)->constvalue; + + signed_val = signed_val < 0 ? 0 : signed_val; + uint32_t val = signed_val > USHRT_MAX ? USHRT_MAX : signed_val; + + /* + * The goal here is to calculate selectivity based on qualifiers. + * + * 1. yb_hash_code(hash_col) -- Single Key selectivity + * 2. yb_hash_code(hash_col) >= ABC and yb_hash_code(hash_col) <= XYZ + * This specifically means that we return all the hash codes between + * ABC and XYZ. YBCEvalHashValueSelectivity takes in ABC and XYZ as + * arguments and finds the number of buckets to search to return what + * is required. If it needs to search 16 buckets out of 48 buckets + * then the selectivity is 0.33 which YBCEvalHashValueSelectivity + * returns. + */ + switch (strategy) + { + case BTLessStrategyNumber: + yb_switch_fallthrough(); + case BTLessEqualStrategyNumber: + greatest_set = true; + greatest = val > greatest ? val : greatest; + break; + case BTGreaterEqualStrategyNumber: + yb_switch_fallthrough(); + case BTGreaterStrategyNumber: + lowest_set = true; + lowest = val < lowest ? val : lowest; + break; + case BTEqualStrategyNumber: + return YBC_SINGLE_KEY_SELECTIVITY; + default: + break; + } + + if (greatest == lowest && greatest_set && lowest_set) + break; + } + + if (!greatest_set && !lowest_set) + return 1.0; + + greatest = greatest_set ? greatest : INT32_MAX; + lowest = lowest_set ? lowest : INT32_MIN; + + selectivity = YBCEvalHashValueSelectivity(lowest, greatest); +#ifdef SELECTIVITY_DEBUG + elog(DEBUG4, "yb_hash_code selectivity is %f", selectivity); +#endif + return selectivity; +} + +/* + * Evaluate the selectivity for some qualified cols given the hash and key cols. + */ +static double +ybcIndexEvalClauseSelectivity(double reltuples, + Bitmapset *qualified_scan_key_cols, + bool is_unique_idx, + Bitmapset *hash_key_cols, + Bitmapset *key_cols) +{ + /* + * If there is no search condition, or not all of the hash columns have + * search conditions, it will be a full-table scan. + */ + if (bms_is_empty(qualified_scan_key_cols) || + !bms_is_subset(hash_key_cols, qualified_scan_key_cols)) + { + return YBC_FULL_SCAN_SELECTIVITY; + } + + /* + * Otherwise, it will be either a full key lookup or range scan + * on a hash key. + */ + if (bms_is_subset(key_cols, qualified_scan_key_cols)) + { + /* For unique indexes full key guarantees single row. */ + if (is_unique_idx) + return ((reltuples == 0) ? + YBC_SINGLE_ROW_SELECTIVITY : + (double) (1.0 / reltuples)); + else + return YBC_SINGLE_KEY_SELECTIVITY; + } + + return YBC_HASH_SCAN_SELECTIVITY; +} + +Oid +ybc_get_attcollation(TupleDesc desc, AttrNumber attnum) +{ + return (attnum > 0 ? + TupleDescAttr(desc, attnum - 1)->attcollation : + InvalidOid); +} + +bool +yb_is_hashed(Expr *clause, IndexOptInfo *index) +{ + bool is_hashed = false; + Node *leftop; + + if (IsA(clause, OpExpr)) + { + leftop = get_leftop(clause); + if (IsA(leftop, FuncExpr)) + { + is_hashed = (((FuncExpr *) leftop)->funcid == F_YB_HASH_CODE); + ListCell *ls; + + if (is_hashed) + { + /* + * YB: We aren't going to push down a yb_hash_code call + * if we matched the call against an expression + */ + foreach(ls, index->indexprs) + { + Node *indexpr = (Node *) lfirst(ls); + + if (indexpr && IsA(indexpr, RelabelType)) + indexpr = (Node *) ((RelabelType *) indexpr)->arg; + if (equal(indexpr, leftop)) + { + is_hashed = false; + break; + } + } + } + } + } + return is_hashed; +} + +/* + * Compute index access portion of IndexScan/IndexOnlyScan node. + * - Table row fetch costs are added by cost_index(). + * - When yb_enable_optimizer_statistics is false, this function also updates + * baserel->rows if the current index qual is more selective than the ones + * seen so far. i.e.: the table cardinality is determined by the most + * selective index qual regardless of the access path that is eventually + * chosen. + */ +void +ybcIndexCostEstimate(struct PlannerInfo *root, IndexPath *path, + Selectivity *selectivity, Cost *startup_cost, + Cost *total_cost) +{ + IndexOptInfo *indexinfo = path->indexinfo; + bool is_primary = false; + RelOptInfo *baserel = path->path.parent; + ListCell *lc; + bool is_backwards_scan = path->indexscandir == BackwardScanDirection; + bool is_unique = indexinfo->unique; + bool is_partial_idx = (indexinfo->indpred != NIL && + indexinfo->predOK); + Bitmapset *const_quals = NULL; + List *hashed_rinfos = NIL; + List *clauses = NIL; + double baserel_rows_estimate; + + + if (!indexinfo->hypothetical) + { + /* Hypothetical index cannot be primary index */ + Relation index = RelationIdGetRelation(indexinfo->indexoid); + + is_primary = index->rd_index->indisprimary; + RelationClose(index); + } + + /* Primary-index scans are always covered in Yugabyte (internally) */ + bool is_uncovered_idx_scan = (!is_primary && + path->path.pathtype != T_IndexOnlyScan); + + YbScanPlanData scan_plan; + + memset(&scan_plan, 0, sizeof(scan_plan)); + + if (is_primary || indexinfo->hypothetical) + { + RangeTblEntry *rte = planner_rt_fetch(indexinfo->rel->relid, root); + + Assert(rte->rtekind == RTE_RELATION); + Oid baserel_oid = rte->relid; + + scan_plan.target_relation = RelationIdGetRelation(baserel_oid); + } + else + { + scan_plan.target_relation = RelationIdGetRelation(indexinfo->indexoid); + } + + for (int i = 0; i < indexinfo->nkeycolumns; i++) + { + int bms_idx; + + if (indexinfo->hypothetical) + bms_idx = YBAttnumToBmsIndexWithMinAttr(YBFirstLowInvalidAttributeNumber, + i + 1); + else + { + if (is_primary) + bms_idx = YBAttnumToBmsIndex(scan_plan.target_relation, indexinfo->indexkeys[i]); + else + bms_idx = YBAttnumToBmsIndex(scan_plan.target_relation, i + 1); + } + + if (i < indexinfo->nhashcolumns) + { + scan_plan.hash_key_cols = bms_add_member(scan_plan.hash_key_cols, bms_idx); + } + scan_plan.key_cols = bms_add_member(scan_plan.key_cols, bms_idx); + } + + /* Find out the search conditions on the key columns */ + foreach(lc, path->indexclauses) + { + IndexClause *iclause = lfirst_node(IndexClause, lc); + int indexcol = iclause->indexcol; + ListCell *lc2; + + foreach(lc2, iclause->indexquals) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc2); + AttrNumber attnum = (is_primary ? + path->indexinfo->indexkeys[indexcol] : + (indexcol + 1)); + Expr *clause = rinfo->clause; + int bms_idx = YBAttnumToBmsIndex(scan_plan.target_relation, attnum); + + if (IsA(clause, NullTest)) + { + const_quals = bms_add_member(const_quals, bms_idx); + ybcAddAttributeColumn(&scan_plan, attnum); + } + else + { + OpExpr *op = (OpExpr *) clause; + Oid clause_op = op->opno; + Node *other_operand = (Node *) lsecond(op->args); + Oid opfamily = path->indexinfo->opfamily[indexcol]; + + /* + * If specified, skip boolean index qual to avoid the row count + * estimate change, a side effect introduced by the fix for + * https://github.com/yugabyte/yugabyte-db/issues/26266 + * for backward compatibility. See the function header + * comment and around the lines updating baserel->rows, too. + */ + if (OidIsValid(clause_op) && + (!yb_ignore_bool_cond_for_legacy_estimate || + !IsBooleanOpfamily(opfamily))) + { + ybcAddAttributeColumn(&scan_plan, attnum); + if (other_operand && IsA(other_operand, Const)) + const_quals = bms_add_member(const_quals, bms_idx); + } + } + + if (yb_is_hashed(clause, path->indexinfo)) + hashed_rinfos = lappend(hashed_rinfos, rinfo); + else + clauses = lappend(clauses, rinfo); + } + } + if (hashed_rinfos != NIL) + { + *selectivity = ybcEvalHashSelectivity(hashed_rinfos); + baserel_rows_estimate = baserel->tuples * (*selectivity); + } + else + { + if (yb_enable_optimizer_statistics) + { + *selectivity = clauselist_selectivity(root /* PlannerInfo */ , + clauses, + path->indexinfo->rel->relid /* varrelid */ , + JOIN_INNER, + NULL /* SpecialJoinInfo */ ); + baserel_rows_estimate = (baserel->tuples * (*selectivity) >= 1 ? + baserel->tuples * (*selectivity) : + 1); + } + else + { + *selectivity = ybcIndexEvalClauseSelectivity(baserel->tuples, + scan_plan.qualified_scan_key_cols, + is_unique, + scan_plan.hash_key_cols, + scan_plan.key_cols); + baserel_rows_estimate = baserel->tuples * (*selectivity); + } + } + + path->path.rows = baserel_rows_estimate; + + /* + * For partial indexes, scale down the rows to account for the predicate. + * Do this after setting the baserel rows since this does not apply to base rel. + */ + if (!yb_enable_optimizer_statistics && is_partial_idx) + *selectivity *= YBC_PARTIAL_IDX_PRED_SELECTIVITY; + + ybcCostEstimate(baserel, *selectivity, is_backwards_scan, + false /* is_seq_scan */ , is_uncovered_idx_scan, + startup_cost, total_cost, + path->indexinfo->reltablespace); + + /* Merge scan should not be possible in non-CBO mode. */ + Assert(!path->yb_index_path_info.merge_scan_saop_cols); + + if (!yb_enable_optimizer_statistics) + { + /* + * Try to evaluate the number of rows this baserel might return. + * We cannot rely on the join conditions here (e.g. t1.c1 = t2.c2) because + * they may not be applied if another join path is chosen. + * So only use the t1.c1 = quals (filtered above) for this. + */ + double const_qual_selectivity = ybcIndexEvalClauseSelectivity(baserel->tuples, + const_quals, + is_unique, + scan_plan.hash_key_cols, + scan_plan.key_cols); + + baserel_rows_estimate = const_qual_selectivity * baserel->tuples; + + if (baserel_rows_estimate < baserel->rows) + baserel->rows = baserel_rows_estimate; + } + + RelationClose(scan_plan.target_relation); +} + +static bool +YbFetchRowData(YbcPgStatement ybc_stmt, Relation relation, Datum ybctid, + Datum *values, bool *nulls, YbcPgSysColumns *syscols) +{ + bool has_data = false; + TupleDesc tupdesc = RelationGetDescr(relation); + + /* Bind ybctid to identify the current row. */ + YbcPgExpr ybctid_expr = YBCNewConstant(ybc_stmt, + BYTEAOID, + InvalidOid, + ybctid, + false); + + HandleYBStatus(YBCPgDmlBindColumn(ybc_stmt, YBTupleIdAttributeNumber, + ybctid_expr)); + + /* + * Set up the scan targets. For index-based scan we need to return all "real" columns. + */ + for (AttrNumber attnum = 1; attnum <= tupdesc->natts; attnum++) + { + if (!TupleDescAttr(tupdesc, attnum - 1)->attisdropped) + YbDmlAppendTargetRegular(tupdesc, attnum, ybc_stmt); + } + YbDmlAppendTargetSystem(YBTupleIdAttributeNumber, ybc_stmt); + + /* + * Execute the select statement. + * This select statement fetch the row for a specific YBCTID, LIMIT setting is not needed. + */ + HandleYBStatus(YBCPgExecSelect(ybc_stmt, NULL /* exec_params */ )); + + /* Fetch one row. */ + HandleYBStatus(YBCPgDmlFetch(ybc_stmt, + tupdesc->natts, + (uint64_t *) values, + nulls, + syscols, + &has_data)); + + return has_data; +} + +bool +YbFetchHeapTuple(Relation relation, Datum ybctid, HeapTuple *tuple) +{ + TupleDesc tupdesc = RelationGetDescr(relation); + Datum *values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum)); + bool *nulls = (bool *) palloc(tupdesc->natts * sizeof(bool)); + YbcPgSysColumns syscols; + + /* Read data */ + YbcPgStatement ybc_stmt = YbNewSelect(relation, NULL /* prepare_params */ ); + + const bool has_data = YbFetchRowData(ybc_stmt, relation, ybctid, values, nulls, &syscols); + + /* Write into the given tuple */ + if (has_data) + { + *tuple = heap_form_tuple(tupdesc, values, nulls); + (*tuple)->t_tableOid = RelationGetRelid(relation); + if (syscols.ybctid != NULL) + HEAPTUPLE_YBCTID(*tuple) = PointerGetDatum(syscols.ybctid); + } + + + /* Free up memory and return data */ + pfree(values); + pfree(nulls); + YBCPgDeleteStatement(ybc_stmt); + return has_data; +} + +void +YBCHandleConflictError(Relation rel, LockWaitPolicy wait_policy) +{ + if (wait_policy == LockWaitError) + { + /* + * In case the user has specified NOWAIT, the intention is to error out + * immediately. If we raise ERRCODE_YB_TXN_CONFLICT, the statement might + * be retried by our retry logic in yb_attempt_to_restart_on_error(). + */ + + if (rel) + ereport(ERROR, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on row in relation \"%s\"", + RelationGetRelationName(rel)))); + else + { + /* + * It is not expected that relation is null. Raise an error wihout + * relation name in release mode. + */ + Assert(false); + ereport(ERROR, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on row"))); + } + } + + ereport(ERROR, + (errcode(ERRCODE_YB_TXN_CONFLICT), + errmsg("could not serialize access due to concurrent update"))); +} + +static bool +YBCIsExplicitRowLockConflictStatus(YbcStatus status) +{ + Assert(status); + const uint32_t err_code = YBCStatusPgsqlError(status); + + return err_code == ERRCODE_YB_TXN_CONFLICT || err_code == ERRCODE_YB_TXN_ABORTED; +} + +static void +HandleExplicitRowLockStatus(YbcPgExplicitRowLockStatus status) +{ + if (status.error_info.is_initialized && + YBCIsExplicitRowLockConflictStatus(status.ybc_status)) + { + YBCFreeStatus(status.ybc_status); + YBCHandleConflictError((OidIsValid(status.error_info.conflicting_table_id) ? + RelationIdGetRelation(status.error_info.conflicting_table_id) : + NULL), + status.error_info.pg_wait_policy); + } + else + { + HandleYBStatus(status.ybc_status); + } +} + +/* + * The return value of this function depends on whether we are batching or not. + * Currently, batching is enabled if the GUC yb_explicit_row_locking_batch_size > 1 + * and the wait policy is not "SKIP LOCKED". + * If we are batching, then the return value is just a placeholder, as we are not + * acquiring the lock on the row before returning. + * Otherwise, the returned TM_Result is adjusted in case of an error in acquiring the lock. + */ +TM_Result +YBCLockTuple(Relation relation, Datum ybctid, RowMarkType mode, + LockWaitPolicy pg_wait_policy, EState *estate) +{ + const YbcPgExplicitRowLockParams lock_params = { + .rowmark = mode, + .pg_wait_policy = pg_wait_policy, + .docdb_wait_policy = YBGetDocDBWaitPolicy(pg_wait_policy) + }; + + const Oid relfile_oid = YbGetRelfileNodeId(relation); + const Oid db_oid = YBCGetDatabaseOid(relation); + + if (yb_explicit_row_locking_batch_size > 1 && + lock_params.pg_wait_policy != LockWaitSkip) + { + HandleExplicitRowLockStatus(YBCAddExplicitRowLockIntent(relfile_oid, + ybctid, db_oid, + &lock_params, + YbBuildTableLocalityInfo(relation))); + YBCPgAddIntoForeignKeyReferenceCache(relfile_oid, ybctid); + return TM_Ok; + } + + YbcPgStatement ybc_stmt = YbNewSelect(relation, NULL /* prepare_params */ ); + + /* Bind ybctid to identify the current row. */ + YbcPgExpr ybctid_expr = YBCNewConstant(ybc_stmt, BYTEAOID, InvalidOid, ybctid, false); + + HandleYBStatus(YBCPgDmlBindColumn(ybc_stmt, YBTupleIdAttributeNumber, ybctid_expr)); + + YbcPgExecParameters exec_params = {0}; + + exec_params.limit_count = 1; + exec_params.rowmark = lock_params.rowmark; + exec_params.pg_wait_policy = lock_params.pg_wait_policy; + exec_params.docdb_wait_policy = lock_params.docdb_wait_policy; + exec_params.stmt_in_txn_limit_ht_for_reads = + estate->yb_exec_params.stmt_in_txn_limit_ht_for_reads; + + TM_Result res = TM_Ok; + bool has_data = false; + Datum *values = NULL; + bool *nulls = NULL; + YbcPgSysColumns syscols; + + HandleYBStatus(YBCPgExecSelect(ybc_stmt, &exec_params)); + YbcStatus status = YBCPgDmlFetch(ybc_stmt, 0, (uint64_t *) values, nulls, &syscols, &has_data); + if (!status) + YBCPgAddIntoForeignKeyReferenceCache(relfile_oid, ybctid); + else + { + const uint32_t err_code = YBCStatusPgsqlError(status); + elog(DEBUG2, "Error when trying to lock row. " + "pg_wait_policy=%d docdb_wait_policy=%d message=%s err_code=%d", + lock_params.pg_wait_policy, lock_params.docdb_wait_policy, + YBCStatusMessageBegin(status), err_code); + + switch(err_code) + { + case ERRCODE_YB_TXN_CONFLICT: + res = TM_Updated; + break; + case ERRCODE_YB_TXN_SKIP_LOCKING: + res = TM_WouldBlock; + break; + default: + HandleYBStatus(status); + break; + } + YBCFreeStatus(status); + } + YBCPgDeleteStatement(ybc_stmt); + return res; +} + +void +YBCFlushTupleLocks() +{ + HandleExplicitRowLockStatus(YBCFlushExplicitRowLockIntents()); +} + +/* + * ANALYZE support: take random sample of a YB table data + */ + +YbSample +ybBeginSample(Relation rel, int targrows) +{ + ReservoirStateData rstate; + TupleDesc tupdesc = RelationGetDescr(rel); + YbSample ybSample = (YbSample) palloc0(sizeof(YbSampleData)); + + ybSample->relation = rel; + ybSample->targrows = targrows; + ybSample->liverows = 0; + ybSample->deadrows = 0; + elog(DEBUG1, "Sampling %d rows from table %s", + targrows, RelationGetRelationName(rel)); + + reservoir_init_selection_state(&rstate, targrows); + /* + * Create new sampler command + */ + ybSample->handle = YbNewSample(rel, targrows, rstate.W, rstate.randstate.s0, rstate.randstate.s1); + for (AttrNumber attnum = 1; attnum <= tupdesc->natts; attnum++) + { + if (!TupleDescAttr(tupdesc, attnum - 1)->attisdropped) + YbDmlAppendTargetRegular(tupdesc, attnum, ybSample->handle); + } + + ybSample->exec_params.yb_fetch_row_limit = yb_fetch_row_limit; + ybSample->exec_params.yb_fetch_size_limit = yb_fetch_size_limit; + ybSample->exec_params.rowmark = -1; + + return ybSample; +} + +/* + * Sequentially scan next block of YB table and select rows for the sample. + * Block is a sequence of rows from one partition, up to specific number of + * rows or the end of the partition. + * Algorithm selects every scanned row until targrows are selected, then it + * select random rows, with decreasing probability, to replace one of the + * previously selected rows. + * The IDs of selected rows are stored in the internal buffer (reservoir). + * Scan ends and function returns false if one of two is true: + * - end of the table is reached + * or + * - targrows are selected and end of a table partition is reached. + */ +bool +ybSampleNextBlock(YbSample ybSample) +{ + bool has_more; + + HandleYBStatus(YBCPgSampleNextBlock(ybSample->handle, &has_more)); + return has_more; +} + +/* + * Fetch the rows selected for the sample into pre-allocated buffer. + * Return number of rows fetched. + */ +int +ybFetchSample(YbSample ybSample, HeapTuple *rows) +{ + Oid relid = RelationGetRelid(ybSample->relation); + TupleDesc tupdesc = RelationGetDescr(ybSample->relation); + Datum *values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum)); + bool *nulls = (bool *) palloc(tupdesc->natts * sizeof(bool)); + int numrows = 0; + int sampledrows; + bool has_data = false; + + /* + * Retrieve liverows and deadrows counters. + * TODO: count deadrows + */ + HandleYBStatus(YBCPgGetEstimatedRowCount(ybSample->handle, + &sampledrows, + &ybSample->liverows, + &ybSample->deadrows)); + while (numrows < sampledrows) + { + /* + * Execute equivalent of + * SELECT * FROM table WHERE ybctid IN [yctid0, ybctid1, ...]; + */ + if (!has_data) + { + HandleYBStatus(YBCPgExecSample(ybSample->handle, + &ybSample->exec_params)); + } + YbcPgSysColumns syscols; + /* Fetch one row. */ + HandleYBStatus(YBCPgDmlFetch(ybSample->handle, + tupdesc->natts, + (uint64_t *) values, + nulls, + &syscols, + &has_data)); + + if (has_data) + { + /* Make a heap tuple in current memory context */ + rows[numrows] = heap_form_tuple(tupdesc, values, nulls); + if (syscols.ybctid != NULL) + HEAPTUPLE_YBCTID(rows[numrows]) = PointerGetDatum(syscols.ybctid); + rows[numrows]->t_tableOid = relid; + ++numrows; + } + } + + if (*YBCGetGFlags()->TEST_delay_after_table_analyze_ms > 0) + { + pg_usleep(*YBCGetGFlags()->TEST_delay_after_table_analyze_ms * 1000L); + } + + pfree(values); + pfree(nulls); + /* Close the DocDB statement */ + YBCPgDeleteStatement(ybSample->handle); + return numrows; +} + +/* + * ybFetchNext + * + * Fetch next row from the provided YbcPgStatement and load it into the slot. + * + * The statement must be ready to be fetched from, in other words it should be + * executed, that means request is sent to the DocDB. + * + * Fetched values are copied from the DocDB response and memory for by-reference + * data types is allocated from the current memory context, so be sure that + * lifetime of that context is appropriate. + * + * slot is expected to be a VirtualTupleTableSlot. Its t_tableOid field is + * updated with provided relid and ybctid field is set to returned ybctid + * value. + */ +void +ybFetchNext(YbcPgStatement handle, TupleTableSlot *slot, Oid relid) +{ + Assert(slot != NULL); + Assert(TTS_IS_VIRTUAL(slot)); + TupleDesc tupdesc = slot->tts_tupleDescriptor; + Datum *values = slot->tts_values; + bool *nulls = slot->tts_isnull; + YbcPgSysColumns syscols; + bool has_data; + + ExecClearTuple(slot); + /* Fetch one row. */ + HandleYBStatus(YBCPgDmlFetch(handle, + tupdesc->natts, + (uint64_t *) values, + nulls, + &syscols, + &has_data)); + if (has_data) + { + slot->tts_nvalid = tupdesc->natts; + slot->tts_flags &= ~TTS_FLAG_EMPTY; /* Not empty */ + TABLETUPLE_YBCTID(slot) = PointerGetDatum(syscols.ybctid); + slot->tts_tableOid = relid; + } +} + +/*************************************************************************** + * Parallel scan on hash partitioned table + * + * Currently not in use, but may be salvaged + ***************************************************************************/ + +/* + * Number of rows per planned parallel range. + */ +int yb_parallel_range_rows = 0; + +/* + * ybParallelWorkers + * + * Estimate how many parallel workers are needed to scan a relation having + * specified number of rows. + */ +int +ybParallelWorkers(double numrows) +{ + /* yb_parallel_range_rows set to 0 disables parallelizm */ + if (yb_parallel_range_rows <= 0) + return 0; + + /* Estimate number of parallel workers */ + double result = ceil(numrows / (double) yb_parallel_range_rows) - 1; + + /* + * Cap it at compile time limit for sanity, later on the value will be + * further capped accoding to the configuration. + */ + return (result > MAX_PARALLEL_WORKER_LIMIT ? + MAX_PARALLEL_WORKER_LIMIT : + (int) result); +} + +/****************************************************************************** + * Parallel scan on YB tables regardless of partitioning + * + * Based on the sequnce of the keys retrieved from the DocDB. + * The keys are fetched and put into a cyclic buffer by any worker that finds + * the number of keys in the buffer is too low. If not fetching keys, the + * workers are continuously scanning ranges between the keys. Worker takes one + * key from the buffer as the low range bound, and copies the next key as the + * high range bound. A mutex is used to ensure that no more than one worker is + * putting or taking rows. The very last key remaining in the buffer can not be + * taken until the fetch is complete, because it is used as a starting key when + * keys are requested from DocDB. + ******************************************************************************/ + +/* + * yb_estimate_parallel_size + * + * Calculate the size of the shared memory block to exchange information + * between the workers. + * TODO(#19467) The variable part of the block is a cyclic buffer to store keys. + * It is a constant currently, but its size should be estimated before the + * parallel scan starts. For normal operation it should hold several keys + * (optimal number is TBD and may depend on the number of workers). However, in + * the worst case scenario, it is safe to allow keys no longer than 1/3 of + * the buffer. The buffer must keep the very last key, and have room before or + * after to add one more key. In the worst case the very last key sits in the + * middle, and the buffer 3 times bigger than the key ensures that one would + * fit. + */ +Size +yb_estimate_parallel_size(void) +{ + Size size = sizeof(YBParallelPartitionKeysData); + + return add_size(size, YB_PARTITION_KEY_DATA_CAPACITY); +} + +/* + * yb_init_partition_key_data + * + * Initialize the YBParallelPartitionKeys structure + */ +void +yb_init_partition_key_data(void *data) +{ + YBParallelPartitionKeys ppk = (YBParallelPartitionKeys) data; + + SpinLockInit(&ppk->mutex); + ConditionVariableInit(&ppk->cv_empty); + ppk->database_oid = InvalidOid; + ppk->table_relfilenode_oid = InvalidOid; + ppk->fetch_status = FETCH_STATUS_IDLE; + ppk->low_offset = 0; + ppk->high_offset = 0; + ppk->key_count = 0; + ppk->total_key_size = 0.0; + ppk->total_key_count = 0.0; + ppk->key_data_size = 0; + ppk->key_data_capacity = YB_PARTITION_KEY_DATA_CAPACITY; +} + +/* + * yb_rescan_partition_key_data + * + * Reset the YBParallelPartitionKeys structure, that is, discard any key data + * that may be still there. We also have to remove the table identification, as + * an empty buffer with valid table ID would look like exhausted, not brand new, + * and wouldn't be valid for fetch. + */ +void +yb_rescan_partition_key_data(void *data) +{ + YBParallelPartitionKeys ppk = (YBParallelPartitionKeys) data; + + /* + * It shouldn't be necessary to acquire the lock here, but there's no harm + * if we do. + */ + SpinLockAcquire(&ppk->mutex); + ppk->database_oid = InvalidOid; + ppk->table_relfilenode_oid = InvalidOid; + ppk->fetch_status = FETCH_STATUS_IDLE; + ppk->low_offset = 0; + ppk->high_offset = 0; + ppk->key_count = 0; + ppk->total_key_size = 0.0; + ppk->total_key_count = 0.0; + ppk->key_data_size = 0; + ppk->key_data_capacity = YB_PARTITION_KEY_DATA_CAPACITY; + SpinLockRelease(&ppk->mutex); +} + +typedef int yb_keylen_t; +#define KEY_LEN(ppk, key_offset) \ + (ppk)->key_data + (key_offset) +#define KEY_DATA(ppk, key_offset) \ + (ppk)->key_data + (key_offset) + sizeof(yb_keylen_t) + +/* + * yb_add_key_unsynchronized + * + * Copy next key into the cyclic buffer. Caller must assure exclusive access to + * the YBParallelPartitionKeys structure. + * Function checks for the available space in the buffer and returns false if it + * is insufficient. Otherwise it appends the key into the buffer. + */ +static bool +yb_add_key_unsynchronized(YBParallelPartitionKeys ppk, + const char *key, yb_keylen_t key_len) +{ + /* Only the first key is allowed to be empty */ + Assert(key_len > 0 || ppk->key_count == 0); + /* Special case: initially empty buffer */ + if (ppk->key_count == 0) + { + Assert(sizeof(key_len) + key_len <= ppk->key_data_capacity); + memcpy(KEY_LEN(ppk, 0), &key_len, sizeof(yb_keylen_t)); + /* Update counters, etc */ + if (key_len > 0) + { + memcpy(KEY_DATA(ppk, 0), key, key_len); + ppk->total_key_size += key_len; + ppk->total_key_count += 1; + } + ++ppk->key_count; + ppk->key_data_size += sizeof(key_len) + key_len; + } + /* need to check empty space */ + else if (ppk->high_offset < ppk->low_offset) + { + /* + * Wrapped around buffer, the available space lays between the end of + * the high key and the beginning of the low key. + */ + yb_keylen_t high_key_len; + + memcpy(&high_key_len, KEY_LEN(ppk, ppk->high_offset), sizeof(yb_keylen_t)); + int free_offset = ppk->high_offset + sizeof(int) + high_key_len; + + /* Check the room in the buffer */ + Assert(free_offset <= ppk->low_offset); + if (ppk->low_offset - free_offset < sizeof(yb_keylen_t) + key_len) + return false; + memcpy(KEY_LEN(ppk, free_offset), &key_len, sizeof(yb_keylen_t)); + memcpy(KEY_DATA(ppk, free_offset), key, key_len); + /* Update counters, etc */ + ++ppk->key_count; + ppk->high_offset = free_offset; + ppk->total_key_size += key_len; + ppk->total_key_count += 1; + } + else /* The low_offset == high_offset iif key_count + * == 1 */ + { + /* + * In not wrapped around buffer we maintain ppk->key_data_size + * pointing at the beginning of the free space. + */ + int free_offset = ppk->key_data_size; + + /* Check for the trailing space capacity */ + if (ppk->key_data_capacity - free_offset >= sizeof(key_len) + key_len) + { + memcpy(KEY_LEN(ppk, free_offset), &key_len, sizeof(yb_keylen_t)); + memcpy(KEY_DATA(ppk, free_offset), key, key_len); + /* Update counters, etc */ + ++ppk->key_count; + ppk->high_offset = free_offset; + ppk->key_data_size += sizeof(key_len) + key_len; + ppk->total_key_size += key_len; + ppk->total_key_count += 1; + } + /* + * The key does not fit into remaining space at the end of the buffer, + * but there may be free space at the beginning, so we can wraparoud. + */ + else if (ppk->low_offset >= sizeof(key_len) + key_len) + { + memcpy(KEY_LEN(ppk, 0), &key_len, sizeof(yb_keylen_t)); + memcpy(KEY_DATA(ppk, 0), key, key_len); + /* Update counters, etc */ + ++ppk->key_count; + ppk->high_offset = 0; + ppk->total_key_size += key_len; + ppk->total_key_count += 1; + } + /* No luck, let caller know */ + else + return false; + } + return true; +} + +/* + * yb_remove_key_unsynchronized + * + * Remove the lowest key from the buffer. Caller must assure exclusive access to + * the YBParallelPartitionKeys structure + */ +static void +yb_remove_key_unsynchronized(YBParallelPartitionKeys ppk) +{ + Assert(ppk->key_count > 0); + yb_keylen_t key_len; + + --ppk->key_count; + memcpy(&key_len, KEY_LEN(ppk, ppk->low_offset), sizeof(yb_keylen_t)); + /* Find offset of the next element */ + int next = ppk->low_offset + sizeof(yb_keylen_t) + key_len; + + if (next == ppk->key_data_size) + { + /* + * The lowest key is actually the last one in the wrapped around cyclic + * buffer, so the next one starts from the beginning of the buffer data. + */ + next = 0; + /* + * Also we need to update the key_data_size to point to the free space + * after the higest key, which will be the last after the removal, as + * the buffer will no longer be wrapped around. + * Special case is if the lowest key is the only key in the buffer. + * Empty buffer is not suposed to be used, but it would make no harm + * to reset. + */ + if (ppk->key_count == 0) + { + ppk->high_offset = 0; + ppk->key_data_size = 0; + } + else + { + memcpy(&key_len, KEY_LEN(ppk, ppk->high_offset), sizeof(yb_keylen_t)); + ppk->key_data_size = ppk->high_offset + sizeof(yb_keylen_t) + key_len; + } + } + ppk->low_offset = next; +} + +/* + * Structure to encapsulate ppk_buffer_fetch_callback's state. + * When worker fetches next portion of keys the buffer may become full. In such + * a case rest of the keys have to be discarded to avoid skipping keys. + * There's no way to let caller know about discarded keys, so callback should + * remember the fact in its state and ignore the rest of the keys. + * The ppk_buffer_initialize_callback changes the fetch status back to IDLE for + * the same purpose because it uses the YBParallelPartitionKeys structure + * exclusively, but ppk_buffer_fetch_callback may work concurrently with other + * workers taking keys from the buffer, and at some point other worker may need + * to fetch more and change the fetch state to WORKING. Hence the separate + * field, a counter, to be able to report inefficient fetch. + */ +typedef struct YbFetchKeysParam +{ + int discarded; + YBParallelPartitionKeys ppk; +} YbFetchKeysParam; + +static void +ppk_buffer_fetch_callback(void *param, const char *key, size_t key_size) +{ + YbFetchKeysParam *fkp = (YbFetchKeysParam *) param; + YBParallelPartitionKeys ppk = fkp->ppk; + + /* Once discarded, discard all the keys, just count them */ + if (fkp->discarded) + { + ++fkp->discarded; + return; + } + if (key_size) + { + bool added; + + SpinLockAcquire(&ppk->mutex); + /* + * Function is supposed to be called by the worker actively performing + * fetch. + */ + Assert(ppk->fetch_status == FETCH_STATUS_WORKING); + added = yb_add_key_unsynchronized(ppk, key, key_size); + SpinLockRelease(&ppk->mutex); + /* + * If a value has been successfully added, notify other workers that + * may be waiting for available key. Key may fail to be added because + * the buffer has no room for it. That means the key and all subsequent + * messages of the block have to be discarded. + * Since this fetch cycle is, in fact, done, allow other workers to + * start another fetch, while this worker will be busy for some time + * throwing away remaining keys. + */ + if (added) + ConditionVariableSignal(&ppk->cv_empty); + else + { + ppk->fetch_status = FETCH_STATUS_IDLE; + ++fkp->discarded; + } + } + else + { + /* The last key from DocDB */ + SpinLockAcquire(&ppk->mutex); + /* Update fetch status */ + Assert(ppk->fetch_status == FETCH_STATUS_WORKING); + ppk->fetch_status = FETCH_STATUS_DONE; + SpinLockRelease(&ppk->mutex); + /* + * The fact that fetch is done makes very last key in the buffer + * available, so if there are workers waiting, let them know. One + * worker will be able to grab the last working range, other will be + * able to tell that their work is done. + */ + ConditionVariableBroadcast(&ppk->cv_empty); + } +} + +/* + * yb_fetch_partition_keys + * + * Fetch some keys from the DocDB and put them into the parallel state + * buffer. Function estimates how many keys to request, but if there are too + * many keys to fit into the buffer, the remaining keys are discarded. + */ +static void +yb_fetch_partition_keys(YBParallelPartitionKeys ppk) +{ + const char *latest_key; + size_t latest_key_size; + uint64_t max_num_ranges; + YbFetchKeysParam fkp = {0, ppk}; + + /* Estimate fetch parameter values */ + SpinLockAcquire(&ppk->mutex); + /* Until fetch is done at least one key must remain in the buffer */ + Assert(ppk->key_count > 0); + yb_keylen_t key_len; + + memcpy(&key_len, KEY_LEN(ppk, ppk->high_offset), sizeof(yb_keylen_t)); + latest_key_size = key_len; + /* Empty key indicates the end of the keys, fetch shouldn't be possible. */ + Assert(latest_key_size); + /* + * It is safe to refer the key data in place, since the highest key can not + * be removed from the buffer until fetch is completed. + */ + latest_key = KEY_DATA(ppk, ppk->high_offset); + + /* + * Find average key size so far. We expect reasonable number have already + * been received during initialization. + */ + double average_key_size = ppk->total_key_size / ppk->total_key_count; + + /* Account for the key length stored in the buffer */ + average_key_size += sizeof(yb_keylen_t); + max_num_ranges = + floor(ppk->key_data_capacity / average_key_size) - ppk->key_count; + if (max_num_ranges < 16) + max_num_ranges = 16; + else if (max_num_ranges > 1024) + max_num_ranges = 1024; + SpinLockRelease(&ppk->mutex); + + /* + * We don't bother to take the lock to read ppk->key_data_capacity because + * it remains constant since its initialization. However, later on we will + * calculate fetch sizes and will take the lock, and capture + * ppk->key_data_capacity under that lock. + */ + HandleYBStatus(YBCGetTableKeyRanges(ppk->database_oid, + ppk->table_relfilenode_oid, + ppk->is_forward ? latest_key : NULL /* lower_bound_key */ , + ppk->is_forward ? latest_key_size : 0 /* lower_bound_key_size */ , + ppk->is_forward ? NULL : latest_key /* upper_bound_key */ , + ppk->is_forward ? 0 : latest_key_size /* upper_bound_key_size */ , + max_num_ranges, yb_parallel_range_size, ppk->is_forward, + (ppk->key_data_capacity / 3) - sizeof(yb_keylen_t) /* max_key_length */ , + ppk_buffer_fetch_callback, &fkp)); + SpinLockAcquire(&ppk->mutex); + /* Update fetch status */ + if (ppk->fetch_status == FETCH_STATUS_WORKING) + ppk->fetch_status = FETCH_STATUS_IDLE; + else + Assert(ppk->fetch_status == FETCH_STATUS_DONE || fkp.discarded); + SpinLockRelease(&ppk->mutex); + /* Log results for debugging and fine tuning */ + if (fkp.discarded) + elog(LOG, "Had to discard %d keys out of requested %d. Plan better!", + fkp.discarded, (int) max_num_ranges); + else + elog(LOG, "Fetch of up to %d keys is completed", (int) max_num_ranges); + /* All keys are accounted for, log stats */ + if (ppk->fetch_status == FETCH_STATUS_DONE) + elog(LOG, "Fetch is done, received %.0f keys (%.0f bytes)", + ppk->total_key_count, ppk->total_key_size); +} + +static void +ppk_buffer_initialize_callback(void *param, const char *key, size_t key_size) +{ + YBParallelPartitionKeys ppk = (YBParallelPartitionKeys) param; + + if (ppk->fetch_status != FETCH_STATUS_WORKING) + { + /* + * Status changes from WORKING to IDLE when buffer is full, in that + * case the remaining keys are ignored. + * Status changes to DONE if the key is empty, indicating the end of + * the keys, callback must not be called after that. + */ + Assert(ppk->fetch_status == FETCH_STATUS_IDLE); + return; + } + if (key_size == 0) + { + elog(LOG, + "All ranges are fetched at once, received %.0f keys (%.0f bytes)", + ppk->total_key_count, ppk->total_key_size); + ppk->fetch_status = FETCH_STATUS_DONE; + } + else if (!yb_add_key_unsynchronized(ppk, key, key_size)) + { + elog(LOG, "Buffer is full after %d initial keys are loaded, " + "discard the rest", ppk->key_count); + ppk->fetch_status = FETCH_STATUS_IDLE; + } +} + +/* + * ybParallelPrepare + * + * Load initial data into the parallel state structure. + * When this function is working, no parallel worker is started yet, so + * the parallel state is owned exclusively, no locking is needed. + */ +void +ybParallelPrepare(YBParallelPartitionKeys ppk, Relation relation, + YbcPgExecParameters *exec_params, bool is_forward) +{ + /* + * The index scan access method's DSM initialization routines do not + * disclose if DSM is initialized for main process or for the background + * worker. However, it is still guaranteed that background workers do not + * start until main worker DSM initialization is completed. + * Hence we always call ybParallelPrepare and use table_relfilenode_oid as + * an indicator: if table_relfilenode_oid is valid, it is a background + * worker and no initialization is needed. + * The table_relfilenode_oid is never changed once initialized, + * so spinlock is not required to check it. The rest of the code still + * has the YBParallelPartitionKeys structure exclusively. + */ + if (OidIsValid(ppk->table_relfilenode_oid)) + { + Assert(ppk->table_relfilenode_oid == YbGetRelfileNodeId(relation)); + return; + } + + /* We expect freshly initialized parallel state */ + Assert(ppk->fetch_status == FETCH_STATUS_IDLE); + Assert(ppk->low_offset == 0); + Assert(ppk->high_offset == 0); + Assert(ppk->key_count == 0); + ppk->database_oid = YBCGetDatabaseOid(relation); + ppk->table_relfilenode_oid = YbGetRelfileNodeId(relation); + ppk->is_forward = is_forward; + /* + * Put empty key as the first to be taken. + * Empty key means lower bound unchanged, so if original request has + * lower bound, it will be used. + * TODO(#19465) Scan conditions may allow to determine boundaries, and we + * have algorithms to do so, however, in practice it happens much later to + * be useful here. We need to move this logic. + */ + yb_add_key_unsynchronized(ppk, NULL, 0); + /* Fetch the first set of keys */ + ppk->fetch_status = FETCH_STATUS_WORKING; + HandleYBStatus(YBCGetTableKeyRanges(ppk->database_oid, + ppk->table_relfilenode_oid, + NULL /* lower_bound_key */ , 0 /* lower_bound_key_size */ , + NULL /* upper_bound_key */ , 0 /* upper_bound_key_size */ , + YB_PARTITION_KEYS_DEFAULT_FETCH_SIZE, + yb_parallel_range_size, is_forward, + (ppk->key_data_capacity / 3) - sizeof(yb_keylen_t), + ppk_buffer_initialize_callback, ppk)); + /* Update fetch status, unless updated by the callback */ + if (ppk->fetch_status == FETCH_STATUS_WORKING) + ppk->fetch_status = FETCH_STATUS_IDLE; +} + +typedef enum YbNextRangeResult +{ + NEXT_RANGE_WAIT, + NEXT_RANGE_SUCCESS, + NEXT_RANGE_FETCH, + NEXT_RANGE_DONE +} YbNextRangeResult; + +/* + * yb_copy_key_unsynchronized + * + * Copy the lowest key from the buffer into newly palloc'ed space and return + * pointer to the space in bound parameter. + * Return NULL if the key is empty. + */ +static void +yb_copy_key_unsynchronized(YBParallelPartitionKeys ppk, + const char **bound, + size_t *bound_size) +{ + yb_keylen_t key_len; + + memcpy(&key_len, KEY_LEN(ppk, ppk->low_offset), sizeof(yb_keylen_t)); + *bound_size = key_len; + if (key_len > 0) + { + *bound = (const char *) palloc(key_len); + memcpy((void *) *bound, KEY_DATA(ppk, ppk->low_offset), key_len); + } + else + *bound = NULL; +} + +/* + * ybParallelNextRange + * + * Take another range to work on from the parallel state. + * If there are too few ybctids in the buffer this function may fetch some + * first. Function may block if there are no ybctids available. + * Return values low_bound and high_bound are the boundaries for the range. + * If they are not NULLs, they are palloc'ed, caller must free them. + * Function returns true if next range exists and valid bounds are returned. + * If false is returned it means no more ranges and the worker should stop. + */ +bool +ybParallelNextRange(YBParallelPartitionKeys ppk, + const char **low_bound, + size_t *low_bound_size, + const char **high_bound, + size_t *high_bound_size) +{ + YbNextRangeResult result = NEXT_RANGE_WAIT; + + while (true) + { + SpinLockAcquire(&ppk->mutex); + /* + * Check if we should fetch key + * TODO(#19469) create config variable for key count triggering the + * fetch or find logic better than the magic number + */ + if (ppk->fetch_status == FETCH_STATUS_IDLE && ppk->key_count < 4) + { + /* + * We will fetch more ranges after mutex is released, for now, + * prevent other workers from attempting to fetch. + */ + ppk->fetch_status = FETCH_STATUS_WORKING; + result = NEXT_RANGE_FETCH; + } + else + { + /* + * When performing forward scan, keys in the buffer are in the + * ascending order, so first one is going to be the lower bound, + * and second, if exists, the higher bound. + * When performing backward scan, keys in the buffer are in the + * descending order, so destination bouns are opposite. + */ + const char **first_key_dest_ptr = ppk->is_forward ? low_bound : high_bound; + size_t *first_key_size_ptr = ppk->is_forward ? low_bound_size : high_bound_size; + const char **second_key_dest_ptr = ppk->is_forward ? high_bound : low_bound; + size_t *second_key_size_ptr = ppk->is_forward ? high_bound_size : low_bound_size; + + /* Have multiple keys, can take one. */ + if (ppk->key_count > 1) + { + yb_copy_key_unsynchronized(ppk, first_key_dest_ptr, + first_key_size_ptr); + yb_remove_key_unsynchronized(ppk); + yb_copy_key_unsynchronized(ppk, second_key_dest_ptr, + second_key_size_ptr); + result = NEXT_RANGE_SUCCESS; + } + /* If the fetch is completed it is OK to take the last key. */ + else if (ppk->fetch_status == FETCH_STATUS_DONE) + { + if (ppk->key_count == 1) + { + yb_copy_key_unsynchronized(ppk, first_key_dest_ptr, + first_key_size_ptr); + yb_remove_key_unsynchronized(ppk); + *second_key_dest_ptr = NULL; + *second_key_size_ptr = 0; + result = NEXT_RANGE_SUCCESS; + } + else + { + /* No more data. */ + result = NEXT_RANGE_DONE; + } + /* The buffer should be empty now. */ + Assert(ppk->key_count == 0); + } + /* Wait otherwise. */ + } + SpinLockRelease(&ppk->mutex); + if (result == NEXT_RANGE_SUCCESS || result == NEXT_RANGE_DONE) + /* All is done. */ + break; + else if (result == NEXT_RANGE_FETCH) + /* Fetch more keys and try again. */ + yb_fetch_partition_keys(ppk); + else /* result == NEXT_RANGE_WAIT */ + { + elog(LOG, "ybParallelNextRange: waiting on empty queue"); + ConditionVariableSleep(&ppk->cv_empty, + WAIT_EVENT_YB_PARALLEL_SCAN_EMPTY); + } + } + ConditionVariableCancelSleep(); + /* + * One value has been taken from the buffer, if there is a worker attempting + * to put fetched data it may be able to proceed now. + */ + Assert(result == NEXT_RANGE_SUCCESS || result == NEXT_RANGE_DONE); + return result == NEXT_RANGE_SUCCESS; +} diff --git a/src/backend/access/ybgin/Makefile b/src/backend/access/ybgin/Makefile new file mode 100644 index 0000000000000..2746279d4b697 --- /dev/null +++ b/src/backend/access/ybgin/Makefile @@ -0,0 +1,17 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for access/ybgin +# +# IDENTIFICATION +# src/backend/access/ybgin/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/backend/access/ybgin +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = ybgin.o ybginget.o ybginscan.o ybginutil.o ybginwrite.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/ybgin/ybgin.c b/src/backend/access/ybgin/ybgin.c new file mode 100644 index 0000000000000..125c86b8d2c66 --- /dev/null +++ b/src/backend/access/ybgin/ybgin.c @@ -0,0 +1,90 @@ +/*-------------------------------------------------------------------------- + * + * ybgin.c + * Implementation of Yugabyte Generalized Inverted Index access method. + * + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * IDENTIFICATION + * src/backend/access/ybgin/ybgin.c + *-------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/amapi.h" +#include "access/gin.h" +#include "access/ybgin.h" +#include "fmgr.h" +#include "nodes/nodes.h" +#include "postgres_ext.h" + +/* + * YBGIN handler function: return IndexAmRoutine with access method parameters + * and callbacks. + */ +Datum +ybginhandler(PG_FUNCTION_ARGS) +{ + IndexAmRoutine *amroutine = makeNode(IndexAmRoutine); + + amroutine->amstrategies = 0; + amroutine->amsupport = GINNProcs; + amroutine->amcanorder = false; + amroutine->amcanorderbyop = false; + amroutine->amcanbackward = false; + amroutine->amcanunique = false; + amroutine->amcanmulticol = false; /* TODO(jason): support multicolumn */ + amroutine->amoptionalkey = true; + amroutine->amsearcharray = false; + amroutine->amsearchnulls = false; + amroutine->amstorage = true; + amroutine->amclusterable = false; + amroutine->ampredlocks = true; /* TODO(jason): check what this is */ + amroutine->amcanparallel = false; + amroutine->amcaninclude = false; + amroutine->ybamcanupdatetupleinplace = false; + amroutine->amkeytype = InvalidOid; + + amroutine->ambuild = ybginbuild; + amroutine->ambuildempty = ybginbuildempty; + amroutine->aminsert = NULL; /* use yb_aminsert below instead */ + amroutine->ambulkdelete = ybginbulkdelete; + amroutine->amvacuumcleanup = ybginvacuumcleanup; + amroutine->amcanreturn = NULL; + amroutine->amcostestimate = ybgincostestimate; + amroutine->amoptions = ybginoptions; + amroutine->amproperty = NULL; + amroutine->amvalidate = ybginvalidate; + amroutine->ambeginscan = ybginbeginscan; + amroutine->amrescan = ybginrescan; + amroutine->amgettuple = ybgingettuple; + amroutine->amgetbitmap = NULL; /* TODO(jason): support bitmap scan */ + amroutine->amendscan = ybginendscan; + amroutine->ammarkpos = NULL; + amroutine->amrestrpos = NULL; + amroutine->amestimateparallelscan = NULL; + amroutine->aminitparallelscan = NULL; + amroutine->amparallelrescan = NULL; + amroutine->yb_amisforybrelation = true; + amroutine->yb_aminsert = ybgininsert; + amroutine->yb_amdelete = ybgindelete; + amroutine->yb_amupdate = NULL; + amroutine->yb_ambackfill = ybginbackfill; + amroutine->yb_ammightrecheck = ybginmightrecheck; + amroutine->yb_ambindschema = ybginbindschema; + + PG_RETURN_POINTER(amroutine); +} diff --git a/src/backend/access/ybgin/ybginget.c b/src/backend/access/ybgin/ybginget.c new file mode 100644 index 0000000000000..3e8a57c13e084 --- /dev/null +++ b/src/backend/access/ybgin/ybginget.c @@ -0,0 +1,638 @@ +/*-------------------------------------------------------------------------- + * + * ybginget.c + * fetch tuples from a Yugabyte GIN scan. + * + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * IDENTIFICATION + * src/backend/access/ybgin/ybginget.c + *-------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup.h" +#include "access/relscan.h" +#include "access/sdir.h" +#include "access/sysattr.h" +#include "access/yb_scan.h" +#include "access/ybgin.h" +#include "access/ybgin_private.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "pg_yb_utils.h" +#include "utils/builtins.h" +#include "utils/elog.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/selfuncs.h" +#include "utils/yb_like_support.h" +#include "yb/yql/pggate/ybc_pggate.h" + +/* Copied from ginget.c. */ +#define GinIsVoidRes(s) ( ((GinScanOpaque) scan->opaque)->isVoidRes ) + +#define TSVECTOR_GIN_FAM_OID ((Oid) 3659) + +/* + * Parts copied from ginget.c. Take the code right under label + * restartScanEntry that initializes entry. + */ +static void +startScanEntry(GinScanEntry entry) +{ + entry->buffer = InvalidBuffer; + ItemPointerSetMin(&entry->curItem); + entry->offset = InvalidOffsetNumber; + if (entry->list) + pfree(entry->list); + entry->list = NULL; + entry->nlist = 0; + entry->matchBitmap = NULL; + entry->matchResult = NULL; + entry->reduceResult = false; + entry->predictNumberResult = 0; +} + +/* + * Comparison function for scan entry indexes. Sorts by predictNumberResult, + * least frequent items first. + * + * Copied from ginget.c. + */ +static int +entryIndexByFrequencyCmp(const void *a1, const void *a2, void *arg) +{ + const GinScanKey key = (const GinScanKey) arg; + int i1 = *(const int *) a1; + int i2 = *(const int *) a2; + uint32 n1 = key->scanEntry[i1]->predictNumberResult; + uint32 n2 = key->scanEntry[i2]->predictNumberResult; + + if (n1 < n2) + return -1; + else if (n1 == n2) + return 0; + else + return 1; +} + +/* + * Copied from ginget.c with the only difference being the lack of parameter + * ginstate, which isn't used there either. + */ +static void +startScanKey(GinScanOpaque so, GinScanKey key) +{ + MemoryContext oldCtx = CurrentMemoryContext; + int i; + int j; + int *entryIndexes; + + ItemPointerSetMin(&key->curItem); + key->curItemMatches = false; + key->recheckCurItem = false; + key->isFinished = false; + + /* + * Divide the entries into two distinct sets: required and additional. + * Additional entries are not enough for a match alone, without any items + * from the required set, but are needed by the consistent function to + * decide if an item matches. When scanning, we can skip over items from + * additional entries that have no corresponding matches in any of the + * required entries. That speeds up queries like "frequent & rare" + * considerably, if the frequent term can be put in the additional set. + * + * There can be many legal ways to divide them entries into these two + * sets. A conservative division is to just put everything in the required + * set, but the more you can put in the additional set, the more you can + * skip during the scan. To maximize skipping, we try to put as many + * frequent items as possible into additional, and less frequent ones into + * required. To do that, sort the entries by frequency + * (predictNumberResult), and put entries into the required set in that + * order, until the consistent function says that none of the remaining + * entries can form a match, without any items from the required set. The + * rest go to the additional set. + */ + if (key->nentries > 1) + { + MemoryContextSwitchTo(so->tempCtx); + + entryIndexes = (int *) palloc(sizeof(int) * key->nentries); + for (i = 0; i < key->nentries; i++) + entryIndexes[i] = i; + qsort_arg(entryIndexes, key->nentries, sizeof(int), + entryIndexByFrequencyCmp, key); + + for (i = 0; i < key->nentries - 1; i++) + { + /* Pass all entries <= i as FALSE, and the rest as MAYBE */ + for (j = 0; j <= i; j++) + key->entryRes[entryIndexes[j]] = GIN_FALSE; + for (j = i + 1; j < key->nentries; j++) + key->entryRes[entryIndexes[j]] = GIN_MAYBE; + + if (key->triConsistentFn(key) == GIN_FALSE) + break; + } + /* i is now the last required entry. */ + + MemoryContextSwitchTo(so->keyCtx); + + key->nrequired = i + 1; + key->nadditional = key->nentries - key->nrequired; + key->requiredEntries = palloc(key->nrequired * sizeof(GinScanEntry)); + key->additionalEntries = palloc(key->nadditional * sizeof(GinScanEntry)); + + j = 0; + for (i = 0; i < key->nrequired; i++) + key->requiredEntries[i] = key->scanEntry[entryIndexes[j++]]; + for (i = 0; i < key->nadditional; i++) + key->additionalEntries[i] = key->scanEntry[entryIndexes[j++]]; + + /* clean up after consistentFn calls (also frees entryIndexes) */ + MemoryContextReset(so->tempCtx); + } + else + { + MemoryContextSwitchTo(so->keyCtx); + + key->nrequired = 1; + key->nadditional = 0; + key->requiredEntries = palloc(1 * sizeof(GinScanEntry)); + key->requiredEntries[0] = key->scanEntry[0]; + } + MemoryContextSwitchTo(oldCtx); +} + +/* + * Parts copied from ginget.c. Don't bother copying GinFuzzySearchLimit code + * since it's not supported. + */ +static void +startScan(IndexScanDesc scan) +{ + GinScanOpaque so = (GinScanOpaque) scan->opaque; + uint32 i; + + for (i = 0; i < so->totalentries; i++) + startScanEntry(so->entries[i]); + + /* Don't support GinFuzzySearchLimit. */ + + /* + * Now that we have the estimates for the entry frequencies, finish + * initializing the scan keys. + */ + for (i = 0; i < so->nkeys; i++) + startScanKey(so, so->keys + i); +} + +/* + * Set up the scan keys, and check for unsatisfiable query. + * + * Parts copied from ginget.c gingetbitmap. Since ybgin does not use bitmap + * scan, things are much different. gingetbitmap iterates over the resulting + * tuples, but ybgingettuple will be called once for each result tuple. Only + * take the setup part of gingetbitmap. + */ +static bool +ybginGetScanKeys(IndexScanDesc scan) +{ + GinScanOpaque so = (GinScanOpaque) scan->opaque; + + ginFreeScanKeys(so); /* there should be no keys yet, but just to be + * sure */ + ginNewScanKey(scan); + + if (GinIsVoidRes(scan)) + return false; + + /* + * Categorize scan entries to required and additional. + */ + startScan(scan); + + return true; +} + +/* + * Generate a Const node of text type from a C string. + * + * Parts copied from string_to_const. + */ +static Const * +text_to_const(Datum conval, Oid colloid) +{ + Oid datatype = TEXTOID; + int constlen = -1; + + return makeConst(datatype, -1, colloid, constlen, + conval, false, false); +} + +/* + * Try to generate a string to serve as an exclusive upperbound for matching + * strings with the given prefix. If successful, return a palloc'd string in + * the form of a Const node; else, return NULL. + */ +static Const * +get_greaterstr(Datum prefix, Oid datatype, Oid colloid) +{ + Const *prefix_const; + FmgrInfo ltproc; + Oid opfamily; + Oid oproid; + + /* yb_make_greater_string cannot accurately handle non-C collations. */ + if (!lc_collate_is_c(colloid)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot handle ybgin scans with prefix on non-C collation %u", + colloid))); + + /* + * For now, hardcode to assume type is text. This is true for the four + * native postgres ybgin opclasses, but it may no longer be true when + * supporting extensions like btree_gin. This assumption makes finding + * opfamily and operator easier. + */ + if (datatype != TEXTOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot handle ybgin scans with prefix on key type %u", + datatype))); + + opfamily = TEXT_LSM_FAM_OID; + oproid = get_opfamily_member(opfamily, datatype, datatype, + BTLessStrategyNumber); + if (oproid == InvalidOid) + elog(ERROR, "no < operator for opfamily %u", opfamily); + fmgr_info(get_opcode(oproid), <proc); + prefix_const = text_to_const(prefix, colloid); + return yb_make_greater_string(prefix_const, <proc, colloid); +} + +static void +ybginSetupBindsForPrefix(TupleDesc tupdesc, YbginScanOpaque ybso, + GinScanEntry entry) +{ + Const *greaterstr; + GinScanOpaque so = (GinScanOpaque) ybso; + Oid colloid; + Oid typoid; + YbcPgExpr expr_start, + expr_end; + + colloid = so->ginstate.supportCollation[0]; + typoid = TupleDescAttr(tupdesc, 0)->atttypid; + + expr_start = YBCNewConstant(ybso->handle, + typoid, + colloid, + entry->queryKey, + false /* is_null */ ); + + greaterstr = get_greaterstr((Datum) entry->queryKey, + typoid, + colloid); + if (greaterstr) + { + expr_end = YBCNewConstant(ybso->handle, + typoid, + colloid, + greaterstr->constvalue, + false /* is_null */ ); + HandleYBStatus(YBCPgDmlBindColumnCondBetween(ybso->handle, + 1 /* attr_num */ , + expr_start, + true, + expr_end, + false)); + pfree(greaterstr); + } + else + HandleYBStatus(YBCPgDmlBindColumnCondBetween(ybso->handle, + 1 /* attr_num */ , + expr_start, + true, + NULL /* attr_value_end */ , + true)); +} + +static void +ybginSetupBindsForPartialMatch(TupleDesc tupdesc, YbginScanOpaque ybso, + GinScanEntry entry) +{ + GinScanOpaque so = (GinScanOpaque) ybso; + + /* + * For now, assume partial match always means prefix match. In the + * future, this should be handled by a new support function, similar to + * the existing support function comparePartial. + * + * TODO(jason): don't assume one column when multicolumn is supported. + */ + if (so->ginstate.index->rd_opfamily[0] != TSVECTOR_GIN_FAM_OID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported ybgin index scan"), + errdetail("Partial match with ybgin index method" + " currently only supports opfamily %u: got %u.", + TSVECTOR_GIN_FAM_OID, + so->ginstate.index->rd_opfamily[0]), + errhint("Turn off index scan using" + " \"SET enable_indexscan TO false\"."))); + ybginSetupBindsForPrefix(tupdesc, ybso, entry); +} + +/* + * Add binds for the select. + */ +static void +ybginSetupBinds(IndexScanDesc scan) +{ + GinScanEntry entry; + GinScanKey key; + GinScanOpaque so = (GinScanOpaque) scan->opaque; + TupleDesc tupdesc; + YbginScanOpaque ybso = (YbginScanOpaque) scan->opaque; + + /* + * For now, only handle single-key scans. Multiple keys are possible even + * if multicolumn is disabled by specifiying the same column in multiple + * conditions (e.g. v @@ 'abc' and v @@ 'bcd'). + */ + if (so->nkeys != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported ybgin index scan"), + errdetail("ybgin index method cannot use" + " more than one scan key: got %d.", + so->nkeys), + errhint("Consider rewriting the query with INTERSECT and" + " UNION."))); + key = &so->keys[0]; + + /* + * For now, only handle the default search mode. + */ + if (key->searchMode != GIN_SEARCH_MODE_DEFAULT) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported ybgin index scan"), + errdetail("ybgin index method does not support" + " non-default search mode: %s.", + ybginSearchModeToString(key->searchMode)))); + + /* + * For now, only handle the case where there's one required scan entry + */ + if (key->nrequired != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported ybgin index scan"), + errdetail("ybgin index method cannot use" + " more than one required scan entry: got %d.", + key->nrequired))); + entry = key->requiredEntries[0]; + + + /* For now, don't handle null entries. */ + if (entry->queryCategory != GIN_CAT_NORM_KEY) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported ybgin index scan"), + errdetail("ybgin index method does not support" + " non-normal null category: %s.", + ybginNullCategoryToString(entry->queryCategory)))); + + /* Bind to the index because why else would we do an index scan? */ + tupdesc = RelationGetDescr(scan->indexRelation); + + if (entry->isPartialMatch) + { + ybginSetupBindsForPartialMatch(tupdesc, ybso, entry); + } + else + { + YbcPgExpr expr; + + /* Bind the one scan entry to the index column. */ + expr = YBCNewConstant(ybso->handle, + TupleDescAttr(tupdesc, 0)->atttypid, + so->ginstate.supportCollation[0], + entry->queryKey, + false /* is_null */ ); + HandleYBStatus(YBCPgDmlBindColumn(ybso->handle, + 1 /* attr_num */ , + expr)); + } +} + +/* + * Add targets for the select. + */ +static void +ybginSetupTargets(IndexScanDesc scan) +{ + TupleDesc tupdesc; + YbginScanOpaque ybso = (YbginScanOpaque) scan->opaque; + + /* + * We don't support IndexOnlyScan, which would directly target the index + * table. Therefore, as an IndexScan, target the base table. Change this + * if we ever support ybgin IndexOnlyScan. + */ + tupdesc = RelationGetDescr(scan->heapRelation); + + /* + * For scans that touch the base table, we seem to always query for the + * ybctid, even if the table may have explicit primary keys. A lower layer + * probably filters this out when not applicable. + */ + YbDmlAppendTargetSystem(YBTupleIdAttributeNumber, ybso->handle); + /* + * For now, target all non-system columns of the base table. This can be + * very inefficient. The lsm index access method avoids this using + * filtering (see YbAddTargetColumnIfRequired). + * + * TODO(jason): don't target unnecessary columns. + */ + for (AttrNumber attnum = 1; attnum <= tupdesc->natts; attnum++) + { + if (!TupleDescAttr(tupdesc, attnum - 1)->attisdropped) + YbDmlAppendTargetRegular(tupdesc, attnum, ybso->handle); + } +} + +/* + * With select prepared, ask pggate to execute it for the first time. This + * will prefetch some rows. Later fetches should not use this. + */ +static void +ybginExecSelect(IndexScanDesc scan, ScanDirection dir) +{ + YbginScanOpaque ybso = (YbginScanOpaque) scan->opaque; + + Assert(!ScanDirectionIsBackward(dir)); + /* Set scan direction, if matters */ + if (ScanDirectionIsForward(dir)) + HandleYBStatus(YBCPgSetForwardScan(ybso->handle, true)); + + HandleYBStatus(YBCPgExecSelect(ybso->handle, NULL /* exec_params */ )); +} + +/* + * Prepare and request the initial execution of select to pggate. + */ +static bool +ybginDoFirstExec(IndexScanDesc scan, ScanDirection dir) +{ + YbginScanOpaque ybso = (YbginScanOpaque) scan->opaque; + + if (!ybginGetScanKeys(scan)) + return false; + + /* binds */ + ybginSetupBinds(scan); + + /* targets */ + if (scan->yb_aggrefs != NIL) + /* + * As of 2023-06-28, aggregate pushdown is only implemented for + * IndexOnlyScan, not IndexScan. + */ + YbDmlAppendTargetsAggregate(scan->yb_aggrefs, + NULL, + RelationGetDescr(scan->indexRelation), + scan->indexRelation, + scan->xs_want_itup, + ybso->handle); + else + ybginSetupTargets(scan); + + YbSetCatalogCacheVersion(ybso->handle, YbGetCatalogCacheVersion()); + + /* execute select */ + ybginExecSelect(scan, dir); + + return true; +} + +/* + * Fetch the next tuple from pggate. + */ +static HeapTuple +ybginFetchNextHeapTuple(IndexScanDesc scan) +{ + bool has_data = false; + bool *nulls; + Datum *values; + HeapTuple tuple = NULL; + TupleDesc tupdesc; + YbcPgSysColumns syscols; + YbginScanOpaque ybso = (YbginScanOpaque) scan->opaque; + + /* + * As an IndexScan, target the base table. Change this if we ever support + * ybgin IndexOnlyScan. + */ + tupdesc = RelationGetDescr(scan->heapRelation); + nulls = (bool *) palloc(tupdesc->natts * sizeof(bool)); + values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum)); + + HandleYBStatus(YBCPgDmlFetch(ybso->handle, + tupdesc->natts, + (uint64_t *) values, + nulls, + &syscols, + &has_data)); + if (has_data) + { + tuple = heap_form_tuple(tupdesc, values, nulls); + + tuple->t_tableOid = RelationGetRelid(scan->heapRelation); + if (syscols.ybctid != NULL) + HEAPTUPLE_YBCTID(tuple) = PointerGetDatum(syscols.ybctid); + } + pfree(values); + pfree(nulls); + + return tuple; +} + +bool +ybgingettuple(IndexScanDesc scan, ScanDirection dir) +{ + HeapTuple tup; + YbginScanOpaque ybso = (YbginScanOpaque) scan->opaque; + + /* Sanity check: amcanbackward. */ + Assert(!ScanDirectionIsBackward(dir)); + + if (!ybso->is_exec_done) + { + if (!ybginDoFirstExec(scan, dir)) + return false; + ybso->is_exec_done = true; + } + + /* fetch */ + if (scan->yb_aggrefs) + { + /* + * TODO(jason): don't assume that recheck is needed. + */ + scan->xs_recheck = true; + + /* + * Aggregate pushdown directly modifies the scan slot rather than + * passing it through xs_hitup or xs_itup. + */ + return ybc_getnext_aggslot(scan, ybso->handle, scan->xs_want_itup); + } + while (HeapTupleIsValid(tup = ybginFetchNextHeapTuple(scan))) + { + if (true) /* TODO(jason): don't assume a match. */ + { + scan->xs_hitup = tup; + scan->xs_hitupdesc = RelationGetDescr(scan->heapRelation); + + /* TODO(jason): don't assume that recheck is needed. */ + scan->xs_recheck = true; + return true; + } + + heap_freetuple(tup); + } + + return false; +} + +/* + * TODO(jason): don't assume that recheck is needed. + */ +bool +ybginmightrecheck(Scan *scan, Relation heapRelation, Relation indexRelation, + bool xs_want_itup, ScanKey keys, int nkeys) +{ + return true; +} diff --git a/src/backend/access/ybgin/ybginscan.c b/src/backend/access/ybgin/ybginscan.c new file mode 100644 index 0000000000000..3826d78fc6e96 --- /dev/null +++ b/src/backend/access/ybgin/ybginscan.c @@ -0,0 +1,107 @@ +/*-------------------------------------------------------------------------- + * + * ybginscan.c + * routines to manage scans of Yugabyte inverted index relations + * + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * IDENTIFICATION + * src/backend/access/ybgin/ybginscan.c + *-------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/genam.h" +#include "access/relscan.h" +#include "access/skey.h" +#include "access/yb_scan.h" +#include "access/ybgin.h" +#include "access/ybgin_private.h" +#include "commands/yb_tablegroup.h" +#include "miscadmin.h" +#include "pg_yb_utils.h" +#include "utils/memutils.h" +#include "utils/palloc.h" +#include "utils/rel.h" +#include "yb/yql/pggate/ybc_pggate.h" + +/* + * Parts copied from ginscan.c ginbeginscan. Do the same thing except + * - palloc size of YbginScanOpaqueData + * - name memory contexts Ybgin + */ +IndexScanDesc +ybginbeginscan(Relation rel, int nkeys, int norderbys) +{ + IndexScanDesc scan; + GinScanOpaque so; + + /* no order by operators allowed */ + Assert(norderbys == 0); + + scan = RelationGetIndexScan(rel, nkeys, norderbys); + + /* allocate private workspace */ + so = (GinScanOpaque) palloc(sizeof(YbginScanOpaqueData)); + so->keys = NULL; + so->nkeys = 0; + so->tempCtx = AllocSetContextCreate(CurrentMemoryContext, + "Ybgin scan temporary context", + ALLOCSET_DEFAULT_SIZES); + so->keyCtx = AllocSetContextCreate(CurrentMemoryContext, + "Ybgin scan key context", + ALLOCSET_DEFAULT_SIZES); + initGinState(&so->ginstate, scan->indexRelation); + + scan->opaque = so; + + return scan; +} + +void +ybginrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, + ScanKey orderbys, int norderbys) +{ + YbginScanOpaque ybso = (YbginScanOpaque) scan->opaque; + YbcPgPrepareParameters prepare_params = { + .index_relfilenode_oid = YbGetRelfileNodeId(scan->indexRelation), + .index_only_scan = scan->xs_want_itup, + .embedded_idx = YbIsScanningEmbeddedIdx(scan->heapRelation, + scan->indexRelation), + }; + + /* Initialize non-yb gin scan opaque fields. */ + ginrescan(scan, scankey, nscankeys, orderbys, norderbys); + + ybso->handle = YbNewSelect(scan->heapRelation, &prepare_params); + + YbApplyPrimaryPushdown(ybso->handle, scan->yb_rel_pushdown); + YbApplySecondaryIndexPushdown(ybso->handle, scan->yb_idx_pushdown); + + /* Initialize ybgin scan opaque is_exec_done. */ + ybso->is_exec_done = false; +} + +void +ybginendscan(IndexScanDesc scan) +{ + /* + * This frees the scan opaque as if it were a GinScanOpaque rather than a + * YbginScanOpaque, but that's fine since the extra field HANDLE doesn't + * need special handling. + */ + ginendscan(scan); +} diff --git a/src/backend/access/ybgin/ybginutil.c b/src/backend/access/ybgin/ybginutil.c new file mode 100644 index 0000000000000..4a53bbfb9bc9c --- /dev/null +++ b/src/backend/access/ybgin/ybginutil.c @@ -0,0 +1,167 @@ +/*-------------------------------------------------------------------------- + * + * ybginutil.c + * Utility routines for the Yugabyte inverted index access method. + * + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * IDENTIFICATION + * src/backend/access/ybgin/ybginutil.c + *-------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/gin_private.h" +#include "access/relation.h" +#include "access/reloptions.h" +#include "commands/yb_cmds.h" +#include "nodes/execnodes.h" +#include "nodes/nodes.h" +#include "utils/index_selfuncs.h" + +void +ybgincostestimate(struct PlannerInfo *root, struct IndexPath *path, + double loop_count, Cost *indexStartupCost, + Cost *indexTotalCost, Selectivity *indexSelectivity, + double *indexCorrelation, double *indexPages) +{ + Assert(!path->yb_index_path_info.merge_scan_saop_cols); + + gincostestimate(root, path, loop_count, indexStartupCost, indexTotalCost, + indexSelectivity, indexCorrelation, indexPages); +} + +bytea * +ybginoptions(Datum reloptions, bool validate) +{ + relopt_value *options; + int numoptions; + int i; + + /* + * Rather than creating a new RELOPT_KIND_YBGIN, reuse RELOPT_KIND_GIN and + * disallow the options that aren't supported. The downside is that most + * gin options will probably apply to only one of (pg)gin or ybgin. The + * upside is that the unsupported options can be ignored to be compatible + * with existing scripts without needing modification. + * + * Since, currently, we disallow any gin reloptions being set, we should be + * able to change our mind later and introduce a RELOPT_KIND_YBGIN if + * desired. + * + * TODO(jason): ignore and clear, rather than error, on unsupported options + * + * To automatically disallow new options in case upstream postgres creates + * them and we import them, + * 1. if the relopt kind is not gin, allow + * 2. allow specific gin relopts (currently none) + * 3. disallow the rest + */ + options = parseRelOptions(reloptions, validate, RELOPT_KIND_GIN, + &numoptions); + for (i = 0; i < numoptions; i++) + { + if (!options[i].isset) + continue; + if (options[i].gen->kinds != RELOPT_KIND_GIN) + continue; + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ybgin indexes do not support reloption %s", + options[i].gen->name))); + } + + return NULL; +} + +bool +ybginvalidate(Oid opclassoid) +{ + /* + * Calling ginvalidate is probably right. It's hard to tell since it + * doesn't work in the first place (issue #8949). + * + * TODO(jason): when it starts working, refactor to use "ybgin" instead of + * "gin" for error messages (probably should create a layer to accept this + * string and pass it down). + */ + return ginvalidate(opclassoid); +} + +void +ybginbindschema(YbcPgStatement handle, + struct IndexInfo *indexInfo, + TupleDesc indexTupleDesc, + int16 *coloptions, + Oid *opclassOids, + Datum reloptions) +{ + YBCBindCreateIndexColumns(handle, + indexInfo, + indexTupleDesc, + coloptions, + indexInfo->ii_NumIndexKeyAttrs); +} + +/* + * Given gin null category, return human-readable string. + */ +const char * +ybginNullCategoryToString(GinNullCategory category) +{ + switch (category) + { + case GIN_CAT_NORM_KEY: + return "normal"; + case GIN_CAT_NULL_KEY: + return "null-key"; + case GIN_CAT_EMPTY_ITEM: + return "empty-item"; + case GIN_CAT_NULL_ITEM: + return "null-item"; + case GIN_CAT_EMPTY_QUERY: + return "empty-query"; + default: + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("unrecognized null category: %d", + category))); + } +} + +/* + * Given gin search mode, return human-readable string. + */ +const char * +ybginSearchModeToString(int32 searchMode) +{ + switch (searchMode) + { + case GIN_SEARCH_MODE_DEFAULT: + return "default"; + case GIN_SEARCH_MODE_INCLUDE_EMPTY: + return "include-empty"; + case GIN_SEARCH_MODE_ALL: + return "all"; + case GIN_SEARCH_MODE_EVERYTHING: + return "everything"; + default: + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("unrecognized search mode: %d", + searchMode))); + } +} diff --git a/src/backend/access/ybgin/ybginwrite.c b/src/backend/access/ybgin/ybginwrite.c new file mode 100644 index 0000000000000..e23d930af7f01 --- /dev/null +++ b/src/backend/access/ybgin/ybginwrite.c @@ -0,0 +1,382 @@ +/*------------------------------------------------------------------------- + * + * ybginwrite.c + * insert and delete routines for the Yugabyte inverted index access method. + * + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * IDENTIFICATION + * src/backend/access/ybgin/ybginwrite.c + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/genam.h" +#include "access/sysattr.h" +#include "access/yb_scan.h" +#include "access/ybgin_private.h" +#include "catalog/index.h" +#include "catalog/pg_type_d.h" +#include "catalog/yb_type.h" +#include "executor/ybModifyTable.h" +#include "nodes/execnodes.h" +#include "nodes/parsenodes.h" +#include "pg_yb_utils.h" +#include "storage/off.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/relcache.h" + +/* + * Parts copied from GinBuildState. Differences: + * - no buildStats because those are tied to postgres storage + * - no tmpCtx because that's tied to bulk inserts, which we won't do because + * it seems to be particularly beneficial for postgres btrees and not for + * Yugabyte DocDB + * - no accum for the same reason + * - add backfilltime to both indicate that the build is for online index + * backfill and specify the write time for it + */ +typedef struct +{ + GinState ginstate; + double indtuples; + MemoryContext funcCtx; + uint64_t *backfilltime; +} YbginBuildState; + +/* + * Utility method to set binds for index write statement. + */ +static void +doBindsForIdxWrite(YbcPgStatement stmt, + void *indexstate, + Relation index, + Datum *values, + bool *isnull, + int n_bound_atts, + Datum ybbasectid, + bool ybctid_as_value) +{ + GinState *ginstate = (GinState *) indexstate; + TupleDesc tupdesc = RelationGetDescr(index); + + if (ybbasectid == 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("missing base table ybctid in index write request"))); + + for (AttrNumber attnum = 1; attnum <= n_bound_atts; ++attnum) + { + Oid type_id = GetTypeId(attnum, tupdesc); + Oid collation_id = YBEncodingCollation(stmt, attnum, + ginstate->supportCollation[attnum - 1]); + Datum value = values[attnum - 1]; + bool is_null = isnull[attnum - 1]; + + YbBindDatumToColumn(stmt, attnum, type_id, collation_id, value, + is_null, &YBCGinNullTypeEntity); + } + + /* Gin indexes cannot be unique. */ + Assert(!index->rd_index->indisunique); + + /* Write base ctid column because it is a key column. */ + YbBindDatumToColumn(stmt, + YBIdxBaseTupleIdAttributeNumber, + BYTEAOID, + InvalidOid, + ybbasectid, + false /* is_null */ , + NULL /* null type_entity */ ); +} + +/* + * Extract entries and write values. + * + * The first part here is identical to first part of ginHeapTupleInsert. + */ +static int32 +ybginTupleWrite(GinState *ginstate, OffsetNumber attnum, + Relation index, Datum value, bool isNull, + Datum ybctid, uint64_t *backfilltime, + bool isinsert) +{ + Datum *entries; + GinNullCategory *categories; + int32 i, + nentries; + + entries = ginExtractEntries(ginstate, attnum, value, isNull, + &nentries, &categories); + + /* Make sure that this is a single-column index. */ + Assert(RelationGetNumberOfAttributes(index) == 1); + + for (i = 0; i < nentries; i++) + { + bool isnull = categories[i] != 0; + + /* + * Pass the null category down using the spot where the data usually + * goes. + */ + if (categories[i] != GIN_CAT_NORM_KEY) + entries[i] = categories[i]; + + /* Assume single-column index for parameters values and isnull. */ + if (isinsert) + YBCExecuteInsertIndex(index, &entries[i], &isnull, ybctid, + backfilltime /* backfill_write_time */ , + doBindsForIdxWrite, (void *) ginstate); + else + { + Assert(!backfilltime); + YBCExecuteDeleteIndex(index, &entries[i], &isnull, ybctid, + doBindsForIdxWrite, (void *) ginstate); + } + } + + return nentries; +} + +/* + * Insert index entries for a single indexable item during "normal" + * (non-fast-update) insertion + */ +static int32 +ybginTupleInsert(GinState *ginstate, OffsetNumber attnum, + Relation index, Datum value, bool isNull, + Datum ybctid, uint64_t *backfilltime) +{ + return ybginTupleWrite(ginstate, attnum, index, value, isNull, ybctid, + backfilltime, true /* isinsert */ ); +} + +/* + * Delete index entries for a single indexable item during "normal" + * (non-fast-update) insertion + */ +static int32 +ybginTupleDelete(GinState *ginstate, OffsetNumber attnum, + Relation index, Datum value, bool isNull, + Datum ybctid) +{ + return ybginTupleWrite(ginstate, attnum, index, value, isNull, ybctid, + NULL /* backfilltime */ , false /* isinsert */ ); +} + +/* + * Callback to insert index tuples after a base table tuple is retrieved. See + * similar ybcinbuildCallback. + */ +static void +ybginBuildCallback(Relation index, Datum ybctid, Datum *values, + bool *isnull, bool tupleIsAlive, void *state) +{ + YbginBuildState *buildstate = (YbginBuildState *) state; + GinState *ginstate = &buildstate->ginstate; + MemoryContext oldCtx; + int i; + int32 nentries = 0; + + oldCtx = MemoryContextSwitchTo(buildstate->funcCtx); + for (i = 0; i < ginstate->origTupdesc->natts; i++) + nentries += ybginTupleInsert(ginstate, (OffsetNumber) (i + 1), + index, values[i], isnull[i], + ybctid, + buildstate->backfilltime); + + buildstate->indtuples += nentries; + + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(buildstate->funcCtx); +} + +/* + * Build code for both ybginbuild and ybginbackfill. + * + * Parts copied from ginbuild. Differences are + * - don't deal with postgres storage (e.g. buffers, pages, tmpCtx) + * - additionally pass through backfill parameters + * - name memory context Ybgin + * - use yb_table_index_build_scan, not table_index_build_scan + */ +static IndexBuildResult * +ybginBuildCommon(Relation heap, Relation index, struct IndexInfo *indexInfo, + struct YbBackfillInfo *bfinfo, + struct YbPgExecOutParam *bfresult) +{ + IndexBuildResult *result; + double reltuples = 0; + YbginBuildState buildstate; + + buildstate.indtuples = 0; + + /* + * We don't need to build YB indexes during a major version upgrade, as we + * simply link the old DocDB table on master. + */ + if (!IsBinaryUpgrade) + { + initGinState(&buildstate.ginstate, index); + if (bfinfo) + buildstate.backfilltime = &bfinfo->read_time; + else + buildstate.backfilltime = NULL; + + /* + * create a temporary memory context that is used for calling + * ginExtractEntries(), and can be reset after each tuple + */ + buildstate.funcCtx = AllocSetContextCreate(CurrentMemoryContext, + "Ybgin build temporary context for user-defined function", + ALLOCSET_DEFAULT_SIZES); + + /* + * Do the heap scan. + */ + if (!bfinfo) + reltuples = yb_table_index_build_scan(heap, index, indexInfo, true, + ybginBuildCallback, + (void *) &buildstate, + NULL /* HeapScanDesc */ ); + else + reltuples = IndexBackfillHeapRangeScan(heap, index, indexInfo, + ybginBuildCallback, + (void *) &buildstate, + bfinfo, + bfresult); + + MemoryContextDelete(buildstate.funcCtx); + } + + /* + * Return statistics + */ + result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult)); + + result->heap_tuples = reltuples; + result->index_tuples = buildstate.indtuples; + + return result; +} + +IndexBuildResult * +ybginbuild(Relation heap, Relation index, struct IndexInfo *indexInfo) +{ + return ybginBuildCommon(heap, index, indexInfo, + NULL /* bfinfo */ , NULL /* bfresult */ ); +} + +void +ybginbuildempty(Relation index) +{ + YBC_LOG_WARNING("Unexpected building of empty unlogged index"); +} + +IndexBulkDeleteResult * +ybginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, + IndexBulkDeleteCallback callback, void *callback_state) +{ + YBC_LOG_WARNING("Unexpected bulk delete of index via vacuum"); + return NULL; +} + +IndexBulkDeleteResult * +ybginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) +{ + YBC_LOG_WARNING("Unexpected index cleanup via vacuum"); + return NULL; +} + +/* + * Write code for both ybgininsert and ybgindelete. + * + * Parts copied from gininsert. Differences are + * - don't copy fastupdate code since it's not supported + * - additionally handle deletes + * - name memory context Ybgin + */ +static void +ybginWrite(Relation index, Datum *values, bool *isnull, Datum ybctid, + Relation heap, struct IndexInfo *indexInfo, bool isinsert) +{ + GinState *ginstate = (GinState *) indexInfo->ii_AmCache; + MemoryContext oldCtx; + MemoryContext writeCtx; + int i; + + /* Initialize GinState cache if first call in this statement */ + if (ginstate == NULL) + { + oldCtx = MemoryContextSwitchTo(indexInfo->ii_Context); + ginstate = (GinState *) palloc(sizeof(GinState)); + initGinState(ginstate, index); + indexInfo->ii_AmCache = (void *) ginstate; + MemoryContextSwitchTo(oldCtx); + } + + writeCtx = AllocSetContextCreate(CurrentMemoryContext, + "Ybgin write temporary context", + ALLOCSET_DEFAULT_SIZES); + + oldCtx = MemoryContextSwitchTo(writeCtx); + + if (GinGetUseFastUpdate(index)) + ereport(DEBUG2, + (errmsg("fast update is not yet supported for ybgin"))); + for (i = 0; i < ginstate->origTupdesc->natts; i++) + { + if (isinsert) + ybginTupleInsert(ginstate, (OffsetNumber) (i + 1), + index, values[i], isnull[i], + ybctid, NULL /* backfilltime */ ); + else + ybginTupleDelete(ginstate, (OffsetNumber) (i + 1), + index, values[i], isnull[i], + ybctid); + } + + MemoryContextSwitchTo(oldCtx); + MemoryContextDelete(writeCtx); +} + +bool +ybgininsert(Relation index, Datum *values, bool *isnull, Datum ybctid, + Relation heap, IndexUniqueCheck checkUnique, + struct IndexInfo *indexInfo, bool shared_insert) +{ + ybginWrite(index, values, isnull, ybctid, heap, indexInfo, true /* isinsert */ ); + + /* index cannot be unique */ + return false; +} + +void +ybgindelete(Relation index, Datum *values, bool *isnull, Datum ybctid, + Relation heap, struct IndexInfo *indexInfo) +{ + ybginWrite(index, values, isnull, ybctid, heap, indexInfo, + false /* isinsert */ ); +} + +IndexBuildResult * +ybginbackfill(Relation heap, Relation index, struct IndexInfo *indexInfo, + struct YbBackfillInfo *bfinfo, struct YbPgExecOutParam *bfresult) +{ + return ybginBuildCommon(heap, index, indexInfo, bfinfo, bfresult); +} diff --git a/src/backend/bootstrap/Makefile b/src/backend/bootstrap/Makefile index 6421efb2270bd..586309dd2b7fa 100644 --- a/src/backend/bootstrap/Makefile +++ b/src/backend/bootstrap/Makefile @@ -13,6 +13,8 @@ include $(top_builddir)/src/Makefile.global override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS) OBJS = \ + yb_bootstrap.o \ + \ bootparse.o \ bootstrap.o diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index e5cf1b3d4397b..22ef665bdf725 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -32,6 +32,13 @@ #include "nodes/makefuncs.h" #include "utils/memutils.h" +/* YB includes */ +#include "bootstrap/yb_bootstrap.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "executor/ybModifyTable.h" +#include "pg_yb_utils.h" + /* * Bison doesn't allocate anything that needs to live across parser calls, @@ -85,6 +92,7 @@ static int num_columns_read = 0; { List *list; IndexElem *ielem; + IndexStmt *istmt; /* Used for YugaByte index/pkey clauses */ char *str; const char *kw; int ival; @@ -93,6 +101,7 @@ static int num_columns_read = 0; %type boot_index_params %type boot_index_param +%type Boot_YBIndex %type boot_ident %type optbootstrap optsharedrelation boot_column_nullness %type oidspec optrowtypeoid @@ -106,6 +115,7 @@ static int num_columns_read = 0; %token XDECLARE INDEX ON USING XBUILD INDICES UNIQUE XTOAST %token OBJ_ID XBOOTSTRAP XSHARED_RELATION XROWTYPE_OID %token XFORCE XNOT XNULL +%token PRIMARY YBCHECKINITDBDONE YBDECLARE %start TopLevel @@ -128,8 +138,10 @@ Boot_Query : | Boot_InsertStmt | Boot_DeclareIndexStmt | Boot_DeclareUniqueIndexStmt + | Boot_DeclarePrimaryIndexStmt | Boot_DeclareToastStmt | Boot_BuildIndsStmt + | Boot_CheckInitDbDone ; Boot_OpenStmt: @@ -149,6 +161,40 @@ Boot_CloseStmt: do_end(); } ; +Boot_YBIndex: + /* EMPTY */ { $$ = NULL; } + | YBDECLARE PRIMARY INDEX boot_ident oidspec ON boot_ident USING boot_ident + LPAREN boot_index_params RPAREN + { + IndexStmt *stmt = makeNode(IndexStmt); + + do_start(); + + stmt->idxname = $4; + stmt->relation = makeRangeVar(NULL, $7, -1); + stmt->accessMethod = $9; + stmt->tableSpace = NULL; + stmt->indexParams = $11; + stmt->options = NIL; + stmt->whereClause = NULL; + stmt->excludeOpNames = NIL; + stmt->idxcomment = NULL; + stmt->indexOid = $5; + stmt->oldNode = InvalidOid; + stmt->unique = true; + stmt->primary = true; + stmt->isconstraint = false; + stmt->deferrable = false; + stmt->initdeferred = false; + stmt->transformed = false; + stmt->concurrent = false; + stmt->if_not_exists = false; + + do_end(); + + $$ = stmt; + } + ; Boot_CreateStmt: XCREATE boot_ident oidspec optbootstrap optsharedrelation optrowtypeoid LPAREN @@ -165,7 +211,7 @@ Boot_CreateStmt: { do_end(); } - RPAREN + RPAREN Boot_YBIndex { TupleDesc tupdesc; bool shared_relation; @@ -202,6 +248,7 @@ Boot_CreateStmt: boot_reldesc = heap_create($2, PG_CATALOG_NAMESPACE, shared_relation ? GLOBALTABLESPACE_OID : 0, + InvalidOid, /* reltablegroup */ $3, InvalidOid, HEAP_TABLE_AM_OID, @@ -223,6 +270,7 @@ Boot_CreateStmt: id = heap_create_with_catalog($2, PG_CATALOG_NAMESPACE, shared_relation ? GLOBALTABLESPACE_OID : 0, + InvalidOid, /* reltablegroup */ $3, $6, InvalidOid, @@ -240,9 +288,16 @@ Boot_CreateStmt: true, false, InvalidOid, - NULL); + NULL, + false); elog(DEBUG4, "relation created with OID %u", id); } + + if (IsYugaByteEnabled()) + { + YBCCreateSysCatalogTable($2, $3, tupdesc, shared_relation, $12); + } + do_end(); } ; @@ -370,6 +425,52 @@ Boot_DeclareUniqueIndexStmt: } ; +Boot_DeclarePrimaryIndexStmt: + XDECLARE PRIMARY INDEX boot_ident oidspec ON boot_ident USING boot_ident LPAREN boot_index_params RPAREN + { + IndexStmt *stmt = makeNode(IndexStmt); + Oid relationId; + + do_start(); + + stmt->idxname = $4; + stmt->relation = makeRangeVar(NULL, $7, -1); + stmt->accessMethod = $9; + stmt->tableSpace = NULL; + stmt->indexParams = $11; + stmt->options = NIL; + stmt->whereClause = NULL; + stmt->excludeOpNames = NIL; + stmt->idxcomment = NULL; + stmt->indexOid = InvalidOid; + stmt->oldNode = InvalidOid; + stmt->unique = true; + stmt->primary = true; + stmt->isconstraint = false; + stmt->deferrable = false; + stmt->initdeferred = false; + stmt->transformed = false; + stmt->concurrent = false; + stmt->if_not_exists = false; + + /* locks and races need not concern us in bootstrap mode */ + relationId = RangeVarGetRelid(stmt->relation, NoLock, + false); + + DefineIndex(relationId, + stmt, + $5, + InvalidOid, + InvalidOid, + false, + false, + false, + true, /* skip_build */ + false); + do_end(); + } + ; + Boot_DeclareToastStmt: XDECLARE XTOAST oidspec oidspec ON boot_ident { @@ -465,6 +566,18 @@ boot_column_val: { InsertOneNull(num_columns_read++); } ; +Boot_CheckInitDbDone: + YBCHECKINITDBDONE + { + /* + * In the normal case, don't run initdb twice. During a ysql major version upgrade, + * initdb is already done for the prior version, but we need to run it for the new + * version. + */ + if (!YBIsMajorUpgradeInitDb() && YBIsInitDbAlreadyDone()) + exit(YB_INITDB_ALREADY_DONE_EXIT_CODE); + } + boot_ident: ID { $$ = $1; } | OPEN { $$ = pstrdup($1); } @@ -472,6 +585,7 @@ boot_ident: | XCREATE { $$ = pstrdup($1); } | INSERT_TUPLE { $$ = pstrdup($1); } | XDECLARE { $$ = pstrdup($1); } + | YBDECLARE { $$ = pstrdup($1); } | INDEX { $$ = pstrdup($1); } | ON { $$ = pstrdup($1); } | USING { $$ = pstrdup($1); } @@ -486,6 +600,7 @@ boot_ident: | XFORCE { $$ = pstrdup($1); } | XNOT { $$ = pstrdup($1); } | XNULL { $$ = pstrdup($1); } + | YBCHECKINITDBDONE { $$ = pstrdup($1); } ; %% diff --git a/src/backend/bootstrap/bootscanner.l b/src/backend/bootstrap/bootscanner.l index 3094ccb93f4e0..e660bd8a464e7 100644 --- a/src/backend/bootstrap/bootscanner.l +++ b/src/backend/bootstrap/bootscanner.l @@ -91,9 +91,11 @@ _null_ { return NULLVAL; } ^\#[^\n]* ; /* drop everything after "#" for comments */ declare { yylval.kw = "declare"; return XDECLARE; } +yb_declare { yylval.kw = "yb_declare"; return YBDECLARE; } /* For YugaByte pkeys */ build { yylval.kw = "build"; return XBUILD; } indices { yylval.kw = "indices"; return INDICES; } unique { yylval.kw = "unique"; return UNIQUE; } +primary { yylval.kw = "primary"; return PRIMARY; } /* For YugaByte pkeys */ index { yylval.kw = "index"; return INDEX; } on { yylval.kw = "on"; return ON; } using { yylval.kw = "using"; return USING; } @@ -102,6 +104,8 @@ FORCE { yylval.kw = "FORCE"; return XFORCE; } NOT { yylval.kw = "NOT"; return XNOT; } NULL { yylval.kw = "NULL"; return XNULL; } +yb_check_if_initdb_is_already_done { yylval.kw = "yb_check_if_initdb_is_already_done"; return YBCHECKINITDBDONE; } + {id} { yylval.str = pstrdup(yytext); return ID; diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 48ff9483af2b0..46c540f9ebe35 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -45,6 +45,15 @@ #include "utils/rel.h" #include "utils/relmapper.h" +/* YB includes */ +#include "bootstrap/yb_bootstrap.h" +#include "catalog/pg_database.h" +#include "commands/yb_cmds.h" +#include "executor/ybModifyTable.h" +#include "pg_yb_utils.h" +#include "storage/pg_shmem.h" +#include + uint32 bootstrap_data_checksum_version = 0; /* No checksum */ @@ -343,8 +352,16 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) */ InitProcess(); + /* YB note: Don't remove read permission from group/others. */ + mode_t yb_oumask; + yb_oumask = umask(0); + umask(yb_oumask & ~S_IRGRP & ~S_IROTH); + BaseInit(); + /* YB note: Restore the original umask. */ + umask(yb_oumask); + bootstrap_signals(); BootStrapXLOG(); @@ -364,6 +381,22 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) Nulls[i] = false; } + + /* + * In YugaByte we only need to create the template1 database + * (corresponding to creating the "base/1" subdir as its oid is hardcoded). + */ + if (IsYugaByteEnabled()) + { + YBCCreateDatabase(Template1DbOid, + "template1", + InvalidOid, + FirstGenbkiObjectId, + false /* colocated */ , + NULL /* retry_on_oid_collision */ , + NULL /* yb_clone_info */ ); + } + /* * Process bootstrap input. */ @@ -371,11 +404,15 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) boot_yyparse(); CommitTransactionCommand(); - /* - * We should now know about all mapped relations, so it's okay to write - * out the initial relation mapping files. - */ - RelationMapFinishBootstrap(); + /* We do not use a relation map file in YugaByte mode yet */ + if (!IsYugaByteEnabled()) + { + /* + * We should now know about all mapped relations, so it's okay to write + * out the initial relation mapping files. + */ + RelationMapFinishBootstrap(); + } /* Clean up and exit */ cleanup(); @@ -405,6 +442,8 @@ bootstrap_signals(void) pqsignal(SIGINT, SIG_DFL); pqsignal(SIGTERM, SIG_DFL); pqsignal(SIGQUIT, SIG_DFL); + if (YBIsEnabledInPostgresEnvVar() || YBIsLocalInitdbEnvVar()) + pqsignal(SIGABRT, YbRemoveSharedMemory); } /* ---------------------------------------------------------------- @@ -620,9 +659,20 @@ InsertOneTuple(void) tupDesc = CreateTupleDesc(numattr, attrtypes); tuple = heap_form_tuple(tupDesc, values, Nulls); + if (IsYugaByteEnabled()) + { + TupleTableSlot *slot = MakeSingleTupleTableSlot(tupDesc, + &TTSOpsHeapTuple); + + ExecStoreHeapTuple(tuple, slot, false); + YBCExecuteInsert(boot_reldesc, slot, ONCONFLICT_NONE); + ExecDropSingleTupleTableSlot(slot); + } + else + simple_heap_insert(boot_reldesc, tuple); + pfree(tupDesc); /* just free's tupDesc, not the attrtypes */ - simple_heap_insert(boot_reldesc, tuple); heap_freetuple(tuple); elog(DEBUG4, "row inserted"); diff --git a/src/backend/bootstrap/yb_bootstrap.c b/src/backend/bootstrap/yb_bootstrap.c new file mode 100644 index 0000000000000..f157dbbda39dc --- /dev/null +++ b/src/backend/bootstrap/yb_bootstrap.c @@ -0,0 +1,153 @@ +/*-------------------------------------------------------------------------------------------------- + * + * yb_bootstrap.c + * YB commands for creating and altering table structures and settings + * + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations + * under the License. + * + * IDENTIFICATION + * src/backend/commands/yb_bootstrap.c + * + *------------------------------------------------------------------------------ + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "catalog/catalog.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_class.h" +#include "catalog/pg_database.h" +#include "catalog/pg_type.h" +#include "catalog/yb_type.h" +#include "commands/dbcommands.h" +#include "commands/yb_cmds.h" +#include "executor/tuptable.h" +#include "executor/ybExpr.h" +#include "miscadmin.h" +#include "parser/parser.h" +#include "pg_yb_utils.h" +#include "utils/rel.h" +#include "utils/relcache.h" +#include "yb/yql/pggate/ybc_pggate.h" + +static void +YBCAddSysCatalogColumn(YbcPgStatement yb_stmt, + IndexStmt *pkey_idx, + const char *attname, + int attnum, + Oid type_id, + int32 typmod, + bool key) +{ + + ListCell *lc; + bool is_key = false; + const YbcPgTypeEntity *col_type = YbDataTypeFromOidMod(attnum, type_id); + + if (pkey_idx) + { + foreach(lc, pkey_idx->indexParams) + { + IndexElem *elem = lfirst(lc); + + if (strcmp(elem->name, attname) == 0) + { + is_key = true; + } + } + } + + /* + * We will call this twice, first for key columns, then for regular + * columns to handle any re-ordering. So only adding the if matching the + * is_key property. + */ + if (key == is_key) + { + HandleYBStatus(YBCPgCreateTableAddColumn(yb_stmt, + attname, + attnum, + col_type, + false /* is_hash */ , + is_key, + false /* is_desc */ , + false /* is_nulls_first */ )); + } +} + +static void +YBCAddSysCatalogColumns(YbcPgStatement yb_stmt, + TupleDesc tupdesc, + IndexStmt *pkey_idx, + const bool key) +{ + for (int attno = 0; attno < tupdesc->natts; attno++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, attno); + + YBCAddSysCatalogColumn(yb_stmt, + pkey_idx, + attr->attname.data, + attr->attnum, + attr->atttypid, + attr->atttypmod, + key); + } +} + +void +YBCCreateSysCatalogTable(const char *table_name, + Oid table_oid, + TupleDesc tupdesc, + bool is_shared_relation, + IndexStmt *pkey_idx) +{ + /* Database and schema are fixed when running inidb. */ + Assert(IsBootstrapProcessingMode()); + char *db_name = "template1"; + char *schema_name = "pg_catalog"; + YbcPgStatement yb_stmt = NULL; + YbcPgYbrowidMode ybrowid_mode = (pkey_idx == NULL + ? PG_YBROWID_MODE_RANGE + : PG_YBROWID_MODE_NONE); + + HandleYBStatus(YBCPgNewCreateTable(db_name, + schema_name, + table_name, + Template1DbOid, + table_oid, + is_shared_relation, + true /* is_sys_catalog_table */ , + false, /* if_not_exists */ + ybrowid_mode, + true, /* is_colocated_via_database */ + InvalidOid /* tablegroup_oid */ , + InvalidOid /* colocation_id */ , + InvalidOid /* tablespace_oid */ , + false /* is_matview */ , + InvalidOid /* pg_table_oid */ , + InvalidOid /* old_relfilenode_oid */ , + false /* is_truncate */ , + &yb_stmt)); + + /* Add all key columns first, then the regular columns */ + if (pkey_idx != NULL) + { + YBCAddSysCatalogColumns(yb_stmt, tupdesc, pkey_idx, /* key */ true); + } + YBCAddSysCatalogColumns(yb_stmt, tupdesc, pkey_idx, /* key */ false); + + HandleYBStatus(YBCPgExecCreateTable(yb_stmt)); +} diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 7304e2b8e95d1..e48731b8ff872 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -13,6 +13,8 @@ subdir = src/backend/catalog top_builddir = ../../.. include $(top_builddir)/src/Makefile.global +SUBDIRS = yb_catalog yb_logical_client + OBJS = \ aclchk.o \ catalog.o \ @@ -72,7 +74,10 @@ CATALOG_HEADERS := \ pg_collation.h pg_parameter_acl.h pg_partitioned_table.h \ pg_range.h pg_transform.h \ pg_sequence.h pg_publication.h pg_publication_namespace.h \ - pg_publication_rel.h pg_subscription.h pg_subscription_rel.h + pg_publication_rel.h pg_subscription.h pg_subscription_rel.h \ + pg_yb_catalog_version.h pg_yb_migration.h \ + pg_yb_profile.h pg_yb_role_profile.h pg_yb_tablegroup.h pg_yb_logical_client_version.h \ + pg_yb_invalidation_messages.h GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h @@ -84,14 +89,14 @@ POSTGRES_BKI_DATA = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_cast.dat pg_class.dat pg_collation.dat pg_conversion.dat \ pg_database.dat pg_language.dat \ pg_namespace.dat pg_opclass.dat pg_operator.dat pg_opfamily.dat \ - pg_proc.dat pg_range.dat pg_tablespace.dat \ + pg_proc.dat pg_range.dat pg_tablespace.dat pg_yb_tablegroup.dat \ pg_ts_config.dat pg_ts_config_map.dat pg_ts_dict.dat pg_ts_parser.dat \ - pg_ts_template.dat pg_type.dat \ + pg_ts_template.dat pg_type.dat pg_yb_catalog_version.dat pg_yb_migration.dat pg_yb_logical_client_version.dat \ ) all: distprep generated-header-symlinks -distprep: bki-stamp +distprep: bki-stamp yb_bki-stamp .PHONY: generated-header-symlinks @@ -110,11 +115,16 @@ bki-stamp: genbki.pl Catalog.pm $(POSTGRES_BKI_SRCS) $(POSTGRES_BKI_DATA) $(top_ --set-version=$(MAJORVERSION) $(POSTGRES_BKI_SRCS) touch $@ +yb_bki-stamp: yb_genbki.pl Catalog.pm $(POSTGRES_BKI_SRCS) $(POSTGRES_BKI_DATA) $(top_srcdir)/configure.ac + $(PERL) $< --include-path=$(top_srcdir)/src/include/ \ + --set-version=$(MAJORVERSION) $(POSTGRES_BKI_SRCS) + touch $@ + # The generated headers must all be symlinked into builddir/src/include/, # using absolute links for the reasons explained in src/backend/Makefile. # We use header-stamp to record that we've done this because the symlinks # themselves may appear older than bki-stamp. -$(top_builddir)/src/include/catalog/header-stamp: bki-stamp +$(top_builddir)/src/include/catalog/header-stamp: bki-stamp yb_bki-stamp prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \ cd '$(dir $@)' && for file in $(GENERATED_HEADERS); do \ rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \ @@ -123,11 +133,14 @@ $(top_builddir)/src/include/catalog/header-stamp: bki-stamp # Note: installation of generated headers is handled elsewhere .PHONY: install-data -install-data: bki-stamp installdirs +install-data: bki-stamp yb_bki-stamp installdirs $(INSTALL_DATA) $(call vpathsearch,postgres.bki) '$(DESTDIR)$(datadir)/postgres.bki' + $(INSTALL_DATA) $(call vpathsearch,yb_postgres.bki) '$(DESTDIR)$(datadir)/yb_postgres.bki' $(INSTALL_DATA) $(call vpathsearch,system_constraints.sql) '$(DESTDIR)$(datadir)/system_constraints.sql' $(INSTALL_DATA) $(srcdir)/system_functions.sql '$(DESTDIR)$(datadir)/system_functions.sql' + $(INSTALL_DATA) $(srcdir)/yb_system_functions.sql '$(DESTDIR)$(datadir)/yb_system_functions.sql' $(INSTALL_DATA) $(srcdir)/system_views.sql '$(DESTDIR)$(datadir)/system_views.sql' + $(INSTALL_DATA) $(srcdir)/yb_system_views.sql '$(DESTDIR)$(datadir)/yb_system_views.sql' $(INSTALL_DATA) $(srcdir)/information_schema.sql '$(DESTDIR)$(datadir)/information_schema.sql' $(INSTALL_DATA) $(srcdir)/sql_features.txt '$(DESTDIR)$(datadir)/sql_features.txt' $(INSTALL_DATA) $(srcdir)/fix-CVE-2024-4317.sql '$(DESTDIR)$(datadir)/fix-CVE-2024-4317.sql' @@ -137,11 +150,11 @@ installdirs: .PHONY: uninstall-data uninstall-data: - rm -f $(addprefix '$(DESTDIR)$(datadir)'/, postgres.bki system_constraints.sql system_functions.sql system_views.sql information_schema.sql sql_features.txt fix-CVE-2024-4317.sql) + rm -f $(addprefix '$(DESTDIR)$(datadir)'/, postgres.bki system_constraints.sql system_functions.sql system_views.sql information_schema.sql sql_features.txt fix-CVE-2024-4317.sql yb_system_functions.sql yb_system_views.sql) # postgres.bki, system_constraints.sql, and the generated headers are # in the distribution tarball, so they are not cleaned here. clean: maintainer-clean: clean - rm -f bki-stamp postgres.bki system_constraints.sql $(GENERATED_HEADERS) + rm -f bki-stamp yb_bki-stamp postgres.bki yb_postgres.bki system_constraints.sql $(GENERATED_HEADERS) diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 81a711691a5e4..7ccb39face662 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -79,6 +79,12 @@ #include "utils/rel.h" #include "utils/syscache.h" +/* YB includes */ +#include "catalog/pg_yb_catalog_version.h" +#include "catalog/pg_yb_tablegroup.h" +#include "commands/yb_tablegroup.h" +#include "pg_yb_utils.h" + /* * Internal format used by ALTER DEFAULT PRIVILEGES. */ @@ -145,6 +151,10 @@ static void recordExtensionInitPriv(Oid objoid, Oid classoid, int objsubid, static void recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid, Acl *new_acl); +/* YB declarations */ +static void ExecGrant_Tablegroup(InternalGrant *grantStmt); +static bool YbCheckAclCopiesEqual(Acl *old_acl, Acl *new_acl); + /* * If is_grant is true, adds the given privileges for the list of @@ -247,6 +257,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs, case OBJECT_SCHEMA: whole_mask = ACL_ALL_RIGHTS_SCHEMA; break; + case OBJECT_YBTABLEGROUP: + whole_mask = ACL_ALL_RIGHTS_TABLEGROUP; + break; case OBJECT_TABLESPACE: whole_mask = ACL_ALL_RIGHTS_TABLESPACE; break; @@ -490,6 +503,10 @@ ExecuteGrantStmt(GrantStmt *stmt) all_privileges = ACL_ALL_RIGHTS_FUNCTION; errormsg = gettext_noop("invalid privilege type %s for routine"); break; + case OBJECT_YBTABLEGROUP: + all_privileges = ACL_ALL_RIGHTS_TABLEGROUP; + errormsg = gettext_noop("invalid privilege type %s for tablegroup"); + break; case OBJECT_TABLESPACE: all_privileges = ACL_ALL_RIGHTS_TABLESPACE; errormsg = gettext_noop("invalid privilege type %s for tablespace"); @@ -609,6 +626,9 @@ ExecGrantStmt_oids(InternalGrant *istmt) case OBJECT_SCHEMA: ExecGrant_Namespace(istmt); break; + case OBJECT_YBTABLEGROUP: + ExecGrant_Tablegroup(istmt); + break; case OBJECT_TABLESPACE: ExecGrant_Tablespace(istmt); break; @@ -746,6 +766,16 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant) objects = lappend_oid(objects, routid); } break; + case OBJECT_YBTABLEGROUP: + foreach(cell, objnames) + { + char *grpname = strVal(lfirst(cell)); + Oid grpoid; + + grpoid = get_tablegroup_oid(grpname, false); + objects = lappend_oid(objects, grpoid); + } + break; case OBJECT_TABLESPACE: foreach(cell, objnames) { @@ -1045,6 +1075,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s all_privileges = ACL_ALL_RIGHTS_FUNCTION; errormsg = gettext_noop("invalid privilege type %s for routine"); break; + case OBJECT_YBTABLEGROUP: + all_privileges = ACL_ALL_RIGHTS_TABLEGROUP; + errormsg = gettext_noop("invalid privilege type %s for tablegroup"); + break; case OBJECT_TYPE: all_privileges = ACL_ALL_RIGHTS_TYPE; errormsg = gettext_noop("invalid privilege type %s for type"); @@ -1250,6 +1284,16 @@ SetDefaultACL(InternalDefaultACL *iacls) this_privileges = ACL_ALL_RIGHTS_SCHEMA; break; + case OBJECT_YBTABLEGROUP: + if (OidIsValid(iacls->nspid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_GRANT_OPERATION), + errmsg("cannot use IN SCHEMA clause when using GRANT/REVOKE ON TABLEGROUPS"))); + objtype = DEFACLOBJ_TABLEGROUP; + if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS) + this_privileges = ACL_ALL_RIGHTS_TABLEGROUP; + break; + default: elog(ERROR, "unrecognized objtype: %d", (int) iacls->objtype); @@ -1483,6 +1527,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid) case DEFACLOBJ_NAMESPACE: iacls.objtype = OBJECT_SCHEMA; break; + case DEFACLOBJ_TABLEGROUP: + iacls.objtype = OBJECT_YBTABLEGROUP; + break; default: /* Shouldn't get here */ elog(ERROR, "unexpected default ACL type: %d", @@ -1531,6 +1578,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid) case NamespaceRelationId: istmt.objtype = OBJECT_SCHEMA; break; + case YbTablegroupRelationId: + istmt.objtype = OBJECT_YBTABLEGROUP; + yb_switch_fallthrough(); case TableSpaceRelationId: istmt.objtype = OBJECT_TABLESPACE; break; @@ -1730,6 +1780,13 @@ ExecGrant_Attribute(InternalGrant *istmt, Oid relOid, const char *relname, relname, attnum, NameStr(pg_attribute_tuple->attname)); + /* + * YB: The original old_acl is pfree'd by merge_acl_with_grant. If the + * original column acl value is null, we cannot skip catalog update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + /* * Generate new ACL. */ @@ -1739,6 +1796,16 @@ ExecGrant_Attribute(InternalGrant *istmt, Oid relOid, const char *relname, col_privileges, grantorId, ownerId); + /* YB: Skip catalog update if there is no ACL change. */ + if (IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl))) + { + pfree(new_acl); + ReleaseSysCache(attr_tuple); + return; + } + /* * We need the members of both old and new ACLs so we can correct the * shared dependency information. @@ -2009,6 +2076,14 @@ ExecGrant_Relation(InternalGrant *istmt) NameStr(pg_class_tuple->relname), 0, NULL); + /* + * YB: The original old_acl is pfree'd by merge_acl_with_grant. If + * the original column acl value is null, we cannot skip catalog + * update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + /* * Generate new ACL. */ @@ -2021,35 +2096,45 @@ ExecGrant_Relation(InternalGrant *istmt) grantorId, ownerId); - /* - * We need the members of both old and new ACLs so we can correct - * the shared dependency information. - */ - nnewmembers = aclmembers(new_acl, &newmembers); - - /* finished building new ACL value, now insert it */ - MemSet(values, 0, sizeof(values)); - MemSet(nulls, false, sizeof(nulls)); - MemSet(replaces, false, sizeof(replaces)); + /* YB: Skip catalog update if there is no ACL change. */ + if (!(IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl)))) + { + /* + * We need the members of both old and new ACLs so we can correct + * the shared dependency information. + */ + nnewmembers = aclmembers(new_acl, &newmembers); - replaces[Anum_pg_class_relacl - 1] = true; - values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl); + /* finished building new ACL value, now insert it */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + MemSet(replaces, false, sizeof(replaces)); - newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), - values, nulls, replaces); + replaces[Anum_pg_class_relacl - 1] = true; + values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl); - CatalogTupleUpdate(relation, &newtuple->t_self, newtuple); - UnlockTuple(relation, &tuple->t_self, InplaceUpdateTupleLock); + newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), + values, nulls, replaces); - /* Update initial privileges for extensions */ - recordExtensionInitPriv(relOid, RelationRelationId, 0, new_acl); + CatalogTupleUpdate(relation, &newtuple->t_self, newtuple); + UnlockTuple(relation, &tuple->t_self, InplaceUpdateTupleLock); - /* Update the shared dependency ACL info */ - updateAclDependencies(RelationRelationId, relOid, 0, - ownerId, - noldmembers, oldmembers, - nnewmembers, newmembers); + /* Update initial privileges for extensions */ + recordExtensionInitPriv(relOid, RelationRelationId, 0, new_acl); + /* Update the shared dependency ACL info */ + updateAclDependencies(RelationRelationId, relOid, 0, + ownerId, + noldmembers, oldmembers, + nnewmembers, newmembers); + } + else + { + /* YB: ACL unchanged, but we still need to release the tuple lock */ + UnlockTuple(relation, &tuple->t_self, InplaceUpdateTupleLock); + } pfree(new_acl); } else @@ -2206,6 +2291,13 @@ ExecGrant_Database(InternalGrant *istmt) NameStr(pg_database_tuple->datname), 0, NULL); + /* + * YB? The original old_acl is pfree'd by merge_acl_with_grant. If the + * original column acl value is null, we cannot skip catalog update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + /* * Generate new ACL. */ @@ -2214,6 +2306,16 @@ ExecGrant_Database(InternalGrant *istmt) istmt->grantees, this_privileges, grantorId, ownerId); + /* YB: Skip catalog update if there is no ACL change. */ + if (IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl))) + { + ReleaseSysCache(tuple); + pfree(new_acl); + continue; + } + /* * We need the members of both old and new ACLs so we can correct the * shared dependency information. @@ -2329,6 +2431,13 @@ ExecGrant_Fdw(InternalGrant *istmt) NameStr(pg_fdw_tuple->fdwname), 0, NULL); + /* + * YB: The original old_acl is pfree'd by merge_acl_with_grant. If the + * original column acl value is null, we cannot skip catalog update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + /* * Generate new ACL. */ @@ -2337,6 +2446,16 @@ ExecGrant_Fdw(InternalGrant *istmt) istmt->grantees, this_privileges, grantorId, ownerId); + /* YB: Skip catalog update if there is no ACL change. */ + if (IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl))) + { + ReleaseSysCache(tuple); + pfree(new_acl); + continue; + } + /* * We need the members of both old and new ACLs so we can correct the * shared dependency information. @@ -2455,6 +2574,13 @@ ExecGrant_ForeignServer(InternalGrant *istmt) NameStr(pg_server_tuple->srvname), 0, NULL); + /* + * YB: The original old_acl is pfree'd by merge_acl_with_grant. If the + * original column acl value is null, we cannot skip catalog update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + /* * Generate new ACL. */ @@ -2463,6 +2589,16 @@ ExecGrant_ForeignServer(InternalGrant *istmt) istmt->grantees, this_privileges, grantorId, ownerId); + /* YB: Skip catalog update if there is no ACL change. */ + if (IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl))) + { + ReleaseSysCache(tuple); + pfree(new_acl); + continue; + } + /* * We need the members of both old and new ACLs so we can correct the * shared dependency information. @@ -2579,6 +2715,13 @@ ExecGrant_Function(InternalGrant *istmt) NameStr(pg_proc_tuple->proname), 0, NULL); + /* + * YB: The original old_acl is pfree'd by merge_acl_with_grant. If the + * original column acl value is null, we cannot skip catalog update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + /* * Generate new ACL. */ @@ -2587,6 +2730,16 @@ ExecGrant_Function(InternalGrant *istmt) istmt->grantees, this_privileges, grantorId, ownerId); + /* YB: Skip catalog update if there is no ACL change. */ + if (IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl))) + { + ReleaseSysCache(tuple); + pfree(new_acl); + continue; + } + /* * We need the members of both old and new ACLs so we can correct the * shared dependency information. @@ -2710,6 +2863,13 @@ ExecGrant_Language(InternalGrant *istmt) NameStr(pg_language_tuple->lanname), 0, NULL); + /* + * YB: The original old_acl is pfree'd by merge_acl_with_grant. If the + * original column acl value is null, we cannot skip catalog update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + /* * Generate new ACL. */ @@ -2718,6 +2878,16 @@ ExecGrant_Language(InternalGrant *istmt) istmt->grantees, this_privileges, grantorId, ownerId); + /* YB: Skip catalog update if there is no ACL change. */ + if (IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl))) + { + ReleaseSysCache(tuple); + pfree(new_acl); + continue; + } + /* * We need the members of both old and new ACLs so we can correct the * shared dependency information. @@ -2848,6 +3018,13 @@ ExecGrant_Largeobject(InternalGrant *istmt) loid, grantorId, OBJECT_LARGEOBJECT, loname, 0, NULL); + /* + * YB: The original old_acl is pfree'd by merge_acl_with_grant. If the + * original column acl value is null, we cannot skip catalog update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + /* * Generate new ACL. */ @@ -2856,6 +3033,15 @@ ExecGrant_Largeobject(InternalGrant *istmt) istmt->grantees, this_privileges, grantorId, ownerId); + /* YB: Skip catalog update if there is no ACL change. */ + if (IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl))) + { + pfree(new_acl); + continue; + } + /* * We need the members of both old and new ACLs so we can correct the * shared dependency information. @@ -2974,6 +3160,13 @@ ExecGrant_Namespace(InternalGrant *istmt) NameStr(pg_namespace_tuple->nspname), 0, NULL); + /* + * YB: The original old_acl is pfree'd by merge_acl_with_grant. If the + * original column acl value is null, we cannot skip catalog update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + /* * Generate new ACL. */ @@ -2982,6 +3175,16 @@ ExecGrant_Namespace(InternalGrant *istmt) istmt->grantees, this_privileges, grantorId, ownerId); + /* YB: Skip catalog update if there is no ACL change. */ + if (IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl))) + { + ReleaseSysCache(tuple); + pfree(new_acl); + continue; + } + /* * We need the members of both old and new ACLs so we can correct the * shared dependency information. @@ -3021,6 +3224,149 @@ ExecGrant_Namespace(InternalGrant *istmt) table_close(relation, RowExclusiveLock); } +static void +ExecGrant_Tablegroup(InternalGrant *istmt) +{ + Relation relation; + ListCell *cell; + + if (MyDatabaseColocated) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot set privileges of an implicit tablegroup " + "in a colocated database"))); + + if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS) + istmt->privileges = ACL_ALL_RIGHTS_TABLEGROUP; + + relation = table_open(YbTablegroupRelationId, RowExclusiveLock); + + foreach(cell, istmt->objects) + { + Oid grpId = lfirst_oid(cell); + Form_pg_yb_tablegroup pg_yb_tablegroup_tuple; + Datum aclDatum; + bool isNull; + AclMode avail_goptions; + AclMode this_privileges; + Acl *old_acl; + Acl *new_acl; + Oid grantorId; + Oid ownerId; + HeapTuple newtuple; + Datum values[Natts_pg_yb_tablegroup]; + bool nulls[Natts_pg_yb_tablegroup]; + bool replaces[Natts_pg_yb_tablegroup]; + int noldmembers; + int nnewmembers; + Oid *oldmembers; + Oid *newmembers; + HeapTuple tuple; + + tuple = SearchSysCache1(YBTABLEGROUPOID, ObjectIdGetDatum(grpId)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for tablegroup %u", grpId); + + pg_yb_tablegroup_tuple = (Form_pg_yb_tablegroup) GETSTRUCT(tuple); + + /* + * Get owner ID and working copy of existing ACL. If there's no ACL, + * substitute the proper default. + */ + ownerId = pg_yb_tablegroup_tuple->grpowner; + aclDatum = heap_getattr(tuple, Anum_pg_yb_tablegroup_grpacl, + RelationGetDescr(relation), &isNull); + if (isNull) + { + old_acl = acldefault(OBJECT_YBTABLEGROUP, ownerId); + /* There are no old member roles according to the catalogs */ + noldmembers = 0; + oldmembers = NULL; + } + else + { + old_acl = DatumGetAclPCopy(aclDatum); + /* Get the roles mentioned in the existing ACL */ + noldmembers = aclmembers(old_acl, &oldmembers); + } + + /* Determine ID to do the grant as, and available grant options */ + select_best_grantor(GetUserId(), istmt->privileges, + old_acl, ownerId, + &grantorId, &avail_goptions); + + /* + * Restrict the privileges to what we can actually grant, and emit the + * standards-mandated warning and error messages. + */ + this_privileges = + restrict_and_check_grant(istmt->is_grant, avail_goptions, + istmt->all_privs, istmt->privileges, + grpId, grantorId, OBJECT_YBTABLEGROUP, + NameStr(pg_yb_tablegroup_tuple->grpname), + 0, NULL); + + /* + * YB: The original old_acl is pfree'd by merge_acl_with_grant. If the + * original column acl value is null, we cannot skip catalog update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + + /* + * Generate new ACL. + */ + new_acl = merge_acl_with_grant(old_acl, istmt->is_grant, + istmt->grant_option, istmt->behavior, + istmt->grantees, this_privileges, + grantorId, ownerId); + + /* YB: Skip catalog update if there is no ACL change. */ + if (IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl))) + { + ReleaseSysCache(tuple); + pfree(new_acl); + continue; + } + + /* + * We need the members of both old and new ACLs so we can correct the + * shared dependency information. + */ + nnewmembers = aclmembers(new_acl, &newmembers); + + /* finished building new ACL value, now insert it */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + MemSet(replaces, false, sizeof(replaces)); + + replaces[Anum_pg_yb_tablegroup_grpacl - 1] = true; + values[Anum_pg_yb_tablegroup_grpacl - 1] = PointerGetDatum(new_acl); + + newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, + nulls, replaces); + + CatalogTupleUpdate(relation, &newtuple->t_self, newtuple); + + /* Update the shared dependency ACL info */ + updateAclDependencies(YbTablegroupRelationId, + ((Form_pg_yb_tablegroup) GETSTRUCT(tuple))->oid, + 0, ownerId, noldmembers, oldmembers, nnewmembers, + newmembers); + + ReleaseSysCache(tuple); + + pfree(new_acl); + + /* prevent error when processing duplicate objects */ + CommandCounterIncrement(); + } + + table_close(relation, RowExclusiveLock); +} + static void ExecGrant_Tablespace(InternalGrant *istmt) { @@ -3098,6 +3444,13 @@ ExecGrant_Tablespace(InternalGrant *istmt) NameStr(pg_tablespace_tuple->spcname), 0, NULL); + /* + * YB: The original old_acl is pfree'd by merge_acl_with_grant. If the + * original column acl value is null, we cannot skip catalog update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + /* * Generate new ACL. */ @@ -3106,6 +3459,16 @@ ExecGrant_Tablespace(InternalGrant *istmt) istmt->grantees, this_privileges, grantorId, ownerId); + /* YB: Skip catalog update if there is no ACL change. */ + if (IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl))) + { + ReleaseSysCache(tuple); + pfree(new_acl); + continue; + } + /* * We need the members of both old and new ACLs so we can correct the * shared dependency information. @@ -3232,6 +3595,13 @@ ExecGrant_Type(InternalGrant *istmt) NameStr(pg_type_tuple->typname), 0, NULL); + /* + * YB: The original old_acl is pfree'd by merge_acl_with_grant. If the + * original column acl value is null, we cannot skip catalog update. + */ + Acl *yb_copy_of_old_acl = + (IsYugaByteEnabled() && !isNull) ? aclcopy(old_acl) : NULL; + /* * Generate new ACL. */ @@ -3240,6 +3610,16 @@ ExecGrant_Type(InternalGrant *istmt) istmt->grantees, this_privileges, grantorId, ownerId); + /* YB: Skip catalog update if there is no ACL change. */ + if (IsYugaByteEnabled() && + yb_copy_of_old_acl && + YbCheckAclCopiesEqual(yb_copy_of_old_acl, aclcopy(new_acl))) + { + ReleaseSysCache(tuple); + pfree(new_acl); + continue; + } + /* * We need the members of both old and new ACLs so we can correct the * shared dependency information. @@ -3383,7 +3763,7 @@ ExecGrant_Parameter(InternalGrant *istmt) */ if (aclequal(new_acl, acldefault(istmt->objtype, ownerId))) { - CatalogTupleDelete(relation, &tuple->t_self); + CatalogTupleDelete(relation, tuple); } else { @@ -3615,9 +3995,15 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_TABLE: msg = gettext_noop("permission denied for table %s"); break; + case OBJECT_YBTABLEGROUP: + msg = gettext_noop("permission denied for tablegroup %s"); + break; case OBJECT_TABLESPACE: msg = gettext_noop("permission denied for tablespace %s"); break; + case OBJECT_YBPROFILE: + msg = gettext_noop("permission denied for profile %s"); + break; case OBJECT_TSCONFIGURATION: msg = gettext_noop("permission denied for text search configuration %s"); break; @@ -3747,6 +4133,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_STATISTIC_EXT: msg = gettext_noop("must be owner of statistics object %s"); break; + case OBJECT_YBTABLEGROUP: + msg = gettext_noop("must be owner of tablegroup %s"); + break; case OBJECT_TABLESPACE: msg = gettext_noop("must be owner of tablespace %s"); break; @@ -3782,6 +4171,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_PARAMETER_ACL: case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_PUBLICATION_REL: + case OBJECT_YBPROFILE: case OBJECT_ROLE: case OBJECT_TRANSFORM: case OBJECT_TSPARSER: @@ -3874,6 +4264,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid, elog(ERROR, "grantable rights not supported for statistics objects"); /* not reached, but keep compiler quiet */ return ACL_NO_RIGHTS; + case OBJECT_YBTABLEGROUP: + return pg_tablegroup_aclmask(table_oid, roleid, mask, how); case OBJECT_TABLESPACE: return pg_tablespace_aclmask(table_oid, roleid, mask, how); case OBJECT_FDW: @@ -4086,17 +4478,20 @@ pg_class_aclmask_ext(Oid table_oid, Oid roleid, AclMode mask, * As of 7.4 we have some updatable system views; those shouldn't be * protected in this way. Assume the view rules can take care of * themselves. ACL_USAGE is if we ever have system sequences. + * + * YB: yb_db_admin is allowed to update pg_yb_catalog_version. */ if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) && IsSystemClass(table_oid, classForm) && classForm->relkind != RELKIND_VIEW && - !superuser_arg(roleid)) + !superuser_arg(roleid) && + !(IsYbDbAdminUser(roleid) && table_oid == YBCatalogVersionRelationId)) mask &= ~(ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE); /* * Otherwise, superusers bypass all permission-checking. */ - if (superuser_arg(roleid)) + if (superuser_arg(roleid) || IsYbDbAdminUser(roleid)) { ReleaseSysCache(tuple); return mask; @@ -4535,7 +4930,8 @@ pg_namespace_aclmask(Oid nsp_oid, Oid roleid, Oid ownerId; /* Superusers bypass all permission checking. */ - if (superuser_arg(roleid)) + if (superuser_arg(roleid) || IsYbDbAdminUser(roleid) || + (IsYbExtensionUser(roleid) && creating_extension)) return mask; /* @@ -4613,10 +5009,10 @@ pg_namespace_aclmask(Oid nsp_oid, Oid roleid, } /* - * Exported routine for examining a user's privileges for a tablespace + * Exported routine for examining a user's privileges for a tablegroup */ AclMode -pg_tablespace_aclmask(Oid spc_oid, Oid roleid, +pg_tablegroup_aclmask(Oid grp_oid, Oid roleid, AclMode mask, AclMaskHow how) { AclMode result; @@ -4626,10 +5022,73 @@ pg_tablespace_aclmask(Oid spc_oid, Oid roleid, Acl *acl; Oid ownerId; + /* First check that the pg_tablegroup catalog actually exists. */ + if (!YbTablegroupCatalogExists) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("tablegroup system catalog does not exist"))); + } + /* Superusers bypass all permission checking. */ if (superuser_arg(roleid)) return mask; + /* + * Get the tablegroup's ACL from pg_yb_tablegroup + */ + tuple = SearchSysCache1(YBTABLEGROUPOID, ObjectIdGetDatum(grp_oid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("tablegroup with OID %u does not exist", grp_oid))); + + ownerId = ((Form_pg_yb_tablegroup) GETSTRUCT(tuple))->grpowner; + + aclDatum = SysCacheGetAttr(YBTABLEGROUPOID, tuple, + Anum_pg_yb_tablegroup_grpacl, &isNull); + + if (isNull) + { + /* No ACL, so build default ACL */ + acl = acldefault(OBJECT_YBTABLEGROUP, ownerId); + aclDatum = (Datum) 0; + } + else + { + /* detoast ACL if necessary */ + acl = DatumGetAclP(aclDatum); + } + + result = aclmask(acl, roleid, ownerId, mask, how); + + /* if we have a detoasted copy, free it */ + if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) + pfree(acl); + + ReleaseSysCache(tuple); + + return result; +} + +/* + * Exported routine for examining a user's privileges for a tablespace + */ +AclMode +pg_tablespace_aclmask(Oid spc_oid, Oid roleid, + AclMode mask, AclMaskHow how) +{ + AclMode result; + HeapTuple tuple; + Datum aclDatum; + bool isNull; + Acl *acl; + Oid ownerId; + + /* Superusers and yb_db_admin role bypass all permission checking. */ + if (superuser_arg(roleid) || IsYbDbAdminUser(roleid)) + return mask; + /* * Get the tablespace's ACL from pg_tablespace */ @@ -5119,6 +5578,18 @@ pg_namespace_aclcheck(Oid nsp_oid, Oid roleid, AclMode mode) return ACLCHECK_NO_PRIV; } +/* + * Exported routine for checking a user's access privileges to a tablegroup + */ +AclResult +pg_tablegroup_aclcheck(Oid grp_oid, Oid roleid, AclMode mode) +{ + if (pg_tablegroup_aclmask(grp_oid, roleid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + /* * Exported routine for checking a user's access privileges to a tablespace */ @@ -5371,6 +5842,41 @@ pg_namespace_ownercheck(Oid nsp_oid, Oid roleid) return has_privs_of_role(roleid, ownerId); } +/* + * Ownership check for a tablegroup (specified by OID). + */ +bool +pg_tablegroup_ownercheck(Oid grp_oid, Oid roleid) +{ + HeapTuple grptuple; + Oid grpowner; + + /* Ensure that the pg_yb_tablegroup catalog actually exists. */ + if (!YbTablegroupCatalogExists) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("tablegroup system catalog does not exist"))); + } + + /* Superusers and yb_db_admin role bypass all permission checking. */ + if (superuser_arg(roleid) || IsYbDbAdminUser(GetUserId())) + return true; + + /* Search syscache for the tablegroup */ + grptuple = SearchSysCache1(YBTABLEGROUPOID, ObjectIdGetDatum(grp_oid)); + if (!HeapTupleIsValid(grptuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("tablegroup with OID %u does not exist", grp_oid))); + + grpowner = ((Form_pg_yb_tablegroup) GETSTRUCT(grptuple))->grpowner; + + ReleaseSysCache(grptuple); + + return has_privs_of_role(roleid, grpowner); +} + /* * Ownership check for a tablespace (specified by OID). */ @@ -5380,8 +5886,8 @@ pg_tablespace_ownercheck(Oid spc_oid, Oid roleid) HeapTuple spctuple; Oid spcowner; - /* Superusers bypass all permission checking. */ - if (superuser_arg(roleid)) + /* Superusers and yb_db_admin role bypass all permission checking. */ + if (superuser_arg(roleid) || IsYbDbAdminUser(roleid)) return true; /* Search syscache for pg_tablespace */ @@ -5569,8 +6075,8 @@ pg_event_trigger_ownercheck(Oid et_oid, Oid roleid) HeapTuple tuple; Oid ownerId; - /* Superusers bypass all permission checking. */ - if (superuser_arg(roleid)) + /* Superusers and yb_db_admin bypass all permission checking. */ + if (superuser_arg(roleid) || IsYbDbAdminUser(roleid)) return true; tuple = SearchSysCache1(EVENTTRIGGEROID, ObjectIdGetDatum(et_oid)); @@ -5914,6 +6420,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid) defaclobjtype = DEFACLOBJ_NAMESPACE; break; + case OBJECT_YBTABLEGROUP: + defaclobjtype = DEFACLOBJ_TABLEGROUP; + break; + default: return NULL; } @@ -6446,7 +6956,7 @@ recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid, Acl *new_a else { /* new_acl is NULL, so delete the entry we found. */ - CatalogTupleDelete(relation, &oldtuple->t_self); + CatalogTupleDelete(relation, oldtuple); } } else @@ -6488,3 +6998,23 @@ recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid, Acl *new_a table_close(relation, RowExclusiveLock); } + +/* + * Returns true if the two copies of ACLs are equal. + * NB: both old_acl and new_acl are pfree'd. + */ +static bool +YbCheckAclCopiesEqual(Acl *old_acl, Acl *new_acl) +{ + Assert(IsYugaByteEnabled()); + Assert(old_acl); + Assert(new_acl); + + aclitemsort(old_acl); + aclitemsort(new_acl); + bool is_equal = aclequal(old_acl, new_acl); + + pfree(old_acl); + pfree(new_acl); + return is_equal; +} diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c index 56defca760d1e..7dd7bab00c73d 100644 --- a/src/backend/catalog/catalog.c +++ b/src/backend/catalog/catalog.c @@ -49,6 +49,22 @@ #include "utils/snapmgr.h" #include "utils/syscache.h" +/* YB includes */ +#include "access/heapam.h" +#include "access/htup_details.h" +#include "catalog/pg_yb_catalog_version.h" +#include "catalog/pg_yb_invalidation_messages.h" +#include "catalog/pg_yb_logical_client_version.h" +#include "catalog/pg_yb_profile.h" +#include "catalog/pg_yb_role_profile.h" +#include "catalog/pg_yb_tablegroup.h" +#include "commands/defrem.h" +#include "executor/spi.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/syscache.h" +#include "yb/yql/pggate/ybc_gflags.h" + /* * Parameters to determine when to emit a log message in * GetNewOidWithIndex() @@ -216,6 +232,22 @@ IsCatalogNamespace(Oid namespaceId) return namespaceId == PG_CATALOG_NAMESPACE; } +/* + * Same logic as with IsCatalogNamespace, to be used when OID isn't known. + * NULL name results in InvalidOid. + */ +bool +YbIsCatalogNamespaceByName(const char *namespace_name) +{ + Oid namespace_oid; + + namespace_oid = namespace_name ? + LookupExplicitNamespace(namespace_name, true) : + InvalidOid; + + return IsCatalogNamespace(namespace_oid); +} + /* * IsToastNamespace * True iff namespace is pg_toast or my temporary-toast-table namespace. @@ -285,7 +317,12 @@ IsSharedRelation(Oid relationId) relationId == SharedDescriptionRelationId || relationId == SharedSecLabelRelationId || relationId == SubscriptionRelationId || - relationId == TableSpaceRelationId) + relationId == TableSpaceRelationId || + relationId == YBCatalogVersionRelationId || + relationId == YbInvalidationMessagesRelationId || + relationId == YbProfileRelationId || + relationId == YbRoleProfileRelationId || + relationId == YBLogicalClientVersionRelationId) return true; /* These are their indexes */ if (relationId == AuthIdOidIndexId || @@ -306,7 +343,13 @@ IsSharedRelation(Oid relationId) relationId == SubscriptionNameIndexId || relationId == SubscriptionObjectIndexId || relationId == TablespaceNameIndexId || - relationId == TablespaceOidIndexId) + relationId == TablespaceOidIndexId || + relationId == YBCatalogVersionDbOidIndexId || + relationId == YbInvalidationMessagesIndexId || + relationId == YbProfileOidIndexId || + relationId == YbProfileRolnameIndexId || + relationId == YbRoleProfileOidIndexId || + relationId == YBLogicalClientVersionDbOidIndexId) return true; /* These are their toast tables and toast indexes */ if (relationId == PgAuthidToastTable || @@ -328,6 +371,32 @@ IsSharedRelation(Oid relationId) relationId == PgTablespaceToastTable || relationId == PgTablespaceToastIndex) return true; + + /* + * In test mode, there might be shared relations other than predefined + * ones. + */ + if (yb_test_system_catalogs_creation) + { + /* To avoid cycle */ + if (relationId == RelationRelationId) + return false; + + Relation pg_class = table_open(RelationRelationId, AccessShareLock); + HeapTuple tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId)); + + bool result = (HeapTupleIsValid(tuple) ? + ((Form_pg_class) GETSTRUCT(tuple))->relisshared : + false); + + if (HeapTupleIsValid(tuple)) + heap_freetuple(tuple); + + table_close(pg_class, AccessShareLock); + + return result; + } + return false; } @@ -383,6 +452,27 @@ IsPinnedObject(Oid classId, Oid objectId) if (classId == DatabaseRelationId) return false; + /* + * YB: Vector type is not pinned so that it can be dropped by the vector + * extension. + */ + if (objectId == VECTOROID) + return false; + + /* + * YB: BSON type is not pinned so that it can be dropped by the documentdb + * extension. + */ + if (objectId == BSONOID) + return false; + + /* + * YB: graphid type is not pinned so that it can be dropped by the age + * extension. + */ + if (objectId == GRAPHIDOID) + return false; + /* * All other initdb-created objects are pinned. This is overkill (the * system doesn't really depend on having every last weird datatype, for @@ -393,6 +483,93 @@ IsPinnedObject(Oid classId, Oid objectId) return true; } +/* + * GetBackendOidFromRelPersistence + * Returns backend oid for the given type of relation persistence. + */ +Oid +GetBackendOidFromRelPersistence(char relpersistence) +{ + switch (relpersistence) + { + case RELPERSISTENCE_TEMP: + return BackendIdForTempRelations(); + case RELPERSISTENCE_UNLOGGED: + case RELPERSISTENCE_PERMANENT: + return InvalidBackendId; + default: + elog(ERROR, "invalid relpersistence: %c", relpersistence); + return InvalidOid; /* placate compiler */ + } +} + +/* + * DoesRelFileExist + * True iff there is an existing file of the same name for this relation. + */ +bool +DoesRelFileExist(const RelFileNodeBackend *rnode) +{ + bool collides; + char *rpath = relpath(*rnode, MAIN_FORKNUM); + int fd = BasicOpenFile(rpath, O_RDONLY | PG_BINARY); + + if (fd >= 0) + { + /* definite collision */ + close(fd); + collides = true; + } + else + { + /* + * Here we have a little bit of a dilemma: if errno is something + * other than ENOENT, should we declare a collision and loop? In + * particular one might think this advisable for, say, EPERM. + * However there really shouldn't be any unreadable files in a + * tablespace directory, and if the EPERM is actually complaining + * that we can't read the directory itself, we'd be in an infinite + * loop. In practice it seems best to go ahead regardless of the + * errno. If there is a colliding file we will get an smgr + * failure when we attempt to create the new relation file. + */ + collides = false; + } + + pfree(rpath); + return collides; +} + +/* + * DoesOidExistInRelation + * True iff the oid already exists in the relation. + * Used typically with relation = pg_class, to check if a new oid is + * already in use. + */ +bool +DoesOidExistInRelation(Oid oid, + Relation relation, + Oid indexId, + AttrNumber oidcolumn) +{ + SysScanDesc scan; + ScanKeyData key; + bool collides; + + ScanKeyInit(&key, + oidcolumn, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(oid)); + + /* see notes above about using SnapshotAny */ + scan = systable_beginscan(relation, indexId, true, SnapshotAny, 1, &key); + + collides = HeapTupleIsValid(systable_getnext(scan)); + + systable_endscan(scan); + + return collides; +} /* * GetNewOidWithIndex @@ -421,9 +598,6 @@ Oid GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn) { Oid newOid; - SysScanDesc scan; - ScanKeyData key; - bool collides; uint64 retries = 0; uint64 retries_before_log = GETNEWOID_LOG_THRESHOLD; @@ -439,8 +613,20 @@ GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn) * pg_upgrade; doing so would risk collisions with the OIDs it wants to * assign. Hitting this assert means there's some path where we failed to * ensure that a type OID is determined by commands in the dump script. + * YB: We may get here during extension upgrade (while executing + * ALTER EXTENSION). Extension upgrade in YB is done as part of + * pg_upgrade. */ - Assert(!IsBinaryUpgrade || RelationGetRelid(relation) != TypeRelationId); + Assert(!IsBinaryUpgrade || yb_binary_restore || RelationGetRelid(relation) != TypeRelationId + || yb_extension_upgrade); + + if (IsYugaByteEnabled()) + { + if (relation->rd_rel->relisshared) + YbDatabaseIdForNewObjectId = Template1DbOid; + else + YbDatabaseIdForNewObjectId = MyDatabaseId; + } /* Generate new OIDs until we find one not in the table */ do @@ -449,19 +635,6 @@ GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn) newOid = GetNewObjectId(); - ScanKeyInit(&key, - oidcolumn, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(newOid)); - - /* see notes above about using SnapshotAny */ - scan = systable_beginscan(relation, indexId, true, - SnapshotAny, 1, &key); - - collides = HeapTupleIsValid(systable_getnext(scan)); - - systable_endscan(scan); - /* * Log that we iterate more than GETNEWOID_LOG_THRESHOLD but have not * yet found OID unused in the relation. Then repeat logging with @@ -492,22 +665,110 @@ GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn) } retries++; - } while (collides); + } while (DoesOidExistInRelation(newOid, relation, indexId, oidcolumn)); + + return newOid; +} + +static HTAB * +YbGetAllRelfilenodes() +{ + static HTAB *relfilenode_htab = NULL; + + if (YBCIsInitDbModeEnvVarSet()) + return NULL; + + /* For backward compatibility and keep unit test happy. */ + if (!*YBCGetGFlags()->ysql_enable_pg_per_database_oid_allocator) + return NULL; + + /* + * For practical purpose, there is no need to refresh the hash table during + * a session. We only need to find out existing relfilenode in pg_class + * before the session starts. After a session starts, a newly allocated OID + * cannot cause collision. + */ + if (relfilenode_htab) + return relfilenode_htab; + + /* + * Connect to SPI manager + */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + char query[100]; + + sprintf(query, "SELECT relfilenode FROM pg_catalog.pg_class WHERE relfilenode >= %u", + FirstNormalObjectId); + SPIPlanPtr plan = SPI_prepare(query, 0, NULL); + + if (plan == NULL) + elog(ERROR, "SPI_prepare failed for \"%s\"", query); + + int saved_yb_fetch_row_limit = yb_fetch_row_limit; + bool saved_yb_is_calling_internal_sql_for_ddl = yb_is_calling_internal_sql_for_ddl; /* - * If at least one log message is emitted, also log the completion of OID - * assignment. + * We are fetching relfilenode column, each row has only 4-bytes, let's + * fetch 256K rows at a time. */ - if (retries > GETNEWOID_LOG_THRESHOLD) + yb_fetch_row_limit = 1024 * 256; + yb_is_calling_internal_sql_for_ddl = true; + PG_TRY(); { - ereport(LOG, - (errmsg_plural("new OID has been assigned in relation \"%s\" after %llu retry", - "new OID has been assigned in relation \"%s\" after %llu retries", - retries, - RelationGetRelationName(relation), (unsigned long long) retries))); + int spirc = SPI_execute_plan(plan, NULL, NULL, true, 0); + + yb_fetch_row_limit = saved_yb_fetch_row_limit; + yb_is_calling_internal_sql_for_ddl = saved_yb_is_calling_internal_sql_for_ddl; + if (spirc != SPI_OK_SELECT) + elog(ERROR, "failed to get relfilenode tuple"); + YBC_LOG_INFO("SPI_processed = %lu", SPI_processed); + } + PG_CATCH(); + { + yb_fetch_row_limit = saved_yb_fetch_row_limit; + yb_is_calling_internal_sql_for_ddl = saved_yb_is_calling_internal_sql_for_ddl; + PG_RE_THROW(); } + PG_END_TRY(); - return newOid; + /* Build a hash table of all the relfilenodes in pg_class. */ + HASHCTL ctl; + + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(Oid); + ctl.hcxt = CacheMemoryContext; + relfilenode_htab = hash_create("relfilenode_htab", 1024, &ctl, HASH_ELEM | HASH_BLOBS); + uint64 i; + + for (i = 0; i < SPI_processed; i++) + { + bool isnull; + Datum qdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull); + + Assert(!isnull); + Oid relfilenode = DatumGetObjectId(qdata); + + hash_search(relfilenode_htab, &relfilenode, HASH_ENTER, NULL); + } + + /* + * Disconnect from SPI manager + */ + if (SPI_finish() != SPI_OK_FINISH) + { + hash_destroy(relfilenode_htab); + relfilenode_htab = NULL; + elog(ERROR, "SPI_finish failed"); + } + + return relfilenode_htab; +} + +static bool +YbDoesRelfilenodeExist(HTAB *htab, Oid relfilenode) +{ + return htab && hash_search(htab, &relfilenode, HASH_FIND, NULL); } /* @@ -530,30 +791,21 @@ Oid GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) { RelFileNodeBackend rnode; - char *rpath; - bool collides; - BackendId backend; + + HTAB *htab = NULL; + + if (IsYugaByteEnabled() && relpersistence != RELPERSISTENCE_TEMP) + htab = YbGetAllRelfilenodes(); /* * If we ever get here during pg_upgrade, there's something wrong; all * relfilenode assignments during a binary-upgrade run should be * determined by commands in the dump script. + * YB: We may get here during extension upgrade (while executing + * ALTER EXTENSION). Extension upgrade in YB is done as part of + * pg_upgrade. */ - Assert(!IsBinaryUpgrade); - - switch (relpersistence) - { - case RELPERSISTENCE_TEMP: - backend = BackendIdForTempRelations(); - break; - case RELPERSISTENCE_UNLOGGED: - case RELPERSISTENCE_PERMANENT: - backend = InvalidBackendId; - break; - default: - elog(ERROR, "invalid relpersistence: %c", relpersistence); - return InvalidOid; /* placate compiler */ - } + Assert(!IsBinaryUpgrade || yb_binary_restore || yb_extension_upgrade); /* This logic should match RelationInitPhysicalAddr */ rnode.node.spcNode = reltablespace ? reltablespace : MyDatabaseTableSpace; @@ -564,7 +816,18 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) * that properly here to make sure that any collisions based on filename * are properly detected. */ - rnode.backend = backend; + rnode.backend = GetBackendOidFromRelPersistence(relpersistence); + + /* + * All the shared relations have relfilenode value as 0, which suggests + * that relfilenode is only used for non-shared relations. That's why + * MyDatabaseId should be used for new relfilenode OID allocation. + */ + if (IsYugaByteEnabled()) + { + Assert(!pg_class || !pg_class->rd_rel->relisshared); + YbDatabaseIdForNewObjectId = MyDatabaseId; + } do { @@ -578,27 +841,11 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) rnode.node.relNode = GetNewObjectId(); /* Check for existing file of same name */ - rpath = relpath(rnode, MAIN_FORKNUM); - - if (access(rpath, F_OK) == 0) - { - /* definite collision */ - collides = true; - } - else - { - /* - * Here we have a little bit of a dilemma: if errno is something - * other than ENOENT, should we declare a collision and loop? In - * practice it seems best to go ahead regardless of the errno. If - * there is a colliding file we will get an smgr failure when we - * attempt to create the new relation file. - */ - collides = false; - } - - pfree(rpath); - } while (collides); + /* + * YB: also check for existing relfilenode in the pg_class catalog table. + */ + } while (DoesRelFileExist(&rnode) || + YbDoesRelfilenodeExist(htab, rnode.node.relNode)); return rnode.node.relNode; } @@ -706,3 +953,287 @@ pg_stop_making_pinned_objects(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } + +/* + * IsTableOidUnused + * Returns true iff the given table oid is not used by any other table + * within the database of the given tablespace. + * + * First checks pg_class to see if the oid is in use (similar to + * GetNewOidWithIndex), and then checks if there are any existing relfiles that + * have the same oid (similar to GetNewRelFileNode). + * + * Similar to GetNewOidWithIndex and GetNewRelFileNode, there is a theoretical + * race condition, but since we don't worry about it there, it should be fine + * here as well. + */ +bool +IsTableOidUnused(Oid table_oid, + Oid reltablespace, + Relation pg_class, + char relpersistence) +{ + RelFileNodeBackend rnode; + Oid oidIndex; + bool collides; + + /* First check for if the oid is used in pg_class. */ + + /* + * TODO(Alex): The relcache will cache the identity of the OID index for + * us + */ + oidIndex = ClassOidIndexId; + + if (!OidIsValid(oidIndex)) + { + elog(WARNING, "Could not find oid index of pg_class."); + } + + collides = DoesOidExistInRelation(table_oid, + pg_class, + oidIndex, + Anum_pg_class_oid); + + if (!collides) + { + /* + * Check if there are existing relfiles with the oid. + * YB Note: It looks like we only run into collisions here for + * temporary tables. + */ + + /* + * The relpath will vary based on the backend ID, so we must initialize + * that properly here to make sure that any collisions based on filename + * are properly detected. + */ + rnode.backend = GetBackendOidFromRelPersistence(relpersistence); + + /* This logic should match RelationInitPhysicalAddr */ + rnode.node.spcNode = (reltablespace ? + reltablespace : + MyDatabaseTableSpace); + rnode.node.dbNode = ((rnode.node.spcNode == GLOBALTABLESPACE_OID) ? + InvalidOid : + MyDatabaseId); + + rnode.node.relNode = table_oid; + + /* Check for existing file of same name */ + collides = DoesRelFileExist(&rnode); + } + + return !collides; +} + +/* + * GetTableOidFromRelOptions + * Scans through relOptions for any 'table_oid' options, and ensures + * that oid is available. Returns that oid, or InvalidOid if unspecified. + */ +Oid +GetTableOidFromRelOptions(List *relOptions, + Oid reltablespace, + char relpersistence) +{ + ListCell *opt_cell; + Oid table_oid; + bool is_oid_free; + + foreach(opt_cell, relOptions) + { + DefElem *def = (DefElem *) lfirst(opt_cell); + + if (strcmp(def->defname, "table_oid") == 0) + { + const char *hintmsg; + + if (!parse_oid(defGetString(def), &table_oid, &hintmsg)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for OID option \"table_oid\""), + hintmsg ? errhint("%s", _(hintmsg)) : 0)); + if (OidIsValid(table_oid)) + { + Relation pg_class_desc = table_open(RelationRelationId, + RowExclusiveLock); + + is_oid_free = IsTableOidUnused(table_oid, + reltablespace, + pg_class_desc, + relpersistence); + table_close(pg_class_desc, RowExclusiveLock); + + if (is_oid_free) + return table_oid; + else + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("table OID %u is in use", table_oid))); + + /* Only process the first table_oid. */ + break; + } + } + } + + return InvalidOid; +} + +/* + * YbGetColocationIdFromRelOptions + * Scans through relOptions for any 'colocation_id' options. + * Returns that ID, or InvalidOid if unspecified. + * + * This is only used during table/index creation, as this reloption is not + * persisted. + */ +Oid +YbGetColocationIdFromRelOptions(List *relOptions) +{ + ListCell *opt_cell; + Oid colocation_id; + + foreach(opt_cell, relOptions) + { + DefElem *def = (DefElem *) lfirst(opt_cell); + + if (strcmp(def->defname, "colocation_id") == 0) + { + const char *hintmsg; + + if (!parse_oid(defGetString(def), &colocation_id, &hintmsg)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for OID option \"colocation_id\""), + hintmsg ? errhint("%s", _(hintmsg)) : 0)); + if (OidIsValid(colocation_id)) + return colocation_id; + } + } + + return InvalidOid; +} + +/* + * GetRowTypeOidFromRelOptions + * Scans through relOptions for any 'row_type_oid' options, and ensures + * that oid is available. Returns that oid, or InvalidOid if unspecified. + */ +Oid +GetRowTypeOidFromRelOptions(List *relOptions) +{ + ListCell *opt_cell; + Oid row_type_oid; + Relation pg_type_desc; + HeapTuple tuple; + + foreach(opt_cell, relOptions) + { + DefElem *def = (DefElem *) lfirst(opt_cell); + + if (strcmp(def->defname, "row_type_oid") == 0) + { + const char *hintmsg; + + if (!parse_oid(defGetString(def), &row_type_oid, &hintmsg)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for OID option \"row_type_oid\""), + hintmsg ? errhint("%s", _(hintmsg)) : 0)); + if (OidIsValid(row_type_oid)) + { + pg_type_desc = table_open(TypeRelationId, AccessExclusiveLock); + + tuple = SearchSysCacheCopy1(TYPEOID, ObjectIdGetDatum(row_type_oid)); + if (HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("type OID %u is in use", row_type_oid))); + + table_close(pg_type_desc, AccessExclusiveLock); + + return row_type_oid; + } + } + } + + return InvalidOid; +} + +bool +YbGetUseInitdbAclFromRelOptions(List *options) +{ + ListCell *opt_cell; + + foreach(opt_cell, options) + { + /* Don't care about multiple occurrences, this reloption is internal. */ + DefElem *def = lfirst_node(DefElem, opt_cell); + + if (strcmp(def->defname, "use_initdb_acl") == 0) + return defGetBoolean(def); + } + + return InvalidOid; +} + +/* + * Is this relation stored into the YB system catalog tablet? + */ +bool +YbIsSysCatalogTabletRelation(Relation rel) +{ + Oid namespaceId = RelationGetNamespace(rel); + char *namespace_name = get_namespace_name_or_temp(namespaceId); + + return YbIsSysCatalogTabletRelationByIds(RelationGetRelid(rel), + namespaceId, + namespace_name); +} + +/* + * Same as above but potentially faster by avoiding get_namespace_name_or_temp + * call in case the caller already has the namespace name. + */ +bool +YbIsSysCatalogTabletRelationByIds(Oid relationId, Oid namespaceId, + char *namespace_name) +{ + Assert(namespace_name); + /* Re-evaluate this when toast relations are supported. */ + Assert(!IsToastNamespace(namespaceId)); + + /* + * YB puts catalog relations and information_schema relations in the sys + * catalog tablet. From commit 5c28dc4a654cc246d0da0807e18d07b81cfe45eb + * to the time of writing (2024-05-22), it is not possible to create user + * relations in pg_catalog because it hits an error if IsYsqlUpgrade is + * false, and if that is true, then relations are created as system + * relations. Before that commit, it seems to segfault when attempting to + * create table in pg_catalog (at least when testing on v2.0.11.0). + */ + if (IsCatalogNamespace(namespaceId) || + strcmp(namespace_name, "information_schema") == 0) + { + if (relationId >= FirstNormalObjectId) + { + /* + * At the time of writing (2024-05-22), it is possible for users + * (with the correct privileges) to create user relations in + * information_schema. Since these are persisted in the sys + * catalog tablet and this function may be called with such tables, + * don't hard fail upon encountering such tables. + */ + Assert(strcmp(namespace_name, "information_schema") == 0); + ereport(WARNING, + (errmsg("unexpected user relation in system namespace"), + errdetail("Table with oid %u should not be in namespace" + " %s.", + relationId, namespace_name))); + } + return true; + } + return false; +} diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 24f1e0184a79a..a8fcdd181cdb2 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -86,6 +86,16 @@ #include "utils/lsyscache.h" #include "utils/syscache.h" +/* YB includes */ +#include "catalog/pg_yb_profile.h" +#include "catalog/pg_yb_role_profile.h" +#include "catalog/pg_yb_tablegroup.h" +#include "commands/yb_cmds.h" +#include "commands/yb_profile.h" +#include "commands/yb_tablegroup.h" +#include "miscadmin.h" +#include "yb/yql/pggate/ybc_gflags.h" + /* * Deletion processing requires additional state for each ObjectAddress that @@ -187,7 +197,12 @@ static const Oid object_classes[] = { PublicationRelationId, /* OCLASS_PUBLICATION */ PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */ SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */ - TransformRelationId /* OCLASS_TRANSFORM */ + TransformRelationId, /* OCLASS_TRANSFORM */ + + /* YB items */ + YbProfileRelationId, /* OCLASS_YBPROFILE */ + YbRoleProfileRelationId, /* OCLASS_YBROLE_PROFILE */ + YbTablegroupRelationId, /* OCLASS_YBTBLGROUP */ }; @@ -202,9 +217,10 @@ static void reportDependentObjects(const ObjectAddresses *targetObjects, DropBehavior behavior, int flags, const ObjectAddress *origObject); -static void deleteOneObject(const ObjectAddress *object, - Relation *depRel, int32 flags); -static void doDeletion(const ObjectAddress *object, int flags); +static void deleteOneObject(const ObjectAddress *object, Relation *depRel, + int32 flags, bool ybOriginalObject); +static void doDeletion(const ObjectAddress *object, int flags, + bool ybOriginalObject); static bool find_expr_references_walker(Node *node, find_expr_references_context *context); static void process_function_rte_ref(RangeTblEntry *rte, AttrNumber attnum, @@ -274,7 +290,8 @@ deleteObjectsInList(ObjectAddresses *targetObjects, Relation *depRel, (thisextra->flags & DEPFLAG_ORIGINAL)) continue; - deleteOneObject(thisobj, depRel, flags); + deleteOneObject(thisobj, depRel, flags, + thisextra->flags & DEPFLAG_ORIGINAL); } } @@ -639,9 +656,9 @@ findDependentObjects(const ObjectAddress *object, /* Otherwise, treat this like an internal dependency */ /* FALL THRU */ + yb_switch_fallthrough(); case DEPENDENCY_INTERNAL: - /* * This object is part of the internal implementation of * another object, or is part of the extension that is the @@ -1190,6 +1207,9 @@ reportDependentObjects(const ObjectAddresses *targetObjects, numNotReportedClient), numNotReportedClient); + if (IsYugaByteEnabled() && clientdetail.data != NULL) + clientdetail.data = YBDetailSorted(clientdetail.data); + if (!ok) { if (origObject) @@ -1254,7 +1274,7 @@ DropObjectById(const ObjectAddress *object) elog(ERROR, "cache lookup failed for %s %u", get_object_class_descr(object->classId), object->objectId); - CatalogTupleDelete(rel, &tup->t_self); + CatalogTupleDelete(rel, tup); ReleaseSysCache(tup); } @@ -1277,7 +1297,7 @@ DropObjectById(const ObjectAddress *object) elog(ERROR, "could not find tuple for %s %u", get_object_class_descr(object->classId), object->objectId); - CatalogTupleDelete(rel, &tup->t_self); + CatalogTupleDelete(rel, tup); systable_endscan(scan); } @@ -1291,12 +1311,16 @@ DropObjectById(const ObjectAddress *object) * *depRel is the already-open pg_depend relation. */ static void -deleteOneObject(const ObjectAddress *object, Relation *depRel, int flags) +deleteOneObject(const ObjectAddress *object, Relation *depRel, int flags, + bool ybOriginalObject) { ScanKeyData key[3]; int nkeys; SysScanDesc scan; HeapTuple tup; + ObjectAddress implicit_tablegroup; + bool is_colocated_tables_with_tablespace_enabled = + *YBCGetGFlags()->ysql_enable_colocated_tables_with_tablespaces; /* DROP hook of the objects being removed */ InvokeObjectDropHookArg(object->classId, object->objectId, @@ -1319,7 +1343,7 @@ deleteOneObject(const ObjectAddress *object, Relation *depRel, int flags) * updates before calling doDeletion() --- they'd get committed right * away, which is not cool if the deletion then fails. */ - doDeletion(object, flags); + doDeletion(object, flags, ybOriginalObject); /* * Reopen depRel if we closed it above @@ -1356,13 +1380,37 @@ deleteOneObject(const ObjectAddress *object, Relation *depRel, int flags) scan = systable_beginscan(*depRel, DependDependerIndexId, true, NULL, nkeys, key); + implicit_tablegroup.objectId = InvalidOid; + while (HeapTupleIsValid(tup = systable_getnext(scan))) { - CatalogTupleDelete(*depRel, &tup->t_self); + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + CatalogTupleDelete(*depRel, tup); + + if (MyDatabaseColocated && + depform->refclassid == YbTablegroupRelationId && + !tablegroupHasDependents(depform->refobjid)) + { + implicit_tablegroup.classId = depform->refclassid; + implicit_tablegroup.objectId = depform->refobjid; + implicit_tablegroup.objectSubId = depform->refobjsubid; + } } systable_endscan(scan); + /* + * YB: Check if implicit tablegroup has any more tables in it. + * If not delete it. + */ + if (is_colocated_tables_with_tablespace_enabled && + OidIsValid(implicit_tablegroup.objectId)) + { + deleteSharedDependencyRecordsFor(implicit_tablegroup.classId, implicit_tablegroup.objectId, + implicit_tablegroup.objectSubId); + RemoveTablegroupById(implicit_tablegroup.objectId, true); + } /* * Delete shared dependency references related to this object. Again, if * subId = 0, remove records for sub-objects too. @@ -1397,7 +1445,7 @@ deleteOneObject(const ObjectAddress *object, Relation *depRel, int flags) * doDeletion: actually delete a single object */ static void -doDeletion(const ObjectAddress *object, int flags) +doDeletion(const ObjectAddress *object, int flags, bool ybOriginalObject) { switch (getObjectClass(object)) { @@ -1412,15 +1460,54 @@ doDeletion(const ObjectAddress *object, int flags) bool concurrent_lock_mode = ((flags & PERFORM_DELETION_CONCURRENT_LOCK) != 0); Assert(object->objectSubId == 0); + + Relation index = RelationIdGetRelation(object->objectId); + + if (IsYBRelation(index) && !index->rd_index->indisprimary) + YBCDropIndex(index); + + RelationClose(index); + index_drop(object->objectId, concurrent, concurrent_lock_mode); } else { if (object->objectSubId != 0) + { + Relation yb_rel = + RelationIdGetRelation(object->objectId); + + /* YB note: perform YBCDropColumn() */ + if (IsYBRelation(yb_rel)) + { + bool skip_if_original = + (flags & YB_SKIP_YB_DROP_ORIGNAL_COLUMN) && + ybOriginalObject; + bool skip_if_pk = + (flags & YB_SKIP_YB_DROP_PK_COLUMN) && + YbIsAttrPrimaryKeyColumn(yb_rel, + object->objectSubId); + + if (!skip_if_original && !skip_if_pk) + YBCDropColumn(yb_rel, object->objectSubId); + } + + RelationClose(yb_rel); + RemoveAttributeById(object->objectId, object->objectSubId); + } else + { + Relation rel = RelationIdGetRelation(object->objectId); + + if (IsYBRelation(rel)) + YBCDropTable(rel); + + RelationClose(rel); + heap_drop_with_catalog(object->objectId); + } } /* @@ -1514,6 +1601,10 @@ doDeletion(const ObjectAddress *object, int flags) DropObjectById(object); break; + case OCLASS_YBTBLGROUP: + RemoveTablegroupById(object->objectId, false); + break; + /* * These global object types are not supported here. */ @@ -1522,6 +1613,8 @@ doDeletion(const ObjectAddress *object, int flags) case OCLASS_TBLSPACE: case OCLASS_SUBSCRIPTION: case OCLASS_PARAMETER_ACL: + case OCLASS_YBPROFILE: + case OCLASS_YBROLE_PROFILE: elog(ERROR, "global objects cannot be deleted by doDeletion"); break; @@ -2972,6 +3065,16 @@ getObjectClass(const ObjectAddress *object) case TransformRelationId: return OCLASS_TRANSFORM; + + /* YB cases */ + case YbProfileRelationId: + return OCLASS_YBPROFILE; + + case YbRoleProfileRelationId: + return OCLASS_YBROLE_PROFILE; + + case YbTablegroupRelationId: + return OCLASS_YBTBLGROUP; } /* shouldn't get here */ @@ -3016,7 +3119,7 @@ DeleteInitPrivs(const ObjectAddress *object) NULL, nkeys, key); while (HeapTupleIsValid(oldtuple = systable_getnext(scan))) - CatalogTupleDelete(relation, &oldtuple->t_self); + CatalogTupleDelete(relation, oldtuple); systable_endscan(scan); diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index e5bc5bb1613f9..76a62a472bb63 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -74,6 +74,12 @@ #include "utils/lsyscache.h" #include "utils/syscache.h" +/* YB includes */ +#include "catalog/pg_authid.h" +#include "catalog/pg_init_privs.h" +#include "catalog/pg_yb_tablegroup.h" +#include "pg_yb_utils.h" + /* Potentially set by pg_upgrade_support functions */ Oid binary_upgrade_next_heap_pg_class_oid = InvalidOid; @@ -98,7 +104,8 @@ static ObjectAddress AddNewRelationType(const char *typeName, char new_rel_kind, Oid ownerid, Oid new_row_type, - Oid new_array_type); + Oid new_array_type, + bool yb_new_rel_is_shared); static void RelationRemoveInheritance(Oid relid); static Oid StoreRelCheck(Relation rel, const char *ccname, Node *expr, bool is_validated, bool is_local, int inhcount, @@ -228,7 +235,65 @@ static const FormData_pg_attribute a6 = { .attislocal = true, }; -static const FormData_pg_attribute *SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6}; +static const FormData_pg_attribute a7 = { + .attname = {"ybctid"}, + .atttypid = BYTEAOID, + .attlen = -1, + .attnum = YBTupleIdAttributeNumber, + .attcacheoff = -1, + .atttypmod = -1, + .attbyval = false, + .attalign = TYPALIGN_INT, + .attstorage = TYPSTORAGE_EXTENDED, + .attnotnull = true, + .attislocal = true, +}; + +static const FormData_pg_attribute *SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7}; + +static const FormData_pg_attribute yb_a1 = { + .attname = {"ybuniqueidxkeysuffix"}, + .atttypid = BYTEAOID, + .attlen = -1, + .attnum = YBUniqueIdxKeySuffixAttributeNumber, + .attcacheoff = -1, + .atttypmod = -1, + .attbyval = false, + .attalign = TYPALIGN_INT, + .attstorage = TYPSTORAGE_EXTENDED, + .attnotnull = false, + .attislocal = true, +}; + +static const FormData_pg_attribute yb_a2 = { + .attname = {"ybidxbasectid"}, + .atttypid = BYTEAOID, + .attlen = -1, + .attnum = YBIdxBaseTupleIdAttributeNumber, + .attcacheoff = -1, + .atttypmod = -1, + .attbyval = false, + .attalign = TYPALIGN_INT, + .attstorage = TYPSTORAGE_EXTENDED, + .attnotnull = true, + .attislocal = true, +}; + +static const FormData_pg_attribute *YbSysAtt[] = {&yb_a1, &yb_a2}; + +/* + * SystemAttributeDefinition() equivalent for attnum <= + * YBFirstLowInvalidAttributeNumber. + */ +const FormData_pg_attribute * +YbSystemAttributeDefinition(AttrNumber attno) +{ + int index = attno - YBSystemFirstLowInvalidAttributeNumber - 1; + + if (index < 0 || index >= lengthof(YbSysAtt)) + elog(ERROR, "invalid YB system attribute number %d", attno); + return YbSysAtt[index]; +} /* * This function returns a Form_pg_attribute pointer for a system attribute. @@ -238,6 +303,8 @@ static const FormData_pg_attribute *SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6}; const FormData_pg_attribute * SystemAttributeDefinition(AttrNumber attno) { + if (attno <= YBFirstLowInvalidAttributeNumber) + return YbSystemAttributeDefinition(attno); if (attno >= 0 || attno < -(int) lengthof(SysAtt)) elog(ERROR, "invalid system attribute number %d", attno); return SysAtt[-attno - 1]; @@ -288,6 +355,7 @@ Relation heap_create(const char *relname, Oid relnamespace, Oid reltablespace, + Oid reltablegroup, Oid relid, Oid relfilenode, Oid accessmtd, @@ -336,8 +404,22 @@ heap_create(const char *relname, reltablespace = InvalidOid; /* Don't create storage for relkinds without physical storage. */ - if (!RELKIND_HAS_STORAGE(relkind)) + /* + * YB: create storage (relfilenode) for parent partition tables + * (see GH#6525). + */ + if (!(IsYugaByteEnabled() && RELKIND_HAS_PARTITIONS(relkind) && + relpersistence != RELPERSISTENCE_TEMP) + && !RELKIND_HAS_STORAGE(relkind)) create_storage = false; + else if (shared_relation) + /* + * YB note: + * Shared relations have no relfilenode. + * Note that relations defined with BKI_BOOTSTRAP option have no relfilenode + * either, but YB tables shouldn't use it. + */ + relfilenode = InvalidOid; else { /* @@ -375,6 +457,15 @@ heap_create(const char *relname, relpersistence, relkind); + /* + * Create local storage in YB only if storage is required and it is a + * temporary relation. + * TODO Consider hooking the YB-Create logic here instead of above. + */ + if (YBIsEnabledInPostgresEnvVar()) + create_storage = (create_storage && + relpersistence == RELPERSISTENCE_TEMP); + /* * Have the storage manager create the relation's disk file, if needed. * @@ -399,10 +490,26 @@ heap_create(const char *relname, * protected by the existence of a physical file; but for relations with * no files, add a pg_shdepend entry to account for that. */ - if (!create_storage && reltablespace != InvalidOid) + if ((IsYBRelation(rel) || !create_storage) && reltablespace != InvalidOid) recordDependencyOnTablespace(RelationRelationId, relid, reltablespace); + if (IsYBRelation(rel) && reltablegroup != InvalidOid) + { + ObjectAddress myself, + tablegroup; + + myself.classId = RelationRelationId; + myself.objectId = relid; + myself.objectSubId = 0; + + tablegroup.classId = YbTablegroupRelationId; + tablegroup.objectId = reltablegroup; + tablegroup.objectSubId = 0; + + recordDependencyOn(&myself, &tablegroup, DEPENDENCY_NORMAL); + } + /* ensure that stats are dropped if transaction aborts */ pgstat_create_relation(rel); @@ -701,7 +808,8 @@ InsertPgAttributeTuples(Relation pg_attribute_rel, TupleDesc tupdesc, Oid new_rel_oid, Datum *attoptions, - CatalogIndexState indstate) + CatalogIndexState indstate, + bool yb_relisshared) { TupleTableSlot **slot; TupleDesc td; @@ -709,6 +817,7 @@ InsertPgAttributeTuples(Relation pg_attribute_rel, int natts = 0; int slotCount = 0; bool close_index = false; + bool yb_shared_insert = yb_relisshared && !IsBootstrapProcessingMode(); td = RelationGetDescr(pg_attribute_rel); @@ -782,7 +891,7 @@ InsertPgAttributeTuples(Relation pg_attribute_rel, /* insert the new tuples and update the indexes */ CatalogTuplesMultiInsertWithInfo(pg_attribute_rel, slot, slotCount, - indstate); + indstate, yb_shared_insert); slotCount = 0; } @@ -806,7 +915,8 @@ InsertPgAttributeTuples(Relation pg_attribute_rel, static void AddNewAttributeTuples(Oid new_rel_oid, TupleDesc tupdesc, - char relkind) + char relkind, + bool yb_relisshared) { Relation rel; CatalogIndexState indstate; @@ -824,24 +934,28 @@ AddNewAttributeTuples(Oid new_rel_oid, /* set stats detail level to a sane default */ for (int i = 0; i < natts; i++) tupdesc->attrs[i].attstattarget = -1; - InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, NULL, indstate); + InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, NULL, indstate, yb_relisshared); - /* add dependencies on their datatypes and collations */ - for (int i = 0; i < natts; i++) + /* YB: Skip adding dependencies for shared relation attrs */ + if (!IsYsqlUpgrade || !yb_relisshared || IsBootstrapProcessingMode()) { - /* Add dependency info */ - ObjectAddressSubSet(myself, RelationRelationId, new_rel_oid, i + 1); - ObjectAddressSet(referenced, TypeRelationId, - tupdesc->attrs[i].atttypid); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); - - /* The default collation is pinned, so don't bother recording it */ - if (OidIsValid(tupdesc->attrs[i].attcollation) && - tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID) + /* add dependencies on their datatypes and collations */ + for (int i = 0; i < natts; i++) { - ObjectAddressSet(referenced, CollationRelationId, - tupdesc->attrs[i].attcollation); + /* Add dependency info */ + ObjectAddressSubSet(myself, RelationRelationId, new_rel_oid, i + 1); + ObjectAddressSet(referenced, TypeRelationId, + tupdesc->attrs[i].atttypid); recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* The default collation is pinned, so don't bother recording it */ + if (OidIsValid(tupdesc->attrs[i].attcollation) && + tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID) + { + ObjectAddressSet(referenced, CollationRelationId, + tupdesc->attrs[i].attcollation); + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } } } @@ -856,7 +970,7 @@ AddNewAttributeTuples(Oid new_rel_oid, td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt); - InsertPgAttributeTuples(rel, td, new_rel_oid, NULL, indstate); + InsertPgAttributeTuples(rel, td, new_rel_oid, NULL, indstate, yb_relisshared); FreeTupleDesc(td); } @@ -893,6 +1007,18 @@ InsertPgClassTuple(Relation pg_class_desc, bool nulls[Natts_pg_class]; HeapTuple tup; + if (IsYBRelation(new_rel_desc) && + rd_rel->relowner != BOOTSTRAP_SUPERUSERID) + { + if (rd_rel->relisshared) + elog(ERROR, "shared relation should be owned by superuser!"); + + if (IsCatalogRelation(new_rel_desc) && IsYsqlUpgrade) + elog(ERROR, + "system relation created during YSQL upgrade " + "should be owned by superuser!"); + } + /* This is a tad tedious, but way cleaner than what we used to do... */ memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); @@ -942,7 +1068,8 @@ InsertPgClassTuple(Relation pg_class_desc, tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls); /* finally insert the new tuple, update the indexes, and clean up */ - CatalogTupleInsert(pg_class_desc, tup); + YBCatalogTupleInsert(pg_class_desc, tup, + rd_rel->relisshared && !IsBootstrapProcessingMode()); heap_freetuple(tup); } @@ -1019,7 +1146,8 @@ AddNewRelationType(const char *typeName, char new_rel_kind, Oid ownerid, Oid new_row_type, - Oid new_array_type) + Oid new_array_type, + bool yb_new_rel_is_shared) { return TypeCreate(new_row_type, /* optional predetermined OID */ @@ -1053,7 +1181,71 @@ AddNewRelationType(const char *typeName, -1, /* typmod */ 0, /* array dimensions for typBaseType */ false, /* Type NOT NULL */ - InvalidOid); /* rowtypes never have a collation */ + InvalidOid, /* rowtypes never have a collation */ + yb_new_rel_is_shared); /* whether new relation is shared */ +} + +/* + * Insert initdb-like permissions into pg_init_privs and return the relacl + * to be set in pg_class. + * + * initdb sets a very specific set of permissions which we replicate here: + * '=r/$BOOTSTRAP_SUPERUSERID' followed by + * pg_catalog.acldefault('r', BOOTSTRAP_SUPERUSERID) + * (which results in {=r/postgres,postgres=arwdDxt/postgres}). + * + * See setup_privileges() in initdb.c. + */ +static Acl * +YbSetInitdbPermissions(Oid relid, char relkind, bool relisshared) +{ + Acl *acl; + + AclItem aclitem; + + aclitem.ai_grantee = ACL_ID_PUBLIC; + aclitem.ai_grantor = BOOTSTRAP_SUPERUSERID; + ACLITEM_SET_RIGHTS(aclitem, ACL_SELECT); + + Acl *allow_read_to_everyone = aclupdate(make_empty_acl(), &aclitem, + ACL_MODECHG_EQL, + BOOTSTRAP_SUPERUSERID, + DROP_RESTRICT); + + Acl *superuser_default = acldefault((relkind == RELKIND_SEQUENCE ? + OBJECT_SEQUENCE : + OBJECT_TABLE), + BOOTSTRAP_SUPERUSERID); + + acl = aclconcat(allow_read_to_everyone, superuser_default); + + /* + * We also need to insert this ACL value into pg_init_privs + * (for shared rels - do the insert in all databases). + */ + + Relation pg_init_privs = table_open(InitPrivsRelationId, RowExclusiveLock); + HeapTuple pg_init_privs_tuple; + Datum values[Natts_pg_init_privs]; + bool nulls[Natts_pg_init_privs]; + + values[Anum_pg_init_privs_objoid - 1] = ObjectIdGetDatum(relid); + values[Anum_pg_init_privs_classoid - 1] = ObjectIdGetDatum(RelationRelationId); + values[Anum_pg_init_privs_objsubid - 1] = (Datum) 0; + values[Anum_pg_init_privs_privtype - 1] = CharGetDatum(INITPRIVS_INITDB); + values[Anum_pg_init_privs_initprivs - 1] = PointerGetDatum(acl); + + MemSet(nulls, false, sizeof(nulls)); + + pg_init_privs_tuple = + heap_form_tuple(RelationGetDescr(pg_init_privs), values, nulls); + + YBCatalogTupleInsert(pg_init_privs, pg_init_privs_tuple, relisshared); + + heap_freetuple(pg_init_privs_tuple); + table_close(pg_init_privs, RowExclusiveLock); + + return acl; } /* -------------------------------- @@ -1078,10 +1270,14 @@ AddNewRelationType(const char *typeName, * mapped_relation: true if the relation will use the relfilenode map * oncommit: ON COMMIT marking (only relevant if it's a temp table) * reloptions: reloptions in Datum form, or (Datum) 0 if none + * YB nota: Not used for system relations in YSQL upgrade mode. * use_user_acl: true if should look for user-defined default permissions; * if false, relacl is always set NULL * allow_system_table_mods: true to allow creation in system namespaces * is_internal: is this a system-generated catalog? + * yb_use_initdb_acl: if true, permissions will be set as if the relation + * is created by initdb via BKI or yb_system_views.sql. + * Can only be used in YSQL upgrade mode, has priority over use_user_acl. * * Output parameters: * typaddress: if not null, gets the object address of the new pg_type entry @@ -1094,6 +1290,7 @@ Oid heap_create_with_catalog(const char *relname, Oid relnamespace, Oid reltablespace, + Oid reltablegroup, Oid relid, Oid reltypeid, Oid reloftypeid, @@ -1111,7 +1308,8 @@ heap_create_with_catalog(const char *relname, bool allow_system_table_mods, bool is_internal, Oid relrewrite, - ObjectAddress *typaddress) + ObjectAddress *typaddress, + bool yb_use_initdb_acl) { Relation pg_class_desc; Relation new_rel_desc; @@ -1125,12 +1323,16 @@ heap_create_with_catalog(const char *relname, TransactionId relfrozenxid; MultiXactId relminmxid; + /* YB variables. */ + bool is_system = IsCatalogNamespace(relnamespace); + pg_class_desc = table_open(RelationRelationId, RowExclusiveLock); /* * sanity checks */ Assert(IsNormalProcessingMode() || IsBootstrapProcessingMode()); + Assert(IsYsqlUpgrade || !is_system || !yb_use_initdb_acl); /* * Validate proposed tupdesc for the desired relkind. If @@ -1141,24 +1343,38 @@ heap_create_with_catalog(const char *relname, allow_system_table_mods ? CHKATYPE_ANYARRAY : 0); /* - * This would fail later on anyway, if the relation already exists. But - * by catching it here we can emit a nicer error message. + * In YB mode, during bootstrap, a relation lookup by name will be a full-table scan + * and slow because secondary indexes are not available yet. So we will skip this + * duplicate name check as it will error later anyway when the indexes are created. */ - existing_relid = get_relname_relid(relname, relnamespace); - if (existing_relid != InvalidOid) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_TABLE), - errmsg("relation \"%s\" already exists", relname))); + if (!IsYugaByteEnabled() || !IsBootstrapProcessingMode()) + { + /* + * This would fail later on anyway, if the relation already exists. But + * by catching it here we can emit a nicer error message. + */ + existing_relid = get_relname_relid(relname, relnamespace); + if (existing_relid != InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_TABLE), + errmsg("relation \"%s\" already exists", relname))); + + /* + * Since we are going to create a rowtype as well, also check for + * collision with an existing type name. If there is one and it's an + * autogenerated array, we can rename it out of the way; otherwise we can + * at least give a good error message. + */ + old_type_oid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, + CStringGetDatum(relname), + ObjectIdGetDatum(relnamespace)); + } + else /* YB */ + { + existing_relid = InvalidOid; + old_type_oid = InvalidOid; + } - /* - * Since we are going to create a rowtype as well, also check for - * collision with an existing type name. If there is one and it's an - * autogenerated array, we can rename it out of the way; otherwise we can - * at least give a good error message. - */ - old_type_oid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, - CStringGetDatum(relname), - ObjectIdGetDatum(relnamespace)); if (OidIsValid(old_type_oid)) { if (!moveArrayTypeName(old_type_oid, relname, relnamespace)) @@ -1184,8 +1400,20 @@ heap_create_with_catalog(const char *relname, */ if (!OidIsValid(relid)) { + bool yb_heap_pg_class_oids_supplied = IsBinaryUpgrade && !yb_binary_restore && + !yb_extension_upgrade; + + if (yb_binary_restore && !yb_ignore_pg_class_oids) + yb_heap_pg_class_oids_supplied = true; + + bool yb_heap_relfilenode_supplied = IsBinaryUpgrade && !yb_binary_restore && + !yb_extension_upgrade; + + if (yb_binary_restore && !yb_ignore_relfilenode_ids) + yb_heap_relfilenode_supplied = true; + /* Use binary-upgrade override for pg_class.oid and relfilenode */ - if (IsBinaryUpgrade) + if (yb_heap_pg_class_oids_supplied) { /* * Indexes are not supported here; they use @@ -1221,7 +1449,13 @@ heap_create_with_catalog(const char *relname, relid = binary_upgrade_next_heap_pg_class_oid; binary_upgrade_next_heap_pg_class_oid = InvalidOid; - if (RELKIND_HAS_STORAGE(relkind)) + /* + * YB: The parent partition has DocDB storage, so we preserve + * its relfilenode when upgrading. + */ + if ((RELKIND_HAS_STORAGE(relkind) || + (IsYugaByteEnabled() && relkind == RELKIND_PARTITIONED_TABLE)) && + yb_heap_relfilenode_supplied) { if (!OidIsValid(binary_upgrade_next_heap_pg_class_relfilenode)) ereport(ERROR, @@ -1239,6 +1473,33 @@ heap_create_with_catalog(const char *relname, relpersistence); } + /* + * YSQL upgrade notes: + * ------------------- + * At this point, reloptions no longer affects the relation + * creation process, the only remaining use for them is to be + * stored in pg_class. ybTransformRelOptions has already + * removed all temporary reloptions. + * + * We want system tables to not have any reloptions stored to + * imitate BKI processing, so we don't allow any reloption not + * removed by ybTransformRelOptions. + * + * Stuff defined through yb_system_views.sql, on the other hand, is + * allowed to have non-temporary reloptions. + * + * (Note: heap_create_with_catalog is not used for CREATE INDEX.) + */ + if (IsYsqlUpgrade && is_system && relkind == RELKIND_RELATION && + reloptions != (Datum) 0) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("unsuppored reloptions were used for a system table " + "during YSQL upgrade"), + errhint("Only a small subset is allowed due to BKI restrictions."))); + } + /* * Other sessions' catalog scans can't find this until we commit. Hence, * it doesn't hurt to hold AccessExclusiveLock. Do it here so callers @@ -1249,7 +1510,11 @@ heap_create_with_catalog(const char *relname, /* * Determine the relation's initial permissions. */ - if (use_user_acl) + if (yb_use_initdb_acl) + { + relacl = YbSetInitdbPermissions(relid, relkind, shared_relation); + } + else if (use_user_acl && !(IsYsqlUpgrade && is_system && relkind == RELKIND_RELATION)) { switch (relkind) { @@ -1285,6 +1550,7 @@ heap_create_with_catalog(const char *relname, new_rel_desc = heap_create(relname, relnamespace, reltablespace, + reltablegroup, relid, relfilenode, accessmtd, @@ -1337,7 +1603,8 @@ heap_create_with_catalog(const char *relname, relkind, ownerid, reltypeid, - new_array_oid); + new_array_oid, + shared_relation); new_type_oid = new_type_addr.objectId; if (typaddress) *typaddress = new_type_addr; @@ -1376,7 +1643,8 @@ heap_create_with_catalog(const char *relname, -1, /* typmod */ 0, /* array dimensions for typBaseType */ false, /* Type NOT NULL */ - InvalidOid); /* rowtypes never have a collation */ + InvalidOid, /* rowtypes never have a collation */ + shared_relation); /* shared relation */ pfree(relarrayname); } @@ -1411,12 +1679,14 @@ heap_create_with_catalog(const char *relname, /* * now add tuples to pg_attribute for the attributes in our new relation. */ - AddNewAttributeTuples(relid, new_rel_desc->rd_att, relkind); + AddNewAttributeTuples(relid, new_rel_desc->rd_att, relkind, shared_relation); /* * Make a dependency link to force the relation to be deleted if its * namespace is. Also make a dependency link to its owner, as well as * dependencies for any roles mentioned in the default ACL. + * Additionally add dependency on the tablespace this table is being + * created in. * * For composite types, these dependencies are tracked for the pg_type * entry, so we needn't record them here. Likewise, TOAST tables don't @@ -1427,6 +1697,10 @@ heap_create_with_catalog(const char *relname, * * Also, skip this in bootstrap mode, since we don't make dependencies * while bootstrapping. + * + * YB NOTE: + * For non-view system relations during YSQL upgrade, we do not need to do + * anything. */ if (relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_TOASTVALUE && @@ -1436,40 +1710,43 @@ heap_create_with_catalog(const char *relname, referenced; ObjectAddresses *addrs; - ObjectAddressSet(myself, RelationRelationId, relid); - - recordDependencyOnOwner(RelationRelationId, relid, ownerid); + if (!IsYsqlUpgrade || !is_system || relkind == RELKIND_VIEW) + { + ObjectAddressSet(myself, RelationRelationId, relid); - recordDependencyOnNewAcl(RelationRelationId, relid, 0, ownerid, relacl); + recordDependencyOnOwner(RelationRelationId, relid, ownerid); - recordDependencyOnCurrentExtension(&myself, false); + recordDependencyOnNewAcl(RelationRelationId, relid, 0, ownerid, relacl); - addrs = new_object_addresses(); + recordDependencyOnCurrentExtension(&myself, false); - ObjectAddressSet(referenced, NamespaceRelationId, relnamespace); - add_exact_object_address(&referenced, addrs); + addrs = new_object_addresses(); - if (reloftypeid) - { - ObjectAddressSet(referenced, TypeRelationId, reloftypeid); + ObjectAddressSet(referenced, NamespaceRelationId, relnamespace); add_exact_object_address(&referenced, addrs); - } - /* - * Make a dependency link to force the relation to be deleted if its - * access method is. - * - * No need to add an explicit dependency for the toast table, as the - * main table depends on it. - */ - if (RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) - { - ObjectAddressSet(referenced, AccessMethodRelationId, accessmtd); - add_exact_object_address(&referenced, addrs); - } + if (reloftypeid) + { + ObjectAddressSet(referenced, TypeRelationId, reloftypeid); + add_exact_object_address(&referenced, addrs); + } - record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); - free_object_addresses(addrs); + /* + * Make a dependency link to force the relation to be deleted if its + * access method is. + * + * No need to add an explicit dependency for the toast table, as the + * main table depends on it. + */ + if (RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) + { + ObjectAddressSet(referenced, AccessMethodRelationId, accessmtd); + add_exact_object_address(&referenced, addrs); + } + + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); + } } /* Post creation hook for new relation */ @@ -1490,6 +1767,13 @@ heap_create_with_catalog(const char *relname, if (oncommit != ONCOMMIT_NOOP) register_on_commit_action(relid, oncommit); + /* Increment sticky object count if the object is a TEMP TABLE. */ + if (YbIsClientYsqlConnMgr() && new_rel_desc->rd_islocaltemp) + { + elog(LOG, "Incrementing sticky object count for TEMP TABLE %s", relname); + increment_sticky_object_count(); + } + /* * ok, the relation has been cataloged, so close our relations and return * the OID of the newly created relation. @@ -1528,7 +1812,7 @@ RelationRemoveInheritance(Oid relid) NULL, 1, &key); while (HeapTupleIsValid(tuple = systable_getnext(scan))) - CatalogTupleDelete(catalogRelation, &tuple->t_self); + CatalogTupleDelete(catalogRelation, tuple); systable_endscan(scan); table_close(catalogRelation, RowExclusiveLock); @@ -1556,7 +1840,7 @@ DeleteRelationTuple(Oid relid) elog(ERROR, "cache lookup failed for relation %u", relid); /* delete the relation tuple from pg_class, and finish up */ - CatalogTupleDelete(pg_class_desc, &tup->t_self); + CatalogTupleDelete(pg_class_desc, tup); ReleaseSysCache(tup); @@ -1593,7 +1877,7 @@ DeleteAttributeTuples(Oid relid) /* Delete all the matching tuples */ while ((atttup = systable_getnext(scan)) != NULL) - CatalogTupleDelete(attrel, &atttup->t_self); + CatalogTupleDelete(attrel, atttup); /* Clean up after the scan */ systable_endscan(scan); @@ -1634,7 +1918,7 @@ DeleteSystemAttributeTuples(Oid relid) /* Delete all the matching tuples */ while ((atttup = systable_getnext(scan)) != NULL) - CatalogTupleDelete(attrel, &atttup->t_self); + CatalogTupleDelete(attrel, atttup); /* Clean up after the scan */ systable_endscan(scan); @@ -1680,7 +1964,7 @@ RemoveAttributeById(Oid relid, AttrNumber attnum) { /* System attribute (probably OID) ... just delete the row */ - CatalogTupleDelete(attr_rel, &tuple->t_self); + CatalogTupleDelete(attr_rel, tuple); } else { @@ -1712,9 +1996,29 @@ RemoveAttributeById(Oid relid, AttrNumber attnum) /* * Change the column name to something that isn't likely to conflict */ - snprintf(newattname, sizeof(newattname), - "........pg.dropped.%d........", attnum); - namestrcpy(&(attStruct->attname), newattname); + if (IsYugaByteEnabled()) + { + /* + * TODO: Should be changed to CatalogTupleUpdate() when we are + * able to update a row's primary key + */ + + CatalogTupleDelete(attr_rel, tuple); + + snprintf(newattname, sizeof(newattname), + "........pg.dropped.%d........", attnum); + namestrcpy(&(attStruct->attname), newattname); + + CatalogTupleInsert(attr_rel, tuple); + } + else + { + snprintf(newattname, sizeof(newattname), + "........pg.dropped.%d........", attnum); + namestrcpy(&(attStruct->attname), newattname); + + CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple); + } /* clear the missing value if any */ if (attStruct->atthasmissing) @@ -1738,8 +2042,6 @@ RemoveAttributeById(Oid relid, AttrNumber attnum) tuple = heap_modify_tuple(tuple, RelationGetDescr(attr_rel), valuesAtt, nullsAtt, replacesAtt); } - - CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple); } /* @@ -1841,7 +2143,7 @@ heap_drop_with_catalog(Oid relid) if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for foreign table %u", relid); - CatalogTupleDelete(rel, &tuple->t_self); + CatalogTupleDelete(rel, tuple); ReleaseSysCache(tuple); table_close(rel, RowExclusiveLock); @@ -1862,8 +2164,9 @@ heap_drop_with_catalog(Oid relid) /* * Schedule unlinking of the relation's physical files at commit. + * For Yugabyte-backed relations, there aren't any physical files to remove. */ - if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind)) + if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind) && !IsYBRelation(rel)) RelationDropStorage(rel); /* ensure that stats are dropped if transaction commits */ @@ -2208,13 +2511,23 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal) { CookedConstraint *con = (CookedConstraint *) lfirst(lc); + /* + * YB: System relations can't have defaults or CHECK constraints, + * BKI syntax doesn't support it. + */ switch (con->contype) { case CONSTR_DEFAULT: + if (IsYBRelation(rel) && IsCatalogRelation(rel)) + elog(ERROR, "system relations can not have DEFAULT constraints"); + con->conoid = StoreAttrDefault(rel, con->attnum, con->expr, is_internal, false); break; case CONSTR_CHECK: + if (IsYBRelation(rel) && IsCatalogRelation(rel)) + elog(ERROR, "system relations can not have CHECK constraints"); + con->conoid = StoreRelCheck(rel, con->name, con->expr, !con->skip_validation, con->is_local, @@ -2337,6 +2650,10 @@ AddRelationNewConstraints(Relation rel, castNode(Const, expr)->constisnull)) continue; + /* YB added */ + if (rel->rd_rel->relisshared && !IsBootstrapProcessingMode()) + elog(ERROR, "shared relations can not have DEFAULT constraints"); + /* If the DEFAULT is volatile we cannot use a missing value */ if (colDef->missingMode && contain_volatile_functions_after_planning((Expr *) expr)) @@ -2372,6 +2689,10 @@ AddRelationNewConstraints(Relation rel, if (cdef->contype != CONSTR_CHECK) continue; + /* YB added */ + if (rel->rd_rel->relisshared && !IsBootstrapProcessingMode()) + elog(ERROR, "shared relations can not have CHECK constraints"); + if (cdef->raw_expr != NULL) { Assert(cdef->cooked_expr == NULL); @@ -2947,7 +3268,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum) /* we must loop even when attnum != 0, in case of inherited stats */ while (HeapTupleIsValid(tuple = systable_getnext(scan))) - CatalogTupleDelete(pgstatistic, &tuple->t_self); + CatalogTupleDelete(pgstatistic, tuple); systable_endscan(scan); @@ -3457,7 +3778,7 @@ RemovePartitionKeyByRelId(Oid relid) elog(ERROR, "cache lookup failed for partition key of relation %u", relid); - CatalogTupleDelete(rel, &tuple->t_self); + CatalogTupleDelete(rel, tuple); ReleaseSysCache(tuple); table_close(rel, RowExclusiveLock); @@ -3554,3 +3875,9 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound) CacheInvalidateRelcache(parent); } + +Node * +YbCookConstraint(ParseState *pstate, Node *raw_constraint, char *relname) +{ + return cookConstraint(pstate, raw_constraint, relname); +} diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 5fc2409822568..2541fdb47bfe7 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -87,6 +87,14 @@ #include "utils/syscache.h" #include "utils/tuplesort.h" +/* YB includes */ +#include "commands/defrem.h" +#include "commands/progress.h" +#include "commands/yb_cmds.h" +#include "parser/parse_utilcmd.h" +#include "pg_yb_utils.h" +#include "pgstat.h" + /* Potentially set by pg_upgrade_support functions */ Oid binary_upgrade_next_index_pg_class_oid = InvalidOid; Oid binary_upgrade_next_index_pg_class_relfilenode = InvalidOid; @@ -113,7 +121,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation, Oid *classObjectId); static void InitializeAttributeOids(Relation indexRelation, int numatts, Oid indexoid); -static void AppendAttributeTuples(Relation indexRelation, Datum *attopts); +static void AppendAttributeTuples(Relation indexRelation, Datum *attopts, bool yb_relisshared); static void UpdateIndexRelation(Oid indexoid, Oid heapoid, Oid parentIndexId, IndexInfo *indexInfo, @@ -124,7 +132,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid, bool isexclusion, bool immediate, bool isvalid, - bool isready); + bool isready, + bool yb_relisshared); static void index_update_stats(Relation rel, bool hasindex, double reltuples); @@ -182,6 +191,20 @@ relationHasPrimaryKey(Relation rel) return result; } +/* + * YBRelationHasPrimaryKey + * See whether an existing relation has a primary key. + * + * Caller must have suitable lock on the relation. + * + * Note: It is just a wrapper over the relationHasPrimaryKey function above. + */ +bool +YBRelationHasPrimaryKey(Relation rel) +{ + return relationHasPrimaryKey(rel); +} + /* * index_check_primary_key * Apply special checks needed before creating a PRIMARY KEY index @@ -503,7 +526,7 @@ InitializeAttributeOids(Relation indexRelation, * ---------------------------------------------------------------- */ static void -AppendAttributeTuples(Relation indexRelation, Datum *attopts) +AppendAttributeTuples(Relation indexRelation, Datum *attopts, bool yb_relisshared) { Relation pg_attribute; CatalogIndexState indstate; @@ -521,7 +544,7 @@ AppendAttributeTuples(Relation indexRelation, Datum *attopts) */ indexTupDesc = RelationGetDescr(indexRelation); - InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, attopts, indstate); + InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, attopts, indstate, yb_relisshared); CatalogCloseIndexes(indstate); @@ -546,7 +569,8 @@ UpdateIndexRelation(Oid indexoid, bool isexclusion, bool immediate, bool isvalid, - bool isready) + bool isready, + bool yb_relisshared) { int2vector *indkey; oidvector *indcollation; @@ -642,7 +666,7 @@ UpdateIndexRelation(Oid indexoid, /* * insert the tuple into the pg_index catalog */ - CatalogTupleInsert(pg_index, tuple); + YBCatalogTupleInsert(pg_index, tuple, yb_relisshared && !IsBootstrapProcessingMode()); /* * close the relation and free the tuple @@ -718,7 +742,13 @@ index_create(Relation heapRelation, bits16 constr_flags, bool allow_system_table_mods, bool is_internal, - Oid *constraintId) + Oid *constraintId, + YbOptSplit *split_options, + const bool skip_index_backfill, + bool is_colocated, + Oid tablegroupId, + Oid colocationId, + bool yb_skip_index_creation) { Oid heapRelationId = RelationGetRelid(heapRelation); Relation pg_class; @@ -841,8 +871,11 @@ index_create(Relation heapRelation, /* * We cannot allow indexing a shared relation after initdb (because * there's no way to make the entry in other databases' pg_class). + * + * YB NOTE: + * Not totally true anymore, we're using a hacky way to do that. */ - if (shared_relation && !IsBootstrapProcessingMode()) + if (shared_relation && !IsBootstrapProcessingMode() && !IsYsqlUpgrade) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("shared indexes cannot be created after initdb"))); @@ -854,27 +887,35 @@ index_create(Relation heapRelation, elog(ERROR, "shared relations must be placed in pg_global tablespace"); /* - * Check for duplicate name (both as to the index, and as to the - * associated constraint if any). Such cases would fail on the relevant - * catalogs' unique indexes anyway, but we prefer to give a friendlier - * error message. + * In YB mode, during bootstrap, a relation lookup by name will be a full-table scan + * and slow because secondary indexes are not available yet. So we will skip this + * duplicate name check as it will error later anyway when the indexes are created. */ - if (get_relname_relid(indexRelationName, namespaceId)) + if (!IsYugaByteEnabled() || !IsBootstrapProcessingMode()) { - if ((flags & INDEX_CREATE_IF_NOT_EXISTS) != 0) + /* + * Check for duplicate name (both as to the index, and as to the + * associated constraint if any). Such cases would fail on the relevant + * catalogs' unique indexes anyway, but we prefer to give a friendlier + * error message. + */ + if (get_relname_relid(indexRelationName, namespaceId)) { - ereport(NOTICE, + if ((flags & INDEX_CREATE_IF_NOT_EXISTS) != 0) + { + ereport(NOTICE, + (errcode(ERRCODE_DUPLICATE_TABLE), + errmsg("relation \"%s\" already exists, skipping", + indexRelationName))); + table_close(pg_class, RowExclusiveLock); + return InvalidOid; + } + + ereport(ERROR, (errcode(ERRCODE_DUPLICATE_TABLE), - errmsg("relation \"%s\" already exists, skipping", + errmsg("relation \"%s\" already exists", indexRelationName))); - table_close(pg_class, RowExclusiveLock); - return InvalidOid; } - - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_TABLE), - errmsg("relation \"%s\" already exists", - indexRelationName))); } if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0 && @@ -909,8 +950,17 @@ index_create(Relation heapRelation, */ if (!OidIsValid(indexRelationId)) { + bool yb_index_pg_class_oids_supplied = IsBinaryUpgrade && !yb_binary_restore; + + if (yb_binary_restore && !yb_ignore_pg_class_oids) + yb_index_pg_class_oids_supplied = true; + + bool yb_index_relfilenode_supplied = IsBinaryUpgrade && !yb_binary_restore; + + if (yb_binary_restore && !yb_ignore_relfilenode_ids) + yb_index_relfilenode_supplied = true; /* Use binary-upgrade override for pg_class.oid and relfilenode */ - if (IsBinaryUpgrade) + if (yb_index_pg_class_oids_supplied) { if (!OidIsValid(binary_upgrade_next_index_pg_class_oid)) ereport(ERROR, @@ -920,14 +970,17 @@ index_create(Relation heapRelation, indexRelationId = binary_upgrade_next_index_pg_class_oid; binary_upgrade_next_index_pg_class_oid = InvalidOid; - /* Override the index relfilenode */ - if ((relkind == RELKIND_INDEX) && - (!OidIsValid(binary_upgrade_next_index_pg_class_relfilenode))) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("index relfilenode value not set when in binary upgrade mode"))); - relFileNode = binary_upgrade_next_index_pg_class_relfilenode; - binary_upgrade_next_index_pg_class_relfilenode = InvalidOid; + if (yb_index_relfilenode_supplied) + { + /* Override the index relfilenode */ + if ((relkind == RELKIND_INDEX) && + (!OidIsValid(binary_upgrade_next_index_pg_class_relfilenode))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("index relfilenode value not set when in binary upgrade mode"))); + relFileNode = binary_upgrade_next_index_pg_class_relfilenode; + binary_upgrade_next_index_pg_class_relfilenode = InvalidOid; + } /* * Note that we want create_storage = true for binary upgrade. The @@ -936,6 +989,12 @@ index_create(Relation heapRelation, */ Assert(create_storage); } + else if (IsYBRelation(heapRelation) && IsCatalogRelation(heapRelation)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("system index must have an explicit OID"))); + } else { indexRelationId = @@ -951,6 +1010,7 @@ index_create(Relation heapRelation, indexRelation = heap_create(indexRelationName, namespaceId, tableSpaceId, + tablegroupId, indexRelationId, relFileNode, accessMethodObjectId, @@ -968,6 +1028,30 @@ index_create(Relation heapRelation, Assert(relminmxid == InvalidMultiXactId); Assert(indexRelationId == RelationGetRelid(indexRelation)); + /* + * Create index in YugaByte only if it is a secondary index. Primary key is + * an implicit part of the base table in YugaByte and doesn't need to be created. + */ + if (IsYBRelation(indexRelation) && !isprimary && !yb_skip_index_creation) + { + YBCCreateIndex(indexRelationName, + indexInfo, + indexTupDesc, + coloptions, + reloptions, + indexRelationId, + heapRelation, + split_options, + skip_index_backfill, + is_colocated, + tablegroupId, + colocationId, + tableSpaceId, + YbGetRelfileNodeId(indexRelation), + InvalidOid /* oldRelfileNodeId */ , + classObjectId); + } + /* * Obtain exclusive lock on it. Although no other transactions can see it * until we commit, this prevents deadlock-risk complaints from lock @@ -985,6 +1069,28 @@ index_create(Relation heapRelation, indexRelation->rd_rel->relam = accessMethodObjectId; indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid); + /* + * YSQL upgrade notes: + * ------------------- + * At this point, reloptions no longer affects the index + * creation process, the only remaining use for them is to be + * stored in pg_class. ybTransformRelOptions has already + * removed all temporary reloptions. + * + * We want system tables and indexes to not have any reloptions + * stored to imitate BKI processing, so we don't allow any reloption + * not removed by ybTransformRelOptions. + */ + if (IsYsqlUpgrade && IsCatalogRelation(heapRelation) && + reloptions != (Datum) 0) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("unsuppored reloptions were used for a system index " + "during YSQL upgrade"), + errhint("Only a small subset is allowed due to BKI restrictions."))); + } + /* * store index's pg_class entry */ @@ -1007,7 +1113,7 @@ index_create(Relation heapRelation, /* * append ATTRIBUTE tuples for the index */ - AppendAttributeTuples(indexRelation, indexInfo->ii_OpclassOptions); + AppendAttributeTuples(indexRelation, indexInfo->ii_OpclassOptions, shared_relation); /* ---------------- * update pg_index @@ -1023,7 +1129,8 @@ index_create(Relation heapRelation, isprimary, is_exclusion, (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0, !concurrent && !invalid, - !concurrent); + !concurrent, + shared_relation); /* * Register relcache invalidation on the indexes' heap relation, to @@ -1052,12 +1159,19 @@ index_create(Relation heapRelation, * * During bootstrap we can't register any dependencies, and we don't try * to make a constraint either. + * + * YB NOTE: + * During YSQL upgrade, we do create the constraints for system indexes. + * This negates the need to do an ALTER TABLE ADD UNIQUE/PK USING INDEX + * later (that initdb does). */ if (!IsBootstrapProcessingMode()) { ObjectAddress myself, referenced; ObjectAddresses *addrs; + bool yb_is_catalog_rel = IsYBRelation(heapRelation) && + IsCatalogRelation(heapRelation); ObjectAddressSet(myself, RelationRelationId, indexRelationId); @@ -1090,7 +1204,7 @@ index_create(Relation heapRelation, if (constraintId) *constraintId = localaddr.objectId; } - else + else if (!yb_is_catalog_rel) { bool have_simple_col = false; @@ -1126,66 +1240,69 @@ index_create(Relation heapRelation, free_object_addresses(addrs); } - /* - * If this is an index partition, create partition dependencies on - * both the parent index and the table. (Note: these must be *in - * addition to*, not instead of, all other dependencies. Otherwise - * we'll be short some dependencies after DETACH PARTITION.) - */ - if (OidIsValid(parentIndexRelid)) + if (!yb_is_catalog_rel) { - ObjectAddressSet(referenced, RelationRelationId, parentIndexRelid); - recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI); + /* + * If this is an index partition, create partition dependencies on + * both the parent index and the table. (Note: these must be *in + * addition to*, not instead of, all other dependencies. Otherwise + * we'll be short some dependencies after DETACH PARTITION.) + */ + if (OidIsValid(parentIndexRelid)) + { + ObjectAddressSet(referenced, RelationRelationId, parentIndexRelid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI); - ObjectAddressSet(referenced, RelationRelationId, heapRelationId); - recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC); - } + ObjectAddressSet(referenced, RelationRelationId, heapRelationId); + recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC); + } - /* placeholder for normal dependencies */ - addrs = new_object_addresses(); + /* placeholder for normal dependencies */ + addrs = new_object_addresses(); - /* Store dependency on collations */ + /* Store dependency on collations */ - /* The default collation is pinned, so don't bother recording it */ - for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) - { - if (OidIsValid(collationObjectId[i]) && - collationObjectId[i] != DEFAULT_COLLATION_OID) + /* The default collation is pinned, so don't bother recording it */ + for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) { - ObjectAddressSet(referenced, CollationRelationId, - collationObjectId[i]); - add_exact_object_address(&referenced, addrs); + if (OidIsValid(collationObjectId[i]) && + collationObjectId[i] != DEFAULT_COLLATION_OID) + { + ObjectAddressSet(referenced, CollationRelationId, + collationObjectId[i]); + add_exact_object_address(&referenced, addrs); + } } - } - /* Store dependency on operator classes */ - for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) - { - ObjectAddressSet(referenced, OperatorClassRelationId, classObjectId[i]); - add_exact_object_address(&referenced, addrs); - } + /* Store dependency on operator classes */ + for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) + { + ObjectAddressSet(referenced, OperatorClassRelationId, classObjectId[i]); + add_exact_object_address(&referenced, addrs); + } - record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); - free_object_addresses(addrs); + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); - /* Store dependencies on anything mentioned in index expressions */ - if (indexInfo->ii_Expressions) - { - recordDependencyOnSingleRelExpr(&myself, - (Node *) indexInfo->ii_Expressions, - heapRelationId, - DEPENDENCY_NORMAL, - DEPENDENCY_AUTO, false); - } + /* Store dependencies on anything mentioned in index expressions */ + if (indexInfo->ii_Expressions) + { + recordDependencyOnSingleRelExpr(&myself, + (Node *) indexInfo->ii_Expressions, + heapRelationId, + DEPENDENCY_NORMAL, + DEPENDENCY_AUTO, false); + } - /* Store dependencies on anything mentioned in predicate */ - if (indexInfo->ii_Predicate) - { - recordDependencyOnSingleRelExpr(&myself, - (Node *) indexInfo->ii_Predicate, - heapRelationId, - DEPENDENCY_NORMAL, - DEPENDENCY_AUTO, false); + /* Store dependencies on anything mentioned in predicate */ + if (indexInfo->ii_Predicate) + { + recordDependencyOnSingleRelExpr(&myself, + (Node *) indexInfo->ii_Predicate, + heapRelationId, + DEPENDENCY_NORMAL, + DEPENDENCY_AUTO, false); + } } } else @@ -1210,7 +1327,7 @@ index_create(Relation heapRelation, * relcache entry has already been rebuilt thanks to sinval update during * CommandCounterIncrement. */ - if (IsBootstrapProcessingMode()) + if (IsBootstrapProcessingMode() || IsYugaByteEnabled()) RelationInitIndexAccessInfo(indexRelation); else Assert(indexRelation->rd_indexcxt != NULL); @@ -1252,6 +1369,12 @@ index_create(Relation heapRelation, } else { + if (IsYugaByteEnabled() && !concurrent && !invalid && + yb_test_block_index_phase[0] != '\0') + YbTestGucBlockWhileStrEqual(&yb_test_block_index_phase, + "backfill", + "non-concurrent index backfill"); + index_build(heapRelation, indexRelation, indexInfo, false, true); } @@ -1261,6 +1384,13 @@ index_create(Relation heapRelation, */ index_close(indexRelation, NoLock); + if (IsYugaByteEnabled() && !concurrent && !invalid && + yb_test_block_index_phase[0] != '\0') + YbTestGucBlockWhileStrEqual(&yb_test_block_index_phase, + "postbackfill", + "operations after a non-concurrent " + "index backfill"); + return indexRelationId; } @@ -1399,6 +1529,16 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId, newInfo->ii_OpclassOptions[i] = get_attoptions(oldIndexId, i + 1); } + /* + * YB: Get whether the indexed table is colocated + * (either via database or a tablegroup). + * If the indexed table is colocated, then this index is colocated as well. + */ + bool is_colocated = (IsYBRelation(heapRelation) && + !IsBootstrapProcessingMode() && + !YbIsConnectedToTemplateDb() && + YbGetTableProperties(heapRelation)->is_colocated); + /* * Now create the new index. * @@ -1424,7 +1564,17 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId, 0, true, /* allow table to be a system catalog? */ false, /* is_internal? */ - NULL); + NULL, + NULL, + true, /* skip_index_backfill */ + is_colocated, + InvalidOid, /* tablegroupId, TODO: fill this + * appropriately when adding + * support for reindex */ + InvalidOid, /* colocationId, TODO: fill this + * appropriately when adding + * support for reindex */ + false /* yb_skip_index_creation */ ); /* Close the relations used and clean up */ index_close(indexRelation, NoLock); @@ -2005,10 +2155,25 @@ index_constraint_create(Relation heapRelation, * * Note that the constraint has a dependency on the table, so we don't * need (or want) any direct dependency from the index to the table. + * + * YB note: + * For constraint on catalog relations, initdb assigns an oid in + * [FirstGenbkiObjectId, FirstUnpinnedObjectId), making them pinned objects + * and hence dependencies on them are not explicity recorded. Migration + * during YSQL upgrade, on the other hand, assigns them oid in + * [FirstUnpinnedObjectId, FirstNormalObjectId) making them unpinned objects + * (GH #27514). Calling recordDependencyOn() would add the dependency + * in pg_depend. It requires additional work to do this correctly for shared + * catalog relations. Adding this dependency is not even required because + * ALTER TABLE ... DROP CONSTRAINT on catalog relations is not supported. + * So, just skip it. */ - ObjectAddressSet(myself, ConstraintRelationId, conOid); - ObjectAddressSet(idxaddr, RelationRelationId, indexRelationId); - recordDependencyOn(&idxaddr, &myself, DEPENDENCY_INTERNAL); + if (!(IsYsqlUpgrade && IsCatalogRelation(heapRelation))) + { + ObjectAddressSet(myself, ConstraintRelationId, conOid); + ObjectAddressSet(idxaddr, RelationRelationId, indexRelationId); + recordDependencyOn(&idxaddr, &myself, DEPENDENCY_INTERNAL); + } /* * Also, if this is a constraint on a partition, give it partition-type @@ -2182,6 +2347,10 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) */ CheckTableNotInUse(userIndexRelation, "DROP INDEX"); + if (IsYugaByteEnabled() && + userIndexRelation->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + YBCRecordTempRelationDDL(); + /* * Drop Index Concurrently is more or less the reverse process of Create * Index Concurrently. @@ -2325,8 +2494,11 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) /* * Schedule physical removal of the files (if any) + * If the relation is a Yugabyte relation, there aren't any physical files to + * remove. */ - if (RELKIND_HAS_STORAGE(userIndexRelation->rd_rel->relkind)) + if (RELKIND_HAS_STORAGE(userIndexRelation->rd_rel->relkind) && + !IsYBRelation(userIndexRelation)) RelationDropStorage(userIndexRelation); /* ensure that stats are dropped if transaction commits */ @@ -2353,7 +2525,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) hasexprs = !heap_attisnull(tuple, Anum_pg_index_indexprs, RelationGetDescr(indexRelation)); - CatalogTupleDelete(indexRelation, &tuple->t_self); + CatalogTupleDelete(indexRelation, tuple); ReleaseSysCache(tuple); table_close(indexRelation, RowExclusiveLock); @@ -2671,8 +2843,9 @@ BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii) */ Assert(ii->ii_Unique); - if (index->rd_rel->relam != BTREE_AM_OID) - elog(ERROR, "unexpected non-btree speculative unique index"); + if (index->rd_rel->relam != BTREE_AM_OID && + index->rd_rel->relam != LSM_AM_OID) + elog(ERROR, "unexpected non-btree/lsm speculative unique index"); ii->ii_UniqueOps = (Oid *) palloc(sizeof(Oid) * indnkeyatts); ii->ii_UniqueProcs = (Oid *) palloc(sizeof(Oid) * indnkeyatts); @@ -2858,7 +3031,7 @@ index_update_stats(Relation rel, */ if (update_stats) { - relpages = RelationGetNumberOfBlocks(rel); + relpages = IsYBRelation(rel) ? 0 : RelationGetNumberOfBlocks(rel); if (rel->rd_rel->relkind != RELKIND_INDEX) visibilitymap_count(rel, &relallvisible, NULL); @@ -2917,6 +3090,12 @@ index_update_stats(Relation rel, dirty = true; } + /** + * When concurrent DDL support is enabled and until inplace catalog updates are fully implemented in #29638, + * avoid updating statistics if we don't really need to. If auto analyze is enabled, it will keep reltuples + * on the (indexed) main table reasonably accurate, so we can skip updating statistics here. + */ + update_stats = update_stats && (YBCIsLegacyModeForCatalogOps() || !YBCIsAutoAnalyzeEnabled()); if (update_stats) { if (rd_rel->relpages != (int32) relpages) @@ -2941,7 +3120,9 @@ index_update_stats(Relation rel, */ if (dirty) { - systable_inplace_update_finish(state, tuple); + systable_inplace_update_finish(state, tuple, + (rd_rel->relisshared && + !IsBootstrapProcessingMode())); /* the above sends a cache inval message */ } else @@ -2993,6 +3174,14 @@ index_build(Relation heapRelation, Assert(PointerIsValid(indexRelation->rd_indam->ambuild)); Assert(PointerIsValid(indexRelation->rd_indam->ambuildempty)); + if (yb_xcluster_automatic_mode_target_ddl) + { + /* Still need to update pg_catalog. */ + index_update_stats(heapRelation, true, -1); + index_update_stats(indexRelation, false, -1); + return; + } + /* * Determine worker process details for parallel CREATE INDEX. Currently, * only btree has support for parallel builds. @@ -3028,6 +3217,10 @@ index_build(Relation heapRelation, save_nestlevel = NewGUCNestLevel(); /* Set up initial progress report status */ + if (IsYugaByteEnabled()) + pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE, + YB_PROGRESS_CREATEIDX_BACKFILLING); + else { const int progress_index[] = { PROGRESS_CREATEIDX_PHASE, @@ -3125,11 +3318,33 @@ index_build(Relation heapRelation, } /* - * Update heap and index pg_class rows + * Sanity check to ensure concurrent index builds don't reach this + * code-path. In YB, we don't compute stats during a concurrent index build + * so we shouldn't update them here. + */ + if (IsYugaByteEnabled()) + Assert(!indexInfo->ii_Concurrent); + + + /* + * #25394 enabled update of base table and index table reltuples after + * `CREATE INDEX [CONCURRENTLY]`. In case of non-concurrent index creation, + * the index reltuples were already being set here, but the base table + * reltuples were being left unchanged for YB tables. + * + * We must maintain this old behavior for non-concurrent index creation, but + * when `yb_enable_update_reltuples_after_create_index` is enabled, we + * should update the base table reltuples as well to achieve consistent + * behavior across both forms of index creation. + * + * When reltuples is passed as -1.0 to index_update_stats, the stats are not + * updated, only relhasindex is updated. */ index_update_stats(heapRelation, true, - stats->heap_tuples); + ((IsYBRelation(heapRelation) && + !yb_enable_update_reltuples_after_create_index) ? + -1.0 : stats->heap_tuples)); index_update_stats(indexRelation, false, @@ -3154,6 +3369,96 @@ index_build(Relation heapRelation, SetUserIdAndSecContext(save_userid, save_sec_context); } +/* + * yb_index_backfill - invoke access-method-specific index backfill procedure + * + * This is mainly a copy of index_build. index_build is used for + * non-multi-stage index creation; yb_index_backfill is used for multi-stage index + * creation. + */ +double +yb_index_backfill(Relation heapRelation, + Relation indexRelation, + IndexInfo *indexInfo, + bool isprimary, + YbBackfillInfo *bfinfo, + YbPgExecOutParam *bfresult) +{ + Oid save_userid; + int save_sec_context; + int save_nestlevel; + IndexBuildResult *result; + + /* + * sanity checks + */ + Assert(RelationIsValid(indexRelation)); + Assert(PointerIsValid(indexRelation->rd_indam)); + Assert(PointerIsValid(indexRelation->rd_indam->yb_ambackfill)); + + ereport(DEBUG1, + (errmsg("backfilling index \"%s\" on table \"%s\"", + RelationGetRelationName(indexRelation), + RelationGetRelationName(heapRelation)))); + + /* + * Switch to the table owner's userid, so that any index functions are run + * as that user. Also lock down security-restricted operations and + * arrange to make GUC variable changes local to this command. + */ + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(heapRelation->rd_rel->relowner, + save_sec_context | SECURITY_RESTRICTED_OPERATION); + save_nestlevel = NewGUCNestLevel(); + + /* + * Call the access method's build procedure + */ + result = indexRelation->rd_indam->yb_ambackfill(heapRelation, + indexRelation, + indexInfo, + bfinfo, + bfresult); + + /* + * I don't think we should be backfilling unlogged indexes. + */ + Assert(indexRelation->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED); + + /* Roll back any GUC changes executed by index functions */ + AtEOXact_GUC(false, save_nestlevel); + + /* Restore userid and security context */ + SetUserIdAndSecContext(save_userid, save_sec_context); + + return result->index_tuples; +} + +double +IndexBackfillHeapRangeScan(Relation table_rel, + Relation index_rel, + IndexInfo *index_info, + YbIndexBuildCallback ybcallback, + void *callback_state, + YbBackfillInfo *bfinfo, + YbPgExecOutParam *bfresult) +{ + return table_rel->rd_tableam->index_build_range_scan(table_rel, + index_rel, + index_info, + true /* allow_sync */ , + false /* any_visible */ , + false /* progress */ , + 0 /* start_blockno */ , + InvalidBlockNumber /* num_blocks */ , + NULL, + callback_state, + NULL, /* scan */ + bfinfo, + bfresult, + ybcallback); +} + /* * IndexCheckExclusion - verify that a new exclusion constraint is satisfied * @@ -3574,10 +3879,18 @@ IndexGetRelation(Oid indexId, bool missing_ok) /* * reindex_index - This routine is used to recreate a single index + * + * preserved_index_split_options: + * During ALTER TYPE on columns with dependent indexes, indexes are rebuilt by + * dropping and recreating them. We skip DocDB index table creation during the rebuild + * phase and defer it to the reindex phase. Since DocDB tables don't exist during the + * reindex phase, split options must be retrieved beforehand and passed via these + * parallel lists to restore correct split options. */ void reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, - ReindexParams *params) + ReindexParams *params, bool is_yb_table_rewrite, + bool yb_copy_split_options, YbOptSplit *preserved_index_split_options) { Relation iRel, heapRelation; @@ -3668,8 +3981,12 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, /* * Partitioned indexes should never get processed here, as they have no * physical storage. + * YB: We want to allow reindexes on partitioned indexes during + * table rewrite to avoid schema inconsistencies during backup/restore + * (see GH#24458). */ - if (iRel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) + if (iRel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX && + !is_yb_table_rewrite) elog(ERROR, "cannot reindex partitioned index \"%s.%s\"", get_namespace_name(RelationGetNamespace(iRel)), RelationGetRelationName(iRel)); @@ -3683,6 +4000,57 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot reindex temporary tables of other sessions"))); + /* + * YB pk indexes share the same storage as their tables, so it is not + * possible to reindex them. However, this code-path may be internally + * invoked by table rewrite, and we need to reset the index's reltuples. + */ + if (!is_yb_table_rewrite && iRel->rd_index->indisprimary && + IsYBRelation(iRel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot reindex nontemporary pk indexes"), + errdetail("Primary key indexes share the same storage as their" + " table for Yugabyte-backed relations."))); + + /* + * Supporting shared index could be complicated, so skip for now. + */ + if (iRel->rd_rel->relisshared) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot reindex shared system indexes"))); + + /* + * Since YB REINDEX currently doesn't take locks, it is not a safe + * operation. Chance of failure or corruption is relatively high. + * Mitigate negative affects by making it a two-step process: + * 1. run the reindex on nonpublic index + * 2. make the index public + * In case (1) fails (e.g. duplicate key violation), things can be fixed or + * changed then reindexed again. After manually checking the reindex has + * no corruption (since the index build is not online), (2) can be done. + * + * Checking indisvalid helps catch reindex on public index, but it is not + * foolproof. For example, the index could be made public while the + * reindex is happening. + * + * indisvalid and indisready should be true for best chance of avoiding + * corruption. + * + * NOTE: reindex is permitted internally on public indexes when the indexed + * table is being rewritten. + */ + if (!is_yb_table_rewrite && iRel->rd_index->indisvalid + && IsYBRelation(iRel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot reindex public indexes"), + errdetail("For safety, indexes should not be serving reads" + " during REINDEX."), + errhint("Run UPDATE pg_index SET indisvalid = false WHERE" + " indexrelid = ''::regclass."))); + /* * Don't allow reindex of an invalid index on TOAST table. This is a * leftover from a failed REINDEX CONCURRENTLY, and if rebuilt it would @@ -3761,13 +4129,27 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, /* Suppress use of the target index while rebuilding it */ SetReindexProcessing(heapId, indexId); - /* Create a new physical relation for the index */ - RelationSetNewRelfilenode(iRel, persistence); + if (YbUseUnsafeTruncate(heapRelation)) + YbUnsafeTruncate(iRel); + else + { + /* Create a new physical relation for the index */ + RelationSetNewRelfilenode(iRel, persistence, yb_copy_split_options, + preserved_index_split_options); + } /* Initialize the index and rebuild */ /* Note: we do not need to re-establish pkey setting */ - index_build(heapRelation, iRel, indexInfo, true, true); - + /* + * YB: Partitioned indexes can reach here as we allow reindexes on + * them during table rewrite. However, we don't actually want to + * do an index build for them (like PG), so skip this. + */ + if (!(IsYBRelation(iRel) && + iRel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)) + { + index_build(heapRelation, iRel, indexInfo, true, true); + } /* Re-allow use of target index */ ResetReindexProcessing(); @@ -3803,8 +4185,12 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, * usability horizon must be advanced to the current transaction on every * build or rebuild. pg_index is OK in this regard because catalog tables * are not subject to early cleanup. + * YB: Partitioned indexes can reach here as we allow reindexes on + * them during table rewrite. However, we don't actually perform an index + * build on them (like PG), so we shouldn't update their pg_index entries. */ - if (!skipped_constraint) + if (!skipped_constraint && !(IsYBRelation(iRel) && + iRel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)) { Relation pg_index; HeapTuple indexTuple; @@ -3905,9 +4291,18 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, * Returns true if any indexes were rebuilt (including toast table's index * when relevant). Note that a CommandCounterIncrement will occur after each * index rebuild. + * + * changedIndexNames and changedIndexSplitOpts: + * During ALTER TYPE on columns with dependent indexes, the indexes are first rebuilt by + * dropping and recreating them. We skip DocDB index creation during the rebuild + * phase and defer it to the reindex phase. Since DocDB tables don't exist during the + * reindex phase, split options must be retrieved beforehand and passed via these + * parallel lists to restore correct split options. */ bool -reindex_relation(Oid relid, int flags, ReindexParams *params) +reindex_relation(Oid relid, int flags, ReindexParams *params, bool is_yb_table_rewrite, + bool yb_copy_split_options, List *changedIndexNames, + List *changedIndexSplitOpts) { Relation rel; Oid toast_relid; @@ -3934,8 +4329,11 @@ reindex_relation(Oid relid, int flags, ReindexParams *params) /* * Partitioned tables should never get processed here, as they have no * physical storage. + * YB: We want to allow reindexes on partitioned indexes during + * table rewrite to avoid schema inconsistencies during backup/restore + * (see GH#24458). */ - if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !is_yb_table_rewrite) elog(ERROR, "cannot reindex partitioned table \"%s.%s\"", get_namespace_name(RelationGetNamespace(rel)), RelationGetRelationName(rel)); @@ -3979,6 +4377,28 @@ reindex_relation(Oid relid, int flags, ReindexParams *params) Oid indexOid = lfirst_oid(indexId); Oid indexNamespaceId = get_rel_namespace(indexOid); + /* TODO(fizaa): add YB prefix to iRel. */ + Relation iRel = index_open(indexOid, AccessExclusiveLock); + + char *currentIndexName = RelationGetRelationName(iRel); + + if (IsYBRelation(iRel)) + { + if (!is_yb_table_rewrite && !iRel->rd_index->indisprimary) + /* + * Drop the old DocDB table associated with this index. + * This is only required for secondary indexes, because a + * primary index in YB doesn't have a DocDB table separate + * from the base relation's table. + * If this is a table rewrite, the indexes on the table + * will automatically be dropped when the table is dropped. + * Note: The drop isn't finalized until after the txn + * commits/aborts. + */ + YBCDropIndex(iRel); + } + index_close(iRel, AccessExclusiveLock); + /* * Skip any invalid indexes on a TOAST table. These can only be * duplicate leftovers from a failed REINDEX CONCURRENTLY, and if @@ -4003,8 +4423,27 @@ reindex_relation(Oid relid, int flags, ReindexParams *params) continue; } + YbOptSplit *preserved_index_split_options = NULL; + if (changedIndexNames != NIL && changedIndexSplitOpts != NIL) + { + ListCell *lc_name; + ListCell *lc_split; + + forboth(lc_name, changedIndexNames, + lc_split, changedIndexSplitOpts) + { + char *savedIndexName = (char *) lfirst(lc_name); + if (strcmp(savedIndexName, currentIndexName) == 0) + { + preserved_index_split_options = (YbOptSplit *) lfirst(lc_split); + break; + } + } + } + reindex_index(indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS), - persistence, params); + persistence, params, is_yb_table_rewrite, + yb_copy_split_options, preserved_index_split_options); CommandCounterIncrement(); @@ -4040,7 +4479,11 @@ reindex_relation(Oid relid, int flags, ReindexParams *params) newparams.options &= ~(REINDEXOPT_MISSING_OK); newparams.tablespaceOid = InvalidOid; - result |= reindex_relation(toast_relid, flags, &newparams); + result |= reindex_relation(toast_relid, flags, &newparams, + false /* is_yb_table_rewrite */ , + yb_copy_split_options, + NIL /* changedIndexNames */ , + NIL /* changedIndexSplitOpts */ ); } return result; @@ -4240,3 +4683,14 @@ RestoreReindexState(void *reindexstate) /* Note the worker has its own transaction nesting level */ reindexingNestLevel = GetCurrentTransactionNestLevel(); } + +/* + * YB Wrapper on index_update_stats to expose this static function. + */ +void +yb_index_update_stats(Relation rel, + bool hasindex, + double reltuples) +{ + index_update_stats(rel, hasindex, reltuples); +} diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c index 0b9209332263b..1955d1a8506b9 100644 --- a/src/backend/catalog/indexing.c +++ b/src/backend/catalog/indexing.c @@ -24,6 +24,13 @@ #include "executor/executor.h" #include "utils/rel.h" +/* YB includes */ +#include "access/yb_scan.h" +#include "executor/ybModifyTable.h" +#include "miscadmin.h" +#include "pg_yb_utils.h" +#include "utils/syscache.h" + /* * CatalogOpenIndexes - open the indexes on a system catalog. @@ -70,9 +77,14 @@ CatalogCloseIndexes(CatalogIndexState indstate) * This should be called for each inserted or updated catalog tuple. * * This is effectively a cut-down version of ExecInsertIndexTuples. + * + * if yb_shared_insert is specified, this insert will be done in every + * database (including template0 and template1). This is needed when + * creating shared relations. + * This flag should not be used during initdb bootstrap. */ static void -CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple) +CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple, bool yb_shared_insert) { int i; int numIndexes; @@ -116,6 +128,13 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple) IndexInfo *indexInfo; Relation index; + /* + * No need to update YugaByte primary key which is intrinic part of + * the base table. + */ + if (IsYugaByteEnabled() && relationDescs[i]->rd_index->indisprimary) + continue; + indexInfo = indexInfoArray[i]; index = relationDescs[i]; @@ -159,11 +178,100 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple) values, /* array of index Datums */ isnull, /* is-null flags */ &(heapTuple->t_self), /* tid of heap tuple */ + heapTuple->t_ybctid, heapRelation, index->rd_index->indisunique ? UNIQUE_CHECK_YES : UNIQUE_CHECK_NO, false, - indexInfo); + indexInfo, + yb_shared_insert); + } + + ExecDropSingleTupleTableSlot(slot); +} + +/* + * CatalogIndexDelete - delete index entries for one catalog tuple + * + * This should be called for each updated or deleted catalog tuple. + * + * This is effectively a cut-down version of ExecDeleteIndexTuples. + */ +static void +CatalogIndexDelete(CatalogIndexState indstate, HeapTuple heapTuple) +{ + int i; + int numIndexes; + RelationPtr relationDescs; + Relation heapRelation; + TupleTableSlot *slot; + IndexInfo **indexInfoArray; + Datum values[INDEX_MAX_KEYS]; + bool isnull[INDEX_MAX_KEYS]; + + /* + * Get information from the state structure. Fall out if nothing to do. + */ + numIndexes = indstate->ri_NumIndices; + if (numIndexes == 0) + return; + relationDescs = indstate->ri_IndexRelationDescs; + indexInfoArray = indstate->ri_IndexRelationInfo; + heapRelation = indstate->ri_RelationDesc; + + /* Need a slot to hold the tuple being examined */ + slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation), + &TTSOpsHeapTuple); + ExecStoreHeapTuple(heapTuple, slot, false); + + /* + * for each index, form and delete the index tuple + */ + for (i = 0; i < numIndexes; i++) + { + /* + * No need to update YugaByte primary key which is intrinic part of + * the base table. + */ + if (IsYugaByteEnabled() && relationDescs[i]->rd_index->indisprimary) + continue; + + IndexInfo *indexInfo; + + indexInfo = indexInfoArray[i]; + + /* If the index is marked as read-only, ignore it */ + if (!indexInfo->ii_ReadyForInserts) + continue; + + /* + * Expressional and partial indexes on system catalogs are not + * supported, nor exclusion constraints, nor deferred uniqueness + */ + Assert(indexInfo->ii_Expressions == NIL); + Assert(indexInfo->ii_Predicate == NIL); + Assert(indexInfo->ii_ExclusionOps == NULL); + Assert(relationDescs[i]->rd_index->indimmediate); + + /* + * FormIndexDatum fills in its values and isnull parameters with the + * appropriate values for the column(s) of the index. + */ + FormIndexDatum(indexInfo, + slot, + NULL, /* no expression eval to do */ + values, + isnull); + + /* + * The index AM does the rest. + */ + yb_index_delete(relationDescs[i], /* index relation */ + values, /* array of index Datums */ + isnull, /* is-null flags */ + HEAPTUPLE_YBCTID(heapTuple), /* heap tuple */ + heapRelation, + indexInfo); } ExecDropSingleTupleTableSlot(slot); @@ -220,6 +328,11 @@ CatalogTupleCheckConstraints(Relation heapRel, HeapTuple tup) void CatalogTupleInsert(Relation heapRel, HeapTuple tup) { + if (IsYugaByteEnabled()) + { + return YBCatalogTupleInsert(heapRel, tup, false); + } + CatalogIndexState indstate; CatalogTupleCheckConstraints(heapRel, tup); @@ -228,7 +341,66 @@ CatalogTupleInsert(Relation heapRel, HeapTuple tup) simple_heap_insert(heapRel, tup); - CatalogIndexInsert(indstate, tup); + CatalogIndexInsert(indstate, tup, false /* yb_shared_insert */ ); + CatalogCloseIndexes(indstate); +} + +/* + * Enhanced version of CatalogTupleInsert. + * + * if yb_shared_insert is specified, this insert will be done in every + * database (including template0 and template1). This is needed when + * creating shared relations. + * This flag should not be used during initdb bootstrap. + */ +void +YBCatalogTupleInsert(Relation heapRel, HeapTuple tup, bool yb_shared_insert) +{ + CatalogIndexState indstate; + + CatalogTupleCheckConstraints(heapRel, tup); + + /* Keep ybctid consistent across all databases. */ + Datum ybctid = 0; + + if (yb_shared_insert) + { + if (!IsYsqlUpgrade) + elog(ERROR, "shared insert cannot be done outside of YSQL upgrade"); + + YB_FOR_EACH_DB(pg_db_tuple) + { + Oid dboid = ((Form_pg_database) GETSTRUCT(pg_db_tuple))->oid; + + /* + * Since this is a catalog table, we assume it exists in all databases. + * YB doesn't use PG locks so it's okay not to take them. + */ + if (dboid == YBCGetDatabaseOid(heapRel)) + continue; /* Will be done after the loop. */ + + YBCExecuteInsertHeapTupleForDb(dboid, + heapRel, + tup, + ONCONFLICT_NONE, + &ybctid, + YB_TRANSACTIONAL); + } + YB_FOR_EACH_DB_END; + } + + YBCExecuteInsertHeapTupleForDb(YBCGetDatabaseOid(heapRel), + heapRel, + tup, + ONCONFLICT_NONE, + &ybctid, + YB_TRANSACTIONAL); + + /* Update the local cache automatically */ + YbSetSysCacheTuple(heapRel, tup); + + indstate = CatalogOpenIndexes(heapRel); + CatalogIndexInsert(indstate, tup, yb_shared_insert); CatalogCloseIndexes(indstate); } @@ -239,16 +411,60 @@ CatalogTupleInsert(Relation heapRel, HeapTuple tup) * CatalogCloseIndexes work across multiple insertions. At some point we * might cache the CatalogIndexState data somewhere (perhaps in the relcache) * so that callers needn't trouble over this ... but we don't do so today. + * + * if yb_shared_insert is specified, this insert will be done in every + * database (including template0 and template1). This is needed when + * creating shared relations. + * This flag should not be used during initdb bootstrap. */ void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup, - CatalogIndexState indstate) + CatalogIndexState indstate, bool yb_shared_insert) { CatalogTupleCheckConstraints(heapRel, tup); - simple_heap_insert(heapRel, tup); + if (IsYugaByteEnabled()) + { + /* Keep ybctid consistent across all databases. */ + Datum ybctid = 0; + + if (yb_shared_insert) + { + if (!IsYsqlUpgrade) + elog(ERROR, "shared insert cannot be done outside of YSQL upgrade"); + + YB_FOR_EACH_DB(pg_db_tuple) + { + Oid dboid = ((Form_pg_database) GETSTRUCT(pg_db_tuple))->oid; + + /* + * Since this is a catalog table, we assume it exists in all databases. + * YB doesn't use PG locks so it's okay not to take them. + */ + if (dboid == YBCGetDatabaseOid(heapRel)) + continue; /* Will be done after the loop. */ + YBCExecuteInsertHeapTupleForDb(dboid, heapRel, tup, + ONCONFLICT_NONE, &ybctid, + YB_TRANSACTIONAL); + } + YB_FOR_EACH_DB_END; + } + YBCExecuteInsertHeapTupleForDb(YBCGetDatabaseOid(heapRel), + heapRel, + tup, + ONCONFLICT_NONE, + &ybctid, + YB_TRANSACTIONAL); + + /* Update the local cache automatically */ + YbSetSysCacheTuple(heapRel, tup); + } + else + { + simple_heap_insert(heapRel, tup); + } - CatalogIndexInsert(indstate, tup); + CatalogIndexInsert(indstate, tup, yb_shared_insert); } /* @@ -259,12 +475,31 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup, */ void CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot, - int ntuples, CatalogIndexState indstate) + int ntuples, CatalogIndexState indstate, + bool yb_shared_insert) { /* Nothing to do */ if (ntuples <= 0) return; + if (IsYugaByteEnabled()) + { + bool shouldFree; + HeapTuple tuple; + + for (int i = 0; i < ntuples; i++) + { + tuple = ExecFetchSlotHeapTuple(slot[i], false, &shouldFree); + tuple->t_tableOid = slot[i]->tts_tableOid; + CatalogTupleInsertWithInfo(heapRel, tuple, indstate, + yb_shared_insert); + + if (shouldFree) + heap_freetuple(tuple); + } + return; + } + heap_multi_insert(heapRel, slot, ntuples, GetCurrentCommandId(true), 0, NULL); @@ -279,13 +514,36 @@ CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot, tuple = ExecFetchSlotHeapTuple(slot[i], true, &should_free); tuple->t_tableOid = slot[i]->tts_tableOid; - CatalogIndexInsert(indstate, tuple); + CatalogIndexInsert(indstate, tuple, yb_shared_insert); if (should_free) heap_freetuple(tuple); } } +static void +YBCatalogTupleUpdate(Relation heapRel, HeapTuple tup, + CatalogIndexState indstate) +{ + HeapTuple oldtup = NULL; + bool has_indices = YBRelHasSecondaryIndices(heapRel); + + Assert(HEAPTUPLE_YBCTID(tup)); + + if (has_indices) + { + YbFetchHeapTuple(heapRel, HEAPTUPLE_YBCTID(tup), &oldtup); + CatalogIndexDelete(indstate, oldtup); + } + + YBCUpdateSysCatalogTuple(heapRel, oldtup, tup); + /* Update the local cache automatically */ + YbSetSysCacheTuple(heapRel, tup); + + if (has_indices) + CatalogIndexInsert(indstate, tup, false /* yb_shared_insert */ ); +} + /* * CatalogTupleUpdate - do heap and indexing work for updating a catalog tuple * @@ -306,9 +564,17 @@ CatalogTupleUpdate(Relation heapRel, ItemPointer otid, HeapTuple tup) indstate = CatalogOpenIndexes(heapRel); - simple_heap_update(heapRel, otid, tup); + if (IsYugaByteEnabled()) + { + YBCatalogTupleUpdate(heapRel, tup, indstate); + } + else + { + simple_heap_update(heapRel, otid, tup); + + CatalogIndexInsert(indstate, tup, false /* yb_shared_insert */ ); + } - CatalogIndexInsert(indstate, tup); CatalogCloseIndexes(indstate); } @@ -326,9 +592,18 @@ CatalogTupleUpdateWithInfo(Relation heapRel, ItemPointer otid, HeapTuple tup, { CatalogTupleCheckConstraints(heapRel, tup); - simple_heap_update(heapRel, otid, tup); + if (IsYugaByteEnabled()) + { + YBCatalogTupleUpdate(heapRel, tup, indstate); + } + else + { + CatalogTupleCheckConstraints(heapRel, tup); + + simple_heap_update(heapRel, otid, tup); - CatalogIndexInsert(indstate, tup); + CatalogIndexInsert(indstate, tup, false /* yb_shared_insert */ ); + } } /* @@ -347,7 +622,19 @@ CatalogTupleUpdateWithInfo(Relation heapRel, ItemPointer otid, HeapTuple tup, * it might be better to do something about caching CatalogIndexState. */ void -CatalogTupleDelete(Relation heapRel, ItemPointer tid) +CatalogTupleDelete(Relation heapRel, HeapTuple yb_tup) { - simple_heap_delete(heapRel, tid); + if (IsYugaByteEnabled()) + { + YBCDeleteSysCatalogTuple(heapRel, yb_tup); + + CatalogIndexState indstate = CatalogOpenIndexes(heapRel); + + CatalogIndexDelete(indstate, yb_tup); + CatalogCloseIndexes(indstate); + } + else + { + simple_heap_delete(heapRel, &yb_tup->t_self); + } } diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 2ea7c1393ffa9..ad698ae47a57f 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -59,6 +59,10 @@ #include "utils/syscache.h" #include "utils/varlena.h" +/* YB includes */ +#include "pg_yb_utils.h" +#include "utils/uuid.h" + /* * The namespace search path is a possibly-empty list of namespace OIDs. @@ -198,6 +202,10 @@ static SubTransactionId myTempNamespaceSubID = InvalidSubTransactionId; */ char *namespace_search_path = NULL; +typedef struct YbTempNamespaceSuffixBuffer +{ + char data[UUID_LEN * 2 + 2]; +} YbTempNamespaceSuffixBuffer; /* Local functions */ static void recomputeNamespacePath(void); @@ -209,6 +217,7 @@ static void NamespaceCallback(Datum arg, int cacheid, uint32 hashvalue); static bool MatchNamedCall(HeapTuple proctup, int nargs, List *argnames, bool include_out_arguments, int pronargs, int **argnumbers); +static char *YbBuildTempNameSuffix(YbTempNamespaceSuffixBuffer *buf); /* @@ -244,6 +253,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, Oid oldRelId = InvalidOid; bool retry = false; bool missing_ok = (flags & RVR_MISSING_OK) != 0; + uint64 yb_inval_count; /* verify that flags do no conflict */ Assert(!((flags & RVR_NOWAIT) && (flags & RVR_SKIP_LOCKED))); @@ -285,6 +295,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, * have been processed that might require a do-over. */ inval_count = SharedInvalidMessageCounter; + yb_inval_count = YbGetCatCacheDeltaRefreshes(); /* * Some non-default relpersistence value may have been specified. The @@ -408,7 +419,8 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, /* * If no invalidation message were processed, we're done! */ - if (inval_count == SharedInvalidMessageCounter) + if (inval_count == SharedInvalidMessageCounter && + yb_inval_count == YbGetCatCacheDeltaRefreshes()) break; /* @@ -543,6 +555,7 @@ RangeVarGetAndCheckCreationNamespace(RangeVar *relation, Oid nspid; Oid oldnspid = InvalidOid; bool retry = false; + uint64 yb_inval_count; /* * We check the catalog name and then ignore it. @@ -568,6 +581,7 @@ RangeVarGetAndCheckCreationNamespace(RangeVar *relation, AclResult aclresult; inval_count = SharedInvalidMessageCounter; + yb_inval_count = YbGetCatCacheDeltaRefreshes(); /* Look up creation namespace and check for existing relation. */ nspid = RangeVarGetCreationNamespace(relation); @@ -620,7 +634,8 @@ RangeVarGetAndCheckCreationNamespace(RangeVar *relation, } /* If no invalidation message were processed, we're done! */ - if (inval_count == SharedInvalidMessageCounter) + if (inval_count == SharedInvalidMessageCounter && + yb_inval_count == YbGetCatCacheDeltaRefreshes()) break; /* Something may have changed, so recheck our work. */ @@ -3998,6 +4013,9 @@ InitTempTableNamespace(void) Assert(!OidIsValid(myTempNamespace)); + if (IsYugaByteEnabled()) + YBCRecordTempRelationDDL(); + /* * First, do permission check to see if we are authorized to make temp * tables. We use a nonstandard error message here since "databasename: @@ -4036,7 +4054,23 @@ InitTempTableNamespace(void) (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION), errmsg("cannot create temporary tables during a parallel operation"))); - snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", MyBackendId); + + /* + * In YB, pg_temp_ and pg_toast_temp_ are not + * unique temp namespace names for the backend if there are multiple nodes. + * So, we add the local tserver uuid as an additional suffix to + * the namespace names to make them unique to the backend. + * The constructed temp suffix is "_" and the namespace + * names are pg_temp__ and + * pg_toast_temp__. + */ + YbTempNamespaceSuffixBuffer ybSuffixBuf; + const char *yb_temp_namespace_suffix = (IsYugaByteEnabled() ? + YbBuildTempNameSuffix(&ybSuffixBuf) : + ""); + + snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%s%d", + yb_temp_namespace_suffix, MyBackendId); namespaceId = get_namespace_oid(namespaceName, true); if (!OidIsValid(namespaceId)) @@ -4068,8 +4102,8 @@ InitTempTableNamespace(void) * it. (We assume there is no need to clean it out if it does exist, since * dropping a parent table should make its toast table go away.) */ - snprintf(namespaceName, sizeof(namespaceName), "pg_toast_temp_%d", - MyBackendId); + snprintf(namespaceName, sizeof(namespaceName), "pg_toast_temp_%s%d", + yb_temp_namespace_suffix, MyBackendId); toastspaceId = get_namespace_oid(namespaceName, true); if (!OidIsValid(toastspaceId)) @@ -4279,11 +4313,28 @@ RemoveTempRelations(Oid tempNamespaceId) object.objectId = tempNamespaceId; object.objectSubId = 0; + bool yb_use_regular_txn_block = YBIsDdlTransactionBlockEnabled(); + + if (IsYugaByteEnabled()) + { + if (yb_use_regular_txn_block) + YBAddDdlTxnState(YB_DDL_MODE_SILENT_ALTERING); + else + YBIncrementDdlNestingLevel(YB_DDL_MODE_SILENT_ALTERING); + YBCRecordTempRelationDDL(); + } performDeletion(&object, DROP_CASCADE, PERFORM_DELETION_INTERNAL | PERFORM_DELETION_QUIETLY | PERFORM_DELETION_SKIP_ORIGINAL | PERFORM_DELETION_SKIP_EXTENSIONS); + if (IsYugaByteEnabled()) + { + if (yb_use_regular_txn_block) + YBMergeDdlTxnState(); + else + YBDecrementDdlNestingLevel(); + } } /* @@ -4668,3 +4719,34 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS) PG_RETURN_BOOL(isOtherTempNamespace(oid)); } + +static char * +YbConvertToHex(const unsigned char *src, size_t len, char *dest) +{ + static const char hex_chars[] = "0123456789abcdef"; + + for (size_t i = 0; i < len; ++i) + { + const int high = src[i] >> 4; + const int low = src[i] & 0x0F; + + *(dest++) = hex_chars[high]; + *(dest++) = hex_chars[low]; + } + return dest; +} + +/* + * Used in YB to construct the temporary namespace suffix. This function + * returns the local tserver uuid as a regular string (without the hyphens), + * and an additional "_" appended at the end. + */ +static char * +YbBuildTempNameSuffix(YbTempNamespaceSuffixBuffer *buf) +{ + char *tail = YbConvertToHex(YbGetLocalTServerUuid(), UUID_LEN, buf->data); + + *(tail++) = '_'; + *tail = 0; + return buf->data; +} diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index d85e9f8f78d35..9cf9e55a604f7 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -90,6 +90,14 @@ #include "utils/regproc.h" #include "utils/syscache.h" +/* YB includes */ +#include "catalog/pg_yb_profile.h" +#include "catalog/pg_yb_role_profile.h" +#include "catalog/pg_yb_tablegroup.h" +#include "commands/yb_profile.h" +#include "commands/yb_tablegroup.h" +#include "pg_yb_utils.h" + /* * ObjectProperty * @@ -428,6 +436,20 @@ static const ObjectPropertyType ObjectProperty[] = OBJECT_TABLE, true }, + { + "tablegroup", + YbTablegroupRelationId, + YbTablegroupOidIndexId, + YBTABLEGROUPOID, + -1, + Anum_pg_yb_tablegroup_oid, + Anum_pg_yb_tablegroup_grpname, + InvalidAttrNumber, + Anum_pg_yb_tablegroup_grpowner, + Anum_pg_yb_tablegroup_grpacl, + OBJECT_YBTABLEGROUP, + true + }, { "tablespace", TableSpaceRelationId, @@ -618,6 +640,20 @@ static const ObjectPropertyType ObjectProperty[] = OBJECT_USER_MAPPING, false }, + { + "ybprofile", + YbProfileRelationId, + YbProfileOidIndexId, + -1, + -1, + Anum_pg_yb_profile_oid, + Anum_pg_yb_profile_prfname, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + OBJECT_YBPROFILE, + true + }, }; /* @@ -791,6 +827,10 @@ static const struct object_type_map { "database", OBJECT_DATABASE }, + /* OCLASS_YBTBLGROUP */ + { + "tablegroup", OBJECT_YBTABLEGROUP + }, /* OCLASS_TBLSPACE */ { "tablespace", OBJECT_TABLESPACE @@ -850,6 +890,10 @@ static const struct object_type_map /* OCLASS_STATISTIC_EXT */ { "statistics object", OBJECT_STATISTIC_EXT + }, + /* OBJECT_YBPROFILE */ + { + "profile", OBJECT_YBPROFILE } }; @@ -945,6 +989,7 @@ get_object_address(ObjectType objtype, Node *object, ObjectAddress old_address = {InvalidOid, InvalidOid, 0}; Relation relation = NULL; uint64 inval_count; + uint64 yb_inval_count; /* Some kind of lock must be taken. */ Assert(lockmode != NoLock); @@ -957,6 +1002,7 @@ get_object_address(ObjectType objtype, Node *object, * been processed that might require a do-over. */ inval_count = SharedInvalidMessageCounter; + yb_inval_count = YbGetCatCacheDeltaRefreshes(); /* Look up object address. */ switch (objtype) @@ -1022,6 +1068,8 @@ get_object_address(ObjectType objtype, Node *object, case OBJECT_ACCESS_METHOD: case OBJECT_PUBLICATION: case OBJECT_SUBSCRIPTION: + case OBJECT_YBTABLEGROUP: + case OBJECT_YBPROFILE: address = get_object_address_unqualified(objtype, castNode(String, object), missing_ok); break; @@ -1219,7 +1267,8 @@ get_object_address(ObjectType objtype, Node *object, * up no longer refers to the object we locked, so we retry the lookup * and see whether we get the same answer. */ - if (inval_count == SharedInvalidMessageCounter || relation != NULL) + if ((inval_count == SharedInvalidMessageCounter && + yb_inval_count == YbGetCatCacheDeltaRefreshes()) || relation != NULL) break; old_address = address; } @@ -1285,6 +1334,11 @@ get_object_address_unqualified(ObjectType objtype, address.objectId = get_extension_oid(name, missing_ok); address.objectSubId = 0; break; + case OBJECT_YBTABLEGROUP: + address.classId = YbTablegroupRelationId; + address.objectId = get_tablegroup_oid(name, missing_ok); + address.objectSubId = 0; + break; case OBJECT_TABLESPACE: address.classId = TableSpaceRelationId; address.objectId = get_tablespace_oid(name, missing_ok); @@ -1335,6 +1389,11 @@ get_object_address_unqualified(ObjectType objtype, address.objectId = get_subscription_oid(name, missing_ok); address.objectSubId = 0; break; + case OBJECT_YBPROFILE: + address.classId = YbProfileRelationId; + address.objectId = yb_get_profile_oid(name, missing_ok); + address.objectSubId = 0; + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); /* placate compiler, which doesn't know elog won't return */ @@ -2020,16 +2079,20 @@ get_object_address_defacl(List *object, bool missing_ok) case DEFACLOBJ_NAMESPACE: objtype_str = "schemas"; break; + case DEFACLOBJ_TABLEGROUP: + objtype_str = "tablegroups"; + break; default: ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognized default ACL object type \"%c\"", objtype), - errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".", + errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".", DEFACLOBJ_RELATION, DEFACLOBJ_SEQUENCE, DEFACLOBJ_FUNCTION, DEFACLOBJ_TYPE, - DEFACLOBJ_NAMESPACE))); + DEFACLOBJ_NAMESPACE, + DEFACLOBJ_TABLEGROUP))); } /* @@ -2247,6 +2310,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) errmsg("name list length must be exactly %d", 1))); /* fall through to check args length */ /* FALLTHROUGH */ + yb_switch_fallthrough(); case OBJECT_DOMCONSTRAINT: case OBJECT_CAST: case OBJECT_PUBLICATION_REL: @@ -2272,6 +2336,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) errmsg("name list length must be at least %d", 3))); /* fall through to check args length */ /* FALLTHROUGH */ + yb_switch_fallthrough(); case OBJECT_OPERATOR: if (list_length(args) != 2) ereport(ERROR, @@ -2310,6 +2375,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) case OBJECT_TABCONSTRAINT: case OBJECT_OPCLASS: case OBJECT_OPFAMILY: + case OBJECT_YBPROFILE: objnode = (Node *) name; break; case OBJECT_ACCESS_METHOD: @@ -2324,6 +2390,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) case OBJECT_ROLE: case OBJECT_SCHEMA: case OBJECT_SUBSCRIPTION: + case OBJECT_YBTABLEGROUP: case OBJECT_TABLESPACE: if (list_length(name) != 1) ereport(ERROR, @@ -2421,13 +2488,18 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, case OBJECT_FOREIGN_TABLE: case OBJECT_COLUMN: case OBJECT_RULE: - case OBJECT_TRIGGER: case OBJECT_POLICY: case OBJECT_TABCONSTRAINT: if (!pg_class_ownercheck(RelationGetRelid(relation), roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, objtype, RelationGetRelationName(relation)); break; + case OBJECT_TRIGGER: + if (!pg_class_ownercheck(RelationGetRelid(relation), roleid) && + !IsYbDbAdminUser(roleid)) + aclcheck_error(ACLCHECK_NOT_OWNER, objtype, + RelationGetRelationName(relation)); + break; case OBJECT_DATABASE: if (!pg_database_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, objtype, @@ -2476,7 +2548,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, NameListToString((castNode(ObjectWithArgs, object))->objname)); break; case OBJECT_SCHEMA: - if (!pg_namespace_ownercheck(address.objectId, roleid)) + if (!pg_namespace_ownercheck(address.objectId, roleid) && !IsYbDbAdminUser(roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, objtype, strVal(object)); break; @@ -2569,6 +2641,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid); } break; + case OBJECT_YBTABLEGROUP: + if (!pg_tablegroup_ownercheck(address.objectId, roleid)) + aclcheck_error(ACLCHECK_NOT_OWNER, objtype, + strVal(object)); + break; case OBJECT_TABLESPACE: if (!pg_tablespace_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, objtype, @@ -2605,9 +2682,21 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, errmsg("must have CREATEROLE privilege"))); } break; + case OBJECT_ACCESS_METHOD: + /* + * YB: Access methods can be owned by super users (always) or + * extension users (only while creating an extension). + */ + if (!superuser_arg(roleid) && + !(IsYbExtensionUser(GetUserId()) && creating_extension)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser or command must be invoked " + "as part of creating an extension by a member " + "of the yb_extension role"))); + break; case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: - case OBJECT_ACCESS_METHOD: case OBJECT_PARAMETER_ACL: /* We treat these object types as being owned by superusers */ if (!superuser_arg(roleid)) @@ -2620,6 +2709,15 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, aclcheck_error(ACLCHECK_NOT_OWNER, objtype, NameListToString(castNode(List, object))); break; + case OBJECT_YBPROFILE: + /* A profile can be dropped by the super user or yb_db_admin */ + if (!superuser() && !IsYbDbAdminUser(GetUserId())) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to drop profile"), + errhint("Must be superuser or a member of the" + " yb_db_admin role to drop a profile."))); + break; default: elog(ERROR, "unrecognized object type: %d", (int) objtype); @@ -3711,6 +3809,18 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) break; } + case OCLASS_YBTBLGROUP: + { + char *tblgroup; + + tblgroup = get_tablegroup_name(object->objectId); + if (!tblgroup) + elog(ERROR, "cache lookup failed for tablegroup %u", + object->objectId); + appendStringInfo(&buffer, _("tablegroup %s"), tblgroup); + break; + } + case OCLASS_TBLSPACE: { char *tblspace; @@ -3872,6 +3982,17 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) _("default privileges on new schemas belonging to role %s"), rolename); break; + case DEFACLOBJ_TABLEGROUP: + + /* + * Cannot set default perms for tablegroups on a + * per-schema level. Must be per-db. + */ + Assert(!nspname); + appendStringInfo(&buffer, + _("default privileges on new tablegroups belonging to role %s"), + rolename); + break; default: /* shouldn't get here */ if (nspname) @@ -4088,7 +4209,29 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) ReleaseSysCache(trfTup); break; } + case OCLASS_YBPROFILE: + { + char *profile; + + profile = yb_get_profile_name(object->objectId); + appendStringInfo(&buffer, _("profile %s"), profile); + break; + } + case OCLASS_YBROLE_PROFILE: + { + HeapTuple tup = yb_get_role_profile_tuple_by_oid(object->objectId); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for role profile %u", + object->objectId); + Form_pg_yb_role_profile rolprfform = (Form_pg_yb_role_profile) GETSTRUCT(tup); + + appendStringInfo(&buffer, _("association between role \"%s\" and profile %s"), + GetUserNameFromId(rolprfform->rolprfrole, false), + yb_get_profile_name(rolprfform->rolprfprofile)); + break; + } /* * There's intentionally no default: case here; we want the * compiler to warn if a new OCLASS hasn't been handled above. @@ -4588,6 +4731,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok) appendStringInfoString(&buffer, "database"); break; + case OCLASS_YBTBLGROUP: + appendStringInfoString(&buffer, "tablegroup"); + break; + case OCLASS_TBLSPACE: appendStringInfoString(&buffer, "tablespace"); break; @@ -4644,6 +4791,13 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok) appendStringInfoString(&buffer, "transform"); break; + case OCLASS_YBPROFILE: + appendStringInfoString(&buffer, "profile"); + break; + + case OCLASS_YBROLE_PROFILE: + appendStringInfoString(&buffer, "role profile"); + break; /* * There's intentionally no default: case here; we want the * compiler to warn if a new OCLASS hasn't been handled above. @@ -5546,6 +5700,21 @@ getObjectIdentityParts(const ObjectAddress *object, break; } + case OCLASS_YBTBLGROUP: + { + char *tblgroup; + + tblgroup = get_tablegroup_name(object->objectId); + if (!tblgroup) + elog(ERROR, "cache lookup failed for tablegroup %u", + object->objectId); + if (objname) + *objname = list_make1(tblgroup); + appendStringInfoString(&buffer, + quote_identifier(tblgroup)); + break; + } + case OCLASS_TBLSPACE: { char *tblspace; @@ -5708,6 +5877,10 @@ getObjectIdentityParts(const ObjectAddress *object, appendStringInfoString(&buffer, " on schemas"); break; + case DEFACLOBJ_TABLEGROUP: + appendStringInfoString(&buffer, + " on tablegroups"); + break; } if (objname) @@ -5950,6 +6123,32 @@ getObjectIdentityParts(const ObjectAddress *object, table_close(transformDesc, AccessShareLock); } break; + case OCLASS_YBPROFILE: + { + char *profile; + + profile = yb_get_profile_name(object->objectId); + if (objname) + *objname = list_make1(profile); + appendStringInfoString(&buffer, + quote_identifier(profile)); + break; + } + case OCLASS_YBROLE_PROFILE: + { + HeapTuple tup = yb_get_role_profile_tuple_by_oid(object->objectId); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for role profile %u", + object->objectId); + + Form_pg_yb_role_profile rolprfform = (Form_pg_yb_role_profile) GETSTRUCT(tup); + + appendStringInfo(&buffer, _("association between role \"%s\" and profile %s"), + GetUserNameFromId(rolprfform->rolprfrole, false), + yb_get_profile_name(rolprfform->rolprfprofile)); + break; + } /* * There's intentionally no default: case here; we want the diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index c6ec479004876..69b6bdb52bc3d 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -32,6 +32,9 @@ #include "utils/rel.h" #include "utils/syscache.h" +/* YB includes */ +#include "pg_yb_utils.h" + static Oid get_partition_parent_worker(Relation inhRel, Oid relid, bool *detach_pending); static void get_partition_ancestors_worker(Relation inhRel, Oid relid, @@ -227,7 +230,9 @@ map_partition_varattnos(List *expr, int fromrel_varno, bool found_whole_row; part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel), - RelationGetDescr(from_rel)); + RelationGetDescr(from_rel), + false /* yb_ignore_type_mismatch */ ); + expr = (List *) map_variable_attnos((Node *) expr, fromrel_varno, 0, part_attmap, @@ -272,7 +277,7 @@ has_partition_attrs(Relation rel, Bitmapset *attnums, bool *used_in_expr) if (partattno != 0) { - if (bms_is_member(partattno - FirstLowInvalidHeapAttributeNumber, + if (bms_is_member(partattno - YBGetFirstLowInvalidAttributeNumber(rel), attnums)) { if (used_in_expr) @@ -287,7 +292,13 @@ has_partition_attrs(Relation rel, Bitmapset *attnums, bool *used_in_expr) Bitmapset *expr_attrs = NULL; /* Find all attributes referenced */ - pull_varattnos(expr, 1, &expr_attrs); + + /* + * pull_varattnos_min_attr offsets attributes by min_attr - 1. + * Therefore pass YBGetFirstLowInvalidAttributeNumber + 1. + */ + pull_varattnos_min_attr(expr, 1, &expr_attrs, + YBGetFirstLowInvalidAttributeNumber(rel) + 1); partexprs_item = lnext(partexprs, partexprs_item); if (bms_overlap(attnums, expr_attrs)) diff --git a/src/backend/catalog/pg_attrdef.c b/src/backend/catalog/pg_attrdef.c index c5d4a9912ea70..84adbd7c637c4 100644 --- a/src/backend/catalog/pg_attrdef.c +++ b/src/backend/catalog/pg_attrdef.c @@ -30,6 +30,9 @@ #include "utils/rel.h" #include "utils/syscache.h" +/* YB includes */ +#include "pg_yb_utils.h" + /* * Store a default expression for column attnum of relation rel. @@ -302,7 +305,7 @@ RemoveAttrDefaultById(Oid attrdefId) myrel = relation_open(myrelid, AccessExclusiveLock); /* Now we can delete the pg_attrdef row */ - CatalogTupleDelete(attrdef_rel, &tuple->t_self); + CatalogTupleDelete(attrdef_rel, tuple); systable_endscan(scan); table_close(attrdef_rel, RowExclusiveLock); diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index c269ac251ac1e..0829fe39fff65 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -35,6 +35,10 @@ #include "utils/rel.h" #include "utils/syscache.h" +/* YB includes */ +#include "pg_yb_utils.h" +#include "utils/catcache.h" + /* * CreateConstraintEntry @@ -242,7 +246,19 @@ CreateConstraintEntry(const char *constraintName, tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls); - CatalogTupleInsert(conDesc, tup); + if (IsYsqlUpgrade && OidIsValid(relId)) + { + /* + * During YSQL upgrade, the constraint on shared relations should be + * inserted in all databases. + */ + Relation rel = RelationIdGetRelation(relId); + + YBCatalogTupleInsert(conDesc, tup, rel->rd_rel->relisshared); + RelationClose(rel); + } + else + CatalogTupleInsert(conDesc, tup); ObjectAddressSet(conobject, ConstraintRelationId, conOid); @@ -646,7 +662,7 @@ RemoveConstraintById(Oid conId) elog(ERROR, "constraint %u is not of a known type", conId); /* Fry the constraint itself */ - CatalogTupleDelete(conDesc, &tup->t_self); + CatalogTupleDelete(conDesc, tup); /* Clean up */ ReleaseSysCache(tup); @@ -972,7 +988,7 @@ get_relation_constraint_attnos(Oid relid, const char *conname, for (i = 0; i < numcols; i++) { conattnos = bms_add_member(conattnos, - attnums[i] - FirstLowInvalidHeapAttributeNumber); + attnums[i] - YBGetFirstLowInvalidAttributeNumberFromOid(relid)); } } } @@ -1113,22 +1129,35 @@ get_primary_key_attnos(Oid relid, bool deferrableOk, Oid *constraintOid) HeapTuple tuple; SysScanDesc scan; ScanKeyData skey[1]; + YbCatCListIterator iterator; /* Set *constraintOid, to avoid complaints about uninitialized vars */ *constraintOid = InvalidOid; - /* Scan pg_constraint for constraints of the target rel */ - pg_constraint = table_open(ConstraintRelationId, AccessShareLock); + /* + * YB change: Search the catcache here to avoid having to go to master. + * This will trigger a master RPC if this is the first time we're looking + * up the primary key for this relation. + */ + if (IsYugaByteEnabled()) + { + iterator = YbCatCListIteratorBegin(SearchSysCacheList1(YBCONSTRAINTRELIDTYPIDNAME, relid)); + } + else + { + /* Scan pg_constraint for constraints of the target rel */ + pg_constraint = table_open(ConstraintRelationId, AccessShareLock); - ScanKeyInit(&skey[0], - Anum_pg_constraint_conrelid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(relid)); + ScanKeyInit(&skey[0], + Anum_pg_constraint_conrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); - scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId, true, - NULL, 1, skey); + scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId, true, + NULL, 1, skey); + } - while (HeapTupleIsValid(tuple = systable_getnext(scan))) + while (HeapTupleIsValid(tuple = IsYugaByteEnabled() ? YbCatCListIteratorGetNext(&iterator) : systable_getnext(scan))) { Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple); Datum adatum; @@ -1151,8 +1180,9 @@ get_primary_key_attnos(Oid relid, bool deferrableOk, Oid *constraintOid) break; /* Extract the conkey array, ie, attnums of PK's columns */ - adatum = heap_getattr(tuple, Anum_pg_constraint_conkey, - RelationGetDescr(pg_constraint), &isNull); + adatum = IsYugaByteEnabled() + ? SysCacheGetAttr(CONSTROID, tuple, Anum_pg_constraint_conkey, &isNull) + : heap_getattr(tuple, Anum_pg_constraint_conkey, RelationGetDescr(pg_constraint), &isNull); if (isNull) elog(ERROR, "null conkey for constraint %u", ((Form_pg_constraint) GETSTRUCT(tuple))->oid); @@ -1177,9 +1207,14 @@ get_primary_key_attnos(Oid relid, bool deferrableOk, Oid *constraintOid) break; } - systable_endscan(scan); + if (IsYugaByteEnabled()) + YbCatCListIteratorFree(&iterator); + else + { + systable_endscan(scan); - table_close(pg_constraint, AccessShareLock); + table_close(pg_constraint, AccessShareLock); + } return pkattnos; } diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c index 42387f4e304bb..28443df6eb385 100644 --- a/src/backend/catalog/pg_db_role_setting.c +++ b/src/backend/catalog/pg_db_role_setting.c @@ -91,7 +91,7 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt) CatalogTupleUpdate(rel, &tuple->t_self, newtuple); } else - CatalogTupleDelete(rel, &tuple->t_self); + CatalogTupleDelete(rel, tuple); } } else if (HeapTupleIsValid(tuple)) @@ -129,7 +129,7 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt) CatalogTupleUpdate(rel, &tuple->t_self, newtuple); } else - CatalogTupleDelete(rel, &tuple->t_self); + CatalogTupleDelete(rel, tuple); } else if (valuestr) { @@ -199,7 +199,7 @@ DropSetting(Oid databaseid, Oid roleid) scan = table_beginscan_catalog(relsetting, numkeys, keys); while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection))) { - CatalogTupleDelete(relsetting, &tup->t_self); + CatalogTupleDelete(relsetting, tup); } table_endscan(scan); diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c index 89bbb5c9e48bf..45b872697455e 100644 --- a/src/backend/catalog/pg_depend.c +++ b/src/backend/catalog/pg_depend.c @@ -29,6 +29,11 @@ #include "utils/lsyscache.h" #include "utils/rel.h" +/* YB includes */ +#include "catalog/pg_yb_tablegroup_d.h" +#include "pg_yb_utils.h" +#include "utils/syscache.h" + static bool isObjectPinned(const ObjectAddress *object); @@ -141,7 +146,7 @@ recordMultipleDependencies(const ObjectAddress *depender, indstate = CatalogOpenIndexes(dependDesc); CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slot_stored_count, - indstate); + indstate, false /* yb_shared_insert */ ); slot_stored_count = 0; } } @@ -154,7 +159,7 @@ recordMultipleDependencies(const ObjectAddress *depender, indstate = CatalogOpenIndexes(dependDesc); CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slot_stored_count, - indstate); + indstate, false /* yb_shared_insert */ ); } if (indstate != NULL) @@ -326,7 +331,7 @@ deleteDependencyRecordsFor(Oid classId, Oid objectId, ((Form_pg_depend) GETSTRUCT(tup))->deptype == DEPENDENCY_EXTENSION) continue; - CatalogTupleDelete(depRel, &tup->t_self); + CatalogTupleDelete(depRel, tup); count++; } @@ -337,6 +342,84 @@ deleteDependencyRecordsFor(Oid classId, Oid objectId, return count; } +/* + * tablegroupHasDependents -- check if the specified tablegroup has any dependents + */ +bool +tablegroupHasDependents(Oid tablegroupId) +{ + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + bool found = false; + + depRel = table_open(DependRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(YbTablegroupRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(tablegroupId)); + + scan = systable_beginscan(depRel, DependReferenceIndexId, true, + NULL, 2, key); + + found = HeapTupleIsValid(tup = systable_getnext(scan)); + + systable_endscan(scan); + table_close(depRel, RowExclusiveLock); + + return found; +} + +/* + * ybIsTablegroupDependent -- check if the specified relation is dependent on + * the specified tablegroup. + */ +bool +ybIsTablegroupDependent(Oid relOid, Oid tablegroupId) +{ + Relation depRel; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + bool found = false; + + depRel = table_open(DependRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(YbTablegroupRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(tablegroupId)); + + scan = systable_beginscan(depRel, DependReferenceIndexId, true, + NULL, 2, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->objid == relOid) + { + found = true; + break; + } + } + + systable_endscan(scan); + table_close(depRel, RowExclusiveLock); + + return found; +} + /* * deleteDependencyRecordsForClass -- delete all records with given depender * classId/objectId, dependee classId, and deptype. @@ -376,7 +459,7 @@ deleteDependencyRecordsForClass(Oid classId, Oid objectId, if (depform->refclassid == refclassId && depform->deptype == deptype) { - CatalogTupleDelete(depRel, &tup->t_self); + CatalogTupleDelete(depRel, tup); count++; } } @@ -425,7 +508,7 @@ deleteDependencyRecordsForSpecific(Oid classId, Oid objectId, char deptype, depform->refobjid == refobjectId && depform->deptype == deptype) { - CatalogTupleDelete(depRel, &tup->t_self); + CatalogTupleDelete(depRel, tup); count++; } } @@ -527,7 +610,7 @@ changeDependencyFor(Oid classId, Oid objectId, depform->refobjid == oldRefObjectId) { if (newIsPinned) - CatalogTupleDelete(depRel, &tup->t_self); + CatalogTupleDelete(depRel, tup); else { /* make a modifiable copy */ @@ -670,7 +753,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId, while (HeapTupleIsValid((tup = systable_getnext(scan)))) { if (newIsPinned) - CatalogTupleDelete(depRel, &tup->t_self); + CatalogTupleDelete(depRel, tup); else { Form_pg_depend depform; diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c index 114715498dd12..78ad439dd9dad 100644 --- a/src/backend/catalog/pg_enum.c +++ b/src/backend/catalog/pg_enum.c @@ -32,9 +32,17 @@ #include "utils/memutils.h" #include "utils/syscache.h" +/* YB includes */ +#include "catalog/yb_oid_assignment.h" +#include "pg_yb_utils.h" +#include + /* Potentially set by pg_upgrade_support functions */ Oid binary_upgrade_next_pg_enum_oid = InvalidOid; +/* YB: NAN is not a valid sortorder. */ +float4 yb_binary_upgrade_next_pg_enum_sortorder = NAN; + /* * Hash table of enum value OIDs created during the current transaction by * AddEnumLabel. We disallow using these values until the transaction is @@ -80,35 +88,49 @@ EnumValuesCreate(Oid enumTypeOid, List *vals) pg_enum = table_open(EnumRelationId, RowExclusiveLock); - /* - * Allocate OIDs for the enum's members. - * - * While this method does not absolutely guarantee that we generate no - * duplicate OIDs (since we haven't entered each oid into the table before - * allocating the next), trouble could only occur if the OID counter wraps - * all the way around before we finish. Which seems unlikely. - */ oids = (Oid *) palloc(num_elems * sizeof(Oid)); - for (elemno = 0; elemno < num_elems; elemno++) + if (YbUsingEnumLabelOidAssignment()) + { + elemno = 0; + foreach(lc, vals) + { + char *label = strVal(lfirst(lc)); + + oids[elemno] = YbLookupOidAssignmentForEnumLabel(enumTypeOid, label); + elemno++; + } + } + else { /* - * We assign even-numbered OIDs to all the new enum labels. This - * tells the comparison functions the OIDs are in the correct sort - * order and can be compared directly. + * Allocate OIDs for the enum's members. + * + * While this method does not absolutely guarantee that we generate no + * duplicate OIDs (since we haven't entered each oid into the table before + * allocating the next), trouble could only occur if the OID counter wraps + * all the way around before we finish. Which seems unlikely. */ - Oid new_oid; - - do + for (elemno = 0; elemno < num_elems; elemno++) { - new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId, - Anum_pg_enum_oid); - } while (new_oid & 1); - oids[elemno] = new_oid; - } + /* + * We assign even-numbered OIDs to all the new enum labels. This + * tells the comparison functions the OIDs are in the correct sort + * order and can be compared directly. + */ + Oid new_oid; - /* sort them, just in case OID counter wrapped from high to low */ - qsort(oids, num_elems, sizeof(Oid), oid_cmp); + do + { + new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId, + Anum_pg_enum_oid); + } while (new_oid & 1); + oids[elemno] = new_oid; + } + + /* sort them, just in case OID counter wrapped from high to low */ + qsort(oids, num_elems, sizeof(Oid), oid_cmp); + } /* and make the entries */ memset(nulls, false, sizeof(nulls)); @@ -173,7 +195,7 @@ EnumValuesDelete(Oid enumTypeOid) while (HeapTupleIsValid(tup = systable_getnext(scan))) { - CatalogTupleDelete(pg_enum, &tup->t_self); + CatalogTupleDelete(pg_enum, tup); } systable_endscan(scan); @@ -371,12 +393,28 @@ AddEnumLabel(Oid enumTypeOid, } /* Get a new OID for the new label */ - if (IsBinaryUpgrade) + if (IsBinaryUpgrade || yb_binary_restore) { if (!OidIsValid(binary_upgrade_next_pg_enum_oid)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("pg_enum OID value not set when in binary upgrade mode"))); + /* + * YB: For backward compatibility, log a warning messages. Backup + * generated by old ysql_dump would not have emitted a query such as + * SELECT + * pg_catalog.yb_binary_upgrade_set_next_pg_enum_sortorder('2'::real); + * to set the enum sortorder. We do not want to fail the restore + * because of that. + */ + if (isnan(yb_binary_upgrade_next_pg_enum_sortorder)) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("pg_enum sortorder value not set when in binary upgrade mode for %s oid %u", + binary_upgrade_next_pg_enum_oid % 2 ? "odd" : "even", + binary_upgrade_next_pg_enum_oid))); + } /* * Use binary-upgrade override for pg_enum.oid, if supplied. During @@ -390,7 +428,14 @@ AddEnumLabel(Oid enumTypeOid, newOid = binary_upgrade_next_pg_enum_oid; binary_upgrade_next_pg_enum_oid = InvalidOid; + + /* Override the previously computed newelemorder above. */ + if (!isnan(yb_binary_upgrade_next_pg_enum_sortorder)) + newelemorder = yb_binary_upgrade_next_pg_enum_sortorder; + yb_binary_upgrade_next_pg_enum_sortorder = NAN; } + else if (YbUsingEnumLabelOidAssignment()) + newOid = YbLookupOidAssignmentForEnumLabel(enumTypeOid, newVal); else { /* @@ -644,6 +689,13 @@ RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems) { int i; + if (IsYugaByteEnabled()) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("renumber enum labels is not yet supported"))); + } + /* * We should only need to increase existing elements' enumsortorders, * never decrease them. Therefore, work from the end backwards, to avoid diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c index 92afbc2f25806..02def562b7f1e 100644 --- a/src/backend/catalog/pg_inherits.c +++ b/src/backend/catalog/pg_inherits.c @@ -32,6 +32,10 @@ #include "utils/snapmgr.h" #include "utils/syscache.h" +/* YB includes */ +#include "pg_yb_utils.h" +#include "utils/catcache.h" + /* * Entry of a hash table used in find_all_inheritors. See below. */ @@ -601,7 +605,7 @@ DeleteInheritsTuple(Oid inhrelid, Oid inhparent, bool expect_detach_pending, childname ? childname : "unknown relation"), errdetail("There's no pending concurrent detach."))); - CatalogTupleDelete(catalogRelation, &inheritsTuple->t_self); + CatalogTupleDelete(catalogRelation, inheritsTuple); found = true; } } diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c index 30f024ce5eb09..e728a5756328f 100644 --- a/src/backend/catalog/pg_largeobject.c +++ b/src/backend/catalog/pg_largeobject.c @@ -115,7 +115,7 @@ LargeObjectDrop(Oid loid) (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("large object %u does not exist", loid))); - CatalogTupleDelete(pg_lo_meta, &tuple->t_self); + CatalogTupleDelete(pg_lo_meta, tuple); systable_endscan(scan); @@ -132,7 +132,7 @@ LargeObjectDrop(Oid loid) NULL, 1, skey); while (HeapTupleIsValid(tuple = systable_getnext(scan))) { - CatalogTupleDelete(pg_largeobject, &tuple->t_self); + CatalogTupleDelete(pg_largeobject, tuple); } systable_endscan(scan); diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c index 939b1349f893a..e7b67a067b08c 100644 --- a/src/backend/catalog/pg_publication.c +++ b/src/backend/catalog/pg_publication.c @@ -45,6 +45,12 @@ #include "utils/rel.h" #include "utils/syscache.h" +/* YB includes */ +#include "pg_yb_utils.h" +#include "yb/yql/pggate/util/ybc_guc.h" + +static Datum yb_pg_relation_is_publishable(PG_FUNCTION_ARGS, Oid relid); + static void publication_translate_columns(Relation targetrel, List *columns, int *natts, AttrNumber **attrs); @@ -85,6 +91,14 @@ check_publication_add_relation(Relation targetrel) errmsg("cannot add relation \"%s\" to publication", RelationGetRelationName(targetrel)), errdetail("This operation is not supported for unlogged tables."))); + + if (IsYugaByteEnabled() && !yb_cdcsdk_stream_tables_without_primary_key && + !YBRelationHasPrimaryKey(targetrel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("table \"%s\" cannot be replicated", + RelationGetRelationName(targetrel)), + errdetail("Replicating tables without primary key is not yet supported."))); } /* @@ -233,6 +247,11 @@ pg_relation_is_publishable(PG_FUNCTION_ARGS) HeapTuple tuple; bool result; + if (IsYugaByteEnabled()) + { + return yb_pg_relation_is_publishable(fcinfo, relid); + } + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); if (!HeapTupleIsValid(tuple)) PG_RETURN_NULL(); @@ -801,6 +820,19 @@ GetAllTablesPublicationRelations(bool pubviaroot) Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple); Oid relid = relForm->oid; + if (IsYugaByteEnabled()) + { + Relation rel; + + rel = table_open(relid, AccessShareLock); + if (yb_is_publishable_relation(rel)) + result = lappend_oid(result, relid); + table_close(rel, AccessShareLock); + + /* Skip the below is_publishable_class call. */ + continue; + } + if (is_publishable_class(relid, relForm) && !(relForm->relispartition && pubviaroot)) result = lappend_oid(result, relid); @@ -979,6 +1011,57 @@ GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt) return result; } +/* + * Gets list of publications by its name. + */ +List * +YBGetPublicationsByNames(List *pubnames, bool missing_ok) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, pubnames) + { + char *pubname = (char *) lfirst(lc); + Publication *pub = GetPublicationByName(pubname, missing_ok); + + result = lappend(result, pub); + } + + return result; +} + +/* + * Gets list of publications oids by name. It is the responsibility of the + * caller to free the memory allocated to the result. + */ +Oid * +YBGetPublicationOidsByNames(List *pubnames) +{ + List *yb_publications = NIL; + Oid *result = palloc(sizeof(Oid) * list_length(pubnames)); + ListCell *lc; + + if (pubnames == NIL) + { + return result; + } + + yb_publications = YBGetPublicationsByNames(pubnames, false /* missing_ok */ ); + + size_t table_idx = 0; + + foreach(lc, yb_publications) + { + Publication *pub = (Publication *) lfirst(lc); + + result[table_idx++] = pub->oid; + } + + list_free(yb_publications); + return result; +} + /* * Get publication using oid * @@ -1213,3 +1296,147 @@ pg_get_publication_tables(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +List * +yb_pg_get_publications_tables(List *publications, bool *yb_is_pub_all_tables) +{ + /* hash table for O(1) rel_oid lookup */ + HTAB *seen_tables; + HASHCTL ctl; + List *tables = NIL; + ListCell *lc; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(Oid); + ctl.hcxt = CurrentMemoryContext; + + seen_tables = hash_create("yb_pg_get_publications_tables temporary table", + 32, /* start small and extend */ + &ctl, + HASH_ELEM | HASH_BLOBS); + + foreach(lc, publications) + { + List *pub_tables; + ListCell *lc_tables; + Publication *pub = (Publication *) lfirst(lc); + bool has_alltables = pub->alltables; + + if (has_alltables) + pub_tables = GetAllTablesPublicationRelations(pub->pubviaroot); + else + pub_tables = GetPublicationRelations(pub->oid, + pub->pubviaroot ? + PUBLICATION_PART_ROOT : + PUBLICATION_PART_LEAF); + + foreach(lc_tables, pub_tables) + { + Oid rel = lfirst_oid(lc_tables); + bool found; + + hash_search(seen_tables, &rel, HASH_ENTER, &found); + if (!found) + tables = lappend_oid(tables, rel); + } + + list_free(pub_tables); + + /* + * Once we have processed a publication with alltables, we have found + * all tables of the database. So, we don't need to process other + * publications. + */ + if (has_alltables) + { + *yb_is_pub_all_tables = true; + break; + } + } + + hash_destroy(seen_tables); + + return tables; +} + +static Datum +yb_pg_relation_is_publishable(PG_FUNCTION_ARGS, Oid relid) +{ + Relation rel; + HeapTuple tuple; + bool result; + + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!tuple) + PG_RETURN_NULL(); + + rel = table_open(relid, AccessShareLock); + result = yb_is_publishable_relation(rel); + table_close(rel, AccessShareLock); + + ReleaseSysCache(tuple); + PG_RETURN_BOOL(result); +} + +/* + * Similar to is_publishable_relation but with additional check for user defined + * primary key when GUC variable 'yb_cdcsdk_stream_tables_without_primary_key' + * is set to false. + */ +bool +yb_is_publishable_relation(Relation rel) +{ + return (is_publishable_class(RelationGetRelid(rel), rel->rd_rel) && + (yb_cdcsdk_stream_tables_without_primary_key || + YBRelationHasPrimaryKey(rel))); +} + +/* + * Log a NOTICE if a relation that cannot be published via logical replication + * is found. + * A slightly modified version of the GetAllTablesPublicationRelations function + * defined earlier in this file. + */ +void +yb_log_unsupported_publication_relations(void) +{ + Relation classRel; + ScanKeyData key[1]; + TableScanDesc scan; + HeapTuple tuple; + + classRel = table_open(RelationRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_class_relkind, + BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(RELKIND_RELATION)); + + scan = table_beginscan_catalog(classRel, 1, key); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple); + Oid relid = relForm->oid; + Relation rel; + + rel = table_open(relid, AccessShareLock); + + if (is_publishable_class(RelationGetRelid(rel), relForm) && + !yb_cdcsdk_stream_tables_without_primary_key && + !YBRelationHasPrimaryKey(rel)) + { + ereport(NOTICE, + (errmsg("tables without primary key will be skipped"))); + + table_close(rel, AccessShareLock); + break; + } + + table_close(rel, AccessShareLock); + } + + table_endscan(scan); + table_close(classRel, AccessShareLock); +} diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c index 06b7b5ad15802..f1327db51863c 100644 --- a/src/backend/catalog/pg_range.c +++ b/src/backend/catalog/pg_range.c @@ -129,7 +129,7 @@ RangeDelete(Oid rangeTypeOid) while (HeapTupleIsValid(tup = systable_getnext(scan))) { - CatalogTupleDelete(pg_range, &tup->t_self); + CatalogTupleDelete(pg_range, tup); } systable_endscan(scan); diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c index 3e8fa008b9dca..a22c1b399e9a9 100644 --- a/src/backend/catalog/pg_shdepend.c +++ b/src/backend/catalog/pg_shdepend.c @@ -68,6 +68,16 @@ #include "utils/memutils.h" #include "utils/syscache.h" +/* YB includes */ +#include "catalog/objectaddress.h" +#include "catalog/pg_yb_profile_d.h" +#include "catalog/pg_yb_role_profile_d.h" +#include "catalog/pg_yb_tablegroup_d.h" +#include "commands/yb_profile.h" +#include "commands/yb_tablegroup.h" +#include "pg_yb_utils.h" +#include "postgres_ext.h" + typedef enum { LOCAL_OBJECT, @@ -99,6 +109,7 @@ static void shdepDropDependency(Relation sdepRel, SharedDependencyType deptype); static void storeObjectDescription(StringInfo descs, SharedDependencyObjectType type, + Oid refobjid, ObjectAddress *object, SharedDependencyType deptype, int count); @@ -258,7 +269,7 @@ shdepChangeDep(Relation sdepRel, { /* No new entry needed, so just delete existing entry if any */ if (oldtup) - CatalogTupleDelete(sdepRel, &oldtup->t_self); + CatalogTupleDelete(sdepRel, oldtup); } else if (oldtup) { @@ -300,6 +311,43 @@ shdepChangeDep(Relation sdepRel, heap_freetuple(oldtup); } +void +shdepFindImplicitTablegroup(Oid tablespaceId, Oid *tablegroupId) +{ + Oid databaseId; + ScanKeyData key[2]; + SysScanDesc scan; + Relation sdepRel; + HeapTuple tup; + + *tablegroupId = InvalidOid; + databaseId = MyDatabaseId; + sdepRel = table_open(SharedDependRelationId, RowExclusiveLock); + + + ScanKeyInit(&key[0], Anum_pg_shdepend_dbid, + BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(databaseId)); + + ScanKeyInit(&key[1], Anum_pg_shdepend_classid, + BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(YbTablegroupRelationId)); + + scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true, NULL, 2, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_shdepend shdepForm = (Form_pg_shdepend) GETSTRUCT(tup); + + if (shdepForm->refobjid == tablespaceId) + { + *tablegroupId = shdepForm->objid; + break; + } + } + + systable_endscan(scan); + table_close(sdepRel, RowExclusiveLock); +} + /* * changeDependencyOnOwner * @@ -362,6 +410,29 @@ recordDependencyOnTablespace(Oid classId, Oid objectId, Oid tablespace) ObjectAddress myself, referenced; + if (IsYugaByteEnabled()) + { + /* + * When recording dependencies on tablespaces for relations, InvalidOid + * indicates the database's default tablespace and should never be + * passed as an argument tablespace to record a tablespace dependency. + * Instead, a dependency on the database's default tablespace is added on + * the creation of the database and exists for the lifetime of the database + * or until the database default tablespace is altered. + */ + if (tablespace == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("tablespace dependencies cannot be recorded on InvalidOid"))); + + /* + * Since the pg_default and pg_global tablespaces cannot be dropped, + * it is safe to never record dependencies on these tablespaces. + */ + if (tablespace == DEFAULTTABLESPACE_OID || tablespace == GLOBALTABLESPACE_OID) + return; + } + ObjectAddressSet(myself, classId, objectId); ObjectAddressSet(referenced, TableSpaceRelationId, tablespace); @@ -771,6 +842,7 @@ checkSharedDependencies(Oid classId, Oid objectId, numReportedDeps++; storeObjectDescription(&descs, objects[i].objtype, + objectId, &objects[i].object, objects[i].deptype, 0); @@ -779,6 +851,7 @@ checkSharedDependencies(Oid classId, Oid objectId, numNotReportedDeps++; storeObjectDescription(&alldescs, objects[i].objtype, + objectId, &objects[i].object, objects[i].deptype, 0); @@ -798,12 +871,12 @@ checkSharedDependencies(Oid classId, Oid objectId, if (numReportedDeps < MAX_REPORTED_DEPS) { numReportedDeps++; - storeObjectDescription(&descs, REMOTE_OBJECT, &object, + storeObjectDescription(&descs, REMOTE_OBJECT, objectId, &object, SHARED_DEPENDENCY_INVALID, dep->count); } else numNotReportedDbs++; - storeObjectDescription(&alldescs, REMOTE_OBJECT, &object, + storeObjectDescription(&alldescs, REMOTE_OBJECT, objectId, &object, SHARED_DEPENDENCY_INVALID, dep->count); } @@ -923,14 +996,16 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId) /* If slots are full, insert a batch of tuples */ if (slot_stored_count == max_slots) { - CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slot_stored_count, indstate); + CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slot_stored_count, indstate, + false /* yb_shared_insert */ ); slot_stored_count = 0; } } /* Insert any tuples left in the buffer */ if (slot_stored_count > 0) - CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slot_stored_count, indstate); + CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slot_stored_count, indstate, + false /* yb_shared_insert */ ); systable_endscan(scan); @@ -974,7 +1049,7 @@ dropDatabaseDependencies(Oid databaseId) while (HeapTupleIsValid(tup = systable_getnext(scan))) { - CatalogTupleDelete(sdepRel, &tup->t_self); + CatalogTupleDelete(sdepRel, tup); } systable_endscan(scan); @@ -1127,7 +1202,7 @@ shdepDropDependency(Relation sdepRel, continue; /* OK, delete it */ - CatalogTupleDelete(sdepRel, &tup->t_self); + CatalogTupleDelete(sdepRel, tup); } systable_endscan(scan); @@ -1205,6 +1280,19 @@ shdepLockAndCheckObject(Oid classId, Oid objectId) break; } + case YbProfileRelationId: + { + /* For lack of a syscache on yb_pg_profile, do this: */ + char *profile = yb_get_profile_name(objectId); + + if (profile == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("profile %u was concurrently dropped", + objectId))); + pfree(profile); + break; + } default: elog(ERROR, "unrecognized shared classId: %u", classId); @@ -1229,6 +1317,7 @@ shdepLockAndCheckObject(Oid classId, Oid objectId) static void storeObjectDescription(StringInfo descs, SharedDependencyObjectType type, + Oid refobjid, ObjectAddress *object, SharedDependencyType deptype, int count) @@ -1256,7 +1345,21 @@ storeObjectDescription(StringInfo descs, else if (deptype == SHARED_DEPENDENCY_POLICY) appendStringInfo(descs, _("target of %s"), objdesc); else if (deptype == SHARED_DEPENDENCY_TABLESPACE) - appendStringInfo(descs, _("tablespace for %s"), objdesc); + { + char implicit_tablegroup_name[33]; + + sprintf(implicit_tablegroup_name, "tablegroup colocation_%u", refobjid); + + /* + * Do not report dependency from implicit tablegroup to tablespace. + * This would be fine since implicit tablegroup will be dropped if no tables + * are present in it. + */ + if (strcmp(implicit_tablegroup_name, objdesc) != 0) + appendStringInfo(descs, _("tablespace for %s"), objdesc); + } + else if (deptype == SHARED_DEPENDENCY_PROFILE) + appendStringInfo(descs, _("profile of %s"), objdesc); else elog(ERROR, "unrecognized dependency type: %d", (int) deptype); @@ -1415,6 +1518,12 @@ shdepDropOwned(List *roleids, DropBehavior behavior) add_exact_object_address(&obj, deleteobjs); } break; + case SHARED_DEPENDENCY_PROFILE: + /* + * If it is a profile object, do not delete. Profile + * associations can be removed by ALTER USER NOPROFILE. + */ + break; } } @@ -1627,3 +1736,74 @@ shdepReassignOwned(List *roleids, Oid newrole) table_close(sdepRel, RowExclusiveLock); } + +/* + * ybRecordDependencyOnProfile + * + * A convenient wrapper of recordSharedDependencyOn -- register the specified + * profile to the given object. + * + * Note: it's the caller's responsibility to ensure that there isn't a profile + * entry for the object already. + */ +void +ybRecordDependencyOnProfile(Oid classId, Oid objectId, Oid profile) +{ + ObjectAddress myself, + referenced; + + ObjectAddressSet(myself, classId, objectId); + ObjectAddressSet(referenced, YbProfileRelationId, profile); + + recordSharedDependencyOn(&myself, &referenced, + SHARED_DEPENDENCY_PROFILE); +} + +/* + * ybChangeDependencyOnProfile + * + * Update the shared dependencies to account for the new profile. + * + * Note: we don't need an objsubid argument because only whole objects + * have tablespaces. + */ +void +ybChangeDependencyOnProfile(Oid roleId, Oid newProfileId) +{ + Relation sdepRel; + + sdepRel = table_open(SharedDependRelationId, RowExclusiveLock); + + if (newProfileId != InvalidOid) + shdepChangeDep(sdepRel, + AuthIdRelationId, roleId, 0, + YbProfileRelationId, newProfileId, + SHARED_DEPENDENCY_PROFILE); + else + shdepDropDependency(sdepRel, + AuthIdRelationId, roleId, 0, true, + InvalidOid, InvalidOid, + SHARED_DEPENDENCY_INVALID); + + table_close(sdepRel, RowExclusiveLock); +} + +/* + * ybDropDependencyOnProfile + * + * Delete all pg_shdepend entries corresponding to a user -> profile mapping. + */ +void +ybDropDependencyOnProfile(Oid roleId) +{ + Relation sdepRel; + + sdepRel = table_open(SharedDependRelationId, RowExclusiveLock); + + shdepDropDependency(sdepRel, AuthIdRelationId, roleId, 0, + true, + YbProfileRelationId, InvalidOid, + SHARED_DEPENDENCY_PROFILE); + + table_close(sdepRel, RowExclusiveLock); +} diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c index add51caadf2a1..7553779df6e19 100644 --- a/src/backend/catalog/pg_subscription.c +++ b/src/backend/catalog/pg_subscription.c @@ -485,7 +485,7 @@ RemoveSubscriptionRel(Oid subid, Oid relid) "DROP SUBSCRIPTION ..."))); } - CatalogTupleDelete(rel, &tup->t_self); + CatalogTupleDelete(rel, tup); } table_endscan(scan); diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index ae12cb7d02954..c6dba43fb4676 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -37,6 +37,11 @@ #include "utils/rel.h" #include "utils/syscache.h" +/* YB includes */ +#include "catalog/catalog.h" +#include "catalog/yb_oid_assignment.h" +#include "pg_yb_utils.h" + static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal); @@ -128,7 +133,7 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId) nulls[Anum_pg_type_typacl - 1] = true; /* Use binary-upgrade override for pg_type.oid? */ - if (IsBinaryUpgrade) + if ((IsBinaryUpgrade || yb_binary_restore) && !yb_extension_upgrade) { if (!OidIsValid(binary_upgrade_next_pg_type_oid)) ereport(ERROR, @@ -138,6 +143,11 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId) typoid = binary_upgrade_next_pg_type_oid; binary_upgrade_next_pg_type_oid = InvalidOid; } + else if (YbUsingTypeOidAssignment()) + { + typoid = YbLookupOidAssignmentForType(get_namespace_name(typeNamespace), + typeName); + } else { typoid = GetNewOidWithIndex(pg_type_desc, TypeOidIndexId, @@ -168,7 +178,9 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId) false, false, true, /* make extension dependency */ - false); + false, + false, /* ybRelationIsSystem */ + false /* ybRelationIsShared */ ); /* Post creation hook for new shell type */ InvokeObjectPostCreateHook(TypeRelationId, typoid, 0); @@ -192,6 +204,11 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId) * Returns the ObjectAddress assigned to the new type. * If newTypeOid is zero (the normal case), a new OID is created; * otherwise we use exactly that OID. + * + * YB NOTE: + * If ybRelationIsShared is specified, pg_type entry will be + * created for ALL databases. newTypeOid should be free in + * all of them. * ---------------------------------------------------------------- */ ObjectAddress @@ -226,7 +243,8 @@ TypeCreate(Oid newTypeOid, int32 typeMod, int32 typNDims, /* Array dimensions for baseType */ bool typeNotNull, - Oid typeCollation) + Oid typeCollation, + bool ybRelationIsShared /* only for relation rowtypes */ ) { Relation pg_type_desc; Oid typeObjectId; @@ -241,6 +259,9 @@ TypeCreate(Oid newTypeOid, int i; ObjectAddress address; + bool isSystem = IsCatalogNamespace(typeNamespace); + bool ybSharedInsert = ybRelationIsShared && !IsBootstrapProcessingMode(); + /* * We assume that the caller validated the arguments individually, but did * not check for bad combinations. @@ -457,6 +478,13 @@ TypeCreate(Oid newTypeOid, replaces); CatalogTupleUpdate(pg_type_desc, &tup->t_self, tup); + if (IsYugaByteEnabled()) + /* + * Update existing shell type requires catalog version increment + * so that if a session has cached the shell type it can get + * refreshed to get the newly defined type. + */ + YBAddModificationAspects(YB_DDL_MODE_VERSION_INCREMENT); typeObjectId = typform->oid; @@ -467,8 +495,15 @@ TypeCreate(Oid newTypeOid, /* Force the OID if requested by caller */ if (OidIsValid(newTypeOid)) typeObjectId = newTypeOid; + /* + * This is already checked by transformCreateStmt for system relations, + * but just in case. + */ + else if (ybRelationIsShared && IsYsqlUpgrade) + elog(ERROR, "shared relations must have an explicit type OID"); /* Use binary-upgrade override for pg_type.oid, if supplied. */ - else if (IsBinaryUpgrade) + else if ((IsBinaryUpgrade || yb_binary_restore) && + !yb_extension_upgrade) { if (!OidIsValid(binary_upgrade_next_pg_type_oid)) ereport(ERROR, @@ -478,6 +513,28 @@ TypeCreate(Oid newTypeOid, typeObjectId = binary_upgrade_next_pg_type_oid; binary_upgrade_next_pg_type_oid = InvalidOid; } + else if (IsYsqlUpgrade && isSystem && relationKind != RELKIND_VIEW) + { + /* + * Views in yb_system_views.sql don't define their oids, we + * auto-assign them. + * + * Also, there's actually a bunch of system relations without + * explicit type OIDs created by initdb - e.g. + * pg_attrdef (non-shared, gets typoid=10000) or + * pg_db_role_setting (shared, gets typoid=11555). + * For now though we hope that any future system relations won't + * be like that - YB relations certainly shouldn't be. + */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("system relations must have an explicit type OID!"))); + } + else if (YbUsingTypeOidAssignment()) + { + typeObjectId = YbLookupOidAssignmentForType(get_namespace_name(typeNamespace), + typeName); + } else { typeObjectId = GetNewOidWithIndex(pg_type_desc, TypeOidIndexId, @@ -489,7 +546,11 @@ TypeCreate(Oid newTypeOid, tup = heap_form_tuple(RelationGetDescr(pg_type_desc), values, nulls); - CatalogTupleInsert(pg_type_desc, tup); + /* + * YB: pg_type has PK(oid), so if row type OID for the shared relation + * is taken in any DB, this step will fail gracefully. + */ + YBCatalogTupleInsert(pg_type_desc, tup, ybSharedInsert); } /* @@ -506,7 +567,9 @@ TypeCreate(Oid newTypeOid, isImplicitArray, isDependentType, true, /* make extension dependency */ - rebuildDeps); + rebuildDeps, + isSystem, + ybSharedInsert); /* Post creation hook for new type */ InvokeObjectPostCreateHook(TypeRelationId, typeObjectId, 0); @@ -563,7 +626,9 @@ GenerateTypeDependencies(HeapTuple typeTuple, bool isImplicitArray, bool isDependentType, bool makeExtensionDep, - bool rebuild) + bool rebuild, + bool ybRelationIsSystem, + bool ybRelationIsShared) { Form_pg_type typeForm = (Form_pg_type) GETSTRUCT(typeTuple); Oid typeObjectId = typeForm->oid; @@ -599,6 +664,19 @@ GenerateTypeDependencies(HeapTuple typeTuple, ObjectAddressSet(myself, TypeRelationId, typeObjectId); + /* + * For non-view/non-implicit-arrays during YSQL upgrade, we do not need to + * do anything. + */ + if (IsYsqlUpgrade && ybRelationIsSystem && relationKind != RELKIND_VIEW && + !isImplicitArray) + { + if (rebuild) + elog(ERROR, "cannot rebuild dependencies for a system relation rowtype"); + + return; + } + /* * Make dependencies on namespace, owner, ACL, extension. * diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index 73da687d5dd2f..a29d8663c0b10 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -452,6 +452,8 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot( IN slot_name name, IN plugin name, IN temporary boolean DEFAULT false, IN twophase boolean DEFAULT false, + IN yb_lsn_type name DEFAULT 'SEQUENCE', + IN yb_ordering_mode name DEFAULT 'TRANSACTION', OUT slot_name name, OUT lsn pg_lsn) RETURNS RECORD LANGUAGE INTERNAL diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 17e924b22691d..5665af544ab96 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -12,6 +12,21 @@ * in any error message about one of them, so don't do that. Also, you * cannot write a semicolon immediately followed by an empty line in a * string literal (including a function body!) or a multiline comment. + * + * Portions Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ CREATE VIEW pg_roles AS @@ -400,7 +415,37 @@ CREATE VIEW pg_publication_tables AS WHERE C.oid = GPT.relid; CREATE VIEW pg_locks AS - SELECT * FROM pg_lock_status() AS L; +SELECT l.locktype, + l.database, + l.relation, + null::int AS page, + null::smallint AS tuple, + null::text AS virtualxid, + null::xid AS transactionid, + l.classid, + l.objid, + l.objsubid, + null::text AS virtualtransaction, + l.pid, + array_to_string(mode, ',') AS mode, + l.granted, + l.fastpath, + l.waitstart, + l.waitend, + jsonb_build_object('node', l.node, + 'transactionid', l.transaction_id, + 'subtransaction_id', l.subtransaction_id, + 'is_explicit', l.is_explicit, + 'tablet_id', l.tablet_id, + 'blocked_by', l.blocked_by, + 'keyrangedetails', jsonb_build_object( + 'cols', to_jsonb(l.hash_cols || l.range_cols), + 'attnum', l.attnum, + 'column_id', l.column_id, + 'multiple_rows_locked', l.multiple_rows_locked + ) + ) AS ybdetails +FROM yb_lock_status(null, null) AS l; CREATE VIEW pg_cursors AS SELECT * FROM pg_cursor() AS C; @@ -881,10 +926,16 @@ CREATE VIEW pg_stat_activity AS s.backend_xmin, S.query_id, S.query, - S.backend_type + S.backend_type, + yb_pg_stat_get_backend_catalog_version(B.beid) AS catalog_version, + yb_pg_stat_get_backend_allocated_mem_bytes(B.beid) AS allocated_mem_bytes, + yb_pg_stat_get_backend_pss_mem_bytes(B.beid) AS pss_mem_bytes, + S.yb_backend_xid FROM pg_stat_get_activity(NULL) AS S LEFT JOIN pg_database AS D ON (S.datid = D.oid) - LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); + LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid) + LEFT JOIN (pg_stat_get_backend_idset() beid CROSS JOIN + pg_stat_get_backend_pid(beid) pid) B ON B.pid = S.pid; CREATE VIEW pg_stat_replication AS SELECT @@ -1012,7 +1063,11 @@ CREATE VIEW pg_replication_slots AS L.confirmed_flush_lsn, L.wal_status, L.safe_wal_size, - L.two_phase + L.two_phase, + L.yb_stream_id, + L.yb_restart_commit_ht, + L.yb_lsn_type, + L.yb_restart_time FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); @@ -1216,24 +1271,14 @@ CREATE VIEW pg_stat_progress_create_index AS S.pid AS pid, S.datid AS datid, D.datname AS datname, S.relid AS relid, CAST(S.param7 AS oid) AS index_relid, - CASE S.param1 WHEN 1 THEN 'CREATE INDEX' + CASE S.param1 WHEN 1 THEN 'CREATE INDEX NONCONCURRENTLY' WHEN 2 THEN 'CREATE INDEX CONCURRENTLY' - WHEN 3 THEN 'REINDEX' + WHEN 3 THEN 'REINDEX NONCONCURRENTLY' WHEN 4 THEN 'REINDEX CONCURRENTLY' END AS command, CASE S.param10 WHEN 0 THEN 'initializing' - WHEN 1 THEN 'waiting for writers before build' - WHEN 2 THEN 'building index' || - COALESCE((': ' || pg_indexam_progress_phasename(S.param9::oid, S.param11)), - '') - WHEN 3 THEN 'waiting for writers before validation' - WHEN 4 THEN 'index validation: scanning index' - WHEN 5 THEN 'index validation: sorting tuples' - WHEN 6 THEN 'index validation: scanning table' - WHEN 7 THEN 'waiting for old snapshots' - WHEN 8 THEN 'waiting for readers before marking dead' - WHEN 9 THEN 'waiting for readers before dropping' - END as phase, + WHEN 1 THEN 'backfilling' + END AS phase, S.param4 AS lockers_total, S.param5 AS lockers_done, S.param6 AS current_locker_pid, @@ -1275,6 +1320,10 @@ CREATE VIEW pg_stat_progress_copy AS WHEN 3 THEN 'PIPE' WHEN 4 THEN 'CALLBACK' END AS "type", + CASE S.param7 WHEN 0 THEN 'IN PROGRESS' + WHEN 1 THEN 'ERROR' + WHEN 2 THEN 'SUCCESS' + END AS yb_status, S.param1 AS bytes_processed, S.param2 AS bytes_total, S.param3 AS tuples_processed, diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 530ef8e767ebe..5737ae0021203 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -86,9 +86,15 @@ CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, rel = table_open(relOid, lockmode); - /* create_toast_table does all the work */ - (void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode, - check, OIDOldToast); + /* Skip toast table creation for non-temp relations in YB mode */ + if (!IsYugaByteEnabled() || isTempOrTempToastNamespace(rel->rd_rel->relnamespace)) + { + /* create_toast_table does all the work */ + + elog(DEBUG1, "Creating toast table for %d", relOid); + (void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode, + check, OIDOldToast); + } table_close(rel, NoLock); } @@ -251,6 +257,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, toast_relid = heap_create_with_catalog(toast_relname, namespaceid, rel->rd_rel->reltablespace, + InvalidOid, /* tablegroup */ toastOid, InvalidOid, InvalidOid, @@ -268,7 +275,8 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, true, true, OIDOldToast, - NULL); + NULL, + false); Assert(toast_relid != InvalidOid); /* make the toast relation visible, else table_open will fail */ @@ -330,7 +338,10 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, BTREE_AM_OID, rel->rd_rel->reltablespace, collationObjectId, classObjectId, coloptions, (Datum) 0, - INDEX_CREATE_IS_PRIMARY, 0, true, true, NULL); + INDEX_CREATE_IS_PRIMARY, 0, true, true, NULL, NULL, + true /* skip_index_backfill */ , false /* is_colocated */ , + InvalidOid /* tablegroupId */ , InvalidOid /* colocationId */, + false /* yb_skip_index_creation */ ); table_close(toast_rel, NoLock); @@ -368,7 +379,8 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid; - systable_inplace_update_finish(state, reltup); + systable_inplace_update_finish(state, reltup, + false /* yb_shared_update */ ); } heap_freetuple(reltup); diff --git a/src/backend/catalog/yb_catalog/Makefile b/src/backend/catalog/yb_catalog/Makefile new file mode 100644 index 0000000000000..030c0bcc8175f --- /dev/null +++ b/src/backend/catalog/yb_catalog/Makefile @@ -0,0 +1,17 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for catalog/yb_catalog +# +# IDENTIFICATION +# src/backend/catalog/yb_catalog/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/backend/catalog/yb_catalog +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = yb_type.o yb_catalog_version.o yb_oid_assignment.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/catalog/yb_catalog/yb_catalog_version.c b/src/backend/catalog/yb_catalog/yb_catalog_version.c new file mode 100644 index 0000000000000..01b5d141183dd --- /dev/null +++ b/src/backend/catalog/yb_catalog/yb_catalog_version.c @@ -0,0 +1,1086 @@ +/*------------------------------------------------------------------------- + * + * yb_catalog_version.c + * utility functions related to the ysql catalog version table. + * + * Portions Copyright (c) YugabyteDB, Inc. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/yb_scan.h" +#include "catalog/catalog.h" +#include "catalog/namespace.h" +#include "catalog/pg_authid_d.h" +#include "catalog/pg_database.h" +#include "catalog/pg_namespace_d.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "catalog/pg_yb_catalog_version.h" +#include "catalog/pg_yb_invalidation_messages.h" +#include "catalog/schemapg.h" +#include "catalog/yb_catalog_version.h" +#include "executor/spi.h" +#include "executor/ybExpr.h" +#include "executor/ybModifyTable.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "optimizer/cost.h" +#include "pg_yb_utils.h" +#include "storage/lmgr.h" +#include "utils/catcache.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "yb/yql/pggate/ybc_gflags.h" +#include "yb/yql/pggate/ybc_pg_typedefs.h" + +YbCatalogVersionType yb_catalog_version_type = CATALOG_VERSION_UNSET; + +static FormData_pg_attribute Desc_pg_yb_catalog_version[Natts_pg_yb_catalog_version] = { + Schema_pg_yb_catalog_version +}; + +static bool YbGetMasterCatalogVersionFromTable(Oid db_oid, uint64_t *version, + bool acquire_row_lock); +static Datum YbGetMasterCatalogVersionTableEntryYbctid(Relation catalog_version_rel, + Oid db_oid); + +/* Retrieve Catalog Version */ + +static uint64_t +YbGetMasterCatalogVersionImpl(bool acquire_row_lock) +{ + uint64_t version = YB_CATCACHE_VERSION_UNINITIALIZED; + + switch (YbGetCatalogVersionType()) + { + case CATALOG_VERSION_CATALOG_TABLE: + if (YbGetMasterCatalogVersionFromTable(YbMasterCatalogVersionTableDBOid(), &version, + acquire_row_lock)) + return version; + /* + * In spite of the fact the pg_yb_catalog_version table exists it has no actual + * version (this could happen during YSQL upgrade), + * fallback to an old protobuf mechanism until the next cache refresh. + */ + yb_catalog_version_type = CATALOG_VERSION_PROTOBUF_ENTRY; + yb_switch_fallthrough(); + case CATALOG_VERSION_PROTOBUF_ENTRY: + /* deprecated (kept for compatibility with old clusters). */ + HandleYBStatus(YBCPgGetCatalogMasterVersion(&version)); + return version; + + case CATALOG_VERSION_UNSET: /* should not happen. */ + break; + } + ereport(FATAL, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("catalog version type was not set, cannot load system catalog."))); + return version; +} + +uint64_t +YbGetMasterCatalogVersion() +{ + /* + * Fetch the latest catalog invalidation messages to see catalog version increments + * from recently committed DDLs. This avoids reading a stale catalog version. + * XXX: AcceptInvalidationMessages() must not call YbGetMasterCatalogVersion() to avoid recursion. + */ + if (!YBCIsLegacyModeForCatalogOps()) + AcceptInvalidationMessages(); + + return YbGetMasterCatalogVersionImpl(false /* acquire_row_lock */ ); +} + +void +YbMaybeLockMasterCatalogVersion() +{ + /* + * When object locks are off (i.e., the old way), we ensure that concurrent + * DDLs don't stamp on each other by incrementing the catalog version. DDLs + * with overlapping [read time, commit time] windows would conflict with each + * other and only one of them would be able to make progress. This catalog + * version increment happens at the end of a DDL transaction. + * + * When auto analyze is turned on, we don't want ANALYZE DDL triggered by auto analyze to + * abort user DDLs. To achieve this, regular DDLs take a FOR KEY SHARE lock on the catalog version + * row with a high priority (see pg_session.cc) while ANALYZE triggered by auto analyze takes + * a low priority FOR UPDATE lock on all rows of the catalog version table. + * (1) Global DDLs (i.e., those that modify shared catalog tables) increment all catalog + * versions at the end of the DDL but only lock the catalog version of the current database + * when they start. To enable them to abort ANALYZE on different DBs that can conflict with + * them, ANALYZE locks all rows of catalog version table. It is challenging to identify a + * global DDL at the start of a DDL so the other way around does not work. + * (2) We enable this feature only if the invalidation messages are used and per-database catalog + * version mode is enabled. + * + */ + if (yb_user_ddls_preempt_auto_analyze && + YBCIsLegacyModeForCatalogOps() && + YbIsInvalidationMessageEnabled() && YBIsDBCatalogVersionMode()) + { + elog(DEBUG3, "Locking catalog version for db oid %d", MyDatabaseId); + YbGetMasterCatalogVersionImpl(true /* acquire_row_lock */ ); + } +} + +/* Modify Catalog Version */ + +static Datum +GetInvalidationMessages(const SharedInvalidationMessage *invalMessages, int nmsgs, + bool *is_null) +{ + if (!invalMessages) + { + Assert(nmsgs >= 0); + *is_null = true; + return (Datum) 0; + } + + size_t str_len = sizeof(SharedInvalidationMessage) * nmsgs; + bytea *bstr = palloc(VARHDRSZ + str_len); + + memcpy(VARDATA(bstr), invalMessages, str_len); + SET_VARSIZE(bstr, VARHDRSZ + str_len); + *is_null = false; + return PointerGetDatum(bstr); +} + +static void +YbCallSQLIncrementCatalogVersions(Oid functionId, bool is_breaking_change, + const char *command_tag) +{ + FmgrInfo flinfo; + + LOCAL_FCINFO(fcinfo, 1); + fmgr_info(functionId, &flinfo); + InitFunctionCallInfoData(*fcinfo, &flinfo, 1, InvalidOid, NULL, NULL); + fcinfo->args[0].value = BoolGetDatum(is_breaking_change); + fcinfo->args[0].isnull = false; + + /* Save old values and set new values to enable the call. */ + bool saved = yb_non_ddl_txn_for_sys_tables_allowed; + + yb_non_ddl_txn_for_sys_tables_allowed = true; + Oid save_userid; + int save_sec_context; + + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID, + SECURITY_RESTRICTED_OPERATION); + /* Calling a user defined function requires a snapshot. */ + bool snapshot_set = ActiveSnapshotSet(); + + if (!snapshot_set) + PushActiveSnapshot(GetTransactionSnapshot()); + + if (!(*YBCGetGFlags()->TEST_hide_details_for_pg_regress)) + { + bool log_ysql_catalog_versions = + *YBCGetGFlags()->log_ysql_catalog_versions; + + ereport(LOG, + (errmsg("%s: incrementing all master db catalog versions (%sbreaking)", + __func__, is_breaking_change ? "" : "non"), + errdetail("Node tag: %s.", command_tag ? command_tag : "n/a"), + errhidestmt(!log_ysql_catalog_versions), + errhidecontext(!log_ysql_catalog_versions))); + } + + PG_TRY(); + { + yb_is_calling_internal_sql_for_ddl = true; + FunctionCallInvoke(fcinfo); + /* Restore old values. */ + yb_non_ddl_txn_for_sys_tables_allowed = saved; + yb_is_calling_internal_sql_for_ddl = false; + SetUserIdAndSecContext(save_userid, save_sec_context); + if (!snapshot_set) + PopActiveSnapshot(); + } + PG_CATCH(); + { + /* Restore old values. */ + yb_non_ddl_txn_for_sys_tables_allowed = saved; + yb_is_calling_internal_sql_for_ddl = false; + SetUserIdAndSecContext(save_userid, save_sec_context); + if (!snapshot_set) + PopActiveSnapshot(); + PG_RE_THROW(); + } + PG_END_TRY(); +} + +/* + * When transactional DDL is enabled, multiple DDLs could contribute to the + * catalog version increment. Hence, we need to be more explicit that we + * are only logging the last catalog version incrementing DDL and there may + * be more such DDLs that are not logged. + */ +static bool +LastDdlInTransactionBlock() +{ + return YBIsDdlTransactionBlockEnabled() && IsTransactionBlock(); +} + +static void +MaybeLogNewSQLIncrementCatalogVersion(bool success, + Oid db_oid, + bool is_breaking_change, + bool is_global_ddl, + const char *command_tag, + uint64_t new_version) +{ + if (!(*YBCGetGFlags()->TEST_hide_details_for_pg_regress)) + { + bool log_ysql_catalog_versions = + *YBCGetGFlags()->log_ysql_catalog_versions; + char tmpbuf1[20] = ""; + char tmpbuf2[60] = " failed"; + + /* Log MyDatabaseId to if it differs from db_oid. */ + if (db_oid != MyDatabaseId) + snprintf(tmpbuf1, sizeof(tmpbuf1), " from %u", MyDatabaseId); + if (success) + snprintf(tmpbuf2, sizeof(tmpbuf2), + ", new version for database %u is %" PRIu64, + db_oid, new_version); + + char *action = is_global_ddl ? "all master db catalog versions" + : "master db catalog version"; + + ereport(LOG, + (errmsg("%s: incrementing %s " + "(%sbreaking) with inval messages%s%s", + __func__, action, is_breaking_change ? "" : "non", + tmpbuf1, tmpbuf2), + errdetail("Local version: %" PRIu64 ", %snode tag: %s.", + YbGetCatalogCacheVersion(), + LastDdlInTransactionBlock() ? "last ddl " : "", + command_tag ? command_tag : "n/a"), + errhidestmt(!log_ysql_catalog_versions), + errhidecontext(!log_ysql_catalog_versions))); + } +} + +static uint64_t +YbCallNewSQLIncrementCatalogVersionHelper(Oid functionId, + Oid db_oid, + bool is_breaking_change, + const char *command_tag, + Datum messages, + bool is_null, + int expiration_secs, + bool is_global_ddl) +{ + FmgrInfo flinfo; + + LOCAL_FCINFO(fcinfo, 4); + fmgr_info(functionId, &flinfo); + InitFunctionCallInfoData(*fcinfo, &flinfo, 4, InvalidOid, NULL, NULL); + fcinfo->args[0].value = ObjectIdGetDatum(db_oid); + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = BoolGetDatum(is_breaking_change); + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = messages; + fcinfo->args[2].isnull = is_null; + fcinfo->args[3].value = expiration_secs; + fcinfo->args[3].isnull = false; + + /* Save old values and set new values to enable the call. */ + bool saved = yb_non_ddl_txn_for_sys_tables_allowed; + + yb_non_ddl_txn_for_sys_tables_allowed = true; + bool saved_enable_seqscan = enable_seqscan; + + /* + * Avoid sequential scan for non-global-impact DDL, otherwise we can get + * conflicts between concurrent cross-database DDLs. + */ + if (!is_global_ddl) + enable_seqscan = false; + Oid save_userid; + int save_sec_context; + + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID, + SECURITY_RESTRICTED_OPERATION); + /* Calling a user defined function requires a snapshot. */ + bool snapshot_set = ActiveSnapshotSet(); + + if (!snapshot_set) + PushActiveSnapshot(GetTransactionSnapshot()); + volatile uint64_t new_version; + + PG_TRY(); + { + yb_is_calling_internal_sql_for_ddl = true; + Datum retval = FunctionCallInvoke(fcinfo); + + /* Restore old values. */ + yb_non_ddl_txn_for_sys_tables_allowed = saved; + yb_is_calling_internal_sql_for_ddl = false; + enable_seqscan = saved_enable_seqscan; + SetUserIdAndSecContext(save_userid, save_sec_context); + if (!snapshot_set) + PopActiveSnapshot(); + if (fcinfo->isnull) + { + elog(WARNING, "function %u returned NULL", functionId); + new_version = YB_CATCACHE_VERSION_UNINITIALIZED; + } + else + { + new_version = DatumGetUInt64(retval); + Assert(new_version != YB_CATCACHE_VERSION_UNINITIALIZED); + } + MaybeLogNewSQLIncrementCatalogVersion(true /* success */ , + db_oid, + is_breaking_change, + is_global_ddl, + command_tag, + new_version); + } + PG_CATCH(); + { + /* Restore old values. */ + yb_non_ddl_txn_for_sys_tables_allowed = saved; + yb_is_calling_internal_sql_for_ddl = false; + enable_seqscan = saved_enable_seqscan; + SetUserIdAndSecContext(save_userid, save_sec_context); + if (!snapshot_set) + PopActiveSnapshot(); + MaybeLogNewSQLIncrementCatalogVersion(false /* success */ , + db_oid, + is_breaking_change, + is_global_ddl, + command_tag, + 0 /* new_version */ ); + PG_RE_THROW(); + } + PG_END_TRY(); + return new_version; +} + +static uint64_t +YbCallNewSQLIncrementCatalogVersion(Oid functionId, Oid db_oid, bool is_breaking_change, + const char *command_tag, + Datum messages, bool is_null, int expiration_secs) +{ + return YbCallNewSQLIncrementCatalogVersionHelper(functionId, + db_oid, + is_breaking_change, + command_tag, + messages, + is_null, + expiration_secs, + false /* is_global_ddl */ ); +} + +static uint64_t +YbCallNewSQLIncrementAllCatalogVersions(Oid functionId, + Oid db_oid, + bool is_breaking_change, + const char *command_tag, + Datum messages, + bool is_null, + int expiration_secs) +{ + return YbCallNewSQLIncrementCatalogVersionHelper(functionId, + db_oid, + is_breaking_change, + command_tag, + messages, + is_null, + expiration_secs, + true /* is_global_ddl */ ); +} + +static Oid +YbGetSQLIncrementCatalogVersionFunctionOidHelper(char *fname) +{ + List *names = list_make2(makeString("pg_catalog"), makeString(fname)); + FuncCandidateList clist = FuncnameGetCandidates(names, + -1 /* nargs */ , + NIL /* argnames */ , + false /* expand_variadic */ , + false /* expand_defaults */ , + false /* include_out_arguments */ , + false /* missing_ok */ ); + + /* We expect exactly one candidate. */ + if (clist && clist->next == NULL) + return clist->oid; + Assert(!clist); + /* When upgrading an old release, the function may not exist. */ + return InvalidOid; +} + +Oid +YbGetSQLIncrementCatalogVersionsFunctionOid() +{ + return YbGetSQLIncrementCatalogVersionFunctionOidHelper("yb_increment_all_db_catalog_versions"); +} + +static Oid +YbGetNewIncrementCatalogVersionFunctionOid() +{ + return YbGetSQLIncrementCatalogVersionFunctionOidHelper("yb_increment_db_catalog_version_with_inval_messages"); +} + +static Oid +YbGetNewIncrementAllCatalogVersionsFunctionOid() +{ + return YbGetSQLIncrementCatalogVersionFunctionOidHelper("yb_increment_all_db_catalog_versions_with_inval_messages"); +} + +static void +YbIncrementMasterDBCatalogVersionTableEntryImpl(Oid db_oid, + bool is_breaking_change, + bool is_global_ddl, + const char *command_tag, + const SharedInvalidationMessage *invalMessages, + int nmsgs) +{ + Assert(YbGetCatalogVersionType() == CATALOG_VERSION_CATALOG_TABLE); + + if (!YBCIsLegacyModeForCatalogOps()) + LockRelationOid(YBCatalogVersionRelationId, ExclusiveLock); + + if (YbIsInvalidationMessageEnabled()) + { + Oid func_oid = is_global_ddl ? YbGetNewIncrementAllCatalogVersionsFunctionOid() + : YbGetNewIncrementCatalogVersionFunctionOid(); + + if (OidIsValid(func_oid) && YbInvalidationMessagesTableExists()) + { + bool is_null = false; + Datum messages = GetInvalidationMessages(invalMessages, nmsgs, &is_null); + int expiration_secs = yb_invalidation_message_expiration_secs; + + if (is_global_ddl) + { + /* + * Call yb_increment_all_db_catalog_versions_with_inval_messages( + * is_breaking_change, messages, expiration_secs). + * Pass MyDatabaseId to get the new version of MyDatabaseId. + */ + uint64_t new_version = + YbCallNewSQLIncrementAllCatalogVersions(func_oid, MyDatabaseId, + is_breaking_change, + command_tag, messages, + is_null, expiration_secs); + + YbSetNewCatalogVersion(new_version); + return; + } + + /* + * Call yb_increment_db_catalog_version_with_inval_messages( + * db_oid, is_breaking_change, is_global_ddl, messages). + */ + uint64_t new_version = + YbCallNewSQLIncrementCatalogVersion(func_oid, db_oid, + is_breaking_change, + command_tag, messages, + is_null, expiration_secs); + + /* + * The new version of database_oid is only meaningful when + * db_oid == MyDatabaseId. + */ + if (db_oid == MyDatabaseId) + YbSetNewCatalogVersion(new_version); + + return; + } + } + + if (is_global_ddl) + { + Assert(YBIsDBCatalogVersionMode()); + Oid func_oid = YbGetSQLIncrementCatalogVersionsFunctionOid(); + + Assert(OidIsValid(func_oid)); + /* Call yb_increment_all_db_catalog_versions(is_breaking_change). */ + YbCallSQLIncrementCatalogVersions(func_oid, is_breaking_change, + command_tag); + return; + } + + /* + * There are two more scenarios we also call the function + * yb_increment_all_db_catalog_versions(is_breaking_change), both + * are related to cluster upgrade to a new release that has the + * per-database catalog version mode on by default: + * (1) during the tryout phase (the phase where the upgrade can be + * rolled back) + * (2) during the finalization phase (the phase where the upgrade can + * not be rolled back) + * In both cases, the gflag --ysql_enable_db_catalog_version_mode is + * true but YBIsDBCatalogVersionMode() is false. PG does not + * distinguish them. In (1), the table pg_yb_catalog_version has only + * one row. In (2), it is possible that pg_yb_catalog_version has + * already been updated to have multiple rows, but this PG backend + * hasn't seen multple rows yet and still operates in global catalog + * version mode. Calling yb_increment_all_db_catalog_versions ensures + * that this PG's version bump has an effect to all the PG backends + * including those already upgraded and are operating in per-database + * catalog version mode. + */ + if (*YBCGetGFlags()->ysql_enable_db_catalog_version_mode && + !YBIsDBCatalogVersionMode()) + { + Oid func_oid = YbGetSQLIncrementCatalogVersionsFunctionOid(); + + if (OidIsValid(func_oid)) + { + /* Call yb_increment_all_db_catalog_versions(is_breaking_change). */ + YbCallSQLIncrementCatalogVersions(func_oid, is_breaking_change, + command_tag); + return; + } + /* + * If the function yb_increment_all_db_catalog_versions does not exist + * yet, there cannot be any PG backend in the cluster running in + * per-database catalog version mode. This is because the function + * is introduced before the table pg_yb_catalog_version is upgraded + * to have one row per database. In this case we continue to increment + * catalog version in the old way below. + */ + } + + YbcPgTypeAttrs type_attrs = {0}; + + Relation rel = RelationIdGetRelation(YBCatalogVersionRelationId); + + YbcPgStatement update_stmt = YbNewUpdate(rel, YB_TRANSACTIONAL); + + Datum ybctid = YbGetMasterCatalogVersionTableEntryYbctid(rel, db_oid); + + /* Bind ybctid to identify the current row. */ + YbcPgExpr ybctid_expr = YBCNewConstant(update_stmt, BYTEAOID, InvalidOid, + ybctid, false /* is_null */ ); + + HandleYBStatus(YBCPgDmlBindColumn(update_stmt, YBTupleIdAttributeNumber, + ybctid_expr)); + + /* Set expression c = c + 1 for current version attribute. */ + AttrNumber attnum = Anum_pg_yb_catalog_version_current_version; + Var *arg1 = makeVar(1, + attnum, + INT8OID, + 0, + InvalidOid, + 0); + + Const *arg2 = makeConst(INT8OID, + 0, + InvalidOid, + sizeof(int64), + (Datum) 1, + false, + true); + + List *args = list_make2(arg1, arg2); + + FuncExpr *expr = makeFuncExpr(F_INT8PL, + INT8OID, + args, + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); + + /* INT8 OID. */ + YbcPgExpr ybc_expr = YBCNewEvalExprCall(update_stmt, (Expr *) expr); + + HandleYBStatus(YBCPgDmlAssignColumn(update_stmt, attnum, ybc_expr)); + YbcPgExpr yb_expr = YBCNewColumnRef(update_stmt, attnum, INT8OID, InvalidOid, &type_attrs); + + YbAppendPrimaryColumnRef(update_stmt, yb_expr); + + /* + * If breaking change set the latest breaking version to the same + * expression. + */ + if (is_breaking_change) + { + ybc_expr = YBCNewEvalExprCall(update_stmt, (Expr *) expr); + HandleYBStatus(YBCPgDmlAssignColumn(update_stmt, attnum + 1, ybc_expr)); + } + + int rows_affected_count = 0; + + if (!(*YBCGetGFlags()->TEST_hide_details_for_pg_regress)) + { + bool log_ysql_catalog_versions = *YBCGetGFlags()->log_ysql_catalog_versions; + char tmpbuf[30] = ""; + + if (YBIsDBCatalogVersionMode()) + snprintf(tmpbuf, sizeof(tmpbuf), " for database %u", db_oid); + ereport(LOG, + (errmsg("%s: incrementing master catalog version (%sbreaking)%s", + __func__, is_breaking_change ? "" : "non", tmpbuf), + errdetail("Local version: %" PRIu64 ", %snode tag: %s.", + YbGetCatalogCacheVersion(), + LastDdlInTransactionBlock() ? "last ddl " : "", + command_tag ? command_tag : "n/a"), + errhidestmt(!log_ysql_catalog_versions), + errhidecontext(!log_ysql_catalog_versions))); + } + + HandleYBStatus(YBCPgDmlExecWriteOp(update_stmt, &rows_affected_count)); + /* + * Under normal situation rows_affected_count should be exactly 1. However + * when a connection is established in per-database catalog version mode, + * if the table pg_yb_catalog_version is updated to only have one row + * for database template1 which is part of the process of converting to + * global catalog version mode, this connection remains in per-database + * catalog version mode. In this case rows_affected_count will be 0 unless + * MyDatabaseId is template1 because in pg_yb_catalog_version the row for + * MyDatabaseId no longer exists. + */ + if (rows_affected_count == 0) + Assert(YBIsDBCatalogVersionMode()); + else + Assert(rows_affected_count == 1); + + /* Cleanup. */ + update_stmt = NULL; + RelationClose(rel); +} + +bool +YbIncrementMasterCatalogVersionTableEntry(bool is_breaking_change, + bool is_global_ddl, + const char *command_tag, + const SharedInvalidationMessage *invalMessages, + int nmsgs) +{ + elog(DEBUG2, "YbIncrementMasterCatalogVersionTableEntry"); + YbResetNewCatalogVersion(); + if (YbGetCatalogVersionType() != CATALOG_VERSION_CATALOG_TABLE) + return false; + + Oid database_oid = YbGetDatabaseOidToIncrementCatalogVersion(); + + Assert(OidIsValid(database_oid)); + + YbIncrementMasterDBCatalogVersionTableEntryImpl(database_oid, + is_breaking_change, + is_global_ddl, + command_tag, + invalMessages, + nmsgs); + if (yb_test_fail_next_inc_catalog_version) + { + yb_test_fail_next_inc_catalog_version = false; + if (YbIsClientYsqlConnMgr()) + YbSendParameterStatusForConnectionManager("yb_test_fail_next_inc_catalog_version", + "false"); + elog(ERROR, "Failed increment catalog version as requested"); + } + + return true; +} + +bool +YbMarkStatementIfCatalogVersionIncrement(YbcPgStatement ybc_stmt, + Relation rel) +{ + if (YbGetCatalogVersionType() != CATALOG_VERSION_PROTOBUF_ENTRY) + { + /* + * Nothing to do -- only need to maintain this for the (old) + * protobuf-based way of storing the version. + */ + return false; + } + + bool is_syscatalog_change = YbIsSystemCatalogChange(rel); + bool modifies_row = false; + + HandleYBStatus(YBCPgDmlModifiesRow(ybc_stmt, &modifies_row)); + + /* + * If this write may invalidate catalog cache tuples (i.e. UPDATE or DELETE), + * or this write may insert into a cached list, we must increment the + * cache version so other sessions can invalidate their caches. + * NOTE: If this relation caches lists, an INSERT could effectively be + * UPDATINGing the list object. + */ + bool is_syscatalog_version_change = (is_syscatalog_change && + (modifies_row || + RelationHasCachedLists(rel))); + + /* Let the master know if this should increment the catalog version. */ + if (is_syscatalog_version_change) + { + HandleYBStatus(YBCPgSetIsSysCatalogVersionChange(ybc_stmt)); + } + + return is_syscatalog_version_change; +} + +void +YbCreateMasterDBCatalogVersionTableEntry(Oid db_oid) +{ + Assert(YbGetCatalogVersionType() == CATALOG_VERSION_CATALOG_TABLE); + Assert(db_oid != MyDatabaseId); + + if (!YBCIsLegacyModeForCatalogOps()) + LockRelationOid(YBCatalogVersionRelationId, ExclusiveLock); + + /* + * The table pg_yb_catalog_version is a shared relation in template1 and + * db_oid is the primary key. There is no separate docdb index table for + * primary key and therefore only one insert statement is needed to insert + * the row for db_oid. + */ + Relation rel = RelationIdGetRelation(YBCatalogVersionRelationId); + + YbcPgStatement insert_stmt = YbNewInsert(rel, YB_SINGLE_SHARD_TRANSACTION); + + Datum ybctid = YbGetMasterCatalogVersionTableEntryYbctid(rel, db_oid); + + YbcPgExpr ybctid_expr = YBCNewConstant(insert_stmt, BYTEAOID, InvalidOid, + ybctid, false /* is_null */ ); + + HandleYBStatus(YBCPgDmlBindColumn(insert_stmt, YBTupleIdAttributeNumber, + ybctid_expr)); + + + + AttrNumber attnum = Anum_pg_yb_catalog_version_current_version; + Datum initial_version = 1; + YbcPgExpr initial_version_expr = YBCNewConstant(insert_stmt, INT8OID, + InvalidOid, + initial_version, + false /* is_null */ ); + + HandleYBStatus(YBCPgDmlBindColumn(insert_stmt, attnum, + initial_version_expr)); + HandleYBStatus(YBCPgDmlBindColumn(insert_stmt, attnum + 1, + initial_version_expr)); + + int rows_affected_count = 0; + + if (*YBCGetGFlags()->log_ysql_catalog_versions) + ereport(LOG, + (errmsg("%s: creating master catalog version for database %u", + __func__, db_oid))); + HandleYBStatus(YBCPgDmlExecWriteOp(insert_stmt, &rows_affected_count)); + /* Insert a new row does not affect any existing rows. */ + Assert(rows_affected_count == 0); + + /* Cleanup. */ + RelationClose(rel); +} + +void +YbDeleteMasterDBInvalidationMessagesTableEntries(Oid db_oid) +{ + /* + * Connect to SPI manager + */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + char query[100]; + + sprintf(query, "DELETE FROM pg_catalog.pg_yb_invalidation_messages WHERE db_oid = %u", db_oid); + SPIPlanPtr plan = SPI_prepare(query, 0, NULL); + + if (plan == NULL) + elog(ERROR, "SPI_prepare failed for \"%s\"", query); + + bool saved_yb_is_calling_internal_sql_for_ddl = yb_is_calling_internal_sql_for_ddl; + Oid save_userid; + int save_sec_context; + + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID, + SECURITY_RESTRICTED_OPERATION); + yb_is_calling_internal_sql_for_ddl = true; + PG_TRY(); + { + int spirc = SPI_execute_plan(plan, NULL, NULL, false, 0); + + yb_is_calling_internal_sql_for_ddl = saved_yb_is_calling_internal_sql_for_ddl; + SetUserIdAndSecContext(save_userid, save_sec_context); + if (spirc != SPI_OK_DELETE) + elog(ERROR, "SPI_execute_plan failed for \"%s\"", query); + ereport((*YBCGetGFlags()->log_ysql_catalog_versions ? LOG : DEBUG1), + (errmsg("%s: deleted %lu invalidation messages for database %u", + __func__, SPI_processed, db_oid))); + } + PG_CATCH(); + { + yb_is_calling_internal_sql_for_ddl = saved_yb_is_calling_internal_sql_for_ddl; + SetUserIdAndSecContext(save_userid, save_sec_context); + PG_RE_THROW(); + } + PG_END_TRY(); + + /* + * Disconnect from SPI manager + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); +} + +void +YbDeleteMasterDBCatalogVersionTableEntry(Oid db_oid) +{ + Assert(YbGetCatalogVersionType() == CATALOG_VERSION_CATALOG_TABLE); + Assert(db_oid != MyDatabaseId); + + if (!YBCIsLegacyModeForCatalogOps()) + LockRelationOid(YBCatalogVersionRelationId, ExclusiveLock); + + /* + * The table pg_yb_catalog_version is a shared relation in template1 and + * db_oid is the primary key. There is no separate docdb index table for + * primary key and therefore only one delete statement is needed to delete + * the row for db_oid. + */ + + Relation rel = RelationIdGetRelation(YBCatalogVersionRelationId); + + YbcPgStatement delete_stmt = YbNewDelete(rel, YB_SINGLE_SHARD_TRANSACTION); + + Datum ybctid = YbGetMasterCatalogVersionTableEntryYbctid(rel, db_oid); + + YbcPgExpr ybctid_expr = YBCNewConstant(delete_stmt, BYTEAOID, InvalidOid, + ybctid, false /* is_null */ ); + + HandleYBStatus(YBCPgDmlBindColumn(delete_stmt, YBTupleIdAttributeNumber, + ybctid_expr)); + + int rows_affected_count = 0; + + if (*YBCGetGFlags()->log_ysql_catalog_versions) + ereport(LOG, + (errmsg("%s: deleting master catalog version for database %u", + __func__, db_oid))); + HandleYBStatus(YBCPgDmlExecWriteOp(delete_stmt, &rows_affected_count)); + Assert(rows_affected_count == 1); + + RelationClose(rel); + + /* + * When invalidation messages are enabled, we also need to delete the + * invalidation messages stored in pg_yb_invalidation_messages table + * for db_oid. + */ + if (YbIsInvalidationMessageEnabled() && YbInvalidationMessagesTableExists()) + YbDeleteMasterDBInvalidationMessagesTableEntries(db_oid); +} + +YbCatalogVersionType +YbGetCatalogVersionType() +{ + if (IsBootstrapProcessingMode()) + { + /* + * We don't have the catalog version table at the start of initdb, + * and there's no point in switching later on. + */ + yb_catalog_version_type = CATALOG_VERSION_PROTOBUF_ENTRY; + } + else if (yb_catalog_version_type == CATALOG_VERSION_UNSET) + { + bool catalog_version_table_exists = false; + + HandleYBStatus(YBCPgTableExists(Template1DbOid, + YBCatalogVersionRelationId, + &catalog_version_table_exists)); + yb_catalog_version_type = (catalog_version_table_exists ? + CATALOG_VERSION_CATALOG_TABLE : + CATALOG_VERSION_PROTOBUF_ENTRY); + } + return yb_catalog_version_type; +} + + +/* + * Check if operation changes a system table, ignore changes during + * initialization (bootstrap mode). + */ +bool +YbIsSystemCatalogChange(Relation rel) +{ + return IsCatalogRelation(rel) && !IsBootstrapProcessingMode(); +} + +bool +YbGetMasterCatalogVersionFromTable(Oid db_oid, uint64_t *version, + bool acquire_row_lock) +{ + *version = 0; /* unset; */ + + int natts = Natts_pg_yb_catalog_version; + + /* + * pg_yb_catalog_version is a shared catalog table, so as per DocDB store, + * it belongs to the template1 database. + */ + int oid_attnum = Anum_pg_yb_catalog_version_db_oid; + int current_version_attnum = Anum_pg_yb_catalog_version_current_version; + Form_pg_attribute oid_attrdesc = &Desc_pg_yb_catalog_version[oid_attnum - 1]; + + YbcPgStatement ybc_stmt; + + HandleYBStatus(YBCPgNewSelect(Template1DbOid, + YBCatalogVersionRelationId, + NULL /* prepare_params */ , + YbBuildSystemTableLocalityInfo(YBCatalogVersionRelationId), + &ybc_stmt)); + + if (!(acquire_row_lock && yb_use_internal_auto_analyze_service_conn)) + { + Datum oid_datum = Int32GetDatum(db_oid); + YbcPgExpr pkey_expr = YBCNewConstant(ybc_stmt, oid_attrdesc->atttypid, + oid_attrdesc->attcollation, + oid_datum, false /* is_null */ ); + + HandleYBStatus(YBCPgDmlBindColumn(ybc_stmt, 1, pkey_expr)); + } + for (AttrNumber attnum = 1; attnum <= natts; ++attnum) + YbDmlAppendTargetRegularAttr(&Desc_pg_yb_catalog_version[attnum - 1], + ybc_stmt); + + YbcPgExecParameters exec_params = {0}; + + if (acquire_row_lock) + { + /* + * We want to stick to Fail-on-Conflict concurrency control to ensure that higher priority + * user DDLs always take precedence over lower priority auto-ANALYZEs. In other words, user DDLs + * should abort running auto-ANALYZEs, and auto-ANALYZEs should face a conflict error if a user + * DDL is already running. + */ + exec_params.pg_wait_policy = LockWaitError; + exec_params.docdb_wait_policy = YBGetDocDBWaitPolicy(exec_params.pg_wait_policy); + exec_params.rowmark = yb_use_internal_auto_analyze_service_conn ? + ROW_MARK_EXCLUSIVE : + ROW_MARK_KEYSHARE; + } + + HandleYBStatus(YBCPgExecSelect(ybc_stmt, acquire_row_lock ? &exec_params : NULL)); + + bool has_data = false; + + Datum *values = palloc0(natts * sizeof(Datum)); + bool *nulls = palloc(natts * sizeof(bool)); + YbcPgSysColumns syscols; + bool result = false; + + if (!YBIsDBCatalogVersionMode()) + { + /* Fetch one row. */ + HandleYBStatus(YBCPgDmlFetch(ybc_stmt, + natts, + (uint64_t *) values, + nulls, + &syscols, + &has_data)); + + if (has_data) + { + *version = DatumGetUInt64(values[current_version_attnum - 1]); + result = true; + } + } + else + { + /* + * When prefetching is enabled we always load all the rows even though + * we bind to the row matching given db_oid. This is a work around to + * pick the row that matches db_oid. This work around should be removed + * when prefetching is enhanced to support filtering. + */ + while (true) + { + /* Fetch one row. */ + HandleYBStatus(YBCPgDmlFetch(ybc_stmt, + natts, + (uint64_t *) values, + nulls, + &syscols, + &has_data)); + + if (!has_data) + ereport(ERROR, + (errcode(ERRCODE_DATABASE_DROPPED), + errmsg("catalog version for database %u was not found.", db_oid), + errhint("Database may have been dropped and recreated. " + "Ensure your client connection pool or metadata cache " + "is refreshed to pick up the new database OID."))); + + uint32_t oid = DatumGetUInt32(values[oid_attnum - 1]); + + if (oid == db_oid) + { + *version = DatumGetUInt64(values[current_version_attnum - 1]); + result = true; + break; + } + } + } + + pfree(values); + pfree(nulls); + return result; +} + +Datum +YbGetMasterCatalogVersionTableEntryYbctid(Relation catalog_version_rel, + Oid db_oid) +{ + /* + * Construct virtual slot (db_oid, null, null) for computing ybctid using + * YBCComputeYBTupleIdFromSlot. Note that db_oid is the primary key so we + * can use null for other columns for simplicity. + */ + TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(catalog_version_rel), + &TTSOpsVirtual); + + slot->tts_values[0] = db_oid; + slot->tts_isnull[0] = false; + slot->tts_values[1] = 0; + slot->tts_isnull[1] = true; + slot->tts_values[2] = 0; + slot->tts_isnull[2] = true; + slot->tts_nvalid = 3; + + Datum ybctid = YBCComputeYBTupleIdFromSlot(catalog_version_rel, slot); + + ExecDropSingleTupleTableSlot(slot); + return ybctid; +} + +Oid +YbMasterCatalogVersionTableDBOid() +{ + /* + * MyDatabaseId is 0 during connection setup time before + * MyDatabaseId is resolved. In per-db mode, we use Template1DbOid + * during this period to find the catalog version in order to load + * initial catalog cache (needed for resolving MyDatabaseId, auth + * check etc.). Once MyDatabaseId is resolved from then on we'll + * use its catalog version. + */ + + return YBIsDBCatalogVersionMode() && OidIsValid(MyDatabaseId) + ? MyDatabaseId : Template1DbOid; +} diff --git a/src/backend/catalog/yb_catalog/yb_oid_assignment.c b/src/backend/catalog/yb_catalog/yb_oid_assignment.c new file mode 100644 index 0000000000000..1715365c8d10f --- /dev/null +++ b/src/backend/catalog/yb_catalog/yb_oid_assignment.c @@ -0,0 +1,433 @@ +/*-------------------------------------------------------------------------------------------------- + * + * yb_oid_assignment.c + * Functions for controlling assigning OIDs in xCluster target universes. + * + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations + * under the License. + * + * IDENTIFICATION + * src/backend/catalog/yb_catalog/yb_oid_assignment.c + * + *-------------------------------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/builtins.h" +#include "utils/hsearch.h" +#include "utils/jsonfuncs.h" + + +static HTAB *yb_enum_label_assignment_map = NULL; +static bool yb_enum_label_assignment_exists = false; + +static HTAB *yb_oid_assignment_map = NULL; +static bool yb_type_oid_assignment_exists = false; +static bool yb_sequence_oid_assignment_exists = false; + +/* + * yb_enum_label_assignment_map key format is .
.* column when inference element misspelled: +insert into insertconflicttest values (1, 'Apple') on conflict (keyy) do update set fruit = excluded.fruit; +ERROR: column "keyy" does not exist +LINE 1: ...nsertconflicttest values (1, 'Apple') on conflict (keyy) do ... + ^ +HINT: Perhaps you meant to reference the column "insertconflicttest.key" or the column "excluded.key". +-- Have useful HINT for EXCLUDED.* RTE within UPDATE: +insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruitt; +ERROR: column excluded.fruitt does not exist +LINE 1: ... 'Apple') on conflict (key) do update set fruit = excluded.f... + ^ +HINT: Perhaps you meant to reference the column "excluded.fruit". +-- inference fails: +insert into insertconflicttest values (3, 'Kiwi') on conflict (key, fruit) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (4, 'Mango') on conflict (fruit, key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (5, 'Lemon') on conflict (fruit) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (6, 'Passionfruit') on conflict (lower(fruit)) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +-- Check the target relation can be aliased +insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (key) do update set fruit = excluded.fruit; -- ok, no reference to target table +insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (key) do update set fruit = ict.fruit; -- ok, alias +insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (key) do update set fruit = insertconflicttest.fruit; -- error, references aliased away name +ERROR: invalid reference to FROM-clause entry for table "insertconflicttest" +LINE 1: ...onfruit') on conflict (key) do update set fruit = insertconf... + ^ +HINT: Perhaps you meant to reference the table alias "ict". +drop index key_index; +-- +-- Composite key tests +-- +create unique index comp_key_index on insertconflicttest(key, fruit); +-- inference succeeds: +insert into insertconflicttest values (7, 'Raspberry') on conflict (key, fruit) do update set fruit = excluded.fruit; +insert into insertconflicttest values (8, 'Lime') on conflict (fruit, key) do update set fruit = excluded.fruit; +-- inference fails: +insert into insertconflicttest values (9, 'Banana') on conflict (key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (10, 'Blueberry') on conflict (key, key, key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (11, 'Cherry') on conflict (key, lower(fruit)) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (12, 'Date') on conflict (lower(fruit), key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop index comp_key_index; +-- +-- Partial index tests, no inference predicate specified +-- +create unique index part_comp_key_index on insertconflicttest(key, fruit) where key < 5; +create unique index expr_part_comp_key_index on insertconflicttest(key, lower(fruit)) where key < 5; +-- inference fails: +insert into insertconflicttest values (13, 'Grape') on conflict (key, fruit) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (14, 'Raisin') on conflict (fruit, key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (15, 'Cranberry') on conflict (key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (16, 'Melon') on conflict (key, key, key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (17, 'Mulberry') on conflict (key, lower(fruit)) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (18, 'Pineapple') on conflict (lower(fruit), key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop index part_comp_key_index; +drop index expr_part_comp_key_index; +-- +-- Expression index tests +-- +create unique index expr_key_index on insertconflicttest(lower(fruit)); +-- inference succeeds: +insert into insertconflicttest values (20, 'Quince') on conflict (lower(fruit)) do update set fruit = excluded.fruit; +insert into insertconflicttest values (21, 'Pomegranate') on conflict (lower(fruit), lower(fruit)) do update set fruit = excluded.fruit; +-- inference fails: +insert into insertconflicttest values (22, 'Apricot') on conflict (upper(fruit)) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (23, 'Blackberry') on conflict (fruit) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop index expr_key_index; +-- +-- Expression index tests (with regular column) +-- +create unique index expr_comp_key_index on insertconflicttest(key, lower(fruit)); +create unique index tricky_expr_comp_key_index on insertconflicttest(key, lower(fruit), upper(fruit)); +-- inference succeeds: +insert into insertconflicttest values (24, 'Plum') on conflict (key, lower(fruit)) do update set fruit = excluded.fruit; +insert into insertconflicttest values (25, 'Peach') on conflict (lower(fruit), key) do update set fruit = excluded.fruit; +-- Should not infer "tricky_expr_comp_key_index" index: +explain (costs off) insert into insertconflicttest values (26, 'Fig') on conflict (lower(fruit), key, lower(fruit), key) do update set fruit = excluded.fruit; + QUERY PLAN +------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: expr_comp_key_index + -> Result *RESULT* +(4 rows) + +-- inference fails: +insert into insertconflicttest values (27, 'Prune') on conflict (key, upper(fruit)) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (28, 'Redcurrant') on conflict (fruit, key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (29, 'Nectarine') on conflict (key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop index expr_comp_key_index; +drop index tricky_expr_comp_key_index; +-- +-- Non-spurious duplicate violation tests +-- +create unique index key_index on insertconflicttest(key); +create unique index fruit_index on insertconflicttest(fruit); +-- succeeds, since UPDATE happens to update "fruit" to existing value: +insert into insertconflicttest values (26, 'Fig') on conflict (key) do update set fruit = excluded.fruit; +-- fails, since UPDATE is to row with key value 26, and we're updating "fruit" +-- to a value that happens to exist in another row ('peach'): +insert into insertconflicttest values (26, 'Peach') on conflict (key) do update set fruit = excluded.fruit; -- YB: output is missing detail because YB handles duplicate detection differently +ERROR: duplicate key value violates unique constraint "fruit_index" +-- succeeds, since "key" isn't repeated/referenced in UPDATE, and "fruit" +-- arbitrates that statement updates existing "Fig" row: +insert into insertconflicttest values (25, 'Fig') on conflict (fruit) do update set fruit = excluded.fruit; +drop index key_index; +drop index fruit_index; +-- +-- Test partial unique index inference +-- +create unique index partial_key_index on insertconflicttest(key) where fruit like '%berry'; +-- Succeeds +insert into insertconflicttest values (23, 'Blackberry') on conflict (key) where fruit like '%berry' do update set fruit = excluded.fruit; +insert into insertconflicttest as t values (23, 'Blackberry') on conflict (key) where fruit like '%berry' and t.fruit = 'inconsequential' do nothing; +-- fails +insert into insertconflicttest values (23, 'Blackberry') on conflict (key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (23, 'Blackberry') on conflict (key) where fruit like '%berry' or fruit = 'consequential' do nothing; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (23, 'Blackberry') on conflict (fruit) where fruit like '%berry' do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop index partial_key_index; +-- +-- Test that wholerow references to ON CONFLICT's EXCLUDED work +-- +create unique index plain on insertconflicttest(key); +-- Succeeds, updates existing row: +insert into insertconflicttest as i values (23, 'Jackfruit') on conflict (key) do update set fruit = excluded.fruit + where i.* != excluded.* returning *; + key | fruit +-----+----------- + 23 | Jackfruit +(1 row) + +-- No update this time, though: +insert into insertconflicttest as i values (23, 'Jackfruit') on conflict (key) do update set fruit = excluded.fruit + where i.* != excluded.* returning *; + key | fruit +-----+------- +(0 rows) + +-- Predicate changed to require match rather than non-match, so updates once more: +insert into insertconflicttest as i values (23, 'Jackfruit') on conflict (key) do update set fruit = excluded.fruit + where i.* = excluded.* returning *; + key | fruit +-----+----------- + 23 | Jackfruit +(1 row) + +-- Assign: +insert into insertconflicttest as i values (23, 'Avocado') on conflict (key) do update set fruit = excluded.*::text + returning *; + key | fruit +-----+-------------- + 23 | (23,Avocado) +(1 row) + +-- deparse whole row var in WHERE and SET clauses: +explain (costs off) insert into insertconflicttest as i values (23, 'Avocado') on conflict (key) do update set fruit = excluded.fruit where excluded.* is null; + QUERY PLAN +----------------------------------------- + Insert on insertconflicttest i + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: plain + Conflict Filter: (excluded.* IS NULL) + -> Result *RESULT* +(5 rows) + +explain (costs off) insert into insertconflicttest as i values (23, 'Avocado') on conflict (key) do update set fruit = excluded.*::text; + QUERY PLAN +----------------------------------- + Insert on insertconflicttest i + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: plain + -> Result *RESULT* +(4 rows) + +drop index plain; +-- Cleanup +drop table insertconflicttest; +-- +-- Verify that EXCLUDED does not allow system column references. These +-- do not make sense because EXCLUDED isn't an already stored tuple +-- (and thus doesn't have a ctid etc). +-- +create table syscolconflicttest(key int4, data text); +insert into syscolconflicttest values (1); +insert into syscolconflicttest values (1) on conflict (key) do update set data = excluded.ctid::text; -- YB: output shows YB error message hit first before PG one +ERROR: system column "ctid" is not supported yet +drop table syscolconflicttest; +-- +-- Previous tests all managed to not test any expressions requiring +-- planner preprocessing ... +-- +create table insertconflict (a bigint, b bigint); +create unique index insertconflicti1 on insertconflict(coalesce(a, 0)); +create unique index insertconflicti2 on insertconflict(b) + where coalesce(a, 1) > 0; +insert into insertconflict values (1, 2) +on conflict (coalesce(a, 0)) do nothing; +insert into insertconflict values (1, 2) +on conflict (b) where coalesce(a, 1) > 0 do nothing; +insert into insertconflict values (1, 2) +on conflict (b) where coalesce(a, 1) > 1 do nothing; +drop table insertconflict; +-- +-- test insertion through view +-- +create table insertconflict (f1 int primary key, f2 text); +create view insertconflictv as + select * from insertconflict with cascaded check option; +ERROR: VIEW WITH CASCADED CHECK OPTION not supported yet +LINE 2: select * from insertconflict with cascaded check option; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +create view insertconflictv as select * from insertconflict; -- YB: workaround view to replace above +insert into insertconflictv values (1,'foo') + on conflict (f1) do update set f2 = excluded.f2; +select * from insertconflict; + f1 | f2 +----+----- + 1 | foo +(1 row) + +insert into insertconflictv values (1,'bar') + on conflict (f1) do update set f2 = excluded.f2; +select * from insertconflict; + f1 | f2 +----+----- + 1 | bar +(1 row) + +drop view insertconflictv; +drop table insertconflict; +-- ****************************************************************** +-- * * +-- * Test inheritance (example taken from tutorial) * +-- * * +-- ****************************************************************** +create table cities ( + name text, + population float8, + altitude int -- (in ft) +); +create table capitals ( + state char(2) +) inherits (cities); +-- Create unique indexes. Due to a general limitation of inheritance, +-- uniqueness is only enforced per-relation. Unique index inference +-- specification will do the right thing, though. +create unique index cities_names_unique on cities (name); +create unique index capitals_names_unique on capitals (name); +-- prepopulate the tables. +insert into cities values ('San Francisco', 7.24E+5, 63); +insert into cities values ('Las Vegas', 2.583E+5, 2174); +insert into cities values ('Mariposa', 1200, 1953); +insert into capitals values ('Sacramento', 3.694E+5, 30, 'CA'); +insert into capitals values ('Madison', 1.913E+5, 845, 'WI'); +-- Tests proper for inheritance: +select * from capitals; + name | population | altitude | state +------------+------------+----------+------- + Sacramento | 369400 | 30 | CA + Madison | 191300 | 845 | WI +(2 rows) + +-- Succeeds: +insert into cities values ('Las Vegas', 2.583E+5, 2174) on conflict do nothing; +WITH ybtmp AS (DELETE FROM capitals WHERE name = 'Sacramento' RETURNING *) + INSERT INTO capitals SELECT * FROM ybtmp; -- YB: imitate PG ctid reallocation for the following update +insert into capitals values ('Sacramento', 4664.E+5, 30, 'CA') on conflict (name) do update set population = excluded.population; +-- Wrong "Sacramento", so do nothing: +insert into capitals values ('Sacramento', 50, 2267, 'NE') on conflict (name) do nothing; +select * from capitals; + name | population | altitude | state +------------+------------+----------+------- + Madison | 191300 | 845 | WI + Sacramento | 466400000 | 30 | CA +(2 rows) + +with ybtmp as (delete from cities where name = 'Las Vegas' returning *) + insert into cities select * from ybtmp; -- YB: imitate PG ctid reallocation for the following update +insert into cities values ('Las Vegas', 5.83E+5, 2001) on conflict (name) do update set population = excluded.population, altitude = excluded.altitude; +select tableoid::regclass, * from cities; + tableoid | name | population | altitude +----------+---------------+------------+---------- + cities | San Francisco | 724000 | 63 + cities | Mariposa | 1200 | 1953 + cities | Las Vegas | 583000 | 2001 + capitals | Madison | 191300 | 845 + capitals | Sacramento | 466400000 | 30 +(5 rows) + +insert into capitals values ('Las Vegas', 5.83E+5, 2222, 'NV') on conflict (name) do update set population = excluded.population; +-- Capitals will contain new capital, Las Vegas: +select * from capitals; + name | population | altitude | state +------------+------------+----------+------- + Madison | 191300 | 845 | WI + Sacramento | 466400000 | 30 | CA + Las Vegas | 583000 | 2222 | NV +(3 rows) + +-- Cities contains two instances of "Las Vegas", since unique constraints don't +-- work across inheritance: +select tableoid::regclass, * from cities; + tableoid | name | population | altitude +----------+---------------+------------+---------- + cities | San Francisco | 724000 | 63 + cities | Mariposa | 1200 | 1953 + cities | Las Vegas | 583000 | 2001 + capitals | Madison | 191300 | 845 + capitals | Sacramento | 466400000 | 30 + capitals | Las Vegas | 583000 | 2222 +(6 rows) + +-- This only affects "cities" version of "Las Vegas": +insert into cities values ('Las Vegas', 5.86E+5, 2223) on conflict (name) do update set population = excluded.population, altitude = excluded.altitude; +select tableoid::regclass, * from cities; + tableoid | name | population | altitude +----------+---------------+------------+---------- + cities | San Francisco | 724000 | 63 + cities | Mariposa | 1200 | 1953 + cities | Las Vegas | 586000 | 2223 + capitals | Madison | 191300 | 845 + capitals | Sacramento | 466400000 | 30 + capitals | Las Vegas | 583000 | 2222 +(6 rows) + +-- clean up +drop table capitals; +drop table cities; +-- Make sure a table named excluded is handled properly +create table excluded(key int primary key, data text); +insert into excluded values(1, '1'); +-- error, ambiguous +insert into excluded values(1, '2') on conflict (key) do update set data = excluded.data RETURNING *; +ERROR: table reference "excluded" is ambiguous +LINE 1: ...es(1, '2') on conflict (key) do update set data = excluded.d... + ^ +-- ok, aliased +insert into excluded AS target values(1, '2') on conflict (key) do update set data = excluded.data RETURNING *; + key | data +-----+------ + 1 | 2 +(1 row) + +-- ok, aliased +insert into excluded AS target values(1, '2') on conflict (key) do update set data = target.data RETURNING *; + key | data +-----+------ + 1 | 2 +(1 row) + +-- make sure excluded isn't a problem in returning clause +insert into excluded values(1, '2') on conflict (key) do update set data = 3 RETURNING excluded.*; + key | data +-----+------ + 1 | 3 +(1 row) + +-- clean up +drop table excluded; +-- check that references to columns after dropped columns are handled correctly +create table dropcol(key int primary key, drop1 int, keep1 text, drop2 numeric, keep2 float); +insert into dropcol(key, drop1, keep1, drop2, keep2) values(1, 1, '1', '1', 1); +-- set using excluded +insert into dropcol(key, drop1, keep1, drop2, keep2) values(1, 2, '2', '2', 2) on conflict(key) + do update set drop1 = excluded.drop1, keep1 = excluded.keep1, drop2 = excluded.drop2, keep2 = excluded.keep2 + where excluded.drop1 is not null and excluded.keep1 is not null and excluded.drop2 is not null and excluded.keep2 is not null + and dropcol.drop1 is not null and dropcol.keep1 is not null and dropcol.drop2 is not null and dropcol.keep2 is not null + returning *; + key | drop1 | keep1 | drop2 | keep2 +-----+-------+-------+-------+------- + 1 | 2 | 2 | 2 | 2 +(1 row) + +; +-- set using existing table +insert into dropcol(key, drop1, keep1, drop2, keep2) values(1, 3, '3', '3', 3) on conflict(key) + do update set drop1 = dropcol.drop1, keep1 = dropcol.keep1, drop2 = dropcol.drop2, keep2 = dropcol.keep2 + returning *; + key | drop1 | keep1 | drop2 | keep2 +-----+-------+-------+-------+------- + 1 | 2 | 2 | 2 | 2 +(1 row) + +; +alter table dropcol drop column drop1, drop column drop2; +-- set using excluded +insert into dropcol(key, keep1, keep2) values(1, '4', 4) on conflict(key) + do update set keep1 = excluded.keep1, keep2 = excluded.keep2 + where excluded.keep1 is not null and excluded.keep2 is not null + and dropcol.keep1 is not null and dropcol.keep2 is not null + returning *; + key | keep1 | keep2 +-----+-------+------- + 1 | 4 | 4 +(1 row) + +; +-- set using existing table +insert into dropcol(key, keep1, keep2) values(1, '5', 5) on conflict(key) + do update set keep1 = dropcol.keep1, keep2 = dropcol.keep2 + returning *; + key | keep1 | keep2 +-----+-------+------- + 1 | 4 | 4 +(1 row) + +; +DROP TABLE dropcol; +/* YB: EXCLUDE constraint not supported yet - see #997 +-- check handling of regular btree constraint along with gist constraint + +create table twoconstraints (f1 int unique, f2 box, + exclude using gist(f2 with &&)); +insert into twoconstraints values(1, '((0,0),(1,1))'); +insert into twoconstraints values(1, '((2,2),(3,3))'); -- fail on f1 +insert into twoconstraints values(2, '((0,0),(1,2))'); -- fail on f2 +insert into twoconstraints values(2, '((0,0),(1,2))') + on conflict on constraint twoconstraints_f1_key do nothing; -- fail on f2 +insert into twoconstraints values(2, '((0,0),(1,2))') + on conflict on constraint twoconstraints_f2_excl do nothing; -- do nothing +select * from twoconstraints; +drop table twoconstraints; +*/ -- YB +-- check handling of self-conflicts at various isolation levels +create table selfconflict (f1 int primary key, f2 int); +begin transaction isolation level read committed; +insert into selfconflict values (1,1), (1,2) on conflict do nothing; +commit; +begin transaction isolation level repeatable read; +insert into selfconflict values (2,1), (2,2) on conflict do nothing; +commit; +begin transaction isolation level serializable; +insert into selfconflict values (3,1), (3,2) on conflict do nothing; +commit; +begin transaction isolation level read committed; +insert into selfconflict values (4,1), (4,2) on conflict(f1) do update set f2 = 0; +ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time +HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. +commit; +begin transaction isolation level repeatable read; +insert into selfconflict values (5,1), (5,2) on conflict(f1) do update set f2 = 0; +ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time +HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. +commit; +begin transaction isolation level serializable; +insert into selfconflict values (6,1), (6,2) on conflict(f1) do update set f2 = 0; +ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time +HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. +commit; +select * from selfconflict; + f1 | f2 +----+---- + 1 | 1 + 2 | 1 + 3 | 1 +(3 rows) + +drop table selfconflict; +-- check ON CONFLICT handling with partitioned tables +create table parted_conflict_test (a int unique, b char) partition by list (a); +create table parted_conflict_test_1 partition of parted_conflict_test (b unique) for values in (1, 2); +-- no indexes required here +insert into parted_conflict_test values (1, 'a') on conflict do nothing; +-- index on a required, which does exist in parent +insert into parted_conflict_test values (1, 'a') on conflict (a) do nothing; +insert into parted_conflict_test values (1, 'a') on conflict (a) do update set b = excluded.b; +-- targeting partition directly will work +insert into parted_conflict_test_1 values (1, 'a') on conflict (a) do nothing; +insert into parted_conflict_test_1 values (1, 'b') on conflict (a) do update set b = excluded.b; +-- index on b required, which doesn't exist in parent +insert into parted_conflict_test values (2, 'b') on conflict (b) do update set a = excluded.a; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +-- targeting partition directly will work +insert into parted_conflict_test_1 values (2, 'b') on conflict (b) do update set a = excluded.a; +-- should see (2, 'b') +select * from parted_conflict_test order by a; + a | b +---+--- + 2 | b +(1 row) + +-- now check that DO UPDATE works correctly for target partition with +-- different attribute numbers +create table parted_conflict_test_2 (b char, a int unique); +alter table parted_conflict_test attach partition parted_conflict_test_2 for values in (3); +truncate parted_conflict_test; +insert into parted_conflict_test values (3, 'a') on conflict (a) do update set b = excluded.b; +insert into parted_conflict_test values (3, 'b') on conflict (a) do update set b = excluded.b; +-- should see (3, 'b') +select * from parted_conflict_test order by a; + a | b +---+--- + 3 | b +(1 row) + +-- case where parent will have a dropped column, but the partition won't +-- YB Note: Dropping and adding a column with the same name is not supported in YB. +-- Split the operation into two steps. +alter table parted_conflict_test drop b; +alter table parted_conflict_test add b char; +create table parted_conflict_test_3 partition of parted_conflict_test for values in (4); +truncate parted_conflict_test; +insert into parted_conflict_test (a, b) values (4, 'a') on conflict (a) do update set b = excluded.b; +insert into parted_conflict_test (a, b) values (4, 'b') on conflict (a) do update set b = excluded.b where parted_conflict_test.b = 'a'; +-- should see (4, 'b') +select * from parted_conflict_test order by a; + a | b +---+--- + 4 | b +(1 row) + +-- case with multi-level partitioning +create table parted_conflict_test_4 partition of parted_conflict_test for values in (5) partition by list (a); +create table parted_conflict_test_4_1 partition of parted_conflict_test_4 for values in (5); +truncate parted_conflict_test; +insert into parted_conflict_test (a, b) values (5, 'a') on conflict (a) do update set b = excluded.b; +insert into parted_conflict_test (a, b) values (5, 'b') on conflict (a) do update set b = excluded.b where parted_conflict_test.b = 'a'; +-- should see (5, 'b') +select * from parted_conflict_test order by a; + a | b +---+--- + 5 | b +(1 row) + +-- test with multiple rows +truncate parted_conflict_test; +insert into parted_conflict_test (a, b) values (1, 'a'), (2, 'a'), (4, 'a') on conflict (a) do update set b = excluded.b where excluded.b = 'b'; +insert into parted_conflict_test (a, b) values (1, 'b'), (2, 'c'), (4, 'b') on conflict (a) do update set b = excluded.b where excluded.b = 'b'; +-- should see (1, 'b'), (2, 'a'), (4, 'b') +select * from parted_conflict_test order by a; + a | b +---+--- + 1 | b + 2 | a + 4 | b +(3 rows) + +drop table parted_conflict_test; +-- test behavior of inserting a conflicting tuple into an intermediate +-- partitioning level +create table parted_conflict (a int primary key, b text) partition by range (a); +create table parted_conflict_1 partition of parted_conflict for values from (0) to (1000) partition by range (a); +create table parted_conflict_1_1 partition of parted_conflict_1 for values from (0) to (500); +insert into parted_conflict values (40, 'forty'); +insert into parted_conflict_1 values (40, 'cuarenta') + on conflict (a) do update set b = excluded.b; +drop table parted_conflict; +-- same thing, but this time try to use an index that's created not in the +-- partition +create table parted_conflict (a int, b text) partition by range (a); +create table parted_conflict_1 partition of parted_conflict for values from (0) to (1000) partition by range (a); +create table parted_conflict_1_1 partition of parted_conflict_1 for values from (0) to (500); +create unique index on only parted_conflict_1 (a); +create unique index on only parted_conflict (a); +alter index parted_conflict_a_idx attach partition parted_conflict_1_a_idx; +insert into parted_conflict values (40, 'forty'); +insert into parted_conflict_1 values (40, 'cuarenta') + on conflict (a) do update set b = excluded.b; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop table parted_conflict; +-- test whole-row Vars in ON CONFLICT expressions +create table parted_conflict (a int, b text, c int) partition by range (a); +create table parted_conflict_1 (drp text, c int, a int, b text); +alter table parted_conflict_1 drop column drp; +create unique index on parted_conflict (a, b); +alter table parted_conflict attach partition parted_conflict_1 for values from (0) to (1000); +truncate parted_conflict; +insert into parted_conflict values (50, 'cincuenta', 1); +insert into parted_conflict values (50, 'cincuenta', 2) + on conflict (a, b) do update set (a, b, c) = row(excluded.*) + where parted_conflict = (50, text 'cincuenta', 1) and + excluded = (50, text 'cincuenta', 2); +-- should see (50, 'cincuenta', 2) +select * from parted_conflict order by a; + a | b | c +----+-----------+--- + 50 | cincuenta | 2 +(1 row) + +-- test with statement level triggers +create or replace function parted_conflict_update_func() returns trigger as $$ +declare + r record; +begin + for r in select * from inserted loop + raise notice 'a = %, b = %, c = %', r.a, r.b, r.c; + end loop; + return new; +end; +$$ language plpgsql; +create trigger parted_conflict_update + after update on parted_conflict + referencing new table as inserted + for each statement + execute procedure parted_conflict_update_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: referencing new table as inserted + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +/* YB: Disabled as create trigger above fails +truncate parted_conflict; + +insert into parted_conflict values (0, 'cero', 1); + +insert into parted_conflict values(0, 'cero', 1) + on conflict (a,b) do update set c = parted_conflict.c + 1; +*/ -- YB +drop table parted_conflict; +drop function parted_conflict_update_func(); diff --git a/src/test/regress/expected/yb.port.insert_conflict_1.out b/src/test/regress/expected/yb.port.insert_conflict_1.out new file mode 100644 index 0000000000000..3f4909a8f0cc1 --- /dev/null +++ b/src/test/regress/expected/yb.port.insert_conflict_1.out @@ -0,0 +1,846 @@ +-- +-- insert...on conflict do unique index inference +-- +create table insertconflicttest(key int4, fruit text); +-- +-- Test unique index inference with operator class specifications and +-- named collations +-- +create unique index op_index_key on insertconflicttest(key, fruit text_pattern_ops); +create unique index collation_index_key on insertconflicttest(key, fruit collate "C"); +create unique index both_index_key on insertconflicttest(key, fruit collate "C" text_pattern_ops); +create unique index both_index_expr_key on insertconflicttest(key, lower(fruit) collate "C" text_pattern_ops); +-- fails +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key) do nothing; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit) do nothing; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +-- succeeds +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit) do nothing; + QUERY PLAN +------------------------------------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: op_index_key, collation_index_key, both_index_key + -> Result *RESULT* +(4 rows) + +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit, key, fruit, key) do nothing; + QUERY PLAN +------------------------------------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: op_index_key, collation_index_key, both_index_key + -> Result *RESULT* +(4 rows) + +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit), key, lower(fruit), key) do nothing; + QUERY PLAN +------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: both_index_expr_key + -> Result *RESULT* +(4 rows) + +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit) do update set fruit = excluded.fruit + where exists (select 1 from insertconflicttest ii where ii.key = excluded.key); + QUERY PLAN +------------------------------------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: op_index_key, collation_index_key, both_index_key + Conflict Filter: (SubPlan 1) + -> Result *RESULT* + SubPlan 1 + -> Index Only Scan using both_index_expr_key on insertconflicttest ii + Index Cond: (key = excluded.key) +(8 rows) + +-- Neither collation nor operator class specifications are required -- +-- supplying them merely *limits* matches to indexes with matching opclasses +-- used for relevant indexes +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit text_pattern_ops) do nothing; + QUERY PLAN +---------------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: op_index_key, both_index_key + -> Result *RESULT* +(4 rows) + +-- Okay, arbitrates using both index where text_pattern_ops opclass does and +-- does not appear. +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit collate "C") do nothing; + QUERY PLAN +----------------------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: collation_index_key, both_index_key + -> Result *RESULT* +(4 rows) + +-- Okay, but only accepts the single index where both opclass and collation are +-- specified +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit collate "C" text_pattern_ops, key) do nothing; + QUERY PLAN +-------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: both_index_key + -> Result *RESULT* +(4 rows) + +-- Okay, but only accepts the single index where both opclass and collation are +-- specified (plus expression variant) +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) collate "C", key, key) do nothing; + QUERY PLAN +------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: both_index_expr_key + -> Result *RESULT* +(4 rows) + +-- Attribute appears twice, while not all attributes/expressions on attributes +-- appearing within index definition match in terms of both opclass and +-- collation. +-- +-- Works because every attribute in inference specification needs to be +-- satisfied once or more by cataloged index attribute, and as always when an +-- attribute in the cataloged definition has a non-default opclass/collation, +-- it still satisfied some inference attribute lacking any particular +-- opclass/collation specification. +-- +-- The implementation is liberal in accepting inference specifications on the +-- assumption that multiple inferred unique indexes will prevent problematic +-- cases. It rolls with unique indexes where attributes redundantly appear +-- multiple times, too (which is not tested here). +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit, key, fruit text_pattern_ops, key) do nothing; + QUERY PLAN +---------------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: op_index_key, both_index_key + -> Result *RESULT* +(4 rows) + +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) collate "C" text_pattern_ops, key, key) do nothing; + QUERY PLAN +------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: both_index_expr_key + -> Result *RESULT* +(4 rows) + +drop index op_index_key; +drop index collation_index_key; +drop index both_index_key; +drop index both_index_expr_key; +-- +-- Make sure that cross matching of attribute opclass/collation does not occur +-- +create unique index cross_match on insertconflicttest(lower(fruit) collate "C", upper(fruit) text_pattern_ops); +-- fails: +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) text_pattern_ops, upper(fruit) collate "C") do nothing; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +-- works: +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) collate "C", upper(fruit) text_pattern_ops) do nothing; + QUERY PLAN +----------------------------------------- + Insert on insertconflicttest + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: cross_match + -> Result *RESULT* +(4 rows) + +drop index cross_match; +-- +-- Single key tests +-- +create unique index key_index on insertconflicttest(key); +-- +-- Explain tests +-- +explain (costs off) insert into insertconflicttest values (0, 'Bilberry') on conflict (key) do update set fruit = excluded.fruit; + QUERY PLAN +--------------------------------------- + Insert on insertconflicttest + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: key_index + -> Result *RESULT* +(4 rows) + +-- Should display qual actually attributable to internal sequential scan: +explain (costs off) insert into insertconflicttest values (0, 'Bilberry') on conflict (key) do update set fruit = excluded.fruit where insertconflicttest.fruit != 'Cawesh'; + QUERY PLAN +----------------------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: key_index + Conflict Filter: (insertconflicttest.fruit <> 'Cawesh'::text) + -> Result *RESULT* +(5 rows) + +-- With EXCLUDED.* expression in scan node: +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key) do update set fruit = excluded.fruit where excluded.fruit != 'Elderberry'; + QUERY PLAN +----------------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: key_index + Conflict Filter: (excluded.fruit <> 'Elderberry'::text) + -> Result *RESULT* +(5 rows) + +-- Does the same, but JSON format shows "Conflict Arbiter Index" as JSON array: +explain (costs off, format json) insert into insertconflicttest values (0, 'Bilberry') on conflict (key) do update set fruit = excluded.fruit where insertconflicttest.fruit != 'Lime' returning *; + QUERY PLAN +------------------------------------------------------------------------ + [ + + { + + "Plan": { + + "Node Type": "ModifyTable", + + "Operation": "Insert", + + "Parallel Aware": false, + + "Async Capable": false, + + "Relation Name": "insertconflicttest", + + "Alias": "insertconflicttest", + + "Conflict Resolution": "UPDATE", + + "Conflict Arbiter Indexes": ["key_index"], + + "Conflict Filter": "(insertconflicttest.fruit <> 'Lime'::text)",+ + "Plans": [ + + { + + "Node Type": "Result *RESULT*", + + "Parent Relationship": "Outer", + + "Parallel Aware": false, + + "Async Capable": false + + } + + ] + + } + + } + + ] +(1 row) + +-- Fails (no unique index inference specification, required for do update variant): +insert into insertconflicttest values (1, 'Apple') on conflict do update set fruit = excluded.fruit; +ERROR: ON CONFLICT DO UPDATE requires inference specification or constraint name +LINE 1: ...nsert into insertconflicttest values (1, 'Apple') on conflic... + ^ +HINT: For example, ON CONFLICT (column_name). +-- inference succeeds: +insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit; +insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key) do update set fruit = excluded.fruit; +-- Succeed, since multi-assignment does not involve subquery: +insert into insertconflicttest +values (1, 'Apple'), (2, 'Orange') +on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key); +-- Give good diagnostic message when EXCLUDED.* spuriously referenced from +-- RETURNING: +insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit; +ERROR: invalid reference to FROM-clause entry for table "excluded" +LINE 1: ...y) do update set fruit = excluded.fruit RETURNING excluded.f... + ^ +HINT: There is an entry for table "excluded", but it cannot be referenced from this part of the query. +-- Only suggest
.* column when inference element misspelled: +insert into insertconflicttest values (1, 'Apple') on conflict (keyy) do update set fruit = excluded.fruit; +ERROR: column "keyy" does not exist +LINE 1: ...nsertconflicttest values (1, 'Apple') on conflict (keyy) do ... + ^ +HINT: Perhaps you meant to reference the column "insertconflicttest.key" or the column "excluded.key". +-- Have useful HINT for EXCLUDED.* RTE within UPDATE: +insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruitt; +ERROR: column excluded.fruitt does not exist +LINE 1: ... 'Apple') on conflict (key) do update set fruit = excluded.f... + ^ +HINT: Perhaps you meant to reference the column "excluded.fruit". +-- inference fails: +insert into insertconflicttest values (3, 'Kiwi') on conflict (key, fruit) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (4, 'Mango') on conflict (fruit, key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (5, 'Lemon') on conflict (fruit) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (6, 'Passionfruit') on conflict (lower(fruit)) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +-- Check the target relation can be aliased +insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (key) do update set fruit = excluded.fruit; -- ok, no reference to target table +insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (key) do update set fruit = ict.fruit; -- ok, alias +insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (key) do update set fruit = insertconflicttest.fruit; -- error, references aliased away name +ERROR: invalid reference to FROM-clause entry for table "insertconflicttest" +LINE 1: ...onfruit') on conflict (key) do update set fruit = insertconf... + ^ +HINT: Perhaps you meant to reference the table alias "ict". +drop index key_index; +-- +-- Composite key tests +-- +create unique index comp_key_index on insertconflicttest(key, fruit); +-- inference succeeds: +insert into insertconflicttest values (7, 'Raspberry') on conflict (key, fruit) do update set fruit = excluded.fruit; +insert into insertconflicttest values (8, 'Lime') on conflict (fruit, key) do update set fruit = excluded.fruit; +-- inference fails: +insert into insertconflicttest values (9, 'Banana') on conflict (key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (10, 'Blueberry') on conflict (key, key, key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (11, 'Cherry') on conflict (key, lower(fruit)) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (12, 'Date') on conflict (lower(fruit), key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop index comp_key_index; +-- +-- Partial index tests, no inference predicate specified +-- +create unique index part_comp_key_index on insertconflicttest(key, fruit) where key < 5; +create unique index expr_part_comp_key_index on insertconflicttest(key, lower(fruit)) where key < 5; +-- inference fails: +insert into insertconflicttest values (13, 'Grape') on conflict (key, fruit) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (14, 'Raisin') on conflict (fruit, key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (15, 'Cranberry') on conflict (key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (16, 'Melon') on conflict (key, key, key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (17, 'Mulberry') on conflict (key, lower(fruit)) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (18, 'Pineapple') on conflict (lower(fruit), key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop index part_comp_key_index; +drop index expr_part_comp_key_index; +-- +-- Expression index tests +-- +create unique index expr_key_index on insertconflicttest(lower(fruit)); +-- inference succeeds: +insert into insertconflicttest values (20, 'Quince') on conflict (lower(fruit)) do update set fruit = excluded.fruit; +insert into insertconflicttest values (21, 'Pomegranate') on conflict (lower(fruit), lower(fruit)) do update set fruit = excluded.fruit; +-- inference fails: +insert into insertconflicttest values (22, 'Apricot') on conflict (upper(fruit)) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (23, 'Blackberry') on conflict (fruit) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop index expr_key_index; +-- +-- Expression index tests (with regular column) +-- +create unique index expr_comp_key_index on insertconflicttest(key, lower(fruit)); +create unique index tricky_expr_comp_key_index on insertconflicttest(key, lower(fruit), upper(fruit)); +-- inference succeeds: +insert into insertconflicttest values (24, 'Plum') on conflict (key, lower(fruit)) do update set fruit = excluded.fruit; +insert into insertconflicttest values (25, 'Peach') on conflict (lower(fruit), key) do update set fruit = excluded.fruit; +-- Should not infer "tricky_expr_comp_key_index" index: +explain (costs off) insert into insertconflicttest values (26, 'Fig') on conflict (lower(fruit), key, lower(fruit), key) do update set fruit = excluded.fruit; + QUERY PLAN +------------------------------------------------- + Insert on insertconflicttest + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: expr_comp_key_index + -> Result *RESULT* +(4 rows) + +-- inference fails: +insert into insertconflicttest values (27, 'Prune') on conflict (key, upper(fruit)) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (28, 'Redcurrant') on conflict (fruit, key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (29, 'Nectarine') on conflict (key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop index expr_comp_key_index; +drop index tricky_expr_comp_key_index; +-- +-- Non-spurious duplicate violation tests +-- +create unique index key_index on insertconflicttest(key); +create unique index fruit_index on insertconflicttest(fruit); +-- succeeds, since UPDATE happens to update "fruit" to existing value: +insert into insertconflicttest values (26, 'Fig') on conflict (key) do update set fruit = excluded.fruit; +-- fails, since UPDATE is to row with key value 26, and we're updating "fruit" +-- to a value that happens to exist in another row ('peach'): +insert into insertconflicttest values (26, 'Peach') on conflict (key) do update set fruit = excluded.fruit; -- YB: output is missing detail because YB handles duplicate detection differently +ERROR: duplicate key value violates unique constraint "fruit_index" +-- succeeds, since "key" isn't repeated/referenced in UPDATE, and "fruit" +-- arbitrates that statement updates existing "Fig" row: +insert into insertconflicttest values (25, 'Fig') on conflict (fruit) do update set fruit = excluded.fruit; +drop index key_index; +drop index fruit_index; +-- +-- Test partial unique index inference +-- +create unique index partial_key_index on insertconflicttest(key) where fruit like '%berry'; +-- Succeeds +insert into insertconflicttest values (23, 'Blackberry') on conflict (key) where fruit like '%berry' do update set fruit = excluded.fruit; +insert into insertconflicttest as t values (23, 'Blackberry') on conflict (key) where fruit like '%berry' and t.fruit = 'inconsequential' do nothing; +-- fails +insert into insertconflicttest values (23, 'Blackberry') on conflict (key) do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (23, 'Blackberry') on conflict (key) where fruit like '%berry' or fruit = 'consequential' do nothing; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into insertconflicttest values (23, 'Blackberry') on conflict (fruit) where fruit like '%berry' do update set fruit = excluded.fruit; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop index partial_key_index; +-- +-- Test that wholerow references to ON CONFLICT's EXCLUDED work +-- +create unique index plain on insertconflicttest(key); +-- Succeeds, updates existing row: +insert into insertconflicttest as i values (23, 'Jackfruit') on conflict (key) do update set fruit = excluded.fruit + where i.* != excluded.* returning *; + key | fruit +-----+----------- + 23 | Jackfruit +(1 row) + +-- No update this time, though: +insert into insertconflicttest as i values (23, 'Jackfruit') on conflict (key) do update set fruit = excluded.fruit + where i.* != excluded.* returning *; + key | fruit +-----+------- +(0 rows) + +-- Predicate changed to require match rather than non-match, so updates once more: +insert into insertconflicttest as i values (23, 'Jackfruit') on conflict (key) do update set fruit = excluded.fruit + where i.* = excluded.* returning *; + key | fruit +-----+----------- + 23 | Jackfruit +(1 row) + +-- Assign: +insert into insertconflicttest as i values (23, 'Avocado') on conflict (key) do update set fruit = excluded.*::text + returning *; + key | fruit +-----+-------------- + 23 | (23,Avocado) +(1 row) + +-- deparse whole row var in WHERE and SET clauses: +explain (costs off) insert into insertconflicttest as i values (23, 'Avocado') on conflict (key) do update set fruit = excluded.fruit where excluded.* is null; + QUERY PLAN +----------------------------------------- + Insert on insertconflicttest i + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: plain + Conflict Filter: (excluded.* IS NULL) + -> Result *RESULT* +(5 rows) + +explain (costs off) insert into insertconflicttest as i values (23, 'Avocado') on conflict (key) do update set fruit = excluded.*::text; + QUERY PLAN +----------------------------------- + Insert on insertconflicttest i + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: plain + -> Result *RESULT* +(4 rows) + +drop index plain; +-- Cleanup +drop table insertconflicttest; +-- +-- Verify that EXCLUDED does not allow system column references. These +-- do not make sense because EXCLUDED isn't an already stored tuple +-- (and thus doesn't have a ctid etc). +-- +create table syscolconflicttest(key int4, data text); +insert into syscolconflicttest values (1); +insert into syscolconflicttest values (1) on conflict (key) do update set data = excluded.ctid::text; -- YB: output shows YB error message hit first before PG one +ERROR: system column "ctid" is not supported yet +drop table syscolconflicttest; +-- +-- Previous tests all managed to not test any expressions requiring +-- planner preprocessing ... +-- +create table insertconflict (a bigint, b bigint); +create unique index insertconflicti1 on insertconflict(coalesce(a, 0)); +create unique index insertconflicti2 on insertconflict(b) + where coalesce(a, 1) > 0; +insert into insertconflict values (1, 2) +on conflict (coalesce(a, 0)) do nothing; +insert into insertconflict values (1, 2) +on conflict (b) where coalesce(a, 1) > 0 do nothing; +insert into insertconflict values (1, 2) +on conflict (b) where coalesce(a, 1) > 1 do nothing; +drop table insertconflict; +-- +-- test insertion through view +-- +create table insertconflict (f1 int primary key, f2 text); +create view insertconflictv as + select * from insertconflict with cascaded check option; +ERROR: VIEW WITH CASCADED CHECK OPTION not supported yet +LINE 2: select * from insertconflict with cascaded check option; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +create view insertconflictv as select * from insertconflict; -- YB: workaround view to replace above +insert into insertconflictv values (1,'foo') + on conflict (f1) do update set f2 = excluded.f2; +select * from insertconflict; + f1 | f2 +----+----- + 1 | foo +(1 row) + +insert into insertconflictv values (1,'bar') + on conflict (f1) do update set f2 = excluded.f2; +select * from insertconflict; + f1 | f2 +----+----- + 1 | bar +(1 row) + +drop view insertconflictv; +drop table insertconflict; +-- ****************************************************************** +-- * * +-- * Test inheritance (example taken from tutorial) * +-- * * +-- ****************************************************************** +create table cities ( + name text, + population float8, + altitude int -- (in ft) +); +create table capitals ( + state char(2) +) inherits (cities); +-- Create unique indexes. Due to a general limitation of inheritance, +-- uniqueness is only enforced per-relation. Unique index inference +-- specification will do the right thing, though. +create unique index cities_names_unique on cities (name); +-- create unique index capitals_names_unique on capitals (name); +-- prepopulate the tables. +insert into cities values ('San Francisco', 7.24E+5, 63); +insert into cities values ('Las Vegas', 2.583E+5, 2174); +insert into cities values ('Mariposa', 1200, 1953); +-- insert into capitals values ('Sacramento', 3.694E+5, 30, 'CA'); +-- insert into capitals values ('Madison', 1.913E+5, 845, 'WI'); +-- Tests proper for inheritance: +-- select * from capitals; +-- Succeeds: +insert into cities values ('Las Vegas', 2.583E+5, 2174) on conflict do nothing; +-- insert into capitals values ('Sacramento', 4664.E+5, 30, 'CA') on conflict (name) do update set population = excluded.population; +-- Wrong "Sacramento", so do nothing: +-- insert into capitals values ('Sacramento', 50, 2267, 'NE') on conflict (name) do nothing; +-- select * from capitals; +with ybtmp as (delete from cities where name = 'Las Vegas' returning *) + insert into cities select * from ybtmp; -- YB: imitate PG ctid reallocation for the following update +insert into cities values ('Las Vegas', 5.83E+5, 2001) on conflict (name) do update set population = excluded.population, altitude = excluded.altitude; +select tableoid::regclass, * from cities; + tableoid | name | population | altitude +----------+---------------+------------+---------- + cities | San Francisco | 724000 | 63 + cities | Mariposa | 1200 | 1953 + cities | Las Vegas | 583000 | 2001 +(3 rows) + +-- insert into capitals values ('Las Vegas', 5.83E+5, 2222, 'NV') on conflict (name) do update set population = excluded.population; +-- Capitals will contain new capital, Las Vegas: +-- select * from capitals; +-- Cities contains two instances of "Las Vegas", since unique constraints don't +-- work across inheritance: +select tableoid::regclass, * from cities; + tableoid | name | population | altitude +----------+---------------+------------+---------- + cities | San Francisco | 724000 | 63 + cities | Mariposa | 1200 | 1953 + cities | Las Vegas | 583000 | 2001 +(3 rows) + +-- This only affects "cities" version of "Las Vegas": +insert into cities values ('Las Vegas', 5.86E+5, 2223) on conflict (name) do update set population = excluded.population, altitude = excluded.altitude; +select tableoid::regclass, * from cities; + tableoid | name | population | altitude +----------+---------------+------------+---------- + cities | San Francisco | 724000 | 63 + cities | Mariposa | 1200 | 1953 + cities | Las Vegas | 586000 | 2223 +(3 rows) + +-- clean up +drop table capitals; +ERROR: table "capitals" does not exist +drop table cities; +-- Make sure a table named excluded is handled properly +create table excluded(key int primary key, data text); +insert into excluded values(1, '1'); +-- error, ambiguous +insert into excluded values(1, '2') on conflict (key) do update set data = excluded.data RETURNING *; +ERROR: table reference "excluded" is ambiguous +LINE 1: ...es(1, '2') on conflict (key) do update set data = excluded.d... + ^ +-- ok, aliased +insert into excluded AS target values(1, '2') on conflict (key) do update set data = excluded.data RETURNING *; + key | data +-----+------ + 1 | 2 +(1 row) + +-- ok, aliased +insert into excluded AS target values(1, '2') on conflict (key) do update set data = target.data RETURNING *; + key | data +-----+------ + 1 | 2 +(1 row) + +-- make sure excluded isn't a problem in returning clause +insert into excluded values(1, '2') on conflict (key) do update set data = 3 RETURNING excluded.*; + key | data +-----+------ + 1 | 3 +(1 row) + +-- clean up +drop table excluded; +-- check that references to columns after dropped columns are handled correctly +create table dropcol(key int primary key, drop1 int, keep1 text, drop2 numeric, keep2 float); +insert into dropcol(key, drop1, keep1, drop2, keep2) values(1, 1, '1', '1', 1); +-- set using excluded +insert into dropcol(key, drop1, keep1, drop2, keep2) values(1, 2, '2', '2', 2) on conflict(key) + do update set drop1 = excluded.drop1, keep1 = excluded.keep1, drop2 = excluded.drop2, keep2 = excluded.keep2 + where excluded.drop1 is not null and excluded.keep1 is not null and excluded.drop2 is not null and excluded.keep2 is not null + and dropcol.drop1 is not null and dropcol.keep1 is not null and dropcol.drop2 is not null and dropcol.keep2 is not null + returning *; + key | drop1 | keep1 | drop2 | keep2 +-----+-------+-------+-------+------- + 1 | 2 | 2 | 2 | 2 +(1 row) + +; +-- set using existing table +insert into dropcol(key, drop1, keep1, drop2, keep2) values(1, 3, '3', '3', 3) on conflict(key) + do update set drop1 = dropcol.drop1, keep1 = dropcol.keep1, drop2 = dropcol.drop2, keep2 = dropcol.keep2 + returning *; + key | drop1 | keep1 | drop2 | keep2 +-----+-------+-------+-------+------- + 1 | 2 | 2 | 2 | 2 +(1 row) + +; +alter table dropcol drop column drop1, drop column drop2; +-- set using excluded +insert into dropcol(key, keep1, keep2) values(1, '4', 4) on conflict(key) + do update set keep1 = excluded.keep1, keep2 = excluded.keep2 + where excluded.keep1 is not null and excluded.keep2 is not null + and dropcol.keep1 is not null and dropcol.keep2 is not null + returning *; + key | keep1 | keep2 +-----+-------+------- + 1 | 4 | 4 +(1 row) + +; +-- set using existing table +insert into dropcol(key, keep1, keep2) values(1, '5', 5) on conflict(key) + do update set keep1 = dropcol.keep1, keep2 = dropcol.keep2 + returning *; + key | keep1 | keep2 +-----+-------+------- + 1 | 4 | 4 +(1 row) + +; +DROP TABLE dropcol; +/* YB: EXCLUDE constraint not supported yet - see #997 +-- check handling of regular btree constraint along with gist constraint + +create table twoconstraints (f1 int unique, f2 box, + exclude using gist(f2 with &&)); +insert into twoconstraints values(1, '((0,0),(1,1))'); +insert into twoconstraints values(1, '((2,2),(3,3))'); -- fail on f1 +insert into twoconstraints values(2, '((0,0),(1,2))'); -- fail on f2 +insert into twoconstraints values(2, '((0,0),(1,2))') + on conflict on constraint twoconstraints_f1_key do nothing; -- fail on f2 +insert into twoconstraints values(2, '((0,0),(1,2))') + on conflict on constraint twoconstraints_f2_excl do nothing; -- do nothing +select * from twoconstraints; +drop table twoconstraints; +*/ -- YB +-- check handling of self-conflicts at various isolation levels +create table selfconflict (f1 int primary key, f2 int); +begin transaction isolation level read committed; +insert into selfconflict values (1,1), (1,2) on conflict do nothing; +commit; +begin transaction isolation level repeatable read; +insert into selfconflict values (2,1), (2,2) on conflict do nothing; +commit; +begin transaction isolation level serializable; +insert into selfconflict values (3,1), (3,2) on conflict do nothing; +commit; +begin transaction isolation level read committed; +insert into selfconflict values (4,1), (4,2) on conflict(f1) do update set f2 = 0; +ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time +HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. +commit; +begin transaction isolation level repeatable read; +insert into selfconflict values (5,1), (5,2) on conflict(f1) do update set f2 = 0; +ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time +HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. +commit; +begin transaction isolation level serializable; +insert into selfconflict values (6,1), (6,2) on conflict(f1) do update set f2 = 0; +ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time +HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. +commit; +select * from selfconflict; + f1 | f2 +----+---- + 1 | 1 + 2 | 1 + 3 | 1 +(3 rows) + +drop table selfconflict; +-- check ON CONFLICT handling with partitioned tables +create table parted_conflict_test (a int unique, b char) partition by list (a); +create table parted_conflict_test_1 partition of parted_conflict_test (b unique) for values in (1, 2); +-- no indexes required here +insert into parted_conflict_test values (1, 'a') on conflict do nothing; +-- index on a required, which does exist in parent +insert into parted_conflict_test values (1, 'a') on conflict (a) do nothing; +insert into parted_conflict_test values (1, 'a') on conflict (a) do update set b = excluded.b; +-- targeting partition directly will work +insert into parted_conflict_test_1 values (1, 'a') on conflict (a) do nothing; +insert into parted_conflict_test_1 values (1, 'b') on conflict (a) do update set b = excluded.b; +-- index on b required, which doesn't exist in parent +insert into parted_conflict_test values (2, 'b') on conflict (b) do update set a = excluded.a; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +-- targeting partition directly will work +insert into parted_conflict_test_1 values (2, 'b') on conflict (b) do update set a = excluded.a; +-- should see (2, 'b') +select * from parted_conflict_test order by a; + a | b +---+--- + 2 | b +(1 row) + +-- now check that DO UPDATE works correctly for target partition with +-- different attribute numbers +create table parted_conflict_test_2 (b char, a int unique); +alter table parted_conflict_test attach partition parted_conflict_test_2 for values in (3); +truncate parted_conflict_test; +insert into parted_conflict_test values (3, 'a') on conflict (a) do update set b = excluded.b; +insert into parted_conflict_test values (3, 'b') on conflict (a) do update set b = excluded.b; +-- should see (3, 'b') +select * from parted_conflict_test order by a; + a | b +---+--- + 3 | b +(1 row) + +-- case where parent will have a dropped column, but the partition won't +-- YB Note: Dropping and adding a column with the same name is not supported in YB. +-- Split the operation into two steps. +alter table parted_conflict_test drop b; +alter table parted_conflict_test add b char; +create table parted_conflict_test_3 partition of parted_conflict_test for values in (4); +truncate parted_conflict_test; +insert into parted_conflict_test (a, b) values (4, 'a') on conflict (a) do update set b = excluded.b; +insert into parted_conflict_test (a, b) values (4, 'b') on conflict (a) do update set b = excluded.b where parted_conflict_test.b = 'a'; +-- should see (4, 'b') +select * from parted_conflict_test order by a; + a | b +---+--- + 4 | b +(1 row) + +-- case with multi-level partitioning +create table parted_conflict_test_4 partition of parted_conflict_test for values in (5) partition by list (a); +create table parted_conflict_test_4_1 partition of parted_conflict_test_4 for values in (5); +truncate parted_conflict_test; +insert into parted_conflict_test (a, b) values (5, 'a') on conflict (a) do update set b = excluded.b; +insert into parted_conflict_test (a, b) values (5, 'b') on conflict (a) do update set b = excluded.b where parted_conflict_test.b = 'a'; +-- should see (5, 'b') +select * from parted_conflict_test order by a; + a | b +---+--- + 5 | b +(1 row) + +-- test with multiple rows +truncate parted_conflict_test; +insert into parted_conflict_test (a, b) values (1, 'a'), (2, 'a'), (4, 'a') on conflict (a) do update set b = excluded.b where excluded.b = 'b'; +insert into parted_conflict_test (a, b) values (1, 'b'), (2, 'c'), (4, 'b') on conflict (a) do update set b = excluded.b where excluded.b = 'b'; +-- should see (1, 'b'), (2, 'a'), (4, 'b') +select * from parted_conflict_test order by a; + a | b +---+--- + 1 | b + 2 | a + 4 | b +(3 rows) + +drop table parted_conflict_test; +-- test behavior of inserting a conflicting tuple into an intermediate +-- partitioning level +create table parted_conflict (a int primary key, b text) partition by range (a); +create table parted_conflict_1 partition of parted_conflict for values from (0) to (1000) partition by range (a); +create table parted_conflict_1_1 partition of parted_conflict_1 for values from (0) to (500); +insert into parted_conflict values (40, 'forty'); +insert into parted_conflict_1 values (40, 'cuarenta') + on conflict (a) do update set b = excluded.b; +drop table parted_conflict; +-- same thing, but this time try to use an index that's created not in the +-- partition +create table parted_conflict (a int, b text) partition by range (a); +create table parted_conflict_1 partition of parted_conflict for values from (0) to (1000) partition by range (a); +create table parted_conflict_1_1 partition of parted_conflict_1 for values from (0) to (500); +create unique index on only parted_conflict_1 (a); +create unique index on only parted_conflict (a); +alter index parted_conflict_a_idx attach partition parted_conflict_1_a_idx; +insert into parted_conflict values (40, 'forty'); +insert into parted_conflict_1 values (40, 'cuarenta') + on conflict (a) do update set b = excluded.b; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop table parted_conflict; +-- test whole-row Vars in ON CONFLICT expressions +create table parted_conflict (a int, b text, c int) partition by range (a); +create table parted_conflict_1 (drp text, c int, a int, b text); +alter table parted_conflict_1 drop column drp; +create unique index on parted_conflict (a, b); +alter table parted_conflict attach partition parted_conflict_1 for values from (0) to (1000); +truncate parted_conflict; +insert into parted_conflict values (50, 'cincuenta', 1); +insert into parted_conflict values (50, 'cincuenta', 2) + on conflict (a, b) do update set (a, b, c) = row(excluded.*) + where parted_conflict = (50, text 'cincuenta', 1) and + excluded = (50, text 'cincuenta', 2); +-- should see (50, 'cincuenta', 2) +select * from parted_conflict order by a; + a | b | c +----+-----------+--- + 50 | cincuenta | 2 +(1 row) + +-- test with statement level triggers +create or replace function parted_conflict_update_func() returns trigger as $$ +declare + r record; +begin + for r in select * from inserted loop + raise notice 'a = %, b = %, c = %', r.a, r.b, r.c; + end loop; + return new; +end; +$$ language plpgsql; +create trigger parted_conflict_update + after update on parted_conflict + referencing new table as inserted + for each statement + execute procedure parted_conflict_update_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: referencing new table as inserted + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +/* YB: Disabled as create trigger above fails +truncate parted_conflict; + +insert into parted_conflict values (0, 'cero', 1); + +insert into parted_conflict values(0, 'cero', 1) + on conflict (a,b) do update set c = parted_conflict.c + 1; +*/ -- YB +drop table parted_conflict; +drop function parted_conflict_update_func(); diff --git a/src/test/regress/expected/yb.port.int8.out b/src/test/regress/expected/yb.port.int8.out new file mode 100644 index 0000000000000..b48bc77a29de0 --- /dev/null +++ b/src/test/regress/expected/yb.port.int8.out @@ -0,0 +1,922 @@ +-- +-- INT8 +-- Test int8 64-bit integers. +-- +-- int8_tbl was already created and filled in test_setup.sql. +-- Here we just try to insert bad values. +INSERT INTO INT8_TBL(q1) VALUES (' '); +ERROR: invalid input syntax for type bigint: " " +LINE 1: INSERT INTO INT8_TBL(q1) VALUES (' '); + ^ +INSERT INTO INT8_TBL(q1) VALUES ('xxx'); +ERROR: invalid input syntax for type bigint: "xxx" +LINE 1: INSERT INTO INT8_TBL(q1) VALUES ('xxx'); + ^ +INSERT INTO INT8_TBL(q1) VALUES ('3908203590239580293850293850329485'); +ERROR: value "3908203590239580293850293850329485" is out of range for type bigint +LINE 1: INSERT INTO INT8_TBL(q1) VALUES ('39082035902395802938502938... + ^ +INSERT INTO INT8_TBL(q1) VALUES ('-1204982019841029840928340329840934'); +ERROR: value "-1204982019841029840928340329840934" is out of range for type bigint +LINE 1: INSERT INTO INT8_TBL(q1) VALUES ('-1204982019841029840928340... + ^ +INSERT INTO INT8_TBL(q1) VALUES ('- 123'); +ERROR: invalid input syntax for type bigint: "- 123" +LINE 1: INSERT INTO INT8_TBL(q1) VALUES ('- 123'); + ^ +INSERT INTO INT8_TBL(q1) VALUES (' 345 5'); +ERROR: invalid input syntax for type bigint: " 345 5" +LINE 1: INSERT INTO INT8_TBL(q1) VALUES (' 345 5'); + ^ +INSERT INTO INT8_TBL(q1) VALUES (''); +ERROR: invalid input syntax for type bigint: "" +LINE 1: INSERT INTO INT8_TBL(q1) VALUES (''); + ^ +SELECT * FROM INT8_TBL; + q1 | q2 +------------------+------------------- + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(5 rows) + +-- int8/int8 cmp +SELECT * FROM INT8_TBL WHERE q2 = 4567890123456789; + q1 | q2 +------------------+------------------ + 123 | 4567890123456789 + 4567890123456789 | 4567890123456789 +(2 rows) + +SELECT * FROM INT8_TBL WHERE q2 <> 4567890123456789; + q1 | q2 +------------------+------------------- + 123 | 456 + 4567890123456789 | 123 + 4567890123456789 | -4567890123456789 +(3 rows) + +SELECT * FROM INT8_TBL WHERE q2 < 4567890123456789; + q1 | q2 +------------------+------------------- + 123 | 456 + 4567890123456789 | 123 + 4567890123456789 | -4567890123456789 +(3 rows) + +SELECT * FROM INT8_TBL WHERE q2 > 4567890123456789; + q1 | q2 +----+---- +(0 rows) + +SELECT * FROM INT8_TBL WHERE q2 <= 4567890123456789; + q1 | q2 +------------------+------------------- + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(5 rows) + +SELECT * FROM INT8_TBL WHERE q2 >= 4567890123456789; + q1 | q2 +------------------+------------------ + 123 | 4567890123456789 + 4567890123456789 | 4567890123456789 +(2 rows) + +-- int8/int4 cmp +SELECT * FROM INT8_TBL WHERE q2 = 456; + q1 | q2 +-----+----- + 123 | 456 +(1 row) + +SELECT * FROM INT8_TBL WHERE q2 <> 456; + q1 | q2 +------------------+------------------- + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(4 rows) + +SELECT * FROM INT8_TBL WHERE q2 < 456; + q1 | q2 +------------------+------------------- + 4567890123456789 | 123 + 4567890123456789 | -4567890123456789 +(2 rows) + +SELECT * FROM INT8_TBL WHERE q2 > 456; + q1 | q2 +------------------+------------------ + 123 | 4567890123456789 + 4567890123456789 | 4567890123456789 +(2 rows) + +SELECT * FROM INT8_TBL WHERE q2 <= 456; + q1 | q2 +------------------+------------------- + 123 | 456 + 4567890123456789 | 123 + 4567890123456789 | -4567890123456789 +(3 rows) + +SELECT * FROM INT8_TBL WHERE q2 >= 456; + q1 | q2 +------------------+------------------ + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 4567890123456789 +(3 rows) + +-- int4/int8 cmp +SELECT * FROM INT8_TBL WHERE 123 = q1; + q1 | q2 +-----+------------------ + 123 | 456 + 123 | 4567890123456789 +(2 rows) + +SELECT * FROM INT8_TBL WHERE 123 <> q1; + q1 | q2 +------------------+------------------- + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(3 rows) + +SELECT * FROM INT8_TBL WHERE 123 < q1; + q1 | q2 +------------------+------------------- + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(3 rows) + +SELECT * FROM INT8_TBL WHERE 123 > q1; + q1 | q2 +----+---- +(0 rows) + +SELECT * FROM INT8_TBL WHERE 123 <= q1; + q1 | q2 +------------------+------------------- + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(5 rows) + +SELECT * FROM INT8_TBL WHERE 123 >= q1; + q1 | q2 +-----+------------------ + 123 | 456 + 123 | 4567890123456789 +(2 rows) + +-- int8/int2 cmp +SELECT * FROM INT8_TBL WHERE q2 = '456'::int2; + q1 | q2 +-----+----- + 123 | 456 +(1 row) + +SELECT * FROM INT8_TBL WHERE q2 <> '456'::int2; + q1 | q2 +------------------+------------------- + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(4 rows) + +SELECT * FROM INT8_TBL WHERE q2 < '456'::int2; + q1 | q2 +------------------+------------------- + 4567890123456789 | 123 + 4567890123456789 | -4567890123456789 +(2 rows) + +SELECT * FROM INT8_TBL WHERE q2 > '456'::int2; + q1 | q2 +------------------+------------------ + 123 | 4567890123456789 + 4567890123456789 | 4567890123456789 +(2 rows) + +SELECT * FROM INT8_TBL WHERE q2 <= '456'::int2; + q1 | q2 +------------------+------------------- + 123 | 456 + 4567890123456789 | 123 + 4567890123456789 | -4567890123456789 +(3 rows) + +SELECT * FROM INT8_TBL WHERE q2 >= '456'::int2; + q1 | q2 +------------------+------------------ + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 4567890123456789 +(3 rows) + +-- int2/int8 cmp +SELECT * FROM INT8_TBL WHERE '123'::int2 = q1; + q1 | q2 +-----+------------------ + 123 | 456 + 123 | 4567890123456789 +(2 rows) + +SELECT * FROM INT8_TBL WHERE '123'::int2 <> q1; + q1 | q2 +------------------+------------------- + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(3 rows) + +SELECT * FROM INT8_TBL WHERE '123'::int2 < q1; + q1 | q2 +------------------+------------------- + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(3 rows) + +SELECT * FROM INT8_TBL WHERE '123'::int2 > q1; + q1 | q2 +----+---- +(0 rows) + +SELECT * FROM INT8_TBL WHERE '123'::int2 <= q1; + q1 | q2 +------------------+------------------- + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(5 rows) + +SELECT * FROM INT8_TBL WHERE '123'::int2 >= q1; + q1 | q2 +-----+------------------ + 123 | 456 + 123 | 4567890123456789 +(2 rows) + +SELECT q1 AS plus, -q1 AS minus FROM INT8_TBL; + plus | minus +------------------+------------------- + 123 | -123 + 123 | -123 + 4567890123456789 | -4567890123456789 + 4567890123456789 | -4567890123456789 + 4567890123456789 | -4567890123456789 +(5 rows) + +SELECT q1, q2, q1 + q2 AS plus FROM INT8_TBL; + q1 | q2 | plus +------------------+-------------------+------------------ + 123 | 456 | 579 + 123 | 4567890123456789 | 4567890123456912 + 4567890123456789 | 123 | 4567890123456912 + 4567890123456789 | 4567890123456789 | 9135780246913578 + 4567890123456789 | -4567890123456789 | 0 +(5 rows) + +SELECT q1, q2, q1 - q2 AS minus FROM INT8_TBL; + q1 | q2 | minus +------------------+-------------------+------------------- + 123 | 456 | -333 + 123 | 4567890123456789 | -4567890123456666 + 4567890123456789 | 123 | 4567890123456666 + 4567890123456789 | 4567890123456789 | 0 + 4567890123456789 | -4567890123456789 | 9135780246913578 +(5 rows) + +SELECT q1, q2, q1 * q2 AS multiply FROM INT8_TBL; +ERROR: bigint out of range +SELECT q1, q2, q1 * q2 AS multiply FROM INT8_TBL + WHERE q1 < 1000 or (q2 > 0 and q2 < 1000); + q1 | q2 | multiply +------------------+------------------+-------------------- + 123 | 456 | 56088 + 123 | 4567890123456789 | 561850485185185047 + 4567890123456789 | 123 | 561850485185185047 +(3 rows) + +SELECT q1, q2, q1 / q2 AS divide, q1 % q2 AS mod FROM INT8_TBL; + q1 | q2 | divide | mod +------------------+-------------------+----------------+----- + 123 | 456 | 0 | 123 + 123 | 4567890123456789 | 0 | 123 + 4567890123456789 | 123 | 37137318076884 | 57 + 4567890123456789 | 4567890123456789 | 1 | 0 + 4567890123456789 | -4567890123456789 | -1 | 0 +(5 rows) + +SELECT q1, float8(q1) FROM INT8_TBL; + q1 | float8 +------------------+----------------------- + 123 | 123 + 123 | 123 + 4567890123456789 | 4.567890123456789e+15 + 4567890123456789 | 4.567890123456789e+15 + 4567890123456789 | 4.567890123456789e+15 +(5 rows) + +SELECT q2, float8(q2) FROM INT8_TBL; + q2 | float8 +-------------------+------------------------ + 456 | 456 + 4567890123456789 | 4.567890123456789e+15 + 123 | 123 + 4567890123456789 | 4.567890123456789e+15 + -4567890123456789 | -4.567890123456789e+15 +(5 rows) + +SELECT 37 + q1 AS plus4 FROM INT8_TBL; + plus4 +------------------ + 160 + 160 + 4567890123456826 + 4567890123456826 + 4567890123456826 +(5 rows) + +SELECT 37 - q1 AS minus4 FROM INT8_TBL; + minus4 +------------------- + -86 + -86 + -4567890123456752 + -4567890123456752 + -4567890123456752 +(5 rows) + +SELECT 2 * q1 AS "twice int4" FROM INT8_TBL; + twice int4 +------------------ + 246 + 246 + 9135780246913578 + 9135780246913578 + 9135780246913578 +(5 rows) + +SELECT q1 * 2 AS "twice int4" FROM INT8_TBL; + twice int4 +------------------ + 246 + 246 + 9135780246913578 + 9135780246913578 + 9135780246913578 +(5 rows) + +-- int8 op int4 +SELECT q1 + 42::int4 AS "8plus4", q1 - 42::int4 AS "8minus4", q1 * 42::int4 AS "8mul4", q1 / 42::int4 AS "8div4" FROM INT8_TBL; + 8plus4 | 8minus4 | 8mul4 | 8div4 +------------------+------------------+--------------------+----------------- + 165 | 81 | 5166 | 2 + 165 | 81 | 5166 | 2 + 4567890123456831 | 4567890123456747 | 191851385185185138 | 108759288653733 + 4567890123456831 | 4567890123456747 | 191851385185185138 | 108759288653733 + 4567890123456831 | 4567890123456747 | 191851385185185138 | 108759288653733 +(5 rows) + +-- int4 op int8 +SELECT 246::int4 + q1 AS "4plus8", 246::int4 - q1 AS "4minus8", 246::int4 * q1 AS "4mul8", 246::int4 / q1 AS "4div8" FROM INT8_TBL; + 4plus8 | 4minus8 | 4mul8 | 4div8 +------------------+-------------------+---------------------+------- + 369 | 123 | 30258 | 2 + 369 | 123 | 30258 | 2 + 4567890123457035 | -4567890123456543 | 1123700970370370094 | 0 + 4567890123457035 | -4567890123456543 | 1123700970370370094 | 0 + 4567890123457035 | -4567890123456543 | 1123700970370370094 | 0 +(5 rows) + +-- int8 op int2 +SELECT q1 + 42::int2 AS "8plus2", q1 - 42::int2 AS "8minus2", q1 * 42::int2 AS "8mul2", q1 / 42::int2 AS "8div2" FROM INT8_TBL; + 8plus2 | 8minus2 | 8mul2 | 8div2 +------------------+------------------+--------------------+----------------- + 165 | 81 | 5166 | 2 + 165 | 81 | 5166 | 2 + 4567890123456831 | 4567890123456747 | 191851385185185138 | 108759288653733 + 4567890123456831 | 4567890123456747 | 191851385185185138 | 108759288653733 + 4567890123456831 | 4567890123456747 | 191851385185185138 | 108759288653733 +(5 rows) + +-- int2 op int8 +SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 AS "2mul8", 246::int2 / q1 AS "2div8" FROM INT8_TBL; + 2plus8 | 2minus8 | 2mul8 | 2div8 +------------------+-------------------+---------------------+------- + 369 | 123 | 30258 | 2 + 369 | 123 | 30258 | 2 + 4567890123457035 | -4567890123456543 | 1123700970370370094 | 0 + 4567890123457035 | -4567890123456543 | 1123700970370370094 | 0 + 4567890123457035 | -4567890123456543 | 1123700970370370094 | 0 +(5 rows) + +SELECT q2, abs(q2) FROM INT8_TBL; + q2 | abs +-------------------+------------------ + 456 | 456 + 4567890123456789 | 4567890123456789 + 123 | 123 + 4567890123456789 | 4567890123456789 + -4567890123456789 | 4567890123456789 +(5 rows) + +SELECT min(q1), min(q2) FROM INT8_TBL; + min | min +-----+------------------- + 123 | -4567890123456789 +(1 row) + +SELECT max(q1), max(q2) FROM INT8_TBL; + max | max +------------------+------------------ + 4567890123456789 | 4567890123456789 +(1 row) + +-- TO_CHAR() +-- +SELECT to_char(q1, '9G999G999G999G999G999'), to_char(q2, '9,999,999,999,999,999') + FROM INT8_TBL; + to_char | to_char +------------------------+------------------------ + 123 | 456 + 123 | 4,567,890,123,456,789 + 4,567,890,123,456,789 | 123 + 4,567,890,123,456,789 | 4,567,890,123,456,789 + 4,567,890,123,456,789 | -4,567,890,123,456,789 +(5 rows) + +SELECT to_char(q1, '9G999G999G999G999G999D999G999'), to_char(q2, '9,999,999,999,999,999.999,999') + FROM INT8_TBL; + to_char | to_char +--------------------------------+-------------------------------- + 123.000,000 | 456.000,000 + 123.000,000 | 4,567,890,123,456,789.000,000 + 4,567,890,123,456,789.000,000 | 123.000,000 + 4,567,890,123,456,789.000,000 | 4,567,890,123,456,789.000,000 + 4,567,890,123,456,789.000,000 | -4,567,890,123,456,789.000,000 +(5 rows) + +SELECT to_char( (q1 * -1), '9999999999999999PR'), to_char( (q2 * -1), '9999999999999999.999PR') + FROM INT8_TBL; + to_char | to_char +--------------------+------------------------ + <123> | <456.000> + <123> | <4567890123456789.000> + <4567890123456789> | <123.000> + <4567890123456789> | <4567890123456789.000> + <4567890123456789> | 4567890123456789.000 +(5 rows) + +SELECT to_char( (q1 * -1), '9999999999999999S'), to_char( (q2 * -1), 'S9999999999999999') + FROM INT8_TBL; + to_char | to_char +-------------------+------------------- + 123- | -456 + 123- | -4567890123456789 + 4567890123456789- | -123 + 4567890123456789- | -4567890123456789 + 4567890123456789- | +4567890123456789 +(5 rows) + +SELECT to_char(q2, 'MI9999999999999999') FROM INT8_TBL; + to_char +------------------- + 456 + 4567890123456789 + 123 + 4567890123456789 + -4567890123456789 +(5 rows) + +SELECT to_char(q2, 'FMS9999999999999999') FROM INT8_TBL; + to_char +------------------- + +456 + +4567890123456789 + +123 + +4567890123456789 + -4567890123456789 +(5 rows) + +SELECT to_char(q2, 'FM9999999999999999THPR') FROM INT8_TBL; + to_char +-------------------- + 456TH + 4567890123456789TH + 123RD + 4567890123456789TH + <4567890123456789> +(5 rows) + +SELECT to_char(q2, 'SG9999999999999999th') FROM INT8_TBL; + to_char +--------------------- + + 456th + +4567890123456789th + + 123rd + +4567890123456789th + -4567890123456789 +(5 rows) + +SELECT to_char(q2, '0999999999999999') FROM INT8_TBL; + to_char +------------------- + 0000000000000456 + 4567890123456789 + 0000000000000123 + 4567890123456789 + -4567890123456789 +(5 rows) + +SELECT to_char(q2, 'S0999999999999999') FROM INT8_TBL; + to_char +------------------- + +0000000000000456 + +4567890123456789 + +0000000000000123 + +4567890123456789 + -4567890123456789 +(5 rows) + +SELECT to_char(q2, 'FM0999999999999999') FROM INT8_TBL; + to_char +------------------- + 0000000000000456 + 4567890123456789 + 0000000000000123 + 4567890123456789 + -4567890123456789 +(5 rows) + +SELECT to_char(q2, 'FM9999999999999999.000') FROM INT8_TBL; + to_char +----------------------- + 456.000 + 4567890123456789.000 + 123.000 + 4567890123456789.000 + -4567890123456789.000 +(5 rows) + +-- YB: TODO: Disabled the test below as it relies on the current locale which is +-- configured in pg_regress using ALTER which we do not support yet. +-- SELECT to_char(q2, 'L9999999999999999.000') FROM INT8_TBL; +SELECT to_char(q2, 'FM9999999999999999.999') FROM INT8_TBL; + to_char +-------------------- + 456. + 4567890123456789. + 123. + 4567890123456789. + -4567890123456789. +(5 rows) + +SELECT to_char(q2, 'S 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 . 9 9 9') FROM INT8_TBL; + to_char +------------------------------------------- + +4 5 6 . 0 0 0 + +4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 . 0 0 0 + +1 2 3 . 0 0 0 + +4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 . 0 0 0 + -4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 . 0 0 0 +(5 rows) + +SELECT to_char(q2, E'99999 "text" 9999 "9999" 999 "\\"text between quote marks\\"" 9999') FROM INT8_TBL; + to_char +----------------------------------------------------------- + text 9999 "text between quote marks" 456 + 45678 text 9012 9999 345 "text between quote marks" 6789 + text 9999 "text between quote marks" 123 + 45678 text 9012 9999 345 "text between quote marks" 6789 + -45678 text 9012 9999 345 "text between quote marks" 6789 +(5 rows) + +SELECT to_char(q2, '999999SG9999999999') FROM INT8_TBL; + to_char +------------------- + + 456 + 456789+0123456789 + + 123 + 456789+0123456789 + 456789-0123456789 +(5 rows) + +-- check min/max values and overflow behavior +select '-9223372036854775808'::int8; + int8 +---------------------- + -9223372036854775808 +(1 row) + +select '-9223372036854775809'::int8; +ERROR: value "-9223372036854775809" is out of range for type bigint +LINE 1: select '-9223372036854775809'::int8; + ^ +select '9223372036854775807'::int8; + int8 +--------------------- + 9223372036854775807 +(1 row) + +select '9223372036854775808'::int8; +ERROR: value "9223372036854775808" is out of range for type bigint +LINE 1: select '9223372036854775808'::int8; + ^ +select -('-9223372036854775807'::int8); + ?column? +--------------------- + 9223372036854775807 +(1 row) + +select -('-9223372036854775808'::int8); +ERROR: bigint out of range +select '9223372036854775800'::int8 + '9223372036854775800'::int8; +ERROR: bigint out of range +select '-9223372036854775800'::int8 + '-9223372036854775800'::int8; +ERROR: bigint out of range +select '9223372036854775800'::int8 - '-9223372036854775800'::int8; +ERROR: bigint out of range +select '-9223372036854775800'::int8 - '9223372036854775800'::int8; +ERROR: bigint out of range +select '9223372036854775800'::int8 * '9223372036854775800'::int8; +ERROR: bigint out of range +select '9223372036854775800'::int8 / '0'::int8; +ERROR: division by zero +select '9223372036854775800'::int8 % '0'::int8; +ERROR: division by zero +select abs('-9223372036854775808'::int8); +ERROR: bigint out of range +select '9223372036854775800'::int8 + '100'::int4; +ERROR: bigint out of range +select '-9223372036854775800'::int8 - '100'::int4; +ERROR: bigint out of range +select '9223372036854775800'::int8 * '100'::int4; +ERROR: bigint out of range +select '100'::int4 + '9223372036854775800'::int8; +ERROR: bigint out of range +select '-100'::int4 - '9223372036854775800'::int8; +ERROR: bigint out of range +select '100'::int4 * '9223372036854775800'::int8; +ERROR: bigint out of range +select '9223372036854775800'::int8 + '100'::int2; +ERROR: bigint out of range +select '-9223372036854775800'::int8 - '100'::int2; +ERROR: bigint out of range +select '9223372036854775800'::int8 * '100'::int2; +ERROR: bigint out of range +select '-9223372036854775808'::int8 / '0'::int2; +ERROR: division by zero +select '100'::int2 + '9223372036854775800'::int8; +ERROR: bigint out of range +select '-100'::int2 - '9223372036854775800'::int8; +ERROR: bigint out of range +select '100'::int2 * '9223372036854775800'::int8; +ERROR: bigint out of range +select '100'::int2 / '0'::int8; +ERROR: division by zero +SELECT CAST(q1 AS int4) FROM int8_tbl WHERE q2 = 456; + q1 +----- + 123 +(1 row) + +SELECT CAST(q1 AS int4) FROM int8_tbl WHERE q2 <> 456; +ERROR: integer out of range +SELECT CAST(q1 AS int2) FROM int8_tbl WHERE q2 = 456; + q1 +----- + 123 +(1 row) + +SELECT CAST(q1 AS int2) FROM int8_tbl WHERE q2 <> 456; +ERROR: smallint out of range +SELECT CAST('42'::int2 AS int8), CAST('-37'::int2 AS int8); + int8 | int8 +------+------ + 42 | -37 +(1 row) + +SELECT CAST(q1 AS float4), CAST(q2 AS float8) FROM INT8_TBL; + q1 | q2 +-------------+------------------------ + 123 | 456 + 123 | 4.567890123456789e+15 + 4.56789e+15 | 123 + 4.56789e+15 | 4.567890123456789e+15 + 4.56789e+15 | -4.567890123456789e+15 +(5 rows) + +SELECT CAST('36854775807.0'::float4 AS int8); + int8 +------------- + 36854775808 +(1 row) + +SELECT CAST('922337203685477580700.0'::float8 AS int8); +ERROR: bigint out of range +SELECT CAST(q1 AS oid) FROM INT8_TBL; +ERROR: OID out of range +SELECT oid::int8 FROM pg_class WHERE relname = 'pg_class'; + oid +------ + 1259 +(1 row) + +-- bit operations +SELECT q1, q2, q1 & q2 AS "and", q1 | q2 AS "or", q1 # q2 AS "xor", ~q1 AS "not" FROM INT8_TBL; + q1 | q2 | and | or | xor | not +------------------+-------------------+------------------+------------------+------------------+------------------- + 123 | 456 | 72 | 507 | 435 | -124 + 123 | 4567890123456789 | 17 | 4567890123456895 | 4567890123456878 | -124 + 4567890123456789 | 123 | 17 | 4567890123456895 | 4567890123456878 | -4567890123456790 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 0 | -4567890123456790 + 4567890123456789 | -4567890123456789 | 1 | -1 | -2 | -4567890123456790 +(5 rows) + +SELECT q1, q1 << 2 AS "shl", q1 >> 3 AS "shr" FROM INT8_TBL; + q1 | shl | shr +------------------+-------------------+----------------- + 123 | 492 | 15 + 123 | 492 | 15 + 4567890123456789 | 18271560493827156 | 570986265432098 + 4567890123456789 | 18271560493827156 | 570986265432098 + 4567890123456789 | 18271560493827156 | 570986265432098 +(5 rows) + +-- generate_series +SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::int8); + generate_series +------------------ + 4567890123456789 + 4567890123456790 + 4567890123456791 + 4567890123456792 + 4567890123456793 + 4567890123456794 + 4567890123456795 + 4567890123456796 + 4567890123456797 + 4567890123456798 + 4567890123456799 +(11 rows) + +SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::int8, 0); +ERROR: step size cannot equal zero +SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::int8, 2); + generate_series +------------------ + 4567890123456789 + 4567890123456791 + 4567890123456793 + 4567890123456795 + 4567890123456797 + 4567890123456799 +(6 rows) + +-- corner case +SELECT (-1::int8<<63)::text; + text +---------------------- + -9223372036854775808 +(1 row) + +SELECT ((-1::int8<<63)+1)::text; + text +---------------------- + -9223372036854775807 +(1 row) + +-- check sane handling of INT64_MIN overflow cases +SELECT (-9223372036854775808)::int8 * (-1)::int8; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 / (-1)::int8; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 % (-1)::int8; + ?column? +---------- + 0 +(1 row) + +SELECT (-9223372036854775808)::int8 * (-1)::int4; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 / (-1)::int4; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 % (-1)::int4; + ?column? +---------- + 0 +(1 row) + +SELECT (-9223372036854775808)::int8 * (-1)::int2; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 / (-1)::int2; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 % (-1)::int2; + ?column? +---------- + 0 +(1 row) + +-- check rounding when casting from float +SELECT x, x::int8 AS int8_value +FROM (VALUES (-2.5::float8), + (-1.5::float8), + (-0.5::float8), + (0.0::float8), + (0.5::float8), + (1.5::float8), + (2.5::float8)) t(x); + x | int8_value +------+------------ + -2.5 | -2 + -1.5 | -2 + -0.5 | 0 + 0 | 0 + 0.5 | 0 + 1.5 | 2 + 2.5 | 2 +(7 rows) + +-- check rounding when casting from numeric +SELECT x, x::int8 AS int8_value +FROM (VALUES (-2.5::numeric), + (-1.5::numeric), + (-0.5::numeric), + (0.0::numeric), + (0.5::numeric), + (1.5::numeric), + (2.5::numeric)) t(x); + x | int8_value +------+------------ + -2.5 | -3 + -1.5 | -2 + -0.5 | -1 + 0.0 | 0 + 0.5 | 1 + 1.5 | 2 + 2.5 | 3 +(7 rows) + +-- test gcd() +SELECT a, b, gcd(a, b), gcd(a, -b), gcd(b, a), gcd(-b, a) +FROM (VALUES (0::int8, 0::int8), + (0::int8, 29893644334::int8), + (288484263558::int8, 29893644334::int8), + (-288484263558::int8, 29893644334::int8), + ((-9223372036854775808)::int8, 1::int8), + ((-9223372036854775808)::int8, 9223372036854775807::int8), + ((-9223372036854775808)::int8, 4611686018427387904::int8)) AS v(a, b); + a | b | gcd | gcd | gcd | gcd +----------------------+---------------------+---------------------+---------------------+---------------------+--------------------- + 0 | 0 | 0 | 0 | 0 | 0 + 0 | 29893644334 | 29893644334 | 29893644334 | 29893644334 | 29893644334 + 288484263558 | 29893644334 | 6835958 | 6835958 | 6835958 | 6835958 + -288484263558 | 29893644334 | 6835958 | 6835958 | 6835958 | 6835958 + -9223372036854775808 | 1 | 1 | 1 | 1 | 1 + -9223372036854775808 | 9223372036854775807 | 1 | 1 | 1 | 1 + -9223372036854775808 | 4611686018427387904 | 4611686018427387904 | 4611686018427387904 | 4611686018427387904 | 4611686018427387904 +(7 rows) + +SELECT gcd((-9223372036854775808)::int8, 0::int8); -- overflow +ERROR: bigint out of range +SELECT gcd((-9223372036854775808)::int8, (-9223372036854775808)::int8); -- overflow +ERROR: bigint out of range +-- test lcm() +SELECT a, b, lcm(a, b), lcm(a, -b), lcm(b, a), lcm(-b, a) +FROM (VALUES (0::int8, 0::int8), + (0::int8, 29893644334::int8), + (29893644334::int8, 29893644334::int8), + (288484263558::int8, 29893644334::int8), + (-288484263558::int8, 29893644334::int8), + ((-9223372036854775808)::int8, 0::int8)) AS v(a, b); + a | b | lcm | lcm | lcm | lcm +----------------------+-------------+------------------+------------------+------------------+------------------ + 0 | 0 | 0 | 0 | 0 | 0 + 0 | 29893644334 | 0 | 0 | 0 | 0 + 29893644334 | 29893644334 | 29893644334 | 29893644334 | 29893644334 | 29893644334 + 288484263558 | 29893644334 | 1261541684539134 | 1261541684539134 | 1261541684539134 | 1261541684539134 + -288484263558 | 29893644334 | 1261541684539134 | 1261541684539134 | 1261541684539134 | 1261541684539134 + -9223372036854775808 | 0 | 0 | 0 | 0 | 0 +(6 rows) + +SELECT lcm((-9223372036854775808)::int8, 1::int8); -- overflow +ERROR: bigint out of range +SELECT lcm(9223372036854775807::int8, 9223372036854775806::int8); -- overflow +ERROR: bigint out of range diff --git a/src/test/regress/expected/yb.port.join.out b/src/test/regress/expected/yb.port.join.out new file mode 100644 index 0000000000000..bc041e8f43fa6 --- /dev/null +++ b/src/test/regress/expected/yb.port.join.out @@ -0,0 +1,2195 @@ +-- +-- JOIN +-- Test JOIN clauses +-- +CREATE TABLE J1_TBL ( + i integer, + j integer, + t text +); +CREATE TABLE J2_TBL ( + i integer, + k integer +); +INSERT INTO J1_TBL VALUES (1, 4, 'one'); +INSERT INTO J1_TBL VALUES (2, 3, 'two'); +INSERT INTO J1_TBL VALUES (3, 2, 'three'); +INSERT INTO J1_TBL VALUES (4, 1, 'four'); +INSERT INTO J1_TBL VALUES (5, 0, 'five'); +INSERT INTO J1_TBL VALUES (6, 6, 'six'); +INSERT INTO J1_TBL VALUES (7, 7, 'seven'); +INSERT INTO J1_TBL VALUES (8, 8, 'eight'); +INSERT INTO J1_TBL VALUES (0, NULL, 'zero'); +INSERT INTO J1_TBL VALUES (NULL, NULL, 'null'); +INSERT INTO J1_TBL VALUES (NULL, 0, 'zero'); +INSERT INTO J2_TBL VALUES (1, -1); +INSERT INTO J2_TBL VALUES (2, 2); +INSERT INTO J2_TBL VALUES (3, -3); +INSERT INTO J2_TBL VALUES (2, 4); +INSERT INTO J2_TBL VALUES (5, -5); +INSERT INTO J2_TBL VALUES (5, -5); +INSERT INTO J2_TBL VALUES (0, NULL); +INSERT INTO J2_TBL VALUES (NULL, NULL); +INSERT INTO J2_TBL VALUES (NULL, 0); +-- +-- CORRELATION NAMES +-- Make sure that table/column aliases are supported +-- before diving into more complex join syntax. +-- +SELECT '' AS "xxx", * + FROM J1_TBL AS tx order by 1, 2, 3, 4; + xxx | i | j | t +-----+---+---+------- + | 0 | | zero + | 1 | 4 | one + | 2 | 3 | two + | 3 | 2 | three + | 4 | 1 | four + | 5 | 0 | five + | 6 | 6 | six + | 7 | 7 | seven + | 8 | 8 | eight + | | 0 | zero + | | | null +(11 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL tx order by 1, 2, 3, 4; + xxx | i | j | t +-----+---+---+------- + | 0 | | zero + | 1 | 4 | one + | 2 | 3 | two + | 3 | 2 | three + | 4 | 1 | four + | 5 | 0 | five + | 6 | 6 | six + | 7 | 7 | seven + | 8 | 8 | eight + | | 0 | zero + | | | null +(11 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL AS t1 (a, b, c) order by 1, 2, 3, 4; + xxx | a | b | c +-----+---+---+------- + | 0 | | zero + | 1 | 4 | one + | 2 | 3 | two + | 3 | 2 | three + | 4 | 1 | four + | 5 | 0 | five + | 6 | 6 | six + | 7 | 7 | seven + | 8 | 8 | eight + | | 0 | zero + | | | null +(11 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) order by 1, 2, 3, 4; + xxx | a | b | c +-----+---+---+------- + | 0 | | zero + | 1 | 4 | one + | 2 | 3 | two + | 3 | 2 | three + | 4 | 1 | four + | 5 | 0 | five + | 6 | 6 | six + | 7 | 7 | seven + | 8 | 8 | eight + | | 0 | zero + | | | null +(11 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) order by 1, 2, 3, 4, 5, 6; + xxx | a | b | c | d | e +-----+---+---+-------+---+---- + | 0 | | zero | 0 | + | 0 | | zero | 1 | -1 + | 0 | | zero | 2 | 2 + | 0 | | zero | 2 | 4 + | 0 | | zero | 3 | -3 + | 0 | | zero | 5 | -5 + | 0 | | zero | 5 | -5 + | 0 | | zero | | 0 + | 0 | | zero | | + | 1 | 4 | one | 0 | + | 1 | 4 | one | 1 | -1 + | 1 | 4 | one | 2 | 2 + | 1 | 4 | one | 2 | 4 + | 1 | 4 | one | 3 | -3 + | 1 | 4 | one | 5 | -5 + | 1 | 4 | one | 5 | -5 + | 1 | 4 | one | | 0 + | 1 | 4 | one | | + | 2 | 3 | two | 0 | + | 2 | 3 | two | 1 | -1 + | 2 | 3 | two | 2 | 2 + | 2 | 3 | two | 2 | 4 + | 2 | 3 | two | 3 | -3 + | 2 | 3 | two | 5 | -5 + | 2 | 3 | two | 5 | -5 + | 2 | 3 | two | | 0 + | 2 | 3 | two | | + | 3 | 2 | three | 0 | + | 3 | 2 | three | 1 | -1 + | 3 | 2 | three | 2 | 2 + | 3 | 2 | three | 2 | 4 + | 3 | 2 | three | 3 | -3 + | 3 | 2 | three | 5 | -5 + | 3 | 2 | three | 5 | -5 + | 3 | 2 | three | | 0 + | 3 | 2 | three | | + | 4 | 1 | four | 0 | + | 4 | 1 | four | 1 | -1 + | 4 | 1 | four | 2 | 2 + | 4 | 1 | four | 2 | 4 + | 4 | 1 | four | 3 | -3 + | 4 | 1 | four | 5 | -5 + | 4 | 1 | four | 5 | -5 + | 4 | 1 | four | | 0 + | 4 | 1 | four | | + | 5 | 0 | five | 0 | + | 5 | 0 | five | 1 | -1 + | 5 | 0 | five | 2 | 2 + | 5 | 0 | five | 2 | 4 + | 5 | 0 | five | 3 | -3 + | 5 | 0 | five | 5 | -5 + | 5 | 0 | five | 5 | -5 + | 5 | 0 | five | | 0 + | 5 | 0 | five | | + | 6 | 6 | six | 0 | + | 6 | 6 | six | 1 | -1 + | 6 | 6 | six | 2 | 2 + | 6 | 6 | six | 2 | 4 + | 6 | 6 | six | 3 | -3 + | 6 | 6 | six | 5 | -5 + | 6 | 6 | six | 5 | -5 + | 6 | 6 | six | | 0 + | 6 | 6 | six | | + | 7 | 7 | seven | 0 | + | 7 | 7 | seven | 1 | -1 + | 7 | 7 | seven | 2 | 2 + | 7 | 7 | seven | 2 | 4 + | 7 | 7 | seven | 3 | -3 + | 7 | 7 | seven | 5 | -5 + | 7 | 7 | seven | 5 | -5 + | 7 | 7 | seven | | 0 + | 7 | 7 | seven | | + | 8 | 8 | eight | 0 | + | 8 | 8 | eight | 1 | -1 + | 8 | 8 | eight | 2 | 2 + | 8 | 8 | eight | 2 | 4 + | 8 | 8 | eight | 3 | -3 + | 8 | 8 | eight | 5 | -5 + | 8 | 8 | eight | 5 | -5 + | 8 | 8 | eight | | 0 + | 8 | 8 | eight | | + | | 0 | zero | 0 | + | | 0 | zero | 1 | -1 + | | 0 | zero | 2 | 2 + | | 0 | zero | 2 | 4 + | | 0 | zero | 3 | -3 + | | 0 | zero | 5 | -5 + | | 0 | zero | 5 | -5 + | | 0 | zero | | 0 + | | 0 | zero | | + | | | null | 0 | + | | | null | 1 | -1 + | | | null | 2 | 2 + | | | null | 2 | 4 + | | | null | 3 | -3 + | | | null | 5 | -5 + | | | null | 5 | -5 + | | | null | | 0 + | | | null | | +(99 rows) + +SELECT '' AS "xxx", t1.a, t2.e + FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) + WHERE t1.a = t2.d order by 1, 2, 3; + xxx | a | e +-----+---+---- + | 0 | + | 1 | -1 + | 2 | 2 + | 2 | 4 + | 3 | -3 + | 5 | -5 + | 5 | -5 +(7 rows) + +-- +-- CROSS JOIN +-- Qualifications are not allowed on cross joins, +-- which degenerate into a standard unqualified inner join. +-- +SELECT '' AS "xxx", * + FROM J1_TBL CROSS JOIN J2_TBL order by 1, 2, 3, 4, 5, 6; + xxx | i | j | t | i | k +-----+---+---+-------+---+---- + | 0 | | zero | 0 | + | 0 | | zero | 1 | -1 + | 0 | | zero | 2 | 2 + | 0 | | zero | 2 | 4 + | 0 | | zero | 3 | -3 + | 0 | | zero | 5 | -5 + | 0 | | zero | 5 | -5 + | 0 | | zero | | 0 + | 0 | | zero | | + | 1 | 4 | one | 0 | + | 1 | 4 | one | 1 | -1 + | 1 | 4 | one | 2 | 2 + | 1 | 4 | one | 2 | 4 + | 1 | 4 | one | 3 | -3 + | 1 | 4 | one | 5 | -5 + | 1 | 4 | one | 5 | -5 + | 1 | 4 | one | | 0 + | 1 | 4 | one | | + | 2 | 3 | two | 0 | + | 2 | 3 | two | 1 | -1 + | 2 | 3 | two | 2 | 2 + | 2 | 3 | two | 2 | 4 + | 2 | 3 | two | 3 | -3 + | 2 | 3 | two | 5 | -5 + | 2 | 3 | two | 5 | -5 + | 2 | 3 | two | | 0 + | 2 | 3 | two | | + | 3 | 2 | three | 0 | + | 3 | 2 | three | 1 | -1 + | 3 | 2 | three | 2 | 2 + | 3 | 2 | three | 2 | 4 + | 3 | 2 | three | 3 | -3 + | 3 | 2 | three | 5 | -5 + | 3 | 2 | three | 5 | -5 + | 3 | 2 | three | | 0 + | 3 | 2 | three | | + | 4 | 1 | four | 0 | + | 4 | 1 | four | 1 | -1 + | 4 | 1 | four | 2 | 2 + | 4 | 1 | four | 2 | 4 + | 4 | 1 | four | 3 | -3 + | 4 | 1 | four | 5 | -5 + | 4 | 1 | four | 5 | -5 + | 4 | 1 | four | | 0 + | 4 | 1 | four | | + | 5 | 0 | five | 0 | + | 5 | 0 | five | 1 | -1 + | 5 | 0 | five | 2 | 2 + | 5 | 0 | five | 2 | 4 + | 5 | 0 | five | 3 | -3 + | 5 | 0 | five | 5 | -5 + | 5 | 0 | five | 5 | -5 + | 5 | 0 | five | | 0 + | 5 | 0 | five | | + | 6 | 6 | six | 0 | + | 6 | 6 | six | 1 | -1 + | 6 | 6 | six | 2 | 2 + | 6 | 6 | six | 2 | 4 + | 6 | 6 | six | 3 | -3 + | 6 | 6 | six | 5 | -5 + | 6 | 6 | six | 5 | -5 + | 6 | 6 | six | | 0 + | 6 | 6 | six | | + | 7 | 7 | seven | 0 | + | 7 | 7 | seven | 1 | -1 + | 7 | 7 | seven | 2 | 2 + | 7 | 7 | seven | 2 | 4 + | 7 | 7 | seven | 3 | -3 + | 7 | 7 | seven | 5 | -5 + | 7 | 7 | seven | 5 | -5 + | 7 | 7 | seven | | 0 + | 7 | 7 | seven | | + | 8 | 8 | eight | 0 | + | 8 | 8 | eight | 1 | -1 + | 8 | 8 | eight | 2 | 2 + | 8 | 8 | eight | 2 | 4 + | 8 | 8 | eight | 3 | -3 + | 8 | 8 | eight | 5 | -5 + | 8 | 8 | eight | 5 | -5 + | 8 | 8 | eight | | 0 + | 8 | 8 | eight | | + | | 0 | zero | 0 | + | | 0 | zero | 1 | -1 + | | 0 | zero | 2 | 2 + | | 0 | zero | 2 | 4 + | | 0 | zero | 3 | -3 + | | 0 | zero | 5 | -5 + | | 0 | zero | 5 | -5 + | | 0 | zero | | 0 + | | 0 | zero | | + | | | null | 0 | + | | | null | 1 | -1 + | | | null | 2 | 2 + | | | null | 2 | 4 + | | | null | 3 | -3 + | | | null | 5 | -5 + | | | null | 5 | -5 + | | | null | | 0 + | | | null | | +(99 rows) + +-- ambiguous column +SELECT '' AS "xxx", i, k, t + FROM J1_TBL CROSS JOIN J2_TBL; +ERROR: column reference "i" is ambiguous +LINE 1: SELECT '' AS "xxx", i, k, t + ^ +-- resolve previous ambiguity by specifying the table name +SELECT '' AS "xxx", t1.i, k, t + FROM J1_TBL t1 CROSS JOIN J2_TBL t2 order by 1, 2, 3, 4; + xxx | i | k | t +-----+---+----+------- + | 0 | -5 | zero + | 0 | -5 | zero + | 0 | -3 | zero + | 0 | -1 | zero + | 0 | 0 | zero + | 0 | 2 | zero + | 0 | 4 | zero + | 0 | | zero + | 0 | | zero + | 1 | -5 | one + | 1 | -5 | one + | 1 | -3 | one + | 1 | -1 | one + | 1 | 0 | one + | 1 | 2 | one + | 1 | 4 | one + | 1 | | one + | 1 | | one + | 2 | -5 | two + | 2 | -5 | two + | 2 | -3 | two + | 2 | -1 | two + | 2 | 0 | two + | 2 | 2 | two + | 2 | 4 | two + | 2 | | two + | 2 | | two + | 3 | -5 | three + | 3 | -5 | three + | 3 | -3 | three + | 3 | -1 | three + | 3 | 0 | three + | 3 | 2 | three + | 3 | 4 | three + | 3 | | three + | 3 | | three + | 4 | -5 | four + | 4 | -5 | four + | 4 | -3 | four + | 4 | -1 | four + | 4 | 0 | four + | 4 | 2 | four + | 4 | 4 | four + | 4 | | four + | 4 | | four + | 5 | -5 | five + | 5 | -5 | five + | 5 | -3 | five + | 5 | -1 | five + | 5 | 0 | five + | 5 | 2 | five + | 5 | 4 | five + | 5 | | five + | 5 | | five + | 6 | -5 | six + | 6 | -5 | six + | 6 | -3 | six + | 6 | -1 | six + | 6 | 0 | six + | 6 | 2 | six + | 6 | 4 | six + | 6 | | six + | 6 | | six + | 7 | -5 | seven + | 7 | -5 | seven + | 7 | -3 | seven + | 7 | -1 | seven + | 7 | 0 | seven + | 7 | 2 | seven + | 7 | 4 | seven + | 7 | | seven + | 7 | | seven + | 8 | -5 | eight + | 8 | -5 | eight + | 8 | -3 | eight + | 8 | -1 | eight + | 8 | 0 | eight + | 8 | 2 | eight + | 8 | 4 | eight + | 8 | | eight + | 8 | | eight + | | -5 | null + | | -5 | null + | | -5 | zero + | | -5 | zero + | | -3 | null + | | -3 | zero + | | -1 | null + | | -1 | zero + | | 0 | null + | | 0 | zero + | | 2 | null + | | 2 | zero + | | 4 | null + | | 4 | zero + | | | null + | | | null + | | | zero + | | | zero +(99 rows) + +SELECT '' AS "xxx", ii, tt, kk + FROM (J1_TBL CROSS JOIN J2_TBL) + AS tx (ii, jj, tt, ii2, kk) order by 1, 2, 3, 4; + xxx | ii | tt | kk +-----+----+-------+---- + | 0 | zero | -5 + | 0 | zero | -5 + | 0 | zero | -3 + | 0 | zero | -1 + | 0 | zero | 0 + | 0 | zero | 2 + | 0 | zero | 4 + | 0 | zero | + | 0 | zero | + | 1 | one | -5 + | 1 | one | -5 + | 1 | one | -3 + | 1 | one | -1 + | 1 | one | 0 + | 1 | one | 2 + | 1 | one | 4 + | 1 | one | + | 1 | one | + | 2 | two | -5 + | 2 | two | -5 + | 2 | two | -3 + | 2 | two | -1 + | 2 | two | 0 + | 2 | two | 2 + | 2 | two | 4 + | 2 | two | + | 2 | two | + | 3 | three | -5 + | 3 | three | -5 + | 3 | three | -3 + | 3 | three | -1 + | 3 | three | 0 + | 3 | three | 2 + | 3 | three | 4 + | 3 | three | + | 3 | three | + | 4 | four | -5 + | 4 | four | -5 + | 4 | four | -3 + | 4 | four | -1 + | 4 | four | 0 + | 4 | four | 2 + | 4 | four | 4 + | 4 | four | + | 4 | four | + | 5 | five | -5 + | 5 | five | -5 + | 5 | five | -3 + | 5 | five | -1 + | 5 | five | 0 + | 5 | five | 2 + | 5 | five | 4 + | 5 | five | + | 5 | five | + | 6 | six | -5 + | 6 | six | -5 + | 6 | six | -3 + | 6 | six | -1 + | 6 | six | 0 + | 6 | six | 2 + | 6 | six | 4 + | 6 | six | + | 6 | six | + | 7 | seven | -5 + | 7 | seven | -5 + | 7 | seven | -3 + | 7 | seven | -1 + | 7 | seven | 0 + | 7 | seven | 2 + | 7 | seven | 4 + | 7 | seven | + | 7 | seven | + | 8 | eight | -5 + | 8 | eight | -5 + | 8 | eight | -3 + | 8 | eight | -1 + | 8 | eight | 0 + | 8 | eight | 2 + | 8 | eight | 4 + | 8 | eight | + | 8 | eight | + | | null | -5 + | | null | -5 + | | null | -3 + | | null | -1 + | | null | 0 + | | null | 2 + | | null | 4 + | | null | + | | null | + | | zero | -5 + | | zero | -5 + | | zero | -3 + | | zero | -1 + | | zero | 0 + | | zero | 2 + | | zero | 4 + | | zero | + | | zero | +(99 rows) + +SELECT '' AS "xxx", tx.ii, tx.jj, tx.kk + FROM (J1_TBL t1 (a, b, c) CROSS JOIN J2_TBL t2 (d, e)) + AS tx (ii, jj, tt, ii2, kk) order by 1, 2, 3, 4; + xxx | ii | jj | kk +-----+----+----+---- + | 0 | | -5 + | 0 | | -5 + | 0 | | -3 + | 0 | | -1 + | 0 | | 0 + | 0 | | 2 + | 0 | | 4 + | 0 | | + | 0 | | + | 1 | 4 | -5 + | 1 | 4 | -5 + | 1 | 4 | -3 + | 1 | 4 | -1 + | 1 | 4 | 0 + | 1 | 4 | 2 + | 1 | 4 | 4 + | 1 | 4 | + | 1 | 4 | + | 2 | 3 | -5 + | 2 | 3 | -5 + | 2 | 3 | -3 + | 2 | 3 | -1 + | 2 | 3 | 0 + | 2 | 3 | 2 + | 2 | 3 | 4 + | 2 | 3 | + | 2 | 3 | + | 3 | 2 | -5 + | 3 | 2 | -5 + | 3 | 2 | -3 + | 3 | 2 | -1 + | 3 | 2 | 0 + | 3 | 2 | 2 + | 3 | 2 | 4 + | 3 | 2 | + | 3 | 2 | + | 4 | 1 | -5 + | 4 | 1 | -5 + | 4 | 1 | -3 + | 4 | 1 | -1 + | 4 | 1 | 0 + | 4 | 1 | 2 + | 4 | 1 | 4 + | 4 | 1 | + | 4 | 1 | + | 5 | 0 | -5 + | 5 | 0 | -5 + | 5 | 0 | -3 + | 5 | 0 | -1 + | 5 | 0 | 0 + | 5 | 0 | 2 + | 5 | 0 | 4 + | 5 | 0 | + | 5 | 0 | + | 6 | 6 | -5 + | 6 | 6 | -5 + | 6 | 6 | -3 + | 6 | 6 | -1 + | 6 | 6 | 0 + | 6 | 6 | 2 + | 6 | 6 | 4 + | 6 | 6 | + | 6 | 6 | + | 7 | 7 | -5 + | 7 | 7 | -5 + | 7 | 7 | -3 + | 7 | 7 | -1 + | 7 | 7 | 0 + | 7 | 7 | 2 + | 7 | 7 | 4 + | 7 | 7 | + | 7 | 7 | + | 8 | 8 | -5 + | 8 | 8 | -5 + | 8 | 8 | -3 + | 8 | 8 | -1 + | 8 | 8 | 0 + | 8 | 8 | 2 + | 8 | 8 | 4 + | 8 | 8 | + | 8 | 8 | + | | 0 | -5 + | | 0 | -5 + | | 0 | -3 + | | 0 | -1 + | | 0 | 0 + | | 0 | 2 + | | 0 | 4 + | | 0 | + | | 0 | + | | | -5 + | | | -5 + | | | -3 + | | | -1 + | | | 0 + | | | 2 + | | | 4 + | | | + | | | +(99 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b order by 1, 2, 3, 4, 5, 6, 7, 8; + xxx | i | j | t | i | k | i | k +-----+---+---+-------+---+----+---+---- + | 0 | | zero | 0 | | 0 | + | 0 | | zero | 0 | | 1 | -1 + | 0 | | zero | 0 | | 2 | 2 + | 0 | | zero | 0 | | 2 | 4 + | 0 | | zero | 0 | | 3 | -3 + | 0 | | zero | 0 | | 5 | -5 + | 0 | | zero | 0 | | 5 | -5 + | 0 | | zero | 0 | | | 0 + | 0 | | zero | 0 | | | + | 0 | | zero | 1 | -1 | 0 | + | 0 | | zero | 1 | -1 | 1 | -1 + | 0 | | zero | 1 | -1 | 2 | 2 + | 0 | | zero | 1 | -1 | 2 | 4 + | 0 | | zero | 1 | -1 | 3 | -3 + | 0 | | zero | 1 | -1 | 5 | -5 + | 0 | | zero | 1 | -1 | 5 | -5 + | 0 | | zero | 1 | -1 | | 0 + | 0 | | zero | 1 | -1 | | + | 0 | | zero | 2 | 2 | 0 | + | 0 | | zero | 2 | 2 | 1 | -1 + | 0 | | zero | 2 | 2 | 2 | 2 + | 0 | | zero | 2 | 2 | 2 | 4 + | 0 | | zero | 2 | 2 | 3 | -3 + | 0 | | zero | 2 | 2 | 5 | -5 + | 0 | | zero | 2 | 2 | 5 | -5 + | 0 | | zero | 2 | 2 | | 0 + | 0 | | zero | 2 | 2 | | + | 0 | | zero | 2 | 4 | 0 | + | 0 | | zero | 2 | 4 | 1 | -1 + | 0 | | zero | 2 | 4 | 2 | 2 + | 0 | | zero | 2 | 4 | 2 | 4 + | 0 | | zero | 2 | 4 | 3 | -3 + | 0 | | zero | 2 | 4 | 5 | -5 + | 0 | | zero | 2 | 4 | 5 | -5 + | 0 | | zero | 2 | 4 | | 0 + | 0 | | zero | 2 | 4 | | + | 0 | | zero | 3 | -3 | 0 | + | 0 | | zero | 3 | -3 | 1 | -1 + | 0 | | zero | 3 | -3 | 2 | 2 + | 0 | | zero | 3 | -3 | 2 | 4 + | 0 | | zero | 3 | -3 | 3 | -3 + | 0 | | zero | 3 | -3 | 5 | -5 + | 0 | | zero | 3 | -3 | 5 | -5 + | 0 | | zero | 3 | -3 | | 0 + | 0 | | zero | 3 | -3 | | + | 0 | | zero | 5 | -5 | 0 | + | 0 | | zero | 5 | -5 | 0 | + | 0 | | zero | 5 | -5 | 1 | -1 + | 0 | | zero | 5 | -5 | 1 | -1 + | 0 | | zero | 5 | -5 | 2 | 2 + | 0 | | zero | 5 | -5 | 2 | 2 + | 0 | | zero | 5 | -5 | 2 | 4 + | 0 | | zero | 5 | -5 | 2 | 4 + | 0 | | zero | 5 | -5 | 3 | -3 + | 0 | | zero | 5 | -5 | 3 | -3 + | 0 | | zero | 5 | -5 | 5 | -5 + | 0 | | zero | 5 | -5 | 5 | -5 + | 0 | | zero | 5 | -5 | 5 | -5 + | 0 | | zero | 5 | -5 | 5 | -5 + | 0 | | zero | 5 | -5 | | 0 + | 0 | | zero | 5 | -5 | | 0 + | 0 | | zero | 5 | -5 | | + | 0 | | zero | 5 | -5 | | + | 0 | | zero | | 0 | 0 | + | 0 | | zero | | 0 | 1 | -1 + | 0 | | zero | | 0 | 2 | 2 + | 0 | | zero | | 0 | 2 | 4 + | 0 | | zero | | 0 | 3 | -3 + | 0 | | zero | | 0 | 5 | -5 + | 0 | | zero | | 0 | 5 | -5 + | 0 | | zero | | 0 | | 0 + | 0 | | zero | | 0 | | + | 0 | | zero | | | 0 | + | 0 | | zero | | | 1 | -1 + | 0 | | zero | | | 2 | 2 + | 0 | | zero | | | 2 | 4 + | 0 | | zero | | | 3 | -3 + | 0 | | zero | | | 5 | -5 + | 0 | | zero | | | 5 | -5 + | 0 | | zero | | | | 0 + | 0 | | zero | | | | + | 1 | 4 | one | 0 | | 0 | + | 1 | 4 | one | 0 | | 1 | -1 + | 1 | 4 | one | 0 | | 2 | 2 + | 1 | 4 | one | 0 | | 2 | 4 + | 1 | 4 | one | 0 | | 3 | -3 + | 1 | 4 | one | 0 | | 5 | -5 + | 1 | 4 | one | 0 | | 5 | -5 + | 1 | 4 | one | 0 | | | 0 + | 1 | 4 | one | 0 | | | + | 1 | 4 | one | 1 | -1 | 0 | + | 1 | 4 | one | 1 | -1 | 1 | -1 + | 1 | 4 | one | 1 | -1 | 2 | 2 + | 1 | 4 | one | 1 | -1 | 2 | 4 + | 1 | 4 | one | 1 | -1 | 3 | -3 + | 1 | 4 | one | 1 | -1 | 5 | -5 + | 1 | 4 | one | 1 | -1 | 5 | -5 + | 1 | 4 | one | 1 | -1 | | 0 + | 1 | 4 | one | 1 | -1 | | + | 1 | 4 | one | 2 | 2 | 0 | + | 1 | 4 | one | 2 | 2 | 1 | -1 + | 1 | 4 | one | 2 | 2 | 2 | 2 + | 1 | 4 | one | 2 | 2 | 2 | 4 + | 1 | 4 | one | 2 | 2 | 3 | -3 + | 1 | 4 | one | 2 | 2 | 5 | -5 + | 1 | 4 | one | 2 | 2 | 5 | -5 + | 1 | 4 | one | 2 | 2 | | 0 + | 1 | 4 | one | 2 | 2 | | + | 1 | 4 | one | 2 | 4 | 0 | + | 1 | 4 | one | 2 | 4 | 1 | -1 + | 1 | 4 | one | 2 | 4 | 2 | 2 + | 1 | 4 | one | 2 | 4 | 2 | 4 + | 1 | 4 | one | 2 | 4 | 3 | -3 + | 1 | 4 | one | 2 | 4 | 5 | -5 + | 1 | 4 | one | 2 | 4 | 5 | -5 + | 1 | 4 | one | 2 | 4 | | 0 + | 1 | 4 | one | 2 | 4 | | + | 1 | 4 | one | 3 | -3 | 0 | + | 1 | 4 | one | 3 | -3 | 1 | -1 + | 1 | 4 | one | 3 | -3 | 2 | 2 + | 1 | 4 | one | 3 | -3 | 2 | 4 + | 1 | 4 | one | 3 | -3 | 3 | -3 + | 1 | 4 | one | 3 | -3 | 5 | -5 + | 1 | 4 | one | 3 | -3 | 5 | -5 + | 1 | 4 | one | 3 | -3 | | 0 + | 1 | 4 | one | 3 | -3 | | + | 1 | 4 | one | 5 | -5 | 0 | + | 1 | 4 | one | 5 | -5 | 0 | + | 1 | 4 | one | 5 | -5 | 1 | -1 + | 1 | 4 | one | 5 | -5 | 1 | -1 + | 1 | 4 | one | 5 | -5 | 2 | 2 + | 1 | 4 | one | 5 | -5 | 2 | 2 + | 1 | 4 | one | 5 | -5 | 2 | 4 + | 1 | 4 | one | 5 | -5 | 2 | 4 + | 1 | 4 | one | 5 | -5 | 3 | -3 + | 1 | 4 | one | 5 | -5 | 3 | -3 + | 1 | 4 | one | 5 | -5 | 5 | -5 + | 1 | 4 | one | 5 | -5 | 5 | -5 + | 1 | 4 | one | 5 | -5 | 5 | -5 + | 1 | 4 | one | 5 | -5 | 5 | -5 + | 1 | 4 | one | 5 | -5 | | 0 + | 1 | 4 | one | 5 | -5 | | 0 + | 1 | 4 | one | 5 | -5 | | + | 1 | 4 | one | 5 | -5 | | + | 1 | 4 | one | | 0 | 0 | + | 1 | 4 | one | | 0 | 1 | -1 + | 1 | 4 | one | | 0 | 2 | 2 + | 1 | 4 | one | | 0 | 2 | 4 + | 1 | 4 | one | | 0 | 3 | -3 + | 1 | 4 | one | | 0 | 5 | -5 + | 1 | 4 | one | | 0 | 5 | -5 + | 1 | 4 | one | | 0 | | 0 + | 1 | 4 | one | | 0 | | + | 1 | 4 | one | | | 0 | + | 1 | 4 | one | | | 1 | -1 + | 1 | 4 | one | | | 2 | 2 + | 1 | 4 | one | | | 2 | 4 + | 1 | 4 | one | | | 3 | -3 + | 1 | 4 | one | | | 5 | -5 + | 1 | 4 | one | | | 5 | -5 + | 1 | 4 | one | | | | 0 + | 1 | 4 | one | | | | + | 2 | 3 | two | 0 | | 0 | + | 2 | 3 | two | 0 | | 1 | -1 + | 2 | 3 | two | 0 | | 2 | 2 + | 2 | 3 | two | 0 | | 2 | 4 + | 2 | 3 | two | 0 | | 3 | -3 + | 2 | 3 | two | 0 | | 5 | -5 + | 2 | 3 | two | 0 | | 5 | -5 + | 2 | 3 | two | 0 | | | 0 + | 2 | 3 | two | 0 | | | + | 2 | 3 | two | 1 | -1 | 0 | + | 2 | 3 | two | 1 | -1 | 1 | -1 + | 2 | 3 | two | 1 | -1 | 2 | 2 + | 2 | 3 | two | 1 | -1 | 2 | 4 + | 2 | 3 | two | 1 | -1 | 3 | -3 + | 2 | 3 | two | 1 | -1 | 5 | -5 + | 2 | 3 | two | 1 | -1 | 5 | -5 + | 2 | 3 | two | 1 | -1 | | 0 + | 2 | 3 | two | 1 | -1 | | + | 2 | 3 | two | 2 | 2 | 0 | + | 2 | 3 | two | 2 | 2 | 1 | -1 + | 2 | 3 | two | 2 | 2 | 2 | 2 + | 2 | 3 | two | 2 | 2 | 2 | 4 + | 2 | 3 | two | 2 | 2 | 3 | -3 + | 2 | 3 | two | 2 | 2 | 5 | -5 + | 2 | 3 | two | 2 | 2 | 5 | -5 + | 2 | 3 | two | 2 | 2 | | 0 + | 2 | 3 | two | 2 | 2 | | + | 2 | 3 | two | 2 | 4 | 0 | + | 2 | 3 | two | 2 | 4 | 1 | -1 + | 2 | 3 | two | 2 | 4 | 2 | 2 + | 2 | 3 | two | 2 | 4 | 2 | 4 + | 2 | 3 | two | 2 | 4 | 3 | -3 + | 2 | 3 | two | 2 | 4 | 5 | -5 + | 2 | 3 | two | 2 | 4 | 5 | -5 + | 2 | 3 | two | 2 | 4 | | 0 + | 2 | 3 | two | 2 | 4 | | + | 2 | 3 | two | 3 | -3 | 0 | + | 2 | 3 | two | 3 | -3 | 1 | -1 + | 2 | 3 | two | 3 | -3 | 2 | 2 + | 2 | 3 | two | 3 | -3 | 2 | 4 + | 2 | 3 | two | 3 | -3 | 3 | -3 + | 2 | 3 | two | 3 | -3 | 5 | -5 + | 2 | 3 | two | 3 | -3 | 5 | -5 + | 2 | 3 | two | 3 | -3 | | 0 + | 2 | 3 | two | 3 | -3 | | + | 2 | 3 | two | 5 | -5 | 0 | + | 2 | 3 | two | 5 | -5 | 0 | + | 2 | 3 | two | 5 | -5 | 1 | -1 + | 2 | 3 | two | 5 | -5 | 1 | -1 + | 2 | 3 | two | 5 | -5 | 2 | 2 + | 2 | 3 | two | 5 | -5 | 2 | 2 + | 2 | 3 | two | 5 | -5 | 2 | 4 + | 2 | 3 | two | 5 | -5 | 2 | 4 + | 2 | 3 | two | 5 | -5 | 3 | -3 + | 2 | 3 | two | 5 | -5 | 3 | -3 + | 2 | 3 | two | 5 | -5 | 5 | -5 + | 2 | 3 | two | 5 | -5 | 5 | -5 + | 2 | 3 | two | 5 | -5 | 5 | -5 + | 2 | 3 | two | 5 | -5 | 5 | -5 + | 2 | 3 | two | 5 | -5 | | 0 + | 2 | 3 | two | 5 | -5 | | 0 + | 2 | 3 | two | 5 | -5 | | + | 2 | 3 | two | 5 | -5 | | + | 2 | 3 | two | | 0 | 0 | + | 2 | 3 | two | | 0 | 1 | -1 + | 2 | 3 | two | | 0 | 2 | 2 + | 2 | 3 | two | | 0 | 2 | 4 + | 2 | 3 | two | | 0 | 3 | -3 + | 2 | 3 | two | | 0 | 5 | -5 + | 2 | 3 | two | | 0 | 5 | -5 + | 2 | 3 | two | | 0 | | 0 + | 2 | 3 | two | | 0 | | + | 2 | 3 | two | | | 0 | + | 2 | 3 | two | | | 1 | -1 + | 2 | 3 | two | | | 2 | 2 + | 2 | 3 | two | | | 2 | 4 + | 2 | 3 | two | | | 3 | -3 + | 2 | 3 | two | | | 5 | -5 + | 2 | 3 | two | | | 5 | -5 + | 2 | 3 | two | | | | 0 + | 2 | 3 | two | | | | + | 3 | 2 | three | 0 | | 0 | + | 3 | 2 | three | 0 | | 1 | -1 + | 3 | 2 | three | 0 | | 2 | 2 + | 3 | 2 | three | 0 | | 2 | 4 + | 3 | 2 | three | 0 | | 3 | -3 + | 3 | 2 | three | 0 | | 5 | -5 + | 3 | 2 | three | 0 | | 5 | -5 + | 3 | 2 | three | 0 | | | 0 + | 3 | 2 | three | 0 | | | + | 3 | 2 | three | 1 | -1 | 0 | + | 3 | 2 | three | 1 | -1 | 1 | -1 + | 3 | 2 | three | 1 | -1 | 2 | 2 + | 3 | 2 | three | 1 | -1 | 2 | 4 + | 3 | 2 | three | 1 | -1 | 3 | -3 + | 3 | 2 | three | 1 | -1 | 5 | -5 + | 3 | 2 | three | 1 | -1 | 5 | -5 + | 3 | 2 | three | 1 | -1 | | 0 + | 3 | 2 | three | 1 | -1 | | + | 3 | 2 | three | 2 | 2 | 0 | + | 3 | 2 | three | 2 | 2 | 1 | -1 + | 3 | 2 | three | 2 | 2 | 2 | 2 + | 3 | 2 | three | 2 | 2 | 2 | 4 + | 3 | 2 | three | 2 | 2 | 3 | -3 + | 3 | 2 | three | 2 | 2 | 5 | -5 + | 3 | 2 | three | 2 | 2 | 5 | -5 + | 3 | 2 | three | 2 | 2 | | 0 + | 3 | 2 | three | 2 | 2 | | + | 3 | 2 | three | 2 | 4 | 0 | + | 3 | 2 | three | 2 | 4 | 1 | -1 + | 3 | 2 | three | 2 | 4 | 2 | 2 + | 3 | 2 | three | 2 | 4 | 2 | 4 + | 3 | 2 | three | 2 | 4 | 3 | -3 + | 3 | 2 | three | 2 | 4 | 5 | -5 + | 3 | 2 | three | 2 | 4 | 5 | -5 + | 3 | 2 | three | 2 | 4 | | 0 + | 3 | 2 | three | 2 | 4 | | + | 3 | 2 | three | 3 | -3 | 0 | + | 3 | 2 | three | 3 | -3 | 1 | -1 + | 3 | 2 | three | 3 | -3 | 2 | 2 + | 3 | 2 | three | 3 | -3 | 2 | 4 + | 3 | 2 | three | 3 | -3 | 3 | -3 + | 3 | 2 | three | 3 | -3 | 5 | -5 + | 3 | 2 | three | 3 | -3 | 5 | -5 + | 3 | 2 | three | 3 | -3 | | 0 + | 3 | 2 | three | 3 | -3 | | + | 3 | 2 | three | 5 | -5 | 0 | + | 3 | 2 | three | 5 | -5 | 0 | + | 3 | 2 | three | 5 | -5 | 1 | -1 + | 3 | 2 | three | 5 | -5 | 1 | -1 + | 3 | 2 | three | 5 | -5 | 2 | 2 + | 3 | 2 | three | 5 | -5 | 2 | 2 + | 3 | 2 | three | 5 | -5 | 2 | 4 + | 3 | 2 | three | 5 | -5 | 2 | 4 + | 3 | 2 | three | 5 | -5 | 3 | -3 + | 3 | 2 | three | 5 | -5 | 3 | -3 + | 3 | 2 | three | 5 | -5 | 5 | -5 + | 3 | 2 | three | 5 | -5 | 5 | -5 + | 3 | 2 | three | 5 | -5 | 5 | -5 + | 3 | 2 | three | 5 | -5 | 5 | -5 + | 3 | 2 | three | 5 | -5 | | 0 + | 3 | 2 | three | 5 | -5 | | 0 + | 3 | 2 | three | 5 | -5 | | + | 3 | 2 | three | 5 | -5 | | + | 3 | 2 | three | | 0 | 0 | + | 3 | 2 | three | | 0 | 1 | -1 + | 3 | 2 | three | | 0 | 2 | 2 + | 3 | 2 | three | | 0 | 2 | 4 + | 3 | 2 | three | | 0 | 3 | -3 + | 3 | 2 | three | | 0 | 5 | -5 + | 3 | 2 | three | | 0 | 5 | -5 + | 3 | 2 | three | | 0 | | 0 + | 3 | 2 | three | | 0 | | + | 3 | 2 | three | | | 0 | + | 3 | 2 | three | | | 1 | -1 + | 3 | 2 | three | | | 2 | 2 + | 3 | 2 | three | | | 2 | 4 + | 3 | 2 | three | | | 3 | -3 + | 3 | 2 | three | | | 5 | -5 + | 3 | 2 | three | | | 5 | -5 + | 3 | 2 | three | | | | 0 + | 3 | 2 | three | | | | + | 4 | 1 | four | 0 | | 0 | + | 4 | 1 | four | 0 | | 1 | -1 + | 4 | 1 | four | 0 | | 2 | 2 + | 4 | 1 | four | 0 | | 2 | 4 + | 4 | 1 | four | 0 | | 3 | -3 + | 4 | 1 | four | 0 | | 5 | -5 + | 4 | 1 | four | 0 | | 5 | -5 + | 4 | 1 | four | 0 | | | 0 + | 4 | 1 | four | 0 | | | + | 4 | 1 | four | 1 | -1 | 0 | + | 4 | 1 | four | 1 | -1 | 1 | -1 + | 4 | 1 | four | 1 | -1 | 2 | 2 + | 4 | 1 | four | 1 | -1 | 2 | 4 + | 4 | 1 | four | 1 | -1 | 3 | -3 + | 4 | 1 | four | 1 | -1 | 5 | -5 + | 4 | 1 | four | 1 | -1 | 5 | -5 + | 4 | 1 | four | 1 | -1 | | 0 + | 4 | 1 | four | 1 | -1 | | + | 4 | 1 | four | 2 | 2 | 0 | + | 4 | 1 | four | 2 | 2 | 1 | -1 + | 4 | 1 | four | 2 | 2 | 2 | 2 + | 4 | 1 | four | 2 | 2 | 2 | 4 + | 4 | 1 | four | 2 | 2 | 3 | -3 + | 4 | 1 | four | 2 | 2 | 5 | -5 + | 4 | 1 | four | 2 | 2 | 5 | -5 + | 4 | 1 | four | 2 | 2 | | 0 + | 4 | 1 | four | 2 | 2 | | + | 4 | 1 | four | 2 | 4 | 0 | + | 4 | 1 | four | 2 | 4 | 1 | -1 + | 4 | 1 | four | 2 | 4 | 2 | 2 + | 4 | 1 | four | 2 | 4 | 2 | 4 + | 4 | 1 | four | 2 | 4 | 3 | -3 + | 4 | 1 | four | 2 | 4 | 5 | -5 + | 4 | 1 | four | 2 | 4 | 5 | -5 + | 4 | 1 | four | 2 | 4 | | 0 + | 4 | 1 | four | 2 | 4 | | + | 4 | 1 | four | 3 | -3 | 0 | + | 4 | 1 | four | 3 | -3 | 1 | -1 + | 4 | 1 | four | 3 | -3 | 2 | 2 + | 4 | 1 | four | 3 | -3 | 2 | 4 + | 4 | 1 | four | 3 | -3 | 3 | -3 + | 4 | 1 | four | 3 | -3 | 5 | -5 + | 4 | 1 | four | 3 | -3 | 5 | -5 + | 4 | 1 | four | 3 | -3 | | 0 + | 4 | 1 | four | 3 | -3 | | + | 4 | 1 | four | 5 | -5 | 0 | + | 4 | 1 | four | 5 | -5 | 0 | + | 4 | 1 | four | 5 | -5 | 1 | -1 + | 4 | 1 | four | 5 | -5 | 1 | -1 + | 4 | 1 | four | 5 | -5 | 2 | 2 + | 4 | 1 | four | 5 | -5 | 2 | 2 + | 4 | 1 | four | 5 | -5 | 2 | 4 + | 4 | 1 | four | 5 | -5 | 2 | 4 + | 4 | 1 | four | 5 | -5 | 3 | -3 + | 4 | 1 | four | 5 | -5 | 3 | -3 + | 4 | 1 | four | 5 | -5 | 5 | -5 + | 4 | 1 | four | 5 | -5 | 5 | -5 + | 4 | 1 | four | 5 | -5 | 5 | -5 + | 4 | 1 | four | 5 | -5 | 5 | -5 + | 4 | 1 | four | 5 | -5 | | 0 + | 4 | 1 | four | 5 | -5 | | 0 + | 4 | 1 | four | 5 | -5 | | + | 4 | 1 | four | 5 | -5 | | + | 4 | 1 | four | | 0 | 0 | + | 4 | 1 | four | | 0 | 1 | -1 + | 4 | 1 | four | | 0 | 2 | 2 + | 4 | 1 | four | | 0 | 2 | 4 + | 4 | 1 | four | | 0 | 3 | -3 + | 4 | 1 | four | | 0 | 5 | -5 + | 4 | 1 | four | | 0 | 5 | -5 + | 4 | 1 | four | | 0 | | 0 + | 4 | 1 | four | | 0 | | + | 4 | 1 | four | | | 0 | + | 4 | 1 | four | | | 1 | -1 + | 4 | 1 | four | | | 2 | 2 + | 4 | 1 | four | | | 2 | 4 + | 4 | 1 | four | | | 3 | -3 + | 4 | 1 | four | | | 5 | -5 + | 4 | 1 | four | | | 5 | -5 + | 4 | 1 | four | | | | 0 + | 4 | 1 | four | | | | + | 5 | 0 | five | 0 | | 0 | + | 5 | 0 | five | 0 | | 1 | -1 + | 5 | 0 | five | 0 | | 2 | 2 + | 5 | 0 | five | 0 | | 2 | 4 + | 5 | 0 | five | 0 | | 3 | -3 + | 5 | 0 | five | 0 | | 5 | -5 + | 5 | 0 | five | 0 | | 5 | -5 + | 5 | 0 | five | 0 | | | 0 + | 5 | 0 | five | 0 | | | + | 5 | 0 | five | 1 | -1 | 0 | + | 5 | 0 | five | 1 | -1 | 1 | -1 + | 5 | 0 | five | 1 | -1 | 2 | 2 + | 5 | 0 | five | 1 | -1 | 2 | 4 + | 5 | 0 | five | 1 | -1 | 3 | -3 + | 5 | 0 | five | 1 | -1 | 5 | -5 + | 5 | 0 | five | 1 | -1 | 5 | -5 + | 5 | 0 | five | 1 | -1 | | 0 + | 5 | 0 | five | 1 | -1 | | + | 5 | 0 | five | 2 | 2 | 0 | + | 5 | 0 | five | 2 | 2 | 1 | -1 + | 5 | 0 | five | 2 | 2 | 2 | 2 + | 5 | 0 | five | 2 | 2 | 2 | 4 + | 5 | 0 | five | 2 | 2 | 3 | -3 + | 5 | 0 | five | 2 | 2 | 5 | -5 + | 5 | 0 | five | 2 | 2 | 5 | -5 + | 5 | 0 | five | 2 | 2 | | 0 + | 5 | 0 | five | 2 | 2 | | + | 5 | 0 | five | 2 | 4 | 0 | + | 5 | 0 | five | 2 | 4 | 1 | -1 + | 5 | 0 | five | 2 | 4 | 2 | 2 + | 5 | 0 | five | 2 | 4 | 2 | 4 + | 5 | 0 | five | 2 | 4 | 3 | -3 + | 5 | 0 | five | 2 | 4 | 5 | -5 + | 5 | 0 | five | 2 | 4 | 5 | -5 + | 5 | 0 | five | 2 | 4 | | 0 + | 5 | 0 | five | 2 | 4 | | + | 5 | 0 | five | 3 | -3 | 0 | + | 5 | 0 | five | 3 | -3 | 1 | -1 + | 5 | 0 | five | 3 | -3 | 2 | 2 + | 5 | 0 | five | 3 | -3 | 2 | 4 + | 5 | 0 | five | 3 | -3 | 3 | -3 + | 5 | 0 | five | 3 | -3 | 5 | -5 + | 5 | 0 | five | 3 | -3 | 5 | -5 + | 5 | 0 | five | 3 | -3 | | 0 + | 5 | 0 | five | 3 | -3 | | + | 5 | 0 | five | 5 | -5 | 0 | + | 5 | 0 | five | 5 | -5 | 0 | + | 5 | 0 | five | 5 | -5 | 1 | -1 + | 5 | 0 | five | 5 | -5 | 1 | -1 + | 5 | 0 | five | 5 | -5 | 2 | 2 + | 5 | 0 | five | 5 | -5 | 2 | 2 + | 5 | 0 | five | 5 | -5 | 2 | 4 + | 5 | 0 | five | 5 | -5 | 2 | 4 + | 5 | 0 | five | 5 | -5 | 3 | -3 + | 5 | 0 | five | 5 | -5 | 3 | -3 + | 5 | 0 | five | 5 | -5 | 5 | -5 + | 5 | 0 | five | 5 | -5 | 5 | -5 + | 5 | 0 | five | 5 | -5 | 5 | -5 + | 5 | 0 | five | 5 | -5 | 5 | -5 + | 5 | 0 | five | 5 | -5 | | 0 + | 5 | 0 | five | 5 | -5 | | 0 + | 5 | 0 | five | 5 | -5 | | + | 5 | 0 | five | 5 | -5 | | + | 5 | 0 | five | | 0 | 0 | + | 5 | 0 | five | | 0 | 1 | -1 + | 5 | 0 | five | | 0 | 2 | 2 + | 5 | 0 | five | | 0 | 2 | 4 + | 5 | 0 | five | | 0 | 3 | -3 + | 5 | 0 | five | | 0 | 5 | -5 + | 5 | 0 | five | | 0 | 5 | -5 + | 5 | 0 | five | | 0 | | 0 + | 5 | 0 | five | | 0 | | + | 5 | 0 | five | | | 0 | + | 5 | 0 | five | | | 1 | -1 + | 5 | 0 | five | | | 2 | 2 + | 5 | 0 | five | | | 2 | 4 + | 5 | 0 | five | | | 3 | -3 + | 5 | 0 | five | | | 5 | -5 + | 5 | 0 | five | | | 5 | -5 + | 5 | 0 | five | | | | 0 + | 5 | 0 | five | | | | + | 6 | 6 | six | 0 | | 0 | + | 6 | 6 | six | 0 | | 1 | -1 + | 6 | 6 | six | 0 | | 2 | 2 + | 6 | 6 | six | 0 | | 2 | 4 + | 6 | 6 | six | 0 | | 3 | -3 + | 6 | 6 | six | 0 | | 5 | -5 + | 6 | 6 | six | 0 | | 5 | -5 + | 6 | 6 | six | 0 | | | 0 + | 6 | 6 | six | 0 | | | + | 6 | 6 | six | 1 | -1 | 0 | + | 6 | 6 | six | 1 | -1 | 1 | -1 + | 6 | 6 | six | 1 | -1 | 2 | 2 + | 6 | 6 | six | 1 | -1 | 2 | 4 + | 6 | 6 | six | 1 | -1 | 3 | -3 + | 6 | 6 | six | 1 | -1 | 5 | -5 + | 6 | 6 | six | 1 | -1 | 5 | -5 + | 6 | 6 | six | 1 | -1 | | 0 + | 6 | 6 | six | 1 | -1 | | + | 6 | 6 | six | 2 | 2 | 0 | + | 6 | 6 | six | 2 | 2 | 1 | -1 + | 6 | 6 | six | 2 | 2 | 2 | 2 + | 6 | 6 | six | 2 | 2 | 2 | 4 + | 6 | 6 | six | 2 | 2 | 3 | -3 + | 6 | 6 | six | 2 | 2 | 5 | -5 + | 6 | 6 | six | 2 | 2 | 5 | -5 + | 6 | 6 | six | 2 | 2 | | 0 + | 6 | 6 | six | 2 | 2 | | + | 6 | 6 | six | 2 | 4 | 0 | + | 6 | 6 | six | 2 | 4 | 1 | -1 + | 6 | 6 | six | 2 | 4 | 2 | 2 + | 6 | 6 | six | 2 | 4 | 2 | 4 + | 6 | 6 | six | 2 | 4 | 3 | -3 + | 6 | 6 | six | 2 | 4 | 5 | -5 + | 6 | 6 | six | 2 | 4 | 5 | -5 + | 6 | 6 | six | 2 | 4 | | 0 + | 6 | 6 | six | 2 | 4 | | + | 6 | 6 | six | 3 | -3 | 0 | + | 6 | 6 | six | 3 | -3 | 1 | -1 + | 6 | 6 | six | 3 | -3 | 2 | 2 + | 6 | 6 | six | 3 | -3 | 2 | 4 + | 6 | 6 | six | 3 | -3 | 3 | -3 + | 6 | 6 | six | 3 | -3 | 5 | -5 + | 6 | 6 | six | 3 | -3 | 5 | -5 + | 6 | 6 | six | 3 | -3 | | 0 + | 6 | 6 | six | 3 | -3 | | + | 6 | 6 | six | 5 | -5 | 0 | + | 6 | 6 | six | 5 | -5 | 0 | + | 6 | 6 | six | 5 | -5 | 1 | -1 + | 6 | 6 | six | 5 | -5 | 1 | -1 + | 6 | 6 | six | 5 | -5 | 2 | 2 + | 6 | 6 | six | 5 | -5 | 2 | 2 + | 6 | 6 | six | 5 | -5 | 2 | 4 + | 6 | 6 | six | 5 | -5 | 2 | 4 + | 6 | 6 | six | 5 | -5 | 3 | -3 + | 6 | 6 | six | 5 | -5 | 3 | -3 + | 6 | 6 | six | 5 | -5 | 5 | -5 + | 6 | 6 | six | 5 | -5 | 5 | -5 + | 6 | 6 | six | 5 | -5 | 5 | -5 + | 6 | 6 | six | 5 | -5 | 5 | -5 + | 6 | 6 | six | 5 | -5 | | 0 + | 6 | 6 | six | 5 | -5 | | 0 + | 6 | 6 | six | 5 | -5 | | + | 6 | 6 | six | 5 | -5 | | + | 6 | 6 | six | | 0 | 0 | + | 6 | 6 | six | | 0 | 1 | -1 + | 6 | 6 | six | | 0 | 2 | 2 + | 6 | 6 | six | | 0 | 2 | 4 + | 6 | 6 | six | | 0 | 3 | -3 + | 6 | 6 | six | | 0 | 5 | -5 + | 6 | 6 | six | | 0 | 5 | -5 + | 6 | 6 | six | | 0 | | 0 + | 6 | 6 | six | | 0 | | + | 6 | 6 | six | | | 0 | + | 6 | 6 | six | | | 1 | -1 + | 6 | 6 | six | | | 2 | 2 + | 6 | 6 | six | | | 2 | 4 + | 6 | 6 | six | | | 3 | -3 + | 6 | 6 | six | | | 5 | -5 + | 6 | 6 | six | | | 5 | -5 + | 6 | 6 | six | | | | 0 + | 6 | 6 | six | | | | + | 7 | 7 | seven | 0 | | 0 | + | 7 | 7 | seven | 0 | | 1 | -1 + | 7 | 7 | seven | 0 | | 2 | 2 + | 7 | 7 | seven | 0 | | 2 | 4 + | 7 | 7 | seven | 0 | | 3 | -3 + | 7 | 7 | seven | 0 | | 5 | -5 + | 7 | 7 | seven | 0 | | 5 | -5 + | 7 | 7 | seven | 0 | | | 0 + | 7 | 7 | seven | 0 | | | + | 7 | 7 | seven | 1 | -1 | 0 | + | 7 | 7 | seven | 1 | -1 | 1 | -1 + | 7 | 7 | seven | 1 | -1 | 2 | 2 + | 7 | 7 | seven | 1 | -1 | 2 | 4 + | 7 | 7 | seven | 1 | -1 | 3 | -3 + | 7 | 7 | seven | 1 | -1 | 5 | -5 + | 7 | 7 | seven | 1 | -1 | 5 | -5 + | 7 | 7 | seven | 1 | -1 | | 0 + | 7 | 7 | seven | 1 | -1 | | + | 7 | 7 | seven | 2 | 2 | 0 | + | 7 | 7 | seven | 2 | 2 | 1 | -1 + | 7 | 7 | seven | 2 | 2 | 2 | 2 + | 7 | 7 | seven | 2 | 2 | 2 | 4 + | 7 | 7 | seven | 2 | 2 | 3 | -3 + | 7 | 7 | seven | 2 | 2 | 5 | -5 + | 7 | 7 | seven | 2 | 2 | 5 | -5 + | 7 | 7 | seven | 2 | 2 | | 0 + | 7 | 7 | seven | 2 | 2 | | + | 7 | 7 | seven | 2 | 4 | 0 | + | 7 | 7 | seven | 2 | 4 | 1 | -1 + | 7 | 7 | seven | 2 | 4 | 2 | 2 + | 7 | 7 | seven | 2 | 4 | 2 | 4 + | 7 | 7 | seven | 2 | 4 | 3 | -3 + | 7 | 7 | seven | 2 | 4 | 5 | -5 + | 7 | 7 | seven | 2 | 4 | 5 | -5 + | 7 | 7 | seven | 2 | 4 | | 0 + | 7 | 7 | seven | 2 | 4 | | + | 7 | 7 | seven | 3 | -3 | 0 | + | 7 | 7 | seven | 3 | -3 | 1 | -1 + | 7 | 7 | seven | 3 | -3 | 2 | 2 + | 7 | 7 | seven | 3 | -3 | 2 | 4 + | 7 | 7 | seven | 3 | -3 | 3 | -3 + | 7 | 7 | seven | 3 | -3 | 5 | -5 + | 7 | 7 | seven | 3 | -3 | 5 | -5 + | 7 | 7 | seven | 3 | -3 | | 0 + | 7 | 7 | seven | 3 | -3 | | + | 7 | 7 | seven | 5 | -5 | 0 | + | 7 | 7 | seven | 5 | -5 | 0 | + | 7 | 7 | seven | 5 | -5 | 1 | -1 + | 7 | 7 | seven | 5 | -5 | 1 | -1 + | 7 | 7 | seven | 5 | -5 | 2 | 2 + | 7 | 7 | seven | 5 | -5 | 2 | 2 + | 7 | 7 | seven | 5 | -5 | 2 | 4 + | 7 | 7 | seven | 5 | -5 | 2 | 4 + | 7 | 7 | seven | 5 | -5 | 3 | -3 + | 7 | 7 | seven | 5 | -5 | 3 | -3 + | 7 | 7 | seven | 5 | -5 | 5 | -5 + | 7 | 7 | seven | 5 | -5 | 5 | -5 + | 7 | 7 | seven | 5 | -5 | 5 | -5 + | 7 | 7 | seven | 5 | -5 | 5 | -5 + | 7 | 7 | seven | 5 | -5 | | 0 + | 7 | 7 | seven | 5 | -5 | | 0 + | 7 | 7 | seven | 5 | -5 | | + | 7 | 7 | seven | 5 | -5 | | + | 7 | 7 | seven | | 0 | 0 | + | 7 | 7 | seven | | 0 | 1 | -1 + | 7 | 7 | seven | | 0 | 2 | 2 + | 7 | 7 | seven | | 0 | 2 | 4 + | 7 | 7 | seven | | 0 | 3 | -3 + | 7 | 7 | seven | | 0 | 5 | -5 + | 7 | 7 | seven | | 0 | 5 | -5 + | 7 | 7 | seven | | 0 | | 0 + | 7 | 7 | seven | | 0 | | + | 7 | 7 | seven | | | 0 | + | 7 | 7 | seven | | | 1 | -1 + | 7 | 7 | seven | | | 2 | 2 + | 7 | 7 | seven | | | 2 | 4 + | 7 | 7 | seven | | | 3 | -3 + | 7 | 7 | seven | | | 5 | -5 + | 7 | 7 | seven | | | 5 | -5 + | 7 | 7 | seven | | | | 0 + | 7 | 7 | seven | | | | + | 8 | 8 | eight | 0 | | 0 | + | 8 | 8 | eight | 0 | | 1 | -1 + | 8 | 8 | eight | 0 | | 2 | 2 + | 8 | 8 | eight | 0 | | 2 | 4 + | 8 | 8 | eight | 0 | | 3 | -3 + | 8 | 8 | eight | 0 | | 5 | -5 + | 8 | 8 | eight | 0 | | 5 | -5 + | 8 | 8 | eight | 0 | | | 0 + | 8 | 8 | eight | 0 | | | + | 8 | 8 | eight | 1 | -1 | 0 | + | 8 | 8 | eight | 1 | -1 | 1 | -1 + | 8 | 8 | eight | 1 | -1 | 2 | 2 + | 8 | 8 | eight | 1 | -1 | 2 | 4 + | 8 | 8 | eight | 1 | -1 | 3 | -3 + | 8 | 8 | eight | 1 | -1 | 5 | -5 + | 8 | 8 | eight | 1 | -1 | 5 | -5 + | 8 | 8 | eight | 1 | -1 | | 0 + | 8 | 8 | eight | 1 | -1 | | + | 8 | 8 | eight | 2 | 2 | 0 | + | 8 | 8 | eight | 2 | 2 | 1 | -1 + | 8 | 8 | eight | 2 | 2 | 2 | 2 + | 8 | 8 | eight | 2 | 2 | 2 | 4 + | 8 | 8 | eight | 2 | 2 | 3 | -3 + | 8 | 8 | eight | 2 | 2 | 5 | -5 + | 8 | 8 | eight | 2 | 2 | 5 | -5 + | 8 | 8 | eight | 2 | 2 | | 0 + | 8 | 8 | eight | 2 | 2 | | + | 8 | 8 | eight | 2 | 4 | 0 | + | 8 | 8 | eight | 2 | 4 | 1 | -1 + | 8 | 8 | eight | 2 | 4 | 2 | 2 + | 8 | 8 | eight | 2 | 4 | 2 | 4 + | 8 | 8 | eight | 2 | 4 | 3 | -3 + | 8 | 8 | eight | 2 | 4 | 5 | -5 + | 8 | 8 | eight | 2 | 4 | 5 | -5 + | 8 | 8 | eight | 2 | 4 | | 0 + | 8 | 8 | eight | 2 | 4 | | + | 8 | 8 | eight | 3 | -3 | 0 | + | 8 | 8 | eight | 3 | -3 | 1 | -1 + | 8 | 8 | eight | 3 | -3 | 2 | 2 + | 8 | 8 | eight | 3 | -3 | 2 | 4 + | 8 | 8 | eight | 3 | -3 | 3 | -3 + | 8 | 8 | eight | 3 | -3 | 5 | -5 + | 8 | 8 | eight | 3 | -3 | 5 | -5 + | 8 | 8 | eight | 3 | -3 | | 0 + | 8 | 8 | eight | 3 | -3 | | + | 8 | 8 | eight | 5 | -5 | 0 | + | 8 | 8 | eight | 5 | -5 | 0 | + | 8 | 8 | eight | 5 | -5 | 1 | -1 + | 8 | 8 | eight | 5 | -5 | 1 | -1 + | 8 | 8 | eight | 5 | -5 | 2 | 2 + | 8 | 8 | eight | 5 | -5 | 2 | 2 + | 8 | 8 | eight | 5 | -5 | 2 | 4 + | 8 | 8 | eight | 5 | -5 | 2 | 4 + | 8 | 8 | eight | 5 | -5 | 3 | -3 + | 8 | 8 | eight | 5 | -5 | 3 | -3 + | 8 | 8 | eight | 5 | -5 | 5 | -5 + | 8 | 8 | eight | 5 | -5 | 5 | -5 + | 8 | 8 | eight | 5 | -5 | 5 | -5 + | 8 | 8 | eight | 5 | -5 | 5 | -5 + | 8 | 8 | eight | 5 | -5 | | 0 + | 8 | 8 | eight | 5 | -5 | | 0 + | 8 | 8 | eight | 5 | -5 | | + | 8 | 8 | eight | 5 | -5 | | + | 8 | 8 | eight | | 0 | 0 | + | 8 | 8 | eight | | 0 | 1 | -1 + | 8 | 8 | eight | | 0 | 2 | 2 + | 8 | 8 | eight | | 0 | 2 | 4 + | 8 | 8 | eight | | 0 | 3 | -3 + | 8 | 8 | eight | | 0 | 5 | -5 + | 8 | 8 | eight | | 0 | 5 | -5 + | 8 | 8 | eight | | 0 | | 0 + | 8 | 8 | eight | | 0 | | + | 8 | 8 | eight | | | 0 | + | 8 | 8 | eight | | | 1 | -1 + | 8 | 8 | eight | | | 2 | 2 + | 8 | 8 | eight | | | 2 | 4 + | 8 | 8 | eight | | | 3 | -3 + | 8 | 8 | eight | | | 5 | -5 + | 8 | 8 | eight | | | 5 | -5 + | 8 | 8 | eight | | | | 0 + | 8 | 8 | eight | | | | + | | 0 | zero | 0 | | 0 | + | | 0 | zero | 0 | | 1 | -1 + | | 0 | zero | 0 | | 2 | 2 + | | 0 | zero | 0 | | 2 | 4 + | | 0 | zero | 0 | | 3 | -3 + | | 0 | zero | 0 | | 5 | -5 + | | 0 | zero | 0 | | 5 | -5 + | | 0 | zero | 0 | | | 0 + | | 0 | zero | 0 | | | + | | 0 | zero | 1 | -1 | 0 | + | | 0 | zero | 1 | -1 | 1 | -1 + | | 0 | zero | 1 | -1 | 2 | 2 + | | 0 | zero | 1 | -1 | 2 | 4 + | | 0 | zero | 1 | -1 | 3 | -3 + | | 0 | zero | 1 | -1 | 5 | -5 + | | 0 | zero | 1 | -1 | 5 | -5 + | | 0 | zero | 1 | -1 | | 0 + | | 0 | zero | 1 | -1 | | + | | 0 | zero | 2 | 2 | 0 | + | | 0 | zero | 2 | 2 | 1 | -1 + | | 0 | zero | 2 | 2 | 2 | 2 + | | 0 | zero | 2 | 2 | 2 | 4 + | | 0 | zero | 2 | 2 | 3 | -3 + | | 0 | zero | 2 | 2 | 5 | -5 + | | 0 | zero | 2 | 2 | 5 | -5 + | | 0 | zero | 2 | 2 | | 0 + | | 0 | zero | 2 | 2 | | + | | 0 | zero | 2 | 4 | 0 | + | | 0 | zero | 2 | 4 | 1 | -1 + | | 0 | zero | 2 | 4 | 2 | 2 + | | 0 | zero | 2 | 4 | 2 | 4 + | | 0 | zero | 2 | 4 | 3 | -3 + | | 0 | zero | 2 | 4 | 5 | -5 + | | 0 | zero | 2 | 4 | 5 | -5 + | | 0 | zero | 2 | 4 | | 0 + | | 0 | zero | 2 | 4 | | + | | 0 | zero | 3 | -3 | 0 | + | | 0 | zero | 3 | -3 | 1 | -1 + | | 0 | zero | 3 | -3 | 2 | 2 + | | 0 | zero | 3 | -3 | 2 | 4 + | | 0 | zero | 3 | -3 | 3 | -3 + | | 0 | zero | 3 | -3 | 5 | -5 + | | 0 | zero | 3 | -3 | 5 | -5 + | | 0 | zero | 3 | -3 | | 0 + | | 0 | zero | 3 | -3 | | + | | 0 | zero | 5 | -5 | 0 | + | | 0 | zero | 5 | -5 | 0 | + | | 0 | zero | 5 | -5 | 1 | -1 + | | 0 | zero | 5 | -5 | 1 | -1 + | | 0 | zero | 5 | -5 | 2 | 2 + | | 0 | zero | 5 | -5 | 2 | 2 + | | 0 | zero | 5 | -5 | 2 | 4 + | | 0 | zero | 5 | -5 | 2 | 4 + | | 0 | zero | 5 | -5 | 3 | -3 + | | 0 | zero | 5 | -5 | 3 | -3 + | | 0 | zero | 5 | -5 | 5 | -5 + | | 0 | zero | 5 | -5 | 5 | -5 + | | 0 | zero | 5 | -5 | 5 | -5 + | | 0 | zero | 5 | -5 | 5 | -5 + | | 0 | zero | 5 | -5 | | 0 + | | 0 | zero | 5 | -5 | | 0 + | | 0 | zero | 5 | -5 | | + | | 0 | zero | 5 | -5 | | + | | 0 | zero | | 0 | 0 | + | | 0 | zero | | 0 | 1 | -1 + | | 0 | zero | | 0 | 2 | 2 + | | 0 | zero | | 0 | 2 | 4 + | | 0 | zero | | 0 | 3 | -3 + | | 0 | zero | | 0 | 5 | -5 + | | 0 | zero | | 0 | 5 | -5 + | | 0 | zero | | 0 | | 0 + | | 0 | zero | | 0 | | + | | 0 | zero | | | 0 | + | | 0 | zero | | | 1 | -1 + | | 0 | zero | | | 2 | 2 + | | 0 | zero | | | 2 | 4 + | | 0 | zero | | | 3 | -3 + | | 0 | zero | | | 5 | -5 + | | 0 | zero | | | 5 | -5 + | | 0 | zero | | | | 0 + | | 0 | zero | | | | + | | | null | 0 | | 0 | + | | | null | 0 | | 1 | -1 + | | | null | 0 | | 2 | 2 + | | | null | 0 | | 2 | 4 + | | | null | 0 | | 3 | -3 + | | | null | 0 | | 5 | -5 + | | | null | 0 | | 5 | -5 + | | | null | 0 | | | 0 + | | | null | 0 | | | + | | | null | 1 | -1 | 0 | + | | | null | 1 | -1 | 1 | -1 + | | | null | 1 | -1 | 2 | 2 + | | | null | 1 | -1 | 2 | 4 + | | | null | 1 | -1 | 3 | -3 + | | | null | 1 | -1 | 5 | -5 + | | | null | 1 | -1 | 5 | -5 + | | | null | 1 | -1 | | 0 + | | | null | 1 | -1 | | + | | | null | 2 | 2 | 0 | + | | | null | 2 | 2 | 1 | -1 + | | | null | 2 | 2 | 2 | 2 + | | | null | 2 | 2 | 2 | 4 + | | | null | 2 | 2 | 3 | -3 + | | | null | 2 | 2 | 5 | -5 + | | | null | 2 | 2 | 5 | -5 + | | | null | 2 | 2 | | 0 + | | | null | 2 | 2 | | + | | | null | 2 | 4 | 0 | + | | | null | 2 | 4 | 1 | -1 + | | | null | 2 | 4 | 2 | 2 + | | | null | 2 | 4 | 2 | 4 + | | | null | 2 | 4 | 3 | -3 + | | | null | 2 | 4 | 5 | -5 + | | | null | 2 | 4 | 5 | -5 + | | | null | 2 | 4 | | 0 + | | | null | 2 | 4 | | + | | | null | 3 | -3 | 0 | + | | | null | 3 | -3 | 1 | -1 + | | | null | 3 | -3 | 2 | 2 + | | | null | 3 | -3 | 2 | 4 + | | | null | 3 | -3 | 3 | -3 + | | | null | 3 | -3 | 5 | -5 + | | | null | 3 | -3 | 5 | -5 + | | | null | 3 | -3 | | 0 + | | | null | 3 | -3 | | + | | | null | 5 | -5 | 0 | + | | | null | 5 | -5 | 0 | + | | | null | 5 | -5 | 1 | -1 + | | | null | 5 | -5 | 1 | -1 + | | | null | 5 | -5 | 2 | 2 + | | | null | 5 | -5 | 2 | 2 + | | | null | 5 | -5 | 2 | 4 + | | | null | 5 | -5 | 2 | 4 + | | | null | 5 | -5 | 3 | -3 + | | | null | 5 | -5 | 3 | -3 + | | | null | 5 | -5 | 5 | -5 + | | | null | 5 | -5 | 5 | -5 + | | | null | 5 | -5 | 5 | -5 + | | | null | 5 | -5 | 5 | -5 + | | | null | 5 | -5 | | 0 + | | | null | 5 | -5 | | 0 + | | | null | 5 | -5 | | + | | | null | 5 | -5 | | + | | | null | | 0 | 0 | + | | | null | | 0 | 1 | -1 + | | | null | | 0 | 2 | 2 + | | | null | | 0 | 2 | 4 + | | | null | | 0 | 3 | -3 + | | | null | | 0 | 5 | -5 + | | | null | | 0 | 5 | -5 + | | | null | | 0 | | 0 + | | | null | | 0 | | + | | | null | | | 0 | + | | | null | | | 1 | -1 + | | | null | | | 2 | 2 + | | | null | | | 2 | 4 + | | | null | | | 3 | -3 + | | | null | | | 5 | -5 + | | | null | | | 5 | -5 + | | | null | | | | 0 + | | | null | | | | +(891 rows) + +-- +-- +-- Inner joins (equi-joins) +-- +-- +-- +-- Inner joins (equi-joins) with USING clause +-- The USING syntax changes the shape of the resulting table +-- by including a column in the USING clause only once in the result. +-- +-- Inner equi-join on specified column +SELECT '' AS "xxx", * + FROM J1_TBL INNER JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + +-- Same as above, slightly different syntax +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, d) USING (a) + ORDER BY a, d; + xxx | a | b | c | d +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, b) USING (b) + ORDER BY b, t1.a; + xxx | b | a | c | a +-----+---+---+-------+--- + | 0 | 5 | five | + | 0 | | zero | + | 2 | 3 | three | 2 + | 4 | 1 | one | 2 +(4 rows) + +-- +-- NATURAL JOIN +-- Inner equi-join on all columns with the same name +-- +SELECT '' AS "xxx", * + FROM J1_TBL NATURAL JOIN J2_TBL order by 1, 2, 3, 4, 5; + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (a, d) order by 1, 2, 3, 4, 5; + xxx | a | b | c | d +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a) order by 1, 2, 3, 4, 5; + xxx | a | b | c | d +-----+---+---+------+--- + | 0 | | zero | + | 2 | 3 | two | 2 + | 4 | 1 | four | 2 +(3 rows) + +-- mismatch number of columns +-- currently, Postgres will fill in with underlying names +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b) NATURAL JOIN J2_TBL t2 (a) order by 1, 2, 3, 4, 5; + xxx | a | b | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 +(7 rows) + +-- +-- Inner joins (equi-joins) +-- +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.i) order by 1, 2, 3, 4, 5, 6; + xxx | i | j | t | i | k +-----+---+---+-------+---+---- + | 0 | | zero | 0 | + | 1 | 4 | one | 1 | -1 + | 2 | 3 | two | 2 | 2 + | 2 | 3 | two | 2 | 4 + | 3 | 2 | three | 3 | -3 + | 5 | 0 | five | 5 | -5 + | 5 | 0 | five | 5 | -5 +(7 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k) order by 1, 2, 3, 4, 5, 6; + xxx | i | j | t | i | k +-----+---+---+------+---+--- + | 0 | | zero | | 0 + | 2 | 3 | two | 2 | 2 + | 4 | 1 | four | 2 | 4 +(3 rows) + +-- +-- Non-equi-joins +-- +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i <= J2_TBL.k) order by 1, 2, 3, 4, 5, 6; + xxx | i | j | t | i | k +-----+---+---+-------+---+--- + | 0 | | zero | 2 | 2 + | 0 | | zero | 2 | 4 + | 0 | | zero | | 0 + | 1 | 4 | one | 2 | 2 + | 1 | 4 | one | 2 | 4 + | 2 | 3 | two | 2 | 2 + | 2 | 3 | two | 2 | 4 + | 3 | 2 | three | 2 | 4 + | 4 | 1 | four | 2 | 4 +(9 rows) + +-- +-- Outer joins +-- Note that OUTER is a noise word +-- +SELECT '' AS "xxx", * + FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i) + ORDER BY i, k, t; + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 4 | 1 | four | + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | 6 | 6 | six | + | 7 | 7 | seven | + | 8 | 8 | eight | + | | | null | + | | 0 | zero | +(13 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL LEFT JOIN J2_TBL USING (i) + ORDER BY i, k, t; + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 4 | 1 | four | + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | 6 | 6 | six | + | 7 | 7 | seven | + | 8 | 8 | eight | + | | | null | + | | 0 | zero | +(13 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | | | | 0 + | | | | +(9 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL RIGHT JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | | | | 0 + | | | | +(9 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i) + ORDER BY i, k, t; + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 4 | 1 | four | + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | 6 | 6 | six | + | 7 | 7 | seven | + | 8 | 8 | eight | + | | | | 0 + | | | null | + | | 0 | zero | + | | | | +(15 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL FULL JOIN J2_TBL USING (i) + ORDER BY i, k, t; + xxx | i | j | t | k +-----+---+---+-------+---- + | 0 | | zero | + | 1 | 4 | one | -1 + | 2 | 3 | two | 2 + | 2 | 3 | two | 4 + | 3 | 2 | three | -3 + | 4 | 1 | four | + | 5 | 0 | five | -5 + | 5 | 0 | five | -5 + | 6 | 6 | six | + | 7 | 7 | seven | + | 8 | 8 | eight | + | | | | 0 + | | | null | + | | 0 | zero | + | | | | +(15 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (k = 1) order by 1, 2, 3, 4, 5; + xxx | i | j | t | k +-----+---+---+---+--- +(0 rows) + +SELECT '' AS "xxx", * + FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (i = 1) order by 1, 2, 3, 4, 5; + xxx | i | j | t | k +-----+---+---+-----+---- + | 1 | 4 | one | -1 +(1 row) + +-- +-- semijoin selectivity for <> +-- +-- explain (costs off) +-- select * from int4_tbl i4, tenk1 a +-- where exists(select * from tenk1 b +-- where a.twothousand = b.twothousand and a.fivethous <> b.fivethous) +-- and i4.f1 = a.tenthous; +-- +-- More complicated constructs +-- +-- +-- Multiway full join +-- +CREATE TABLE t1 (name TEXT, n INTEGER); +CREATE TABLE t2 (name TEXT, n INTEGER); +CREATE TABLE t3 (name TEXT, n INTEGER); +INSERT INTO t1 VALUES ( 'bb', 11 ); +INSERT INTO t2 VALUES ( 'bb', 12 ); +INSERT INTO t2 VALUES ( 'cc', 22 ); +INSERT INTO t2 VALUES ( 'ee', 42 ); +INSERT INTO t3 VALUES ( 'bb', 13 ); +INSERT INTO t3 VALUES ( 'cc', 23 ); +INSERT INTO t3 VALUES ( 'dd', 33 ); +SELECT * FROM t1 FULL JOIN t2 USING (name) FULL JOIN t3 USING (name) order by 1, 2, 3, 4; + name | n | n | n +------+----+----+---- + bb | 11 | 12 | 13 + cc | | 22 | 23 + dd | | | 33 + ee | | 42 | +(4 rows) + +-- +-- Test interactions of join syntax and subqueries +-- +-- Basic cases (we expect planner to pull up the subquery here) +SELECT * FROM +(SELECT * FROM t2) as s2 +INNER JOIN +(SELECT * FROM t3) s3 +USING (name) order by 1, 2, 3; + name | n | n +------+----+---- + bb | 12 | 13 + cc | 22 | 23 +(2 rows) + +SELECT * FROM +(SELECT * FROM t2) as s2 +LEFT JOIN +(SELECT * FROM t3) s3 +USING (name) order by 1, 2, 3; + name | n | n +------+----+---- + bb | 12 | 13 + cc | 22 | 23 + ee | 42 | +(3 rows) + +SELECT * FROM +(SELECT * FROM t2) as s2 +FULL JOIN +(SELECT * FROM t3) s3 +USING (name) order by 1, 2, 3; + name | n | n +------+----+---- + bb | 12 | 13 + cc | 22 | 23 + dd | | 33 + ee | 42 | +(4 rows) + +-- Cases with non-nullable expressions in subquery results; +-- make sure these go to null as expected +SELECT * FROM +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL INNER JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5; + name | s2_n | s2_2 | s3_n | s3_2 +------+------+------+------+------ + bb | 12 | 2 | 13 | 3 + cc | 22 | 2 | 23 | 3 +(2 rows) + +SELECT * FROM +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL LEFT JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5; + name | s2_n | s2_2 | s3_n | s3_2 +------+------+------+------+------ + bb | 12 | 2 | 13 | 3 + cc | 22 | 2 | 23 | 3 + ee | 42 | 2 | | +(3 rows) + +SELECT * FROM +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL FULL JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4; + name | s2_n | s2_2 | s3_n | s3_2 +------+------+------+------+------ + bb | 12 | 2 | 13 | 3 + cc | 22 | 2 | 23 | 3 + dd | | | 33 | 3 + ee | 42 | 2 | | +(4 rows) + +SELECT * FROM +(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1 +NATURAL INNER JOIN +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL INNER JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5, 6, 7; + name | s1_n | s1_1 | s2_n | s2_2 | s3_n | s3_2 +------+------+------+------+------+------+------ + bb | 11 | 1 | 12 | 2 | 13 | 3 +(1 row) + +SELECT * FROM +(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1 +NATURAL FULL JOIN +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL FULL JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5, 6, 7; + name | s1_n | s1_1 | s2_n | s2_2 | s3_n | s3_2 +------+------+------+------+------+------+------ + bb | 11 | 1 | 12 | 2 | 13 | 3 + cc | | | 22 | 2 | 23 | 3 + dd | | | | | 33 | 3 + ee | | | 42 | 2 | | +(4 rows) + +SELECT * FROM +(SELECT name, n as s1_n FROM t1) as s1 +NATURAL FULL JOIN + (SELECT * FROM + (SELECT name, n as s2_n FROM t2) as s2 + NATURAL FULL JOIN + (SELECT name, n as s3_n FROM t3) as s3 + ) ss2 order by 1, 2, 3, 4; + name | s1_n | s2_n | s3_n +------+------+------+------ + bb | 11 | 12 | 13 + cc | | 22 | 23 + dd | | | 33 + ee | | 42 | +(4 rows) + +SELECT * FROM +(SELECT name, n as s1_n FROM t1) as s1 +NATURAL FULL JOIN + (SELECT * FROM + (SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 + NATURAL FULL JOIN + (SELECT name, n as s3_n FROM t3) as s3 + ) ss2 order by 1, 2, 3, 4, 5; + name | s1_n | s2_n | s2_2 | s3_n +------+------+------+------+------ + bb | 11 | 12 | 2 | 13 + cc | | 22 | 2 | 23 + dd | | | | 33 + ee | | 42 | 2 | +(4 rows) + +-- Test for propagation of nullability constraints into sub-joins +create temp table x (x1 int, x2 int); +insert into x values (1,11); +insert into x values (2,22); +insert into x values (3,null); +insert into x values (4,44); +insert into x values (5,null); +create temp table y (y1 int, y2 int); +insert into y values (1,111); +insert into y values (2,222); +insert into y values (3,333); +insert into y values (4,null); +select * from x order by 1, 2; + x1 | x2 +----+---- + 1 | 11 + 2 | 22 + 3 | + 4 | 44 + 5 | +(5 rows) + +select * from y order by 1, 2; + y1 | y2 +----+----- + 1 | 111 + 2 | 222 + 3 | 333 + 4 | +(4 rows) + +select * from x left join y on (x1 = y1 and x2 is not null) order by 1, 2, 3, 4; + x1 | x2 | y1 | y2 +----+----+----+----- + 1 | 11 | 1 | 111 + 2 | 22 | 2 | 222 + 3 | | | + 4 | 44 | 4 | + 5 | | | +(5 rows) + +select * from x left join y on (x1 = y1 and y2 is not null) order by 1, 2, 3, 4; + x1 | x2 | y1 | y2 +----+----+----+----- + 1 | 11 | 1 | 111 + 2 | 22 | 2 | 222 + 3 | | 3 | 333 + 4 | 44 | | + 5 | | | +(5 rows) + +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) order by 1, 2, 3, 4, 5, 6; + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 3 | | 3 | 333 | 3 | + 4 | 44 | 4 | | 4 | 44 + 5 | | | | 5 | +(5 rows) + +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1 and x2 is not null) order by 1, 2, 3, 4, 5, 6; + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 3 | | 3 | 333 | | + 4 | 44 | 4 | | 4 | 44 + 5 | | | | | +(5 rows) + +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1 and y2 is not null) order by 1, 2, 3, 4, 5, 6; + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 3 | | 3 | 333 | 3 | + 4 | 44 | 4 | | | + 5 | | | | | +(5 rows) + +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1 and xx2 is not null) order by 1, 2, 3, 4, 5, 6; + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 3 | | 3 | 333 | | + 4 | 44 | 4 | | 4 | 44 + 5 | | | | | +(5 rows) + +-- these should NOT give the same answers as above +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) where (x2 is not null) order by 1; + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 4 | 44 | 4 | | 4 | 44 +(3 rows) + +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) where (y2 is not null) order by 1; + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 3 | | 3 | 333 | 3 | +(3 rows) + +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) where (xx2 is not null) order by 1; + x1 | x2 | y1 | y2 | xx1 | xx2 +----+----+----+-----+-----+----- + 1 | 11 | 1 | 111 | 1 | 11 + 2 | 22 | 2 | 222 | 2 | 22 + 4 | 44 | 4 | | 4 | 44 +(3 rows) + +-- +-- regression test: check for bug with propagation of implied equality +-- to outside an IN +-- +select count(*) from tenk1 a where unique1 in + (select unique1 from tenk1 b join tenk1 c using (unique1) + where b.unique2 = 42); + count +------- + 1 +(1 row) + +-- +-- regression test: check for failure to generate a plan with multiple +-- degenerate IN clauses +-- +select count(*) from tenk1 x where + x.unique1 in (select a.f1 from int4_tbl a,float8_tbl b where a.f1=b.f1) and + x.unique1 = 0 and + x.unique1 in (select aa.f1 from int4_tbl aa,float8_tbl bb where aa.f1=bb.f1); + count +------- + 1 +(1 row) + +-- try that with GEQO too +begin; +set geqo = on; +set geqo_threshold = 2; +select count(*) from tenk1 x where + x.unique1 in (select a.f1 from int4_tbl a,float8_tbl b where a.f1=b.f1) and + x.unique1 = 0 and + x.unique1 in (select aa.f1 from int4_tbl aa,float8_tbl bb where aa.f1=bb.f1); + count +------- + 1 +(1 row) + +rollback; +-- +-- Test hints given on incorrect column references are useful +-- +select t1.uunique1 from + tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, prefer "t1" suggestion +ERROR: column t1.uunique1 does not exist +LINE 1: select t1.uunique1 from + ^ +HINT: Perhaps you meant to reference the column "t1.unique1". +select t2.uunique1 from + tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, prefer "t2" suggestion +ERROR: column t2.uunique1 does not exist +LINE 1: select t2.uunique1 from + ^ +HINT: Perhaps you meant to reference the column "t2.unique1". +select uunique1 from + tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, suggest both at once +ERROR: column "uunique1" does not exist +LINE 1: select uunique1 from + ^ +HINT: Perhaps you meant to reference the column "t1.unique1" or the column "t2.unique1". diff --git a/src/test/regress/expected/yb.port.jsonb.out b/src/test/regress/expected/yb.port.jsonb.out new file mode 100644 index 0000000000000..41259ed00dc36 --- /dev/null +++ b/src/test/regress/expected/yb.port.jsonb.out @@ -0,0 +1,5519 @@ +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +CREATE TABLE testjsonb ( + j jsonb +); +\set filename :abs_srcdir '/data/jsonb.data' +COPY testjsonb FROM :'filename'; +-- Strings. +SELECT '""'::jsonb; -- OK. + jsonb +------- + "" +(1 row) + +SELECT $$''$$::jsonb; -- ERROR, single quotes are not allowed +ERROR: invalid input syntax for type json +LINE 1: SELECT $$''$$::jsonb; + ^ +DETAIL: Token "'" is invalid. +CONTEXT: JSON data, line 1: '... +SELECT '"abc"'::jsonb; -- OK + jsonb +------- + "abc" +(1 row) + +SELECT '"abc'::jsonb; -- ERROR, quotes not closed +ERROR: invalid input syntax for type json +LINE 1: SELECT '"abc'::jsonb; + ^ +DETAIL: Token ""abc" is invalid. +CONTEXT: JSON data, line 1: "abc +SELECT '"abc +def"'::jsonb; -- ERROR, unescaped newline in string constant +ERROR: invalid input syntax for type json +LINE 1: SELECT '"abc + ^ +DETAIL: Character with value 0x0a must be escaped. +CONTEXT: JSON data, line 1: "abc +SELECT '"\n\"\\"'::jsonb; -- OK, legal escapes + jsonb +---------- + "\n\"\\" +(1 row) + +SELECT '"\v"'::jsonb; -- ERROR, not a valid JSON escape +ERROR: invalid input syntax for type json +LINE 1: SELECT '"\v"'::jsonb; + ^ +DETAIL: Escape sequence "\v" is invalid. +CONTEXT: JSON data, line 1: "\v... +-- see json_encoding test for input with unicode escapes +-- Numbers. +SELECT '1'::jsonb; -- OK + jsonb +------- + 1 +(1 row) + +SELECT '0'::jsonb; -- OK + jsonb +------- + 0 +(1 row) + +SELECT '01'::jsonb; -- ERROR, not valid according to JSON spec +ERROR: invalid input syntax for type json +LINE 1: SELECT '01'::jsonb; + ^ +DETAIL: Token "01" is invalid. +CONTEXT: JSON data, line 1: 01 +SELECT '0.1'::jsonb; -- OK + jsonb +------- + 0.1 +(1 row) + +SELECT '9223372036854775808'::jsonb; -- OK, even though it's too large for int8 + jsonb +--------------------- + 9223372036854775808 +(1 row) + +SELECT '1e100'::jsonb; -- OK + jsonb +------------------------------------------------------------------------------------------------------- + 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +(1 row) + +SELECT '1.3e100'::jsonb; -- OK + jsonb +------------------------------------------------------------------------------------------------------- + 13000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +(1 row) + +SELECT '1f2'::jsonb; -- ERROR +ERROR: invalid input syntax for type json +LINE 1: SELECT '1f2'::jsonb; + ^ +DETAIL: Token "1f2" is invalid. +CONTEXT: JSON data, line 1: 1f2 +SELECT '0.x1'::jsonb; -- ERROR +ERROR: invalid input syntax for type json +LINE 1: SELECT '0.x1'::jsonb; + ^ +DETAIL: Token "0.x1" is invalid. +CONTEXT: JSON data, line 1: 0.x1 +SELECT '1.3ex100'::jsonb; -- ERROR +ERROR: invalid input syntax for type json +LINE 1: SELECT '1.3ex100'::jsonb; + ^ +DETAIL: Token "1.3ex100" is invalid. +CONTEXT: JSON data, line 1: 1.3ex100 +-- Arrays. +SELECT '[]'::jsonb; -- OK + jsonb +------- + [] +(1 row) + +SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::jsonb; -- OK + jsonb +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] +(1 row) + +SELECT '[1,2]'::jsonb; -- OK + jsonb +-------- + [1, 2] +(1 row) + +SELECT '[1,2,]'::jsonb; -- ERROR, trailing comma +ERROR: invalid input syntax for type json +LINE 1: SELECT '[1,2,]'::jsonb; + ^ +DETAIL: Expected JSON value, but found "]". +CONTEXT: JSON data, line 1: [1,2,] +SELECT '[1,2'::jsonb; -- ERROR, no closing bracket +ERROR: invalid input syntax for type json +LINE 1: SELECT '[1,2'::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: [1,2 +SELECT '[1,[2]'::jsonb; -- ERROR, no closing bracket +ERROR: invalid input syntax for type json +LINE 1: SELECT '[1,[2]'::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: [1,[2] +-- Objects. +SELECT '{}'::jsonb; -- OK + jsonb +------- + {} +(1 row) + +SELECT '{"abc"}'::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc"}'::jsonb; + ^ +DETAIL: Expected ":", but found "}". +CONTEXT: JSON data, line 1: {"abc"} +SELECT '{"abc":1}'::jsonb; -- OK + jsonb +------------ + {"abc": 1} +(1 row) + +SELECT '{1:"abc"}'::jsonb; -- ERROR, keys must be strings +ERROR: invalid input syntax for type json +LINE 1: SELECT '{1:"abc"}'::jsonb; + ^ +DETAIL: Expected string or "}", but found "1". +CONTEXT: JSON data, line 1: {1... +SELECT '{"abc",1}'::jsonb; -- ERROR, wrong separator +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc",1}'::jsonb; + ^ +DETAIL: Expected ":", but found ",". +CONTEXT: JSON data, line 1: {"abc",... +SELECT '{"abc"=1}'::jsonb; -- ERROR, totally wrong separator +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc"=1}'::jsonb; + ^ +DETAIL: Token "=" is invalid. +CONTEXT: JSON data, line 1: {"abc"=... +SELECT '{"abc"::1}'::jsonb; -- ERROR, another wrong separator +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc"::1}'::jsonb; + ^ +DETAIL: Expected JSON value, but found ":". +CONTEXT: JSON data, line 1: {"abc"::... +SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::jsonb; -- OK + jsonb +-------------------------------------------------------------------- + {"abc": 1, "def": 2, "ghi": [3, 4], "hij": {"klm": 5, "nop": [6]}} +(1 row) + +SELECT '{"abc":1:2}'::jsonb; -- ERROR, colon in wrong spot +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc":1:2}'::jsonb; + ^ +DETAIL: Expected "," or "}", but found ":". +CONTEXT: JSON data, line 1: {"abc":1:... +SELECT '{"abc":1,3}'::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc":1,3}'::jsonb; + ^ +DETAIL: Expected string, but found "3". +CONTEXT: JSON data, line 1: {"abc":1,3... +-- Recursion. +SET max_stack_depth = '100kB'; +SELECT repeat('[', 10000)::jsonb; +ERROR: stack depth limit exceeded +HINT: Increase the configuration parameter "max_stack_depth" (currently 100kB), after ensuring the platform's stack depth limit is adequate. +SELECT repeat('{"a":', 10000)::jsonb; +ERROR: stack depth limit exceeded +HINT: Increase the configuration parameter "max_stack_depth" (currently 100kB), after ensuring the platform's stack depth limit is adequate. +RESET max_stack_depth; +-- Miscellaneous stuff. +SELECT 'true'::jsonb; -- OK + jsonb +------- + true +(1 row) + +SELECT 'false'::jsonb; -- OK + jsonb +------- + false +(1 row) + +SELECT 'null'::jsonb; -- OK + jsonb +------- + null +(1 row) + +SELECT ' true '::jsonb; -- OK, even with extra whitespace + jsonb +------- + true +(1 row) + +SELECT 'true false'::jsonb; -- ERROR, too many values +ERROR: invalid input syntax for type json +LINE 1: SELECT 'true false'::jsonb; + ^ +DETAIL: Expected end of input, but found "false". +CONTEXT: JSON data, line 1: true false +SELECT 'true, false'::jsonb; -- ERROR, too many values +ERROR: invalid input syntax for type json +LINE 1: SELECT 'true, false'::jsonb; + ^ +DETAIL: Expected end of input, but found ",". +CONTEXT: JSON data, line 1: true,... +SELECT 'truf'::jsonb; -- ERROR, not a keyword +ERROR: invalid input syntax for type json +LINE 1: SELECT 'truf'::jsonb; + ^ +DETAIL: Token "truf" is invalid. +CONTEXT: JSON data, line 1: truf +SELECT 'trues'::jsonb; -- ERROR, not a keyword +ERROR: invalid input syntax for type json +LINE 1: SELECT 'trues'::jsonb; + ^ +DETAIL: Token "trues" is invalid. +CONTEXT: JSON data, line 1: trues +SELECT ''::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT ''::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: +SELECT ' '::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT ' '::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: +-- Multi-line JSON input to check ERROR reporting +SELECT '{ + "one": 1, + "two":"two", + "three": + true}'::jsonb; -- OK + jsonb +----------------------------------------- + {"one": 1, "two": "two", "three": true} +(1 row) + +SELECT '{ + "one": 1, + "two":,"two", -- ERROR extraneous comma before field "two" + "three": + true}'::jsonb; +ERROR: invalid input syntax for type json +LINE 1: SELECT '{ + ^ +DETAIL: Expected JSON value, but found ",". +CONTEXT: JSON data, line 3: "two":,... +SELECT '{ + "one": 1, + "two":"two", + "averyveryveryveryveryveryveryveryveryverylongfieldname":}'::jsonb; +ERROR: invalid input syntax for type json +LINE 1: SELECT '{ + ^ +DETAIL: Expected JSON value, but found "}". +CONTEXT: JSON data, line 4: ...yveryveryveryveryveryveryveryverylongfieldname":} +-- ERROR missing value for last field +-- make sure jsonb is passed through json generators without being escaped +SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']); + array_to_json +-------------------------- + [{"a": 1},{"b": [2, 3]}] +(1 row) + +-- anyarray column +CREATE TEMP TABLE rows AS +SELECT x, 'txt' || x as y +FROM generate_series(1,3) AS x; +analyze rows; +select attname, to_jsonb(histogram_bounds) histogram_bounds +from pg_stats +where tablename = 'rows' and + schemaname = pg_my_temp_schema()::regnamespace::text +order by 1; + attname | histogram_bounds +---------+-------------------------- + x | [1, 2, 3] + y | ["txt1", "txt2", "txt3"] +(2 rows) + +-- to_jsonb, timestamps +select to_jsonb(timestamp '2014-05-28 12:22:35.614298'); + to_jsonb +------------------------------ + "2014-05-28T12:22:35.614298" +(1 row) + +BEGIN; +SET LOCAL TIME ZONE 10.5; +select to_jsonb(timestamptz '2014-05-28 12:22:35.614298-04'); + to_jsonb +------------------------------------ + "2014-05-29T02:52:35.614298+10:30" +(1 row) + +SET LOCAL TIME ZONE -8; +select to_jsonb(timestamptz '2014-05-28 12:22:35.614298-04'); + to_jsonb +------------------------------------ + "2014-05-28T08:22:35.614298-08:00" +(1 row) + +COMMIT; +select to_jsonb(date '2014-05-28'); + to_jsonb +-------------- + "2014-05-28" +(1 row) + +select to_jsonb(date 'Infinity'); + to_jsonb +------------ + "infinity" +(1 row) + +select to_jsonb(date '-Infinity'); + to_jsonb +------------- + "-infinity" +(1 row) + +select to_jsonb(timestamp 'Infinity'); + to_jsonb +------------ + "infinity" +(1 row) + +select to_jsonb(timestamp '-Infinity'); + to_jsonb +------------- + "-infinity" +(1 row) + +select to_jsonb(timestamptz 'Infinity'); + to_jsonb +------------ + "infinity" +(1 row) + +select to_jsonb(timestamptz '-Infinity'); + to_jsonb +------------- + "-infinity" +(1 row) + +--jsonb_agg +SELECT jsonb_agg(q) + FROM ( SELECT $$a$$ || x AS b, y AS c, + ARRAY[ROW(x.*,ARRAY[1,2,3]), + ROW(y.*,ARRAY[4,5,6])] AS z + FROM generate_series(1,2) x, + generate_series(4,5) y) q; + jsonb_agg +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [{"b": "a1", "c": 4, "z": [{"f1": 1, "f2": [1, 2, 3]}, {"f1": 4, "f2": [4, 5, 6]}]}, {"b": "a1", "c": 5, "z": [{"f1": 1, "f2": [1, 2, 3]}, {"f1": 5, "f2": [4, 5, 6]}]}, {"b": "a2", "c": 4, "z": [{"f1": 2, "f2": [1, 2, 3]}, {"f1": 4, "f2": [4, 5, 6]}]}, {"b": "a2", "c": 5, "z": [{"f1": 2, "f2": [1, 2, 3]}, {"f1": 5, "f2": [4, 5, 6]}]}] +(1 row) + +SELECT jsonb_agg(q ORDER BY x, y) + FROM rows q; + jsonb_agg +----------------------------------------------------------------------- + [{"x": 1, "y": "txt1"}, {"x": 2, "y": "txt2"}, {"x": 3, "y": "txt3"}] +(1 row) + +UPDATE rows SET x = NULL WHERE x = 1; +SELECT jsonb_agg(q ORDER BY x NULLS FIRST, y) + FROM rows q; + jsonb_agg +-------------------------------------------------------------------------- + [{"x": null, "y": "txt1"}, {"x": 2, "y": "txt2"}, {"x": 3, "y": "txt3"}] +(1 row) + +-- jsonb extraction functions +CREATE TEMP TABLE test_jsonb ( + json_type text, + test_json jsonb +); +INSERT INTO test_jsonb VALUES +('scalar','"a scalar"'), +('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'), +('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}'); +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar'; + ?column? +---------- + +(1 row) + +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array'; + ?column? +---------- + +(1 row) + +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + +(1 row) + +SELECT test_json -> 'field2' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + "val2" +(1 row) + +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'scalar'; + ?column? +---------- + +(1 row) + +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'array'; + ?column? +---------- + +(1 row) + +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + val2 +(1 row) + +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'scalar'; + ?column? +---------- + +(1 row) + +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'array'; + ?column? +---------- + "two" +(1 row) + +SELECT test_json -> 9 FROM test_jsonb WHERE json_type = 'array'; + ?column? +---------- + +(1 row) + +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + +(1 row) + +SELECT test_json ->> 6 FROM test_jsonb WHERE json_type = 'array'; + ?column? +----------- + [1, 2, 3] +(1 row) + +SELECT test_json ->> 7 FROM test_jsonb WHERE json_type = 'array'; + ?column? +----------- + {"f1": 9} +(1 row) + +SELECT test_json ->> 'field4' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + 4 +(1 row) + +SELECT test_json ->> 'field5' FROM test_jsonb WHERE json_type = 'object'; + ?column? +----------- + [1, 2, 3] +(1 row) + +SELECT test_json ->> 'field6' FROM test_jsonb WHERE json_type = 'object'; + ?column? +----------- + {"f1": 9} +(1 row) + +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'scalar'; + ?column? +---------- + +(1 row) + +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'array'; + ?column? +---------- + two +(1 row) + +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + +(1 row) + +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'scalar'; +ERROR: cannot call jsonb_object_keys on a scalar +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'array'; +ERROR: cannot call jsonb_object_keys on an array +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'object'; + jsonb_object_keys +------------------- + field1 + field2 + field3 + field4 + field5 + field6 +(6 rows) + +-- nulls +SELECT (test_json->'field3') IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'object'; + expect_false +-------------- + f +(1 row) + +SELECT (test_json->>'field3') IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'object'; + expect_true +------------- + t +(1 row) + +SELECT (test_json->3) IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'array'; + expect_false +-------------- + f +(1 row) + +SELECT (test_json->>3) IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'array'; + expect_true +------------- + t +(1 row) + +-- corner cases +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> null::text; + ?column? +---------- + +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> null::int; + ?column? +---------- + +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> 1; + ?column? +---------- + +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> 'z'; + ?column? +---------- + +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> ''; + ?column? +---------- + +(1 row) + +select '[{"b": "c"}, {"b": "cc"}]'::jsonb -> 1; + ?column? +------------- + {"b": "cc"} +(1 row) + +select '[{"b": "c"}, {"b": "cc"}]'::jsonb -> 3; + ?column? +---------- + +(1 row) + +select '[{"b": "c"}, {"b": "cc"}]'::jsonb -> 'z'; + ?column? +---------- + +(1 row) + +select '{"a": "c", "b": null}'::jsonb -> 'b'; + ?column? +---------- + null +(1 row) + +select '"foo"'::jsonb -> 1; + ?column? +---------- + +(1 row) + +select '"foo"'::jsonb -> 'z'; + ?column? +---------- + +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb ->> null::text; + ?column? +---------- + +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb ->> null::int; + ?column? +---------- + +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb ->> 1; + ?column? +---------- + +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb ->> 'z'; + ?column? +---------- + +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb ->> ''; + ?column? +---------- + +(1 row) + +select '[{"b": "c"}, {"b": "cc"}]'::jsonb ->> 1; + ?column? +------------- + {"b": "cc"} +(1 row) + +select '[{"b": "c"}, {"b": "cc"}]'::jsonb ->> 3; + ?column? +---------- + +(1 row) + +select '[{"b": "c"}, {"b": "cc"}]'::jsonb ->> 'z'; + ?column? +---------- + +(1 row) + +select '{"a": "c", "b": null}'::jsonb ->> 'b'; + ?column? +---------- + +(1 row) + +select '"foo"'::jsonb ->> 1; + ?column? +---------- + +(1 row) + +select '"foo"'::jsonb ->> 'z'; + ?column? +---------- + +(1 row) + +-- equality and inequality +SELECT '{"x":"y"}'::jsonb = '{"x":"y"}'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT '{"x":"y"}'::jsonb = '{"x":"z"}'::jsonb; + ?column? +---------- + f +(1 row) + +SELECT '{"x":"y"}'::jsonb <> '{"x":"y"}'::jsonb; + ?column? +---------- + f +(1 row) + +SELECT '{"x":"y"}'::jsonb <> '{"x":"z"}'::jsonb; + ?column? +---------- + t +(1 row) + +-- containment +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); + jsonb_contains +---------------- + t +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":null}'); + jsonb_contains +---------------- + t +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "g":null}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"g":null}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"c"}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); + jsonb_contains +---------------- + t +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":"q"}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "g":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"g":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}'; + ?column? +---------- + f +(1 row) + +SELECT '[1,2]'::jsonb @> '[1,2,2]'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT '[1,1,2]'::jsonb @> '[1,2,2]'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT '[[1,2]]'::jsonb @> '[[1,2,2]]'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT '[1,2,2]'::jsonb <@ '[1,2]'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT '[1,2,2]'::jsonb <@ '[1,1,2]'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT '[[1,2,2]]'::jsonb <@ '[[1,2]]'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + t +(1 row) + +SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + t +(1 row) + +SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT jsonb_contained('{"g":null}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT jsonb_contained('{"a":"c"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + t +(1 row) + +SELECT jsonb_contained('{"a":"b", "c":"q"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "c":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"c"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "c":"q"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +-- Raw scalar may contain another raw scalar, array may contain a raw scalar +SELECT '[5]'::jsonb @> '[5]'; + ?column? +---------- + t +(1 row) + +SELECT '5'::jsonb @> '5'; + ?column? +---------- + t +(1 row) + +SELECT '[5]'::jsonb @> '5'; + ?column? +---------- + t +(1 row) + +-- But a raw scalar cannot contain an array +SELECT '5'::jsonb @> '[5]'; + ?column? +---------- + f +(1 row) + +-- In general, one thing should always contain itself. Test array containment: +SELECT '["9", ["7", "3"], 1]'::jsonb @> '["9", ["7", "3"], 1]'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT '["9", ["7", "3"], ["1"]]'::jsonb @> '["9", ["7", "3"], ["1"]]'::jsonb; + ?column? +---------- + t +(1 row) + +-- array containment string matching confusion bug +SELECT '{ "name": "Bob", "tags": [ "enim", "qui"]}'::jsonb @> '{"tags":["qu"]}'; + ?column? +---------- + f +(1 row) + +-- array length +SELECT jsonb_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]'); + jsonb_array_length +-------------------- + 5 +(1 row) + +SELECT jsonb_array_length('[]'); + jsonb_array_length +-------------------- + 0 +(1 row) + +SELECT jsonb_array_length('{"f1":1,"f2":[5,6]}'); +ERROR: cannot get array length of a non-array +SELECT jsonb_array_length('4'); +ERROR: cannot get array length of a scalar +-- each +SELECT jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}'); + jsonb_each +-------------------- + (f1,"[1, 2, 3]") + (f2,"{""f3"": 1}") + (f4,null) +(3 rows) + +SELECT jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + q +------------------------------------------------------ + (1,"""first""") + (a,"{""1"": ""first"", ""b"": ""c"", ""c"": ""b""}") + (b,"[1, 2]") + (c,"""cc""") + (n,null) +(5 rows) + +SELECT * FROM jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q; + key | value +-----+----------- + f1 | [1, 2, 3] + f2 | {"f3": 1} + f4 | null + f5 | 99 + f6 | "stringy" +(5 rows) + +SELECT * FROM jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + key | value +-----+------------------------------------ + 1 | "first" + a | {"1": "first", "b": "c", "c": "b"} + b | [1, 2] + c | "cc" + n | null +(5 rows) + +SELECT jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}'); + jsonb_each_text +-------------------- + (f1,"[1, 2, 3]") + (f2,"{""f3"": 1}") + (f4,) + (f5,null) +(4 rows) + +SELECT jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + q +------------------------------------------------------ + (1,first) + (a,"{""1"": ""first"", ""b"": ""c"", ""c"": ""b""}") + (b,"[1, 2]") + (c,cc) + (n,) +(5 rows) + +SELECT * FROM jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q; + key | value +-----+----------- + f1 | [1, 2, 3] + f2 | {"f3": 1} + f4 | + f5 | 99 + f6 | stringy +(5 rows) + +SELECT * FROM jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + key | value +-----+------------------------------------ + 1 | first + a | {"1": "first", "b": "c", "c": "b"} + b | [1, 2] + c | cc + n | +(5 rows) + +-- exists +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'a'); + jsonb_exists +-------------- + t +(1 row) + +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'b'); + jsonb_exists +-------------- + t +(1 row) + +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'c'); + jsonb_exists +-------------- + f +(1 row) + +SELECT jsonb_exists('{"a":"null", "b":"qq"}', 'a'); + jsonb_exists +-------------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ? 'a'; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ? 'b'; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ? 'c'; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":"null", "b":"qq"}' ? 'a'; + ?column? +---------- + t +(1 row) + +-- array exists - array elements should behave as keys +SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; + count +------- + 3 +(1 row) + +-- type sensitive array exists - should return no rows (since "exists" only +-- matches strings that are either object keys or array elements) +SELECT count(*) from testjsonb WHERE j->'array' ? '5'::text; + count +------- + 0 +(1 row) + +-- However, a raw scalar is *contained* within the array +SELECT count(*) from testjsonb WHERE j->'array' @> '5'::jsonb; + count +------- + 1 +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['a','b']); + jsonb_exists_any +------------------ + t +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['b','a']); + jsonb_exists_any +------------------ + t +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','a']); + jsonb_exists_any +------------------ + t +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','d']); + jsonb_exists_any +------------------ + f +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', '{}'::text[]); + jsonb_exists_any +------------------ + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['a','b']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['b','a']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','a']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','d']; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| '{}'::text[]; + ?column? +---------- + f +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['a','b']); + jsonb_exists_all +------------------ + t +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['b','a']); + jsonb_exists_all +------------------ + t +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','a']); + jsonb_exists_all +------------------ + f +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','d']); + jsonb_exists_all +------------------ + f +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', '{}'::text[]); + jsonb_exists_all +------------------ + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','b']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['b','a']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','a']; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','d']; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','a', 'b', 'b', 'b']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& '{}'::text[]; + ?column? +---------- + t +(1 row) + +-- typeof +SELECT jsonb_typeof('{}') AS object; + object +-------- + object +(1 row) + +SELECT jsonb_typeof('{"c":3,"p":"o"}') AS object; + object +-------- + object +(1 row) + +SELECT jsonb_typeof('[]') AS array; + array +------- + array +(1 row) + +SELECT jsonb_typeof('["a", 1]') AS array; + array +------- + array +(1 row) + +SELECT jsonb_typeof('null') AS "null"; + null +------ + null +(1 row) + +SELECT jsonb_typeof('1') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('-1') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('1.0') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('1e2') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('-1.0') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('true') AS boolean; + boolean +--------- + boolean +(1 row) + +SELECT jsonb_typeof('false') AS boolean; + boolean +--------- + boolean +(1 row) + +SELECT jsonb_typeof('"hello"') AS string; + string +-------- + string +(1 row) + +SELECT jsonb_typeof('"true"') AS string; + string +-------- + string +(1 row) + +SELECT jsonb_typeof('"1.0"') AS string; + string +-------- + string +(1 row) + +-- jsonb_build_array, jsonb_build_object, jsonb_object_agg +SELECT jsonb_build_array('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}'); + jsonb_build_array +------------------------------------------------------------------------- + ["a", 1, "b", 1.2, "c", true, "d", null, "e", {"x": 3, "y": [1, 2, 3]}] +(1 row) + +SELECT jsonb_build_array('a', NULL); -- ok + jsonb_build_array +------------------- + ["a", null] +(1 row) + +SELECT jsonb_build_array(VARIADIC NULL::text[]); -- ok + jsonb_build_array +------------------- + +(1 row) + +SELECT jsonb_build_array(VARIADIC '{}'::text[]); -- ok + jsonb_build_array +------------------- + [] +(1 row) + +SELECT jsonb_build_array(VARIADIC '{a,b,c}'::text[]); -- ok + jsonb_build_array +------------------- + ["a", "b", "c"] +(1 row) + +SELECT jsonb_build_array(VARIADIC ARRAY['a', NULL]::text[]); -- ok + jsonb_build_array +------------------- + ["a", null] +(1 row) + +SELECT jsonb_build_array(VARIADIC '{1,2,3,4}'::text[]); -- ok + jsonb_build_array +---------------------- + ["1", "2", "3", "4"] +(1 row) + +SELECT jsonb_build_array(VARIADIC '{1,2,3,4}'::int[]); -- ok + jsonb_build_array +------------------- + [1, 2, 3, 4] +(1 row) + +SELECT jsonb_build_array(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok + jsonb_build_array +-------------------- + [1, 4, 2, 5, 3, 6] +(1 row) + +SELECT jsonb_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}'); + jsonb_build_object +------------------------------------------------------------------------- + {"a": 1, "b": 1.2, "c": true, "d": null, "e": {"x": 3, "y": [1, 2, 3]}} +(1 row) + +SELECT jsonb_build_object( + 'a', jsonb_build_object('b',false,'c',99), + 'd', jsonb_build_object('e',array[9,8,7]::int[], + 'f', (select row_to_json(r) from ( select relkind, oid::regclass as name from pg_class where relname = 'pg_class') r))); + jsonb_build_object +------------------------------------------------------------------------------------------------ + {"a": {"b": false, "c": 99}, "d": {"e": [9, 8, 7], "f": {"name": "pg_class", "relkind": "r"}}} +(1 row) + +SELECT jsonb_build_object('{a,b,c}'::text[]); -- error +ERROR: argument list must have even number of elements +HINT: The arguments of jsonb_build_object() must consist of alternating keys and values. +SELECT jsonb_build_object('{a,b,c}'::text[], '{d,e,f}'::text[]); -- error, key cannot be array +ERROR: key value must be scalar, not array, composite, or json +SELECT jsonb_build_object('a', 'b', 'c'); -- error +ERROR: argument list must have even number of elements +HINT: The arguments of jsonb_build_object() must consist of alternating keys and values. +SELECT jsonb_build_object(NULL, 'a'); -- error, key cannot be NULL +ERROR: argument 1: key must not be null +SELECT jsonb_build_object('a', NULL); -- ok + jsonb_build_object +-------------------- + {"a": null} +(1 row) + +SELECT jsonb_build_object(VARIADIC NULL::text[]); -- ok + jsonb_build_object +-------------------- + +(1 row) + +SELECT jsonb_build_object(VARIADIC '{}'::text[]); -- ok + jsonb_build_object +-------------------- + {} +(1 row) + +SELECT jsonb_build_object(VARIADIC '{a,b,c}'::text[]); -- error +ERROR: argument list must have even number of elements +HINT: The arguments of jsonb_build_object() must consist of alternating keys and values. +SELECT jsonb_build_object(VARIADIC ARRAY['a', NULL]::text[]); -- ok + jsonb_build_object +-------------------- + {"a": null} +(1 row) + +SELECT jsonb_build_object(VARIADIC ARRAY[NULL, 'a']::text[]); -- error, key cannot be NULL +ERROR: argument 1: key must not be null +SELECT jsonb_build_object(VARIADIC '{1,2,3,4}'::text[]); -- ok + jsonb_build_object +---------------------- + {"1": "2", "3": "4"} +(1 row) + +SELECT jsonb_build_object(VARIADIC '{1,2,3,4}'::int[]); -- ok + jsonb_build_object +-------------------- + {"1": 2, "3": 4} +(1 row) + +SELECT jsonb_build_object(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok + jsonb_build_object +-------------------------- + {"1": 4, "2": 5, "3": 6} +(1 row) + +-- empty objects/arrays +SELECT jsonb_build_array(); + jsonb_build_array +------------------- + [] +(1 row) + +SELECT jsonb_build_object(); + jsonb_build_object +-------------------- + {} +(1 row) + +-- make sure keys are quoted +SELECT jsonb_build_object(1,2); + jsonb_build_object +-------------------- + {"1": 2} +(1 row) + +-- keys must be scalar and not null +SELECT jsonb_build_object(null,2); +ERROR: argument 1: key must not be null +SELECT jsonb_build_object(r,2) FROM (SELECT 1 AS a, 2 AS b) r; +ERROR: key value must be scalar, not array, composite, or json +SELECT jsonb_build_object(json '{"a":1,"b":2}', 3); +ERROR: key value must be scalar, not array, composite, or json +SELECT jsonb_build_object('{1,2,3}'::int[], 3); +ERROR: key value must be scalar, not array, composite, or json +-- handling of NULL values +SELECT jsonb_object_agg(1, NULL::jsonb); + jsonb_object_agg +------------------ + {"1": null} +(1 row) + +SELECT jsonb_object_agg(NULL, '{"a":1}'); +ERROR: field name must not be null +CREATE TEMP TABLE foo (serial_num int, name text, type text); +INSERT INTO foo VALUES (847001,'t15','GE1043'); +INSERT INTO foo VALUES (847002,'t16','GE1043'); +INSERT INTO foo VALUES (847003,'sub-alpha','GESS90'); +SELECT jsonb_build_object('turbines',jsonb_object_agg(serial_num,jsonb_build_object('name',name,'type',type))) +FROM foo; + jsonb_build_object +------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"turbines": {"847001": {"name": "t15", "type": "GE1043"}, "847002": {"name": "t16", "type": "GE1043"}, "847003": {"name": "sub-alpha", "type": "GESS90"}}} +(1 row) + +SELECT jsonb_object_agg(name, type) FROM foo; + jsonb_object_agg +----------------------------------------------------------- + {"t15": "GE1043", "t16": "GE1043", "sub-alpha": "GESS90"} +(1 row) + +INSERT INTO foo VALUES (999999, NULL, 'bar'); +SELECT jsonb_object_agg(name, type) FROM foo; +ERROR: field name must not be null +-- edge case for parser +SELECT jsonb_object_agg(DISTINCT 'a', 'abc'); + jsonb_object_agg +------------------ + {"a": "abc"} +(1 row) + +-- jsonb_object +-- empty object, one dimension +SELECT jsonb_object('{}'); + jsonb_object +-------------- + {} +(1 row) + +-- empty object, two dimensions +SELECT jsonb_object('{}', '{}'); + jsonb_object +-------------- + {} +(1 row) + +-- one dimension +SELECT jsonb_object('{a,1,b,2,3,NULL,"d e f","a b c"}'); + jsonb_object +--------------------------------------------------- + {"3": null, "a": "1", "b": "2", "d e f": "a b c"} +(1 row) + +-- same but with two dimensions +SELECT jsonb_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}'); + jsonb_object +--------------------------------------------------- + {"3": null, "a": "1", "b": "2", "d e f": "a b c"} +(1 row) + +-- odd number error +SELECT jsonb_object('{a,b,c}'); +ERROR: array must have even number of elements +-- one column error +SELECT jsonb_object('{{a},{b}}'); +ERROR: array must have two columns +-- too many columns error +SELECT jsonb_object('{{a,b,c},{b,c,d}}'); +ERROR: array must have two columns +-- too many dimensions error +SELECT jsonb_object('{{{a,b},{c,d}},{{b,c},{d,e}}}'); +ERROR: wrong number of array subscripts +--two argument form of jsonb_object +select jsonb_object('{a,b,c,"d e f"}','{1,2,3,"a b c"}'); + jsonb_object +-------------------------------------------------- + {"a": "1", "b": "2", "c": "3", "d e f": "a b c"} +(1 row) + +-- too many dimensions +SELECT jsonb_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}', '{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}'); +ERROR: wrong number of array subscripts +-- mismatched dimensions +select jsonb_object('{a,b,c,"d e f",g}','{1,2,3,"a b c"}'); +ERROR: mismatched array dimensions +select jsonb_object('{a,b,c,"d e f"}','{1,2,3,"a b c",g}'); +ERROR: mismatched array dimensions +-- null key error +select jsonb_object('{a,b,NULL,"d e f"}','{1,2,3,"a b c"}'); +ERROR: null value not allowed for object key +-- empty key is allowed +select jsonb_object('{a,b,"","d e f"}','{1,2,3,"a b c"}'); + jsonb_object +------------------------------------------------- + {"": "3", "a": "1", "b": "2", "d e f": "a b c"} +(1 row) + +-- extract_path, extract_path_as_text +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); + jsonb_extract_path +-------------------- + "stringy" +(1 row) + +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); + jsonb_extract_path +-------------------- + {"f3": 1} +(1 row) + +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); + jsonb_extract_path +-------------------- + "f3" +(1 row) + +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); + jsonb_extract_path +-------------------- + 1 +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); + jsonb_extract_path_text +------------------------- + stringy +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); + jsonb_extract_path_text +------------------------- + {"f3": 1} +(1 row) + +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); + jsonb_extract_path_text +------------------------- + f3 +(1 row) + +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); + jsonb_extract_path_text +------------------------- + 1 +(1 row) + +-- extract_path nulls +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_false; + expect_false +-------------- + f +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_true; + expect_true +------------- + t +(1 row) + +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_false; + expect_false +-------------- + f +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_true; + expect_true +------------- + t +(1 row) + +-- extract_path operators +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f4','f6']; + ?column? +----------- + "stringy" +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2']; + ?column? +----------- + {"f3": 1} +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','0']; + ?column? +---------- + "f3" +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','1']; + ?column? +---------- + 1 +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f4','f6']; + ?column? +---------- + stringy +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2']; + ?column? +----------- + {"f3": 1} +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','0']; + ?column? +---------- + f3 +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','1']; + ?column? +---------- + 1 +(1 row) + +-- corner cases for same +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> '{}'; + ?column? +---------------------------- + {"a": {"b": {"c": "foo"}}} +(1 row) + +select '[1,2,3]'::jsonb #> '{}'; + ?column? +----------- + [1, 2, 3] +(1 row) + +select '"foo"'::jsonb #> '{}'; + ?column? +---------- + "foo" +(1 row) + +select '42'::jsonb #> '{}'; + ?column? +---------- + 42 +(1 row) + +select 'null'::jsonb #> '{}'; + ?column? +---------- + null +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a']; + ?column? +--------------------- + {"b": {"c": "foo"}} +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a', null]; + ?column? +---------- + +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a', '']; + ?column? +---------- + +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b']; + ?column? +-------------- + {"c": "foo"} +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b','c']; + ?column? +---------- + "foo" +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b','c','d']; + ?column? +---------- + +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','z','c']; + ?column? +---------- + +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #> array['a','1','b']; + ?column? +---------- + "cc" +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #> array['a','z','b']; + ?column? +---------- + +(1 row) + +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #> array['1','b']; + ?column? +---------- + "cc" +(1 row) + +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #> array['z','b']; + ?column? +---------- + +(1 row) + +select '[{"b": "c"}, {"b": null}]'::jsonb #> array['1','b']; + ?column? +---------- + null +(1 row) + +select '"foo"'::jsonb #> array['z']; + ?column? +---------- + +(1 row) + +select '42'::jsonb #> array['f2']; + ?column? +---------- + +(1 row) + +select '42'::jsonb #> array['0']; + ?column? +---------- + +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> '{}'; + ?column? +---------------------------- + {"a": {"b": {"c": "foo"}}} +(1 row) + +select '[1,2,3]'::jsonb #>> '{}'; + ?column? +----------- + [1, 2, 3] +(1 row) + +select '"foo"'::jsonb #>> '{}'; + ?column? +---------- + foo +(1 row) + +select '42'::jsonb #>> '{}'; + ?column? +---------- + 42 +(1 row) + +select 'null'::jsonb #>> '{}'; + ?column? +---------- + +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a']; + ?column? +--------------------- + {"b": {"c": "foo"}} +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a', null]; + ?column? +---------- + +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a', '']; + ?column? +---------- + +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b']; + ?column? +-------------- + {"c": "foo"} +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b','c']; + ?column? +---------- + foo +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b','c','d']; + ?column? +---------- + +(1 row) + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','z','c']; + ?column? +---------- + +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #>> array['a','1','b']; + ?column? +---------- + cc +(1 row) + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #>> array['a','z','b']; + ?column? +---------- + +(1 row) + +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #>> array['1','b']; + ?column? +---------- + cc +(1 row) + +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #>> array['z','b']; + ?column? +---------- + +(1 row) + +select '[{"b": "c"}, {"b": null}]'::jsonb #>> array['1','b']; + ?column? +---------- + +(1 row) + +select '"foo"'::jsonb #>> array['z']; + ?column? +---------- + +(1 row) + +select '42'::jsonb #>> array['f2']; + ?column? +---------- + +(1 row) + +select '42'::jsonb #>> array['0']; + ?column? +---------- + +(1 row) + +-- array_elements +SELECT jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]'); + jsonb_array_elements +---------------------------- + 1 + true + [1, [2, 3]] + null + {"f1": 1, "f2": [7, 8, 9]} + false +(6 rows) + +SELECT * FROM jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q; + value +---------------------------- + 1 + true + [1, [2, 3]] + null + {"f1": 1, "f2": [7, 8, 9]} + false +(6 rows) + +SELECT jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]'); + jsonb_array_elements_text +---------------------------- + 1 + true + [1, [2, 3]] + + {"f1": 1, "f2": [7, 8, 9]} + false + stringy +(7 rows) + +SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q; + value +---------------------------- + 1 + true + [1, [2, 3]] + + {"f1": 1, "f2": [7, 8, 9]} + false + stringy +(7 rows) + +-- populate_record +CREATE TYPE jbpop AS (a text, b int, c timestamp); +CREATE DOMAIN jsb_int_not_null AS int NOT NULL; +CREATE DOMAIN jsb_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3); +CREATE DOMAIN jsb_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3); +create type jb_unordered_pair as (x int, y int); +create domain jb_ordered_pair as jb_unordered_pair check((value).x <= (value).y); +CREATE TYPE jsbrec AS ( + i int, + ia _int4, + ia1 int[], + ia2 int[][], + ia3 int[][][], + ia1d jsb_int_array_1d, + ia2d jsb_int_array_2d, + t text, + ta text[], + c char(10), + ca char(10)[], + ts timestamp, + js json, + jsb jsonb, + jsa json[], + rec jbpop, + reca jbpop[] +); +CREATE TYPE jsbrec_i_not_null AS ( + i jsb_int_not_null +); +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q; + a | b | c +--------+---+--- + blurfl | | +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q; + a | b | c +--------+---+-------------------------- + blurfl | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q; + a | b | c +--------+---+--- + blurfl | | +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q; + a | b | c +--------+---+-------------------------- + blurfl | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":[100,200,false],"x":43.2}') q; + a | b | c +-------------------+---+--- + [100, 200, false] | | +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":[100,200,false],"x":43.2}') q; + a | b | c +-------------------+---+-------------------------- + [100, 200, false] | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}') q; +ERROR: invalid input syntax for type timestamp: "[100, 200, false]" +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop, '{}') q; + a | b | c +---+---+-------------------------- + x | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"x": 43.2}') q; +ERROR: domain jsb_int_not_null does not allow null values +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": null}') q; +ERROR: domain jsb_int_not_null does not allow null values +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": 12345}') q; + i +------- + 12345 +(1 row) + +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": null}') q; + ia +---- + +(1 row) + +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": 123}') q; +ERROR: expected JSON array +HINT: See the value of key "ia". +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [1, "2", null, 4]}') q; + ia +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1, 2], [3, 4]]}') q; + ia +--------------- + {{1,2},{3,4}} +(1 row) + +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], 2]}') q; +ERROR: expected JSON array +HINT: See the array element [1] of key "ia". +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], [2, 3]]}') q; +ERROR: malformed JSON array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": "{1,2,3}"}') q; + ia +--------- + {1,2,3} +(1 row) + +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": null}') q; + ia1 +----- + +(1 row) + +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": 123}') q; +ERROR: expected JSON array +HINT: See the value of key "ia1". +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": [1, "2", null, 4]}') q; + ia1 +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": [[1, 2, 3]]}') q; + ia1 +----------- + {{1,2,3}} +(1 row) + +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": null}') q; + ia1d +------ + +(1 row) + +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": 123}') q; +ERROR: expected JSON array +HINT: See the value of key "ia1d". +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": [1, "2", null, 4]}') q; +ERROR: value for domain jsb_int_array_1d violates check constraint "jsb_int_array_1d_check" +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": [1, "2", null]}') q; + ia1d +------------ + {1,2,NULL} +(1 row) + +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [1, "2", null, 4]}') q; + ia2 +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], [null, 4]]}') q; + ia2 +------------------ + {{1,2},{NULL,4}} +(1 row) + +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[], []]}') q; + ia2 +----- + {} +(1 row) + +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], [3]]}') q; +ERROR: malformed JSON array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], 3, 4]}') q; +ERROR: expected JSON array +HINT: See the array element [1] of key "ia2". +SELECT ia2d FROM jsonb_populate_record(NULL::jsbrec, '{"ia2d": [[1, "2"], [null, 4]]}') q; +ERROR: value for domain jsb_int_array_2d violates check constraint "jsb_int_array_2d_check" +SELECT ia2d FROM jsonb_populate_record(NULL::jsbrec, '{"ia2d": [[1, "2", 3], [null, 5, 6]]}') q; + ia2d +---------------------- + {{1,2,3},{NULL,5,6}} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [1, "2", null, 4]}') q; + ia3 +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [[1, 2], [null, 4]]}') q; + ia3 +------------------ + {{1,2},{NULL,4}} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[], []], [[], []], [[], []] ]}') q; + ia3 +----- + {} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2]], [[3, 4]] ]}') q; + ia3 +------------------- + {{{1,2}},{{3,4}}} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8]] ]}') q; + ia3 +------------------------------- + {{{1,2},{3,4}},{{5,6},{7,8}}} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8], [9, 10]] ]}') q; +ERROR: malformed JSON array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": null}') q; + ta +---- + +(1 row) + +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": 123}') q; +ERROR: expected JSON array +HINT: See the value of key "ta". +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": [1, "2", null, 4]}') q; + ta +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": [[1, 2, 3], {"k": "v"}]}') q; +ERROR: expected JSON array +HINT: See the array element [1] of key "ta". +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": null}') q; + c +--- + +(1 row) + +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaa"}') q; + c +------------ + aaa +(1 row) + +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaaaaaaaaa"}') q; + c +------------ + aaaaaaaaaa +(1 row) + +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaaaaaaaaaaaa"}') q; +ERROR: value too long for type character(10) +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": null}') q; + ca +---- + +(1 row) + +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": 123}') q; +ERROR: expected JSON array +HINT: See the value of key "ca". +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": [1, "2", null, 4]}') q; + ca +----------------------------------------------- + {"1 ","2 ",NULL,"4 "} +(1 row) + +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": ["aaaaaaaaaaaaaaaa"]}') q; +ERROR: value too long for type character(10) +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": [[1, 2, 3], {"k": "v"}]}') q; +ERROR: expected JSON array +HINT: See the array element [1] of key "ca". +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": null}') q; + js +---- + +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": true}') q; + js +------ + true +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": 123.45}') q; + js +-------- + 123.45 +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": "123.45"}') q; + js +---------- + "123.45" +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": "abc"}') q; + js +------- + "abc" +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": [123, "123", null, {"key": "value"}]}') q; + js +-------------------------------------- + [123, "123", null, {"key": "value"}] +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": {"a": "bbb", "b": null, "c": 123.45}}') q; + js +-------------------------------------- + {"a": "bbb", "b": null, "c": 123.45} +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": null}') q; + jsb +----- + +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": true}') q; + jsb +------ + true +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": 123.45}') q; + jsb +-------- + 123.45 +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": "123.45"}') q; + jsb +---------- + "123.45" +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": "abc"}') q; + jsb +------- + "abc" +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": [123, "123", null, {"key": "value"}]}') q; + jsb +-------------------------------------- + [123, "123", null, {"key": "value"}] +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": {"a": "bbb", "b": null, "c": 123.45}}') q; + jsb +-------------------------------------- + {"a": "bbb", "b": null, "c": 123.45} +(1 row) + +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": null}') q; + jsa +----- + +(1 row) + +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": 123}') q; +ERROR: expected JSON array +HINT: See the value of key "jsa". +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": [1, "2", null, 4]}') q; + jsa +-------------------- + {1,"\"2\"",NULL,4} +(1 row) + +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": ["aaa", null, [1, 2, "3", {}], { "k" : "v" }]}') q; + jsa +------------------------------------------------------- + {"\"aaa\"",NULL,"[1, 2, \"3\", {}]","{\"k\": \"v\"}"} +(1 row) + +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": 123}') q; +ERROR: cannot call populate_composite on a scalar +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": [1, 2]}') q; +ERROR: cannot call populate_composite on an array +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}') q; + rec +----------------------------------- + (abc,,"Thu Jan 02 00:00:00 2003") +(1 row) + +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": "(abc,42,01.02.2003)"}') q; + rec +------------------------------------- + (abc,42,"Thu Jan 02 00:00:00 2003") +(1 row) + +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": 123}') q; +ERROR: expected JSON array +HINT: See the value of key "reca". +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": [1, 2]}') q; +ERROR: cannot call populate_composite on a scalar +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}]}') q; + reca +-------------------------------------------------------- + {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} +(1 row) + +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": ["(abc,42,01.02.2003)"]}') q; + reca +------------------------------------------- + {"(abc,42,\"Thu Jan 02 00:00:00 2003\")"} +(1 row) + +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": "{\"(abc,42,01.02.2003)\"}"}') q; + reca +------------------------------------------- + {"(abc,42,\"Thu Jan 02 00:00:00 2003\")"} +(1 row) + +SELECT rec FROM jsonb_populate_record( + row(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + row('x',3,'2012-12-31 15:30:56')::jbpop,NULL)::jsbrec, + '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}' +) q; + rec +------------------------------------ + (abc,3,"Thu Jan 02 00:00:00 2003") +(1 row) + +-- anonymous record type +SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}'); +ERROR: could not determine row type for result of jsonb_populate_record +HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list. +SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}'); + jsonb_populate_record +----------------------- + (0,1) +(1 row) + +SELECT * FROM + jsonb_populate_record(null::record, '{"x": 776}') AS (x int, y int); + x | y +-----+--- + 776 | +(1 row) + +-- composite domain +SELECT jsonb_populate_record(null::jb_ordered_pair, '{"x": 0, "y": 1}'); + jsonb_populate_record +----------------------- + (0,1) +(1 row) + +SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 0}'); + jsonb_populate_record +----------------------- + (0,2) +(1 row) + +SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 1, "y": 0}'); +ERROR: value for domain jb_ordered_pair violates check constraint "jb_ordered_pair_check" +-- populate_recordset +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +--------+---+-------------------------- + blurfl | | + | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +--------+----+-------------------------- + blurfl | 99 | + def | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +--------+---+-------------------------- + blurfl | | + | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +--------+----+-------------------------- + blurfl | 99 | + def | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +-----------------+----+-------------------------- + [100, 200, 300] | 99 | + {"z": true} | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; +ERROR: invalid input syntax for type timestamp: "[100, 200, 300]" +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +--------+---+-------------------------- + blurfl | | + | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +--------+----+-------------------------- + blurfl | 99 | + def | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +-----------------+----+-------------------------- + [100, 200, 300] | 99 | + {"z": true} | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +-- anonymous record type +SELECT jsonb_populate_recordset(null::record, '[{"x": 0, "y": 1}]'); +ERROR: could not determine row type for result of jsonb_populate_recordset +HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list. +SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]'); + jsonb_populate_recordset +-------------------------- + (0,1) +(1 row) + +SELECT i, jsonb_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]') +FROM (VALUES (1),(2)) v(i); + i | jsonb_populate_recordset +---+-------------------------- + 1 | (42,50) + 1 | (1,43) + 2 | (42,50) + 2 | (2,43) +(4 rows) + +SELECT * FROM + jsonb_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int); + x | y +-----+--- + 776 | +(1 row) + +-- empty array is a corner case +SELECT jsonb_populate_recordset(null::record, '[]'); +ERROR: could not determine row type for result of jsonb_populate_recordset +HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list. +SELECT jsonb_populate_recordset(row(1,2), '[]'); + jsonb_populate_recordset +-------------------------- +(0 rows) + +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[]') q; + a | b | c +---+---+--- +(0 rows) + +SELECT * FROM + jsonb_populate_recordset(null::record, '[]') AS (x int, y int); + x | y +---+--- +(0 rows) + +-- composite domain +SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]'); + jsonb_populate_recordset +-------------------------- + (0,1) +(1 row) + +SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 0}, {"y": 3}]'); + jsonb_populate_recordset +-------------------------- + (0,2) + (1,3) +(2 rows) + +SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 1, "y": 0}]'); +ERROR: value for domain jb_ordered_pair violates check constraint "jb_ordered_pair_check" +-- negative cases where the wrong record type is supplied +select * from jsonb_populate_recordset(row(0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text); +ERROR: function return row and query-specified return row do not match +DETAIL: Returned row contains 1 attribute, but query expects 2. +select * from jsonb_populate_recordset(row(0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text); +ERROR: function return row and query-specified return row do not match +DETAIL: Returned type integer at ordinal position 1, but query expects text. +select * from jsonb_populate_recordset(row(0::int,0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text); +ERROR: function return row and query-specified return row do not match +DETAIL: Returned row contains 3 attributes, but query expects 2. +select * from jsonb_populate_recordset(row(1000000000::int,50::int),'[{"b":"2"},{"a":"3"}]') q (a text, b text); +ERROR: function return row and query-specified return row do not match +DETAIL: Returned type integer at ordinal position 1, but query expects text. +-- jsonb_to_record and jsonb_to_recordset +select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}') + as x(a int, b text, d text); + a | b | d +---+-----+--- + 1 | foo | +(1 row) + +select * from jsonb_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar","c":true}]') + as x(a int, b text, c boolean); + a | b | c +---+-----+--- + 1 | foo | + 2 | bar | t +(2 rows) + +select *, c is null as c_is_null +from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8, "ca": ["1 2", 3], "ia": [[1,2],[3,4]], "r": {"a": "aaa", "b": 123}}'::jsonb) + as t(a int, b jsonb, c text, x int, ca char(5)[], ia int[][], r jbpop); + a | b | c | x | ca | ia | r | c_is_null +---+-------------------+---+---+-------------------+---------------+------------+----------- + 1 | {"c": 16, "d": 2} | | 8 | {"1 2 ","3 "} | {{1,2},{3,4}} | (aaa,123,) | t +(1 row) + +select *, c is null as c_is_null +from jsonb_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::jsonb) + as t(a int, b jsonb, c text, x int); + a | b | c | x | c_is_null +---+-------------------+---+---+----------- + 1 | {"c": 16, "d": 2} | | 8 | t +(1 row) + +select * from jsonb_to_record('{"ia": null}') as x(ia _int4); + ia +---- + +(1 row) + +select * from jsonb_to_record('{"ia": 123}') as x(ia _int4); +ERROR: expected JSON array +HINT: See the value of key "ia". +select * from jsonb_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4); + ia +-------------- + {1,2,NULL,4} +(1 row) + +select * from jsonb_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4); + ia +--------------- + {{1,2},{3,4}} +(1 row) + +select * from jsonb_to_record('{"ia": [[1], 2]}') as x(ia _int4); +ERROR: expected JSON array +HINT: See the array element [1] of key "ia". +select * from jsonb_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4); +ERROR: malformed JSON array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +select * from jsonb_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]); + ia2 +--------- + {1,2,3} +(1 row) + +select * from jsonb_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]); + ia2 +--------------- + {{1,2},{3,4}} +(1 row) + +select * from jsonb_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]); + ia2 +----------------- + {{{1},{2},{3}}} +(1 row) + +select * from jsonb_to_record('{"out": {"key": 1}}') as x(out json); + out +------------ + {"key": 1} +(1 row) + +select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out json); + out +-------------- + [{"key": 1}] +(1 row) + +select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out json); + out +---------------- + "{\"key\": 1}" +(1 row) + +select * from jsonb_to_record('{"out": {"key": 1}}') as x(out jsonb); + out +------------ + {"key": 1} +(1 row) + +select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out jsonb); + out +-------------- + [{"key": 1}] +(1 row) + +select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb); + out +---------------- + "{\"key\": 1}" +(1 row) + +-- test type info caching in jsonb_populate_record() +CREATE TEMP TABLE jsbpoptest (js jsonb); +INSERT INTO jsbpoptest +SELECT '{ + "jsa": [1, "2", null, 4], + "rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}, + "reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}] +}'::jsonb +FROM generate_series(1, 3); +SELECT (jsonb_populate_record(NULL::jsbrec, js)).* FROM jsbpoptest; + i | ia | ia1 | ia2 | ia3 | ia1d | ia2d | t | ta | c | ca | ts | js | jsb | jsa | rec | reca +---+----+-----+-----+-----+------+------+---+----+---+----+----+----+-----+--------------------+-----------------------------------+-------------------------------------------------------- + | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} + | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} + | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} +(3 rows) + +DROP TYPE jsbrec; +DROP TYPE jsbrec_i_not_null; +DROP DOMAIN jsb_int_not_null; +DROP DOMAIN jsb_int_array_1d; +DROP DOMAIN jsb_int_array_2d; +DROP DOMAIN jb_ordered_pair; +DROP TYPE jb_unordered_pair; +-- indexing +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ? 'public'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ? 'bar'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; + count +------- + 337 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)'; + count +------- + 1012 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public)'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.bar)'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) || exists($.disabled)'; + count +------- + 337 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) && exists($.disabled)'; + count +------- + 42 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$'; + count +------- + 1012 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; + count +------- + 0 +(1 row) + +CREATE INDEX jidx ON testjsonb USING gin (j); +SET enable_seqscan = off; +-- YB note: yb_test_ybgin_disable_cost_factor setting is needed to really force +-- index scan even if it is detected to be not supported. +SET yb_test_ybgin_disable_cost_factor = 0.5; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"array":["foo"]}'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"array":["bar"]}'; + count +------- + 3 +(1 row) + +-- exercise GIN_SEARCH_MODE_ALL +SELECT count(*) FROM testjsonb WHERE j @> '{}'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM testjsonb WHERE j ? 'public'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ? 'bar'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 2. +SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null'; + QUERY PLAN +----------------------------------------------------------- + Aggregate + -> Index Scan using jidx on testjsonb + Index Cond: (j @@ '($."wait" == null)'::jsonpath) +(3 rows) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.wait == null))'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.wait ? (@ == null))'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "foo"'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "bar"'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.array[*] == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array ? (@[*] == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array[*] ? (@ == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public)'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.bar)'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) || exists($.disabled)'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) && exists($.disabled)'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; + QUERY PLAN +------------------------------------------------------------- + Aggregate + -> Index Scan using jidx on testjsonb + Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath) +(3 rows) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +-- array exists - array elements should behave as keys (for GIN index scans too) +CREATE INDEX jidx_array ON testjsonb USING gin((j->'array')); +SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; + count +------- + 3 +(1 row) + +-- type sensitive array exists - should return no rows (since "exists" only +-- matches strings that are either object keys or array elements) +SELECT count(*) from testjsonb WHERE j->'array' ? '5'::text; + count +------- + 0 +(1 row) + +-- However, a raw scalar is *contained* within the array +SELECT count(*) from testjsonb WHERE j->'array' @> '5'::jsonb; + count +------- + 1 +(1 row) + +RESET yb_test_ybgin_disable_cost_factor; +RESET enable_seqscan; +SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow; + count +------- + 4791 +(1 row) + +SELECT key, count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow GROUP BY key ORDER BY count DESC, key; + key | count +-----------+------- + line | 884 + query | 207 + pos | 203 + node | 202 + space | 197 + status | 195 + public | 194 + title | 190 + wait | 190 + org | 189 + user | 189 + coauthors | 188 + disabled | 185 + indexed | 184 + cleaned | 180 + bad | 179 + date | 179 + world | 176 + state | 172 + subtitle | 169 + auth | 168 + abstract | 161 + array | 5 + age | 2 + foo | 2 + fool | 1 +(26 rows) + +-- sort/hash +SELECT count(distinct j) FROM testjsonb; + count +------- + 894 +(1 row) + +SET enable_hashagg = off; +SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2; + count +------- + 894 +(1 row) + +SET enable_hashagg = on; +SET enable_sort = off; +SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2; + count +------- + 894 +(1 row) + +SELECT distinct * FROM (values (jsonb '{}' || ''::text),('{}')) v(j); + j +---- + {} +(1 row) + +SET enable_sort = on; +RESET enable_hashagg; +RESET enable_sort; +DROP INDEX jidx; +DROP INDEX jidx_array; +-- btree +CREATE INDEX jidx ON testjsonb USING btree (j); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +ERROR: INDEX on column of type 'JSONB' not yet supported +SET enable_seqscan = off; +SELECT count(*) FROM testjsonb WHERE j > '{"p":1}'; + count +------- + 884 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j = '{"pos":98, "line":371, "node":"CBA", "indexed":true}'; + count +------- + 1 +(1 row) + +--gin path opclass +DROP INDEX jidx; +ERROR: index "jidx" does not exist +CREATE INDEX jidx ON testjsonb USING gin (j jsonb_path_ops); +SET enable_seqscan = off; +-- YB note: yb_test_ybgin_disable_cost_factor setting is needed to really force +-- index scan even if it is detected to be not supported. +SET yb_test_ybgin_disable_cost_factor = 0.5; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; + count +------- + 2 +(1 row) + +-- exercise GIN_SEARCH_MODE_ALL +SELECT count(*) FROM testjsonb WHERE j @> '{}'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.wait == null))'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.wait ? (@ == null))'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "foo"'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "bar"'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.array[*] == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array ? (@[*] == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array[*] ? (@ == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; + QUERY PLAN +------------------------------------------------------------- + Aggregate + -> Index Scan using jidx on testjsonb + Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath) +(3 rows) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +RESET yb_test_ybgin_disable_cost_factor; +RESET enable_seqscan; +DROP INDEX jidx; +-- nested tests +SELECT '{"ff":{"a":12,"b":16}}'::jsonb; + jsonb +---------------------------- + {"ff": {"a": 12, "b": 16}} +(1 row) + +SELECT '{"ff":{"a":12,"b":16},"qq":123}'::jsonb; + jsonb +--------------------------------------- + {"ff": {"a": 12, "b": 16}, "qq": 123} +(1 row) + +SELECT '{"aa":["a","aaa"],"qq":{"a":12,"b":16,"c":["c1","c2"],"d":{"d1":"d1","d2":"d2","d1":"d3"}}}'::jsonb; + jsonb +-------------------------------------------------------------------------------------------------- + {"aa": ["a", "aaa"], "qq": {"a": 12, "b": 16, "c": ["c1", "c2"], "d": {"d1": "d3", "d2": "d2"}}} +(1 row) + +SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2"],"d":{"d1":"d1","d2":"d2"}}}'::jsonb; + jsonb +------------------------------------------------------------------------------------------------------ + {"aa": ["a", "aaa"], "qq": {"a": "12", "b": "16", "c": ["c1", "c2"], "d": {"d1": "d1", "d2": "d2"}}} +(1 row) + +SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2",["c3"],{"c4":4}],"d":{"d1":"d1","d2":"d2"}}}'::jsonb; + jsonb +------------------------------------------------------------------------------------------------------------------------- + {"aa": ["a", "aaa"], "qq": {"a": "12", "b": "16", "c": ["c1", "c2", ["c3"], {"c4": 4}], "d": {"d1": "d1", "d2": "d2"}}} +(1 row) + +SELECT '{"ff":["a","aaa"]}'::jsonb; + jsonb +---------------------- + {"ff": ["a", "aaa"]} +(1 row) + +SELECT + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'ff', + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'qq', + ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'Y') IS NULL AS f, + ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb ->> 'Y') IS NULL AS t, + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'x'; + ?column? | ?column? | f | t | ?column? +--------------------+----------+---+---+---------- + {"a": 12, "b": 16} | 123 | f | t | [1, 2] +(1 row) + +-- nested containment +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[2,1],"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; + ?column? +---------- + f +(1 row) + +SELECT '["a","b"]'::jsonb @> '["a","b","c","b"]'; + ?column? +---------- + f +(1 row) + +SELECT '["a","b","c","b"]'::jsonb @> '["a","b"]'; + ?column? +---------- + t +(1 row) + +SELECT '["a","b","c",[1,2]]'::jsonb @> '["a",[1,2]]'; + ?column? +---------- + t +(1 row) + +SELECT '["a","b","c",[1,2]]'::jsonb @> '["b",[1,2]]'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[2]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[3]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"c":3}]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4}]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},3]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},1]}'; + ?column? +---------- + t +(1 row) + +-- check some corner cases for indexed nested containment (bug #13756) +create temp table nestjsonb (j jsonb); +insert into nestjsonb (j) values ('{"a":[["b",{"x":1}],["b",{"x":2}]],"c":3}'); +insert into nestjsonb (j) values ('[[14,2,3]]'); +insert into nestjsonb (j) values ('[1,[14,2,3]]'); +create index on nestjsonb using gin(j jsonb_path_ops); +set enable_seqscan = on; +set enable_bitmapscan = off; +select * from nestjsonb where j @> '{"a":[[{"x":2}]]}'::jsonb; + j +--------------------------------------------------- + {"a": [["b", {"x": 1}], ["b", {"x": 2}]], "c": 3} +(1 row) + +select * from nestjsonb where j @> '{"c":3}'; + j +--------------------------------------------------- + {"a": [["b", {"x": 1}], ["b", {"x": 2}]], "c": 3} +(1 row) + +select * from nestjsonb where j @> '[[14]]'; + j +----------------- + [[14, 2, 3]] + [1, [14, 2, 3]] +(2 rows) + +set enable_seqscan = off; +set enable_bitmapscan = on; +select * from nestjsonb where j @> '{"a":[[{"x":2}]]}'::jsonb; + j +--------------------------------------------------- + {"a": [["b", {"x": 1}], ["b", {"x": 2}]], "c": 3} +(1 row) + +select * from nestjsonb where j @> '{"c":3}'; + j +--------------------------------------------------- + {"a": [["b", {"x": 1}], ["b", {"x": 2}]], "c": 3} +(1 row) + +select * from nestjsonb where j @> '[[14]]'; + j +----------------- + [[14, 2, 3]] + [1, [14, 2, 3]] +(2 rows) + +reset enable_seqscan; +reset enable_bitmapscan; +-- nested object field / array index lookup +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'n'; + ?column? +---------- + null +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'a'; + ?column? +---------- + 1 +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'b'; + ?column? +---------- + [1, 2] +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'c'; + ?column? +---------- + {"1": 2} +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd'; + ?column? +--------------- + {"1": [2, 3]} +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd' -> '1'; + ?column? +---------- + [2, 3] +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'e'; + ?column? +---------- + +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 0; --expecting error + ?column? +---------- + +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 0; + ?column? +---------- + "a" +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 1; + ?column? +---------- + "b" +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 2; + ?column? +---------- + "c" +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 3; + ?column? +---------- + [1, 2] +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 3 -> 1; + ?column? +---------- + 2 +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 4; + ?column? +---------- + null +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 5; + ?column? +---------- + +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> -1; + ?column? +---------- + null +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> -5; + ?column? +---------- + "a" +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> -6; + ?column? +---------- + +(1 row) + +--nested path extraction +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{0}'; + ?column? +---------- + +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{a}'; + ?column? +---------- + "b" +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c}'; + ?column? +----------- + [1, 2, 3] +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,0}'; + ?column? +---------- + 1 +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,1}'; + ?column? +---------- + 2 +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,2}'; + ?column? +---------- + 3 +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,3}'; + ?column? +---------- + +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-1}'; + ?column? +---------- + 3 +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-3}'; + ?column? +---------- + 1 +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-4}'; + ?column? +---------- + +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{0}'; + ?column? +---------- + 0 +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{3}'; + ?column? +---------- + [3, 4] +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4}'; + ?column? +--------------- + {"5": "five"} +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4,5}'; + ?column? +---------- + "five" +(1 row) + +--nested exists +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'n'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'a'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'b'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'c'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'd'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'e'; + ?column? +---------- + f +(1 row) + +-- jsonb_strip_nulls +select jsonb_strip_nulls(null); + jsonb_strip_nulls +------------------- + +(1 row) + +select jsonb_strip_nulls('1'); + jsonb_strip_nulls +------------------- + 1 +(1 row) + +select jsonb_strip_nulls('"a string"'); + jsonb_strip_nulls +------------------- + "a string" +(1 row) + +select jsonb_strip_nulls('null'); + jsonb_strip_nulls +------------------- + null +(1 row) + +select jsonb_strip_nulls('[1,2,null,3,4]'); + jsonb_strip_nulls +-------------------- + [1, 2, null, 3, 4] +(1 row) + +select jsonb_strip_nulls('{"a":1,"b":null,"c":[2,null,3],"d":{"e":4,"f":null}}'); + jsonb_strip_nulls +-------------------------------------------- + {"a": 1, "c": [2, null, 3], "d": {"e": 4}} +(1 row) + +select jsonb_strip_nulls('[1,{"a":1,"b":null,"c":2},3]'); + jsonb_strip_nulls +-------------------------- + [1, {"a": 1, "c": 2}, 3] +(1 row) + +-- an empty object is not null and should not be stripped +select jsonb_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }'); + jsonb_strip_nulls +-------------------- + {"a": {}, "d": {}} +(1 row) + +select jsonb_pretty('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}'); + jsonb_pretty +---------------------------- + { + + "a": "test", + + "b": [ + + 1, + + 2, + + 3 + + ], + + "c": "test3", + + "d": { + + "dd": "test4", + + "dd2": { + + "ddd": "test5"+ + } + + } + + } +(1 row) + +select jsonb_pretty('[{"f1":1,"f2":null},2,null,[[{"x":true},6,7],8],3]'); + jsonb_pretty +--------------------------- + [ + + { + + "f1": 1, + + "f2": null + + }, + + 2, + + null, + + [ + + [ + + { + + "x": true+ + }, + + 6, + + 7 + + ], + + 8 + + ], + + 3 + + ] +(1 row) + +select jsonb_pretty('{"a":["b", "c"], "d": {"e":"f"}}'); + jsonb_pretty +------------------ + { + + "a": [ + + "b", + + "c" + + ], + + "d": { + + "e": "f"+ + } + + } +(1 row) + +select jsonb_concat('{"d": "test", "a": [1, 2]}', '{"g": "test2", "c": {"c1":1, "c2":2}}'); + jsonb_concat +------------------------------------------------------------------- + {"a": [1, 2], "c": {"c1": 1, "c2": 2}, "d": "test", "g": "test2"} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"cq":"l", "b":"g", "fg":false}'; + ?column? +--------------------------------------------- + {"b": "g", "aa": 1, "cq": "l", "fg": false} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"aq":"l"}'; + ?column? +--------------------------------------- + {"b": 2, "aa": 1, "aq": "l", "cq": 3} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"aa":"l"}'; + ?column? +------------------------------ + {"b": 2, "aa": "l", "cq": 3} +(1 row) + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{}'; + ?column? +---------------------------- + {"b": 2, "aa": 1, "cq": 3} +(1 row) + +select '["a", "b"]'::jsonb || '["c"]'; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '["a", "b"]'::jsonb || '["c", "d"]'; + ?column? +---------------------- + ["a", "b", "c", "d"] +(1 row) + +select '["c"]' || '["a", "b"]'::jsonb; + ?column? +----------------- + ["c", "a", "b"] +(1 row) + +select '["a", "b"]'::jsonb || '"c"'; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '"c"' || '["a", "b"]'::jsonb; + ?column? +----------------- + ["c", "a", "b"] +(1 row) + +select '[]'::jsonb || '["a"]'::jsonb; + ?column? +---------- + ["a"] +(1 row) + +select '[]'::jsonb || '"a"'::jsonb; + ?column? +---------- + ["a"] +(1 row) + +select '"b"'::jsonb || '"a"'::jsonb; + ?column? +------------ + ["b", "a"] +(1 row) + +select '{}'::jsonb || '{"a":"b"}'::jsonb; + ?column? +------------ + {"a": "b"} +(1 row) + +select '[]'::jsonb || '{"a":"b"}'::jsonb; + ?column? +-------------- + [{"a": "b"}] +(1 row) + +select '{"a":"b"}'::jsonb || '[]'::jsonb; + ?column? +-------------- + [{"a": "b"}] +(1 row) + +select '"a"'::jsonb || '{"a":1}'; + ?column? +----------------- + ["a", {"a": 1}] +(1 row) + +select '{"a":1}' || '"a"'::jsonb; + ?column? +----------------- + [{"a": 1}, "a"] +(1 row) + +select '[3]'::jsonb || '{}'::jsonb; + ?column? +---------- + [3, {}] +(1 row) + +select '3'::jsonb || '[]'::jsonb; + ?column? +---------- + [3] +(1 row) + +select '3'::jsonb || '4'::jsonb; + ?column? +---------- + [3, 4] +(1 row) + +select '3'::jsonb || '{}'::jsonb; + ?column? +---------- + [3, {}] +(1 row) + +select '["a", "b"]'::jsonb || '{"c":1}'; + ?column? +---------------------- + ["a", "b", {"c": 1}] +(1 row) + +select '{"c": 1}'::jsonb || '["a", "b"]'; + ?column? +---------------------- + [{"c": 1}, "a", "b"] +(1 row) + +select '{}'::jsonb || '{"cq":"l", "b":"g", "fg":false}'; + ?column? +------------------------------------ + {"b": "g", "cq": "l", "fg": false} +(1 row) + +select pg_column_size('{}'::jsonb || '{}'::jsonb) = pg_column_size('{}'::jsonb); + ?column? +---------- + t +(1 row) + +select pg_column_size('{"aa":1}'::jsonb || '{"b":2}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select pg_column_size('{"aa":1, "b":2}'::jsonb || '{}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select pg_column_size('{}'::jsonb || '{"aa":1, "b":2}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'a'); + jsonb_delete +------------------ + {"b": 2, "c": 3} +(1 row) + +select jsonb_delete('{"a":null , "b":2, "c":3}'::jsonb, 'a'); + jsonb_delete +------------------ + {"b": 2, "c": 3} +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'b'); + jsonb_delete +------------------ + {"a": 1, "c": 3} +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'c'); + jsonb_delete +------------------ + {"a": 1, "b": 2} +(1 row) + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'd'); + jsonb_delete +-------------------------- + {"a": 1, "b": 2, "c": 3} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'a'; + ?column? +------------------ + {"b": 2, "c": 3} +(1 row) + +select '{"a":null , "b":2, "c":3}'::jsonb - 'a'; + ?column? +------------------ + {"b": 2, "c": 3} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'b'; + ?column? +------------------ + {"a": 1, "c": 3} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'c'; + ?column? +------------------ + {"a": 1, "b": 2} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - 'd'; + ?column? +-------------------------- + {"a": 1, "b": 2, "c": 3} +(1 row) + +select pg_column_size('{"a":1 , "b":2, "c":3}'::jsonb - 'b') = pg_column_size('{"a":1, "b":2}'::jsonb); + ?column? +---------- + t +(1 row) + +select '["a","b","c"]'::jsonb - 3; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - 2; + ?column? +------------ + ["a", "b"] +(1 row) + +select '["a","b","c"]'::jsonb - 1; + ?column? +------------ + ["a", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - 0; + ?column? +------------ + ["b", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - -1; + ?column? +------------ + ["a", "b"] +(1 row) + +select '["a","b","c"]'::jsonb - -2; + ?column? +------------ + ["a", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - -3; + ?column? +------------ + ["b", "c"] +(1 row) + +select '["a","b","c"]'::jsonb - -4; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - '{b}'::text[]; + ?column? +------------------ + {"a": 1, "c": 3} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - '{c,b}'::text[]; + ?column? +---------- + {"a": 1} +(1 row) + +select '{"a":1 , "b":2, "c":3}'::jsonb - '{}'::text[]; + ?column? +-------------------------- + {"a": 1, "b": 2, "c": 3} +(1 row) + +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{n}', '[1,2,3]'); + jsonb_set +-------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": [1, 2, 3]} +(1 row) + +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '[1,2,3]'); + jsonb_set +----------------------------------------------------------------------------- + {"a": 1, "b": [1, [1, 2, 3]], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,1,0}', '[1,2,3]'); + jsonb_set +----------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [[1, 2, 3], 3]}, "n": null} +(1 row) + +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,NULL,0}', '[1,2,3]'); +ERROR: path element at position 2 is null +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{n}', '{"1": 2}'); + jsonb_set +------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": {"1": 2}} +(1 row) + +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '{"1": 2}'); + jsonb_set +---------------------------------------------------------------------------- + {"a": 1, "b": [1, {"1": 2}], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,1,0}', '{"1": 2}'); + jsonb_set +---------------------------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [{"1": 2}, 3]}, "n": null} +(1 row) + +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,NULL,0}', '{"1": 2}'); +ERROR: path element at position 2 is null +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '"test"'); + jsonb_set +-------------------------------------------------------------------------- + {"a": 1, "b": [1, "test"], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '{"f": "test"}'); + jsonb_set +--------------------------------------------------------------------------------- + {"a": 1, "b": [1, {"f": "test"}], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{n}'); + jsonb_delete_path +---------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}} +(1 row) + +select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{b,-1}'); + jsonb_delete_path +------------------------------------------------------------------ + {"a": 1, "b": [1], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{d,1,0}'); + jsonb_delete_path +------------------------------------------------------------------ + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [3]}, "n": null} +(1 row) + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{n}'; + ?column? +---------------------------------------------------------- + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [2, 3]}} +(1 row) + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{b,-1}'; + ?column? +------------------------------------------------------------------ + {"a": 1, "b": [1], "c": {"1": 2}, "d": {"1": [2, 3]}, "n": null} +(1 row) + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{b,-1e}'; -- invalid array subscript +ERROR: path element at position 2 is not an integer: "-1e" +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{d,1,0}'; + ?column? +------------------------------------------------------------------ + {"a": 1, "b": [1, 2], "c": {"1": 2}, "d": {"1": [3]}, "n": null} +(1 row) + +-- empty structure and error conditions for delete and replace +select '"a"'::jsonb - 'a'; -- error +ERROR: cannot delete from scalar +select '{}'::jsonb - 'a'; + ?column? +---------- + {} +(1 row) + +select '[]'::jsonb - 'a'; + ?column? +---------- + [] +(1 row) + +select '"a"'::jsonb - 1; -- error +ERROR: cannot delete from scalar +select '{}'::jsonb - 1; -- error +ERROR: cannot delete from object using integer index +select '[]'::jsonb - 1; + ?column? +---------- + [] +(1 row) + +select '"a"'::jsonb #- '{a}'; -- error +ERROR: cannot delete path in scalar +select '{}'::jsonb #- '{a}'; + ?column? +---------- + {} +(1 row) + +select '[]'::jsonb #- '{a}'; + ?column? +---------- + [] +(1 row) + +select jsonb_set('"a"','{a}','"b"'); --error +ERROR: cannot set path in scalar +select jsonb_set('{}','{a}','"b"', false); + jsonb_set +----------- + {} +(1 row) + +select jsonb_set('[]','{1}','"b"', false); + jsonb_set +----------- + [] +(1 row) + +select jsonb_set('[{"f1":1,"f2":null},2,null,3]', '{0}','[2,3,4]', false); + jsonb_set +------------------------- + [[2, 3, 4], 2, null, 3] +(1 row) + +-- jsonb_set adding instead of replacing +-- prepend to array +select jsonb_set('{"a":1,"b":[0,1,2],"c":{"d":4}}','{b,-33}','{"foo":123}'); + jsonb_set +------------------------------------------------------- + {"a": 1, "b": [{"foo": 123}, 0, 1, 2], "c": {"d": 4}} +(1 row) + +-- append to array +select jsonb_set('{"a":1,"b":[0,1,2],"c":{"d":4}}','{b,33}','{"foo":123}'); + jsonb_set +------------------------------------------------------- + {"a": 1, "b": [0, 1, 2, {"foo": 123}], "c": {"d": 4}} +(1 row) + +-- check nesting levels addition +select jsonb_set('{"a":1,"b":[4,5,[0,1,2],6,7],"c":{"d":4}}','{b,2,33}','{"foo":123}'); + jsonb_set +--------------------------------------------------------------------- + {"a": 1, "b": [4, 5, [0, 1, 2, {"foo": 123}], 6, 7], "c": {"d": 4}} +(1 row) + +-- add new key +select jsonb_set('{"a":1,"b":[0,1,2],"c":{"d":4}}','{c,e}','{"foo":123}'); + jsonb_set +------------------------------------------------------------ + {"a": 1, "b": [0, 1, 2], "c": {"d": 4, "e": {"foo": 123}}} +(1 row) + +-- adding doesn't do anything if elements before last aren't present +select jsonb_set('{"a":1,"b":[0,1,2],"c":{"d":4}}','{x,-33}','{"foo":123}'); + jsonb_set +----------------------------------------- + {"a": 1, "b": [0, 1, 2], "c": {"d": 4}} +(1 row) + +select jsonb_set('{"a":1,"b":[0,1,2],"c":{"d":4}}','{x,y}','{"foo":123}'); + jsonb_set +----------------------------------------- + {"a": 1, "b": [0, 1, 2], "c": {"d": 4}} +(1 row) + +-- add to empty object +select jsonb_set('{}','{x}','{"foo":123}'); + jsonb_set +--------------------- + {"x": {"foo": 123}} +(1 row) + +--add to empty array +select jsonb_set('[]','{0}','{"foo":123}'); + jsonb_set +---------------- + [{"foo": 123}] +(1 row) + +select jsonb_set('[]','{99}','{"foo":123}'); + jsonb_set +---------------- + [{"foo": 123}] +(1 row) + +select jsonb_set('[]','{-99}','{"foo":123}'); + jsonb_set +---------------- + [{"foo": 123}] +(1 row) + +select jsonb_set('{"a": [1, 2, 3]}', '{a, non_integer}', '"new_value"'); +ERROR: path element at position 2 is not an integer: "non_integer" +select jsonb_set('{"a": {"b": [1, 2, 3]}}', '{a, b, non_integer}', '"new_value"'); +ERROR: path element at position 3 is not an integer: "non_integer" +select jsonb_set('{"a": {"b": [1, 2, 3]}}', '{a, b, NULL}', '"new_value"'); +ERROR: path element at position 3 is null +-- jsonb_set_lax +\pset null NULL +-- pass though non nulls to jsonb_set +select jsonb_set_lax('{"a":1,"b":2}','{b}','5') ; + jsonb_set_lax +------------------ + {"a": 1, "b": 5} +(1 row) + +select jsonb_set_lax('{"a":1,"b":2}','{d}','6', true) ; + jsonb_set_lax +-------------------------- + {"a": 1, "b": 2, "d": 6} +(1 row) + +-- using the default treatment +select jsonb_set_lax('{"a":1,"b":2}','{b}',null); + jsonb_set_lax +--------------------- + {"a": 1, "b": null} +(1 row) + +select jsonb_set_lax('{"a":1,"b":2}','{d}',null,true); + jsonb_set_lax +----------------------------- + {"a": 1, "b": 2, "d": null} +(1 row) + +-- errors +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, true, null); +ERROR: null_value_treatment must be "delete_key", "return_target", "use_json_null", or "raise_exception" +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, true, 'no_such_treatment'); +ERROR: null_value_treatment must be "delete_key", "return_target", "use_json_null", or "raise_exception" +-- explicit treatments +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, null_value_treatment => 'raise_exception') as raise_exception; +ERROR: JSON value must not be null +DETAIL: Exception was raised because null_value_treatment is "raise_exception". +HINT: To avoid, either change the null_value_treatment argument or ensure that an SQL NULL is not passed. +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, null_value_treatment => 'return_target') as return_target; + return_target +------------------ + {"a": 1, "b": 2} +(1 row) + +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, null_value_treatment => 'delete_key') as delete_key; + delete_key +------------ + {"a": 1} +(1 row) + +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, null_value_treatment => 'use_json_null') as use_json_null; + use_json_null +--------------------- + {"a": 1, "b": null} +(1 row) + +\pset null '' +-- jsonb_insert +select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"'); + jsonb_insert +------------------------------- + {"a": [0, "new_value", 1, 2]} +(1 row) + +select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"', true); + jsonb_insert +------------------------------- + {"a": [0, 1, "new_value", 2]} +(1 row) + +select jsonb_insert('{"a": {"b": {"c": [0, 1, "test1", "test2"]}}}', '{a, b, c, 2}', '"new_value"'); + jsonb_insert +------------------------------------------------------------ + {"a": {"b": {"c": [0, 1, "new_value", "test1", "test2"]}}} +(1 row) + +select jsonb_insert('{"a": {"b": {"c": [0, 1, "test1", "test2"]}}}', '{a, b, c, 2}', '"new_value"', true); + jsonb_insert +------------------------------------------------------------ + {"a": {"b": {"c": [0, 1, "test1", "new_value", "test2"]}}} +(1 row) + +select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '{"b": "value"}'); + jsonb_insert +---------------------------------- + {"a": [0, {"b": "value"}, 1, 2]} +(1 row) + +select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '["value1", "value2"]'); + jsonb_insert +---------------------------------------- + {"a": [0, ["value1", "value2"], 1, 2]} +(1 row) + +-- edge cases +select jsonb_insert('{"a": [0,1,2]}', '{a, 0}', '"new_value"'); + jsonb_insert +------------------------------- + {"a": ["new_value", 0, 1, 2]} +(1 row) + +select jsonb_insert('{"a": [0,1,2]}', '{a, 0}', '"new_value"', true); + jsonb_insert +------------------------------- + {"a": [0, "new_value", 1, 2]} +(1 row) + +select jsonb_insert('{"a": [0,1,2]}', '{a, 2}', '"new_value"'); + jsonb_insert +------------------------------- + {"a": [0, 1, "new_value", 2]} +(1 row) + +select jsonb_insert('{"a": [0,1,2]}', '{a, 2}', '"new_value"', true); + jsonb_insert +------------------------------- + {"a": [0, 1, 2, "new_value"]} +(1 row) + +select jsonb_insert('{"a": [0,1,2]}', '{a, -1}', '"new_value"'); + jsonb_insert +------------------------------- + {"a": [0, 1, "new_value", 2]} +(1 row) + +select jsonb_insert('{"a": [0,1,2]}', '{a, -1}', '"new_value"', true); + jsonb_insert +------------------------------- + {"a": [0, 1, 2, "new_value"]} +(1 row) + +select jsonb_insert('[]', '{1}', '"new_value"'); + jsonb_insert +--------------- + ["new_value"] +(1 row) + +select jsonb_insert('[]', '{1}', '"new_value"', true); + jsonb_insert +--------------- + ["new_value"] +(1 row) + +select jsonb_insert('{"a": []}', '{a, 1}', '"new_value"'); + jsonb_insert +---------------------- + {"a": ["new_value"]} +(1 row) + +select jsonb_insert('{"a": []}', '{a, 1}', '"new_value"', true); + jsonb_insert +---------------------- + {"a": ["new_value"]} +(1 row) + +select jsonb_insert('{"a": [0,1,2]}', '{a, 10}', '"new_value"'); + jsonb_insert +------------------------------- + {"a": [0, 1, 2, "new_value"]} +(1 row) + +select jsonb_insert('{"a": [0,1,2]}', '{a, -10}', '"new_value"'); + jsonb_insert +------------------------------- + {"a": ["new_value", 0, 1, 2]} +(1 row) + +-- jsonb_insert should be able to insert new value for objects, but not to replace +select jsonb_insert('{"a": {"b": "value"}}', '{a, c}', '"new_value"'); + jsonb_insert +----------------------------------------- + {"a": {"b": "value", "c": "new_value"}} +(1 row) + +select jsonb_insert('{"a": {"b": "value"}}', '{a, c}', '"new_value"', true); + jsonb_insert +----------------------------------------- + {"a": {"b": "value", "c": "new_value"}} +(1 row) + +select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"'); +ERROR: cannot replace existing key +HINT: Try using the function jsonb_set to replace key value. +select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true); +ERROR: cannot replace existing key +HINT: Try using the function jsonb_set to replace key value. +-- jsonb subscript +select ('123'::jsonb)['a']; + jsonb +------- + +(1 row) + +select ('123'::jsonb)[0]; + jsonb +------- + +(1 row) + +select ('123'::jsonb)[NULL]; + jsonb +------- + +(1 row) + +select ('{"a": 1}'::jsonb)['a']; + jsonb +------- + 1 +(1 row) + +select ('{"a": 1}'::jsonb)[0]; + jsonb +------- + +(1 row) + +select ('{"a": 1}'::jsonb)['not_exist']; + jsonb +------- + +(1 row) + +select ('{"a": 1}'::jsonb)[NULL]; + jsonb +------- + +(1 row) + +select ('[1, "2", null]'::jsonb)['a']; + jsonb +------- + +(1 row) + +select ('[1, "2", null]'::jsonb)[0]; + jsonb +------- + 1 +(1 row) + +select ('[1, "2", null]'::jsonb)['1']; + jsonb +------- + "2" +(1 row) + +select ('[1, "2", null]'::jsonb)[1.0]; +ERROR: subscript type numeric is not supported +LINE 1: select ('[1, "2", null]'::jsonb)[1.0]; + ^ +HINT: jsonb subscript must be coercible to either integer or text. +select ('[1, "2", null]'::jsonb)[2]; + jsonb +------- + null +(1 row) + +select ('[1, "2", null]'::jsonb)[3]; + jsonb +------- + +(1 row) + +select ('[1, "2", null]'::jsonb)[-2]; + jsonb +------- + "2" +(1 row) + +select ('[1, "2", null]'::jsonb)[1]['a']; + jsonb +------- + +(1 row) + +select ('[1, "2", null]'::jsonb)[1][0]; + jsonb +------- + +(1 row) + +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b']; + jsonb +------- + "c" +(1 row) + +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']; + jsonb +----------- + [1, 2, 3] +(1 row) + +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1]; + jsonb +------- + 2 +(1 row) + +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a']; + jsonb +------- + +(1 row) + +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']; + jsonb +--------------- + {"a2": "aaa"} +(1 row) + +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']; + jsonb +------- + "aaa" +(1 row) + +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3']; + jsonb +------- + +(1 row) + +select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1']; + jsonb +----------------------- + ["aaa", "bbb", "ccc"] +(1 row) + +select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2]; + jsonb +------- + "ccc" +(1 row) + +-- slices are not supported +select ('{"a": 1}'::jsonb)['a':'b']; +ERROR: jsonb subscript does not support slices +LINE 1: select ('{"a": 1}'::jsonb)['a':'b']; + ^ +select ('[1, "2", null]'::jsonb)[1:2]; +ERROR: jsonb subscript does not support slices +LINE 1: select ('[1, "2", null]'::jsonb)[1:2]; + ^ +select ('[1, "2", null]'::jsonb)[:2]; +ERROR: jsonb subscript does not support slices +LINE 1: select ('[1, "2", null]'::jsonb)[:2]; + ^ +select ('[1, "2", null]'::jsonb)[1:]; +ERROR: jsonb subscript does not support slices +LINE 1: select ('[1, "2", null]'::jsonb)[1:]; + ^ +select ('[1, "2", null]'::jsonb)[:]; +ERROR: jsonb subscript does not support slices +create TEMP TABLE test_jsonb_subscript ( + id int, + test_json jsonb +); +insert into test_jsonb_subscript values +(1, '{}'), -- empty jsonb +(2, '{"key": "value"}'); -- jsonb with data +-- update empty jsonb +update test_jsonb_subscript set test_json['a'] = '1' where id = 1; +select * from test_jsonb_subscript; + id | test_json +----+------------------ + 2 | {"key": "value"} + 1 | {"a": 1} +(2 rows) + +-- update jsonb with some data +update test_jsonb_subscript set test_json['a'] = '1' where id = 2; +select * from test_jsonb_subscript; + id | test_json +----+-------------------------- + 1 | {"a": 1} + 2 | {"a": 1, "key": "value"} +(2 rows) + +-- replace jsonb +update test_jsonb_subscript set test_json['a'] = '"test"'; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------- + 1 | {"a": "test"} + 2 | {"a": "test", "key": "value"} +(2 rows) + +-- replace by object +update test_jsonb_subscript set test_json['a'] = '{"b": 1}'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+--------------------------------- + 1 | {"a": {"b": 1}} + 2 | {"a": {"b": 1}, "key": "value"} +(2 rows) + +-- replace by array +update test_jsonb_subscript set test_json['a'] = '[1, 2, 3]'::jsonb; +select * from test_jsonb_subscript; + id | test_json +----+---------------------------------- + 1 | {"a": [1, 2, 3]} + 2 | {"a": [1, 2, 3], "key": "value"} +(2 rows) + +-- use jsonb subscription in where clause +select * from test_jsonb_subscript where test_json['key'] = '"value"'; + id | test_json +----+---------------------------------- + 2 | {"a": [1, 2, 3], "key": "value"} +(1 row) + +select * from test_jsonb_subscript where test_json['key_doesnt_exists'] = '"value"'; + id | test_json +----+----------- +(0 rows) + +select * from test_jsonb_subscript where test_json['key'] = '"wrong_value"'; + id | test_json +----+----------- +(0 rows) + +-- NULL +update test_jsonb_subscript set test_json[NULL] = '1'; +ERROR: jsonb subscript in assignment must not be null +update test_jsonb_subscript set test_json['another_key'] = NULL; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------------------------- + 1 | {"a": [1, 2, 3], "another_key": null} + 2 | {"a": [1, 2, 3], "key": "value", "another_key": null} +(2 rows) + +-- NULL as jsonb source +insert into test_jsonb_subscript values (3, NULL); +update test_jsonb_subscript set test_json['a'] = '1' where id = 3; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------------------------- + 1 | {"a": [1, 2, 3], "another_key": null} + 2 | {"a": [1, 2, 3], "key": "value", "another_key": null} + 3 | {"a": 1} +(3 rows) + +update test_jsonb_subscript set test_json = NULL where id = 3; +update test_jsonb_subscript set test_json[0] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+--------------------------------------------------------------- + 1 | {"0": 1, "a": [1, 2, 3], "another_key": null} + 2 | {"0": 1, "a": [1, 2, 3], "key": "value", "another_key": null} + 3 | [1] +(3 rows) + +-- Fill the gaps logic +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '[0]'); +update test_jsonb_subscript set test_json[5] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+-------------------------------- + 1 | [0, null, null, null, null, 1] +(1 row) + +update test_jsonb_subscript set test_json[-4] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+----------------------------- + 1 | [0, null, 1, null, null, 1] +(1 row) + +update test_jsonb_subscript set test_json[-8] = '1'; +ERROR: path element at position 1 is out of range: -8 +select * from test_jsonb_subscript; + id | test_json +----+----------------------------- + 1 | [0, null, 1, null, null, 1] +(1 row) + +-- keep consistent values position +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '[]'); +update test_jsonb_subscript set test_json[5] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+----------------------------------- + 1 | [null, null, null, null, null, 1] +(1 row) + +-- create the whole path +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{}'); +update test_jsonb_subscript set test_json['a'][0]['b'][0]['c'] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+---------------------------- + 1 | {"a": [{"b": [{"c": 1}]}]} +(1 row) + +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{}'); +update test_jsonb_subscript set test_json['a'][2]['b'][2]['c'][2] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------------------------------------ + 1 | {"a": [null, null, {"b": [null, null, {"c": [null, null, 1]}]}]} +(1 row) + +-- create the whole path with already existing keys +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{"b": 1}'); +update test_jsonb_subscript set test_json['a'][0] = '2'; +select * from test_jsonb_subscript; + id | test_json +----+-------------------- + 1 | {"a": [2], "b": 1} +(1 row) + +-- the start jsonb is an object, first subscript is treated as a key +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{}'); +update test_jsonb_subscript set test_json[0]['a'] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+----------------- + 1 | {"0": {"a": 1}} +(1 row) + +-- the start jsonb is an array +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '[]'); +update test_jsonb_subscript set test_json[0]['a'] = '1'; +update test_jsonb_subscript set test_json[2]['b'] = '2'; +select * from test_jsonb_subscript; + id | test_json +----+---------------------------- + 1 | [{"a": 1}, null, {"b": 2}] +(1 row) + +-- overwriting an existing path +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{}'); +update test_jsonb_subscript set test_json['a']['b'][1] = '1'; +update test_jsonb_subscript set test_json['a']['b'][10] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+---------------------------------------------------------------------------- + 1 | {"a": {"b": [null, 1, null, null, null, null, null, null, null, null, 1]}} +(1 row) + +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '[]'); +update test_jsonb_subscript set test_json[0][0][0] = '1'; +update test_jsonb_subscript set test_json[0][0][1] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+------------ + 1 | [[[1, 1]]] +(1 row) + +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{}'); +update test_jsonb_subscript set test_json['a']['b'][10] = '1'; +update test_jsonb_subscript set test_json['a'][10][10] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+------------------------------------------------------------------------------------------------------------------------------------------------------ + 1 | {"a": {"b": [null, null, null, null, null, null, null, null, null, null, 1], "10": [null, null, null, null, null, null, null, null, null, null, 1]}} +(1 row) + +-- an empty sub element +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{"a": {}}'); +update test_jsonb_subscript set test_json['a']['b']['c'][2] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+-------------------------------------- + 1 | {"a": {"b": {"c": [null, null, 1]}}} +(1 row) + +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{"a": []}'); +update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1'; +select * from test_jsonb_subscript; + id | test_json +----+--------------------------------------- + 1 | {"a": [null, {"c": [null, null, 1]}]} +(1 row) + +-- trying replace assuming a composite object, but it's an element or a value +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{"a": 1}'); +update test_jsonb_subscript set test_json['a']['b'] = '1'; +ERROR: cannot replace existing key +DETAIL: The path assumes key is a composite object, but it is a scalar value. +update test_jsonb_subscript set test_json['a']['b']['c'] = '1'; +ERROR: cannot replace existing key +DETAIL: The path assumes key is a composite object, but it is a scalar value. +update test_jsonb_subscript set test_json['a'][0] = '1'; +ERROR: cannot replace existing key +DETAIL: The path assumes key is a composite object, but it is a scalar value. +update test_jsonb_subscript set test_json['a'][0]['c'] = '1'; +ERROR: cannot replace existing key +DETAIL: The path assumes key is a composite object, but it is a scalar value. +update test_jsonb_subscript set test_json['a'][0][0] = '1'; +ERROR: cannot replace existing key +DETAIL: The path assumes key is a composite object, but it is a scalar value. +-- trying replace assuming a composite object, but it's a raw scalar +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, 'null'); +update test_jsonb_subscript set test_json[0] = '1'; +ERROR: cannot replace existing key +DETAIL: The path assumes key is a composite object, but it is a scalar value. +update test_jsonb_subscript set test_json[0][0] = '1'; +ERROR: cannot replace existing key +DETAIL: The path assumes key is a composite object, but it is a scalar value. +-- try some things with short-header and toasted subscript values +drop table test_jsonb_subscript; +create temp table test_jsonb_subscript ( + id text, + test_json jsonb +); +insert into test_jsonb_subscript values('foo', '{"foo": "bar"}'); +insert into test_jsonb_subscript + select s, ('{"' || s || '": "bar"}')::jsonb from repeat('xyzzy', 500) s; +select length(id), test_json[id] from test_jsonb_subscript; + length | test_json +--------+----------- + 3 | "bar" + 2500 | "bar" +(2 rows) + +update test_jsonb_subscript set test_json[id] = '"baz"'; +select length(id), test_json[id] from test_jsonb_subscript; + length | test_json +--------+----------- + 3 | "baz" + 2500 | "baz" +(2 rows) + +\x +table test_jsonb_subscript; +-[ RECORD 1 ]-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +id | foo +test_json | {"foo": "baz"} +-[ RECORD 2 ]-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +id | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzy +test_json | {"xyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzyxyzzy": "baz"} + +\x +-- jsonb to tsvector +select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb); + to_tsvector +--------------------------------------------------------------------------- + 'aaa':1 'bbb':2 'ccc':4 'ddd':3 'eee':6 'fff':7 'ggg':8 'hhh':10 'iii':11 +(1 row) + +-- jsonb to tsvector with config +select to_tsvector('simple', '{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb); + to_tsvector +--------------------------------------------------------------------------- + 'aaa':1 'bbb':2 'ccc':4 'ddd':3 'eee':6 'fff':7 'ggg':8 'hhh':10 'iii':11 +(1 row) + +-- jsonb to tsvector with stop words +select to_tsvector('english', '{"a": "aaa in bbb ddd ccc", "b": ["the eee fff ggg"], "c": {"d": "hhh. iii"}}'::jsonb); + to_tsvector +---------------------------------------------------------------------------- + 'aaa':1 'bbb':3 'ccc':5 'ddd':4 'eee':8 'fff':9 'ggg':10 'hhh':12 'iii':13 +(1 row) + +-- jsonb to tsvector with numeric values +select to_tsvector('english', '{"a": "aaa in bbb ddd ccc", "b": 123, "c": 456}'::jsonb); + to_tsvector +--------------------------------- + 'aaa':1 'bbb':3 'ccc':5 'ddd':4 +(1 row) + +-- jsonb_to_tsvector +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"all"'); + jsonb_to_tsvector +---------------------------------------------------------------------------------------- + '123':8 '456':12 'aaa':2 'b':6 'bbb':4 'c':10 'd':14 'f':18 'fals':20 'g':22 'true':16 +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"key"'); + jsonb_to_tsvector +-------------------------------- + 'b':2 'c':4 'd':6 'f':8 'g':10 +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"string"'); + jsonb_to_tsvector +------------------- + 'aaa':1 'bbb':3 +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"numeric"'); + jsonb_to_tsvector +------------------- + '123':1 '456':3 +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"boolean"'); + jsonb_to_tsvector +------------------- + 'fals':3 'true':1 +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '["string", "numeric"]'); + jsonb_to_tsvector +--------------------------------- + '123':5 '456':7 'aaa':1 'bbb':3 +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"all"'); + jsonb_to_tsvector +---------------------------------------------------------------------------------------- + '123':8 '456':12 'aaa':2 'b':6 'bbb':4 'c':10 'd':14 'f':18 'fals':20 'g':22 'true':16 +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"key"'); + jsonb_to_tsvector +-------------------------------- + 'b':2 'c':4 'd':6 'f':8 'g':10 +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"string"'); + jsonb_to_tsvector +------------------- + 'aaa':1 'bbb':3 +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"numeric"'); + jsonb_to_tsvector +------------------- + '123':1 '456':3 +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"boolean"'); + jsonb_to_tsvector +------------------- + 'fals':3 'true':1 +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '["string", "numeric"]'); + jsonb_to_tsvector +--------------------------------- + '123':5 '456':7 'aaa':1 'bbb':3 +(1 row) + +-- to_tsvector corner cases +select to_tsvector('""'::jsonb); + to_tsvector +------------- + +(1 row) + +select to_tsvector('{}'::jsonb); + to_tsvector +------------- + +(1 row) + +select to_tsvector('[]'::jsonb); + to_tsvector +------------- + +(1 row) + +select to_tsvector('null'::jsonb); + to_tsvector +------------- + +(1 row) + +-- jsonb_to_tsvector corner cases +select jsonb_to_tsvector('""'::jsonb, '"all"'); + jsonb_to_tsvector +------------------- + +(1 row) + +select jsonb_to_tsvector('{}'::jsonb, '"all"'); + jsonb_to_tsvector +------------------- + +(1 row) + +select jsonb_to_tsvector('[]'::jsonb, '"all"'); + jsonb_to_tsvector +------------------- + +(1 row) + +select jsonb_to_tsvector('null'::jsonb, '"all"'); + jsonb_to_tsvector +------------------- + +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '""'); +ERROR: wrong flag in flag array: "" +HINT: Possible values are: "string", "numeric", "boolean", "key", and "all". +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '{}'); +ERROR: wrong flag type, only arrays and scalars are allowed +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '[]'); + jsonb_to_tsvector +------------------- + +(1 row) + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, 'null'); +ERROR: flag array element is not a string +HINT: Possible values are: "string", "numeric", "boolean", "key", and "all". +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '["all", null]'); +ERROR: flag array element is not a string +HINT: Possible values are: "string", "numeric", "boolean", "key", and "all". +-- ts_headline for jsonb +select ts_headline('{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::jsonb, tsquery('bbb & ddd & hhh')); + ts_headline +------------------------------------------------------------------------------------------------------------------ + {"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]} +(1 row) + +select ts_headline('english', '{"a": "aaa bbb", "b": {"c": "ccc ddd fff"}, "d": ["ggg hhh", "iii jjj"]}'::jsonb, tsquery('bbb & ddd & hhh')); + ts_headline +----------------------------------------------------------------------------------------------- + {"a": "aaa bbb", "b": {"c": "ccc ddd fff"}, "d": ["ggg hhh", "iii jjj"]} +(1 row) + +select ts_headline('{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::jsonb, tsquery('bbb & ddd & hhh'), 'StartSel = <, StopSel = >'); + ts_headline +--------------------------------------------------------------------------------------------------- + {"a": "aaa ", "b": {"c": "ccc fff", "c1": "ccc1 ddd1"}, "d": ["ggg ", "iii jjj"]} +(1 row) + +select ts_headline('english', '{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::jsonb, tsquery('bbb & ddd & hhh'), 'StartSel = <, StopSel = >'); + ts_headline +--------------------------------------------------------------------------------------------------- + {"a": "aaa ", "b": {"c": "ccc fff", "c1": "ccc1 ddd1"}, "d": ["ggg ", "iii jjj"]} +(1 row) + +-- corner cases for ts_headline with jsonb +select ts_headline('null'::jsonb, tsquery('aaa & bbb')); + ts_headline +------------- + null +(1 row) + +select ts_headline('{}'::jsonb, tsquery('aaa & bbb')); + ts_headline +------------- + {} +(1 row) + +select ts_headline('[]'::jsonb, tsquery('aaa & bbb')); + ts_headline +------------- + [] +(1 row) + +-- casts +select 'true'::jsonb::bool; + bool +------ + t +(1 row) + +select '[]'::jsonb::bool; +ERROR: cannot cast jsonb array to type boolean +select '1.0'::jsonb::float; + float8 +-------- + 1 +(1 row) + +select '[1.0]'::jsonb::float; +ERROR: cannot cast jsonb array to type double precision +select '12345'::jsonb::int4; + int4 +------- + 12345 +(1 row) + +select '"hello"'::jsonb::int4; +ERROR: cannot cast jsonb string to type integer +select '12345'::jsonb::numeric; + numeric +--------- + 12345 +(1 row) + +select '{}'::jsonb::numeric; +ERROR: cannot cast jsonb object to type numeric +select '12345.05'::jsonb::numeric; + numeric +---------- + 12345.05 +(1 row) + +select '12345.05'::jsonb::float4; + float4 +---------- + 12345.05 +(1 row) + +select '12345.05'::jsonb::float8; + float8 +---------- + 12345.05 +(1 row) + +select '12345.05'::jsonb::int2; + int2 +------- + 12345 +(1 row) + +select '12345.05'::jsonb::int4; + int4 +------- + 12345 +(1 row) + +select '12345.05'::jsonb::int8; + int8 +------- + 12345 +(1 row) + +select '12345.0000000000000000000000000000000000000000000005'::jsonb::numeric; + numeric +------------------------------------------------------ + 12345.0000000000000000000000000000000000000000000005 +(1 row) + +select '12345.0000000000000000000000000000000000000000000005'::jsonb::float4; + float4 +-------- + 12345 +(1 row) + +select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8; + float8 +-------- + 12345 +(1 row) + +select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2; + int2 +------- + 12345 +(1 row) + +select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4; + int4 +------- + 12345 +(1 row) + +select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8; + int8 +------- + 12345 +(1 row) + diff --git a/src/test/regress/expected/yb.port.limit.out b/src/test/regress/expected/yb.port.limit.out new file mode 100644 index 0000000000000..064e04c0832d5 --- /dev/null +++ b/src/test/regress/expected/yb.port.limit.out @@ -0,0 +1,591 @@ +-- +-- LIMIT +-- Check the LIMIT/OFFSET feature of SELECT +-- +SELECT ''::text AS two, unique1, unique2, stringu1 + FROM onek WHERE unique1 > 50 + ORDER BY unique1 LIMIT 2; + two | unique1 | unique2 | stringu1 +-----+---------+---------+---------- + | 51 | 76 | ZBAAAA + | 52 | 985 | ACAAAA +(2 rows) + +SELECT ''::text AS five, unique1, unique2, stringu1 + FROM onek WHERE unique1 > 60 + ORDER BY unique1 LIMIT 5; + five | unique1 | unique2 | stringu1 +------+---------+---------+---------- + | 61 | 560 | JCAAAA + | 62 | 633 | KCAAAA + | 63 | 296 | LCAAAA + | 64 | 479 | MCAAAA + | 65 | 64 | NCAAAA +(5 rows) + +SELECT ''::text AS two, unique1, unique2, stringu1 + FROM onek WHERE unique1 > 60 AND unique1 < 63 + ORDER BY unique1 LIMIT 5; + two | unique1 | unique2 | stringu1 +-----+---------+---------+---------- + | 61 | 560 | JCAAAA + | 62 | 633 | KCAAAA +(2 rows) + +SELECT ''::text AS three, unique1, unique2, stringu1 + FROM onek WHERE unique1 > 100 + ORDER BY unique1 LIMIT 3 OFFSET 20; + three | unique1 | unique2 | stringu1 +-------+---------+---------+---------- + | 121 | 700 | REAAAA + | 122 | 519 | SEAAAA + | 123 | 777 | TEAAAA +(3 rows) + +SELECT ''::text AS zero, unique1, unique2, stringu1 + FROM onek WHERE unique1 < 50 + ORDER BY unique1 DESC LIMIT 8 OFFSET 99; + zero | unique1 | unique2 | stringu1 +------+---------+---------+---------- +(0 rows) + +SELECT ''::text AS eleven, unique1, unique2, stringu1 + FROM onek WHERE unique1 < 50 + ORDER BY unique1 DESC LIMIT 20 OFFSET 39; + eleven | unique1 | unique2 | stringu1 +--------+---------+---------+---------- + | 10 | 520 | KAAAAA + | 9 | 49 | JAAAAA + | 8 | 653 | IAAAAA + | 7 | 647 | HAAAAA + | 6 | 978 | GAAAAA + | 5 | 541 | FAAAAA + | 4 | 833 | EAAAAA + | 3 | 431 | DAAAAA + | 2 | 326 | CAAAAA + | 1 | 214 | BAAAAA + | 0 | 998 | AAAAAA +(11 rows) + +SELECT ''::text AS ten, unique1, unique2, stringu1 + FROM onek + ORDER BY unique1 OFFSET 990; + ten | unique1 | unique2 | stringu1 +-----+---------+---------+---------- + | 990 | 369 | CMAAAA + | 991 | 426 | DMAAAA + | 992 | 363 | EMAAAA + | 993 | 661 | FMAAAA + | 994 | 695 | GMAAAA + | 995 | 144 | HMAAAA + | 996 | 258 | IMAAAA + | 997 | 21 | JMAAAA + | 998 | 549 | KMAAAA + | 999 | 152 | LMAAAA +(10 rows) + +SELECT ''::text AS five, unique1, unique2, stringu1 + FROM onek + ORDER BY unique1 OFFSET 990 LIMIT 5; + five | unique1 | unique2 | stringu1 +------+---------+---------+---------- + | 990 | 369 | CMAAAA + | 991 | 426 | DMAAAA + | 992 | 363 | EMAAAA + | 993 | 661 | FMAAAA + | 994 | 695 | GMAAAA +(5 rows) + +SELECT ''::text AS five, unique1, unique2, stringu1 + FROM onek + ORDER BY unique1 LIMIT 5 OFFSET 900; + five | unique1 | unique2 | stringu1 +------+---------+---------+---------- + | 900 | 913 | QIAAAA + | 901 | 931 | RIAAAA + | 902 | 702 | SIAAAA + | 903 | 641 | TIAAAA + | 904 | 793 | UIAAAA +(5 rows) + +-- Test null limit and offset. The planner would discard a simple null +-- constant, so to ensure executor is exercised, do this: +select * from int8_tbl limit (case when random() < 0.5 then null::bigint end); + q1 | q2 +------------------+------------------- + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(5 rows) + +select * from int8_tbl offset (case when random() < 0.5 then null::bigint end); + q1 | q2 +------------------+------------------- + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(5 rows) + +-- Test assorted cases involving backwards fetch from a LIMIT plan node +begin; +declare c1 cursor for select * from int8_tbl limit 10; +fetch all in c1; + q1 | q2 +------------------+------------------- + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(5 rows) + +fetch 1 in c1; + q1 | q2 +----+---- +(0 rows) + +/* YB: backward fetch is not supported, so we also can't "re-fetch" rows from the cursor +fetch backward 1 in c1; +fetch backward all in c1; +fetch backward 1 in c1; +fetch all in c1; +*/ -- YB: backward fetch is not supported +declare c2 cursor for select * from int8_tbl limit 3; +fetch all in c2; + q1 | q2 +------------------+------------------ + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 +(3 rows) + +fetch 1 in c2; + q1 | q2 +----+---- +(0 rows) + +/* YB: backward fetch is not supported, so we also can't "re-fetch" rows from the cursor +fetch backward 1 in c2; +fetch backward all in c2; +fetch backward 1 in c2; +fetch all in c2; +*/ -- YB: backward fetch is not supported +declare c3 cursor for select * from int8_tbl offset 3; +fetch all in c3; + q1 | q2 +------------------+------------------- + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(2 rows) + +fetch 1 in c3; + q1 | q2 +----+---- +(0 rows) + +/* YB: backward fetch is not supported, so we also can't "re-fetch" rows from the cursor +fetch backward 1 in c3; +fetch backward all in c3; +fetch backward 1 in c3; +fetch all in c3; +*/ -- YB: backward fetch is not supported +declare c4 cursor for select * from int8_tbl offset 10; +fetch all in c4; + q1 | q2 +----+---- +(0 rows) + +fetch 1 in c4; + q1 | q2 +----+---- +(0 rows) + +/* YB: backward fetch is not supported, so we also can't "re-fetch" rows from the cursor +fetch backward 1 in c4; +fetch backward all in c4; +fetch backward 1 in c4; +fetch all in c4; +*/ -- YB: backward fetch is not supported +declare c5 cursor for select * from int8_tbl order by q1 fetch first 2 rows with ties; +fetch all in c5; + q1 | q2 +-----+------------------ + 123 | 456 + 123 | 4567890123456789 +(2 rows) + +fetch 1 in c5; + q1 | q2 +----+---- +(0 rows) + +fetch backward 1 in c5; -- YB: include a failure so that if this is supported, this test will fail. +ERROR: FETCH BACKWARD not supported yet +LINE 1: fetch backward 1 in c5; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/6514. React with thumbs up to raise its priority +/* YB: backward fetch is not supported, so we also can't "re-fetch" rows from the cursor +fetch backward 1 in c5; +fetch all in c5; +fetch backward all in c5; +fetch all in c5; +fetch backward all in c5; +*/ -- YB: backward fetch is not supported +rollback; +-- Stress test for variable LIMIT in conjunction with bounded-heap sorting +SELECT + (SELECT n + FROM (VALUES (1)) AS x, + (SELECT n FROM generate_series(1,10) AS n + ORDER BY n LIMIT 1 OFFSET s-1) AS y) AS z + FROM generate_series(1,10) AS s; + z +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +-- +-- Test behavior of volatile and set-returning functions in conjunction +-- with ORDER BY and LIMIT. +-- +create temp sequence testseq; +explain (verbose, costs off) +select unique1, unique2, nextval('testseq') + from tenk1 order by unique2 limit 10; + QUERY PLAN +---------------------------------------------------------------- + Limit + Output: unique1, unique2, (nextval('testseq'::regclass)) + -> Index Scan using tenk1_unique2 on public.tenk1 + Output: unique1, unique2, nextval('testseq'::regclass) +(4 rows) + +select unique1, unique2, nextval('testseq') + from tenk1 order by unique2 limit 10; + unique1 | unique2 | nextval +---------+---------+--------- + 8800 | 0 | 1 + 1891 | 1 | 2 + 3420 | 2 | 3 + 9850 | 3 | 4 + 7164 | 4 | 5 + 8009 | 5 | 6 + 5057 | 6 | 7 + 6701 | 7 | 8 + 4321 | 8 | 9 + 3043 | 9 | 10 +(10 rows) + +select currval('testseq'); + currval +--------- + 10 +(1 row) + +explain (verbose, costs off) +select unique1, unique2, nextval('testseq') + from tenk1 order by tenthous limit 10; + QUERY PLAN +-------------------------------------------------------------------------- + Limit + Output: unique1, unique2, (nextval('testseq'::regclass)), tenthous + -> Result + Output: unique1, unique2, nextval('testseq'::regclass), tenthous + -> Sort + Output: unique1, unique2, tenthous + Sort Key: tenk1.tenthous + -> Seq Scan on public.tenk1 + Output: unique1, unique2, tenthous +(9 rows) + +select unique1, unique2, nextval('testseq') + from tenk1 order by tenthous limit 10; + unique1 | unique2 | nextval +---------+---------+--------- + 0 | 9998 | 11 + 1 | 2838 | 12 + 2 | 2716 | 13 + 3 | 5679 | 14 + 4 | 1621 | 15 + 5 | 5557 | 16 + 6 | 2855 | 17 + 7 | 8518 | 18 + 8 | 5435 | 19 + 9 | 4463 | 20 +(10 rows) + +select currval('testseq'); + currval +--------- + 20 +(1 row) + +explain (verbose, costs off) +select unique1, unique2, generate_series(1,10) + from tenk1 order by unique2 limit 7; + QUERY PLAN +------------------------------------------------------------ + Limit + Output: unique1, unique2, (generate_series(1, 10)) + -> ProjectSet + Output: unique1, unique2, generate_series(1, 10) + -> Index Scan using tenk1_unique2 on public.tenk1 + Output: unique1, unique2 +(6 rows) + +select unique1, unique2, generate_series(1,10) + from tenk1 order by unique2 limit 7; + unique1 | unique2 | generate_series +---------+---------+----------------- + 8800 | 0 | 1 + 8800 | 0 | 2 + 8800 | 0 | 3 + 8800 | 0 | 4 + 8800 | 0 | 5 + 8800 | 0 | 6 + 8800 | 0 | 7 +(7 rows) + +explain (verbose, costs off) +select unique1, unique2, generate_series(1,10) + from tenk1 order by tenthous limit 7; + QUERY PLAN +-------------------------------------------------------------------- + Limit + Output: unique1, unique2, (generate_series(1, 10)), tenthous + -> ProjectSet + Output: unique1, unique2, generate_series(1, 10), tenthous + -> Sort + Output: unique1, unique2, tenthous + Sort Key: tenk1.tenthous + -> Seq Scan on public.tenk1 + Output: unique1, unique2, tenthous +(9 rows) + +select unique1, unique2, generate_series(1,10) + from tenk1 order by tenthous limit 7; + unique1 | unique2 | generate_series +---------+---------+----------------- + 0 | 9998 | 1 + 0 | 9998 | 2 + 0 | 9998 | 3 + 0 | 9998 | 4 + 0 | 9998 | 5 + 0 | 9998 | 6 + 0 | 9998 | 7 +(7 rows) + +-- use of random() is to keep planner from folding the expressions together +explain (verbose, costs off) +select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + ProjectSet + Output: generate_series(0, 2), generate_series(((random() * '0.1'::double precision))::integer, 2) + -> Result *RESULT* +(3 rows) + +select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2; + s1 | s2 +----+---- + 0 | 0 + 1 | 1 + 2 | 2 +(3 rows) + +explain (verbose, costs off) +select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2 +order by s2 desc; + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Sort + Output: (generate_series(0, 2)), (generate_series(((random() * '0.1'::double precision))::integer, 2)) + Sort Key: (generate_series(((random() * '0.1'::double precision))::integer, 2)) DESC + -> ProjectSet + Output: generate_series(0, 2), generate_series(((random() * '0.1'::double precision))::integer, 2) + -> Result *RESULT* +(6 rows) + +select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2 +order by s2 desc; + s1 | s2 +----+---- + 2 | 2 + 1 | 1 + 0 | 0 +(3 rows) + +-- test for failure to set all aggregates' aggtranstype +explain (verbose, costs off) +select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 + from tenk1 group by thousand order by thousand limit 3; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------- + Limit + Output: (sum(tenthous)), (((sum(tenthous))::double precision + (random() * '0'::double precision))), thousand + -> GroupAggregate + Output: sum(tenthous), ((sum(tenthous))::double precision + (random() * '0'::double precision)), thousand + Group Key: tenk1.thousand + -> Index Only Scan using tenk1_thous_tenthous on public.tenk1 + Output: thousand, tenthous +(7 rows) + +select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 + from tenk1 group by thousand order by thousand limit 3; + s1 | s2 +-------+------- + 45000 | 45000 + 45010 | 45010 + 45020 | 45020 +(3 rows) + +-- +-- FETCH FIRST +-- Check the WITH TIES clause +-- +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 2 ROW WITH TIES; + thousand +---------- + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 +(10 rows) + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST ROWS WITH TIES; + thousand +---------- + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 +(10 rows) + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 1 ROW WITH TIES; + thousand +---------- + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 +(10 rows) + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 2 ROW ONLY; + thousand +---------- + 0 + 0 +(2 rows) + +-- SKIP LOCKED and WITH TIES are incompatible +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 1 ROW WITH TIES FOR UPDATE SKIP LOCKED; +ERROR: SKIP LOCKED and WITH TIES options cannot be used together +-- should fail +SELECT ''::text AS two, unique1, unique2, stringu1 + FROM onek WHERE unique1 > 50 + FETCH FIRST 2 ROW WITH TIES; +ERROR: WITH TIES cannot be specified without ORDER BY clause +-- test ruleutils +CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand FETCH FIRST 5 ROWS WITH TIES OFFSET 10; +\d+ limit_thousand_v_1 + View "public.limit_thousand_v_1" + Column | Type | Collation | Nullable | Default | Storage | Description +----------+---------+-----------+----------+---------+---------+------------- + thousand | integer | | | | plain | +View definition: + SELECT onek.thousand + FROM onek + WHERE onek.thousand < 995 + ORDER BY onek.thousand + OFFSET 10 + FETCH FIRST 5 ROWS WITH TIES; + +CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand OFFSET 10 FETCH FIRST 5 ROWS ONLY; +\d+ limit_thousand_v_2 + View "public.limit_thousand_v_2" + Column | Type | Collation | Nullable | Default | Storage | Description +----------+---------+-----------+----------+---------+---------+------------- + thousand | integer | | | | plain | +View definition: + SELECT onek.thousand + FROM onek + WHERE onek.thousand < 995 + ORDER BY onek.thousand + OFFSET 10 + LIMIT 5; + +CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand FETCH FIRST NULL ROWS WITH TIES; -- fails +ERROR: row count cannot be null in FETCH FIRST ... WITH TIES clause +CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand FETCH FIRST (NULL+1) ROWS WITH TIES; +\d+ limit_thousand_v_3 + View "public.limit_thousand_v_3" + Column | Type | Collation | Nullable | Default | Storage | Description +----------+---------+-----------+----------+---------+---------+------------- + thousand | integer | | | | plain | +View definition: + SELECT onek.thousand + FROM onek + WHERE onek.thousand < 995 + ORDER BY onek.thousand + FETCH FIRST (NULL::integer + 1) ROWS WITH TIES; + +CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand FETCH FIRST NULL ROWS ONLY; +\d+ limit_thousand_v_4 + View "public.limit_thousand_v_4" + Column | Type | Collation | Nullable | Default | Storage | Description +----------+---------+-----------+----------+---------+---------+------------- + thousand | integer | | | | plain | +View definition: + SELECT onek.thousand + FROM onek + WHERE onek.thousand < 995 + ORDER BY onek.thousand + LIMIT ALL; + +-- leave these views diff --git a/src/test/regress/expected/yb.port.lock.out b/src/test/regress/expected/yb.port.lock.out new file mode 100644 index 0000000000000..9888922153b65 --- /dev/null +++ b/src/test/regress/expected/yb.port.lock.out @@ -0,0 +1,302 @@ +-- +-- Test the LOCK statement +-- +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX +\set regresslib :libdir '/regress' :dlsuffix +-- Setup +CREATE SCHEMA lock_schema1; +SET search_path = lock_schema1; +CREATE TABLE lock_tbl1 (a BIGINT); +CREATE TABLE lock_tbl1a (a BIGINT); +CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1; +CREATE VIEW lock_view2(a,b) AS SELECT * FROM lock_tbl1, lock_tbl1a; +CREATE VIEW lock_view3 AS SELECT * from lock_view2; +CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1; +CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a); +CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub; +CREATE ROLE regress_rol_lock1; +ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1; +GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1; +-- Try all valid lock options; also try omitting the optional TABLE keyword. +BEGIN TRANSACTION; +LOCK TABLE lock_tbl1 IN ACCESS SHARE MODE; +LOCK lock_tbl1 IN ROW SHARE MODE; +LOCK TABLE lock_tbl1 IN ROW EXCLUSIVE MODE; +LOCK TABLE lock_tbl1 IN SHARE UPDATE EXCLUSIVE MODE; +LOCK TABLE lock_tbl1 IN SHARE MODE; +LOCK lock_tbl1 IN SHARE ROW EXCLUSIVE MODE; +LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE; +LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE; +ROLLBACK; +-- Try using NOWAIT along with valid options. +BEGIN TRANSACTION; +LOCK TABLE lock_tbl1 IN ACCESS SHARE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN ROW SHARE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN ROW EXCLUSIVE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN SHARE UPDATE EXCLUSIVE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT; +ROLLBACK; +-- Verify that we can lock views. +BEGIN TRANSACTION; +LOCK TABLE lock_view1 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) + pg_sleep +---------- + +(1 row) + +-- lock_view1 and lock_tbl1 are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; + relname +------------ + lock_tbl1 + lock_view1 +(2 rows) + +ROLLBACK; +BEGIN TRANSACTION; +LOCK TABLE lock_view2 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) + pg_sleep +---------- + +(1 row) + +-- lock_view1, lock_tbl1, and lock_tbl1a are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; + relname +------------ + lock_tbl1 + lock_tbl1a + lock_view2 +(3 rows) + +ROLLBACK; +BEGIN TRANSACTION; +LOCK TABLE lock_view3 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) + pg_sleep +---------- + +(1 row) + +-- lock_view3, lock_view2, lock_tbl1, and lock_tbl1a are locked recursively. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; + relname +------------ + lock_tbl1 + lock_tbl1a + lock_view2 + lock_view3 +(4 rows) + +ROLLBACK; +BEGIN TRANSACTION; +LOCK TABLE lock_view4 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) + pg_sleep +---------- + +(1 row) + +-- lock_view4, lock_tbl1, and lock_tbl1a are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; + relname +------------ + lock_tbl1 + lock_tbl1a + lock_view4 +(3 rows) + +ROLLBACK; +BEGIN TRANSACTION; +LOCK TABLE lock_view5 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) + pg_sleep +---------- + +(1 row) + +-- lock_view5, lock_tbl1, and lock_tbl1a are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; + relname +------------ + lock_tbl1 + lock_tbl1a + lock_view5 +(3 rows) + +ROLLBACK; +BEGIN TRANSACTION; +LOCK TABLE lock_view6 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) + pg_sleep +---------- + +(1 row) + +-- lock_view6 an lock_tbl1 are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; + relname +------------ + lock_tbl1 + lock_view6 +(2 rows) + +ROLLBACK; +-- Verify that we cope with infinite recursion in view definitions. +CREATE OR REPLACE VIEW lock_view2 AS SELECT * from lock_view3; +BEGIN TRANSACTION; +LOCK TABLE lock_view2 IN EXCLUSIVE MODE; +ROLLBACK; +CREATE VIEW lock_view7 AS SELECT * from lock_view2; +BEGIN TRANSACTION; +LOCK TABLE lock_view7 IN EXCLUSIVE MODE; +ROLLBACK; +-- Verify that we can lock a table with inheritance children. +CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1); +CREATE TABLE lock_tbl3 () INHERITS (lock_tbl2); +BEGIN TRANSACTION; +LOCK TABLE lock_tbl1 * IN ACCESS EXCLUSIVE MODE; +ROLLBACK; +-- Child tables are locked without granting explicit permission to do so as +-- long as we have permission to lock the parent. +GRANT UPDATE ON TABLE lock_tbl1 TO regress_rol_lock1; +SET ROLE regress_rol_lock1; +-- fail when child locked directly +BEGIN; +LOCK TABLE lock_tbl2; +ERROR: permission denied for table lock_tbl2 +ROLLBACK; +BEGIN; +LOCK TABLE lock_tbl1 * IN ACCESS EXCLUSIVE MODE; +ROLLBACK; +BEGIN; +LOCK TABLE ONLY lock_tbl1; +ROLLBACK; +RESET ROLE; +REVOKE UPDATE ON TABLE lock_tbl1 FROM regress_rol_lock1; +-- Tables referred to by views are locked without explicit permission to do so +-- as long as we have permission to lock the view itself. +SET ROLE regress_rol_lock1; +-- fail without permissions on the view +BEGIN; +LOCK TABLE lock_view1; +ERROR: permission denied for view lock_view1 +ROLLBACK; +RESET ROLE; +GRANT UPDATE ON TABLE lock_view1 TO regress_rol_lock1; +GRANT yb_db_admin TO regress_rol_lock1; -- YB: for pg_locks access +SET ROLE regress_rol_lock1; +BEGIN; +LOCK TABLE lock_view1 IN ACCESS EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) + pg_sleep +---------- + +(1 row) + +-- lock_view1 and lock_tbl1 (plus children lock_tbl2 and lock_tbl3) are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'AccessExclusiveLock' + order by relname; + relname +------------ + lock_tbl1 + lock_tbl2 + lock_tbl3 + lock_view1 +(4 rows) + +ROLLBACK; +RESET ROLE; +REVOKE UPDATE ON TABLE lock_view1 FROM regress_rol_lock1; +REVOKE yb_db_admin FROM regress_rol_lock1; -- YB: for pg_locks access +-- Tables referred to by security invoker views require explicit permission to +-- be locked. +CREATE VIEW lock_view8 WITH (security_invoker) AS SELECT * FROM lock_tbl1; +SET ROLE regress_rol_lock1; +-- fail without permissions on the view +BEGIN; +LOCK TABLE lock_view8; +ERROR: permission denied for view lock_view8 +ROLLBACK; +RESET ROLE; +GRANT UPDATE ON TABLE lock_view8 TO regress_rol_lock1; +SET ROLE regress_rol_lock1; +-- fail without permissions on the table referenced by the view +BEGIN; +LOCK TABLE lock_view8; +ERROR: permission denied for table lock_tbl1 +ROLLBACK; +RESET ROLE; +GRANT UPDATE ON TABLE lock_tbl1 TO regress_rol_lock1; +BEGIN; +LOCK TABLE lock_view8 IN ACCESS EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) + pg_sleep +---------- + +(1 row) + +-- lock_view8 and lock_tbl1 (plus children lock_tbl2 and lock_tbl3) are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'AccessExclusiveLock' + order by relname; + relname +------------ + lock_tbl1 + lock_tbl2 + lock_tbl3 + lock_view8 +(4 rows) + +ROLLBACK; +RESET ROLE; +REVOKE UPDATE ON TABLE lock_view8 FROM regress_rol_lock1; +-- +-- Clean up +-- +DROP VIEW lock_view8; +DROP VIEW lock_view7; +DROP VIEW lock_view6; +DROP VIEW lock_view5; +DROP VIEW lock_view4; +DROP VIEW lock_view3 CASCADE; +NOTICE: drop cascades to view lock_view2 +DROP VIEW lock_view1; +DROP TABLE lock_tbl3; +DROP TABLE lock_tbl2; +DROP TABLE lock_tbl1; +DROP TABLE lock_tbl1a; +DROP SCHEMA lock_schema1 CASCADE; +DROP ROLE regress_rol_lock1; +-- atomic ops tests +RESET search_path; +CREATE FUNCTION test_atomic_ops() + RETURNS bool + AS :'regresslib' + LANGUAGE C; +SELECT test_atomic_ops(); + test_atomic_ops +----------------- + t +(1 row) + diff --git a/src/test/regress/expected/yb.port.macaddr.out b/src/test/regress/expected/yb.port.macaddr.out new file mode 100644 index 0000000000000..9862ed73ad19b --- /dev/null +++ b/src/test/regress/expected/yb.port.macaddr.out @@ -0,0 +1,161 @@ +-- +-- macaddr +-- +CREATE TABLE macaddr_data (a int, b macaddr); +INSERT INTO macaddr_data VALUES (1, '08:00:2b:01:02:03'); +INSERT INTO macaddr_data VALUES (2, '08-00-2b-01-02-03'); +INSERT INTO macaddr_data VALUES (3, '08002b:010203'); +INSERT INTO macaddr_data VALUES (4, '08002b-010203'); +INSERT INTO macaddr_data VALUES (5, '0800.2b01.0203'); +INSERT INTO macaddr_data VALUES (6, '0800-2b01-0203'); +INSERT INTO macaddr_data VALUES (7, '08002b010203'); +INSERT INTO macaddr_data VALUES (8, '0800:2b01:0203'); -- invalid +ERROR: invalid input syntax for type macaddr: "0800:2b01:0203" +LINE 1: INSERT INTO macaddr_data VALUES (8, '0800:2b01:0203'); + ^ +INSERT INTO macaddr_data VALUES (9, 'not even close'); -- invalid +ERROR: invalid input syntax for type macaddr: "not even close" +LINE 1: INSERT INTO macaddr_data VALUES (9, 'not even close'); + ^ +INSERT INTO macaddr_data VALUES (10, '08:00:2b:01:02:04'); +INSERT INTO macaddr_data VALUES (11, '08:00:2b:01:02:02'); +INSERT INTO macaddr_data VALUES (12, '08:00:2a:01:02:03'); +INSERT INTO macaddr_data VALUES (13, '08:00:2c:01:02:03'); +INSERT INTO macaddr_data VALUES (14, '08:00:2a:01:02:04'); +SELECT * FROM macaddr_data ORDER BY a; + a | b +----+------------------- + 1 | 08:00:2b:01:02:03 + 2 | 08:00:2b:01:02:03 + 3 | 08:00:2b:01:02:03 + 4 | 08:00:2b:01:02:03 + 5 | 08:00:2b:01:02:03 + 6 | 08:00:2b:01:02:03 + 7 | 08:00:2b:01:02:03 + 10 | 08:00:2b:01:02:04 + 11 | 08:00:2b:01:02:02 + 12 | 08:00:2a:01:02:03 + 13 | 08:00:2c:01:02:03 + 14 | 08:00:2a:01:02:04 +(12 rows) + +-- TODO(neil) Create index on non-empty table is not yet supported. +-- CREATE INDEX macaddr_data_lsm ON macaddr_data USING lsm (b); +-- CREATE INDEX macaddr_data_hash ON macaddr_data USING hash (b); +SELECT a, b, trunc(b) FROM macaddr_data ORDER BY 2, 1; + a | b | trunc +----+-------------------+------------------- + 12 | 08:00:2a:01:02:03 | 08:00:2a:00:00:00 + 14 | 08:00:2a:01:02:04 | 08:00:2a:00:00:00 + 11 | 08:00:2b:01:02:02 | 08:00:2b:00:00:00 + 1 | 08:00:2b:01:02:03 | 08:00:2b:00:00:00 + 2 | 08:00:2b:01:02:03 | 08:00:2b:00:00:00 + 3 | 08:00:2b:01:02:03 | 08:00:2b:00:00:00 + 4 | 08:00:2b:01:02:03 | 08:00:2b:00:00:00 + 5 | 08:00:2b:01:02:03 | 08:00:2b:00:00:00 + 6 | 08:00:2b:01:02:03 | 08:00:2b:00:00:00 + 7 | 08:00:2b:01:02:03 | 08:00:2b:00:00:00 + 10 | 08:00:2b:01:02:04 | 08:00:2b:00:00:00 + 13 | 08:00:2c:01:02:03 | 08:00:2c:00:00:00 +(12 rows) + +SELECT b < '08:00:2b:01:02:04' FROM macaddr_data WHERE a = 1; -- true + ?column? +---------- + t +(1 row) + +SELECT b > '08:00:2b:01:02:04' FROM macaddr_data WHERE a = 1; -- false + ?column? +---------- + f +(1 row) + +SELECT b > '08:00:2b:01:02:03' FROM macaddr_data WHERE a = 1; -- false + ?column? +---------- + f +(1 row) + +SELECT b <= '08:00:2b:01:02:04' FROM macaddr_data WHERE a = 1; -- true + ?column? +---------- + t +(1 row) + +SELECT b >= '08:00:2b:01:02:04' FROM macaddr_data WHERE a = 1; -- false + ?column? +---------- + f +(1 row) + +SELECT b = '08:00:2b:01:02:03' FROM macaddr_data WHERE a = 1; -- true + ?column? +---------- + t +(1 row) + +SELECT b <> '08:00:2b:01:02:04' FROM macaddr_data WHERE a = 1; -- true + ?column? +---------- + t +(1 row) + +SELECT b <> '08:00:2b:01:02:03' FROM macaddr_data WHERE a = 1; -- false + ?column? +---------- + f +(1 row) + +SELECT ~b FROM macaddr_data ORDER BY a; + ?column? +------------------- + f7:ff:d4:fe:fd:fc + f7:ff:d4:fe:fd:fc + f7:ff:d4:fe:fd:fc + f7:ff:d4:fe:fd:fc + f7:ff:d4:fe:fd:fc + f7:ff:d4:fe:fd:fc + f7:ff:d4:fe:fd:fc + f7:ff:d4:fe:fd:fb + f7:ff:d4:fe:fd:fd + f7:ff:d5:fe:fd:fc + f7:ff:d3:fe:fd:fc + f7:ff:d5:fe:fd:fb +(12 rows) + +SELECT b & '00:00:00:ff:ff:ff' FROM macaddr_data ORDER BY a; + ?column? +------------------- + 00:00:00:01:02:03 + 00:00:00:01:02:03 + 00:00:00:01:02:03 + 00:00:00:01:02:03 + 00:00:00:01:02:03 + 00:00:00:01:02:03 + 00:00:00:01:02:03 + 00:00:00:01:02:04 + 00:00:00:01:02:02 + 00:00:00:01:02:03 + 00:00:00:01:02:03 + 00:00:00:01:02:04 +(12 rows) + +SELECT b | '01:02:03:04:05:06' FROM macaddr_data ORDER BY a; + ?column? +------------------- + 09:02:2b:05:07:07 + 09:02:2b:05:07:07 + 09:02:2b:05:07:07 + 09:02:2b:05:07:07 + 09:02:2b:05:07:07 + 09:02:2b:05:07:07 + 09:02:2b:05:07:07 + 09:02:2b:05:07:06 + 09:02:2b:05:07:06 + 09:02:2b:05:07:07 + 09:02:2f:05:07:07 + 09:02:2b:05:07:06 +(12 rows) + +DROP TABLE macaddr_data; diff --git a/src/test/regress/expected/yb.port.macaddr8.out b/src/test/regress/expected/yb.port.macaddr8.out new file mode 100644 index 0000000000000..945e5e4b5e7b4 --- /dev/null +++ b/src/test/regress/expected/yb.port.macaddr8.out @@ -0,0 +1,355 @@ +-- +-- macaddr8 +-- +-- test various cases of valid and invalid input +-- valid +SELECT '08:00:2b:01:02:03 '::macaddr8; + macaddr8 +------------------------- + 08:00:2b:ff:fe:01:02:03 +(1 row) + +SELECT ' 08:00:2b:01:02:03 '::macaddr8; + macaddr8 +------------------------- + 08:00:2b:ff:fe:01:02:03 +(1 row) + +SELECT ' 08:00:2b:01:02:03'::macaddr8; + macaddr8 +------------------------- + 08:00:2b:ff:fe:01:02:03 +(1 row) + +SELECT '08:00:2b:01:02:03:04:05 '::macaddr8; + macaddr8 +------------------------- + 08:00:2b:01:02:03:04:05 +(1 row) + +SELECT ' 08:00:2b:01:02:03:04:05 '::macaddr8; + macaddr8 +------------------------- + 08:00:2b:01:02:03:04:05 +(1 row) + +SELECT ' 08:00:2b:01:02:03:04:05'::macaddr8; + macaddr8 +------------------------- + 08:00:2b:01:02:03:04:05 +(1 row) + +SELECT '123 08:00:2b:01:02:03'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "123 08:00:2b:01:02:03" +LINE 1: SELECT '123 08:00:2b:01:02:03'::macaddr8; + ^ +SELECT '08:00:2b:01:02:03 123'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "08:00:2b:01:02:03 123" +LINE 1: SELECT '08:00:2b:01:02:03 123'::macaddr8; + ^ +SELECT '123 08:00:2b:01:02:03:04:05'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "123 08:00:2b:01:02:03:04:05" +LINE 1: SELECT '123 08:00:2b:01:02:03:04:05'::macaddr8; + ^ +SELECT '08:00:2b:01:02:03:04:05 123'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "08:00:2b:01:02:03:04:05 123" +LINE 1: SELECT '08:00:2b:01:02:03:04:05 123'::macaddr8; + ^ +SELECT '08:00:2b:01:02:03:04:05:06:07'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "08:00:2b:01:02:03:04:05:06:07" +LINE 1: SELECT '08:00:2b:01:02:03:04:05:06:07'::macaddr8; + ^ +SELECT '08-00-2b-01-02-03-04-05-06-07'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "08-00-2b-01-02-03-04-05-06-07" +LINE 1: SELECT '08-00-2b-01-02-03-04-05-06-07'::macaddr8; + ^ +SELECT '08002b:01020304050607'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "08002b:01020304050607" +LINE 1: SELECT '08002b:01020304050607'::macaddr8; + ^ +SELECT '08002b01020304050607'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "08002b01020304050607" +LINE 1: SELECT '08002b01020304050607'::macaddr8; + ^ +SELECT '0z002b0102030405'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "0z002b0102030405" +LINE 1: SELECT '0z002b0102030405'::macaddr8; + ^ +SELECT '08002b010203xyza'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "08002b010203xyza" +LINE 1: SELECT '08002b010203xyza'::macaddr8; + ^ +SELECT '08:00-2b:01:02:03:04:05'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "08:00-2b:01:02:03:04:05" +LINE 1: SELECT '08:00-2b:01:02:03:04:05'::macaddr8; + ^ +SELECT '08:00-2b:01:02:03:04:05'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "08:00-2b:01:02:03:04:05" +LINE 1: SELECT '08:00-2b:01:02:03:04:05'::macaddr8; + ^ +SELECT '08:00:2b:01.02:03:04:05'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "08:00:2b:01.02:03:04:05" +LINE 1: SELECT '08:00:2b:01.02:03:04:05'::macaddr8; + ^ +SELECT '08:00:2b:01.02:03:04:05'::macaddr8; -- invalid +ERROR: invalid input syntax for type macaddr8: "08:00:2b:01.02:03:04:05" +LINE 1: SELECT '08:00:2b:01.02:03:04:05'::macaddr8; + ^ +-- test converting a MAC address to modified EUI-64 for inclusion +-- in an ipv6 address +SELECT macaddr8_set7bit('00:08:2b:01:02:03'::macaddr8); + macaddr8_set7bit +------------------------- + 02:08:2b:ff:fe:01:02:03 +(1 row) + +CREATE TABLE macaddr8_data (a int, b macaddr8); +INSERT INTO macaddr8_data VALUES (1, '08:00:2b:01:02:03'); +INSERT INTO macaddr8_data VALUES (2, '08-00-2b-01-02-03'); +INSERT INTO macaddr8_data VALUES (3, '08002b:010203'); +INSERT INTO macaddr8_data VALUES (4, '08002b-010203'); +INSERT INTO macaddr8_data VALUES (5, '0800.2b01.0203'); +INSERT INTO macaddr8_data VALUES (6, '0800-2b01-0203'); +INSERT INTO macaddr8_data VALUES (7, '08002b010203'); +INSERT INTO macaddr8_data VALUES (8, '0800:2b01:0203'); +INSERT INTO macaddr8_data VALUES (9, 'not even close'); -- invalid +ERROR: invalid input syntax for type macaddr8: "not even close" +LINE 1: INSERT INTO macaddr8_data VALUES (9, 'not even close'); + ^ +INSERT INTO macaddr8_data VALUES (10, '08:00:2b:01:02:04'); +INSERT INTO macaddr8_data VALUES (11, '08:00:2b:01:02:02'); +INSERT INTO macaddr8_data VALUES (12, '08:00:2a:01:02:03'); +INSERT INTO macaddr8_data VALUES (13, '08:00:2c:01:02:03'); +INSERT INTO macaddr8_data VALUES (14, '08:00:2a:01:02:04'); +INSERT INTO macaddr8_data VALUES (15, '08:00:2b:01:02:03:04:05'); +INSERT INTO macaddr8_data VALUES (16, '08-00-2b-01-02-03-04-05'); +INSERT INTO macaddr8_data VALUES (17, '08002b:0102030405'); +INSERT INTO macaddr8_data VALUES (18, '08002b-0102030405'); +INSERT INTO macaddr8_data VALUES (19, '0800.2b01.0203.0405'); +INSERT INTO macaddr8_data VALUES (20, '08002b01:02030405'); +INSERT INTO macaddr8_data VALUES (21, '08002b0102030405'); +SELECT * FROM macaddr8_data ORDER BY 1; + a | b +----+------------------------- + 1 | 08:00:2b:ff:fe:01:02:03 + 2 | 08:00:2b:ff:fe:01:02:03 + 3 | 08:00:2b:ff:fe:01:02:03 + 4 | 08:00:2b:ff:fe:01:02:03 + 5 | 08:00:2b:ff:fe:01:02:03 + 6 | 08:00:2b:ff:fe:01:02:03 + 7 | 08:00:2b:ff:fe:01:02:03 + 8 | 08:00:2b:ff:fe:01:02:03 + 10 | 08:00:2b:ff:fe:01:02:04 + 11 | 08:00:2b:ff:fe:01:02:02 + 12 | 08:00:2a:ff:fe:01:02:03 + 13 | 08:00:2c:ff:fe:01:02:03 + 14 | 08:00:2a:ff:fe:01:02:04 + 15 | 08:00:2b:01:02:03:04:05 + 16 | 08:00:2b:01:02:03:04:05 + 17 | 08:00:2b:01:02:03:04:05 + 18 | 08:00:2b:01:02:03:04:05 + 19 | 08:00:2b:01:02:03:04:05 + 20 | 08:00:2b:01:02:03:04:05 + 21 | 08:00:2b:01:02:03:04:05 +(20 rows) + +-- TODO(neil) INDEX non-empty table. +-- CREATE INDEX macaddr8_data_lsm ON macaddr8_data USING lsm (b); +-- CREATE INDEX macaddr8_data_hash ON macaddr8_data USING hash (b); +SELECT a, b, trunc(b) FROM macaddr8_data ORDER BY 2, 1; + a | b | trunc +----+-------------------------+------------------------- + 12 | 08:00:2a:ff:fe:01:02:03 | 08:00:2a:00:00:00:00:00 + 14 | 08:00:2a:ff:fe:01:02:04 | 08:00:2a:00:00:00:00:00 + 15 | 08:00:2b:01:02:03:04:05 | 08:00:2b:00:00:00:00:00 + 16 | 08:00:2b:01:02:03:04:05 | 08:00:2b:00:00:00:00:00 + 17 | 08:00:2b:01:02:03:04:05 | 08:00:2b:00:00:00:00:00 + 18 | 08:00:2b:01:02:03:04:05 | 08:00:2b:00:00:00:00:00 + 19 | 08:00:2b:01:02:03:04:05 | 08:00:2b:00:00:00:00:00 + 20 | 08:00:2b:01:02:03:04:05 | 08:00:2b:00:00:00:00:00 + 21 | 08:00:2b:01:02:03:04:05 | 08:00:2b:00:00:00:00:00 + 11 | 08:00:2b:ff:fe:01:02:02 | 08:00:2b:00:00:00:00:00 + 1 | 08:00:2b:ff:fe:01:02:03 | 08:00:2b:00:00:00:00:00 + 2 | 08:00:2b:ff:fe:01:02:03 | 08:00:2b:00:00:00:00:00 + 3 | 08:00:2b:ff:fe:01:02:03 | 08:00:2b:00:00:00:00:00 + 4 | 08:00:2b:ff:fe:01:02:03 | 08:00:2b:00:00:00:00:00 + 5 | 08:00:2b:ff:fe:01:02:03 | 08:00:2b:00:00:00:00:00 + 6 | 08:00:2b:ff:fe:01:02:03 | 08:00:2b:00:00:00:00:00 + 7 | 08:00:2b:ff:fe:01:02:03 | 08:00:2b:00:00:00:00:00 + 8 | 08:00:2b:ff:fe:01:02:03 | 08:00:2b:00:00:00:00:00 + 10 | 08:00:2b:ff:fe:01:02:04 | 08:00:2b:00:00:00:00:00 + 13 | 08:00:2c:ff:fe:01:02:03 | 08:00:2c:00:00:00:00:00 +(20 rows) + +SELECT b < '08:00:2b:01:02:04' FROM macaddr8_data WHERE a = 1; -- true + ?column? +---------- + t +(1 row) + +SELECT b > '08:00:2b:ff:fe:01:02:04' FROM macaddr8_data WHERE a = 1; -- false + ?column? +---------- + f +(1 row) + +SELECT b > '08:00:2b:ff:fe:01:02:03' FROM macaddr8_data WHERE a = 1; -- false + ?column? +---------- + f +(1 row) + +SELECT b::macaddr <= '08:00:2b:01:02:04' FROM macaddr8_data WHERE a = 1; -- true + ?column? +---------- + t +(1 row) + +SELECT b::macaddr >= '08:00:2b:01:02:04' FROM macaddr8_data WHERE a = 1; -- false + ?column? +---------- + f +(1 row) + +SELECT b = '08:00:2b:ff:fe:01:02:03' FROM macaddr8_data WHERE a = 1; -- true + ?column? +---------- + t +(1 row) + +SELECT b::macaddr <> '08:00:2b:01:02:04'::macaddr FROM macaddr8_data WHERE a = 1; -- true + ?column? +---------- + t +(1 row) + +SELECT b::macaddr <> '08:00:2b:01:02:03'::macaddr FROM macaddr8_data WHERE a = 1; -- false + ?column? +---------- + f +(1 row) + +SELECT b < '08:00:2b:01:02:03:04:06' FROM macaddr8_data WHERE a = 15; -- true + ?column? +---------- + t +(1 row) + +SELECT b > '08:00:2b:01:02:03:04:06' FROM macaddr8_data WHERE a = 15; -- false + ?column? +---------- + f +(1 row) + +SELECT b > '08:00:2b:01:02:03:04:05' FROM macaddr8_data WHERE a = 15; -- false + ?column? +---------- + f +(1 row) + +SELECT b <= '08:00:2b:01:02:03:04:06' FROM macaddr8_data WHERE a = 15; -- true + ?column? +---------- + t +(1 row) + +SELECT b >= '08:00:2b:01:02:03:04:06' FROM macaddr8_data WHERE a = 15; -- false + ?column? +---------- + f +(1 row) + +SELECT b = '08:00:2b:01:02:03:04:05' FROM macaddr8_data WHERE a = 15; -- true + ?column? +---------- + t +(1 row) + +SELECT b <> '08:00:2b:01:02:03:04:06' FROM macaddr8_data WHERE a = 15; -- true + ?column? +---------- + t +(1 row) + +SELECT b <> '08:00:2b:01:02:03:04:05' FROM macaddr8_data WHERE a = 15; -- false + ?column? +---------- + f +(1 row) + +SELECT ~b FROM macaddr8_data ORDER BY a; + ?column? +------------------------- + f7:ff:d4:00:01:fe:fd:fc + f7:ff:d4:00:01:fe:fd:fc + f7:ff:d4:00:01:fe:fd:fc + f7:ff:d4:00:01:fe:fd:fc + f7:ff:d4:00:01:fe:fd:fc + f7:ff:d4:00:01:fe:fd:fc + f7:ff:d4:00:01:fe:fd:fc + f7:ff:d4:00:01:fe:fd:fc + f7:ff:d4:00:01:fe:fd:fb + f7:ff:d4:00:01:fe:fd:fd + f7:ff:d5:00:01:fe:fd:fc + f7:ff:d3:00:01:fe:fd:fc + f7:ff:d5:00:01:fe:fd:fb + f7:ff:d4:fe:fd:fc:fb:fa + f7:ff:d4:fe:fd:fc:fb:fa + f7:ff:d4:fe:fd:fc:fb:fa + f7:ff:d4:fe:fd:fc:fb:fa + f7:ff:d4:fe:fd:fc:fb:fa + f7:ff:d4:fe:fd:fc:fb:fa + f7:ff:d4:fe:fd:fc:fb:fa +(20 rows) + +SELECT b & '00:00:00:ff:ff:ff' FROM macaddr8_data ORDER BY a; + ?column? +------------------------- + 00:00:00:ff:fe:01:02:03 + 00:00:00:ff:fe:01:02:03 + 00:00:00:ff:fe:01:02:03 + 00:00:00:ff:fe:01:02:03 + 00:00:00:ff:fe:01:02:03 + 00:00:00:ff:fe:01:02:03 + 00:00:00:ff:fe:01:02:03 + 00:00:00:ff:fe:01:02:03 + 00:00:00:ff:fe:01:02:04 + 00:00:00:ff:fe:01:02:02 + 00:00:00:ff:fe:01:02:03 + 00:00:00:ff:fe:01:02:03 + 00:00:00:ff:fe:01:02:04 + 00:00:00:01:02:03:04:05 + 00:00:00:01:02:03:04:05 + 00:00:00:01:02:03:04:05 + 00:00:00:01:02:03:04:05 + 00:00:00:01:02:03:04:05 + 00:00:00:01:02:03:04:05 + 00:00:00:01:02:03:04:05 +(20 rows) + +SELECT b | '01:02:03:04:05:06' FROM macaddr8_data ORDER BY a; + ?column? +------------------------- + 09:02:2b:ff:fe:05:07:07 + 09:02:2b:ff:fe:05:07:07 + 09:02:2b:ff:fe:05:07:07 + 09:02:2b:ff:fe:05:07:07 + 09:02:2b:ff:fe:05:07:07 + 09:02:2b:ff:fe:05:07:07 + 09:02:2b:ff:fe:05:07:07 + 09:02:2b:ff:fe:05:07:07 + 09:02:2b:ff:fe:05:07:06 + 09:02:2b:ff:fe:05:07:06 + 09:02:2b:ff:fe:05:07:07 + 09:02:2f:ff:fe:05:07:07 + 09:02:2b:ff:fe:05:07:06 + 09:02:2b:ff:fe:07:05:07 + 09:02:2b:ff:fe:07:05:07 + 09:02:2b:ff:fe:07:05:07 + 09:02:2b:ff:fe:07:05:07 + 09:02:2b:ff:fe:07:05:07 + 09:02:2b:ff:fe:07:05:07 + 09:02:2b:ff:fe:07:05:07 +(20 rows) + +DROP TABLE macaddr8_data; diff --git a/src/test/regress/expected/yb.port.matview.out b/src/test/regress/expected/yb.port.matview.out new file mode 100644 index 0000000000000..16b95ceb042a6 --- /dev/null +++ b/src/test/regress/expected/yb.port.matview.out @@ -0,0 +1,656 @@ +-- create a table to use as a basis for views and materialized views in various combinations +CREATE TABLE mvtest_t (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL); +INSERT INTO mvtest_t VALUES + (1, 'x', 2), + (2, 'x', 3), + (3, 'y', 5), + (4, 'y', 7), + (5, 'z', 11); +-- we want a view based on the table, too, since views present additional challenges +CREATE VIEW mvtest_tv AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type; +SELECT * FROM mvtest_tv ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 11 +(3 rows) + +-- create a materialized view with no data, and confirm correct behavior +EXPLAIN (costs off) + CREATE MATERIALIZED VIEW mvtest_tm AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type WITH NO DATA; + QUERY PLAN +---------------------------- + HashAggregate + Group Key: type + -> Seq Scan on mvtest_t +(3 rows) + +CREATE MATERIALIZED VIEW mvtest_tm AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type WITH NO DATA; +SELECT relispopulated FROM pg_class WHERE oid = 'mvtest_tm'::regclass; + relispopulated +---------------- + f +(1 row) + +SELECT * FROM mvtest_tm ORDER BY type; +ERROR: materialized view "mvtest_tm" has not been populated +HINT: Use the REFRESH MATERIALIZED VIEW command. +REFRESH MATERIALIZED VIEW mvtest_tm; +SELECT relispopulated FROM pg_class WHERE oid = 'mvtest_tm'::regclass; + relispopulated +---------------- + t +(1 row) + +CREATE UNIQUE INDEX mvtest_tm_type ON mvtest_tm (type); +SELECT * FROM mvtest_tm ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 11 +(3 rows) + +-- create various views +EXPLAIN (costs off) + CREATE MATERIALIZED VIEW mvtest_tvm AS SELECT * FROM mvtest_tv ORDER BY type; + QUERY PLAN +---------------------------------- + Sort + Sort Key: mvtest_t.type + -> HashAggregate + Group Key: mvtest_t.type + -> Seq Scan on mvtest_t +(5 rows) + +CREATE MATERIALIZED VIEW mvtest_tvm AS SELECT * FROM mvtest_tv ORDER BY type; +SELECT * FROM mvtest_tvm ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 11 +(3 rows) + +CREATE MATERIALIZED VIEW mvtest_tmm AS SELECT sum(totamt) AS grandtot FROM mvtest_tm; +CREATE MATERIALIZED VIEW mvtest_tvmm AS SELECT sum(totamt) AS grandtot FROM mvtest_tvm; +CREATE UNIQUE INDEX mvtest_tvmm_expr ON mvtest_tvmm ((grandtot > 0)); +CREATE UNIQUE INDEX mvtest_tvmm_pred ON mvtest_tvmm (grandtot) WHERE grandtot < 0; +CREATE VIEW mvtest_tvv AS SELECT sum(totamt) AS grandtot FROM mvtest_tv; +EXPLAIN (costs off) + CREATE MATERIALIZED VIEW mvtest_tvvm AS SELECT * FROM mvtest_tvv; + QUERY PLAN +---------------------------------- + Aggregate + -> HashAggregate + Group Key: mvtest_t.type + -> Seq Scan on mvtest_t +(4 rows) + +CREATE MATERIALIZED VIEW mvtest_tvvm AS SELECT * FROM mvtest_tvv; +CREATE VIEW mvtest_tvvmv AS SELECT * FROM mvtest_tvvm; +CREATE MATERIALIZED VIEW mvtest_bb AS SELECT * FROM mvtest_tvvmv; +CREATE INDEX mvtest_aa ON mvtest_bb (grandtot); +-- check that plans seem reasonable +\d+ mvtest_tvm + Materialized view "public.mvtest_tvm" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + type | text | | | | extended | | + totamt | numeric | | | | main | | +View definition: + SELECT mvtest_tv.type, + mvtest_tv.totamt + FROM mvtest_tv + ORDER BY mvtest_tv.type; + +\d+ mvtest_tvm + Materialized view "public.mvtest_tvm" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + type | text | | | | extended | | + totamt | numeric | | | | main | | +View definition: + SELECT mvtest_tv.type, + mvtest_tv.totamt + FROM mvtest_tv + ORDER BY mvtest_tv.type; + +\d+ mvtest_tvvm + Materialized view "public.mvtest_tvvm" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +----------+---------+-----------+----------+---------+---------+--------------+------------- + grandtot | numeric | | | | main | | +View definition: + SELECT mvtest_tvv.grandtot + FROM mvtest_tvv; + +\d+ mvtest_bb + Materialized view "public.mvtest_bb" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +----------+---------+-----------+----------+---------+---------+--------------+------------- + grandtot | numeric | | | | main | | +Indexes: + "mvtest_aa" lsm (grandtot ASC) +View definition: + SELECT mvtest_tvvmv.grandtot + FROM mvtest_tvvmv; + +-- test schema behavior +-- CREATE SCHEMA mvtest_mvschema; +-- ALTER MATERIALIZED VIEW mvtest_tvm SET SCHEMA mvtest_mvschema; +-- \d+ mvtest_tvm +-- \d+ mvtest_tvmm +-- SET search_path = mvtest_mvschema, public; +-- \d+ mvtest_tvm +-- modify the underlying table data +INSERT INTO mvtest_t VALUES (6, 'z', 13); +-- confirm pre- and post-refresh contents of fairly simple materialized views +SELECT * FROM mvtest_tm ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 11 +(3 rows) + +SELECT * FROM mvtest_tvm ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 11 +(3 rows) + +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_tm; +REFRESH MATERIALIZED VIEW mvtest_tvm; +SELECT * FROM mvtest_tm ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 24 +(3 rows) + +SELECT * FROM mvtest_tvm ORDER BY type; + type | totamt +------+-------- + x | 5 + y | 12 + z | 24 +(3 rows) + +RESET search_path; +-- confirm pre- and post-refresh contents of nested materialized views +EXPLAIN (costs off) + SELECT * FROM mvtest_tmm; + QUERY PLAN +------------------------ + Seq Scan on mvtest_tmm +(1 row) + +EXPLAIN (costs off) + SELECT * FROM mvtest_tvmm; + QUERY PLAN +------------------------- + Seq Scan on mvtest_tvmm +(1 row) + +EXPLAIN (costs off) + SELECT * FROM mvtest_tvvm; + QUERY PLAN +------------------------- + Seq Scan on mvtest_tvvm +(1 row) + +SELECT * FROM mvtest_tmm; + grandtot +---------- + 28 +(1 row) + +SELECT * FROM mvtest_tvmm; + grandtot +---------- + 28 +(1 row) + +SELECT * FROM mvtest_tvvm; + grandtot +---------- + 28 +(1 row) + +REFRESH MATERIALIZED VIEW mvtest_tmm; +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_tvmm; +ERROR: cannot refresh materialized view "public.mvtest_tvmm" concurrently +HINT: Create a unique index with no WHERE clause on one or more columns of the materialized view. +REFRESH MATERIALIZED VIEW mvtest_tvmm; +REFRESH MATERIALIZED VIEW mvtest_tvvm; +EXPLAIN (costs off) + SELECT * FROM mvtest_tmm; + QUERY PLAN +------------------------ + Seq Scan on mvtest_tmm +(1 row) + +EXPLAIN (costs off) + SELECT * FROM mvtest_tvmm; + QUERY PLAN +------------------------- + Seq Scan on mvtest_tvmm +(1 row) + +EXPLAIN (costs off) + SELECT * FROM mvtest_tvvm; + QUERY PLAN +------------------------- + Seq Scan on mvtest_tvvm +(1 row) + +SELECT * FROM mvtest_tmm; + grandtot +---------- + 41 +(1 row) + +SELECT * FROM mvtest_tvmm; + grandtot +---------- + 41 +(1 row) + +SELECT * FROM mvtest_tvvm; + grandtot +---------- + 41 +(1 row) + +-- test diemv when the mv does not exist +DROP MATERIALIZED VIEW IF EXISTS no_such_mv; +NOTICE: materialized view "no_such_mv" does not exist, skipping +-- make sure invalid combination of options is prohibited +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_tvmm WITH NO DATA; +ERROR: CONCURRENTLY and WITH NO DATA options cannot be used together +-- no tuple locks on materialized views +SELECT * FROM mvtest_tvvm FOR SHARE; +ERROR: cannot lock rows in materialized view "mvtest_tvvm" +-- test join of mv and view +SELECT type, m.totamt AS mtot, v.totamt AS vtot FROM mvtest_tm m LEFT JOIN mvtest_tv v USING (type) ORDER BY type; + type | mtot | vtot +------+------+------ + x | 5 | 5 + y | 12 | 12 + z | 24 | 24 +(3 rows) + +-- make sure that dependencies are reported properly when they block the drop +DROP TABLE mvtest_t; +ERROR: cannot drop table mvtest_t because other objects depend on it +DETAIL: materialized view mvtest_bb depends on view mvtest_tvvmv +materialized view mvtest_tm depends on table mvtest_t +materialized view mvtest_tmm depends on materialized view mvtest_tm +materialized view mvtest_tvm depends on view mvtest_tv +materialized view mvtest_tvmm depends on materialized view mvtest_tvm +materialized view mvtest_tvvm depends on view mvtest_tvv +view mvtest_tv depends on table mvtest_t +view mvtest_tvv depends on view mvtest_tv +view mvtest_tvvmv depends on materialized view mvtest_tvvm +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- make sure dependencies are dropped and reported +-- and make sure that transactional behavior is correct on rollback +-- incidentally leaving some interesting materialized views for pg_dump testing +BEGIN; +DROP TABLE mvtest_t CASCADE; +NOTICE: drop cascades to 9 other objects +DETAIL: drop cascades to materialized view mvtest_bb +drop cascades to materialized view mvtest_tm +drop cascades to materialized view mvtest_tmm +drop cascades to materialized view mvtest_tvm +drop cascades to materialized view mvtest_tvmm +drop cascades to materialized view mvtest_tvvm +drop cascades to view mvtest_tv +drop cascades to view mvtest_tvv +drop cascades to view mvtest_tvvmv +ROLLBACK; +-- some additional tests not using base tables +CREATE VIEW mvtest_vt1 AS SELECT 1 moo; +CREATE VIEW mvtest_vt2 AS SELECT moo, 2*moo FROM mvtest_vt1 UNION ALL SELECT moo, 3*moo FROM mvtest_vt1; +\d+ mvtest_vt2 + View "public.mvtest_vt2" + Column | Type | Collation | Nullable | Default | Storage | Description +----------+---------+-----------+----------+---------+---------+------------- + moo | integer | | | | plain | + ?column? | integer | | | | plain | +View definition: + SELECT mvtest_vt1.moo, + 2 * mvtest_vt1.moo AS "?column?" + FROM mvtest_vt1 +UNION ALL + SELECT mvtest_vt1.moo, + 3 * mvtest_vt1.moo + FROM mvtest_vt1; + +CREATE MATERIALIZED VIEW mv_test2 AS SELECT moo, 2*moo FROM mvtest_vt2 UNION ALL SELECT moo, 3*moo FROM mvtest_vt2; +\d+ mv_test2 + Materialized view "public.mv_test2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +----------+---------+-----------+----------+---------+---------+--------------+------------- + moo | integer | | | | plain | | + ?column? | integer | | | | plain | | +View definition: + SELECT mvtest_vt2.moo, + 2 * mvtest_vt2.moo AS "?column?" + FROM mvtest_vt2 +UNION ALL + SELECT mvtest_vt2.moo, + 3 * mvtest_vt2.moo + FROM mvtest_vt2; + +CREATE MATERIALIZED VIEW mv_test3 AS SELECT * FROM mv_test2 WHERE moo = 12345; +SELECT relispopulated FROM pg_class WHERE oid = 'mv_test3'::regclass; + relispopulated +---------------- + t +(1 row) + +DROP VIEW mvtest_vt1 CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to materialized view mv_test2 +drop cascades to materialized view mv_test3 +drop cascades to view mvtest_vt2 +-- test that duplicate values on unique index prevent refresh +CREATE TABLE mvtest_foo(a, b) AS VALUES(1, 10); +CREATE MATERIALIZED VIEW mvtest_mv AS SELECT * FROM mvtest_foo; +CREATE UNIQUE INDEX ON mvtest_mv(a); +INSERT INTO mvtest_foo SELECT * FROM mvtest_foo; +REFRESH MATERIALIZED VIEW mvtest_mv; +ERROR: duplicate key value violates unique constraint "mvtest_mv_a_idx" +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv; +ERROR: new data for materialized view "mvtest_mv" contains duplicate rows without any null columns +DETAIL: Row: (1,10) +DROP TABLE mvtest_foo CASCADE; +NOTICE: drop cascades to materialized view mvtest_mv +-- make sure that all columns covered by unique indexes works +CREATE TABLE mvtest_foo(a, b, c) AS VALUES(1, 2, 3); +CREATE MATERIALIZED VIEW mvtest_mv AS SELECT * FROM mvtest_foo; +CREATE UNIQUE INDEX ON mvtest_mv (a); +CREATE UNIQUE INDEX ON mvtest_mv (b); +CREATE UNIQUE INDEX on mvtest_mv (c); +INSERT INTO mvtest_foo VALUES(2, 3, 4); +INSERT INTO mvtest_foo VALUES(3, 4, 5); +REFRESH MATERIALIZED VIEW mvtest_mv; +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv; +DROP TABLE mvtest_foo CASCADE; +NOTICE: drop cascades to materialized view mvtest_mv +-- allow subquery to reference unpopulated matview if WITH NO DATA is specified +CREATE MATERIALIZED VIEW mvtest_mv1 AS SELECT 1 AS col1 WITH NO DATA; +CREATE MATERIALIZED VIEW mvtest_mv2 AS SELECT * FROM mvtest_mv1 + WHERE col1 = (SELECT LEAST(col1) FROM mvtest_mv1) WITH NO DATA; +DROP MATERIALIZED VIEW mvtest_mv1 CASCADE; +NOTICE: drop cascades to materialized view mvtest_mv2 +-- make sure that types with unusual equality tests work +CREATE TABLE mvtest_boxes (id serial primary key, b box); +INSERT INTO mvtest_boxes (b) VALUES + ('(32,32),(31,31)'), + ('(2.0000004,2.0000004),(1,1)'), + ('(1.9999996,1.9999996),(1,1)'); +CREATE MATERIALIZED VIEW mvtest_boxmv AS SELECT * FROM mvtest_boxes; +CREATE UNIQUE INDEX mvtest_boxmv_id ON mvtest_boxmv (id); +UPDATE mvtest_boxes SET b = '(2,2),(1,1)' WHERE id = 2; +-- REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_boxmv; +REFRESH MATERIALIZED VIEW mvtest_boxmv; +SELECT * FROM mvtest_boxmv ORDER BY id; + id | b +----+----------------------------- + 1 | (32,32),(31,31) + 2 | (2,2),(1,1) + 3 | (1.9999996,1.9999996),(1,1) +(3 rows) + +DROP TABLE mvtest_boxes CASCADE; +NOTICE: drop cascades to materialized view mvtest_boxmv +-- make sure that column names are handled correctly +CREATE TABLE mvtest_v (i int, j int); +CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj, kk) AS SELECT i, j FROM mvtest_v; -- error +ERROR: too many column names were specified +CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj) AS SELECT i, j FROM mvtest_v; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_2 (ii) AS SELECT i, j FROM mvtest_v; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj, kk) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- error +ERROR: too many column names were specified +CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_4 (ii) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok +ALTER TABLE mvtest_v RENAME COLUMN i TO x; +INSERT INTO mvtest_v values (1, 2); +CREATE UNIQUE INDEX mvtest_mv_v_ii ON mvtest_mv_v (ii); +REFRESH MATERIALIZED VIEW mvtest_mv_v; +UPDATE mvtest_v SET j = 3 WHERE x = 1; +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv_v; +REFRESH MATERIALIZED VIEW mvtest_mv_v_2; +REFRESH MATERIALIZED VIEW mvtest_mv_v_3; +REFRESH MATERIALIZED VIEW mvtest_mv_v_4; +SELECT * FROM mvtest_v; + x | j +---+--- + 1 | 3 +(1 row) + +SELECT * FROM mvtest_mv_v; + ii | jj +----+---- + 1 | 3 +(1 row) + +SELECT * FROM mvtest_mv_v_2; + ii | j +----+--- + 1 | 3 +(1 row) + +SELECT * FROM mvtest_mv_v_3; + ii | jj +----+---- + 1 | 3 +(1 row) + +SELECT * FROM mvtest_mv_v_4; + ii | j +----+--- + 1 | 3 +(1 row) + +DROP TABLE mvtest_v CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to materialized view mvtest_mv_v +drop cascades to materialized view mvtest_mv_v_2 +drop cascades to materialized view mvtest_mv_v_3 +drop cascades to materialized view mvtest_mv_v_4 +-- Check that unknown literals are converted to "text" in CREATE MATVIEW, +-- so that we don't end up with unknown-type columns. +CREATE MATERIALIZED VIEW mv_unspecified_types AS + SELECT 42 as i, 42.5 as num, 'foo' as u, 'foo'::unknown as u2, null as n; +\d+ mv_unspecified_types + Materialized view "public.mv_unspecified_types" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + i | integer | | | | plain | | + num | numeric | | | | main | | + u | text | | | | extended | | + u2 | text | | | | extended | | + n | text | | | | extended | | +View definition: + SELECT 42 AS i, + 42.5 AS num, + 'foo'::text AS u, + 'foo'::text AS u2, + NULL::text AS n; + +SELECT * FROM mv_unspecified_types; + i | num | u | u2 | n +----+------+-----+-----+--- + 42 | 42.5 | foo | foo | +(1 row) + +DROP MATERIALIZED VIEW mv_unspecified_types; +-- make sure that create WITH NO DATA does not plan the query (bug #13907) +create materialized view mvtest_error as select 1/0 as x; -- fail +ERROR: division by zero +create materialized view mvtest_error as select 1/0 as x with no data; +refresh materialized view mvtest_error; -- fail here +ERROR: division by zero +drop materialized view mvtest_error; +-- make sure that matview rows can be referenced as source rows (bug #9398) +CREATE TABLE mvtest_v AS SELECT generate_series(1,10) AS a; +CREATE MATERIALIZED VIEW mvtest_mv_v AS SELECT a FROM mvtest_v WHERE a <= 5; +DELETE FROM mvtest_v WHERE EXISTS ( SELECT * FROM mvtest_mv_v WHERE mvtest_mv_v.a = mvtest_v.a ); +SELECT * FROM mvtest_v ORDER BY a; + a +---- + 6 + 7 + 8 + 9 + 10 +(5 rows) + +SELECT * FROM mvtest_mv_v ORDER BY a; + a +--- + 1 + 2 + 3 + 4 + 5 +(5 rows) + +DROP TABLE mvtest_v CASCADE; +NOTICE: drop cascades to materialized view mvtest_mv_v +-- make sure running as superuser works when MV owned by another role (bug #11208) +CREATE ROLE regress_user_mvtest; +SET ROLE regress_user_mvtest; +-- this test case also checks for ambiguity in the queries issued by +-- refresh_by_match_merge(), by choosing column names that intentionally +-- duplicate all the aliases used in those queries +CREATE TABLE mvtest_foo_data AS SELECT i, + i+1 AS tid, + md5(random()::text) AS mv, + md5(random()::text) AS newdata, + md5(random()::text) AS newdata2, + md5(random()::text) AS diff + FROM generate_series(1, 10) i; +CREATE MATERIALIZED VIEW mvtest_mv_foo AS SELECT * FROM mvtest_foo_data; +CREATE MATERIALIZED VIEW mvtest_mv_foo AS SELECT * FROM mvtest_foo_data; +ERROR: relation "mvtest_mv_foo" already exists +CREATE MATERIALIZED VIEW IF NOT EXISTS mvtest_mv_foo AS SELECT * FROM mvtest_foo_data; +NOTICE: relation "mvtest_mv_foo" already exists, skipping +CREATE UNIQUE INDEX ON mvtest_mv_foo (i); +RESET ROLE; +REFRESH MATERIALIZED VIEW mvtest_mv_foo; +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv_foo; +DROP OWNED BY regress_user_mvtest CASCADE; +DROP ROLE regress_user_mvtest; +-- make sure that create WITH NO DATA works via SPI +BEGIN; +CREATE FUNCTION mvtest_func() + RETURNS void AS $$ +BEGIN + CREATE MATERIALIZED VIEW mvtest1 AS SELECT 1 AS x; + CREATE MATERIALIZED VIEW mvtest2 AS SELECT 1 AS x WITH NO DATA; +END; +$$ LANGUAGE plpgsql; +SELECT mvtest_func(); + mvtest_func +------------- + +(1 row) + +SELECT * FROM mvtest1; + x +--- + 1 +(1 row) + +SELECT * FROM mvtest2; +ERROR: materialized view "mvtest2" has not been populated +HINT: Use the REFRESH MATERIALIZED VIEW command. +ROLLBACK; +-- INSERT privileges if relation owner is not allowed to insert. +CREATE SCHEMA matview_schema; +CREATE USER regress_matview_user; +ALTER DEFAULT PRIVILEGES FOR ROLE regress_matview_user + REVOKE INSERT ON TABLES FROM regress_matview_user; +GRANT ALL ON SCHEMA matview_schema TO public; +SET SESSION AUTHORIZATION regress_matview_user; +CREATE MATERIALIZED VIEW matview_schema.mv_withdata1 (a) AS + SELECT generate_series(1, 10) WITH DATA; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW matview_schema.mv_withdata2 (a) AS + SELECT generate_series(1, 10) WITH DATA; + QUERY PLAN +----------------------------------------------- + ProjectSet (actual rows=10 loops=1) + -> Result *RESULT* (actual rows=1 loops=1) +(2 rows) + +REFRESH MATERIALIZED VIEW matview_schema.mv_withdata2; +CREATE MATERIALIZED VIEW matview_schema.mv_nodata1 (a) AS + SELECT generate_series(1, 10) WITH NO DATA; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW matview_schema.mv_nodata2 (a) AS + SELECT generate_series(1, 10) WITH NO DATA; + QUERY PLAN +---------------------------------------- + ProjectSet (never executed) + -> Result *RESULT* (never executed) +(2 rows) + +REFRESH MATERIALIZED VIEW matview_schema.mv_nodata2; +RESET SESSION AUTHORIZATION; +ALTER DEFAULT PRIVILEGES FOR ROLE regress_matview_user + GRANT INSERT ON TABLES TO regress_matview_user; +DROP SCHEMA matview_schema CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to materialized view matview_schema.mv_nodata1 +drop cascades to materialized view matview_schema.mv_nodata2 +drop cascades to materialized view matview_schema.mv_withdata1 +drop cascades to materialized view matview_schema.mv_withdata2 +DROP USER regress_matview_user; +-- CREATE MATERIALIZED VIEW ... IF NOT EXISTS +CREATE MATERIALIZED VIEW matview_ine_tab AS SELECT 1; +CREATE MATERIALIZED VIEW matview_ine_tab AS SELECT 1 / 0; -- error +ERROR: relation "matview_ine_tab" already exists +CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS + SELECT 1 / 0; -- ok +NOTICE: relation "matview_ine_tab" already exists, skipping +CREATE MATERIALIZED VIEW matview_ine_tab AS + SELECT 1 / 0 WITH NO DATA; -- error +ERROR: relation "matview_ine_tab" already exists +CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS + SELECT 1 / 0 WITH NO DATA; -- ok +NOTICE: relation "matview_ine_tab" already exists, skipping +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW matview_ine_tab AS + SELECT 1 / 0; -- error +ERROR: relation "matview_ine_tab" already exists +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS + SELECT 1 / 0; -- ok +NOTICE: relation "matview_ine_tab" already exists, skipping + QUERY PLAN +------------ +(0 rows) + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW matview_ine_tab AS + SELECT 1 / 0 WITH NO DATA; -- error +ERROR: relation "matview_ine_tab" already exists +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS + SELECT 1 / 0 WITH NO DATA; -- ok +NOTICE: relation "matview_ine_tab" already exists, skipping + QUERY PLAN +------------ +(0 rows) + +DROP MATERIALIZED VIEW matview_ine_tab; diff --git a/src/test/regress/expected/yb.port.misc.out b/src/test/regress/expected/yb.port.misc.out new file mode 100644 index 0000000000000..ad7b9d11cab20 --- /dev/null +++ b/src/test/regress/expected/yb.port.misc.out @@ -0,0 +1,399 @@ +-- +-- MISC +-- +-- directory paths and dlsuffix are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +\getenv abs_builddir PG_ABS_BUILDDIR +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX +\set regresslib :libdir '/regress' :dlsuffix +CREATE FUNCTION overpaid(emp) + RETURNS bool + AS :'regresslib' + LANGUAGE C STRICT; +CREATE FUNCTION reverse_name(name) + RETURNS name + AS :'regresslib' + LANGUAGE C STRICT; +-- +-- BTREE +-- +UPDATE onek + SET unique1 = onek.unique1 + 1; +UPDATE onek + SET unique1 = onek.unique1 - 1; +-- +-- BTREE partial +-- +-- UPDATE onek2 +-- SET unique1 = onek2.unique1 + 1; +--UPDATE onek2 +-- SET unique1 = onek2.unique1 - 1; +-- +-- BTREE shutting out non-functional updates +-- +-- the following two tests seem to take a long time on some +-- systems. This non-func update stuff needs to be examined +-- more closely. - jolly (2/22/96) +-- +SELECT two, stringu1, ten, string4 + INTO TABLE tmp + FROM onek; +UPDATE tmp + SET stringu1 = reverse_name(onek.stringu1) + FROM onek + WHERE onek.stringu1 = 'JBAAAA' and + onek.stringu1 = tmp.stringu1; +UPDATE tmp + SET stringu1 = reverse_name(onek2.stringu1) + FROM onek2 + WHERE onek2.stringu1 = 'JCAAAA' and + onek2.stringu1 = tmp.stringu1; +DROP TABLE tmp; +--UPDATE person* +-- SET age = age + 1; +--UPDATE person* +-- SET age = age + 3 +-- WHERE name = 'linda'; +-- +-- copy +-- +\set filename :abs_builddir '/results/onek.data' +COPY onek TO :'filename'; +CREATE TABLE onek_copy (LIKE onek); -- YB: create YB table instead of temp table to exercise YB tables +COPY onek_copy FROM :'filename'; +SELECT * FROM onek EXCEPT ALL SELECT * FROM onek_copy; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- +(0 rows) + +SELECT * FROM onek_copy EXCEPT ALL SELECT * FROM onek; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- +(0 rows) + +\set filename :abs_builddir '/results/stud_emp.data' +COPY BINARY stud_emp TO :'filename'; +CREATE TEMP TABLE stud_emp_copy (LIKE stud_emp); +COPY BINARY stud_emp_copy FROM :'filename'; +WARNING: ROWS_PER_TRANSACTION is not supported on temporary tables +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Either copy onto non-temporary table or set rows_per_transaction option to `0` to disable batching and remove this warning. +SELECT * FROM stud_emp_copy; + name | age | location | salary | manager | gpa | percent +-------+-----+------------+--------+---------+-----+--------- + jeff | 23 | (8,7.7) | 600 | sharon | 3.5 | + cim | 30 | (10.5,4.7) | 400 | | 3.4 | + linda | 19 | (0.9,6.1) | 100 | | 2.9 | +(3 rows) + +-- +-- test data for postquel functions +-- +CREATE TABLE hobbies_r ( + name text, + person text +); +CREATE TABLE equipment_r ( + name text, + hobby text +); +INSERT INTO hobbies_r (name, person) + SELECT 'posthacking', p.name + FROM person* p + WHERE p.name = 'mike' or p.name = 'jeff'; +INSERT INTO hobbies_r (name, person) + SELECT 'basketball', p.name + FROM person p + WHERE p.name = 'joe' or p.name = 'sally'; +INSERT INTO hobbies_r (name) VALUES ('skywalking'); +INSERT INTO equipment_r (name, hobby) VALUES ('advil', 'posthacking'); +INSERT INTO equipment_r (name, hobby) VALUES ('peet''s coffee', 'posthacking'); +INSERT INTO equipment_r (name, hobby) VALUES ('hightops', 'basketball'); +INSERT INTO equipment_r (name, hobby) VALUES ('guts', 'skywalking'); +-- +-- postquel functions +-- +CREATE FUNCTION hobbies(person) + RETURNS setof hobbies_r + AS 'select * from hobbies_r where person = $1.name' + LANGUAGE SQL; +CREATE FUNCTION hobby_construct(text, text) + RETURNS hobbies_r + AS 'select $1 as name, $2 as hobby' + LANGUAGE SQL; +CREATE FUNCTION hobby_construct_named(name text, hobby text) + RETURNS hobbies_r + AS 'select name, hobby' + LANGUAGE SQL; +CREATE FUNCTION hobbies_by_name(hobbies_r.name%TYPE) + RETURNS hobbies_r.person%TYPE + AS 'select person from hobbies_r where name = $1' + LANGUAGE SQL; +NOTICE: type reference hobbies_r.name%TYPE converted to text +NOTICE: type reference hobbies_r.person%TYPE converted to text +CREATE FUNCTION equipment(hobbies_r) + RETURNS setof equipment_r + AS 'select * from equipment_r where hobby = $1.name' + LANGUAGE SQL; +CREATE FUNCTION equipment_named(hobby hobbies_r) + RETURNS setof equipment_r + AS 'select * from equipment_r where equipment_r.hobby = equipment_named.hobby.name' + LANGUAGE SQL; +CREATE FUNCTION equipment_named_ambiguous_1a(hobby hobbies_r) + RETURNS setof equipment_r + AS 'select * from equipment_r where hobby = equipment_named_ambiguous_1a.hobby.name' + LANGUAGE SQL; +CREATE FUNCTION equipment_named_ambiguous_1b(hobby hobbies_r) + RETURNS setof equipment_r + AS 'select * from equipment_r where equipment_r.hobby = hobby.name' + LANGUAGE SQL; +CREATE FUNCTION equipment_named_ambiguous_1c(hobby hobbies_r) + RETURNS setof equipment_r + AS 'select * from equipment_r where hobby = hobby.name' + LANGUAGE SQL; +CREATE FUNCTION equipment_named_ambiguous_2a(hobby text) + RETURNS setof equipment_r + AS 'select * from equipment_r where hobby = equipment_named_ambiguous_2a.hobby' + LANGUAGE SQL; +CREATE FUNCTION equipment_named_ambiguous_2b(hobby text) + RETURNS setof equipment_r + AS 'select * from equipment_r where equipment_r.hobby = hobby' + LANGUAGE SQL; +-- +-- mike does post_hacking, +-- joe and sally play basketball, and +-- everyone else does nothing. +-- +SELECT p.name, name(p.hobbies) FROM ONLY person p; + name | name +-------+------------- + mike | posthacking + joe | basketball + sally | basketball +(3 rows) + +-- +-- as above, but jeff also does post_hacking. +-- +SELECT p.name, name(p.hobbies) FROM person* p; + name | name +-------+------------- + mike | posthacking + joe | basketball + sally | basketball + jeff | posthacking +(4 rows) + +-- +-- the next two queries demonstrate how functions generate bogus duplicates. +-- this is a "feature" .. +-- +SELECT DISTINCT hobbies_r.name, name(hobbies_r.equipment) FROM hobbies_r + ORDER BY 1,2; + name | name +-------------+--------------- + basketball | hightops + posthacking | advil + posthacking | peet's coffee + skywalking | guts +(4 rows) + +SELECT hobbies_r.name, (hobbies_r.equipment).name FROM hobbies_r; + name | name +-------------+--------------- + posthacking | advil + posthacking | peet's coffee + posthacking | advil + posthacking | peet's coffee + basketball | hightops + basketball | hightops + skywalking | guts +(7 rows) + +-- +-- mike needs advil and peet's coffee, +-- joe and sally need hightops, and +-- everyone else is fine. +-- +SELECT p.name, name(p.hobbies), name(equipment(p.hobbies)) FROM ONLY person p; + name | name | name +-------+-------------+--------------- + mike | posthacking | advil + mike | posthacking | peet's coffee + joe | basketball | hightops + sally | basketball | hightops +(4 rows) + +-- +-- as above, but jeff needs advil and peet's coffee as well. +-- +SELECT p.name, name(p.hobbies), name(equipment(p.hobbies)) FROM person* p; + name | name | name +-------+-------------+--------------- + mike | posthacking | advil + mike | posthacking | peet's coffee + joe | basketball | hightops + sally | basketball | hightops + jeff | posthacking | advil + jeff | posthacking | peet's coffee +(6 rows) + +-- +-- just like the last two, but make sure that the target list fixup and +-- unflattening is being done correctly. +-- +SELECT name(equipment(p.hobbies)), p.name, name(p.hobbies) FROM ONLY person p; + name | name | name +---------------+-------+------------- + advil | mike | posthacking + peet's coffee | mike | posthacking + hightops | joe | basketball + hightops | sally | basketball +(4 rows) + +SELECT (p.hobbies).equipment.name, p.name, name(p.hobbies) FROM person* p; + name | name | name +---------------+-------+------------- + advil | mike | posthacking + peet's coffee | mike | posthacking + hightops | joe | basketball + hightops | sally | basketball + advil | jeff | posthacking + peet's coffee | jeff | posthacking +(6 rows) + +SELECT (p.hobbies).equipment.name, name(p.hobbies), p.name FROM ONLY person p; + name | name | name +---------------+-------------+------- + advil | posthacking | mike + peet's coffee | posthacking | mike + hightops | basketball | joe + hightops | basketball | sally +(4 rows) + +SELECT name(equipment(p.hobbies)), name(p.hobbies), p.name FROM person* p; + name | name | name +---------------+-------------+------- + advil | posthacking | mike + peet's coffee | posthacking | mike + hightops | basketball | joe + hightops | basketball | sally + advil | posthacking | jeff + peet's coffee | posthacking | jeff +(6 rows) + +SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer'))); + name +------ + guts +(1 row) + +SELECT name(equipment(hobby_construct_named(text 'skywalking', text 'mer'))); + name +------ + guts +(1 row) + +SELECT name(equipment_named(hobby_construct_named(text 'skywalking', text 'mer'))); + name +------ + guts +(1 row) + +SELECT name(equipment_named_ambiguous_1a(hobby_construct_named(text 'skywalking', text 'mer'))); + name +------ + guts +(1 row) + +SELECT name(equipment_named_ambiguous_1b(hobby_construct_named(text 'skywalking', text 'mer'))); + name +------ + guts +(1 row) + +SELECT name(equipment_named_ambiguous_1c(hobby_construct_named(text 'skywalking', text 'mer'))); + name +------ + guts +(1 row) + +SELECT name(equipment_named_ambiguous_2a(text 'skywalking')); + name +------ + guts +(1 row) + +SELECT name(equipment_named_ambiguous_2b(text 'skywalking')); + name +--------------- + advil + peet's coffee + hightops + guts +(4 rows) + +/* YB: output sometimes gives joe, sometimes sally +SELECT hobbies_by_name('basketball'); +*/ -- YB +SELECT name, overpaid(emp.*) FROM emp; + name | overpaid +--------+---------- + sharon | t + sam | t + bill | t + jeff | f + cim | f + linda | f +(6 rows) + +-- +-- Try a few cases with SQL-spec row constructor expressions +-- +SELECT * FROM equipment(ROW('skywalking', 'mer')); + name | hobby +------+------------ + guts | skywalking +(1 row) + +SELECT name(equipment(ROW('skywalking', 'mer'))); + name +------ + guts +(1 row) + +SELECT *, name(equipment(h.*)) FROM hobbies_r h; + name | person | name +-------------+--------+--------------- + posthacking | mike | advil + posthacking | mike | peet's coffee + posthacking | jeff | advil + posthacking | jeff | peet's coffee + basketball | joe | hightops + basketball | sally | hightops + skywalking | | guts +(7 rows) + +SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h; + name | person | name +-------------+--------+--------------- + posthacking | mike | advil + posthacking | mike | peet's coffee + posthacking | jeff | advil + posthacking | jeff | peet's coffee + basketball | joe | hightops + basketball | sally | hightops + skywalking | | guts +(7 rows) + +-- +-- functional joins +-- +-- +-- instance rules +-- +-- +-- rewrite rules +-- +DROP TABLE onek_copy; -- YB: mimic natural discard of temp tables for tables that have been converted from temp tables in the upstream PG to YB tables here diff --git a/src/test/regress/expected/yb.port.misc_functions.out b/src/test/regress/expected/yb.port.misc_functions.out new file mode 100644 index 0000000000000..133d11995b5d1 --- /dev/null +++ b/src/test/regress/expected/yb.port.misc_functions.out @@ -0,0 +1,534 @@ +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX +\set regresslib :libdir '/regress' :dlsuffix +-- +-- num_nulls() +-- +SELECT num_nonnulls(NULL); + num_nonnulls +-------------- + 0 +(1 row) + +SELECT num_nonnulls('1'); + num_nonnulls +-------------- + 1 +(1 row) + +SELECT num_nonnulls(NULL::text); + num_nonnulls +-------------- + 0 +(1 row) + +SELECT num_nonnulls(NULL::text, NULL::int); + num_nonnulls +-------------- + 0 +(1 row) + +SELECT num_nonnulls(1, 2, NULL::text, NULL::point, '', int8 '9', 1.0 / NULL); + num_nonnulls +-------------- + 4 +(1 row) + +SELECT num_nonnulls(VARIADIC '{1,2,NULL,3}'::int[]); + num_nonnulls +-------------- + 3 +(1 row) + +SELECT num_nonnulls(VARIADIC '{"1","2","3","4"}'::text[]); + num_nonnulls +-------------- + 4 +(1 row) + +SELECT num_nonnulls(VARIADIC ARRAY(SELECT CASE WHEN i <> 40 THEN i END FROM generate_series(1, 100) i)); + num_nonnulls +-------------- + 99 +(1 row) + +SELECT num_nulls(NULL); + num_nulls +----------- + 1 +(1 row) + +SELECT num_nulls('1'); + num_nulls +----------- + 0 +(1 row) + +SELECT num_nulls(NULL::text); + num_nulls +----------- + 1 +(1 row) + +SELECT num_nulls(NULL::text, NULL::int); + num_nulls +----------- + 2 +(1 row) + +SELECT num_nulls(1, 2, NULL::text, NULL::point, '', int8 '9', 1.0 / NULL); + num_nulls +----------- + 3 +(1 row) + +SELECT num_nulls(VARIADIC '{1,2,NULL,3}'::int[]); + num_nulls +----------- + 1 +(1 row) + +SELECT num_nulls(VARIADIC '{"1","2","3","4"}'::text[]); + num_nulls +----------- + 0 +(1 row) + +SELECT num_nulls(VARIADIC ARRAY(SELECT CASE WHEN i <> 40 THEN i END FROM generate_series(1, 100) i)); + num_nulls +----------- + 1 +(1 row) + +-- special cases +SELECT num_nonnulls(VARIADIC NULL::text[]); + num_nonnulls +-------------- + +(1 row) + +SELECT num_nonnulls(VARIADIC '{}'::int[]); + num_nonnulls +-------------- + 0 +(1 row) + +SELECT num_nulls(VARIADIC NULL::text[]); + num_nulls +----------- + +(1 row) + +SELECT num_nulls(VARIADIC '{}'::int[]); + num_nulls +----------- + 0 +(1 row) + +-- should fail, one or more arguments is required +SELECT num_nonnulls(); +ERROR: function num_nonnulls() does not exist +LINE 1: SELECT num_nonnulls(); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +SELECT num_nulls(); +ERROR: function num_nulls() does not exist +LINE 1: SELECT num_nulls(); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +-- +-- canonicalize_path() +-- +CREATE FUNCTION test_canonicalize_path(text) + RETURNS text + AS :'regresslib' + LANGUAGE C STRICT IMMUTABLE; +SELECT test_canonicalize_path('/'); + test_canonicalize_path +------------------------ + / +(1 row) + +SELECT test_canonicalize_path('/./abc/def/'); + test_canonicalize_path +------------------------ + /abc/def +(1 row) + +SELECT test_canonicalize_path('/./../abc/def'); + test_canonicalize_path +------------------------ + /abc/def +(1 row) + +SELECT test_canonicalize_path('/./../../abc/def/'); + test_canonicalize_path +------------------------ + /abc/def +(1 row) + +SELECT test_canonicalize_path('/abc/.././def/ghi'); + test_canonicalize_path +------------------------ + /def/ghi +(1 row) + +SELECT test_canonicalize_path('/abc/./../def/ghi//'); + test_canonicalize_path +------------------------ + /def/ghi +(1 row) + +SELECT test_canonicalize_path('/abc/def/../..'); + test_canonicalize_path +------------------------ + / +(1 row) + +SELECT test_canonicalize_path('/abc/def/../../..'); + test_canonicalize_path +------------------------ + / +(1 row) + +SELECT test_canonicalize_path('/abc/def/../../../../ghi/jkl'); + test_canonicalize_path +------------------------ + /ghi/jkl +(1 row) + +SELECT test_canonicalize_path('.'); + test_canonicalize_path +------------------------ + . +(1 row) + +SELECT test_canonicalize_path('./'); + test_canonicalize_path +------------------------ + . +(1 row) + +SELECT test_canonicalize_path('./abc/..'); + test_canonicalize_path +------------------------ + . +(1 row) + +SELECT test_canonicalize_path('abc/../'); + test_canonicalize_path +------------------------ + . +(1 row) + +SELECT test_canonicalize_path('abc/../def'); + test_canonicalize_path +------------------------ + def +(1 row) + +SELECT test_canonicalize_path('..'); + test_canonicalize_path +------------------------ + .. +(1 row) + +SELECT test_canonicalize_path('../abc/def'); + test_canonicalize_path +------------------------ + ../abc/def +(1 row) + +SELECT test_canonicalize_path('../abc/..'); + test_canonicalize_path +------------------------ + .. +(1 row) + +SELECT test_canonicalize_path('../abc/../def'); + test_canonicalize_path +------------------------ + ../def +(1 row) + +SELECT test_canonicalize_path('../abc/../../def/ghi'); + test_canonicalize_path +------------------------ + ../../def/ghi +(1 row) + +SELECT test_canonicalize_path('./abc/./def/.'); + test_canonicalize_path +------------------------ + abc/def +(1 row) + +SELECT test_canonicalize_path('./abc/././def/.'); + test_canonicalize_path +------------------------ + abc/def +(1 row) + +SELECT test_canonicalize_path('./abc/./def/.././ghi/../../../jkl/mno'); + test_canonicalize_path +------------------------ + ../jkl/mno +(1 row) + +-- +-- pg_log_backend_memory_contexts() +-- +-- Memory contexts are logged and they are not returned to the function. +-- Furthermore, their contents can vary depending on the timing. However, +-- we can at least verify that the code doesn't fail, and that the +-- permissions are set properly. +-- +SELECT pg_log_backend_memory_contexts(pg_backend_pid()); + pg_log_backend_memory_contexts +-------------------------------- + t +(1 row) + +SELECT pg_log_backend_memory_contexts(pid) FROM pg_stat_activity + WHERE backend_type = 'checkpointer'; + pg_log_backend_memory_contexts +-------------------------------- + t +(1 row) + +CREATE ROLE regress_log_memory; +SELECT has_function_privilege('regress_log_memory', + 'pg_log_backend_memory_contexts(integer)', 'EXECUTE'); -- no + has_function_privilege +------------------------ + f +(1 row) + +GRANT EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) + TO regress_log_memory; +SELECT has_function_privilege('regress_log_memory', + 'pg_log_backend_memory_contexts(integer)', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +SET ROLE regress_log_memory; +SELECT pg_log_backend_memory_contexts(pg_backend_pid()); + pg_log_backend_memory_contexts +-------------------------------- + t +(1 row) + +RESET ROLE; +REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) + FROM regress_log_memory; +DROP ROLE regress_log_memory; +-- +-- Test some built-in SRFs +-- +-- The outputs of these are variable, so we can't just print their results +-- directly, but we can at least verify that the code doesn't fail. +-- +select setting as segsize +from pg_settings where name = 'wal_segment_size' +\gset +select count(*) > 0 as ok from pg_ls_waldir(); + ok +---- + t +(1 row) + +-- Test ProjectSet as well as FunctionScan +select count(*) > 0 as ok from (select pg_ls_waldir()) ss; + ok +---- + t +(1 row) + +-- Test not-run-to-completion cases. +select * from pg_ls_waldir() limit 0; + name | size | modification +------+------+-------------- +(0 rows) + +select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss; + ok +---- + t +(1 row) + +select (w).size = :segsize as ok +from (select pg_ls_waldir() w) ss where length((w).name) = 24 limit 1; + ok +---- + t +(1 row) + +select count(*) >= 0 as ok from pg_ls_archive_statusdir(); + ok +---- + t +(1 row) + +select * from (select pg_ls_dir('.') a) a where a = 'base' limit 1; + a +------ + base +(1 row) + +-- Test missing_ok (second argument) +select pg_ls_dir('does not exist', false, false); -- error +ERROR: could not open directory "does not exist": No such file or directory +select pg_ls_dir('does not exist', true, false); -- ok + pg_ls_dir +----------- +(0 rows) + +-- Test include_dot_dirs (third argument) +select count(*) = 1 as dot_found + from pg_ls_dir('.', false, true) as ls where ls = '.'; + dot_found +----------- + t +(1 row) + +select count(*) = 1 as dot_found + from pg_ls_dir('.', false, false) as ls where ls = '.'; + dot_found +----------- + f +(1 row) + +select * from (select (pg_timezone_names()).name) ptn where name='UTC' limit 1; + name +------ + UTC +(1 row) + +select count(*) > 0 from + (select pg_tablespace_databases(oid) as pts from pg_tablespace + where spcname = 'pg_default') pts + join pg_database db on pts.pts = db.oid; + ?column? +---------- + t +(1 row) + +-- +-- Test replication slot directory functions +-- +CREATE ROLE regress_slot_dir_funcs; +-- Not available by default. +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_logicalsnapdir()', 'EXECUTE'); + has_function_privilege +------------------------ + f +(1 row) + +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_logicalmapdir()', 'EXECUTE'); + has_function_privilege +------------------------ + f +(1 row) + +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_replslotdir(text)', 'EXECUTE'); + has_function_privilege +------------------------ + f +(1 row) + +GRANT pg_monitor TO regress_slot_dir_funcs; +-- Role is now part of pg_monitor, so these are available. +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_logicalsnapdir()', 'EXECUTE'); + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_logicalmapdir()', 'EXECUTE'); + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_replslotdir(text)', 'EXECUTE'); + has_function_privilege +------------------------ + t +(1 row) + +DROP ROLE regress_slot_dir_funcs; +-- +-- Test adding a support function to a subject function +-- +CREATE FUNCTION my_int_eq(int, int) RETURNS bool + LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE + AS $$int4eq$$; +-- By default, planner does not think that's selective +EXPLAIN (COSTS OFF) +SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1 +WHERE my_int_eq(a.unique2, 42); -- YB: output's a and b are flipped + QUERY PLAN +---------------------------------------- + Hash Join + Hash Cond: (a.unique1 = b.unique1) + -> Seq Scan on tenk1 a + Filter: my_int_eq(unique2, 42) + -> Hash + -> Seq Scan on tenk1 b +(6 rows) + +-- With support function that knows it's int4eq, we get a different plan +CREATE FUNCTION test_support_func(internal) + RETURNS internal + AS :'regresslib', 'test_support_func' + LANGUAGE C STRICT; +ALTER FUNCTION my_int_eq(int, int) SUPPORT test_support_func; +EXPLAIN (COSTS OFF) +SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1 +WHERE my_int_eq(a.unique2, 42); -- YB: TODO: investigate why NestLoop is still higher cost + QUERY PLAN +---------------------------------------- + Hash Join + Hash Cond: (a.unique1 = b.unique1) + -> Seq Scan on tenk1 a + Filter: my_int_eq(unique2, 42) + -> Hash + -> Seq Scan on tenk1 b +(6 rows) + +-- Also test non-default rowcount estimate +CREATE FUNCTION my_gen_series(int, int) RETURNS SETOF integer + LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE + AS $$generate_series_int4$$ + SUPPORT test_support_func; +EXPLAIN (COSTS OFF) +SELECT * FROM tenk1 a JOIN my_gen_series(1,1000) g ON a.unique1 = g; + QUERY PLAN +---------------------------------------- + Hash Join + Hash Cond: (g.g = a.unique1) + -> Function Scan on my_gen_series g + -> Hash + -> Seq Scan on tenk1 a +(5 rows) + +EXPLAIN (COSTS OFF) +SELECT * FROM tenk1 a JOIN my_gen_series(1,10) g ON a.unique1 = g; + QUERY PLAN +---------------------------------------------------------------------- + YB Batched Nested Loop Join + Join Filter: (a.unique1 = g.g) + -> Function Scan on my_gen_series g + -> Index Scan using tenk1_unique1 on tenk1 a + Index Cond: (unique1 = ANY (ARRAY[g.g, $1, $2, ..., $1023])) +(5 rows) + diff --git a/src/test/regress/expected/yb.port.multirangetypes.out b/src/test/regress/expected/yb.port.multirangetypes.out new file mode 100644 index 0000000000000..756ca780f0940 --- /dev/null +++ b/src/test/regress/expected/yb.port.multirangetypes.out @@ -0,0 +1,3330 @@ +-- Tests for multirange data types. +-- +-- test input parser +-- +-- negative tests; should fail +select ''::textmultirange; +ERROR: malformed multirange literal: "" +LINE 1: select ''::textmultirange; + ^ +DETAIL: Missing left brace. +select '{,}'::textmultirange; +ERROR: malformed multirange literal: "{,}" +LINE 1: select '{,}'::textmultirange; + ^ +DETAIL: Expected range start. +select '{(,)}.'::textmultirange; +ERROR: malformed multirange literal: "{(,)}." +LINE 1: select '{(,)}.'::textmultirange; + ^ +DETAIL: Junk after closing right brace. +select '{[a,c),}'::textmultirange; +ERROR: malformed multirange literal: "{[a,c),}" +LINE 1: select '{[a,c),}'::textmultirange; + ^ +DETAIL: Expected range start. +select '{,[a,c)}'::textmultirange; +ERROR: malformed multirange literal: "{,[a,c)}" +LINE 1: select '{,[a,c)}'::textmultirange; + ^ +DETAIL: Expected range start. +select '{-[a,z)}'::textmultirange; +ERROR: malformed multirange literal: "{-[a,z)}" +LINE 1: select '{-[a,z)}'::textmultirange; + ^ +DETAIL: Expected range start. +select '{[a,z) - }'::textmultirange; +ERROR: malformed multirange literal: "{[a,z) - }" +LINE 1: select '{[a,z) - }'::textmultirange; + ^ +DETAIL: Expected comma or end of multirange. +select '{(",a)}'::textmultirange; +ERROR: malformed multirange literal: "{(",a)}" +LINE 1: select '{(",a)}'::textmultirange; + ^ +DETAIL: Unexpected end of input. +select '{(,,a)}'::textmultirange; +ERROR: malformed range literal: "(,,a)" +LINE 1: select '{(,,a)}'::textmultirange; + ^ +DETAIL: Too many commas. +select '{(),a)}'::textmultirange; +ERROR: malformed range literal: "()" +LINE 1: select '{(),a)}'::textmultirange; + ^ +DETAIL: Missing comma after lower bound. +select '{(a,))}'::textmultirange; +ERROR: malformed multirange literal: "{(a,))}" +LINE 1: select '{(a,))}'::textmultirange; + ^ +DETAIL: Expected comma or end of multirange. +select '{(],a)}'::textmultirange; +ERROR: malformed range literal: "(]" +LINE 1: select '{(],a)}'::textmultirange; + ^ +DETAIL: Missing comma after lower bound. +select '{(a,])}'::textmultirange; +ERROR: malformed multirange literal: "{(a,])}" +LINE 1: select '{(a,])}'::textmultirange; + ^ +DETAIL: Expected comma or end of multirange. +select '{[z,a]}'::textmultirange; +ERROR: range lower bound must be less than or equal to range upper bound +LINE 1: select '{[z,a]}'::textmultirange; + ^ +-- should succeed +select '{}'::textmultirange; + textmultirange +---------------- + {} +(1 row) + +select ' {} '::textmultirange; + textmultirange +---------------- + {} +(1 row) + +select ' { empty, empty } '::textmultirange; + textmultirange +---------------- + {} +(1 row) + +select ' {( " a " " a ", " z " " z " ) }'::textmultirange; + textmultirange +---------------------------- + {(" a a "," z z ")} +(1 row) + +select textrange('\\\\', repeat('a', 200))::textmultirange; + textrange +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {["\\\\\\\\",aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)} +(1 row) + +select '{(,z)}'::textmultirange; + textmultirange +---------------- + {(,z)} +(1 row) + +select '{(a,)}'::textmultirange; + textmultirange +---------------- + {(a,)} +(1 row) + +select '{[,z]}'::textmultirange; + textmultirange +---------------- + {(,z]} +(1 row) + +select '{[a,]}'::textmultirange; + textmultirange +---------------- + {[a,)} +(1 row) + +select '{(,)}'::textmultirange; + textmultirange +---------------- + {(,)} +(1 row) + +select '{[ , ]}'::textmultirange; + textmultirange +---------------- + {[" "," "]} +(1 row) + +select '{["",""]}'::textmultirange; + textmultirange +---------------- + {["",""]} +(1 row) + +select '{[",",","]}'::textmultirange; + textmultirange +---------------- + {[",",","]} +(1 row) + +select '{["\\","\\"]}'::textmultirange; + textmultirange +---------------- + {["\\","\\"]} +(1 row) + +select '{["""","\""]}'::textmultirange; + textmultirange +---------------- + {["""",""""]} +(1 row) + +select '{(\\,a)}'::textmultirange; + textmultirange +---------------- + {("\\",a)} +(1 row) + +select '{((,z)}'::textmultirange; + textmultirange +---------------- + {("(",z)} +(1 row) + +select '{([,z)}'::textmultirange; + textmultirange +---------------- + {("[",z)} +(1 row) + +select '{(!,()}'::textmultirange; + textmultirange +---------------- + {(!,"(")} +(1 row) + +select '{(!,[)}'::textmultirange; + textmultirange +---------------- + {(!,"[")} +(1 row) + +select '{[a,a]}'::textmultirange; + textmultirange +---------------- + {[a,a]} +(1 row) + +select '{[a,a],[a,b]}'::textmultirange; + textmultirange +---------------- + {[a,b]} +(1 row) + +select '{[a,b), [b,e]}'::textmultirange; + textmultirange +---------------- + {[a,e]} +(1 row) + +select '{[a,d), [b,f]}'::textmultirange; + textmultirange +---------------- + {[a,f]} +(1 row) + +select '{[a,a],[b,b]}'::textmultirange; + textmultirange +---------------- + {[a,a],[b,b]} +(1 row) + +-- without canonicalization, we can't join these: +select '{[a,a], [b,b]}'::textmultirange; + textmultirange +---------------- + {[a,a],[b,b]} +(1 row) + +-- with canonicalization, we can join these: +select '{[1,2], [3,4]}'::int4multirange; + int4multirange +---------------- + {[1,5)} +(1 row) + +select '{[a,a], [b,b], [c,c]}'::textmultirange; + textmultirange +--------------------- + {[a,a],[b,b],[c,c]} +(1 row) + +select '{[a,d], [b,e]}'::textmultirange; + textmultirange +---------------- + {[a,e]} +(1 row) + +select '{[a,d), [d,e)}'::textmultirange; + textmultirange +---------------- + {[a,e)} +(1 row) + +-- these are allowed but normalize to empty: +select '{[a,a)}'::textmultirange; + textmultirange +---------------- + {} +(1 row) + +select '{(a,a]}'::textmultirange; + textmultirange +---------------- + {} +(1 row) + +select '{(a,a)}'::textmultirange; + textmultirange +---------------- + {} +(1 row) + +-- +-- test the constructor +--- +select textmultirange(); + textmultirange +---------------- + {} +(1 row) + +select textmultirange(textrange('a', 'c')); + textmultirange +---------------- + {[a,c)} +(1 row) + +select textmultirange(textrange('a', 'c'), textrange('f', 'g')); + textmultirange +---------------- + {[a,c),[f,g)} +(1 row) + +select textmultirange(textrange('\\\\', repeat('a', 200)), textrange('c', 'd')); + textmultirange +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {["\\\\\\\\",aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa),[c,d)} +(1 row) + +-- +-- test casts, both a built-in range type and a user-defined one: +-- +select 'empty'::int4range::int4multirange; + int4multirange +---------------- + {} +(1 row) + +select int4range(1, 3)::int4multirange; + int4range +----------- + {[1,3)} +(1 row) + +select int4range(1, null)::int4multirange; + int4range +----------- + {[1,)} +(1 row) + +select int4range(null, null)::int4multirange; + int4range +----------- + {(,)} +(1 row) + +select 'empty'::textrange::textmultirange; + textmultirange +---------------- + {} +(1 row) + +select textrange('a', 'c')::textmultirange; + textrange +----------- + {[a,c)} +(1 row) + +select textrange('a', null)::textmultirange; + textrange +----------- + {[a,)} +(1 row) + +select textrange(null, null)::textmultirange; + textrange +----------- + {(,)} +(1 row) + +-- +-- test unnest(multirange) function +-- +select unnest(int4multirange(int4range('5', '6'), int4range('1', '2'))); + unnest +-------- + [1,2) + [5,6) +(2 rows) + +select unnest(textmultirange(textrange('a', 'b'), textrange('d', 'e'))); + unnest +-------- + [a,b) + [d,e) +(2 rows) + +select unnest(textmultirange(textrange('\\\\', repeat('a', 200)), textrange('c', 'd'))); + unnest +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + ["\\\\\\\\",aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) + [c,d) +(2 rows) + +-- +-- create some test data and test the operators +-- +CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE); +CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr); +ERROR: INDEX on column of type 'NUMMULTIRANGE' not yet supported +INSERT INTO nummultirange_test VALUES('{}'); +INSERT INTO nummultirange_test VALUES('{[,)}'); +INSERT INTO nummultirange_test VALUES('{[3,]}'); +INSERT INTO nummultirange_test VALUES('{[,), [3,]}'); +INSERT INTO nummultirange_test VALUES('{[, 5)}'); +INSERT INTO nummultirange_test VALUES(nummultirange()); +INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[])); +INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2))); +INSERT INTO nummultirange_test VALUES('{empty}'); +INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9))); +INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1))); +SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr; + nmr | isempty | lower | upper +-----------------------+---------+-------+------- + {} | t | | + {} | t | | + {} | t | | + {} | t | | + {(,5)} | f | | 5 + {(,)} | f | | + {(,)} | f | | + {[1.1,2.2)} | f | 1.1 | 2.2 + {[1.7,1.7],[1.9,2.1)} | f | 1.7 | 2.1 + {[1.7,1.9)} | f | 1.7 | 1.9 + {[3,)} | f | 3 | +(11 rows) + +SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr; + nmr | lower_inc | lower_inf | upper_inc | upper_inf +-----------------------+-----------+-----------+-----------+----------- + {} | f | f | f | f + {} | f | f | f | f + {} | f | f | f | f + {} | f | f | f | f + {(,5)} | f | t | f | f + {(,)} | f | t | f | t + {(,)} | f | t | f | t + {[1.1,2.2)} | t | f | f | f + {[1.7,1.7],[1.9,2.1)} | t | f | f | f + {[1.7,1.9)} | t | f | f | f + {[3,)} | t | f | f | t +(11 rows) + +SELECT * FROM nummultirange_test WHERE nmr = '{}'; + nmr +----- + {} + {} + {} + {} +(4 rows) + +SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}'; + nmr +-------- + {(,5)} +(1 row) + +SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}'; + nmr +-------- + {[3,)} +(1 row) + +SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}'; + nmr +----- +(0 rows) + +SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}'; + nmr +----------------------- + {[1.7,1.7],[1.9,2.1)} +(1 row) + +SELECT * FROM nummultirange_test WHERE nmr < '{}'; + nmr +----- +(0 rows) + +SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}'; + nmr +-------- + {} + {(,)} + {(,)} + {(,5)} + {} + {} + {} +(7 rows) + +SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}'; + nmr +-------- + {} + {(,)} + {(,)} + {(,5)} + {} + {} + {} +(7 rows) + +SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}'; + nmr +----------------------- + {} + {(,)} + {[3,)} + {(,)} + {(,5)} + {} + {} + {[1.1,2.2)} + {} + {[1.7,1.9)} + {[1.7,1.7],[1.9,2.1)} +(11 rows) + +SELECT * FROM nummultirange_test WHERE nmr <= '{}'; + nmr +----- + {} + {} + {} + {} +(4 rows) + +SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}'; + nmr +----------------------- + {} + {(,)} + {[3,)} + {(,)} + {(,5)} + {} + {} + {[1.1,2.2)} + {} + {[1.7,1.9)} + {[1.7,1.7],[1.9,2.1)} +(11 rows) + +SELECT * FROM nummultirange_test WHERE nmr >= '{}'; + nmr +----------------------- + {} + {(,)} + {[3,)} + {(,)} + {(,5)} + {} + {} + {[1.1,2.2)} + {} + {[1.7,1.9)} + {[1.7,1.7],[1.9,2.1)} +(11 rows) + +SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}'; + nmr +-------- + {[3,)} +(1 row) + +SELECT * FROM nummultirange_test WHERE nmr > '{}'; + nmr +----------------------- + {(,)} + {[3,)} + {(,)} + {(,5)} + {[1.1,2.2)} + {[1.7,1.9)} + {[1.7,1.7],[1.9,2.1)} +(7 rows) + +SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}'; + nmr +----------------------- + {[3,)} + {[1.1,2.2)} + {[1.7,1.9)} + {[1.7,1.7],[1.9,2.1)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}'; + nmr +----------------------- + {[3,)} + {[1.1,2.2)} + {[1.7,1.9)} + {[1.7,1.7],[1.9,2.1)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}'; + nmr +----- +(0 rows) + +SELECT * FROM nummultirange_test WHERE nmr <> '{}'; + nmr +----------------------- + {(,)} + {[3,)} + {(,)} + {(,5)} + {[1.1,2.2)} + {[1.7,1.9)} + {[1.7,1.7],[1.9,2.1)} +(7 rows) + +SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}'; + nmr +----------------------- + {} + {(,)} + {[3,)} + {(,)} + {} + {} + {[1.1,2.2)} + {} + {[1.7,1.9)} + {[1.7,1.7],[1.9,2.1)} +(10 rows) + +select nummultirange(numrange(2.0, 1.0)); +ERROR: range lower bound must be less than or equal to range upper bound +select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0)); + nummultirange +----------------------- + {[1.0,2.0),[5.0,6.0)} +(1 row) + +analyze nummultirange_test; +-- overlaps +SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr); + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr; + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2)); + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2); + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0))); + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)); + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0)); + nmr +-------- + {(,)} + {[3,)} + {(,)} +(3 rows) + +SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0)); + nmr +-------- + {(,)} + {[3,)} + {(,)} +(3 rows) + +-- mr contains x +SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0); + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE nmr @> 4.0; + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2)); + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2); + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}'); + nmr +-------- + {(,)} + {[3,)} + {(,)} +(3 rows) + +SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange; + nmr +-------- + {(,)} + {[3,)} + {(,)} +(3 rows) + +-- x is contained by mr +SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr); + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr; + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr); + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr; + nmr +-------- + {(,)} + {[3,)} + {(,)} + {(,5)} +(4 rows) + +SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr); + nmr +-------- + {(,)} + {[3,)} + {(,)} +(3 rows) + +SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr; + nmr +-------- + {(,)} + {[3,)} + {(,)} +(3 rows) + +-- overlaps +SELECT 'empty'::numrange && nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT 'empty'::numrange && nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange() && 'empty'::numrange; + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) && 'empty'::numrange; + ?column? +---------- + f +(1 row) + +SELECT nummultirange() && nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT nummultirange() && nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) && nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8)); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4)); + ?column? +---------- + t +(1 row) + +select '{(10,20),(30,40),(50,60)}'::nummultirange && '(42,92)'::numrange; + ?column? +---------- + t +(1 row) + +-- contains +SELECT nummultirange() @> nummultirange(); + ?column? +---------- + t +(1 row) + +SELECT nummultirange() @> 'empty'::numrange; + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(null,null)) @> numrange(1,2); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(null,null)) @> numrange(null,2); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(null,null)) @> numrange(2,null); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(null,5)) @> numrange(null,3); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(null,5)) @> numrange(null,8); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(5,null)) @> numrange(8,null); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(5,null)) @> numrange(3,null); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,5)) @> numrange(8,9); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,5)) @> numrange(3,9); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,5)) @> numrange(1,4); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,5)) @> numrange(1,5); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7); + ?column? +---------- + t +(1 row) + +SELECT '{[1,5)}'::nummultirange @> '{[1,5)}'; + ?column? +---------- + t +(1 row) + +SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}'; + ?column? +---------- + t +(1 row) + +SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}'; + ?column? +---------- + t +(1 row) + +SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}'; + ?column? +---------- + f +(1 row) + +SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}'; + ?column? +---------- + t +(1 row) + +select '{(10,20),(30,40),(50,60)}'::nummultirange @> '(52,56)'::numrange; + ?column? +---------- + t +(1 row) + +SELECT numrange(null,null) @> nummultirange(numrange(1,2)); + ?column? +---------- + t +(1 row) + +SELECT numrange(null,null) @> nummultirange(numrange(null,2)); + ?column? +---------- + t +(1 row) + +SELECT numrange(null,null) @> nummultirange(numrange(2,null)); + ?column? +---------- + t +(1 row) + +SELECT numrange(null,5) @> nummultirange(numrange(null,3)); + ?column? +---------- + t +(1 row) + +SELECT numrange(null,5) @> nummultirange(numrange(null,8)); + ?column? +---------- + f +(1 row) + +SELECT numrange(5,null) @> nummultirange(numrange(8,null)); + ?column? +---------- + t +(1 row) + +SELECT numrange(5,null) @> nummultirange(numrange(3,null)); + ?column? +---------- + f +(1 row) + +SELECT numrange(1,5) @> nummultirange(numrange(8,9)); + ?column? +---------- + f +(1 row) + +SELECT numrange(1,5) @> nummultirange(numrange(3,9)); + ?column? +---------- + f +(1 row) + +SELECT numrange(1,5) @> nummultirange(numrange(1,4)); + ?column? +---------- + t +(1 row) + +SELECT numrange(1,5) @> nummultirange(numrange(1,5)); + ?column? +---------- + t +(1 row) + +SELECT numrange(1,9) @> nummultirange(numrange(-4,-2), numrange(1,5)); + ?column? +---------- + f +(1 row) + +SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(8,9)); + ?column? +---------- + t +(1 row) + +SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(6,9)); + ?column? +---------- + t +(1 row) + +SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(6,10)); + ?column? +---------- + f +(1 row) + +SELECT '{[1,9)}' @> '{[1,5)}'::nummultirange; + ?column? +---------- + t +(1 row) + +SELECT '{[1,9)}' @> '{[-4,-2), [1,5)}'::nummultirange; + ?column? +---------- + f +(1 row) + +SELECT '{[1,9)}' @> '{[1,5), [8,9)}'::nummultirange; + ?column? +---------- + t +(1 row) + +SELECT '{[1,9)}' @> '{[1,5), [6,9)}'::nummultirange; + ?column? +---------- + t +(1 row) + +SELECT '{[1,9)}' @> '{[1,5), [6,10)}'::nummultirange; + ?column? +---------- + f +(1 row) + +-- is contained by +SELECT nummultirange() <@ nummultirange(); + ?column? +---------- + t +(1 row) + +SELECT 'empty'::numrange <@ nummultirange(); + ?column? +---------- + t +(1 row) + +SELECT numrange(1,2) <@ nummultirange(numrange(null,null)); + ?column? +---------- + t +(1 row) + +SELECT numrange(null,2) <@ nummultirange(numrange(null,null)); + ?column? +---------- + t +(1 row) + +SELECT numrange(2,null) <@ nummultirange(numrange(null,null)); + ?column? +---------- + t +(1 row) + +SELECT numrange(null,3) <@ nummultirange(numrange(null,5)); + ?column? +---------- + t +(1 row) + +SELECT numrange(null,8) <@ nummultirange(numrange(null,5)); + ?column? +---------- + f +(1 row) + +SELECT numrange(8,null) <@ nummultirange(numrange(5,null)); + ?column? +---------- + t +(1 row) + +SELECT numrange(3,null) <@ nummultirange(numrange(5,null)); + ?column? +---------- + f +(1 row) + +SELECT numrange(8,9) <@ nummultirange(numrange(1,5)); + ?column? +---------- + f +(1 row) + +SELECT numrange(3,9) <@ nummultirange(numrange(1,5)); + ?column? +---------- + f +(1 row) + +SELECT numrange(1,4) <@ nummultirange(numrange(1,5)); + ?column? +---------- + t +(1 row) + +SELECT numrange(1,5) <@ nummultirange(numrange(1,5)); + ?column? +---------- + t +(1 row) + +SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5)); + ?column? +---------- + t +(1 row) + +SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9)); + ?column? +---------- + t +(1 row) + +SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9)); + ?column? +---------- + f +(1 row) + +SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9)); + ?column? +---------- + t +(1 row) + +SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange; + ?column? +---------- + t +(1 row) + +SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange; + ?column? +---------- + t +(1 row) + +SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange; + ?column? +---------- + t +(1 row) + +SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange; + ?column? +---------- + f +(1 row) + +SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange; + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,2)) <@ numrange(null,null); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(null,2)) <@ numrange(null,null); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(2,null)) <@ numrange(null,null); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(null,3)) <@ numrange(null,5); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(null,8)) <@ numrange(null,5); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(8,null)) <@ numrange(5,null); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(3,null)) <@ numrange(5,null); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(8,9)) <@ numrange(1,5); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(3,9)) <@ numrange(1,5); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,4)) <@ numrange(1,5); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,5)) <@ numrange(1,5); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(-4,-2), numrange(1,5)) <@ numrange(1,9); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,5), numrange(8,9)) <@ numrange(1,9); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,5), numrange(6,9)) <@ numrange(1,9); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,5), numrange(6,10)) <@ numrange(1,9); + ?column? +---------- + f +(1 row) + +SELECT '{[1,5)}'::nummultirange <@ '{[1,9)}'; + ?column? +---------- + t +(1 row) + +SELECT '{[-4,-2), [1,5)}'::nummultirange <@ '{[1,9)}'; + ?column? +---------- + f +(1 row) + +SELECT '{[1,5), [8,9)}'::nummultirange <@ '{[1,9)}'; + ?column? +---------- + t +(1 row) + +SELECT '{[1,5), [6,9)}'::nummultirange <@ '{[1,9)}'; + ?column? +---------- + t +(1 row) + +SELECT '{[1,5), [6,10)}'::nummultirange <@ '{[1,9)}'; + ?column? +---------- + f +(1 row) + +-- overleft +SELECT 'empty'::numrange &< nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT 'empty'::numrange &< nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange() &< 'empty'::numrange; + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange; + ?column? +---------- + f +(1 row) + +SELECT nummultirange() &< nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) &< nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT nummultirange() &< nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +SELECT numrange(6,7) &< nummultirange(numrange(3,4)); + ?column? +---------- + f +(1 row) + +SELECT numrange(1,2) &< nummultirange(numrange(3,4)); + ?column? +---------- + t +(1 row) + +SELECT numrange(1,4) &< nummultirange(numrange(3,4)); + ?column? +---------- + t +(1 row) + +SELECT numrange(1,6) &< nummultirange(numrange(3,4)); + ?column? +---------- + f +(1 row) + +SELECT numrange(3.5,6) &< nummultirange(numrange(3,4)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(6,7)) &< numrange(3,4); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) &< numrange(3,4); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,4)) &< numrange(3,4); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,6)) &< numrange(3,4); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4)); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4)); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4)); + ?column? +---------- + f +(1 row) + +-- overright +SELECT nummultirange() &> 'empty'::numrange; + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange; + ?column? +---------- + f +(1 row) + +SELECT 'empty'::numrange &> nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT 'empty'::numrange &> nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange() &> nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT nummultirange() &> nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) &> nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(3,4)) &> numrange(6,7); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(3,4)) &> numrange(1,2); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(3,4)) &> numrange(1,4); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(3,4)) &> numrange(1,6); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6); + ?column? +---------- + f +(1 row) + +SELECT numrange(3,4) &> nummultirange(numrange(6,7)); + ?column? +---------- + f +(1 row) + +SELECT numrange(3,4) &> nummultirange(numrange(1,2)); + ?column? +---------- + t +(1 row) + +SELECT numrange(3,4) &> nummultirange(numrange(1,4)); + ?column? +---------- + t +(1 row) + +SELECT numrange(3,4) &> nummultirange(numrange(1,6)); + ?column? +---------- + t +(1 row) + +SELECT numrange(3,4) &> nummultirange(numrange(3.5,6)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2)); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4)); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6)); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6)); + ?column? +---------- + f +(1 row) + +-- meets +SELECT 'empty'::numrange -|- nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT 'empty'::numrange -|- nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange() -|- 'empty'::numrange; + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange; + ?column? +---------- + f +(1 row) + +SELECT nummultirange() -|- nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) -|- nummultirange(); + ?column? +---------- + f +(1 row) + +SELECT nummultirange() -|- nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +SELECT numrange(1,2) -|- nummultirange(numrange(2,4)); + ?column? +---------- + t +(1 row) + +SELECT numrange(1,2) -|- nummultirange(numrange(3,4)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) -|- numrange(2,4); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,2)) -|- numrange(3,4); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4)); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7)); + ?column? +---------- + t +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9)); + ?column? +---------- + f +(1 row) + +SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7)); + ?column? +---------- + t +(1 row) + +-- strictly left +select 'empty'::numrange << nummultirange(); + ?column? +---------- + f +(1 row) + +select numrange(1,2) << nummultirange(); + ?column? +---------- + f +(1 row) + +select numrange(1,2) << nummultirange(numrange(3,4)); + ?column? +---------- + t +(1 row) + +select numrange(1,2) << nummultirange(numrange(0,4)); + ?column? +---------- + f +(1 row) + +select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8)); + ?column? +---------- + f +(1 row) + +select nummultirange() << 'empty'::numrange; + ?column? +---------- + f +(1 row) + +select nummultirange() << numrange(1,2); + ?column? +---------- + f +(1 row) + +select nummultirange(numrange(3,4)) << numrange(3,6); + ?column? +---------- + f +(1 row) + +select nummultirange(numrange(0,2)) << numrange(3,6); + ?column? +---------- + t +(1 row) + +select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6); + ?column? +---------- + f +(1 row) + +select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6); + ?column? +---------- + t +(1 row) + +select nummultirange() << nummultirange(); + ?column? +---------- + f +(1 row) + +select nummultirange() << nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +select nummultirange(numrange(1,2)) << nummultirange(); + ?column? +---------- + f +(1 row) + +select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4)); + ?column? +---------- + t +(1 row) + +select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8)); + ?column? +---------- + t +(1 row) + +select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8)); + ?column? +---------- + f +(1 row) + +-- strictly right +select nummultirange() >> 'empty'::numrange; + ?column? +---------- + f +(1 row) + +select nummultirange() >> numrange(1,2); + ?column? +---------- + f +(1 row) + +select nummultirange(numrange(3,4)) >> numrange(1,2); + ?column? +---------- + t +(1 row) + +select nummultirange(numrange(0,4)) >> numrange(1,2); + ?column? +---------- + f +(1 row) + +select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2); + ?column? +---------- + f +(1 row) + +select 'empty'::numrange >> nummultirange(); + ?column? +---------- + f +(1 row) + +select numrange(1,2) >> nummultirange(); + ?column? +---------- + f +(1 row) + +select numrange(3,6) >> nummultirange(numrange(3,4)); + ?column? +---------- + f +(1 row) + +select numrange(3,6) >> nummultirange(numrange(0,2)); + ?column? +---------- + t +(1 row) + +select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8)); + ?column? +---------- + f +(1 row) + +select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2)); + ?column? +---------- + t +(1 row) + +select nummultirange() >> nummultirange(); + ?column? +---------- + f +(1 row) + +select nummultirange(numrange(1,2)) >> nummultirange(); + ?column? +---------- + f +(1 row) + +select nummultirange() >> nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2)); + ?column? +---------- + f +(1 row) + +select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2)); + ?column? +---------- + t +(1 row) + +select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2)); + ?column? +---------- + t +(1 row) + +select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5)); + ?column? +---------- + f +(1 row) + +-- union +SELECT nummultirange() + nummultirange(); + ?column? +---------- + {} +(1 row) + +SELECT nummultirange() + nummultirange(numrange(1,2)); + ?column? +---------- + {[1,2)} +(1 row) + +SELECT nummultirange(numrange(1,2)) + nummultirange(); + ?column? +---------- + {[1,2)} +(1 row) + +SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2)); + ?column? +---------- + {[1,2)} +(1 row) + +SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4)); + ?column? +---------- + {[1,4)} +(1 row) + +SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4)); + ?column? +--------------- + {[1,2),[3,4)} +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4)); + ?column? +---------- + {[1,5)} +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4)); + ?column? +--------------- + {[1,2),[3,5)} +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9)); + ?column? +---------- + {[0,9)} +(1 row) + +-- merge +SELECT range_merge(nummultirange()); + range_merge +------------- + empty +(1 row) + +SELECT range_merge(nummultirange(numrange(1,2))); + range_merge +------------- + [1,2) +(1 row) + +SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8))); + range_merge +------------- + [1,8) +(1 row) + +-- minus +SELECT nummultirange() - nummultirange(); + ?column? +---------- + {} +(1 row) + +SELECT nummultirange() - nummultirange(numrange(1,2)); + ?column? +---------- + {} +(1 row) + +SELECT nummultirange(numrange(1,2)) - nummultirange(); + ?column? +---------- + {[1,2)} +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange(); + ?column? +--------------- + {[1,2),[3,4)} +(1 row) + +SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2)); + ?column? +---------- + {} +(1 row) + +SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4)); + ?column? +---------- + {[1,2)} +(1 row) + +SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4)); + ?column? +---------- + {[1,2)} +(1 row) + +SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2)); + ?column? +---------- + {[2,4)} +(1 row) + +SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3)); + ?column? +--------------- + {[1,2),[3,4)} +(1 row) + +SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8)); + ?column? +---------- + {} +(1 row) + +SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2)); + ?column? +---------- + {[2,4)} +(1 row) + +SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4)); + ?column? +--------------- + {[2,3),[4,8)} +(1 row) + +SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null)); + ?column? +--------------- + {[1,2),[3,5)} +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0)); + ?column? +--------------- + {[1,2),[4,5)} +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4)); + ?column? +--------------- + {[1,2),[4,5)} +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5)); + ?column? +---------- + {[1,2)} +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9)); + ?column? +---------- + {} +(1 row) + +SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9)); + ?column? +---------- + {[1,2)} +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9)); + ?column? +--------------- + {[1,2),[4,5)} +(1 row) + +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9)); + ?column? +--------------- + {[1,2),[4,5)} +(1 row) + +-- intersection +SELECT nummultirange() * nummultirange(); + ?column? +---------- + {} +(1 row) + +SELECT nummultirange() * nummultirange(numrange(1,2)); + ?column? +---------- + {} +(1 row) + +SELECT nummultirange(numrange(1,2)) * nummultirange(); + ?column? +---------- + {} +(1 row) + +SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange; + ?column? +---------- + {[1,3)} +(1 row) + +SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange; + ?column? +---------- + {[1,3)} +(1 row) + +SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange; + ?column? +---------- + {[1,2)} +(1 row) + +SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange; + ?column? +---------- + {[2,3)} +(1 row) + +SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange; + ?column? +---------- + {[2,3)} +(1 row) + +SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange; + ?column? +--------------- + {[1,2),[3,4)} +(1 row) + +SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange; + ?column? +---------------------- + {[1,4),[7,8),[9,10)} +(1 row) + +SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange; + ?column? +---------- + {[9,10)} +(1 row) + +SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange; + ?column? +---------- + {[9,10)} +(1 row) + +SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange; + ?column? +---------------------------- + {[1,2),[3,4),[7,8),[9,10)} +(1 row) + +SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange; + ?column? +---------------------------- + {[1,2),[3,4),[7,8),[9,10)} +(1 row) + +-- test GiST index +create table test_multirange_gist(mr int4multirange); +insert into test_multirange_gist select int4multirange(int4range(g, g+10),int4range(g+20, g+30),int4range(g+40, g+50)) from generate_series(1,2000) g; +insert into test_multirange_gist select '{}'::int4multirange from generate_series(1,500) g; +insert into test_multirange_gist select int4multirange(int4range(g, g+10000)) from generate_series(1,1000) g; +insert into test_multirange_gist select int4multirange(int4range(NULL, g*10, '(]'), int4range(g*10, g*20, '(]')) from generate_series(1,100) g; +insert into test_multirange_gist select int4multirange(int4range(g*10, g*20, '(]'), int4range(g*20, NULL, '(]')) from generate_series(1,100) g; +create index test_mulrirange_gist_idx on test_multirange_gist using gist (mr); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +-- test statistics and selectivity estimation as well +-- +-- We don't check the accuracy of selectivity estimation, but at least check +-- it doesn't fall. +analyze test_multirange_gist; +-- first, verify non-indexed results +SET enable_seqscan = t; +SET enable_indexscan = f; +SET enable_bitmapscan = f; +select count(*) from test_multirange_gist where mr = '{}'::int4multirange; + count +------- + 500 +(1 row) + +select count(*) from test_multirange_gist where mr @> 'empty'::int4range; + count +------- + 3700 +(1 row) + +select count(*) from test_multirange_gist where mr && 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr <@ 'empty'::int4range; + count +------- + 500 +(1 row) + +select count(*) from test_multirange_gist where mr << 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr >> 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr &< 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr &> 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr -|- 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; + count +------- + 3700 +(1 row) + +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; + count +------- + 3700 +(1 row) + +select count(*) from test_multirange_gist where mr && '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr <@ '{}'::int4multirange; + count +------- + 500 +(1 row) + +select count(*) from test_multirange_gist where mr << '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr >> '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr &< '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr &> '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr -|- '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60)); + count +------- + 1 +(1 row) + +select count(*) from test_multirange_gist where mr @> 10; + count +------- + 120 +(1 row) + +select count(*) from test_multirange_gist where mr @> int4range(10,20); + count +------- + 111 +(1 row) + +select count(*) from test_multirange_gist where mr && int4range(10,20); + count +------- + 139 +(1 row) + +select count(*) from test_multirange_gist where mr <@ int4range(10,50); + count +------- + 500 +(1 row) + +select count(*) from test_multirange_gist where mr << int4range(100,500); + count +------- + 54 +(1 row) + +select count(*) from test_multirange_gist where mr >> int4range(100,500); + count +------- + 2053 +(1 row) + +select count(*) from test_multirange_gist where mr &< int4range(100,500); + count +------- + 474 +(1 row) + +select count(*) from test_multirange_gist where mr &> int4range(100,500); + count +------- + 2893 +(1 row) + +select count(*) from test_multirange_gist where mr -|- int4range(100,500); + count +------- + 3 +(1 row) + +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; + count +------- + 3700 +(1 row) + +select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40)); + count +------- + 110 +(1 row) + +select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange; + count +------- + 218 +(1 row) + +select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange; + count +------- + 500 +(1 row) + +select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500)); + count +------- + 54 +(1 row) + +select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500)); + count +------- + 2053 +(1 row) + +select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500)); + count +------- + 474 +(1 row) + +select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500)); + count +------- + 2893 +(1 row) + +select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500)); + count +------- + 3 +(1 row) + +-- now check same queries using index +SET enable_seqscan = f; +SET enable_indexscan = t; +SET enable_bitmapscan = f; +select count(*) from test_multirange_gist where mr = '{}'::int4multirange; + count +------- + 500 +(1 row) + +select count(*) from test_multirange_gist where mr @> 'empty'::int4range; + count +------- + 3700 +(1 row) + +select count(*) from test_multirange_gist where mr && 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr <@ 'empty'::int4range; + count +------- + 500 +(1 row) + +select count(*) from test_multirange_gist where mr << 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr >> 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr &< 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr &> 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr -|- 'empty'::int4range; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; + count +------- + 3700 +(1 row) + +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; + count +------- + 3700 +(1 row) + +select count(*) from test_multirange_gist where mr && '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr <@ '{}'::int4multirange; + count +------- + 500 +(1 row) + +select count(*) from test_multirange_gist where mr << '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr >> '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr &< '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr &> '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr -|- '{}'::int4multirange; + count +------- + 0 +(1 row) + +select count(*) from test_multirange_gist where mr @> 'empty'::int4range; + count +------- + 3700 +(1 row) + +select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60)); + count +------- + 1 +(1 row) + +select count(*) from test_multirange_gist where mr @> 10; + count +------- + 120 +(1 row) + +select count(*) from test_multirange_gist where mr @> int4range(10,20); + count +------- + 111 +(1 row) + +select count(*) from test_multirange_gist where mr && int4range(10,20); + count +------- + 139 +(1 row) + +select count(*) from test_multirange_gist where mr <@ int4range(10,50); + count +------- + 500 +(1 row) + +select count(*) from test_multirange_gist where mr << int4range(100,500); + count +------- + 54 +(1 row) + +select count(*) from test_multirange_gist where mr >> int4range(100,500); + count +------- + 2053 +(1 row) + +select count(*) from test_multirange_gist where mr &< int4range(100,500); + count +------- + 474 +(1 row) + +select count(*) from test_multirange_gist where mr &> int4range(100,500); + count +------- + 2893 +(1 row) + +select count(*) from test_multirange_gist where mr -|- int4range(100,500); + count +------- + 3 +(1 row) + +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; + count +------- + 3700 +(1 row) + +select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40)); + count +------- + 110 +(1 row) + +select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange; + count +------- + 218 +(1 row) + +select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange; + count +------- + 500 +(1 row) + +select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500)); + count +------- + 54 +(1 row) + +select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500)); + count +------- + 2053 +(1 row) + +select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500)); + count +------- + 474 +(1 row) + +select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500)); + count +------- + 2893 +(1 row) + +select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500)); + count +------- + 3 +(1 row) + +drop table test_multirange_gist; +-- +-- range_agg function +-- +create table reservations ( room_id integer not null, booked_during daterange ); +insert into reservations values +-- 1: has a meets and a gap +(1, daterange('2018-07-01', '2018-07-07')), +(1, daterange('2018-07-07', '2018-07-14')), +(1, daterange('2018-07-20', '2018-07-22')), +-- 2: just a single row +(2, daterange('2018-07-01', '2018-07-03')), +-- 3: one null range +(3, NULL), +-- 4: two null ranges +(4, NULL), +(4, NULL), +-- 5: a null range and a non-null range +(5, NULL), +(5, daterange('2018-07-01', '2018-07-03')), +-- 6: has overlap +(6, daterange('2018-07-01', '2018-07-07')), +(6, daterange('2018-07-05', '2018-07-10')), +-- 7: two ranges that meet: no gap or overlap +(7, daterange('2018-07-01', '2018-07-07')), +(7, daterange('2018-07-07', '2018-07-14')), +-- 8: an empty range +(8, 'empty'::daterange) +; +SELECT room_id, range_agg(booked_during) +FROM reservations +GROUP BY room_id +ORDER BY room_id; + room_id | range_agg +---------+--------------------------------------------------- + 1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)} + 2 | {[07-01-2018,07-03-2018)} + 3 | + 4 | + 5 | {[07-01-2018,07-03-2018)} + 6 | {[07-01-2018,07-10-2018)} + 7 | {[07-01-2018,07-14-2018)} + 8 | {} +(8 rows) + +-- range_agg on a custom range type too +SELECT range_agg(r) +FROM (VALUES + ('[a,c]'::textrange), + ('[b,b]'::textrange), + ('[c,f]'::textrange), + ('[g,h)'::textrange), + ('[h,j)'::textrange) + ) t(r); + range_agg +--------------- + {[a,f],[g,j)} +(1 row) + +-- range_agg with multirange inputs +select range_agg(nmr) from nummultirange_test; + range_agg +----------- + {(,)} +(1 row) + +select range_agg(nmr) from nummultirange_test where false; + range_agg +----------- + +(1 row) + +select range_agg(null::nummultirange) from nummultirange_test; + range_agg +----------- + +(1 row) + +select range_agg(nmr) from (values ('{}'::nummultirange)) t(nmr); + range_agg +----------- + {} +(1 row) + +select range_agg(nmr) from (values ('{}'::nummultirange), ('{}'::nummultirange)) t(nmr); + range_agg +----------- + {} +(1 row) + +select range_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr); + range_agg +----------- + {[1,2]} +(1 row) + +select range_agg(nmr) from (values ('{[1,2], [5,6]}'::nummultirange)) t(nmr); + range_agg +--------------- + {[1,2],[5,6]} +(1 row) + +select range_agg(nmr) from (values ('{[1,2], [2,3]}'::nummultirange)) t(nmr); + range_agg +----------- + {[1,3]} +(1 row) + +select range_agg(nmr) from (values ('{[1,2]}'::nummultirange), ('{[5,6]}'::nummultirange)) t(nmr); + range_agg +--------------- + {[1,2],[5,6]} +(1 row) + +select range_agg(nmr) from (values ('{[1,2]}'::nummultirange), ('{[2,3]}'::nummultirange)) t(nmr); + range_agg +----------- + {[1,3]} +(1 row) + +-- +-- range_intersect_agg function +-- +select range_intersect_agg(nmr) from nummultirange_test; + range_intersect_agg +--------------------- + {} +(1 row) + +select range_intersect_agg(nmr) from nummultirange_test where false; + range_intersect_agg +--------------------- + +(1 row) + +select range_intersect_agg(null::nummultirange) from nummultirange_test; + range_intersect_agg +--------------------- + +(1 row) + +select range_intersect_agg(nmr) from (values ('{[1,3]}'::nummultirange), ('{[6,12]}'::nummultirange)) t(nmr); + range_intersect_agg +--------------------- + {} +(1 row) + +select range_intersect_agg(nmr) from (values ('{[1,6]}'::nummultirange), ('{[3,12]}'::nummultirange)) t(nmr); + range_intersect_agg +--------------------- + {[3,6]} +(1 row) + +select range_intersect_agg(nmr) from (values ('{[1,6], [10,12]}'::nummultirange), ('{[4,14]}'::nummultirange)) t(nmr); + range_intersect_agg +--------------------- + {[4,6],[10,12]} +(1 row) + +-- test with just one input: +select range_intersect_agg(nmr) from (values ('{}'::nummultirange)) t(nmr); + range_intersect_agg +--------------------- + {} +(1 row) + +select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr); + range_intersect_agg +--------------------- + {[1,2]} +(1 row) + +select range_intersect_agg(nmr) from (values ('{[1,6], [10,12]}'::nummultirange)) t(nmr); + range_intersect_agg +--------------------- + {[1,6],[10,12]} +(1 row) + +select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0; + range_intersect_agg +--------------------- + {[3,5)} +(1 row) + +create table nummultirange_test2(nmr nummultirange); +create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr); +NOTICE: substituting access method "lsm" for "hash" in YugabyteDB +ERROR: INDEX on column of type 'NUMMULTIRANGE' not yet supported +INSERT INTO nummultirange_test2 VALUES('{[, 5)}'); +INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2))); +INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2))); +INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()'))); +INSERT INTO nummultirange_test2 VALUES('{}'); +select * from nummultirange_test2 where nmr = '{}'; + nmr +----- + {} +(1 row) + +select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2)); + nmr +------------- + {[1.1,2.2)} + {[1.1,2.2)} +(2 rows) + +select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3)); + nmr +----- +(0 rows) + +set enable_nestloop=t; +set enable_hashjoin=f; +set enable_mergejoin=f; +select * from nummultirange_test natural join nummultirange_test2 order by nmr; + nmr +------------- + {} + {} + {} + {} + {(,5)} + {[1.1,2.2)} + {[1.1,2.2)} +(7 rows) + +set enable_nestloop=f; +set enable_hashjoin=t; +set enable_mergejoin=f; +select * from nummultirange_test natural join nummultirange_test2 order by nmr; + nmr +------------- + {} + {} + {} + {} + {(,5)} + {[1.1,2.2)} + {[1.1,2.2)} +(7 rows) + +set enable_nestloop=f; +set enable_hashjoin=f; +set enable_mergejoin=t; +select * from nummultirange_test natural join nummultirange_test2 order by nmr; + nmr +------------- + {} + {} + {} + {} + {(,5)} + {[1.1,2.2)} + {[1.1,2.2)} +(7 rows) + +set enable_nestloop to default; +set enable_hashjoin to default; +set enable_mergejoin to default; +DROP TABLE nummultirange_test2; +-- +-- Test user-defined multirange of floats +-- +select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8; + ?column? +---------- + t +(1 row) + +create table float8multirange_test(f8mr float8multirange, i int); +ERROR: type not yet supported in Yugabyte: (user_defined_type) +-- +-- Test multirange types over domains +-- +create domain mydomain as int4; +create type mydomainrange as range(subtype=mydomain); +select '{[4,50)}'::mydomainmultirange @> 7::mydomain; + ?column? +---------- + t +(1 row) + +drop domain mydomain cascade; +NOTICE: drop cascades to type mydomainrange +-- +-- Test domains over multirange types +-- +create domain restrictedmultirange as int4multirange check (upper(value) < 10); +select '{[4,5)}'::restrictedmultirange @> 7; + ?column? +---------- + f +(1 row) + +select '{[4,50)}'::restrictedmultirange @> 7; -- should fail +ERROR: value for domain restrictedmultirange violates check constraint "restrictedmultirange_check" +drop domain restrictedmultirange; +--- +-- Check automatic naming of multiranges +--- +create type intr as range(subtype=int); +select intr_multirange(intr(1,10)); + intr_multirange +----------------- + {[1,10)} +(1 row) + +drop type intr; +create type intmultirange as (x int, y int); +create type intrange as range(subtype=int); -- should fail +ERROR: type "intmultirange" already exists +DETAIL: Failed while creating a multirange type for type "intrange". +HINT: You can manually specify a multirange type name using the "multirange_type_name" attribute. +drop type intmultirange; +create type intr_multirange as (x int, y int); +create type intr as range(subtype=int); -- should fail +ERROR: type "intr_multirange" already exists +DETAIL: Failed while creating a multirange type for type "intr". +HINT: You can manually specify a multirange type name using the "multirange_type_name" attribute. +drop type intr_multirange; +-- +-- Test multiple multirange types over the same subtype and manual naming of +-- the multirange type. +-- +-- should fail +create type textrange1 as range(subtype=text, multirange_type_name=int, collation="C"); +ERROR: type "int4" already exists +-- should pass +create type textrange1 as range(subtype=text, multirange_type_name=multirange_of_text, collation="C"); +-- should pass, because existing _textrange1 is automatically renamed +create type textrange2 as range(subtype=text, multirange_type_name=_textrange1, collation="C"); +select multirange_of_text(textrange2('a','Z')); -- should fail +ERROR: function multirange_of_text(textrange2) does not exist +LINE 1: select multirange_of_text(textrange2('a','Z')); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +select multirange_of_text(textrange1('a','Z')) @> 'b'::text; +ERROR: range lower bound must be less than or equal to range upper bound +select unnest(multirange_of_text(textrange1('a','b'), textrange1('d','e'))); + unnest +-------- + [a,b) + [d,e) +(2 rows) + +select _textrange1(textrange2('a','z')) @> 'b'::text; + ?column? +---------- + t +(1 row) + +drop type textrange1; +drop type textrange2; +-- +-- Test polymorphic type system +-- +create function anyarray_anymultirange_func(a anyarray, r anymultirange) + returns anyelement as 'select $1[1] + lower($2);' language sql; +select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20))); + anyarray_anymultirange_func +----------------------------- + 11 +(1 row) + +-- should fail +select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20))); +ERROR: function anyarray_anymultirange_func(integer[], nummultirange) does not exist +LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +drop function anyarray_anymultirange_func(anyarray, anymultirange); +-- should fail +create function bogus_func(anyelement) + returns anymultirange as 'select int4multirange(int4range(1,10))' language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anymultirange requires at least one input of type anyrange or anymultirange. +-- should fail +create function bogus_func(int) + returns anymultirange as 'select int4multirange(int4range(1,10))' language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anymultirange requires at least one input of type anyrange or anymultirange. +create function range_add_bounds(anymultirange) + returns anyelement as 'select lower($1) + upper($1)' language sql; +select range_add_bounds(int4multirange(int4range(1, 17))); + range_add_bounds +------------------ + 18 +(1 row) + +select range_add_bounds(nummultirange(numrange(1.0001, 123.123))); + range_add_bounds +------------------ + 124.1231 +(1 row) + +create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement) + as $$ select upper($1) + $2[1] $$ + language sql; +select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]); + multirangetypes_sql +--------------------- + 12 +(1 row) + +select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]); -- match failure +ERROR: function multirangetypes_sql(nummultirange, integer[]) does not exist +LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange) + returns anycompatible as 'select $1[1] + lower($2);' language sql; +select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20))); + anycompatiblearray_anycompatiblemultirange_func +------------------------------------------------- + 11 +(1 row) + +select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20))); + anycompatiblearray_anycompatiblemultirange_func +------------------------------------------------- + 11 +(1 row) + +-- should fail +select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20))); +ERROR: function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist +LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange); +create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange) + returns anycompatible as 'select lower($1) + lower($2);' language sql; +select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20))); + anycompatiblerange_anycompatiblemultirange_func +------------------------------------------------- + 11 +(1 row) + +-- should fail +select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20))); +ERROR: function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist +LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange); +-- should fail +create function bogus_func(anycompatible) + returns anycompatiblerange as 'select int4range(1,10)' language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange. +-- +-- Arrays of multiranges +-- +select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))]; + array +---------------------------------- + {"{[1.1,1.2)}","{[12.3,155.5)}"} +(1 row) + +create table i8mr_array (f1 int, f2 int8multirange[]); +insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]); +select * from i8mr_array; + f1 | f2 +----+------------------------- + 42 | {"{[1,10)}","{[2,20)}"} +(1 row) + +drop table i8mr_array; +-- +-- Multiranges of arrays +-- +select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1])); + arraymultirange +--------------------- + {["{1,2}","{2,1}")} +(1 row) + +select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2])); -- fail +ERROR: range lower bound must be less than or equal to range upper bound +select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1])); + ?column? +---------- + f +(1 row) + +select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1])); + ?column? +---------- + t +(1 row) + +-- +-- Ranges of composites +-- +create type two_ints as (a int, b int); +create type two_ints_range as range (subtype = two_ints); +-- with force_parallel_mode on, this exercises tqueue.c's range remapping +select *, row_to_json(upper(t)) as u from + (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))), + (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t); + t | u +---------------------+--------------- + {["(1,2)","(3,4)")} | {"a":3,"b":4} + {["(5,6)","(7,8)")} | {"a":7,"b":8} +(2 rows) + +drop type two_ints cascade; +NOTICE: drop cascades to type two_ints_range +-- +-- Check behavior when subtype lacks a hash function +-- +set enable_sort = off; -- try to make it pick a hash setop implementation +select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange; + cashmultirange +----------------- + {($2.00,$5.00)} +(1 row) + +reset enable_sort; +-- +-- OUT/INOUT/TABLE functions +-- +-- infer anymultirange from anymultirange +create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text) + as $$ select $1, 'foo'::text $$ language sql; +select * from mr_outparam_succeed(int4multirange(int4range(1,2))); + r | t +---------+----- + {[1,2)} | foo +(1 row) + +-- infer anyarray from anymultirange +create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text) + as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql; +select * from mr_outparam_succeed2(int4multirange(int4range(1,2))); + r | t +-----+----- + {2} | foo +(1 row) + +-- infer anyrange from anymultirange +create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text) + as $$ select range_merge($1), 'foo'::text $$ language sql; +select * from mr_outparam_succeed3(int4multirange(int4range(1,2))); + r | t +-------+----- + [1,2) | foo +(1 row) + +-- infer anymultirange from anyrange +create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text) + as $$ select multirange($1), 'foo'::text $$ language sql; +select * from mr_outparam_succeed4(int4range(1,2)); + r | t +---------+----- + {[1,2)} | foo +(1 row) + +-- infer anyelement from anymultirange +create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange) + as $$ select upper($1), $1 $$ language sql; +select * from mr_inoutparam_succeed(int4multirange(int4range(1,2))); + i | r +---+--------- + 2 | {[1,2)} +(1 row) + +-- infer anyelement+anymultirange from anyelement+anymultirange +create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange) + as $$ select $1, $2 $$ language sql; +select * from mr_table_succeed(123, int4multirange(int4range(1,11))); + i | r +-----+---------- + 123 | {[1,11)} +(1 row) + +-- use anymultirange in plpgsql +create function mr_polymorphic(i anyrange) returns anymultirange + as $$ begin return multirange($1); end; $$ language plpgsql; +select mr_polymorphic(int4range(1, 4)); + mr_polymorphic +---------------- + {[1,4)} +(1 row) + +-- should fail +create function mr_outparam_fail(i anyelement, out r anymultirange, out t text) + as $$ select '[1,10]', 'foo' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anymultirange requires at least one input of type anyrange or anymultirange. +--should fail +create function mr_inoutparam_fail(inout i anyelement, out r anymultirange) + as $$ select $1, '[1,10]' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anymultirange requires at least one input of type anyrange or anymultirange. +--should fail +create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange) + as $$ select $1, '[1,10]' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anymultirange requires at least one input of type anyrange or anymultirange. diff --git a/src/test/regress/expected/yb.port.namespace.out b/src/test/regress/expected/yb.port.namespace.out new file mode 100644 index 0000000000000..6511a87cb022f --- /dev/null +++ b/src/test/regress/expected/yb.port.namespace.out @@ -0,0 +1,125 @@ +-- +-- Regression tests for schemas (namespaces) +-- +-- set the whitespace-only search_path to test that the +-- GUC list syntax is preserved during a schema creation +SELECT pg_catalog.set_config('search_path', ' ', false); + set_config +------------ + +(1 row) + +CREATE SCHEMA test_ns_schema_1 + CREATE UNIQUE INDEX abc_a_idx ON abc (a) + CREATE VIEW abc_view AS + SELECT a+1 AS a, b+1 AS b FROM abc + CREATE TABLE abc ( + a serial, + b int UNIQUE + ); +ERROR: CREATE SCHEMA with elements not supported yet +LINE 2: CREATE UNIQUE INDEX abc_a_idx ON abc (a) + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +CREATE SCHEMA test_ns_schema_1; -- YB: TODO(dmitry): workaround for schema creation with elements +CREATE TABLE test_ns_schema_1.abc (a serial, b int UNIQUE); -- YB +CREATE UNIQUE INDEX abc_a_idx ON test_ns_schema_1.abc (a); -- YB +CREATE VIEW test_ns_schema_1.abc_view AS SELECT a+1 AS a, b+1 AS b FROM test_ns_schema_1.abc; -- YB +-- verify that the correct search_path restored on abort +SET search_path to public; +BEGIN; +SET search_path to public, test_ns_schema_1; +CREATE SCHEMA test_ns_schema_2; -- YB: TODO(dmitry): schema creation with elements +CREATE VIEW test_ns_schema_2.abc_view AS SELECT c FROM abc; -- YB +ERROR: column "c" does not exist +LINE 1: CREATE VIEW test_ns_schema_2.abc_view AS SELECT c FROM abc; + ^ +COMMIT; +DROP SCHEMA test_ns_schema_2 CASCADE; -- YB: manually drop SCHEMA because above COMMIT rollback fails to roll back CREATE SCHEMA DDL +SHOW search_path; + search_path +------------- + public +(1 row) + +-- verify that the correct search_path preserved +-- after creating the schema and on commit +BEGIN; +SET search_path to public, test_ns_schema_1; +CREATE SCHEMA test_ns_schema_2; -- YB: TODO(dmitry): schema creation with elements +CREATE VIEW test_ns_schema_2.abc_view AS SELECT a FROM abc; -- YB +SHOW search_path; + search_path +-------------------------- + public, test_ns_schema_1 +(1 row) + +COMMIT; +SHOW search_path; + search_path +-------------------------- + public, test_ns_schema_1 +(1 row) + +DROP SCHEMA test_ns_schema_2 CASCADE; +NOTICE: drop cascades to view test_ns_schema_2.abc_view +-- verify that the objects were created +SELECT COUNT(*) FROM pg_class WHERE relnamespace = + (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_1'); + count +------- + 5 +(1 row) + +INSERT INTO test_ns_schema_1.abc DEFAULT VALUES; +INSERT INTO test_ns_schema_1.abc DEFAULT VALUES; +INSERT INTO test_ns_schema_1.abc DEFAULT VALUES; +SELECT * FROM test_ns_schema_1.abc; + a | b +---+--- + 1 | + 2 | + 3 | +(3 rows) + +SELECT * FROM test_ns_schema_1.abc_view; + a | b +---+--- + 2 | + 3 | + 4 | +(3 rows) + +ALTER SCHEMA test_ns_schema_1 RENAME TO test_ns_schema_renamed; +SELECT COUNT(*) FROM pg_class WHERE relnamespace = + (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_1'); + count +------- + 0 +(1 row) + +-- test IF NOT EXISTS cases +CREATE SCHEMA test_ns_schema_renamed; -- fail, already exists +ERROR: schema "test_ns_schema_renamed" already exists +CREATE SCHEMA IF NOT EXISTS test_ns_schema_renamed; -- ok with notice +NOTICE: schema "test_ns_schema_renamed" already exists, skipping +CREATE SCHEMA IF NOT EXISTS test_ns_schema_renamed -- fail, disallowed + CREATE TABLE abc ( + a serial, + b int UNIQUE + ); +ERROR: CREATE SCHEMA IF NOT EXISTS cannot include schema elements +LINE 2: CREATE TABLE abc ( + ^ +DROP SCHEMA test_ns_schema_renamed CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table test_ns_schema_renamed.abc +drop cascades to view test_ns_schema_renamed.abc_view +-- verify that the objects were dropped +SELECT COUNT(*) FROM pg_class WHERE relnamespace = + (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_renamed'); + count +------- + 0 +(1 row) + diff --git a/src/test/regress/expected/yb.port.numeric.out b/src/test/regress/expected/yb.port.numeric.out new file mode 100644 index 0000000000000..0f3452a5f5ceb --- /dev/null +++ b/src/test/regress/expected/yb.port.numeric.out @@ -0,0 +1,3363 @@ +-- +-- NUMERIC +-- +CREATE TABLE num_data (id int4, val numeric(210,10)); +CREATE TABLE num_exp_add (id1 int4, id2 int4, expected numeric(210,10)); +CREATE TABLE num_exp_sub (id1 int4, id2 int4, expected numeric(210,10)); +CREATE TABLE num_exp_div (id1 int4, id2 int4, expected numeric(210,10)); +CREATE TABLE num_exp_mul (id1 int4, id2 int4, expected numeric(210,10)); +CREATE TABLE num_exp_sqrt (id int4, expected numeric(210,10)); +CREATE TABLE num_exp_ln (id int4, expected numeric(210,10)); +CREATE TABLE num_exp_log10 (id int4, expected numeric(210,10)); +CREATE TABLE num_exp_power_10_ln (id int4, expected numeric(210,10)); +CREATE TABLE num_result (id1 int4, id2 int4, result numeric(210,10)); +-- ****************************** +-- * The following EXPECTED results are computed by bc(1) +-- * with a scale of 200 +-- ****************************** +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_add VALUES (0,0,'0'); +INSERT INTO num_exp_sub VALUES (0,0,'0'); +INSERT INTO num_exp_mul VALUES (0,0,'0'); +INSERT INTO num_exp_div VALUES (0,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (0,1,'0'); +INSERT INTO num_exp_sub VALUES (0,1,'0'); +INSERT INTO num_exp_mul VALUES (0,1,'0'); +INSERT INTO num_exp_div VALUES (0,1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (0,2,'-34338492.215397047'); +INSERT INTO num_exp_sub VALUES (0,2,'34338492.215397047'); +INSERT INTO num_exp_mul VALUES (0,2,'0'); +INSERT INTO num_exp_div VALUES (0,2,'0'); +INSERT INTO num_exp_add VALUES (0,3,'4.31'); +INSERT INTO num_exp_sub VALUES (0,3,'-4.31'); +INSERT INTO num_exp_mul VALUES (0,3,'0'); +INSERT INTO num_exp_div VALUES (0,3,'0'); +INSERT INTO num_exp_add VALUES (0,4,'7799461.4119'); +INSERT INTO num_exp_sub VALUES (0,4,'-7799461.4119'); +INSERT INTO num_exp_mul VALUES (0,4,'0'); +INSERT INTO num_exp_div VALUES (0,4,'0'); +INSERT INTO num_exp_add VALUES (0,5,'16397.038491'); +INSERT INTO num_exp_sub VALUES (0,5,'-16397.038491'); +INSERT INTO num_exp_mul VALUES (0,5,'0'); +INSERT INTO num_exp_div VALUES (0,5,'0'); +INSERT INTO num_exp_add VALUES (0,6,'93901.57763026'); +INSERT INTO num_exp_sub VALUES (0,6,'-93901.57763026'); +INSERT INTO num_exp_mul VALUES (0,6,'0'); +INSERT INTO num_exp_div VALUES (0,6,'0'); +INSERT INTO num_exp_add VALUES (0,7,'-83028485'); +INSERT INTO num_exp_sub VALUES (0,7,'83028485'); +INSERT INTO num_exp_mul VALUES (0,7,'0'); +INSERT INTO num_exp_div VALUES (0,7,'0'); +INSERT INTO num_exp_add VALUES (0,8,'74881'); +INSERT INTO num_exp_sub VALUES (0,8,'-74881'); +INSERT INTO num_exp_mul VALUES (0,8,'0'); +INSERT INTO num_exp_div VALUES (0,8,'0'); +INSERT INTO num_exp_add VALUES (0,9,'-24926804.045047420'); +INSERT INTO num_exp_sub VALUES (0,9,'24926804.045047420'); +INSERT INTO num_exp_mul VALUES (0,9,'0'); +INSERT INTO num_exp_div VALUES (0,9,'0'); +INSERT INTO num_exp_add VALUES (1,0,'0'); +INSERT INTO num_exp_sub VALUES (1,0,'0'); +INSERT INTO num_exp_mul VALUES (1,0,'0'); +INSERT INTO num_exp_div VALUES (1,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (1,1,'0'); +INSERT INTO num_exp_sub VALUES (1,1,'0'); +INSERT INTO num_exp_mul VALUES (1,1,'0'); +INSERT INTO num_exp_div VALUES (1,1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (1,2,'-34338492.215397047'); +INSERT INTO num_exp_sub VALUES (1,2,'34338492.215397047'); +INSERT INTO num_exp_mul VALUES (1,2,'0'); +INSERT INTO num_exp_div VALUES (1,2,'0'); +INSERT INTO num_exp_add VALUES (1,3,'4.31'); +INSERT INTO num_exp_sub VALUES (1,3,'-4.31'); +INSERT INTO num_exp_mul VALUES (1,3,'0'); +INSERT INTO num_exp_div VALUES (1,3,'0'); +INSERT INTO num_exp_add VALUES (1,4,'7799461.4119'); +INSERT INTO num_exp_sub VALUES (1,4,'-7799461.4119'); +INSERT INTO num_exp_mul VALUES (1,4,'0'); +INSERT INTO num_exp_div VALUES (1,4,'0'); +INSERT INTO num_exp_add VALUES (1,5,'16397.038491'); +INSERT INTO num_exp_sub VALUES (1,5,'-16397.038491'); +INSERT INTO num_exp_mul VALUES (1,5,'0'); +INSERT INTO num_exp_div VALUES (1,5,'0'); +INSERT INTO num_exp_add VALUES (1,6,'93901.57763026'); +INSERT INTO num_exp_sub VALUES (1,6,'-93901.57763026'); +INSERT INTO num_exp_mul VALUES (1,6,'0'); +INSERT INTO num_exp_div VALUES (1,6,'0'); +INSERT INTO num_exp_add VALUES (1,7,'-83028485'); +INSERT INTO num_exp_sub VALUES (1,7,'83028485'); +INSERT INTO num_exp_mul VALUES (1,7,'0'); +INSERT INTO num_exp_div VALUES (1,7,'0'); +INSERT INTO num_exp_add VALUES (1,8,'74881'); +INSERT INTO num_exp_sub VALUES (1,8,'-74881'); +INSERT INTO num_exp_mul VALUES (1,8,'0'); +INSERT INTO num_exp_div VALUES (1,8,'0'); +INSERT INTO num_exp_add VALUES (1,9,'-24926804.045047420'); +INSERT INTO num_exp_sub VALUES (1,9,'24926804.045047420'); +INSERT INTO num_exp_mul VALUES (1,9,'0'); +INSERT INTO num_exp_div VALUES (1,9,'0'); +INSERT INTO num_exp_add VALUES (2,0,'-34338492.215397047'); +INSERT INTO num_exp_sub VALUES (2,0,'-34338492.215397047'); +INSERT INTO num_exp_mul VALUES (2,0,'0'); +INSERT INTO num_exp_div VALUES (2,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (2,1,'-34338492.215397047'); +INSERT INTO num_exp_sub VALUES (2,1,'-34338492.215397047'); +INSERT INTO num_exp_mul VALUES (2,1,'0'); +INSERT INTO num_exp_div VALUES (2,1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (2,2,'-68676984.430794094'); +INSERT INTO num_exp_sub VALUES (2,2,'0'); +INSERT INTO num_exp_mul VALUES (2,2,'1179132047626883.596862135856320209'); +INSERT INTO num_exp_div VALUES (2,2,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (2,3,'-34338487.905397047'); +INSERT INTO num_exp_sub VALUES (2,3,'-34338496.525397047'); +INSERT INTO num_exp_mul VALUES (2,3,'-147998901.44836127257'); +INSERT INTO num_exp_div VALUES (2,3,'-7967167.56737750510440835266'); +INSERT INTO num_exp_add VALUES (2,4,'-26539030.803497047'); +INSERT INTO num_exp_sub VALUES (2,4,'-42137953.627297047'); +INSERT INTO num_exp_mul VALUES (2,4,'-267821744976817.8111137106593'); +INSERT INTO num_exp_div VALUES (2,4,'-4.40267480046830116685'); +INSERT INTO num_exp_add VALUES (2,5,'-34322095.176906047'); +INSERT INTO num_exp_sub VALUES (2,5,'-34354889.253888047'); +INSERT INTO num_exp_mul VALUES (2,5,'-563049578578.769242506736077'); +INSERT INTO num_exp_div VALUES (2,5,'-2094.18866914563535496429'); +INSERT INTO num_exp_add VALUES (2,6,'-34244590.637766787'); +INSERT INTO num_exp_sub VALUES (2,6,'-34432393.793027307'); +INSERT INTO num_exp_mul VALUES (2,6,'-3224438592470.18449811926184222'); +INSERT INTO num_exp_div VALUES (2,6,'-365.68599891479766440940'); +INSERT INTO num_exp_add VALUES (2,7,'-117366977.215397047'); +INSERT INTO num_exp_sub VALUES (2,7,'48689992.784602953'); +INSERT INTO num_exp_mul VALUES (2,7,'2851072985828710.485883795'); +INSERT INTO num_exp_div VALUES (2,7,'.41357483778485235518'); +INSERT INTO num_exp_add VALUES (2,8,'-34263611.215397047'); +INSERT INTO num_exp_sub VALUES (2,8,'-34413373.215397047'); +INSERT INTO num_exp_mul VALUES (2,8,'-2571300635581.146276407'); +INSERT INTO num_exp_div VALUES (2,8,'-458.57416721727870888476'); +INSERT INTO num_exp_add VALUES (2,9,'-59265296.260444467'); +INSERT INTO num_exp_sub VALUES (2,9,'-9411688.170349627'); +INSERT INTO num_exp_mul VALUES (2,9,'855948866655588.453741509242968740'); +INSERT INTO num_exp_div VALUES (2,9,'1.37757299946438931811'); +INSERT INTO num_exp_add VALUES (3,0,'4.31'); +INSERT INTO num_exp_sub VALUES (3,0,'4.31'); +INSERT INTO num_exp_mul VALUES (3,0,'0'); +INSERT INTO num_exp_div VALUES (3,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (3,1,'4.31'); +INSERT INTO num_exp_sub VALUES (3,1,'4.31'); +INSERT INTO num_exp_mul VALUES (3,1,'0'); +INSERT INTO num_exp_div VALUES (3,1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (3,2,'-34338487.905397047'); +INSERT INTO num_exp_sub VALUES (3,2,'34338496.525397047'); +INSERT INTO num_exp_mul VALUES (3,2,'-147998901.44836127257'); +INSERT INTO num_exp_div VALUES (3,2,'-.00000012551512084352'); +INSERT INTO num_exp_add VALUES (3,3,'8.62'); +INSERT INTO num_exp_sub VALUES (3,3,'0'); +INSERT INTO num_exp_mul VALUES (3,3,'18.5761'); +INSERT INTO num_exp_div VALUES (3,3,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (3,4,'7799465.7219'); +INSERT INTO num_exp_sub VALUES (3,4,'-7799457.1019'); +INSERT INTO num_exp_mul VALUES (3,4,'33615678.685289'); +INSERT INTO num_exp_div VALUES (3,4,'.00000055260225961552'); +INSERT INTO num_exp_add VALUES (3,5,'16401.348491'); +INSERT INTO num_exp_sub VALUES (3,5,'-16392.728491'); +INSERT INTO num_exp_mul VALUES (3,5,'70671.23589621'); +INSERT INTO num_exp_div VALUES (3,5,'.00026285234387695504'); +INSERT INTO num_exp_add VALUES (3,6,'93905.88763026'); +INSERT INTO num_exp_sub VALUES (3,6,'-93897.26763026'); +INSERT INTO num_exp_mul VALUES (3,6,'404715.7995864206'); +INSERT INTO num_exp_div VALUES (3,6,'.00004589912234457595'); +INSERT INTO num_exp_add VALUES (3,7,'-83028480.69'); +INSERT INTO num_exp_sub VALUES (3,7,'83028489.31'); +INSERT INTO num_exp_mul VALUES (3,7,'-357852770.35'); +INSERT INTO num_exp_div VALUES (3,7,'-.00000005190989574240'); +INSERT INTO num_exp_add VALUES (3,8,'74885.31'); +INSERT INTO num_exp_sub VALUES (3,8,'-74876.69'); +INSERT INTO num_exp_mul VALUES (3,8,'322737.11'); +INSERT INTO num_exp_div VALUES (3,8,'.00005755799201399553'); +INSERT INTO num_exp_add VALUES (3,9,'-24926799.735047420'); +INSERT INTO num_exp_sub VALUES (3,9,'24926808.355047420'); +INSERT INTO num_exp_mul VALUES (3,9,'-107434525.43415438020'); +INSERT INTO num_exp_div VALUES (3,9,'-.00000017290624149854'); +INSERT INTO num_exp_add VALUES (4,0,'7799461.4119'); +INSERT INTO num_exp_sub VALUES (4,0,'7799461.4119'); +INSERT INTO num_exp_mul VALUES (4,0,'0'); +INSERT INTO num_exp_div VALUES (4,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (4,1,'7799461.4119'); +INSERT INTO num_exp_sub VALUES (4,1,'7799461.4119'); +INSERT INTO num_exp_mul VALUES (4,1,'0'); +INSERT INTO num_exp_div VALUES (4,1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (4,2,'-26539030.803497047'); +INSERT INTO num_exp_sub VALUES (4,2,'42137953.627297047'); +INSERT INTO num_exp_mul VALUES (4,2,'-267821744976817.8111137106593'); +INSERT INTO num_exp_div VALUES (4,2,'-.22713465002993920385'); +INSERT INTO num_exp_add VALUES (4,3,'7799465.7219'); +INSERT INTO num_exp_sub VALUES (4,3,'7799457.1019'); +INSERT INTO num_exp_mul VALUES (4,3,'33615678.685289'); +INSERT INTO num_exp_div VALUES (4,3,'1809619.81714617169373549883'); +INSERT INTO num_exp_add VALUES (4,4,'15598922.8238'); +INSERT INTO num_exp_sub VALUES (4,4,'0'); +INSERT INTO num_exp_mul VALUES (4,4,'60831598315717.14146161'); +INSERT INTO num_exp_div VALUES (4,4,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (4,5,'7815858.450391'); +INSERT INTO num_exp_sub VALUES (4,5,'7783064.373409'); +INSERT INTO num_exp_mul VALUES (4,5,'127888068979.9935054429'); +INSERT INTO num_exp_div VALUES (4,5,'475.66281046305802686061'); +INSERT INTO num_exp_add VALUES (4,6,'7893362.98953026'); +INSERT INTO num_exp_sub VALUES (4,6,'7705559.83426974'); +INSERT INTO num_exp_mul VALUES (4,6,'732381731243.745115764094'); +INSERT INTO num_exp_div VALUES (4,6,'83.05996138436129499606'); +INSERT INTO num_exp_add VALUES (4,7,'-75229023.5881'); +INSERT INTO num_exp_sub VALUES (4,7,'90827946.4119'); +INSERT INTO num_exp_mul VALUES (4,7,'-647577464846017.9715'); +INSERT INTO num_exp_div VALUES (4,7,'-.09393717604145131637'); +INSERT INTO num_exp_add VALUES (4,8,'7874342.4119'); +INSERT INTO num_exp_sub VALUES (4,8,'7724580.4119'); +INSERT INTO num_exp_mul VALUES (4,8,'584031469984.4839'); +INSERT INTO num_exp_div VALUES (4,8,'104.15808298366741897143'); +INSERT INTO num_exp_add VALUES (4,9,'-17127342.633147420'); +INSERT INTO num_exp_sub VALUES (4,9,'32726265.456947420'); +INSERT INTO num_exp_mul VALUES (4,9,'-194415646271340.1815956522980'); +INSERT INTO num_exp_div VALUES (4,9,'-.31289456112403769409'); +INSERT INTO num_exp_add VALUES (5,0,'16397.038491'); +INSERT INTO num_exp_sub VALUES (5,0,'16397.038491'); +INSERT INTO num_exp_mul VALUES (5,0,'0'); +INSERT INTO num_exp_div VALUES (5,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (5,1,'16397.038491'); +INSERT INTO num_exp_sub VALUES (5,1,'16397.038491'); +INSERT INTO num_exp_mul VALUES (5,1,'0'); +INSERT INTO num_exp_div VALUES (5,1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (5,2,'-34322095.176906047'); +INSERT INTO num_exp_sub VALUES (5,2,'34354889.253888047'); +INSERT INTO num_exp_mul VALUES (5,2,'-563049578578.769242506736077'); +INSERT INTO num_exp_div VALUES (5,2,'-.00047751189505192446'); +INSERT INTO num_exp_add VALUES (5,3,'16401.348491'); +INSERT INTO num_exp_sub VALUES (5,3,'16392.728491'); +INSERT INTO num_exp_mul VALUES (5,3,'70671.23589621'); +INSERT INTO num_exp_div VALUES (5,3,'3804.41728329466357308584'); +INSERT INTO num_exp_add VALUES (5,4,'7815858.450391'); +INSERT INTO num_exp_sub VALUES (5,4,'-7783064.373409'); +INSERT INTO num_exp_mul VALUES (5,4,'127888068979.9935054429'); +INSERT INTO num_exp_div VALUES (5,4,'.00210232958726897192'); +INSERT INTO num_exp_add VALUES (5,5,'32794.076982'); +INSERT INTO num_exp_sub VALUES (5,5,'0'); +INSERT INTO num_exp_mul VALUES (5,5,'268862871.275335557081'); +INSERT INTO num_exp_div VALUES (5,5,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (5,6,'110298.61612126'); +INSERT INTO num_exp_sub VALUES (5,6,'-77504.53913926'); +INSERT INTO num_exp_mul VALUES (5,6,'1539707782.76899778633766'); +INSERT INTO num_exp_div VALUES (5,6,'.17461941433576102689'); +INSERT INTO num_exp_add VALUES (5,7,'-83012087.961509'); +INSERT INTO num_exp_sub VALUES (5,7,'83044882.038491'); +INSERT INTO num_exp_mul VALUES (5,7,'-1361421264394.416135'); +INSERT INTO num_exp_div VALUES (5,7,'-.00019748690453643710'); +INSERT INTO num_exp_add VALUES (5,8,'91278.038491'); +INSERT INTO num_exp_sub VALUES (5,8,'-58483.961509'); +INSERT INTO num_exp_mul VALUES (5,8,'1227826639.244571'); +INSERT INTO num_exp_div VALUES (5,8,'.21897461960978085228'); +INSERT INTO num_exp_add VALUES (5,9,'-24910407.006556420'); +INSERT INTO num_exp_sub VALUES (5,9,'24943201.083538420'); +INSERT INTO num_exp_mul VALUES (5,9,'-408725765384.257043660243220'); +INSERT INTO num_exp_div VALUES (5,9,'-.00065780749354660427'); +INSERT INTO num_exp_add VALUES (6,0,'93901.57763026'); +INSERT INTO num_exp_sub VALUES (6,0,'93901.57763026'); +INSERT INTO num_exp_mul VALUES (6,0,'0'); +INSERT INTO num_exp_div VALUES (6,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (6,1,'93901.57763026'); +INSERT INTO num_exp_sub VALUES (6,1,'93901.57763026'); +INSERT INTO num_exp_mul VALUES (6,1,'0'); +INSERT INTO num_exp_div VALUES (6,1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (6,2,'-34244590.637766787'); +INSERT INTO num_exp_sub VALUES (6,2,'34432393.793027307'); +INSERT INTO num_exp_mul VALUES (6,2,'-3224438592470.18449811926184222'); +INSERT INTO num_exp_div VALUES (6,2,'-.00273458651128995823'); +INSERT INTO num_exp_add VALUES (6,3,'93905.88763026'); +INSERT INTO num_exp_sub VALUES (6,3,'93897.26763026'); +INSERT INTO num_exp_mul VALUES (6,3,'404715.7995864206'); +INSERT INTO num_exp_div VALUES (6,3,'21786.90896293735498839907'); +INSERT INTO num_exp_add VALUES (6,4,'7893362.98953026'); +INSERT INTO num_exp_sub VALUES (6,4,'-7705559.83426974'); +INSERT INTO num_exp_mul VALUES (6,4,'732381731243.745115764094'); +INSERT INTO num_exp_div VALUES (6,4,'.01203949512295682469'); +INSERT INTO num_exp_add VALUES (6,5,'110298.61612126'); +INSERT INTO num_exp_sub VALUES (6,5,'77504.53913926'); +INSERT INTO num_exp_mul VALUES (6,5,'1539707782.76899778633766'); +INSERT INTO num_exp_div VALUES (6,5,'5.72674008674192359679'); +INSERT INTO num_exp_add VALUES (6,6,'187803.15526052'); +INSERT INTO num_exp_sub VALUES (6,6,'0'); +INSERT INTO num_exp_mul VALUES (6,6,'8817506281.4517452372676676'); +INSERT INTO num_exp_div VALUES (6,6,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (6,7,'-82934583.42236974'); +INSERT INTO num_exp_sub VALUES (6,7,'83122386.57763026'); +INSERT INTO num_exp_mul VALUES (6,7,'-7796505729750.37795610'); +INSERT INTO num_exp_div VALUES (6,7,'-.00113095617281538980'); +INSERT INTO num_exp_add VALUES (6,8,'168782.57763026'); +INSERT INTO num_exp_sub VALUES (6,8,'19020.57763026'); +INSERT INTO num_exp_mul VALUES (6,8,'7031444034.53149906'); +INSERT INTO num_exp_div VALUES (6,8,'1.25401073209839612184'); +INSERT INTO num_exp_add VALUES (6,9,'-24832902.467417160'); +INSERT INTO num_exp_sub VALUES (6,9,'25020705.622677680'); +INSERT INTO num_exp_mul VALUES (6,9,'-2340666225110.29929521292692920'); +INSERT INTO num_exp_div VALUES (6,9,'-.00376709254265256789'); +INSERT INTO num_exp_add VALUES (7,0,'-83028485'); +INSERT INTO num_exp_sub VALUES (7,0,'-83028485'); +INSERT INTO num_exp_mul VALUES (7,0,'0'); +INSERT INTO num_exp_div VALUES (7,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (7,1,'-83028485'); +INSERT INTO num_exp_sub VALUES (7,1,'-83028485'); +INSERT INTO num_exp_mul VALUES (7,1,'0'); +INSERT INTO num_exp_div VALUES (7,1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (7,2,'-117366977.215397047'); +INSERT INTO num_exp_sub VALUES (7,2,'-48689992.784602953'); +INSERT INTO num_exp_mul VALUES (7,2,'2851072985828710.485883795'); +INSERT INTO num_exp_div VALUES (7,2,'2.41794207151503385700'); +INSERT INTO num_exp_add VALUES (7,3,'-83028480.69'); +INSERT INTO num_exp_sub VALUES (7,3,'-83028489.31'); +INSERT INTO num_exp_mul VALUES (7,3,'-357852770.35'); +INSERT INTO num_exp_div VALUES (7,3,'-19264149.65197215777262180974'); +INSERT INTO num_exp_add VALUES (7,4,'-75229023.5881'); +INSERT INTO num_exp_sub VALUES (7,4,'-90827946.4119'); +INSERT INTO num_exp_mul VALUES (7,4,'-647577464846017.9715'); +INSERT INTO num_exp_div VALUES (7,4,'-10.64541262725136247686'); +INSERT INTO num_exp_add VALUES (7,5,'-83012087.961509'); +INSERT INTO num_exp_sub VALUES (7,5,'-83044882.038491'); +INSERT INTO num_exp_mul VALUES (7,5,'-1361421264394.416135'); +INSERT INTO num_exp_div VALUES (7,5,'-5063.62688881730941836574'); +INSERT INTO num_exp_add VALUES (7,6,'-82934583.42236974'); +INSERT INTO num_exp_sub VALUES (7,6,'-83122386.57763026'); +INSERT INTO num_exp_mul VALUES (7,6,'-7796505729750.37795610'); +INSERT INTO num_exp_div VALUES (7,6,'-884.20756174009028770294'); +INSERT INTO num_exp_add VALUES (7,7,'-166056970'); +INSERT INTO num_exp_sub VALUES (7,7,'0'); +INSERT INTO num_exp_mul VALUES (7,7,'6893729321395225'); +INSERT INTO num_exp_div VALUES (7,7,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (7,8,'-82953604'); +INSERT INTO num_exp_sub VALUES (7,8,'-83103366'); +INSERT INTO num_exp_mul VALUES (7,8,'-6217255985285'); +INSERT INTO num_exp_div VALUES (7,8,'-1108.80577182462841041118'); +INSERT INTO num_exp_add VALUES (7,9,'-107955289.045047420'); +INSERT INTO num_exp_sub VALUES (7,9,'-58101680.954952580'); +INSERT INTO num_exp_mul VALUES (7,9,'2069634775752159.035758700'); +INSERT INTO num_exp_div VALUES (7,9,'3.33089171198810413382'); +INSERT INTO num_exp_add VALUES (8,0,'74881'); +INSERT INTO num_exp_sub VALUES (8,0,'74881'); +INSERT INTO num_exp_mul VALUES (8,0,'0'); +INSERT INTO num_exp_div VALUES (8,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (8,1,'74881'); +INSERT INTO num_exp_sub VALUES (8,1,'74881'); +INSERT INTO num_exp_mul VALUES (8,1,'0'); +INSERT INTO num_exp_div VALUES (8,1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (8,2,'-34263611.215397047'); +INSERT INTO num_exp_sub VALUES (8,2,'34413373.215397047'); +INSERT INTO num_exp_mul VALUES (8,2,'-2571300635581.146276407'); +INSERT INTO num_exp_div VALUES (8,2,'-.00218067233500788615'); +INSERT INTO num_exp_add VALUES (8,3,'74885.31'); +INSERT INTO num_exp_sub VALUES (8,3,'74876.69'); +INSERT INTO num_exp_mul VALUES (8,3,'322737.11'); +INSERT INTO num_exp_div VALUES (8,3,'17373.78190255220417633410'); +INSERT INTO num_exp_add VALUES (8,4,'7874342.4119'); +INSERT INTO num_exp_sub VALUES (8,4,'-7724580.4119'); +INSERT INTO num_exp_mul VALUES (8,4,'584031469984.4839'); +INSERT INTO num_exp_div VALUES (8,4,'.00960079113741758956'); +INSERT INTO num_exp_add VALUES (8,5,'91278.038491'); +INSERT INTO num_exp_sub VALUES (8,5,'58483.961509'); +INSERT INTO num_exp_mul VALUES (8,5,'1227826639.244571'); +INSERT INTO num_exp_div VALUES (8,5,'4.56673929509287019456'); +INSERT INTO num_exp_add VALUES (8,6,'168782.57763026'); +INSERT INTO num_exp_sub VALUES (8,6,'-19020.57763026'); +INSERT INTO num_exp_mul VALUES (8,6,'7031444034.53149906'); +INSERT INTO num_exp_div VALUES (8,6,'.79744134113322314424'); +INSERT INTO num_exp_add VALUES (8,7,'-82953604'); +INSERT INTO num_exp_sub VALUES (8,7,'83103366'); +INSERT INTO num_exp_mul VALUES (8,7,'-6217255985285'); +INSERT INTO num_exp_div VALUES (8,7,'-.00090187120721280172'); +INSERT INTO num_exp_add VALUES (8,8,'149762'); +INSERT INTO num_exp_sub VALUES (8,8,'0'); +INSERT INTO num_exp_mul VALUES (8,8,'5607164161'); +INSERT INTO num_exp_div VALUES (8,8,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (8,9,'-24851923.045047420'); +INSERT INTO num_exp_sub VALUES (8,9,'25001685.045047420'); +INSERT INTO num_exp_mul VALUES (8,9,'-1866544013697.195857020'); +INSERT INTO num_exp_div VALUES (8,9,'-.00300403532938582735'); +INSERT INTO num_exp_add VALUES (9,0,'-24926804.045047420'); +INSERT INTO num_exp_sub VALUES (9,0,'-24926804.045047420'); +INSERT INTO num_exp_mul VALUES (9,0,'0'); +INSERT INTO num_exp_div VALUES (9,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (9,1,'-24926804.045047420'); +INSERT INTO num_exp_sub VALUES (9,1,'-24926804.045047420'); +INSERT INTO num_exp_mul VALUES (9,1,'0'); +INSERT INTO num_exp_div VALUES (9,1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (9,2,'-59265296.260444467'); +INSERT INTO num_exp_sub VALUES (9,2,'9411688.170349627'); +INSERT INTO num_exp_mul VALUES (9,2,'855948866655588.453741509242968740'); +INSERT INTO num_exp_div VALUES (9,2,'.72591434384152961526'); +INSERT INTO num_exp_add VALUES (9,3,'-24926799.735047420'); +INSERT INTO num_exp_sub VALUES (9,3,'-24926808.355047420'); +INSERT INTO num_exp_mul VALUES (9,3,'-107434525.43415438020'); +INSERT INTO num_exp_div VALUES (9,3,'-5783481.21694835730858468677'); +INSERT INTO num_exp_add VALUES (9,4,'-17127342.633147420'); +INSERT INTO num_exp_sub VALUES (9,4,'-32726265.456947420'); +INSERT INTO num_exp_mul VALUES (9,4,'-194415646271340.1815956522980'); +INSERT INTO num_exp_div VALUES (9,4,'-3.19596478892958416484'); +INSERT INTO num_exp_add VALUES (9,5,'-24910407.006556420'); +INSERT INTO num_exp_sub VALUES (9,5,'-24943201.083538420'); +INSERT INTO num_exp_mul VALUES (9,5,'-408725765384.257043660243220'); +INSERT INTO num_exp_div VALUES (9,5,'-1520.20159364322004505807'); +INSERT INTO num_exp_add VALUES (9,6,'-24832902.467417160'); +INSERT INTO num_exp_sub VALUES (9,6,'-25020705.622677680'); +INSERT INTO num_exp_mul VALUES (9,6,'-2340666225110.29929521292692920'); +INSERT INTO num_exp_div VALUES (9,6,'-265.45671195426965751280'); +INSERT INTO num_exp_add VALUES (9,7,'-107955289.045047420'); +INSERT INTO num_exp_sub VALUES (9,7,'58101680.954952580'); +INSERT INTO num_exp_mul VALUES (9,7,'2069634775752159.035758700'); +INSERT INTO num_exp_div VALUES (9,7,'.30021990699995814689'); +INSERT INTO num_exp_add VALUES (9,8,'-24851923.045047420'); +INSERT INTO num_exp_sub VALUES (9,8,'-25001685.045047420'); +INSERT INTO num_exp_mul VALUES (9,8,'-1866544013697.195857020'); +INSERT INTO num_exp_div VALUES (9,8,'-332.88556569820675471748'); +INSERT INTO num_exp_add VALUES (9,9,'-49853608.090094840'); +INSERT INTO num_exp_sub VALUES (9,9,'0'); +INSERT INTO num_exp_mul VALUES (9,9,'621345559900192.420120630048656400'); +INSERT INTO num_exp_div VALUES (9,9,'1.00000000000000000000'); +-- COMMIT TRANSACTION; -- YB +BEGIN TRANSACTION; +INSERT INTO num_exp_sqrt VALUES (0,'0'); +INSERT INTO num_exp_sqrt VALUES (1,'0'); +INSERT INTO num_exp_sqrt VALUES (2,'5859.90547836712524903505'); +INSERT INTO num_exp_sqrt VALUES (3,'2.07605394920266944396'); +INSERT INTO num_exp_sqrt VALUES (4,'2792.75158435189147418923'); +INSERT INTO num_exp_sqrt VALUES (5,'128.05092147657509145473'); +INSERT INTO num_exp_sqrt VALUES (6,'306.43364311096782703406'); +INSERT INTO num_exp_sqrt VALUES (7,'9111.99676251039939975230'); +INSERT INTO num_exp_sqrt VALUES (8,'273.64392922189960397542'); +INSERT INTO num_exp_sqrt VALUES (9,'4992.67503899937593364766'); +COMMIT TRANSACTION; +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_ln VALUES (0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_ln VALUES (1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_ln VALUES (2,'17.35177750493897715514'); +INSERT INTO num_exp_ln VALUES (3,'1.46093790411565641971'); +INSERT INTO num_exp_ln VALUES (4,'15.86956523951936572464'); +INSERT INTO num_exp_ln VALUES (5,'9.70485601768871834038'); +INSERT INTO num_exp_ln VALUES (6,'11.45000246622944403127'); +INSERT INTO num_exp_ln VALUES (7,'18.23469429965478772991'); +INSERT INTO num_exp_ln VALUES (8,'11.22365546576315513668'); +INSERT INTO num_exp_ln VALUES (9,'17.03145425013166006962'); +-- COMMIT TRANSACTION; -- YB +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_log10 VALUES (0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_log10 VALUES (1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_log10 VALUES (2,'7.53578122160797276459'); +INSERT INTO num_exp_log10 VALUES (3,'.63447727016073160075'); +INSERT INTO num_exp_log10 VALUES (4,'6.89206461372691743345'); +INSERT INTO num_exp_log10 VALUES (5,'4.21476541614777768626'); +INSERT INTO num_exp_log10 VALUES (6,'4.97267288886207207671'); +INSERT INTO num_exp_log10 VALUES (7,'7.91922711353275546914'); +INSERT INTO num_exp_log10 VALUES (8,'4.87437163556421004138'); +INSERT INTO num_exp_log10 VALUES (9,'7.39666659961986567059'); +-- COMMIT TRANSACTION; -- YB +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_power_10_ln VALUES (0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_power_10_ln VALUES (1,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_power_10_ln VALUES (2,'224790267919917955.13261618583642653184'); +INSERT INTO num_exp_power_10_ln VALUES (3,'28.90266599445155957393'); +INSERT INTO num_exp_power_10_ln VALUES (4,'7405685069594999.07733999469386277636'); +INSERT INTO num_exp_power_10_ln VALUES (5,'5068226527.32127265408584640098'); +INSERT INTO num_exp_power_10_ln VALUES (6,'281839893606.99372343357047819067'); +INSERT INTO num_exp_power_10_ln VALUES (7,'1716699575118597095.42330819910640247627'); +INSERT INTO num_exp_power_10_ln VALUES (8,'167361463828.07491320069016125952'); +INSERT INTO num_exp_power_10_ln VALUES (9,'107511333880052007.04141124673540337457'); +-- COMMIT TRANSACTION; -- YB +BEGIN TRANSACTION; +INSERT INTO num_data VALUES (0, '0'); +INSERT INTO num_data VALUES (1, '0'); +INSERT INTO num_data VALUES (2, '-34338492.215397047'); +INSERT INTO num_data VALUES (3, '4.31'); +INSERT INTO num_data VALUES (4, '7799461.4119'); +INSERT INTO num_data VALUES (5, '16397.038491'); +INSERT INTO num_data VALUES (6, '93901.57763026'); +INSERT INTO num_data VALUES (7, '-83028485'); +INSERT INTO num_data VALUES (8, '74881'); +INSERT INTO num_data VALUES (9, '-24926804.045047420'); +COMMIT TRANSACTION; +-- ****************************** +-- * Create indices for faster checks +-- ****************************** +CREATE UNIQUE INDEX num_exp_add_idx ON num_exp_add (id1, id2); +CREATE UNIQUE INDEX num_exp_sub_idx ON num_exp_sub (id1, id2); +CREATE UNIQUE INDEX num_exp_div_idx ON num_exp_div (id1, id2); +CREATE UNIQUE INDEX num_exp_mul_idx ON num_exp_mul (id1, id2); +CREATE UNIQUE INDEX num_exp_sqrt_idx ON num_exp_sqrt (id); +CREATE UNIQUE INDEX num_exp_ln_idx ON num_exp_ln (id); +CREATE UNIQUE INDEX num_exp_log10_idx ON num_exp_log10 (id); +CREATE UNIQUE INDEX num_exp_power_10_ln_idx ON num_exp_power_10_ln (id); +VACUUM ANALYZE num_exp_add; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_sub; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_div; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_mul; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_sqrt; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_ln; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_log10; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_power_10_ln; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +-- ****************************** +-- * Now check the behaviour of the NUMERIC type +-- ****************************** +-- ****************************** +-- * Addition check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val + t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_add t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val + t2.val, 10) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 10) as expected + FROM num_result t1, num_exp_add t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 10); + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +-- ****************************** +-- * Subtraction check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val - t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_sub t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val - t2.val, 40) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 40) + FROM num_result t1, num_exp_sub t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 40); + id1 | id2 | result | round +-----+-----+--------+------- +(0 rows) + +-- ****************************** +-- * Multiply check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val * t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_mul t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val * t2.val, 30) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 30) as expected + FROM num_result t1, num_exp_mul t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 30); + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +-- ****************************** +-- * Division check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val / t2.val + FROM num_data t1, num_data t2 + WHERE t2.val != '0.0'; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_div t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val / t2.val, 80) + FROM num_data t1, num_data t2 + WHERE t2.val != '0.0'; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 80) as expected + FROM num_result t1, num_exp_div t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 80); + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +-- ****************************** +-- * Square root check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, SQRT(ABS(val)) + FROM num_data; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_sqrt t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + id1 | result | expected +-----+--------+---------- +(0 rows) + +-- ****************************** +-- * Natural logarithm check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, LN(ABS(val)) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_ln t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + id1 | result | expected +-----+--------+---------- +(0 rows) + +-- ****************************** +-- * Logarithm base 10 check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, LOG(numeric '10', ABS(val)) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_log10 t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + id1 | result | expected +-----+--------+---------- +(0 rows) + +-- ****************************** +-- * POWER(10, LN(value)) check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, POWER(numeric '10', LN(ABS(round(val,200)))) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_power_10_ln t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + id1 | result | expected +-----+--------+---------- +(0 rows) + +-- ****************************** +-- * Check behavior with Inf and NaN inputs. It's easiest to handle these +-- * separately from the num_data framework used above, because some input +-- * combinations will throw errors. +-- ****************************** +WITH v(x) AS + (VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan')) +SELECT x1, x2, + x1 + x2 AS sum, + x1 - x2 AS diff, + x1 * x2 AS prod +FROM v AS v1(x1), v AS v2(x2); + x1 | x2 | sum | diff | prod +-----------+-----------+-----------+-----------+----------- + 0 | 0 | 0 | 0 | 0 + 0 | 1 | 1 | -1 | 0 + 0 | -1 | -1 | 1 | 0 + 0 | 4.2 | 4.2 | -4.2 | 0.0 + 0 | Infinity | Infinity | -Infinity | NaN + 0 | -Infinity | -Infinity | Infinity | NaN + 0 | NaN | NaN | NaN | NaN + 1 | 0 | 1 | 1 | 0 + 1 | 1 | 2 | 0 | 1 + 1 | -1 | 0 | 2 | -1 + 1 | 4.2 | 5.2 | -3.2 | 4.2 + 1 | Infinity | Infinity | -Infinity | Infinity + 1 | -Infinity | -Infinity | Infinity | -Infinity + 1 | NaN | NaN | NaN | NaN + -1 | 0 | -1 | -1 | 0 + -1 | 1 | 0 | -2 | -1 + -1 | -1 | -2 | 0 | 1 + -1 | 4.2 | 3.2 | -5.2 | -4.2 + -1 | Infinity | Infinity | -Infinity | -Infinity + -1 | -Infinity | -Infinity | Infinity | Infinity + -1 | NaN | NaN | NaN | NaN + 4.2 | 0 | 4.2 | 4.2 | 0.0 + 4.2 | 1 | 5.2 | 3.2 | 4.2 + 4.2 | -1 | 3.2 | 5.2 | -4.2 + 4.2 | 4.2 | 8.4 | 0.0 | 17.64 + 4.2 | Infinity | Infinity | -Infinity | Infinity + 4.2 | -Infinity | -Infinity | Infinity | -Infinity + 4.2 | NaN | NaN | NaN | NaN + Infinity | 0 | Infinity | Infinity | NaN + Infinity | 1 | Infinity | Infinity | Infinity + Infinity | -1 | Infinity | Infinity | -Infinity + Infinity | 4.2 | Infinity | Infinity | Infinity + Infinity | Infinity | Infinity | NaN | Infinity + Infinity | -Infinity | NaN | Infinity | -Infinity + Infinity | NaN | NaN | NaN | NaN + -Infinity | 0 | -Infinity | -Infinity | NaN + -Infinity | 1 | -Infinity | -Infinity | -Infinity + -Infinity | -1 | -Infinity | -Infinity | Infinity + -Infinity | 4.2 | -Infinity | -Infinity | -Infinity + -Infinity | Infinity | NaN | -Infinity | -Infinity + -Infinity | -Infinity | -Infinity | NaN | Infinity + -Infinity | NaN | NaN | NaN | NaN + NaN | 0 | NaN | NaN | NaN + NaN | 1 | NaN | NaN | NaN + NaN | -1 | NaN | NaN | NaN + NaN | 4.2 | NaN | NaN | NaN + NaN | Infinity | NaN | NaN | NaN + NaN | -Infinity | NaN | NaN | NaN + NaN | NaN | NaN | NaN | NaN +(49 rows) + +WITH v(x) AS + (VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan')) +SELECT x1, x2, + x1 / x2 AS quot, + x1 % x2 AS mod, + div(x1, x2) AS div +FROM v AS v1(x1), v AS v2(x2) WHERE x2 != 0; + x1 | x2 | quot | mod | div +-----------+-----------+-------------------------+------+----------- + 0 | 1 | 0.00000000000000000000 | 0 | 0 + 1 | 1 | 1.00000000000000000000 | 0 | 1 + -1 | 1 | -1.00000000000000000000 | 0 | -1 + 4.2 | 1 | 4.2000000000000000 | 0.2 | 4 + Infinity | 1 | Infinity | NaN | Infinity + -Infinity | 1 | -Infinity | NaN | -Infinity + NaN | 1 | NaN | NaN | NaN + 0 | -1 | 0.00000000000000000000 | 0 | 0 + 1 | -1 | -1.00000000000000000000 | 0 | -1 + -1 | -1 | 1.00000000000000000000 | 0 | 1 + 4.2 | -1 | -4.2000000000000000 | 0.2 | -4 + Infinity | -1 | -Infinity | NaN | -Infinity + -Infinity | -1 | Infinity | NaN | Infinity + NaN | -1 | NaN | NaN | NaN + 0 | 4.2 | 0.00000000000000000000 | 0.0 | 0 + 1 | 4.2 | 0.23809523809523809524 | 1.0 | 0 + -1 | 4.2 | -0.23809523809523809524 | -1.0 | 0 + 4.2 | 4.2 | 1.00000000000000000000 | 0.0 | 1 + Infinity | 4.2 | Infinity | NaN | Infinity + -Infinity | 4.2 | -Infinity | NaN | -Infinity + NaN | 4.2 | NaN | NaN | NaN + 0 | Infinity | 0 | 0 | 0 + 1 | Infinity | 0 | 1 | 0 + -1 | Infinity | 0 | -1 | 0 + 4.2 | Infinity | 0 | 4.2 | 0 + Infinity | Infinity | NaN | NaN | NaN + -Infinity | Infinity | NaN | NaN | NaN + NaN | Infinity | NaN | NaN | NaN + 0 | -Infinity | 0 | 0 | 0 + 1 | -Infinity | 0 | 1 | 0 + -1 | -Infinity | 0 | -1 | 0 + 4.2 | -Infinity | 0 | 4.2 | 0 + Infinity | -Infinity | NaN | NaN | NaN + -Infinity | -Infinity | NaN | NaN | NaN + NaN | -Infinity | NaN | NaN | NaN + 0 | NaN | NaN | NaN | NaN + 1 | NaN | NaN | NaN | NaN + -1 | NaN | NaN | NaN | NaN + 4.2 | NaN | NaN | NaN | NaN + Infinity | NaN | NaN | NaN | NaN + -Infinity | NaN | NaN | NaN | NaN + NaN | NaN | NaN | NaN | NaN +(42 rows) + +SELECT 'inf'::numeric / '0'; +ERROR: division by zero +SELECT '-inf'::numeric / '0'; +ERROR: division by zero +SELECT 'nan'::numeric / '0'; + ?column? +---------- + NaN +(1 row) + +SELECT '0'::numeric / '0'; +ERROR: division by zero +SELECT 'inf'::numeric % '0'; +ERROR: division by zero +SELECT '-inf'::numeric % '0'; +ERROR: division by zero +SELECT 'nan'::numeric % '0'; + ?column? +---------- + NaN +(1 row) + +SELECT '0'::numeric % '0'; +ERROR: division by zero +SELECT div('inf'::numeric, '0'); +ERROR: division by zero +SELECT div('-inf'::numeric, '0'); +ERROR: division by zero +SELECT div('nan'::numeric, '0'); + div +----- + NaN +(1 row) + +SELECT div('0'::numeric, '0'); +ERROR: division by zero +WITH v(x) AS + (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan')) +SELECT x, -x as minusx, abs(x), floor(x), ceil(x), sign(x), numeric_inc(x) as inc +FROM v; + x | minusx | abs | floor | ceil | sign | inc +-----------+-----------+----------+-----------+-----------+------+----------- + 0 | 0 | 0 | 0 | 0 | 0 | 1 + 1 | -1 | 1 | 1 | 1 | 1 | 2 + -1 | 1 | 1 | -1 | -1 | -1 | 0 + 4.2 | -4.2 | 4.2 | 4 | 5 | 1 | 5.2 + -7.777 | 7.777 | 7.777 | -8 | -7 | -1 | -6.777 + Infinity | -Infinity | Infinity | Infinity | Infinity | 1 | Infinity + -Infinity | Infinity | Infinity | -Infinity | -Infinity | -1 | -Infinity + NaN | NaN | NaN | NaN | NaN | NaN | NaN +(8 rows) + +WITH v(x) AS + (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan')) +SELECT x, round(x), round(x,1) as round1, trunc(x), trunc(x,1) as trunc1 +FROM v; + x | round | round1 | trunc | trunc1 +-----------+-----------+-----------+-----------+----------- + 0 | 0 | 0.0 | 0 | 0.0 + 1 | 1 | 1.0 | 1 | 1.0 + -1 | -1 | -1.0 | -1 | -1.0 + 4.2 | 4 | 4.2 | 4 | 4.2 + -7.777 | -8 | -7.8 | -7 | -7.7 + Infinity | Infinity | Infinity | Infinity | Infinity + -Infinity | -Infinity | -Infinity | -Infinity | -Infinity + NaN | NaN | NaN | NaN | NaN +(8 rows) + +-- the large values fall into the numeric abbreviation code's maximal classes +WITH v(x) AS + (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('1e340'),('-1e340'), + ('inf'),('-inf'),('nan'), + ('inf'),('-inf'),('nan')) +SELECT substring(x::text, 1, 32) +FROM v ORDER BY x; + substring +---------------------------------- + -Infinity + -Infinity + -1000000000000000000000000000000 + -7.777 + -1 + 0 + 1 + 4.2 + 10000000000000000000000000000000 + Infinity + Infinity + NaN + NaN +(13 rows) + +WITH v(x) AS + (VALUES('0'::numeric),('1'),('4.2'),('inf'),('nan')) +SELECT x, sqrt(x) +FROM v; + x | sqrt +----------+------------------- + 0 | 0.000000000000000 + 1 | 1.000000000000000 + 4.2 | 2.049390153191920 + Infinity | Infinity + NaN | NaN +(5 rows) + +SELECT sqrt('-1'::numeric); +ERROR: cannot take square root of a negative number +SELECT sqrt('-inf'::numeric); +ERROR: cannot take square root of a negative number +WITH v(x) AS + (VALUES('1'::numeric),('4.2'),('inf'),('nan')) +SELECT x, + log(x), + log10(x), + ln(x) +FROM v; + x | log | log10 | ln +----------+--------------------+--------------------+-------------------- + 1 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 4.2 | 0.6232492903979005 | 0.6232492903979005 | 1.4350845252893226 + Infinity | Infinity | Infinity | Infinity + NaN | NaN | NaN | NaN +(4 rows) + +SELECT ln('0'::numeric); +ERROR: cannot take logarithm of zero +SELECT ln('-1'::numeric); +ERROR: cannot take logarithm of a negative number +SELECT ln('-inf'::numeric); +ERROR: cannot take logarithm of a negative number +WITH v(x) AS + (VALUES('2'::numeric),('4.2'),('inf'),('nan')) +SELECT x1, x2, + log(x1, x2) +FROM v AS v1(x1), v AS v2(x2); + x1 | x2 | log +----------+----------+-------------------- + 2 | 2 | 1.0000000000000000 + 2 | 4.2 | 2.0703893278913979 + 2 | Infinity | Infinity + 2 | NaN | NaN + 4.2 | 2 | 0.4830009440873890 + 4.2 | 4.2 | 1.0000000000000000 + 4.2 | Infinity | Infinity + 4.2 | NaN | NaN + Infinity | 2 | 0 + Infinity | 4.2 | 0 + Infinity | Infinity | NaN + Infinity | NaN | NaN + NaN | 2 | NaN + NaN | 4.2 | NaN + NaN | Infinity | NaN + NaN | NaN | NaN +(16 rows) + +SELECT log('0'::numeric, '10'); +ERROR: cannot take logarithm of zero +SELECT log('10'::numeric, '0'); +ERROR: cannot take logarithm of zero +SELECT log('-inf'::numeric, '10'); +ERROR: cannot take logarithm of a negative number +SELECT log('10'::numeric, '-inf'); +ERROR: cannot take logarithm of a negative number +SELECT log('inf'::numeric, '0'); +ERROR: cannot take logarithm of zero +SELECT log('inf'::numeric, '-inf'); +ERROR: cannot take logarithm of a negative number +SELECT log('-inf'::numeric, 'inf'); +ERROR: cannot take logarithm of a negative number +WITH v(x) AS + (VALUES('0'::numeric),('1'),('2'),('4.2'),('inf'),('nan')) +SELECT x1, x2, + power(x1, x2) +FROM v AS v1(x1), v AS v2(x2) WHERE x1 != 0 OR x2 >= 0; + x1 | x2 | power +----------+----------+--------------------- + 0 | 0 | 1.0000000000000000 + 0 | 1 | 0.0000000000000000 + 0 | 2 | 0.0000000000000000 + 0 | 4.2 | 0.0000000000000000 + 0 | Infinity | 0 + 0 | NaN | NaN + 1 | 0 | 1.0000000000000000 + 1 | 1 | 1.0000000000000000 + 1 | 2 | 1.0000000000000000 + 1 | 4.2 | 1.0000000000000000 + 1 | Infinity | 1 + 1 | NaN | 1 + 2 | 0 | 1.0000000000000000 + 2 | 1 | 2.0000000000000000 + 2 | 2 | 4.0000000000000000 + 2 | 4.2 | 18.379173679952560 + 2 | Infinity | Infinity + 2 | NaN | NaN + 4.2 | 0 | 1.0000000000000000 + 4.2 | 1 | 4.2000000000000000 + 4.2 | 2 | 17.6400000000000000 + 4.2 | 4.2 | 414.61691860129675 + 4.2 | Infinity | Infinity + 4.2 | NaN | NaN + Infinity | 0 | 1 + Infinity | 1 | Infinity + Infinity | 2 | Infinity + Infinity | 4.2 | Infinity + Infinity | Infinity | Infinity + Infinity | NaN | NaN + NaN | 0 | 1 + NaN | 1 | NaN + NaN | 2 | NaN + NaN | 4.2 | NaN + NaN | Infinity | NaN + NaN | NaN | NaN +(36 rows) + +SELECT power('0'::numeric, '-1'); +ERROR: zero raised to a negative power is undefined +SELECT power('0'::numeric, '-inf'); +ERROR: zero raised to a negative power is undefined +SELECT power('-1'::numeric, 'inf'); + power +------- + 1 +(1 row) + +SELECT power('-2'::numeric, '3'); + power +--------------------- + -8.0000000000000000 +(1 row) + +SELECT power('-2'::numeric, '3.3'); +ERROR: a negative number raised to a non-integer power yields a complex result +SELECT power('-2'::numeric, '-1'); + power +--------------------- + -0.5000000000000000 +(1 row) + +SELECT power('-2'::numeric, '-1.5'); +ERROR: a negative number raised to a non-integer power yields a complex result +SELECT power('-2'::numeric, 'inf'); + power +---------- + Infinity +(1 row) + +SELECT power('-2'::numeric, '-inf'); + power +------- + 0 +(1 row) + +SELECT power('inf'::numeric, '-2'); + power +------- + 0 +(1 row) + +SELECT power('inf'::numeric, '-inf'); + power +------- + 0 +(1 row) + +SELECT power('-inf'::numeric, '2'); + power +---------- + Infinity +(1 row) + +SELECT power('-inf'::numeric, '3'); + power +----------- + -Infinity +(1 row) + +SELECT power('-inf'::numeric, '4.5'); +ERROR: a negative number raised to a non-integer power yields a complex result +SELECT power('-inf'::numeric, '-2'); + power +------- + 0 +(1 row) + +SELECT power('-inf'::numeric, '-3'); + power +------- + 0 +(1 row) + +SELECT power('-inf'::numeric, '0'); + power +------- + 1 +(1 row) + +SELECT power('-inf'::numeric, 'inf'); + power +---------- + Infinity +(1 row) + +SELECT power('-inf'::numeric, '-inf'); + power +------- + 0 +(1 row) + +-- ****************************** +-- * miscellaneous checks for things that have been broken in the past... +-- ****************************** +-- numeric AVG used to fail on some platforms +SELECT AVG(val) FROM num_data; + avg +------------------------ + -13430913.592242320700 +(1 row) + +SELECT MAX(val) FROM num_data; + max +-------------------- + 7799461.4119000000 +(1 row) + +SELECT MIN(val) FROM num_data; + min +---------------------- + -83028485.0000000000 +(1 row) + +SELECT STDDEV(val) FROM num_data; + stddev +------------------------------- + 27791203.28758835329805617386 +(1 row) + +SELECT VARIANCE(val) FROM num_data; + variance +-------------------------------------- + 772350980172061.69659105821915863601 +(1 row) + +-- Check for appropriate rounding and overflow +CREATE TABLE fract_only (id int, val numeric(4,4)); +INSERT INTO fract_only VALUES (1, '0.0'); +INSERT INTO fract_only VALUES (2, '0.1'); +INSERT INTO fract_only VALUES (3, '1.0'); -- should fail +ERROR: numeric field overflow +DETAIL: A field with precision 4, scale 4 must round to an absolute value less than 1. +INSERT INTO fract_only VALUES (4, '-0.9999'); +INSERT INTO fract_only VALUES (5, '0.99994'); +INSERT INTO fract_only VALUES (6, '0.99995'); -- should fail +ERROR: numeric field overflow +DETAIL: A field with precision 4, scale 4 must round to an absolute value less than 1. +INSERT INTO fract_only VALUES (7, '0.00001'); +INSERT INTO fract_only VALUES (8, '0.00017'); +INSERT INTO fract_only VALUES (9, 'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO fract_only VALUES (10, 'Inf'); -- should fail +ERROR: numeric field overflow +DETAIL: A field with precision 4, scale 4 cannot hold an infinite value. +INSERT INTO fract_only VALUES (11, '-Inf'); -- should fail +ERROR: numeric field overflow +DETAIL: A field with precision 4, scale 4 cannot hold an infinite value. +SELECT * FROM fract_only; + id | val +----+--------- + 1 | 0.0000 + 2 | 0.1000 + 4 | -0.9999 + 5 | 0.9999 + 7 | 0.0000 + 8 | 0.0002 +(6 rows) + +DROP TABLE fract_only; +-- Check conversion to integers +SELECT (-9223372036854775808.5)::int8; -- should fail +ERROR: bigint out of range +SELECT (-9223372036854775808.4)::int8; -- ok + int8 +---------------------- + -9223372036854775808 +(1 row) + +SELECT 9223372036854775807.4::int8; -- ok + int8 +--------------------- + 9223372036854775807 +(1 row) + +SELECT 9223372036854775807.5::int8; -- should fail +ERROR: bigint out of range +SELECT (-2147483648.5)::int4; -- should fail +ERROR: integer out of range +SELECT (-2147483648.4)::int4; -- ok + int4 +------------- + -2147483648 +(1 row) + +SELECT 2147483647.4::int4; -- ok + int4 +------------ + 2147483647 +(1 row) + +SELECT 2147483647.5::int4; -- should fail +ERROR: integer out of range +SELECT (-32768.5)::int2; -- should fail +ERROR: smallint out of range +SELECT (-32768.4)::int2; -- ok + int2 +-------- + -32768 +(1 row) + +SELECT 32767.4::int2; -- ok + int2 +------- + 32767 +(1 row) + +SELECT 32767.5::int2; -- should fail +ERROR: smallint out of range +-- Check inf/nan conversion behavior +SELECT 'NaN'::float8::numeric; + numeric +--------- + NaN +(1 row) + +SELECT 'Infinity'::float8::numeric; + numeric +---------- + Infinity +(1 row) + +SELECT '-Infinity'::float8::numeric; + numeric +----------- + -Infinity +(1 row) + +SELECT 'NaN'::numeric::float8; + float8 +-------- + NaN +(1 row) + +SELECT 'Infinity'::numeric::float8; + float8 +---------- + Infinity +(1 row) + +SELECT '-Infinity'::numeric::float8; + float8 +----------- + -Infinity +(1 row) + +SELECT 'NaN'::float4::numeric; + numeric +--------- + NaN +(1 row) + +SELECT 'Infinity'::float4::numeric; + numeric +---------- + Infinity +(1 row) + +SELECT '-Infinity'::float4::numeric; + numeric +----------- + -Infinity +(1 row) + +SELECT 'NaN'::numeric::float4; + float4 +-------- + NaN +(1 row) + +SELECT 'Infinity'::numeric::float4; + float4 +---------- + Infinity +(1 row) + +SELECT '-Infinity'::numeric::float4; + float4 +----------- + -Infinity +(1 row) + +SELECT '42'::int2::numeric; + numeric +--------- + 42 +(1 row) + +SELECT 'NaN'::numeric::int2; +ERROR: cannot convert NaN to smallint +SELECT 'Infinity'::numeric::int2; +ERROR: cannot convert infinity to smallint +SELECT '-Infinity'::numeric::int2; +ERROR: cannot convert infinity to smallint +SELECT 'NaN'::numeric::int4; +ERROR: cannot convert NaN to integer +SELECT 'Infinity'::numeric::int4; +ERROR: cannot convert infinity to integer +SELECT '-Infinity'::numeric::int4; +ERROR: cannot convert infinity to integer +SELECT 'NaN'::numeric::int8; +ERROR: cannot convert NaN to bigint +SELECT 'Infinity'::numeric::int8; +ERROR: cannot convert infinity to bigint +SELECT '-Infinity'::numeric::int8; +ERROR: cannot convert infinity to bigint +-- Simple check that ceil(), floor(), and round() work correctly +CREATE TABLE ceil_floor_round (a numeric); +INSERT INTO ceil_floor_round VALUES ('-5.5'); +INSERT INTO ceil_floor_round VALUES ('-5.499999'); +INSERT INTO ceil_floor_round VALUES ('9.5'); +INSERT INTO ceil_floor_round VALUES ('9.4999999'); +INSERT INTO ceil_floor_round VALUES ('0.0'); +INSERT INTO ceil_floor_round VALUES ('0.0000001'); +INSERT INTO ceil_floor_round VALUES ('-0.000001'); +SELECT a, ceil(a), ceiling(a), floor(a), round(a) FROM ceil_floor_round; + a | ceil | ceiling | floor | round +-----------+------+---------+-------+------- + -5.5 | -5 | -5 | -6 | -6 + -5.499999 | -5 | -5 | -6 | -5 + 9.5 | 10 | 10 | 9 | 10 + 9.4999999 | 10 | 10 | 9 | 9 + 0 | 0 | 0 | 0 | 0 + 0.0000001 | 1 | 1 | 0 | 0 + -0.000001 | 0 | 0 | -1 | 0 +(7 rows) + +DROP TABLE ceil_floor_round; +-- Check rounding, it should round ties away from zero. +SELECT i as pow, + round((-2.5 * 10 ^ i)::numeric, -i), + round((-1.5 * 10 ^ i)::numeric, -i), + round((-0.5 * 10 ^ i)::numeric, -i), + round((0.5 * 10 ^ i)::numeric, -i), + round((1.5 * 10 ^ i)::numeric, -i), + round((2.5 * 10 ^ i)::numeric, -i) +FROM generate_series(-5,5) AS t(i); + pow | round | round | round | round | round | round +-----+----------+----------+----------+---------+---------+--------- + -5 | -0.00003 | -0.00002 | -0.00001 | 0.00001 | 0.00002 | 0.00003 + -4 | -0.0003 | -0.0002 | -0.0001 | 0.0001 | 0.0002 | 0.0003 + -3 | -0.003 | -0.002 | -0.001 | 0.001 | 0.002 | 0.003 + -2 | -0.03 | -0.02 | -0.01 | 0.01 | 0.02 | 0.03 + -1 | -0.3 | -0.2 | -0.1 | 0.1 | 0.2 | 0.3 + 0 | -3 | -2 | -1 | 1 | 2 | 3 + 1 | -30 | -20 | -10 | 10 | 20 | 30 + 2 | -300 | -200 | -100 | 100 | 200 | 300 + 3 | -3000 | -2000 | -1000 | 1000 | 2000 | 3000 + 4 | -30000 | -20000 | -10000 | 10000 | 20000 | 30000 + 5 | -300000 | -200000 | -100000 | 100000 | 200000 | 300000 +(11 rows) + +-- Testing for width_bucket(). For convenience, we test both the +-- numeric and float8 versions of the function in this file. +-- errors +SELECT width_bucket(5.0, 3.0, 4.0, 0); +ERROR: count must be greater than zero +SELECT width_bucket(5.0, 3.0, 4.0, -5); +ERROR: count must be greater than zero +SELECT width_bucket(3.5, 3.0, 3.0, 888); +ERROR: lower bound cannot equal upper bound +SELECT width_bucket(5.0::float8, 3.0::float8, 4.0::float8, 0); +ERROR: count must be greater than zero +SELECT width_bucket(5.0::float8, 3.0::float8, 4.0::float8, -5); +ERROR: count must be greater than zero +SELECT width_bucket(3.5::float8, 3.0::float8, 3.0::float8, 888); +ERROR: lower bound cannot equal upper bound +SELECT width_bucket('NaN', 3.0, 4.0, 888); +ERROR: operand, lower bound, and upper bound cannot be NaN +SELECT width_bucket(0::float8, 'NaN', 4.0::float8, 888); +ERROR: operand, lower bound, and upper bound cannot be NaN +SELECT width_bucket(2.0, 3.0, '-inf', 888); +ERROR: lower and upper bounds must be finite +SELECT width_bucket(0::float8, '-inf', 4.0::float8, 888); +ERROR: lower and upper bounds must be finite +-- normal operation +CREATE TABLE width_bucket_test (operand_num numeric, operand_f8 float8); +COPY width_bucket_test (operand_num) FROM stdin; +UPDATE width_bucket_test SET operand_f8 = operand_num::float8; +SELECT + operand_num, + width_bucket(operand_num, 0, 10, 5) AS wb_1, + width_bucket(operand_f8, 0, 10, 5) AS wb_1f, + width_bucket(operand_num, 10, 0, 5) AS wb_2, + width_bucket(operand_f8, 10, 0, 5) AS wb_2f, + width_bucket(operand_num, 2, 8, 4) AS wb_3, + width_bucket(operand_f8, 2, 8, 4) AS wb_3f, + width_bucket(operand_num, 5.0, 5.5, 20) AS wb_4, + width_bucket(operand_f8, 5.0, 5.5, 20) AS wb_4f, + width_bucket(operand_num, -25, 25, 10) AS wb_5, + width_bucket(operand_f8, -25, 25, 10) AS wb_5f + FROM width_bucket_test; + operand_num | wb_1 | wb_1f | wb_2 | wb_2f | wb_3 | wb_3f | wb_4 | wb_4f | wb_5 | wb_5f +------------------+------+-------+------+-------+------+-------+------+-------+------+------- + -5.2 | 0 | 0 | 6 | 6 | 0 | 0 | 0 | 0 | 4 | 4 + -0.0000000001 | 0 | 0 | 6 | 6 | 0 | 0 | 0 | 0 | 5 | 5 + 0.000000000001 | 1 | 1 | 5 | 5 | 0 | 0 | 0 | 0 | 6 | 6 + 1 | 1 | 1 | 5 | 5 | 0 | 0 | 0 | 0 | 6 | 6 + 1.99999999999999 | 1 | 1 | 5 | 5 | 0 | 0 | 0 | 0 | 6 | 6 + 2 | 2 | 2 | 5 | 5 | 1 | 1 | 0 | 0 | 6 | 6 + 2.00000000000001 | 2 | 2 | 4 | 4 | 1 | 1 | 0 | 0 | 6 | 6 + 3 | 2 | 2 | 4 | 4 | 1 | 1 | 0 | 0 | 6 | 6 + 4 | 3 | 3 | 4 | 4 | 2 | 2 | 0 | 0 | 6 | 6 + 4.5 | 3 | 3 | 3 | 3 | 2 | 2 | 0 | 0 | 6 | 6 + 5 | 3 | 3 | 3 | 3 | 3 | 3 | 1 | 1 | 7 | 7 + 5.5 | 3 | 3 | 3 | 3 | 3 | 3 | 21 | 21 | 7 | 7 + 6 | 4 | 4 | 3 | 3 | 3 | 3 | 21 | 21 | 7 | 7 + 7 | 4 | 4 | 2 | 2 | 4 | 4 | 21 | 21 | 7 | 7 + 8 | 5 | 5 | 2 | 2 | 5 | 5 | 21 | 21 | 7 | 7 + 9 | 5 | 5 | 1 | 1 | 5 | 5 | 21 | 21 | 7 | 7 + 9.99999999999999 | 5 | 5 | 1 | 1 | 5 | 5 | 21 | 21 | 7 | 7 + 10 | 6 | 6 | 1 | 1 | 5 | 5 | 21 | 21 | 8 | 8 + 10.0000000000001 | 6 | 6 | 0 | 0 | 5 | 5 | 21 | 21 | 8 | 8 +(19 rows) + +-- Check positive and negative infinity: we require +-- finite bucket bounds, but allow an infinite operand +SELECT width_bucket(0.0::numeric, 'Infinity'::numeric, 5, 10); -- error +ERROR: lower and upper bounds must be finite +SELECT width_bucket(0.0::numeric, 5, '-Infinity'::numeric, 20); -- error +ERROR: lower and upper bounds must be finite +SELECT width_bucket('Infinity'::numeric, 1, 10, 10), + width_bucket('-Infinity'::numeric, 1, 10, 10); + width_bucket | width_bucket +--------------+-------------- + 11 | 0 +(1 row) + +SELECT width_bucket(0.0::float8, 'Infinity'::float8, 5, 10); -- error +ERROR: lower and upper bounds must be finite +SELECT width_bucket(0.0::float8, 5, '-Infinity'::float8, 20); -- error +ERROR: lower and upper bounds must be finite +SELECT width_bucket('Infinity'::float8, 1, 10, 10), + width_bucket('-Infinity'::float8, 1, 10, 10); + width_bucket | width_bucket +--------------+-------------- + 11 | 0 +(1 row) + +DROP TABLE width_bucket_test; +-- Simple test for roundoff error when results should be exact +SELECT x, width_bucket(x::float8, 10, 100, 9) as flt, + width_bucket(x::numeric, 10, 100, 9) as num +FROM generate_series(0, 110, 10) x; + x | flt | num +-----+-----+----- + 0 | 0 | 0 + 10 | 1 | 1 + 20 | 2 | 2 + 30 | 3 | 3 + 40 | 4 | 4 + 50 | 5 | 5 + 60 | 6 | 6 + 70 | 7 | 7 + 80 | 8 | 8 + 90 | 9 | 9 + 100 | 10 | 10 + 110 | 10 | 10 +(12 rows) + +SELECT x, width_bucket(x::float8, 100, 10, 9) as flt, + width_bucket(x::numeric, 100, 10, 9) as num +FROM generate_series(0, 110, 10) x; + x | flt | num +-----+-----+----- + 0 | 10 | 10 + 10 | 10 | 10 + 20 | 9 | 9 + 30 | 8 | 8 + 40 | 7 | 7 + 50 | 6 | 6 + 60 | 5 | 5 + 70 | 4 | 4 + 80 | 3 | 3 + 90 | 2 | 2 + 100 | 1 | 1 + 110 | 0 | 0 +(12 rows) + +-- +-- TO_CHAR() +-- +SELECT to_char(val, '9G999G999G999G999G999') + FROM num_data; + to_char +------------------------ + 0 + 0 + -34,338,492 + 4 + 7,799,461 + 16,397 + 93,902 + -83,028,485 + 74,881 + -24,926,804 +(10 rows) + +SELECT to_char(val, '9G999G999G999G999G999D999G999G999G999G999') + FROM num_data; + to_char +-------------------------------------------- + .000,000,000,000,000 + .000,000,000,000,000 + -34,338,492.215,397,047,000,000 + 4.310,000,000,000,000 + 7,799,461.411,900,000,000,000 + 16,397.038,491,000,000,000 + 93,901.577,630,260,000,000 + -83,028,485.000,000,000,000,000 + 74,881.000,000,000,000,000 + -24,926,804.045,047,420,000,000 +(10 rows) + +SELECT to_char(val, '9999999999999999.999999999999999PR') + FROM num_data; + to_char +------------------------------------ + .000000000000000 + .000000000000000 + <34338492.215397047000000> + 4.310000000000000 + 7799461.411900000000000 + 16397.038491000000000 + 93901.577630260000000 + <83028485.000000000000000> + 74881.000000000000000 + <24926804.045047420000000> +(10 rows) + +SELECT to_char(val, '9999999999999999.999999999999999S') + FROM num_data; + to_char +----------------------------------- + .000000000000000+ + .000000000000000+ + 34338492.215397047000000- + 4.310000000000000+ + 7799461.411900000000000+ + 16397.038491000000000+ + 93901.577630260000000+ + 83028485.000000000000000- + 74881.000000000000000+ + 24926804.045047420000000- +(10 rows) + +SELECT to_char(val, 'MI9999999999999999.999999999999999') FROM num_data; + to_char +----------------------------------- + .000000000000000 + .000000000000000 + - 34338492.215397047000000 + 4.310000000000000 + 7799461.411900000000000 + 16397.038491000000000 + 93901.577630260000000 + - 83028485.000000000000000 + 74881.000000000000000 + - 24926804.045047420000000 +(10 rows) + +SELECT to_char(val, 'FMS9999999999999999.999999999999999') FROM num_data; + to_char +--------------------- + +0. + +0. + -34338492.215397047 + +4.31 + +7799461.4119 + +16397.038491 + +93901.57763026 + -83028485. + +74881. + -24926804.04504742 +(10 rows) + +SELECT to_char(val, 'FM9999999999999999.999999999999999THPR') FROM num_data; + to_char +---------------------- + 0. + 0. + <34338492.215397047> + 4.31 + 7799461.4119 + 16397.038491 + 93901.57763026 + <83028485.> + 74881. + <24926804.04504742> +(10 rows) + +SELECT to_char(val, 'SG9999999999999999.999999999999999th') FROM num_data; + to_char +----------------------------------- + + .000000000000000 + + .000000000000000 + - 34338492.215397047000000 + + 4.310000000000000 + + 7799461.411900000000000 + + 16397.038491000000000 + + 93901.577630260000000 + - 83028485.000000000000000 + + 74881.000000000000000 + - 24926804.045047420000000 +(10 rows) + +SELECT to_char(val, '0999999999999999.999999999999999') FROM num_data; + to_char +----------------------------------- + 0000000000000000.000000000000000 + 0000000000000000.000000000000000 + -0000000034338492.215397047000000 + 0000000000000004.310000000000000 + 0000000007799461.411900000000000 + 0000000000016397.038491000000000 + 0000000000093901.577630260000000 + -0000000083028485.000000000000000 + 0000000000074881.000000000000000 + -0000000024926804.045047420000000 +(10 rows) + +SELECT to_char(val, 'S0999999999999999.999999999999999') FROM num_data; + to_char +----------------------------------- + +0000000000000000.000000000000000 + +0000000000000000.000000000000000 + -0000000034338492.215397047000000 + +0000000000000004.310000000000000 + +0000000007799461.411900000000000 + +0000000000016397.038491000000000 + +0000000000093901.577630260000000 + -0000000083028485.000000000000000 + +0000000000074881.000000000000000 + -0000000024926804.045047420000000 +(10 rows) + +SELECT to_char(val, 'FM0999999999999999.999999999999999') FROM num_data; + to_char +----------------------------- + 0000000000000000. + 0000000000000000. + -0000000034338492.215397047 + 0000000000000004.31 + 0000000007799461.4119 + 0000000000016397.038491 + 0000000000093901.57763026 + -0000000083028485. + 0000000000074881. + -0000000024926804.04504742 +(10 rows) + +SELECT to_char(val, 'FM9999999999999999.099999999999999') FROM num_data; + to_char +--------------------- + .0 + .0 + -34338492.215397047 + 4.31 + 7799461.4119 + 16397.038491 + 93901.57763026 + -83028485.0 + 74881.0 + -24926804.04504742 +(10 rows) + +SELECT to_char(val, 'FM9999999999990999.990999999999999') FROM num_data; + to_char +--------------------- + 0000.000 + 0000.000 + -34338492.215397047 + 0004.310 + 7799461.4119 + 16397.038491 + 93901.57763026 + -83028485.000 + 74881.000 + -24926804.04504742 +(10 rows) + +SELECT to_char(val, 'FM0999999999999999.999909999999999') FROM num_data; + to_char +----------------------------- + 0000000000000000.00000 + 0000000000000000.00000 + -0000000034338492.215397047 + 0000000000000004.31000 + 0000000007799461.41190 + 0000000000016397.038491 + 0000000000093901.57763026 + -0000000083028485.00000 + 0000000000074881.00000 + -0000000024926804.04504742 +(10 rows) + +SELECT to_char(val, 'FM9999999990999999.099999999999999') FROM num_data; + to_char +--------------------- + 0000000.0 + 0000000.0 + -34338492.215397047 + 0000004.31 + 7799461.4119 + 0016397.038491 + 0093901.57763026 + -83028485.0 + 0074881.0 + -24926804.04504742 +(10 rows) + +-- YB: TODO: Disabled the test below as it relies on the current locale which is +-- configured in pg_regress using ALTER which we do not support yet. +-- SELECT to_char(val, 'L9999999999999999.099999999999999') FROM num_data; -- YB +SELECT to_char(val, 'FM9999999999999999.99999999999999') FROM num_data; + to_char +--------------------- + 0. + 0. + -34338492.215397047 + 4.31 + 7799461.4119 + 16397.038491 + 93901.57763026 + -83028485. + 74881. + -24926804.04504742 +(10 rows) + +SELECT to_char(val, 'S 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 . 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9') FROM num_data; + to_char +----------------------------------------------------------------------- + +. 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +. 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + -3 4 3 3 8 4 9 2 . 2 1 5 3 9 7 0 4 7 0 0 0 0 0 0 0 0 + +4 . 3 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +7 7 9 9 4 6 1 . 4 1 1 9 0 0 0 0 0 0 0 0 0 0 0 0 0 + +1 6 3 9 7 . 0 3 8 4 9 1 0 0 0 0 0 0 0 0 0 0 0 + +9 3 9 0 1 . 5 7 7 6 3 0 2 6 0 0 0 0 0 0 0 0 0 + -8 3 0 2 8 4 8 5 . 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +7 4 8 8 1 . 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + -2 4 9 2 6 8 0 4 . 0 4 5 0 4 7 4 2 0 0 0 0 0 0 0 0 0 +(10 rows) + +SELECT to_char(val, 'FMS 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 . 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9') FROM num_data; + to_char +------------------------------------------------------- + +0 . + +0 . + -3 4 3 3 8 4 9 2 . 2 1 5 3 9 7 0 4 7 + +4 . 3 1 + +7 7 9 9 4 6 1 . 4 1 1 9 + +1 6 3 9 7 . 0 3 8 4 9 1 + +9 3 9 0 1 . 5 7 7 6 3 0 2 6 + -8 3 0 2 8 4 8 5 . + +7 4 8 8 1 . + -2 4 9 2 6 8 0 4 . 0 4 5 0 4 7 4 2 +(10 rows) + +SELECT to_char(val, E'99999 "text" 9999 "9999" 999 "\\"text between quote marks\\"" 9999') FROM num_data; + to_char +----------------------------------------------------------- + text 9999 "text between quote marks" 0 + text 9999 "text between quote marks" 0 + text -3 9999 433 "text between quote marks" 8492 + text 9999 "text between quote marks" 4 + text 9999 779 "text between quote marks" 9461 + text 9999 1 "text between quote marks" 6397 + text 9999 9 "text between quote marks" 3902 + text -8 9999 302 "text between quote marks" 8485 + text 9999 7 "text between quote marks" 4881 + text -2 9999 492 "text between quote marks" 6804 +(10 rows) + +SELECT to_char(val, '999999SG9999999999') FROM num_data; + to_char +------------------- + + 0 + + 0 + - 34338492 + + 4 + + 7799461 + + 16397 + + 93902 + - 83028485 + + 74881 + - 24926804 +(10 rows) + +SELECT to_char(val, 'FM9999999999999999.999999999999999') FROM num_data; + to_char +--------------------- + 0. + 0. + -34338492.215397047 + 4.31 + 7799461.4119 + 16397.038491 + 93901.57763026 + -83028485. + 74881. + -24926804.04504742 +(10 rows) + +SELECT to_char(val, '9.999EEEE') FROM num_data; + to_char +------------ + 0.000e+00 + 0.000e+00 + -3.434e+07 + 4.310e+00 + 7.799e+06 + 1.640e+04 + 9.390e+04 + -8.303e+07 + 7.488e+04 + -2.493e+07 +(10 rows) + +WITH v(val) AS + (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan')) +SELECT val, + to_char(val, '9.999EEEE') as numeric, + to_char(val::float8, '9.999EEEE') as float8, + to_char(val::float4, '9.999EEEE') as float4 +FROM v; + val | numeric | float8 | float4 +------------+------------+------------+------------ + 0 | 0.000e+00 | 0.000e+00 | 0.000e+00 + -4.2 | -4.200e+00 | -4.200e+00 | -4.200e+00 + 4200000000 | 4.200e+09 | 4.200e+09 | 4.200e+09 + 0.000012 | 1.200e-05 | 1.200e-05 | 1.200e-05 + Infinity | #.####### | #.####### | #.####### + -Infinity | #.####### | #.####### | #.####### + NaN | #.####### | #.####### | #.####### +(7 rows) + +WITH v(exp) AS + (VALUES(-16379),(-16378),(-1234),(-789),(-45),(-5),(-4),(-3),(-2),(-1),(0), + (1),(2),(3),(4),(5),(38),(275),(2345),(45678),(131070),(131071)) +SELECT exp, + to_char(('1.2345e'||exp)::numeric, '9.999EEEE') as numeric +FROM v; + exp | numeric +--------+---------------- + -16379 | 1.235e-16379 + -16378 | 1.235e-16378 + -1234 | 1.235e-1234 + -789 | 1.235e-789 + -45 | 1.235e-45 + -5 | 1.235e-05 + -4 | 1.235e-04 + -3 | 1.235e-03 + -2 | 1.235e-02 + -1 | 1.235e-01 + 0 | 1.235e+00 + 1 | 1.235e+01 + 2 | 1.235e+02 + 3 | 1.235e+03 + 4 | 1.235e+04 + 5 | 1.235e+05 + 38 | 1.235e+38 + 275 | 1.235e+275 + 2345 | 1.235e+2345 + 45678 | 1.235e+45678 + 131070 | 1.235e+131070 + 131071 | 1.235e+131071 +(22 rows) + +WITH v(val) AS + (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan')) +SELECT val, + to_char(val, 'MI9999999999.99') as numeric, + to_char(val::float8, 'MI9999999999.99') as float8, + to_char(val::float4, 'MI9999999999.99') as float4 +FROM v; + val | numeric | float8 | float4 +------------+----------------+----------------+---------------- + 0 | .00 | .00 | .00 + -4.2 | - 4.20 | - 4.20 | - 4.20 + 4200000000 | 4200000000.00 | 4200000000.00 | 4200000000 + 0.000012 | .00 | .00 | .00 + Infinity | Infinity | Infinity | Infinity + -Infinity | - Infinity | - Infinity | - Infinity + NaN | NaN | NaN | NaN +(7 rows) + +WITH v(val) AS + (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan')) +SELECT val, + to_char(val, 'MI99.99') as numeric, + to_char(val::float8, 'MI99.99') as float8, + to_char(val::float4, 'MI99.99') as float4 +FROM v; + val | numeric | float8 | float4 +------------+---------+--------+-------- + 0 | .00 | .00 | .00 + -4.2 | - 4.20 | - 4.20 | - 4.20 + 4200000000 | ##.## | ##.## | ##. + 0.000012 | .00 | .00 | .00 + Infinity | ##.## | ##.## | ##. + -Infinity | -##.## | -##.## | -##. + NaN | ##.## | ##.## | ##.## +(7 rows) + +SELECT to_char('100'::numeric, 'FM999.9'); + to_char +--------- + 100. +(1 row) + +SELECT to_char('100'::numeric, 'FM999.'); + to_char +--------- + 100 +(1 row) + +SELECT to_char('100'::numeric, 'FM999'); + to_char +--------- + 100 +(1 row) + +-- Check parsing of literal text in a format string +SELECT to_char('100'::numeric, 'foo999'); + to_char +--------- + foo 100 +(1 row) + +SELECT to_char('100'::numeric, 'f\oo999'); + to_char +---------- + f\oo 100 +(1 row) + +SELECT to_char('100'::numeric, 'f\\oo999'); + to_char +----------- + f\\oo 100 +(1 row) + +SELECT to_char('100'::numeric, 'f\"oo999'); + to_char +---------- + f"oo 100 +(1 row) + +SELECT to_char('100'::numeric, 'f\\"oo999'); + to_char +----------- + f\"oo 100 +(1 row) + +SELECT to_char('100'::numeric, 'f"ool"999'); + to_char +---------- + fool 100 +(1 row) + +SELECT to_char('100'::numeric, 'f"\ool"999'); + to_char +---------- + fool 100 +(1 row) + +SELECT to_char('100'::numeric, 'f"\\ool"999'); + to_char +----------- + f\ool 100 +(1 row) + +SELECT to_char('100'::numeric, 'f"ool\"999'); + to_char +---------- + fool"999 +(1 row) + +SELECT to_char('100'::numeric, 'f"ool\\"999'); + to_char +----------- + fool\ 100 +(1 row) + +-- TO_NUMBER() +-- +SET lc_numeric = 'C'; +SELECT to_number('-34,338,492', '99G999G999'); + to_number +----------- + -34338492 +(1 row) + +SELECT to_number('-34,338,492.654,878', '99G999G999D999G999'); + to_number +------------------ + -34338492.654878 +(1 row) + +SELECT to_number('<564646.654564>', '999999.999999PR'); + to_number +---------------- + -564646.654564 +(1 row) + +SELECT to_number('0.00001-', '9.999999S'); + to_number +----------- + -0.00001 +(1 row) + +SELECT to_number('5.01-', 'FM9.999999S'); + to_number +----------- + -5.01 +(1 row) + +SELECT to_number('5.01-', 'FM9.999999MI'); + to_number +----------- + -5.01 +(1 row) + +SELECT to_number('5 4 4 4 4 8 . 7 8', '9 9 9 9 9 9 . 9 9'); + to_number +----------- + 544448.78 +(1 row) + +SELECT to_number('.01', 'FM9.99'); + to_number +----------- + 0.01 +(1 row) + +SELECT to_number('.0', '99999999.99999999'); + to_number +----------- + 0.0 +(1 row) + +SELECT to_number('0', '99.99'); + to_number +----------- + 0 +(1 row) + +SELECT to_number('.-01', 'S99.99'); + to_number +----------- + -0.01 +(1 row) + +SELECT to_number('.01-', '99.99S'); + to_number +----------- + -0.01 +(1 row) + +SELECT to_number(' . 0 1-', ' 9 9 . 9 9 S'); + to_number +----------- + -0.01 +(1 row) + +SELECT to_number('34,50','999,99'); + to_number +----------- + 3450 +(1 row) + +SELECT to_number('123,000','999G'); + to_number +----------- + 123 +(1 row) + +SELECT to_number('123456','999G999'); + to_number +----------- + 123456 +(1 row) + +SELECT to_number('$1234.56','L9,999.99'); + to_number +----------- + 1234.56 +(1 row) + +SELECT to_number('$1234.56','L99,999.99'); + to_number +----------- + 1234.56 +(1 row) + +SELECT to_number('$1,234.56','L99,999.99'); + to_number +----------- + 1234.56 +(1 row) + +SELECT to_number('1234.56','L99,999.99'); + to_number +----------- + 1234.56 +(1 row) + +SELECT to_number('1,234.56','L99,999.99'); + to_number +----------- + 1234.56 +(1 row) + +SELECT to_number('42nd', '99th'); + to_number +----------- + 42 +(1 row) + +RESET lc_numeric; +-- +-- Input syntax +-- +CREATE TABLE num_input_test (n1 numeric); +-- good inputs +INSERT INTO num_input_test(n1) VALUES (' 123'); +INSERT INTO num_input_test(n1) VALUES (' 3245874 '); +INSERT INTO num_input_test(n1) VALUES (' -93853'); +INSERT INTO num_input_test(n1) VALUES ('555.50'); +INSERT INTO num_input_test(n1) VALUES ('-555.50'); +INSERT INTO num_input_test(n1) VALUES ('NaN '); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_input_test(n1) VALUES (' nan'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_input_test(n1) VALUES (' inf '); +ERROR: DECIMAL does not support Infinity yet +INSERT INTO num_input_test(n1) VALUES (' +inf '); +ERROR: DECIMAL does not support Infinity yet +INSERT INTO num_input_test(n1) VALUES (' -inf '); +ERROR: DECIMAL does not support Infinity yet +INSERT INTO num_input_test(n1) VALUES (' Infinity '); +ERROR: DECIMAL does not support Infinity yet +INSERT INTO num_input_test(n1) VALUES (' +inFinity '); +ERROR: DECIMAL does not support Infinity yet +INSERT INTO num_input_test(n1) VALUES (' -INFINITY '); +ERROR: DECIMAL does not support Infinity yet +-- bad inputs +INSERT INTO num_input_test(n1) VALUES (' '); +ERROR: invalid input syntax for type numeric: " " +LINE 1: INSERT INTO num_input_test(n1) VALUES (' '); + ^ +INSERT INTO num_input_test(n1) VALUES (' 1234 %'); +ERROR: invalid input syntax for type numeric: " 1234 %" +LINE 1: INSERT INTO num_input_test(n1) VALUES (' 1234 %'); + ^ +INSERT INTO num_input_test(n1) VALUES ('xyz'); +ERROR: invalid input syntax for type numeric: "xyz" +LINE 1: INSERT INTO num_input_test(n1) VALUES ('xyz'); + ^ +INSERT INTO num_input_test(n1) VALUES ('- 1234'); +ERROR: invalid input syntax for type numeric: "- 1234" +LINE 1: INSERT INTO num_input_test(n1) VALUES ('- 1234'); + ^ +INSERT INTO num_input_test(n1) VALUES ('5 . 0'); +ERROR: invalid input syntax for type numeric: "5 . 0" +LINE 1: INSERT INTO num_input_test(n1) VALUES ('5 . 0'); + ^ +INSERT INTO num_input_test(n1) VALUES ('5. 0 '); +ERROR: invalid input syntax for type numeric: "5. 0 " +LINE 1: INSERT INTO num_input_test(n1) VALUES ('5. 0 '); + ^ +INSERT INTO num_input_test(n1) VALUES (''); +ERROR: invalid input syntax for type numeric: "" +LINE 1: INSERT INTO num_input_test(n1) VALUES (''); + ^ +INSERT INTO num_input_test(n1) VALUES (' N aN '); +ERROR: invalid input syntax for type numeric: " N aN " +LINE 1: INSERT INTO num_input_test(n1) VALUES (' N aN '); + ^ +INSERT INTO num_input_test(n1) VALUES ('+ infinity'); +ERROR: invalid input syntax for type numeric: "+ infinity" +LINE 1: INSERT INTO num_input_test(n1) VALUES ('+ infinity'); + ^ +SELECT * FROM num_input_test; + n1 +--------- + 123 + 3245874 + -93853 + 555.5 + -555.5 +(5 rows) + +-- +-- Test precision and scale typemods +-- +CREATE TABLE num_typemod_test ( + millions numeric(3, -6), + thousands numeric(3, -3), + units numeric(3, 0), + thousandths numeric(3, 3), + millionths numeric(3, 6) +); +\d num_typemod_test + Table "public.num_typemod_test" + Column | Type | Collation | Nullable | Default +-------------+---------------+-----------+----------+--------- + millions | numeric(3,-6) | | | + thousands | numeric(3,-3) | | | + units | numeric(3,0) | | | + thousandths | numeric(3,3) | | | + millionths | numeric(3,6) | | | + +-- rounding of valid inputs +INSERT INTO num_typemod_test VALUES (123456, 123, 0.123, 0.000123, 0.000000123); +INSERT INTO num_typemod_test VALUES (654321, 654, 0.654, 0.000654, 0.000000654); +INSERT INTO num_typemod_test VALUES (2345678, 2345, 2.345, 0.002345, 0.000002345); +INSERT INTO num_typemod_test VALUES (7654321, 7654, 7.654, 0.007654, 0.000007654); +INSERT INTO num_typemod_test VALUES (12345678, 12345, 12.345, 0.012345, 0.000012345); +INSERT INTO num_typemod_test VALUES (87654321, 87654, 87.654, 0.087654, 0.000087654); +INSERT INTO num_typemod_test VALUES (123456789, 123456, 123.456, 0.123456, 0.000123456); +INSERT INTO num_typemod_test VALUES (987654321, 987654, 987.654, 0.987654, 0.000987654); +INSERT INTO num_typemod_test VALUES ('NaN', 'NaN', 'NaN', 'NaN', 'NaN'); +ERROR: DECIMAL does not support NaN yet +SELECT scale(millions), * FROM num_typemod_test ORDER BY millions; + scale | millions | thousands | units | thousandths | millionths +-------+-----------+-----------+-------+-------------+------------ + 0 | 0 | 0 | 0 | 0.000 | 0.000000 + 0 | 1000000 | 1000 | 1 | 0.001 | 0.000001 + 0 | 2000000 | 2000 | 2 | 0.002 | 0.000002 + 0 | 8000000 | 8000 | 8 | 0.008 | 0.000008 + 0 | 12000000 | 12000 | 12 | 0.012 | 0.000012 + 0 | 88000000 | 88000 | 88 | 0.088 | 0.000088 + 0 | 123000000 | 123000 | 123 | 0.123 | 0.000123 + 0 | 988000000 | 988000 | 988 | 0.988 | 0.000988 +(8 rows) + +-- invalid inputs +INSERT INTO num_typemod_test (millions) VALUES ('inf'); +ERROR: numeric field overflow +DETAIL: A field with precision 3, scale -6 cannot hold an infinite value. +INSERT INTO num_typemod_test (millions) VALUES (999500000); +ERROR: numeric field overflow +DETAIL: A field with precision 3, scale -6 must round to an absolute value less than 10^9. +INSERT INTO num_typemod_test (thousands) VALUES (999500); +ERROR: numeric field overflow +DETAIL: A field with precision 3, scale -3 must round to an absolute value less than 10^6. +INSERT INTO num_typemod_test (units) VALUES (999.5); +ERROR: numeric field overflow +DETAIL: A field with precision 3, scale 0 must round to an absolute value less than 10^3. +INSERT INTO num_typemod_test (thousandths) VALUES (0.9995); +ERROR: numeric field overflow +DETAIL: A field with precision 3, scale 3 must round to an absolute value less than 1. +INSERT INTO num_typemod_test (millionths) VALUES (0.0009995); +ERROR: numeric field overflow +DETAIL: A field with precision 3, scale 6 must round to an absolute value less than 10^-3. +-- +-- Test some corner cases for multiplication +-- +select 4790999999999999999999999999999999999999999999999999999999999999999999999999999999999999 * 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; + ?column? +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 47909999999999999999999999999999999999999999999999999999999999999999999999999999999999985209000000000000000000000000000000000000000000000000000000000000000000000000000000000001 +(1 row) + +select 4789999999999999999999999999999999999999999999999999999999999999999999999999999999999999 * 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; + ?column? +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 47899999999999999999999999999999999999999999999999999999999999999999999999999999999999985210000000000000000000000000000000000000000000000000000000000000000000000000000000000001 +(1 row) + +select 4770999999999999999999999999999999999999999999999999999999999999999999999999999999999999 * 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; + ?column? +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 47709999999999999999999999999999999999999999999999999999999999999999999999999999999999985229000000000000000000000000000000000000000000000000000000000000000000000000000000000001 +(1 row) + +select 4769999999999999999999999999999999999999999999999999999999999999999999999999999999999999 * 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; + ?column? +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 47699999999999999999999999999999999999999999999999999999999999999999999999999999999999985230000000000000000000000000000000000000000000000000000000000000000000000000000000000001 +(1 row) + +select trim_scale((0.1 - 2e-16383) * (0.1 - 3e-16383)); + trim_scale +------------ + 0.01 +(1 row) + +-- +-- Test some corner cases for division +-- +select 999999999999999999999::numeric/1000000000000000000000; + ?column? +------------------------ + 1.00000000000000000000 +(1 row) + +select div(999999999999999999999::numeric,1000000000000000000000); + div +----- + 0 +(1 row) + +select mod(999999999999999999999::numeric,1000000000000000000000); + mod +----------------------- + 999999999999999999999 +(1 row) + +select div(-9999999999999999999999::numeric,1000000000000000000000); + div +----- + -9 +(1 row) + +select mod(-9999999999999999999999::numeric,1000000000000000000000); + mod +------------------------ + -999999999999999999999 +(1 row) + +select div(-9999999999999999999999::numeric,1000000000000000000000)*1000000000000000000000 + mod(-9999999999999999999999::numeric,1000000000000000000000); + ?column? +------------------------- + -9999999999999999999999 +(1 row) + +select mod (70.0,70) ; + mod +----- + 0.0 +(1 row) + +select div (70.0,70) ; + div +----- + 1 +(1 row) + +select 70.0 / 70 ; + ?column? +------------------------ + 1.00000000000000000000 +(1 row) + +select 12345678901234567890 % 123; + ?column? +---------- + 78 +(1 row) + +select 12345678901234567890 / 123; + ?column? +-------------------- + 100371373180768845 +(1 row) + +select div(12345678901234567890, 123); + div +-------------------- + 100371373180768844 +(1 row) + +select div(12345678901234567890, 123) * 123 + 12345678901234567890 % 123; + ?column? +---------------------- + 12345678901234567890 +(1 row) + +-- +-- Test some corner cases for square root +-- +select sqrt(1.000000000000003::numeric); + sqrt +------------------- + 1.000000000000001 +(1 row) + +select sqrt(1.000000000000004::numeric); + sqrt +------------------- + 1.000000000000002 +(1 row) + +select sqrt(96627521408608.56340355805::numeric); + sqrt +--------------------- + 9829929.87811248648 +(1 row) + +select sqrt(96627521408608.56340355806::numeric); + sqrt +--------------------- + 9829929.87811248649 +(1 row) + +select sqrt(515549506212297735.073688290367::numeric); + sqrt +------------------------ + 718017761.766585921184 +(1 row) + +select sqrt(515549506212297735.073688290368::numeric); + sqrt +------------------------ + 718017761.766585921185 +(1 row) + +select sqrt(8015491789940783531003294973900306::numeric); + sqrt +------------------- + 89529278953540017 +(1 row) + +select sqrt(8015491789940783531003294973900307::numeric); + sqrt +------------------- + 89529278953540018 +(1 row) + +-- +-- Test code path for raising to integer powers +-- +select 10.0 ^ -2147483648 as rounds_to_zero; + rounds_to_zero +-------------------- + 0.0000000000000000 +(1 row) + +select 10.0 ^ -2147483647 as rounds_to_zero; + rounds_to_zero +-------------------- + 0.0000000000000000 +(1 row) + +select 10.0 ^ 2147483647 as overflows; +ERROR: value overflows numeric format +select 117743296169.0 ^ 1000000000 as overflows; +ERROR: value overflows numeric format +-- cases that used to return inaccurate results +select 3.789 ^ 21; + ?column? +-------------------------------- + 1409343026052.8716016316022141 +(1 row) + +select 3.789 ^ 35; + ?column? +---------------------------------------- + 177158169650516670809.3820586142670135 +(1 row) + +select 1.2 ^ 345; + ?column? +----------------------------------------------- + 2077446682327378559843444695.5827049735727869 +(1 row) + +select 0.12 ^ (-20); + ?column? +-------------------------------------- + 2608405330458882702.5529619561355838 +(1 row) + +select 1.000000000123 ^ (-2147483648); + ?column? +-------------------- + 0.7678656556403084 +(1 row) + +select coalesce(nullif(0.9999999999 ^ 23300000000000, 0), 0) as rounds_to_zero; + rounds_to_zero +---------------- + 0 +(1 row) + +select round(((1 - 1.500012345678e-1000) ^ 1.45e1003) * 1e1000); + round +---------------------------------------------------------- + 25218976308958387188077465658068501556514992509509282366 +(1 row) + +-- cases that used to error out +select 0.12 ^ (-25); + ?column? +------------------------------------------- + 104825960103961013959336.4983657883169110 +(1 row) + +select 0.5678 ^ (-85); + ?column? +---------------------------------------- + 782333637740774446257.7719390061997396 +(1 row) + +select coalesce(nullif(0.9999999999 ^ 70000000000000, 0), 0) as underflows; + underflows +------------ + 0 +(1 row) + +-- negative base to integer powers +select (-1.0) ^ 2147483646; + ?column? +-------------------- + 1.0000000000000000 +(1 row) + +select (-1.0) ^ 2147483647; + ?column? +--------------------- + -1.0000000000000000 +(1 row) + +select (-1.0) ^ 2147483648; + ?column? +-------------------- + 1.0000000000000000 +(1 row) + +select (-1.0) ^ 1000000000000000; + ?column? +-------------------- + 1.0000000000000000 +(1 row) + +select (-1.0) ^ 1000000000000001; + ?column? +--------------------- + -1.0000000000000000 +(1 row) + +-- +-- Tests for raising to non-integer powers +-- +-- special cases +select 0.0 ^ 0.0; + ?column? +-------------------- + 1.0000000000000000 +(1 row) + +select (-12.34) ^ 0.0; + ?column? +-------------------- + 1.0000000000000000 +(1 row) + +select 12.34 ^ 0.0; + ?column? +-------------------- + 1.0000000000000000 +(1 row) + +select 0.0 ^ 12.34; + ?column? +-------------------- + 0.0000000000000000 +(1 row) + +-- NaNs +select 'NaN'::numeric ^ 'NaN'::numeric; + ?column? +---------- + NaN +(1 row) + +select 'NaN'::numeric ^ 0; + ?column? +---------- + 1 +(1 row) + +select 'NaN'::numeric ^ 1; + ?column? +---------- + NaN +(1 row) + +select 0 ^ 'NaN'::numeric; + ?column? +---------- + NaN +(1 row) + +select 1 ^ 'NaN'::numeric; + ?column? +---------- + 1 +(1 row) + +-- invalid inputs +select 0.0 ^ (-12.34); +ERROR: zero raised to a negative power is undefined +select (-12.34) ^ 1.2; +ERROR: a negative number raised to a non-integer power yields a complex result +-- cases that used to generate inaccurate results +select 32.1 ^ 9.8; + ?column? +-------------------- + 580429286790711.10 +(1 row) + +select 32.1 ^ (-9.8); + ?column? +---------------------------------- + 0.000000000000001722862754788209 +(1 row) + +select 12.3 ^ 45.6; + ?column? +------------------------------------------------------ + 50081010321492803393171165777624533697036806969694.9 +(1 row) + +select 12.3 ^ (-45.6); + ?column? +--------------------------------------------------------------------- + 0.00000000000000000000000000000000000000000000000001996764828785491 +(1 row) + +-- big test +select 1.234 ^ 5678; + ?column? +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 307239295662090741644584872593956173493568238595074141254349565406661439636598896798876823220904084953233015553994854875890890858118656468658643918169805277399402542281777901029346337707622181574346585989613344285010764501017625366742865066948856161360224801370482171458030533346309750557140549621313515752078638620714732831815297168231790779296290266207315344008883935010274044001522606235576584215999260117523114297033944018699691024106823438431754073086813382242140602291215149759520833200152654884259619588924545324.5973362312547382 +(1 row) + +-- +-- Tests for EXP() +-- +-- special cases +select exp(0.0); + exp +-------------------- + 1.0000000000000000 +(1 row) + +select exp(1.0); + exp +-------------------- + 2.7182818284590452 +(1 row) + +select exp(1.0::numeric(71,70)); + exp +-------------------------------------------------------------------------- + 2.7182818284590452353602874713526624977572470936999595749669676277240766 +(1 row) + +select exp('nan'::numeric); + exp +----- + NaN +(1 row) + +select exp('inf'::numeric); + exp +---------- + Infinity +(1 row) + +select exp('-inf'::numeric); + exp +----- + 0 +(1 row) + +select coalesce(nullif(exp(-5000::numeric), 0), 0) as rounds_to_zero; + rounds_to_zero +---------------- + 0 +(1 row) + +select coalesce(nullif(exp(-10000::numeric), 0), 0) as underflows; + underflows +------------ + 0 +(1 row) + +-- cases that used to generate inaccurate results +select exp(32.999); + exp +--------------------- + 214429043492155.053 +(1 row) + +select exp(-32.999); + exp +---------------------------------- + 0.000000000000004663547361468248 +(1 row) + +select exp(123.456); + exp +------------------------------------------------------------ + 413294435277809344957685441227343146614594393746575438.725 +(1 row) + +select exp(-123.456); + exp +------------------------------------------------------------------------- + 0.000000000000000000000000000000000000000000000000000002419582541264601 +(1 row) + +-- big test +select exp(1234.5678); + exp +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 146549072930959479983482138503979804217622199675223653966270157446954995433819741094410764947112047906012815540251009949604426069672532417736057033099274204598385314594846509975629046864798765888104789074984927709616261452461385220475510438783429612447831614003668421849727379202555580791042606170523016207262965336641214601082882495255771621327088265411334088968112458492660609809762865582162764292604697957813514621259353683899630997077707406305730694385703091201347848855199354307506425820147289848677003277208302716466011827836279231.9667 +(1 row) + +-- +-- Tests for generate_series +-- +select * from generate_series(0.0::numeric, 4.0::numeric); + generate_series +----------------- + 0.0 + 1.0 + 2.0 + 3.0 + 4.0 +(5 rows) + +select * from generate_series(0.1::numeric, 4.0::numeric, 1.3::numeric); + generate_series +----------------- + 0.1 + 1.4 + 2.7 + 4.0 +(4 rows) + +select * from generate_series(4.0::numeric, -1.5::numeric, -2.2::numeric); + generate_series +----------------- + 4.0 + 1.8 + -0.4 +(3 rows) + +-- Trigger errors +select * from generate_series(-100::numeric, 100::numeric, 0::numeric); +ERROR: step size cannot equal zero +select * from generate_series(-100::numeric, 100::numeric, 'nan'::numeric); +ERROR: step size cannot be NaN +select * from generate_series('nan'::numeric, 100::numeric, 10::numeric); +ERROR: start value cannot be NaN +select * from generate_series(0::numeric, 'nan'::numeric, 10::numeric); +ERROR: stop value cannot be NaN +select * from generate_series('inf'::numeric, 'inf'::numeric, 10::numeric); +ERROR: start value cannot be infinity +select * from generate_series(0::numeric, 'inf'::numeric, 10::numeric); +ERROR: stop value cannot be infinity +select * from generate_series(0::numeric, '42'::numeric, '-inf'::numeric); +ERROR: step size cannot be infinity +-- Checks maximum, output is truncated +select (i / (10::numeric ^ 131071))::numeric(1,0) + from generate_series(6 * (10::numeric ^ 131071), + 9 * (10::numeric ^ 131071), + 10::numeric ^ 131071) as a(i); + numeric +--------- + 6 + 7 + 8 + 9 +(4 rows) + +-- Check usage with variables +select * from generate_series(1::numeric, 3::numeric) i, generate_series(i,3) j; + i | j +---+--- + 1 | 1 + 1 | 2 + 1 | 3 + 2 | 2 + 2 | 3 + 3 | 3 +(6 rows) + +select * from generate_series(1::numeric, 3::numeric) i, generate_series(1,i) j; + i | j +---+--- + 1 | 1 + 2 | 1 + 2 | 2 + 3 | 1 + 3 | 2 + 3 | 3 +(6 rows) + +select * from generate_series(1::numeric, 3::numeric) i, generate_series(1,5,i) j; + i | j +---+--- + 1 | 1 + 1 | 2 + 1 | 3 + 1 | 4 + 1 | 5 + 2 | 1 + 2 | 3 + 2 | 5 + 3 | 1 + 3 | 4 +(10 rows) + +-- +-- Tests for LN() +-- +-- Invalid inputs +select ln(-12.34); +ERROR: cannot take logarithm of a negative number +select ln(0.0); +ERROR: cannot take logarithm of zero +-- Some random tests +select ln(1.2345678e-28); + ln +----------------------------------------- + -64.26166165451762991204894255882820859 +(1 row) + +select ln(0.0456789); + ln +--------------------- + -3.0861187944847439 +(1 row) + +select ln(0.349873948359354029493948309745709580730482050975); + ln +----------------------------------------------------- + -1.050182336912082775693991697979750253056317885460 +(1 row) + +select ln(0.99949452); + ln +------------------------- + -0.00050560779808326467 +(1 row) + +select ln(1.00049687395); + ln +------------------------ + 0.00049675054901370394 +(1 row) + +select ln(1234.567890123456789); + ln +-------------------- + 7.1184763012977896 +(1 row) + +select ln(5.80397490724e5); + ln +-------------------- + 13.271468476626518 +(1 row) + +select ln(9.342536355e34); + ln +-------------------- + 80.522470935524187 +(1 row) + +-- +-- Tests for LOG() (base 10) +-- +-- invalid inputs +select log(-12.34); +ERROR: cannot take logarithm of a negative number +CONTEXT: SQL function "log" statement 1 +select log(0.0); +ERROR: cannot take logarithm of zero +CONTEXT: SQL function "log" statement 1 +-- some random tests +select log(1.234567e-89); + log +----------------------------------------------------------------------------------------------------- + -88.90848533591373725637496492944925187293052336306443143312825869985819779294142441287021741054275 +(1 row) + +select log(3.4634998359873254962349856073435545); + log +-------------------------------------- + 0.5395151714070134409152404011959981 +(1 row) + +select log(9.999999999999999999); + log +---------------------- + 1.000000000000000000 +(1 row) + +select log(10.00000000000000000); + log +--------------------- + 1.00000000000000000 +(1 row) + +select log(10.00000000000000001); + log +--------------------- + 1.00000000000000000 +(1 row) + +select log(590489.45235237); + log +------------------- + 5.771212144411727 +(1 row) + +-- +-- Tests for LOG() (arbitrary base) +-- +-- invalid inputs +select log(-12.34, 56.78); +ERROR: cannot take logarithm of a negative number +select log(-12.34, -56.78); +ERROR: cannot take logarithm of a negative number +select log(12.34, -56.78); +ERROR: cannot take logarithm of a negative number +select log(0.0, 12.34); +ERROR: cannot take logarithm of zero +select log(12.34, 0.0); +ERROR: cannot take logarithm of zero +select log(1.0, 12.34); +ERROR: division by zero +-- some random tests +select log(1.23e-89, 6.4689e45); + log +------------------------------------------------------------------------------------------------ + -0.5152489207781856983977054971756484879653568168479201885425588841094788842469115325262329756 +(1 row) + +select log(0.99923, 4.58934e34); + log +--------------------- + -103611.55579544132 +(1 row) + +select log(1.000016, 8.452010e18); + log +-------------------- + 2723830.2877097365 +(1 row) + +select log(3.1954752e47, 9.4792021e-73); + log +------------------------------------------------------------------------------------- + -1.51613372350688302142917386143459361608600157692779164475351842333265418126982165 +(1 row) + +-- +-- Tests for scale() +-- +select scale(numeric 'NaN'); + scale +------- + +(1 row) + +select scale(numeric 'inf'); + scale +------- + +(1 row) + +select scale(NULL::numeric); + scale +------- + +(1 row) + +select scale(1.12); + scale +------- + 2 +(1 row) + +select scale(0); + scale +------- + 0 +(1 row) + +select scale(0.00); + scale +------- + 2 +(1 row) + +select scale(1.12345); + scale +------- + 5 +(1 row) + +select scale(110123.12475871856128); + scale +------- + 14 +(1 row) + +select scale(-1123.12471856128); + scale +------- + 11 +(1 row) + +select scale(-13.000000000000000); + scale +------- + 15 +(1 row) + +-- +-- Tests for min_scale() +-- +select min_scale(numeric 'NaN') is NULL; -- should be true + ?column? +---------- + t +(1 row) + +select min_scale(numeric 'inf') is NULL; -- should be true + ?column? +---------- + t +(1 row) + +select min_scale(0); -- no digits + min_scale +----------- + 0 +(1 row) + +select min_scale(0.00); -- no digits again + min_scale +----------- + 0 +(1 row) + +select min_scale(1.0); -- no scale + min_scale +----------- + 0 +(1 row) + +select min_scale(1.1); -- scale 1 + min_scale +----------- + 1 +(1 row) + +select min_scale(1.12); -- scale 2 + min_scale +----------- + 2 +(1 row) + +select min_scale(1.123); -- scale 3 + min_scale +----------- + 3 +(1 row) + +select min_scale(1.1234); -- scale 4, filled digit + min_scale +----------- + 4 +(1 row) + +select min_scale(1.12345); -- scale 5, 2 NDIGITS + min_scale +----------- + 5 +(1 row) + +select min_scale(1.1000); -- 1 pos in NDIGITS + min_scale +----------- + 1 +(1 row) + +select min_scale(1e100); -- very big number + min_scale +----------- + 0 +(1 row) + +-- +-- Tests for trim_scale() +-- +select trim_scale(numeric 'NaN'); + trim_scale +------------ + NaN +(1 row) + +select trim_scale(numeric 'inf'); + trim_scale +------------ + Infinity +(1 row) + +select trim_scale(1.120); + trim_scale +------------ + 1.12 +(1 row) + +select trim_scale(0); + trim_scale +------------ + 0 +(1 row) + +select trim_scale(0.00); + trim_scale +------------ + 0 +(1 row) + +select trim_scale(1.1234500); + trim_scale +------------ + 1.12345 +(1 row) + +select trim_scale(110123.12475871856128000); + trim_scale +----------------------- + 110123.12475871856128 +(1 row) + +select trim_scale(-1123.124718561280000000); + trim_scale +------------------- + -1123.12471856128 +(1 row) + +select trim_scale(-13.00000000000000000000); + trim_scale +------------ + -13 +(1 row) + +select trim_scale(1e100); + trim_scale +------------------------------------------------------------------------------------------------------- + 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +(1 row) + +-- +-- Tests for SUM() +-- +-- cases that need carry propagation +SELECT SUM(9999::numeric) FROM generate_series(1, 100000); + sum +----------- + 999900000 +(1 row) + +SELECT SUM((-9999)::numeric) FROM generate_series(1, 100000); + sum +------------ + -999900000 +(1 row) + +-- +-- Tests for VARIANCE() +-- +CREATE TABLE num_variance (a numeric); +INSERT INTO num_variance VALUES (0); +INSERT INTO num_variance VALUES (3e-500); +INSERT INTO num_variance VALUES (-3e-500); +INSERT INTO num_variance VALUES (4e-500 - 1e-16383); +INSERT INTO num_variance VALUES (-4e-500 + 1e-16383); +-- variance is just under 12.5e-1000 and so should round down to 12e-1000 +SELECT trim_scale(variance(a) * 1e1000) FROM num_variance; + trim_scale +------------ + 12 +(1 row) + +-- check that parallel execution produces the same result +BEGIN; +ALTER TABLE num_variance SET (parallel_workers = 4); +NOTICE: storage parameters are currently ignored in YugabyteDB +SET LOCAL parallel_setup_cost = 0; +SET LOCAL max_parallel_workers_per_gather = 4; +SELECT trim_scale(variance(a) * 1e1000) FROM num_variance; + trim_scale +------------ + 12 +(1 row) + +ROLLBACK; +-- case where sum of squares would overflow but variance does not +DELETE FROM num_variance; +INSERT INTO num_variance SELECT 9e131071 + x FROM generate_series(1, 5) x; +SELECT variance(a) FROM num_variance; + variance +-------------------- + 2.5000000000000000 +(1 row) + +-- check that parallel execution produces the same result +BEGIN; +ALTER TABLE num_variance SET (parallel_workers = 4); +NOTICE: storage parameters are currently ignored in YugabyteDB +SET LOCAL parallel_setup_cost = 0; +SET LOCAL max_parallel_workers_per_gather = 4; +SELECT variance(a) FROM num_variance; + variance +-------------------- + 2.5000000000000000 +(1 row) + +ROLLBACK; +DROP TABLE num_variance; +-- +-- Tests for GCD() +-- +SELECT a, b, gcd(a, b), gcd(a, -b), gcd(-b, a), gcd(-b, -a) +FROM (VALUES (0::numeric, 0::numeric), + (0::numeric, numeric 'NaN'), + (0::numeric, 46375::numeric), + (433125::numeric, 46375::numeric), + (43312.5::numeric, 4637.5::numeric), + (4331.250::numeric, 463.75000::numeric), + ('inf', '0'), + ('inf', '42'), + ('inf', 'inf') + ) AS v(a, b); + a | b | gcd | gcd | gcd | gcd +----------+-----------+---------+---------+---------+--------- + 0 | 0 | 0 | 0 | 0 | 0 + 0 | NaN | NaN | NaN | NaN | NaN + 0 | 46375 | 46375 | 46375 | 46375 | 46375 + 433125 | 46375 | 875 | 875 | 875 | 875 + 43312.5 | 4637.5 | 87.5 | 87.5 | 87.5 | 87.5 + 4331.250 | 463.75000 | 8.75000 | 8.75000 | 8.75000 | 8.75000 + Infinity | 0 | NaN | NaN | NaN | NaN + Infinity | 42 | NaN | NaN | NaN | NaN + Infinity | Infinity | NaN | NaN | NaN | NaN +(9 rows) + +-- +-- Tests for LCM() +-- +SELECT a,b, lcm(a, b), lcm(a, -b), lcm(-b, a), lcm(-b, -a) +FROM (VALUES (0::numeric, 0::numeric), + (0::numeric, numeric 'NaN'), + (0::numeric, 13272::numeric), + (13272::numeric, 13272::numeric), + (423282::numeric, 13272::numeric), + (42328.2::numeric, 1327.2::numeric), + (4232.820::numeric, 132.72000::numeric), + ('inf', '0'), + ('inf', '42'), + ('inf', 'inf') + ) AS v(a, b); + a | b | lcm | lcm | lcm | lcm +----------+-----------+--------------+--------------+--------------+-------------- + 0 | 0 | 0 | 0 | 0 | 0 + 0 | NaN | NaN | NaN | NaN | NaN + 0 | 13272 | 0 | 0 | 0 | 0 + 13272 | 13272 | 13272 | 13272 | 13272 | 13272 + 423282 | 13272 | 11851896 | 11851896 | 11851896 | 11851896 + 42328.2 | 1327.2 | 1185189.6 | 1185189.6 | 1185189.6 | 1185189.6 + 4232.820 | 132.72000 | 118518.96000 | 118518.96000 | 118518.96000 | 118518.96000 + Infinity | 0 | NaN | NaN | NaN | NaN + Infinity | 42 | NaN | NaN | NaN | NaN + Infinity | Infinity | NaN | NaN | NaN | NaN +(10 rows) + +SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overflow +ERROR: value overflows numeric format +-- +-- Tests for factorial +-- +SELECT factorial(4); + factorial +----------- + 24 +(1 row) + +SELECT factorial(15); + factorial +--------------- + 1307674368000 +(1 row) + +SELECT factorial(100000); +ERROR: value overflows numeric format +SELECT factorial(0); + factorial +----------- + 1 +(1 row) + +SELECT factorial(-4); +ERROR: factorial of a negative number is undefined +-- +-- Tests for pg_lsn() +-- +SELECT pg_lsn(23783416::numeric); + pg_lsn +----------- + 0/16AE7F8 +(1 row) + +SELECT pg_lsn(0::numeric); + pg_lsn +-------- + 0/0 +(1 row) + +SELECT pg_lsn(18446744073709551615::numeric); + pg_lsn +------------------- + FFFFFFFF/FFFFFFFF +(1 row) + +SELECT pg_lsn(-1::numeric); +ERROR: pg_lsn out of range +SELECT pg_lsn(18446744073709551616::numeric); +ERROR: pg_lsn out of range +SELECT pg_lsn('NaN'::numeric); +ERROR: cannot convert NaN to pg_lsn diff --git a/src/test/regress/expected/yb.port.numeric_big.out b/src/test/regress/expected/yb.port.numeric_big.out new file mode 100644 index 0000000000000..18ce47ce69625 --- /dev/null +++ b/src/test/regress/expected/yb.port.numeric_big.out @@ -0,0 +1,2103 @@ +-- ****************************** +-- * Test suite for the Postgres NUMERIC data type +-- ****************************** +-- Must drop tables created by short numeric test. +DROP TABLE num_data; +DROP TABLE num_exp_add; +DROP TABLE num_exp_sub; +DROP TABLE num_exp_div; +DROP TABLE num_exp_mul; +DROP TABLE num_exp_sqrt; +DROP TABLE num_exp_ln; +DROP TABLE num_exp_log10; +DROP TABLE num_exp_power_10_ln; +DROP TABLE num_result; +CREATE TABLE num_data (id int4, val numeric(1000,800)); +CREATE TABLE num_exp_add (id1 int4, id2 int4, expected numeric(1000,800)); +CREATE TABLE num_exp_sub (id1 int4, id2 int4, expected numeric(1000,800)); +CREATE TABLE num_exp_div (id1 int4, id2 int4, expected numeric(1000,800)); +CREATE TABLE num_exp_mul (id1 int4, id2 int4, expected numeric(1000,800)); +CREATE TABLE num_exp_sqrt (id int4, expected numeric(1000,800)); +CREATE TABLE num_exp_ln (id int4, expected numeric(1000,800)); +CREATE TABLE num_exp_log10 (id int4, expected numeric(1000,800)); +CREATE TABLE num_exp_power_10_ln (id int4, expected numeric(1000,800)); +CREATE TABLE num_result (id1 int4, id2 int4, result numeric(1000,800)); +-- ****************************** +-- * The following EXPECTED results are computed by bc(1) +-- * with a scale of 1000 +-- ****************************** +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_add VALUES (0,0,'0'); +INSERT INTO num_exp_sub VALUES (0,0,'0'); +INSERT INTO num_exp_mul VALUES (0,0,'0'); +INSERT INTO num_exp_div VALUES (0,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (0,1,'85243.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (0,1,'-85243.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (0,1,'0'); +INSERT INTO num_exp_div VALUES (0,1,'0'); +INSERT INTO num_exp_add VALUES (0,2,'-994877526002806872754342148749241.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (0,2,'994877526002806872754342148749241.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (0,2,'0'); +INSERT INTO num_exp_div VALUES (0,2,'0'); +INSERT INTO num_exp_add VALUES (0,3,'-60302029489319384367663884408085757480.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (0,3,'60302029489319384367663884408085757480.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (0,3,'0'); +INSERT INTO num_exp_div VALUES (0,3,'0'); +INSERT INTO num_exp_add VALUES (0,4,'5329378275943663322215245.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (0,4,'-5329378275943663322215245.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (0,4,'0'); +INSERT INTO num_exp_div VALUES (0,4,'0'); +INSERT INTO num_exp_add VALUES (0,5,'-652755630.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (0,5,'652755630.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (0,5,'0'); +INSERT INTO num_exp_div VALUES (0,5,'0'); +INSERT INTO num_exp_add VALUES (0,6,'.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_sub VALUES (0,6,'-.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_mul VALUES (0,6,'0'); +INSERT INTO num_exp_div VALUES (0,6,'0'); +INSERT INTO num_exp_add VALUES (0,7,'-818934540071845742'); +INSERT INTO num_exp_sub VALUES (0,7,'818934540071845742'); +INSERT INTO num_exp_mul VALUES (0,7,'0'); +INSERT INTO num_exp_div VALUES (0,7,'0'); +INSERT INTO num_exp_add VALUES (0,8,'8496986223.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_sub VALUES (0,8,'-8496986223.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (0,8,'0'); +INSERT INTO num_exp_div VALUES (0,8,'0'); +INSERT INTO num_exp_add VALUES (0,9,'54863480.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (0,9,'-54863480.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (0,9,'0'); +INSERT INTO num_exp_div VALUES (0,9,'0'); +INSERT INTO num_exp_add VALUES (1,0,'85243.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (1,0,'85243.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (1,0,'0'); +INSERT INTO num_exp_div VALUES (1,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (1,1,'170486.79080049955252152479695727201571965474311716541919780029226071455736587237347615553466832461907447637054203186991790701615551214692555785671028648640897898741246882118067609728317430043806625387779037980513762118868084887015059202190301421555269486602797852927777567694581746398790609996101506730430853942556475840126871131898407356048450541232591147357021858041662012293323494543567675306406079659294204054863522259037763051870433216859794083051717080761509518250300466106939998045710070'); +INSERT INTO num_exp_sub VALUES (1,1,'0'); +INSERT INTO num_exp_mul VALUES (1,1,'7266436459.363324713115467666113895787027372854351303425444968800459979742082292257107107767894843498525848597439323325297125474674300428669958003640228730876886174255457103020291514229439701871032118057857763809224712818579091741996335014138185389554630910658876423205103697147288306070059640369158894028731728589073730895396494400175420670713113234800826523252075036892246807434088405522834549449664122407363485486902219500109237667016524913027290777216477989904700729228025571098410870506256758678625928245828210775042611512394316804583459576285681159178280400209217948833631961377519855502763611693070238579591463373484424582723121059964236704135695706864890193388054537703767833595331866551990460050750959493829603581882430597105627056085260296454181999581594565113210481151487049158699087454047624433576922179904629'); +INSERT INTO num_exp_div VALUES (1,1,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (1,2,'-994877526002806872754342148663997.64812998474240514147207095573950146764154822009863493316394610578375247334825932838513167168342610420582834742950389452212867974756590355021495169819086060202117180229196935525386766373096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (1,2,'994877526002806872754342148834484.43893048429492666626902822775522112238466538551783273345620682034111834572173548391979999630250058057637037929942180153828419189449146140692523818459983958943364062347264545253704196416903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (1,2,'-84806738323879544552397401815149740513.8505875535743013876823142649666132764556588225959336097903898464616542203793600590311980154402068027051522932586050753865288419084437796768749509032177577451738712965496693249429231838833655025794915864261585848007162358912070811805298210095333433397862313304655108809804359760907473898420016370058274978588765092161529583480924554820756527238472641797198545539410039895140087686344382628317530286295498797849942258314364503000942821309916954725689781458590617068629906894951122301020797266469357701283289275708774593896770378558232444454118891917258610753077932026885574920166837998049508644891327208474213193224700658584824407382455480657734911543930195324144216374573825'); +INSERT INTO num_exp_div VALUES (1,2,'-.000000000000000000000000000085682300757901809257711279577127388124986344391495296640171942990079130291883279872719240502687189411421655284515420074848478500192127657883342858267913417679786356766341637336955924836847768457039175660279784295612167899455618405343686908907695358239088351870495830739180518509859269437015797489301844593920484927630172344269378248455657186218762679357609204333669024237648538465053048724383898528808961206696787294681884412485427843796696788390072124570957047672341581447744981862017791206857428430183366004980966398716823512288330174863890117558744630102020144500158878244146399686532935435591262767487823942606452349972401012308378888947381934278131785907155692007064636085000405504866631011593239041758448995933095907216863744502344014999804306234830774259496097549717476344048'); +INSERT INTO num_exp_add VALUES (1,3,'-60302029489319384367663884408085672236.83687099063256754698860828386302509843815398979402006244388708674093244201278399438376682321121138429850885935540924586964982855913223221441591310211730902799041126800414795030815514254713522692405212716783388698431088814919226444677188004928663343696636297536500970117716818423689175692808344185016908913828066250587407384563498516598672584120143890364303296142744031320345312431817858545326010704685255237541162931904446804064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (1,3,'60302029489319384367663884408085842723.62767149018508907178556555587874475318127115521321786273614780129829831438626014991843514783028586066905089122532715288580534070605779007112619958852628801540288008918482404759132944298520148080184250697297150817299173701934285646867489426483932830299434150464278537812298564822479785688909850915447762856384542090714278516461905872647123125352735037721325154184406043613668806975385533851732090363979459292404685190942209855935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (1,3,'-5140349743195574373979577554212527512597024.162480344833040409158673429491690439298506850052285119390701002577176786023622062742050099464897084793357329597395417632908812044304066963549928478520702505283307379218587635434673128958824348493758429380623577527186462464399974242800361134191519694694139153279582776168995426125926314513926640766117733774558011741611075336271613675760116784769700605008122422944290652448956922432960815546502965310676913079866511016221573557684245901002643719965652152439520727383305120298495304784052489867651462175349450610643411043707261107569691076730261762793560088893354750383257372118118753366377402045596735023445172252225346164608897913115394905485106225627590643805003075069931177395059698550161546962768768895596088478488887530518018212441345360153523733317120037436403475909117998647781920105313938836144009539683'); +INSERT INTO num_exp_div VALUES (1,3,'-.000000000000000000000000000000001413607404628860353773457807436398753936801768769045711604884548436548520368932184112069166807060840219636509423284498981041814526856251281381511288768719259120481595036745286884246627534964287523188738499223075292690431699417313258943941279343383979626641848305343592679057491670166887054819766294147341982669243114259272404203080347707713358471397866402657818267495050115642987782080912962056565478445923456884713049272637646637760989004917643369240372476411912794578381690666695711891846833983534126217706309741885844723208036219144146342212915129560758201609824034610223907791643110990898577049488934294259106725414517181607988173722432655731491050637087261030314548853334338835938120502930424813699221083197863303458179445322810087784892821862085562891180364134284641396475'); +INSERT INTO num_exp_add VALUES (1,4,'5329378275943663322300488.64471790965256505869684245785528331091076155554650629138833809683459634328609777839510066435612911583108717191216693735823717997111970662575497378762952496582183738308720094529950793570383580785385569873278068217936841324404119828637880370718028782103860007754579779716996004352284614661690063919125301052941328989181561787543541920734755989452320799185700078241880935083616978140555713297241612718277766918005268951861880490889884082730841740604517529391011862694381726143520658746305661338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (1,4,'-5329378275943663322130001.85391741010004353389988518583956365616764439012730849109607738227723047091262162286043233973705463946054514004224903034208166782419414876904468730122054597840936856190652484801633363526576955397606531892764306099068756437389060626447578949162759295501062154826802212022414257953494004665588557188694447110384853149054690655645134564686305448219729651828678220200218922790293483596988037990835533058983562863141746692824117439019450865871047657552800448629502344444081260036580660700595591338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (1,4,'454294299613767152878025320780.534199313974295807138790763501115780294529340799108297697573066187975311338382917022391830256203305238757334106943821060545424417350991354829668286194840925251162479496893943917530660694097932059166013476064988623431110002057735318529554555260199417935495388243829261809007709919225000608711536928171687251088217591210419208480251102484043683131687013687838713055660405381318396419588727500715930145098362997142075433472039319292466570912777345841400769387321465602989947078951135489852486382469990409873227894248208197179481868230244584527040573428134962626267135732247029762468417273891700661832893497067151409134724061246612631376075173287264787886064622106855886785805818642123776489793586531950438285720668411465570116161790343538663297713926678759640594912243360541590368666922379919514826022141331900181'); +INSERT INTO num_exp_div VALUES (1,4,'.000000000000000000015994998100440878014888861029956505927201309704413242103407885948184870841766875212766910686894450511886242468216220470061916924303252919423028993720180330014505454865704155281502763018913215741264982350384245753394656021401865680441649920273268554396350483440173848850052788410943178207336328451359951614056237100465802151856198860908371340425459435127133071447273887829397881221098443685586506647314622864702873235212396755866459409263439958011711379929751157260020133239574261188528305921244365838405372320186907437842180388704854605498842516581811515413843298370501194935797268161171428747542997504369133579105180311662221854071962295818264211400101689450830279979372422749150894553349570063000769685274875561760334738424509532610467832951796852051505383374693614022043010735004494395190'); +INSERT INTO num_exp_add VALUES (1,5,'-652670387.03916046850422757312745971450663862747133703839829692066597367760104802542475264601221776157515632293978442027199108085723617181683235487266149426304575903892721468296143475297345699313102262188759506518376019936160961709578829069446312051432780603656651983414612264636232727512091101057374054475214114364113300402823059519499217878746766275164739724770556122895799337810694888119810524986616938847385753562624139431982468828696587199570410008890188532132652095915565323400735066310142303225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (1,5,'652840873.82996096805674909792441698652235828221445420381749472095823439215841389779822880154688608619423079931032645214190898787339168396375791272937178074945473802633968350414211085025663129356908887576538544498889782055029046596593888271636613472988050090259449836342389832330814473910881711053475561205644968306669776242949930651397625234795216816397330872127577980937461350104018382663378200293023018506679957617487661691020231880567020416430204091941905612894161614165865789507675064355852373225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (1,5,'-55643106304872.575994253221940844841058071061962511162776681458310912066379595519265546225338405882027547140476045378015935579066580347282075024392379464189067155567624835346798806677988850250198082355055954078446421075165109896091047534711081616362392995575466807084807876544560268050611445006601394735810211678919646667455478469014906335433468365011768049600750224822391684377238242162320161552720449713229523135506671063115436813348612986916614320012995541575293478341408982118538094438068036422562665160411591652618670802973618768526197813319204816293073794413317669922144705633308090832805914096147659820167569140291210526520361556881576175809360614782817717579318298657744021133210954279487777567785280633309576696708168342539425395482429923273623865667723482418178781573723597156804085501875735112311466228778929147929'); +INSERT INTO num_exp_div VALUES (1,5,'-.000130590057635351941758745900947472461593749814351229292370661147301124533787181489468804246182606762727711479707901680546780430454163647774077629503207962424213266902732555945190365467801995495570282501722505521485829885605904543846887348545254658726343578684749830307120625129857380290225370772763609458975555029415082569247186899112975387051141777417911244576134390940441209829852154391377911942082738699481875795620569383196133124499983396562167632007454221121465745085962247988140942672429187053671899537331280701003778040796615094903602095098880716919238394057384949891444700347825726273725378453454782330181608182747900774711384845635284701538541452235224216112380245660177463043471814071809869894647262285332580556739424040615194137651616350340752691170045698234853734471923738591898290468792787543896'); +INSERT INTO num_exp_add VALUES (1,6,'85243.44233732197133191329295927531563604777955507322414928382967007765263923984471408038635831036097817458527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (1,6,'85243.34846317758118961150399799670008360696356209219504851646259063690472663252876207514831001425809630178527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (1,6,'4001.075404054519813215296429095020391062109905613738157927030437221793757373268325953178030040276107574363822832168160758728653712686313134828282109532831190239521843808940611025488601517574653932032236616573457735900045655665690517797280666732780030171712864961531623060353548802466577910774711998056232872212688464691036260746751992072745518373073825852119460094113694393273456369345499434994672730920070410547163082189385645712866100999708173472360864669110044660667614583576570496399103026286828660558854973376227247132815728164629722965145778698957093136175449225024685874279280018547740'); +INSERT INTO num_exp_div VALUES (1,6,'1816120.848909727306817960620941575637231136442992819290405125420545200026620306446043740992108329883383706060582482495616151605111275635501481354526017831484915013545483361715432312183101964395505340188909970344423950565285639911521082834494088840596716495422427543520536844348040681236845850482165744696068209384509064196671206362539077218412355776790921130042376467606683622970728503408501481791356294886150690067651815776445750760428874351556866105285911902433352126498951242195408782804314174041618879250740246352525074791310920062276490422853700893340860452528740673590486626464460321410814395342850270921486724297414692313177440726749004398703147904603937755702369682956482832074779404350351752662820773690162594400557957241676636030332988289683112176900913522668426137377289536793838959751008646843014106876005'); +INSERT INTO num_exp_add VALUES (1,7,'-818934540071760498.60459975022373923760152136399214017262844141729040109985386964272131706381326192223266583769046276181472898406504104649192224392653722107164485675679551050629376558940966195135841284978096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (1,7,'818934540071930985.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (1,7,'-69808760806266041400340.70700818693892852138813934414383886494691670042143650609934777814995087699409404201920249076407981012095999320858479644760715204999741683528746097757549835956359129287002171391961763797857794730120426599135099619822532290339000466211195776337667123320942107370731349851576864242697412616810236323676004067839744992733887503405311090677026008324895177587064547630828026123718296429295638934384446325302964896473296829265805737112709269803814942537657996725913938408781715328945194948010970'); +INSERT INTO num_exp_div VALUES (1,7,'-.000000000000104090609479936344103210175655521317012597986331111866307697262848964666360492361638117930801818899121383806224630563676018240181412174154250663423230239912527388431901852952893943812666142740182651125508583527237123596541789628675379232473721293630968882045044077795828674268595016625198802475186587918019739056755398151182369187670251750080227679555002307777300392769289647975058449905106584837938556260801229545589323224752038795423164214112897202147313792076165011373139219134850954217300915326944185918762838321705825423789073869940092569940135329697980600082436317664012683589681419530904283106912171330819469065141821685734295058255484933744156717782754922568796985634397878149984177882018261742637463462647452140104146195353696596211873925359508622779658904411330975862442989437933211964821'); +INSERT INTO num_exp_add VALUES (1,8,'8497071467.03603749330791582407836434318377133169438097066269854720538319012928851657498035372443556191720308219530866834905045144302106406146277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (1,8,'-8496900980.24523699375539429928140707116805167695126380524350074691312247557192264420150419818976723729812860582476663647913254442686555191453722107164485675679551050629376558940966195135841284978096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_mul VALUES (1,8,'724311956372274.0135050255361637906710330203036651743488213007179039756514944640108625580172737414192938789413338554327986697518463087452612658955180411327002900979574347739956600177846996063741787205122007268468674386396156638261992679442768654367111433834151087792255469957061758837789341439211010331332174981459471333376067541234901538285101103690622656631026001337239036711179989456674399137008584021283568040818388709554256523118702728176420022080138548890713013682480239784198421500241995499841675772793497485550923152267616622892846304530712344886979674416990935007952941652591352603797627920865960622077762568060903908151958000'); +INSERT INTO num_exp_div VALUES (1,8,'.000010032191786198542900505683562217892317481076466949299850809276743457759270150820565375820388277409258249926696079166209409657808406245382887790534127749833677458375931047385994887406206232330491317602830654688957983804698568410728278089250379255157030886262396950539100566975000094268415749476738358914633948867977798590927055566888255636132486899287919515638902721543629183577900872078173883974905921239149419877613723476347774771230668479296621531969573505480695490386225866950545725121902534610730154727385072738079149623798073810167706094070842646222833137345669922898403368997676634709281456818189049718956207208697021706186341405575300648248555331280690778367620868775005181264547924615247991795542738868003191757946979714250339430363902549866892041102771965653407197094250270379367437342632741280710'); +INSERT INTO num_exp_add VALUES (1,9,'54948723.74225051983134098996071145685528795757427462111901537365053896571438476055974853245403475510333627298551845046116291696445177112567064282766115207407461565363967417615506303416694032848457927390574251904212425813072768882213388082765916956736282110801611726537663292922699021333445658549608928179155685881583228490235606377831724593358583903616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (1,9,'-54778236.95145002027881946516375418483956830283115745569981757335827825115701888818627237691936643048426179661497641859124500994829625897874508497095086558766563666622720535497438693688376602804651302002795213923698663694204683995198328880575615535181012624198813873609885725228117274934655048553507421448724831939026752650108735245933317237310133362383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_mul VALUES (1,9,'4676749348240.390309875431213992853550297086049749814750492488995108783145961719774217441193547534210468967573344456866203963659951312519988497979489304488948342258375915152429008993288817366720647491166024151209542534474867042837694499222928509320280684557676243780452100132238968233413333851595648146954975713386711764268506890884764704949969602122157394714663532141060559896359465918874990769222345665160127552795532197771168442486088776803398878354288847069602460071745966589164282641033852314335279121191855487126430176047553895892632834940595958394834437871886013513058514896870683979585091413977173250824451205330441299000850618134248917380244749589254309567551846327349592529960432446947239714236828401206843011440433362544797025114476612133622499094287321570559088587999417440664282418005102546343020409520421747216'); +INSERT INTO num_exp_div VALUES (1,9,'.001553736563217204408368240901181555234014339476186598647410198373122572205209277343865051610898136462487966496673511261433286284257044548634547569923035899634327495195510767312478861719221916387940027268721306540663743713345337497285507595251328382906111997524508729275471287648008479480805967901972481289402930660848950039779707354469389216931774094174326513465502460315792834278614886136688161679443873815113442220055827192996984074129528034845339130162104547166079591654852164993577408422015514100323825529286511720963047269483211930770803479398243069649400360625259869765138545866815758888670363356947311319523139395191102286838888146829667276592755438606664644975648828848738708349790766370694194763606850690923803984129157519048493985198591771429264967247245289970213262206709011468289046840862597010969'); +INSERT INTO num_exp_add VALUES (2,0,'-994877526002806872754342148749241.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (2,0,'-994877526002806872754342148749241.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (2,0,'0'); +INSERT INTO num_exp_div VALUES (2,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (2,1,'-994877526002806872754342148663997.64812998474240514147207095573950146764154822009863493316394610578375247334825932838513167168342610420582834742950389452212867974756590355021495169819086060202117180229196935525386766373096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (2,1,'-994877526002806872754342148834484.43893048429492666626902822775522112238466538551783273345620682034111834572173548391979999630250058057637037929942180153828419189449146140692523818459983958943364062347264545253704196416903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (2,1,'-84806738323879544552397401815149740513.8505875535743013876823142649666132764556588225959336097903898464616542203793600590311980154402068027051522932586050753865288419084437796768749509032177577451738712965496693249429231838833655025794915864261585848007162358912070811805298210095333433397862313304655108809804359760907473898420016370058274978588765092161529583480924554820756527238472641797198545539410039895140087686344382628317530286295498797849942258314364503000942821309916954725689781458590617068629906894951122301020797266469357701283289275708774593896770378558232444454118891917258610753077932026885574920166837998049508644891327208474213193224700658584824407382455480657734911543930195324144216374573825'); +INSERT INTO num_exp_div VALUES (2,1,'-11671021799770914903865020509.301561107153561058074179843542446420696517132461554451075945807420674211966679216615407057626541711186781735967334896541890595771915856783008831770988426637435694856170266346306640678577376310547806764332837625966429200996250687908930748245035578756314083608655163891041399241377675534416837659335561005203219889972336214863417948542956735403991871098341470996860469878038840964359144637726669728240650066795729910649523281308716277906908340457162235831526838308777581569974551673352306004330423694524256415657620427590352277556907586751621496248973165690360552007637570957980230685679819820147036159174977086193494572117089582758015847544798464543446227632367713941117001423437766840744488426025388612316819120660814681298624293065972395923651314350558006567251033289878238407790871784676348196394482477767774'); +INSERT INTO num_exp_add VALUES (2,2,'-1989755052005613745508684297498482.08706046903733180774109918349472259002621360561646766662015292612487081906999481230493166798592668478219872672892569606041287164205736495714018988279070019145481242576461480779090962790'); +INSERT INTO num_exp_sub VALUES (2,2,'0'); +INSERT INTO num_exp_mul VALUES (2,2,'989781291745465665243281323944996915810556285052564220274237162526.1617859904902612197894543199389468971679632139059029459520163585971122643624316475417489000981872666677202334180945949860058384424993911721081868337499377890298636260338063268639283065887210924895929155083478140340889209440025415565915964293989840603863813531303253038823629712989041722072693449251635519992922148998556112923060331794396659338057474019846675262291146025'); +INSERT INTO num_exp_div VALUES (2,2,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (2,3,'-60303024366845387174536638750234506721.2758014749274942132576365116182462208228193753118527959000939070820507877345194783035668195137119648748792386548310474079340204536236936213411512867171486174240518914767934028451971067161683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (2,3,'60301034611793381560791130065937008239.1887410058901624055165373281235236307966057696953851292799409809571799686645246659986351515277852800926805119259053513475211488115663286642009614039264484259692394657121785950542874788161683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (2,3,'59993133911282372667149627097418449223835595194300848703012380022306762.154418449236691515146061305380465061074531890529497774836941002526095632166401249277270674802626154774328055399254982998368191676630276960361274433270795772477146870294928855773172789856196219950097157391050424577381777627004101100872747943673762087675405200265837631665464736842180920496158545887039337399558993437594084473932658319914390365451919627956823980800124880375978662052111797881386060353490432427832058851094210488804887183034572364751639107535041308434932952695103493677600969712634416241541391613699710826602011076372592299807609658979777598672141389319098817824624950794758296679318319299142035'); +INSERT INTO num_exp_div VALUES (2,3,'.000016498242835741013709859217005931279826178662180173096568520102488480129191427472581644597420895622947234184547373944996197105916093347103336318249582032230903680989710242610024298937774441533502282949127537125997753002819456724709929935850697744632904111143787011103837624936502324835260843148595669524694347566421203164808527739207590986975750648112133699756328511947175496694080071202064255118777680958612315513441989609682655431197367166056616661045712867189326408877133865572680407329449150282415810958772293869902662884761202424695742898573841869524376684740249281181605067345203479719345061595919652192297531638467223956758315591610733251562492794891852151639643060692698365496208796638230566761231611376199140556503620471090364900792180618741355091923808605890415081571900697282725022629812561702118'); +INSERT INTO num_exp_add VALUES (2,4,'-994877520673428596810678826533995.79421257464236160757218576989993781147390382997132644206786872350652200243563770552469933194637146474528320738725486418004701192337175478117026439697031462361180324038544450723753402846519731908503949116978812841497201119103409772457270340059605961197538918709309004130294868847110690336360689446090125918336908930881873778405661757289469281163974774492810850778950071063044769131228124355961427111369335109426492177657001035045332525699055300921341010989742896430768506909949340276549373661076950964959025967328861569387160956730002517417236732463510495205173523163676450203614971844583064927040066684531931069310935516821795449174271052747559395296525950219449541557191520903507653089998307641491381797101485104546410643'); +INSERT INTO num_exp_sub VALUES (2,4,'-994877531332185148698005470964486.29284789439497020016891341359478477855230977564514122455228420261834881663435710678023233603955522003691551934167083188036585971868561017596992548582038556784300918537917030055337559943480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (2,4,'-5302078674303935968062773235453828254014583744527466365136.236414807326868572353809920518232561005161225922028750078608989965741402418802255050636954800114792425419735155504035469350521800895164087027043476055514245942961100610551646034472084954313670284875310691807937254054948742125729353864014122131419164449567115006621212424805182687707372956385102095255735458593389920872596796806885847543910224476727171570873698525606016990229936284811067826588349092841322512643043008589065847223683467371925773023109720951609815041012521485326120380123169545818055967455575736140138663815073081494226676896278654189873597341203197903408668523514375373841493189836809506003729379742035629498519683885268256481104619815130659628225053833297766479068686119691010593208135616363994230674606991733148502293102108193522604968743948323130517040609601859735899914987426089053869350663'); +INSERT INTO num_exp_div VALUES (2,4,'-186677971.517539861245390308778107722315862721823627804195528485535806132067679059453022306691281662574091826898288146790399178357754908901382135796783067563944022498807930452234032896817601590728156392188660701355670595952594500812333935362955625137944589981298793332621503315902294100258945995827423279442031218510259915311555745581797315793010762585658196457363672908315687720174516274528662385172326028870945153551774300419158584379602045442200523311437013776079979639415633358878239012925000523542907592866797199229858272764668664323316251874027468128770456766875866492004650352654523634716923150212263912760225390093339729495231675627059805624175587380165509763048913150826017167286786277908970769297060278191518730887417202276531151575412404467497036737825989088867451153485938272367300939127313445244028528055624'); +INSERT INTO num_exp_add VALUES (2,5,'-994877526002806872754342801504871.47809095279915423939648794226185974985600242391612965412218049794216637114648812993201775787765690351615479957141288239552036371132381627958673244764559862836085530643408020551049895730005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (2,5,'-994877526002806872754341495993610.60896951623817756834461124123286284017021118170033801249797242818270444792350668237291391010826978126604392715751281366489250793073354867755345743514510156309395711933053460228041067059994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (2,5,'649411906691138274293985410502516861224852.2323455192714410716272307781034189160865613770320102043319541634113746032638191509585045862973333645830298922352816245477556264222094036953195419857712804755170632292914187367964994214922001758104594052499795564860466055599417895782179851297585155129541589802249540436678824225950907268084876110445460948679383611117263673106597132046331719468816839434908155684738864149955129235751738204036443603521478609787295079710078973503970964790273461142497259987849074597264522099648376356902360358310245001183020992360260836105404118742418040965190000718736837422434593694808973939805954329718232693154128543253581495885789333274488461716809104532693754070810202831113003978085636579574171344721710232931261731022478029314435363413498991740750878099825781577297965642009156858479681236085226911858782115'); +INSERT INTO num_exp_div VALUES (2,5,'1524119409495532727030986.638577103454261465522025182901477334004986357902177024959076085490119358611626688213654669281670407680244740174673394111775678935383154847014211641601227316639834450258566053805263858706381900273201146454036688771735398324537667996974210741719621449948660517037619359095556637235980122706739013220201060795557114248610410815988952748489854367480813823114296393315170621979351958306734282429929421779129764262568942699813166237466796852578307944635545174715298176546980314973426586923195248536376403319094417073026382024413817222396402299695717290716014320518777088811749776114378145110676170242861393274018655137797545194817703831240390631723050378397773341835222892981773205967439339460305257986693600088957772328044922955990976285151896366292514128607363007421484320868718566256882080399264346243272770200676'); +INSERT INTO num_exp_add VALUES (2,6,'-994877526002806872754342148749240.99659316232359475297606895243958507460511031229368344962653674268847910587702140353344168594152240599109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (2,6,'-994877526002806872754342148749241.09046730671373705476503023105513751542110329332278421699361618343639171319297340877148998204440427879109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (2,6,'-46696638263247522384986521136500.479312417066793299922708112595886608370451213741279484136907754744903470430131032928908162742687359367826808123516519335458861613010646992354378739165872253762686683966945711430182491860196341344982195078000259063231136011430995647812149294224699587849791008794261026932467933475782780'); +INSERT INTO num_exp_div VALUES (2,6,'-21195986018643887410662481595901800.342199657994285865579781485758715114242459388977583220756870314514884887803267837816669111279417861218648323488364513921592045485003563036021370174294475403630933854767386355037781881144701319212711655881277140183173924089814927297045029394618083349813549439341772734606115369911736164723942330187830605893993276674913563980890459604886172701331890746621222114280438198802989678877404376001410627722336243835841751052795437979198996482216031399073597399901975686733315751292369326904428230195579137225651689857057115970784985439417129044974524632220457594191305254649113470116960582543784928547885740020507755033347968928034294570497118410435615856155184563329718831512839630769097935523279881940380220955993456451396417879773380305142918906742431812580562496634831735169817705720949712410595406012323294829461'); +INSERT INTO num_exp_add VALUES (2,7,'-994877526002807691688882220594983.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (2,7,'-994877526002806053819802076903499.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (2,7,'814739569184924399102711674444306584731316176345067.39834031417849342571224916231092924046722938910652929295271097903377854123984307101079073134405782275535446337229706620713104545454319555885847481531722101704765783025789147453570970090'); +INSERT INTO num_exp_div VALUES (2,7,'1214843772391778.127361407585140553741220126410637250571020684739034685508176000812180032686291124045768750332493129822580347351032145964983629059968936201592138368806173099130176852606440296388856520582890650384142745607345709716826703676313341953999327129144154152914234659001555055379537780751567782847296067128932113870102563522810980359433259696591977617184951677390423898232135100000764121508662830515405980450892222598485287609657612482190264517684867291774820716746063133066053446257163185646067618679478975882247893469409405379034723543061767846895135644429012095930584952053545016706315299076691015196261253199176743281648949731423486208098120903720124071047872917636988241710583721537777321338769039241700203546247947405745989053846970910400831817998342969657501678430211657755864160072525313889413731419647001970593'); +INSERT INTO num_exp_add VALUES (2,8,'-994877526002806872754333651763017.40289299098701084219066388457144979069028441485513418625082363021182982914675513019536443438529749838106171095037135009526312783302868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (2,8,'-994877526002806872754350645735464.68416747805032096555043529892327279933592919076133348036932929591304098992323968210956723360062918640113701577855434596514974380902868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (2,8,'-8453460632655529853033389979024265783461224.3195241893307807116624750282852146303290708492834695194274289713076935297734670940696121761483641291930931061232942894577813178566088927221374036301485916497770984757492912292002695944367308880163698595015497307574177176409203214324418237020500352652934909632442547242092296504047310806151851207329042221920888326000'); +INSERT INTO num_exp_div VALUES (2,8,'-117085929036205907700251.219065234073336548829793284434494573185718678644093751558890746941383215425734761534822966779511801033216479269605150574332107020180872343673157350081102818832254463561564431056604957702984438484261858890324442581609284935850435611342611117035589511568432559140282381526487115307554496353616929034919886387903446436924514812698404129456069856633480965357915969548215985452939172313964007318881987188665231550330515412104367728617802960792164260429920719961650164518261501571220901151359208484337831586551714193024143212288426326740373893030225940355268499071669300664200888186064836443459131985786957267268845966279576380786883200277187591448294590370986026461176853573555996139940001165172158855197070946665074838360933025833716166930231164328918316437195201546383664484983447934244744303265471044295601062898'); +INSERT INTO num_exp_add VALUES (2,9,'-994877526002806872754342093885760.69667996446358567630831677089993316481039076439881735980566785462673358516198695146576524119916430759085192883825888457383242076882081857926408611052522393579396644731758241837010163568445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_sub VALUES (2,9,'-994877526002806872754342203612721.39038050457374613143278241259478942521582284121765030681448507149813723390800786083916642678676237719134679789066681148658045087323654637787610377226547625566084597844703238942080799221554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (2,9,'-54582443595378013373024060492546032003692.4875677735896411267274323339692558458420972958075073392126734000341372096298914875892612108329218081214550050039133117695428196702128258481789017059073444323729583900855712795086447886053552786449313809589992185978097430132940882612817775035217244553616977182049775786664446683332098226841743818600819221587510039430478859412452506872131851471967577741190323481953867845129745440745526578327709351120432530702446916035797432129052518980799424635406993848916727957825620638983706180841278402925286540375225365057191075559133035'); +INSERT INTO num_exp_div VALUES (2,9,'-18133693300409132895168796.074616314168631402221003009151140409826855230810646429042722071403306917323628118792142878282108022292754325022530103525285999179488507720688317761243448898240836430183645778132937666952111134601563043980164547020295727057908447220163534134835130866457657964382363853570827467081988390359191484798677813656413640874450449802233520570178139244957518604566383671867773821069602665918688868868894979351219381089954104823746091972754649316823714354000113723793845707472924569647945844436702275724514171940901057842455729977729388911537391920702753167125695758365521631000334183494148229356487592577177344247694925635113222720411958290166668659311154664393442690740373285505786584987609789805525300762074682544164213490532272590665630428583216403362629445153016404037983825555019274338559686335405719430737559715778'); +INSERT INTO num_exp_add VALUES (3,0,'-60302029489319384367663884408085757480.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,0,'-60302029489319384367663884408085757480.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,0,'0'); +INSERT INTO num_exp_div VALUES (3,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (3,1,'-60302029489319384367663884408085672236.83687099063256754698860828386302509843815398979402006244388708674093244201278399438376682321121138429850885935540924586964982855913223221441591310211730902799041126800414795030815514254713522692405212716783388698431088814919226444677188004928663343696636297536500970117716818423689175692808344185016908913828066250587407384563498516598672584120143890364303296142744031320345312431817858545326010704685255237541162931904446804064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (3,1,'-60302029489319384367663884408085842723.62767149018508907178556555587874475318127115521321786273614780129829831438626014991843514783028586066905089122532715288580534070605779007112619958852628801540288008918482404759132944298520148080184250697297150817299173701934285646867489426483932830299434150464278537812298564822479785688909850915447762856384542090714278516461905872647123125352735037721325154184406043613668806975385533851732090363979459292404685190942209855935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (3,1,'-5140349743195574373979577554212527512597024.162480344833040409158673429491690439298506850052285119390701002577176786023622062742050099464897084793357329597395417632908812044304066963549928478520702505283307379218587635434673128958824348493758429380623577527186462464399974242800361134191519694694139153279582776168995426125926314513926640766117733774558011741611075336271613675760116784769700605008122422944290652448956922432960815546502965310676913079866511016221573557684245901002643719965652152439520727383305120298495304784052489867651462175349450610643411043707261107569691076730261762793560088893354750383257372118118753366377402045596735023445172252225346164608897913115394905485106225627590643805003075069931177395059698550161546962768768895596088478488887530518018212441345360153523733317120037436403475909117998647781920105313938836144009539683'); +INSERT INTO num_exp_div VALUES (3,1,'-707409990019504668223608170643582.082425157530076679823177950190511141917761066423266390864536360056345386873500583953954967225431526056199231768143978526582904071798714789552447782850723926323452633811653766838064983821149041415149067433978085927687765773012158659685363079191901396502099956189371719135315616249471739677995520904113581848295732911534266040260836644379296158092198514963023001686666281725991605685524015227112003429486755206848316731257322742428352116058878710728614841247581716185886403744830796740424927494009978599974431617064012221450054532987372285996679180090592706458366967534834069977644215413076082570497451654516268857039718730203921980307096740864747006176117071983875364434497517026142488015705391255750729200497229031250705777282987863242056223584453312226818451807347197583925624299372040413470456696588043062815'); +INSERT INTO num_exp_add VALUES (3,2,'-60303024366845387174536638750234506721.2758014749274942132576365116182462208228193753118527959000939070820507877345194783035668195137119648748792386548310474079340204536236936213411512867171486174240518914767934028451971067161683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,2,'-60301034611793381560791130065937008239.1887410058901624055165373281235236307966057696953851292799409809571799686645246659986351515277852800926805119259053513475211488115663286642009614039264484259692394657121785950542874788161683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,2,'59993133911282372667149627097418449223835595194300848703012380022306762.154418449236691515146061305380465061074531890529497774836941002526095632166401249277270674802626154774328055399254982998368191676630276960361274433270795772477146870294928855773172789856196219950097157391050424577381777627004101100872747943673762087675405200265837631665464736842180920496158545887039337399558993437594084473932658319914390365451919627956823980800124880375978662052111797881386060353490432427832058851094210488804887183034572364751639107535041308434932952695103493677600969712634416241541391613699710826602011076372592299807609658979777598672141389319098817824624950794758296679318319299142035'); +INSERT INTO num_exp_div VALUES (3,2,'60612.515523995516156897729403721504966784736064970538891936016753206905080265887046037910122269129293912171105589512464185386239562077778499936203155976336284324712221812806801062157592930664021782540155687632208890794166119782594464410498356083266087045927038416810562596141871858142749062925965665039981381277808608946877852933015970874447235220989360704166270479475802673572039541121473138382812420076284458769543418652217394352637294823914346726065145538710933281768776286965107974980550163605068693568717671571780028113969794125200592691656568731359981803586296135840575095063824258761205175762907549288801963550628589530419118771779395037240198270853609924445368393952404606326559485235840170339343865253618184271158932135392539396160392488927771488269959497352568205940636180870805982484030168838833607478593'); +INSERT INTO num_exp_add VALUES (3,3,'-120604058978638768735327768816171514960.4645424808176566187741738397417698516194251450072379251800348880392307563990441443022019710414972449675597505807363987554551692651900222855421126906435970433932913571889719978994845855323367077258946341408053951573026251685351209154467743141259617399607044800077950793001538324616896138171819510046467177021260834130168590102540438924579570947287892808562845032715007493401411940720339239705810106866471452994584812284665666'); +INSERT INTO num_exp_sub VALUES (3,3,'0'); +INSERT INTO num_exp_mul VALUES (3,3,'3636334760530744652235488357607657374520053530993537920755375319352615385278.023608692512217812784472508939511216316773023870624171279878340621219698109986095090336065266376220109007718694455520948311677863167090936408887147442375455695868593092154861636486745490748828207939155392396090682312136290864359484540126174821846208064763823279315343506148025281475729723686566174395516982893064510403581479746673749128344955124070957545815390178764940816628194640888255387443237798761377617383817511745005525149990207764725040109364671749403389999498572538135588695345112358160274671918953118753964073105250116426665508214894805722798842017943220605600452911496071424281587802689830031742105619630787641205011894680546049982654601956546154572720177337696285354350903475239411654436042931409507429892682706228354459580412759920815932840348933425754970917910500027837428631661182510071352138858'); +INSERT INTO num_exp_div VALUES (3,3,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (3,4,'-60302029489314054989387940744763542234.98295358053252401308872309802346144227050959966671157134780970446370197110016237152333448347415674483796371931316021552756816073493808344537122580089676304958104270609762310229182150728136567294798680824019082599362332377530165818229609055765904048195574142709698758095302560470195171027219786996322461803443213101532716728918363951912367135900414238535625075942525108530051828834829820554490477645701692374399416239080329365045332525699055300921341010989742896430768506909949340276549373661076950964959025967328861569387160956730002517417236732463510495205173523163676450203614971844583064927040066684531931069310935516821795449174271052747559395296525950219449541557191520903507653089998307641491381797101485104546410643'); +INSERT INTO num_exp_sub VALUES (3,4,'-60302029489324713745939828071407972725.48158890028513260568545074171830840934891554534052635383222518357552878529888177277886748756734050012959603126757618322788700853025193884017088688974683399381224865109134889560766307825097103477790782590061456916367930139323346273315068375646692125800496305291080749834712822775973790354498408104142209966769395239768969172107040437333428573572464689550003374384624966403962290572373571842567623422963022155546431883766327294954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (3,4,'-321372325955692885069615337209737469749246561535004445508427591.072860243358366933071485495726715620133686420023451450292996945184959542770492705998350644739298629407567812798540119555932604687814429669592481327761428042980782672136901602006622227365754036664912989085940235439697789102358431343119457114603363936544931303133371137532006899162833369543279729021228901466728220729625107362063321334489394782322741444425117731922691457341543446841167138481424319752111748042440994701571955325673470021626946676976482516292402239416632497972073915818846704053624707839813514171497746804751780741682011937606462260710753056669269928580460921188286249923152921382198282201761171043384698319895970192114563900025573490442674225227682235790590616707857188385274186584856872573669591460447105688151281208238908470285147895678001948902280493477604361481216667716971590499226735103039'); +INSERT INTO num_exp_div VALUES (3,4,'-11315021446594.877643290091276308982961654569173523687151347727612592478433578066762912541361898899908505997444632820107356713116459078630334224890355872486337973552333755378190316811715776951317058334754704988120078733912131691682869448731717816749620336196719541702138949084375907248656748314375183301372633028246109596775255074617515860012417935744433243071057057560464360663978361945666099558526069794464437818864063206829678640156992474597480916575712563493776637239091589972373682399519931569163592317107392231951775499293572134702843085474656152913351183535194499521618027894129537558509428098859715020703897463518891082573242502356303078754574312965093639182648263511466558336912294702019648266054331227425119096294871153811412169351624751542166779635702042223762951850816568617453355571302500885410532963789364822647'); +INSERT INTO num_exp_add VALUES (3,5,'-60302029489319384367663884408738513110.66683195868931664491302527038538338065260819361151478340212147889934633981101279593065290940544218360883531149731823374304151252289014494378769385157204705433009477214625880056478643611622410268943757215673170753460135411513114716313801477916713433956086133878890802448531292334570886746283905390661877220497842493537338035961123751393889400517474762491881277080205381424363695095196058838349029211365212855028824622924678684631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (3,5,'-60302029489319384367663884407433001849.79771052212833997386114856935638647096681695139572314177791340913988441658803134837154906163605506135872443908341816501241365674229987734175441883907154998906319658504271319733469814941611260503645706198407368762270127105340397375230875953495882740039984314121888705481484090911598074635434289709802794549714765847764347865064280637851906308955404165593747173246944693509650424312007333558709071857299501674917023499921977975368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (3,5,'39362489275784146262776411377472433635883331946.794473520543457442955620133347015506556162839462623905489255080102447195050109095701660164272430316804466254467810714209179752718730906325952685817112992943656292503112803950215110778476301809440329937774061163668461957943313261962261081942055908935814323069621279128270849852239727888939033546870208376394878842958202403235309372240005941467570230067124830916866857395233038346727879951123599893174252558078732888910139309038957525961212820831321973219557165558911222848692996406741318948607549825343491479728117062814094258484536263158005174429922237853707635743736923521032098496725445243775790161216159399180889906705265012270270348146530113428221072591696851818281866095288773371414866822270689959827332258348570976075184933893434327278299820594014788148344260948638847457822697682605612771344335201258128'); +INSERT INTO num_exp_div VALUES (3,5,'92380711368470856513514428781.033155715252174277753317877861994356621252232374386687048394529670637693505779282500567256835271428113529026462111032257747830329068594622091282098767000694818101994264352932243278144124687156236926607422077479412495979777588932692081795130282128890441931602671468684153168580234070246201722180460130467506344034452687371838907269162119534950946217165384250603250357360223255177692065141037447374172264943732616165429783010079281851748804739433821308362193703012671569249508710820679009084891198169587484117171861141580870066764275087111843275285564262902405980617569581840831518012986031156042600391943605532635833608358301306456966765206853910579231447150839538731157206153540873916893579943906851149770881336811951119112558311734171557608362620988555075663589827484854016702489324791126228380209309587206299'); +INSERT INTO num_exp_add VALUES (3,6,'-60302029489319384367663884408085757480.1853341682137571584926062805631087054017160819890685789064777236456590745415460695320768374693076860837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,6,'-60302029489319384367663884408085757480.2792083126038994602815675591786611462177090630181693462735571643935716818574980747701251335721895588837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,6,'-2830400711649493468815157129316992649.40542786074520931471973065281957756940496588853021620372179463538053123396140685749478530925306163968207226329985017644835203709485594362663495728106061878665324856417118064730721101615473194292620972173690618491026470353143141125614124440035267592258385099934706896692953497971326605145704135723011753705907329979207428661473172503098296622281647255008204864404416199384701720347319806375450632245634238172654086373193251877533131784268854289406126119630708578053354762596511353053106459297339360827562281168219966099848212'); +INSERT INTO num_exp_div VALUES (3,6,'-1284742031601444539630782308463065726620.121021225455596762466053504195700643301310745151565435123335541550963124666304408503436412726848834604336377169205828654564329888653766451656774534718709065521243637375270687684572524302099749018591530352756390467862377335526634920857924031482455373589053524922608255779040656019538392173139295812160325688504210040741075388404155144782519528791757450256668977268409265390016721724966592135644698341754332845002439113523127047593325646484654291494607100188094186116001064043796216982681807318598789324900462932294782971663150070521334398542559480877366424630693734132836518604260869235580641521264976411493166969530737254118968281271908306432918913600567757535151861421384835424322504855607676315840963696944683182767935565256136130185809101891760917733694553800748568697830680328155128016670099315391685422333'); +INSERT INTO num_exp_add VALUES (3,7,'-60302029489319384368482818948157603222.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,7,'-60302029489319384366844949868013911738.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,7,'49383414785234649002982046297226894664526726187218771083.0993243619030008310875293647868815940421844461627295157812843657782639833900543200310573708100000958929315945039020410482966753145208427035917753919085618457760620513481628641658765820294863970581642745379331727722585319163262763708386199720411053619449096019862596221607526610103408936214184850115071874430846697061554769773328338028749631552202705583855831155461651414320570061181212214810086436100771547030013079997847086'); +INSERT INTO num_exp_div VALUES (3,7,'73634737013325927185.787791148221519354461791539553527545166847382784629235192342551464898036004011575416717008403527685470842765455409054592207142526523023201841973047779202013398235864494503216973882479116841765663948294836180515686647139678530220909072497288527276378202532400736141014848907023234659020093073127450778982904578906877634654521825977382116752537063128793631412296206704078569268566614023846282524151679028060869175439188773864994186109445961525301841201265289707928211114515861536069733921800160245586536759625418951427346236213019358749196674633237197452976517130405065120577692737021174118093373953642724512531935525024447977867020930500433287279183436509990047372809400167546185096048971157700858970777301410692908939206693154161335335755844997198191427289546263182822280127912118140820265025555165337881999926'); +INSERT INTO num_exp_add VALUES (3,8,'-60302029489319384367663884399588771256.5916339968771732477072012126949734214868901845505193155307646111690097978112797961939995859130827784737422228762767014427842766445950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,8,'-60302029489319384367663884416582743703.8729084839404833710669726270467964301325349604567186096492702768702209585877643481082023851284144664938175277044596973126708926205950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,8,'-512385513828318260570283740065493064477880918352.732624553690077857674083796435724202494963885926573907185100543184828131859183999195040110586155435203949963570735841632689374488877298209082579317039061893012560130258753218955057387206477423088065663401594359617882154814262843273526859406265633827109554791772242178864873774889091687515990672487380368975556580539271333144212685871370972163560839446696514092637412587953506052848750866803569213269271165856310101244342151576488190595936869490659700946174362872797854591188391982770203203644172999264143929484089237665313698600170041324566984832357000400'); +INSERT INTO num_exp_div VALUES (3,8,'-7096872691348467943606706217.907270287823269424282176534343841939501231816905820949045946136373255017076943323578903040918266385724756894003692978391468202345397178445216069294845721607024056189567609414049207292919519881725733381453217071918292453682942046440563446278374996563501512335133749731529362537349288419883140401056747081065947774593869673146309163791076953204291951821124894409171722911526435445719071769008713367057971351892550570642991097981458696464929009464411568672010548002196406312721789582428747564855324072212842315229302959908665089850886951261233852165624100634055045684536311382452553544676139507899503993644452161529145849579200003677255968757773363970434791501820320494192909660871475590637419913907191608957830524390049664686282439567943053924245852983990958276537000732363895444894582579142752920882750130052682'); +INSERT INTO num_exp_add VALUES (3,9,'-60302029489319384367663884408030893999.8854209703537480818248540990234567956069965340942024890856088355839135538265116174644003927269495876835324407641642359213535695803871472434650475144516723617632059130297610134243891145006222068960999879308472500422640481972089756410157246974765071949782242392661524488959954348903412713930092273629207697480131360047867213863018127928853922173643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,9,'-60302029489319384367663884408140620960.5791215104639085369493197407183130560124286109130354360944260524553172025725325268378015783145476572840273098165721628341015996848028750420770651761919246816300854441592109844750954710317145008297946462099581451150385769713261452744310496166494545449824802407416426304041583975713483424241727236417259479541129474082301376239522310995725648773643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,9,'-3308379209762459471107480259839508279070920437.883503980178028214343751083865562028455061662673132221930429904398963590401793045470444301883103141901787466923883803951815572606105617157736442670792467625964359169270739534412932791178258858918086886061702512427989129732248215348301444245772127142869263635282888226326427510486246184233225114523636171202034558843515894542952126988613018789833835507734620046994907453602573865012044120483116345444810078666601100257620969379968264504287700045822481492526688635364586344704730579892342786173395802035361824932075736340405960099542224953439044947229246847140957298841482874444906129049023002897135347878048572628834749795298712449864571996898774444932083319581439741625832405434317985988163261591679157437224404970927012111196724239860528859217322132733404472897289'); +INSERT INTO num_exp_div VALUES (3,9,'-1099128766678422054524173986658.839339966689456265703816212189145237878729886466041806078542573981227645802109969871638687985985845489422516004202630099080709709893022100481258818112345013009059633421290241583864468453396484606925071369550998772875840640325758308835852391176503689677263605949075815552026731067384737231681068134099746550363063940273625924224721503126912810251607546172009765059506591787282558727077669973711491157840340631805422942099954647016059576777054339588421998882440726473698513560202030309804089250300097589174314677765341104767702983421063649104691583044460507666600260994707192787133590502137391691330098102374713996115782701417107878938473243874299874872852713499024851414757892169376458916467621226859152075901273014182163212783658933754507272478777304254191033562324994395916168496097385872331012258027431094381'); +INSERT INTO num_exp_add VALUES (4,0,'5329378275943663322215245.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,0,'5329378275943663322215245.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,0,'0'); +INSERT INTO num_exp_div VALUES (4,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (4,1,'5329378275943663322300488.64471790965256505869684245785528331091076155554650629138833809683459634328609777839510066435612911583108717191216693735823717997111970662575497378762952496582183738308720094529950793570383580785385569873278068217936841324404119828637880370718028782103860007754579779716996004352284614661690063919125301052941328989181561787543541920734755989452320799185700078241880935083616978140555713297241612718277766918005268951861880490889884082730841740604517529391011862694381726143520658746305661338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,1,'5329378275943663322130001.85391741010004353389988518583956365616764439012730849109607738227723047091262162286043233973705463946054514004224903034208166782419414876904468730122054597840936856190652484801633363526576955397606531892764306099068756437389060626447578949162759295501062154826802212022414257953494004665588557188694447110384853149054690655645134564686305448219729651828678220200218922790293483596988037990835533058983562863141746692824117439019450865871047657552800448629502344444081260036580660700595591338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,1,'454294299613767152878025320780.534199313974295807138790763501115780294529340799108297697573066187975311338382917022391830256203305238757334106943821060545424417350991354829668286194840925251162479496893943917530660694097932059166013476064988623431110002057735318529554555260199417935495388243829261809007709919225000608711536928171687251088217591210419208480251102484043683131687013687838713055660405381318396419588727500715930145098362997142075433472039319292466570912777345841400769387321465602989947078951135489852486382469990409873227894248208197179481868230244584527040573428134962626267135732247029762468417273891700661832893497067151409134724061246612631376075173287264787886064622106855886785805818642123776489793586531950438285720668411465570116161790343538663297713926678759640594912243360541590368666922379919514826022141331900181'); +INSERT INTO num_exp_div VALUES (4,1,'62519544780217042176.800424689664850775296526267109332647921183817056683200043718160298562843864918741523494444361916531159341418970534833628106062976341639276761669219281771109561175175033739624472497927501467465456946098280878993371659461957361369508794842102784763955539708800574418468150309301129490186416766691183270872711413796386178009615777589066235359283212636467980113350635181915492452697347977967985810294150853782607014649150457138118264698071689065469752702524632313088938504181640435324554007553994564705401249228914199354821595855823113730697333390936834057091883654016371107974899726642500486005445063301647520527084320363513388355471718583708935211830796440056542408492723718088396437530207347815505844074508948817594746824098278470533148171941442049323578854023683167934569551595335539887777638716651319134577441'); +INSERT INTO num_exp_add VALUES (4,2,'-994877520673428596810678826533995.79421257464236160757218576989993781147390382997132644206786872350652200243563770552469933194637146474528320738725486418004701192337175478117026439697031462361180324038544450723753402846519731908503949116978812841497201119103409772457270340059605961197538918709309004130294868847110690336360689446090125918336908930881873778405661757289469281163974774492810850778950071063044769131228124355961427111369335109426492177657001035045332525699055300921341010989742896430768506909949340276549373661076950964959025967328861569387160956730002517417236732463510495205173523163676450203614971844583064927040066684531931069310935516821795449174271052747559395296525950219449541557191520903507653089998307641491381797101485104546410643'); +INSERT INTO num_exp_sub VALUES (4,2,'994877531332185148698005470964486.29284789439497020016891341359478477855230977564514122455228420261834881663435710678023233603955522003691551934167083188036585971868561017596992548582038556784300918537917030055337559943480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,2,'-5302078674303935968062773235453828254014583744527466365136.236414807326868572353809920518232561005161225922028750078608989965741402418802255050636954800114792425419735155504035469350521800895164087027043476055514245942961100610551646034472084954313670284875310691807937254054948742125729353864014122131419164449567115006621212424805182687707372956385102095255735458593389920872596796806885847543910224476727171570873698525606016990229936284811067826588349092841322512643043008589065847223683467371925773023109720951609815041012521485326120380123169545818055967455575736140138663815073081494226676896278654189873597341203197903408668523514375373841493189836809506003729379742035629498519683885268256481104619815130659628225053833297766479068686119691010593208135616363994230674606991733148502293102108193522604968743948323130517040609601859735899914987426089053869350663'); +INSERT INTO num_exp_div VALUES (4,2,'-.000000005356818439105666775800262590702859770599410113087721172791624002387236505438218124867814437523686300450045582100868990117124343222534568799037421944272316277130975314766456260710406160143182498931595199129228915695802952695510723443157825968340043198200740606202264287904755124946591110599335909404657109057432686191440989434662797205973563889238804413861126260401987949920244286377128599413927273444061572120561496904543200956508673923547626768641271397088562966176629018606103663605145666976048261236691866387601532424530473754175270500777679603569715192364542901360534980926452487443629100484491344001509360344122933911316486556042277769848194790964257060927912344609376571637126617813506411190014141992988288983968823792971270853369317867326071952900448455162898476163801382836761898292684175721846'); +INSERT INTO num_exp_add VALUES (4,3,'-60302029489314054989387940744763542234.98295358053252401308872309802346144227050959966671157134780970446370197110016237152333448347415674483796371931316021552756816073493808344537122580089676304958104270609762310229182150728136567294798680824019082599362332377530165818229609055765904048195574142709698758095302560470195171027219786996322461803443213101532716728918363951912367135900414238535625075942525108530051828834829820554490477645701692374399416239080329365045332525699055300921341010989742896430768506909949340276549373661076950964959025967328861569387160956730002517417236732463510495205173523163676450203614971844583064927040066684531931069310935516821795449174271052747559395296525950219449541557191520903507653089998307641491381797101485104546410643'); +INSERT INTO num_exp_sub VALUES (4,3,'60302029489324713745939828071407972725.48158890028513260568545074171830840934891554534052635383222518357552878529888177277886748756734050012959603126757618322788700853025193884017088688974683399381224865109134889560766307825097103477790782590061456916367930139323346273315068375646692125800496305291080749834712822775973790354498408104142209966769395239768969172107040437333428573572464689550003374384624966403962290572373571842567623422963022155546431883766327294954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,3,'-321372325955692885069615337209737469749246561535004445508427591.072860243358366933071485495726715620133686420023451450292996945184959542770492705998350644739298629407567812798540119555932604687814429669592481327761428042980782672136901602006622227365754036664912989085940235439697789102358431343119457114603363936544931303133371137532006899162833369543279729021228901466728220729625107362063321334489394782322741444425117731922691457341543446841167138481424319752111748042440994701571955325673470021626946676976482516292402239416632497972073915818846704053624707839813514171497746804751780741682011937606462260710753056669269928580460921188286249923152921382198282201761171043384698319895970192114563900025573490442674225227682235790590616707857188385274186584856872573669591460447105688151281208238908470285147895678001948902280493477604361481216667716971590499226735103039'); +INSERT INTO num_exp_div VALUES (4,3,'-.000000000000088378091435340426596348183959201660680284222502095357746364378698792730669202270228092348823133529449019715406417264278615046537007844589547485282959556860316942508808911542109265489435572674031608663747132688980867386885961271358592278360097086532747883342438036287136994589308551796702164612609710942175900921197001888540314760352113821737014875886635147123114456910985089625906448913621495025509697742196814421833448856595853403450682101743559369637786458968714240975228615283970739279506239628546165569688434254286341567486905374255702980370754235630955328837646999003123103831262789115646588779721625156078607919060762857866951417867378220773543985422722165221371084387943737083254760594128718841665355053236168688218864433967871311858292181233490194833547273501436630325295640020916257836404'); +INSERT INTO num_exp_add VALUES (4,4,'10658756551887326644430490.49863531975260859259672764369484696707840594567381478248441547911182681419871940125553300409318375529163231195441596770031884779531385539479966108885007094423120594499372579331584157096960536182992101766042374317005597761793180455085459319880788077604922162581381991739410262305778619327278621107819748163326182138236252443188676485421061437672050451014378298442099857873910461737543751288077145777261329781147015644685997929909334948601889398157317978020514207138462986180101319446901252677846098070081948065342276861225678086539994965165526535072979009589652953672647099592770056310833870145919866630936137861378128966356409101651457894504881209406948099561100916885616958192984693820003384717017236405797029790907178714'); +INSERT INTO num_exp_sub VALUES (4,4,'0'); +INSERT INTO num_exp_mul VALUES (4,4,'28402272808100253242547006276715304015308580784958.804614276533085644370816876160290159450291717634111299841065255625515058118012211808741402904995080624675460593676923639082981788732031193774047612589113654423166826140872334380708795266307037944059108148612979119729408762532396036043629484049508789880964586236575769826806092391573178899640321403656891487586452524427223891405519836671312830183895761747460911777623703557946796784873885800089025388390522992806365773290733075927321101736155663727528284512100509273076328103465333687228713897893434161293693971954442699482857938492961830350598789444266860160794913830991304996676299650460125000959751177037694425217989910261807246272771711816326991282202653917488360776928533800529297474279497910326579608191975246060946079639658615178160271122713225105861574160788280907842327681375920919676063500116492292319'); +INSERT INTO num_exp_div VALUES (4,4,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (4,5,'5329378275943662669459614.81475694159581596077242547133292502869630735172901157043010370467618244548786897684821457816189831652076071977025794948484549600736179389638319303817478693948215387894509009504287664213474693208847025374388286162907794727810231557001266897729978691844410171412189947386181530441402903608214502713480332746271552746231631136145916685939539173054989927058122097304419584979598595477177513004218594211597809300517607260841648610322863666300637648662611916496850248528515936635845594390453288113296413254893687029540384176335735114863908372780241463999450547422213639667099644505472777149095004849805371205203850993689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,5,'5329378275943663974970875.68387837815679263182430217236192193838209859394480321205431177443564436871085042440731842593128543877087159218415801821547335178795206149841646805067528400474905206604863569827296492883485842974145076391654088154097803033982948898084192422150809385760511991169192044353228731864375715719064118394339415417054629392004621307042759799481522264617060523956256201137680272894311866260366238283858551565663520480629408383844349319586471282301251749494706061523663958609947049544255725056447964564549684815188261035801892684889942971676086592385285071073528462167439314005547455087297279161738865296114495425732286867689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,5,'-3478781676337858247983014311182511.567538638808357215203593479841446379226774481291286361639429856698999485760647422501864626078375852610019829111004807806660731243672830787729048847342063218718651165150612717759770504648306347926061960607388621011846314969634048226452709389995594961695723139571002939804473057725442880410434039783304583526414509590532906062732322732569475349107437896717416548237633532805602064623969799081086996320156575550896200848758685986331692388099427314008504506503745527468550106879602399030419569897808150076298414568875477195447656904373310322813412927463518325927626891046356679526447117311923853482118502868148386882363449163182892615259995945992014431502761210899772725227648729095696228388558331052524469604046072203605897109629560683446827492904111565278516043939137760721315953500281379039771826554155511347152'); +INSERT INTO num_exp_div VALUES (4,5,'-8164430956184510.184223536017248184022252663660196916321116266103608317725855237211273642694947892658721606226082017525816544904635887836163201565923338826779819876742736219975639586566502584026349778499211535661173597356253186281116862244165796632756909578140184577853088376334255860281874385669242675881761388233070861374295536603371778669602656670852115614651462552069294889723058758969660566508798011830996965570446030123780674316363670374970480994905368006454513642480180066435609577311074332150098288374616437489163254821095377348025470309665651059603665062887597814064136313866690824972464351274062540825405003954064175728198182815347642172934453828192850870808373638597839434504241236228591053696481146252072190903430582534862988719805163692697482513169856291048966811374872266165034373412719593685881972700171726777938'); +INSERT INTO num_exp_add VALUES (4,6,'5329378275943663322215245.29625473207137544719284446115519970394719946335145777492574745992986971075733570324679065009803281404581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,6,'5329378275943663322215245.20238058768123314540388318253964726313120648232235700755866801918195710344138369800874235399515094124581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,6,'250145412892811547138949.592621291590152419206270097656346630226508074074623894951308487425470437268130465956063593951784820669318897182831355375451719125809800516979013437732298382708070979871283132689492336823087794373113039154669229889503700598930220858275174342776478898670277868700384853696009897221747924643343353942154528501454689084608965009561564638167714973711022212547096732831847202912862290958304510651828842182545311077713664465815992616213663619529378061133917572474298028065850515876361609671565914027186063801852554353160801534696062207299890867876199323530337336273950892723090754719547285920090419070001019943385293110663922226230169381423410428577990604776655422105400452217085311617728003688836185608912367677734364834577573255789160419371322775733777518997638403409000055707558465286469808848200141192627396502735'); +INSERT INTO num_exp_div VALUES (4,6,'113543048739697485358574290.758354267447744932153707340542459183720907885610125346262898114677742971240785031722334497858930434531517077525413654346644836353208132641713415396062580605566225794048569430676355036264762949452090151450855446984773994337170590068740235544320694721909983307239491151139099779296496785240814600627140543144068640768857707110930453204162312973998304574796413938461971472337040811785231390930046688391955000749644938061585377150632133417156866197053052425576957646564943278156977176976876921235395711611898108821587442609611001702344783440618040704066809035404237786023075676374788819144406909313755996914145273176359246052899650387182222905558751208368173052381982668563471143298720677965028880626152749773712037769548408324298835212547215352657271696665387200792785056233953536347605130973626194099064678842085'); +INSERT INTO num_exp_add VALUES (4,7,'5329377457009123250369503.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,7,'5329379094878203394060987.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,7,'-4364411947278810125327066890819882483326918.05664098958260550284395870948992407314161088028674246708928421994893923699743452802989464864039994566042797942433140378990308345483670828497915478397481687305406460330009319949623844175096007381662809083363069100235985794575399268709260901964834244796150883807308976949196661411035264619638771824190014274817662519438658481432363824187693821267613212631153175155634316128036152465184903927860719447693468054624663668062006049759837326188252927823612718163916100588143128358998656306593393889422386501730237442526450419990376323903182669190482615734972147533221144682538647497701130447816148459762464395194383090936159579764712919396391813914821973715879062992249315474841639591907249142779103650773383644785606333916967894'); +INSERT INTO num_exp_div VALUES (4,7,'-6507697.520580964829176145824902679560705744817573189143227837387224410616222039115571544850095278317993922427931439719549137387753697989249394347047436951117850128104928719365703899136632100669607126357491484781141296021264049762417528697619931558728863308905257358126654378784709213859234056696519305650316810797382293500878834933984458810656133463638442959750083607649924453935287420620424368291770694630751828333903156364366745210911640207075765008558904788350844410055253643515389003711759818446776538393914018427075074171758415188027562645239606914126802490579848138218395145734902830046359100742374008993296019987093605275289913663224324033923096998194326249508491872193747944673057257521552387923218450155737056841633810711295424578984452176016198348344913655301417872189073133147510027427530833694019910340299'); +INSERT INTO num_exp_add VALUES (4,8,'5329378275943671819201468.88995490340795935797824952902333498786202536079000703830146057240651898748760197658486790165425772165585380839129948178510273188565692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,8,'5329378275943654825229021.60868041634464923461847811467151197921638058488380774418295490670530782671111742467066510243892603363577850356311648591521611590965692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,8,'45283653791262997781451381354094822.762732909505051438036873220502792213670540454778361182993875916509061144859281577740137081988678361247725064336120451090222456518107029158304937620179032477664627949959143233370320432203497828243297406462513350790251761540074946469824444452248386782451723637769289822576372357189700319768797708375563651655860093365309717823602754924352327588945034832436331911584742966378275504545736896430718939807674966738116698454215555860047859161126694019895490767779791933882712567492115664113775047192011252893773389940988533801360010782816196288710063568554147458866942816721046004257953642508395867837127678980002737669139369781058046396738606563716339660654364541530532834806205571191828994250708412638796240377704994928921528330863683630622922959130920715261879547446054261914770022377059156125037157979236658010950'); +INSERT INTO num_exp_div VALUES (4,8,'627208063620965.397582272040628872773601055303353339700043792111288801181637510303989399395425313995651311362368773096988861977687484912995632130587762386590996099363383976320342247076516604162469063709298438133327434461462906199160715395064249299615054970359309619951777972710299484596875999967582794277241285253106817446259313281064844416249524876385699646393555435017820686376877981018047574348711991428666249794623006175739581915209218834701034964043360823844816042368184094857692062884223864639972005010863342567608351008172649209459933114800143792514183138995700133608613158857147417653998048890116531052767737435620558349226865105888201598712435680481803901906613772821370519525404423549161696526405320391828194356063547089626322474164332505209233143121068245585662919687001395119229263995765376465304715643388771609446'); +INSERT INTO num_exp_add VALUES (4,9,'5329378275943663377078725.59616792993138452386059664269485161374191901124632386474661634799161523147237015531446709484039091244606359050341194730653343894986479159670583937529516163204904273806158788218327396375034882788180783796976731912141525319602448709213495905899041406302673881364465504945113279286939663215197485367850132991968081639290297033476859158044889351836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,9,'5329378275943663267351764.90246738982122406873613100099999535333648693442749091773779913112021158272634924594106590925279284284556872145100402039378540884544906379809382171355490931218216320693213791113256760721925653394811317969065642404864072442190731745871963413981746671302248281216916486794296983018838956112081135739969615171358100498945955409711817327376172085836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,9,'292388240303165948041827159734686.255558469787242316676287235194652580157149226950109397295920730296960145548003120827363226435916209781396711693581454960342091452830648929118261388933297036933167543189308061917640517578583521401267417187854611829815212778183983326568586118831109538377828156118900313778053576483381085207892754728937946691892849474364477434665960112125254104966566712906532318984871145605839506991591027939136026602051635433295687547552796828217859648186757719639965988287173297286034098497871707197092627676226053609131138590878743560287292934815277894463305001278326023708395571840850120055316276256138004565442099731931051413153564744766098053176049414330146267604802971221161572130161432525297614616942172815141372973870720928125699420370428856022295499447755488148545048400795053604349570217878099721865670458104653570360'); +INSERT INTO num_exp_div VALUES (4,9,'97138902640718538.241246716463110895614166618530828908023040947887095196830690221211560526562522274118188963051412359798837957512805692731972838989047910709158995922699598619854907969493232150042212406549916252602794415099066259707018021422154933830674786488990033885447289593742424717170197810316367637885248684134204152352748803532396210051700193575105804898183523770153431536054848843504020390623875664696278263569145547515663340450903772852615789980257449146000410036925975898331113013857953289990299253584950458042598491897496393582249411290555264437893099880371008957017323366523688894303458743415715114628052487518110654201696604914159777300997374156315186315524817636714210119873791848535246674326877611945112249137224923201544452904111118569299934059002046318394345055859769572070097973298522564724884895879226870720839'); +INSERT INTO num_exp_add VALUES (5,0,'-652755630.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,0,'-652755630.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (5,0,'0'); +INSERT INTO num_exp_div VALUES (5,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (5,1,'-652670387.03916046850422757312745971450663862747133703839829692066597367760104802542475264601221776157515632293978442027199108085723617181683235487266149426304575903892721468296143475297345699313102262188759506518376019936160961709578829069446312051432780603656651983414612264636232727512091101057374054475214114364113300402823059519499217878746766275164739724770556122895799337810694888119810524986616938847385753562624139431982468828696587199570410008890188532132652095915565323400735066310142303225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,1,'-652840873.82996096805674909792441698652235828221445420381749472095823439215841389779822880154688608619423079931032645214190898787339168396375791272937178074945473802633968350414211085025663129356908887576538544498889782055029046596593888271636613472988050090259449836342389832330814473910881711053475561205644968306669776242949930651397625234795216816397330872127577980937461350104018382663378200293023018506679957617487661691020231880567020416430204091941905612894161614165865789507675064355852373225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (5,1,'-55643106304872.575994253221940844841058071061962511162776681458310912066379595519265546225338405882027547140476045378015935579066580347282075024392379464189067155567624835346798806677988850250198082355055954078446421075165109896091047534711081616362392995575466807084807876544560268050611445006601394735810211678919646667455478469014906335433468365011768049600750224822391684377238242162320161552720449713229523135506671063115436813348612986916614320012995541575293478341408982118538094438068036422562665160411591652618670802973618768526197813319204816293073794413317669922144705633308090832805914096147659820167569140291210526520361556881576175809360614782817717579318298657744021133210954279487777567785280633309576696708168342539425395482429923273623865667723482418178781573723597156804085501875735112311466228778929147929'); +INSERT INTO num_exp_div VALUES (5,1,'-7657.550797567691019915353529993301413746369700087741672762343206271266232635965032053368224472333368713006346867984576168784127503674579531243603836945595880917241997606783133673324236134063757452734295148763280059050480246827193380861494669624151921824660313516974440913733511526807313019192263170823268678149435664224184903925632177789052038092611394447709922076676981043877747276056677801802695466205531230350209787298926245402046182150996849906836743231861317120171583577624262765589605263477198809166390259128339127005924586833372241946051704497188891325715185091060185547236923494393813210904033520844572880475265306843414506359253445517738473745552980984097762509546161690823646176501838559393690565709795724159196133663168004773260451322595899506776323262195323943138344537866088159583331807728944620284996'); +INSERT INTO num_exp_add VALUES (5,2,'-994877526002806872754342801504871.47809095279915423939648794226185974985600242391612965412218049794216637114648812993201775787765690351615479957141288239552036371132381627958673244764559862836085530643408020551049895730005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,2,'994877526002806872754341495993610.60896951623817756834461124123286284017021118170033801249797242818270444792350668237291391010826978126604392715751281366489250793073354867755345743514510156309395711933053460228041067059994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (5,2,'649411906691138274293985410502516861224852.2323455192714410716272307781034189160865613770320102043319541634113746032638191509585045862973333645830298922352816245477556264222094036953195419857712804755170632292914187367964994214922001758104594052499795564860466055599417895782179851297585155129541589802249540436678824225950907268084876110445460948679383611117263673106597132046331719468816839434908155684738864149955129235751738204036443603521478609787295079710078973503970964790273461142497259987849074597264522099648376356902360358310245001183020992360260836105404118742418040965190000718736837422434593694808973939805954329718232693154128543253581495885789333274488461716809104532693754070810202831113003978085636579574171344721710232931261731022478029314435363413498991740750878099825781577297965642009156858479681236085226911858782115'); +INSERT INTO num_exp_div VALUES (5,2,'.000000000000000000000000656116570506105776235076334177868550033347254561166417969910286926369599900073757929714260350320362090452092025380232792749476245042480546813848702351830607516880397305138543526307608094143028291193163613755680419049060162928958489964834941920423432354996040147818253087783193280640282263490705632002572757216731766513434035163528102590524432221718194164133959630768718395847710529339782880381264265894322494716854757290930538739000043383104085867828258790010654331660516512156519838978751447311068903958136482041673109857552178367614498426226323001399275980281507353231821022591045797658991388304873240910526149138339658220844723880158150606035181559877351791752701872877147074033569061408920725522180134133183999181370354585872214368766629114773129541658653693832843354053701079334077'); +INSERT INTO num_exp_add VALUES (5,3,'-60302029489319384367663884408738513110.66683195868931664491302527038538338065260819361151478340212147889934633981101279593065290940544218360883531149731823374304151252289014494378769385157204705433009477214625880056478643611622410268943757215673170753460135411513114716313801477916713433956086133878890802448531292334570886746283905390661877220497842493537338035961123751393889400517474762491881277080205381424363695095196058838349029211365212855028824622924678684631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,3,'60302029489319384367663884407433001849.79771052212833997386114856935638647096681695139572314177791340913988441658803134837154906163605506135872443908341816501241365674229987734175441883907154998906319658504271319733469814941611260503645706198407368762270127105340397375230875953495882740039984314121888705481484090911598074635434289709802794549714765847764347865064280637851906308955404165593747173246944693509650424312007333558709071857299501674917023499921977975368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (5,3,'39362489275784146262776411377472433635883331946.794473520543457442955620133347015506556162839462623905489255080102447195050109095701660164272430316804466254467810714209179752718730906325952685817112992943656292503112803950215110778476301809440329937774061163668461957943313261962261081942055908935814323069621279128270849852239727888939033546870208376394878842958202403235309372240005941467570230067124830916866857395233038346727879951123599893174252558078732888910139309038957525961212820831321973219557165558911222848692996406741318948607549825343491479728117062814094258484536263158005174429922237853707635743736923521032098496725445243775790161216159399180889906705265012270270348146530113428221072591696851818281866095288773371414866822270689959827332258348570976075184933893434327278299820594014788148344260948638847457822697682605612771344335201258128'); +INSERT INTO num_exp_div VALUES (5,3,'.000000000000000000000000000010824770508763323320533297369674519056450544793568147911931789010432012750062661590994728968589403602468229106206242395792957238667714358401601098858606386995096923432407249369639633268143022787987190106724545750803196130511146323174462918572423414631798141263222875752767731279138952850500369328934959764805948568471324562210715908420467881411844098258193571194910997918428786213948547748701831331312040839544355427357749520227124858111324859160114175254197992204974033767300989488517391063188153561391320190653403747521648794370679322504188364455328709488846777004202196382575648619395139553279192346251133156445942281048959845827006761160755031086836046398020850814350246219929303018051720203943879538087954853996826539712240458022307680912400297508925714946398031304516583939283'); +INSERT INTO num_exp_add VALUES (5,4,'5329378275943662669459614.81475694159581596077242547133292502869630735172901157043010370467618244548786897684821457816189831652076071977025794948484549600736179389638319303817478693948215387894509009504287664213474693208847025374388286162907794727810231557001266897729978691844410171412189947386181530441402903608214502713480332746271552746231631136145916685939539173054989927058122097304419584979598595477177513004218594211597809300517607260841648610322863666300637648662611916496850248528515936635845594390453288113296413254893687029540384176335735114863908372780241463999450547422213639667099644505472777149095004849805371205203850993689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (5,4,'-5329378275943663974970875.68387837815679263182430217236192193838209859394480321205431177443564436871085042440731842593128543877087159218415801821547335178795206149841646805067528400474905206604863569827296492883485842974145076391654088154097803033982948898084192422150809385760511991169192044353228731864375715719064118394339415417054629392004621307042759799481522264617060523956256201137680272894311866260366238283858551565663520480629408383844349319586471282301251749494706061523663958609947049544255725056447964564549684815188261035801892684889942971676086592385285071073528462167439314005547455087297279161738865296114495425732286867689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (5,4,'-3478781676337858247983014311182511.567538638808357215203593479841446379226774481291286361639429856698999485760647422501864626078375852610019829111004807806660731243672830787729048847342063218718651165150612717759770504648306347926061960607388621011846314969634048226452709389995594961695723139571002939804473057725442880410434039783304583526414509590532906062732322732569475349107437896717416548237633532805602064623969799081086996320156575550896200848758685986331692388099427314008504506503745527468550106879602399030419569897808150076298414568875477195447656904373310322813412927463518325927626891046356679526447117311923853482118502868148386882363449163182892615259995945992014431502761210899772725227648729095696228388558331052524469604046072203605897109629560683446827492904111565278516043939137760721315953500281379039771826554155511347152'); +INSERT INTO num_exp_div VALUES (5,4,'-.000000000000000122482510461124748279475400009367345900846466958806966807399903713411658400733717078392550780910604704603123670767210550800752620037863340961255721285160854785449315208955654408132775022766783343331151895973970395232686910362226184006990485313002943710214511418310741271074710741339586430026286272098156531835438969774325517509155992092194349661122678547097423264670055720422496527272118788005921590521726691666219504214087867030003203385360001614199656989667055583749577099440092378355805901262289841168751608673297446473709956390142112843400255748161809121986096092991616144443486023218404881798896685413932215981950393130292001833627899480153863300557853617312991880655905907971211246077450786084079040513198340644157868678782195341316027563717617074364438885981635394382733697473265872796207'); +INSERT INTO num_exp_add VALUES (5,5,'-1305511260.86912143656097667105187670102899690968579124221579164162420806975946192322298144755910384776938712225011087241390006873062785578059026760203327501250049706526689818710354560323008828670011149765298051017265801991190008306172717341082925524420830693916101819757002096967047201422972812110849615680859082670783076645772990170896843113541983091562070596898134103833260687914713270783188725279639957354065711180111801123002700709263607616000614100832094145026813710081431112908410130665994676451253271560294574006261508508554207856812178219605043607074077914745225674338447810581824502012643860446309124220528435874'); +INSERT INTO num_exp_sub VALUES (5,5,'0'); +INSERT INTO num_exp_mul VALUES (5,5,'426089913064020811.057708378200224487694731586862745370027417544052374884336177893807736467646454486029424673621605232432043672119510371547153895504456723242262639262542904151307250842477327375961936454637964429999741717244285121019840463692418987118402683746281993192269229200465080358289645050337976214115902915692028162689089167194843185708212911364017271332623359100711545479273675423617018342297822477514128997410642005300368966199980354369928371655155437291469427189561877718971914040675572136507472590254222870537216617260612835805368361975725573009455402822669103118872235140158440342063571894152305875004532651814592458133460160514384171804043127771746596286988679698684698755896736275307574630777027620558428909546664763675431701332632828281070572045822129984625797185173815273651376003614106277727279230096226977335510'); +INSERT INTO num_exp_div VALUES (5,5,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (5,6,'-652755630.38762364608541718463145771120672223443489913059334543712856431450577465795351472116052777583325262472505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,6,'-652755630.48149779047555948642041898982227467525089211162244620449564375525368726526946672639857607193613449752505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (5,6,'-30638438.151446159804025029882398388155309149089870990062944469684482366692824338098201222171115395923414887930224163525189097571163687285244255335505387733673499447610577050114902372990462064696637481657064525319516004273769831260452832960893174173254560250804003884280384718123289136453955482855362019158401218620018346500189769819687260476334734259702665316562988639223597110627626759216850014150105605927773639897638043177685498804811787888811168524202700283461266793154726325540776914500415140842975457394524215869103737379109516024460317825645645301237375972914247141703084877141866316168268901439172491577729880760950895760711857112463508064820414904611059588717092145484656103798852859978690742216940980929562068'); +INSERT INTO num_exp_div VALUES (5,6,'-13907037655.047994416383638650569341223199042786813441967582376077478024677494832069402897226848055043557486983268019376307288565911231748501636517992289743940159005664424461285010295150828744259113760652210086696250085454819340987566229400805422509198052317518991183515696724846560872057916862620762789778660622787735923967096950195583369113574365386627110408307941105082873469072519133330718161987781080307947247163619814890462416622144825161521790673339279047700672881113718394727610096366361422482794458375587355933614201638489194194834709433413694420512869179976485096875057742460003147602405353823942488343056906912173170809084207937229591627643451380735179767199816663168139837088183577975769442341678933576388936845704303859241320794255052627716474860113993958556604381707826493168941926878481079724185426298004604'); +INSERT INTO num_exp_add VALUES (5,7,'-818934540724601372.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,7,'818934539419090111.56543928171951166447406164948550154515710437889210417918789596512026903838850927622044807611530643887494456379304996563468607210970486619898336249374975146736655090644822719838495585664994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (5,7,'534564131989234694540350103.27821462973515555648644772098605028371173048154132108733819196629002548296868548691993248746628993380136454426833349407578676005545111508293942736555269938962058196496152360848131645787941032968937794930046928523006455386861100809286408671908320322523368135203881520526880998279355848280412933152306299256343179622513731096363088094541514890135766460631462465021694553063366717467560655272004461368865264059368514271105464855575429914212085797297268595943955105608543373940035636033207568676745293499106348500559628723682588033431457023964317090780615020801564861497990103549650624438425421690193862533733474254'); +INSERT INTO num_exp_div VALUES (5,7,'.000000000797079129642393611556079160915147221153735075943759104977169600937534508973732991117540626046659124172765761873705978811124901421049332579161931652390647472911517923131800238903184679028518657818755558526885018755394697157094867449047655737107085020874974955627907737126958129710597811740696534189608639914753884882702680512272194316887744972931453458445314561564591875764930680945589486999586667912816485821717403892703364322658245615895415781719033810595358092343690359557942948213374234065052300866661453767599465059289920067095083062096458980564265691295895672503728815182981118876144075942348853666085714846210822847053889733510154276933759200630639642310562242207518883342516103725757482864105340008709446643820864294556778969997115586027866760708448174502158738150605938364482719960251612464993'); +INSERT INTO num_exp_add VALUES (5,8,'7844230593.20607652525116672615394735666141304947992676684520382624714879797087461877675155217754947572297228288498221620714146356962938009770486619898336249374975146736655090644822719838495585664994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_sub VALUES (5,8,'-9149741854.07519796181214339720582405769040995916571800906099546787135686773033654199973299973665332349235940513509308862104153230025723587829513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (5,8,'-5546455599206321494.0676583421119904300307105296377723816472192007866147764761501865875232824814135783697976183493106885436876081315217834621720906478074798596116645640251460842350553806256223963023430631066024389364515688765194373161385579258482225808660340732705687558150699172147896486727530192499184101617379930846663835628510376484675411350654979679181852179924386290069790336316958202582966248703889464308649631486542724072047294216362186036638115240070658004553260251510288423749333873893917690832829128021808383128393431810674177390352413548658782609064839524756041501835115152819802758773711821322162752064589750295542985780512921839490040396053737870038534216948323935020460307350020911362024271167085905714873548388570602799432705061561572854498075600'); +INSERT INTO num_exp_div VALUES (5,8,'-.076822018213756690975099471985461347542955923191183223634407380481978143225129486622351714276452369661632980197282261508936298649901018470846144321441236073683990324039849865750139470288565622579952182053792815638469841531577235191276257498209844422440366423136595067535337374223115507557306455001792362506235886189722508617024948653046102060677266555476719102193278190540414934812073355995577639986512222998268934000209944414236509139290657402937840986061987219441410741189615344050459067454369371094189930607834375561948483494321255500497786795636801854613881105643003358210407867114145806225724880370339074242480071595684502491827709175732777776915682786771730423733673667248186336046898260378049328204094804755195626798951644386924178161926128482002518979482630732440619051262620098544265763306253807191182'); +INSERT INTO num_exp_add VALUES (5,9,'-597892150.08771044822540810796370552966707032464017958269847934730769542644402913723848026909285133109089452632480800168074607090893991283808726990171062867538012237270000932798704781608969096508450960185964292594677356241956277714380500188870696516251767979457838109804726539408115452577436052503866633026489282425086547752714324273565900641436632912781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,9,'-707619110.78141098833556856308817117136192658504561165951731229431651264331543278598450117846625251667849259592530287073315399782168794294250299770032264633712037469256688885911649778714039732161560189579333758422588445749233730591792217152212229008169062714458263709952275557558931748845536759606982982654369800245696528893058665897330942472105350178781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (5,9,'-35812445701642379.972368737320206275515144213236752803936806738624588812089615098329765811617509505790110909629109400553415312470540217508070421816878544125783329593128638405659896184248784794258084116406472768709113030915308410565617764394827427154923321461158387012978726512246146545834669665093228316853342805604075936530371665576147966721599968786161939347726656168798065647411457701453987215491345496003650288850096338695703984042549594979897253521041581573388369367579323607093487743440894765114619634001789457486407909224339065748496715380572175183589195611952939575073075140094901024063428239223964510824958346570603142906309198033196987949067156046076497974760641964978711558209708743776024313916111738542765749928287600981397080809041007714387564206594515733287925008053261840295560398311905155157989225181164097547541'); +INSERT INTO num_exp_div VALUES (5,9,'-11.897816658873986795664687519069203701902563457968097729876034796143085813450454323128600602495745166997629078984618283588337379184733369491549230343315369634754204412939757136108898254582353378508832611703989221079986765793923635928759179573599208612516427628403686659479459867527627014558600521732194240404211484706621458983727740143568799713006127585168144158660566534382037451913967363675002134687952374080694449905223371627606557311710348820900963340884001770733452314715448053233208783321215998063958966729954113843581448912079950334969908657535514847005768455377990262943747367245613296497099716892292154137652893990339292671106003657659470243633112063075297194691349631518467702876183897580432003030164590920118726657290102377710611324297862045849839571689192181090062958059281673245670440852080202548743'); +INSERT INTO num_exp_add VALUES (6,0,'.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_sub VALUES (6,0,'.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_mul VALUES (6,0,'0'); +INSERT INTO num_exp_div VALUES (6,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (6,1,'85243.44233732197133191329295927531563604777955507322414928382967007765263923984471408038635831036097817458527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (6,1,'-85243.34846317758118961150399799670008360696356209219504851646259063690472663252876207514831001425809630178527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (6,1,'4001.075404054519813215296429095020391062109905613738157927030437221793757373268325953178030040276107574363822832168160758728653712686313134828282109532831190239521843808940611025488601517574653932032236616573457735900045655665690517797280666732780030171712864961531623060353548802466577910774711998056232872212688464691036260746751992072745518373073825852119460094113694393273456369345499434994672730920070410547163082189385645712866100999708173472360864669110044660667614583576570496399103026286828660558854973376227247132815728164629722965145778698957093136175449225024685874279280018547740'); +INSERT INTO num_exp_div VALUES (6,1,'.000000550624150700285432940805295709861455424264970126953321538967550091614148982212874391026630805836518138806917934859138493583812313778188030836027246840794439412443826640206464415527687555214009725107630387889854278497875708390050387195108441635824296563108288712340902423706104029452615686971019125750530034798026103476074158922893374911891438688457439945897348811702908216883650280617098402133628688982793791562476980709924382381505517834196446365877784931355599480881104446907801805570471686295270927836995181422963320376948188855989986414581755633425437161760674162177776773597848142496583128607548351599750592863590334617838124741567654525843413232313914310487355539260264225486180000012813397807525203822863232682089295055713257835007742845010741137213301116647610033909062369843750685396196342928455'); +INSERT INTO num_exp_add VALUES (6,2,'-994877526002806872754342148749240.99659316232359475297606895243958507460511031229368344962653674268847910587702140353344168594152240599109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (6,2,'994877526002806872754342148749241.09046730671373705476503023105513751542110329332278421699361618343639171319297340877148998204440427879109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (6,2,'-46696638263247522384986521136500.479312417066793299922708112595886608370451213741279484136907754744903470430131032928908162742687359367826808123516519335458861613010646992354378739165872253762686683966945711430182491860196341344982195078000259063231136011430995647812149294224699587849791008794261026932467933475782780'); +INSERT INTO num_exp_div VALUES (6,2,'-.000000000000000000000000000000000047178744084866106587600962473825168237820701199970144691815329658682341685812472535816245052671243808078367856957579485152424914481414614360809698177236664771558713606961423658442962083541733004775309314926918118528217478256885324362912426275407382550929085958089798861918760121727491366034496581249711153289495601712583077918760003840368008056353090552282274780428335438032908213783490070198414584291402513547386013689752310173492320159738977752795528725029134841933604057954874523842273790958618375118974623107241366036640538085329921129023905888674299774726871808862832797230915933851225308164365269753526489223540580759951230801125605963901491073619448437890841032149898629231552019804656219062534881074125995130202820302133432951999011667568746004715268323913437054078537'); +INSERT INTO num_exp_add VALUES (6,3,'-60302029489319384367663884408085757480.1853341682137571584926062805631087054017160819890685789064777236456590745415460695320768374693076860837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (6,3,'60302029489319384367663884408085757480.2792083126038994602815675591786611462177090630181693462735571643935716818574980747701251335721895588837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (6,3,'-2830400711649493468815157129316992649.40542786074520931471973065281957756940496588853021620372179463538053123396140685749478530925306163968207226329985017644835203709485594362663495728106061878665324856417118064730721101615473194292620972173690618491026470353143141125614124440035267592258385099934706896692953497971326605145704135723011753705907329979207428661473172503098296622281647255008204864404416199384701720347319806375450632245634238172654086373193251877533131784268854289406126119630708578053354762596511353053106459297339360827562281168219966099848212'); +INSERT INTO num_exp_div VALUES (6,3,'-.000000000000000000000000000000000000000778366376597400971124059102619954214055884926284646546105035591052258074563706355894551049631537984053410850060739107742208523938741961208742831871056600773325053133977559789796700130019975964192371715826863472981072974742704091801166438465082519558956925444635729210849210496466189037623555622901738570979273502405907969114110345815802999687171113749364073269902319653450479463404003706147915064100959774312307195946966281098140229199529866429134937742584938255441169541436021827079647129394362379406256722903991353136733939395366152312959281905058592776286736536360235356737359904478313225848562436632109470589310799000750518904145312512621838935796912993778920622238202744037977772169066929474233952081158212174549695244127987299282384885288897893503991509410567351494'); +INSERT INTO num_exp_add VALUES (6,4,'5329378275943663322215245.29625473207137544719284446115519970394719946335145777492574745992986971075733570324679065009803281404581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (6,4,'-5329378275943663322215245.20238058768123314540388318253964726313120648232235700755866801918195710344138369800874235399515094124581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (6,4,'250145412892811547138949.592621291590152419206270097656346630226508074074623894951308487425470437268130465956063593951784820669318897182831355375451719125809800516979013437732298382708070979871283132689492336823087794373113039154669229889503700598930220858275174342776478898670277868700384853696009897221747924643343353942154528501454689084608965009561564638167714973711022212547096732831847202912862290958304510651828842182545311077713664465815992616213663619529378061133917572474298028065850515876361609671565914027186063801852554353160801534696062207299890867876199323530337336273950892723090754719547285920090419070001019943385293110663922226230169381423410428577990604776655422105400452217085311617728003688836185608912367677734364834577573255789160419371322775733777518997638403409000055707558465286469808848200141192627396502735'); +INSERT INTO num_exp_div VALUES (6,4,'.000000000000000000000000008807232244507937251856465017967626593430084223212999583902527587737263981869382895220711835510154989851222501080395520249593128253795609198666884523792646863341248402687314509176781281863891589925961900674092953408613128961234166906173266411035009516545964362406728942021813644419154548354247112601793685146960840364604115937119024575638240439041250900118977183124605578660115160551830946251713350556181960983267689939549506518185340972020820080460565392359379680036788592213479105831301723237102710863182596413567756605711230290883888612188805367801369264231165178487334557824054205160222371548005742602736713668548450400926514169967213301919971189065307721110805424950794015852531342286935114651278691214233054575660712537044810163930633456573860895791198853393107188289695511873068'); +INSERT INTO num_exp_add VALUES (6,5,'-652755630.38762364608541718463145771120672223443489913059334543712856431450577465795351472116052777583325262472505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (6,5,'652755630.48149779047555948642041898982227467525089211162244620449564375525368726526946672639857607193613449752505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (6,5,'-30638438.151446159804025029882398388155309149089870990062944469684482366692824338098201222171115395923414887930224163525189097571163687285244255335505387733673499447610577050114902372990462064696637481657064525319516004273769831260452832960893174173254560250804003884280384718123289136453955482855362019158401218620018346500189769819687260476334734259702665316562988639223597110627626759216850014150105605927773639897638043177685498804811787888811168524202700283461266793154726325540776914500415140842975457394524215869103737379109516024460317825645645301237375972914247141703084877141866316168268901439172491577729880760950895760711857112463508064820414904611059588717092145484656103798852859978690742216940980929562068'); +INSERT INTO num_exp_div VALUES (6,5,'-.000000000071906039575366987930696117572143566208825430801491864851999044659045681114433294052065377679745375399878664822361548237094424148992770296383642432040129230180142339557437679166815114510467763288057917694948929009212876391059413439647163295629904270262780935228234994930653489111444964446097124407804311494588517082748514970905563707392765567625639455978464081409330528324962333492925267647686759704415549221137291475247571296491073010175087298752769122449499990102435819414671847617062560524758344361194566796343756743243766853291113852464023843527189221162680613675369708907935197867458588904367993736363321133720345058432019986643353417257503619558797249295232894674255060861358071309619524800424087896023710729815248847792174290644245138831518072176198607255346603270853333176255533974364728342822'); +INSERT INTO num_exp_add VALUES (6,6,'.0938741443901423017889612786155524408159929810291007673670794407479126073159520052380482961028818728'); +INSERT INTO num_exp_sub VALUES (6,6,'0'); +INSERT INTO num_exp_mul VALUES (6,6,'.00220308874624532134736695825088747995945783791378828770826401323533973395137378460250799184832278118133622563295093909508983301127615815865216895482784469538070133388154961402881325731054433770884496'); +INSERT INTO num_exp_div VALUES (6,6,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (6,7,'-818934540071845741.9530629278049288491055193606922237795920035094854496163164602796260436963420239973809758519485590636'); +INSERT INTO num_exp_sub VALUES (6,7,'818934540071845742.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_mul VALUES (6,7,'-38438389630389612.0042045464692275627184627672063157323631169405883031379129843031477339360597564128205768842448328088'); +INSERT INTO num_exp_div VALUES (6,7,'-.000000000000000000057314803440765029050667129936880528769333499793237773980613524885506515999851858649385968476426313207429914995755091541422893944525222307473169425244462149015717526718376299808423552027796204632286454853167559026787019718806449038446612978917236245943248168920696452018925986743620392955122431521581268518101342690974749463089739042586011924590503136498488946387508310209984849243014542648765897536338824721211252335866349509669538308454367849024503312249951727948786393404944555844863805495937835281927012430439403132382055464307180153473189842433614777883826783689904293115204700185380661601223693428304020047393499702811581067120117405280772944184877279069842269329959037186324135435468322336398566440055479142909170224780318371473684868152271947368867666706912563225912012901437076773416'); +INSERT INTO num_exp_add VALUES (6,8,'8496986223.68757431572672621257436634648368772473081887846765003074279255322456188404621827857612554765910678041003765241409149793494330798800'); +INSERT INTO num_exp_sub VALUES (6,8,'-8496986223.59370017133658391078540506786813528391482589743854926337571311247664927673026627333807725155622490761003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (6,8,'398823655.819545574205652791249227663407026876411660299394659390409794761643751582473390322547798567169668246138880832642141417531427935520467563318363116897177899262525720710134129529640376020947774470933902793259531840625444267816319963200'); +INSERT INTO num_exp_div VALUES (6,8,'.000000000005523967081937952184172713994498918048454262874017009201501812494019618863622631634736130436187167745347383745890248619882896153083428308074678908731005176810208100004498415662458272149380846809398637385270265351808328466537502823071145089961996689711299405627596294988646826454676198092260759424935699382655736524042353938814268760468122584678267125994645166955751211397353140569987758938572953312303398024147927938612934833827734142292697389251052485981023756760420972614486278837214553818521196182883489483756785207650821722660455451660719560529693418375773124813290305501923899840247103166971466167032437598057958226806335324315214908788839919408525748236713611579486768218564733151121028172253396652755590051310396973181595992981076269789287489208817712754098019817792758730835341151711523474207'); +INSERT INTO num_exp_add VALUES (6,9,'54863480.39378734225015137845671346015520435061071252892396685718794832880965812803098645730572474084523997120024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (6,9,'-54863480.29991319786000907666775218153965190979471954789486608982086888806174552071503445206767644474235809840024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (6,9,'2575131.137912978352131546639620215541477987701194164886305951830806120142596646541302305984776928560906754259789485960991272272782091464270104432109904222200473616116525297615725803495463468272171161659654385929185160689572943852767523792651123455283534072794326647404332228203001469884016996499768656263775233430922446983838511590562929268821678518640501686017030536100955531423152839988008496919169395159653034847677470665418765966542111749439412'); +INSERT INTO num_exp_div VALUES (6,9,'.000000000855524875533453524582534418967571681572635027972658867593464437484123442242521660317156546196609749230372398872487667521984251509483676665788527375343148382604836976332389890799079878151841905152004537926201190193814594954194044560537664560344224646197027029681984683465852110060077865421064400958821808374370779297676624123638191407441015008434084079839721156870032377372497814037418047056438760664237367081226979226606227037631073946209105678283624370820396871058367779887709720661001099338250009251834581804647326512873792849059661525874160414378459696930831877643599421297749483849526695657467708603491876916749718079725746259119898269814551222336219537198318796277931946529242436502235147453584237994498566122973953203597470078105606906752099294162422474758048436539653041606499637623370030079916'); +INSERT INTO num_exp_add VALUES (7,0,'-818934540071845742'); +INSERT INTO num_exp_sub VALUES (7,0,'-818934540071845742'); +INSERT INTO num_exp_mul VALUES (7,0,'0'); +INSERT INTO num_exp_div VALUES (7,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (7,1,'-818934540071760498.60459975022373923760152136399214017262844141729040109985386964272131706381326192223266583769046276181472898406504104649192224392653722107164485675679551050629376558940966195135841284978096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (7,1,'-818934540071930985.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (7,1,'-69808760806266041400340.70700818693892852138813934414383886494691670042143650609934777814995087699409404201920249076407981012095999320858479644760715204999741683528746097757549835956359129287002171391961763797857794730120426599135099619822532290339000466211195776337667123320942107370731349851576864242697412616810236323676004067839744992733887503405311090677026008324895177587064547630828026123718296429295638934384446325302964896473296829265805737112709269803814942537657996725913938408781715328945194948010970'); +INSERT INTO num_exp_div VALUES (7,1,'-9607014551997.140858001442365669993007297071681832468350855627077185145567261170534005832165603932891201648027598773639089125980996652005412450490063683624648655909636499261774535015914730479401090227915382926027949990128880284298688443593909017437720828163877690126019616194376778317148693270900349151496295698078575648169637635898560612738481294674167553369445426793073304518646116539082953755973571046622684332425840412198776081251646424875405772676893185726872613804612566569794177506268399878105117763696990094108960076591684779180089885283939385808214239337829666227427148603057941899878123459708920227867371285837642561064461118016739395972994827327543594846953341750907541716807985738518071480209106185726125017342997283356926976052909493074301401955202616191210810331245427141945840542129607439703255628683506772979'); +INSERT INTO num_exp_add VALUES (7,2,'-994877526002807691688882220594983.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (7,2,'994877526002806053819802076903499.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (7,2,'814739569184924399102711674444306584731316176345067.39834031417849342571224916231092924046722938910652929295271097903377854123984307101079073134405782275535446337229706620713104545454319555885847481531722101704765783025789147453570970090'); +INSERT INTO num_exp_div VALUES (7,2,'.000000000000000823151110229758332661330617426417726331211894330147399760458555778324097596176117291103184653828305857999638466183347321835058943563347767579219763002258622507889760416640758842509635599414768344140175277742935564567127659688612699366182158030839083982896107176174766408199870924563237827899202849733606842856491701660599599211106794572237923985121475458446997860253437578966578617985764298513928307852082168209458400544457824307270777530312648199364084272310536024283945598340590403612752287693234647719354745060851129534452514828239800716088248915975054881011343555492596002595181046121935660176097475159074973635534016835214952415720717896518544064238656360099884889450237541254761746029507300068198731306211736696956568648033834554273602524147075895460874922913883751452403825099444642503437'); +INSERT INTO num_exp_add VALUES (7,3,'-60302029489319384368482818948157603222.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (7,3,'60302029489319384366844949868013911738.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (7,3,'49383414785234649002982046297226894664526726187218771083.0993243619030008310875293647868815940421844461627295157812843657782639833900543200310573708100000958929315945039020410482966753145208427035917753919085618457760620513481628641658765820294863970581642745379331727722585319163262763708386199720411053619449096019862596221607526610103408936214184850115071874430846697061554769773328338028749631552202705583855831155461651414320570061181212214810086436100771547030013079997847086'); +INSERT INTO num_exp_div VALUES (7,3,'.000000000000000000013580546907080371873577430837141172674171921610919544849037647398734065712983603204704663262116138799357430947986241590690589753181299773842880079777640016786921825609617596862828930939366173224366864448436461306602680780407912534492687474933386043505172346330210659476505435994582446405414027199938970759003336829722057241708213838318628292667946636226143164221380503228191376939596663443230082698085439531600756771639601022064620204571458766303985028143400866776954225590745596639602613498355332049777798367675438365442468743270334407716567057368347458892075084694158566383133325959042076573734408841629149903649365079563374278550978052491499304166424686842598833319515705663176855033865872333988551611996194856472662292344160194821687681312501127516922809221030420253714666026321243515830'); +INSERT INTO num_exp_add VALUES (7,4,'5329377457009123250369503.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (7,4,'-5329379094878203394060987.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (7,4,'-4364411947278810125327066890819882483326918.05664098958260550284395870948992407314161088028674246708928421994893923699743452802989464864039994566042797942433140378990308345483670828497915478397481687305406460330009319949623844175096007381662809083363069100235985794575399268709260901964834244796150883807308976949196661411035264619638771824190014274817662519438658481432363824187693821267613212631153175155634316128036152465184903927860719447693468054624663668062006049759837326188252927823612718163916100588143128358998656306593393889422386501730237442526450419990376323903182669190482615734972147533221144682538647497701130447816148459762464395194383090936159579764712919396391813914821973715879062992249315474841639591907249142779103650773383644785606333916967894'); +INSERT INTO num_exp_div VALUES (7,4,'-.000000153664179510102140733858340480800294287837601105047285453457000254577644933901525444082336054243749405512900867540483190494113677173628646221933766421338612376123824684592850465460156248403574333545090544920568230979754949827013129083778435107488003838746926270955224758508832133483591156567868631938590248213604979638895901933775098150684618378235712437137852195098700137765601802898366867034641606131280434771339920637353140131159441790904703083143627590062236537714415872864218260252838432414759890832271190606933534662897006726154587341385852258168335058931957995901987808602365467861573344491265289043037273815504867254228957776127752540924854546837197432384563153608878864912196453587628891285275067452280357349897203095502806923463147414086919014592380804424300739713935051357374227246098303140106'); +INSERT INTO num_exp_add VALUES (7,5,'-818934540724601372.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (7,5,'-818934539419090111.56543928171951166447406164948550154515710437889210417918789596512026903838850927622044807611530643887494456379304996563468607210970486619898336249374975146736655090644822719838495585664994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (7,5,'534564131989234694540350103.27821462973515555648644772098605028371173048154132108733819196629002548296868548691993248746628993380136454426833349407578676005545111508293942736555269938962058196496152360848131645787941032968937794930046928523006455386861100809286408671908320322523368135203881520526880998279355848280412933152306299256343179622513731096363088094541514890135766460631462465021694553063366717467560655272004461368865264059368514271105464855575429914212085797297268595943955105608543373940035636033207568676745293499106348500559628723682588033431457023964317090780615020801564861497990103549650624438425421690193862533733474254'); +INSERT INTO num_exp_div VALUES (7,5,'1254580584.048971438599349046867230181719371038956756285986415773300837165755558702217197735811549684202279755101552533605390208155708695952004683670878589028717509749282693444655857296902117478518511492735290086040573521482737598395369632843374456793385511847676556826348943588519880411018079886373631771830925920986588708409208527042927229627786932908015502292313887561198156623702404977221789649731458241770690830680067801377815840764873662400590343236662968218256211697981048576328148435241545372543075051594952109757428031762469834781538302930957095080167901199455226976113347018972534334210416375400979738414416582588689496706548495076287263281908191770792203069614447622517839588243746755480572371988630084226963919158931419126724681617069720048557166545204944250492282054791996953359013543036918134163144772567093'); +INSERT INTO num_exp_add VALUES (7,6,'-818934540071845741.9530629278049288491055193606922237795920035094854496163164602796260436963420239973809758519485590636'); +INSERT INTO num_exp_sub VALUES (7,6,'-818934540071845742.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_mul VALUES (7,6,'-38438389630389612.0042045464692275627184627672063157323631169405883031379129843031477339360597564128205768842448328088'); +INSERT INTO num_exp_div VALUES (7,6,'-17447499423661151023.558342555162228919125358089491573318627107322332520978657843895009110781773496490472817700487707134216424855867015781267287628022535529641238372370292374146871103236048507252055787621394728096799222976387108688980537900309311204203302960751747509648304056939321473462375648710590981564101023812800603438271190184064874290215309040519813024962909469701968804925443161094255632624090623433640078421818321246597728308302979223833487133268472455479442002005374793705431817866798804822885690193667521606781156962792120052947767160957903073698536973292205899421787948529970837601521657406211962967291912148632072929662185840265855612193255596825032457033402506154930851214421895488796227471490998190312007513478459049382774782886773158311656817014322925167278223360446454868236479549745612973293185989975394307678926'); +INSERT INTO num_exp_add VALUES (7,7,'-1637869080143691484'); +INSERT INTO num_exp_sub VALUES (7,7,'0'); +INSERT INTO num_exp_mul VALUES (7,7,'670653780922685519356619170643530564'); +INSERT INTO num_exp_div VALUES (7,7,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (7,8,'-818934531574859518.35936275646834493832011429282408849567717761204690035294074716714939441961175772404289860039233415598996234758590850206505669201200'); +INSERT INTO num_exp_sub VALUES (7,8,'-818934548568831965.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (7,8,'-6958475505053954666339703437.48985528725312694198056665033448258303533387675711770743843194274181580881296671866212320171337132096489224277825857521033238709600'); +INSERT INTO num_exp_div VALUES (7,8,'-96379412.478435590945480884955616049873645089637121682284625533034225619945532704111492738646389632607594293500930307222576571876059094206480673293295865214240456906965855425738072430281475736130342229749511650392658808510082775031098547507966544723255869156056349218776847523349173551313282283869146710349521487706884633419341568648959204688757523312579312713453540395840470692533267158388401676533369105590789036132185107859069994833345453200014884023709597817280132465224778002071890368479648934317322270613208789859930618055792958996389145963056607200020526949699302565905917600478429628844015684879886549766473809801710003649193772354147104446894109928903223843036925147624639466770660174828940577089095480826473544099693433597812637069287644606693066736302793687011165899362920686114156254982709172925265118077531'); +INSERT INTO num_exp_add VALUES (7,9,'-818934540016982261.65314972994491977243776717915257186979728396159058352649559139156429817562698954531329940720620096519975256547379603654362598494779213610069399116912987384006656023443527501447464682173445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_sub VALUES (7,9,'-818934540126709222.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (7,9,'-44929599044588573810654775.83678007633232843418115790847152455559258007804727916986432256198687661496804050903769496933400455947645400628259699874770581538122521805603947464462448454681701547899144129061961394870320463199545502030106801911915987309444301341575451240764927967432593181449618816978119423290767783843864768557371257918447461479570164065303599994081990686'); +INSERT INTO num_exp_div VALUES (7,9,'-14926769772.797708334489652004325241753714626257641081061212878627972973992233480868793527325656854681817156284203427388055525855608883067129036717726368707982450450575794623567027457808927082390474261155500697096284790656757163047499531247323702909360444831707029353441147768321257650234732286165724178549576948957405037843360446785505536809409054071975214796532504678683693402401018726571884721963641317944453797513145055081061680091585467186975354801535734149952115333241283186621720677488342266420359417174224757781125498130120775969091933838082305123652811689513300403051544682523761263183781206840940347226802620226164265210810994106136738030959199259066517106713585343004140573604437146025585149934286364795122716971496775012412420105368351774715982565252533025207453326002101655121126631180162560463548157187175671'); +INSERT INTO num_exp_add VALUES (8,0,'8496986223.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_sub VALUES (8,0,'8496986223.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (8,0,'0'); +INSERT INTO num_exp_div VALUES (8,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (8,1,'8497071467.03603749330791582407836434318377133169438097066269854720538319012928851657498035372443556191720308219530866834905045144302106406146277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (8,1,'8496900980.24523699375539429928140707116805167695126380524350074691312247557192264420150419818976723729812860582476663647913254442686555191453722107164485675679551050629376558940966195135841284978096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_mul VALUES (8,1,'724311956372274.0135050255361637906710330203036651743488213007179039756514944640108625580172737414192938789413338554327986697518463087452612658955180411327002900979574347739956600177846996063741787205122007268468674386396156638261992679442768654367111433834151087792255469957061758837789341439211010331332174981459471333376067541234901538285101103690622656631026001337239036711179989456674399137008584021283568040818388709554256523118702728176420022080138548890713013682480239784198421500241995499841675772793497485550923152267616622892846304530712344886979674416990935007952941652591352603797627920865960622077762568060903908151958000'); +INSERT INTO num_exp_div VALUES (8,1,'99679.115123747637190903598543851248555278745675862923884476564848911494649941770503156134872464666625927195645517181131678518619856156844072856993813601495176097972982587061507650426363887871820112714099226501603733968262566093655417466145183587899155614471697804006772915054739361437054029183182533671508695646413074668188590846200362324428338974890534273352188276373478524543505805545661569395314989170104140776362043880099775594658817242753124957385625811310332354760117110779649164022618274859298031549851269619167173746259018497289174255201452265070501056913033329291819570027877856677145579673495987354805150868813877928857472561883332547900866904764950837506993759536410161752469488392566682723027340638271076406246129989851281210810196699482980833204884400423019400653089825859983062096326294783573417554749'); +INSERT INTO num_exp_add VALUES (8,2,'-994877526002806872754333651763017.40289299098701084219066388457144979069028441485513418625082363021182982914675513019536443438529749838106171095037135009526312783302868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (8,2,'994877526002806872754350645735464.68416747805032096555043529892327279933592919076133348036932929591304098992323968210956723360062918640113701577855434596514974380902868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (8,2,'-8453460632655529853033389979024265783461224.3195241893307807116624750282852146303290708492834695194274289713076935297734670940696121761483641291930931061232942894577813178566088927221374036301485916497770984757492912292002695944367308880163698595015497307574177176409203214324418237020500352652934909632442547242092296504047310806151851207329042221920888326000'); +INSERT INTO num_exp_div VALUES (8,2,'-.000000000000000000000008540735921314463871578184793632135730756619558669911183806487803411545406462244216408739432325839683804021466133071768612386706692296158696852363349481716813410857655324486448455846562309041306880675446880859847445987588059144788756984750993583865748280824370754934966494724951583311563735533173023858438364336214213295786266815116844775733072416507474834701984381586060478606371028156925222726225495235702395502085206072985373035972506738983640539009567237336002073370431753469632428303255926718930619221521257726366850472572830063284204851204189447233044832163423057501488364913539948261528280564870049935369825245920984413480757133585498984374354957754078525161296201228031555280486615145365039415418251448980923331334883673792135893857917681235883506783408111446970710546686739582471'); +INSERT INTO num_exp_add VALUES (8,3,'-60302029489319384367663884399588771256.5916339968771732477072012126949734214868901845505193155307646111690097978112797961939995859130827784737422228762767014427842766445950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (8,3,'60302029489319384367663884416582743703.8729084839404833710669726270467964301325349604567186096492702768702209585877643481082023851284144664938175277044596973126708926205950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (8,3,'-512385513828318260570283740065493064477880918352.732624553690077857674083796435724202494963885926573907185100543184828131859183999195040110586155435203949963570735841632689374488877298209082579317039061893012560130258753218955057387206477423088065663401594359617882154814262843273526859406265633827109554791772242178864873774889091687515990672487380368975556580539271333144212685871370972163560839446696514092637412587953506052848750866803569213269271165856310101244342151576488190595936869490659700946174362872797854591188391982770203203644172999264143929484089237665313698600170041324566984832357000400'); +INSERT INTO num_exp_div VALUES (8,3,'-.000000000000000000000000000140907135225782279761112255989433531718277338909398600029580768021365259747075253760824424092983497958717844671162530550507041138147836569244869107757945370200122955794509365120853536859837243314494576053441804831018954867623755033888264275704547752628348151132333655667171970175829826792355986148522268067032057293494927558322394395160508723637192234110428953945018965078022622950949911124494740703606109543716688008516750321047603009424529696862953094999450658951089435460411028678817795100630449046993274191915359520936265372754315076684798942557329584282177053819106884196674660057281227248874819417305259132106690385871316407455034281900110779740008476645291647094776093567400422266906817555937149628005629880142615126571231411138926043531449659320501743591992888328328980526602'); +INSERT INTO num_exp_add VALUES (8,4,'5329378275943671819201468.88995490340795935797824952902333498786202536079000703830146057240651898748760197658486790165425772165585380839129948178510273188565692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (8,4,'-5329378275943654825229021.60868041634464923461847811467151197921638058488380774418295490670530782671111742467066510243892603363577850356311648591521611590965692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (8,4,'45283653791262997781451381354094822.762732909505051438036873220502792213670540454778361182993875916509061144859281577740137081988678361247725064336120451090222456518107029158304937620179032477664627949959143233370320432203497828243297406462513350790251761540074946469824444452248386782451723637769289822576372357189700319768797708375563651655860093365309717823602754924352327588945034832436331911584742966378275504545736896430718939807674966738116698454215555860047859161126694019895490767779791933882712567492115664113775047192011252893773389940988533801360010782816196288710063568554147458866942816721046004257953642508395867837127678980002737669139369781058046396738606563716339660654364541530532834806205571191828994250708412638796240377704994928921528330863683630622922959130920715261879547446054261914770022377059156125037157979236658010950'); +INSERT INTO num_exp_div VALUES (8,4,'.000000000000001594367257057971052149628499448029056279649281098852958322409409919964709324200796473211884339143791758566019217634542932882694487712398244322522748736692741288668885362384266615527166964187404128216235057387796054457728789109537338988453837993084016408244895452291151218602815057669592284587317035387004942691671916981967449109983992675125005085762403043329820872839739877674121174083273716295673230993049263574856197011389828478636779342320299895806297835595427859271617831720398457416685435560152182883615601663820189195644140652141180949257192740185075408019971747810015931542757445763460947106918998459997631117642552273815713467150465548031203738878873114842844016176922502916339025283749846225376341878386377192605865913018132981323065698049618379727531925408677611856682983907951667054819'); +INSERT INTO num_exp_add VALUES (8,5,'7844230593.20607652525116672615394735666141304947992676684520382624714879797087461877675155217754947572297228288498221620714146356962938009770486619898336249374975146736655090644822719838495585664994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_sub VALUES (8,5,'9149741854.07519796181214339720582405769040995916571800906099546787135686773033654199973299973665332349235940513509308862104153230025723587829513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (8,5,'-5546455599206321494.0676583421119904300307105296377723816472192007866147764761501865875232824814135783697976183493106885436876081315217834621720906478074798596116645640251460842350553806256223963023430631066024389364515688765194373161385579258482225808660340732705687558150699172147896486727530192499184101617379930846663835628510376484675411350654979679181852179924386290069790336316958202582966248703889464308649631486542724072047294216362186036638115240070658004553260251510288423749333873893917690832829128021808383128393431810674177390352413548658782609064839524756041501835115152819802758773711821322162752064589750295542985780512921839490040396053737870038534216948323935020460307350020911362024271167085905714873548388570602799432705061561572854498075600'); +INSERT INTO num_exp_div VALUES (8,5,'-13.017101389051085341042057308965769356145255575582875626848796382322826525772114256699384710400140437710569924703769685567402446691691210934185000959063158239023412379691360587119206695513775971704926722817528818197919265145207032750407924774510773427697188520818450702875142190949766251178733262143962213111236591970766836685919581025629742334704854852196126735685421250263035895756028805974153787560164935038227108975229771590754808331856162035119882347418116049174638416621093907738608991987582465865527947015457540650512339263071898410531735438556948115098562123055444965056347091625748703503220861221718449714020622377233272042277814766996198081939221253025243417993701684007826177845003391944496774674489538520354606358872276671998045196738090133576377830721671972381371985771591052597345572374064920279182'); +INSERT INTO num_exp_add VALUES (8,6,'8496986223.68757431572672621257436634648368772473081887846765003074279255322456188404621827857612554765910678041003765241409149793494330798800'); +INSERT INTO num_exp_sub VALUES (8,6,'8496986223.59370017133658391078540506786813528391482589743854926337571311247664927673026627333807725155622490761003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (8,6,'398823655.819545574205652791249227663407026876411660299394659390409794761643751582473390322547798567169668246138880832642141417531427935520467563318363116897177899262525720710134129529640376020947774470933902793259531840625444267816319963200'); +INSERT INTO num_exp_div VALUES (8,6,'181029319177.110996740664566780784253502559986936959009611748146099327460471609593148344991059106574612143724330935988823134137686051475120980257829276671900076859337187540608483895641504622910361858962883971613675309676443079313179200981488761707281247447120551917205792352229666049191991270809865110506639390610910481490688182068719005593641339338678014189749279508731647492051879768743158839680867283217578754666643688259810863605002821607490100820241093473083445658378988069593782353275713240897038366242558466047071334385431080003439842348547427066389352198560236731403235927478177780757802759046212921140424771887928786549573201311120885052685761195784207710933764480136690216943336587118385525047554334029388869436622866247240903231799829259264158812528305210833683370536416861544931420820452512390255774498188962903'); +INSERT INTO num_exp_add VALUES (8,7,'-818934531574859518.35936275646834493832011429282408849567717761204690035294074716714939441961175772404289860039233415598996234758590850206505669201200'); +INSERT INTO num_exp_sub VALUES (8,7,'818934548568831965.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (8,7,'-6958475505053954666339703437.48985528725312694198056665033448258303533387675711770743843194274181580881296671866212320171337132096489224277825857521033238709600'); +INSERT INTO num_exp_div VALUES (8,7,'-.000000010375659845651632013446652385870617923988120764298690164486716047614260682259722116360931978511176121353975789418625836899338225571166376573732227571704071000348895791547943896682585450808398324252224265156214259224488248639550967292466343168350213394398101712526534464002532408445204630441167137710565437434313424987517531891145368203998329086865151248833625645567863740298397742783405267970015165358620026813812552194344790169289440822038223606218360105618852154152168496637886434061050281055613760360200323363465925493033734895631921307644481639236601187225135325401868178006133838932915485272554505684060229409404902185944047523033315868230944723282246159741659387362889777495094736963530708159604929268812778894177095572578862150793098548829744006499229853198046828954650334595737117597239208825268'); +INSERT INTO num_exp_add VALUES (8,8,'16993972447.28127448706331012335977141435182300864564477590619929411850566570121116077648455191420279921533168802007530482818299586988661597600'); +INSERT INTO num_exp_sub VALUES (8,8,'0'); +INSERT INTO num_exp_mul VALUES (8,8,'72198774884738777393.8687539247642452953425155400068591498151280875559609979248583367700231031634872342122563819478919600402159024059794279536786611373504966204744811722007869415559012475160471227957857756325962941799428857291371597146319816910515366298862558849452235442246081440000'); +INSERT INTO num_exp_div VALUES (8,8,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (8,9,'8551849703.98748751358673528924211852802333963452553842636251612056366144128630740476125273064380199240146487881028508694029546139131732304020786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (8,9,'8442122743.29378697347657483411765288632848337412010634954368317355484422441490375601523182127040080681386680920979021788788753447856929293579213610069399116912987384006656023443527501447464682173445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_mul VALUES (8,9,'466174236688165594.9218054325256670866060556227711696100465581464881295978997280335378678072434776702952026828137140986670189756965420183565968027969700090735690246176791371115610886533930223141650377886909408268207750238603105232560663571044993507074695683027062426288270199495225881785499139012931143826099668999261931834700467395442768201666740663642498098541516326470052372008385656719236306238735524802875519713512894448940917708118676095378518264553310312628830009314653641136566040400'); +INSERT INTO num_exp_div VALUES (8,9,'154.875085756903716715488911525453064308758123952566428258639786597308109810869086867746263482721081985848551254298524280231489145092826397833394044637104667137816928932471315095067524966582810436282901424423215992139000153713476369887383242289102867530775908269805285313842050961754114751975054515055089553180717444020378611767296609130477264722612784088270193199394531972594028420402254831778715196248487757266330454269044609134602570688339750190391651801546906342796660819535014295618246236706572780627362908121159003488810140236665846928586992082180006454824311789091323774002510945263351862712964422865623934112293184149374573706760114682326698881257123280119140924775171374360283137569618025005229268057970275164869735173660958715166148344076027212231446680947914004346760896298312286730627916684448923824769'); +INSERT INTO num_exp_add VALUES (9,0,'54863480.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (9,0,'54863480.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (9,0,'0'); +INSERT INTO num_exp_div VALUES (9,0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_add VALUES (9,1,'54948723.74225051983134098996071145685528795757427462111901537365053896571438476055974853245403475510333627298551845046116291696445177112567064282766115207407461565363967417615506303416694032848457927390574251904212425813072768882213388082765916956736282110801611726537663292922699021333445658549608928179155685881583228490235606377831724593358583903616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (9,1,'54778236.95145002027881946516375418483956830283115745569981757335827825115701888818627237691936643048426179661497641859124500994829625897874508497095086558766563666622720535497438693688376602804651302002795213923698663694204683995198328880575615535181012624198813873609885725228117274934655048553507421448724831939026752650108735245933317237310133362383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_mul VALUES (9,1,'4676749348240.390309875431213992853550297086049749814750492488995108783145961719774217441193547534210468967573344456866203963659951312519988497979489304488948342258375915152429008993288817366720647491166024151209542534474867042837694499222928509320280684557676243780452100132238968233413333851595648146954975713386711764268506890884764704949969602122157394714663532141060559896359465918874990769222345665160127552795532197771168442486088776803398878354288847069602460071745966589164282641033852314335279121191855487126430176047553895892632834940595958394834437871886013513058514896870683979585091413977173250824451205330441299000850618134248917380244749589254309567551846327349592529960432446947239714236828401206843011440433362544797025114476612133622499094287321570559088587999417440664282418005102546343020409520421747216'); +INSERT INTO num_exp_div VALUES (9,1,'643.609749344751131516972294140174556703217311736700045690413622699888869645595256683013323517984528456698303984909359393772036036540901870537096836621035845014213031549051156299974682317824766457362427063305495772666640279328909129870227828460705733995380145417663304348663705694070309475835826101153850359826502235923289787750107778906593010060115662191620280031872002110849782776325630424918493602259707267214006217268630948545349980430128422952869610116216278256812581821942763705098526140427280008360043829906543029486315209818099697988089748683904695870401517598840185535891464842870210715421728852789815860153472208176465166954851895457846723102438114697692610933532992841803219018495137378534010155991355251803548866919409031477821173935696065078362044927492034445482457329200246282082707380974745411383781'); +INSERT INTO num_exp_add VALUES (9,2,'-994877526002806872754342093885760.69667996446358567630831677089993316481039076439881735980566785462673358516198695146576524119916430759085192883825888457383242076882081857926408611052522393579396644731758241837010163568445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_sub VALUES (9,2,'994877526002806872754342203612721.39038050457374613143278241259478942521582284121765030681448507149813723390800786083916642678676237719134679789066681148658045087323654637787610377226547625566084597844703238942080799221554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (9,2,'-54582443595378013373024060492546032003692.4875677735896411267274323339692558458420972958075073392126734000341372096298914875892612108329218081214550050039133117695428196702128258481789017059073444323729583900855712795086447886053552786449313809589992185978097430132940882612817775035217244553616977182049775786664446683332098226841743818600819221587510039430478859412452506872131851471967577741190323481953867845129745440745526578327709351120432530702446916035797432129052518980799424635406993848916727957825620638983706180841278402925286540375225365057191075559133035'); +INSERT INTO num_exp_div VALUES (9,2,'-.000000000000000000000000055145964114074763360265614481666934002579974728749248345352023099030383962250681574081874554842623852433135871821620640200582985140388676650602814646133317791813938390695683843848260103199745295436998313216878337673674660966362155480524935736646623766057029148471463569162153009963312016563281545776175277904913263614668092319707343286073000287493274965714031678784835459999763925833141049057636632430975424499618419962303087175237320046300285962065818926167792812657620724550768858763098967149546312995222223400007044549870620849992226072041407997925405957501929449911416474388622107825120486594723448780503829317691081601820425151593487431389373265285594626753418140874747955925763163132984655078996173911578832035721963554569605730262976354029623260224710106409129114204296314733036'); +INSERT INTO num_exp_add VALUES (9,3,'-60302029489319384367663884408030893999.8854209703537480818248540990234567956069965340942024890856088355839135538265116174644003927269495876835324407641642359213535695803871472434650475144516723617632059130297610134243891145006222068960999879308472500422640481972089756410157246974765071949782242392661524488959954348903412713930092273629207697480131360047867213863018127928853922173643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (9,3,'60302029489319384367663884408140620960.5791215104639085369493197407183130560124286109130354360944260524553172025725325268378015783145476572840273098165721628341015996848028750420770651761919246816300854441592109844750954710317145008297946462099581451150385769713261452744310496166494545449824802407416426304041583975713483424241727236417259479541129474082301376239522310995725648773643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (9,3,'-3308379209762459471107480259839508279070920437.883503980178028214343751083865562028455061662673132221930429904398963590401793045470444301883103141901787466923883803951815572606105617157736442670792467625964359169270739534412932791178258858918086886061702512427989129732248215348301444245772127142869263635282888226326427510486246184233225114523636171202034558843515894542952126988613018789833835507734620046994907453602573865012044120483116345444810078666601100257620969379968264504287700045822481492526688635364586344704730579892342786173395802035361824932075736340405960099542224953439044947229246847140957298841482874444906129049023002897135347878048572628834749795298712449864571996898774444932083319581439741625832405434317985988163261591679157437224404970927012111196724239860528859217322132733404472897289'); +INSERT INTO num_exp_div VALUES (9,3,'-.000000000000000000000000000000909811507365065002714756487495210579371808512079908127938523896001746219475805196061435010714649189975968123072269549018826343830061696154665503565341929634172463095299662727352635590451263034658630449260378893723785917860125051787451512267088404686342938118993621396641623525252649748977992770709930435013456855344203854749977414354164157192885125263071636468941596567220391082793700307461350484216679632552883058303710297475827456761138832914743429330069022439380297715971317819244718196187172770061156794130040674050533617155253444764036426045091327368023602807193742585178432544430741520636125146531502042579276206322507516332917325631822606079220413965396706334639331097621824106950192993127113903265025719013680733760540930122186345919977470628988674677630636632053583144327'); +INSERT INTO num_exp_add VALUES (9,4,'5329378275943663377078725.59616792993138452386059664269485161374191901124632386474661634799161523147237015531446709484039091244606359050341194730653343894986479159670583937529516163204904273806158788218327396375034882788180783796976731912141525319602448709213495905899041406302673881364465504945113279286939663215197485367850132991968081639290297033476859158044889351836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (9,4,'-5329378275943663267351764.90246738982122406873613100099999535333648693442749091773779913112021158272634924594106590925279284284556872145100402039378540884544906379809382171355490931218216320693213791113256760721925653394811317969065642404864072442190731745871963413981746671302248281216916486794296983018838956112081135739969615171358100498945955409711817327376172085836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (9,4,'292388240303165948041827159734686.255558469787242316676287235194652580157149226950109397295920730296960145548003120827363226435916209781396711693581454960342091452830648929118261388933297036933167543189308061917640517578583521401267417187854611829815212778183983326568586118831109538377828156118900313778053576483381085207892754728937946691892849474364477434665960112125254104966566712906532318984871145605839506991591027939136026602051635433295687547552796828217859648186757719639965988287173297286034098497871707197092627676226053609131138590878743560287292934815277894463305001278326023708395571840850120055316276256138004565442099731931051413153564744766098053176049414330146267604802971221161572130161432525297614616942172815141372973870720928125699420370428856022295499447755488148545048400795053604349570217878099721865670458104653570360'); +INSERT INTO num_exp_div VALUES (9,4,'.000000000000000010294536718194523982241053267404812827031741197656209184880073175960433631103885281961037127283726462743623757855378209281373475473018922090781553213750339001555832360656399849031527008437303091226051008068950896796359518673740801770866360774945096397034708173365378527676779736929035450380795854046109380272505550244458858231227568118355064007614608452292270378691774826689216790090661497154742954386244856792006376222923780801296832612827123778915598893970651480451509706836620045721191411824060983487064555397842027454385628620582036592315345973096405447742002746762099231557054678593446667904250189208490698468539396733604833688133512716508825505666644390119877423938820483653319376926639295680552194966870285838815705038244628263602997511842285889300557188773128635554621378148419364876651'); +INSERT INTO num_exp_add VALUES (9,5,'-597892150.08771044822540810796370552966707032464017958269847934730769542644402913723848026909285133109089452632480800168074607090893991283808726990171062867538012237270000932798704781608969096508450960185964292594677356241956277714380500188870696516251767979457838109804726539408115452577436052503866633026489282425086547752714324273565900641436632912781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (9,5,'707619110.78141098833556856308817117136192658504561165951731229431651264331543278598450117846625251667849259592530287073315399782168794294250299770032264633712037469256688885911649778714039732161560189579333758422588445749233730591792217152212229008169062714458263709952275557558931748845536759606982982654369800245696528893058665897330942472105350178781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (9,5,'-35812445701642379.972368737320206275515144213236752803936806738624588812089615098329765811617509505790110909629109400553415312470540217508070421816878544125783329593128638405659896184248784794258084116406472768709113030915308410565617764394827427154923321461158387012978726512246146545834669665093228316853342805604075936530371665576147966721599968786161939347726656168798065647411457701453987215491345496003650288850096338695703984042549594979897253521041581573388369367579323607093487743440894765114619634001789457486407909224339065748496715380572175183589195611952939575073075140094901024063428239223964510824958346570603142906309198033196987949067156046076497974760641964978711558209708743776024313916111738542765749928287600981397080809041007714387564206594515733287925008053261840295560398311905155157989225181164097547541'); +INSERT INTO num_exp_div VALUES (9,5,'-.084049034261605466896663277055600903951276881294745183935726262038673990196778002490449355450474227878560465916800470848046625257516764244432096856845087412397406701521972651300484716852035267197801389708234913163750232707469240634303111868882057393120649919262424619226282082184091177505826009374043368623853156698509808569378758387708910629731005691079770517679511879694426434724918004419953301426679939010592502325130576915399009756468717124460489039474155719834555522581553817856854607844133431854471292027873672356863673617090151801474016666978499651970627896504709551656249007718965259502928591648533670568214972768900993459927860068104745163979267716597907297073374689384723943955361288974065531322408839914599555769945298758102515352082822617428033648130099822033393662643586331479103933840387663729387'); +INSERT INTO num_exp_add VALUES (9,6,'54863480.39378734225015137845671346015520435061071252892396685718794832880965812803098645730572474084523997120024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (9,6,'54863480.29991319786000907666775218153965190979471954789486608982086888806174552071503445206767644474235809840024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (9,6,'2575131.137912978352131546639620215541477987701194164886305951830806120142596646541302305984776928560906754259789485960991272272782091464270104432109904222200473616116525297615725803495463468272171161659654385929185160689572943852767523792651123455283534072794326647404332228203001469884016996499768656263775233430922446983838511590562929268821678518640501686017030536100955531423152839988008496919169395159653034847677470665418765966542111749439412'); +INSERT INTO num_exp_div VALUES (9,6,'1168873084.346566233232746391559830634361431940000227460271861554316197556566224118756340501278103405856646766537018954185964066240457859194626558143313125824412559635129130086906976028635444060218797992547370132082916380788496584864016645155338102476357490305222392452114945853620686975383081427840791892729407194179236897452655907829255937027286698570784397487382242990326347080472574546312522326038419753951437799831430690304084087684303035538181812523230890783372773953961677974396907303758903934808035747944477277528267001070234880092255363221274303820343225415479126819937070570562654065195009839593938440374000473302075568746771126391307584779249330981594640387657042725725493800876630516005713789705652827210295338592985225924959199657729900181287069808881130884115897407246324220524401243575641227725030779990490'); +INSERT INTO num_exp_add VALUES (9,7,'-818934540016982261.65314972994491977243776717915257186979728396159058352649559139156429817562698954531329940720620096519975256547379603654362598494779213610069399116912987384006656023443527501447464682173445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_sub VALUES (9,7,'818934540126709222.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (9,7,'-44929599044588573810654775.83678007633232843418115790847152455559258007804727916986432256198687661496804050903769496933400455947645400628259699874770581538122521805603947464462448454681701547899144129061961394870320463199545502030106801911915987309444301341575451240764927967432593181449618816978119423290767783843864768557371257918447461479570164065303599994081990686'); +INSERT INTO num_exp_div VALUES (9,7,'-.000000000066993731076524206362744068866774567920404984046399050881532938231826344009126898802592302273719505485084766150904380671495128604515800845609713368334606489445184535043833069145643553083555507533900955661105251251918425885537513359541698046533092111969478225528665278023069818968531644884466229545497943710817187632203193468836772459599856856811131193744272314519908999458320275710240994009061040198159739169960258978462113813370513611735006229733329565083659159456172425715216475781507996483885669437855000029758892126410922067202159414570164537031153818197618428471046051340835826664787585016361564969663413176434498159140395476980277574789931364078570781760777773379636490084338326576889857824344578398580499610233575273027387501809967324874264742269453420400624883982643066864175851881870402856698'); +INSERT INTO num_exp_add VALUES (9,8,'8551849703.98748751358673528924211852802333963452553842636251612056366144128630740476125273064380199240146487881028508694029546139131732304020786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (9,8,'-8442122743.29378697347657483411765288632848337412010634954368317355484422441490375601523182127040080681386680920979021788788753447856929293579213610069399116912987384006656023443527501447464682173445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_mul VALUES (9,8,'466174236688165594.9218054325256670866060556227711696100465581464881295978997280335378678072434776702952026828137140986670189756965420183565968027969700090735690246176791371115610886533930223141650377886909408268207750238603105232560663571044993507074695683027062426288270199495225881785499139012931143826099668999261931834700467395442768201666740663642498098541516326470052372008385656719236306238735524802875519713512894448940917708118676095378518264553310312628830009314653641136566040400'); +INSERT INTO num_exp_div VALUES (9,8,'.006456816440893715330247418029019114736889626790871612141686117271826070935285769018710680035004320626745647926106882508048159628931624522666638442625219959259156539178378186912871506893482633695438850964052285542425753626455183282159259999492971992739484319464700978750304962671213318202670228197968646486740006148091321740497272644910882302412140576608739962605210964504469426861972705740810533465451230811358870068391007718532021526225893542801514255726272411690175555142385382688220121052891017808391607717500701760375927811435030512071347521837090721052128992926357375527600337655573639413811262412492632491693179011503973930804928749370652038245414768103001067902012962988384812280453070895781287237746786414435546976395632454474312533482077585837153357017362048554313154580576238549196250793055676215164'); +INSERT INTO num_exp_add VALUES (9,9,'109726960.69370054011016045512446564169485626040543207681883294700881721687140364874602090937340118558759806960049486905240792691274803010441572779861201766174025231986687953112944997105070635653109229393369465827911089507277452877411716963341532491917294735000425600147549018150816296268100707103116349627880517820609981140344341623765041830668717266'); +INSERT INTO num_exp_sub VALUES (9,9,'0'); +INSERT INTO num_exp_mul VALUES (9,9,'3010001475769225.8286280957637941018500905354415197182850820227163907782811814730309044010416886791014702373809932926301368137684091094408663914110947072451332976891128659038142954192986392936981664792370678656287232795203974766040821110221158579481177539669363513848425151485663431478439528936592701070340012569297177488556353760756495238304538439278682066056721729656193616571456456325016960870401748115848423105783116854283646624807603476682295234280408938557209608025246638166902335016025467565869375885610813662767004038102486303756741615124814580306266901273803721191779461890468156043551004644728343579032524687612403663816107770451694666844862368101122025340182510019516924578414085461628689'); +INSERT INTO num_exp_div VALUES (9,9,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +-- COMMIT TRANSACTION; -- YB +BEGIN TRANSACTION; +INSERT INTO num_exp_sqrt VALUES (0,'0'); +INSERT INTO num_exp_sqrt VALUES (1,'291.964716019333021494947753821238960905461614737525349376826064492714634914263808902604580614735501799528494357560837535773816469841426747889103714048646989532842972129124080559131220979335403729022278994440514872845756198274805589586120535745968205107562348427941379641465378272611453955517402598409789621997041856848783989993820946766177453801729783316269310186191833995557234577548740940419224137195404391193633808203715191863638616433190672511651125299379882126530500870287424768024674231651229908224729856278167033444719242144302972892419034855417126978468296581589282861879645409909873113678361180607775255758820910366926076380306290306477790931129670172989289536405788838857428768869345763784112862591549008321546447442552533919976570125718481191724503352619626562352280522949665158335559389298720990302071'); +INSERT INTO num_exp_sqrt VALUES (2,'31541679188064906.712574384704440356216787857626740375004266523720148374188511622980520374202725176835435173058936870163875556102907654264048353814040480579464700545975346621546520503928314632418705230212623378642743044255181848913683862360044189531298446109955034944189751302497670367665492719604026161836224535961347218522748523360100432275693829501972749859329753224444694962089604095212784768854310289429208671271394086829270986183171968944659703708706544668326267327938226750760690620258967209626420981505237183055363540806281098871221581265173394406715458619627534396065960117454160969749739483126059760636526242783235685190739315590041294766649891987044641492234243404608847939002062827210734973778130441825067858641461599799772535304379732674727995848518807202053316225824685704785148921785964036119338754973714515974054'); +INSERT INTO num_exp_sqrt VALUES (3,'7765438138915239878.949520541017683429203286303188179443533225547096446554008374834292278237558244698868300666061834105683999048386497322007336816482648302911579331582895326423063492240235074387242190187374869842856897538718280497895072291181675294000739548676781615025944675912072664211455701112700937190832332966000160156597821149428032612782336278939437593991008833233156511435294360065004167893309428565243314846456225604669764879344135321428948841659419438769652686215993544390780212859309497190065178705035652106614050448518931820975038314187040226298661787490226917902356569717171481159691409131778764973037046501816919243659681416263730519167614043077472097520207347950292377914586524327206547377189493301153212000966249655331053184913579513686655963686155890934436604123384536027235444923674128269748280097789270784333442'); +INSERT INTO num_exp_sqrt VALUES (4,'2308544622905.016172868282330339228589083058636874526727829838244942341440716909466939214393597311710652963849541394758298277969240038668406494621950956862959196896847352631445328917063551082418729435554972200530109505384839391233286173517804321019323644218483570886304028175359854335870835404627608254205407525763332087823548640923282031978903399118139052814618531713327991857575390136755426466065839913887477577516426991104516201265995293600539957187007068885368699949673989051443005684755994465547159213587471972139403333249259808344536605314911144950465968669770276463111776581675944967401948957460097365849699783091843609965345747287667911324039374314413430490112443463386381631812537639503425989372084906324702158112088898424705684574998783112519152403201231176840068666882123684602080460378627639651465436618032671756'); +INSERT INTO num_exp_sqrt VALUES (5,'25549.082770905117529972076915050747181125832857399138345044265535151111965091602789684342996759657333588444489085160336703294705499665424408218434077722506748278242942379566431768762487954917389137120540138359870652558814224523699917122023018717544160579704907452934297025088008618627873220397030397424422097405152321366495319708580932627092620533785271831833326130796638935296720064431288560292191928489034307645738331451165431755179025359993690642194334018457793169983249853388987495489562746304107188105521296156525984787815685365255240654972150342496329030279439124533240114879332406941960563154881888172285475336782757262639979527682925214971861707635327995621436598536743180180978457735632181738067997521785965451385630326464388080990200265186437768409003553910194212076755448477164192901658547251079126833187'); +INSERT INTO num_exp_sqrt VALUES (6,'.216649653115510782473161631235601739254284877523828136703593069337209747459679979369185882839688430004369697316986054374456779366220242645866798278985273820408495361607183119980716020227424205519727777568954933592987351750339481522149106749713967143685591960510946511796062486795368200503801097611436787402191532618456991115230272084771674098613479989808680789347124789253499967359190605681912854639520917409710307182238065185749856554472717209097115325999946728168357936779767099041518574001682560265549916593333117469681763348860131760281253987626822958726920016922608371657319505153308390495179319529587670415367205193280809809356733443291197315823747505896510820272670040485083775482983378341120809542502350385555577946098824446199419354197416933858522419312733314383889554606932774046771497129486979593226'); +INSERT INTO num_exp_sqrt VALUES (7,'904950020.759072496304165474991957396337281699986101765045213964054286624338102141970514306010139529492299343393832200631760194440206005974547202512275476562767685193838576516154915404389465528270010938533075930081897392863141132529694804621418663424569202655893682412466871297412964570322984865326770090075582481194532433411398133265643849129084449161396724635797324126396071308557057830046688990212282866035593809633839882468628249964862932050189148498591642162462777480125024786829078066012617362076651920045684345679767223337287825546294839320770903419463644110383560050404456170063805115223954191445548226706113970164823214416171441655706141596091717118495955441099867737827763335880891937222647408575142200256804313345924443344596462585960919126827045197885802122062165934504665811115031150357820196176799560314653'); +INSERT INTO num_exp_sqrt VALUES (8,'92179.098626752893864900181023972781406074846653380680747862421481598042923358730531575438403865501429843141967819802251116774924400485954931201776260931315313253827346015775662310076094882239170765060649024538403329505426563390044695320714825481746233901773893996663258170360232639353378395244461670781152793416950717050461856097473105730100523010642696332151571372764781034028324977128554099993021459338419164426784774496292405945103200724413639660488309795423335142455569853549710795692020963174011003447023610692365550245567840477105794884132665155376243735213346877116105595296043532605899184658904822980397411096930267453332143879534914237169761039374689145860503772331147367757318826885494994339695470190886515765452545019167989882527248872835783707554463866334705735781549392895480816605355996057201589681125'); +INSERT INTO num_exp_sqrt VALUES (9,'7406.988615277484686670011157489572203134420118818648711986549881046321377798441006745317356200279801348355202517703531020643333388857073977704009782384103170022716610432579974132111487533733493986910583223121269323909760573942980360508642443245341392335557152177332615977623338526935953706604224108508582338123915133189529507760875123300397933931420500010248194253078118618381590347297853307090813639981736227771834732256867579490224181748450683295253634852775448770576585177080941820456051588076218688792321741398867304684922665590162004919486643750098085197190000638539994723704724550600891137853975703823903659121582583388450687255538838161486019214242094423895463814933532217776443473765708693285683261505695170847285063013324823850724236845500162436661946026097459146424122412596018946436589967013641971183281'); +COMMIT TRANSACTION; +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_ln VALUES (0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_ln VALUES (1,'11.353265918833698201334218522735144514838241118349715803442713722607336732214173255618762341321138898556011520430414052782971985419141860417968593746833898952016980791997105866598425597066404919489902082738711038276194174786383758877067916049129476352925010880025206629976454341252818402788928939407784629386362069592202090897264194883276572978998896242281239126931595483958092059051047739223830394259082355969005503976135238921488192773135287876801394308064862257453262299764712613486466254696464150007113953810688169396432889052881763511661127351872408811370081346456019961324265446884877073712053408327408917588393884214304220369626106333713688792094943405258431214313197283237071070354654837081449831786573831004911008790533179001070424813584405346221388686999574752038655226138085374176702005198770598232862'); +INSERT INTO num_exp_ln VALUES (2,'75.980172429959420723484178622920965327708652620924912610122049843800380131746381968266727388919414524075492921510147435877107720844487333947572033626887969846858337336557672107987074468763307953130616555202495401302128216460637786993535376622372745654109623249396257174895352222213037880060756992073605135503615371392439827458529942230210514752764526895030759481226199720092008002458654297737883219558685499445394647863430593136350562417924068100891680398878483362058595716232013516337079804607378041880078724811071904523716775991447489914128580100888252698281559809224785596795038122963619830942475652745611551345360922016753939774272970008770647516790944335173711498988149783075646985898883858697162003144539047532603946093022417842140993960433780913606807466518632121884254341907122163281927271483110212890483'); +INSERT INTO num_exp_ln VALUES (3,'86.992429107491709045555322727377654177072455841678650084144967727028762699430180506209786297136121512625728883607972513154010138109866327600596617277403558404624813332464431424791338402731178416819791932126837396086742033973404980654712734845137075562739300866280737071167943367603243180515859476717635339619107593771719314284984269343476343816253634799874584843436046260962736006310389088154751401911743739429257286834178656182340416539923956100441369280015412718483971113838923221170027312390404790743389872757674342133486652087007983701950040432125562287337697971646750563062524010514537132255605131615248097901911480464339325353279118429890601202554448469387179349495284716473293965884844451619766312048304583068386805927433174443889441171878078987788018564357316138422561213329104267180509029624308926098065'); +INSERT INTO num_exp_ln VALUES (4,'56.935276817066740776567329017240462885579486075188456418197311631774373422196025180114152248099799048545382060930401786002025479108787121595516444894009593031141335985913019897883627990503003577804436730367402618412514152465206336556967419434371593632864308139215157721913158949066717186782560422199668568894551013785702491365073449320535603830475158258853167712460432995074161536886421366716995573365924430692151761737886552457036412140640821310927642146210426044265504978418405684030862182425702683702307323138985481047994648222224089112998195621687911787785594701557252468626097576375468916953563766801336922479861708649876362257086586679701715813254414915314296890025577780265459584203893089574567331742100451277992780400302806430264717887468808962517029442262560742822875484362427192693300423729233467613910'); +INSERT INTO num_exp_ln VALUES (5,'20.296713391219923821414834924710998522858242536565236229645868008008504475111229451635162536658197320282791428572861452713483981402773630985812066048575864982038046409484905688236579134672910905547858248343712686247795669280482288748331949478864729205285910525962001251260319741279139167559906461672936902355959755164523720443059989357054368460911050707727029320725144824995614445423492687177126412520389766864793826362309254124276325522276592246655562770110024099522184080118637524912964002223613671995639705240767929562023556724031894855094820328152633412077228479168557819219970917880393852962560319397442566813746504969336443969816954424715197797253670026862362130664772772977978222813915593329422557592316429203293264572088112274848838446633519530653849595288125585730314673691986554304725866754516304420665'); +INSERT INTO num_exp_ln VALUES (6,'-3.058947463851998053084898503420969773173569760507671013593014983772013099601022840164736581595033399273677583253456908293015637115395777673836877852797643436458673662566205707359569792482081945396989472318998080581824382006377064185813936544714612287417301161454496258176319380348780934551188852900784476213986897306897793456700682073399936398243222895442594762628402487110466705108765286617060826203345783502301472192906817785365563881556293576463515218574477264521950513789471494214626744754200844840310516235570475410854073969787604451971790833680742315518808178608136598148628107328076871698598743664423452623124027059698038466681488746505289551548778131621576387262707147068500249466398507704796800459013580425992071957391417767257856002976954566094297724379688683375704613872658653366052459242767328235849'); +INSERT INTO num_exp_ln VALUES (7,'41.246780548917246608934265057073076900048579756649769602488660179351587788197892095257027979113051775079905924990472069951828742350559917110289416201523653941731339141666097617614477426376799479821365070373247490598890520285155435501242427296281987676879064510605563522117334502131946383957407685328562874307957108543536378261847119286989184256009392692140821396916222386573424618796707564187152459973446833193743614720624765332006827171872712331032607870580880807058576154429597725560836582655488602546786785520452359711161305828045237044625934404295366273012300148250900116489718279757540843657039519736455668388572899273464839528462223812926410544976290646668870192676914370659142463304861500879195867873346447316374869974900582948166687948531910220128160490935170837209017355954301127162240133341813847180541'); +INSERT INTO num_exp_ln VALUES (8,'22.862977375646110045361670561177818139082238721442691850491173190000619222046296383571431877856442345505931635735363450488731186880557789439424987680284612480261693386095598289519783790826332183796775862215503493910816035128476952347072320869461206895223935484838130924268616681347949695029657753251443811448783435000569829291535036468240771401957519222523032235686030017496209956550934543164421459898155836108824017735809352580723262896259290484291175350770265895317482371895188221452083719817251845416195168686335127805092334984596224320638378502008767433534450949989322562311171685891891122105437154553106840103473941148230953978989145470651955269817951560544095229079088083494695756914405635176899994279484466773598435268700064279990885608144109747858515514066444373797446449729058958270758597627587968112958'); +INSERT INTO num_exp_ln VALUES (9,'17.820358481980064387183481028572263407130633079314879566896470101569251997264841660326428805413719418277889123643557369421967068805165885825106611310020187894256310674762734896979157570968168599492401269694048046876387337971177513661006711375440365724346137980004810780215236524986274043416621637509807126148966029923572853117418545426960105154053049098579812135003711132897895016476695223444397389521434633067499404903493027304737402519428197015899833229473322655155458942323004249812974150129789653469524573801259946118454333405580647485894435301530550214095993989552176497867244278699359917247910082169086524111229983698975613609318418313798992088206507831757327320958918656453341769110558376097374227592021075267882222057385413453949580066342977546145482215220982989992069525148522710254796105001938615214263'); +-- COMMIT TRANSACTION; -- YB +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_log10 VALUES (0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_log10 VALUES (1,'4.930660740129727276654889314296515979425461685461970306647398411855044094312185293195497201658739777714943974003690119189101973212927970410047992001003936259467465542044528955416040460487922970233600641954269411521809500203864460110903973264337093883907933081597350982496469748131390809569321256206859934619579029279954574676601709408712255490686948453752571699579252140062805776361984468580258289509013081691778727372026090522694670379557247829136504595898935235926069699309392675806881162434168418505908116911054206058735257796918687777716036307205415038158583184624809880157060625643069601549803887864772092583549388533013233603450097615537162442973385137488450178790573546382354482351187412256794374383453695483855501587939419102008302408157959291557415763034668013452188944554607063362933134950906875499201'); +INSERT INTO num_exp_log10 VALUES (2,'32.997769620388965086774969704518222090258389987679691893351902336370051104718852164011301929506188893338106627980171059175447833290713847317665944354651476245003161501753612545484635275306181777040447675475670149066399611203341262105766118892586541910243351018829302798733989560900125591073082441126709911019648451232244139674063434385451279378543163944005973452562993913383659295688375546058256196254319767218634546732685705517341998116744642480938405113447415486950667007645850519659606476727681944251201236366198374488204017630268083077471516734133869728427050843306716313813724061560369884508660845630727190444623729815564381063131729592825825486515070406390371638817503915214206586939112681762984038333298146999891250107667687034785493312416966635780188163871680959873288697497561452228182734430749066579749'); +INSERT INTO num_exp_log10 VALUES (3,'37.780331928743475574895606142114739140772838801045013007323050327909196792739138159615327729728110344767302636436234256468332011934881494997184865617793179255006442447189720642997935223133982347184994174261506212652322213673745795726283311685835974151422721233207287206894148660531800622455957268888702309499182978182878524951883775154983702898237404558813230370364953160102391101897560104513279410610948028599674950811462114131673380477843456965645417025376374320207504913806546872166094337441573669261285052323206348035827948287081776955945081345131570610652073053464020209215624179904586956137079321655773178387441622685682721151900601340680061607114354850640946256225260430676099781727317540719923791064452012925902993317349390523278687089530234444415688602090547516647302454865526291471706301790881694022223'); +INSERT INTO num_exp_log10 VALUES (4,'24.726676547286224970759328746582840552419566534667446425423046931401641497155587075591229106937829957279943690528061985864558314570189069764367933957499905044566413640017549478921384160584906257607957223101377816440084188042395098536074479064548620374152344954289432050971466476174493306432228880930006524504974367146536665170956555486181410864034862861231267121149652317599303804477688621597163730470970207231328339082779056152481480926452142005969020950341307977091850953883445808399574256295803245530993204179747743812544604144379381347499056545148243304041538981954204310612049423688645476667184129189153715486929216331980316967699254518020077226689317148303152585009031597809279387172427408557115400021035692880631275593381822805377317270568779655383061987766693697518921188619814204902583361096973421134004'); +INSERT INTO num_exp_log10 VALUES (5,'8.814750626578650238811431417807018895270298639823442501111235973209197727215795256506525221092818797578008152140054383421240180435087611869193019443372556081555311825248667278358330916098378127100899126895012782320751838528480712942601038190627182482614147263228588284866661508052724762701223357327343090598060805245853527435948381893458352744679795853650453594546267600486696643924152372736774331080527157374379043696696647158270918245668579680394279565181670004245143555617589138267976417280970718829942998800499312890580011246294669585429723974582350357991472101919333996770115834067969654217063942059882195268353998096891812525364797586486311202350700339609637274043915687880562465121559531284337603363356183320193656553931871200575467929714875483123706358278876389849119105053294688326141759401230994901405'); +INSERT INTO num_exp_log10 VALUES (6,'-1.328484003982869642690619298690906747763234110040562640557173509402512757735587333095924652711056556491908059708986413635120656426593745303715671199761364516107844087845783714418487426723538440387069985879601248897538855843115404484229652166941838283489828419407478748732927617251897244190697443966424660881366993754577233476597163021768156814527570512834684713730559883782625870597080940193303268818336816535968869931456641949301731046034660616615392129109391145214470757259042172416816936479713743188047425796931722546185493217275537303458837771965375448968719169174136287532752370175863826715450565025635651343928205805494319778539652563499901671319955144823432132740582617949774638538594081514904904341299199113721131520557004571803778698005652464301037962272085633628653321081368256925971558076970172779715'); +INSERT INTO num_exp_log10 VALUES (7,'17.913249188669140643510654105014358282516966474257460687880559542190804665566625978925406311113121982595279826214959603627387555578965653325278444455875162277940655989601428868642914577248262147833499137348602966573601719040813549936948178463592211685237720748377879836890106515699728652218324794927458352954247096536337594789471529493944292143186953509162522579060020018226817623648563806559917579317916242706559131476179714031602207057714677845347616752450567251644277767418397621490301286115159509360375419599968738067461569666699939732107480135216621373057421990702923042287910730395998082514702629760389192370666675364405730936537832803383367187639209534697198515928978064543150195911463663617683085348965065679311986715357338675515370634753254774665197233934933271954463040729779956682570415317734489164385'); +INSERT INTO num_exp_log10 VALUES (8,'9.929264914121995501917993119394933531225401243275938207624866270551448544301376913376130982251708700134720886862945040266148728213253651323129942781577143957084726727561987639140151337848818195806259935747329665025823709044567138449084349729747202164413995795609659711723455165142329822773177102845804114214340046404641970845707372809306219463962664551623665322610139794354769767829380018857313559373283673392337954610346290037758389035140213224696023751541663171574697035012610534455189013755134090933979479069288110010954211669067225249755249337768792642303351914884187159646984708862430789018895140670365476746734456807215043628059581947593694929159076346249490593187993386780521089745819640214783614157516171005086731241769146397577246387886107367648843380733370112546792442909347322732196805316614555689762'); +INSERT INTO num_exp_log10 VALUES (9,'7.739283354261751283625223433456284905560931805428759681411970457812279544250432389511382263439324085689734710188041049046660480575958686859942980599595036769090747781359217248301544587434077376812293034848418204834388504169166350770257248896025815531248627658465029806509131631454856186387892627989218208026727504548130018922325585619738185507999433763118148418722504204066578294826264005398891049629199412773138457218976050467479292777172717500219850781664314597312411301296201533610562886229900497272268364496763758868455934979903774531992886483396489868888731578355541611359130188566524240259770918423445785338175040098706500034487703124623745259139247432324145633151895802637182446905097253961951018926565652497920605819785424451050191604602898777804133717341512568151920576684198443843944721398831404081859'); +-- COMMIT TRANSACTION; -- YB +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_power_10_ln VALUES (0,'NaN'); +ERROR: DECIMAL does not support NaN yet +INSERT INTO num_exp_power_10_ln VALUES (1,'225561990715.277245515991117670624124484084762557459065170589803293759247930753528436379932442146759103295277479258327642314622036941865221478746258727236601688778946696303277607709407496616423493315166963938393760548678730128692212077086588682984700837334554241405763691119669847463520746595280034536307041368063462023793177898200220207765205127584303464304601759554817607633012272490650155253979182893585119965271975927569080191838676053084168631217591768468344106219831174026139608715965691941366334940196517120885214887008671956523579678156919416435031020452971977153991139145404842034138317592877675821045409772456977018293365238179815614004574330200783530118851005077771478448804470170641452481992602803877112958872108069738434946694089025321283178188028224338756015337492913115267362635647236447601252924834642796058'); +INSERT INTO num_exp_power_10_ln VALUES (2,'9553718264533556311125292459627965006385666643531070061102266984368939757379.536714147420215784125170401370065894858487440153494392538261078415409784085960333028254155527328359894197540839556987826344995348426293585457768226283066583722499658006242709930685932246087653832230889613022921575445199055131152661556678809191264086381976922223866204038615136758192929883317207903579770917317641181652055458721731297347443662717939116561947785705140374908203404860090658919334137955075887697259604047657534191202566335372150375993361370075961180728155127447781364264047857624746079509591666068708743260905728661917791822925979235918475633100283148558978385583805341715868143937062092264994833222352433299015979561976964779350640064096690062929265992966564232453102431600199173711947391200249130712039686700111791790265309426741120465259677894665532560198051256215915373145226284270408649736509'); +INSERT INTO num_exp_power_10_ln VALUES (3,'982718444846268846508445482774217796844461660819285525931206164100817251856409365450682.362683768066405322653747385034480250394145008573806022660379219602846285813744865438912887625784087005970975437905783802114553690522787857272953842288090141945268495451006273685577260054069522075046955466204804067271437138871789034722069934693546671607506851844248427950939791205412350536883779850165603116191193657054604569586553874805856647223849267039531773072343908345333155562072887754900969504551717514980465801806565999410206735831440712124661645970935112535081991606671600328471264697018198676317466846450405861359235297846597981143547119390922405594115478086038680663368675222949247096131378724350715530605691796680604309063173515781378545860473572389718345696107553363715518601596249508215455106779522851210398208919496668879040223859884166805448827948087400426315425231119801173387715922086154065273'); +INSERT INTO num_exp_power_10_ln VALUES (4,'861542720105376650266753999919217194383259935058507531116.774511336660822591851369622743235084609149542494189385785321912210129989390054947787009383210009523204976629456268332186620016067379702483800883493431423160815760933380418976582725913410929214462739708321325884209636272001805871036779154087677637129248122540412937033791526383240502286607736226090213753913654673523613612439527815137888202973659987501649474772884055648603290154867585312925699571949539600328906295652872654314913539778815035321695215634102441494403825526533235061083947035338872599854931230001361227174477274708230470794066733245241594719912710139298949856243576688344051439047966427547889756037265151798639614843866387316916203238068277912991427278268083231579195846744438643659745041780103653332041031419793815914447232121937821142169172566753399257291244398531365781832297786941359729799400'); +INSERT INTO num_exp_power_10_ln VALUES (5,'198021976607570296508.271597639984889464620426933601643322058775615235389194561064983706229795978402690473201671702614911129095149240715527556855309177671128442458698638704394974473956869419481315262823632891676087912529523219333012290621046361106033860210270638559271706082115529424772192777643046125905852037759566224116373416253787241195450409652089019290072319861181399387753223422998872180810295299831487867222464355713552301775702554189470264147325049133532522718679336524769566984150923939420759804463781082299907043016120177416779442865059261387111806785876531152192378576258351599534512031062777609734092707165605364139201322351960602280089186180302246827234844736393745487324460438448807241887783263546165171099497316415863122023114646876909575845860402164818094500541234974716577550807551946414081410743197768993152975501'); +INSERT INTO num_exp_power_10_ln VALUES (6,'.000873076977206566818052116526263730226812004454463281371489634779519089200224205946321120805055212090024554381349223642352209212670470260295303361873760972918129853308169576675500721645609379420329169271088810484607337679253503247351324049221970104335289487989027621978310506220905131150125321713385148268584530413680037620544212746920563790371941626294733473967065607791756894237438288480748407449237446113996117912144587258434808327522518688617394025018756570740098795745692805352377041347367240475846033282850136270250633825482156304826383360291164928049344226886150285595932088884965511963310715773499733217615863523253012606066583814112265708693122563204149232245895551314975524172504103194858904869273185785182598234060315036187756490539352752560361560286717869643902435677448962235275054804452967413005'); +INSERT INTO num_exp_power_10_ln VALUES (7,'176514565873872717825163931126806100435750.096278384530154766967061948052237623936423931849868926020451465515367348890410352640552194499619062823622476972850692557798609619250753020363520533767813563613425606228355802781302735485038377521515850536680425059519814786118919994914180918228654298075183514200191737597656810036850772127169441661576862538643715648802139886576391427423689320082366572297580054381937437005879583216745596935643579262248665490169331304003204939561361718554509909313409421397022626924406091551900222555950699170864234411017062042057683304265485826061096835531732950909546314722726990314852356462874701181085379772134121978510387397276859318242238150439474660772561390798432890789762504242822787017140808209820627435991445529404692793744568204608385843245177656436105160780897472099970336514833257055017279707999437302548655364559'); +INSERT INTO num_exp_power_10_ln VALUES (8,'72941951052009383458167.300747500436981484566111756088702608000390737594784514635592222758882092500858797317505303492923829092720870826490477962201959426813271424853341826896270963213736922458746003100613943600855942721319226948714369219316345322636075285343544788982588956431405042577296229122673590336976893594798942025893296105815818487227300314490440902574022885833779324177053242170024559675073866612316965636832258283516275906085642459351367507561963945012828379111856700009391438637054015804558386733558956649061672420804826896303889067785497738203077050774825608647969196321506624991188638449047860249367840775936911749905927108478444112230174584693363226143549933224252679398881354887872642908328737917862751077365602631600279486028043329404269490375935308156815477700961014566228692743960491745353377403533037122586797765130'); +INSERT INTO num_exp_power_10_ln VALUES (9,'661239032819374816.097553651299556484820492272269662685578275493609248662925676004753503494252951243895572437264999063878330704584509915845096232798927524470286655554736724913758600775591269525423912692080421094644542553026831758426157681271572808657664918053119324646138457659418857926209701677786068580819823633713337632456905824562235373422309621872998037966404189020165296080436871220718574009921789858751384547836431858428729570977259373272041837411903005303672798845573379758630607982213326716018594073712340609488043353995410508475153538231445235003980586600882223782814368245305160648543466496726973755388826656879616734762068443462618454921858705377028522664844761719759342490380417060255776725333319537746890406213693117052223545525717132695297770810635066731941724108167146710297146989770382041617889670713111888375717'); +-- COMMIT TRANSACTION; -- YB +BEGIN TRANSACTION; +INSERT INTO num_data VALUES (0, '0'); +INSERT INTO num_data VALUES (1, '85243.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_data VALUES (2, '-994877526002806872754342148749241.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_data VALUES (3, '-60302029489319384367663884408085757480.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_data VALUES (4, '5329378275943663322215245.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_data VALUES (5, '-652755630.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_data VALUES (6, '0.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_data VALUES (7, '-818934540071845742'); +INSERT INTO num_data VALUES (8, '8496986223.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_data VALUES (9, '054863480.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +COMMIT TRANSACTION; +-- ****************************** +-- * Create indices for faster checks +-- ****************************** +CREATE UNIQUE INDEX num_exp_add_idx ON num_exp_add (id1, id2); +CREATE UNIQUE INDEX num_exp_sub_idx ON num_exp_sub (id1, id2); +CREATE UNIQUE INDEX num_exp_div_idx ON num_exp_div (id1, id2); +CREATE UNIQUE INDEX num_exp_mul_idx ON num_exp_mul (id1, id2); +CREATE UNIQUE INDEX num_exp_sqrt_idx ON num_exp_sqrt (id); +CREATE UNIQUE INDEX num_exp_ln_idx ON num_exp_ln (id); +CREATE UNIQUE INDEX num_exp_log10_idx ON num_exp_log10 (id); +CREATE UNIQUE INDEX num_exp_power_10_ln_idx ON num_exp_power_10_ln (id); +VACUUM ANALYZE num_exp_add; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_sub; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_div; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_mul; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_sqrt; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_ln; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_log10; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +VACUUM ANALYZE num_exp_power_10_ln; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +-- ****************************** +-- * Now check the behaviour of the NUMERIC type +-- ****************************** +-- ****************************** +-- * Addition check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val + t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_add t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val + t2.val, 10) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 10) as expected + FROM num_result t1, num_exp_add t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 10); + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +-- ****************************** +-- * Subtraction check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val - t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_sub t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val - t2.val, 40) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 40) + FROM num_result t1, num_exp_sub t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 40); + id1 | id2 | result | round +-----+-----+--------+------- +(0 rows) + +-- ****************************** +-- * Multiply check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val * t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_mul t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val * t2.val, 30) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 30) as expected + FROM num_result t1, num_exp_mul t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 30); + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +-- ****************************** +-- * Division check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val / t2.val + FROM num_data t1, num_data t2 + WHERE t2.val != '0.0'; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_div t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val / t2.val, 80) + FROM num_data t1, num_data t2 + WHERE t2.val != '0.0'; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 80) as expected + FROM num_result t1, num_exp_div t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 80); + id1 | id2 | result | expected +-----+-----+--------+---------- +(0 rows) + +-- ****************************** +-- * Square root check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, SQRT(ABS(val)) + FROM num_data; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_sqrt t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + id1 | result | expected +-----+--------+---------- +(0 rows) + +-- ****************************** +-- * Natural logarithm check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, LN(ABS(val)) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_ln t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + id1 | result | expected +-----+--------+---------- +(0 rows) + +-- ****************************** +-- * Logarithm base 10 check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, LOG('10'::numeric, ABS(val)) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_log10 t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + id1 | result | expected +-----+--------+---------- +(0 rows) + +-- ****************************** +-- * POW(10, LN(value)) check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, POW(numeric '10', LN(ABS(round(val,1000)))) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_power_10_ln t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + id1 | result | expected +-----+--------+---------- +(0 rows) + +-- +-- Test code path for raising to integer powers +-- +-- base less than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of POW(): +-- +-- for p in {-20..20} +-- do +-- b="0.084738" +-- r=$(bc -ql <<< "scale=500 ; $b^$p" | head -n 1) +-- echo "($b, $p, $r)," +-- done +WITH t(b, p, bc_result) AS (VALUES +(0.084738, -20, 2744326694304960114888.7859130502035257), +(0.084738, -19, 232548755422013710215.4459407000481464), +(0.084738, -18, 19705716436950597776.2364581230406798), +(0.084738, -17, 1669822999434319754.3627249884302211), +(0.084738, -16, 141497461326065387.3451885900696001), +(0.084738, -15, 11990211877848128.7928565907453178), +(0.084738, -14, 1016026574105094.7376490817865767), +(0.084738, -13, 86096059836517.5178789078924309), +(0.084738, -12, 7295607918426.8214300228969888), +(0.084738, -11, 618215223791.6519943372802450), +(0.084738, -10, 52386321633.6570066961524534), +(0.084738, -9, 4439112122.5928274334185666), +(0.084738, -8, 376161483.0442710110530225), +(0.084738, -7, 31875171.7502054369346110), +(0.084738, -6, 2701038.3037689083149651), +(0.084738, -5, 228880.5837847697527935), +(0.084738, -4, 19394.8829087538193122), +(0.084738, -3, 1643.4835879219811409), +(0.084738, -2, 139.2655122733328379), +(0.084738, -1, 11.8010809790176780), +(0.084738, 0, 1), +(0.084738, 1, .084738), +(0.084738, 2, .007180528644), +(0.084738, 3, .0006084636362353), +(0.084738, 4, .0000515599916073), +(0.084738, 5, .0000043690905688), +(0.084738, 6, .0000003702279966), +(0.084738, 7, .0000000313723800), +(0.084738, 8, .0000000026584327), +(0.084738, 9, .0000000002252703), +(0.084738, 10, .0000000000190890), +(0.084738, 11, .0000000000016176), +(0.084738, 12, .0000000000001371), +(0.084738, 13, .0000000000000116), +(0.084738, 14, .0000000000000010), +(0.084738, 15, .0000000000000001), +(0.084738, 16, .0000000000000000), +(0.084738, 17, .0000000000000000), +(0.084738, 18, .0000000000000000), +(0.084738, 19, .0000000000000000), +(0.084738, 20, .0000000000000000)) +SELECT b, p, bc_result, b^p AS power, b^p - bc_result AS diff FROM t; + b | p | bc_result | power | diff +----------+-----+-----------------------------------------+-----------------------------------------+-------------------- + 0.084738 | -20 | 2744326694304960114888.7859130502035257 | 2744326694304960114888.7859130502035257 | 0.0000000000000000 + 0.084738 | -19 | 232548755422013710215.4459407000481464 | 232548755422013710215.4459407000481464 | 0.0000000000000000 + 0.084738 | -18 | 19705716436950597776.2364581230406798 | 19705716436950597776.2364581230406798 | 0.0000000000000000 + 0.084738 | -17 | 1669822999434319754.3627249884302211 | 1669822999434319754.3627249884302211 | 0.0000000000000000 + 0.084738 | -16 | 141497461326065387.3451885900696001 | 141497461326065387.3451885900696001 | 0.0000000000000000 + 0.084738 | -15 | 11990211877848128.7928565907453178 | 11990211877848128.7928565907453178 | 0.0000000000000000 + 0.084738 | -14 | 1016026574105094.7376490817865767 | 1016026574105094.7376490817865767 | 0.0000000000000000 + 0.084738 | -13 | 86096059836517.5178789078924309 | 86096059836517.5178789078924309 | 0.0000000000000000 + 0.084738 | -12 | 7295607918426.8214300228969888 | 7295607918426.8214300228969888 | 0.0000000000000000 + 0.084738 | -11 | 618215223791.6519943372802450 | 618215223791.6519943372802450 | 0.0000000000000000 + 0.084738 | -10 | 52386321633.6570066961524534 | 52386321633.6570066961524534 | 0.0000000000000000 + 0.084738 | -9 | 4439112122.5928274334185666 | 4439112122.5928274334185666 | 0.0000000000000000 + 0.084738 | -8 | 376161483.0442710110530225 | 376161483.0442710110530225 | 0.0000000000000000 + 0.084738 | -7 | 31875171.7502054369346110 | 31875171.7502054369346110 | 0.0000000000000000 + 0.084738 | -6 | 2701038.3037689083149651 | 2701038.3037689083149651 | 0.0000000000000000 + 0.084738 | -5 | 228880.5837847697527935 | 228880.5837847697527935 | 0.0000000000000000 + 0.084738 | -4 | 19394.8829087538193122 | 19394.8829087538193122 | 0.0000000000000000 + 0.084738 | -3 | 1643.4835879219811409 | 1643.4835879219811409 | 0.0000000000000000 + 0.084738 | -2 | 139.2655122733328379 | 139.2655122733328379 | 0.0000000000000000 + 0.084738 | -1 | 11.8010809790176780 | 11.8010809790176780 | 0.0000000000000000 + 0.084738 | 0 | 1 | 1.0000000000000000 | 0.0000000000000000 + 0.084738 | 1 | 0.084738 | 0.0847380000000000 | 0.0000000000000000 + 0.084738 | 2 | 0.007180528644 | 0.0071805286440000 | 0.0000000000000000 + 0.084738 | 3 | 0.0006084636362353 | 0.0006084636362353 | 0.0000000000000000 + 0.084738 | 4 | 0.0000515599916073 | 0.0000515599916073 | 0.0000000000000000 + 0.084738 | 5 | 0.0000043690905688 | 0.0000043690905688 | 0.0000000000000000 + 0.084738 | 6 | 0.0000003702279966 | 0.0000003702279966 | 0.0000000000000000 + 0.084738 | 7 | 0.0000000313723800 | 0.0000000313723800 | 0.0000000000000000 + 0.084738 | 8 | 0.0000000026584327 | 0.0000000026584327 | 0.0000000000000000 + 0.084738 | 9 | 0.0000000002252703 | 0.0000000002252703 | 0.0000000000000000 + 0.084738 | 10 | 0.0000000000190890 | 0.0000000000190890 | 0.0000000000000000 + 0.084738 | 11 | 0.0000000000016176 | 0.0000000000016176 | 0.0000000000000000 + 0.084738 | 12 | 0.0000000000001371 | 0.0000000000001371 | 0.0000000000000000 + 0.084738 | 13 | 0.0000000000000116 | 0.0000000000000116 | 0.0000000000000000 + 0.084738 | 14 | 0.0000000000000010 | 0.0000000000000010 | 0.0000000000000000 + 0.084738 | 15 | 0.0000000000000001 | 0.0000000000000001 | 0.0000000000000000 + 0.084738 | 16 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 0.084738 | 17 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 0.084738 | 18 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 0.084738 | 19 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 0.084738 | 20 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 +(41 rows) + +-- base greater than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of POW(): +-- +-- for p in {-20..20} +-- do +-- b="37.821637" +-- r=$(bc -ql <<< "scale=500 ; $b^$p" | head -n 1) +-- echo "($b, $p, $r)," +-- done +WITH t(b, p, bc_result) AS (VALUES +(37.821637, -20, .0000000000000000), +(37.821637, -19, .0000000000000000), +(37.821637, -18, .0000000000000000), +(37.821637, -17, .0000000000000000), +(37.821637, -16, .0000000000000000), +(37.821637, -15, .0000000000000000), +(37.821637, -14, .0000000000000000), +(37.821637, -13, .0000000000000000), +(37.821637, -12, .0000000000000000), +(37.821637, -11, .0000000000000000), +(37.821637, -10, .0000000000000002), +(37.821637, -9, .0000000000000063), +(37.821637, -8, .0000000000002388), +(37.821637, -7, .0000000000090327), +(37.821637, -6, .0000000003416316), +(37.821637, -5, .0000000129210673), +(37.821637, -4, .0000004886959182), +(37.821637, -3, .0000184832796213), +(37.821637, -2, .0006990678924066), +(37.821637, -1, .0264398920649574), +(37.821637, 0, 1), +(37.821637, 1, 37.821637), +(37.821637, 2, 1430.476225359769), +(37.821637, 3, 54102.9525326873775219), +(37.821637, 4, 2046262.2313195326271135), +(37.821637, 5, 77392987.3197773940323425), +(37.821637, 6, 2927129472.7542235178972258), +(37.821637, 7, 110708828370.5116321107718772), +(37.821637, 8, 4187189119324.7924539711577286), +(37.821637, 9, 158366346921451.9852944363360812), +(37.821637, 10, 5989674486279224.5007355092228730), +(37.821637, 11, 226539294168214309.7083246628376531), +(37.821637, 12, 8568086950266418559.9938312759931069), +(37.821637, 13, 324059074417413536066.1494087598581043), +(37.821637, 14, 12256444679171401239980.3109258799733927), +(37.821637, 15, 463558801566202198479885.2069857662592280), +(37.821637, 16, 17532552720991931019508170.1002855156233684), +(37.821637, 17, 663109844696719094948877928.0672523682648687), +(37.821637, 18, 25079899837245684700124994552.6717306599041850), +(37.821637, 19, 948562867640665366544581398598.1275771806665398), +(37.821637, 20, 35876200451584291931921101974730.6901038166532866)) +SELECT b, p, bc_result, b^p AS power, b^p - bc_result AS diff FROM t; + b | p | bc_result | power | diff +-----------+-----+---------------------------------------------------+---------------------------------------------------+-------------------- + 37.821637 | -20 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 37.821637 | -19 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 37.821637 | -18 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 37.821637 | -17 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 37.821637 | -16 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 37.821637 | -15 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 37.821637 | -14 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 37.821637 | -13 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 37.821637 | -12 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 37.821637 | -11 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000 + 37.821637 | -10 | 0.0000000000000002 | 0.0000000000000002 | 0.0000000000000000 + 37.821637 | -9 | 0.0000000000000063 | 0.0000000000000063 | 0.0000000000000000 + 37.821637 | -8 | 0.0000000000002388 | 0.0000000000002388 | 0.0000000000000000 + 37.821637 | -7 | 0.0000000000090327 | 0.0000000000090327 | 0.0000000000000000 + 37.821637 | -6 | 0.0000000003416316 | 0.0000000003416316 | 0.0000000000000000 + 37.821637 | -5 | 0.0000000129210673 | 0.0000000129210673 | 0.0000000000000000 + 37.821637 | -4 | 0.0000004886959182 | 0.0000004886959182 | 0.0000000000000000 + 37.821637 | -3 | 0.0000184832796213 | 0.0000184832796213 | 0.0000000000000000 + 37.821637 | -2 | 0.0006990678924066 | 0.0006990678924066 | 0.0000000000000000 + 37.821637 | -1 | 0.0264398920649574 | 0.0264398920649574 | 0.0000000000000000 + 37.821637 | 0 | 1 | 1.0000000000000000 | 0.0000000000000000 + 37.821637 | 1 | 37.821637 | 37.8216370000000000 | 0.0000000000000000 + 37.821637 | 2 | 1430.476225359769 | 1430.4762253597690000 | 0.0000000000000000 + 37.821637 | 3 | 54102.9525326873775219 | 54102.9525326873775219 | 0.0000000000000000 + 37.821637 | 4 | 2046262.2313195326271135 | 2046262.2313195326271135 | 0.0000000000000000 + 37.821637 | 5 | 77392987.3197773940323425 | 77392987.3197773940323425 | 0.0000000000000000 + 37.821637 | 6 | 2927129472.7542235178972258 | 2927129472.7542235178972258 | 0.0000000000000000 + 37.821637 | 7 | 110708828370.5116321107718772 | 110708828370.5116321107718772 | 0.0000000000000000 + 37.821637 | 8 | 4187189119324.7924539711577286 | 4187189119324.7924539711577286 | 0.0000000000000000 + 37.821637 | 9 | 158366346921451.9852944363360812 | 158366346921451.9852944363360812 | 0.0000000000000000 + 37.821637 | 10 | 5989674486279224.5007355092228730 | 5989674486279224.5007355092228730 | 0.0000000000000000 + 37.821637 | 11 | 226539294168214309.7083246628376531 | 226539294168214309.7083246628376531 | 0.0000000000000000 + 37.821637 | 12 | 8568086950266418559.9938312759931069 | 8568086950266418559.9938312759931069 | 0.0000000000000000 + 37.821637 | 13 | 324059074417413536066.1494087598581043 | 324059074417413536066.1494087598581043 | 0.0000000000000000 + 37.821637 | 14 | 12256444679171401239980.3109258799733927 | 12256444679171401239980.3109258799733927 | 0.0000000000000000 + 37.821637 | 15 | 463558801566202198479885.2069857662592280 | 463558801566202198479885.2069857662592280 | 0.0000000000000000 + 37.821637 | 16 | 17532552720991931019508170.1002855156233684 | 17532552720991931019508170.1002855156233684 | 0.0000000000000000 + 37.821637 | 17 | 663109844696719094948877928.0672523682648687 | 663109844696719094948877928.0672523682648687 | 0.0000000000000000 + 37.821637 | 18 | 25079899837245684700124994552.6717306599041850 | 25079899837245684700124994552.6717306599041850 | 0.0000000000000000 + 37.821637 | 19 | 948562867640665366544581398598.1275771806665398 | 948562867640665366544581398598.1275771806665398 | 0.0000000000000000 + 37.821637 | 20 | 35876200451584291931921101974730.6901038166532866 | 35876200451584291931921101974730.6901038166532866 | 0.0000000000000000 +(41 rows) + +-- +-- Tests for raising to non-integer powers +-- +-- base less than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of POW(): +-- +-- for n in {-20..20} +-- do +-- b="0.06933247" +-- p="$n.342987" +-- r=$(bc -ql <<< "scale=500 ; e($p*l($b))" | head -n 1) +-- echo "($b, $p, $r)," +-- done +WITH t(b, p, bc_result) AS (VALUES +(0.06933247, -20.342987, 379149253615977128356318.39406340), +(0.06933247, -19.342987, 26287354251852125772450.59436685), +(0.06933247, -18.342987, 1822567200045909954554.65766042), +(0.06933247, -17.342987, 126363085720167050546.86216560), +(0.06933247, -16.342987, 8761064849800910427.02880469), +(0.06933247, -15.342987, 607426265866876128.15466179), +(0.06933247, -14.342987, 42114363355427213.14899924), +(0.06933247, -13.342987, 2919892833909256.59283660), +(0.06933247, -12.342987, 202443382310228.51544515), +(0.06933247, -11.342987, 14035899730722.44924025), +(0.06933247, -10.342987, 973143597003.32229028), +(0.06933247, -9.342987, 67470449244.92493259), +(0.06933247, -8.342987, 4677892898.16028054), +(0.06933247, -7.342987, 324329869.02491071), +(0.06933247, -6.342987, 22486590.914273551), +(0.06933247, -5.342987, 1559050.8899661435), +(0.06933247, -4.342987, 108092.84905705095), +(0.06933247, -3.342987, 7494.3442144625131), +(0.06933247, -2.342987, 519.60139541889576), +(0.06933247, -1.342987, 36.025248159838727), +(0.06933247, 0.342987, .40036522320023350), +(0.06933247, 1.342987, .02775830982657349), +(0.06933247, 2.342987, .001924552183301612), +(0.06933247, 3.342987, .0001334339565121935), +(0.06933247, 4.342987, .000009251305786862961), +(0.06933247, 5.342987, .0000006414158809285026), +(0.06933247, 6.342987, .00000004447094732199898), +(0.06933247, 7.342987, .000000003083280621074075), +(0.06933247, 8.342987, .0000000002137714611621997), +(0.06933247, 9.342987, .00000000001482130341788437), +(0.06933247, 10.342987, .000000000001027597574581366), +(0.06933247, 11.342987, .00000000000007124587801173530), +(0.06933247, 12.342987, .000000000000004939652699872298), +(0.06933247, 13.342987, .0000000000000003424783226243151), +(0.06933247, 14.342987, .00000000000000002374486802900065), +(0.06933247, 15.342987, .000000000000000001646290350274646), +(0.06933247, 16.342987, .0000000000000000001141413763217064), +(0.06933247, 17.342987, .000000000000000000007913703549583420), +(0.06933247, 18.342987, .0000000000000000000005486766139403860), +(0.06933247, 19.342987, .00000000000000000000003804110487572339), +(0.06933247, 20.342987, .000000000000000000000002637483762562946)) +SELECT b, p, bc_result, b^p AS power, b^p - bc_result AS diff FROM t; + b | p | bc_result | power | diff +------------+------------+-------------------------------------------+-------------------------------------------+------------------------------------------- + 0.06933247 | -20.342987 | 379149253615977128356318.39406340 | 379149253615977128356318.39406340 | 0.00000000 + 0.06933247 | -19.342987 | 26287354251852125772450.59436685 | 26287354251852125772450.59436685 | 0.00000000 + 0.06933247 | -18.342987 | 1822567200045909954554.65766042 | 1822567200045909954554.65766042 | 0.00000000 + 0.06933247 | -17.342987 | 126363085720167050546.86216560 | 126363085720167050546.86216560 | 0.00000000 + 0.06933247 | -16.342987 | 8761064849800910427.02880469 | 8761064849800910427.02880469 | 0.00000000 + 0.06933247 | -15.342987 | 607426265866876128.15466179 | 607426265866876128.15466179 | 0.00000000 + 0.06933247 | -14.342987 | 42114363355427213.14899924 | 42114363355427213.14899924 | 0.00000000 + 0.06933247 | -13.342987 | 2919892833909256.59283660 | 2919892833909256.59283660 | 0.00000000 + 0.06933247 | -12.342987 | 202443382310228.51544515 | 202443382310228.51544515 | 0.00000000 + 0.06933247 | -11.342987 | 14035899730722.44924025 | 14035899730722.44924025 | 0.00000000 + 0.06933247 | -10.342987 | 973143597003.32229028 | 973143597003.32229028 | 0.00000000 + 0.06933247 | -9.342987 | 67470449244.92493259 | 67470449244.92493259 | 0.00000000 + 0.06933247 | -8.342987 | 4677892898.16028054 | 4677892898.16028054 | 0.00000000 + 0.06933247 | -7.342987 | 324329869.02491071 | 324329869.02491071 | 0.00000000 + 0.06933247 | -6.342987 | 22486590.914273551 | 22486590.914273551 | 0.000000000 + 0.06933247 | -5.342987 | 1559050.8899661435 | 1559050.8899661435 | 0.0000000000 + 0.06933247 | -4.342987 | 108092.84905705095 | 108092.84905705095 | 0.00000000000 + 0.06933247 | -3.342987 | 7494.3442144625131 | 7494.3442144625131 | 0.0000000000000 + 0.06933247 | -2.342987 | 519.60139541889576 | 519.60139541889576 | 0.00000000000000 + 0.06933247 | -1.342987 | 36.025248159838727 | 36.025248159838727 | 0.000000000000000 + 0.06933247 | 0.342987 | 0.40036522320023350 | 0.4003652232002335 | 0.00000000000000000 + 0.06933247 | 1.342987 | 0.02775830982657349 | 0.02775830982657349 | 0.00000000000000000 + 0.06933247 | 2.342987 | 0.001924552183301612 | 0.001924552183301612 | 0.000000000000000000 + 0.06933247 | 3.342987 | 0.0001334339565121935 | 0.0001334339565121935 | 0.0000000000000000000 + 0.06933247 | 4.342987 | 0.000009251305786862961 | 0.000009251305786862961 | 0.000000000000000000000 + 0.06933247 | 5.342987 | 0.0000006414158809285026 | 0.0000006414158809285026 | 0.0000000000000000000000 + 0.06933247 | 6.342987 | 0.00000004447094732199898 | 0.00000004447094732199898 | 0.00000000000000000000000 + 0.06933247 | 7.342987 | 0.000000003083280621074075 | 0.000000003083280621074075 | 0.000000000000000000000000 + 0.06933247 | 8.342987 | 0.0000000002137714611621997 | 0.0000000002137714611621997 | 0.0000000000000000000000000 + 0.06933247 | 9.342987 | 0.00000000001482130341788437 | 0.00000000001482130341788437 | 0.00000000000000000000000000 + 0.06933247 | 10.342987 | 0.000000000001027597574581366 | 0.000000000001027597574581366 | 0.000000000000000000000000000 + 0.06933247 | 11.342987 | 0.00000000000007124587801173530 | 0.00000000000007124587801173530 | 0.00000000000000000000000000000 + 0.06933247 | 12.342987 | 0.000000000000004939652699872298 | 0.000000000000004939652699872298 | 0.000000000000000000000000000000 + 0.06933247 | 13.342987 | 0.0000000000000003424783226243151 | 0.0000000000000003424783226243151 | 0.0000000000000000000000000000000 + 0.06933247 | 14.342987 | 0.00000000000000002374486802900065 | 0.00000000000000002374486802900065 | 0.00000000000000000000000000000000 + 0.06933247 | 15.342987 | 0.000000000000000001646290350274646 | 0.000000000000000001646290350274646 | 0.000000000000000000000000000000000 + 0.06933247 | 16.342987 | 0.0000000000000000001141413763217064 | 0.0000000000000000001141413763217064 | 0.0000000000000000000000000000000000 + 0.06933247 | 17.342987 | 0.000000000000000000007913703549583420 | 0.000000000000000000007913703549583420 | 0.000000000000000000000000000000000000 + 0.06933247 | 18.342987 | 0.0000000000000000000005486766139403860 | 0.0000000000000000000005486766139403860 | 0.0000000000000000000000000000000000000 + 0.06933247 | 19.342987 | 0.00000000000000000000003804110487572339 | 0.00000000000000000000003804110487572339 | 0.00000000000000000000000000000000000000 + 0.06933247 | 20.342987 | 0.000000000000000000000002637483762562946 | 0.000000000000000000000002637483762562946 | 0.000000000000000000000000000000000000000 +(41 rows) + +-- base greater than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of POW(): +-- +-- for n in {-20..20} +-- do +-- b="27.234987" +-- p="$n.230957" +-- r=$(bc -ql <<< "scale=500 ; e($p*l($b))" | head -n 1) +-- echo "($b, $p, $r)," +-- done +WITH t(b, p, bc_result) AS (VALUES +(27.234987, -20.230957, .000000000000000000000000000009247064512095633), +(27.234987, -19.230957, .0000000000000000000000000002518436817750859), +(27.234987, -18.230957, .000000000000000000000000006858959399176602), +(27.234987, -17.230957, .0000000000000000000000001868036700701026), +(27.234987, -16.230957, .000000000000000000000005087595525911532), +(27.234987, -15.230957, .0000000000000000000001385605980094587), +(27.234987, -14.230957, .000000000000000000003773696085499835), +(27.234987, -13.230957, .0000000000000000001027765638305389), +(27.234987, -12.230957, .000000000000000002799118379829397), +(27.234987, -11.230957, .00000000000000007623395268611469), +(27.234987, -10.230957, .000000000000002076230710364949), +(27.234987, -9.230957, .00000000000005654611640579014), +(27.234987, -8.230957, .000000000001540032745212181), +(27.234987, -7.230957, .00000000004194277179542807), +(27.234987, -6.230957, .000000001142310844592450), +(27.234987, -5.230957, .00000003111082100243440), +(27.234987, -4.230957, .0000008473028055606278), +(27.234987, -3.230957, .00002307628089450723), +(27.234987, -2.230957, .0006284822101702527), +(27.234987, -1.230957, .01711670482371810), +(27.234987, 0.230957, 2.1451253063142300), +(27.234987, 1.230957, 58.422459830839071), +(27.234987, 2.230957, 1591.1349340009243), +(27.234987, 3.230957, 43334.539242761031), +(27.234987, 4.230957, 1180215.6129275865), +(27.234987, 5.230957, 32143156.875279851), +(27.234987, 6.230957, 875418459.63720737), +(27.234987, 7.230957, 23842010367.779367), +(27.234987, 8.230957, 649336842420.336290), +(27.234987, 9.230957, 17684680461938.907402), +(27.234987, 10.230957, 481642042480060.137900), +(27.234987, 11.230957, 13117514765597885.614921), +(27.234987, 12.230957, 357255344113366461.949871), +(27.234987, 13.230957, 9729844652608062117.440722), +(27.234987, 14.230957, 264992192625800087863.690528), +(27.234987, 15.230957, 7217058921265161257566.469315), +(27.234987, 16.230957, 196556505898890690402726.443417), +(27.234987, 17.230957, 5353213882921711267539279.451015), +(27.234987, 18.230957, 145794710509592328389185797.837767), +(27.234987, 19.230957, 3970717045397510438979206144.696206), +(27.234987, 20.230957, 108142427112079606637962972621.121293)) +SELECT b, p, bc_result, b^p AS power, b^p - bc_result AS diff FROM t; + b | p | bc_result | power | diff +-----------+------------+-------------------------------------------------+-------------------------------------------------+------------------------------------------------- + 27.234987 | -20.230957 | 0.000000000000000000000000000009247064512095633 | 0.000000000000000000000000000009247064512095633 | 0.000000000000000000000000000000000000000000000 + 27.234987 | -19.230957 | 0.0000000000000000000000000002518436817750859 | 0.0000000000000000000000000002518436817750859 | 0.0000000000000000000000000000000000000000000 + 27.234987 | -18.230957 | 0.000000000000000000000000006858959399176602 | 0.000000000000000000000000006858959399176602 | 0.000000000000000000000000000000000000000000 + 27.234987 | -17.230957 | 0.0000000000000000000000001868036700701026 | 0.0000000000000000000000001868036700701026 | 0.0000000000000000000000000000000000000000 + 27.234987 | -16.230957 | 0.000000000000000000000005087595525911532 | 0.000000000000000000000005087595525911532 | 0.000000000000000000000000000000000000000 + 27.234987 | -15.230957 | 0.0000000000000000000001385605980094587 | 0.0000000000000000000001385605980094587 | 0.0000000000000000000000000000000000000 + 27.234987 | -14.230957 | 0.000000000000000000003773696085499835 | 0.000000000000000000003773696085499835 | 0.000000000000000000000000000000000000 + 27.234987 | -13.230957 | 0.0000000000000000001027765638305389 | 0.0000000000000000001027765638305389 | 0.0000000000000000000000000000000000 + 27.234987 | -12.230957 | 0.000000000000000002799118379829397 | 0.000000000000000002799118379829397 | 0.000000000000000000000000000000000 + 27.234987 | -11.230957 | 0.00000000000000007623395268611469 | 0.00000000000000007623395268611469 | 0.00000000000000000000000000000000 + 27.234987 | -10.230957 | 0.000000000000002076230710364949 | 0.000000000000002076230710364949 | 0.000000000000000000000000000000 + 27.234987 | -9.230957 | 0.00000000000005654611640579014 | 0.00000000000005654611640579014 | 0.00000000000000000000000000000 + 27.234987 | -8.230957 | 0.000000000001540032745212181 | 0.000000000001540032745212181 | 0.000000000000000000000000000 + 27.234987 | -7.230957 | 0.00000000004194277179542807 | 0.00000000004194277179542807 | 0.00000000000000000000000000 + 27.234987 | -6.230957 | 0.000000001142310844592450 | 0.000000001142310844592450 | 0.000000000000000000000000 + 27.234987 | -5.230957 | 0.00000003111082100243440 | 0.00000003111082100243440 | 0.00000000000000000000000 + 27.234987 | -4.230957 | 0.0000008473028055606278 | 0.0000008473028055606278 | 0.0000000000000000000000 + 27.234987 | -3.230957 | 0.00002307628089450723 | 0.00002307628089450723 | 0.00000000000000000000 + 27.234987 | -2.230957 | 0.0006284822101702527 | 0.0006284822101702527 | 0.0000000000000000000 + 27.234987 | -1.230957 | 0.01711670482371810 | 0.01711670482371810 | 0.00000000000000000 + 27.234987 | 0.230957 | 2.1451253063142300 | 2.1451253063142300 | 0.0000000000000000 + 27.234987 | 1.230957 | 58.422459830839071 | 58.422459830839071 | 0.000000000000000 + 27.234987 | 2.230957 | 1591.1349340009243 | 1591.1349340009243 | 0.0000000000000 + 27.234987 | 3.230957 | 43334.539242761031 | 43334.539242761031 | 0.000000000000 + 27.234987 | 4.230957 | 1180215.6129275865 | 1180215.6129275865 | 0.0000000000 + 27.234987 | 5.230957 | 32143156.875279851 | 32143156.875279851 | 0.000000000 + 27.234987 | 6.230957 | 875418459.63720737 | 875418459.63720737 | 0.00000000 + 27.234987 | 7.230957 | 23842010367.779367 | 23842010367.779367 | 0.000000 + 27.234987 | 8.230957 | 649336842420.336290 | 649336842420.336290 | 0.000000 + 27.234987 | 9.230957 | 17684680461938.907402 | 17684680461938.907402 | 0.000000 + 27.234987 | 10.230957 | 481642042480060.137900 | 481642042480060.137900 | 0.000000 + 27.234987 | 11.230957 | 13117514765597885.614921 | 13117514765597885.614921 | 0.000000 + 27.234987 | 12.230957 | 357255344113366461.949871 | 357255344113366461.949871 | 0.000000 + 27.234987 | 13.230957 | 9729844652608062117.440722 | 9729844652608062117.440722 | 0.000000 + 27.234987 | 14.230957 | 264992192625800087863.690528 | 264992192625800087863.690528 | 0.000000 + 27.234987 | 15.230957 | 7217058921265161257566.469315 | 7217058921265161257566.469315 | 0.000000 + 27.234987 | 16.230957 | 196556505898890690402726.443417 | 196556505898890690402726.443417 | 0.000000 + 27.234987 | 17.230957 | 5353213882921711267539279.451015 | 5353213882921711267539279.451015 | 0.000000 + 27.234987 | 18.230957 | 145794710509592328389185797.837767 | 145794710509592328389185797.837767 | 0.000000 + 27.234987 | 19.230957 | 3970717045397510438979206144.696206 | 3970717045397510438979206144.696206 | 0.000000 + 27.234987 | 20.230957 | 108142427112079606637962972621.121293 | 108142427112079606637962972621.121293 | 0.000000 +(41 rows) + +-- Inputs close to overflow +-- +-- bc(1) results computed with a scale of 2700 and truncated to 4 decimal +-- places. +WITH t(b, p, bc_result) AS (VALUES +(0.12, -2829.8369, 58463948950011752465280493160293790845494328939320966633018493248607815580903065923369555885857984675501574162389726507612128133630191173383130639968378879506624785786843501848666498440326970769604109017960864573408272864266102690849952650095786874354625921641729880352858506454246180842452983243549491658464046163869265572232996388827878976066830374513768599285647145439771472435206769249126377164951470622827631950210853282324510655982757098065657709137845327135766013147354253426364240746381620690117663724329288646510198895137275207992825719846135857839292915100523542874885080351683587865157015032404901182924720371819942957083390475846809517968191151435281268695782594904484795360890092607679215675240583291240729468370895035823777914792823688291214492607109455017754453939895630226174304357121900605689015734289765672740769194115142607443713769825894380064727556869268488695795705030158832909348803019429370973064732712469794182891757241046263341655894972953512257981661670321890336672832647028099324621932563236459127918144141230217523147304565594514812518826936144181257723061181656522095236928347413997136815409159361412494284201481609684892562646522086577634100783077813105675590737823924220663206479031113753135119759722725207724879578900186075841393115040465401462266086907464970054073340036852442184414587772177753008511913377364966775792477387717262694468450099866775550614257191941835797445874557362115814601886902749237439492398087966544817154173072811937702110580330775581851211123491341435883319798273456296794954514173820352334127081705706502510709179711510240917772628308487366740741280043704807717608366220401933596364641284631036907635403895053036499618723044314773148779735006542501244942039455169872946018271985844759209768927953340447524637670938413827595013338859796135512187473850161303598087634723542727044978083220970836296653305188470017342167913572166172051819741354902582606590658382067039498769674611071582171914886494269818475850690414812481252963932223686078322390396586222238852602472958831686564971334200490182175112490433364675164900946902818404704835106260174052265784055642968397240262737313737007322288203637798365320295080314524864099419556398713380156353062937736280885716820226469419928595465390700629307079710611273715705695938635644841913194091407807776191951797748706106000922803167645881087385311847268311361092838264814899353459146959869764278464187826798546290981492648723002412475976344071283321798061003719251864595518596639432393032991023409676558943539937377229130132816883146259468718344018277257037013406135980469482324577407154032999045733141275895.3432), +(1.2, 32908.8896, 58463467728170833376633133695001863276259293590926929026251227859007891876739460057725441400966420577009060860805883032969522911803372870882799865787473726926215148161529632590083389287080925059682489116446754279752928005457087175157581627230586554364417068189211136840990661174760199073702207450133797324318403866058202372178813998850887986769280847189341565507156189065295823921162851958925352114220880236114784962150135485415106748467247897246441194126125699204912883449386043559785865023459356275014504597646990160571664166410683323036984805434677654413174177920726210827006973855410386789516533036723888687725436216478665958434776205940192130053647653715221076841771578099896259902368829351569726536927952661429685419815305418450230567773264738536471211804481206474781470237730069753206249915908804615495060673071058534441654604668770343616386612119048579369195201590008082689834456232255266932976831478404670192731621439902738547169253818323045451045749609624500171633897705543164388470746657118050314064066768449450440405619135824055131398727045420324382226572368236570500391463795989258779677208133531636928003546809249007993065200108076924439703799231711400266122025052209803513232429907231051873161206025860851056337427740362763618748092029386371493898291580557004812947013231371383576580415676519066503391905962989205397824064923920045371823949776899815750413244195402085917098964452866825666226141169411712884994564949174271056284898570445214367063763956186792886147126466387576513166370247576466566827375268334148320298849218878848928271566491769458471357076035396330179659440244425914213309776100351793665960978678576150833311810944729586040624059867137538839913141142139636023129691775489034134511666020819676247950267220131499463010350308195762769192775344260909521732256844149916046793599150786757764962585268686580124987490115873389726527572428003433405659445349155536369077209682951123806333170190998931670309088422483075609203671527331975811507450670132060984691061148836994322505371265263690017938762760088575875666254883673433331627055180154954694693433502522592907190906966067656027637884202418119121728966267936832338377284832958974299187166554160783467156478554899314000348357280306042140481751668215838656488457943830180819301102535170705017482946779698265096226184239631924271857062033454725540956591929965181603262502135610768915716020374362368495244256420143645126927013882334008435586481691725030031204304273292938132599127402133470745819213047706793887965197191137237066440328777206799072470374264316425913530947082957300047105685634407092811630672103242089966046839626911122.7149)) +SELECT b, p, bc_result, b^p AS power, b^p - bc_result AS diff FROM t; + b | p | bc_result | power | diff +------+------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------- + 0.12 | -2829.8369 | 58463948950011752465280493160293790845494328939320966633018493248607815580903065923369555885857984675501574162389726507612128133630191173383130639968378879506624785786843501848666498440326970769604109017960864573408272864266102690849952650095786874354625921641729880352858506454246180842452983243549491658464046163869265572232996388827878976066830374513768599285647145439771472435206769249126377164951470622827631950210853282324510655982757098065657709137845327135766013147354253426364240746381620690117663724329288646510198895137275207992825719846135857839292915100523542874885080351683587865157015032404901182924720371819942957083390475846809517968191151435281268695782594904484795360890092607679215675240583291240729468370895035823777914792823688291214492607109455017754453939895630226174304357121900605689015734289765672740769194115142607443713769825894380064727556869268488695795705030158832909348803019429370973064732712469794182891757241046263341655894972953512257981661670321890336672832647028099324621932563236459127918144141230217523147304565594514812518826936144181257723061181656522095236928347413997136815409159361412494284201481609684892562646522086577634100783077813105675590737823924220663206479031113753135119759722725207724879578900186075841393115040465401462266086907464970054073340036852442184414587772177753008511913377364966775792477387717262694468450099866775550614257191941835797445874557362115814601886902749237439492398087966544817154173072811937702110580330775581851211123491341435883319798273456296794954514173820352334127081705706502510709179711510240917772628308487366740741280043704807717608366220401933596364641284631036907635403895053036499618723044314773148779735006542501244942039455169872946018271985844759209768927953340447524637670938413827595013338859796135512187473850161303598087634723542727044978083220970836296653305188470017342167913572166172051819741354902582606590658382067039498769674611071582171914886494269818475850690414812481252963932223686078322390396586222238852602472958831686564971334200490182175112490433364675164900946902818404704835106260174052265784055642968397240262737313737007322288203637798365320295080314524864099419556398713380156353062937736280885716820226469419928595465390700629307079710611273715705695938635644841913194091407807776191951797748706106000922803167645881087385311847268311361092838264814899353459146959869764278464187826798546290981492648723002412475976344071283321798061003719251864595518596639432393032991023409676558943539937377229130132816883146259468718344018277257037013406135980469482324577407154032999045733141275895.3432 | 58463948950011752465280493160293790845494328939320966633018493248607815580903065923369555885857984675501574162389726507612128133630191173383130639968378879506624785786843501848666498440326970769604109017960864573408272864266102690849952650095786874354625921641729880352858506454246180842452983243549491658464046163869265572232996388827878976066830374513768599285647145439771472435206769249126377164951470622827631950210853282324510655982757098065657709137845327135766013147354253426364240746381620690117663724329288646510198895137275207992825719846135857839292915100523542874885080351683587865157015032404901182924720371819942957083390475846809517968191151435281268695782594904484795360890092607679215675240583291240729468370895035823777914792823688291214492607109455017754453939895630226174304357121900605689015734289765672740769194115142607443713769825894380064727556869268488695795705030158832909348803019429370973064732712469794182891757241046263341655894972953512257981661670321890336672832647028099324621932563236459127918144141230217523147304565594514812518826936144181257723061181656522095236928347413997136815409159361412494284201481609684892562646522086577634100783077813105675590737823924220663206479031113753135119759722725207724879578900186075841393115040465401462266086907464970054073340036852442184414587772177753008511913377364966775792477387717262694468450099866775550614257191941835797445874557362115814601886902749237439492398087966544817154173072811937702110580330775581851211123491341435883319798273456296794954514173820352334127081705706502510709179711510240917772628308487366740741280043704807717608366220401933596364641284631036907635403895053036499618723044314773148779735006542501244942039455169872946018271985844759209768927953340447524637670938413827595013338859796135512187473850161303598087634723542727044978083220970836296653305188470017342167913572166172051819741354902582606590658382067039498769674611071582171914886494269818475850690414812481252963932223686078322390396586222238852602472958831686564971334200490182175112490433364675164900946902818404704835106260174052265784055642968397240262737313737007322288203637798365320295080314524864099419556398713380156353062937736280885716820226469419928595465390700629307079710611273715705695938635644841913194091407807776191951797748706106000922803167645881087385311847268311361092838264814899353459146959869764278464187826798546290981492648723002412475976344071283321798061003719251864595518596639432393032991023409676558943539937377229130132816883146259468718344018277257037013406135980469482324577407154032999045733141275895.3432 | 0.0000 + 1.2 | 32908.8896 | 58463467728170833376633133695001863276259293590926929026251227859007891876739460057725441400966420577009060860805883032969522911803372870882799865787473726926215148161529632590083389287080925059682489116446754279752928005457087175157581627230586554364417068189211136840990661174760199073702207450133797324318403866058202372178813998850887986769280847189341565507156189065295823921162851958925352114220880236114784962150135485415106748467247897246441194126125699204912883449386043559785865023459356275014504597646990160571664166410683323036984805434677654413174177920726210827006973855410386789516533036723888687725436216478665958434776205940192130053647653715221076841771578099896259902368829351569726536927952661429685419815305418450230567773264738536471211804481206474781470237730069753206249915908804615495060673071058534441654604668770343616386612119048579369195201590008082689834456232255266932976831478404670192731621439902738547169253818323045451045749609624500171633897705543164388470746657118050314064066768449450440405619135824055131398727045420324382226572368236570500391463795989258779677208133531636928003546809249007993065200108076924439703799231711400266122025052209803513232429907231051873161206025860851056337427740362763618748092029386371493898291580557004812947013231371383576580415676519066503391905962989205397824064923920045371823949776899815750413244195402085917098964452866825666226141169411712884994564949174271056284898570445214367063763956186792886147126466387576513166370247576466566827375268334148320298849218878848928271566491769458471357076035396330179659440244425914213309776100351793665960978678576150833311810944729586040624059867137538839913141142139636023129691775489034134511666020819676247950267220131499463010350308195762769192775344260909521732256844149916046793599150786757764962585268686580124987490115873389726527572428003433405659445349155536369077209682951123806333170190998931670309088422483075609203671527331975811507450670132060984691061148836994322505371265263690017938762760088575875666254883673433331627055180154954694693433502522592907190906966067656027637884202418119121728966267936832338377284832958974299187166554160783467156478554899314000348357280306042140481751668215838656488457943830180819301102535170705017482946779698265096226184239631924271857062033454725540956591929965181603262502135610768915716020374362368495244256420143645126927013882334008435586481691725030031204304273292938132599127402133470745819213047706793887965197191137237066440328777206799072470374264316425913530947082957300047105685634407092811630672103242089966046839626911122.7149 | 58463467728170833376633133695001863276259293590926929026251227859007891876739460057725441400966420577009060860805883032969522911803372870882799865787473726926215148161529632590083389287080925059682489116446754279752928005457087175157581627230586554364417068189211136840990661174760199073702207450133797324318403866058202372178813998850887986769280847189341565507156189065295823921162851958925352114220880236114784962150135485415106748467247897246441194126125699204912883449386043559785865023459356275014504597646990160571664166410683323036984805434677654413174177920726210827006973855410386789516533036723888687725436216478665958434776205940192130053647653715221076841771578099896259902368829351569726536927952661429685419815305418450230567773264738536471211804481206474781470237730069753206249915908804615495060673071058534441654604668770343616386612119048579369195201590008082689834456232255266932976831478404670192731621439902738547169253818323045451045749609624500171633897705543164388470746657118050314064066768449450440405619135824055131398727045420324382226572368236570500391463795989258779677208133531636928003546809249007993065200108076924439703799231711400266122025052209803513232429907231051873161206025860851056337427740362763618748092029386371493898291580557004812947013231371383576580415676519066503391905962989205397824064923920045371823949776899815750413244195402085917098964452866825666226141169411712884994564949174271056284898570445214367063763956186792886147126466387576513166370247576466566827375268334148320298849218878848928271566491769458471357076035396330179659440244425914213309776100351793665960978678576150833311810944729586040624059867137538839913141142139636023129691775489034134511666020819676247950267220131499463010350308195762769192775344260909521732256844149916046793599150786757764962585268686580124987490115873389726527572428003433405659445349155536369077209682951123806333170190998931670309088422483075609203671527331975811507450670132060984691061148836994322505371265263690017938762760088575875666254883673433331627055180154954694693433502522592907190906966067656027637884202418119121728966267936832338377284832958974299187166554160783467156478554899314000348357280306042140481751668215838656488457943830180819301102535170705017482946779698265096226184239631924271857062033454725540956591929965181603262502135610768915716020374362368495244256420143645126927013882334008435586481691725030031204304273292938132599127402133470745819213047706793887965197191137237066440328777206799072470374264316425913530947082957300047105685634407092811630672103242089966046839626911122.7149 | 0.0000 +(2 rows) + +-- +-- Tests for EXP() +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of EXP(): +-- +-- for n in {-20..20} +-- do +-- x="$n.29837" +-- r=$(bc -ql <<< "scale=500 ; e($x)" | head -n 1) +-- echo "($x, $r)," +-- done +WITH t(x, bc_result) AS (VALUES +(-20.29837, .000000001529431101152222), +(-19.29837, .000000004157424770142192), +(-18.29837, .00000001130105220586304), +(-17.29837, .00000003071944485366452), +(-16.29837, .00000008350410872606600), +(-15.29837, .0000002269877013517336), +(-14.29837, .0000006170165438681061), +(-13.29837, .000001677224859055276), +(-12.29837, .000004559169856609741), +(-11.29837, .00001239310857408049), +(-10.29837, .00003368796183504298), +(-9.29837, .00009157337449401917), +(-8.29837, .0002489222398577673), +(-7.29837, .0006766408013046928), +(-6.29837, .001839300394580514), +(-5.29837, .004999736839665763), +(-4.29837, .01359069379834070), +(-3.29837, .03694333598818056), +(-2.29837, .1004223988993283), +(-1.29837, .2729763820983097), +(0.29837, 1.3476603299656679), +(1.29837, 3.6633205858807959), +(2.29837, 9.9579377804197108), +(3.29837, 27.068481317440698), +(4.29837, 73.579760889182206), +(5.29837, 200.01052696742555), +(6.29837, 543.68498095607070), +(7.29837, 1477.8890041389891), +(8.29837, 4017.3188244304487), +(9.29837, 10920.204759575742), +(10.29837, 29684.194161006717), +(11.29837, 80690.005580314652), +(12.29837, 219338.17590722828), +(13.29837, 596222.97785597218), +(14.29837, 1620702.0864156289), +(15.29837, 4405525.0308492653), +(16.29837, 11975458.636179032), +(17.29837, 32552671.598188404), +(18.29837, 88487335.673150406), +(19.29837, 240533516.60908059), +(20.29837, 653837887.33381570)) +SELECT x, bc_result, exp(x), exp(x)-bc_result AS diff FROM t; + x | bc_result | exp | diff +-----------+----------------------------+----------------------------+---------------------------- + -20.29837 | 0.000000001529431101152222 | 0.000000001529431101152222 | 0.000000000000000000000000 + -19.29837 | 0.000000004157424770142192 | 0.000000004157424770142192 | 0.000000000000000000000000 + -18.29837 | 0.00000001130105220586304 | 0.00000001130105220586304 | 0.00000000000000000000000 + -17.29837 | 0.00000003071944485366452 | 0.00000003071944485366452 | 0.00000000000000000000000 + -16.29837 | 0.00000008350410872606600 | 0.00000008350410872606600 | 0.00000000000000000000000 + -15.29837 | 0.0000002269877013517336 | 0.0000002269877013517336 | 0.0000000000000000000000 + -14.29837 | 0.0000006170165438681061 | 0.0000006170165438681061 | 0.0000000000000000000000 + -13.29837 | 0.000001677224859055276 | 0.000001677224859055276 | 0.000000000000000000000 + -12.29837 | 0.000004559169856609741 | 0.000004559169856609741 | 0.000000000000000000000 + -11.29837 | 0.00001239310857408049 | 0.00001239310857408049 | 0.00000000000000000000 + -10.29837 | 0.00003368796183504298 | 0.00003368796183504298 | 0.00000000000000000000 + -9.29837 | 0.00009157337449401917 | 0.00009157337449401917 | 0.00000000000000000000 + -8.29837 | 0.0002489222398577673 | 0.0002489222398577673 | 0.0000000000000000000 + -7.29837 | 0.0006766408013046928 | 0.0006766408013046928 | 0.0000000000000000000 + -6.29837 | 0.001839300394580514 | 0.001839300394580514 | 0.000000000000000000 + -5.29837 | 0.004999736839665763 | 0.004999736839665763 | 0.000000000000000000 + -4.29837 | 0.01359069379834070 | 0.01359069379834070 | 0.00000000000000000 + -3.29837 | 0.03694333598818056 | 0.03694333598818056 | 0.00000000000000000 + -2.29837 | 0.1004223988993283 | 0.1004223988993283 | 0.0000000000000000 + -1.29837 | 0.2729763820983097 | 0.2729763820983097 | 0.0000000000000000 + 0.29837 | 1.3476603299656679 | 1.3476603299656679 | 0.0000000000000000 + 1.29837 | 3.6633205858807959 | 3.6633205858807959 | 0.0000000000000000 + 2.29837 | 9.9579377804197108 | 9.9579377804197108 | 0.0000000000000000 + 3.29837 | 27.068481317440698 | 27.068481317440698 | 0.000000000000000 + 4.29837 | 73.579760889182206 | 73.579760889182206 | 0.000000000000000 + 5.29837 | 200.01052696742555 | 200.01052696742555 | 0.00000000000000 + 6.29837 | 543.68498095607070 | 543.68498095607070 | 0.00000000000000 + 7.29837 | 1477.8890041389891 | 1477.8890041389891 | 0.0000000000000 + 8.29837 | 4017.3188244304487 | 4017.3188244304487 | 0.0000000000000 + 9.29837 | 10920.204759575742 | 10920.204759575742 | 0.000000000000 + 10.29837 | 29684.194161006717 | 29684.194161006717 | 0.000000000000 + 11.29837 | 80690.005580314652 | 80690.005580314652 | 0.000000000000 + 12.29837 | 219338.17590722828 | 219338.17590722828 | 0.00000000000 + 13.29837 | 596222.97785597218 | 596222.97785597218 | 0.00000000000 + 14.29837 | 1620702.0864156289 | 1620702.0864156289 | 0.0000000000 + 15.29837 | 4405525.0308492653 | 4405525.0308492653 | 0.0000000000 + 16.29837 | 11975458.636179032 | 11975458.636179032 | 0.000000000 + 17.29837 | 32552671.598188404 | 32552671.598188404 | 0.000000000 + 18.29837 | 88487335.673150406 | 88487335.673150406 | 0.000000000 + 19.29837 | 240533516.60908059 | 240533516.60908059 | 0.00000000 + 20.29837 | 653837887.33381570 | 653837887.33381570 | 0.00000000 +(41 rows) + +-- +-- Tests for LN() +-- +-- input very small +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(10^-$p)" | head -n 1) +-- echo "('1.0e-$p', $l)," +-- done +WITH t(x, bc_result) AS (VALUES +('1.0e-1', -2.3025850929940457), +('1.0e-2', -4.6051701859880914), +('1.0e-3', -6.9077552789821371), +('1.0e-4', -9.2103403719761827), +('1.0e-5', -11.512925464970228), +('1.0e-6', -13.815510557964274), +('1.0e-7', -16.118095650958320), +('1.0e-8', -18.420680743952365), +('1.0e-9', -20.723265836946411), +('1.0e-10', -23.025850929940457), +('1.0e-11', -25.328436022934503), +('1.0e-12', -27.631021115928548), +('1.0e-13', -29.933606208922594), +('1.0e-14', -32.236191301916640), +('1.0e-15', -34.5387763949106853), +('1.0e-16', -36.84136148790473094), +('1.0e-17', -39.143946580898776628), +('1.0e-18', -41.4465316738928223123), +('1.0e-19', -43.74911676688686799634), +('1.0e-20', -46.051701859880913680360), +('1.0e-21', -48.3542869528749593643778), +('1.0e-22', -50.65687204586900504839581), +('1.0e-23', -52.959457138863050732413803), +('1.0e-24', -55.2620422318570964164317949), +('1.0e-25', -57.56462732485114210044978637), +('1.0e-26', -59.867212417845187784467777822), +('1.0e-27', -62.1697975108392334684857692765), +('1.0e-28', -64.47238260383327915250376073116), +('1.0e-29', -66.774967696827324836521752185847), +('1.0e-30', -69.0775527898213705205397436405309), +('1.0e-31', -71.38013788281541620455773509521529), +('1.0e-32', -73.682722975809461888575726549899655), +('1.0e-33', -75.9853080688035075725937180045840189), +('1.0e-34', -78.28789316179755325661170945926838306), +('1.0e-35', -80.590478254791598940629700913952747266), +('1.0e-36', -82.8930633477856446246476923686371114736), +('1.0e-37', -85.19564844077969030866568382332147568124), +('1.0e-38', -87.498233533773735992683675278005839888842), +('1.0e-39', -89.8008186267677816767016667326902040964430), +('1.0e-40', -92.10340371976182736071965818737456830404406)) +SELECT x, bc_result, ln(x::numeric), ln(x::numeric)-bc_result AS diff FROM t; + x | bc_result | ln | diff +---------+-----------------------------------------------+-----------------------------------------------+--------------------------------------------- + 1.0e-1 | -2.3025850929940457 | -2.3025850929940457 | 0.0000000000000000 + 1.0e-2 | -4.6051701859880914 | -4.6051701859880914 | 0.0000000000000000 + 1.0e-3 | -6.9077552789821371 | -6.9077552789821371 | 0.0000000000000000 + 1.0e-4 | -9.2103403719761827 | -9.2103403719761827 | 0.0000000000000000 + 1.0e-5 | -11.512925464970228 | -11.512925464970228 | 0.000000000000000 + 1.0e-6 | -13.815510557964274 | -13.815510557964274 | 0.000000000000000 + 1.0e-7 | -16.118095650958320 | -16.118095650958320 | 0.000000000000000 + 1.0e-8 | -18.420680743952365 | -18.420680743952365 | 0.000000000000000 + 1.0e-9 | -20.723265836946411 | -20.723265836946411 | 0.000000000000000 + 1.0e-10 | -23.025850929940457 | -23.025850929940457 | 0.000000000000000 + 1.0e-11 | -25.328436022934503 | -25.328436022934503 | 0.000000000000000 + 1.0e-12 | -27.631021115928548 | -27.631021115928548 | 0.000000000000000 + 1.0e-13 | -29.933606208922594 | -29.933606208922594 | 0.000000000000000 + 1.0e-14 | -32.236191301916640 | -32.236191301916640 | 0.000000000000000 + 1.0e-15 | -34.5387763949106853 | -34.5387763949106853 | 0.0000000000000000 + 1.0e-16 | -36.84136148790473094 | -36.84136148790473094 | 0.00000000000000000 + 1.0e-17 | -39.143946580898776628 | -39.143946580898776628 | 0.000000000000000000 + 1.0e-18 | -41.4465316738928223123 | -41.4465316738928223123 | 0.0000000000000000000 + 1.0e-19 | -43.74911676688686799634 | -43.74911676688686799634 | 0.00000000000000000000 + 1.0e-20 | -46.051701859880913680360 | -46.051701859880913680360 | 0.000000000000000000000 + 1.0e-21 | -48.3542869528749593643778 | -48.3542869528749593643778 | 0.0000000000000000000000 + 1.0e-22 | -50.65687204586900504839581 | -50.65687204586900504839581 | 0.00000000000000000000000 + 1.0e-23 | -52.959457138863050732413803 | -52.959457138863050732413803 | 0.000000000000000000000000 + 1.0e-24 | -55.2620422318570964164317949 | -55.2620422318570964164317949 | 0.0000000000000000000000000 + 1.0e-25 | -57.56462732485114210044978637 | -57.56462732485114210044978637 | 0.00000000000000000000000000 + 1.0e-26 | -59.867212417845187784467777822 | -59.867212417845187784467777822 | 0.000000000000000000000000000 + 1.0e-27 | -62.1697975108392334684857692765 | -62.1697975108392334684857692765 | 0.0000000000000000000000000000 + 1.0e-28 | -64.47238260383327915250376073116 | -64.47238260383327915250376073116 | 0.00000000000000000000000000000 + 1.0e-29 | -66.774967696827324836521752185847 | -66.774967696827324836521752185847 | 0.000000000000000000000000000000 + 1.0e-30 | -69.0775527898213705205397436405309 | -69.0775527898213705205397436405309 | 0.0000000000000000000000000000000 + 1.0e-31 | -71.38013788281541620455773509521529 | -71.38013788281541620455773509521529 | 0.00000000000000000000000000000000 + 1.0e-32 | -73.682722975809461888575726549899655 | -73.682722975809461888575726549899655 | 0.000000000000000000000000000000000 + 1.0e-33 | -75.9853080688035075725937180045840189 | -75.9853080688035075725937180045840189 | 0.0000000000000000000000000000000000 + 1.0e-34 | -78.28789316179755325661170945926838306 | -78.28789316179755325661170945926838306 | 0.00000000000000000000000000000000000 + 1.0e-35 | -80.590478254791598940629700913952747266 | -80.590478254791598940629700913952747266 | 0.000000000000000000000000000000000000 + 1.0e-36 | -82.8930633477856446246476923686371114736 | -82.8930633477856446246476923686371114736 | 0.0000000000000000000000000000000000000 + 1.0e-37 | -85.19564844077969030866568382332147568124 | -85.19564844077969030866568382332147568124 | 0.00000000000000000000000000000000000000 + 1.0e-38 | -87.498233533773735992683675278005839888842 | -87.498233533773735992683675278005839888842 | 0.000000000000000000000000000000000000000 + 1.0e-39 | -89.8008186267677816767016667326902040964430 | -89.8008186267677816767016667326902040964430 | 0.0000000000000000000000000000000000000000 + 1.0e-40 | -92.10340371976182736071965818737456830404406 | -92.10340371976182736071965818737456830404406 | 0.00000000000000000000000000000000000000000 +(40 rows) + +-- input very close to but smaller than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(1-10^-$p)" | head -n 1) +-- echo "('1.0e-$p', $l)," +-- done +WITH t(x, bc_result) AS (VALUES +('1.0e-1', -.10536051565782630), +('1.0e-2', -.010050335853501441), +('1.0e-3', -.0010005003335835335), +('1.0e-4', -.00010000500033335834), +('1.0e-5', -.000010000050000333336), +('1.0e-6', -.0000010000005000003333), +('1.0e-7', -.00000010000000500000033), +('1.0e-8', -.000000010000000050000000), +('1.0e-9', -.0000000010000000005000000), +('1.0e-10', -.00000000010000000000500000), +('1.0e-11', -.000000000010000000000050000), +('1.0e-12', -.0000000000010000000000005000), +('1.0e-13', -.00000000000010000000000000500), +('1.0e-14', -.000000000000010000000000000050), +('1.0e-15', -.0000000000000010000000000000005), +('1.0e-16', -.00000000000000010000000000000001), +('1.0e-17', -.000000000000000010000000000000000), +('1.0e-18', -.0000000000000000010000000000000000), +('1.0e-19', -.00000000000000000010000000000000000), +('1.0e-20', -.000000000000000000010000000000000000), +('1.0e-21', -.0000000000000000000010000000000000000), +('1.0e-22', -.00000000000000000000010000000000000000), +('1.0e-23', -.000000000000000000000010000000000000000), +('1.0e-24', -.0000000000000000000000010000000000000000), +('1.0e-25', -.00000000000000000000000010000000000000000), +('1.0e-26', -.000000000000000000000000010000000000000000), +('1.0e-27', -.0000000000000000000000000010000000000000000), +('1.0e-28', -.00000000000000000000000000010000000000000000), +('1.0e-29', -.000000000000000000000000000010000000000000000), +('1.0e-30', -.0000000000000000000000000000010000000000000000), +('1.0e-31', -.00000000000000000000000000000010000000000000000), +('1.0e-32', -.000000000000000000000000000000010000000000000000), +('1.0e-33', -.0000000000000000000000000000000010000000000000000), +('1.0e-34', -.00000000000000000000000000000000010000000000000000), +('1.0e-35', -.000000000000000000000000000000000010000000000000000), +('1.0e-36', -.0000000000000000000000000000000000010000000000000000), +('1.0e-37', -.00000000000000000000000000000000000010000000000000000), +('1.0e-38', -.000000000000000000000000000000000000010000000000000000), +('1.0e-39', -.0000000000000000000000000000000000000010000000000000000), +('1.0e-40', -.00000000000000000000000000000000000000010000000000000000)) +SELECT '1-'||x, bc_result, ln(1.0-x::numeric), ln(1.0-x::numeric)-bc_result AS diff FROM t; + ?column? | bc_result | ln | diff +-----------+-------------------------------------------------------------+-------------------------------------------------------------+------------------------------------------------------------ + 1-1.0e-1 | -0.10536051565782630 | -0.10536051565782630 | 0.00000000000000000 + 1-1.0e-2 | -0.010050335853501441 | -0.010050335853501441 | 0.000000000000000000 + 1-1.0e-3 | -0.0010005003335835335 | -0.0010005003335835335 | 0.0000000000000000000 + 1-1.0e-4 | -0.00010000500033335834 | -0.00010000500033335834 | 0.00000000000000000000 + 1-1.0e-5 | -0.000010000050000333336 | -0.000010000050000333336 | 0.000000000000000000000 + 1-1.0e-6 | -0.0000010000005000003333 | -0.0000010000005000003333 | 0.0000000000000000000000 + 1-1.0e-7 | -0.00000010000000500000033 | -0.00000010000000500000033 | 0.00000000000000000000000 + 1-1.0e-8 | -0.000000010000000050000000 | -0.000000010000000050000000 | 0.000000000000000000000000 + 1-1.0e-9 | -0.0000000010000000005000000 | -0.0000000010000000005000000 | 0.0000000000000000000000000 + 1-1.0e-10 | -0.00000000010000000000500000 | -0.00000000010000000000500000 | 0.00000000000000000000000000 + 1-1.0e-11 | -0.000000000010000000000050000 | -0.000000000010000000000050000 | 0.000000000000000000000000000 + 1-1.0e-12 | -0.0000000000010000000000005000 | -0.0000000000010000000000005000 | 0.0000000000000000000000000000 + 1-1.0e-13 | -0.00000000000010000000000000500 | -0.00000000000010000000000000500 | 0.00000000000000000000000000000 + 1-1.0e-14 | -0.000000000000010000000000000050 | -0.000000000000010000000000000050 | 0.000000000000000000000000000000 + 1-1.0e-15 | -0.0000000000000010000000000000005 | -0.0000000000000010000000000000005 | 0.0000000000000000000000000000000 + 1-1.0e-16 | -0.00000000000000010000000000000001 | -0.00000000000000010000000000000001 | 0.00000000000000000000000000000000 + 1-1.0e-17 | -0.000000000000000010000000000000000 | -0.000000000000000010000000000000000 | 0.000000000000000000000000000000000 + 1-1.0e-18 | -0.0000000000000000010000000000000000 | -0.0000000000000000010000000000000000 | 0.0000000000000000000000000000000000 + 1-1.0e-19 | -0.00000000000000000010000000000000000 | -0.00000000000000000010000000000000000 | 0.00000000000000000000000000000000000 + 1-1.0e-20 | -0.000000000000000000010000000000000000 | -0.000000000000000000010000000000000000 | 0.000000000000000000000000000000000000 + 1-1.0e-21 | -0.0000000000000000000010000000000000000 | -0.0000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000 + 1-1.0e-22 | -0.00000000000000000000010000000000000000 | -0.00000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000 + 1-1.0e-23 | -0.000000000000000000000010000000000000000 | -0.000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000 + 1-1.0e-24 | -0.0000000000000000000000010000000000000000 | -0.0000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000 + 1-1.0e-25 | -0.00000000000000000000000010000000000000000 | -0.00000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000 + 1-1.0e-26 | -0.000000000000000000000000010000000000000000 | -0.000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000000 + 1-1.0e-27 | -0.0000000000000000000000000010000000000000000 | -0.0000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000000 + 1-1.0e-28 | -0.00000000000000000000000000010000000000000000 | -0.00000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000000 + 1-1.0e-29 | -0.000000000000000000000000000010000000000000000 | -0.000000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000000000 + 1-1.0e-30 | -0.0000000000000000000000000000010000000000000000 | -0.0000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000000000 + 1-1.0e-31 | -0.00000000000000000000000000000010000000000000000 | -0.00000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000000000 + 1-1.0e-32 | -0.000000000000000000000000000000010000000000000000 | -0.000000000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000000000000 + 1-1.0e-33 | -0.0000000000000000000000000000000010000000000000000 | -0.0000000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000000000000 + 1-1.0e-34 | -0.00000000000000000000000000000000010000000000000000 | -0.00000000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000000000000 + 1-1.0e-35 | -0.000000000000000000000000000000000010000000000000000 | -0.000000000000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000000000000000 + 1-1.0e-36 | -0.0000000000000000000000000000000000010000000000000000 | -0.0000000000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000000000000000 + 1-1.0e-37 | -0.00000000000000000000000000000000000010000000000000000 | -0.00000000000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000000000000000 + 1-1.0e-38 | -0.000000000000000000000000000000000000010000000000000000 | -0.000000000000000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000000000000000000 + 1-1.0e-39 | -0.0000000000000000000000000000000000000010000000000000000 | -0.0000000000000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000000000000000000 + 1-1.0e-40 | -0.00000000000000000000000000000000000000010000000000000000 | -0.00000000000000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000000000000000000 +(40 rows) + +-- input very close to but larger than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(1+10^-$p)" | head -n 1) +-- echo "('1.0e-$p', $l)," +-- done +WITH t(x, bc_result) AS (VALUES +('1.0e-1', .09531017980432486), +('1.0e-2', .009950330853168083), +('1.0e-3', .0009995003330835332), +('1.0e-4', .00009999500033330834), +('1.0e-5', .000009999950000333331), +('1.0e-6', .0000009999995000003333), +('1.0e-7', .00000009999999500000033), +('1.0e-8', .000000009999999950000000), +('1.0e-9', .0000000009999999995000000), +('1.0e-10', .00000000009999999999500000), +('1.0e-11', .000000000009999999999950000), +('1.0e-12', .0000000000009999999999995000), +('1.0e-13', .00000000000009999999999999500), +('1.0e-14', .000000000000009999999999999950), +('1.0e-15', .0000000000000009999999999999995), +('1.0e-16', .00000000000000010000000000000000), +('1.0e-17', .000000000000000010000000000000000), +('1.0e-18', .0000000000000000010000000000000000), +('1.0e-19', .00000000000000000010000000000000000), +('1.0e-20', .000000000000000000010000000000000000), +('1.0e-21', .0000000000000000000010000000000000000), +('1.0e-22', .00000000000000000000010000000000000000), +('1.0e-23', .000000000000000000000010000000000000000), +('1.0e-24', .0000000000000000000000010000000000000000), +('1.0e-25', .00000000000000000000000010000000000000000), +('1.0e-26', .000000000000000000000000010000000000000000), +('1.0e-27', .0000000000000000000000000010000000000000000), +('1.0e-28', .00000000000000000000000000010000000000000000), +('1.0e-29', .000000000000000000000000000010000000000000000), +('1.0e-30', .0000000000000000000000000000010000000000000000), +('1.0e-31', .00000000000000000000000000000010000000000000000), +('1.0e-32', .000000000000000000000000000000010000000000000000), +('1.0e-33', .0000000000000000000000000000000010000000000000000), +('1.0e-34', .00000000000000000000000000000000010000000000000000), +('1.0e-35', .000000000000000000000000000000000010000000000000000), +('1.0e-36', .0000000000000000000000000000000000010000000000000000), +('1.0e-37', .00000000000000000000000000000000000010000000000000000), +('1.0e-38', .000000000000000000000000000000000000010000000000000000), +('1.0e-39', .0000000000000000000000000000000000000010000000000000000), +('1.0e-40', .00000000000000000000000000000000000000010000000000000000)) +SELECT '1+'||x, bc_result, ln(1.0+x::numeric), ln(1.0+x::numeric)-bc_result AS diff FROM t; + ?column? | bc_result | ln | diff +-----------+------------------------------------------------------------+------------------------------------------------------------+------------------------------------------------------------ + 1+1.0e-1 | 0.09531017980432486 | 0.09531017980432486 | 0.00000000000000000 + 1+1.0e-2 | 0.009950330853168083 | 0.009950330853168083 | 0.000000000000000000 + 1+1.0e-3 | 0.0009995003330835332 | 0.0009995003330835332 | 0.0000000000000000000 + 1+1.0e-4 | 0.00009999500033330834 | 0.00009999500033330834 | 0.00000000000000000000 + 1+1.0e-5 | 0.000009999950000333331 | 0.000009999950000333331 | 0.000000000000000000000 + 1+1.0e-6 | 0.0000009999995000003333 | 0.0000009999995000003333 | 0.0000000000000000000000 + 1+1.0e-7 | 0.00000009999999500000033 | 0.00000009999999500000033 | 0.00000000000000000000000 + 1+1.0e-8 | 0.000000009999999950000000 | 0.000000009999999950000000 | 0.000000000000000000000000 + 1+1.0e-9 | 0.0000000009999999995000000 | 0.0000000009999999995000000 | 0.0000000000000000000000000 + 1+1.0e-10 | 0.00000000009999999999500000 | 0.00000000009999999999500000 | 0.00000000000000000000000000 + 1+1.0e-11 | 0.000000000009999999999950000 | 0.000000000009999999999950000 | 0.000000000000000000000000000 + 1+1.0e-12 | 0.0000000000009999999999995000 | 0.0000000000009999999999995000 | 0.0000000000000000000000000000 + 1+1.0e-13 | 0.00000000000009999999999999500 | 0.00000000000009999999999999500 | 0.00000000000000000000000000000 + 1+1.0e-14 | 0.000000000000009999999999999950 | 0.000000000000009999999999999950 | 0.000000000000000000000000000000 + 1+1.0e-15 | 0.0000000000000009999999999999995 | 0.0000000000000009999999999999995 | 0.0000000000000000000000000000000 + 1+1.0e-16 | 0.00000000000000010000000000000000 | 0.00000000000000010000000000000000 | 0.00000000000000000000000000000000 + 1+1.0e-17 | 0.000000000000000010000000000000000 | 0.000000000000000010000000000000000 | 0.000000000000000000000000000000000 + 1+1.0e-18 | 0.0000000000000000010000000000000000 | 0.0000000000000000010000000000000000 | 0.0000000000000000000000000000000000 + 1+1.0e-19 | 0.00000000000000000010000000000000000 | 0.00000000000000000010000000000000000 | 0.00000000000000000000000000000000000 + 1+1.0e-20 | 0.000000000000000000010000000000000000 | 0.000000000000000000010000000000000000 | 0.000000000000000000000000000000000000 + 1+1.0e-21 | 0.0000000000000000000010000000000000000 | 0.0000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000 + 1+1.0e-22 | 0.00000000000000000000010000000000000000 | 0.00000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000 + 1+1.0e-23 | 0.000000000000000000000010000000000000000 | 0.000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000 + 1+1.0e-24 | 0.0000000000000000000000010000000000000000 | 0.0000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000 + 1+1.0e-25 | 0.00000000000000000000000010000000000000000 | 0.00000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000 + 1+1.0e-26 | 0.000000000000000000000000010000000000000000 | 0.000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000000 + 1+1.0e-27 | 0.0000000000000000000000000010000000000000000 | 0.0000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000000 + 1+1.0e-28 | 0.00000000000000000000000000010000000000000000 | 0.00000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000000 + 1+1.0e-29 | 0.000000000000000000000000000010000000000000000 | 0.000000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000000000 + 1+1.0e-30 | 0.0000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000000000 + 1+1.0e-31 | 0.00000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000000000 + 1+1.0e-32 | 0.000000000000000000000000000000010000000000000000 | 0.000000000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000000000000 + 1+1.0e-33 | 0.0000000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000000000000 + 1+1.0e-34 | 0.00000000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000000000000 + 1+1.0e-35 | 0.000000000000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000000000000000 + 1+1.0e-36 | 0.0000000000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000000000000000 + 1+1.0e-37 | 0.00000000000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000000000000000 + 1+1.0e-38 | 0.000000000000000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000010000000000000000 | 0.000000000000000000000000000000000000000000000000000000 + 1+1.0e-39 | 0.0000000000000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000010000000000000000 | 0.0000000000000000000000000000000000000000000000000000000 + 1+1.0e-40 | 0.00000000000000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000010000000000000000 | 0.00000000000000000000000000000000000000000000000000000000 +(40 rows) + +-- input very large +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(10^$p)" | head -n 1) +-- echo "('1.0e$p', $l)," +-- done +WITH t(x, bc_result) AS (VALUES +('1.0e1', 2.3025850929940457), +('1.0e2', 4.6051701859880914), +('1.0e3', 6.9077552789821371), +('1.0e4', 9.2103403719761827), +('1.0e5', 11.512925464970228), +('1.0e6', 13.815510557964274), +('1.0e7', 16.118095650958320), +('1.0e8', 18.420680743952365), +('1.0e9', 20.723265836946411), +('1.0e10', 23.025850929940457), +('1.0e11', 25.328436022934503), +('1.0e12', 27.631021115928548), +('1.0e13', 29.933606208922594), +('1.0e14', 32.236191301916640), +('1.0e15', 34.538776394910685), +('1.0e16', 36.841361487904731), +('1.0e17', 39.143946580898777), +('1.0e18', 41.446531673892822), +('1.0e19', 43.749116766886868), +('1.0e20', 46.051701859880914), +('1.0e21', 48.354286952874959), +('1.0e22', 50.656872045869005), +('1.0e23', 52.959457138863051), +('1.0e24', 55.262042231857096), +('1.0e25', 57.564627324851142), +('1.0e26', 59.867212417845188), +('1.0e27', 62.169797510839233), +('1.0e28', 64.472382603833279), +('1.0e29', 66.774967696827325), +('1.0e30', 69.077552789821371), +('1.0e31', 71.380137882815416), +('1.0e32', 73.682722975809462), +('1.0e33', 75.985308068803508), +('1.0e34', 78.287893161797553), +('1.0e35', 80.590478254791599), +('1.0e36', 82.893063347785645), +('1.0e37', 85.195648440779690), +('1.0e38', 87.498233533773736), +('1.0e39', 89.800818626767782), +('1.0e40', 92.103403719761827)) +SELECT x, bc_result, ln(x::numeric), ln(x::numeric)-bc_result AS diff FROM t; + x | bc_result | ln | diff +--------+--------------------+--------------------+-------------------- + 1.0e1 | 2.3025850929940457 | 2.3025850929940457 | 0.0000000000000000 + 1.0e2 | 4.6051701859880914 | 4.6051701859880914 | 0.0000000000000000 + 1.0e3 | 6.9077552789821371 | 6.9077552789821371 | 0.0000000000000000 + 1.0e4 | 9.2103403719761827 | 9.2103403719761827 | 0.0000000000000000 + 1.0e5 | 11.512925464970228 | 11.512925464970228 | 0.000000000000000 + 1.0e6 | 13.815510557964274 | 13.815510557964274 | 0.000000000000000 + 1.0e7 | 16.118095650958320 | 16.118095650958320 | 0.000000000000000 + 1.0e8 | 18.420680743952365 | 18.420680743952365 | 0.000000000000000 + 1.0e9 | 20.723265836946411 | 20.723265836946411 | 0.000000000000000 + 1.0e10 | 23.025850929940457 | 23.025850929940457 | 0.000000000000000 + 1.0e11 | 25.328436022934503 | 25.328436022934503 | 0.000000000000000 + 1.0e12 | 27.631021115928548 | 27.631021115928548 | 0.000000000000000 + 1.0e13 | 29.933606208922594 | 29.933606208922594 | 0.000000000000000 + 1.0e14 | 32.236191301916640 | 32.236191301916640 | 0.000000000000000 + 1.0e15 | 34.538776394910685 | 34.538776394910685 | 0.000000000000000 + 1.0e16 | 36.841361487904731 | 36.841361487904731 | 0.000000000000000 + 1.0e17 | 39.143946580898777 | 39.143946580898777 | 0.000000000000000 + 1.0e18 | 41.446531673892822 | 41.446531673892822 | 0.000000000000000 + 1.0e19 | 43.749116766886868 | 43.749116766886868 | 0.000000000000000 + 1.0e20 | 46.051701859880914 | 46.051701859880914 | 0.000000000000000 + 1.0e21 | 48.354286952874959 | 48.354286952874959 | 0.000000000000000 + 1.0e22 | 50.656872045869005 | 50.656872045869005 | 0.000000000000000 + 1.0e23 | 52.959457138863051 | 52.959457138863051 | 0.000000000000000 + 1.0e24 | 55.262042231857096 | 55.262042231857096 | 0.000000000000000 + 1.0e25 | 57.564627324851142 | 57.564627324851142 | 0.000000000000000 + 1.0e26 | 59.867212417845188 | 59.867212417845188 | 0.000000000000000 + 1.0e27 | 62.169797510839233 | 62.169797510839233 | 0.000000000000000 + 1.0e28 | 64.472382603833279 | 64.472382603833279 | 0.000000000000000 + 1.0e29 | 66.774967696827325 | 66.774967696827325 | 0.000000000000000 + 1.0e30 | 69.077552789821371 | 69.077552789821371 | 0.000000000000000 + 1.0e31 | 71.380137882815416 | 71.380137882815416 | 0.000000000000000 + 1.0e32 | 73.682722975809462 | 73.682722975809462 | 0.000000000000000 + 1.0e33 | 75.985308068803508 | 75.985308068803508 | 0.000000000000000 + 1.0e34 | 78.287893161797553 | 78.287893161797553 | 0.000000000000000 + 1.0e35 | 80.590478254791599 | 80.590478254791599 | 0.000000000000000 + 1.0e36 | 82.893063347785645 | 82.893063347785645 | 0.000000000000000 + 1.0e37 | 85.195648440779690 | 85.195648440779690 | 0.000000000000000 + 1.0e38 | 87.498233533773736 | 87.498233533773736 | 0.000000000000000 + 1.0e39 | 89.800818626767782 | 89.800818626767782 | 0.000000000000000 + 1.0e40 | 92.103403719761827 | 92.103403719761827 | 0.000000000000000 +(40 rows) + +-- input huge +-- +-- bc(1) results computed with a scale of 1000 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..10} +-- do +-- l=$(bc -ql <<< "scale=1000 ; l(10^${p}00)" | head -n 1) +-- echo "('1.0e${p}00', $l)," +-- done +WITH t(x, bc_result) AS (VALUES +('1.0e100', 230.25850929940457), +('1.0e200', 460.51701859880914), +('1.0e300', 690.77552789821371), +('1.0e400', 921.03403719761827), +('1.0e500', 1151.2925464970228), +('1.0e600', 1381.5510557964274), +('1.0e700', 1611.8095650958320), +('1.0e800', 1842.0680743952365), +('1.0e900', 2072.3265836946411), +('1.0e1000', 2302.5850929940457)) +SELECT x, bc_result, ln(x::numeric), ln(x::numeric)-bc_result AS diff FROM t; + x | bc_result | ln | diff +----------+--------------------+--------------------+------------------ + 1.0e100 | 230.25850929940457 | 230.25850929940457 | 0.00000000000000 + 1.0e200 | 460.51701859880914 | 460.51701859880914 | 0.00000000000000 + 1.0e300 | 690.77552789821371 | 690.77552789821371 | 0.00000000000000 + 1.0e400 | 921.03403719761827 | 921.03403719761827 | 0.00000000000000 + 1.0e500 | 1151.2925464970228 | 1151.2925464970228 | 0.0000000000000 + 1.0e600 | 1381.5510557964274 | 1381.5510557964274 | 0.0000000000000 + 1.0e700 | 1611.8095650958320 | 1611.8095650958320 | 0.0000000000000 + 1.0e800 | 1842.0680743952365 | 1842.0680743952365 | 0.0000000000000 + 1.0e900 | 2072.3265836946411 | 2072.3265836946411 | 0.0000000000000 + 1.0e1000 | 2302.5850929940457 | 2302.5850929940457 | 0.0000000000000 +(10 rows) + +-- inputs with 1000 decimal places +-- +-- bc(1) results computed with a scale of 2000 and rounded to 1000 decimal +-- places +WITH t(x, bc_result) AS (VALUES +(484990182159328900690402236933516249572671683638747490717351807610531884491845416923860371219625151551889257298200816555016472471293780254009492949585031653913930735918829139712249577547959394351523545901788627247613322896296041868431769047433229466634098452564756860190085118463828382895145244362033728480588969626012192733802377468089120757046364393407262957242230928854711898925295251902007136232994524624903257456111389508582206404271734668422903183500589303866613158037169610592539145461637447957948521714058034772237111009429638870236361143304703683377693378577075353794118557951847394763531830696578809001981568860219578880229402696449243344235099860421846016326538272155937175661905904288335499593232232926636205909086901191153907183842087577811871344870731324067822883041265129394268082883745408414994.8967939438561591657171240282983703914075472645212002662497023142663831371447287624846942598424990784971781730103682951722370983277124599054059027055336437808366784501932987082321905202623642371063626378290734289114618092750984153422293450048717769065428713836637664433167768445609659527458911187829232316677137895259433038764404970599325009178297626038331436654541552998098529141205301472138026818453893127265938030066392881979113522757891639646670670272542401773230506961559808927249585675430838495658225557294666522469887436551840596777627408780618586500922973500018513068499587683746133637919751545157547095670767246977244726331271787622126889459658539988980096764323712767863722912919120929339399753431689512753214200090670880647731689804555417871258907716687575767185444541243606329768784843125926070743277339790277626515824924290352180761378846035233155198504033292692893297993698953705472933411199778880561376633444249703838589180474329586470353212010427945060694794274109764269805332803290229, + 1864.3702986939570026328504202935192533137907736189919154633800554877738455118081651650863235106905871352085850240570561347180517240105510505203972860921397909573687877993477806728098306202020229409548306695695574102950949468160529713610952021974630774784174851619325758380143625473386495586347322798415543385655090746985183329114860118551572428921774322172798724455202876781611633419444058398798142214904998877857425038669920064728855823072107227506485770367799671977282350083029452784747350395161797215115525867416898416360638482342253129160308632504217096916335590470843180746834864303790913372081974355613359678634194879425862536147988835528973291020680020540866655622823550861337486588647231688134992810403147262346312159819432914207194632564009749236609081399504118359354620598232725290537215007867979331582119891661859015726276335168158288396939655310210558566592649049602925182137256134162660116182293851038854455437841571331011002023088829768308520393956515509475418031437505751407687618234418262), +(87190145885430429849953615409019208993240447426362428988181639909267773304254748257120061524000254226856815085523676417146197197996896030672521334101413071112068202429835905642444187493717977611730127126387257253646790849384975208460867137315507010888782632024640844766297185244443116696943912406389670302370461137850160539373600494054874979342373255280815156048999900951842673141766630630919020492255966628630634124452614590400422133958133100159154995520080124736657520969784129924799670552560034302960877087853678350801769339861812435411200669026902417951572668727488315537985378304242438181615160041688723201917323705450185975141141262578884689500612295576288125956289035673242989906973367691922065122033180281670221390667818909912035903387888639331486823729897326624516015340.0330856710565117793999512551468220085711713631167607285185762046751452975325645379302403715842570486302993296501788672462090620871511446272026693318239212657949496275318383141403236705902077406660768573015707706831878445598837931116223956945944726162551477136715847593742032488181481888084716920605114101902724395659898621880016853548602514706686907951229872573180602614761229992106144727082722940736406782659562775289407005631298246624198606031298081220736931229256511054595028182057216042683060059115371651410352645266000330509331097811566633211452233019461903115970558624057877018778178814946285827512359903934291318219271464841957435711594154280905473802599888081783098187210283997106131616471807951265003903143099667366508222327805543948921694362089860577380749774036318574113007382111997454202845559941557812813566442364810680529092880773126707073967537693927177460459341763934709686530005721141046645111784404932103241501569571235364365556796422998363930810983452790309019295181282099408260156, + 1793.5767085750017553306932533574391150814202249805881581227430032600579405884415934520704053351781361105595296647510475380766428668443641914861849764330704062323054023252886955844207807229267936432730818329225450152491146839618683772020068682795388746108876393249306737841247788224204701299467519965182171772253974884845661168860422489046657965359832930382114760565628765599962013955588754803194908990025689040598990346417563277021386852342928910383706995866844541160576254266641602065102228267316550706943783591722246885978355472097314691737807509436806788803362444745551013400341861820755594413819894154786253014501454443272120342005711761286524843010157182464200556865694401941794983935172457481497909987740544409272349152397774548604845897687504977786762391359552407068124283290504752932824699865504970420939586707791994870941813718246825616335675307740641350673558328821461530563823677144691877374809441673507467507447891562257806191361453045937798278733402269265623588493124129181374135958668436774), +(93936642222690597390233191619858485419795942047468396309991947772747208870873993801669373075421461116465960407843923269693395211616591453397070258466704654943689268224479477016161636938138334729982904232438440955361656138189836032891825113139184685132178764873033678116450665758561650355252211196676137179184043639278410827092182700922151290703747496962700158844772453483316974221113826173404445159281421213715669245417896170368554410830320000019029956317336703559699859949692222685614036912057150632902650913831404804982509990655560731349634628713944739168096272097122388116038119844786988276635032016787352796502360718569977397214936366251320294621522016.6483354941025384161536675750898007896744690911429670830432784905421638721478353275821072200938900938046264210604940707974410950770029535636602548377806284157951164875821446035013896786653932045182167021839184824627082391478016195098055107001433336586881395912782883663046617432598969149948351689103230162742769845955320418573803127107923535948653168889411316007796459064267436246637115946581149511513369842911210359447262641996566147462977170742544980481275049898092152042927981394239266559286915303786701737610786594006685748456635797125029722684151298695274097006242412384086302106763844070230264910503179385988626477852818174114043927841085089058972074427820150462261941575665882880501074676800316585217150509780489224388148722603385921057007086785238310735038314861960410473809826927329368597558806004392175746233568789445929554890241140656324160187253042639339549705859147930476532359840809944163908006480881926041259363654863689570520534301207043189181147254153307163555433328278834311658232337, + 1510.4332713542154696529645934345554302578243896764921637693542962119938599884313210100957753316832762996428481801312323020427109678979117469716796746760060470871840325255146954580681101106876674367471955788143763250819168311353856748872452260808797135108102729064040463343792765872545182299889360257515315869180266759715933989413256377582681707188367254513700731642913479683031478361835565783219287780434673712341147656477670848734998849030451414278832848680301511646182446524915091598080243532068451726548537866633622180283865668708517173065893429240665300584705585310049892047293928733753369421499719516009692095913169665213597158441636480707309244604139865130782756488091268094213446272360006907802989573582755585110277620911226015342778471352130366770729972784317323917141031824334355639769512749560550167491709646539950725523461943580211843652293561678342656010571108219244870234329176123205423872844099992204896411752620881541000940129833754169391528449211839693800724450201835161044717173715867437)) +SELECT trim_scale(ln(x::numeric)-bc_result) AS diff FROM t; + diff +------ + 0 + 0 + 0 +(3 rows) + +-- +-- Tests for LOG() (base 10) +-- +-- input very small, exact result known +WITH t(x) AS (SELECT '1e-'||n FROM generate_series(1, 100) g(n)) +SELECT x, log(x::numeric) FROM t; + x | log +--------+----------------------------------------------------------------------------------------------------------- + 1e-1 | -1.0000000000000000 + 1e-2 | -2.0000000000000000 + 1e-3 | -3.0000000000000000 + 1e-4 | -4.0000000000000000 + 1e-5 | -5.000000000000000 + 1e-6 | -6.000000000000000 + 1e-7 | -7.000000000000000 + 1e-8 | -8.000000000000000 + 1e-9 | -9.000000000000000 + 1e-10 | -10.000000000000000 + 1e-11 | -11.000000000000000 + 1e-12 | -12.000000000000000 + 1e-13 | -13.000000000000000 + 1e-14 | -14.000000000000000 + 1e-15 | -15.000000000000000 + 1e-16 | -16.0000000000000000 + 1e-17 | -17.00000000000000000 + 1e-18 | -18.000000000000000000 + 1e-19 | -19.0000000000000000000 + 1e-20 | -20.00000000000000000000 + 1e-21 | -21.000000000000000000000 + 1e-22 | -22.0000000000000000000000 + 1e-23 | -23.00000000000000000000000 + 1e-24 | -24.000000000000000000000000 + 1e-25 | -25.0000000000000000000000000 + 1e-26 | -26.00000000000000000000000000 + 1e-27 | -27.000000000000000000000000000 + 1e-28 | -28.0000000000000000000000000000 + 1e-29 | -29.00000000000000000000000000000 + 1e-30 | -30.000000000000000000000000000000 + 1e-31 | -31.0000000000000000000000000000000 + 1e-32 | -32.00000000000000000000000000000000 + 1e-33 | -33.000000000000000000000000000000000 + 1e-34 | -34.0000000000000000000000000000000000 + 1e-35 | -35.00000000000000000000000000000000000 + 1e-36 | -36.000000000000000000000000000000000000 + 1e-37 | -37.0000000000000000000000000000000000000 + 1e-38 | -38.00000000000000000000000000000000000000 + 1e-39 | -39.000000000000000000000000000000000000000 + 1e-40 | -40.0000000000000000000000000000000000000000 + 1e-41 | -41.00000000000000000000000000000000000000000 + 1e-42 | -42.000000000000000000000000000000000000000000 + 1e-43 | -43.0000000000000000000000000000000000000000000 + 1e-44 | -44.00000000000000000000000000000000000000000000 + 1e-45 | -45.000000000000000000000000000000000000000000000 + 1e-46 | -46.0000000000000000000000000000000000000000000000 + 1e-47 | -47.00000000000000000000000000000000000000000000000 + 1e-48 | -48.000000000000000000000000000000000000000000000000 + 1e-49 | -49.0000000000000000000000000000000000000000000000000 + 1e-50 | -50.00000000000000000000000000000000000000000000000000 + 1e-51 | -51.000000000000000000000000000000000000000000000000000 + 1e-52 | -52.0000000000000000000000000000000000000000000000000000 + 1e-53 | -53.00000000000000000000000000000000000000000000000000000 + 1e-54 | -54.000000000000000000000000000000000000000000000000000000 + 1e-55 | -55.0000000000000000000000000000000000000000000000000000000 + 1e-56 | -56.00000000000000000000000000000000000000000000000000000000 + 1e-57 | -57.000000000000000000000000000000000000000000000000000000000 + 1e-58 | -58.0000000000000000000000000000000000000000000000000000000000 + 1e-59 | -59.00000000000000000000000000000000000000000000000000000000000 + 1e-60 | -60.000000000000000000000000000000000000000000000000000000000000 + 1e-61 | -61.0000000000000000000000000000000000000000000000000000000000000 + 1e-62 | -62.00000000000000000000000000000000000000000000000000000000000000 + 1e-63 | -63.000000000000000000000000000000000000000000000000000000000000000 + 1e-64 | -64.0000000000000000000000000000000000000000000000000000000000000000 + 1e-65 | -65.00000000000000000000000000000000000000000000000000000000000000000 + 1e-66 | -66.000000000000000000000000000000000000000000000000000000000000000000 + 1e-67 | -67.0000000000000000000000000000000000000000000000000000000000000000000 + 1e-68 | -68.00000000000000000000000000000000000000000000000000000000000000000000 + 1e-69 | -69.000000000000000000000000000000000000000000000000000000000000000000000 + 1e-70 | -70.0000000000000000000000000000000000000000000000000000000000000000000000 + 1e-71 | -71.00000000000000000000000000000000000000000000000000000000000000000000000 + 1e-72 | -72.000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-73 | -73.0000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-74 | -74.00000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-75 | -75.000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-76 | -76.0000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-77 | -77.00000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-78 | -78.000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-79 | -79.0000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-80 | -80.00000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-81 | -81.000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-82 | -82.0000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-83 | -83.00000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-84 | -84.000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-85 | -85.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-86 | -86.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-87 | -87.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-88 | -88.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-89 | -89.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-90 | -90.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-91 | -91.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-92 | -92.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-93 | -93.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-94 | -94.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-95 | -95.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-96 | -96.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-97 | -97.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-98 | -98.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-99 | -99.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1e-100 | -100.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +(100 rows) + +-- input very small, non-exact results +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..50..7} +-- do +-- for d in {9..1..3} +-- do +-- l=$(bc -ql <<< "scale=500 ; l($d*10^-$p) / l(10)" | head -n 1) +-- echo "('${d}.0e-$p', $l)," +-- done +-- done +WITH t(x, bc_result) AS (VALUES +('9.0e-1', -.04575749056067513), +('6.0e-1', -.2218487496163564), +('3.0e-1', -.5228787452803376), +('9.0e-8', -7.045757490560675), +('6.0e-8', -7.221848749616356), +('3.0e-8', -7.522878745280338), +('9.0e-15', -14.0457574905606751), +('6.0e-15', -14.2218487496163564), +('3.0e-15', -14.5228787452803376), +('9.0e-22', -21.04575749056067512540994), +('6.0e-22', -21.22184874961635636749123), +('3.0e-22', -21.52287874528033756270497), +('9.0e-29', -28.045757490560675125409944193490), +('6.0e-29', -28.221848749616356367491233202020), +('3.0e-29', -28.522878745280337562704972096745), +('9.0e-36', -35.0457574905606751254099441934897693816), +('6.0e-36', -35.2218487496163563674912332020203916640), +('3.0e-36', -35.5228787452803375627049720967448846908), +('9.0e-43', -42.04575749056067512540994419348976938159974227), +('6.0e-43', -42.22184874961635636749123320202039166403168125), +('3.0e-43', -42.52287874528033756270497209674488469079987114), +('9.0e-50', -49.045757490560675125409944193489769381599742271618608), +('6.0e-50', -49.221848749616356367491233202020391664031681254347196), +('3.0e-50', -49.522878745280337562704972096744884690799871135809304)) +SELECT x, bc_result, log(x::numeric), log(x::numeric)-bc_result AS diff FROM t; + x | bc_result | log | diff +---------+---------------------------------------------------------+---------------------------------------------------------+------------------------------------------------------- + 9.0e-1 | -0.04575749056067513 | -0.04575749056067513 | 0.00000000000000000 + 6.0e-1 | -0.2218487496163564 | -0.2218487496163564 | 0.0000000000000000 + 3.0e-1 | -0.5228787452803376 | -0.5228787452803376 | 0.0000000000000000 + 9.0e-8 | -7.045757490560675 | -7.045757490560675 | 0.000000000000000 + 6.0e-8 | -7.221848749616356 | -7.221848749616356 | 0.000000000000000 + 3.0e-8 | -7.522878745280338 | -7.522878745280338 | 0.000000000000000 + 9.0e-15 | -14.0457574905606751 | -14.0457574905606751 | 0.0000000000000000 + 6.0e-15 | -14.2218487496163564 | -14.2218487496163564 | 0.0000000000000000 + 3.0e-15 | -14.5228787452803376 | -14.5228787452803376 | 0.0000000000000000 + 9.0e-22 | -21.04575749056067512540994 | -21.04575749056067512540994 | 0.00000000000000000000000 + 6.0e-22 | -21.22184874961635636749123 | -21.22184874961635636749123 | 0.00000000000000000000000 + 3.0e-22 | -21.52287874528033756270497 | -21.52287874528033756270497 | 0.00000000000000000000000 + 9.0e-29 | -28.045757490560675125409944193490 | -28.045757490560675125409944193490 | 0.000000000000000000000000000000 + 6.0e-29 | -28.221848749616356367491233202020 | -28.221848749616356367491233202020 | 0.000000000000000000000000000000 + 3.0e-29 | -28.522878745280337562704972096745 | -28.522878745280337562704972096745 | 0.000000000000000000000000000000 + 9.0e-36 | -35.0457574905606751254099441934897693816 | -35.0457574905606751254099441934897693816 | 0.0000000000000000000000000000000000000 + 6.0e-36 | -35.2218487496163563674912332020203916640 | -35.2218487496163563674912332020203916640 | 0.0000000000000000000000000000000000000 + 3.0e-36 | -35.5228787452803375627049720967448846908 | -35.5228787452803375627049720967448846908 | 0.0000000000000000000000000000000000000 + 9.0e-43 | -42.04575749056067512540994419348976938159974227 | -42.04575749056067512540994419348976938159974227 | 0.00000000000000000000000000000000000000000000 + 6.0e-43 | -42.22184874961635636749123320202039166403168125 | -42.22184874961635636749123320202039166403168125 | 0.00000000000000000000000000000000000000000000 + 3.0e-43 | -42.52287874528033756270497209674488469079987114 | -42.52287874528033756270497209674488469079987114 | 0.00000000000000000000000000000000000000000000 + 9.0e-50 | -49.045757490560675125409944193489769381599742271618608 | -49.045757490560675125409944193489769381599742271618608 | 0.000000000000000000000000000000000000000000000000000 + 6.0e-50 | -49.221848749616356367491233202020391664031681254347196 | -49.221848749616356367491233202020391664031681254347196 | 0.000000000000000000000000000000000000000000000000000 + 3.0e-50 | -49.522878745280337562704972096744884690799871135809304 | -49.522878745280337562704972096744884690799871135809304 | 0.000000000000000000000000000000000000000000000000000 +(24 rows) + +-- input very close to but smaller than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40..7} +-- do +-- for d in {9..1..3} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(1-$d*10^-$p) / l(10)" | head -n 1) +-- echo "('${d}.0e-$p', $l)," +-- done +-- done +WITH t(x, bc_result) AS (VALUES +('9.0e-1', -1.0000000000000000), +('6.0e-1', -.3979400086720376), +('3.0e-1', -.1549019599857432), +('9.0e-8', -.000000039086505130185422), +('6.0e-8', -.000000026057669695925208), +('3.0e-8', -.000000013028834652530076), +('9.0e-15', -.0000000000000039086503371292840), +('6.0e-15', -.0000000000000026057668914195188), +('3.0e-15', -.0000000000000013028834457097574), +('9.0e-22', -.00000000000000000000039086503371292664), +('6.0e-22', -.00000000000000000000026057668914195110), +('3.0e-22', -.00000000000000000000013028834457097555), +('9.0e-29', -.000000000000000000000000000039086503371292664), +('6.0e-29', -.000000000000000000000000000026057668914195110), +('3.0e-29', -.000000000000000000000000000013028834457097555), +('9.0e-36', -.0000000000000000000000000000000000039086503371292664), +('6.0e-36', -.0000000000000000000000000000000000026057668914195110), +('3.0e-36', -.0000000000000000000000000000000000013028834457097555)) +SELECT '1-'||x, bc_result, log(1.0-x::numeric), log(1.0-x::numeric)-bc_result AS diff FROM t; + ?column? | bc_result | log | diff +-----------+---------------------------------------------------------+---------------------------------------------------------+-------------------------------------------------------- + 1-9.0e-1 | -1.0000000000000000 | -1.0000000000000000 | 0.0000000000000000 + 1-6.0e-1 | -0.3979400086720376 | -0.3979400086720376 | 0.0000000000000000 + 1-3.0e-1 | -0.1549019599857432 | -0.1549019599857432 | 0.0000000000000000 + 1-9.0e-8 | -0.000000039086505130185422 | -0.000000039086505130185422 | 0.000000000000000000000000 + 1-6.0e-8 | -0.000000026057669695925208 | -0.000000026057669695925208 | 0.000000000000000000000000 + 1-3.0e-8 | -0.000000013028834652530076 | -0.000000013028834652530076 | 0.000000000000000000000000 + 1-9.0e-15 | -0.0000000000000039086503371292840 | -0.0000000000000039086503371292840 | 0.0000000000000000000000000000000 + 1-6.0e-15 | -0.0000000000000026057668914195188 | -0.0000000000000026057668914195188 | 0.0000000000000000000000000000000 + 1-3.0e-15 | -0.0000000000000013028834457097574 | -0.0000000000000013028834457097574 | 0.0000000000000000000000000000000 + 1-9.0e-22 | -0.00000000000000000000039086503371292664 | -0.00000000000000000000039086503371292664 | 0.00000000000000000000000000000000000000 + 1-6.0e-22 | -0.00000000000000000000026057668914195110 | -0.00000000000000000000026057668914195110 | 0.00000000000000000000000000000000000000 + 1-3.0e-22 | -0.00000000000000000000013028834457097555 | -0.00000000000000000000013028834457097555 | 0.00000000000000000000000000000000000000 + 1-9.0e-29 | -0.000000000000000000000000000039086503371292664 | -0.000000000000000000000000000039086503371292664 | 0.000000000000000000000000000000000000000000000 + 1-6.0e-29 | -0.000000000000000000000000000026057668914195110 | -0.000000000000000000000000000026057668914195110 | 0.000000000000000000000000000000000000000000000 + 1-3.0e-29 | -0.000000000000000000000000000013028834457097555 | -0.000000000000000000000000000013028834457097555 | 0.000000000000000000000000000000000000000000000 + 1-9.0e-36 | -0.0000000000000000000000000000000000039086503371292664 | -0.0000000000000000000000000000000000039086503371292664 | 0.0000000000000000000000000000000000000000000000000000 + 1-6.0e-36 | -0.0000000000000000000000000000000000026057668914195110 | -0.0000000000000000000000000000000000026057668914195110 | 0.0000000000000000000000000000000000000000000000000000 + 1-3.0e-36 | -0.0000000000000000000000000000000000013028834457097555 | -0.0000000000000000000000000000000000013028834457097555 | 0.0000000000000000000000000000000000000000000000000000 +(18 rows) + +-- input very close to but larger than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40..7} +-- do +-- for d in {9..1..3} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(1+$d*10^-$p) / l(10)" | head -n 1) +-- echo "('${d}.0e-$p', $l)," +-- done +-- done +WITH t(x, bc_result) AS (VALUES +('9.0e-1', .2787536009528290), +('6.0e-1', .2041199826559248), +('3.0e-1', .1139433523068368), +('9.0e-8', .000000039086501612400118), +('6.0e-8', .000000026057668132465074), +('3.0e-8', .000000013028834261665042), +('9.0e-15', .0000000000000039086503371292489), +('6.0e-15', .0000000000000026057668914195031), +('3.0e-15', .0000000000000013028834457097535), +('9.0e-22', .00000000000000000000039086503371292664), +('6.0e-22', .00000000000000000000026057668914195110), +('3.0e-22', .00000000000000000000013028834457097555), +('9.0e-29', .000000000000000000000000000039086503371292664), +('6.0e-29', .000000000000000000000000000026057668914195110), +('3.0e-29', .000000000000000000000000000013028834457097555), +('9.0e-36', .0000000000000000000000000000000000039086503371292664), +('6.0e-36', .0000000000000000000000000000000000026057668914195110), +('3.0e-36', .0000000000000000000000000000000000013028834457097555)) +SELECT '1+'||x, bc_result, log(1.0+x::numeric), log(1.0+x::numeric)-bc_result AS diff FROM t; + ?column? | bc_result | log | diff +-----------+--------------------------------------------------------+--------------------------------------------------------+-------------------------------------------------------- + 1+9.0e-1 | 0.2787536009528290 | 0.2787536009528290 | 0.0000000000000000 + 1+6.0e-1 | 0.2041199826559248 | 0.2041199826559248 | 0.0000000000000000 + 1+3.0e-1 | 0.1139433523068368 | 0.1139433523068368 | 0.0000000000000000 + 1+9.0e-8 | 0.000000039086501612400118 | 0.000000039086501612400118 | 0.000000000000000000000000 + 1+6.0e-8 | 0.000000026057668132465074 | 0.000000026057668132465074 | 0.000000000000000000000000 + 1+3.0e-8 | 0.000000013028834261665042 | 0.000000013028834261665042 | 0.000000000000000000000000 + 1+9.0e-15 | 0.0000000000000039086503371292489 | 0.0000000000000039086503371292489 | 0.0000000000000000000000000000000 + 1+6.0e-15 | 0.0000000000000026057668914195031 | 0.0000000000000026057668914195031 | 0.0000000000000000000000000000000 + 1+3.0e-15 | 0.0000000000000013028834457097535 | 0.0000000000000013028834457097535 | 0.0000000000000000000000000000000 + 1+9.0e-22 | 0.00000000000000000000039086503371292664 | 0.00000000000000000000039086503371292664 | 0.00000000000000000000000000000000000000 + 1+6.0e-22 | 0.00000000000000000000026057668914195110 | 0.00000000000000000000026057668914195110 | 0.00000000000000000000000000000000000000 + 1+3.0e-22 | 0.00000000000000000000013028834457097555 | 0.00000000000000000000013028834457097555 | 0.00000000000000000000000000000000000000 + 1+9.0e-29 | 0.000000000000000000000000000039086503371292664 | 0.000000000000000000000000000039086503371292664 | 0.000000000000000000000000000000000000000000000 + 1+6.0e-29 | 0.000000000000000000000000000026057668914195110 | 0.000000000000000000000000000026057668914195110 | 0.000000000000000000000000000000000000000000000 + 1+3.0e-29 | 0.000000000000000000000000000013028834457097555 | 0.000000000000000000000000000013028834457097555 | 0.000000000000000000000000000000000000000000000 + 1+9.0e-36 | 0.0000000000000000000000000000000000039086503371292664 | 0.0000000000000000000000000000000000039086503371292664 | 0.0000000000000000000000000000000000000000000000000000 + 1+6.0e-36 | 0.0000000000000000000000000000000000026057668914195110 | 0.0000000000000000000000000000000000026057668914195110 | 0.0000000000000000000000000000000000000000000000000000 + 1+3.0e-36 | 0.0000000000000000000000000000000000013028834457097555 | 0.0000000000000000000000000000000000013028834457097555 | 0.0000000000000000000000000000000000000000000000000000 +(18 rows) + +-- input very large, exact result known +WITH t(x) AS (SELECT '1e'||n FROM generate_series(1, 100) g(n)) +SELECT x, log(x::numeric) FROM t; + x | log +-------+-------------------- + 1e1 | 1.0000000000000000 + 1e2 | 2.0000000000000000 + 1e3 | 3.0000000000000000 + 1e4 | 4.0000000000000000 + 1e5 | 5.000000000000000 + 1e6 | 6.000000000000000 + 1e7 | 7.000000000000000 + 1e8 | 8.000000000000000 + 1e9 | 9.000000000000000 + 1e10 | 10.000000000000000 + 1e11 | 11.000000000000000 + 1e12 | 12.000000000000000 + 1e13 | 13.000000000000000 + 1e14 | 14.000000000000000 + 1e15 | 15.000000000000000 + 1e16 | 16.000000000000000 + 1e17 | 17.000000000000000 + 1e18 | 18.000000000000000 + 1e19 | 19.000000000000000 + 1e20 | 20.000000000000000 + 1e21 | 21.000000000000000 + 1e22 | 22.000000000000000 + 1e23 | 23.000000000000000 + 1e24 | 24.000000000000000 + 1e25 | 25.000000000000000 + 1e26 | 26.000000000000000 + 1e27 | 27.000000000000000 + 1e28 | 28.000000000000000 + 1e29 | 29.000000000000000 + 1e30 | 30.000000000000000 + 1e31 | 31.000000000000000 + 1e32 | 32.000000000000000 + 1e33 | 33.000000000000000 + 1e34 | 34.000000000000000 + 1e35 | 35.000000000000000 + 1e36 | 36.000000000000000 + 1e37 | 37.000000000000000 + 1e38 | 38.000000000000000 + 1e39 | 39.000000000000000 + 1e40 | 40.000000000000000 + 1e41 | 41.000000000000000 + 1e42 | 42.000000000000000 + 1e43 | 43.000000000000000 + 1e44 | 44.00000000000000 + 1e45 | 45.00000000000000 + 1e46 | 46.00000000000000 + 1e47 | 47.00000000000000 + 1e48 | 48.00000000000000 + 1e49 | 49.00000000000000 + 1e50 | 50.00000000000000 + 1e51 | 51.00000000000000 + 1e52 | 52.00000000000000 + 1e53 | 53.00000000000000 + 1e54 | 54.00000000000000 + 1e55 | 55.00000000000000 + 1e56 | 56.00000000000000 + 1e57 | 57.00000000000000 + 1e58 | 58.00000000000000 + 1e59 | 59.00000000000000 + 1e60 | 60.00000000000000 + 1e61 | 61.00000000000000 + 1e62 | 62.00000000000000 + 1e63 | 63.00000000000000 + 1e64 | 64.00000000000000 + 1e65 | 65.00000000000000 + 1e66 | 66.00000000000000 + 1e67 | 67.00000000000000 + 1e68 | 68.00000000000000 + 1e69 | 69.00000000000000 + 1e70 | 70.00000000000000 + 1e71 | 71.00000000000000 + 1e72 | 72.00000000000000 + 1e73 | 73.00000000000000 + 1e74 | 74.00000000000000 + 1e75 | 75.00000000000000 + 1e76 | 76.00000000000000 + 1e77 | 77.00000000000000 + 1e78 | 78.00000000000000 + 1e79 | 79.00000000000000 + 1e80 | 80.00000000000000 + 1e81 | 81.00000000000000 + 1e82 | 82.00000000000000 + 1e83 | 83.00000000000000 + 1e84 | 84.00000000000000 + 1e85 | 85.00000000000000 + 1e86 | 86.00000000000000 + 1e87 | 87.00000000000000 + 1e88 | 88.00000000000000 + 1e89 | 89.00000000000000 + 1e90 | 90.00000000000000 + 1e91 | 91.00000000000000 + 1e92 | 92.00000000000000 + 1e93 | 93.00000000000000 + 1e94 | 94.00000000000000 + 1e95 | 95.00000000000000 + 1e96 | 96.00000000000000 + 1e97 | 97.00000000000000 + 1e98 | 98.00000000000000 + 1e99 | 99.00000000000000 + 1e100 | 100.00000000000000 +(100 rows) + +-- input very large, non-exact results +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {10..50..7} +-- do +-- for d in {2..9..3} +-- do +-- l=$(bc -ql <<< "scale=500 ; l($d*10^$p) / l(10)" | head -n 1) +-- echo "('${d}.0e$p', $l)," +-- done +-- done +WITH t(x, bc_result) AS (VALUES +('2.0e10', 10.301029995663981), +('5.0e10', 10.698970004336019), +('8.0e10', 10.903089986991944), +('2.0e17', 17.301029995663981), +('5.0e17', 17.698970004336019), +('8.0e17', 17.903089986991944), +('2.0e24', 24.301029995663981), +('5.0e24', 24.698970004336019), +('8.0e24', 24.903089986991944), +('2.0e31', 31.301029995663981), +('5.0e31', 31.698970004336019), +('8.0e31', 31.903089986991944), +('2.0e38', 38.301029995663981), +('5.0e38', 38.698970004336019), +('8.0e38', 38.903089986991944), +('2.0e45', 45.30102999566398), +('5.0e45', 45.69897000433602), +('8.0e45', 45.90308998699194)) +SELECT x, bc_result, log(x::numeric), log(x::numeric)-bc_result AS diff FROM t; + x | bc_result | log | diff +--------+--------------------+--------------------+------------------- + 2.0e10 | 10.301029995663981 | 10.301029995663981 | 0.000000000000000 + 5.0e10 | 10.698970004336019 | 10.698970004336019 | 0.000000000000000 + 8.0e10 | 10.903089986991944 | 10.903089986991944 | 0.000000000000000 + 2.0e17 | 17.301029995663981 | 17.301029995663981 | 0.000000000000000 + 5.0e17 | 17.698970004336019 | 17.698970004336019 | 0.000000000000000 + 8.0e17 | 17.903089986991944 | 17.903089986991944 | 0.000000000000000 + 2.0e24 | 24.301029995663981 | 24.301029995663981 | 0.000000000000000 + 5.0e24 | 24.698970004336019 | 24.698970004336019 | 0.000000000000000 + 8.0e24 | 24.903089986991944 | 24.903089986991944 | 0.000000000000000 + 2.0e31 | 31.301029995663981 | 31.301029995663981 | 0.000000000000000 + 5.0e31 | 31.698970004336019 | 31.698970004336019 | 0.000000000000000 + 8.0e31 | 31.903089986991944 | 31.903089986991944 | 0.000000000000000 + 2.0e38 | 38.301029995663981 | 38.301029995663981 | 0.000000000000000 + 5.0e38 | 38.698970004336019 | 38.698970004336019 | 0.000000000000000 + 8.0e38 | 38.903089986991944 | 38.903089986991944 | 0.000000000000000 + 2.0e45 | 45.30102999566398 | 45.30102999566398 | 0.00000000000000 + 5.0e45 | 45.69897000433602 | 45.69897000433602 | 0.00000000000000 + 8.0e45 | 45.90308998699194 | 45.90308998699194 | 0.00000000000000 +(18 rows) + diff --git a/src/test/regress/expected/yb.port.partition_aggregate.out b/src/test/regress/expected/yb.port.partition_aggregate.out new file mode 100644 index 0000000000000..47408b3351392 --- /dev/null +++ b/src/test/regress/expected/yb.port.partition_aggregate.out @@ -0,0 +1,1539 @@ +-- +-- PARTITION_AGGREGATE +-- Test partitionwise aggregation on partitioned tables +-- +-- Note: to ensure plan stability, it's a good idea to make the partitions of +-- any one partitioned table in this test all have different numbers of rows. +-- +-- YB: Use colocated DB and enable parallel execution. +SET client_min_messages TO 'warning'; +DROP DATABASE IF EXISTS px_colocated WITH (force); +CREATE DATABASE px_colocated WITH colocation = on; +ALTER DATABASE px_colocated SET yb_parallel_range_rows = 1000; +ALTER DATABASE px_colocated SET yb_enable_parallel_append = on; +ALTER DATABASE px_colocated SET yb_enable_cbo = on; +\c px_colocated +-- Enable partitionwise aggregate, which by default is disabled. +SET enable_partitionwise_aggregate TO true; +-- Enable partitionwise join, which by default is disabled. +SET enable_partitionwise_join TO true; +-- Disable parallel plans. +SET max_parallel_workers_per_gather TO 0; +-- Disable incremental sort, which can influence selected plans due to fuzz factor. +SET enable_incremental_sort TO off; +-- +-- Tests for list partitioned tables. +-- +CREATE TABLE pagg_tab (a int, b int, c text, d int) PARTITION BY LIST(c); +CREATE TABLE pagg_tab_p1 PARTITION OF pagg_tab FOR VALUES IN ('0000', '0001', '0002', '0003', '0004'); +CREATE TABLE pagg_tab_p2 PARTITION OF pagg_tab FOR VALUES IN ('0005', '0006', '0007', '0008'); +CREATE TABLE pagg_tab_p3 PARTITION OF pagg_tab FOR VALUES IN ('0009', '0010', '0011'); +INSERT INTO pagg_tab SELECT i % 20, i % 30, to_char(i % 12, 'FM0000'), i % 30 FROM generate_series(0, 2999) i; +ANALYZE pagg_tab; +-- When GROUP BY clause matches; full aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT c, sum(a), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY c HAVING avg(d) < 15 ORDER BY 1, 2, 3; + QUERY PLAN +-------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_1.c, (sum(pagg_tab_1.a)), (avg(pagg_tab_1.b)) + -> Append + -> HashAggregate + Group Key: pagg_tab_1.c + Filter: (avg(pagg_tab_1.d) < '15'::numeric) + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + -> HashAggregate + Group Key: pagg_tab_2.c + Filter: (avg(pagg_tab_2.d) < '15'::numeric) + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + -> HashAggregate + Group Key: pagg_tab_3.c + Filter: (avg(pagg_tab_3.d) < '15'::numeric) + -> Seq Scan on pagg_tab_p3 pagg_tab_3 +(15 rows) + +SELECT c, sum(a), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY c HAVING avg(d) < 15 ORDER BY 1, 2, 3; + c | sum | avg | count | min | max +------+------+---------------------+-------+-----+----- + 0000 | 2000 | 12.0000000000000000 | 250 | 0 | 24 + 0001 | 2250 | 13.0000000000000000 | 250 | 1 | 25 + 0002 | 2500 | 14.0000000000000000 | 250 | 2 | 26 + 0006 | 2500 | 12.0000000000000000 | 250 | 2 | 24 + 0007 | 2750 | 13.0000000000000000 | 250 | 3 | 25 + 0008 | 2000 | 14.0000000000000000 | 250 | 0 | 26 +(6 rows) + +-- When GROUP BY clause does not match; partial aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT a, sum(b), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY a HAVING avg(d) < 15 ORDER BY 1, 2, 3; + QUERY PLAN +-------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_1.a, (sum(pagg_tab_1.b)), (avg(pagg_tab_1.b)) + -> Finalize GroupAggregate + Group Key: pagg_tab_1.a + Filter: (avg(pagg_tab_1.d) < '15'::numeric) + -> Sort + Sort Key: pagg_tab_1.a + -> Append + -> Partial HashAggregate + Group Key: pagg_tab_1.a + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + -> Partial HashAggregate + Group Key: pagg_tab_2.a + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + -> Partial HashAggregate + Group Key: pagg_tab_3.a + -> Seq Scan on pagg_tab_p3 pagg_tab_3 +(17 rows) + +SELECT a, sum(b), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY a HAVING avg(d) < 15 ORDER BY 1, 2, 3; + a | sum | avg | count | min | max +----+------+---------------------+-------+-----+----- + 0 | 1500 | 10.0000000000000000 | 150 | 0 | 20 + 1 | 1650 | 11.0000000000000000 | 150 | 1 | 21 + 2 | 1800 | 12.0000000000000000 | 150 | 2 | 22 + 3 | 1950 | 13.0000000000000000 | 150 | 3 | 23 + 4 | 2100 | 14.0000000000000000 | 150 | 4 | 24 + 10 | 1500 | 10.0000000000000000 | 150 | 10 | 20 + 11 | 1650 | 11.0000000000000000 | 150 | 11 | 21 + 12 | 1800 | 12.0000000000000000 | 150 | 12 | 22 + 13 | 1950 | 13.0000000000000000 | 150 | 13 | 23 + 14 | 2100 | 14.0000000000000000 | 150 | 14 | 24 +(10 rows) + +-- Check with multiple columns in GROUP BY +EXPLAIN (COSTS OFF) +SELECT a, c, count(*) FROM pagg_tab GROUP BY a, c; + QUERY PLAN +------------------------------------------------ + Append + -> HashAggregate + Group Key: pagg_tab_1.a, pagg_tab_1.c + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + -> HashAggregate + Group Key: pagg_tab_2.a, pagg_tab_2.c + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + -> HashAggregate + Group Key: pagg_tab_3.a, pagg_tab_3.c + -> Seq Scan on pagg_tab_p3 pagg_tab_3 +(10 rows) + +-- Check with multiple columns in GROUP BY, order in GROUP BY is reversed +EXPLAIN (COSTS OFF) +SELECT a, c, count(*) FROM pagg_tab GROUP BY c, a; + QUERY PLAN +------------------------------------------------ + Append + -> HashAggregate + Group Key: pagg_tab_1.c, pagg_tab_1.a + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + -> HashAggregate + Group Key: pagg_tab_2.c, pagg_tab_2.a + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + -> HashAggregate + Group Key: pagg_tab_3.c, pagg_tab_3.a + -> Seq Scan on pagg_tab_p3 pagg_tab_3 +(10 rows) + +-- Check with multiple columns in GROUP BY, order in target-list is reversed +EXPLAIN (COSTS OFF) +SELECT c, a, count(*) FROM pagg_tab GROUP BY a, c; + QUERY PLAN +------------------------------------------------ + Append + -> HashAggregate + Group Key: pagg_tab_1.a, pagg_tab_1.c + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + -> HashAggregate + Group Key: pagg_tab_2.a, pagg_tab_2.c + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + -> HashAggregate + Group Key: pagg_tab_3.a, pagg_tab_3.c + -> Seq Scan on pagg_tab_p3 pagg_tab_3 +(10 rows) + +-- Test when input relation for grouping is dummy +EXPLAIN (COSTS OFF) +SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; + QUERY PLAN +-------------------------------- + HashAggregate + Group Key: c + -> Result pagg_tab + One-Time Filter: false +(4 rows) + +SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; + c | sum +---+----- +(0 rows) + +EXPLAIN (COSTS OFF) +SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; + QUERY PLAN +-------------------------------- + GroupAggregate + Group Key: c + -> Result pagg_tab + One-Time Filter: false +(4 rows) + +SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; + c | sum +---+----- +(0 rows) + +-- Test GroupAggregate paths by disabling hash aggregates. +SET enable_hashagg TO false; +-- When GROUP BY clause matches full aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT c, sum(a), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; + QUERY PLAN +-------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_1.c, (sum(pagg_tab_1.a)), (avg(pagg_tab_1.b)) + -> Append + -> GroupAggregate + Group Key: pagg_tab_1.c + Filter: (avg(pagg_tab_1.d) < '15'::numeric) + -> Sort + Sort Key: pagg_tab_1.c + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + -> GroupAggregate + Group Key: pagg_tab_2.c + Filter: (avg(pagg_tab_2.d) < '15'::numeric) + -> Sort + Sort Key: pagg_tab_2.c + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + -> GroupAggregate + Group Key: pagg_tab_3.c + Filter: (avg(pagg_tab_3.d) < '15'::numeric) + -> Sort + Sort Key: pagg_tab_3.c + -> Seq Scan on pagg_tab_p3 pagg_tab_3 +(21 rows) + +SELECT c, sum(a), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; + c | sum | avg | count +------+------+---------------------+------- + 0000 | 2000 | 12.0000000000000000 | 250 + 0001 | 2250 | 13.0000000000000000 | 250 + 0002 | 2500 | 14.0000000000000000 | 250 + 0006 | 2500 | 12.0000000000000000 | 250 + 0007 | 2750 | 13.0000000000000000 | 250 + 0008 | 2000 | 14.0000000000000000 | 250 +(6 rows) + +-- When GROUP BY clause does not match; partial aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT a, sum(b), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; + QUERY PLAN +-------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_1.a, (sum(pagg_tab_1.b)), (avg(pagg_tab_1.b)) + -> Finalize GroupAggregate + Group Key: pagg_tab_1.a + Filter: (avg(pagg_tab_1.d) < '15'::numeric) + -> Merge Append + Sort Key: pagg_tab_1.a + -> Partial GroupAggregate + Group Key: pagg_tab_1.a + -> Sort + Sort Key: pagg_tab_1.a + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + -> Partial GroupAggregate + Group Key: pagg_tab_2.a + -> Sort + Sort Key: pagg_tab_2.a + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + -> Partial GroupAggregate + Group Key: pagg_tab_3.a + -> Sort + Sort Key: pagg_tab_3.a + -> Seq Scan on pagg_tab_p3 pagg_tab_3 +(22 rows) + +SELECT a, sum(b), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; + a | sum | avg | count +----+------+---------------------+------- + 0 | 1500 | 10.0000000000000000 | 150 + 1 | 1650 | 11.0000000000000000 | 150 + 2 | 1800 | 12.0000000000000000 | 150 + 3 | 1950 | 13.0000000000000000 | 150 + 4 | 2100 | 14.0000000000000000 | 150 + 10 | 1500 | 10.0000000000000000 | 150 + 11 | 1650 | 11.0000000000000000 | 150 + 12 | 1800 | 12.0000000000000000 | 150 + 13 | 1950 | 13.0000000000000000 | 150 + 14 | 2100 | 14.0000000000000000 | 150 +(10 rows) + +-- Test partitionwise grouping without any aggregates +EXPLAIN (COSTS OFF) +SELECT c FROM pagg_tab GROUP BY c ORDER BY 1; + QUERY PLAN +------------------------------------------------------ + Merge Append + Sort Key: pagg_tab_1.c + -> Group + Group Key: pagg_tab_1.c + -> Sort + Sort Key: pagg_tab_1.c + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + -> Group + Group Key: pagg_tab_2.c + -> Sort + Sort Key: pagg_tab_2.c + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + -> Group + Group Key: pagg_tab_3.c + -> Sort + Sort Key: pagg_tab_3.c + -> Seq Scan on pagg_tab_p3 pagg_tab_3 +(17 rows) + +SELECT c FROM pagg_tab GROUP BY c ORDER BY 1; + c +------ + 0000 + 0001 + 0002 + 0003 + 0004 + 0005 + 0006 + 0007 + 0008 + 0009 + 0010 + 0011 +(12 rows) + +EXPLAIN (COSTS OFF) +SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1; + QUERY PLAN +------------------------------------------------------------ + Group + Group Key: pagg_tab_1.a + -> Merge Append + Sort Key: pagg_tab_1.a + -> Group + Group Key: pagg_tab_1.a + -> Sort + Sort Key: pagg_tab_1.a + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + Storage Filter: (a < 3) + -> Group + Group Key: pagg_tab_2.a + -> Sort + Sort Key: pagg_tab_2.a + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + Storage Filter: (a < 3) + -> Group + Group Key: pagg_tab_3.a + -> Sort + Sort Key: pagg_tab_3.a + -> Seq Scan on pagg_tab_p3 pagg_tab_3 + Storage Filter: (a < 3) +(22 rows) + +SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1; + a +--- + 0 + 1 + 2 +(3 rows) + +RESET enable_hashagg; +-- ROLLUP, partitionwise aggregation does not apply +EXPLAIN (COSTS OFF) +SELECT c, sum(a) FROM pagg_tab GROUP BY rollup(c) ORDER BY 1, 2; + QUERY PLAN +------------------------------------------------------ + Sort + Sort Key: pagg_tab.c, (sum(pagg_tab.a)) + -> MixedAggregate + Hash Key: pagg_tab.c + Group Key: () + -> Append + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + -> Seq Scan on pagg_tab_p3 pagg_tab_3 +(9 rows) + +-- ORDERED SET within the aggregate. +-- Full aggregation; since all the rows that belong to the same group come +-- from the same partition, having an ORDER BY within the aggregate doesn't +-- make any difference. +EXPLAIN (COSTS OFF) +SELECT c, sum(b order by a) FROM pagg_tab GROUP BY c ORDER BY 1, 2; + QUERY PLAN +--------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_1.c, (sum(pagg_tab_1.b ORDER BY pagg_tab_1.a)) + -> Append + -> GroupAggregate + Group Key: pagg_tab_1.c + -> Sort + Sort Key: pagg_tab_1.c + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + -> GroupAggregate + Group Key: pagg_tab_2.c + -> Sort + Sort Key: pagg_tab_2.c + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + -> GroupAggregate + Group Key: pagg_tab_3.c + -> Sort + Sort Key: pagg_tab_3.c + -> Seq Scan on pagg_tab_p3 pagg_tab_3 +(18 rows) + +-- Since GROUP BY clause does not match with PARTITION KEY; we need to do +-- partial aggregation. However, ORDERED SET are not partial safe and thus +-- partitionwise aggregation plan is not generated. +EXPLAIN (COSTS OFF) +SELECT a, sum(b order by a) FROM pagg_tab GROUP BY a ORDER BY 1, 2; + QUERY PLAN +--------------------------------------------------------------- + Sort + Sort Key: pagg_tab.a, (sum(pagg_tab.b ORDER BY pagg_tab.a)) + -> GroupAggregate + Group Key: pagg_tab.a + -> Sort + Sort Key: pagg_tab.a + -> Append + -> Seq Scan on pagg_tab_p1 pagg_tab_1 + -> Seq Scan on pagg_tab_p2 pagg_tab_2 + -> Seq Scan on pagg_tab_p3 pagg_tab_3 +(10 rows) + +-- JOIN query +CREATE TABLE pagg_tab1(x int, y int) PARTITION BY RANGE(x); +CREATE TABLE pagg_tab1_p1 PARTITION OF pagg_tab1 FOR VALUES FROM (0) TO (10); +CREATE TABLE pagg_tab1_p2 PARTITION OF pagg_tab1 FOR VALUES FROM (10) TO (20); +CREATE TABLE pagg_tab1_p3 PARTITION OF pagg_tab1 FOR VALUES FROM (20) TO (30); +CREATE TABLE pagg_tab2(x int, y int) PARTITION BY RANGE(y); +CREATE TABLE pagg_tab2_p1 PARTITION OF pagg_tab2 FOR VALUES FROM (0) TO (10); +CREATE TABLE pagg_tab2_p2 PARTITION OF pagg_tab2 FOR VALUES FROM (10) TO (20); +CREATE TABLE pagg_tab2_p3 PARTITION OF pagg_tab2 FOR VALUES FROM (20) TO (30); +INSERT INTO pagg_tab1 SELECT i % 30, i % 20 FROM generate_series(0, 299, 2) i; +INSERT INTO pagg_tab2 SELECT i % 20, i % 30 FROM generate_series(0, 299, 3) i; +ANALYZE pagg_tab1; +ANALYZE pagg_tab2; +-- When GROUP BY clause matches; full aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; + QUERY PLAN +------------------------------------------------------------- + Sort + Sort Key: t1_1.x, (sum(t1_1.y)), (count(*)) + -> Append + -> HashAggregate + Group Key: t1_1.x + -> Hash Join + Hash Cond: (t1_1.x = t2_1.y) + -> Seq Scan on pagg_tab1_p1 t1_1 + -> Hash + -> Seq Scan on pagg_tab2_p1 t2_1 + -> HashAggregate + Group Key: t1_2.x + -> Hash Join + Hash Cond: (t1_2.x = t2_2.y) + -> Seq Scan on pagg_tab1_p2 t1_2 + -> Hash + -> Seq Scan on pagg_tab2_p2 t2_2 + -> HashAggregate + Group Key: t1_3.x + -> Hash Join + Hash Cond: (t1_3.x = t2_3.y) + -> Seq Scan on pagg_tab1_p3 t1_3 + -> Hash + -> Seq Scan on pagg_tab2_p3 t2_3 +(24 rows) + +SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; + x | sum | count +----+------+------- + 0 | 500 | 100 + 6 | 1100 | 100 + 12 | 700 | 100 + 18 | 1300 | 100 + 24 | 900 | 100 +(5 rows) + +-- Check with whole-row reference; partitionwise aggregation does not apply +EXPLAIN (COSTS OFF) +SELECT t1.x, sum(t1.y), count(t1) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; + QUERY PLAN +------------------------------------------------------------- + Sort + Sort Key: t1.x, (sum(t1.y)), (count(((t1.*)::pagg_tab1))) + -> HashAggregate + Group Key: t1.x + -> Hash Join + Hash Cond: (t1.x = t2.y) + -> Append + -> Seq Scan on pagg_tab1_p1 t1_1 + -> Seq Scan on pagg_tab1_p2 t1_2 + -> Seq Scan on pagg_tab1_p3 t1_3 + -> Hash + -> Append + -> Seq Scan on pagg_tab2_p1 t2_1 + -> Seq Scan on pagg_tab2_p2 t2_2 + -> Seq Scan on pagg_tab2_p3 t2_3 +(15 rows) + +SELECT t1.x, sum(t1.y), count(t1) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; + x | sum | count +----+------+------- + 0 | 500 | 100 + 6 | 1100 | 100 + 12 | 700 | 100 + 18 | 1300 | 100 + 24 | 900 | 100 +(5 rows) + +-- GROUP BY having other matching key +EXPLAIN (COSTS OFF) +SELECT t2.y, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t2.y ORDER BY 1, 2, 3; + QUERY PLAN +------------------------------------------------------------- + Sort + Sort Key: t2_1.y, (sum(t1_1.y)), (count(*)) + -> Append + -> HashAggregate + Group Key: t2_1.y + -> Hash Join + Hash Cond: (t1_1.x = t2_1.y) + -> Seq Scan on pagg_tab1_p1 t1_1 + -> Hash + -> Seq Scan on pagg_tab2_p1 t2_1 + -> HashAggregate + Group Key: t2_2.y + -> Hash Join + Hash Cond: (t1_2.x = t2_2.y) + -> Seq Scan on pagg_tab1_p2 t1_2 + -> Hash + -> Seq Scan on pagg_tab2_p2 t2_2 + -> HashAggregate + Group Key: t2_3.y + -> Hash Join + Hash Cond: (t1_3.x = t2_3.y) + -> Seq Scan on pagg_tab1_p3 t1_3 + -> Hash + -> Seq Scan on pagg_tab2_p3 t2_3 +(24 rows) + +-- When GROUP BY clause does not match; partial aggregation is performed for each partition. +-- Also test GroupAggregate paths by disabling hash aggregates. +SET enable_hashagg TO false; +EXPLAIN (COSTS OFF) +SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3; + QUERY PLAN +------------------------------------------------------------------------- + Sort + Sort Key: t1_1.y, (sum(t1_1.x)), (count(*)) + -> Finalize GroupAggregate + Group Key: t1_1.y + Filter: (avg(t1_1.x) > '10'::numeric) + -> Merge Append + Sort Key: t1_1.y + -> Partial GroupAggregate + Group Key: t1_1.y + -> Sort + Sort Key: t1_1.y + -> Hash Join + Hash Cond: (t1_1.x = t2_1.y) + -> Seq Scan on pagg_tab1_p1 t1_1 + -> Hash + -> Seq Scan on pagg_tab2_p1 t2_1 + -> Partial GroupAggregate + Group Key: t1_2.y + -> Sort + Sort Key: t1_2.y + -> Hash Join + Hash Cond: (t1_2.x = t2_2.y) + -> Seq Scan on pagg_tab1_p2 t1_2 + -> Hash + -> Seq Scan on pagg_tab2_p2 t2_2 + -> Partial GroupAggregate + Group Key: t1_3.y + -> Sort + Sort Key: t1_3.y + -> Hash Join + Hash Cond: (t1_3.x = t2_3.y) + -> Seq Scan on pagg_tab1_p3 t1_3 + -> Hash + -> Seq Scan on pagg_tab2_p3 t2_3 +(34 rows) + +SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3; + y | sum | count +----+------+------- + 2 | 600 | 50 + 4 | 1200 | 50 + 8 | 900 | 50 + 12 | 600 | 50 + 14 | 1200 | 50 + 18 | 900 | 50 +(6 rows) + +RESET enable_hashagg; +-- Check with LEFT/RIGHT/FULL OUTER JOINs which produces NULL values for +-- aggregation +-- LEFT JOIN, should produce partial partitionwise aggregation plan as +-- GROUP BY is on nullable column +EXPLAIN (COSTS OFF) +SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; + QUERY PLAN +------------------------------------------------------------------ + Finalize GroupAggregate + Group Key: b_1.y + -> Sort + Sort Key: b_1.y + -> Append + -> Partial HashAggregate + Group Key: b_1.y + -> Hash Left Join + Hash Cond: (a_1.x = b_1.y) + -> Seq Scan on pagg_tab1_p1 a_1 + -> Hash + -> Seq Scan on pagg_tab2_p1 b_1 + -> Partial HashAggregate + Group Key: b_2.y + -> Hash Left Join + Hash Cond: (a_2.x = b_2.y) + -> Seq Scan on pagg_tab1_p2 a_2 + -> Hash + -> Seq Scan on pagg_tab2_p2 b_2 + -> Partial HashAggregate + Group Key: b_3.y + -> Hash Left Join + Hash Cond: (a_3.x = b_3.y) + -> Seq Scan on pagg_tab1_p3 a_3 + -> Hash + -> Seq Scan on pagg_tab2_p3 b_3 +(26 rows) + +SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; + y | sum +----+------ + 0 | 500 + 6 | 1100 + 12 | 700 + 18 | 1300 + 24 | 900 + | 900 +(6 rows) + +-- RIGHT JOIN, should produce full partitionwise aggregation plan as +-- GROUP BY is on non-nullable column +EXPLAIN (COSTS OFF) +SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; + QUERY PLAN +------------------------------------------------------------ + Sort + Sort Key: b_1.y + -> Append + -> HashAggregate + Group Key: b_1.y + -> Hash Right Join + Hash Cond: (a_1.x = b_1.y) + -> Seq Scan on pagg_tab1_p1 a_1 + -> Hash + -> Seq Scan on pagg_tab2_p1 b_1 + -> HashAggregate + Group Key: b_2.y + -> Hash Right Join + Hash Cond: (a_2.x = b_2.y) + -> Seq Scan on pagg_tab1_p2 a_2 + -> Hash + -> Seq Scan on pagg_tab2_p2 b_2 + -> HashAggregate + Group Key: b_3.y + -> Hash Right Join + Hash Cond: (a_3.x = b_3.y) + -> Seq Scan on pagg_tab1_p3 a_3 + -> Hash + -> Seq Scan on pagg_tab2_p3 b_3 +(24 rows) + +SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; + y | sum +----+------ + 0 | 500 + 3 | + 6 | 1100 + 9 | + 12 | 700 + 15 | + 18 | 1300 + 21 | + 24 | 900 + 27 | +(10 rows) + +-- FULL JOIN, should produce partial partitionwise aggregation plan as +-- GROUP BY is on nullable column +EXPLAIN (COSTS OFF) +SELECT a.x, sum(b.x) FROM pagg_tab1 a FULL OUTER JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x ORDER BY 1 NULLS LAST; + QUERY PLAN +------------------------------------------------------------------ + Finalize GroupAggregate + Group Key: a_1.x + -> Sort + Sort Key: a_1.x + -> Append + -> Partial HashAggregate + Group Key: a_1.x + -> Hash Full Join + Hash Cond: (b_1.y = a_1.x) + -> Seq Scan on pagg_tab2_p1 b_1 + -> Hash + -> Seq Scan on pagg_tab1_p1 a_1 + -> Partial HashAggregate + Group Key: a_2.x + -> Hash Full Join + Hash Cond: (a_2.x = b_2.y) + -> Seq Scan on pagg_tab1_p2 a_2 + -> Hash + -> Seq Scan on pagg_tab2_p2 b_2 + -> Partial HashAggregate + Group Key: a_3.x + -> Hash Full Join + Hash Cond: (a_3.x = b_3.y) + -> Seq Scan on pagg_tab1_p3 a_3 + -> Hash + -> Seq Scan on pagg_tab2_p3 b_3 +(26 rows) + +SELECT a.x, sum(b.x) FROM pagg_tab1 a FULL OUTER JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x ORDER BY 1 NULLS LAST; + x | sum +----+------ + 0 | 500 + 2 | + 4 | + 6 | 1100 + 8 | + 10 | + 12 | 700 + 14 | + 16 | + 18 | 1300 + 20 | + 22 | + 24 | 900 + 26 | + 28 | + | 500 +(16 rows) + +-- LEFT JOIN, with dummy relation on right side, ideally +-- should produce full partitionwise aggregation plan as GROUP BY is on +-- non-nullable columns. +-- But right now we are unable to do partitionwise join in this case. +EXPLAIN (COSTS OFF) +SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; + QUERY PLAN +-------------------------------------------------------------------- + Sort + Sort Key: pagg_tab1.x, pagg_tab2.y + -> HashAggregate + Group Key: pagg_tab1.x, pagg_tab2.y + -> Hash Left Join + Hash Cond: (pagg_tab1.x = pagg_tab2.y) + Filter: ((pagg_tab1.x > 5) OR (pagg_tab2.y < 20)) + -> Append + -> Seq Scan on pagg_tab1_p1 pagg_tab1_1 + Storage Filter: (x < 20) + -> Seq Scan on pagg_tab1_p2 pagg_tab1_2 + Storage Filter: (x < 20) + -> Hash + -> Append + -> Seq Scan on pagg_tab2_p2 pagg_tab2_1 + Storage Filter: (y > 10) + -> Seq Scan on pagg_tab2_p3 pagg_tab2_2 + Storage Filter: (y > 10) +(18 rows) + +SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; + x | y | count +----+----+------- + 6 | | 10 + 8 | | 10 + 10 | | 10 + 12 | 12 | 100 + 14 | | 10 + 16 | | 10 + 18 | 18 | 100 +(7 rows) + +-- FULL JOIN, with dummy relations on both sides, ideally +-- should produce partial partitionwise aggregation plan as GROUP BY is on +-- nullable columns. +-- But right now we are unable to do partitionwise join in this case. +EXPLAIN (COSTS OFF) +SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a FULL JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; + QUERY PLAN +-------------------------------------------------------------------- + Sort + Sort Key: pagg_tab1.x, pagg_tab2.y + -> HashAggregate + Group Key: pagg_tab1.x, pagg_tab2.y + -> Hash Full Join + Hash Cond: (pagg_tab1.x = pagg_tab2.y) + Filter: ((pagg_tab1.x > 5) OR (pagg_tab2.y < 20)) + -> Append + -> Seq Scan on pagg_tab1_p1 pagg_tab1_1 + Storage Filter: (x < 20) + -> Seq Scan on pagg_tab1_p2 pagg_tab1_2 + Storage Filter: (x < 20) + -> Hash + -> Append + -> Seq Scan on pagg_tab2_p2 pagg_tab2_1 + Storage Filter: (y > 10) + -> Seq Scan on pagg_tab2_p3 pagg_tab2_2 + Storage Filter: (y > 10) +(18 rows) + +SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a FULL JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; + x | y | count +----+----+------- + 6 | | 10 + 8 | | 10 + 10 | | 10 + 12 | 12 | 100 + 14 | | 10 + 16 | | 10 + 18 | 18 | 100 + | 15 | 10 +(8 rows) + +-- Empty join relation because of empty outer side, no partitionwise agg plan +EXPLAIN (COSTS OFF) +SELECT a.x, a.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x = 1 AND x = 2) a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x, a.y ORDER BY 1, 2; + QUERY PLAN +--------------------------------------- + GroupAggregate + Group Key: pagg_tab1.x, pagg_tab1.y + -> Sort + Sort Key: pagg_tab1.y + -> Result + One-Time Filter: false +(6 rows) + +SELECT a.x, a.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x = 1 AND x = 2) a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x, a.y ORDER BY 1, 2; + x | y | count +---+---+------- +(0 rows) + +-- Partition by multiple columns +CREATE TABLE pagg_tab_m (a int, b int, c int) PARTITION BY RANGE(a, ((a+b)/2)); +CREATE TABLE pagg_tab_m_p1 PARTITION OF pagg_tab_m FOR VALUES FROM (0, 0) TO (12, 12); +CREATE TABLE pagg_tab_m_p2 PARTITION OF pagg_tab_m FOR VALUES FROM (12, 12) TO (22, 22); +CREATE TABLE pagg_tab_m_p3 PARTITION OF pagg_tab_m FOR VALUES FROM (22, 22) TO (30, 30); +INSERT INTO pagg_tab_m SELECT i % 30, i % 40, i % 50 FROM generate_series(0, 2999) i; +ANALYZE pagg_tab_m; +-- Partial aggregation as GROUP BY clause does not match with PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a HAVING avg(c) < 22 ORDER BY 1, 2, 3; + QUERY PLAN +-------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_m_1.a, (sum(pagg_tab_m_1.b)), (avg(pagg_tab_m_1.c)) + -> Finalize GroupAggregate + Group Key: pagg_tab_m_1.a + Filter: (avg(pagg_tab_m_1.c) < '22'::numeric) + -> Sort + Sort Key: pagg_tab_m_1.a + -> Append + -> Partial HashAggregate + Group Key: pagg_tab_m_1.a + -> Seq Scan on pagg_tab_m_p1 pagg_tab_m_1 + -> Partial HashAggregate + Group Key: pagg_tab_m_2.a + -> Seq Scan on pagg_tab_m_p2 pagg_tab_m_2 + -> Partial HashAggregate + Group Key: pagg_tab_m_3.a + -> Seq Scan on pagg_tab_m_p3 pagg_tab_m_3 +(17 rows) + +SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a HAVING avg(c) < 22 ORDER BY 1, 2, 3; + a | sum | avg | count +----+------+---------------------+------- + 0 | 1500 | 20.0000000000000000 | 100 + 1 | 1600 | 21.0000000000000000 | 100 + 10 | 1500 | 20.0000000000000000 | 100 + 11 | 1600 | 21.0000000000000000 | 100 + 20 | 1500 | 20.0000000000000000 | 100 + 21 | 1600 | 21.0000000000000000 | 100 +(6 rows) + +-- Full aggregation as GROUP BY clause matches with PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a, (a+b)/2 HAVING sum(b) < 50 ORDER BY 1, 2, 3; + QUERY PLAN +---------------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_m_1.a, (sum(pagg_tab_m_1.b)), (avg(pagg_tab_m_1.c)) + -> Append + -> HashAggregate + Group Key: pagg_tab_m_1.a, ((pagg_tab_m_1.a + pagg_tab_m_1.b) / 2) + Filter: (sum(pagg_tab_m_1.b) < 50) + -> Seq Scan on pagg_tab_m_p1 pagg_tab_m_1 + -> HashAggregate + Group Key: pagg_tab_m_2.a, ((pagg_tab_m_2.a + pagg_tab_m_2.b) / 2) + Filter: (sum(pagg_tab_m_2.b) < 50) + -> Seq Scan on pagg_tab_m_p2 pagg_tab_m_2 + -> HashAggregate + Group Key: pagg_tab_m_3.a, ((pagg_tab_m_3.a + pagg_tab_m_3.b) / 2) + Filter: (sum(pagg_tab_m_3.b) < 50) + -> Seq Scan on pagg_tab_m_p3 pagg_tab_m_3 +(15 rows) + +SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a, (a+b)/2 HAVING sum(b) < 50 ORDER BY 1, 2, 3; + a | sum | avg | count +----+-----+---------------------+------- + 0 | 0 | 20.0000000000000000 | 25 + 1 | 25 | 21.0000000000000000 | 25 + 10 | 0 | 20.0000000000000000 | 25 + 11 | 25 | 21.0000000000000000 | 25 + 20 | 0 | 20.0000000000000000 | 25 + 21 | 25 | 21.0000000000000000 | 25 +(6 rows) + +-- Full aggregation as PARTITION KEY is part of GROUP BY clause +EXPLAIN (COSTS OFF) +SELECT a, c, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY (a+b)/2, 2, 1 HAVING sum(b) = 50 AND avg(c) > 25 ORDER BY 1, 2, 3; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_m_1.a, pagg_tab_m_1.c, (sum(pagg_tab_m_1.b)) + -> Append + -> HashAggregate + Group Key: ((pagg_tab_m_1.a + pagg_tab_m_1.b) / 2), pagg_tab_m_1.c, pagg_tab_m_1.a + Filter: ((sum(pagg_tab_m_1.b) = 50) AND (avg(pagg_tab_m_1.c) > '25'::numeric)) + -> Seq Scan on pagg_tab_m_p1 pagg_tab_m_1 + -> HashAggregate + Group Key: ((pagg_tab_m_2.a + pagg_tab_m_2.b) / 2), pagg_tab_m_2.c, pagg_tab_m_2.a + Filter: ((sum(pagg_tab_m_2.b) = 50) AND (avg(pagg_tab_m_2.c) > '25'::numeric)) + -> Seq Scan on pagg_tab_m_p2 pagg_tab_m_2 + -> HashAggregate + Group Key: ((pagg_tab_m_3.a + pagg_tab_m_3.b) / 2), pagg_tab_m_3.c, pagg_tab_m_3.a + Filter: ((sum(pagg_tab_m_3.b) = 50) AND (avg(pagg_tab_m_3.c) > '25'::numeric)) + -> Seq Scan on pagg_tab_m_p3 pagg_tab_m_3 +(15 rows) + +SELECT a, c, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY (a+b)/2, 2, 1 HAVING sum(b) = 50 AND avg(c) > 25 ORDER BY 1, 2, 3; + a | c | sum | avg | count +----+----+-----+---------------------+------- + 0 | 30 | 50 | 30.0000000000000000 | 5 + 0 | 40 | 50 | 40.0000000000000000 | 5 + 10 | 30 | 50 | 30.0000000000000000 | 5 + 10 | 40 | 50 | 40.0000000000000000 | 5 + 20 | 30 | 50 | 30.0000000000000000 | 5 + 20 | 40 | 50 | 40.0000000000000000 | 5 +(6 rows) + +-- Test with multi-level partitioning scheme +CREATE TABLE pagg_tab_ml (a int, b int, c text) PARTITION BY RANGE(a); +CREATE TABLE pagg_tab_ml_p1 PARTITION OF pagg_tab_ml FOR VALUES FROM (0) TO (12); +CREATE TABLE pagg_tab_ml_p2 PARTITION OF pagg_tab_ml FOR VALUES FROM (12) TO (20) PARTITION BY LIST (c); +CREATE TABLE pagg_tab_ml_p2_s1 PARTITION OF pagg_tab_ml_p2 FOR VALUES IN ('0000', '0001', '0002'); +CREATE TABLE pagg_tab_ml_p2_s2 PARTITION OF pagg_tab_ml_p2 FOR VALUES IN ('0003'); +-- This level of partitioning has different column positions than the parent +CREATE TABLE pagg_tab_ml_p3(b int, c text, a int) PARTITION BY RANGE (b); +CREATE TABLE pagg_tab_ml_p3_s1(c text, a int, b int); +CREATE TABLE pagg_tab_ml_p3_s2 PARTITION OF pagg_tab_ml_p3 FOR VALUES FROM (7) TO (10); +ALTER TABLE pagg_tab_ml_p3 ATTACH PARTITION pagg_tab_ml_p3_s1 FOR VALUES FROM (0) TO (7); +ALTER TABLE pagg_tab_ml ATTACH PARTITION pagg_tab_ml_p3 FOR VALUES FROM (20) TO (30); +INSERT INTO pagg_tab_ml SELECT i % 30, i % 10, to_char(i % 4, 'FM0000') FROM generate_series(0, 29999) i; +ANALYZE pagg_tab_ml; +-- For Parallel Append +SET max_parallel_workers_per_gather TO 2; +SET parallel_setup_cost = 0; +-- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY +-- for level 1 only. For subpartitions, GROUP BY clause does not match with +-- PARTITION KEY, but still we do not see a partial aggregation as array_agg() +-- is not partial agg safe. +EXPLAIN (COSTS OFF) +SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_ml_1.a, (sum(pagg_tab_ml_1.b)), (array_agg(DISTINCT pagg_tab_ml_1.c)) + -> Gather + Workers Planned: 2 + -> Parallel Append + -> GroupAggregate + Group Key: pagg_tab_ml_1.a + Filter: (avg(pagg_tab_ml_1.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_1.a + -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml_1 + -> GroupAggregate + Group Key: pagg_tab_ml_6.a + Filter: (avg(pagg_tab_ml_6.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_6.a + -> Append + -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_6 + -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_7 + -> GroupAggregate + Group Key: pagg_tab_ml_3.a + Filter: (avg(pagg_tab_ml_3.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_3.a + -> Append + -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_3 + -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_4 +(27 rows) + +SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; + a | sum | array_agg | count +----+------+-------------+------- + 0 | 0 | {0000,0002} | 1000 + 1 | 1000 | {0001,0003} | 1000 + 2 | 2000 | {0000,0002} | 1000 + 10 | 0 | {0000,0002} | 1000 + 11 | 1000 | {0001,0003} | 1000 + 12 | 2000 | {0000,0002} | 1000 + 20 | 0 | {0000,0002} | 1000 + 21 | 1000 | {0001,0003} | 1000 + 22 | 2000 | {0000,0002} | 1000 +(9 rows) + +-- Without ORDER BY clause, to test Gather at top-most path +EXPLAIN (COSTS OFF) +SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3; + QUERY PLAN +--------------------------------------------------------------------------- + Gather + Workers Planned: 2 + -> Parallel Append + -> GroupAggregate + Group Key: pagg_tab_ml_1.a + Filter: (avg(pagg_tab_ml_1.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_1.a + -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml_1 + -> GroupAggregate + Group Key: pagg_tab_ml_6.a + Filter: (avg(pagg_tab_ml_6.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_6.a + -> Append + -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_6 + -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_7 + -> GroupAggregate + Group Key: pagg_tab_ml_3.a + Filter: (avg(pagg_tab_ml_3.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_3.a + -> Append + -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_3 + -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_4 +(25 rows) + +RESET parallel_setup_cost; +-- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY +-- for level 1 only. For subpartitions, GROUP BY clause does not match with +-- PARTITION KEY, thus we will have a partial aggregation for them. +EXPLAIN (COSTS OFF) +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; + QUERY PLAN +--------------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_ml_1.a, (sum(pagg_tab_ml_1.b)), (count(*)) + -> Append + -> HashAggregate + Group Key: pagg_tab_ml_1.a + Filter: (avg(pagg_tab_ml_1.b) < '3'::numeric) + -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml_1 + -> Finalize GroupAggregate + Group Key: pagg_tab_ml_3.a + Filter: (avg(pagg_tab_ml_3.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_3.a + -> Append + -> Partial HashAggregate + Group Key: pagg_tab_ml_3.a + -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_3 + -> Partial HashAggregate + Group Key: pagg_tab_ml_4.a + -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_4 + -> Finalize GroupAggregate + Group Key: pagg_tab_ml_6.a + Filter: (avg(pagg_tab_ml_6.b) < '3'::numeric) + -> Sort + Sort Key: pagg_tab_ml_6.a + -> Append + -> Partial HashAggregate + Group Key: pagg_tab_ml_6.a + -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_6 + -> Partial HashAggregate + Group Key: pagg_tab_ml_7.a + -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_7 +(31 rows) + +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; + a | sum | count +----+------+------- + 0 | 0 | 1000 + 1 | 1000 | 1000 + 2 | 2000 | 1000 + 10 | 0 | 1000 + 11 | 1000 | 1000 + 12 | 2000 | 1000 + 20 | 0 | 1000 + 21 | 1000 | 1000 + 22 | 2000 | 1000 +(9 rows) + +-- Partial aggregation at all levels as GROUP BY clause does not match with +-- PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b ORDER BY 1, 2, 3; + QUERY PLAN +--------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_ml_1.b, (sum(pagg_tab_ml_1.a)), (count(*)) + -> Finalize GroupAggregate + Group Key: pagg_tab_ml_1.b + -> Sort + Sort Key: pagg_tab_ml_1.b + -> Append + -> Partial HashAggregate + Group Key: pagg_tab_ml_1.b + -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml_1 + -> Partial HashAggregate + Group Key: pagg_tab_ml_3.b + -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_3 + -> Partial HashAggregate + Group Key: pagg_tab_ml_4.b + -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_4 + -> Partial HashAggregate + Group Key: pagg_tab_ml_6.b + -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_6 + -> Partial HashAggregate + Group Key: pagg_tab_ml_7.b + -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_7 +(22 rows) + +SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b HAVING avg(a) < 15 ORDER BY 1, 2, 3; + b | sum | count +---+-------+------- + 0 | 30000 | 3000 + 1 | 33000 | 3000 + 2 | 36000 | 3000 + 3 | 39000 | 3000 + 4 | 42000 | 3000 +(5 rows) + +-- Full aggregation at all levels as GROUP BY clause matches with PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; + QUERY PLAN +---------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_ml_1.a, (sum(pagg_tab_ml_1.b)), (count(*)) + -> Append + -> HashAggregate + Group Key: pagg_tab_ml_1.a, pagg_tab_ml_1.b, pagg_tab_ml_1.c + Filter: (avg(pagg_tab_ml_1.b) > '7'::numeric) + -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml_1 + -> HashAggregate + Group Key: pagg_tab_ml_3.a, pagg_tab_ml_3.b, pagg_tab_ml_3.c + Filter: (avg(pagg_tab_ml_3.b) > '7'::numeric) + -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_3 + -> HashAggregate + Group Key: pagg_tab_ml_4.a, pagg_tab_ml_4.b, pagg_tab_ml_4.c + Filter: (avg(pagg_tab_ml_4.b) > '7'::numeric) + -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_4 + -> HashAggregate + Group Key: pagg_tab_ml_6.a, pagg_tab_ml_6.b, pagg_tab_ml_6.c + Filter: (avg(pagg_tab_ml_6.b) > '7'::numeric) + -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_6 + -> HashAggregate + Group Key: pagg_tab_ml_7.a, pagg_tab_ml_7.b, pagg_tab_ml_7.c + Filter: (avg(pagg_tab_ml_7.b) > '7'::numeric) + -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_7 +(23 rows) + +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; + a | sum | count +----+------+------- + 8 | 4000 | 500 + 8 | 4000 | 500 + 9 | 4500 | 500 + 9 | 4500 | 500 + 18 | 4000 | 500 + 18 | 4000 | 500 + 19 | 4500 | 500 + 19 | 4500 | 500 + 28 | 4000 | 500 + 28 | 4000 | 500 + 29 | 4500 | 500 + 29 | 4500 | 500 +(12 rows) + +-- Parallelism within partitionwise aggregates +SET min_parallel_table_scan_size TO '8kB'; +SET parallel_setup_cost TO 0; +-- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY +-- for level 1 only. For subpartitions, GROUP BY clause does not match with +-- PARTITION KEY, thus we will have a partial aggregation for them. +EXPLAIN (COSTS OFF) +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Sort + Sort Key: pagg_tab_ml_1.a, (sum(pagg_tab_ml_1.b)), (count(*)) + -> Append + -> Finalize GroupAggregate + Group Key: pagg_tab_ml_1.a + Filter: (avg(pagg_tab_ml_1.b) < '3'::numeric) + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: pagg_tab_ml_1.a + -> Partial HashAggregate + Group Key: pagg_tab_ml_1.a + -> Parallel Seq Scan on pagg_tab_ml_p1 pagg_tab_ml_1 + -> Finalize GroupAggregate + Group Key: pagg_tab_ml_3.a + Filter: (avg(pagg_tab_ml_3.b) < '3'::numeric) + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: pagg_tab_ml_3.a + -> Parallel Append + -> Partial HashAggregate + Group Key: pagg_tab_ml_3.a + -> Parallel Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_3 + -> Partial HashAggregate + Group Key: pagg_tab_ml_4.a + -> Parallel Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_4 + -> Finalize GroupAggregate + Group Key: pagg_tab_ml_6.a + Filter: (avg(pagg_tab_ml_6.b) < '3'::numeric) + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: pagg_tab_ml_6.a + -> Parallel Append + -> Partial HashAggregate + Group Key: pagg_tab_ml_6.a + -> Parallel Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_6 + -> Partial HashAggregate + Group Key: pagg_tab_ml_7.a + -> Parallel Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_7 +(41 rows) + +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; + a | sum | count +----+------+------- + 0 | 0 | 1000 + 1 | 1000 | 1000 + 2 | 2000 | 1000 + 10 | 0 | 1000 + 11 | 1000 | 1000 + 12 | 2000 | 1000 + 20 | 0 | 1000 + 21 | 1000 | 1000 + 22 | 2000 | 1000 +(9 rows) + +-- Partial aggregation at all levels as GROUP BY clause does not match with +-- PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b ORDER BY 1, 2, 3; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: pagg_tab_ml_1.b, (sum(pagg_tab_ml_1.a)), (count(*)) + -> Finalize GroupAggregate + Group Key: pagg_tab_ml_1.b + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: pagg_tab_ml_1.b + -> Parallel Append + -> Partial HashAggregate + Group Key: pagg_tab_ml_1.b + -> Parallel Seq Scan on pagg_tab_ml_p1 pagg_tab_ml_1 + -> Partial HashAggregate + Group Key: pagg_tab_ml_6.b + -> Parallel Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_6 + -> Partial HashAggregate + Group Key: pagg_tab_ml_3.b + -> Parallel Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_3 + -> Partial HashAggregate + Group Key: pagg_tab_ml_7.b + -> Parallel Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_7 + -> Partial HashAggregate + Group Key: pagg_tab_ml_4.b + -> Parallel Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_4 +(24 rows) + +SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b HAVING avg(a) < 15 ORDER BY 1, 2, 3; + b | sum | count +---+-------+------- + 0 | 30000 | 3000 + 1 | 33000 | 3000 + 2 | 36000 | 3000 + 3 | 39000 | 3000 + 4 | 42000 | 3000 +(5 rows) + +-- Full aggregation at all levels as GROUP BY clause matches with PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; + QUERY PLAN +---------------------------------------------------------------------------------- + Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: pagg_tab_ml_1.a, (sum(pagg_tab_ml_1.b)), (count(*)) + -> Parallel Append + -> HashAggregate + Group Key: pagg_tab_ml_1.a, pagg_tab_ml_1.b, pagg_tab_ml_1.c + Filter: (avg(pagg_tab_ml_1.b) > '7'::numeric) + -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml_1 + -> HashAggregate + Group Key: pagg_tab_ml_6.a, pagg_tab_ml_6.b, pagg_tab_ml_6.c + Filter: (avg(pagg_tab_ml_6.b) > '7'::numeric) + -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_6 + -> HashAggregate + Group Key: pagg_tab_ml_3.a, pagg_tab_ml_3.b, pagg_tab_ml_3.c + Filter: (avg(pagg_tab_ml_3.b) > '7'::numeric) + -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_3 + -> HashAggregate + Group Key: pagg_tab_ml_7.a, pagg_tab_ml_7.b, pagg_tab_ml_7.c + Filter: (avg(pagg_tab_ml_7.b) > '7'::numeric) + -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_7 + -> HashAggregate + Group Key: pagg_tab_ml_4.a, pagg_tab_ml_4.b, pagg_tab_ml_4.c + Filter: (avg(pagg_tab_ml_4.b) > '7'::numeric) + -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_4 +(25 rows) + +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; + a | sum | count +----+------+------- + 8 | 4000 | 500 + 8 | 4000 | 500 + 9 | 4500 | 500 + 9 | 4500 | 500 + 18 | 4000 | 500 + 18 | 4000 | 500 + 19 | 4500 | 500 + 19 | 4500 | 500 + 28 | 4000 | 500 + 28 | 4000 | 500 + 29 | 4500 | 500 + 29 | 4500 | 500 +(12 rows) + +-- Parallelism within partitionwise aggregates (single level) +-- Add few parallel setup cost, so that we will see a plan which gathers +-- partially created paths even for full aggregation and sticks a single Gather +-- followed by finalization step. +-- Without this, the cost of doing partial aggregation + Gather + finalization +-- for each partition and then Append over it turns out to be same and this +-- wins as we add it first. This parallel_setup_cost plays a vital role in +-- costing such plans. +SET parallel_setup_cost TO 10; +CREATE TABLE pagg_tab_para(x int, y int) PARTITION BY RANGE(x); +CREATE TABLE pagg_tab_para_p1 PARTITION OF pagg_tab_para FOR VALUES FROM (0) TO (12); +CREATE TABLE pagg_tab_para_p2 PARTITION OF pagg_tab_para FOR VALUES FROM (12) TO (22); +CREATE TABLE pagg_tab_para_p3 PARTITION OF pagg_tab_para FOR VALUES FROM (22) TO (30); +INSERT INTO pagg_tab_para SELECT i % 30, i % 20 FROM generate_series(0, 29999) i; +ANALYZE pagg_tab_para; +-- When GROUP BY clause matches; full aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; + QUERY PLAN +------------------------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_para_1.x, (sum(pagg_tab_para_1.y)), (avg(pagg_tab_para_1.y)) + -> Finalize GroupAggregate + Group Key: pagg_tab_para_1.x + Filter: (avg(pagg_tab_para_1.y) < '7'::numeric) + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: pagg_tab_para_1.x + -> Parallel Append + -> Partial HashAggregate + Group Key: pagg_tab_para_1.x + -> Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para_1 + -> Partial HashAggregate + Group Key: pagg_tab_para_2.x + -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_2 + -> Partial HashAggregate + Group Key: pagg_tab_para_3.x + -> Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_3 +(19 rows) + +SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; + x | sum | avg | count +----+------+--------------------+------- + 0 | 5000 | 5.0000000000000000 | 1000 + 1 | 6000 | 6.0000000000000000 | 1000 + 10 | 5000 | 5.0000000000000000 | 1000 + 11 | 6000 | 6.0000000000000000 | 1000 + 20 | 5000 | 5.0000000000000000 | 1000 + 21 | 6000 | 6.0000000000000000 | 1000 +(6 rows) + +-- When GROUP BY clause does not match; partial aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3; + QUERY PLAN +------------------------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_para_1.y, (sum(pagg_tab_para_1.x)), (avg(pagg_tab_para_1.x)) + -> Finalize GroupAggregate + Group Key: pagg_tab_para_1.y + Filter: (avg(pagg_tab_para_1.x) < '12'::numeric) + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: pagg_tab_para_1.y + -> Parallel Append + -> Partial HashAggregate + Group Key: pagg_tab_para_1.y + -> Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para_1 + -> Partial HashAggregate + Group Key: pagg_tab_para_2.y + -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_2 + -> Partial HashAggregate + Group Key: pagg_tab_para_3.y + -> Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_3 +(19 rows) + +SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3; + y | sum | avg | count +----+-------+---------------------+------- + 0 | 15000 | 10.0000000000000000 | 1500 + 1 | 16500 | 11.0000000000000000 | 1500 + 10 | 15000 | 10.0000000000000000 | 1500 + 11 | 16500 | 11.0000000000000000 | 1500 +(4 rows) + +-- Test when parent can produce parallel paths but not any (or some) of its children +-- (Use one more aggregate to tilt the cost estimates for the plan we want) +ALTER TABLE pagg_tab_para_p1 SET (parallel_workers = 0); +NOTICE: storage parameters are currently ignored in YugabyteDB +ALTER TABLE pagg_tab_para_p3 SET (parallel_workers = 0); +NOTICE: storage parameters are currently ignored in YugabyteDB +ANALYZE pagg_tab_para; +EXPLAIN (COSTS OFF) +SELECT x, sum(y), avg(y), sum(x+y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; + QUERY PLAN +------------------------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_para.x, (sum(pagg_tab_para.y)), (avg(pagg_tab_para.y)) + -> Finalize GroupAggregate + Group Key: pagg_tab_para.x + Filter: (avg(pagg_tab_para.y) < '7'::numeric) + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: pagg_tab_para.x + -> Partial HashAggregate + Group Key: pagg_tab_para.x + -> Parallel Append + -> Seq Scan on pagg_tab_para_p1 pagg_tab_para_1 + -> Seq Scan on pagg_tab_para_p3 pagg_tab_para_3 + -> Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_2 +(15 rows) + +SELECT x, sum(y), avg(y), sum(x+y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; + x | sum | avg | sum | count +----+------+--------------------+-------+------- + 0 | 5000 | 5.0000000000000000 | 5000 | 1000 + 1 | 6000 | 6.0000000000000000 | 7000 | 1000 + 10 | 5000 | 5.0000000000000000 | 15000 | 1000 + 11 | 6000 | 6.0000000000000000 | 17000 | 1000 + 20 | 5000 | 5.0000000000000000 | 25000 | 1000 + 21 | 6000 | 6.0000000000000000 | 27000 | 1000 +(6 rows) + +ALTER TABLE pagg_tab_para_p2 SET (parallel_workers = 0); +NOTICE: storage parameters are currently ignored in YugabyteDB +ANALYZE pagg_tab_para; +-- YB: yet another aggregate is required to "tilt the cost estimates" to get +-- the desired plan. Only one more aggregate is not enough because of higher +-- scan costs relative to vanilla PG's execution nodes. +EXPLAIN (COSTS OFF) +SELECT x, sum(y), avg(y), sum(x+y), count(*), avg(x+y) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; -- YB use one more agg + QUERY PLAN +---------------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_para.x, (sum(pagg_tab_para.y)), (avg(pagg_tab_para.y)) + -> Finalize GroupAggregate + Group Key: pagg_tab_para.x + Filter: (avg(pagg_tab_para.y) < '7'::numeric) + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: pagg_tab_para.x + -> Partial HashAggregate + Group Key: pagg_tab_para.x + -> Parallel Append + -> Seq Scan on pagg_tab_para_p1 pagg_tab_para_1 + -> Seq Scan on pagg_tab_para_p2 pagg_tab_para_2 + -> Seq Scan on pagg_tab_para_p3 pagg_tab_para_3 +(15 rows) + +SELECT x, sum(y), avg(y), sum(x+y), count(*), avg(x+y) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; -- YB use one more agg + x | sum | avg | sum | count | avg +----+------+--------------------+-------+-------+--------------------- + 0 | 5000 | 5.0000000000000000 | 5000 | 1000 | 5.0000000000000000 + 1 | 6000 | 6.0000000000000000 | 7000 | 1000 | 7.0000000000000000 + 10 | 5000 | 5.0000000000000000 | 15000 | 1000 | 15.0000000000000000 + 11 | 6000 | 6.0000000000000000 | 17000 | 1000 | 17.0000000000000000 + 20 | 5000 | 5.0000000000000000 | 25000 | 1000 | 25.0000000000000000 + 21 | 6000 | 6.0000000000000000 | 27000 | 1000 | 27.0000000000000000 +(6 rows) + +-- Reset parallelism parameters to get partitionwise aggregation plan. +RESET min_parallel_table_scan_size; +RESET parallel_setup_cost; +EXPLAIN (COSTS OFF) +SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; + QUERY PLAN +----------------------------------------------------------------------------------- + Sort + Sort Key: pagg_tab_para_1.x, (sum(pagg_tab_para_1.y)), (avg(pagg_tab_para_1.y)) + -> Append + -> HashAggregate + Group Key: pagg_tab_para_1.x + Filter: (avg(pagg_tab_para_1.y) < '7'::numeric) + -> Seq Scan on pagg_tab_para_p1 pagg_tab_para_1 + -> HashAggregate + Group Key: pagg_tab_para_2.x + Filter: (avg(pagg_tab_para_2.y) < '7'::numeric) + -> Seq Scan on pagg_tab_para_p2 pagg_tab_para_2 + -> HashAggregate + Group Key: pagg_tab_para_3.x + Filter: (avg(pagg_tab_para_3.y) < '7'::numeric) + -> Seq Scan on pagg_tab_para_p3 pagg_tab_para_3 +(15 rows) + +SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; + x | sum | avg | count +----+------+--------------------+------- + 0 | 5000 | 5.0000000000000000 | 1000 + 1 | 6000 | 6.0000000000000000 | 1000 + 10 | 5000 | 5.0000000000000000 | 1000 + 11 | 6000 | 6.0000000000000000 | 1000 + 20 | 5000 | 5.0000000000000000 | 1000 + 21 | 6000 | 6.0000000000000000 | 1000 +(6 rows) + diff --git a/src/test/regress/expected/yb.port.partition_join.out b/src/test/regress/expected/yb.port.partition_join.out new file mode 100644 index 0000000000000..769d3f7a3d394 --- /dev/null +++ b/src/test/regress/expected/yb.port.partition_join.out @@ -0,0 +1,2939 @@ +-- +-- PARTITION_JOIN +-- Test partitionwise join between partitioned tables +-- +-- Enable partitionwise join, which by default is disabled. +SET enable_partitionwise_join to true; +-- YB: explicitly set enable_partitionwise_aggregate to original pg default. +SET enable_partitionwise_aggregate to false; +-- +-- partitioned by a single column +-- +CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600); +CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES FROM (250) TO (500); +INSERT INTO prt1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 2 = 0; +CREATE INDEX iprt1_p1_a on prt1_p1(a); +CREATE INDEX iprt1_p2_a on prt1_p2(a); +CREATE INDEX iprt1_p3_a on prt1_p3(a); +ANALYZE prt1; +CREATE TABLE prt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES FROM (250) TO (500); +CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 3 = 0; +CREATE INDEX iprt2_p1_b on prt2_p1(b); +CREATE INDEX iprt2_p2_b on prt2_p2(b); +CREATE INDEX iprt2_p3_b on prt2_p3(b); +ANALYZE prt2; +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 150 | 0150 | 150 | 0150 + 300 | 0300 | 300 | 0300 + 450 | 0450 | 450 | 0450 +(4 rows) + +-- left outer join, with whole-row reference; partitionwise join does not apply +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; + t1 | t2 +--------------+-------------- + (0,0,0000) | (0,0,0000) + (50,0,0050) | + (100,0,0100) | + (150,0,0150) | (0,150,0150) + (200,0,0200) | + (250,0,0250) | + (300,0,0300) | (0,300,0300) + (350,0,0350) | + (400,0,0400) | + (450,0,0450) | (0,450,0450) + (500,0,0500) | + (550,0,0550) | +(12 rows) + +-- right outer join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 150 | 0150 | 150 | 0150 + 300 | 0300 | 300 | 0300 + 450 | 0450 | 450 | 0450 + | | 75 | 0075 + | | 225 | 0225 + | | 375 | 0375 + | | 525 | 0525 +(8 rows) + +-- full outer join, with placeholder vars +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; + QUERY PLAN +---------------------------------------------------------------- + Sort + Sort Key: prt1.a, prt2.b + -> Append + -> Hash Full Join + Hash Cond: (prt1_1.a = prt2_1.b) + Filter: (((50) = prt1_1.a) OR ((75) = prt2_1.b)) + -> Seq Scan on prt1_p1 prt1_1 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p1 prt2_1 + Storage Filter: (a = 0) + -> Hash Full Join + Hash Cond: (prt1_2.a = prt2_2.b) + Filter: (((50) = prt1_2.a) OR ((75) = prt2_2.b)) + -> Seq Scan on prt1_p2 prt1_2 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p2 prt2_2 + Storage Filter: (a = 0) + -> Hash Full Join + Hash Cond: (prt1_3.a = prt2_3.b) + Filter: (((50) = prt1_3.a) OR ((75) = prt2_3.b)) + -> Seq Scan on prt1_p3 prt1_3 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p3 prt2_3 + Storage Filter: (a = 0) +(27 rows) + +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; + a | c | b | c +----+------+----+------ + 50 | 0050 | | + | | 75 | 0075 +(2 rows) + +-- Join with pruned partitions from joining relations +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 300 | 0300 | 300 | 0300 +(1 row) + +-- Currently we can't do partitioned join if nullable-side partitions are pruned +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 0 | 0000 | | + 50 | 0050 | | + 100 | 0100 | | + 150 | 0150 | | + 200 | 0200 | | + 250 | 0250 | | + 300 | 0300 | 300 | 0300 + 350 | 0350 | | + 400 | 0400 | | +(9 rows) + +-- Currently we can't do partitioned join if nullable-side partitions are pruned +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 OR t2.a = 0 ORDER BY t1.a, t2.b; + QUERY PLAN +----------------------------------------------------- + Sort + Sort Key: prt1.a, prt2.b + -> Hash Full Join + Hash Cond: (prt1.a = prt2.b) + Filter: ((prt1.b = 0) OR (prt2.a = 0)) + -> Append + -> Seq Scan on prt1_p1 prt1_1 + Storage Filter: (a < 450) + -> Seq Scan on prt1_p2 prt1_2 + Storage Filter: (a < 450) + -> Hash + -> Append + -> Seq Scan on prt2_p2 prt2_1 + Storage Filter: (b > 250) + -> Seq Scan on prt2_p3 prt2_2 + Storage Filter: (b > 250) +(16 rows) + +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 OR t2.a = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 0 | 0000 | | + 50 | 0050 | | + 100 | 0100 | | + 150 | 0150 | | + 200 | 0200 | | + 250 | 0250 | | + 300 | 0300 | 300 | 0300 + 350 | 0350 | | + 400 | 0400 | | + | | 375 | 0375 + | | 450 | 0450 + | | 525 | 0525 +(12 rows) + +-- Semi-join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a; + a | b | c +-----+---+------ + 0 | 0 | 0000 + 150 | 0 | 0150 + 300 | 0 | 0300 + 450 | 0 | 0450 +(4 rows) + +-- Anti-join with aggregates +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT sum(t1.a), avg(t1.a), sum(t1.b), avg(t1.b) FROM prt1 t1 WHERE NOT EXISTS (SELECT 1 FROM prt2 t2 WHERE t1.a = t2.b); +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT sum(t1.a), avg(t1.a), sum(t1.b), avg(t1.b) FROM prt1 t1 WHERE NOT EXISTS (SELECT 1 FROM prt2 t2 WHERE t1.a = t2.b); + sum | avg | sum | avg +-------+----------------------+------+--------------------- + 60000 | 300.0000000000000000 | 2400 | 12.0000000000000000 +(1 row) + +-- lateral reference +EXPLAIN (COSTS OFF) +SELECT * FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss + ON t1.a = ss.t2a WHERE t1.b = 0 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Nested Loop Left Join + -> Seq Scan on prt1_p1 t1_1 + Storage Filter: (b = 0) + -> Nested Loop + -> Index Only Scan using iprt1_p1_a on prt1_p1 t2_1 + Index Cond: (a = t1_1.a) + -> Index Scan using iprt2_p1_b on prt2_p1 t3_1 + Index Cond: (b = t2_1.a) + -> Nested Loop Left Join + -> Seq Scan on prt1_p2 t1_2 + Storage Filter: (b = 0) + -> Nested Loop + -> Index Only Scan using iprt1_p2_a on prt1_p2 t2_2 + Index Cond: (a = t1_2.a) + -> Index Scan using iprt2_p2_b on prt2_p2 t3_2 + Index Cond: (b = t2_2.a) + -> Nested Loop Left Join + -> Seq Scan on prt1_p3 t1_3 + Storage Filter: (b = 0) + -> Nested Loop + -> Index Only Scan using iprt1_p3_a on prt1_p3 t2_3 + Index Cond: (a = t1_3.a) + -> Index Scan using iprt2_p3_b on prt2_p3 t3_3 + Index Cond: (b = t2_3.a) +(27 rows) + +SELECT * FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss + ON t1.a = ss.t2a WHERE t1.b = 0 ORDER BY t1.a; + a | b | c | t2a | t3a | least +-----+---+------+-----+-----+------- + 0 | 0 | 0000 | 0 | 0 | 0 + 50 | 0 | 0050 | | | + 100 | 0 | 0100 | | | + 150 | 0 | 0150 | 150 | 0 | 150 + 200 | 0 | 0200 | | | + 250 | 0 | 0250 | | | + 300 | 0 | 0300 | 300 | 0 | 300 + 350 | 0 | 0350 | | | + 400 | 0 | 0400 | | | + 450 | 0 | 0450 | 450 | 0 | 450 + 500 | 0 | 0500 | | | + 550 | 0 | 0550 | | | +(12 rows) + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss + ON t1.c = ss.t2c WHERE (t1.b + coalesce(ss.t2b, 0)) = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss + ON t1.c = ss.t2c WHERE (t1.b + coalesce(ss.t2b, 0)) = 0 ORDER BY t1.a; + a | t2a | t2c +-----+-----+------ + 0 | 0 | 0000 + 50 | | + 100 | | + 150 | 150 | 0150 + 200 | | + 250 | | + 300 | 300 | 0300 + 350 | | + 400 | | + 450 | 450 | 0450 + 500 | | + 550 | | +(12 rows) + +-- bug with inadequate sort key representation +SET enable_partitionwise_aggregate TO true; +SET enable_hashjoin TO false; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) + WHERE a BETWEEN 490 AND 510 + GROUP BY 1, 2 ORDER BY 1, 2; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) + WHERE a BETWEEN 490 AND 510 + GROUP BY 1, 2 ORDER BY 1, 2; + a | b +-----+---- + 490 | 15 + 492 | 17 + 494 | 19 + 495 | 20 + 496 | 21 + 498 | 23 + 500 | 0 + 501 | 1 + 502 | 2 + 504 | 4 + 506 | 6 + 507 | 7 + 508 | 8 + 510 | 10 +(14 rows) + +RESET enable_partitionwise_aggregate; +-- YB: explicitly set enable_partitionwise_aggregate to original pg default. +SET enable_partitionwise_aggregate to false; +RESET enable_hashjoin; +-- +-- partitioned by expression +-- +CREATE TABLE prt1_e (a int, b int, c int) PARTITION BY RANGE(((a + b)/2)); +CREATE TABLE prt1_e_p1 PARTITION OF prt1_e FOR VALUES FROM (0) TO (250); +CREATE TABLE prt1_e_p2 PARTITION OF prt1_e FOR VALUES FROM (250) TO (500); +CREATE TABLE prt1_e_p3 PARTITION OF prt1_e FOR VALUES FROM (500) TO (600); +INSERT INTO prt1_e SELECT i, i, i % 25 FROM generate_series(0, 599, 2) i; +CREATE INDEX iprt1_e_p1_ab2 on prt1_e_p1(((a+b)/2)); +CREATE INDEX iprt1_e_p2_ab2 on prt1_e_p2(((a+b)/2)); +CREATE INDEX iprt1_e_p3_ab2 on prt1_e_p3(((a+b)/2)); +ANALYZE prt1_e; +CREATE TABLE prt2_e (a int, b int, c int) PARTITION BY RANGE(((b + a)/2)); +CREATE TABLE prt2_e_p1 PARTITION OF prt2_e FOR VALUES FROM (0) TO (250); +CREATE TABLE prt2_e_p2 PARTITION OF prt2_e FOR VALUES FROM (250) TO (500); +CREATE TABLE prt2_e_p3 PARTITION OF prt2_e FOR VALUES FROM (500) TO (600); +INSERT INTO prt2_e SELECT i, i, i % 25 FROM generate_series(0, 599, 3) i; +ANALYZE prt2_e; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.c = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.c = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+---+-----+--- + 0 | 0 | 0 | 0 + 150 | 0 | 150 | 0 + 300 | 0 | 300 | 0 + 450 | 0 | 450 | 0 +(4 rows) + +-- +-- N-way join +-- +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c | ?column? | c +-----+------+-----+------+----------+--- + 0 | 0000 | 0 | 0000 | 0 | 0 + 150 | 0150 | 150 | 0150 | 300 | 0 + 300 | 0300 | 300 | 0300 | 600 | 0 + 450 | 0450 | 450 | 0450 | 900 | 0 +(4 rows) + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; + a | c | b | c | ?column? | c +-----+------+-----+------+----------+--- + 0 | 0000 | 0 | 0000 | 0 | 0 + 50 | 0050 | | | 100 | 0 + 100 | 0100 | | | 200 | 0 + 150 | 0150 | 150 | 0150 | 300 | 0 + 200 | 0200 | | | 400 | 0 + 250 | 0250 | | | 500 | 0 + 300 | 0300 | 300 | 0300 | 600 | 0 + 350 | 0350 | | | 700 | 0 + 400 | 0400 | | | 800 | 0 + 450 | 0450 | 450 | 0450 | 900 | 0 + 500 | 0500 | | | 1000 | 0 + 550 | 0550 | | | 1100 | 0 +(12 rows) + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; + a | c | b | c | ?column? | c +-----+------+-----+------+----------+--- + 0 | 0000 | 0 | 0000 | 0 | 0 + 50 | 0050 | | | 100 | 0 + 100 | 0100 | | | 200 | 0 + 150 | 0150 | 150 | 0150 | 300 | 0 + 200 | 0200 | | | 400 | 0 + 250 | 0250 | | | 500 | 0 + 300 | 0300 | 300 | 0300 | 600 | 0 + 350 | 0350 | | | 700 | 0 + 400 | 0400 | | | 800 | 0 + 450 | 0450 | 450 | 0450 | 900 | 0 + 500 | 0500 | | | 1000 | 0 + 550 | 0550 | | | 1100 | 0 +(12 rows) + +-- +-- 3-way full join +-- +EXPLAIN (COSTS OFF) +SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) + WHERE a BETWEEN 490 AND 510; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + -> Append + -> Hash Full Join + Hash Cond: ((COALESCE(prt1_1.a, p2_1.a) = p3_1.a) AND (COALESCE(prt1_1.b, p2_1.b) = p3_1.b)) + Filter: ((COALESCE(COALESCE(prt1_1.a, p2_1.a), p3_1.a) >= 490) AND (COALESCE(COALESCE(prt1_1.a, p2_1.a), p3_1.a) <= 510)) + -> Hash Full Join + Hash Cond: ((prt1_1.a = p2_1.a) AND (prt1_1.b = p2_1.b)) + -> Seq Scan on prt1_p1 prt1_1 + -> Hash + -> Seq Scan on prt2_p1 p2_1 + -> Hash + -> Seq Scan on prt2_p1 p3_1 + -> Hash Full Join + Hash Cond: ((COALESCE(prt1_2.a, p2_2.a) = p3_2.a) AND (COALESCE(prt1_2.b, p2_2.b) = p3_2.b)) + Filter: ((COALESCE(COALESCE(prt1_2.a, p2_2.a), p3_2.a) >= 490) AND (COALESCE(COALESCE(prt1_2.a, p2_2.a), p3_2.a) <= 510)) + -> Hash Full Join + Hash Cond: ((prt1_2.a = p2_2.a) AND (prt1_2.b = p2_2.b)) + -> Seq Scan on prt1_p2 prt1_2 + -> Hash + -> Seq Scan on prt2_p2 p2_2 + -> Hash + -> Seq Scan on prt2_p2 p3_2 + -> Hash Full Join + Hash Cond: ((COALESCE(prt1_3.a, p2_3.a) = p3_3.a) AND (COALESCE(prt1_3.b, p2_3.b) = p3_3.b)) + Filter: ((COALESCE(COALESCE(prt1_3.a, p2_3.a), p3_3.a) >= 490) AND (COALESCE(COALESCE(prt1_3.a, p2_3.a), p3_3.a) <= 510)) + -> Hash Full Join + Hash Cond: ((prt1_3.a = p2_3.a) AND (prt1_3.b = p2_3.b)) + -> Seq Scan on prt1_p3 prt1_3 + -> Hash + -> Seq Scan on prt2_p3 p2_3 + -> Hash + -> Seq Scan on prt2_p3 p3_3 +(32 rows) + +SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) + WHERE a BETWEEN 490 AND 510; + count +------- + 14 +(1 row) + +-- +-- 4-way full join +-- +EXPLAIN (COSTS OFF) +SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) FULL JOIN prt1 p4 (a,b,c) USING (a, b) + WHERE a BETWEEN 490 AND 510; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + -> Append + -> Hash Full Join + Hash Cond: ((COALESCE(COALESCE(prt1_1.a, p2_1.a), p3_1.a) = p4_1.a) AND (COALESCE(COALESCE(prt1_1.b, p2_1.b), p3_1.b) = p4_1.b)) + Filter: ((COALESCE(COALESCE(COALESCE(prt1_1.a, p2_1.a), p3_1.a), p4_1.a) >= 490) AND (COALESCE(COALESCE(COALESCE(prt1_1.a, p2_1.a), p3_1.a), p4_1.a) <= 510)) + -> Hash Full Join + Hash Cond: ((COALESCE(prt1_1.a, p2_1.a) = p3_1.a) AND (COALESCE(prt1_1.b, p2_1.b) = p3_1.b)) + -> Hash Full Join + Hash Cond: ((prt1_1.a = p2_1.a) AND (prt1_1.b = p2_1.b)) + -> Seq Scan on prt1_p1 prt1_1 + -> Hash + -> Seq Scan on prt2_p1 p2_1 + -> Hash + -> Seq Scan on prt2_p1 p3_1 + -> Hash + -> Seq Scan on prt1_p1 p4_1 + -> Hash Full Join + Hash Cond: ((COALESCE(COALESCE(prt1_2.a, p2_2.a), p3_2.a) = p4_2.a) AND (COALESCE(COALESCE(prt1_2.b, p2_2.b), p3_2.b) = p4_2.b)) + Filter: ((COALESCE(COALESCE(COALESCE(prt1_2.a, p2_2.a), p3_2.a), p4_2.a) >= 490) AND (COALESCE(COALESCE(COALESCE(prt1_2.a, p2_2.a), p3_2.a), p4_2.a) <= 510)) + -> Hash Full Join + Hash Cond: ((COALESCE(prt1_2.a, p2_2.a) = p3_2.a) AND (COALESCE(prt1_2.b, p2_2.b) = p3_2.b)) + -> Hash Full Join + Hash Cond: ((prt1_2.a = p2_2.a) AND (prt1_2.b = p2_2.b)) + -> Seq Scan on prt1_p2 prt1_2 + -> Hash + -> Seq Scan on prt2_p2 p2_2 + -> Hash + -> Seq Scan on prt2_p2 p3_2 + -> Hash + -> Seq Scan on prt1_p2 p4_2 + -> Hash Full Join + Hash Cond: ((COALESCE(COALESCE(prt1_3.a, p2_3.a), p3_3.a) = p4_3.a) AND (COALESCE(COALESCE(prt1_3.b, p2_3.b), p3_3.b) = p4_3.b)) + Filter: ((COALESCE(COALESCE(COALESCE(prt1_3.a, p2_3.a), p3_3.a), p4_3.a) >= 490) AND (COALESCE(COALESCE(COALESCE(prt1_3.a, p2_3.a), p3_3.a), p4_3.a) <= 510)) + -> Hash Full Join + Hash Cond: ((COALESCE(prt1_3.a, p2_3.a) = p3_3.a) AND (COALESCE(prt1_3.b, p2_3.b) = p3_3.b)) + -> Hash Full Join + Hash Cond: ((prt1_3.a = p2_3.a) AND (prt1_3.b = p2_3.b)) + -> Seq Scan on prt1_p3 prt1_3 + -> Hash + -> Seq Scan on prt2_p3 p2_3 + -> Hash + -> Seq Scan on prt2_p3 p3_3 + -> Hash + -> Seq Scan on prt1_p3 p4_3 +(44 rows) + +SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) FULL JOIN prt1 p4 (a,b,c) USING (a, b) + WHERE a BETWEEN 490 AND 510; + count +------- + 14 +(1 row) + +-- Cases with non-nullable expressions in subquery results; +-- make sure these go to null as expected +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM prt1_e WHERE prt1_e.c = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b; + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Sort + Sort Key: prt1.a, prt2.b, ((prt1_e.a + prt1_e.b)) + -> Append + -> Hash Full Join + Hash Cond: (prt1_1.a = ((prt1_e_1.a + prt1_e_1.b) / 2)) + Filter: ((prt1_1.a = (50)) OR (prt2_1.b = (75)) OR (((prt1_e_1.a + prt1_e_1.b) / 2) = (50))) + -> Hash Full Join + Hash Cond: (prt1_1.a = prt2_1.b) + -> Seq Scan on prt1_p1 prt1_1 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p1 prt2_1 + Storage Filter: (a = 0) + -> Hash + -> Seq Scan on prt1_e_p1 prt1_e_1 + Storage Filter: (c = 0) + -> Hash Full Join + Hash Cond: (prt1_2.a = ((prt1_e_2.a + prt1_e_2.b) / 2)) + Filter: ((prt1_2.a = (50)) OR (prt2_2.b = (75)) OR (((prt1_e_2.a + prt1_e_2.b) / 2) = (50))) + -> Hash Full Join + Hash Cond: (prt1_2.a = prt2_2.b) + -> Seq Scan on prt1_p2 prt1_2 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p2 prt2_2 + Storage Filter: (a = 0) + -> Hash + -> Seq Scan on prt1_e_p2 prt1_e_2 + Storage Filter: (c = 0) + -> Hash Full Join + Hash Cond: (prt1_3.a = ((prt1_e_3.a + prt1_e_3.b) / 2)) + Filter: ((prt1_3.a = (50)) OR (prt2_3.b = (75)) OR (((prt1_e_3.a + prt1_e_3.b) / 2) = (50))) + -> Hash Full Join + Hash Cond: (prt1_3.a = prt2_3.b) + -> Seq Scan on prt1_p3 prt1_3 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p3 prt2_3 + Storage Filter: (a = 0) + -> Hash + -> Seq Scan on prt1_e_p3 prt1_e_3 + Storage Filter: (c = 0) +(42 rows) + +SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM prt1_e WHERE prt1_e.c = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b; + a | phv | b | phv | ?column? | phv +----+-----+----+-----+----------+----- + 50 | 50 | | | 100 | 50 + | | 75 | 75 | | +(2 rows) + +-- Semi-join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.a = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.b = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.a = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.b = 0 ORDER BY t1.a; + a | b | c +-----+---+------ + 0 | 0 | 0000 + 150 | 0 | 0150 + 300 | 0 | 0300 + 450 | 0 | 0450 +(4 rows) + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; + a | b | c +-----+---+------ + 0 | 0 | 0000 + 150 | 0 | 0150 + 300 | 0 | 0300 + 450 | 0 | 0450 +(4 rows) + +-- test merge joins +SET enable_hashjoin TO off; +SET enable_nestloop TO off; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; + a | b | c +-----+---+------ + 0 | 0 | 0000 + 150 | 0 | 0150 + 300 | 0 | 0300 + 450 | 0 | 0450 +(4 rows) + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; + a | c | b | c | ?column? | c +-----+------+-----+------+----------+--- + 0 | 0000 | 0 | 0000 | 0 | 0 + 50 | 0050 | | | 100 | 0 + 100 | 0100 | | | 200 | 0 + 150 | 0150 | 150 | 0150 | 300 | 0 + 200 | 0200 | | | 400 | 0 + 250 | 0250 | | | 500 | 0 + 300 | 0300 | 300 | 0300 | 600 | 0 + 350 | 0350 | | | 700 | 0 + 400 | 0400 | | | 800 | 0 + 450 | 0450 | 450 | 0450 | 900 | 0 + 500 | 0500 | | | 1000 | 0 + 550 | 0550 | | | 1100 | 0 +(12 rows) + +-- MergeAppend on nullable column +-- This should generate a partitionwise join, but currently fails to +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; + a | b +-----+----- + 0 | + 50 | + 100 | + 150 | + 200 | + 250 | + 300 | 300 + 350 | + 400 | +(9 rows) + +-- merge join when expression with whole-row reference needs to be sorted; +-- partitionwise join does not apply +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; + a | b +----+---- + 0 | 0 + 6 | 6 + 12 | 12 + 18 | 18 + 24 | 24 +(5 rows) + +RESET enable_hashjoin; +RESET enable_nestloop; +-- +-- partitioned by multiple columns +-- +CREATE TABLE prt1_m (a int, b int, c int) PARTITION BY RANGE(a, ((a + b)/2)); +CREATE TABLE prt1_m_p1 PARTITION OF prt1_m FOR VALUES FROM (0, 0) TO (250, 250); +CREATE TABLE prt1_m_p2 PARTITION OF prt1_m FOR VALUES FROM (250, 250) TO (500, 500); +CREATE TABLE prt1_m_p3 PARTITION OF prt1_m FOR VALUES FROM (500, 500) TO (600, 600); +INSERT INTO prt1_m SELECT i, i, i % 25 FROM generate_series(0, 599, 2) i; +ANALYZE prt1_m; +CREATE TABLE prt2_m (a int, b int, c int) PARTITION BY RANGE(((b + a)/2), b); +CREATE TABLE prt2_m_p1 PARTITION OF prt2_m FOR VALUES FROM (0, 0) TO (250, 250); +CREATE TABLE prt2_m_p2 PARTITION OF prt2_m FOR VALUES FROM (250, 250) TO (500, 500); +CREATE TABLE prt2_m_p3 PARTITION OF prt2_m FOR VALUES FROM (500, 500) TO (600, 600); +INSERT INTO prt2_m SELECT i, i, i % 25 FROM generate_series(0, 599, 3) i; +ANALYZE prt2_m; +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.c = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.c = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------ + Sort + Sort Key: prt1_m.a, prt2_m.b + -> Append + -> Hash Full Join + Hash Cond: ((prt1_m_1.a = ((prt2_m_1.b + prt2_m_1.a) / 2)) AND (((prt1_m_1.a + prt1_m_1.b) / 2) = prt2_m_1.b)) + -> Seq Scan on prt1_m_p1 prt1_m_1 + Storage Filter: (c = 0) + -> Hash + -> Seq Scan on prt2_m_p1 prt2_m_1 + Storage Filter: (c = 0) + -> Hash Full Join + Hash Cond: ((prt1_m_2.a = ((prt2_m_2.b + prt2_m_2.a) / 2)) AND (((prt1_m_2.a + prt1_m_2.b) / 2) = prt2_m_2.b)) + -> Seq Scan on prt1_m_p2 prt1_m_2 + Storage Filter: (c = 0) + -> Hash + -> Seq Scan on prt2_m_p2 prt2_m_2 + Storage Filter: (c = 0) + -> Hash Full Join + Hash Cond: ((prt1_m_3.a = ((prt2_m_3.b + prt2_m_3.a) / 2)) AND (((prt1_m_3.a + prt1_m_3.b) / 2) = prt2_m_3.b)) + -> Seq Scan on prt1_m_p3 prt1_m_3 + Storage Filter: (c = 0) + -> Hash + -> Seq Scan on prt2_m_p3 prt2_m_3 + Storage Filter: (c = 0) +(24 rows) + +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.c = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.c = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b; + a | c | b | c +-----+---+-----+--- + 0 | 0 | 0 | 0 + 50 | 0 | | + 100 | 0 | | + 150 | 0 | 150 | 0 + 200 | 0 | | + 250 | 0 | | + 300 | 0 | 300 | 0 + 350 | 0 | | + 400 | 0 | | + 450 | 0 | 450 | 0 + 500 | 0 | | + 550 | 0 | | + | | 75 | 0 + | | 225 | 0 + | | 375 | 0 + | | 525 | 0 +(16 rows) + +-- +-- tests for list partitioned tables. +-- +CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0003', '0004', '0010'); +CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0001', '0005', '0002', '0009'); +CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN ('0006', '0007', '0008', '0011'); +INSERT INTO plt1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE plt1; +CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0000', '0003', '0004', '0010'); +CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0001', '0005', '0002', '0009'); +CREATE TABLE plt2_p3 PARTITION OF plt2 FOR VALUES IN ('0006', '0007', '0008', '0011'); +INSERT INTO plt2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i; +ANALYZE plt2; +-- +-- list partitioned by expression +-- +CREATE TABLE plt1_e (a int, b int, c text) PARTITION BY LIST(ltrim(c, 'A')); +CREATE TABLE plt1_e_p1 PARTITION OF plt1_e FOR VALUES IN ('0000', '0003', '0004', '0010'); +CREATE TABLE plt1_e_p2 PARTITION OF plt1_e FOR VALUES IN ('0001', '0005', '0002', '0009'); +CREATE TABLE plt1_e_p3 PARTITION OF plt1_e FOR VALUES IN ('0006', '0007', '0008', '0011'); +INSERT INTO plt1_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE plt1_e; +-- test partition matching with N-way join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; + avg | avg | avg | c | c | c +----------------------+----------------------+-----------------------+------+------+------- + 24.0000000000000000 | 24.0000000000000000 | 48.0000000000000000 | 0000 | 0000 | A0000 + 75.0000000000000000 | 75.0000000000000000 | 148.0000000000000000 | 0001 | 0001 | A0001 + 123.0000000000000000 | 123.0000000000000000 | 248.0000000000000000 | 0002 | 0002 | A0002 + 174.0000000000000000 | 174.0000000000000000 | 348.0000000000000000 | 0003 | 0003 | A0003 + 225.0000000000000000 | 225.0000000000000000 | 448.0000000000000000 | 0004 | 0004 | A0004 + 273.0000000000000000 | 273.0000000000000000 | 548.0000000000000000 | 0005 | 0005 | A0005 + 324.0000000000000000 | 324.0000000000000000 | 648.0000000000000000 | 0006 | 0006 | A0006 + 375.0000000000000000 | 375.0000000000000000 | 748.0000000000000000 | 0007 | 0007 | A0007 + 423.0000000000000000 | 423.0000000000000000 | 848.0000000000000000 | 0008 | 0008 | A0008 + 474.0000000000000000 | 474.0000000000000000 | 948.0000000000000000 | 0009 | 0009 | A0009 + 525.0000000000000000 | 525.0000000000000000 | 1048.0000000000000000 | 0010 | 0010 | A0010 + 573.0000000000000000 | 573.0000000000000000 | 1148.0000000000000000 | 0011 | 0011 | A0011 +(12 rows) + +-- joins where one of the relations is proven empty +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a = 1 AND t1.a = 2; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 LEFT JOIN prt2 t2 ON t1.a = t2.b; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b, prt1 t3 WHERE t2.b = t3.a; + +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +-- +-- tests for hash partitioned tables. +-- +CREATE TABLE pht1 (a int, b int, c text) PARTITION BY HASH(c); +CREATE TABLE pht1_p1 PARTITION OF pht1 FOR VALUES WITH (MODULUS 3, REMAINDER 0); +CREATE TABLE pht1_p2 PARTITION OF pht1 FOR VALUES WITH (MODULUS 3, REMAINDER 1); +CREATE TABLE pht1_p3 PARTITION OF pht1 FOR VALUES WITH (MODULUS 3, REMAINDER 2); +INSERT INTO pht1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE pht1; +CREATE TABLE pht2 (a int, b int, c text) PARTITION BY HASH(c); +CREATE TABLE pht2_p1 PARTITION OF pht2 FOR VALUES WITH (MODULUS 3, REMAINDER 0); +CREATE TABLE pht2_p2 PARTITION OF pht2 FOR VALUES WITH (MODULUS 3, REMAINDER 1); +CREATE TABLE pht2_p3 PARTITION OF pht2 FOR VALUES WITH (MODULUS 3, REMAINDER 2); +INSERT INTO pht2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i; +ANALYZE pht2; +-- +-- hash partitioned by expression +-- +CREATE TABLE pht1_e (a int, b int, c text) PARTITION BY HASH(ltrim(c, 'A')); +CREATE TABLE pht1_e_p1 PARTITION OF pht1_e FOR VALUES WITH (MODULUS 3, REMAINDER 0); +CREATE TABLE pht1_e_p2 PARTITION OF pht1_e FOR VALUES WITH (MODULUS 3, REMAINDER 1); +CREATE TABLE pht1_e_p3 PARTITION OF pht1_e FOR VALUES WITH (MODULUS 3, REMAINDER 2); +INSERT INTO pht1_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 299, 2) i; +ANALYZE pht1_e; +-- test partition matching with N-way join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; + avg | avg | avg | c | c | c +----------------------+----------------------+----------------------+------+------+------- + 24.0000000000000000 | 24.0000000000000000 | 48.0000000000000000 | 0000 | 0000 | A0000 + 75.0000000000000000 | 75.0000000000000000 | 148.0000000000000000 | 0001 | 0001 | A0001 + 123.0000000000000000 | 123.0000000000000000 | 248.0000000000000000 | 0002 | 0002 | A0002 + 174.0000000000000000 | 174.0000000000000000 | 348.0000000000000000 | 0003 | 0003 | A0003 + 225.0000000000000000 | 225.0000000000000000 | 448.0000000000000000 | 0004 | 0004 | A0004 + 273.0000000000000000 | 273.0000000000000000 | 548.0000000000000000 | 0005 | 0005 | A0005 +(6 rows) + +-- test default partition behavior for range +ALTER TABLE prt1 DETACH PARTITION prt1_p3; +ALTER TABLE prt1 ATTACH PARTITION prt1_p3 DEFAULT; +ANALYZE prt1; +ALTER TABLE prt2 DETACH PARTITION prt2_p3; +ALTER TABLE prt2 ATTACH PARTITION prt2_p3 DEFAULT; +ANALYZE prt2; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +-- test default partition behavior for list +ALTER TABLE plt1 DETACH PARTITION plt1_p3; +ALTER TABLE plt1 ATTACH PARTITION plt1_p3 DEFAULT; +ANALYZE plt1; +ALTER TABLE plt2 DETACH PARTITION plt2_p3; +ALTER TABLE plt2 ATTACH PARTITION plt2_p3 DEFAULT; +ANALYZE plt2; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT avg(t1.a), avg(t2.b), t1.c, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.a % 25 = 0 GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c; +*/ -- YB: The explain plan changes significantly between PG and YB +-- +-- multiple levels of partitioning +-- +CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES FROM (0) TO (250); +CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES FROM (250) TO (500) PARTITION BY LIST (c); +CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES IN ('0000', '0001'); +CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES IN ('0002', '0003'); +CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES FROM (500) TO (600) PARTITION BY RANGE (b); +CREATE TABLE prt1_l_p3_p1 PARTITION OF prt1_l_p3 FOR VALUES FROM (0) TO (13); +CREATE TABLE prt1_l_p3_p2 PARTITION OF prt1_l_p3 FOR VALUES FROM (13) TO (25); +INSERT INTO prt1_l SELECT i, i % 25, to_char(i % 4, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE prt1_l; +CREATE TABLE prt2_l (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE prt2_l_p1 PARTITION OF prt2_l FOR VALUES FROM (0) TO (250); +CREATE TABLE prt2_l_p2 PARTITION OF prt2_l FOR VALUES FROM (250) TO (500) PARTITION BY LIST (c); +CREATE TABLE prt2_l_p2_p1 PARTITION OF prt2_l_p2 FOR VALUES IN ('0000', '0001'); +CREATE TABLE prt2_l_p2_p2 PARTITION OF prt2_l_p2 FOR VALUES IN ('0002', '0003'); +CREATE TABLE prt2_l_p3 PARTITION OF prt2_l FOR VALUES FROM (500) TO (600) PARTITION BY RANGE (a); +CREATE TABLE prt2_l_p3_p1 PARTITION OF prt2_l_p3 FOR VALUES FROM (0) TO (13); +CREATE TABLE prt2_l_p3_p2 PARTITION OF prt2_l_p3 FOR VALUES FROM (13) TO (25); +INSERT INTO prt2_l SELECT i % 25, i, to_char(i % 4, 'FM0000') FROM generate_series(0, 599, 3) i; +ANALYZE prt2_l; +-- inner join, qual covering only top-level partitions +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 150 | 0002 | 150 | 0002 + 300 | 0000 | 300 | 0000 + 450 | 0002 | 450 | 0002 +(4 rows) + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 50 | 0002 | | + 100 | 0000 | | + 150 | 0002 | 150 | 0002 + 200 | 0000 | | + 250 | 0002 | | + 300 | 0000 | 300 | 0000 + 350 | 0002 | | + 400 | 0000 | | + 450 | 0002 | 450 | 0002 + 500 | 0000 | | + 550 | 0002 | | +(12 rows) + +-- right join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.a = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.a = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 150 | 0002 | 150 | 0002 + 300 | 0000 | 300 | 0000 + 450 | 0002 | 450 | 0002 + | | 75 | 0003 + | | 225 | 0001 + | | 375 | 0003 + | | 525 | 0001 +(8 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.a = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c) ORDER BY t1.a, t2.b; + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Sort + Sort Key: prt1_l.a, prt2_l.b + -> Append + -> Hash Full Join + Hash Cond: ((prt1_l_1.a = prt2_l_1.b) AND ((prt1_l_1.c)::text = (prt2_l_1.c)::text)) + -> Seq Scan on prt1_l_p1 prt1_l_1 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_l_p1 prt2_l_1 + Storage Filter: (a = 0) + -> Hash Full Join + Hash Cond: ((prt1_l_3.a = prt2_l_3.b) AND ((prt1_l_3.c)::text = (prt2_l_3.c)::text)) + -> Seq Scan on prt1_l_p2_p1 prt1_l_3 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_l_p2_p1 prt2_l_3 + Storage Filter: (a = 0) + -> Hash Full Join + Hash Cond: ((prt1_l_4.a = prt2_l_4.b) AND ((prt1_l_4.c)::text = (prt2_l_4.c)::text)) + -> Seq Scan on prt1_l_p2_p2 prt1_l_4 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_l_p2_p2 prt2_l_4 + Storage Filter: (a = 0) + -> Hash Full Join + Hash Cond: ((prt1_l_6.a = prt2_l_6.b) AND ((prt1_l_6.c)::text = (prt2_l_6.c)::text)) + -> Seq Scan on prt1_l_p3_p1 prt1_l_6 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_l_p3_p1 prt2_l_6 + Storage Filter: (a = 0) +(31 rows) + +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.a = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c) ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 50 | 0002 | | + 100 | 0000 | | + 150 | 0002 | 150 | 0002 + 200 | 0000 | | + 250 | 0002 | | + 300 | 0000 | 300 | 0000 + 350 | 0002 | | + 400 | 0000 | | + 450 | 0002 | 450 | 0002 + 500 | 0000 | | + 550 | 0002 | | + | | 75 | 0003 + | | 225 | 0001 + | | 375 | 0003 + | | 525 | 0001 +(16 rows) + +-- lateral partitionwise join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT * FROM prt1_l t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss + ON t1.a = ss.t2a AND t1.c = ss.t2c WHERE t1.b = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT * FROM prt1_l t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss + ON t1.a = ss.t2a AND t1.c = ss.t2c WHERE t1.b = 0 ORDER BY t1.a; + a | b | c | t2a | t2c | t2b | t3b | least +-----+---+------+-----+------+-----+-----+------- + 0 | 0 | 0000 | 0 | 0000 | 0 | 0 | 0 + 50 | 0 | 0002 | | | | | + 100 | 0 | 0000 | | | | | + 150 | 0 | 0002 | 150 | 0002 | 0 | 150 | 150 + 200 | 0 | 0000 | | | | | + 250 | 0 | 0002 | | | | | + 300 | 0 | 0000 | 300 | 0000 | 0 | 300 | 300 + 350 | 0 | 0002 | | | | | + 400 | 0 | 0000 | | | | | + 450 | 0 | 0002 | 450 | 0002 | 0 | 450 | 450 + 500 | 0 | 0000 | | | | | + 550 | 0 | 0002 | | | | | +(12 rows) + +-- join with one side empty +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c; + QUERY PLAN +------------------------------------------------------------------------- + Hash Left Join + Hash Cond: ((t2.b = a) AND (t2.a = b) AND ((t2.c)::text = (c)::text)) + -> Append + -> Seq Scan on prt2_l_p1 t2_1 + -> Seq Scan on prt2_l_p2_p1 t2_3 + -> Seq Scan on prt2_l_p2_p2 t2_4 + -> Seq Scan on prt2_l_p3_p1 t2_6 + -> Seq Scan on prt2_l_p3_p2 t2_7 + -> Hash + -> Result prt1_l + One-Time Filter: false +(11 rows) + +-- Test case to verify proper handling of subqueries in a partitioned delete. +-- The weird-looking lateral join is just there to force creation of a +-- nestloop parameter within the subquery, which exposes the problem if the +-- planner fails to make multiple copies of the subquery as appropriate. +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +DELETE FROM prt1_l +WHERE EXISTS ( + SELECT 1 + FROM int4_tbl, + LATERAL (SELECT int4_tbl.f1 FROM int8_tbl LIMIT 2) ss + WHERE prt1_l.c IS NULL); + +*/ -- YB: The explain plan changes significantly between PG and YB +-- +-- negative testcases +-- +CREATE TABLE prt1_n (a int, b int, c varchar) PARTITION BY RANGE(c); +CREATE TABLE prt1_n_p1 PARTITION OF prt1_n FOR VALUES FROM ('0000') TO ('0250'); +CREATE TABLE prt1_n_p2 PARTITION OF prt1_n FOR VALUES FROM ('0250') TO ('0500'); +INSERT INTO prt1_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 499, 2) i; +ANALYZE prt1_n; +CREATE TABLE prt2_n (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE prt2_n_p1 PARTITION OF prt2_n FOR VALUES IN ('0000', '0003', '0004', '0010', '0006', '0007'); +CREATE TABLE prt2_n_p2 PARTITION OF prt2_n FOR VALUES IN ('0001', '0005', '0002', '0009', '0008', '0011'); +INSERT INTO prt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE prt2_n; +CREATE TABLE prt3_n (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE prt3_n_p1 PARTITION OF prt3_n FOR VALUES IN ('0000', '0004', '0006', '0007'); +CREATE TABLE prt3_n_p2 PARTITION OF prt3_n FOR VALUES IN ('0001', '0002', '0008', '0010'); +CREATE TABLE prt3_n_p3 PARTITION OF prt3_n FOR VALUES IN ('0003', '0005', '0009', '0011'); +INSERT INTO prt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE prt3_n; +CREATE TABLE prt4_n (a int, b int, c text) PARTITION BY RANGE(a); +CREATE TABLE prt4_n_p1 PARTITION OF prt4_n FOR VALUES FROM (0) TO (300); +CREATE TABLE prt4_n_p2 PARTITION OF prt4_n FOR VALUES FROM (300) TO (500); +CREATE TABLE prt4_n_p3 PARTITION OF prt4_n FOR VALUES FROM (500) TO (600); +INSERT INTO prt4_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE prt4_n; +-- partitionwise join can not be applied if the partition ranges differ +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2 WHERE t1.a = t2.a; + QUERY PLAN +---------------------------------------------- + Hash Join + Hash Cond: (t1.a = t2.a) + -> Append + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 + -> Hash + -> Append + -> Seq Scan on prt4_n_p1 t2_1 + -> Seq Scan on prt4_n_p2 t2_2 + -> Seq Scan on prt4_n_p3 t2_3 +(11 rows) + +/*+Set(yb_bnl_batch_size 1024) YbBatchedNL(t1 t2)*/ EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2 WHERE t1.a = t2.a; + QUERY PLAN +----------------------------------------------------------------------- + YB Batched Nested Loop Join + Join Filter: (t1.a = t2.a) + -> Append + -> Seq Scan on prt4_n_p1 t2_1 + -> Seq Scan on prt4_n_p2 t2_2 + -> Seq Scan on prt4_n_p3 t2_3 + -> Append + -> Index Scan using iprt1_p1_a on prt1_p1 t1_1 + Index Cond: (a = ANY (ARRAY[t2.a, $1, $2, ..., $1023])) + -> Index Scan using iprt1_p2_a on prt1_p2 t1_2 + Index Cond: (a = ANY (ARRAY[t2.a, $1, $2, ..., $1023])) + -> Index Scan using iprt1_p3_a on prt1_p3 t1_3 + Index Cond: (a = ANY (ARRAY[t2.a, $1, $2, ..., $1023])) +(13 rows) + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a and t1.a = t3.b; + +-- partitionwise join can not be applied if there are no equi-join conditions +-- between partition keys +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON (t1.a < t2.b); + +-- equi-join with join condition on partial keys does not qualify for +-- partitionwise join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1, prt2_m t2 WHERE t1.a = (t2.b + t2.a)/2; +*/ -- YB: The explain plan changes significantly between PG and YB +-- equi-join between out-of-order partition key columns does not qualify for +-- partitionwise join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 LEFT JOIN prt2_m t2 ON t1.a = t2.b; + QUERY PLAN +---------------------------------------------- + Hash Left Join + Hash Cond: (t1.a = t2.b) + -> Append + -> Seq Scan on prt1_m_p1 t1_1 + -> Seq Scan on prt1_m_p2 t1_2 + -> Seq Scan on prt1_m_p3 t1_3 + -> Hash + -> Append + -> Seq Scan on prt2_m_p1 t2_1 + -> Seq Scan on prt2_m_p2 t2_2 + -> Seq Scan on prt2_m_p3 t2_3 +(11 rows) + +-- equi-join between non-key columns does not qualify for partitionwise join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 LEFT JOIN prt2_m t2 ON t1.c = t2.c; + QUERY PLAN +---------------------------------------------- + Hash Left Join + Hash Cond: (t1.c = t2.c) + -> Append + -> Seq Scan on prt1_m_p1 t1_1 + -> Seq Scan on prt1_m_p2 t1_2 + -> Seq Scan on prt1_m_p3 t1_3 + -> Hash + -> Append + -> Seq Scan on prt2_m_p1 t2_1 + -> Seq Scan on prt2_m_p2 t2_2 + -> Seq Scan on prt2_m_p3 t2_3 +(11 rows) + +-- partitionwise join can not be applied for a join between list and range +-- partitioned tables +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 LEFT JOIN prt2_n t2 ON (t1.c = t2.c); + QUERY PLAN +---------------------------------------------- + Hash Right Join + Hash Cond: (t2.c = (t1.c)::text) + -> Append + -> Seq Scan on prt2_n_p1 t2_1 + -> Seq Scan on prt2_n_p2 t2_2 + -> Hash + -> Append + -> Seq Scan on prt1_n_p1 t1_1 + -> Seq Scan on prt1_n_p2 t1_2 +(9 rows) + +-- partitionwise join can not be applied between tables with different +-- partition lists +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 JOIN prt2_n t2 ON (t1.c = t2.c) JOIN plt1 t3 ON (t1.c = t3.c); +*/ -- YB: The explain plan changes significantly between PG and YB +-- partitionwise join can not be applied for a join between key column and +-- non-key column +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 FULL JOIN prt1 t2 ON (t1.c = t2.c); + QUERY PLAN +---------------------------------------------- + Hash Full Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + -> Append + -> Seq Scan on prt1_p1 t2_1 + -> Seq Scan on prt1_p2 t2_2 + -> Seq Scan on prt1_p3 t2_3 + -> Hash + -> Append + -> Seq Scan on prt1_n_p1 t1_1 + -> Seq Scan on prt1_n_p2 t1_2 +(10 rows) + +-- +-- Test some other plan types in a partitionwise join (unfortunately, +-- we need larger tables to get the planner to choose these plan types) +-- +create temp table prtx1 (a integer, b integer, c integer) + partition by range (a); +create temp table prtx1_1 partition of prtx1 for values from (1) to (11); +create temp table prtx1_2 partition of prtx1 for values from (11) to (21); +create temp table prtx1_3 partition of prtx1 for values from (21) to (31); +create temp table prtx2 (a integer, b integer, c integer) + partition by range (a); +create temp table prtx2_1 partition of prtx2 for values from (1) to (11); +create temp table prtx2_2 partition of prtx2 for values from (11) to (21); +create temp table prtx2_3 partition of prtx2 for values from (21) to (31); +insert into prtx1 select 1 + i%30, i, i + from generate_series(1,1000) i; +insert into prtx2 select 1 + i%30, i, i + from generate_series(1,500) i, generate_series(1,10) j; +create index on prtx2 (b); +create index on prtx2 (c); +analyze prtx1; +analyze prtx2; +set enable_indexscan = off; -- YB: force bitmap scan +explain (costs off) +select * from prtx1 +where not exists (select 1 from prtx2 + where prtx2.a=prtx1.a and prtx2.b=prtx1.b and prtx2.c=123) + and a<20 and c=120; + QUERY PLAN +------------------------------------------------------------- + Append + -> Nested Loop Anti Join + -> Seq Scan on prtx1_1 + Filter: ((a < 20) AND (c = 120)) + -> Bitmap Heap Scan on prtx2_1 + Recheck Cond: ((b = prtx1_1.b) AND (c = 123)) + Filter: (a = prtx1_1.a) + -> BitmapAnd + -> Bitmap Index Scan on prtx2_1_b_idx + Index Cond: (b = prtx1_1.b) + -> Bitmap Index Scan on prtx2_1_c_idx + Index Cond: (c = 123) + -> Nested Loop Anti Join + -> Seq Scan on prtx1_2 + Filter: ((a < 20) AND (c = 120)) + -> Bitmap Heap Scan on prtx2_2 + Recheck Cond: ((b = prtx1_2.b) AND (c = 123)) + Filter: (a = prtx1_2.a) + -> BitmapAnd + -> Bitmap Index Scan on prtx2_2_b_idx + Index Cond: (b = prtx1_2.b) + -> Bitmap Index Scan on prtx2_2_c_idx + Index Cond: (c = 123) +(23 rows) + +select * from prtx1 +where not exists (select 1 from prtx2 + where prtx2.a=prtx1.a and prtx2.b=prtx1.b and prtx2.c=123) + and a<20 and c=120; + a | b | c +---+-----+----- + 1 | 120 | 120 +(1 row) + +explain (costs off) +select * from prtx1 +where not exists (select 1 from prtx2 + where prtx2.a=prtx1.a and (prtx2.b=prtx1.b+1 or prtx2.c=99)) + and a<20 and c=91; + QUERY PLAN +----------------------------------------------------------------- + Append + -> Nested Loop Anti Join + -> Seq Scan on prtx1_1 + Filter: ((a < 20) AND (c = 91)) + -> Bitmap Heap Scan on prtx2_1 + Recheck Cond: ((b = (prtx1_1.b + 1)) OR (c = 99)) + Filter: (a = prtx1_1.a) + -> BitmapOr + -> Bitmap Index Scan on prtx2_1_b_idx + Index Cond: (b = (prtx1_1.b + 1)) + -> Bitmap Index Scan on prtx2_1_c_idx + Index Cond: (c = 99) + -> Nested Loop Anti Join + -> Seq Scan on prtx1_2 + Filter: ((a < 20) AND (c = 91)) + -> Bitmap Heap Scan on prtx2_2 + Recheck Cond: ((b = (prtx1_2.b + 1)) OR (c = 99)) + Filter: (a = prtx1_2.a) + -> BitmapOr + -> Bitmap Index Scan on prtx2_2_b_idx + Index Cond: (b = (prtx1_2.b + 1)) + -> Bitmap Index Scan on prtx2_2_c_idx + Index Cond: (c = 99) +(23 rows) + +select * from prtx1 +where not exists (select 1 from prtx2 + where prtx2.a=prtx1.a and (prtx2.b=prtx1.b+1 or prtx2.c=99)) + and a<20 and c=91; + a | b | c +---+----+---- + 2 | 91 | 91 +(1 row) + +-- +-- Test advanced partition-matching algorithm for partitioned join +-- +-- Tests for range-partitioned tables +CREATE TABLE prt1_adv (a int, b int, c varchar) PARTITION BY RANGE (a); +CREATE TABLE prt1_adv_p1 PARTITION OF prt1_adv FOR VALUES FROM (100) TO (200); +CREATE TABLE prt1_adv_p2 PARTITION OF prt1_adv FOR VALUES FROM (200) TO (300); +CREATE TABLE prt1_adv_p3 PARTITION OF prt1_adv FOR VALUES FROM (300) TO (400); +CREATE INDEX prt1_adv_a_idx ON prt1_adv (a); +INSERT INTO prt1_adv SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(100, 399) i; +ANALYZE prt1_adv; +CREATE TABLE prt2_adv (a int, b int, c varchar) PARTITION BY RANGE (b); +CREATE TABLE prt2_adv_p1 PARTITION OF prt2_adv FOR VALUES FROM (100) TO (150); +CREATE TABLE prt2_adv_p2 PARTITION OF prt2_adv FOR VALUES FROM (200) TO (300); +CREATE TABLE prt2_adv_p3 PARTITION OF prt2_adv FOR VALUES FROM (350) TO (500); +CREATE INDEX prt2_adv_b_idx ON prt2_adv (b); +INSERT INTO prt2_adv_p1 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(100, 149) i; +INSERT INTO prt2_adv_p2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(200, 299) i; +INSERT INTO prt2_adv_p3 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(350, 499) i; +ANALYZE prt2_adv; +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 100 | 0100 | 100 | 0100 + 125 | 0125 | 125 | 0125 + 200 | 0200 | 200 | 0200 + 225 | 0225 | 225 | 0225 + 250 | 0250 | 250 | 0250 + 275 | 0275 | 275 | 0275 + 350 | 0350 | 350 | 0350 + 375 | 0375 | 375 | 0375 +(8 rows) + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Semi Join + Hash Cond: (t1_1.a = t2_1.b) + -> Seq Scan on prt1_adv_p1 t1_1 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p1 t2_1 + -> Hash Semi Join + Hash Cond: (t1_2.a = t2_2.b) + -> Seq Scan on prt1_adv_p2 t1_2 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p2 t2_2 + -> Hash Semi Join + Hash Cond: (t1_3.a = t2_3.b) + -> Seq Scan on prt1_adv_p3 t1_3 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p3 t2_3 +(21 rows) + +SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + a | b | c +-----+---+------ + 100 | 0 | 0100 + 125 | 0 | 0125 + 200 | 0 | 0200 + 225 | 0 | 0225 + 250 | 0 | 0250 + 275 | 0 | 0275 + 350 | 0 | 0350 + 375 | 0 | 0375 +(8 rows) + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 100 | 0100 | 100 | 0100 + 125 | 0125 | 125 | 0125 + 150 | 0150 | | + 175 | 0175 | | + 200 | 0200 | 200 | 0200 + 225 | 0225 | 225 | 0225 + 250 | 0250 | 250 | 0250 + 275 | 0275 | 275 | 0275 + 300 | 0300 | | + 325 | 0325 | | + 350 | 0350 | 350 | 0350 + 375 | 0375 | 375 | 0375 +(12 rows) + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Anti Join + Hash Cond: (t1_1.a = t2_1.b) + -> Seq Scan on prt1_adv_p1 t1_1 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p1 t2_1 + -> Hash Anti Join + Hash Cond: (t1_2.a = t2_2.b) + -> Seq Scan on prt1_adv_p2 t1_2 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p2 t2_2 + -> Hash Anti Join + Hash Cond: (t1_3.a = t2_3.b) + -> Seq Scan on prt1_adv_p3 t1_3 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p3 t2_3 +(21 rows) + +SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + a | b | c +-----+---+------ + 150 | 0 | 0150 + 175 | 0 | 0175 + 300 | 0 | 0300 + 325 | 0 | 0325 +(4 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; + QUERY PLAN +-------------------------------------------------------------------------- + Sort + Sort Key: prt1_adv.a, prt2_adv.b + -> Append + -> Hash Full Join + Hash Cond: (prt1_adv_1.a = prt2_adv_1.b) + Filter: (((175) = prt1_adv_1.a) OR ((425) = prt2_adv_1.b)) + -> Seq Scan on prt1_adv_p1 prt1_adv_1 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p1 prt2_adv_1 + Storage Filter: (a = 0) + -> Hash Full Join + Hash Cond: (prt1_adv_2.a = prt2_adv_2.b) + Filter: (((175) = prt1_adv_2.a) OR ((425) = prt2_adv_2.b)) + -> Seq Scan on prt1_adv_p2 prt1_adv_2 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p2 prt2_adv_2 + Storage Filter: (a = 0) + -> Hash Full Join + Hash Cond: (prt2_adv_3.b = prt1_adv_3.a) + Filter: (((175) = prt1_adv_3.a) OR ((425) = prt2_adv_3.b)) + -> Seq Scan on prt2_adv_p3 prt2_adv_3 + Storage Filter: (a = 0) + -> Hash + -> Seq Scan on prt1_adv_p3 prt1_adv_3 + Storage Filter: (b = 0) +(27 rows) + +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 175 | 0175 | | + | | 425 | 0425 +(2 rows) + +-- Test cases where one side has an extra partition +CREATE TABLE prt2_adv_extra PARTITION OF prt2_adv FOR VALUES FROM (500) TO (MAXVALUE); +INSERT INTO prt2_adv SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(500, 599) i; +ANALYZE prt2_adv; +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 100 | 0100 | 100 | 0100 + 125 | 0125 | 125 | 0125 + 200 | 0200 | 200 | 0200 + 225 | 0225 | 225 | 0225 + 250 | 0250 | 250 | 0250 + 275 | 0275 | 275 | 0275 + 350 | 0350 | 350 | 0350 + 375 | 0375 | 375 | 0375 +(8 rows) + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Semi Join + Hash Cond: (t1_1.a = t2_1.b) + -> Seq Scan on prt1_adv_p1 t1_1 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p1 t2_1 + -> Hash Semi Join + Hash Cond: (t1_2.a = t2_2.b) + -> Seq Scan on prt1_adv_p2 t1_2 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p2 t2_2 + -> Hash Semi Join + Hash Cond: (t1_3.a = t2_3.b) + -> Seq Scan on prt1_adv_p3 t1_3 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p3 t2_3 +(21 rows) + +SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + a | b | c +-----+---+------ + 100 | 0 | 0100 + 125 | 0 | 0125 + 200 | 0 | 0200 + 225 | 0 | 0225 + 250 | 0 | 0250 + 275 | 0 | 0275 + 350 | 0 | 0350 + 375 | 0 | 0375 +(8 rows) + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 100 | 0100 | 100 | 0100 + 125 | 0125 | 125 | 0125 + 150 | 0150 | | + 175 | 0175 | | + 200 | 0200 | 200 | 0200 + 225 | 0225 | 225 | 0225 + 250 | 0250 | 250 | 0250 + 275 | 0275 | 275 | 0275 + 300 | 0300 | | + 325 | 0325 | | + 350 | 0350 | 350 | 0350 + 375 | 0375 | 375 | 0375 +(12 rows) + +-- left join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.b, t1.c, t2.a, t2.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a; +*/ -- YB: The explain plan changes significantly between PG and YB +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Anti Join + Hash Cond: (t1_1.a = t2_1.b) + -> Seq Scan on prt1_adv_p1 t1_1 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p1 t2_1 + -> Hash Anti Join + Hash Cond: (t1_2.a = t2_2.b) + -> Seq Scan on prt1_adv_p2 t1_2 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p2 t2_2 + -> Hash Anti Join + Hash Cond: (t1_3.a = t2_3.b) + -> Seq Scan on prt1_adv_p3 t1_3 + Storage Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_adv_p3 t2_3 +(21 rows) + +SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + a | b | c +-----+---+------ + 150 | 0 | 0150 + 175 | 0 | 0175 + 300 | 0 | 0300 + 325 | 0 | 0325 +(4 rows) + +-- anti join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt2_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt1_adv t2 WHERE t1.b = t2.a) AND t1.a = 0 ORDER BY t1.b; + QUERY PLAN +------------------------------------------------------ + Sort + Sort Key: t1.b + -> Hash Anti Join + Hash Cond: (t1.b = t2.a) + -> Append + -> Seq Scan on prt2_adv_p1 t1_1 + Storage Filter: (a = 0) + -> Seq Scan on prt2_adv_p2 t1_2 + Storage Filter: (a = 0) + -> Seq Scan on prt2_adv_p3 t1_3 + Storage Filter: (a = 0) + -> Seq Scan on prt2_adv_extra t1_4 + Storage Filter: (a = 0) + -> Hash + -> Append + -> Seq Scan on prt1_adv_p1 t2_1 + -> Seq Scan on prt1_adv_p2 t2_2 + -> Seq Scan on prt1_adv_p3 t2_3 +(18 rows) + +-- full join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; + QUERY PLAN +---------------------------------------------------------------- + Sort + Sort Key: prt1_adv.a, prt2_adv.b + -> Hash Full Join + Hash Cond: (prt2_adv.b = prt1_adv.a) + Filter: (((175) = prt1_adv.a) OR ((425) = prt2_adv.b)) + -> Append + -> Seq Scan on prt2_adv_p1 prt2_adv_1 + Storage Filter: (a = 0) + -> Seq Scan on prt2_adv_p2 prt2_adv_2 + Storage Filter: (a = 0) + -> Seq Scan on prt2_adv_p3 prt2_adv_3 + Storage Filter: (a = 0) + -> Seq Scan on prt2_adv_extra prt2_adv_4 + Storage Filter: (a = 0) + -> Hash + -> Append + -> Seq Scan on prt1_adv_p1 prt1_adv_1 + Storage Filter: (b = 0) + -> Seq Scan on prt1_adv_p2 prt1_adv_2 + Storage Filter: (b = 0) + -> Seq Scan on prt1_adv_p3 prt1_adv_3 + Storage Filter: (b = 0) +(22 rows) + +-- 3-way join where not every pair of relations can do partitioned join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) INNER JOIN prt1_adv t3 ON (t1.b = t3.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a, t3.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) INNER JOIN prt1_adv t3 ON (t1.b = t3.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a, t3.a; + b | c | a | c | a | c +-----+------+-----+------+-----+------ + 100 | 0100 | 100 | 0100 | 100 | 0100 + 125 | 0125 | 125 | 0125 | 125 | 0125 + 200 | 0200 | 200 | 0200 | 200 | 0200 + 225 | 0225 | 225 | 0225 | 225 | 0225 + 250 | 0250 | 250 | 0250 | 250 | 0250 + 275 | 0275 | 275 | 0275 | 275 | 0275 + 350 | 0350 | 350 | 0350 | 350 | 0350 + 375 | 0375 | 375 | 0375 | 375 | 0375 +(8 rows) + +DROP TABLE prt2_adv_extra; +-- Test cases where a partition on one side matches multiple partitions on +-- the other side; we currently can't do partitioned join in such cases +ALTER TABLE prt2_adv DETACH PARTITION prt2_adv_p3; +-- Split prt2_adv_p3 into two partitions so that prt1_adv_p3 matches both +CREATE TABLE prt2_adv_p3_1 PARTITION OF prt2_adv FOR VALUES FROM (350) TO (375); +CREATE TABLE prt2_adv_p3_2 PARTITION OF prt2_adv FOR VALUES FROM (375) TO (500); +INSERT INTO prt2_adv SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(350, 499) i; +ANALYZE prt2_adv; +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------- + Sort + Sort Key: t1.a + -> Hash Anti Join + Hash Cond: (t1.a = t2.b) + -> Append + -> Seq Scan on prt1_adv_p1 t1_1 + Storage Filter: (b = 0) + -> Seq Scan on prt1_adv_p2 t1_2 + Storage Filter: (b = 0) + -> Seq Scan on prt1_adv_p3 t1_3 + Storage Filter: (b = 0) + -> Hash + -> Append + -> Seq Scan on prt2_adv_p1 t2_1 + -> Seq Scan on prt2_adv_p2 t2_2 + -> Seq Scan on prt2_adv_p3_1 t2_3 + -> Seq Scan on prt2_adv_p3_2 t2_4 +(17 rows) + +-- full join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +DROP TABLE prt2_adv_p3_1; +DROP TABLE prt2_adv_p3_2; +ANALYZE prt2_adv; +-- Test default partitions +ALTER TABLE prt1_adv DETACH PARTITION prt1_adv_p1; +-- Change prt1_adv_p1 to the default partition +ALTER TABLE prt1_adv ATTACH PARTITION prt1_adv_p1 DEFAULT; +ALTER TABLE prt1_adv DETACH PARTITION prt1_adv_p3; +ANALYZE prt1_adv; +-- We can do partitioned join even if only one of relations has the default +-- partition +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 100 | 0100 | 100 | 0100 + 125 | 0125 | 125 | 0125 + 200 | 0200 | 200 | 0200 + 225 | 0225 | 225 | 0225 + 250 | 0250 | 250 | 0250 + 275 | 0275 | 275 | 0275 +(6 rows) + +-- Restore prt1_adv_p3 +ALTER TABLE prt1_adv ATTACH PARTITION prt1_adv_p3 FOR VALUES FROM (300) TO (400); +ANALYZE prt1_adv; +-- Restore prt2_adv_p3 +ALTER TABLE prt2_adv ATTACH PARTITION prt2_adv_p3 FOR VALUES FROM (350) TO (500); +ANALYZE prt2_adv; +-- Partitioned join can't be applied because the default partition of prt1_adv +-- matches prt2_adv_p1 and prt2_adv_p3 +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +ALTER TABLE prt2_adv DETACH PARTITION prt2_adv_p3; +-- Change prt2_adv_p3 to the default partition +ALTER TABLE prt2_adv ATTACH PARTITION prt2_adv_p3 DEFAULT; +ANALYZE prt2_adv; +-- Partitioned join can't be applied because the default partition of prt1_adv +-- matches prt2_adv_p1 and prt2_adv_p3 +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +DROP TABLE prt1_adv_p3; +ANALYZE prt1_adv; +DROP TABLE prt2_adv_p3; +ANALYZE prt2_adv; +CREATE TABLE prt3_adv (a int, b int, c varchar) PARTITION BY RANGE (a); +CREATE TABLE prt3_adv_p1 PARTITION OF prt3_adv FOR VALUES FROM (200) TO (300); +CREATE TABLE prt3_adv_p2 PARTITION OF prt3_adv FOR VALUES FROM (300) TO (400); +CREATE INDEX prt3_adv_a_idx ON prt3_adv (a); +INSERT INTO prt3_adv SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(200, 399) i; +ANALYZE prt3_adv; +-- 3-way join to test the default partition of a join relation +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) LEFT JOIN prt3_adv t3 ON (t1.a = t3.a) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) LEFT JOIN prt3_adv t3 ON (t1.a = t3.a) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a; + a | c | b | c | a | c +-----+------+-----+------+-----+------ + 100 | 0100 | 100 | 0100 | | + 125 | 0125 | 125 | 0125 | | + 150 | 0150 | | | | + 175 | 0175 | | | | + 200 | 0200 | 200 | 0200 | 200 | 0200 + 225 | 0225 | 225 | 0225 | 225 | 0225 + 250 | 0250 | 250 | 0250 | 250 | 0250 + 275 | 0275 | 275 | 0275 | 275 | 0275 +(8 rows) + +DROP TABLE prt1_adv; +DROP TABLE prt2_adv; +DROP TABLE prt3_adv; +-- Test interaction of partitioned join with partition pruning +CREATE TABLE prt1_adv (a int, b int, c varchar) PARTITION BY RANGE (a); +CREATE TABLE prt1_adv_p1 PARTITION OF prt1_adv FOR VALUES FROM (100) TO (200); +CREATE TABLE prt1_adv_p2 PARTITION OF prt1_adv FOR VALUES FROM (200) TO (300); +CREATE TABLE prt1_adv_p3 PARTITION OF prt1_adv FOR VALUES FROM (300) TO (400); +CREATE INDEX prt1_adv_a_idx ON prt1_adv (a); +INSERT INTO prt1_adv SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(100, 399) i; +ANALYZE prt1_adv; +CREATE TABLE prt2_adv (a int, b int, c varchar) PARTITION BY RANGE (b); +CREATE TABLE prt2_adv_p1 PARTITION OF prt2_adv FOR VALUES FROM (100) TO (200); +CREATE TABLE prt2_adv_p2 PARTITION OF prt2_adv FOR VALUES FROM (200) TO (400); +CREATE INDEX prt2_adv_b_idx ON prt2_adv (b); +INSERT INTO prt2_adv SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(100, 399) i; +ANALYZE prt2_adv; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 100 | 0100 | 100 | 0100 + 125 | 0125 | 125 | 0125 + 150 | 0150 | 150 | 0150 + 175 | 0175 | 175 | 0175 + 200 | 0200 | 200 | 0200 + 225 | 0225 | 225 | 0225 + 250 | 0250 | 250 | 0250 + 275 | 0275 | 275 | 0275 +(8 rows) + +DROP TABLE prt1_adv_p3; +CREATE TABLE prt1_adv_default PARTITION OF prt1_adv DEFAULT; +ANALYZE prt1_adv; +CREATE TABLE prt2_adv_default PARTITION OF prt2_adv DEFAULT; +ANALYZE prt2_adv; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; + a | c | b | c +-----+------+-----+------ + 100 | 0100 | 100 | 0100 + 125 | 0125 | 125 | 0125 + 150 | 0150 | 150 | 0150 + 175 | 0175 | 175 | 0175 + 200 | 0200 | 200 | 0200 + 225 | 0225 | 225 | 0225 + 250 | 0250 | 250 | 0250 + 275 | 0275 | 275 | 0275 +(8 rows) + +DROP TABLE prt1_adv; +DROP TABLE prt2_adv; +-- Tests for list-partitioned tables +CREATE TABLE plt1_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt1_adv_p1 PARTITION OF plt1_adv FOR VALUES IN ('0001', '0003'); +CREATE TABLE plt1_adv_p2 PARTITION OF plt1_adv FOR VALUES IN ('0004', '0006'); +CREATE TABLE plt1_adv_p3 PARTITION OF plt1_adv FOR VALUES IN ('0008', '0009'); +INSERT INTO plt1_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9); +ANALYZE plt1_adv; +CREATE TABLE plt2_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt2_adv_p1 PARTITION OF plt2_adv FOR VALUES IN ('0002', '0003'); +CREATE TABLE plt2_adv_p2 PARTITION OF plt2_adv FOR VALUES IN ('0004', '0006'); +CREATE TABLE plt2_adv_p3 PARTITION OF plt2_adv FOR VALUES IN ('0007', '0009'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +ANALYZE plt2_adv; +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 9 | 0009 | 9 | 0009 +(4 rows) + +-- semi join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + a | b | c +---+---+------ + 3 | 3 | 0003 + 4 | 4 | 0004 + 6 | 6 | 0006 + 9 | 9 | 0009 +(4 rows) + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 1 | 0001 | | + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 8 | 0008 | | + 9 | 0009 | 9 | 0009 +(6 rows) + +-- anti join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + a | b | c +---+---+------ + 1 | 1 | 0001 + 8 | 8 | 0008 +(2 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +----------------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Append + -> Hash Full Join + Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c)) + Filter: ((COALESCE(t1_1.b, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10)) + -> Seq Scan on plt1_adv_p1 t1_1 + -> Hash + -> Seq Scan on plt2_adv_p1 t2_1 + -> Hash Full Join + Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c)) + Filter: ((COALESCE(t1_2.b, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10)) + -> Seq Scan on plt1_adv_p2 t1_2 + -> Hash + -> Seq Scan on plt2_adv_p2 t2_2 + -> Hash Full Join + Hash Cond: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c)) + Filter: ((COALESCE(t1_3.b, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10)) + -> Seq Scan on plt1_adv_p3 t1_3 + -> Hash + -> Seq Scan on plt2_adv_p3 t2_3 +(21 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + a | c | a | c +---+------+---+------ + 1 | 0001 | | + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 8 | 0008 | | + 9 | 0009 | 9 | 0009 + | | 2 | 0002 + | | 7 | 0007 +(8 rows) + +-- Test cases where one side has an extra partition +CREATE TABLE plt2_adv_extra PARTITION OF plt2_adv FOR VALUES IN ('0000'); +INSERT INTO plt2_adv_extra VALUES (0, 0, '0000'); +ANALYZE plt2_adv; +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 9 | 0009 | 9 | 0009 +(4 rows) + +-- semi join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + a | b | c +---+---+------ + 3 | 3 | 0003 + 4 | 4 | 0004 + 6 | 6 | 0006 + 9 | 9 | 0009 +(4 rows) + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 1 | 0001 | | + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 8 | 0008 | | + 9 | 0009 | 9 | 0009 +(6 rows) + +-- left join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt2_adv t1 LEFT JOIN plt1_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +-- anti join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + a | b | c +---+---+------ + 1 | 1 | 0001 + 8 | 8 | 0008 +(2 rows) + +-- anti join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt2_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt1_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Anti Join + Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c)) + -> Append + -> Seq Scan on plt2_adv_extra t1_1 + Storage Filter: (b < 10) + -> Seq Scan on plt2_adv_p1 t1_2 + Storage Filter: (b < 10) + -> Seq Scan on plt2_adv_p2 t1_3 + Storage Filter: (b < 10) + -> Seq Scan on plt2_adv_p3 t1_4 + Storage Filter: (b < 10) + -> Hash + -> Append + -> Seq Scan on plt1_adv_p1 t2_1 + -> Seq Scan on plt1_adv_p2 t2_2 + -> Seq Scan on plt1_adv_p3 t2_3 +(18 rows) + +-- full join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Hash Full Join + Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) + Filter: ((COALESCE(t1.b, 0) < 10) AND (COALESCE(t2.b, 0) < 10)) + -> Append + -> Seq Scan on plt2_adv_extra t2_1 + -> Seq Scan on plt2_adv_p1 t2_2 + -> Seq Scan on plt2_adv_p2 t2_3 + -> Seq Scan on plt2_adv_p3 t2_4 + -> Hash + -> Append + -> Seq Scan on plt1_adv_p1 t1_1 + -> Seq Scan on plt1_adv_p2 t1_2 + -> Seq Scan on plt1_adv_p3 t1_3 +(15 rows) + +DROP TABLE plt2_adv_extra; +-- Test cases where a partition on one side matches multiple partitions on +-- the other side; we currently can't do partitioned join in such cases +ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p2; +-- Split plt2_adv_p2 into two partitions so that plt1_adv_p2 matches both +CREATE TABLE plt2_adv_p2_1 PARTITION OF plt2_adv FOR VALUES IN ('0004'); +CREATE TABLE plt2_adv_p2_2 PARTITION OF plt2_adv FOR VALUES IN ('0006'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (4, 6); +ANALYZE plt2_adv; +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------- + Sort + Sort Key: t1.a + -> Hash Semi Join + Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c)) + -> Append + -> Seq Scan on plt1_adv_p1 t1_1 + Storage Filter: (b < 10) + -> Seq Scan on plt1_adv_p2 t1_2 + Storage Filter: (b < 10) + -> Seq Scan on plt1_adv_p3 t1_3 + Storage Filter: (b < 10) + -> Hash + -> Append + -> Seq Scan on plt2_adv_p1 t2_1 + -> Seq Scan on plt2_adv_p2_1 t2_2 + -> Seq Scan on plt2_adv_p2_2 t2_3 + -> Seq Scan on plt2_adv_p3 t2_4 +(17 rows) + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------- + Sort + Sort Key: t1.a + -> Hash Anti Join + Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c)) + -> Append + -> Seq Scan on plt1_adv_p1 t1_1 + Storage Filter: (b < 10) + -> Seq Scan on plt1_adv_p2 t1_2 + Storage Filter: (b < 10) + -> Seq Scan on plt1_adv_p3 t1_3 + Storage Filter: (b < 10) + -> Hash + -> Append + -> Seq Scan on plt2_adv_p1 t2_1 + -> Seq Scan on plt2_adv_p2_1 t2_2 + -> Seq Scan on plt2_adv_p2_2 t2_3 + -> Seq Scan on plt2_adv_p3 t2_4 +(17 rows) + +-- full join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; +*/ -- YB: The explain plan changes significantly between PG and YB +DROP TABLE plt2_adv_p2_1; +DROP TABLE plt2_adv_p2_2; +-- Restore plt2_adv_p2 +ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p2 FOR VALUES IN ('0004', '0006'); +-- Test NULL partitions +ALTER TABLE plt1_adv DETACH PARTITION plt1_adv_p1; +-- Change plt1_adv_p1 to the NULL partition +CREATE TABLE plt1_adv_p1_null PARTITION OF plt1_adv FOR VALUES IN (NULL, '0001', '0003'); +INSERT INTO plt1_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3); +INSERT INTO plt1_adv VALUES (-1, -1, NULL); +ANALYZE plt1_adv; +ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p3; +-- Change plt2_adv_p3 to the NULL partition +CREATE TABLE plt2_adv_p3_null PARTITION OF plt2_adv FOR VALUES IN (NULL, '0007', '0009'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (7, 9); +INSERT INTO plt2_adv VALUES (-1, -1, NULL); +ANALYZE plt2_adv; +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 9 | 0009 | 9 | 0009 +(4 rows) + +-- semi join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + a | b | c +---+---+------ + 3 | 3 | 0003 + 4 | 4 | 0004 + 6 | 6 | 0006 + 9 | 9 | 0009 +(4 rows) + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +----+------+---+------ + -1 | | | + 1 | 0001 | | + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 8 | 0008 | | + 9 | 0009 | 9 | 0009 +(7 rows) + +-- anti join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + a | b | c +----+----+------ + -1 | -1 | + 1 | 1 | 0001 + 8 | 8 | 0008 +(3 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +----------------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Append + -> Hash Full Join + Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c)) + Filter: ((COALESCE(t1_1.b, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10)) + -> Seq Scan on plt1_adv_p1_null t1_1 + -> Hash + -> Seq Scan on plt2_adv_p1 t2_1 + -> Hash Full Join + Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c)) + Filter: ((COALESCE(t1_2.b, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10)) + -> Seq Scan on plt1_adv_p2 t1_2 + -> Hash + -> Seq Scan on plt2_adv_p2 t2_2 + -> Hash Full Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) + Filter: ((COALESCE(t1_3.b, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10)) + -> Seq Scan on plt2_adv_p3_null t2_3 + -> Hash + -> Seq Scan on plt1_adv_p3 t1_3 +(21 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + a | c | a | c +----+------+----+------ + -1 | | | + 1 | 0001 | | + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 8 | 0008 | | + 9 | 0009 | 9 | 0009 + | | -1 | + | | 2 | 0002 + | | 7 | 0007 +(10 rows) + +DROP TABLE plt1_adv_p1_null; +-- Restore plt1_adv_p1 +ALTER TABLE plt1_adv ATTACH PARTITION plt1_adv_p1 FOR VALUES IN ('0001', '0003'); +-- Add to plt1_adv the extra NULL partition containing only NULL values as the +-- key values +CREATE TABLE plt1_adv_extra PARTITION OF plt1_adv FOR VALUES IN (NULL); +INSERT INTO plt1_adv VALUES (-1, -1, NULL); +ANALYZE plt1_adv; +DROP TABLE plt2_adv_p3_null; +-- Restore plt2_adv_p3 +ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p3 FOR VALUES IN ('0007', '0009'); +ANALYZE plt2_adv; +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 9 | 0009 | 9 | 0009 +(4 rows) + +-- left join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +-- full join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Hash Full Join + Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c)) + Filter: ((COALESCE(t1.b, 0) < 10) AND (COALESCE(t2.b, 0) < 10)) + -> Append + -> Seq Scan on plt1_adv_p1 t1_1 + -> Seq Scan on plt1_adv_p2 t1_2 + -> Seq Scan on plt1_adv_p3 t1_3 + -> Seq Scan on plt1_adv_extra t1_4 + -> Hash + -> Append + -> Seq Scan on plt2_adv_p1 t2_1 + -> Seq Scan on plt2_adv_p2 t2_2 + -> Seq Scan on plt2_adv_p3 t2_3 +(15 rows) + +-- Add to plt2_adv the extra NULL partition containing only NULL values as the +-- key values +CREATE TABLE plt2_adv_extra PARTITION OF plt2_adv FOR VALUES IN (NULL); +INSERT INTO plt2_adv VALUES (-1, -1, NULL); +ANALYZE plt2_adv; +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 9 | 0009 | 9 | 0009 +(4 rows) + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +----+------+---+------ + -1 | | | + 1 | 0001 | | + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 8 | 0008 | | + 9 | 0009 | 9 | 0009 +(7 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +----------------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Append + -> Hash Full Join + Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c)) + Filter: ((COALESCE(t1_1.b, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10)) + -> Seq Scan on plt1_adv_p1 t1_1 + -> Hash + -> Seq Scan on plt2_adv_p1 t2_1 + -> Hash Full Join + Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c)) + Filter: ((COALESCE(t1_2.b, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10)) + -> Seq Scan on plt1_adv_p2 t1_2 + -> Hash + -> Seq Scan on plt2_adv_p2 t2_2 + -> Hash Full Join + Hash Cond: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c)) + Filter: ((COALESCE(t1_3.b, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10)) + -> Seq Scan on plt1_adv_p3 t1_3 + -> Hash + -> Seq Scan on plt2_adv_p3 t2_3 + -> Hash Full Join + Hash Cond: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c)) + Filter: ((COALESCE(t1_4.b, 0) < 10) AND (COALESCE(t2_4.b, 0) < 10)) + -> Seq Scan on plt1_adv_extra t1_4 + -> Hash + -> Seq Scan on plt2_adv_extra t2_4 +(27 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + a | c | a | c +----+------+----+------ + -1 | | | + 1 | 0001 | | + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 + 8 | 0008 | | + 9 | 0009 | 9 | 0009 + | | -1 | + | | 2 | 0002 + | | 7 | 0007 +(10 rows) + +-- 3-way join to test the NULL partition of a join relation +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt1_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt1_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c | a | c +----+------+---+------+---+------ + -1 | | | | | + 1 | 0001 | | | 1 | 0001 + 3 | 0003 | 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 | 6 | 0006 + 8 | 0008 | | | 8 | 0008 + 9 | 0009 | 9 | 0009 | 9 | 0009 +(7 rows) + +DROP TABLE plt1_adv_extra; +DROP TABLE plt2_adv_extra; +-- Test default partitions +ALTER TABLE plt1_adv DETACH PARTITION plt1_adv_p1; +-- Change plt1_adv_p1 to the default partition +ALTER TABLE plt1_adv ATTACH PARTITION plt1_adv_p1 DEFAULT; +DROP TABLE plt1_adv_p3; +ANALYZE plt1_adv; +DROP TABLE plt2_adv_p3; +ANALYZE plt2_adv; +-- We can do partitioned join even if only one of relations has the default +-- partition +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 +(3 rows) + +ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p2; +-- Change plt2_adv_p2 to contain '0005' in addition to '0004' and '0006' as +-- the key values +CREATE TABLE plt2_adv_p2_ext PARTITION OF plt2_adv FOR VALUES IN ('0004', '0005', '0006'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (4, 5, 6); +ANALYZE plt2_adv; +-- Partitioned join can't be applied because the default partition of plt1_adv +-- matches plt2_adv_p1 and plt2_adv_p2_ext +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Join + Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) + -> Append + -> Seq Scan on plt2_adv_p1 t2_1 + -> Seq Scan on plt2_adv_p2_ext t2_2 + -> Hash + -> Append + -> Seq Scan on plt1_adv_p2 t1_1 + Storage Filter: (b < 10) + -> Seq Scan on plt1_adv_p1 t1_2 + Storage Filter: (b < 10) +(13 rows) + +ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p2_ext; +-- Change plt2_adv_p2_ext to the default partition +ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p2_ext DEFAULT; +ANALYZE plt2_adv; +-- Partitioned join can't be applied because the default partition of plt1_adv +-- matches plt2_adv_p1 and plt2_adv_p2_ext +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Join + Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) + -> Append + -> Seq Scan on plt2_adv_p1 t2_1 + -> Seq Scan on plt2_adv_p2_ext t2_2 + -> Hash + -> Append + -> Seq Scan on plt1_adv_p2 t1_1 + Storage Filter: (b < 10) + -> Seq Scan on plt1_adv_p1 t1_2 + Storage Filter: (b < 10) +(13 rows) + +DROP TABLE plt2_adv_p2_ext; +-- Restore plt2_adv_p2 +ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p2 FOR VALUES IN ('0004', '0006'); +ANALYZE plt2_adv; +CREATE TABLE plt3_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt3_adv_p1 PARTITION OF plt3_adv FOR VALUES IN ('0004', '0006'); +CREATE TABLE plt3_adv_p2 PARTITION OF plt3_adv FOR VALUES IN ('0007', '0009'); +INSERT INTO plt3_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (4, 6, 7, 9); +ANALYZE plt3_adv; +-- 3-way join to test the default partition of a join relation +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt3_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt3_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c | a | c +---+------+---+------+---+------ + 1 | 0001 | | | | + 3 | 0003 | 3 | 0003 | | + 4 | 0004 | 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 | 6 | 0006 +(4 rows) + +-- Test cases where one side has the default partition while the other side +-- has the NULL partition +DROP TABLE plt2_adv_p1; +-- Add the NULL partition to plt2_adv +CREATE TABLE plt2_adv_p1_null PARTITION OF plt2_adv FOR VALUES IN (NULL, '0001', '0003'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3); +INSERT INTO plt2_adv VALUES (-1, -1, NULL); +ANALYZE plt2_adv; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 1 | 0001 | 1 | 0001 + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 +(4 rows) + +DROP TABLE plt2_adv_p1_null; +-- Add the NULL partition that contains only NULL values as the key values +CREATE TABLE plt2_adv_p1_null PARTITION OF plt2_adv FOR VALUES IN (NULL); +INSERT INTO plt2_adv VALUES (-1, -1, NULL); +ANALYZE plt2_adv; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 4 | 0004 | 4 | 0004 + 6 | 0006 | 6 | 0006 +(2 rows) + +DROP TABLE plt1_adv; +DROP TABLE plt2_adv; +DROP TABLE plt3_adv; +-- Test interaction of partitioned join with partition pruning +CREATE TABLE plt1_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt1_adv_p1 PARTITION OF plt1_adv FOR VALUES IN ('0001'); +CREATE TABLE plt1_adv_p2 PARTITION OF plt1_adv FOR VALUES IN ('0002'); +CREATE TABLE plt1_adv_p3 PARTITION OF plt1_adv FOR VALUES IN ('0003'); +CREATE TABLE plt1_adv_p4 PARTITION OF plt1_adv FOR VALUES IN (NULL, '0004', '0005'); +INSERT INTO plt1_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 2, 3, 4, 5); +INSERT INTO plt1_adv VALUES (-1, -1, NULL); +ANALYZE plt1_adv; +CREATE TABLE plt2_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt2_adv_p1 PARTITION OF plt2_adv FOR VALUES IN ('0001', '0002'); +CREATE TABLE plt2_adv_p2 PARTITION OF plt2_adv FOR VALUES IN (NULL); +CREATE TABLE plt2_adv_p3 PARTITION OF plt2_adv FOR VALUES IN ('0003'); +CREATE TABLE plt2_adv_p4 PARTITION OF plt2_adv FOR VALUES IN ('0004', '0005'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 2, 3, 4, 5); +INSERT INTO plt2_adv VALUES (-1, -1, NULL); +ANALYZE plt2_adv; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 5 | 0005 | 5 | 0005 +(3 rows) + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; + a | c | a | c +----+---+---+--- + -1 | | | +(1 row) + +CREATE TABLE plt1_adv_default PARTITION OF plt1_adv DEFAULT; +ANALYZE plt1_adv; +CREATE TABLE plt2_adv_default PARTITION OF plt2_adv DEFAULT; +ANALYZE plt2_adv; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; + a | c | a | c +---+------+---+------ + 3 | 0003 | 3 | 0003 + 4 | 0004 | 4 | 0004 + 5 | 0005 | 5 | 0005 +(3 rows) + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; + a | c | a | c +----+---+---+--- + -1 | | | +(1 row) + +DROP TABLE plt1_adv; +DROP TABLE plt2_adv; +-- Test the process_outer_partition() code path +CREATE TABLE plt1_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt1_adv_p1 PARTITION OF plt1_adv FOR VALUES IN ('0000', '0001', '0002'); +CREATE TABLE plt1_adv_p2 PARTITION OF plt1_adv FOR VALUES IN ('0003', '0004'); +INSERT INTO plt1_adv SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i; +ANALYZE plt1_adv; +CREATE TABLE plt2_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt2_adv_p1 PARTITION OF plt2_adv FOR VALUES IN ('0002'); +CREATE TABLE plt2_adv_p2 PARTITION OF plt2_adv FOR VALUES IN ('0003', '0004'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (2, 3, 4); +ANALYZE plt2_adv; +CREATE TABLE plt3_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt3_adv_p1 PARTITION OF plt3_adv FOR VALUES IN ('0001'); +CREATE TABLE plt3_adv_p2 PARTITION OF plt3_adv FOR VALUES IN ('0003', '0004'); +INSERT INTO plt3_adv SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (1, 3, 4); +ANALYZE plt3_adv; +-- This tests that when merging partitions from plt1_adv and plt2_adv in +-- merge_list_bounds(), process_outer_partition() returns an already-assigned +-- merged partition when re-called with plt1_adv_p1 for the second list value +-- '0001' of that partition +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.c = t2.c)) FULL JOIN plt3_adv t3 ON (t1.c = t3.c) WHERE coalesce(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.c, t1.a, t2.a, t3.a + -> Append + -> Hash Full Join + Hash Cond: (t1_1.c = t3_1.c) + Filter: (((COALESCE(t1_1.a, 0) % 5) <> 3) AND ((COALESCE(t1_1.a, 0) % 5) <> 4)) + -> Hash Left Join + Hash Cond: (t1_1.c = t2_1.c) + -> Seq Scan on plt1_adv_p1 t1_1 + -> Hash + -> Seq Scan on plt2_adv_p1 t2_1 + -> Hash + -> Seq Scan on plt3_adv_p1 t3_1 + -> Hash Full Join + Hash Cond: (t1_2.c = t3_2.c) + Filter: (((COALESCE(t1_2.a, 0) % 5) <> 3) AND ((COALESCE(t1_2.a, 0) % 5) <> 4)) + -> Hash Left Join + Hash Cond: (t1_2.c = t2_2.c) + -> Seq Scan on plt1_adv_p2 t1_2 + -> Hash + -> Seq Scan on plt2_adv_p2 t2_2 + -> Hash + -> Seq Scan on plt3_adv_p2 t3_2 +(23 rows) + +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.c = t2.c)) FULL JOIN plt3_adv t3 ON (t1.c = t3.c) WHERE coalesce(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; + a | c | a | c | a | c +----+------+----+------+----+------ + 0 | 0000 | | | | + 5 | 0000 | | | | + 10 | 0000 | | | | + 15 | 0000 | | | | + 20 | 0000 | | | | + 1 | 0001 | | | 1 | 0001 + 1 | 0001 | | | 6 | 0001 + 1 | 0001 | | | 11 | 0001 + 1 | 0001 | | | 16 | 0001 + 1 | 0001 | | | 21 | 0001 + 6 | 0001 | | | 1 | 0001 + 6 | 0001 | | | 6 | 0001 + 6 | 0001 | | | 11 | 0001 + 6 | 0001 | | | 16 | 0001 + 6 | 0001 | | | 21 | 0001 + 11 | 0001 | | | 1 | 0001 + 11 | 0001 | | | 6 | 0001 + 11 | 0001 | | | 11 | 0001 + 11 | 0001 | | | 16 | 0001 + 11 | 0001 | | | 21 | 0001 + 16 | 0001 | | | 1 | 0001 + 16 | 0001 | | | 6 | 0001 + 16 | 0001 | | | 11 | 0001 + 16 | 0001 | | | 16 | 0001 + 16 | 0001 | | | 21 | 0001 + 21 | 0001 | | | 1 | 0001 + 21 | 0001 | | | 6 | 0001 + 21 | 0001 | | | 11 | 0001 + 21 | 0001 | | | 16 | 0001 + 21 | 0001 | | | 21 | 0001 + 2 | 0002 | 2 | 0002 | | + 2 | 0002 | 7 | 0002 | | + 2 | 0002 | 12 | 0002 | | + 2 | 0002 | 17 | 0002 | | + 2 | 0002 | 22 | 0002 | | + 7 | 0002 | 2 | 0002 | | + 7 | 0002 | 7 | 0002 | | + 7 | 0002 | 12 | 0002 | | + 7 | 0002 | 17 | 0002 | | + 7 | 0002 | 22 | 0002 | | + 12 | 0002 | 2 | 0002 | | + 12 | 0002 | 7 | 0002 | | + 12 | 0002 | 12 | 0002 | | + 12 | 0002 | 17 | 0002 | | + 12 | 0002 | 22 | 0002 | | + 17 | 0002 | 2 | 0002 | | + 17 | 0002 | 7 | 0002 | | + 17 | 0002 | 12 | 0002 | | + 17 | 0002 | 17 | 0002 | | + 17 | 0002 | 22 | 0002 | | + 22 | 0002 | 2 | 0002 | | + 22 | 0002 | 7 | 0002 | | + 22 | 0002 | 12 | 0002 | | + 22 | 0002 | 17 | 0002 | | + 22 | 0002 | 22 | 0002 | | +(55 rows) + +DROP TABLE plt1_adv; +DROP TABLE plt2_adv; +DROP TABLE plt3_adv; +-- Tests for multi-level partitioned tables +CREATE TABLE alpha (a double precision, b int, c text) PARTITION BY RANGE (a); +CREATE TABLE alpha_neg PARTITION OF alpha FOR VALUES FROM ('-Infinity') TO (0) PARTITION BY RANGE (b); +CREATE TABLE alpha_pos PARTITION OF alpha FOR VALUES FROM (0) TO (10.0) PARTITION BY LIST (c); +CREATE TABLE alpha_neg_p1 PARTITION OF alpha_neg FOR VALUES FROM (100) TO (200); +CREATE TABLE alpha_neg_p2 PARTITION OF alpha_neg FOR VALUES FROM (200) TO (300); +CREATE TABLE alpha_neg_p3 PARTITION OF alpha_neg FOR VALUES FROM (300) TO (400); +CREATE TABLE alpha_pos_p1 PARTITION OF alpha_pos FOR VALUES IN ('0001', '0003'); +CREATE TABLE alpha_pos_p2 PARTITION OF alpha_pos FOR VALUES IN ('0004', '0006'); +CREATE TABLE alpha_pos_p3 PARTITION OF alpha_pos FOR VALUES IN ('0008', '0009'); +INSERT INTO alpha_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 399) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9); +INSERT INTO alpha_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 399) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9); +ANALYZE alpha; +CREATE TABLE beta (a double precision, b int, c text) PARTITION BY RANGE (a); +CREATE TABLE beta_neg PARTITION OF beta FOR VALUES FROM (-10.0) TO (0) PARTITION BY RANGE (b); +CREATE TABLE beta_pos PARTITION OF beta FOR VALUES FROM (0) TO ('Infinity') PARTITION BY LIST (c); +CREATE TABLE beta_neg_p1 PARTITION OF beta_neg FOR VALUES FROM (100) TO (150); +CREATE TABLE beta_neg_p2 PARTITION OF beta_neg FOR VALUES FROM (200) TO (300); +CREATE TABLE beta_neg_p3 PARTITION OF beta_neg FOR VALUES FROM (350) TO (500); +CREATE TABLE beta_pos_p1 PARTITION OF beta_pos FOR VALUES IN ('0002', '0003'); +CREATE TABLE beta_pos_p2 PARTITION OF beta_pos FOR VALUES IN ('0004', '0006'); +CREATE TABLE beta_pos_p3 PARTITION OF beta_pos FOR VALUES IN ('0007', '0009'); +INSERT INTO beta_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 149) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +INSERT INTO beta_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(200, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +INSERT INTO beta_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(350, 499) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +INSERT INTO beta_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 149) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +INSERT INTO beta_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(200, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +INSERT INTO beta_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(350, 499) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +ANALYZE beta; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b; + a | b | c | a | b | c +----+-----+------+----+-----+------ + -1 | 126 | 0006 | -1 | 126 | 0006 + -1 | 129 | 0009 | -1 | 129 | 0009 + -1 | 133 | 0003 | -1 | 133 | 0003 + -1 | 134 | 0004 | -1 | 134 | 0004 + -1 | 136 | 0006 | -1 | 136 | 0006 + -1 | 139 | 0009 | -1 | 139 | 0009 + -1 | 143 | 0003 | -1 | 143 | 0003 + -1 | 144 | 0004 | -1 | 144 | 0004 + -1 | 146 | 0006 | -1 | 146 | 0006 + -1 | 149 | 0009 | -1 | 149 | 0009 + -1 | 203 | 0003 | -1 | 203 | 0003 + -1 | 204 | 0004 | -1 | 204 | 0004 + -1 | 206 | 0006 | -1 | 206 | 0006 + -1 | 209 | 0009 | -1 | 209 | 0009 + -1 | 213 | 0003 | -1 | 213 | 0003 + -1 | 214 | 0004 | -1 | 214 | 0004 + -1 | 216 | 0006 | -1 | 216 | 0006 + -1 | 219 | 0009 | -1 | 219 | 0009 + -1 | 223 | 0003 | -1 | 223 | 0003 + -1 | 224 | 0004 | -1 | 224 | 0004 + 1 | 126 | 0006 | 1 | 126 | 0006 + 1 | 129 | 0009 | 1 | 129 | 0009 + 1 | 133 | 0003 | 1 | 133 | 0003 + 1 | 134 | 0004 | 1 | 134 | 0004 + 1 | 136 | 0006 | 1 | 136 | 0006 + 1 | 139 | 0009 | 1 | 139 | 0009 + 1 | 143 | 0003 | 1 | 143 | 0003 + 1 | 144 | 0004 | 1 | 144 | 0004 + 1 | 146 | 0006 | 1 | 146 | 0006 + 1 | 149 | 0009 | 1 | 149 | 0009 + 1 | 203 | 0003 | 1 | 203 | 0003 + 1 | 204 | 0004 | 1 | 204 | 0004 + 1 | 206 | 0006 | 1 | 206 | 0006 + 1 | 209 | 0009 | 1 | 209 | 0009 + 1 | 213 | 0003 | 1 | 213 | 0003 + 1 | 214 | 0004 | 1 | 214 | 0004 + 1 | 216 | 0006 | 1 | 216 | 0006 + 1 | 219 | 0009 | 1 | 219 | 0009 + 1 | 223 | 0003 | 1 | 223 | 0003 + 1 | 224 | 0004 | 1 | 224 | 0004 +(40 rows) + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b, t2.b; + a | b | c | a | b | c +----+-----+------+----+-----+------ + -1 | 104 | 0004 | -1 | 104 | 0004 + -1 | 104 | 0004 | -1 | 204 | 0004 + -1 | 109 | 0009 | -1 | 109 | 0009 + -1 | 109 | 0009 | -1 | 209 | 0009 + -1 | 204 | 0004 | -1 | 104 | 0004 + -1 | 204 | 0004 | -1 | 204 | 0004 + -1 | 209 | 0009 | -1 | 109 | 0009 + -1 | 209 | 0009 | -1 | 209 | 0009 + 1 | 104 | 0004 | 1 | 104 | 0004 + 1 | 104 | 0004 | 1 | 204 | 0004 + 1 | 109 | 0009 | 1 | 109 | 0009 + 1 | 109 | 0009 | 1 | 209 | 0009 + 1 | 204 | 0004 | 1 | 104 | 0004 + 1 | 204 | 0004 | 1 | 204 | 0004 + 1 | 209 | 0009 | 1 | 109 | 0009 + 1 | 209 | 0009 | 1 | 209 | 0009 +(16 rows) + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b; + a | b | c | a | b | c +----+-----+------+----+-----+------ + -1 | 104 | 0004 | -1 | 104 | 0004 + -1 | 109 | 0009 | -1 | 109 | 0009 + -1 | 204 | 0004 | -1 | 204 | 0004 + -1 | 209 | 0009 | -1 | 209 | 0009 + 1 | 104 | 0004 | 1 | 104 | 0004 + 1 | 109 | 0009 | 1 | 109 | 0009 + 1 | 204 | 0004 | 1 | 204 | 0004 + 1 | 209 | 0009 | 1 | 209 | 0009 +(8 rows) + +-- partitionwise join with fractional paths +CREATE TABLE fract_t (id BIGINT, PRIMARY KEY (id)) PARTITION BY RANGE (id); +CREATE TABLE fract_t0 PARTITION OF fract_t FOR VALUES FROM ('0') TO ('1000'); +CREATE TABLE fract_t1 PARTITION OF fract_t FOR VALUES FROM ('1000') TO ('2000'); +-- insert data +INSERT INTO fract_t (id) (SELECT generate_series(0, 1999)); +ANALYZE fract_t; +-- verify plan; nested index only scans +SET max_parallel_workers_per_gather = 0; +SET enable_partitionwise_join = on; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT * FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY id ASC LIMIT 10; + +EXPLAIN (COSTS OFF) +SELECT * FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY id DESC LIMIT 10; +*/ -- YB: The explain plan changes significantly between PG and YB +-- cleanup +DROP TABLE fract_t; +RESET max_parallel_workers_per_gather; +RESET enable_partitionwise_join; diff --git a/src/test/regress/expected/yb.port.partition_prune.out b/src/test/regress/expected/yb.port.partition_prune.out new file mode 100644 index 0000000000000..6500a39a3875a --- /dev/null +++ b/src/test/regress/expected/yb.port.partition_prune.out @@ -0,0 +1,3600 @@ +-- +-- Test partitioning planner code +-- +-- Force generic plans to be used for all prepared statements in this file. +set plan_cache_mode = force_generic_plan; +-- YB: explicitly set enable_partitionwise_aggregate to original pg default. +SET enable_partitionwise_aggregate to false; +create table lp (a char) partition by list (a); +create table lp_default partition of lp default; +create table lp_ef partition of lp for values in ('e', 'f'); +create table lp_ad partition of lp for values in ('a', 'd'); +create table lp_bc partition of lp for values in ('b', 'c'); +create table lp_g partition of lp for values in ('g'); +create table lp_null partition of lp for values in (null); +explain (costs off) select * from lp; + QUERY PLAN +----------------------------------- + Append + -> Seq Scan on lp_ad lp_1 + -> Seq Scan on lp_bc lp_2 + -> Seq Scan on lp_ef lp_3 + -> Seq Scan on lp_g lp_4 + -> Seq Scan on lp_null lp_5 + -> Seq Scan on lp_default lp_6 +(7 rows) + +explain (costs off) select * from lp where a > 'a' and a < 'd'; + QUERY PLAN +------------------------------------------------------------------- + Append + -> Seq Scan on lp_bc lp_1 + Storage Filter: ((a > 'a'::bpchar) AND (a < 'd'::bpchar)) + -> Seq Scan on lp_default lp_2 + Storage Filter: ((a > 'a'::bpchar) AND (a < 'd'::bpchar)) +(5 rows) + +explain (costs off) select * from lp where a > 'a' and a <= 'd'; + QUERY PLAN +-------------------------------------------------------------------- + Append + -> Seq Scan on lp_ad lp_1 + Storage Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar)) + -> Seq Scan on lp_bc lp_2 + Storage Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar)) + -> Seq Scan on lp_default lp_3 + Storage Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar)) +(7 rows) + +explain (costs off) select * from lp where a = 'a'; + QUERY PLAN +------------------------------------- + Seq Scan on lp_ad lp_1 + Storage Filter: (a = 'a'::bpchar) +(2 rows) + +explain (costs off) select * from lp where 'a' = a; /* commuted */ + QUERY PLAN +------------------------------------- + Seq Scan on lp_ad lp_1 + Storage Filter: ('a'::bpchar = a) +(2 rows) + +explain (costs off) select * from lp where a is not null; + QUERY PLAN +----------------------------------------- + Append + -> Seq Scan on lp_ad lp_1 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on lp_bc lp_2 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on lp_ef lp_3 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on lp_g lp_4 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on lp_default lp_5 + Storage Filter: (a IS NOT NULL) +(11 rows) + +explain (costs off) select * from lp where a is null; + QUERY PLAN +------------------------------- + Seq Scan on lp_null lp_1 + Storage Filter: (a IS NULL) +(2 rows) + +explain (costs off) select * from lp where a = 'a' or a = 'c'; + QUERY PLAN +------------------------------------------------------------------ + Append + -> Seq Scan on lp_ad lp_1 + Storage Filter: ((a = 'a'::bpchar) OR (a = 'c'::bpchar)) + -> Seq Scan on lp_bc lp_2 + Storage Filter: ((a = 'a'::bpchar) OR (a = 'c'::bpchar)) +(5 rows) + +explain (costs off) select * from lp where a is not null and (a = 'a' or a = 'c'); + QUERY PLAN +---------------------------------------------------------------------------------------- + Append + -> Seq Scan on lp_ad lp_1 + Storage Filter: ((a IS NOT NULL) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar))) + -> Seq Scan on lp_bc lp_2 + Storage Filter: ((a IS NOT NULL) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar))) +(5 rows) + +explain (costs off) select * from lp where a <> 'g'; + QUERY PLAN +-------------------------------------------- + Append + -> Seq Scan on lp_ad lp_1 + Storage Filter: (a <> 'g'::bpchar) + -> Seq Scan on lp_bc lp_2 + Storage Filter: (a <> 'g'::bpchar) + -> Seq Scan on lp_ef lp_3 + Storage Filter: (a <> 'g'::bpchar) + -> Seq Scan on lp_default lp_4 + Storage Filter: (a <> 'g'::bpchar) +(9 rows) + +explain (costs off) select * from lp where a <> 'a' and a <> 'd'; + QUERY PLAN +--------------------------------------------------------------------- + Append + -> Seq Scan on lp_bc lp_1 + Storage Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) + -> Seq Scan on lp_ef lp_2 + Storage Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) + -> Seq Scan on lp_g lp_3 + Storage Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) + -> Seq Scan on lp_default lp_4 + Storage Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) +(9 rows) + +explain (costs off) select * from lp where a not in ('a', 'd'); + QUERY PLAN +-------------------------------------------------------- + Append + -> Seq Scan on lp_bc lp_1 + Storage Filter: (a <> ALL ('{a,d}'::bpchar[])) + -> Seq Scan on lp_ef lp_2 + Storage Filter: (a <> ALL ('{a,d}'::bpchar[])) + -> Seq Scan on lp_g lp_3 + Storage Filter: (a <> ALL ('{a,d}'::bpchar[])) + -> Seq Scan on lp_default lp_4 + Storage Filter: (a <> ALL ('{a,d}'::bpchar[])) +(9 rows) + +-- collation matches the partitioning collation, pruning works +create table coll_pruning (a text collate "C") partition by list (a); +create table coll_pruning_a partition of coll_pruning for values in ('a'); +create table coll_pruning_b partition of coll_pruning for values in ('b'); +create table coll_pruning_def partition of coll_pruning default; +explain (costs off) select * from coll_pruning where a collate "C" = 'a' collate "C"; + QUERY PLAN +----------------------------------------------- + Seq Scan on coll_pruning_a coll_pruning_1 + Storage Filter: (a = 'a'::text COLLATE "C") +(2 rows) + +-- collation doesn't match the partitioning collation, no pruning occurs +explain (costs off) select * from coll_pruning where a collate "POSIX" = 'a' collate "POSIX"; + QUERY PLAN +----------------------------------------------------------------- + Append + -> Seq Scan on coll_pruning_a coll_pruning_1 + Storage Filter: ((a)::text = 'a'::text COLLATE "POSIX") + -> Seq Scan on coll_pruning_b coll_pruning_2 + Storage Filter: ((a)::text = 'a'::text COLLATE "POSIX") + -> Seq Scan on coll_pruning_def coll_pruning_3 + Storage Filter: ((a)::text = 'a'::text COLLATE "POSIX") +(7 rows) + +create table rlp (a int, b varchar) partition by range (a); +create table rlp_default partition of rlp default partition by list (a); +create table rlp_default_default partition of rlp_default default; +create table rlp_default_10 partition of rlp_default for values in (10); +create table rlp_default_30 partition of rlp_default for values in (30); +create table rlp_default_null partition of rlp_default for values in (null); +create table rlp1 partition of rlp for values from (minvalue) to (1); +create table rlp2 partition of rlp for values from (1) to (10); +create table rlp3 (b varchar, a int) partition by list (b varchar_ops); +create table rlp3_default partition of rlp3 default; +create table rlp3abcd partition of rlp3 for values in ('ab', 'cd'); +create table rlp3efgh partition of rlp3 for values in ('ef', 'gh'); +create table rlp3nullxy partition of rlp3 for values in (null, 'xy'); +alter table rlp attach partition rlp3 for values from (15) to (20); +create table rlp4 partition of rlp for values from (20) to (30) partition by range (a); +create table rlp4_default partition of rlp4 default; +create table rlp4_1 partition of rlp4 for values from (20) to (25); +create table rlp4_2 partition of rlp4 for values from (25) to (29); +create table rlp5 partition of rlp for values from (31) to (maxvalue) partition by range (a); +create table rlp5_default partition of rlp5 default; +create table rlp5_1 partition of rlp5 for values from (31) to (40); +explain (costs off) select * from rlp where a < 1; + QUERY PLAN +--------------------------- + Seq Scan on rlp1 rlp_1 + Storage Filter: (a < 1) +(2 rows) + +explain (costs off) select * from rlp where 1 > a; /* commuted */ + QUERY PLAN +--------------------------- + Seq Scan on rlp1 rlp_1 + Storage Filter: (1 > a) +(2 rows) + +explain (costs off) select * from rlp where a <= 1; + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on rlp1 rlp_1 + Storage Filter: (a <= 1) + -> Seq Scan on rlp2 rlp_2 + Storage Filter: (a <= 1) +(5 rows) + +explain (costs off) select * from rlp where a = 1; + QUERY PLAN +--------------------------- + Seq Scan on rlp2 rlp_1 + Storage Filter: (a = 1) +(2 rows) + +explain (costs off) select * from rlp where a = 1::bigint; /* same as above */ + QUERY PLAN +------------------------------------- + Seq Scan on rlp2 rlp_1 + Storage Filter: (a = '1'::bigint) +(2 rows) + +explain (costs off) select * from rlp where a = 1::numeric; /* no pruning */ + QUERY PLAN +------------------------------------------------------- + Append + -> Seq Scan on rlp1 rlp_1 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp2 rlp_2 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp3abcd rlp_4 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp3efgh rlp_5 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp3nullxy rlp_6 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp3_default rlp_7 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp4_1 rlp_9 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp4_2 rlp_10 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp4_default rlp_11 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp5_1 rlp_13 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp5_default rlp_14 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp_default_10 rlp_16 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp_default_30 rlp_17 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp_default_null rlp_18 + Storage Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlp_default_default rlp_19 + Storage Filter: ((a)::numeric = '1'::numeric) +(31 rows) + +explain (costs off) select * from rlp where a <= 10; + QUERY PLAN +--------------------------------------------- + Append + -> Seq Scan on rlp1 rlp_1 + Storage Filter: (a <= 10) + -> Seq Scan on rlp2 rlp_2 + Storage Filter: (a <= 10) + -> Seq Scan on rlp_default_10 rlp_4 + Storage Filter: (a <= 10) + -> Seq Scan on rlp_default_default rlp_5 + Storage Filter: (a <= 10) +(9 rows) + +explain (costs off) select * from rlp where a > 10; + QUERY PLAN +---------------------------------------------- + Append + -> Seq Scan on rlp3abcd rlp_2 + Storage Filter: (a > 10) + -> Seq Scan on rlp3efgh rlp_3 + Storage Filter: (a > 10) + -> Seq Scan on rlp3nullxy rlp_4 + Storage Filter: (a > 10) + -> Seq Scan on rlp3_default rlp_5 + Storage Filter: (a > 10) + -> Seq Scan on rlp4_1 rlp_7 + Storage Filter: (a > 10) + -> Seq Scan on rlp4_2 rlp_8 + Storage Filter: (a > 10) + -> Seq Scan on rlp4_default rlp_9 + Storage Filter: (a > 10) + -> Seq Scan on rlp5_1 rlp_11 + Storage Filter: (a > 10) + -> Seq Scan on rlp5_default rlp_12 + Storage Filter: (a > 10) + -> Seq Scan on rlp_default_30 rlp_14 + Storage Filter: (a > 10) + -> Seq Scan on rlp_default_default rlp_15 + Storage Filter: (a > 10) +(23 rows) + +explain (costs off) select * from rlp where a < 15; + QUERY PLAN +--------------------------------------------- + Append + -> Seq Scan on rlp1 rlp_1 + Storage Filter: (a < 15) + -> Seq Scan on rlp2 rlp_2 + Storage Filter: (a < 15) + -> Seq Scan on rlp_default_10 rlp_4 + Storage Filter: (a < 15) + -> Seq Scan on rlp_default_default rlp_5 + Storage Filter: (a < 15) +(9 rows) + +explain (costs off) select * from rlp where a <= 15; + QUERY PLAN +---------------------------------------------- + Append + -> Seq Scan on rlp1 rlp_1 + Storage Filter: (a <= 15) + -> Seq Scan on rlp2 rlp_2 + Storage Filter: (a <= 15) + -> Seq Scan on rlp3abcd rlp_4 + Storage Filter: (a <= 15) + -> Seq Scan on rlp3efgh rlp_5 + Storage Filter: (a <= 15) + -> Seq Scan on rlp3nullxy rlp_6 + Storage Filter: (a <= 15) + -> Seq Scan on rlp3_default rlp_7 + Storage Filter: (a <= 15) + -> Seq Scan on rlp_default_10 rlp_9 + Storage Filter: (a <= 15) + -> Seq Scan on rlp_default_default rlp_10 + Storage Filter: (a <= 15) +(17 rows) + +explain (costs off) select * from rlp where a > 15 and b = 'ab'; + QUERY PLAN +----------------------------------------------------------------- + Append + -> Seq Scan on rlp3abcd rlp_2 + Storage Filter: ((a > 15) AND ((b)::text = 'ab'::text)) + -> Seq Scan on rlp4_1 rlp_4 + Storage Filter: ((a > 15) AND ((b)::text = 'ab'::text)) + -> Seq Scan on rlp4_2 rlp_5 + Storage Filter: ((a > 15) AND ((b)::text = 'ab'::text)) + -> Seq Scan on rlp4_default rlp_6 + Storage Filter: ((a > 15) AND ((b)::text = 'ab'::text)) + -> Seq Scan on rlp5_1 rlp_8 + Storage Filter: ((a > 15) AND ((b)::text = 'ab'::text)) + -> Seq Scan on rlp5_default rlp_9 + Storage Filter: ((a > 15) AND ((b)::text = 'ab'::text)) + -> Seq Scan on rlp_default_30 rlp_11 + Storage Filter: ((a > 15) AND ((b)::text = 'ab'::text)) + -> Seq Scan on rlp_default_default rlp_12 + Storage Filter: ((a > 15) AND ((b)::text = 'ab'::text)) +(17 rows) + +explain (costs off) select * from rlp where a = 16; + QUERY PLAN +-------------------------------------- + Append + -> Seq Scan on rlp3abcd rlp_2 + Storage Filter: (a = 16) + -> Seq Scan on rlp3efgh rlp_3 + Storage Filter: (a = 16) + -> Seq Scan on rlp3nullxy rlp_4 + Storage Filter: (a = 16) + -> Seq Scan on rlp3_default rlp_5 + Storage Filter: (a = 16) +(9 rows) + +explain (costs off) select * from rlp where a = 16 and b in ('not', 'in', 'here'); + QUERY PLAN +------------------------------------------------------------------------------ + Seq Scan on rlp3_default rlp_2 + Storage Filter: ((a = 16) AND ((b)::text = ANY ('{not,in,here}'::text[]))) +(2 rows) + +explain (costs off) select * from rlp where a = 16 and b < 'ab'; + QUERY PLAN +----------------------------------------------------------- + Seq Scan on rlp3_default rlp_2 + Storage Filter: (((b)::text < 'ab'::text) AND (a = 16)) +(2 rows) + +explain (costs off) select * from rlp where a = 16 and b <= 'ab'; + QUERY PLAN +------------------------------------------------------------------ + Append + -> Seq Scan on rlp3abcd rlp_2 + Storage Filter: (((b)::text <= 'ab'::text) AND (a = 16)) + -> Seq Scan on rlp3_default rlp_3 + Storage Filter: (((b)::text <= 'ab'::text) AND (a = 16)) +(5 rows) + +explain (costs off) select * from rlp where a = 16 and b is null; + QUERY PLAN +---------------------------------------------- + Seq Scan on rlp3nullxy rlp_2 + Storage Filter: ((b IS NULL) AND (a = 16)) +(2 rows) + +explain (costs off) select * from rlp where a = 16 and b is not null; + QUERY PLAN +-------------------------------------------------------- + Append + -> Seq Scan on rlp3abcd rlp_2 + Storage Filter: ((b IS NOT NULL) AND (a = 16)) + -> Seq Scan on rlp3efgh rlp_3 + Storage Filter: ((b IS NOT NULL) AND (a = 16)) + -> Seq Scan on rlp3nullxy rlp_4 + Storage Filter: ((b IS NOT NULL) AND (a = 16)) + -> Seq Scan on rlp3_default rlp_5 + Storage Filter: ((b IS NOT NULL) AND (a = 16)) +(9 rows) + +explain (costs off) select * from rlp where a is null; + QUERY PLAN +------------------------------------ + Seq Scan on rlp_default_null rlp_2 + Storage Filter: (a IS NULL) +(2 rows) + +explain (costs off) select * from rlp where a is not null; + QUERY PLAN +---------------------------------------------- + Append + -> Seq Scan on rlp1 rlp_1 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp2 rlp_2 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp3abcd rlp_4 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp3efgh rlp_5 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp3nullxy rlp_6 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp3_default rlp_7 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp4_1 rlp_9 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp4_2 rlp_10 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp4_default rlp_11 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp5_1 rlp_13 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp5_default rlp_14 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp_default_10 rlp_16 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp_default_30 rlp_17 + Storage Filter: (a IS NOT NULL) + -> Seq Scan on rlp_default_default rlp_18 + Storage Filter: (a IS NOT NULL) +(29 rows) + +explain (costs off) select * from rlp where a > 30; + QUERY PLAN +--------------------------------------------- + Append + -> Seq Scan on rlp5_1 rlp_2 + Storage Filter: (a > 30) + -> Seq Scan on rlp5_default rlp_3 + Storage Filter: (a > 30) + -> Seq Scan on rlp_default_default rlp_5 + Storage Filter: (a > 30) +(7 rows) + +explain (costs off) select * from rlp where a = 30; /* only default is scanned */ + QUERY PLAN +---------------------------------- + Seq Scan on rlp_default_30 rlp_2 + Storage Filter: (a = 30) +(2 rows) + +explain (costs off) select * from rlp where a <= 31; + QUERY PLAN +---------------------------------------------- + Append + -> Seq Scan on rlp1 rlp_1 + Storage Filter: (a <= 31) + -> Seq Scan on rlp2 rlp_2 + Storage Filter: (a <= 31) + -> Seq Scan on rlp3abcd rlp_4 + Storage Filter: (a <= 31) + -> Seq Scan on rlp3efgh rlp_5 + Storage Filter: (a <= 31) + -> Seq Scan on rlp3nullxy rlp_6 + Storage Filter: (a <= 31) + -> Seq Scan on rlp3_default rlp_7 + Storage Filter: (a <= 31) + -> Seq Scan on rlp4_1 rlp_9 + Storage Filter: (a <= 31) + -> Seq Scan on rlp4_2 rlp_10 + Storage Filter: (a <= 31) + -> Seq Scan on rlp4_default rlp_11 + Storage Filter: (a <= 31) + -> Seq Scan on rlp5_1 rlp_13 + Storage Filter: (a <= 31) + -> Seq Scan on rlp_default_10 rlp_15 + Storage Filter: (a <= 31) + -> Seq Scan on rlp_default_30 rlp_16 + Storage Filter: (a <= 31) + -> Seq Scan on rlp_default_default rlp_17 + Storage Filter: (a <= 31) +(27 rows) + +explain (costs off) select * from rlp where a = 1 or a = 7; + QUERY PLAN +---------------------------------------- + Seq Scan on rlp2 rlp_1 + Storage Filter: ((a = 1) OR (a = 7)) +(2 rows) + +explain (costs off) select * from rlp where a = 1 or b = 'ab'; + QUERY PLAN +--------------------------------------------------------------- + Append + -> Seq Scan on rlp1 rlp_1 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp2 rlp_2 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp3abcd rlp_4 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp4_1 rlp_6 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp4_2 rlp_7 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp4_default rlp_8 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp5_1 rlp_10 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp5_default rlp_11 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp_default_10 rlp_13 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp_default_30 rlp_14 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp_default_null rlp_15 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp_default_default rlp_16 + Storage Filter: ((a = 1) OR ((b)::text = 'ab'::text)) +(25 rows) + +explain (costs off) select * from rlp where a > 20 and a < 27; + QUERY PLAN +------------------------------------------------- + Append + -> Seq Scan on rlp4_1 rlp_2 + Storage Filter: ((a > 20) AND (a < 27)) + -> Seq Scan on rlp4_2 rlp_3 + Storage Filter: ((a > 20) AND (a < 27)) +(5 rows) + +explain (costs off) select * from rlp where a = 29; + QUERY PLAN +-------------------------------- + Seq Scan on rlp4_default rlp_2 + Storage Filter: (a = 29) +(2 rows) + +explain (costs off) select * from rlp where a >= 29; + QUERY PLAN +--------------------------------------------- + Append + -> Seq Scan on rlp4_default rlp_2 + Storage Filter: (a >= 29) + -> Seq Scan on rlp5_1 rlp_4 + Storage Filter: (a >= 29) + -> Seq Scan on rlp5_default rlp_5 + Storage Filter: (a >= 29) + -> Seq Scan on rlp_default_30 rlp_7 + Storage Filter: (a >= 29) + -> Seq Scan on rlp_default_default rlp_8 + Storage Filter: (a >= 29) +(11 rows) + +explain (costs off) select * from rlp where a < 1 or (a > 20 and a < 25); + QUERY PLAN +-------------------------------------------------------------- + Append + -> Seq Scan on rlp1 rlp_1 + Storage Filter: ((a < 1) OR ((a > 20) AND (a < 25))) + -> Seq Scan on rlp4_1 rlp_3 + Storage Filter: ((a < 1) OR ((a > 20) AND (a < 25))) +(5 rows) + +-- where clause contradicts sub-partition's constraint +explain (costs off) select * from rlp where a = 20 or a = 40; + QUERY PLAN +------------------------------------------------ + Append + -> Seq Scan on rlp4_1 rlp_2 + Storage Filter: ((a = 20) OR (a = 40)) + -> Seq Scan on rlp5_default rlp_4 + Storage Filter: ((a = 20) OR (a = 40)) +(5 rows) + +explain (costs off) select * from rlp3 where a = 20; /* empty */ + QUERY PLAN +-------------------------- + Result rlp3 + One-Time Filter: false +(2 rows) + +-- redundant clauses are eliminated +explain (costs off) select * from rlp where a > 1 and a = 10; /* only default */ + QUERY PLAN +------------------------------------------ + Seq Scan on rlp_default_10 rlp_2 + Storage Filter: ((a > 1) AND (a = 10)) +(2 rows) + +explain (costs off) select * from rlp where a > 1 and a >=15; /* rlp3 onwards, including default */ + QUERY PLAN +------------------------------------------------- + Append + -> Seq Scan on rlp3abcd rlp_2 + Storage Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp3efgh rlp_3 + Storage Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp3nullxy rlp_4 + Storage Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp3_default rlp_5 + Storage Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp4_1 rlp_7 + Storage Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp4_2 rlp_8 + Storage Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp4_default rlp_9 + Storage Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp5_1 rlp_11 + Storage Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp5_default rlp_12 + Storage Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp_default_30 rlp_14 + Storage Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp_default_default rlp_15 + Storage Filter: ((a > 1) AND (a >= 15)) +(23 rows) + +explain (costs off) select * from rlp where a = 1 and a = 3; /* empty */ + QUERY PLAN +-------------------------- + Result rlp + One-Time Filter: false +(2 rows) + +explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = 15); + QUERY PLAN +--------------------------------------------------------------------------- + Append + -> Seq Scan on rlp2 rlp_1 + Storage Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) + -> Seq Scan on rlp3abcd rlp_3 + Storage Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) + -> Seq Scan on rlp3efgh rlp_4 + Storage Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) + -> Seq Scan on rlp3nullxy rlp_5 + Storage Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) + -> Seq Scan on rlp3_default rlp_6 + Storage Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) +(11 rows) + +-- multi-column keys +create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +create table mc3p_default partition of mc3p default; +create table mc3p0 partition of mc3p for values from (minvalue, minvalue, minvalue) to (1, 1, 1); +create table mc3p1 partition of mc3p for values from (1, 1, 1) to (10, 5, 10); +create table mc3p2 partition of mc3p for values from (10, 5, 10) to (10, 10, 10); +create table mc3p3 partition of mc3p for values from (10, 10, 10) to (10, 10, 20); +create table mc3p4 partition of mc3p for values from (10, 10, 20) to (10, maxvalue, maxvalue); +create table mc3p5 partition of mc3p for values from (11, 1, 1) to (20, 10, 10); +create table mc3p6 partition of mc3p for values from (20, 10, 10) to (20, 20, 20); +create table mc3p7 partition of mc3p for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue); +explain (costs off) select * from mc3p where a = 1; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on mc3p0 mc3p_1 + Storage Filter: (a = 1) + -> Seq Scan on mc3p1 mc3p_2 + Storage Filter: (a = 1) + -> Seq Scan on mc3p_default mc3p_3 + Storage Filter: (a = 1) +(7 rows) + +explain (costs off) select * from mc3p where a = 1 and abs(b) < 1; + QUERY PLAN +---------------------------------------------------- + Append + -> Seq Scan on mc3p0 mc3p_1 + Storage Filter: ((a = 1) AND (abs(b) < 1)) + -> Seq Scan on mc3p_default mc3p_2 + Storage Filter: ((a = 1) AND (abs(b) < 1)) +(5 rows) + +explain (costs off) select * from mc3p where a = 1 and abs(b) = 1; + QUERY PLAN +---------------------------------------------------- + Append + -> Seq Scan on mc3p0 mc3p_1 + Storage Filter: ((a = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p1 mc3p_2 + Storage Filter: ((a = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p_default mc3p_3 + Storage Filter: ((a = 1) AND (abs(b) = 1)) +(7 rows) + +explain (costs off) select * from mc3p where a = 1 and abs(b) = 1 and c < 8; + QUERY PLAN +---------------------------------------------------------------- + Append + -> Seq Scan on mc3p0 mc3p_1 + Storage Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p1 mc3p_2 + Storage Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1)) +(5 rows) + +explain (costs off) select * from mc3p where a = 10 and abs(b) between 5 and 35; + QUERY PLAN +------------------------------------------------------------------------- + Append + -> Seq Scan on mc3p1 mc3p_1 + Storage Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) + -> Seq Scan on mc3p2 mc3p_2 + Storage Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) + -> Seq Scan on mc3p3 mc3p_3 + Storage Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) + -> Seq Scan on mc3p4 mc3p_4 + Storage Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) + -> Seq Scan on mc3p_default mc3p_5 + Storage Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) +(11 rows) + +explain (costs off) select * from mc3p where a > 10; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on mc3p5 mc3p_1 + Storage Filter: (a > 10) + -> Seq Scan on mc3p6 mc3p_2 + Storage Filter: (a > 10) + -> Seq Scan on mc3p7 mc3p_3 + Storage Filter: (a > 10) + -> Seq Scan on mc3p_default mc3p_4 + Storage Filter: (a > 10) +(9 rows) + +explain (costs off) select * from mc3p where a >= 10; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on mc3p1 mc3p_1 + Storage Filter: (a >= 10) + -> Seq Scan on mc3p2 mc3p_2 + Storage Filter: (a >= 10) + -> Seq Scan on mc3p3 mc3p_3 + Storage Filter: (a >= 10) + -> Seq Scan on mc3p4 mc3p_4 + Storage Filter: (a >= 10) + -> Seq Scan on mc3p5 mc3p_5 + Storage Filter: (a >= 10) + -> Seq Scan on mc3p6 mc3p_6 + Storage Filter: (a >= 10) + -> Seq Scan on mc3p7 mc3p_7 + Storage Filter: (a >= 10) + -> Seq Scan on mc3p_default mc3p_8 + Storage Filter: (a >= 10) +(17 rows) + +explain (costs off) select * from mc3p where a < 10; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on mc3p0 mc3p_1 + Storage Filter: (a < 10) + -> Seq Scan on mc3p1 mc3p_2 + Storage Filter: (a < 10) + -> Seq Scan on mc3p_default mc3p_3 + Storage Filter: (a < 10) +(7 rows) + +explain (costs off) select * from mc3p where a <= 10 and abs(b) < 10; + QUERY PLAN +------------------------------------------------------- + Append + -> Seq Scan on mc3p0 mc3p_1 + Storage Filter: ((a <= 10) AND (abs(b) < 10)) + -> Seq Scan on mc3p1 mc3p_2 + Storage Filter: ((a <= 10) AND (abs(b) < 10)) + -> Seq Scan on mc3p2 mc3p_3 + Storage Filter: ((a <= 10) AND (abs(b) < 10)) + -> Seq Scan on mc3p_default mc3p_4 + Storage Filter: ((a <= 10) AND (abs(b) < 10)) +(9 rows) + +explain (costs off) select * from mc3p where a = 11 and abs(b) = 0; + QUERY PLAN +----------------------------------------------- + Seq Scan on mc3p_default mc3p_1 + Storage Filter: ((a = 11) AND (abs(b) = 0)) +(2 rows) + +explain (costs off) select * from mc3p where a = 20 and abs(b) = 10 and c = 100; + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on mc3p6 mc3p_1 + Storage Filter: ((a = 20) AND (c = 100) AND (abs(b) = 10)) +(2 rows) + +explain (costs off) select * from mc3p where a > 20; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on mc3p7 mc3p_1 + Storage Filter: (a > 20) + -> Seq Scan on mc3p_default mc3p_2 + Storage Filter: (a > 20) +(5 rows) + +explain (costs off) select * from mc3p where a >= 20; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on mc3p5 mc3p_1 + Storage Filter: (a >= 20) + -> Seq Scan on mc3p6 mc3p_2 + Storage Filter: (a >= 20) + -> Seq Scan on mc3p7 mc3p_3 + Storage Filter: (a >= 20) + -> Seq Scan on mc3p_default mc3p_4 + Storage Filter: (a >= 20) +(9 rows) + +explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------- + Append + -> Seq Scan on mc3p1 mc3p_1 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) + -> Seq Scan on mc3p2 mc3p_2 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) + -> Seq Scan on mc3p5 mc3p_3 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) + -> Seq Scan on mc3p_default mc3p_4 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20))) +(9 rows) + +explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20) or a < 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Append + -> Seq Scan on mc3p0 mc3p_1 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) + -> Seq Scan on mc3p1 mc3p_2 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) + -> Seq Scan on mc3p2 mc3p_3 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) + -> Seq Scan on mc3p5 mc3p_4 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) + -> Seq Scan on mc3p_default mc3p_5 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1)) +(11 rows) + +explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20) or a < 1 or a = 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + Append + -> Seq Scan on mc3p0 mc3p_1 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) + -> Seq Scan on mc3p1 mc3p_2 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) + -> Seq Scan on mc3p2 mc3p_3 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) + -> Seq Scan on mc3p5 mc3p_4 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) + -> Seq Scan on mc3p_default mc3p_5 + Storage Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1)) +(11 rows) + +explain (costs off) select * from mc3p where a = 1 or abs(b) = 1 or c = 1; + QUERY PLAN +-------------------------------------------------------------- + Append + -> Seq Scan on mc3p0 mc3p_1 + Storage Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) + -> Seq Scan on mc3p1 mc3p_2 + Storage Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) + -> Seq Scan on mc3p2 mc3p_3 + Storage Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) + -> Seq Scan on mc3p3 mc3p_4 + Storage Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) + -> Seq Scan on mc3p4 mc3p_5 + Storage Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) + -> Seq Scan on mc3p5 mc3p_6 + Storage Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) + -> Seq Scan on mc3p6 mc3p_7 + Storage Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) + -> Seq Scan on mc3p7 mc3p_8 + Storage Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) + -> Seq Scan on mc3p_default mc3p_9 + Storage Filter: ((a = 1) OR (abs(b) = 1) OR (c = 1)) +(19 rows) + +explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 and abs(b) = 10); + QUERY PLAN +-------------------------------------------------------------------------------------- + Append + -> Seq Scan on mc3p0 mc3p_1 + Storage Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) + -> Seq Scan on mc3p1 mc3p_2 + Storage Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) + -> Seq Scan on mc3p2 mc3p_3 + Storage Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) + -> Seq Scan on mc3p3 mc3p_4 + Storage Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) + -> Seq Scan on mc3p4 mc3p_5 + Storage Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) + -> Seq Scan on mc3p_default mc3p_6 + Storage Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10))) +(13 rows) + +explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 and abs(b) = 9); + QUERY PLAN +------------------------------------------------------------------------------------- + Append + -> Seq Scan on mc3p0 mc3p_1 + Storage Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) + -> Seq Scan on mc3p1 mc3p_2 + Storage Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) + -> Seq Scan on mc3p2 mc3p_3 + Storage Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) + -> Seq Scan on mc3p_default mc3p_4 + Storage Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9))) +(9 rows) + +-- a simpler multi-column keys case +create table mc2p (a int, b int) partition by range (a, b); +create table mc2p_default partition of mc2p default; +create table mc2p0 partition of mc2p for values from (minvalue, minvalue) to (1, minvalue); +create table mc2p1 partition of mc2p for values from (1, minvalue) to (1, 1); +create table mc2p2 partition of mc2p for values from (1, 1) to (2, minvalue); +create table mc2p3 partition of mc2p for values from (2, minvalue) to (2, 1); +create table mc2p4 partition of mc2p for values from (2, 1) to (2, maxvalue); +create table mc2p5 partition of mc2p for values from (2, maxvalue) to (maxvalue, maxvalue); +explain (costs off) select * from mc2p where a < 2; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on mc2p0 mc2p_1 + Storage Filter: (a < 2) + -> Seq Scan on mc2p1 mc2p_2 + Storage Filter: (a < 2) + -> Seq Scan on mc2p2 mc2p_3 + Storage Filter: (a < 2) + -> Seq Scan on mc2p_default mc2p_4 + Storage Filter: (a < 2) +(9 rows) + +explain (costs off) select * from mc2p where a = 2 and b < 1; + QUERY PLAN +----------------------------------------- + Seq Scan on mc2p3 mc2p_1 + Storage Filter: ((b < 1) AND (a = 2)) +(2 rows) + +explain (costs off) select * from mc2p where a > 1; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on mc2p2 mc2p_1 + Storage Filter: (a > 1) + -> Seq Scan on mc2p3 mc2p_2 + Storage Filter: (a > 1) + -> Seq Scan on mc2p4 mc2p_3 + Storage Filter: (a > 1) + -> Seq Scan on mc2p5 mc2p_4 + Storage Filter: (a > 1) + -> Seq Scan on mc2p_default mc2p_5 + Storage Filter: (a > 1) +(11 rows) + +explain (costs off) select * from mc2p where a = 1 and b > 1; + QUERY PLAN +----------------------------------------- + Seq Scan on mc2p2 mc2p_1 + Storage Filter: ((b > 1) AND (a = 1)) +(2 rows) + +-- all partitions but the default one should be pruned +explain (costs off) select * from mc2p where a = 1 and b is null; + QUERY PLAN +--------------------------------------------- + Seq Scan on mc2p_default mc2p_1 + Storage Filter: ((b IS NULL) AND (a = 1)) +(2 rows) + +explain (costs off) select * from mc2p where a is null and b is null; + QUERY PLAN +------------------------------------------------- + Seq Scan on mc2p_default mc2p_1 + Storage Filter: ((a IS NULL) AND (b IS NULL)) +(2 rows) + +explain (costs off) select * from mc2p where a is null and b = 1; + QUERY PLAN +--------------------------------------------- + Seq Scan on mc2p_default mc2p_1 + Storage Filter: ((a IS NULL) AND (b = 1)) +(2 rows) + +explain (costs off) select * from mc2p where a is null; + QUERY PLAN +--------------------------------- + Seq Scan on mc2p_default mc2p_1 + Storage Filter: (a IS NULL) +(2 rows) + +explain (costs off) select * from mc2p where b is null; + QUERY PLAN +--------------------------------- + Seq Scan on mc2p_default mc2p_1 + Storage Filter: (b IS NULL) +(2 rows) + +-- boolean partitioning +create table boolpart (a bool) partition by list (a); +create table boolpart_default partition of boolpart default; +create table boolpart_t partition of boolpart for values in ('true'); +create table boolpart_f partition of boolpart for values in ('false'); +explain (costs off) select * from boolpart where a in (true, false); + QUERY PLAN +-------------------------------------------------------- + Append + -> Seq Scan on boolpart_f boolpart_1 + Storage Filter: (a = ANY ('{t,f}'::boolean[])) + -> Seq Scan on boolpart_t boolpart_2 + Storage Filter: (a = ANY ('{t,f}'::boolean[])) +(5 rows) + +explain (costs off) select * from boolpart where a = false; + QUERY PLAN +----------------------------------- + Seq Scan on boolpart_f boolpart_1 + Storage Filter: (NOT a) +(2 rows) + +explain (costs off) select * from boolpart where not a = false; + QUERY PLAN +----------------------------------- + Seq Scan on boolpart_t boolpart_1 + Storage Filter: a +(2 rows) + +explain (costs off) select * from boolpart where a is true or a is not true; + QUERY PLAN +-------------------------------------------------- + Append + -> Seq Scan on boolpart_f boolpart_1 + Filter: ((a IS TRUE) OR (a IS NOT TRUE)) + -> Seq Scan on boolpart_t boolpart_2 + Filter: ((a IS TRUE) OR (a IS NOT TRUE)) + -> Seq Scan on boolpart_default boolpart_3 + Filter: ((a IS TRUE) OR (a IS NOT TRUE)) +(7 rows) + +explain (costs off) select * from boolpart where a is not true; + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on boolpart_f boolpart_1 + Filter: (a IS NOT TRUE) + -> Seq Scan on boolpart_default boolpart_2 + Filter: (a IS NOT TRUE) +(5 rows) + +explain (costs off) select * from boolpart where a is not true and a is not false; + QUERY PLAN +-------------------------------------------------- + Seq Scan on boolpart_default boolpart_1 + Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) +(2 rows) + +explain (costs off) select * from boolpart where a is unknown; + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on boolpart_f boolpart_1 + Filter: (a IS UNKNOWN) + -> Seq Scan on boolpart_t boolpart_2 + Filter: (a IS UNKNOWN) + -> Seq Scan on boolpart_default boolpart_3 + Filter: (a IS UNKNOWN) +(7 rows) + +explain (costs off) select * from boolpart where a is not unknown; + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on boolpart_f boolpart_1 + Filter: (a IS NOT UNKNOWN) + -> Seq Scan on boolpart_t boolpart_2 + Filter: (a IS NOT UNKNOWN) + -> Seq Scan on boolpart_default boolpart_3 + Filter: (a IS NOT UNKNOWN) +(7 rows) + +create table boolrangep (a bool, b bool, c int) partition by range (a,b,c); +create table boolrangep_tf partition of boolrangep for values from ('true', 'false', 0) to ('true', 'false', 100); +create table boolrangep_ft partition of boolrangep for values from ('false', 'true', 0) to ('false', 'true', 100); +create table boolrangep_ff1 partition of boolrangep for values from ('false', 'false', 0) to ('false', 'false', 50); +create table boolrangep_ff2 partition of boolrangep for values from ('false', 'false', 50) to ('false', 'false', 100); +-- try a more complex case that's been known to trip up pruning in the past +explain (costs off) select * from boolrangep where not a and not b and c = 25; + QUERY PLAN +------------------------------------------------------ + Seq Scan on boolrangep_ff1 boolrangep_1 + Storage Filter: ((NOT a) AND (NOT b) AND (c = 25)) +(2 rows) + +-- test scalar-to-array operators +create table coercepart (a varchar) partition by list (a); +create table coercepart_ab partition of coercepart for values in ('ab'); +create table coercepart_bc partition of coercepart for values in ('bc'); +create table coercepart_cd partition of coercepart for values in ('cd'); +explain (costs off) select * from coercepart where a in ('ab', to_char(125, '999')); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------ + Append + -> Seq Scan on coercepart_ab coercepart_1 + Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[])) + -> Seq Scan on coercepart_bc coercepart_2 + Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[])) + -> Seq Scan on coercepart_cd coercepart_3 + Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[])) +(7 rows) + +explain (costs off) select * from coercepart where a ~ any ('{ab}'); + QUERY PLAN +------------------------------------------------------------ + Append + -> Seq Scan on coercepart_ab coercepart_1 + Storage Filter: ((a)::text ~ ANY ('{ab}'::text[])) + -> Seq Scan on coercepart_bc coercepart_2 + Storage Filter: ((a)::text ~ ANY ('{ab}'::text[])) + -> Seq Scan on coercepart_cd coercepart_3 + Storage Filter: ((a)::text ~ ANY ('{ab}'::text[])) +(7 rows) + +explain (costs off) select * from coercepart where a !~ all ('{ab}'); + QUERY PLAN +------------------------------------------------------------- + Append + -> Seq Scan on coercepart_ab coercepart_1 + Storage Filter: ((a)::text !~ ALL ('{ab}'::text[])) + -> Seq Scan on coercepart_bc coercepart_2 + Storage Filter: ((a)::text !~ ALL ('{ab}'::text[])) + -> Seq Scan on coercepart_cd coercepart_3 + Storage Filter: ((a)::text !~ ALL ('{ab}'::text[])) +(7 rows) + +explain (costs off) select * from coercepart where a ~ any ('{ab,bc}'); + QUERY PLAN +--------------------------------------------------------------- + Append + -> Seq Scan on coercepart_ab coercepart_1 + Storage Filter: ((a)::text ~ ANY ('{ab,bc}'::text[])) + -> Seq Scan on coercepart_bc coercepart_2 + Storage Filter: ((a)::text ~ ANY ('{ab,bc}'::text[])) + -> Seq Scan on coercepart_cd coercepart_3 + Storage Filter: ((a)::text ~ ANY ('{ab,bc}'::text[])) +(7 rows) + +explain (costs off) select * from coercepart where a !~ all ('{ab,bc}'); + QUERY PLAN +---------------------------------------------------------------- + Append + -> Seq Scan on coercepart_ab coercepart_1 + Storage Filter: ((a)::text !~ ALL ('{ab,bc}'::text[])) + -> Seq Scan on coercepart_bc coercepart_2 + Storage Filter: ((a)::text !~ ALL ('{ab,bc}'::text[])) + -> Seq Scan on coercepart_cd coercepart_3 + Storage Filter: ((a)::text !~ ALL ('{ab,bc}'::text[])) +(7 rows) + +explain (costs off) select * from coercepart where a = any ('{ab,bc}'); + QUERY PLAN +--------------------------------------------------------------- + Append + -> Seq Scan on coercepart_ab coercepart_1 + Storage Filter: ((a)::text = ANY ('{ab,bc}'::text[])) + -> Seq Scan on coercepart_bc coercepart_2 + Storage Filter: ((a)::text = ANY ('{ab,bc}'::text[])) +(5 rows) + +explain (costs off) select * from coercepart where a = any ('{ab,null}'); + QUERY PLAN +----------------------------------------------------------- + Seq Scan on coercepart_ab coercepart_1 + Storage Filter: ((a)::text = ANY ('{ab,NULL}'::text[])) +(2 rows) + +explain (costs off) select * from coercepart where a = any (null::text[]); + QUERY PLAN +-------------------------- + Result coercepart + One-Time Filter: false +(2 rows) + +explain (costs off) select * from coercepart where a = all ('{ab}'); + QUERY PLAN +------------------------------------------------------ + Seq Scan on coercepart_ab coercepart_1 + Storage Filter: ((a)::text = ALL ('{ab}'::text[])) +(2 rows) + +explain (costs off) select * from coercepart where a = all ('{ab,bc}'); + QUERY PLAN +-------------------------- + Result coercepart + One-Time Filter: false +(2 rows) + +explain (costs off) select * from coercepart where a = all ('{ab,null}'); + QUERY PLAN +-------------------------- + Result coercepart + One-Time Filter: false +(2 rows) + +explain (costs off) select * from coercepart where a = all (null::text[]); + QUERY PLAN +-------------------------- + Result coercepart + One-Time Filter: false +(2 rows) + +drop table coercepart; +CREATE TABLE part (a INT, b INT) PARTITION BY LIST (a); +CREATE TABLE part_p1 PARTITION OF part FOR VALUES IN (-2,-1,0,1,2); +CREATE TABLE part_p2 PARTITION OF part DEFAULT PARTITION BY RANGE(a); +CREATE TABLE part_p2_p1 PARTITION OF part_p2 DEFAULT; +CREATE TABLE part_rev (b INT, c INT, a INT); +ALTER TABLE part ATTACH PARTITION part_rev FOR VALUES IN (3); -- fail +ERROR: table "part_rev" contains column "c" not found in parent "part" +DETAIL: The new partition may contain only the columns present in parent. +ALTER TABLE part_rev DROP COLUMN c; +ALTER TABLE part ATTACH PARTITION part_rev FOR VALUES IN (3); -- now it's ok +INSERT INTO part VALUES (-1,-1), (1,1), (2,NULL), (NULL,-2),(NULL,NULL); +EXPLAIN (COSTS OFF) SELECT tableoid::regclass as part, a, b FROM part WHERE a IS NULL ORDER BY 1, 2, 3; + QUERY PLAN +--------------------------------------------------------------- + Sort + Sort Key: ((part_2.tableoid)::regclass), part_2.a, part_2.b + -> Seq Scan on part_p2_p1 part_2 + Storage Filter: (a IS NULL) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM part p(x) ORDER BY x; + QUERY PLAN +----------------------------------------------- + Sort + Output: p.x, p.b + Sort Key: p.x + -> Append + -> Seq Scan on public.part_p1 p_1 + Output: p_1.x, p_1.b + -> Seq Scan on public.part_rev p_2 + Output: p_2.x, p_2.b + -> Seq Scan on public.part_p2_p1 p_4 + Output: p_4.x, p_4.b +(10 rows) + +-- +-- some more cases +-- +-- +-- pruning for partitioned table appearing inside a sub-query +-- +-- pruning won't work for mc3p, because some keys are Params +explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = t1.b and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1; + QUERY PLAN +------------------------------------------------------------------------------- + Nested Loop + -> Append + -> Seq Scan on mc2p1 t1_1 + Storage Filter: (a = 1) + -> Seq Scan on mc2p2 t1_2 + Storage Filter: (a = 1) + -> Seq Scan on mc2p_default t1_3 + Storage Filter: (a = 1) + -> Aggregate + -> Append + -> Seq Scan on mc3p0 t2_1 + Storage Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p1 t2_2 + Storage Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p2 t2_3 + Storage Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p3 t2_4 + Storage Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p4 t2_5 + Storage Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p5 t2_6 + Storage Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p6 t2_7 + Storage Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p7 t2_8 + Storage Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p_default t2_9 + Storage Filter: ((a = t1.b) AND (c = 1) AND (abs(b) = 1)) +(28 rows) + +-- pruning should work fine, because values for a prefix of keys (a, b) are +-- available +explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.c = t1.b and abs(t2.b) = 1 and t2.a = 1) s where t1.a = 1; + QUERY PLAN +------------------------------------------------------------------------------- + Nested Loop + -> Append + -> Seq Scan on mc2p1 t1_1 + Storage Filter: (a = 1) + -> Seq Scan on mc2p2 t1_2 + Storage Filter: (a = 1) + -> Seq Scan on mc2p_default t1_3 + Storage Filter: (a = 1) + -> Aggregate + -> Append + -> Seq Scan on mc3p0 t2_1 + Storage Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p1 t2_2 + Storage Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p_default t2_3 + Storage Filter: ((c = t1.b) AND (a = 1) AND (abs(b) = 1)) +(16 rows) + +-- also here, because values for all keys are provided +explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = 1 and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1; + QUERY PLAN +---------------------------------------------------------------------- + Nested Loop + -> Finalize Aggregate + -> Seq Scan on mc3p1 t2_1 + Storage Filter: ((a = 1) AND (c = 1) AND (abs(b) = 1)) + Partial Aggregate: true + -> Append + -> Seq Scan on mc2p1 t1_1 + Storage Filter: (a = 1) + -> Seq Scan on mc2p2 t1_2 + Storage Filter: (a = 1) + -> Seq Scan on mc2p_default t1_3 + Storage Filter: (a = 1) +(12 rows) + +-- +-- pruning with clauses containing <> operator +-- +-- doesn't prune range partitions +create table rp (a int) partition by range (a); +create table rp0 partition of rp for values from (minvalue) to (1); +create table rp1 partition of rp for values from (1) to (2); +create table rp2 partition of rp for values from (2) to (maxvalue); +explain (costs off) select * from rp where a <> 1; + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on rp0 rp_1 + Storage Filter: (a <> 1) + -> Seq Scan on rp1 rp_2 + Storage Filter: (a <> 1) + -> Seq Scan on rp2 rp_3 + Storage Filter: (a <> 1) +(7 rows) + +explain (costs off) select * from rp where a <> 1 and a <> 2; + QUERY PLAN +------------------------------------------------- + Append + -> Seq Scan on rp0 rp_1 + Storage Filter: ((a <> 1) AND (a <> 2)) + -> Seq Scan on rp1 rp_2 + Storage Filter: ((a <> 1) AND (a <> 2)) + -> Seq Scan on rp2 rp_3 + Storage Filter: ((a <> 1) AND (a <> 2)) +(7 rows) + +-- null partition should be eliminated due to strict <> clause. +explain (costs off) select * from lp where a <> 'a'; + QUERY PLAN +-------------------------------------------- + Append + -> Seq Scan on lp_ad lp_1 + Storage Filter: (a <> 'a'::bpchar) + -> Seq Scan on lp_bc lp_2 + Storage Filter: (a <> 'a'::bpchar) + -> Seq Scan on lp_ef lp_3 + Storage Filter: (a <> 'a'::bpchar) + -> Seq Scan on lp_g lp_4 + Storage Filter: (a <> 'a'::bpchar) + -> Seq Scan on lp_default lp_5 + Storage Filter: (a <> 'a'::bpchar) +(11 rows) + +-- ensure we detect contradictions in clauses; a can't be NULL and NOT NULL. +explain (costs off) select * from lp where a <> 'a' and a is null; + QUERY PLAN +-------------------------- + Result lp + One-Time Filter: false +(2 rows) + +explain (costs off) select * from lp where (a <> 'a' and a <> 'd') or a is null; + QUERY PLAN +-------------------------------------------------------------------------------------- + Append + -> Seq Scan on lp_bc lp_1 + Storage Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) + -> Seq Scan on lp_ef lp_2 + Storage Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) + -> Seq Scan on lp_g lp_3 + Storage Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) + -> Seq Scan on lp_null lp_4 + Storage Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) + -> Seq Scan on lp_default lp_5 + Storage Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) +(11 rows) + +-- check that it also works for a partitioned table that's not root, +-- which in this case are partitions of rlp that are themselves +-- list-partitioned on b +explain (costs off) select * from rlp where a = 15 and b <> 'ab' and b <> 'cd' and b <> 'xy' and b is not null; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------- + Append + -> Seq Scan on rlp3efgh rlp_2 + Storage Filter: ((b IS NOT NULL) AND ((b)::text <> 'ab'::text) AND ((b)::text <> 'cd'::text) AND ((b)::text <> 'xy'::text) AND (a = 15)) + -> Seq Scan on rlp3_default rlp_3 + Storage Filter: ((b IS NOT NULL) AND ((b)::text <> 'ab'::text) AND ((b)::text <> 'cd'::text) AND ((b)::text <> 'xy'::text) AND (a = 15)) +(5 rows) + +-- +-- different collations for different keys with same expression +-- +create table coll_pruning_multi (a text) partition by range (substr(a, 1) collate "POSIX", substr(a, 1) collate "C"); +create table coll_pruning_multi1 partition of coll_pruning_multi for values from ('a', 'a') to ('a', 'e'); +create table coll_pruning_multi2 partition of coll_pruning_multi for values from ('a', 'e') to ('a', 'z'); +create table coll_pruning_multi3 partition of coll_pruning_multi for values from ('b', 'a') to ('b', 'e'); +-- no pruning, because no value for the leading key +explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' collate "C"; + QUERY PLAN +---------------------------------------------------------------- + Append + -> Seq Scan on coll_pruning_multi1 coll_pruning_multi_1 + Storage Filter: (substr(a, 1) = 'e'::text COLLATE "C") + -> Seq Scan on coll_pruning_multi2 coll_pruning_multi_2 + Storage Filter: (substr(a, 1) = 'e'::text COLLATE "C") + -> Seq Scan on coll_pruning_multi3 coll_pruning_multi_3 + Storage Filter: (substr(a, 1) = 'e'::text COLLATE "C") +(7 rows) + +-- pruning, with a value provided for the leading key +explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'a' collate "POSIX"; + QUERY PLAN +-------------------------------------------------------------------- + Append + -> Seq Scan on coll_pruning_multi1 coll_pruning_multi_1 + Storage Filter: (substr(a, 1) = 'a'::text COLLATE "POSIX") + -> Seq Scan on coll_pruning_multi2 coll_pruning_multi_2 + Storage Filter: (substr(a, 1) = 'a'::text COLLATE "POSIX") +(5 rows) + +-- pruning, with values provided for both keys +explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' collate "C" and substr(a, 1) = 'a' collate "POSIX"; + QUERY PLAN +----------------------------------------------------------------------------------------------------------- + Seq Scan on coll_pruning_multi2 coll_pruning_multi_1 + Storage Filter: ((substr(a, 1) = 'e'::text COLLATE "C") AND (substr(a, 1) = 'a'::text COLLATE "POSIX")) +(2 rows) + +-- +-- LIKE operators don't prune +-- +create table like_op_noprune (a text) partition by list (a); +create table like_op_noprune1 partition of like_op_noprune for values in ('ABC'); +create table like_op_noprune2 partition of like_op_noprune for values in ('BCD'); +explain (costs off) select * from like_op_noprune where a like '%BC'; + QUERY PLAN +------------------------------------------------------ + Append + -> Seq Scan on like_op_noprune1 like_op_noprune_1 + Storage Filter: (a ~~ '%BC'::text) + -> Seq Scan on like_op_noprune2 like_op_noprune_2 + Storage Filter: (a ~~ '%BC'::text) +(5 rows) + +-- +-- tests wherein clause value requires a cross-type comparison function +-- +create table lparted_by_int2 (a smallint) partition by list (a); +create table lparted_by_int2_1 partition of lparted_by_int2 for values in (1); +create table lparted_by_int2_16384 partition of lparted_by_int2 for values in (16384); +explain (costs off) select * from lparted_by_int2 where a = 100000000000000; + QUERY PLAN +-------------------------- + Result lparted_by_int2 + One-Time Filter: false +(2 rows) + +create table rparted_by_int2 (a smallint) partition by range (a); +create table rparted_by_int2_1 partition of rparted_by_int2 for values from (1) to (10); +create table rparted_by_int2_16384 partition of rparted_by_int2 for values from (10) to (16384); +-- all partitions pruned +explain (costs off) select * from rparted_by_int2 where a > 100000000000000; + QUERY PLAN +-------------------------- + Result rparted_by_int2 + One-Time Filter: false +(2 rows) + +create table rparted_by_int2_maxvalue partition of rparted_by_int2 for values from (16384) to (maxvalue); +-- all partitions but rparted_by_int2_maxvalue pruned +explain (costs off) select * from rparted_by_int2 where a > 100000000000000; + QUERY PLAN +-------------------------------------------------------- + Seq Scan on rparted_by_int2_maxvalue rparted_by_int2_1 + Storage Filter: (a > '100000000000000'::bigint) +(2 rows) + +drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, boolrangep, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2; +-- +-- Test Partition pruning for HASH partitioning +-- +-- Use hand-rolled hash functions and operator classes to get predictable +-- result on different machines. See the definitions of +-- part_part_test_int4_ops and part_test_text_ops in insert.sql. +-- +create or replace function part_hashint4_noop(value int4, seed int8) +returns int8 as $$ +select value + seed; +$$ language sql immutable; +create operator class part_test_int4_ops +for type int4 +using hash as +operator 1 =, +function 2 part_hashint4_noop(int4, int8); +create or replace function part_hashtext_length(value text, seed int8) +RETURNS int8 AS $$ +select length(coalesce(value, ''))::int8 +$$ language sql immutable; +create operator class part_test_text_ops +for type text +using hash as +operator 1 =, +function 2 part_hashtext_length(text, int8); +create table hp (a int, b text, c int) + partition by hash (a part_test_int4_ops, b part_test_text_ops); +create table hp0 partition of hp for values with (modulus 4, remainder 0); +create table hp3 partition of hp for values with (modulus 4, remainder 3); +create table hp1 partition of hp for values with (modulus 4, remainder 1); +create table hp2 partition of hp for values with (modulus 4, remainder 2); +insert into hp values (null, null, 0); +insert into hp values (1, null, 1); +insert into hp values (1, 'xxx', 2); +insert into hp values (null, 'xxx', 3); +insert into hp values (2, 'xxx', 4); +insert into hp values (1, 'abcde', 5); +select tableoid::regclass, * from hp order by c; + tableoid | a | b | c +----------+---+-------+--- + hp0 | | | 0 + hp1 | 1 | | 1 + hp0 | 1 | xxx | 2 + hp2 | | xxx | 3 + hp3 | 2 | xxx | 4 + hp2 | 1 | abcde | 5 +(6 rows) + +-- partial keys won't prune, nor would non-equality conditions +explain (costs off) select * from hp where a = 1; + QUERY PLAN +--------------------------------- + Append + -> Seq Scan on hp0 hp_1 + Storage Filter: (a = 1) + -> Seq Scan on hp1 hp_2 + Storage Filter: (a = 1) + -> Seq Scan on hp2 hp_3 + Storage Filter: (a = 1) + -> Seq Scan on hp3 hp_4 + Storage Filter: (a = 1) +(9 rows) + +explain (costs off) select * from hp where b = 'xxx'; + QUERY PLAN +------------------------------------------- + Append + -> Seq Scan on hp0 hp_1 + Storage Filter: (b = 'xxx'::text) + -> Seq Scan on hp1 hp_2 + Storage Filter: (b = 'xxx'::text) + -> Seq Scan on hp2 hp_3 + Storage Filter: (b = 'xxx'::text) + -> Seq Scan on hp3 hp_4 + Storage Filter: (b = 'xxx'::text) +(9 rows) + +explain (costs off) select * from hp where a is null; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on hp0 hp_1 + Storage Filter: (a IS NULL) + -> Seq Scan on hp1 hp_2 + Storage Filter: (a IS NULL) + -> Seq Scan on hp2 hp_3 + Storage Filter: (a IS NULL) + -> Seq Scan on hp3 hp_4 + Storage Filter: (a IS NULL) +(9 rows) + +explain (costs off) select * from hp where b is null; + QUERY PLAN +------------------------------------- + Append + -> Seq Scan on hp0 hp_1 + Storage Filter: (b IS NULL) + -> Seq Scan on hp1 hp_2 + Storage Filter: (b IS NULL) + -> Seq Scan on hp2 hp_3 + Storage Filter: (b IS NULL) + -> Seq Scan on hp3 hp_4 + Storage Filter: (b IS NULL) +(9 rows) + +explain (costs off) select * from hp where a < 1 and b = 'xxx'; + QUERY PLAN +--------------------------------------------------------- + Append + -> Seq Scan on hp0 hp_1 + Storage Filter: ((a < 1) AND (b = 'xxx'::text)) + -> Seq Scan on hp1 hp_2 + Storage Filter: ((a < 1) AND (b = 'xxx'::text)) + -> Seq Scan on hp2 hp_3 + Storage Filter: ((a < 1) AND (b = 'xxx'::text)) + -> Seq Scan on hp3 hp_4 + Storage Filter: ((a < 1) AND (b = 'xxx'::text)) +(9 rows) + +explain (costs off) select * from hp where a <> 1 and b = 'yyy'; + QUERY PLAN +---------------------------------------------------------- + Append + -> Seq Scan on hp0 hp_1 + Storage Filter: ((a <> 1) AND (b = 'yyy'::text)) + -> Seq Scan on hp1 hp_2 + Storage Filter: ((a <> 1) AND (b = 'yyy'::text)) + -> Seq Scan on hp2 hp_3 + Storage Filter: ((a <> 1) AND (b = 'yyy'::text)) + -> Seq Scan on hp3 hp_4 + Storage Filter: ((a <> 1) AND (b = 'yyy'::text)) +(9 rows) + +explain (costs off) select * from hp where a <> 1 and b <> 'xxx'; + QUERY PLAN +----------------------------------------------------------- + Append + -> Seq Scan on hp0 hp_1 + Storage Filter: ((a <> 1) AND (b <> 'xxx'::text)) + -> Seq Scan on hp1 hp_2 + Storage Filter: ((a <> 1) AND (b <> 'xxx'::text)) + -> Seq Scan on hp2 hp_3 + Storage Filter: ((a <> 1) AND (b <> 'xxx'::text)) + -> Seq Scan on hp3 hp_4 + Storage Filter: ((a <> 1) AND (b <> 'xxx'::text)) +(9 rows) + +-- pruning should work if either a value or a IS NULL clause is provided for +-- each of the keys +explain (costs off) select * from hp where a is null and b is null; + QUERY PLAN +------------------------------------------------- + Seq Scan on hp0 hp_1 + Storage Filter: ((a IS NULL) AND (b IS NULL)) +(2 rows) + +explain (costs off) select * from hp where a = 1 and b is null; + QUERY PLAN +--------------------------------------------- + Seq Scan on hp1 hp_1 + Storage Filter: ((b IS NULL) AND (a = 1)) +(2 rows) + +explain (costs off) select * from hp where a = 1 and b = 'xxx'; + QUERY PLAN +--------------------------------------------------- + Seq Scan on hp0 hp_1 + Storage Filter: ((a = 1) AND (b = 'xxx'::text)) +(2 rows) + +explain (costs off) select * from hp where a is null and b = 'xxx'; + QUERY PLAN +------------------------------------------------------- + Seq Scan on hp2 hp_1 + Storage Filter: ((a IS NULL) AND (b = 'xxx'::text)) +(2 rows) + +explain (costs off) select * from hp where a = 2 and b = 'xxx'; + QUERY PLAN +--------------------------------------------------- + Seq Scan on hp3 hp_1 + Storage Filter: ((a = 2) AND (b = 'xxx'::text)) +(2 rows) + +explain (costs off) select * from hp where a = 1 and b = 'abcde'; + QUERY PLAN +----------------------------------------------------- + Seq Scan on hp2 hp_1 + Storage Filter: ((a = 1) AND (b = 'abcde'::text)) +(2 rows) + +explain (costs off) select * from hp where (a = 1 and b = 'abcde') or (a = 2 and b = 'xxx') or (a is null and b is null); + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------- + Append + -> Seq Scan on hp0 hp_1 + Storage Filter: (((a = 1) AND (b = 'abcde'::text)) OR ((a = 2) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL))) + -> Seq Scan on hp2 hp_2 + Storage Filter: (((a = 1) AND (b = 'abcde'::text)) OR ((a = 2) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL))) + -> Seq Scan on hp3 hp_3 + Storage Filter: (((a = 1) AND (b = 'abcde'::text)) OR ((a = 2) AND (b = 'xxx'::text)) OR ((a IS NULL) AND (b IS NULL))) +(7 rows) + +-- test pruning when not all the partitions exist +drop table hp1; +drop table hp3; +explain (costs off) select * from hp where a = 1 and b = 'abcde'; + QUERY PLAN +----------------------------------------------------- + Seq Scan on hp2 hp_1 + Storage Filter: ((a = 1) AND (b = 'abcde'::text)) +(2 rows) + +explain (costs off) select * from hp where a = 1 and b = 'abcde' and + (c = 2 or c = 3); + QUERY PLAN +------------------------------------------------------------------------------ + Seq Scan on hp2 hp_1 + Storage Filter: ((a = 1) AND (b = 'abcde'::text) AND ((c = 2) OR (c = 3))) +(2 rows) + +drop table hp2; +explain (costs off) select * from hp where a = 1 and b = 'abcde' and + (c = 2 or c = 3); + QUERY PLAN +-------------------------- + Result hp + One-Time Filter: false +(2 rows) + +drop table hp; +-- +-- Test runtime partition pruning +-- +create table ab (a int not null, b int not null) partition by list (a); +create table ab_a2 partition of ab for values in(2) partition by list (b); +create table ab_a2_b1 partition of ab_a2 for values in (1); +create table ab_a2_b2 partition of ab_a2 for values in (2); +create table ab_a2_b3 partition of ab_a2 for values in (3); +create table ab_a1 partition of ab for values in(1) partition by list (b); +create table ab_a1_b1 partition of ab_a1 for values in (1); +create table ab_a1_b2 partition of ab_a1 for values in (2); +create table ab_a1_b3 partition of ab_a1 for values in (3); +create table ab_a3 partition of ab for values in(3) partition by list (b); +create table ab_a3_b1 partition of ab_a3 for values in (1); +create table ab_a3_b2 partition of ab_a3 for values in (2); +create table ab_a3_b3 partition of ab_a3 for values in (3); +-- Disallow index only scans as concurrent transactions may stop visibility +-- bits being set causing "Heap Fetches" to be unstable in the EXPLAIN ANALYZE +-- output. +set enable_indexonlyscan = off; +prepare ab_q1 (int, int, int) as +select * from ab where a between $1 and $2 and b <= $3; +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2, 3); + QUERY PLAN +----------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 6 + -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a2_b3 ab_3 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) +(8 rows) + +explain (analyze, costs off, summary off, timing off) execute ab_q1 (1, 2, 3); + QUERY PLAN +----------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 3 + -> Seq Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a1_b2 ab_2 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a2_b1 ab_4 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a2_b2 ab_5 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a2_b3 ab_6 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) +(14 rows) + +deallocate ab_q1; +-- Runtime pruning after optimizer pruning +prepare ab_q1 (int, int) as +select a from ab where a between $1 and $2 and b < 3; +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2); + QUERY PLAN +--------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 4 + -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) +(6 rows) + +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 4); + QUERY PLAN +--------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 2 + -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a3_b1 ab_3 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a3_b2 ab_4 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) +(10 rows) + +-- Ensure a mix of PARAM_EXTERN and PARAM_EXEC Params work together at +-- different levels of partitioning. +prepare ab_q2 (int, int) as +select a from ab where a between $1 and $2 and b < (select 3); +explain (analyze, costs off, summary off, timing off) execute ab_q2 (2, 2); + QUERY PLAN +---------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 6 + InitPlan 1 (returns $0) + -> Result *RESULT* (actual rows=1 loops=1) + -> Seq Scan on ab_a2_b1 ab_1 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b < $0)) + -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b < $0)) + -> Seq Scan on ab_a2_b3 ab_3 (never executed) + Storage Filter: ((a >= $1) AND (a <= $2) AND (b < $0)) +(10 rows) + +-- As above, but swap the PARAM_EXEC Param to the first partition level +prepare ab_q3 (int, int) as +select a from ab where b between $1 and $2 and a < (select 3); +explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2); + QUERY PLAN +---------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 6 + InitPlan 1 (returns $0) + -> Result *RESULT* (actual rows=1 loops=1) + -> Seq Scan on ab_a1_b2 ab_1 (actual rows=0 loops=1) + Storage Filter: ((b >= $1) AND (b <= $2) AND (a < $0)) + -> Seq Scan on ab_a2_b2 ab_2 (actual rows=0 loops=1) + Storage Filter: ((b >= $1) AND (b <= $2) AND (a < $0)) + -> Seq Scan on ab_a3_b2 ab_3 (never executed) + Storage Filter: ((b >= $1) AND (b <= $2) AND (a < $0)) +(10 rows) + +-- Test a backwards Append scan +create table list_part (a int) partition by list (a); +create table list_part1 partition of list_part for values in (1); +create table list_part2 partition of list_part for values in (2); +create table list_part3 partition of list_part for values in (3); +create table list_part4 partition of list_part for values in (4); +insert into list_part select generate_series(1,4); +begin; +-- Don't select an actual value out of the table as the order of the Append's +-- subnodes may not be stable. +declare cur SCROLL CURSOR for select 1 from list_part where a > (select 1) and a < (select 4); +/* YB: cannot fetch backward +-- move beyond the final row +move 3 from cur; +-- Ensure we get two rows. +fetch backward all from cur; +*/ -- YB: cannot fetch backward +commit; +begin; +-- Test run-time pruning using stable functions +create function list_part_fn(int) returns int as $$ begin return $1; end;$$ language plpgsql stable; +-- Ensure pruning works using a stable function containing no Vars +explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1); + QUERY PLAN +------------------------------------------------------------------ + Append (actual rows=1 loops=1) + Subplans Removed: 3 + -> Seq Scan on list_part1 list_part_1 (actual rows=1 loops=1) + Filter: (a = list_part_fn(1)) +(4 rows) + +-- Ensure pruning does not take place when the function has a Var parameter +explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(a); + QUERY PLAN +------------------------------------------------------------------ + Append (actual rows=4 loops=1) + -> Seq Scan on list_part1 list_part_1 (actual rows=1 loops=1) + Filter: (a = list_part_fn(a)) + -> Seq Scan on list_part2 list_part_2 (actual rows=1 loops=1) + Filter: (a = list_part_fn(a)) + -> Seq Scan on list_part3 list_part_3 (actual rows=1 loops=1) + Filter: (a = list_part_fn(a)) + -> Seq Scan on list_part4 list_part_4 (actual rows=1 loops=1) + Filter: (a = list_part_fn(a)) +(9 rows) + +-- Ensure pruning does not take place when the expression contains a Var. +explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1) + a; + QUERY PLAN +------------------------------------------------------------------ + Append (actual rows=0 loops=1) + -> Seq Scan on list_part1 list_part_1 (actual rows=0 loops=1) + Filter: (a = (list_part_fn(1) + a)) + Rows Removed by Filter: 1 + -> Seq Scan on list_part2 list_part_2 (actual rows=0 loops=1) + Filter: (a = (list_part_fn(1) + a)) + Rows Removed by Filter: 1 + -> Seq Scan on list_part3 list_part_3 (actual rows=0 loops=1) + Filter: (a = (list_part_fn(1) + a)) + Rows Removed by Filter: 1 + -> Seq Scan on list_part4 list_part_4 (actual rows=0 loops=1) + Filter: (a = (list_part_fn(1) + a)) + Rows Removed by Filter: 1 +(13 rows) + +rollback; +drop table list_part; +-- Parallel append +-- Parallel queries won't necessarily get as many workers as the planner +-- asked for. This affects not only the "Workers Launched:" field of EXPLAIN +-- results, but also row counts and loop counts for parallel scans, Gathers, +-- and everything in between. This function filters out the values we can't +-- rely on to be stable. +-- This removes enough info that you might wonder why bother with EXPLAIN +-- ANALYZE at all. The answer is that we need to see '(never executed)' +-- notations because that's the only way to verify runtime pruning. +create function explain_parallel_append(text) returns setof text +language plpgsql as +$$ +declare + ln text; +begin + /* YB: parallelism is hard to force. Just ignore the plan entirely. + for ln in + execute format('explain (analyze, costs off, summary off, timing off) %s', + $1) + loop + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + ln := regexp_replace(ln, 'Rows Removed by Filter: \d+', 'Rows Removed by Filter: N'); + return next ln; + end loop; + */ -- YB: parallelism is hard to force. Just ignore the plan entirely. +end; +$$; +prepare ab_q4 (int, int) as +select avg(a) from ab where a between $1 and $2 and b < 4; +-- 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 = 2; +select explain_parallel_append('execute ab_q4 (2, 2)'); + explain_parallel_append +------------------------- +(0 rows) + +-- Test run-time pruning with IN lists. +prepare ab_q5 (int, int, int) as +select avg(a) from ab where a in($1,$2,$3) and b < 4; +select explain_parallel_append('execute ab_q5 (1, 1, 1)'); + explain_parallel_append +------------------------- +(0 rows) + +select explain_parallel_append('execute ab_q5 (2, 3, 3)'); + explain_parallel_append +------------------------- +(0 rows) + +-- Try some params whose values do not belong to any partition. +select explain_parallel_append('execute ab_q5 (33, 44, 55)'); + explain_parallel_append +------------------------- +(0 rows) + +-- Test Parallel Append with PARAM_EXEC Params +select explain_parallel_append('select count(*) from ab where (a = (select 1) or a = (select 3)) and b = 2'); + explain_parallel_append +------------------------- +(0 rows) + +-- Test pruning during parallel nested loop query +create table lprt_a (a int not null); +-- Insert some values we won't find in ab +insert into lprt_a select 0 from generate_series(1,100); +-- and insert some values that we should find. +insert into lprt_a values(1),(1); +analyze lprt_a; +create index ab_a2_b1_a_idx on ab_a2_b1 (a); +create index ab_a2_b2_a_idx on ab_a2_b2 (a); +create index ab_a2_b3_a_idx on ab_a2_b3 (a); +create index ab_a1_b1_a_idx on ab_a1_b1 (a); +create index ab_a1_b2_a_idx on ab_a1_b2 (a); +create index ab_a1_b3_a_idx on ab_a1_b3 (a); +create index ab_a3_b1_a_idx on ab_a3_b1 (a); +create index ab_a3_b2_a_idx on ab_a3_b2 (a); +create index ab_a3_b3_a_idx on ab_a3_b3 (a); +set enable_hashjoin = 0; +set enable_mergejoin = 0; +set enable_memoize = 0; +select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(0, 0, 1)'); + explain_parallel_append +------------------------- +(0 rows) + +-- Ensure the same partitions are pruned when we make the nested loop +-- parameter an Expr rather than a plain Param. +select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a + 0 where a.a in(0, 0, 1)'); + explain_parallel_append +------------------------- +(0 rows) + +insert into lprt_a values(3),(3); +select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 3)'); + explain_parallel_append +------------------------- +(0 rows) + +select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)'); + explain_parallel_append +------------------------- +(0 rows) + +delete from lprt_a where a = 1; +select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)'); + explain_parallel_append +------------------------- +(0 rows) + +reset enable_hashjoin; +reset enable_mergejoin; +reset enable_memoize; +reset parallel_setup_cost; +reset parallel_tuple_cost; +reset min_parallel_table_scan_size; +reset max_parallel_workers_per_gather; +set enable_indexscan = false; -- YB: try to force bitmap scans for the following plans +set yb_enable_bitmapscan = true; -- YB: try to force bitmap scans for the following plans +-- Test run-time partition pruning with an initplan +explain (analyze, costs off, summary off, timing off) +select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1 from lprt_a); + QUERY PLAN +------------------------------------------------------------------------- + Append (actual rows=0 loops=1) + InitPlan 1 (returns $0) + -> Finalize Aggregate (actual rows=1 loops=1) + -> Seq Scan on lprt_a (actual rows=3 loops=1) + Partial Aggregate: true + InitPlan 2 (returns $1) + -> Finalize Aggregate (actual rows=1 loops=1) + -> Seq Scan on lprt_a lprt_a_1 (actual rows=3 loops=1) + Partial Aggregate: true + -> YB Bitmap Table Scan on ab_a1_b1 ab_2 (never executed) + Storage Recheck Cond: (a = $0) + Storage Filter: (b = $1) + -> Bitmap Index Scan on ab_a1_b1_a_idx (never executed) + Index Cond: (a = $0) + -> YB Bitmap Table Scan on ab_a1_b2 ab_3 (never executed) + Storage Recheck Cond: (a = $0) + Storage Filter: (b = $1) + -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed) + Index Cond: (a = $0) + -> YB Bitmap Table Scan on ab_a1_b3 ab_4 (never executed) + Storage Recheck Cond: (a = $0) + Storage Filter: (b = $1) + -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) + Index Cond: (a = $0) + -> YB Bitmap Table Scan on ab_a2_b1 ab_6 (never executed) + Storage Recheck Cond: (a = $0) + Storage Filter: (b = $1) + -> Bitmap Index Scan on ab_a2_b1_a_idx (never executed) + Index Cond: (a = $0) + -> YB Bitmap Table Scan on ab_a2_b2 ab_7 (never executed) + Storage Recheck Cond: (a = $0) + Storage Filter: (b = $1) + -> Bitmap Index Scan on ab_a2_b2_a_idx (never executed) + Index Cond: (a = $0) + -> YB Bitmap Table Scan on ab_a2_b3 ab_8 (never executed) + Storage Recheck Cond: (a = $0) + Storage Filter: (b = $1) + -> Bitmap Index Scan on ab_a2_b3_a_idx (never executed) + Index Cond: (a = $0) + -> YB Bitmap Table Scan on ab_a3_b1 ab_10 (never executed) + Storage Recheck Cond: (a = $0) + Storage Filter: (b = $1) + -> Bitmap Index Scan on ab_a3_b1_a_idx (never executed) + Index Cond: (a = $0) + -> YB Bitmap Table Scan on ab_a3_b2 ab_11 (actual rows=0 loops=1) + Storage Recheck Cond: (a = $0) + Storage Filter: (b = $1) + -> Bitmap Index Scan on ab_a3_b2_a_idx (actual rows=0 loops=1) + Index Cond: (a = $0) + -> YB Bitmap Table Scan on ab_a3_b3 ab_12 (never executed) + Storage Recheck Cond: (a = $0) + Storage Filter: (b = $1) + -> Bitmap Index Scan on ab_a3_b3_a_idx (never executed) + Index Cond: (a = $0) +(54 rows) + +-- Test run-time partition pruning with UNION ALL parents +explain (analyze, costs off, summary off, timing off) +select * from (select * from ab where a = 1 union all select * from ab) ab where b = (select 1); + QUERY PLAN +------------------------------------------------------------------------------- + Append (actual rows=0 loops=1) + InitPlan 1 (returns $0) + -> Result *RESULT* (actual rows=1 loops=1) + -> Append (actual rows=0 loops=1) + -> YB Bitmap Table Scan on ab_a1_b1 ab_16 (actual rows=0 loops=1) + Storage Filter: (b = $0) + -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) + Index Cond: (a = 1) + -> YB Bitmap Table Scan on ab_a1_b2 ab_17 (never executed) + Storage Filter: (b = $0) + -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed) + Index Cond: (a = 1) + -> YB Bitmap Table Scan on ab_a1_b3 ab_18 (never executed) + Storage Filter: (b = $0) + -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) + Index Cond: (a = 1) + -> Seq Scan on ab_a1_b1 ab_3 (actual rows=0 loops=1) + Storage Filter: (b = $0) + -> Seq Scan on ab_a1_b2 ab_4 (never executed) + Storage Filter: (b = $0) + -> Seq Scan on ab_a1_b3 ab_5 (never executed) + Storage Filter: (b = $0) + -> Seq Scan on ab_a2_b1 ab_7 (actual rows=0 loops=1) + Storage Filter: (b = $0) + -> Seq Scan on ab_a2_b2 ab_8 (never executed) + Storage Filter: (b = $0) + -> Seq Scan on ab_a2_b3 ab_9 (never executed) + Storage Filter: (b = $0) + -> Seq Scan on ab_a3_b1 ab_11 (actual rows=0 loops=1) + Storage Filter: (b = $0) + -> Seq Scan on ab_a3_b2 ab_12 (never executed) + Storage Filter: (b = $0) + -> Seq Scan on ab_a3_b3 ab_13 (never executed) + Storage Filter: (b = $0) +(34 rows) + +-- A case containing a UNION ALL with a non-partitioned child. +explain (analyze, costs off, summary off, timing off) +select * from (select * from ab where a = 1 union all (values(10,5)) union all select * from ab) ab where b = (select 1); + QUERY PLAN +------------------------------------------------------------------------------- + Append (actual rows=0 loops=1) + InitPlan 1 (returns $0) + -> Result *RESULT* (actual rows=1 loops=1) + -> Append (actual rows=0 loops=1) + -> YB Bitmap Table Scan on ab_a1_b1 ab_16 (actual rows=0 loops=1) + Storage Filter: (b = $0) + -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) + Index Cond: (a = 1) + -> YB Bitmap Table Scan on ab_a1_b2 ab_17 (never executed) + Storage Filter: (b = $0) + -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed) + Index Cond: (a = 1) + -> YB Bitmap Table Scan on ab_a1_b3 ab_18 (never executed) + Storage Filter: (b = $0) + -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) + Index Cond: (a = 1) + -> Result (actual rows=0 loops=1) + One-Time Filter: (5 = $0) + -> Seq Scan on ab_a1_b1 ab_3 (actual rows=0 loops=1) + Storage Filter: (b = $0) + -> Seq Scan on ab_a1_b2 ab_4 (never executed) + Storage Filter: (b = $0) + -> Seq Scan on ab_a1_b3 ab_5 (never executed) + Storage Filter: (b = $0) + -> Seq Scan on ab_a2_b1 ab_7 (actual rows=0 loops=1) + Storage Filter: (b = $0) + -> Seq Scan on ab_a2_b2 ab_8 (never executed) + Storage Filter: (b = $0) + -> Seq Scan on ab_a2_b3 ab_9 (never executed) + Storage Filter: (b = $0) + -> Seq Scan on ab_a3_b1 ab_11 (actual rows=0 loops=1) + Storage Filter: (b = $0) + -> Seq Scan on ab_a3_b2 ab_12 (never executed) + Storage Filter: (b = $0) + -> Seq Scan on ab_a3_b3 ab_13 (never executed) + Storage Filter: (b = $0) +(36 rows) + +-- Another UNION ALL test, but containing a mix of exec init and exec run-time pruning. +create table xy_1 (x int, y int); +insert into xy_1 values(100,-10); +set enable_bitmapscan = 0; +set enable_indexscan = 0; +prepare ab_q6 as +select * from ( + select tableoid::regclass,a,b from ab +union all + select tableoid::regclass,x,y from xy_1 +union all + select tableoid::regclass,a,b from ab +) ab where a = $1 and b = (select -10); +-- Ensure the xy_1 subplan is not pruned. +explain (analyze, costs off, summary off, timing off) execute ab_q6(1); + QUERY PLAN +-------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 12 + InitPlan 1 (returns $0) + -> Result *RESULT* (actual rows=1 loops=1) + -> Seq Scan on ab_a1_b1 ab_1 (never executed) + Storage Filter: ((a = $1) AND (b = $0)) + -> Seq Scan on ab_a1_b2 ab_2 (never executed) + Storage Filter: ((a = $1) AND (b = $0)) + -> Seq Scan on ab_a1_b3 ab_3 (never executed) + Storage Filter: ((a = $1) AND (b = $0)) + -> Seq Scan on xy_1 (actual rows=0 loops=1) + Storage Filter: ((x = $1) AND (y = $0)) + -> Seq Scan on ab_a1_b1 ab_4 (never executed) + Storage Filter: ((a = $1) AND (b = $0)) + -> Seq Scan on ab_a1_b2 ab_5 (never executed) + Storage Filter: ((a = $1) AND (b = $0)) + -> Seq Scan on ab_a1_b3 ab_6 (never executed) + Storage Filter: ((a = $1) AND (b = $0)) +(18 rows) + +-- Ensure we see just the xy_1 row. +execute ab_q6(100); + tableoid | a | b +----------+-----+----- + xy_1 | 100 | -10 +(1 row) + +reset enable_bitmapscan; +-- reset enable_indexscan; -- YB: keep forcing bitmap scans for the next plan +deallocate ab_q1; +deallocate ab_q2; +deallocate ab_q3; +deallocate ab_q4; +deallocate ab_q5; +deallocate ab_q6; +-- UPDATE on a partition subtree has been seen to have problems. +insert into ab values (1,2); +explain (analyze, costs off, summary off, timing off) +update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; + QUERY PLAN +------------------------------------------------------------------------------------------- + Update on ab_a1 (actual rows=0 loops=1) + Update on ab_a1_b1 ab_a1_1 + Update on ab_a1_b2 ab_a1_2 + Update on ab_a1_b3 ab_a1_3 + -> Nested Loop (actual rows=1 loops=1) + -> Append (actual rows=1 loops=1) + -> YB Bitmap Table Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1) + -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) + Index Cond: (a = 1) + -> YB Bitmap Table Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1) + -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) + Index Cond: (a = 1) + -> YB Bitmap Table Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1) + -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=0 loops=1) + Index Cond: (a = 1) + -> Materialize (actual rows=1 loops=1) + -> Append (actual rows=1 loops=1) + -> YB Bitmap Table Scan on ab_a1_b1 ab_2 (actual rows=0 loops=1) + -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) + Index Cond: (a = 1) + -> YB Bitmap Table Scan on ab_a1_b2 ab_3 (actual rows=1 loops=1) + -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) + Index Cond: (a = 1) + -> YB Bitmap Table Scan on ab_a1_b3 ab_4 (actual rows=0 loops=1) + -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=0 loops=1) + Index Cond: (a = 1) +(26 rows) + +table ab; + a | b +---+--- + 1 | 3 +(1 row) + +reset yb_enable_bitmapscan; -- YB +reset enable_indexscan; -- YB +-- Test UPDATE where source relation has run-time pruning enabled +truncate ab; +insert into ab values (1, 1), (1, 2), (1, 3), (2, 1); +explain (analyze, costs off, summary off, timing off) +update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1); + QUERY PLAN +------------------------------------------------------------------------------ + Update on ab_a1 (actual rows=0 loops=1) + Update on ab_a1_b1 ab_a1_1 + Update on ab_a1_b2 ab_a1_2 + Update on ab_a1_b3 ab_a1_3 + InitPlan 1 (returns $0) + -> Result *RESULT* (actual rows=1 loops=1) + -> Nested Loop (actual rows=3 loops=1) + -> Append (actual rows=3 loops=1) + -> Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1) + -> Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1) + -> Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1) + -> Materialize (actual rows=1 loops=3) + -> Append (actual rows=1 loops=1) + -> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1) + Storage Filter: (b = $0) + -> Seq Scan on ab_a2_b2 ab_a2_2 (never executed) + Storage Filter: (b = $0) + -> Seq Scan on ab_a2_b3 ab_a2_3 (never executed) + Storage Filter: (b = $0) +(19 rows) + +select tableoid::regclass, * from ab; + tableoid | a | b +----------+---+--- + ab_a1_b3 | 1 | 3 + ab_a1_b3 | 1 | 3 + ab_a1_b3 | 1 | 3 + ab_a2_b1 | 2 | 1 +(4 rows) + +drop table ab, lprt_a; +-- Join +create table tbl1(col1 int); +insert into tbl1 values (501), (505); +-- Basic table +create table tprt (col1 int) partition by range (col1); +create table tprt_1 partition of tprt for values from (1) to (501); +create table tprt_2 partition of tprt for values from (501) to (1001); +create table tprt_3 partition of tprt for values from (1001) to (2001); +create table tprt_4 partition of tprt for values from (2001) to (3001); +create table tprt_5 partition of tprt for values from (3001) to (4001); +create table tprt_6 partition of tprt for values from (4001) to (5001); +create index tprt1_idx on tprt_1 (col1); +create index tprt2_idx on tprt_2 (col1); +create index tprt3_idx on tprt_3 (col1); +create index tprt4_idx on tprt_4 (col1); +create index tprt5_idx on tprt_5 (col1); +create index tprt6_idx on tprt_6 (col1); +insert into tprt values (10), (20), (501), (502), (505), (1001), (4500); +set enable_hashjoin = off; +set enable_mergejoin = off; +/* YB: The explain plan changes significantly between PG and YB +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 > tprt.col1; + +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; +*/ -- YB: The explain plan changes significantly between PG and YB +/*+Set(yb_bnl_batch_size 1024)*/ explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; + QUERY PLAN +------------------------------------------------------------------------------- + YB Batched Nested Loop Join (actual rows=2 loops=1) + Join Filter: (tbl1.col1 = tprt.col1) + -> Seq Scan on tbl1 (actual rows=2 loops=1) + -> Append (actual rows=2 loops=1) + -> Index Scan using tprt1_idx on tprt_1 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt2_idx on tprt_2 (actual rows=2 loops=1) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt3_idx on tprt_3 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt4_idx on tprt_4 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt5_idx on tprt_5 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt6_idx on tprt_6 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) +(16 rows) + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 > tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ + 501 | 10 + 501 | 20 + 505 | 10 + 505 | 20 + 505 | 501 + 505 | 502 +(6 rows) + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ + 501 | 501 + 505 | 505 +(2 rows) + +-- Multiple partitions +insert into tbl1 values (1001), (1010), (1011); +/* YB: The explain plan changes significantly between PG and YB +explain (analyze, costs off, summary off, timing off) +select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1; + +explain (analyze, costs off, summary off, timing off) +select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1; +*/ -- YB: The explain plan changes significantly between PG and YB +/*+Set(yb_bnl_batch_size 1024)*/ explain (analyze, costs off, summary off, timing off) +select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1; + QUERY PLAN +------------------------------------------------------------------------------- + YB Batched Nested Loop Join (actual rows=3 loops=1) + Join Filter: (tbl1.col1 = tprt.col1) + -> Seq Scan on tbl1 (actual rows=5 loops=1) + -> Append (actual rows=3 loops=1) + -> Index Scan using tprt1_idx on tprt_1 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt2_idx on tprt_2 (actual rows=2 loops=1) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt3_idx on tprt_3 (actual rows=1 loops=1) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt4_idx on tprt_4 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt5_idx on tprt_5 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt6_idx on tprt_6 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) +(16 rows) + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 > tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ + 501 | 10 + 501 | 20 + 505 | 10 + 505 | 20 + 505 | 501 + 505 | 502 + 1001 | 10 + 1001 | 20 + 1001 | 501 + 1001 | 502 + 1001 | 505 + 1010 | 10 + 1010 | 20 + 1010 | 501 + 1010 | 502 + 1010 | 505 + 1010 | 1001 + 1011 | 10 + 1011 | 20 + 1011 | 501 + 1011 | 502 + 1011 | 505 + 1011 | 1001 +(23 rows) + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ + 501 | 501 + 505 | 505 + 1001 | 1001 +(3 rows) + +-- Last partition +delete from tbl1; +insert into tbl1 values (4400); +/* YB: The explain plan changes significantly between PG and YB +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 < tprt.col1; +*/ -- YB: The explain plan changes significantly between PG and YB +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 < tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ + 4400 | 4500 +(1 row) + +-- No matching partition +delete from tbl1; +insert into tbl1 values (10000); +/* YB: The explain plan changes significantly between PG and YB +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; +*/ -- YB: The explain plan changes significantly between PG and YB +/*+Set(yb_bnl_batch_size 1024)*/ explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; + QUERY PLAN +------------------------------------------------------------------------------- + YB Batched Nested Loop Join (actual rows=0 loops=1) + Join Filter: (tbl1.col1 = tprt.col1) + -> Seq Scan on tbl1 (actual rows=1 loops=1) + -> Append (actual rows=0 loops=1) + -> Index Scan using tprt1_idx on tprt_1 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt2_idx on tprt_2 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt3_idx on tprt_3 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt4_idx on tprt_4 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt5_idx on tprt_5 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) + -> Index Scan using tprt6_idx on tprt_6 (never executed) + Index Cond: (col1 = ANY (ARRAY[tbl1.col1, $1, $2, ..., $1023])) +(16 rows) + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ +(0 rows) + +drop table tbl1, tprt; +-- Test with columns defined in varying orders between each level +create table part_abc (a int not null, b int not null, c int not null) partition by list (a); +create table part_bac (b int not null, a int not null, c int not null) partition by list (b); +create table part_cab (c int not null, a int not null, b int not null) partition by list (c); +create table part_abc_p1 (a int not null, b int not null, c int not null); +alter table part_abc attach partition part_bac for values in(1); +alter table part_bac attach partition part_cab for values in(2); +alter table part_cab attach partition part_abc_p1 for values in(3); +prepare part_abc_q1 (int, int, int) as +select * from part_abc where a = $1 and b = $2 and c = $3; +-- Single partition should be scanned. +explain (analyze, costs off, summary off, timing off) execute part_abc_q1 (1, 2, 3); + QUERY PLAN +---------------------------------------------------------- + Seq Scan on part_abc_p1 part_abc (actual rows=0 loops=1) + Storage Filter: ((a = $1) AND (b = $2) AND (c = $3)) +(2 rows) + +deallocate part_abc_q1; +drop table part_abc; +-- Ensure that an Append node properly handles a sub-partitioned table +-- matching without any of its leaf partitions matching the clause. +create table listp (a int, b int) partition by list (a); +create table listp_1 partition of listp for values in(1) partition by list (b); +create table listp_1_1 partition of listp_1 for values in(1); +create table listp_2 partition of listp for values in(2) partition by list (b); +create table listp_2_1 partition of listp_2 for values in(2); +select * from listp where b = 1; + a | b +---+--- +(0 rows) + +-- Ensure that an Append node properly can handle selection of all first level +-- partitions before finally detecting the correct set of 2nd level partitions +-- which match the given parameter. +prepare q1 (int,int) as select * from listp where b in ($1,$2); +explain (analyze, costs off, summary off, timing off) execute q1 (1,1); + QUERY PLAN +------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 1 + -> Seq Scan on listp_1_1 listp_1 (actual rows=0 loops=1) + Filter: (b = ANY (ARRAY[$1, $2])) +(4 rows) + +explain (analyze, costs off, summary off, timing off) execute q1 (2,2); + QUERY PLAN +------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 1 + -> Seq Scan on listp_2_1 listp_1 (actual rows=0 loops=1) + Filter: (b = ANY (ARRAY[$1, $2])) +(4 rows) + +-- Try with no matching partitions. +explain (analyze, costs off, summary off, timing off) execute q1 (0,0); + QUERY PLAN +-------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 2 +(2 rows) + +deallocate q1; +-- Test more complex cases where a not-equal condition further eliminates partitions. +prepare q1 (int,int,int,int) as select * from listp where b in($1,$2) and $3 <> b and $4 <> b; +-- Both partitions allowed by IN clause, but one disallowed by <> clause +explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,0); + QUERY PLAN +------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 1 + -> Seq Scan on listp_1_1 listp_1 (actual rows=0 loops=1) + Storage Filter: (($3 <> b) AND ($4 <> b)) + Filter: (b = ANY (ARRAY[$1, $2])) +(5 rows) + +-- Both partitions allowed by IN clause, then both excluded again by <> clauses. +explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,1); + QUERY PLAN +-------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 2 +(2 rows) + +-- Ensure Params that evaluate to NULL properly prune away all partitions +explain (analyze, costs off, summary off, timing off) +select * from listp where a = (select null::int); + QUERY PLAN +------------------------------------------------------ + Append (actual rows=0 loops=1) + InitPlan 1 (returns $0) + -> Result *RESULT* (actual rows=1 loops=1) + -> Seq Scan on listp_1_1 listp_2 (never executed) + Storage Filter: (a = $0) + -> Seq Scan on listp_2_1 listp_4 (never executed) + Storage Filter: (a = $0) +(7 rows) + +drop table listp; +-- +-- check that stable query clauses are only used in run-time pruning +-- +create table stable_qual_pruning (a timestamp) partition by range (a); +create table stable_qual_pruning1 partition of stable_qual_pruning + for values from ('2000-01-01') to ('2000-02-01'); +create table stable_qual_pruning2 partition of stable_qual_pruning + for values from ('2000-02-01') to ('2000-03-01'); +create table stable_qual_pruning3 partition of stable_qual_pruning + for values from ('3000-02-01') to ('3000-03-01'); +-- comparison against a stable value requires run-time pruning +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning where a < localtimestamp; + QUERY PLAN +-------------------------------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 1 + -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) + Storage Filter: (a < LOCALTIMESTAMP) + -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_2 (actual rows=0 loops=1) + Storage Filter: (a < LOCALTIMESTAMP) +(6 rows) + +-- timestamp < timestamptz comparison is only stable, not immutable +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning where a < '2000-02-01'::timestamptz; + QUERY PLAN +-------------------------------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 2 + -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) + Filter: (a < 'Tue Feb 01 00:00:00 2000 PST'::timestamp with time zone) +(4 rows) + +-- check ScalarArrayOp cases +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(array['2010-02-01', '2020-01-01']::timestamp[]); + QUERY PLAN +---------------------------------------------------- + Result stable_qual_pruning (actual rows=0 loops=1) + One-Time Filter: false +(2 rows) + +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(array['2000-02-01', '2010-01-01']::timestamp[]); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Seq Scan on stable_qual_pruning2 stable_qual_pruning_1 (actual rows=0 loops=1) + Storage Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000","Fri Jan 01 00:00:00 2010"}'::timestamp without time zone[])) +(2 rows) + +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(array['2000-02-01', localtimestamp]::timestamp[]); + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Append (actual rows=0 loops=1) + Subplans Removed: 2 + -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_2 (actual rows=0 loops=1) + Filter: (a = ANY (ARRAY['Tue Feb 01 00:00:00 2000'::timestamp without time zone, LOCALTIMESTAMP])) +(4 rows) + +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(array['2010-02-01', '2020-01-01']::timestamptz[]); + QUERY PLAN +-------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 3 +(2 rows) + +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(array['2000-02-01', '2010-01-01']::timestamptz[]); + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Append (actual rows=0 loops=1) + Subplans Removed: 2 + -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_2 (actual rows=0 loops=1) + Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000 PST","Fri Jan 01 00:00:00 2010 PST"}'::timestamp with time zone[])) +(4 rows) + +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(null::timestamptz[]); + QUERY PLAN +-------------------------------------------------------------------------------------- + Append (actual rows=0 loops=1) + -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) + Filter: (a = ANY (NULL::timestamp with time zone[])) + -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_2 (actual rows=0 loops=1) + Filter: (a = ANY (NULL::timestamp with time zone[])) + -> Seq Scan on stable_qual_pruning3 stable_qual_pruning_3 (actual rows=0 loops=1) + Filter: (a = ANY (NULL::timestamp with time zone[])) +(7 rows) + +drop table stable_qual_pruning; +-- +-- Check that pruning with composite range partitioning works correctly when +-- it must ignore clauses for trailing keys once it has seen a clause with +-- non-inclusive operator for an earlier key +-- +create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +create table mc3p0 partition of mc3p + for values from (0, 0, 0) to (0, maxvalue, maxvalue); +create table mc3p1 partition of mc3p + for values from (1, 1, 1) to (2, minvalue, minvalue); +create table mc3p2 partition of mc3p + for values from (2, minvalue, minvalue) to (3, maxvalue, maxvalue); +insert into mc3p values (0, 1, 1), (1, 1, 1), (2, 1, 1); +explain (analyze, costs off, summary off, timing off) +select * from mc3p where a < 3 and abs(b) = 1; + QUERY PLAN +-------------------------------------------------------- + Append (actual rows=3 loops=1) + -> Seq Scan on mc3p0 mc3p_1 (actual rows=1 loops=1) + Storage Filter: ((a < 3) AND (abs(b) = 1)) + -> Seq Scan on mc3p1 mc3p_2 (actual rows=1 loops=1) + Storage Filter: ((a < 3) AND (abs(b) = 1)) + -> Seq Scan on mc3p2 mc3p_3 (actual rows=1 loops=1) + Storage Filter: ((a < 3) AND (abs(b) = 1)) +(7 rows) + +-- +-- Check that pruning with composite range partitioning works correctly when +-- a combination of runtime parameters is specified, not all of whose values +-- are available at the same time +-- +prepare ps1 as + select * from mc3p where a = $1 and abs(b) < (select 3); +explain (analyze, costs off, summary off, timing off) +execute ps1(1); + QUERY PLAN +-------------------------------------------------------- + Append (actual rows=1 loops=1) + Subplans Removed: 2 + InitPlan 1 (returns $0) + -> Result *RESULT* (actual rows=1 loops=1) + -> Seq Scan on mc3p1 mc3p_1 (actual rows=1 loops=1) + Storage Filter: ((a = $1) AND (abs(b) < $0)) +(6 rows) + +deallocate ps1; +prepare ps2 as + select * from mc3p where a <= $1 and abs(b) < (select 3); +explain (analyze, costs off, summary off, timing off) +execute ps2(1); + QUERY PLAN +-------------------------------------------------------- + Append (actual rows=2 loops=1) + Subplans Removed: 1 + InitPlan 1 (returns $0) + -> Result *RESULT* (actual rows=1 loops=1) + -> Seq Scan on mc3p0 mc3p_1 (actual rows=1 loops=1) + Storage Filter: ((a <= $1) AND (abs(b) < $0)) + -> Seq Scan on mc3p1 mc3p_2 (actual rows=1 loops=1) + Storage Filter: ((a <= $1) AND (abs(b) < $0)) +(8 rows) + +deallocate ps2; +drop table mc3p; +-- Ensure runtime pruning works with initplans params with boolean types +create table boolvalues (value bool not null); +insert into boolvalues values('t'),('f'); +create table boolp (a bool) partition by list (a); +create table boolp_t partition of boolp for values in('t'); +create table boolp_f partition of boolp for values in('f'); +explain (analyze, costs off, summary off, timing off) +select * from boolp where a = (select value from boolvalues where value); + QUERY PLAN +----------------------------------------------------------- + Append (actual rows=0 loops=1) + InitPlan 1 (returns $0) + -> Seq Scan on boolvalues (actual rows=1 loops=1) + Storage Filter: value + -> Seq Scan on boolp_f boolp_1 (never executed) + Storage Filter: (a = $0) + -> Seq Scan on boolp_t boolp_2 (actual rows=0 loops=1) + Storage Filter: (a = $0) +(8 rows) + +explain (analyze, costs off, summary off, timing off) +select * from boolp where a = (select value from boolvalues where not value); + QUERY PLAN +----------------------------------------------------------- + Append (actual rows=0 loops=1) + InitPlan 1 (returns $0) + -> Seq Scan on boolvalues (actual rows=1 loops=1) + Storage Filter: (NOT value) + -> Seq Scan on boolp_f boolp_1 (actual rows=0 loops=1) + Storage Filter: (a = $0) + -> Seq Scan on boolp_t boolp_2 (never executed) + Storage Filter: (a = $0) +(8 rows) + +drop table boolp; +-- +-- Test run-time pruning of MergeAppend subnodes +-- +set enable_seqscan = off; +set enable_sort = off; +create table ma_test (a int, b int) partition by range (a); +create table ma_test_p1 partition of ma_test for values from (0) to (10); +create table ma_test_p2 partition of ma_test for values from (10) to (20); +create table ma_test_p3 partition of ma_test for values from (20) to (30); +insert into ma_test select x,x from generate_series(0,29) t(x); +create index on ma_test (b ASC); -- YB: change to ASC to allow for range queries +analyze ma_test; +prepare mt_q1 (int) as select a from ma_test where a >= $1 and a % 10 = 5 order by b; +explain (analyze, costs off, summary off, timing off) execute mt_q1(15); + QUERY PLAN +----------------------------------------------------------------------------------------- + Merge Append (actual rows=2 loops=1) + Sort Key: ma_test.b + Subplans Removed: 1 + -> Index Scan using ma_test_p2_b_idx on ma_test_p2 ma_test_1 (actual rows=1 loops=1) + Storage Filter: ((a >= $1) AND ((a % 10) = 5)) + -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_2 (actual rows=1 loops=1) + Storage Filter: ((a >= $1) AND ((a % 10) = 5)) +(7 rows) + +execute mt_q1(15); + a +---- + 15 + 25 +(2 rows) + +explain (analyze, costs off, summary off, timing off) execute mt_q1(25); + QUERY PLAN +----------------------------------------------------------------------------------------- + Merge Append (actual rows=1 loops=1) + Sort Key: ma_test.b + Subplans Removed: 2 + -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_1 (actual rows=1 loops=1) + Storage Filter: ((a >= $1) AND ((a % 10) = 5)) +(5 rows) + +execute mt_q1(25); + a +---- + 25 +(1 row) + +-- Ensure MergeAppend behaves correctly when no subplans match +explain (analyze, costs off, summary off, timing off) execute mt_q1(35); + QUERY PLAN +-------------------------------------- + Merge Append (actual rows=0 loops=1) + Sort Key: ma_test.b + Subplans Removed: 3 +(3 rows) + +execute mt_q1(35); + a +--- +(0 rows) + +deallocate mt_q1; +prepare mt_q2 (int) as select * from ma_test where a >= $1 order by b limit 1; +-- Ensure output list looks sane when the MergeAppend has no subplans. +explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35); + QUERY PLAN +-------------------------------------------- + Limit (actual rows=0 loops=1) + Output: ma_test.a, ma_test.b + -> Merge Append (actual rows=0 loops=1) + Sort Key: ma_test.b + Subplans Removed: 3 +(5 rows) + +deallocate mt_q2; +-- ensure initplan params properly prune partitions +explain (analyze, costs off, summary off, timing off) select * from ma_test where a >= (select min(b) from ma_test_p2) order by b; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Merge Append (actual rows=20 loops=1) + Sort Key: ma_test.b + InitPlan 2 (returns $1) + -> Result (actual rows=1 loops=1) + InitPlan 1 (returns $0) + -> Limit (actual rows=1 loops=1) + -> Index Scan using ma_test_p2_b_idx on ma_test_p2 (actual rows=1 loops=1) + Index Cond: (b IS NOT NULL) + -> Index Scan using ma_test_p1_b_idx on ma_test_p1 ma_test_1 (never executed) + Storage Filter: (a >= $1) + -> Index Scan using ma_test_p2_b_idx on ma_test_p2 ma_test_2 (actual rows=10 loops=1) + Storage Filter: (a >= $1) + -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_3 (actual rows=10 loops=1) + Storage Filter: (a >= $1) +(14 rows) + +reset enable_seqscan; +reset enable_sort; +drop table ma_test; +reset enable_indexonlyscan; +-- +-- check that pruning works properly when the partition key is of a +-- pseudotype +-- +-- array type list partition key +create table pp_arrpart (a int[]) partition by list (a); +create table pp_arrpart1 partition of pp_arrpart for values in ('{1}'); +create table pp_arrpart2 partition of pp_arrpart for values in ('{2, 3}', '{4, 5}'); +explain (costs off) select * from pp_arrpart where a = '{1}'; + QUERY PLAN +-------------------------------------- + Seq Scan on pp_arrpart1 pp_arrpart_1 + Filter: (a = '{1}'::integer[]) +(2 rows) + +explain (costs off) select * from pp_arrpart where a = '{1, 2}'; + QUERY PLAN +-------------------------- + Result pp_arrpart + One-Time Filter: false +(2 rows) + +explain (costs off) select * from pp_arrpart where a in ('{4, 5}', '{1}'); + QUERY PLAN +---------------------------------------------------------------------- + Append + -> Seq Scan on pp_arrpart1 pp_arrpart_1 + Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) + -> Seq Scan on pp_arrpart2 pp_arrpart_2 + Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) +(5 rows) + +explain (costs off) update pp_arrpart set a = a where a = '{1}'; + QUERY PLAN +-------------------------------------------- + Update on pp_arrpart + Update on pp_arrpart1 pp_arrpart_1 + -> Seq Scan on pp_arrpart1 pp_arrpart_1 + Filter: (a = '{1}'::integer[]) +(4 rows) + +explain (costs off) delete from pp_arrpart where a = '{1}'; + QUERY PLAN +-------------------------------------------- + Delete on pp_arrpart + Delete on pp_arrpart1 pp_arrpart_1 + -> Seq Scan on pp_arrpart1 pp_arrpart_1 + Filter: (a = '{1}'::integer[]) +(4 rows) + +drop table pp_arrpart; +-- array type hash partition key +create table pph_arrpart (a int[]) partition by hash (a); +create table pph_arrpart1 partition of pph_arrpart for values with (modulus 2, remainder 0); +create table pph_arrpart2 partition of pph_arrpart for values with (modulus 2, remainder 1); +insert into pph_arrpart values ('{1}'), ('{1, 2}'), ('{4, 5}'); +select tableoid::regclass, * from pph_arrpart order by 1,2; -- YB: add more ordering + tableoid | a +--------------+------- + pph_arrpart1 | {1,2} + pph_arrpart1 | {4,5} + pph_arrpart2 | {1} +(3 rows) + +explain (costs off) select * from pph_arrpart where a = '{1}'; + QUERY PLAN +---------------------------------------- + Seq Scan on pph_arrpart2 pph_arrpart_1 + Filter: (a = '{1}'::integer[]) +(2 rows) + +explain (costs off) select * from pph_arrpart where a = '{1, 2}'; + QUERY PLAN +---------------------------------------- + Seq Scan on pph_arrpart1 pph_arrpart_1 + Filter: (a = '{1,2}'::integer[]) +(2 rows) + +explain (costs off) select * from pph_arrpart where a in ('{4, 5}', '{1}'); + QUERY PLAN +---------------------------------------------------------------------- + Append + -> Seq Scan on pph_arrpart1 pph_arrpart_1 + Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) + -> Seq Scan on pph_arrpart2 pph_arrpart_2 + Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) +(5 rows) + +drop table pph_arrpart; +-- enum type list partition key +create type pp_colors as enum ('green', 'blue', 'black'); +create table pp_enumpart (a pp_colors) partition by list (a); +create table pp_enumpart_green partition of pp_enumpart for values in ('green'); +create table pp_enumpart_blue partition of pp_enumpart for values in ('blue'); +explain (costs off) select * from pp_enumpart where a = 'blue'; + QUERY PLAN +-------------------------------------------- + Seq Scan on pp_enumpart_blue pp_enumpart_1 + Filter: (a = 'blue'::pp_colors) +(2 rows) + +explain (costs off) select * from pp_enumpart where a = 'black'; + QUERY PLAN +-------------------------- + Result pp_enumpart + One-Time Filter: false +(2 rows) + +drop table pp_enumpart; +drop type pp_colors; +-- record type as partition key +create type pp_rectype as (a int, b int); +create table pp_recpart (a pp_rectype) partition by list (a); +create table pp_recpart_11 partition of pp_recpart for values in ('(1,1)'); +create table pp_recpart_23 partition of pp_recpart for values in ('(2,3)'); +explain (costs off) select * from pp_recpart where a = '(1,1)'::pp_rectype; + QUERY PLAN +---------------------------------------- + Seq Scan on pp_recpart_11 pp_recpart_1 + Filter: (a = '(1,1)'::pp_rectype) +(2 rows) + +explain (costs off) select * from pp_recpart where a = '(1,2)'::pp_rectype; + QUERY PLAN +-------------------------- + Result pp_recpart + One-Time Filter: false +(2 rows) + +drop table pp_recpart; +drop type pp_rectype; +-- range type partition key +create table pp_intrangepart (a int4range) partition by list (a); +create table pp_intrangepart12 partition of pp_intrangepart for values in ('[1,2]'); +create table pp_intrangepart2inf partition of pp_intrangepart for values in ('[2,)'); +explain (costs off) select * from pp_intrangepart where a = '[1,2]'::int4range; + QUERY PLAN +------------------------------------------------- + Seq Scan on pp_intrangepart12 pp_intrangepart_1 + Filter: (a = '[1,3)'::int4range) +(2 rows) + +explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range; + QUERY PLAN +-------------------------- + Result pp_intrangepart + One-Time Filter: false +(2 rows) + +drop table pp_intrangepart; +-- +-- Ensure the enable_partition_prune GUC properly disables partition pruning. +-- +create table pp_lp (a int, value int) partition by list (a); +create table pp_lp1 partition of pp_lp for values in(1); +create table pp_lp2 partition of pp_lp for values in(2); +explain (costs off) select * from pp_lp where a = 1; + QUERY PLAN +---------------------------- + Seq Scan on pp_lp1 pp_lp_1 + Storage Filter: (a = 1) +(2 rows) + +explain (costs off) update pp_lp set value = 10 where a = 1; + QUERY PLAN +---------------------------------- + Update on pp_lp + Update on pp_lp1 pp_lp_1 + -> Seq Scan on pp_lp1 pp_lp_1 + Storage Filter: (a = 1) +(4 rows) + +explain (costs off) delete from pp_lp where a = 1; + QUERY PLAN +---------------------------------- + Delete on pp_lp + Delete on pp_lp1 pp_lp_1 + -> Seq Scan on pp_lp1 pp_lp_1 + Storage Filter: (a = 1) +(4 rows) + +set enable_partition_pruning = off; +set constraint_exclusion = 'partition'; -- this should not affect the result. +explain (costs off) select * from pp_lp where a = 1; + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on pp_lp1 pp_lp_1 + Storage Filter: (a = 1) + -> Seq Scan on pp_lp2 pp_lp_2 + Storage Filter: (a = 1) +(5 rows) + +explain (costs off) update pp_lp set value = 10 where a = 1; + QUERY PLAN +---------------------------------------- + Update on pp_lp + Update on pp_lp1 pp_lp_1 + Update on pp_lp2 pp_lp_2 + -> Append + -> Seq Scan on pp_lp1 pp_lp_1 + Storage Filter: (a = 1) + -> Seq Scan on pp_lp2 pp_lp_2 + Storage Filter: (a = 1) +(8 rows) + +explain (costs off) delete from pp_lp where a = 1; + QUERY PLAN +---------------------------------------- + Delete on pp_lp + Delete on pp_lp1 pp_lp_1 + Delete on pp_lp2 pp_lp_2 + -> Append + -> Seq Scan on pp_lp1 pp_lp_1 + Storage Filter: (a = 1) + -> Seq Scan on pp_lp2 pp_lp_2 + Storage Filter: (a = 1) +(8 rows) + +set constraint_exclusion = 'off'; -- this should not affect the result. +explain (costs off) select * from pp_lp where a = 1; + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on pp_lp1 pp_lp_1 + Storage Filter: (a = 1) + -> Seq Scan on pp_lp2 pp_lp_2 + Storage Filter: (a = 1) +(5 rows) + +explain (costs off) update pp_lp set value = 10 where a = 1; + QUERY PLAN +---------------------------------------- + Update on pp_lp + Update on pp_lp1 pp_lp_1 + Update on pp_lp2 pp_lp_2 + -> Append + -> Seq Scan on pp_lp1 pp_lp_1 + Storage Filter: (a = 1) + -> Seq Scan on pp_lp2 pp_lp_2 + Storage Filter: (a = 1) +(8 rows) + +explain (costs off) delete from pp_lp where a = 1; + QUERY PLAN +---------------------------------------- + Delete on pp_lp + Delete on pp_lp1 pp_lp_1 + Delete on pp_lp2 pp_lp_2 + -> Append + -> Seq Scan on pp_lp1 pp_lp_1 + Storage Filter: (a = 1) + -> Seq Scan on pp_lp2 pp_lp_2 + Storage Filter: (a = 1) +(8 rows) + +drop table pp_lp; +-- Ensure enable_partition_prune does not affect non-partitioned tables. +create table inh_lp (a int, value int); +create table inh_lp1 (a int, value int, check(a = 1)) inherits (inh_lp); +NOTICE: merging column "a" with inherited definition +NOTICE: merging column "value" with inherited definition +create table inh_lp2 (a int, value int, check(a = 2)) inherits (inh_lp); +NOTICE: merging column "a" with inherited definition +NOTICE: merging column "value" with inherited definition +set constraint_exclusion = 'partition'; +-- inh_lp2 should be removed in the following 3 cases. +explain (costs off) select * from inh_lp where a = 1; + QUERY PLAN +------------------------------------ + Append + -> Seq Scan on inh_lp inh_lp_1 + Storage Filter: (a = 1) + -> Seq Scan on inh_lp1 inh_lp_2 + Storage Filter: (a = 1) +(5 rows) + +explain (costs off) update inh_lp set value = 10 where a = 1; + QUERY PLAN +------------------------------------------------ + Update on inh_lp + Update on inh_lp inh_lp_1 + Update on inh_lp1 inh_lp_2 + -> Result inh_lp + -> Append + -> Seq Scan on inh_lp inh_lp_1 + Storage Filter: (a = 1) + -> Seq Scan on inh_lp1 inh_lp_2 + Storage Filter: (a = 1) +(9 rows) + +explain (costs off) delete from inh_lp where a = 1; + QUERY PLAN +------------------------------------------ + Delete on inh_lp + Delete on inh_lp inh_lp_1 + Delete on inh_lp1 inh_lp_2 + -> Append + -> Seq Scan on inh_lp inh_lp_1 + Storage Filter: (a = 1) + -> Seq Scan on inh_lp1 inh_lp_2 + Storage Filter: (a = 1) +(8 rows) + +-- Ensure we don't exclude normal relations when we only expect to exclude +-- inheritance children +explain (costs off) update inh_lp1 set value = 10 where a = 2; + QUERY PLAN +--------------------------------- + Update on inh_lp1 + -> Seq Scan on inh_lp1 + Storage Filter: (a = 2) +(3 rows) + +drop table inh_lp cascade; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table inh_lp1 +drop cascades to table inh_lp2 +reset enable_partition_pruning; +reset constraint_exclusion; +-- Check pruning for a partition tree containing only temporary relations +create temp table pp_temp_parent (a int) partition by list (a); +create temp table pp_temp_part_1 partition of pp_temp_parent for values in (1); +create temp table pp_temp_part_def partition of pp_temp_parent default; +explain (costs off) select * from pp_temp_parent where true; + QUERY PLAN +----------------------------------------------------- + Append + -> Seq Scan on pp_temp_part_1 pp_temp_parent_1 + -> Seq Scan on pp_temp_part_def pp_temp_parent_2 +(3 rows) + +explain (costs off) select * from pp_temp_parent where a = 2; + QUERY PLAN +----------------------------------------------- + Seq Scan on pp_temp_part_def pp_temp_parent_1 + Filter: (a = 2) +(2 rows) + +drop table pp_temp_parent; +-- Stress run-time partition pruning a bit more, per bug reports +create temp table p (a int, b int, c int) partition by list (a); +create temp table p1 partition of p for values in (1); +create temp table p2 partition of p for values in (2); +create temp table q (a int, b int, c int) partition by list (a); +create temp table q1 partition of q for values in (1) partition by list (b); +create temp table q11 partition of q1 for values in (1) partition by list (c); +create temp table q111 partition of q11 for values in (1); +create temp table q2 partition of q for values in (2) partition by list (b); +create temp table q21 partition of q2 for values in (1); +create temp table q22 partition of q2 for values in (2); +insert into q22 values (2, 2, 3); +explain (costs off) +select * +from ( + select * from p + union all + select * from q1 + union all + select 1, 1, 1 + ) s(a, b, c) +where s.a = 1 and s.b = 1 and s.c = (select 1); + QUERY PLAN +---------------------------------------------------- + Append + InitPlan 1 (returns $0) + -> Result *RESULT* + -> Seq Scan on p1 p_1 + Filter: ((a = 1) AND (b = 1) AND (c = $0)) + -> Seq Scan on q111 q1_2 + Filter: ((a = 1) AND (b = 1) AND (c = $0)) + -> Result + One-Time Filter: (1 = $0) +(9 rows) + +select * +from ( + select * from p + union all + select * from q1 + union all + select 1, 1, 1 + ) s(a, b, c) +where s.a = 1 and s.b = 1 and s.c = (select 1); + a | b | c +---+---+--- + 1 | 1 | 1 +(1 row) + +prepare q (int, int) as +select * +from ( + select * from p + union all + select * from q1 + union all + select 1, 1, 1 + ) s(a, b, c) +where s.a = $1 and s.b = $2 and s.c = (select 1); +explain (costs off) execute q (1, 1); + QUERY PLAN +--------------------------------------------------------------- + Append + Subplans Removed: 1 + InitPlan 1 (returns $0) + -> Result *RESULT* + -> Seq Scan on p1 p + Filter: ((a = $1) AND (b = $2) AND (c = $0)) + -> Seq Scan on q111 q1 + Filter: ((a = $1) AND (b = $2) AND (c = $0)) + -> Result + One-Time Filter: ((1 = $1) AND (1 = $2) AND (1 = $0)) +(10 rows) + +execute q (1, 1); + a | b | c +---+---+--- + 1 | 1 | 1 +(1 row) + +drop table p, q; +-- Ensure run-time pruning works correctly when we match a partitioned table +-- on the first level but find no matching partitions on the second level. +create table listp (a int, b int) partition by list (a); +create table listp1 partition of listp for values in(1); +create table listp2 partition of listp for values in(2) partition by list(b); +create table listp2_10 partition of listp2 for values in (10); +-- YB: #26410 +explain (analyze, costs off, summary off, timing off) +select * from listp where a = (select 2) and b <> 10; + QUERY PLAN +---------------------------------------------------- + Seq Scan on listp1 listp_1 (actual rows=0 loops=1) + Storage Filter: ((b <> 10) AND (a = $0)) + InitPlan 1 (returns $0) + -> Result *RESULT* (actual rows=1 loops=1) +(4 rows) + +-- +-- check that a partition directly accessed in a query is excluded with +-- constraint_exclusion = on +-- +-- turn off partition pruning, so that it doesn't interfere +set enable_partition_pruning to off; +-- setting constraint_exclusion to 'partition' disables exclusion +set constraint_exclusion to 'partition'; +explain (costs off) select * from listp1 where a = 2; + QUERY PLAN +--------------------------- + Seq Scan on listp1 + Storage Filter: (a = 2) +(2 rows) + +explain (costs off) update listp1 set a = 1 where a = 2; + QUERY PLAN +--------------------------------- + Update on listp1 + -> Seq Scan on listp1 + Storage Filter: (a = 2) +(3 rows) + +-- constraint exclusion enabled +set constraint_exclusion to 'on'; +explain (costs off) select * from listp1 where a = 2; + QUERY PLAN +-------------------------- + Result listp1 + One-Time Filter: false +(2 rows) + +explain (costs off) update listp1 set a = 1 where a = 2; + QUERY PLAN +-------------------------------- + Update on listp1 + -> Result listp1 + One-Time Filter: false +(3 rows) + +reset constraint_exclusion; +reset enable_partition_pruning; +drop table listp; +-- Ensure run-time pruning works correctly for nested Append nodes +set parallel_setup_cost to 0; +set parallel_tuple_cost to 0; +create table listp (a int) partition by list(a); +create table listp_12 partition of listp for values in(1,2) partition by list(a); +create table listp_12_1 partition of listp_12 for values in(1); +create table listp_12_2 partition of listp_12 for values in(2); +-- Force the 2nd subnode of the Append to be non-parallel. This results in +-- a nested Append node because the mixed parallel / non-parallel paths cannot +-- be pulled into the top-level Append. +alter table listp_12_1 set (parallel_workers = 0); +NOTICE: storage parameters are currently ignored in YugabyteDB +-- Ensure that listp_12_2 is not scanned. (The nested Append is not seen in +-- the plan as it's pulled in setref.c due to having just a single subnode). +select explain_parallel_append('select * from listp where a = (select 1);'); + explain_parallel_append +------------------------- +(0 rows) + +-- Like the above but throw some more complexity at the planner by adding +-- a UNION ALL. We expect both sides of the union not to scan the +-- non-required partitions. +select explain_parallel_append( +'select * from listp where a = (select 1) + union all +select * from listp where a = (select 2);'); + explain_parallel_append +------------------------- +(0 rows) + +drop table listp; +reset parallel_tuple_cost; +reset parallel_setup_cost; +-- Test case for run-time pruning with a nested Merge Append +set enable_sort to 0; +create table rangep (a int, b int) partition by range (a); +create table rangep_0_to_100 partition of rangep for values from (0) to (100) partition by list (b); +-- We need 3 sub-partitions. 1 to validate pruning worked and another two +-- because a single remaining partition would be pulled up to the main Append. +create table rangep_0_to_100_1 partition of rangep_0_to_100 for values in(1); +create table rangep_0_to_100_2 partition of rangep_0_to_100 for values in(2); +create table rangep_0_to_100_3 partition of rangep_0_to_100 for values in(3); +create table rangep_100_to_200 partition of rangep for values from (100) to (200); +create index on rangep (a ASC); -- YB: change to ASC to allow for range queries +-- Ensure run-time pruning works on the nested Merge Append +explain (analyze on, costs off, timing off, summary off) +select * from rangep where b IN((select 1),(select 2)) order by a; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Merge Append (actual rows=0 loops=1) + Sort Key: rangep.a + InitPlan 1 (returns $0) + -> Result *RESULT* (actual rows=1 loops=1) + InitPlan 2 (returns $1) + -> Result *RESULT*_1 (actual rows=1 loops=1) + -> Index Scan using rangep_0_to_100_1_a_idx on rangep_0_to_100_1 rangep_2 (actual rows=0 loops=1) + Filter: (b = ANY (ARRAY[$0, $1])) + -> Index Scan using rangep_0_to_100_2_a_idx on rangep_0_to_100_2 rangep_3 (actual rows=0 loops=1) + Filter: (b = ANY (ARRAY[$0, $1])) + -> Index Scan using rangep_0_to_100_3_a_idx on rangep_0_to_100_3 rangep_4 (never executed) + Filter: (b = ANY (ARRAY[$0, $1])) + -> Index Scan using rangep_100_to_200_a_idx on rangep_100_to_200 rangep_5 (actual rows=0 loops=1) + Filter: (b = ANY (ARRAY[$0, $1])) +(14 rows) + +reset enable_sort; +drop table rangep; +-- +-- Check that gen_prune_steps_from_opexps() works well for various cases of +-- clauses for different partition keys +-- +create table rp_prefix_test1 (a int, b varchar) partition by range(a, b); +create table rp_prefix_test1_p1 partition of rp_prefix_test1 for values from (1, 'a') to (1, 'b'); +create table rp_prefix_test1_p2 partition of rp_prefix_test1 for values from (2, 'a') to (2, 'b'); +-- Don't call get_steps_using_prefix() with the last partition key b plus +-- an empty prefix +explain (costs off) select * from rp_prefix_test1 where a <= 1 and b = 'a'; + QUERY PLAN +---------------------------------------------------------- + Seq Scan on rp_prefix_test1_p1 rp_prefix_test1_1 + Storage Filter: ((a <= 1) AND ((b)::text = 'a'::text)) +(2 rows) + +create table rp_prefix_test2 (a int, b int, c int) partition by range(a, b, c); +create table rp_prefix_test2_p1 partition of rp_prefix_test2 for values from (1, 1, 0) to (1, 1, 10); +create table rp_prefix_test2_p2 partition of rp_prefix_test2 for values from (2, 2, 0) to (2, 2, 10); +-- Don't call get_steps_using_prefix() with the last partition key c plus +-- an invalid prefix (ie, b = 1) +explain (costs off) select * from rp_prefix_test2 where a <= 1 and b = 1 and c >= 0; + QUERY PLAN +------------------------------------------------------- + Seq Scan on rp_prefix_test2_p1 rp_prefix_test2_1 + Storage Filter: ((a <= 1) AND (c >= 0) AND (b = 1)) +(2 rows) + +create table rp_prefix_test3 (a int, b int, c int, d int) partition by range(a, b, c, d); +create table rp_prefix_test3_p1 partition of rp_prefix_test3 for values from (1, 1, 1, 0) to (1, 1, 1, 10); +create table rp_prefix_test3_p2 partition of rp_prefix_test3 for values from (2, 2, 2, 0) to (2, 2, 2, 10); +-- Test that get_steps_using_prefix() handles a prefix that contains multiple +-- clauses for the partition key b (ie, b >= 1 and b >= 2) +explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b >= 2 and c >= 2 and d >= 0; + QUERY PLAN +---------------------------------------------------------------------------------- + Seq Scan on rp_prefix_test3_p2 rp_prefix_test3_1 + Storage Filter: ((a >= 1) AND (b >= 1) AND (b >= 2) AND (c >= 2) AND (d >= 0)) +(2 rows) + +-- Test that get_steps_using_prefix() handles a prefix that contains multiple +-- clauses for the partition key b (ie, b >= 1 and b = 2) (This also tests +-- that the caller arranges clauses in that prefix in the required order) +explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b = 2 and c = 2 and d >= 0; + QUERY PLAN +-------------------------------------------------------------------------------- + Seq Scan on rp_prefix_test3_p2 rp_prefix_test3_1 + Storage Filter: ((a >= 1) AND (b >= 1) AND (d >= 0) AND (b = 2) AND (c = 2)) +(2 rows) + +create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops); +create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 2, remainder 0); +create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 2, remainder 1); +-- Test that get_steps_using_prefix() handles non-NULL step_nullkeys +explain (costs off) select * from hp_prefix_test where a = 1 and b is null and c = 1 and d = 1; + QUERY PLAN +--------------------------------------------------------------------- + Seq Scan on hp_prefix_test_p1 hp_prefix_test_1 + Storage Filter: ((b IS NULL) AND (a = 1) AND (c = 1) AND (d = 1)) +(2 rows) + +drop table rp_prefix_test1; +drop table rp_prefix_test2; +drop table rp_prefix_test3; +drop table hp_prefix_test; +-- +-- Check that gen_partprune_steps() detects self-contradiction from clauses +-- regardless of the order of the clauses (Here we use a custom operator to +-- prevent the equivclass.c machinery from reordering the clauses) +-- +create operator === ( + leftarg = int4, + rightarg = int4, + procedure = int4eq, + commutator = ===, + hashes +); +create operator class part_test_int4_ops2 +for type int4 +using hash as +operator 1 ===, +function 2 part_hashint4_noop(int4, int8); +create table hp_contradict_test (a int, b int) partition by hash (a part_test_int4_ops2, b part_test_int4_ops2); +create table hp_contradict_test_p1 partition of hp_contradict_test for values with (modulus 2, remainder 0); +create table hp_contradict_test_p2 partition of hp_contradict_test for values with (modulus 2, remainder 1); +explain (costs off) select * from hp_contradict_test where a is null and a === 1 and b === 1; + QUERY PLAN +--------------------------- + Result hp_contradict_test + One-Time Filter: false +(2 rows) + +explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 and a is null; + QUERY PLAN +--------------------------- + Result hp_contradict_test + One-Time Filter: false +(2 rows) + +drop table hp_contradict_test; +drop operator class part_test_int4_ops2 using hash; +drop operator ===(int4, int4); diff --git a/src/test/regress/expected/yb.port.pg_lsn.out b/src/test/regress/expected/yb.port.pg_lsn.out new file mode 100644 index 0000000000000..0fce799fb6307 --- /dev/null +++ b/src/test/regress/expected/yb.port.pg_lsn.out @@ -0,0 +1,224 @@ +-- +-- PG_LSN +-- +CREATE TABLE PG_LSN_TBL (f1 pg_lsn); +-- Largest and smallest input +INSERT INTO PG_LSN_TBL VALUES ('0/0'); +INSERT INTO PG_LSN_TBL VALUES ('FFFFFFFF/FFFFFFFF'); +-- Incorrect input +INSERT INTO PG_LSN_TBL VALUES ('G/0'); +ERROR: invalid input syntax for type pg_lsn: "G/0" +LINE 1: INSERT INTO PG_LSN_TBL VALUES ('G/0'); + ^ +INSERT INTO PG_LSN_TBL VALUES ('-1/0'); +ERROR: invalid input syntax for type pg_lsn: "-1/0" +LINE 1: INSERT INTO PG_LSN_TBL VALUES ('-1/0'); + ^ +INSERT INTO PG_LSN_TBL VALUES (' 0/12345678'); +ERROR: invalid input syntax for type pg_lsn: " 0/12345678" +LINE 1: INSERT INTO PG_LSN_TBL VALUES (' 0/12345678'); + ^ +INSERT INTO PG_LSN_TBL VALUES ('ABCD/'); +ERROR: invalid input syntax for type pg_lsn: "ABCD/" +LINE 1: INSERT INTO PG_LSN_TBL VALUES ('ABCD/'); + ^ +INSERT INTO PG_LSN_TBL VALUES ('/ABCD'); +ERROR: invalid input syntax for type pg_lsn: "/ABCD" +LINE 1: INSERT INTO PG_LSN_TBL VALUES ('/ABCD'); + ^ +DROP TABLE PG_LSN_TBL; +-- Operators +SELECT '0/16AE7F8' = '0/16AE7F8'::pg_lsn; + ?column? +---------- + t +(1 row) + +SELECT '0/16AE7F8'::pg_lsn != '0/16AE7F7'; + ?column? +---------- + t +(1 row) + +SELECT '0/16AE7F7' < '0/16AE7F8'::pg_lsn; + ?column? +---------- + t +(1 row) + +SELECT '0/16AE7F8' > pg_lsn '0/16AE7F7'; + ?column? +---------- + t +(1 row) + +SELECT '0/16AE7F7'::pg_lsn - '0/16AE7F8'::pg_lsn; + ?column? +---------- + -1 +(1 row) + +SELECT '0/16AE7F8'::pg_lsn - '0/16AE7F7'::pg_lsn; + ?column? +---------- + 1 +(1 row) + +-- Check lsm and hash opclasses +EXPLAIN (COSTS OFF) +SELECT DISTINCT (i || '/' || j)::pg_lsn f + FROM generate_series(1, 10) i, + generate_series(1, 10) j, + generate_series(1, 5) k + WHERE i <= 10 AND j > 0 AND j <= 10 + ORDER BY f; + QUERY PLAN +-------------------------------------------------------------------------- + Sort + Sort Key: (((((i.i)::text || '/'::text) || (j.j)::text))::pg_lsn) + -> HashAggregate + Group Key: ((((i.i)::text || '/'::text) || (j.j)::text))::pg_lsn + -> Nested Loop + -> Function Scan on generate_series k + -> Materialize + -> Nested Loop + -> Function Scan on generate_series j + Filter: ((j > 0) AND (j <= 10)) + -> Function Scan on generate_series i + Filter: (i <= 10) +(12 rows) + +SELECT DISTINCT (i || '/' || j)::pg_lsn f + FROM generate_series(1, 10) i, + generate_series(1, 10) j, + generate_series(1, 5) k + WHERE i <= 10 AND j > 0 AND j <= 10 + ORDER BY f; + f +------- + 1/1 + 1/2 + 1/3 + 1/4 + 1/5 + 1/6 + 1/7 + 1/8 + 1/9 + 1/10 + 2/1 + 2/2 + 2/3 + 2/4 + 2/5 + 2/6 + 2/7 + 2/8 + 2/9 + 2/10 + 3/1 + 3/2 + 3/3 + 3/4 + 3/5 + 3/6 + 3/7 + 3/8 + 3/9 + 3/10 + 4/1 + 4/2 + 4/3 + 4/4 + 4/5 + 4/6 + 4/7 + 4/8 + 4/9 + 4/10 + 5/1 + 5/2 + 5/3 + 5/4 + 5/5 + 5/6 + 5/7 + 5/8 + 5/9 + 5/10 + 6/1 + 6/2 + 6/3 + 6/4 + 6/5 + 6/6 + 6/7 + 6/8 + 6/9 + 6/10 + 7/1 + 7/2 + 7/3 + 7/4 + 7/5 + 7/6 + 7/7 + 7/8 + 7/9 + 7/10 + 8/1 + 8/2 + 8/3 + 8/4 + 8/5 + 8/6 + 8/7 + 8/8 + 8/9 + 8/10 + 9/1 + 9/2 + 9/3 + 9/4 + 9/5 + 9/6 + 9/7 + 9/8 + 9/9 + 9/10 + 10/1 + 10/2 + 10/3 + 10/4 + 10/5 + 10/6 + 10/7 + 10/8 + 10/9 + 10/10 +(100 rows) + +-- Check ordering +CREATE TABLE PG_LSN_TBL_ASC(a INT, b PG_LSN, PRIMARY KEY(a HASH, b ASC)); +INSERT INTO PG_LSN_TBL_ASC VALUES + (1, 'DEADBEAF/DEADBEAF'), + (1, '7FFFFFFF/FFFFFFFF'), + (1, '80000000/00000000'), + (1, '0/0'), + (1, '0/1'), + (1, 'FFFFFFFF/FFFFFFFF'), + (1, '0/64'), + (1, 'FFFFFFFF/FFFFFF9C'); +SELECT b FROM PG_LSN_TBL_ASC WHERE a=1; + b +------------------- + 0/0 + 0/1 + 0/64 + 7FFFFFFF/FFFFFFFF + 80000000/0 + DEADBEAF/DEADBEAF + FFFFFFFF/FFFFFF9C + FFFFFFFF/FFFFFFFF +(8 rows) + diff --git a/src/test/regress/expected/yb.port.plpgsql.out b/src/test/regress/expected/yb.port.plpgsql.out new file mode 100644 index 0000000000000..d876b43d56263 --- /dev/null +++ b/src/test/regress/expected/yb.port.plpgsql.out @@ -0,0 +1,5962 @@ +-- +-- PLPGSQL +-- +-- Scenario: +-- +-- A building with a modern TP cable installation where any +-- of the wall connectors can be used to plug in phones, +-- ethernet interfaces or local office hubs. The backside +-- of the wall connectors is wired to one of several patch- +-- fields in the building. +-- +-- In the patchfields, there are hubs and all the slots +-- representing the wall connectors. In addition there are +-- slots that can represent a phone line from the central +-- phone system. +-- +-- Triggers ensure consistency of the patching information. +-- +-- Functions are used to build up powerful views that let +-- you look behind the wall when looking at a patchfield +-- or into a room. +-- +create table Room ( + roomno char(8), + comment text +); +create unique index Room_rno on Room using btree (roomno bpchar_ops); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +create table WSlot ( + slotname char(20), + roomno char(8), + slotlink char(20), + backlink char(20) +); +create unique index WSlot_name on WSlot using btree (slotname bpchar_ops); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +create table PField ( + name text, + comment text +); +create unique index PField_name on PField using btree (name text_ops); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +create table PSlot ( + slotname char(20), + pfname text, + slotlink char(20), + backlink char(20) +); +create unique index PSlot_name on PSlot using btree (slotname bpchar_ops); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +create table PLine ( + slotname char(20), + phonenumber char(20), + comment text, + backlink char(20) +); +create unique index PLine_name on PLine using btree (slotname bpchar_ops); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +create table Hub ( + name char(14), + comment text, + nslots integer +); +create unique index Hub_name on Hub using btree (name bpchar_ops); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +create table HSlot ( + slotname char(20), + hubname char(14), + slotno integer, + slotlink char(20) +); +create unique index HSlot_name on HSlot using btree (slotname bpchar_ops); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +create index HSlot_hubname on HSlot using btree (hubname bpchar_ops); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +create table System ( + name text, + comment text +); +create unique index System_name on System using btree (name text_ops); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +create table IFace ( + slotname char(20), + sysname text, + ifname text, + slotlink char(20) +); +create unique index IFace_name on IFace using btree (slotname bpchar_ops); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +create table PHone ( + slotname char(20), + comment text, + slotlink char(20) +); +create unique index PHone_name on PHone using btree (slotname bpchar_ops); +NOTICE: substituting access method "lsm" for "btree" in YugabyteDB +-- ************************************************************ +-- * +-- * Trigger procedures and functions for the patchfield +-- * test of PL/pgSQL +-- * +-- ************************************************************ +-- ************************************************************ +-- * AFTER UPDATE on Room +-- * - If room no changes let wall slots follow +-- ************************************************************ +create function tg_room_au() returns trigger as ' +begin + if new.roomno != old.roomno then + update WSlot set roomno = new.roomno where roomno = old.roomno; + end if; + return new; +end; +' language plpgsql; +create trigger tg_room_au after update + on Room for each row execute procedure tg_room_au(); +-- ************************************************************ +-- * AFTER DELETE on Room +-- * - delete wall slots in this room +-- ************************************************************ +create function tg_room_ad() returns trigger as ' +begin + delete from WSlot where roomno = old.roomno; + return old; +end; +' language plpgsql; +create trigger tg_room_ad after delete + on Room for each row execute procedure tg_room_ad(); +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on WSlot +-- * - Check that room exists +-- ************************************************************ +create function tg_wslot_biu() returns trigger as $$ +begin + if count(*) = 0 from Room where roomno = new.roomno then + raise exception 'Room % does not exist', new.roomno; + end if; + return new; +end; +$$ language plpgsql; +create trigger tg_wslot_biu before insert or update + on WSlot for each row execute procedure tg_wslot_biu(); +-- ************************************************************ +-- * AFTER UPDATE on PField +-- * - Let PSlots of this field follow +-- ************************************************************ +create function tg_pfield_au() returns trigger as ' +begin + if new.name != old.name then + update PSlot set pfname = new.name where pfname = old.name; + end if; + return new; +end; +' language plpgsql; +create trigger tg_pfield_au after update + on PField for each row execute procedure tg_pfield_au(); +-- ************************************************************ +-- * AFTER DELETE on PField +-- * - Remove all slots of this patchfield +-- ************************************************************ +create function tg_pfield_ad() returns trigger as ' +begin + delete from PSlot where pfname = old.name; + return old; +end; +' language plpgsql; +create trigger tg_pfield_ad after delete + on PField for each row execute procedure tg_pfield_ad(); +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on PSlot +-- * - Ensure that our patchfield does exist +-- ************************************************************ +create function tg_pslot_biu() returns trigger as $proc$ +declare + pfrec record; + ps alias for new; +begin + select into pfrec * from PField where name = ps.pfname; + if not found then + raise exception $$Patchfield "%" does not exist$$, ps.pfname; + end if; + return ps; +end; +$proc$ language plpgsql; +create trigger tg_pslot_biu before insert or update + on PSlot for each row execute procedure tg_pslot_biu(); +-- ************************************************************ +-- * AFTER UPDATE on System +-- * - If system name changes let interfaces follow +-- ************************************************************ +create function tg_system_au() returns trigger as ' +begin + if new.name != old.name then + update IFace set sysname = new.name where sysname = old.name; + end if; + return new; +end; +' language plpgsql; +create trigger tg_system_au after update + on System for each row execute procedure tg_system_au(); +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on IFace +-- * - set the slotname to IF.sysname.ifname +-- ************************************************************ +create function tg_iface_biu() returns trigger as $$ +declare + sname text; + sysrec record; +begin + select into sysrec * from system where name = new.sysname; + if not found then + raise exception $q$system "%" does not exist$q$, new.sysname; + end if; + sname := 'IF.' || new.sysname; + sname := sname || '.'; + sname := sname || new.ifname; + if length(sname) > 20 then + raise exception 'IFace slotname "%" too long (20 char max)', sname; + end if; + new.slotname := sname; + return new; +end; +$$ language plpgsql; +create trigger tg_iface_biu before insert or update + on IFace for each row execute procedure tg_iface_biu(); +-- ************************************************************ +-- * AFTER INSERT or UPDATE or DELETE on Hub +-- * - insert/delete/rename slots as required +-- ************************************************************ +create function tg_hub_a() returns trigger as ' +declare + hname text; + dummy integer; +begin + if tg_op = ''INSERT'' then + dummy := tg_hub_adjustslots(new.name, 0, new.nslots); + return new; + end if; + if tg_op = ''UPDATE'' then + if new.name != old.name then + update HSlot set hubname = new.name where hubname = old.name; + end if; + dummy := tg_hub_adjustslots(new.name, old.nslots, new.nslots); + return new; + end if; + if tg_op = ''DELETE'' then + dummy := tg_hub_adjustslots(old.name, old.nslots, 0); + return old; + end if; +end; +' language plpgsql; +create trigger tg_hub_a after insert or update or delete + on Hub for each row execute procedure tg_hub_a(); +-- ************************************************************ +-- * Support function to add/remove slots of Hub +-- ************************************************************ +create function tg_hub_adjustslots(hname bpchar, + oldnslots integer, + newnslots integer) +returns integer as ' +begin + if newnslots = oldnslots then + return 0; + end if; + if newnslots < oldnslots then + delete from HSlot where hubname = hname and slotno > newnslots; + return 0; + end if; + for i in oldnslots + 1 .. newnslots loop + insert into HSlot (slotname, hubname, slotno, slotlink) + values (''HS.dummy'', hname, i, ''''); + end loop; + return 0; +end +' language plpgsql; +-- Test comments +COMMENT ON FUNCTION tg_hub_adjustslots_wrong(bpchar, integer, integer) IS 'function with args'; +ERROR: function tg_hub_adjustslots_wrong(character, integer, integer) does not exist +COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS 'function with args'; +COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS NULL; +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on HSlot +-- * - prevent from manual manipulation +-- * - set the slotname to HS.hubname.slotno +-- ************************************************************ +create function tg_hslot_biu() returns trigger as ' +declare + sname text; + xname HSlot.slotname%TYPE; + hubrec record; +begin + select into hubrec * from Hub where name = new.hubname; + if not found then + raise exception ''no manual manipulation of HSlot''; + end if; + if new.slotno < 1 or new.slotno > hubrec.nslots then + raise exception ''no manual manipulation of HSlot''; + end if; + if tg_op = ''UPDATE'' and new.hubname != old.hubname then + if count(*) > 0 from Hub where name = old.hubname then + raise exception ''no manual manipulation of HSlot''; + end if; + end if; + sname := ''HS.'' || trim(new.hubname); + sname := sname || ''.''; + sname := sname || new.slotno::text; + if length(sname) > 20 then + raise exception ''HSlot slotname "%" too long (20 char max)'', sname; + end if; + new.slotname := sname; + return new; +end; +' language plpgsql; +create trigger tg_hslot_biu before insert or update + on HSlot for each row execute procedure tg_hslot_biu(); +-- ************************************************************ +-- * BEFORE DELETE on HSlot +-- * - prevent from manual manipulation +-- ************************************************************ +create function tg_hslot_bd() returns trigger as ' +declare + hubrec record; +begin + select into hubrec * from Hub where name = old.hubname; + if not found then + return old; + end if; + if old.slotno > hubrec.nslots then + return old; + end if; + raise exception ''no manual manipulation of HSlot''; +end; +' language plpgsql; +create trigger tg_hslot_bd before delete + on HSlot for each row execute procedure tg_hslot_bd(); +-- ************************************************************ +-- * BEFORE INSERT on all slots +-- * - Check name prefix +-- ************************************************************ +create function tg_chkslotname() returns trigger as ' +begin + if substr(new.slotname, 1, 2) != tg_argv[0] then + raise exception ''slotname must begin with %'', tg_argv[0]; + end if; + return new; +end; +' language plpgsql; +create trigger tg_chkslotname before insert + on PSlot for each row execute procedure tg_chkslotname('PS'); +create trigger tg_chkslotname before insert + on WSlot for each row execute procedure tg_chkslotname('WS'); +create trigger tg_chkslotname before insert + on PLine for each row execute procedure tg_chkslotname('PL'); +create trigger tg_chkslotname before insert + on IFace for each row execute procedure tg_chkslotname('IF'); +create trigger tg_chkslotname before insert + on PHone for each row execute procedure tg_chkslotname('PH'); +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on all slots with slotlink +-- * - Set slotlink to empty string if NULL value given +-- ************************************************************ +create function tg_chkslotlink() returns trigger as ' +begin + if new.slotlink isnull then + new.slotlink := ''''; + end if; + return new; +end; +' language plpgsql; +create trigger tg_chkslotlink before insert or update + on PSlot for each row execute procedure tg_chkslotlink(); +create trigger tg_chkslotlink before insert or update + on WSlot for each row execute procedure tg_chkslotlink(); +create trigger tg_chkslotlink before insert or update + on IFace for each row execute procedure tg_chkslotlink(); +create trigger tg_chkslotlink before insert or update + on HSlot for each row execute procedure tg_chkslotlink(); +create trigger tg_chkslotlink before insert or update + on PHone for each row execute procedure tg_chkslotlink(); +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on all slots with backlink +-- * - Set backlink to empty string if NULL value given +-- ************************************************************ +create function tg_chkbacklink() returns trigger as ' +begin + if new.backlink isnull then + new.backlink := ''''; + end if; + return new; +end; +' language plpgsql; +create trigger tg_chkbacklink before insert or update + on PSlot for each row execute procedure tg_chkbacklink(); +create trigger tg_chkbacklink before insert or update + on WSlot for each row execute procedure tg_chkbacklink(); +create trigger tg_chkbacklink before insert or update + on PLine for each row execute procedure tg_chkbacklink(); +-- ************************************************************ +-- * BEFORE UPDATE on PSlot +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_pslot_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from PSlot where slotname = old.slotname; + insert into PSlot ( + slotname, + pfname, + slotlink, + backlink + ) values ( + new.slotname, + new.pfname, + new.slotlink, + new.backlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_pslot_bu before update + on PSlot for each row execute procedure tg_pslot_bu(); +-- ************************************************************ +-- * BEFORE UPDATE on WSlot +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_wslot_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from WSlot where slotname = old.slotname; + insert into WSlot ( + slotname, + roomno, + slotlink, + backlink + ) values ( + new.slotname, + new.roomno, + new.slotlink, + new.backlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_wslot_bu before update + on WSlot for each row execute procedure tg_Wslot_bu(); +-- ************************************************************ +-- * BEFORE UPDATE on PLine +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_pline_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from PLine where slotname = old.slotname; + insert into PLine ( + slotname, + phonenumber, + comment, + backlink + ) values ( + new.slotname, + new.phonenumber, + new.comment, + new.backlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_pline_bu before update + on PLine for each row execute procedure tg_pline_bu(); +-- ************************************************************ +-- * BEFORE UPDATE on IFace +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_iface_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from IFace where slotname = old.slotname; + insert into IFace ( + slotname, + sysname, + ifname, + slotlink + ) values ( + new.slotname, + new.sysname, + new.ifname, + new.slotlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_iface_bu before update + on IFace for each row execute procedure tg_iface_bu(); +-- ************************************************************ +-- * BEFORE UPDATE on HSlot +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_hslot_bu() returns trigger as ' +begin + if new.slotname != old.slotname or new.hubname != old.hubname then + delete from HSlot where slotname = old.slotname; + insert into HSlot ( + slotname, + hubname, + slotno, + slotlink + ) values ( + new.slotname, + new.hubname, + new.slotno, + new.slotlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_hslot_bu before update + on HSlot for each row execute procedure tg_hslot_bu(); +-- ************************************************************ +-- * BEFORE UPDATE on PHone +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_phone_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from PHone where slotname = old.slotname; + insert into PHone ( + slotname, + comment, + slotlink + ) values ( + new.slotname, + new.comment, + new.slotlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_phone_bu before update + on PHone for each row execute procedure tg_phone_bu(); +-- ************************************************************ +-- * AFTER INSERT or UPDATE or DELETE on slot with backlink +-- * - Ensure that the opponent correctly points back to us +-- ************************************************************ +create function tg_backlink_a() returns trigger as ' +declare + dummy integer; +begin + if tg_op = ''INSERT'' then + if new.backlink != '''' then + dummy := tg_backlink_set(new.backlink, new.slotname); + end if; + return new; + end if; + if tg_op = ''UPDATE'' then + if new.backlink != old.backlink then + if old.backlink != '''' then + dummy := tg_backlink_unset(old.backlink, old.slotname); + end if; + if new.backlink != '''' then + dummy := tg_backlink_set(new.backlink, new.slotname); + end if; + else + if new.slotname != old.slotname and new.backlink != '''' then + dummy := tg_slotlink_set(new.backlink, new.slotname); + end if; + end if; + return new; + end if; + if tg_op = ''DELETE'' then + if old.backlink != '''' then + dummy := tg_backlink_unset(old.backlink, old.slotname); + end if; + return old; + end if; +end; +' language plpgsql; +create trigger tg_backlink_a after insert or update or delete + on PSlot for each row execute procedure tg_backlink_a('PS'); +create trigger tg_backlink_a after insert or update or delete + on WSlot for each row execute procedure tg_backlink_a('WS'); +create trigger tg_backlink_a after insert or update or delete + on PLine for each row execute procedure tg_backlink_a('PL'); +-- ************************************************************ +-- * Support function to set the opponents backlink field +-- * if it does not already point to the requested slot +-- ************************************************************ +create function tg_backlink_set(myname bpchar, blname bpchar) +returns integer as ' +declare + mytype char(2); + link char(4); + rec record; +begin + mytype := substr(myname, 1, 2); + link := mytype || substr(blname, 1, 2); + if link = ''PLPL'' then + raise exception + ''backlink between two phone lines does not make sense''; + end if; + if link in (''PLWS'', ''WSPL'') then + raise exception + ''direct link of phone line to wall slot not permitted''; + end if; + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.backlink != blname then + update PSlot set backlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.backlink != blname then + update WSlot set backlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PL'' then + select into rec * from PLine where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.backlink != blname then + update PLine set backlink = blname where slotname = myname; + end if; + return 0; + end if; + raise exception ''illegal backlink beginning with %'', mytype; +end; +' language plpgsql; +-- ************************************************************ +-- * Support function to clear out the backlink field if +-- * it still points to specific slot +-- ************************************************************ +create function tg_backlink_unset(bpchar, bpchar) +returns integer as ' +declare + myname alias for $1; + blname alias for $2; + mytype char(2); + rec record; +begin + mytype := substr(myname, 1, 2); + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.backlink = blname then + update PSlot set backlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.backlink = blname then + update WSlot set backlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PL'' then + select into rec * from PLine where slotname = myname; + if not found then + return 0; + end if; + if rec.backlink = blname then + update PLine set backlink = '''' where slotname = myname; + end if; + return 0; + end if; +end +' language plpgsql; +-- ************************************************************ +-- * AFTER INSERT or UPDATE or DELETE on slot with slotlink +-- * - Ensure that the opponent correctly points back to us +-- ************************************************************ +create function tg_slotlink_a() returns trigger as ' +declare + dummy integer; +begin + if tg_op = ''INSERT'' then + if new.slotlink != '''' then + dummy := tg_slotlink_set(new.slotlink, new.slotname); + end if; + return new; + end if; + if tg_op = ''UPDATE'' then + if new.slotlink != old.slotlink then + if old.slotlink != '''' then + dummy := tg_slotlink_unset(old.slotlink, old.slotname); + end if; + if new.slotlink != '''' then + dummy := tg_slotlink_set(new.slotlink, new.slotname); + end if; + else + if new.slotname != old.slotname and new.slotlink != '''' then + dummy := tg_slotlink_set(new.slotlink, new.slotname); + end if; + end if; + return new; + end if; + if tg_op = ''DELETE'' then + if old.slotlink != '''' then + dummy := tg_slotlink_unset(old.slotlink, old.slotname); + end if; + return old; + end if; +end; +' language plpgsql; +create trigger tg_slotlink_a after insert or update or delete + on PSlot for each row execute procedure tg_slotlink_a('PS'); +create trigger tg_slotlink_a after insert or update or delete + on WSlot for each row execute procedure tg_slotlink_a('WS'); +create trigger tg_slotlink_a after insert or update or delete + on IFace for each row execute procedure tg_slotlink_a('IF'); +create trigger tg_slotlink_a after insert or update or delete + on HSlot for each row execute procedure tg_slotlink_a('HS'); +create trigger tg_slotlink_a after insert or update or delete + on PHone for each row execute procedure tg_slotlink_a('PH'); +-- ************************************************************ +-- * Support function to set the opponents slotlink field +-- * if it does not already point to the requested slot +-- ************************************************************ +create function tg_slotlink_set(bpchar, bpchar) +returns integer as ' +declare + myname alias for $1; + blname alias for $2; + mytype char(2); + link char(4); + rec record; +begin + mytype := substr(myname, 1, 2); + link := mytype || substr(blname, 1, 2); + if link = ''PHPH'' then + raise exception + ''slotlink between two phones does not make sense''; + end if; + if link in (''PHHS'', ''HSPH'') then + raise exception + ''link of phone to hub does not make sense''; + end if; + if link in (''PHIF'', ''IFPH'') then + raise exception + ''link of phone to hub does not make sense''; + end if; + if link in (''PSWS'', ''WSPS'') then + raise exception + ''slotlink from patchslot to wallslot not permitted''; + end if; + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update PSlot set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update WSlot set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''IF'' then + select into rec * from IFace where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update IFace set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''HS'' then + select into rec * from HSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update HSlot set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PH'' then + select into rec * from PHone where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update PHone set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + raise exception ''illegal slotlink beginning with %'', mytype; +end; +' language plpgsql; +-- ************************************************************ +-- * Support function to clear out the slotlink field if +-- * it still points to specific slot +-- ************************************************************ +create function tg_slotlink_unset(bpchar, bpchar) +returns integer as ' +declare + myname alias for $1; + blname alias for $2; + mytype char(2); + rec record; +begin + mytype := substr(myname, 1, 2); + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update PSlot set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update WSlot set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''IF'' then + select into rec * from IFace where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update IFace set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''HS'' then + select into rec * from HSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update HSlot set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PH'' then + select into rec * from PHone where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update PHone set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; +end; +' language plpgsql; +-- ************************************************************ +-- * Describe the backside of a patchfield slot +-- ************************************************************ +create function pslot_backlink_view(bpchar) +returns text as ' +<> +declare + rec record; + bltype char(2); + retval text; +begin + select into rec * from PSlot where slotname = $1; + if not found then + return ''''; + end if; + if rec.backlink = '''' then + return ''-''; + end if; + bltype := substr(rec.backlink, 1, 2); + if bltype = ''PL'' then + declare + rec record; + begin + select into rec * from PLine where slotname = "outer".rec.backlink; + retval := ''Phone line '' || trim(rec.phonenumber); + if rec.comment != '''' then + retval := retval || '' (''; + retval := retval || rec.comment; + retval := retval || '')''; + end if; + return retval; + end; + end if; + if bltype = ''WS'' then + select into rec * from WSlot where slotname = rec.backlink; + retval := trim(rec.slotname) || '' in room ''; + retval := retval || trim(rec.roomno); + retval := retval || '' -> ''; + return retval || wslot_slotlink_view(rec.slotname); + end if; + return rec.backlink; +end; +' language plpgsql; +-- ************************************************************ +-- * Describe the front of a patchfield slot +-- ************************************************************ +create function pslot_slotlink_view(bpchar) +returns text as ' +declare + psrec record; + sltype char(2); + retval text; +begin + select into psrec * from PSlot where slotname = $1; + if not found then + return ''''; + end if; + if psrec.slotlink = '''' then + return ''-''; + end if; + sltype := substr(psrec.slotlink, 1, 2); + if sltype = ''PS'' then + retval := trim(psrec.slotlink) || '' -> ''; + return retval || pslot_backlink_view(psrec.slotlink); + end if; + if sltype = ''HS'' then + retval := comment from Hub H, HSlot HS + where HS.slotname = psrec.slotlink + and H.name = HS.hubname; + retval := retval || '' slot ''; + retval := retval || slotno::text from HSlot + where slotname = psrec.slotlink; + return retval; + end if; + return psrec.slotlink; +end; +' language plpgsql; +-- ************************************************************ +-- * Describe the front of a wall connector slot +-- ************************************************************ +create function wslot_slotlink_view(bpchar) +returns text as ' +declare + rec record; + sltype char(2); + retval text; +begin + select into rec * from WSlot where slotname = $1; + if not found then + return ''''; + end if; + if rec.slotlink = '''' then + return ''-''; + end if; + sltype := substr(rec.slotlink, 1, 2); + if sltype = ''PH'' then + select into rec * from PHone where slotname = rec.slotlink; + retval := ''Phone '' || trim(rec.slotname); + if rec.comment != '''' then + retval := retval || '' (''; + retval := retval || rec.comment; + retval := retval || '')''; + end if; + return retval; + end if; + if sltype = ''IF'' then + declare + syrow System%RowType; + ifrow IFace%ROWTYPE; + begin + select into ifrow * from IFace where slotname = rec.slotlink; + select into syrow * from System where name = ifrow.sysname; + retval := syrow.name || '' IF ''; + retval := retval || ifrow.ifname; + if syrow.comment != '''' then + retval := retval || '' (''; + retval := retval || syrow.comment; + retval := retval || '')''; + end if; + return retval; + end; + end if; + return rec.slotlink; +end; +' language plpgsql; +-- ************************************************************ +-- * View of a patchfield describing backside and patches +-- ************************************************************ +create view Pfield_v1 as select PF.pfname, PF.slotname, + pslot_backlink_view(PF.slotname) as backside, + pslot_slotlink_view(PF.slotname) as patch + from PSlot PF; +-- +-- First we build the house - so we create the rooms +-- +insert into Room values ('001', 'Entrance'); +insert into Room values ('002', 'Office'); +insert into Room values ('003', 'Office'); +insert into Room values ('004', 'Technical'); +insert into Room values ('101', 'Office'); +insert into Room values ('102', 'Conference'); +insert into Room values ('103', 'Restroom'); +insert into Room values ('104', 'Technical'); +insert into Room values ('105', 'Office'); +insert into Room values ('106', 'Office'); +-- +-- Second we install the wall connectors +-- +insert into WSlot values ('WS.001.1a', '001', '', ''); +insert into WSlot values ('WS.001.1b', '001', '', ''); +insert into WSlot values ('WS.001.2a', '001', '', ''); +insert into WSlot values ('WS.001.2b', '001', '', ''); +insert into WSlot values ('WS.001.3a', '001', '', ''); +insert into WSlot values ('WS.001.3b', '001', '', ''); +insert into WSlot values ('WS.002.1a', '002', '', ''); +insert into WSlot values ('WS.002.1b', '002', '', ''); +insert into WSlot values ('WS.002.2a', '002', '', ''); +insert into WSlot values ('WS.002.2b', '002', '', ''); +insert into WSlot values ('WS.002.3a', '002', '', ''); +insert into WSlot values ('WS.002.3b', '002', '', ''); +insert into WSlot values ('WS.003.1a', '003', '', ''); +insert into WSlot values ('WS.003.1b', '003', '', ''); +insert into WSlot values ('WS.003.2a', '003', '', ''); +insert into WSlot values ('WS.003.2b', '003', '', ''); +insert into WSlot values ('WS.003.3a', '003', '', ''); +insert into WSlot values ('WS.003.3b', '003', '', ''); +insert into WSlot values ('WS.101.1a', '101', '', ''); +insert into WSlot values ('WS.101.1b', '101', '', ''); +insert into WSlot values ('WS.101.2a', '101', '', ''); +insert into WSlot values ('WS.101.2b', '101', '', ''); +insert into WSlot values ('WS.101.3a', '101', '', ''); +insert into WSlot values ('WS.101.3b', '101', '', ''); +insert into WSlot values ('WS.102.1a', '102', '', ''); +insert into WSlot values ('WS.102.1b', '102', '', ''); +insert into WSlot values ('WS.102.2a', '102', '', ''); +insert into WSlot values ('WS.102.2b', '102', '', ''); +insert into WSlot values ('WS.102.3a', '102', '', ''); +insert into WSlot values ('WS.102.3b', '102', '', ''); +insert into WSlot values ('WS.105.1a', '105', '', ''); +insert into WSlot values ('WS.105.1b', '105', '', ''); +insert into WSlot values ('WS.105.2a', '105', '', ''); +insert into WSlot values ('WS.105.2b', '105', '', ''); +insert into WSlot values ('WS.105.3a', '105', '', ''); +insert into WSlot values ('WS.105.3b', '105', '', ''); +insert into WSlot values ('WS.106.1a', '106', '', ''); +insert into WSlot values ('WS.106.1b', '106', '', ''); +insert into WSlot values ('WS.106.2a', '106', '', ''); +insert into WSlot values ('WS.106.2b', '106', '', ''); +insert into WSlot values ('WS.106.3a', '106', '', ''); +insert into WSlot values ('WS.106.3b', '106', '', ''); +-- +-- Now create the patch fields and their slots +-- +insert into PField values ('PF0_1', 'Wallslots basement'); +-- +-- The cables for these will be made later, so they are unconnected for now +-- +insert into PSlot values ('PS.base.a1', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a2', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a3', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a4', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a5', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a6', 'PF0_1', '', ''); +-- +-- These are already wired to the wall connectors +-- +insert into PSlot values ('PS.base.b1', 'PF0_1', '', 'WS.002.1a'); +insert into PSlot values ('PS.base.b2', 'PF0_1', '', 'WS.002.1b'); +insert into PSlot values ('PS.base.b3', 'PF0_1', '', 'WS.002.2a'); +insert into PSlot values ('PS.base.b4', 'PF0_1', '', 'WS.002.2b'); +insert into PSlot values ('PS.base.b5', 'PF0_1', '', 'WS.002.3a'); +insert into PSlot values ('PS.base.b6', 'PF0_1', '', 'WS.002.3b'); +insert into PSlot values ('PS.base.c1', 'PF0_1', '', 'WS.003.1a'); +insert into PSlot values ('PS.base.c2', 'PF0_1', '', 'WS.003.1b'); +insert into PSlot values ('PS.base.c3', 'PF0_1', '', 'WS.003.2a'); +insert into PSlot values ('PS.base.c4', 'PF0_1', '', 'WS.003.2b'); +insert into PSlot values ('PS.base.c5', 'PF0_1', '', 'WS.003.3a'); +insert into PSlot values ('PS.base.c6', 'PF0_1', '', 'WS.003.3b'); +-- +-- This patchfield will be renamed later into PF0_2 - so its +-- slots references in pfname should follow +-- +insert into PField values ('PF0_X', 'Phonelines basement'); +insert into PSlot values ('PS.base.ta1', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta2', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta3', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta4', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta5', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta6', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb1', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb2', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb3', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb4', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb5', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb6', 'PF0_X', '', ''); +insert into PField values ('PF1_1', 'Wallslots first floor'); +insert into PSlot values ('PS.first.a1', 'PF1_1', '', 'WS.101.1a'); +insert into PSlot values ('PS.first.a2', 'PF1_1', '', 'WS.101.1b'); +insert into PSlot values ('PS.first.a3', 'PF1_1', '', 'WS.101.2a'); +insert into PSlot values ('PS.first.a4', 'PF1_1', '', 'WS.101.2b'); +insert into PSlot values ('PS.first.a5', 'PF1_1', '', 'WS.101.3a'); +insert into PSlot values ('PS.first.a6', 'PF1_1', '', 'WS.101.3b'); +insert into PSlot values ('PS.first.b1', 'PF1_1', '', 'WS.102.1a'); +insert into PSlot values ('PS.first.b2', 'PF1_1', '', 'WS.102.1b'); +insert into PSlot values ('PS.first.b3', 'PF1_1', '', 'WS.102.2a'); +insert into PSlot values ('PS.first.b4', 'PF1_1', '', 'WS.102.2b'); +insert into PSlot values ('PS.first.b5', 'PF1_1', '', 'WS.102.3a'); +insert into PSlot values ('PS.first.b6', 'PF1_1', '', 'WS.102.3b'); +insert into PSlot values ('PS.first.c1', 'PF1_1', '', 'WS.105.1a'); +insert into PSlot values ('PS.first.c2', 'PF1_1', '', 'WS.105.1b'); +insert into PSlot values ('PS.first.c3', 'PF1_1', '', 'WS.105.2a'); +insert into PSlot values ('PS.first.c4', 'PF1_1', '', 'WS.105.2b'); +insert into PSlot values ('PS.first.c5', 'PF1_1', '', 'WS.105.3a'); +insert into PSlot values ('PS.first.c6', 'PF1_1', '', 'WS.105.3b'); +insert into PSlot values ('PS.first.d1', 'PF1_1', '', 'WS.106.1a'); +insert into PSlot values ('PS.first.d2', 'PF1_1', '', 'WS.106.1b'); +insert into PSlot values ('PS.first.d3', 'PF1_1', '', 'WS.106.2a'); +insert into PSlot values ('PS.first.d4', 'PF1_1', '', 'WS.106.2b'); +insert into PSlot values ('PS.first.d5', 'PF1_1', '', 'WS.106.3a'); +insert into PSlot values ('PS.first.d6', 'PF1_1', '', 'WS.106.3b'); +-- +-- Now we wire the wall connectors 1a-2a in room 001 to the +-- patchfield. In the second update we make an error, and +-- correct it after +-- +update PSlot set backlink = 'WS.001.1a' where slotname = 'PS.base.a1'; +update PSlot set backlink = 'WS.001.1b' where slotname = 'PS.base.a3'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | PS.base.a1 + WS.001.1b | 001 | | PS.base.a3 + WS.001.2a | 001 | | + WS.001.2b | 001 | | + WS.001.3a | 001 | | + WS.001.3b | 001 | | +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | + PS.base.a3 | PF0_1 | | WS.001.1b + PS.base.a4 | PF0_1 | | + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | +(6 rows) + +update PSlot set backlink = 'WS.001.2a' where slotname = 'PS.base.a3'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | PS.base.a1 + WS.001.1b | 001 | | + WS.001.2a | 001 | | PS.base.a3 + WS.001.2b | 001 | | + WS.001.3a | 001 | | + WS.001.3b | 001 | | +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | +(6 rows) + +update PSlot set backlink = 'WS.001.1b' where slotname = 'PS.base.a2'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | PS.base.a1 + WS.001.1b | 001 | | PS.base.a2 + WS.001.2a | 001 | | PS.base.a3 + WS.001.2b | 001 | | + WS.001.3a | 001 | | + WS.001.3b | 001 | | +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | WS.001.1b + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | +(6 rows) + +-- +-- Same procedure for 2b-3b but this time updating the WSlot instead +-- of the PSlot. Due to the triggers the result is the same: +-- WSlot and corresponding PSlot point to each other. +-- +update WSlot set backlink = 'PS.base.a4' where slotname = 'WS.001.2b'; +update WSlot set backlink = 'PS.base.a6' where slotname = 'WS.001.3a'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | PS.base.a1 + WS.001.1b | 001 | | PS.base.a2 + WS.001.2a | 001 | | PS.base.a3 + WS.001.2b | 001 | | PS.base.a4 + WS.001.3a | 001 | | PS.base.a6 + WS.001.3b | 001 | | +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | WS.001.1b + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | WS.001.2b + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | WS.001.3a +(6 rows) + +update WSlot set backlink = 'PS.base.a6' where slotname = 'WS.001.3b'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | PS.base.a1 + WS.001.1b | 001 | | PS.base.a2 + WS.001.2a | 001 | | PS.base.a3 + WS.001.2b | 001 | | PS.base.a4 + WS.001.3a | 001 | | + WS.001.3b | 001 | | PS.base.a6 +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | WS.001.1b + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | WS.001.2b + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | WS.001.3b +(6 rows) + +update WSlot set backlink = 'PS.base.a5' where slotname = 'WS.001.3a'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | PS.base.a1 + WS.001.1b | 001 | | PS.base.a2 + WS.001.2a | 001 | | PS.base.a3 + WS.001.2b | 001 | | PS.base.a4 + WS.001.3a | 001 | | PS.base.a5 + WS.001.3b | 001 | | PS.base.a6 +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | WS.001.1b + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | WS.001.2b + PS.base.a5 | PF0_1 | | WS.001.3a + PS.base.a6 | PF0_1 | | WS.001.3b +(6 rows) + +insert into PField values ('PF1_2', 'Phonelines first floor'); +insert into PSlot values ('PS.first.ta1', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta2', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta3', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta4', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta5', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta6', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb1', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb2', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb3', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb4', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb5', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb6', 'PF1_2', '', ''); +-- +-- Fix the wrong name for patchfield PF0_2 +-- +update PField set name = 'PF0_2' where name = 'PF0_X'; +select * from PSlot order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | WS.001.1b + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | WS.001.2b + PS.base.a5 | PF0_1 | | WS.001.3a + PS.base.a6 | PF0_1 | | WS.001.3b + PS.base.b1 | PF0_1 | | WS.002.1a + PS.base.b2 | PF0_1 | | WS.002.1b + PS.base.b3 | PF0_1 | | WS.002.2a + PS.base.b4 | PF0_1 | | WS.002.2b + PS.base.b5 | PF0_1 | | WS.002.3a + PS.base.b6 | PF0_1 | | WS.002.3b + PS.base.c1 | PF0_1 | | WS.003.1a + PS.base.c2 | PF0_1 | | WS.003.1b + PS.base.c3 | PF0_1 | | WS.003.2a + PS.base.c4 | PF0_1 | | WS.003.2b + PS.base.c5 | PF0_1 | | WS.003.3a + PS.base.c6 | PF0_1 | | WS.003.3b + PS.base.ta1 | PF0_2 | | + PS.base.ta2 | PF0_2 | | + PS.base.ta3 | PF0_2 | | + PS.base.ta4 | PF0_2 | | + PS.base.ta5 | PF0_2 | | + PS.base.ta6 | PF0_2 | | + PS.base.tb1 | PF0_2 | | + PS.base.tb2 | PF0_2 | | + PS.base.tb3 | PF0_2 | | + PS.base.tb4 | PF0_2 | | + PS.base.tb5 | PF0_2 | | + PS.base.tb6 | PF0_2 | | + PS.first.a1 | PF1_1 | | WS.101.1a + PS.first.a2 | PF1_1 | | WS.101.1b + PS.first.a3 | PF1_1 | | WS.101.2a + PS.first.a4 | PF1_1 | | WS.101.2b + PS.first.a5 | PF1_1 | | WS.101.3a + PS.first.a6 | PF1_1 | | WS.101.3b + PS.first.b1 | PF1_1 | | WS.102.1a + PS.first.b2 | PF1_1 | | WS.102.1b + PS.first.b3 | PF1_1 | | WS.102.2a + PS.first.b4 | PF1_1 | | WS.102.2b + PS.first.b5 | PF1_1 | | WS.102.3a + PS.first.b6 | PF1_1 | | WS.102.3b + PS.first.c1 | PF1_1 | | WS.105.1a + PS.first.c2 | PF1_1 | | WS.105.1b + PS.first.c3 | PF1_1 | | WS.105.2a + PS.first.c4 | PF1_1 | | WS.105.2b + PS.first.c5 | PF1_1 | | WS.105.3a + PS.first.c6 | PF1_1 | | WS.105.3b + PS.first.d1 | PF1_1 | | WS.106.1a + PS.first.d2 | PF1_1 | | WS.106.1b + PS.first.d3 | PF1_1 | | WS.106.2a + PS.first.d4 | PF1_1 | | WS.106.2b + PS.first.d5 | PF1_1 | | WS.106.3a + PS.first.d6 | PF1_1 | | WS.106.3b + PS.first.ta1 | PF1_2 | | + PS.first.ta2 | PF1_2 | | + PS.first.ta3 | PF1_2 | | + PS.first.ta4 | PF1_2 | | + PS.first.ta5 | PF1_2 | | + PS.first.ta6 | PF1_2 | | + PS.first.tb1 | PF1_2 | | + PS.first.tb2 | PF1_2 | | + PS.first.tb3 | PF1_2 | | + PS.first.tb4 | PF1_2 | | + PS.first.tb5 | PF1_2 | | + PS.first.tb6 | PF1_2 | | +(66 rows) + +select * from WSlot order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | PS.base.a1 + WS.001.1b | 001 | | PS.base.a2 + WS.001.2a | 001 | | PS.base.a3 + WS.001.2b | 001 | | PS.base.a4 + WS.001.3a | 001 | | PS.base.a5 + WS.001.3b | 001 | | PS.base.a6 + WS.002.1a | 002 | | PS.base.b1 + WS.002.1b | 002 | | PS.base.b2 + WS.002.2a | 002 | | PS.base.b3 + WS.002.2b | 002 | | PS.base.b4 + WS.002.3a | 002 | | PS.base.b5 + WS.002.3b | 002 | | PS.base.b6 + WS.003.1a | 003 | | PS.base.c1 + WS.003.1b | 003 | | PS.base.c2 + WS.003.2a | 003 | | PS.base.c3 + WS.003.2b | 003 | | PS.base.c4 + WS.003.3a | 003 | | PS.base.c5 + WS.003.3b | 003 | | PS.base.c6 + WS.101.1a | 101 | | PS.first.a1 + WS.101.1b | 101 | | PS.first.a2 + WS.101.2a | 101 | | PS.first.a3 + WS.101.2b | 101 | | PS.first.a4 + WS.101.3a | 101 | | PS.first.a5 + WS.101.3b | 101 | | PS.first.a6 + WS.102.1a | 102 | | PS.first.b1 + WS.102.1b | 102 | | PS.first.b2 + WS.102.2a | 102 | | PS.first.b3 + WS.102.2b | 102 | | PS.first.b4 + WS.102.3a | 102 | | PS.first.b5 + WS.102.3b | 102 | | PS.first.b6 + WS.105.1a | 105 | | PS.first.c1 + WS.105.1b | 105 | | PS.first.c2 + WS.105.2a | 105 | | PS.first.c3 + WS.105.2b | 105 | | PS.first.c4 + WS.105.3a | 105 | | PS.first.c5 + WS.105.3b | 105 | | PS.first.c6 + WS.106.1a | 106 | | PS.first.d1 + WS.106.1b | 106 | | PS.first.d2 + WS.106.2a | 106 | | PS.first.d3 + WS.106.2b | 106 | | PS.first.d4 + WS.106.3a | 106 | | PS.first.d5 + WS.106.3b | 106 | | PS.first.d6 +(42 rows) + +-- +-- Install the central phone system and create the phone numbers. +-- They are wired on insert to the patchfields. Again the +-- triggers automatically tell the PSlots to update their +-- backlink field. +-- +insert into PLine values ('PL.001', '-0', 'Central call', 'PS.base.ta1'); +insert into PLine values ('PL.002', '-101', '', 'PS.base.ta2'); +insert into PLine values ('PL.003', '-102', '', 'PS.base.ta3'); +insert into PLine values ('PL.004', '-103', '', 'PS.base.ta5'); +insert into PLine values ('PL.005', '-104', '', 'PS.base.ta6'); +insert into PLine values ('PL.006', '-106', '', 'PS.base.tb2'); +insert into PLine values ('PL.007', '-108', '', 'PS.base.tb3'); +insert into PLine values ('PL.008', '-109', '', 'PS.base.tb4'); +insert into PLine values ('PL.009', '-121', '', 'PS.base.tb5'); +insert into PLine values ('PL.010', '-122', '', 'PS.base.tb6'); +insert into PLine values ('PL.015', '-134', '', 'PS.first.ta1'); +insert into PLine values ('PL.016', '-137', '', 'PS.first.ta3'); +insert into PLine values ('PL.017', '-139', '', 'PS.first.ta4'); +insert into PLine values ('PL.018', '-362', '', 'PS.first.tb1'); +insert into PLine values ('PL.019', '-363', '', 'PS.first.tb2'); +insert into PLine values ('PL.020', '-364', '', 'PS.first.tb3'); +insert into PLine values ('PL.021', '-365', '', 'PS.first.tb5'); +insert into PLine values ('PL.022', '-367', '', 'PS.first.tb6'); +insert into PLine values ('PL.028', '-501', 'Fax entrance', 'PS.base.ta2'); +insert into PLine values ('PL.029', '-502', 'Fax first floor', 'PS.first.ta1'); +-- +-- Buy some phones, plug them into the wall and patch the +-- phone lines to the corresponding patchfield slots. +-- +insert into PHone values ('PH.hc001', 'Hicom standard', 'WS.001.1a'); +update PSlot set slotlink = 'PS.base.ta1' where slotname = 'PS.base.a1'; +insert into PHone values ('PH.hc002', 'Hicom standard', 'WS.002.1a'); +update PSlot set slotlink = 'PS.base.ta5' where slotname = 'PS.base.b1'; +insert into PHone values ('PH.hc003', 'Hicom standard', 'WS.002.2a'); +update PSlot set slotlink = 'PS.base.tb2' where slotname = 'PS.base.b3'; +insert into PHone values ('PH.fax001', 'Canon fax', 'WS.001.2a'); +update PSlot set slotlink = 'PS.base.ta2' where slotname = 'PS.base.a3'; +-- +-- Install a hub at one of the patchfields, plug a computers +-- ethernet interface into the wall and patch it to the hub. +-- +insert into Hub values ('base.hub1', 'Patchfield PF0_1 hub', 16); +insert into System values ('orion', 'PC'); +insert into IFace values ('IF', 'orion', 'eth0', 'WS.002.1b'); +update PSlot set slotlink = 'HS.base.hub1.1' where slotname = 'PS.base.b2'; +-- +-- Now we take a look at the patchfield +-- +select * from PField_v1 where pfname = 'PF0_1' order by slotname; + pfname | slotname | backside | patch +--------+----------------------+----------------------------------------------------------+----------------------------------------------- + PF0_1 | PS.base.a1 | WS.001.1a in room 001 -> Phone PH.hc001 (Hicom standard) | PS.base.ta1 -> Phone line -0 (Central call) + PF0_1 | PS.base.a2 | WS.001.1b in room 001 -> - | - + PF0_1 | PS.base.a3 | WS.001.2a in room 001 -> Phone PH.fax001 (Canon fax) | PS.base.ta2 -> Phone line -501 (Fax entrance) + PF0_1 | PS.base.a4 | WS.001.2b in room 001 -> - | - + PF0_1 | PS.base.a5 | WS.001.3a in room 001 -> - | - + PF0_1 | PS.base.a6 | WS.001.3b in room 001 -> - | - + PF0_1 | PS.base.b1 | WS.002.1a in room 002 -> Phone PH.hc002 (Hicom standard) | PS.base.ta5 -> Phone line -103 + PF0_1 | PS.base.b2 | WS.002.1b in room 002 -> orion IF eth0 (PC) | Patchfield PF0_1 hub slot 1 + PF0_1 | PS.base.b3 | WS.002.2a in room 002 -> Phone PH.hc003 (Hicom standard) | PS.base.tb2 -> Phone line -106 + PF0_1 | PS.base.b4 | WS.002.2b in room 002 -> - | - + PF0_1 | PS.base.b5 | WS.002.3a in room 002 -> - | - + PF0_1 | PS.base.b6 | WS.002.3b in room 002 -> - | - + PF0_1 | PS.base.c1 | WS.003.1a in room 003 -> - | - + PF0_1 | PS.base.c2 | WS.003.1b in room 003 -> - | - + PF0_1 | PS.base.c3 | WS.003.2a in room 003 -> - | - + PF0_1 | PS.base.c4 | WS.003.2b in room 003 -> - | - + PF0_1 | PS.base.c5 | WS.003.3a in room 003 -> - | - + PF0_1 | PS.base.c6 | WS.003.3b in room 003 -> - | - +(18 rows) + +select * from PField_v1 where pfname = 'PF0_2' order by slotname; + pfname | slotname | backside | patch +--------+----------------------+--------------------------------+------------------------------------------------------------------------ + PF0_2 | PS.base.ta1 | Phone line -0 (Central call) | PS.base.a1 -> WS.001.1a in room 001 -> Phone PH.hc001 (Hicom standard) + PF0_2 | PS.base.ta2 | Phone line -501 (Fax entrance) | PS.base.a3 -> WS.001.2a in room 001 -> Phone PH.fax001 (Canon fax) + PF0_2 | PS.base.ta3 | Phone line -102 | - + PF0_2 | PS.base.ta4 | - | - + PF0_2 | PS.base.ta5 | Phone line -103 | PS.base.b1 -> WS.002.1a in room 002 -> Phone PH.hc002 (Hicom standard) + PF0_2 | PS.base.ta6 | Phone line -104 | - + PF0_2 | PS.base.tb1 | - | - + PF0_2 | PS.base.tb2 | Phone line -106 | PS.base.b3 -> WS.002.2a in room 002 -> Phone PH.hc003 (Hicom standard) + PF0_2 | PS.base.tb3 | Phone line -108 | - + PF0_2 | PS.base.tb4 | Phone line -109 | - + PF0_2 | PS.base.tb5 | Phone line -121 | - + PF0_2 | PS.base.tb6 | Phone line -122 | - +(12 rows) + +-- +-- Finally we want errors +-- +insert into PField values ('PF1_1', 'should fail due to unique index'); +ERROR: duplicate key value violates unique constraint "pfield_name" +update PSlot set backlink = 'WS.not.there' where slotname = 'PS.base.a1'; +ERROR: WS.not.there does not exist +CONTEXT: PL/pgSQL function tg_backlink_set(character,character) line 30 at RAISE +PL/pgSQL function tg_backlink_a() line 17 at assignment +update PSlot set backlink = 'XX.illegal' where slotname = 'PS.base.a1'; +ERROR: illegal backlink beginning with XX +CONTEXT: PL/pgSQL function tg_backlink_set(character,character) line 47 at RAISE +PL/pgSQL function tg_backlink_a() line 17 at assignment +update PSlot set slotlink = 'PS.not.there' where slotname = 'PS.base.a1'; +ERROR: PS.not.there does not exist +CONTEXT: PL/pgSQL function tg_slotlink_set(character,character) line 30 at RAISE +PL/pgSQL function tg_slotlink_a() line 17 at assignment +update PSlot set slotlink = 'XX.illegal' where slotname = 'PS.base.a1'; +ERROR: illegal slotlink beginning with XX +CONTEXT: PL/pgSQL function tg_slotlink_set(character,character) line 77 at RAISE +PL/pgSQL function tg_slotlink_a() line 17 at assignment +insert into HSlot values ('HS', 'base.hub1', 1, ''); +ERROR: duplicate key value violates unique constraint "hslot_name" +CONTEXT: PL/pgSQL function tg_slotlink_a() line 4 at statement block +insert into HSlot values ('HS', 'base.hub1', 20, ''); +ERROR: no manual manipulation of HSlot +CONTEXT: PL/pgSQL function tg_hslot_biu() line 12 at RAISE +delete from HSlot; +ERROR: no manual manipulation of HSlot +CONTEXT: PL/pgSQL function tg_hslot_bd() line 12 at RAISE +insert into IFace values ('IF', 'notthere', 'eth0', ''); +ERROR: system "notthere" does not exist +CONTEXT: PL/pgSQL function tg_iface_biu() line 8 at RAISE +insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', ''); +ERROR: IFace slotname "IF.orion.ethernet_interface_name_too_long" too long (20 char max) +CONTEXT: PL/pgSQL function tg_iface_biu() line 14 at RAISE +-- +-- The following tests are unrelated to the scenario outlined above; +-- they merely exercise specific parts of PL/pgSQL +-- +-- +-- Test recursion, per bug report 7-Sep-01 +-- +CREATE FUNCTION recursion_test(int,int) RETURNS text AS ' +DECLARE rslt text; +BEGIN + IF $1 <= 0 THEN + rslt = CAST($2 AS TEXT); + ELSE + rslt = CAST($1 AS TEXT) || '','' || recursion_test($1 - 1, $2); + END IF; + RETURN rslt; +END;' LANGUAGE plpgsql; +SELECT recursion_test(4,3); + recursion_test +---------------- + 4,3,2,1,3 +(1 row) + +-- +-- Test the FOUND magic variable +-- +CREATE TABLE found_test_tbl (a int); +create function test_found() + returns boolean as ' + declare + begin + insert into found_test_tbl values (1); + if FOUND then + insert into found_test_tbl values (2); + end if; + + update found_test_tbl set a = 100 where a = 1; + if FOUND then + insert into found_test_tbl values (3); + end if; + + delete from found_test_tbl where a = 9999; -- matches no rows + if not FOUND then + insert into found_test_tbl values (4); + end if; + + for i in 1 .. 10 loop + -- no need to do anything + end loop; + if FOUND then + insert into found_test_tbl values (5); + end if; + + -- never executes the loop + for i in 2 .. 1 loop + -- no need to do anything + end loop; + if not FOUND then + insert into found_test_tbl values (6); + end if; + return true; + end;' language plpgsql; +select test_found(); + test_found +------------ + t +(1 row) + +-- YB note: add ordering. +select * from found_test_tbl order by ((a % 98) + 0.5 * (a / 100)); + a +----- + 2 + 100 + 3 + 4 + 5 + 6 +(6 rows) + +-- +-- Test set-returning functions for PL/pgSQL +-- +create function test_table_func_rec() returns setof found_test_tbl as ' +DECLARE + rec RECORD; +BEGIN + FOR rec IN select * from found_test_tbl LOOP + RETURN NEXT rec; + END LOOP; + RETURN; +END;' language plpgsql; +-- YB note: add ordering. +select * from test_table_func_rec() order by ((a % 98) + 0.5 * (a / 100)); + a +----- + 2 + 100 + 3 + 4 + 5 + 6 +(6 rows) + +create function test_table_func_row() returns setof found_test_tbl as ' +DECLARE + row found_test_tbl%ROWTYPE; +BEGIN + FOR row IN select * from found_test_tbl LOOP + RETURN NEXT row; + END LOOP; + RETURN; +END;' language plpgsql; +-- YB note: add ordering. +select * from test_table_func_row() order by ((a % 98) + 0.5 * (a / 100)); + a +----- + 2 + 100 + 3 + 4 + 5 + 6 +(6 rows) + +create function test_ret_set_scalar(int,int) returns setof int as ' +DECLARE + i int; +BEGIN + FOR i IN $1 .. $2 LOOP + RETURN NEXT i + 1; + END LOOP; + RETURN; +END;' language plpgsql; +select * from test_ret_set_scalar(1,10); + test_ret_set_scalar +--------------------- + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 +(10 rows) + +create function test_ret_set_rec_dyn(int) returns setof record as ' +DECLARE + retval RECORD; +BEGIN + IF $1 > 10 THEN + SELECT INTO retval 5, 10, 15; + RETURN NEXT retval; + RETURN NEXT retval; + ELSE + SELECT INTO retval 50, 5::numeric, ''xxx''::text; + RETURN NEXT retval; + RETURN NEXT retval; + END IF; + RETURN; +END;' language plpgsql; +SELECT * FROM test_ret_set_rec_dyn(1500) AS (a int, b int, c int); + a | b | c +---+----+---- + 5 | 10 | 15 + 5 | 10 | 15 +(2 rows) + +SELECT * FROM test_ret_set_rec_dyn(5) AS (a int, b numeric, c text); + a | b | c +----+---+----- + 50 | 5 | xxx + 50 | 5 | xxx +(2 rows) + +create function test_ret_rec_dyn(int) returns record as ' +DECLARE + retval RECORD; +BEGIN + IF $1 > 10 THEN + SELECT INTO retval 5, 10, 15; + RETURN retval; + ELSE + SELECT INTO retval 50, 5::numeric, ''xxx''::text; + RETURN retval; + END IF; +END;' language plpgsql; +SELECT * FROM test_ret_rec_dyn(1500) AS (a int, b int, c int); + a | b | c +---+----+---- + 5 | 10 | 15 +(1 row) + +SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text); + a | b | c +----+---+----- + 50 | 5 | xxx +(1 row) + +-- +-- Test some simple polymorphism cases. +-- +create function f1(x anyelement) returns anyelement as $$ +begin + return x + 1; +end$$ language plpgsql; +select f1(42) as int, f1(4.5) as num; + int | num +-----+----- + 43 | 5.5 +(1 row) + +select f1(point(3,4)); -- fail for lack of + operator +ERROR: operator does not exist: point + integer +LINE 1: x + 1 + ^ +HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +QUERY: x + 1 +CONTEXT: PL/pgSQL function f1(anyelement) line 3 at RETURN +drop function f1(x anyelement); +create function f1(x anyelement) returns anyarray as $$ +begin + return array[x + 1, x + 2]; +end$$ language plpgsql; +select f1(42) as int, f1(4.5) as num; + int | num +---------+----------- + {43,44} | {5.5,6.5} +(1 row) + +drop function f1(x anyelement); +create function f1(x anyarray) returns anyelement as $$ +begin + return x[1]; +end$$ language plpgsql; +select f1(array[2,4]) as int, f1(array[4.5, 7.7]) as num; + int | num +-----+----- + 2 | 4.5 +(1 row) + +select f1(stavalues1) from pg_statistic; -- fail, can't infer element type +ERROR: cannot determine element type of "anyarray" argument +drop function f1(x anyarray); +create function f1(x anyarray) returns anyarray as $$ +begin + return x; +end$$ language plpgsql; +select f1(array[2,4]) as int, f1(array[4.5, 7.7]) as num; + int | num +-------+----------- + {2,4} | {4.5,7.7} +(1 row) + +select f1(stavalues1) from pg_statistic; -- fail, can't infer element type +ERROR: PL/pgSQL functions cannot accept type anyarray +CONTEXT: compilation of PL/pgSQL function "f1" near line 1 +drop function f1(x anyarray); +-- fail, can't infer type: +create function f1(x anyelement) returns anyrange as $$ +begin + return array[x + 1, x + 2]; +end$$ language plpgsql; +ERROR: cannot determine result data type +DETAIL: A result of type anyrange requires at least one input of type anyrange or anymultirange. +create function f1(x anyrange) returns anyarray as $$ +begin + return array[lower(x), upper(x)]; +end$$ language plpgsql; +select f1(int4range(42, 49)) as int, f1(float8range(4.5, 7.8)) as num; + int | num +---------+----------- + {42,49} | {4.5,7.8} +(1 row) + +drop function f1(x anyrange); +create function f1(x anycompatible, y anycompatible) returns anycompatiblearray as $$ +begin + return array[x, y]; +end$$ language plpgsql; +select f1(2, 4) as int, f1(2, 4.5) as num; + int | num +-------+--------- + {2,4} | {2,4.5} +(1 row) + +drop function f1(x anycompatible, y anycompatible); +create function f1(x anycompatiblerange, y anycompatible, z anycompatible) returns anycompatiblearray as $$ +begin + return array[lower(x), upper(x), y, z]; +end$$ language plpgsql; +select f1(int4range(42, 49), 11, 2::smallint) as int, f1(float8range(4.5, 7.8), 7.8, 11::real) as num; + int | num +--------------+------------------ + {42,49,11,2} | {4.5,7.8,7.8,11} +(1 row) + +select f1(int4range(42, 49), 11, 4.5) as fail; -- range type doesn't fit +ERROR: function f1(int4range, integer, numeric) does not exist +LINE 1: select f1(int4range(42, 49), 11, 4.5) as fail; + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +drop function f1(x anycompatiblerange, y anycompatible, z anycompatible); +-- fail, can't infer type: +create function f1(x anycompatible) returns anycompatiblerange as $$ +begin + return array[x + 1, x + 2]; +end$$ language plpgsql; +ERROR: cannot determine result data type +DETAIL: A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange. +create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$ +begin + return x; +end$$ language plpgsql; +select f1(int4range(42, 49), array[11]) as int, f1(float8range(4.5, 7.8), array[7]) as num; + int | num +---------+----------- + [42,49) | [4.5,7.8) +(1 row) + +drop function f1(x anycompatiblerange, y anycompatiblearray); +create function f1(a anyelement, b anyarray, + c anycompatible, d anycompatible, + OUT x anyarray, OUT y anycompatiblearray) +as $$ +begin + x := a || b; + y := array[c, d]; +end$$ language plpgsql; +select x, pg_typeof(x), y, pg_typeof(y) + from f1(11, array[1, 2], 42, 34.5); + x | pg_typeof | y | pg_typeof +----------+-----------+-----------+----------- + {11,1,2} | integer[] | {42,34.5} | numeric[] +(1 row) + +select x, pg_typeof(x), y, pg_typeof(y) + from f1(11, array[1, 2], point(1,2), point(3,4)); + x | pg_typeof | y | pg_typeof +----------+-----------+-------------------+----------- + {11,1,2} | integer[] | {"(1,2)","(3,4)"} | point[] +(1 row) + +select x, pg_typeof(x), y, pg_typeof(y) + from f1(11, '{1,2}', point(1,2), '(3,4)'); + x | pg_typeof | y | pg_typeof +----------+-----------+-------------------+----------- + {11,1,2} | integer[] | {"(1,2)","(3,4)"} | point[] +(1 row) + +select x, pg_typeof(x), y, pg_typeof(y) + from f1(11, array[1, 2.2], 42, 34.5); -- fail +ERROR: function f1(integer, numeric[], integer, numeric) does not exist +LINE 2: from f1(11, array[1, 2.2], 42, 34.5); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +drop function f1(a anyelement, b anyarray, + c anycompatible, d anycompatible); +-- +-- Test handling of OUT parameters, including polymorphic cases. +-- Note that RETURN is optional with OUT params; we try both ways. +-- +-- wrong way to do it: +create function f1(in i int, out j int) returns int as $$ +begin + return i+1; +end$$ language plpgsql; +ERROR: RETURN cannot have a parameter in function with OUT parameters +LINE 3: return i+1; + ^ +create function f1(in i int, out j int) as $$ +begin + j := i+1; + return; +end$$ language plpgsql; +select f1(42); + f1 +---- + 43 +(1 row) + +select * from f1(42); + j +---- + 43 +(1 row) + +create or replace function f1(inout i int) as $$ +begin + i := i+1; +end$$ language plpgsql; +select f1(42); + f1 +---- + 43 +(1 row) + +select * from f1(42); + i +---- + 43 +(1 row) + +drop function f1(int); +create function f1(in i int, out j int) returns setof int as $$ +begin + j := i+1; + return next; + j := i+2; + return next; + return; +end$$ language plpgsql; +select * from f1(42); + j +---- + 43 + 44 +(2 rows) + +drop function f1(int); +create function f1(in i int, out j int, out k text) as $$ +begin + j := i; + j := j+1; + k := 'foo'; +end$$ language plpgsql; +select f1(42); + f1 +---------- + (43,foo) +(1 row) + +select * from f1(42); + j | k +----+----- + 43 | foo +(1 row) + +drop function f1(int); +create function f1(in i int, out j int, out k text) returns setof record as $$ +begin + j := i+1; + k := 'foo'; + return next; + j := j+1; + k := 'foot'; + return next; +end$$ language plpgsql; +select * from f1(42); + j | k +----+------ + 43 | foo + 44 | foot +(2 rows) + +drop function f1(int); +create function duplic(in i anyelement, out j anyelement, out k anyarray) as $$ +begin + j := i; + k := array[j,j]; + return; +end$$ language plpgsql; +select * from duplic(42); + j | k +----+--------- + 42 | {42,42} +(1 row) + +select * from duplic('foo'::text); + j | k +-----+----------- + foo | {foo,foo} +(1 row) + +drop function duplic(anyelement); +create function duplic(in i anycompatiblerange, out j anycompatible, out k anycompatiblearray) as $$ +begin + j := lower(i); + k := array[lower(i),upper(i)]; + return; +end$$ language plpgsql; +select * from duplic(int4range(42,49)); + j | k +----+--------- + 42 | {42,49} +(1 row) + +select * from duplic(textrange('aaa', 'bbb')); + j | k +-----+----------- + aaa | {aaa,bbb} +(1 row) + +drop function duplic(anycompatiblerange); +-- +-- test PERFORM +-- +create table perform_test ( + a INT, + b INT +); +create function perform_simple_func(int) returns boolean as ' +BEGIN + IF $1 < 20 THEN + INSERT INTO perform_test VALUES ($1, $1 + 10); + RETURN TRUE; + ELSE + RETURN FALSE; + END IF; +END;' language plpgsql; +create function perform_test_func() returns void as ' +BEGIN + IF FOUND then + INSERT INTO perform_test VALUES (100, 100); + END IF; + + PERFORM perform_simple_func(5); + + IF FOUND then + INSERT INTO perform_test VALUES (100, 100); + END IF; + + PERFORM perform_simple_func(50); + + IF FOUND then + INSERT INTO perform_test VALUES (100, 100); + END IF; + + RETURN; +END;' language plpgsql; +SELECT perform_test_func(); + perform_test_func +------------------- + +(1 row) + +-- YB note: add ordering. +SELECT * FROM perform_test ORDER BY a; + a | b +-----+----- + 5 | 15 + 100 | 100 + 100 | 100 +(3 rows) + +drop table perform_test; +/* + * TODO(jason): the following test section was moved to src/pl/plpgsql/src by + * upstream PG 6726d8d476b424633ebdc7068da3f5a6e6da10af. Since YB does not yet + * support tests there, leave it here for now. + */ +-- +-- Test error trapping +-- +create function trap_zero_divide(int) returns int as $$ +declare x int; + sx smallint; +begin + begin -- start a subtransaction + raise notice 'should see this'; + x := 100 / $1; + raise notice 'should see this only if % <> 0', $1; + sx := $1; + raise notice 'should see this only if % fits in smallint', $1; + if $1 < 0 then + raise exception '% is less than zero', $1; + end if; + exception + when division_by_zero then + raise notice 'caught division_by_zero'; + x := -1; + when NUMERIC_VALUE_OUT_OF_RANGE then + raise notice 'caught numeric_value_out_of_range'; + x := -2; + end; + return x; +end$$ language plpgsql; +select trap_zero_divide(50); +NOTICE: should see this +NOTICE: should see this only if 50 <> 0 +NOTICE: should see this only if 50 fits in smallint + trap_zero_divide +------------------ + 2 +(1 row) + +select trap_zero_divide(0); +NOTICE: should see this +NOTICE: caught division_by_zero + trap_zero_divide +------------------ + -1 +(1 row) + +select trap_zero_divide(100000); +NOTICE: should see this +NOTICE: should see this only if 100000 <> 0 +NOTICE: caught numeric_value_out_of_range + trap_zero_divide +------------------ + -2 +(1 row) + +select trap_zero_divide(-100); +NOTICE: should see this +NOTICE: should see this only if -100 <> 0 +NOTICE: should see this only if -100 fits in smallint +ERROR: -100 is less than zero +CONTEXT: PL/pgSQL function trap_zero_divide(integer) line 12 at RAISE +create function trap_matching_test(int) returns int as $$ +declare x int; + sx smallint; + y int; +begin + begin -- start a subtransaction + x := 100 / $1; + sx := $1; + select into y unique1 from tenk1 where unique2 = + (select unique2 from tenk1 b where ten = $1); + exception + when data_exception then -- category match + raise notice 'caught data_exception'; + x := -1; + when NUMERIC_VALUE_OUT_OF_RANGE OR CARDINALITY_VIOLATION then + raise notice 'caught numeric_value_out_of_range or cardinality_violation'; + x := -2; + end; + return x; +end$$ language plpgsql; +select trap_matching_test(50); + trap_matching_test +-------------------- + 2 +(1 row) + +select trap_matching_test(0); +NOTICE: caught data_exception + trap_matching_test +-------------------- + -1 +(1 row) + +select trap_matching_test(100000); +NOTICE: caught data_exception + trap_matching_test +-------------------- + -1 +(1 row) + +select trap_matching_test(1); +NOTICE: caught numeric_value_out_of_range or cardinality_violation + trap_matching_test +-------------------- + -2 +(1 row) + +create temp table foo (f1 int); +create function subxact_rollback_semantics() returns int as $$ +declare x int; +begin + x := 1; + insert into foo values(x); + begin + x := x + 1; + insert into foo values(x); + raise exception 'inner'; + exception + when others then + x := x * 10; + end; + insert into foo values(x); + return x; +end$$ language plpgsql; +-- TODO:(dmitry) Uncomment when issue #1541 will be resolved +-- select subxact_rollback_semantics(); +select * from foo order by f1; + f1 +---- +(0 rows) + +drop table foo; +create function trap_timeout() returns void as $$ +begin + declare x int; + begin + -- we assume this will take longer than 2 seconds: + select count(*) into x from tenk1 a, tenk1 b, tenk1 c; + exception + when others then + raise notice 'caught others?'; + when query_canceled then + raise notice 'nyeah nyeah, can''t stop me'; + end; + -- Abort transaction to abandon the statement_timeout setting. Otherwise, + -- the next top-level statement would be vulnerable to the timeout. + raise exception 'end of function'; +end$$ language plpgsql; +begin; +set statement_timeout to 2000; +select trap_timeout(); +NOTICE: nyeah nyeah, can't stop me +ERROR: end of function +CONTEXT: PL/pgSQL function trap_timeout() line 15 at RAISE +rollback; +-- Test for pass-by-ref values being stored in proper context +create function test_variable_storage() returns text as $$ +declare x text; +begin + x := '1234'; + begin + x := x || '5678'; + -- force error inside subtransaction SPI context + perform trap_zero_divide(-100); + exception + when others then + x := x || '9012'; + end; + return x; +end$$ language plpgsql; +select test_variable_storage(); +NOTICE: should see this +NOTICE: should see this only if -100 <> 0 +NOTICE: should see this only if -100 fits in smallint + test_variable_storage +----------------------- + 123456789012 +(1 row) + +-- +-- test foreign key error trapping +-- +create temp table master(f1 int primary key); +create temp table slave(f1 int references master deferrable); +insert into master values(1); +insert into slave values(1); +insert into slave values(2); -- fails +ERROR: insert or update on table "slave" violates foreign key constraint "slave_f1_fkey" +DETAIL: Key (f1)=(2) is not present in table "master". +create function trap_foreign_key(int) returns int as $$ +begin + begin -- start a subtransaction + insert into slave values($1); + exception + when foreign_key_violation then + raise notice 'caught foreign_key_violation'; + return 0; + end; + return 1; +end$$ language plpgsql; +create function trap_foreign_key_2() returns int as $$ +begin + begin -- start a subtransaction + set constraints all immediate; + exception + when foreign_key_violation then + raise notice 'caught foreign_key_violation'; + return 0; + end; + return 1; +end$$ language plpgsql; +select trap_foreign_key(1); + trap_foreign_key +------------------ + 1 +(1 row) + +select trap_foreign_key(2); -- detects FK violation +NOTICE: caught foreign_key_violation + trap_foreign_key +------------------ + 0 +(1 row) + +begin; + set constraints all deferred; + select trap_foreign_key(2); -- should not detect FK violation + trap_foreign_key +------------------ + 1 +(1 row) + + savepoint x; + set constraints all immediate; -- fails +ERROR: insert or update on table "slave" violates foreign key constraint "slave_f1_fkey" +DETAIL: Key (f1)=(2) is not present in table "master". + rollback to x; + select trap_foreign_key_2(); -- detects FK violation +NOTICE: caught foreign_key_violation + trap_foreign_key_2 +-------------------- + 0 +(1 row) + +commit; -- still fails +ERROR: insert or update on table "slave" violates foreign key constraint "slave_f1_fkey" +DETAIL: Key (f1)=(2) is not present in table "master". +drop function trap_foreign_key(int); +drop function trap_foreign_key_2(); +-- +-- Test proper snapshot handling in simple expressions +-- +create temp table users(login text, id serial); +create function sp_id_user(a_login text) returns int as $$ +declare x int; +begin + select into x id from users where login = a_login; + if found then return x; end if; + return 0; +end$$ language plpgsql stable; +insert into users values('user1'); +select sp_id_user('user1'); + sp_id_user +------------ + 1 +(1 row) + +select sp_id_user('userx'); + sp_id_user +------------ + 0 +(1 row) + +create function sp_add_user(a_login text) returns int as $$ +declare my_id_user int; +begin + my_id_user = sp_id_user( a_login ); + IF my_id_user > 0 THEN + RETURN -1; -- error code for existing user + END IF; + INSERT INTO users ( login ) VALUES ( a_login ); + my_id_user = sp_id_user( a_login ); + IF my_id_user = 0 THEN + RETURN -2; -- error code for insertion failure + END IF; + RETURN my_id_user; +end$$ language plpgsql; +select sp_add_user('user1'); + sp_add_user +------------- + -1 +(1 row) + +select sp_add_user('user2'); + sp_add_user +------------- + 2 +(1 row) + +select sp_add_user('user2'); + sp_add_user +------------- + -1 +(1 row) + +select sp_add_user('user3'); + sp_add_user +------------- + 3 +(1 row) + +select sp_add_user('user3'); + sp_add_user +------------- + -1 +(1 row) + +drop function sp_add_user(text); +drop function sp_id_user(text); +-- +-- tests for refcursors +-- +create table rc_test (a int, b int); +copy rc_test from stdin; +create function return_unnamed_refcursor() returns refcursor as $$ +declare + rc refcursor; +begin + open rc for select a from rc_test; + return rc; +end +$$ language plpgsql; +create function use_refcursor(rc refcursor) returns int as $$ +declare + rc refcursor; + x record; +begin + rc := return_unnamed_refcursor(); + fetch next from rc into x; + return x.a; +end +$$ language plpgsql; +select use_refcursor(return_unnamed_refcursor()); + use_refcursor +--------------- + 5 +(1 row) + +create function return_refcursor(rc refcursor) returns refcursor as $$ +begin + open rc for select a from rc_test; + return rc; +end +$$ language plpgsql; +create function refcursor_test1(refcursor) returns refcursor as $$ +begin + perform return_refcursor($1); + return $1; +end +$$ language plpgsql; +begin; +select refcursor_test1('test1'); + refcursor_test1 +----------------- + test1 +(1 row) + +fetch next in test1; + a +--- + 5 +(1 row) + +select refcursor_test1('test2'); + refcursor_test1 +----------------- + test2 +(1 row) + +fetch all from test2; + a +----- + 5 + 50 + 500 +(3 rows) + +commit; +-- should fail +fetch next from test1; +ERROR: cursor "test1" does not exist +create function refcursor_test2(int, int) returns boolean as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; + nonsense record; +begin + open c1($1, $2); + fetch c1 into nonsense; + close c1; + if found then + return true; + else + return false; + end if; +end +$$ language plpgsql; +select refcursor_test2(20000, 20000) as "Should be false", + refcursor_test2(20, 20) as "Should be true"; + Should be false | Should be true +-----------------+---------------- + f | t +(1 row) + +-- should fail +create function constant_refcursor() returns refcursor as $$ +declare + rc constant refcursor; +begin + open rc for select a from rc_test; + return rc; +end +$$ language plpgsql; +select constant_refcursor(); +ERROR: variable "rc" is declared CONSTANT +CONTEXT: PL/pgSQL function constant_refcursor() line 5 at OPEN +-- but it's okay like this +create or replace function constant_refcursor() returns refcursor as $$ +declare + rc constant refcursor := 'my_cursor_name'; +begin + open rc for select a from rc_test; + return rc; +end +$$ language plpgsql; +select constant_refcursor(); + constant_refcursor +-------------------- + my_cursor_name +(1 row) + +-- +-- tests for cursors with named parameter arguments +-- +create function namedparmcursor_test1(int, int) returns boolean as $$ +declare + c1 cursor (param1 int, param12 int) for select * from rc_test where a > param1 and b > param12; + nonsense record; +begin + open c1(param12 := $2, param1 := $1); + fetch c1 into nonsense; + close c1; + if found then + return true; + else + return false; + end if; +end +$$ language plpgsql; +select namedparmcursor_test1(20000, 20000) as "Should be false", + namedparmcursor_test1(20, 20) as "Should be true"; + Should be false | Should be true +-----------------+---------------- + f | t +(1 row) + +-- mixing named and positional argument notations +create function namedparmcursor_test2(int, int) returns boolean as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; + nonsense record; +begin + open c1(param1 := $1, $2); + fetch c1 into nonsense; + close c1; + if found then + return true; + else + return false; + end if; +end +$$ language plpgsql; +select namedparmcursor_test2(20, 20); + namedparmcursor_test2 +----------------------- + t +(1 row) + +-- mixing named and positional: param2 is given twice, once in named notation +-- and second time in positional notation. Should throw an error at parse time +create function namedparmcursor_test3() returns void as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; +begin + open c1(param2 := 20, 21); +end +$$ language plpgsql; +ERROR: value for parameter "param2" of cursor "c1" specified more than once +LINE 5: open c1(param2 := 20, 21); + ^ +-- mixing named and positional: same as previous test, but param1 is duplicated +create function namedparmcursor_test4() returns void as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; +begin + open c1(20, param1 := 21); +end +$$ language plpgsql; +ERROR: value for parameter "param1" of cursor "c1" specified more than once +LINE 5: open c1(20, param1 := 21); + ^ +-- duplicate named parameter, should throw an error at parse time +create function namedparmcursor_test5() returns void as $$ +declare + c1 cursor (p1 int, p2 int) for + select * from tenk1 where thousand = p1 and tenthous = p2; +begin + open c1 (p2 := 77, p2 := 42); +end +$$ language plpgsql; +ERROR: value for parameter "p2" of cursor "c1" specified more than once +LINE 6: open c1 (p2 := 77, p2 := 42); + ^ +-- not enough parameters, should throw an error at parse time +create function namedparmcursor_test6() returns void as $$ +declare + c1 cursor (p1 int, p2 int) for + select * from tenk1 where thousand = p1 and tenthous = p2; +begin + open c1 (p2 := 77); +end +$$ language plpgsql; +ERROR: not enough arguments for cursor "c1" +LINE 6: open c1 (p2 := 77); + ^ +-- division by zero runtime error, the context given in the error message +-- should be sensible +create function namedparmcursor_test7() returns void as $$ +declare + c1 cursor (p1 int, p2 int) for + select * from tenk1 where thousand = p1 and tenthous = p2; +begin + open c1 (p2 := 77, p1 := 42/0); +end $$ language plpgsql; +select namedparmcursor_test7(); +ERROR: division by zero +CONTEXT: SQL expression "42/0 AS p1, 77 AS p2" +PL/pgSQL function namedparmcursor_test7() line 6 at OPEN +-- check that line comments work correctly within the argument list (there +-- is some special handling of this case in the code: the newline after the +-- comment must be preserved when the argument-evaluating query is +-- constructed, otherwise the comment effectively comments out the next +-- argument, too) +create function namedparmcursor_test8() returns int4 as $$ +declare + c1 cursor (p1 int, p2 int) for + select count(*) from tenk1 where thousand = p1 and tenthous = p2; + n int4; +begin + open c1 (77 -- test + , 42); + fetch c1 into n; + return n; +end $$ language plpgsql; +select namedparmcursor_test8(); + namedparmcursor_test8 +----------------------- + 0 +(1 row) + +-- cursor parameter name can match plpgsql variable or unreserved keyword +create function namedparmcursor_test9(p1 int) returns int4 as $$ +declare + c1 cursor (p1 int, p2 int, debug int) for + select count(*) from tenk1 where thousand = p1 and tenthous = p2 + and four = debug; + p2 int4 := 1006; + n int4; +begin + open c1 (p1 := p1, p2 := p2, debug := 2); + fetch c1 into n; + return n; +end $$ language plpgsql; +select namedparmcursor_test9(6); + namedparmcursor_test9 +----------------------- + 1 +(1 row) + +-- +-- tests for "raise" processing +-- +create function raise_test1(int) returns int as $$ +begin + raise notice 'This message has too many parameters!', $1; + return $1; +end; +$$ language plpgsql; +ERROR: too many parameters specified for RAISE +CONTEXT: compilation of PL/pgSQL function "raise_test1" near line 3 +create function raise_test2(int) returns int as $$ +begin + raise notice 'This message has too few parameters: %, %, %', $1, $1; + return $1; +end; +$$ language plpgsql; +ERROR: too few parameters specified for RAISE +CONTEXT: compilation of PL/pgSQL function "raise_test2" near line 3 +create function raise_test3(int) returns int as $$ +begin + raise notice 'This message has no parameters (despite having %% signs in it)!'; + return $1; +end; +$$ language plpgsql; +select raise_test3(1); +NOTICE: This message has no parameters (despite having % signs in it)! + raise_test3 +------------- + 1 +(1 row) + +-- Test re-RAISE inside a nested exception block. This case is allowed +-- by Oracle's PL/SQL but was handled differently by PG before 9.1. +CREATE FUNCTION reraise_test() RETURNS void AS $$ +BEGIN + BEGIN + RAISE syntax_error; + EXCEPTION + WHEN syntax_error THEN + BEGIN + raise notice 'exception % thrown in inner block, reraising', sqlerrm; + RAISE; + EXCEPTION + WHEN OTHERS THEN + raise notice 'RIGHT - exception % caught in inner block', sqlerrm; + END; + END; +EXCEPTION + WHEN OTHERS THEN + raise notice 'WRONG - exception % caught in outer block', sqlerrm; +END; +$$ LANGUAGE plpgsql; +SELECT reraise_test(); +NOTICE: exception syntax_error thrown in inner block, reraising +NOTICE: RIGHT - exception syntax_error caught in inner block + reraise_test +-------------- + +(1 row) + +-- +-- reject function definitions that contain malformed SQL queries at +-- compile-time, where possible +-- +create function bad_sql1() returns int as $$ +declare a int; +begin + a := 5; + Johnny Yuma; + a := 10; + return a; +end$$ language plpgsql; +ERROR: syntax error at or near "Johnny" +LINE 5: Johnny Yuma; + ^ +create function bad_sql2() returns int as $$ +declare r record; +begin + for r in select I fought the law, the law won LOOP + raise notice 'in loop'; + end loop; + return 5; +end;$$ language plpgsql; +ERROR: syntax error at or near "the" +LINE 4: for r in select I fought the law, the law won LOOP + ^ +-- a RETURN expression is mandatory, except for void-returning +-- functions, where it is not allowed +create function missing_return_expr() returns int as $$ +begin + return ; +end;$$ language plpgsql; +ERROR: missing expression at or near ";" +LINE 3: return ; + ^ +create function void_return_expr() returns void as $$ +begin + return 5; +end;$$ language plpgsql; +ERROR: RETURN cannot have a parameter in function returning void +LINE 3: return 5; + ^ +-- VOID functions are allowed to omit RETURN +create function void_return_expr() returns void as $$ +begin + perform 2+2; +end;$$ language plpgsql; +select void_return_expr(); + void_return_expr +------------------ + +(1 row) + +-- but ordinary functions are not +create function missing_return_expr() returns int as $$ +begin + perform 2+2; +end;$$ language plpgsql; +select missing_return_expr(); +ERROR: control reached end of function without RETURN +CONTEXT: PL/pgSQL function missing_return_expr() +drop function void_return_expr(); +drop function missing_return_expr(); +-- +-- EXECUTE ... INTO test +-- +create table eifoo (i integer, y integer); +create type eitype as (i integer, y integer); +create or replace function execute_into_test(varchar) returns record as $$ +declare + _r record; + _rt eifoo%rowtype; + _v eitype; + i int; + j int; + k int; +begin + execute 'insert into '||$1||' values(10,15)'; + execute 'select (row).* from (select row(10,1)::eifoo) s' into _r; + raise notice '% %', _r.i, _r.y; + execute 'select * from '||$1||' limit 1' into _rt; + raise notice '% %', _rt.i, _rt.y; + execute 'select *, 20 from '||$1||' limit 1' into i, j, k; + raise notice '% % %', i, j, k; + execute 'select 1,2' into _v; + return _v; +end; $$ language plpgsql; +select execute_into_test('eifoo'); +NOTICE: 10 1 +NOTICE: 10 15 +NOTICE: 10 15 20 + execute_into_test +------------------- + (1,2) +(1 row) + +drop table eifoo cascade; +drop type eitype cascade; +-- +-- SQLSTATE and SQLERRM test +-- +create function excpt_test1() returns void as $$ +begin + raise notice '% %', sqlstate, sqlerrm; +end; $$ language plpgsql; +-- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION +-- blocks +select excpt_test1(); +ERROR: column "sqlstate" does not exist +LINE 1: sqlstate + ^ +QUERY: sqlstate +CONTEXT: PL/pgSQL function excpt_test1() line 3 at RAISE +create function excpt_test2() returns void as $$ +begin + begin + begin + raise notice '% %', sqlstate, sqlerrm; + end; + end; +end; $$ language plpgsql; +-- should fail +select excpt_test2(); +ERROR: column "sqlstate" does not exist +LINE 1: sqlstate + ^ +QUERY: sqlstate +CONTEXT: PL/pgSQL function excpt_test2() line 5 at RAISE +create function excpt_test3() returns void as $$ +begin + begin + raise exception 'user exception'; + exception when others then + raise notice 'caught exception % %', sqlstate, sqlerrm; + begin + raise notice '% %', sqlstate, sqlerrm; + perform 10/0; + exception + when substring_error then + -- this exception handler shouldn't be invoked + raise notice 'unexpected exception: % %', sqlstate, sqlerrm; + when division_by_zero then + raise notice 'caught exception % %', sqlstate, sqlerrm; + end; + raise notice '% %', sqlstate, sqlerrm; + end; +end; $$ language plpgsql; +select excpt_test3(); +NOTICE: caught exception P0001 user exception +NOTICE: P0001 user exception +NOTICE: caught exception 22012 division by zero +NOTICE: P0001 user exception + excpt_test3 +------------- + +(1 row) + +create function excpt_test4() returns text as $$ +begin + begin perform 1/0; + exception when others then return sqlerrm; end; +end; $$ language plpgsql; +select excpt_test4(); + excpt_test4 +------------------ + division by zero +(1 row) + +drop function excpt_test1(); +drop function excpt_test2(); +drop function excpt_test3(); +drop function excpt_test4(); +-- parameters of raise stmt can be expressions +create function raise_exprs() returns void as $$ +declare + a integer[] = '{10,20,30}'; + c varchar = 'xyz'; + i integer; +begin + i := 2; + raise notice '%; %; %; %; %; %', a, a[i], c, (select c || 'abc'), row(10,'aaa',NULL,30), NULL; +end;$$ language plpgsql; +select raise_exprs(); +NOTICE: {10,20,30}; 20; xyz; xyzabc; (10,aaa,,30); + raise_exprs +------------- + +(1 row) + +drop function raise_exprs(); +-- regression test: verify that multiple uses of same plpgsql datum within +-- a SQL command all get mapped to the same $n parameter. The return value +-- of the SELECT is not important, we only care that it doesn't fail with +-- a complaint about an ungrouped column reference. +create function multi_datum_use(p1 int) returns bool as $$ +declare + x int; + y int; +begin + select into x,y unique1/p1, unique1/$1 from tenk1 group by unique1/p1; + return x = y; +end$$ language plpgsql; +select multi_datum_use(42); + multi_datum_use +----------------- + t +(1 row) + +-- +-- Test STRICT limiter in both planned and EXECUTE invocations. +-- Note that a data-modifying query is quasi strict (disallow multi rows) +-- by default in the planned case, but not in EXECUTE. +-- +create temp table foo (f1 int, f2 int); +insert into foo values (1,2), (3,4); +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should work + insert into foo values(5,6) returning * into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +NOTICE: x.f1 = 5, x.f2 = 6 + stricttest +------------ + +(1 row) + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should fail due to implicit strict + insert into foo values(7,8),(9,10) returning * into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned more than one row +HINT: Make sure the query returns a single row, or use LIMIT 1. +CONTEXT: PL/pgSQL function stricttest() line 5 at SQL statement +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should work + execute 'insert into foo values(5,6) returning *' into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +NOTICE: x.f1 = 5, x.f2 = 6 + stricttest +------------ + +(1 row) + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- this should work since EXECUTE isn't as picky + execute 'insert into foo values(7,8),(9,10) returning *' into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +NOTICE: x.f1 = 7, x.f2 = 8 + stricttest +------------ + +(1 row) + +-- YB note: add ordering. +select * from foo order by f1; + f1 | f2 +----+---- + 1 | 2 + 3 | 4 + 5 | 6 + 5 | 6 + 7 | 8 + 9 | 10 +(6 rows) + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should work + select * from foo where f1 = 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +NOTICE: x.f1 = 3, x.f2 = 4 + stricttest +------------ + +(1 row) + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should fail, no rows + select * from foo where f1 = 0 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned no rows +CONTEXT: PL/pgSQL function stricttest() line 5 at SQL statement +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should fail, too many rows + select * from foo where f1 > 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned more than one row +HINT: Make sure the query returns a single row, or use LIMIT 1. +CONTEXT: PL/pgSQL function stricttest() line 5 at SQL statement +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should work + execute 'select * from foo where f1 = 3' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +NOTICE: x.f1 = 3, x.f2 = 4 + stricttest +------------ + +(1 row) + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should fail, no rows + execute 'select * from foo where f1 = 0' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned no rows +CONTEXT: PL/pgSQL function stricttest() line 5 at EXECUTE +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should fail, too many rows + execute 'select * from foo where f1 > 3' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned more than one row +CONTEXT: PL/pgSQL function stricttest() line 5 at EXECUTE +drop function stricttest(); +-- test printing parameters after failure due to STRICT +set plpgsql.print_strict_params to true; +create or replace function stricttest() returns void as $$ +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- no rows + select * from foo where f1 = p1 and f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned no rows +DETAIL: parameters: p1 = '2', p3 = 'foo' +CONTEXT: PL/pgSQL function stricttest() line 8 at SQL statement +create or replace function stricttest() returns void as $$ +declare +x record; +p1 int := 2; +p3 text := $a$'Valame Dios!' dijo Sancho; 'no le dije yo a vuestra merced que mirase bien lo que hacia?'$a$; +begin + -- no rows + select * from foo where f1 = p1 and f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned no rows +DETAIL: parameters: p1 = '2', p3 = '''Valame Dios!'' dijo Sancho; ''no le dije yo a vuestra merced que mirase bien lo que hacia?''' +CONTEXT: PL/pgSQL function stricttest() line 8 at SQL statement +create or replace function stricttest() returns void as $$ +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- too many rows + select * from foo where f1 > p1 or f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned more than one row +DETAIL: parameters: p1 = '2', p3 = 'foo' +HINT: Make sure the query returns a single row, or use LIMIT 1. +CONTEXT: PL/pgSQL function stricttest() line 8 at SQL statement +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- too many rows, no params + select * from foo where f1 > 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned more than one row +HINT: Make sure the query returns a single row, or use LIMIT 1. +CONTEXT: PL/pgSQL function stricttest() line 5 at SQL statement +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- no rows + execute 'select * from foo where f1 = $1 or f1::text = $2' using 0, 'foo' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned no rows +DETAIL: parameters: $1 = '0', $2 = 'foo' +CONTEXT: PL/pgSQL function stricttest() line 5 at EXECUTE +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- too many rows + execute 'select * from foo where f1 > $1' using 1 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned more than one row +DETAIL: parameters: $1 = '1' +CONTEXT: PL/pgSQL function stricttest() line 5 at EXECUTE +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- too many rows, no parameters + execute 'select * from foo where f1 > 3' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned more than one row +CONTEXT: PL/pgSQL function stricttest() line 5 at EXECUTE +create or replace function stricttest() returns void as $$ +-- override the global +#print_strict_params off +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- too many rows + select * from foo where f1 > p1 or f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned more than one row +HINT: Make sure the query returns a single row, or use LIMIT 1. +CONTEXT: PL/pgSQL function stricttest() line 10 at SQL statement +reset plpgsql.print_strict_params; +create or replace function stricttest() returns void as $$ +-- override the global +#print_strict_params on +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- too many rows + select * from foo where f1 > p1 or f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest(); +ERROR: query returned more than one row +DETAIL: parameters: p1 = '2', p3 = 'foo' +HINT: Make sure the query returns a single row, or use LIMIT 1. +CONTEXT: PL/pgSQL function stricttest() line 10 at SQL statement +-- test warnings and errors +set plpgsql.extra_warnings to 'all'; +set plpgsql.extra_warnings to 'none'; +set plpgsql.extra_errors to 'all'; +set plpgsql.extra_errors to 'none'; +-- test warnings when shadowing a variable +set plpgsql.extra_warnings to 'shadowed_variables'; +-- simple shadowing of input and output parameters +create or replace function shadowtest(in1 int) + returns table (out1 int) as $$ +declare +in1 int; +out1 int; +begin +end +$$ language plpgsql; +WARNING: variable "in1" shadows a previously defined variable +LINE 4: in1 int; + ^ +WARNING: variable "out1" shadows a previously defined variable +LINE 5: out1 int; + ^ +select shadowtest(1); + shadowtest +------------ +(0 rows) + +set plpgsql.extra_warnings to 'shadowed_variables'; +select shadowtest(1); + shadowtest +------------ +(0 rows) + +create or replace function shadowtest(in1 int) + returns table (out1 int) as $$ +declare +in1 int; +out1 int; +begin +end +$$ language plpgsql; +WARNING: variable "in1" shadows a previously defined variable +LINE 4: in1 int; + ^ +WARNING: variable "out1" shadows a previously defined variable +LINE 5: out1 int; + ^ +select shadowtest(1); + shadowtest +------------ +(0 rows) + +drop function shadowtest(int); +-- shadowing in a second DECLARE block +create or replace function shadowtest() + returns void as $$ +declare +f1 int; +begin + declare + f1 int; + begin + end; +end$$ language plpgsql; +WARNING: variable "f1" shadows a previously defined variable +LINE 7: f1 int; + ^ +drop function shadowtest(); +-- several levels of shadowing +create or replace function shadowtest(in1 int) + returns void as $$ +declare +in1 int; +begin + declare + in1 int; + begin + end; +end$$ language plpgsql; +WARNING: variable "in1" shadows a previously defined variable +LINE 4: in1 int; + ^ +WARNING: variable "in1" shadows a previously defined variable +LINE 7: in1 int; + ^ +drop function shadowtest(int); +-- shadowing in cursor definitions +create or replace function shadowtest() + returns void as $$ +declare +f1 int; +c1 cursor (f1 int) for select 1; +begin +end$$ language plpgsql; +WARNING: variable "f1" shadows a previously defined variable +LINE 5: c1 cursor (f1 int) for select 1; + ^ +drop function shadowtest(); +-- test errors when shadowing a variable +set plpgsql.extra_errors to 'shadowed_variables'; +create or replace function shadowtest(f1 int) + returns boolean as $$ +declare f1 int; begin return 1; end $$ language plpgsql; +ERROR: variable "f1" shadows a previously defined variable +LINE 3: declare f1 int; begin return 1; end $$ language plpgsql; + ^ +select shadowtest(1); +ERROR: function shadowtest(integer) does not exist +LINE 1: select shadowtest(1); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +reset plpgsql.extra_errors; +reset plpgsql.extra_warnings; +create or replace function shadowtest(f1 int) + returns boolean as $$ +declare f1 int; begin return 1; end $$ language plpgsql; +select shadowtest(1); + shadowtest +------------ + t +(1 row) + +-- runtime extra checks +set plpgsql.extra_warnings to 'too_many_rows'; +do $$ +declare x int; +begin + select v from generate_series(1,2) g(v) into x; +end; +$$; +WARNING: query returned more than one row +HINT: Make sure the query returns a single row, or use LIMIT 1. +set plpgsql.extra_errors to 'too_many_rows'; +do $$ +declare x int; +begin + select v from generate_series(1,2) g(v) into x; +end; +$$; +ERROR: query returned more than one row +HINT: Make sure the query returns a single row, or use LIMIT 1. +CONTEXT: PL/pgSQL function inline_code_block line 4 at SQL statement +reset plpgsql.extra_errors; +reset plpgsql.extra_warnings; +set plpgsql.extra_warnings to 'strict_multi_assignment'; +do $$ +declare + x int; + y int; +begin + select 1 into x, y; + select 1,2 into x, y; + select 1,2,3 into x, y; +end +$$; +WARNING: number of source and target fields in assignment does not match +DETAIL: strict_multi_assignment check of extra_warnings is active. +HINT: Make sure the query returns the exact list of columns. +WARNING: number of source and target fields in assignment does not match +DETAIL: strict_multi_assignment check of extra_warnings is active. +HINT: Make sure the query returns the exact list of columns. +set plpgsql.extra_errors to 'strict_multi_assignment'; +do $$ +declare + x int; + y int; +begin + select 1 into x, y; + select 1,2 into x, y; + select 1,2,3 into x, y; +end +$$; +ERROR: number of source and target fields in assignment does not match +DETAIL: strict_multi_assignment check of extra_errors is active. +HINT: Make sure the query returns the exact list of columns. +CONTEXT: PL/pgSQL function inline_code_block line 6 at SQL statement +create table test_01(a int, b int, c int); +alter table test_01 drop column a; +-- the check is active only when source table is not empty +insert into test_01 values(10,20); +do $$ +declare + x int; + y int; +begin + select * from test_01 into x, y; -- should be ok + raise notice 'ok'; + select * from test_01 into x; -- should to fail +end; +$$; +NOTICE: ok +ERROR: number of source and target fields in assignment does not match +DETAIL: strict_multi_assignment check of extra_errors is active. +HINT: Make sure the query returns the exact list of columns. +CONTEXT: PL/pgSQL function inline_code_block line 8 at SQL statement +do $$ +declare + t test_01; +begin + select 1, 2 into t; -- should be ok + raise notice 'ok'; + select 1, 2, 3 into t; -- should fail; +end; +$$; +NOTICE: ok +ERROR: number of source and target fields in assignment does not match +DETAIL: strict_multi_assignment check of extra_errors is active. +HINT: Make sure the query returns the exact list of columns. +CONTEXT: PL/pgSQL function inline_code_block line 7 at SQL statement +do $$ +declare + t test_01; +begin + select 1 into t; -- should fail; +end; +$$; +ERROR: number of source and target fields in assignment does not match +DETAIL: strict_multi_assignment check of extra_errors is active. +HINT: Make sure the query returns the exact list of columns. +CONTEXT: PL/pgSQL function inline_code_block line 5 at SQL statement +drop table test_01; +reset plpgsql.extra_errors; +reset plpgsql.extra_warnings; +-- test scrollable cursor support +create function sc_test() returns setof integer as $$ +declare + c scroll cursor for select f1 from int4_tbl; + x integer; +begin + open c; + fetch last from c into x; + while found loop + return next x; + fetch prior from c into x; + end loop; + close c; +end; +$$ language plpgsql; +-- YB note: add ordering. +select * from sc_test() ORDER BY (sign(sc_test) - abs(sc_test)); + sc_test +------------- + -2147483647 + 2147483647 + -123456 + 123456 + 0 +(5 rows) + +create or replace function sc_test() returns setof integer as $$ +declare + c no scroll cursor for select f1 from int4_tbl; + x integer; +begin + open c; + fetch last from c into x; + while found loop + return next x; + fetch prior from c into x; + end loop; + close c; +end; +$$ language plpgsql; +select * from sc_test(); -- fails because of NO SCROLL specification +ERROR: cursor can only scan forward +HINT: Declare it with SCROLL option to enable backward scan. +CONTEXT: PL/pgSQL function sc_test() line 7 at FETCH +create or replace function sc_test() returns setof integer as $$ +declare + c refcursor; + x integer; +begin + open c scroll for select f1 from int4_tbl; + fetch last from c into x; + while found loop + return next x; + fetch prior from c into x; + end loop; + close c; +end; +$$ language plpgsql; +-- YB note: add ordering. +select * from sc_test() ORDER BY (sign(sc_test) - abs(sc_test)); + sc_test +------------- + -2147483647 + 2147483647 + -123456 + 123456 + 0 +(5 rows) + +create or replace function sc_test() returns setof integer as $$ +declare + c refcursor; + x integer; +begin + -- YB note: add ordering. + open c scroll for execute 'select f1 from int4_tbl ORDER BY (sign(f1) - abs(f1)) DESC'; + fetch last from c into x; + while found loop + return next x; + fetch relative -2 from c into x; + end loop; + close c; +end; +$$ language plpgsql; +select * from sc_test(); + sc_test +------------- + -2147483647 + -123456 + 0 +(3 rows) + +create or replace function sc_test() returns setof integer as $$ +declare + c refcursor; + x integer; +begin + open c scroll for execute 'select f1 from int4_tbl'; + fetch last from c into x; + while found loop + return next x; + move backward 2 from c; + fetch relative -1 from c into x; + end loop; + close c; +end; +$$ language plpgsql; +ERROR: MOVE not supported yet +LINE 10: move backward 2 from c; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +drop function sc_test(); +-- test qualified variable names +create function pl_qual_names (param1 int) returns void as $$ +<> +declare + param1 int := 1; +begin + <> + declare + param1 int := 2; + begin + raise notice 'param1 = %', param1; + raise notice 'pl_qual_names.param1 = %', pl_qual_names.param1; + raise notice 'outerblock.param1 = %', outerblock.param1; + raise notice 'innerblock.param1 = %', innerblock.param1; + end; +end; +$$ language plpgsql; +select pl_qual_names(42); +NOTICE: param1 = 2 +NOTICE: pl_qual_names.param1 = 42 +NOTICE: outerblock.param1 = 1 +NOTICE: innerblock.param1 = 2 + pl_qual_names +--------------- + +(1 row) + +drop function pl_qual_names(int); +-- tests for RETURN QUERY +create function ret_query1(out int, out int) returns setof record as $$ +begin + $1 := -1; + $2 := -2; + return next; + return query select x + 1, x * 10 from generate_series(0, 10) s (x); + return next; +end; +$$ language plpgsql; +select * from ret_query1(); + column1 | column2 +---------+--------- + -1 | -2 + 1 | 0 + 2 | 10 + 3 | 20 + 4 | 30 + 5 | 40 + 6 | 50 + 7 | 60 + 8 | 70 + 9 | 80 + 10 | 90 + 11 | 100 + -1 | -2 +(13 rows) + +create type record_type as (x text, y int, z boolean); +create or replace function ret_query2(lim int) returns setof record_type as $$ +begin + return query select md5(s.x::text), s.x, s.x > 0 + from generate_series(-8, lim) s (x) where s.x % 2 = 0; +end; +$$ language plpgsql; +select * from ret_query2(8); + x | y | z +----------------------------------+----+--- + a8d2ec85eaf98407310b72eb73dda247 | -8 | f + 596a3d04481816330f07e4f97510c28f | -6 | f + 0267aaf632e87a63288a08331f22c7c3 | -4 | f + 5d7b9adcbe1c629ec722529dd12e5129 | -2 | f + cfcd208495d565ef66e7dff9f98764da | 0 | f + c81e728d9d4c2f636f067f89cc14862c | 2 | t + a87ff679a2f3e71d9181a67b7542122c | 4 | t + 1679091c5a880faf6fb5e6087eb1b2dc | 6 | t + c9f0f895fb98ab9159f51fd0297e236d | 8 | t +(9 rows) + +-- test EXECUTE USING +create function exc_using(int, text) returns int as $$ +declare i int; +begin + for i in execute 'select * from generate_series(1,$1)' using $1+1 loop + raise notice '%', i; + end loop; + execute 'select $2 + $2*3 + length($1)' into i using $2,$1; + return i; +end +$$ language plpgsql; +select exc_using(5, 'foobar'); +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 +NOTICE: 5 +NOTICE: 6 + exc_using +----------- + 26 +(1 row) + +drop function exc_using(int, text); +create or replace function exc_using(int) returns void as $$ +declare + c refcursor; + i int; +begin + open c for execute 'select * from generate_series(1,$1)' using $1+1; + loop + fetch c into i; + exit when not found; + raise notice '%', i; + end loop; + close c; + return; +end; +$$ language plpgsql; +select exc_using(5); +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 +NOTICE: 5 +NOTICE: 6 + exc_using +----------- + +(1 row) + +drop function exc_using(int); +-- test FOR-over-cursor +create or replace function forc01() returns void as $$ +declare + c cursor(r1 integer, r2 integer) + for select * from generate_series(r1,r2) i; + c2 cursor + for select * from generate_series(41,43) i; +begin + for r in c(5,7) loop + raise notice '% from %', r.i, c; + end loop; + -- again, to test if cursor was closed properly + for r in c(9,10) loop + raise notice '% from %', r.i, c; + end loop; + -- and test a parameterless cursor + for r in c2 loop + raise notice '% from %', r.i, c2; + end loop; + -- and try it with a hand-assigned name + raise notice 'after loop, c2 = %', c2; + c2 := 'special_name'; + for r in c2 loop + raise notice '% from %', r.i, c2; + end loop; + raise notice 'after loop, c2 = %', c2; + -- and try it with a generated name + -- (which we can't show in the output because it's variable) + c2 := null; + for r in c2 loop + raise notice '%', r.i; + end loop; + raise notice 'after loop, c2 = %', c2; + return; +end; +$$ language plpgsql; +select forc01(); +NOTICE: 5 from c +NOTICE: 6 from c +NOTICE: 7 from c +NOTICE: 9 from c +NOTICE: 10 from c +NOTICE: 41 from c2 +NOTICE: 42 from c2 +NOTICE: 43 from c2 +NOTICE: after loop, c2 = c2 +NOTICE: 41 from special_name +NOTICE: 42 from special_name +NOTICE: 43 from special_name +NOTICE: after loop, c2 = special_name +NOTICE: 41 +NOTICE: 42 +NOTICE: 43 +NOTICE: after loop, c2 = + forc01 +-------- + +(1 row) + +-- try updating the cursor's current row +create temp table forc_test as + select n as i, n as j from generate_series(1,10) n; +create or replace function forc01() returns void as $$ +declare + c cursor for select * from forc_test; +begin + for r in c loop + raise notice '%, %', r.i, r.j; + update forc_test set i = i * 100, j = r.j * 2 where current of c; + end loop; +end; +$$ language plpgsql; +ERROR: WHERE CURRENT OF not supported yet +LINE 7: update forc_test set i = i * 100, j = r.j * 2 where curr... + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +drop function forc01(); +-- fail because cursor has no query bound to it +create or replace function forc_bad() returns void as $$ +declare + c refcursor; +begin + for r in c loop + raise notice '%', r.i; + end loop; +end; +$$ language plpgsql; +ERROR: cursor FOR loop must use a bound cursor variable +LINE 5: for r in c loop + ^ +-- test RETURN QUERY EXECUTE +create or replace function return_dquery() +returns setof int as $$ +begin + return query execute 'select * from (values(10),(20)) f'; + return query execute 'select * from (values($1),($2)) f' using 40,50; +end; +$$ language plpgsql; +select * from return_dquery(); + return_dquery +--------------- + 10 + 20 + 40 + 50 +(4 rows) + +drop function return_dquery(); +-- test RETURN QUERY with dropped columns +create table tabwithcols(a int, b int, c int, d int); +insert into tabwithcols values(10,20,30,40),(50,60,70,80); +create or replace function returnqueryf() +returns setof tabwithcols as $$ +begin + -- YB note: add ordering. + return query select * from tabwithcols order by a; + return query execute 'select * from tabwithcols order by a'; +end; +$$ language plpgsql; +select * from returnqueryf(); + a | b | c | d +----+----+----+---- + 10 | 20 | 30 | 40 + 50 | 60 | 70 | 80 + 10 | 20 | 30 | 40 + 50 | 60 | 70 | 80 +(4 rows) + +alter table tabwithcols drop column b; +select * from returnqueryf(); + a | c | d +----+----+---- + 10 | 30 | 40 + 50 | 70 | 80 + 10 | 30 | 40 + 50 | 70 | 80 +(4 rows) + +alter table tabwithcols drop column d; +select * from returnqueryf(); + a | c +----+---- + 10 | 30 + 50 | 70 + 10 | 30 + 50 | 70 +(4 rows) + +alter table tabwithcols add column d int; +select * from returnqueryf(); + a | c | d +----+----+--- + 10 | 30 | + 50 | 70 | + 10 | 30 | + 50 | 70 | +(4 rows) + +drop function returnqueryf(); +drop table tabwithcols; +-- +-- Tests for composite-type results +-- +create type compostype as (x int, y varchar); +-- test: use of variable of composite type in return statement +create or replace function compos() returns compostype as $$ +declare + v compostype; +begin + v := (1, 'hello'); + return v; +end; +$$ language plpgsql; +select compos(); + compos +----------- + (1,hello) +(1 row) + +-- test: use of variable of record type in return statement +create or replace function compos() returns compostype as $$ +declare + v record; +begin + v := (1, 'hello'::varchar); + return v; +end; +$$ language plpgsql; +select compos(); + compos +----------- + (1,hello) +(1 row) + +-- test: use of row expr in return statement +create or replace function compos() returns compostype as $$ +begin + return (1, 'hello'::varchar); +end; +$$ language plpgsql; +select compos(); + compos +----------- + (1,hello) +(1 row) + +-- this does not work currently (no implicit casting) +create or replace function compos() returns compostype as $$ +begin + return (1, 'hello'); +end; +$$ language plpgsql; +select compos(); +ERROR: returned record type does not match expected record type +DETAIL: Returned type unknown does not match expected type character varying in column 2. +CONTEXT: PL/pgSQL function compos() while casting return value to function's return type +-- ... but this does +create or replace function compos() returns compostype as $$ +begin + return (1, 'hello')::compostype; +end; +$$ language plpgsql; +select compos(); + compos +----------- + (1,hello) +(1 row) + +drop function compos(); +-- test: return a row expr as record. +create or replace function composrec() returns record as $$ +declare + v record; +begin + v := (1, 'hello'); + return v; +end; +$$ language plpgsql; +select composrec(); + composrec +----------- + (1,hello) +(1 row) + +-- test: return row expr in return statement. +create or replace function composrec() returns record as $$ +begin + return (1, 'hello'); +end; +$$ language plpgsql; +select composrec(); + composrec +----------- + (1,hello) +(1 row) + +drop function composrec(); +-- test: row expr in RETURN NEXT statement. +create or replace function compos() returns setof compostype as $$ +begin + for i in 1..3 + loop + return next (1, 'hello'::varchar); + end loop; + return next null::compostype; + return next (2, 'goodbye')::compostype; +end; +$$ language plpgsql; +select * from compos(); + x | y +---+--------- + 1 | hello + 1 | hello + 1 | hello + | + 2 | goodbye +(5 rows) + +drop function compos(); +-- test: use invalid expr in return statement. +create or replace function compos() returns compostype as $$ +begin + return 1 + 1; +end; +$$ language plpgsql; +select compos(); +ERROR: cannot return non-composite value from function returning composite type +CONTEXT: PL/pgSQL function compos() line 3 at RETURN +-- RETURN variable is a different code path ... +create or replace function compos() returns compostype as $$ +declare x int := 42; +begin + return x; +end; +$$ language plpgsql; +select * from compos(); +ERROR: cannot return non-composite value from function returning composite type +CONTEXT: PL/pgSQL function compos() line 4 at RETURN +drop function compos(); +-- test: invalid use of composite variable in scalar-returning function +create or replace function compos() returns int as $$ +declare + v compostype; +begin + v := (1, 'hello'); + return v; +end; +$$ language plpgsql; +select compos(); +ERROR: invalid input syntax for type integer: "(1,hello)" +CONTEXT: PL/pgSQL function compos() while casting return value to function's return type +-- test: invalid use of composite expression in scalar-returning function +create or replace function compos() returns int as $$ +begin + return (1, 'hello')::compostype; +end; +$$ language plpgsql; +select compos(); +ERROR: invalid input syntax for type integer: "(1,hello)" +CONTEXT: PL/pgSQL function compos() while casting return value to function's return type +drop function compos(); +drop type compostype; +-- +-- Tests for 8.4's new RAISE features +-- +create or replace function raise_test() returns void as $$ +begin + raise notice '% % %', 1, 2, 3 + using errcode = '55001', detail = 'some detail info', hint = 'some hint'; + raise '% % %', 1, 2, 3 + using errcode = 'division_by_zero', detail = 'some detail info'; +end; +$$ language plpgsql; +select raise_test(); +NOTICE: 1 2 3 +DETAIL: some detail info +HINT: some hint +ERROR: 1 2 3 +DETAIL: some detail info +CONTEXT: PL/pgSQL function raise_test() line 5 at RAISE +-- Since we can't actually see the thrown SQLSTATE in default psql output, +-- test it like this; this also tests re-RAISE +create or replace function raise_test() returns void as $$ +begin + raise 'check me' + using errcode = 'division_by_zero', detail = 'some detail info'; + exception + when others then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; +select raise_test(); +NOTICE: SQLSTATE: 22012 SQLERRM: check me +ERROR: check me +DETAIL: some detail info +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +create or replace function raise_test() returns void as $$ +begin + raise 'check me' + using errcode = '1234F', detail = 'some detail info'; + exception + when others then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; +select raise_test(); +NOTICE: SQLSTATE: 1234F SQLERRM: check me +ERROR: check me +DETAIL: some detail info +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +-- SQLSTATE specification in WHEN +create or replace function raise_test() returns void as $$ +begin + raise 'check me' + using errcode = '1234F', detail = 'some detail info'; + exception + when sqlstate '1234F' then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; +select raise_test(); +NOTICE: SQLSTATE: 1234F SQLERRM: check me +ERROR: check me +DETAIL: some detail info +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero using detail = 'some detail info'; + exception + when others then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; +select raise_test(); +NOTICE: SQLSTATE: 22012 SQLERRM: division_by_zero +ERROR: division_by_zero +DETAIL: some detail info +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero; +end; +$$ language plpgsql; +select raise_test(); +ERROR: division_by_zero +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +create or replace function raise_test() returns void as $$ +begin + raise sqlstate '1234F'; +end; +$$ language plpgsql; +select raise_test(); +ERROR: 1234F +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero using message = 'custom' || ' message'; +end; +$$ language plpgsql; +select raise_test(); +ERROR: custom message +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +create or replace function raise_test() returns void as $$ +begin + raise using message = 'custom' || ' message', errcode = '22012'; +end; +$$ language plpgsql; +select raise_test(); +ERROR: custom message +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +-- conflict on message +create or replace function raise_test() returns void as $$ +begin + raise notice 'some message' using message = 'custom' || ' message', errcode = '22012'; +end; +$$ language plpgsql; +select raise_test(); +ERROR: RAISE option already specified: MESSAGE +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +-- conflict on errcode +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero using message = 'custom' || ' message', errcode = '22012'; +end; +$$ language plpgsql; +select raise_test(); +ERROR: RAISE option already specified: ERRCODE +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +-- nothing to re-RAISE +create or replace function raise_test() returns void as $$ +begin + raise; +end; +$$ language plpgsql; +select raise_test(); +ERROR: RAISE without parameters cannot be used outside an exception handler +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +-- test access to exception data +create function zero_divide() returns int as $$ +declare v int := 0; +begin + return 10 / v; +end; +$$ language plpgsql; +create or replace function raise_test() returns void as $$ +begin + raise exception 'custom exception' + using detail = 'some detail of custom exception', + hint = 'some hint related to custom exception'; +end; +$$ language plpgsql; +create function stacked_diagnostics_test() returns void as $$ +declare _sqlstate text; + _message text; + _context text; +begin + perform zero_divide(); +exception when others then + get stacked diagnostics + _sqlstate = returned_sqlstate, + _message = message_text, + _context = pg_exception_context; + raise notice 'sqlstate: %, message: %, context: [%]', + _sqlstate, _message, replace(_context, E'\n', ' <- '); +end; +$$ language plpgsql; +select stacked_diagnostics_test(); +NOTICE: sqlstate: 22012, message: division by zero, context: [PL/pgSQL function zero_divide() line 4 at RETURN <- SQL statement "SELECT zero_divide()" <- PL/pgSQL function stacked_diagnostics_test() line 6 at PERFORM] + stacked_diagnostics_test +-------------------------- + +(1 row) + +create or replace function stacked_diagnostics_test() returns void as $$ +declare _detail text; + _hint text; + _message text; +begin + perform raise_test(); +exception when others then + get stacked diagnostics + _message = message_text, + _detail = pg_exception_detail, + _hint = pg_exception_hint; + raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; +end; +$$ language plpgsql; +select stacked_diagnostics_test(); +NOTICE: message: custom exception, detail: some detail of custom exception, hint: some hint related to custom exception + stacked_diagnostics_test +-------------------------- + +(1 row) + +-- fail, cannot use stacked diagnostics statement outside handler +create or replace function stacked_diagnostics_test() returns void as $$ +declare _detail text; + _hint text; + _message text; +begin + get stacked diagnostics + _message = message_text, + _detail = pg_exception_detail, + _hint = pg_exception_hint; + raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; +end; +$$ language plpgsql; +select stacked_diagnostics_test(); +ERROR: GET STACKED DIAGNOSTICS cannot be used outside an exception handler +CONTEXT: PL/pgSQL function stacked_diagnostics_test() line 6 at GET STACKED DIAGNOSTICS +drop function zero_divide(); +drop function stacked_diagnostics_test(); +-- check cases where implicit SQLSTATE variable could be confused with +-- SQLSTATE as a keyword, cf bug #5524 +create or replace function raise_test() returns void as $$ +begin + perform 1/0; +exception + when sqlstate '22012' then + raise notice using message = sqlstate; + raise sqlstate '22012' using message = 'substitute message'; +end; +$$ language plpgsql; +select raise_test(); +NOTICE: 22012 +ERROR: substitute message +CONTEXT: PL/pgSQL function raise_test() line 7 at RAISE +drop function raise_test(); +-- test passing column_name, constraint_name, datatype_name, table_name +-- and schema_name error fields +create or replace function stacked_diagnostics_test() returns void as $$ +declare _column_name text; + _constraint_name text; + _datatype_name text; + _table_name text; + _schema_name text; +begin + raise exception using + column = '>>some column name<<', + constraint = '>>some constraint name<<', + datatype = '>>some datatype name<<', + table = '>>some table name<<', + schema = '>>some schema name<<'; +exception when others then + get stacked diagnostics + _column_name = column_name, + _constraint_name = constraint_name, + _datatype_name = pg_datatype_name, + _table_name = table_name, + _schema_name = schema_name; + raise notice 'column %, constraint %, type %, table %, schema %', + _column_name, _constraint_name, _datatype_name, _table_name, _schema_name; +end; +$$ language plpgsql; +select stacked_diagnostics_test(); +NOTICE: column >>some column name<<, constraint >>some constraint name<<, type >>some datatype name<<, table >>some table name<<, schema >>some schema name<< + stacked_diagnostics_test +-------------------------- + +(1 row) + +drop function stacked_diagnostics_test(); +-- test variadic functions +create or replace function vari(variadic int[]) +returns void as $$ +begin + for i in array_lower($1,1)..array_upper($1,1) loop + raise notice '%', $1[i]; + end loop; end; +$$ language plpgsql; +select vari(1,2,3,4,5); +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 +NOTICE: 5 + vari +------ + +(1 row) + +select vari(3,4,5); +NOTICE: 3 +NOTICE: 4 +NOTICE: 5 + vari +------ + +(1 row) + +select vari(variadic array[5,6,7]); +NOTICE: 5 +NOTICE: 6 +NOTICE: 7 + vari +------ + +(1 row) + +drop function vari(int[]); +-- coercion test +create or replace function pleast(variadic numeric[]) +returns numeric as $$ +declare aux numeric = $1[array_lower($1,1)]; +begin + for i in array_lower($1,1)+1..array_upper($1,1) loop + if $1[i] < aux then aux := $1[i]; end if; + end loop; + return aux; +end; +$$ language plpgsql immutable strict; +select pleast(10,1,2,3,-16); + pleast +-------- + -16 +(1 row) + +select pleast(10.2,2.2,-1.1); + pleast +-------- + -1.1 +(1 row) + +select pleast(10.2,10, -20); + pleast +-------- + -20 +(1 row) + +select pleast(10,20, -1.0); + pleast +-------- + -1.0 +(1 row) + +-- in case of conflict, non-variadic version is preferred +create or replace function pleast(numeric) +returns numeric as $$ +begin + raise notice 'non-variadic function called'; + return $1; +end; +$$ language plpgsql immutable strict; +select pleast(10); +NOTICE: non-variadic function called + pleast +-------- + 10 +(1 row) + +drop function pleast(numeric[]); +drop function pleast(numeric); +-- test table functions +create function tftest(int) returns table(a int, b int) as $$ +begin + return query select $1, $1+i from generate_series(1,5) g(i); +end; +$$ language plpgsql immutable strict; +select * from tftest(10); + a | b +----+---- + 10 | 11 + 10 | 12 + 10 | 13 + 10 | 14 + 10 | 15 +(5 rows) + +create or replace function tftest(a1 int) returns table(a int, b int) as $$ +begin + a := a1; b := a1 + 1; + return next; + a := a1 * 10; b := a1 * 10 + 1; + return next; +end; +$$ language plpgsql immutable strict; +select * from tftest(10); + a | b +-----+----- + 10 | 11 + 100 | 101 +(2 rows) + +drop function tftest(int); +create function rttest() +returns setof int as $$ +declare rc int; +begin + return query values(10),(20); + get diagnostics rc = row_count; + raise notice '% %', found, rc; + return query select * from (values(10),(20)) f(a) where false; + get diagnostics rc = row_count; + raise notice '% %', found, rc; + return query execute 'values(10),(20)'; + get diagnostics rc = row_count; + raise notice '% %', found, rc; + return query execute 'select * from (values(10),(20)) f(a) where false'; + get diagnostics rc = row_count; + raise notice '% %', found, rc; +end; +$$ language plpgsql; +select * from rttest(); +NOTICE: t 2 +NOTICE: f 0 +NOTICE: t 2 +NOTICE: f 0 + rttest +-------- + 10 + 20 + 10 + 20 +(4 rows) + +-- check some error cases, too +create or replace function rttest() +returns setof int as $$ +begin + return query select 10 into no_such_table; +end; +$$ language plpgsql; +select * from rttest(); +ERROR: SELECT INTO query does not return tuples +CONTEXT: SQL statement "select 10 into no_such_table" +PL/pgSQL function rttest() line 3 at RETURN QUERY +create or replace function rttest() +returns setof int as $$ +begin + return query execute 'select 10 into no_such_table'; +end; +$$ language plpgsql; +select * from rttest(); +ERROR: SELECT INTO query does not return tuples +CONTEXT: SQL statement "select 10 into no_such_table" +PL/pgSQL function rttest() line 3 at RETURN QUERY +select * from no_such_table; +ERROR: relation "no_such_table" does not exist +LINE 1: select * from no_such_table; + ^ +drop function rttest(); +-- Test for proper cleanup at subtransaction exit. This example +-- exposed a bug in PG 8.2. +CREATE FUNCTION leaker_1(fail BOOL) RETURNS INTEGER AS $$ +DECLARE + v_var INTEGER; +BEGIN + BEGIN + v_var := (leaker_2(fail)).error_code; + EXCEPTION + WHEN others THEN RETURN 0; + END; + RETURN 1; +END; +$$ LANGUAGE plpgsql; +CREATE FUNCTION leaker_2(fail BOOL, OUT error_code INTEGER, OUT new_id INTEGER) + RETURNS RECORD AS $$ +BEGIN + IF fail THEN + RAISE EXCEPTION 'fail ...'; + END IF; + error_code := 1; + new_id := 1; + RETURN; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM leaker_1(false); + leaker_1 +---------- + 1 +(1 row) + +SELECT * FROM leaker_1(true); + leaker_1 +---------- + 0 +(1 row) + +DROP FUNCTION leaker_1(bool); +DROP FUNCTION leaker_2(bool); +-- Test for appropriate cleanup of non-simple expression evaluations +-- (bug in all versions prior to August 2010) +CREATE FUNCTION nonsimple_expr_test() RETURNS text[] AS $$ +DECLARE + arr text[]; + lr text; + i integer; +BEGIN + arr := array[array['foo','bar'], array['baz', 'quux']]; + lr := 'fool'; + i := 1; + -- use sub-SELECTs to make expressions non-simple + arr[(SELECT i)][(SELECT i+1)] := (SELECT lr); + RETURN arr; +END; +$$ LANGUAGE plpgsql; +SELECT nonsimple_expr_test(); + nonsimple_expr_test +------------------------- + {{foo,fool},{baz,quux}} +(1 row) + +DROP FUNCTION nonsimple_expr_test(); +CREATE FUNCTION nonsimple_expr_test() RETURNS integer AS $$ +declare + i integer NOT NULL := 0; +begin + begin + i := (SELECT NULL::integer); -- should throw error + exception + WHEN OTHERS THEN + i := (SELECT 1::integer); + end; + return i; +end; +$$ LANGUAGE plpgsql; +SELECT nonsimple_expr_test(); + nonsimple_expr_test +--------------------- + 1 +(1 row) + +DROP FUNCTION nonsimple_expr_test(); +-- +-- Test cases involving recursion and error recovery in simple expressions +-- (bugs in all versions before October 2010). The problems are most +-- easily exposed by mutual recursion between plpgsql and sql functions. +-- +create function recurse(float8) returns float8 as +$$ +begin + if ($1 > 0) then + return sql_recurse($1 - 1); + else + return $1; + end if; +end; +$$ language plpgsql; +-- "limit" is to prevent this from being inlined +create function sql_recurse(float8) returns float8 as +$$ select recurse($1) limit 1; $$ language sql; +select recurse(10); + recurse +--------- + 0 +(1 row) + +create function error1(text) returns text language sql as +$$ SELECT relname::text FROM pg_class c WHERE c.oid = $1::regclass $$; +create function error2(p_name_table text) returns text language plpgsql as $$ +begin + return error1(p_name_table); +end$$; +BEGIN; +create table public.stuffs (stuff text); +SAVEPOINT a; +select error2('nonexistent.stuffs'); +ERROR: schema "nonexistent" does not exist +CONTEXT: SQL function "error1" statement 1 +PL/pgSQL function error2(text) line 3 at RETURN +ROLLBACK TO a; +select error2('public.stuffs'); + error2 +-------- + stuffs +(1 row) + +rollback; +drop function error2(p_name_table text); +drop function error1(text); +-- Test for proper handling of cast-expression caching +create function sql_to_date(integer) returns date as $$ +select $1::text::date +$$ language sql immutable strict; +create cast (integer as date) with function sql_to_date(integer) as assignment; +create function cast_invoker(integer) returns date as $$ +begin + return $1; +end$$ language plpgsql; +select cast_invoker(20150717); + cast_invoker +-------------- + 07-17-2015 +(1 row) + +select cast_invoker(20150718); -- second call crashed in pre-release 9.5 + cast_invoker +-------------- + 07-18-2015 +(1 row) + +begin; +select cast_invoker(20150717); + cast_invoker +-------------- + 07-17-2015 +(1 row) + +select cast_invoker(20150718); + cast_invoker +-------------- + 07-18-2015 +(1 row) + +savepoint s1; +select cast_invoker(20150718); + cast_invoker +-------------- + 07-18-2015 +(1 row) + +select cast_invoker(-1); -- fails +ERROR: invalid input syntax for type date: "-1" +CONTEXT: SQL function "sql_to_date" statement 1 +PL/pgSQL function cast_invoker(integer) while casting return value to function's return type +rollback to savepoint s1; +select cast_invoker(20150719); + cast_invoker +-------------- + 07-19-2015 +(1 row) + +select cast_invoker(20150720); + cast_invoker +-------------- + 07-20-2015 +(1 row) + +commit; +drop function cast_invoker(integer); +drop function sql_to_date(integer) cascade; +NOTICE: drop cascades to cast from integer to date +-- Test handling of cast cache inside DO blocks +-- (to check the original crash case, this must be a cast not previously +-- used in this session) +begin; +do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$; +do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$; +end; +-- Test for consistent reporting of error context +create function fail() returns int language plpgsql as $$ +begin + return 1/0; +end +$$; +select fail(); +ERROR: division by zero +CONTEXT: SQL expression "1/0" +PL/pgSQL function fail() line 3 at RETURN +select fail(); +ERROR: division by zero +CONTEXT: SQL expression "1/0" +PL/pgSQL function fail() line 3 at RETURN +drop function fail(); +-- Test handling of string literals. +set standard_conforming_strings = off; +create or replace function strtest() returns text as $$ +begin + raise notice 'foo\\bar\041baz'; + return 'foo\\bar\041baz'; +end +$$ language plpgsql; +WARNING: nonstandard use of \\ in a string literal +LINE 3: raise notice 'foo\\bar\041baz'; + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +WARNING: nonstandard use of \\ in a string literal +LINE 4: return 'foo\\bar\041baz'; + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +WARNING: nonstandard use of \\ in a string literal +LINE 4: return 'foo\\bar\041baz'; + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +select strtest(); +NOTICE: foo\bar!baz +WARNING: nonstandard use of \\ in a string literal +LINE 1: 'foo\\bar\041baz' + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +QUERY: 'foo\\bar\041baz' + strtest +------------- + foo\bar!baz +(1 row) + +create or replace function strtest() returns text as $$ +begin + raise notice E'foo\\bar\041baz'; + return E'foo\\bar\041baz'; +end +$$ language plpgsql; +select strtest(); +NOTICE: foo\bar!baz + strtest +------------- + foo\bar!baz +(1 row) + +set standard_conforming_strings = on; +create or replace function strtest() returns text as $$ +begin + raise notice 'foo\\bar\041baz\'; + return 'foo\\bar\041baz\'; +end +$$ language plpgsql; +select strtest(); +NOTICE: foo\\bar\041baz\ + strtest +------------------ + foo\\bar\041baz\ +(1 row) + +create or replace function strtest() returns text as $$ +begin + raise notice E'foo\\bar\041baz'; + return E'foo\\bar\041baz'; +end +$$ language plpgsql; +select strtest(); +NOTICE: foo\bar!baz + strtest +------------- + foo\bar!baz +(1 row) + +drop function strtest(); +-- Test anonymous code blocks. +DO $$ +DECLARE r record; +BEGIN + FOR r IN SELECT rtrim(roomno) AS roomno, comment FROM Room ORDER BY roomno + LOOP + RAISE NOTICE '%, %', r.roomno, r.comment; + END LOOP; +END$$; +NOTICE: 001, Entrance +NOTICE: 002, Office +NOTICE: 003, Office +NOTICE: 004, Technical +NOTICE: 101, Office +NOTICE: 102, Conference +NOTICE: 103, Restroom +NOTICE: 104, Technical +NOTICE: 105, Office +NOTICE: 106, Office +-- these are to check syntax error reporting +DO LANGUAGE plpgsql $$begin return 1; end$$; +ERROR: RETURN cannot have a parameter in function returning void +LINE 1: DO LANGUAGE plpgsql $$begin return 1; end$$; + ^ +DO $$ +DECLARE r record; +BEGIN + FOR r IN SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno + LOOP + RAISE NOTICE '%, %', r.roomno, r.comment; + END LOOP; +END$$; +ERROR: column "foo" does not exist +LINE 1: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomn... + ^ +QUERY: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno +CONTEXT: PL/pgSQL function inline_code_block line 4 at FOR over SELECT rows +-- Check handling of errors thrown from/into anonymous code blocks. +do $outer$ +begin + for i in 1..10 loop + begin + execute $ex$ + do $$ + declare x int = 0; + begin + x := 1 / x; + end; + $$; + $ex$; + exception when division_by_zero then + raise notice 'caught division by zero'; + end; + end loop; +end; +$outer$; +NOTICE: caught division by zero +NOTICE: caught division by zero +NOTICE: caught division by zero +NOTICE: caught division by zero +NOTICE: caught division by zero +NOTICE: caught division by zero +NOTICE: caught division by zero +NOTICE: caught division by zero +NOTICE: caught division by zero +NOTICE: caught division by zero +-- Check variable scoping -- a var is not available in its own or prior +-- default expressions, but it is available in later ones. +do $$ +declare x int := x + 1; -- error +begin + raise notice 'x = %', x; +end; +$$; +ERROR: column "x" does not exist +LINE 1: x + 1 + ^ +QUERY: x + 1 +CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization +do $$ +declare y int := x + 1; -- error + x int := 42; +begin + raise notice 'x = %, y = %', x, y; +end; +$$; +ERROR: column "x" does not exist +LINE 1: x + 1 + ^ +QUERY: x + 1 +CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization +do $$ +declare x int := 42; + y int := x + 1; +begin + raise notice 'x = %, y = %', x, y; +end; +$$; +NOTICE: x = 42, y = 43 +do $$ +declare x int := 42; +begin + declare y int := x + 1; + x int := x + 2; + z int := x * 10; + begin + raise notice 'x = %, y = %, z = %', x, y, z; + end; +end; +$$; +NOTICE: x = 44, y = 43, z = 440 +-- Check handling of conflicts between plpgsql vars and table columns. +set plpgsql.variable_conflict = error; +create function conflict_test() returns setof int8_tbl as $$ +declare r record; + q1 bigint := 42; +begin + for r in select q1,q2 from int8_tbl loop + return next r; + end loop; +end; +$$ language plpgsql; +select * from conflict_test(); +ERROR: column reference "q1" is ambiguous +LINE 1: select q1,q2 from int8_tbl + ^ +DETAIL: It could refer to either a PL/pgSQL variable or a table column. +QUERY: select q1,q2 from int8_tbl +CONTEXT: PL/pgSQL function conflict_test() line 5 at FOR over SELECT rows +create or replace function conflict_test() returns setof int8_tbl as $$ +#variable_conflict use_variable +declare r record; + q1 bigint := 42; +begin + for r in select q1,q2 from int8_tbl loop + return next r; + end loop; +end; +$$ language plpgsql; +select * from conflict_test(); + q1 | q2 +----+------------------- + 42 | 456 + 42 | 4567890123456789 + 42 | 123 + 42 | 4567890123456789 + 42 | -4567890123456789 +(5 rows) + +create or replace function conflict_test() returns setof int8_tbl as $$ +#variable_conflict use_column +declare r record; + q1 bigint := 42; +begin + for r in select q1,q2 from int8_tbl loop + return next r; + end loop; +end; +$$ language plpgsql; +select * from conflict_test(); + q1 | q2 +------------------+------------------- + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(5 rows) + +drop function conflict_test(); +-- Check that an unreserved keyword can be used as a variable name +create function unreserved_test() returns int as $$ +declare + forward int := 21; +begin + forward := forward * 2; + return forward; +end +$$ language plpgsql; +select unreserved_test(); + unreserved_test +----------------- + 42 +(1 row) + +create or replace function unreserved_test() returns int as $$ +declare + return int := 42; +begin + return := return + 1; + return return; +end +$$ language plpgsql; +select unreserved_test(); + unreserved_test +----------------- + 43 +(1 row) + +create or replace function unreserved_test() returns int as $$ +declare + comment int := 21; +begin + comment := comment * 2; + comment on function unreserved_test() is 'this is a test'; + return comment; +end +$$ language plpgsql; +select unreserved_test(); + unreserved_test +----------------- + 42 +(1 row) + +select obj_description('unreserved_test()'::regprocedure, 'pg_proc'); + obj_description +----------------- + this is a test +(1 row) + +drop function unreserved_test(); +-- +-- Test FOREACH over arrays +-- +create function foreach_test(anyarray) +returns void as $$ +declare x int; +begin + foreach x in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; +select foreach_test(ARRAY[1,2,3,4]); +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[1,2],[3,4]]); +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 + foreach_test +-------------- + +(1 row) + +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int; +begin + foreach x slice 1 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; +-- should fail +select foreach_test(ARRAY[1,2,3,4]); +ERROR: FOREACH ... SLICE loop variable must be of an array type +CONTEXT: PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array +select foreach_test(ARRAY[[1,2],[3,4]]); +ERROR: FOREACH ... SLICE loop variable must be of an array type +CONTEXT: PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int[]; +begin + foreach x slice 1 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; +select foreach_test(ARRAY[1,2,3,4]); +NOTICE: {1,2,3,4} + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[1,2],[3,4]]); +NOTICE: {1,2} +NOTICE: {3,4} + foreach_test +-------------- + +(1 row) + +-- higher level of slicing +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int[]; +begin + foreach x slice 2 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; +-- should fail +select foreach_test(ARRAY[1,2,3,4]); +ERROR: slice dimension (2) is out of the valid range 0..1 +CONTEXT: PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array +-- ok +select foreach_test(ARRAY[[1,2],[3,4]]); +NOTICE: {{1,2},{3,4}} + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[[1,2]],[[3,4]]]); +NOTICE: {{1,2}} +NOTICE: {{3,4}} + foreach_test +-------------- + +(1 row) + +create type xy_tuple AS (x int, y int); +-- iteration over array of records +create or replace function foreach_test(anyarray) +returns void as $$ +declare r record; +begin + foreach r in array $1 + loop + raise notice '%', r; + end loop; + end; +$$ language plpgsql; +select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); +NOTICE: (10,20) +NOTICE: (40,69) +NOTICE: (35,78) + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); +NOTICE: (10,20) +NOTICE: (40,69) +NOTICE: (35,78) +NOTICE: (88,76) + foreach_test +-------------- + +(1 row) + +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int; y int; +begin + foreach x, y in array $1 + loop + raise notice 'x = %, y = %', x, y; + end loop; + end; +$$ language plpgsql; +select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); +NOTICE: x = 10, y = 20 +NOTICE: x = 40, y = 69 +NOTICE: x = 35, y = 78 + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); +NOTICE: x = 10, y = 20 +NOTICE: x = 40, y = 69 +NOTICE: x = 35, y = 78 +NOTICE: x = 88, y = 76 + foreach_test +-------------- + +(1 row) + +-- slicing over array of composite types +create or replace function foreach_test(anyarray) +returns void as $$ +declare x xy_tuple[]; +begin + foreach x slice 1 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; +select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); +NOTICE: {"(10,20)","(40,69)","(35,78)"} + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); +NOTICE: {"(10,20)","(40,69)"} +NOTICE: {"(35,78)","(88,76)"} + foreach_test +-------------- + +(1 row) + +drop function foreach_test(anyarray); +drop type xy_tuple; +-- +-- Assorted tests for array subscript assignment +-- +create temp table rtype (id int, ar text[]); +create function arrayassign1() returns text[] language plpgsql as $$ +declare + r record; +begin + r := row(12, '{foo,bar,baz}')::rtype; + r.ar[2] := 'replace'; + return r.ar; +end$$; +select arrayassign1(); + arrayassign1 +------------------- + {foo,replace,baz} +(1 row) + +select arrayassign1(); -- try again to exercise internal caching + arrayassign1 +------------------- + {foo,replace,baz} +(1 row) + +create domain orderedarray as int[2] + constraint sorted check (value[1] < value[2]); +select '{1,2}'::orderedarray; + orderedarray +-------------- + {1,2} +(1 row) + +select '{2,1}'::orderedarray; -- fail +ERROR: value for domain orderedarray violates check constraint "sorted" +create function testoa(x1 int, x2 int, x3 int) returns orderedarray +language plpgsql as $$ +declare res orderedarray; +begin + res := array[x1, x2]; + res[2] := x3; + return res; +end$$; +select testoa(1,2,3); + testoa +-------- + {1,3} +(1 row) + +select testoa(1,2,3); -- try again to exercise internal caching + testoa +-------- + {1,3} +(1 row) + +select testoa(2,1,3); -- fail at initial assign +ERROR: value for domain orderedarray violates check constraint "sorted" +CONTEXT: PL/pgSQL function testoa(integer,integer,integer) line 4 at assignment +select testoa(1,2,1); -- fail at update +ERROR: value for domain orderedarray violates check constraint "sorted" +CONTEXT: PL/pgSQL function testoa(integer,integer,integer) line 5 at assignment +drop function arrayassign1(); +drop function testoa(x1 int, x2 int, x3 int); +-- +-- Test handling of expanded arrays +-- +create function returns_rw_array(int) returns int[] +language plpgsql as $$ + declare r int[]; + begin r := array[$1, $1]; return r; end; +$$ stable; +create function consumes_rw_array(int[]) returns int +language plpgsql as $$ + begin return $1[1]; end; +$$ stable; +select consumes_rw_array(returns_rw_array(42)); + consumes_rw_array +------------------- + 42 +(1 row) + +-- bug #14174 +explain (verbose, costs off) +select i, a from + (select returns_rw_array(1) as a offset 0) ss, + lateral consumes_rw_array(a) i; + QUERY PLAN +----------------------------------------------------------------- + Nested Loop + Output: i.i, (returns_rw_array(1)) + -> Result *RESULT* + Output: returns_rw_array(1) + -> Function Scan on public.consumes_rw_array i + Output: i.i + Function Call: consumes_rw_array((returns_rw_array(1))) +(7 rows) + +select i, a from + (select returns_rw_array(1) as a offset 0) ss, + lateral consumes_rw_array(a) i; + i | a +---+------- + 1 | {1,1} +(1 row) + +explain (verbose, costs off) +select consumes_rw_array(a), a from returns_rw_array(1) a; + QUERY PLAN +-------------------------------------------- + Function Scan on public.returns_rw_array a + Output: consumes_rw_array(a), a + Function Call: returns_rw_array(1) +(3 rows) + +select consumes_rw_array(a), a from returns_rw_array(1) a; + consumes_rw_array | a +-------------------+------- + 1 | {1,1} +(1 row) + +explain (verbose, costs off) +select consumes_rw_array(a), a from + (values (returns_rw_array(1)), (returns_rw_array(2))) v(a); + QUERY PLAN +--------------------------------------------------------------------- + Values Scan on "*VALUES*" + Output: consumes_rw_array("*VALUES*".column1), "*VALUES*".column1 +(2 rows) + +select consumes_rw_array(a), a from + (values (returns_rw_array(1)), (returns_rw_array(2))) v(a); + consumes_rw_array | a +-------------------+------- + 1 | {1,1} + 2 | {2,2} +(2 rows) + +do $$ +declare a int[] := array[1,2]; +begin + a := a || 3; + raise notice 'a = %', a; +end$$; +NOTICE: a = {1,2,3} +-- +-- Test access to call stack +-- +create function inner_func(int) +returns int as $$ +declare _context text; +begin + get diagnostics _context = pg_context; + raise notice '***%***', _context; + -- lets do it again, just for fun.. + get diagnostics _context = pg_context; + raise notice '***%***', _context; + raise notice 'lets make sure we didnt break anything'; + return 2 * $1; +end; +$$ language plpgsql; +create or replace function outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into inner_func()'; + myresult := inner_func($1); + raise notice 'inner_func() done'; + return myresult; +end; +$$ language plpgsql; +create or replace function outer_outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into outer_func()'; + myresult := outer_func($1); + raise notice 'outer_func() done'; + return myresult; +end; +$$ language plpgsql; +select outer_outer_func(10); +NOTICE: calling down into outer_func() +NOTICE: calling down into inner_func() +NOTICE: ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +NOTICE: ***PL/pgSQL function inner_func(integer) line 7 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +NOTICE: lets make sure we didnt break anything +NOTICE: inner_func() done +NOTICE: outer_func() done + outer_outer_func +------------------ + 20 +(1 row) + +-- repeated call should to work +select outer_outer_func(20); +NOTICE: calling down into outer_func() +NOTICE: calling down into inner_func() +NOTICE: ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +NOTICE: ***PL/pgSQL function inner_func(integer) line 7 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +NOTICE: lets make sure we didnt break anything +NOTICE: inner_func() done +NOTICE: outer_func() done + outer_outer_func +------------------ + 40 +(1 row) + +drop function outer_outer_func(int); +drop function outer_func(int); +drop function inner_func(int); +-- access to call stack from exception +create function inner_func(int) +returns int as $$ +declare + _context text; + sx int := 5; +begin + begin + perform sx / 0; + exception + when division_by_zero then + get diagnostics _context = pg_context; + raise notice '***%***', _context; + end; + + -- lets do it again, just for fun.. + get diagnostics _context = pg_context; + raise notice '***%***', _context; + raise notice 'lets make sure we didnt break anything'; + return 2 * $1; +end; +$$ language plpgsql; +create or replace function outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into inner_func()'; + myresult := inner_func($1); + raise notice 'inner_func() done'; + return myresult; +end; +$$ language plpgsql; +create or replace function outer_outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into outer_func()'; + myresult := outer_func($1); + raise notice 'outer_func() done'; + return myresult; +end; +$$ language plpgsql; +select outer_outer_func(10); +NOTICE: calling down into outer_func() +NOTICE: calling down into inner_func() +NOTICE: ***PL/pgSQL function inner_func(integer) line 10 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +NOTICE: ***PL/pgSQL function inner_func(integer) line 15 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +NOTICE: lets make sure we didnt break anything +NOTICE: inner_func() done +NOTICE: outer_func() done + outer_outer_func +------------------ + 20 +(1 row) + +-- repeated call should to work +select outer_outer_func(20); +NOTICE: calling down into outer_func() +NOTICE: calling down into inner_func() +NOTICE: ***PL/pgSQL function inner_func(integer) line 10 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +NOTICE: ***PL/pgSQL function inner_func(integer) line 15 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +NOTICE: lets make sure we didnt break anything +NOTICE: inner_func() done +NOTICE: outer_func() done + outer_outer_func +------------------ + 40 +(1 row) + +drop function outer_outer_func(int); +drop function outer_func(int); +drop function inner_func(int); +-- +-- Test ASSERT +-- +do $$ +begin + assert 1=1; -- should succeed +end; +$$; +do $$ +begin + assert 1=0; -- should fail +end; +$$; +ERROR: assertion failed +CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT +do $$ +begin + assert NULL; -- should fail +end; +$$; +ERROR: assertion failed +CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT +-- check controlling GUC +set plpgsql.check_asserts = off; +do $$ +begin + assert 1=0; -- won't be tested +end; +$$; +reset plpgsql.check_asserts; +-- test custom message +do $$ +declare var text := 'some value'; +begin + assert 1=0, format('assertion failed, var = "%s"', var); +end; +$$; +ERROR: assertion failed, var = "some value" +CONTEXT: PL/pgSQL function inline_code_block line 4 at ASSERT +-- ensure assertions are not trapped by 'others' +do $$ +begin + assert 1=0, 'unhandled assertion'; +exception when others then + null; -- do nothing +end; +$$; +ERROR: unhandled assertion +CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT +-- Test use of plpgsql in a domain check constraint (cf. bug #14414) +create function plpgsql_domain_check(val int) returns boolean as $$ +begin return val > 0; end +$$ language plpgsql immutable; +create domain plpgsql_domain as integer check(plpgsql_domain_check(value)); +do $$ +declare v_test plpgsql_domain; +begin + v_test := 1; +end; +$$; +do $$ +declare v_test plpgsql_domain := 1; +begin + v_test := 0; -- fail +end; +$$; +ERROR: value for domain plpgsql_domain violates check constraint "plpgsql_domain_check" +CONTEXT: PL/pgSQL function inline_code_block line 4 at assignment +-- Test handling of expanded array passed to a domain constraint (bug #14472) +create function plpgsql_arr_domain_check(val int[]) returns boolean as $$ +begin return val[1] > 0; end +$$ language plpgsql immutable; +create domain plpgsql_arr_domain as int[] check(plpgsql_arr_domain_check(value)); +do $$ +declare v_test plpgsql_arr_domain; +begin + v_test := array[1]; + v_test := v_test || 2; +end; +$$; +do $$ +declare v_test plpgsql_arr_domain := array[1]; +begin + v_test := 0 || v_test; -- fail +end; +$$; +ERROR: value for domain plpgsql_arr_domain violates check constraint "plpgsql_arr_domain_check" +CONTEXT: PL/pgSQL function inline_code_block line 4 at assignment +-- +-- test usage of transition tables in AFTER triggers +-- +CREATE TABLE transition_table_base (id int PRIMARY KEY, val text); +CREATE FUNCTION transition_table_base_ins_func() + RETURNS trigger + LANGUAGE plpgsql +AS $$ +DECLARE + t text; + l text; +BEGIN + t = ''; + FOR l IN EXECUTE + $q$ + EXPLAIN (TIMING off, COSTS off, VERBOSE on) + SELECT * FROM newtable + $q$ LOOP + t = t || l || E'\n'; + END LOOP; + + RAISE INFO '%', t; + RETURN new; +END; +$$; +CREATE TRIGGER transition_table_base_ins_trig + AFTER INSERT ON transition_table_base + REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable + FOR EACH STATEMENT + EXECUTE PROCEDURE transition_table_base_ins_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +CREATE TRIGGER transition_table_base_ins_trig + AFTER INSERT ON transition_table_base + REFERENCING NEW TABLE AS newtable + FOR EACH STATEMENT + EXECUTE PROCEDURE transition_table_base_ins_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: REFERENCING NEW TABLE AS newtable + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +INSERT INTO transition_table_base VALUES (1, 'One'), (2, 'Two'); +INSERT INTO transition_table_base VALUES (3, 'Three'), (4, 'Four'); +CREATE OR REPLACE FUNCTION transition_table_base_upd_func() + RETURNS trigger + LANGUAGE plpgsql +AS $$ +DECLARE + t text; + l text; +BEGIN + t = ''; + FOR l IN EXECUTE + $q$ + EXPLAIN (TIMING off, COSTS off, VERBOSE on) + SELECT * FROM oldtable ot FULL JOIN newtable nt USING (id) + $q$ LOOP + t = t || l || E'\n'; + END LOOP; + + RAISE INFO '%', t; + RETURN new; +END; +$$; +CREATE TRIGGER transition_table_base_upd_trig + AFTER UPDATE ON transition_table_base + REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable + FOR EACH STATEMENT + EXECUTE PROCEDURE transition_table_base_upd_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +UPDATE transition_table_base + SET val = '*' || val || '*' + WHERE id BETWEEN 2 AND 3; +CREATE TABLE transition_table_level1 +( + level1_no serial NOT NULL , + level1_node_name varchar(255), + PRIMARY KEY (level1_no) +) WITHOUT OIDS; +CREATE TABLE transition_table_level2 +( + level2_no serial NOT NULL , + parent_no int NOT NULL, + level1_node_name varchar(255), + PRIMARY KEY (level2_no) +) WITHOUT OIDS; +CREATE TABLE transition_table_status +( + level int NOT NULL, + node_no int NOT NULL, + status int, + PRIMARY KEY (level, node_no) +) WITHOUT OIDS; +CREATE FUNCTION transition_table_level1_ri_parent_del_func() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ + DECLARE n bigint; + BEGIN + PERFORM FROM p JOIN transition_table_level2 c ON c.parent_no = p.level1_no; + IF FOUND THEN + RAISE EXCEPTION 'RI error'; + END IF; + RETURN NULL; + END; +$$; +CREATE TRIGGER transition_table_level1_ri_parent_del_trigger + AFTER DELETE ON transition_table_level1 + REFERENCING OLD TABLE AS p + FOR EACH STATEMENT EXECUTE PROCEDURE + transition_table_level1_ri_parent_del_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: REFERENCING OLD TABLE AS p + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +CREATE FUNCTION transition_table_level1_ri_parent_upd_func() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ + DECLARE + x int; + BEGIN + WITH p AS (SELECT level1_no, sum(delta) cnt + FROM (SELECT level1_no, 1 AS delta FROM i + UNION ALL + SELECT level1_no, -1 AS delta FROM d) w + GROUP BY level1_no + HAVING sum(delta) < 0) + SELECT level1_no + FROM p JOIN transition_table_level2 c ON c.parent_no = p.level1_no + INTO x; + IF FOUND THEN + RAISE EXCEPTION 'RI error'; + END IF; + RETURN NULL; + END; +$$; +CREATE TRIGGER transition_table_level1_ri_parent_upd_trigger + AFTER UPDATE ON transition_table_level1 + REFERENCING OLD TABLE AS d NEW TABLE AS i + FOR EACH STATEMENT EXECUTE PROCEDURE + transition_table_level1_ri_parent_upd_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: REFERENCING OLD TABLE AS d NEW TABLE AS i + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +CREATE FUNCTION transition_table_level2_ri_child_insupd_func() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ + BEGIN + PERFORM FROM i + LEFT JOIN transition_table_level1 p + ON p.level1_no IS NOT NULL AND p.level1_no = i.parent_no + WHERE p.level1_no IS NULL; + IF FOUND THEN + RAISE EXCEPTION 'RI error'; + END IF; + RETURN NULL; + END; +$$; +CREATE TRIGGER transition_table_level2_ri_child_ins_trigger + AFTER INSERT ON transition_table_level2 + REFERENCING NEW TABLE AS i + FOR EACH STATEMENT EXECUTE PROCEDURE + transition_table_level2_ri_child_insupd_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: REFERENCING NEW TABLE AS i + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +CREATE TRIGGER transition_table_level2_ri_child_upd_trigger + AFTER UPDATE ON transition_table_level2 + REFERENCING NEW TABLE AS i + FOR EACH STATEMENT EXECUTE PROCEDURE + transition_table_level2_ri_child_insupd_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: REFERENCING NEW TABLE AS i + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +-- create initial test data +INSERT INTO transition_table_level1 (level1_no) + SELECT generate_series(1,200); +ANALYZE transition_table_level1; +INSERT INTO transition_table_level2 (level2_no, parent_no) + SELECT level2_no, level2_no / 50 + 1 AS parent_no + FROM generate_series(1,9999) level2_no; +ANALYZE transition_table_level2; +INSERT INTO transition_table_status (level, node_no, status) + SELECT 1, level1_no, 0 FROM transition_table_level1; +INSERT INTO transition_table_status (level, node_no, status) + SELECT 2, level2_no, 0 FROM transition_table_level2; +ANALYZE transition_table_status; +INSERT INTO transition_table_level1(level1_no) + SELECT generate_series(201,1000); +ANALYZE transition_table_level1; +-- behave reasonably if someone tries to modify a transition table +CREATE FUNCTION transition_table_level2_bad_usage_func() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ + BEGIN + INSERT INTO dx VALUES (1000000, 1000000, 'x'); + RETURN NULL; + END; +$$; +CREATE TRIGGER transition_table_level2_bad_usage_trigger + AFTER DELETE ON transition_table_level2 + REFERENCING OLD TABLE AS dx + FOR EACH STATEMENT EXECUTE PROCEDURE + transition_table_level2_bad_usage_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: REFERENCING OLD TABLE AS dx + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +DELETE FROM transition_table_level2 + WHERE level2_no BETWEEN 301 AND 305; +DROP TRIGGER transition_table_level2_bad_usage_trigger + ON transition_table_level2; +ERROR: trigger "transition_table_level2_bad_usage_trigger" for table "transition_table_level2" does not exist +-- attempt modifications which would break RI (should all fail) +DELETE FROM transition_table_level1 + WHERE level1_no = 25; +UPDATE transition_table_level1 SET level1_no = -1 + WHERE level1_no = 30; +INSERT INTO transition_table_level2 (level2_no, parent_no) + VALUES (10000, 10000); +UPDATE transition_table_level2 SET parent_no = 2000 + WHERE level2_no = 40; +-- attempt modifications which would not break RI (should all succeed) +DELETE FROM transition_table_level1 + WHERE level1_no BETWEEN 201 AND 1000; +DELETE FROM transition_table_level1 + WHERE level1_no BETWEEN 100000000 AND 100000010; +SELECT count(*) FROM transition_table_level1; + count +------- + 199 +(1 row) + +DELETE FROM transition_table_level2 + WHERE level2_no BETWEEN 211 AND 220; +SELECT count(*) FROM transition_table_level2; + count +------- + 9985 +(1 row) + +CREATE TABLE alter_table_under_transition_tables +( + id int PRIMARY KEY, + name text +); +CREATE FUNCTION alter_table_under_transition_tables_upd_func() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ +BEGIN + RAISE WARNING 'old table = %, new table = %', + (SELECT string_agg(id || '=' || name, ',') FROM d), + (SELECT string_agg(id || '=' || name, ',') FROM i); + RAISE NOTICE 'one = %', (SELECT 1 FROM alter_table_under_transition_tables LIMIT 1); + RETURN NULL; +END; +$$; +-- should fail, TRUNCATE is not compatible with transition tables +CREATE TRIGGER alter_table_under_transition_tables_upd_trigger + AFTER TRUNCATE OR UPDATE ON alter_table_under_transition_tables + REFERENCING OLD TABLE AS d NEW TABLE AS i + FOR EACH STATEMENT EXECUTE PROCEDURE + alter_table_under_transition_tables_upd_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: REFERENCING OLD TABLE AS d NEW TABLE AS i + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +-- should work +CREATE TRIGGER alter_table_under_transition_tables_upd_trigger + AFTER UPDATE ON alter_table_under_transition_tables + REFERENCING OLD TABLE AS d NEW TABLE AS i + FOR EACH STATEMENT EXECUTE PROCEDURE + alter_table_under_transition_tables_upd_func(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 3: REFERENCING OLD TABLE AS d NEW TABLE AS i + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +INSERT INTO alter_table_under_transition_tables + VALUES (1, '1'), (2, '2'), (3, '3'); +UPDATE alter_table_under_transition_tables + SET name = name || name; +-- now change 'name' to an integer to see what happens... +ALTER TABLE alter_table_under_transition_tables + ALTER COLUMN name TYPE int USING name::integer; +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +UPDATE alter_table_under_transition_tables + SET name = (name::text || name::text)::integer; +-- now drop column 'name' +ALTER TABLE alter_table_under_transition_tables + DROP column name; +UPDATE alter_table_under_transition_tables + SET id = id; +-- +-- Test multiple reference to a transition table +-- +CREATE TABLE multi_test (i int); +INSERT INTO multi_test VALUES (1); +CREATE OR REPLACE FUNCTION multi_test_trig() RETURNS trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'count = %', (SELECT COUNT(*) FROM new_test); + RAISE NOTICE 'count union = %', + (SELECT COUNT(*) + FROM (SELECT * FROM new_test UNION ALL SELECT * FROM new_test) ss); + RETURN NULL; +END$$; +CREATE TRIGGER my_trigger AFTER UPDATE ON multi_test + REFERENCING NEW TABLE AS new_test OLD TABLE as old_test + FOR EACH STATEMENT EXECUTE PROCEDURE multi_test_trig(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 2: REFERENCING NEW TABLE AS new_test OLD TABLE as old_test + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +UPDATE multi_test SET i = i; +DROP TABLE multi_test; +DROP FUNCTION multi_test_trig(); +-- +-- Check type parsing and record fetching from partitioned tables +-- +CREATE TABLE partitioned_table (a int, b text) PARTITION BY LIST (a); +CREATE TABLE pt_part1 PARTITION OF partitioned_table FOR VALUES IN (1); +CREATE TABLE pt_part2 PARTITION OF partitioned_table FOR VALUES IN (2); +INSERT INTO partitioned_table VALUES (1, 'Row 1'); +INSERT INTO partitioned_table VALUES (2, 'Row 2'); +CREATE OR REPLACE FUNCTION get_from_partitioned_table(partitioned_table.a%type) +RETURNS partitioned_table AS $$ +DECLARE + a_val partitioned_table.a%TYPE; + result partitioned_table%ROWTYPE; +BEGIN + a_val := $1; + SELECT * INTO result FROM partitioned_table WHERE a = a_val; + RETURN result; +END; $$ LANGUAGE plpgsql; +NOTICE: type reference partitioned_table.a%TYPE converted to integer +SELECT * FROM get_from_partitioned_table(1) AS t; + a | b +---+------- + 1 | Row 1 +(1 row) + +CREATE OR REPLACE FUNCTION list_partitioned_table() +RETURNS SETOF partitioned_table.a%TYPE AS $$ +DECLARE + row partitioned_table%ROWTYPE; + a_val partitioned_table.a%TYPE; +BEGIN + FOR row IN SELECT * FROM partitioned_table ORDER BY a LOOP + a_val := row.a; + RETURN NEXT a_val; + END LOOP; + RETURN; +END; $$ LANGUAGE plpgsql; +NOTICE: type reference partitioned_table.a%TYPE converted to integer +SELECT * FROM list_partitioned_table() AS t; + t +--- + 1 + 2 +(2 rows) + +-- +-- Check argument name is used instead of $n in error message +-- +CREATE FUNCTION fx(x WSlot) RETURNS void AS $$ +BEGIN + GET DIAGNOSTICS x = ROW_COUNT; + RETURN; +END; $$ LANGUAGE plpgsql; +ERROR: "x" is not a scalar variable +LINE 3: GET DIAGNOSTICS x = ROW_COUNT; + ^ +/* + * TODO(jason): the following section belongs in src/pl/plpgsql/src, but YB + * does not yet support running tests there, so that's why it is here. + */ +-- inner COMMIT with output arguments +CREATE PROCEDURE test_proc7c(x int, INOUT a int, INOUT b numeric) +LANGUAGE plpgsql +AS $$ +BEGIN + a := x / 10; + b := x / 2; + COMMIT; +END; +$$; +CREATE PROCEDURE test_proc7cc(_x int) +LANGUAGE plpgsql +AS $$ +DECLARE _a int; _b numeric; +BEGIN + CALL test_proc7c(_x, _a, _b); + RAISE NOTICE '_x: %,_a: %, _b: %', _x, _a, _b; +END +$$; +CALL test_proc7cc(10); +NOTICE: _x: 10,_a: 1, _b: 5 diff --git a/src/test/regress/expected/yb.port.polygon.out b/src/test/regress/expected/yb.port.polygon.out new file mode 100644 index 0000000000000..1e9ef20e20808 --- /dev/null +++ b/src/test/regress/expected/yb.port.polygon.out @@ -0,0 +1,299 @@ +-- +-- POLYGON +-- +-- polygon logic +-- +CREATE TABLE POLYGON_TBL(f1 polygon); +INSERT INTO POLYGON_TBL(f1) VALUES ('(2.0,0.0),(2.0,4.0),(0.0,0.0)'); +INSERT INTO POLYGON_TBL(f1) VALUES ('(3.0,1.0),(3.0,3.0),(1.0,0.0)'); +INSERT INTO POLYGON_TBL(f1) VALUES ('(1,2),(3,4),(5,6),(7,8)'); +INSERT INTO POLYGON_TBL(f1) VALUES ('(7,8),(5,6),(3,4),(1,2)'); -- Reverse +INSERT INTO POLYGON_TBL(f1) VALUES ('(1,2),(7,8),(5,6),(3,-4)'); +-- degenerate polygons +INSERT INTO POLYGON_TBL(f1) VALUES ('(0.0,0.0)'); +INSERT INTO POLYGON_TBL(f1) VALUES ('(0.0,1.0),(0.0,1.0)'); +-- bad polygon input strings +INSERT INTO POLYGON_TBL(f1) VALUES ('0.0'); +ERROR: invalid input syntax for type polygon: "0.0" +LINE 1: INSERT INTO POLYGON_TBL(f1) VALUES ('0.0'); + ^ +INSERT INTO POLYGON_TBL(f1) VALUES ('(0.0 0.0'); +ERROR: invalid input syntax for type polygon: "(0.0 0.0" +LINE 1: INSERT INTO POLYGON_TBL(f1) VALUES ('(0.0 0.0'); + ^ +INSERT INTO POLYGON_TBL(f1) VALUES ('(0,1,2)'); +ERROR: invalid input syntax for type polygon: "(0,1,2)" +LINE 1: INSERT INTO POLYGON_TBL(f1) VALUES ('(0,1,2)'); + ^ +INSERT INTO POLYGON_TBL(f1) VALUES ('(0,1,2,3'); +ERROR: invalid input syntax for type polygon: "(0,1,2,3" +LINE 1: INSERT INTO POLYGON_TBL(f1) VALUES ('(0,1,2,3'); + ^ +INSERT INTO POLYGON_TBL(f1) VALUES ('asdf'); +ERROR: invalid input syntax for type polygon: "asdf" +LINE 1: INSERT INTO POLYGON_TBL(f1) VALUES ('asdf'); + ^ +SELECT * FROM POLYGON_TBL; + f1 +---------------------------- + ((2,0),(2,4),(0,0)) + ((3,1),(3,3),(1,0)) + ((1,2),(3,4),(5,6),(7,8)) + ((7,8),(5,6),(3,4),(1,2)) + ((1,2),(7,8),(5,6),(3,-4)) + ((0,0)) + ((0,1),(0,1)) +(7 rows) + +-- +-- Test the SP-GiST index +-- +CREATE TABLE quad_poly_tbl (id int, p polygon); +INSERT INTO quad_poly_tbl + SELECT (x - 1) * 100 + y, polygon(circle(point(x * 10, y * 10), 1 + (x + y) % 10)) + FROM generate_series(1, 100) x, + generate_series(1, 100) y; +INSERT INTO quad_poly_tbl + SELECT i, polygon '((200, 300),(210, 310),(230, 290))' + FROM generate_series(10001, 11000) AS i; +INSERT INTO quad_poly_tbl + VALUES + (11001, NULL), + (11002, NULL), + (11003, NULL); +CREATE INDEX quad_poly_tbl_idx ON quad_poly_tbl USING spgist(p); +ERROR: index method "spgist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +-- get reference results for ORDER BY distance from seq scan +SET enable_seqscan = ON; +SET enable_indexscan = OFF; +SET enable_bitmapscan = OFF; +CREATE TEMP TABLE quad_poly_tbl_ord_seq2 AS +SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id +FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; +-- check results from index scan +SET enable_seqscan = OFF; +SET enable_indexscan = OFF; +SET enable_bitmapscan = ON; +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p << polygon '((300,300),(400,600),(600,500),(700,200))'; + QUERY PLAN +------------------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p << '((300,300),(400,600),(600,500),(700,200))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p << polygon '((300,300),(400,600),(600,500),(700,200))'; + count +------- + 3890 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p &< polygon '((300,300),(400,600),(600,500),(700,200))'; + QUERY PLAN +------------------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p &< '((300,300),(400,600),(600,500),(700,200))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p &< polygon '((300,300),(400,600),(600,500),(700,200))'; + count +------- + 7900 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p && polygon '((300,300),(400,600),(600,500),(700,200))'; + QUERY PLAN +------------------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p && '((300,300),(400,600),(600,500),(700,200))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p && polygon '((300,300),(400,600),(600,500),(700,200))'; + count +------- + 977 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p &> polygon '((300,300),(400,600),(600,500),(700,200))'; + QUERY PLAN +------------------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p &> '((300,300),(400,600),(600,500),(700,200))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p &> polygon '((300,300),(400,600),(600,500),(700,200))'; + count +------- + 7000 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p >> polygon '((300,300),(400,600),(600,500),(700,200))'; + QUERY PLAN +------------------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p >> '((300,300),(400,600),(600,500),(700,200))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p >> polygon '((300,300),(400,600),(600,500),(700,200))'; + count +------- + 2990 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p <<| polygon '((300,300),(400,600),(600,500),(700,200))'; + QUERY PLAN +-------------------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p <<| '((300,300),(400,600),(600,500),(700,200))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p <<| polygon '((300,300),(400,600),(600,500),(700,200))'; + count +------- + 1890 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p &<| polygon '((300,300),(400,600),(600,500),(700,200))'; + QUERY PLAN +-------------------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p &<| '((300,300),(400,600),(600,500),(700,200))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p &<| polygon '((300,300),(400,600),(600,500),(700,200))'; + count +------- + 6900 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p |&> polygon '((300,300),(400,600),(600,500),(700,200))'; + QUERY PLAN +-------------------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p |&> '((300,300),(400,600),(600,500),(700,200))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p |&> polygon '((300,300),(400,600),(600,500),(700,200))'; + count +------- + 9000 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p |>> polygon '((300,300),(400,600),(600,500),(700,200))'; + QUERY PLAN +-------------------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p |>> '((300,300),(400,600),(600,500),(700,200))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p |>> polygon '((300,300),(400,600),(600,500),(700,200))'; + count +------- + 3990 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; + QUERY PLAN +------------------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p <@ '((300,300),(400,600),(600,500),(700,200))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; + count +------- + 831 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p @> polygon '((340,550),(343,552),(341,553))'; + QUERY PLAN +--------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p @> '((340,550),(343,552),(341,553))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p @> polygon '((340,550),(343,552),(341,553))'; + count +------- + 1 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(230, 290))'; + QUERY PLAN +--------------------------------------------------------------------------- + Finalize Aggregate + -> Seq Scan on quad_poly_tbl + Storage Filter: (p ~= '((200,300),(210,310),(230,290))'::polygon) + Partial Aggregate: true +(4 rows) + +SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(230, 290))'; + count +------- + 1000 +(1 row) + +-- test ORDER BY distance +SET enable_indexscan = ON; +SET enable_bitmapscan = OFF; +EXPLAIN (COSTS OFF) +SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id +FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; + QUERY PLAN +------------------------------------------------------------------------------------------- + WindowAgg + -> Sort + Sort Key: ((p <-> '(123,456)'::point)) + -> Seq Scan on quad_poly_tbl + Storage Filter: (p <@ '((300,300),(400,600),(600,500),(700,200))'::polygon) +(5 rows) + +CREATE TEMP TABLE quad_poly_tbl_ord_idx2 AS +SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id +FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; +SELECT * +FROM quad_poly_tbl_ord_seq2 seq FULL JOIN quad_poly_tbl_ord_idx2 idx + ON seq.n = idx.n AND seq.id = idx.id AND + (seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL) +WHERE seq.id IS NULL OR idx.id IS NULL; + n | dist | id | n | dist | id +---+------+----+---+------+---- +(0 rows) + +RESET enable_seqscan; +RESET enable_indexscan; +RESET enable_bitmapscan; diff --git a/src/test/regress/expected/yb.port.portals.out b/src/test/regress/expected/yb.port.portals.out new file mode 100644 index 0000000000000..183274df0e20a --- /dev/null +++ b/src/test/regress/expected/yb.port.portals.out @@ -0,0 +1,1252 @@ +-- +-- Cursor regression tests +-- +BEGIN; +DECLARE foo1 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +DECLARE foo2 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; +DECLARE foo3 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +DECLARE foo4 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; +DECLARE foo5 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +DECLARE foo6 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; +DECLARE foo7 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +DECLARE foo8 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; +DECLARE foo9 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +DECLARE foo10 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; +DECLARE foo11 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +DECLARE foo12 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; +DECLARE foo13 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +DECLARE foo14 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; +DECLARE foo15 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +DECLARE foo16 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; +DECLARE foo17 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +DECLARE foo18 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; +DECLARE foo19 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +DECLARE foo20 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; +DECLARE foo21 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +DECLARE foo22 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; +DECLARE foo23 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +FETCH 1 in foo1; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx +(1 row) + +FETCH NEXT in foo1; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx +(1 row) + +FETCH FORWARD in foo1; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx +(1 row) + +FETCH FORWARD 2 in foo1; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx +(2 rows) + +FETCH 2 in foo2; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx +(2 rows) + +FETCH 3 in foo2; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx +(3 rows) + +FETCH 3 in foo3; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx +(3 rows) + +FETCH 4 in foo4; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx +(4 rows) + +FETCH 5 in foo5; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx +(5 rows) + +FETCH 6 in foo6; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx +(6 rows) + +FETCH 7 in foo7; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx +(7 rows) + +FETCH 8 in foo8; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx +(8 rows) + +FETCH 9 in foo9; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx +(9 rows) + +FETCH 10 in foo10; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx +(10 rows) + +FETCH 11 in foo11; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx +(11 rows) + +FETCH 12 in foo12; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx +(12 rows) + +FETCH 13 in foo13; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx + 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx +(13 rows) + +FETCH 14 in foo14; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx + 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx + 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx +(14 rows) + +FETCH 15 in foo15; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx + 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx + 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx + 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx +(15 rows) + +FETCH 16 in foo16; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx + 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx + 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx + 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx + 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx +(16 rows) + +FETCH 17 in foo17; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx + 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx + 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx + 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx + 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx + 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx +(17 rows) + +FETCH 18 in foo18; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx + 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx + 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx + 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx + 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx + 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx + 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx +(18 rows) + +FETCH 19 in foo19; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx + 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx + 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx + 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx + 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx + 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx + 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx + 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx +(19 rows) + +FETCH 20 in foo20; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx + 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx + 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx + 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx + 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx + 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx + 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx + 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx + 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx +(20 rows) + +FETCH 21 in foo21; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx + 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx + 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx + 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx + 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx + 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx + 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx + 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx + 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx + 9460 | 20 | 0 | 0 | 0 | 0 | 60 | 460 | 1460 | 4460 | 9460 | 120 | 121 | WZAAAA | UAAAAA | AAAAxx +(21 rows) + +FETCH 22 in foo22; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx + 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx + 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx + 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx + 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx + 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx + 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx + 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx + 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx + 9460 | 20 | 0 | 0 | 0 | 0 | 60 | 460 | 1460 | 4460 | 9460 | 120 | 121 | WZAAAA | UAAAAA | AAAAxx + 59 | 21 | 1 | 3 | 9 | 19 | 59 | 59 | 59 | 59 | 59 | 118 | 119 | HCAAAA | VAAAAA | HHHHxx +(22 rows) + +FETCH 23 in foo23; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx + 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx + 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx + 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx + 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx + 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx + 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx + 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx + 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx + 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx + 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx + 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx + 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx + 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx + 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx + 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx + 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx + 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx + 9460 | 20 | 0 | 0 | 0 | 0 | 60 | 460 | 1460 | 4460 | 9460 | 120 | 121 | WZAAAA | UAAAAA | AAAAxx + 59 | 21 | 1 | 3 | 9 | 19 | 59 | 59 | 59 | 59 | 59 | 118 | 119 | HCAAAA | VAAAAA | HHHHxx + 8020 | 22 | 0 | 0 | 0 | 0 | 20 | 20 | 20 | 3020 | 8020 | 40 | 41 | MWAAAA | WAAAAA | OOOOxx +(23 rows) + +-- FETCH backward 1 in foo23; +-- FETCH backward 2 in foo22; +-- FETCH backward 3 in foo21; +-- FETCH backward 4 in foo20; +-- FETCH backward 5 in foo19; +-- FETCH backward 6 in foo18; +-- FETCH backward 7 in foo17; +-- FETCH backward 8 in foo16; +-- FETCH backward 9 in foo15; +-- FETCH backward 10 in foo14; +-- FETCH backward 11 in foo13; +-- FETCH backward 12 in foo12; +-- FETCH backward 13 in foo11; +-- FETCH backward 14 in foo10; +-- FETCH backward 15 in foo9; +-- FETCH backward 16 in foo8; +-- FETCH backward 17 in foo7; +-- FETCH backward 18 in foo6; +-- FETCH backward 19 in foo5; +-- FETCH backward 20 in foo4; +-- FETCH backward 21 in foo3; +-- FETCH backward 22 in foo2; +-- FETCH backward 23 in foo1; +CLOSE foo1; +CLOSE foo2; +CLOSE foo3; +CLOSE foo4; +CLOSE foo5; +CLOSE foo6; +CLOSE foo7; +CLOSE foo8; +CLOSE foo9; +CLOSE foo10; +CLOSE foo11; +CLOSE foo12; +-- leave some cursors open, to test that auto-close works. +-- record this in the system view as well (do not query the time field there +-- however) +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors ORDER BY 1; + name | statement | is_holdable | is_binary | is_scrollable +-------+-----------------------------------------------------------------------+-------------+-----------+--------------- + foo13 | DECLARE foo13 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t + foo14 | DECLARE foo14 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; | f | f | t + foo15 | DECLARE foo15 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t + foo16 | DECLARE foo16 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; | f | f | t + foo17 | DECLARE foo17 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t + foo18 | DECLARE foo18 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; | f | f | t + foo19 | DECLARE foo19 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t + foo20 | DECLARE foo20 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; | f | f | t + foo21 | DECLARE foo21 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t + foo22 | DECLARE foo22 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; | f | f | t + foo23 | DECLARE foo23 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t +(11 rows) + +END; +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; + name | statement | is_holdable | is_binary | is_scrollable +------+-----------+-------------+-----------+--------------- +(0 rows) + +-- +-- NO SCROLL disallows backward fetching +-- +BEGIN; +DECLARE foo24 NO SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; +FETCH 1 FROM foo24; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx +(1 row) + +-- FETCH BACKWARD 1 FROM foo24; -- should fail +END; +-- +-- Cursors outside transaction blocks +-- +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; + name | statement | is_holdable | is_binary | is_scrollable +------+-----------+-------------+-----------+--------------- +(0 rows) + +BEGIN; +DECLARE foo25 SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2 ORDER BY unique2; +FETCH FROM foo25; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx +(1 row) + +FETCH FROM foo25; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx +(1 row) + +COMMIT; +FETCH FROM foo25; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx +(1 row) + +-- FETCH BACKWARD FROM foo25; +-- FETCH ABSOLUTE -1 FROM foo25; +FETCH -3 FROM foo25; +ERROR: FETCH - not supported yet +LINE 1: FETCH -3 FROM foo25; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/6514. React with thumbs up to raise its priority +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; + name | statement | is_holdable | is_binary | is_scrollable +-------+---------------------------------------------------------------------------------+-------------+-----------+--------------- + foo25 | DECLARE foo25 SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2 ORDER BY unique2; | t | f | t +(1 row) + +CLOSE foo25; +-- +-- ROLLBACK should close holdable cursors +-- +BEGIN; +DECLARE foo26 CURSOR WITH HOLD FOR SELECT * FROM tenk1 ORDER BY unique2; +ROLLBACK; +-- should fail +FETCH FROM foo26; +ERROR: cursor "foo26" does not exist +-- +-- Parameterized DECLARE needs to insert param values into the cursor portal +-- +BEGIN; +CREATE FUNCTION declares_cursor(text) + RETURNS void + AS 'DECLARE c CURSOR FOR SELECT stringu1 FROM tenk1 WHERE stringu1 LIKE $1;' + LANGUAGE SQL; +SELECT declares_cursor('AB%'); + declares_cursor +----------------- + +(1 row) + +FETCH ALL FROM c; + stringu1 +---------- + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA +(15 rows) + +CLOSE c; +-- Run the same statements but with FORWARD option. +SELECT declares_cursor('AB%'); + declares_cursor +----------------- + +(1 row) + +FETCH FORWARD ALL FROM c; + stringu1 +---------- + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA + ABAAAA +(15 rows) + +ROLLBACK; +-- +-- Test behavior of both volatile and stable functions inside a cursor; +-- in particular we want to see what happens during commit of a holdable +-- cursor +-- +create temp table tt1(f1 int); +create function count_tt1_v() returns int8 as +'select count(*) from tt1' language sql volatile; +create function count_tt1_s() returns int8 as +'select count(*) from tt1' language sql stable; +begin; +insert into tt1 values(1); +declare c1 cursor for select count_tt1_v(), count_tt1_s(); +insert into tt1 values(2); +fetch all from c1; + count_tt1_v | count_tt1_s +-------------+------------- + 2 | 1 +(1 row) + +rollback; +begin; +insert into tt1 values(1); +declare c2 cursor with hold for select count_tt1_v(), count_tt1_s(); +insert into tt1 values(2); +commit; +delete from tt1; +fetch all from c2; + count_tt1_v | count_tt1_s +-------------+------------- + 2 | 1 +(1 row) + +drop function count_tt1_v(); +drop function count_tt1_s(); +-- Create a cursor with the BINARY option and check the pg_cursors view +BEGIN; +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; + name | statement | is_holdable | is_binary | is_scrollable +------+----------------------------------------------------------------------+-------------+-----------+--------------- + c2 | declare c2 cursor with hold for select count_tt1_v(), count_tt1_s(); | t | f | f +(1 row) + +DECLARE bc BINARY CURSOR FOR SELECT * FROM tenk1; +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors ORDER BY 1; + name | statement | is_holdable | is_binary | is_scrollable +------+----------------------------------------------------------------------+-------------+-----------+--------------- + bc | DECLARE bc BINARY CURSOR FOR SELECT * FROM tenk1; | f | t | f + c2 | declare c2 cursor with hold for select count_tt1_v(), count_tt1_s(); | t | f | f +(2 rows) + +ROLLBACK; +-- We should not see the portal that is created internally to +-- implement EXECUTE in pg_cursors +PREPARE cprep AS + SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; +EXECUTE cprep; + name | statement | is_holdable | is_binary | is_scrollable +------+----------------------------------------------------------------------+-------------+-----------+--------------- + c2 | declare c2 cursor with hold for select count_tt1_v(), count_tt1_s(); | t | f | f +(1 row) + +-- test CLOSE ALL; +SELECT name FROM pg_cursors ORDER BY 1; + name +------ + c2 +(1 row) + +CLOSE ALL; +SELECT name FROM pg_cursors ORDER BY 1; + name +------ +(0 rows) + +BEGIN; +DECLARE foo1 CURSOR WITH HOLD FOR SELECT 1; +DECLARE foo2 CURSOR WITHOUT HOLD FOR SELECT 1; +SELECT name FROM pg_cursors ORDER BY 1; + name +------ + foo1 + foo2 +(2 rows) + +CLOSE ALL; +SELECT name FROM pg_cursors ORDER BY 1; + name +------ +(0 rows) + +COMMIT; +-- +-- Tests for updatable cursors +-- +CREATE TEMP TABLE uctest(f1 int, f2 text); +INSERT INTO uctest VALUES (1, 'one'), (2, 'two'), (3, 'three'); +SELECT * FROM uctest ORDER BY f1; + f1 | f2 +----+------- + 1 | one + 2 | two + 3 | three +(3 rows) + +-- Check DELETE WHERE CURRENT +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM uctest ORDER BY f1; +FETCH 2 FROM c1; + f1 | f2 +----+----- + 1 | one + 2 | two +(2 rows) + +DELETE FROM uctest WHERE CURRENT OF c1; +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM uctest WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +-- should show deletion +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- cursor did not move +FETCH ALL FROM c1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- cursor is insensitive +MOVE BACKWARD ALL IN c1; +ERROR: FETCH BACKWARD not supported yet +LINE 1: MOVE BACKWARD ALL IN c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/6514. React with thumbs up to raise its priority +FETCH ALL FROM c1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +-- should still see deletion +SELECT * FROM uctest; + f1 | f2 +----+------- + 1 | one + 2 | two + 3 | three +(3 rows) + +-- Check UPDATE WHERE CURRENT; this time use FOR UPDATE +BEGIN; +-- DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; -- YB: Disable 'FOR UPDATE' due to github #27149 +DECLARE c1 CURSOR FOR SELECT * FROM uctest; +FETCH c1; + f1 | f2 +----+----- + 1 | one +(1 row) + +UPDATE uctest SET f1 = 8 WHERE CURRENT OF c1; +ERROR: WHERE CURRENT OF not supported yet +LINE 1: UPDATE uctest SET f1 = 8 WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +SELECT * FROM uctest; + f1 | f2 +----+------- + 1 | one + 2 | two + 3 | three +(3 rows) + +-- Check repeated-update and update-then-delete cases +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM uctest; +FETCH c1; + f1 | f2 +----+----- + 1 | one +(1 row) + +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; +ERROR: WHERE CURRENT OF not supported yet +LINE 1: UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; +ERROR: WHERE CURRENT OF not supported yet +LINE 1: UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- insensitive cursor should not show effects of updates or deletes +-- FETCH RELATIVE 0 FROM c1; +DELETE FROM uctest WHERE CURRENT OF c1; +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM uctest WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +DELETE FROM uctest WHERE CURRENT OF c1; -- no-op +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM uctest WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op +ERROR: WHERE CURRENT OF not supported yet +LINE 1: UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +-- FETCH RELATIVE 0 FROM c1; +ROLLBACK; +SELECT * FROM uctest; + f1 | f2 +----+------- + 1 | one + 2 | two + 3 | three +(3 rows) + +BEGIN; +-- DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; -- YB: Disable 'FOR UPDATE' due to github #27149 +DECLARE c1 CURSOR FOR SELECT * FROM uctest; +FETCH c1; + f1 | f2 +----+----- + 1 | one +(1 row) + +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; +ERROR: WHERE CURRENT OF not supported yet +LINE 1: UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; +ERROR: WHERE CURRENT OF not supported yet +LINE 1: UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +DELETE FROM uctest WHERE CURRENT OF c1; +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM uctest WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +DELETE FROM uctest WHERE CURRENT OF c1; -- no-op +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM uctest WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op +ERROR: WHERE CURRENT OF not supported yet +LINE 1: UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +--- sensitive cursors cannot currently scroll back, so this is an error: +-- FETCH RELATIVE 0 FROM c1; +ROLLBACK; +SELECT * FROM uctest; + f1 | f2 +----+------- + 1 | one + 2 | two + 3 | three +(3 rows) + +-- Check inheritance cases +CREATE TEMP TABLE ucchild () inherits (uctest); +INSERT INTO ucchild values(100, 'hundred'); +SELECT * FROM uctest; + f1 | f2 +-----+--------- + 1 | one + 2 | two + 3 | three + 100 | hundred +(4 rows) + +BEGIN; +-- DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; -- YB: Disable 'FOR UPDATE' due to #27149 +DECLARE c1 CURSOR FOR SELECT * FROM uctest; +FETCH 1 FROM c1; + f1 | f2 +----+----- + 1 | one +(1 row) + +-- UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- YB: CURRENT OF is not supported #737 +FETCH 1 FROM c1; + f1 | f2 +----+----- + 2 | two +(1 row) + +-- UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- YB: CURRENT OF is not supported #737 +FETCH 1 FROM c1; + f1 | f2 +----+------- + 3 | three +(1 row) + +-- UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- YB: CURRENT OF is not supported #737 +FETCH 1 FROM c1; + f1 | f2 +-----+--------- + 100 | hundred +(1 row) + +COMMIT; +SELECT * FROM uctest; + f1 | f2 +-----+--------- + 1 | one + 2 | two + 3 | three + 100 | hundred +(4 rows) + +-- Can update from a self-join, but only if FOR UPDATE says which to use +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5; +FETCH 1 FROM c1; + f1 | f2 | f1 | f2 +----+----+----+---- +(0 rows) + +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail +ERROR: WHERE CURRENT OF not supported yet +LINE 1: UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +ROLLBACK; +BEGIN; +-- DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5 FOR UPDATE; -- YB: Disable 'FOR UPDATE' due to github #27149 +DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5; +FETCH 1 FROM c1; + f1 | f2 | f1 | f2 +----+----+----+---- +(0 rows) + +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail +ERROR: WHERE CURRENT OF not supported yet +LINE 1: UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +ROLLBACK; +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5 FOR SHARE OF a; +FETCH 1 FROM c1; + f1 | f2 | f1 | f2 +----+----+----+---- +(0 rows) + +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; +ERROR: WHERE CURRENT OF not supported yet +LINE 1: UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT * FROM uctest; +ERROR: current transaction is aborted, commands ignored until end of transaction block +ROLLBACK; +-- Check various error cases +DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no such cursor +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM uctest WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +DECLARE cx CURSOR WITH HOLD FOR SELECT * FROM uctest; +DELETE FROM uctest WHERE CURRENT OF cx; -- fail, can't use held cursor +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM uctest WHERE CURRENT OF cx; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM tenk2; +DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM uctest WHERE CURRENT OF c; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +ROLLBACK; +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM tenk2 FOR SHARE; +DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM uctest WHERE CURRENT OF c; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +ROLLBACK; +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM tenk1 JOIN tenk2 USING (unique1); +DELETE FROM tenk1 WHERE CURRENT OF c; -- fail, cursor is on a join +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM tenk1 WHERE CURRENT OF c; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +ROLLBACK; +BEGIN; +DECLARE c CURSOR FOR SELECT f1,count(*) FROM uctest GROUP BY f1; +DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor is on aggregation +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM uctest WHERE CURRENT OF c; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +ROLLBACK; +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM uctest; +DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no current row +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM uctest WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +ROLLBACK; +BEGIN; +-- DECLARE c1 CURSOR FOR SELECT MIN(f1) FROM uctest FOR UPDATE; -- YB: Disable 'FOR UPDATE' due to github #27149 +DECLARE c1 CURSOR FOR SELECT MIN(f1) FROM uctest; +ROLLBACK; +-- WHERE CURRENT OF may someday work with views, but today is not that day. +-- For now, just make sure it errors out cleanly. +CREATE TEMP VIEW ucview AS SELECT * FROM uctest; +CREATE RULE ucrule AS ON DELETE TO ucview DO INSTEAD + DELETE FROM uctest WHERE f1 = OLD.f1; +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM ucview; +FETCH FROM c1; + f1 | f2 +----+----- + 1 | one +(1 row) + +DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM ucview WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +ROLLBACK; +-- Check WHERE CURRENT OF with an index-only scan +BEGIN; +EXPLAIN (costs off) +DECLARE c1 CURSOR FOR SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA'; + QUERY PLAN +--------------------------------------------- + Index Only Scan using onek_stringu1 on onek + Index Cond: (stringu1 = 'DZAAAA'::name) +(2 rows) + +DECLARE c1 CURSOR FOR SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA'; +FETCH FROM c1; + stringu1 +---------- + DZAAAA +(1 row) + +DELETE FROM onek WHERE CURRENT OF c1; +ERROR: WHERE CURRENT OF not supported yet +LINE 1: DELETE FROM onek WHERE CURRENT OF c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/737. React with thumbs up to raise its priority +SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA'; +ERROR: current transaction is aborted, commands ignored until end of transaction block +ROLLBACK; +-- Check behavior with rewinding to a previous child scan node, +-- as per bug #15395 +BEGIN; +CREATE TABLE current_check (currentid int, payload text); +CREATE TABLE current_check_1 () INHERITS (current_check); +CREATE TABLE current_check_2 () INHERITS (current_check); +INSERT INTO current_check_1 SELECT i, 'p' || i FROM generate_series(1,9) i; +INSERT INTO current_check_2 SELECT i, 'P' || i FROM generate_series(10,19) i; +DECLARE c1 SCROLL CURSOR FOR SELECT * FROM current_check; +/* YB: FETCH ABSOLUTE, DELETE .. WHERE CURRENT OF not supported #737 #6514 +-- This tests the fetch-backwards code path +FETCH ABSOLUTE 12 FROM c1; +FETCH ABSOLUTE 8 FROM c1; +DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *; + +-- This tests the ExecutorRewind code path +FETCH ABSOLUTE 13 FROM c1; +FETCH ABSOLUTE 1 FROM c1; +DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *; +*/ -- YB +SELECT * FROM current_check ORDER BY currentid; -- YB: make order match PG results + currentid | payload +-----------+--------- + 1 | p1 + 2 | p2 + 3 | p3 + 4 | p4 + 5 | p5 + 6 | p6 + 7 | p7 + 8 | p8 + 9 | p9 + 10 | P10 + 11 | P11 + 12 | P12 + 13 | P13 + 14 | P14 + 15 | P15 + 16 | P16 + 17 | P17 + 18 | P18 + 19 | P19 +(19 rows) + +ROLLBACK; +-- Make sure snapshot management works okay, per bug report in +-- 235395b90909301035v7228ce63q392931f15aa74b31@mail.gmail.com +-- TODO(#12494): reverse this change +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +CREATE TABLE cursor (a int); +INSERT INTO cursor VALUES (1); +-- DECLARE c1 NO SCROLL CURSOR FOR SELECT * FROM cursor FOR UPDATE; -- YB: github #27149 +-- UPDATE cursor SET a = 2; +FETCH ALL FROM c1; +ERROR: cursor "c1" does not exist +COMMIT; +/* YB: The below DROP TABLE is commented because the above FETCH ALL command fails +in YB, so the transaction aborts and the table 'cursor' is rolled back. +This isn't the case in PG. +DROP TABLE cursor; +*/ -- YB +-- Check rewinding a cursor containing a stable function in LIMIT, +-- per bug report in 8336843.9833.1399385291498.JavaMail.root@quick +begin; +create function nochange(int) returns int + as 'select $1 limit 1' language sql stable; +declare c cursor for select * from int8_tbl limit nochange(3); +ERROR: relation "int8_tbl" does not exist +LINE 1: declare c cursor for select * from int8_tbl limit nochange(3... + ^ +fetch all from c; +ERROR: current transaction is aborted, commands ignored until end of transaction block +move backward all in c; +ERROR: FETCH BACKWARD not supported yet +LINE 1: move backward all in c; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/6514. React with thumbs up to raise its priority +fetch all from c; +ERROR: current transaction is aborted, commands ignored until end of transaction block +rollback; +-- Check handling of non-backwards-scan-capable plans with scroll cursors +begin; +explain (costs off) declare c1 cursor for select (select 42) as x; + QUERY PLAN +--------------------------- + Result *RESULT*_1 + InitPlan 1 (returns $0) + -> Result *RESULT* +(3 rows) + +explain (costs off) declare c1 scroll cursor for select (select 42) as x; + QUERY PLAN +--------------------------- + Materialize + InitPlan 1 (returns $0) + -> Result *RESULT* + -> Result *RESULT*_1 +(4 rows) + +declare c1 scroll cursor for select (select 42) as x; +fetch all in c1; + x +---- + 42 +(1 row) + +fetch backward all in c1; +ERROR: FETCH BACKWARD not supported yet +LINE 1: fetch backward all in c1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/6514. React with thumbs up to raise its priority +rollback; +begin; +explain (costs off) declare c2 cursor for select generate_series(1,3) as g; + QUERY PLAN +----------------------- + ProjectSet + -> Result *RESULT* +(2 rows) + +explain (costs off) declare c2 scroll cursor for select generate_series(1,3) as g; + QUERY PLAN +----------------------------- + Materialize + -> ProjectSet + -> Result *RESULT* +(3 rows) + +declare c2 scroll cursor for select generate_series(1,3) as g; +fetch all in c2; + g +--- + 1 + 2 + 3 +(3 rows) + +fetch backward all in c2; +ERROR: FETCH BACKWARD not supported yet +LINE 1: fetch backward all in c2; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/6514. React with thumbs up to raise its priority +rollback; diff --git a/src/test/regress/expected/yb.port.prepare.out b/src/test/regress/expected/yb.port.prepare.out new file mode 100644 index 0000000000000..0ab13940dc653 --- /dev/null +++ b/src/test/regress/expected/yb.port.prepare.out @@ -0,0 +1,177 @@ +-- Regression tests for prepareable statements. We query the content +-- of the pg_prepared_statements view as prepared statements are +-- created and removed. +SELECT name, statement, parameter_types FROM pg_prepared_statements; + name | statement | parameter_types +------+-----------+----------------- +(0 rows) + +PREPARE q1 AS SELECT 1 AS a; +EXECUTE q1; + a +--- + 1 +(1 row) + +SELECT name, statement, parameter_types FROM pg_prepared_statements; + name | statement | parameter_types +------+------------------------------+----------------- + q1 | PREPARE q1 AS SELECT 1 AS a; | {} +(1 row) + +-- should fail +PREPARE q1 AS SELECT 2; +ERROR: prepared statement "q1" already exists +-- should succeed +DEALLOCATE q1; +PREPARE q1 AS SELECT 2; +EXECUTE q1; + ?column? +---------- + 2 +(1 row) + +PREPARE q2 AS SELECT 2 AS b; +SELECT name, statement, parameter_types FROM pg_prepared_statements; + name | statement | parameter_types +------+------------------------------+----------------- + q1 | PREPARE q1 AS SELECT 2; | {} + q2 | PREPARE q2 AS SELECT 2 AS b; | {} +(2 rows) + +-- sql92 syntax +DEALLOCATE PREPARE q1; +SELECT name, statement, parameter_types FROM pg_prepared_statements; + name | statement | parameter_types +------+------------------------------+----------------- + q2 | PREPARE q2 AS SELECT 2 AS b; | {} +(1 row) + +DEALLOCATE PREPARE q2; +-- the view should return the empty set again +SELECT name, statement, parameter_types FROM pg_prepared_statements; + name | statement | parameter_types +------+-----------+----------------- +(0 rows) + +-- parameterized queries +PREPARE q2(text) AS + SELECT datname, datistemplate, datallowconn + FROM pg_database WHERE datname = $1; +EXECUTE q2('postgres'); + datname | datistemplate | datallowconn +----------+---------------+-------------- + postgres | f | t +(1 row) + +PREPARE q3(text, int, float, boolean, smallint) AS + SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR + ten = $3::bigint OR true = $4 OR odd = $5::int) + ORDER BY unique1; +EXECUTE q3('AAAAxx', 5::smallint, 10.5::float, false, 4::bigint); + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 2 | 2716 | 0 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 4 | 5 | CAAAAA | MAEAAA | AAAAxx + 102 | 612 | 0 | 2 | 2 | 2 | 2 | 102 | 102 | 102 | 102 | 4 | 5 | YDAAAA | OXAAAA | AAAAxx + 802 | 2908 | 0 | 2 | 2 | 2 | 2 | 802 | 802 | 802 | 802 | 4 | 5 | WEAAAA | WHEAAA | AAAAxx + 902 | 1104 | 0 | 2 | 2 | 2 | 2 | 902 | 902 | 902 | 902 | 4 | 5 | SIAAAA | MQBAAA | AAAAxx + 1002 | 2580 | 0 | 2 | 2 | 2 | 2 | 2 | 1002 | 1002 | 1002 | 4 | 5 | OMAAAA | GVDAAA | AAAAxx + 1602 | 8148 | 0 | 2 | 2 | 2 | 2 | 602 | 1602 | 1602 | 1602 | 4 | 5 | QJAAAA | KBMAAA | AAAAxx + 1702 | 7940 | 0 | 2 | 2 | 2 | 2 | 702 | 1702 | 1702 | 1702 | 4 | 5 | MNAAAA | KTLAAA | AAAAxx + 2102 | 6184 | 0 | 2 | 2 | 2 | 2 | 102 | 102 | 2102 | 2102 | 4 | 5 | WCAAAA | WDJAAA | AAAAxx + 2202 | 8028 | 0 | 2 | 2 | 2 | 2 | 202 | 202 | 2202 | 2202 | 4 | 5 | SGAAAA | UWLAAA | AAAAxx + 2302 | 7112 | 0 | 2 | 2 | 2 | 2 | 302 | 302 | 2302 | 2302 | 4 | 5 | OKAAAA | ONKAAA | AAAAxx + 2902 | 6816 | 0 | 2 | 2 | 2 | 2 | 902 | 902 | 2902 | 2902 | 4 | 5 | QHAAAA | ECKAAA | AAAAxx + 3202 | 7128 | 0 | 2 | 2 | 2 | 2 | 202 | 1202 | 3202 | 3202 | 4 | 5 | ETAAAA | EOKAAA | AAAAxx + 3902 | 9224 | 0 | 2 | 2 | 2 | 2 | 902 | 1902 | 3902 | 3902 | 4 | 5 | CUAAAA | UQNAAA | AAAAxx + 4102 | 7676 | 0 | 2 | 2 | 2 | 2 | 102 | 102 | 4102 | 4102 | 4 | 5 | UBAAAA | GJLAAA | AAAAxx + 4202 | 6628 | 0 | 2 | 2 | 2 | 2 | 202 | 202 | 4202 | 4202 | 4 | 5 | QFAAAA | YUJAAA | AAAAxx + 4502 | 412 | 0 | 2 | 2 | 2 | 2 | 502 | 502 | 4502 | 4502 | 4 | 5 | ERAAAA | WPAAAA | AAAAxx + 4702 | 2520 | 0 | 2 | 2 | 2 | 2 | 702 | 702 | 4702 | 4702 | 4 | 5 | WYAAAA | YSDAAA | AAAAxx + 4902 | 1600 | 0 | 2 | 2 | 2 | 2 | 902 | 902 | 4902 | 4902 | 4 | 5 | OGAAAA | OJCAAA | AAAAxx + 5602 | 8796 | 0 | 2 | 2 | 2 | 2 | 602 | 1602 | 602 | 5602 | 4 | 5 | MHAAAA | IANAAA | AAAAxx + 6002 | 8932 | 0 | 2 | 2 | 2 | 2 | 2 | 2 | 1002 | 6002 | 4 | 5 | WWAAAA | OFNAAA | AAAAxx + 6402 | 3808 | 0 | 2 | 2 | 2 | 2 | 402 | 402 | 1402 | 6402 | 4 | 5 | GMAAAA | MQFAAA | AAAAxx + 7602 | 1040 | 0 | 2 | 2 | 2 | 2 | 602 | 1602 | 2602 | 7602 | 4 | 5 | KGAAAA | AOBAAA | AAAAxx + 7802 | 7508 | 0 | 2 | 2 | 2 | 2 | 802 | 1802 | 2802 | 7802 | 4 | 5 | COAAAA | UCLAAA | AAAAxx + 8002 | 9980 | 0 | 2 | 2 | 2 | 2 | 2 | 2 | 3002 | 8002 | 4 | 5 | UVAAAA | WTOAAA | AAAAxx + 8302 | 7800 | 0 | 2 | 2 | 2 | 2 | 302 | 302 | 3302 | 8302 | 4 | 5 | IHAAAA | AOLAAA | AAAAxx + 8402 | 5708 | 0 | 2 | 2 | 2 | 2 | 402 | 402 | 3402 | 8402 | 4 | 5 | ELAAAA | OLIAAA | AAAAxx + 8602 | 5440 | 0 | 2 | 2 | 2 | 2 | 602 | 602 | 3602 | 8602 | 4 | 5 | WSAAAA | GBIAAA | AAAAxx + 9502 | 1812 | 0 | 2 | 2 | 2 | 2 | 502 | 1502 | 4502 | 9502 | 4 | 5 | MBAAAA | SRCAAA | AAAAxx + 9602 | 9972 | 0 | 2 | 2 | 2 | 2 | 602 | 1602 | 4602 | 9602 | 4 | 5 | IFAAAA | OTOAAA | AAAAxx +(29 rows) + +-- too few params +EXECUTE q3('bool'); +ERROR: wrong number of parameters for prepared statement "q3" +DETAIL: Expected 5 parameters but got 1. +-- too many params +EXECUTE q3('bytea', 5::smallint, 10.5::float, false, 4::bigint, true); +ERROR: wrong number of parameters for prepared statement "q3" +DETAIL: Expected 5 parameters but got 6. +-- wrong param types +EXECUTE q3(5::smallint, 10.5::float, false, 4::bigint, 'bytea'); +ERROR: parameter $3 of type boolean cannot be coerced to the expected type double precision +LINE 1: EXECUTE q3(5::smallint, 10.5::float, false, 4::bigint, 'byte... + ^ +HINT: You will need to rewrite or cast the expression. +-- invalid type +PREPARE q4(nonexistenttype) AS SELECT $1; +ERROR: type "nonexistenttype" does not exist +LINE 1: PREPARE q4(nonexistenttype) AS SELECT $1; + ^ +-- create table as execute +PREPARE q5(int, text) AS + SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2 + ORDER BY unique1; +CREATE TEMPORARY TABLE q5_prep_results AS EXECUTE q5(200, 'DTAAAA'); +ERROR: CREATE TABLE AS EXECUTE not supported yet +LINE 1: CREATE TEMPORARY TABLE q5_prep_results AS EXECUTE q5(200, 'D... + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +/* YB: uncomment when above CREATE TABLE AS EXECUTE is supported +SELECT * FROM q5_prep_results; +CREATE TEMPORARY TABLE q5_prep_nodata AS EXECUTE q5(200, 'DTAAAA') + WITH NO DATA; +SELECT * FROM q5_prep_nodata; +*/ -- YB +SELECT 1; -- YB: add intermediate dummy query to avoid comments from polluting the below select from pg_prepared_statements + ?column? +---------- + 1 +(1 row) + +-- unknown or unspecified parameter types: should succeed +PREPARE q6 AS + SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2; +PREPARE q7(unknown) AS + SELECT * FROM road WHERE thepath = $1; +SELECT name, statement, parameter_types FROM pg_prepared_statements + ORDER BY name; + name | statement | parameter_types +------+------------------------------------------------------------------+---------------------------------------------------- + q2 | PREPARE q2(text) AS +| {text} + | SELECT datname, datistemplate, datallowconn +| + | FROM pg_database WHERE datname = $1; | + q3 | PREPARE q3(text, int, float, boolean, smallint) AS +| {text,integer,"double precision",boolean,smallint} + | SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR+| + | ten = $3::bigint OR true = $4 OR odd = $5::int) +| + | ORDER BY unique1; | + q5 | PREPARE q5(int, text) AS +| {integer,text} + | SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2 +| + | ORDER BY unique1; | + q6 | PREPARE q6 AS +| {integer,name} + | SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2; | + q7 | PREPARE q7(unknown) AS +| {path} + | SELECT * FROM road WHERE thepath = $1; | +(5 rows) + +-- test DEALLOCATE ALL; +DEALLOCATE ALL; +SELECT name, statement, parameter_types FROM pg_prepared_statements + ORDER BY name; + name | statement | parameter_types +------+-----------+----------------- +(0 rows) + diff --git a/src/test/regress/expected/yb.port.privileges.out b/src/test/regress/expected/yb.port.privileges.out new file mode 100644 index 0000000000000..cf8bfef78d758 --- /dev/null +++ b/src/test/regress/expected/yb.port.privileges.out @@ -0,0 +1,2558 @@ +-- +-- Test access privileges +-- +-- Clean up in case a prior regression run failed +-- Suppress NOTICE messages when users/groups don't exist +SET client_min_messages TO 'warning'; +DROP ROLE IF EXISTS regress_priv_group1; +DROP ROLE IF EXISTS regress_priv_group2; +DROP ROLE IF EXISTS regress_priv_user1; +DROP ROLE IF EXISTS regress_priv_user2; +DROP ROLE IF EXISTS regress_priv_user3; +DROP ROLE IF EXISTS regress_priv_user4; +DROP ROLE IF EXISTS regress_priv_user5; +DROP ROLE IF EXISTS regress_priv_user6; +DROP ROLE IF EXISTS regress_priv_user7; +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid; + lo_unlink +----------- +(0 rows) + +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject +RESET client_min_messages; +-- test proper begins here +CREATE USER regress_priv_user1; +CREATE USER regress_priv_user2; +CREATE USER regress_priv_user3; +CREATE USER regress_priv_user4; +CREATE USER regress_priv_user5; +CREATE USER regress_priv_user5; -- duplicate +ERROR: role "regress_priv_user5" already exists +CREATE USER regress_priv_user6; +CREATE USER regress_priv_user7; +CREATE USER regress_priv_user8; +CREATE USER regress_priv_user9; +CREATE USER regress_priv_user10; +CREATE ROLE regress_priv_role; +GRANT pg_read_all_data TO regress_priv_user6; +GRANT pg_write_all_data TO regress_priv_user7; +GRANT pg_read_all_settings TO regress_priv_user8 WITH ADMIN OPTION; +SET SESSION AUTHORIZATION regress_priv_user8; +GRANT pg_read_all_settings TO regress_priv_user9 WITH ADMIN OPTION; +SET SESSION AUTHORIZATION regress_priv_user9; +GRANT pg_read_all_settings TO regress_priv_user10; +SET SESSION AUTHORIZATION regress_priv_user8; +REVOKE pg_read_all_settings FROM regress_priv_user10; +REVOKE ADMIN OPTION FOR pg_read_all_settings FROM regress_priv_user9; +REVOKE pg_read_all_settings FROM regress_priv_user9; +RESET SESSION AUTHORIZATION; +REVOKE ADMIN OPTION FOR pg_read_all_settings FROM regress_priv_user8; +SET SESSION AUTHORIZATION regress_priv_user8; +SET ROLE pg_read_all_settings; +RESET ROLE; +RESET SESSION AUTHORIZATION; +REVOKE pg_read_all_settings FROM regress_priv_user8; +DROP USER regress_priv_user10; +DROP USER regress_priv_user9; +DROP USER regress_priv_user8; +CREATE GROUP regress_priv_group1; +CREATE GROUP regress_priv_group2 WITH USER regress_priv_user1, regress_priv_user2; +ALTER GROUP regress_priv_group1 ADD USER regress_priv_user4; +ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -- duplicate +NOTICE: role "regress_priv_user2" is already a member of role "regress_priv_group2" +ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2; +GRANT regress_priv_group2 TO regress_priv_user4 WITH ADMIN OPTION; +-- prepare non-leakproof function for later +CREATE FUNCTION leak(integer,integer) RETURNS boolean + AS 'int4lt' + LANGUAGE internal IMMUTABLE STRICT; -- but deliberately not LEAKPROOF +ALTER FUNCTION leak(integer,integer) OWNER TO regress_priv_user1; +-- test owner privileges +GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY CURRENT_ROLE; +REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY foo; -- error +REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY regress_priv_user2; -- error +REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY CURRENT_USER; +REVOKE regress_priv_role FROM regress_priv_user1 GRANTED BY CURRENT_ROLE; +DROP ROLE regress_priv_role; +SET SESSION AUTHORIZATION regress_priv_user1; +SELECT session_user, current_user; + session_user | current_user +--------------------+-------------------- + regress_priv_user1 | regress_priv_user1 +(1 row) + +CREATE TABLE atest1 ( a int, b text ); +SELECT * FROM atest1; + a | b +---+--- +(0 rows) + +INSERT INTO atest1 VALUES (1, 'one'); +DELETE FROM atest1; +UPDATE atest1 SET a = 1 WHERE b = 'blech'; +TRUNCATE atest1; +BEGIN; +LOCK atest1 IN ACCESS EXCLUSIVE MODE; +COMMIT; +REVOKE ALL ON atest1 FROM PUBLIC; +SELECT * FROM atest1; + a | b +---+--- +(0 rows) + +GRANT ALL ON atest1 TO regress_priv_user2; +GRANT SELECT ON atest1 TO regress_priv_user3, regress_priv_user4; +SELECT * FROM atest1; + a | b +---+--- +(0 rows) + +CREATE TABLE atest2 (col1 varchar(10), col2 boolean); +GRANT SELECT ON atest2 TO regress_priv_user2; +GRANT UPDATE ON atest2 TO regress_priv_user3; +GRANT INSERT ON atest2 TO regress_priv_user4 GRANTED BY CURRENT_USER; +GRANT TRUNCATE ON atest2 TO regress_priv_user5 GRANTED BY CURRENT_ROLE; +GRANT TRUNCATE ON atest2 TO regress_priv_user4 GRANTED BY regress_priv_user5; -- error +ERROR: grantor must be current user +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT session_user, current_user; + session_user | current_user +--------------------+-------------------- + regress_priv_user2 | regress_priv_user2 +(1 row) + +-- try various combinations of queries on atest1 and atest2 +SELECT * FROM atest1; -- ok + a | b +---+--- +(0 rows) + +SELECT * FROM atest2; -- ok + col1 | col2 +------+------ +(0 rows) + +INSERT INTO atest1 VALUES (2, 'two'); -- ok +INSERT INTO atest2 VALUES ('foo', true); -- fail +ERROR: permission denied for table atest2 +INSERT INTO atest1 SELECT 1, b FROM atest1; -- ok +UPDATE atest1 SET a = 1 WHERE a = 2; -- ok +UPDATE atest2 SET col2 = NOT col2; -- fail +ERROR: permission denied for table atest2 +SELECT * FROM atest1 FOR UPDATE; -- ok + a | b +---+----- + 1 | two + 1 | two +(2 rows) + +SELECT * FROM atest2 FOR UPDATE; -- fail +ERROR: permission denied for table atest2 +DELETE FROM atest2; -- fail +ERROR: permission denied for table atest2 +TRUNCATE atest2; -- fail +ERROR: permission denied for table atest2 +BEGIN; +LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- fail +ERROR: permission denied for table atest2 +COMMIT; +COPY atest2 FROM stdin; -- fail +ERROR: permission denied for table atest2 +GRANT ALL ON atest1 TO PUBLIC; -- fail +WARNING: no privileges were granted for "atest1" +-- checks in subquery, both ok +SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) ); + a | b +---+--- +(0 rows) + +SELECT * FROM atest2 WHERE ( col1 IN ( SELECT b FROM atest1 ) ); + col1 | col2 +------+------ +(0 rows) + +SET SESSION AUTHORIZATION regress_priv_user6; +SELECT * FROM atest1; -- ok + a | b +---+----- + 1 | two + 1 | two +(2 rows) + +SELECT * FROM atest2; -- ok + col1 | col2 +------+------ +(0 rows) + +INSERT INTO atest2 VALUES ('foo', true); -- fail +ERROR: permission denied for table atest2 +SET SESSION AUTHORIZATION regress_priv_user7; +SELECT * FROM atest1; -- fail +ERROR: permission denied for table atest1 +SELECT * FROM atest2; -- fail +ERROR: permission denied for table atest2 +INSERT INTO atest2 VALUES ('foo', true); -- ok +UPDATE atest2 SET col2 = true; -- ok +DELETE FROM atest2; -- ok +-- Make sure we are not able to modify system catalogs +UPDATE pg_catalog.pg_class SET relname = '123'; -- fail +ERROR: permission denied for table pg_class +DELETE FROM pg_catalog.pg_class; -- fail +ERROR: permission denied for table pg_class +UPDATE pg_toast.pg_toast_1213 SET chunk_id = 1; -- fail +ERROR: relation "pg_toast.pg_toast_1213" does not exist +LINE 1: UPDATE pg_toast.pg_toast_1213 SET chunk_id = 1; + ^ +SET SESSION AUTHORIZATION regress_priv_user3; +SELECT session_user, current_user; + session_user | current_user +--------------------+-------------------- + regress_priv_user3 | regress_priv_user3 +(1 row) + +SELECT * FROM atest1; -- ok + a | b +---+----- + 1 | two + 1 | two +(2 rows) + +SELECT * FROM atest2; -- fail +ERROR: permission denied for table atest2 +INSERT INTO atest1 VALUES (2, 'two'); -- fail +ERROR: permission denied for table atest1 +INSERT INTO atest2 VALUES ('foo', true); -- fail +ERROR: permission denied for table atest2 +INSERT INTO atest1 SELECT 1, b FROM atest1; -- fail +ERROR: permission denied for table atest1 +UPDATE atest1 SET a = 1 WHERE a = 2; -- fail +ERROR: permission denied for table atest1 +UPDATE atest2 SET col2 = NULL; -- ok +UPDATE atest2 SET col2 = NOT col2; -- fails; requires SELECT on atest2 +ERROR: permission denied for table atest2 +UPDATE atest2 SET col2 = true FROM atest1 WHERE atest1.a = 5; -- ok +SELECT * FROM atest1 FOR UPDATE; -- fail +ERROR: permission denied for table atest1 +SELECT * FROM atest2 FOR UPDATE; -- fail +ERROR: permission denied for table atest2 +DELETE FROM atest2; -- fail +ERROR: permission denied for table atest2 +TRUNCATE atest2; -- fail +ERROR: permission denied for table atest2 +BEGIN; +LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- ok +COMMIT; +COPY atest2 FROM stdin; -- fail +ERROR: permission denied for table atest2 +-- checks in subquery, both fail +SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) ); +ERROR: permission denied for table atest2 +SELECT * FROM atest2 WHERE ( col1 IN ( SELECT b FROM atest1 ) ); +ERROR: permission denied for table atest2 +SET SESSION AUTHORIZATION regress_priv_user4; +COPY atest2 FROM stdin; -- ok +SELECT * FROM atest1; -- ok + a | b +---+----- + 1 | two + 1 | two +(2 rows) + +-- test leaky-function protections in selfuncs +-- regress_priv_user1 will own a table and provide views for it. +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE TABLE atest12 as + SELECT x AS a, 10001 - x AS b FROM generate_series(1,10000) x; +CREATE INDEX ON atest12 (a); +CREATE INDEX ON atest12 (abs(a)); +-- results below depend on having quite accurate stats for atest12, so... +ALTER TABLE atest12 SET (autovacuum_enabled = off); +NOTICE: storage parameters are currently ignored in YugabyteDB +SET default_statistics_target = 10000; +VACUUM ANALYZE atest12; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +RESET default_statistics_target; +CREATE OPERATOR <<< (procedure = leak, leftarg = integer, rightarg = integer, + restrict = scalarltsel); +-- views with leaky operator +CREATE VIEW atest12v AS + SELECT * FROM atest12 WHERE b <<< 5; +CREATE VIEW atest12sbv WITH (security_barrier=true) AS + SELECT * FROM atest12 WHERE b <<< 5; +GRANT SELECT ON atest12v TO PUBLIC; +GRANT SELECT ON atest12sbv TO PUBLIC; +/* YB: TODO: investigate query plan differences between YB and postgres +-- This plan should use nestloop, knowing that few rows will be selected. +EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; + +-- And this one. +EXPLAIN (COSTS OFF) SELECT * FROM atest12 x, atest12 y + WHERE x.a = y.b and abs(y.a) <<< 5; + +-- This should also be a nestloop, but the security barrier forces the inner +-- scan to be materialized +EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y WHERE x.a = y.b; +*/ -- YB +-- Check if regress_priv_user2 can break security. +SET SESSION AUTHORIZATION regress_priv_user2; +CREATE FUNCTION leak2(integer,integer) RETURNS boolean + AS $$begin raise notice 'leak % %', $1, $2; return $1 > $2; end$$ + LANGUAGE plpgsql immutable; +CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer, + restrict = scalargtsel); +-- This should not show any "leak" notices before failing. +EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0; +ERROR: permission denied for table atest12 +/* YB: TODO: investigate query plan differences between YB and postgres +-- These plans should continue to use a nestloop, since they execute with the +-- privileges of the view owner. +EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; +EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y WHERE x.a = y.b; + +-- A non-security barrier view does not guard against information leakage. +EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y + WHERE x.a = y.b and abs(y.a) <<< 5; + +-- But a security barrier view isolates the leaky operator. +EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y + WHERE x.a = y.b and abs(y.a) <<< 5; +*/ -- YB +-- Now regress_priv_user1 grants sufficient access to regress_priv_user2. +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT (a, b) ON atest12 TO PUBLIC; +SET SESSION AUTHORIZATION regress_priv_user2; +/* YB: TODO: investigate query plan differences between YB and postgres +-- regress_priv_user2 should continue to get a good row estimate. +EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; + +-- But not for this, due to lack of table-wide permissions needed +-- to make use of the expression index's statistics. +EXPLAIN (COSTS OFF) SELECT * FROM atest12 x, atest12 y + WHERE x.a = y.b and abs(y.a) <<< 5; +*/ -- YB +-- clean up (regress_priv_user1's objects are all dropped later) +DROP FUNCTION leak2(integer, integer) CASCADE; +NOTICE: drop cascades to operator >>>(integer,integer) +-- groups +SET SESSION AUTHORIZATION regress_priv_user3; +CREATE TABLE atest3 (one int, two int, three int); +GRANT DELETE ON atest3 TO GROUP regress_priv_group2; +SET SESSION AUTHORIZATION regress_priv_user1; +SELECT * FROM atest3; -- fail +ERROR: permission denied for table atest3 +DELETE FROM atest3; -- ok +/* YB: avoid transaction because regress_priv_user1 privileges get inherited from regress_priv_group2 in further queries if not rolled back +BEGIN; +*/ -- YB +RESET SESSION AUTHORIZATION; +ALTER ROLE regress_priv_user1 NOINHERIT; +SET SESSION AUTHORIZATION regress_priv_user1; +DELETE FROM atest3; +ERROR: permission denied for table atest3 +/* YB: manually roll back changes due to lack of transaction +ROLLBACK; +*/ -- YB +RESET SESSION AUTHORIZATION; -- YB: manual rollback +ALTER ROLE regress_priv_user1 INHERIT; -- YB: manual rollback (see #1404) +-- views +SET SESSION AUTHORIZATION regress_priv_user3; +CREATE VIEW atestv1 AS SELECT * FROM atest1; -- ok +/* The next *should* fail, but it's not implemented that way yet. */ +CREATE VIEW atestv2 AS SELECT * FROM atest2; +CREATE VIEW atestv3 AS SELECT * FROM atest3; -- ok +/* Empty view is a corner case that failed in 9.2. */ +CREATE VIEW atestv0 AS SELECT 0 as x WHERE false; -- ok +SELECT * FROM atestv1; -- ok + a | b +---+----- + 1 | two + 1 | two +(2 rows) + +SELECT * FROM atestv2; -- fail +ERROR: permission denied for table atest2 +GRANT SELECT ON atestv1, atestv3 TO regress_priv_user4; +GRANT SELECT ON atestv2 TO regress_priv_user2; +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT * FROM atestv1; -- ok + a | b +---+----- + 1 | two + 1 | two +(2 rows) + +SELECT * FROM atestv2; -- fail +ERROR: permission denied for view atestv2 +SELECT * FROM atestv3; -- ok + one | two | three +-----+-----+------- +(0 rows) + +SELECT * FROM atestv0; -- fail +ERROR: permission denied for view atestv0 +-- Appendrels excluded by constraints failed to check permissions in 8.4-9.2. +select * from + ((select a.q1 as x from int8_tbl a offset 0) + union all + (select b.q2 as x from int8_tbl b offset 0)) ss +where false; +ERROR: permission denied for table int8_tbl +set constraint_exclusion = on; +select * from + ((select a.q1 as x, random() from int8_tbl a where q1 > 0) + union all + (select b.q2 as x, random() from int8_tbl b where q2 > 0)) ss +where x < 0; +ERROR: permission denied for table int8_tbl +reset constraint_exclusion; +CREATE VIEW atestv4 AS SELECT * FROM atestv3; -- nested view +SELECT * FROM atestv4; -- ok + one | two | three +-----+-----+------- +(0 rows) + +GRANT SELECT ON atestv4 TO regress_priv_user2; +SET SESSION AUTHORIZATION regress_priv_user2; +-- Two complex cases: +SELECT * FROM atestv3; -- fail +ERROR: permission denied for view atestv3 +SELECT * FROM atestv4; -- ok (even though regress_priv_user2 cannot access underlying atestv3) + one | two | three +-----+-----+------- +(0 rows) + +SELECT * FROM atest2; -- ok + col1 | col2 +------+------ + bar | t +(1 row) + +SELECT * FROM atestv2; -- fail (even though regress_priv_user2 can access underlying atest2) +ERROR: permission denied for table atest2 +-- Test column level permissions +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE TABLE atest5 (one int, two int unique, three int, four int unique); +CREATE TABLE atest6 (one int, two int, blue int); +GRANT SELECT (one), INSERT (two), UPDATE (three) ON atest5 TO regress_priv_user4; +GRANT ALL (one) ON atest5 TO regress_priv_user3; +INSERT INTO atest5 VALUES (1,2,3); +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT * FROM atest5; -- fail +ERROR: permission denied for table atest5 +SELECT one FROM atest5; -- ok + one +----- + 1 +(1 row) + +COPY atest5 (one) TO stdout; -- ok +1 +SELECT two FROM atest5; -- fail +ERROR: permission denied for table atest5 +COPY atest5 (two) TO stdout; -- fail +ERROR: permission denied for table atest5 +SELECT atest5 FROM atest5; -- fail +ERROR: permission denied for table atest5 +COPY atest5 (one,two) TO stdout; -- fail +ERROR: permission denied for table atest5 +SELECT 1 FROM atest5; -- ok + ?column? +---------- + 1 +(1 row) + +SELECT 1 FROM atest5 a JOIN atest5 b USING (one); -- ok + ?column? +---------- + 1 +(1 row) + +SELECT 1 FROM atest5 a JOIN atest5 b USING (two); -- fail +ERROR: permission denied for table atest5 +SELECT 1 FROM atest5 a NATURAL JOIN atest5 b; -- fail +ERROR: permission denied for table atest5 +SELECT * FROM (atest5 a JOIN atest5 b USING (one)) j; -- fail +ERROR: permission denied for table atest5 +SELECT j.* FROM (atest5 a JOIN atest5 b USING (one)) j; -- fail +ERROR: permission denied for table atest5 +SELECT (j.*) IS NULL FROM (atest5 a JOIN atest5 b USING (one)) j; -- fail +ERROR: permission denied for table atest5 +SELECT one FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- ok + one +----- + 1 +(1 row) + +SELECT j.one FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- ok + one +----- + 1 +(1 row) + +SELECT two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail +ERROR: permission denied for table atest5 +SELECT j.two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail +ERROR: permission denied for table atest5 +SELECT y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail +ERROR: permission denied for table atest5 +SELECT j.y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail +ERROR: permission denied for table atest5 +SELECT * FROM (atest5 a JOIN atest5 b USING (one)); -- fail +ERROR: permission denied for table atest5 +SELECT a.* FROM (atest5 a JOIN atest5 b USING (one)); -- fail +ERROR: permission denied for table atest5 +SELECT (a.*) IS NULL FROM (atest5 a JOIN atest5 b USING (one)); -- fail +ERROR: permission denied for table atest5 +SELECT two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail +ERROR: permission denied for table atest5 +SELECT a.two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail +ERROR: permission denied for table atest5 +SELECT y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail +ERROR: permission denied for table atest5 +SELECT b.y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail +ERROR: permission denied for table atest5 +SELECT y FROM (atest5 a LEFT JOIN atest5 b(one,x,y,z) USING (one)); -- fail +ERROR: permission denied for table atest5 +SELECT b.y FROM (atest5 a LEFT JOIN atest5 b(one,x,y,z) USING (one)); -- fail +ERROR: permission denied for table atest5 +SELECT y FROM (atest5 a FULL JOIN atest5 b(one,x,y,z) USING (one)); -- fail +ERROR: permission denied for table atest5 +SELECT b.y FROM (atest5 a FULL JOIN atest5 b(one,x,y,z) USING (one)); -- fail +ERROR: permission denied for table atest5 +SELECT 1 FROM atest5 WHERE two = 2; -- fail +ERROR: permission denied for table atest5 +SELECT * FROM atest1, atest5; -- fail +ERROR: permission denied for table atest5 +SELECT atest1.* FROM atest1, atest5; -- ok + a | b +---+----- + 1 | two + 1 | two +(2 rows) + +SELECT atest1.*,atest5.one FROM atest1, atest5; -- ok + a | b | one +---+-----+----- + 1 | two | 1 + 1 | two | 1 +(2 rows) + +SELECT atest1.*,atest5.one FROM atest1 JOIN atest5 ON (atest1.a = atest5.two); -- fail +ERROR: permission denied for table atest5 +SELECT atest1.*,atest5.one FROM atest1 JOIN atest5 ON (atest1.a = atest5.one); -- ok + a | b | one +---+-----+----- + 1 | two | 1 + 1 | two | 1 +(2 rows) + +SELECT one, two FROM atest5; -- fail +ERROR: permission denied for table atest5 +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT (one,two) ON atest6 TO regress_priv_user4; +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT one, two FROM atest5 NATURAL JOIN atest6; -- fail still +ERROR: permission denied for table atest5 +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT (two) ON atest5 TO regress_priv_user4; +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT one, two FROM atest5 NATURAL JOIN atest6; -- ok now + one | two +-----+----- +(0 rows) + +-- test column-level privileges for INSERT and UPDATE +INSERT INTO atest5 (two) VALUES (3); -- ok +COPY atest5 FROM stdin; -- fail +ERROR: permission denied for table atest5 +COPY atest5 (two) FROM stdin; -- ok +INSERT INTO atest5 (three) VALUES (4); -- fail +ERROR: permission denied for table atest5 +INSERT INTO atest5 VALUES (5,5,5); -- fail +ERROR: permission denied for table atest5 +UPDATE atest5 SET three = 10; -- ok +UPDATE atest5 SET one = 8; -- fail +ERROR: permission denied for table atest5 +UPDATE atest5 SET three = 5, one = 2; -- fail +ERROR: permission denied for table atest5 +-- Check that column level privs are enforced in RETURNING +-- Ok. +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10; +-- Error. No SELECT on column three. +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10 RETURNING atest5.three; +ERROR: permission denied for table atest5 +-- Ok. May SELECT on column "one": +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10 RETURNING atest5.one; + one +----- + +(1 row) + +-- Check that column level privileges are enforced for EXCLUDED +-- Ok. we may select one +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = EXCLUDED.one; +-- Error. No select rights on three +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = EXCLUDED.three; +ERROR: permission denied for table atest5 +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set one = 8; -- fails (due to UPDATE) +ERROR: permission denied for table atest5 +INSERT INTO atest5(three) VALUES (4) ON CONFLICT (two) DO UPDATE set three = 10; -- fails (due to INSERT) +ERROR: permission denied for table atest5 +-- Check that the columns in the inference require select privileges +INSERT INTO atest5(four) VALUES (4); -- fail +ERROR: permission denied for table atest5 +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT INSERT (four) ON atest5 TO regress_priv_user4; +SET SESSION AUTHORIZATION regress_priv_user4; +INSERT INTO atest5(four) VALUES (4) ON CONFLICT (four) DO UPDATE set three = 3; -- fails (due to SELECT) +ERROR: permission denied for table atest5 +INSERT INTO atest5(four) VALUES (4) ON CONFLICT ON CONSTRAINT atest5_four_key DO UPDATE set three = 3; -- fails (due to SELECT) +ERROR: permission denied for table atest5 +INSERT INTO atest5(four) VALUES (4); -- ok +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT (four) ON atest5 TO regress_priv_user4; +SET SESSION AUTHORIZATION regress_priv_user4; +INSERT INTO atest5(four) VALUES (4) ON CONFLICT (four) DO UPDATE set three = 3; -- ok +INSERT INTO atest5(four) VALUES (4) ON CONFLICT ON CONSTRAINT atest5_four_key DO UPDATE set three = 3; -- ok +SET SESSION AUTHORIZATION regress_priv_user1; +REVOKE ALL (one) ON atest5 FROM regress_priv_user4; +GRANT SELECT (one,two,blue) ON atest6 TO regress_priv_user4; +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT one FROM atest5; -- fail +ERROR: permission denied for table atest5 +UPDATE atest5 SET one = 1; -- fail +ERROR: permission denied for table atest5 +SELECT atest6 FROM atest6; -- ok + atest6 +-------- +(0 rows) + +COPY atest6 TO stdout; -- ok +-- test column privileges with MERGE +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE TABLE mtarget (a int, b text); +CREATE TABLE msource (a int, b text); +INSERT INTO mtarget VALUES (1, 'init1'), (2, 'init2'); +INSERT INTO msource VALUES (1, 'source1'), (2, 'source2'), (3, 'source3'); +GRANT SELECT (a) ON msource TO regress_priv_user4; +GRANT SELECT (a) ON mtarget TO regress_priv_user4; +GRANT INSERT (a,b) ON mtarget TO regress_priv_user4; +GRANT UPDATE (b) ON mtarget TO regress_priv_user4; +SET SESSION AUTHORIZATION regress_priv_user4; +-- +-- test source privileges +-- +-- fail (no SELECT priv on s.b) +MERGE INTO mtarget t USING msource s ON t.a = s.a +WHEN MATCHED THEN + UPDATE SET b = s.b +WHEN NOT MATCHED THEN + INSERT VALUES (a, NULL); +ERROR: This statement not supported yet +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +-- YB: port further queries when above works +-- check error reporting with column privs +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2)); +GRANT SELECT (c1) ON t1 TO regress_priv_user2; +GRANT INSERT (c1, c2, c3) ON t1 TO regress_priv_user2; +GRANT UPDATE (c1, c2, c3) ON t1 TO regress_priv_user2; +-- seed data +INSERT INTO t1 VALUES (1, 1, 1); +INSERT INTO t1 VALUES (1, 2, 1); +INSERT INTO t1 VALUES (2, 1, 2); +INSERT INTO t1 VALUES (2, 2, 2); +INSERT INTO t1 VALUES (3, 1, 3); +SET SESSION AUTHORIZATION regress_priv_user2; +INSERT INTO t1 (c1, c2) VALUES (1, 1); -- fail, but row not shown +ERROR: duplicate key value violates unique constraint "t1_pkey" +UPDATE t1 SET c2 = 1; -- fail, but row not shown +ERROR: duplicate key value violates unique constraint "t1_pkey" +INSERT INTO t1 (c1, c2) VALUES (null, null); -- fail, but see columns being inserted +ERROR: null value in column "c1" of relation "t1" violates not-null constraint +DETAIL: Failing row contains (c1, c2) = (null, null). +INSERT INTO t1 (c3) VALUES (null); -- fail, but see columns being inserted or have SELECT +ERROR: null value in column "c1" of relation "t1" violates not-null constraint +DETAIL: Failing row contains (c1, c3) = (null, null). +INSERT INTO t1 (c1) VALUES (5); -- fail, but see columns being inserted or have SELECT +ERROR: null value in column "c2" of relation "t1" violates not-null constraint +DETAIL: Failing row contains (c1) = (5). +UPDATE t1 SET c3 = 10; -- fail, but see columns with SELECT rights, or being modified +ERROR: new row for relation "t1" violates check constraint "t1_c3_check" +DETAIL: Failing row contains (c1, c3) = (1, 10). +SET SESSION AUTHORIZATION regress_priv_user1; +DROP TABLE t1; +-- check error reporting with column privs on a partitioned table +CREATE TABLE errtst(a text, b text NOT NULL, c text, secret1 text, secret2 text) PARTITION BY LIST (a); +CREATE TABLE errtst_part_1(secret2 text, c text, a text, b text NOT NULL, secret1 text); +CREATE TABLE errtst_part_2(secret1 text, secret2 text, a text, c text, b text NOT NULL); +ALTER TABLE errtst ATTACH PARTITION errtst_part_1 FOR VALUES IN ('aaa'); +ALTER TABLE errtst ATTACH PARTITION errtst_part_2 FOR VALUES IN ('aaaa'); +GRANT SELECT (a, b, c) ON TABLE errtst TO regress_priv_user2; +GRANT UPDATE (a, b, c) ON TABLE errtst TO regress_priv_user2; +GRANT INSERT (a, b, c) ON TABLE errtst TO regress_priv_user2; +INSERT INTO errtst_part_1 (a, b, c, secret1, secret2) +VALUES ('aaa', 'bbb', 'ccc', 'the body', 'is in the attic'); +SET SESSION AUTHORIZATION regress_priv_user2; +-- Perform a few updates that violate the NOT NULL constraint. Make sure +-- the error messages don't leak the secret fields. +-- simple insert. +INSERT INTO errtst (a, b) VALUES ('aaa', NULL); +ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint +DETAIL: Failing row contains (a, b, c) = (aaa, null, null). +-- simple update. +UPDATE errtst SET b = NULL; +ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint +DETAIL: Failing row contains (a, b, c) = (aaa, null, ccc). +-- partitioning key is updated, doesn't move the row. +UPDATE errtst SET a = 'aaa', b = NULL; +ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint +DETAIL: Failing row contains (a, b, c) = (aaa, null, ccc). +-- row is moved to another partition. +UPDATE errtst SET a = 'aaaa', b = NULL; +ERROR: null value in column "b" of relation "errtst_part_2" violates not-null constraint +DETAIL: Failing row contains (a, b, c) = (aaaa, null, ccc). +-- row is moved to another partition. This differs from the previous case in +-- that the new partition is excluded by constraint exclusion, so its +-- ResultRelInfo is not created at ExecInitModifyTable, but needs to be +-- constructed on the fly when the updated tuple is routed to it. +UPDATE errtst SET a = 'aaaa', b = NULL WHERE a = 'aaa'; +ERROR: null value in column "b" of relation "errtst_part_2" violates not-null constraint +DETAIL: Failing row contains (a, b, c) = (aaaa, null, ccc). +SET SESSION AUTHORIZATION regress_priv_user1; +DROP TABLE errtst; +-- test column-level privileges when involved with DELETE +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER TABLE atest6 ADD COLUMN three integer; +GRANT DELETE ON atest5 TO regress_priv_user3; +GRANT SELECT (two) ON atest5 TO regress_priv_user3; +REVOKE ALL (one) ON atest5 FROM regress_priv_user3; +GRANT SELECT (one) ON atest5 TO regress_priv_user4; +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT atest6 FROM atest6; -- fail +ERROR: permission denied for table atest6 +SELECT one FROM atest5 NATURAL JOIN atest6; -- fail +ERROR: permission denied for table atest5 +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER TABLE atest6 DROP COLUMN three; +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT atest6 FROM atest6; -- ok + atest6 +-------- +(0 rows) + +SELECT one FROM atest5 NATURAL JOIN atest6; -- ok + one +----- +(0 rows) + +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER TABLE atest6 DROP COLUMN two; +REVOKE SELECT (one,blue) ON atest6 FROM regress_priv_user4; +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT * FROM atest6; -- fail +ERROR: permission denied for table atest6 +SELECT 1 FROM atest6; -- fail +ERROR: permission denied for table atest6 +SET SESSION AUTHORIZATION regress_priv_user3; +DELETE FROM atest5 WHERE one = 1; -- fail +ERROR: permission denied for table atest5 +DELETE FROM atest5 WHERE two = 2; -- ok +-- check inheritance cases +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE TABLE atestp1 (f1 int, f2 int); +CREATE TABLE atestp2 (fx int, fy int); +CREATE TABLE atestc (fz int) INHERITS (atestp1, atestp2); +GRANT SELECT(fx,fy,tableoid) ON atestp2 TO regress_priv_user2; +GRANT SELECT(fx) ON atestc TO regress_priv_user2; +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT fx FROM atestp2; -- ok + fx +---- +(0 rows) + +SELECT fy FROM atestp2; -- ok + fy +---- +(0 rows) + +SELECT atestp2 FROM atestp2; -- ok + atestp2 +--------- +(0 rows) + +SELECT tableoid FROM atestp2; -- ok + tableoid +---------- +(0 rows) + +SELECT fy FROM atestc; -- fail +ERROR: permission denied for table atestc +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT(fy,tableoid) ON atestc TO regress_priv_user2; +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT fx FROM atestp2; -- still ok + fx +---- +(0 rows) + +SELECT fy FROM atestp2; -- ok + fy +---- +(0 rows) + +SELECT atestp2 FROM atestp2; -- ok + atestp2 +--------- +(0 rows) + +SELECT tableoid FROM atestp2; -- ok + tableoid +---------- +(0 rows) + +-- child's permissions do not apply when operating on parent +SET SESSION AUTHORIZATION regress_priv_user1; +REVOKE ALL ON atestc FROM regress_priv_user2; +GRANT ALL ON atestp1 TO regress_priv_user2; +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT f2 FROM atestp1; -- ok + f2 +---- +(0 rows) + +SELECT f2 FROM atestc; -- fail +ERROR: permission denied for table atestc +DELETE FROM atestp1; -- ok +DELETE FROM atestc; -- fail +ERROR: permission denied for table atestc +UPDATE atestp1 SET f1 = 1; -- ok +UPDATE atestc SET f1 = 1; -- fail +ERROR: permission denied for table atestc +TRUNCATE atestp1; -- ok +TRUNCATE atestc; -- fail +ERROR: permission denied for table atestc +BEGIN; +LOCK atestp1; +END; +BEGIN; +LOCK atestc; +ERROR: permission denied for table atestc +END; +-- privileges on functions, languages +-- switch to superuser +\c - +REVOKE ALL PRIVILEGES ON LANGUAGE sql FROM PUBLIC; +GRANT USAGE ON LANGUAGE sql TO regress_priv_user1; -- ok +GRANT USAGE ON LANGUAGE c TO PUBLIC; -- fail +ERROR: language "c" is not trusted +DETAIL: GRANT and REVOKE are not allowed on untrusted languages, because only superusers can use untrusted languages. +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT USAGE ON LANGUAGE sql TO regress_priv_user2; -- fail +WARNING: no privileges were granted for "sql" +CREATE FUNCTION priv_testfunc1(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql; +CREATE FUNCTION priv_testfunc2(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql; +CREATE AGGREGATE priv_testagg1(int) (sfunc = int4pl, stype = int4); +CREATE PROCEDURE priv_testproc1(int) AS 'select $1;' LANGUAGE sql; +REVOKE ALL ON FUNCTION priv_testfunc1(int), priv_testfunc2(int), priv_testagg1(int) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION priv_testfunc1(int), priv_testfunc2(int), priv_testagg1(int) TO regress_priv_user2; +REVOKE ALL ON FUNCTION priv_testproc1(int) FROM PUBLIC; -- fail, not a function +ERROR: priv_testproc1(integer) is not a function +REVOKE ALL ON PROCEDURE priv_testproc1(int) FROM PUBLIC; +GRANT EXECUTE ON PROCEDURE priv_testproc1(int) TO regress_priv_user2; +GRANT USAGE ON FUNCTION priv_testfunc1(int) TO regress_priv_user3; -- semantic error +ERROR: invalid privilege type USAGE for function +GRANT USAGE ON FUNCTION priv_testagg1(int) TO regress_priv_user3; -- semantic error +ERROR: invalid privilege type USAGE for function +GRANT USAGE ON PROCEDURE priv_testproc1(int) TO regress_priv_user3; -- semantic error +ERROR: invalid privilege type USAGE for procedure +GRANT ALL PRIVILEGES ON FUNCTION priv_testfunc1(int) TO regress_priv_user4; +GRANT ALL PRIVILEGES ON FUNCTION priv_testfunc_nosuch(int) TO regress_priv_user4; +ERROR: function priv_testfunc_nosuch(integer) does not exist +GRANT ALL PRIVILEGES ON FUNCTION priv_testagg1(int) TO regress_priv_user4; +GRANT ALL PRIVILEGES ON PROCEDURE priv_testproc1(int) TO regress_priv_user4; +CREATE FUNCTION priv_testfunc4(boolean) RETURNS text + AS 'select col1 from atest2 where col2 = $1;' + LANGUAGE sql SECURITY DEFINER; +GRANT EXECUTE ON FUNCTION priv_testfunc4(boolean) TO regress_priv_user3; +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT priv_testfunc1(5), priv_testfunc2(5); -- ok + priv_testfunc1 | priv_testfunc2 +----------------+---------------- + 10 | 15 +(1 row) + +CREATE FUNCTION priv_testfunc3(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql; -- fail +ERROR: permission denied for language sql +SELECT priv_testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- ok + priv_testagg1 +--------------- + 6 +(1 row) + +CALL priv_testproc1(6); -- ok +SET SESSION AUTHORIZATION regress_priv_user3; +SELECT priv_testfunc1(5); -- fail +ERROR: permission denied for function priv_testfunc1 +SELECT priv_testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- fail +ERROR: permission denied for aggregate priv_testagg1 +CALL priv_testproc1(6); -- fail +ERROR: permission denied for procedure priv_testproc1 +SELECT col1 FROM atest2 WHERE col2 = true; -- fail +ERROR: permission denied for table atest2 +SELECT priv_testfunc4(true); -- ok + priv_testfunc4 +---------------- + bar +(1 row) + +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT priv_testfunc1(5); -- ok + priv_testfunc1 +---------------- + 10 +(1 row) + +SELECT priv_testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- ok + priv_testagg1 +--------------- + 6 +(1 row) + +CALL priv_testproc1(6); -- ok +DROP FUNCTION priv_testfunc1(int); -- fail +ERROR: must be owner of function priv_testfunc1 +DROP AGGREGATE priv_testagg1(int); -- fail +ERROR: must be owner of aggregate priv_testagg1 +DROP PROCEDURE priv_testproc1(int); -- fail +ERROR: must be owner of procedure priv_testproc1 +\c - +DROP FUNCTION priv_testfunc1(int); -- ok +-- restore to sanity +GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC; +-- verify privilege checks on array-element coercions +/* YB: avoid transaction because using int8 throws error in further queries if not rolled back +BEGIN; +*/ -- YB +SELECT '{1}'::int4[]::int8[]; + int8 +------ + {1} +(1 row) + +REVOKE ALL ON FUNCTION int8(integer) FROM PUBLIC; +SELECT '{1}'::int4[]::int8[]; --superuser, succeed + int8 +------ + {1} +(1 row) + +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT '{1}'::int4[]::int8[]; --other user, fail +ERROR: permission denied for function int8 +/* YB: manually roll back changes due to lack of transaction +ROLLBACK; +*/ -- YB +RESET SESSION AUTHORIZATION; -- YB: manual rollback +GRANT ALL ON FUNCTION int8(integer) TO PUBLIC; -- YB: manual rollback +SET SESSION AUTHORIZATION regress_priv_user4; -- YB: manual rollback (see #1404) +-- privileges on types +-- switch to superuser +\c - +CREATE TYPE priv_testtype1 AS (a int, b text); +REVOKE USAGE ON TYPE priv_testtype1 FROM PUBLIC; +GRANT USAGE ON TYPE priv_testtype1 TO regress_priv_user2; +GRANT USAGE ON TYPE _priv_testtype1 TO regress_priv_user2; -- fail +ERROR: cannot set privileges of array types +HINT: Set the privileges of the element type instead. +GRANT USAGE ON DOMAIN priv_testtype1 TO regress_priv_user2; -- fail +ERROR: "priv_testtype1" is not a domain +CREATE DOMAIN priv_testdomain1 AS int; +REVOKE USAGE on DOMAIN priv_testdomain1 FROM PUBLIC; +GRANT USAGE ON DOMAIN priv_testdomain1 TO regress_priv_user2; +GRANT USAGE ON TYPE priv_testdomain1 TO regress_priv_user2; -- ok +SET SESSION AUTHORIZATION regress_priv_user1; +-- commands that should fail +CREATE AGGREGATE priv_testagg1a(priv_testdomain1) (sfunc = int4_sum, stype = bigint); +ERROR: permission denied for type priv_testdomain1 +CREATE DOMAIN priv_testdomain2a AS priv_testdomain1; +ERROR: permission denied for type priv_testdomain1 +CREATE DOMAIN priv_testdomain3a AS int; +CREATE FUNCTION castfunc(int) RETURNS priv_testdomain3a AS $$ SELECT $1::priv_testdomain3a $$ LANGUAGE SQL; +CREATE CAST (priv_testdomain1 AS priv_testdomain3a) WITH FUNCTION castfunc(int); +ERROR: permission denied for type priv_testdomain1 +DROP FUNCTION castfunc(int) CASCADE; +DROP DOMAIN priv_testdomain3a; +CREATE FUNCTION priv_testfunc5a(a priv_testdomain1) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; +ERROR: permission denied for type priv_testdomain1 +CREATE FUNCTION priv_testfunc6a(b int) RETURNS priv_testdomain1 LANGUAGE SQL AS $$ SELECT $1::priv_testdomain1 $$; +ERROR: permission denied for type priv_testdomain1 +CREATE OPERATOR !+! (PROCEDURE = int4pl, LEFTARG = priv_testdomain1, RIGHTARG = priv_testdomain1); +ERROR: permission denied for type priv_testdomain1 +CREATE TABLE test5a (a int, b priv_testdomain1); +ERROR: permission denied for type priv_testdomain1 +CREATE TABLE test6a OF priv_testtype1; +ERROR: permission denied for type priv_testtype1 +CREATE TABLE test10a (a int[], b priv_testtype1[]); +ERROR: permission denied for type priv_testtype1 +CREATE TABLE test9a (a int, b int); +ALTER TABLE test9a ADD COLUMN c priv_testdomain1; +ERROR: permission denied for type priv_testdomain1 +ALTER TABLE test9a ALTER COLUMN b TYPE priv_testdomain1; +ERROR: permission denied for type priv_testdomain1 +CREATE TYPE test7a AS (a int, b priv_testdomain1); +ERROR: permission denied for type priv_testdomain1 +CREATE TYPE test8a AS (a int, b int); +ALTER TYPE test8a ADD ATTRIBUTE c priv_testdomain1; +ERROR: ALTER TYPE ADD ATTRIBUTE not supported yet +LINE 1: ALTER TYPE test8a ADD ATTRIBUTE c priv_testdomain1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1893. React with thumbs up to raise its priority +ALTER TYPE test8a ALTER ATTRIBUTE b TYPE priv_testdomain1; +ERROR: ALTER TYPE ALTER ATTRIBUTE not supported yet +LINE 1: ALTER TYPE test8a ALTER ATTRIBUTE b TYPE priv_testdomain1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1893. React with thumbs up to raise its priority +CREATE TABLE test11a AS (SELECT 1::priv_testdomain1 AS a); +ERROR: permission denied for type priv_testdomain1 +REVOKE ALL ON TYPE priv_testtype1 FROM PUBLIC; +ERROR: permission denied for type priv_testtype1 +SET SESSION AUTHORIZATION regress_priv_user2; +-- commands that should succeed +CREATE AGGREGATE priv_testagg1b(priv_testdomain1) (sfunc = int4_sum, stype = bigint); +CREATE DOMAIN priv_testdomain2b AS priv_testdomain1; +CREATE DOMAIN priv_testdomain3b AS int; +CREATE FUNCTION castfunc(int) RETURNS priv_testdomain3b AS $$ SELECT $1::priv_testdomain3b $$ LANGUAGE SQL; +CREATE CAST (priv_testdomain1 AS priv_testdomain3b) WITH FUNCTION castfunc(int); +WARNING: cast will be ignored because the source data type is a domain +CREATE FUNCTION priv_testfunc5b(a priv_testdomain1) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; +CREATE FUNCTION priv_testfunc6b(b int) RETURNS priv_testdomain1 LANGUAGE SQL AS $$ SELECT $1::priv_testdomain1 $$; +CREATE OPERATOR !! (PROCEDURE = priv_testfunc5b, RIGHTARG = priv_testdomain1); +CREATE TABLE test5b (a int, b priv_testdomain1); +CREATE TABLE test6b OF priv_testtype1; +CREATE TABLE test10b (a int[], b priv_testtype1[]); +CREATE TABLE test9b (a int, b int); +ALTER TABLE test9b ADD COLUMN c priv_testdomain1; +ALTER TABLE test9b ALTER COLUMN b TYPE priv_testdomain1; +CREATE TYPE test7b AS (a int, b priv_testdomain1); +CREATE TYPE test8b AS (a int, b int); +ALTER TYPE test8b ADD ATTRIBUTE c priv_testdomain1; +ERROR: ALTER TYPE ADD ATTRIBUTE not supported yet +LINE 1: ALTER TYPE test8b ADD ATTRIBUTE c priv_testdomain1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1893. React with thumbs up to raise its priority +ALTER TYPE test8b ALTER ATTRIBUTE b TYPE priv_testdomain1; +ERROR: ALTER TYPE ALTER ATTRIBUTE not supported yet +LINE 1: ALTER TYPE test8b ALTER ATTRIBUTE b TYPE priv_testdomain1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1893. React with thumbs up to raise its priority +CREATE TABLE test11b AS (SELECT 1::priv_testdomain1 AS a); +REVOKE ALL ON TYPE priv_testtype1 FROM PUBLIC; +WARNING: no privileges could be revoked for "priv_testtype1" +\c - +DROP AGGREGATE priv_testagg1b(priv_testdomain1); +DROP DOMAIN priv_testdomain2b; +DROP OPERATOR !! (NONE, priv_testdomain1); +DROP FUNCTION priv_testfunc5b(a priv_testdomain1); +DROP FUNCTION priv_testfunc6b(b int); +DROP TABLE test5b; +DROP TABLE test6b; +DROP TABLE test9b; +DROP TABLE test10b; +DROP TYPE test7b; +DROP TYPE test8b; +DROP CAST (priv_testdomain1 AS priv_testdomain3b); +DROP FUNCTION castfunc(int) CASCADE; +DROP DOMAIN priv_testdomain3b; +DROP TABLE test11b; +DROP TYPE priv_testtype1; -- ok +DROP DOMAIN priv_testdomain1; -- ok +-- truncate +SET SESSION AUTHORIZATION regress_priv_user5; +TRUNCATE atest2; -- ok +TRUNCATE atest3; -- fail +ERROR: permission denied for table atest3 +-- has_table_privilege function +-- bad-input checks +select has_table_privilege(NULL,'pg_authid','select'); + has_table_privilege +--------------------- + +(1 row) + +select has_table_privilege('pg_shad','select'); +ERROR: relation "pg_shad" does not exist +select has_table_privilege('nosuchuser','pg_authid','select'); +ERROR: role "nosuchuser" does not exist +select has_table_privilege('pg_authid','sel'); +ERROR: unrecognized privilege type: "sel" +select has_table_privilege(-999999,'pg_authid','update'); + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(1,'select'); + has_table_privilege +--------------------- + +(1 row) + +-- superuser +\c - +select has_table_privilege(current_user,'pg_authid','select'); + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(current_user,'pg_authid','insert'); + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(t2.oid,'pg_authid','update') +from (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(t2.oid,'pg_authid','delete') +from (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + t +(1 row) + +-- 'rule' privilege no longer exists, but for backwards compatibility +-- has_table_privilege still recognizes the keyword and says FALSE +select has_table_privilege(current_user,t1.oid,'rule') +from (select oid from pg_class where relname = 'pg_authid') as t1; + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(current_user,t1.oid,'references') +from (select oid from pg_class where relname = 'pg_authid') as t1; + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(t2.oid,t1.oid,'select') +from (select oid from pg_class where relname = 'pg_authid') as t1, + (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(t2.oid,t1.oid,'insert') +from (select oid from pg_class where relname = 'pg_authid') as t1, + (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege('pg_authid','update'); + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege('pg_authid','delete'); + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege('pg_authid','truncate'); + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(t1.oid,'select') +from (select oid from pg_class where relname = 'pg_authid') as t1; + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(t1.oid,'trigger') +from (select oid from pg_class where relname = 'pg_authid') as t1; + has_table_privilege +--------------------- + t +(1 row) + +-- non-superuser +SET SESSION AUTHORIZATION regress_priv_user3; +select has_table_privilege(current_user,'pg_class','select'); + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(current_user,'pg_class','insert'); + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(t2.oid,'pg_class','update') +from (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(t2.oid,'pg_class','delete') +from (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(current_user,t1.oid,'references') +from (select oid from pg_class where relname = 'pg_class') as t1; + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(t2.oid,t1.oid,'select') +from (select oid from pg_class where relname = 'pg_class') as t1, + (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(t2.oid,t1.oid,'insert') +from (select oid from pg_class where relname = 'pg_class') as t1, + (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege('pg_class','update'); + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege('pg_class','delete'); + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege('pg_class','truncate'); + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(t1.oid,'select') +from (select oid from pg_class where relname = 'pg_class') as t1; + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(t1.oid,'trigger') +from (select oid from pg_class where relname = 'pg_class') as t1; + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(current_user,'atest1','select'); + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(current_user,'atest1','insert'); + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(t2.oid,'atest1','update') +from (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(t2.oid,'atest1','delete') +from (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(current_user,t1.oid,'references') +from (select oid from pg_class where relname = 'atest1') as t1; + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(t2.oid,t1.oid,'select') +from (select oid from pg_class where relname = 'atest1') as t1, + (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(t2.oid,t1.oid,'insert') +from (select oid from pg_class where relname = 'atest1') as t1, + (select oid from pg_roles where rolname = current_user) as t2; + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege('atest1','update'); + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege('atest1','delete'); + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege('atest1','truncate'); + has_table_privilege +--------------------- + f +(1 row) + +select has_table_privilege(t1.oid,'select') +from (select oid from pg_class where relname = 'atest1') as t1; + has_table_privilege +--------------------- + t +(1 row) + +select has_table_privilege(t1.oid,'trigger') +from (select oid from pg_class where relname = 'atest1') as t1; + has_table_privilege +--------------------- + f +(1 row) + +-- has_column_privilege function +-- bad-input checks (as non-super-user) +select has_column_privilege('pg_authid',NULL,'select'); + has_column_privilege +---------------------- + +(1 row) + +select has_column_privilege('pg_authid','nosuchcol','select'); +ERROR: column "nosuchcol" of relation "pg_authid" does not exist +select has_column_privilege(9999,'nosuchcol','select'); + has_column_privilege +---------------------- + +(1 row) + +select has_column_privilege(9999,99::int2,'select'); + has_column_privilege +---------------------- + +(1 row) + +select has_column_privilege('pg_authid',99::int2,'select'); + has_column_privilege +---------------------- + +(1 row) + +select has_column_privilege(9999,99::int2,'select'); + has_column_privilege +---------------------- + +(1 row) + +create temp table mytable(f1 int, f2 int, f3 int); +alter table mytable drop column f2; +select has_column_privilege('mytable','f2','select'); +ERROR: column "f2" of relation "mytable" does not exist +select has_column_privilege('mytable','........pg.dropped.2........','select'); + has_column_privilege +---------------------- + +(1 row) + +select has_column_privilege('mytable',2::int2,'select'); + has_column_privilege +---------------------- + +(1 row) + +select has_column_privilege('mytable',99::int2,'select'); + has_column_privilege +---------------------- + +(1 row) + +revoke select on table mytable from regress_priv_user3; +select has_column_privilege('mytable',2::int2,'select'); + has_column_privilege +---------------------- + +(1 row) + +select has_column_privilege('mytable',99::int2,'select'); + has_column_privilege +---------------------- + +(1 row) + +drop table mytable; +-- Grant options +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE TABLE atest4 (a int); +GRANT SELECT ON atest4 TO regress_priv_user2 WITH GRANT OPTION; +GRANT UPDATE ON atest4 TO regress_priv_user2; +GRANT SELECT ON atest4 TO GROUP regress_priv_group1 WITH GRANT OPTION; +SET SESSION AUTHORIZATION regress_priv_user2; +GRANT SELECT ON atest4 TO regress_priv_user3; +GRANT UPDATE ON atest4 TO regress_priv_user3; -- fail +WARNING: no privileges were granted for "atest4" +SET SESSION AUTHORIZATION regress_priv_user1; +REVOKE SELECT ON atest4 FROM regress_priv_user3; -- does nothing +SELECT has_table_privilege('regress_priv_user3', 'atest4', 'SELECT'); -- true + has_table_privilege +--------------------- + t +(1 row) + +REVOKE SELECT ON atest4 FROM regress_priv_user2; -- fail +ERROR: dependent privileges exist +HINT: Use CASCADE to revoke them too. +REVOKE GRANT OPTION FOR SELECT ON atest4 FROM regress_priv_user2 CASCADE; -- ok +SELECT has_table_privilege('regress_priv_user2', 'atest4', 'SELECT'); -- true + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regress_priv_user3', 'atest4', 'SELECT'); -- false + has_table_privilege +--------------------- + f +(1 row) + +SELECT has_table_privilege('regress_priv_user1', 'atest4', 'SELECT WITH GRANT OPTION'); -- true + has_table_privilege +--------------------- + t +(1 row) + +-- security-restricted operations +\c - +CREATE ROLE regress_sro_user; +-- Check that index expressions and predicates are run as the table's owner +-- A dummy index function checking current_user +CREATE FUNCTION sro_ifun(int) RETURNS int AS $$ +BEGIN + -- Below we set the table's owner to regress_sro_user + ASSERT current_user = 'regress_sro_user', + format('sro_ifun(%s) called by %s', $1, current_user); + RETURN $1; +END; +$$ LANGUAGE plpgsql IMMUTABLE; +-- Create a table owned by regress_sro_user +CREATE TABLE sro_tab (a int); +ALTER TABLE sro_tab OWNER TO regress_sro_user; +INSERT INTO sro_tab VALUES (1), (2), (3); +-- Create an expression index with a predicate +CREATE INDEX sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0))) + WHERE sro_ifun(a + 10) > sro_ifun(10); +DROP INDEX sro_idx; +-- Do the same concurrently +CREATE INDEX CONCURRENTLY sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0))) + WHERE sro_ifun(a + 10) > sro_ifun(10); +-- REINDEX +REINDEX TABLE sro_tab; +ERROR: REINDEX TABLE not supported yet +LINE 1: REINDEX TABLE sro_tab; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +REINDEX INDEX sro_idx; +ERROR: cannot reindex public indexes +DETAIL: For safety, indexes should not be serving reads during REINDEX. +HINT: Run UPDATE pg_index SET indisvalid = false WHERE indexrelid = ''::regclass. +REINDEX TABLE CONCURRENTLY sro_tab; +ERROR: REINDEX CONCURRENTLY not supported yet +LINE 1: REINDEX TABLE CONCURRENTLY sro_tab; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +DROP INDEX sro_idx; +-- CLUSTER +CREATE INDEX sro_cluster_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0))); +CLUSTER sro_tab USING sro_cluster_idx; +ERROR: CLUSTER not supported yet +LINE 1: CLUSTER sro_tab USING sro_cluster_idx; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +DROP INDEX sro_cluster_idx; +-- BRIN index +CREATE INDEX sro_brin ON sro_tab USING brin ((sro_ifun(a) + sro_ifun(0))); +ERROR: index method "brin" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +-- YB: port further queries when above works +-- Check with a partitioned table +CREATE TABLE sro_ptab (a int) PARTITION BY RANGE (a); +ALTER TABLE sro_ptab OWNER TO regress_sro_user; +CREATE TABLE sro_part PARTITION OF sro_ptab FOR VALUES FROM (1) TO (10); +ALTER TABLE sro_part OWNER TO regress_sro_user; +INSERT INTO sro_ptab VALUES (1), (2), (3); +CREATE INDEX sro_pidx ON sro_ptab ((sro_ifun(a) + sro_ifun(0))) + WHERE sro_ifun(a + 10) > sro_ifun(10); +REINDEX TABLE sro_ptab; +ERROR: REINDEX TABLE not supported yet +LINE 1: REINDEX TABLE sro_ptab; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +REINDEX INDEX CONCURRENTLY sro_pidx; +ERROR: REINDEX CONCURRENTLY not supported yet +LINE 1: REINDEX INDEX CONCURRENTLY sro_pidx; + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +SET SESSION AUTHORIZATION regress_sro_user; +CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS + 'GRANT regress_priv_group2 TO regress_sro_user'; +CREATE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS + 'DECLARE c CURSOR WITH HOLD FOR SELECT unwanted_grant(); SELECT true'; +-- REFRESH of this MV will queue a GRANT at end of transaction +CREATE MATERIALIZED VIEW sro_mv AS SELECT mv_action() WITH NO DATA; +REFRESH MATERIALIZED VIEW sro_mv; +ERROR: cannot create a cursor WITH HOLD within security-restricted operation +CONTEXT: SQL function "mv_action" statement 1 +\c - +REFRESH MATERIALIZED VIEW sro_mv; +ERROR: cannot create a cursor WITH HOLD within security-restricted operation +CONTEXT: SQL function "mv_action" statement 1 +SET SESSION AUTHORIZATION regress_sro_user; +-- INSERT to this table will queue a GRANT at end of transaction +CREATE TABLE sro_trojan_table (); +CREATE FUNCTION sro_trojan() RETURNS trigger LANGUAGE plpgsql AS + 'BEGIN PERFORM unwanted_grant(); RETURN NULL; END'; +CREATE CONSTRAINT TRIGGER t AFTER INSERT ON sro_trojan_table + INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE sro_trojan(); +ERROR: CREATE CONSTRAINT TRIGGER not supported yet +LINE 1: CREATE CONSTRAINT TRIGGER t AFTER INSERT ON sro_trojan_table + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1709. React with thumbs up to raise its priority +-- YB: port the remaining queries when the above is fixed +-- Admin options +SET SESSION AUTHORIZATION regress_priv_user4; +CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS + 'GRANT regress_priv_group2 TO regress_priv_user5'; +GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION +SET ROLE regress_priv_group2; +GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege +ERROR: must have admin option on role "regress_priv_group2" +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no ADMIN OPTION +ERROR: must have admin option on role "regress_priv_group2" +SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN +NOTICE: role "regress_priv_user5" is already a member of role "regress_priv_group2" + dogrant_ok +------------ + +(1 row) + +SET ROLE regress_priv_group2; +GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help +ERROR: must have admin option on role "regress_priv_group2" +SET SESSION AUTHORIZATION regress_priv_group2; +GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin +ERROR: must have admin option on role "regress_priv_group2" +SET SESSION AUTHORIZATION regress_priv_user4; +DROP FUNCTION dogrant_ok(); +REVOKE regress_priv_group2 FROM regress_priv_user5; +-- has_sequence_privilege tests +\c - +CREATE SEQUENCE x_seq; +GRANT USAGE on x_seq to regress_priv_user2; +SELECT has_sequence_privilege('regress_priv_user1', 'atest1', 'SELECT'); +ERROR: "atest1" is not a sequence +SELECT has_sequence_privilege('regress_priv_user1', 'x_seq', 'INSERT'); +ERROR: unrecognized privilege type: "INSERT" +SELECT has_sequence_privilege('regress_priv_user1', 'x_seq', 'SELECT'); + has_sequence_privilege +------------------------ + f +(1 row) + +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT has_sequence_privilege('x_seq', 'USAGE'); + has_sequence_privilege +------------------------ + t +(1 row) + +-- largeobject privilege tests +\c - +SET SESSION AUTHORIZATION regress_priv_user1; +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT lo_create(1001); + lo_create +----------- + 1001 +(1 row) + +SELECT lo_create(1002); + lo_create +----------- + 1002 +(1 row) + +SELECT lo_create(1003); + lo_create +----------- + 1003 +(1 row) + +SELECT lo_create(1004); + lo_create +----------- + 1004 +(1 row) + +SELECT lo_create(1005); + lo_create +----------- + 1005 +(1 row) + +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject +GRANT ALL ON LARGE OBJECT 1001 TO PUBLIC; +GRANT SELECT ON LARGE OBJECT 1003 TO regress_priv_user2; +GRANT SELECT,UPDATE ON LARGE OBJECT 1004 TO regress_priv_user2; +GRANT ALL ON LARGE OBJECT 1005 TO regress_priv_user2; +GRANT SELECT ON LARGE OBJECT 1005 TO regress_priv_user2 WITH GRANT OPTION; +GRANT SELECT, INSERT ON LARGE OBJECT 1001 TO PUBLIC; -- to be failed +ERROR: invalid privilege type INSERT for large object +GRANT SELECT, UPDATE ON LARGE OBJECT 1001 TO nosuchuser; -- to be failed +ERROR: role "nosuchuser" does not exist +GRANT SELECT, UPDATE ON LARGE OBJECT 999 TO PUBLIC; -- to be failed +ERROR: large object 999 does not exist +\c - +SET SESSION AUTHORIZATION regress_priv_user2; +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT lo_create(2001); + lo_create +----------- + 2001 +(1 row) + +SELECT lo_create(2002); + lo_create +----------- + 2002 +(1 row) + +SELECT loread(lo_open(1001, x'20000'::int), 32); -- allowed, for now + loread +-------- + \x +(1 row) + +SELECT lowrite(lo_open(1001, x'40000'::int), 'abcd'); -- fail, wrong mode +ERROR: large object descriptor 0 was not opened for writing +SELECT loread(lo_open(1001, x'40000'::int), 32); + loread +-------- + \x +(1 row) + +SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied +ERROR: permission denied for large object 1002 +SELECT loread(lo_open(1003, x'40000'::int), 32); + loread +-------- + \x +(1 row) + +SELECT loread(lo_open(1004, x'40000'::int), 32); + loread +-------- + \x +(1 row) + +SELECT lowrite(lo_open(1001, x'20000'::int), 'abcd'); + lowrite +--------- + 4 +(1 row) + +SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied +ERROR: permission denied for large object 1002 +SELECT lowrite(lo_open(1003, x'20000'::int), 'abcd'); -- to be denied +ERROR: permission denied for large object 1003 +SELECT lowrite(lo_open(1004, x'20000'::int), 'abcd'); + lowrite +--------- + 4 +(1 row) + +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject +GRANT SELECT ON LARGE OBJECT 1005 TO regress_priv_user3; +GRANT UPDATE ON LARGE OBJECT 1006 TO regress_priv_user3; -- to be denied +ERROR: large object 1006 does not exist +REVOKE ALL ON LARGE OBJECT 2001, 2002 FROM PUBLIC; +GRANT ALL ON LARGE OBJECT 2001 TO regress_priv_user3; +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT lo_unlink(1001); -- to be denied +ERROR: must be owner of large object 1001 +SELECT lo_unlink(2002); + lo_unlink +----------- + 1 +(1 row) + +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject +\c - +-- confirm ACL setting +SELECT oid, pg_get_userbyid(lomowner) ownername, lomacl FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid; + oid | ownername | lomacl +------+--------------------+------------------------------------------------------------------------------------------------------------------------------ + 1001 | regress_priv_user1 | {regress_priv_user1=rw/regress_priv_user1,=rw/regress_priv_user1} + 1002 | regress_priv_user1 | + 1003 | regress_priv_user1 | {regress_priv_user1=rw/regress_priv_user1,regress_priv_user2=r/regress_priv_user1} + 1004 | regress_priv_user1 | {regress_priv_user1=rw/regress_priv_user1,regress_priv_user2=rw/regress_priv_user1} + 1005 | regress_priv_user1 | {regress_priv_user1=rw/regress_priv_user1,regress_priv_user2=r*w/regress_priv_user1,regress_priv_user3=r/regress_priv_user2} + 2001 | regress_priv_user2 | {regress_priv_user2=rw/regress_priv_user2,regress_priv_user3=rw/regress_priv_user2} +(6 rows) + +SET SESSION AUTHORIZATION regress_priv_user3; +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT loread(lo_open(1001, x'40000'::int), 32); + loread +------------ + \x61626364 +(1 row) + +SELECT loread(lo_open(1003, x'40000'::int), 32); -- to be denied +ERROR: permission denied for large object 1003 +SELECT loread(lo_open(1005, x'40000'::int), 32); + loread +-------- + \x +(1 row) + +SELECT lo_truncate(lo_open(1005, x'20000'::int), 10); -- to be denied +ERROR: permission denied for large object 1005 +SELECT lo_truncate(lo_open(2001, x'20000'::int), 10); + lo_truncate +------------- + 0 +(1 row) + +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject +-- compatibility mode in largeobject permission +\c - +SET lo_compat_privileges = false; -- default setting +SET SESSION AUTHORIZATION regress_priv_user4; +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied +ERROR: permission denied for large object 1002 +SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied +ERROR: permission denied for large object 1002 +SELECT lo_truncate(lo_open(1002, x'20000'::int), 10); -- to be denied +ERROR: permission denied for large object 1002 +SELECT lo_put(1002, 1, 'abcd'); -- to be denied +ERROR: permission denied for large object 1002 +SELECT lo_unlink(1002); -- to be denied +ERROR: must be owner of large object 1002 +SELECT lo_export(1001, '/dev/null'); -- to be denied +ERROR: permission denied for function lo_export +SELECT lo_import('/dev/null'); -- to be denied +ERROR: permission denied for function lo_import +SELECT lo_import('/dev/null', 2003); -- to be denied +ERROR: permission denied for function lo_import +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject +\c - +SET lo_compat_privileges = true; -- compatibility mode +SET SESSION AUTHORIZATION regress_priv_user4; +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT loread(lo_open(1002, x'40000'::int), 32); + loread +-------- + \x +(1 row) + +SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); + lowrite +--------- + 4 +(1 row) + +SELECT lo_truncate(lo_open(1002, x'20000'::int), 10); + lo_truncate +------------- + 0 +(1 row) + +SELECT lo_unlink(1002); + lo_unlink +----------- + 1 +(1 row) + +SELECT lo_export(1001, '/dev/null'); -- to be denied +ERROR: permission denied for function lo_export +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject +-- don't allow unpriv users to access pg_largeobject contents +\c - +SELECT * FROM pg_largeobject LIMIT 0; + loid | pageno | data +------+--------+------ +(0 rows) + +SET SESSION AUTHORIZATION regress_priv_user1; +SELECT * FROM pg_largeobject LIMIT 0; -- to be denied +ERROR: permission denied for table pg_largeobject +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject +-- pg_signal_backend can't signal superusers +RESET SESSION AUTHORIZATION; +BEGIN; +CREATE OR REPLACE FUNCTION terminate_nothrow(pid int) RETURNS bool + LANGUAGE plpgsql SECURITY DEFINER SET client_min_messages = error AS $$ +BEGIN + RETURN pg_terminate_backend($1); +EXCEPTION WHEN OTHERS THEN + RETURN false; +END$$; +ALTER FUNCTION terminate_nothrow OWNER TO pg_signal_backend; +SELECT backend_type FROM pg_stat_activity +WHERE CASE WHEN COALESCE(usesysid, 10) = 10 THEN terminate_nothrow(pid) END; + backend_type +-------------- +(0 rows) + +ROLLBACK; +-- test pg_database_owner +RESET SESSION AUTHORIZATION; +GRANT pg_database_owner TO regress_priv_user1; +ERROR: role "pg_database_owner" cannot have explicit members +GRANT regress_priv_user1 TO pg_database_owner; +ERROR: role "pg_database_owner" cannot be a member of any role +CREATE TABLE datdba_only (); +ALTER TABLE datdba_only OWNER TO pg_database_owner; +REVOKE DELETE ON datdba_only FROM pg_database_owner; +SELECT + pg_has_role('regress_priv_user1', 'pg_database_owner', 'USAGE') as priv, + pg_has_role('regress_priv_user1', 'pg_database_owner', 'MEMBER') as mem, + pg_has_role('regress_priv_user1', 'pg_database_owner', + 'MEMBER WITH ADMIN OPTION') as admin; + priv | mem | admin +------+-----+------- + f | f | f +(1 row) + +/* YB: avoid transaction because not rolling back owner change causes discrepancy in further queries +BEGIN; +*/ -- YB +DO $$BEGIN EXECUTE format( + 'ALTER DATABASE %I OWNER TO regress_priv_group2', current_catalog); END$$; +SELECT + pg_has_role('regress_priv_user1', 'pg_database_owner', 'USAGE') as priv, + pg_has_role('regress_priv_user1', 'pg_database_owner', 'MEMBER') as mem, + pg_has_role('regress_priv_user1', 'pg_database_owner', + 'MEMBER WITH ADMIN OPTION') as admin; + priv | mem | admin +------+-----+------- + t | t | f +(1 row) + +SET SESSION AUTHORIZATION regress_priv_user1; +TABLE information_schema.enabled_roles ORDER BY role_name COLLATE "C"; + role_name +--------------------- + pg_database_owner + regress_priv_group2 + regress_priv_user1 +(3 rows) + +TABLE information_schema.applicable_roles ORDER BY role_name COLLATE "C"; + grantee | role_name | is_grantable +---------------------+---------------------+-------------- + regress_priv_group2 | pg_database_owner | NO + regress_priv_user1 | regress_priv_group2 | NO +(2 rows) + +INSERT INTO datdba_only DEFAULT VALUES; +/* YB: avoid transaction: SAVEPOINT q; */ DELETE FROM datdba_only; /* YB: avoid transaction: ROLLBACK to q; */ +ERROR: permission denied for table datdba_only +SET SESSION AUTHORIZATION regress_priv_user2; +TABLE information_schema.enabled_roles; + role_name +-------------------- + regress_priv_user2 +(1 row) + +INSERT INTO datdba_only DEFAULT VALUES; +ERROR: permission denied for table datdba_only +/* YB: manually roll back changes due to lack of transaction +ROLLBACK; +*/ -- YB +RESET SESSION AUTHORIZATION; -- YB: manual rollback +DO $$BEGIN EXECUTE format( -- YB: manual rollback + 'ALTER DATABASE %I OWNER TO postgres', current_catalog); END$$; -- YB: manual rollback (see #1404) +-- test default ACLs +\c - +CREATE SCHEMA testns; +GRANT ALL ON SCHEMA testns TO regress_priv_user1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +-- placeholder for test with duplicated schema and role names +ALTER DEFAULT PRIVILEGES IN SCHEMA testns,testns GRANT SELECT ON TABLES TO public,public; +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT INSERT ON TABLES TO regress_priv_user1; +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns REVOKE INSERT ON TABLES FROM regress_priv_user1; +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +ALTER DEFAULT PRIVILEGES FOR ROLE regress_priv_user1 REVOKE EXECUTE ON FUNCTIONS FROM public; +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT USAGE ON SCHEMAS TO regress_priv_user2; -- error +ERROR: cannot use IN SCHEMA clause when using GRANT/REVOKE ON SCHEMAS +-- +-- Testing blanket default grants is very hazardous since it might change +-- the privileges attached to objects created by concurrent regression tests. +-- To avoid that, be sure to revoke the privileges again before committing. +-- +BEGIN; +ALTER DEFAULT PRIVILEGES GRANT USAGE ON SCHEMAS TO regress_priv_user2; +CREATE SCHEMA testns2; +SELECT has_schema_privilege('regress_priv_user2', 'testns2', 'USAGE'); -- yes + has_schema_privilege +---------------------- + t +(1 row) + +SELECT has_schema_privilege('regress_priv_user6', 'testns2', 'USAGE'); -- yes + has_schema_privilege +---------------------- + t +(1 row) + +SELECT has_schema_privilege('regress_priv_user2', 'testns2', 'CREATE'); -- no + has_schema_privilege +---------------------- + f +(1 row) + +ALTER DEFAULT PRIVILEGES REVOKE USAGE ON SCHEMAS FROM regress_priv_user2; +CREATE SCHEMA testns3; +SELECT has_schema_privilege('regress_priv_user2', 'testns3', 'USAGE'); -- no + has_schema_privilege +---------------------- + f +(1 row) + +SELECT has_schema_privilege('regress_priv_user2', 'testns3', 'CREATE'); -- no + has_schema_privilege +---------------------- + f +(1 row) + +ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_priv_user2; +CREATE SCHEMA testns4; +SELECT has_schema_privilege('regress_priv_user2', 'testns4', 'USAGE'); -- yes + has_schema_privilege +---------------------- + t +(1 row) + +SELECT has_schema_privilege('regress_priv_user2', 'testns4', 'CREATE'); -- yes + has_schema_privilege +---------------------- + t +(1 row) + +ALTER DEFAULT PRIVILEGES REVOKE ALL ON SCHEMAS FROM regress_priv_user2; +COMMIT; +-- Test for DROP OWNED BY with shared dependencies. This is done in a +-- separate, rollbacked, transaction to avoid any trouble with other +-- regression sessions. +-- YB: TODO(#1404): The ROLLBACK statement in this transaction will not actually revert the DROP of large object 2001 after DROP OWNED BY regress_priv_user2, regress_priv_user2. +-- YB: if it is owned by regress_priv_user2. This object is needed during a later SELECT lo_unlink(oid). Change the owner away from regress_priv_user2 to avoid dropping the object in the first place. +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLS to pg_largeobject +ALTER LARGE OBJECT 2001 OWNER TO postgres; -- YB: change owner to avoid drop +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLS to pg_largeobject +BEGIN; +ALTER DEFAULT PRIVILEGES GRANT ALL ON FUNCTIONS TO regress_priv_user2; +ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_priv_user2; +ALTER DEFAULT PRIVILEGES GRANT ALL ON SEQUENCES TO regress_priv_user2; +ALTER DEFAULT PRIVILEGES GRANT ALL ON TABLES TO regress_priv_user2; +ALTER DEFAULT PRIVILEGES GRANT ALL ON TYPES TO regress_priv_user2; +SELECT count(*) FROM pg_shdepend + WHERE deptype = 'a' AND + refobjid = 'regress_priv_user2'::regrole AND + classid = 'pg_default_acl'::regclass; + count +------- + 5 +(1 row) + +DROP OWNED BY regress_priv_user2, regress_priv_user2; +SELECT count(*) FROM pg_shdepend + WHERE deptype = 'a' AND + refobjid = 'regress_priv_user2'::regrole AND + classid = 'pg_default_acl'::regclass; + count +------- + 0 +(1 row) + +ROLLBACK; +CREATE SCHEMA testns5; +SELECT has_schema_privilege('regress_priv_user2', 'testns5', 'USAGE'); -- no + has_schema_privilege +---------------------- + f +(1 row) + +SELECT has_schema_privilege('regress_priv_user2', 'testns5', 'CREATE'); -- no + has_schema_privilege +---------------------- + f +(1 row) + +SET ROLE regress_priv_user1; +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; +CREATE AGGREGATE testns.agg1(int) (sfunc = int4pl, stype = int4); +CREATE PROCEDURE testns.bar() AS 'select 1' LANGUAGE sql; +SELECT has_function_privilege('regress_priv_user2', 'testns.foo()', 'EXECUTE'); -- no + has_function_privilege +------------------------ + f +(1 row) + +SELECT has_function_privilege('regress_priv_user2', 'testns.agg1(int)', 'EXECUTE'); -- no + has_function_privilege +------------------------ + f +(1 row) + +SELECT has_function_privilege('regress_priv_user2', 'testns.bar()', 'EXECUTE'); -- no + has_function_privilege +------------------------ + f +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT EXECUTE ON ROUTINES to public; +DROP FUNCTION testns.foo(); +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; +DROP AGGREGATE testns.agg1(int); +CREATE AGGREGATE testns.agg1(int) (sfunc = int4pl, stype = int4); +DROP PROCEDURE testns.bar(); +CREATE PROCEDURE testns.bar() AS 'select 1' LANGUAGE sql; +SELECT has_function_privilege('regress_priv_user2', 'testns.foo()', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_priv_user2', 'testns.agg1(int)', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_priv_user2', 'testns.bar()', 'EXECUTE'); -- yes (counts as function here) + has_function_privilege +------------------------ + t +(1 row) + +DROP FUNCTION testns.foo(); +DROP AGGREGATE testns.agg1(int); +DROP PROCEDURE testns.bar(); +ALTER DEFAULT PRIVILEGES FOR ROLE regress_priv_user1 REVOKE USAGE ON TYPES FROM public; +CREATE DOMAIN testns.priv_testdomain1 AS int; +SELECT has_type_privilege('regress_priv_user2', 'testns.priv_testdomain1', 'USAGE'); -- no + has_type_privilege +-------------------- + f +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT USAGE ON TYPES to public; +DROP DOMAIN testns.priv_testdomain1; +CREATE DOMAIN testns.priv_testdomain1 AS int; +SELECT has_type_privilege('regress_priv_user2', 'testns.priv_testdomain1', 'USAGE'); -- yes + has_type_privilege +-------------------- + t +(1 row) + +DROP DOMAIN testns.priv_testdomain1; +RESET ROLE; +SELECT count(*) + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname = 'testns'; + count +------- + 3 +(1 row) + +DROP SCHEMA testns CASCADE; +NOTICE: drop cascades to table testns.acltest1 +DROP SCHEMA testns2 CASCADE; +DROP SCHEMA testns3 CASCADE; +DROP SCHEMA testns4 CASCADE; +DROP SCHEMA testns5 CASCADE; +SELECT d.* -- check that entries went away + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname IS NULL AND defaclnamespace != 0; + oid | defaclrole | defaclnamespace | defaclobjtype | defaclacl +-----+------------+-----------------+---------------+----------- +(0 rows) + +-- Grant on all objects of given type in a schema +\c - +CREATE SCHEMA testns; +CREATE TABLE testns.t1 (f1 int); +CREATE TABLE testns.t2 (f1 int); +SELECT has_table_privilege('regress_priv_user1', 'testns.t1', 'SELECT'); -- false + has_table_privilege +--------------------- + f +(1 row) + +GRANT ALL ON ALL TABLES IN SCHEMA testns TO regress_priv_user1; +SELECT has_table_privilege('regress_priv_user1', 'testns.t1', 'SELECT'); -- true + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regress_priv_user1', 'testns.t2', 'SELECT'); -- true + has_table_privilege +--------------------- + t +(1 row) + +REVOKE ALL ON ALL TABLES IN SCHEMA testns FROM regress_priv_user1; +SELECT has_table_privilege('regress_priv_user1', 'testns.t1', 'SELECT'); -- false + has_table_privilege +--------------------- + f +(1 row) + +SELECT has_table_privilege('regress_priv_user1', 'testns.t2', 'SELECT'); -- false + has_table_privilege +--------------------- + f +(1 row) + +CREATE FUNCTION testns.priv_testfunc(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql; +CREATE AGGREGATE testns.priv_testagg(int) (sfunc = int4pl, stype = int4); +CREATE PROCEDURE testns.priv_testproc(int) AS 'select 3' LANGUAGE sql; +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testfunc(int)', 'EXECUTE'); -- true by default + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testagg(int)', 'EXECUTE'); -- true by default + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- true by default + has_function_privilege +------------------------ + t +(1 row) + +REVOKE ALL ON ALL FUNCTIONS IN SCHEMA testns FROM PUBLIC; +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testfunc(int)', 'EXECUTE'); -- false + has_function_privilege +------------------------ + f +(1 row) + +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testagg(int)', 'EXECUTE'); -- false + has_function_privilege +------------------------ + f +(1 row) + +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- still true, not a function + has_function_privilege +------------------------ + t +(1 row) + +REVOKE ALL ON ALL PROCEDURES IN SCHEMA testns FROM PUBLIC; +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- now false + has_function_privilege +------------------------ + f +(1 row) + +GRANT ALL ON ALL ROUTINES IN SCHEMA testns TO PUBLIC; +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testfunc(int)', 'EXECUTE'); -- true + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testagg(int)', 'EXECUTE'); -- true + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- true + has_function_privilege +------------------------ + t +(1 row) + +\set VERBOSITY terse \\ -- YB: suppress cascade details +DROP SCHEMA testns CASCADE; +NOTICE: drop cascades to 5 other objects +\set VERBOSITY default \\ -- YB +-- Change owner of the schema & and rename of new schema owner +\c - +CREATE ROLE regress_schemauser1 superuser login; +CREATE ROLE regress_schemauser2 superuser login; +SET SESSION ROLE regress_schemauser1; +CREATE SCHEMA testns; +SELECT nspname, rolname FROM pg_namespace, pg_roles WHERE pg_namespace.nspname = 'testns' AND pg_namespace.nspowner = pg_roles.oid; + nspname | rolname +---------+--------------------- + testns | regress_schemauser1 +(1 row) + +ALTER SCHEMA testns OWNER TO regress_schemauser2; +ALTER ROLE regress_schemauser2 RENAME TO regress_schemauser_renamed; +SELECT nspname, rolname FROM pg_namespace, pg_roles WHERE pg_namespace.nspname = 'testns' AND pg_namespace.nspowner = pg_roles.oid; + nspname | rolname +---------+---------------------------- + testns | regress_schemauser_renamed +(1 row) + +set session role regress_schemauser_renamed; +\set VERBOSITY terse \\ -- YB: suppress cascade details +DROP SCHEMA testns CASCADE; +\set VERBOSITY default \\ -- YB +-- clean up +\c - +DROP ROLE regress_schemauser1; +DROP ROLE regress_schemauser_renamed; +-- test that dependent privileges are revoked (or not) properly +\c - +set session role regress_priv_user1; +create table dep_priv_test (a int); +grant select on dep_priv_test to regress_priv_user2 with grant option; +grant select on dep_priv_test to regress_priv_user3 with grant option; +set session role regress_priv_user2; +grant select on dep_priv_test to regress_priv_user4 with grant option; +set session role regress_priv_user3; +grant select on dep_priv_test to regress_priv_user4 with grant option; +set session role regress_priv_user4; +grant select on dep_priv_test to regress_priv_user5; +\dp dep_priv_test + Access privileges + Schema | Name | Type | Access privileges | Column privileges | Policies +--------+---------------+-------+-----------------------------------------------+-------------------+---------- + public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| | + | | | regress_priv_user2=r*/regress_priv_user1 +| | + | | | regress_priv_user3=r*/regress_priv_user1 +| | + | | | regress_priv_user4=r*/regress_priv_user2 +| | + | | | regress_priv_user4=r*/regress_priv_user3 +| | + | | | regress_priv_user5=r/regress_priv_user4 | | +(1 row) + +set session role regress_priv_user2; +revoke select on dep_priv_test from regress_priv_user4 cascade; +\dp dep_priv_test + Access privileges + Schema | Name | Type | Access privileges | Column privileges | Policies +--------+---------------+-------+-----------------------------------------------+-------------------+---------- + public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| | + | | | regress_priv_user2=r*/regress_priv_user1 +| | + | | | regress_priv_user3=r*/regress_priv_user1 +| | + | | | regress_priv_user4=r*/regress_priv_user3 +| | + | | | regress_priv_user5=r/regress_priv_user4 | | +(1 row) + +set session role regress_priv_user3; +revoke select on dep_priv_test from regress_priv_user4 cascade; +\dp dep_priv_test + Access privileges + Schema | Name | Type | Access privileges | Column privileges | Policies +--------+---------------+-------+-----------------------------------------------+-------------------+---------- + public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| | + | | | regress_priv_user2=r*/regress_priv_user1 +| | + | | | regress_priv_user3=r*/regress_priv_user1 | | +(1 row) + +set session role regress_priv_user1; +drop table dep_priv_test; +-- clean up +\c +drop sequence x_seq; +DROP AGGREGATE priv_testagg1(int); +DROP FUNCTION priv_testfunc2(int); +DROP FUNCTION priv_testfunc4(boolean); +DROP PROCEDURE priv_testproc1(int); +DROP VIEW atestv0; +DROP VIEW atestv1; +DROP VIEW atestv2; +-- this should cascade to drop atestv4 +DROP VIEW atestv3 CASCADE; +NOTICE: drop cascades to view atestv4 +-- this should complain "does not exist" +DROP VIEW atestv4; +ERROR: view "atestv4" does not exist +DROP TABLE atest1; +DROP TABLE atest2; +DROP TABLE atest3; +DROP TABLE atest4; +DROP TABLE atest5; +DROP TABLE atest6; +DROP TABLE atestc; +DROP TABLE atestp1; +DROP TABLE atestp2; +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid; + lo_unlink +----------- + 1 + 1 + 1 + 1 + 1 +(5 rows) + +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject +DROP GROUP regress_priv_group1; +DROP GROUP regress_priv_group2; +-- these are needed to clean up permissions +REVOKE USAGE ON LANGUAGE sql FROM regress_priv_user1; +DROP OWNED BY regress_priv_user1; +DROP USER regress_priv_user1; +DROP USER regress_priv_user2; +DROP USER regress_priv_user3; +DROP USER regress_priv_user4; +DROP USER regress_priv_user5; +DROP USER regress_priv_user6; +DROP USER regress_priv_user7; +DROP USER regress_priv_user8; -- does not exist +ERROR: role "regress_priv_user8" does not exist +-- permissions with LOCK TABLE +CREATE USER regress_locktable_user; +CREATE TABLE lock_table (a int); +-- LOCK TABLE and SELECT permission +GRANT SELECT ON lock_table TO regress_locktable_user; +SET SESSION AUTHORIZATION regress_locktable_user; +BEGIN; +LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should fail +ERROR: permission denied for table lock_table +ROLLBACK; +BEGIN; +LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass +COMMIT; +BEGIN; +LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail +ERROR: permission denied for table lock_table +ROLLBACK; +\c +REVOKE SELECT ON lock_table FROM regress_locktable_user; +-- LOCK TABLE and INSERT permission +GRANT INSERT ON lock_table TO regress_locktable_user; +SET SESSION AUTHORIZATION regress_locktable_user; +BEGIN; +LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass +COMMIT; +BEGIN; +LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail +ERROR: permission denied for table lock_table +ROLLBACK; +BEGIN; +LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail +ERROR: permission denied for table lock_table +ROLLBACK; +\c +REVOKE INSERT ON lock_table FROM regress_locktable_user; +-- LOCK TABLE and UPDATE permission +GRANT UPDATE ON lock_table TO regress_locktable_user; +SET SESSION AUTHORIZATION regress_locktable_user; +BEGIN; +LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass +COMMIT; +BEGIN; +LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail +ERROR: permission denied for table lock_table +ROLLBACK; +BEGIN; +LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass +COMMIT; +\c +REVOKE UPDATE ON lock_table FROM regress_locktable_user; +-- LOCK TABLE and DELETE permission +GRANT DELETE ON lock_table TO regress_locktable_user; +SET SESSION AUTHORIZATION regress_locktable_user; +BEGIN; +LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass +COMMIT; +BEGIN; +LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail +ERROR: permission denied for table lock_table +ROLLBACK; +BEGIN; +LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass +COMMIT; +\c +REVOKE DELETE ON lock_table FROM regress_locktable_user; +-- LOCK TABLE and TRUNCATE permission +GRANT TRUNCATE ON lock_table TO regress_locktable_user; +SET SESSION AUTHORIZATION regress_locktable_user; +BEGIN; +LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass +COMMIT; +BEGIN; +LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail +ERROR: permission denied for table lock_table +ROLLBACK; +BEGIN; +LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass +COMMIT; +\c +REVOKE TRUNCATE ON lock_table FROM regress_locktable_user; +-- clean up +DROP TABLE lock_table; +DROP USER regress_locktable_user; +-- test to check privileges of system views pg_shmem_allocations and +-- pg_backend_memory_contexts. +-- switch to superuser +\c - +CREATE ROLE regress_readallstats; +SELECT has_table_privilege('regress_readallstats','pg_backend_memory_contexts','SELECT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +SELECT has_table_privilege('regress_readallstats','pg_shmem_allocations','SELECT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +GRANT pg_read_all_stats TO regress_readallstats; +SELECT has_table_privilege('regress_readallstats','pg_backend_memory_contexts','SELECT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regress_readallstats','pg_shmem_allocations','SELECT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +-- run query to ensure that functions within views can be executed +SET ROLE regress_readallstats; +SELECT COUNT(*) >= 0 AS ok FROM pg_backend_memory_contexts; + ok +---- + t +(1 row) + +SELECT COUNT(*) >= 0 AS ok FROM pg_shmem_allocations; + ok +---- + t +(1 row) + +RESET ROLE; +-- clean up +DROP ROLE regress_readallstats; diff --git a/src/test/regress/expected/yb.port.publication.out b/src/test/regress/expected/yb.port.publication.out new file mode 100644 index 0000000000000..c5f4de7cbdeb2 --- /dev/null +++ b/src/test/regress/expected/yb.port.publication.out @@ -0,0 +1,305 @@ +-- +-- PUBLICATION +-- +CREATE ROLE regress_publication_user LOGIN SUPERUSER; +CREATE ROLE regress_publication_user2; +CREATE ROLE regress_publication_user_dummy LOGIN NOSUPERUSER; +SET SESSION AUTHORIZATION 'regress_publication_user'; +-- suppress warning that depends on wal_level +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_default; +RESET client_min_messages; +COMMENT ON PUBLICATION testpub_default IS 'test publication'; +SELECT obj_description(p.oid, 'pg_publication') FROM pg_publication p; + obj_description +------------------ + test publication +(1 row) + +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpib_ins_trunct WITH (publish = insert); +ERROR: publishing only a subset of DML commands is not yet supported +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19250. React with thumbs up to raise its priority. +RESET client_min_messages; +ALTER PUBLICATION testpub_default SET (publish = update); +ERROR: publishing only a subset of DML commands is not yet supported +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19250. React with thumbs up to raise its priority. +-- error cases +CREATE PUBLICATION testpub_xxx WITH (foo); +ERROR: unrecognized publication parameter: "foo" +CREATE PUBLICATION testpub_xxx WITH (publish = 'cluster, vacuum'); +ERROR: unrecognized value for publication option "publish": "cluster" +\dRp + List of publications + Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root +-----------------+--------------------------+------------+---------+---------+---------+-----------+---------- + testpub_default | regress_publication_user | f | t | t | t | t | f +(1 row) + +ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete'); +ERROR: publishing only a subset of DML commands is not yet supported +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19250. React with thumbs up to raise its priority. +\dRp + List of publications + Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root +-----------------+--------------------------+------------+---------+---------+---------+-----------+---------- + testpub_default | regress_publication_user | f | t | t | t | t | f +(1 row) + +--- adding tables +CREATE SCHEMA pub_test; +CREATE TABLE testpub_tbl1 (id serial primary key, data text); +CREATE TABLE pub_test.testpub_nopk (foo int, bar int); +CREATE VIEW testpub_view AS SELECT 1; +CREATE TABLE testpub_parted (a int) PARTITION BY LIST (a); +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_foralltables FOR ALL TABLES WITH (publish = 'insert'); +ERROR: publishing only a subset of DML commands is not yet supported +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19250. React with thumbs up to raise its priority. +RESET client_min_messages; +-- YB Note: publishing subset of records is unsupported (#19250). +CREATE PUBLICATION testpub_foralltables FOR ALL TABLES; +NOTICE: tables without primary key will be skipped +ALTER PUBLICATION testpub_foralltables SET (publish = 'insert, update'); +ERROR: publishing only a subset of DML commands is not yet supported +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19250. React with thumbs up to raise its priority. +-- YB Note: publishing subset of records is unsupported (#19250). +ALTER PUBLICATION testpub_foralltables SET (publish = 'insert, update, delete, truncate'); +CREATE TABLE testpub_tbl2 (id serial primary key, data text); +-- fail - can't add to for all tables publication +ALTER PUBLICATION testpub_foralltables ADD TABLE testpub_tbl2; +ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES +DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications. +-- fail - can't drop from all tables publication +ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2; +ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES +DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications. +-- fail - can't add to for all tables publication +ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk; +ERROR: publication "testpub_foralltables" is defined as FOR ALL TABLES +DETAIL: Tables cannot be added to or dropped from FOR ALL TABLES publications. +SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables'; + pubname | puballtables +----------------------+-------------- + testpub_foralltables | t +(1 row) + +\d+ testpub_tbl2 + Table "public.testpub_tbl2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+------------------------------------------+----------+--------------+------------- + id | integer | | not null | nextval('testpub_tbl2_id_seq'::regclass) | plain | | + data | text | | | | extended | | +Indexes: + "testpub_tbl2_pkey" PRIMARY KEY, lsm (id HASH) +Publications: + "testpub_foralltables" + +\dRp+ testpub_foralltables + Publication testpub_foralltables + Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+---------+---------+---------+-----------+---------- + regress_publication_user | t | t | t | t | t | f +(1 row) + +DROP TABLE testpub_tbl2; +DROP PUBLICATION testpub_foralltables; +CREATE TABLE testpub_tbl3 (a int); +CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3); +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3; +ERROR: table "testpub_tbl3" cannot be replicated +DETAIL: Replicating tables without primary key is not yet supported. +CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3; +ERROR: table "testpub_tbl3" cannot be replicated +DETAIL: Replicating tables without primary key is not yet supported. +RESET client_min_messages; +\dRp+ testpub3 +\dRp+ testpub4 +DROP TABLE testpub_tbl3, testpub_tbl3a; +DROP PUBLICATION testpub3, testpub4; +ERROR: DROP multiple objects not supported yet +LINE 1: DROP PUBLICATION testpub3, testpub4; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/880. React with thumbs up to raise its priority +-- YB Note: Drop publications one by one to ensure cleanup. +-- These will fail now but will pass once we support streaming +-- tables without pk. +DROP PUBLICATION testpub3; +ERROR: publication "testpub3" does not exist +DROP PUBLICATION testpub4; +ERROR: publication "testpub4" does not exist +-- Tests for partitioned tables +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_forparted; +CREATE PUBLICATION testpub_forparted1; +RESET client_min_messages; +CREATE TABLE testpub_parted1 (LIKE testpub_parted); +CREATE TABLE testpub_parted2 (LIKE testpub_parted); +ALTER PUBLICATION testpub_forparted1 SET (publish='insert'); +ERROR: publishing only a subset of DML commands is not yet supported +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19250. React with thumbs up to raise its priority. +ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (1); +ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted2 FOR VALUES IN (2); +-- works despite missing REPLICA IDENTITY, because updates are not replicated +UPDATE testpub_parted1 SET a = 1; +-- only parent is listed as being in publication, not the partition +ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted; +ERROR: table "testpub_parted" cannot be replicated +DETAIL: Replicating tables without primary key is not yet supported. +DROP TABLE testpub_parted1, testpub_parted2; +DROP PUBLICATION testpub_forparted, testpub_forparted1; +ERROR: DROP multiple objects not supported yet +LINE 1: DROP PUBLICATION testpub_forparted, testpub_forparted1; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/880. React with thumbs up to raise its priority +-- YB Note: Drop publications one by one to ensure cleanup. +DROP PUBLICATION testpub_forparted; +DROP PUBLICATION testpub_forparted1; +-- fail - view +CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_view; +ERROR: cannot add relation "testpub_view" to publication +DETAIL: This operation is not supported for views. +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1, pub_test.testpub_nopk; +ERROR: table "testpub_nopk" cannot be replicated +DETAIL: Replicating tables without primary key is not yet supported. +-- YB Note: tables without pk are unsupported. +CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1; +RESET client_min_messages; +-- fail - already added +ALTER PUBLICATION testpub_fortbl ADD TABLE testpub_tbl1; +ERROR: relation "testpub_tbl1" is already member of publication "testpub_fortbl" +-- fail - already added +CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1; +ERROR: publication "testpub_fortbl" already exists +\dRp+ testpub_fortbl + Publication testpub_fortbl + Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+---------+---------+---------+-----------+---------- + regress_publication_user | f | t | t | t | t | f +Tables: + "public.testpub_tbl1" + +-- fail - view +ALTER PUBLICATION testpub_default ADD TABLE testpub_view; +ERROR: cannot add relation "testpub_view" to publication +DETAIL: This operation is not supported for views. +ALTER PUBLICATION testpub_default ADD TABLE testpub_tbl1; +ALTER PUBLICATION testpub_default SET TABLE testpub_tbl1; +ALTER PUBLICATION testpub_default ADD TABLE pub_test.testpub_nopk; +ERROR: table "testpub_nopk" cannot be replicated +DETAIL: Replicating tables without primary key is not yet supported. +ALTER PUBLICATION testpib_ins_trunct ADD TABLE pub_test.testpub_nopk, testpub_tbl1; +ERROR: publication "testpib_ins_trunct" does not exist +\d+ pub_test.testpub_nopk + Table "pub_test.testpub_nopk" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + foo | integer | | | | plain | | + bar | integer | | | | plain | | + +\d+ testpub_tbl1 + Table "public.testpub_tbl1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+------------------------------------------+----------+--------------+------------- + id | integer | | not null | nextval('testpub_tbl1_id_seq'::regclass) | plain | | + data | text | | | | extended | | +Indexes: + "testpub_tbl1_pkey" PRIMARY KEY, lsm (id HASH) +Publications: + "testpub_default" + "testpub_fortbl" + +\dRp+ testpub_default + Publication testpub_default + Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+---------+---------+---------+-----------+---------- + regress_publication_user | f | t | t | t | t | f +Tables: + "public.testpub_tbl1" + +ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1, pub_test.testpub_nopk; +ERROR: relation "testpub_nopk" is not part of the publication +-- YB Note: tables without pk are unsupported. +ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1; +-- fail - nonexistent +ALTER PUBLICATION testpub_default DROP TABLE pub_test.testpub_nopk; +ERROR: relation "testpub_nopk" is not part of the publication +\d+ testpub_tbl1 + Table "public.testpub_tbl1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+------------------------------------------+----------+--------------+------------- + id | integer | | not null | nextval('testpub_tbl1_id_seq'::regclass) | plain | | + data | text | | | | extended | | +Indexes: + "testpub_tbl1_pkey" PRIMARY KEY, lsm (id HASH) +Publications: + "testpub_fortbl" + +-- permissions +SET ROLE regress_publication_user2; +CREATE PUBLICATION testpub2; -- fail +ERROR: permission denied for database yugabyte +SET ROLE regress_publication_user; +GRANT CREATE ON DATABASE yugabyte TO regress_publication_user2; +SET ROLE regress_publication_user2; +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub2; -- ok +RESET client_min_messages; +ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail +ERROR: must be owner of table testpub_tbl1 +SET ROLE regress_publication_user; +GRANT regress_publication_user TO regress_publication_user2; +SET ROLE regress_publication_user2; +ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok +DROP PUBLICATION testpub2; +SET ROLE regress_publication_user; +REVOKE CREATE ON DATABASE yugabyte FROM regress_publication_user2; +DROP TABLE testpub_parted; +DROP VIEW testpub_view; +-- YB Note: We do not allow dropping of tables that are part of a non-ALL TABLES publication. +DROP TABLE testpub_tbl1; +ERROR: cannot drop a table which is part of a publication. +HINT: Use pg_publication_tables to find all such publications and retry after dropping the table from them. +ALTER PUBLICATION testpub_fortbl DROP TABLE testpub_tbl1; -- YB: Drop the table from publication first. +DROP TABLE testpub_tbl1; +\dRp+ testpub_default + Publication testpub_default + Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root +--------------------------+------------+---------+---------+---------+-----------+---------- + regress_publication_user | f | t | t | t | t | f +(1 row) + +-- fail - must be owner of publication +SET ROLE regress_publication_user_dummy; +ALTER PUBLICATION testpub_default RENAME TO testpub_dummy; +ERROR: must be owner of publication testpub_default +RESET ROLE; +ALTER PUBLICATION testpub_default RENAME TO testpub_foo; +\dRp testpub_foo + List of publications + Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root +-------------+--------------------------+------------+---------+---------+---------+-----------+---------- + testpub_foo | regress_publication_user | f | t | t | t | t | f +(1 row) + +-- rename back to keep the rest simple +ALTER PUBLICATION testpub_foo RENAME TO testpub_default; +ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2; +\dRp testpub_default + List of publications + Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root +-----------------+---------------------------+------------+---------+---------+---------+-----------+---------- + testpub_default | regress_publication_user2 | f | t | t | t | t | f +(1 row) + +DROP PUBLICATION testpub_default; +DROP PUBLICATION testpib_ins_trunct; +ERROR: publication "testpib_ins_trunct" does not exist +DROP PUBLICATION testpub_fortbl; +DROP SCHEMA pub_test CASCADE; +NOTICE: drop cascades to table pub_test.testpub_nopk +RESET SESSION AUTHORIZATION; +DROP ROLE regress_publication_user, regress_publication_user2; +DROP ROLE regress_publication_user_dummy; diff --git a/src/test/regress/expected/yb.port.rangetypes.out b/src/test/regress/expected/yb.port.rangetypes.out new file mode 100644 index 0000000000000..b8a309d23c462 --- /dev/null +++ b/src/test/regress/expected/yb.port.rangetypes.out @@ -0,0 +1,1151 @@ +-- Tests for range data types. +-- +-- test input parser +-- (type textrange was already made in test_setup.sql) +-- +-- negative tests; should fail +select ''::textrange; +ERROR: malformed range literal: "" +LINE 1: select ''::textrange; + ^ +DETAIL: Missing left parenthesis or bracket. +select '-[a,z)'::textrange; +ERROR: malformed range literal: "-[a,z)" +LINE 1: select '-[a,z)'::textrange; + ^ +DETAIL: Missing left parenthesis or bracket. +select '[a,z) - '::textrange; +ERROR: malformed range literal: "[a,z) - " +LINE 1: select '[a,z) - '::textrange; + ^ +DETAIL: Junk after right parenthesis or bracket. +select '(",a)'::textrange; +ERROR: malformed range literal: "(",a)" +LINE 1: select '(",a)'::textrange; + ^ +DETAIL: Unexpected end of input. +select '(,,a)'::textrange; +ERROR: malformed range literal: "(,,a)" +LINE 1: select '(,,a)'::textrange; + ^ +DETAIL: Too many commas. +select '(),a)'::textrange; +ERROR: malformed range literal: "(),a)" +LINE 1: select '(),a)'::textrange; + ^ +DETAIL: Missing comma after lower bound. +select '(a,))'::textrange; +ERROR: malformed range literal: "(a,))" +LINE 1: select '(a,))'::textrange; + ^ +DETAIL: Junk after right parenthesis or bracket. +select '(],a)'::textrange; +ERROR: malformed range literal: "(],a)" +LINE 1: select '(],a)'::textrange; + ^ +DETAIL: Missing comma after lower bound. +select '(a,])'::textrange; +ERROR: malformed range literal: "(a,])" +LINE 1: select '(a,])'::textrange; + ^ +DETAIL: Junk after right parenthesis or bracket. +select '[z,a]'::textrange; +ERROR: range lower bound must be less than or equal to range upper bound +LINE 1: select '[z,a]'::textrange; + ^ +-- should succeed +select ' empty '::textrange; + textrange +----------- + empty +(1 row) + +select ' ( empty, empty ) '::textrange; + textrange +---------------------- + (" empty"," empty ") +(1 row) + +select ' ( " a " " a ", " z " " z " ) '::textrange; + textrange +-------------------------- + (" a a "," z z ") +(1 row) + +select '(a,)'::textrange; + textrange +----------- + (a,) +(1 row) + +select '[,z]'::textrange; + textrange +----------- + (,z] +(1 row) + +select '[a,]'::textrange; + textrange +----------- + [a,) +(1 row) + +select '(,)'::textrange; + textrange +----------- + (,) +(1 row) + +select '[ , ]'::textrange; + textrange +----------- + [" "," "] +(1 row) + +select '["",""]'::textrange; + textrange +----------- + ["",""] +(1 row) + +select '[",",","]'::textrange; + textrange +----------- + [",",","] +(1 row) + +select '["\\","\\"]'::textrange; + textrange +------------- + ["\\","\\"] +(1 row) + +select '(\\,a)'::textrange; + textrange +----------- + ("\\",a) +(1 row) + +select '((,z)'::textrange; + textrange +----------- + ("(",z) +(1 row) + +select '([,z)'::textrange; + textrange +----------- + ("[",z) +(1 row) + +select '(!,()'::textrange; + textrange +----------- + (!,"(") +(1 row) + +select '(!,[)'::textrange; + textrange +----------- + (!,"[") +(1 row) + +select '[a,a]'::textrange; + textrange +----------- + [a,a] +(1 row) + +-- these are allowed but normalize to empty: +select '[a,a)'::textrange; + textrange +----------- + empty +(1 row) + +select '(a,a]'::textrange; + textrange +----------- + empty +(1 row) + +select '(a,a)'::textrange; + textrange +----------- + empty +(1 row) + +-- +-- create some test data and test the operators +-- +CREATE TABLE numrange_test (nr NUMRANGE); +create index numrange_test_btree on numrange_test(nr); +ERROR: INDEX on column of type 'NUMRANGE' not yet supported +INSERT INTO numrange_test VALUES('[,)'); +INSERT INTO numrange_test VALUES('[3,]'); +INSERT INTO numrange_test VALUES('[, 5)'); +INSERT INTO numrange_test VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test VALUES('empty'); +INSERT INTO numrange_test VALUES(numrange(1.7, 1.7, '[]')); +SELECT nr, isempty(nr), lower(nr), upper(nr) FROM numrange_test; + nr | isempty | lower | upper +-----------+---------+-------+------- + (,) | f | | + [3,) | f | 3 | + (,5) | f | | 5 + [1.1,2.2) | f | 1.1 | 2.2 + empty | t | | + [1.7,1.7] | f | 1.7 | 1.7 +(6 rows) + +SELECT nr, lower_inc(nr), lower_inf(nr), upper_inc(nr), upper_inf(nr) FROM numrange_test; + nr | lower_inc | lower_inf | upper_inc | upper_inf +-----------+-----------+-----------+-----------+----------- + (,) | f | t | f | t + [3,) | t | f | f | t + (,5) | f | t | f | f + [1.1,2.2) | t | f | f | f + empty | f | f | f | f + [1.7,1.7] | t | f | t | f +(6 rows) + +SELECT * FROM numrange_test WHERE range_contains(nr, numrange(1.9,1.91)); + nr +----------- + (,) + (,5) + [1.1,2.2) +(3 rows) + +SELECT * FROM numrange_test WHERE nr @> numrange(1.0,10000.1); + nr +----- + (,) +(1 row) + +SELECT * FROM numrange_test WHERE range_contained_by(numrange(-1e7,-10000.1), nr); + nr +------ + (,) + (,5) +(2 rows) + +SELECT * FROM numrange_test WHERE 1.9 <@ nr; + nr +----------- + (,) + (,5) + [1.1,2.2) +(3 rows) + +select * from numrange_test where nr = 'empty'; + nr +------- + empty +(1 row) + +select * from numrange_test where nr = '(1.1, 2.2)'; + nr +---- +(0 rows) + +select * from numrange_test where nr = '[1.1, 2.2)'; + nr +----------- + [1.1,2.2) +(1 row) + +select * from numrange_test where nr < 'empty'; + nr +---- +(0 rows) + +select * from numrange_test where nr < numrange(-1000.0, -1000.0,'[]'); + nr +------- + (,) + (,5) + empty +(3 rows) + +select * from numrange_test where nr < numrange(0.0, 1.0,'[]'); + nr +------- + (,) + (,5) + empty +(3 rows) + +select * from numrange_test where nr < numrange(1000.0, 1001.0,'[]'); + nr +----------- + (,) + [3,) + (,5) + [1.1,2.2) + empty + [1.7,1.7] +(6 rows) + +select * from numrange_test where nr <= 'empty'; + nr +------- + empty +(1 row) + +select * from numrange_test where nr >= 'empty'; + nr +----------- + (,) + [3,) + (,5) + [1.1,2.2) + empty + [1.7,1.7] +(6 rows) + +select * from numrange_test where nr > 'empty'; + nr +----------- + (,) + [3,) + (,5) + [1.1,2.2) + [1.7,1.7] +(5 rows) + +select * from numrange_test where nr > numrange(-1001.0, -1000.0,'[]'); + nr +----------- + [3,) + [1.1,2.2) + [1.7,1.7] +(3 rows) + +select * from numrange_test where nr > numrange(0.0, 1.0,'[]'); + nr +----------- + [3,) + [1.1,2.2) + [1.7,1.7] +(3 rows) + +select * from numrange_test where nr > numrange(1000.0, 1000.0,'[]'); + nr +---- +(0 rows) + +select numrange(2.0, 1.0); +ERROR: range lower bound must be less than or equal to range upper bound +select numrange(2.0, 3.0) -|- numrange(3.0, 4.0); + ?column? +---------- + t +(1 row) + +select range_adjacent(numrange(2.0, 3.0), numrange(3.1, 4.0)); + range_adjacent +---------------- + f +(1 row) + +select range_adjacent(numrange(2.0, 3.0), numrange(3.1, null)); + range_adjacent +---------------- + f +(1 row) + +select numrange(2.0, 3.0, '[]') -|- numrange(3.0, 4.0, '()'); + ?column? +---------- + t +(1 row) + +select numrange(1.0, 2.0) -|- numrange(2.0, 3.0,'[]'); + ?column? +---------- + t +(1 row) + +select range_adjacent(numrange(2.0, 3.0, '(]'), numrange(1.0, 2.0, '(]')); + range_adjacent +---------------- + t +(1 row) + +select numrange(1.1, 3.3) <@ numrange(0.1,10.1); + ?column? +---------- + t +(1 row) + +select numrange(0.1, 10.1) <@ numrange(1.1,3.3); + ?column? +---------- + f +(1 row) + +select numrange(1.1, 2.2) - numrange(2.0, 3.0); + ?column? +----------- + [1.1,2.0) +(1 row) + +select numrange(1.1, 2.2) - numrange(2.2, 3.0); + ?column? +----------- + [1.1,2.2) +(1 row) + +select numrange(1.1, 2.2,'[]') - numrange(2.0, 3.0); + ?column? +----------- + [1.1,2.0) +(1 row) + +select range_minus(numrange(10.1,12.2,'[]'), numrange(110.0,120.2,'(]')); + range_minus +------------- + [10.1,12.2] +(1 row) + +select range_minus(numrange(10.1,12.2,'[]'), numrange(0.0,120.2,'(]')); + range_minus +------------- + empty +(1 row) + +select numrange(4.5, 5.5, '[]') && numrange(5.5, 6.5); + ?column? +---------- + t +(1 row) + +select numrange(1.0, 2.0) << numrange(3.0, 4.0); + ?column? +---------- + t +(1 row) + +select numrange(1.0, 3.0,'[]') << numrange(3.0, 4.0,'[]'); + ?column? +---------- + f +(1 row) + +select numrange(1.0, 3.0,'()') << numrange(3.0, 4.0,'()'); + ?column? +---------- + t +(1 row) + +select numrange(1.0, 2.0) >> numrange(3.0, 4.0); + ?column? +---------- + f +(1 row) + +select numrange(3.0, 70.0) &< numrange(6.6, 100.0); + ?column? +---------- + t +(1 row) + +select numrange(1.1, 2.2) < numrange(1.0, 200.2); + ?column? +---------- + f +(1 row) + +select numrange(1.1, 2.2) < numrange(1.1, 1.2); + ?column? +---------- + f +(1 row) + +select numrange(1.0, 2.0) + numrange(2.0, 3.0); + ?column? +----------- + [1.0,3.0) +(1 row) + +select numrange(1.0, 2.0) + numrange(1.5, 3.0); + ?column? +----------- + [1.0,3.0) +(1 row) + +select numrange(1.0, 2.0) + numrange(2.5, 3.0); -- should fail +ERROR: result of range union would not be contiguous +select range_merge(numrange(1.0, 2.0), numrange(2.0, 3.0)); + range_merge +------------- + [1.0,3.0) +(1 row) + +select range_merge(numrange(1.0, 2.0), numrange(1.5, 3.0)); + range_merge +------------- + [1.0,3.0) +(1 row) + +select range_merge(numrange(1.0, 2.0), numrange(2.5, 3.0)); -- shouldn't fail + range_merge +------------- + [1.0,3.0) +(1 row) + +select numrange(1.0, 2.0) * numrange(2.0, 3.0); + ?column? +---------- + empty +(1 row) + +select numrange(1.0, 2.0) * numrange(1.5, 3.0); + ?column? +----------- + [1.5,2.0) +(1 row) + +select numrange(1.0, 2.0) * numrange(2.5, 3.0); + ?column? +---------- + empty +(1 row) + +select range_intersect_agg(nr) from numrange_test; + range_intersect_agg +--------------------- + empty +(1 row) + +select range_intersect_agg(nr) from numrange_test where false; + range_intersect_agg +--------------------- + +(1 row) + +select range_intersect_agg(nr) from numrange_test where nr @> 4.0; + range_intersect_agg +--------------------- + [3,5) +(1 row) + +analyze numrange_test; +create table numrange_test2(nr numrange); +create index numrange_test2_hash_idx on numrange_test2 using hash (nr); +NOTICE: substituting access method "lsm" for "hash" in YugabyteDB +ERROR: INDEX on column of type 'NUMRANGE' not yet supported +INSERT INTO numrange_test2 VALUES('[, 5)'); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2,'()')); +INSERT INTO numrange_test2 VALUES('empty'); +select * from numrange_test2 where nr = 'empty'::numrange; + nr +------- + empty +(1 row) + +select * from numrange_test2 where nr = numrange(1.1, 2.2); + nr +----------- + [1.1,2.2) + [1.1,2.2) +(2 rows) + +select * from numrange_test2 where nr = numrange(1.1, 2.3); + nr +---- +(0 rows) + +set enable_nestloop=t; +set enable_hashjoin=f; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; + nr +----------- + empty + (,5) + [1.1,2.2) + [1.1,2.2) +(4 rows) + +set enable_nestloop=f; +set enable_hashjoin=t; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; + nr +----------- + empty + (,5) + [1.1,2.2) + [1.1,2.2) +(4 rows) + +set enable_nestloop=f; +set enable_hashjoin=f; +set enable_mergejoin=t; +select * from numrange_test natural join numrange_test2 order by nr; + nr +----------- + empty + (,5) + [1.1,2.2) + [1.1,2.2) +(4 rows) + +set enable_nestloop to default; +set enable_hashjoin to default; +set enable_mergejoin to default; +-- keep numrange_test around to help exercise dump/reload +DROP TABLE numrange_test2; +-- +-- Apply a subset of the above tests on a collatable type, too +-- +CREATE TABLE textrange_test (tr textrange); +create index textrange_test_btree on textrange_test(tr); +ERROR: INDEX on column of type 'user_defined_type' not yet supported +INSERT INTO textrange_test VALUES('[,)'); +INSERT INTO textrange_test VALUES('["a",]'); +INSERT INTO textrange_test VALUES('[,"q")'); +INSERT INTO textrange_test VALUES(textrange('b', 'g')); +INSERT INTO textrange_test VALUES('empty'); +INSERT INTO textrange_test VALUES(textrange('d', 'd', '[]')); +SELECT tr, isempty(tr), lower(tr), upper(tr) FROM textrange_test; + tr | isempty | lower | upper +-------+---------+-------+------- + (,) | f | | + [a,) | f | a | + (,q) | f | | q + [b,g) | f | b | g + empty | t | | + [d,d] | f | d | d +(6 rows) + +SELECT tr, lower_inc(tr), lower_inf(tr), upper_inc(tr), upper_inf(tr) FROM textrange_test; + tr | lower_inc | lower_inf | upper_inc | upper_inf +-------+-----------+-----------+-----------+----------- + (,) | f | t | f | t + [a,) | t | f | f | t + (,q) | f | t | f | f + [b,g) | t | f | f | f + empty | f | f | f | f + [d,d] | t | f | t | f +(6 rows) + +SELECT * FROM textrange_test WHERE range_contains(tr, textrange('f', 'fx')); + tr +------- + (,) + [a,) + (,q) + [b,g) +(4 rows) + +SELECT * FROM textrange_test WHERE tr @> textrange('a', 'z'); + tr +------ + (,) + [a,) +(2 rows) + +SELECT * FROM textrange_test WHERE range_contained_by(textrange('0','9'), tr); + tr +------ + (,) + (,q) +(2 rows) + +SELECT * FROM textrange_test WHERE 'e'::text <@ tr; + tr +------- + (,) + [a,) + (,q) + [b,g) +(4 rows) + +select * from textrange_test where tr = 'empty'; + tr +------- + empty +(1 row) + +select * from textrange_test where tr = '("b","g")'; + tr +---- +(0 rows) + +select * from textrange_test where tr = '["b","g")'; + tr +------- + [b,g) +(1 row) + +select * from textrange_test where tr < 'empty'; + tr +---- +(0 rows) + +-- test canonical form for int4range +select int4range(1, 10, '[]'); + int4range +----------- + [1,11) +(1 row) + +select int4range(1, 10, '[)'); + int4range +----------- + [1,10) +(1 row) + +select int4range(1, 10, '(]'); + int4range +----------- + [2,11) +(1 row) + +select int4range(1, 10, '()'); + int4range +----------- + [2,10) +(1 row) + +select int4range(1, 2, '()'); + int4range +----------- + empty +(1 row) + +-- test canonical form for daterange +select daterange('2000-01-10'::date, '2000-01-20'::date, '[]'); + daterange +------------------------- + [01-10-2000,01-21-2000) +(1 row) + +select daterange('2000-01-10'::date, '2000-01-20'::date, '[)'); + daterange +------------------------- + [01-10-2000,01-20-2000) +(1 row) + +select daterange('2000-01-10'::date, '2000-01-20'::date, '(]'); + daterange +------------------------- + [01-11-2000,01-21-2000) +(1 row) + +select daterange('2000-01-10'::date, '2000-01-20'::date, '()'); + daterange +------------------------- + [01-11-2000,01-20-2000) +(1 row) + +select daterange('2000-01-10'::date, '2000-01-11'::date, '()'); + daterange +----------- + empty +(1 row) + +select daterange('2000-01-10'::date, '2000-01-11'::date, '(]'); + daterange +------------------------- + [01-11-2000,01-12-2000) +(1 row) + +select daterange('-infinity'::date, '2000-01-01'::date, '()'); + daterange +------------------------ + (-infinity,01-01-2000) +(1 row) + +select daterange('-infinity'::date, '2000-01-01'::date, '[)'); + daterange +------------------------ + [-infinity,01-01-2000) +(1 row) + +select daterange('2000-01-01'::date, 'infinity'::date, '[)'); + daterange +----------------------- + [01-01-2000,infinity) +(1 row) + +select daterange('2000-01-01'::date, 'infinity'::date, '[]'); + daterange +----------------------- + [01-01-2000,infinity] +(1 row) + +-- test GiST index that's been built incrementally +create table test_range_gist(ir int4range); +create index test_range_gist_idx on test_range_gist using gist (ir); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +-- test SP-GiST index that's been built incrementally +create table test_range_spgist(ir int4range); +create index test_range_spgist_idx on test_range_spgist using spgist (ir); +ERROR: index method "spgist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +-- test elem <@ range operator +create table test_range_elem(i int4); +create index test_range_elem_idx on test_range_elem (i); +insert into test_range_elem select i from generate_series(1,100) i; +SET enable_seqscan = f; +select count(*) from test_range_elem where i <@ int4range(10,50); + count +------- + 40 +(1 row) + +-- also test spgist index on anyrange expression +create index on test_range_elem using spgist(int4range(i,i+10)); +ERROR: index method "spgist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +explain (costs off) +select count(*) from test_range_elem where int4range(i,i+10) <@ int4range(10,30); + QUERY PLAN +-------------------------------------------------------------------- + Aggregate + -> Index Only Scan using test_range_elem_idx on test_range_elem + Filter: (int4range(i, (i + 10)) <@ '[10,30)'::int4range) +(3 rows) + +select count(*) from test_range_elem where int4range(i,i+10) <@ int4range(10,30); + count +------- + 11 +(1 row) + +RESET enable_seqscan; +drop table test_range_elem; +-- +-- Btree_gist is not included by default, so to test exclusion +-- constraints with range types, use singleton int ranges for the "=" +-- portion of the constraint. +-- +create table test_range_excl( + room int4range, + speaker int4range, + during tsrange, + exclude using gist (room with =, during with &&), + exclude using gist (speaker with =, during with &&) +); +ERROR: EXCLUDE constraint not supported yet +LINE 5: exclude using gist (room with =, during with &&), + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/3944. React with thumbs up to raise its priority +-- test bigint ranges +select int8range(10000000000::int8, 20000000000::int8,'(]'); + int8range +--------------------------- + [10000000001,20000000001) +(1 row) + +-- test tstz ranges +set timezone to '-08'; +select '[2010-01-01 01:00:00 -05, 2010-01-01 02:00:00 -08)'::tstzrange; + tstzrange +----------------------------------------------------------------- + ["Thu Dec 31 22:00:00 2009 -08","Fri Jan 01 02:00:00 2010 -08") +(1 row) + +-- should fail +select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)'::tstzrange; +ERROR: range lower bound must be less than or equal to range upper bound +LINE 1: select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)':... + ^ +set timezone to default; +-- +-- Test user-defined range of floats +-- (type float8range was already made in test_setup.sql) +-- +--should fail +create type bogus_float8range as range (subtype=float8, subtype_diff=float4mi); +ERROR: function float4mi(double precision, double precision) does not exist +select '[123.001, 5.e9)'::float8range @> 888.882::float8; + ?column? +---------- + t +(1 row) + +create table float8range_test(f8r float8range, i int); +insert into float8range_test values(float8range(-100.00007, '1.111113e9'), 42); +select * from float8range_test; + f8r | i +-------------------------+---- + [-100.00007,1111113000) | 42 +(1 row) + +drop table float8range_test; +-- +-- Test range types over domains +-- +create domain mydomain as int4; +create type mydomainrange as range(subtype=mydomain); +select '[4,50)'::mydomainrange @> 7::mydomain; + ?column? +---------- + t +(1 row) + +drop domain mydomain; -- fail +ERROR: cannot drop type mydomain because other objects depend on it +DETAIL: type mydomainrange depends on type mydomain +HINT: Use DROP ... CASCADE to drop the dependent objects too. +drop domain mydomain cascade; +NOTICE: drop cascades to type mydomainrange +-- +-- Test domains over range types +-- +create domain restrictedrange as int4range check (upper(value) < 10); +select '[4,5)'::restrictedrange @> 7; + ?column? +---------- + f +(1 row) + +select '[4,50)'::restrictedrange @> 7; -- should fail +ERROR: value for domain restrictedrange violates check constraint "restrictedrange_check" +drop domain restrictedrange; +-- +-- Test multiple range types over the same subtype +-- +create type textrange1 as range(subtype=text, collation="C"); +create type textrange2 as range(subtype=text, collation="C"); +select textrange1('a','Z') @> 'b'::text; +ERROR: range lower bound must be less than or equal to range upper bound +select textrange2('a','z') @> 'b'::text; + ?column? +---------- + t +(1 row) + +drop type textrange1; +drop type textrange2; +-- +-- Test polymorphic type system +-- +create function anyarray_anyrange_func(a anyarray, r anyrange) + returns anyelement as 'select $1[1] + lower($2);' language sql; +select anyarray_anyrange_func(ARRAY[1,2], int4range(10,20)); + anyarray_anyrange_func +------------------------ + 11 +(1 row) + +-- should fail +select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20)); +ERROR: function anyarray_anyrange_func(integer[], numrange) does not exist +LINE 1: select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20)); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +drop function anyarray_anyrange_func(anyarray, anyrange); +-- should fail +create function bogus_func(anyelement) + returns anyrange as 'select int4range(1,10)' language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anyrange requires at least one input of type anyrange or anymultirange. +-- should fail +create function bogus_func(int) + returns anyrange as 'select int4range(1,10)' language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anyrange requires at least one input of type anyrange or anymultirange. +create function range_add_bounds(anyrange) + returns anyelement as 'select lower($1) + upper($1)' language sql; +select range_add_bounds(int4range(1, 17)); + range_add_bounds +------------------ + 18 +(1 row) + +select range_add_bounds(numrange(1.0001, 123.123)); + range_add_bounds +------------------ + 124.1231 +(1 row) + +create function rangetypes_sql(q anyrange, b anyarray, out c anyelement) + as $$ select upper($1) + $2[1] $$ + language sql; +select rangetypes_sql(int4range(1,10), ARRAY[2,20]); + rangetypes_sql +---------------- + 12 +(1 row) + +select rangetypes_sql(numrange(1,10), ARRAY[2,20]); -- match failure +ERROR: function rangetypes_sql(numrange, integer[]) does not exist +LINE 1: select rangetypes_sql(numrange(1,10), ARRAY[2,20]); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +create function anycompatiblearray_anycompatiblerange_func(a anycompatiblearray, r anycompatiblerange) + returns anycompatible as 'select $1[1] + lower($2);' language sql; +select anycompatiblearray_anycompatiblerange_func(ARRAY[1,2], int4range(10,20)); + anycompatiblearray_anycompatiblerange_func +-------------------------------------------- + 11 +(1 row) + +select anycompatiblearray_anycompatiblerange_func(ARRAY[1,2], numrange(10,20)); + anycompatiblearray_anycompatiblerange_func +-------------------------------------------- + 11 +(1 row) + +-- should fail +select anycompatiblearray_anycompatiblerange_func(ARRAY[1.1,2], int4range(10,20)); +ERROR: function anycompatiblearray_anycompatiblerange_func(numeric[], int4range) does not exist +LINE 1: select anycompatiblearray_anycompatiblerange_func(ARRAY[1.1,... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, anycompatiblerange); +-- should fail +create function bogus_func(anycompatible) + returns anycompatiblerange as 'select int4range(1,10)' language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange. +-- +-- Arrays of ranges +-- +select ARRAY[numrange(1.1, 1.2), numrange(12.3, 155.5)]; + array +------------------------------ + {"[1.1,1.2)","[12.3,155.5)"} +(1 row) + +create table i8r_array (f1 int, f2 int8range[]); +insert into i8r_array values (42, array[int8range(1,10), int8range(2,20)]); +select * from i8r_array; + f1 | f2 +----+--------------------- + 42 | {"[1,10)","[2,20)"} +(1 row) + +drop table i8r_array; +-- +-- Ranges of arrays +-- +create type arrayrange as range (subtype=int4[]); +select arrayrange(ARRAY[1,2], ARRAY[2,1]); + arrayrange +------------------- + ["{1,2}","{2,1}") +(1 row) + +select arrayrange(ARRAY[2,1], ARRAY[1,2]); -- fail +ERROR: range lower bound must be less than or equal to range upper bound +select array[1,1] <@ arrayrange(array[1,2], array[2,1]); + ?column? +---------- + f +(1 row) + +select array[1,3] <@ arrayrange(array[1,2], array[2,1]); + ?column? +---------- + t +(1 row) + +-- +-- Ranges of composites +-- +create type two_ints as (a int, b int); +create type two_ints_range as range (subtype = two_ints); +-- with force_parallel_mode on, this exercises tqueue.c's range remapping +select *, row_to_json(upper(t)) as u from + (values (two_ints_range(row(1,2), row(3,4))), + (two_ints_range(row(5,6), row(7,8)))) v(t); + t | u +-------------------+--------------- + ["(1,2)","(3,4)") | {"a":3,"b":4} + ["(5,6)","(7,8)") | {"a":7,"b":8} +(2 rows) + +-- this must be rejected to avoid self-inclusion issues: +alter type two_ints add attribute c two_ints_range; +ERROR: ALTER TYPE ADD ATTRIBUTE not supported yet +LINE 1: alter type two_ints add attribute c two_ints_range; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1893. React with thumbs up to raise its priority +drop type two_ints cascade; +NOTICE: drop cascades to type two_ints_range +-- +-- Check behavior when subtype lacks a hash function +-- +create type cashrange as range (subtype = money); +set enable_sort = off; -- try to make it pick a hash setop implementation +select '(2,5)'::cashrange except select '(5,6)'::cashrange; + cashrange +--------------- + ($2.00,$5.00) +(1 row) + +reset enable_sort; +-- +-- OUT/INOUT/TABLE functions +-- +-- infer anyrange from anyrange +create function outparam_succeed(i anyrange, out r anyrange, out t text) + as $$ select $1, 'foo'::text $$ language sql; +select * from outparam_succeed(int4range(1,2)); + r | t +-------+----- + [1,2) | foo +(1 row) + +create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray) + as $$ select array[lower($1), upper($1)], array[upper($1), lower($1)] $$ + language sql; +select * from outparam2_succeed(int4range(1,11)); + lu | ul +--------+-------- + {1,11} | {11,1} +(1 row) + +-- infer anyarray from anyrange +create function outparam_succeed2(i anyrange, out r anyarray, out t text) + as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql; +select * from outparam_succeed2(int4range(int4range(1,2))); + r | t +-----+----- + {2} | foo +(1 row) + +-- infer anyelement from anyrange +create function inoutparam_succeed(out i anyelement, inout r anyrange) + as $$ select upper($1), $1 $$ language sql; +select * from inoutparam_succeed(int4range(1,2)); + i | r +---+------- + 2 | [1,2) +(1 row) + +create function table_succeed(r anyrange) + returns table(l anyelement, u anyelement) + as $$ select lower($1), upper($1) $$ + language sql; +select * from table_succeed(int4range(1,11)); + l | u +---+---- + 1 | 11 +(1 row) + +-- should fail +create function outparam_fail(i anyelement, out r anyrange, out t text) + as $$ select '[1,10]', 'foo' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anyrange requires at least one input of type anyrange or anymultirange. +--should fail +create function inoutparam_fail(inout i anyelement, out r anyrange) + as $$ select $1, '[1,10]' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anyrange requires at least one input of type anyrange or anymultirange. +--should fail +create function table_fail(i anyelement) returns table(i anyelement, r anyrange) + as $$ select $1, '[1,10]' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A result of type anyrange requires at least one input of type anyrange or anymultirange. diff --git a/src/test/regress/expected/yb.port.replica_identity.out b/src/test/regress/expected/yb.port.replica_identity.out new file mode 100644 index 0000000000000..297e5a3130fba --- /dev/null +++ b/src/test/regress/expected/yb.port.replica_identity.out @@ -0,0 +1,89 @@ +-- YB note: deferable unique constraints and OIDs are not supported for user tables +CREATE TABLE test_replica_identity ( + id serial primary key, + keya text not null, + keyb text not null, + nonkey text, + -- CONSTRAINT test_replica_identity_unique_defer UNIQUE (keya, keyb) DEFERRABLE, + CONSTRAINT test_replica_identity_unique_nondefer UNIQUE (keya, keyb) +);-- WITH OIDS; +CREATE INDEX test_replica_identity_keyab ON test_replica_identity (keya, keyb); +-- default is 'c' (CHANGE) for user created tables +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; + relreplident +-------------- + c +(1 row) + +-- but 'none' for system tables +SELECT relreplident FROM pg_class WHERE oid = 'pg_class'::regclass; + relreplident +-------------- + n +(1 row) + +SELECT relreplident FROM pg_class WHERE oid = 'pg_constraint'::regclass; + relreplident +-------------- + n +(1 row) + +-- YB Note: REPLICA IDENTITY mode INDEX is not supported yet, we will assert the failure message +-- here. Other queries for testing INDEX mode should be ported whenever the support is added. +ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab; +ERROR: ALTER TABLE REPLICA IDENTITY USING INDEX not supported yet +LINE 1: ...LTER TABLE test_replica_identity REPLICA IDENTITY USING INDE... + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1124. React with thumbs up to raise its priority +-- YB Note: Since REPLICA IDENTITY mode INDEX is not supported yet, this will be 'c' (CHANGE) +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; + relreplident +-------------- + c +(1 row) + +---- +-- Make sure non index cases work +---- +ALTER TABLE test_replica_identity REPLICA IDENTITY DEFAULT; +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; + relreplident +-------------- + d +(1 row) + +SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident; + count +------- + 0 +(1 row) + +ALTER TABLE test_replica_identity REPLICA IDENTITY FULL; +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; + relreplident +-------------- + f +(1 row) + +\d+ test_replica_identity + Table "public.test_replica_identity" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------------------------------------------------+----------+--------------+------------- + id | integer | | not null | nextval('test_replica_identity_id_seq'::regclass) | plain | | + keya | text | | not null | | extended | | + keyb | text | | not null | | extended | | + nonkey | text | | | | extended | | +Indexes: + "test_replica_identity_pkey" PRIMARY KEY, lsm (id HASH) + "test_replica_identity_keyab" lsm (keya HASH, keyb ASC) + "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, lsm (keya HASH, keyb ASC) +Replica Identity: FULL + +ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING; +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; + relreplident +-------------- + n +(1 row) + +DROP TABLE test_replica_identity; diff --git a/src/test/regress/expected/yb.port.rowsecurity.out b/src/test/regress/expected/yb.port.rowsecurity.out new file mode 100644 index 0000000000000..f16c4283e15e7 --- /dev/null +++ b/src/test/regress/expected/yb.port.rowsecurity.out @@ -0,0 +1,4391 @@ +-- +-- Test of Row-level security feature +-- +-- Clean up in case a prior regression run failed +-- Suppress NOTICE messages when users/groups don't exist +SET client_min_messages TO 'warning'; +DROP USER IF EXISTS regress_rls_alice; +DROP USER IF EXISTS regress_rls_bob; +DROP USER IF EXISTS regress_rls_carol; +DROP USER IF EXISTS regress_rls_dave; +DROP USER IF EXISTS regress_rls_exempt_user; +DROP ROLE IF EXISTS regress_rls_group1; +DROP ROLE IF EXISTS regress_rls_group2; +DROP SCHEMA IF EXISTS regress_rls_schema CASCADE; +RESET client_min_messages; +-- initial setup +CREATE USER regress_rls_alice NOLOGIN; +CREATE USER regress_rls_bob NOLOGIN; +CREATE USER regress_rls_carol NOLOGIN; +CREATE USER regress_rls_dave NOLOGIN; +CREATE USER regress_rls_exempt_user BYPASSRLS NOLOGIN; +CREATE ROLE regress_rls_group1 NOLOGIN; +CREATE ROLE regress_rls_group2 NOLOGIN; +GRANT regress_rls_group1 TO regress_rls_bob; +GRANT regress_rls_group2 TO regress_rls_carol; +CREATE SCHEMA regress_rls_schema; +GRANT ALL ON SCHEMA regress_rls_schema to public; +SET search_path = regress_rls_schema; +-- setup of malicious function +CREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool + COST 0.0000001 LANGUAGE plpgsql + AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END'; +GRANT EXECUTE ON FUNCTION f_leak(text) TO public; +-- BASIC Row-Level Security Scenario +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE uaccount ( + pguser name primary key, + seclv int +); +GRANT SELECT ON uaccount TO public; +INSERT INTO uaccount VALUES + ('regress_rls_alice', 99), + ('regress_rls_bob', 1), + ('regress_rls_carol', 2), + ('regress_rls_dave', 3); +CREATE TABLE category ( + cid int primary key, + cname text +); +GRANT ALL ON category TO public; +INSERT INTO category VALUES + (11, 'novel'), + (22, 'science fiction'), + (33, 'technology'), + (44, 'manga'); +CREATE TABLE document ( + did int primary key, + cid int references category(cid), + dlevel int not null, + dauthor name, + dtitle text +); +GRANT ALL ON document TO public; +INSERT INTO document VALUES + ( 1, 11, 1, 'regress_rls_bob', 'my first novel'), + ( 2, 11, 2, 'regress_rls_bob', 'my second novel'), + ( 3, 22, 2, 'regress_rls_bob', 'my science fiction'), + ( 4, 44, 1, 'regress_rls_bob', 'my first manga'), + ( 5, 44, 2, 'regress_rls_bob', 'my second manga'), + ( 6, 22, 1, 'regress_rls_carol', 'great science fiction'), + ( 7, 33, 2, 'regress_rls_carol', 'great technology book'), + ( 8, 44, 1, 'regress_rls_carol', 'great manga'), + ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'), + (10, 33, 2, 'regress_rls_dave', 'awesome technology book'); +ALTER TABLE document ENABLE ROW LEVEL SECURITY; +-- user's security level must be higher than or equal to document's +CREATE POLICY p1 ON document AS PERMISSIVE + USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user)); +-- try to create a policy of bogus type +CREATE POLICY p1 ON document AS UGLY + USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user)); +ERROR: unrecognized row security option "ugly" +LINE 1: CREATE POLICY p1 ON document AS UGLY + ^ +HINT: Only PERMISSIVE or RESTRICTIVE policies are supported currently. +-- but Dave isn't allowed to anything at cid 50 or above +-- this is to make sure that we sort the policies by name first +-- when applying WITH CHECK, a later INSERT by Dave should fail due +-- to p1r first +CREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave + USING (cid <> 44 AND cid < 50); +-- and Dave isn't allowed to see manga documents +CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave + USING (cid <> 44); +\dp + Access privileges + Schema | Name | Type | Access privileges | Column privileges | Policies +--------------------+----------+-------+---------------------------------------------+-------------------+-------------------------------------------- + regress_rls_schema | category | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | + | | | =arwdDxt/regress_rls_alice | | + regress_rls_schema | document | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | p1: + + | | | =arwdDxt/regress_rls_alice | | (u): (dlevel <= ( SELECT uaccount.seclv + + | | | | | FROM uaccount + + | | | | | WHERE (uaccount.pguser = CURRENT_USER)))+ + | | | | | p2r (RESTRICTIVE): + + | | | | | (u): ((cid <> 44) AND (cid < 50)) + + | | | | | to: regress_rls_dave + + | | | | | p1r (RESTRICTIVE): + + | | | | | (u): (cid <> 44) + + | | | | | to: regress_rls_dave + regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | + | | | =r/regress_rls_alice | | +(3 rows) + +\d document + Table "regress_rls_schema.document" + Column | Type | Collation | Nullable | Default +---------+---------+-----------+----------+--------- + did | integer | | not null | + cid | integer | | | + dlevel | integer | | not null | + dauthor | name | | | + dtitle | text | | | +Indexes: + "document_pkey" PRIMARY KEY, lsm (did ASC) +Foreign-key constraints: + "document_cid_fkey" FOREIGN KEY (cid) REFERENCES category(cid) +Policies: + POLICY "p1" + USING ((dlevel <= ( SELECT uaccount.seclv + FROM uaccount + WHERE (uaccount.pguser = CURRENT_USER)))) + POLICY "p1r" AS RESTRICTIVE + TO regress_rls_dave + USING ((cid <> 44)) + POLICY "p2r" AS RESTRICTIVE + TO regress_rls_dave + USING (((cid <> 44) AND (cid < 50))) + +SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename = 'document' ORDER BY policyname; + schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check +--------------------+-----------+------------+-------------+--------------------+-----+--------------------------------------------+------------ + regress_rls_schema | document | p1 | PERMISSIVE | {public} | ALL | (dlevel <= ( SELECT uaccount.seclv +| + | | | | | | FROM uaccount +| + | | | | | | WHERE (uaccount.pguser = CURRENT_USER))) | + regress_rls_schema | document | p1r | RESTRICTIVE | {regress_rls_dave} | ALL | (cid <> 44) | + regress_rls_schema | document | p2r | RESTRICTIVE | {regress_rls_dave} | ALL | ((cid <> 44) AND (cid < 50)) | +(3 rows) + +-- viewpoint from regress_rls_bob +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my first manga +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great manga +NOTICE: f_leak => awesome science fiction + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 4 | 44 | 1 | regress_rls_bob | my first manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction +(5 rows) + +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my first manga +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great manga +NOTICE: f_leak => awesome science fiction + cid | did | dlevel | dauthor | dtitle | cname +-----+-----+--------+-------------------+-------------------------+----------------- + 11 | 1 | 1 | regress_rls_bob | my first novel | novel + 44 | 4 | 1 | regress_rls_bob | my first manga | manga + 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction + 44 | 8 | 1 | regress_rls_carol | great manga | manga + 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction +(5 rows) + +-- try a sampled version +SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0) + WHERE f_leak(dtitle) ORDER BY did; +ERROR: 'TABLESAMPLE' clause is not yet supported by YugaByte +-- viewpoint from regress_rls_carol +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => my first manga +NOTICE: f_leak => my second manga +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => great manga +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => awesome technology book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book +(10 rows) + +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => my first manga +NOTICE: f_leak => my second manga +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => great manga +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => awesome technology book + cid | did | dlevel | dauthor | dtitle | cname +-----+-----+--------+-------------------+-------------------------+----------------- + 11 | 1 | 1 | regress_rls_bob | my first novel | novel + 11 | 2 | 2 | regress_rls_bob | my second novel | novel + 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction + 44 | 4 | 1 | regress_rls_bob | my first manga | manga + 44 | 5 | 2 | regress_rls_bob | my second manga | manga + 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction + 33 | 7 | 2 | regress_rls_carol | great technology book | technology + 44 | 8 | 1 | regress_rls_carol | great manga | manga + 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction + 33 | 10 | 2 | regress_rls_dave | awesome technology book | technology +(10 rows) + +-- try a sampled version +SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0) + WHERE f_leak(dtitle) ORDER BY did; +ERROR: 'TABLESAMPLE' clause is not yet supported by YugaByte +EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); + QUERY PLAN +---------------------------------------------------- + Seq Scan on document + Storage Filter: (dlevel <= $0) + Filter: f_leak(dtitle) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); + QUERY PLAN +--------------------------------------------------------------------------- + YB Batched Nested Loop Join + Join Filter: (document.cid = category.cid) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) + -> Seq Scan on document + Storage Filter: (dlevel <= $0) + Filter: f_leak(dtitle) + -> Index Scan using category_pkey on category + Index Cond: (cid = ANY (ARRAY[document.cid, $2, $3, ..., $1024])) +(10 rows) + +-- viewpoint from regress_rls_dave +SET SESSION AUTHORIZATION regress_rls_dave; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => awesome technology book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book +(7 rows) + +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => awesome technology book + cid | did | dlevel | dauthor | dtitle | cname +-----+-----+--------+-------------------+-------------------------+----------------- + 11 | 1 | 1 | regress_rls_bob | my first novel | novel + 11 | 2 | 2 | regress_rls_bob | my second novel | novel + 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction + 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction + 33 | 7 | 2 | regress_rls_carol | great technology book | technology + 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction + 33 | 10 | 2 | regress_rls_dave | awesome technology book | technology +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); + QUERY PLAN +----------------------------------------------------------------------------------- + Seq Scan on document + Storage Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0)) + Filter: f_leak(dtitle) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); + QUERY PLAN +----------------------------------------------------------------------------------------- + YB Batched Nested Loop Join + Join Filter: (document.cid = category.cid) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) + -> Seq Scan on document + Storage Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0)) + Filter: f_leak(dtitle) + -> Index Scan using category_pkey on category + Index Cond: (cid = ANY (ARRAY[document.cid, $2, $3, ..., $1024])) +(10 rows) + +-- 44 would technically fail for both p2r and p1r, but we should get an error +-- back from p1r for this because it sorts first +INSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail +ERROR: new row violates row-level security policy "p1r" for table "document" +-- Just to see a p2r error +INSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail +ERROR: new row violates row-level security policy "p2r" for table "document" +-- only owner can change policies +ALTER POLICY p1 ON document USING (true); --fail +ERROR: must be owner of table document +DROP POLICY p1 ON document; --fail +ERROR: must be owner of relation document +SET SESSION AUTHORIZATION regress_rls_alice; +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; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => my first manga +NOTICE: f_leak => my second manga + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+-------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga +(5 rows) + +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => my first manga +NOTICE: f_leak => my second manga + cid | did | dlevel | dauthor | dtitle | cname +-----+-----+--------+-----------------+--------------------+----------------- + 11 | 1 | 1 | regress_rls_bob | my first novel | novel + 11 | 2 | 2 | regress_rls_bob | my second novel | novel + 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction + 44 | 4 | 1 | regress_rls_bob | my first manga | manga + 44 | 5 | 2 | regress_rls_bob | my second manga | manga +(5 rows) + +-- viewpoint from rls_regres_carol again +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => great manga + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------- + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga +(3 rows) + +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did; +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => great manga + cid | did | dlevel | dauthor | dtitle | cname +-----+-----+--------+-------------------+-----------------------+----------------- + 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction + 33 | 7 | 2 | regress_rls_carol | great technology book | technology + 44 | 8 | 1 | regress_rls_carol | great manga | manga +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); + QUERY PLAN +--------------------------------------------------------- + Seq Scan on document + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); + QUERY PLAN +--------------------------------------------------------------------------- + YB Batched Nested Loop Join + Join Filter: (document.cid = category.cid) + -> Seq Scan on document + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) + -> Index Scan using category_pkey on category + Index Cond: (cid = ANY (ARRAY[document.cid, $1, $2, ..., $1023])) +(6 rows) + +-- interaction of FK/PK constraints +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE POLICY p2 ON category + USING (CASE WHEN current_user = 'regress_rls_bob' THEN cid IN (11, 33) + WHEN current_user = 'regress_rls_carol' THEN cid IN (22, 44) + ELSE false END); +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; + did | cid | dlevel | dauthor | dtitle | cid | cname +-----+-----+--------+-----------------+--------------------+-----+------------ + 1 | 11 | 1 | regress_rls_bob | my first novel | 11 | novel + 2 | 11 | 2 | regress_rls_bob | my second novel | 11 | novel + 3 | 22 | 2 | regress_rls_bob | my science fiction | | + 4 | 44 | 1 | regress_rls_bob | my first manga | | + 5 | 44 | 2 | regress_rls_bob | my second manga | | + | | | | | 33 | technology +(6 rows) + +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". +-- 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; + did | cid | dlevel | dauthor | dtitle | cid | cname +-----+-----+--------+-------------------+-----------------------+-----+----------------- + 6 | 22 | 1 | regress_rls_carol | great science fiction | 22 | science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book | | + 8 | 44 | 1 | regress_rls_carol | great manga | 44 | manga +(3 rows) + +INSERT INTO document VALUES (11, 33, 1, current_user, 'hoge'); +-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row +SET SESSION AUTHORIZATION regress_rls_bob; +INSERT INTO document VALUES (8, 44, 1, 'regress_rls_bob', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see +ERROR: duplicate key value violates unique constraint "document_pkey" +SELECT * FROM document WHERE did = 8; -- and confirm we can't see it + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+---------+-------- +(0 rows) + +-- RLS policies are checked before constraints +INSERT INTO document VALUES (8, 44, 1, 'regress_rls_carol', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation +ERROR: new row violates row-level security policy for table "document" +UPDATE document SET did = 8, dauthor = 'regress_rls_carol' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation +ERROR: new row violates row-level security policy for table "document" +-- database superuser does bypass RLS policy when enabled +RESET SESSION AUTHORIZATION; +SET row_security TO ON; +SELECT * FROM document; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book + 11 | 33 | 1 | regress_rls_carol | hoge +(11 rows) + +SELECT * FROM category; + cid | cname +-----+----------------- + 11 | novel + 22 | science fiction + 33 | technology + 44 | manga +(4 rows) + +-- database superuser does bypass RLS policy when disabled +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM document; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book + 11 | 33 | 1 | regress_rls_carol | hoge +(11 rows) + +SELECT * FROM category; + cid | cname +-----+----------------- + 11 | novel + 22 | science fiction + 33 | technology + 44 | manga +(4 rows) + +-- database non-superuser with bypass privilege can bypass RLS policy when disabled +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO OFF; +SELECT * FROM document; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book + 11 | 33 | 1 | regress_rls_carol | hoge +(11 rows) + +SELECT * FROM category; + cid | cname +-----+----------------- + 11 | novel + 22 | science fiction + 33 | technology + 44 | manga +(4 rows) + +-- RLS policy does not apply to table owner when RLS enabled. +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +SELECT * FROM document; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book + 11 | 33 | 1 | regress_rls_carol | hoge +(11 rows) + +SELECT * FROM category; + cid | cname +-----+----------------- + 11 | novel + 22 | science fiction + 33 | technology + 44 | manga +(4 rows) + +-- RLS policy does not apply to table owner when RLS disabled. +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO OFF; +SELECT * FROM document; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book + 11 | 33 | 1 | regress_rls_carol | hoge +(11 rows) + +SELECT * FROM category; + cid | cname +-----+----------------- + 11 | novel + 22 | science fiction + 33 | technology + 44 | manga +(4 rows) + +-- +-- Table inheritance and RLS policy +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +CREATE TABLE t1 (id int not null primary key, a int, junk1 text, b text); +ALTER TABLE t1 DROP COLUMN junk1; -- just a disturbing factor +GRANT ALL ON t1 TO public; +COPY t1 FROM stdin WITH ; +CREATE TABLE t2 (c float) INHERITS (t1); +GRANT ALL ON t2 TO public; +COPY t2 FROM stdin; +CREATE TABLE t3 (id int not null primary key, c text, b text, a int); +ALTER TABLE t3 INHERIT t1; +GRANT ALL ON t3 TO public; +COPY t3(id, a,b,c) FROM stdin; +CREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number +CREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number +ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE t2 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM t1; + id | a | b +-----+---+----- + 102 | 2 | bbb + 104 | 4 | dad + 202 | 2 | bcd + 204 | 4 | def + 302 | 2 | yyy +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a % 2) = 0) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a % 2) = 0) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a % 2) = 0) +(7 rows) + +SELECT * FROM t1 WHERE f_leak(b); +NOTICE: f_leak => bbb +NOTICE: f_leak => dad +NOTICE: f_leak => bcd +NOTICE: f_leak => def +NOTICE: f_leak => yyy + id | a | b +-----+---+----- + 102 | 2 | bbb + 104 | 4 | dad + 202 | 2 | bcd + 204 | 4 | def + 302 | 2 | yyy +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(10 rows) + +-- reference to system column +SELECT tableoid::regclass, * FROM t1; + tableoid | id | a | b +----------+-----+---+----- + t1 | 102 | 2 | bbb + t1 | 104 | 4 | dad + t2 | 202 | 2 | bcd + t2 | 204 | 4 | def + t3 | 302 | 2 | yyy +(5 rows) + +EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a % 2) = 0) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a % 2) = 0) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a % 2) = 0) +(7 rows) + +-- reference to whole-row reference +SELECT *, t1 FROM t1; + id | a | b | t1 +-----+---+-----+------------- + 102 | 2 | bbb | (102,2,bbb) + 104 | 4 | dad | (104,4,dad) + 202 | 2 | bcd | (202,2,bcd) + 204 | 4 | def | (204,4,def) + 302 | 2 | yyy | (302,2,yyy) +(5 rows) + +EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a % 2) = 0) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a % 2) = 0) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a % 2) = 0) +(7 rows) + +-- for share/update lock +SELECT * FROM t1 FOR SHARE; + id | a | b +-----+---+----- + 102 | 2 | bbb + 104 | 4 | dad + 202 | 2 | bcd + 204 | 4 | def + 302 | 2 | yyy +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE; + QUERY PLAN +--------------------------------------------- + LockRows + -> Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a % 2) = 0) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a % 2) = 0) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a % 2) = 0) +(8 rows) + +SELECT * FROM t1 WHERE f_leak(b) FOR SHARE; +NOTICE: f_leak => bbb +NOTICE: f_leak => dad +NOTICE: f_leak => bcd +NOTICE: f_leak => def +NOTICE: f_leak => yyy + id | a | b +-----+---+----- + 102 | 2 | bbb + 104 | 4 | dad + 202 | 2 | bcd + 204 | 4 | def + 302 | 2 | yyy +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE; + QUERY PLAN +--------------------------------------------- + LockRows + -> Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(11 rows) + +-- union all query +SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3; + a | b | tableoid +---+-----+---------- + 1 | abc | t2 + 3 | cde | t2 + 1 | xxx | t3 + 2 | yyy | t3 + 3 | zzz | t3 +(5 rows) + +EXPLAIN (COSTS OFF) SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3; + QUERY PLAN +--------------------------------------- + Append + -> Seq Scan on t2 + Storage Filter: ((a % 2) = 1) + -> Seq Scan on t3 +(4 rows) + +-- superuser is allowed to bypass RLS checks +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM t1 WHERE f_leak(b); +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad +NOTICE: f_leak => abc +NOTICE: f_leak => bcd +NOTICE: f_leak => cde +NOTICE: f_leak => def +NOTICE: f_leak => xxx +NOTICE: f_leak => yyy +NOTICE: f_leak => zzz + id | a | b +-----+---+----- + 101 | 1 | aba + 102 | 2 | bbb + 103 | 3 | ccc + 104 | 4 | dad + 201 | 1 | abc + 202 | 2 | bcd + 203 | 3 | cde + 204 | 4 | def + 301 | 1 | xxx + 302 | 2 | yyy + 303 | 3 | zzz +(11 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Filter: f_leak(b) +(7 rows) + +-- non-superuser with bypass privilege can bypass RLS policy when disabled +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO OFF; +SELECT * FROM t1 WHERE f_leak(b); +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad +NOTICE: f_leak => abc +NOTICE: f_leak => bcd +NOTICE: f_leak => cde +NOTICE: f_leak => def +NOTICE: f_leak => xxx +NOTICE: f_leak => yyy +NOTICE: f_leak => zzz + id | a | b +-----+---+----- + 101 | 1 | aba + 102 | 2 | bbb + 103 | 3 | ccc + 104 | 4 | dad + 201 | 1 | abc + 202 | 2 | bcd + 203 | 3 | cde + 204 | 4 | def + 301 | 1 | xxx + 302 | 2 | yyy + 303 | 3 | zzz +(11 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Filter: f_leak(b) +(7 rows) + +-- +-- Partitioned Tables +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE part_document ( + did int, + cid int, + dlevel int not null, + dauthor name, + dtitle text +) PARTITION BY RANGE (cid); +GRANT ALL ON part_document TO public; +-- Create partitions for document categories +CREATE TABLE part_document_fiction PARTITION OF part_document FOR VALUES FROM (11) to (12); +CREATE TABLE part_document_satire PARTITION OF part_document FOR VALUES FROM (55) to (56); +CREATE TABLE part_document_nonfiction PARTITION OF part_document FOR VALUES FROM (99) to (100); +GRANT ALL ON part_document_fiction TO public; +GRANT ALL ON part_document_satire TO public; +GRANT ALL ON part_document_nonfiction TO public; +INSERT INTO part_document VALUES + ( 1, 11, 1, 'regress_rls_bob', 'my first novel'), + ( 2, 11, 2, 'regress_rls_bob', 'my second novel'), + ( 3, 99, 2, 'regress_rls_bob', 'my science textbook'), + ( 4, 55, 1, 'regress_rls_bob', 'my first satire'), + ( 5, 99, 2, 'regress_rls_bob', 'my history book'), + ( 6, 11, 1, 'regress_rls_carol', 'great science fiction'), + ( 7, 99, 2, 'regress_rls_carol', 'great technology book'), + ( 8, 55, 2, 'regress_rls_carol', 'great satire'), + ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'), + (10, 99, 2, 'regress_rls_dave', 'awesome technology book'); +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 +CREATE POLICY pp1 ON part_document AS PERMISSIVE + USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user)); +-- Dave is only allowed to see cid < 55 +CREATE POLICY pp1r ON part_document AS RESTRICTIVE TO regress_rls_dave + USING (cid < 55); +\d+ part_document + Partitioned table "regress_rls_schema.part_document" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +---------+---------+-----------+----------+---------+----------+--------------+------------- + did | integer | | | | plain | | + cid | integer | | | | plain | | + dlevel | integer | | not null | | plain | | + dauthor | name | | | | plain | | + dtitle | text | | | | extended | | +Partition key: RANGE (cid) +Policies: + POLICY "pp1" + USING ((dlevel <= ( SELECT uaccount.seclv + FROM uaccount + WHERE (uaccount.pguser = CURRENT_USER)))) + POLICY "pp1r" AS RESTRICTIVE + TO regress_rls_dave + USING ((cid < 55)) +Partitions: part_document_fiction FOR VALUES FROM (11) TO (12), + part_document_nonfiction FOR VALUES FROM (99) TO (100), + part_document_satire FOR VALUES FROM (55) TO (56) + +SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname; + schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check +--------------------+---------------+------------+-------------+--------------------+-----+--------------------------------------------+------------ + regress_rls_schema | part_document | pp1 | PERMISSIVE | {public} | ALL | (dlevel <= ( SELECT uaccount.seclv +| + | | | | | | FROM uaccount +| + | | | | | | WHERE (uaccount.pguser = CURRENT_USER))) | + regress_rls_schema | part_document | pp1r | RESTRICTIVE | {regress_rls_dave} | ALL | (cid < 55) | +(2 rows) + +-- viewpoint from regress_rls_bob +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => my first satire + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 4 | 55 | 1 | regress_rls_bob | my first satire + 6 | 11 | 1 | regress_rls_carol | great science fiction + 9 | 11 | 1 | regress_rls_dave | awesome science fiction +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +------------------------------------------------------------ + Append + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) + -> Seq Scan on part_document_fiction part_document_1 + Storage Filter: (dlevel <= $0) + Filter: f_leak(dtitle) + -> Seq Scan on part_document_satire part_document_2 + Storage Filter: (dlevel <= $0) + Filter: f_leak(dtitle) + -> Seq Scan on part_document_nonfiction part_document_3 + Storage Filter: (dlevel <= $0) + Filter: f_leak(dtitle) +(13 rows) + +-- viewpoint from regress_rls_carol +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => my first satire +NOTICE: f_leak => great satire +NOTICE: f_leak => my science textbook +NOTICE: f_leak => my history book +NOTICE: f_leak => great technology book +NOTICE: f_leak => awesome technology book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire + 9 | 11 | 1 | regress_rls_dave | awesome science fiction + 10 | 99 | 2 | regress_rls_dave | awesome technology book +(10 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +------------------------------------------------------------ + Append + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) + -> Seq Scan on part_document_fiction part_document_1 + Storage Filter: (dlevel <= $0) + Filter: f_leak(dtitle) + -> Seq Scan on part_document_satire part_document_2 + Storage Filter: (dlevel <= $0) + Filter: f_leak(dtitle) + -> Seq Scan on part_document_nonfiction part_document_3 + Storage Filter: (dlevel <= $0) + Filter: f_leak(dtitle) +(13 rows) + +-- viewpoint from regress_rls_dave +SET SESSION AUTHORIZATION regress_rls_dave; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 6 | 11 | 1 | regress_rls_carol | great science fiction + 9 | 11 | 1 | regress_rls_dave | awesome science fiction +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +---------------------------------------------------- + Seq Scan on part_document_fiction part_document + Storage Filter: ((cid < 55) AND (dlevel <= $0)) + Filter: f_leak(dtitle) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) +(6 rows) + +-- pp1 ERROR +INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail +ERROR: new row violates row-level security policy for table "part_document" +-- pp1r ERROR +INSERT INTO part_document VALUES (100, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail +ERROR: new row violates row-level security policy "pp1r" for table "part_document" +-- Show that RLS policy does not apply for direct inserts to children +-- This should fail with RLS POLICY pp1r violation. +INSERT INTO part_document VALUES (100, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail +ERROR: new row violates row-level security policy "pp1r" for table "part_document" +-- But this should succeed. +INSERT INTO part_document_satire VALUES (100, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- success +-- We still cannot see the row using the parent +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 6 | 11 | 1 | regress_rls_carol | great science fiction + 9 | 11 | 1 | regress_rls_dave | awesome science fiction +(4 rows) + +-- But we can if we look directly +SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first satire +NOTICE: f_leak => great satire +NOTICE: f_leak => testing RLS with partitions + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 4 | 55 | 1 | regress_rls_bob | my first satire + 8 | 55 | 2 | regress_rls_carol | great satire + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(3 rows) + +-- Turn on RLS and create policy on child to show RLS is checked before constraints +SET SESSION AUTHORIZATION regress_rls_alice; +ALTER TABLE part_document_satire ENABLE ROW LEVEL SECURITY; +CREATE POLICY pp3 ON part_document_satire AS RESTRICTIVE + USING (cid < 55); +-- This should fail with RLS violation now. +SET SESSION AUTHORIZATION regress_rls_dave; +INSERT INTO part_document_satire VALUES (101, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail +ERROR: new row violates row-level security policy for table "part_document_satire" +-- And now we cannot see directly into the partition either, due to RLS +SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+---------+-------- +(0 rows) + +-- The parent looks same as before +-- viewpoint from regress_rls_dave +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 6 | 11 | 1 | regress_rls_carol | great science fiction + 9 | 11 | 1 | regress_rls_dave | awesome science fiction +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +---------------------------------------------------- + Seq Scan on part_document_fiction part_document + Storage Filter: ((cid < 55) AND (dlevel <= $0)) + Filter: f_leak(dtitle) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) +(6 rows) + +-- viewpoint from regress_rls_carol +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => my first satire +NOTICE: f_leak => great satire +NOTICE: f_leak => testing RLS with partitions +NOTICE: f_leak => my science textbook +NOTICE: f_leak => my history book +NOTICE: f_leak => great technology book +NOTICE: f_leak => awesome technology book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire + 9 | 11 | 1 | regress_rls_dave | awesome science fiction + 10 | 99 | 2 | regress_rls_dave | awesome technology book + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(11 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +------------------------------------------------------------ + Append + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) + -> Seq Scan on part_document_fiction part_document_1 + Storage Filter: (dlevel <= $0) + Filter: f_leak(dtitle) + -> Seq Scan on part_document_satire part_document_2 + Storage Filter: (dlevel <= $0) + Filter: f_leak(dtitle) + -> Seq Scan on part_document_nonfiction part_document_3 + Storage Filter: (dlevel <= $0) + Filter: f_leak(dtitle) +(13 rows) + +-- only owner can change policies +ALTER POLICY pp1 ON part_document USING (true); --fail +ERROR: must be owner of table part_document +DROP POLICY pp1 ON part_document; --fail +ERROR: must be owner of relation part_document +SET SESSION AUTHORIZATION regress_rls_alice; +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; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my first satire +NOTICE: f_leak => my science textbook +NOTICE: f_leak => my history book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+--------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book +(5 rows) + +-- viewpoint from rls_regres_carol again +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great satire +NOTICE: f_leak => great technology book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------- + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +--------------------------------------------------------------- + Append + -> Seq Scan on part_document_fiction part_document_1 + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) + -> Seq Scan on part_document_satire part_document_2 + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) + -> Seq Scan on part_document_nonfiction part_document_3 + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) +(7 rows) + +-- database superuser does bypass RLS policy when enabled +RESET SESSION AUTHORIZATION; +SET row_security TO ON; +SELECT * FROM part_document ORDER BY did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire + 9 | 11 | 1 | regress_rls_dave | awesome science fiction + 10 | 99 | 2 | regress_rls_dave | awesome technology book + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(11 rows) + +SELECT * FROM part_document_satire ORDER by did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 4 | 55 | 1 | regress_rls_bob | my first satire + 8 | 55 | 2 | regress_rls_carol | great satire + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(3 rows) + +-- database non-superuser with bypass privilege can bypass RLS policy when disabled +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO OFF; +SELECT * FROM part_document ORDER BY did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire + 9 | 11 | 1 | regress_rls_dave | awesome science fiction + 10 | 99 | 2 | regress_rls_dave | awesome technology book + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(11 rows) + +SELECT * FROM part_document_satire ORDER by did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 4 | 55 | 1 | regress_rls_bob | my first satire + 8 | 55 | 2 | regress_rls_carol | great satire + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(3 rows) + +-- RLS policy does not apply to table owner when RLS enabled. +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +SELECT * FROM part_document ORDER by did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire + 9 | 11 | 1 | regress_rls_dave | awesome science fiction + 10 | 99 | 2 | regress_rls_dave | awesome technology book + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(11 rows) + +SELECT * FROM part_document_satire ORDER by did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 4 | 55 | 1 | regress_rls_bob | my first satire + 8 | 55 | 2 | regress_rls_carol | great satire + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(3 rows) + +-- When RLS disabled, other users get ERROR. +SET SESSION AUTHORIZATION regress_rls_dave; +SET row_security TO OFF; +SELECT * FROM part_document ORDER by did; +ERROR: query would be affected by row-level security policy for table "part_document" +SELECT * FROM part_document_satire ORDER by did; +ERROR: query would be affected by row-level security policy for table "part_document_satire" +-- Check behavior with a policy that uses a SubPlan not an InitPlan. +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +CREATE POLICY pp3 ON part_document AS RESTRICTIVE + USING ((SELECT dlevel <= seclv FROM uaccount WHERE pguser = current_user)); +SET SESSION AUTHORIZATION regress_rls_carol; +INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_carol', 'testing pp3'); -- fail +ERROR: new row violates row-level security policy "pp3" for table "part_document" +----- Dependencies ----- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +CREATE TABLE dependee (x integer, y integer); +CREATE TABLE dependent (x integer, y integer); +CREATE POLICY d1 ON dependent FOR ALL + TO PUBLIC + USING (x = (SELECT d.x FROM dependee d WHERE d.y = y)); +DROP TABLE dependee; -- Should fail without CASCADE due to dependency on row security qual? +ERROR: cannot drop table dependee because other objects depend on it +DETAIL: policy d1 on table dependent depends on table dependee +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE dependee CASCADE; +NOTICE: drop cascades to policy d1 on table dependent +EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified + QUERY PLAN +----------------------- + Seq Scan on dependent +(1 row) + +----- RECURSION ---- +-- +-- Simple recursion +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE rec1 (x integer, y integer); +CREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y)); +ALTER TABLE rec1 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rec1; -- fail, direct recursion +ERROR: infinite recursion detected in policy for relation "rec1" +-- +-- Mutual recursion +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE rec2 (a integer, b integer); +ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y)); +CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b)); +ALTER TABLE rec2 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rec1; -- fail, mutual recursion +ERROR: infinite recursion detected in policy for relation "rec1" +-- +-- Mutual recursion via views +-- +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW rec1v AS SELECT * FROM rec1; +CREATE VIEW rec2v AS SELECT * FROM rec2; +SET SESSION AUTHORIZATION regress_rls_alice; +ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y)); +ALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b)); +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rec1; -- fail, mutual recursion via views +ERROR: infinite recursion detected in policy for relation "rec1" +-- +-- Mutual recursion via .s.b views +-- +SET SESSION AUTHORIZATION regress_rls_bob; +DROP VIEW rec1v, rec2v CASCADE; -- YB: fails because drop multiple objects not supported (see #880) +ERROR: DROP multiple objects not supported yet +LINE 1: DROP VIEW rec1v, rec2v CASCADE; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/880. React with thumbs up to raise its priority +DROP VIEW rec1v CASCADE; -- YB: workaround +NOTICE: drop cascades to policy r2 on table rec2 +DROP VIEW rec2v CASCADE; -- YB: workaround +NOTICE: drop cascades to policy r1 on table rec1 +CREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1; +CREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2; +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y)); +CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b)); +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rec1; -- fail, mutual recursion via s.b. views +ERROR: infinite recursion detected in policy for relation "rec1" +-- +-- recursive RLS and VIEWs in policy +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE s1 (a int, b text); +INSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x); +CREATE TABLE s2 (x int, y text); +INSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x); +GRANT SELECT ON s1, s2 TO regress_rls_bob; +CREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%')); +CREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%')); +CREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1)); +ALTER TABLE s1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE s2 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%'; +SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion) +ERROR: infinite recursion detected in policy for relation "s1" +INSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion) +ERROR: infinite recursion detected in policy for relation "s1" +SET SESSION AUTHORIZATION regress_rls_alice; +DROP POLICY p3 on s1; +ALTER POLICY p2 ON s2 USING (x % 2 = 0); +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM s1 WHERE f_leak(b); -- OK +NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c +NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c + a | b +---+---------------------------------- + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b); + QUERY PLAN +------------------------------------------------------------------- + Seq Scan on s1 + Filter: ((hashed SubPlan 1) AND f_leak(b)) + SubPlan 1 + -> Seq Scan on s2 + Storage Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text)) +(5 rows) + +SET SESSION AUTHORIZATION regress_rls_alice; +ALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM s1 WHERE f_leak(b); -- OK +NOTICE: f_leak => 0267aaf632e87a63288a08331f22c7c3 +NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc + a | b +----+---------------------------------- + -4 | 0267aaf632e87a63288a08331f22c7c3 + 6 | 1679091c5a880faf6fb5e6087eb1b2dc +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b); + QUERY PLAN +------------------------------------------------------------------- + Seq Scan on s1 + Filter: ((hashed SubPlan 1) AND f_leak(b)) + SubPlan 1 + -> Seq Scan on s2 + Storage Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text)) +(5 rows) + +SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%'; + xx | x | y +----+----+---------------------------------- + -6 | -6 | 596a3d04481816330f07e4f97510c28f + -4 | -4 | 0267aaf632e87a63288a08331f22c7c3 + 2 | 2 | c81e728d9d4c2f636f067f89cc14862c +(3 rows) + +EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%'; + QUERY PLAN +--------------------------------------------------------------------------------- + Seq Scan on s2 s2_1 + Storage Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text)) + SubPlan 2 + -> Limit + -> Seq Scan on s1 + Filter: (hashed SubPlan 1) + SubPlan 1 + -> Seq Scan on s2 + Storage Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text)) +(9 rows) + +SET SESSION AUTHORIZATION regress_rls_alice; +ALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%')); +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion via view) +ERROR: infinite recursion detected in policy for relation "s1" +-- prepared statement with regress_rls_alice privilege +PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1; +EXECUTE p1(2); + id | a | b +-----+---+----- + 102 | 2 | bbb + 202 | 2 | bcd + 302 | 2 | yyy +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE p1(2); + QUERY PLAN +------------------------------------------------------ + Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a <= 2) AND ((a % 2) = 0)) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a <= 2) AND ((a % 2) = 0)) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a <= 2) AND ((a % 2) = 0)) +(7 rows) + +-- superuser is allowed to bypass RLS checks +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM t1 WHERE f_leak(b); +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad +NOTICE: f_leak => abc +NOTICE: f_leak => bcd +NOTICE: f_leak => cde +NOTICE: f_leak => def +NOTICE: f_leak => xxx +NOTICE: f_leak => yyy +NOTICE: f_leak => zzz + id | a | b +-----+---+----- + 101 | 1 | aba + 102 | 2 | bbb + 103 | 3 | ccc + 104 | 4 | dad + 201 | 1 | abc + 202 | 2 | bcd + 203 | 3 | cde + 204 | 4 | def + 301 | 1 | xxx + 302 | 2 | yyy + 303 | 3 | zzz +(11 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Filter: f_leak(b) +(7 rows) + +-- plan cache should be invalidated +EXECUTE p1(2); + id | a | b +-----+---+----- + 101 | 1 | aba + 102 | 2 | bbb + 201 | 1 | abc + 202 | 2 | bcd + 301 | 1 | xxx + 302 | 2 | yyy +(6 rows) + +EXPLAIN (COSTS OFF) EXECUTE p1(2); + QUERY PLAN +---------------------------------- + Append + -> Seq Scan on t1 t1_1 + Storage Filter: (a <= 2) + -> Seq Scan on t2 t1_2 + Storage Filter: (a <= 2) + -> Seq Scan on t3 t1_3 + Storage Filter: (a <= 2) +(7 rows) + +PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1; +EXECUTE p2(2); + id | a | b +-----+---+----- + 102 | 2 | bbb + 202 | 2 | bcd + 302 | 2 | yyy +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE p2(2); + QUERY PLAN +--------------------------------- + Append + -> Seq Scan on t1 t1_1 + Storage Filter: (a = 2) + -> Seq Scan on t2 t1_2 + Storage Filter: (a = 2) + -> Seq Scan on t3 t1_3 + Storage Filter: (a = 2) +(7 rows) + +-- also, case when privilege switch from superuser +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +EXECUTE p2(2); + id | a | b +-----+---+----- + 102 | 2 | bbb + 202 | 2 | bcd + 302 | 2 | yyy +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE p2(2); + QUERY PLAN +----------------------------------------------------- + Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a = 2) AND ((a % 2) = 0)) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a = 2) AND ((a % 2) = 0)) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a = 2) AND ((a % 2) = 0)) +(7 rows) + +-- +-- UPDATE / DELETE and Row-level security +-- +SET SESSION AUTHORIZATION regress_rls_bob; +EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b); + QUERY PLAN +--------------------------------------------------- + Update on t1 + Update on t1 t1_1 + Update on t2 t1_2 + Update on t3 t1_3 + -> Result t1 + -> Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(15 rows) + +UPDATE t1 SET b = b || b WHERE f_leak(b); +NOTICE: f_leak => bbb +NOTICE: f_leak => dad +NOTICE: f_leak => bcd +NOTICE: f_leak => def +NOTICE: f_leak => yyy +EXPLAIN (COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b); + QUERY PLAN +--------------------------------------- + Update on t1 + -> Seq Scan on t1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(4 rows) + +UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b); +NOTICE: f_leak => bbbbbb +NOTICE: f_leak => daddad +-- returning clause with system column +UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; +NOTICE: f_leak => bbbbbb_updt +NOTICE: f_leak => daddad_updt + tableoid | id | a | b | t1 +----------+-----+---+-------------+--------------------- + t1 | 102 | 2 | bbbbbb_updt | (102,2,bbbbbb_updt) + t1 | 104 | 4 | daddad_updt | (104,4,daddad_updt) +(2 rows) + +UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *; +NOTICE: f_leak => bbbbbb_updt +NOTICE: f_leak => daddad_updt +NOTICE: f_leak => bcdbcd +NOTICE: f_leak => defdef +NOTICE: f_leak => yyyyyy + id | a | b +-----+---+------------- + 102 | 2 | bbbbbb_updt + 104 | 4 | daddad_updt + 202 | 2 | bcdbcd + 204 | 4 | defdef + 302 | 2 | yyyyyy +(5 rows) + +UPDATE t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; +NOTICE: f_leak => bbbbbb_updt +NOTICE: f_leak => daddad_updt +NOTICE: f_leak => bcdbcd +NOTICE: f_leak => defdef +NOTICE: f_leak => yyyyyy + tableoid | id | a | b | t1 +----------+-----+---+-------------+--------------------- + t1 | 102 | 2 | bbbbbb_updt | (102,2,bbbbbb_updt) + t1 | 104 | 4 | daddad_updt | (104,4,daddad_updt) + t2 | 202 | 2 | bcdbcd | (202,2,bcdbcd) + t2 | 204 | 4 | defdef | (204,4,defdef) + t3 | 302 | 2 | yyyyyy | (302,2,yyyyyy) +(5 rows) + +-- updates with from clause +EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t3 +WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b); + QUERY PLAN +----------------------------------------------------------- + Update on t2 + -> Nested Loop + -> Seq Scan on t2 + Storage Filter: ((a = 3) AND ((a % 2) = 1)) + Filter: f_leak(b) + -> Materialize + -> Seq Scan on t3 + Storage Filter: (a = 2) + Filter: f_leak(b) +(9 rows) + +UPDATE t2 SET b=t2.b FROM t3 +WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b); +NOTICE: f_leak => cde +NOTICE: f_leak => yyyyyy +EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); + QUERY PLAN +----------------------------------------------------------------- + Update on t1 + Update on t1 t1_1 + Update on t2 t1_2 + Update on t3 t1_3 + -> Nested Loop + -> Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a = 3) AND ((a % 2) = 0)) + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a = 3) AND ((a % 2) = 0)) + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a = 3) AND ((a % 2) = 0)) + Filter: f_leak(b) + -> Materialize + -> Seq Scan on t2 + Storage Filter: ((a = 3) AND ((a % 2) = 1)) + Filter: f_leak(b) +(19 rows) + +UPDATE t1 SET b=t1.b FROM t2 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); +EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); + QUERY PLAN +----------------------------------------------------------------- + Update on t2 + -> Nested Loop + -> Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a = 3) AND ((a % 2) = 0)) + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a = 3) AND ((a % 2) = 0)) + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a = 3) AND ((a % 2) = 0)) + Filter: f_leak(b) + -> Materialize + -> Seq Scan on t2 + Storage Filter: ((a = 3) AND ((a % 2) = 1)) + Filter: f_leak(b) +(16 rows) + +UPDATE t2 SET b=t2.b FROM t1 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); +-- updates with from clause self join +EXPLAIN (COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2 +WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b +AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2; + QUERY PLAN +----------------------------------------------------------------- + Update on t2 t2_1 + -> Merge Join + Merge Cond: (t2_1.b = t2_2.b) + -> Sort + Sort Key: t2_1.b + -> Seq Scan on t2 t2_1 + Storage Filter: ((a = 3) AND ((a % 2) = 1)) + Filter: f_leak(b) + -> Sort + Sort Key: t2_2.b + -> Seq Scan on t2 t2_2 + Storage Filter: ((a = 3) AND ((a % 2) = 1)) + Filter: f_leak(b) +(13 rows) + +UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2 +WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b +AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2; +NOTICE: f_leak => cde +NOTICE: f_leak => cde + id | a | b | c | id | a | b | c | t2_1 | t2_2 +-----+---+-----+-----+-----+---+-----+-----+-----------------+----------------- + 203 | 3 | cde | 3.3 | 203 | 3 | cde | 3.3 | (203,3,cde,3.3) | (203,3,cde,3.3) +(1 row) + +EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 +WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b +AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; + QUERY PLAN +----------------------------------------------------------------------- + Update on t1 t1_1 + Update on t1 t1_1_1 + Update on t2 t1_1_2 + Update on t3 t1_1_3 + -> Merge Join + Merge Cond: (t1_1.b = t1_2.b) + -> Sort + Sort Key: t1_1.b + -> Append + -> Seq Scan on t1 t1_1_1 + Storage Filter: ((a = 4) AND ((a % 2) = 0)) + Filter: f_leak(b) + -> Seq Scan on t2 t1_1_2 + Storage Filter: ((a = 4) AND ((a % 2) = 0)) + Filter: f_leak(b) + -> Seq Scan on t3 t1_1_3 + Storage Filter: ((a = 4) AND ((a % 2) = 0)) + Filter: f_leak(b) + -> Sort + Sort Key: t1_2.b + -> Append + -> Seq Scan on t1 t1_2_1 + Storage Filter: ((a = 4) AND ((a % 2) = 0)) + Filter: f_leak(b) + -> Seq Scan on t2 t1_2_2 + Storage Filter: ((a = 4) AND ((a % 2) = 0)) + Filter: f_leak(b) + -> Seq Scan on t3 t1_2_3 + Storage Filter: ((a = 4) AND ((a % 2) = 0)) + Filter: f_leak(b) +(30 rows) + +UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 +WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b +AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; +NOTICE: f_leak => daddad_updt +NOTICE: f_leak => defdef +NOTICE: f_leak => daddad_updt +NOTICE: f_leak => defdef + id | a | b | id | a | b | t1_1 | t1_2 +-----+---+-------------+-----+---+-------------+---------------------+--------------------- + 104 | 4 | daddad_updt | 104 | 4 | daddad_updt | (104,4,daddad_updt) | (104,4,daddad_updt) + 204 | 4 | defdef | 204 | 4 | defdef | (204,4,defdef) | (204,4,defdef) +(2 rows) + +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM t1 ORDER BY a,b; + id | a | b +-----+---+------------- + 101 | 1 | aba + 201 | 1 | abc + 301 | 1 | xxx + 102 | 2 | bbbbbb_updt + 202 | 2 | bcdbcd + 302 | 2 | yyyyyy + 103 | 3 | ccc + 203 | 3 | cde + 303 | 3 | zzz + 104 | 4 | daddad_updt + 204 | 4 | defdef +(11 rows) + +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b); + QUERY PLAN +--------------------------------------- + Delete on t1 + -> Seq Scan on t1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(4 rows) + +EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b); + QUERY PLAN +--------------------------------------------- + Delete on t1 + Delete on t1 t1_1 + Delete on t2 t1_2 + Delete on t3 t1_3 + -> Append + -> Seq Scan on t1 t1_1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(14 rows) + +/* YB: these queries fail in YB and are tracked by #26577 +DELETE FROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; +DELETE FROM t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; +*/ -- YB +-- +-- S.b. view on top of Row-level security +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE b1 (a int, b text); +INSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x); +CREATE POLICY p1 ON b1 USING (a % 2 = 0); +ALTER TABLE b1 ENABLE ROW LEVEL SECURITY; +GRANT ALL ON b1 TO regress_rls_bob; +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION; +ERROR: VIEW WITH CHECK OPTION not supported yet +LINE 1: ...security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK... + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +-- YB: port further queries when above works +-- +-- INSERT ... ON CONFLICT DO UPDATE and Row-level security +-- +SET SESSION AUTHORIZATION regress_rls_alice; +DROP POLICY p1 ON document; +DROP POLICY p1r ON document; +CREATE POLICY p1 ON document FOR SELECT USING (true); +CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user); +CREATE POLICY p3 ON document FOR UPDATE + USING (cid = (SELECT cid from category WHERE cname = 'novel')) + WITH CHECK (dauthor = current_user); +SET SESSION AUTHORIZATION regress_rls_bob; +-- Exists... +SELECT * FROM document WHERE did = 2; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+----------------- + 2 | 11 | 2 | regress_rls_bob | my second novel +(1 row) + +-- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since +-- alternative UPDATE path happens to be taken): +INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor; +ERROR: new row violates row-level security policy for table "document" +-- Violates USING qual for UPDATE policy p3. +-- +-- UPDATE path is taken, but UPDATE fails purely because *existing* row to be +-- updated is not a "novel"/cid 11 (row is not leaked, even though we have +-- SELECT privileges sufficient to see the row in this instance): +INSERT INTO document VALUES (33, 22, 1, 'regress_rls_bob', 'okay science fiction'); -- preparation for next statement +INSERT INTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'Some novel, replaces sci-fi') -- takes UPDATE path + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle; +ERROR: new row violates row-level security policy (USING expression) for table "document" +-- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs +-- not violated): +INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+---------------- + 2 | 11 | 2 | regress_rls_bob | my first novel +(1 row) + +-- Fine (we INSERT, so "cid = 33" ("technology") isn't evaluated): +INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+----------------------- + 78 | 11 | 1 | regress_rls_bob | some technology novel +(1 row) + +-- Fine (same query, but we UPDATE, so "cid = 33", ("technology") is not the +-- case in respect of *existing* tuple): +INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+----------------------- + 78 | 33 | 1 | regress_rls_bob | some technology novel +(1 row) + +-- Same query a third time, but now fails due to existing tuple finally not +-- passing quals: +INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; +ERROR: new row violates row-level security policy (USING expression) for table "document" +-- Don't fail just because INSERT doesn't satisfy WITH CHECK option that +-- originated as a barrier/USING() qual from the UPDATE. Note that the UPDATE +-- path *isn't* taken, and so UPDATE-related policy does not apply: +INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+---------------------------------- + 79 | 33 | 1 | regress_rls_bob | technology book, can only insert +(1 row) + +-- But this time, the same statement fails, because the UPDATE path is taken, +-- and updating the row just inserted falls afoul of security barrier qual +-- (enforced as WCO) -- what we might have updated target tuple to is +-- irrelevant, in fact. +INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; +ERROR: new row violates row-level security policy (USING expression) for table "document" +-- Test default USING qual enforced as WCO +SET SESSION AUTHORIZATION regress_rls_alice; +DROP POLICY p1 ON document; +DROP POLICY p2 ON document; +DROP POLICY p3 ON document; +CREATE POLICY p3_with_default ON document FOR UPDATE + USING (cid = (SELECT cid from category WHERE cname = 'novel')); +SET SESSION AUTHORIZATION regress_rls_bob; +-- Just because WCO-style enforcement of USING quals occurs with +-- existing/target tuple does not mean that the implementation can be allowed +-- to fail to also enforce this qual against the final tuple appended to +-- relation (since in the absence of an explicit WCO, this is also interpreted +-- as an UPDATE/ALL WCO in general). +-- +-- UPDATE path is taken here (fails due to existing tuple). Note that this is +-- not reported as a "USING expression", because it's an RLS UPDATE check that originated as +-- a USING qual for the purposes of RLS in general, as opposed to an explicit +-- USING qual that is ordinarily a security barrier. We leave it up to the +-- UPDATE to make this fail: +INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; +ERROR: new row violates row-level security policy for table "document" +-- UPDATE path is taken here. Existing tuple passes, since its cid +-- corresponds to "novel", but default USING qual is enforced against +-- post-UPDATE tuple too (as always when updating with a policy that lacks an +-- explicit WCO), and so this fails: +INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *; +ERROR: new row violates row-level security policy for table "document" +SET SESSION AUTHORIZATION regress_rls_alice; +DROP POLICY p3_with_default ON document; +-- +-- Test ALL policies with ON CONFLICT DO UPDATE (much the same as existing UPDATE +-- tests) +-- +CREATE POLICY p3_with_all ON document FOR ALL + USING (cid = (SELECT cid from category WHERE cname = 'novel')) + WITH CHECK (dauthor = current_user); +SET SESSION AUTHORIZATION regress_rls_bob; +-- Fails, since ALL WCO is enforced in insert path: +INSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33; +ERROR: new row violates row-level security policy for table "document" +-- Fails, since ALL policy USING qual is enforced (existing, target tuple is in +-- violation, since it has the "manga" cid): +INSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle; +ERROR: new row violates row-level security policy (USING expression) for table "document" +-- Fails, since ALL WCO are enforced: +INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol'; +ERROR: new row violates row-level security policy for table "document" +-- +-- MERGE +-- +RESET SESSION AUTHORIZATION; +DROP POLICY p3_with_all ON document; +ALTER TABLE document ADD COLUMN dnotes text DEFAULT ''; +-- all documents are readable +CREATE POLICY p1 ON document FOR SELECT USING (true); +-- one may insert documents only authored by them +CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user); +-- one may only update documents in 'novel' category +CREATE POLICY p3 ON document FOR UPDATE + USING (cid = (SELECT cid from category WHERE cname = 'novel')) + WITH CHECK (dauthor = current_user); +-- one may only delete documents in 'manga' category +CREATE POLICY p4 ON document FOR DELETE + USING (cid = (SELECT cid from category WHERE cname = 'manga')); +SELECT * FROM document ORDER BY (CASE WHEN (did = 2) THEN 50 ELSE did END); -- YB: add ordering + did | cid | dlevel | dauthor | dtitle | dnotes +-----+-----+--------+-------------------+----------------------------------+-------- + 1 | 11 | 1 | regress_rls_bob | my first novel | + 3 | 22 | 2 | regress_rls_bob | my science fiction | + 4 | 44 | 1 | regress_rls_bob | my first manga | + 5 | 44 | 2 | regress_rls_bob | my second manga | + 6 | 22 | 1 | regress_rls_carol | great science fiction | + 7 | 33 | 2 | regress_rls_carol | great technology book | + 8 | 44 | 1 | regress_rls_carol | great manga | + 9 | 22 | 1 | regress_rls_dave | awesome science fiction | + 10 | 33 | 2 | regress_rls_dave | awesome technology book | + 11 | 33 | 1 | regress_rls_carol | hoge | + 33 | 22 | 1 | regress_rls_bob | okay science fiction | + 2 | 11 | 2 | regress_rls_bob | my first novel | + 78 | 33 | 1 | regress_rls_bob | some technology novel | + 79 | 33 | 1 | regress_rls_bob | technology book, can only insert | +(14 rows) + +SET SESSION AUTHORIZATION regress_rls_bob; +-- Fails, since update violates WITH CHECK qual on dauthor +MERGE INTO document d +USING (SELECT 1 as sdid) s +ON did = s.sdid +WHEN MATCHED THEN + UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dauthor = 'regress_rls_alice'; +ERROR: This statement not supported yet +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +-- YB: port further queries when above works +-- +-- ROLE/GROUP +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE z1 (a int, b text); +CREATE TABLE z2 (a int, b text); +GRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2, + regress_rls_bob, regress_rls_carol; +INSERT INTO z1 VALUES + (1, 'aba'), + (2, 'bbb'), + (3, 'ccc'), + (4, 'dad'); +CREATE POLICY p1 ON z1 TO regress_rls_group1 USING (a % 2 = 0); +CREATE POLICY p2 ON z1 TO regress_rls_group2 USING (a % 2 = 1); +ALTER TABLE z1 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM z1 WHERE f_leak(b); +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(3 rows) + +PREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) EXECUTE plancache_test; + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(3 rows) + +PREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2; +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; + QUERY PLAN +----------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) + -> CTE Scan on q + -> Materialize + -> Seq Scan on z2 +(8 rows) + +PREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b); +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + QUERY PLAN +--------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z2 + -> CTE Scan on q + -> Materialize + -> Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(8 rows) + +SET ROLE regress_rls_group1; +SELECT * FROM z1 WHERE f_leak(b); +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test; + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; + QUERY PLAN +----------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) + -> CTE Scan on q + -> Materialize + -> Seq Scan on z2 +(8 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + QUERY PLAN +--------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z2 + -> CTE Scan on q + -> Materialize + -> Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(8 rows) + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM z1 WHERE f_leak(b); +NOTICE: f_leak => aba +NOTICE: f_leak => ccc + a | b +---+----- + 1 | aba + 3 | ccc +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 1) + Filter: f_leak(b) +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test; + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 1) + Filter: f_leak(b) +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; + QUERY PLAN +----------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z1 + Storage Filter: ((a % 2) = 1) + Filter: f_leak(b) + -> CTE Scan on q + -> Materialize + -> Seq Scan on z2 +(8 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + QUERY PLAN +--------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z2 + -> CTE Scan on q + -> Materialize + -> Seq Scan on z1 + Storage Filter: ((a % 2) = 1) + Filter: f_leak(b) +(8 rows) + +SET ROLE regress_rls_group2; +SELECT * FROM z1 WHERE f_leak(b); +NOTICE: f_leak => aba +NOTICE: f_leak => ccc + a | b +---+----- + 1 | aba + 3 | ccc +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 1) + Filter: f_leak(b) +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test; + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 1) + Filter: f_leak(b) +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; + QUERY PLAN +----------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z1 + Storage Filter: ((a % 2) = 1) + Filter: f_leak(b) + -> CTE Scan on q + -> Materialize + -> Seq Scan on z2 +(8 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + QUERY PLAN +--------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z2 + -> CTE Scan on q + -> Materialize + -> Seq Scan on z1 + Storage Filter: ((a % 2) = 1) + Filter: f_leak(b) +(8 rows) + +-- +-- Views should follow policy for view owner. +-- +-- View and Table owner are the same. +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); +GRANT SELECT ON rls_view TO regress_rls_bob; +-- Query as role that is not owner of view or table. Should return all records. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad + a | b +---+----- + 1 | aba + 2 | bbb + 3 | ccc + 4 | dad +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------- + Seq Scan on z1 + Filter: f_leak(b) +(2 rows) + +-- Query as view/table owner. Should return all records. +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM rls_view; +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad + a | b +---+----- + 1 | aba + 2 | bbb + 3 | ccc + 4 | dad +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------- + Seq Scan on z1 + Filter: f_leak(b) +(2 rows) + +DROP VIEW rls_view; +-- View and Table owners are different. +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); +GRANT SELECT ON rls_view TO regress_rls_alice; +-- Query as role that is not owner of view but is owner of table. +-- Should return records based on view owner policies. +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM rls_view; +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(3 rows) + +-- Query as role that is not owner of table but is owner of view. +-- Should return records based on view owner policies. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(3 rows) + +-- Query as role that is not the owner of the table or view without permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for view rls_view +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for view rls_view +-- Query as role that is not the owner of the table or view with permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +GRANT SELECT ON rls_view TO regress_rls_carol; +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(3 rows) + +-- Policy requiring access to another table. +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE z1_blacklist (a int); +INSERT INTO z1_blacklist VALUES (3), (4); +CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist)); +-- Query as role that is not owner of table but is owner of view without permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for table z1_blacklist +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for table z1_blacklist +-- Query as role that is not the owner of the table or view without permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for table z1_blacklist +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for table z1_blacklist +-- Query as role that is not owner of table but is owner of view with permissions. +SET SESSION AUTHORIZATION regress_rls_alice; +GRANT SELECT ON z1_blacklist TO regress_rls_bob; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +NOTICE: f_leak => bbb + a | b +---+----- + 2 | bbb +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +---------------------------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: ((NOT (hashed SubPlan 1)) AND f_leak(b)) + SubPlan 1 + -> Seq Scan on z1_blacklist +(5 rows) + +-- Query as role that is not the owner of the table or view with permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; +NOTICE: f_leak => bbb + a | b +---+----- + 2 | bbb +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +---------------------------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: ((NOT (hashed SubPlan 1)) AND f_leak(b)) + SubPlan 1 + -> Seq Scan on z1_blacklist +(5 rows) + +SET SESSION AUTHORIZATION regress_rls_alice; +REVOKE SELECT ON z1_blacklist FROM regress_rls_bob; +DROP POLICY p3 ON z1; +SET SESSION AUTHORIZATION regress_rls_bob; +DROP VIEW rls_view; +-- +-- Security invoker views should follow policy for current user. +-- +-- View and table owner are the same. +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE VIEW rls_view WITH (security_invoker) AS + SELECT * FROM z1 WHERE f_leak(b); +GRANT SELECT ON rls_view TO regress_rls_bob; +GRANT SELECT ON rls_view TO regress_rls_carol; +-- Query as table owner. Should return all records. +SELECT * FROM rls_view; +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad + a | b +---+----- + 1 | aba + 2 | bbb + 3 | ccc + 4 | dad +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------- + Seq Scan on z1 + Filter: f_leak(b) +(2 rows) + +-- Queries as other users. +-- Should return records based on current user's policies. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(3 rows) + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; +NOTICE: f_leak => aba +NOTICE: f_leak => ccc + a | b +---+----- + 1 | aba + 3 | ccc +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 1) + Filter: f_leak(b) +(3 rows) + +-- View and table owners are different. +SET SESSION AUTHORIZATION regress_rls_alice; +DROP VIEW rls_view; +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW rls_view WITH (security_invoker) AS + SELECT * FROM z1 WHERE f_leak(b); +GRANT SELECT ON rls_view TO regress_rls_alice; +GRANT SELECT ON rls_view TO regress_rls_carol; +-- Query as table owner. Should return all records. +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM rls_view; +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad + a | b +---+----- + 1 | aba + 2 | bbb + 3 | ccc + 4 | dad +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------- + Seq Scan on z1 + Filter: f_leak(b) +(2 rows) + +-- Queries as other users. +-- Should return records based on current user's policies. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(3 rows) + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; +NOTICE: f_leak => aba +NOTICE: f_leak => ccc + a | b +---+----- + 1 | aba + 3 | ccc +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 1) + Filter: f_leak(b) +(3 rows) + +-- Policy requiring access to another table. +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist)); +-- Query as role that is not owner of table but is owner of view without permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for table z1_blacklist +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for table z1_blacklist +-- Query as role that is not the owner of the table or view without permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for table z1_blacklist +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for table z1_blacklist +-- Query as role that is not owner of table but is owner of view with permissions. +SET SESSION AUTHORIZATION regress_rls_alice; +GRANT SELECT ON z1_blacklist TO regress_rls_bob; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +NOTICE: f_leak => bbb + a | b +---+----- + 2 | bbb +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +---------------------------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 0) + Filter: ((NOT (hashed SubPlan 1)) AND f_leak(b)) + SubPlan 1 + -> Seq Scan on z1_blacklist +(5 rows) + +-- Query as role that is not the owner of the table or view without permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for table z1_blacklist +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for table z1_blacklist +-- Query as role that is not the owner of the table or view with permissions. +SET SESSION AUTHORIZATION regress_rls_alice; +GRANT SELECT ON z1_blacklist TO regress_rls_carol; +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; +NOTICE: f_leak => aba + a | b +---+----- + 1 | aba +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +---------------------------------------------------- + Seq Scan on z1 + Storage Filter: ((a % 2) = 1) + Filter: ((NOT (hashed SubPlan 1)) AND f_leak(b)) + SubPlan 1 + -> Seq Scan on z1_blacklist +(5 rows) + +SET SESSION AUTHORIZATION regress_rls_bob; +DROP VIEW rls_view; +-- +-- Command specific +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE x1 (a int, b text, c text); +GRANT ALL ON x1 TO PUBLIC; +INSERT INTO x1 VALUES + (1, 'abc', 'regress_rls_bob'), + (2, 'bcd', 'regress_rls_bob'), + (3, 'cde', 'regress_rls_carol'), + (4, 'def', 'regress_rls_carol'), + (5, 'efg', 'regress_rls_bob'), + (6, 'fgh', 'regress_rls_bob'), + (7, 'fgh', 'regress_rls_carol'), + (8, 'fgh', 'regress_rls_carol'); +CREATE POLICY p0 ON x1 FOR ALL USING (c = current_user); +CREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0); +CREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1); +CREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0); +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; +NOTICE: f_leak => abc +NOTICE: f_leak => bcd +NOTICE: f_leak => def +NOTICE: f_leak => efg +NOTICE: f_leak => fgh +NOTICE: f_leak => fgh + a | b | c +---+-----+------------------- + 1 | abc | regress_rls_bob + 2 | bcd | regress_rls_bob + 4 | def | regress_rls_carol + 5 | efg | regress_rls_bob + 6 | fgh | regress_rls_bob + 8 | fgh | regress_rls_carol +(6 rows) + +UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *; +NOTICE: f_leak => abc +NOTICE: f_leak => bcd +NOTICE: f_leak => def +NOTICE: f_leak => efg +NOTICE: f_leak => fgh +NOTICE: f_leak => fgh + a | b | c +---+----------+------------------- + 1 | abc_updt | regress_rls_bob + 2 | bcd_updt | regress_rls_bob + 4 | def_updt | regress_rls_carol + 5 | efg_updt | regress_rls_bob + 6 | fgh_updt | regress_rls_bob + 8 | fgh_updt | regress_rls_carol +(6 rows) + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC; +NOTICE: f_leak => bcd_updt +NOTICE: f_leak => cde +NOTICE: f_leak => def_updt +NOTICE: f_leak => fgh_updt +NOTICE: f_leak => fgh +NOTICE: f_leak => fgh_updt + a | b | c +---+----------+------------------- + 2 | bcd_updt | regress_rls_bob + 3 | cde | regress_rls_carol + 4 | def_updt | regress_rls_carol + 6 | fgh_updt | regress_rls_bob + 7 | fgh | regress_rls_carol + 8 | fgh_updt | regress_rls_carol +(6 rows) + +UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *; +NOTICE: f_leak => bcd_updt +NOTICE: f_leak => cde +NOTICE: f_leak => def_updt +NOTICE: f_leak => fgh_updt +NOTICE: f_leak => fgh +NOTICE: f_leak => fgh_updt + a | b | c +---+---------------+------------------- + 2 | bcd_updt_updt | regress_rls_bob + 3 | cde_updt | regress_rls_carol + 4 | def_updt_updt | regress_rls_carol + 6 | fgh_updt_updt | regress_rls_bob + 7 | fgh_updt | regress_rls_carol + 8 | fgh_updt_updt | regress_rls_carol +(6 rows) + +DELETE FROM x1 WHERE f_leak(b) RETURNING *; +NOTICE: f_leak => bcd_updt_updt +NOTICE: f_leak => cde_updt +NOTICE: f_leak => def_updt_updt +NOTICE: f_leak => fgh_updt_updt +NOTICE: f_leak => fgh_updt +NOTICE: f_leak => fgh_updt_updt + a | b | c +---+---------------+------------------- + 2 | bcd_updt_updt | regress_rls_bob + 3 | cde_updt | regress_rls_carol + 4 | def_updt_updt | regress_rls_carol + 6 | fgh_updt_updt | regress_rls_bob + 7 | fgh_updt | regress_rls_carol + 8 | fgh_updt_updt | regress_rls_carol +(6 rows) + +-- +-- Duplicate Policy Names +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE y1 (a int, b text); +CREATE TABLE y2 (a int, b text); +GRANT ALL ON y1, y2 TO regress_rls_bob; +CREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0); +CREATE POLICY p2 ON y1 FOR SELECT USING (a > 2); +CREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1); --fail +ERROR: policy "p1" for table "y1" already exists +CREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0); --OK +ALTER TABLE y1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE y2 ENABLE ROW LEVEL SECURITY; +-- +-- Expression structure with SBV +-- +-- Create view as table owner. RLS should NOT be applied. +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE VIEW rls_sbv WITH (security_barrier) AS + SELECT * FROM y1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1); + QUERY PLAN +--------------------------- + Seq Scan on y1 + Storage Filter: (a = 1) + Filter: f_leak(b) +(3 rows) + +DROP VIEW rls_sbv; +-- Create view as role that does not own table. RLS should be applied. +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW rls_sbv WITH (security_barrier) AS + SELECT * FROM y1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1); + QUERY PLAN +------------------------------------------------------------ + Seq Scan on y1 + Storage Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0))) + Filter: f_leak(b) +(3 rows) + +DROP VIEW rls_sbv; +-- +-- Expression structure +-- +SET SESSION AUTHORIZATION regress_rls_alice; +INSERT INTO y2 (SELECT x, md5(x::text) FROM generate_series(0,20) x); +CREATE POLICY p2 ON y2 USING (a % 3 = 0); +CREATE POLICY p3 ON y2 USING (a % 4 = 0); +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM y2 WHERE f_leak(b); +NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da +NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c +NOTICE: f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3 +NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c +NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc +NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d +NOTICE: f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26 +NOTICE: f_leak => d3d9446802a44259755d38e6d163e820 +NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710 +NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56 +NOTICE: f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3 +NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf +NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23 +NOTICE: f_leak => 98f13708210194c475687be6106a3b84 + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 +(14 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b); + QUERY PLAN +--------------------------------------------------------------------- + Seq Scan on y2 + Storage Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) + Filter: f_leak(b) +(3 rows) + +-- +-- Qual push-down of leaky functions, when not referring to table +-- +SELECT * FROM y2 WHERE f_leak('abc'); -- YB: output has difference in number of NOTICEs due to expression pushdown +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 +(14 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc'); + QUERY PLAN +--------------------------------------------------------------------- + Seq Scan on y2 + Storage Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) + Filter: f_leak('abc'::text) +(3 rows) + +CREATE TABLE test_qual_pushdown ( + abc text +); +INSERT INTO test_qual_pushdown VALUES ('abc'),('def'); +SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc); +NOTICE: f_leak => abc +NOTICE: f_leak => def + a | b | abc +---+---+----- +(0 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc); + QUERY PLAN +--------------------------------------------------------------------------------- + Merge Join + Merge Cond: (y2.b = test_qual_pushdown.abc) + -> Sort + Sort Key: y2.b + -> Seq Scan on y2 + Storage Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) + -> Sort + Sort Key: test_qual_pushdown.abc + -> Seq Scan on test_qual_pushdown + Filter: f_leak(abc) +(10 rows) + +SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b); +NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da +NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c +NOTICE: f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3 +NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c +NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc +NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d +NOTICE: f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26 +NOTICE: f_leak => d3d9446802a44259755d38e6d163e820 +NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710 +NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56 +NOTICE: f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3 +NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf +NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23 +NOTICE: f_leak => 98f13708210194c475687be6106a3b84 + a | b | abc +---+---+----- +(0 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b); + QUERY PLAN +--------------------------------------------------------------------------------- + Merge Join + Merge Cond: (y2.b = test_qual_pushdown.abc) + -> Sort + Sort Key: y2.b + -> Seq Scan on y2 + Storage Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) + Filter: f_leak(b) + -> Sort + Sort Key: test_qual_pushdown.abc + -> Seq Scan on test_qual_pushdown +(10 rows) + +DROP TABLE test_qual_pushdown; +-- +-- Plancache invalidate on user change. +-- +RESET SESSION AUTHORIZATION; +\set VERBOSITY terse \\ -- YB: suppress cascade details +DROP TABLE t1 CASCADE; +NOTICE: drop cascades to 2 other objects +\set VERBOSITY default \\ -- YB +CREATE TABLE t1 (a integer); +GRANT SELECT ON t1 TO regress_rls_bob, regress_rls_carol; +CREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0); +CREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0); +ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; +-- Prepare as regress_rls_bob +SET ROLE regress_rls_bob; +PREPARE role_inval AS SELECT * FROM t1; +-- Check plan +EXPLAIN (COSTS OFF) EXECUTE role_inval; + QUERY PLAN +--------------------------------- + Seq Scan on t1 + Storage Filter: ((a % 2) = 0) +(2 rows) + +-- Change to regress_rls_carol +SET ROLE regress_rls_carol; +-- Check plan- should be different +EXPLAIN (COSTS OFF) EXECUTE role_inval; + QUERY PLAN +--------------------------------- + Seq Scan on t1 + Storage Filter: ((a % 4) = 0) +(2 rows) + +-- Change back to regress_rls_bob +SET ROLE regress_rls_bob; +-- Check plan- should be back to original +EXPLAIN (COSTS OFF) EXECUTE role_inval; + QUERY PLAN +--------------------------------- + Seq Scan on t1 + Storage Filter: ((a % 2) = 0) +(2 rows) + +-- +-- CTE and RLS +-- +RESET SESSION AUTHORIZATION; +DROP TABLE t1 CASCADE; +CREATE TABLE t1 (a integer, b text); +CREATE POLICY p1 ON t1 USING (a % 2 = 0); +ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; +GRANT ALL ON t1 TO regress_rls_bob; +INSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(0,20) x); +SET SESSION AUTHORIZATION regress_rls_bob; +WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1; +NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da +NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c +NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c +NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc +NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d +NOTICE: f_leak => d3d9446802a44259755d38e6d163e820 +NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710 +NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56 +NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf +NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23 +NOTICE: f_leak => 98f13708210194c475687be6106a3b84 + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 +(11 rows) + +EXPLAIN (COSTS OFF) +WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1; + QUERY PLAN +----------------------------------------- + CTE Scan on cte1 + CTE cte1 + -> Seq Scan on t1 + Storage Filter: ((a % 2) = 0) + Filter: f_leak(b) +(5 rows) + +WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail +ERROR: new row violates row-level security policy for table "t1" +WITH ybtmp AS (DELETE FROM t1 RETURNING *) + INSERT INTO t1 SELECT * FROM ybtmp; -- YB: imitate PG ctid reallocation for the following update +WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 +(11 rows) + +WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail +ERROR: new row violates row-level security policy for table "t1" +WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok + a | b +----+--------- + 20 | Success +(1 row) + +-- +-- Rename Policy +-- +RESET SESSION AUTHORIZATION; +ALTER POLICY p1 ON t1 RENAME TO p1; --fail +ERROR: policy "p1" for table "t1" already exists +SELECT polname, relname + FROM pg_policy pol + JOIN pg_class pc ON (pc.oid = pol.polrelid) + WHERE relname = 't1'; + polname | relname +---------+--------- + p1 | t1 +(1 row) + +ALTER POLICY p1 ON t1 RENAME TO p2; --ok +SELECT polname, relname + FROM pg_policy pol + JOIN pg_class pc ON (pc.oid = pol.polrelid) + WHERE relname = 't1'; + polname | relname +---------+--------- + p2 | t1 +(1 row) + +-- +-- Check INSERT SELECT +-- +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE TABLE t2 (a integer, b text); +INSERT INTO t2 (SELECT * FROM t1); +EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM t1); + QUERY PLAN +--------------------------------------- + Insert on t2 + -> Seq Scan on t1 + Storage Filter: ((a % 2) = 0) +(3 rows) + +SELECT * FROM t2; + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 + 20 | Success +(12 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t2; + QUERY PLAN +---------------- + Seq Scan on t2 +(1 row) + +CREATE TABLE t3 AS SELECT * FROM t1; +SELECT * FROM t3; + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 + 20 | Success +(12 rows) + +SELECT * INTO t4 FROM t1; +SELECT * FROM t4; + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 + 20 | Success +(12 rows) + +-- +-- RLS with JOIN +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE blog (id integer, author text, post text); +CREATE TABLE comment (blog_id integer, message text); +GRANT ALL ON blog, comment TO regress_rls_bob; +CREATE POLICY blog_1 ON blog USING (id % 2 = 0); +ALTER TABLE blog ENABLE ROW LEVEL SECURITY; +INSERT INTO blog VALUES + (1, 'alice', 'blog #1'), + (2, 'bob', 'blog #1'), + (3, 'alice', 'blog #2'), + (4, 'alice', 'blog #3'), + (5, 'john', 'blog #1'); +INSERT INTO comment VALUES + (1, 'cool blog'), + (1, 'fun blog'), + (3, 'crazy blog'), + (5, 'what?'), + (4, 'insane!'), + (2, 'who did it?'); +SET SESSION AUTHORIZATION regress_rls_bob; +-- Check RLS JOIN with Non-RLS. +/*+ HashJoin(comment blog) Leading((comment blog)) */ -- YB: use pg_hint to ensure same query plan and result ordering as postgres +SELECT id, author, message FROM blog JOIN comment ON id = blog_id; + id | author | message +----+--------+------------- + 4 | alice | insane! + 2 | bob | who did it? +(2 rows) + +-- Check Non-RLS JOIN with RLS. +/*+ HashJoin(comment blog) Leading((comment blog)) */ -- YB: use pg_hint to ensure same query plan and result ordering as postgres +SELECT id, author, message FROM comment JOIN blog ON id = blog_id; + id | author | message +----+--------+------------- + 4 | alice | insane! + 2 | bob | who did it? +(2 rows) + +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE POLICY comment_1 ON comment USING (blog_id < 4); +ALTER TABLE comment ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +-- Check RLS JOIN RLS +SELECT id, author, message FROM blog JOIN comment ON id = blog_id; + id | author | message +----+--------+------------- + 2 | bob | who did it? +(1 row) + +SELECT id, author, message FROM comment JOIN blog ON id = blog_id; + id | author | message +----+--------+------------- + 2 | bob | who did it? +(1 row) + +SET SESSION AUTHORIZATION regress_rls_alice; +DROP TABLE blog, comment; +-- +-- Default Deny Policy +-- +RESET SESSION AUTHORIZATION; +DROP POLICY p2 ON t1; +ALTER TABLE t1 OWNER TO regress_rls_alice; +-- Check that default deny does not apply to superuser. +RESET SESSION AUTHORIZATION; +SELECT * FROM t1; + a | b +----+---------------------------------- + 1 | c4ca4238a0b923820dcc509a6f75849b + 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + 5 | e4da3b7fbbce2345d7772b0674a318d5 + 7 | 8f14e45fceea167a5a36dedd4bea2543 + 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 11 | 6512bd43d9caa6e02c990b0a82652dca + 13 | c51ce410c124a10e0db5e4b97fc2af39 + 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3 + 17 | 70efdf2ec9b086079795c442636b55fb + 19 | 1f0e3dad99908345f7439f8ffabdffc4 + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 + 20 | Success +(22 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1; + QUERY PLAN +---------------- + Seq Scan on t1 +(1 row) + +-- Check that default deny does not apply to table owner. +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM t1; + a | b +----+---------------------------------- + 1 | c4ca4238a0b923820dcc509a6f75849b + 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + 5 | e4da3b7fbbce2345d7772b0674a318d5 + 7 | 8f14e45fceea167a5a36dedd4bea2543 + 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 11 | 6512bd43d9caa6e02c990b0a82652dca + 13 | c51ce410c124a10e0db5e4b97fc2af39 + 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3 + 17 | 70efdf2ec9b086079795c442636b55fb + 19 | 1f0e3dad99908345f7439f8ffabdffc4 + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 + 20 | Success +(22 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1; + QUERY PLAN +---------------- + Seq Scan on t1 +(1 row) + +-- Check that default deny applies to non-owner/non-superuser when RLS on. +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +SELECT * FROM t1; + a | b +---+--- +(0 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1; + QUERY PLAN +-------------------------- + Result t1 + One-Time Filter: false +(2 rows) + +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM t1; + a | b +---+--- +(0 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1; + QUERY PLAN +-------------------------- + Result t1 + One-Time Filter: false +(2 rows) + +-- +-- COPY TO/FROM +-- +RESET SESSION AUTHORIZATION; +DROP TABLE copy_t CASCADE; +ERROR: table "copy_t" does not exist +CREATE TABLE copy_t (a integer, b text); +CREATE POLICY p1 ON copy_t USING (a % 2 = 0); +ALTER TABLE copy_t ENABLE ROW LEVEL SECURITY; +GRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user; +INSERT INTO copy_t (SELECT x, md5(x::text) FROM generate_series(0,10) x); +-- Check COPY TO as Superuser/owner. +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; +0,cfcd208495d565ef66e7dff9f98764da +1,c4ca4238a0b923820dcc509a6f75849b +2,c81e728d9d4c2f636f067f89cc14862c +3,eccbc87e4b5ce2fe28308fd9f2a7baf3 +4,a87ff679a2f3e71d9181a67b7542122c +5,e4da3b7fbbce2345d7772b0674a318d5 +6,1679091c5a880faf6fb5e6087eb1b2dc +7,8f14e45fceea167a5a36dedd4bea2543 +8,c9f0f895fb98ab9159f51fd0297e236d +9,45c48cce2e2d7fbdea1afc51c7c6ad26 +10,d3d9446802a44259755d38e6d163e820 +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; +0,cfcd208495d565ef66e7dff9f98764da +1,c4ca4238a0b923820dcc509a6f75849b +2,c81e728d9d4c2f636f067f89cc14862c +3,eccbc87e4b5ce2fe28308fd9f2a7baf3 +4,a87ff679a2f3e71d9181a67b7542122c +5,e4da3b7fbbce2345d7772b0674a318d5 +6,1679091c5a880faf6fb5e6087eb1b2dc +7,8f14e45fceea167a5a36dedd4bea2543 +8,c9f0f895fb98ab9159f51fd0297e236d +9,45c48cce2e2d7fbdea1afc51c7c6ad26 +10,d3d9446802a44259755d38e6d163e820 +-- Check COPY TO as user with permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO OFF; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS +ERROR: query would be affected by row-level security policy for table "copy_t" +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok +0,cfcd208495d565ef66e7dff9f98764da +2,c81e728d9d4c2f636f067f89cc14862c +4,a87ff679a2f3e71d9181a67b7542122c +6,1679091c5a880faf6fb5e6087eb1b2dc +8,c9f0f895fb98ab9159f51fd0297e236d +10,d3d9446802a44259755d38e6d163e820 +-- Check COPY TO as user with permissions and BYPASSRLS +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO OFF; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok +0,cfcd208495d565ef66e7dff9f98764da +1,c4ca4238a0b923820dcc509a6f75849b +2,c81e728d9d4c2f636f067f89cc14862c +3,eccbc87e4b5ce2fe28308fd9f2a7baf3 +4,a87ff679a2f3e71d9181a67b7542122c +5,e4da3b7fbbce2345d7772b0674a318d5 +6,1679091c5a880faf6fb5e6087eb1b2dc +7,8f14e45fceea167a5a36dedd4bea2543 +8,c9f0f895fb98ab9159f51fd0297e236d +9,45c48cce2e2d7fbdea1afc51c7c6ad26 +10,d3d9446802a44259755d38e6d163e820 +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok +0,cfcd208495d565ef66e7dff9f98764da +1,c4ca4238a0b923820dcc509a6f75849b +2,c81e728d9d4c2f636f067f89cc14862c +3,eccbc87e4b5ce2fe28308fd9f2a7baf3 +4,a87ff679a2f3e71d9181a67b7542122c +5,e4da3b7fbbce2345d7772b0674a318d5 +6,1679091c5a880faf6fb5e6087eb1b2dc +7,8f14e45fceea167a5a36dedd4bea2543 +8,c9f0f895fb98ab9159f51fd0297e236d +9,45c48cce2e2d7fbdea1afc51c7c6ad26 +10,d3d9446802a44259755d38e6d163e820 +-- Check COPY TO as user without permissions. SET row_security TO OFF; +SET SESSION AUTHORIZATION regress_rls_carol; +SET row_security TO OFF; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS +ERROR: query would be affected by row-level security policy for table "copy_t" +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied +ERROR: permission denied for table copy_t +-- Check COPY relation TO; keep it just one row to avoid reordering issues +RESET SESSION AUTHORIZATION; +SET row_security TO ON; +CREATE TABLE copy_rel_to (a integer, b text); +CREATE POLICY p1 ON copy_rel_to USING (a % 2 = 0); +ALTER TABLE copy_rel_to ENABLE ROW LEVEL SECURITY; +GRANT ALL ON copy_rel_to TO regress_rls_bob, regress_rls_exempt_user; +INSERT INTO copy_rel_to VALUES (1, md5('1')); +-- Check COPY TO as Superuser/owner. +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; +1,c4ca4238a0b923820dcc509a6f75849b +SET row_security TO ON; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; +1,c4ca4238a0b923820dcc509a6f75849b +-- Check COPY TO as user with permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO OFF; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS +ERROR: query would be affected by row-level security policy for table "copy_rel_to" +SET row_security TO ON; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok +-- Check COPY TO as user with permissions and BYPASSRLS +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO OFF; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok +1,c4ca4238a0b923820dcc509a6f75849b +SET row_security TO ON; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok +1,c4ca4238a0b923820dcc509a6f75849b +-- Check COPY TO as user without permissions. SET row_security TO OFF; +SET SESSION AUTHORIZATION regress_rls_carol; +SET row_security TO OFF; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied +ERROR: permission denied for table copy_rel_to +SET row_security TO ON; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied +ERROR: permission denied for table copy_rel_to +-- Check COPY FROM as Superuser/owner. +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +COPY copy_t FROM STDIN; --ok +SET row_security TO ON; +COPY copy_t FROM STDIN; --ok +-- Check COPY FROM as user with permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO OFF; +COPY copy_t FROM STDIN; --fail - would be affected by RLS. +ERROR: query would be affected by row-level security policy for table "copy_t" +SET row_security TO ON; +COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS. +ERROR: COPY FROM not supported with row-level security +HINT: Use INSERT statements instead. +-- Check COPY FROM as user with permissions and BYPASSRLS +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO ON; +COPY copy_t FROM STDIN; --ok +-- Check COPY FROM as user without permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SET row_security TO OFF; +COPY copy_t FROM STDIN; --fail - permission denied. +ERROR: permission denied for table copy_t +SET row_security TO ON; +COPY copy_t FROM STDIN; --fail - permission denied. +ERROR: permission denied for table copy_t +RESET SESSION AUTHORIZATION; +DROP TABLE copy_t; +DROP TABLE copy_rel_to CASCADE; +-- Check WHERE CURRENT OF +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE current_check (currentid int, payload text, rlsuser text); +GRANT ALL ON current_check TO PUBLIC; +INSERT INTO current_check VALUES + (1, 'abc', 'regress_rls_bob'), + (2, 'bcd', 'regress_rls_bob'), + (3, 'cde', 'regress_rls_bob'), + (4, 'def', 'regress_rls_bob'); +CREATE POLICY p1 ON current_check FOR SELECT USING (currentid % 2 = 0); +CREATE POLICY p2 ON current_check FOR DELETE USING (currentid = 4 AND rlsuser = current_user); +CREATE POLICY p3 ON current_check FOR UPDATE USING (currentid = 4) WITH CHECK (rlsuser = current_user); +ALTER TABLE current_check ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +-- Can SELECT even rows +SELECT * FROM current_check; + currentid | payload | rlsuser +-----------+---------+----------------- + 2 | bcd | regress_rls_bob + 4 | def | regress_rls_bob +(2 rows) + +-- Cannot UPDATE row 2 +UPDATE current_check SET payload = payload || '_new' WHERE currentid = 2 RETURNING *; + currentid | payload | rlsuser +-----------+---------+--------- +(0 rows) + +BEGIN; +DECLARE current_check_cursor SCROLL CURSOR FOR SELECT * FROM current_check; +-- Returns rows that can be seen according to SELECT policy, like plain SELECT +-- above (even rows) +FETCH ABSOLUTE 1 FROM current_check_cursor; +ERROR: FETCH ABSOLUTE not supported yet +LINE 1: FETCH ABSOLUTE 1 FROM current_check_cursor; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/6514. React with thumbs up to raise its priority +-- YB: Port further queries when above is supported +COMMIT; +-- +-- check pg_stats view filtering +-- +SET row_security TO ON; +SET SESSION AUTHORIZATION regress_rls_alice; +ANALYZE current_check; +-- Stats visible +SELECT row_security_active('current_check'); + row_security_active +--------------------- + f +(1 row) + +SELECT attname, most_common_vals FROM pg_stats + WHERE tablename = 'current_check' + ORDER BY 1; + attname | most_common_vals +-----------+------------------- + currentid | + payload | + rlsuser | {regress_rls_bob} +(3 rows) + +SET SESSION AUTHORIZATION regress_rls_bob; +-- Stats not visible +SELECT row_security_active('current_check'); + row_security_active +--------------------- + t +(1 row) + +SELECT attname, most_common_vals FROM pg_stats + WHERE tablename = 'current_check' + ORDER BY 1; + attname | most_common_vals +---------+------------------ +(0 rows) + +-- +-- Collation support +-- +BEGIN; +CREATE TABLE coll_t (c) AS VALUES ('bar'::text); +CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C")); +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; + inputcollid +------------------ + inputcollid 950 +(1 row) + +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM coll_t; + c +----- + bar +(1 row) + +ROLLBACK; +DROP TABLE coll_t; -- YB: workaround for lack of transaction DDL +-- +-- Shared Object Dependencies +-- +RESET SESSION AUTHORIZATION; +BEGIN; +CREATE ROLE regress_rls_eve; +CREATE ROLE regress_rls_frank; +CREATE TABLE tbl1 (c) AS VALUES ('bar'::text); +GRANT SELECT ON TABLE tbl1 TO regress_rls_eve; +CREATE POLICY P ON tbl1 TO regress_rls_eve, regress_rls_frank USING (true); +SELECT refclassid::regclass, deptype + FROM pg_depend + WHERE classid = 'pg_policy'::regclass + AND refobjid = 'tbl1'::regclass; + refclassid | deptype +------------+--------- + pg_class | a +(1 row) + +SELECT refclassid::regclass, deptype + FROM pg_shdepend + WHERE classid = 'pg_policy'::regclass + AND refobjid IN ('regress_rls_eve'::regrole, 'regress_rls_frank'::regrole); + refclassid | deptype +------------+--------- + pg_authid | r + pg_authid | r +(2 rows) + +SAVEPOINT q; +DROP ROLE regress_rls_eve; --fails due to dependency on POLICY p +ERROR: role "regress_rls_eve" cannot be dropped because some objects depend on it +DETAIL: privileges for table tbl1 +target of policy p on table tbl1 +ROLLBACK TO q; +ALTER POLICY p ON tbl1 TO regress_rls_frank USING (true); +SAVEPOINT q; +DROP ROLE regress_rls_eve; --fails due to dependency on GRANT SELECT +ERROR: role "regress_rls_eve" cannot be dropped because some objects depend on it +DETAIL: privileges for table tbl1 +ROLLBACK TO q; +REVOKE ALL ON TABLE tbl1 FROM regress_rls_eve; +SAVEPOINT q; +DROP ROLE regress_rls_eve; --succeeds +ROLLBACK TO q; +SAVEPOINT q; +DROP ROLE regress_rls_frank; --fails due to dependency on POLICY p +ERROR: role "regress_rls_frank" cannot be dropped because some objects depend on it +DETAIL: target of policy p on table tbl1 +ROLLBACK TO q; +DROP POLICY p ON tbl1; +SAVEPOINT q; +DROP ROLE regress_rls_frank; -- succeeds +ROLLBACK TO q; +ROLLBACK; -- cleanup +DROP TABLE tbl1; -- YB: workaround for lack of transactional DDL +-- +-- Converting table to view +-- +BEGIN; +CREATE TABLE t (c int); +CREATE POLICY p ON t USING (c % 2 = 1); +ALTER TABLE t ENABLE ROW LEVEL SECURITY; +SAVEPOINT q; +CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD + SELECT * FROM generate_series(1,5) t0(c); -- fails due to row-level security enabled +ERROR: could not convert table "t" to a view because it has row security enabled +ROLLBACK TO q; +ALTER TABLE t DISABLE ROW LEVEL SECURITY; +SAVEPOINT q; +CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD + SELECT * FROM generate_series(1,5) t0(c); -- fails due to policy p on t +ERROR: could not convert table "t" to a view because it has row security policies +ROLLBACK TO q; +DROP POLICY p ON t; +CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD + SELECT * FROM generate_series(1,5) t0(c); -- succeeds +ROLLBACK; +DROP VIEW t; -- YB: manually ROLLBACK and DROP the VIEW conversion above to prevent conflict with CREATE TABLE below (see #1404) +-- +-- Policy expression handling +-- +BEGIN; +CREATE TABLE t (c) AS VALUES ('bar'::text); +CREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions +ERROR: aggregate functions are not allowed in policy expressions +ROLLBACK; +DROP TABLE t; -- YB: workaround for lack of transactional DDL +-- +-- Non-target relations are only subject to SELECT policies +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE r1 (a int); +CREATE TABLE r2 (a int); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO r2 VALUES (10), (20); +GRANT ALL ON r1, r2 TO regress_rls_bob; +CREATE POLICY p1 ON r1 USING (true); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +CREATE POLICY p1 ON r2 FOR SELECT USING (true); +CREATE POLICY p2 ON r2 FOR INSERT WITH CHECK (false); +CREATE POLICY p3 ON r2 FOR UPDATE USING (false); +CREATE POLICY p4 ON r2 FOR DELETE USING (false); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM r1; + a +---- + 10 + 20 +(2 rows) + +SELECT * FROM r2; + a +---- + 10 + 20 +(2 rows) + +-- r2 is read-only +INSERT INTO r2 VALUES (2); -- Not allowed +ERROR: new row violates row-level security policy for table "r2" +UPDATE r2 SET a = 2 RETURNING *; -- Updates nothing + a +--- +(0 rows) + +DELETE FROM r2 RETURNING *; -- Deletes nothing + a +--- +(0 rows) + +-- r2 can be used as a non-target relation in DML +INSERT INTO r1 SELECT a + 1 FROM r2 RETURNING *; -- OK + a +---- + 11 + 21 +(2 rows) + +UPDATE r1 SET a = r2.a + 2 FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK + a | a +----+---- + 12 | 10 + 22 | 20 +(2 rows) + +DELETE FROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK + a | a +----+---- + 12 | 10 + 22 | 20 +(2 rows) + +SELECT * FROM r1; + a +---- + 11 + 21 +(2 rows) + +SELECT * FROM r2; + a +---- + 10 + 20 +(2 rows) + +SET SESSION AUTHORIZATION regress_rls_alice; +DROP TABLE r1; +DROP TABLE r2; +-- +-- FORCE ROW LEVEL SECURITY applies RLS to owners too +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security = on; +CREATE TABLE r1 (a int); +INSERT INTO r1 VALUES (10), (20); +CREATE POLICY p1 ON r1 USING (false); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; +-- No error, but no rows +TABLE r1; + a +--- +(0 rows) + +-- RLS error +INSERT INTO r1 VALUES (1); +ERROR: new row violates row-level security policy for table "r1" +-- No error (unable to see any rows to update) +UPDATE r1 SET a = 1; +TABLE r1; + a +--- +(0 rows) + +-- No error (unable to see any rows to delete) +DELETE FROM r1; +TABLE r1; + a +--- +(0 rows) + +SET row_security = off; +-- these all fail, would be affected by RLS +TABLE r1; +ERROR: query would be affected by row-level security policy for table "r1" +HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. +UPDATE r1 SET a = 1; +ERROR: query would be affected by row-level security policy for table "r1" +HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. +DELETE FROM r1; +ERROR: query would be affected by row-level security policy for table "r1" +HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. +DROP TABLE r1; +-- +-- FORCE ROW LEVEL SECURITY does not break RI +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security = on; +CREATE TABLE r1 (a int PRIMARY KEY); +CREATE TABLE r2 (a int REFERENCES r1); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO r2 VALUES (10), (20); +-- Create policies on r2 which prevent the +-- owner from seeing any rows, but RI should +-- still see them. +CREATE POLICY p1 ON r2 USING (false); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r2 FORCE ROW LEVEL SECURITY; +-- Errors due to rows in r2 +DELETE FROM r1; +ERROR: update or delete on table "r1" violates foreign key constraint "r2_a_fkey" on table "r2" +DETAIL: Key (a)=(10) is still referenced from table "r2". +-- Reset r2 to no-RLS +DROP POLICY p1 ON r2; +ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; +ALTER TABLE r2 DISABLE ROW LEVEL SECURITY; +-- clean out r2 for INSERT test below +DELETE FROM r2; +-- Change r1 to not allow rows to be seen +CREATE POLICY p1 ON r1 USING (false); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; +-- No rows seen +TABLE r1; + a +--- +(0 rows) + +-- No error, RI still sees that row exists in r1 +INSERT INTO r2 VALUES (10); +DROP TABLE r2; +DROP TABLE r1; +-- Ensure cascaded DELETE works +CREATE TABLE r1 (a int PRIMARY KEY); +CREATE TABLE r2 (a int REFERENCES r1 ON DELETE CASCADE); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO r2 VALUES (10), (20); +-- Create policies on r2 which prevent the +-- owner from seeing any rows, but RI should +-- still see them. +CREATE POLICY p1 ON r2 USING (false); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r2 FORCE ROW LEVEL SECURITY; +-- Deletes all records from both +DELETE FROM r1; +-- Remove FORCE from r2 +ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; +-- As owner, we now bypass RLS +-- verify no rows in r2 now +TABLE r2; + a +--- +(0 rows) + +DROP TABLE r2; +DROP TABLE r1; +-- Ensure cascaded UPDATE works +CREATE TABLE r1 (a int PRIMARY KEY); +CREATE TABLE r2 (a int REFERENCES r1 ON UPDATE CASCADE); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO r2 VALUES (10), (20); +-- Create policies on r2 which prevent the +-- owner from seeing any rows, but RI should +-- still see them. +CREATE POLICY p1 ON r2 USING (false); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r2 FORCE ROW LEVEL SECURITY; +-- Updates records in both +UPDATE r1 SET a = a+5; +-- Remove FORCE from r2 +ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; +-- As owner, we now bypass RLS +-- verify records in r2 updated +TABLE r2; + a +---- + 15 + 25 +(2 rows) + +DROP TABLE r2; +DROP TABLE r1; +-- +-- Test INSERT+RETURNING applies SELECT policies as +-- WithCheckOptions (meaning an error is thrown) +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security = on; +CREATE TABLE r1 (a int); +CREATE POLICY p1 ON r1 FOR SELECT USING (false); +CREATE POLICY p2 ON r1 FOR INSERT WITH CHECK (true); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; +-- Works fine +INSERT INTO r1 VALUES (10), (20); +-- No error, but no rows +TABLE r1; + a +--- +(0 rows) + +SET row_security = off; +-- fail, would be affected by RLS +TABLE r1; +ERROR: query would be affected by row-level security policy for table "r1" +HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. +SET row_security = on; +-- Error +INSERT INTO r1 VALUES (10), (20) RETURNING *; +ERROR: new row violates row-level security policy for table "r1" +DROP TABLE r1; +-- +-- Test UPDATE+RETURNING applies SELECT policies as +-- WithCheckOptions (meaning an error is thrown) +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security = on; +CREATE TABLE r1 (a int PRIMARY KEY); +CREATE POLICY p1 ON r1 FOR SELECT USING (a < 20); +CREATE POLICY p2 ON r1 FOR UPDATE USING (a < 20) WITH CHECK (true); +CREATE POLICY p3 ON r1 FOR INSERT WITH CHECK (true); +INSERT INTO r1 VALUES (10); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; +-- Works fine +UPDATE r1 SET a = 30; +-- Show updated rows +ALTER TABLE r1 NO FORCE ROW LEVEL SECURITY; +TABLE r1; + a +---- + 30 +(1 row) + +-- reset value in r1 for test with RETURNING +UPDATE r1 SET a = 10; +-- Verify row reset +TABLE r1; + a +---- + 10 +(1 row) + +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; +-- Error +UPDATE r1 SET a = 30 RETURNING *; +ERROR: new row violates row-level security policy for table "r1" +-- UPDATE path of INSERT ... ON CONFLICT DO UPDATE should also error out +INSERT INTO r1 VALUES (10) + ON CONFLICT (a) DO UPDATE SET a = 30 RETURNING *; +ERROR: new row violates row-level security policy for table "r1" +-- Should still error out without RETURNING (use of arbiter always requires +-- SELECT permissions) +INSERT INTO r1 VALUES (10) + ON CONFLICT (a) DO UPDATE SET a = 30; +ERROR: new row violates row-level security policy for table "r1" +INSERT INTO r1 VALUES (10) + ON CONFLICT ON CONSTRAINT r1_pkey DO UPDATE SET a = 30; +ERROR: new row violates row-level security policy for table "r1" +DROP TABLE r1; +-- Check dependency handling +RESET SESSION AUTHORIZATION; +CREATE TABLE dep1 (c1 int); +CREATE TABLE dep2 (c1 int); +CREATE POLICY dep_p1 ON dep1 TO regress_rls_bob USING (c1 > (select max(dep2.c1) from dep2)); +ALTER POLICY dep_p1 ON dep1 TO regress_rls_bob,regress_rls_carol; +-- Should return one +SELECT count(*) = 1 FROM pg_depend + WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') + AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2'); + ?column? +---------- + t +(1 row) + +ALTER POLICY dep_p1 ON dep1 USING (true); +-- Should return one +SELECT count(*) = 1 FROM pg_shdepend + WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') + AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_bob'); + ?column? +---------- + t +(1 row) + +-- Should return one +SELECT count(*) = 1 FROM pg_shdepend + WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') + AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_carol'); + ?column? +---------- + t +(1 row) + +-- Should return zero +SELECT count(*) = 0 FROM pg_depend + WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') + AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2'); + ?column? +---------- + t +(1 row) + +-- DROP OWNED BY testing +RESET SESSION AUTHORIZATION; +CREATE ROLE regress_rls_dob_role1; +CREATE ROLE regress_rls_dob_role2; +CREATE TABLE dob_t1 (c1 int); +CREATE TABLE dob_t2 (c1 int) PARTITION BY RANGE (c1); +CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1 USING (true); +DROP OWNED BY regress_rls_dob_role1; +DROP POLICY p1 ON dob_t1; -- should fail, already gone +ERROR: policy "p1" for table "dob_t1" does not exist +CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true); +DROP OWNED BY regress_rls_dob_role1; +DROP POLICY p1 ON dob_t1; -- should succeed +-- same cases with duplicate polroles entries +CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role1 USING (true); +DROP OWNED BY regress_rls_dob_role1; +DROP POLICY p1 ON dob_t1; -- should fail, already gone +ERROR: policy "p1" for table "dob_t1" does not exist +CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role1,regress_rls_dob_role2 USING (true); +DROP OWNED BY regress_rls_dob_role1; +DROP POLICY p1 ON dob_t1; -- should succeed +-- partitioned target +CREATE POLICY p1 ON dob_t2 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true); +DROP OWNED BY regress_rls_dob_role1; +DROP POLICY p1 ON dob_t2; -- should succeed +DROP USER regress_rls_dob_role1; +DROP USER regress_rls_dob_role2; +-- Bug #15708: view + table with RLS should check policies as view owner +CREATE TABLE ref_tbl (a int); +INSERT INTO ref_tbl VALUES (1); +CREATE TABLE rls_tbl (a int); +INSERT INTO rls_tbl VALUES (10); +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +CREATE POLICY p1 ON rls_tbl USING (EXISTS (SELECT 1 FROM ref_tbl)); +GRANT SELECT ON ref_tbl TO regress_rls_bob; +GRANT SELECT ON rls_tbl TO regress_rls_bob; +CREATE VIEW rls_view AS SELECT * FROM rls_tbl; +ALTER VIEW rls_view OWNER TO regress_rls_bob; +GRANT SELECT ON rls_view TO regress_rls_alice; +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM ref_tbl; -- Permission denied +ERROR: permission denied for table ref_tbl +SELECT * FROM rls_tbl; -- Permission denied +ERROR: permission denied for table rls_tbl +SELECT * FROM rls_view; -- OK + a +---- + 10 +(1 row) + +RESET SESSION AUTHORIZATION; +DROP VIEW rls_view; +DROP TABLE rls_tbl; +DROP TABLE ref_tbl; +-- Leaky operator test +CREATE TABLE rls_tbl (a int); +INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x; +ANALYZE rls_tbl; +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +GRANT SELECT ON rls_tbl TO regress_rls_alice; +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE FUNCTION op_leak(int, int) RETURNS bool + AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END' + LANGUAGE plpgsql; +CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int, + restrict = scalarltsel); +SELECT * FROM rls_tbl WHERE a <<< 1000; + a +--- +(0 rows) + +DROP OPERATOR <<< (int, int); +DROP FUNCTION op_leak(int, int); +RESET SESSION AUTHORIZATION; +DROP TABLE rls_tbl; +-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE rls_tbl (a int, b int, c int); +CREATE POLICY p1 ON rls_tbl USING (rls_tbl >= ROW(1,1,1)); +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +ALTER TABLE rls_tbl FORCE ROW LEVEL SECURITY; +INSERT INTO rls_tbl SELECT 10, 20, 30; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rls_tbl + SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss; + QUERY PLAN +-------------------------------------------------------------------- + Insert on regress_rls_schema.rls_tbl + -> Subquery Scan on ss + Output: ss.b, ss.c, NULL::integer + -> Sort + Output: rls_tbl_1.b, rls_tbl_1.c, rls_tbl_1.a + Sort Key: rls_tbl_1.a + -> Seq Scan on regress_rls_schema.rls_tbl rls_tbl_1 + Output: rls_tbl_1.b, rls_tbl_1.c, rls_tbl_1.a + Filter: (rls_tbl_1.* >= '(1,1,1)'::record) +(9 rows) + +INSERT INTO rls_tbl + SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss; +SELECT * FROM rls_tbl; + a | b | c +----+----+---- + 10 | 20 | 30 + 20 | 30 | +(2 rows) + +DROP TABLE rls_tbl; +RESET SESSION AUTHORIZATION; +-- CVE-2023-2455: inlining an SRF may introduce an RLS dependency +create table rls_t (c text); +insert into rls_t values ('invisible to bob'); +alter table rls_t enable row level security; +grant select on rls_t to regress_rls_alice, regress_rls_bob; +create policy p1 on rls_t for select to regress_rls_alice using (true); +create policy p2 on rls_t for select to regress_rls_bob using (false); +create function rls_f () returns setof rls_t + stable language sql + as $$ select * from rls_t $$; +prepare q as select current_user, * from rls_f(); +set role regress_rls_alice; +execute q; + current_user | c +-------------------+------------------ + regress_rls_alice | invisible to bob +(1 row) + +set role regress_rls_bob; +execute q; + current_user | c +--------------+--- +(0 rows) + +-- make sure RLS dependencies in CTEs are handled +reset role; +create or replace function rls_f() returns setof rls_t + stable language sql + as $$ with cte as (select * from rls_t) select * from cte $$; +prepare r as select current_user, * from rls_f(); +set role regress_rls_alice; +execute r; + current_user | c +-------------------+------------------ + regress_rls_alice | invisible to bob +(1 row) + +set role regress_rls_bob; +execute r; + current_user | c +--------------+--- +(0 rows) + +-- make sure RLS dependencies in subqueries are handled +reset role; +create or replace function rls_f() returns setof rls_t + stable language sql + as $$ select * from (select * from rls_t) _ $$; +prepare s as select current_user, * from rls_f(); +set role regress_rls_alice; +execute s; + current_user | c +-------------------+------------------ + regress_rls_alice | invisible to bob +(1 row) + +set role regress_rls_bob; +execute s; + current_user | c +--------------+--- +(0 rows) + +-- make sure RLS dependencies in sublinks are handled +reset role; +create or replace function rls_f() returns setof rls_t + stable language sql + as $$ select exists(select * from rls_t)::text $$; +prepare t as select current_user, * from rls_f(); +set role regress_rls_alice; +execute t; + current_user | c +-------------------+------ + regress_rls_alice | true +(1 row) + +set role regress_rls_bob; +execute t; + current_user | c +-----------------+------- + regress_rls_bob | false +(1 row) + +-- make sure RLS dependencies are handled when coercion projections are inserted +reset role; +create or replace function rls_f() returns setof rls_t + stable language sql + as $$ select * from (select array_agg(c) as cs from rls_t) _ group by cs $$; +prepare u as select current_user, * from rls_f(); +set role regress_rls_alice; +execute u; + current_user | c +-------------------+---------------------- + regress_rls_alice | {"invisible to bob"} +(1 row) + +set role regress_rls_bob; +execute u; + current_user | c +-----------------+--- + regress_rls_bob | +(1 row) + +-- make sure RLS dependencies in security invoker views are handled +reset role; +create view rls_v with (security_invoker) as select * from rls_t; +grant select on rls_v to regress_rls_alice, regress_rls_bob; +create or replace function rls_f() returns setof rls_t + stable language sql + as $$ select * from rls_v $$; +prepare v as select current_user, * from rls_f(); +set role regress_rls_alice; +execute v; + current_user | c +-------------------+------------------ + regress_rls_alice | invisible to bob +(1 row) + +set role regress_rls_bob; +execute v; + current_user | c +--------------+--- +(0 rows) + +RESET ROLE; +DROP FUNCTION rls_f(); +DROP VIEW rls_v; +DROP TABLE rls_t; +-- +-- Clean up objects +-- +RESET SESSION AUTHORIZATION; +DROP SCHEMA regress_rls_schema CASCADE; -- YB: output lacks bv1 due to comment-out above +NOTICE: drop cascades to 29 other objects +DETAIL: drop cascades to function f_leak(text) +drop cascades to table b1 +drop cascades to table category +drop cascades to table current_check +drop cascades to table dep1 +drop cascades to table dep2 +drop cascades to table dependent +drop cascades to table dob_t1 +drop cascades to table dob_t2 +drop cascades to table document +drop cascades to table part_document +drop cascades to table rec1 +drop cascades to table rec2 +drop cascades to table s1 +drop cascades to table s2 +drop cascades to table t1 +drop cascades to table t2 +drop cascades to table t3 +drop cascades to table t4 +drop cascades to table uaccount +drop cascades to table x1 +drop cascades to table y1 +drop cascades to table y2 +drop cascades to table z1 +drop cascades to table z1_blacklist +drop cascades to table z2 +drop cascades to view rec1v +drop cascades to view rec2v +drop cascades to view v2 +DROP USER regress_rls_alice; +DROP USER regress_rls_bob; +DROP USER regress_rls_carol; +DROP USER regress_rls_dave; +DROP USER regress_rls_exempt_user; +DROP ROLE regress_rls_group1; +DROP ROLE regress_rls_group2; +-- Arrange to have a few policies left over, for testing +-- pg_dump/pg_restore +CREATE SCHEMA regress_rls_schema; +CREATE TABLE rls_tbl (c1 int); +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +CREATE POLICY p1 ON rls_tbl USING (c1 > 5); +CREATE POLICY p2 ON rls_tbl FOR SELECT USING (c1 <= 3); +CREATE POLICY p3 ON rls_tbl FOR UPDATE USING (c1 <= 3) WITH CHECK (c1 > 5); +CREATE POLICY p4 ON rls_tbl FOR DELETE USING (c1 <= 3); +CREATE TABLE rls_tbl_force (c1 int); +ALTER TABLE rls_tbl_force ENABLE ROW LEVEL SECURITY; +ALTER TABLE rls_tbl_force FORCE ROW LEVEL SECURITY; +CREATE POLICY p1 ON rls_tbl_force USING (c1 = 5) WITH CHECK (c1 < 5); +CREATE POLICY p2 ON rls_tbl_force FOR SELECT USING (c1 = 8); +CREATE POLICY p3 ON rls_tbl_force FOR UPDATE USING (c1 = 8) WITH CHECK (c1 >= 5); +CREATE POLICY p4 ON rls_tbl_force FOR DELETE USING (c1 = 8); diff --git a/src/test/regress/expected/yb.port.rowtypes.out b/src/test/regress/expected/yb.port.rowtypes.out new file mode 100644 index 0000000000000..32f387d849a98 --- /dev/null +++ b/src/test/regress/expected/yb.port.rowtypes.out @@ -0,0 +1,1229 @@ +-- +-- ROWTYPES +-- +-- Make both a standalone composite type and a table rowtype +create type complex as (r float8, i float8); +create temp table fullname (first text, last text); +-- Nested composite +create type quad as (c1 complex, c2 complex); +-- Some simple tests of I/O conversions and row construction +select (1.1,2.2)::complex, row((3.3,4.4),(5.5,null))::quad; + row | row +-----------+------------------------ + (1.1,2.2) | ("(3.3,4.4)","(5.5,)") +(1 row) + +select row('Joe', 'Blow')::fullname, '(Joe,Blow)'::fullname; + row | fullname +------------+------------ + (Joe,Blow) | (Joe,Blow) +(1 row) + +select '(Joe,von Blow)'::fullname, '(Joe,d''Blow)'::fullname; + fullname | fullname +------------------+-------------- + (Joe,"von Blow") | (Joe,d'Blow) +(1 row) + +select '(Joe,"von""Blow")'::fullname, E'(Joe,d\\\\Blow)'::fullname; + fullname | fullname +-------------------+----------------- + (Joe,"von""Blow") | (Joe,"d\\Blow") +(1 row) + +select '(Joe,"Blow,Jr")'::fullname; + fullname +----------------- + (Joe,"Blow,Jr") +(1 row) + +select '(Joe,)'::fullname; -- ok, null 2nd column + fullname +---------- + (Joe,) +(1 row) + +select '(Joe)'::fullname; -- bad +ERROR: malformed record literal: "(Joe)" +LINE 1: select '(Joe)'::fullname; + ^ +DETAIL: Too few columns. +select '(Joe,,)'::fullname; -- bad +ERROR: malformed record literal: "(Joe,,)" +LINE 1: select '(Joe,,)'::fullname; + ^ +DETAIL: Too many columns. +select '[]'::fullname; -- bad +ERROR: malformed record literal: "[]" +LINE 1: select '[]'::fullname; + ^ +DETAIL: Missing left parenthesis. +select ' (Joe,Blow) '::fullname; -- ok, extra whitespace + fullname +------------ + (Joe,Blow) +(1 row) + +select '(Joe,Blow) /'::fullname; -- bad +ERROR: malformed record literal: "(Joe,Blow) /" +LINE 1: select '(Joe,Blow) /'::fullname; + ^ +DETAIL: Junk after right parenthesis. +create temp table quadtable(f1 int, q quad); +insert into quadtable values (1, ((3.3,4.4),(5.5,6.6))); +insert into quadtable values (2, ((null,4.4),(5.5,6.6))); +select * from quadtable; + f1 | q +----+--------------------------- + 1 | ("(3.3,4.4)","(5.5,6.6)") + 2 | ("(,4.4)","(5.5,6.6)") +(2 rows) + +select f1, q.c1 from quadtable; -- fails, q is a table reference +ERROR: missing FROM-clause entry for table "q" +LINE 1: select f1, q.c1 from quadtable; + ^ +select f1, (q).c1, (qq.q).c1.i from quadtable qq; + f1 | c1 | i +----+-----------+----- + 1 | (3.3,4.4) | 4.4 + 2 | (,4.4) | 4.4 +(2 rows) + +create temp table people (fn fullname, bd date); +insert into people values ('(Joe,Blow)', '1984-01-10'); +select * from people; + fn | bd +------------+------------ + (Joe,Blow) | 01-10-1984 +(1 row) + +-- at the moment this will not work due to ALTER TABLE inadequacy: +alter table fullname add column suffix text default ''; +ERROR: cannot alter table "fullname" because column "people.fn" uses its row type +-- but this should work: +alter table fullname add column suffix text default null; +select * from people; + fn | bd +-------------+------------ + (Joe,Blow,) | 01-10-1984 +(1 row) + +-- test insertion/updating of subfields +update people set fn.suffix = 'Jr'; +select * from people; + fn | bd +---------------+------------ + (Joe,Blow,Jr) | 01-10-1984 +(1 row) + +insert into quadtable (f1, q.c1.r, q.c2.i) values(44,55,66); +update quadtable set q.c1.r = 12 where f1 = 2; +update quadtable set q.c1 = 12; -- error, type mismatch +ERROR: subfield "c1" is of type complex but expression is of type integer +LINE 1: update quadtable set q.c1 = 12; + ^ +HINT: You will need to rewrite or cast the expression. +select * from quadtable; + f1 | q +----+--------------------------- + 1 | ("(3.3,4.4)","(5.5,6.6)") + 44 | ("(55,)","(,66)") + 2 | ("(12,4.4)","(5.5,6.6)") +(3 rows) + +-- The object here is to ensure that toasted references inside +-- composite values don't cause problems. The large f1 value will +-- be toasted inside pp, it must still work after being copied to people. +create temp table pp (f1 text); +-- insert into pp values (repeat('abcdefghijkl', 100000)); -- YB: TODO(jason): uncomment when issue #2003 is closed or closing. +insert into people select ('Jim', f1, null)::fullname, current_date from pp; +select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people; -- YB: output is missing a row due to commented out INSERT above + first | substr | length +-------+--------+-------- + Joe | Blow | 4 +(1 row) + +-- Test row comparison semantics. Prior to PG 8.2 we did this in a totally +-- non-spec-compliant way. +select ROW(1,2) < ROW(1,3) as true; + true +------ + t +(1 row) + +select ROW(1,2) < ROW(1,1) as false; + false +------- + f +(1 row) + +select ROW(1,2) < ROW(1,NULL) as null; + null +------ + +(1 row) + +select ROW(1,2,3) < ROW(1,3,NULL) as true; -- the NULL is not examined + true +------ + t +(1 row) + +select ROW(11,'ABC') < ROW(11,'DEF') as true; + true +------ + t +(1 row) + +select ROW(11,'ABC') > ROW(11,'DEF') as false; + false +------- + f +(1 row) + +select ROW(12,'ABC') > ROW(11,'DEF') as true; + true +------ + t +(1 row) + +-- = and <> have different NULL-behavior than < etc +select ROW(1,2,3) < ROW(1,NULL,4) as null; + null +------ + +(1 row) + +select ROW(1,2,3) = ROW(1,NULL,4) as false; + false +------- + f +(1 row) + +select ROW(1,2,3) <> ROW(1,NULL,4) as true; + true +------ + t +(1 row) + +-- We allow operators beyond the six standard ones, if they have btree +-- operator classes. +select ROW('ABC','DEF') ~<=~ ROW('DEF','ABC') as true; + true +------ + t +(1 row) + +select ROW('ABC','DEF') ~>=~ ROW('DEF','ABC') as false; + false +------- + f +(1 row) + +select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail; +ERROR: could not determine interpretation of row comparison operator ~~ +LINE 1: select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail; + ^ +HINT: Row comparison operators must be associated with btree operator families. +-- Comparisons of ROW() expressions can cope with some type mismatches +select ROW(1,2) = ROW(1,2::int8); + ?column? +---------- + t +(1 row) + +select ROW(1,2) in (ROW(3,4), ROW(1,2)); + ?column? +---------- + t +(1 row) + +select ROW(1,2) in (ROW(3,4), ROW(1,2::int8)); + ?column? +---------- + t +(1 row) + +-- Check row comparison with a subselect +select unique1, unique2 from tenk1 +where (unique1, unique2) < any (select ten, ten from tenk1 where hundred < 3) + and unique1 <= 20 +order by 1; + unique1 | unique2 +---------+--------- + 0 | 9998 + 1 | 2838 +(2 rows) + +-- Also check row comparison with an indexable condition +explain (costs off) +select thousand, tenthous from tenk1 +where (thousand, tenthous) >= (997, 5000) +order by thousand, tenthous; + QUERY PLAN +----------------------------------------------------------- + Index Only Scan using tenk1_thous_tenthous on tenk1 + Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000)) +(2 rows) + +select thousand, tenthous from tenk1 +where (thousand, tenthous) >= (997, 5000) +order by thousand, tenthous; + thousand | tenthous +----------+---------- + 997 | 5997 + 997 | 6997 + 997 | 7997 + 997 | 8997 + 997 | 9997 + 998 | 998 + 998 | 1998 + 998 | 2998 + 998 | 3998 + 998 | 4998 + 998 | 5998 + 998 | 6998 + 998 | 7998 + 998 | 8998 + 998 | 9998 + 999 | 999 + 999 | 1999 + 999 | 2999 + 999 | 3999 + 999 | 4999 + 999 | 5999 + 999 | 6999 + 999 | 7999 + 999 | 8999 + 999 | 9999 +(25 rows) + +explain (costs off) +select thousand, tenthous, four from tenk1 +where (thousand, tenthous, four) > (998, 5000, 3) +order by thousand, tenthous; -- YB: TODO(tim): update output when bitmap scan is supported + QUERY PLAN +--------------------------------------------------------------- + Index Scan using tenk1_thous_tenthous on tenk1 + Index Cond: (ROW(thousand, tenthous) >= ROW(998, 5000)) + Filter: (ROW(thousand, tenthous, four) > ROW(998, 5000, 3)) +(3 rows) + +select thousand, tenthous, four from tenk1 +where (thousand, tenthous, four) > (998, 5000, 3) +order by thousand, tenthous; + thousand | tenthous | four +----------+----------+------ + 998 | 5998 | 2 + 998 | 6998 | 2 + 998 | 7998 | 2 + 998 | 8998 | 2 + 998 | 9998 | 2 + 999 | 999 | 3 + 999 | 1999 | 3 + 999 | 2999 | 3 + 999 | 3999 | 3 + 999 | 4999 | 3 + 999 | 5999 | 3 + 999 | 6999 | 3 + 999 | 7999 | 3 + 999 | 8999 | 3 + 999 | 9999 | 3 +(15 rows) + +explain (costs off) +select thousand, tenthous from tenk1 +where (998, 5000) < (thousand, tenthous) +order by thousand, tenthous; + QUERY PLAN +---------------------------------------------------------- + Index Only Scan using tenk1_thous_tenthous on tenk1 + Index Cond: (ROW(thousand, tenthous) > ROW(998, 5000)) +(2 rows) + +select thousand, tenthous from tenk1 +where (998, 5000) < (thousand, tenthous) +order by thousand, tenthous; + thousand | tenthous +----------+---------- + 998 | 5998 + 998 | 6998 + 998 | 7998 + 998 | 8998 + 998 | 9998 + 999 | 999 + 999 | 1999 + 999 | 2999 + 999 | 3999 + 999 | 4999 + 999 | 5999 + 999 | 6999 + 999 | 7999 + 999 | 8999 + 999 | 9999 +(15 rows) + +explain (costs off) +select thousand, hundred from tenk1 +where (998, 5000) < (thousand, hundred) +order by thousand, hundred; -- YB: TODO(tim): update output when bitmap scan is supported + QUERY PLAN +----------------------------------------------------------- + Incremental Sort + Sort Key: thousand, hundred + Presorted Key: thousand + -> Index Scan using tenk1_thous_tenthous on tenk1 + Index Cond: (thousand >= 998) + Filter: (ROW(998, 5000) < ROW(thousand, hundred)) +(6 rows) + +select thousand, hundred from tenk1 +where (998, 5000) < (thousand, hundred) +order by thousand, hundred; + thousand | hundred +----------+--------- + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 + 999 | 99 +(10 rows) + +-- Test case for bug #14010: indexed row comparisons fail with nulls +create temp table test_table (a text, b text); +insert into test_table values ('a', 'b'); +insert into test_table select 'a', null from generate_series(1,1000); +insert into test_table values ('b', 'a'); +create index on test_table (a,b); +set enable_sort = off; +explain (costs off) +select a,b from test_table where (a,b) > ('a','a') order by a,b; + QUERY PLAN +-------------------------------------------------------- + Index Only Scan using test_table_a_b_idx on test_table + Index Cond: (ROW(a, b) > ROW('a'::text, 'a'::text)) +(2 rows) + +select a,b from test_table where (a,b) > ('a','a') order by a,b; + a | b +---+--- + a | b + b | a +(2 rows) + +reset enable_sort; +-- Check row comparisons with IN +select * from int8_tbl i8 where i8 in (row(123,456)); -- fail, type mismatch +ERROR: cannot compare dissimilar column types bigint and integer at record column 1 +explain (costs off) +select * from int8_tbl i8 +where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)'); + QUERY PLAN +------------------------------------------------------------------------------- + Seq Scan on int8_tbl i8 + Filter: (i8.* = ANY ('{"(123,456)","(4567890123456789,123)"}'::int8_tbl[])) +(2 rows) + +select * from int8_tbl i8 +where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)'); + q1 | q2 +------------------+----- + 123 | 456 + 4567890123456789 | 123 +(2 rows) + +-- Check ability to select columns from an anonymous rowtype +select (row(1, 2.0)).f1; + f1 +---- + 1 +(1 row) + +select (row(1, 2.0)).f2; + f2 +----- + 2.0 +(1 row) + +select (row(1, 2.0)).nosuch; -- fail +ERROR: could not identify column "nosuch" in record data type +LINE 1: select (row(1, 2.0)).nosuch; + ^ +select (row(1, 2.0)).*; + f1 | f2 +----+----- + 1 | 2.0 +(1 row) + +select (r).f1 from (select row(1, 2.0) as r) ss; + f1 +---- + 1 +(1 row) + +select (r).f3 from (select row(1, 2.0) as r) ss; -- fail +ERROR: could not identify column "f3" in record data type +LINE 1: select (r).f3 from (select row(1, 2.0) as r) ss; + ^ +select (r).* from (select row(1, 2.0) as r) ss; + f1 | f2 +----+----- + 1 | 2.0 +(1 row) + +-- Check some corner cases involving empty rowtypes +select ROW(); + row +----- + () +(1 row) + +select ROW() IS NULL; + ?column? +---------- + t +(1 row) + +select ROW() = ROW(); +ERROR: cannot compare rows of zero length +LINE 1: select ROW() = ROW(); + ^ +-- Check ability to create arrays of anonymous rowtypes +select array[ row(1,2), row(3,4), row(5,6) ]; + array +--------------------------- + {"(1,2)","(3,4)","(5,6)"} +(1 row) + +-- Check ability to compare an anonymous row to elements of an array +select row(1,1.1) = any (array[ row(7,7.7), row(1,1.1), row(0,0.0) ]); + ?column? +---------- + t +(1 row) + +select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]); + ?column? +---------- + f +(1 row) + +-- Check behavior with a non-comparable rowtype +create type cantcompare as (p point, r float8); +create temp table cc (f1 cantcompare); +insert into cc values('("(1,2)",3)'); +insert into cc values('("(4,5)",6)'); +select * from cc order by f1; -- fail, but should complain about cantcompare +ERROR: could not identify an ordering operator for type cantcompare +LINE 1: select * from cc order by f1; + ^ +HINT: Use an explicit ordering operator or modify the query. +-- +-- Tests for record_{eq,cmp} +-- +create type testtype1 as (a int, b int); +-- all true +select row(1, 2)::testtype1 < row(1, 3)::testtype1; + ?column? +---------- + t +(1 row) + +select row(1, 2)::testtype1 <= row(1, 3)::testtype1; + ?column? +---------- + t +(1 row) + +select row(1, 2)::testtype1 = row(1, 2)::testtype1; + ?column? +---------- + t +(1 row) + +select row(1, 2)::testtype1 <> row(1, 3)::testtype1; + ?column? +---------- + t +(1 row) + +select row(1, 3)::testtype1 >= row(1, 2)::testtype1; + ?column? +---------- + t +(1 row) + +select row(1, 3)::testtype1 > row(1, 2)::testtype1; + ?column? +---------- + t +(1 row) + +-- all false +select row(1, -2)::testtype1 < row(1, -3)::testtype1; + ?column? +---------- + f +(1 row) + +select row(1, -2)::testtype1 <= row(1, -3)::testtype1; + ?column? +---------- + f +(1 row) + +select row(1, -2)::testtype1 = row(1, -3)::testtype1; + ?column? +---------- + f +(1 row) + +select row(1, -2)::testtype1 <> row(1, -2)::testtype1; + ?column? +---------- + f +(1 row) + +select row(1, -3)::testtype1 >= row(1, -2)::testtype1; + ?column? +---------- + f +(1 row) + +select row(1, -3)::testtype1 > row(1, -2)::testtype1; + ?column? +---------- + f +(1 row) + +-- true, but see *< below +select row(1, -2)::testtype1 < row(1, 3)::testtype1; + ?column? +---------- + t +(1 row) + +-- mismatches +create type testtype3 as (a int, b text); +select row(1, 2)::testtype1 < row(1, 'abc')::testtype3; +ERROR: cannot compare dissimilar column types integer and text at record column 2 +select row(1, 2)::testtype1 <> row(1, 'abc')::testtype3; +ERROR: cannot compare dissimilar column types integer and text at record column 2 +create type testtype5 as (a int); +select row(1, 2)::testtype1 < row(1)::testtype5; +ERROR: cannot compare record types with different numbers of columns +select row(1, 2)::testtype1 <> row(1)::testtype5; +ERROR: cannot compare record types with different numbers of columns +-- non-comparable types +create type testtype6 as (a int, b point); +select row(1, '(1,2)')::testtype6 < row(1, '(1,3)')::testtype6; +ERROR: could not identify a comparison function for type point +select row(1, '(1,2)')::testtype6 <> row(1, '(1,3)')::testtype6; +ERROR: could not identify an equality operator for type point +drop type testtype1, testtype3, testtype5, testtype6; +-- +-- Tests for record_image_{eq,cmp} +-- +create type testtype1 as (a int, b int); +-- all true +select row(1, 2)::testtype1 *< row(1, 3)::testtype1; + ?column? +---------- + t +(1 row) + +select row(1, 2)::testtype1 *<= row(1, 3)::testtype1; + ?column? +---------- + t +(1 row) + +select row(1, 2)::testtype1 *= row(1, 2)::testtype1; + ?column? +---------- + t +(1 row) + +select row(1, 2)::testtype1 *<> row(1, 3)::testtype1; + ?column? +---------- + t +(1 row) + +select row(1, 3)::testtype1 *>= row(1, 2)::testtype1; + ?column? +---------- + t +(1 row) + +select row(1, 3)::testtype1 *> row(1, 2)::testtype1; + ?column? +---------- + t +(1 row) + +-- all false +select row(1, -2)::testtype1 *< row(1, -3)::testtype1; + ?column? +---------- + f +(1 row) + +select row(1, -2)::testtype1 *<= row(1, -3)::testtype1; + ?column? +---------- + f +(1 row) + +select row(1, -2)::testtype1 *= row(1, -3)::testtype1; + ?column? +---------- + f +(1 row) + +select row(1, -2)::testtype1 *<> row(1, -2)::testtype1; + ?column? +---------- + f +(1 row) + +select row(1, -3)::testtype1 *>= row(1, -2)::testtype1; + ?column? +---------- + f +(1 row) + +select row(1, -3)::testtype1 *> row(1, -2)::testtype1; + ?column? +---------- + f +(1 row) + +-- This returns the "wrong" order because record_image_cmp works on +-- unsigned datums without knowing about the actual data type. +select row(1, -2)::testtype1 *< row(1, 3)::testtype1; + ?column? +---------- + f +(1 row) + +-- other types +create type testtype2 as (a smallint, b bool); -- byval different sizes +select row(1, true)::testtype2 *< row(2, true)::testtype2; + ?column? +---------- + t +(1 row) + +select row(-2, true)::testtype2 *< row(-1, true)::testtype2; + ?column? +---------- + t +(1 row) + +select row(0, false)::testtype2 *< row(0, true)::testtype2; + ?column? +---------- + t +(1 row) + +select row(0, false)::testtype2 *<> row(0, true)::testtype2; + ?column? +---------- + t +(1 row) + +create type testtype3 as (a int, b text); -- variable length +select row(1, 'abc')::testtype3 *< row(1, 'abd')::testtype3; + ?column? +---------- + t +(1 row) + +select row(1, 'abc')::testtype3 *< row(1, 'abcd')::testtype3; + ?column? +---------- + t +(1 row) + +select row(1, 'abc')::testtype3 *> row(1, 'abd')::testtype3; + ?column? +---------- + f +(1 row) + +select row(1, 'abc')::testtype3 *<> row(1, 'abd')::testtype3; + ?column? +---------- + t +(1 row) + +create type testtype4 as (a int, b point); -- by ref, fixed length +select row(1, '(1,2)')::testtype4 *< row(1, '(1,3)')::testtype4; + ?column? +---------- + t +(1 row) + +select row(1, '(1,2)')::testtype4 *<> row(1, '(1,3)')::testtype4; + ?column? +---------- + t +(1 row) + +-- mismatches +select row(1, 2)::testtype1 *< row(1, 'abc')::testtype3; +ERROR: cannot compare dissimilar column types integer and text at record column 2 +select row(1, 2)::testtype1 *<> row(1, 'abc')::testtype3; +ERROR: cannot compare dissimilar column types integer and text at record column 2 +create type testtype5 as (a int); +select row(1, 2)::testtype1 *< row(1)::testtype5; +ERROR: cannot compare record types with different numbers of columns +select row(1, 2)::testtype1 *<> row(1)::testtype5; +ERROR: cannot compare record types with different numbers of columns +-- non-comparable types +create type testtype6 as (a int, b point); +select row(1, '(1,2)')::testtype6 *< row(1, '(1,3)')::testtype6; + ?column? +---------- + t +(1 row) + +select row(1, '(1,2)')::testtype6 *>= row(1, '(1,3)')::testtype6; + ?column? +---------- + f +(1 row) + +select row(1, '(1,2)')::testtype6 *<> row(1, '(1,3)')::testtype6; + ?column? +---------- + t +(1 row) + +-- anonymous rowtypes in coldeflists +select q.a, q.b = row(2), q.c = array[row(3)], q.d = row(row(4)) from + unnest(array[row(1, row(2), array[row(3)], row(row(4))), + row(2, row(3), array[row(4)], row(row(5)))]) + as q(a int, b record, c record[], d record); + a | ?column? | ?column? | ?column? +---+----------+----------+---------- + 1 | t | t | t + 2 | f | f | f +(2 rows) + +drop type testtype1, testtype2, testtype3, testtype4, testtype5, testtype6; +-- +-- Test case derived from bug #5716: check multiple uses of a rowtype result +-- +BEGIN; +CREATE TABLE price ( + id SERIAL PRIMARY KEY, + active BOOLEAN NOT NULL, + price NUMERIC +); +CREATE TYPE price_input AS ( + id INTEGER, + price NUMERIC +); +CREATE TYPE price_key AS ( + id INTEGER +); +CREATE FUNCTION price_key_from_table(price) RETURNS price_key AS $$ + SELECT $1.id +$$ LANGUAGE SQL; +CREATE FUNCTION price_key_from_input(price_input) RETURNS price_key AS $$ + SELECT $1.id +$$ LANGUAGE SQL; +insert into price values (1,false,42), (10,false,100), (11,true,17.99); +UPDATE price + SET active = true, price = input_prices.price + FROM unnest(ARRAY[(10, 123.00), (11, 99.99)]::price_input[]) input_prices + WHERE price_key_from_table(price.*) = price_key_from_input(input_prices.*); +select * from price order by id; -- YB: add ordering since PK is default HASH instead of ASC; output differs with upstream PG on 123 + id | active | price +----+--------+------- + 1 | f | 42 + 10 | t | 123 + 11 | t | 99.99 +(3 rows) + +rollback; +-- +-- Test case derived from bug #9085: check * qualification of composite +-- parameters for SQL functions +-- +create temp table compos (f1 int, f2 text); +create function fcompos1(v compos) returns void as $$ +insert into compos values (v); -- fail +$$ language sql; +ERROR: column "f1" is of type integer but expression is of type compos +LINE 2: insert into compos values (v); -- fail + ^ +HINT: You will need to rewrite or cast the expression. +create function fcompos1(v compos) returns void as $$ +insert into compos values (v.*); +$$ language sql; +create function fcompos2(v compos) returns void as $$ +select fcompos1(v); +$$ language sql; +create function fcompos3(v compos) returns void as $$ +select fcompos1(fcompos3.v.*); +$$ language sql; +select fcompos1(row(1,'one')); + fcompos1 +---------- + +(1 row) + +select fcompos2(row(2,'two')); + fcompos2 +---------- + +(1 row) + +select fcompos3(row(3,'three')); + fcompos3 +---------- + +(1 row) + +select * from compos; + f1 | f2 +----+------- + 1 | one + 2 | two + 3 | three +(3 rows) + +-- +-- We allow I/O conversion casts from composite types to strings to be +-- invoked via cast syntax, but not functional syntax. This is because +-- the latter is too prone to be invoked unintentionally. +-- +select cast (fullname as text) from fullname; + fullname +---------- +(0 rows) + +select fullname::text from fullname; + fullname +---------- +(0 rows) + +select text(fullname) from fullname; -- error +ERROR: function text(fullname) does not exist +LINE 1: select text(fullname) from fullname; + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +select fullname.text from fullname; -- error +ERROR: column fullname.text does not exist +LINE 1: select fullname.text from fullname; + ^ +-- same, but RECORD instead of named composite type: +select cast (row('Jim', 'Beam') as text); + row +------------ + (Jim,Beam) +(1 row) + +select (row('Jim', 'Beam'))::text; + row +------------ + (Jim,Beam) +(1 row) + +select text(row('Jim', 'Beam')); -- error +ERROR: function text(record) does not exist +LINE 1: select text(row('Jim', 'Beam')); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +select (row('Jim', 'Beam')).text; -- error +ERROR: could not identify column "text" in record data type +LINE 1: select (row('Jim', 'Beam')).text; + ^ +-- +-- Check the equivalence of functional and column notation +-- +insert into fullname values ('Joe', 'Blow'); +select f.last from fullname f; + last +------ + Blow +(1 row) + +select last(f) from fullname f; + last +------ + Blow +(1 row) + +create function longname(fullname) returns text language sql +as $$select $1.first || ' ' || $1.last$$; +select f.longname from fullname f; + longname +---------- + Joe Blow +(1 row) + +select longname(f) from fullname f; + longname +---------- + Joe Blow +(1 row) + +-- Starting in v11, the notational form does matter if there's ambiguity +alter table fullname add column longname text; +select f.longname from fullname f; + longname +---------- + +(1 row) + +select longname(f) from fullname f; + longname +---------- + Joe Blow +(1 row) + +-- +-- Test that composite values are seen to have the correct column names +-- (bug #11210 and other reports) +-- +select row_to_json(i) from int8_tbl i; + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +-- since "i" is of type "int8_tbl", attaching aliases doesn't change anything: +select row_to_json(i) from int8_tbl i(x,y); + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +-- in these examples, we'll report the exposed column names of the subselect: +select row_to_json(ss) from + (select q1, q2 from int8_tbl) as ss; + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +select row_to_json(ss) from + (select q1, q2 from int8_tbl offset 0) as ss; + row_to_json +------------------------------------------------ + {"q1":123,"q2":456} + {"q1":123,"q2":4567890123456789} + {"q1":4567890123456789,"q2":123} + {"q1":4567890123456789,"q2":4567890123456789} + {"q1":4567890123456789,"q2":-4567890123456789} +(5 rows) + +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl) as ss; + row_to_json +---------------------------------------------- + {"a":123,"b":456} + {"a":123,"b":4567890123456789} + {"a":4567890123456789,"b":123} + {"a":4567890123456789,"b":4567890123456789} + {"a":4567890123456789,"b":-4567890123456789} +(5 rows) + +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl offset 0) as ss; + row_to_json +---------------------------------------------- + {"a":123,"b":456} + {"a":123,"b":4567890123456789} + {"a":4567890123456789,"b":123} + {"a":4567890123456789,"b":4567890123456789} + {"a":4567890123456789,"b":-4567890123456789} +(5 rows) + +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl) as ss(x,y); + row_to_json +---------------------------------------------- + {"x":123,"y":456} + {"x":123,"y":4567890123456789} + {"x":4567890123456789,"y":123} + {"x":4567890123456789,"y":4567890123456789} + {"x":4567890123456789,"y":-4567890123456789} +(5 rows) + +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl offset 0) as ss(x,y); + row_to_json +---------------------------------------------- + {"x":123,"y":456} + {"x":123,"y":4567890123456789} + {"x":4567890123456789,"y":123} + {"x":4567890123456789,"y":4567890123456789} + {"x":4567890123456789,"y":-4567890123456789} +(5 rows) + +explain (costs off) +select row_to_json(q) from + (select thousand, tenthous from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; + QUERY PLAN +------------------------------------------------------------- + Subquery Scan on q + -> Index Only Scan using tenk1_thous_tenthous on tenk1 + Index Cond: ((thousand = 42) AND (tenthous < 2000)) +(3 rows) + +select row_to_json(q) from + (select thousand, tenthous from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; + row_to_json +--------------------------------- + {"thousand":42,"tenthous":42} + {"thousand":42,"tenthous":1042} +(2 rows) + +select row_to_json(q) from + (select thousand as x, tenthous as y from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; + row_to_json +------------------- + {"x":42,"y":42} + {"x":42,"y":1042} +(2 rows) + +select row_to_json(q) from + (select thousand as x, tenthous as y from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q(a,b); + row_to_json +------------------- + {"a":42,"b":42} + {"a":42,"b":1042} +(2 rows) + +/* YB: TODO(jason): uncomment when issue #1129 is closed or closing. +create temp table tt1 as select * from int8_tbl limit 2; +create temp table tt2 () inherits(tt1); +insert into tt2 values(0,0); +select row_to_json(r) from (select q2,q1 from tt1 offset 0) r; + +-- check no-op rowtype conversions +create temp table tt3 () inherits(tt2); +insert into tt3 values(33,44); +select row_to_json(tt3::tt2::tt1) from tt3; +*/ -- YB +-- +-- IS [NOT] NULL should not recurse into nested composites (bug #14235) +-- +explain (verbose, costs off) +select r, r is null as isnull, r is not null as isnotnull +from (values (1,row(1,2)), (1,row(null,null)), (1,null), + (null,row(1,2)), (null,row(null,null)), (null,null) ) r(a,b); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Values Scan on "*VALUES*" + Output: ROW("*VALUES*".column1, "*VALUES*".column2), (("*VALUES*".column1 IS NULL) AND ("*VALUES*".column2 IS NOT DISTINCT FROM NULL)), (("*VALUES*".column1 IS NOT NULL) AND ("*VALUES*".column2 IS DISTINCT FROM NULL)) +(2 rows) + +select r, r is null as isnull, r is not null as isnotnull +from (values (1,row(1,2)), (1,row(null,null)), (1,null), + (null,row(1,2)), (null,row(null,null)), (null,null) ) r(a,b); + r | isnull | isnotnull +-------------+--------+----------- + (1,"(1,2)") | f | t + (1,"(,)") | f | t + (1,) | f | f + (,"(1,2)") | f | f + (,"(,)") | f | f + (,) | t | f +(6 rows) + +explain (verbose, costs off) +with r(a,b) as materialized + (values (1,row(1,2)), (1,row(null,null)), (1,null), + (null,row(1,2)), (null,row(null,null)), (null,null) ) +select r, r is null as isnull, r is not null as isnotnull from r; + QUERY PLAN +---------------------------------------------------------- + CTE Scan on r + Output: r.*, (r.* IS NULL), (r.* IS NOT NULL) + CTE r + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2 +(5 rows) + +with r(a,b) as materialized + (values (1,row(1,2)), (1,row(null,null)), (1,null), + (null,row(1,2)), (null,row(null,null)), (null,null) ) +select r, r is null as isnull, r is not null as isnotnull from r; + r | isnull | isnotnull +-------------+--------+----------- + (1,"(1,2)") | f | t + (1,"(,)") | f | t + (1,) | f | f + (,"(1,2)") | f | f + (,"(,)") | f | f + (,) | t | f +(6 rows) + +-- +-- Tests for component access / FieldSelect +-- +CREATE TABLE compositetable(a text, b text); +INSERT INTO compositetable(a, b) VALUES('fa', 'fb'); +-- composite type columns can't directly be accessed (error) +SELECT d.a FROM (SELECT compositetable AS d FROM compositetable) s; +ERROR: missing FROM-clause entry for table "d" +LINE 1: SELECT d.a FROM (SELECT compositetable AS d FROM compositeta... + ^ +-- but can be accessed with proper parens +SELECT (d).a, (d).b FROM (SELECT compositetable AS d FROM compositetable) s; + a | b +----+---- + fa | fb +(1 row) + +-- system columns can't be accessed in composite types (error) +SELECT (d).ctid FROM (SELECT compositetable AS d FROM compositetable) s; +ERROR: column "ctid" not found in data type compositetable +LINE 1: SELECT (d).ctid FROM (SELECT compositetable AS d FROM compos... + ^ +-- accessing non-existing column in NULL datum errors out +SELECT (NULL::compositetable).nonexistent; +ERROR: column "nonexistent" not found in data type compositetable +LINE 1: SELECT (NULL::compositetable).nonexistent; + ^ +-- existing column in a NULL composite yield NULL +SELECT (NULL::compositetable).a; + a +--- + +(1 row) + +-- oids can't be accessed in composite types (error) +SELECT (NULL::compositetable).oid; +ERROR: column "oid" not found in data type compositetable +LINE 1: SELECT (NULL::compositetable).oid; + ^ +DROP TABLE compositetable; diff --git a/src/test/regress/expected/yb.port.rules.out b/src/test/regress/expected/yb.port.rules.out new file mode 100644 index 0000000000000..4f6d33f8930e5 --- /dev/null +++ b/src/test/regress/expected/yb.port.rules.out @@ -0,0 +1,2893 @@ +-- +-- RULES +-- From Jan's original setup_ruletest.sql and run_ruletest.sql +-- - thomas 1998-09-13 +-- +-- +-- Tables and rules for the view test +-- +create table rtest_t1 (a int4, b int4); +create table rtest_t2 (a int4, b int4); +create table rtest_t3 (a int4, b int4); +create view rtest_v1 as select * from rtest_t1; +create rule rtest_v1_ins as on insert to rtest_v1 do instead + insert into rtest_t1 values (new.a, new.b); +create rule rtest_v1_upd as on update to rtest_v1 do instead + update rtest_t1 set a = new.a, b = new.b + where a = old.a; +create rule rtest_v1_del as on delete to rtest_v1 do instead + delete from rtest_t1 where a = old.a; +-- Test comments +COMMENT ON RULE rtest_v1_bad ON rtest_v1 IS 'bad rule'; +ERROR: rule "rtest_v1_bad" for relation "rtest_v1" does not exist +COMMENT ON RULE rtest_v1_del ON rtest_v1 IS 'delete rule'; +COMMENT ON RULE rtest_v1_del ON rtest_v1 IS NULL; +-- +-- Tables and rules for the constraint update/delete test +-- +-- Note: +-- Now that we have multiple action rule support, we check +-- both possible syntaxes to define them (The last action +-- can but must not have a semicolon at the end). +-- +create table rtest_system (sysname text, sysdesc text); +create table rtest_interface (sysname text, ifname text); +create table rtest_person (pname text, pdesc text); +create table rtest_admin (pname text, sysname text); +create rule rtest_sys_upd as on update to rtest_system do also ( + update rtest_interface set sysname = new.sysname + where sysname = old.sysname; + update rtest_admin set sysname = new.sysname + where sysname = old.sysname + ); +create rule rtest_sys_del as on delete to rtest_system do also ( + delete from rtest_interface where sysname = old.sysname; + delete from rtest_admin where sysname = old.sysname; + ); +create rule rtest_pers_upd as on update to rtest_person do also + update rtest_admin set pname = new.pname where pname = old.pname; +create rule rtest_pers_del as on delete to rtest_person do also + delete from rtest_admin where pname = old.pname; +-- +-- Tables and rules for the logging test +-- +create table rtest_emp (ename char(20), salary money); +create table rtest_emplog (ename char(20), who name, action char(10), newsal money, oldsal money); +create table rtest_empmass (ename char(20), salary money); +create rule rtest_emp_ins as on insert to rtest_emp do + insert into rtest_emplog values (new.ename, current_user, + 'hired', new.salary, '0.00'); +create rule rtest_emp_upd as on update to rtest_emp where new.salary != old.salary do + insert into rtest_emplog values (new.ename, current_user, + 'honored', new.salary, old.salary); +create rule rtest_emp_del as on delete to rtest_emp do + insert into rtest_emplog values (old.ename, current_user, + 'fired', '0.00', old.salary); +-- +-- Tables and rules for the multiple cascaded qualified instead +-- rule test +-- +create table rtest_t4 (a int4, b text); +create table rtest_t5 (a int4, b text); +create table rtest_t6 (a int4, b text); +create table rtest_t7 (a int4, b text); +create table rtest_t8 (a int4, b text); +create table rtest_t9 (a int4, b text); +create rule rtest_t4_ins1 as on insert to rtest_t4 + where new.a >= 10 and new.a < 20 do instead + insert into rtest_t5 values (new.a, new.b); +create rule rtest_t4_ins2 as on insert to rtest_t4 + where new.a >= 20 and new.a < 30 do + insert into rtest_t6 values (new.a, new.b); +create rule rtest_t5_ins as on insert to rtest_t5 + where new.a > 15 do + insert into rtest_t7 values (new.a, new.b); +create rule rtest_t6_ins as on insert to rtest_t6 + where new.a > 25 do instead + insert into rtest_t8 values (new.a, new.b); +-- +-- Tables and rules for the rule fire order test +-- +-- As of PG 7.3, the rules should fire in order by name, regardless +-- of INSTEAD attributes or creation order. +-- +create table rtest_order1 (a int4); +create table rtest_order2 (a int4, b int4, c text); +create sequence rtest_seq; +create rule rtest_order_r3 as on insert to rtest_order1 do instead + insert into rtest_order2 values (new.a, nextval('rtest_seq'), + 'rule 3 - this should run 3rd'); +create rule rtest_order_r4 as on insert to rtest_order1 + where a < 100 do instead + insert into rtest_order2 values (new.a, nextval('rtest_seq'), + 'rule 4 - this should run 4th'); +create rule rtest_order_r2 as on insert to rtest_order1 do + insert into rtest_order2 values (new.a, nextval('rtest_seq'), + 'rule 2 - this should run 2nd'); +create rule rtest_order_r1 as on insert to rtest_order1 do instead + insert into rtest_order2 values (new.a, nextval('rtest_seq'), + 'rule 1 - this should run 1st'); +-- +-- Tables and rules for the instead nothing test +-- +create table rtest_nothn1 (a int4, b text); +create table rtest_nothn2 (a int4, b text); +create table rtest_nothn3 (a int4, b text); +create table rtest_nothn4 (a int4, b text); +create rule rtest_nothn_r1 as on insert to rtest_nothn1 + where new.a >= 10 and new.a < 20 do instead nothing; +create rule rtest_nothn_r2 as on insert to rtest_nothn1 + where new.a >= 30 and new.a < 40 do instead nothing; +create rule rtest_nothn_r3 as on insert to rtest_nothn2 + where new.a >= 100 do instead + insert into rtest_nothn3 values (new.a, new.b); +create rule rtest_nothn_r4 as on insert to rtest_nothn2 + do instead nothing; +-- +-- Tests on a view that is select * of a table +-- and has insert/update/delete instead rules to +-- behave close like the real table. +-- +-- +-- We need test date later +-- +insert into rtest_t2 values (1, 21); +insert into rtest_t2 values (2, 22); +insert into rtest_t2 values (3, 23); +insert into rtest_t3 values (1, 31); +insert into rtest_t3 values (2, 32); +insert into rtest_t3 values (3, 33); +insert into rtest_t3 values (4, 34); +insert into rtest_t3 values (5, 35); +-- insert values +insert into rtest_v1 values (1, 11); +insert into rtest_v1 values (2, 12); +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 11 + 2 | 12 +(2 rows) + +-- delete with constant expression +delete from rtest_v1 where a = 1; +select * from rtest_v1; + a | b +---+---- + 2 | 12 +(1 row) + +insert into rtest_v1 values (1, 11); +delete from rtest_v1 where b = 12; +select * from rtest_v1; + a | b +---+---- + 1 | 11 +(1 row) + +insert into rtest_v1 values (2, 12); +insert into rtest_v1 values (2, 13); +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 11 + 2 | 12 + 2 | 13 +(3 rows) + +** Remember the delete rule on rtest_v1: It says +** DO INSTEAD DELETE FROM rtest_t1 WHERE a = old.a +** So this time both rows with a = 2 must get deleted +\p +** Remember the delete rule on rtest_v1: It says +** DO INSTEAD DELETE FROM rtest_t1 WHERE a = old.a +** So this time both rows with a = 2 must get deleted +\r +delete from rtest_v1 where b = 12; +select * from rtest_v1; + a | b +---+---- + 1 | 11 +(1 row) + +delete from rtest_v1; +-- insert select +insert into rtest_v1 select * from rtest_t2; +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 21 + 2 | 22 + 3 | 23 +(3 rows) + +delete from rtest_v1; +-- same with swapped targetlist +insert into rtest_v1 (b, a) select b, a from rtest_t2; +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 21 + 2 | 22 + 3 | 23 +(3 rows) + +-- now with only one target attribute +insert into rtest_v1 (a) select a from rtest_t3; +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 21 + 1 | + 2 | 22 + 2 | + 3 | 23 + 3 | + 4 | + 5 | +(8 rows) + +select * from rtest_v1 where b isnull ORDER BY a, b; + a | b +---+--- + 1 | + 2 | + 3 | + 4 | + 5 | +(5 rows) + +-- let attribute a differ (must be done on rtest_t1 - see above) +update rtest_t1 set a = a + 10 where b isnull; +delete from rtest_v1 where b isnull; +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 21 + 2 | 22 + 3 | 23 +(3 rows) + +-- now updates with constant expression +update rtest_v1 set b = 42 where a = 2; +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 21 + 2 | 42 + 3 | 23 +(3 rows) + +update rtest_v1 set b = 99 where b = 42; +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 21 + 2 | 99 + 3 | 23 +(3 rows) + +update rtest_v1 set b = 88 where b < 50; +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 88 + 2 | 99 + 3 | 88 +(3 rows) + +delete from rtest_v1; +insert into rtest_v1 select rtest_t2.a, rtest_t3.b + from rtest_t2, rtest_t3 + where rtest_t2.a = rtest_t3.a; +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 31 + 2 | 32 + 3 | 33 +(3 rows) + +-- updates in a mergejoin +update rtest_v1 set b = rtest_t2.b from rtest_t2 where rtest_v1.a = rtest_t2.a; +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 21 + 2 | 22 + 3 | 23 +(3 rows) + +insert into rtest_v1 select * from rtest_t3; +select * from rtest_v1 ORDER BY a, b; + a | b +---+---- + 1 | 21 + 1 | 31 + 2 | 22 + 2 | 32 + 3 | 23 + 3 | 33 + 4 | 34 + 5 | 35 +(8 rows) + +update rtest_t1 set a = a + 10 where b > 30; +select * from rtest_v1 ORDER BY a, b; + a | b +----+---- + 1 | 21 + 2 | 22 + 3 | 23 + 11 | 31 + 12 | 32 + 13 | 33 + 14 | 34 + 15 | 35 +(8 rows) + +update rtest_v1 set a = rtest_t3.a + 20 from rtest_t3 where rtest_v1.b = rtest_t3.b; +select * from rtest_v1 ORDER BY a, b; + a | b +----+---- + 1 | 21 + 2 | 22 + 3 | 23 + 21 | 31 + 22 | 32 + 23 | 33 + 24 | 34 + 25 | 35 +(8 rows) + +-- +-- Test for constraint updates/deletes +-- +insert into rtest_system values ('orion', 'Linux Jan Wieck'); +insert into rtest_system values ('notjw', 'WinNT Jan Wieck (notebook)'); +insert into rtest_system values ('neptun', 'Fileserver'); +insert into rtest_interface values ('orion', 'eth0'); +insert into rtest_interface values ('orion', 'eth1'); +insert into rtest_interface values ('notjw', 'eth0'); +insert into rtest_interface values ('neptun', 'eth0'); +insert into rtest_person values ('jw', 'Jan Wieck'); +insert into rtest_person values ('bm', 'Bruce Momjian'); +insert into rtest_admin values ('jw', 'orion'); +insert into rtest_admin values ('jw', 'notjw'); +insert into rtest_admin values ('bm', 'neptun'); +update rtest_system set sysname = 'pluto' where sysname = 'neptun'; +select * from rtest_interface ORDER BY sysname, ifname; + sysname | ifname +---------+-------- + notjw | eth0 + orion | eth0 + orion | eth1 + pluto | eth0 +(4 rows) + +select * from rtest_admin ORDER BY pname, sysname; + pname | sysname +-------+--------- + bm | pluto + jw | notjw + jw | orion +(3 rows) + +update rtest_person set pname = 'jwieck' where pdesc = 'Jan Wieck'; +-- Note: use ORDER BY here to ensure consistent output across all systems. +-- The above UPDATE affects two rows with equal keys, so they could be +-- updated in either order depending on the whim of the local qsort(). +select * from rtest_admin order by pname, sysname; + pname | sysname +--------+--------- + bm | pluto + jwieck | notjw + jwieck | orion +(3 rows) + +delete from rtest_system where sysname = 'orion'; +select * from rtest_interface ORDER BY sysname, ifname; + sysname | ifname +---------+-------- + notjw | eth0 + pluto | eth0 +(2 rows) + +select * from rtest_admin ORDER BY pname, sysname; + pname | sysname +--------+--------- + bm | pluto + jwieck | notjw +(2 rows) + +-- +-- Rule qualification test +-- +insert into rtest_emp values ('wiecc', '5000.00'); +insert into rtest_emp values ('gates', '80000.00'); +update rtest_emp set ename = 'wiecx' where ename = 'wiecc'; +update rtest_emp set ename = 'wieck', salary = '6000.00' where ename = 'wiecx'; +update rtest_emp set salary = '7000.00' where ename = 'wieck'; +delete from rtest_emp where ename = 'gates'; +select ename, who = current_user as "matches user", action, newsal, oldsal from rtest_emplog order by ename, action, newsal; + ename | matches user | action | newsal | oldsal +----------------------+--------------+------------+------------+------------ + gates | t | fired | $0.00 | $80,000.00 + gates | t | hired | $80,000.00 | $0.00 + wiecc | t | hired | $5,000.00 | $0.00 + wieck | t | honored | $6,000.00 | $5,000.00 + wieck | t | honored | $7,000.00 | $6,000.00 +(5 rows) + +insert into rtest_empmass values ('meyer', '4000.00'); +insert into rtest_empmass values ('maier', '5000.00'); +insert into rtest_empmass values ('mayr', '6000.00'); +insert into rtest_emp select * from rtest_empmass; +select ename, who = current_user as "matches user", action, newsal, oldsal from rtest_emplog order by ename, action, newsal; + ename | matches user | action | newsal | oldsal +----------------------+--------------+------------+------------+------------ + gates | t | fired | $0.00 | $80,000.00 + gates | t | hired | $80,000.00 | $0.00 + maier | t | hired | $5,000.00 | $0.00 + mayr | t | hired | $6,000.00 | $0.00 + meyer | t | hired | $4,000.00 | $0.00 + wiecc | t | hired | $5,000.00 | $0.00 + wieck | t | honored | $6,000.00 | $5,000.00 + wieck | t | honored | $7,000.00 | $6,000.00 +(8 rows) + +update rtest_empmass set salary = salary + '1000.00'; +update rtest_emp set salary = rtest_empmass.salary from rtest_empmass where rtest_emp.ename = rtest_empmass.ename; +select ename, who = current_user as "matches user", action, newsal, oldsal from rtest_emplog order by ename, action, newsal; + ename | matches user | action | newsal | oldsal +----------------------+--------------+------------+------------+------------ + gates | t | fired | $0.00 | $80,000.00 + gates | t | hired | $80,000.00 | $0.00 + maier | t | hired | $5,000.00 | $0.00 + maier | t | honored | $6,000.00 | $5,000.00 + mayr | t | hired | $6,000.00 | $0.00 + mayr | t | honored | $7,000.00 | $6,000.00 + meyer | t | hired | $4,000.00 | $0.00 + meyer | t | honored | $5,000.00 | $4,000.00 + wiecc | t | hired | $5,000.00 | $0.00 + wieck | t | honored | $6,000.00 | $5,000.00 + wieck | t | honored | $7,000.00 | $6,000.00 +(11 rows) + +delete from rtest_emp using rtest_empmass where rtest_emp.ename = rtest_empmass.ename; +select ename, who = current_user as "matches user", action, newsal, oldsal from rtest_emplog order by ename, action, newsal; + ename | matches user | action | newsal | oldsal +----------------------+--------------+------------+------------+------------ + gates | t | fired | $0.00 | $80,000.00 + gates | t | hired | $80,000.00 | $0.00 + maier | t | fired | $0.00 | $6,000.00 + maier | t | hired | $5,000.00 | $0.00 + maier | t | honored | $6,000.00 | $5,000.00 + mayr | t | fired | $0.00 | $7,000.00 + mayr | t | hired | $6,000.00 | $0.00 + mayr | t | honored | $7,000.00 | $6,000.00 + meyer | t | fired | $0.00 | $5,000.00 + meyer | t | hired | $4,000.00 | $0.00 + meyer | t | honored | $5,000.00 | $4,000.00 + wiecc | t | hired | $5,000.00 | $0.00 + wieck | t | honored | $6,000.00 | $5,000.00 + wieck | t | honored | $7,000.00 | $6,000.00 +(14 rows) + +-- +-- Multiple cascaded qualified instead rule test +-- +insert into rtest_t4 values (1, 'Record should go to rtest_t4'); +insert into rtest_t4 values (2, 'Record should go to rtest_t4'); +insert into rtest_t4 values (10, 'Record should go to rtest_t5'); +insert into rtest_t4 values (15, 'Record should go to rtest_t5'); +insert into rtest_t4 values (19, 'Record should go to rtest_t5 and t7'); +insert into rtest_t4 values (20, 'Record should go to rtest_t4 and t6'); +insert into rtest_t4 values (26, 'Record should go to rtest_t4 and t8'); +insert into rtest_t4 values (28, 'Record should go to rtest_t4 and t8'); +insert into rtest_t4 values (30, 'Record should go to rtest_t4'); +insert into rtest_t4 values (40, 'Record should go to rtest_t4'); +select * from rtest_t4 ORDER BY a, b; + a | b +----+------------------------------------- + 1 | Record should go to rtest_t4 + 2 | Record should go to rtest_t4 + 20 | Record should go to rtest_t4 and t6 + 26 | Record should go to rtest_t4 and t8 + 28 | Record should go to rtest_t4 and t8 + 30 | Record should go to rtest_t4 + 40 | Record should go to rtest_t4 +(7 rows) + +select * from rtest_t5 ORDER BY a, b; + a | b +----+------------------------------------- + 10 | Record should go to rtest_t5 + 15 | Record should go to rtest_t5 + 19 | Record should go to rtest_t5 and t7 +(3 rows) + +select * from rtest_t6 ORDER BY a, b; + a | b +----+------------------------------------- + 20 | Record should go to rtest_t4 and t6 +(1 row) + +select * from rtest_t7 ORDER BY a, b; + a | b +----+------------------------------------- + 19 | Record should go to rtest_t5 and t7 +(1 row) + +select * from rtest_t8 ORDER BY a, b; + a | b +----+------------------------------------- + 26 | Record should go to rtest_t4 and t8 + 28 | Record should go to rtest_t4 and t8 +(2 rows) + +delete from rtest_t4; +delete from rtest_t5; +delete from rtest_t6; +delete from rtest_t7; +delete from rtest_t8; +insert into rtest_t9 values (1, 'Record should go to rtest_t4'); +insert into rtest_t9 values (2, 'Record should go to rtest_t4'); +insert into rtest_t9 values (10, 'Record should go to rtest_t5'); +insert into rtest_t9 values (15, 'Record should go to rtest_t5'); +insert into rtest_t9 values (19, 'Record should go to rtest_t5 and t7'); +insert into rtest_t9 values (20, 'Record should go to rtest_t4 and t6'); +insert into rtest_t9 values (26, 'Record should go to rtest_t4 and t8'); +insert into rtest_t9 values (28, 'Record should go to rtest_t4 and t8'); +insert into rtest_t9 values (30, 'Record should go to rtest_t4'); +insert into rtest_t9 values (40, 'Record should go to rtest_t4'); +insert into rtest_t4 select * from rtest_t9 where a < 20; +select * from rtest_t4 ORDER BY a, b; + a | b +---+------------------------------ + 1 | Record should go to rtest_t4 + 2 | Record should go to rtest_t4 +(2 rows) + +select * from rtest_t5 ORDER BY a, b; + a | b +----+------------------------------------- + 10 | Record should go to rtest_t5 + 15 | Record should go to rtest_t5 + 19 | Record should go to rtest_t5 and t7 +(3 rows) + +select * from rtest_t6; + a | b +---+--- +(0 rows) + +select * from rtest_t7; + a | b +----+------------------------------------- + 19 | Record should go to rtest_t5 and t7 +(1 row) + +select * from rtest_t8; + a | b +---+--- +(0 rows) + +insert into rtest_t4 select * from rtest_t9 where b ~ 'and t8'; +select * from rtest_t4 ORDER BY a, b; + a | b +----+------------------------------------- + 1 | Record should go to rtest_t4 + 2 | Record should go to rtest_t4 + 26 | Record should go to rtest_t4 and t8 + 28 | Record should go to rtest_t4 and t8 +(4 rows) + +select * from rtest_t5 ORDER BY a, b; + a | b +----+------------------------------------- + 10 | Record should go to rtest_t5 + 15 | Record should go to rtest_t5 + 19 | Record should go to rtest_t5 and t7 +(3 rows) + +select * from rtest_t6; + a | b +---+--- +(0 rows) + +select * from rtest_t7; + a | b +----+------------------------------------- + 19 | Record should go to rtest_t5 and t7 +(1 row) + +select * from rtest_t8 ORDER BY a, b; + a | b +----+------------------------------------- + 26 | Record should go to rtest_t4 and t8 + 28 | Record should go to rtest_t4 and t8 +(2 rows) + +insert into rtest_t4 select a + 1, b from rtest_t9 where a in (20, 30, 40); +select * from rtest_t4 ORDER BY a, b; + a | b +----+------------------------------------- + 1 | Record should go to rtest_t4 + 2 | Record should go to rtest_t4 + 21 | Record should go to rtest_t4 and t6 + 26 | Record should go to rtest_t4 and t8 + 28 | Record should go to rtest_t4 and t8 + 31 | Record should go to rtest_t4 + 41 | Record should go to rtest_t4 +(7 rows) + +select * from rtest_t5 ORDER BY a, b; + a | b +----+------------------------------------- + 10 | Record should go to rtest_t5 + 15 | Record should go to rtest_t5 + 19 | Record should go to rtest_t5 and t7 +(3 rows) + +select * from rtest_t6; + a | b +----+------------------------------------- + 21 | Record should go to rtest_t4 and t6 +(1 row) + +select * from rtest_t7; + a | b +----+------------------------------------- + 19 | Record should go to rtest_t5 and t7 +(1 row) + +select * from rtest_t8 ORDER BY a, b; + a | b +----+------------------------------------- + 26 | Record should go to rtest_t4 and t8 + 28 | Record should go to rtest_t4 and t8 +(2 rows) + +-- +-- Check that the ordering of rules fired is correct +-- +insert into rtest_order1 values (1); +select * from rtest_order2 ORDER BY a, b, c; + a | b | c +---+---+------------------------------ + 1 | 1 | rule 1 - this should run 1st + 1 | 2 | rule 2 - this should run 2nd + 1 | 3 | rule 3 - this should run 3rd + 1 | 4 | rule 4 - this should run 4th +(4 rows) + +-- +-- Check if instead nothing w/without qualification works +-- +insert into rtest_nothn1 values (1, 'want this'); +insert into rtest_nothn1 values (2, 'want this'); +insert into rtest_nothn1 values (10, 'don''t want this'); +insert into rtest_nothn1 values (19, 'don''t want this'); +insert into rtest_nothn1 values (20, 'want this'); +insert into rtest_nothn1 values (29, 'want this'); +insert into rtest_nothn1 values (30, 'don''t want this'); +insert into rtest_nothn1 values (39, 'don''t want this'); +insert into rtest_nothn1 values (40, 'want this'); +insert into rtest_nothn1 values (50, 'want this'); +insert into rtest_nothn1 values (60, 'want this'); +select * from rtest_nothn1 ORDER BY a, b; + a | b +----+----------- + 1 | want this + 2 | want this + 20 | want this + 29 | want this + 40 | want this + 50 | want this + 60 | want this +(7 rows) + +insert into rtest_nothn2 values (10, 'too small'); +insert into rtest_nothn2 values (50, 'too small'); +insert into rtest_nothn2 values (100, 'OK'); +insert into rtest_nothn2 values (200, 'OK'); +select * from rtest_nothn2; + a | b +---+--- +(0 rows) + +select * from rtest_nothn3 ORDER BY a, b; + a | b +-----+---- + 100 | OK + 200 | OK +(2 rows) + +delete from rtest_nothn1; +delete from rtest_nothn2; +delete from rtest_nothn3; +insert into rtest_nothn4 values (1, 'want this'); +insert into rtest_nothn4 values (2, 'want this'); +insert into rtest_nothn4 values (10, 'don''t want this'); +insert into rtest_nothn4 values (19, 'don''t want this'); +insert into rtest_nothn4 values (20, 'want this'); +insert into rtest_nothn4 values (29, 'want this'); +insert into rtest_nothn4 values (30, 'don''t want this'); +insert into rtest_nothn4 values (39, 'don''t want this'); +insert into rtest_nothn4 values (40, 'want this'); +insert into rtest_nothn4 values (50, 'want this'); +insert into rtest_nothn4 values (60, 'want this'); +insert into rtest_nothn1 select * from rtest_nothn4; +select * from rtest_nothn1 ORDER BY a, b; + a | b +----+----------- + 1 | want this + 2 | want this + 20 | want this + 29 | want this + 40 | want this + 50 | want this + 60 | want this +(7 rows) + +delete from rtest_nothn4; +insert into rtest_nothn4 values (10, 'too small'); +insert into rtest_nothn4 values (50, 'too small'); +insert into rtest_nothn4 values (100, 'OK'); +insert into rtest_nothn4 values (200, 'OK'); +insert into rtest_nothn2 select * from rtest_nothn4; +select * from rtest_nothn2; + a | b +---+--- +(0 rows) + +select * from rtest_nothn3 ORDER BY a, b; + a | b +-----+---- + 100 | OK + 200 | OK +(2 rows) + +create table rtest_view1 (a int4, b text, v bool); +create table rtest_view2 (a int4); +create table rtest_view3 (a int4, b text); +create table rtest_view4 (a int4, b text, c int4); +create view rtest_vview1 as select a, b from rtest_view1 X + where 0 < (select count(*) from rtest_view2 Y where Y.a = X.a); +create view rtest_vview2 as select a, b from rtest_view1 where v; +create view rtest_vview3 as select a, b from rtest_vview2 X + where 0 < (select count(*) from rtest_view2 Y where Y.a = X.a); +create view rtest_vview4 as select X.a, X.b, count(Y.a) as refcount + from rtest_view1 X, rtest_view2 Y + where X.a = Y.a + group by X.a, X.b; +create function rtest_viewfunc1(int4) returns int4 as + 'select count(*)::int4 from rtest_view2 where a = $1' + language sql; +create view rtest_vview5 as select a, b, rtest_viewfunc1(a) as refcount + from rtest_view1; +insert into rtest_view1 values (1, 'item 1', 't'); +insert into rtest_view1 values (2, 'item 2', 't'); +insert into rtest_view1 values (3, 'item 3', 't'); +insert into rtest_view1 values (4, 'item 4', 'f'); +insert into rtest_view1 values (5, 'item 5', 't'); +insert into rtest_view1 values (6, 'item 6', 'f'); +insert into rtest_view1 values (7, 'item 7', 't'); +insert into rtest_view1 values (8, 'item 8', 't'); +insert into rtest_view2 values (2); +insert into rtest_view2 values (2); +insert into rtest_view2 values (4); +insert into rtest_view2 values (5); +insert into rtest_view2 values (7); +insert into rtest_view2 values (7); +insert into rtest_view2 values (7); +insert into rtest_view2 values (7); +select * from rtest_vview1 ORDER BY a, b; + a | b +---+-------- + 2 | item 2 + 4 | item 4 + 5 | item 5 + 7 | item 7 +(4 rows) + +select * from rtest_vview2 ORDER BY a, b; + a | b +---+-------- + 1 | item 1 + 2 | item 2 + 3 | item 3 + 5 | item 5 + 7 | item 7 + 8 | item 8 +(6 rows) + +select * from rtest_vview3 ORDER BY a, b; + a | b +---+-------- + 2 | item 2 + 5 | item 5 + 7 | item 7 +(3 rows) + +select * from rtest_vview4 order by a, b; + a | b | refcount +---+--------+---------- + 2 | item 2 | 2 + 4 | item 4 | 1 + 5 | item 5 | 1 + 7 | item 7 | 4 +(4 rows) + +select * from rtest_vview5 ORDER BY a, b, refcount; + a | b | refcount +---+--------+---------- + 1 | item 1 | 0 + 2 | item 2 | 2 + 3 | item 3 | 0 + 4 | item 4 | 1 + 5 | item 5 | 1 + 6 | item 6 | 0 + 7 | item 7 | 4 + 8 | item 8 | 0 +(8 rows) + +insert into rtest_view3 select * from rtest_vview1 where a < 7; +select * from rtest_view3 ORDER BY a, b; + a | b +---+-------- + 2 | item 2 + 4 | item 4 + 5 | item 5 +(3 rows) + +delete from rtest_view3; +insert into rtest_view3 select * from rtest_vview2 where a != 5 and b !~ '2'; +select * from rtest_view3 ORDER BY a, b; + a | b +---+-------- + 1 | item 1 + 3 | item 3 + 7 | item 7 + 8 | item 8 +(4 rows) + +delete from rtest_view3; +insert into rtest_view3 select * from rtest_vview3; +select * from rtest_view3 ORDER BY a, b; + a | b +---+-------- + 2 | item 2 + 5 | item 5 + 7 | item 7 +(3 rows) + +delete from rtest_view3; +insert into rtest_view4 select * from rtest_vview4 where 3 > refcount; +select * from rtest_view4 order by a, b; + a | b | c +---+--------+--- + 2 | item 2 | 2 + 4 | item 4 | 1 + 5 | item 5 | 1 +(3 rows) + +delete from rtest_view4; +insert into rtest_view4 select * from rtest_vview5 where a > 2 and refcount = 0; +select * from rtest_view4 ORDER BY a, b, c; + a | b | c +---+--------+--- + 3 | item 3 | 0 + 6 | item 6 | 0 + 8 | item 8 | 0 +(3 rows) + +delete from rtest_view4; +-- +-- Test for computations in views +-- +create table rtest_comp ( + part text, + unit char(4), + size float +); +create table rtest_unitfact ( + unit char(4), + factor float +); +create view rtest_vcomp as + select X.part, (X.size * Y.factor) as size_in_cm + from rtest_comp X, rtest_unitfact Y + where X.unit = Y.unit; +insert into rtest_unitfact values ('m', 100.0); +insert into rtest_unitfact values ('cm', 1.0); +insert into rtest_unitfact values ('inch', 2.54); +insert into rtest_comp values ('p1', 'm', 5.0); +insert into rtest_comp values ('p2', 'm', 3.0); +insert into rtest_comp values ('p3', 'cm', 5.0); +insert into rtest_comp values ('p4', 'cm', 15.0); +insert into rtest_comp values ('p5', 'inch', 7.0); +insert into rtest_comp values ('p6', 'inch', 4.4); +select * from rtest_vcomp order by part; + part | size_in_cm +------+-------------------- + p1 | 500 + p2 | 300 + p3 | 5 + p4 | 15 + p5 | 17.78 + p6 | 11.176000000000002 +(6 rows) + +select * from rtest_vcomp where size_in_cm > 10.0 order by size_in_cm using >; + part | size_in_cm +------+-------------------- + p1 | 500 + p2 | 300 + p5 | 17.78 + p4 | 15 + p6 | 11.176000000000002 +(5 rows) + +-- +-- In addition run the (slightly modified) queries from the +-- programmers manual section on the rule system. +-- +CREATE TABLE shoe_data ( + shoename char(10), -- primary key + sh_avail integer, -- available # of pairs + slcolor char(10), -- preferred shoelace color + slminlen float, -- minimum shoelace length + slmaxlen float, -- maximum shoelace length + slunit char(8) -- length unit +); +CREATE TABLE shoelace_data ( + sl_name char(10), -- primary key + sl_avail integer, -- available # of pairs + sl_color char(10), -- shoelace color + sl_len float, -- shoelace length + sl_unit char(8) -- length unit +); +CREATE TABLE unit ( + un_name char(8), -- the primary key + un_fact float -- factor to transform to cm +); +CREATE VIEW shoe AS + SELECT sh.shoename, + sh.sh_avail, + sh.slcolor, + sh.slminlen, + sh.slminlen * un.un_fact AS slminlen_cm, + sh.slmaxlen, + sh.slmaxlen * un.un_fact AS slmaxlen_cm, + sh.slunit + FROM shoe_data sh, unit un + WHERE sh.slunit = un.un_name; +CREATE VIEW shoelace AS + SELECT s.sl_name, + s.sl_avail, + s.sl_color, + s.sl_len, + s.sl_unit, + s.sl_len * u.un_fact AS sl_len_cm + FROM shoelace_data s, unit u + WHERE s.sl_unit = u.un_name; +CREATE VIEW shoe_ready AS + SELECT rsh.shoename, + rsh.sh_avail, + rsl.sl_name, + rsl.sl_avail, + int4smaller(rsh.sh_avail, rsl.sl_avail) AS total_avail + FROM shoe rsh, shoelace rsl + WHERE rsl.sl_color = rsh.slcolor + AND rsl.sl_len_cm >= rsh.slminlen_cm + AND rsl.sl_len_cm <= rsh.slmaxlen_cm; +INSERT INTO unit VALUES ('cm', 1.0); +INSERT INTO unit VALUES ('m', 100.0); +INSERT INTO unit VALUES ('inch', 2.54); +INSERT INTO shoe_data VALUES ('sh1', 2, 'black', 70.0, 90.0, 'cm'); +INSERT INTO shoe_data VALUES ('sh2', 0, 'black', 30.0, 40.0, 'inch'); +INSERT INTO shoe_data VALUES ('sh3', 4, 'brown', 50.0, 65.0, 'cm'); +INSERT INTO shoe_data VALUES ('sh4', 3, 'brown', 40.0, 50.0, 'inch'); +INSERT INTO shoelace_data VALUES ('sl1', 5, 'black', 80.0, 'cm'); +INSERT INTO shoelace_data VALUES ('sl2', 6, 'black', 100.0, 'cm'); +INSERT INTO shoelace_data VALUES ('sl3', 0, 'black', 35.0 , 'inch'); +INSERT INTO shoelace_data VALUES ('sl4', 8, 'black', 40.0 , 'inch'); +INSERT INTO shoelace_data VALUES ('sl5', 4, 'brown', 1.0 , 'm'); +INSERT INTO shoelace_data VALUES ('sl6', 0, 'brown', 0.9 , 'm'); +INSERT INTO shoelace_data VALUES ('sl7', 7, 'brown', 60 , 'cm'); +INSERT INTO shoelace_data VALUES ('sl8', 1, 'brown', 40 , 'inch'); +-- SELECTs in doc +SELECT * FROM shoelace ORDER BY sl_name; + sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm +------------+----------+------------+--------+----------+----------- + sl1 | 5 | black | 80 | cm | 80 + sl2 | 6 | black | 100 | cm | 100 + sl3 | 0 | black | 35 | inch | 88.9 + sl4 | 8 | black | 40 | inch | 101.6 + sl5 | 4 | brown | 1 | m | 100 + sl6 | 0 | brown | 0.9 | m | 90 + sl7 | 7 | brown | 60 | cm | 60 + sl8 | 1 | brown | 40 | inch | 101.6 +(8 rows) + +SELECT * FROM shoe_ready WHERE total_avail >= 2 ORDER BY 1; + shoename | sh_avail | sl_name | sl_avail | total_avail +------------+----------+------------+----------+------------- + sh1 | 2 | sl1 | 5 | 2 + sh3 | 4 | sl7 | 7 | 4 +(2 rows) + + CREATE TABLE shoelace_log ( + sl_name char(10), -- shoelace changed + sl_avail integer, -- new available value + log_who name, -- who did it + log_when timestamp -- when + ); +-- Want "log_who" to be CURRENT_USER, +-- but that is non-portable for the regression test +-- - thomas 1999-02-21 + CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data + WHERE NEW.sl_avail != OLD.sl_avail + DO INSERT INTO shoelace_log VALUES ( + NEW.sl_name, + NEW.sl_avail, + 'Al Bundy', + 'epoch' + ); +UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7'; +SELECT * FROM shoelace_log; + sl_name | sl_avail | log_who | log_when +------------+----------+----------+-------------------------- + sl7 | 6 | Al Bundy | Thu Jan 01 00:00:00 1970 +(1 row) + + CREATE RULE shoelace_ins AS ON INSERT TO shoelace + DO INSTEAD + INSERT INTO shoelace_data VALUES ( + NEW.sl_name, + NEW.sl_avail, + NEW.sl_color, + NEW.sl_len, + NEW.sl_unit); + CREATE RULE shoelace_upd AS ON UPDATE TO shoelace + DO INSTEAD + UPDATE shoelace_data SET + sl_name = NEW.sl_name, + sl_avail = NEW.sl_avail, + sl_color = NEW.sl_color, + sl_len = NEW.sl_len, + sl_unit = NEW.sl_unit + WHERE sl_name = OLD.sl_name; + CREATE RULE shoelace_del AS ON DELETE TO shoelace + DO INSTEAD + DELETE FROM shoelace_data + WHERE sl_name = OLD.sl_name; + CREATE TABLE shoelace_arrive ( + arr_name char(10), + arr_quant integer + ); + CREATE TABLE shoelace_ok ( + ok_name char(10), + ok_quant integer + ); + CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok + DO INSTEAD + UPDATE shoelace SET + sl_avail = sl_avail + NEW.ok_quant + WHERE sl_name = NEW.ok_name; +INSERT INTO shoelace_arrive VALUES ('sl3', 10); +INSERT INTO shoelace_arrive VALUES ('sl6', 20); +INSERT INTO shoelace_arrive VALUES ('sl8', 20); +SELECT * FROM shoelace ORDER BY sl_name; + sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm +------------+----------+------------+--------+----------+----------- + sl1 | 5 | black | 80 | cm | 80 + sl2 | 6 | black | 100 | cm | 100 + sl3 | 0 | black | 35 | inch | 88.9 + sl4 | 8 | black | 40 | inch | 101.6 + sl5 | 4 | brown | 1 | m | 100 + sl6 | 0 | brown | 0.9 | m | 90 + sl7 | 6 | brown | 60 | cm | 60 + sl8 | 1 | brown | 40 | inch | 101.6 +(8 rows) + +insert into shoelace_ok select * from shoelace_arrive; +SELECT * FROM shoelace ORDER BY sl_name; + sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm +------------+----------+------------+--------+----------+----------- + sl1 | 5 | black | 80 | cm | 80 + sl2 | 6 | black | 100 | cm | 100 + sl3 | 10 | black | 35 | inch | 88.9 + sl4 | 8 | black | 40 | inch | 101.6 + sl5 | 4 | brown | 1 | m | 100 + sl6 | 20 | brown | 0.9 | m | 90 + sl7 | 6 | brown | 60 | cm | 60 + sl8 | 21 | brown | 40 | inch | 101.6 +(8 rows) + +SELECT * FROM shoelace_log ORDER BY sl_name; + sl_name | sl_avail | log_who | log_when +------------+----------+----------+-------------------------- + sl3 | 10 | Al Bundy | Thu Jan 01 00:00:00 1970 + sl6 | 20 | Al Bundy | Thu Jan 01 00:00:00 1970 + sl7 | 6 | Al Bundy | Thu Jan 01 00:00:00 1970 + sl8 | 21 | Al Bundy | Thu Jan 01 00:00:00 1970 +(4 rows) + + CREATE VIEW shoelace_obsolete AS + SELECT * FROM shoelace WHERE NOT EXISTS + (SELECT shoename FROM shoe WHERE slcolor = sl_color); + CREATE VIEW shoelace_candelete AS + SELECT * FROM shoelace_obsolete WHERE sl_avail = 0; +insert into shoelace values ('sl9', 0, 'pink', 35.0, 'inch', 0.0); +insert into shoelace values ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0); +-- Unsupported (even though a similar updatable view construct is) +insert into shoelace values ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0) + on conflict do nothing; +ERROR: INSERT with ON CONFLICT clause cannot be used with table that has INSERT or UPDATE rules +SELECT * FROM shoelace_obsolete ORDER BY sl_len_cm; + sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm +------------+----------+------------+--------+----------+----------- + sl9 | 0 | pink | 35 | inch | 88.9 + sl10 | 1000 | magenta | 40 | inch | 101.6 +(2 rows) + +SELECT * FROM shoelace_candelete; + sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm +------------+----------+------------+--------+----------+----------- + sl9 | 0 | pink | 35 | inch | 88.9 +(1 row) + +DELETE FROM shoelace WHERE EXISTS + (SELECT * FROM shoelace_candelete + WHERE sl_name = shoelace.sl_name); +SELECT * FROM shoelace ORDER BY sl_name; + sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm +------------+----------+------------+--------+----------+----------- + sl1 | 5 | black | 80 | cm | 80 + sl10 | 1000 | magenta | 40 | inch | 101.6 + sl2 | 6 | black | 100 | cm | 100 + sl3 | 10 | black | 35 | inch | 88.9 + sl4 | 8 | black | 40 | inch | 101.6 + sl5 | 4 | brown | 1 | m | 100 + sl6 | 20 | brown | 0.9 | m | 90 + sl7 | 6 | brown | 60 | cm | 60 + sl8 | 21 | brown | 40 | inch | 101.6 +(9 rows) + +SELECT * FROM shoe ORDER BY shoename; + shoename | sh_avail | slcolor | slminlen | slminlen_cm | slmaxlen | slmaxlen_cm | slunit +------------+----------+------------+----------+-------------+----------+-------------+---------- + sh1 | 2 | black | 70 | 70 | 90 | 90 | cm + sh2 | 0 | black | 30 | 76.2 | 40 | 101.6 | inch + sh3 | 4 | brown | 50 | 50 | 65 | 65 | cm + sh4 | 3 | brown | 40 | 101.6 | 50 | 127 | inch +(4 rows) + +SELECT count(*) FROM shoe; + count +------- + 4 +(1 row) + +-- +-- Simple test of qualified ON INSERT ... this did not work in 7.0 ... +-- +create table rules_foo (f1 int); +create table rules_foo2 (f1 int); +create rule rules_foorule as on insert to rules_foo where f1 < 100 +do instead nothing; +insert into rules_foo values(1); +insert into rules_foo values(1001); +select * from rules_foo; + f1 +------ + 1001 +(1 row) + +drop rule rules_foorule on rules_foo; +-- this should fail because f1 is not exposed for unqualified reference: +create rule rules_foorule as on insert to rules_foo where f1 < 100 +do instead insert into rules_foo2 values (f1); +ERROR: column "f1" does not exist +LINE 2: do instead insert into rules_foo2 values (f1); + ^ +HINT: There is a column named "f1" in table "old", but it cannot be referenced from this part of the query. +-- this is the correct way: +create rule rules_foorule as on insert to rules_foo where f1 < 100 +do instead insert into rules_foo2 values (new.f1); +insert into rules_foo values(2); +insert into rules_foo values(100); +select * from rules_foo ORDER BY f1; + f1 +------ + 100 + 1001 +(2 rows) + +select * from rules_foo2; + f1 +---- + 2 +(1 row) + +drop rule rules_foorule on rules_foo; +drop table rules_foo; +drop table rules_foo2; +-- +-- Test rules containing INSERT ... SELECT, which is a very ugly special +-- case as of 7.1. Example is based on bug report from Joel Burton. +-- +create table pparent (pid int, txt text); +insert into pparent values (1,'parent1'); +insert into pparent values (2,'parent2'); +create table cchild (pid int, descrip text); +insert into cchild values (1,'descrip1'); +create view vview as + select pparent.pid, txt, descrip from + pparent left join cchild using (pid); +create rule rrule as + on update to vview do instead +( + insert into cchild (pid, descrip) + select old.pid, new.descrip where old.descrip isnull; + update cchild set descrip = new.descrip where cchild.pid = old.pid; +); +select * from vview ORDER BY pid, txt, descrip; + pid | txt | descrip +-----+---------+---------- + 1 | parent1 | descrip1 + 2 | parent2 | +(2 rows) + +update vview set descrip='test1' where pid=1; +select * from vview ORDER BY pid, txt, descrip; + pid | txt | descrip +-----+---------+--------- + 1 | parent1 | test1 + 2 | parent2 | +(2 rows) + +update vview set descrip='test2' where pid=2; +select * from vview ORDER BY pid, txt, descrip; + pid | txt | descrip +-----+---------+--------- + 1 | parent1 | test1 + 2 | parent2 | test2 +(2 rows) + +update vview set descrip='test3' where pid=3; +select * from vview ORDER BY pid, txt, descrip; + pid | txt | descrip +-----+---------+--------- + 1 | parent1 | test1 + 2 | parent2 | test2 +(2 rows) + +select * from cchild ORDER BY pid, descrip; + pid | descrip +-----+--------- + 1 | test1 + 2 | test2 +(2 rows) + +drop rule rrule on vview; +drop view vview; +drop table pparent; +drop table cchild; +-- +-- Check that ruleutils are working +-- +-- temporarily disable fancy output, so view changes create less diff noise +\a\t +-- TODO(jason): port `create_view`, `matview`, and `tablesample` Postgres +-- regress tests, then add the appropriate rows to the output. +SELECT viewname, definition FROM pg_views +WHERE schemaname = 'pg_catalog' +ORDER BY viewname; +pg_available_extension_versions| SELECT e.name, + e.version, + (x.extname IS NOT NULL) AS installed, + e.superuser, + e.trusted, + e.relocatable, + e.schema, + e.requires, + e.comment + FROM (pg_available_extension_versions() e(name, version, superuser, trusted, relocatable, schema, requires, comment) + LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion)))); +pg_available_extensions| SELECT e.name, + e.default_version, + x.extversion AS installed_version, + e.comment + FROM (pg_available_extensions() e(name, default_version, comment) + LEFT JOIN pg_extension x ON ((e.name = x.extname))); +pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name, + pg_get_backend_memory_contexts.ident, + pg_get_backend_memory_contexts.parent, + pg_get_backend_memory_contexts.level, + pg_get_backend_memory_contexts.total_bytes, + pg_get_backend_memory_contexts.total_nblocks, + pg_get_backend_memory_contexts.free_bytes, + pg_get_backend_memory_contexts.free_chunks, + pg_get_backend_memory_contexts.used_bytes + FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes); +pg_config| SELECT pg_config.name, + pg_config.setting + FROM pg_config() pg_config(name, setting); +pg_cursors| SELECT c.name, + c.statement, + c.is_holdable, + c.is_binary, + c.is_scrollable, + c.creation_time + FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time); +pg_file_settings| SELECT a.sourcefile, + a.sourceline, + a.seqno, + a.name, + a.setting, + a.applied, + a.error + FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error); +pg_group| SELECT pg_authid.rolname AS groname, + pg_authid.oid AS grosysid, + ARRAY( SELECT pg_auth_members.member + FROM pg_auth_members + WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist + FROM pg_authid + WHERE (NOT pg_authid.rolcanlogin); +pg_hba_file_rules| SELECT a.line_number, + a.type, + a.database, + a.user_name, + a.address, + a.netmask, + a.auth_method, + a.options, + a.error + FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error); +pg_ident_file_mappings| SELECT a.line_number, + a.map_name, + a.sys_name, + a.pg_username, + a.error + FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error); +pg_indexes| SELECT n.nspname AS schemaname, + c.relname AS tablename, + i.relname AS indexname, + t.spcname AS tablespace, + pg_get_indexdef(i.oid) AS indexdef + FROM ((((pg_index x + JOIN pg_class c ON ((c.oid = x.indrelid))) + JOIN pg_class i ON ((i.oid = x.indexrelid))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace))) + WHERE ((c.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'p'::"char"])) AND (i.relkind = ANY (ARRAY['i'::"char", 'I'::"char"]))); +pg_locks| SELECT l.locktype, + l.database, + l.relation, + NULL::integer AS page, + NULL::smallint AS tuple, + NULL::text AS virtualxid, + NULL::xid AS transactionid, + l.classid, + l.objid, + l.objsubid, + NULL::text AS virtualtransaction, + l.pid, + array_to_string(l.mode, ','::text) AS mode, + l.granted, + l.fastpath, + l.waitstart, + l.waitend, + jsonb_build_object('node', l.node, 'transactionid', l.transaction_id, 'subtransaction_id', l.subtransaction_id, 'is_explicit', l.is_explicit, 'tablet_id', l.tablet_id, 'blocked_by', l.blocked_by, 'keyrangedetails', jsonb_build_object('cols', to_jsonb((l.hash_cols || l.range_cols)), 'attnum', l.attnum, 'column_id', l.column_id, 'multiple_rows_locked', l.multiple_rows_locked)) AS ybdetails + FROM yb_lock_status(NULL::oid, NULL::uuid) l(locktype, database, relation, pid, mode, granted, fastpath, waitstart, waitend, node, tablet_id, transaction_id, subtransaction_id, status_tablet_id, is_explicit, hash_cols, range_cols, attnum, column_id, multiple_rows_locked, blocked_by, classid, objid, objsubid); +pg_matviews| SELECT n.nspname AS schemaname, + c.relname AS matviewname, + pg_get_userbyid(c.relowner) AS matviewowner, + t.spcname AS tablespace, + c.relhasindex AS hasindexes, + c.relispopulated AS ispopulated, + pg_get_viewdef(c.oid) AS definition + FROM ((pg_class c + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace))) + WHERE (c.relkind = 'm'::"char"); +pg_policies| SELECT n.nspname AS schemaname, + c.relname AS tablename, + pol.polname AS policyname, + CASE + WHEN pol.polpermissive THEN 'PERMISSIVE'::text + ELSE 'RESTRICTIVE'::text + END AS permissive, + CASE + WHEN (pol.polroles = '{0}'::oid[]) THEN (string_to_array('public'::text, ''::text))::name[] + ELSE ARRAY( SELECT pg_authid.rolname + FROM pg_authid + WHERE (pg_authid.oid = ANY (pol.polroles)) + ORDER BY pg_authid.rolname) + END AS roles, + CASE pol.polcmd + WHEN 'r'::"char" THEN 'SELECT'::text + WHEN 'a'::"char" THEN 'INSERT'::text + WHEN 'w'::"char" THEN 'UPDATE'::text + WHEN 'd'::"char" THEN 'DELETE'::text + WHEN '*'::"char" THEN 'ALL'::text + ELSE NULL::text + END AS cmd, + pg_get_expr(pol.polqual, pol.polrelid) AS qual, + pg_get_expr(pol.polwithcheck, pol.polrelid) AS with_check + FROM ((pg_policy pol + JOIN pg_class c ON ((c.oid = pol.polrelid))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))); +pg_prepared_statements| SELECT p.name, + p.statement, + p.prepare_time, + p.parameter_types, + p.from_sql, + p.generic_plans, + p.custom_plans + FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, from_sql, generic_plans, custom_plans); +pg_prepared_xacts| SELECT p.transaction, + p.gid, + p.prepared, + u.rolname AS owner, + d.datname AS database + FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid) + LEFT JOIN pg_authid u ON ((p.ownerid = u.oid))) + LEFT JOIN pg_database d ON ((p.dbid = d.oid))); +pg_publication_tables| SELECT p.pubname, + n.nspname AS schemaname, + c.relname AS tablename, + ( SELECT array_agg(a.attname ORDER BY a.attnum) AS array_agg + FROM pg_attribute a + WHERE ((a.attrelid = gpt.relid) AND (a.attnum > 0) AND (NOT a.attisdropped) AND ((a.attnum = ANY ((gpt.attrs)::smallint[])) OR (gpt.attrs IS NULL)))) AS attnames, + pg_get_expr(gpt.qual, gpt.relid) AS rowfilter + FROM pg_publication p, + LATERAL pg_get_publication_tables((p.pubname)::text) gpt(relid, attrs, qual), + (pg_class c + JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE (c.oid = gpt.relid); +pg_replication_origin_status| SELECT pg_show_replication_origin_status.local_id, + pg_show_replication_origin_status.external_id, + pg_show_replication_origin_status.remote_lsn, + pg_show_replication_origin_status.local_lsn + FROM pg_show_replication_origin_status() pg_show_replication_origin_status(local_id, external_id, remote_lsn, local_lsn); +pg_replication_slots| SELECT l.slot_name, + l.plugin, + l.slot_type, + l.datoid, + d.datname AS database, + l.temporary, + l.active, + l.active_pid, + l.xmin, + l.catalog_xmin, + l.restart_lsn, + l.confirmed_flush_lsn, + l.wal_status, + l.safe_wal_size, + l.two_phase, + l.yb_stream_id, + l.yb_restart_commit_ht, + l.yb_lsn_type, + l.yb_restart_time + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, safe_wal_size, two_phase, yb_stream_id, yb_restart_commit_ht, yb_lsn_type, yb_restart_time) + LEFT JOIN pg_database d ON ((l.datoid = d.oid))); +pg_roles| SELECT pg_authid.rolname, + pg_authid.rolsuper, + pg_authid.rolinherit, + pg_authid.rolcreaterole, + pg_authid.rolcreatedb, + pg_authid.rolcanlogin, + pg_authid.rolreplication, + pg_authid.rolconnlimit, + '********'::text AS rolpassword, + pg_authid.rolvaliduntil, + pg_authid.rolbypassrls, + s.setconfig AS rolconfig, + pg_authid.oid + FROM (pg_authid + LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid)))); +pg_rules| SELECT n.nspname AS schemaname, + c.relname AS tablename, + r.rulename, + pg_get_ruledef(r.oid) AS definition + FROM ((pg_rewrite r + JOIN pg_class c ON ((c.oid = r.ev_class))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE (r.rulename <> '_RETURN'::name); +pg_seclabels| SELECT l.objoid, + l.classoid, + l.objsubid, + CASE + WHEN (rel.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) THEN 'table'::text + WHEN (rel.relkind = 'v'::"char") THEN 'view'::text + WHEN (rel.relkind = 'm'::"char") THEN 'materialized view'::text + WHEN (rel.relkind = 'S'::"char") THEN 'sequence'::text + WHEN (rel.relkind = 'f'::"char") THEN 'foreign table'::text + ELSE NULL::text + END AS objtype, + rel.relnamespace AS objnamespace, + CASE + WHEN pg_table_is_visible(rel.oid) THEN quote_ident((rel.relname)::text) + ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((rel.relname)::text)) + END AS objname, + l.provider, + l.label + FROM ((pg_seclabel l + JOIN pg_class rel ON (((l.classoid = rel.tableoid) AND (l.objoid = rel.oid)))) + JOIN pg_namespace nsp ON ((rel.relnamespace = nsp.oid))) + WHERE (l.objsubid = 0) +UNION ALL + SELECT l.objoid, + l.classoid, + l.objsubid, + 'column'::text AS objtype, + rel.relnamespace AS objnamespace, + (( + CASE + WHEN pg_table_is_visible(rel.oid) THEN quote_ident((rel.relname)::text) + ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((rel.relname)::text)) + END || '.'::text) || (att.attname)::text) AS objname, + l.provider, + l.label + FROM (((pg_seclabel l + JOIN pg_class rel ON (((l.classoid = rel.tableoid) AND (l.objoid = rel.oid)))) + JOIN pg_attribute att ON (((rel.oid = att.attrelid) AND (l.objsubid = att.attnum)))) + JOIN pg_namespace nsp ON ((rel.relnamespace = nsp.oid))) + WHERE (l.objsubid <> 0) +UNION ALL + SELECT l.objoid, + l.classoid, + l.objsubid, + CASE pro.prokind + WHEN 'a'::"char" THEN 'aggregate'::text + WHEN 'f'::"char" THEN 'function'::text + WHEN 'p'::"char" THEN 'procedure'::text + WHEN 'w'::"char" THEN 'window'::text + ELSE NULL::text + END AS objtype, + pro.pronamespace AS objnamespace, + ((( + CASE + WHEN pg_function_is_visible(pro.oid) THEN quote_ident((pro.proname)::text) + ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((pro.proname)::text)) + END || '('::text) || pg_get_function_arguments(pro.oid)) || ')'::text) AS objname, + l.provider, + l.label + FROM ((pg_seclabel l + JOIN pg_proc pro ON (((l.classoid = pro.tableoid) AND (l.objoid = pro.oid)))) + JOIN pg_namespace nsp ON ((pro.pronamespace = nsp.oid))) + WHERE (l.objsubid = 0) +UNION ALL + SELECT l.objoid, + l.classoid, + l.objsubid, + CASE + WHEN (typ.typtype = 'd'::"char") THEN 'domain'::text + ELSE 'type'::text + END AS objtype, + typ.typnamespace AS objnamespace, + CASE + WHEN pg_type_is_visible(typ.oid) THEN quote_ident((typ.typname)::text) + ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((typ.typname)::text)) + END AS objname, + l.provider, + l.label + FROM ((pg_seclabel l + JOIN pg_type typ ON (((l.classoid = typ.tableoid) AND (l.objoid = typ.oid)))) + JOIN pg_namespace nsp ON ((typ.typnamespace = nsp.oid))) + WHERE (l.objsubid = 0) +UNION ALL + SELECT l.objoid, + l.classoid, + l.objsubid, + 'large object'::text AS objtype, + NULL::oid AS objnamespace, + (l.objoid)::text AS objname, + l.provider, + l.label + FROM (pg_seclabel l + JOIN pg_largeobject_metadata lom ON ((l.objoid = lom.oid))) + WHERE ((l.classoid = ('pg_largeobject'::regclass)::oid) AND (l.objsubid = 0)) +UNION ALL + SELECT l.objoid, + l.classoid, + l.objsubid, + 'language'::text AS objtype, + NULL::oid AS objnamespace, + quote_ident((lan.lanname)::text) AS objname, + l.provider, + l.label + FROM (pg_seclabel l + JOIN pg_language lan ON (((l.classoid = lan.tableoid) AND (l.objoid = lan.oid)))) + WHERE (l.objsubid = 0) +UNION ALL + SELECT l.objoid, + l.classoid, + l.objsubid, + 'schema'::text AS objtype, + nsp.oid AS objnamespace, + quote_ident((nsp.nspname)::text) AS objname, + l.provider, + l.label + FROM (pg_seclabel l + JOIN pg_namespace nsp ON (((l.classoid = nsp.tableoid) AND (l.objoid = nsp.oid)))) + WHERE (l.objsubid = 0) +UNION ALL + SELECT l.objoid, + l.classoid, + l.objsubid, + 'event trigger'::text AS objtype, + NULL::oid AS objnamespace, + quote_ident((evt.evtname)::text) AS objname, + l.provider, + l.label + FROM (pg_seclabel l + JOIN pg_event_trigger evt ON (((l.classoid = evt.tableoid) AND (l.objoid = evt.oid)))) + WHERE (l.objsubid = 0) +UNION ALL + SELECT l.objoid, + l.classoid, + l.objsubid, + 'publication'::text AS objtype, + NULL::oid AS objnamespace, + quote_ident((p.pubname)::text) AS objname, + l.provider, + l.label + FROM (pg_seclabel l + JOIN pg_publication p ON (((l.classoid = p.tableoid) AND (l.objoid = p.oid)))) + WHERE (l.objsubid = 0) +UNION ALL + SELECT l.objoid, + l.classoid, + 0 AS objsubid, + 'subscription'::text AS objtype, + NULL::oid AS objnamespace, + quote_ident((s.subname)::text) AS objname, + l.provider, + l.label + FROM (pg_shseclabel l + JOIN pg_subscription s ON (((l.classoid = s.tableoid) AND (l.objoid = s.oid)))) +UNION ALL + SELECT l.objoid, + l.classoid, + 0 AS objsubid, + 'database'::text AS objtype, + NULL::oid AS objnamespace, + quote_ident((dat.datname)::text) AS objname, + l.provider, + l.label + FROM (pg_shseclabel l + JOIN pg_database dat ON (((l.classoid = dat.tableoid) AND (l.objoid = dat.oid)))) +UNION ALL + SELECT l.objoid, + l.classoid, + 0 AS objsubid, + 'tablespace'::text AS objtype, + NULL::oid AS objnamespace, + quote_ident((spc.spcname)::text) AS objname, + l.provider, + l.label + FROM (pg_shseclabel l + JOIN pg_tablespace spc ON (((l.classoid = spc.tableoid) AND (l.objoid = spc.oid)))) +UNION ALL + SELECT l.objoid, + l.classoid, + 0 AS objsubid, + 'role'::text AS objtype, + NULL::oid AS objnamespace, + quote_ident((rol.rolname)::text) AS objname, + l.provider, + l.label + FROM (pg_shseclabel l + JOIN pg_authid rol ON (((l.classoid = rol.tableoid) AND (l.objoid = rol.oid)))); +pg_sequences| SELECT n.nspname AS schemaname, + c.relname AS sequencename, + pg_get_userbyid(c.relowner) AS sequenceowner, + (s.seqtypid)::regtype AS data_type, + s.seqstart AS start_value, + s.seqmin AS min_value, + s.seqmax AS max_value, + s.seqincrement AS increment_by, + s.seqcycle AS cycle, + s.seqcache AS cache_size, + CASE + WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN pg_sequence_last_value((c.oid)::regclass) + ELSE NULL::bigint + END AS last_value + FROM ((pg_sequence s + JOIN pg_class c ON ((c.oid = s.seqrelid))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char")); +pg_settings| SELECT a.name, + a.setting, + a.unit, + a.category, + a.short_desc, + a.extra_desc, + a.context, + a.vartype, + a.source, + a.min_val, + a.max_val, + a.enumvals, + a.boot_val, + a.reset_val, + a.sourcefile, + a.sourceline, + a.pending_restart + FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline, pending_restart); +pg_shadow| SELECT pg_authid.rolname AS usename, + pg_authid.oid AS usesysid, + pg_authid.rolcreatedb AS usecreatedb, + pg_authid.rolsuper AS usesuper, + pg_authid.rolreplication AS userepl, + pg_authid.rolbypassrls AS usebypassrls, + pg_authid.rolpassword AS passwd, + pg_authid.rolvaliduntil AS valuntil, + s.setconfig AS useconfig + FROM (pg_authid + LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid)))) + WHERE pg_authid.rolcanlogin; +pg_shmem_allocations| SELECT pg_get_shmem_allocations.name, + pg_get_shmem_allocations.off, + pg_get_shmem_allocations.size, + pg_get_shmem_allocations.allocated_size + FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size); +pg_stat_activity| SELECT s.datid, + d.datname, + s.pid, + s.leader_pid, + s.usesysid, + u.rolname AS usename, + s.application_name, + s.client_addr, + s.client_hostname, + s.client_port, + s.backend_start, + s.xact_start, + s.query_start, + s.state_change, + s.wait_event_type, + s.wait_event, + s.state, + s.backend_xid, + s.backend_xmin, + s.query_id, + s.query, + s.backend_type, + yb_pg_stat_get_backend_catalog_version(b.beid) AS catalog_version, + yb_pg_stat_get_backend_allocated_mem_bytes(b.beid) AS allocated_mem_bytes, + yb_pg_stat_get_backend_pss_mem_bytes(b.beid) AS pss_mem_bytes, + s.yb_backend_xid + FROM (((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id, yb_backend_xid) + LEFT JOIN pg_database d ON ((s.datid = d.oid))) + LEFT JOIN pg_authid u ON ((s.usesysid = u.oid))) + LEFT JOIN (pg_stat_get_backend_idset() beid(beid) + CROSS JOIN LATERAL pg_stat_get_backend_pid(beid.beid) pid(pid)) b ON ((b.pid = s.pid))); +pg_stat_all_indexes| SELECT c.oid AS relid, + i.oid AS indexrelid, + n.nspname AS schemaname, + c.relname, + i.relname AS indexrelname, + pg_stat_get_numscans(i.oid) AS idx_scan, + pg_stat_get_tuples_returned(i.oid) AS idx_tup_read, + pg_stat_get_tuples_fetched(i.oid) AS idx_tup_fetch + FROM (((pg_class c + JOIN pg_index x ON ((c.oid = x.indrelid))) + JOIN pg_class i ON ((i.oid = x.indexrelid))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"])); +pg_stat_all_tables| SELECT c.oid AS relid, + n.nspname AS schemaname, + c.relname, + pg_stat_get_numscans(c.oid) AS seq_scan, + pg_stat_get_tuples_returned(c.oid) AS seq_tup_read, + (sum(pg_stat_get_numscans(i.indexrelid)))::bigint AS idx_scan, + ((sum(pg_stat_get_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tuples_fetched(c.oid)) AS idx_tup_fetch, + pg_stat_get_tuples_inserted(c.oid) AS n_tup_ins, + pg_stat_get_tuples_updated(c.oid) AS n_tup_upd, + pg_stat_get_tuples_deleted(c.oid) AS n_tup_del, + pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd, + pg_stat_get_live_tuples(c.oid) AS n_live_tup, + pg_stat_get_dead_tuples(c.oid) AS n_dead_tup, + pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, + pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, + pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count + FROM ((pg_class c + LEFT JOIN pg_index i ON ((c.oid = i.indrelid))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"])) + GROUP BY c.oid, n.nspname, c.relname; +pg_stat_archiver| SELECT s.archived_count, + s.last_archived_wal, + s.last_archived_time, + s.failed_count, + s.last_failed_wal, + s.last_failed_time, + s.stats_reset + FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed, + pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req, + pg_stat_get_checkpoint_write_time() AS checkpoint_write_time, + pg_stat_get_checkpoint_sync_time() AS checkpoint_sync_time, + pg_stat_get_bgwriter_buf_written_checkpoints() AS buffers_checkpoint, + pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, + pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, + pg_stat_get_buf_written_backend() AS buffers_backend, + pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync, + pg_stat_get_buf_alloc() AS buffers_alloc, + pg_stat_get_bgwriter_stat_reset_time() AS stats_reset; +pg_stat_database| SELECT d.oid AS datid, + d.datname, + CASE + WHEN (d.oid = (0)::oid) THEN 0 + ELSE pg_stat_get_db_numbackends(d.oid) + END AS numbackends, + pg_stat_get_db_xact_commit(d.oid) AS xact_commit, + pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback, + (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read, + pg_stat_get_db_blocks_hit(d.oid) AS blks_hit, + pg_stat_get_db_tuples_returned(d.oid) AS tup_returned, + pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched, + pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted, + pg_stat_get_db_tuples_updated(d.oid) AS tup_updated, + pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted, + pg_stat_get_db_conflict_all(d.oid) AS conflicts, + pg_stat_get_db_temp_files(d.oid) AS temp_files, + pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes, + pg_stat_get_db_deadlocks(d.oid) AS deadlocks, + pg_stat_get_db_checksum_failures(d.oid) AS checksum_failures, + pg_stat_get_db_checksum_last_failure(d.oid) AS checksum_last_failure, + pg_stat_get_db_blk_read_time(d.oid) AS blk_read_time, + pg_stat_get_db_blk_write_time(d.oid) AS blk_write_time, + pg_stat_get_db_session_time(d.oid) AS session_time, + pg_stat_get_db_active_time(d.oid) AS active_time, + pg_stat_get_db_idle_in_transaction_time(d.oid) AS idle_in_transaction_time, + pg_stat_get_db_sessions(d.oid) AS sessions, + pg_stat_get_db_sessions_abandoned(d.oid) AS sessions_abandoned, + pg_stat_get_db_sessions_fatal(d.oid) AS sessions_fatal, + pg_stat_get_db_sessions_killed(d.oid) AS sessions_killed, + pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset + FROM ( SELECT 0 AS oid, + NULL::name AS datname + UNION ALL + SELECT pg_database.oid, + pg_database.datname + FROM pg_database) d; +pg_stat_database_conflicts| SELECT d.oid AS datid, + d.datname, + pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace, + pg_stat_get_db_conflict_lock(d.oid) AS confl_lock, + pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot, + pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin, + pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock + FROM pg_database d; +pg_stat_gssapi| SELECT s.pid, + s.gss_auth AS gss_authenticated, + s.gss_princ AS principal, + s.gss_enc AS encrypted + FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id, yb_backend_xid) + WHERE (s.client_port IS NOT NULL); +pg_stat_progress_analyze| SELECT s.pid, + s.datid, + d.datname, + s.relid, + CASE s.param1 + WHEN 0 THEN 'initializing'::text + WHEN 1 THEN 'acquiring sample rows'::text + WHEN 2 THEN 'acquiring inherited sample rows'::text + WHEN 3 THEN 'computing statistics'::text + WHEN 4 THEN 'computing extended statistics'::text + WHEN 5 THEN 'finalizing analyze'::text + ELSE NULL::text + END AS phase, + s.param2 AS sample_blks_total, + s.param3 AS sample_blks_scanned, + s.param4 AS ext_stats_total, + s.param5 AS ext_stats_computed, + s.param6 AS child_tables_total, + s.param7 AS child_tables_done, + (s.param8)::oid AS current_child_table_relid + FROM (pg_stat_get_progress_info('ANALYZE'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20) + LEFT JOIN pg_database d ON ((s.datid = d.oid))); +pg_stat_progress_basebackup| SELECT s.pid, + CASE s.param1 + WHEN 0 THEN 'initializing'::text + WHEN 1 THEN 'waiting for checkpoint to finish'::text + WHEN 2 THEN 'estimating backup size'::text + WHEN 3 THEN 'streaming database files'::text + WHEN 4 THEN 'waiting for wal archiving to finish'::text + WHEN 5 THEN 'transferring wal files'::text + ELSE NULL::text + END AS phase, + CASE s.param2 + WHEN '-1'::integer THEN NULL::bigint + ELSE s.param2 + END AS backup_total, + s.param3 AS backup_streamed, + s.param4 AS tablespaces_total, + s.param5 AS tablespaces_streamed + FROM pg_stat_get_progress_info('BASEBACKUP'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20); +pg_stat_progress_cluster| SELECT s.pid, + s.datid, + d.datname, + s.relid, + CASE s.param1 + WHEN 1 THEN 'CLUSTER'::text + WHEN 2 THEN 'VACUUM FULL'::text + ELSE NULL::text + END AS command, + CASE s.param2 + WHEN 0 THEN 'initializing'::text + WHEN 1 THEN 'seq scanning heap'::text + WHEN 2 THEN 'index scanning heap'::text + WHEN 3 THEN 'sorting tuples'::text + WHEN 4 THEN 'writing new heap'::text + WHEN 5 THEN 'swapping relation files'::text + WHEN 6 THEN 'rebuilding index'::text + WHEN 7 THEN 'performing final cleanup'::text + ELSE NULL::text + END AS phase, + (s.param3)::oid AS cluster_index_relid, + s.param4 AS heap_tuples_scanned, + s.param5 AS heap_tuples_written, + s.param6 AS heap_blks_total, + s.param7 AS heap_blks_scanned, + s.param8 AS index_rebuild_count + FROM (pg_stat_get_progress_info('CLUSTER'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20) + LEFT JOIN pg_database d ON ((s.datid = d.oid))); +pg_stat_progress_copy| SELECT s.pid, + s.datid, + d.datname, + s.relid, + CASE s.param5 + WHEN 1 THEN 'COPY FROM'::text + WHEN 2 THEN 'COPY TO'::text + ELSE NULL::text + END AS command, + CASE s.param6 + WHEN 1 THEN 'FILE'::text + WHEN 2 THEN 'PROGRAM'::text + WHEN 3 THEN 'PIPE'::text + WHEN 4 THEN 'CALLBACK'::text + ELSE NULL::text + END AS type, + CASE s.param7 + WHEN 0 THEN 'IN PROGRESS'::text + WHEN 1 THEN 'ERROR'::text + WHEN 2 THEN 'SUCCESS'::text + ELSE NULL::text + END AS yb_status, + s.param1 AS bytes_processed, + s.param2 AS bytes_total, + s.param3 AS tuples_processed, + s.param4 AS tuples_excluded + FROM (pg_stat_get_progress_info('COPY'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20) + LEFT JOIN pg_database d ON ((s.datid = d.oid))); +pg_stat_progress_create_index| SELECT s.pid, + s.datid, + d.datname, + s.relid, + (s.param7)::oid AS index_relid, + CASE s.param1 + WHEN 1 THEN 'CREATE INDEX NONCONCURRENTLY'::text + WHEN 2 THEN 'CREATE INDEX CONCURRENTLY'::text + WHEN 3 THEN 'REINDEX NONCONCURRENTLY'::text + WHEN 4 THEN 'REINDEX CONCURRENTLY'::text + ELSE NULL::text + END AS command, + CASE s.param10 + WHEN 0 THEN 'initializing'::text + WHEN 1 THEN 'backfilling'::text + ELSE NULL::text + END AS phase, + s.param4 AS lockers_total, + s.param5 AS lockers_done, + s.param6 AS current_locker_pid, + s.param16 AS blocks_total, + s.param17 AS blocks_done, + s.param12 AS tuples_total, + s.param13 AS tuples_done, + s.param14 AS partitions_total, + s.param15 AS partitions_done + FROM (pg_stat_get_progress_info('CREATE INDEX'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20) + LEFT JOIN pg_database d ON ((s.datid = d.oid))); +pg_stat_progress_vacuum| SELECT s.pid, + s.datid, + d.datname, + s.relid, + CASE s.param1 + WHEN 0 THEN 'initializing'::text + WHEN 1 THEN 'scanning heap'::text + WHEN 2 THEN 'vacuuming indexes'::text + WHEN 3 THEN 'vacuuming heap'::text + WHEN 4 THEN 'cleaning up indexes'::text + WHEN 5 THEN 'truncating heap'::text + WHEN 6 THEN 'performing final cleanup'::text + ELSE NULL::text + END AS phase, + s.param2 AS heap_blks_total, + s.param3 AS heap_blks_scanned, + s.param4 AS heap_blks_vacuumed, + s.param5 AS index_vacuum_count, + s.param6 AS max_dead_tuples, + s.param7 AS num_dead_tuples + FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20) + LEFT JOIN pg_database d ON ((s.datid = d.oid))); +pg_stat_recovery_prefetch| SELECT s.stats_reset, + s.prefetch, + s.hit, + s.skip_init, + s.skip_new, + s.skip_fpw, + s.skip_rep, + s.wal_distance, + s.block_distance, + s.io_depth + FROM pg_stat_get_recovery_prefetch() s(stats_reset, prefetch, hit, skip_init, skip_new, skip_fpw, skip_rep, wal_distance, block_distance, io_depth); +pg_stat_replication| SELECT s.pid, + s.usesysid, + u.rolname AS usename, + s.application_name, + s.client_addr, + s.client_hostname, + s.client_port, + s.backend_start, + s.backend_xmin, + w.state, + w.sent_lsn, + w.write_lsn, + w.flush_lsn, + w.replay_lsn, + w.write_lag, + w.flush_lag, + w.replay_lag, + w.sync_priority, + w.sync_state, + w.reply_time + FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id, yb_backend_xid) + JOIN pg_stat_get_wal_senders() w(pid, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag, sync_priority, sync_state, reply_time) ON ((s.pid = w.pid))) + LEFT JOIN pg_authid u ON ((s.usesysid = u.oid))); +pg_stat_replication_slots| SELECT s.slot_name, + s.spill_txns, + s.spill_count, + s.spill_bytes, + s.stream_txns, + s.stream_count, + s.stream_bytes, + s.total_txns, + s.total_bytes, + s.stats_reset + FROM pg_replication_slots r, + LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset) + WHERE (r.datoid IS NOT NULL); +pg_stat_slru| SELECT s.name, + s.blks_zeroed, + s.blks_hit, + s.blks_read, + s.blks_written, + s.blks_exists, + s.flushes, + s.truncates, + s.stats_reset + FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset); +pg_stat_ssl| SELECT s.pid, + s.ssl, + s.sslversion AS version, + s.sslcipher AS cipher, + s.sslbits AS bits, + s.ssl_client_dn AS client_dn, + s.ssl_client_serial AS client_serial, + s.ssl_issuer_dn AS issuer_dn + FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid, query_id, yb_backend_xid) + WHERE (s.client_port IS NOT NULL); +pg_stat_statements| SELECT pg_stat_statements.userid, + pg_stat_statements.dbid, + pg_stat_statements.toplevel, + pg_stat_statements.queryid, + pg_stat_statements.query, + pg_stat_statements.plans, + pg_stat_statements.total_plan_time, + pg_stat_statements.min_plan_time, + pg_stat_statements.max_plan_time, + pg_stat_statements.mean_plan_time, + pg_stat_statements.stddev_plan_time, + pg_stat_statements.calls, + pg_stat_statements.total_exec_time, + pg_stat_statements.min_exec_time, + pg_stat_statements.max_exec_time, + pg_stat_statements.mean_exec_time, + pg_stat_statements.stddev_exec_time, + pg_stat_statements.rows, + pg_stat_statements.shared_blks_hit, + pg_stat_statements.shared_blks_read, + pg_stat_statements.shared_blks_dirtied, + pg_stat_statements.shared_blks_written, + pg_stat_statements.local_blks_hit, + pg_stat_statements.local_blks_read, + pg_stat_statements.local_blks_dirtied, + pg_stat_statements.local_blks_written, + pg_stat_statements.temp_blks_read, + pg_stat_statements.temp_blks_written, + pg_stat_statements.blk_read_time, + pg_stat_statements.blk_write_time, + pg_stat_statements.temp_blk_read_time, + pg_stat_statements.temp_blk_write_time, + pg_stat_statements.wal_records, + pg_stat_statements.wal_fpi, + pg_stat_statements.wal_bytes, + pg_stat_statements.jit_functions, + pg_stat_statements.jit_generation_time, + pg_stat_statements.jit_inlining_count, + pg_stat_statements.jit_inlining_time, + pg_stat_statements.jit_optimization_count, + pg_stat_statements.jit_optimization_time, + pg_stat_statements.jit_emission_count, + pg_stat_statements.jit_emission_time, + pg_stat_statements.yb_latency_histogram, + pg_stat_statements.docdb_read_rpcs, + pg_stat_statements.docdb_write_rpcs, + pg_stat_statements.catalog_wait_time, + pg_stat_statements.docdb_read_operations, + pg_stat_statements.docdb_write_operations, + pg_stat_statements.docdb_rows_scanned, + pg_stat_statements.docdb_rows_returned, + pg_stat_statements.docdb_wait_time, + pg_stat_statements.conflict_retries, + pg_stat_statements.read_restart_retries, + pg_stat_statements.total_retries, + pg_stat_statements.docdb_obsolete_rows_scanned, + pg_stat_statements.docdb_seeks, + pg_stat_statements.docdb_nexts, + pg_stat_statements.docdb_prevs, + pg_stat_statements.docdb_read_time, + pg_stat_statements.docdb_write_time + FROM pg_stat_statements(true) pg_stat_statements(userid, dbid, toplevel, queryid, query, plans, total_plan_time, min_plan_time, max_plan_time, mean_plan_time, stddev_plan_time, calls, total_exec_time, min_exec_time, max_exec_time, mean_exec_time, stddev_exec_time, rows, shared_blks_hit, shared_blks_read, shared_blks_dirtied, shared_blks_written, local_blks_hit, local_blks_read, local_blks_dirtied, local_blks_written, temp_blks_read, temp_blks_written, blk_read_time, blk_write_time, temp_blk_read_time, temp_blk_write_time, wal_records, wal_fpi, wal_bytes, jit_functions, jit_generation_time, jit_inlining_count, jit_inlining_time, jit_optimization_count, jit_optimization_time, jit_emission_count, jit_emission_time, yb_latency_histogram, docdb_read_rpcs, docdb_write_rpcs, catalog_wait_time, docdb_read_operations, docdb_write_operations, docdb_rows_scanned, docdb_rows_returned, docdb_wait_time, conflict_retries, read_restart_retries, total_retries, docdb_obsolete_rows_scanned, docdb_seeks, docdb_nexts, docdb_prevs, docdb_read_time, docdb_write_time); +pg_stat_statements_info| SELECT pg_stat_statements_info.dealloc, + pg_stat_statements_info.stats_reset + FROM pg_stat_statements_info() pg_stat_statements_info(dealloc, stats_reset); +pg_stat_subscription| SELECT su.oid AS subid, + su.subname, + st.pid, + st.relid, + st.received_lsn, + st.last_msg_send_time, + st.last_msg_receipt_time, + st.latest_end_lsn, + st.latest_end_time + FROM (pg_subscription su + LEFT JOIN pg_stat_get_subscription(NULL::oid) st(subid, relid, pid, received_lsn, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time) ON ((st.subid = su.oid))); +pg_stat_subscription_stats| SELECT ss.subid, + s.subname, + ss.apply_error_count, + ss.sync_error_count, + ss.stats_reset + FROM pg_subscription s, + LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, stats_reset); +pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid, + pg_stat_all_indexes.indexrelid, + pg_stat_all_indexes.schemaname, + pg_stat_all_indexes.relname, + pg_stat_all_indexes.indexrelname, + pg_stat_all_indexes.idx_scan, + pg_stat_all_indexes.idx_tup_read, + pg_stat_all_indexes.idx_tup_fetch + FROM pg_stat_all_indexes + WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text)); +pg_stat_sys_tables| SELECT pg_stat_all_tables.relid, + pg_stat_all_tables.schemaname, + pg_stat_all_tables.relname, + pg_stat_all_tables.seq_scan, + pg_stat_all_tables.seq_tup_read, + pg_stat_all_tables.idx_scan, + pg_stat_all_tables.idx_tup_fetch, + pg_stat_all_tables.n_tup_ins, + pg_stat_all_tables.n_tup_upd, + pg_stat_all_tables.n_tup_del, + pg_stat_all_tables.n_tup_hot_upd, + pg_stat_all_tables.n_live_tup, + pg_stat_all_tables.n_dead_tup, + pg_stat_all_tables.n_mod_since_analyze, + pg_stat_all_tables.n_ins_since_vacuum, + pg_stat_all_tables.last_vacuum, + pg_stat_all_tables.last_autovacuum, + pg_stat_all_tables.last_analyze, + pg_stat_all_tables.last_autoanalyze, + pg_stat_all_tables.vacuum_count, + pg_stat_all_tables.autovacuum_count, + pg_stat_all_tables.analyze_count, + pg_stat_all_tables.autoanalyze_count + FROM pg_stat_all_tables + WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text)); +pg_stat_user_functions| SELECT p.oid AS funcid, + n.nspname AS schemaname, + p.proname AS funcname, + pg_stat_get_function_calls(p.oid) AS calls, + pg_stat_get_function_total_time(p.oid) AS total_time, + pg_stat_get_function_self_time(p.oid) AS self_time + FROM (pg_proc p + LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace))) + WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_function_calls(p.oid) IS NOT NULL)); +pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid, + pg_stat_all_indexes.indexrelid, + pg_stat_all_indexes.schemaname, + pg_stat_all_indexes.relname, + pg_stat_all_indexes.indexrelname, + pg_stat_all_indexes.idx_scan, + pg_stat_all_indexes.idx_tup_read, + pg_stat_all_indexes.idx_tup_fetch + FROM pg_stat_all_indexes + WHERE ((pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_indexes.schemaname !~ '^pg_toast'::text)); +pg_stat_user_tables| SELECT pg_stat_all_tables.relid, + pg_stat_all_tables.schemaname, + pg_stat_all_tables.relname, + pg_stat_all_tables.seq_scan, + pg_stat_all_tables.seq_tup_read, + pg_stat_all_tables.idx_scan, + pg_stat_all_tables.idx_tup_fetch, + pg_stat_all_tables.n_tup_ins, + pg_stat_all_tables.n_tup_upd, + pg_stat_all_tables.n_tup_del, + pg_stat_all_tables.n_tup_hot_upd, + pg_stat_all_tables.n_live_tup, + pg_stat_all_tables.n_dead_tup, + pg_stat_all_tables.n_mod_since_analyze, + pg_stat_all_tables.n_ins_since_vacuum, + pg_stat_all_tables.last_vacuum, + pg_stat_all_tables.last_autovacuum, + pg_stat_all_tables.last_analyze, + pg_stat_all_tables.last_autoanalyze, + pg_stat_all_tables.vacuum_count, + pg_stat_all_tables.autovacuum_count, + pg_stat_all_tables.analyze_count, + pg_stat_all_tables.autoanalyze_count + FROM pg_stat_all_tables + WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text)); +pg_stat_wal| SELECT w.wal_records, + w.wal_fpi, + w.wal_bytes, + w.wal_buffers_full, + w.wal_write, + w.wal_sync, + w.wal_write_time, + w.wal_sync_time, + w.stats_reset + FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, wal_write, wal_sync, wal_write_time, wal_sync_time, stats_reset); +pg_stat_wal_receiver| SELECT s.pid, + s.status, + s.receive_start_lsn, + s.receive_start_tli, + s.written_lsn, + s.flushed_lsn, + s.received_tli, + s.last_msg_send_time, + s.last_msg_receipt_time, + s.latest_end_lsn, + s.latest_end_time, + s.slot_name, + s.sender_host, + s.sender_port, + s.conninfo + FROM pg_stat_get_wal_receiver() s(pid, status, receive_start_lsn, receive_start_tli, written_lsn, flushed_lsn, received_tli, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time, slot_name, sender_host, sender_port, conninfo) + WHERE (s.pid IS NOT NULL); +pg_stat_xact_all_tables| SELECT c.oid AS relid, + n.nspname AS schemaname, + c.relname, + pg_stat_get_xact_numscans(c.oid) AS seq_scan, + pg_stat_get_xact_tuples_returned(c.oid) AS seq_tup_read, + (sum(pg_stat_get_xact_numscans(i.indexrelid)))::bigint AS idx_scan, + ((sum(pg_stat_get_xact_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_xact_tuples_fetched(c.oid)) AS idx_tup_fetch, + pg_stat_get_xact_tuples_inserted(c.oid) AS n_tup_ins, + pg_stat_get_xact_tuples_updated(c.oid) AS n_tup_upd, + pg_stat_get_xact_tuples_deleted(c.oid) AS n_tup_del, + pg_stat_get_xact_tuples_hot_updated(c.oid) AS n_tup_hot_upd + FROM ((pg_class c + LEFT JOIN pg_index i ON ((c.oid = i.indrelid))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"])) + GROUP BY c.oid, n.nspname, c.relname; +pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid, + pg_stat_xact_all_tables.schemaname, + pg_stat_xact_all_tables.relname, + pg_stat_xact_all_tables.seq_scan, + pg_stat_xact_all_tables.seq_tup_read, + pg_stat_xact_all_tables.idx_scan, + pg_stat_xact_all_tables.idx_tup_fetch, + pg_stat_xact_all_tables.n_tup_ins, + pg_stat_xact_all_tables.n_tup_upd, + pg_stat_xact_all_tables.n_tup_del, + pg_stat_xact_all_tables.n_tup_hot_upd + FROM pg_stat_xact_all_tables + WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text)); +pg_stat_xact_user_functions| SELECT p.oid AS funcid, + n.nspname AS schemaname, + p.proname AS funcname, + pg_stat_get_xact_function_calls(p.oid) AS calls, + pg_stat_get_xact_function_total_time(p.oid) AS total_time, + pg_stat_get_xact_function_self_time(p.oid) AS self_time + FROM (pg_proc p + LEFT JOIN pg_namespace n ON ((n.oid = p.pronamespace))) + WHERE ((p.prolang <> (12)::oid) AND (pg_stat_get_xact_function_calls(p.oid) IS NOT NULL)); +pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid, + pg_stat_xact_all_tables.schemaname, + pg_stat_xact_all_tables.relname, + pg_stat_xact_all_tables.seq_scan, + pg_stat_xact_all_tables.seq_tup_read, + pg_stat_xact_all_tables.idx_scan, + pg_stat_xact_all_tables.idx_tup_fetch, + pg_stat_xact_all_tables.n_tup_ins, + pg_stat_xact_all_tables.n_tup_upd, + pg_stat_xact_all_tables.n_tup_del, + pg_stat_xact_all_tables.n_tup_hot_upd + FROM pg_stat_xact_all_tables + WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text)); +pg_statio_all_indexes| SELECT c.oid AS relid, + i.oid AS indexrelid, + n.nspname AS schemaname, + c.relname, + i.relname AS indexrelname, + (pg_stat_get_blocks_fetched(i.oid) - pg_stat_get_blocks_hit(i.oid)) AS idx_blks_read, + pg_stat_get_blocks_hit(i.oid) AS idx_blks_hit + FROM (((pg_class c + JOIN pg_index x ON ((c.oid = x.indrelid))) + JOIN pg_class i ON ((i.oid = x.indexrelid))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"])); +pg_statio_all_sequences| SELECT c.oid AS relid, + n.nspname AS schemaname, + c.relname, + (pg_stat_get_blocks_fetched(c.oid) - pg_stat_get_blocks_hit(c.oid)) AS blks_read, + pg_stat_get_blocks_hit(c.oid) AS blks_hit + FROM (pg_class c + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE (c.relkind = 'S'::"char"); +pg_statio_all_tables| SELECT c.oid AS relid, + n.nspname AS schemaname, + c.relname, + (pg_stat_get_blocks_fetched(c.oid) - pg_stat_get_blocks_hit(c.oid)) AS heap_blks_read, + pg_stat_get_blocks_hit(c.oid) AS heap_blks_hit, + i.idx_blks_read, + i.idx_blks_hit, + (pg_stat_get_blocks_fetched(t.oid) - pg_stat_get_blocks_hit(t.oid)) AS toast_blks_read, + pg_stat_get_blocks_hit(t.oid) AS toast_blks_hit, + x.idx_blks_read AS tidx_blks_read, + x.idx_blks_hit AS tidx_blks_hit + FROM ((((pg_class c + LEFT JOIN pg_class t ON ((c.reltoastrelid = t.oid))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + LEFT JOIN LATERAL ( SELECT (sum((pg_stat_get_blocks_fetched(pg_index.indexrelid) - pg_stat_get_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read, + (sum(pg_stat_get_blocks_hit(pg_index.indexrelid)))::bigint AS idx_blks_hit + FROM pg_index + WHERE (pg_index.indrelid = c.oid)) i ON (true)) + LEFT JOIN LATERAL ( SELECT (sum((pg_stat_get_blocks_fetched(pg_index.indexrelid) - pg_stat_get_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read, + (sum(pg_stat_get_blocks_hit(pg_index.indexrelid)))::bigint AS idx_blks_hit + FROM pg_index + WHERE (pg_index.indrelid = t.oid)) x ON (true)) + WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"])); +pg_statio_sys_indexes| SELECT pg_statio_all_indexes.relid, + pg_statio_all_indexes.indexrelid, + pg_statio_all_indexes.schemaname, + pg_statio_all_indexes.relname, + pg_statio_all_indexes.indexrelname, + pg_statio_all_indexes.idx_blks_read, + pg_statio_all_indexes.idx_blks_hit + FROM pg_statio_all_indexes + WHERE ((pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_indexes.schemaname ~ '^pg_toast'::text)); +pg_statio_sys_sequences| SELECT pg_statio_all_sequences.relid, + pg_statio_all_sequences.schemaname, + pg_statio_all_sequences.relname, + pg_statio_all_sequences.blks_read, + pg_statio_all_sequences.blks_hit + FROM pg_statio_all_sequences + WHERE ((pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_sequences.schemaname ~ '^pg_toast'::text)); +pg_statio_sys_tables| SELECT pg_statio_all_tables.relid, + pg_statio_all_tables.schemaname, + pg_statio_all_tables.relname, + pg_statio_all_tables.heap_blks_read, + pg_statio_all_tables.heap_blks_hit, + pg_statio_all_tables.idx_blks_read, + pg_statio_all_tables.idx_blks_hit, + pg_statio_all_tables.toast_blks_read, + pg_statio_all_tables.toast_blks_hit, + pg_statio_all_tables.tidx_blks_read, + pg_statio_all_tables.tidx_blks_hit + FROM pg_statio_all_tables + WHERE ((pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_statio_all_tables.schemaname ~ '^pg_toast'::text)); +pg_statio_user_indexes| SELECT pg_statio_all_indexes.relid, + pg_statio_all_indexes.indexrelid, + pg_statio_all_indexes.schemaname, + pg_statio_all_indexes.relname, + pg_statio_all_indexes.indexrelname, + pg_statio_all_indexes.idx_blks_read, + pg_statio_all_indexes.idx_blks_hit + FROM pg_statio_all_indexes + WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text)); +pg_statio_user_sequences| SELECT pg_statio_all_sequences.relid, + pg_statio_all_sequences.schemaname, + pg_statio_all_sequences.relname, + pg_statio_all_sequences.blks_read, + pg_statio_all_sequences.blks_hit + FROM pg_statio_all_sequences + WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text)); +pg_statio_user_tables| SELECT pg_statio_all_tables.relid, + pg_statio_all_tables.schemaname, + pg_statio_all_tables.relname, + pg_statio_all_tables.heap_blks_read, + pg_statio_all_tables.heap_blks_hit, + pg_statio_all_tables.idx_blks_read, + pg_statio_all_tables.idx_blks_hit, + pg_statio_all_tables.toast_blks_read, + pg_statio_all_tables.toast_blks_hit, + pg_statio_all_tables.tidx_blks_read, + pg_statio_all_tables.tidx_blks_hit + FROM pg_statio_all_tables + WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text)); +pg_stats| SELECT n.nspname AS schemaname, + c.relname AS tablename, + a.attname, + s.stainherit AS inherited, + s.stanullfrac AS null_frac, + s.stawidth AS avg_width, + s.stadistinct AS n_distinct, + CASE + WHEN (s.stakind1 = 1) THEN s.stavalues1 + WHEN (s.stakind2 = 1) THEN s.stavalues2 + WHEN (s.stakind3 = 1) THEN s.stavalues3 + WHEN (s.stakind4 = 1) THEN s.stavalues4 + WHEN (s.stakind5 = 1) THEN s.stavalues5 + ELSE NULL::anyarray + END AS most_common_vals, + CASE + WHEN (s.stakind1 = 1) THEN s.stanumbers1 + WHEN (s.stakind2 = 1) THEN s.stanumbers2 + WHEN (s.stakind3 = 1) THEN s.stanumbers3 + WHEN (s.stakind4 = 1) THEN s.stanumbers4 + WHEN (s.stakind5 = 1) THEN s.stanumbers5 + ELSE NULL::real[] + END AS most_common_freqs, + CASE + WHEN (s.stakind1 = 2) THEN s.stavalues1 + WHEN (s.stakind2 = 2) THEN s.stavalues2 + WHEN (s.stakind3 = 2) THEN s.stavalues3 + WHEN (s.stakind4 = 2) THEN s.stavalues4 + WHEN (s.stakind5 = 2) THEN s.stavalues5 + ELSE NULL::anyarray + END AS histogram_bounds, + CASE + WHEN (s.stakind1 = 3) THEN s.stanumbers1[1] + WHEN (s.stakind2 = 3) THEN s.stanumbers2[1] + WHEN (s.stakind3 = 3) THEN s.stanumbers3[1] + WHEN (s.stakind4 = 3) THEN s.stanumbers4[1] + WHEN (s.stakind5 = 3) THEN s.stanumbers5[1] + ELSE NULL::real + END AS correlation, + CASE + WHEN (s.stakind1 = 4) THEN s.stavalues1 + WHEN (s.stakind2 = 4) THEN s.stavalues2 + WHEN (s.stakind3 = 4) THEN s.stavalues3 + WHEN (s.stakind4 = 4) THEN s.stavalues4 + WHEN (s.stakind5 = 4) THEN s.stavalues5 + ELSE NULL::anyarray + END AS most_common_elems, + CASE + WHEN (s.stakind1 = 4) THEN s.stanumbers1 + WHEN (s.stakind2 = 4) THEN s.stanumbers2 + WHEN (s.stakind3 = 4) THEN s.stanumbers3 + WHEN (s.stakind4 = 4) THEN s.stanumbers4 + WHEN (s.stakind5 = 4) THEN s.stanumbers5 + ELSE NULL::real[] + END AS most_common_elem_freqs, + CASE + WHEN (s.stakind1 = 5) THEN s.stanumbers1 + WHEN (s.stakind2 = 5) THEN s.stanumbers2 + WHEN (s.stakind3 = 5) THEN s.stanumbers3 + WHEN (s.stakind4 = 5) THEN s.stanumbers4 + WHEN (s.stakind5 = 5) THEN s.stanumbers5 + ELSE NULL::real[] + END AS elem_count_histogram, + CASE + WHEN (s.stakind1 = 6) THEN s.stavalues1 + WHEN (s.stakind2 = 6) THEN s.stavalues2 + WHEN (s.stakind3 = 6) THEN s.stavalues3 + WHEN (s.stakind4 = 6) THEN s.stavalues4 + WHEN (s.stakind5 = 6) THEN s.stavalues5 + ELSE NULL::anyarray + END AS range_length_histogram, + CASE + WHEN (s.stakind1 = 6) THEN s.stanumbers1[1] + WHEN (s.stakind2 = 6) THEN s.stanumbers2[1] + WHEN (s.stakind3 = 6) THEN s.stanumbers3[1] + WHEN (s.stakind4 = 6) THEN s.stanumbers4[1] + WHEN (s.stakind5 = 6) THEN s.stanumbers5[1] + ELSE NULL::real + END AS range_empty_frac, + CASE + WHEN (s.stakind1 = 7) THEN s.stavalues1 + WHEN (s.stakind2 = 7) THEN s.stavalues2 + WHEN (s.stakind3 = 7) THEN s.stavalues3 + WHEN (s.stakind4 = 7) THEN s.stavalues4 + WHEN (s.stakind5 = 7) THEN s.stavalues5 + ELSE NULL::anyarray + END AS range_bounds_histogram + FROM (((pg_statistic s + JOIN pg_class c ON ((c.oid = s.starelid))) + JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum)))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE ((NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid)))); +pg_stats_ext| SELECT cn.nspname AS schemaname, + c.relname AS tablename, + sn.nspname AS statistics_schemaname, + s.stxname AS statistics_name, + pg_get_userbyid(s.stxowner) AS statistics_owner, + ( SELECT array_agg(a.attname ORDER BY a.attnum) AS array_agg + FROM (unnest(s.stxkeys) k(k) + JOIN pg_attribute a ON (((a.attrelid = s.stxrelid) AND (a.attnum = k.k))))) AS attnames, + pg_get_statisticsobjdef_expressions(s.oid) AS exprs, + s.stxkind AS kinds, + sd.stxdinherit AS inherited, + sd.stxdndistinct AS n_distinct, + sd.stxddependencies AS dependencies, + m.most_common_vals, + m.most_common_val_nulls, + m.most_common_freqs, + m.most_common_base_freqs + FROM (((((pg_statistic_ext s + JOIN pg_class c ON ((c.oid = s.stxrelid))) + JOIN pg_statistic_ext_data sd ON ((s.oid = sd.stxoid))) + LEFT JOIN pg_namespace cn ON ((cn.oid = c.relnamespace))) + LEFT JOIN pg_namespace sn ON ((sn.oid = s.stxnamespace))) + LEFT JOIN LATERAL ( SELECT array_agg(pg_mcv_list_items."values") AS most_common_vals, + array_agg(pg_mcv_list_items.nulls) AS most_common_val_nulls, + array_agg(pg_mcv_list_items.frequency) AS most_common_freqs, + array_agg(pg_mcv_list_items.base_frequency) AS most_common_base_freqs + FROM pg_mcv_list_items(sd.stxdmcv) pg_mcv_list_items(index, "values", nulls, frequency, base_frequency)) m ON ((sd.stxdmcv IS NOT NULL))) + WHERE (pg_has_role(c.relowner, 'USAGE'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid)))); +pg_stats_ext_exprs| SELECT cn.nspname AS schemaname, + c.relname AS tablename, + sn.nspname AS statistics_schemaname, + s.stxname AS statistics_name, + pg_get_userbyid(s.stxowner) AS statistics_owner, + stat.expr, + sd.stxdinherit AS inherited, + (stat.a).stanullfrac AS null_frac, + (stat.a).stawidth AS avg_width, + (stat.a).stadistinct AS n_distinct, + CASE + WHEN ((stat.a).stakind1 = 1) THEN (stat.a).stavalues1 + WHEN ((stat.a).stakind2 = 1) THEN (stat.a).stavalues2 + WHEN ((stat.a).stakind3 = 1) THEN (stat.a).stavalues3 + WHEN ((stat.a).stakind4 = 1) THEN (stat.a).stavalues4 + WHEN ((stat.a).stakind5 = 1) THEN (stat.a).stavalues5 + ELSE NULL::anyarray + END AS most_common_vals, + CASE + WHEN ((stat.a).stakind1 = 1) THEN (stat.a).stanumbers1 + WHEN ((stat.a).stakind2 = 1) THEN (stat.a).stanumbers2 + WHEN ((stat.a).stakind3 = 1) THEN (stat.a).stanumbers3 + WHEN ((stat.a).stakind4 = 1) THEN (stat.a).stanumbers4 + WHEN ((stat.a).stakind5 = 1) THEN (stat.a).stanumbers5 + ELSE NULL::real[] + END AS most_common_freqs, + CASE + WHEN ((stat.a).stakind1 = 2) THEN (stat.a).stavalues1 + WHEN ((stat.a).stakind2 = 2) THEN (stat.a).stavalues2 + WHEN ((stat.a).stakind3 = 2) THEN (stat.a).stavalues3 + WHEN ((stat.a).stakind4 = 2) THEN (stat.a).stavalues4 + WHEN ((stat.a).stakind5 = 2) THEN (stat.a).stavalues5 + ELSE NULL::anyarray + END AS histogram_bounds, + CASE + WHEN ((stat.a).stakind1 = 3) THEN (stat.a).stanumbers1[1] + WHEN ((stat.a).stakind2 = 3) THEN (stat.a).stanumbers2[1] + WHEN ((stat.a).stakind3 = 3) THEN (stat.a).stanumbers3[1] + WHEN ((stat.a).stakind4 = 3) THEN (stat.a).stanumbers4[1] + WHEN ((stat.a).stakind5 = 3) THEN (stat.a).stanumbers5[1] + ELSE NULL::real + END AS correlation, + CASE + WHEN ((stat.a).stakind1 = 4) THEN (stat.a).stavalues1 + WHEN ((stat.a).stakind2 = 4) THEN (stat.a).stavalues2 + WHEN ((stat.a).stakind3 = 4) THEN (stat.a).stavalues3 + WHEN ((stat.a).stakind4 = 4) THEN (stat.a).stavalues4 + WHEN ((stat.a).stakind5 = 4) THEN (stat.a).stavalues5 + ELSE NULL::anyarray + END AS most_common_elems, + CASE + WHEN ((stat.a).stakind1 = 4) THEN (stat.a).stanumbers1 + WHEN ((stat.a).stakind2 = 4) THEN (stat.a).stanumbers2 + WHEN ((stat.a).stakind3 = 4) THEN (stat.a).stanumbers3 + WHEN ((stat.a).stakind4 = 4) THEN (stat.a).stanumbers4 + WHEN ((stat.a).stakind5 = 4) THEN (stat.a).stanumbers5 + ELSE NULL::real[] + END AS most_common_elem_freqs, + CASE + WHEN ((stat.a).stakind1 = 5) THEN (stat.a).stanumbers1 + WHEN ((stat.a).stakind2 = 5) THEN (stat.a).stanumbers2 + WHEN ((stat.a).stakind3 = 5) THEN (stat.a).stanumbers3 + WHEN ((stat.a).stakind4 = 5) THEN (stat.a).stanumbers4 + WHEN ((stat.a).stakind5 = 5) THEN (stat.a).stanumbers5 + ELSE NULL::real[] + END AS elem_count_histogram + FROM (((((pg_statistic_ext s + JOIN pg_class c ON ((c.oid = s.stxrelid))) + LEFT JOIN pg_statistic_ext_data sd ON ((s.oid = sd.stxoid))) + LEFT JOIN pg_namespace cn ON ((cn.oid = c.relnamespace))) + LEFT JOIN pg_namespace sn ON ((sn.oid = s.stxnamespace))) + JOIN LATERAL ( SELECT unnest(pg_get_statisticsobjdef_expressions(s.oid)) AS expr, + unnest(sd.stxdexpr) AS a) stat ON ((stat.expr IS NOT NULL))) + WHERE (pg_has_role(c.relowner, 'USAGE'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid)))); +pg_tables| SELECT n.nspname AS schemaname, + c.relname AS tablename, + pg_get_userbyid(c.relowner) AS tableowner, + t.spcname AS tablespace, + c.relhasindex AS hasindexes, + c.relhasrules AS hasrules, + c.relhastriggers AS hastriggers, + c.relrowsecurity AS rowsecurity + FROM ((pg_class c + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace))) + WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])); +pg_timezone_abbrevs| SELECT pg_timezone_abbrevs.abbrev, + pg_timezone_abbrevs.utc_offset, + pg_timezone_abbrevs.is_dst + FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst); +pg_timezone_names| SELECT pg_timezone_names.name, + pg_timezone_names.abbrev, + pg_timezone_names.utc_offset, + pg_timezone_names.is_dst + FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst); +pg_user| SELECT pg_shadow.usename, + pg_shadow.usesysid, + pg_shadow.usecreatedb, + pg_shadow.usesuper, + pg_shadow.userepl, + pg_shadow.usebypassrls, + '********'::text AS passwd, + pg_shadow.valuntil, + pg_shadow.useconfig + FROM pg_shadow; +pg_user_mappings| SELECT u.oid AS umid, + s.oid AS srvid, + s.srvname, + u.umuser, + CASE + WHEN (u.umuser = (0)::oid) THEN 'public'::name + ELSE a.rolname + END AS usename, + CASE + WHEN (((u.umuser <> (0)::oid) AND (a.rolname = CURRENT_USER) AND (pg_has_role(s.srvowner, 'USAGE'::text) OR has_server_privilege(s.oid, 'USAGE'::text))) OR ((u.umuser = (0)::oid) AND pg_has_role(s.srvowner, 'USAGE'::text)) OR ( SELECT pg_authid.rolsuper + FROM pg_authid + WHERE (pg_authid.rolname = CURRENT_USER))) THEN u.umoptions + ELSE NULL::text[] + END AS umoptions + FROM ((pg_user_mapping u + JOIN pg_foreign_server s ON ((u.umserver = s.oid))) + LEFT JOIN pg_authid a ON ((a.oid = u.umuser))); +pg_views| SELECT n.nspname AS schemaname, + c.relname AS viewname, + pg_get_userbyid(c.relowner) AS viewowner, + pg_get_viewdef(c.oid) AS definition + FROM (pg_class c + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE (c.relkind = 'v'::"char"); +yb_active_session_history| SELECT yb_active_session_history.sample_time, + yb_active_session_history.root_request_id, + yb_active_session_history.rpc_request_id, + yb_active_session_history.wait_event_component, + yb_active_session_history.wait_event_class, + yb_active_session_history.wait_event, + yb_active_session_history.top_level_node_id, + yb_active_session_history.query_id, + yb_active_session_history.pid, + yb_active_session_history.client_node_ip, + yb_active_session_history.wait_event_aux, + yb_active_session_history.sample_weight, + yb_active_session_history.wait_event_type, + yb_active_session_history.ysql_dbid, + yb_active_session_history.wait_event_code, + yb_active_session_history.pss_mem_bytes, + yb_active_session_history.ysql_userid, + yb_active_session_history.plan_id + FROM yb_active_session_history() yb_active_session_history(sample_time, root_request_id, rpc_request_id, wait_event_component, wait_event_class, wait_event, top_level_node_id, query_id, pid, client_node_ip, wait_event_aux, sample_weight, wait_event_type, ysql_dbid, wait_event_code, pss_mem_bytes, ysql_userid, plan_id); +yb_local_tablets| SELECT yb_local_tablets.tablet_id, + yb_local_tablets.table_id, + yb_local_tablets.table_type, + yb_local_tablets.namespace_name, + yb_local_tablets.ysql_schema_name, + yb_local_tablets.table_name, + yb_local_tablets.partition_key_start, + yb_local_tablets.partition_key_end, + yb_local_tablets.state + FROM yb_local_tablets() yb_local_tablets(tablet_id, table_id, table_type, namespace_name, ysql_schema_name, table_name, partition_key_start, partition_key_end, state); +yb_pg_stat_plans| SELECT stat_plans.dbid, + stat_plans.userid, + stat_plans.queryid, + stat_plans.planid, + stat_plans.first_used, + stat_plans.last_used, + stat_plans.hints, + stat_plans.calls, + stat_plans.avg_exec_time, + stat_plans.max_exec_time, + stat_plans.max_exec_time_params, + stat_plans.avg_est_cost, + stat_plans.plan + FROM yb_pg_stat_plans_get_all_entries() stat_plans(dbid oid, userid oid, queryid bigint, planid bigint, first_used timestamp with time zone, last_used timestamp with time zone, hints text, calls bigint, avg_exec_time double precision, max_exec_time double precision, max_exec_time_params text, avg_est_cost double precision, plan text); +yb_pg_stat_plans_insights| WITH cte AS ( + SELECT yb_pg_stat_plans.dbid, + yb_pg_stat_plans.userid, + yb_pg_stat_plans.queryid, + yb_pg_stat_plans.planid, + yb_pg_stat_plans.first_used, + yb_pg_stat_plans.last_used, + yb_pg_stat_plans.hints, + yb_pg_stat_plans.avg_exec_time, + yb_pg_stat_plans.avg_est_cost, + min(yb_pg_stat_plans.avg_exec_time) OVER (PARTITION BY yb_pg_stat_plans.dbid, yb_pg_stat_plans.userid, yb_pg_stat_plans.queryid) AS min_avg_exec_time, + min(yb_pg_stat_plans.avg_est_cost) OVER (PARTITION BY yb_pg_stat_plans.dbid, yb_pg_stat_plans.userid, yb_pg_stat_plans.queryid) AS min_avg_est_cost + FROM yb_pg_stat_plans + ) + SELECT cte.dbid, + cte.userid, + cte.queryid, + cte.planid, + cte.first_used, + cte.last_used, + cte.hints, + cte.avg_exec_time, + cte.avg_est_cost, + cte.min_avg_exec_time, + cte.min_avg_est_cost, + CASE + WHEN (((cte.avg_exec_time = cte.min_avg_exec_time) AND (cte.min_avg_est_cost <> cte.avg_est_cost)) OR ((cte.avg_exec_time <> cte.min_avg_exec_time) AND (cte.min_avg_est_cost = cte.avg_est_cost))) THEN 'Yes'::text + ELSE 'No'::text + END AS plan_require_evaluation, + CASE + WHEN (cte.avg_exec_time = cte.min_avg_exec_time) THEN 'Yes'::text + ELSE 'No'::text + END AS plan_min_exec_time + FROM cte + ORDER BY cte.queryid, cte.planid, cte.last_used; +yb_query_diagnostics_status| SELECT yb_get_query_diagnostics_status.status, + yb_get_query_diagnostics_status.description, + yb_get_query_diagnostics_status.query_id, + yb_get_query_diagnostics_status.start_time, + yb_get_query_diagnostics_status.diagnostics_interval_sec, + yb_get_query_diagnostics_status.bind_var_query_min_duration_ms, + yb_get_query_diagnostics_status.explain_params, + yb_get_query_diagnostics_status.path + FROM yb_get_query_diagnostics_status() yb_get_query_diagnostics_status(status, description, query_id, start_time, diagnostics_interval_sec, bind_var_query_min_duration_ms, explain_params, path); +yb_servers_metrics| SELECT yb_servers_metrics.uuid, + yb_servers_metrics.metrics, + yb_servers_metrics.status, + yb_servers_metrics.error + FROM yb_servers_metrics() yb_servers_metrics(uuid, metrics, status, error); +yb_tablet_metadata| SELECT t.tablet_id, + CASE + WHEN ((t.namespace = 'system'::text) AND (t.object_name = 'transactions'::text)) THEN NULL::oid + WHEN (length(t.object_uuid) <> 32) THEN NULL::oid + ELSE (((('x'::text || "right"(t.object_uuid, 8)))::bit(32))::integer)::oid + END AS oid, + t.namespace AS db_name, + t.object_name AS relname, + t.start_hash_code, + t.end_hash_code, + t.leader, + t.replicas + FROM ((yb_get_tablet_metadata() t(tablet_id, object_uuid, namespace, object_name, type, start_hash_code, end_hash_code, leader, replicas) + LEFT JOIN pg_class c ON ((c.relname = t.object_name))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE (((t.namespace = 'system'::text) AND (t.object_name = 'transactions'::text)) OR ((t.type = 'YSQL'::text) AND (n.nspname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])))); +yb_terminated_queries| SELECT d.datname AS databasename, + s.backend_pid, + s.query_id, + s.query_text, + s.termination_reason, + s.query_start AS query_start_time, + s.query_end AS query_end_time + FROM (yb_pg_stat_get_queries(NULL::oid) s(db_oid, backend_pid, query_id, query_text, termination_reason, query_start, query_end) + LEFT JOIN pg_database d ON ((s.db_oid = d.oid))); +yb_wait_event_desc| SELECT yb_wait_event_desc.wait_event_class, + yb_wait_event_desc.wait_event_type, + yb_wait_event_desc.wait_event, + yb_wait_event_desc.wait_event_description, + yb_wait_event_desc.wait_event_code, + yb_wait_event_desc.wait_event_aux_description + FROM yb_wait_event_desc() yb_wait_event_desc(wait_event_class, wait_event_type, wait_event, wait_event_description, wait_event_code, wait_event_aux_description); +SELECT tablename, rulename, definition FROM pg_rules +WHERE schemaname = 'pg_catalog' +ORDER BY tablename, rulename; +pg_settings|pg_settings_n|CREATE RULE pg_settings_n AS + ON UPDATE TO pg_catalog.pg_settings DO INSTEAD NOTHING; +pg_settings|pg_settings_u|CREATE RULE pg_settings_u AS + ON UPDATE TO pg_catalog.pg_settings + WHERE (new.name = old.name) DO SELECT set_config(old.name, new.setting, false) AS set_config; +-- restore normal output mode +\a\t +-- +-- CREATE OR REPLACE RULE +-- +CREATE TABLE ruletest_tbl (a int, b int); +CREATE TABLE ruletest_tbl2 (a int, b int); +CREATE OR REPLACE RULE myrule AS ON INSERT TO ruletest_tbl + DO INSTEAD INSERT INTO ruletest_tbl2 VALUES (10, 10); +INSERT INTO ruletest_tbl VALUES (99, 99); +CREATE OR REPLACE RULE myrule AS ON INSERT TO ruletest_tbl + DO INSTEAD INSERT INTO ruletest_tbl2 VALUES (1000, 1000); +INSERT INTO ruletest_tbl VALUES (99, 99); +SELECT * FROM ruletest_tbl2 ORDER BY a, b; + a | b +------+------ + 10 | 10 + 1000 | 1000 +(2 rows) + +-- test FOR UPDATE in rules +create table rules_base(f1 int, f2 int); +insert into rules_base values(1,2), (11,12); +create rule r1 as on update to rules_base do instead + select * from rules_base where f1 = 1 for update; +update rules_base set f2 = f2 + 1; + f1 | f2 +----+---- + 1 | 2 +(1 row) + +create or replace rule r1 as on update to rules_base do instead + select * from rules_base where f1 = 11 for update of rules_base; +update rules_base set f2 = f2 + 1; + f1 | f2 +----+---- + 11 | 12 +(1 row) + +create or replace rule r1 as on update to rules_base do instead + select * from rules_base where f1 = 11 for update of old; -- error +ERROR: relation "old" in FOR UPDATE clause not found in FROM clause +LINE 2: select * from rules_base where f1 = 11 for update of old; + ^ +drop table rules_base; +-- +-- Test non-SELECT rule on security invoker view. +-- Should use view owner's permissions. +-- +CREATE USER regress_rule_user1; +CREATE TABLE ruletest_t1 (x int); +CREATE TABLE ruletest_t2 (x int); +CREATE VIEW ruletest_v1 WITH (security_invoker=true) AS + SELECT * FROM ruletest_t1; +GRANT INSERT ON ruletest_v1 TO regress_rule_user1; +CREATE RULE rule1 AS ON INSERT TO ruletest_v1 + DO INSTEAD INSERT INTO ruletest_t2 VALUES (NEW.*); +SET SESSION AUTHORIZATION regress_rule_user1; +INSERT INTO ruletest_v1 VALUES (1); +RESET SESSION AUTHORIZATION; +SELECT * FROM ruletest_t1; + x +--- +(0 rows) + +SELECT * FROM ruletest_t2; + x +--- + 1 +(1 row) + +DROP VIEW ruletest_v1; +DROP TABLE ruletest_t2; +DROP TABLE ruletest_t1; +DROP USER regress_rule_user1; diff --git a/src/test/regress/expected/yb.port.select.out b/src/test/regress/expected/yb.port.select.out new file mode 100644 index 0000000000000..dff3fedc8d667 --- /dev/null +++ b/src/test/regress/expected/yb.port.select.out @@ -0,0 +1,952 @@ +-- +-- SELECT +-- +SET yb_enable_bitmapscan TO on; +-- btree index +-- awk '{if($1<10){print;}else{next;}}' onek.data | sort +0n -1 +-- +SELECT * FROM onek + WHERE onek.unique1 < 10 + ORDER BY onek.unique1; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 0 | 998 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | AAAAAA | KMBAAA | OOOOxx + 1 | 214 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 3 | BAAAAA | GIAAAA | OOOOxx + 2 | 326 | 0 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 4 | 5 | CAAAAA | OMAAAA | OOOOxx + 3 | 431 | 1 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 6 | 7 | DAAAAA | PQAAAA | VVVVxx + 4 | 833 | 0 | 0 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 8 | 9 | EAAAAA | BGBAAA | HHHHxx + 5 | 541 | 1 | 1 | 5 | 5 | 5 | 5 | 5 | 5 | 5 | 10 | 11 | FAAAAA | VUAAAA | HHHHxx + 6 | 978 | 0 | 2 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 12 | 13 | GAAAAA | QLBAAA | OOOOxx + 7 | 647 | 1 | 3 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 14 | 15 | HAAAAA | XYAAAA | VVVVxx + 8 | 653 | 0 | 0 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 16 | 17 | IAAAAA | DZAAAA | HHHHxx + 9 | 49 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 18 | 19 | JAAAAA | XBAAAA | HHHHxx +(10 rows) + +-- +-- awk '{if($1<20){print $1,$14;}else{next;}}' onek.data | sort +0nr -1 +-- +SELECT onek.unique1, onek.stringu1 FROM onek + WHERE onek.unique1 < 20 + ORDER BY unique1 using >; + unique1 | stringu1 +---------+---------- + 19 | TAAAAA + 18 | SAAAAA + 17 | RAAAAA + 16 | QAAAAA + 15 | PAAAAA + 14 | OAAAAA + 13 | NAAAAA + 12 | MAAAAA + 11 | LAAAAA + 10 | KAAAAA + 9 | JAAAAA + 8 | IAAAAA + 7 | HAAAAA + 6 | GAAAAA + 5 | FAAAAA + 4 | EAAAAA + 3 | DAAAAA + 2 | CAAAAA + 1 | BAAAAA + 0 | AAAAAA +(20 rows) + +-- +-- awk '{if($1>980){print $1,$14;}else{next;}}' onek.data | sort +1d -2 +-- +SELECT onek.unique1, onek.stringu1 FROM onek + WHERE onek.unique1 > 980 + ORDER BY stringu1 using <; + unique1 | stringu1 +---------+---------- + 988 | AMAAAA + 989 | BMAAAA + 990 | CMAAAA + 991 | DMAAAA + 992 | EMAAAA + 993 | FMAAAA + 994 | GMAAAA + 995 | HMAAAA + 996 | IMAAAA + 997 | JMAAAA + 998 | KMAAAA + 999 | LMAAAA + 981 | TLAAAA + 982 | ULAAAA + 983 | VLAAAA + 984 | WLAAAA + 985 | XLAAAA + 986 | YLAAAA + 987 | ZLAAAA +(19 rows) + +-- +-- awk '{if($1>980){print $1,$16;}else{next;}}' onek.data | +-- sort +1d -2 +0nr -1 +-- +SELECT onek.unique1, onek.string4 FROM onek + WHERE onek.unique1 > 980 + ORDER BY string4 using <, unique1 using >; + unique1 | string4 +---------+--------- + 999 | AAAAxx + 995 | AAAAxx + 983 | AAAAxx + 982 | AAAAxx + 981 | AAAAxx + 998 | HHHHxx + 997 | HHHHxx + 993 | HHHHxx + 990 | HHHHxx + 986 | HHHHxx + 996 | OOOOxx + 991 | OOOOxx + 988 | OOOOxx + 987 | OOOOxx + 985 | OOOOxx + 994 | VVVVxx + 992 | VVVVxx + 989 | VVVVxx + 984 | VVVVxx +(19 rows) + +-- +-- awk '{if($1>980){print $1,$16;}else{next;}}' onek.data | +-- sort +1dr -2 +0n -1 +-- +SELECT onek.unique1, onek.string4 FROM onek + WHERE onek.unique1 > 980 + ORDER BY string4 using >, unique1 using <; + unique1 | string4 +---------+--------- + 984 | VVVVxx + 989 | VVVVxx + 992 | VVVVxx + 994 | VVVVxx + 985 | OOOOxx + 987 | OOOOxx + 988 | OOOOxx + 991 | OOOOxx + 996 | OOOOxx + 986 | HHHHxx + 990 | HHHHxx + 993 | HHHHxx + 997 | HHHHxx + 998 | HHHHxx + 981 | AAAAxx + 982 | AAAAxx + 983 | AAAAxx + 995 | AAAAxx + 999 | AAAAxx +(19 rows) + +-- +-- awk '{if($1<20){print $1,$16;}else{next;}}' onek.data | +-- sort +0nr -1 +1d -2 +-- +SELECT onek.unique1, onek.string4 FROM onek + WHERE onek.unique1 < 20 + ORDER BY unique1 using >, string4 using <; + unique1 | string4 +---------+--------- + 19 | OOOOxx + 18 | VVVVxx + 17 | HHHHxx + 16 | OOOOxx + 15 | VVVVxx + 14 | AAAAxx + 13 | OOOOxx + 12 | AAAAxx + 11 | OOOOxx + 10 | AAAAxx + 9 | HHHHxx + 8 | HHHHxx + 7 | VVVVxx + 6 | OOOOxx + 5 | HHHHxx + 4 | HHHHxx + 3 | VVVVxx + 2 | OOOOxx + 1 | OOOOxx + 0 | OOOOxx +(20 rows) + +-- +-- awk '{if($1<20){print $1,$16;}else{next;}}' onek.data | +-- sort +0n -1 +1dr -2 +-- +SELECT onek.unique1, onek.string4 FROM onek + WHERE onek.unique1 < 20 + ORDER BY unique1 using <, string4 using >; + unique1 | string4 +---------+--------- + 0 | OOOOxx + 1 | OOOOxx + 2 | OOOOxx + 3 | VVVVxx + 4 | HHHHxx + 5 | HHHHxx + 6 | OOOOxx + 7 | VVVVxx + 8 | HHHHxx + 9 | HHHHxx + 10 | AAAAxx + 11 | OOOOxx + 12 | AAAAxx + 13 | OOOOxx + 14 | AAAAxx + 15 | VVVVxx + 16 | OOOOxx + 17 | HHHHxx + 18 | VVVVxx + 19 | OOOOxx +(20 rows) + +-- +-- test partial btree indexes +-- +-- As of 7.2, planner probably won't pick an indexscan without stats, +-- so ANALYZE first. Also, we want to prevent it from picking a bitmapscan +-- followed by sort, because that could hide index ordering problems. +-- +ANALYZE onek2; +SET enable_seqscan TO off; +SET enable_bitmapscan TO off; +SET enable_sort TO off; +-- +-- awk '{if($1<10){print $0;}else{next;}}' onek.data | sort +0n -1 +-- +SELECT onek2.* FROM onek2 WHERE onek2.unique1 < 10; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 0 | 998 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | AAAAAA | KMBAAA | OOOOxx + 1 | 214 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 3 | BAAAAA | GIAAAA | OOOOxx + 2 | 326 | 0 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 4 | 5 | CAAAAA | OMAAAA | OOOOxx + 3 | 431 | 1 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 6 | 7 | DAAAAA | PQAAAA | VVVVxx + 4 | 833 | 0 | 0 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 8 | 9 | EAAAAA | BGBAAA | HHHHxx + 5 | 541 | 1 | 1 | 5 | 5 | 5 | 5 | 5 | 5 | 5 | 10 | 11 | FAAAAA | VUAAAA | HHHHxx + 6 | 978 | 0 | 2 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 12 | 13 | GAAAAA | QLBAAA | OOOOxx + 7 | 647 | 1 | 3 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 14 | 15 | HAAAAA | XYAAAA | VVVVxx + 8 | 653 | 0 | 0 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 16 | 17 | IAAAAA | DZAAAA | HHHHxx + 9 | 49 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 18 | 19 | JAAAAA | XBAAAA | HHHHxx +(10 rows) + +-- +-- awk '{if($1<20){print $1,$14;}else{next;}}' onek.data | sort +0nr -1 +-- +SELECT onek2.unique1, onek2.stringu1 FROM onek2 + WHERE onek2.unique1 < 20 + ORDER BY unique1 using >; + unique1 | stringu1 +---------+---------- + 19 | TAAAAA + 18 | SAAAAA + 17 | RAAAAA + 16 | QAAAAA + 15 | PAAAAA + 14 | OAAAAA + 13 | NAAAAA + 12 | MAAAAA + 11 | LAAAAA + 10 | KAAAAA + 9 | JAAAAA + 8 | IAAAAA + 7 | HAAAAA + 6 | GAAAAA + 5 | FAAAAA + 4 | EAAAAA + 3 | DAAAAA + 2 | CAAAAA + 1 | BAAAAA + 0 | AAAAAA +(20 rows) + +-- +-- awk '{if($1>980){print $1,$14;}else{next;}}' onek.data | sort +1d -2 +-- +SELECT onek2.unique1, onek2.stringu1 FROM onek2 + WHERE onek2.unique1 > 980; + unique1 | stringu1 +---------+---------- + 981 | TLAAAA + 982 | ULAAAA + 983 | VLAAAA + 984 | WLAAAA + 985 | XLAAAA + 986 | YLAAAA + 987 | ZLAAAA + 988 | AMAAAA + 989 | BMAAAA + 990 | CMAAAA + 991 | DMAAAA + 992 | EMAAAA + 993 | FMAAAA + 994 | GMAAAA + 995 | HMAAAA + 996 | IMAAAA + 997 | JMAAAA + 998 | KMAAAA + 999 | LMAAAA +(19 rows) + +RESET enable_seqscan; +RESET enable_bitmapscan; +RESET enable_sort; +-- +-- awk '{print $1,$2;}' person.data | +-- awk '{if(NF!=2){print $3,$2;}else{print;}}' - emp.data | +-- awk '{if(NF!=2){print $3,$2;}else{print;}}' - student.data | +-- awk 'BEGIN{FS=" ";}{if(NF!=2){print $4,$5;}else{print;}}' - stud_emp.data +-- +-- SELECT name, age FROM person*; ??? check if different +SELECT p.name, p.age FROM person* p; -- YB: output missing rows due to lack of INHERITS support + name | age +---------+----- + mike | 40 + joe | 20 + sally | 34 + sandra | 19 + alex | 30 + sue | 50 + denise | 24 + sarah | 88 + teresa | 38 + nan | 28 + leah | 68 + wendy | 78 + melissa | 28 + joan | 18 + mary | 8 + jane | 58 + liza | 38 + jean | 28 + jenifer | 38 + juanita | 58 + susan | 78 + zena | 98 + martie | 88 + chris | 78 + pat | 18 + zola | 58 + louise | 98 + edna | 18 + bertha | 88 + sumi | 38 + koko | 88 + gina | 18 + rean | 48 + sharon | 78 + paula | 68 + julie | 68 + belinda | 38 + karen | 48 + carina | 58 + diane | 18 + esther | 98 + trudy | 88 + fanny | 8 + carmen | 78 + lita | 25 + pamela | 48 + sandy | 38 + trisha | 88 + uma | 78 + velma | 68 +(50 rows) + +-- +-- awk '{print $1,$2;}' person.data | +-- awk '{if(NF!=2){print $3,$2;}else{print;}}' - emp.data | +-- awk '{if(NF!=2){print $3,$2;}else{print;}}' - student.data | +-- awk 'BEGIN{FS=" ";}{if(NF!=1){print $4,$5;}else{print;}}' - stud_emp.data | +-- sort +1nr -2 +-- +SELECT p.name, p.age FROM person* p ORDER BY age using >, name; -- YB: output missing rows due to lack of INHERITS support + name | age +---------+----- + esther | 98 + louise | 98 + zena | 98 + bertha | 88 + koko | 88 + martie | 88 + sarah | 88 + trisha | 88 + trudy | 88 + carmen | 78 + chris | 78 + sharon | 78 + susan | 78 + uma | 78 + wendy | 78 + julie | 68 + leah | 68 + paula | 68 + velma | 68 + carina | 58 + jane | 58 + juanita | 58 + zola | 58 + sue | 50 + karen | 48 + pamela | 48 + rean | 48 + mike | 40 + belinda | 38 + jenifer | 38 + liza | 38 + sandy | 38 + sumi | 38 + teresa | 38 + sally | 34 + alex | 30 + jean | 28 + melissa | 28 + nan | 28 + lita | 25 + denise | 24 + joe | 20 + sandra | 19 + diane | 18 + edna | 18 + gina | 18 + joan | 18 + pat | 18 + fanny | 8 + mary | 8 +(50 rows) + +-- +-- Test some cases involving whole-row Var referencing a subquery +-- +select foo from (select 1 offset 0) as foo; + foo +----- + (1) +(1 row) + +select foo from (select null offset 0) as foo; + foo +----- + () +(1 row) + +select foo from (select 'xyzzy',1,null offset 0) as foo; + foo +------------ + (xyzzy,1,) +(1 row) + +-- +-- Test VALUES lists +-- +select * from onek, (values(147, 'RFAAAA'), (931, 'VJAAAA')) as v (i, j) + WHERE onek.unique1 = v.i and onek.stringu1 = v.j; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 | i | j +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------+-----+-------- + 147 | 0 | 1 | 3 | 7 | 7 | 7 | 47 | 147 | 147 | 147 | 14 | 15 | RFAAAA | AAAAAA | AAAAxx | 147 | RFAAAA + 931 | 1 | 1 | 3 | 1 | 11 | 1 | 31 | 131 | 431 | 931 | 2 | 3 | VJAAAA | BAAAAA | HHHHxx | 931 | VJAAAA +(2 rows) + +-- a more complex case +-- looks like we're coding lisp :-) +select * from onek, + (values ((select i from + (values(10000), (2), (389), (1000), (2000), ((select 10029))) as foo(i) + order by i asc limit 1))) bar (i) + where onek.unique1 = bar.i; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 | i +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------+--- + 2 | 326 | 0 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 4 | 5 | CAAAAA | OMAAAA | OOOOxx | 2 +(1 row) + +-- try VALUES in a subquery +select * from onek + where (unique1,ten) in (values (1,1), (20,0), (99,9), (17,99)) + order by unique1; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 1 | 214 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 3 | BAAAAA | GIAAAA | OOOOxx + 20 | 306 | 0 | 0 | 0 | 0 | 0 | 20 | 20 | 20 | 20 | 0 | 1 | UAAAAA | ULAAAA | OOOOxx + 99 | 101 | 1 | 3 | 9 | 19 | 9 | 99 | 99 | 99 | 99 | 18 | 19 | VDAAAA | XDAAAA | HHHHxx +(3 rows) + +-- VALUES is also legal as a standalone query or a set-operation member +VALUES (1,2), (3,4+4), (7,77.7); + column1 | column2 +---------+--------- + 1 | 2 + 3 | 8 + 7 | 77.7 +(3 rows) + +VALUES (1,2), (3,4+4), (7,77.7) +UNION ALL +SELECT 2+2, 57 +UNION ALL +TABLE int8_tbl; + column1 | column2 +------------------+------------------- + 1 | 2 + 3 | 8 + 7 | 77.7 + 4 | 57 + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(9 rows) + +-- corner case: VALUES with no columns +CREATE TEMP TABLE nocols(); +INSERT INTO nocols DEFAULT VALUES; +SELECT * FROM nocols n, LATERAL (VALUES(n.*)) v; +-- +(1 row) + +-- +-- Test ORDER BY options +-- +CREATE TEMP TABLE foo (f1 int); +INSERT INTO foo VALUES (42),(3),(10),(7),(null),(null),(1); +SELECT * FROM foo ORDER BY f1; + f1 +---- + 1 + 3 + 7 + 10 + 42 + + +(7 rows) + +SELECT * FROM foo ORDER BY f1 ASC; -- same thing + f1 +---- + 1 + 3 + 7 + 10 + 42 + + +(7 rows) + +SELECT * FROM foo ORDER BY f1 NULLS FIRST; + f1 +---- + + + 1 + 3 + 7 + 10 + 42 +(7 rows) + +SELECT * FROM foo ORDER BY f1 DESC; + f1 +---- + + + 42 + 10 + 7 + 3 + 1 +(7 rows) + +SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; + f1 +---- + 42 + 10 + 7 + 3 + 1 + + +(7 rows) + +-- check if indexscans do the right things +CREATE INDEX fooi ON foo (f1); +SET enable_sort = false; +SELECT * FROM foo ORDER BY f1; + f1 +---- + 1 + 3 + 7 + 10 + 42 + + +(7 rows) + +SELECT * FROM foo ORDER BY f1 NULLS FIRST; + f1 +---- + + + 1 + 3 + 7 + 10 + 42 +(7 rows) + +SELECT * FROM foo ORDER BY f1 DESC; + f1 +---- + + + 42 + 10 + 7 + 3 + 1 +(7 rows) + +SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; + f1 +---- + 42 + 10 + 7 + 3 + 1 + + +(7 rows) + +DROP INDEX fooi; +CREATE INDEX fooi ON foo (f1 DESC); +SELECT * FROM foo ORDER BY f1; + f1 +---- + 1 + 3 + 7 + 10 + 42 + + +(7 rows) + +SELECT * FROM foo ORDER BY f1 NULLS FIRST; + f1 +---- + + + 1 + 3 + 7 + 10 + 42 +(7 rows) + +SELECT * FROM foo ORDER BY f1 DESC; + f1 +---- + + + 42 + 10 + 7 + 3 + 1 +(7 rows) + +SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; + f1 +---- + 42 + 10 + 7 + 3 + 1 + + +(7 rows) + +DROP INDEX fooi; +CREATE INDEX fooi ON foo (f1 DESC NULLS LAST); +SELECT * FROM foo ORDER BY f1; + f1 +---- + 1 + 3 + 7 + 10 + 42 + + +(7 rows) + +SELECT * FROM foo ORDER BY f1 NULLS FIRST; + f1 +---- + + + 1 + 3 + 7 + 10 + 42 +(7 rows) + +SELECT * FROM foo ORDER BY f1 DESC; + f1 +---- + + + 42 + 10 + 7 + 3 + 1 +(7 rows) + +SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; + f1 +---- + 42 + 10 + 7 + 3 + 1 + + +(7 rows) + +-- +-- Test planning of some cases with partial indexes +-- +-- partial index is usable +explain (costs off) +select * from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; + QUERY PLAN +----------------------------------------------- + Index Scan using onek2_u2_prtl on onek2 + Index Cond: (unique2 = 11) + Storage Filter: (stringu1 = 'ATAAAA'::name) +(3 rows) + +select * from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 494 | 11 | 0 | 2 | 4 | 14 | 4 | 94 | 94 | 494 | 494 | 8 | 9 | ATAAAA | LAAAAA | VVVVxx +(1 row) + +-- actually run the query with an analyze to use the partial index +explain (costs off, analyze on, timing off, summary off) +select * from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; + QUERY PLAN +----------------------------------------------------------------- + Index Scan using onek2_u2_prtl on onek2 (actual rows=1 loops=1) + Index Cond: (unique2 = 11) + Storage Filter: (stringu1 = 'ATAAAA'::name) +(3 rows) + +explain (costs off) +select unique2 from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; + QUERY PLAN +----------------------------------------------- + Index Scan using onek2_u2_prtl on onek2 + Index Cond: (unique2 = 11) + Storage Filter: (stringu1 = 'ATAAAA'::name) +(3 rows) + +select unique2 from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; + unique2 +--------- + 11 +(1 row) + +-- partial index predicate implies clause, so no need for retest +explain (costs off) +select * from onek2 where unique2 = 11 and stringu1 < 'B'; + QUERY PLAN +----------------------------------------- + Index Scan using onek2_u2_prtl on onek2 + Index Cond: (unique2 = 11) +(2 rows) + +select * from onek2 where unique2 = 11 and stringu1 < 'B'; + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 494 | 11 | 0 | 2 | 4 | 14 | 4 | 94 | 94 | 494 | 494 | 8 | 9 | ATAAAA | LAAAAA | VVVVxx +(1 row) + +explain (costs off) +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; + QUERY PLAN +---------------------------------------------- + Index Only Scan using onek2_u2_prtl on onek2 + Index Cond: (unique2 = 11) +(2 rows) + +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; + unique2 +--------- + 11 +(1 row) + +-- but if it's an update target, must retest anyway +explain (costs off) +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B' for update; + QUERY PLAN +------------------------------------------------ + LockRows + -> Index Scan using onek2_u2_prtl on onek2 + Index Cond: (unique2 = 11) + Storage Filter: (stringu1 < 'B'::name) +(4 rows) + +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B' for update; + unique2 +--------- + 11 +(1 row) + +-- partial index is not applicable +explain (costs off) +select unique2 from onek2 where unique2 = 11 and stringu1 < 'C'; + QUERY PLAN +--------------------------------------------------------------- + Seq Scan on onek2 + Storage Filter: ((stringu1 < 'C'::name) AND (unique2 = 11)) +(2 rows) + +select unique2 from onek2 where unique2 = 11 and stringu1 < 'C'; + unique2 +--------- + 11 +(1 row) + +-- partial index implies clause, but bitmap scan must recheck predicate anyway +SET enable_indexscan TO off; +explain (costs off) +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; + QUERY PLAN +------------------------------------------ + YB Bitmap Table Scan on onek2 + -> Bitmap Index Scan on onek2_u2_prtl + Index Cond: (unique2 = 11) +(3 rows) + +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; + unique2 +--------- + 11 +(1 row) + +RESET enable_indexscan; +-- check multi-index cases too +explain (costs off) +select unique1, unique2 from onek2 + where (unique2 = 11 or unique1 = 0) and stringu1 < 'B'; + QUERY PLAN +------------------------------------------------ + YB Bitmap Table Scan on onek2 + Storage Filter: (stringu1 < 'B'::name) + -> BitmapOr + -> Bitmap Index Scan on onek2_u2_prtl + Index Cond: (unique2 = 11) + -> Bitmap Index Scan on onek2_u1_prtl + Index Cond: (unique1 = 0) +(7 rows) + +select unique1, unique2 from onek2 + where (unique2 = 11 or unique1 = 0) and stringu1 < 'B'; + unique1 | unique2 +---------+--------- + 0 | 998 + 494 | 11 +(2 rows) + +explain (costs off) +select unique1, unique2 from onek2 + where (unique2 = 11 and stringu1 < 'B') or unique1 = 0; + QUERY PLAN +------------------------------------------------ + YB Bitmap Table Scan on onek2 + -> BitmapOr + -> Bitmap Index Scan on onek2_u2_prtl + Index Cond: (unique2 = 11) + -> Bitmap Index Scan on onek2_u1_prtl + Index Cond: (unique1 = 0) +(6 rows) + +select unique1, unique2 from onek2 + where (unique2 = 11 and stringu1 < 'B') or unique1 = 0; + unique1 | unique2 +---------+--------- + 0 | 998 + 494 | 11 +(2 rows) + +-- +-- Test some corner cases that have been known to confuse the planner +-- +-- ORDER BY on a constant doesn't really need any sorting +SELECT 1 AS x ORDER BY x; + x +--- + 1 +(1 row) + +-- But ORDER BY on a set-valued expression does +create function sillysrf(int) returns setof int as + 'values (1),(10),(2),($1)' language sql immutable; +select sillysrf(42); + sillysrf +---------- + 1 + 10 + 2 + 42 +(4 rows) + +select sillysrf(-1) order by 1; + sillysrf +---------- + -1 + 1 + 2 + 10 +(4 rows) + +drop function sillysrf(int); +-- X = X isn't a no-op, it's effectively X IS NOT NULL assuming = is strict +-- (see bug #5084) +select * from (values (2),(null),(1)) v(k) where k = k order by k; + k +--- + 1 + 2 +(2 rows) + +select * from (values (2),(null),(1)) v(k) where k = k; + k +--- + 2 + 1 +(2 rows) + +-- Test partitioned tables with no partitions, which should be handled the +-- same as the non-inheritance case when expanding its RTE. +create table list_parted_tbl (a int,b int) partition by list (a); +create table list_parted_tbl1 partition of list_parted_tbl + for values in (1) partition by list(b); +explain (costs off) select * from list_parted_tbl; + QUERY PLAN +-------------------------- + Result list_parted_tbl + One-Time Filter: false +(2 rows) + +drop table list_parted_tbl; diff --git a/src/test/regress/expected/yb.port.select_distinct.out b/src/test/regress/expected/yb.port.select_distinct.out new file mode 100644 index 0000000000000..142db939c7f49 --- /dev/null +++ b/src/test/regress/expected/yb.port.select_distinct.out @@ -0,0 +1,245 @@ +-- +-- SELECT_DISTINCT +-- +-- +-- awk '{print $3;}' onek.data | sort -n | uniq +-- +SELECT DISTINCT two FROM onek ORDER BY 1; + two +----- + 0 + 1 +(2 rows) + +-- +-- awk '{print $5;}' onek.data | sort -n | uniq +-- +SELECT DISTINCT ten FROM onek ORDER BY 1; + ten +----- + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +(10 rows) + +-- +-- awk '{print $16;}' onek.data | sort -d | uniq +-- +SELECT DISTINCT string4 FROM onek ORDER BY 1; + string4 +--------- + AAAAxx + HHHHxx + OOOOxx + VVVVxx +(4 rows) + +-- +-- awk '{print $3,$16,$5;}' onek.data | sort -d | uniq | +-- sort +0n -1 +1d -2 +2n -3 +-- +SELECT DISTINCT two, string4, ten + FROM onek + ORDER BY two using <, string4 using <, ten using <; + two | string4 | ten +-----+---------+----- + 0 | AAAAxx | 0 + 0 | AAAAxx | 2 + 0 | AAAAxx | 4 + 0 | AAAAxx | 6 + 0 | AAAAxx | 8 + 0 | HHHHxx | 0 + 0 | HHHHxx | 2 + 0 | HHHHxx | 4 + 0 | HHHHxx | 6 + 0 | HHHHxx | 8 + 0 | OOOOxx | 0 + 0 | OOOOxx | 2 + 0 | OOOOxx | 4 + 0 | OOOOxx | 6 + 0 | OOOOxx | 8 + 0 | VVVVxx | 0 + 0 | VVVVxx | 2 + 0 | VVVVxx | 4 + 0 | VVVVxx | 6 + 0 | VVVVxx | 8 + 1 | AAAAxx | 1 + 1 | AAAAxx | 3 + 1 | AAAAxx | 5 + 1 | AAAAxx | 7 + 1 | AAAAxx | 9 + 1 | HHHHxx | 1 + 1 | HHHHxx | 3 + 1 | HHHHxx | 5 + 1 | HHHHxx | 7 + 1 | HHHHxx | 9 + 1 | OOOOxx | 1 + 1 | OOOOxx | 3 + 1 | OOOOxx | 5 + 1 | OOOOxx | 7 + 1 | OOOOxx | 9 + 1 | VVVVxx | 1 + 1 | VVVVxx | 3 + 1 | VVVVxx | 5 + 1 | VVVVxx | 7 + 1 | VVVVxx | 9 +(40 rows) + +-- +-- awk '{print $2;}' person.data | +-- awk '{if(NF!=1){print $2;}else{print;}}' - emp.data | +-- awk '{if(NF!=1){print $2;}else{print;}}' - student.data | +-- awk 'BEGIN{FS=" ";}{if(NF!=1){print $5;}else{print;}}' - stud_emp.data | +-- sort -n -r | uniq +-- TODO(#1129): add missing rows 23, 60 when inheritance is supported. +-- +SELECT DISTINCT p.age FROM person* p ORDER BY age using >; + age +----- + 98 + 88 + 78 + 68 + 58 + 50 + 48 + 40 + 38 + 34 + 30 + 28 + 25 + 24 + 20 + 19 + 18 + 8 +(18 rows) + +-- +-- Check mentioning same column more than once +-- +EXPLAIN (VERBOSE, COSTS OFF) +SELECT count(*) FROM + (SELECT DISTINCT two, four, two FROM tenk1) ss; + QUERY PLAN +-------------------------------------------------------- + Aggregate + Output: count(*) + -> HashAggregate + Output: tenk1.two, tenk1.four, tenk1.two + Group Key: tenk1.two, tenk1.four, tenk1.two + -> Seq Scan on public.tenk1 + Output: tenk1.two, tenk1.four, tenk1.two +(7 rows) + +SELECT count(*) FROM + (SELECT DISTINCT two, four, two FROM tenk1) ss; + count +------- + 4 +(1 row) + +-- +-- Also, some tests of IS DISTINCT FROM, which doesn't quite deserve its +-- very own regression file. +-- +CREATE TEMP TABLE disttable (f1 integer); +INSERT INTO DISTTABLE VALUES(1); +INSERT INTO DISTTABLE VALUES(2); +INSERT INTO DISTTABLE VALUES(3); +INSERT INTO DISTTABLE VALUES(NULL); +-- basic cases +SELECT f1, f1 IS DISTINCT FROM 2 as "not 2" FROM disttable; + f1 | not 2 +----+------- + 1 | t + 2 | f + 3 | t + | t +(4 rows) + +SELECT f1, f1 IS DISTINCT FROM NULL as "not null" FROM disttable; + f1 | not null +----+---------- + 1 | t + 2 | t + 3 | t + | f +(4 rows) + +SELECT f1, f1 IS DISTINCT FROM f1 as "false" FROM disttable; + f1 | false +----+------- + 1 | f + 2 | f + 3 | f + | f +(4 rows) + +SELECT f1, f1 IS DISTINCT FROM f1+1 as "not null" FROM disttable; + f1 | not null +----+---------- + 1 | t + 2 | t + 3 | t + | f +(4 rows) + +-- check that optimizer constant-folds it properly +SELECT 1 IS DISTINCT FROM 2 as "yes"; + yes +----- + t +(1 row) + +SELECT 2 IS DISTINCT FROM 2 as "no"; + no +---- + f +(1 row) + +SELECT 2 IS DISTINCT FROM null as "yes"; + yes +----- + t +(1 row) + +SELECT null IS DISTINCT FROM null as "no"; + no +---- + f +(1 row) + +-- negated form +SELECT 1 IS NOT DISTINCT FROM 2 as "no"; + no +---- + f +(1 row) + +SELECT 2 IS NOT DISTINCT FROM 2 as "yes"; + yes +----- + t +(1 row) + +SELECT 2 IS NOT DISTINCT FROM null as "no"; + no +---- + f +(1 row) + +SELECT null IS NOT DISTINCT FROM null as "yes"; + yes +----- + t +(1 row) + diff --git a/src/test/regress/expected/yb.port.sequence.out b/src/test/regress/expected/yb.port.sequence.out new file mode 100644 index 0000000000000..441eb01fff3a5 --- /dev/null +++ b/src/test/regress/expected/yb.port.sequence.out @@ -0,0 +1,855 @@ +-- +-- CREATE SEQUENCE +-- +-- various error cases +CREATE SEQUENCE sequence_testx INCREMENT BY 0; +ERROR: INCREMENT must not be zero +CREATE SEQUENCE sequence_testx INCREMENT BY -1 MINVALUE 20; +ERROR: MINVALUE (20) must be less than MAXVALUE (-1) +CREATE SEQUENCE sequence_testx INCREMENT BY 1 MAXVALUE -20; +ERROR: MINVALUE (1) must be less than MAXVALUE (-20) +CREATE SEQUENCE sequence_testx INCREMENT BY -1 START 10; +ERROR: START value (10) cannot be greater than MAXVALUE (-1) +CREATE SEQUENCE sequence_testx INCREMENT BY 1 START -10; +ERROR: START value (-10) cannot be less than MINVALUE (1) +CREATE SEQUENCE sequence_testx CACHE 0; +ERROR: CACHE (0) must be greater than zero +-- OWNED BY errors +CREATE SEQUENCE sequence_testx OWNED BY nobody; -- nonsense word +ERROR: invalid OWNED BY option +HINT: Specify OWNED BY table.column or OWNED BY NONE. +CREATE SEQUENCE sequence_testx OWNED BY pg_class_oid_index.oid; -- not a table +ERROR: sequence cannot be owned by relation "pg_class_oid_index" +DETAIL: This operation is not supported for indexes. +SET SESSION AUTHORIZATION postgres; -- YB: the following query expects to be run as the owner of pg_class +CREATE SEQUENCE sequence_testx OWNED BY pg_class.relname; -- not same schema +ERROR: sequence must be in same schema as table it is linked to +RESET SESSION AUTHORIZATION; -- YB: undo above +CREATE TABLE sequence_test_table (a int); +CREATE SEQUENCE sequence_testx OWNED BY sequence_test_table.b; -- wrong column +ERROR: column "b" of relation "sequence_test_table" does not exist +DROP TABLE sequence_test_table; +-- sequence data types +CREATE SEQUENCE sequence_test5 AS integer; +CREATE SEQUENCE sequence_test6 AS smallint; +CREATE SEQUENCE sequence_test7 AS bigint; +CREATE SEQUENCE sequence_test8 AS integer MAXVALUE 100000; +CREATE SEQUENCE sequence_test9 AS integer INCREMENT BY -1; +CREATE SEQUENCE sequence_test10 AS integer MINVALUE -100000 START 1; +CREATE SEQUENCE sequence_test11 AS smallint; +CREATE SEQUENCE sequence_test12 AS smallint INCREMENT -1; +CREATE SEQUENCE sequence_test13 AS smallint MINVALUE -32768; +CREATE SEQUENCE sequence_test14 AS smallint MAXVALUE 32767 INCREMENT -1; +CREATE SEQUENCE sequence_testx AS text; +ERROR: sequence type must be smallint, integer, or bigint +CREATE SEQUENCE sequence_testx AS nosuchtype; +ERROR: type "nosuchtype" does not exist +LINE 1: CREATE SEQUENCE sequence_testx AS nosuchtype; + ^ +CREATE SEQUENCE sequence_testx AS smallint MAXVALUE 100000; +ERROR: MAXVALUE (100000) is out of range for sequence data type smallint +CREATE SEQUENCE sequence_testx AS smallint MINVALUE -100000; +ERROR: MINVALUE (-100000) is out of range for sequence data type smallint +ALTER SEQUENCE sequence_test5 AS smallint; -- success, max will be adjusted +ALTER SEQUENCE sequence_test8 AS smallint; -- fail, max has to be adjusted +ERROR: MAXVALUE (100000) is out of range for sequence data type smallint +ALTER SEQUENCE sequence_test8 AS smallint MAXVALUE 20000; -- ok now +ALTER SEQUENCE sequence_test9 AS smallint; -- success, min will be adjusted +ALTER SEQUENCE sequence_test10 AS smallint; -- fail, min has to be adjusted +ERROR: MINVALUE (-100000) is out of range for sequence data type smallint +ALTER SEQUENCE sequence_test10 AS smallint MINVALUE -20000; -- ok now +ALTER SEQUENCE sequence_test11 AS int; -- max will be adjusted +ALTER SEQUENCE sequence_test12 AS int; -- min will be adjusted +ALTER SEQUENCE sequence_test13 AS int; -- min and max will be adjusted +ALTER SEQUENCE sequence_test14 AS int; -- min and max will be adjusted +--- +--- test creation of SERIAL column +--- +CREATE TABLE serialTest1 (f1 text, f2 serial); +INSERT INTO serialTest1 VALUES ('foo'); +INSERT INTO serialTest1 VALUES ('bar'); +INSERT INTO serialTest1 VALUES ('force', 100); +INSERT INTO serialTest1 VALUES ('wrong', NULL); +ERROR: null value in column "f2" of relation "serialtest1" violates not-null constraint +DETAIL: Failing row contains (wrong, null). +SELECT * FROM serialTest1 ORDER BY f2; -- YB: add ordering + f1 | f2 +-------+----- + foo | 1 + bar | 2 + force | 100 +(3 rows) + +SELECT pg_get_serial_sequence('serialTest1', 'f2'); + pg_get_serial_sequence +--------------------------- + public.serialtest1_f2_seq +(1 row) + +-- test smallserial / bigserial +CREATE TABLE serialTest2 (f1 text, f2 serial, f3 smallserial, f4 serial2, + f5 bigserial, f6 serial8); +INSERT INTO serialTest2 (f1) + VALUES ('test_defaults'); +INSERT INTO serialTest2 (f1, f2, f3, f4, f5, f6) + VALUES ('test_max_vals', 2147483647, 32767, 32767, 9223372036854775807, + 9223372036854775807), + ('test_min_vals', -2147483648, -32768, -32768, -9223372036854775808, + -9223372036854775808); +-- All these INSERTs should fail: +INSERT INTO serialTest2 (f1, f3) + VALUES ('bogus', -32769); +ERROR: smallint out of range +INSERT INTO serialTest2 (f1, f4) + VALUES ('bogus', -32769); +ERROR: smallint out of range +INSERT INTO serialTest2 (f1, f3) + VALUES ('bogus', 32768); +ERROR: smallint out of range +INSERT INTO serialTest2 (f1, f4) + VALUES ('bogus', 32768); +ERROR: smallint out of range +INSERT INTO serialTest2 (f1, f5) + VALUES ('bogus', -9223372036854775809); +ERROR: bigint out of range +INSERT INTO serialTest2 (f1, f6) + VALUES ('bogus', -9223372036854775809); +ERROR: bigint out of range +INSERT INTO serialTest2 (f1, f5) + VALUES ('bogus', 9223372036854775808); +ERROR: bigint out of range +INSERT INTO serialTest2 (f1, f6) + VALUES ('bogus', 9223372036854775808); +ERROR: bigint out of range +SELECT * FROM serialTest2 ORDER BY f2 ASC; + f1 | f2 | f3 | f4 | f5 | f6 +---------------+-------------+--------+--------+----------------------+---------------------- + test_min_vals | -2147483648 | -32768 | -32768 | -9223372036854775808 | -9223372036854775808 + test_defaults | 1 | 1 | 1 | 1 | 1 + test_max_vals | 2147483647 | 32767 | 32767 | 9223372036854775807 | 9223372036854775807 +(3 rows) + +SELECT nextval('serialTest2_f2_seq'); + nextval +--------- + 2 +(1 row) + +SELECT nextval('serialTest2_f3_seq'); + nextval +--------- + 2 +(1 row) + +SELECT nextval('serialTest2_f4_seq'); + nextval +--------- + 2 +(1 row) + +SELECT nextval('serialTest2_f5_seq'); + nextval +--------- + 2 +(1 row) + +SELECT nextval('serialTest2_f6_seq'); + nextval +--------- + 2 +(1 row) + +-- basic sequence operations using both text and oid references +CREATE SEQUENCE sequence_test; +CREATE SEQUENCE IF NOT EXISTS sequence_test; +NOTICE: relation "sequence_test" already exists, skipping +SELECT nextval('sequence_test'::text); + nextval +--------- + 1 +(1 row) + +SELECT nextval('sequence_test'::regclass); + nextval +--------- + 2 +(1 row) + +SELECT currval('sequence_test'::text); + currval +--------- + 2 +(1 row) + +SELECT currval('sequence_test'::regclass); + currval +--------- + 2 +(1 row) + +SELECT setval('sequence_test'::text, 32); + setval +-------- + 32 +(1 row) + +SELECT nextval('sequence_test'::regclass); + nextval +--------- + 33 +(1 row) + +SELECT setval('sequence_test'::text, 99, false); + setval +-------- + 99 +(1 row) + +SELECT nextval('sequence_test'::regclass); + nextval +--------- + 99 +(1 row) + +SELECT setval('sequence_test'::regclass, 32); + setval +-------- + 32 +(1 row) + +SELECT nextval('sequence_test'::text); + nextval +--------- + 33 +(1 row) + +SELECT setval('sequence_test'::regclass, 99, false); + setval +-------- + 99 +(1 row) + +SELECT nextval('sequence_test'::text); + nextval +--------- + 99 +(1 row) + +DISCARD SEQUENCES; +SELECT currval('sequence_test'::regclass); +ERROR: currval of sequence "sequence_test" is not yet defined in this session +DROP SEQUENCE sequence_test; +-- renaming sequences +CREATE SEQUENCE foo_seq; +ALTER TABLE foo_seq RENAME TO foo_seq_new; +SELECT * FROM foo_seq_new; + last_value | log_cnt | is_called +------------+---------+----------- + 1 | 0 | f +(1 row) + +SELECT nextval('foo_seq_new'); + nextval +--------- + 1 +(1 row) + +SELECT nextval('foo_seq_new'); + nextval +--------- + 2 +(1 row) + +-- log_cnt can be higher if there is a checkpoint just at the right +-- time, so just test for the expected range +SELECT last_value, log_cnt IN (0) AS log_cnt_ok, is_called FROM foo_seq_new; -- YB: log_cnt is always zero + last_value | log_cnt_ok | is_called +------------+------------+----------- + 2 | t | t +(1 row) + +DROP SEQUENCE foo_seq_new; +-- renaming serial sequences +ALTER TABLE serialtest1_f2_seq RENAME TO serialtest1_f2_foo; +INSERT INTO serialTest1 VALUES ('more'); +SELECT * FROM serialTest1 ORDER BY substr(f1, 3); -- YB: add ordering + f1 | f2 +-------+----- + foo | 1 + bar | 2 + force | 100 + more | 3 +(4 rows) + +-- +-- Check dependencies of serial and ordinary sequences +-- +CREATE TEMP SEQUENCE myseq2; +CREATE TEMP SEQUENCE myseq3; +CREATE TEMP TABLE t1 ( + f1 serial, + f2 int DEFAULT nextval('myseq2'), + f3 int DEFAULT nextval('myseq3'::text) +); +-- Both drops should fail, but with different error messages: +DROP SEQUENCE t1_f1_seq; +ERROR: cannot drop sequence t1_f1_seq because other objects depend on it +DETAIL: default value for column f1 of table t1 depends on sequence t1_f1_seq +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP SEQUENCE myseq2; +ERROR: cannot drop sequence myseq2 because other objects depend on it +DETAIL: default value for column f2 of table t1 depends on sequence myseq2 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- This however will work: +DROP SEQUENCE myseq3; +DROP TABLE t1; +-- Fails because no longer existent: +DROP SEQUENCE t1_f1_seq; +ERROR: sequence "t1_f1_seq" does not exist +-- Now OK: +DROP SEQUENCE myseq2; +-- +-- Alter sequence +-- +ALTER SEQUENCE IF EXISTS sequence_test2 RESTART WITH 24 + INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE; +NOTICE: relation "sequence_test2" does not exist, skipping +ALTER SEQUENCE serialTest1 CYCLE; -- error, not a sequence +ERROR: "serialtest1" is not a sequence +CREATE SEQUENCE sequence_test2 START WITH 32; +CREATE SEQUENCE sequence_test4 INCREMENT BY -1; +SELECT nextval('sequence_test2'); + nextval +--------- + 32 +(1 row) + +SELECT nextval('sequence_test4'); + nextval +--------- + -1 +(1 row) + +ALTER SEQUENCE sequence_test2 RESTART; +SELECT nextval('sequence_test2'); + nextval +--------- + 32 +(1 row) + +ALTER SEQUENCE sequence_test2 RESTART WITH 0; -- error +ERROR: RESTART value (0) cannot be less than MINVALUE (1) +ALTER SEQUENCE sequence_test4 RESTART WITH 40; -- error +ERROR: RESTART value (40) cannot be greater than MAXVALUE (-1) +-- test CYCLE and NO CYCLE +ALTER SEQUENCE sequence_test2 RESTART WITH 24 + INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE; +SELECT nextval('sequence_test2'); + nextval +--------- + 24 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + 28 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + 32 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + 36 +(1 row) + +SELECT nextval('sequence_test2'); -- cycled + nextval +--------- + 5 +(1 row) + +ALTER SEQUENCE sequence_test2 RESTART WITH 24 + NO CYCLE; +SELECT nextval('sequence_test2'); + nextval +--------- + 24 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + 28 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + 32 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + 36 +(1 row) + +SELECT nextval('sequence_test2'); -- error +ERROR: nextval: reached maximum value of sequence "sequence_test2" (36) +ALTER SEQUENCE sequence_test2 RESTART WITH -24 START WITH -24 + INCREMENT BY -4 MINVALUE -36 MAXVALUE -5 CYCLE; +SELECT nextval('sequence_test2'); + nextval +--------- + -24 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + -28 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + -32 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + -36 +(1 row) + +SELECT nextval('sequence_test2'); -- cycled + nextval +--------- + -5 +(1 row) + +ALTER SEQUENCE sequence_test2 RESTART WITH -24 + NO CYCLE; +SELECT nextval('sequence_test2'); + nextval +--------- + -24 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + -28 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + -32 +(1 row) + +SELECT nextval('sequence_test2'); + nextval +--------- + -36 +(1 row) + +SELECT nextval('sequence_test2'); -- error +ERROR: nextval: reached minimum value of sequence "sequence_test2" (-36) +-- reset +ALTER SEQUENCE IF EXISTS sequence_test2 RESTART WITH 32 START WITH 32 + INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE; +SELECT setval('sequence_test2', -100); -- error +ERROR: setval: value -100 is out of bounds for sequence "sequence_test2" (5..36) +SELECT setval('sequence_test2', 100); -- error +ERROR: setval: value 100 is out of bounds for sequence "sequence_test2" (5..36) +SELECT setval('sequence_test2', 5); + setval +-------- + 5 +(1 row) + +CREATE SEQUENCE sequence_test3; -- not read from, to test is_called +-- Information schema +SELECT * FROM information_schema.sequences + WHERE sequence_name ~ ANY(ARRAY['sequence_test', 'serialtest']) + ORDER BY sequence_name ASC; + sequence_catalog | sequence_schema | sequence_name | data_type | numeric_precision | numeric_precision_radix | numeric_scale | start_value | minimum_value | maximum_value | increment | cycle_option +------------------+-----------------+--------------------+-----------+-------------------+-------------------------+---------------+-------------+----------------------+---------------------+-----------+-------------- + yugabyte | public | sequence_test10 | smallint | 16 | 2 | 0 | 1 | -20000 | 32767 | 1 | NO + yugabyte | public | sequence_test11 | integer | 32 | 2 | 0 | 1 | 1 | 2147483647 | 1 | NO + yugabyte | public | sequence_test12 | integer | 32 | 2 | 0 | -1 | -2147483648 | -1 | -1 | NO + yugabyte | public | sequence_test13 | integer | 32 | 2 | 0 | -32768 | -2147483648 | 2147483647 | 1 | NO + yugabyte | public | sequence_test14 | integer | 32 | 2 | 0 | 32767 | -2147483648 | 2147483647 | -1 | NO + yugabyte | public | sequence_test2 | bigint | 64 | 2 | 0 | 32 | 5 | 36 | 4 | YES + yugabyte | public | sequence_test3 | bigint | 64 | 2 | 0 | 1 | 1 | 9223372036854775807 | 1 | NO + yugabyte | public | sequence_test4 | bigint | 64 | 2 | 0 | -1 | -9223372036854775808 | -1 | -1 | NO + yugabyte | public | sequence_test5 | smallint | 16 | 2 | 0 | 1 | 1 | 32767 | 1 | NO + yugabyte | public | sequence_test6 | smallint | 16 | 2 | 0 | 1 | 1 | 32767 | 1 | NO + yugabyte | public | sequence_test7 | bigint | 64 | 2 | 0 | 1 | 1 | 9223372036854775807 | 1 | NO + yugabyte | public | sequence_test8 | smallint | 16 | 2 | 0 | 1 | 1 | 20000 | 1 | NO + yugabyte | public | sequence_test9 | smallint | 16 | 2 | 0 | -1 | -32768 | -1 | -1 | NO + yugabyte | public | serialtest1_f2_foo | integer | 32 | 2 | 0 | 1 | 1 | 2147483647 | 1 | NO + yugabyte | public | serialtest2_f2_seq | integer | 32 | 2 | 0 | 1 | 1 | 2147483647 | 1 | NO + yugabyte | public | serialtest2_f3_seq | smallint | 16 | 2 | 0 | 1 | 1 | 32767 | 1 | NO + yugabyte | public | serialtest2_f4_seq | smallint | 16 | 2 | 0 | 1 | 1 | 32767 | 1 | NO + yugabyte | public | serialtest2_f5_seq | bigint | 64 | 2 | 0 | 1 | 1 | 9223372036854775807 | 1 | NO + yugabyte | public | serialtest2_f6_seq | bigint | 64 | 2 | 0 | 1 | 1 | 9223372036854775807 | 1 | NO +(19 rows) + +SELECT schemaname, sequencename, start_value, min_value, max_value, increment_by, cycle, cache_size -- YB: YB doesn't store last_value in pg_sequences +FROM pg_sequences +WHERE sequencename ~ ANY(ARRAY['sequence_test', 'serialtest']) + ORDER BY sequencename ASC; + schemaname | sequencename | start_value | min_value | max_value | increment_by | cycle | cache_size +------------+--------------------+-------------+----------------------+---------------------+--------------+-------+------------ + public | sequence_test10 | 1 | -20000 | 32767 | 1 | f | 1 + public | sequence_test11 | 1 | 1 | 2147483647 | 1 | f | 1 + public | sequence_test12 | -1 | -2147483648 | -1 | -1 | f | 1 + public | sequence_test13 | -32768 | -2147483648 | 2147483647 | 1 | f | 1 + public | sequence_test14 | 32767 | -2147483648 | 2147483647 | -1 | f | 1 + public | sequence_test2 | 32 | 5 | 36 | 4 | t | 1 + public | sequence_test3 | 1 | 1 | 9223372036854775807 | 1 | f | 1 + public | sequence_test4 | -1 | -9223372036854775808 | -1 | -1 | f | 1 + public | sequence_test5 | 1 | 1 | 32767 | 1 | f | 1 + public | sequence_test6 | 1 | 1 | 32767 | 1 | f | 1 + public | sequence_test7 | 1 | 1 | 9223372036854775807 | 1 | f | 1 + public | sequence_test8 | 1 | 1 | 20000 | 1 | f | 1 + public | sequence_test9 | -1 | -32768 | -1 | -1 | f | 1 + public | serialtest1_f2_foo | 1 | 1 | 2147483647 | 1 | f | 1 + public | serialtest2_f2_seq | 1 | 1 | 2147483647 | 1 | f | 1 + public | serialtest2_f3_seq | 1 | 1 | 32767 | 1 | f | 1 + public | serialtest2_f4_seq | 1 | 1 | 32767 | 1 | f | 1 + public | serialtest2_f5_seq | 1 | 1 | 9223372036854775807 | 1 | f | 1 + public | serialtest2_f6_seq | 1 | 1 | 9223372036854775807 | 1 | f | 1 +(19 rows) + +SELECT * FROM pg_sequence_parameters('sequence_test4'::regclass); + start_value | minimum_value | maximum_value | increment | cycle_option | cache_size | data_type +-------------+----------------------+---------------+-----------+--------------+------------+----------- + -1 | -9223372036854775808 | -1 | -1 | f | 1 | 20 +(1 row) + +\d sequence_test4 + Sequence "public.sequence_test4" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+----------------------+---------+-----------+---------+------- + bigint | -1 | -9223372036854775808 | -1 | -1 | no | 1 + +\d serialtest2_f2_seq + Sequence "public.serialtest2_f2_seq" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +---------+-------+---------+------------+-----------+---------+------- + integer | 1 | 1 | 2147483647 | 1 | no | 1 +Owned by: public.serialtest2.f2 + +-- Test comments +COMMENT ON SEQUENCE asdf IS 'won''t work'; +ERROR: relation "asdf" does not exist +COMMENT ON SEQUENCE sequence_test2 IS 'will work'; +COMMENT ON SEQUENCE sequence_test2 IS NULL; +-- Test lastval() +CREATE SEQUENCE seq; +SELECT nextval('seq'); + nextval +--------- + 1 +(1 row) + +SELECT lastval(); + lastval +--------- + 1 +(1 row) + +SELECT setval('seq', 99); + setval +-------- + 99 +(1 row) + +SELECT lastval(); + lastval +--------- + 99 +(1 row) + +DISCARD SEQUENCES; +SELECT lastval(); +ERROR: lastval is not yet defined in this session +CREATE SEQUENCE seq2; +SELECT nextval('seq2'); + nextval +--------- + 1 +(1 row) + +SELECT lastval(); + lastval +--------- + 1 +(1 row) + +DROP SEQUENCE seq2; +-- should fail +SELECT lastval(); +ERROR: lastval is not yet defined in this session +-- unlogged sequences +-- (more tests in src/test/recovery/) +CREATE UNLOGGED SEQUENCE sequence_test_unlogged; +NOTICE: unlogged option is currently ignored in YugabyteDB, all non-temp relations will be logged +ALTER SEQUENCE sequence_test_unlogged SET LOGGED; +\d sequence_test_unlogged + Sequence "public.sequence_test_unlogged" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 + +ALTER SEQUENCE sequence_test_unlogged SET UNLOGGED; +NOTICE: unlogged option is currently ignored in YugabyteDB, all non-temp relations will be logged +\d sequence_test_unlogged + Sequence "public.sequence_test_unlogged" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 + +DROP SEQUENCE sequence_test_unlogged; +-- Test sequences in read-only transactions +CREATE TEMPORARY SEQUENCE sequence_test_temp1; +START TRANSACTION READ ONLY; +SELECT nextval('sequence_test_temp1'); -- ok + nextval +--------- + 1 +(1 row) + +SELECT nextval('sequence_test2'); -- error +ERROR: cannot execute nextval() in a read-only transaction +ROLLBACK; +START TRANSACTION READ ONLY; +SELECT setval('sequence_test_temp1', 1); -- ok + setval +-------- + 1 +(1 row) + +SELECT setval('sequence_test2', 1); -- error +ERROR: cannot execute setval() in a read-only transaction +ROLLBACK; +-- privileges tests +CREATE USER regress_seq_user; +-- nextval +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT SELECT ON seq3 TO regress_seq_user; +SELECT nextval('seq3'); +ERROR: permission denied for sequence seq3 +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT UPDATE ON seq3 TO regress_seq_user; +SELECT nextval('seq3'); + nextval +--------- + 1 +(1 row) + +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT USAGE ON seq3 TO regress_seq_user; +SELECT nextval('seq3'); + nextval +--------- + 1 +(1 row) + +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK +-- currval +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); + nextval +--------- + 1 +(1 row) + +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT SELECT ON seq3 TO regress_seq_user; +SELECT currval('seq3'); + currval +--------- + 1 +(1 row) + +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); + nextval +--------- + 1 +(1 row) + +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT UPDATE ON seq3 TO regress_seq_user; +SELECT currval('seq3'); +ERROR: permission denied for sequence seq3 +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); + nextval +--------- + 1 +(1 row) + +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT USAGE ON seq3 TO regress_seq_user; +SELECT currval('seq3'); + currval +--------- + 1 +(1 row) + +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK +-- lastval +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); + nextval +--------- + 1 +(1 row) + +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT SELECT ON seq3 TO regress_seq_user; +SELECT lastval(); + lastval +--------- + 1 +(1 row) + +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); + nextval +--------- + 1 +(1 row) + +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT UPDATE ON seq3 TO regress_seq_user; +SELECT lastval(); +ERROR: permission denied for sequence seq3 +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); + nextval +--------- + 1 +(1 row) + +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT USAGE ON seq3 TO regress_seq_user; +SELECT lastval(); + lastval +--------- + 1 +(1 row) + +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK +-- setval +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +REVOKE ALL ON seq3 FROM regress_seq_user; +SAVEPOINT save; +SELECT setval('seq3', 5); +ERROR: permission denied for sequence seq3 +ROLLBACK TO save; +GRANT UPDATE ON seq3 TO regress_seq_user; +SELECT setval('seq3', 5); + setval +-------- + 5 +(1 row) + +SELECT nextval('seq3'); + nextval +--------- + 6 +(1 row) + +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK +-- ALTER SEQUENCE +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +ALTER SEQUENCE sequence_test2 START WITH 1; +ERROR: must be owner of sequence sequence_test2 +ROLLBACK; +-- Sequences should get wiped out as well: +DROP TABLE serialTest1, serialTest2; +-- Make sure sequences are gone: +SELECT * FROM information_schema.sequences WHERE sequence_name IN + ('sequence_test2', 'serialtest2_f2_seq', 'serialtest2_f3_seq', + 'serialtest2_f4_seq', 'serialtest2_f5_seq', 'serialtest2_f6_seq') + ORDER BY sequence_name ASC; + sequence_catalog | sequence_schema | sequence_name | data_type | numeric_precision | numeric_precision_radix | numeric_scale | start_value | minimum_value | maximum_value | increment | cycle_option +------------------+-----------------+----------------+-----------+-------------------+-------------------------+---------------+-------------+---------------+---------------+-----------+-------------- + yugabyte | public | sequence_test2 | bigint | 64 | 2 | 0 | 32 | 5 | 36 | 4 | YES +(1 row) + +DROP USER regress_seq_user; +DROP SEQUENCE seq; +-- cache tests +CREATE SEQUENCE test_seq1 CACHE 10; +SELECT nextval('test_seq1'); + nextval +--------- + 1 +(1 row) + +SELECT nextval('test_seq1'); + nextval +--------- + 2 +(1 row) + +SELECT nextval('test_seq1'); + nextval +--------- + 3 +(1 row) + +DROP SEQUENCE test_seq1; diff --git a/src/test/regress/expected/yb.port.stats_ext.out b/src/test/regress/expected/yb.port.stats_ext.out new file mode 100644 index 0000000000000..6f7a36a730814 --- /dev/null +++ b/src/test/regress/expected/yb.port.stats_ext.out @@ -0,0 +1,3274 @@ +-- Generic extended statistics support +-- +-- Note: tables for which we check estimated row counts should be created +-- with autovacuum_enabled = off, so that we don't have unstable results +-- from auto-analyze happening when we didn't expect it. +-- +-- check the number of estimated/actual rows in the top node +create function check_estimated_rows(text) returns table (estimated int, actual int) +language plpgsql as +$$ +declare + ln text; + tmp text[]; + first_row bool := true; +begin + for ln in + execute format('explain analyze %s', $1) + loop + if first_row then + first_row := false; + tmp := regexp_match(ln, 'rows=(\d*) .* rows=(\d*)'); + return query select tmp[1]::int, tmp[2]::int; + end if; + end loop; +end; +$$; +-- YB note: enable CBO +SET yb_enable_optimizer_statistics TO on; +SET yb_enable_base_scans_cost_model TO on; +-- Verify failures +CREATE TABLE ext_stats_test (x text, y int, z int); +CREATE STATISTICS tst; +ERROR: syntax error at or near ";" +LINE 1: CREATE STATISTICS tst; + ^ +CREATE STATISTICS tst ON a, b; +ERROR: syntax error at or near ";" +LINE 1: CREATE STATISTICS tst ON a, b; + ^ +CREATE STATISTICS tst FROM sometab; +ERROR: syntax error at or near "FROM" +LINE 1: CREATE STATISTICS tst FROM sometab; + ^ +CREATE STATISTICS tst ON a, b FROM nonexistent; +ERROR: relation "nonexistent" does not exist +CREATE STATISTICS tst ON a, b FROM ext_stats_test; +ERROR: column "a" does not exist +CREATE STATISTICS tst ON x, x, y FROM ext_stats_test; +ERROR: duplicate column name in statistics definition +CREATE STATISTICS tst ON x, x, y, x, x, y, x, x, y FROM ext_stats_test; +ERROR: cannot have more than 8 columns in statistics +CREATE STATISTICS tst ON x, x, y, x, x, (x || 'x'), (y + 1), (x || 'x'), (x || 'x'), (y + 1) FROM ext_stats_test; +ERROR: cannot have more than 8 columns in statistics +CREATE STATISTICS tst ON (x || 'x'), (x || 'x'), (y + 1), (x || 'x'), (x || 'x'), (y + 1), (x || 'x'), (x || 'x'), (y + 1) FROM ext_stats_test; +ERROR: cannot have more than 8 columns in statistics +CREATE STATISTICS tst ON (x || 'x'), (x || 'x'), y FROM ext_stats_test; +ERROR: duplicate expression in statistics definition +CREATE STATISTICS tst (unrecognized) ON x, y FROM ext_stats_test; +ERROR: unrecognized statistics kind "unrecognized" +-- incorrect expressions +CREATE STATISTICS tst ON (y) FROM ext_stats_test; -- single column reference +ERROR: extended statistics require at least 2 columns +CREATE STATISTICS tst ON y + z FROM ext_stats_test; -- missing parentheses +ERROR: syntax error at or near "+" +LINE 1: CREATE STATISTICS tst ON y + z FROM ext_stats_test; + ^ +CREATE STATISTICS tst ON (x, y) FROM ext_stats_test; -- tuple expression +ERROR: syntax error at or near "," +LINE 1: CREATE STATISTICS tst ON (x, y) FROM ext_stats_test; + ^ +DROP TABLE ext_stats_test; +-- Ensure stats are dropped sanely, and test IF NOT EXISTS while at it +CREATE TABLE ab1 (a INTEGER, b INTEGER, c INTEGER); +CREATE STATISTICS IF NOT EXISTS ab1_a_b_stats ON a, b FROM ab1; +COMMENT ON STATISTICS ab1_a_b_stats IS 'new comment'; +CREATE ROLE regress_stats_ext; +SET SESSION AUTHORIZATION regress_stats_ext; +COMMENT ON STATISTICS ab1_a_b_stats IS 'changed comment'; +ERROR: must be owner of statistics object ab1_a_b_stats +DROP STATISTICS ab1_a_b_stats; +ERROR: must be owner of statistics object ab1_a_b_stats +ALTER STATISTICS ab1_a_b_stats RENAME TO ab1_a_b_stats_new; +ERROR: must be owner of statistics object ab1_a_b_stats +RESET SESSION AUTHORIZATION; +DROP ROLE regress_stats_ext; +CREATE STATISTICS IF NOT EXISTS ab1_a_b_stats ON a, b FROM ab1; +NOTICE: statistics object "ab1_a_b_stats" already exists, skipping +DROP STATISTICS ab1_a_b_stats; +CREATE SCHEMA regress_schema_2; +CREATE STATISTICS regress_schema_2.ab1_a_b_stats ON a, b FROM ab1; +-- Let's also verify the pg_get_statisticsobjdef output looks sane. +SELECT pg_get_statisticsobjdef(oid) FROM pg_statistic_ext WHERE stxname = 'ab1_a_b_stats'; + pg_get_statisticsobjdef +------------------------------------------------------------------- + CREATE STATISTICS regress_schema_2.ab1_a_b_stats ON a, b FROM ab1 +(1 row) + +DROP STATISTICS regress_schema_2.ab1_a_b_stats; +-- Ensure statistics are dropped when columns are +CREATE STATISTICS ab1_b_c_stats ON b, c FROM ab1; +CREATE STATISTICS ab1_a_b_c_stats ON a, b, c FROM ab1; +CREATE STATISTICS ab1_b_a_stats ON b, a FROM ab1; +ALTER TABLE ab1 DROP COLUMN a; +\d ab1 + Table "public.ab1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + b | integer | | | + c | integer | | | +Statistics objects: + "public.ab1_b_c_stats" ON b, c FROM ab1 + +-- Ensure statistics are dropped when table is +SELECT stxname FROM pg_statistic_ext WHERE stxname LIKE 'ab1%'; + stxname +--------------- + ab1_b_c_stats +(1 row) + +DROP TABLE ab1; +SELECT stxname FROM pg_statistic_ext WHERE stxname LIKE 'ab1%'; + stxname +--------- +(0 rows) + +-- Ensure things work sanely with SET STATISTICS 0 +CREATE TABLE ab1 (a INTEGER, b INTEGER); +ALTER TABLE ab1 ALTER a SET STATISTICS 0; +INSERT INTO ab1 SELECT a, a%23 FROM generate_series(1, 1000) a; +CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; +ANALYZE ab1; +WARNING: statistics object "public.ab1_a_b_stats" could not be computed for relation "public.ab1" +ALTER TABLE ab1 ALTER a SET STATISTICS -1; +-- setting statistics target 0 skips the statistics, without printing any message, so check catalog +ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0; +\d ab1 + Table "public.ab1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | +Statistics objects: + "public.ab1_a_b_stats" ON a, b FROM ab1; STATISTICS 0 + +ANALYZE ab1; +SELECT stxname, stxdndistinct, stxddependencies, stxdmcv, stxdinherit + FROM pg_statistic_ext s LEFT JOIN pg_statistic_ext_data d ON (d.stxoid = s.oid) + WHERE s.stxname = 'ab1_a_b_stats'; + stxname | stxdndistinct | stxddependencies | stxdmcv | stxdinherit +---------------+---------------+------------------+---------+------------- + ab1_a_b_stats | | | | +(1 row) + +ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1; +\d+ ab1 + Table "public.ab1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | +Statistics objects: + "public.ab1_a_b_stats" ON a, b FROM ab1 + +-- partial analyze doesn't build stats either +ANALYZE ab1 (a); +WARNING: statistics object "public.ab1_a_b_stats" could not be computed for relation "public.ab1" +ANALYZE ab1; +DROP TABLE ab1; +ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0; +ERROR: statistics object "ab1_a_b_stats" does not exist +ALTER STATISTICS IF EXISTS ab1_a_b_stats SET STATISTICS 0; +NOTICE: statistics object "ab1_a_b_stats" does not exist, skipping +/* YB: uncomment when INHERITS is supported (#5956) +-- Ensure we can build statistics for tables with inheritance. +CREATE TABLE ab1 (a INTEGER, b INTEGER); +CREATE TABLE ab1c () INHERITS (ab1); +INSERT INTO ab1 VALUES (1,1); +CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; +ANALYZE ab1; +DROP TABLE ab1 CASCADE; + +-- Tests for stats with inheritance +CREATE TABLE stxdinh(a int, b int); +CREATE TABLE stxdinh1() INHERITS(stxdinh); +CREATE TABLE stxdinh2() INHERITS(stxdinh); +INSERT INTO stxdinh SELECT mod(a,50), mod(a,100) FROM generate_series(0, 1999) a; +INSERT INTO stxdinh1 SELECT mod(a,100), mod(a,100) FROM generate_series(0, 999) a; +INSERT INTO stxdinh2 SELECT mod(a,100), mod(a,100) FROM generate_series(0, 999) a; +VACUUM ANALYZE stxdinh, stxdinh1, stxdinh2; +-- Ensure non-inherited stats are not applied to inherited query +-- Without stats object, it looks like this +SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinh* GROUP BY 1, 2'); +SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinh* WHERE a = 0 AND b = 0'); +CREATE STATISTICS stxdinh ON a, b FROM stxdinh; +VACUUM ANALYZE stxdinh, stxdinh1, stxdinh2; +-- See if the extended stats affect the estimates +SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinh* GROUP BY 1, 2'); +-- Dependencies are applied at individual relations (within append), so +-- this estimate changes a bit because we improve estimates for the parent +SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinh* WHERE a = 0 AND b = 0'); +-- Ensure correct (non-inherited) stats are applied to inherited query +SELECT * FROM check_estimated_rows('SELECT a, b FROM ONLY stxdinh GROUP BY 1, 2'); +SELECT * FROM check_estimated_rows('SELECT a, b FROM ONLY stxdinh WHERE a = 0 AND b = 0'); +DROP TABLE stxdinh, stxdinh1, stxdinh2; +*/ -- YB +-- Ensure inherited stats ARE applied to inherited query in partitioned table +CREATE TABLE stxdinp(i int, a int, b int) PARTITION BY RANGE (i); +CREATE TABLE stxdinp1 PARTITION OF stxdinp FOR VALUES FROM (1) TO (100); +INSERT INTO stxdinp SELECT 1, a/100, a/100 FROM generate_series(1, 999) a; +CREATE STATISTICS stxdinp ON (a + 1), a, b FROM stxdinp; +ANALYZE stxdinp; -- partitions are processed recursively -- YB: just use ANALYZE because VACUUM ANALYZE is no-op +SELECT 1 FROM pg_statistic_ext WHERE stxrelid = 'stxdinp'::regclass; + ?column? +---------- + 1 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinp GROUP BY 1, 2'); + estimated | actual +-----------+-------- + 10 | 10 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT a + 1, b FROM ONLY stxdinp GROUP BY 1, 2'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +DROP TABLE stxdinp; +-- basic test for statistics on expressions +CREATE TABLE ab1 (a INTEGER, b INTEGER, c TIMESTAMP, d TIMESTAMPTZ); +-- expression stats may be built on a single expression column +CREATE STATISTICS ab1_exprstat_1 ON (a+b) FROM ab1; +-- with a single expression, we only enable expression statistics +CREATE STATISTICS ab1_exprstat_2 ON (a+b) FROM ab1; +SELECT stxkind FROM pg_statistic_ext WHERE stxname = 'ab1_exprstat_2'; + stxkind +--------- + {e} +(1 row) + +-- adding anything to the expression builds all statistics kinds +CREATE STATISTICS ab1_exprstat_3 ON (a+b), a FROM ab1; +SELECT stxkind FROM pg_statistic_ext WHERE stxname = 'ab1_exprstat_3'; + stxkind +----------- + {d,f,m,e} +(1 row) + +-- date_trunc on timestamptz is not immutable, but that should not matter +CREATE STATISTICS ab1_exprstat_4 ON date_trunc('day', d) FROM ab1; +-- date_trunc on timestamp is immutable +CREATE STATISTICS ab1_exprstat_5 ON date_trunc('day', c) FROM ab1; +-- check use of a boolean-returning expression +CREATE STATISTICS ab1_exprstat_6 ON + (case a when 1 then true else false end), b FROM ab1; +-- insert some data and run analyze, to test that these cases build properly +INSERT INTO ab1 +SELECT x / 10, x / 3, + '2020-10-01'::timestamp + x * interval '1 day', + '2020-10-01'::timestamptz + x * interval '1 day' +FROM generate_series(1, 100) x; +ANALYZE ab1; +-- apply some stats +SELECT * FROM check_estimated_rows('SELECT * FROM ab1 WHERE (case a when 1 then true else false end) AND b=2'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +DROP TABLE ab1; +-- Verify supported object types for extended statistics +CREATE schema tststats; +CREATE TABLE tststats.t (a int, b int, c text); +CREATE INDEX ti ON tststats.t (a, b); +CREATE SEQUENCE tststats.s; +CREATE VIEW tststats.v AS SELECT * FROM tststats.t; +CREATE MATERIALIZED VIEW tststats.mv AS SELECT * FROM tststats.t; +CREATE TYPE tststats.ty AS (a int, b int, c text); +CREATE FOREIGN DATA WRAPPER extstats_dummy_fdw; +CREATE SERVER extstats_dummy_srv FOREIGN DATA WRAPPER extstats_dummy_fdw; +CREATE FOREIGN TABLE tststats.f (a int, b int, c text) SERVER extstats_dummy_srv; +CREATE TABLE tststats.pt (a int, b int, c text) PARTITION BY RANGE (a, b); +CREATE TABLE tststats.pt1 PARTITION OF tststats.pt FOR VALUES FROM (-10, -10) TO (10, 10); +CREATE STATISTICS tststats.s1 ON a, b FROM tststats.t; +CREATE STATISTICS tststats.s2 ON a, b FROM tststats.ti; +ERROR: cannot define statistics for relation "ti" +DETAIL: This operation is not supported for indexes. +CREATE STATISTICS tststats.s3 ON a, b FROM tststats.s; +ERROR: cannot define statistics for relation "s" +DETAIL: This operation is not supported for sequences. +CREATE STATISTICS tststats.s4 ON a, b FROM tststats.v; +ERROR: cannot define statistics for relation "v" +DETAIL: This operation is not supported for views. +CREATE STATISTICS tststats.s5 ON a, b FROM tststats.mv; +CREATE STATISTICS tststats.s6 ON a, b FROM tststats.ty; +ERROR: cannot define statistics for relation "ty" +DETAIL: This operation is not supported for composite types. +CREATE STATISTICS tststats.s7 ON a, b FROM tststats.f; +CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt; +CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1; +/* YB: uncomment when text value TOASTing is supported (#2003) +DO $$ +DECLARE + relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass; +BEGIN + EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname; +EXCEPTION WHEN wrong_object_type THEN + RAISE NOTICE 'stats on toast table not created'; +END; +$$; +*/ -- YB +DROP SCHEMA tststats CASCADE; +NOTICE: drop cascades to 7 other objects +DETAIL: drop cascades to foreign table tststats.f +drop cascades to materialized view tststats.mv +drop cascades to sequence tststats.s +drop cascades to table tststats.pt +drop cascades to table tststats.t +drop cascades to type tststats.ty +drop cascades to view tststats.v +DROP FOREIGN DATA WRAPPER extstats_dummy_fdw CASCADE; +NOTICE: drop cascades to server extstats_dummy_srv +-- n-distinct tests +CREATE TABLE ndistinct ( + filler1 TEXT, + filler2 NUMERIC, + a INT, + b INT, + filler3 DATE, + c INT, + d INT +) +WITH (autovacuum_enabled = off); +WARNING: storage parameter autovacuum_enabled is unsupported, ignoring +-- over-estimates when using only per-column statistics +INSERT INTO ndistinct (a, b, c, filler1) + SELECT i/100, i/100, i/100, cash_words((i/100)::money) + FROM generate_series(1,1000) s(i); +ANALYZE ndistinct; +-- Group Aggregate, due to over-estimate of the number of groups +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 100 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c'); + estimated | actual +-----------+-------- + 100 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c'); + estimated | actual +-----------+-------- + 100 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c, d'); + estimated | actual +-----------+-------- + 200 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c, d'); + estimated | actual +-----------+-------- + 200 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (a+1)'); + estimated | actual +-----------+-------- + 100 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + estimated | actual +-----------+-------- + 100 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + estimated | actual +-----------+-------- + 100 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + estimated | actual +-----------+-------- + 100 | 11 +(1 row) + +-- correct command +CREATE STATISTICS s10 ON a, b, c FROM ndistinct; +ANALYZE ndistinct; +SELECT s.stxkind, d.stxdndistinct + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxrelid = 'ndistinct'::regclass + AND d.stxoid = s.oid; + stxkind | stxdndistinct +---------+----------------------------------------------------- + {d,f,m} | {"3, 4": 11, "3, 6": 11, "4, 6": 11, "3, 4, 6": 11} +(1 row) + +-- minor improvement, make sure the ctid does not break the matching +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY ctid, a, b'); +ERROR: system column "ctid" is not supported yet +CONTEXT: SQL statement "explain analyze SELECT COUNT(*) FROM ndistinct GROUP BY ctid, a, b" +PL/pgSQL function check_estimated_rows(text) line 7 at FOR over EXECUTE statement +-- Hash Aggregate, thanks to estimates improved by the statistic +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 11 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c'); + estimated | actual +-----------+-------- + 11 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c'); + estimated | actual +-----------+-------- + 11 | 11 +(1 row) + +-- partial improvement (match on attributes) +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (a+1)'); + estimated | actual +-----------+-------- + 11 | 11 +(1 row) + +-- expressions - no improvement +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + estimated | actual +-----------+-------- + 11 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + estimated | actual +-----------+-------- + 11 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + estimated | actual +-----------+-------- + 11 | 11 +(1 row) + +-- last two plans keep using Group Aggregate, because 'd' is not covered +-- by the statistic and while it's NULL-only we assume 200 values for it +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c, d'); + estimated | actual +-----------+-------- + 200 | 11 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c, d'); + estimated | actual +-----------+-------- + 200 | 11 +(1 row) + +TRUNCATE TABLE ndistinct; +-- under-estimates when using only per-column statistics +INSERT INTO ndistinct (a, b, c, filler1) + SELECT mod(i,13), mod(i,17), mod(i,19), + cash_words(mod(i,23)::int::money) + FROM generate_series(1,1000) s(i); +ANALYZE ndistinct; +SELECT s.stxkind, d.stxdndistinct + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxrelid = 'ndistinct'::regclass + AND d.stxoid = s.oid; + stxkind | stxdndistinct +---------+---------------------------------------------------------- + {d,f,m} | {"3, 4": 221, "3, 6": 247, "4, 6": 323, "3, 4, 6": 1000} +(1 row) + +-- correct estimates +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 221 | 221 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c'); + estimated | actual +-----------+-------- + 1000 | 1000 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c, d'); + estimated | actual +-----------+-------- + 1000 | 1000 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c, d'); + estimated | actual +-----------+-------- + 323 | 323 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, d'); + estimated | actual +-----------+-------- + 200 | 13 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (a+1)'); + estimated | actual +-----------+-------- + 221 | 221 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + estimated | actual +-----------+-------- + 221 | 221 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + estimated | actual +-----------+-------- + 1000 | 1000 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + estimated | actual +-----------+-------- + 221 | 221 +(1 row) + +DROP STATISTICS s10; +SELECT s.stxkind, d.stxdndistinct + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxrelid = 'ndistinct'::regclass + AND d.stxoid = s.oid; + stxkind | stxdndistinct +---------+--------------- +(0 rows) + +-- dropping the statistics results in under-estimates +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 100 | 221 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c'); + estimated | actual +-----------+-------- + 100 | 1000 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c, d'); + estimated | actual +-----------+-------- + 200 | 1000 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c, d'); + estimated | actual +-----------+-------- + 200 | 323 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, d'); + estimated | actual +-----------+-------- + 200 | 13 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (a+1)'); + estimated | actual +-----------+-------- + 100 | 221 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + estimated | actual +-----------+-------- + 100 | 221 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + estimated | actual +-----------+-------- + 100 | 1000 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + estimated | actual +-----------+-------- + 100 | 221 +(1 row) + +-- ndistinct estimates with statistics on expressions +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + estimated | actual +-----------+-------- + 100 | 221 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + estimated | actual +-----------+-------- + 100 | 1000 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + estimated | actual +-----------+-------- + 100 | 221 +(1 row) + +CREATE STATISTICS s10 (ndistinct) ON (a+1), (b+100), (2*c) FROM ndistinct; +ANALYZE ndistinct; +SELECT s.stxkind, d.stxdndistinct + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxrelid = 'ndistinct'::regclass + AND d.stxoid = s.oid; + stxkind | stxdndistinct +---------+------------------------------------------------------------------- + {d,e} | {"-1, -2": 221, "-1, -3": 247, "-2, -3": 323, "-1, -2, -3": 1000} +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + estimated | actual +-----------+-------- + 221 | 221 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + estimated | actual +-----------+-------- + 1000 | 1000 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + estimated | actual +-----------+-------- + 221 | 221 +(1 row) + +DROP STATISTICS s10; +-- a mix of attributes and expressions +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 100 | 221 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (2*c)'); + estimated | actual +-----------+-------- + 100 | 247 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (2*c)'); + estimated | actual +-----------+-------- + 100 | 1000 +(1 row) + +CREATE STATISTICS s10 (ndistinct) ON a, b, (2*c) FROM ndistinct; +ANALYZE ndistinct; +SELECT s.stxkind, d.stxdndistinct + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxrelid = 'ndistinct'::regclass + AND d.stxoid = s.oid; + stxkind | stxdndistinct +---------+------------------------------------------------------------- + {d,e} | {"3, 4": 221, "3, -1": 247, "4, -1": 323, "3, 4, -1": 1000} +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 221 | 221 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (2*c)'); + estimated | actual +-----------+-------- + 247 | 247 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (2*c)'); + estimated | actual +-----------+-------- + 1000 | 1000 +(1 row) + +DROP STATISTICS s10; +-- combination of multiple ndistinct statistics, with/without expressions +TRUNCATE ndistinct; +-- two mostly independent groups of columns +INSERT INTO ndistinct (a, b, c, d) + SELECT mod(i,3), mod(i,9), mod(i,5), mod(i,20) + FROM generate_series(1,1000) s(i); +ANALYZE ndistinct; +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 27 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + estimated | actual +-----------+-------- + 27 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + estimated | actual +-----------+-------- + 27 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + estimated | actual +-----------+-------- + 27 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + estimated | actual +-----------+-------- + 100 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + estimated | actual +-----------+-------- + 100 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + estimated | actual +-----------+-------- + 100 | 180 +(1 row) + +-- basic statistics on both attributes (no expressions) +CREATE STATISTICS s11 (ndistinct) ON a, b FROM ndistinct; +CREATE STATISTICS s12 (ndistinct) ON c, d FROM ndistinct; +ANALYZE ndistinct; +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + estimated | actual +-----------+-------- + 45 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + estimated | actual +-----------+-------- + 45 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + estimated | actual +-----------+-------- + 100 | 180 +(1 row) + +-- replace the second statistics by statistics on expressions +DROP STATISTICS s12; +CREATE STATISTICS s12 (ndistinct) ON (c * 10), (d - 1) FROM ndistinct; +ANALYZE ndistinct; +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + estimated | actual +-----------+-------- + 45 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + estimated | actual +-----------+-------- + 45 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + estimated | actual +-----------+-------- + 100 | 180 +(1 row) + +-- replace the second statistics by statistics on both attributes and expressions +DROP STATISTICS s12; +CREATE STATISTICS s12 (ndistinct) ON c, d, (c * 10), (d - 1) FROM ndistinct; +ANALYZE ndistinct; +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + estimated | actual +-----------+-------- + 45 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + estimated | actual +-----------+-------- + 45 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + estimated | actual +-----------+-------- + 100 | 180 +(1 row) + +-- replace the other statistics by statistics on both attributes and expressions +DROP STATISTICS s11; +CREATE STATISTICS s11 (ndistinct) ON a, b, (a*5), (b+1) FROM ndistinct; +ANALYZE ndistinct; +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + estimated | actual +-----------+-------- + 45 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + estimated | actual +-----------+-------- + 45 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + estimated | actual +-----------+-------- + 100 | 180 +(1 row) + +-- replace statistics by somewhat overlapping ones (this expected to get worse estimate +-- because the first statistics shall be applied to 3 columns, and the second one can't +-- be really applied) +DROP STATISTICS s11; +DROP STATISTICS s12; +CREATE STATISTICS s11 (ndistinct) ON a, b, (a*5), (b+1) FROM ndistinct; +CREATE STATISTICS s12 (ndistinct) ON a, (b+1), (c * 10) FROM ndistinct; +ANALYZE ndistinct; +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + estimated | actual +-----------+-------- + 9 | 9 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + estimated | actual +-----------+-------- + 45 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + estimated | actual +-----------+-------- + 100 | 45 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + estimated | actual +-----------+-------- + 100 | 180 +(1 row) + +DROP STATISTICS s11; +DROP STATISTICS s12; +-- functional dependencies tests +CREATE TABLE functional_dependencies ( + filler1 TEXT, + filler2 NUMERIC, + a INT, + b TEXT, + filler3 DATE, + c INT, + d TEXT +) +WITH (autovacuum_enabled = off); +WARNING: storage parameter autovacuum_enabled is unsupported, ignoring +CREATE INDEX fdeps_ab_idx ON functional_dependencies (a, b); +CREATE INDEX fdeps_abc_idx ON functional_dependencies (a, b, c); +-- random data (no functional dependencies) +INSERT INTO functional_dependencies (a, b, c, filler1) + SELECT mod(i, 5), mod(i, 7), mod(i, 11), i FROM generate_series(1,1000) s(i); +ANALYZE functional_dependencies; +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'''); + estimated | actual +-----------+-------- + 29 | 29 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + estimated | actual +-----------+-------- + 3 | 3 +(1 row) + +-- create statistics +CREATE STATISTICS func_deps_stat (dependencies) ON a, b, c FROM functional_dependencies; +ANALYZE functional_dependencies; +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'''); + estimated | actual +-----------+-------- + 29 | 29 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + estimated | actual +-----------+-------- + 3 | 3 +(1 row) + +-- a => b, a => c, b => c +TRUNCATE functional_dependencies; +DROP STATISTICS func_deps_stat; +-- now do the same thing, but with expressions +INSERT INTO functional_dependencies (a, b, c, filler1) + SELECT i, i, i, i FROM generate_series(1,5000) s(i); +ANALYZE functional_dependencies; +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1'); + estimated | actual +-----------+-------- + 1 | 35 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1 AND mod(c, 7) = 1'); + estimated | actual +-----------+-------- + 1 | 5 +(1 row) + +-- create statistics +CREATE STATISTICS func_deps_stat (dependencies) ON (mod(a,11)), (mod(b::int, 13)), (mod(c, 7)) FROM functional_dependencies; +ANALYZE functional_dependencies; +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1'); + estimated | actual +-----------+-------- + 35 | 35 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1 AND mod(c, 7) = 1'); + estimated | actual +-----------+-------- + 5 | 5 +(1 row) + +-- a => b, a => c, b => c +TRUNCATE functional_dependencies; +DROP STATISTICS func_deps_stat; +INSERT INTO functional_dependencies (a, b, c, filler1) + SELECT mod(i,100), mod(i,50), mod(i,25), i FROM generate_series(1,5000) s(i); +ANALYZE functional_dependencies; +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'''); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +-- IN +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ''1'''); + estimated | actual +-----------+-------- + 2 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b IN (''1'', ''2'')'); + estimated | actual +-----------+-------- + 4 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b IN (''1'', ''2'')'); + estimated | actual +-----------+-------- + 8 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b = ''1'''); + estimated | actual +-----------+-------- + 4 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c = 1'); + estimated | actual +-----------+-------- + 1 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c IN (1)'); + estimated | actual +-----------+-------- + 1 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); + estimated | actual +-----------+-------- + 3 | 400 +(1 row) + +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); + estimated | actual +-----------+-------- + 2 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND (b = ''1'' OR b = ''2'')'); + estimated | actual +-----------+-------- + 4 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 2 OR a = 51 OR a = 52) AND (b = ''1'' OR b = ''2'')'); + estimated | actual +-----------+-------- + 8 | 200 +(1 row) + +-- OR clauses referencing different attributes +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR b = ''1'') AND b = ''1'''); + estimated | actual +-----------+-------- + 3 | 100 +(1 row) + +-- ANY +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ''1'''); + estimated | actual +-----------+-------- + 2 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 4 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 8 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = 1'); + estimated | actual +-----------+-------- + 1 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = ANY (ARRAY[1])'); + estimated | actual +-----------+-------- + 1 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); + estimated | actual +-----------+-------- + 3 | 400 +(1 row) + +-- ANY with inequalities should not benefit from functional dependencies +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1'''); + estimated | actual +-----------+-------- + 2472 | 2400 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a >= ANY (ARRAY[1, 51]) AND b <= ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1441 | 1250 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a <= ANY (ARRAY[1, 2, 51, 52]) AND b >= ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 3909 | 2550 +(1 row) + +-- ALL (should not benefit from functional dependencies) +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ALL (ARRAY[''1''])'); + estimated | actual +-----------+-------- + 2 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ALL (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b = ALL (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +-- create statistics +CREATE STATISTICS func_deps_stat (dependencies) ON a, b, c FROM functional_dependencies; +ANALYZE functional_dependencies; +-- print the detected dependencies +SELECT dependencies FROM pg_stats_ext WHERE statistics_name = 'func_deps_stat'; + dependencies +------------------------------------------------------------------------------------------------------------ + {"3 => 4": 1.000000, "3 => 6": 1.000000, "4 => 6": 1.000000, "3, 4 => 6": 1.000000, "3, 6 => 4": 1.000000} +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'''); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +-- IN +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ''1'''); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b IN (''1'', ''2'')'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b IN (''1'', ''2'')'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b = ''1'''); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c = 1'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c IN (1)'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); + estimated | actual +-----------+-------- + 400 | 400 +(1 row) + +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); + estimated | actual +-----------+-------- + 99 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND (b = ''1'' OR b = ''2'')'); + estimated | actual +-----------+-------- + 99 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 2 OR a = 51 OR a = 52) AND (b = ''1'' OR b = ''2'')'); + estimated | actual +-----------+-------- + 197 | 200 +(1 row) + +-- OR clauses referencing different attributes are incompatible +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR b = ''1'') AND b = ''1'''); + estimated | actual +-----------+-------- + 3 | 100 +(1 row) + +-- ANY +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ''1'''); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = 1'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = ANY (ARRAY[1])'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); + estimated | actual +-----------+-------- + 400 | 400 +(1 row) + +-- ANY with inequalities should not benefit from functional dependencies +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1'''); + estimated | actual +-----------+-------- + 2472 | 2400 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a >= ANY (ARRAY[1, 51]) AND b <= ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1441 | 1250 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a <= ANY (ARRAY[1, 2, 51, 52]) AND b >= ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 3909 | 2550 +(1 row) + +-- ALL (should not benefit from functional dependencies) +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ALL (ARRAY[''1''])'); + estimated | actual +-----------+-------- + 2 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ALL (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b = ALL (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +-- changing the type of column c causes all its stats to be dropped, reverting +-- to default estimates without any statistics, i.e. 0.5% selectivity for each +-- condition +ALTER TABLE functional_dependencies ALTER COLUMN c TYPE numeric; +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +ANALYZE functional_dependencies; +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +DROP STATISTICS func_deps_stat; +-- now try functional dependencies with expressions +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = 2 AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = 2 AND upper(b) = ''1'' AND (c + 1) = 2'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +-- IN +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) IN (''1'', ''2'')'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) IN (''1'', ''2'')'); + estimated | actual +-----------+-------- + 1 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 52, 102, 152) AND upper(b) IN (''1'', ''26'') AND (c + 1) = 2'); + estimated | actual +-----------+-------- + 1 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 52, 102, 152) AND upper(b) IN (''1'', ''26'') AND (c + 1) IN (2)'); + estimated | actual +-----------+-------- + 1 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 52, 54, 102, 104, 152, 154) AND upper(b) IN (''1'', ''2'', ''26'', ''27'') AND (c + 1) IN (2, 3)'); + estimated | actual +-----------+-------- + 1 | 400 +(1 row) + +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 102) AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 102) AND (upper(b) = ''1'' OR upper(b) = ''2'')'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 4 OR (a * 2) = 102 OR (a * 2) = 104) AND (upper(b) = ''1'' OR upper(b) = ''2'')'); + estimated | actual +-----------+-------- + 1 | 200 +(1 row) + +-- OR clauses referencing different attributes +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR upper(b) = ''1'') AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +-- ANY +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 102]) AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 102]) AND upper(b) = ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 4, 102, 104]) AND upper(b) = ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 52, 102, 152]) AND upper(b) = ANY (ARRAY[''1'', ''26'']) AND (c + 1) = 2'); + estimated | actual +-----------+-------- + 1 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 52, 102, 152]) AND upper(b) = ANY (ARRAY[''1'', ''26'']) AND (c + 1) = ANY (ARRAY[2])'); + estimated | actual +-----------+-------- + 1 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 4, 52, 54, 102, 104, 152, 154]) AND upper(b) = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND (c + 1) = ANY (ARRAY[2, 3])'); + estimated | actual +-----------+-------- + 1 | 400 +(1 row) + +-- ANY with inequalities should not benefit from functional dependencies +-- the estimates however improve thanks to having expression statistics +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) < ANY (ARRAY[2, 102]) AND upper(b) > ''1'''); + estimated | actual +-----------+-------- + 926 | 2400 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) >= ANY (ARRAY[2, 102]) AND upper(b) <= ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1543 | 1250 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) <= ANY (ARRAY[2, 4, 102, 104]) AND upper(b) >= ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 2229 | 2550 +(1 row) + +-- ALL (should not benefit from functional dependencies) +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ALL (ARRAY[''1''])'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ALL (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) = ALL (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +-- create statistics on expressions +CREATE STATISTICS func_deps_stat (dependencies) ON (a * 2), upper(b), (c + 1) FROM functional_dependencies; +ANALYZE functional_dependencies; +-- print the detected dependencies +SELECT dependencies FROM pg_stats_ext WHERE statistics_name = 'func_deps_stat'; + dependencies +------------------------------------------------------------------------------------------------------------------------ + {"-1 => -2": 1.000000, "-1 => -3": 1.000000, "-2 => -3": 1.000000, "-1, -2 => -3": 1.000000, "-1, -3 => -2": 1.000000} +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = 2 AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = 2 AND upper(b) = ''1'' AND (c + 1) = 2'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +-- IN +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) IN (''1'', ''2'')'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) IN (''1'', ''2'')'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 52, 102, 152) AND upper(b) IN (''1'', ''26'') AND (c + 1) = 2'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 52, 102, 152) AND upper(b) IN (''1'', ''26'') AND (c + 1) IN (2)'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 52, 54, 102, 104, 152, 154) AND upper(b) IN (''1'', ''2'', ''26'', ''27'') AND (c + 1) IN (2, 3)'); + estimated | actual +-----------+-------- + 400 | 400 +(1 row) + +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 102) AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 99 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 102) AND (upper(b) = ''1'' OR upper(b) = ''2'')'); + estimated | actual +-----------+-------- + 99 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 4 OR (a * 2) = 102 OR (a * 2) = 104) AND (upper(b) = ''1'' OR upper(b) = ''2'')'); + estimated | actual +-----------+-------- + 197 | 200 +(1 row) + +-- OR clauses referencing different attributes +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR upper(b) = ''1'') AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 3 | 100 +(1 row) + +-- ANY +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 102]) AND upper(b) = ''1'''); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 102]) AND upper(b) = ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 4, 102, 104]) AND upper(b) = ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 52, 102, 152]) AND upper(b) = ANY (ARRAY[''1'', ''26'']) AND (c + 1) = 2'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 52, 102, 152]) AND upper(b) = ANY (ARRAY[''1'', ''26'']) AND (c + 1) = ANY (ARRAY[2])'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 4, 52, 54, 102, 104, 152, 154]) AND upper(b) = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND (c + 1) = ANY (ARRAY[2, 3])'); + estimated | actual +-----------+-------- + 400 | 400 +(1 row) + +-- ANY with inequalities should not benefit from functional dependencies +-- the estimates however improve thanks to having expression statistics +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) < ANY (ARRAY[2, 102]) AND upper(b) > ''1'''); + estimated | actual +-----------+-------- + 2472 | 2400 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) >= ANY (ARRAY[2, 102]) AND upper(b) <= ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1441 | 1250 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) <= ANY (ARRAY[2, 4, 102, 104]) AND upper(b) >= ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 3909 | 2550 +(1 row) + +-- ALL (should not benefit from functional dependencies) +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ALL (ARRAY[''1''])'); + estimated | actual +-----------+-------- + 2 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ALL (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) = ALL (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +-- check the ability to use multiple functional dependencies +CREATE TABLE functional_dependencies_multi ( + a INTEGER, + b INTEGER, + c INTEGER, + d INTEGER +) +WITH (autovacuum_enabled = off); +WARNING: storage parameter autovacuum_enabled is unsupported, ignoring +INSERT INTO functional_dependencies_multi (a, b, c, d) + SELECT + mod(i,7), + mod(i,7), + mod(i,11), + mod(i,11) + FROM generate_series(1,5000) s(i); +ANALYZE functional_dependencies_multi; +-- estimates without any functional dependencies +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE a = 0 AND b = 0'); + estimated | actual +-----------+-------- + 102 | 714 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE 0 = a AND 0 = b'); + estimated | actual +-----------+-------- + 102 | 714 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE c = 0 AND d = 0'); + estimated | actual +-----------+-------- + 41 | 454 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE a = 0 AND b = 0 AND c = 0 AND d = 0'); + estimated | actual +-----------+-------- + 1 | 64 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE 0 = a AND b = 0 AND 0 = c AND d = 0'); + estimated | actual +-----------+-------- + 1 | 64 +(1 row) + +-- create separate functional dependencies +CREATE STATISTICS functional_dependencies_multi_1 (dependencies) ON a, b FROM functional_dependencies_multi; +CREATE STATISTICS functional_dependencies_multi_2 (dependencies) ON c, d FROM functional_dependencies_multi; +ANALYZE functional_dependencies_multi; +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE a = 0 AND b = 0'); + estimated | actual +-----------+-------- + 714 | 714 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE 0 = a AND 0 = b'); + estimated | actual +-----------+-------- + 714 | 714 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE c = 0 AND d = 0'); + estimated | actual +-----------+-------- + 454 | 454 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE a = 0 AND b = 0 AND c = 0 AND d = 0'); + estimated | actual +-----------+-------- + 65 | 64 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE 0 = a AND b = 0 AND 0 = c AND d = 0'); + estimated | actual +-----------+-------- + 65 | 64 +(1 row) + +DROP TABLE functional_dependencies_multi; +-- MCV lists +CREATE TABLE mcv_lists ( + filler1 TEXT, + filler2 NUMERIC, + a INT, + b VARCHAR, + filler3 DATE, + c INT, + d TEXT, + ia INT[] +) +WITH (autovacuum_enabled = off); +WARNING: storage parameter autovacuum_enabled is unsupported, ignoring +-- random data (no MCV list) +INSERT INTO mcv_lists (a, b, c, filler1) + SELECT mod(i,37), mod(i,41), mod(i,43), mod(i,47) FROM generate_series(1,5000) s(i); +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + estimated | actual +-----------+-------- + 3 | 4 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'' AND c = 1'); + estimated | actual +-----------+-------- + 1 | 1 +(1 row) + +-- create statistics +CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists; +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + estimated | actual +-----------+-------- + 3 | 4 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'' AND c = 1'); + estimated | actual +-----------+-------- + 1 | 1 +(1 row) + +TRUNCATE mcv_lists; +DROP STATISTICS mcv_lists_stats; +-- random data (no MCV list), but with expression +INSERT INTO mcv_lists (a, b, c, filler1) + SELECT i, i, i, i FROM generate_series(1,1000) s(i); +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 AND mod(b::int,11) = 1'); + estimated | actual +-----------+-------- + 1 | 13 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 AND mod(b::int,11) = 1 AND mod(c,13) = 1'); + estimated | actual +-----------+-------- + 1 | 1 +(1 row) + +-- create statistics +CREATE STATISTICS mcv_lists_stats (mcv) ON (mod(a,7)), (mod(b::int,11)), (mod(c,13)) FROM mcv_lists; +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 AND mod(b::int,11) = 1'); + estimated | actual +-----------+-------- + 13 | 13 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 AND mod(b::int,11) = 1 AND mod(c,13) = 1'); + estimated | actual +-----------+-------- + 1 | 1 +(1 row) + +-- 100 distinct combinations, all in the MCV list +TRUNCATE mcv_lists; +DROP STATISTICS mcv_lists_stats; +INSERT INTO mcv_lists (a, b, c, ia, filler1) + SELECT mod(i,100), mod(i,50), mod(i,25), array[mod(i,25)], i + FROM generate_series(1,5000) s(i); +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = a AND ''1'' = b'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 1 AND b < ''1'''); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > a AND ''1'' > b'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= 0 AND b <= ''0'''); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 0 >= a AND ''0'' >= b'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'' AND c = 1'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 5 AND b < ''1'' AND c < 5'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 5 AND ''1'' > b AND 5 > c'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= 4 AND b <= ''0'' AND c <= 4'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 4 >= a AND ''0'' >= b AND 4 >= c'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1'); + estimated | actual +-----------+-------- + 343 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL'); + estimated | actual +-----------+-------- + 343 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52) AND b IN ( ''1'', ''2'')'); + estimated | actual +-----------+-------- + 8 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52, NULL) AND b IN ( ''1'', ''2'', NULL)'); + estimated | actual +-----------+-------- + 8 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 8 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[NULL, 1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2'', NULL])'); + estimated | actual +-----------+-------- + 8 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, 2, 3]) AND b IN (''1'', ''2'', ''3'')'); + estimated | actual +-----------+-------- + 26 | 150 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, NULL, 2, 3]) AND b IN (''1'', ''2'', NULL, ''3'')'); + estimated | actual +-----------+-------- + 26 | 150 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3])'); + estimated | actual +-----------+-------- + 10 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3, NULL])'); + estimated | actual +-----------+-------- + 10 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', ''3'') AND c > ANY (ARRAY[1, 2, 3])'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', NULL, ''3'') AND c > ANY (ARRAY[1, 2, NULL, 3])'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)'); + estimated | actual +-----------+-------- + 4 | 50 +(1 row) + +-- create statistics +CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c, ia FROM mcv_lists; +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = a AND ''1'' = b'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 1 AND b < ''1'''); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > a AND ''1'' > b'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= 0 AND b <= ''0'''); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 0 >= a AND ''0'' >= b'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'' AND c = 1'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 5 AND b < ''1'' AND c < 5'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 5 AND ''1'' > b AND 5 > c'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= 4 AND b <= ''0'' AND c <= 4'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 4 >= a AND ''0'' >= b AND 4 >= c'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52) AND b IN ( ''1'', ''2'')'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52, NULL) AND b IN ( ''1'', ''2'', NULL)'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[NULL, 1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2'', NULL])'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, 2, 3]) AND b IN (''1'', ''2'', ''3'')'); + estimated | actual +-----------+-------- + 150 | 150 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, NULL, 2, 3]) AND b IN (''1'', ''2'', NULL, ''3'')'); + estimated | actual +-----------+-------- + 150 | 150 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3])'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3, NULL])'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', ''3'') AND c > ANY (ARRAY[1, 2, 3])'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', NULL, ''3'') AND c > ANY (ARRAY[1, 2, NULL, 3])'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)'); + estimated | actual +-----------+-------- + 4 | 50 +(1 row) + +-- check change of unrelated column type does not reset the MCV statistics +ALTER TABLE mcv_lists ALTER COLUMN d TYPE VARCHAR(64); +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +SELECT d.stxdmcv IS NOT NULL + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxname = 'mcv_lists_stats' + AND d.stxoid = s.oid; + ?column? +---------- + t +(1 row) + +-- check change of column type resets the MCV statistics +ALTER TABLE mcv_lists ALTER COLUMN c TYPE numeric; +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +-- 100 distinct combinations, all in the MCV list, but with expressions +TRUNCATE mcv_lists; +DROP STATISTICS mcv_lists_stats; +INSERT INTO mcv_lists (a, b, c, filler1) + SELECT i, i, i, i FROM generate_series(1,1000) s(i); +ANALYZE mcv_lists; +-- without any stats on the expressions, we have to use default selectivities, which +-- is why the estimates here are different from the pre-computed case above +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,20) AND 1 = mod(b::int,10)'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < 1 AND mod(b::int,10) < 1'); + estimated | actual +-----------+-------- + 111 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,20) AND 1 > mod(b::int,10)'); + estimated | actual +-----------+-------- + 111 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1 AND mod(c,5) = 1'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 OR mod(b::int,10) = 1 OR mod(c,25) = 1 OR d IS NOT NULL'); + estimated | actual +-----------+-------- + 15 | 120 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) IN (1, 2, 51, 52, NULL) AND mod(b::int,10) IN ( 1, 2, NULL)'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,10) = ANY (ARRAY[1, 2])'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,10) IN (1, 2, NULL, 3)'); + estimated | actual +-----------+-------- + 11 | 150 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < ALL (ARRAY[4, 5]) AND mod(b::int,10) IN (1, 2, 3) AND mod(c,5) > ANY (ARRAY[1, 2, 3])'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +-- create statistics with expressions only (we create three separate stats, in order not to build more complex extended stats) +CREATE STATISTICS mcv_lists_stats_1 ON (mod(a,20)) FROM mcv_lists; +CREATE STATISTICS mcv_lists_stats_2 ON (mod(b::int,10)) FROM mcv_lists; +CREATE STATISTICS mcv_lists_stats_3 ON (mod(c,5)) FROM mcv_lists; +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1'); + estimated | actual +-----------+-------- + 5 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,20) AND 1 = mod(b::int,10)'); + estimated | actual +-----------+-------- + 5 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < 1 AND mod(b::int,10) < 1'); + estimated | actual +-----------+-------- + 5 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,20) AND 1 > mod(b::int,10)'); + estimated | actual +-----------+-------- + 5 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1 AND mod(c,5) = 1'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 OR mod(b::int,10) = 1 OR mod(c,25) = 1 OR d IS NOT NULL'); + estimated | actual +-----------+-------- + 149 | 120 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) IN (1, 2, 51, 52, NULL) AND mod(b::int,10) IN ( 1, 2, NULL)'); + estimated | actual +-----------+-------- + 20 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,10) = ANY (ARRAY[1, 2])'); + estimated | actual +-----------+-------- + 20 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,10) IN (1, 2, NULL, 3)'); + estimated | actual +-----------+-------- + 116 | 150 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < ALL (ARRAY[4, 5]) AND mod(b::int,10) IN (1, 2, 3) AND mod(c,5) > ANY (ARRAY[1, 2, 3])'); + estimated | actual +-----------+-------- + 12 | 100 +(1 row) + +DROP STATISTICS mcv_lists_stats_1; +DROP STATISTICS mcv_lists_stats_2; +DROP STATISTICS mcv_lists_stats_3; +-- create statistics with both MCV and expressions +CREATE STATISTICS mcv_lists_stats (mcv) ON (mod(a,20)), (mod(b::int,10)), (mod(c,5)) FROM mcv_lists; +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,20) AND 1 = mod(b::int,10)'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < 1 AND mod(b::int,10) < 1'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,20) AND 1 > mod(b::int,10)'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1 AND mod(c,5) = 1'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 OR mod(b::int,10) = 1 OR mod(c,25) = 1 OR d IS NOT NULL'); + estimated | actual +-----------+-------- + 105 | 120 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) IN (1, 2, 51, 52, NULL) AND mod(b::int,10) IN ( 1, 2, NULL)'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,10) = ANY (ARRAY[1, 2])'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,10) IN (1, 2, NULL, 3)'); + estimated | actual +-----------+-------- + 150 | 150 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < ALL (ARRAY[4, 5]) AND mod(b::int,10) IN (1, 2, 3) AND mod(c,5) > ANY (ARRAY[1, 2, 3])'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +-- we can't use the statistic for OR clauses that are not fully covered (missing 'd' attribute) +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 OR mod(b::int,10) = 1 OR mod(c,5) = 1 OR d IS NOT NULL'); + estimated | actual +-----------+-------- + 200 | 200 +(1 row) + +-- 100 distinct combinations with NULL values, all in the MCV list +TRUNCATE mcv_lists; +DROP STATISTICS mcv_lists_stats; +INSERT INTO mcv_lists (a, b, c, filler1) + SELECT + (CASE WHEN mod(i,100) = 1 THEN NULL ELSE mod(i,100) END), + (CASE WHEN mod(i,50) = 1 THEN NULL ELSE mod(i,50) END), + (CASE WHEN mod(i,25) = 1 THEN NULL ELSE mod(i,25) END), + i + FROM generate_series(1,5000) s(i); +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NULL'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NULL AND c IS NULL'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NOT NULL'); + estimated | actual +-----------+-------- + 49 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NOT NULL AND b IS NULL AND c IS NOT NULL'); + estimated | actual +-----------+-------- + 95 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (0, 1) AND b IN (''0'', ''1'')'); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +-- create statistics +CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists; +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NULL'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NULL AND c IS NULL'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NOT NULL'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NOT NULL AND b IS NULL AND c IS NOT NULL'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (0, 1) AND b IN (''0'', ''1'')'); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +-- test pg_mcv_list_items with a very simple (single item) MCV list +TRUNCATE mcv_lists; +INSERT INTO mcv_lists (a, b, c) SELECT 1, 2, 3 FROM generate_series(1,1000) s(i); +ANALYZE mcv_lists; +SELECT m.* + FROM pg_statistic_ext s, pg_statistic_ext_data d, + pg_mcv_list_items(d.stxdmcv) m + WHERE s.stxname = 'mcv_lists_stats' + AND d.stxoid = s.oid; + index | values | nulls | frequency | base_frequency +-------+---------+---------+-----------+---------------- + 0 | {1,2,3} | {f,f,f} | 1 | 1 +(1 row) + +-- 2 distinct combinations with NULL values, all in the MCV list +TRUNCATE mcv_lists; +DROP STATISTICS mcv_lists_stats; +INSERT INTO mcv_lists (a, b, c, d) + SELECT + NULL, -- always NULL + (CASE WHEN mod(i,2) = 0 THEN NULL ELSE 'x' END), + (CASE WHEN mod(i,2) = 0 THEN NULL ELSE 0 END), + (CASE WHEN mod(i,2) = 0 THEN NULL ELSE 'x' END) + FROM generate_series(1,5000) s(i); +ANALYZE mcv_lists; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE b = ''x'' OR d = ''x'''); + estimated | actual +-----------+-------- + 3750 | 2500 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''x'' OR d = ''x'''); + estimated | actual +-----------+-------- + 3750 | 2500 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND (b = ''x'' OR d = ''x'')'); + estimated | actual +-----------+-------- + 3750 | 2500 +(1 row) + +-- create statistics +CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, d FROM mcv_lists; +ANALYZE mcv_lists; +-- test pg_mcv_list_items with MCV list containing variable-length data and NULLs +SELECT m.* + FROM pg_statistic_ext s, pg_statistic_ext_data d, + pg_mcv_list_items(d.stxdmcv) m + WHERE s.stxname = 'mcv_lists_stats' + AND d.stxoid = s.oid; + index | values | nulls | frequency | base_frequency +-------+------------------+---------+-----------+---------------- + 0 | {NULL,x,x} | {t,f,f} | 0.5 | 0.25 + 1 | {NULL,NULL,NULL} | {t,t,t} | 0.5 | 0.25 +(2 rows) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE b = ''x'' OR d = ''x'''); + estimated | actual +-----------+-------- + 2500 | 2500 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''x'' OR d = ''x'''); + estimated | actual +-----------+-------- + 2500 | 2500 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND (b = ''x'' OR d = ''x'')'); + estimated | actual +-----------+-------- + 2500 | 2500 +(1 row) + +-- mcv with pass-by-ref fixlen types, e.g. uuid +CREATE TABLE mcv_lists_uuid ( + a UUID, + b UUID, + c UUID +) +WITH (autovacuum_enabled = off); +WARNING: storage parameter autovacuum_enabled is unsupported, ignoring +INSERT INTO mcv_lists_uuid (a, b, c) + SELECT + md5(mod(i,100)::text)::uuid, + md5(mod(i,50)::text)::uuid, + md5(mod(i,25)::text)::uuid + FROM generate_series(1,5000) s(i); +ANALYZE mcv_lists_uuid; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_uuid WHERE a = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND b = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'''); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_uuid WHERE a = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND b = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND c = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'''); + estimated | actual +-----------+-------- + 1 | 50 +(1 row) + +CREATE STATISTICS mcv_lists_uuid_stats (mcv) ON a, b, c + FROM mcv_lists_uuid; +ANALYZE mcv_lists_uuid; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_uuid WHERE a = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND b = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'''); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_uuid WHERE a = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND b = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND c = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'''); + estimated | actual +-----------+-------- + 50 | 50 +(1 row) + +DROP TABLE mcv_lists_uuid; +-- mcv with arrays +CREATE TABLE mcv_lists_arrays ( + a TEXT[], + b NUMERIC[], + c INT[] +) +WITH (autovacuum_enabled = off); +WARNING: storage parameter autovacuum_enabled is unsupported, ignoring +INSERT INTO mcv_lists_arrays (a, b, c) + SELECT + ARRAY[md5((i/100)::text), md5((i/100-1)::text), md5((i/100+1)::text)], + ARRAY[(i/100-1)::numeric/1000, (i/100)::numeric/1000, (i/100+1)::numeric/1000], + ARRAY[(i/100-1), i/100, (i/100+1)] + FROM generate_series(1,5000) s(i); +CREATE STATISTICS mcv_lists_arrays_stats (mcv) ON a, b, c + FROM mcv_lists_arrays; +ANALYZE mcv_lists_arrays; +-- mcv with bool +CREATE TABLE mcv_lists_bool ( + a BOOL, + b BOOL, + c BOOL +) +WITH (autovacuum_enabled = off); +WARNING: storage parameter autovacuum_enabled is unsupported, ignoring +INSERT INTO mcv_lists_bool (a, b, c) + SELECT + (mod(i,2) = 0), (mod(i,4) = 0), (mod(i,8) = 0) + FROM generate_series(1,10000) s(i); +ANALYZE mcv_lists_bool; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE a AND b AND c'); + estimated | actual +-----------+-------- + 156 | 1250 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND b AND c'); + estimated | actual +-----------+-------- + 156 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND NOT b AND c'); + estimated | actual +-----------+-------- + 469 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND b AND NOT c'); + estimated | actual +-----------+-------- + 1094 | 0 +(1 row) + +CREATE STATISTICS mcv_lists_bool_stats (mcv) ON a, b, c + FROM mcv_lists_bool; +ANALYZE mcv_lists_bool; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE a AND b AND c'); + estimated | actual +-----------+-------- + 1250 | 1250 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND b AND c'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND NOT b AND c'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND b AND NOT c'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +-- mcv covering just a small fraction of data +CREATE TABLE mcv_lists_partial ( + a INT, + b INT, + c INT +); +-- 10 frequent groups, each with 100 elements +INSERT INTO mcv_lists_partial (a, b, c) + SELECT + mod(i,10), + mod(i,10), + mod(i,10) + FROM generate_series(0,999) s(i); +-- 100 groups that will make it to the MCV list (includes the 10 frequent ones) +INSERT INTO mcv_lists_partial (a, b, c) + SELECT + i, + i, + i + FROM generate_series(0,99) s(i); +-- 4000 groups in total, most of which won't make it (just a single item) +INSERT INTO mcv_lists_partial (a, b, c) + SELECT + i, + i, + i + FROM generate_series(0,3999) s(i); +ANALYZE mcv_lists_partial; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 AND b = 0 AND c = 0'); + estimated | actual +-----------+-------- + 1 | 102 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 0'); + estimated | actual +-----------+-------- + 300 | 102 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 10 AND b = 10 AND c = 10'); + estimated | actual +-----------+-------- + 1 | 2 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 10 OR b = 10 OR c = 10'); + estimated | actual +-----------+-------- + 6 | 2 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 AND b = 0 AND c = 10'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 10'); + estimated | actual +-----------+-------- + 204 | 104 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0 AND c = 0) OR (a = 1 AND b = 1 AND c = 1) OR (a = 2 AND b = 2 AND c = 2)'); + estimated | actual +-----------+-------- + 1 | 306 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0) OR (a = 0 AND c = 0) OR (b = 0 AND c = 0)'); + estimated | actual +-----------+-------- + 6 | 102 +(1 row) + +CREATE STATISTICS mcv_lists_partial_stats (mcv) ON a, b, c + FROM mcv_lists_partial; +ANALYZE mcv_lists_partial; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 AND b = 0 AND c = 0'); + estimated | actual +-----------+-------- + 102 | 102 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 0'); + estimated | actual +-----------+-------- + 96 | 102 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 10 AND b = 10 AND c = 10'); + estimated | actual +-----------+-------- + 2 | 2 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 10 OR b = 10 OR c = 10'); + estimated | actual +-----------+-------- + 2 | 2 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 AND b = 0 AND c = 10'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 10'); + estimated | actual +-----------+-------- + 102 | 104 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0 AND c = 0) OR (a = 1 AND b = 1 AND c = 1) OR (a = 2 AND b = 2 AND c = 2)'); + estimated | actual +-----------+-------- + 306 | 306 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0) OR (a = 0 AND c = 0) OR (b = 0 AND c = 0)'); + estimated | actual +-----------+-------- + 108 | 102 +(1 row) + +DROP TABLE mcv_lists_partial; +-- check the ability to use multiple MCV lists +CREATE TABLE mcv_lists_multi ( + a INTEGER, + b INTEGER, + c INTEGER, + d INTEGER +) +WITH (autovacuum_enabled = off); +WARNING: storage parameter autovacuum_enabled is unsupported, ignoring +INSERT INTO mcv_lists_multi (a, b, c, d) + SELECT + mod(i,5), + mod(i,5), + mod(i,7), + mod(i,7) + FROM generate_series(1,5000) s(i); +ANALYZE mcv_lists_multi; +-- estimates without any mcv statistics +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 AND b = 0'); + estimated | actual +-----------+-------- + 200 | 1000 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE c = 0 AND d = 0'); + estimated | actual +-----------+-------- + 102 | 714 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE b = 0 AND c = 0'); + estimated | actual +-----------+-------- + 143 | 142 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE b = 0 OR c = 0'); + estimated | actual +-----------+-------- + 1571 | 1572 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 AND b = 0 AND c = 0 AND d = 0'); + estimated | actual +-----------+-------- + 4 | 142 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE (a = 0 AND b = 0) OR (c = 0 AND d = 0)'); + estimated | actual +-----------+-------- + 298 | 1572 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 OR b = 0 OR c = 0 OR d = 0'); + estimated | actual +-----------+-------- + 2649 | 1572 +(1 row) + +-- create separate MCV statistics +CREATE STATISTICS mcv_lists_multi_1 (mcv) ON a, b FROM mcv_lists_multi; +CREATE STATISTICS mcv_lists_multi_2 (mcv) ON c, d FROM mcv_lists_multi; +ANALYZE mcv_lists_multi; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 AND b = 0'); + estimated | actual +-----------+-------- + 1000 | 1000 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE c = 0 AND d = 0'); + estimated | actual +-----------+-------- + 714 | 714 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE b = 0 AND c = 0'); + estimated | actual +-----------+-------- + 143 | 142 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE b = 0 OR c = 0'); + estimated | actual +-----------+-------- + 1571 | 1572 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 AND b = 0 AND c = 0 AND d = 0'); + estimated | actual +-----------+-------- + 143 | 142 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE (a = 0 AND b = 0) OR (c = 0 AND d = 0)'); + estimated | actual +-----------+-------- + 1571 | 1572 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 OR b = 0 OR c = 0 OR d = 0'); + estimated | actual +-----------+-------- + 1571 | 1572 +(1 row) + +DROP TABLE mcv_lists_multi; +-- statistics on integer expressions +CREATE TABLE expr_stats (a int, b int, c int); +INSERT INTO expr_stats SELECT mod(i,10), mod(i,10), mod(i,10) FROM generate_series(1,1000) s(i); +ANALYZE expr_stats; +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (2*a) = 0 AND (3*b) = 0'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (a+b) = 0 AND (a-b) = 0'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +CREATE STATISTICS expr_stats_1 (mcv) ON (a+b), (a-b), (2*a), (3*b) FROM expr_stats; +ANALYZE expr_stats; +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (2*a) = 0 AND (3*b) = 0'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (a+b) = 0 AND (a-b) = 0'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +DROP STATISTICS expr_stats_1; +DROP TABLE expr_stats; +-- statistics on a mix columns and expressions +CREATE TABLE expr_stats (a int, b int, c int); +INSERT INTO expr_stats SELECT mod(i,10), mod(i,10), mod(i,10) FROM generate_series(1,1000) s(i); +ANALYZE expr_stats; +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (2*a) = 0 AND (3*b) = 0'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 3 AND b = 3 AND (a-b) = 0'); + estimated | actual +-----------+-------- + 1 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND b = 1 AND (a-b) = 0'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +CREATE STATISTICS expr_stats_1 (mcv) ON a, b, (2*a), (3*b), (a+b), (a-b) FROM expr_stats; +ANALYZE expr_stats; +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (2*a) = 0 AND (3*b) = 0'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 3 AND b = 3 AND (a-b) = 0'); + estimated | actual +-----------+-------- + 100 | 100 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND b = 1 AND (a-b) = 0'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +DROP TABLE expr_stats; +-- statistics on expressions with different data types +CREATE TABLE expr_stats (a int, b name, c text); +INSERT INTO expr_stats SELECT mod(i,10), md5(mod(i,10)::text), md5(mod(i,10)::text) FROM generate_series(1,1000) s(i); +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 +(1 row) + +CREATE STATISTICS expr_stats_1 (mcv) ON a, b, (b || c), (c || b) FROM expr_stats; +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 +(1 row) + +DROP TABLE expr_stats; +-- test handling of a mix of compatible and incompatible expressions +CREATE TABLE expr_stats_incompatible_test ( + c0 double precision, + c1 boolean NOT NULL +); +CREATE STATISTICS expr_stat_comp_1 ON c0, c1 FROM expr_stats_incompatible_test; +INSERT INTO expr_stats_incompatible_test VALUES (1234,false), (5678,true); +ANALYZE expr_stats_incompatible_test; +SELECT c0 FROM ONLY expr_stats_incompatible_test WHERE +( + upper('x') LIKE ('x'||('[0,1]'::int4range)) + AND + (c0 IN (0, 1) OR c1) +); + c0 +---- +(0 rows) + +DROP TABLE expr_stats_incompatible_test; +-- Permission tests. Users should not be able to see specific data values in +-- the extended statistics, if they lack permission to see those values in +-- the underlying table. +-- +-- Currently this is only relevant for MCV stats. +CREATE SCHEMA tststats; +CREATE TABLE tststats.priv_test_tbl ( + a int, + b int +); +INSERT INTO tststats.priv_test_tbl + SELECT mod(i,5), mod(i,10) FROM generate_series(1,100) s(i); +CREATE STATISTICS tststats.priv_test_stats (mcv) ON a, b + FROM tststats.priv_test_tbl; +ANALYZE tststats.priv_test_tbl; +-- Check printing info about extended statistics by \dX +create table stts_t1 (a int, b int); +create statistics stts_1 (ndistinct) on a, b from stts_t1; +create statistics stts_2 (ndistinct, dependencies) on a, b from stts_t1; +create statistics stts_3 (ndistinct, dependencies, mcv) on a, b from stts_t1; +create table stts_t2 (a int, b int, c int); +create statistics stts_4 on b, c from stts_t2; +create table stts_t3 (col1 int, col2 int, col3 int); +create statistics stts_hoge on col1, col2, col3 from stts_t3; +create schema stts_s1; +create schema stts_s2; +create statistics stts_s1.stts_foo on col1, col2 from stts_t3; +create statistics stts_s2.stts_yama (dependencies, mcv) on col1, col3 from stts_t3; +insert into stts_t1 select i,i from generate_series(1,100) i; +analyze stts_t1; +set search_path to public, stts_s1, stts_s2, tststats; +\dX + List of extended statistics + Schema | Name | Definition | Ndistinct | Dependencies | MCV +----------+------------------------+------------------------------------------------------------------+-----------+--------------+--------- + public | func_deps_stat | (a * 2), upper(b), (c + 1::numeric) FROM functional_dependencies | | defined | + public | mcv_lists_arrays_stats | a, b, c FROM mcv_lists_arrays | | | defined + public | mcv_lists_bool_stats | a, b, c FROM mcv_lists_bool | | | defined + public | mcv_lists_stats | a, b, d FROM mcv_lists | | | defined + public | stts_1 | a, b FROM stts_t1 | defined | | + public | stts_2 | a, b FROM stts_t1 | defined | defined | + public | stts_3 | a, b FROM stts_t1 | defined | defined | defined + public | stts_4 | b, c FROM stts_t2 | defined | defined | defined + public | stts_hoge | col1, col2, col3 FROM stts_t3 | defined | defined | defined + stts_s1 | stts_foo | col1, col2 FROM stts_t3 | defined | defined | defined + stts_s2 | stts_yama | col1, col3 FROM stts_t3 | | defined | defined + tststats | priv_test_stats | a, b FROM priv_test_tbl | | | defined +(12 rows) + +\dX stts_? + List of extended statistics + Schema | Name | Definition | Ndistinct | Dependencies | MCV +--------+--------+-------------------+-----------+--------------+--------- + public | stts_1 | a, b FROM stts_t1 | defined | | + public | stts_2 | a, b FROM stts_t1 | defined | defined | + public | stts_3 | a, b FROM stts_t1 | defined | defined | defined + public | stts_4 | b, c FROM stts_t2 | defined | defined | defined +(4 rows) + +\dX *stts_hoge + List of extended statistics + Schema | Name | Definition | Ndistinct | Dependencies | MCV +--------+-----------+-------------------------------+-----------+--------------+--------- + public | stts_hoge | col1, col2, col3 FROM stts_t3 | defined | defined | defined +(1 row) + +\dX+ + List of extended statistics + Schema | Name | Definition | Ndistinct | Dependencies | MCV +----------+------------------------+------------------------------------------------------------------+-----------+--------------+--------- + public | func_deps_stat | (a * 2), upper(b), (c + 1::numeric) FROM functional_dependencies | | defined | + public | mcv_lists_arrays_stats | a, b, c FROM mcv_lists_arrays | | | defined + public | mcv_lists_bool_stats | a, b, c FROM mcv_lists_bool | | | defined + public | mcv_lists_stats | a, b, d FROM mcv_lists | | | defined + public | stts_1 | a, b FROM stts_t1 | defined | | + public | stts_2 | a, b FROM stts_t1 | defined | defined | + public | stts_3 | a, b FROM stts_t1 | defined | defined | defined + public | stts_4 | b, c FROM stts_t2 | defined | defined | defined + public | stts_hoge | col1, col2, col3 FROM stts_t3 | defined | defined | defined + stts_s1 | stts_foo | col1, col2 FROM stts_t3 | defined | defined | defined + stts_s2 | stts_yama | col1, col3 FROM stts_t3 | | defined | defined + tststats | priv_test_stats | a, b FROM priv_test_tbl | | | defined +(12 rows) + +\dX+ stts_? + List of extended statistics + Schema | Name | Definition | Ndistinct | Dependencies | MCV +--------+--------+-------------------+-----------+--------------+--------- + public | stts_1 | a, b FROM stts_t1 | defined | | + public | stts_2 | a, b FROM stts_t1 | defined | defined | + public | stts_3 | a, b FROM stts_t1 | defined | defined | defined + public | stts_4 | b, c FROM stts_t2 | defined | defined | defined +(4 rows) + +\dX+ *stts_hoge + List of extended statistics + Schema | Name | Definition | Ndistinct | Dependencies | MCV +--------+-----------+-------------------------------+-----------+--------------+--------- + public | stts_hoge | col1, col2, col3 FROM stts_t3 | defined | defined | defined +(1 row) + +\dX+ stts_s2.stts_yama + List of extended statistics + Schema | Name | Definition | Ndistinct | Dependencies | MCV +---------+-----------+-------------------------+-----------+--------------+--------- + stts_s2 | stts_yama | col1, col3 FROM stts_t3 | | defined | defined +(1 row) + +set search_path to public, stts_s1; +\dX + List of extended statistics + Schema | Name | Definition | Ndistinct | Dependencies | MCV +---------+------------------------+------------------------------------------------------------------+-----------+--------------+--------- + public | func_deps_stat | (a * 2), upper(b), (c + 1::numeric) FROM functional_dependencies | | defined | + public | mcv_lists_arrays_stats | a, b, c FROM mcv_lists_arrays | | | defined + public | mcv_lists_bool_stats | a, b, c FROM mcv_lists_bool | | | defined + public | mcv_lists_stats | a, b, d FROM mcv_lists | | | defined + public | stts_1 | a, b FROM stts_t1 | defined | | + public | stts_2 | a, b FROM stts_t1 | defined | defined | + public | stts_3 | a, b FROM stts_t1 | defined | defined | defined + public | stts_4 | b, c FROM stts_t2 | defined | defined | defined + public | stts_hoge | col1, col2, col3 FROM stts_t3 | defined | defined | defined + stts_s1 | stts_foo | col1, col2 FROM stts_t3 | defined | defined | defined +(10 rows) + +create role regress_stats_ext nosuperuser; +set role regress_stats_ext; +\dX + List of extended statistics + Schema | Name | Definition | Ndistinct | Dependencies | MCV +--------+------------------------+------------------------------------------------------------------+-----------+--------------+--------- + public | func_deps_stat | (a * 2), upper(b), (c + 1::numeric) FROM functional_dependencies | | defined | + public | mcv_lists_arrays_stats | a, b, c FROM mcv_lists_arrays | | | defined + public | mcv_lists_bool_stats | a, b, c FROM mcv_lists_bool | | | defined + public | mcv_lists_stats | a, b, d FROM mcv_lists | | | defined + public | stts_1 | a, b FROM stts_t1 | defined | | + public | stts_2 | a, b FROM stts_t1 | defined | defined | + public | stts_3 | a, b FROM stts_t1 | defined | defined | defined + public | stts_4 | b, c FROM stts_t2 | defined | defined | defined + public | stts_hoge | col1, col2, col3 FROM stts_t3 | defined | defined | defined +(9 rows) + +reset role; +drop table stts_t1, stts_t2, stts_t3; +drop schema stts_s1, stts_s2 cascade; -- YB: fails because dropping multiple schemas is not supported (#880) +ERROR: DROP multiple objects not supported yet +LINE 1: drop schema stts_s1, stts_s2 cascade; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/880. React with thumbs up to raise its priority +drop schema stts_s1 cascade; -- YB: workaround for above +drop schema stts_s2 cascade; -- YB: workaround for above +drop user regress_stats_ext; +reset search_path; +-- User with no access +CREATE USER regress_stats_user1; +GRANT USAGE ON SCHEMA tststats TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +SELECT * FROM tststats.priv_test_tbl; -- Permission denied +ERROR: permission denied for table priv_test_tbl +-- Check individual columns if we don't have table privilege +SELECT * FROM tststats.priv_test_tbl + WHERE a = 1 and tststats.priv_test_tbl.* > (1, 1) is not null; +ERROR: permission denied for table priv_test_tbl +-- Attempt to gain access using a leaky operator +CREATE FUNCTION op_leak(int, int) RETURNS bool + AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END' + LANGUAGE plpgsql; +CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int, + restrict = scalarltsel); +SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied +ERROR: permission denied for table priv_test_tbl +DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied +ERROR: permission denied for table priv_test_tbl +-- Grant access via a security barrier view, but hide all data +RESET SESSION AUTHORIZATION; +CREATE VIEW tststats.priv_test_view WITH (security_barrier=true) + AS SELECT * FROM tststats.priv_test_tbl WHERE false; +GRANT SELECT, DELETE ON tststats.priv_test_view TO regress_stats_user1; +-- Should now have access via the view, but see nothing and leak nothing +SET SESSION AUTHORIZATION regress_stats_user1; +SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak + a | b +---+--- +(0 rows) + +DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak +-- Grant table access, but hide all data with RLS +RESET SESSION AUTHORIZATION; +ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY; +GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1; +-- Should now have direct table access, but see nothing and leak nothing +SET SESSION AUTHORIZATION regress_stats_user1; +SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak + a | b +---+--- +(0 rows) + +DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak +-- Tidy up +DROP OPERATOR <<< (int, int); +DROP FUNCTION op_leak(int, int); +RESET SESSION AUTHORIZATION; +DROP SCHEMA tststats CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table tststats.priv_test_tbl +drop cascades to view tststats.priv_test_view +DROP USER regress_stats_user1; diff --git a/src/test/regress/expected/yb.port.stats_import.out b/src/test/regress/expected/yb.port.stats_import.out new file mode 100644 index 0000000000000..ed71661a4e96a --- /dev/null +++ b/src/test/regress/expected/yb.port.stats_import.out @@ -0,0 +1,1327 @@ +CREATE SCHEMA stats_import; +CREATE TYPE stats_import.complex_type AS ( + a integer, + b real, + c text, + d date, + e jsonb); +CREATE TABLE stats_import.test( + id INTEGER PRIMARY KEY, + name text, + comp stats_import.complex_type, + arange int4range, + tags text[] +) WITH (autovacuum_enabled = false); +WARNING: storage parameter autovacuum_enabled is unsupported, ignoring +SELECT + pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relpages', 18::integer, + 'reltuples', 21::real, + 'relallvisible', 24::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +-- CREATE INDEX on a table with autovac disabled should not overwrite +-- stats +CREATE INDEX test_i ON stats_import.test(id); +SELECT relname, relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass +ORDER BY relname; + relname | relpages | reltuples | relallvisible +---------+----------+-----------+--------------- + test | 18 | 21 | 24 +(1 row) + +SELECT pg_clear_relation_stats('stats_import', 'test'); + pg_clear_relation_stats +------------------------- + +(1 row) + +-- +-- relstats tests +-- +-- error: schemaname missing +SELECT pg_catalog.pg_restore_relation_stats( + 'relname', 'test', + 'relpages', 17::integer); +ERROR: "schemaname" cannot be NULL +-- error: relname missing +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relpages', 17::integer); +ERROR: "relname" cannot be NULL +--- error: schemaname is wrong type +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 3.6::float, + 'relname', 'test', + 'relpages', 17::integer); +WARNING: argument "schemaname" has type "double precision", expected type "text" +ERROR: "schemaname" cannot be NULL +--- error: relname is wrong type +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 0::oid, + 'relpages', 17::integer); +WARNING: argument "relname" has type "oid", expected type "text" +ERROR: "relname" cannot be NULL +-- error: relation not found +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'nope', + 'relpages', 17::integer); +ERROR: relation "stats_import.nope" does not exist +-- error: odd number of variadic arguments cannot be pairs +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relallvisible'); +ERROR: variadic arguments must be name/value pairs +HINT: Provide an even number of variadic arguments that can be divided into pairs. +-- error: argument name is NULL +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + NULL, '17'::integer); +ERROR: name at variadic position 5 is NULL +-- starting stats +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test_i'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 0 | 0 | 0 +(1 row) + +-- regular indexes have special case locking rules +BEGIN; +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test_i', + 'relpages', 18::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +-- YB: DDLs run execute in internal transactions, that get auto committed. +SELECT mode FROM pg_locks +WHERE relation = 'stats_import.test'::regclass AND + pid = pg_backend_pid() AND granted; + mode +------ +(0 rows) + +-- YB: DDLs run execute in internal transactions, that get auto committed. +SELECT mode FROM pg_locks +WHERE relation = 'stats_import.test_i'::regclass AND + pid = pg_backend_pid() AND granted; + mode +------ +(0 rows) + +COMMIT; +-- relpages may be -1 for partitioned tables +CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i); +CREATE TABLE stats_import.part_child_1 + PARTITION OF stats_import.part_parent + FOR VALUES FROM (0) TO (10) + WITH (autovacuum_enabled = false); +WARNING: storage parameter autovacuum_enabled is unsupported, ignoring +CREATE INDEX part_parent_i ON stats_import.part_parent(i); +ANALYZE stats_import.part_parent; +SELECT relpages +FROM pg_class +WHERE oid = 'stats_import.part_parent'::regclass; + relpages +---------- + -1 +(1 row) + +-- +-- Partitioned indexes aren't analyzed but it is possible to set +-- stats. The locking rules are different from normal indexes due to +-- the rules for in-place updates: both the partitioned table and the +-- partitioned index are locked in ShareUpdateExclusive mode. +-- +BEGIN; +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'part_parent_i', + 'relpages', 2::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +-- YB: DDLs run execute in internal transactions, that get auto committed. +SELECT mode FROM pg_locks +WHERE relation = 'stats_import.part_parent'::regclass AND + pid = pg_backend_pid() AND granted; + mode +------ +(0 rows) + +-- YB: DDLs run execute in internal transactions, that get auto committed. +SELECT mode FROM pg_locks +WHERE relation = 'stats_import.part_parent_i'::regclass AND + pid = pg_backend_pid() AND granted; + mode +------ +(0 rows) + +COMMIT; +SELECT relpages +FROM pg_class +WHERE oid = 'stats_import.part_parent_i'::regclass; + relpages +---------- + 2 +(1 row) + +-- ok: set all relstats, with version, no bounds checking +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'version', 150000::integer, + 'relpages', '-17'::integer, + 'reltuples', 400::real, + 'relallvisible', 4::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + -17 | 400 | 4 +(1 row) + +-- ok: set just relpages, rest stay same +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relpages', '16'::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 16 | 400 | 4 +(1 row) + +-- ok: set just reltuples, rest stay same +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', '500'::real); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 16 | 500 | 4 +(1 row) + +-- ok: set just relallvisible, rest stay same +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relallvisible', 5::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 16 | 500 | 5 +(1 row) + +-- warn: bad relpages type, rest updated +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relpages', 'nope'::text, + 'reltuples', 400.0::real, + 'relallvisible', 4::integer); +WARNING: argument "relpages" has type "text", expected type "integer" + pg_restore_relation_stats +--------------------------- + f +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 16 | 400 | 4 +(1 row) + +-- unrecognized argument name, rest ok +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relpages', '171'::integer, + 'nope', 10::integer); +WARNING: unrecognized argument name: "nope" + pg_restore_relation_stats +--------------------------- + f +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 171 | 400 | 4 +(1 row) + +-- ok: clear stats +SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test'); + pg_clear_relation_stats +------------------------- + +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 0 | -1 | 0 +(1 row) + +-- invalid relkinds for statistics +CREATE SEQUENCE stats_import.testseq; +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'testseq'); +ERROR: cannot modify statistics for relation "testseq" +DETAIL: This operation is not supported for sequences. +SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq'); +ERROR: cannot modify statistics for relation "testseq" +DETAIL: This operation is not supported for sequences. +CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test; +SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview'); +ERROR: cannot modify statistics for relation "testview" +DETAIL: This operation is not supported for views. +-- +-- attribute stats +-- +-- error: schemaname missing +SELECT pg_catalog.pg_restore_attribute_stats( + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.1::real); +ERROR: "schemaname" cannot be NULL +-- error: schema does not exist +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'nope', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.1::real); +ERROR: schema "nope" does not exist +-- error: relname missing +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.1::real); +ERROR: "relname" cannot be NULL +-- error: relname does not exist +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'nope', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.1::real); +ERROR: relation "stats_import.nope" does not exist +-- error: relname null +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', NULL, + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.1::real); +ERROR: "relname" cannot be NULL +-- error: NULL attname +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', NULL, + 'inherited', false::boolean, + 'null_frac', 0.1::real); +ERROR: must specify either attname or attnum +-- error: attname doesn't exist +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'nope', + 'inherited', false::boolean, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); +ERROR: column "nope" of relation "test" does not exist +-- error: both attname and attnum +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'attnum', 1::smallint, + 'inherited', false::boolean, + 'null_frac', 0.1::real); +ERROR: cannot specify both attname and attnum +-- error: neither attname nor attnum +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'inherited', false::boolean, + 'null_frac', 0.1::real); +ERROR: must specify either attname or attnum +-- error: attribute is system column +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'xmin', + 'inherited', false::boolean, + 'null_frac', 0.1::real); +ERROR: cannot modify statistics on system column "xmin" +-- error: inherited null +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', NULL::boolean, + 'null_frac', 0.1::real); +ERROR: "inherited" cannot be NULL +-- ok: just the fixed values, with version, no stakinds +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.2::real, + 'avg_width', 5::integer, + 'n_distinct', 0.6::real); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- +-- ok: restore by attnum, we normally reserve this for +-- indexes, but there is no reason it shouldn't work +-- for any stat-having relation. +-- +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attnum', 1::smallint, + 'inherited', false::boolean, + 'null_frac', 0.4::real); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.4 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- warn: unrecognized argument name, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.2::real, + 'nope', 0.5::real); +WARNING: unrecognized argument name: "nope" + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- warn: mcv / mcf null mismatch part 1, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.21::real, + 'most_common_freqs', '{0.1,0.2,0.3}'::real[] + ); +WARNING: "most_common_vals" must be specified when "most_common_freqs" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.21 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- warn: mcv / mcf null mismatch part 2, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.21::real, + 'most_common_vals', '{1,2,3}'::text + ); +WARNING: "most_common_freqs" must be specified when "most_common_vals" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.21 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- warn: mcf type mismatch, mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.22::real, + 'most_common_vals', '{2,1,3}'::text, + 'most_common_freqs', '{0.2,0.1}'::double precision[] + ); +WARNING: argument "most_common_freqs" has type "double precision[]", expected type "real[]" +WARNING: "most_common_freqs" must be specified when "most_common_vals" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.22 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- warn: mcv cast failure, mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.23::real, + 'most_common_vals', '{2,four,3}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[] + ); +ERROR: invalid input syntax for type integer: "four" +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.22 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- ok: mcv+mcf +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'most_common_vals', '{2,1,3}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[] + ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.22 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | | +(1 row) + +-- warn: NULL in histogram array, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.24::real, + 'histogram_bounds', '{1,NULL,3,4}'::text + ); +WARNING: "histogram_bounds" array cannot contain NULL values + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.24 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | | +(1 row) + +-- ok: histogram_bounds +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'histogram_bounds', '{1,2,3,4}'::text + ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.24 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | | +(1 row) + +-- warn: elem_count_histogram null element, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'tags', + 'inherited', false::boolean, + 'null_frac', 0.25::real, + 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[] + ); +WARNING: "elem_count_histogram" array cannot contain NULL values + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | tags | f | 0.25 | 0 | 0 | | | | | | | | | | +(1 row) + +-- ok: elem_count_histogram +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'tags', + 'inherited', false::boolean, + 'null_frac', 0.26::real, + 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------ + stats_import | test | tags | f | 0.26 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | | +(1 row) + +-- warn: range stats on a scalar type, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.27::real, + 'range_empty_frac', 0.5::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); +WARNING: attribute "id" is not a range type +DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM. + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.27 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | | +(1 row) + +-- warn: range_empty_frac range_length_hist null mismatch, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'arange', + 'inherited', false::boolean, + 'null_frac', 0.28::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); +WARNING: "range_empty_frac" must be specified when "range_length_histogram" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | arange | f | 0.28 | 0 | 0 | | | | | | | | | | +(1 row) + +-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'arange', + 'inherited', false::boolean, + 'null_frac', 0.29::real, + 'range_empty_frac', 0.5::real + ); +WARNING: "range_length_histogram" must be specified when "range_empty_frac" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | | | +(1 row) + +-- ok: range_empty_frac + range_length_hist +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'arange', + 'inherited', false::boolean, + 'range_empty_frac', 0.5::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | +(1 row) + +-- warn: range bounds histogram on scalar, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.31::real, + 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); +WARNING: attribute "id" is not a range type +DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM. + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.31 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | | +(1 row) + +-- ok: range_bounds_histogram +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'arange', + 'inherited', false::boolean, + 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+-------------------------------------- + stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"} +(1 row) + +-- warn: cannot set most_common_elems for range type, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'arange', + 'inherited', false::boolean, + 'null_frac', 0.32::real, + 'most_common_elems', '{3,1}'::text, + 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[] + ); +WARNING: unable to determine element type of attribute "arange" +DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST. + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+-------------------------------------- + stats_import | test | arange | f | 0.32 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"} +(1 row) + +-- warn: scalars can't have mcelem, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.33::real, + 'most_common_elems', '{1,3}'::text, + 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[] + ); +WARNING: unable to determine element type of attribute "id" +DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST. + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.33 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | | +(1 row) + +-- warn: mcelem / mcelem mismatch, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'tags', + 'inherited', false::boolean, + 'null_frac', 0.34::real, + 'most_common_elems', '{one,two}'::text + ); +WARNING: "most_common_elem_freqs" must be specified when "most_common_elems" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------ + stats_import | test | tags | f | 0.34 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | | +(1 row) + +-- warn: mcelem / mcelem null mismatch part 2, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'tags', + 'inherited', false::boolean, + 'null_frac', 0.35::real, + 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[] + ); +WARNING: "most_common_elems" must be specified when "most_common_elem_freqs" is specified + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------ + stats_import | test | tags | f | 0.35 | 0 | 0 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | | +(1 row) + +-- ok: mcelem +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'tags', + 'inherited', false::boolean, + 'most_common_elems', '{one,three}'::text, + 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[] + ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------ + stats_import | test | tags | f | 0.35 | 0 | 0 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | | +(1 row) + +-- warn: scalars can't have elem_count_histogram, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.36::real, + 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[] + ); +WARNING: unable to determine element type of attribute "id" +DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST. + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.36 | 5 | 0.6 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | | +(1 row) + +-- +-- Test the ability to exactly copy data from one table to an identical table, +-- correctly reconstructing the stakind order as well as the staopN and +-- stacollN values. Because oids are not stable across databases, we can only +-- test this when the source and destination are on the same database +-- instance. For that reason, we borrow and adapt a query found in fe_utils +-- and used by pg_dump/pg_upgrade. +-- +INSERT INTO stats_import.test +SELECT 1, 'one', (1, 1.1, 'ONE', '2001-01-01', '{ "xkey": "xval" }')::stats_import.complex_type, int4range(1,4), array['red','green'] +UNION ALL +SELECT 2, 'two', (2, 2.2, 'TWO', '2002-02-02', '[true, 4, "six"]')::stats_import.complex_type, int4range(1,4), array['blue','yellow'] +UNION ALL +SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type, int4range(-1,1), array['"orange"', 'purple', 'cyan'] +UNION ALL +SELECT 4, 'four', NULL, int4range(0,100), NULL; +CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1)); +-- Generate statistics on table with data +ANALYZE stats_import.test; +CREATE TABLE stats_import.test_clone ( LIKE stats_import.test ) + WITH (autovacuum_enabled = false); +WARNING: storage parameter autovacuum_enabled is unsupported, ignoring +CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1)); +-- +-- Copy stats from test to test_clone, and is_odd to is_odd_clone +-- +SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.* +FROM pg_catalog.pg_stats AS s +CROSS JOIN LATERAL + pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', s.tablename::text || '_clone', + 'attname', s.attname::text, + 'inherited', s.inherited, + 'version', 150000, + 'null_frac', s.null_frac, + 'avg_width', s.avg_width, + 'n_distinct', s.n_distinct, + 'most_common_vals', s.most_common_vals::text, + 'most_common_freqs', s.most_common_freqs, + 'histogram_bounds', s.histogram_bounds::text, + 'correlation', s.correlation, + 'most_common_elems', s.most_common_elems::text, + 'most_common_elem_freqs', s.most_common_elem_freqs, + 'elem_count_histogram', s.elem_count_histogram, + 'range_bounds_histogram', s.range_bounds_histogram::text, + 'range_empty_frac', s.range_empty_frac, + 'range_length_histogram', s.range_length_histogram::text) AS r +WHERE s.schemaname = 'stats_import' +AND s.tablename IN ('test', 'is_odd') +ORDER BY s.tablename, s.attname, s.inherited; + schemaname | tablename | attname | inherited | r +--------------+-----------+---------+-----------+--- + stats_import | is_odd | expr | f | t + stats_import | test | arange | f | t + stats_import | test | comp | f | t + stats_import | test | id | f | t + stats_import | test | name | f | t + stats_import | test | tags | f | t +(6 rows) + +SELECT c.relname, COUNT(*) AS num_stats +FROM pg_class AS c +JOIN pg_statistic s ON s.starelid = c.oid +WHERE c.relnamespace = 'stats_import'::regnamespace +AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone') +GROUP BY c.relname +ORDER BY c.relname; + relname | num_stats +--------------+----------- + is_odd | 1 + is_odd_clone | 1 + test | 5 + test_clone | 5 +(4 rows) + +-- check test minus test_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + +-- check test_clone minus test +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + +-- check is_odd minus is_odd_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + +-- check is_odd_clone minus is_odd +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass; + attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction +---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+----------- +(0 rows) + +-- attribute stats exist before a clear, but not after +SELECT COUNT(*) +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + count +------- + 1 +(1 row) + +SELECT pg_catalog.pg_clear_attribute_stats( + schemaname => 'stats_import', + relname => 'test', + attname => 'arange', + inherited => false); + pg_clear_attribute_stats +-------------------------- + +(1 row) + +SELECT COUNT(*) +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + count +------- + 0 +(1 row) + +-- temp tables +CREATE TEMP TABLE stats_temp(i int); +SELECT pg_restore_relation_stats( + 'schemaname', 'pg_temp', + 'relname', 'stats_temp', + 'relpages', '-19'::integer, + 'reltuples', 401::real, + 'relallvisible', 5::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relname, relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'pg_temp.stats_temp'::regclass +ORDER BY relname; + relname | relpages | reltuples | relallvisible +------------+----------+-----------+--------------- + stats_temp | -19 | 401 | 5 +(1 row) + +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'pg_temp', + 'relname', 'stats_temp', + 'attname', 'i', + 'inherited', false::boolean, + 'null_frac', 0.0123::real + ); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT tablename, null_frac +FROM pg_stats +WHERE schemaname like 'pg_temp%' +AND tablename = 'stats_temp' +AND inherited = false +AND attname = 'i'; + tablename | null_frac +------------+----------- + stats_temp | 0.0123 +(1 row) + +DROP TABLE stats_temp; +DROP SCHEMA stats_import CASCADE; +NOTICE: drop cascades to 6 other objects +DETAIL: drop cascades to sequence stats_import.testseq +drop cascades to table stats_import.part_parent +drop cascades to table stats_import.test +drop cascades to table stats_import.test_clone +drop cascades to type stats_import.complex_type +drop cascades to view stats_import.testview diff --git a/src/test/regress/expected/yb.port.strings.out b/src/test/regress/expected/yb.port.strings.out new file mode 100644 index 0000000000000..5782be7644b43 --- /dev/null +++ b/src/test/regress/expected/yb.port.strings.out @@ -0,0 +1,2665 @@ +-- +-- STRINGS +-- Test various data entry syntaxes. +-- +-- SQL string continuation syntax +-- E021-03 character string literals +SELECT 'first line' +' - next line' + ' - third line' + AS "Three lines to one"; + Three lines to one +------------------------------------- + first line - next line - third line +(1 row) + +-- illegal string continuation syntax +SELECT 'first line' +' - next line' /* this comment is not allowed here */ +' - third line' + AS "Illegal comment within continuation"; +ERROR: syntax error at or near "' - third line'" +LINE 3: ' - third line' + ^ +-- Unicode escapes +SET standard_conforming_strings TO on; +SELECT U&'d\0061t\+000061' AS U&"d\0061t\+000061"; + data +------ + data +(1 row) + +SELECT U&'d!0061t\+000061' UESCAPE '!' AS U&"d*0061t\+000061" UESCAPE '*'; + dat\+000061 +------------- + dat\+000061 +(1 row) + +SELECT U&'a\\b' AS "a\b"; + a\b +----- + a\b +(1 row) + +SELECT U&' \' UESCAPE '!' AS "tricky"; + tricky +-------- + \ +(1 row) + +SELECT 'tricky' AS U&"\" UESCAPE '!'; + \ +-------- + tricky +(1 row) + +SELECT U&'wrong: \061'; +ERROR: invalid Unicode escape +LINE 1: SELECT U&'wrong: \061'; + ^ +HINT: Unicode escapes must be \XXXX or \+XXXXXX. +SELECT U&'wrong: \+0061'; +ERROR: invalid Unicode escape +LINE 1: SELECT U&'wrong: \+0061'; + ^ +HINT: Unicode escapes must be \XXXX or \+XXXXXX. +SELECT U&'wrong: +0061' UESCAPE +; +ERROR: UESCAPE must be followed by a simple string literal at or near "+" +LINE 1: SELECT U&'wrong: +0061' UESCAPE +; + ^ +SELECT U&'wrong: +0061' UESCAPE '+'; +ERROR: invalid Unicode escape character at or near "'+'" +LINE 1: SELECT U&'wrong: +0061' UESCAPE '+'; + ^ +SELECT U&'wrong: \db99'; +ERROR: invalid Unicode surrogate pair +LINE 1: SELECT U&'wrong: \db99'; + ^ +SELECT U&'wrong: \db99xy'; +ERROR: invalid Unicode surrogate pair +LINE 1: SELECT U&'wrong: \db99xy'; + ^ +SELECT U&'wrong: \db99\\'; +ERROR: invalid Unicode surrogate pair +LINE 1: SELECT U&'wrong: \db99\\'; + ^ +SELECT U&'wrong: \db99\0061'; +ERROR: invalid Unicode surrogate pair +LINE 1: SELECT U&'wrong: \db99\0061'; + ^ +SELECT U&'wrong: \+00db99\+000061'; +ERROR: invalid Unicode surrogate pair +LINE 1: SELECT U&'wrong: \+00db99\+000061'; + ^ +SELECT U&'wrong: \+2FFFFF'; +ERROR: invalid Unicode escape value +LINE 1: SELECT U&'wrong: \+2FFFFF'; + ^ +-- while we're here, check the same cases in E-style literals +SELECT E'd\u0061t\U00000061' AS "data"; + data +------ + data +(1 row) + +SELECT E'a\\b' AS "a\b"; + a\b +----- + a\b +(1 row) + +SELECT E'wrong: \u061'; +ERROR: invalid Unicode escape +LINE 1: SELECT E'wrong: \u061'; + ^ +HINT: Unicode escapes must be \uXXXX or \UXXXXXXXX. +SELECT E'wrong: \U0061'; +ERROR: invalid Unicode escape +LINE 1: SELECT E'wrong: \U0061'; + ^ +HINT: Unicode escapes must be \uXXXX or \UXXXXXXXX. +SELECT E'wrong: \udb99'; +ERROR: invalid Unicode surrogate pair at or near "'" +LINE 1: SELECT E'wrong: \udb99'; + ^ +SELECT E'wrong: \udb99xy'; +ERROR: invalid Unicode surrogate pair at or near "x" +LINE 1: SELECT E'wrong: \udb99xy'; + ^ +SELECT E'wrong: \udb99\\'; +ERROR: invalid Unicode surrogate pair at or near "\" +LINE 1: SELECT E'wrong: \udb99\\'; + ^ +SELECT E'wrong: \udb99\u0061'; +ERROR: invalid Unicode surrogate pair at or near "\u0061" +LINE 1: SELECT E'wrong: \udb99\u0061'; + ^ +SELECT E'wrong: \U0000db99\U00000061'; +ERROR: invalid Unicode surrogate pair at or near "\U00000061" +LINE 1: SELECT E'wrong: \U0000db99\U00000061'; + ^ +SELECT E'wrong: \U002FFFFF'; +ERROR: invalid Unicode escape value at or near "\U002FFFFF" +LINE 1: SELECT E'wrong: \U002FFFFF'; + ^ +SET standard_conforming_strings TO off; +SELECT U&'d\0061t\+000061' AS U&"d\0061t\+000061"; +ERROR: unsafe use of string constant with Unicode escapes +LINE 1: SELECT U&'d\0061t\+000061' AS U&"d\0061t\+000061"; + ^ +DETAIL: String constants with Unicode escapes cannot be used when standard_conforming_strings is off. +SELECT U&'d!0061t\+000061' UESCAPE '!' AS U&"d*0061t\+000061" UESCAPE '*'; +ERROR: unsafe use of string constant with Unicode escapes +LINE 1: SELECT U&'d!0061t\+000061' UESCAPE '!' AS U&"d*0061t\+000061... + ^ +DETAIL: String constants with Unicode escapes cannot be used when standard_conforming_strings is off. +SELECT U&' \' UESCAPE '!' AS "tricky"; +ERROR: unsafe use of string constant with Unicode escapes +LINE 1: SELECT U&' \' UESCAPE '!' AS "tricky"; + ^ +DETAIL: String constants with Unicode escapes cannot be used when standard_conforming_strings is off. +SELECT 'tricky' AS U&"\" UESCAPE '!'; + \ +-------- + tricky +(1 row) + +SELECT U&'wrong: \061'; +ERROR: unsafe use of string constant with Unicode escapes +LINE 1: SELECT U&'wrong: \061'; + ^ +DETAIL: String constants with Unicode escapes cannot be used when standard_conforming_strings is off. +SELECT U&'wrong: \+0061'; +ERROR: unsafe use of string constant with Unicode escapes +LINE 1: SELECT U&'wrong: \+0061'; + ^ +DETAIL: String constants with Unicode escapes cannot be used when standard_conforming_strings is off. +SELECT U&'wrong: +0061' UESCAPE '+'; +ERROR: unsafe use of string constant with Unicode escapes +LINE 1: SELECT U&'wrong: +0061' UESCAPE '+'; + ^ +DETAIL: String constants with Unicode escapes cannot be used when standard_conforming_strings is off. +RESET standard_conforming_strings; +-- bytea +SET bytea_output TO hex; +SELECT E'\\xDeAdBeEf'::bytea; + bytea +------------ + \xdeadbeef +(1 row) + +SELECT E'\\x De Ad Be Ef '::bytea; + bytea +------------ + \xdeadbeef +(1 row) + +SELECT E'\\xDeAdBeE'::bytea; +ERROR: invalid hexadecimal data: odd number of digits +LINE 1: SELECT E'\\xDeAdBeE'::bytea; + ^ +SELECT E'\\xDeAdBeEx'::bytea; +ERROR: invalid hexadecimal digit: "x" +LINE 1: SELECT E'\\xDeAdBeEx'::bytea; + ^ +SELECT E'\\xDe00BeEf'::bytea; + bytea +------------ + \xde00beef +(1 row) + +SELECT E'DeAdBeEf'::bytea; + bytea +-------------------- + \x4465416442654566 +(1 row) + +SELECT E'De\\000dBeEf'::bytea; + bytea +-------------------- + \x4465006442654566 +(1 row) + +SELECT E'De\123dBeEf'::bytea; + bytea +-------------------- + \x4465536442654566 +(1 row) + +SELECT E'De\\123dBeEf'::bytea; + bytea +-------------------- + \x4465536442654566 +(1 row) + +SELECT E'De\\678dBeEf'::bytea; +ERROR: invalid input syntax for type bytea +LINE 1: SELECT E'De\\678dBeEf'::bytea; + ^ +SET bytea_output TO escape; +SELECT E'\\xDeAdBeEf'::bytea; + bytea +------------------ + \336\255\276\357 +(1 row) + +SELECT E'\\x De Ad Be Ef '::bytea; + bytea +------------------ + \336\255\276\357 +(1 row) + +SELECT E'\\xDe00BeEf'::bytea; + bytea +------------------ + \336\000\276\357 +(1 row) + +SELECT E'DeAdBeEf'::bytea; + bytea +---------- + DeAdBeEf +(1 row) + +SELECT E'De\\000dBeEf'::bytea; + bytea +------------- + De\000dBeEf +(1 row) + +SELECT E'De\\123dBeEf'::bytea; + bytea +---------- + DeSdBeEf +(1 row) + +-- +-- test conversions between various string types +-- E021-10 implicit casting among the character data types +-- +SELECT CAST(f1 AS text) AS "text(char)" FROM CHAR_TBL; + text(char) +------------ + a + ab + abcd + abcd +(4 rows) + +SELECT CAST(f1 AS text) AS "text(varchar)" FROM VARCHAR_TBL; + text(varchar) +--------------- + a + ab + abcd + abcd +(4 rows) + +SELECT CAST(name 'namefield' AS text) AS "text(name)"; + text(name) +------------ + namefield +(1 row) + +-- since this is an explicit cast, it should truncate w/o error: +SELECT CAST(f1 AS char(10)) AS "char(text)" FROM TEXT_TBL; + char(text) +------------ + doh! + hi de ho n +(2 rows) + +-- note: implicit-cast case is tested in char.sql +SELECT CAST(f1 AS char(20)) AS "char(text)" FROM TEXT_TBL; + char(text) +---------------------- + doh! + hi de ho neighbor +(2 rows) + +SELECT CAST(f1 AS char(10)) AS "char(varchar)" FROM VARCHAR_TBL; + char(varchar) +--------------- + a + ab + abcd + abcd +(4 rows) + +SELECT CAST(name 'namefield' AS char(10)) AS "char(name)"; + char(name) +------------ + namefield +(1 row) + +SELECT CAST(f1 AS varchar) AS "varchar(text)" FROM TEXT_TBL; + varchar(text) +------------------- + doh! + hi de ho neighbor +(2 rows) + +SELECT CAST(f1 AS varchar) AS "varchar(char)" FROM CHAR_TBL; + varchar(char) +--------------- + a + ab + abcd + abcd +(4 rows) + +SELECT CAST(name 'namefield' AS varchar) AS "varchar(name)"; + varchar(name) +--------------- + namefield +(1 row) + +-- +-- test SQL string functions +-- E### and T### are feature reference numbers from SQL99 +-- +-- E021-09 trim function +SELECT TRIM(BOTH FROM ' bunch o blanks ') = 'bunch o blanks' AS "bunch o blanks"; + bunch o blanks +---------------- + t +(1 row) + +SELECT TRIM(LEADING FROM ' bunch o blanks ') = 'bunch o blanks ' AS "bunch o blanks "; + bunch o blanks +------------------ + t +(1 row) + +SELECT TRIM(TRAILING FROM ' bunch o blanks ') = ' bunch o blanks' AS " bunch o blanks"; + bunch o blanks +------------------ + t +(1 row) + +SELECT TRIM(BOTH 'x' FROM 'xxxxxsome Xsxxxxx') = 'some Xs' AS "some Xs"; + some Xs +--------- + t +(1 row) + +-- E021-06 substring expression +SELECT SUBSTRING('1234567890' FROM 3) = '34567890' AS "34567890"; + 34567890 +---------- + t +(1 row) + +SELECT SUBSTRING('1234567890' FROM 4 FOR 3) = '456' AS "456"; + 456 +----- + t +(1 row) + +-- test overflow cases +SELECT SUBSTRING('string' FROM 2 FOR 2147483646) AS "tring"; + tring +------- + tring +(1 row) + +SELECT SUBSTRING('string' FROM -10 FOR 2147483646) AS "string"; + string +-------- + string +(1 row) + +SELECT SUBSTRING('string' FROM -10 FOR -2147483646) AS "error"; +ERROR: negative substring length not allowed +-- T581 regular expression substring (with SQL's bizarre regexp syntax) +SELECT SUBSTRING('abcdefg' SIMILAR 'a#"(b_d)#"%' ESCAPE '#') AS "bcd"; + bcd +----- + bcd +(1 row) + +-- obsolete SQL99 syntax +SELECT SUBSTRING('abcdefg' FROM 'a#"(b_d)#"%' FOR '#') AS "bcd"; + bcd +----- + bcd +(1 row) + +-- No match should return NULL +SELECT SUBSTRING('abcdefg' SIMILAR '#"(b_d)#"%' ESCAPE '#') IS NULL AS "True"; + True +------ + t +(1 row) + +-- Null inputs should return NULL +SELECT SUBSTRING('abcdefg' SIMILAR '%' ESCAPE NULL) IS NULL AS "True"; + True +------ + t +(1 row) + +SELECT SUBSTRING(NULL SIMILAR '%' ESCAPE '#') IS NULL AS "True"; + True +------ + t +(1 row) + +SELECT SUBSTRING('abcdefg' SIMILAR NULL ESCAPE '#') IS NULL AS "True"; + True +------ + t +(1 row) + +-- The first and last parts should act non-greedy +SELECT SUBSTRING('abcdefg' SIMILAR 'a#"%#"g' ESCAPE '#') AS "bcdef"; + bcdef +------- + bcdef +(1 row) + +SELECT SUBSTRING('abcdefg' SIMILAR 'a*#"%#"g*' ESCAPE '#') AS "abcdefg"; + abcdefg +--------- + abcdefg +(1 row) + +-- Vertical bar in any part affects only that part +SELECT SUBSTRING('abcdefg' SIMILAR 'a|b#"%#"g' ESCAPE '#') AS "bcdef"; + bcdef +------- + bcdef +(1 row) + +SELECT SUBSTRING('abcdefg' SIMILAR 'a#"%#"x|g' ESCAPE '#') AS "bcdef"; + bcdef +------- + bcdef +(1 row) + +SELECT SUBSTRING('abcdefg' SIMILAR 'a#"%|ab#"g' ESCAPE '#') AS "bcdef"; + bcdef +------- + bcdef +(1 row) + +-- Can't have more than two part separators +SELECT SUBSTRING('abcdefg' SIMILAR 'a*#"%#"g*#"x' ESCAPE '#') AS "error"; +ERROR: SQL regular expression may not contain more than two escape-double-quote separators +CONTEXT: SQL function "substring" statement 1 +-- Postgres extension: with 0 or 1 separator, assume parts 1 and 3 are empty +SELECT SUBSTRING('abcdefg' SIMILAR 'a#"%g' ESCAPE '#') AS "bcdefg"; + bcdefg +-------- + bcdefg +(1 row) + +SELECT SUBSTRING('abcdefg' SIMILAR 'a%g' ESCAPE '#') AS "abcdefg"; + abcdefg +--------- + abcdefg +(1 row) + +-- substring() with just two arguments is not allowed by SQL spec; +-- we accept it, but we interpret the pattern as a POSIX regexp not SQL +SELECT SUBSTRING('abcdefg' FROM 'c.e') AS "cde"; + cde +----- + cde +(1 row) + +-- With a parenthesized subexpression, return only what matches the subexpr +SELECT SUBSTRING('abcdefg' FROM 'b(.*)f') AS "cde"; + cde +----- + cde +(1 row) + +-- Check case where we have a match, but not a subexpression match +SELECT SUBSTRING('foo' FROM 'foo(bar)?') IS NULL AS t; + t +--- + t +(1 row) + +-- Check behavior of SIMILAR TO, which uses largely the same regexp variant +SELECT 'abcdefg' SIMILAR TO '_bcd%' AS true; + true +------ + t +(1 row) + +SELECT 'abcdefg' SIMILAR TO 'bcd%' AS false; + false +------- + f +(1 row) + +SELECT 'abcdefg' SIMILAR TO '_bcd#%' ESCAPE '#' AS false; + false +------- + f +(1 row) + +SELECT 'abcd%' SIMILAR TO '_bcd#%' ESCAPE '#' AS true; + true +------ + t +(1 row) + +-- Postgres uses '\' as the default escape character, which is not per spec +SELECT 'abcdefg' SIMILAR TO '_bcd\%' AS false; + false +------- + f +(1 row) + +-- and an empty string to mean "no escape", which is also not per spec +SELECT 'abcd\efg' SIMILAR TO '_bcd\%' ESCAPE '' AS true; + true +------ + t +(1 row) + +-- these behaviors are per spec, though: +SELECT 'abcdefg' SIMILAR TO '_bcd%' ESCAPE NULL AS null; + null +------ + +(1 row) + +SELECT 'abcdefg' SIMILAR TO '_bcd#%' ESCAPE '##' AS error; +ERROR: invalid escape string +HINT: Escape string must be empty or one character. +-- Test backslash escapes in regexp_replace's replacement string +SELECT regexp_replace('1112223333', E'(\\d{3})(\\d{3})(\\d{4})', E'(\\1) \\2-\\3'); + regexp_replace +---------------- + (111) 222-3333 +(1 row) + +SELECT regexp_replace('foobarrbazz', E'(.)\\1', E'X\\&Y', 'g'); + regexp_replace +------------------- + fXooYbaXrrYbaXzzY +(1 row) + +SELECT regexp_replace('foobarrbazz', E'(.)\\1', E'X\\\\Y', 'g'); + regexp_replace +---------------- + fX\YbaX\YbaX\Y +(1 row) + +-- not an error, though perhaps it should be: +SELECT regexp_replace('foobarrbazz', E'(.)\\1', E'X\\Y\\1Z\\'); + regexp_replace +----------------- + fX\YoZ\barrbazz +(1 row) + +SELECT regexp_replace('AAA BBB CCC ', E'\\s+', ' ', 'g'); + regexp_replace +---------------- + AAA BBB CCC +(1 row) + +SELECT regexp_replace('AAA', '^|$', 'Z', 'g'); + regexp_replace +---------------- + ZAAAZ +(1 row) + +SELECT regexp_replace('AAA aaa', 'A+', 'Z', 'gi'); + regexp_replace +---------------- + Z Z +(1 row) + +-- invalid regexp option +SELECT regexp_replace('AAA aaa', 'A+', 'Z', 'z'); +ERROR: invalid regular expression option: "z" +-- extended regexp_replace tests +SELECT regexp_replace('A PostgreSQL function', 'A|e|i|o|u', 'X', 1); + regexp_replace +----------------------- + X PostgreSQL function +(1 row) + +SELECT regexp_replace('A PostgreSQL function', 'A|e|i|o|u', 'X', 1, 2); + regexp_replace +----------------------- + A PXstgreSQL function +(1 row) + +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 0, 'i'); + regexp_replace +----------------------- + X PXstgrXSQL fXnctXXn +(1 row) + +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 1, 'i'); + regexp_replace +----------------------- + X PostgreSQL function +(1 row) + +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 2, 'i'); + regexp_replace +----------------------- + A PXstgreSQL function +(1 row) + +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 3, 'i'); + regexp_replace +----------------------- + A PostgrXSQL function +(1 row) + +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 9, 'i'); + regexp_replace +----------------------- + A PostgreSQL function +(1 row) + +SELECT regexp_replace('A PostgreSQL function', 'A|e|i|o|u', 'X', 7, 0, 'i'); + regexp_replace +----------------------- + A PostgrXSQL fXnctXXn +(1 row) + +-- 'g' flag should be ignored when N is specified +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 1, 'g'); + regexp_replace +----------------------- + A PXstgreSQL function +(1 row) + +-- errors +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', -1, 0, 'i'); +ERROR: invalid value for parameter "start": -1 +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, -1, 'i'); +ERROR: invalid value for parameter "n": -1 +-- erroneous invocation of non-extended form +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', '1'); +ERROR: invalid regular expression option: "1" +HINT: If you meant to use regexp_replace() with a start parameter, cast the fourth argument to integer explicitly. +-- regexp_count tests +SELECT regexp_count('123123123123123', '(12)3'); + regexp_count +-------------- + 5 +(1 row) + +SELECT regexp_count('123123123123', '123', 1); + regexp_count +-------------- + 4 +(1 row) + +SELECT regexp_count('123123123123', '123', 3); + regexp_count +-------------- + 3 +(1 row) + +SELECT regexp_count('123123123123', '123', 33); + regexp_count +-------------- + 0 +(1 row) + +SELECT regexp_count('ABCABCABCABC', 'Abc', 1, ''); + regexp_count +-------------- + 0 +(1 row) + +SELECT regexp_count('ABCABCABCABC', 'Abc', 1, 'i'); + regexp_count +-------------- + 4 +(1 row) + +-- errors +SELECT regexp_count('123123123123', '123', 0); +ERROR: invalid value for parameter "start": 0 +SELECT regexp_count('123123123123', '123', -3); +ERROR: invalid value for parameter "start": -3 +-- regexp_like tests +SELECT regexp_like('Steven', '^Ste(v|ph)en$'); + regexp_like +------------- + t +(1 row) + +SELECT regexp_like('a'||CHR(10)||'d', 'a.d', 'n'); + regexp_like +------------- + f +(1 row) + +SELECT regexp_like('a'||CHR(10)||'d', 'a.d', 's'); + regexp_like +------------- + t +(1 row) + +SELECT regexp_like('abc', ' a . c ', 'x'); + regexp_like +------------- + t +(1 row) + +SELECT regexp_like('abc', 'a.c', 'g'); -- error +ERROR: regexp_like() does not support the "global" option +-- regexp_instr tests +SELECT regexp_instr('abcdefghi', 'd.f'); + regexp_instr +-------------- + 4 +(1 row) + +SELECT regexp_instr('abcdefghi', 'd.q'); + regexp_instr +-------------- + 0 +(1 row) + +SELECT regexp_instr('abcabcabc', 'a.c'); + regexp_instr +-------------- + 1 +(1 row) + +SELECT regexp_instr('abcabcabc', 'a.c', 2); + regexp_instr +-------------- + 4 +(1 row) + +SELECT regexp_instr('abcabcabc', 'a.c', 1, 3); + regexp_instr +-------------- + 7 +(1 row) + +SELECT regexp_instr('abcabcabc', 'a.c', 1, 4); + regexp_instr +-------------- + 0 +(1 row) + +SELECT regexp_instr('abcabcabc', 'A.C', 1, 2, 0, 'i'); + regexp_instr +-------------- + 4 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 0); + regexp_instr +-------------- + 1 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 1); + regexp_instr +-------------- + 1 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 2); + regexp_instr +-------------- + 4 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 3); + regexp_instr +-------------- + 5 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 4); + regexp_instr +-------------- + 7 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 5); + regexp_instr +-------------- + 0 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 0); + regexp_instr +-------------- + 9 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 1); + regexp_instr +-------------- + 4 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 2); + regexp_instr +-------------- + 9 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 3); + regexp_instr +-------------- + 7 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 4); + regexp_instr +-------------- + 9 +(1 row) + +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 5); + regexp_instr +-------------- + 0 +(1 row) + +-- Check case where we have a match, but not a subexpression match +SELECT regexp_instr('foo', 'foo(bar)?', 1, 1, 0, '', 1); + regexp_instr +-------------- + 0 +(1 row) + +-- errors +SELECT regexp_instr('abcabcabc', 'a.c', 0, 1); +ERROR: invalid value for parameter "start": 0 +SELECT regexp_instr('abcabcabc', 'a.c', 1, 0); +ERROR: invalid value for parameter "n": 0 +SELECT regexp_instr('abcabcabc', 'a.c', 1, 1, -1); +ERROR: invalid value for parameter "endoption": -1 +SELECT regexp_instr('abcabcabc', 'a.c', 1, 1, 2); +ERROR: invalid value for parameter "endoption": 2 +SELECT regexp_instr('abcabcabc', 'a.c', 1, 1, 0, 'g'); +ERROR: regexp_instr() does not support the "global" option +SELECT regexp_instr('abcabcabc', 'a.c', 1, 1, 0, '', -1); +ERROR: invalid value for parameter "subexpr": -1 +-- regexp_substr tests +SELECT regexp_substr('abcdefghi', 'd.f'); + regexp_substr +--------------- + def +(1 row) + +SELECT regexp_substr('abcdefghi', 'd.q') IS NULL AS t; + t +--- + t +(1 row) + +SELECT regexp_substr('abcabcabc', 'a.c'); + regexp_substr +--------------- + abc +(1 row) + +SELECT regexp_substr('abcabcabc', 'a.c', 2); + regexp_substr +--------------- + abc +(1 row) + +SELECT regexp_substr('abcabcabc', 'a.c', 1, 3); + regexp_substr +--------------- + abc +(1 row) + +SELECT regexp_substr('abcabcabc', 'a.c', 1, 4) IS NULL AS t; + t +--- + t +(1 row) + +SELECT regexp_substr('abcabcabc', 'A.C', 1, 2, 'i'); + regexp_substr +--------------- + abc +(1 row) + +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 0); + regexp_substr +--------------- + 12345678 +(1 row) + +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 1); + regexp_substr +--------------- + 123 +(1 row) + +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 2); + regexp_substr +--------------- + 45678 +(1 row) + +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 3); + regexp_substr +--------------- + 56 +(1 row) + +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 4); + regexp_substr +--------------- + 78 +(1 row) + +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 5) IS NULL AS t; + t +--- + t +(1 row) + +-- Check case where we have a match, but not a subexpression match +SELECT regexp_substr('foo', 'foo(bar)?', 1, 1, '', 1) IS NULL AS t; + t +--- + t +(1 row) + +-- errors +SELECT regexp_substr('abcabcabc', 'a.c', 0, 1); +ERROR: invalid value for parameter "start": 0 +SELECT regexp_substr('abcabcabc', 'a.c', 1, 0); +ERROR: invalid value for parameter "n": 0 +SELECT regexp_substr('abcabcabc', 'a.c', 1, 1, 'g'); +ERROR: regexp_substr() does not support the "global" option +SELECT regexp_substr('abcabcabc', 'a.c', 1, 1, '', -1); +ERROR: invalid value for parameter "subexpr": -1 +-- set so we can tell NULL from empty string +\pset null '\\N' +-- return all matches from regexp +SELECT regexp_matches('foobarbequebaz', $re$(bar)(beque)$re$); + regexp_matches +---------------- + {bar,beque} +(1 row) + +-- test case insensitive +SELECT regexp_matches('foObARbEqUEbAz', $re$(bar)(beque)$re$, 'i'); + regexp_matches +---------------- + {bAR,bEqUE} +(1 row) + +-- global option - more than one match +SELECT regexp_matches('foobarbequebazilbarfbonk', $re$(b[^b]+)(b[^b]+)$re$, 'g'); + regexp_matches +---------------- + {bar,beque} + {bazil,barf} +(2 rows) + +-- empty capture group (matched empty string) +SELECT regexp_matches('foobarbequebaz', $re$(bar)(.*)(beque)$re$); + regexp_matches +---------------- + {bar,"",beque} +(1 row) + +-- no match +SELECT regexp_matches('foobarbequebaz', $re$(bar)(.+)(beque)$re$); + regexp_matches +---------------- +(0 rows) + +-- optional capture group did not match, null entry in array +SELECT regexp_matches('foobarbequebaz', $re$(bar)(.+)?(beque)$re$); + regexp_matches +------------------ + {bar,NULL,beque} +(1 row) + +-- no capture groups +SELECT regexp_matches('foobarbequebaz', $re$barbeque$re$); + regexp_matches +---------------- + {barbeque} +(1 row) + +-- start/end-of-line matches are of zero length +SELECT regexp_matches('foo' || chr(10) || 'bar' || chr(10) || 'bequq' || chr(10) || 'baz', '^', 'mg'); + regexp_matches +---------------- + {""} + {""} + {""} + {""} +(4 rows) + +SELECT regexp_matches('foo' || chr(10) || 'bar' || chr(10) || 'bequq' || chr(10) || 'baz', '$', 'mg'); + regexp_matches +---------------- + {""} + {""} + {""} + {""} +(4 rows) + +SELECT regexp_matches('1' || chr(10) || '2' || chr(10) || '3' || chr(10) || '4' || chr(10), '^.?', 'mg'); + regexp_matches +---------------- + {1} + {2} + {3} + {4} + {""} +(5 rows) + +SELECT regexp_matches(chr(10) || '1' || chr(10) || '2' || chr(10) || '3' || chr(10) || '4' || chr(10), '.?$', 'mg'); + regexp_matches +---------------- + {""} + {1} + {""} + {2} + {""} + {3} + {""} + {4} + {""} + {""} +(10 rows) + +SELECT regexp_matches(chr(10) || '1' || chr(10) || '2' || chr(10) || '3' || chr(10) || '4', '.?$', 'mg'); + regexp_matches +---------------- + {""} + {1} + {""} + {2} + {""} + {3} + {""} + {4} + {""} +(9 rows) + +-- give me errors +SELECT regexp_matches('foobarbequebaz', $re$(bar)(beque)$re$, 'gz'); +ERROR: invalid regular expression option: "z" +SELECT regexp_matches('foobarbequebaz', $re$(barbeque$re$); +ERROR: invalid regular expression: parentheses () not balanced +SELECT regexp_matches('foobarbequebaz', $re$(bar)(beque){2,1}$re$); +ERROR: invalid regular expression: invalid repetition count(s) +-- split string on regexp +SELECT foo, length(foo) FROM regexp_split_to_table('the quick brown fox jumps over the lazy dog', $re$\s+$re$) AS foo; + foo | length +-------+-------- + the | 3 + quick | 5 + brown | 5 + fox | 3 + jumps | 5 + over | 4 + the | 3 + lazy | 4 + dog | 3 +(9 rows) + +SELECT regexp_split_to_array('the quick brown fox jumps over the lazy dog', $re$\s+$re$); + regexp_split_to_array +----------------------------------------------- + {the,quick,brown,fox,jumps,over,the,lazy,dog} +(1 row) + +SELECT foo, length(foo) FROM regexp_split_to_table('the quick brown fox jumps over the lazy dog', $re$\s*$re$) AS foo; + foo | length +-----+-------- + t | 1 + h | 1 + e | 1 + q | 1 + u | 1 + i | 1 + c | 1 + k | 1 + b | 1 + r | 1 + o | 1 + w | 1 + n | 1 + f | 1 + o | 1 + x | 1 + j | 1 + u | 1 + m | 1 + p | 1 + s | 1 + o | 1 + v | 1 + e | 1 + r | 1 + t | 1 + h | 1 + e | 1 + l | 1 + a | 1 + z | 1 + y | 1 + d | 1 + o | 1 + g | 1 +(35 rows) + +SELECT regexp_split_to_array('the quick brown fox jumps over the lazy dog', $re$\s*$re$); + regexp_split_to_array +------------------------------------------------------------------------- + {t,h,e,q,u,i,c,k,b,r,o,w,n,f,o,x,j,u,m,p,s,o,v,e,r,t,h,e,l,a,z,y,d,o,g} +(1 row) + +SELECT foo, length(foo) FROM regexp_split_to_table('the quick brown fox jumps over the lazy dog', '') AS foo; + foo | length +-----+-------- + t | 1 + h | 1 + e | 1 + | 1 + q | 1 + u | 1 + i | 1 + c | 1 + k | 1 + | 1 + b | 1 + r | 1 + o | 1 + w | 1 + n | 1 + | 1 + f | 1 + o | 1 + x | 1 + | 1 + j | 1 + u | 1 + m | 1 + p | 1 + s | 1 + | 1 + o | 1 + v | 1 + e | 1 + r | 1 + | 1 + t | 1 + h | 1 + e | 1 + | 1 + l | 1 + a | 1 + z | 1 + y | 1 + | 1 + d | 1 + o | 1 + g | 1 +(43 rows) + +SELECT regexp_split_to_array('the quick brown fox jumps over the lazy dog', ''); + regexp_split_to_array +--------------------------------------------------------------------------------------------------------- + {t,h,e," ",q,u,i,c,k," ",b,r,o,w,n," ",f,o,x," ",j,u,m,p,s," ",o,v,e,r," ",t,h,e," ",l,a,z,y," ",d,o,g} +(1 row) + +-- case insensitive +SELECT foo, length(foo) FROM regexp_split_to_table('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'i') AS foo; + foo | length +---------------------------+-------- + th | 2 + QUick bROWn FOx jUMPs ov | 25 + r Th | 4 + lazy dOG | 9 +(4 rows) + +SELECT regexp_split_to_array('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'i'); + regexp_split_to_array +----------------------------------------------------- + {th," QUick bROWn FOx jUMPs ov","r Th"," lazy dOG"} +(1 row) + +-- no match of pattern +SELECT foo, length(foo) FROM regexp_split_to_table('the quick brown fox jumps over the lazy dog', 'nomatch') AS foo; + foo | length +---------------------------------------------+-------- + the quick brown fox jumps over the lazy dog | 43 +(1 row) + +SELECT regexp_split_to_array('the quick brown fox jumps over the lazy dog', 'nomatch'); + regexp_split_to_array +------------------------------------------------- + {"the quick brown fox jumps over the lazy dog"} +(1 row) + +-- some corner cases +SELECT regexp_split_to_array('123456','1'); + regexp_split_to_array +----------------------- + {"",23456} +(1 row) + +SELECT regexp_split_to_array('123456','6'); + regexp_split_to_array +----------------------- + {12345,""} +(1 row) + +SELECT regexp_split_to_array('123456','.'); + regexp_split_to_array +------------------------ + {"","","","","","",""} +(1 row) + +SELECT regexp_split_to_array('123456',''); + regexp_split_to_array +----------------------- + {1,2,3,4,5,6} +(1 row) + +SELECT regexp_split_to_array('123456','(?:)'); + regexp_split_to_array +----------------------- + {1,2,3,4,5,6} +(1 row) + +SELECT regexp_split_to_array('1',''); + regexp_split_to_array +----------------------- + {1} +(1 row) + +-- errors +SELECT foo, length(foo) FROM regexp_split_to_table('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'zippy') AS foo; +ERROR: invalid regular expression option: "z" +SELECT regexp_split_to_array('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'iz'); +ERROR: invalid regular expression option: "z" +-- global option meaningless for regexp_split +SELECT foo, length(foo) FROM regexp_split_to_table('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'g') AS foo; +ERROR: regexp_split_to_table() does not support the "global" option +SELECT regexp_split_to_array('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'g'); +ERROR: regexp_split_to_array() does not support the "global" option +-- change NULL-display back +\pset null '' +-- E021-11 position expression +SELECT POSITION('4' IN '1234567890') = '4' AS "4"; + 4 +--- + t +(1 row) + +SELECT POSITION('5' IN '1234567890') = '5' AS "5"; + 5 +--- + t +(1 row) + +-- T312 character overlay function +SELECT OVERLAY('abcdef' PLACING '45' FROM 4) AS "abc45f"; + abc45f +-------- + abc45f +(1 row) + +SELECT OVERLAY('yabadoo' PLACING 'daba' FROM 5) AS "yabadaba"; + yabadaba +---------- + yabadaba +(1 row) + +SELECT OVERLAY('yabadoo' PLACING 'daba' FROM 5 FOR 0) AS "yabadabadoo"; + yabadabadoo +------------- + yabadabadoo +(1 row) + +SELECT OVERLAY('babosa' PLACING 'ubb' FROM 2 FOR 4) AS "bubba"; + bubba +------- + bubba +(1 row) + +-- +-- test LIKE +-- Be sure to form every test as a LIKE/NOT LIKE pair. +-- +-- simplest examples +-- E061-04 like predicate +SELECT 'hawkeye' LIKE 'h%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye' NOT LIKE 'h%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye' LIKE 'H%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye' NOT LIKE 'H%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye' LIKE 'indio%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye' NOT LIKE 'indio%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye' LIKE 'h%eye' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye' NOT LIKE 'h%eye' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio' LIKE '_ndio' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio' NOT LIKE '_ndio' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio' LIKE 'in__o' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio' NOT LIKE 'in__o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio' LIKE 'in_o' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio' NOT LIKE 'in_o' AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::name LIKE '_b_' AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::name NOT LIKE '_b_' AS "false"; + false +------- + f +(1 row) + +SELECT 'abc'::bytea LIKE '_b_'::bytea AS "true"; + true +------ + t +(1 row) + +SELECT 'abc'::bytea NOT LIKE '_b_'::bytea AS "false"; + false +------- + f +(1 row) + +-- unused escape character +SELECT 'hawkeye' LIKE 'h%' ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye' NOT LIKE 'h%' ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio' LIKE 'ind_o' ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio' NOT LIKE 'ind_o' ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%' LIKE 'h#%' ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%' NOT LIKE 'h#%' ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye' LIKE 'h#%' ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%wkeye' NOT LIKE 'h#%' ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye' LIKE 'h#%%' ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%wkeye' NOT LIKE 'h#%%' ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'h%awkeye' LIKE 'h#%a%k%e' ESCAPE '#' AS "true"; + true +------ + t +(1 row) + +SELECT 'h%awkeye' NOT LIKE 'h#%a%k%e' ESCAPE '#' AS "false"; + false +------- + f +(1 row) + +SELECT 'indio' LIKE '_ndio' ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'indio' NOT LIKE '_ndio' ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio' LIKE 'i$_d_o' ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio' NOT LIKE 'i$_d_o' ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio' LIKE 'i$_nd_o' ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'i_dio' NOT LIKE 'i$_nd_o' ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio' LIKE 'i$_d%o' ESCAPE '$' AS "true"; + true +------ + t +(1 row) + +SELECT 'i_dio' NOT LIKE 'i$_d%o' ESCAPE '$' AS "false"; + false +------- + f +(1 row) + +SELECT 'a_c'::bytea LIKE 'a$__'::bytea ESCAPE '$'::bytea AS "true"; + true +------ + t +(1 row) + +SELECT 'a_c'::bytea NOT LIKE 'a$__'::bytea ESCAPE '$'::bytea AS "false"; + false +------- + f +(1 row) + +-- escape character same as pattern character +SELECT 'maca' LIKE 'm%aca' ESCAPE '%' AS "true"; + true +------ + t +(1 row) + +SELECT 'maca' NOT LIKE 'm%aca' ESCAPE '%' AS "false"; + false +------- + f +(1 row) + +SELECT 'ma%a' LIKE 'm%a%%a' ESCAPE '%' AS "true"; + true +------ + t +(1 row) + +SELECT 'ma%a' NOT LIKE 'm%a%%a' ESCAPE '%' AS "false"; + false +------- + f +(1 row) + +SELECT 'bear' LIKE 'b_ear' ESCAPE '_' AS "true"; + true +------ + t +(1 row) + +SELECT 'bear' NOT LIKE 'b_ear' ESCAPE '_' AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r' LIKE 'b_e__r' ESCAPE '_' AS "true"; + true +------ + t +(1 row) + +SELECT 'be_r' NOT LIKE 'b_e__r' ESCAPE '_' AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r' LIKE '__e__r' ESCAPE '_' AS "false"; + false +------- + f +(1 row) + +SELECT 'be_r' NOT LIKE '__e__r' ESCAPE '_' AS "true"; + true +------ + t +(1 row) + +-- +-- test ILIKE (case-insensitive LIKE) +-- Be sure to form every test as an ILIKE/NOT ILIKE pair. +-- +SELECT 'hawkeye' ILIKE 'h%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye' NOT ILIKE 'h%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye' ILIKE 'H%' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye' NOT ILIKE 'H%' AS "false"; + false +------- + f +(1 row) + +SELECT 'hawkeye' ILIKE 'H%Eye' AS "true"; + true +------ + t +(1 row) + +SELECT 'hawkeye' NOT ILIKE 'H%Eye' AS "false"; + false +------- + f +(1 row) + +SELECT 'Hawkeye' ILIKE 'h%' AS "true"; + true +------ + t +(1 row) + +SELECT 'Hawkeye' NOT ILIKE 'h%' AS "false"; + false +------- + f +(1 row) + +SELECT 'ABC'::name ILIKE '_b_' AS "true"; + true +------ + t +(1 row) + +SELECT 'ABC'::name NOT ILIKE '_b_' AS "false"; + false +------- + f +(1 row) + +-- +-- test %/_ combination cases, cf bugs #4821 and #5478 +-- +SELECT 'foo' LIKE '_%' as t, 'f' LIKE '_%' as t, '' LIKE '_%' as f; + t | t | f +---+---+--- + t | t | f +(1 row) + +SELECT 'foo' LIKE '%_' as t, 'f' LIKE '%_' as t, '' LIKE '%_' as f; + t | t | f +---+---+--- + t | t | f +(1 row) + +SELECT 'foo' LIKE '__%' as t, 'foo' LIKE '___%' as t, 'foo' LIKE '____%' as f; + t | t | f +---+---+--- + t | t | f +(1 row) + +SELECT 'foo' LIKE '%__' as t, 'foo' LIKE '%___' as t, 'foo' LIKE '%____' as f; + t | t | f +---+---+--- + t | t | f +(1 row) + +SELECT 'jack' LIKE '%____%' AS t; + t +--- + t +(1 row) + +-- +-- basic tests of LIKE with indexes +-- +CREATE TABLE texttest (a text PRIMARY KEY, b int); +SELECT * FROM texttest WHERE a LIKE '%1%'; + a | b +---+--- +(0 rows) + +CREATE TABLE byteatest (a bytea PRIMARY KEY, b int); +SELECT * FROM byteatest WHERE a LIKE '%1%'; + a | b +---+--- +(0 rows) + +DROP TABLE texttest, byteatest; +-- +-- test implicit type conversion +-- +-- E021-07 character concatenation +SELECT 'unknown' || ' and unknown' AS "Concat unknown types"; + Concat unknown types +---------------------- + unknown and unknown +(1 row) + +SELECT text 'text' || ' and unknown' AS "Concat text to unknown type"; + Concat text to unknown type +----------------------------- + text and unknown +(1 row) + +SELECT char(20) 'characters' || ' and text' AS "Concat char to unknown type"; + Concat char to unknown type +----------------------------- + characters and text +(1 row) + +SELECT text 'text' || char(20) ' and characters' AS "Concat text to char"; + Concat text to char +--------------------- + text and characters +(1 row) + +SELECT text 'text' || varchar ' and varchar' AS "Concat text to varchar"; + Concat text to varchar +------------------------ + text and varchar +(1 row) + +-- +-- test substr with toasted text values +-- +CREATE TABLE toasttest(f1 text); +insert into toasttest values(repeat('1234567890',10000)); +insert into toasttest values(repeat('1234567890',10000)); +-- +-- Ensure that some values are uncompressed, to test the faster substring +-- operation used in that case +-- +alter table toasttest alter column f1 set storage external; +NOTICE: ALTER action ALTER COLUMN ... SET STORAGE has no effect on YB tables +insert into toasttest values(repeat('1234567890',10000)); +insert into toasttest values(repeat('1234567890',10000)); +-- If the starting position is zero or less, then return from the start of the string +-- adjusting the length to be consistent with the "negative start" per SQL. +SELECT substr(f1, -1, 5) from toasttest; + substr +-------- + 123 + 123 + 123 + 123 +(4 rows) + +-- If the length is less than zero, an ERROR is thrown. +SELECT substr(f1, 5, -1) from toasttest; +ERROR: negative substring length not allowed +-- If no third argument (length) is provided, the length to the end of the +-- string is assumed. +SELECT substr(f1, 99995) from toasttest; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +-- If start plus length is > string length, the result is truncated to +-- string length +SELECT substr(f1, 99995, 10) from toasttest; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +TRUNCATE TABLE toasttest; +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +-- expect >0 blocks +SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty + FROM pg_class where relname = 'toasttest'; + is_empty +---------- + +(1 row) + +TRUNCATE TABLE toasttest; +ALTER TABLE toasttest set (toast_tuple_target = 4080); +NOTICE: storage parameters are currently ignored in YugabyteDB +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +-- expect 0 blocks +SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty + FROM pg_class where relname = 'toasttest'; + is_empty +---------- + +(1 row) + +DROP TABLE toasttest; +-- +-- test substr with toasted bytea values +-- +CREATE TABLE toasttest(f1 bytea); +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +-- +-- Ensure that some values are uncompressed, to test the faster substring +-- operation used in that case +-- +alter table toasttest alter column f1 set storage external; +NOTICE: ALTER action ALTER COLUMN ... SET STORAGE has no effect on YB tables +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +-- If the starting position is zero or less, then return from the start of the string +-- adjusting the length to be consistent with the "negative start" per SQL. +SELECT substr(f1, -1, 5) from toasttest; + substr +-------- + 123 + 123 + 123 + 123 +(4 rows) + +-- If the length is less than zero, an ERROR is thrown. +SELECT substr(f1, 5, -1) from toasttest; +ERROR: negative substring length not allowed +-- If no third argument (length) is provided, the length to the end of the +-- string is assumed. +SELECT substr(f1, 99995) from toasttest; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +-- If start plus length is > string length, the result is truncated to +-- string length +SELECT substr(f1, 99995, 10) from toasttest; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +DROP TABLE toasttest; +-- test internally compressing datums +-- this tests compressing a datum to a very small size which exercises a +-- corner case in packed-varlena handling: even though small, the compressed +-- datum must be given a 4-byte header because there are no bits to indicate +-- compression in a 1-byte header +CREATE TABLE toasttest (c char(4096)); +INSERT INTO toasttest VALUES('x'); +SELECT length(c), c::text FROM toasttest; + length | c +--------+--- + 1 | x +(1 row) + +SELECT c FROM toasttest; + c +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + x +(1 row) + +DROP TABLE toasttest; +-- +-- test length +-- +SELECT length('abcdef') AS "length_6"; + length_6 +---------- + 6 +(1 row) + +-- +-- test strpos +-- +SELECT strpos('abcdef', 'cd') AS "pos_3"; + pos_3 +------- + 3 +(1 row) + +SELECT strpos('abcdef', 'xy') AS "pos_0"; + pos_0 +------- + 0 +(1 row) + +SELECT strpos('abcdef', '') AS "pos_1"; + pos_1 +------- + 1 +(1 row) + +SELECT strpos('', 'xy') AS "pos_0"; + pos_0 +------- + 0 +(1 row) + +SELECT strpos('', '') AS "pos_1"; + pos_1 +------- + 1 +(1 row) + +-- +-- test replace +-- +SELECT replace('abcdef', 'de', '45') AS "abc45f"; + abc45f +-------- + abc45f +(1 row) + +SELECT replace('yabadabadoo', 'ba', '123') AS "ya123da123doo"; + ya123da123doo +--------------- + ya123da123doo +(1 row) + +SELECT replace('yabadoo', 'bad', '') AS "yaoo"; + yaoo +------ + yaoo +(1 row) + +-- +-- test split_part +-- +select split_part('','@',1) AS "empty string"; + empty string +-------------- + +(1 row) + +select split_part('','@',-1) AS "empty string"; + empty string +-------------- + +(1 row) + +select split_part('joeuser@mydatabase','',1) AS "joeuser@mydatabase"; + joeuser@mydatabase +-------------------- + joeuser@mydatabase +(1 row) + +select split_part('joeuser@mydatabase','',2) AS "empty string"; + empty string +-------------- + +(1 row) + +select split_part('joeuser@mydatabase','',-1) AS "joeuser@mydatabase"; + joeuser@mydatabase +-------------------- + joeuser@mydatabase +(1 row) + +select split_part('joeuser@mydatabase','',-2) AS "empty string"; + empty string +-------------- + +(1 row) + +select split_part('joeuser@mydatabase','@',0) AS "an error"; +ERROR: field position must not be zero +select split_part('joeuser@mydatabase','@@',1) AS "joeuser@mydatabase"; + joeuser@mydatabase +-------------------- + joeuser@mydatabase +(1 row) + +select split_part('joeuser@mydatabase','@@',2) AS "empty string"; + empty string +-------------- + +(1 row) + +select split_part('joeuser@mydatabase','@',1) AS "joeuser"; + joeuser +--------- + joeuser +(1 row) + +select split_part('joeuser@mydatabase','@',2) AS "mydatabase"; + mydatabase +------------ + mydatabase +(1 row) + +select split_part('joeuser@mydatabase','@',3) AS "empty string"; + empty string +-------------- + +(1 row) + +select split_part('@joeuser@mydatabase@','@',2) AS "joeuser"; + joeuser +--------- + joeuser +(1 row) + +select split_part('joeuser@mydatabase','@',-1) AS "mydatabase"; + mydatabase +------------ + mydatabase +(1 row) + +select split_part('joeuser@mydatabase','@',-2) AS "joeuser"; + joeuser +--------- + joeuser +(1 row) + +select split_part('joeuser@mydatabase','@',-3) AS "empty string"; + empty string +-------------- + +(1 row) + +select split_part('@joeuser@mydatabase@','@',-2) AS "mydatabase"; + mydatabase +------------ + mydatabase +(1 row) + +-- +-- test to_hex +-- +select to_hex(256*256*256 - 1) AS "ffffff"; + ffffff +-------- + ffffff +(1 row) + +select to_hex(256::bigint*256::bigint*256::bigint*256::bigint - 1) AS "ffffffff"; + ffffffff +---------- + ffffffff +(1 row) + +-- +-- MD5 test suite - from IETF RFC 1321 +-- (see: ftp://ftp.rfc-editor.org/in-notes/rfc1321.txt) +-- +select md5('') = 'd41d8cd98f00b204e9800998ecf8427e' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('a') = '0cc175b9c0f1b6a831c399e269772661' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('abc') = '900150983cd24fb0d6963f7d28e17f72' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('message digest') = 'f96b697d7cb7938d525a2f31aaf161d0' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('abcdefghijklmnopqrstuvwxyz') = 'c3fcd3d76192e4007dfb496cca67e13b' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') = 'd174ab98d277d9f5a5611c2c9f419d9f' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('12345678901234567890123456789012345678901234567890123456789012345678901234567890') = '57edf4a22be3c955ac49da2e2107b67a' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5(''::bytea) = 'd41d8cd98f00b204e9800998ecf8427e' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('a'::bytea) = '0cc175b9c0f1b6a831c399e269772661' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('abc'::bytea) = '900150983cd24fb0d6963f7d28e17f72' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('message digest'::bytea) = 'f96b697d7cb7938d525a2f31aaf161d0' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('abcdefghijklmnopqrstuvwxyz'::bytea) = 'c3fcd3d76192e4007dfb496cca67e13b' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'::bytea) = 'd174ab98d277d9f5a5611c2c9f419d9f' AS "TRUE"; + TRUE +------ + t +(1 row) + +select md5('12345678901234567890123456789012345678901234567890123456789012345678901234567890'::bytea) = '57edf4a22be3c955ac49da2e2107b67a' AS "TRUE"; + TRUE +------ + t +(1 row) + +-- +-- SHA-2 +-- +SET bytea_output TO hex; +SELECT sha224(''); + sha224 +------------------------------------------------------------ + \xd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f +(1 row) + +SELECT sha224('The quick brown fox jumps over the lazy dog.'); + sha224 +------------------------------------------------------------ + \x619cba8e8e05826e9b8c519c0a5c68f4fb653e8a3d8aa04bb2c8cd4c +(1 row) + +SELECT sha256(''); + sha256 +-------------------------------------------------------------------- + \xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +(1 row) + +SELECT sha256('The quick brown fox jumps over the lazy dog.'); + sha256 +-------------------------------------------------------------------- + \xef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c +(1 row) + +SELECT sha384(''); + sha384 +---------------------------------------------------------------------------------------------------- + \x38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b +(1 row) + +SELECT sha384('The quick brown fox jumps over the lazy dog.'); + sha384 +---------------------------------------------------------------------------------------------------- + \xed892481d8272ca6df370bf706e4d7bc1b5739fa2177aae6c50e946678718fc67a7af2819a021c2fc34e91bdb63409d7 +(1 row) + +SELECT sha512(''); + sha512 +------------------------------------------------------------------------------------------------------------------------------------ + \xcf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e +(1 row) + +SELECT sha512('The quick brown fox jumps over the lazy dog.'); + sha512 +------------------------------------------------------------------------------------------------------------------------------------ + \x91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed +(1 row) + +-- +-- encode/decode +-- +SELECT encode('\x1234567890abcdef00', 'hex'); + encode +-------------------- + 1234567890abcdef00 +(1 row) + +SELECT decode('1234567890abcdef00', 'hex'); + decode +---------------------- + \x1234567890abcdef00 +(1 row) + +SELECT encode(('\x' || repeat('1234567890abcdef0001', 7))::bytea, 'base64'); + encode +------------------------------------------------------------------------------ + EjRWeJCrze8AARI0VniQq83vAAESNFZ4kKvN7wABEjRWeJCrze8AARI0VniQq83vAAESNFZ4kKvN+ + 7wABEjRWeJCrze8AAQ== +(1 row) + +SELECT decode(encode(('\x' || repeat('1234567890abcdef0001', 7))::bytea, + 'base64'), 'base64'); + decode +------------------------------------------------------------------------------------------------------------------------------------------------ + \x1234567890abcdef00011234567890abcdef00011234567890abcdef00011234567890abcdef00011234567890abcdef00011234567890abcdef00011234567890abcdef0001 +(1 row) + +SELECT encode('\x1234567890abcdef00', 'escape'); + encode +----------------------------- + \x124Vx\220\253\315\357\000 +(1 row) + +SELECT decode(encode('\x1234567890abcdef00', 'escape'), 'escape'); + decode +---------------------- + \x1234567890abcdef00 +(1 row) + +-- +-- get_bit/set_bit etc +-- +SELECT get_bit('\x1234567890abcdef00'::bytea, 43); + get_bit +--------- + 1 +(1 row) + +SELECT get_bit('\x1234567890abcdef00'::bytea, 99); -- error +ERROR: index 99 out of valid range, 0..71 +SELECT set_bit('\x1234567890abcdef00'::bytea, 43, 0); + set_bit +---------------------- + \x1234567890a3cdef00 +(1 row) + +SELECT set_bit('\x1234567890abcdef00'::bytea, 99, 0); -- error +ERROR: index 99 out of valid range, 0..71 +SELECT get_byte('\x1234567890abcdef00'::bytea, 3); + get_byte +---------- + 120 +(1 row) + +SELECT get_byte('\x1234567890abcdef00'::bytea, 99); -- error +ERROR: index 99 out of valid range, 0..8 +SELECT set_byte('\x1234567890abcdef00'::bytea, 7, 11); + set_byte +---------------------- + \x1234567890abcd0b00 +(1 row) + +SELECT set_byte('\x1234567890abcdef00'::bytea, 99, 11); -- error +ERROR: index 99 out of valid range, 0..8 +-- +-- test behavior of escape_string_warning and standard_conforming_strings options +-- +set escape_string_warning = off; +set standard_conforming_strings = off; +show escape_string_warning; + escape_string_warning +----------------------- + off +(1 row) + +show standard_conforming_strings; + standard_conforming_strings +----------------------------- + off +(1 row) + +set escape_string_warning = on; +set standard_conforming_strings = on; +show escape_string_warning; + escape_string_warning +----------------------- + on +(1 row) + +show standard_conforming_strings; + standard_conforming_strings +----------------------------- + on +(1 row) + +select 'a\bcd' as f1, 'a\b''cd' as f2, 'a\b''''cd' as f3, 'abcd\' as f4, 'ab\''cd' as f5, '\\' as f6; + f1 | f2 | f3 | f4 | f5 | f6 +-------+--------+---------+-------+--------+---- + a\bcd | a\b'cd | a\b''cd | abcd\ | ab\'cd | \\ +(1 row) + +set standard_conforming_strings = off; +select 'a\\bcd' as f1, 'a\\b\'cd' as f2, 'a\\b\'''cd' as f3, 'abcd\\' as f4, 'ab\\\'cd' as f5, '\\\\' as f6; +WARNING: nonstandard use of \\ in a string literal +LINE 1: select 'a\\bcd' as f1, 'a\\b\'cd' as f2, 'a\\b\'''cd' as f3,... + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +WARNING: nonstandard use of \\ in a string literal +LINE 1: select 'a\\bcd' as f1, 'a\\b\'cd' as f2, 'a\\b\'''cd' as f3,... + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +WARNING: nonstandard use of \\ in a string literal +LINE 1: select 'a\\bcd' as f1, 'a\\b\'cd' as f2, 'a\\b\'''cd' as f3,... + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +WARNING: nonstandard use of \\ in a string literal +LINE 1: ...bcd' as f1, 'a\\b\'cd' as f2, 'a\\b\'''cd' as f3, 'abcd\\' ... + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +WARNING: nonstandard use of \\ in a string literal +LINE 1: ...'cd' as f2, 'a\\b\'''cd' as f3, 'abcd\\' as f4, 'ab\\\'cd'... + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +WARNING: nonstandard use of \\ in a string literal +LINE 1: ...'''cd' as f3, 'abcd\\' as f4, 'ab\\\'cd' as f5, '\\\\' as ... + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. + f1 | f2 | f3 | f4 | f5 | f6 +-------+--------+---------+-------+--------+---- + a\bcd | a\b'cd | a\b''cd | abcd\ | ab\'cd | \\ +(1 row) + +set escape_string_warning = off; +set standard_conforming_strings = on; +select 'a\bcd' as f1, 'a\b''cd' as f2, 'a\b''''cd' as f3, 'abcd\' as f4, 'ab\''cd' as f5, '\\' as f6; + f1 | f2 | f3 | f4 | f5 | f6 +-------+--------+---------+-------+--------+---- + a\bcd | a\b'cd | a\b''cd | abcd\ | ab\'cd | \\ +(1 row) + +set standard_conforming_strings = off; +select 'a\\bcd' as f1, 'a\\b\'cd' as f2, 'a\\b\'''cd' as f3, 'abcd\\' as f4, 'ab\\\'cd' as f5, '\\\\' as f6; + f1 | f2 | f3 | f4 | f5 | f6 +-------+--------+---------+-------+--------+---- + a\bcd | a\b'cd | a\b''cd | abcd\ | ab\'cd | \\ +(1 row) + +reset standard_conforming_strings; +-- +-- Additional string functions +-- +SET bytea_output TO escape; +SELECT initcap('hi THOMAS'); + initcap +----------- + Hi Thomas +(1 row) + +SELECT lpad('hi', 5, 'xy'); + lpad +------- + xyxhi +(1 row) + +SELECT lpad('hi', 5); + lpad +------- + hi +(1 row) + +SELECT lpad('hi', -5, 'xy'); + lpad +------ + +(1 row) + +SELECT lpad('hello', 2); + lpad +------ + he +(1 row) + +SELECT lpad('hi', 5, ''); + lpad +------ + hi +(1 row) + +SELECT rpad('hi', 5, 'xy'); + rpad +------- + hixyx +(1 row) + +SELECT rpad('hi', 5); + rpad +------- + hi +(1 row) + +SELECT rpad('hi', -5, 'xy'); + rpad +------ + +(1 row) + +SELECT rpad('hello', 2); + rpad +------ + he +(1 row) + +SELECT rpad('hi', 5, ''); + rpad +------ + hi +(1 row) + +SELECT ltrim('zzzytrim', 'xyz'); + ltrim +------- + trim +(1 row) + +SELECT translate('', '14', 'ax'); + translate +----------- + +(1 row) + +SELECT translate('12345', '14', 'ax'); + translate +----------- + a23x5 +(1 row) + +SELECT ascii('x'); + ascii +------- + 120 +(1 row) + +SELECT ascii(''); + ascii +------- + 0 +(1 row) + +SELECT chr(65); + chr +----- + A +(1 row) + +SELECT chr(0); +ERROR: null character not permitted +SELECT repeat('Pg', 4); + repeat +---------- + PgPgPgPg +(1 row) + +SELECT repeat('Pg', -4); + repeat +-------- + +(1 row) + +SELECT SUBSTRING('1234567890'::bytea FROM 3) "34567890"; + 34567890 +---------- + 34567890 +(1 row) + +SELECT SUBSTRING('1234567890'::bytea FROM 4 FOR 3) AS "456"; + 456 +----- + 456 +(1 row) + +SELECT SUBSTRING('string'::bytea FROM 2 FOR 2147483646) AS "tring"; + tring +------- + tring +(1 row) + +SELECT SUBSTRING('string'::bytea FROM -10 FOR 2147483646) AS "string"; + string +-------- + string +(1 row) + +SELECT SUBSTRING('string'::bytea FROM -10 FOR -2147483646) AS "error"; +ERROR: negative substring length not allowed +SELECT trim(E'\\000'::bytea from E'\\000Tom\\000'::bytea); + btrim +------- + Tom +(1 row) + +SELECT trim(leading E'\\000'::bytea from E'\\000Tom\\000'::bytea); + ltrim +--------- + Tom\000 +(1 row) + +SELECT trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea); + rtrim +--------- + \000Tom +(1 row) + +SELECT btrim(E'\\000trim\\000'::bytea, E'\\000'::bytea); + btrim +------- + trim +(1 row) + +SELECT btrim(''::bytea, E'\\000'::bytea); + btrim +------- + +(1 row) + +SELECT btrim(E'\\000trim\\000'::bytea, ''::bytea); + btrim +-------------- + \000trim\000 +(1 row) + +SELECT encode(overlay(E'Th\\000omas'::bytea placing E'Th\\001omas'::bytea from 2),'escape'); + encode +------------- + TTh\x01omas +(1 row) + +SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 8),'escape'); + encode +-------------------- + Th\000omas\x02\x03 +(1 row) + +SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 5 for 3),'escape'); + encode +----------------- + Th\000o\x02\x03 +(1 row) + +SELECT bit_count('\x1234567890'::bytea); + bit_count +----------- + 15 +(1 row) + +SELECT unistr('\0064at\+0000610'); + unistr +-------- + data0 +(1 row) + +SELECT unistr('d\u0061t\U000000610'); + unistr +-------- + data0 +(1 row) + +SELECT unistr('a\\b'); + unistr +-------- + a\b +(1 row) + +-- errors: +SELECT unistr('wrong: \db99'); +ERROR: invalid Unicode surrogate pair +SELECT unistr('wrong: \db99\0061'); +ERROR: invalid Unicode surrogate pair +SELECT unistr('wrong: \+00db99\+000061'); +ERROR: invalid Unicode surrogate pair +SELECT unistr('wrong: \+2FFFFF'); +ERROR: invalid Unicode code point: 2FFFFF +SELECT unistr('wrong: \udb99\u0061'); +ERROR: invalid Unicode surrogate pair +SELECT unistr('wrong: \U0000db99\U00000061'); +ERROR: invalid Unicode surrogate pair +SELECT unistr('wrong: \U002FFFFF'); +ERROR: invalid Unicode code point: 2FFFFF +SELECT unistr('wrong: \xyz'); +ERROR: invalid Unicode escape +HINT: Unicode escapes must be \XXXX, \+XXXXXX, \uXXXX, or \UXXXXXXXX. diff --git a/src/test/regress/expected/yb.port.subselect.out b/src/test/regress/expected/yb.port.subselect.out new file mode 100644 index 0000000000000..8912f22ad8225 --- /dev/null +++ b/src/test/regress/expected/yb.port.subselect.out @@ -0,0 +1,30 @@ +-- +-- SUBSELECT +-- +-- another variant of that (bug #16213) +explain (verbose, costs off) +select * from +(values + (3 not in (select * from (values (1), (2)) ss1)), + (false) +) ss; + QUERY PLAN +-------------------------------------- + Values Scan on "*VALUES*_1" + Output: "*VALUES*_1".column1 + SubPlan 1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1 +(5 rows) + +select * from +(values + (3 not in (select * from (values (1), (2)) ss1)), + (false) +) ss; + column1 +--------- + t + f +(2 rows) + diff --git a/src/test/regress/expected/yb.port.test_setup.out b/src/test/regress/expected/yb.port.test_setup.out new file mode 100644 index 0000000000000..b8966ef0e80d8 --- /dev/null +++ b/src/test/regress/expected/yb.port.test_setup.out @@ -0,0 +1,232 @@ +-- +-- TEST_SETUP --- prepare environment expected by regression test scripts +-- +-- directory paths and dlsuffix are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX +\set regresslib :libdir '/regress' :dlsuffix +-- +-- Postgres formerly made the public schema read/write by default, +-- and most of the core regression tests still expect that. +-- +GRANT ALL ON SCHEMA public TO public; +-- +-- These tables have traditionally been referenced by many tests, +-- so create and populate them. Insert only non-error values here. +-- (Some subsequent tests try to insert erroneous values. That's okay +-- because the table won't actually change. Do not change the contents +-- of these tables in later tests, as it may affect other tests.) +-- +CREATE TABLE CHAR_TBL(f1 char(4)); +INSERT INTO CHAR_TBL (f1) VALUES + ('a'), + ('ab'), + ('abcd'), + ('abcd '); +VACUUM CHAR_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE FLOAT8_TBL(f1 float8); +INSERT INTO FLOAT8_TBL(f1) VALUES + ('0.0'), + ('-34.84'), + ('-1004.30'), + ('-1.2345678901234e+200'), + ('-1.2345678901234e-200'); +VACUUM FLOAT8_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE INT2_TBL(f1 int2); +INSERT INTO INT2_TBL(f1) VALUES + ('0 '), + (' 1234 '), + (' -1234'), + ('32767'), -- largest and smallest values + ('-32767'); +VACUUM INT2_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE INT4_TBL(f1 int4); +INSERT INTO INT4_TBL(f1) VALUES + (' 0 '), + ('123456 '), + (' -123456'), + ('2147483647'), -- largest and smallest values + ('-2147483647'); +VACUUM INT4_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE INT8_TBL(q1 int8, q2 int8); +INSERT INTO INT8_TBL VALUES + (' 123 ',' 456'), + ('123 ','4567890123456789'), + ('4567890123456789','123'), + (+4567890123456789,'4567890123456789'), + ('+4567890123456789','-4567890123456789'); +VACUUM INT8_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE POINT_TBL(f1 point); +INSERT INTO POINT_TBL(f1) VALUES + ('(0.0,0.0)'), + ('(-10.0,0.0)'), + ('(-3.0,4.0)'), + ('(5.1, 34.5)'), + ('(-5.0,-12.0)'), + ('(1e-300,-1e-300)'), -- To underflow + ('(1e+300,Inf)'), -- To overflow + ('(Inf,1e+300)'), -- Transposed + (' ( Nan , NaN ) '), + ('10.0,10.0'); +-- We intentionally don't vacuum point_tbl here; geometry depends on that +-- YB note: add primary key for sorting +CREATE TABLE TEXT_TBL (f1 text PRIMARY KEY); +INSERT INTO TEXT_TBL VALUES + ('doh!'), + ('hi de ho neighbor'); +VACUUM TEXT_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE VARCHAR_TBL(f1 varchar(4)); +INSERT INTO VARCHAR_TBL (f1) VALUES + ('a'), + ('ab'), + ('abcd'), + ('abcd '); +VACUUM VARCHAR_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE onek ( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +); +\set filename :abs_srcdir '/data/onek.data' +COPY onek FROM :'filename'; +VACUUM ANALYZE onek; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE onek2 AS SELECT * FROM onek; +VACUUM ANALYZE onek2; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE tenk1 ( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +); +\set filename :abs_srcdir '/data/tenk.data' +COPY tenk1 FROM :'filename'; +VACUUM ANALYZE tenk1; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE tenk2 AS SELECT * FROM tenk1; +VACUUM ANALYZE tenk2; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE person ( + name text, + age int4, + location point +); +\set filename :abs_srcdir '/data/person.data' +COPY person FROM :'filename'; +VACUUM ANALYZE person; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE emp ( + salary int4, + manager name +) INHERITS (person); +\set filename :abs_srcdir '/data/emp.data' +COPY emp FROM :'filename'; +VACUUM ANALYZE emp; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE student ( + gpa float8 +) INHERITS (person); +\set filename :abs_srcdir '/data/student.data' +COPY student FROM :'filename'; +VACUUM ANALYZE student; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE stud_emp ( + percent int4 +) INHERITS (emp, student); +NOTICE: merging multiple inherited definitions of column "name" +NOTICE: merging multiple inherited definitions of column "age" +NOTICE: merging multiple inherited definitions of column "location" +\set filename :abs_srcdir '/data/stud_emp.data' +COPY stud_emp FROM :'filename'; +CREATE TABLE road ( + name text, + thepath path +); +\set filename :abs_srcdir '/data/streets.data' +COPY road FROM :'filename'; +VACUUM ANALYZE road; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE ihighway () INHERITS (road); +INSERT INTO ihighway + SELECT * + FROM ONLY road + WHERE name ~ 'I- .*'; +VACUUM ANALYZE ihighway; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE shighway ( + surface text +) INHERITS (road); +INSERT INTO shighway + SELECT *, 'asphalt' + FROM ONLY road + WHERE name ~ 'State Hwy.*'; +VACUUM ANALYZE shighway; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +-- +-- Also create some non-built-in range types. +-- +create type float8range as range (subtype = float8, subtype_diff = float8mi); +create type textrange as range (subtype = text, collation = "C"); +-- +-- Create some C functions that will be used by various tests. +-- +CREATE FUNCTION binary_coercible(oid, oid) + RETURNS bool + AS :'regresslib', 'binary_coercible' + LANGUAGE C STRICT STABLE PARALLEL SAFE; +CREATE FUNCTION ttdummy () + RETURNS trigger + AS :'regresslib' + LANGUAGE C; +-- Use hand-rolled hash functions and operator classes to get predictable +-- result on different machines. The hash function for int4 simply returns +-- the sum of the values passed to it and the one for text returns the length +-- of the non-empty string value passed to it or 0. +create function part_hashint4_noop(value int4, seed int8) + returns int8 as $$ + select value + seed; + $$ language sql strict immutable parallel safe; +create operator class part_test_int4_ops for type int4 using hash as + operator 1 =, + function 2 part_hashint4_noop(int4, int8); +create function part_hashtext_length(value text, seed int8) + returns int8 as $$ + select length(coalesce(value, ''))::int8 + $$ language sql strict immutable parallel safe; +create operator class part_test_text_ops for type text using hash as + operator 1 =, + function 2 part_hashtext_length(text, int8); diff --git a/src/test/regress/expected/yb.port.test_setup_1.out b/src/test/regress/expected/yb.port.test_setup_1.out new file mode 100644 index 0000000000000..6226cd31f2ac7 --- /dev/null +++ b/src/test/regress/expected/yb.port.test_setup_1.out @@ -0,0 +1,262 @@ +-- +-- TEST_SETUP --- prepare environment expected by regression test scripts +-- +-- directory paths and dlsuffix are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX +\set regresslib :libdir '/regress' :dlsuffix +-- +-- Postgres formerly made the public schema read/write by default, +-- and most of the core regression tests still expect that. +-- +GRANT ALL ON SCHEMA public TO public; +-- +-- These tables have traditionally been referenced by many tests, +-- so create and populate them. Insert only non-error values here. +-- (Some subsequent tests try to insert erroneous values. That's okay +-- because the table won't actually change. Do not change the contents +-- of these tables in later tests, as it may affect other tests.) +-- +CREATE TABLE CHAR_TBL(f1 char(4)); +INSERT INTO CHAR_TBL (f1) VALUES + ('a'), + ('ab'), + ('abcd'), + ('abcd '); +VACUUM CHAR_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE FLOAT8_TBL(f1 float8); +INSERT INTO FLOAT8_TBL(f1) VALUES + ('0.0'), + ('-34.84'), + ('-1004.30'), + ('-1.2345678901234e+200'), + ('-1.2345678901234e-200'); +VACUUM FLOAT8_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE INT2_TBL(f1 int2); +INSERT INTO INT2_TBL(f1) VALUES + ('0 '), + (' 1234 '), + (' -1234'), + ('32767'), -- largest and smallest values + ('-32767'); +VACUUM INT2_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE INT4_TBL(f1 int4); +INSERT INTO INT4_TBL(f1) VALUES + (' 0 '), + ('123456 '), + (' -123456'), + ('2147483647'), -- largest and smallest values + ('-2147483647'); +VACUUM INT4_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE INT8_TBL(q1 int8, q2 int8); +INSERT INTO INT8_TBL VALUES + (' 123 ',' 456'), + ('123 ','4567890123456789'), + ('4567890123456789','123'), + (+4567890123456789,'4567890123456789'), + ('+4567890123456789','-4567890123456789'); +VACUUM INT8_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE POINT_TBL(f1 point); +INSERT INTO POINT_TBL(f1) VALUES + ('(0.0,0.0)'), + ('(-10.0,0.0)'), + ('(-3.0,4.0)'), + ('(5.1, 34.5)'), + ('(-5.0,-12.0)'), + ('(1e-300,-1e-300)'), -- To underflow + ('(1e+300,Inf)'), -- To overflow + ('(Inf,1e+300)'), -- Transposed + (' ( Nan , NaN ) '), + ('10.0,10.0'); +-- We intentionally don't vacuum point_tbl here; geometry depends on that +-- YB note: add primary key for sorting +CREATE TABLE TEXT_TBL (f1 text PRIMARY KEY); +INSERT INTO TEXT_TBL VALUES + ('doh!'), + ('hi de ho neighbor'); +VACUUM TEXT_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE VARCHAR_TBL(f1 varchar(4)); +INSERT INTO VARCHAR_TBL (f1) VALUES + ('a'), + ('ab'), + ('abcd'), + ('abcd '); +VACUUM VARCHAR_TBL; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE onek ( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +); +\set filename :abs_srcdir '/data/onek.data' +COPY onek FROM :'filename'; +VACUUM ANALYZE onek; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE onek2 AS SELECT * FROM onek; +VACUUM ANALYZE onek2; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE tenk1 ( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +); +\set filename :abs_srcdir '/data/tenk.data' +COPY tenk1 FROM :'filename'; +VACUUM ANALYZE tenk1; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE tenk2 AS SELECT * FROM tenk1; +VACUUM ANALYZE tenk2; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE person ( + name text, + age int4, + location point +); +\set filename :abs_srcdir '/data/person.data' +COPY person FROM :'filename'; +VACUUM ANALYZE person; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE emp ( + salary int4, + manager name +) INHERITS (person); +ERROR: INHERITS not supported yet +LINE 4: ) INHERITS (person); + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1129. React with thumbs up to raise its priority +\set filename :abs_srcdir '/data/emp.data' +COPY emp FROM :'filename'; +ERROR: relation "emp" does not exist +VACUUM ANALYZE emp; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +ERROR: relation "emp" does not exist +CREATE TABLE student ( + gpa float8 +) INHERITS (person); +ERROR: INHERITS not supported yet +LINE 3: ) INHERITS (person); + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1129. React with thumbs up to raise its priority +\set filename :abs_srcdir '/data/student.data' +COPY student FROM :'filename'; +ERROR: relation "student" does not exist +VACUUM ANALYZE student; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +ERROR: relation "student" does not exist +CREATE TABLE stud_emp ( + percent int4 +) INHERITS (emp, student); +ERROR: INHERITS not supported yet +LINE 3: ) INHERITS (emp, student); + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1129. React with thumbs up to raise its priority +\set filename :abs_srcdir '/data/stud_emp.data' +COPY stud_emp FROM :'filename'; +ERROR: relation "stud_emp" does not exist +CREATE TABLE road ( + name text, + thepath path +); +\set filename :abs_srcdir '/data/streets.data' +COPY road FROM :'filename'; +VACUUM ANALYZE road; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +CREATE TABLE ihighway () INHERITS (road); +ERROR: INHERITS not supported yet +LINE 1: CREATE TABLE ihighway () INHERITS (road); + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1129. React with thumbs up to raise its priority +INSERT INTO ihighway + SELECT * + FROM ONLY road + WHERE name ~ 'I- .*'; +ERROR: relation "ihighway" does not exist +LINE 1: INSERT INTO ihighway + ^ +VACUUM ANALYZE ihighway; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +ERROR: relation "ihighway" does not exist +CREATE TABLE shighway ( + surface text +) INHERITS (road); +ERROR: INHERITS not supported yet +LINE 3: ) INHERITS (road); + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1129. React with thumbs up to raise its priority +INSERT INTO shighway + SELECT *, 'asphalt' + FROM ONLY road + WHERE name ~ 'State Hwy.*'; +ERROR: relation "shighway" does not exist +LINE 1: INSERT INTO shighway + ^ +VACUUM ANALYZE shighway; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically +ERROR: relation "shighway" does not exist +-- +-- Also create some non-built-in range types. +-- +create type float8range as range (subtype = float8, subtype_diff = float8mi); +create type textrange as range (subtype = text, collation = "C"); +-- +-- Create some C functions that will be used by various tests. +-- +CREATE FUNCTION binary_coercible(oid, oid) + RETURNS bool + AS :'regresslib', 'binary_coercible' + LANGUAGE C STRICT STABLE PARALLEL SAFE; +CREATE FUNCTION ttdummy () + RETURNS trigger + AS :'regresslib' + LANGUAGE C; +-- Use hand-rolled hash functions and operator classes to get predictable +-- result on different machines. The hash function for int4 simply returns +-- the sum of the values passed to it and the one for text returns the length +-- of the non-empty string value passed to it or 0. +create function part_hashint4_noop(value int4, seed int8) + returns int8 as $$ + select value + seed; + $$ language sql strict immutable parallel safe; +create operator class part_test_int4_ops for type int4 using hash as + operator 1 =, + function 2 part_hashint4_noop(int4, int8); +create function part_hashtext_length(value text, seed int8) + returns int8 as $$ + select length(coalesce(value, ''))::int8 + $$ language sql strict immutable parallel safe; +create operator class part_test_text_ops for type text using hash as + operator 1 =, + function 2 part_hashtext_length(text, int8); diff --git a/src/test/regress/expected/yb.port.text.out b/src/test/regress/expected/yb.port.text.out new file mode 100644 index 0000000000000..9a5f74380e8f5 --- /dev/null +++ b/src/test/regress/expected/yb.port.text.out @@ -0,0 +1,438 @@ +-- +-- TEXT +-- +SELECT text 'this is a text string' = text 'this is a text string' AS true; + true +------ + t +(1 row) + +SELECT text 'this is a text string' = text 'this is a text strin' AS false; + false +------- + f +(1 row) + +-- text_tbl was already created and filled in test_setup.sql. +SELECT '' AS two, * FROM TEXT_TBL; + two | f1 +-----+------------------- + | doh! + | hi de ho neighbor +(2 rows) + +-- As of 8.3 we have removed most implicit casts to text, so that for example +-- this no longer works: +select length(42); +ERROR: function length(integer) does not exist +LINE 1: select length(42); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +-- But as a special exception for usability's sake, we still allow implicit +-- casting to text in concatenations, so long as the other input is text or +-- an unknown literal. So these work: +select 'four: '::text || 2+2; + ?column? +---------- + four: 4 +(1 row) + +select 'four: ' || 2+2; + ?column? +---------- + four: 4 +(1 row) + +-- but not this: +select 3 || 4.0; +ERROR: operator does not exist: integer || numeric +LINE 1: select 3 || 4.0; + ^ +HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +/* + * various string functions + */ +select concat('one'); + concat +-------- + one +(1 row) + +select concat(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD')); + concat +---------------------- + 123hellotf03-09-2010 +(1 row) + +select concat_ws('#','one'); + concat_ws +----------- + one +(1 row) + +select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD')); + concat_ws +---------------------------- + 1#2#3#hello#t#f#03-09-2010 +(1 row) + +select concat_ws(',',10,20,null,30); + concat_ws +----------- + 10,20,30 +(1 row) + +select concat_ws('',10,20,null,30); + concat_ws +----------- + 102030 +(1 row) + +select concat_ws(NULL,10,20,null,30) is null; + ?column? +---------- + t +(1 row) + +select reverse('abcde'); + reverse +--------- + edcba +(1 row) + +select i, left('ahoj', i), right('ahoj', i) from generate_series(-5, 5) t(i) order by i; + i | left | right +----+------+------- + -5 | | + -4 | | + -3 | a | j + -2 | ah | oj + -1 | aho | hoj + 0 | | + 1 | a | j + 2 | ah | oj + 3 | aho | hoj + 4 | ahoj | ahoj + 5 | ahoj | ahoj +(11 rows) + +select quote_literal(''); + quote_literal +--------------- + '' +(1 row) + +select quote_literal('abc'''); + quote_literal +--------------- + 'abc''' +(1 row) + +select quote_literal(e'\\'); + quote_literal +--------------- + E'\\' +(1 row) + +-- check variadic labeled argument +select concat(variadic array[1,2,3]); + concat +-------- + 123 +(1 row) + +select concat_ws(',', variadic array[1,2,3]); + concat_ws +----------- + 1,2,3 +(1 row) + +select concat_ws(',', variadic NULL::int[]); + concat_ws +----------- + +(1 row) + +select concat(variadic NULL::int[]) is NULL; + ?column? +---------- + t +(1 row) + +select concat(variadic '{}'::int[]) = ''; + ?column? +---------- + t +(1 row) + +--should fail +select concat_ws(',', variadic 10); +ERROR: VARIADIC argument must be an array +LINE 1: select concat_ws(',', variadic 10); + ^ +/* + * format + */ +select format(NULL); + format +-------- + +(1 row) + +select format('Hello'); + format +-------- + Hello +(1 row) + +select format('Hello %s', 'World'); + format +------------- + Hello World +(1 row) + +select format('Hello %%'); + format +--------- + Hello % +(1 row) + +select format('Hello %%%%'); + format +---------- + Hello %% +(1 row) + +-- should fail +select format('Hello %s %s', 'World'); +ERROR: too few arguments for format() +select format('Hello %s'); +ERROR: too few arguments for format() +select format('Hello %x', 20); +ERROR: unrecognized format() type specifier "x" +HINT: For a single "%" use "%%". +-- check literal and sql identifiers +select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', 10, 'Hello'); + format +---------------------------------------- + INSERT INTO mytab VALUES('10','Hello') +(1 row) + +select format('%s%s%s','Hello', NULL,'World'); + format +------------ + HelloWorld +(1 row) + +select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', 10, NULL); + format +------------------------------------- + INSERT INTO mytab VALUES('10',NULL) +(1 row) + +select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', NULL, 'Hello'); + format +---------------------------------------- + INSERT INTO mytab VALUES(NULL,'Hello') +(1 row) + +-- should fail, sql identifier cannot be NULL +select format('INSERT INTO %I VALUES(%L,%L)', NULL, 10, 'Hello'); +ERROR: null values cannot be formatted as an SQL identifier +-- check positional placeholders +select format('%1$s %3$s', 1, 2, 3); + format +-------- + 1 3 +(1 row) + +select format('%1$s %12$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + format +-------- + 1 12 +(1 row) + +-- should fail +select format('%1$s %4$s', 1, 2, 3); +ERROR: too few arguments for format() +select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); +ERROR: too few arguments for format() +select format('%0$s', 'Hello'); +ERROR: format specifies argument 0, but arguments are numbered from 1 +select format('%*0$s', 'Hello'); +ERROR: format specifies argument 0, but arguments are numbered from 1 +select format('%1$', 1); +ERROR: unterminated format() type specifier +HINT: For a single "%" use "%%". +select format('%1$1', 1); +ERROR: unterminated format() type specifier +HINT: For a single "%" use "%%". +-- check mix of positional and ordered placeholders +select format('Hello %s %1$s %s', 'World', 'Hello again'); + format +------------------------------- + Hello World World Hello again +(1 row) + +select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again'); + format +-------------------------------------------------- + Hello World Hello again, Hello again Hello again +(1 row) + +-- check variadic labeled arguments +select format('%s, %s', variadic array['Hello','World']); + format +-------------- + Hello, World +(1 row) + +select format('%s, %s', variadic array[1, 2]); + format +-------- + 1, 2 +(1 row) + +select format('%s, %s', variadic array[true, false]); + format +-------- + t, f +(1 row) + +select format('%s, %s', variadic array[true, false]::text[]); + format +------------- + true, false +(1 row) + +-- check variadic with positional placeholders +select format('%2$s, %1$s', variadic array['first', 'second']); + format +--------------- + second, first +(1 row) + +select format('%2$s, %1$s', variadic array[1, 2]); + format +-------- + 2, 1 +(1 row) + +-- variadic argument can be array type NULL, but should not be referenced +select format('Hello', variadic NULL::int[]); + format +-------- + Hello +(1 row) + +-- variadic argument allows simulating more than FUNC_MAX_ARGS parameters +select format(string_agg('%s',','), variadic array_agg(i)) +from generate_series(1,200) g(i); + format +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200 +(1 row) + +-- check field widths and left, right alignment +select format('>>%10s<<', 'Hello'); + format +---------------- + >> Hello<< +(1 row) + +select format('>>%10s<<', NULL); + format +---------------- + >> << +(1 row) + +select format('>>%10s<<', ''); + format +---------------- + >> << +(1 row) + +select format('>>%-10s<<', ''); + format +---------------- + >> << +(1 row) + +select format('>>%-10s<<', 'Hello'); + format +---------------- + >>Hello << +(1 row) + +select format('>>%-10s<<', NULL); + format +---------------- + >> << +(1 row) + +select format('>>%1$10s<<', 'Hello'); + format +---------------- + >> Hello<< +(1 row) + +select format('>>%1$-10I<<', 'Hello'); + format +---------------- + >>"Hello" << +(1 row) + +select format('>>%2$*1$L<<', 10, 'Hello'); + format +---------------- + >> 'Hello'<< +(1 row) + +select format('>>%2$*1$L<<', 10, NULL); + format +---------------- + >> NULL<< +(1 row) + +select format('>>%2$*1$L<<', -10, NULL); + format +---------------- + >>NULL << +(1 row) + +select format('>>%*s<<', 10, 'Hello'); + format +---------------- + >> Hello<< +(1 row) + +select format('>>%*1$s<<', 10, 'Hello'); + format +---------------- + >> Hello<< +(1 row) + +select format('>>%-s<<', 'Hello'); + format +----------- + >>Hello<< +(1 row) + +select format('>>%10L<<', NULL); + format +---------------- + >> NULL<< +(1 row) + +select format('>>%2$*1$L<<', NULL, 'Hello'); + format +------------- + >>'Hello'<< +(1 row) + +select format('>>%2$*1$L<<', 0, 'Hello'); + format +------------- + >>'Hello'<< +(1 row) + diff --git a/src/test/regress/expected/yb.port.transactions.out b/src/test/regress/expected/yb.port.transactions.out new file mode 100644 index 0000000000000..c831cae1b5f3a --- /dev/null +++ b/src/test/regress/expected/yb.port.transactions.out @@ -0,0 +1,1109 @@ +-- +-- TRANSACTIONS +-- +BEGIN; +CREATE TABLE xacttest (a smallint, b real); +INSERT INTO xacttest VALUES + (56, 7.8), + (100, 99.097), + (0, 0.09561), + (42, 324.78); +INSERT INTO xacttest (a, b) VALUES (777, 777.777); +END; +-- should retrieve one value-- +SELECT a FROM xacttest WHERE a > 100; + a +----- + 777 +(1 row) + +BEGIN; +CREATE TABLE disappear (a int4); +DELETE FROM xacttest; +-- should be empty +SELECT * FROM xacttest; + a | b +---+--- +(0 rows) + +ABORT; +-- should not exist +SELECT count(oid) FROM pg_class WHERE relname = 'disappear'; -- YB: change to count, TODO: transactional DDL + count +------- + 1 +(1 row) + +-- should have members again +SELECT * FROM xacttest; + a | b +-----+--------- + 56 | 7.8 + 100 | 99.097 + 0 | 0.09561 + 42 | 324.78 + 777 | 777.777 +(5 rows) + +-- Read-only tests +CREATE TABLE writetest (a int); +CREATE TEMPORARY TABLE temptest (a int); +BEGIN; +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE; -- ok +ROLLBACK; BEGIN ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE; -- YB workaround of above failure +SELECT * FROM writetest; -- ok + a +--- +(0 rows) + +SET TRANSACTION READ WRITE; --fail +ERROR: transaction read-write mode must be set before any query +COMMIT; +BEGIN; +SET TRANSACTION READ ONLY; -- ok +SET TRANSACTION READ WRITE; -- ok +-- YB: above fails even without a savepoint in YB. If that is fixed, port remaining queries +COMMIT; +BEGIN; +SET TRANSACTION READ WRITE; -- ok +SAVEPOINT x; +SET TRANSACTION READ WRITE; -- ok +SET TRANSACTION READ ONLY; -- ok +SELECT * FROM writetest; -- ok + a +--- +(0 rows) + +SET TRANSACTION READ ONLY; -- ok +SET TRANSACTION READ WRITE; --fail +ERROR: cannot set transaction read-write mode inside a read-only transaction +COMMIT; +BEGIN; +SET TRANSACTION READ WRITE; -- ok +SAVEPOINT x; +SET TRANSACTION READ ONLY; -- ok +SELECT * FROM writetest; -- ok + a +--- +(0 rows) + +ROLLBACK TO SAVEPOINT x; +SHOW transaction_read_only; -- off + transaction_read_only +----------------------- + off +(1 row) + +SAVEPOINT y; +SET TRANSACTION READ ONLY; -- ok +SELECT * FROM writetest; -- ok + a +--- +(0 rows) + +RELEASE SAVEPOINT y; +SHOW transaction_read_only; -- off + transaction_read_only +----------------------- + off +(1 row) + +COMMIT; +SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY; +DROP TABLE writetest; -- fail +ERROR: cannot execute DROP TABLE in a read-only transaction +INSERT INTO writetest VALUES (1); -- fail +ERROR: cannot execute INSERT in a read-only transaction +SELECT * FROM writetest; -- ok + a +--- +(0 rows) + +DELETE FROM temptest; -- ok +UPDATE temptest SET a = 0 FROM writetest WHERE temptest.a = 1 AND writetest.a = temptest.a; -- ok +PREPARE test AS UPDATE writetest SET a = 0; -- ok +EXECUTE test; -- fail +ERROR: cannot execute UPDATE in a read-only transaction +SELECT * FROM writetest, temptest; -- ok + a | a +---+--- +(0 rows) + +CREATE TABLE test AS SELECT * FROM writetest; -- fail +ERROR: cannot execute CREATE TABLE AS in a read-only transaction +START TRANSACTION READ WRITE; +DROP TABLE writetest; -- ok +COMMIT; +-- Subtransactions, basic tests +-- create & drop tables +SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE; +CREATE TABLE trans_foobar (a int); +BEGIN; + CREATE TABLE trans_foo (a int); + SAVEPOINT one; +/* YB: avoid DROP TABLE since it is not rolled back due to lack of transactional DDL + DROP TABLE trans_foo; +*/ -- YB + CREATE TABLE trans_bar (a int); + ROLLBACK TO SAVEPOINT one; + DROP TABLE trans_bar; -- YB: above CREATE TABLE is not rolled back due to lack of transactional DDL + RELEASE SAVEPOINT one; + SAVEPOINT two; + CREATE TABLE trans_baz (a int); + RELEASE SAVEPOINT two; + drop TABLE trans_foobar; + CREATE TABLE trans_barbaz (a int); +COMMIT; +-- should exist: trans_barbaz, trans_baz, trans_foo +SELECT * FROM trans_foo; -- should be empty + a +--- +(0 rows) + +SELECT * FROM trans_bar; -- shouldn't exist +ERROR: relation "trans_bar" does not exist +LINE 1: SELECT * FROM trans_bar; + ^ +SELECT * FROM trans_barbaz; -- should be empty + a +--- +(0 rows) + +SELECT * FROM trans_baz; -- should be empty + a +--- +(0 rows) + +-- inserts +BEGIN; + INSERT INTO trans_foo VALUES (1); + SAVEPOINT one; + INSERT into trans_bar VALUES (1); +ERROR: relation "trans_bar" does not exist +LINE 1: INSERT into trans_bar VALUES (1); + ^ + ROLLBACK TO one; + RELEASE SAVEPOINT one; + SAVEPOINT two; + INSERT into trans_barbaz VALUES (1); + RELEASE two; + SAVEPOINT three; + SAVEPOINT four; + INSERT INTO trans_foo VALUES (2); + RELEASE SAVEPOINT four; + ROLLBACK TO SAVEPOINT three; + RELEASE SAVEPOINT three; + INSERT INTO trans_foo VALUES (3); +COMMIT; +SELECT * FROM trans_foo; -- should have 1 and 3 + a +--- + 1 + 3 +(2 rows) + +SELECT * FROM trans_barbaz; -- should have 1 + a +--- + 1 +(1 row) + +-- test whole-tree commit +BEGIN; + SAVEPOINT one; + SELECT trans_foo; +ERROR: column "trans_foo" does not exist +LINE 1: SELECT trans_foo; + ^ + ROLLBACK TO SAVEPOINT one; + RELEASE SAVEPOINT one; + SAVEPOINT two; + CREATE TABLE savepoints (a int); + SAVEPOINT three; + INSERT INTO savepoints VALUES (1); + SAVEPOINT four; + INSERT INTO savepoints VALUES (2); + SAVEPOINT five; + INSERT INTO savepoints VALUES (3); + ROLLBACK TO SAVEPOINT five; +COMMIT; +COMMIT; -- should not be in a transaction block +WARNING: there is no transaction in progress +SELECT * FROM savepoints; + a +--- + 1 + 2 +(2 rows) + +-- test whole-tree rollback +BEGIN; + SAVEPOINT one; + DELETE FROM savepoints WHERE a=1; + RELEASE SAVEPOINT one; + SAVEPOINT two; + DELETE FROM savepoints WHERE a=1; + SAVEPOINT three; + DELETE FROM savepoints WHERE a=2; +ROLLBACK; +COMMIT; -- should not be in a transaction block +WARNING: there is no transaction in progress +SELECT * FROM savepoints; + a +--- + 1 + 2 +(2 rows) + +-- test whole-tree commit on an aborted subtransaction +BEGIN; + INSERT INTO savepoints VALUES (4); + SAVEPOINT one; + INSERT INTO savepoints VALUES (5); + SELECT trans_foo; +ERROR: column "trans_foo" does not exist +LINE 1: SELECT trans_foo; + ^ +COMMIT; +SELECT * FROM savepoints; + a +--- + 1 + 2 +(2 rows) + +BEGIN; + INSERT INTO savepoints VALUES (6); + SAVEPOINT one; + INSERT INTO savepoints VALUES (7); + RELEASE SAVEPOINT one; + INSERT INTO savepoints VALUES (8); +COMMIT; +-- rows 6 and 8 should have been created by the same xact +SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=8; +ERROR: system column "xmin" is not supported yet +-- rows 6 and 7 should have been created by different xacts +SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=7; +ERROR: system column "xmin" is not supported yet +BEGIN; + INSERT INTO savepoints VALUES (9); + SAVEPOINT one; + INSERT INTO savepoints VALUES (10); + ROLLBACK TO SAVEPOINT one; + INSERT INTO savepoints VALUES (11); +COMMIT; +SELECT a FROM savepoints WHERE a in (9, 10, 11); + a +---- + 9 + 11 +(2 rows) + +-- rows 9 and 11 should have been created by different xacts +SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=9 AND b.a=11; +ERROR: system column "xmin" is not supported yet +BEGIN; + INSERT INTO savepoints VALUES (12); + SAVEPOINT one; + INSERT INTO savepoints VALUES (13); + SAVEPOINT two; + INSERT INTO savepoints VALUES (14); + ROLLBACK TO SAVEPOINT one; + INSERT INTO savepoints VALUES (15); + SAVEPOINT two; + INSERT INTO savepoints VALUES (16); + SAVEPOINT three; + INSERT INTO savepoints VALUES (17); +COMMIT; +SELECT a FROM savepoints WHERE a BETWEEN 12 AND 17; + a +---- + 12 + 15 + 16 + 17 +(4 rows) + +BEGIN; + INSERT INTO savepoints VALUES (18); + SAVEPOINT one; + INSERT INTO savepoints VALUES (19); + SAVEPOINT two; + INSERT INTO savepoints VALUES (20); + ROLLBACK TO SAVEPOINT one; + INSERT INTO savepoints VALUES (21); + ROLLBACK TO SAVEPOINT one; + INSERT INTO savepoints VALUES (22); +COMMIT; +SELECT a FROM savepoints WHERE a BETWEEN 18 AND 22; + a +---- + 18 + 22 +(2 rows) + +DROP TABLE savepoints; +-- only in a transaction block: +SAVEPOINT one; +ERROR: SAVEPOINT can only be used in transaction blocks +ROLLBACK TO SAVEPOINT one; +ERROR: ROLLBACK TO SAVEPOINT can only be used in transaction blocks +RELEASE SAVEPOINT one; +ERROR: RELEASE SAVEPOINT can only be used in transaction blocks +-- Only "rollback to" allowed in aborted state +BEGIN; + SAVEPOINT one; + SELECT 0/0; +ERROR: division by zero + SAVEPOINT two; -- ignored till the end of ... +ERROR: current transaction is aborted, commands ignored until end of transaction block + RELEASE SAVEPOINT one; -- ignored till the end of ... +ERROR: current transaction is aborted, commands ignored until end of transaction block + ROLLBACK TO SAVEPOINT one; + SELECT 1; + ?column? +---------- + 1 +(1 row) + +COMMIT; +SELECT 1; -- this should work + ?column? +---------- + 1 +(1 row) + +-- check non-transactional behavior of cursors +BEGIN; + DECLARE c CURSOR FOR SELECT unique2 FROM tenk1 ORDER BY unique2; + SAVEPOINT one; + FETCH 10 FROM c; + unique2 +--------- + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +(10 rows) + + ROLLBACK TO SAVEPOINT one; + FETCH 10 FROM c; + unique2 +--------- + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 +(10 rows) + + RELEASE SAVEPOINT one; + FETCH 10 FROM c; + unique2 +--------- + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 +(10 rows) + + CLOSE c; + DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1 ORDER BY unique2; + SAVEPOINT two; + FETCH 10 FROM c; +ERROR: division by zero + ROLLBACK TO SAVEPOINT two; + -- c is now dead to the world ... + FETCH 10 FROM c; +ERROR: portal "c" cannot be run + ROLLBACK TO SAVEPOINT two; + RELEASE SAVEPOINT two; + FETCH 10 FROM c; +ERROR: portal "c" cannot be run +COMMIT; +-- +-- Check that "stable" functions are really stable. They should not be +-- able to see the partial results of the calling query. (Ideally we would +-- also check that they don't see commits of concurrent transactions, but +-- that's a mite hard to do within the limitations of pg_regress.) +-- +select * from xacttest; + a | b +-----+--------- + 56 | 7.8 + 100 | 99.097 + 0 | 0.09561 + 42 | 324.78 + 777 | 777.777 +(5 rows) + +-- YB: imitate PG ctid reallocation for the following updates: row a=0 should be first: +create temp table ybtmp as select * from xacttest where a > 0; +delete from xacttest where a > 0; -- YB +insert into xacttest select * from ybtmp; +drop table ybtmp; +create or replace function max_xacttest() returns smallint language sql as +'select max(a) from xacttest' stable; +begin; +update xacttest set a = max_xacttest() + 10 where a > 0; +select * from xacttest; -- YB: TODO(#22947): fix output + a | b +-----+--------- + 0 | 0.09561 + 787 | 7.8 + 797 | 99.097 + 807 | 324.78 + 817 | 777.777 +(5 rows) + +rollback; +-- But a volatile function can see the partial results of the calling query +create or replace function max_xacttest() returns smallint language sql as +'select max(a) from xacttest' volatile; +begin; +update xacttest set a = max_xacttest() + 10 where a > 0; +select * from xacttest; + a | b +-----+--------- + 0 | 0.09561 + 787 | 7.8 + 797 | 99.097 + 807 | 324.78 + 817 | 777.777 +(5 rows) + +rollback; +-- Now the same test with plpgsql (since it depends on SPI which is different) +create or replace function max_xacttest() returns smallint language plpgsql as +'begin return max(a) from xacttest; end' stable; +begin; +update xacttest set a = max_xacttest() + 10 where a > 0; +select * from xacttest; -- YB: TODO(#22947): fix output + a | b +-----+--------- + 0 | 0.09561 + 787 | 7.8 + 797 | 99.097 + 807 | 324.78 + 817 | 777.777 +(5 rows) + +rollback; +create or replace function max_xacttest() returns smallint language plpgsql as +'begin return max(a) from xacttest; end' volatile; +begin; +update xacttest set a = max_xacttest() + 10 where a > 0; +select * from xacttest; + a | b +-----+--------- + 0 | 0.09561 + 787 | 7.8 + 797 | 99.097 + 807 | 324.78 + 817 | 777.777 +(5 rows) + +rollback; +-- test case for problems with dropping an open relation during abort +BEGIN; + savepoint x; + CREATE TABLE koju (a INT UNIQUE); + INSERT INTO koju VALUES (1); + INSERT INTO koju VALUES (1); +ERROR: duplicate key value violates unique constraint "koju_a_key" + rollback to x; + /* YB: above CREATE TABLE is not rolled back due to lack of transactional DDL + CREATE TABLE koju (a INT UNIQUE); + */ -- YB + INSERT INTO koju VALUES (1); + INSERT INTO koju VALUES (1); +ERROR: duplicate key value violates unique constraint "koju_a_key" +ROLLBACK; +DROP TABLE trans_foo; +DROP TABLE trans_baz; +DROP TABLE trans_barbaz; +-- test case for problems with revalidating an open relation during abort +create function inverse(int) returns float8 as +$$ +begin + analyze revalidate_bug; + return 1::float8/$1; +exception + when division_by_zero then return 0; +end$$ language plpgsql volatile; +create table revalidate_bug (c float8 unique); +insert into revalidate_bug values (1); +insert into revalidate_bug values (inverse(0)); +drop table revalidate_bug; +drop function inverse(int); +-- verify that cursors created during an aborted subtransaction are +-- closed, but that we do not rollback the effect of any FETCHs +-- performed in the aborted subtransaction +begin; +savepoint x; +create table abc (a int); +insert into abc values (5); +insert into abc values (10); +declare foo cursor for select * from abc; +fetch from foo; + a +--- + 5 +(1 row) + +rollback to x; +-- should fail +fetch from foo; +ERROR: cursor "foo" does not exist +commit; +drop table abc; -- YB: above CREATE TABLE is not rolled back due to lack of transactional DDL +begin; +create table abc (a int); +insert into abc values (5); +insert into abc values (10); +insert into abc values (15); +declare foo cursor for select * from abc; +fetch from foo; + a +--- + 5 +(1 row) + +savepoint x; +fetch from foo; + a +---- + 10 +(1 row) + +rollback to x; +fetch from foo; + a +---- + 15 +(1 row) + +abort; +drop table abc; -- YB: above CREATE TABLE is not rolled back due to lack of transactional DDL +-- Test for proper cleanup after a failure in a cursor portal +-- that was created in an outer subtransaction +CREATE FUNCTION invert(x float8) RETURNS float8 LANGUAGE plpgsql AS +$$ begin return 1/x; end $$; +CREATE FUNCTION create_temp_tab() RETURNS text +LANGUAGE plpgsql AS $$ +BEGIN + CREATE TEMP TABLE new_table (f1 float8); + -- case of interest is that we fail while holding an open + -- relcache reference to new_table + INSERT INTO new_table SELECT invert(0.0); + RETURN 'foo'; +END $$; +BEGIN; +DECLARE ok CURSOR FOR SELECT * FROM int8_tbl; +DECLARE ctt CURSOR FOR SELECT create_temp_tab(); +FETCH ok; + q1 | q2 +-----+----- + 123 | 456 +(1 row) + +SAVEPOINT s1; +FETCH ok; -- should work + q1 | q2 +-----+------------------ + 123 | 4567890123456789 +(1 row) + +FETCH ctt; -- error occurs here +ERROR: division by zero +CONTEXT: PL/pgSQL function invert(double precision) line 1 at RETURN +SQL statement "INSERT INTO new_table SELECT invert(0.0)" +PL/pgSQL function create_temp_tab() line 6 at SQL statement +ROLLBACK TO s1; +FETCH ok; -- should work + q1 | q2 +------------------+----- + 4567890123456789 | 123 +(1 row) + +FETCH ctt; -- must be rejected +ERROR: portal "ctt" cannot be run +COMMIT; +DROP FUNCTION create_temp_tab(); +DROP FUNCTION invert(x float8); +-- Tests for AND CHAIN +CREATE TABLE abc (a int); +-- set nondefault value so we have something to override below +SET default_transaction_read_only = on; +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +SHOW transaction_isolation; + transaction_isolation +----------------------- + repeatable read +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + on +(1 row) + +INSERT INTO abc VALUES (1); +INSERT INTO abc VALUES (2); +COMMIT AND CHAIN; -- TBLOCK_END +SHOW transaction_isolation; + transaction_isolation +----------------------- + repeatable read +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + on +(1 row) + +INSERT INTO abc VALUES ('error'); +ERROR: invalid input syntax for type integer: "error" +LINE 1: INSERT INTO abc VALUES ('error'); + ^ +INSERT INTO abc VALUES (3); -- check it's really aborted +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT AND CHAIN; -- TBLOCK_ABORT_END +SHOW transaction_isolation; + transaction_isolation +----------------------- + repeatable read +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + on +(1 row) + +INSERT INTO abc VALUES (4); +COMMIT; +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +SHOW transaction_isolation; + transaction_isolation +----------------------- + repeatable read +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + on +(1 row) + +SAVEPOINT x; +INSERT INTO abc VALUES ('error'); +ERROR: invalid input syntax for type integer: "error" +LINE 1: INSERT INTO abc VALUES ('error'); + ^ +COMMIT AND CHAIN; -- TBLOCK_ABORT_PENDING +SHOW transaction_isolation; + transaction_isolation +----------------------- + repeatable read +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + on +(1 row) + +INSERT INTO abc VALUES (5); +COMMIT; +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +SHOW transaction_isolation; + transaction_isolation +----------------------- + repeatable read +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + on +(1 row) + +SAVEPOINT x; +COMMIT AND CHAIN; -- TBLOCK_SUBCOMMIT +SHOW transaction_isolation; + transaction_isolation +----------------------- + repeatable read +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + on +(1 row) + +COMMIT; +-- different mix of options just for fun +START TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ WRITE, NOT DEFERRABLE; +SHOW transaction_isolation; + transaction_isolation +----------------------- + serializable +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + off +(1 row) + +INSERT INTO abc VALUES (6); +ROLLBACK AND CHAIN; -- TBLOCK_ABORT_PENDING +SHOW transaction_isolation; + transaction_isolation +----------------------- + serializable +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + off +(1 row) + +INSERT INTO abc VALUES ('error'); +ERROR: invalid input syntax for type integer: "error" +LINE 1: INSERT INTO abc VALUES ('error'); + ^ +ROLLBACK AND CHAIN; -- TBLOCK_ABORT_END +SHOW transaction_isolation; + transaction_isolation +----------------------- + serializable +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + off +(1 row) + +ROLLBACK; +-- not allowed outside a transaction block +COMMIT AND CHAIN; -- error +ERROR: COMMIT AND CHAIN can only be used in transaction blocks +ROLLBACK AND CHAIN; -- error +ERROR: ROLLBACK AND CHAIN can only be used in transaction blocks +SELECT * FROM abc ORDER BY 1; + a +--- + 1 + 2 + 4 + 5 +(4 rows) + +RESET default_transaction_read_only; +DROP TABLE abc; +-- Test assorted behaviors around the implicit transaction block created +-- when multiple SQL commands are sent in a single Query message. These +-- tests rely on the fact that psql will not break SQL commands apart at a +-- backslash-quoted semicolon, but will send them as one Query. +create temp table i_table (f1 int); +-- psql will show all results of a multi-statement Query +SELECT 1\; SELECT 2\; SELECT 3; + ?column? +---------- + 1 +(1 row) + + ?column? +---------- + 2 +(1 row) + + ?column? +---------- + 3 +(1 row) + +-- this implicitly commits: +insert into i_table values(1)\; select * from i_table; + f1 +---- + 1 +(1 row) + +-- 1/0 error will cause rolling back the whole implicit transaction +insert into i_table values(2)\; select * from i_table\; select 1/0; + f1 +---- + 1 + 2 +(2 rows) + +ERROR: division by zero +select * from i_table; + f1 +---- + 1 +(1 row) + +rollback; -- we are not in a transaction at this point +WARNING: there is no transaction in progress +-- can use regular begin/commit/rollback within a single Query +begin\; insert into i_table values(3)\; commit; +rollback; -- we are not in a transaction at this point +WARNING: there is no transaction in progress +begin\; insert into i_table values(4)\; rollback; +rollback; -- we are not in a transaction at this point +WARNING: there is no transaction in progress +-- begin converts implicit transaction into a regular one that +-- can extend past the end of the Query +select 1\; begin\; insert into i_table values(5); + ?column? +---------- + 1 +(1 row) + +commit; +select 1\; begin\; insert into i_table values(6); + ?column? +---------- + 1 +(1 row) + +rollback; +-- commit in implicit-transaction state commits but issues a warning. +insert into i_table values(7)\; commit\; insert into i_table values(8)\; select 1/0; +WARNING: there is no transaction in progress +ERROR: division by zero +-- similarly, rollback aborts but issues a warning. +insert into i_table values(9)\; rollback\; select 2; +WARNING: there is no transaction in progress + ?column? +---------- + 2 +(1 row) + +select * from i_table; + f1 +---- + 1 + 3 + 5 + 7 +(4 rows) + +rollback; -- we are not in a transaction at this point +WARNING: there is no transaction in progress +-- implicit transaction block is still a transaction block, for e.g. VACUUM +SELECT 1\; VACUUM; +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically + ?column? +---------- + 1 +(1 row) + +SELECT 1\; COMMIT\; VACUUM; +WARNING: there is no transaction in progress +NOTICE: VACUUM is a no-op statement since YugabyteDB performs garbage collection of dead tuples automatically + ?column? +---------- + 1 +(1 row) + +-- we disallow savepoint-related commands in implicit-transaction state +SELECT 1\; SAVEPOINT sp; + ?column? +---------- + 1 +(1 row) + +ERROR: SAVEPOINT can only be used in transaction blocks +SELECT 1\; COMMIT\; SAVEPOINT sp; +WARNING: there is no transaction in progress + ?column? +---------- + 1 +(1 row) + +ERROR: SAVEPOINT can only be used in transaction blocks +ROLLBACK TO SAVEPOINT sp\; SELECT 2; +ERROR: ROLLBACK TO SAVEPOINT can only be used in transaction blocks +SELECT 2\; RELEASE SAVEPOINT sp\; SELECT 3; + ?column? +---------- + 2 +(1 row) + +ERROR: RELEASE SAVEPOINT can only be used in transaction blocks +-- but this is OK, because the BEGIN converts it to a regular xact +SELECT 1\; BEGIN\; SAVEPOINT sp\; ROLLBACK TO SAVEPOINT sp\; COMMIT; + ?column? +---------- + 1 +(1 row) + +-- Tests for AND CHAIN in implicit transaction blocks +SET TRANSACTION READ ONLY\; COMMIT AND CHAIN; -- error +ERROR: COMMIT AND CHAIN can only be used in transaction blocks +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SET TRANSACTION READ ONLY\; ROLLBACK AND CHAIN; -- error +ERROR: ROLLBACK AND CHAIN can only be used in transaction blocks +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +CREATE TABLE abc (a int); +-- COMMIT/ROLLBACK + COMMIT/ROLLBACK AND CHAIN +INSERT INTO abc VALUES (7)\; COMMIT\; INSERT INTO abc VALUES (8)\; COMMIT AND CHAIN; -- 7 commit, 8 error +WARNING: there is no transaction in progress +ERROR: COMMIT AND CHAIN can only be used in transaction blocks +INSERT INTO abc VALUES (9)\; ROLLBACK\; INSERT INTO abc VALUES (10)\; ROLLBACK AND CHAIN; -- 9 rollback, 10 error +WARNING: there is no transaction in progress +ERROR: ROLLBACK AND CHAIN can only be used in transaction blocks +-- COMMIT/ROLLBACK AND CHAIN + COMMIT/ROLLBACK +INSERT INTO abc VALUES (11)\; COMMIT AND CHAIN\; INSERT INTO abc VALUES (12)\; COMMIT; -- 11 error, 12 not reached +ERROR: COMMIT AND CHAIN can only be used in transaction blocks +INSERT INTO abc VALUES (13)\; ROLLBACK AND CHAIN\; INSERT INTO abc VALUES (14)\; ROLLBACK; -- 13 error, 14 not reached +ERROR: ROLLBACK AND CHAIN can only be used in transaction blocks +-- START TRANSACTION + COMMIT/ROLLBACK AND CHAIN +START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO abc VALUES (15)\; COMMIT AND CHAIN; -- 15 ok +SHOW transaction_isolation; -- transaction is active at this point + transaction_isolation +----------------------- + repeatable read +(1 row) + +COMMIT; +START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO abc VALUES (16)\; ROLLBACK AND CHAIN; -- 16 ok +SHOW transaction_isolation; -- transaction is active at this point + transaction_isolation +----------------------- + repeatable read +(1 row) + +ROLLBACK; +SET default_transaction_isolation = 'read committed'; +-- START TRANSACTION + COMMIT/ROLLBACK + COMMIT/ROLLBACK AND CHAIN +START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO abc VALUES (17)\; COMMIT\; INSERT INTO abc VALUES (18)\; COMMIT AND CHAIN; -- 17 commit, 18 error +ERROR: COMMIT AND CHAIN can only be used in transaction blocks +SHOW transaction_isolation; -- out of transaction block + transaction_isolation +----------------------- + read committed +(1 row) + +START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO abc VALUES (19)\; ROLLBACK\; INSERT INTO abc VALUES (20)\; ROLLBACK AND CHAIN; -- 19 rollback, 20 error +ERROR: ROLLBACK AND CHAIN can only be used in transaction blocks +SHOW transaction_isolation; -- out of transaction block + transaction_isolation +----------------------- + read committed +(1 row) + +RESET default_transaction_isolation; +SELECT * FROM abc ORDER BY 1; + a +---- + 7 + 15 + 17 +(3 rows) + +DROP TABLE abc; +-- Test for successful cleanup of an aborted transaction at session exit. +-- THIS MUST BE THE LAST TEST IN THIS FILE. +begin; +select 1/0; +ERROR: division by zero +rollback to X; +ERROR: savepoint "x" does not exist +-- DO NOT ADD ANYTHING HERE. diff --git a/src/test/regress/expected/yb.port.triggers.out b/src/test/regress/expected/yb.port.triggers.out new file mode 100644 index 0000000000000..41a4378eade7b --- /dev/null +++ b/src/test/regress/expected/yb.port.triggers.out @@ -0,0 +1,3419 @@ +-- +-- TRIGGERS +-- +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX +\set autoinclib :libdir '/autoinc' :dlsuffix +\set refintlib :libdir '/refint' :dlsuffix +\set regresslib :libdir '/regress' :dlsuffix +CREATE FUNCTION autoinc () + RETURNS trigger + AS :'autoinclib' + LANGUAGE C; +CREATE FUNCTION check_primary_key () + RETURNS trigger + AS :'refintlib' + LANGUAGE C; +CREATE FUNCTION check_foreign_key () + RETURNS trigger + AS :'refintlib' + LANGUAGE C; +CREATE FUNCTION trigger_return_old () + RETURNS trigger + AS :'regresslib' + LANGUAGE C; +CREATE FUNCTION set_ttdummy (int4) + RETURNS int4 + AS :'regresslib' + LANGUAGE C STRICT; +create table pkeys (pkey1 int4 not null, pkey2 text not null); +create table fkeys (fkey1 int4, fkey2 text, fkey3 int); +create table fkeys2 (fkey21 int4, fkey22 text, pkey23 int not null); +create index fkeys_i on fkeys (fkey1, fkey2); +create index fkeys2_i on fkeys2 (fkey21, fkey22); +create index fkeys2p_i on fkeys2 (pkey23); +insert into pkeys values (10, '1'); +insert into pkeys values (20, '2'); +insert into pkeys values (30, '3'); +insert into pkeys values (40, '4'); +insert into pkeys values (50, '5'); +insert into pkeys values (60, '6'); +create unique index pkeys_i on pkeys (pkey1, pkey2); +-- +-- For fkeys: +-- (fkey1, fkey2) --> pkeys (pkey1, pkey2) +-- (fkey3) --> fkeys2 (pkey23) +-- +create trigger check_fkeys_pkey_exist + before insert or update on fkeys + for each row + execute function + check_primary_key ('fkey1', 'fkey2', 'pkeys', 'pkey1', 'pkey2'); +create trigger check_fkeys_pkey2_exist + before insert or update on fkeys + for each row + execute function check_primary_key ('fkey3', 'fkeys2', 'pkey23'); +-- +-- For fkeys2: +-- (fkey21, fkey22) --> pkeys (pkey1, pkey2) +-- +create trigger check_fkeys2_pkey_exist + before insert or update on fkeys2 + for each row + execute procedure + check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2'); +-- Test comments +COMMENT ON TRIGGER check_fkeys2_pkey_bad ON fkeys2 IS 'wrong'; +ERROR: trigger "check_fkeys2_pkey_bad" for table "fkeys2" does not exist +COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS 'right'; +COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS NULL; +-- +-- For pkeys: +-- ON DELETE/UPDATE (pkey1, pkey2) CASCADE: +-- fkeys (fkey1, fkey2) and fkeys2 (fkey21, fkey22) +-- +create trigger check_pkeys_fkey_cascade + before delete or update on pkeys + for each row + execute procedure + check_foreign_key (2, 'cascade', 'pkey1', 'pkey2', + 'fkeys', 'fkey1', 'fkey2', 'fkeys2', 'fkey21', 'fkey22'); +-- +-- For fkeys2: +-- ON DELETE/UPDATE (pkey23) RESTRICT: +-- fkeys (fkey3) +-- +create trigger check_fkeys2_fkey_restrict + before delete or update on fkeys2 + for each row + execute procedure check_foreign_key (1, 'restrict', 'pkey23', 'fkeys', 'fkey3'); +insert into fkeys2 values (10, '1', 1); +insert into fkeys2 values (30, '3', 2); +insert into fkeys2 values (40, '4', 5); +insert into fkeys2 values (50, '5', 3); +-- no key in pkeys +insert into fkeys2 values (70, '5', 3); +ERROR: tuple references non-existent key +DETAIL: Trigger "check_fkeys2_pkey_exist" found tuple referencing non-existent key in "pkeys". +insert into fkeys values (10, '1', 2); +insert into fkeys values (30, '3', 3); +insert into fkeys values (40, '4', 2); +insert into fkeys values (50, '5', 2); +-- no key in pkeys +insert into fkeys values (70, '5', 1); +ERROR: tuple references non-existent key +DETAIL: Trigger "check_fkeys_pkey_exist" found tuple referencing non-existent key in "pkeys". +-- no key in fkeys2 +insert into fkeys values (60, '6', 4); +ERROR: tuple references non-existent key +DETAIL: Trigger "check_fkeys_pkey2_exist" found tuple referencing non-existent key in "fkeys2". +delete from pkeys where pkey1 = 30 and pkey2 = '3'; +NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted +ERROR: "check_fkeys2_fkey_restrict": tuple is referenced in "fkeys" +CONTEXT: SQL statement "delete from fkeys2 where fkey21 = $1 and fkey22 = $2 " +delete from pkeys where pkey1 = 40 and pkey2 = '4'; +NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted +NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are deleted +update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 50 and pkey2 = '5'; +NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted +ERROR: "check_fkeys2_fkey_restrict": tuple is referenced in "fkeys" +CONTEXT: SQL statement "delete from fkeys2 where fkey21 = $1 and fkey22 = $2 " +update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 10 and pkey2 = '1'; +NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted +NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are deleted +SELECT trigger_name, event_manipulation, event_object_schema, event_object_table, + action_order, action_condition, action_orientation, action_timing, + action_reference_old_table, action_reference_new_table + FROM information_schema.triggers + WHERE event_object_table in ('pkeys', 'fkeys', 'fkeys2') + ORDER BY trigger_name COLLATE "C", 2; + trigger_name | event_manipulation | event_object_schema | event_object_table | action_order | action_condition | action_orientation | action_timing | action_reference_old_table | action_reference_new_table +----------------------------+--------------------+---------------------+--------------------+--------------+------------------+--------------------+---------------+----------------------------+---------------------------- + check_fkeys2_fkey_restrict | DELETE | public | fkeys2 | 1 | | ROW | BEFORE | | + check_fkeys2_fkey_restrict | UPDATE | public | fkeys2 | 1 | | ROW | BEFORE | | + check_fkeys2_pkey_exist | INSERT | public | fkeys2 | 1 | | ROW | BEFORE | | + check_fkeys2_pkey_exist | UPDATE | public | fkeys2 | 2 | | ROW | BEFORE | | + check_fkeys_pkey2_exist | INSERT | public | fkeys | 1 | | ROW | BEFORE | | + check_fkeys_pkey2_exist | UPDATE | public | fkeys | 1 | | ROW | BEFORE | | + check_fkeys_pkey_exist | INSERT | public | fkeys | 2 | | ROW | BEFORE | | + check_fkeys_pkey_exist | UPDATE | public | fkeys | 2 | | ROW | BEFORE | | + check_pkeys_fkey_cascade | DELETE | public | pkeys | 1 | | ROW | BEFORE | | + check_pkeys_fkey_cascade | UPDATE | public | pkeys | 1 | | ROW | BEFORE | | +(10 rows) + +DROP TABLE pkeys; +DROP TABLE fkeys; +DROP TABLE fkeys2; +-- Check behavior when trigger returns unmodified trigtuple +create table trigtest (f1 int, f2 text); +create trigger trigger_return_old + before insert or delete or update on trigtest + for each row execute procedure trigger_return_old(); +insert into trigtest values(1, 'foo'); +select * from trigtest; + f1 | f2 +----+----- + 1 | foo +(1 row) + +update trigtest set f2 = f2 || 'bar'; +select * from trigtest; + f1 | f2 +----+----- + 1 | foo +(1 row) + +delete from trigtest; +select * from trigtest; + f1 | f2 +----+---- +(0 rows) + +drop table trigtest; +create sequence ttdummy_seq increment 10 start 0 minvalue 0; +create table tttest ( + price_id int4, + price_val int4, + price_on int4, + price_off int4 default 999999 +); +create trigger ttdummy + before delete or update on tttest + for each row + execute procedure + ttdummy (price_on, price_off); +create trigger ttserial + before insert or update on tttest + for each row + execute procedure + autoinc (price_on, ttdummy_seq); +insert into tttest values (1, 1, null); +insert into tttest values (2, 2, null); +insert into tttest values (3, 3, 0); +select * from tttest order by price_id; + price_id | price_val | price_on | price_off +----------+-----------+----------+----------- + 1 | 1 | 10 | 999999 + 2 | 2 | 20 | 999999 + 3 | 3 | 30 | 999999 +(3 rows) + +delete from tttest where price_id = 2; +select * from tttest order by price_id; + price_id | price_val | price_on | price_off +----------+-----------+----------+----------- + 1 | 1 | 10 | 999999 + 2 | 2 | 20 | 40 + 3 | 3 | 30 | 999999 +(3 rows) + +-- what do we see ? +-- get current prices +select * from tttest where price_off = 999999 order by price_id; + price_id | price_val | price_on | price_off +----------+-----------+----------+----------- + 1 | 1 | 10 | 999999 + 3 | 3 | 30 | 999999 +(2 rows) + +-- change price for price_id == 3 +update tttest set price_val = 30 where price_id = 3 and price_on = 30; +select * from tttest order by price_id, price_val; + price_id | price_val | price_on | price_off +----------+-----------+----------+----------- + 1 | 1 | 10 | 999999 + 2 | 2 | 20 | 40 + 3 | 3 | 30 | 50 + 3 | 30 | 50 | 999999 +(4 rows) + +-- now we want to change price_id in ALL tuples +-- this gets us not what we need +update tttest set price_id = 5 where price_id = 3; +select * from tttest order by price_id, price_val; + price_id | price_val | price_on | price_off +----------+-----------+----------+----------- + 1 | 1 | 10 | 999999 + 2 | 2 | 20 | 40 + 3 | 3 | 30 | 50 + 3 | 30 | 50 | 60 + 5 | 30 | 60 | 999999 +(5 rows) + +-- restore data as before last update: +select set_ttdummy(0); + set_ttdummy +------------- + 1 +(1 row) + +delete from tttest where price_id = 5; +update tttest set price_off = 999999 where price_val = 30; +select * from tttest order by price_id, price_val; + price_id | price_val | price_on | price_off +----------+-----------+----------+----------- + 1 | 1 | 10 | 999999 + 2 | 2 | 20 | 40 + 3 | 3 | 30 | 50 + 3 | 30 | 50 | 999999 +(4 rows) + +-- and try change price_id now! +update tttest set price_id = 5 where price_id = 3; +select * from tttest order by price_id, price_val; + price_id | price_val | price_on | price_off +----------+-----------+----------+----------- + 1 | 1 | 10 | 999999 + 2 | 2 | 20 | 40 + 5 | 3 | 30 | 50 + 5 | 30 | 50 | 999999 +(4 rows) + +-- isn't it what we need ? +select set_ttdummy(1); + set_ttdummy +------------- + 0 +(1 row) + +-- we want to correct some "date" +update tttest set price_on = -1 where price_id = 1; +ERROR: ttdummy (tttest): you cannot change price_on and/or price_off columns (use set_ttdummy) +-- but this doesn't work +-- try in this way +select set_ttdummy(0); + set_ttdummy +------------- + 1 +(1 row) + +update tttest set price_on = -1 where price_id = 1; +select * from tttest order by price_id, price_val; + price_id | price_val | price_on | price_off +----------+-----------+----------+----------- + 1 | 1 | -1 | 999999 + 2 | 2 | 20 | 40 + 5 | 3 | 30 | 50 + 5 | 30 | 50 | 999999 +(4 rows) + +-- isn't it what we need ? +-- get price for price_id == 5 as it was @ "date" 35 +select * from tttest where price_on <= 35 and price_off > 35 and price_id = 5; + price_id | price_val | price_on | price_off +----------+-----------+----------+----------- + 5 | 3 | 30 | 50 +(1 row) + +drop table tttest; +drop sequence ttdummy_seq; +-- +-- tests for per-statement triggers +-- +CREATE TABLE log_table (tstamp timestamp default timeofday()::timestamp); +CREATE TABLE main_table (a int unique, b int); +COPY main_table (a,b) FROM stdin; +CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS ' +BEGIN + RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; + RETURN NULL; +END;'; +CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt'); +CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt'); +-- +-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified, +-- CREATE TRIGGER should default to 'FOR EACH STATEMENT' +-- +CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table +EXECUTE PROCEDURE trigger_func('after_upd_stmt'); +-- Both insert and update statement level triggers (before and after) should +-- fire. Doesn't fire UPDATE before trigger, but only because one isn't +-- defined. +INSERT INTO main_table (a, b) VALUES (5, 10) ON CONFLICT (a) + DO UPDATE SET b = EXCLUDED.b; +NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT +CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row'); +INSERT INTO main_table DEFAULT VALUES; +NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT +UPDATE main_table SET a = a + 1 WHERE b < 30; +NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +-- UPDATE that effects zero rows should still call per-statement trigger +UPDATE main_table SET a = a + 2 WHERE b > 100; +NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +-- constraint now unneeded +ALTER TABLE main_table DROP CONSTRAINT main_table_a_key; +-- COPY should fire per-row and per-statement INSERT triggers +COPY main_table (a, b) FROM stdin; +NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT +SELECT * FROM main_table ORDER BY a, b; + a | b +----+---- + 6 | 10 + 21 | 20 + 30 | 40 + 31 | 10 + 50 | 35 + 50 | 60 + 81 | 15 + | +(8 rows) + +-- +-- test triggers with WHEN clause +-- +CREATE TRIGGER modified_a BEFORE UPDATE OF a ON main_table +FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('modified_a'); +CREATE TRIGGER modified_any BEFORE UPDATE OF a ON main_table +FOR EACH ROW WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECUTE PROCEDURE trigger_func('modified_any'); +CREATE TRIGGER insert_a AFTER INSERT ON main_table +FOR EACH ROW WHEN (NEW.a = 123) EXECUTE PROCEDURE trigger_func('insert_a'); +CREATE TRIGGER delete_a AFTER DELETE ON main_table +FOR EACH ROW WHEN (OLD.a = 123) EXECUTE PROCEDURE trigger_func('delete_a'); +CREATE TRIGGER insert_when BEFORE INSERT ON main_table +FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('insert_when'); +CREATE TRIGGER delete_when AFTER DELETE ON main_table +FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('delete_when'); +SELECT trigger_name, event_manipulation, event_object_schema, event_object_table, + action_order, action_condition, action_orientation, action_timing, + action_reference_old_table, action_reference_new_table + FROM information_schema.triggers + WHERE event_object_table IN ('main_table') + ORDER BY trigger_name COLLATE "C", 2; + trigger_name | event_manipulation | event_object_schema | event_object_table | action_order | action_condition | action_orientation | action_timing | action_reference_old_table | action_reference_new_table +----------------------+--------------------+---------------------+--------------------+--------------+--------------------------------+--------------------+---------------+----------------------------+---------------------------- + after_ins_stmt_trig | INSERT | public | main_table | 1 | | STATEMENT | AFTER | | + after_upd_row_trig | UPDATE | public | main_table | 1 | | ROW | AFTER | | + after_upd_stmt_trig | UPDATE | public | main_table | 1 | | STATEMENT | AFTER | | + before_ins_stmt_trig | INSERT | public | main_table | 1 | | STATEMENT | BEFORE | | + delete_a | DELETE | public | main_table | 1 | (old.a = 123) | ROW | AFTER | | + delete_when | DELETE | public | main_table | 1 | true | STATEMENT | AFTER | | + insert_a | INSERT | public | main_table | 1 | (new.a = 123) | ROW | AFTER | | + insert_when | INSERT | public | main_table | 2 | true | STATEMENT | BEFORE | | + modified_a | UPDATE | public | main_table | 1 | (old.a <> new.a) | ROW | BEFORE | | + modified_any | UPDATE | public | main_table | 2 | (old.* IS DISTINCT FROM new.*) | ROW | BEFORE | | +(10 rows) + +INSERT INTO main_table (a) VALUES (123), (456); +NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(insert_when) called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(insert_a) called: action = INSERT, when = AFTER, level = ROW +NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT +COPY main_table FROM stdin; +NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(insert_when) called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(insert_a) called: action = INSERT, when = AFTER, level = ROW +NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT +DELETE FROM main_table WHERE a IN (123, 456); +NOTICE: trigger_func(delete_a) called: action = DELETE, when = AFTER, level = ROW +NOTICE: trigger_func(delete_a) called: action = DELETE, when = AFTER, level = ROW +NOTICE: trigger_func(delete_when) called: action = DELETE, when = AFTER, level = STATEMENT +SELECT * FROM main_table ORDER BY a, b; + a | b +----+---- + 6 | 10 + 21 | 20 + 30 | 40 + 31 | 10 + 50 | 35 + 50 | 60 + 81 | 15 + | +(8 rows) + +-- In YB we cannot rely on row order inside update so use multiple calls to check behavior. +-- Note: trigger_func returns null so if called as before-trigger it will cancel the op. +-- Update rows where 'a' is already 50 (no modify_a notices): +-- 1. expect 1 modify_any notice (there are 2 rows but one has b=60 already). +-- 2. expect 1 after_upd_row notice (for the b=60 row as the other is cancelled by the trigger). +-- 3. expect 1 after_upd_stmt notice (once per statement). +UPDATE main_table SET a = 50, b = 60 WHERE a = 50; +NOTICE: trigger_func(modified_any) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +-- Update rows where 'a' is not 50: +-- 1. expect 5 modify_a notices (one for each row). +-- 2. expect no modify_any or after_upd_row notices as modify_a trigger call cancels the op. +-- 3. expect 1 after_upd_stmt notice (once per statement). +UPDATE main_table SET a = 50, b = 60 WHERE a != 50; +NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +-- Check end state should be the same as start as trigger_func returns null cancelling row ops. +SELECT * FROM main_table ORDER BY a, b; + a | b +----+---- + 6 | 10 + 21 | 20 + 30 | 40 + 31 | 10 + 50 | 35 + 50 | 60 + 81 | 15 + | +(8 rows) + +SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a'; + pg_get_triggerdef +------------------------------------------------------------------------------------------------------------------------------------------- + CREATE TRIGGER modified_a BEFORE UPDATE OF a ON main_table FOR EACH ROW WHEN (old.a <> new.a) EXECUTE FUNCTION trigger_func('modified_a') +(1 row) + +SELECT pg_get_triggerdef(oid, false) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a'; + pg_get_triggerdef +---------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE TRIGGER modified_a BEFORE UPDATE OF a ON public.main_table FOR EACH ROW WHEN ((old.a <> new.a)) EXECUTE FUNCTION trigger_func('modified_a') +(1 row) + +SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_any'; + pg_get_triggerdef +------------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE TRIGGER modified_any BEFORE UPDATE OF a ON main_table FOR EACH ROW WHEN (old.* IS DISTINCT FROM new.*) EXECUTE FUNCTION trigger_func('modified_any') +(1 row) + +DROP TRIGGER modified_a ON main_table; +DROP TRIGGER modified_any ON main_table; +DROP TRIGGER insert_a ON main_table; +DROP TRIGGER delete_a ON main_table; +DROP TRIGGER insert_when ON main_table; +DROP TRIGGER delete_when ON main_table; +-- Test column-level triggers +DROP TRIGGER after_upd_row_trig ON main_table; +CREATE TRIGGER before_upd_a_row_trig BEFORE UPDATE OF a ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_a_row'); +CREATE TRIGGER after_upd_b_row_trig AFTER UPDATE OF b ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_b_row'); +CREATE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row'); +CREATE TRIGGER before_upd_a_stmt_trig BEFORE UPDATE OF a ON main_table +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_upd_a_stmt'); +CREATE TRIGGER after_upd_b_stmt_trig AFTER UPDATE OF b ON main_table +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_upd_b_stmt'); +SELECT pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'after_upd_a_b_row_trig'; + pg_get_triggerdef +------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON public.main_table FOR EACH ROW EXECUTE FUNCTION trigger_func('after_upd_a_b_row') +(1 row) + +UPDATE main_table SET a = 50; +NOTICE: trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +UPDATE main_table SET b = 10; +NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +-- +-- Test case for bug with BEFORE trigger followed by AFTER trigger with WHEN +-- +CREATE TABLE some_t (some_col boolean NOT NULL); +CREATE FUNCTION dummy_update_func() RETURNS trigger AS $$ +BEGIN + RAISE NOTICE 'dummy_update_func(%) called: action = %, old = %, new = %', + TG_ARGV[0], TG_OP, OLD, NEW; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER some_trig_before BEFORE UPDATE ON some_t FOR EACH ROW + EXECUTE PROCEDURE dummy_update_func('before'); +CREATE TRIGGER some_trig_aftera AFTER UPDATE ON some_t FOR EACH ROW + WHEN (NOT OLD.some_col AND NEW.some_col) + EXECUTE PROCEDURE dummy_update_func('aftera'); +CREATE TRIGGER some_trig_afterb AFTER UPDATE ON some_t FOR EACH ROW + WHEN (NOT NEW.some_col) + EXECUTE PROCEDURE dummy_update_func('afterb'); +INSERT INTO some_t VALUES (TRUE); +UPDATE some_t SET some_col = TRUE; +NOTICE: dummy_update_func(before) called: action = UPDATE, old = (t), new = (t) +UPDATE some_t SET some_col = FALSE; +NOTICE: dummy_update_func(before) called: action = UPDATE, old = (t), new = (f) +NOTICE: dummy_update_func(afterb) called: action = UPDATE, old = (t), new = (f) +UPDATE some_t SET some_col = TRUE; +NOTICE: dummy_update_func(before) called: action = UPDATE, old = (f), new = (t) +NOTICE: dummy_update_func(aftera) called: action = UPDATE, old = (f), new = (t) +DROP TABLE some_t; +-- bogus cases +CREATE TRIGGER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col'); +ERROR: duplicate trigger events specified at or near "ON" +LINE 1: ...ER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_ta... + ^ +CREATE TRIGGER error_upd_a_a BEFORE UPDATE OF a, a ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_a_a'); +ERROR: column "a" specified more than once +CREATE TRIGGER error_ins_a BEFORE INSERT OF a ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_ins_a'); +ERROR: syntax error at or near "OF" +LINE 1: CREATE TRIGGER error_ins_a BEFORE INSERT OF a ON main_table + ^ +CREATE TRIGGER error_ins_when BEFORE INSERT OR UPDATE ON main_table +FOR EACH ROW WHEN (OLD.a <> NEW.a) +EXECUTE PROCEDURE trigger_func('error_ins_old'); +ERROR: INSERT trigger's WHEN condition cannot reference OLD values +LINE 2: FOR EACH ROW WHEN (OLD.a <> NEW.a) + ^ +CREATE TRIGGER error_del_when BEFORE DELETE OR UPDATE ON main_table +FOR EACH ROW WHEN (OLD.a <> NEW.a) +EXECUTE PROCEDURE trigger_func('error_del_new'); +ERROR: DELETE trigger's WHEN condition cannot reference NEW values +LINE 2: FOR EACH ROW WHEN (OLD.a <> NEW.a) + ^ +CREATE TRIGGER error_del_when BEFORE INSERT OR UPDATE ON main_table +FOR EACH ROW WHEN (NEW.tableoid <> 0) +EXECUTE PROCEDURE trigger_func('error_when_sys_column'); +ERROR: BEFORE trigger's WHEN condition cannot reference NEW system columns +LINE 2: FOR EACH ROW WHEN (NEW.tableoid <> 0) + ^ +CREATE TRIGGER error_stmt_when BEFORE UPDATE OF a ON main_table +FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*) +EXECUTE PROCEDURE trigger_func('error_stmt_when'); +ERROR: statement trigger's WHEN condition cannot reference column values +LINE 2: FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*) + ^ +-- check dependency restrictions +\set VERBOSITY terse \\ -- suppress cascade details +ALTER TABLE main_table DROP COLUMN b; +ERROR: cannot drop column b of table main_table because other objects depend on it +\set VERBOSITY default +-- TODO Skip this test in YB as DDLs are not yet transactional #1383. +/* +-- this should succeed, but we'll roll it back to keep the triggers around +begin; +DROP TRIGGER after_upd_a_b_row_trig ON main_table; +DROP TRIGGER after_upd_b_row_trig ON main_table; +DROP TRIGGER after_upd_b_stmt_trig ON main_table; +ALTER TABLE main_table DROP COLUMN b; +rollback; +*/ +-- Test enable/disable triggers +create table trigtest (i serial primary key); +-- test that disabling RI triggers works +create table trigtest2 (i int references trigtest(i) on delete cascade); +create function trigtest() returns trigger as $$ +begin + raise notice '% % % %', TG_RELNAME, TG_OP, TG_WHEN, TG_LEVEL; + return new; +end;$$ language plpgsql; +create trigger trigtest_b_row_tg before insert or update or delete on trigtest +for each row execute procedure trigtest(); +create trigger trigtest_a_row_tg after insert or update or delete on trigtest +for each row execute procedure trigtest(); +create trigger trigtest_b_stmt_tg before insert or update or delete on trigtest +for each statement execute procedure trigtest(); +create trigger trigtest_a_stmt_tg after insert or update or delete on trigtest +for each statement execute procedure trigtest(); +insert into trigtest default values; +NOTICE: trigtest INSERT BEFORE STATEMENT +NOTICE: trigtest INSERT BEFORE ROW +NOTICE: trigtest INSERT AFTER ROW +NOTICE: trigtest INSERT AFTER STATEMENT +alter table trigtest disable trigger trigtest_b_row_tg; +insert into trigtest default values; +NOTICE: trigtest INSERT BEFORE STATEMENT +NOTICE: trigtest INSERT AFTER ROW +NOTICE: trigtest INSERT AFTER STATEMENT +alter table trigtest disable trigger user; +insert into trigtest default values; +alter table trigtest enable trigger trigtest_a_stmt_tg; +insert into trigtest default values; +NOTICE: trigtest INSERT AFTER STATEMENT +set session_replication_role = replica; +insert into trigtest default values; -- does not trigger +alter table trigtest enable always trigger trigtest_a_stmt_tg; +insert into trigtest default values; -- now it does +NOTICE: trigtest INSERT AFTER STATEMENT +reset session_replication_role; +insert into trigtest2 values(1); +insert into trigtest2 values(2); +delete from trigtest where i=2; +NOTICE: trigtest DELETE AFTER STATEMENT +select * from trigtest2; + i +--- + 1 +(1 row) + +alter table trigtest disable trigger all; +delete from trigtest where i=1; +select * from trigtest2; + i +--- + 1 +(1 row) + +-- ensure we still insert, even when all triggers are disabled +insert into trigtest default values; +select * from trigtest order by i; + i +--- + 3 + 4 + 5 + 6 + 7 +(5 rows) + +drop table trigtest2; +drop table trigtest; +-- dump trigger data +CREATE TABLE trigger_test ( + i int, + v varchar +); +CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger +LANGUAGE plpgsql AS $$ + +declare + + argstr text; + relid text; + +begin + + relid := TG_relid::regclass; + + -- plpgsql can't discover its trigger data in a hash like perl and python + -- can, or by a sort of reflection like tcl can, + -- so we have to hard code the names. + raise NOTICE 'TG_NAME: %', TG_name; + raise NOTICE 'TG_WHEN: %', TG_when; + raise NOTICE 'TG_LEVEL: %', TG_level; + raise NOTICE 'TG_OP: %', TG_op; + raise NOTICE 'TG_RELID::regclass: %', relid; + raise NOTICE 'TG_RELNAME: %', TG_relname; + raise NOTICE 'TG_TABLE_NAME: %', TG_table_name; + raise NOTICE 'TG_TABLE_SCHEMA: %', TG_table_schema; + raise NOTICE 'TG_NARGS: %', TG_nargs; + + argstr := '['; + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; + argstr := argstr || ']'; + raise NOTICE 'TG_ARGV: %', argstr; + + if TG_OP != 'INSERT' then + raise NOTICE 'OLD: %', OLD; + end if; + + if TG_OP != 'DELETE' then + raise NOTICE 'NEW: %', NEW; + end if; + + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; + +end; +$$; +CREATE TRIGGER show_trigger_data_trig +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +insert into trigger_test values(1,'insert'); +NOTICE: TG_NAME: show_trigger_data_trig +NOTICE: TG_WHEN: BEFORE +NOTICE: TG_LEVEL: ROW +NOTICE: TG_OP: INSERT +NOTICE: TG_RELID::regclass: trigger_test +NOTICE: TG_RELNAME: trigger_test +NOTICE: TG_TABLE_NAME: trigger_test +NOTICE: TG_TABLE_SCHEMA: public +NOTICE: TG_NARGS: 2 +NOTICE: TG_ARGV: [23, skidoo] +NOTICE: NEW: (1,insert) +update trigger_test set v = 'update' where i = 1; +NOTICE: TG_NAME: show_trigger_data_trig +NOTICE: TG_WHEN: BEFORE +NOTICE: TG_LEVEL: ROW +NOTICE: TG_OP: UPDATE +NOTICE: TG_RELID::regclass: trigger_test +NOTICE: TG_RELNAME: trigger_test +NOTICE: TG_TABLE_NAME: trigger_test +NOTICE: TG_TABLE_SCHEMA: public +NOTICE: TG_NARGS: 2 +NOTICE: TG_ARGV: [23, skidoo] +NOTICE: OLD: (1,insert) +NOTICE: NEW: (1,update) +delete from trigger_test; +NOTICE: TG_NAME: show_trigger_data_trig +NOTICE: TG_WHEN: BEFORE +NOTICE: TG_LEVEL: ROW +NOTICE: TG_OP: DELETE +NOTICE: TG_RELID::regclass: trigger_test +NOTICE: TG_RELNAME: trigger_test +NOTICE: TG_TABLE_NAME: trigger_test +NOTICE: TG_TABLE_SCHEMA: public +NOTICE: TG_NARGS: 2 +NOTICE: TG_ARGV: [23, skidoo] +NOTICE: OLD: (1,update) +DROP TRIGGER show_trigger_data_trig on trigger_test; +DROP FUNCTION trigger_data(); +DROP TABLE trigger_test; +-- +-- Test use of row comparisons on OLD/NEW +-- +-- In YB adding primary key to ensure f1 order is stable (ascending). +CREATE TABLE trigger_test (h int, f1 int, f2 text, f3 text, primary key (h HASH, f1 ASC)); +-- this is the obvious (and wrong...) way to compare rows +CREATE FUNCTION mytrigger() RETURNS trigger LANGUAGE plpgsql as $$ +begin + if row(old.*) = row(new.*) then + raise notice 'row % not changed', new.f1; + else + raise notice 'row % changed', new.f1; + end if; + return new; +end$$; +CREATE TRIGGER t +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE mytrigger(); +INSERT INTO trigger_test VALUES(1, 1, 'foo', 'bar'); +INSERT INTO trigger_test VALUES(1, 2, 'baz', 'quux'); +UPDATE trigger_test SET f3 = 'bar'; +NOTICE: row 1 not changed +NOTICE: row 2 changed +UPDATE trigger_test SET f3 = NULL; +NOTICE: row 1 changed +NOTICE: row 2 changed +-- this demonstrates that the above isn't really working as desired: +UPDATE trigger_test SET f3 = NULL; +NOTICE: row 1 changed +NOTICE: row 2 changed +-- the right way when considering nulls is +CREATE OR REPLACE FUNCTION mytrigger() RETURNS trigger LANGUAGE plpgsql as $$ +begin + if row(old.*) is distinct from row(new.*) then + raise notice 'row % changed', new.f1; + else + raise notice 'row % not changed', new.f1; + end if; + return new; +end$$; +UPDATE trigger_test SET f3 = 'bar'; +NOTICE: row 1 changed +NOTICE: row 2 changed +UPDATE trigger_test SET f3 = NULL; +NOTICE: row 1 changed +NOTICE: row 2 changed +UPDATE trigger_test SET f3 = NULL; +NOTICE: row 1 not changed +NOTICE: row 2 not changed +DROP TABLE trigger_test; +DROP FUNCTION mytrigger(); +-- Test snapshot management in serializable transactions involving triggers +-- per bug report in 6bc73d4c0910042358k3d1adff3qa36f8df75198ecea@mail.gmail.com +CREATE FUNCTION serializable_update_trig() RETURNS trigger LANGUAGE plpgsql AS +$$ +declare + rec record; +begin + new.description = 'updated in trigger'; + return new; +end; +$$; +CREATE TABLE serializable_update_tab ( + id int, + filler text, + description text +); +CREATE TRIGGER serializable_update_trig BEFORE UPDATE ON serializable_update_tab + FOR EACH ROW EXECUTE PROCEDURE serializable_update_trig(); +INSERT INTO serializable_update_tab SELECT a, repeat('xyzxz', 100), 'new' + FROM generate_series(1, 50) a; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +UPDATE serializable_update_tab SET description = 'no no', id = 1 WHERE id = 1; +COMMIT; +SELECT description FROM serializable_update_tab WHERE id = 1; + description +-------------------- + updated in trigger +(1 row) + +DROP TABLE serializable_update_tab; +-- minimal update trigger +CREATE TABLE min_updates_test ( + f1 text, + f2 int, + f3 int); +INSERT INTO min_updates_test VALUES ('a',1,2),('b','2',null); +CREATE TRIGGER z_min_update +BEFORE UPDATE ON min_updates_test +FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); +\set QUIET false +UPDATE min_updates_test SET f1 = f1; +UPDATE 0 +UPDATE min_updates_test SET f2 = f2 + 1; +UPDATE 2 +UPDATE min_updates_test SET f3 = 2 WHERE f3 is null; +UPDATE 1 +\set QUIET true +SELECT * FROM min_updates_test order by f1; + f1 | f2 | f3 +----+----+---- + a | 2 | 2 + b | 3 | 2 +(2 rows) + +DROP TABLE min_updates_test; +-- +-- Test triggers on views +-- +CREATE VIEW main_view AS SELECT a, b FROM main_table; +-- VIEW trigger function +CREATE OR REPLACE FUNCTION view_trigger() RETURNS trigger +LANGUAGE plpgsql AS $$ +declare + argstr text := ''; +begin + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; + + raise notice '% % % % (%)', TG_RELNAME, TG_WHEN, TG_OP, TG_LEVEL, argstr; + + if TG_LEVEL = 'ROW' then + if TG_OP = 'INSERT' then + raise NOTICE 'NEW: %', NEW; + INSERT INTO main_table VALUES (NEW.a, NEW.b); + RETURN NEW; + end if; + + if TG_OP = 'UPDATE' then + raise NOTICE 'OLD: %, NEW: %', OLD, NEW; + UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b; + if NOT FOUND then RETURN NULL; end if; + RETURN NEW; + end if; + + if TG_OP = 'DELETE' then + raise NOTICE 'OLD: %', OLD; + DELETE FROM main_table WHERE a = OLD.a AND b = OLD.b; + if NOT FOUND then RETURN NULL; end if; + RETURN OLD; + end if; + end if; + + RETURN NULL; +end; +$$; +-- Before row triggers aren't allowed on views +CREATE TRIGGER invalid_trig BEFORE INSERT ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row'); +ERROR: "main_view" is a view +DETAIL: Views cannot have row-level BEFORE or AFTER triggers. +CREATE TRIGGER invalid_trig BEFORE UPDATE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row'); +ERROR: "main_view" is a view +DETAIL: Views cannot have row-level BEFORE or AFTER triggers. +CREATE TRIGGER invalid_trig BEFORE DELETE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row'); +ERROR: "main_view" is a view +DETAIL: Views cannot have row-level BEFORE or AFTER triggers. +-- After row triggers aren't allowed on views +CREATE TRIGGER invalid_trig AFTER INSERT ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row'); +ERROR: "main_view" is a view +DETAIL: Views cannot have row-level BEFORE or AFTER triggers. +CREATE TRIGGER invalid_trig AFTER UPDATE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row'); +ERROR: "main_view" is a view +DETAIL: Views cannot have row-level BEFORE or AFTER triggers. +CREATE TRIGGER invalid_trig AFTER DELETE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row'); +ERROR: "main_view" is a view +DETAIL: Views cannot have row-level BEFORE or AFTER triggers. +-- Truncate triggers aren't allowed on views +CREATE TRIGGER invalid_trig BEFORE TRUNCATE ON main_view +EXECUTE PROCEDURE trigger_func('before_tru_row'); +ERROR: "main_view" is a view +DETAIL: Views cannot have TRUNCATE triggers. +CREATE TRIGGER invalid_trig AFTER TRUNCATE ON main_view +EXECUTE PROCEDURE trigger_func('before_tru_row'); +ERROR: "main_view" is a view +DETAIL: Views cannot have TRUNCATE triggers. +-- INSTEAD OF triggers aren't allowed on tables +CREATE TRIGGER invalid_trig INSTEAD OF INSERT ON main_table +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins'); +ERROR: "main_table" is a table +DETAIL: Tables cannot have INSTEAD OF triggers. +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_table +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd'); +ERROR: "main_table" is a table +DETAIL: Tables cannot have INSTEAD OF triggers. +CREATE TRIGGER invalid_trig INSTEAD OF DELETE ON main_table +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del'); +ERROR: "main_table" is a table +DETAIL: Tables cannot have INSTEAD OF triggers. +-- Don't support WHEN clauses with INSTEAD OF triggers +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view +FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE view_trigger('instead_of_upd'); +ERROR: INSTEAD OF triggers cannot have WHEN conditions +-- Don't support column-level INSTEAD OF triggers +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE OF a ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd'); +ERROR: INSTEAD OF triggers cannot have column lists +-- Don't support statement-level INSTEAD OF triggers +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view +EXECUTE PROCEDURE view_trigger('instead_of_upd'); +ERROR: INSTEAD OF triggers must be FOR EACH ROW +-- Valid INSTEAD OF triggers +CREATE TRIGGER instead_of_insert_trig INSTEAD OF INSERT ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins'); +CREATE TRIGGER instead_of_update_trig INSTEAD OF UPDATE ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd'); +CREATE TRIGGER instead_of_delete_trig INSTEAD OF DELETE ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del'); +-- Valid BEFORE statement VIEW triggers +CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_ins_stmt'); +CREATE TRIGGER before_upd_stmt_trig BEFORE UPDATE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_upd_stmt'); +CREATE TRIGGER before_del_stmt_trig BEFORE DELETE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_del_stmt'); +-- Valid AFTER statement VIEW triggers +CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_ins_stmt'); +CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_upd_stmt'); +CREATE TRIGGER after_del_stmt_trig AFTER DELETE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_del_stmt'); +\set QUIET false +-- Insert into view using trigger +INSERT INTO main_view VALUES (20, 30); +NOTICE: main_view BEFORE INSERT STATEMENT (before_view_ins_stmt) +NOTICE: main_view INSTEAD OF INSERT ROW (instead_of_ins) +NOTICE: NEW: (20,30) +NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT +NOTICE: main_view AFTER INSERT STATEMENT (after_view_ins_stmt) +INSERT 0 1 +INSERT INTO main_view VALUES (21, 31) RETURNING a, b; +NOTICE: main_view BEFORE INSERT STATEMENT (before_view_ins_stmt) +NOTICE: main_view INSTEAD OF INSERT ROW (instead_of_ins) +NOTICE: NEW: (21,31) +NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT +NOTICE: main_view AFTER INSERT STATEMENT (after_view_ins_stmt) + a | b +----+---- + 21 | 31 +(1 row) + +INSERT 0 1 +-- Table trigger will prevent updates +UPDATE main_view SET b = 31 WHERE a = 20; +NOTICE: main_view BEFORE UPDATE STATEMENT (before_view_upd_stmt) +NOTICE: main_view INSTEAD OF UPDATE ROW (instead_of_upd) +NOTICE: OLD: (20,30), NEW: (20,31) +NOTICE: trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +NOTICE: main_view AFTER UPDATE STATEMENT (after_view_upd_stmt) +UPDATE 0 +UPDATE main_view SET b = 32 WHERE a = 21 AND b = 31 RETURNING a, b; +NOTICE: main_view BEFORE UPDATE STATEMENT (before_view_upd_stmt) +NOTICE: main_view INSTEAD OF UPDATE ROW (instead_of_upd) +NOTICE: OLD: (21,31), NEW: (21,32) +NOTICE: trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW +NOTICE: trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +NOTICE: main_view AFTER UPDATE STATEMENT (after_view_upd_stmt) + a | b +---+--- +(0 rows) + +UPDATE 0 +-- Remove table trigger to allow updates +DROP TRIGGER before_upd_a_row_trig ON main_table; +DROP TRIGGER +UPDATE main_view SET b = 31 WHERE a = 20; +NOTICE: main_view BEFORE UPDATE STATEMENT (before_view_upd_stmt) +NOTICE: main_view INSTEAD OF UPDATE ROW (instead_of_upd) +NOTICE: OLD: (20,30), NEW: (20,31) +NOTICE: trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +NOTICE: main_view AFTER UPDATE STATEMENT (after_view_upd_stmt) +UPDATE 1 +UPDATE main_view SET b = 32 WHERE a = 21 AND b = 31 RETURNING a, b; +NOTICE: main_view BEFORE UPDATE STATEMENT (before_view_upd_stmt) +NOTICE: main_view INSTEAD OF UPDATE ROW (instead_of_upd) +NOTICE: OLD: (21,31), NEW: (21,32) +NOTICE: trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT +NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW +NOTICE: trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +NOTICE: main_view AFTER UPDATE STATEMENT (after_view_upd_stmt) + a | b +----+---- + 21 | 32 +(1 row) + +UPDATE 1 +SELECT * from main_view order by a, b; + a | b +----+---- + 6 | 10 + 20 | 31 + 21 | 10 + 21 | 32 + 30 | 10 + 31 | 10 + 50 | 10 + 50 | 10 + 81 | 10 + | 10 +(10 rows) + +-- Before and after stmt triggers should fire even when no rows are affected +UPDATE main_view SET b = 0 WHERE false; +NOTICE: main_view BEFORE UPDATE STATEMENT (before_view_upd_stmt) +NOTICE: main_view AFTER UPDATE STATEMENT (after_view_upd_stmt) +UPDATE 0 +-- Delete from view using trigger +DELETE FROM main_view WHERE a IN (10,20); +NOTICE: main_view BEFORE DELETE STATEMENT (before_view_del_stmt) +NOTICE: main_view INSTEAD OF DELETE ROW (instead_of_del) +NOTICE: OLD: (20,31) +NOTICE: main_view AFTER DELETE STATEMENT (after_view_del_stmt) +DELETE 1 +DELETE FROM main_view WHERE a = 31 RETURNING a, b; +NOTICE: main_view BEFORE DELETE STATEMENT (before_view_del_stmt) +NOTICE: main_view INSTEAD OF DELETE ROW (instead_of_del) +NOTICE: OLD: (31,10) +NOTICE: main_view AFTER DELETE STATEMENT (after_view_del_stmt) + a | b +----+---- + 31 | 10 +(1 row) + +DELETE 1 +SELECT * from main_view order by a, b; + a | b +----+---- + 6 | 10 + 21 | 10 + 21 | 32 + 30 | 10 + 50 | 10 + 50 | 10 + 81 | 10 + | 10 +(8 rows) + +\set QUIET true +-- Describe view should list triggers +\d main_view + View "public.main_view" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | +Triggers: + after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt') + after_ins_stmt_trig AFTER INSERT ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_ins_stmt') + after_upd_stmt_trig AFTER UPDATE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_upd_stmt') + before_del_stmt_trig BEFORE DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('before_view_del_stmt') + before_ins_stmt_trig BEFORE INSERT ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('before_view_ins_stmt') + before_upd_stmt_trig BEFORE UPDATE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('before_view_upd_stmt') + instead_of_delete_trig INSTEAD OF DELETE ON main_view FOR EACH ROW EXECUTE FUNCTION view_trigger('instead_of_del') + instead_of_insert_trig INSTEAD OF INSERT ON main_view FOR EACH ROW EXECUTE FUNCTION view_trigger('instead_of_ins') + instead_of_update_trig INSTEAD OF UPDATE ON main_view FOR EACH ROW EXECUTE FUNCTION view_trigger('instead_of_upd') + +-- Test dropping view triggers +DROP TRIGGER instead_of_insert_trig ON main_view; +DROP TRIGGER instead_of_delete_trig ON main_view; +\d+ main_view + View "public.main_view" + Column | Type | Collation | Nullable | Default | Storage | Description +--------+---------+-----------+----------+---------+---------+------------- + a | integer | | | | plain | + b | integer | | | | plain | +View definition: + SELECT main_table.a, + main_table.b + FROM main_table; +Triggers: + after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_del_stmt') + after_ins_stmt_trig AFTER INSERT ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_ins_stmt') + after_upd_stmt_trig AFTER UPDATE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('after_view_upd_stmt') + before_del_stmt_trig BEFORE DELETE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('before_view_del_stmt') + before_ins_stmt_trig BEFORE INSERT ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('before_view_ins_stmt') + before_upd_stmt_trig BEFORE UPDATE ON main_view FOR EACH STATEMENT EXECUTE FUNCTION view_trigger('before_view_upd_stmt') + instead_of_update_trig INSTEAD OF UPDATE ON main_view FOR EACH ROW EXECUTE FUNCTION view_trigger('instead_of_upd') + +DROP VIEW main_view; +-- +-- Test triggers on a join view +-- +CREATE TABLE country_table ( + country_id serial primary key, + country_name text unique not null, + continent text not null +); +INSERT INTO country_table (country_name, continent) + VALUES ('Japan', 'Asia'), + ('UK', 'Europe'), + ('USA', 'North America') + RETURNING *; + country_id | country_name | continent +------------+--------------+--------------- + 1 | Japan | Asia + 2 | UK | Europe + 3 | USA | North America +(3 rows) + +CREATE TABLE city_table ( + city_id serial primary key, + city_name text not null, + population bigint, + country_id int references country_table +); +CREATE VIEW city_view AS + SELECT city_id, city_name, population, country_name, continent + FROM city_table ci + LEFT JOIN country_table co ON co.country_id = ci.country_id; +CREATE FUNCTION city_insert() RETURNS trigger LANGUAGE plpgsql AS $$ +declare + ctry_id int; +begin + if NEW.country_name IS NOT NULL then + SELECT country_id, continent INTO ctry_id, NEW.continent + FROM country_table WHERE country_name = NEW.country_name; + if NOT FOUND then + raise exception 'No such country: "%"', NEW.country_name; + end if; + else + NEW.continent := NULL; + end if; + + if NEW.city_id IS NOT NULL then + INSERT INTO city_table + VALUES(NEW.city_id, NEW.city_name, NEW.population, ctry_id); + else + INSERT INTO city_table(city_name, population, country_id) + VALUES(NEW.city_name, NEW.population, ctry_id) + RETURNING city_id INTO NEW.city_id; + end if; + + RETURN NEW; +end; +$$; +CREATE TRIGGER city_insert_trig INSTEAD OF INSERT ON city_view +FOR EACH ROW EXECUTE PROCEDURE city_insert(); +CREATE FUNCTION city_delete() RETURNS trigger LANGUAGE plpgsql AS $$ +begin + DELETE FROM city_table WHERE city_id = OLD.city_id; + if NOT FOUND then RETURN NULL; end if; + RETURN OLD; +end; +$$; +CREATE TRIGGER city_delete_trig INSTEAD OF DELETE ON city_view +FOR EACH ROW EXECUTE PROCEDURE city_delete(); +CREATE FUNCTION city_update() RETURNS trigger LANGUAGE plpgsql AS $$ +declare + ctry_id int; +begin + if NEW.country_name IS DISTINCT FROM OLD.country_name then + SELECT country_id, continent INTO ctry_id, NEW.continent + FROM country_table WHERE country_name = NEW.country_name; + if NOT FOUND then + raise exception 'No such country: "%"', NEW.country_name; + end if; + + UPDATE city_table SET city_name = NEW.city_name, + population = NEW.population, + country_id = ctry_id + WHERE city_id = OLD.city_id; + else + UPDATE city_table SET city_name = NEW.city_name, + population = NEW.population + WHERE city_id = OLD.city_id; + NEW.continent := OLD.continent; + end if; + + if NOT FOUND then RETURN NULL; end if; + RETURN NEW; +end; +$$; +CREATE TRIGGER city_update_trig INSTEAD OF UPDATE ON city_view +FOR EACH ROW EXECUTE PROCEDURE city_update(); +\set QUIET false +-- INSERT .. RETURNING +INSERT INTO city_view(city_name) VALUES('Tokyo') RETURNING *; + city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+----------- + 1 | Tokyo | | | +(1 row) + +INSERT 0 1 +INSERT INTO city_view(city_name, population) VALUES('London', 7556900) RETURNING *; + city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+----------- + 2 | London | 7556900 | | +(1 row) + +INSERT 0 1 +INSERT INTO city_view(city_name, country_name) VALUES('Washington DC', 'USA') RETURNING *; + city_id | city_name | population | country_name | continent +---------+---------------+------------+--------------+--------------- + 3 | Washington DC | | USA | North America +(1 row) + +INSERT 0 1 +INSERT INTO city_view(city_id, city_name) VALUES(123456, 'New York') RETURNING *; + city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+----------- + 123456 | New York | | | +(1 row) + +INSERT 0 1 +INSERT INTO city_view VALUES(234567, 'Birmingham', 1016800, 'UK', 'EU') RETURNING *; + city_id | city_name | population | country_name | continent +---------+------------+------------+--------------+----------- + 234567 | Birmingham | 1016800 | UK | Europe +(1 row) + +INSERT 0 1 +-- UPDATE .. RETURNING +UPDATE city_view SET country_name = 'Japon' WHERE city_name = 'Tokyo'; -- error +ERROR: No such country: "Japon" +CONTEXT: PL/pgSQL function city_update() line 9 at RAISE +UPDATE city_view SET country_name = 'Japan' WHERE city_name = 'Takyo'; -- no match +UPDATE 0 +UPDATE city_view SET country_name = 'Japan' WHERE city_name = 'Tokyo' RETURNING *; -- OK + city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+----------- + 1 | Tokyo | | Japan | Asia +(1 row) + +UPDATE 1 +UPDATE city_view SET population = 13010279 WHERE city_name = 'Tokyo' RETURNING *; + city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+----------- + 1 | Tokyo | 13010279 | Japan | Asia +(1 row) + +UPDATE 1 +UPDATE city_view SET country_name = 'UK' WHERE city_name = 'New York' RETURNING *; + city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+----------- + 123456 | New York | | UK | Europe +(1 row) + +UPDATE 1 +UPDATE city_view SET country_name = 'USA', population = 8391881 WHERE city_name = 'New York' RETURNING *; + city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+--------------- + 123456 | New York | 8391881 | USA | North America +(1 row) + +UPDATE 1 +UPDATE city_view SET continent = 'EU' WHERE continent = 'Europe' RETURNING *; + city_id | city_name | population | country_name | continent +---------+------------+------------+--------------+----------- + 234567 | Birmingham | 1016800 | UK | Europe +(1 row) + +UPDATE 1 +UPDATE city_view v1 SET country_name = v2.country_name FROM city_view v2 + WHERE v2.city_name = 'Birmingham' AND v1.city_name = 'London' RETURNING *; + city_id | city_name | population | country_name | continent | city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+-----------+---------+------------+------------+--------------+----------- + 2 | London | 7556900 | UK | Europe | 234567 | Birmingham | 1016800 | UK | Europe +(1 row) + +UPDATE 1 +-- DELETE .. RETURNING +DELETE FROM city_view WHERE city_name = 'Birmingham' RETURNING *; + city_id | city_name | population | country_name | continent +---------+------------+------------+--------------+----------- + 234567 | Birmingham | 1016800 | UK | Europe +(1 row) + +DELETE 1 +\set QUIET true +-- read-only view with WHERE clause +CREATE VIEW european_city_view AS + SELECT * FROM city_view WHERE continent = 'Europe'; +SELECT count(*) FROM european_city_view; + count +------- + 1 +(1 row) + +CREATE FUNCTION no_op_trig_fn() RETURNS trigger LANGUAGE plpgsql +AS 'begin RETURN NULL; end'; +CREATE TRIGGER no_op_trig INSTEAD OF INSERT OR UPDATE OR DELETE +ON european_city_view FOR EACH ROW EXECUTE PROCEDURE no_op_trig_fn(); +\set QUIET false +INSERT INTO european_city_view VALUES (0, 'x', 10000, 'y', 'z'); +INSERT 0 0 +UPDATE european_city_view SET population = 10000; +UPDATE 0 +DELETE FROM european_city_view; +DELETE 0 +\set QUIET true +-- rules bypassing no-op triggers +CREATE RULE european_city_insert_rule AS ON INSERT TO european_city_view +DO INSTEAD INSERT INTO city_view +VALUES (NEW.city_id, NEW.city_name, NEW.population, NEW.country_name, NEW.continent) +RETURNING *; +CREATE RULE european_city_update_rule AS ON UPDATE TO european_city_view +DO INSTEAD UPDATE city_view SET + city_name = NEW.city_name, + population = NEW.population, + country_name = NEW.country_name +WHERE city_id = OLD.city_id +RETURNING NEW.*; +CREATE RULE european_city_delete_rule AS ON DELETE TO european_city_view +DO INSTEAD DELETE FROM city_view WHERE city_id = OLD.city_id RETURNING *; +\set QUIET false +-- INSERT not limited by view's WHERE clause, but UPDATE AND DELETE are +INSERT INTO european_city_view(city_name, country_name) + VALUES ('Cambridge', 'USA') RETURNING *; + city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+--------------- + 4 | Cambridge | | USA | North America +(1 row) + +INSERT 0 1 +UPDATE european_city_view SET country_name = 'UK' + WHERE city_name = 'Cambridge'; +UPDATE 0 +DELETE FROM european_city_view WHERE city_name = 'Cambridge'; +DELETE 0 +-- UPDATE and DELETE via rule and trigger +UPDATE city_view SET country_name = 'UK' + WHERE city_name = 'Cambridge' RETURNING *; + city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+----------- + 4 | Cambridge | | UK | Europe +(1 row) + +UPDATE 1 +UPDATE european_city_view SET population = 122800 + WHERE city_name = 'Cambridge' RETURNING *; + city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+----------- + 4 | Cambridge | 122800 | UK | Europe +(1 row) + +UPDATE 1 +DELETE FROM european_city_view WHERE city_name = 'Cambridge' RETURNING *; + city_id | city_name | population | country_name | continent +---------+-----------+------------+--------------+----------- + 4 | Cambridge | 122800 | UK | Europe +(1 row) + +DELETE 1 +-- join UPDATE test +UPDATE city_view v SET population = 599657 + FROM city_table ci, country_table co + WHERE ci.city_name = 'Washington DC' and co.country_name = 'USA' + AND v.city_id = ci.city_id AND v.country_name = co.country_name + RETURNING co.country_id, v.country_name, + v.city_id, v.city_name, v.population; + country_id | country_name | city_id | city_name | population +------------+--------------+---------+---------------+------------ + 3 | USA | 3 | Washington DC | 599657 +(1 row) + +UPDATE 1 +\set QUIET true +SELECT * FROM city_view order by city_id; + city_id | city_name | population | country_name | continent +---------+---------------+------------+--------------+--------------- + 1 | Tokyo | 13010279 | Japan | Asia + 2 | London | 7556900 | UK | Europe + 3 | Washington DC | 599657 | USA | North America + 123456 | New York | 8391881 | USA | North America +(4 rows) + +DROP TABLE city_table CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to view city_view +drop cascades to view european_city_view +DROP TABLE country_table; +-- Test pg_trigger_depth() +create table depth_a (id int not null primary key); +create table depth_b (id int not null primary key); +create table depth_c (id int not null primary key); +create function depth_a_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + insert into depth_b values (new.id); + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + return new; +end; +$$; +create trigger depth_a_tr before insert on depth_a + for each row execute procedure depth_a_tf(); +create function depth_b_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + begin + execute 'insert into depth_c values (' || new.id::text || ')'; + exception + when sqlstate 'U9999' then + raise notice 'SQLSTATE = U9999: depth = %', pg_trigger_depth(); + end; + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + if new.id = 1 then + execute 'insert into depth_c values (' || new.id::text || ')'; + end if; + return new; +end; +$$; +create trigger depth_b_tr before insert on depth_b + for each row execute procedure depth_b_tf(); +create function depth_c_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + if new.id = 1 then + raise exception sqlstate 'U9999'; + end if; + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + return new; +end; +$$; +create trigger depth_c_tr before insert on depth_c + for each row execute procedure depth_c_tf(); +select pg_trigger_depth(); + pg_trigger_depth +------------------ + 0 +(1 row) + +insert into depth_a values (1); +NOTICE: depth_a_tr: depth = 1 +NOTICE: depth_b_tr: depth = 2 +NOTICE: depth_c_tr: depth = 3 +NOTICE: SQLSTATE = U9999: depth = 2 +NOTICE: depth_b_tr: depth = 2 +NOTICE: depth_c_tr: depth = 3 +ERROR: U9999 +CONTEXT: PL/pgSQL function depth_c_tf() line 5 at RAISE +SQL statement "insert into depth_c values (1)" +PL/pgSQL function depth_b_tf() line 12 at EXECUTE +SQL statement "insert into depth_b values (new.id)" +PL/pgSQL function depth_a_tf() line 4 at SQL statement +select pg_trigger_depth(); + pg_trigger_depth +------------------ + 0 +(1 row) + +insert into depth_a values (2); +NOTICE: depth_a_tr: depth = 1 +NOTICE: depth_b_tr: depth = 2 +NOTICE: depth_c_tr: depth = 3 +NOTICE: depth_c_tr: depth = 3 +NOTICE: depth_b_tr: depth = 2 +NOTICE: depth_a_tr: depth = 1 +select pg_trigger_depth(); + pg_trigger_depth +------------------ + 0 +(1 row) + +drop table depth_a; +drop table depth_b; +drop table depth_c; +drop function depth_a_tf(); +drop function depth_b_tf(); +drop function depth_c_tf(); +-- +-- Test updates to rows during firing of BEFORE ROW triggers. +-- As of 9.2, such cases should be rejected (see bug #6123). +-- +create temp table parent ( + aid int not null primary key, + val1 text, + val2 text, + val3 text, + val4 text, + bcnt int not null default 0); +create temp table child ( + bid int not null primary key, + aid int not null, + val1 text); +create function parent_upd_func() + returns trigger language plpgsql as +$$ +begin + if old.val1 <> new.val1 then + new.val2 = new.val1; + delete from child where child.aid = new.aid and child.val1 = new.val1; + end if; + return new; +end; +$$; +create trigger parent_upd_trig before update on parent + for each row execute procedure parent_upd_func(); +create function parent_del_func() + returns trigger language plpgsql as +$$ +begin + delete from child where aid = old.aid; + return old; +end; +$$; +create trigger parent_del_trig before delete on parent + for each row execute procedure parent_del_func(); +create function child_ins_func() + returns trigger language plpgsql as +$$ +begin + update parent set bcnt = bcnt + 1 where aid = new.aid; + return new; +end; +$$; +create trigger child_ins_trig after insert on child + for each row execute procedure child_ins_func(); +create function child_del_func() + returns trigger language plpgsql as +$$ +begin + update parent set bcnt = bcnt - 1 where aid = old.aid; + return old; +end; +$$; +create trigger child_del_trig after delete on child + for each row execute procedure child_del_func(); +insert into parent values (1, 'a', 'a', 'a', 'a', 0); +insert into child values (10, 1, 'b'); +select * from parent; select * from child; + aid | val1 | val2 | val3 | val4 | bcnt +-----+------+------+------+------+------ + 1 | a | a | a | a | 1 +(1 row) + + bid | aid | val1 +-----+-----+------ + 10 | 1 | b +(1 row) + +update parent set val1 = 'b' where aid = 1; -- should fail +ERROR: tuple to be updated was already modified by an operation triggered by the current command +HINT: Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows. +select * from parent; select * from child; + aid | val1 | val2 | val3 | val4 | bcnt +-----+------+------+------+------+------ + 1 | a | a | a | a | 1 +(1 row) + + bid | aid | val1 +-----+-----+------ + 10 | 1 | b +(1 row) + +delete from parent where aid = 1; -- should fail +ERROR: tuple to be deleted was already modified by an operation triggered by the current command +HINT: Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows. +select * from parent; select * from child; + aid | val1 | val2 | val3 | val4 | bcnt +-----+------+------+------+------+------ + 1 | a | a | a | a | 1 +(1 row) + + bid | aid | val1 +-----+-----+------ + 10 | 1 | b +(1 row) + +-- replace the trigger function with one that restarts the deletion after +-- having modified a child +create or replace function parent_del_func() + returns trigger language plpgsql as +$$ +begin + delete from child where aid = old.aid; + if found then + delete from parent where aid = old.aid; + return null; -- cancel outer deletion + end if; + return old; +end; +$$; +delete from parent where aid = 1; +select * from parent; select * from child; + aid | val1 | val2 | val3 | val4 | bcnt +-----+------+------+------+------+------ +(0 rows) + + bid | aid | val1 +-----+-----+------ +(0 rows) + +drop table parent; +drop table child; +drop function parent_upd_func(); +drop function parent_del_func(); +drop function child_ins_func(); +drop function child_del_func(); +-- similar case, but with a self-referencing FK so that parent and child +-- rows can be affected by a single operation +create temp table self_ref_trigger ( + id int primary key, + parent int references self_ref_trigger, + data text, + nchildren int not null default 0 +); +create function self_ref_trigger_ins_func() + returns trigger language plpgsql as +$$ +begin + if new.parent is not null then + update self_ref_trigger set nchildren = nchildren + 1 + where id = new.parent; + end if; + return new; +end; +$$; +create trigger self_ref_trigger_ins_trig before insert on self_ref_trigger + for each row execute procedure self_ref_trigger_ins_func(); +create function self_ref_trigger_del_func() + returns trigger language plpgsql as +$$ +begin + if old.parent is not null then + update self_ref_trigger set nchildren = nchildren - 1 + where id = old.parent; + end if; + return old; +end; +$$; +create trigger self_ref_trigger_del_trig before delete on self_ref_trigger + for each row execute procedure self_ref_trigger_del_func(); +insert into self_ref_trigger values (1, null, 'root'); +insert into self_ref_trigger values (2, 1, 'root child A'); +insert into self_ref_trigger values (3, 1, 'root child B'); +insert into self_ref_trigger values (4, 2, 'grandchild 1'); +insert into self_ref_trigger values (5, 3, 'grandchild 2'); +update self_ref_trigger set data = 'root!' where id = 1; +select * from self_ref_trigger; + id | parent | data | nchildren +----+--------+--------------+----------- + 2 | 1 | root child A | 1 + 4 | 2 | grandchild 1 | 0 + 3 | 1 | root child B | 1 + 5 | 3 | grandchild 2 | 0 + 1 | | root! | 2 +(5 rows) + +delete from self_ref_trigger; +ERROR: tuple to be updated was already modified by an operation triggered by the current command +HINT: Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows. +select * from self_ref_trigger; + id | parent | data | nchildren +----+--------+--------------+----------- + 2 | 1 | root child A | 1 + 4 | 2 | grandchild 1 | 0 + 3 | 1 | root child B | 1 + 5 | 3 | grandchild 2 | 0 + 1 | | root! | 2 +(5 rows) + +drop table self_ref_trigger; +drop function self_ref_trigger_ins_func(); +drop function self_ref_trigger_del_func(); +-- +-- Check that statement triggers work correctly even with all children excluded +-- +create table stmt_trig_on_empty_upd (a int); +create table stmt_trig_on_empty_upd1 () inherits (stmt_trig_on_empty_upd); +create function update_stmt_notice() returns trigger as $$ +begin + raise notice 'updating %', TG_TABLE_NAME; + return null; +end; +$$ language plpgsql; +create trigger before_stmt_trigger + before update on stmt_trig_on_empty_upd + execute procedure update_stmt_notice(); +create trigger before_stmt_trigger + before update on stmt_trig_on_empty_upd1 + execute procedure update_stmt_notice(); +-- inherited no-op update +update stmt_trig_on_empty_upd set a = a where false returning a+1 as aa; +NOTICE: updating stmt_trig_on_empty_upd + aa +---- +(0 rows) + +-- simple no-op update +update stmt_trig_on_empty_upd1 set a = a where false returning a+1 as aa; +NOTICE: updating stmt_trig_on_empty_upd1 + aa +---- +(0 rows) + +drop table stmt_trig_on_empty_upd cascade; +NOTICE: drop cascades to table stmt_trig_on_empty_upd1 +drop function update_stmt_notice(); +-- +-- Check that index creation (or DDL in general) is prohibited in a trigger +-- +create table trigger_ddl_table ( + col1 integer, + col2 integer +); +create function trigger_ddl_func() returns trigger as $$ +begin + alter table trigger_ddl_table add primary key (col1); + return new; +end$$ language plpgsql; +create trigger trigger_ddl_func before insert on trigger_ddl_table for each row + execute procedure trigger_ddl_func(); +insert into trigger_ddl_table values (1, 42); -- fail +ERROR: cannot ALTER TABLE "trigger_ddl_table" because it is being used by active queries in this session +CONTEXT: SQL statement "alter table trigger_ddl_table add primary key (col1)" +PL/pgSQL function trigger_ddl_func() line 3 at SQL statement +create or replace function trigger_ddl_func() returns trigger as $$ +begin + create index on trigger_ddl_table (col2); + return new; +end$$ language plpgsql; +insert into trigger_ddl_table values (1, 42); -- fail +NOTICE: making create index for table "trigger_ddl_table" nonconcurrent +DETAIL: Create index in transaction block cannot be concurrent. +HINT: Consider running it outside of a transaction block. See https://github.com/yugabyte/yugabyte-db/issues/6240. +ERROR: cannot CREATE INDEX "trigger_ddl_table" because it is being used by active queries in this session +CONTEXT: SQL statement "create index on trigger_ddl_table (col2)" +PL/pgSQL function trigger_ddl_func() line 3 at SQL statement +drop table trigger_ddl_table; +drop function trigger_ddl_func(); +-- +-- Verify behavior of before and after triggers with INSERT...ON CONFLICT +-- DO UPDATE +-- +create table upsert (key int4 primary key, color text); +create function upsert_before_func() + returns trigger language plpgsql as +$$ +begin + if (TG_OP = 'UPDATE') then + raise warning 'before update (old): %', old.*::text; + raise warning 'before update (new): %', new.*::text; + elsif (TG_OP = 'INSERT') then + raise warning 'before insert (new): %', new.*::text; + if new.key % 2 = 0 then + new.key := new.key + 1; + new.color := new.color || ' trig modified'; + raise warning 'before insert (new, modified): %', new.*::text; + end if; + end if; + return new; +end; +$$; +create trigger upsert_before_trig before insert or update on upsert + for each row execute procedure upsert_before_func(); +create function upsert_after_func() + returns trigger language plpgsql as +$$ +begin + if (TG_OP = 'UPDATE') then + raise warning 'after update (old): %', old.*::text; + raise warning 'after update (new): %', new.*::text; + elsif (TG_OP = 'INSERT') then + raise warning 'after insert (new): %', new.*::text; + end if; + return null; +end; +$$; +create trigger upsert_after_trig after insert or update on upsert + for each row execute procedure upsert_after_func(); +insert into upsert values(1, 'black') on conflict (key) do update set color = 'updated ' || upsert.color; +WARNING: before insert (new): (1,black) +WARNING: after insert (new): (1,black) +insert into upsert values(2, 'red') on conflict (key) do update set color = 'updated ' || upsert.color; +WARNING: before insert (new): (2,red) +WARNING: before insert (new, modified): (3,"red trig modified") +WARNING: after insert (new): (3,"red trig modified") +insert into upsert values(3, 'orange') on conflict (key) do update set color = 'updated ' || upsert.color; +WARNING: before insert (new): (3,orange) +WARNING: before update (old): (3,"red trig modified") +WARNING: before update (new): (3,"updated red trig modified") +WARNING: after update (old): (3,"red trig modified") +WARNING: after update (new): (3,"updated red trig modified") +insert into upsert values(4, 'green') on conflict (key) do update set color = 'updated ' || upsert.color; +WARNING: before insert (new): (4,green) +WARNING: before insert (new, modified): (5,"green trig modified") +WARNING: after insert (new): (5,"green trig modified") +insert into upsert values(5, 'purple') on conflict (key) do update set color = 'updated ' || upsert.color; +WARNING: before insert (new): (5,purple) +WARNING: before update (old): (5,"green trig modified") +WARNING: before update (new): (5,"updated green trig modified") +WARNING: after update (old): (5,"green trig modified") +WARNING: after update (new): (5,"updated green trig modified") +insert into upsert values(6, 'white') on conflict (key) do update set color = 'updated ' || upsert.color; +WARNING: before insert (new): (6,white) +WARNING: before insert (new, modified): (7,"white trig modified") +WARNING: after insert (new): (7,"white trig modified") +insert into upsert values(7, 'pink') on conflict (key) do update set color = 'updated ' || upsert.color; +WARNING: before insert (new): (7,pink) +WARNING: before update (old): (7,"white trig modified") +WARNING: before update (new): (7,"updated white trig modified") +WARNING: after update (old): (7,"white trig modified") +WARNING: after update (new): (7,"updated white trig modified") +insert into upsert values(8, 'yellow') on conflict (key) do update set color = 'updated ' || upsert.color; +WARNING: before insert (new): (8,yellow) +WARNING: before insert (new, modified): (9,"yellow trig modified") +WARNING: after insert (new): (9,"yellow trig modified") +select * from upsert order by key; + key | color +-----+----------------------------- + 1 | black + 3 | updated red trig modified + 5 | updated green trig modified + 7 | updated white trig modified + 9 | yellow trig modified +(5 rows) + +drop table upsert; +drop function upsert_before_func(); +drop function upsert_after_func(); +-- +-- Verify that triggers with transition tables are not allowed on +-- views +-- +create table my_table (i int); +create view my_view as select * from my_table; +create function my_trigger_function() returns trigger as $$ begin end; $$ language plpgsql; +create trigger my_trigger after update on my_view referencing old table as old_table + for each statement execute procedure my_trigger_function(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 1: create trigger my_trigger after update on my_view referencin... + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +drop function my_trigger_function(); +drop view my_view; +drop table my_table; +-- +-- Verify cases that are unsupported with partitioned tables +-- +create table parted_trig (a int) partition by list (a); +create function trigger_nothing() returns trigger + language plpgsql as $$ begin end; $$; +create trigger failed instead of update on parted_trig + for each row execute procedure trigger_nothing(); +ERROR: "parted_trig" is a table +DETAIL: Tables cannot have INSTEAD OF triggers. +create trigger failed after update on parted_trig + referencing old table as old_table + for each row execute procedure trigger_nothing(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 2: referencing old table as old_table + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +drop table parted_trig; +-- +-- Verify trigger creation for partitioned tables, and drop behavior +-- +create table trigpart (a int, b int) partition by range (a); +create table trigpart1 partition of trigpart for values from (0) to (1000); +create trigger trg1 after insert on trigpart for each row execute procedure trigger_nothing(); +create table trigpart2 partition of trigpart for values from (1000) to (2000); +create table trigpart3 (like trigpart); +alter table trigpart attach partition trigpart3 for values from (2000) to (3000); +create table trigpart4 partition of trigpart for values from (3000) to (4000) partition by range (a); +create table trigpart41 partition of trigpart4 for values from (3000) to (3500); +create table trigpart42 (like trigpart); +alter table trigpart4 attach partition trigpart42 for values from (3500) to (4000); +select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger + where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text; + tgrelid | tgname | tgfoid +------------+--------+----------------- + trigpart | trg1 | trigger_nothing + trigpart1 | trg1 | trigger_nothing + trigpart2 | trg1 | trigger_nothing + trigpart3 | trg1 | trigger_nothing + trigpart4 | trg1 | trigger_nothing + trigpart41 | trg1 | trigger_nothing + trigpart42 | trg1 | trigger_nothing +(7 rows) + +-- Disabling the below tests temporarily because each run of the test can +-- throw one of two errors. The error message displayed contains the first dependency +-- resulting from the scan of pg_depend preventing the delete of the trigger. The trigger +-- depends on the partition table itself and on the trigger created on the partitioned +-- table, and either of the two error messages are possible. +/* +drop trigger trg1 on trigpart1; -- fail +drop trigger trg1 on trigpart2; -- fail +*/ +drop table trigpart2; -- ok, trigger should be gone in that partition +select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger + where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text; + tgrelid | tgname | tgfoid +------------+--------+----------------- + trigpart | trg1 | trigger_nothing + trigpart1 | trg1 | trigger_nothing + trigpart3 | trg1 | trigger_nothing + trigpart4 | trg1 | trigger_nothing + trigpart41 | trg1 | trigger_nothing + trigpart42 | trg1 | trigger_nothing +(6 rows) + +drop trigger trg1 on trigpart; -- ok, all gone +select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger + where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text; + tgrelid | tgname | tgfoid +---------+--------+-------- +(0 rows) + +-- check detach behavior +create trigger trg1 after insert on trigpart for each row execute procedure trigger_nothing(); +\d trigpart3 + Table "public.trigpart3" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | +Partition of: trigpart FOR VALUES FROM (2000) TO (3000) +Triggers: + trg1 AFTER INSERT ON trigpart3 FOR EACH ROW EXECUTE FUNCTION trigger_nothing(), ON TABLE trigpart + +alter table trigpart detach partition trigpart3; +drop trigger trg1 on trigpart3; -- fail due to "does not exist" +ERROR: trigger "trg1" for table "trigpart3" does not exist +alter table trigpart detach partition trigpart4; +drop trigger trg1 on trigpart41; -- fail due to "does not exist" +ERROR: trigger "trg1" for table "trigpart41" does not exist +drop table trigpart4; +alter table trigpart attach partition trigpart3 for values from (2000) to (3000); +alter table trigpart detach partition trigpart3; +alter table trigpart attach partition trigpart3 for values from (2000) to (3000); +drop table trigpart3; +select tgrelid::regclass::text, tgname, tgfoid::regproc, tgenabled, tgisinternal from pg_trigger + where tgname ~ '^trg1' order by 1; + tgrelid | tgname | tgfoid | tgenabled | tgisinternal +-----------+--------+-----------------+-----------+-------------- + trigpart | trg1 | trigger_nothing | O | f + trigpart1 | trg1 | trigger_nothing | O | f +(2 rows) + +create table trigpart3 (like trigpart); +create trigger trg1 after insert on trigpart3 for each row execute procedure trigger_nothing(); +\d trigpart3 + Table "public.trigpart3" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | +Triggers: + trg1 AFTER INSERT ON trigpart3 FOR EACH ROW EXECUTE FUNCTION trigger_nothing() + +alter table trigpart attach partition trigpart3 FOR VALUES FROM (2000) to (3000); -- fail +ERROR: trigger "trg1" for relation "trigpart3" already exists +drop table trigpart3; +drop table trigpart; +drop function trigger_nothing(); +-- +-- Verify that triggers are fired for partitioned tables +-- +create table parted_stmt_trig (a int) partition by list (a); +create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1); +create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2); +create table parted2_stmt_trig (a int) partition by list (a); +create table parted2_stmt_trig1 partition of parted2_stmt_trig for values in (1); +create table parted2_stmt_trig2 partition of parted2_stmt_trig for values in (2); +create or replace function trigger_notice() returns trigger as $$ + begin + raise notice 'trigger % on % % % for %', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL; + if TG_LEVEL = 'ROW' then + return NEW; + end if; + return null; + end; + $$ language plpgsql; +-- insert/update/delete statement-level triggers on the parent +create trigger trig_ins_before before insert on parted_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_ins_after after insert on parted_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_upd_before before update on parted_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_upd_after after update on parted_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_del_before before delete on parted_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_del_after after delete on parted_stmt_trig + for each statement execute procedure trigger_notice(); +-- insert/update/delete row-level triggers on the parent +create trigger trig_ins_after_parent after insert on parted_stmt_trig + for each row execute procedure trigger_notice(); +create trigger trig_upd_after_parent after update on parted_stmt_trig + for each row execute procedure trigger_notice(); +create trigger trig_del_after_parent after delete on parted_stmt_trig + for each row execute procedure trigger_notice(); +-- insert/update/delete row-level triggers on the first partition +create trigger trig_ins_before_child before insert on parted_stmt_trig1 + for each row execute procedure trigger_notice(); +create trigger trig_ins_after_child after insert on parted_stmt_trig1 + for each row execute procedure trigger_notice(); +create trigger trig_upd_before_child before update on parted_stmt_trig1 + for each row execute procedure trigger_notice(); +create trigger trig_upd_after_child after update on parted_stmt_trig1 + for each row execute procedure trigger_notice(); +create trigger trig_del_before_child before delete on parted_stmt_trig1 + for each row execute procedure trigger_notice(); +create trigger trig_del_after_child after delete on parted_stmt_trig1 + for each row execute procedure trigger_notice(); +-- insert/update/delete statement-level triggers on the parent +create trigger trig_ins_before_3 before insert on parted2_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_ins_after_3 after insert on parted2_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_upd_before_3 before update on parted2_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_upd_after_3 after update on parted2_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_del_before_3 before delete on parted2_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_del_after_3 after delete on parted2_stmt_trig + for each statement execute procedure trigger_notice(); +with ins (a) as ( + insert into parted2_stmt_trig values (1), (2) returning a +) insert into parted_stmt_trig select a from ins returning tableoid::regclass, a; +NOTICE: trigger trig_ins_before on parted_stmt_trig BEFORE INSERT for STATEMENT +NOTICE: trigger trig_ins_before_3 on parted2_stmt_trig BEFORE INSERT for STATEMENT +NOTICE: trigger trig_ins_before_child on parted_stmt_trig1 BEFORE INSERT for ROW +NOTICE: trigger trig_ins_after_child on parted_stmt_trig1 AFTER INSERT for ROW +NOTICE: trigger trig_ins_after_parent on parted_stmt_trig1 AFTER INSERT for ROW +NOTICE: trigger trig_ins_after_parent on parted_stmt_trig2 AFTER INSERT for ROW +NOTICE: trigger trig_ins_after_3 on parted2_stmt_trig AFTER INSERT for STATEMENT +NOTICE: trigger trig_ins_after on parted_stmt_trig AFTER INSERT for STATEMENT + tableoid | a +-------------------+--- + parted_stmt_trig1 | 1 + parted_stmt_trig2 | 2 +(2 rows) + +with upd as ( + update parted2_stmt_trig set a = a +) update parted_stmt_trig set a = a; +NOTICE: trigger trig_upd_before on parted_stmt_trig BEFORE UPDATE for STATEMENT +NOTICE: trigger trig_upd_before_child on parted_stmt_trig1 BEFORE UPDATE for ROW +NOTICE: trigger trig_upd_before_3 on parted2_stmt_trig BEFORE UPDATE for STATEMENT +NOTICE: trigger trig_upd_after_child on parted_stmt_trig1 AFTER UPDATE for ROW +NOTICE: trigger trig_upd_after_parent on parted_stmt_trig1 AFTER UPDATE for ROW +NOTICE: trigger trig_upd_after_parent on parted_stmt_trig2 AFTER UPDATE for ROW +NOTICE: trigger trig_upd_after on parted_stmt_trig AFTER UPDATE for STATEMENT +NOTICE: trigger trig_upd_after_3 on parted2_stmt_trig AFTER UPDATE for STATEMENT +delete from parted_stmt_trig; +NOTICE: trigger trig_del_before on parted_stmt_trig BEFORE DELETE for STATEMENT +NOTICE: trigger trig_del_before_child on parted_stmt_trig1 BEFORE DELETE for ROW +NOTICE: trigger trig_del_after_parent on parted_stmt_trig2 AFTER DELETE for ROW +NOTICE: trigger trig_del_after on parted_stmt_trig AFTER DELETE for STATEMENT +-- insert via copy on the parent +copy parted_stmt_trig(a) from stdin; +NOTICE: trigger trig_ins_before on parted_stmt_trig BEFORE INSERT for STATEMENT +NOTICE: trigger trig_ins_before_child on parted_stmt_trig1 BEFORE INSERT for ROW +NOTICE: trigger trig_ins_after_child on parted_stmt_trig1 AFTER INSERT for ROW +NOTICE: trigger trig_ins_after_parent on parted_stmt_trig1 AFTER INSERT for ROW +NOTICE: trigger trig_ins_after_parent on parted_stmt_trig2 AFTER INSERT for ROW +NOTICE: trigger trig_ins_after on parted_stmt_trig AFTER INSERT for STATEMENT +-- insert via copy on the first partition +copy parted_stmt_trig1(a) from stdin; +NOTICE: trigger trig_ins_before_child on parted_stmt_trig1 BEFORE INSERT for ROW +NOTICE: trigger trig_ins_after_child on parted_stmt_trig1 AFTER INSERT for ROW +NOTICE: trigger trig_ins_after_parent on parted_stmt_trig1 AFTER INSERT for ROW +-- Disabling a trigger in the parent table should disable children triggers too +alter table parted_stmt_trig disable trigger trig_ins_after_parent; +insert into parted_stmt_trig values (1); +NOTICE: trigger trig_ins_before on parted_stmt_trig BEFORE INSERT for STATEMENT +NOTICE: trigger trig_ins_before_child on parted_stmt_trig1 BEFORE INSERT for ROW +NOTICE: trigger trig_ins_after_child on parted_stmt_trig1 AFTER INSERT for ROW +NOTICE: trigger trig_ins_after on parted_stmt_trig AFTER INSERT for STATEMENT +alter table parted_stmt_trig enable trigger trig_ins_after_parent; +insert into parted_stmt_trig values (1); +NOTICE: trigger trig_ins_before on parted_stmt_trig BEFORE INSERT for STATEMENT +NOTICE: trigger trig_ins_before_child on parted_stmt_trig1 BEFORE INSERT for ROW +NOTICE: trigger trig_ins_after_child on parted_stmt_trig1 AFTER INSERT for ROW +NOTICE: trigger trig_ins_after_parent on parted_stmt_trig1 AFTER INSERT for ROW +NOTICE: trigger trig_ins_after on parted_stmt_trig AFTER INSERT for STATEMENT +drop table parted_stmt_trig, parted2_stmt_trig; +-- Verify that triggers fire in alphabetical order +create table parted_trig (a int) partition by range (a); +create table parted_trig_1 partition of parted_trig for values from (0) to (1000) + partition by range (a); +create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100); +create table parted_trig_2 partition of parted_trig for values from (1000) to (2000); +create trigger zzz after insert on parted_trig for each row execute procedure trigger_notice(); +create trigger mmm after insert on parted_trig_1_1 for each row execute procedure trigger_notice(); +create trigger aaa after insert on parted_trig_1 for each row execute procedure trigger_notice(); +create trigger bbb after insert on parted_trig for each row execute procedure trigger_notice(); +create trigger qqq after insert on parted_trig_1_1 for each row execute procedure trigger_notice(); +insert into parted_trig values (50), (1500); +NOTICE: trigger aaa on parted_trig_1_1 AFTER INSERT for ROW +NOTICE: trigger bbb on parted_trig_1_1 AFTER INSERT for ROW +NOTICE: trigger mmm on parted_trig_1_1 AFTER INSERT for ROW +NOTICE: trigger qqq on parted_trig_1_1 AFTER INSERT for ROW +NOTICE: trigger zzz on parted_trig_1_1 AFTER INSERT for ROW +NOTICE: trigger bbb on parted_trig_2 AFTER INSERT for ROW +NOTICE: trigger zzz on parted_trig_2 AFTER INSERT for ROW +drop table parted_trig; +-- test irregular partitions (i.e., different column definitions), +-- including that the WHEN clause works +create function bark(text) returns bool language plpgsql immutable + as $$ begin raise notice '% <- woof!', $1; return true; end; $$; +create or replace function trigger_notice_ab() returns trigger as $$ + begin + raise notice 'trigger % on % % % for %: (a,b)=(%,%)', + TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL, + NEW.a, NEW.b; + if TG_LEVEL = 'ROW' then + return NEW; + end if; + return null; + end; + $$ language plpgsql; +create table parted_irreg_ancestor (fd text, b text, fd2 int, fd3 int, a int) + partition by range (b); +alter table parted_irreg_ancestor drop column fd, + drop column fd2, drop column fd3; +create table parted_irreg (fd int, a int, fd2 int, b text) + partition by range (b); +alter table parted_irreg drop column fd, drop column fd2; +alter table parted_irreg_ancestor attach partition parted_irreg + for values from ('aaaa') to ('zzzz'); +create table parted1_irreg (b text, fd int, a int); +alter table parted1_irreg drop column fd; +alter table parted_irreg attach partition parted1_irreg + for values from ('aaaa') to ('bbbb'); +create trigger parted_trig after insert on parted_irreg + for each row execute procedure trigger_notice_ab(); +create trigger parted_trig_odd after insert on parted_irreg for each row + when (bark(new.b) AND new.a % 2 = 1) execute procedure trigger_notice_ab(); +-- we should hear barking for every insert, but parted_trig_odd only emits +-- noise for odd values of a. parted_trig does it for all inserts. +insert into parted_irreg values (1, 'aardvark'), (2, 'aanimals'); +NOTICE: aardvark <- woof! +NOTICE: aanimals <- woof! +NOTICE: trigger parted_trig on parted1_irreg AFTER INSERT for ROW: (a,b)=(1,aardvark) +NOTICE: trigger parted_trig_odd on parted1_irreg AFTER INSERT for ROW: (a,b)=(1,aardvark) +NOTICE: trigger parted_trig on parted1_irreg AFTER INSERT for ROW: (a,b)=(2,aanimals) +insert into parted1_irreg values ('aardwolf', 2); +NOTICE: aardwolf <- woof! +NOTICE: trigger parted_trig on parted1_irreg AFTER INSERT for ROW: (a,b)=(2,aardwolf) +insert into parted_irreg_ancestor values ('aasvogel', 3); +NOTICE: aasvogel <- woof! +NOTICE: trigger parted_trig on parted1_irreg AFTER INSERT for ROW: (a,b)=(3,aasvogel) +NOTICE: trigger parted_trig_odd on parted1_irreg AFTER INSERT for ROW: (a,b)=(3,aasvogel) +drop table parted_irreg_ancestor; +-- Before triggers and partitions +create table parted (a int, b int, c text) partition by list (a); +create table parted_1 partition of parted for values in (1) + partition by list (b); +create table parted_1_1 partition of parted_1 for values in (1); +create function parted_trigfunc() returns trigger language plpgsql as $$ +begin + new.a = new.a + 1; + return new; +end; +$$; +insert into parted values (1, 1, 'uno uno v1'); -- works +create trigger t before insert or update or delete on parted + for each row execute function parted_trigfunc(); +insert into parted values (1, 1, 'uno uno v2'); -- fail +ERROR: moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported +DETAIL: Before executing trigger "t", the row was to be in partition "public.parted_1_1". +update parted set c = c || 'v3'; -- fail +ERROR: no partition of relation "parted" found for row +DETAIL: Partition key of the failing row contains (a) = (2). +create or replace function parted_trigfunc() returns trigger language plpgsql as $$ +begin + new.b = new.b + 1; + return new; +end; +$$; +insert into parted values (1, 1, 'uno uno v4'); -- fail +ERROR: moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported +DETAIL: Before executing trigger "t", the row was to be in partition "public.parted_1_1". +update parted set c = c || 'v5'; -- fail +ERROR: no partition of relation "parted_1" found for row +DETAIL: Partition key of the failing row contains (b) = (2). +create or replace function parted_trigfunc() returns trigger language plpgsql as $$ +begin + new.c = new.c || ' did '|| TG_OP; + return new; +end; +$$; +insert into parted values (1, 1, 'uno uno'); -- works +update parted set c = c || ' v6'; -- works +-- YB note: add order by desc to match PG ordering +select tableoid::regclass, * from parted order by c desc; + tableoid | a | b | c +------------+---+---+---------------------------------- + parted_1_1 | 1 | 1 | uno uno v1 v6 did UPDATE + parted_1_1 | 1 | 1 | uno uno did INSERT v6 did UPDATE +(2 rows) + +-- update itself moves tuple to new partition; trigger still works +truncate table parted; +create table parted_2 partition of parted for values in (2); +insert into parted values (1, 1, 'uno uno v5'); +update parted set a = 2; +select tableoid::regclass, * from parted; + tableoid | a | b | c +----------+---+---+--------------------------------------------- + parted_2 | 2 | 1 | uno uno v5 did INSERT did UPDATE did INSERT +(1 row) + +-- both trigger and update change the partition +create or replace function parted_trigfunc2() returns trigger language plpgsql as $$ +begin + new.a = new.a + 1; + return new; +end; +$$; +create trigger t2 before update on parted + for each row execute function parted_trigfunc2(); +truncate table parted; +insert into parted values (1, 1, 'uno uno v6'); +create table parted_3 partition of parted for values in (3); +update parted set a = a + 1; +select tableoid::regclass, * from parted; + tableoid | a | b | c +----------+---+---+--------------------------------------------- + parted_3 | 3 | 1 | uno uno v6 did INSERT did UPDATE did INSERT +(1 row) + +-- there's no partition for a=0, but this update works anyway because +-- the trigger causes the tuple to be routed to another partition +update parted set a = 0; +select tableoid::regclass, * from parted; + tableoid | a | b | c +------------+---+---+------------------------------------------------------------------- + parted_1_1 | 1 | 1 | uno uno v6 did INSERT did UPDATE did INSERT did UPDATE did INSERT +(1 row) + +drop table parted; +create table parted (a int, b int, c text) partition by list ((a + b)); +create or replace function parted_trigfunc() returns trigger language plpgsql as $$ +begin + new.a = new.a + new.b; + return new; +end; +$$; +create table parted_1 partition of parted for values in (1, 2); +create table parted_2 partition of parted for values in (3, 4); +create trigger t before insert or update on parted + for each row execute function parted_trigfunc(); +insert into parted values (0, 1, 'zero win'); +insert into parted values (1, 1, 'one fail'); +ERROR: moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported +DETAIL: Before executing trigger "t", the row was to be in partition "public.parted_1". +insert into parted values (1, 2, 'two fail'); +ERROR: moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported +DETAIL: Before executing trigger "t", the row was to be in partition "public.parted_2". +select * from parted; + a | b | c +---+---+---------- + 1 | 1 | zero win +(1 row) + +drop table parted; +drop function parted_trigfunc(); +-- +-- Constraint triggers and partitioned tables +create table parted_constr_ancestor (a int, b text) + partition by range (b); +create table parted_constr (a int, b text) + partition by range (b); +alter table parted_constr_ancestor attach partition parted_constr + for values from ('aaaa') to ('zzzz'); +create table parted1_constr (a int, b text); +alter table parted_constr attach partition parted1_constr + for values from ('aaaa') to ('bbbb'); +create constraint trigger parted_trig after insert on parted_constr_ancestor + deferrable + for each row execute procedure trigger_notice_ab(); +ERROR: CREATE CONSTRAINT TRIGGER not supported yet +LINE 1: create constraint trigger parted_trig after insert on parted... + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1709. React with thumbs up to raise its priority +/* +create constraint trigger parted_trig_two after insert on parted_constr + deferrable initially deferred + for each row when (bark(new.b) AND new.a % 2 = 1) + execute procedure trigger_notice_ab(); + +-- The immediate constraint is fired immediately; the WHEN clause of the +-- deferred constraint is also called immediately. The deferred constraint +-- is fired at commit time. +begin; +insert into parted_constr values (1, 'aardvark'); +insert into parted1_constr values (2, 'aardwolf'); +insert into parted_constr_ancestor values (3, 'aasvogel'); +commit; + +-- The WHEN clause is immediate, and both constraint triggers are fired at +-- commit time. +begin; +set constraints parted_trig deferred; +insert into parted_constr values (1, 'aardvark'); +insert into parted1_constr values (2, 'aardwolf'), (3, 'aasvogel'); +commit; +*/ +drop table parted_constr_ancestor; +drop function bark(text); +-- Test that the WHEN clause is set properly to partitions +create table parted_trigger (a int, b text) partition by range (a); +create table parted_trigger_1 partition of parted_trigger for values from (0) to (1000); +create table parted_trigger_2 (drp int, a int, b text); +alter table parted_trigger_2 drop column drp; +alter table parted_trigger attach partition parted_trigger_2 for values from (1000) to (2000); +create trigger parted_trigger after update on parted_trigger + for each row when (new.a % 2 = 1 and length(old.b) >= 2) execute procedure trigger_notice_ab(); +create table parted_trigger_3 (b text, a int) partition by range (length(b)); +create table parted_trigger_3_1 partition of parted_trigger_3 for values from (1) to (3); +create table parted_trigger_3_2 partition of parted_trigger_3 for values from (3) to (5); +alter table parted_trigger attach partition parted_trigger_3 for values from (2000) to (3000); +insert into parted_trigger values + (0, 'a'), (1, 'bbb'), (2, 'bcd'), (3, 'c'), + (1000, 'c'), (1001, 'ddd'), (1002, 'efg'), (1003, 'f'), + (2000, 'e'), (2001, 'fff'), (2002, 'ghi'), (2003, 'h'); +update parted_trigger set a = a + 2; -- notice for odd 'a' values, long 'b' values +NOTICE: trigger parted_trigger on parted_trigger_1 AFTER UPDATE for ROW: (a,b)=(3,bbb) +NOTICE: trigger parted_trigger on parted_trigger_2 AFTER UPDATE for ROW: (a,b)=(1003,ddd) +NOTICE: trigger parted_trigger on parted_trigger_3_2 AFTER UPDATE for ROW: (a,b)=(2003,fff) +drop table parted_trigger; +-- try a constraint trigger, also +create table parted_referenced (a int); +create table unparted_trigger (a int, b text); -- for comparison purposes +create table parted_trigger (a int, b text) partition by range (a); +create table parted_trigger_1 partition of parted_trigger for values from (0) to (1000); +create table parted_trigger_2 (drp int, a int, b text); +alter table parted_trigger_2 drop column drp; +alter table parted_trigger attach partition parted_trigger_2 for values from (1000) to (2000); +create constraint trigger parted_trigger after update on parted_trigger + from parted_referenced + for each row execute procedure trigger_notice_ab(); +ERROR: CREATE CONSTRAINT TRIGGER not supported yet +LINE 1: create constraint trigger parted_trigger after update on par... + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1709. React with thumbs up to raise its priority +/* +create constraint trigger parted_trigger after update on unparted_trigger + from parted_referenced + for each row execute procedure trigger_notice_ab(); +create table parted_trigger_3 (b text, a int) partition by range (length(b)); +create table parted_trigger_3_1 partition of parted_trigger_3 for values from (1) to (3); +create table parted_trigger_3_2 partition of parted_trigger_3 for values from (3) to (5); +alter table parted_trigger attach partition parted_trigger_3 for values from (2000) to (3000); +select tgname, conname, t.tgrelid::regclass, t.tgconstrrelid::regclass, + c.conrelid::regclass, c.confrelid::regclass + from pg_trigger t join pg_constraint c on (t.tgconstraint = c.oid) + where tgname = 'parted_trigger' + order by t.tgrelid::regclass::text; +*/ +drop table parted_referenced, parted_trigger, unparted_trigger; +-- verify that the "AFTER UPDATE OF columns" event is propagated correctly +create table parted_trigger (a int, b text) partition by range (a); +create table parted_trigger_1 partition of parted_trigger for values from (0) to (1000); +create table parted_trigger_2 (drp int, a int, b text); +alter table parted_trigger_2 drop column drp; +alter table parted_trigger attach partition parted_trigger_2 for values from (1000) to (2000); +create trigger parted_trigger after update of b on parted_trigger + for each row execute procedure trigger_notice_ab(); +create table parted_trigger_3 (b text, a int) partition by range (length(b)); +create table parted_trigger_3_1 partition of parted_trigger_3 for values from (1) to (4); +create table parted_trigger_3_2 partition of parted_trigger_3 for values from (4) to (8); +alter table parted_trigger attach partition parted_trigger_3 for values from (2000) to (3000); +insert into parted_trigger values (0, 'a'), (1000, 'c'), (2000, 'e'), (2001, 'eeee'); +update parted_trigger set a = a + 2; -- no notices here +update parted_trigger set b = b || 'b'; -- all triggers should fire +NOTICE: trigger parted_trigger on parted_trigger_1 AFTER UPDATE for ROW: (a,b)=(2,ab) +NOTICE: trigger parted_trigger on parted_trigger_2 AFTER UPDATE for ROW: (a,b)=(1002,cb) +NOTICE: trigger parted_trigger on parted_trigger_3_1 AFTER UPDATE for ROW: (a,b)=(2002,eb) +NOTICE: trigger parted_trigger on parted_trigger_3_2 AFTER UPDATE for ROW: (a,b)=(2003,eeeeb) +drop table parted_trigger; +drop function trigger_notice_ab(); +-- Make sure we don't end up with unnecessary copies of triggers, when +-- cloning them. +create table trg_clone (a int) partition by range (a); +create table trg_clone1 partition of trg_clone for values from (0) to (1000); +alter table trg_clone add constraint uniq unique (a) deferrable; +ERROR: DEFERRABLE unique constraints are not supported yet +LINE 1: alter table trg_clone add constraint uniq unique (a) deferra... + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1129. React with thumbs up to raise its priority +/* +create table trg_clone2 partition of trg_clone for values from (1000) to (2000); +create table trg_clone3 partition of trg_clone for values from (2000) to (3000) + partition by range (a); +create table trg_clone_3_3 partition of trg_clone3 for values from (2000) to (2100); +select tgrelid::regclass, count(*) from pg_trigger + where tgrelid::regclass in ('trg_clone', 'trg_clone1', 'trg_clone2', + 'trg_clone3', 'trg_clone_3_3') + group by tgrelid::regclass order by tgrelid::regclass; +*/ +drop table trg_clone; +-- Test the interaction between ALTER TABLE .. DISABLE TRIGGER and +-- both kinds of inheritance. Historically, legacy inheritance has +-- not recursed to children, so that behavior is preserved. +create table parent (a int); +create table child1 () inherits (parent); +create function trig_nothing() returns trigger language plpgsql + as $$ begin return null; end $$; +create trigger tg after insert on parent + for each row execute function trig_nothing(); +create trigger tg after insert on child1 + for each row execute function trig_nothing(); +alter table parent disable trigger tg; +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text; + tgrelid | tgname | tgenabled +---------+--------+----------- + child1 | tg | O + parent | tg | D +(2 rows) + +alter table only parent enable always trigger tg; +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text; + tgrelid | tgname | tgenabled +---------+--------+----------- + child1 | tg | O + parent | tg | A +(2 rows) + +drop table parent, child1; +create table parent (a int) partition by list (a); +create table child1 partition of parent for values in (1); +create trigger tg after insert on parent + for each row execute procedure trig_nothing(); +create trigger tg_stmt after insert on parent + for statement execute procedure trig_nothing(); +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgname; + tgrelid | tgname | tgenabled +---------+---------+----------- + child1 | tg | O + parent | tg | O + parent | tg_stmt | O +(3 rows) + +alter table only parent enable always trigger tg; -- no recursion because ONLY +alter table parent enable always trigger tg_stmt; -- no recursion because statement trigger +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgname; + tgrelid | tgname | tgenabled +---------+---------+----------- + child1 | tg | O + parent | tg | A + parent | tg_stmt | A +(3 rows) + +-- The following is a no-op for the parent trigger but not so +-- for the child trigger, so recursion should be applied. +alter table parent enable always trigger tg; +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgname; + tgrelid | tgname | tgenabled +---------+---------+----------- + child1 | tg | A + parent | tg | A + parent | tg_stmt | A +(3 rows) + +-- This variant malfunctioned in some releases. +alter table parent disable trigger user; +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgname; + tgrelid | tgname | tgenabled +---------+---------+----------- + child1 | tg | D + parent | tg | D + parent | tg_stmt | D +(3 rows) + +drop table parent, child1; +-- Check processing of foreign key triggers +create table parent (a int primary key, f int references parent) + partition by list (a); +create table child1 partition of parent for values in (1); +select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, + tgfoid::regproc, tgenabled + from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgfoid; + tgrelid | tgname | tgfoid | tgenabled +---------+-------------------------+------------------------+----------- + child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O + child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O + parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O + parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O + parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | O + parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | O +(6 rows) + +alter table parent disable trigger all; +select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, + tgfoid::regproc, tgenabled + from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgfoid; + tgrelid | tgname | tgfoid | tgenabled +---------+-------------------------+------------------------+----------- + child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | D + child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | D + parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | D + parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | D + parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | D + parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | D +(6 rows) + +drop table parent, child1; +-- Verify that firing state propagates correctly on creation, too +CREATE TABLE trgfire (i int) PARTITION BY RANGE (i); +CREATE TABLE trgfire1 PARTITION OF trgfire FOR VALUES FROM (1) TO (10); +CREATE OR REPLACE FUNCTION tgf() RETURNS trigger LANGUAGE plpgsql + AS $$ begin raise exception 'except'; end $$; +CREATE TRIGGER tg AFTER INSERT ON trgfire FOR EACH ROW EXECUTE FUNCTION tgf(); +INSERT INTO trgfire VALUES (1); +ERROR: except +CONTEXT: PL/pgSQL function tgf() line 1 at RAISE +ALTER TABLE trgfire DISABLE TRIGGER tg; +INSERT INTO trgfire VALUES (1); +CREATE TABLE trgfire2 PARTITION OF trgfire FOR VALUES FROM (10) TO (20); +INSERT INTO trgfire VALUES (11); +CREATE TABLE trgfire3 (LIKE trgfire); +ALTER TABLE trgfire ATTACH PARTITION trgfire3 FOR VALUES FROM (20) TO (30); +INSERT INTO trgfire VALUES (21); +CREATE TABLE trgfire4 PARTITION OF trgfire FOR VALUES FROM (30) TO (40) PARTITION BY LIST (i); +CREATE TABLE trgfire4_30 PARTITION OF trgfire4 FOR VALUES IN (30); +INSERT INTO trgfire VALUES (30); +CREATE TABLE trgfire5 (LIKE trgfire) PARTITION BY LIST (i); +CREATE TABLE trgfire5_40 PARTITION OF trgfire5 FOR VALUES IN (40); +ALTER TABLE trgfire ATTACH PARTITION trgfire5 FOR VALUES FROM (40) TO (50); +INSERT INTO trgfire VALUES (40); +SELECT tgrelid::regclass, tgenabled FROM pg_trigger + WHERE tgrelid::regclass IN (SELECT oid from pg_class where relname LIKE 'trgfire%') + ORDER BY tgrelid::regclass::text; + tgrelid | tgenabled +-------------+----------- + trgfire | D + trgfire1 | D + trgfire2 | D + trgfire3 | D + trgfire4 | D + trgfire4_30 | D + trgfire5 | D + trgfire5_40 | D +(8 rows) + +ALTER TABLE trgfire ENABLE TRIGGER tg; +INSERT INTO trgfire VALUES (1); +ERROR: except +CONTEXT: PL/pgSQL function tgf() line 1 at RAISE +INSERT INTO trgfire VALUES (11); +ERROR: except +CONTEXT: PL/pgSQL function tgf() line 1 at RAISE +INSERT INTO trgfire VALUES (21); +ERROR: except +CONTEXT: PL/pgSQL function tgf() line 1 at RAISE +INSERT INTO trgfire VALUES (30); +ERROR: except +CONTEXT: PL/pgSQL function tgf() line 1 at RAISE +INSERT INTO trgfire VALUES (40); +ERROR: except +CONTEXT: PL/pgSQL function tgf() line 1 at RAISE +DROP TABLE trgfire; +DROP FUNCTION tgf(); +-- +-- Test the interaction between transition tables and both kinds of +-- inheritance. We'll dump the contents of the transition tables in a +-- format that shows the attribute order, so that we can distinguish +-- tuple formats (though not dropped attributes). +-- +-- Let these function be created since they are also used in other tests. +create or replace function dump_insert() returns trigger language plpgsql as +$$ + begin + raise notice 'trigger = %, new table = %', + TG_NAME, + (select string_agg(new_table::text, ', ' order by a) from new_table); + return null; + end; +$$; +create or replace function dump_update() returns trigger language plpgsql as +$$ + begin + raise notice 'trigger = %, old table = %, new table = %', + TG_NAME, + (select string_agg(old_table::text, ', ' order by a) from old_table), + (select string_agg(new_table::text, ', ' order by a) from new_table); + return null; + end; +$$; +create or replace function dump_delete() returns trigger language plpgsql as +$$ + begin + raise notice 'trigger = %, old table = %', + TG_NAME, + (select string_agg(old_table::text, ', ' order by a) from old_table); + return null; + end; +$$; +-- +-- Verify behavior of statement triggers on partition hierarchy with +-- transition tables. Tuples should appear to each trigger in the +-- format of the relation the trigger is attached to. +-- +-- set up a partition hierarchy with some different TupleDescriptors +create table parent (a text, b int) partition by list (a); +-- a child matching parent +create table child1 partition of parent for values in ('AAA'); +-- a child with a dropped column +create table child2 (x int, a text, b int); +alter table child2 drop column x; +alter table parent attach partition child2 for values in ('BBB'); +-- a child with a different column order +create table child3 (b int, a text); +alter table parent attach partition child3 for values in ('CCC'); +create trigger parent_insert_trig + after insert on parent referencing new table as new_table + for each statement execute procedure dump_insert(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 2: after insert on parent referencing new table as new_table + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +/* YB: Transition tables (REFERENCING clause) are not supported +create trigger parent_update_trig + after update on parent referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger parent_delete_trig + after delete on parent referencing old table as old_table + for each statement execute procedure dump_delete(); +create trigger child1_insert_trig + after insert on child1 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child1_update_trig + after update on child1 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child1_delete_trig + after delete on child1 referencing old table as old_table + for each statement execute procedure dump_delete(); + +create trigger child2_insert_trig + after insert on child2 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child2_update_trig + after update on child2 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child2_delete_trig + after delete on child2 referencing old table as old_table + for each statement execute procedure dump_delete(); + +create trigger child3_insert_trig + after insert on child3 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child3_update_trig + after update on child3 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child3_delete_trig + after delete on child3 referencing old table as old_table + for each statement execute procedure dump_delete(); + +SELECT trigger_name, event_manipulation, event_object_schema, event_object_table, + action_order, action_condition, action_orientation, action_timing, + action_reference_old_table, action_reference_new_table + FROM information_schema.triggers + WHERE event_object_table IN ('parent', 'child1', 'child2', 'child3') + ORDER BY trigger_name COLLATE "C", 2; + +-- insert directly into children sees respective child-format tuples +insert into child1 values ('AAA', 42); +insert into child2 values ('BBB', 42); +insert into child3 values (42, 'CCC'); + +-- update via parent sees parent-format tuples +update parent set b = b + 1; + +-- delete via parent sees parent-format tuples +delete from parent; + +-- insert into parent sees parent-format tuples +insert into parent values ('AAA', 42); +insert into parent values ('BBB', 42); +insert into parent values ('CCC', 42); + +-- delete from children sees respective child-format tuples +delete from child1; +delete from child2; +delete from child3; + +-- copy into parent sees parent-format tuples +copy parent (a, b) from stdin; +AAA 42 +BBB 42 +CCC 42 +\. + +-- DML affecting parent sees tuples collected from children even if +-- there is no transition table trigger on the children +drop trigger child1_insert_trig on child1; +drop trigger child1_update_trig on child1; +drop trigger child1_delete_trig on child1; +drop trigger child2_insert_trig on child2; +drop trigger child2_update_trig on child2; +drop trigger child2_delete_trig on child2; +drop trigger child3_insert_trig on child3; +drop trigger child3_update_trig on child3; +drop trigger child3_delete_trig on child3; +delete from parent; + +-- copy into parent sees tuples collected from children even if there +-- is no transition-table trigger on the children +copy parent (a, b) from stdin; +AAA 42 +BBB 42 +CCC 42 +\. + +-- insert into parent with a before trigger on a child tuple before +-- insertion, and we capture the newly modified row in parent format +create or replace function intercept_insert() returns trigger language plpgsql as +$$ + begin + new.b = new.b + 1000; + return new; + end; +$$; + +create trigger intercept_insert_child3 + before insert on child3 + for each row execute procedure intercept_insert(); + + +-- insert, parent trigger sees post-modification parent-format tuple +insert into parent values ('AAA', 42), ('BBB', 42), ('CCC', 66); + +-- copy, parent trigger sees post-modification parent-format tuple +copy parent (a, b) from stdin; +AAA 42 +BBB 42 +CCC 234 +\. + +drop table child1, child2, child3, parent; +drop function intercept_insert(); + +-- +-- Verify prohibition of row triggers with transition triggers on +-- partitions +-- +create table parent (a text, b int) partition by list (a); +create table child partition of parent for values in ('AAA'); + +-- adding row trigger with transition table fails +create trigger child_row_trig + after insert on child referencing new table as new_table + for each row execute procedure dump_insert(); + +-- detaching it first works +alter table parent detach partition child; + +create trigger child_row_trig + after insert on child referencing new table as new_table + for each row execute procedure dump_insert(); + +-- but now we're not allowed to reattach it +alter table parent attach partition child for values in ('AAA'); + +-- drop the trigger, and now we're allowed to attach it again +drop trigger child_row_trig on child; +alter table parent attach partition child for values in ('AAA'); + +drop table child, parent; + +-- +-- Verify behavior of statement triggers on (non-partition) +-- inheritance hierarchy with transition tables; similar to the +-- partition case, except there is no rerouting on insertion and child +-- tables can have extra columns +-- + +-- set up inheritance hierarchy with different TupleDescriptors +create table parent (a text, b int); + +-- a child matching parent +create table child1 () inherits (parent); + +-- a child with a different column order +create table child2 (b int, a text); +alter table child2 inherit parent; + +-- a child with an extra column +create table child3 (c text) inherits (parent); + +create trigger parent_insert_trig + after insert on parent referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger parent_update_trig + after update on parent referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger parent_delete_trig + after delete on parent referencing old table as old_table + for each statement execute procedure dump_delete(); + +create trigger child1_insert_trig + after insert on child1 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child1_update_trig + after update on child1 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child1_delete_trig + after delete on child1 referencing old table as old_table + for each statement execute procedure dump_delete(); + +create trigger child2_insert_trig + after insert on child2 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child2_update_trig + after update on child2 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child2_delete_trig + after delete on child2 referencing old table as old_table + for each statement execute procedure dump_delete(); + +create trigger child3_insert_trig + after insert on child3 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child3_update_trig + after update on child3 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child3_delete_trig + after delete on child3 referencing old table as old_table + for each statement execute procedure dump_delete(); + +-- insert directly into children sees respective child-format tuples +insert into child1 values ('AAA', 42); +insert into child2 values (42, 'BBB'); +insert into child3 values ('CCC', 42, 'foo'); + +-- update via parent sees parent-format tuples +update parent set b = b + 1; + +-- delete via parent sees parent-format tuples +delete from parent; + +-- reinsert values into children for next test... +insert into child1 values ('AAA', 42); +insert into child2 values (42, 'BBB'); +insert into child3 values ('CCC', 42, 'foo'); + +-- delete from children sees respective child-format tuples +delete from child1; +delete from child2; +delete from child3; + +-- copy into parent sees parent-format tuples (no rerouting, so these +-- are really inserted into the parent) +copy parent (a, b) from stdin; +AAA 42 +BBB 42 +CCC 42 +\. + +-- same behavior for copy if there is an index (interesting because rows are +-- captured by a different code path in copy.c if there are indexes) +create index on parent(b); +copy parent (a, b) from stdin; +DDD 42 +\. + +-- DML affecting parent sees tuples collected from children even if +-- there is no transition table trigger on the children +drop trigger child1_insert_trig on child1; +drop trigger child1_update_trig on child1; +drop trigger child1_delete_trig on child1; +drop trigger child2_insert_trig on child2; +drop trigger child2_update_trig on child2; +drop trigger child2_delete_trig on child2; +drop trigger child3_insert_trig on child3; +drop trigger child3_update_trig on child3; +drop trigger child3_delete_trig on child3; +delete from parent; + +drop table child1, child2, child3, parent; + +-- +-- Verify prohibition of row triggers with transition triggers on +-- inheritance children +-- +create table parent (a text, b int); +create table child () inherits (parent); + +-- adding row trigger with transition table fails +create trigger child_row_trig + after insert on child referencing new table as new_table + for each row execute procedure dump_insert(); + +-- disinheriting it first works +alter table child no inherit parent; + +create trigger child_row_trig + after insert on child referencing new table as new_table + for each row execute procedure dump_insert(); + +-- but now we're not allowed to make it inherit anymore +alter table child inherit parent; + +-- drop the trigger, and now we're allowed to make it inherit again +drop trigger child_row_trig on child; +alter table child inherit parent; +*/ +drop table parent; -- YB: child table was commented out above +-- +-- Verify behavior of queries with wCTEs, where multiple transition +-- tuplestores can be active at the same time because there are +-- multiple DML statements that might fire triggers with transition +-- tables +-- +create table table1 (a int); +create table table2 (a text); +create trigger table1_trig + after insert on table1 referencing new table as new_table + for each statement execute procedure dump_insert(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 2: after insert on table1 referencing new table as new_table + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +-- TODO When transition tables ('REFERENCING' clause) are supported in YB enable the tests below. +/* +create trigger table2_trig + after insert on table2 referencing new table as new_table + for each statement execute procedure dump_insert(); + +with wcte as (insert into table1 values (42)) + insert into table2 values ('hello world'); + +with wcte as (insert into table1 values (43)) + insert into table1 values (44); + +select * from table1; +select * from table2; + +drop table table1; +drop table table2; + +-- +-- Verify behavior of INSERT ... ON CONFLICT DO UPDATE ... with +-- transition tables. +-- + +create table my_table (a int primary key, b text); +create trigger my_table_insert_trig + after insert on my_table referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger my_table_update_trig + after update on my_table referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); + +-- inserts only +insert into my_table values (1, 'AAA'), (2, 'BBB') + on conflict (a) do + update set b = my_table.b || ':' || excluded.b; + +-- mixture of inserts and updates +insert into my_table values (1, 'AAA'), (2, 'BBB'), (3, 'CCC'), (4, 'DDD') + on conflict (a) do + update set b = my_table.b || ':' || excluded.b; + +-- updates only +insert into my_table values (3, 'CCC'), (4, 'DDD') + on conflict (a) do + update set b = my_table.b || ':' || excluded.b; + +-- +-- now using a partitioned table +-- + +create table iocdu_tt_parted (a int primary key, b text) partition by list (a); +create table iocdu_tt_parted1 partition of iocdu_tt_parted for values in (1); +create table iocdu_tt_parted2 partition of iocdu_tt_parted for values in (2); +create table iocdu_tt_parted3 partition of iocdu_tt_parted for values in (3); +create table iocdu_tt_parted4 partition of iocdu_tt_parted for values in (4); +create trigger iocdu_tt_parted_insert_trig + after insert on iocdu_tt_parted referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger iocdu_tt_parted_update_trig + after update on iocdu_tt_parted referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); + +-- inserts only +insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB') + on conflict (a) do + update set b = iocdu_tt_parted.b || ':' || excluded.b; + +-- mixture of inserts and updates +insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB'), (3, 'CCC'), (4, 'DDD') + on conflict (a) do + update set b = iocdu_tt_parted.b || ':' || excluded.b; + +-- updates only +insert into iocdu_tt_parted values (3, 'CCC'), (4, 'DDD') + on conflict (a) do + update set b = iocdu_tt_parted.b || ':' || excluded.b; + +drop table iocdu_tt_parted; + +-- +-- Verify that you can't create a trigger with transition tables for +-- more than one event. +-- + +create trigger my_table_multievent_trig + after insert or update on my_table referencing new table as new_table + for each statement execute procedure dump_insert(); + +-- +-- Verify that you can't create a trigger with transition tables with +-- a column list. +-- + +create trigger my_table_col_update_trig + after update of b on my_table referencing new table as new_table + for each statement execute procedure dump_insert(); + +drop table my_table; + +-- +-- Test firing of triggers with transition tables by foreign key cascades +-- + +create table refd_table (a int primary key, b text); +create table trig_table (a int, b text, + foreign key (a) references refd_table on update cascade on delete cascade +); + +create trigger trig_table_before_trig + before insert or update or delete on trig_table + for each statement execute procedure trigger_func('trig_table'); +create trigger trig_table_insert_trig + after insert on trig_table referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger trig_table_update_trig + after update on trig_table referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger trig_table_delete_trig + after delete on trig_table referencing old table as old_table + for each statement execute procedure dump_delete(); + +insert into refd_table values + (1, 'one'), + (2, 'two'), + (3, 'three'); +insert into trig_table values + (1, 'one a'), + (1, 'one b'), + (2, 'two a'), + (2, 'two b'), + (3, 'three a'), + (3, 'three b'); + +update refd_table set a = 11 where b = 'one'; + +select * from trig_table; + +delete from refd_table where length(b) = 3; + +select * from trig_table; + +drop table refd_table, trig_table; + +-- +-- self-referential FKs are even more fun +-- + +create table self_ref (a int primary key, + b int references self_ref(a) on delete cascade); + +create trigger self_ref_before_trig + before delete on self_ref + for each statement execute procedure trigger_func('self_ref'); +create trigger self_ref_r_trig + after delete on self_ref referencing old table as old_table + for each row execute procedure dump_delete(); +create trigger self_ref_s_trig + after delete on self_ref referencing old table as old_table + for each statement execute procedure dump_delete(); + +insert into self_ref values (1, null), (2, 1), (3, 2); + +delete from self_ref where a = 1; + +-- without AR trigger, cascaded deletes all end up in one transition table +drop trigger self_ref_r_trig on self_ref; + +insert into self_ref values (1, null), (2, 1), (3, 2), (4, 3); + +delete from self_ref where a = 1; + +drop table self_ref; +*/ +-- cleanup +drop function dump_insert(); +drop function dump_update(); +drop function dump_delete(); +-- +-- Rows per transaction should be disabled if table contains non Referential Integrity triggers. +-- +CREATE TABLE tbl(k INT PRIMARY KEY); +CREATE TABLE shadow_tbl(k INT PRIMARY KEY); +CREATE OR REPLACE FUNCTION trigger_func() RETURNS trigger AS $$ +BEGIN + INSERT INTO shadow_tbl VALUES(NEW.k); + RETURN NEW; +END; $$ +LANGUAGE 'plpgsql'; +CREATE TRIGGER tbl_insert_trigger AFTER INSERT + ON tbl FOR EACH ROW EXECUTE PROCEDURE trigger_func(); +-- A warning should be shown disabling the ROWS_PER_TRANSACTION value. +COPY tbl FROM STDIN WITH (ROWS_PER_TRANSACTION 2); +WARNING: ROWS_PER_TRANSACTION is not supported on table with non RI trigger +DETAIL: Defaulting to using one transaction for the entire copy. +HINT: Set rows_per_transaction option to `0` to disable batching and remove this warning. +DROP TRIGGER tbl_insert_trigger ON tbl; +DROP TABLE shadow_tbl; +DROP TABLE tbl; +-- +-- Tests for CREATE OR REPLACE TRIGGER +-- +create table my_table (id integer); +create function funcA() returns trigger as $$ +begin + raise notice 'hello from funcA'; + return null; +end; $$ language plpgsql; +create function funcB() returns trigger as $$ +begin + raise notice 'hello from funcB'; + return null; +end; $$ language plpgsql; +create trigger my_trig + after insert on my_table + for each row execute procedure funcA(); +create trigger my_trig + before insert on my_table + for each row execute procedure funcB(); -- should fail +ERROR: trigger "my_trig" for relation "my_table" already exists +insert into my_table values (1); +NOTICE: hello from funcA +create or replace trigger my_trig + before insert on my_table + for each row execute procedure funcB(); -- OK +insert into my_table values (2); -- this insert should become a no-op +NOTICE: hello from funcB +table my_table; + id +---- + 1 +(1 row) + +drop table my_table; +-- test CREATE OR REPLACE TRIGGER on partition table +create table parted_trig (a int) partition by range (a); +create table parted_trig_1 partition of parted_trig + for values from (0) to (1000) partition by range (a); +create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100); +create table parted_trig_2 partition of parted_trig for values from (1000) to (2000); +create table default_parted_trig partition of parted_trig default; +-- test that trigger can be replaced by another one +-- at the same level of partition table +create or replace trigger my_trig + after insert on parted_trig + for each row execute procedure funcA(); +insert into parted_trig (a) values (50); +NOTICE: hello from funcA +create or replace trigger my_trig + after insert on parted_trig + for each row execute procedure funcB(); +insert into parted_trig (a) values (50); +NOTICE: hello from funcB +-- test that child trigger cannot be replaced directly +create or replace trigger my_trig + after insert on parted_trig + for each row execute procedure funcA(); +insert into parted_trig (a) values (50); +NOTICE: hello from funcA +create or replace trigger my_trig + after insert on parted_trig_1 + for each row execute procedure funcB(); -- should fail +ERROR: trigger "my_trig" for relation "parted_trig_1" is an internal or a child trigger +insert into parted_trig (a) values (50); +NOTICE: hello from funcA +drop trigger my_trig on parted_trig; +insert into parted_trig (a) values (50); +-- test that user trigger can be overwritten by one defined at upper level +create trigger my_trig + after insert on parted_trig_1 + for each row execute procedure funcA(); +insert into parted_trig (a) values (50); +NOTICE: hello from funcA +create trigger my_trig + after insert on parted_trig + for each row execute procedure funcB(); -- should fail +ERROR: trigger "my_trig" for relation "parted_trig_1" already exists +insert into parted_trig (a) values (50); +NOTICE: hello from funcA +create or replace trigger my_trig + after insert on parted_trig + for each row execute procedure funcB(); +insert into parted_trig (a) values (50); +NOTICE: hello from funcB +-- cleanup +drop table parted_trig; +drop function funcA(); +drop function funcB(); +-- Leave around some objects for other tests +create table trigger_parted (a int primary key) partition by list (a); +create function trigger_parted_trigfunc() returns trigger language plpgsql as + $$ begin end; $$; +create trigger aft_row after insert or update on trigger_parted + for each row execute function trigger_parted_trigfunc(); +create table trigger_parted_p1 partition of trigger_parted for values in (1) + partition by list (a); +create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1); +create table trigger_parted_p2 partition of trigger_parted for values in (2) + partition by list (a); +create table trigger_parted_p2_2 partition of trigger_parted_p2 for values in (2); +alter table only trigger_parted_p2 disable trigger aft_row; +alter table trigger_parted_p2_2 enable always trigger aft_row; +-- Test trigger renaming on partitioned tables +create table grandparent (id int, primary key (id)) partition by range (id); +create table middle partition of grandparent for values from (1) to (10) +partition by range (id); +create table chi partition of middle for values from (1) to (5); +create table cho partition of middle for values from (6) to (10); +create function f () returns trigger as +$$ begin return new; end; $$ +language plpgsql; +create trigger a after insert on grandparent +for each row execute procedure f(); +alter trigger a on grandparent rename to b; +select tgrelid::regclass, tgname, +(select tgname from pg_trigger tr where tr.oid = pg_trigger.tgparentid) parent_tgname +from pg_trigger where tgrelid in (select relid from pg_partition_tree('grandparent')) +order by tgname, tgrelid::regclass::text COLLATE "C"; + tgrelid | tgname | parent_tgname +-------------+--------+--------------- + chi | b | b + cho | b | b + grandparent | b | + middle | b | b +(4 rows) + +alter trigger a on only grandparent rename to b; -- ONLY not supported +ERROR: syntax error at or near "only" +LINE 1: alter trigger a on only grandparent rename to b; + ^ +alter trigger b on middle rename to c; -- can't rename trigger on partition +ERROR: cannot rename trigger "b" on table "middle" +HINT: Rename the trigger on the partitioned table "grandparent" instead. +create trigger c after insert on middle +for each row execute procedure f(); +alter trigger b on grandparent rename to c; +ERROR: trigger "c" for relation "middle" already exists +-- Rename cascading does not affect statement triggers +create trigger p after insert on grandparent for each statement execute function f(); +create trigger p after insert on middle for each statement execute function f(); +alter trigger p on grandparent rename to q; +select tgrelid::regclass, tgname, +(select tgname from pg_trigger tr where tr.oid = pg_trigger.tgparentid) parent_tgname +from pg_trigger where tgrelid in (select relid from pg_partition_tree('grandparent')) +order by tgname, tgrelid::regclass::text COLLATE "C"; + tgrelid | tgname | parent_tgname +-------------+--------+--------------- + chi | b | b + cho | b | b + grandparent | b | + middle | b | b + chi | c | c + cho | c | c + middle | c | + middle | p | + grandparent | q | +(9 rows) + +drop table grandparent; +-- Trigger renaming does not recurse on legacy inheritance +create table parent (a int); +create table child () inherits (parent); +create trigger parenttrig after insert on parent +for each row execute procedure f(); +create trigger parenttrig after insert on child +for each row execute procedure f(); +alter trigger parenttrig on parent rename to anothertrig; +\d+ child + Table "public.child" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | +Triggers: + parenttrig AFTER INSERT ON child FOR EACH ROW EXECUTE FUNCTION f() +Inherits: parent + +drop table parent, child; +drop function f(); diff --git a/src/test/regress/expected/yb.port.truncate.out b/src/test/regress/expected/yb.port.truncate.out new file mode 100644 index 0000000000000..8cbed909a385f --- /dev/null +++ b/src/test/regress/expected/yb.port.truncate.out @@ -0,0 +1,605 @@ +-- Test basic TRUNCATE functionality. +CREATE TABLE truncate_a (col1 integer primary key); +INSERT INTO truncate_a VALUES (1); +INSERT INTO truncate_a VALUES (2); +SELECT * FROM truncate_a; + col1 +------ + 1 + 2 +(2 rows) + +-- Roll truncate back +BEGIN; +TRUNCATE truncate_a; +ROLLBACK; +SELECT * FROM truncate_a; -- YB: TRUNCATE is currently not rolled back + col1 +------ +(0 rows) + +INSERT INTO truncate_a VALUES (1); -- YB: add rows back +INSERT INTO truncate_a VALUES (2); -- YB: add rows back +-- Commit the truncate this time +BEGIN; +TRUNCATE truncate_a; +COMMIT; +SELECT * FROM truncate_a; + col1 +------ +(0 rows) + +-- Test foreign-key checks +CREATE TABLE trunc_b (a int REFERENCES truncate_a); +CREATE TABLE trunc_c (a serial PRIMARY KEY); +CREATE TABLE trunc_d (a int REFERENCES trunc_c); +CREATE TABLE trunc_e (a int REFERENCES truncate_a, b int REFERENCES trunc_c); +TRUNCATE TABLE truncate_a; -- fail +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "trunc_b" references "truncate_a". +HINT: Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE. +TRUNCATE TABLE truncate_a,trunc_b; -- fail +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "trunc_e" references "truncate_a". +HINT: Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE. +TRUNCATE TABLE truncate_a,trunc_b,trunc_e; -- ok +TRUNCATE TABLE truncate_a,trunc_e; -- fail +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "trunc_b" references "truncate_a". +HINT: Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE. +TRUNCATE TABLE trunc_c; -- fail +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "trunc_d" references "trunc_c". +HINT: Truncate table "trunc_d" at the same time, or use TRUNCATE ... CASCADE. +TRUNCATE TABLE trunc_c,trunc_d; -- fail +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "trunc_e" references "trunc_c". +HINT: Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE. +TRUNCATE TABLE trunc_c,trunc_d,trunc_e; -- ok +TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a; -- fail +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "trunc_b" references "truncate_a". +HINT: Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE. +TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a,trunc_b; -- ok +TRUNCATE TABLE truncate_a RESTRICT; -- fail +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "trunc_b" references "truncate_a". +HINT: Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE. +TRUNCATE TABLE truncate_a CASCADE; -- ok +NOTICE: truncate cascades to table "trunc_b" +NOTICE: truncate cascades to table "trunc_e" +-- circular references +ALTER TABLE truncate_a ADD FOREIGN KEY (col1) REFERENCES trunc_c; +-- Add some data to verify that truncating actually works ... +INSERT INTO trunc_c VALUES (1); +INSERT INTO truncate_a VALUES (1); +INSERT INTO trunc_b VALUES (1); +INSERT INTO trunc_d VALUES (1); +INSERT INTO trunc_e VALUES (1,1); +TRUNCATE TABLE trunc_c; +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "truncate_a" references "trunc_c". +HINT: Truncate table "truncate_a" at the same time, or use TRUNCATE ... CASCADE. +TRUNCATE TABLE trunc_c,truncate_a; +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "trunc_d" references "trunc_c". +HINT: Truncate table "trunc_d" at the same time, or use TRUNCATE ... CASCADE. +TRUNCATE TABLE trunc_c,truncate_a,trunc_d; +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "trunc_e" references "trunc_c". +HINT: Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE. +TRUNCATE TABLE trunc_c,truncate_a,trunc_d,trunc_e; +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "trunc_b" references "truncate_a". +HINT: Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE. +TRUNCATE TABLE trunc_c,truncate_a,trunc_d,trunc_e,trunc_b; +-- Verify that truncating did actually work +SELECT * FROM truncate_a + UNION ALL + SELECT * FROM trunc_c + UNION ALL + SELECT * FROM trunc_b + UNION ALL + SELECT * FROM trunc_d; + col1 +------ +(0 rows) + +SELECT * FROM trunc_e; + a | b +---+--- +(0 rows) + +-- Add data again to test TRUNCATE ... CASCADE +INSERT INTO trunc_c VALUES (1); +INSERT INTO truncate_a VALUES (1); +INSERT INTO trunc_b VALUES (1); +INSERT INTO trunc_d VALUES (1); +INSERT INTO trunc_e VALUES (1,1); +TRUNCATE TABLE trunc_c CASCADE; -- ok +NOTICE: truncate cascades to table "truncate_a" +NOTICE: truncate cascades to table "trunc_d" +NOTICE: truncate cascades to table "trunc_e" +NOTICE: truncate cascades to table "trunc_b" +SELECT * FROM truncate_a + UNION ALL + SELECT * FROM trunc_c + UNION ALL + SELECT * FROM trunc_b + UNION ALL + SELECT * FROM trunc_d; + col1 +------ +(0 rows) + +SELECT * FROM trunc_e; + a | b +---+--- +(0 rows) + +DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE; +-- Test TRUNCATE with inheritance +CREATE TABLE trunc_f (col1 integer primary key); +INSERT INTO trunc_f VALUES (1); +INSERT INTO trunc_f VALUES (2); +CREATE TABLE trunc_fa (col2a text) INHERITS (trunc_f); +INSERT INTO trunc_fa VALUES (3, 'three'); +CREATE TABLE trunc_fb (col2b int) INHERITS (trunc_f); +INSERT INTO trunc_fb VALUES (4, 444); +CREATE TABLE trunc_faa (col3 text) INHERITS (trunc_fa); +INSERT INTO trunc_faa VALUES (5, 'five', 'FIVE'); +CREATE OR REPLACE PROCEDURE yb_reset_trunc_f() +LANGUAGE plpgsql +AS $$ +BEGIN + DELETE FROM trunc_f; + INSERT INTO trunc_f VALUES (1); + INSERT INTO trunc_f VALUES (2); + INSERT INTO trunc_fa VALUES (3, 'three'); + INSERT INTO trunc_fb VALUES (4, 444); + INSERT INTO trunc_faa VALUES (5, 'five', 'FIVE'); +END; +$$; +BEGIN; +SELECT * FROM trunc_f; + col1 +------ + 1 + 2 + 3 + 4 + 5 +(5 rows) + +TRUNCATE trunc_f; +SELECT * FROM trunc_f; + col1 +------ +(0 rows) + +ROLLBACK; +CALL yb_reset_trunc_f(); -- YB: TRUNCATE is nontransactional so cannot be rolled back +BEGIN; +SELECT * FROM trunc_f; + col1 +------ + 1 + 2 + 3 + 4 + 5 +(5 rows) + +TRUNCATE ONLY trunc_f; +SELECT * FROM trunc_f; + col1 +------ + 3 + 4 + 5 +(3 rows) + +ROLLBACK; +CALL yb_reset_trunc_f(); -- YB: TRUNCATE is nontransactional so cannot be rolled back +BEGIN; +SELECT * FROM trunc_f; + col1 +------ + 1 + 2 + 3 + 4 + 5 +(5 rows) + +SELECT * FROM trunc_fa; + col1 | col2a +------+------- + 3 | three + 5 | five +(2 rows) + +SELECT * FROM trunc_faa; + col1 | col2a | col3 +------+-------+------ + 5 | five | FIVE +(1 row) + +TRUNCATE ONLY trunc_fb, ONLY trunc_fa; +SELECT * FROM trunc_f; + col1 +------ + 1 + 2 + 5 +(3 rows) + +SELECT * FROM trunc_fa; + col1 | col2a +------+------- + 5 | five +(1 row) + +SELECT * FROM trunc_faa; + col1 | col2a | col3 +------+-------+------ + 5 | five | FIVE +(1 row) + +ROLLBACK; +CALL yb_reset_trunc_f(); -- YB: TRUNCATE is nontransactional so cannot be rolled back +BEGIN; +SELECT * FROM trunc_f; + col1 +------ + 1 + 2 + 3 + 4 + 5 +(5 rows) + +SELECT * FROM trunc_fa; + col1 | col2a +------+------- + 3 | three + 5 | five +(2 rows) + +SELECT * FROM trunc_faa; + col1 | col2a | col3 +------+-------+------ + 5 | five | FIVE +(1 row) + +TRUNCATE ONLY trunc_fb, trunc_fa; +SELECT * FROM trunc_f; + col1 +------ + 1 + 2 +(2 rows) + +SELECT * FROM trunc_fa; + col1 | col2a +------+------- +(0 rows) + +SELECT * FROM trunc_faa; + col1 | col2a | col3 +------+-------+------ +(0 rows) + +ROLLBACK; +\set VERBOSITY terse \\ -- YB: suppress cascade details +DROP TABLE trunc_f CASCADE; +NOTICE: drop cascades to 3 other objects +\set VERBOSITY default \\ -- YB +-- Test ON TRUNCATE triggers +CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text); +CREATE TABLE trunc_trigger_log (tgop text, tglevel text, tgwhen text, + tgargv text, tgtable name, rowcount bigint); +CREATE FUNCTION trunctrigger() RETURNS trigger as $$ +declare c bigint; +begin + execute 'select count(*) from ' || quote_ident(tg_table_name) into c; + insert into trunc_trigger_log values + (TG_OP, TG_LEVEL, TG_WHEN, TG_ARGV[0], tg_table_name, c); + return null; +end; +$$ LANGUAGE plpgsql; +-- basic before trigger +INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux'); +CREATE TRIGGER t +BEFORE TRUNCATE ON trunc_trigger_test +FOR EACH STATEMENT +EXECUTE PROCEDURE trunctrigger('before trigger truncate'); +SELECT count(*) as "Row count in test table" FROM trunc_trigger_test; + Row count in test table +------------------------- + 2 +(1 row) + +SELECT * FROM trunc_trigger_log; + tgop | tglevel | tgwhen | tgargv | tgtable | rowcount +------+---------+--------+--------+---------+---------- +(0 rows) + +TRUNCATE trunc_trigger_test; +SELECT count(*) as "Row count in test table" FROM trunc_trigger_test; + Row count in test table +------------------------- + 0 +(1 row) + +SELECT * FROM trunc_trigger_log; + tgop | tglevel | tgwhen | tgargv | tgtable | rowcount +----------+-----------+--------+-------------------------+--------------------+---------- + TRUNCATE | STATEMENT | BEFORE | before trigger truncate | trunc_trigger_test | 2 +(1 row) + +DROP TRIGGER t ON trunc_trigger_test; +truncate trunc_trigger_log; +-- same test with an after trigger +INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux'); +CREATE TRIGGER tt +AFTER TRUNCATE ON trunc_trigger_test +FOR EACH STATEMENT +EXECUTE PROCEDURE trunctrigger('after trigger truncate'); +SELECT count(*) as "Row count in test table" FROM trunc_trigger_test; + Row count in test table +------------------------- + 2 +(1 row) + +SELECT * FROM trunc_trigger_log; + tgop | tglevel | tgwhen | tgargv | tgtable | rowcount +------+---------+--------+--------+---------+---------- +(0 rows) + +TRUNCATE trunc_trigger_test; +SELECT count(*) as "Row count in test table" FROM trunc_trigger_test; + Row count in test table +------------------------- + 0 +(1 row) + +SELECT * FROM trunc_trigger_log; + tgop | tglevel | tgwhen | tgargv | tgtable | rowcount +----------+-----------+--------+------------------------+--------------------+---------- + TRUNCATE | STATEMENT | AFTER | after trigger truncate | trunc_trigger_test | 0 +(1 row) + +DROP TABLE trunc_trigger_test; +DROP TABLE trunc_trigger_log; +DROP FUNCTION trunctrigger(); +-- test TRUNCATE ... RESTART IDENTITY +CREATE SEQUENCE truncate_a_id1 START WITH 33; +CREATE TABLE truncate_a (id serial, + id1 integer default nextval('truncate_a_id1'), PRIMARY KEY (id ASC)); -- YB: add pk for query ordering +ALTER SEQUENCE truncate_a_id1 OWNED BY truncate_a.id1; +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + id | id1 +----+----- + 1 | 33 + 2 | 34 +(2 rows) + +TRUNCATE truncate_a; +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + id | id1 +----+----- + 3 | 35 + 4 | 36 +(2 rows) + +TRUNCATE truncate_a RESTART IDENTITY; +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + id | id1 +----+----- + 1 | 33 + 2 | 34 +(2 rows) + +CREATE TABLE truncate_b (id int GENERATED ALWAYS AS IDENTITY (START WITH 44), PRIMARY KEY (id ASC)); -- YB: add pk for query ordering +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + id +---- + 44 + 45 +(2 rows) + +TRUNCATE truncate_b; +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + id +---- + 46 + 47 +(2 rows) + +TRUNCATE truncate_b RESTART IDENTITY; +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + id +---- + 44 + 45 +(2 rows) + +/* YB: uncomment when TRUNCATE rollback is supported +-- check rollback of a RESTART IDENTITY operation +BEGIN; +TRUNCATE truncate_a RESTART IDENTITY; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; +ROLLBACK; +*/ -- YB +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + id | id1 +----+----- + 1 | 33 + 2 | 34 + 3 | 35 + 4 | 36 +(4 rows) + +DROP TABLE truncate_a; +SELECT nextval('truncate_a_id1'); -- fail, seq should have been dropped +ERROR: relation "truncate_a_id1" does not exist +LINE 1: SELECT nextval('truncate_a_id1'); + ^ +-- partitioned table +CREATE TABLE truncparted (a int, b char) PARTITION BY LIST (a); +-- error, can't truncate a partitioned table +TRUNCATE ONLY truncparted; +ERROR: cannot truncate only a partitioned table +HINT: Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly. +CREATE TABLE truncparted1 PARTITION OF truncparted FOR VALUES IN (1); +INSERT INTO truncparted VALUES (1, 'a'); +-- error, must truncate partitions +TRUNCATE ONLY truncparted; +ERROR: cannot truncate only a partitioned table +HINT: Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly. +TRUNCATE truncparted; +DROP TABLE truncparted; +-- foreign key on partitioned table: partition key is referencing column. +-- Make sure truncate did execute on all tables +CREATE FUNCTION tp_ins_data() RETURNS void LANGUAGE plpgsql AS $$ + BEGIN + INSERT INTO truncprim VALUES (1), (100), (150); + INSERT INTO truncpart VALUES (1), (100), (150); + END +$$; +CREATE FUNCTION tp_chk_data(OUT pktb regclass, OUT pkval int, OUT fktb regclass, OUT fkval int) + RETURNS SETOF record LANGUAGE plpgsql AS $$ + BEGIN + RETURN QUERY SELECT + pk.tableoid::regclass, pk.a, fk.tableoid::regclass, fk.a + FROM truncprim pk FULL JOIN truncpart fk USING (a) + ORDER BY 2, 4; + END +$$; +CREATE TABLE truncprim (a int PRIMARY KEY); +CREATE TABLE truncpart (a int REFERENCES truncprim) + PARTITION BY RANGE (a); +CREATE TABLE truncpart_1 PARTITION OF truncpart FOR VALUES FROM (0) TO (100); +CREATE TABLE truncpart_2 PARTITION OF truncpart FOR VALUES FROM (100) TO (200) + PARTITION BY RANGE (a); +CREATE TABLE truncpart_2_1 PARTITION OF truncpart_2 FOR VALUES FROM (100) TO (150); +CREATE TABLE truncpart_2_d PARTITION OF truncpart_2 DEFAULT; +TRUNCATE TABLE truncprim; -- should fail +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "truncpart" references "truncprim". +HINT: Truncate table "truncpart" at the same time, or use TRUNCATE ... CASCADE. +select tp_ins_data(); + tp_ins_data +------------- + +(1 row) + +-- should truncate everything +TRUNCATE TABLE truncprim, truncpart; +select * from tp_chk_data(); + pktb | pkval | fktb | fkval +------+-------+------+------- +(0 rows) + +select tp_ins_data(); + tp_ins_data +------------- + +(1 row) + +-- should truncate everything +TRUNCATE TABLE truncprim CASCADE; +NOTICE: truncate cascades to table "truncpart" +NOTICE: truncate cascades to table "truncpart_1" +NOTICE: truncate cascades to table "truncpart_2" +NOTICE: truncate cascades to table "truncpart_2_1" +NOTICE: truncate cascades to table "truncpart_2_d" +SELECT * FROM tp_chk_data(); + pktb | pkval | fktb | fkval +------+-------+------+------- +(0 rows) + +SELECT tp_ins_data(); + tp_ins_data +------------- + +(1 row) + +-- should truncate all partitions +TRUNCATE TABLE truncpart; +SELECT * FROM tp_chk_data(); + pktb | pkval | fktb | fkval +-----------+-------+------+------- + truncprim | 1 | | + truncprim | 100 | | + truncprim | 150 | | +(3 rows) + +DROP TABLE truncprim, truncpart; +DROP FUNCTION tp_ins_data(), tp_chk_data(); +-- test cascade when referencing a partitioned table +CREATE TABLE trunc_a (a INT PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE trunc_a1 PARTITION OF trunc_a FOR VALUES FROM (0) TO (10); +CREATE TABLE trunc_a2 PARTITION OF trunc_a FOR VALUES FROM (10) TO (20) + PARTITION BY RANGE (a); +CREATE TABLE trunc_a21 PARTITION OF trunc_a2 FOR VALUES FROM (10) TO (12); +CREATE TABLE trunc_a22 PARTITION OF trunc_a2 FOR VALUES FROM (12) TO (16); +CREATE TABLE trunc_a2d PARTITION OF trunc_a2 DEFAULT; +CREATE TABLE trunc_a3 PARTITION OF trunc_a FOR VALUES FROM (20) TO (30); +INSERT INTO trunc_a VALUES (0), (5), (10), (15), (20), (25); +-- truncate a partition cascading to a table +CREATE TABLE ref_b ( + b INT PRIMARY KEY, + a INT REFERENCES trunc_a(a) ON DELETE CASCADE +); +INSERT INTO ref_b VALUES (10, 0), (50, 5), (100, 10), (150, 15); +TRUNCATE TABLE trunc_a1 CASCADE; +NOTICE: truncate cascades to table "ref_b" +SELECT a FROM ref_b; + a +--- +(0 rows) + +DROP TABLE ref_b; +-- truncate a partition cascading to a partitioned table +CREATE TABLE ref_c ( + c INT PRIMARY KEY, + a INT REFERENCES trunc_a(a) ON DELETE CASCADE +) PARTITION BY RANGE (c); +CREATE TABLE ref_c1 PARTITION OF ref_c FOR VALUES FROM (100) TO (200); +CREATE TABLE ref_c2 PARTITION OF ref_c FOR VALUES FROM (200) TO (300); +INSERT INTO ref_c VALUES (100, 10), (150, 15), (200, 20), (250, 25); +TRUNCATE TABLE trunc_a21 CASCADE; +NOTICE: truncate cascades to table "ref_c" +NOTICE: truncate cascades to table "ref_c1" +NOTICE: truncate cascades to table "ref_c2" +SELECT a as "from table ref_c" FROM ref_c; + from table ref_c +------------------ +(0 rows) + +SELECT a as "from table trunc_a" FROM trunc_a ORDER BY a; + from table trunc_a +-------------------- + 15 + 20 + 25 +(3 rows) + +DROP TABLE trunc_a, ref_c; diff --git a/src/test/regress/expected/yb.port.tsdicts.out b/src/test/regress/expected/yb.port.tsdicts.out new file mode 100644 index 0000000000000..2524ec2768f3c --- /dev/null +++ b/src/test/regress/expected/yb.port.tsdicts.out @@ -0,0 +1,620 @@ +--Test text search dictionaries and configurations +-- Test ISpell dictionary with ispell affix file +CREATE TEXT SEARCH DICTIONARY ispell ( + Template=ispell, + DictFile=ispell_sample, + AffFile=ispell_sample +); +SELECT ts_lexize('ispell', 'skies'); + ts_lexize +----------- + {sky} +(1 row) + +SELECT ts_lexize('ispell', 'bookings'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('ispell', 'booking'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('ispell', 'foot'); + ts_lexize +----------- + {foot} +(1 row) + +SELECT ts_lexize('ispell', 'foots'); + ts_lexize +----------- + {foot} +(1 row) + +SELECT ts_lexize('ispell', 'rebookings'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('ispell', 'rebooking'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('ispell', 'rebook'); + ts_lexize +----------- + +(1 row) + +SELECT ts_lexize('ispell', 'unbookings'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('ispell', 'unbooking'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('ispell', 'unbook'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('ispell', 'footklubber'); + ts_lexize +---------------- + {foot,klubber} +(1 row) + +SELECT ts_lexize('ispell', 'footballklubber'); + ts_lexize +------------------------------------------------------ + {footballklubber,foot,ball,klubber,football,klubber} +(1 row) + +SELECT ts_lexize('ispell', 'ballyklubber'); + ts_lexize +---------------- + {ball,klubber} +(1 row) + +SELECT ts_lexize('ispell', 'footballyklubber'); + ts_lexize +--------------------- + {foot,ball,klubber} +(1 row) + +-- Test ISpell dictionary with hunspell affix file +CREATE TEXT SEARCH DICTIONARY hunspell ( + Template=ispell, + DictFile=ispell_sample, + AffFile=hunspell_sample +); +SELECT ts_lexize('hunspell', 'skies'); + ts_lexize +----------- + {sky} +(1 row) + +SELECT ts_lexize('hunspell', 'bookings'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell', 'booking'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell', 'foot'); + ts_lexize +----------- + {foot} +(1 row) + +SELECT ts_lexize('hunspell', 'foots'); + ts_lexize +----------- + {foot} +(1 row) + +SELECT ts_lexize('hunspell', 'rebookings'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell', 'rebooking'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell', 'rebook'); + ts_lexize +----------- + +(1 row) + +SELECT ts_lexize('hunspell', 'unbookings'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('hunspell', 'unbooking'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('hunspell', 'unbook'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('hunspell', 'footklubber'); + ts_lexize +---------------- + {foot,klubber} +(1 row) + +SELECT ts_lexize('hunspell', 'footballklubber'); + ts_lexize +------------------------------------------------------ + {footballklubber,foot,ball,klubber,football,klubber} +(1 row) + +SELECT ts_lexize('hunspell', 'ballyklubber'); + ts_lexize +---------------- + {ball,klubber} +(1 row) + +SELECT ts_lexize('hunspell', 'footballyklubber'); + ts_lexize +--------------------- + {foot,ball,klubber} +(1 row) + +-- Test ISpell dictionary with hunspell affix file with FLAG long parameter +CREATE TEXT SEARCH DICTIONARY hunspell_long ( + Template=ispell, + DictFile=hunspell_sample_long, + AffFile=hunspell_sample_long +); +SELECT ts_lexize('hunspell_long', 'skies'); + ts_lexize +----------- + {sky} +(1 row) + +SELECT ts_lexize('hunspell_long', 'bookings'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell_long', 'booking'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell_long', 'foot'); + ts_lexize +----------- + {foot} +(1 row) + +SELECT ts_lexize('hunspell_long', 'foots'); + ts_lexize +----------- + {foot} +(1 row) + +SELECT ts_lexize('hunspell_long', 'rebookings'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell_long', 'rebooking'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell_long', 'rebook'); + ts_lexize +----------- + +(1 row) + +SELECT ts_lexize('hunspell_long', 'unbookings'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('hunspell_long', 'unbooking'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('hunspell_long', 'unbook'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('hunspell_long', 'booked'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('hunspell_long', 'footklubber'); + ts_lexize +---------------- + {foot,klubber} +(1 row) + +SELECT ts_lexize('hunspell_long', 'footballklubber'); + ts_lexize +------------------------------------------------------ + {footballklubber,foot,ball,klubber,football,klubber} +(1 row) + +SELECT ts_lexize('hunspell_long', 'ballyklubber'); + ts_lexize +---------------- + {ball,klubber} +(1 row) + +SELECT ts_lexize('hunspell_long', 'ballsklubber'); + ts_lexize +---------------- + {ball,klubber} +(1 row) + +SELECT ts_lexize('hunspell_long', 'footballyklubber'); + ts_lexize +--------------------- + {foot,ball,klubber} +(1 row) + +SELECT ts_lexize('hunspell_long', 'ex-machina'); + ts_lexize +--------------- + {ex-,machina} +(1 row) + +-- Test ISpell dictionary with hunspell affix file with FLAG num parameter +CREATE TEXT SEARCH DICTIONARY hunspell_num ( + Template=ispell, + DictFile=hunspell_sample_num, + AffFile=hunspell_sample_num +); +SELECT ts_lexize('hunspell_num', 'skies'); + ts_lexize +----------- + {sky} +(1 row) + +SELECT ts_lexize('hunspell_num', 'sk'); + ts_lexize +----------- + {sky} +(1 row) + +SELECT ts_lexize('hunspell_num', 'bookings'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell_num', 'booking'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell_num', 'foot'); + ts_lexize +----------- + {foot} +(1 row) + +SELECT ts_lexize('hunspell_num', 'foots'); + ts_lexize +----------- + {foot} +(1 row) + +SELECT ts_lexize('hunspell_num', 'rebookings'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell_num', 'rebooking'); + ts_lexize +---------------- + {booking,book} +(1 row) + +SELECT ts_lexize('hunspell_num', 'rebook'); + ts_lexize +----------- + +(1 row) + +SELECT ts_lexize('hunspell_num', 'unbookings'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('hunspell_num', 'unbooking'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('hunspell_num', 'unbook'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('hunspell_num', 'booked'); + ts_lexize +----------- + {book} +(1 row) + +SELECT ts_lexize('hunspell_num', 'footklubber'); + ts_lexize +---------------- + {foot,klubber} +(1 row) + +SELECT ts_lexize('hunspell_num', 'footballklubber'); + ts_lexize +------------------------------------------------------ + {footballklubber,foot,ball,klubber,football,klubber} +(1 row) + +SELECT ts_lexize('hunspell_num', 'ballyklubber'); + ts_lexize +---------------- + {ball,klubber} +(1 row) + +SELECT ts_lexize('hunspell_num', 'footballyklubber'); + ts_lexize +--------------------- + {foot,ball,klubber} +(1 row) + +-- Synonym dictionary +CREATE TEXT SEARCH DICTIONARY synonym ( + Template=synonym, + Synonyms=synonym_sample +); +SELECT ts_lexize('synonym', 'PoStGrEs'); + ts_lexize +----------- + {pgsql} +(1 row) + +SELECT ts_lexize('synonym', 'Gogle'); + ts_lexize +----------- + {googl} +(1 row) + +SELECT ts_lexize('synonym', 'indices'); + ts_lexize +----------- + {index} +(1 row) + +-- Create and simple test thesaurus dictionary +-- More tests in configuration checks because ts_lexize() +-- cannot pass more than one word to thesaurus. +CREATE TEXT SEARCH DICTIONARY thesaurus ( + Template=thesaurus, + DictFile=thesaurus_sample, + Dictionary=english_stem +); +SELECT ts_lexize('thesaurus', 'one'); + ts_lexize +----------- + {1} +(1 row) + +-- Test ispell dictionary in configuration +CREATE TEXT SEARCH CONFIGURATION ispell_tst ( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION ispell_tst ALTER MAPPING FOR + word, numword, asciiword, hword, numhword, asciihword, hword_part, hword_numpart, hword_asciipart + WITH ispell, english_stem; +SELECT to_tsvector('ispell_tst', 'Booking the skies after rebookings for footballklubber from a foot'); + to_tsvector +---------------------------------------------------------------------------------------------------- + 'ball':7 'book':1,5 'booking':1,5 'foot':7,10 'football':7 'footballklubber':7 'klubber':7 'sky':3 +(1 row) + +SELECT to_tsquery('ispell_tst', 'footballklubber'); + to_tsquery +-------------------------------------------------------------------------- + 'footballklubber' | 'foot' & 'ball' & 'klubber' | 'football' & 'klubber' +(1 row) + +SELECT to_tsquery('ispell_tst', 'footballyklubber:b & rebookings:A & sky'); + to_tsquery +------------------------------------------------------------------------ + 'foot':B & 'ball':B & 'klubber':B & ( 'booking':A | 'book':A ) & 'sky' +(1 row) + +-- Test ispell dictionary with hunspell affix in configuration +CREATE TEXT SEARCH CONFIGURATION hunspell_tst ( + COPY=ispell_tst +); +ALTER TEXT SEARCH CONFIGURATION hunspell_tst ALTER MAPPING + REPLACE ispell WITH hunspell; +SELECT to_tsvector('hunspell_tst', 'Booking the skies after rebookings for footballklubber from a foot'); + to_tsvector +---------------------------------------------------------------------------------------------------- + 'ball':7 'book':1,5 'booking':1,5 'foot':7,10 'football':7 'footballklubber':7 'klubber':7 'sky':3 +(1 row) + +SELECT to_tsquery('hunspell_tst', 'footballklubber'); + to_tsquery +-------------------------------------------------------------------------- + 'footballklubber' | 'foot' & 'ball' & 'klubber' | 'football' & 'klubber' +(1 row) + +SELECT to_tsquery('hunspell_tst', 'footballyklubber:b & rebookings:A & sky'); + to_tsquery +------------------------------------------------------------------------ + 'foot':B & 'ball':B & 'klubber':B & ( 'booking':A | 'book':A ) & 'sky' +(1 row) + +SELECT to_tsquery('hunspell_tst', 'footballyklubber:b <-> sky'); + to_tsquery +------------------------------------------------- + ( 'foot':B & 'ball':B & 'klubber':B ) <-> 'sky' +(1 row) + +SELECT phraseto_tsquery('hunspell_tst', 'footballyklubber sky'); + phraseto_tsquery +------------------------------------------- + ( 'foot' & 'ball' & 'klubber' ) <-> 'sky' +(1 row) + +-- Test ispell dictionary with hunspell affix with FLAG long in configuration +ALTER TEXT SEARCH CONFIGURATION hunspell_tst ALTER MAPPING + REPLACE hunspell WITH hunspell_long; +SELECT to_tsvector('hunspell_tst', 'Booking the skies after rebookings for footballklubber from a foot'); + to_tsvector +---------------------------------------------------------------------------------------------------- + 'ball':7 'book':1,5 'booking':1,5 'foot':7,10 'football':7 'footballklubber':7 'klubber':7 'sky':3 +(1 row) + +SELECT to_tsquery('hunspell_tst', 'footballklubber'); + to_tsquery +-------------------------------------------------------------------------- + 'footballklubber' | 'foot' & 'ball' & 'klubber' | 'football' & 'klubber' +(1 row) + +SELECT to_tsquery('hunspell_tst', 'footballyklubber:b & rebookings:A & sky'); + to_tsquery +------------------------------------------------------------------------ + 'foot':B & 'ball':B & 'klubber':B & ( 'booking':A | 'book':A ) & 'sky' +(1 row) + +-- Test ispell dictionary with hunspell affix with FLAG num in configuration +ALTER TEXT SEARCH CONFIGURATION hunspell_tst ALTER MAPPING + REPLACE hunspell_long WITH hunspell_num; +SELECT to_tsvector('hunspell_tst', 'Booking the skies after rebookings for footballklubber from a foot'); + to_tsvector +---------------------------------------------------------------------------------------------------- + 'ball':7 'book':1,5 'booking':1,5 'foot':7,10 'football':7 'footballklubber':7 'klubber':7 'sky':3 +(1 row) + +SELECT to_tsquery('hunspell_tst', 'footballklubber'); + to_tsquery +-------------------------------------------------------------------------- + 'footballklubber' | 'foot' & 'ball' & 'klubber' | 'football' & 'klubber' +(1 row) + +SELECT to_tsquery('hunspell_tst', 'footballyklubber:b & rebookings:A & sky'); + to_tsquery +------------------------------------------------------------------------ + 'foot':B & 'ball':B & 'klubber':B & ( 'booking':A | 'book':A ) & 'sky' +(1 row) + +-- Test synonym dictionary in configuration +CREATE TEXT SEARCH CONFIGURATION synonym_tst ( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION synonym_tst ALTER MAPPING FOR + asciiword, hword_asciipart, asciihword + WITH synonym, english_stem; +SELECT to_tsvector('synonym_tst', 'Postgresql is often called as postgres or pgsql and pronounced as postgre'); + to_tsvector +--------------------------------------------------- + 'call':4 'often':3 'pgsql':1,6,8,12 'pronounc':10 +(1 row) + +SELECT to_tsvector('synonym_tst', 'Most common mistake is to write Gogle instead of Google'); + to_tsvector +---------------------------------------------------------- + 'common':2 'googl':7,10 'instead':8 'mistak':3 'write':6 +(1 row) + +SELECT to_tsvector('synonym_tst', 'Indexes or indices - Which is right plural form of index?'); + to_tsvector +---------------------------------------------- + 'form':8 'index':1,3,10 'plural':7 'right':6 +(1 row) + +SELECT to_tsquery('synonym_tst', 'Index & indices'); + to_tsquery +--------------------- + 'index' & 'index':* +(1 row) + +-- test thesaurus in configuration +-- see thesaurus_sample.ths to understand 'odd' resulting tsvector +CREATE TEXT SEARCH CONFIGURATION thesaurus_tst ( + COPY=synonym_tst +); +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR + asciiword, hword_asciipart, asciihword + WITH synonym, thesaurus, english_stem; +SELECT to_tsvector('thesaurus_tst', 'one postgres one two one two three one'); + to_tsvector +---------------------------------- + '1':1,5 '12':3 '123':4 'pgsql':2 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'Supernovae star is very new star and usually called supernovae (abbreviation SN)'); + to_tsvector +-------------------------------------------------------------- + 'abbrevi':10 'call':8 'new':4 'sn':1,9,11 'star':5 'usual':7 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'Booking tickets is looking like a booking a tickets'); + to_tsvector +------------------------------------------------------- + 'card':3,10 'invit':2,9 'like':6 'look':5 'order':1,8 +(1 row) + +-- invalid: non-lowercase quoted identifiers +CREATE TEXT SEARCH DICTIONARY tsdict_case +( + Template = ispell, + "DictFile" = ispell_sample, + "AffFile" = ispell_sample +); +ERROR: unrecognized Ispell parameter: "DictFile" diff --git a/src/test/regress/expected/yb.port.tsearch.out b/src/test/regress/expected/yb.port.tsearch.out new file mode 100644 index 0000000000000..4c36bc82a82e4 --- /dev/null +++ b/src/test/regress/expected/yb.port.tsearch.out @@ -0,0 +1,2101 @@ +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +-- +-- Sanity checks for text search catalogs +-- +-- NB: we assume the oidjoins test will have caught any dangling links, +-- that is OID or REGPROC fields that are not zero and do not match some +-- row in the linked-to table. However, if we want to enforce that a link +-- field can't be 0, we have to check it here. +-- Find unexpected zero link entries +SELECT oid, prsname +FROM pg_ts_parser +WHERE prsnamespace = 0 OR prsstart = 0 OR prstoken = 0 OR prsend = 0 OR + -- prsheadline is optional + prslextype = 0; + oid | prsname +-----+--------- +(0 rows) + +SELECT oid, dictname +FROM pg_ts_dict +WHERE dictnamespace = 0 OR dictowner = 0 OR dicttemplate = 0; + oid | dictname +-----+---------- +(0 rows) + +SELECT oid, tmplname +FROM pg_ts_template +WHERE tmplnamespace = 0 OR tmpllexize = 0; -- tmplinit is optional + oid | tmplname +-----+---------- +(0 rows) + +SELECT oid, cfgname +FROM pg_ts_config +WHERE cfgnamespace = 0 OR cfgowner = 0 OR cfgparser = 0; + oid | cfgname +-----+--------- +(0 rows) + +SELECT mapcfg, maptokentype, mapseqno +FROM pg_ts_config_map +WHERE mapcfg = 0 OR mapdict = 0; + mapcfg | maptokentype | mapseqno +--------+--------------+---------- +(0 rows) + +-- Look for pg_ts_config_map entries that aren't one of parser's token types +SELECT * FROM + ( SELECT oid AS cfgid, (ts_token_type(cfgparser)).tokid AS tokid + FROM pg_ts_config ) AS tt +RIGHT JOIN pg_ts_config_map AS m + ON (tt.cfgid=m.mapcfg AND tt.tokid=m.maptokentype) +WHERE + tt.cfgid IS NULL OR tt.tokid IS NULL; + cfgid | tokid | mapcfg | maptokentype | mapseqno | mapdict +-------+-------+--------+--------------+----------+--------- +(0 rows) + +-- Load some test data +CREATE TABLE test_tsvector( + t text, + a tsvector +); +\set filename :abs_srcdir '/data/tsearch.data' +COPY test_tsvector FROM :'filename'; +ANALYZE test_tsvector; +-- test basic text search behavior without indexes, then with +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; + count +------- + 158 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh'; + count +------- + 17 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt'; + count +------- + 6 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt'; + count +------- + 98 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)'; + count +------- + 23 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)'; + count +------- + 39 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*'; + count +------- + 494 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}'); + count +------- + 158 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme'; + count +------- + 508 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh'; + count +------- + 432 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt'; + count +------- + 6 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)'; + count +------- + 507 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)'; + count +------- + 508 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)'; + count +------- + 507 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wd:A'; + count +------- + 56 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wd:D'; + count +------- + 58 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!wd:A'; + count +------- + 452 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!wd:D'; + count +------- + 450 +(1 row) + +create index wowidx on test_tsvector using gist (a); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +-- YB: port remaining queries when gist is supported +CREATE INDEX wowidx ON test_tsvector USING gin (a); +SET enable_seqscan=OFF; +SET yb_test_ybgin_disable_cost_factor = 0.5; -- YB: enable_seqscan=OFF is not enough to force index scan +-- GIN only supports bitmapscan, so no need to test plain indexscan +-- YB note: ybgin is the opposite: it supports indexscan, not bitmapscan +explain (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; + QUERY PLAN +------------------------------------------------------- + Aggregate + -> Index Scan using wowidx on test_tsvector + Index Cond: (a @@ '''wr'' | ''qh'''::tsquery) +(3 rows) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 2. +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh'; + count +------- + 17 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt'; + count +------- + 6 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 2. +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 3. +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 2. +SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one required scan entry: got 2. +SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}'); + count +------- + 158 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt'; + count +------- + 6 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM test_tsvector WHERE a @@ 'wd:A'; + count +------- + 56 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wd:D'; + count +------- + 58 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!wd:A'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +SELECT count(*) FROM test_tsvector WHERE a @@ '!wd:D'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +-- Test optimization of non-empty GIN_SEARCH_MODE_ALL queries +EXPLAIN (COSTS OFF) +SELECT count(*) FROM test_tsvector WHERE a @@ '!qh'; + QUERY PLAN +------------------------------------------------ + Aggregate + -> Index Scan using wowidx on test_tsvector + Index Cond: (a @@ '!''qh'''::tsquery) +(3 rows) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!qh'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method does not support non-default search mode: all. +EXPLAIN (COSTS OFF) +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr' AND a @@ '!qh'; + QUERY PLAN +------------------------------------------------------------------------------ + Aggregate + -> Index Scan using wowidx on test_tsvector + Index Cond: ((a @@ '''wr'''::tsquery) AND (a @@ '!''qh'''::tsquery)) +(3 rows) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr' AND a @@ '!qh'; +ERROR: unsupported ybgin index scan +DETAIL: ybgin index method cannot use more than one scan key: got 2. +HINT: Consider rewriting the query with INTERSECT and UNION. +RESET yb_test_ybgin_disable_cost_factor; +RESET enable_seqscan; +INSERT INTO test_tsvector VALUES ('???', 'DFG:1A,2B,6C,10 FGH'); +SELECT * FROM ts_stat('SELECT a FROM test_tsvector') ORDER BY ndoc DESC, nentry DESC, word LIMIT 10; + word | ndoc | nentry +------+------+-------- + qq | 108 | 108 + qt | 102 | 102 + qe | 100 | 101 + qh | 98 | 99 + qw | 98 | 98 + qa | 97 | 97 + ql | 94 | 94 + qs | 94 | 94 + qr | 92 | 93 + qi | 92 | 92 +(10 rows) + +SELECT * FROM ts_stat('SELECT a FROM test_tsvector', 'AB') ORDER BY ndoc DESC, nentry DESC, word; + word | ndoc | nentry +------+------+-------- + DFG | 1 | 2 +(1 row) + +--dictionaries and to_tsvector +SELECT ts_lexize('english_stem', 'skies'); + ts_lexize +----------- + {sky} +(1 row) + +SELECT ts_lexize('english_stem', 'identity'); + ts_lexize +----------- + {ident} +(1 row) + +SELECT * FROM ts_token_type('default'); + tokid | alias | description +-------+-----------------+------------------------------------------ + 1 | asciiword | Word, all ASCII + 2 | word | Word, all letters + 3 | numword | Word, letters and digits + 4 | email | Email address + 5 | url | URL + 6 | host | Host + 7 | sfloat | Scientific notation + 8 | version | Version number + 9 | hword_numpart | Hyphenated word part, letters and digits + 10 | hword_part | Hyphenated word part, all letters + 11 | hword_asciipart | Hyphenated word part, all ASCII + 12 | blank | Space symbols + 13 | tag | XML tag + 14 | protocol | Protocol head + 15 | numhword | Hyphenated word, letters and digits + 16 | asciihword | Hyphenated word, all ASCII + 17 | hword | Hyphenated word, all letters + 18 | url_path | URL path + 19 | file | File or path name + 20 | float | Decimal notation + 21 | int | Signed integer + 22 | uint | Unsigned integer + 23 | entity | XML entity +(23 rows) + +SELECT * FROM ts_parse('default', '345 qwe@efd.r '' http://www.com/ http://aew.werc.ewr/?ad=qwe&dw 1aew.werc.ewr/?ad=qwe&dw 2aew.werc.ewr http://3aew.werc.ewr/?ad=qwe&dw http://4aew.werc.ewr http://5aew.werc.ewr:8100/? ad=qwe&dw 6aew.werc.ewr:8100/?ad=qwe&dw 7aew.werc.ewr:8100/?ad=qwe&dw=%20%32 +4.0e-10 qwe qwe qwqwe 234.435 455 5.005 teodor@stack.net teodor@123-stack.net 123_teodor@stack.net 123-teodor@stack.net qwe-wer asdf qwer jf sdjk ewr1> ewri2 +/usr/local/fff /awdf/dwqe/4325 rewt/ewr wefjn /wqe-324/ewr gist.h gist.h.c gist.c. readline 4.2 4.2. 4.2, readline-4.2 readline-4.2. 234 + wow < jqw <> qwerty'); + tokid | token +-------+-------------------------------------- + 22 | 345 + 12 | + 1 | qwe + 12 | @ + 19 | efd.r + 12 | ' + 14 | http:// + 6 | www.com + 12 | / + 14 | http:// + 5 | aew.werc.ewr/?ad=qwe&dw + 6 | aew.werc.ewr + 18 | /?ad=qwe&dw + 12 | + 5 | 1aew.werc.ewr/?ad=qwe&dw + 6 | 1aew.werc.ewr + 18 | /?ad=qwe&dw + 12 | + 6 | 2aew.werc.ewr + 12 | + 14 | http:// + 5 | 3aew.werc.ewr/?ad=qwe&dw + 6 | 3aew.werc.ewr + 18 | /?ad=qwe&dw + 12 | + 14 | http:// + 6 | 4aew.werc.ewr + 12 | + 14 | http:// + 5 | 5aew.werc.ewr:8100/? + 6 | 5aew.werc.ewr:8100 + 18 | /? + 12 | + 1 | ad + 12 | = + 1 | qwe + 12 | & + 1 | dw + 12 | + 5 | 6aew.werc.ewr:8100/?ad=qwe&dw + 6 | 6aew.werc.ewr:8100 + 18 | /?ad=qwe&dw + 12 | + 5 | 7aew.werc.ewr:8100/?ad=qwe&dw=%20%32 + 6 | 7aew.werc.ewr:8100 + 18 | /?ad=qwe&dw=%20%32 + 12 | + 7 | +4.0e-10 + 12 | + 1 | qwe + 12 | + 1 | qwe + 12 | + 1 | qwqwe + 12 | + 20 | 234.435 + 12 | + 22 | 455 + 12 | + 20 | 5.005 + 12 | + 4 | teodor@stack.net + 12 | + 4 | teodor@123-stack.net + 12 | + 4 | 123_teodor@stack.net + 12 | + 4 | 123-teodor@stack.net + 12 | + 16 | qwe-wer + 11 | qwe + 12 | - + 11 | wer + 12 | + 1 | asdf + 12 | + 13 | + 1 | qwer + 12 | + 1 | jf + 12 | + 1 | sdjk + 12 | < + 1 | we + 12 | + 1 | hjwer + 12 | + 13 | + 12 | + 3 | ewr1 + 12 | > + 3 | ewri2 + 12 | + 13 | + 12 | + + | + 19 | /usr/local/fff + 12 | + 19 | /awdf/dwqe/4325 + 12 | + 19 | rewt/ewr + 12 | + 1 | wefjn + 12 | + 19 | /wqe-324/ewr + 12 | + 19 | gist.h + 12 | + 19 | gist.h.c + 12 | + 19 | gist.c + 12 | . + 1 | readline + 12 | + 20 | 4.2 + 12 | + 20 | 4.2 + 12 | . + 20 | 4.2 + 12 | , + 1 | readline + 20 | -4.2 + 12 | + 1 | readline + 20 | -4.2 + 12 | . + 22 | 234 + 12 | + + | + 12 | < + 1 | i + 12 | + 13 | + 12 | + 1 | wow + 12 | + 12 | < + 1 | jqw + 12 | + 12 | <> + 1 | qwerty +(139 rows) + +SELECT to_tsvector('english', '345 qwe@efd.r '' http://www.com/ http://aew.werc.ewr/?ad=qwe&dw 1aew.werc.ewr/?ad=qwe&dw 2aew.werc.ewr http://3aew.werc.ewr/?ad=qwe&dw http://4aew.werc.ewr http://5aew.werc.ewr:8100/? ad=qwe&dw 6aew.werc.ewr:8100/?ad=qwe&dw 7aew.werc.ewr:8100/?ad=qwe&dw=%20%32 +4.0e-10 qwe qwe qwqwe 234.435 455 5.005 teodor@stack.net teodor@123-stack.net 123_teodor@stack.net 123-teodor@stack.net qwe-wer asdf qwer jf sdjk ewr1> ewri2 +/usr/local/fff /awdf/dwqe/4325 rewt/ewr wefjn /wqe-324/ewr gist.h gist.h.c gist.c. readline 4.2 4.2. 4.2, readline-4.2 readline-4.2. 234 + wow < jqw <> qwerty'); + to_tsvector +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + '+4.0e-10':28 '-4.2':63,65 '/?':18 '/?ad=qwe&dw':7,10,14,24 '/?ad=qwe&dw=%20%32':27 '/awdf/dwqe/4325':51 '/usr/local/fff':50 '/wqe-324/ewr':54 '123-teodor@stack.net':38 '123_teodor@stack.net':37 '1aew.werc.ewr':9 '1aew.werc.ewr/?ad=qwe&dw':8 '234':66 '234.435':32 '2aew.werc.ewr':11 '345':1 '3aew.werc.ewr':13 '3aew.werc.ewr/?ad=qwe&dw':12 '4.2':59,60,61 '455':33 '4aew.werc.ewr':15 '5.005':34 '5aew.werc.ewr:8100':17 '5aew.werc.ewr:8100/?':16 '6aew.werc.ewr:8100':23 '6aew.werc.ewr:8100/?ad=qwe&dw':22 '7aew.werc.ewr:8100':26 '7aew.werc.ewr:8100/?ad=qwe&dw=%20%32':25 'ad':19 'aew.werc.ewr':6 'aew.werc.ewr/?ad=qwe&dw':5 'asdf':42 'dw':21 'efd.r':3 'ewr1':48 'ewri2':49 'gist.c':57 'gist.h':55 'gist.h.c':56 'hjwer':47 'jf':44 'jqw':69 'qwe':2,20,29,30,40 'qwe-wer':39 'qwer':43 'qwerti':70 'qwqwe':31 'readlin':58,62,64 'rewt/ewr':52 'sdjk':45 'teodor@123-stack.net':36 'teodor@stack.net':35 'wefjn':53 'wer':41 'wow':68 'www.com':4 +(1 row) + +SELECT length(to_tsvector('english', '345 qwe@efd.r '' http://www.com/ http://aew.werc.ewr/?ad=qwe&dw 1aew.werc.ewr/?ad=qwe&dw 2aew.werc.ewr http://3aew.werc.ewr/?ad=qwe&dw http://4aew.werc.ewr http://5aew.werc.ewr:8100/? ad=qwe&dw 6aew.werc.ewr:8100/?ad=qwe&dw 7aew.werc.ewr:8100/?ad=qwe&dw=%20%32 +4.0e-10 qwe qwe qwqwe 234.435 455 5.005 teodor@stack.net teodor@123-stack.net 123_teodor@stack.net 123-teodor@stack.net qwe-wer asdf qwer jf sdjk ewr1> ewri2 +/usr/local/fff /awdf/dwqe/4325 rewt/ewr wefjn /wqe-324/ewr gist.h gist.h.c gist.c. readline 4.2 4.2. 4.2, readline-4.2 readline-4.2. 234 + wow < jqw <> qwerty')); + length +-------- + 56 +(1 row) + +-- ts_debug +SELECT * from ts_debug('english', 'abc&nm1;def©ghiõjkl'); + alias | description | token | dictionaries | dictionary | lexemes +-----------+-----------------+----------------------------+----------------+--------------+--------- + tag | XML tag | | {} | | + asciiword | Word, all ASCII | abc | {english_stem} | english_stem | {abc} + entity | XML entity | &nm1; | {} | | + asciiword | Word, all ASCII | def | {english_stem} | english_stem | {def} + entity | XML entity | © | {} | | + asciiword | Word, all ASCII | ghi | {english_stem} | english_stem | {ghi} + entity | XML entity | õ | {} | | + asciiword | Word, all ASCII | jkl | {english_stem} | english_stem | {jkl} + tag | XML tag | | {} | | +(9 rows) + +-- check parsing of URLs +SELECT * from ts_debug('english', 'http://www.harewoodsolutions.co.uk/press.aspx'); + alias | description | token | dictionaries | dictionary | lexemes +----------+---------------+----------------------------------------+--------------+------------+------------------------------------------ + protocol | Protocol head | http:// | {} | | + url | URL | www.harewoodsolutions.co.uk/press.aspx | {simple} | simple | {www.harewoodsolutions.co.uk/press.aspx} + host | Host | www.harewoodsolutions.co.uk | {simple} | simple | {www.harewoodsolutions.co.uk} + url_path | URL path | /press.aspx | {simple} | simple | {/press.aspx} + tag | XML tag | | {} | | +(5 rows) + +SELECT * from ts_debug('english', 'http://aew.wer0c.ewr/id?ad=qwe&dw'); + alias | description | token | dictionaries | dictionary | lexemes +----------+---------------+----------------------------+--------------+------------+------------------------------ + protocol | Protocol head | http:// | {} | | + url | URL | aew.wer0c.ewr/id?ad=qwe&dw | {simple} | simple | {aew.wer0c.ewr/id?ad=qwe&dw} + host | Host | aew.wer0c.ewr | {simple} | simple | {aew.wer0c.ewr} + url_path | URL path | /id?ad=qwe&dw | {simple} | simple | {/id?ad=qwe&dw} + tag | XML tag | | {} | | +(5 rows) + +SELECT * from ts_debug('english', 'http://5aew.werc.ewr:8100/?'); + alias | description | token | dictionaries | dictionary | lexemes +----------+---------------+----------------------+--------------+------------+------------------------ + protocol | Protocol head | http:// | {} | | + url | URL | 5aew.werc.ewr:8100/? | {simple} | simple | {5aew.werc.ewr:8100/?} + host | Host | 5aew.werc.ewr:8100 | {simple} | simple | {5aew.werc.ewr:8100} + url_path | URL path | /? | {simple} | simple | {/?} +(4 rows) + +SELECT * from ts_debug('english', '5aew.werc.ewr:8100/?xx'); + alias | description | token | dictionaries | dictionary | lexemes +----------+-------------+------------------------+--------------+------------+-------------------------- + url | URL | 5aew.werc.ewr:8100/?xx | {simple} | simple | {5aew.werc.ewr:8100/?xx} + host | Host | 5aew.werc.ewr:8100 | {simple} | simple | {5aew.werc.ewr:8100} + url_path | URL path | /?xx | {simple} | simple | {/?xx} +(3 rows) + +SELECT token, alias, + dictionaries, dictionaries is null as dnull, array_dims(dictionaries) as ddims, + lexemes, lexemes is null as lnull, array_dims(lexemes) as ldims +from ts_debug('english', 'a title'); + token | alias | dictionaries | dnull | ddims | lexemes | lnull | ldims +-------+-----------+----------------+-------+-------+---------+-------+------- + a | asciiword | {english_stem} | f | [1:1] | {} | f | + | blank | {} | f | | | t | + title | asciiword | {english_stem} | f | [1:1] | {titl} | f | [1:1] +(3 rows) + +-- to_tsquery +SELECT to_tsquery('english', 'qwe & sKies '); + to_tsquery +--------------- + 'qwe' & 'sky' +(1 row) + +SELECT to_tsquery('simple', 'qwe & sKies '); + to_tsquery +----------------- + 'qwe' & 'skies' +(1 row) + +SELECT to_tsquery('english', '''the wether'':dc & '' sKies '':BC '); + to_tsquery +------------------------ + 'wether':CD & 'sky':BC +(1 row) + +SELECT to_tsquery('english', 'asd&(and|fghj)'); + to_tsquery +---------------- + 'asd' & 'fghj' +(1 row) + +SELECT to_tsquery('english', '(asd&and)|fghj'); + to_tsquery +---------------- + 'asd' | 'fghj' +(1 row) + +SELECT to_tsquery('english', '(asd&!and)|fghj'); + to_tsquery +---------------- + 'asd' | 'fghj' +(1 row) + +SELECT to_tsquery('english', '(the|and&(i&1))&fghj'); + to_tsquery +-------------- + '1' & 'fghj' +(1 row) + +SELECT plainto_tsquery('english', 'the and z 1))& fghj'); + plainto_tsquery +-------------------- + 'z' & '1' & 'fghj' +(1 row) + +SELECT plainto_tsquery('english', 'foo bar') && plainto_tsquery('english', 'asd'); + ?column? +----------------------- + 'foo' & 'bar' & 'asd' +(1 row) + +SELECT plainto_tsquery('english', 'foo bar') || plainto_tsquery('english', 'asd fg'); + ?column? +------------------------------ + 'foo' & 'bar' | 'asd' & 'fg' +(1 row) + +SELECT plainto_tsquery('english', 'foo bar') || !!plainto_tsquery('english', 'asd fg'); + ?column? +----------------------------------- + 'foo' & 'bar' | !( 'asd' & 'fg' ) +(1 row) + +SELECT plainto_tsquery('english', 'foo bar') && 'asd | fg'; + ?column? +---------------------------------- + 'foo' & 'bar' & ( 'asd' | 'fg' ) +(1 row) + +-- Check stop word deletion, a and s are stop-words +SELECT to_tsquery('english', '!(a & !b) & c'); + to_tsquery +------------- + !!'b' & 'c' +(1 row) + +SELECT to_tsquery('english', '!(a & !b)'); + to_tsquery +------------ + !!'b' +(1 row) + +SELECT to_tsquery('english', '(1 <-> 2) <-> a'); + to_tsquery +------------- + '1' <-> '2' +(1 row) + +SELECT to_tsquery('english', '(1 <-> a) <-> 2'); + to_tsquery +------------- + '1' <2> '2' +(1 row) + +SELECT to_tsquery('english', '(a <-> 1) <-> 2'); + to_tsquery +------------- + '1' <-> '2' +(1 row) + +SELECT to_tsquery('english', 'a <-> (1 <-> 2)'); + to_tsquery +------------- + '1' <-> '2' +(1 row) + +SELECT to_tsquery('english', '1 <-> (a <-> 2)'); + to_tsquery +------------- + '1' <2> '2' +(1 row) + +SELECT to_tsquery('english', '1 <-> (2 <-> a)'); + to_tsquery +------------- + '1' <-> '2' +(1 row) + +SELECT to_tsquery('english', '(1 <-> 2) <3> a'); + to_tsquery +------------- + '1' <-> '2' +(1 row) + +SELECT to_tsquery('english', '(1 <-> a) <3> 2'); + to_tsquery +------------- + '1' <4> '2' +(1 row) + +SELECT to_tsquery('english', '(a <-> 1) <3> 2'); + to_tsquery +------------- + '1' <3> '2' +(1 row) + +SELECT to_tsquery('english', 'a <3> (1 <-> 2)'); + to_tsquery +------------- + '1' <-> '2' +(1 row) + +SELECT to_tsquery('english', '1 <3> (a <-> 2)'); + to_tsquery +------------- + '1' <4> '2' +(1 row) + +SELECT to_tsquery('english', '1 <3> (2 <-> a)'); + to_tsquery +------------- + '1' <3> '2' +(1 row) + +SELECT to_tsquery('english', '(1 <3> 2) <-> a'); + to_tsquery +------------- + '1' <3> '2' +(1 row) + +SELECT to_tsquery('english', '(1 <3> a) <-> 2'); + to_tsquery +------------- + '1' <4> '2' +(1 row) + +SELECT to_tsquery('english', '(a <3> 1) <-> 2'); + to_tsquery +------------- + '1' <-> '2' +(1 row) + +SELECT to_tsquery('english', 'a <-> (1 <3> 2)'); + to_tsquery +------------- + '1' <3> '2' +(1 row) + +SELECT to_tsquery('english', '1 <-> (a <3> 2)'); + to_tsquery +------------- + '1' <4> '2' +(1 row) + +SELECT to_tsquery('english', '1 <-> (2 <3> a)'); + to_tsquery +------------- + '1' <-> '2' +(1 row) + +SELECT to_tsquery('english', '((a <-> 1) <-> 2) <-> s'); + to_tsquery +------------- + '1' <-> '2' +(1 row) + +SELECT to_tsquery('english', '(2 <-> (a <-> 1)) <-> s'); + to_tsquery +------------- + '2' <2> '1' +(1 row) + +SELECT to_tsquery('english', '((1 <-> a) <-> 2) <-> s'); + to_tsquery +------------- + '1' <2> '2' +(1 row) + +SELECT to_tsquery('english', '(2 <-> (1 <-> a)) <-> s'); + to_tsquery +------------- + '2' <-> '1' +(1 row) + +SELECT to_tsquery('english', 's <-> ((a <-> 1) <-> 2)'); + to_tsquery +------------- + '1' <-> '2' +(1 row) + +SELECT to_tsquery('english', 's <-> (2 <-> (a <-> 1))'); + to_tsquery +------------- + '2' <2> '1' +(1 row) + +SELECT to_tsquery('english', 's <-> ((1 <-> a) <-> 2)'); + to_tsquery +------------- + '1' <2> '2' +(1 row) + +SELECT to_tsquery('english', 's <-> (2 <-> (1 <-> a))'); + to_tsquery +------------- + '2' <-> '1' +(1 row) + +SELECT to_tsquery('english', '((a <-> 1) <-> s) <-> 2'); + to_tsquery +------------- + '1' <2> '2' +(1 row) + +SELECT to_tsquery('english', '(s <-> (a <-> 1)) <-> 2'); + to_tsquery +------------- + '1' <-> '2' +(1 row) + +SELECT to_tsquery('english', '((1 <-> a) <-> s) <-> 2'); + to_tsquery +------------- + '1' <3> '2' +(1 row) + +SELECT to_tsquery('english', '(s <-> (1 <-> a)) <-> 2'); + to_tsquery +------------- + '1' <2> '2' +(1 row) + +SELECT to_tsquery('english', '2 <-> ((a <-> 1) <-> s)'); + to_tsquery +------------- + '2' <2> '1' +(1 row) + +SELECT to_tsquery('english', '2 <-> (s <-> (a <-> 1))'); + to_tsquery +------------- + '2' <3> '1' +(1 row) + +SELECT to_tsquery('english', '2 <-> ((1 <-> a) <-> s)'); + to_tsquery +------------- + '2' <-> '1' +(1 row) + +SELECT to_tsquery('english', '2 <-> (s <-> (1 <-> a))'); + to_tsquery +------------- + '2' <2> '1' +(1 row) + +SELECT to_tsquery('english', 'foo <-> (a <-> (the <-> bar))'); + to_tsquery +----------------- + 'foo' <3> 'bar' +(1 row) + +SELECT to_tsquery('english', '((foo <-> a) <-> the) <-> bar'); + to_tsquery +----------------- + 'foo' <3> 'bar' +(1 row) + +SELECT to_tsquery('english', 'foo <-> a <-> the <-> bar'); + to_tsquery +----------------- + 'foo' <3> 'bar' +(1 row) + +SELECT phraseto_tsquery('english', 'PostgreSQL can be extended by the user in many ways'); + phraseto_tsquery +----------------------------------------------------------- + 'postgresql' <3> 'extend' <3> 'user' <2> 'mani' <-> 'way' +(1 row) + +SELECT ts_rank_cd(to_tsvector('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +'), to_tsquery('english', 'paint&water')); + ts_rank_cd +------------ + 0.05 +(1 row) + +SELECT ts_rank_cd(to_tsvector('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +'), to_tsquery('english', 'breath&motion&water')); + ts_rank_cd +------------- + 0.008333334 +(1 row) + +SELECT ts_rank_cd(to_tsvector('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +'), to_tsquery('english', 'ocean')); + ts_rank_cd +------------ + 0.1 +(1 row) + +SELECT ts_rank_cd(to_tsvector('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +'), to_tsquery('english', 'painted <-> Ship')); + ts_rank_cd +------------ + 0.1 +(1 row) + +SELECT ts_rank_cd(strip(to_tsvector('both stripped')), + to_tsquery('both & stripped')); + ts_rank_cd +------------ + 0 +(1 row) + +SELECT ts_rank_cd(to_tsvector('unstripped') || strip(to_tsvector('stripped')), + to_tsquery('unstripped & stripped')); + ts_rank_cd +------------ + 0 +(1 row) + +--headline tests +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'paint&water')); + ts_headline +----------------------------------------- + painted Ocean. + + Water, water, every where+ + And all the boards did shrink; + + Water, water, every +(1 row) + +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'breath&motion&water')); + ts_headline +---------------------------------- + breath nor motion,+ + As idle as a painted Ship + + Upon a painted Ocean. + + Water, water +(1 row) + +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'ocean')); + ts_headline +---------------------------------- + Ocean. + + Water, water, every where + + And all the boards did shrink;+ + Water, water, every where +(1 row) + +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', phraseto_tsquery('english', 'painted Ocean')); + ts_headline +--------------------------------------- + painted Ship + + Upon a painted Ocean.+ + Water, water, every where + + And all the boards did shrink +(1 row) + +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', phraseto_tsquery('english', 'idle as a painted Ship')); + ts_headline +--------------------------------------------- + idle as a painted Ship+ + Upon a painted Ocean. + + Water, water, every where + + And all the boards +(1 row) + +SELECT ts_headline('english', +'Lorem ipsum urna. Nullam nullam ullamcorper urna.', +to_tsquery('english','Lorem') && phraseto_tsquery('english','ullamcorper urna'), +'MaxWords=100, MinWords=1'); + ts_headline +------------------------------------------------------------------------------- + Lorem ipsum urna. Nullam nullam ullamcorper urna +(1 row) + +SELECT ts_headline('english', ' + + + +Sea view wow foo bar qq +YES   +ff-bg + + +', +to_tsquery('english', 'sea&foo'), 'HighlightAll=true'); + ts_headline +----------------------------------------------------------------------------- + + + + + + + + + Sea view wow foo bar qq + + YES  + + ff-bg + + + + + + +(1 row) + +SELECT ts_headline('simple', '1 2 3 1 3'::text, '1 <-> 3', 'MaxWords=2, MinWords=1'); + ts_headline +------------------- + 1 3 +(1 row) + +SELECT ts_headline('simple', '1 2 3 1 3'::text, '1 & 3', 'MaxWords=4, MinWords=1'); + ts_headline +--------------------- + 1 2 3 +(1 row) + +SELECT ts_headline('simple', '1 2 3 1 3'::text, '1 <-> 3', 'MaxWords=4, MinWords=1'); + ts_headline +---------------------------- + 3 1 3 +(1 row) + +--Check if headline fragments work +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'ocean'), 'MaxFragments=1'); + ts_headline +------------------------------------ + after day, + + We stuck, nor breath nor motion,+ + As idle as a painted Ship + + Upon a painted Ocean. + + Water, water, every where + + And all the boards did shrink; + + Water, water, every where, + + Nor any drop +(1 row) + +--Check if more than one fragments are displayed +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'Coleridge & stuck'), 'MaxFragments=2'); + ts_headline +---------------------------------------------- + after day, day after day, + + We stuck, nor breath nor motion, + + As idle as a painted Ship + + Upon a painted Ocean. + + Water, water, every where + + And all the boards did shrink; + + Water, water, every where ... drop to drink.+ + S. T. Coleridge +(1 row) + +--Fragments when there all query words are not in the document +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'ocean & seahorse'), 'MaxFragments=1'); + ts_headline +------------------------------------ + + + Day after day, day after day, + + We stuck, nor breath nor motion,+ + As idle as +(1 row) + +--FragmentDelimiter option +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'Coleridge & stuck'), 'MaxFragments=2,FragmentDelimiter=***'); + ts_headline +-------------------------------------------- + after day, day after day, + + We stuck, nor breath nor motion, + + As idle as a painted Ship + + Upon a painted Ocean. + + Water, water, every where + + And all the boards did shrink; + + Water, water, every where***drop to drink.+ + S. T. Coleridge +(1 row) + +--Fragments with phrase search +SELECT ts_headline('english', +'Lorem ipsum urna. Nullam nullam ullamcorper urna.', +to_tsquery('english','Lorem') && phraseto_tsquery('english','ullamcorper urna'), +'MaxFragments=100, MaxWords=100, MinWords=1'); + ts_headline +------------------------------------------------------------------------------- + Lorem ipsum urna. Nullam nullam ullamcorper urna +(1 row) + +--Rewrite sub system +CREATE TABLE test_tsquery (txtkeyword TEXT, txtsample TEXT); +\set ECHO none +ALTER TABLE test_tsquery ADD COLUMN keyword tsquery; +UPDATE test_tsquery SET keyword = to_tsquery('english', txtkeyword); +ALTER TABLE test_tsquery ADD COLUMN sample tsquery; +UPDATE test_tsquery SET sample = to_tsquery('english', txtsample::text); +SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new <-> york'; + count +------- + 2 +(1 row) + +SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new <-> york'; + count +------- + 3 +(1 row) + +SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new <-> york'; + count +------- + 1 +(1 row) + +SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new <-> york'; + count +------- + 4 +(1 row) + +SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new <-> york'; + count +------- + 3 +(1 row) + +CREATE UNIQUE INDEX bt_tsq ON test_tsquery (keyword); +ERROR: INDEX on column of type 'TSQUERY' not yet supported +/* YB: uncomment when above CREATE INDEX is supported +SET enable_seqscan=OFF; + +SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new <-> york'; + +RESET enable_seqscan; +*/ -- YB +SELECT ts_rewrite('foo & bar & qq & new & york', 'new & york'::tsquery, 'big & apple | nyc | new & york & city'); + ts_rewrite +------------------------------------------------------------------------------ + 'foo' & 'bar' & 'qq' & ( 'city' & 'new' & 'york' | 'nyc' | 'big' & 'apple' ) +(1 row) + +SELECT ts_rewrite(ts_rewrite('new & !york ', 'york', '!jersey'), + 'jersey', 'mexico'); + ts_rewrite +-------------------- + 'new' & !!'mexico' +(1 row) + +SELECT ts_rewrite('moscow', 'SELECT keyword, sample FROM test_tsquery'::text ); + ts_rewrite +--------------------- + 'moskva' | 'moscow' +(1 row) + +SELECT ts_rewrite('moscow & hotel', 'SELECT keyword, sample FROM test_tsquery'::text ); + ts_rewrite +----------------------------------- + 'hotel' & ( 'moskva' | 'moscow' ) +(1 row) + +SELECT ts_rewrite('bar & qq & foo & (new <-> york)', 'SELECT keyword, sample FROM test_tsquery'::text ); + ts_rewrite +------------------------------------------------------------------------------------- + 'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' <-> 'appl' | 'new' <-> 'york' ) +(1 row) + +SELECT ts_rewrite( 'moscow', 'SELECT keyword, sample FROM test_tsquery'); + ts_rewrite +--------------------- + 'moskva' | 'moscow' +(1 row) + +SELECT ts_rewrite( 'moscow & hotel', 'SELECT keyword, sample FROM test_tsquery'); + ts_rewrite +----------------------------------- + 'hotel' & ( 'moskva' | 'moscow' ) +(1 row) + +SELECT ts_rewrite( 'bar & qq & foo & (new <-> york)', 'SELECT keyword, sample FROM test_tsquery'); + ts_rewrite +------------------------------------------------------------------------------------- + 'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' <-> 'appl' | 'new' <-> 'york' ) +(1 row) + +SELECT ts_rewrite('1 & (2 <-> 3)', 'SELECT keyword, sample FROM test_tsquery'::text ); + ts_rewrite +------------- + '2' <-> '4' +(1 row) + +SELECT ts_rewrite('1 & (2 <2> 3)', 'SELECT keyword, sample FROM test_tsquery'::text ); + ts_rewrite +------------------- + '1' & '2' <2> '3' +(1 row) + +SELECT ts_rewrite('5 <-> (1 & (2 <-> 3))', 'SELECT keyword, sample FROM test_tsquery'::text ); + ts_rewrite +------------------------- + '5' <-> ( '2' <-> '4' ) +(1 row) + +SELECT ts_rewrite('5 <-> (6 | 8)', 'SELECT keyword, sample FROM test_tsquery'::text ); + ts_rewrite +----------------------- + '5' <-> ( '6' | '8' ) +(1 row) + +-- Check empty substitution +SELECT ts_rewrite(to_tsquery('5 & (6 | 5)'), to_tsquery('5'), to_tsquery('')); +NOTICE: text-search query doesn't contain lexemes: "" + ts_rewrite +------------ + '6' +(1 row) + +SELECT ts_rewrite(to_tsquery('!5'), to_tsquery('5'), to_tsquery('')); +NOTICE: text-search query doesn't contain lexemes: "" + ts_rewrite +------------ + +(1 row) + +SELECT keyword FROM test_tsquery WHERE keyword @> 'new'; + keyword +------------------ + 'new' <-> 'york' +(1 row) + +SELECT keyword FROM test_tsquery WHERE keyword @> 'moscow'; + keyword +---------- + 'moscow' +(1 row) + +SELECT keyword FROM test_tsquery WHERE keyword <@ 'new'; + keyword +--------- +(0 rows) + +SELECT keyword FROM test_tsquery WHERE keyword <@ 'moscow'; + keyword +---------- + 'moscow' +(1 row) + +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query; + ts_rewrite +--------------------- + 'moskva' | 'moscow' +(1 row) + +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query; + ts_rewrite +----------------------------------- + 'hotel' & ( 'moskva' | 'moscow' ) +(1 row) + +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query; + ts_rewrite +------------------------------------------------------------------------------------- + 'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' <-> 'appl' | 'new' <-> 'york' ) +(1 row) + +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query; + ts_rewrite +--------------------- + 'moskva' | 'moscow' +(1 row) + +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query; + ts_rewrite +----------------------------------- + 'hotel' & ( 'moskva' | 'moscow' ) +(1 row) + +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query; + ts_rewrite +------------------------------------------------------------------------------------- + 'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' <-> 'appl' | 'new' <-> 'york' ) +(1 row) + +CREATE INDEX qq ON test_tsquery USING gist (keyword tsquery_ops); +ERROR: index method "gist" not supported yet +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1337. React with thumbs up to raise its priority +/* YB: uncomment when above CREATE INDEX is supported +SET enable_seqscan=OFF; + +SELECT keyword FROM test_tsquery WHERE keyword @> 'new'; +SELECT keyword FROM test_tsquery WHERE keyword @> 'moscow'; +SELECT keyword FROM test_tsquery WHERE keyword <@ 'new'; +SELECT keyword FROM test_tsquery WHERE keyword <@ 'moscow'; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query; +*/ -- YB +SELECT ts_rewrite(tsquery_phrase('foo', 'foo'), 'foo', 'bar | baz'); + ts_rewrite +----------------------------------------- + ( 'bar' | 'baz' ) <-> ( 'bar' | 'baz' ) +(1 row) + +SELECT to_tsvector('foo bar') @@ + ts_rewrite(tsquery_phrase('foo', 'foo'), 'foo', 'bar | baz'); + ?column? +---------- + f +(1 row) + +SELECT to_tsvector('bar baz') @@ + ts_rewrite(tsquery_phrase('foo', 'foo'), 'foo', 'bar | baz'); + ?column? +---------- + t +(1 row) + +/* YB: uncomment when above CREATE INDEX is supported +RESET enable_seqscan; +*/ -- YB +--test GUC +SET default_text_search_config=simple; +SELECT to_tsvector('SKIES My booKs'); + to_tsvector +---------------------------- + 'books':3 'my':2 'skies':1 +(1 row) + +SELECT plainto_tsquery('SKIES My booKs'); + plainto_tsquery +-------------------------- + 'skies' & 'my' & 'books' +(1 row) + +SELECT to_tsquery('SKIES & My | booKs'); + to_tsquery +-------------------------- + 'skies' & 'my' | 'books' +(1 row) + +SET default_text_search_config=english; +SELECT to_tsvector('SKIES My booKs'); + to_tsvector +------------------ + 'book':3 'sky':1 +(1 row) + +SELECT plainto_tsquery('SKIES My booKs'); + plainto_tsquery +----------------- + 'sky' & 'book' +(1 row) + +SELECT to_tsquery('SKIES & My | booKs'); + to_tsquery +---------------- + 'sky' | 'book' +(1 row) + +--trigger +CREATE TRIGGER tsvectorupdate +BEFORE UPDATE OR INSERT ON test_tsvector +FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger(a, 'pg_catalog.english', t); +SELECT count(*) FROM test_tsvector WHERE a @@ to_tsquery('345&qwerty'); + count +------- + 0 +(1 row) + +INSERT INTO test_tsvector (t) VALUES ('345 qwerty'); +SELECT count(*) FROM test_tsvector WHERE a @@ to_tsquery('345&qwerty'); + count +------- + 1 +(1 row) + +UPDATE test_tsvector SET t = null WHERE t = '345 qwerty'; +SELECT count(*) FROM test_tsvector WHERE a @@ to_tsquery('345&qwerty'); + count +------- + 0 +(1 row) + +INSERT INTO test_tsvector (t) VALUES ('345 qwerty'); +SELECT count(*) FROM test_tsvector WHERE a @@ to_tsquery('345&qwerty'); + count +------- + 1 +(1 row) + +-- Test inlining of immutable constant functions +-- to_tsquery(text) is not immutable, so it won't be inlined +explain (costs off) +select * from test_tsquery, to_tsquery('new') q where txtsample @@ q; + QUERY PLAN +------------------------------------------------ + Nested Loop + Join Filter: (test_tsquery.txtsample @@ q.q) + -> Function Scan on to_tsquery q + -> Seq Scan on test_tsquery +(4 rows) + +-- to_tsquery(regconfig, text) is an immutable function. +-- That allows us to get rid of using function scan and join at all. +explain (costs off) +select * from test_tsquery, to_tsquery('english', 'new') q where txtsample @@ q; + QUERY PLAN +--------------------------------------------- + Seq Scan on test_tsquery + Filter: (txtsample @@ '''new'''::tsquery) +(2 rows) + +-- test finding items in GIN's pending list +create temp table pendtest (ts tsvector); +create index pendtest_idx on pendtest using gin(ts); +insert into pendtest values (to_tsvector('Lore ipsam')); +insert into pendtest values (to_tsvector('Lore ipsum')); +select * from pendtest where 'ipsu:*'::tsquery @@ ts; + ts +-------------------- + 'ipsum':2 'lore':1 +(1 row) + +select * from pendtest where 'ipsa:*'::tsquery @@ ts; + ts +-------------------- + 'ipsam':2 'lore':1 +(1 row) + +select * from pendtest where 'ips:*'::tsquery @@ ts; + ts +-------------------- + 'ipsam':2 'lore':1 + 'ipsum':2 'lore':1 +(2 rows) + +select * from pendtest where 'ipt:*'::tsquery @@ ts; + ts +---- +(0 rows) + +select * from pendtest where 'ipi:*'::tsquery @@ ts; + ts +---- +(0 rows) + +--check OP_PHRASE on index +create temp table phrase_index_test(fts tsvector); +insert into phrase_index_test values ('A fat cat has just eaten a rat.'); +insert into phrase_index_test values (to_tsvector('english', 'A fat cat has just eaten a rat.')); +create index phrase_index_test_idx on phrase_index_test using gin(fts); +set enable_seqscan = off; +select * from phrase_index_test where fts @@ phraseto_tsquery('english', 'fat cat'); + fts +----------------------------------- + 'cat':3 'eaten':6 'fat':2 'rat':8 +(1 row) + +set enable_seqscan = on; +-- test websearch_to_tsquery function +select websearch_to_tsquery('simple', 'I have a fat:*ABCD cat'); + websearch_to_tsquery +--------------------------------------------- + 'i' & 'have' & 'a' & 'fat' & 'abcd' & 'cat' +(1 row) + +select websearch_to_tsquery('simple', 'orange:**AABBCCDD'); + websearch_to_tsquery +----------------------- + 'orange' & 'aabbccdd' +(1 row) + +select websearch_to_tsquery('simple', 'fat:A!cat:B|rat:C<'); + websearch_to_tsquery +----------------------------------------- + 'fat' & 'a' & 'cat' & 'b' & 'rat' & 'c' +(1 row) + +select websearch_to_tsquery('simple', 'fat:A : cat:B'); + websearch_to_tsquery +--------------------------- + 'fat' & 'a' & 'cat' & 'b' +(1 row) + +select websearch_to_tsquery('simple', 'fat*rat'); + websearch_to_tsquery +---------------------- + 'fat' <-> 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'fat-rat'); + websearch_to_tsquery +------------------------------- + 'fat-rat' <-> 'fat' <-> 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'fat_rat'); + websearch_to_tsquery +---------------------- + 'fat' <-> 'rat' +(1 row) + +-- weights are completely ignored +select websearch_to_tsquery('simple', 'abc : def'); + websearch_to_tsquery +---------------------- + 'abc' & 'def' +(1 row) + +select websearch_to_tsquery('simple', 'abc:def'); + websearch_to_tsquery +---------------------- + 'abc' & 'def' +(1 row) + +select websearch_to_tsquery('simple', 'a:::b'); + websearch_to_tsquery +---------------------- + 'a' & 'b' +(1 row) + +select websearch_to_tsquery('simple', 'abc:d'); + websearch_to_tsquery +---------------------- + 'abc' & 'd' +(1 row) + +select websearch_to_tsquery('simple', ':'); +NOTICE: text-search query contains only stop words or doesn't contain lexemes, ignored + websearch_to_tsquery +---------------------- + +(1 row) + +-- these operators are ignored +select websearch_to_tsquery('simple', 'abc & def'); + websearch_to_tsquery +---------------------- + 'abc' & 'def' +(1 row) + +select websearch_to_tsquery('simple', 'abc | def'); + websearch_to_tsquery +---------------------- + 'abc' & 'def' +(1 row) + +select websearch_to_tsquery('simple', 'abc <-> def'); + websearch_to_tsquery +---------------------- + 'abc' & 'def' +(1 row) + +select websearch_to_tsquery('simple', 'abc (pg or class)'); + websearch_to_tsquery +------------------------ + 'abc' & 'pg' | 'class' +(1 row) + +-- NOT is ignored in quotes +select websearch_to_tsquery('english', 'My brand new smartphone'); + websearch_to_tsquery +------------------------------- + 'brand' & 'new' & 'smartphon' +(1 row) + +select websearch_to_tsquery('english', 'My brand "new smartphone"'); + websearch_to_tsquery +--------------------------------- + 'brand' & 'new' <-> 'smartphon' +(1 row) + +select websearch_to_tsquery('english', 'My brand "new -smartphone"'); + websearch_to_tsquery +--------------------------------- + 'brand' & 'new' <-> 'smartphon' +(1 row) + +-- test OR operator +select websearch_to_tsquery('simple', 'cat or rat'); + websearch_to_tsquery +---------------------- + 'cat' | 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'cat OR rat'); + websearch_to_tsquery +---------------------- + 'cat' | 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'cat "OR" rat'); + websearch_to_tsquery +---------------------- + 'cat' & 'or' & 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'cat OR'); + websearch_to_tsquery +---------------------- + 'cat' & 'or' +(1 row) + +select websearch_to_tsquery('simple', 'OR rat'); + websearch_to_tsquery +---------------------- + 'or' & 'rat' +(1 row) + +select websearch_to_tsquery('simple', '"fat cat OR rat"'); + websearch_to_tsquery +------------------------------------ + 'fat' <-> 'cat' <-> 'or' <-> 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'fat (cat OR rat'); + websearch_to_tsquery +----------------------- + 'fat' & 'cat' | 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'or OR or'); + websearch_to_tsquery +---------------------- + 'or' | 'or' +(1 row) + +-- OR is an operator here ... +select websearch_to_tsquery('simple', '"fat cat"or"fat rat"'); + websearch_to_tsquery +----------------------------------- + 'fat' <-> 'cat' | 'fat' <-> 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'fat or(rat'); + websearch_to_tsquery +---------------------- + 'fat' | 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'fat or)rat'); + websearch_to_tsquery +---------------------- + 'fat' | 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'fat or&rat'); + websearch_to_tsquery +---------------------- + 'fat' | 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'fat or|rat'); + websearch_to_tsquery +---------------------- + 'fat' | 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'fat or!rat'); + websearch_to_tsquery +---------------------- + 'fat' | 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'fat orrat'); + websearch_to_tsquery +---------------------- + 'fat' | 'rat' +(1 row) + +select websearch_to_tsquery('simple', 'fat or '); + websearch_to_tsquery +---------------------- + 'fat' & 'or' +(1 row) + +-- ... but not here +select websearch_to_tsquery('simple', 'abc orange'); + websearch_to_tsquery +---------------------- + 'abc' & 'orange' +(1 row) + +select websearch_to_tsquery('simple', 'abc OR1234'); + websearch_to_tsquery +---------------------- + 'abc' & 'or1234' +(1 row) + +select websearch_to_tsquery('simple', 'abc or-abc'); + websearch_to_tsquery +------------------------------------- + 'abc' & 'or-abc' <-> 'or' <-> 'abc' +(1 row) + +select websearch_to_tsquery('simple', 'abc OR_abc'); + websearch_to_tsquery +------------------------ + 'abc' & 'or' <-> 'abc' +(1 row) + +-- test quotes +select websearch_to_tsquery('english', '"pg_class pg'); + websearch_to_tsquery +--------------------------- + 'pg' <-> 'class' <-> 'pg' +(1 row) + +select websearch_to_tsquery('english', 'pg_class pg"'); + websearch_to_tsquery +------------------------- + 'pg' <-> 'class' & 'pg' +(1 row) + +select websearch_to_tsquery('english', '"pg_class pg"'); + websearch_to_tsquery +--------------------------- + 'pg' <-> 'class' <-> 'pg' +(1 row) + +select websearch_to_tsquery('english', '"pg_class : pg"'); + websearch_to_tsquery +--------------------------- + 'pg' <-> 'class' <-> 'pg' +(1 row) + +select websearch_to_tsquery('english', 'abc "pg_class pg"'); + websearch_to_tsquery +----------------------------------- + 'abc' & 'pg' <-> 'class' <-> 'pg' +(1 row) + +select websearch_to_tsquery('english', '"pg_class pg" def'); + websearch_to_tsquery +----------------------------------- + 'pg' <-> 'class' <-> 'pg' & 'def' +(1 row) + +select websearch_to_tsquery('english', 'abc "pg pg_class pg" def'); + websearch_to_tsquery +---------------------------------------------------- + 'abc' & 'pg' <-> 'pg' <-> 'class' <-> 'pg' & 'def' +(1 row) + +select websearch_to_tsquery('english', ' or "pg pg_class pg" or '); + websearch_to_tsquery +------------------------------------ + 'pg' <-> 'pg' <-> 'class' <-> 'pg' +(1 row) + +select websearch_to_tsquery('english', '""pg pg_class pg""'); + websearch_to_tsquery +-------------------------------- + 'pg' & 'pg' <-> 'class' & 'pg' +(1 row) + +select websearch_to_tsquery('english', 'abc """"" def'); + websearch_to_tsquery +---------------------- + 'abc' & 'def' +(1 row) + +select websearch_to_tsquery('english', 'cat -"fat rat"'); + websearch_to_tsquery +------------------------------ + 'cat' & !( 'fat' <-> 'rat' ) +(1 row) + +select websearch_to_tsquery('english', 'cat -"fat rat" cheese'); + websearch_to_tsquery +---------------------------------------- + 'cat' & !( 'fat' <-> 'rat' ) & 'chees' +(1 row) + +select websearch_to_tsquery('english', 'abc "def -"'); + websearch_to_tsquery +---------------------- + 'abc' & 'def' +(1 row) + +select websearch_to_tsquery('english', 'abc "def :"'); + websearch_to_tsquery +---------------------- + 'abc' & 'def' +(1 row) + +select websearch_to_tsquery('english', '"A fat cat" has just eaten a -rat.'); + websearch_to_tsquery +------------------------------------ + 'fat' <-> 'cat' & 'eaten' & !'rat' +(1 row) + +select websearch_to_tsquery('english', '"A fat cat" has just eaten OR !rat.'); + websearch_to_tsquery +----------------------------------- + 'fat' <-> 'cat' & 'eaten' | 'rat' +(1 row) + +select websearch_to_tsquery('english', '"A fat cat" has just (+eaten OR -rat)'); + websearch_to_tsquery +------------------------------------ + 'fat' <-> 'cat' & 'eaten' | !'rat' +(1 row) + +select websearch_to_tsquery('english', 'this is ----fine'); + websearch_to_tsquery +---------------------- + !!!!'fine' +(1 row) + +select websearch_to_tsquery('english', '(()) )))) this ||| is && -fine, "dear friend" OR good'); + websearch_to_tsquery +---------------------------------------- + !'fine' & 'dear' <-> 'friend' | 'good' +(1 row) + +select websearch_to_tsquery('english', 'an old <-> cat " is fine &&& too'); + websearch_to_tsquery +------------------------ + 'old' & 'cat' & 'fine' +(1 row) + +select websearch_to_tsquery('english', '"A the" OR just on'); +NOTICE: text-search query contains only stop words or doesn't contain lexemes, ignored + websearch_to_tsquery +---------------------- + +(1 row) + +select websearch_to_tsquery('english', '"a fat cat" ate a rat'); + websearch_to_tsquery +--------------------------------- + 'fat' <-> 'cat' & 'ate' & 'rat' +(1 row) + +select to_tsvector('english', 'A fat cat ate a rat') @@ + websearch_to_tsquery('english', '"a fat cat" ate a rat'); + ?column? +---------- + t +(1 row) + +select to_tsvector('english', 'A fat grey cat ate a rat') @@ + websearch_to_tsquery('english', '"a fat cat" ate a rat'); + ?column? +---------- + f +(1 row) + +-- cases handled by gettoken_tsvector() +select websearch_to_tsquery(''''); +NOTICE: text-search query contains only stop words or doesn't contain lexemes, ignored + websearch_to_tsquery +---------------------- + +(1 row) + +select websearch_to_tsquery('''abc''''def'''); + websearch_to_tsquery +---------------------- + 'abc' <-> 'def' +(1 row) + +select websearch_to_tsquery('\abc'); + websearch_to_tsquery +---------------------- + 'abc' +(1 row) + +select websearch_to_tsquery('\'); +NOTICE: text-search query contains only stop words or doesn't contain lexemes, ignored + websearch_to_tsquery +---------------------- + +(1 row) + diff --git a/src/test/regress/expected/yb.port.update.out b/src/test/regress/expected/yb.port.update.out new file mode 100644 index 0000000000000..64ee7ba817d3a --- /dev/null +++ b/src/test/regress/expected/yb.port.update.out @@ -0,0 +1,757 @@ +-- Test ON CONFLICT DO UPDATE with partitioned table and non-identical children +CREATE TABLE upsert_test ( + a INT PRIMARY KEY, + b TEXT +) PARTITION BY LIST (a); +CREATE TABLE upsert_test_1 PARTITION OF upsert_test FOR VALUES IN (1); +CREATE TABLE upsert_test_2 (b TEXT, a INT PRIMARY KEY); +ALTER TABLE upsert_test ATTACH PARTITION upsert_test_2 FOR VALUES IN (2); +INSERT INTO upsert_test VALUES(1, 'Boo'), (2, 'Zoo'); +-- uncorrelated sub-select: +WITH aaa AS (SELECT 1 AS a, 'Foo' AS b) INSERT INTO upsert_test + VALUES (1, 'Bar') ON CONFLICT(a) + DO UPDATE SET (b, a) = (SELECT b, a FROM aaa) RETURNING *; + a | b +---+----- + 1 | Foo +(1 row) + +-- correlated sub-select: +WITH aaa AS (SELECT 1 AS ctea, ' Foo' AS cteb) INSERT INTO upsert_test + VALUES (1, 'Bar'), (2, 'Baz') ON CONFLICT(a) + DO UPDATE SET (b, a) = (SELECT upsert_test.b||cteb, upsert_test.a FROM aaa) RETURNING *; + a | b +---+--------- + 1 | Foo Foo + 2 | Zoo Foo +(2 rows) + +DROP TABLE upsert_test; +--------------------------- +-- UPDATE with row movement +--------------------------- +-- When a partitioned table receives an UPDATE to the partitioned key and the +-- new values no longer meet the partition's bound, the row must be moved to +-- the correct partition for the new partition key (if one exists). We must +-- also ensure that updatable views on partitioned tables properly enforce any +-- WITH CHECK OPTION that is defined. The situation with triggers in this case +-- also requires thorough testing as partition key updates causing row +-- movement convert UPDATEs into DELETE+INSERT. +CREATE TABLE range_parted ( + a text, + b bigint, + c numeric, + d int, + e varchar +) PARTITION BY RANGE (a, b); +-- Create partitions intentionally in descending bound order, so as to test +-- that update-row-movement works with the leaf partitions not in bound order. +CREATE TABLE part_b_20_b_30 (e varchar, c numeric, a text, b bigint, d int); +ALTER TABLE range_parted ATTACH PARTITION part_b_20_b_30 FOR VALUES FROM ('b', 20) TO ('b', 30); +CREATE TABLE part_b_10_b_20 (e varchar, c numeric, a text, b bigint, d int) PARTITION BY RANGE (c); +CREATE TABLE part_b_1_b_10 PARTITION OF range_parted FOR VALUES FROM ('b', 1) TO ('b', 10); +ALTER TABLE range_parted ATTACH PARTITION part_b_10_b_20 FOR VALUES FROM ('b', 10) TO ('b', 20); +CREATE TABLE part_a_10_a_20 PARTITION OF range_parted FOR VALUES FROM ('a', 10) TO ('a', 20); +CREATE TABLE part_a_1_a_10 PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('a', 10); +-- Check that partition-key UPDATE works sanely on a partitioned table that +-- does not have any child partitions. +UPDATE part_b_10_b_20 set b = b - 6; +-- Create some more partitions following the above pattern of descending bound +-- order, but let's make the situation a bit more complex by having the +-- attribute numbers of the columns vary from their parent partition. +CREATE TABLE part_c_100_200 (e varchar, c numeric, a text, b bigint, d int) PARTITION BY range (abs(d)); +ALTER TABLE part_c_100_200 DROP COLUMN e, DROP COLUMN c, DROP COLUMN a; +ALTER TABLE part_c_100_200 ADD COLUMN c numeric, ADD COLUMN e varchar, ADD COLUMN a text; +ALTER TABLE part_c_100_200 DROP COLUMN b; +ALTER TABLE part_c_100_200 ADD COLUMN b bigint; +CREATE TABLE part_d_1_15 PARTITION OF part_c_100_200 FOR VALUES FROM (1) TO (15); +CREATE TABLE part_d_15_20 PARTITION OF part_c_100_200 FOR VALUES FROM (15) TO (20); +ALTER TABLE part_b_10_b_20 ATTACH PARTITION part_c_100_200 FOR VALUES FROM (100) TO (200); +CREATE TABLE part_c_1_100 (e varchar, d int, c numeric, b bigint, a text); +ALTER TABLE part_b_10_b_20 ATTACH PARTITION part_c_1_100 FOR VALUES FROM (1) TO (100); +\set init_range_parted 'truncate range_parted; insert into range_parted VALUES (''a'', 1, 1, 1), (''a'', 10, 200, 1), (''b'', 12, 96, 1), (''b'', 13, 97, 2), (''b'', 15, 105, 16), (''b'', 17, 105, 19)' +\set show_data 'select tableoid::regclass::text COLLATE "C" partname, * from range_parted ORDER BY 1, 2, 3, 4, 5, 6' +:init_range_parted; +:show_data; + partname | a | b | c | d | e +----------------+---+----+-----+----+--- + part_a_10_a_20 | a | 10 | 200 | 1 | + part_a_1_a_10 | a | 1 | 1 | 1 | + part_c_1_100 | b | 12 | 96 | 1 | + part_c_1_100 | b | 13 | 97 | 2 | + part_d_15_20 | b | 15 | 105 | 16 | + part_d_15_20 | b | 17 | 105 | 19 | +(6 rows) + +-- The order of subplans should be in bound order +EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97; + QUERY PLAN +------------------------------------------------------- + Update on range_parted + Update on part_a_1_a_10 range_parted_1 + Update on part_a_10_a_20 range_parted_2 + Update on part_b_1_b_10 range_parted_3 + Update on part_c_1_100 range_parted_5 + Update on part_d_1_15 range_parted_7 + Update on part_d_15_20 range_parted_8 + Update on part_b_20_b_30 range_parted_9 + -> Append + -> Seq Scan on part_a_1_a_10 range_parted_1 + Storage Filter: (c > '97'::numeric) + -> Seq Scan on part_a_10_a_20 range_parted_2 + Storage Filter: (c > '97'::numeric) + -> Seq Scan on part_b_1_b_10 range_parted_3 + Storage Filter: (c > '97'::numeric) + -> Seq Scan on part_c_1_100 range_parted_5 + Storage Filter: (c > '97'::numeric) + -> Seq Scan on part_d_1_15 range_parted_7 + Storage Filter: (c > '97'::numeric) + -> Seq Scan on part_d_15_20 range_parted_8 + Storage Filter: (c > '97'::numeric) + -> Seq Scan on part_b_20_b_30 range_parted_9 + Storage Filter: (c > '97'::numeric) +(23 rows) + +-- fail, row movement happens only within the partition subtree. +UPDATE part_c_100_200 set c = c - 20, d = c WHERE c = 105 AND b = 15; +ERROR: new row for relation "part_c_100_200" violates partition constraint +DETAIL: Failing row contains (105, 85, null, b, 15). +-- fail, no partition key update, so no attempt to move tuple, +-- but "a = 'a'" violates partition constraint enforced by root partition) +UPDATE part_b_10_b_20 set a = 'a' WHERE b = 12; +ERROR: new row for relation "part_b_10_b_20" violates partition constraint +DETAIL: Failing row contains (null, 96, a, 12, 1). +-- ok, partition key update, no constraint violation +UPDATE range_parted set d = d - 10 WHERE d > 10; +-- ok, no partition key update, no constraint violation +UPDATE range_parted set e = d; +-- No row found +UPDATE part_c_1_100 set c = c + 20 WHERE c = 98; +-- ok, row movement +WITH x AS (UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a) +SELECT * FROM x ORDER BY b; + c | b | a +-----+----+--- + 116 | 12 | b + 117 | 13 | b + 125 | 15 | b + 125 | 17 | b +(4 rows) + +:show_data; + partname | a | b | c | d | e +----------------+---+----+-----+---+--- + part_a_10_a_20 | a | 10 | 200 | 1 | 1 + part_a_1_a_10 | a | 1 | 1 | 1 | 1 + part_d_1_15 | b | 12 | 116 | 1 | 1 + part_d_1_15 | b | 13 | 117 | 2 | 2 + part_d_1_15 | b | 15 | 125 | 6 | 6 + part_d_1_15 | b | 17 | 125 | 9 | 9 +(6 rows) + +-- fail, row movement happens only within the partition subtree. +UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 AND d = 2 returning *; +ERROR: new row for relation "part_b_10_b_20" violates partition constraint +DETAIL: Failing row contains (2, 117, b, 7, 2). +-- ok, row movement, with subset of rows moved into different partition. +WITH x AS (UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c AS f) +SELECT * FROM x ORDER BY a, f; + a | f +---+----- + a | 204 + b | 124 + b | 134 + b | 136 +(4 rows) + +:show_data; + partname | a | b | c | d | e +---------------+---+----+-----+---+--- + part_a_1_a_10 | a | 1 | 1 | 1 | 1 + part_a_1_a_10 | a | 4 | 200 | 1 | 1 + part_b_1_b_10 | b | 7 | 117 | 2 | 2 + part_b_1_b_10 | b | 9 | 125 | 6 | 6 + part_d_1_15 | b | 11 | 125 | 9 | 9 + part_d_1_15 | b | 12 | 116 | 1 | 1 +(6 rows) + +-- Common table needed for multiple test scenarios. +CREATE TABLE mintab(c1 int); +INSERT into mintab VALUES (120); +-- update partition key using updatable view. +CREATE VIEW upview AS SELECT * FROM range_parted WHERE (select c > c1 FROM mintab) WITH CHECK OPTION; +ERROR: VIEW WITH CHECK OPTION not supported yet +LINE 1: ...OM range_parted WHERE (select c > c1 FROM mintab) WITH CHECK... + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +/* +The following tests should be enabled after VIEW WITH CHECK OPTION +is supported. +-- ok +UPDATE upview set c = 199 WHERE b = 4; +-- fail, check option violation +UPDATE upview set c = 120 WHERE b = 4; +-- fail, row movement with check option violation +-- ok, row movement, check option passes +UPDATE upview set a = 'b', b = 15 WHERE b = 4; +-- cleanup +DROP VIEW upview; +*/ +-- RETURNING having whole-row vars. +:init_range_parted; +WITH x AS (UPDATE range_parted set c = 95 WHERE a = 'b' and b > 10 and c > 100 returning (range_parted), *) +SELECT * FROM x ORDER BY b; + range_parted | a | b | c | d | e +---------------+---+----+----+----+--- + (b,15,95,16,) | b | 15 | 95 | 16 | + (b,17,95,19,) | b | 17 | 95 | 19 | +(2 rows) + +:show_data; + partname | a | b | c | d | e +----------------+---+----+-----+----+--- + part_a_10_a_20 | a | 10 | 200 | 1 | + part_a_1_a_10 | a | 1 | 1 | 1 | + part_c_1_100 | b | 12 | 96 | 1 | + part_c_1_100 | b | 13 | 97 | 2 | + part_c_1_100 | b | 15 | 95 | 16 | + part_c_1_100 | b | 17 | 95 | 19 | +(6 rows) + +-- The following tests computing RETURNING when the source and the destination +-- partitions of a UPDATE row movement operation have different tuple +-- descriptors, which has been shown to be problematic in the cases where the +-- RETURNING targetlist contains non-target relation attributes that are +-- computed by referring to the source partition plan's output tuple. Also, +-- a trigger on the destination relation may change the tuple, which must be +-- reflected in the RETURNING output, so we test that too. +CREATE TABLE part_c_1_c_20 (LIKE range_parted); +-- YB Note: Split the below statement into 2 Alter statements to work around GH #20342 +-- ALTER TABLE part_c_1_c_20 DROP a, DROP b, ADD a text, ADD b bigint; +ALTER TABLE part_c_1_c_20 DROP a, DROP b; +ALTER TABLE part_c_1_c_20 ADD a text, ADD b bigint; +ALTER TABLE range_parted ATTACH PARTITION part_c_1_c_20 FOR VALUES FROM ('c', 1) TO ('c', 20); +CREATE FUNCTION trigfunc () RETURNS TRIGGER LANGUAGE plpgsql as $$ BEGIN NEW.e := 'in trigfunc()'; RETURN NEW; END; $$; +CREATE TRIGGER trig BEFORE INSERT ON part_c_1_c_20 FOR EACH ROW EXECUTE FUNCTION trigfunc(); +UPDATE range_parted r set a = 'c' FROM (VALUES ('a', 1), ('a', 10), ('b', 12)) s(x, y) WHERE s.x = r.a AND s.y = r.b RETURNING tableoid::regclass, *; + tableoid | a | b | c | d | e | x | y +---------------+---+----+-----+---+---------------+---+---- + part_c_1_c_20 | c | 1 | 1 | 1 | in trigfunc() | a | 1 + part_c_1_c_20 | c | 10 | 200 | 1 | in trigfunc() | a | 10 + part_c_1_c_20 | c | 12 | 96 | 1 | in trigfunc() | b | 12 +(3 rows) + +DROP TRIGGER trig ON part_c_1_c_20; +DROP FUNCTION trigfunc; +:init_range_parted; +CREATE FUNCTION trigfunc () RETURNS TRIGGER LANGUAGE plpgsql as $$ BEGIN RETURN NULL; END; $$; +CREATE TRIGGER trig BEFORE INSERT ON part_c_1_c_20 FOR EACH ROW EXECUTE FUNCTION trigfunc(); +UPDATE range_parted r set a = 'c' FROM (VALUES ('a', 1), ('a', 10), ('b', 12)) s(x, y) WHERE s.x = r.a AND s.y = r.b RETURNING tableoid::regclass, *; + tableoid | a | b | c | d | e | x | y +----------+---+---+---+---+---+---+--- +(0 rows) + +:show_data; + partname | a | b | c | d | e +--------------+---+----+-----+----+--- + part_c_1_100 | b | 13 | 97 | 2 | + part_d_15_20 | b | 15 | 105 | 16 | + part_d_15_20 | b | 17 | 105 | 19 | +(3 rows) + +DROP TABLE part_c_1_c_20; +DROP FUNCTION trigfunc; +-- Transition tables with update row movement +:init_range_parted; +CREATE FUNCTION trans_updatetrigfunc() RETURNS trigger LANGUAGE plpgsql AS +$$ + begin + raise notice 'trigger = %, old table = %, new table = %', + TG_NAME, + (select string_agg(old_table::text, ', ' ORDER BY a) FROM old_table), + (select string_agg(new_table::text, ', ' ORDER BY a) FROM new_table); + return null; + end; +$$; +-- Enable the following tests after REFERENCING clause (transition tables) is supported. +CREATE TRIGGER trans_updatetrig + AFTER UPDATE ON range_parted REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc(); +ERROR: REFERENCING clause (transition tables) not supported yet +LINE 2: AFTER UPDATE ON range_parted REFERENCING OLD TABLE AS old_... + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1668. React with thumbs up to raise its priority +/* +UPDATE range_parted set c = (case when c = 96 then 110 else c + 1 end ) WHERE a = 'b' and b > 10 and c >= 96; +:show_data; +:init_range_parted; + +-- Enabling OLD TABLE capture for both DELETE as well as UPDATE stmt triggers +-- should not cause DELETEd rows to be captured twice. Similar thing for +-- INSERT triggers and inserted rows. +CREATE TRIGGER trans_deletetrig + AFTER DELETE ON range_parted REFERENCING OLD TABLE AS old_table + FOR EACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc(); +CREATE TRIGGER trans_inserttrig + AFTER INSERT ON range_parted REFERENCING NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc(); +UPDATE range_parted set c = c + 50 WHERE a = 'b' and b > 10 and c >= 96; +:show_data; +DROP TRIGGER trans_deletetrig ON range_parted; +DROP TRIGGER trans_inserttrig ON range_parted; +-- Don't drop trans_updatetrig yet. It is required below. + +-- Test with transition tuple conversion happening for rows moved into the +-- new partition. This requires a trigger that references transition table +-- (we already have trans_updatetrig). For inserted rows, the conversion +-- is not usually needed, because the original tuple is already compatible with +-- the desired transition tuple format. But conversion happens when there is a +-- BR trigger because the trigger can change the inserted row. So install a +-- BR triggers on those child partitions where the rows will be moved. +CREATE FUNCTION func_parted_mod_b() RETURNS trigger AS $$ +BEGIN + NEW.b = NEW.b + 1; + return NEW; +END $$ language plpgsql; +CREATE TRIGGER trig_c1_100 BEFORE UPDATE OR INSERT ON part_c_1_100 + FOR EACH ROW EXECUTE PROCEDURE func_parted_mod_b(); +CREATE TRIGGER trig_d1_15 BEFORE UPDATE OR INSERT ON part_d_1_15 + FOR EACH ROW EXECUTE PROCEDURE func_parted_mod_b(); +CREATE TRIGGER trig_d15_20 BEFORE UPDATE OR INSERT ON part_d_15_20 + FOR EACH ROW EXECUTE PROCEDURE func_parted_mod_b(); +:init_range_parted; +UPDATE range_parted set c = (case when c = 96 then 110 else c + 1 end) WHERE a = 'b' and b > 10 and c >= 96; +:show_data; +:init_range_parted; +UPDATE range_parted set c = c + 50 WHERE a = 'b' and b > 10 and c >= 96; +:show_data; + +-- Case where per-partition tuple conversion map array is allocated, but the +-- map is not required for the particular tuple that is routed, thanks to +-- matching table attributes of the partition and the target table. +:init_range_parted; +UPDATE range_parted set b = 15 WHERE b = 1; +:show_data; +DROP TRIGGER trans_updatetrig ON range_parted; +DROP TRIGGER trig_c1_100 ON part_c_1_100; +DROP TRIGGER trig_d1_15 ON part_d_1_15; +DROP TRIGGER trig_d15_20 ON part_d_15_20; +DROP FUNCTION func_parted_mod_b(); +*/ +-- RLS policies with update-row-movement +----------------------------------------- +ALTER TABLE range_parted ENABLE ROW LEVEL SECURITY; +CREATE USER regress_range_parted_user; +GRANT ALL ON range_parted, mintab TO regress_range_parted_user; +CREATE POLICY seeall ON range_parted AS PERMISSIVE FOR SELECT USING (true); +CREATE POLICY policy_range_parted ON range_parted for UPDATE USING (true) WITH CHECK (c % 2 = 0); +:init_range_parted; +SET SESSION AUTHORIZATION regress_range_parted_user; +-- This should fail with RLS violation error while moving row from +-- part_a_10_a_20 to part_d_1_15, because we are setting 'c' to an odd number. +UPDATE range_parted set a = 'b', c = 151 WHERE a = 'a' and c = 200; +ERROR: new row violates row-level security policy for table "range_parted" +RESET SESSION AUTHORIZATION; +-- Create a trigger on part_d_1_15 +CREATE FUNCTION func_d_1_15() RETURNS trigger AS $$ +BEGIN + NEW.c = NEW.c + 1; -- Make even numbers odd, or vice versa + return NEW; +END $$ LANGUAGE plpgsql; +CREATE TRIGGER trig_d_1_15 BEFORE INSERT ON part_d_1_15 + FOR EACH ROW EXECUTE PROCEDURE func_d_1_15(); +:init_range_parted; +SET SESSION AUTHORIZATION regress_range_parted_user; +-- Here, RLS checks should succeed while moving row from part_a_10_a_20 to +-- part_d_1_15. Even though the UPDATE is setting 'c' to an odd number, the +-- trigger at the destination partition again makes it an even number. +UPDATE range_parted set a = 'b', c = 151 WHERE a = 'a' and c = 200; +RESET SESSION AUTHORIZATION; +:init_range_parted; +SET SESSION AUTHORIZATION regress_range_parted_user; +-- This should fail with RLS violation error. Even though the UPDATE is setting +-- 'c' to an even number, the trigger at the destination partition again makes +-- it an odd number. +UPDATE range_parted set a = 'b', c = 150 WHERE a = 'a' and c = 200; +ERROR: new row violates row-level security policy for table "range_parted" +-- Cleanup +RESET SESSION AUTHORIZATION; +DROP TRIGGER trig_d_1_15 ON part_d_1_15; +DROP FUNCTION func_d_1_15(); +-- Policy expression contains SubPlan +RESET SESSION AUTHORIZATION; +:init_range_parted; +CREATE POLICY policy_range_parted_subplan on range_parted + AS RESTRICTIVE for UPDATE USING (true) + WITH CHECK ((SELECT range_parted.c <= c1 FROM mintab)); +SET SESSION AUTHORIZATION regress_range_parted_user; +-- fail, mintab has row with c1 = 120 +UPDATE range_parted set a = 'b', c = 122 WHERE a = 'a' and c = 200; +ERROR: new row violates row-level security policy "policy_range_parted_subplan" for table "range_parted" +-- ok +UPDATE range_parted set a = 'b', c = 120 WHERE a = 'a' and c = 200; +-- RLS policy expression contains whole row. +RESET SESSION AUTHORIZATION; +:init_range_parted; +CREATE POLICY policy_range_parted_wholerow on range_parted AS RESTRICTIVE for UPDATE USING (true) + WITH CHECK (range_parted = row('b', 10, 112, 1, NULL)::range_parted); +SET SESSION AUTHORIZATION regress_range_parted_user; +-- ok, should pass the RLS check +UPDATE range_parted set a = 'b', c = 112 WHERE a = 'a' and c = 200; +RESET SESSION AUTHORIZATION; +:init_range_parted; +SET SESSION AUTHORIZATION regress_range_parted_user; +-- fail, the whole row RLS check should fail +UPDATE range_parted set a = 'b', c = 116 WHERE a = 'a' and c = 200; +ERROR: new row violates row-level security policy "policy_range_parted_wholerow" for table "range_parted" +-- Cleanup +RESET SESSION AUTHORIZATION; +DROP POLICY policy_range_parted ON range_parted; +DROP POLICY policy_range_parted_subplan ON range_parted; +DROP POLICY policy_range_parted_wholerow ON range_parted; +REVOKE ALL ON range_parted, mintab FROM regress_range_parted_user; +DROP USER regress_range_parted_user; +DROP TABLE mintab; +-- statement triggers with update row movement +--------------------------------------------------- +:init_range_parted; +CREATE FUNCTION trigfunc() returns trigger language plpgsql as +$$ + begin + raise notice 'trigger = % fired on table % during %', + TG_NAME, TG_TABLE_NAME, TG_OP; + return null; + end; +$$; +-- Triggers on root partition +CREATE TRIGGER parent_delete_trig + AFTER DELETE ON range_parted for each statement execute procedure trigfunc(); +CREATE TRIGGER parent_update_trig + AFTER UPDATE ON range_parted for each statement execute procedure trigfunc(); +CREATE TRIGGER parent_insert_trig + AFTER INSERT ON range_parted for each statement execute procedure trigfunc(); +-- Triggers on leaf partition part_c_1_100 +CREATE TRIGGER c1_delete_trig + AFTER DELETE ON part_c_1_100 for each statement execute procedure trigfunc(); +CREATE TRIGGER c1_update_trig + AFTER UPDATE ON part_c_1_100 for each statement execute procedure trigfunc(); +CREATE TRIGGER c1_insert_trig + AFTER INSERT ON part_c_1_100 for each statement execute procedure trigfunc(); +-- Triggers on leaf partition part_d_1_15 +CREATE TRIGGER d1_delete_trig + AFTER DELETE ON part_d_1_15 for each statement execute procedure trigfunc(); +CREATE TRIGGER d1_update_trig + AFTER UPDATE ON part_d_1_15 for each statement execute procedure trigfunc(); +CREATE TRIGGER d1_insert_trig + AFTER INSERT ON part_d_1_15 for each statement execute procedure trigfunc(); +-- Triggers on leaf partition part_d_15_20 +CREATE TRIGGER d15_delete_trig + AFTER DELETE ON part_d_15_20 for each statement execute procedure trigfunc(); +CREATE TRIGGER d15_update_trig + AFTER UPDATE ON part_d_15_20 for each statement execute procedure trigfunc(); +CREATE TRIGGER d15_insert_trig + AFTER INSERT ON part_d_15_20 for each statement execute procedure trigfunc(); +-- Move all rows from part_c_100_200 to part_c_1_100. None of the delete or +-- insert statement triggers should be fired. +UPDATE range_parted set c = c - 50 WHERE c > 97; +NOTICE: trigger = parent_update_trig fired on table range_parted during UPDATE +:show_data; + partname | a | b | c | d | e +----------------+---+----+-----+----+--- + part_a_10_a_20 | a | 10 | 150 | 1 | + part_a_1_a_10 | a | 1 | 1 | 1 | + part_c_1_100 | b | 12 | 96 | 1 | + part_c_1_100 | b | 13 | 97 | 2 | + part_c_1_100 | b | 15 | 55 | 16 | + part_c_1_100 | b | 17 | 55 | 19 | +(6 rows) + +DROP TRIGGER parent_delete_trig ON range_parted; +DROP TRIGGER parent_update_trig ON range_parted; +DROP TRIGGER parent_insert_trig ON range_parted; +DROP TRIGGER c1_delete_trig ON part_c_1_100; +DROP TRIGGER c1_update_trig ON part_c_1_100; +DROP TRIGGER c1_insert_trig ON part_c_1_100; +DROP TRIGGER d1_delete_trig ON part_d_1_15; +DROP TRIGGER d1_update_trig ON part_d_1_15; +DROP TRIGGER d1_insert_trig ON part_d_1_15; +DROP TRIGGER d15_delete_trig ON part_d_15_20; +DROP TRIGGER d15_update_trig ON part_d_15_20; +DROP TRIGGER d15_insert_trig ON part_d_15_20; +-- Creating default partition for range +:init_range_parted; +create table part_def partition of range_parted default; +\d+ part_def + Table "public.part_def" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+-------------------+-----------+----------+---------+----------+--------------+------------- + a | text | | | | extended | | + b | bigint | | | | plain | | + c | numeric | | | | main | | + d | integer | | | | plain | | + e | character varying | | | | extended | | +Partition of: range_parted DEFAULT +Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint))))) + +insert into range_parted values ('c', 9); +-- ok +update part_def set a = 'd' where a = 'c'; +-- fail +update part_def set a = 'a' where a = 'd'; +ERROR: new row for relation "part_def" violates partition constraint +DETAIL: Failing row contains (a, 9, null, null, null). +:show_data; + partname | a | b | c | d | e +----------------+---+----+-----+----+--- + part_a_10_a_20 | a | 10 | 200 | 1 | + part_a_1_a_10 | a | 1 | 1 | 1 | + part_c_1_100 | b | 12 | 96 | 1 | + part_c_1_100 | b | 13 | 97 | 2 | + part_d_15_20 | b | 15 | 105 | 16 | + part_d_15_20 | b | 17 | 105 | 19 | + part_def | d | 9 | | | +(7 rows) + +-- Update row movement from non-default to default partition. +-- fail, default partition is not under part_a_10_a_20; +UPDATE part_a_10_a_20 set a = 'ad' WHERE a = 'a'; +ERROR: new row for relation "part_a_10_a_20" violates partition constraint +DETAIL: Failing row contains (ad, 10, 200, 1, null). +-- ok +UPDATE range_parted set a = 'ad' WHERE a = 'a'; +UPDATE range_parted set a = 'bd' WHERE a = 'b'; +:show_data; + partname | a | b | c | d | e +----------+----+----+-----+----+--- + part_def | ad | 1 | 1 | 1 | + part_def | ad | 10 | 200 | 1 | + part_def | bd | 12 | 96 | 1 | + part_def | bd | 13 | 97 | 2 | + part_def | bd | 15 | 105 | 16 | + part_def | bd | 17 | 105 | 19 | + part_def | d | 9 | | | +(7 rows) + +-- Update row movement from default to non-default partitions. +-- ok +UPDATE range_parted set a = 'a' WHERE a = 'ad'; +UPDATE range_parted set a = 'b' WHERE a = 'bd'; +:show_data; + partname | a | b | c | d | e +----------------+---+----+-----+----+--- + part_a_10_a_20 | a | 10 | 200 | 1 | + part_a_1_a_10 | a | 1 | 1 | 1 | + part_c_1_100 | b | 12 | 96 | 1 | + part_c_1_100 | b | 13 | 97 | 2 | + part_d_15_20 | b | 15 | 105 | 16 | + part_d_15_20 | b | 17 | 105 | 19 | + part_def | d | 9 | | | +(7 rows) + +-- Cleanup: range_parted no longer needed. +DROP TABLE range_parted; +CREATE TABLE list_parted ( + a text, + b int +) PARTITION BY list (a); +CREATE TABLE list_part1 PARTITION OF list_parted for VALUES in ('a', 'b'); +CREATE TABLE list_default PARTITION OF list_parted default; +INSERT into list_part1 VALUES ('a', 1); +INSERT into list_default VALUES ('d', 10); +-- fail +UPDATE list_default set a = 'a' WHERE a = 'd'; +ERROR: new row for relation "list_default" violates partition constraint +DETAIL: Failing row contains (a, 10). +-- ok +UPDATE list_default set a = 'x' WHERE a = 'd'; +DROP TABLE list_parted; +-------------- +-- Some more update-partition-key test scenarios below. This time use list +-- partitions. +-------------- +-- Setup for list partitions +CREATE TABLE list_parted (a numeric, b int, c int8) PARTITION BY list (a); +CREATE TABLE sub_parted PARTITION OF list_parted for VALUES in (1) PARTITION BY list (b); +CREATE TABLE sub_part1(b int, c int8, a numeric); +ALTER TABLE sub_parted ATTACH PARTITION sub_part1 for VALUES in (1); +CREATE TABLE sub_part2(b int, c int8, a numeric); +ALTER TABLE sub_parted ATTACH PARTITION sub_part2 for VALUES in (2); +CREATE TABLE list_part1(a numeric, b int, c int8); +ALTER TABLE list_parted ATTACH PARTITION list_part1 for VALUES in (2,3); +INSERT into list_parted VALUES (2,5,50); +INSERT into list_parted VALUES (3,6,60); +INSERT into sub_parted VALUES (1,1,60); +INSERT into sub_parted VALUES (1,2,10); +-- Test partition constraint violation when intermediate ancestor is used and +-- constraint is inherited from upper root. +UPDATE sub_parted set a = 2 WHERE c = 10; +ERROR: new row for relation "sub_parted" violates partition constraint +DETAIL: Failing row contains (2, 2, 10). +-- Test update-partition-key, where the unpruned partitions do not have their +-- partition keys updated. +SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1; + tableoid | a | b | c +------------+---+---+---- + list_part1 | 2 | 5 | 50 +(1 row) + +UPDATE list_parted set b = c + a WHERE a = 2; +SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1; + tableoid | a | b | c +------------+---+----+---- + list_part1 | 2 | 52 | 50 +(1 row) + +-- Test the case where BR UPDATE triggers change the partition key. +CREATE FUNCTION func_parted_mod_b() returns trigger as $$ +BEGIN + NEW.b = 2; -- This is changing partition key column. + return NEW; +END $$ LANGUAGE plpgsql; +CREATE TRIGGER parted_mod_b before update on sub_part1 + for each row execute procedure func_parted_mod_b(); +SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; + tableoid | a | b | c +------------+---+----+---- + list_part1 | 2 | 52 | 50 + list_part1 | 3 | 6 | 60 + sub_part1 | 1 | 1 | 60 + sub_part2 | 1 | 2 | 10 +(4 rows) + +-- This should do the tuple routing even though there is no explicit +-- partition-key update, because there is a trigger on sub_part1. +UPDATE list_parted set c = 70 WHERE b = 1; +SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; + tableoid | a | b | c +------------+---+----+---- + list_part1 | 2 | 52 | 50 + list_part1 | 3 | 6 | 60 + sub_part2 | 1 | 2 | 10 + sub_part2 | 1 | 2 | 70 +(4 rows) + +DROP TRIGGER parted_mod_b ON sub_part1; +-- If BR DELETE trigger prevented DELETE from happening, we should also skip +-- the INSERT if that delete is part of UPDATE=>DELETE+INSERT. +CREATE OR REPLACE FUNCTION func_parted_mod_b() returns trigger as $$ +BEGIN + raise notice 'Trigger: Got OLD row %, but returning NULL', OLD; + return NULL; +END $$ LANGUAGE plpgsql; +CREATE TRIGGER trig_skip_delete before delete on sub_part2 + for each row execute procedure func_parted_mod_b(); +UPDATE list_parted set b = 1 WHERE c = 70; +NOTICE: Trigger: Got OLD row (2,70,1), but returning NULL +SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; + tableoid | a | b | c +------------+---+----+---- + list_part1 | 2 | 52 | 50 + list_part1 | 3 | 6 | 60 + sub_part2 | 1 | 2 | 10 + sub_part2 | 1 | 2 | 70 +(4 rows) + +-- Drop the trigger. Now the row should be moved. +DROP TRIGGER trig_skip_delete ON sub_part2; +UPDATE list_parted set b = 1 WHERE c = 70; +SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; + tableoid | a | b | c +------------+---+----+---- + list_part1 | 2 | 52 | 50 + list_part1 | 3 | 6 | 60 + sub_part1 | 1 | 1 | 70 + sub_part2 | 1 | 2 | 10 +(4 rows) + +DROP FUNCTION func_parted_mod_b(); +-- UPDATE partition-key with FROM clause. If join produces multiple output +-- rows for the same row to be modified, we should tuple-route the row only +-- once. There should not be any rows inserted. +CREATE TABLE non_parted (id int); +INSERT into non_parted VALUES (1), (1), (1), (2), (2), (2), (3), (3), (3); +UPDATE list_parted t1 set a = 2 FROM non_parted t2 WHERE t1.a = t2.id and a = 1; +SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; + tableoid | a | b | c +------------+---+----+---- + list_part1 | 2 | 1 | 70 + list_part1 | 2 | 2 | 10 + list_part1 | 2 | 52 | 50 + list_part1 | 3 | 6 | 60 +(4 rows) + +DROP TABLE non_parted; +-- Cleanup: list_parted no longer needed. +DROP TABLE list_parted; +-- create custom operator class and hash function, for the same reason +-- explained in alter_table.sql +create or replace function dummy_hashint4(a int4, seed int8) returns int8 as +$$ begin return (a + seed); end; $$ language 'plpgsql' immutable; +create operator class custom_opclass for type int4 using hash as +operator 1 = , function 2 dummy_hashint4(int4, int8); +create table hash_parted ( + a int, + b int +) partition by hash (a custom_opclass, b custom_opclass); +create table hpart1 partition of hash_parted for values with (modulus 2, remainder 1); +create table hpart2 partition of hash_parted for values with (modulus 4, remainder 2); +create table hpart3 partition of hash_parted for values with (modulus 8, remainder 0); +create table hpart4 partition of hash_parted for values with (modulus 8, remainder 4); +insert into hpart1 values (1, 1); +insert into hpart2 values (2, 5); +insert into hpart4 values (3, 4); +-- fail +update hpart1 set a = 3, b=4 where a = 1; +ERROR: new row for relation "hpart1" violates partition constraint +DETAIL: Failing row contains (3, 4). +-- ok, row movement +update hash_parted set b = b - 1 where b = 1; +-- ok +update hash_parted set b = b + 8 where b = 1; +-- cleanup +drop table hash_parted; +drop operator class custom_opclass using hash; +drop function dummy_hashint4(a int4, seed int8); +CREATE TABLE parted (a int, b text) PARTITION BY RANGE(a); +CREATE TABLE part_a_1_5 PARTITION OF parted (a, b, PRIMARY KEY(a)) FOR VALUES FROM (1) TO (5); +CREATE TABLE part_a_5_10 PARTITION OF parted (a, b, PRIMARY KEY(a)) FOR VALUES FROM (5) TO (10); +INSERT INTO parted VALUES (1, '1'), (2, '2'), (4, '4'), (6, '6'), (8, '8'); +-- Test whether single row optimization is invoked when +-- only one partition is being updated. +EXPLAIN UPDATE parted SET b='5' WHERE a = 1; + QUERY PLAN +--------------------------------------------------------- + Update on parted (cost=0.00..4.11 rows=0 width=0) + Update on part_a_1_5 parted + -> Result parted (cost=0.00..4.11 rows=1 width=100) +(3 rows) + +UPDATE parted SET b='5' WHERE a = 1; +-- Verify that single row optimization is not invoked when +-- multiple partitions are being updated. +EXPLAIN UPDATE parted SET b='6' WHERE a > 1; + QUERY PLAN +--------------------------------------------------------------------------------------- + Update on parted (cost=0.00..215.00 rows=0 width=0) + Update on part_a_1_5 parted_1 + Update on part_a_5_10 parted_2 + -> Append (cost=0.00..215.00 rows=2000 width=100) + -> Seq Scan on part_a_1_5 parted_1 (cost=0.00..102.50 rows=1000 width=100) + Storage Filter: (a > 1) + -> Seq Scan on part_a_5_10 parted_2 (cost=0.00..102.50 rows=1000 width=100) + Storage Filter: (a > 1) +(8 rows) + +UPDATE parted SET b='6' WHERE a > 1; +-- Verify that the updates happened successfully. +SELECT * FROM parted ORDER BY a; + a | b +---+--- + 1 | 5 + 2 | 6 + 4 | 6 + 6 | 6 + 8 | 6 +(5 rows) + +DROP TABLE parted; diff --git a/src/test/regress/expected/yb.port.uuid.out b/src/test/regress/expected/yb.port.uuid.out new file mode 100644 index 0000000000000..2b789d39e7b63 --- /dev/null +++ b/src/test/regress/expected/yb.port.uuid.out @@ -0,0 +1,137 @@ +-- regression test for the uuid datatype +-- creating test tables +CREATE TABLE guid1 +( + guid_field UUID, + text_field TEXT DEFAULT(now()) +); +CREATE TABLE guid2 +( + guid_field UUID, + text_field TEXT DEFAULT(now()) +); +-- inserting invalid data tests +-- too long +INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111F'); +ERROR: invalid input syntax for type uuid: "11111111-1111-1111-1111-111111111111F" +LINE 1: INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-111... + ^ +-- too short +INSERT INTO guid1(guid_field) VALUES('{11111111-1111-1111-1111-11111111111}'); +ERROR: invalid input syntax for type uuid: "{11111111-1111-1111-1111-11111111111}" +LINE 1: INSERT INTO guid1(guid_field) VALUES('{11111111-1111-1111-11... + ^ +-- valid data but invalid format +INSERT INTO guid1(guid_field) VALUES('111-11111-1111-1111-1111-111111111111'); +ERROR: invalid input syntax for type uuid: "111-11111-1111-1111-1111-111111111111" +LINE 1: INSERT INTO guid1(guid_field) VALUES('111-11111-1111-1111-11... + ^ +INSERT INTO guid1(guid_field) VALUES('{22222222-2222-2222-2222-222222222222 '); +ERROR: invalid input syntax for type uuid: "{22222222-2222-2222-2222-222222222222 " +LINE 1: INSERT INTO guid1(guid_field) VALUES('{22222222-2222-2222-22... + ^ +-- invalid data +INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-G111-111111111111'); +ERROR: invalid input syntax for type uuid: "11111111-1111-1111-G111-111111111111" +LINE 1: INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-G11... + ^ +INSERT INTO guid1(guid_field) VALUES('11+11111-1111-1111-1111-111111111111'); +ERROR: invalid input syntax for type uuid: "11+11111-1111-1111-1111-111111111111" +LINE 1: INSERT INTO guid1(guid_field) VALUES('11+11111-1111-1111-111... + ^ +--inserting three input formats +INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111'); +INSERT INTO guid1(guid_field) VALUES('{22222222-2222-2222-2222-222222222222}'); +INSERT INTO guid1(guid_field) VALUES('3f3e3c3b3a3039383736353433a2313e'); +-- ordering test +SELECT guid_field FROM guid1 ORDER BY guid_field ASC; + guid_field +-------------------------------------- + 11111111-1111-1111-1111-111111111111 + 22222222-2222-2222-2222-222222222222 + 3f3e3c3b-3a30-3938-3736-353433a2313e +(3 rows) + +SELECT guid_field FROM guid1 ORDER BY guid_field DESC; + guid_field +-------------------------------------- + 3f3e3c3b-3a30-3938-3736-353433a2313e + 22222222-2222-2222-2222-222222222222 + 11111111-1111-1111-1111-111111111111 +(3 rows) + +-- = operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field = '3f3e3c3b-3a30-3938-3736-353433a2313e'; + count +------- + 1 +(1 row) + +-- <> operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field <> '11111111111111111111111111111111'; + count +------- + 2 +(1 row) + +-- < operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field < '22222222-2222-2222-2222-222222222222'; + count +------- + 1 +(1 row) + +-- <= operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field <= '22222222-2222-2222-2222-222222222222'; + count +------- + 2 +(1 row) + +-- > operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field > '22222222-2222-2222-2222-222222222222'; + count +------- + 1 +(1 row) + +-- >= operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field >= '22222222-2222-2222-2222-222222222222'; + count +------- + 2 +(1 row) + +---------------------------------------------------------------------------------------------------- +-- Uncomment the following test cases once INDEX is supported in YugaByte +-- lsm and hash index creation test +-- CREATE INDEX guid1_lsm ON guid1 USING LSM (guid_field); +-- CREATE INDEX guid1_hash ON guid1 USING HASH (guid_field); +-- unique index test +-- CREATE UNIQUE INDEX guid1_unique_LSM ON guid1 USING LSM (guid_field); +-- should fail +-- INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111'); +-- check to see whether the new indexes are actually there +-- SELECT count(*) FROM pg_class WHERE relkind='i' AND relname LIKE 'guid%'; +---------------------------------------------------------------------------------------------------- +-- populating the test tables with additional records +INSERT INTO guid1(guid_field) VALUES('44444444-4444-4444-4444-444444444444'); +INSERT INTO guid2(guid_field) VALUES('11111111-1111-1111-1111-111111111111'); +INSERT INTO guid2(guid_field) VALUES('{22222222-2222-2222-2222-222222222222}'); +INSERT INTO guid2(guid_field) VALUES('3f3e3c3b3a3039383736353433a2313e'); +-- join test +SELECT COUNT(*) FROM guid1 g1 INNER JOIN guid2 g2 ON g1.guid_field = g2.guid_field; + count +------- + 3 +(1 row) + +SELECT COUNT(*) FROM guid1 g1 LEFT JOIN guid2 g2 ON g1.guid_field = g2.guid_field + WHERE g2.guid_field IS NULL; + count +------- + 1 +(1 row) + +-- clean up +DROP TABLE guid1, guid2 CASCADE; diff --git a/src/test/regress/expected/yb.port.varchar.out b/src/test/regress/expected/yb.port.varchar.out new file mode 100644 index 0000000000000..3db9aa29f1c2f --- /dev/null +++ b/src/test/regress/expected/yb.port.varchar.out @@ -0,0 +1,124 @@ +-- +-- VARCHAR +-- +-- +-- Build a table for testing +-- (This temporarily hides the table created in test_setup.sql) +-- YB note: but we also want to test YB tables instead of temporary tables, so +-- create a new table in a new namespace instead. +-- +CREATE SCHEMA yb_tmp; +SET search_path TO yb_tmp; +-- YB note: add ordering column to match upstream PG output ordering. Also +-- adjust SELECTs below to not use * and instead use f1 in order to avoid +-- outputting up the ordering column. +CREATE TABLE VARCHAR_TBL(ybid serial, f1 varchar(1), PRIMARY KEY (ybid ASC)); +INSERT INTO VARCHAR_TBL (f1) VALUES ('a'); +INSERT INTO VARCHAR_TBL (f1) VALUES ('A'); +-- any of the following three input formats are acceptable +INSERT INTO VARCHAR_TBL (f1) VALUES ('1'); +INSERT INTO VARCHAR_TBL (f1) VALUES (2); +INSERT INTO VARCHAR_TBL (f1) VALUES ('3'); +-- zero-length char +INSERT INTO VARCHAR_TBL (f1) VALUES (''); +-- try varchar's of greater than 1 length +INSERT INTO VARCHAR_TBL (f1) VALUES ('cd'); +ERROR: value too long for type character varying(1) +INSERT INTO VARCHAR_TBL (f1) VALUES ('c '); +SELECT '' AS seven, f1 FROM VARCHAR_TBL; + seven | f1 +-------+---- + | a + | A + | 1 + | 2 + | 3 + | + | c +(7 rows) + +SELECT '' AS six, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 <> 'a'; + six | f1 +-----+---- + | A + | 1 + | 2 + | 3 + | + | c +(6 rows) + +SELECT '' AS one, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 = 'a'; + one | f1 +-----+---- + | a +(1 row) + +SELECT '' AS five, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 < 'a'; + five | f1 +------+---- + | A + | 1 + | 2 + | 3 + | +(5 rows) + +SELECT '' AS six, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 <= 'a'; + six | f1 +-----+---- + | a + | A + | 1 + | 2 + | 3 + | +(6 rows) + +SELECT '' AS one, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 > 'a'; + one | f1 +-----+---- + | c +(1 row) + +SELECT '' AS two, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 >= 'a'; + two | f1 +-----+---- + | a + | c +(2 rows) + +DROP TABLE VARCHAR_TBL; +-- YB note: reset the changes from above. +DROP SCHEMA yb_tmp; +RESET search_path; +-- +-- Now test longer arrays of char +-- +-- This varchar_tbl was already created and filled in test_setup.sql. +-- Here we just try to insert bad values. +-- +INSERT INTO VARCHAR_TBL (f1) VALUES ('abcde'); +ERROR: value too long for type character varying(4) +-- YB note: add query ordering to match upstream PG output ordering. +SELECT '' AS four, * FROM VARCHAR_TBL ORDER BY 1, 2; + four | f1 +------+------ + | a + | ab + | abcd + | abcd +(4 rows) + diff --git a/src/test/regress/expected/yb.port.window.out b/src/test/regress/expected/yb.port.window.out new file mode 100644 index 0000000000000..4cb88a84ad32e --- /dev/null +++ b/src/test/regress/expected/yb.port.window.out @@ -0,0 +1,3847 @@ +-- +-- WINDOW FUNCTIONS +-- +CREATE TEMPORARY TABLE empsalary ( + depname varchar, + empno bigint, + salary int, + enroll_date date +); +INSERT INTO empsalary VALUES +('develop', 10, 5200, '2007-08-01'), +('sales', 1, 5000, '2006-10-01'), +('personnel', 5, 3500, '2007-12-10'), +('sales', 4, 4800, '2007-08-08'), +('personnel', 2, 3900, '2006-12-23'), +('develop', 7, 4200, '2008-01-01'), +('develop', 9, 4500, '2008-01-01'), +('sales', 3, 4800, '2007-08-01'), +('develop', 8, 6000, '2006-10-01'), +('develop', 11, 5200, '2007-08-15'); +SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary; + depname | empno | salary | sum +-----------+-------+--------+------- + develop | 7 | 4200 | 25100 + develop | 9 | 4500 | 25100 + develop | 11 | 5200 | 25100 + develop | 10 | 5200 | 25100 + develop | 8 | 6000 | 25100 + personnel | 5 | 3500 | 7400 + personnel | 2 | 3900 | 7400 + sales | 3 | 4800 | 14600 + sales | 4 | 4800 | 14600 + sales | 1 | 5000 | 14600 +(10 rows) + +SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER by 1,2,3,4; + depname | empno | salary | rank +-----------+-------+--------+------ + develop | 7 | 4200 | 1 + develop | 8 | 6000 | 5 + develop | 9 | 4500 | 2 + develop | 10 | 5200 | 3 + develop | 11 | 5200 | 3 + personnel | 2 | 3900 | 2 + personnel | 5 | 3500 | 1 + sales | 1 | 5000 | 3 + sales | 3 | 4800 | 1 + sales | 4 | 4800 | 1 +(10 rows) + +-- with GROUP BY +SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1 +GROUP BY four, ten ORDER BY four, ten; + four | ten | sum | avg +------+-----+------+------------------------ + 0 | 0 | 0 | 0.00000000000000000000 + 0 | 2 | 0 | 2.0000000000000000 + 0 | 4 | 0 | 4.0000000000000000 + 0 | 6 | 0 | 6.0000000000000000 + 0 | 8 | 0 | 8.0000000000000000 + 1 | 1 | 2500 | 1.00000000000000000000 + 1 | 3 | 2500 | 3.0000000000000000 + 1 | 5 | 2500 | 5.0000000000000000 + 1 | 7 | 2500 | 7.0000000000000000 + 1 | 9 | 2500 | 9.0000000000000000 + 2 | 0 | 5000 | 0.00000000000000000000 + 2 | 2 | 5000 | 2.0000000000000000 + 2 | 4 | 5000 | 4.0000000000000000 + 2 | 6 | 5000 | 6.0000000000000000 + 2 | 8 | 5000 | 8.0000000000000000 + 3 | 1 | 7500 | 1.00000000000000000000 + 3 | 3 | 7500 | 3.0000000000000000 + 3 | 5 | 7500 | 5.0000000000000000 + 3 | 7 | 7500 | 7.0000000000000000 + 3 | 9 | 7500 | 9.0000000000000000 +(20 rows) + +SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname) ORDER by 1,2,3,4; + depname | empno | salary | sum +-----------+-------+--------+------- + develop | 7 | 4200 | 25100 + develop | 8 | 6000 | 25100 + develop | 9 | 4500 | 25100 + develop | 10 | 5200 | 25100 + develop | 11 | 5200 | 25100 + personnel | 2 | 3900 | 7400 + personnel | 5 | 3500 | 7400 + sales | 1 | 5000 | 14600 + sales | 3 | 4800 | 14600 + sales | 4 | 4800 | 14600 +(10 rows) + +SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w; + depname | empno | salary | rank +-----------+-------+--------+------ + develop | 7 | 4200 | 1 + personnel | 5 | 3500 | 1 + sales | 3 | 4800 | 1 + sales | 4 | 4800 | 1 + personnel | 2 | 3900 | 2 + develop | 9 | 4500 | 2 + sales | 1 | 5000 | 3 + develop | 11 | 5200 | 3 + develop | 10 | 5200 | 3 + develop | 8 | 6000 | 5 +(10 rows) + +-- empty window specification +SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10 ORDER by 1; + count +------- + 10 + 10 + 10 + 10 + 10 + 10 + 10 + 10 + 10 + 10 +(10 rows) + +SELECT COUNT(*) OVER w FROM tenk1 WHERE unique2 < 10 WINDOW w AS () ORDER by 1; + count +------- + 10 + 10 + 10 + 10 + 10 + 10 + 10 + 10 + 10 + 10 +(10 rows) + +-- no window operation +SELECT four FROM tenk1 WHERE FALSE WINDOW w AS (PARTITION BY ten); + four +------ +(0 rows) + +-- cumulative aggregate +SELECT sum(four) OVER (PARTITION BY ten ORDER BY unique2) AS sum_1, ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + sum_1 | ten | four +-------+-----+------ + 0 | 0 | 0 + 0 | 0 | 0 + 0 | 4 | 0 + 1 | 7 | 1 + 1 | 9 | 1 + 2 | 0 | 2 + 3 | 1 | 3 + 3 | 3 | 3 + 4 | 1 | 1 + 5 | 1 | 1 +(10 rows) + +SELECT row_number() OVER (ORDER BY unique2) FROM tenk1 WHERE unique2 < 10 ORDER by 1; + row_number +------------ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +SELECT rank() OVER (PARTITION BY four ORDER BY ten) AS rank_1, ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + rank_1 | ten | four +--------+-----+------ + 1 | 0 | 0 + 1 | 0 | 0 + 1 | 0 | 2 + 1 | 1 | 1 + 1 | 1 | 1 + 1 | 1 | 3 + 2 | 3 | 3 + 3 | 4 | 0 + 3 | 7 | 1 + 4 | 9 | 1 +(10 rows) + +SELECT dense_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + dense_rank | ten | four +------------+-----+------ + 1 | 0 | 0 + 1 | 0 | 0 + 1 | 0 | 2 + 1 | 1 | 1 + 1 | 1 | 1 + 1 | 1 | 3 + 2 | 3 | 3 + 2 | 4 | 0 + 2 | 7 | 1 + 3 | 9 | 1 +(10 rows) + +SELECT percent_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + percent_rank | ten | four +--------------------+-----+------ + 0 | 0 | 0 + 0 | 0 | 0 + 0 | 0 | 2 + 0 | 1 | 1 + 0 | 1 | 1 + 0 | 1 | 3 + 0.6666666666666666 | 7 | 1 + 1 | 3 | 3 + 1 | 4 | 0 + 1 | 9 | 1 +(10 rows) + +SELECT cume_dist() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + cume_dist | ten | four +--------------------+-----+------ + 0.5 | 1 | 1 + 0.5 | 1 | 1 + 0.5 | 1 | 3 + 0.6666666666666666 | 0 | 0 + 0.6666666666666666 | 0 | 0 + 0.75 | 7 | 1 + 1 | 0 | 2 + 1 | 3 | 3 + 1 | 4 | 0 + 1 | 9 | 1 +(10 rows) + +SELECT ntile(3) OVER (ORDER BY ten, four), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + ntile | ten | four +-------+-----+------ + 1 | 0 | 0 + 1 | 0 | 0 + 1 | 0 | 2 + 1 | 1 | 1 + 2 | 1 | 1 + 2 | 1 | 3 + 2 | 3 | 3 + 3 | 4 | 0 + 3 | 7 | 1 + 3 | 9 | 1 +(10 rows) + +SELECT ntile(NULL) OVER (ORDER BY ten, four), ten, four FROM tenk1 LIMIT 2; + ntile | ten | four +-------+-----+------ + | 0 | 0 + | 0 | 0 +(2 rows) + +SELECT lag(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + lag | ten | four +-----+-----+------ + 0 | 0 | 0 + 0 | 4 | 0 + 1 | 1 | 1 + 1 | 3 | 3 + 1 | 7 | 1 + 7 | 9 | 1 + | 0 | 0 + | 0 | 2 + | 1 | 1 + | 1 | 3 +(10 rows) + +SELECT lag(ten, four) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + lag | ten | four +-----+-----+------ + 0 | 0 | 0 + 0 | 0 | 0 + 1 | 1 | 1 + 1 | 7 | 1 + 4 | 4 | 0 + 7 | 9 | 1 + | 0 | 2 + | 1 | 1 + | 1 | 3 + | 3 | 3 +(10 rows) + +SELECT lag(ten, four, 0) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + lag | ten | four +-----+-----+------ + 0 | 0 | 0 + 0 | 0 | 0 + 0 | 0 | 2 + 0 | 1 | 1 + 0 | 1 | 3 + 0 | 3 | 3 + 1 | 1 | 1 + 1 | 7 | 1 + 4 | 4 | 0 + 7 | 9 | 1 +(10 rows) + +SELECT lead(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + lead | ten | four +------+-----+------ + 0 | 0 | 0 + 1 | 1 | 1 + 3 | 1 | 3 + 4 | 0 | 0 + 7 | 1 | 1 + 9 | 7 | 1 + | 0 | 2 + | 3 | 3 + | 4 | 0 + | 9 | 1 +(10 rows) + +SELECT lead(ten * 2, 1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + lead | ten | four +------+-----+------ + 0 | 0 | 0 + 2 | 1 | 1 + 6 | 1 | 3 + 8 | 0 | 0 + 14 | 1 | 1 + 18 | 7 | 1 + | 0 | 2 + | 3 | 3 + | 4 | 0 + | 9 | 1 +(10 rows) + +SELECT lead(ten * 2, 1, -1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + lead | ten | four +------+-----+------ + -1 | 0 | 2 + -1 | 3 | 3 + -1 | 4 | 0 + -1 | 9 | 1 + 0 | 0 | 0 + 2 | 1 | 1 + 6 | 1 | 3 + 8 | 0 | 0 + 14 | 1 | 1 + 18 | 7 | 1 +(10 rows) + +SELECT first_value(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + first_value | ten | four +-------------+-----+------ + 0 | 0 | 0 + 0 | 0 | 0 + 0 | 0 | 2 + 0 | 4 | 0 + 1 | 1 | 1 + 1 | 1 | 1 + 1 | 1 | 3 + 1 | 3 | 3 + 1 | 7 | 1 + 1 | 9 | 1 +(10 rows) + +-- last_value returns the last row of the frame, which is CURRENT ROW in ORDER BY window. +-- Changed from original window.sql: ORDER BY unique1 instead of ORDER BY ten +-- See for more information: https://github.com/yugabyte/yugabyte-db/issues/4832 +SELECT last_value(four) OVER (ORDER BY unique1), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + last_value | ten | four +------------+-----+------ + 0 | 0 | 0 + 0 | 0 | 0 + 0 | 4 | 0 + 1 | 1 | 1 + 1 | 1 | 1 + 1 | 7 | 1 + 1 | 9 | 1 + 2 | 0 | 2 + 3 | 1 | 3 + 3 | 3 | 3 +(10 rows) + +SELECT last_value(ten) OVER (PARTITION BY four), ten, four FROM + (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten)s + ORDER BY four, ten; + last_value | ten | four +------------+-----+------ + 4 | 0 | 0 + 4 | 0 | 0 + 4 | 4 | 0 + 9 | 1 | 1 + 9 | 1 | 1 + 9 | 7 | 1 + 9 | 9 | 1 + 0 | 0 | 2 + 3 | 1 | 3 + 3 | 3 | 3 +(10 rows) + +SELECT nth_value(ten, four + 1) OVER (PARTITION BY four), ten, four + FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten)s; + nth_value | ten | four +-----------+-----+------ + 0 | 0 | 0 + 0 | 0 | 0 + 0 | 4 | 0 + 1 | 1 | 1 + 1 | 1 | 1 + 1 | 7 | 1 + 1 | 9 | 1 + | 0 | 2 + | 1 | 3 + | 3 | 3 +(10 rows) + +SELECT ten, two, sum(hundred) AS gsum, sum(sum(hundred)) OVER (PARTITION BY two ORDER BY ten) AS wsum +FROM tenk1 GROUP BY ten, two; + ten | two | gsum | wsum +-----+-----+-------+-------- + 0 | 0 | 45000 | 45000 + 2 | 0 | 47000 | 92000 + 4 | 0 | 49000 | 141000 + 6 | 0 | 51000 | 192000 + 8 | 0 | 53000 | 245000 + 1 | 1 | 46000 | 46000 + 3 | 1 | 48000 | 94000 + 5 | 1 | 50000 | 144000 + 7 | 1 | 52000 | 196000 + 9 | 1 | 54000 | 250000 +(10 rows) + +SELECT count(*) OVER (PARTITION BY four), four FROM (SELECT * FROM tenk1 WHERE two = 1)s WHERE unique2 < 10 ORDER by 1,2; + count | four +-------+------ + 2 | 3 + 2 | 3 + 4 | 1 + 4 | 1 + 4 | 1 + 4 | 1 +(6 rows) + +SELECT (count(*) OVER (PARTITION BY four ORDER BY ten) + + sum(hundred) OVER (PARTITION BY four ORDER BY ten))::varchar AS cntsum + FROM tenk1 WHERE unique2 < 10 ORDER by 1; + cntsum +-------- + 136 + 22 + 22 + 24 + 24 + 51 + 82 + 87 + 92 + 92 +(10 rows) + +-- opexpr with different windows evaluation. +SELECT * FROM( + SELECT count(*) OVER (PARTITION BY four ORDER BY ten) + + sum(hundred) OVER (PARTITION BY two ORDER BY ten) AS total, + count(*) OVER (PARTITION BY four ORDER BY ten) AS fourcount, + sum(hundred) OVER (PARTITION BY two ORDER BY ten) AS twosum + FROM tenk1 +)sub +WHERE total <> fourcount + twosum; + total | fourcount | twosum +-------+-----------+-------- +(0 rows) + +SELECT avg(four) OVER (PARTITION BY four ORDER BY thousand / 100) FROM tenk1 WHERE unique2 < 10 ORDER by 1; + avg +------------------------ + 0.00000000000000000000 + 0.00000000000000000000 + 0.00000000000000000000 + 1.00000000000000000000 + 1.00000000000000000000 + 1.00000000000000000000 + 1.00000000000000000000 + 2.0000000000000000 + 3.0000000000000000 + 3.0000000000000000 +(10 rows) + +SELECT ten, two, sum(hundred) AS gsum, sum(sum(hundred)) OVER win AS wsum +FROM tenk1 GROUP BY ten, two WINDOW win AS (PARTITION BY two ORDER BY ten) ORDER by 1,2,3,4; + ten | two | gsum | wsum +-----+-----+-------+-------- + 0 | 0 | 45000 | 45000 + 1 | 1 | 46000 | 46000 + 2 | 0 | 47000 | 92000 + 3 | 1 | 48000 | 94000 + 4 | 0 | 49000 | 141000 + 5 | 1 | 50000 | 144000 + 6 | 0 | 51000 | 192000 + 7 | 1 | 52000 | 196000 + 8 | 0 | 53000 | 245000 + 9 | 1 | 54000 | 250000 +(10 rows) + +-- more than one window with GROUP BY +SELECT sum(salary), + row_number() OVER (ORDER BY depname), + sum(sum(salary)) OVER (ORDER BY depname DESC) +FROM empsalary GROUP BY depname; + sum | row_number | sum +-------+------------+------- + 25100 | 1 | 47100 + 7400 | 2 | 22000 + 14600 | 3 | 14600 +(3 rows) + +-- identical windows with different names +SELECT sum(salary) OVER w1, count(*) OVER w2 +FROM empsalary WINDOW w1 AS (ORDER BY salary), w2 AS (ORDER BY salary) ORDER by 1,2; + sum | count +-------+------- + 3500 | 1 + 7400 | 2 + 11600 | 3 + 16100 | 4 + 25700 | 6 + 25700 | 6 + 30700 | 7 + 41100 | 9 + 41100 | 9 + 47100 | 10 +(10 rows) + +-- subplan +SELECT lead(ten, (SELECT two FROM tenk1 WHERE s.unique2 = unique2)) OVER (PARTITION BY four ORDER BY ten) +FROM tenk1 s WHERE unique2 < 10 ORDER by 1; + lead +------ + 0 + 0 + 0 + 1 + 3 + 4 + 7 + 9 + + +(10 rows) + +-- empty table +SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 WHERE FALSE)s; + count +------- +(0 rows) + +-- mixture of agg/wfunc in the same window +SELECT sum(salary) OVER w, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary DESC) ORDER by 1,2; + sum | rank +-------+------ + 3900 | 1 + 5000 | 1 + 6000 | 1 + 7400 | 2 + 14600 | 2 + 14600 | 2 + 16400 | 2 + 16400 | 2 + 20900 | 4 + 25100 | 5 +(10 rows) + +-- strict aggs +SELECT empno, depname, salary, bonus, depadj, MIN(bonus) OVER (ORDER BY empno), MAX(depadj) OVER () FROM( + SELECT *, + CASE WHEN enroll_date < '2008-01-01' THEN 2008 - extract(YEAR FROM enroll_date) END * 500 AS bonus, + CASE WHEN + AVG(salary) OVER (PARTITION BY depname) < salary + THEN 200 END AS depadj FROM empsalary +)s +ORDER by 1,2,3,4,5,6,7; + empno | depname | salary | bonus | depadj | min | max +-------+-----------+--------+-------+--------+------+----- + 1 | sales | 5000 | 1000 | 200 | 1000 | 200 + 2 | personnel | 3900 | 1000 | 200 | 1000 | 200 + 3 | sales | 4800 | 500 | | 500 | 200 + 4 | sales | 4800 | 500 | | 500 | 200 + 5 | personnel | 3500 | 500 | | 500 | 200 + 7 | develop | 4200 | | | 500 | 200 + 8 | develop | 6000 | 1000 | 200 | 500 | 200 + 9 | develop | 4500 | | | 500 | 200 + 10 | develop | 5200 | 500 | 200 | 500 | 200 + 11 | develop | 5200 | 500 | 200 | 500 | 200 +(10 rows) + +-- window function over ungrouped agg over empty row set (bug before 9.1) +SELECT SUM(COUNT(f1)) OVER () FROM int4_tbl WHERE f1=42; + sum +----- + 0 +(1 row) + +-- window function with ORDER BY an expression involving aggregates (9.1 bug) +select ten, + sum(unique1) + sum(unique2) as res, + rank() over (order by sum(unique1) + sum(unique2)) as rank +from tenk1 +group by ten order by ten; + ten | res | rank +-----+----------+------ + 0 | 9976146 | 4 + 1 | 10114187 | 9 + 2 | 10059554 | 8 + 3 | 9878541 | 1 + 4 | 9881005 | 2 + 5 | 9981670 | 5 + 6 | 9947099 | 3 + 7 | 10120309 | 10 + 8 | 9991305 | 6 + 9 | 10040184 | 7 +(10 rows) + +-- window and aggregate with GROUP BY expression (9.2 bug) +explain (costs off) +select first_value(max(x)) over (), y + from (select unique1 as x, ten+four as y from tenk1) ss + group by y; + QUERY PLAN +--------------------------------------------- + WindowAgg + -> HashAggregate + Group Key: (tenk1.ten + tenk1.four) + -> Seq Scan on tenk1 +(4 rows) + +-- test non-default frame specifications +SELECT four, ten, + sum(ten) over (partition by four order by ten), + last_value(ten) over (partition by four order by ten) +FROM (select distinct ten, four from tenk1) ss +ORDER by 1,2,3,4; + four | ten | sum | last_value +------+-----+-----+------------ + 0 | 0 | 0 | 0 + 0 | 2 | 2 | 2 + 0 | 4 | 6 | 4 + 0 | 6 | 12 | 6 + 0 | 8 | 20 | 8 + 1 | 1 | 1 | 1 + 1 | 3 | 4 | 3 + 1 | 5 | 9 | 5 + 1 | 7 | 16 | 7 + 1 | 9 | 25 | 9 + 2 | 0 | 0 | 0 + 2 | 2 | 2 | 2 + 2 | 4 | 6 | 4 + 2 | 6 | 12 | 6 + 2 | 8 | 20 | 8 + 3 | 1 | 1 | 1 + 3 | 3 | 4 | 3 + 3 | 5 | 9 | 5 + 3 | 7 | 16 | 7 + 3 | 9 | 25 | 9 +(20 rows) + +SELECT four, ten, + sum(ten) over (partition by four order by ten range between unbounded preceding and current row), + last_value(ten) over (partition by four order by ten range between unbounded preceding and current row) +FROM (select distinct ten, four from tenk1) ss +ORDER by 1,2,3,4; + four | ten | sum | last_value +------+-----+-----+------------ + 0 | 0 | 0 | 0 + 0 | 2 | 2 | 2 + 0 | 4 | 6 | 4 + 0 | 6 | 12 | 6 + 0 | 8 | 20 | 8 + 1 | 1 | 1 | 1 + 1 | 3 | 4 | 3 + 1 | 5 | 9 | 5 + 1 | 7 | 16 | 7 + 1 | 9 | 25 | 9 + 2 | 0 | 0 | 0 + 2 | 2 | 2 | 2 + 2 | 4 | 6 | 4 + 2 | 6 | 12 | 6 + 2 | 8 | 20 | 8 + 3 | 1 | 1 | 1 + 3 | 3 | 4 | 3 + 3 | 5 | 9 | 5 + 3 | 7 | 16 | 7 + 3 | 9 | 25 | 9 +(20 rows) + +SELECT four, ten, + sum(ten) over (partition by four order by ten range between unbounded preceding and unbounded following), + last_value(ten) over (partition by four order by ten range between unbounded preceding and unbounded following) +FROM (select distinct ten, four from tenk1) ss +ORDER by 1,2,3,4; + four | ten | sum | last_value +------+-----+-----+------------ + 0 | 0 | 20 | 8 + 0 | 2 | 20 | 8 + 0 | 4 | 20 | 8 + 0 | 6 | 20 | 8 + 0 | 8 | 20 | 8 + 1 | 1 | 25 | 9 + 1 | 3 | 25 | 9 + 1 | 5 | 25 | 9 + 1 | 7 | 25 | 9 + 1 | 9 | 25 | 9 + 2 | 0 | 20 | 8 + 2 | 2 | 20 | 8 + 2 | 4 | 20 | 8 + 2 | 6 | 20 | 8 + 2 | 8 | 20 | 8 + 3 | 1 | 25 | 9 + 3 | 3 | 25 | 9 + 3 | 5 | 25 | 9 + 3 | 7 | 25 | 9 + 3 | 9 | 25 | 9 +(20 rows) + +SELECT four, ten/4 as two, + sum(ten/4) over (partition by four order by ten/4 range between unbounded preceding and current row), + last_value(ten/4) over (partition by four order by ten/4 range between unbounded preceding and current row) +FROM (select distinct ten, four from tenk1) ss +ORDER by 1,2,3,4; + four | two | sum | last_value +------+-----+-----+------------ + 0 | 0 | 0 | 0 + 0 | 0 | 0 | 0 + 0 | 1 | 2 | 1 + 0 | 1 | 2 | 1 + 0 | 2 | 4 | 2 + 1 | 0 | 0 | 0 + 1 | 0 | 0 | 0 + 1 | 1 | 2 | 1 + 1 | 1 | 2 | 1 + 1 | 2 | 4 | 2 + 2 | 0 | 0 | 0 + 2 | 0 | 0 | 0 + 2 | 1 | 2 | 1 + 2 | 1 | 2 | 1 + 2 | 2 | 4 | 2 + 3 | 0 | 0 | 0 + 3 | 0 | 0 | 0 + 3 | 1 | 2 | 1 + 3 | 1 | 2 | 1 + 3 | 2 | 4 | 2 +(20 rows) + +SELECT four, ten/4 as two, + sum(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row), + last_value(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row) +FROM (select distinct ten, four from tenk1) ss +ORDER by 1,2,3,4; + four | two | sum | last_value +------+-----+-----+------------ + 0 | 0 | 0 | 0 + 0 | 0 | 0 | 0 + 0 | 1 | 1 | 1 + 0 | 1 | 2 | 1 + 0 | 2 | 4 | 2 + 1 | 0 | 0 | 0 + 1 | 0 | 0 | 0 + 1 | 1 | 1 | 1 + 1 | 1 | 2 | 1 + 1 | 2 | 4 | 2 + 2 | 0 | 0 | 0 + 2 | 0 | 0 | 0 + 2 | 1 | 1 | 1 + 2 | 1 | 2 | 1 + 2 | 2 | 4 | 2 + 3 | 0 | 0 | 0 + 3 | 0 | 0 | 0 + 3 | 1 | 1 | 1 + 3 | 1 | 2 | 1 + 3 | 2 | 4 | 2 +(20 rows) + +SELECT sum(unique1) over (order by four range between current row and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 10 | 3 | 3 + 10 | 7 | 3 + 18 | 2 | 2 + 18 | 6 | 2 + 33 | 1 | 1 + 33 | 5 | 1 + 33 | 9 | 1 + 45 | 0 | 0 + 45 | 4 | 0 + 45 | 8 | 0 +(10 rows) + +SELECT sum(unique1) over (order by unique1 rows between current row and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 9 | 9 | 1 + 17 | 8 | 0 + 24 | 7 | 3 + 30 | 6 | 2 + 35 | 5 | 1 + 39 | 4 | 0 + 42 | 3 | 3 + 44 | 2 | 2 + 45 | 0 | 0 + 45 | 1 | 1 +(10 rows) + +SELECT sum(unique1) over (order by unique1 rows between 2 preceding and 2 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 3 | 0 | 0 + 6 | 1 | 1 + 10 | 2 | 2 + 15 | 3 | 3 + 20 | 4 | 0 + 24 | 9 | 1 + 25 | 5 | 1 + 30 | 6 | 2 + 30 | 8 | 0 + 35 | 7 | 3 +(10 rows) + +SELECT sum(unique1) over (order by unique1 rows between 2 preceding and 2 following exclude no others), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 3 | 0 | 0 + 6 | 1 | 1 + 10 | 2 | 2 + 15 | 3 | 3 + 20 | 4 | 0 + 24 | 9 | 1 + 25 | 5 | 1 + 30 | 6 | 2 + 30 | 8 | 0 + 35 | 7 | 3 +(10 rows) + +SELECT sum(unique1) over (order by unique1 rows between 2 preceding and 2 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 3 | 0 | 0 + 5 | 1 | 1 + 8 | 2 | 2 + 12 | 3 | 3 + 15 | 9 | 1 + 16 | 4 | 0 + 20 | 5 | 1 + 22 | 8 | 0 + 24 | 6 | 2 + 28 | 7 | 3 +(10 rows) + +SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + | 0 | 0 + | 1 | 1 + | 2 | 2 + | 3 | 3 + | 4 | 0 + | 5 | 1 + | 6 | 2 + | 7 | 3 + | 8 | 0 + | 9 | 1 +(10 rows) + +SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 0 | 0 | 0 + 1 | 1 | 1 + 2 | 2 | 2 + 3 | 3 | 3 + 4 | 4 | 0 + 5 | 5 | 1 + 6 | 6 | 2 + 7 | 7 | 3 + 8 | 8 | 0 + 9 | 9 | 1 +(10 rows) + +-- Changed from original window.sql (lines 218- 252): ORDER BY unique1 added. +-- See for more information: https://github.com/yugabyte/yugabyte-db/issues/4832 +SELECT first_value(unique1) over (ORDER BY unique1 rows between current row and 2 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + first_value | unique1 | four +-------------+---------+------ + 1 | 0 | 0 + 2 | 1 | 1 + 3 | 2 | 2 + 4 | 3 | 3 + 5 | 4 | 0 + 6 | 5 | 1 + 7 | 6 | 2 + 8 | 7 | 3 + 9 | 8 | 0 + | 9 | 1 +(10 rows) + +SELECT first_value(unique1) over (order by unique1 rows between current row and 2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + first_value | unique1 | four +-------------+---------+------ + 1 | 0 | 0 + 2 | 1 | 1 + 3 | 2 | 2 + 4 | 3 | 3 + 5 | 4 | 0 + 6 | 5 | 1 + 7 | 6 | 2 + 8 | 7 | 3 + 9 | 8 | 0 + | 9 | 1 +(10 rows) + +SELECT first_value(unique1) over (ORDER BY unique1 rows between current row and 2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + first_value | unique1 | four +-------------+---------+------ + 0 | 0 | 0 + 1 | 1 | 1 + 2 | 2 | 2 + 3 | 3 | 3 + 4 | 4 | 0 + 5 | 5 | 1 + 6 | 6 | 2 + 7 | 7 | 3 + 8 | 8 | 0 + 9 | 9 | 1 +(10 rows) + +SELECT last_value(unique1) over (order by unique1 rows between current row and 2 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + last_value | unique1 | four +------------+---------+------ + 2 | 0 | 0 + 3 | 1 | 1 + 4 | 2 | 2 + 5 | 3 | 3 + 6 | 4 | 0 + 7 | 5 | 1 + 8 | 6 | 2 + 9 | 7 | 3 + 9 | 8 | 0 + | 9 | 1 +(10 rows) + +SELECT last_value(unique1) over (order by unique1 rows between current row and 2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + last_value | unique1 | four +------------+---------+------ + 2 | 0 | 0 + 3 | 1 | 1 + 4 | 2 | 2 + 5 | 3 | 3 + 6 | 4 | 0 + 7 | 5 | 1 + 8 | 6 | 2 + 9 | 7 | 3 + 9 | 8 | 0 + | 9 | 1 +(10 rows) + +SELECT last_value(unique1) over (order by unique1 rows between current row and 2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + last_value | unique1 | four +------------+---------+------ + 2 | 0 | 0 + 3 | 1 | 1 + 4 | 2 | 2 + 5 | 3 | 3 + 6 | 4 | 0 + 7 | 5 | 1 + 8 | 6 | 2 + 9 | 7 | 3 + 9 | 8 | 0 + 9 | 9 | 1 +(10 rows) + +SELECT sum(unique1) over (order by unique1 rows between 2 preceding and 1 preceding), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 0 | 1 | 1 + 1 | 2 | 2 + 3 | 3 | 3 + 5 | 4 | 0 + 7 | 5 | 1 + 9 | 6 | 2 + 11 | 7 | 3 + 13 | 8 | 0 + 15 | 9 | 1 + | 0 | 0 +(10 rows) + +SELECT sum(unique1) over (order by unique1 rows between 1 following and 3 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 6 | 0 | 0 + 9 | 1 | 1 + 9 | 8 | 0 + 12 | 2 | 2 + 15 | 3 | 3 + 17 | 7 | 3 + 18 | 4 | 0 + 21 | 5 | 1 + 24 | 6 | 2 + | 9 | 1 +(10 rows) + +SELECT sum(unique1) over (order by unique1 rows between unbounded preceding and 1 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 1 | 0 | 0 + 3 | 1 | 1 + 6 | 2 | 2 + 10 | 3 | 3 + 15 | 4 | 0 + 21 | 5 | 1 + 28 | 6 | 2 + 36 | 7 | 3 + 45 | 8 | 0 + 45 | 9 | 1 +(10 rows) + +SELECT sum(unique1) over (w range between current row and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four) ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 10 | 3 | 3 + 10 | 7 | 3 + 18 | 2 | 2 + 18 | 6 | 2 + 33 | 1 | 1 + 33 | 5 | 1 + 33 | 9 | 1 + 45 | 0 | 0 + 45 | 4 | 0 + 45 | 8 | 0 +(10 rows) + +SELECT sum(unique1) over (w range between unbounded preceding and current row exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four) ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 4 | 8 | 0 + 8 | 4 | 0 + 12 | 0 | 0 + 18 | 9 | 1 + 22 | 5 | 1 + 26 | 1 | 1 + 29 | 6 | 2 + 33 | 2 | 2 + 38 | 7 | 3 + 42 | 3 | 3 +(10 rows) + +SELECT sum(unique1) over (w range between unbounded preceding and current row exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four) ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 12 | 1 | 1 + 12 | 5 | 1 + 12 | 9 | 1 + 27 | 2 | 2 + 27 | 6 | 2 + 35 | 3 | 3 + 35 | 7 | 3 + | 0 | 0 + | 4 | 0 + | 8 | 0 +(10 rows) + +SELECT sum(unique1) over (w range between unbounded preceding and current row exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four) ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 0 | 0 | 0 + 4 | 4 | 0 + 8 | 8 | 0 + 13 | 1 | 1 + 17 | 5 | 1 + 21 | 9 | 1 + 29 | 2 | 2 + 33 | 6 | 2 + 38 | 3 | 3 + 42 | 7 | 3 +(10 rows) + +SELECT first_value(unique1) over w, + nth_value(unique1, 2) over w AS nth_2, + last_value(unique1) over w, unique1, four +FROM tenk1 WHERE unique1 < 10 +WINDOW w AS (order by unique1 range between current row and unbounded following) ORDER by 1,2,3,4,5; + first_value | nth_2 | last_value | unique1 | four +-------------+-------+------------+---------+------ + 0 | 1 | 9 | 0 | 0 + 1 | 2 | 9 | 1 | 1 + 2 | 3 | 9 | 2 | 2 + 3 | 4 | 9 | 3 | 3 + 4 | 5 | 9 | 4 | 0 + 5 | 6 | 9 | 5 | 1 + 6 | 7 | 9 | 6 | 2 + 7 | 8 | 9 | 7 | 3 + 8 | 9 | 9 | 8 | 0 + 9 | | 9 | 9 | 1 +(10 rows) + +SELECT sum(unique1) over + (order by unique1 + rows (SELECT unique1 FROM tenk1 ORDER BY unique1 LIMIT 1) + 1 PRECEDING), + unique1 +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2; + sum | unique1 +-----+--------- + 0 | 0 + 1 | 1 + 3 | 2 + 5 | 3 + 7 | 4 + 9 | 5 + 11 | 6 + 13 | 7 + 15 | 8 + 17 | 9 +(10 rows) + +CREATE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following) as sum_rows + FROM generate_series(1, 10) i; +SELECT * FROM v_window; + i | sum_rows +----+---------- + 1 | 3 + 2 | 6 + 3 | 9 + 4 | 12 + 5 | 15 + 6 | 18 + 7 | 21 + 8 | 24 + 9 | 27 + 10 | 19 +(10 rows) + +SELECT pg_get_viewdef('v_window'); + pg_get_viewdef +--------------------------------------------------------------------------------------- + SELECT i.i, + + sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+ + FROM generate_series(1, 10) i(i); +(1 row) + +--TODO: remove the following drop view's after closing issue #4888 +drop view v_window; +CREATE OR REPLACE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following + exclude current row) as sum_rows FROM generate_series(1, 10) i; +SELECT * FROM v_window; + i | sum_rows +----+---------- + 1 | 2 + 2 | 4 + 3 | 6 + 4 | 8 + 5 | 10 + 6 | 12 + 7 | 14 + 8 | 16 + 9 | 18 + 10 | 9 +(10 rows) + +SELECT pg_get_viewdef('v_window'); + pg_get_viewdef +----------------------------------------------------------------------------------------------------------- + SELECT i.i, + + sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) AS sum_rows+ + FROM generate_series(1, 10) i(i); +(1 row) + +drop view v_window; +CREATE OR REPLACE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following + exclude group) as sum_rows FROM generate_series(1, 10) i; +SELECT * FROM v_window; + i | sum_rows +----+---------- + 1 | 2 + 2 | 4 + 3 | 6 + 4 | 8 + 5 | 10 + 6 | 12 + 7 | 14 + 8 | 16 + 9 | 18 + 10 | 9 +(10 rows) + +SELECT pg_get_viewdef('v_window'); + pg_get_viewdef +----------------------------------------------------------------------------------------------------- + SELECT i.i, + + sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+ + FROM generate_series(1, 10) i(i); +(1 row) + +drop view v_window; +CREATE OR REPLACE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following + exclude ties) as sum_rows FROM generate_series(1, 10) i; +SELECT * FROM v_window; + i | sum_rows +----+---------- + 1 | 3 + 2 | 6 + 3 | 9 + 4 | 12 + 5 | 15 + 6 | 18 + 7 | 21 + 8 | 24 + 9 | 27 + 10 | 19 +(10 rows) + +SELECT pg_get_viewdef('v_window'); + pg_get_viewdef +---------------------------------------------------------------------------------------------------- + SELECT i.i, + + sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) AS sum_rows+ + FROM generate_series(1, 10) i(i); +(1 row) + +drop view v_window; +CREATE OR REPLACE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following + exclude no others) as sum_rows FROM generate_series(1, 10) i; +SELECT * FROM v_window; + i | sum_rows +----+---------- + 1 | 3 + 2 | 6 + 3 | 9 + 4 | 12 + 5 | 15 + 6 | 18 + 7 | 21 + 8 | 24 + 9 | 27 + 10 | 19 +(10 rows) + +SELECT pg_get_viewdef('v_window'); + pg_get_viewdef +--------------------------------------------------------------------------------------- + SELECT i.i, + + sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+ + FROM generate_series(1, 10) i(i); +(1 row) + +drop view v_window; +CREATE OR REPLACE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i groups between 1 preceding and 1 following) as sum_rows FROM generate_series(1, 10) i; +SELECT * FROM v_window; + i | sum_rows +----+---------- + 1 | 3 + 2 | 6 + 3 | 9 + 4 | 12 + 5 | 15 + 6 | 18 + 7 | 21 + 8 | 24 + 9 | 27 + 10 | 19 +(10 rows) + +SELECT pg_get_viewdef('v_window'); + pg_get_viewdef +----------------------------------------------------------------------------------------- + SELECT i.i, + + sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows+ + FROM generate_series(1, 10) i(i); +(1 row) + +DROP VIEW v_window; +CREATE TEMP VIEW v_window AS + SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i + FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i; +SELECT pg_get_viewdef('v_window'); + pg_get_viewdef +--------------------------------------------------------------------------------------------------------------------------- + SELECT i.i, + + min(i.i) OVER (ORDER BY i.i RANGE BETWEEN '@ 1 day'::interval PRECEDING AND '@ 10 days'::interval FOLLOWING) AS min_i+ + FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i); +(1 row) + +-- RANGE offset PRECEDING/FOLLOWING tests +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 12 | 1 | 1 + 12 | 5 | 1 + 12 | 9 | 1 + 23 | 3 | 3 + 23 | 7 | 3 + 27 | 2 | 2 + 27 | 6 | 2 + | 0 | 0 + | 4 | 0 + | 8 | 0 +(10 rows) + +SELECT sum(unique1) over (order by four desc range between 2::int8 preceding and 1::int2 preceding), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 10 | 2 | 2 + 10 | 6 | 2 + 18 | 1 | 1 + 18 | 5 | 1 + 18 | 9 | 1 + 23 | 0 | 0 + 23 | 4 | 0 + 23 | 8 | 0 + | 3 | 3 + | 7 | 3 +(10 rows) + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude no others), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 12 | 1 | 1 + 12 | 5 | 1 + 12 | 9 | 1 + 23 | 3 | 3 + 23 | 7 | 3 + 27 | 2 | 2 + 27 | 6 | 2 + | 0 | 0 + | 4 | 0 + | 8 | 0 +(10 rows) + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 12 | 1 | 1 + 12 | 5 | 1 + 12 | 9 | 1 + 23 | 3 | 3 + 23 | 7 | 3 + 27 | 2 | 2 + 27 | 6 | 2 + | 0 | 0 + | 4 | 0 + | 8 | 0 +(10 rows) + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 12 | 1 | 1 + 12 | 5 | 1 + 12 | 9 | 1 + 23 | 3 | 3 + 23 | 7 | 3 + 27 | 2 | 2 + 27 | 6 | 2 + | 0 | 0 + | 4 | 0 + | 8 | 0 +(10 rows) + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 12 | 1 | 1 + 12 | 5 | 1 + 12 | 9 | 1 + 23 | 3 | 3 + 23 | 7 | 3 + 27 | 2 | 2 + 27 | 6 | 2 + | 0 | 0 + | 4 | 0 + | 8 | 0 +(10 rows) + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::int2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 26 | 3 | 3 + 30 | 7 | 3 + 31 | 1 | 1 + 33 | 0 | 0 + 35 | 5 | 1 + 37 | 4 | 0 + 39 | 2 | 2 + 39 | 9 | 1 + 41 | 8 | 0 + 43 | 6 | 2 +(10 rows) + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::int2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 23 | 3 | 3 + 23 | 7 | 3 + 30 | 1 | 1 + 30 | 5 | 1 + 30 | 9 | 1 + 33 | 0 | 0 + 33 | 4 | 0 + 33 | 8 | 0 + 37 | 2 | 2 + 37 | 6 | 2 +(10 rows) + +SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 4 | 0 | 0 + 6 | 1 | 1 + 8 | 2 | 2 + 8 | 6 | 2 + 10 | 3 | 3 + 10 | 7 | 3 + 12 | 4 | 0 + 12 | 8 | 0 + 14 | 9 | 1 + 15 | 5 | 1 +(10 rows) + +SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following + exclude current row),unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 2 | 6 | 2 + 3 | 7 | 3 + 4 | 0 | 0 + 4 | 8 | 0 + 5 | 1 | 1 + 5 | 9 | 1 + 6 | 2 | 2 + 7 | 3 | 3 + 8 | 4 | 0 + 10 | 5 | 1 +(10 rows) + +select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following), + salary, enroll_date from empsalary ORDER by 1,2,3; + sum | salary | enroll_date +-------+--------+------------- + 32200 | 4200 | 01-01-2008 + 32200 | 4500 | 01-01-2008 + 34900 | 5000 | 10-01-2006 + 34900 | 6000 | 10-01-2006 + 36100 | 3500 | 12-10-2007 + 38400 | 3900 | 12-23-2006 + 47100 | 4800 | 08-01-2007 + 47100 | 4800 | 08-08-2007 + 47100 | 5200 | 08-01-2007 + 47100 | 5200 | 08-15-2007 +(10 rows) + +select sum(salary) over (order by enroll_date desc range between '1 year'::interval preceding and '1 year'::interval following), + salary, enroll_date from empsalary ORDER by 1,2,3; + sum | salary | enroll_date +-------+--------+------------- + 32200 | 4200 | 01-01-2008 + 32200 | 4500 | 01-01-2008 + 34900 | 5000 | 10-01-2006 + 34900 | 6000 | 10-01-2006 + 36100 | 3500 | 12-10-2007 + 38400 | 3900 | 12-23-2006 + 47100 | 4800 | 08-01-2007 + 47100 | 4800 | 08-08-2007 + 47100 | 5200 | 08-01-2007 + 47100 | 5200 | 08-15-2007 +(10 rows) + +select sum(salary) over (order by enroll_date desc range between '1 year'::interval following and '1 year'::interval following), + salary, enroll_date from empsalary ORDER by 1,2,3; + sum | salary | enroll_date +-----+--------+------------- + | 3500 | 12-10-2007 + | 3900 | 12-23-2006 + | 4200 | 01-01-2008 + | 4500 | 01-01-2008 + | 4800 | 08-01-2007 + | 4800 | 08-08-2007 + | 5000 | 10-01-2006 + | 5200 | 08-01-2007 + | 5200 | 08-15-2007 + | 6000 | 10-01-2006 +(10 rows) + +select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following + exclude current row), salary, enroll_date from empsalary ORDER by 1,2,3; + sum | salary | enroll_date +-------+--------+------------- + 27700 | 4500 | 01-01-2008 + 28000 | 4200 | 01-01-2008 + 28900 | 6000 | 10-01-2006 + 29900 | 5000 | 10-01-2006 + 32600 | 3500 | 12-10-2007 + 34500 | 3900 | 12-23-2006 + 41900 | 5200 | 08-01-2007 + 41900 | 5200 | 08-15-2007 + 42300 | 4800 | 08-01-2007 + 42300 | 4800 | 08-08-2007 +(10 rows) + +select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following + exclude group), salary, enroll_date from empsalary ORDER by 1,2,3; + sum | salary | enroll_date +-------+--------+------------- + 23500 | 4200 | 01-01-2008 + 23500 | 4500 | 01-01-2008 + 23900 | 5000 | 10-01-2006 + 23900 | 6000 | 10-01-2006 + 32600 | 3500 | 12-10-2007 + 34500 | 3900 | 12-23-2006 + 37100 | 4800 | 08-01-2007 + 37100 | 5200 | 08-01-2007 + 41900 | 5200 | 08-15-2007 + 42300 | 4800 | 08-08-2007 +(10 rows) + +select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following + exclude ties), salary, enroll_date from empsalary ORDER by 1,2,3; + sum | salary | enroll_date +-------+--------+------------- + 27700 | 4200 | 01-01-2008 + 28000 | 4500 | 01-01-2008 + 28900 | 5000 | 10-01-2006 + 29900 | 6000 | 10-01-2006 + 36100 | 3500 | 12-10-2007 + 38400 | 3900 | 12-23-2006 + 41900 | 4800 | 08-01-2007 + 42300 | 5200 | 08-01-2007 + 47100 | 4800 | 08-08-2007 + 47100 | 5200 | 08-15-2007 +(10 rows) + +select first_value(salary) over(order by salary range between 1000 preceding and 1000 following), + lead(salary) over(order by salary range between 1000 preceding and 1000 following), + nth_value(salary, 1) over(order by salary range between 1000 preceding and 1000 following), + salary from empsalary ORDER by 1,2,3,4; + first_value | lead | nth_value | salary +-------------+------+-----------+-------- + 3500 | 3900 | 3500 | 3500 + 3500 | 4200 | 3500 | 3900 + 3500 | 4500 | 3500 | 4200 + 3500 | 4800 | 3500 | 4500 + 3900 | 4800 | 3900 | 4800 + 3900 | 5000 | 3900 | 4800 + 4200 | 5200 | 4200 | 5000 + 4200 | 5200 | 4200 | 5200 + 4200 | 6000 | 4200 | 5200 + 5000 | | 5000 | 6000 +(10 rows) + +select last_value(salary) over(order by salary range between 1000 preceding and 1000 following), + lag(salary) over(order by salary range between 1000 preceding and 1000 following), + salary from empsalary ORDER by 1,2,3; + last_value | lag | salary +------------+------+-------- + 4500 | | 3500 + 4800 | 3500 | 3900 + 5200 | 3900 | 4200 + 5200 | 4200 | 4500 + 5200 | 4500 | 4800 + 5200 | 4800 | 4800 + 6000 | 4800 | 5000 + 6000 | 5000 | 5200 + 6000 | 5200 | 5200 + 6000 | 5200 | 6000 +(10 rows) + +select first_value(salary) over(order by salary range between 1000 following and 3000 following + exclude current row), + lead(salary) over(order by salary range between 1000 following and 3000 following exclude ties), + nth_value(salary, 1) over(order by salary range between 1000 following and 3000 following + exclude ties), + salary from empsalary ORDER by 1,2,3,4; + first_value | lead | nth_value | salary +-------------+------+-----------+-------- + 4500 | 3900 | 4500 | 3500 + 5000 | 4200 | 5000 | 3900 + 5200 | 4500 | 5200 | 4200 + 6000 | 4800 | 6000 | 4500 + 6000 | 4800 | 6000 | 4800 + 6000 | 5000 | 6000 | 4800 + 6000 | 5200 | 6000 | 5000 + | 5200 | | 5200 + | 6000 | | 5200 + | | | 6000 +(10 rows) + +select last_value(salary) over(order by salary range between 1000 following and 3000 following + exclude group), + lag(salary) over(order by salary range between 1000 following and 3000 following exclude group), + salary from empsalary ORDER by 1,2,3; + last_value | lag | salary +------------+------+-------- + 6000 | 3500 | 3900 + 6000 | 3900 | 4200 + 6000 | 4200 | 4500 + 6000 | 4500 | 4800 + 6000 | 4800 | 4800 + 6000 | 4800 | 5000 + 6000 | | 3500 + | 5000 | 5200 + | 5200 | 5200 + | 5200 | 6000 +(10 rows) + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude ties), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + first_value | last_value | salary | enroll_date +-------------+------------+--------+------------- + 5000 | 3500 | 3900 | 12-23-2006 + 5000 | 4200 | 3500 | 12-10-2007 + 5000 | 4200 | 4200 | 01-01-2008 + 5000 | 4200 | 4500 | 01-01-2008 + 5000 | 4200 | 4800 | 08-01-2007 + 5000 | 4200 | 4800 | 08-08-2007 + 5000 | 4200 | 5200 | 08-01-2007 + 5000 | 4200 | 5200 | 08-15-2007 + 5000 | 5200 | 5000 | 10-01-2006 + 6000 | 5200 | 6000 | 10-01-2006 +(10 rows) + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude ties), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude ties), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + first_value | last_value | salary | enroll_date +-------------+------------+--------+------------- + 5000 | 3500 | 3900 | 12-23-2006 + 5000 | 4200 | 3500 | 12-10-2007 + 5000 | 4200 | 4200 | 01-01-2008 + 5000 | 4200 | 4800 | 08-01-2007 + 5000 | 4200 | 4800 | 08-08-2007 + 5000 | 4200 | 5200 | 08-01-2007 + 5000 | 4200 | 5200 | 08-15-2007 + 5000 | 4500 | 4500 | 01-01-2008 + 5000 | 5200 | 5000 | 10-01-2006 + 6000 | 5200 | 6000 | 10-01-2006 +(10 rows) + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude group), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude group), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + first_value | last_value | salary | enroll_date +-------------+------------+--------+------------- + 3900 | 5200 | 5000 | 10-01-2006 + 3900 | 5200 | 6000 | 10-01-2006 + 5000 | 3500 | 3900 | 12-23-2006 + 5000 | 3500 | 4200 | 01-01-2008 + 5000 | 3500 | 4500 | 01-01-2008 + 5000 | 4200 | 3500 | 12-10-2007 + 5000 | 4200 | 4800 | 08-01-2007 + 5000 | 4200 | 4800 | 08-08-2007 + 5000 | 4200 | 5200 | 08-01-2007 + 5000 | 4200 | 5200 | 08-15-2007 +(10 rows) + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude current row), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude current row), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + first_value | last_value | salary | enroll_date +-------------+------------+--------+------------- + 5000 | 3500 | 3900 | 12-23-2006 + 5000 | 4200 | 3500 | 12-10-2007 + 5000 | 4200 | 4500 | 01-01-2008 + 5000 | 4200 | 4800 | 08-01-2007 + 5000 | 4200 | 4800 | 08-08-2007 + 5000 | 4200 | 5200 | 08-01-2007 + 5000 | 4200 | 5200 | 08-15-2007 + 5000 | 4500 | 4200 | 01-01-2008 + 5000 | 5200 | 6000 | 10-01-2006 + 6000 | 5200 | 5000 | 10-01-2006 +(10 rows) + +-- RANGE offset PRECEDING/FOLLOWING with null values +select x, y, + first_value(y) over w, + last_value(y) over w +from + (select x, x as y from generate_series(1,5) as x + union all select null, 42 + union all select null, 43) ss +window w as + (order by x asc nulls first range between 2 preceding and 2 following) +ORDER by 1,2,3,4; + x | y | first_value | last_value +---+----+-------------+------------ + 1 | 1 | 1 | 3 + 2 | 2 | 1 | 4 + 3 | 3 | 1 | 5 + 4 | 4 | 2 | 5 + 5 | 5 | 3 | 5 + | 42 | 42 | 43 + | 43 | 42 | 43 +(7 rows) + +select x, y, + first_value(y) over w, + last_value(y) over w +from + (select x, x as y from generate_series(1,5) as x + union all select null, 42 + union all select null, 43) ss +window w as + (order by x asc nulls last range between 2 preceding and 2 following) +ORDER by 1,2,3,4; + x | y | first_value | last_value +---+----+-------------+------------ + 1 | 1 | 1 | 3 + 2 | 2 | 1 | 4 + 3 | 3 | 1 | 5 + 4 | 4 | 2 | 5 + 5 | 5 | 3 | 5 + | 42 | 42 | 43 + | 43 | 42 | 43 +(7 rows) + +select x, y, + first_value(y) over w, + last_value(y) over w +from + (select x, x as y from generate_series(1,5) as x + union all select null, 42 + union all select null, 43) ss +window w as + (order by x desc nulls first range between 2 preceding and 2 following) +ORDER by 1,2,3,4; + x | y | first_value | last_value +---+----+-------------+------------ + 1 | 1 | 3 | 1 + 2 | 2 | 4 | 1 + 3 | 3 | 5 | 1 + 4 | 4 | 5 | 2 + 5 | 5 | 5 | 3 + | 42 | 43 | 42 + | 43 | 43 | 42 +(7 rows) + +select x, y, + first_value(y) over w, + last_value(y) over w +from + (select x, x as y from generate_series(1,5) as x + union all select null, 42 + union all select null, 43) ss +window w as + (order by x desc nulls last range between 2 preceding and 2 following) +ORDER by 1,2,3,4; + x | y | first_value | last_value +---+----+-------------+------------ + 1 | 1 | 3 | 1 + 2 | 2 | 4 | 1 + 3 | 3 | 5 | 1 + 4 | 4 | 5 | 2 + 5 | 5 | 5 | 3 + | 42 | 42 | 43 + | 43 | 42 | 43 +(7 rows) + +-- Check overflow behavior for various integer sizes +select x, last_value(x) over (order by x::smallint range between current row and 2147450884 following) +from generate_series(32764, 32766) x ORDER by 1,2; + x | last_value +-------+------------ + 32764 | 32766 + 32765 | 32766 + 32766 | 32766 +(3 rows) + +select x, last_value(x) over (order by x::smallint desc range between current row and 2147450885 following) +from generate_series(-32766, -32764) x ORDER by 1,2; + x | last_value +--------+------------ + -32766 | -32766 + -32765 | -32766 + -32764 | -32766 +(3 rows) + +select x, last_value(x) over (order by x range between current row and 4 following) +from generate_series(2147483644, 2147483646) x ORDER by 1,2; + x | last_value +------------+------------ + 2147483644 | 2147483646 + 2147483645 | 2147483646 + 2147483646 | 2147483646 +(3 rows) + +select x, last_value(x) over (order by x desc range between current row and 5 following) +from generate_series(-2147483646, -2147483644) x ORDER by 1,2; + x | last_value +-------------+------------- + -2147483646 | -2147483646 + -2147483645 | -2147483646 + -2147483644 | -2147483646 +(3 rows) + +select x, last_value(x) over (order by x range between current row and 4 following) +from generate_series(9223372036854775804, 9223372036854775806) x ORDER by 1,2; + x | last_value +---------------------+--------------------- + 9223372036854775804 | 9223372036854775806 + 9223372036854775805 | 9223372036854775806 + 9223372036854775806 | 9223372036854775806 +(3 rows) + +select x, last_value(x) over (order by x desc range between current row and 5 following) +from generate_series(-9223372036854775806, -9223372036854775804) x ORDER by 1,2; + x | last_value +----------------------+---------------------- + -9223372036854775806 | -9223372036854775806 + -9223372036854775805 | -9223372036854775806 + -9223372036854775804 | -9223372036854775806 +(3 rows) + +-- Test in_range for other numeric datatypes +create temp table numerics( + id int, + f_float4 float4, + f_float8 float8, + f_numeric numeric +); +insert into numerics values +(0, '-infinity', '-infinity', '-1000'), -- numeric type lacks infinities +(1, -3, -3, -3), +(2, -1, -1, -1), +(3, 0, 0, 0), +(4, 1.1, 1.1, 1.1), +(5, 1.12, 1.12, 1.12), +(6, 2, 2, 2), +(7, 100, 100, 100), +(8, 'infinity', 'infinity', '1000'), +(9, 'NaN', 'NaN', 'NaN'); +select id, f_float4, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float4 range between + 1 preceding and 1 following) +ORDER by 1,2,3,4; + id | f_float4 | first_value | last_value +----+-----------+-------------+------------ + 0 | -Infinity | 0 | 0 + 1 | -3 | 1 | 1 + 2 | -1 | 2 | 3 + 3 | 0 | 2 | 3 + 4 | 1.1 | 4 | 6 + 5 | 1.12 | 4 | 6 + 6 | 2 | 4 | 6 + 7 | 100 | 7 | 7 + 8 | Infinity | 8 | 8 + 9 | NaN | 9 | 9 +(10 rows) + +select id, f_float4, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float4 range between + 1 preceding and 1.1::float4 following) +ORDER by 1,2,3,4; + id | f_float4 | first_value | last_value +----+-----------+-------------+------------ + 0 | -Infinity | 0 | 0 + 1 | -3 | 1 | 1 + 2 | -1 | 2 | 3 + 3 | 0 | 2 | 4 + 4 | 1.1 | 4 | 6 + 5 | 1.12 | 4 | 6 + 6 | 2 | 4 | 6 + 7 | 100 | 7 | 7 + 8 | Infinity | 8 | 8 + 9 | NaN | 9 | 9 +(10 rows) + +select id, f_float4, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float4 range between + 'inf' preceding and 'inf' following) +ORDER by 1,2,3,4; + id | f_float4 | first_value | last_value +----+-----------+-------------+------------ + 0 | -Infinity | 0 | 8 + 1 | -3 | 0 | 8 + 2 | -1 | 0 | 8 + 3 | 0 | 0 | 8 + 4 | 1.1 | 0 | 8 + 5 | 1.12 | 0 | 8 + 6 | 2 | 0 | 8 + 7 | 100 | 0 | 8 + 8 | Infinity | 0 | 8 + 9 | NaN | 9 | 9 +(10 rows) + +select id, f_float4, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float4 range between + 1.1 preceding and 'NaN' following); -- error, NaN disallowed +ERROR: invalid preceding or following size in window function +select id, f_float8, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float8 range between + 1 preceding and 1 following) +ORDER by 1,2,3,4; + id | f_float8 | first_value | last_value +----+-----------+-------------+------------ + 0 | -Infinity | 0 | 0 + 1 | -3 | 1 | 1 + 2 | -1 | 2 | 3 + 3 | 0 | 2 | 3 + 4 | 1.1 | 4 | 6 + 5 | 1.12 | 4 | 6 + 6 | 2 | 4 | 6 + 7 | 100 | 7 | 7 + 8 | Infinity | 8 | 8 + 9 | NaN | 9 | 9 +(10 rows) + +select id, f_float8, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float8 range between + 1 preceding and 1.1::float8 following) +ORDER by 1,2,3,4; + id | f_float8 | first_value | last_value +----+-----------+-------------+------------ + 0 | -Infinity | 0 | 0 + 1 | -3 | 1 | 1 + 2 | -1 | 2 | 3 + 3 | 0 | 2 | 4 + 4 | 1.1 | 4 | 6 + 5 | 1.12 | 4 | 6 + 6 | 2 | 4 | 6 + 7 | 100 | 7 | 7 + 8 | Infinity | 8 | 8 + 9 | NaN | 9 | 9 +(10 rows) + +select id, f_float8, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float8 range between + 'inf' preceding and 'inf' following) +ORDER by 1,2,3,4; + id | f_float8 | first_value | last_value +----+-----------+-------------+------------ + 0 | -Infinity | 0 | 8 + 1 | -3 | 0 | 8 + 2 | -1 | 0 | 8 + 3 | 0 | 0 | 8 + 4 | 1.1 | 0 | 8 + 5 | 1.12 | 0 | 8 + 6 | 2 | 0 | 8 + 7 | 100 | 0 | 8 + 8 | Infinity | 0 | 8 + 9 | NaN | 9 | 9 +(10 rows) + +select id, f_float8, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float8 range between + 1.1 preceding and 'NaN' following); -- error, NaN disallowed +ERROR: invalid preceding or following size in window function +select id, f_numeric, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_numeric range between + 1 preceding and 1 following) +ORDER by 1,2,3,4; + id | f_numeric | first_value | last_value +----+-----------+-------------+------------ + 0 | -1000 | 0 | 0 + 1 | -3 | 1 | 1 + 2 | -1 | 2 | 3 + 3 | 0 | 2 | 3 + 4 | 1.1 | 4 | 6 + 5 | 1.12 | 4 | 6 + 6 | 2 | 4 | 6 + 7 | 100 | 7 | 7 + 8 | 1000 | 8 | 8 + 9 | NaN | 9 | 9 +(10 rows) + +select id, f_numeric, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_numeric range between + 1 preceding and 1.1::numeric following) +ORDER by 1,2,3,4; + id | f_numeric | first_value | last_value +----+-----------+-------------+------------ + 0 | -1000 | 0 | 0 + 1 | -3 | 1 | 1 + 2 | -1 | 2 | 3 + 3 | 0 | 2 | 4 + 4 | 1.1 | 4 | 6 + 5 | 1.12 | 4 | 6 + 6 | 2 | 4 | 6 + 7 | 100 | 7 | 7 + 8 | 1000 | 8 | 8 + 9 | NaN | 9 | 9 +(10 rows) + +select id, f_numeric, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_numeric range between + 1 preceding and 1.1::float8 following); -- currently unsupported +ERROR: RANGE with offset PRECEDING/FOLLOWING is not supported for column type numeric and offset type double precision +LINE 4: 1 preceding and 1.1::float8 following); + ^ +HINT: Cast the offset value to an appropriate type. +select id, f_numeric, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_numeric range between + 1.1 preceding and 'NaN' following); -- error, NaN disallowed +ERROR: invalid preceding or following size in window function +-- Test in_range for other datetime datatypes +create temp table datetimes( + id int, + f_time time, + f_timetz timetz, + f_interval interval, + f_timestamptz timestamptz, + f_timestamp timestamp +); +insert into datetimes values +(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'), +(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'), +(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'), +(4, '14:00', '14:00 BST', '4 years', '2002-10-19 10:23:54+01', '2002-10-19 10:23:54'), +(5, '15:00', '15:00 BST', '5 years', '2003-10-19 10:23:54+01', '2003-10-19 10:23:54'), +(6, '15:00', '15:00 BST', '5 years', '2004-10-19 10:23:54+01', '2004-10-19 10:23:54'), +(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'), +(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'), +(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'), +(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'); +select id, f_time, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_time range between + '70 min'::interval preceding and '2 hours'::interval following) +ORDER by 1,2,3,4; + id | f_time | first_value | last_value +----+----------+-------------+------------ + 1 | 11:00:00 | 1 | 3 + 2 | 12:00:00 | 1 | 4 + 3 | 13:00:00 | 2 | 6 + 4 | 14:00:00 | 3 | 6 + 5 | 15:00:00 | 4 | 7 + 6 | 15:00:00 | 4 | 7 + 7 | 17:00:00 | 7 | 9 + 8 | 18:00:00 | 7 | 10 + 9 | 19:00:00 | 8 | 10 + 10 | 20:00:00 | 9 | 10 +(10 rows) + +select id, f_time, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_time desc range between + '70 min' preceding and '2 hours' following) +ORDER by 1,2,3,4; + id | f_time | first_value | last_value +----+----------+-------------+------------ + 1 | 11:00:00 | 2 | 1 + 2 | 12:00:00 | 3 | 1 + 3 | 13:00:00 | 4 | 1 + 4 | 14:00:00 | 6 | 2 + 5 | 15:00:00 | 6 | 3 + 6 | 15:00:00 | 6 | 3 + 7 | 17:00:00 | 8 | 5 + 8 | 18:00:00 | 9 | 7 + 9 | 19:00:00 | 10 | 7 + 10 | 20:00:00 | 10 | 8 +(10 rows) + +select id, f_timetz, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timetz range between + '70 min'::interval preceding and '2 hours'::interval following) +ORDER by 1,2,3,4; + id | f_timetz | first_value | last_value +----+-------------+-------------+------------ + 1 | 11:00:00+01 | 1 | 3 + 2 | 12:00:00+01 | 1 | 4 + 3 | 13:00:00+01 | 2 | 6 + 4 | 14:00:00+01 | 3 | 6 + 5 | 15:00:00+01 | 4 | 7 + 6 | 15:00:00+01 | 4 | 7 + 7 | 17:00:00+01 | 7 | 9 + 8 | 18:00:00+01 | 7 | 10 + 9 | 19:00:00+01 | 8 | 10 + 10 | 20:00:00+01 | 9 | 10 +(10 rows) + +select id, f_timetz, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timetz desc range between + '70 min' preceding and '2 hours' following) +ORDER by 1,2,3,4; + id | f_timetz | first_value | last_value +----+-------------+-------------+------------ + 1 | 11:00:00+01 | 2 | 1 + 2 | 12:00:00+01 | 3 | 1 + 3 | 13:00:00+01 | 4 | 1 + 4 | 14:00:00+01 | 6 | 2 + 5 | 15:00:00+01 | 6 | 3 + 6 | 15:00:00+01 | 6 | 3 + 7 | 17:00:00+01 | 8 | 5 + 8 | 18:00:00+01 | 9 | 7 + 9 | 19:00:00+01 | 10 | 7 + 10 | 20:00:00+01 | 10 | 8 +(10 rows) + +select id, f_interval, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_interval range between + '1 year'::interval preceding and '1 year'::interval following) +ORDER by 1,2,3,4; + id | f_interval | first_value | last_value +----+------------+-------------+------------ + 1 | @ 1 year | 1 | 2 + 2 | @ 2 years | 1 | 3 + 3 | @ 3 years | 2 | 4 + 4 | @ 4 years | 3 | 6 + 5 | @ 5 years | 4 | 6 + 6 | @ 5 years | 4 | 6 + 7 | @ 7 years | 7 | 8 + 8 | @ 8 years | 7 | 9 + 9 | @ 9 years | 8 | 10 + 10 | @ 10 years | 9 | 10 +(10 rows) + +select id, f_interval, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_interval desc range between + '1 year' preceding and '1 year' following) +ORDER by 1,2,3,4; + id | f_interval | first_value | last_value +----+------------+-------------+------------ + 1 | @ 1 year | 2 | 1 + 2 | @ 2 years | 3 | 1 + 3 | @ 3 years | 4 | 2 + 4 | @ 4 years | 6 | 3 + 5 | @ 5 years | 6 | 4 + 6 | @ 5 years | 6 | 4 + 7 | @ 7 years | 8 | 7 + 8 | @ 8 years | 9 | 7 + 9 | @ 9 years | 10 | 8 + 10 | @ 10 years | 10 | 9 +(10 rows) + +select id, f_timestamptz, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timestamptz range between + '1 year'::interval preceding and '1 year'::interval following) +ORDER by 1,2,3,4; + id | f_timestamptz | first_value | last_value +----+------------------------------+-------------+------------ + 1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3 + 2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4 + 3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4 + 4 | Sat Oct 19 02:23:54 2002 PDT | 2 | 5 + 5 | Sun Oct 19 02:23:54 2003 PDT | 4 | 6 + 6 | Tue Oct 19 02:23:54 2004 PDT | 5 | 7 + 7 | Wed Oct 19 02:23:54 2005 PDT | 6 | 8 + 8 | Thu Oct 19 02:23:54 2006 PDT | 7 | 9 + 9 | Fri Oct 19 02:23:54 2007 PDT | 8 | 10 + 10 | Sun Oct 19 02:23:54 2008 PDT | 9 | 10 +(10 rows) + +select id, f_timestamptz, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timestamptz desc range between + '1 year' preceding and '1 year' following) +ORDER by 1,2,3,4; + id | f_timestamptz | first_value | last_value +----+------------------------------+-------------+------------ + 1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1 + 2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1 + 3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1 + 4 | Sat Oct 19 02:23:54 2002 PDT | 5 | 2 + 5 | Sun Oct 19 02:23:54 2003 PDT | 6 | 4 + 6 | Tue Oct 19 02:23:54 2004 PDT | 7 | 5 + 7 | Wed Oct 19 02:23:54 2005 PDT | 8 | 6 + 8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7 + 9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8 + 10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9 +(10 rows) + +select id, f_timestamp, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timestamp range between + '1 year'::interval preceding and '1 year'::interval following) +ORDER by 1,2,3,4; + id | f_timestamp | first_value | last_value +----+--------------------------+-------------+------------ + 1 | Thu Oct 19 10:23:54 2000 | 1 | 3 + 2 | Fri Oct 19 10:23:54 2001 | 1 | 4 + 3 | Fri Oct 19 10:23:54 2001 | 1 | 4 + 4 | Sat Oct 19 10:23:54 2002 | 2 | 5 + 5 | Sun Oct 19 10:23:54 2003 | 4 | 6 + 6 | Tue Oct 19 10:23:54 2004 | 5 | 7 + 7 | Wed Oct 19 10:23:54 2005 | 6 | 8 + 8 | Thu Oct 19 10:23:54 2006 | 7 | 9 + 9 | Fri Oct 19 10:23:54 2007 | 8 | 10 + 10 | Sun Oct 19 10:23:54 2008 | 9 | 10 +(10 rows) + +select id, f_timestamp, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timestamp desc range between + '1 year' preceding and '1 year' following) +ORDER by 1,2,3,4; + id | f_timestamp | first_value | last_value +----+--------------------------+-------------+------------ + 1 | Thu Oct 19 10:23:54 2000 | 3 | 1 + 2 | Fri Oct 19 10:23:54 2001 | 4 | 1 + 3 | Fri Oct 19 10:23:54 2001 | 4 | 1 + 4 | Sat Oct 19 10:23:54 2002 | 5 | 2 + 5 | Sun Oct 19 10:23:54 2003 | 6 | 4 + 6 | Tue Oct 19 10:23:54 2004 | 7 | 5 + 7 | Wed Oct 19 10:23:54 2005 | 8 | 6 + 8 | Thu Oct 19 10:23:54 2006 | 9 | 7 + 9 | Fri Oct 19 10:23:54 2007 | 10 | 8 + 10 | Sun Oct 19 10:23:54 2008 | 10 | 9 +(10 rows) + +-- RANGE offset PRECEDING/FOLLOWING error cases +select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_date from empsalary; +ERROR: RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column +LINE 1: select sum(salary) over (order by enroll_date, salary range ... + ^ +select sum(salary) over (range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_date from empsalary; +ERROR: RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column +LINE 1: select sum(salary) over (range between '1 year'::interval pr... + ^ +select sum(salary) over (order by depname range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_date from empsalary; +ERROR: RANGE with offset PRECEDING/FOLLOWING is not supported for column type text +LINE 1: ... sum(salary) over (order by depname range between '1 year'::... + ^ +select max(enroll_date) over (order by enroll_date range between 1 preceding and 2 following + exclude ties), salary, enroll_date from empsalary; +ERROR: RANGE with offset PRECEDING/FOLLOWING is not supported for column type date and offset type integer +LINE 1: ...ll_date) over (order by enroll_date range between 1 precedin... + ^ +HINT: Cast the offset value to an appropriate type. +select max(enroll_date) over (order by salary range between -1 preceding and 2 following + exclude ties), salary, enroll_date from empsalary; +ERROR: invalid preceding or following size in window function +select max(enroll_date) over (order by salary range between 1 preceding and -2 following + exclude ties), salary, enroll_date from empsalary; +ERROR: invalid preceding or following size in window function +select max(enroll_date) over (order by salary range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_date from empsalary; +ERROR: RANGE with offset PRECEDING/FOLLOWING is not supported for column type integer and offset type interval +LINE 1: ...(enroll_date) over (order by salary range between '1 year'::... + ^ +HINT: Cast the offset value to an appropriate type. +select max(enroll_date) over (order by enroll_date range between '1 year'::interval preceding and '-2 years'::interval following + exclude ties), salary, enroll_date from empsalary; +ERROR: invalid preceding or following size in window function +-- GROUPS tests +SELECT sum(unique1) over (order by four groups between unbounded preceding and current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 12 | 0 | 0 + 12 | 4 | 0 + 12 | 8 | 0 + 27 | 1 | 1 + 27 | 5 | 1 + 27 | 9 | 1 + 35 | 2 | 2 + 35 | 6 | 2 + 45 | 3 | 3 + 45 | 7 | 3 +(10 rows) + +SELECT sum(unique1) over (order by four groups between unbounded preceding and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 45 | 0 | 0 + 45 | 1 | 1 + 45 | 2 | 2 + 45 | 3 | 3 + 45 | 4 | 0 + 45 | 5 | 1 + 45 | 6 | 2 + 45 | 7 | 3 + 45 | 8 | 0 + 45 | 9 | 1 +(10 rows) + +SELECT sum(unique1) over (order by four groups between current row and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 10 | 3 | 3 + 10 | 7 | 3 + 18 | 2 | 2 + 18 | 6 | 2 + 33 | 1 | 1 + 33 | 5 | 1 + 33 | 9 | 1 + 45 | 0 | 0 + 45 | 4 | 0 + 45 | 8 | 0 +(10 rows) + +SELECT sum(unique1) over (order by four groups between 1 preceding and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 18 | 3 | 3 + 18 | 7 | 3 + 33 | 2 | 2 + 33 | 6 | 2 + 45 | 0 | 0 + 45 | 1 | 1 + 45 | 4 | 0 + 45 | 5 | 1 + 45 | 8 | 0 + 45 | 9 | 1 +(10 rows) + +SELECT sum(unique1) over (order by four groups between 1 following and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 10 | 2 | 2 + 10 | 6 | 2 + 18 | 1 | 1 + 18 | 5 | 1 + 18 | 9 | 1 + 33 | 0 | 0 + 33 | 4 | 0 + 33 | 8 | 0 + | 3 | 3 + | 7 | 3 +(10 rows) + +SELECT sum(unique1) over (order by four groups between unbounded preceding and 2 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 35 | 0 | 0 + 35 | 4 | 0 + 35 | 8 | 0 + 45 | 1 | 1 + 45 | 2 | 2 + 45 | 3 | 3 + 45 | 5 | 1 + 45 | 6 | 2 + 45 | 7 | 3 + 45 | 9 | 1 +(10 rows) + +SELECT sum(unique1) over (order by four groups between 2 preceding and 1 preceding), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 12 | 1 | 1 + 12 | 5 | 1 + 12 | 9 | 1 + 23 | 3 | 3 + 23 | 7 | 3 + 27 | 2 | 2 + 27 | 6 | 2 + | 0 | 0 + | 4 | 0 + | 8 | 0 +(10 rows) + +SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 27 | 0 | 0 + 27 | 4 | 0 + 27 | 8 | 0 + 33 | 3 | 3 + 33 | 7 | 3 + 35 | 1 | 1 + 35 | 5 | 1 + 35 | 9 | 1 + 45 | 2 | 2 + 45 | 6 | 2 +(10 rows) + +SELECT sum(unique1) over (order by four groups between 0 preceding and 0 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 8 | 2 | 2 + 8 | 6 | 2 + 10 | 3 | 3 + 10 | 7 | 3 + 12 | 0 | 0 + 12 | 4 | 0 + 12 | 8 | 0 + 15 | 1 | 1 + 15 | 5 | 1 + 15 | 9 | 1 +(10 rows) + +SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following + exclude current row), unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 19 | 8 | 0 + 23 | 4 | 0 + 26 | 7 | 3 + 26 | 9 | 1 + 27 | 0 | 0 + 30 | 3 | 3 + 30 | 5 | 1 + 34 | 1 | 1 + 39 | 6 | 2 + 43 | 2 | 2 +(10 rows) + +SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following + exclude group), unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 15 | 0 | 0 + 15 | 4 | 0 + 15 | 8 | 0 + 20 | 1 | 1 + 20 | 5 | 1 + 20 | 9 | 1 + 23 | 3 | 3 + 23 | 7 | 3 + 37 | 2 | 2 + 37 | 6 | 2 +(10 rows) + +SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following + exclude ties), unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + sum | unique1 | four +-----+---------+------ + 15 | 0 | 0 + 19 | 4 | 0 + 21 | 1 | 1 + 23 | 8 | 0 + 25 | 5 | 1 + 26 | 3 | 3 + 29 | 9 | 1 + 30 | 7 | 3 + 39 | 2 | 2 + 43 | 6 | 2 +(10 rows) + +SELECT sum(unique1) over (partition by ten + order by four groups between 0 preceding and 0 following),unique1, four, ten +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3,4; + sum | unique1 | four | ten +-----+---------+------+----- + 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 + 2 | 2 | 2 | 2 + 3 | 3 | 3 | 3 + 4 | 4 | 0 | 4 + 5 | 5 | 1 | 5 + 6 | 6 | 2 | 6 + 7 | 7 | 3 | 7 + 8 | 8 | 0 | 8 + 9 | 9 | 1 | 9 +(10 rows) + +SELECT sum(unique1) over (partition by ten + order by four groups between 0 preceding and 0 following exclude current row), unique1, four, ten +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3,4; + sum | unique1 | four | ten +-----+---------+------+----- + | 0 | 0 | 0 + | 1 | 1 | 1 + | 2 | 2 | 2 + | 3 | 3 | 3 + | 4 | 0 | 4 + | 5 | 1 | 5 + | 6 | 2 | 6 + | 7 | 3 | 7 + | 8 | 0 | 8 + | 9 | 1 | 9 +(10 rows) + +SELECT sum(unique1) over (partition by ten + order by four groups between 0 preceding and 0 following exclude group), unique1, four, ten +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3,4; + sum | unique1 | four | ten +-----+---------+------+----- + | 0 | 0 | 0 + | 1 | 1 | 1 + | 2 | 2 | 2 + | 3 | 3 | 3 + | 4 | 0 | 4 + | 5 | 1 | 5 + | 6 | 2 | 6 + | 7 | 3 | 7 + | 8 | 0 | 8 + | 9 | 1 | 9 +(10 rows) + +SELECT sum(unique1) over (partition by ten + order by four groups between 0 preceding and 0 following exclude ties), unique1, four, ten +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3,4; + sum | unique1 | four | ten +-----+---------+------+----- + 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 + 2 | 2 | 2 | 2 + 3 | 3 | 3 | 3 + 4 | 4 | 0 | 4 + 5 | 5 | 1 | 5 + 6 | 6 | 2 | 6 + 7 | 7 | 3 | 7 + 8 | 8 | 0 | 8 + 9 | 9 | 1 | 9 +(10 rows) + +select first_value(salary) over(order by enroll_date groups between 1 preceding and 1 following), + lead(salary) over(order by enroll_date groups between 1 preceding and 1 following), + nth_value(salary, 1) over(order by enroll_date groups between 1 preceding and 1 following), + salary, enroll_date from empsalary ORDER by 1,2,3,4,5; + first_value | lead | nth_value | salary | enroll_date +-------------+------+-----------+--------+------------- + 3500 | 4200 | 3500 | 4500 | 01-01-2008 + 3500 | | 3500 | 4200 | 01-01-2008 + 3900 | 4800 | 3900 | 5200 | 08-01-2007 + 3900 | 5200 | 3900 | 4800 | 08-01-2007 + 4800 | 3500 | 4800 | 5200 | 08-15-2007 + 4800 | 5200 | 4800 | 4800 | 08-08-2007 + 5000 | 3900 | 5000 | 6000 | 10-01-2006 + 5000 | 4800 | 5000 | 3900 | 12-23-2006 + 5000 | 6000 | 5000 | 5000 | 10-01-2006 + 5200 | 4500 | 5200 | 3500 | 12-10-2007 +(10 rows) + +select last_value(salary) over(order by enroll_date groups between 1 preceding and 1 following), + lag(salary) over(order by enroll_date groups between 1 preceding and 1 following), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + last_value | lag | salary | enroll_date +------------+------+--------+------------- + 3500 | 4800 | 5200 | 08-15-2007 + 3900 | 5000 | 6000 | 10-01-2006 + 3900 | | 5000 | 10-01-2006 + 4200 | 3500 | 4500 | 01-01-2008 + 4200 | 4500 | 4200 | 01-01-2008 + 4200 | 5200 | 3500 | 12-10-2007 + 4800 | 3900 | 4800 | 08-01-2007 + 4800 | 4800 | 5200 | 08-01-2007 + 5200 | 5200 | 4800 | 08-08-2007 + 5200 | 6000 | 3900 | 12-23-2006 +(10 rows) + +select first_value(salary) over(order by enroll_date groups between 1 following and 3 following + exclude current row), + lead(salary) over(order by enroll_date groups between 1 following and 3 following exclude ties), + nth_value(salary, 1) over(order by enroll_date groups between 1 following and 3 following + exclude ties), + salary, enroll_date from empsalary ORDER by 1,2,3,4,5; + first_value | lead | nth_value | salary | enroll_date +-------------+------+-----------+--------+------------- + 3500 | 3500 | 3500 | 5200 | 08-15-2007 + 3900 | 3900 | 3900 | 6000 | 10-01-2006 + 3900 | 6000 | 3900 | 5000 | 10-01-2006 + 4500 | 4500 | 4500 | 3500 | 12-10-2007 + 4800 | 4800 | 4800 | 3900 | 12-23-2006 + 4800 | 4800 | 4800 | 5200 | 08-01-2007 + 4800 | 5200 | 4800 | 4800 | 08-01-2007 + 5200 | 5200 | 5200 | 4800 | 08-08-2007 + | 4200 | | 4500 | 01-01-2008 + | | | 4200 | 01-01-2008 +(10 rows) + +select last_value(salary) over(order by enroll_date groups between 1 following and 3 following + exclude group), + lag(salary) over(order by enroll_date groups between 1 following and 3 following exclude group), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + last_value | lag | salary | enroll_date +------------+------+--------+------------- + 3500 | 3900 | 4800 | 08-01-2007 + 3500 | 4800 | 5200 | 08-01-2007 + 4200 | 4800 | 5200 | 08-15-2007 + 4200 | 5200 | 3500 | 12-10-2007 + 4200 | 5200 | 4800 | 08-08-2007 + 4800 | 5000 | 6000 | 10-01-2006 + 4800 | | 5000 | 10-01-2006 + 5200 | 6000 | 3900 | 12-23-2006 + | 3500 | 4500 | 01-01-2008 + | 4500 | 4200 | 01-01-2008 +(10 rows) + +-- Show differences in offset interpretation between ROWS, RANGE, and GROUPS +WITH cte (x) AS ( + SELECT * FROM generate_series(1, 35, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following) +ORDER by 1,2; + x | sum +----+----- + 1 | 4 + 3 | 9 + 5 | 15 + 7 | 21 + 9 | 27 + 11 | 33 + 13 | 39 + 15 | 45 + 17 | 51 + 19 | 57 + 21 | 63 + 23 | 69 + 25 | 75 + 27 | 81 + 29 | 87 + 31 | 93 + 33 | 99 + 35 | 68 +(18 rows) + +WITH cte (x) AS ( + SELECT * FROM generate_series(1, 35, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x range between 1 preceding and 1 following) +ORDER by 1,2; + x | sum +----+----- + 1 | 1 + 3 | 3 + 5 | 5 + 7 | 7 + 9 | 9 + 11 | 11 + 13 | 13 + 15 | 15 + 17 | 17 + 19 | 19 + 21 | 21 + 23 | 23 + 25 | 25 + 27 | 27 + 29 | 29 + 31 | 31 + 33 | 33 + 35 | 35 +(18 rows) + +WITH cte (x) AS ( + SELECT * FROM generate_series(1, 35, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following) +ORDER by 1,2; + x | sum +----+----- + 1 | 4 + 3 | 9 + 5 | 15 + 7 | 21 + 9 | 27 + 11 | 33 + 13 | 39 + 15 | 45 + 17 | 51 + 19 | 57 + 21 | 63 + 23 | 69 + 25 | 75 + 27 | 81 + 29 | 87 + 31 | 93 + 33 | 99 + 35 | 68 +(18 rows) + +WITH cte (x) AS ( + select 1 union all select 1 union all select 1 union all + SELECT * FROM generate_series(5, 49, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following) +ORDER by 1,2; + x | sum +----+----- + 1 | 2 + 1 | 3 + 1 | 7 + 5 | 13 + 7 | 21 + 9 | 27 + 11 | 33 + 13 | 39 + 15 | 45 + 17 | 51 + 19 | 57 + 21 | 63 + 23 | 69 + 25 | 75 + 27 | 81 + 29 | 87 + 31 | 93 + 33 | 99 + 35 | 105 + 37 | 111 + 39 | 117 + 41 | 123 + 43 | 129 + 45 | 135 + 47 | 141 + 49 | 96 +(26 rows) + +WITH cte (x) AS ( + select 1 union all select 1 union all select 1 union all + SELECT * FROM generate_series(5, 49, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x range between 1 preceding and 1 following) +ORDER by 1,2; + x | sum +----+----- + 1 | 3 + 1 | 3 + 1 | 3 + 5 | 5 + 7 | 7 + 9 | 9 + 11 | 11 + 13 | 13 + 15 | 15 + 17 | 17 + 19 | 19 + 21 | 21 + 23 | 23 + 25 | 25 + 27 | 27 + 29 | 29 + 31 | 31 + 33 | 33 + 35 | 35 + 37 | 37 + 39 | 39 + 41 | 41 + 43 | 43 + 45 | 45 + 47 | 47 + 49 | 49 +(26 rows) + +WITH cte (x) AS ( + select 1 union all select 1 union all select 1 union all + SELECT * FROM generate_series(5, 49, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following) +ORDER by 1,2; + x | sum +----+----- + 1 | 8 + 1 | 8 + 1 | 8 + 5 | 15 + 7 | 21 + 9 | 27 + 11 | 33 + 13 | 39 + 15 | 45 + 17 | 51 + 19 | 57 + 21 | 63 + 23 | 69 + 25 | 75 + 27 | 81 + 29 | 87 + 31 | 93 + 33 | 99 + 35 | 105 + 37 | 111 + 39 | 117 + 41 | 123 + 43 | 129 + 45 | 135 + 47 | 141 + 49 | 96 +(26 rows) + +-- with UNION +SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0; + count +------- +(0 rows) + +-- check some degenerate cases +create temp table t1 (f1 int, f2 int8); +insert into t1 values (1,1),(1,2),(2,2); +select f1, sum(f1) over (partition by f1 + range between 1 preceding and 1 following) +from t1 where f1 = f2; -- error, must have order by +ERROR: RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column +LINE 1: select f1, sum(f1) over (partition by f1 + ^ +explain (costs off) +select f1, sum(f1) over (partition by f1 order by f2 + range between 1 preceding and 1 following) +from t1 where f1 = f2 +ORDER by 1,2; + QUERY PLAN +--------------------------------------- + Sort + Sort Key: f1, (sum(f1) OVER (?)) + -> WindowAgg + -> Sort + Sort Key: f1 + -> Seq Scan on t1 + Filter: (f1 = f2) +(7 rows) + +select f1, sum(f1) over (partition by f1 order by f2 + range between 1 preceding and 1 following) +from t1 where f1 = f2 +ORDER by 1,2; + f1 | sum +----+----- + 1 | 1 + 2 | 2 +(2 rows) + +select f1, sum(f1) over (partition by f1, f1 order by f2 + range between 2 preceding and 1 preceding) +from t1 where f1 = f2 +ORDER by 1,2; + f1 | sum +----+----- + 1 | + 2 | +(2 rows) + +select f1, sum(f1) over (partition by f1, f2 order by f2 + range between 1 following and 2 following) +from t1 where f1 = f2 +ORDER by 1,2; + f1 | sum +----+----- + 1 | + 2 | +(2 rows) + +select f1, sum(f1) over (partition by f1 + groups between 1 preceding and 1 following) +from t1 where f1 = f2; -- error, must have order by +ERROR: GROUPS mode requires an ORDER BY clause +LINE 1: select f1, sum(f1) over (partition by f1 + ^ +explain (costs off) +select f1, sum(f1) over (partition by f1 order by f2 + groups between 1 preceding and 1 following) +from t1 where f1 = f2 +ORDER by 1,2; + QUERY PLAN +--------------------------------------- + Sort + Sort Key: f1, (sum(f1) OVER (?)) + -> WindowAgg + -> Sort + Sort Key: f1 + -> Seq Scan on t1 + Filter: (f1 = f2) +(7 rows) + +select f1, sum(f1) over (partition by f1 order by f2 + groups between 1 preceding and 1 following) +from t1 where f1 = f2 +ORDER by 1,2; + f1 | sum +----+----- + 1 | 1 + 2 | 2 +(2 rows) + +select f1, sum(f1) over (partition by f1, f1 order by f2 + groups between 2 preceding and 1 preceding) +from t1 where f1 = f2 +ORDER by 1,2; + f1 | sum +----+----- + 1 | + 2 | +(2 rows) + +select f1, sum(f1) over (partition by f1, f2 order by f2 + groups between 1 following and 2 following) +from t1 where f1 = f2 +ORDER by 1,2; + f1 | sum +----+----- + 1 | + 2 | +(2 rows) + +-- ordering by a non-integer constant is allowed +SELECT rank() OVER (ORDER BY length('abc')) ORDER by 1; + rank +------ + 1 +(1 row) + +-- can't order by another window function +SELECT rank() OVER (ORDER BY rank() OVER (ORDER BY random())); +ERROR: window functions are not allowed in window definitions +LINE 1: SELECT rank() OVER (ORDER BY rank() OVER (ORDER BY random())... + ^ +-- some other errors +SELECT * FROM empsalary WHERE row_number() OVER (ORDER BY salary) < 10; +ERROR: window functions are not allowed in WHERE +LINE 1: SELECT * FROM empsalary WHERE row_number() OVER (ORDER BY sa... + ^ +SELECT * FROM empsalary INNER JOIN tenk1 ON row_number() OVER (ORDER BY salary) < 10; +ERROR: window functions are not allowed in JOIN conditions +LINE 1: SELECT * FROM empsalary INNER JOIN tenk1 ON row_number() OVE... + ^ +SELECT rank() OVER (ORDER BY 1), count(*) FROM empsalary GROUP BY 1; +ERROR: window functions are not allowed in GROUP BY +LINE 1: SELECT rank() OVER (ORDER BY 1), count(*) FROM empsalary GRO... + ^ +SELECT * FROM rank() OVER (ORDER BY random()); +ERROR: syntax error at or near "ORDER" +LINE 1: SELECT * FROM rank() OVER (ORDER BY random()); + ^ +DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())) > 10; +ERROR: window functions are not allowed in WHERE +LINE 1: DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())... + ^ +DELETE FROM empsalary RETURNING rank() OVER (ORDER BY random()); +ERROR: window functions are not allowed in RETURNING +LINE 1: DELETE FROM empsalary RETURNING rank() OVER (ORDER BY random... + ^ +SELECT count(*) OVER w FROM tenk1 WINDOW w AS (ORDER BY unique1), w AS (ORDER BY unique1); +ERROR: window "w" is already defined +LINE 1: ...w FROM tenk1 WINDOW w AS (ORDER BY unique1), w AS (ORDER BY ... + ^ +SELECT rank() OVER (PARTITION BY four, ORDER BY ten) FROM tenk1; +ERROR: syntax error at or near "ORDER" +LINE 1: SELECT rank() OVER (PARTITION BY four, ORDER BY ten) FROM te... + ^ +SELECT count() OVER () FROM tenk1; +ERROR: count(*) must be used to call a parameterless aggregate function +LINE 1: SELECT count() OVER () FROM tenk1; + ^ +SELECT generate_series(1, 100) OVER () FROM empsalary; +ERROR: OVER specified, but generate_series is not a window function nor an aggregate function +LINE 1: SELECT generate_series(1, 100) OVER () FROM empsalary; + ^ +SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1; +ERROR: argument of ntile must be greater than zero +SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1; +ERROR: argument of nth_value must be greater than zero +-- filter +SELECT sum(salary), row_number() OVER (ORDER BY depname), sum( + sum(salary) FILTER (WHERE enroll_date > '2007-01-01') +) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum", + depname +FROM empsalary GROUP BY depname ORDER by 1,2,3,4; + sum | row_number | filtered_sum | depname +-------+------------+--------------+----------- + 7400 | 2 | 3500 | personnel + 14600 | 3 | | sales + 25100 | 1 | 22600 | develop +(3 rows) + +-- Test pushdown of quals into a subquery containing window functions +-- pushdown is safe because all PARTITION BY clauses include depname: +EXPLAIN (COSTS OFF) +SELECT * FROM + (SELECT depname, + sum(salary) OVER (PARTITION BY depname) depsalary, + min(salary) OVER (PARTITION BY depname || 'A', depname) depminsalary + FROM empsalary) emp +WHERE depname = 'sales'; + QUERY PLAN +-------------------------------------------------------------------------- + Subquery Scan on emp + -> WindowAgg + -> WindowAgg + -> Sort + Sort Key: (((empsalary.depname)::text || 'A'::text)) + -> Seq Scan on empsalary + Filter: ((depname)::text = 'sales'::text) +(7 rows) + +-- pushdown is unsafe because there's a PARTITION BY clause without depname: +EXPLAIN (COSTS OFF) +SELECT * FROM + (SELECT depname, + sum(salary) OVER (PARTITION BY enroll_date) enroll_salary, + min(salary) OVER (PARTITION BY depname) depminsalary + FROM empsalary) emp +WHERE depname = 'sales'; + QUERY PLAN +------------------------------------------------------- + Subquery Scan on emp + Filter: ((emp.depname)::text = 'sales'::text) + -> WindowAgg + -> Sort + Sort Key: empsalary.enroll_date + -> WindowAgg + -> Sort + Sort Key: empsalary.depname + -> Seq Scan on empsalary +(9 rows) + +-- cleanup +DROP TABLE empsalary; +-- test user-defined window function with named args and default args +CREATE FUNCTION nth_value_def(val anyelement, n integer = 1) RETURNS anyelement + LANGUAGE internal WINDOW IMMUTABLE STRICT AS 'window_nth_value'; +SELECT nth_value_def(n := 2, val := ten) OVER (PARTITION BY four), ten, four + FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s + ORDER by 1,2,3; + nth_value_def | ten | four +---------------+-----+------ + 0 | 0 | 0 + 0 | 0 | 0 + 0 | 4 | 0 + 1 | 1 | 1 + 1 | 1 | 1 + 1 | 7 | 1 + 1 | 9 | 1 + 3 | 1 | 3 + 3 | 3 | 3 + | 0 | 2 +(10 rows) + +SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four + FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s + ORDER by 1,2,3; + nth_value_def | ten | four +---------------+-----+------ + 0 | 0 | 0 + 0 | 0 | 0 + 0 | 0 | 2 + 0 | 4 | 0 + 1 | 1 | 1 + 1 | 1 | 1 + 1 | 1 | 3 + 1 | 3 | 3 + 1 | 7 | 1 + 1 | 9 | 1 +(10 rows) + +-- +-- Test the basic moving-aggregate machinery +-- +-- create aggregates that record the series of transform calls (these are +-- intentionally not true inverses) +CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS +$$ SELECT COALESCE($1, '') || '*' || quote_nullable($2) $$ +LANGUAGE SQL IMMUTABLE; +CREATE FUNCTION logging_msfunc_nonstrict(text, anyelement) RETURNS text AS +$$ SELECT COALESCE($1, '') || '+' || quote_nullable($2) $$ +LANGUAGE SQL IMMUTABLE; +CREATE FUNCTION logging_minvfunc_nonstrict(text, anyelement) RETURNS text AS +$$ SELECT $1 || '-' || quote_nullable($2) $$ +LANGUAGE SQL IMMUTABLE; +CREATE AGGREGATE logging_agg_nonstrict (anyelement) +( + stype = text, + sfunc = logging_sfunc_nonstrict, + mstype = text, + msfunc = logging_msfunc_nonstrict, + minvfunc = logging_minvfunc_nonstrict +); +CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement) +( + stype = text, + sfunc = logging_sfunc_nonstrict, + mstype = text, + msfunc = logging_msfunc_nonstrict, + minvfunc = logging_minvfunc_nonstrict, + initcond = 'I', + minitcond = 'MI' +); +CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS +$$ SELECT $1 || '*' || quote_nullable($2) $$ +LANGUAGE SQL STRICT IMMUTABLE; +CREATE FUNCTION logging_msfunc_strict(text, anyelement) RETURNS text AS +$$ SELECT $1 || '+' || quote_nullable($2) $$ +LANGUAGE SQL STRICT IMMUTABLE; +CREATE FUNCTION logging_minvfunc_strict(text, anyelement) RETURNS text AS +$$ SELECT $1 || '-' || quote_nullable($2) $$ +LANGUAGE SQL STRICT IMMUTABLE; +CREATE AGGREGATE logging_agg_strict (text) +( + stype = text, + sfunc = logging_sfunc_strict, + mstype = text, + msfunc = logging_msfunc_strict, + minvfunc = logging_minvfunc_strict +); +CREATE AGGREGATE logging_agg_strict_initcond (anyelement) +( + stype = text, + sfunc = logging_sfunc_strict, + mstype = text, + msfunc = logging_msfunc_strict, + minvfunc = logging_minvfunc_strict, + initcond = 'I', + minitcond = 'MI' +); +-- test strict and non-strict cases +SELECT + p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row, + logging_agg_nonstrict(v) over wnd as nstrict, + logging_agg_nonstrict_initcond(v) over wnd as nstrict_init, + logging_agg_strict(v::text) over wnd as strict, + logging_agg_strict_initcond(v) over wnd as strict_init +FROM (VALUES + (1, 1, NULL), + (1, 2, 'a'), + (1, 3, 'b'), + (1, 4, NULL), + (1, 5, NULL), + (1, 6, 'c'), + (2, 1, NULL), + (2, 2, 'x'), + (3, 1, 'z') +) AS t(p, i, v) +WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +ORDER BY p, i; + row | nstrict | nstrict_init | strict | strict_init +----------+-----------------------------------------------+-------------------------------------------------+-----------+---------------- + 1,1:NULL | +NULL | MI+NULL | | MI + 1,2:a | +NULL+'a' | MI+NULL+'a' | a | MI+'a' + 1,3:b | +NULL+'a'-NULL+'b' | MI+NULL+'a'-NULL+'b' | a+'b' | MI+'a'+'b' + 1,4:NULL | +NULL+'a'-NULL+'b'-'a'+NULL | MI+NULL+'a'-NULL+'b'-'a'+NULL | a+'b'-'a' | MI+'a'+'b'-'a' + 1,5:NULL | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL | MI+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL | | MI + 1,6:c | +NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | MI+NULL+'a'-NULL+'b'-'a'+NULL-'b'+NULL-NULL+'c' | c | MI+'c' + 2,1:NULL | +NULL | MI+NULL | | MI + 2,2:x | +NULL+'x' | MI+NULL+'x' | x | MI+'x' + 3,1:z | +'z' | MI+'z' | z | MI+'z' +(9 rows) + +-- and again, but with filter +SELECT + p::text || ',' || i::text || ':' || + CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row, + logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt, + logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt, + logging_agg_strict(v::text) filter(where f) over wnd as strict_filt, + logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt +FROM (VALUES + (1, 1, true, NULL), + (1, 2, false, 'a'), + (1, 3, true, 'b'), + (1, 4, false, NULL), + (1, 5, false, NULL), + (1, 6, false, 'c'), + (2, 1, false, NULL), + (2, 2, true, 'x'), + (3, 1, true, 'z') +) AS t(p, i, f, v) +WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +ORDER BY p, i; + row | nstrict_filt | nstrict_init_filt | strict_filt | strict_init_filt +----------+--------------+-------------------+-------------+------------------ + 1,1:NULL | +NULL | MI+NULL | | MI + 1,2:- | +NULL | MI+NULL | | MI + 1,3:b | +'b' | MI+'b' | b | MI+'b' + 1,4:- | +'b' | MI+'b' | b | MI+'b' + 1,5:- | | MI | | MI + 1,6:- | | MI | | MI + 2,1:- | | MI | | MI + 2,2:x | +'x' | MI+'x' | x | MI+'x' + 3,1:z | +'z' | MI+'z' | z | MI+'z' +(9 rows) + +-- test that volatile arguments disable moving-aggregate mode +SELECT + i::text || ':' || COALESCE(v::text, 'NULL') as row, + logging_agg_strict(v::text) + over wnd as inverse, + logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END) + over wnd as noinverse +FROM (VALUES + (1, 'a'), + (2, 'b'), + (3, 'c') +) AS t(i, v) +WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +ORDER BY i; + row | inverse | noinverse +-----+---------------+----------- + 1:a | a | a + 2:b | a+'b' | a*'b' + 3:c | a+'b'-'a'+'c' | b*'c' +(3 rows) + +SELECT + i::text || ':' || COALESCE(v::text, 'NULL') as row, + logging_agg_strict(v::text) filter(where true) + over wnd as inverse, + logging_agg_strict(v::text) filter(where random() >= 0) + over wnd as noinverse +FROM (VALUES + (1, 'a'), + (2, 'b'), + (3, 'c') +) AS t(i, v) +WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +ORDER BY i; + row | inverse | noinverse +-----+---------------+----------- + 1:a | a | a + 2:b | a+'b' | a*'b' + 3:c | a+'b'-'a'+'c' | b*'c' +(3 rows) + +-- test that non-overlapping windows don't use inverse transitions +SELECT + logging_agg_strict(v::text) OVER wnd +FROM (VALUES + (1, 'a'), + (2, 'b'), + (3, 'c') +) AS t(i, v) +WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW) +ORDER BY i; + logging_agg_strict +-------------------- + a + b + c +(3 rows) + +-- test that returning NULL from the inverse transition functions +-- restarts the aggregation from scratch. The second aggregate is supposed +-- to test cases where only some aggregates restart, the third one checks +-- that one aggregate restarting doesn't cause others to restart. +CREATE FUNCTION sum_int_randrestart_minvfunc(int4, int4) RETURNS int4 AS +$$ SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END $$ +LANGUAGE SQL STRICT; +CREATE AGGREGATE sum_int_randomrestart (int4) +( + stype = int4, + sfunc = int4pl, + mstype = int4, + msfunc = int4pl, + minvfunc = sum_int_randrestart_minvfunc +); +WITH +vs AS ( + SELECT i, (random() * 100)::int4 AS v + FROM generate_series(1, 100) AS i +), +sum_following AS ( + SELECT i, SUM(v) OVER + (ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s + FROM vs +) +SELECT DISTINCT + sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1, + -sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2, + 100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3 +FROM vs +JOIN sum_following ON sum_following.i = vs.i +WINDOW fwd AS ( + ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +); + eq1 | eq2 | eq3 +-----+-----+----- + t | t | t +(1 row) + +-- +-- Test various built-in aggregates that have moving-aggregate support +-- +-- test inverse transition functions handle NULLs properly +SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + i | avg +---+-------------------- + 1 | 1.5000000000000000 + 2 | 2.0000000000000000 + 3 | + 4 | +(4 rows) + +SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + i | avg +---+-------------------- + 1 | 1.5000000000000000 + 2 | 2.0000000000000000 + 3 | + 4 | +(4 rows) + +SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + i | avg +---+-------------------- + 1 | 1.5000000000000000 + 2 | 2.0000000000000000 + 3 | + 4 | +(4 rows) + +SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v); + i | avg +---+-------------------- + 1 | 2.0000000000000000 + 2 | 2.5000000000000000 + 3 | + 4 | +(4 rows) + +SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v); + i | avg +---+------------ + 1 | @ 1.5 secs + 2 | @ 2 secs + 3 | + 4 | +(4 rows) + +SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + i | sum +---+----- + 1 | 3 + 2 | 2 + 3 | + 4 | +(4 rows) + +SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + i | sum +---+----- + 1 | 3 + 2 | 2 + 3 | + 4 | +(4 rows) + +SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + i | sum +---+----- + 1 | 3 + 2 | 2 + 3 | + 4 | +(4 rows) + +SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v); + i | sum +---+------- + 1 | $3.30 + 2 | $2.20 + 3 | + 4 | +(4 rows) + +SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v); + i | sum +---+---------- + 1 | @ 3 secs + 2 | @ 2 secs + 3 | + 4 | +(4 rows) + +SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v); + i | sum +---+----- + 1 | 3.3 + 2 | 2.2 + 3 | + 4 | +(4 rows) + +SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n); + sum +------ + 6.01 + 5 + 3 +(3 rows) + +SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + i | count +---+------- + 1 | 2 + 2 | 1 + 3 | 0 + 4 | 0 +(4 rows) + +SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + i | count +---+------- + 1 | 4 + 2 | 3 + 3 | 2 + 4 | 1 +(4 rows) + +SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + var_pop +----------------------- + 21704.000000000000 + 13868.750000000000 + 11266.666666666667 + 4225.0000000000000000 + 0 +(5 rows) + +SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + var_pop +----------------------- + 21704.000000000000 + 13868.750000000000 + 11266.666666666667 + 4225.0000000000000000 + 0 +(5 rows) + +SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + var_pop +----------------------- + 21704.000000000000 + 13868.750000000000 + 11266.666666666667 + 4225.0000000000000000 + 0 +(5 rows) + +SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + var_pop +----------------------- + 21704.000000000000 + 13868.750000000000 + 11266.666666666667 + 4225.0000000000000000 + 0 +(5 rows) + +SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + var_samp +----------------------- + 27130.000000000000 + 18491.666666666667 + 16900.000000000000 + 8450.0000000000000000 + +(5 rows) + +SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + var_samp +----------------------- + 27130.000000000000 + 18491.666666666667 + 16900.000000000000 + 8450.0000000000000000 + +(5 rows) + +SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + var_samp +----------------------- + 27130.000000000000 + 18491.666666666667 + 16900.000000000000 + 8450.0000000000000000 + +(5 rows) + +SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + var_samp +----------------------- + 27130.000000000000 + 18491.666666666667 + 16900.000000000000 + 8450.0000000000000000 + +(5 rows) + +SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + variance +----------------------- + 27130.000000000000 + 18491.666666666667 + 16900.000000000000 + 8450.0000000000000000 + +(5 rows) + +SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + variance +----------------------- + 27130.000000000000 + 18491.666666666667 + 16900.000000000000 + 8450.0000000000000000 + +(5 rows) + +SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + variance +----------------------- + 27130.000000000000 + 18491.666666666667 + 16900.000000000000 + 8450.0000000000000000 + +(5 rows) + +SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + variance +----------------------- + 27130.000000000000 + 18491.666666666667 + 16900.000000000000 + 8450.0000000000000000 + +(5 rows) + +SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + stddev_pop +--------------------- + 147.322774885623 + 147.322774885623 + 117.765657133139 + 106.144555520604 + 65.0000000000000000 + 0 +(6 rows) + +SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + stddev_pop +--------------------- + 147.322774885623 + 147.322774885623 + 117.765657133139 + 106.144555520604 + 65.0000000000000000 + 0 +(6 rows) + +SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + stddev_pop +--------------------- + 147.322774885623 + 147.322774885623 + 117.765657133139 + 106.144555520604 + 65.0000000000000000 + 0 +(6 rows) + +SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + stddev_pop +--------------------- + 147.322774885623 + 147.322774885623 + 117.765657133139 + 106.144555520604 + 65.0000000000000000 + 0 +(6 rows) + +SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + stddev_samp +--------------------- + 164.711869639076 + 164.711869639076 + 135.984067694222 + 130.000000000000 + 91.9238815542511782 + +(6 rows) + +SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + stddev_samp +--------------------- + 164.711869639076 + 164.711869639076 + 135.984067694222 + 130.000000000000 + 91.9238815542511782 + +(6 rows) + +SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + stddev_samp +--------------------- + 164.711869639076 + 164.711869639076 + 135.984067694222 + 130.000000000000 + 91.9238815542511782 + +(6 rows) + +SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + stddev_samp +--------------------- + 164.711869639076 + 164.711869639076 + 135.984067694222 + 130.000000000000 + 91.9238815542511782 + +(6 rows) + +SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + stddev +--------------------- + 164.711869639076 + 164.711869639076 + 135.984067694222 + 130.000000000000 + 91.9238815542511782 + +(6 rows) + +SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + stddev +--------------------- + 164.711869639076 + 164.711869639076 + 135.984067694222 + 130.000000000000 + 91.9238815542511782 + +(6 rows) + +SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + stddev +--------------------- + 164.711869639076 + 164.711869639076 + 135.984067694222 + 130.000000000000 + 91.9238815542511782 + +(6 rows) + +SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + stddev +--------------------- + 164.711869639076 + 164.711869639076 + 135.984067694222 + 130.000000000000 + 91.9238815542511782 + +(6 rows) + +-- test that inverse transition functions work with various frame options +SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + i | sum +---+----- + 1 | 1 + 2 | 2 + 3 | + 4 | +(4 rows) + +SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + i | sum +---+----- + 1 | 3 + 2 | 2 + 3 | + 4 | +(4 rows) + +SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v); + i | sum +---+----- + 1 | 3 + 2 | 6 + 3 | 9 + 4 | 7 +(4 rows) + +-- ensure aggregate over numeric properly recovers from NaN values +SELECT a, b, + SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b); + a | b | sum +---+-----+----- + 1 | 1 | 1 + 2 | 2 | 3 + 3 | NaN | NaN + 4 | 3 | NaN + 5 | 4 | 7 +(5 rows) + +-- It might be tempting for someone to add an inverse trans function for +-- float and double precision. This should not be done as it can give incorrect +-- results. This test should fail if anyone ever does this without thinking too +-- hard about it. +SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9') + FROM (VALUES(1,1e20),(2,1)) n(i,n); + to_char +-------------------------- + 100000000000000000000 + 1.0 +(2 rows) + +SELECT i, b, bool_and(b) OVER w, bool_or(b) OVER w + FROM (VALUES (1,true), (2,true), (3,false), (4,false), (5,true)) v(i,b) + WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING); + i | b | bool_and | bool_or +---+---+----------+--------- + 1 | t | t | t + 2 | t | f | t + 3 | f | f | f + 4 | f | f | t + 5 | t | t | t +(5 rows) + diff --git a/src/test/regress/expected/yb.port.with.out b/src/test/regress/expected/yb.port.with.out new file mode 100644 index 0000000000000..3711189f1ded1 --- /dev/null +++ b/src/test/regress/expected/yb.port.with.out @@ -0,0 +1,3429 @@ +-- +-- Tests for common table expressions (WITH query, ... SELECT ...) +-- +-- Basic WITH +WITH q1(x,y) AS (SELECT 1,2) +SELECT * FROM q1, q1 AS q2; + x | y | x | y +---+---+---+--- + 1 | 2 | 1 | 2 +(1 row) + +-- Multiple uses are evaluated only once +SELECT count(*) FROM ( + WITH q1(x) AS (SELECT random() FROM generate_series(1, 5)) + SELECT * FROM q1 + UNION + SELECT * FROM q1 +) ss; + count +------- + 5 +(1 row) + +-- WITH RECURSIVE +-- sum of 1..100 +WITH RECURSIVE t(n) AS ( + VALUES (1) +UNION ALL + SELECT n+1 FROM t WHERE n < 100 +) +SELECT sum(n) FROM t; + sum +------ + 5050 +(1 row) + +WITH RECURSIVE t(n) AS ( + SELECT (VALUES(1)) +UNION ALL + SELECT n+1 FROM t WHERE n < 5 +) +SELECT * FROM t; + n +--- + 1 + 2 + 3 + 4 + 5 +(5 rows) + +-- UNION DISTINCT requires hashable type +WITH RECURSIVE t(n) AS ( + VALUES (1::money) +UNION + SELECT n+1::money FROM t WHERE n < 100::money +) +SELECT sum(n) FROM t; +ERROR: could not implement recursive UNION +DETAIL: All column datatypes must be hashable. +-- recursive view +CREATE RECURSIVE VIEW nums (n) AS + VALUES (1) +UNION ALL + SELECT n+1 FROM nums WHERE n < 5; +SELECT * FROM nums; + n +--- + 1 + 2 + 3 + 4 + 5 +(5 rows) + +CREATE OR REPLACE RECURSIVE VIEW nums (n) AS + VALUES (1) +UNION ALL + SELECT n+1 FROM nums WHERE n < 6; +SELECT * FROM nums; + n +--- + 1 + 2 + 3 + 4 + 5 + 6 +(6 rows) + +-- This is an infinite loop with UNION ALL, but not with UNION +WITH RECURSIVE t(n) AS ( + SELECT 1 +UNION + SELECT 10-n FROM t) +SELECT * FROM t; + n +--- + 1 + 9 +(2 rows) + +-- This'd be an infinite loop, but outside query reads only as much as needed +WITH RECURSIVE t(n) AS ( + VALUES (1) +UNION ALL + SELECT n+1 FROM t) +SELECT * FROM t LIMIT 10; + n +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +-- UNION case should have same property +WITH RECURSIVE t(n) AS ( + SELECT 1 +UNION + SELECT n+1 FROM t) +SELECT * FROM t LIMIT 10; + n +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +-- Test behavior with an unknown-type literal in the WITH +WITH q AS (SELECT 'foo' AS x) +SELECT x, pg_typeof(x) FROM q; + x | pg_typeof +-----+----------- + foo | text +(1 row) + +WITH RECURSIVE t(n) AS ( + SELECT 'foo' +UNION ALL + SELECT n || ' bar' FROM t WHERE length(n) < 20 +) +SELECT n, pg_typeof(n) FROM t; + n | pg_typeof +-------------------------+----------- + foo | text + foo bar | text + foo bar bar | text + foo bar bar bar | text + foo bar bar bar bar | text + foo bar bar bar bar bar | text +(6 rows) + +-- In a perfect world, this would work and resolve the literal as int ... +-- but for now, we have to be content with resolving to text too soon. +WITH RECURSIVE t(n) AS ( + SELECT '7' +UNION ALL + SELECT n+1 FROM t WHERE n < 10 +) +SELECT n, pg_typeof(n) FROM t; +ERROR: operator does not exist: text + integer +LINE 4: SELECT n+1 FROM t WHERE n < 10 + ^ +HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +-- Deeply nested WITH caused a list-munging problem in v13 +-- Detection of cross-references and self-references +WITH RECURSIVE w1(c1) AS + (WITH w2(c2) AS + (WITH w3(c3) AS + (WITH w4(c4) AS + (WITH w5(c5) AS + (WITH RECURSIVE w6(c6) AS + (WITH w6(c6) AS + (WITH w8(c8) AS + (SELECT 1) + SELECT * FROM w8) + SELECT * FROM w6) + SELECT * FROM w6) + SELECT * FROM w5) + SELECT * FROM w4) + SELECT * FROM w3) + SELECT * FROM w2) +SELECT * FROM w1; + c1 +---- + 1 +(1 row) + +-- Detection of invalid self-references +WITH RECURSIVE outermost(x) AS ( + SELECT 1 + UNION (WITH innermost1 AS ( + SELECT 2 + UNION (WITH innermost2 AS ( + SELECT 3 + UNION (WITH innermost3 AS ( + SELECT 4 + UNION (WITH innermost4 AS ( + SELECT 5 + UNION (WITH innermost5 AS ( + SELECT 6 + UNION (WITH innermost6 AS + (SELECT 7) + SELECT * FROM innermost6)) + SELECT * FROM innermost5)) + SELECT * FROM innermost4)) + SELECT * FROM innermost3)) + SELECT * FROM innermost2)) + SELECT * FROM outermost + UNION SELECT * FROM innermost1) + ) + SELECT * FROM outermost ORDER BY 1; + x +--- + 1 + 2 + 3 + 4 + 5 + 6 + 7 +(7 rows) + +-- +-- Some examples with a tree +-- +-- department structure represented here is as follows: +-- +-- ROOT-+->A-+->B-+->C +-- | | +-- | +->D-+->F +-- +->E-+->G +CREATE TEMP TABLE department ( + id INTEGER PRIMARY KEY, -- department ID + parent_department INTEGER REFERENCES department, -- upper department ID + name TEXT -- department name +); +INSERT INTO department VALUES (0, NULL, 'ROOT'); +INSERT INTO department VALUES (1, 0, 'A'); +INSERT INTO department VALUES (2, 1, 'B'); +INSERT INTO department VALUES (3, 2, 'C'); +INSERT INTO department VALUES (4, 2, 'D'); +INSERT INTO department VALUES (5, 0, 'E'); +INSERT INTO department VALUES (6, 4, 'F'); +INSERT INTO department VALUES (7, 5, 'G'); +-- extract all departments under 'A'. Result should be A, B, C, D and F +WITH RECURSIVE subdepartment AS +( + -- non recursive term + SELECT name as root_name, * FROM department WHERE name = 'A' + UNION ALL + -- recursive term + SELECT sd.root_name, d.* FROM department AS d, subdepartment AS sd + WHERE d.parent_department = sd.id +) +SELECT * FROM subdepartment ORDER BY name; + root_name | id | parent_department | name +-----------+----+-------------------+------ + A | 1 | 0 | A + A | 2 | 1 | B + A | 3 | 2 | C + A | 4 | 2 | D + A | 6 | 4 | F +(5 rows) + +-- extract all departments under 'A' with "level" number +WITH RECURSIVE subdepartment(level, id, parent_department, name) AS +( + -- non recursive term + SELECT 1, * FROM department WHERE name = 'A' + UNION ALL + -- recursive term + SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd + WHERE d.parent_department = sd.id +) +SELECT * FROM subdepartment ORDER BY name; + level | id | parent_department | name +-------+----+-------------------+------ + 1 | 1 | 0 | A + 2 | 2 | 1 | B + 3 | 3 | 2 | C + 3 | 4 | 2 | D + 4 | 6 | 4 | F +(5 rows) + +-- extract all departments under 'A' with "level" number. +-- Only shows level 2 or more +WITH RECURSIVE subdepartment(level, id, parent_department, name) AS +( + -- non recursive term + SELECT 1, * FROM department WHERE name = 'A' + UNION ALL + -- recursive term + SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd + WHERE d.parent_department = sd.id +) +SELECT * FROM subdepartment WHERE level >= 2 ORDER BY name; + level | id | parent_department | name +-------+----+-------------------+------ + 2 | 2 | 1 | B + 3 | 3 | 2 | C + 3 | 4 | 2 | D + 4 | 6 | 4 | F +(4 rows) + +-- "RECURSIVE" is ignored if the query has no self-reference +WITH RECURSIVE subdepartment AS +( + -- note lack of recursive UNION structure + SELECT * FROM department WHERE name = 'A' +) +SELECT * FROM subdepartment ORDER BY name; + id | parent_department | name +----+-------------------+------ + 1 | 0 | A +(1 row) + +-- inside subqueries +SELECT count(*) FROM ( + WITH RECURSIVE t(n) AS ( + SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 500 + ) + SELECT * FROM t) AS t WHERE n < ( + SELECT count(*) FROM ( + WITH RECURSIVE t(n) AS ( + SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 100 + ) + SELECT * FROM t WHERE n < 50000 + ) AS t WHERE n < 100); + count +------- + 98 +(1 row) + +-- use same CTE twice at different subquery levels +WITH q1(x,y) AS ( + SELECT hundred, sum(ten) FROM tenk1 GROUP BY hundred + ) +SELECT count(*) FROM q1 WHERE y > (SELECT sum(y)/100 FROM q1 qsub); + count +------- + 50 +(1 row) + +-- via a VIEW +CREATE TEMPORARY VIEW vsubdepartment AS + WITH RECURSIVE subdepartment AS + ( + -- non recursive term + SELECT * FROM department WHERE name = 'A' + UNION ALL + -- recursive term + SELECT d.* FROM department AS d, subdepartment AS sd + WHERE d.parent_department = sd.id + ) + SELECT * FROM subdepartment; +SELECT * FROM vsubdepartment ORDER BY name; + id | parent_department | name +----+-------------------+------ + 1 | 0 | A + 2 | 1 | B + 3 | 2 | C + 4 | 2 | D + 6 | 4 | F +(5 rows) + +-- Check reverse listing +SELECT pg_get_viewdef('vsubdepartment'::regclass); + pg_get_viewdef +----------------------------------------------- + WITH RECURSIVE subdepartment AS ( + + SELECT department.id, + + department.parent_department, + + department.name + + FROM department + + WHERE (department.name = 'A'::text)+ + UNION ALL + + SELECT d.id, + + d.parent_department, + + d.name + + FROM department d, + + subdepartment sd + + WHERE (d.parent_department = sd.id)+ + ) + + SELECT subdepartment.id, + + subdepartment.parent_department, + + subdepartment.name + + FROM subdepartment; +(1 row) + +SELECT pg_get_viewdef('vsubdepartment'::regclass, true); + pg_get_viewdef +--------------------------------------------- + WITH RECURSIVE subdepartment AS ( + + SELECT department.id, + + department.parent_department, + + department.name + + FROM department + + WHERE department.name = 'A'::text+ + UNION ALL + + SELECT d.id, + + d.parent_department, + + d.name + + FROM department d, + + subdepartment sd + + WHERE d.parent_department = sd.id+ + ) + + SELECT subdepartment.id, + + subdepartment.parent_department, + + subdepartment.name + + FROM subdepartment; +(1 row) + +-- Another reverse-listing example +CREATE VIEW sums_1_100 AS +WITH RECURSIVE t(n) AS ( + VALUES (1) +UNION ALL + SELECT n+1 FROM t WHERE n < 100 +) +SELECT sum(n) FROM t; +\d+ sums_1_100 + View "public.sums_1_100" + Column | Type | Collation | Nullable | Default | Storage | Description +--------+--------+-----------+----------+---------+---------+------------- + sum | bigint | | | | plain | +View definition: + WITH RECURSIVE t(n) AS ( + VALUES (1) + UNION ALL + SELECT t_1.n + 1 + FROM t t_1 + WHERE t_1.n < 100 + ) + SELECT sum(t.n) AS sum + FROM t; + +-- corner case in which sub-WITH gets initialized first +with recursive q as ( + select * from department + union all + (with x as (select * from q) + select * from x) + ) +select * from q limit 24; + id | parent_department | name +----+-------------------+------ + 0 | | ROOT + 1 | 0 | A + 2 | 1 | B + 3 | 2 | C + 4 | 2 | D + 5 | 0 | E + 6 | 4 | F + 7 | 5 | G + 0 | | ROOT + 1 | 0 | A + 2 | 1 | B + 3 | 2 | C + 4 | 2 | D + 5 | 0 | E + 6 | 4 | F + 7 | 5 | G + 0 | | ROOT + 1 | 0 | A + 2 | 1 | B + 3 | 2 | C + 4 | 2 | D + 5 | 0 | E + 6 | 4 | F + 7 | 5 | G +(24 rows) + +with recursive q as ( + select * from department + union all + (with recursive x as ( + select * from department + union all + (select * from q union all select * from x) + ) + select * from x) + ) +select * from q limit 32; + id | parent_department | name +----+-------------------+------ + 0 | | ROOT + 1 | 0 | A + 2 | 1 | B + 3 | 2 | C + 4 | 2 | D + 5 | 0 | E + 6 | 4 | F + 7 | 5 | G + 0 | | ROOT + 1 | 0 | A + 2 | 1 | B + 3 | 2 | C + 4 | 2 | D + 5 | 0 | E + 6 | 4 | F + 7 | 5 | G + 0 | | ROOT + 1 | 0 | A + 2 | 1 | B + 3 | 2 | C + 4 | 2 | D + 5 | 0 | E + 6 | 4 | F + 7 | 5 | G + 0 | | ROOT + 1 | 0 | A + 2 | 1 | B + 3 | 2 | C + 4 | 2 | D + 5 | 0 | E + 6 | 4 | F + 7 | 5 | G +(32 rows) + +-- recursive term has sub-UNION +WITH RECURSIVE t(i,j) AS ( + VALUES (1,2) + UNION ALL + SELECT t2.i, t.j+1 FROM + (SELECT 2 AS i UNION ALL SELECT 3 AS i) AS t2 + JOIN t ON (t2.i = t.i+1)) + SELECT * FROM t; + i | j +---+--- + 1 | 2 + 2 | 3 + 3 | 4 +(3 rows) + +-- +-- different tree example +-- +CREATE TEMPORARY TABLE tree( + id INTEGER PRIMARY KEY, + parent_id INTEGER REFERENCES tree(id) +); +INSERT INTO tree +VALUES (1, NULL), (2, 1), (3,1), (4,2), (5,2), (6,2), (7,3), (8,3), + (9,4), (10,4), (11,7), (12,7), (13,7), (14, 9), (15,11), (16,11); +-- +-- get all paths from "second level" nodes to leaf nodes +-- +WITH RECURSIVE t(id, path) AS ( + VALUES(1,ARRAY[]::integer[]) +UNION ALL + SELECT tree.id, t.path || tree.id + FROM tree JOIN t ON (tree.parent_id = t.id) +) +SELECT t1.*, t2.* FROM t AS t1 JOIN t AS t2 ON + (t1.path[1] = t2.path[1] AND + array_upper(t1.path,1) = 1 AND + array_upper(t2.path,1) > 1) + ORDER BY t1.id, t2.id; + id | path | id | path +----+------+----+------------- + 2 | {2} | 4 | {2,4} + 2 | {2} | 5 | {2,5} + 2 | {2} | 6 | {2,6} + 2 | {2} | 9 | {2,4,9} + 2 | {2} | 10 | {2,4,10} + 2 | {2} | 14 | {2,4,9,14} + 3 | {3} | 7 | {3,7} + 3 | {3} | 8 | {3,8} + 3 | {3} | 11 | {3,7,11} + 3 | {3} | 12 | {3,7,12} + 3 | {3} | 13 | {3,7,13} + 3 | {3} | 15 | {3,7,11,15} + 3 | {3} | 16 | {3,7,11,16} +(13 rows) + +-- just count 'em +WITH RECURSIVE t(id, path) AS ( + VALUES(1,ARRAY[]::integer[]) +UNION ALL + SELECT tree.id, t.path || tree.id + FROM tree JOIN t ON (tree.parent_id = t.id) +) +SELECT t1.id, count(t2.*) FROM t AS t1 JOIN t AS t2 ON + (t1.path[1] = t2.path[1] AND + array_upper(t1.path,1) = 1 AND + array_upper(t2.path,1) > 1) + GROUP BY t1.id + ORDER BY t1.id; + id | count +----+------- + 2 | 6 + 3 | 7 +(2 rows) + +-- this variant tickled a whole-row-variable bug in 8.4devel +WITH RECURSIVE t(id, path) AS ( + VALUES(1,ARRAY[]::integer[]) +UNION ALL + SELECT tree.id, t.path || tree.id + FROM tree JOIN t ON (tree.parent_id = t.id) +) +SELECT t1.id, t2.path, t2 FROM t AS t1 JOIN t AS t2 ON +(t1.id=t2.id); + id | path | t2 +----+-------------+-------------------- + 1 | {} | (1,{}) + 2 | {2} | (2,{2}) + 3 | {3} | (3,{3}) + 4 | {2,4} | (4,"{2,4}") + 5 | {2,5} | (5,"{2,5}") + 6 | {2,6} | (6,"{2,6}") + 7 | {3,7} | (7,"{3,7}") + 8 | {3,8} | (8,"{3,8}") + 9 | {2,4,9} | (9,"{2,4,9}") + 10 | {2,4,10} | (10,"{2,4,10}") + 11 | {3,7,11} | (11,"{3,7,11}") + 12 | {3,7,12} | (12,"{3,7,12}") + 13 | {3,7,13} | (13,"{3,7,13}") + 14 | {2,4,9,14} | (14,"{2,4,9,14}") + 15 | {3,7,11,15} | (15,"{3,7,11,15}") + 16 | {3,7,11,16} | (16,"{3,7,11,16}") +(16 rows) + +-- SEARCH clause +create temp table graph0( f int, t int, label text ); +insert into graph0 values + (1, 2, 'arc 1 -> 2'), + (1, 3, 'arc 1 -> 3'), + (2, 3, 'arc 2 -> 3'), + (1, 4, 'arc 1 -> 4'), + (4, 5, 'arc 4 -> 5'); +explain (verbose, costs off) +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select * from search_graph order by seq; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Sort + Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq + Sort Key: search_graph.seq + CTE search_graph + -> Recursive Union + -> Seq Scan on pg_temp.graph0 g + Output: g.f, g.t, g.label, ARRAY[ROW(g.f, g.t)] + -> Merge Join + Output: g_1.f, g_1.t, g_1.label, array_cat(sg.seq, ARRAY[ROW(g_1.f, g_1.t)]) + Merge Cond: (g_1.f = sg.t) + -> Sort + Output: g_1.f, g_1.t, g_1.label + Sort Key: g_1.f + -> Seq Scan on pg_temp.graph0 g_1 + Output: g_1.f, g_1.t, g_1.label + -> Sort + Output: sg.seq, sg.t + Sort Key: sg.t + -> WorkTable Scan on search_graph sg + Output: sg.seq, sg.t + -> CTE Scan on search_graph + Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq +(22 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select * from search_graph order by seq; + f | t | label | seq +---+---+------------+------------------- + 1 | 2 | arc 1 -> 2 | {"(1,2)"} + 2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | {"(1,3)"} + 1 | 4 | arc 1 -> 4 | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} + 2 | 3 | arc 2 -> 3 | {"(2,3)"} + 4 | 5 | arc 4 -> 5 | {"(4,5)"} +(7 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union distinct + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select * from search_graph order by seq; + f | t | label | seq +---+---+------------+------------------- + 1 | 2 | arc 1 -> 2 | {"(1,2)"} + 2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | {"(1,3)"} + 1 | 4 | arc 1 -> 4 | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} + 2 | 3 | arc 2 -> 3 | {"(2,3)"} + 4 | 5 | arc 4 -> 5 | {"(4,5)"} +(7 rows) + +explain (verbose, costs off) +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq +select * from search_graph order by seq; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Sort + Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq + Sort Key: search_graph.seq + CTE search_graph + -> Recursive Union + -> Seq Scan on pg_temp.graph0 g + Output: g.f, g.t, g.label, ROW('0'::bigint, g.f, g.t) + -> Merge Join + Output: g_1.f, g_1.t, g_1.label, ROW(int8inc((sg.seq)."*DEPTH*"), g_1.f, g_1.t) + Merge Cond: (g_1.f = sg.t) + -> Sort + Output: g_1.f, g_1.t, g_1.label + Sort Key: g_1.f + -> Seq Scan on pg_temp.graph0 g_1 + Output: g_1.f, g_1.t, g_1.label + -> Sort + Output: sg.seq, sg.t + Sort Key: sg.t + -> WorkTable Scan on search_graph sg + Output: sg.seq, sg.t + -> CTE Scan on search_graph + Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq +(22 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq +select * from search_graph order by seq; + f | t | label | seq +---+---+------------+--------- + 1 | 2 | arc 1 -> 2 | (0,1,2) + 1 | 3 | arc 1 -> 3 | (0,1,3) + 1 | 4 | arc 1 -> 4 | (0,1,4) + 2 | 3 | arc 2 -> 3 | (0,2,3) + 4 | 5 | arc 4 -> 5 | (0,4,5) + 2 | 3 | arc 2 -> 3 | (1,2,3) + 4 | 5 | arc 4 -> 5 | (1,4,5) +(7 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union distinct + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq +select * from search_graph order by seq; + f | t | label | seq +---+---+------------+--------- + 1 | 2 | arc 1 -> 2 | (0,1,2) + 1 | 3 | arc 1 -> 3 | (0,1,3) + 1 | 4 | arc 1 -> 4 | (0,1,4) + 2 | 3 | arc 2 -> 3 | (0,2,3) + 4 | 5 | arc 4 -> 5 | (0,4,5) + 2 | 3 | arc 2 -> 3 | (1,2,3) + 4 | 5 | arc 4 -> 5 | (1,4,5) +(7 rows) + +-- a constant initial value causes issues for EXPLAIN +explain (verbose, costs off) +with recursive test as ( + select 1 as x + union all + select x + 1 + from test +) search depth first by x set y +select * from test limit 5; + QUERY PLAN +----------------------------------------------------------------------------------- + Limit + Output: test_1.x, test_1.y + CTE test + -> Recursive Union + -> Result *RESULT* + Output: 1, '{(1)}'::record[] + -> WorkTable Scan on test + Output: (test.x + 1), array_cat(test.y, ARRAY[ROW((test.x + 1))]) + -> CTE Scan on test test_1 + Output: test_1.x, test_1.y +(10 rows) + +with recursive test as ( + select 1 as x + union all + select x + 1 + from test +) search depth first by x set y +select * from test limit 5; + x | y +---+----------------------- + 1 | {(1)} + 2 | {(1),(2)} + 3 | {(1),(2),(3)} + 4 | {(1),(2),(3),(4)} + 5 | {(1),(2),(3),(4),(5)} +(5 rows) + +explain (verbose, costs off) +with recursive test as ( + select 1 as x + union all + select x + 1 + from test +) search breadth first by x set y +select * from test limit 5; + QUERY PLAN +-------------------------------------------------------------------------------------- + Limit + Output: test_1.x, test_1.y + CTE test + -> Recursive Union + -> Result *RESULT* + Output: 1, '(0,1)'::record + -> WorkTable Scan on test + Output: (test.x + 1), ROW(int8inc((test.y)."*DEPTH*"), (test.x + 1)) + -> CTE Scan on test test_1 + Output: test_1.x, test_1.y +(10 rows) + +with recursive test as ( + select 1 as x + union all + select x + 1 + from test +) search breadth first by x set y +select * from test limit 5; + x | y +---+------- + 1 | (0,1) + 2 | (1,2) + 3 | (2,3) + 4 | (3,4) + 5 | (4,5) +(5 rows) + +-- various syntax errors +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by foo, tar set seq +select * from search_graph; +ERROR: search column "foo" not in WITH query column list +LINE 7: ) search depth first by foo, tar set seq + ^ +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set label +select * from search_graph; +ERROR: search sequence column name "label" already used in WITH query column list +LINE 7: ) search depth first by f, t set label + ^ +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t, f set seq +select * from search_graph; +ERROR: search column "f" specified more than once +LINE 7: ) search depth first by f, t, f set seq + ^ +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select * from search_graph order by seq; +ERROR: with a SEARCH or CYCLE clause, the left side of the UNION must be a SELECT +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + (select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t) +) search depth first by f, t set seq +select * from search_graph order by seq; +ERROR: with a SEARCH or CYCLE clause, the right side of the UNION must be a SELECT +-- check that we distinguish same CTE name used at different levels +-- (this case could be supported, perhaps, but it isn't today) +with recursive x(col) as ( + select 1 + union + (with x as (select * from x) + select * from x) +) search depth first by col set seq +select * from x; +ERROR: with a SEARCH or CYCLE clause, the recursive reference to WITH query "x" must be at the top level of its right-hand SELECT +-- test ruleutils and view expansion +create temp view v_search as +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select f, t, label from search_graph; +select pg_get_viewdef('v_search'); + pg_get_viewdef +------------------------------------------------ + WITH RECURSIVE search_graph(f, t, label) AS (+ + SELECT g.f, + + g.t, + + g.label + + FROM graph0 g + + UNION ALL + + SELECT g.f, + + g.t, + + g.label + + FROM graph0 g, + + search_graph sg + + WHERE (g.f = sg.t) + + ) SEARCH DEPTH FIRST BY f, t SET seq + + SELECT search_graph.f, + + search_graph.t, + + search_graph.label + + FROM search_graph; +(1 row) + +select * from v_search; + f | t | label +---+---+------------ + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 + 4 | 5 | arc 4 -> 5 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 +(7 rows) + +-- +-- test cycle detection +-- +create temp table graph( f int, t int, label text ); +insert into graph values + (1, 2, 'arc 1 -> 2'), + (1, 3, 'arc 1 -> 3'), + (2, 3, 'arc 2 -> 3'), + (1, 4, 'arc 1 -> 4'), + (4, 5, 'arc 4 -> 5'), + (5, 1, 'arc 5 -> 1'); +with recursive search_graph(f, t, label, is_cycle, path) as ( + select *, false, array[row(g.f, g.t)] from graph g + union all + select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t) + from graph g, search_graph sg + where g.f = sg.t and not is_cycle +) +select * from search_graph; + f | t | label | is_cycle | path +---+---+------------+----------+------------------------------------------- + 1 | 2 | arc 1 -> 2 | f | {"(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,3)"} + 2 | 3 | arc 2 -> 3 | f | {"(2,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | f | {"(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} +(25 rows) + +-- UNION DISTINCT exercises row type hashing support +with recursive search_graph(f, t, label, is_cycle, path) as ( + select *, false, array[row(g.f, g.t)] from graph g + union distinct + select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t) + from graph g, search_graph sg + where g.f = sg.t and not is_cycle +) +select * from search_graph; + f | t | label | is_cycle | path +---+---+------------+----------+------------------------------------------- + 1 | 2 | arc 1 -> 2 | f | {"(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,3)"} + 2 | 3 | arc 2 -> 3 | f | {"(2,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | f | {"(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} +(25 rows) + +-- ordering by the path column has same effect as SEARCH DEPTH FIRST +with recursive search_graph(f, t, label, is_cycle, path) as ( + select *, false, array[row(g.f, g.t)] from graph g + union all + select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t) + from graph g, search_graph sg + where g.f = sg.t and not is_cycle +) +select * from search_graph order by path; + f | t | label | is_cycle | path +---+---+------------+----------+------------------------------------------- + 1 | 2 | arc 1 -> 2 | f | {"(1,2)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(2,3)"} + 4 | 5 | arc 4 -> 5 | f | {"(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"} + 2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} + 4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"} + 2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} + 4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} +(25 rows) + +-- CYCLE clause +explain (verbose, costs off) +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle using path +select * from search_graph; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------- + CTE Scan on search_graph + Output: search_graph.f, search_graph.t, search_graph.label, search_graph.is_cycle, search_graph.path + CTE search_graph + -> Recursive Union + -> Seq Scan on pg_temp.graph g + Output: g.f, g.t, g.label, false, ARRAY[ROW(g.f, g.t)] + -> Merge Join + Output: g_1.f, g_1.t, g_1.label, CASE WHEN (ROW(g_1.f, g_1.t) = ANY (sg.path)) THEN true ELSE false END, array_cat(sg.path, ARRAY[ROW(g_1.f, g_1.t)]) + Merge Cond: (g_1.f = sg.t) + -> Sort + Output: g_1.f, g_1.t, g_1.label + Sort Key: g_1.f + -> Seq Scan on pg_temp.graph g_1 + Output: g_1.f, g_1.t, g_1.label + -> Sort + Output: sg.path, sg.t + Sort Key: sg.t + -> WorkTable Scan on search_graph sg + Output: sg.path, sg.t + Filter: (NOT sg.is_cycle) +(20 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle using path +select * from search_graph; + f | t | label | is_cycle | path +---+---+------------+----------+------------------------------------------- + 1 | 2 | arc 1 -> 2 | f | {"(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,3)"} + 2 | 3 | arc 2 -> 3 | f | {"(2,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | f | {"(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} +(25 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph g + union distinct + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to 'Y' default 'N' using path +select * from search_graph; + f | t | label | is_cycle | path +---+---+------------+----------+------------------------------------------- + 1 | 2 | arc 1 -> 2 | N | {"(1,2)"} + 1 | 3 | arc 1 -> 3 | N | {"(1,3)"} + 2 | 3 | arc 2 -> 3 | N | {"(2,3)"} + 1 | 4 | arc 1 -> 4 | N | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | N | {"(4,5)"} + 5 | 1 | arc 5 -> 1 | N | {"(5,1)"} + 1 | 2 | arc 1 -> 2 | N | {"(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | N | {"(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | N | {"(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | N | {"(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | N | {"(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | N | {"(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | N | {"(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | N | {"(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | N | {"(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | N | {"(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | N | {"(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | N | {"(1,4)","(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | N | {"(1,4)","(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | Y | {"(1,4)","(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | N | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | Y | {"(4,5)","(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | Y | {"(5,1)","(1,4)","(4,5)","(5,1)"} + 2 | 3 | arc 2 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} +(25 rows) + +explain (verbose, costs off) +with recursive test as ( + select 0 as x + union all + select (x + 1) % 10 + from test +) cycle x set is_cycle using path +select * from test; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + CTE Scan on test test_1 + Output: test_1.x, test_1.is_cycle, test_1.path + CTE test + -> Recursive Union + -> Result *RESULT* + Output: 0, false, '{(0)}'::record[] + -> WorkTable Scan on test + Output: ((test.x + 1) % 10), CASE WHEN (ROW(((test.x + 1) % 10)) = ANY (test.path)) THEN true ELSE false END, array_cat(test.path, ARRAY[ROW(((test.x + 1) % 10))]) + Filter: (NOT test.is_cycle) +(9 rows) + +with recursive test as ( + select 0 as x + union all + select (x + 1) % 10 + from test +) cycle x set is_cycle using path +select * from test; + x | is_cycle | path +---+----------+----------------------------------------------- + 0 | f | {(0)} + 1 | f | {(0),(1)} + 2 | f | {(0),(1),(2)} + 3 | f | {(0),(1),(2),(3)} + 4 | f | {(0),(1),(2),(3),(4)} + 5 | f | {(0),(1),(2),(3),(4),(5)} + 6 | f | {(0),(1),(2),(3),(4),(5),(6)} + 7 | f | {(0),(1),(2),(3),(4),(5),(6),(7)} + 8 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8)} + 9 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)} + 0 | t | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(0)} +(11 rows) + +with recursive test as ( + select 0 as x + union all + select (x + 1) % 10 + from test + where not is_cycle -- redundant, but legal +) cycle x set is_cycle using path +select * from test; + x | is_cycle | path +---+----------+----------------------------------------------- + 0 | f | {(0)} + 1 | f | {(0),(1)} + 2 | f | {(0),(1),(2)} + 3 | f | {(0),(1),(2),(3)} + 4 | f | {(0),(1),(2),(3),(4)} + 5 | f | {(0),(1),(2),(3),(4),(5)} + 6 | f | {(0),(1),(2),(3),(4),(5),(6)} + 7 | f | {(0),(1),(2),(3),(4),(5),(6),(7)} + 8 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8)} + 9 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)} + 0 | t | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(0)} +(11 rows) + +-- multiple CTEs +with recursive +graph(f, t, label) as ( + values (1, 2, 'arc 1 -> 2'), + (1, 3, 'arc 1 -> 3'), + (2, 3, 'arc 2 -> 3'), + (1, 4, 'arc 1 -> 4'), + (4, 5, 'arc 4 -> 5'), + (5, 1, 'arc 5 -> 1') +), +search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to true default false using path +select f, t, label from search_graph; + f | t | label +---+---+------------ + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 4 | arc 1 -> 4 + 1 | 3 | arc 1 -> 3 + 1 | 2 | arc 1 -> 2 + 5 | 1 | arc 5 -> 1 + 1 | 4 | arc 1 -> 4 + 1 | 3 | arc 1 -> 3 + 1 | 2 | arc 1 -> 2 + 4 | 5 | arc 4 -> 5 + 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 + 1 | 3 | arc 1 -> 3 + 1 | 2 | arc 1 -> 2 + 4 | 5 | arc 4 -> 5 + 2 | 3 | arc 2 -> 3 + 5 | 1 | arc 5 -> 1 + 2 | 3 | arc 2 -> 3 +(25 rows) + +-- star expansion +with recursive a as ( + select 1 as b + union all + select * from a +) cycle b set c using p +select * from a; + b | c | p +---+---+----------- + 1 | f | {(1)} + 1 | t | {(1),(1)} +(2 rows) + +-- search+cycle +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq + cycle f, t set is_cycle using path +select * from search_graph; + f | t | label | seq | is_cycle | path +---+---+------------+-------------------------------------------+----------+------------------------------------------- + 1 | 2 | arc 1 -> 2 | {"(1,2)"} | f | {"(1,2)"} + 1 | 3 | arc 1 -> 3 | {"(1,3)"} | f | {"(1,3)"} + 2 | 3 | arc 2 -> 3 | {"(2,3)"} | f | {"(2,3)"} + 1 | 4 | arc 1 -> 4 | {"(1,4)"} | f | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | {"(4,5)"} | f | {"(4,5)"} + 5 | 1 | arc 5 -> 1 | {"(5,1)"} | f | {"(5,1)"} + 1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f | {"(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f | {"(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f | {"(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f | {"(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f | {"(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f | {"(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f | {"(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f | {"(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f | {"(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f | {"(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f | {"(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f | {"(1,4)","(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} + 2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} +(25 rows) + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq + cycle f, t set is_cycle using path +select * from search_graph; + f | t | label | seq | is_cycle | path +---+---+------------+---------+----------+------------------------------------------- + 1 | 2 | arc 1 -> 2 | (0,1,2) | f | {"(1,2)"} + 1 | 3 | arc 1 -> 3 | (0,1,3) | f | {"(1,3)"} + 2 | 3 | arc 2 -> 3 | (0,2,3) | f | {"(2,3)"} + 1 | 4 | arc 1 -> 4 | (0,1,4) | f | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | (0,4,5) | f | {"(4,5)"} + 5 | 1 | arc 5 -> 1 | (0,5,1) | f | {"(5,1)"} + 1 | 2 | arc 1 -> 2 | (1,1,2) | f | {"(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | (1,1,3) | f | {"(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | (1,1,4) | f | {"(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | (1,2,3) | f | {"(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | (1,4,5) | f | {"(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | (1,5,1) | f | {"(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | (2,1,2) | f | {"(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | (2,1,3) | f | {"(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | (2,1,4) | f | {"(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | (2,2,3) | f | {"(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | (2,4,5) | f | {"(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | (2,5,1) | f | {"(1,4)","(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | (3,1,2) | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | (3,1,3) | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | (3,1,4) | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | (3,2,3) | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | (3,4,5) | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | (3,5,1) | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} + 2 | 3 | arc 2 -> 3 | (4,2,3) | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} +(25 rows) + +-- various syntax errors +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle foo, tar set is_cycle using path +select * from search_graph; +ERROR: cycle column "foo" not in WITH query column list +LINE 7: ) cycle foo, tar set is_cycle using path + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to true default 55 using path +select * from search_graph; +ERROR: CYCLE types boolean and integer cannot be matched +LINE 7: ) cycle f, t set is_cycle to true default 55 using path + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to point '(1,1)' default point '(0,0)' using path +select * from search_graph; +ERROR: could not identify an equality operator for type point +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set label to true default false using path +select * from search_graph; +ERROR: cycle mark column name "label" already used in WITH query column list +LINE 7: ) cycle f, t set label to true default false using path + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to true default false using label +select * from search_graph; +ERROR: cycle path column name "label" already used in WITH query column list +LINE 7: ) cycle f, t set is_cycle to true default false using label + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set foo to true default false using foo +select * from search_graph; +ERROR: cycle mark column name and cycle path column name are the same +LINE 7: ) cycle f, t set foo to true default false using foo + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t, f set is_cycle to true default false using path +select * from search_graph; +ERROR: cycle column "f" specified more than once +LINE 7: ) cycle f, t, f set is_cycle to true default false using pat... + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search depth first by f, t set foo + cycle f, t set foo to true default false using path +select * from search_graph; +ERROR: search sequence column name and cycle mark column name are the same +LINE 7: ) search depth first by f, t set foo + ^ +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search depth first by f, t set foo + cycle f, t set is_cycle to true default false using foo +select * from search_graph; +ERROR: search sequence column name and cycle path column name are the same +LINE 7: ) search depth first by f, t set foo + ^ +-- test ruleutils and view expansion +create temp view v_cycle1 as +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle using path +select f, t, label from search_graph; +create temp view v_cycle2 as +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to 'Y' default 'N' using path +select f, t, label from search_graph; +select pg_get_viewdef('v_cycle1'); + pg_get_viewdef +------------------------------------------------ + WITH RECURSIVE search_graph(f, t, label) AS (+ + SELECT g.f, + + g.t, + + g.label + + FROM graph g + + UNION ALL + + SELECT g.f, + + g.t, + + g.label + + FROM graph g, + + search_graph sg + + WHERE (g.f = sg.t) + + ) CYCLE f, t SET is_cycle USING path + + SELECT search_graph.f, + + search_graph.t, + + search_graph.label + + FROM search_graph; +(1 row) + +select pg_get_viewdef('v_cycle2'); + pg_get_viewdef +----------------------------------------------------------------------------- + WITH RECURSIVE search_graph(f, t, label) AS ( + + SELECT g.f, + + g.t, + + g.label + + FROM graph g + + UNION ALL + + SELECT g.f, + + g.t, + + g.label + + FROM graph g, + + search_graph sg + + WHERE (g.f = sg.t) + + ) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+ + SELECT search_graph.f, + + search_graph.t, + + search_graph.label + + FROM search_graph; +(1 row) + +select * from v_cycle1; + f | t | label +---+---+------------ + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 2 | 3 | arc 2 -> 3 +(25 rows) + +select * from v_cycle2; + f | t | label +---+---+------------ + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 1 | 2 | arc 1 -> 2 + 1 | 3 | arc 1 -> 3 + 1 | 4 | arc 1 -> 4 + 2 | 3 | arc 2 -> 3 + 4 | 5 | arc 4 -> 5 + 5 | 1 | arc 5 -> 1 + 2 | 3 | arc 2 -> 3 +(25 rows) + +-- +-- test multiple WITH queries +-- +WITH RECURSIVE + y (id) AS (VALUES (1)), + x (id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5) +SELECT * FROM x; + id +---- + 1 + 2 + 3 + 4 + 5 +(5 rows) + +-- forward reference OK +WITH RECURSIVE + x(id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5), + y(id) AS (values (1)) + SELECT * FROM x; + id +---- + 1 + 2 + 3 + 4 + 5 +(5 rows) + +WITH RECURSIVE + x(id) AS + (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5), + y(id) AS + (VALUES (1) UNION ALL SELECT id+1 FROM y WHERE id < 10) + SELECT y.*, x.* FROM y LEFT JOIN x USING (id); + id | id +----+---- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | + 7 | + 8 | + 9 | + 10 | +(10 rows) + +WITH RECURSIVE + x(id) AS + (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5), + y(id) AS + (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 10) + SELECT y.*, x.* FROM y LEFT JOIN x USING (id); + id | id +----+---- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | +(6 rows) + +WITH RECURSIVE + x(id) AS + (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ), + y(id) AS + (SELECT * FROM x UNION ALL SELECT * FROM x), + z(id) AS + (SELECT * FROM x UNION ALL SELECT id+1 FROM z WHERE id < 10) + SELECT * FROM z; + id +---- + 1 + 2 + 3 + 2 + 3 + 4 + 3 + 4 + 5 + 4 + 5 + 6 + 5 + 6 + 7 + 6 + 7 + 8 + 7 + 8 + 9 + 8 + 9 + 10 + 9 + 10 + 10 +(27 rows) + +WITH RECURSIVE + x(id) AS + (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ), + y(id) AS + (SELECT * FROM x UNION ALL SELECT * FROM x), + z(id) AS + (SELECT * FROM y UNION ALL SELECT id+1 FROM z WHERE id < 10) + SELECT * FROM z; + id +---- + 1 + 2 + 3 + 1 + 2 + 3 + 2 + 3 + 4 + 2 + 3 + 4 + 3 + 4 + 5 + 3 + 4 + 5 + 4 + 5 + 6 + 4 + 5 + 6 + 5 + 6 + 7 + 5 + 6 + 7 + 6 + 7 + 8 + 6 + 7 + 8 + 7 + 8 + 9 + 7 + 8 + 9 + 8 + 9 + 10 + 8 + 9 + 10 + 9 + 10 + 9 + 10 + 10 + 10 +(54 rows) + +-- +-- Test WITH attached to a data-modifying statement +-- +CREATE TEMPORARY TABLE y (a INTEGER); +INSERT INTO y SELECT generate_series(1, 10); +WITH t AS ( + SELECT a FROM y +) +INSERT INTO y +SELECT a+20 FROM t RETURNING *; + a +---- + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 +(10 rows) + +SELECT * FROM y; + a +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 +(20 rows) + +WITH t AS ( + SELECT a FROM y +) +UPDATE y SET a = y.a-10 FROM t WHERE y.a > 20 AND t.a = y.a RETURNING y.a; + a +---- + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 +(10 rows) + +SELECT * FROM y; + a +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 +(20 rows) + +WITH RECURSIVE t(a) AS ( + SELECT 11 + UNION ALL + SELECT a+1 FROM t WHERE a < 50 +) +DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a; + a +---- + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 +(10 rows) + +SELECT * FROM y; + a +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +DROP TABLE y; +-- +-- error cases +-- +WITH x(n, b) AS (SELECT 1) +SELECT * FROM x; +ERROR: WITH query "x" has 1 columns available but 2 columns specified +LINE 1: WITH x(n, b) AS (SELECT 1) + ^ +-- INTERSECT +WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x) + SELECT * FROM x; +ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term +LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x... + ^ +WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x) + SELECT * FROM x; +ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term +LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FR... + ^ +-- EXCEPT +WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x) + SELECT * FROM x; +ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term +LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x) + ^ +WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x) + SELECT * FROM x; +ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term +LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM ... + ^ +-- no non-recursive term +WITH RECURSIVE x(n) AS (SELECT n FROM x) + SELECT * FROM x; +ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term +LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x) + ^ +-- recursive term in the left hand side (strictly speaking, should allow this) +WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1) + SELECT * FROM x; +ERROR: recursive reference to query "x" must not appear within its non-recursive term +LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1) + ^ +CREATE TABLE y (a INTEGER, ybsort SERIAL, PRIMARY KEY (ybsort ASC)); -- YB: exercise YB table instead of temp table +INSERT INTO y SELECT generate_series(1, 10); +-- LEFT JOIN +WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1 + UNION ALL + SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10) +SELECT * FROM x; +ERROR: recursive reference to query "x" must not appear within an outer join +LINE 3: SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10) + ^ +-- RIGHT JOIN +WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1 + UNION ALL + SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10) +SELECT * FROM x; +ERROR: recursive reference to query "x" must not appear within an outer join +LINE 3: SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10) + ^ +-- FULL JOIN +WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1 + UNION ALL + SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10) +SELECT * FROM x; +ERROR: recursive reference to query "x" must not appear within an outer join +LINE 3: SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10) + ^ +-- subquery +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x + WHERE n IN (SELECT * FROM x)) + SELECT * FROM x; +ERROR: recursive reference to query "x" must not appear within a subquery +LINE 2: WHERE n IN (SELECT * FROM x)) + ^ +-- aggregate functions +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) FROM x) + SELECT * FROM x; +ERROR: aggregate functions are not allowed in a recursive query's recursive term +LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) F... + ^ +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FROM x) + SELECT * FROM x; +ERROR: aggregate functions are not allowed in a recursive query's recursive term +LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FRO... + ^ +-- ORDER BY +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1) + SELECT * FROM x; +ERROR: ORDER BY in a recursive query is not implemented +LINE 1: ...VE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1) + ^ +-- LIMIT/OFFSET +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1) + SELECT * FROM x; +ERROR: OFFSET in a recursive query is not implemented +LINE 1: ... AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1) + ^ +-- FOR UPDATE +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x FOR UPDATE) + SELECT * FROM x; +ERROR: FOR UPDATE/SHARE in a recursive query is not implemented +-- target list has a recursive query name +WITH RECURSIVE x(id) AS (values (1) + UNION ALL + SELECT (SELECT * FROM x) FROM x WHERE id < 5 +) SELECT * FROM x; +ERROR: recursive reference to query "x" must not appear within a subquery +LINE 3: SELECT (SELECT * FROM x) FROM x WHERE id < 5 + ^ +-- mutual recursive query (not implemented) +WITH RECURSIVE + x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id < 5), + y (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 5) +SELECT * FROM x; +ERROR: mutual recursion between WITH items is not implemented +LINE 2: x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id ... + ^ +-- non-linear recursion is not allowed +WITH RECURSIVE foo(i) AS + (values (1) + UNION ALL + (SELECT i+1 FROM foo WHERE i < 10 + UNION ALL + SELECT i+1 FROM foo WHERE i < 5) +) SELECT * FROM foo; +ERROR: recursive reference to query "foo" must not appear more than once +LINE 6: SELECT i+1 FROM foo WHERE i < 5) + ^ +WITH RECURSIVE foo(i) AS + (values (1) + UNION ALL + SELECT * FROM + (SELECT i+1 FROM foo WHERE i < 10 + UNION ALL + SELECT i+1 FROM foo WHERE i < 5) AS t +) SELECT * FROM foo; +ERROR: recursive reference to query "foo" must not appear more than once +LINE 7: SELECT i+1 FROM foo WHERE i < 5) AS t + ^ +WITH RECURSIVE foo(i) AS + (values (1) + UNION ALL + (SELECT i+1 FROM foo WHERE i < 10 + EXCEPT + SELECT i+1 FROM foo WHERE i < 5) +) SELECT * FROM foo; +ERROR: recursive reference to query "foo" must not appear within EXCEPT +LINE 6: SELECT i+1 FROM foo WHERE i < 5) + ^ +WITH RECURSIVE foo(i) AS + (values (1) + UNION ALL + (SELECT i+1 FROM foo WHERE i < 10 + INTERSECT + SELECT i+1 FROM foo WHERE i < 5) +) SELECT * FROM foo; +ERROR: recursive reference to query "foo" must not appear more than once +LINE 6: SELECT i+1 FROM foo WHERE i < 5) + ^ +-- Wrong type induced from non-recursive term +WITH RECURSIVE foo(i) AS + (SELECT i FROM (VALUES(1),(2)) t(i) + UNION ALL + SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10) +SELECT * FROM foo; +ERROR: recursive query "foo" column 1 has type integer in non-recursive term but type numeric overall +LINE 2: (SELECT i FROM (VALUES(1),(2)) t(i) + ^ +HINT: Cast the output of the non-recursive term to the correct type. +-- rejects different typmod, too (should we allow this?) +WITH RECURSIVE foo(i) AS + (SELECT i::numeric(3,0) FROM (VALUES(1),(2)) t(i) + UNION ALL + SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10) +SELECT * FROM foo; +ERROR: recursive query "foo" column 1 has type numeric(3,0) in non-recursive term but type numeric overall +LINE 2: (SELECT i::numeric(3,0) FROM (VALUES(1),(2)) t(i) + ^ +HINT: Cast the output of the non-recursive term to the correct type. +-- disallow OLD/NEW reference in CTE +CREATE TEMPORARY TABLE x (n integer); +CREATE RULE r2 AS ON UPDATE TO x DO INSTEAD + WITH t AS (SELECT OLD.*) UPDATE y SET a = t.n FROM t; +ERROR: cannot refer to OLD within WITH query +-- +-- test for bug #4902 +-- +with cte(foo) as ( values(42) ) values((select foo from cte)); + column1 +--------- + 42 +(1 row) + +with cte(foo) as ( select 42 ) select * from ((select foo from cte)) q; + foo +----- + 42 +(1 row) + +-- test CTE referencing an outer-level variable (to see that changed-parameter +-- signaling still works properly after fixing this bug) +SELECT * FROM ( -- YB +select ( with cte(foo) as ( values(f1) ) + select (select foo from cte) ) +from int4_tbl +LIMIT ALL) ybview ORDER BY (abs(foo) - sign(foo)); -- YB ordering + foo +------------- + 0 + 123456 + -123456 + 2147483647 + -2147483647 +(5 rows) + +SELECT * FROM ( -- YB +select ( with cte(foo) as ( values(f1) ) + values((select foo from cte)) ) +from int4_tbl +LIMIT ALL) ybview ORDER BY (abs(column1) - sign(column1)); -- YB ordering + column1 +------------- + 0 + 123456 + -123456 + 2147483647 + -2147483647 +(5 rows) + +-- +-- test for nested-recursive-WITH bug +-- +WITH RECURSIVE t(j) AS ( + WITH RECURSIVE s(i) AS ( + VALUES (1) + UNION ALL + SELECT i+1 FROM s WHERE i < 10 + ) + SELECT i FROM s + UNION ALL + SELECT j+1 FROM t WHERE j < 10 +) +SELECT * FROM t; + j +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 5 + 6 + 7 + 8 + 9 + 10 + 6 + 7 + 8 + 9 + 10 + 7 + 8 + 9 + 10 + 8 + 9 + 10 + 9 + 10 + 10 +(55 rows) + +-- +-- test WITH attached to intermediate-level set operation +-- +WITH outermost(x) AS ( + SELECT 1 + UNION (WITH innermost as (SELECT 2) + SELECT * FROM innermost + UNION SELECT 3) +) +SELECT * FROM outermost ORDER BY 1; + x +--- + 1 + 2 + 3 +(3 rows) + +WITH outermost(x) AS ( + SELECT 1 + UNION (WITH innermost as (SELECT 2) + SELECT * FROM outermost -- fail + UNION SELECT * FROM innermost) +) +SELECT * FROM outermost ORDER BY 1; +ERROR: relation "outermost" does not exist +LINE 4: SELECT * FROM outermost -- fail + ^ +DETAIL: There is a WITH item named "outermost", but it cannot be referenced from this part of the query. +HINT: Use WITH RECURSIVE, or re-order the WITH items to remove forward references. +WITH RECURSIVE outermost(x) AS ( + SELECT 1 + UNION (WITH innermost as (SELECT 2) + SELECT * FROM outermost + UNION SELECT * FROM innermost) +) +SELECT * FROM outermost ORDER BY 1; + x +--- + 1 + 2 +(2 rows) + +WITH RECURSIVE outermost(x) AS ( + WITH innermost as (SELECT 2 FROM outermost) -- fail + SELECT * FROM innermost + UNION SELECT * from outermost +) +SELECT * FROM outermost ORDER BY 1; +ERROR: recursive reference to query "outermost" must not appear within a subquery +LINE 2: WITH innermost as (SELECT 2 FROM outermost) -- fail + ^ +-- +-- This test will fail with the old implementation of PARAM_EXEC parameter +-- assignment, because the "q1" Var passed down to A's targetlist subselect +-- looks exactly like the "A.id" Var passed down to C's subselect, causing +-- the old code to give them the same runtime PARAM_EXEC slot. But the +-- lifespans of the two parameters overlap, thanks to B also reading A. +-- +with +A as ( select q2 as id, (select q1) as x from int8_tbl ), +B as ( select id, row_number() over (partition by id) as r from A ), +C as ( select A.id, array(select B.id from B where B.id = A.id) from A ) +select * from C; + id | array +-------------------+------------------------------------- + 456 | {456} + 4567890123456789 | {4567890123456789,4567890123456789} + 123 | {123} + 4567890123456789 | {4567890123456789,4567890123456789} + -4567890123456789 | {-4567890123456789} +(5 rows) + +-- +-- Test CTEs read in non-initialization orders +-- +WITH RECURSIVE + tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)), + iter (id_key, row_type, link) AS ( + SELECT 0, 'base', 17 + UNION ALL ( + WITH remaining(id_key, row_type, link, min) AS ( + SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER () + FROM tab INNER JOIN iter USING (link) + WHERE tab.id_key > iter.id_key + ), + first_remaining AS ( + SELECT id_key, row_type, link + FROM remaining + WHERE id_key=min + ), + effect AS ( + SELECT tab.id_key, 'new'::text, tab.link + FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key + WHERE e.row_type = 'false' + ) + SELECT * FROM first_remaining + UNION ALL SELECT * FROM effect + ) + ) +SELECT * FROM iter; + id_key | row_type | link +--------+----------+------ + 0 | base | 17 + 1 | true | 17 + 2 | true | 17 + 3 | true | 17 + 4 | true | 17 + 5 | true | 17 + 6 | true | 17 +(7 rows) + +WITH RECURSIVE + tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)), + iter (id_key, row_type, link) AS ( + SELECT 0, 'base', 17 + UNION ( + WITH remaining(id_key, row_type, link, min) AS ( + SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER () + FROM tab INNER JOIN iter USING (link) + WHERE tab.id_key > iter.id_key + ), + first_remaining AS ( + SELECT id_key, row_type, link + FROM remaining + WHERE id_key=min + ), + effect AS ( + SELECT tab.id_key, 'new'::text, tab.link + FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key + WHERE e.row_type = 'false' + ) + SELECT * FROM first_remaining + UNION ALL SELECT * FROM effect + ) + ) +SELECT * FROM iter; + id_key | row_type | link +--------+----------+------ + 0 | base | 17 + 1 | true | 17 + 2 | true | 17 + 3 | true | 17 + 4 | true | 17 + 5 | true | 17 + 6 | true | 17 +(7 rows) + +-- +-- Data-modifying statements in WITH +-- +-- INSERT ... RETURNING +WITH t AS ( + INSERT INTO y + VALUES + (11), + (12), + (13), + (14), + (15), + (16), + (17), + (18), + (19), + (20) + RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column + a +---- + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 +(10 rows) + +SELECT a FROM y; -- YB: avoid ybsort column + a +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 +(20 rows) + +-- UPDATE ... RETURNING +WITH t AS ( + UPDATE y + SET a=a+1 + RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column + a +---- + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 +(20 rows) + +SELECT a FROM y; -- YB: avoid ybsort column + a +---- + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 +(20 rows) + +-- DELETE ... RETURNING +WITH t AS ( + DELETE FROM y + WHERE a <= 10 + RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column + a +---- + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(9 rows) + +SELECT a FROM y; -- YB: avoid ybsort column + a +---- + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 +(11 rows) + +-- forward reference +WITH RECURSIVE t AS ( + INSERT INTO y + SELECT a+5 FROM t2 WHERE a > 5 + RETURNING * +), t2 AS ( + UPDATE y SET a=a-11, ybsort=nextval('y_ybsort_seq') RETURNING * -- YB: mimic upstream PG's new ctid allocation +) +SELECT a FROM t -- YB: avoid ybsort column +UNION ALL +SELECT a FROM t2; -- YB: avoid ybsort column + a +---- + 11 + 12 + 13 + 14 + 15 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(16 rows) + +SELECT a FROM y; -- YB: avoid ybsort column + a +---- + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 11 + 7 + 12 + 8 + 13 + 9 + 14 + 10 + 15 +(16 rows) + +-- unconditional DO INSTEAD rule +CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD + INSERT INTO y VALUES(42) RETURNING *; +WITH t AS ( + DELETE FROM y RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column + a +---- + 42 +(1 row) + +SELECT a FROM y; -- YB: avoid ybsort column + a +---- + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 11 + 7 + 12 + 8 + 13 + 9 + 14 + 10 + 15 + 42 +(17 rows) + +DROP RULE y_rule ON y; +-- check merging of outer CTE with CTE in a rule action +CREATE TEMP TABLE bug6051 AS + select i from generate_series(1,3) as t(i); +SELECT * FROM bug6051; + i +--- + 1 + 2 + 3 +(3 rows) + +WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) +INSERT INTO bug6051 SELECT * FROM t1; +SELECT * FROM bug6051; + i +--- + 1 + 2 + 3 +(3 rows) + +CREATE TEMP TABLE bug6051_2 (i int); +CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD + INSERT INTO bug6051_2 + VALUES(NEW.i); +WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) +INSERT INTO bug6051 SELECT * FROM t1; +SELECT * FROM bug6051; + i +--- +(0 rows) + +SELECT * FROM bug6051_2; + i +--- + 1 + 2 + 3 +(3 rows) + +-- check INSERT...SELECT rule actions are disallowed on commands +-- that have modifyingCTEs +CREATE OR REPLACE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD + INSERT INTO bug6051_2 + SELECT NEW.i; +WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) +INSERT INTO bug6051 SELECT * FROM t1; +ERROR: INSERT...SELECT rule actions are not supported for queries having data-modifying statements in WITH +-- silly example to verify that hasModifyingCTE flag is propagated +CREATE TEMP TABLE bug6051_3 AS + SELECT a FROM generate_series(11,13) AS a; +CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD + SELECT i FROM bug6051_2; +BEGIN; SET LOCAL force_parallel_mode = on; +WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * ) + INSERT INTO bug6051_3 SELECT * FROM t1; + i +--- + 1 + 2 + 3 + 1 + 2 + 3 + 1 + 2 + 3 +(9 rows) + +COMMIT; +SELECT * FROM bug6051_3; + a +--- +(0 rows) + +-- check case where CTE reference is removed due to optimization +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q1 FROM +( + WITH t_cte AS (SELECT * FROM int8_tbl t) + SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub + FROM int8_tbl i8 +) ss; + QUERY PLAN +-------------------------------------- + Subquery Scan on ss + Output: ss.q1 + -> Seq Scan on public.int8_tbl i8 + Output: i8.q1, NULL::bigint +(4 rows) + +SELECT q1 FROM +( + WITH t_cte AS (SELECT * FROM int8_tbl t) + SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub + FROM int8_tbl i8 +) ss; + q1 +------------------ + 123 + 123 + 4567890123456789 + 4567890123456789 + 4567890123456789 +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q1 FROM +( + WITH t_cte AS MATERIALIZED (SELECT q1, q2 FROM int8_tbl t) -- YB: avoid ybsort column + SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub + FROM int8_tbl i8 +) ss; + QUERY PLAN +--------------------------------------------- + Subquery Scan on ss + Output: ss.q1 + -> Seq Scan on public.int8_tbl i8 + Output: i8.q1, NULL::bigint + CTE t_cte + -> Seq Scan on public.int8_tbl t + Output: t.q1, t.q2 +(7 rows) + +SELECT q1 FROM +( + WITH t_cte AS MATERIALIZED (SELECT * FROM int8_tbl t) + SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub + FROM int8_tbl i8 +) ss; + q1 +------------------ + 123 + 123 + 4567890123456789 + 4567890123456789 + 4567890123456789 +(5 rows) + +-- a truly recursive CTE in the same list +WITH RECURSIVE t(a) AS ( + SELECT 0 + UNION ALL + SELECT a+1 FROM t WHERE a+1 < 5 +), t2 as ( + INSERT INTO y + SELECT * FROM t RETURNING * +) +SELECT a FROM t2 JOIN y USING (a) ORDER BY a; -- YB: avoid ybsort column + a +--- + 0 + 1 + 2 + 3 + 4 +(5 rows) + +SELECT a FROM y; -- YB: avoid ybsort column + a +---- + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 11 + 7 + 12 + 8 + 13 + 9 + 14 + 10 + 15 + 42 + 0 + 1 + 2 + 3 + 4 +(22 rows) + +-- data-modifying WITH in a modifying statement +WITH t AS ( + DELETE FROM y + WHERE a <= 10 + RETURNING * +) +INSERT INTO y SELECT -a FROM t RETURNING a; -- YB: avoid ybsort column + a +----- + 0 + -1 + -2 + -3 + -4 + -5 + -6 + -7 + -8 + -9 + -10 + 0 + -1 + -2 + -3 + -4 +(16 rows) + +SELECT a FROM y; -- YB: avoid ybsort column + a +----- + 11 + 12 + 13 + 14 + 15 + 42 + 0 + -1 + -2 + -3 + -4 + -5 + -6 + -7 + -8 + -9 + -10 + 0 + -1 + -2 + -3 + -4 +(22 rows) + +-- check that WITH query is run to completion even if outer query isn't +WITH t AS ( + UPDATE y SET a = a * 100 RETURNING * +) +SELECT a FROM t LIMIT 10; -- YB: avoid ybsort column + a +------ + 1100 + 1200 + 1300 + 1400 + 1500 + 4200 + 0 + -100 + -200 + -300 +(10 rows) + +SELECT a FROM y; -- YB: avoid ybsort column + a +------- + 1100 + 1200 + 1300 + 1400 + 1500 + 4200 + 0 + -100 + -200 + -300 + -400 + -500 + -600 + -700 + -800 + -900 + -1000 + 0 + -100 + -200 + -300 + -400 +(22 rows) + +-- data-modifying WITH containing INSERT...ON CONFLICT DO UPDATE +CREATE TABLE withz AS SELECT i AS k, (i || ' v')::text v FROM generate_series(1, 16, 3) i; +ALTER TABLE withz ADD UNIQUE (k); +WITH t AS ( + INSERT INTO withz SELECT i, 'insert' + FROM generate_series(0, 16) i + ON CONFLICT (k) DO UPDATE SET v = withz.v || ', now update' + RETURNING * +) +SELECT k, v, a FROM t JOIN y ON t.k = y.a ORDER BY a, k; -- YB: avoid ybsort column + k | v | a +---+--------+--- + 0 | insert | 0 + 0 | insert | 0 +(2 rows) + +-- Test EXCLUDED.* reference within CTE +WITH aa AS ( + INSERT INTO withz VALUES(1, 5) ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v + WHERE withz.k != EXCLUDED.k + RETURNING * +) +SELECT * FROM aa; + k | v +---+--- +(0 rows) + +-- New query/snapshot demonstrates side-effects of previous query. +SELECT * FROM withz ORDER BY k; + k | v +----+------------------ + 0 | insert + 1 | 1 v, now update + 2 | insert + 3 | insert + 4 | 4 v, now update + 5 | insert + 6 | insert + 7 | 7 v, now update + 8 | insert + 9 | insert + 10 | 10 v, now update + 11 | insert + 12 | insert + 13 | 13 v, now update + 14 | insert + 15 | insert + 16 | 16 v, now update +(17 rows) + +-- +-- Ensure subqueries within the update clause work, even if they +-- reference outside values +-- +WITH aa AS (SELECT 1 a, 2 b) +INSERT INTO withz VALUES(1, 'insert') +ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1); +WITH aa AS (SELECT 1 a, 2 b) +INSERT INTO withz VALUES(1, 'insert') +ON CONFLICT (k) DO UPDATE SET v = ' update' WHERE withz.k = (SELECT a FROM aa); +WITH aa AS (SELECT 1 a, 2 b) +INSERT INTO withz VALUES(1, 'insert') +ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1); +WITH aa AS (SELECT 'a' a, 'b' b UNION ALL SELECT 'a' a, 'b' b) +INSERT INTO withz VALUES(1, 'insert') +ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 'a' LIMIT 1); +WITH aa AS (SELECT 1 a, 2 b) +INSERT INTO withz VALUES(1, (SELECT b || ' insert' FROM aa WHERE a = 1 )) +ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1); +-- Update a row more than once, in different parts of a wCTE. That is +-- an allowed, presumably very rare, edge case, but since it was +-- broken in the past, having a test seems worthwhile. +WITH simpletup AS ( + SELECT 2 k, 'Green' v), +upsert_cte AS ( + INSERT INTO withz VALUES(2, 'Blue') ON CONFLICT (k) DO + UPDATE SET (k, v) = (SELECT k, v FROM simpletup WHERE simpletup.k = withz.k) + RETURNING k, v) +INSERT INTO withz VALUES(2, 'Red') ON CONFLICT (k) DO +UPDATE SET (k, v) = (SELECT k, v FROM upsert_cte WHERE upsert_cte.k = withz.k) +RETURNING k, v; -- YB: output is different from PG (see #6782) + k | v +---+------- + 2 | Green +(1 row) + +DROP TABLE withz; +-- WITH referenced by MERGE statement +CREATE TABLE m AS SELECT i AS k, (i || ' v')::text v FROM generate_series(1, 16, 3) i; +ALTER TABLE m ADD UNIQUE (k); +WITH RECURSIVE cte_basic AS (SELECT 1 a, 'cte_basic val' b) +MERGE INTO m USING (select 0 k, 'merge source SubPlan' v) o ON m.k=o.k +WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1) +WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); +ERROR: This statement not supported yet +LINE 1: WITH RECURSIVE cte_basic AS (SELECT 1 a, 'cte_basic val' b) + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +-- Basic: +WITH cte_basic AS MATERIALIZED (SELECT 1 a, 'cte_basic val' b) +MERGE INTO m USING (select 0 k, 'merge source SubPlan' v offset 0) o ON m.k=o.k +WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1) +WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); -- YB: port more queries from the original test once this query is fixed +ERROR: This statement not supported yet +LINE 1: WITH cte_basic AS MATERIALIZED (SELECT 1 a, 'cte_basic val' ... + ^ +HINT: Please report the issue on https://github.com/YugaByte/yugabyte-db/issues +DROP TABLE m; +-- check that run to completion happens in proper ordering +TRUNCATE TABLE y; +INSERT INTO y SELECT generate_series(1, 3); +CREATE TABLE yy (a INTEGER, ybsort INT); -- YB: exercise YB table +ALTER TABLE y DROP CONSTRAINT y_pkey; -- YB: ybsort column needs to be non-unique for the next queries +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +WITH RECURSIVE t1 AS ( + INSERT INTO y SELECT a FROM y ORDER BY y.ybsort RETURNING * -- YB: add ordering; avoid selecting ybsort column to auto-generate it during insert +), t2 AS ( + INSERT INTO yy SELECT * FROM t1 RETURNING * +) +SELECT 1; + ?column? +---------- + 1 +(1 row) + +SELECT a FROM y ORDER BY ybsort; -- YB: avoid ybsort column; add ordering + a +--- + 1 + 2 + 3 + 1 + 2 + 3 +(6 rows) + +SELECT a FROM yy ORDER BY ybsort; -- YB: avoid ybsort column; add ordering + a +--- + 1 + 2 + 3 +(3 rows) + +WITH RECURSIVE t1 AS ( + INSERT INTO yy SELECT * FROM t2 RETURNING * +), t2 AS ( + INSERT INTO y SELECT a FROM y ORDER BY y.ybsort RETURNING * -- YB: add ordering; avoid selecting ybsort column to auto-generate it during insert +) +SELECT 1; + ?column? +---------- + 1 +(1 row) + +SELECT a FROM y ORDER BY ybsort; -- YB: avoid ybsort column; add ordering + a +--- + 1 + 2 + 3 + 1 + 2 + 3 + 1 + 2 + 3 + 1 + 2 + 3 +(12 rows) + +SELECT a FROM yy ORDER BY ybsort; -- YB: avoid ybsort column; add ordering + a +--- + 1 + 2 + 3 + 1 + 2 + 3 + 1 + 2 + 3 +(9 rows) + +-- triggers +TRUNCATE TABLE y; +ALTER TABLE y ADD PRIMARY KEY (ybsort ASC); -- YB: ybsort column can be unique again +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +INSERT INTO y SELECT generate_series(1, 10); +CREATE FUNCTION y_trigger() RETURNS trigger AS $$ +begin + raise notice 'y_trigger: a = %', new.a; + return new; +end; +$$ LANGUAGE plpgsql; +CREATE TRIGGER y_trig BEFORE INSERT ON y FOR EACH ROW + EXECUTE PROCEDURE y_trigger(); +WITH t AS ( + INSERT INTO y + VALUES + (21), + (22), + (23) + RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column +NOTICE: y_trigger: a = 21 +NOTICE: y_trigger: a = 22 +NOTICE: y_trigger: a = 23 + a +---- + 21 + 22 + 23 +(3 rows) + +SELECT a FROM y; -- YB: avoid ybsort column + a +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 21 + 22 + 23 +(13 rows) + +DROP TRIGGER y_trig ON y; +CREATE TRIGGER y_trig AFTER INSERT ON y FOR EACH ROW + EXECUTE PROCEDURE y_trigger(); +WITH t AS ( + INSERT INTO y + VALUES + (31), + (32), + (33) + RETURNING * +) +SELECT a FROM t LIMIT 1; -- YB: avoid ybsort column +NOTICE: y_trigger: a = 31 +NOTICE: y_trigger: a = 32 +NOTICE: y_trigger: a = 33 + a +---- + 31 +(1 row) + +SELECT a FROM y; -- YB: avoid ybsort column + a +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 21 + 22 + 23 + 31 + 32 + 33 +(16 rows) + +DROP TRIGGER y_trig ON y; +CREATE OR REPLACE FUNCTION y_trigger() RETURNS trigger AS $$ +begin + raise notice 'y_trigger'; + return null; +end; +$$ LANGUAGE plpgsql; +CREATE TRIGGER y_trig AFTER INSERT ON y FOR EACH STATEMENT + EXECUTE PROCEDURE y_trigger(); +WITH t AS ( + INSERT INTO y + VALUES + (41), + (42), + (43) + RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column +NOTICE: y_trigger + a +---- + 41 + 42 + 43 +(3 rows) + +SELECT a FROM y; -- YB: avoid ybsort column + a +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 21 + 22 + 23 + 31 + 32 + 33 + 41 + 42 + 43 +(19 rows) + +DROP TRIGGER y_trig ON y; +DROP FUNCTION y_trigger(); +-- WITH attached to inherited UPDATE or DELETE +CREATE TEMP TABLE parent ( id int, val text ); +CREATE TEMP TABLE child1 ( ) INHERITS ( parent ); +CREATE TEMP TABLE child2 ( ) INHERITS ( parent ); +INSERT INTO parent VALUES ( 1, 'p1' ); +INSERT INTO child1 VALUES ( 11, 'c11' ),( 12, 'c12' ); +INSERT INTO child2 VALUES ( 23, 'c21' ),( 24, 'c22' ); +WITH rcte AS ( SELECT sum(id) AS totalid FROM parent ) +UPDATE parent SET id = id + totalid FROM rcte; +SELECT * FROM parent; + id | val +----+----- + 72 | p1 + 82 | c11 + 83 | c12 + 94 | c21 + 95 | c22 +(5 rows) + +WITH wcte AS ( INSERT INTO child1 VALUES ( 42, 'new' ) RETURNING id AS newid ) +UPDATE parent SET id = id + newid FROM wcte; +SELECT * FROM parent; + id | val +-----+----- + 114 | p1 + 42 | new + 124 | c11 + 125 | c12 + 136 | c21 + 137 | c22 +(6 rows) + +WITH rcte AS ( SELECT max(id) AS maxid FROM parent ) +DELETE FROM parent USING rcte WHERE id = maxid; +SELECT * FROM parent; + id | val +-----+----- + 114 | p1 + 42 | new + 124 | c11 + 125 | c12 + 136 | c21 +(5 rows) + +WITH wcte AS ( INSERT INTO child2 VALUES ( 42, 'new2' ) RETURNING id AS newid ) +DELETE FROM parent USING wcte WHERE id = newid; +SELECT * FROM parent; + id | val +-----+------ + 114 | p1 + 124 | c11 + 125 | c12 + 136 | c21 + 42 | new2 +(5 rows) + +-- check EXPLAIN VERBOSE for a wCTE with RETURNING +EXPLAIN (VERBOSE, COSTS OFF) +WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 ) +DELETE FROM a_star USING wcte WHERE aa = q2; + QUERY PLAN +----------------------------------------------------------------------------- + Delete on public.a_star + Delete on public.a_star a_star_1 + Delete on public.b_star a_star_2 + Delete on public.c_star a_star_3 + Delete on public.d_star a_star_4 + Delete on public.e_star a_star_5 + Delete on public.f_star a_star_6 + CTE wcte + -> Insert on public.int8_tbl + Output: int8_tbl.q2 + -> Result *RESULT* + Output: '42'::bigint, '47'::bigint + -> Hash Join + Output: wcte.*, a_star.tableoid, a_star.ybctid + Hash Cond: (a_star.aa = wcte.q2) + -> Append + -> Seq Scan on public.a_star a_star_1 + Output: a_star_1.aa, a_star_1.tableoid, a_star_1.ybctid + -> Seq Scan on public.b_star a_star_2 + Output: a_star_2.aa, a_star_2.tableoid, a_star_2.ybctid + -> Seq Scan on public.c_star a_star_3 + Output: a_star_3.aa, a_star_3.tableoid, a_star_3.ybctid + -> Seq Scan on public.d_star a_star_4 + Output: a_star_4.aa, a_star_4.tableoid, a_star_4.ybctid + -> Seq Scan on public.e_star a_star_5 + Output: a_star_5.aa, a_star_5.tableoid, a_star_5.ybctid + -> Seq Scan on public.f_star a_star_6 + Output: a_star_6.aa, a_star_6.tableoid, a_star_6.ybctid + -> Hash + Output: wcte.*, wcte.q2 + -> CTE Scan on wcte + Output: wcte.*, wcte.q2 +(32 rows) + +-- error cases +-- data-modifying WITH tries to use its own output +WITH RECURSIVE t AS ( + INSERT INTO y + SELECT * FROM t +) +VALUES(FALSE); -- YB: it's unclear why the line number in output is different from upstream. +ERROR: recursive query "t" must not contain data-modifying statements +LINE 1: WITH RECURSIVE t AS ( + ^ +-- no RETURNING in a referenced data-modifying WITH +WITH t AS ( + INSERT INTO y VALUES(0) +) +SELECT * FROM t; +ERROR: WITH query "t" does not have a RETURNING clause +LINE 4: SELECT * FROM t; + ^ +-- data-modifying WITH allowed only at the top level +SELECT * FROM ( + WITH t AS (UPDATE y SET a=a+1 RETURNING *) + SELECT * FROM t +) ss; +ERROR: WITH clause containing a data-modifying statement must be at the top level +LINE 2: WITH t AS (UPDATE y SET a=a+1 RETURNING *) + ^ +-- most variants of rules aren't allowed +CREATE RULE y_rule AS ON INSERT TO y WHERE a=0 DO INSTEAD DELETE FROM y; +WITH t AS ( + INSERT INTO y VALUES(0) +) +VALUES(FALSE); +ERROR: conditional DO INSTEAD rules are not supported for data-modifying statements in WITH +CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTHING; +WITH t AS ( + INSERT INTO y VALUES(0) +) +VALUES(FALSE); +ERROR: DO INSTEAD NOTHING rules are not supported for data-modifying statements in WITH +CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTIFY foo; +WITH t AS ( + INSERT INTO y VALUES(0) +) +VALUES(FALSE); +ERROR: DO INSTEAD NOTIFY rules are not supported for data-modifying statements in WITH +CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO ALSO NOTIFY foo; +WITH t AS ( + INSERT INTO y VALUES(0) +) +VALUES(FALSE); +ERROR: DO ALSO rules are not supported for data-modifying statements in WITH +CREATE OR REPLACE RULE y_rule AS ON INSERT TO y + DO INSTEAD (NOTIFY foo; NOTIFY bar); +WITH t AS ( + INSERT INTO y VALUES(0) +) +VALUES(FALSE); +ERROR: multi-statement DO INSTEAD rules are not supported for data-modifying statements in WITH +DROP RULE y_rule ON y; +-- check that parser lookahead for WITH doesn't cause any odd behavior +create table foo (with baz); -- fail, WITH is a reserved word +ERROR: syntax error at or near "with" +LINE 1: create table foo (with baz); + ^ +create table foo (with ordinality); -- fail, WITH is a reserved word +ERROR: syntax error at or near "with" +LINE 1: create table foo (with ordinality); + ^ +with ordinality as (select 1 as x) select * from ordinality; + x +--- + 1 +(1 row) + +-- check sane response to attempt to modify CTE relation +WITH with_test AS (SELECT 42) INSERT INTO with_test VALUES (1); +ERROR: relation "with_test" does not exist +LINE 1: WITH with_test AS (SELECT 42) INSERT INTO with_test VALUES (... + ^ +-- check response to attempt to modify table with same name as a CTE (perhaps +-- surprisingly it works, because CTEs don't hide tables from data-modifying +-- statements) +create temp table with_test (i int); +with with_test as (select 42) insert into with_test select * from with_test; +select * from with_test; + i +---- + 42 +(1 row) + +drop table with_test; diff --git a/src/test/regress/expected/yb.port.xmlmap.out b/src/test/regress/expected/yb.port.xmlmap.out new file mode 100644 index 0000000000000..5273ddf07a04e --- /dev/null +++ b/src/test/regress/expected/yb.port.xmlmap.out @@ -0,0 +1,1294 @@ +CREATE SCHEMA testxmlschema; +CREATE TABLE testxmlschema.test1 (a int, b text); +INSERT INTO testxmlschema.test1 VALUES (1, 'one'), (2, 'two'), (-1, null); +CREATE DOMAIN testxmldomain AS varchar; +CREATE TABLE testxmlschema.test2 (z int, y varchar(500), x char(6), + w numeric(9,2), v smallint, u bigint, t real, + s time, stz timetz, r timestamp, rtz timestamptz, q date, + p xml, o testxmldomain, n bool, m bytea, aaa text); +ALTER TABLE testxmlschema.test2 DROP COLUMN aaa; +INSERT INTO testxmlschema.test2 VALUES (55, 'abc', 'def', + 98.6, 2, 999, 0, + '21:07', '21:11 +05', '2009-06-08 21:07:30', '2009-06-08 21:07:30 -07', '2009-06-08', + NULL, 'ABC', true, 'XYZ'); +SELECT table_to_xml('testxmlschema.test1', false, false, ''); + table_to_xml +--------------------------------------------------------------- + + + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + + + +(1 row) + +SELECT table_to_xml('testxmlschema.test1', true, false, 'foo'); + table_to_xml +--------------------------------------------------------------------------- + + + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + + + + + +(1 row) + +SELECT table_to_xml('testxmlschema.test1', false, true, ''); + table_to_xml +--------------------------------------------------------------- + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + +(1 row) + +SELECT table_to_xml('testxmlschema.test1', true, true, ''); + table_to_xml +--------------------------------------------------------------- + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + + + +(1 row) + +SELECT table_to_xml('testxmlschema.test2', false, false, ''); + table_to_xml +--------------------------------------------------------------- + + + + + + + 55 + + abc + + def + + 98.60 + + 2 + + 999 + + 0 + + 21:07:00 + + 21:11:00+05 + + 2009-06-08T21:07:30 + + 2009-06-08T21:07:30-07:00 + + 2009-06-08 + + ABC + + true + + WFla + + + + + + + + +(1 row) + +SELECT table_to_xmlschema('testxmlschema.test1', false, false, ''); + table_to_xmlschema +--------------------------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +(1 row) + +SELECT table_to_xmlschema('testxmlschema.test1', true, false, ''); + table_to_xmlschema +--------------------------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +(1 row) + +SELECT table_to_xmlschema('testxmlschema.test1', false, true, 'foo'); + table_to_xmlschema +-------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +(1 row) + +SELECT table_to_xmlschema('testxmlschema.test1', true, true, ''); + table_to_xmlschema +---------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +(1 row) + +SELECT table_to_xmlschema('testxmlschema.test2', false, false, ''); + table_to_xmlschema +---------------------------------------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +(1 row) + +SELECT table_to_xml_and_xmlschema('testxmlschema.test1', false, false, ''); + table_to_xml_and_xmlschema +--------------------------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + + + +(1 row) + +SELECT table_to_xml_and_xmlschema('testxmlschema.test1', true, false, ''); + table_to_xml_and_xmlschema +--------------------------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + + + + + +(1 row) + +SELECT table_to_xml_and_xmlschema('testxmlschema.test1', false, true, ''); + table_to_xml_and_xmlschema +-------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + +(1 row) + +SELECT table_to_xml_and_xmlschema('testxmlschema.test1', true, true, 'foo'); + table_to_xml_and_xmlschema +---------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + + + +(1 row) + +SELECT query_to_xml('SELECT * FROM testxmlschema.test1', false, false, ''); + query_to_xml +--------------------------------------------------------------- +
+ + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + +
+ + +(1 row) + +SELECT query_to_xmlschema('SELECT * FROM testxmlschema.test1', false, false, ''); + query_to_xmlschema +-------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +(1 row) + +SELECT query_to_xml_and_xmlschema('SELECT * FROM testxmlschema.test1', true, true, ''); + query_to_xml_and_xmlschema +---------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + + + +(1 row) + +DECLARE xc CURSOR WITH HOLD FOR SELECT * FROM testxmlschema.test1 ORDER BY 1, 2; +SELECT cursor_to_xml('xc'::refcursor, 5, false, true, ''); + cursor_to_xml +------------------------------------------------------------- + + + -1 + + + + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + +(1 row) + +SELECT cursor_to_xmlschema('xc'::refcursor, false, true, ''); + cursor_to_xmlschema +-------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +(1 row) + +MOVE BACKWARD ALL IN xc; +ERROR: FETCH BACKWARD not supported yet +LINE 1: MOVE BACKWARD ALL IN xc; + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/6514. React with thumbs up to raise its priority +SELECT cursor_to_xml('xc'::refcursor, 5, true, false, ''); + cursor_to_xml +--------------------------------------------------------------- + + + + +
+ + +(1 row) + +SELECT cursor_to_xmlschema('xc'::refcursor, true, false, ''); + cursor_to_xmlschema +---------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +(1 row) + +SELECT schema_to_xml('testxmlschema', false, true, ''); + schema_to_xml +----------------------------------------------------------------------- + + + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + + + + + 55 + + abc + + def + + 98.60 + + 2 + + 999 + + 0 + + 21:07:00 + + 21:11:00+05 + + 2009-06-08T21:07:30 + + 2009-06-08T21:07:30-07:00 + + 2009-06-08 + + ABC + + true + + WFla + + + + + + + + + + +(1 row) + +SELECT schema_to_xml('testxmlschema', true, false, ''); + schema_to_xml +----------------------------------------------------------------------- + + + + + + + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + + + + + + + + + + + + + 55 + + abc + + def + + 98.60 + + 2 + + 999 + + 0 + + 21:07:00 + + 21:11:00+05 + + 2009-06-08T21:07:30 + + 2009-06-08T21:07:30-07:00 + + 2009-06-08 + +

+ + ABC + + true + + WFla + + + + + + + + + + + + +(1 row) + +SELECT schema_to_xmlschema('testxmlschema', false, true, ''); + schema_to_xmlschema +---------------------------------------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +(1 row) + +SELECT schema_to_xmlschema('testxmlschema', true, false, ''); + schema_to_xmlschema +---------------------------------------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +(1 row) + +SELECT schema_to_xml_and_xmlschema('testxmlschema', true, true, 'foo'); + schema_to_xml_and_xmlschema +---------------------------------------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + one + + + + + + + + 2 + + two + + + + + + + + -1 + + + + + + + + + + + + 55 + + abc + + def + + 98.60 + + 2 + + 999 + + 0 + + 21:07:00 + + 21:11:00+05 + + 2009-06-08T21:07:30 + + 2009-06-08T21:07:30-07:00 + + 2009-06-08 + +

+ + ABC + + true + + WFla + + + + + + + + + + +(1 row) + +-- test that domains are transformed like their base types +CREATE DOMAIN testboolxmldomain AS bool; +CREATE DOMAIN testdatexmldomain AS date; +CREATE TABLE testxmlschema.test3 + AS SELECT true c1, + true::testboolxmldomain c2, + '2013-02-21'::date c3, + '2013-02-21'::testdatexmldomain c4; +SELECT xmlforest(c1, c2, c3, c4) FROM testxmlschema.test3; + xmlforest +------------------------------------------------------------------ + truetrue2013-02-212013-02-21 +(1 row) + +SELECT table_to_xml('testxmlschema.test3', true, true, ''); + table_to_xml +--------------------------------------------------------------- + + + true + + true + + 2013-02-21 + + 2013-02-21 + + + + + + +(1 row) + diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 3a248c6306b7f..c0a0b3ef1bddd 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -126,6 +126,9 @@ static StringInfo psql_start_command(void); static void psql_add_command(StringInfo buf, const char *query,...) pg_attribute_printf(2, 3); static void psql_end_command(StringInfo buf, const char *database); +/* YB */ +static void yb_postprocess_output(const char *filename); + /* * allow core files if possible. */ @@ -990,7 +993,7 @@ psql_start_command(void) StringInfo buf = makeStringInfo(); appendStringInfo(buf, - "\"%s%spsql\" -X", + "\"%s%sysqlsh\" -X", bindir ? bindir : "", bindir ? "/" : ""); return buf; @@ -1323,6 +1326,9 @@ results_differ(const char *testname, const char *resultsfile, const char *defaul /* Name to use for temporary diff file */ snprintf(diff, sizeof(diff), "%s.diff", resultsfile); + yb_postprocess_output(resultsfile); + yb_postprocess_output(expectfile); + /* OK, run the diff */ snprintf(cmd, sizeof(cmd), "diff %s \"%s\" \"%s\" > \"%s\"", @@ -1357,6 +1363,8 @@ results_differ(const char *testname, const char *resultsfile, const char *defaul continue; } + yb_postprocess_output(alt_expectfile); + snprintf(cmd, sizeof(cmd), "diff %s \"%s\" \"%s\" > \"%s\"", basic_diff_opts, alt_expectfile, resultsfile, diff); @@ -1385,6 +1393,8 @@ results_differ(const char *testname, const char *resultsfile, const char *defaul if (platform_expectfile) { + yb_postprocess_output(default_expectfile); + snprintf(cmd, sizeof(cmd), "diff %s \"%s\" \"%s\" > \"%s\"", basic_diff_opts, default_expectfile, resultsfile, diff); @@ -2593,3 +2603,31 @@ regression_main(int argc, char *argv[], return 0; } + +/* + * A Yugabyte-specific way to post-process the results file and + * expected output files before running diff. As part of this we can remove + * trailing whitespace and LLVM sanitizer suppression warnings. + */ +static void +yb_postprocess_output(const char *filename) +{ + char cmd[4096]; + int r; + + Assert(filename); + const char *postprocess_cmd = getenv("YB_PG_REGRESS_RESULTSFILE_POSTPROCESS_CMD"); + + if (postprocess_cmd == NULL) + return; + + snprintf(cmd, sizeof(cmd), "%s \"%s\"", postprocess_cmd, filename); + r = system(cmd); + + if (!WIFEXITED(r) || WEXITSTATUS(r) > 1) + { + fprintf(stderr, "postprocess command failed with status %d: %s\n", + r, cmd); + exit(2); + } +} diff --git a/src/test/regress/pg_regress_main.c b/src/test/regress/pg_regress_main.c index a4b354c9e6c0e..a636e9a89fc31 100644 --- a/src/test/regress/pg_regress_main.c +++ b/src/test/regress/pg_regress_main.c @@ -21,20 +21,20 @@ #include "pg_regress.h" /* - * start a psql test process for specified file (including redirection), + * start a ysqlsh test process for specified file (including redirection), * and return process ID */ static PID_TYPE -psql_start_test(const char *testname, - _stringlist **resultfiles, - _stringlist **expectfiles, - _stringlist **tags) +ysqlsh_start_test(const char *testname, + _stringlist **resultfiles, + _stringlist **expectfiles, + _stringlist **tags) { PID_TYPE pid; char infile[MAXPGPATH]; char outfile[MAXPGPATH]; char expectfile[MAXPGPATH]; - char psql_cmd[MAXPGPATH * 3]; + char ysqlsh_cmd[MAXPGPATH * 3]; size_t offset = 0; char *appnameenv; @@ -64,9 +64,9 @@ psql_start_test(const char *testname, if (launcher) { - offset += snprintf(psql_cmd + offset, sizeof(psql_cmd) - offset, + offset += snprintf(ysqlsh_cmd + offset, sizeof(ysqlsh_cmd) - offset, "%s ", launcher); - if (offset >= sizeof(psql_cmd)) + if (offset >= sizeof(ysqlsh_cmd)) { fprintf(stderr, _("command too long\n")); exit(2); @@ -77,15 +77,15 @@ psql_start_test(const char *testname, * Use HIDE_TABLEAM to hide different AMs to allow to use regression tests * against different AMs without unnecessary differences. */ - offset += snprintf(psql_cmd + offset, sizeof(psql_cmd) - offset, - "\"%s%spsql\" -X -a -q -d \"%s\" %s < \"%s\" > \"%s\" 2>&1", + offset += snprintf(ysqlsh_cmd + offset, sizeof(ysqlsh_cmd) - offset, + "\"%s%sysqlsh\" -X -a -q -d \"%s\" %s < \"%s\" > \"%s\" 2>&1", bindir ? bindir : "", bindir ? "/" : "", dblist->str, "-v HIDE_TABLEAM=on -v HIDE_TOAST_COMPRESSION=on", infile, outfile); - if (offset >= sizeof(psql_cmd)) + if (offset >= sizeof(ysqlsh_cmd)) { fprintf(stderr, _("command too long\n")); exit(2); @@ -95,7 +95,7 @@ psql_start_test(const char *testname, setenv("PGAPPNAME", appnameenv, 1); free(appnameenv); - pid = spawn_process(psql_cmd); + pid = spawn_process(ysqlsh_cmd); if (pid == INVALID_PID) { @@ -110,7 +110,7 @@ psql_start_test(const char *testname, } static void -psql_init(int argc, char **argv) +ysqlsh_init(int argc, char **argv) { /* set default regression database name */ add_stringlist_item(&dblist, "regression"); @@ -120,7 +120,7 @@ int main(int argc, char *argv[]) { return regression_main(argc, argv, - psql_init, - psql_start_test, + ysqlsh_init, + ysqlsh_start_test, NULL /* no postfunc needed */ ); } diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index 5c0ecbace42a2..cb89c1bdfe735 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -46,6 +46,9 @@ #include "utils/rel.h" #include "utils/typcache.h" +/* YB includes */ +#include "utils/syscache.h" + #define EXPECT_TRUE(expr) \ do { \ if (!(expr)) \ @@ -1330,3 +1333,202 @@ get_columns_length(PG_FUNCTION_ARGS) PG_RETURN_INT32(column_offset); } + +/* YB */ + +/* + * Input and output functions for large, fixed-length name type. + */ +PG_FUNCTION_INFO_V1(bigname_in); +Datum +bigname_in(PG_FUNCTION_ARGS) +{ + char *input_string = PG_GETARG_CSTRING(0); + char *result = (char *) palloc0(128 * sizeof(char)); + + strncpy(result, input_string, 127 * sizeof(char)); + + PG_RETURN_POINTER(result); +} + +PG_FUNCTION_INFO_V1(bigname_out); +Datum +bigname_out(PG_FUNCTION_ARGS) +{ + char *internal_string = (char *) PG_GETARG_POINTER(0); + char *result = (char *) palloc(128); + + strncpy(result, internal_string, 128); + + PG_RETURN_CSTRING(result); +} + +/* + * Run given query using SPI_execute_with_args, retrieve only count rows, and + * return how many rows were returned. + */ +PG_FUNCTION_INFO_V1(yb_run_spi); +Datum +yb_run_spi(PG_FUNCTION_ARGS) +{ + char *query = text_to_cstring(PG_GETARG_TEXT_PP(0)); + int count = PG_GETARG_INT32(1); + int return_count; + int res; + + /* Open SPI context. */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + res = SPI_execute_with_args(query, 0, NULL, NULL, NULL, false, count); + if (res != SPI_OK_INSERT_RETURNING) + ereport(ERROR, + (errmsg("SPI_exec failed with %s: %s", + SPI_result_code_string(res), query))); + + if (SPI_processed <= 0) + return_count = 0; + else + return_count = SPI_tuptable->numvals; + + SPI_finish(); + + PG_RETURN_INT64(return_count); +} + +/* + * Set-returning function to return information about the catalog caches. + * + * Used in tests to verify the catalog cache has not been unexpectedly modified. + */ +PG_FUNCTION_INFO_V1(yb_cacheinfo); +Datum +yb_cacheinfo(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * Switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* + * Build a tuple descriptor for our result rows. + * We'll return: + * cache_id (int4) + * name (text) + * reloid (oid) + * indoid (oid) + * nkeys (int4) + * key1 (int4) + * key2 (int4) + * key3 (int4) + * key4 (int4) + * nbuckets (int4) + */ + tupdesc = CreateTemplateTupleDesc(10); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "cache_id", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "name", TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "reloid", OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "indoid", OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "nkeys", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "key1", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "key2", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "key3", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "key4", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "nbuckets", INT4OID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + /* + * Store the array length so we know how many times to iterate. + */ + funcctx->max_calls = SysCacheSize; + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + if (funcctx->call_cntr < funcctx->max_calls) + { + Datum values[10]; + bool nulls[10]; + HeapTuple tuple; + Datum result; + + /* Clear null flags */ + MemSet(nulls, false, sizeof(nulls)); + + /* Fill values from cacheinfo */ + YbCopyCacheInfoToValues(funcctx->call_cntr, values); + + /* Build tuple and convert to datum */ + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + + SRF_RETURN_DONE(funcctx); +} + +/* + * Helper to extract the cache id + up to 4 key attributes from the passed-in record, + * then compute the hash value. + */ +PG_FUNCTION_INFO_V1(catalog_cache_compute_hash_tuple); +Datum +catalog_cache_compute_hash_tuple(PG_FUNCTION_ARGS) +{ + Datum *values; + bool *nulls; + HeapTupleData tmptup; + + /* + * If the argument is NULL (e.g. SELECT catalog_cache_compute_hash_tuple(NULL)), + * we'll just return NULL. + */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + /* + * Extract the tuple argument. + */ + HeapTupleHeader td = PG_GETARG_HEAPTUPLEHEADER(0); + + /* + * From the tuple header, fetch the rowtype's OID and typmod so we can look up + * its TupleDesc (layout info). + */ + Oid tupType = HeapTupleHeaderGetTypeId(td); + int32 tupTypmod = HeapTupleHeaderGetTypMod(td); + TupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + tmptup.t_len = HeapTupleHeaderGetDatumLength(td); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = td; + + /* + * Deform the tuple into a Datum array so we can pass the keys to the hash + * function. + */ + values = (Datum *) palloc0(sizeof(Datum) * tupdesc->natts); + nulls = (bool *) palloc0(sizeof(bool) * tupdesc->natts); + heap_deform_tuple(&tmptup, tupdesc, values, nulls); + + /* Compute the hash value. */ + uint32 hashval = YbSysCacheComputeHashValue(values[0], values[1], values[2], values[3], values[4]); + + ReleaseTupleDesc(tupdesc); + PG_RETURN_UINT32(hashval); +} diff --git a/src/test/regress/sql/yb.depd.create_index.sql b/src/test/regress/sql/yb.depd.create_index.sql new file mode 100644 index 0000000000000..52686162a83bc --- /dev/null +++ b/src/test/regress/sql/yb.depd.create_index.sql @@ -0,0 +1,60 @@ +-- +-- Build dependency objects found in the create_index test. This is a faster +-- version of yb.port.create_index due to usage of NONCONCURRENTLY and omission +-- of queries that are of no use to followup tests. +-- +-- A common dependency chain would be yb.port.test_setup, yb.depd.create_index. +-- + +-- +-- BTREE +-- +CREATE INDEX NONCONCURRENTLY onek_unique1 ON onek USING btree(unique1 int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY onek_unique2 ON onek USING btree(unique2 int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY onek_hundred ON onek USING btree(hundred int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY onek_stringu1 ON onek USING btree(stringu1 name_ops ASC); + +CREATE INDEX NONCONCURRENTLY tenk1_unique1 ON tenk1 USING btree(unique1 int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY tenk1_unique2 ON tenk1 USING btree(unique2 int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY tenk1_hundred ON tenk1 USING btree(hundred int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY tenk1_thous_tenthous ON tenk1 (thousand ASC, tenthous ASC); + +CREATE INDEX NONCONCURRENTLY tenk2_unique1 ON tenk2 USING btree(unique1 int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY tenk2_unique2 ON tenk2 USING btree(unique2 int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY tenk2_hundred ON tenk2 USING btree(hundred int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY rix ON road USING btree (name text_ops ASC); + +CREATE INDEX NONCONCURRENTLY iix ON ihighway USING btree (name text_ops ASC); + +-- +-- BTREE partial indices +-- +CREATE INDEX NONCONCURRENTLY onek2_u1_prtl ON onek2 USING btree(unique1 int4_ops ASC) + where unique1 < 20 or unique1 > 980; + +CREATE INDEX NONCONCURRENTLY onek2_u2_prtl ON onek2 USING btree(unique2 int4_ops ASC) + where stringu1 < 'B'; + +CREATE INDEX NONCONCURRENTLY onek2_stu1_prtl ON onek2 USING btree(stringu1 name_ops ASC) + where onek2.stringu1 >= 'J' and onek2.stringu1 < 'K'; + +--- + +CREATE TABLE fast_emp4000 ( + home_base box +); + +--- + +-- TODO(jason): botharrayidx is used by amutils test, but this query currently +-- fails for multiple reasons. +-- CREATE INDEX NONCONCURRENTLY botharrayidx ON array_index_op_test USING gin (i, t); diff --git a/src/test/regress/sql/yb.depd.float8_tbl.sql b/src/test/regress/sql/yb.depd.float8_tbl.sql new file mode 100644 index 0000000000000..09a930e302821 --- /dev/null +++ b/src/test/regress/sql/yb.depd.float8_tbl.sql @@ -0,0 +1,21 @@ +-- +-- A collection of queries to build the float8_tbl table. +-- +-- The queries are taken from the relevant dependency files. Since it is +-- faster to run this rather than each file itself (e.g. dependency chain +-- test_setup, create_index), prefer using this. +-- + +-- +-- test_setup +-- + +CREATE TABLE FLOAT8_TBL(f1 float8); + +INSERT INTO FLOAT8_TBL(f1) VALUES + ('0.0'), + ('-34.84'), + ('-1004.30'), + ('-1.2345678901234e+200'), + ('-1.2345678901234e-200'); +VACUUM FLOAT8_TBL; diff --git a/src/test/regress/sql/yb.depd.int4_tbl.sql b/src/test/regress/sql/yb.depd.int4_tbl.sql new file mode 100644 index 0000000000000..75226af6ff7db --- /dev/null +++ b/src/test/regress/sql/yb.depd.int4_tbl.sql @@ -0,0 +1,21 @@ +-- +-- A collection of queries to build the onek2 table. +-- +-- The queries are taken from the relevant dependency files. Since it is +-- faster to run this rather than each file itself (e.g. dependency chain +-- test_setup, create_index), prefer using this. +-- + +-- +-- test_setup +-- + +CREATE TABLE INT4_TBL(f1 int4); + +INSERT INTO INT4_TBL(f1) VALUES + (' 0 '), + ('123456 '), + (' -123456'), + ('2147483647'), -- largest and smallest values + ('-2147483647'); +VACUUM INT4_TBL; diff --git a/src/test/regress/sql/yb.depd.int8_tbl.sql b/src/test/regress/sql/yb.depd.int8_tbl.sql new file mode 100644 index 0000000000000..2f88dc54d9c10 --- /dev/null +++ b/src/test/regress/sql/yb.depd.int8_tbl.sql @@ -0,0 +1,21 @@ +-- +-- A collection of queries to build the int8_tbl table. +-- +-- The queries are taken from the relevant dependency files. Since it is +-- faster to run this rather than each file itself (e.g. dependency chain +-- test_setup, create_index), prefer using this. +-- + +-- +-- test_setup +-- + +CREATE TABLE INT8_TBL(q1 int8, q2 int8); + +INSERT INTO INT8_TBL VALUES + (' 123 ',' 456'), + ('123 ','4567890123456789'), + ('4567890123456789','123'), + (+4567890123456789,'4567890123456789'), + ('+4567890123456789','-4567890123456789'); +VACUUM INT8_TBL; diff --git a/src/test/regress/sql/yb.depd.onek.sql b/src/test/regress/sql/yb.depd.onek.sql new file mode 100644 index 0000000000000..d660d0f0cdd78 --- /dev/null +++ b/src/test/regress/sql/yb.depd.onek.sql @@ -0,0 +1,53 @@ +-- +-- A collection of queries to build the onek table. +-- +-- The queries are taken from the relevant dependency files. Since it is +-- faster to run this rather than each file itself (e.g. dependency chain +-- test_setup, create_index), prefer using this. +-- + +-- +-- test_setup +-- + +\getenv abs_srcdir PG_ABS_SRCDIR + +-- + +CREATE TABLE onek ( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +); + +\set filename :abs_srcdir '/data/onek.data' +COPY onek FROM :'filename'; +VACUUM ANALYZE onek; + +-- +-- yb.port.create_index +-- (With modification to make them all nonconcurrent for performance.) +-- + +CREATE INDEX NONCONCURRENTLY onek_unique1 ON onek USING btree(unique1 int4_ops ASC); + +-- + +CREATE INDEX NONCONCURRENTLY onek_unique2 ON onek USING btree(unique2 int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY onek_hundred ON onek USING btree(hundred int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY onek_stringu1 ON onek USING btree(stringu1 name_ops ASC); diff --git a/src/test/regress/sql/yb.depd.onek2.sql b/src/test/regress/sql/yb.depd.onek2.sql new file mode 100644 index 0000000000000..6d45fd8f40deb --- /dev/null +++ b/src/test/regress/sql/yb.depd.onek2.sql @@ -0,0 +1,31 @@ +-- +-- A collection of queries to build the onek2 table. +-- +-- The queries are taken from the relevant dependency files. Since it is +-- faster to run this rather than each file itself (e.g. dependency chain +-- test_setup, create_index), prefer using this. +-- +-- DEPENDENCY: this file must be run after onek has been populated (by +-- yb.depd.onek). +-- + +-- +-- test_setup +-- + +CREATE TABLE onek2 AS SELECT * FROM onek; +VACUUM ANALYZE onek2; + +-- +-- yb.port.create_index +-- (With modification to make them all nonconcurrent for performance.) +-- + +CREATE INDEX NONCONCURRENTLY onek2_u1_prtl ON onek2 USING btree(unique1 int4_ops ASC) + where unique1 < 20 or unique1 > 980; + +CREATE INDEX NONCONCURRENTLY onek2_u2_prtl ON onek2 USING btree(unique2 int4_ops ASC) + where stringu1 < 'B'; + +CREATE INDEX NONCONCURRENTLY onek2_stu1_prtl ON onek2 USING btree(stringu1 name_ops ASC) + where onek2.stringu1 >= 'J' and onek2.stringu1 < 'K'; diff --git a/src/test/regress/sql/yb.depd.part_test_int4_ops.sql b/src/test/regress/sql/yb.depd.part_test_int4_ops.sql new file mode 100644 index 0000000000000..d1a0ea5fd7f79 --- /dev/null +++ b/src/test/regress/sql/yb.depd.part_test_int4_ops.sql @@ -0,0 +1,20 @@ +-- +-- A collection of queries to build the part_test_int4_ops opclass. +-- +-- The queries are taken from the relevant dependency files. Since it is +-- faster to run this rather than each file itself (e.g. dependency chain +-- test_setup, create_index), prefer using this. +-- + +-- +-- test_setup +-- + +create function part_hashint4_noop(value int4, seed int8) + returns int8 as $$ + select value + seed; + $$ language sql strict immutable parallel safe; + +create operator class part_test_int4_ops for type int4 using hash as + operator 1 =, + function 2 part_hashint4_noop(int4, int8); diff --git a/src/test/regress/sql/yb.depd.person.sql b/src/test/regress/sql/yb.depd.person.sql new file mode 100644 index 0000000000000..e254bc1d16ae4 --- /dev/null +++ b/src/test/regress/sql/yb.depd.person.sql @@ -0,0 +1,25 @@ +-- +-- A collection of queries to build the person table. +-- +-- The queries are taken from the relevant dependency files. Since it is +-- faster to run this rather than each file itself (e.g. dependency chain +-- test_setup, create_index), prefer using this. +-- + +-- +-- test_setup +-- + +\getenv abs_srcdir PG_ABS_SRCDIR + +-- + +CREATE TABLE person ( + name text, + age int4, + location point +); + +\set filename :abs_srcdir '/data/person.data' +COPY person FROM :'filename'; +VACUUM ANALYZE person; diff --git a/src/test/regress/sql/yb.depd.tenk1.sql b/src/test/regress/sql/yb.depd.tenk1.sql new file mode 100644 index 0000000000000..2df9fc5cd6519 --- /dev/null +++ b/src/test/regress/sql/yb.depd.tenk1.sql @@ -0,0 +1,58 @@ +-- +-- A collection of queries to build the tenk1 table. +-- +-- The queries are taken from the relevant dependency files. Since it is +-- faster to run this rather than each file itself (e.g. dependency chain +-- test_setup, create_index), prefer using this. Also, to make it usable with +-- yb.port.test_setup and yb.port.create_index, make it idempotent. +-- + +-- To avoid IF NOT EXISTS NOTICE messages. +SET client_min_messages TO WARNING; + +-- +-- test_setup +-- + +\getenv abs_srcdir PG_ABS_SRCDIR + +-- + +CREATE TABLE IF NOT EXISTS tenk1 ( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +); +\set filename :abs_srcdir '/data/tenk.data' +CREATE TEMP TABLE tenk1tmp (LIKE tenk1); +COPY tenk1tmp FROM :'filename'; +DO $$BEGIN IF NOT EXISTS (SELECT * FROM tenk1 LIMIT 1) THEN +INSERT INTO tenk1 SELECT * FROM tenk1tmp; +END IF; END$$; +VACUUM ANALYZE tenk1; + +-- +-- yb.port.create_index +-- (With modification to make them all nonconcurrent for performance.) +-- + +CREATE INDEX NONCONCURRENTLY IF NOT EXISTS tenk1_unique1 ON tenk1 USING btree(unique1 int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY IF NOT EXISTS tenk1_unique2 ON tenk1 USING btree(unique2 int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY IF NOT EXISTS tenk1_hundred ON tenk1 USING btree(hundred int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY IF NOT EXISTS tenk1_thous_tenthous ON tenk1 (thousand ASC, tenthous ASC); diff --git a/src/test/regress/sql/yb.depd.tenk2.sql b/src/test/regress/sql/yb.depd.tenk2.sql new file mode 100644 index 0000000000000..1853021d96662 --- /dev/null +++ b/src/test/regress/sql/yb.depd.tenk2.sql @@ -0,0 +1,31 @@ +-- +-- A collection of queries to build the tenk2 table. +-- +-- The queries are taken from the relevant dependency files. Since it is +-- faster to run this rather than each file itself (e.g. dependency chain +-- test_setup, create_index), prefer using this. Also, to make it usable with +-- yb.port.test_setup and yb.port.create_index, make it idempotent. +-- +-- DEPENDENCY: this file must be run after tenk1 has been populated. +-- + +-- To avoid IF NOT EXISTS NOTICE messages. +SET client_min_messages TO WARNING; + +-- +-- test_setup +-- + +CREATE TABLE IF NOT EXISTS tenk2 AS SELECT * FROM tenk1; +VACUUM ANALYZE tenk2; + +-- +-- yb.port.create_index +-- (With modification to make them all nonconcurrent for performance.) +-- + +CREATE INDEX NONCONCURRENTLY IF NOT EXISTS tenk2_unique1 ON tenk2 USING btree(unique1 int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY IF NOT EXISTS tenk2_unique2 ON tenk2 USING btree(unique2 int4_ops ASC); + +CREATE INDEX NONCONCURRENTLY IF NOT EXISTS tenk2_hundred ON tenk2 USING btree(hundred int4_ops ASC); diff --git a/src/test/regress/sql/yb.orig.aggregates.sql b/src/test/regress/sql/yb.orig.aggregates.sql new file mode 100644 index 0000000000000..3e6a3049fb0e6 --- /dev/null +++ b/src/test/regress/sql/yb.orig.aggregates.sql @@ -0,0 +1,380 @@ +-- YB AGGREGATES TEST (for pushdown) + +SET yb_enable_bitmapscan = true; + +-- +-- Test basic aggregates and verify overflow is handled properly. +-- + +-- avoid bit-exact output here because operations may not be bit-exact. +SET extra_float_digits = 0; + +CREATE TABLE ybaggtest ( + id int PRIMARY KEY, + int_2 int2, + int_4 int4, + int_8 int8, + float_4 float4, + float_8 float8 +); +CREATE INDEX NONCONCURRENTLY ybaggtestindex ON ybaggtest ( + (int_8, int_2) HASH, + float_4 DESC, + int_4 ASC +) INCLUDE (float_8); +CREATE INDEX NONCONCURRENTLY ybaggtestindexsimple ON ybaggtest ( + int_4 ASC +); + +-- Insert maximum integer values multiple times to force overflow on SUM (both in DocDB and PG). +INSERT INTO ybaggtest VALUES (1, 32767, 2147483647, 9223372036854775807, 1.1, 2.2); +INSERT INTO ybaggtest + SELECT series, t.int_2, t.int_4, t.int_8, t.float_4, t.float_8 + FROM ybaggtest as t CROSS JOIN generate_series(2, 100) as series; + +-- Verify COUNT(...) returns proper value. +\set explain 'EXPLAIN (COSTS OFF)' +\set ss '/*+SeqScan(ybaggtest)*/' +\set ios '/*+IndexOnlyScan(ybaggtest ybaggtestindex)*/' +\set is '/*+IndexScan(ybaggtest ybaggtestindexsimple)*/' +\set bs '/*+BitmapScan(ybaggtest)*/' +\set query 'SELECT COUNT(*) FROM ybaggtest' +\set isquery ':query WHERE int_4 > 0' +\set run ':explain :query; :explain :ss :query; :explain :ios :query; :explain :is :isquery; :explain :bs :isquery; :query; :ss :query; :ios :query; :is :isquery; :bs :isquery' +:run; +\set query 'SELECT COUNT(0) FROM ybaggtest' +:run; +\set query 'SELECT COUNT(NULL) FROM ybaggtest' +:run; + +-- Delete row, verify COUNT(...) returns proper value. +DELETE FROM ybaggtest WHERE id = 100; +SELECT COUNT(*) FROM ybaggtest; +/*+IndexOnlyScan(ybaggtest ybaggtestindex)*/ +SELECT COUNT(*) FROM ybaggtest; +SELECT COUNT(0) FROM ybaggtest; +/*+IndexOnlyScan(ybaggtest ybaggtestindex)*/ +SELECT COUNT(0) FROM ybaggtest; + +-- Verify selecting different aggs for same column works. +\set query 'SELECT SUM(int_4), MAX(int_4), MIN(int_4), SUM(int_2), MAX(int_2), MIN(int_2) FROM ybaggtest' +:run; + +-- Verify SUMs are correct for all fields and do not overflow. +\set query 'SELECT SUM(int_2), SUM(int_4), SUM(int_8), SUM(float_4), SUM(float_8) FROM ybaggtest' +:run; +-- ...and do the same query excluding the int_8 column to test agg pushdown. +-- TODO(#16289): remove this. +\set query 'SELECT SUM(int_2), SUM(int_4), SUM(float_4), SUM(float_8) FROM ybaggtest' +:run; + +-- Verify shared aggregates work as expected. +\set query 'SELECT SUM(int_4), SUM(int_4) + 1 FROM ybaggtest' +:run; + +-- Verify NaN float values are respected by aggregates. +INSERT INTO ybaggtest (id, int_4, float_4, float_8) VALUES (101, 1, 'NaN', 'NaN'); +\set query 'SELECT COUNT(float_4), SUM(float_4), MAX(float_4), MIN(float_4) FROM ybaggtest' +:run; +\set query 'SELECT COUNT(float_8), SUM(float_8), MAX(float_8), MIN(float_8) FROM ybaggtest' +:run; + +-- In case indexquals are planned to be rechecked, pushdown should be avoided. +-- These queries skip Index Scan because the Index Scan hint forces using ybaggtestindexsimple, +-- which only contains int_4. +\set run_ss_ios_bs ':explain :query; :explain :ss :query; :explain :ios :query; :explain :bs :query; :query; :ss :query; :ios :query; :bs :query' +\set query 'SELECT COUNT(*) FROM ybaggtest WHERE float_4 > 0' +:run_ss_ios_bs; +\set query 'SELECT COUNT(*) FROM ybaggtest WHERE int_4 > 0 AND float_8 > 0' +:run_ss_ios_bs; +\set query 'SELECT COUNT(*) FROM ybaggtest WHERE int_8 = 9223372036854775807' +:run_ss_ios_bs; +\set query 'SELECT COUNT(*) FROM ybaggtest WHERE int_8 = 9223372036854775807 AND int_2 = 32767' +:run_ss_ios_bs; + +-- In case YB recheck might happen, pushdown should be avoided. +\set query 'SELECT MAX(a.int_4) FROM ybaggtest AS a LEFT JOIN ybaggtest AS b ON a.id = b.id WHERE a.int_4 = 1 AND a.int_4 BETWEEN 7 AND 14' +:explain :query; :query; + +-- Backward scan. +EXPLAIN (COSTS OFF) SELECT SUM(float_4) FROM (SELECT float_4 FROM ybaggtest WHERE int_8 = 0 AND int_2 = 0 ORDER BY float_4) t; +EXPLAIN (COSTS OFF) SELECT SUM(float_4) FROM (SELECT float_4 FROM ybaggtest WHERE int_8 = 0 AND int_2 = 0 ORDER BY float_4 LIMIT 1) t; +EXPLAIN (COSTS OFF) SELECT SUM(float_4 + 1) FROM (SELECT float_4 FROM ybaggtest WHERE int_8 = 0 AND int_2 = 0 ORDER BY float_4) t; + +-- Negative tests - pushdown not supported +EXPLAIN (COSTS OFF) SELECT int_2, COUNT(*), SUM(int_4) FROM ybaggtest GROUP BY int_2; +EXPLAIN (COSTS OFF) SELECT DISTINCT int_8 FROM ybaggtest; +EXPLAIN (COSTS OFF) SELECT COUNT(distinct int_4), SUM(int_4) FROM ybaggtest; +EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM pg_type WHERE oid = 21 AND oid < 0; + +-- +-- Test NULL rows are handled properly by COUNT. +-- +-- Create table without primary key. +CREATE TABLE ybaggtest2 ( + a int +); + +-- Create index where column a is not part of the key. +CREATE INDEX NONCONCURRENTLY ybaggtest2index ON ybaggtest2 ((1)) INCLUDE (a); + +-- Insert NULL rows. +INSERT INTO ybaggtest2 VALUES (NULL), (NULL); + +-- Insert regular rows. +INSERT INTO ybaggtest2 VALUES (1), (2), (3); + +-- Verify NULL rows are included in COUNT(*) but not in COUNT(row). +\set ss '/*+SeqScan(ybaggtest2)*/' +\set ios '/*+IndexOnlyScan(ybaggtest2 ybaggtest2index)*/' +\set query 'SELECT COUNT(*) FROM ybaggtest2' +-- tests using run_ss_ios cannot use a plain index scan (so they also cannot use a bitmap index scan) +-- adding a hint would just re-test sequential scan +\set run_ss_ios ':explain :query; :explain :ss :query; :explain :ios :query; :ss :query; :ios :query;' +:run_ss_ios; +\set query 'SELECT COUNT(a) FROM ybaggtest2' +:run_ss_ios; +\set query 'SELECT COUNT(*), COUNT(a) FROM ybaggtest2' +:run_ss_ios; + +-- Verify MAX/MIN respect NULL values. +\set query 'SELECT MAX(a), MIN(a) FROM ybaggtest2' +:run_ss_ios; + +-- Verify SUM/MAX/MIN work as expected with constant arguments. +\set query 'SELECT SUM(2), MAX(2), MIN(2) FROM ybaggtest2' +:run_ss_ios; +\set query 'SELECT SUM(NULL::int), MAX(NULL), MIN(NULL) FROM ybaggtest2' +:run_ss_ios; +-- Verify IS NULL, IS NOT NULL quals. +\set query 'SELECT COUNT(*) FROM ybaggtest2 WHERE a IS NULL' +:run_ss_ios; +\set query 'SELECT COUNT(*) FROM ybaggtest2 WHERE a IS NOT NULL' +:run_ss_ios; + +-- +-- Test column created with default value. +-- +CREATE TABLE digit(k INT PRIMARY KEY, v TEXT NOT NULL); +INSERT INTO digit VALUES(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'), (5, 'five'), (6, 'six'); +CREATE TABLE test(k INT PRIMARY KEY); +ALTER TABLE test ADD v1 int DEFAULT 5; +ALTER TABLE test ADD v2 int DEFAULT 10; +CREATE INDEX NONCONCURRENTLY testindex ON test (k ASC) INCLUDE (v1, v2); +INSERT INTO test VALUES(1), (2), (3); +\set ss '/*+SeqScan(test)*/' +\set ios '/*+IndexOnlyScan(test testindex)*/' +\set is '/*+IndexScan(test testindex)*/' +\set bs '/*+BitmapScan(test)*/' +\set query 'SELECT COUNT(*) FROM test' +\set isquery ':query WHERE k > 0' +:run; +\set query 'SELECT COUNT(k) FROM test' +:run; +\set query 'SELECT COUNT(v1) FROM test' +:run; +\set query 'SELECT COUNT(v2) FROM test' +:run; +\set outerquery1 'SELECT * FROM digit AS d INNER JOIN' +\set innerquery 'SELECT COUNT(v2) AS count FROM test' +\set outerquery2 'AS c ON (d.k = c.count)' +\set query ':outerquery1 (:innerquery) :outerquery2' +\set isquery ':outerquery1 (:innerquery WHERE k > 0) :outerquery2' +:run; +INSERT INTO test VALUES(4, NULL, 10), (5, 5, NULL), (6, 5, NULL); +\set query 'SELECT COUNT(*) FROM test' +\set isquery ':query WHERE k > 0' +:run; +\set query 'SELECT COUNT(k) FROM test' +:run; +\set query 'SELECT COUNT(v1) FROM test' +:run; +\set query 'SELECT COUNT(v2) FROM test' +:run; +\set innerquery 'SELECT COUNT(*) AS count FROM test' +\set query ':outerquery1 (:innerquery) :outerquery2' +\set isquery ':outerquery1 (:innerquery WHERE k > 0) :outerquery2' +:run; +\set innerquery 'SELECT COUNT(k) AS count FROM test' +:run; +\set innerquery 'SELECT COUNT(v1) AS count FROM test' +:run; +\set innerquery 'SELECT COUNT(v2) AS count FROM test' +:run; + +DROP TABLE test; +DROP TABLE digit; + +-- +-- Test dropped column. +-- +CREATE TABLE test(K INT PRIMARY KEY, v1 INT NOT NULL, v2 INT NOT NULL); +CREATE INDEX NONCONCURRENTLY testindex ON test (K ASC) INCLUDE (v2); +INSERT INTO test VALUES(1, 1, 1), (2, 2, 2), (3, 3, 3); +AlTER TABLE test DROP v1; +\set query 'SELECT MIN(v2) FROM test' +\set isquery ':query WHERE K > 0' +:run; +\set query 'SELECT MAX(v2) FROM test' +:run; +\set query 'SELECT SUM(v2) FROM test' +:run; +\set query 'SELECT COUNT(v2) FROM test' +:run; + +-- +-- Test https://github.com/yugabyte/yugabyte-db/issues/10085: avoid pushdown +-- for certain cases. +-- +-- Original test case that had postgres FATAL: +CREATE TABLE t1(c0 DECIMAL ); +CREATE INDEX NONCONCURRENTLY t1index ON t1 (c0 ASC); +INSERT INTO t1(c0) VALUES(0.4632167437031089463062016875483095645904541015625), (0.82173140818865475498711248292238451540470123291015625), (0.69990454445895500246166420765803195536136627197265625), (0.7554730989898816861938257716246880590915679931640625); +ALTER TABLE ONLY t1 FORCE ROW LEVEL SECURITY, DISABLE ROW LEVEL SECURITY, NO FORCE ROW LEVEL SECURITY; +INSERT INTO t1(c0) VALUES(0.9946693818538820952568357824929989874362945556640625), (0.13653666831997435249235195442452095448970794677734375), (0.3359001510719556993223022800520993769168853759765625), (0.312027233370160583802999099134467542171478271484375); +\set ss '/*+SeqScan(t1)*/' +\set ios '/*+IndexOnlyScan(t1 t1index)*/' +\set is '/*+IndexScan(t1 t1index)*/' +\set bs '/*+BitmapScan(t1)*/' +\set outerquery1 'SELECT SUM(count) FROM' +\set innerquery 'SELECT (CAST(((((''[-1962327130,2000870418)''::int4range)*(''(-1293215916,183586536]''::int4range)))-(((''[-545024026,526859443]''::int4range)*(NULL)))) AS VARCHAR)~current_query())::INT as count FROM ONLY t1' +\set outerquery2 'as res' +\set query ':outerquery1 (:innerquery) :outerquery2' +\set isquery ':outerquery1 (:innerquery WHERE c0 > 0) :outerquery2' +:run; + +-- Simplified test case that had postgres FATAL: +CREATE TABLE t2(c0 DECIMAL ); +CREATE INDEX NONCONCURRENTLY t2index ON t2 (c0 ASC); +INSERT INTO t2 VALUES(1), (2), (3); +\set ss '/*+SeqScan(t2)*/' +\set ios '/*+IndexOnlyScan(t2 t2index)*/' +\set is '/*+IndexScan(t2 t2index)*/' +\set bs '/*+BitmapScan(t2)*/' +\set outerquery1 'SELECT SUM(r) < 6 from' +\set innerquery 'SELECT random() as r from t2' +:run; + +-- Simplified test case that had postgres FATAL: +CREATE TABLE t3(c0 DECIMAL ); +CREATE INDEX NONCONCURRENTLY t3index ON t3 (c0 ASC); +INSERT INTO t3 VALUES(1), (2), (3); +\set ss '/*+SeqScan(t3)*/' +\set ios '/*+IndexOnlyScan(t3 t3index)*/' +\set is '/*+IndexScan(t3 t3index)*/' +\set bs '/*+BitmapScan(t3)*/' +\set outerquery1 'SELECT SUM(r) from' +\set innerquery 'SELECT (NULL=random())::int as r from t3' +:run; + +-- Test case that did not have postgres FATAL but showed wrong result 't': +CREATE TABLE t4(c0 FLOAT8); +CREATE INDEX NONCONCURRENTLY t4index ON t4 (c0 ASC); +INSERT INTO t4 VALUES(1), (2), (3); +\set ss '/*+SeqScan(t4)*/' +\set ios '/*+IndexOnlyScan(t4 t4index)*/' +\set is '/*+IndexScan(t4 t4index)*/' +\set bs '/*+BitmapScan(t4)*/' +\set outerquery1 'SELECT SUM(r) = 6 from' +\set innerquery 'SELECT random() as r from t4' +:run; + +--- +--- SPLIT +--- +CREATE TABLE rsplit (i int, PRIMARY KEY (i ASC)) SPLIT AT VALUES ((2)); +CREATE INDEX ON rsplit (i ASC) SPLIT AT VALUES ((3)); +INSERT INTO rsplit VALUES (1), (2), (3); +\set ss '/*+SeqScan(rsplit)*/' +\set ios '/*+IndexOnlyScan(rsplit rsplit_i_idx)*/' +\set is '/*+IndexScan(rsplit rsplit_i_idx)*/' +\set bs '/*+BitmapScan(rsplit)*/' +\set query 'SELECT SUM(i) FROM rsplit WHERE i = 2' +\set isquery ':query' +:run; +\set query 'SELECT SUM(i) FROM rsplit WHERE i = 3' +:run; + +-- +-- System tables. +-- +\set ss '/*+SeqScan(pg_type)*/' +\set ios '/*+IndexOnlyScan(pg_type pg_type_typname_nsp_index)*/' +\set is '/*+IndexScan(pg_type pg_type_typname_nsp_index)*/' +\set bs '/*+BitmapScan(pg_type)*/' +\set query 'SELECT MIN(typnamespace) FROM pg_type' +\set isquery ':query WHERE typname > ''''' +:run; + +-- +-- Temp tables. +-- +CREATE TEMP TABLE tmp (i int, j int); +CREATE INDEX ON tmp (j ASC); +INSERT INTO tmp SELECT g, -g FROM generate_series(1, 10) g; +\set ss '/*+SeqScan(tmp)*/' +\set ios '/*+IndexOnlyScan(tmp tmp_j_idx)*/' +\set is '/*+IndexScan(tmp tmp_j_idx)*/' +\set query 'SELECT SUM(j), AVG(j), COUNT(*), MAX(j) FROM tmp' +\set isquery ':query WHERE j < 0' +-- TODO(#18619): change the following to :run; +:run_ss_ios; + +-- +-- Prepared statements. +-- +CREATE TABLE ptest(k int, i int, v int, primary key(k asc)); +CREATE INDEX NONCONCURRENTLY ptestindex on ptest(i asc); +INSERT INTO ptest select i, i + 1, i + 2 from generate_series(1, 10) i; +PREPARE iss as select sum(k) from ptest where k = ANY($1); +PREPARE ioss as select sum(i) from ptest where i = ANY($1); +\set prm 'ARRAY[1, 2]' +\set issexec 'EXECUTE iss(:prm)' +\set iossexec 'EXECUTE ioss(:prm)' +\set exec ':explain :issexec; :explain :iossexec; :issexec; :iossexec;' +:exec +-- repeat multiple times to make sure the statements are not replanned and cached plan is used +\set prm 'ARRAY[3, 4]' +:exec +\set prm 'ARRAY[5, 6]' +:exec +\set prm 'ARRAY[7, 8]' +:exec +\set prm 'ARRAY[9, 10]' +:exec +\set prm 'ARRAY[11, 12]' +:exec +DEALLOCATE iss; +DEALLOCATE ioss; + +-- Internal parameters test +CREATE TABLE nlptest (v1 int, v2 int); +INSERT INTO nlptest SELECT i*2-1, i*2 FROM generate_series(1,6) i; +/*+ IndexScan(t2) NestLoop(t1 t2) Leading((t1 t2)) */ +SELECT * FROM (SELECT v1, v2 FROM nlptest t1) AS vw1, + LATERAL(SELECT sum(k) FROM ptest t2 WHERE k = ANY(ARRAY[vw1.v1, vw1.v2])) AS vw2 +ORDER BY v1; +/*+ IndexScan(t2) NestLoop(t1 t2) Leading((t1 t2)) */ +SELECT * FROM (SELECT v1, v2 FROM nlptest t1) AS vw1, + LATERAL(SELECT sum(i) FROM ptest t2 WHERE i = ANY(ARRAY[vw1.v1, vw1.v2])) AS vw2 +ORDER BY v1; + +DROP TABLE ptest; +DROP TABLE nlptest; + +-- +-- Colocation. +-- +CREATE DATABASE co COLOCATION TRUE; +\c co +SET yb_enable_bitmapscan = true; +CREATE TABLE t (i int, j int, k int); +CREATE INDEX NONCONCURRENTLY i ON t (j, k DESC, i); +INSERT INTO t VALUES (1, 2, 3), (4, 2, 6); +\set ss '/*+SeqScan(t)*/' +\set ios '/*+IndexOnlyScan(t i)*/' +\set is '/*+IndexScan(t i)*/' +\set bs '/*+BitmapScan(t)*/' +\set query 'SELECT MAX(k), AVG(i), COUNT(*), SUM(j) FROM t' +\set isquery ':query WHERE j = 2' +:run; diff --git a/src/test/regress/sql/yb.orig.alter_operator.sql b/src/test/regress/sql/yb.orig.alter_operator.sql new file mode 100644 index 0000000000000..ab98267b15982 --- /dev/null +++ b/src/test/regress/sql/yb.orig.alter_operator.sql @@ -0,0 +1,43 @@ +CREATE FUNCTION alter_op_test_fn(boolean, boolean) +RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE; + +CREATE FUNCTION customcontsel(internal, oid, internal, integer) +RETURNS float8 AS 'contsel' LANGUAGE internal STABLE STRICT; + +CREATE OPERATOR === ( + LEFTARG = boolean, + RIGHTARG = boolean, + PROCEDURE = alter_op_test_fn, + COMMUTATOR = ===, + NEGATOR = !==, + RESTRICT = customcontsel, + JOIN = contjoinsel, + HASHES, MERGES +); + +CREATE USER regress_alter_op_user; +ALTER OPERATOR === (boolean, boolean) OWNER TO regress_alter_op_user; +DROP USER regress_alter_op_user; -- error +SET SESSION AUTHORIZATION regress_alter_op_user; +ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE); +SELECT oprrest FROM pg_operator WHERE oprname = '===' + AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype; + +-- Cleanup +RESET SESSION AUTHORIZATION; +ALTER OPERATOR === (boolean, boolean) OWNER TO yugabyte; +DROP USER regress_alter_op_user; + +-- +-- Test SET SCHEMA +-- +CREATE SCHEMA op_schema; +ALTER OPERATOR === (boolean, boolean) SET SCHEMA op_schema; +SELECT ns.nspname FROM pg_operator op + INNER JOIN pg_namespace ns ON ns.oid = op.oprnamespace + WHERE op.oprname = '===' + AND op.oprleft = 'boolean'::regtype AND op.oprright = 'boolean'::regtype; + +-- Cleanup +ALTER OPERATOR op_schema.=== (boolean, boolean) SET SCHEMA public; +DROP SCHEMA op_schema; diff --git a/src/test/regress/sql/yb.orig.alter_table.sql b/src/test/regress/sql/yb.orig.alter_table.sql new file mode 100644 index 0000000000000..5ec74458fe948 --- /dev/null +++ b/src/test/regress/sql/yb.orig.alter_table.sql @@ -0,0 +1,241 @@ +--- +--- Verify renaming on temp tables +--- +CREATE TEMP TABLE temp_table(a int primary key, b int); +CREATE INDEX temp_table_b_idx ON temp_table(b); +ALTER INDEX temp_table_pkey RENAME TO temp_table_pkey_new; +ALTER INDEX temp_table_b_idx RENAME TO temp_table_b_idx_new; + +--- +--- Verify yb_db_admin role can ALTER table +--- +CREATE TABLE foo(a INT UNIQUE); +CREATE TABLE bar(b INT); +ALTER TABLE bar ADD CONSTRAINT baz FOREIGN KEY (b) REFERENCES foo(a); +CREATE TABLE table_other(a int, b int); +CREATE INDEX index_table_other ON table_other(a); +CREATE USER regress_alter_table_user1; +SET SESSION AUTHORIZATION yb_db_admin; +ALTER TABLE table_other RENAME to table_new; +ALTER TABLE table_new OWNER TO regress_alter_table_user1; +ALTER TABLE bar DROP CONSTRAINT baz; +ALTER TABLE pg_database RENAME TO test; -- should fail +ALTER TABLE pg_tablespace OWNER TO regress_alter_table_user1; -- should fail +--- +--- Verify yb_db_admin role can ALTER index +--- +ALTER INDEX index_table_other RENAME TO index_table_other_new; +RESET SESSION AUTHORIZATION; +DROP TABLE foo; +DROP TABLE bar; +DROP TABLE table_new; +DROP USER regress_alter_table_user1; + +--- +--- Verify alter table which requires table rewrite +--- + +--- Table without primary key index +--- Empty table case +CREATE TABLE no_pk_tbl(k INT); +ALTER TABLE no_pk_tbl ADD COLUMN s1 TIMESTAMP DEFAULT clock_timestamp(); +ALTER TABLE no_pk_tbl ADD COLUMN v1 SERIAL; +\d no_pk_tbl; +--- Non-empty case +INSERT INTO no_pk_tbl VALUES(1), (2), (3); +ALTER TABLE no_pk_tbl ADD COLUMN s2 TIMESTAMP DEFAULT clock_timestamp(); +ALTER TABLE no_pk_tbl ADD COLUMN v2 SERIAL; +\d no_pk_tbl; +DROP TABLE no_pk_tbl; + +--- Table with primary key index +--- Empty table case +CREATE TABLE pk_tbl(k INT PRIMARY KEY); +ALTER TABLE pk_tbl ADD COLUMN s1 TIMESTAMP DEFAULT clock_timestamp(); +ALTER TABLE pk_tbl ADD COLUMN v1 SERIAL; +\d pk_tbl; +--- Non-empty case +INSERT INTO pk_tbl VALUES(1), (2), (3); +ALTER TABLE pk_tbl ADD COLUMN s2 TIMESTAMP DEFAULT clock_timestamp(); +ALTER TABLE pk_tbl ADD COLUMN v2 SERIAL; +\d pk_tbl; +DROP TABLE pk_tbl; + +-- Verify cache cleanup of table names when TABLE RENAME fails. +CREATE TABLE rename_test (id int); +SET yb_test_fail_next_ddl TO 1; +ALTER TABLE rename_test RENAME TO foobar; +-- The table name must be unchanged. +SELECT * FROM rename_test; +-- The name 'foobar' must be invalid. +SELECT * FROM foobar; +-- Rename operation must succeed now. +ALTER TABLE rename_test RENAME TO foobar; +DROP TABLE foobar; + +-- +-- ALTER TABLE ADD COLUMN ... DEFAULT tests. +-- +CREATE TABLE foo(a int); +INSERT INTO foo VALUES (1), (2), (3); +-- Test add column with int default value. +ALTER TABLE foo ADD COLUMN b int DEFAULT 6; +INSERT INTO foo(a) VALUES (4); +INSERT INTO foo VALUES (5, 7); +INSERT INTO foo VALUES (6, null); +SELECT * FROM foo ORDER BY a; +CREATE TYPE typefoo AS (a inet, b BIT(3)); +-- Test add column with a UDT default value. +ALTER TABLE foo ADD COLUMN c typefoo DEFAULT ('127.0.0.1', B'010'); +SELECT * FROM foo ORDER BY a; +CREATE FUNCTION functionfoo() +RETURNS TIMESTAMP +LANGUAGE plpgsql STABLE +AS +$$ +BEGIN +RETURN '01-01-2023'; +END; +$$; +-- Test add column with a non-volatile expression default value. +ALTER TABLE foo ADD COLUMN d TIMESTAMP DEFAULT functionfoo(); +SELECT * FROM foo ORDER BY a; +-- Test add column with default value and collation +ALTER TABLE foo ADD COLUMN e varchar DEFAULT 'hi' COLLATE "en_US"; +INSERT INTO foo(a, e) VALUES(7, 'a'); +INSERT INTO foo(a, e) VALUES(8, 'zz'); +SELECT * FROM foo ORDER BY e, a; +SELECT * FROM foo WHERE e COLLATE "C" < 'hi' ORDER BY e; +-- Test updating columns that have missing default values. +UPDATE foo SET d = '01-01-2024' WHERE a = 1; +SELECT * FROM foo ORDER BY a; +UPDATE foo SET b = 8 WHERE b is null; +SELECT * FROM foo WHERE b = 8; +UPDATE foo SET b = null WHERE b = 8; +SELECT * FROM foo WHERE b is null; +-- Test expression pushdown on column with default value. +EXPLAIN SELECT * FROM foo WHERE d = '01-01-2023'; +SELECT * FROM foo WHERE b = 6 ORDER BY a; +-- Verify that we set pg_attribute.atthasmissing and +-- pg_attribute.attmissingval. +SELECT atthasmissing, attmissingval FROM pg_attribute + WHERE attrelid='foo'::regclass; +-- Verify that ALTER TABLE ... SET DEFAULT doesn't change missing values. +ALTER TABLE foo ALTER COLUMN b SET DEFAULT 7; +INSERT INTO foo(a) VALUES (9); +SELECT * FROM foo ORDER BY a; +-- Verify that indexes on columns with missing default values work. +CREATE INDEX ON foo(b); +EXPLAIN SELECT b FROM foo WHERE b = 6; +SELECT b FROM foo WHERE b = 6; +EXPLAIN SELECT * FROM foo WHERE b = 6 ORDER BY a; +SELECT * FROM foo WHERE b = 6 ORDER BY a; +-- Verify that defaults are copied for tables created using CREATE TABLE LIKE +-- clause. +CREATE TABLE dummy (LIKE foo INCLUDING DEFAULTS); +INSERT INTO dummy(a) VALUES (1); +SELECT * FROM dummy; +-- Verify that missing values work after table rewrite. +ALTER TABLE foo ADD PRIMARY KEY (a); +SELECT * FROM foo ORDER BY a; +-- Verify missing default values for partitioned tables. +CREATE TABLE foo_part (a int) PARTITION BY RANGE (a); +CREATE TABLE foo_part_1 PARTITION OF foo_part FOR VALUES FROM (1) TO (6); +CREATE TABLE foo_part_2 PARTITION OF foo_part FOR VALUES FROM (6) TO (11); +INSERT INTO foo_part VALUES (generate_series(1, 10)); +CREATE FUNCTION functionfoopart() +RETURNS TEXT +LANGUAGE plpgsql STABLE +AS +$$ +BEGIN +RETURN 'default'; +END; +$$; +ALTER TABLE foo_part ADD COLUMN b TEXT default functionfoopart(); +INSERT INTO foo_part VALUES (1, null), (6, null); +SELECT * FROM foo_part ORDER BY a, b; +-- Verify that ADD COLUMN ... DEFAULT NOT NULL fails when the default value is +-- null. +ALTER TABLE foo ADD COLUMN g int DEFAULT null NOT NULL; +-- Test add column with volatile default value. +ALTER TABLE foo ADD COLUMN f FLOAT DEFAULT random(); + +-- +-- Tests for ALTER TABLE ... ADD COLUMN ... UNIQUE +-- +CREATE TABLE foo_unique(a int); +INSERT INTO foo_unique VALUES (1), (2), (3); +ALTER TABLE foo_unique ADD COLUMN b int UNIQUE NOT NULL; -- should fail +ALTER TABLE foo_unique ADD COLUMN b int UNIQUE; +ALTER TABLE foo_unique ADD COLUMN c float UNIQUE DEFAULT 1.0; -- should fail +ALTER TABLE foo_unique ADD COLUMN c float UNIQUE NOT NULL DEFAULT random(); +ALTER TABLE foo_unique ADD COLUMN d float UNIQUE CHECK (d >= 1) DEFAULT random(); -- should fail +ALTER TABLE foo_unique ADD COLUMN d float UNIQUE CHECK (d >= 1); +SELECT a,b,d FROM foo_unique ORDER BY a; + +-- +-- Test for cascaded drops on columns +-- +CREATE TABLE test_dropcolumn(a int, b int, c int); +CREATE TYPE test_dropcolumn_type AS (a int, b int); +ALTER TABLE test_dropcolumn ADD COLUMN d test_dropcolumn_type; +DROP TYPE test_dropcolumn_type CASCADE; -- should drop the column d +ALTER TABLE test_dropcolumn ADD COLUMN d int; + +-- +-- Test for ALTER TABLE ... VALIDATE CONSTRAINT +-- +-- check constraints +CREATE TABLE test_validate_constraint(a int, b int); +INSERT INTO test_validate_constraint VALUES (1, 1), (2, 2), (3, 3), (-1, -1); +ALTER TABLE test_validate_constraint ADD CONSTRAINT test_validate_constraint_check CHECK (a > 0) NOT VALID; +ALTER TABLE test_validate_constraint VALIDATE CONSTRAINT test_validate_constraint_check; -- should fail. +DELETE FROM test_validate_constraint WHERE a = -1; +ALTER TABLE test_validate_constraint VALIDATE CONSTRAINT test_validate_constraint_check; +-- foreign key constraints +CREATE TABLE test_validate_constraint_parent(a int PRIMARY KEY); +CREATE TABLE test_validate_constraint_child(b int); +INSERT INTO test_validate_constraint_parent VALUES (1), (2); +INSERT INTO test_validate_constraint_child VALUES (1), (2), (3); +ALTER TABLE test_validate_constraint_child ADD CONSTRAINT test_validate_constraint_child_fk + FOREIGN KEY (b) REFERENCES test_validate_constraint_parent(a) NOT VALID; +ALTER TABLE test_validate_constraint_child + VALIDATE CONSTRAINT test_validate_constraint_child_fk; -- should fail. +INSERT INTO test_validate_constraint_parent VALUES (3); +ALTER TABLE test_validate_constraint_child + VALIDATE CONSTRAINT test_validate_constraint_child_fk; +-- partitioned tables +CREATE TABLE test_validate_constraint_part (a int) PARTITION BY RANGE (a); +CREATE TABLE test_validate_constraint_part_1 + PARTITION OF test_validate_constraint_part FOR VALUES FROM (1) TO (6); +CREATE TABLE test_validate_constraint_part_2 + PARTITION OF test_validate_constraint_part FOR VALUES FROM (6) TO (11); +INSERT INTO test_validate_constraint_part VALUES (generate_series(1, 10)); +ALTER TABLE test_validate_constraint_part + ADD CONSTRAINT test_validate_constraint_part_check CHECK (a % 2 = 0) NOT VALID; +ALTER TABLE test_validate_constraint_part + VALIDATE CONSTRAINT test_validate_constraint_part_check; -- should fail. +DELETE FROM test_validate_constraint_part WHERE a % 2 = 1 AND a < 6; +ALTER TABLE test_validate_constraint_part + VALIDATE CONSTRAINT test_validate_constraint_part_check; -- should still fail. +DELETE FROM test_validate_constraint_part WHERE a % 2 = 1; +ALTER TABLE test_validate_constraint_part + VALIDATE CONSTRAINT test_validate_constraint_part_check; + +-- validate fix for "schema version mismatch" after failed ALTER TABLE. +CREATE TABLE pk(a int primary key); +INSERT INTO pk values (1); +CREATE TABLE fk(a int); +INSERT INTO fk values (2); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk; -- should fail due to FK constraint violation. +BEGIN; +SELECT * from pk; +COMMIT; + +-- verify error message for alter primary key index change owner +CREATE TABLE yb_foo(id INT, PRIMARY KEY(id)); +ALTER INDEX yb_foo_pkey OWNER TO postgres; +-- for temp table we get same error as in PG +CREATE TEMP TABLE pg_foo(id INT, PRIMARY KEY(id)); +ALTER INDEX pg_foo_pkey OWNER TO postgres; diff --git a/src/test/regress/sql/yb.orig.alter_table_rewrite.sql b/src/test/regress/sql/yb.orig.alter_table_rewrite.sql new file mode 100644 index 0000000000000..9aefbe42e1860 --- /dev/null +++ b/src/test/regress/sql/yb.orig.alter_table_rewrite.sql @@ -0,0 +1,716 @@ +-- +-- ALTER TABLE with table rewrite tests. +-- +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 + +-- Suppress NOTICE messages during table rewrite operations. +SET client_min_messages TO WARNING; +-- Test add column operation with table rewrite. +CREATE TABLESPACE test_tablespace LOCATION '/invalid'; +CREATE TABLE table_rewrite_test1_fk1(a int PRIMARY KEY); +INSERT INTO table_rewrite_test1_fk1 VALUES (1), (2), (3), (4); +CREATE TABLE table_rewrite_test1 + (a int REFERENCES table_rewrite_test1_fk1(a), b int, c text, + d int generated always as identity, drop_me int, e int DEFAULT 10, + f int NOT NULL, username text, + PRIMARY KEY (a ASC)) + TABLESPACE test_tablespace; +ALTER TABLE table_rewrite_test1 DROP COLUMN drop_me; +INSERT INTO table_rewrite_test1 (a, b, c, e, f, username) + VALUES(1, 1, 'text', 10, 1, 'user1'), (2, 2, 'text2', 20, 2, 'user2'), + (3, 3, 'text33', 30, 3, 'user3'); +-- create some dependencies on the table. +-- 1. check constraint. +ALTER TABLE table_rewrite_test1 ADD CONSTRAINT a_check CHECK(a > 0); +-- 2. indexes. +CREATE UNIQUE INDEX test_index ON table_rewrite_test1(b) + TABLESPACE test_tablespace; +CREATE INDEX test_index2 ON table_rewrite_test1((e * 2)) + TABLESPACE test_tablespace; +-- 3. foreign key constraint. +CREATE TABLE table_rewrite_test1_fk2(a int REFERENCES table_rewrite_test1(b)); +-- 4. dependent view. +CREATE VIEW table_rewrite_test1_view AS SELECT a, b FROM table_rewrite_test1; +-- 5. column with missing default value. +ALTER TABLE table_rewrite_test1 ADD COLUMN g int DEFAULT 5; +-- 6. dependent trigger. +CREATE FUNCTION dummy() RETURNS TRIGGER +AS $$ +BEGIN + RETURN NULL; +END; +$$ LANGUAGE PLPGSQL; +CREATE TRIGGER dummy_trigger + AFTER INSERT + ON table_rewrite_test1 + FOR EACH ROW + EXECUTE PROCEDURE dummy(); +-- 6. enable RLS and create policy objects. +ALTER TABLE table_rewrite_test1 ENABLE ROW LEVEL SECURITY; +CREATE USER user1; +CREATE POLICY p ON table_rewrite_test1 + FOR SELECT TO user1 USING (username = CURRENT_USER); +GRANT SELECT ON table_rewrite_test1 TO user1; +-- 7. extended statistics object. +CREATE STATISTICS s1(dependencies) ON a, b FROM table_rewrite_test1; +-- perform several table rewrite operations. +ALTER TABLE table_rewrite_test1 ADD COLUMN h SERIAL, ADD COLUMN i BIGSERIAL; +ALTER TABLE table_rewrite_test1 + ADD COLUMN j timestamp DEFAULT clock_timestamp() NOT NULL, + DROP CONSTRAINT table_rewrite_test1_pkey; +ALTER TABLE table_rewrite_test1 ADD PRIMARY KEY(a DESC); +ALTER TABLE table_rewrite_test1 ALTER c TYPE int USING LENGTH(c); +-- verify table data. +SELECT a, b, username, c, d, e, f, g, h, i FROM table_rewrite_test1 ORDER BY a; +-- verify that we preserved dependent objects. +\d table_rewrite_test1; +\d test_index; +\d test_index2; +SELECT * FROM table_rewrite_test1_view ORDER BY a; +EXPLAIN SELECT a, b, username, c, d, e, f, g, h, i FROM table_rewrite_test1 WHERE b = 3; +SELECT a, b, username, c, d, e, f, g, h, i FROM table_rewrite_test1 WHERE b = 3; +EXPLAIN SELECT a, b, username, c, d, e, f, g, h, i FROM table_rewrite_test1 WHERE e * 2 = 20; +SELECT a, b, username, c, d, e, f, g, h, i FROM table_rewrite_test1 WHERE e * 2 = 20; +-- verify PK constraint. +INSERT INTO table_rewrite_test1(a, f) VALUES (3, 4); -- should fail. +-- verify FK constraint where the altered table references a FK table. +INSERT INTO table_rewrite_test1(a, f) VALUES (4, 4); +INSERT INTO table_rewrite_test1(a, f) VALUES (5, 5); -- should fail. +INSERT INTO table_rewrite_test1_fk1 VALUES (5), (6); +INSERT INTO table_rewrite_test1(a, f) VALUES (5, 5); +DELETE FROM table_rewrite_test1_fk1 WHERE a = 3; -- should fail. +-- verify FK constraint where another table references the altered table via +-- an FK constraint.. +INSERT INTO table_rewrite_test1_fk2(a) VALUES (3); +INSERT INTO table_rewrite_test1_fk2(a) VALUES (6); -- should fail. +INSERT INTO table_rewrite_test1(a, b, f) VALUES (6, 4, 4); +INSERT INTO table_rewrite_test1_fk2(a) VALUES (4); +DELETE FROM table_rewrite_test1 WHERE b = 3; -- should fail. +DELETE FROM table_rewrite_test1_fk2; +DELETE FROM table_rewrite_test1 WHERE b = 3; +INSERT INTO table_rewrite_test1_fk2 VALUES (3); -- should fail. +-- verify policies and RLS. +SET ROLE user1; +SELECT a, b, username, c, d, e, f, g, h, i FROM table_rewrite_test1 ORDER BY a; +SET ROLE yugabyte; + +-- verify future ALTER operations on the table succeed. +ALTER TABLE table_rewrite_test1 ADD COLUMN k int, DROP COLUMN b CASCADE; +ALTER TABLE table_rewrite_test1 RENAME TO table_rewrite_test1_new; + +-- cleanup. +DROP TABLE table_rewrite_test1_new CASCADE; +DROP USER user1; + +-- Test table rewrite operations on a partitioned table. +CREATE TABLE test_partitioned(a int, PRIMARY KEY (a ASC), b text) PARTITION BY RANGE (a); +CREATE TABLE test_partitioned_part1 PARTITION OF test_partitioned FOR VALUES FROM (1) TO (6); +CREATE TABLE test_partitioned_part2 PARTITION OF test_partitioned FOR VALUES FROM (6) TO (11); +CREATE INDEX test_partitioned_index ON test_partitioned(b); +INSERT INTO test_partitioned VALUES(generate_series(1, 10), 'text'); +ALTER TABLE test_partitioned ADD COLUMN c SERIAL, ALTER COLUMN b TYPE int USING length(b); +ALTER TABLE test_partitioned DROP CONSTRAINT test_partitioned_pkey; +ALTER TABLE test_partitioned ADD PRIMARY KEY (a ASC); +SELECT * FROM test_partitioned; +EXPLAIN (COSTS OFF) SELECT * FROM test_partitioned WHERE b = 4; +SELECT * FROM test_partitioned WHERE b = 4; + +-- Test table rewrite on temp tables. +CREATE TEMP TABLE temp_table_rewrite_test(a int); +INSERT INTO temp_table_rewrite_test VALUES(1), (2), (3); +CREATE INDEX ON temp_table_rewrite_test(a); +ALTER TABLE temp_table_rewrite_test ADD COLUMN c SERIAL; +SELECT * FROM temp_table_rewrite_test ORDER BY a; + +-- Test rewrite with colocation. +CREATE DATABASE mydb WITH colocation = true; +\c mydb; +-- Suppress NOTICE messages during table rewrite operations. +SET client_min_messages TO WARNING; +CREATE TABLE base (col int, col2 int); +CREATE INDEX base_idx ON base(col2); +INSERT INTO base VALUES (1, 3), (2, 2), (3, 1); +ALTER TABLE base ADD PRIMARY KEY (col HASH); -- should fail. +ALTER TABLE base + ADD PRIMARY KEY (col), ADD COLUMN col3 float DEFAULT random(); +ALTER TABLE base ALTER COLUMN col2 TYPE int2; +ALTER TABLE base ADD COLUMN col4 SERIAL; +SELECT col, col2, col4 FROM base; +SELECT col, col2, col4 FROM base WHERE col2 = 1; +ALTER TABLE base DROP CONSTRAINT base_pkey; +SELECT col, col2, col4 FROM base ORDER BY col; +\d+ base; +SELECT num_tablets, num_hash_key_columns, is_colocated FROM + yb_table_properties('base_idx'::regclass); +CREATE UNIQUE INDEX base_idx_unique ON base(col); +ALTER TABLE base ADD PRIMARY KEY USING INDEX base_idx_unique; +INSERT INTO base VALUES (1, 1); -- should fail. +INSERT INTO base VALUES (4, 4), (5, 5); +SELECT col, col2 FROM base; +CREATE TABLE base2 (col int, col2 int) WITH (COLOCATION=false); +CREATE INDEX base2_idx ON base2(col2); +INSERT INTO base2 VALUES (1, 3), (2, 2), (3, 1); +ALTER TABLE base2 + ADD PRIMARY KEY (col ASC), ADD COLUMN col3 float DEFAULT random(); +ALTER TABLE base2 ALTER COLUMN col2 TYPE int2; +ALTER TABLE base2 ADD COLUMN col4 SERIAL; +SELECT col, col2, col4 FROM base2; +SELECT col, col2, col4 FROM base2 WHERE col2 = 1; +ALTER TABLE base2 DROP CONSTRAINT base2_pkey; +SELECT col, col2, col4 FROM base2 ORDER BY col; +\d+ base2; +SELECT num_tablets, num_hash_key_columns, is_colocated FROM + yb_table_properties('base2_idx'::regclass); +CREATE TABLE t5(c0 money UNIQUE); +ALTER TABLE ONLY t5 ALTER COLUMN c0 SET DATA TYPE money USING c0::text::money; +\d t5; +\c yugabyte; +-- Suppress NOTICE messages during table rewrite operations. +SET client_min_messages TO WARNING; +-- Test rewrite with tablegroups. +CREATE TABLEGROUP tg1; +CREATE TABLE test_tablegroup (id int) TABLEGROUP tg1; +INSERT INTO test_tablegroup VALUES (1), (2); +CREATE TABLE test_tablegroup2 (id int, id2 int UNIQUE WITH + (colocation_id=100501)) WITH (colocation_id=100500) TABLEGROUP tg1; +INSERT INTO test_tablegroup2 VALUES (1, 1), (2, 2); +ALTER TABLE test_tablegroup ADD PRIMARY KEY (id ASC); +ALTER TABLE test_tablegroup ADD COLUMN c SERIAL; +ALTER TABLE test_tablegroup2 ADD PRIMARY KEY (id ASC); +ALTER TABLE test_tablegroup2 ADD COLUMN c SERIAL; +SELECT * FROM test_tablegroup; +SELECT * FROM test_tablegroup2; +\d test_tablegroup; +\d test_tablegroup2; +ALTER TABLE test_tablegroup DROP CONSTRAINT test_tablegroup_pkey; +ALTER TABLE test_tablegroup2 DROP CONSTRAINT test_tablegroup2_pkey; +SELECT * FROM test_tablegroup ORDER BY id; +SELECT * FROM test_tablegroup2 ORDER BY id; +\d test_tablegroup; +\d test_tablegroup2; + +-- Tests for ALTER TABLE ... ADD COLUMN rewrite operations. +-- verify split options are preserved. +CREATE TABLE test_add_column(a int, b int) + SPLIT INTO 2 TABLETS; +CREATE INDEX test_add_column_idx ON test_add_column(b ASC) + SPLIT AT VALUES ((5), (10)); +INSERT INTO test_add_column VALUES (1, 1); +ALTER TABLE test_add_column ADD COLUMN c SERIAL; +ALTER TABLE test_add_column ADD COLUMN d SERIAL PRIMARY KEY; +SELECT num_tablets, num_hash_key_columns FROM + yb_table_properties('test_add_column'::regclass); +SELECT yb_get_range_split_clause('test_add_column_idx'::regclass); +INSERT INTO test_add_column(a, d) VALUES (2, 1); -- should fail. +INSERT INTO test_add_column(a, d) VALUES (2, 2); +SELECT * FROM test_add_column; + +-- Tests for ALTER TABLE ADD/DROP PRIMARY KEY. +-- basic tests. +CREATE TABLE nopk (id int, v int); +ALTER TABLE nopk ADD PRIMARY KEY (id); +ALTER TABLE nopk ADD PRIMARY KEY (id); -- should fail. +ALTER TABLE nopk DROP CONSTRAINT nopk_pkey; +INSERT INTO nopk VALUES (1, 1); +INSERT INTO nopk VALUES (1, 2); +ALTER TABLE nopk ADD PRIMARY KEY (id); -- should fail. +DELETE FROM nopk WHERE v = 2; +ALTER TABLE nopk ADD PRIMARY KEY (id); +ALTER TABLE nopk DROP CONSTRAINT nopk_pkey; +ALTER TABLE nopk ALTER COLUMN id DROP NOT NULL; +INSERT INTO nopk VALUES (null); +ALTER TABLE nopk ADD PRIMARY KEY (id); -- should fail. +DROP TABLE nopk; +-- test complex pks. +CREATE TABLE complex_pk (v1 int, v2 text, v3 char, v4 boolean); +INSERT INTO complex_pk VALUES (1, '111', '1', 'true'), (2, '222', '2', 'false'); +ALTER TABLE complex_pk ADD PRIMARY KEY ((v1, v2) HASH, v3 ASC, v4 DESC); +SELECT * FROM yb_table_properties('complex_pk'::regclass); +INSERT INTO complex_pk VALUES (2, '222', '3', 'true'); +INSERT INTO complex_pk VALUES (2, '222', '2', 'false'); -- should fail. +SELECT * FROM complex_pk ORDER BY v1, v2, v3, v4; +ALTER TABLE complex_pk DROP CONSTRAINT complex_pk_pkey; +INSERT INTO complex_pk VALUES (2, '222', '3', 'true'); +SELECT * FROM complex_pk ORDER BY v1, v2, v3, v4; +-- test range pks. +CREATE TABLE range_pk (id int); +INSERT INTO range_pk VALUES (1), (2), (3); +ALTER TABLE range_pk ADD PRIMARY KEY (id DESC); +SELECT * FROM range_pk; -- should be in descending order. +ALTER TABLE range_pk DROP CONSTRAINT range_pk_pkey; +SELECT * FROM range_pk ORDER BY id; +-- test include pks. +CREATE TABLE include_pk (id int, v1 int, v2 int); +INSERT INTO include_pk VALUES (1, 11, 111), (2, 22, 222); +ALTER TABLE include_pk ADD PRIMARY KEY (id) INCLUDE (v1, v2); +INSERT INTO include_pk VALUES (3, 11, 111); +EXPLAIN SELECT v1 FROM include_pk WHERE id = 2; +SELECT v1 FROM include_pk WHERE id = 2; +INSERT INTO include_pk VALUES (3, 99, 999); -- should fail. +SELECT * FROM include_pk ORDER BY id; +EXPLAIN SELECT v1 FROM include_pk WHERE id = 3; +SELECT v1 FROM include_pk WHERE id = 3; +ALTER TABLE include_pk DROP CONSTRAINT include_pk_pkey; +EXPLAIN SELECT v1 FROM include_pk WHERE id = 3; +SELECT v1 FROM include_pk WHERE id = 3; +-- test pk with UDT. +CREATE TYPE typeid AS (i int); +CREATE TABLE nopk_udt (id typeid, v int); +ALTER TABLE nopk_udt ADD PRIMARY KEY (id); -- should fail. +-- test pk USING INDEX. +CREATE TABLE nopk_usingindex (id int) SPLIT INTO 5 TABLETS; +INSERT INTO nopk_usingindex VALUES (1), (2), (3); +CREATE INDEX nopk_idx ON nopk_usingindex(id); +CREATE UNIQUE INDEX nopk_idx2 ON nopk_usingindex (id HASH); +CREATE UNIQUE INDEX nopk_idx3 ON nopk_usingindex (id ASC); +CREATE UNIQUE INDEX nopk_idx4 ON nopk_usingindex (id DESC); +ALTER TABLE nopk_usingindex ADD PRIMARY KEY USING INDEX nopk_idx; -- should fail. +INSERT INTO nopk_usingindex VALUES (null); +ALTER TABLE nopk_usingindex ADD PRIMARY KEY USING INDEX nopk_idx2; -- should fail. +DELETE FROM nopk_usingindex WHERE id IS NULL; +ALTER TABLE nopk_usingindex ADD PRIMARY KEY USING INDEX nopk_idx2; +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('nopk_usingindex'::regclass); +INSERT INTO nopk_usingindex VALUES (4); +INSERT INTO nopk_usingindex VALUES (1); -- should fail. +INSERT INTO nopk_usingindex VALUES (null); -- should fail. +SELECT * FROM nopk_usingindex ORDER BY id; +DROP INDEX nopk_idx2; -- should fail. +ALTER TABLE nopk_usingindex DROP CONSTRAINT nopk_idx2; +ALTER TABLE nopk_usingindex ADD PRIMARY KEY USING INDEX nopk_idx3; +SELECT * FROM nopk_usingindex; +DROP INDEX nopk_idx3; -- should fail. +ALTER TABLE nopk_usingindex DROP CONSTRAINT nopk_idx3; +ALTER TABLE nopk_usingindex ADD PRIMARY KEY USING INDEX nopk_idx4; -- should fail (not supported in PG). +-- test adding/dropping pks on partitioned tables. +CREATE TABLE nopk_whole (id int) PARTITION BY LIST (id); +CREATE TABLE nopk_part1 PARTITION OF nopk_whole FOR VALUES IN (1, 2, 3); +CREATE TABLE nopk_part2 PARTITION OF nopk_whole FOR VALUES IN (10, 20, 30, 40) + PARTITION BY LIST (id); +CREATE TABLE nopk_part2_part1 PARTITION OF nopk_part2 FOR VALUES IN (10, 20); +CREATE TABLE nopk_part2_part2 PARTITION OF nopk_part2 FOR VALUES IN (30, 40); +ALTER TABLE nopk_whole ADD PRIMARY KEY (id); +-- verify that we cannot drop inherited PK constraints. +ALTER TABLE nopk_part1 DROP CONSTRAINT nopk_part1_pkey; -- should fail. +ALTER TABLE nopk_part2 DROP CONSTRAINT nopk_part2_pkey; -- should fail. +ALTER TABLE nopk_part2_part1 DROP CONSTRAINT nopk_part2_part1_pkey; -- should fail. +ALTER TABLE nopk_part2_part2 DROP CONSTRAINT nopk_part2_part2_pkey; -- should fail. +ALTER TABLE nopk_whole DROP CONSTRAINT nopk_whole_pkey; +-- verify that we can successfully add and drop PK constraints on partitions. +ALTER TABLE nopk_part1 ADD PRIMARY KEY (id); +ALTER TABLE nopk_part1 DROP CONSTRAINT nopk_part1_pkey; +ALTER TABLE nopk_part2 ADD PRIMARY KEY (id); +ALTER TABLE nopk_part2 DROP CONSTRAINT nopk_part2_pkey; +ALTER TABLE nopk_part2_part1 ADD PRIMARY KEY (id); +ALTER TABLE nopk_part2_part1 DROP CONSTRAINT nopk_part2_part1_pkey; +ALTER TABLE nopk_part2_part2 ADD PRIMARY KEY (id); +ALTER TABLE nopk_part2_part2 DROP CONSTRAINT nopk_part2_part2_pkey; +-- tests for altered table referenced by a partitioned FK table. +CREATE TABLE test (id int unique); +CREATE TABLE test_part (id int REFERENCES test(id)) PARTITION BY RANGE(id); +CREATE TABLE test_part_1 PARTITION OF test_part FOR VALUES FROM (1) TO (100); +INSERT INTO test VALUES (1); +INSERT INTO test_part VALUES (1); +ALTER TABLE test ADD PRIMARY KEY (id); +SELECT * FROM test; +SELECT * FROM test_part ORDER BY id; +INSERT INTO test_part VALUES (2); -- should fail. +INSERT INTO test_part_1 VALUES (2); -- should fail. +DROP TABLE test CASCADE; +-- tests for split options. +-- verify split options are preserved when we add/drop a hash key. +CREATE TABLE nopk (id int, a int, b text, c float, d timestamp, e money) + SPLIT INTO 5 TABLETS; +CREATE INDEX nopk_idx1 ON nopk(id) SPLIT INTO 4 TABLETS; +CREATE INDEX nopk_idx2 ON nopk(id ASC, a ASC, b ASC, c ASC, d ASC, e ASC) + SPLIT AT VALUES + ((10, 20, E'test123\"\"''\\\\\\u0068\\u0069', '-Infinity', '1999-01-01', + '12.34'), + (20, 30, E'test123\"\"''\\\\\\u0068\\u0069z', 'Infinity', '2023-01-01', + '56.78')); +ALTER TABLE nopk ADD PRIMARY KEY (id); +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('nopk'::regclass); +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('nopk_idx1'::regclass); +SELECT yb_get_range_split_clause('nopk_idx2'::regclass); +ALTER TABLE nopk DROP CONSTRAINT nopk_pkey; +-- verify split options are not preserved when we add a range key, and only the number of +-- tablets is preserved when we drop a range key. +CREATE TABLE nopk2 (id int) SPLIT INTO 5 TABLETS; +ALTER TABLE nopk2 ADD PRIMARY KEY (id); +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('nopk2'::regclass); +CREATE TABLE range_pk_test (id int, primary key(id asc)) SPLIT AT VALUES ((5), (10), (15), (20)); +ALTER TABLE range_pk_test DROP CONSTRAINT range_pk_test_pkey; +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('range_pk_test'::regclass); +-- rules: +-- 1. new table rewrite + rule +-- 2. old table rewrite + rule -- fail +-- 3. new table rewrite + no more rule +-- 4. old table rewrite + no more rule +CREATE TABLE pk_rule_table (id int); +CREATE RULE pk_rule AS + ON INSERT TO pk_rule_table + DO INSTEAD DELETE FROM pk_rule_table; +ALTER TABLE pk_rule_table ADD PRIMARY KEY (id); +SET yb_enable_alter_table_rewrite = OFF; +ALTER TABLE pk_rule_table DROP CONSTRAINT pk_rule_table_pkey; -- should fail. +RESET yb_enable_alter_table_rewrite; +DROP RULE pk_rule ON pk_rule_table; +ALTER TABLE pk_rule_table DROP CONSTRAINT pk_rule_table_pkey; +SET yb_enable_alter_table_rewrite = OFF; +ALTER TABLE pk_rule_table ADD PRIMARY KEY (id); +RESET yb_enable_alter_table_rewrite; + +-- Tests for ALTER TYPE. +-- basic tests. +CREATE TABLE varchar_table(id SERIAL, c1 varchar(10), PRIMARY KEY (id ASC)); +CREATE TABLE int4_table(id SERIAL, c1 int4, PRIMARY KEY (id ASC)); +ALTER TABLE varchar_table ALTER c1 TYPE int; -- should fail. +ALTER TABLE int4_table ALTER c1 TYPE int8; +INSERT INTO int4_table(c1) VALUES (2 ^ 40); +ALTER TABLE int4_table ALTER c1 TYPE int4; -- should fail. +INSERT INTO varchar_table(c1) VALUES ('aa'); +ALTER TABLE varchar_table ALTER c1 TYPE varchar(1); -- should fail. +INSERT INTO varchar_table(c1) VALUES ('a'), (2), ('aaa'); +ALTER TABLE varchar_table ALTER c1 TYPE text; +SELECT * from varchar_table; +INSERT INTO varchar_table(c1) VALUES ('a'), ('bb'), ('ccc'); +ALTER TABLE varchar_table ALTER c1 TYPE int USING length(c1); +SELECT * from varchar_table; +ALTER TABLE varchar_table ALTER c1 TYPE double precision USING sqrt(c1); +SELECT * from varchar_table; +ALTER TABLE int4_table ADD COLUMN c2 varchar, ADD COLUMN c3 int, ALTER c1 TYPE varchar; +INSERT INTO int4_table(c1, c2, c3) VALUES ('a', 'a', 1), ('b', 'b', 2), ('c', 'c', 3); +SELECT * from int4_table; +CREATE TABLE int4_table2(c1 int) PARTITION BY RANGE(c1); +ALTER TABLE int4_table2 ALTER c1 TYPE varchar; -- should fail. +CREATE TABLE pk_table(c1 int primary key); +INSERT INTO pk_table VALUES (1), (2); +ALTER TABLE pk_table ALTER c1 TYPE varchar; +SELECT * from pk_table ORDER BY c1 ASC; +INSERT INTO pk_table VALUES (1); -- should fail. +ALTER TABLE pk_table ALTER c1 TYPE int USING length(c1); -- should fail. +-- test ALTER TYPE on a column that is a part of a view/rule: +-- 1. column part of view --fail +-- 2. column not part of view +-- 3. column part of view via _RETURN rule -- fail +-- 4. column not part of view via _RETURN rule +-- 5. rule on other table/view that affects this table +-- 6. rule on other table/view + rule on this table +-- 7. rule on other table/view + rule on this table + old rewrite -- fail +-- 8. rule on other table/view + no more rule on this table +-- 9. rule on other table/view (this is dropped for #22063) + no more rule on this table + old rewrite +CREATE TABLE test_table (c1 int, c2 varchar, c3 varchar, c4 varchar); +CREATE VIEW test_view AS SELECT c2 FROM test_table; +ALTER TABLE test_table ALTER c2 TYPE int USING length(c2); -- should fail. +ALTER TABLE test_table ALTER c3 TYPE varchar(1); +CREATE TABLE dummy_table (c3 varchar); +CREATE RULE "_RETURN" AS + ON SELECT TO dummy_table + DO INSTEAD + SELECT c3 FROM test_table; +ALTER TABLE test_table ALTER c3 TYPE varchar(1); -- should fail. +ALTER TABLE test_table ALTER c4 TYPE varchar(1); +CREATE RULE dummy_rule AS + ON INSERT TO dummy_table + DO INSTEAD DELETE FROM test_table; +ALTER TABLE test_table ALTER c4 TYPE char; +CREATE RULE test_rule AS + ON INSERT TO test_table + DO INSTEAD DELETE FROM dummy_table; +ALTER TABLE test_table ALTER c4 TYPE varchar(1); +SET yb_enable_alter_table_rewrite = OFF; +ALTER TABLE test_table ALTER c4 TYPE char; -- should fail. +RESET yb_enable_alter_table_rewrite; +DROP RULE test_rule ON test_table; +ALTER TABLE test_table ALTER c4 TYPE char; +SET yb_enable_alter_table_rewrite = OFF; +-- TODO(#22063): DROP VIEW is needed to avoid non-deterministic error. +DROP VIEW dummy_table; +ALTER TABLE test_table ALTER c4 TYPE varchar(1); +RESET yb_enable_alter_table_rewrite; +DROP TABLE test_table CASCADE; +-- test ALTER TYPE on a foreign key column. +CREATE TABLE fk_table (c1 varchar primary key); +CREATE TABLE test_table (c1 varchar primary key references fk_table(c1)); +CREATE TABLE fk_table2 (c1 varchar references test_table(c1)); +INSERT INTO fk_table VALUES ('a'), ('aa'), ('aaa'); +INSERT INTO test_table VALUES ('a'), ('aa'), ('aaa'); +INSERT INTO fk_table2 VALUES ('a'), ('aa'), ('aaa'); +ALTER TABLE fk_table ALTER c1 TYPE int USING LENGTH(c1); -- should fail. +ALTER TABLE fk_table ALTER c1 TYPE varchar(3); +\d fk_table; +\d test_table; +\d fk_table2; +INSERT INTO fk_table2 VALUES ('aaaa'); -- should fail. +INSERT INTO test_table VALUES ('aaaa'); -- should fail. +DROP TABLE test_table CASCADE; +-- test ALTER TYPE where an index with a default tablespace is re-created. +CREATE TABLESPACE test_tblspc LOCATION '/invalid'; +SET default_tablespace = test_tblspc; +CREATE TABLE test_table (c1 int, c2 int); +CREATE INDEX test_index ON test_table(c1); +ALTER TABLE test_table ALTER c1 TYPE int2; +\d test_index; +-- test ALTER TYPE on a table with a range key. +CREATE TABLE range_key_table(col1 int, a int, b int, col2 int, c text, d text, + col3 int, e int, PRIMARY KEY((a, b) HASH, c ASC, d DESC) INCLUDE (e)); +INSERT INTO range_key_table(a, b, c, d) + VALUES (1, 1, 'a', 'e'), (1, 1, 'aa', 'f'), (1, 1, 'aa', 'g'); +CREATE INDEX ON range_key_table(c ASC); +ALTER TABLE range_key_table ALTER c TYPE int USING length(c); +SELECT a, b, c, d FROM range_key_table; +\d range_key_table; +-- tests for split options. +CREATE TABLE test1 (c1 varchar, c2 varchar) SPLIT INTO 5 TABLETS; +CREATE INDEX idx1 ON test1(c1) SPLIT INTO 5 TABLETS; +CREATE INDEX idx2 ON test1(c1 HASH, c2 ASC) SPLIT INTO 5 TABLETS; +CREATE INDEX idx3 ON test1(c2 HASH) INCLUDE(c1) SPLIT INTO 5 TABLETS; +CREATE INDEX idx4 ON test1(c1 ASC) SPLIT AT VALUES (('h')); +ALTER TABLE test1 ALTER c1 TYPE int USING length(c1); +-- verify hash split options on the table are preserved. +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('test1'::regclass); +-- verify hash split options on the indexes are preserved. +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('idx1'::regclass); +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('idx2'::regclass); +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('idx3'::regclass); +SELECT yb_get_range_split_clause('idx4'::regclass); +CREATE TABLE test2 (c1 varchar, c2 varchar, PRIMARY KEY(c1 ASC, c2 DESC)) + SPLIT AT VALUES (('h', 20)); +ALTER TABLE test2 ALTER c1 TYPE int USING length(c1); +CREATE TABLE test3 (c1 varchar, c2 varchar, PRIMARY KEY(c2 ASC)) + SPLIT AT VALUES ((E'test123\"\"''\\\\\\u0068\\u0069')); +ALTER TABLE test3 ALTER c1 TYPE int USING length(c1); +-- verify range split options on the table are only preserved when the altered column is not a +-- part of the table's key. +SELECT yb_get_range_split_clause('test2'::regclass); +SELECT yb_get_range_split_clause('test3'::regclass); +-- test ALTER TYPE on temp tables. +CREATE TEMP TABLE test_table (col1 text UNIQUE); +INSERT INTO test_table VALUES ('1'), ('01'); +ALTER TABLE test_table ALTER COLUMN col1 TYPE integer using col1::int; +ALTER TABLE test_table DROP CONSTRAINT test_table_col1_key; +ALTER TABLE test_table ALTER COLUMN col1 TYPE integer using col1::int; +SELECT * FROM test_table; + +-- +-- Test ALTER TABLE ... ADD COLUMN ... GENERATED ALWAYS AS IDENTITY +-- +CREATE TABLE test_identity (id int, PRIMARY KEY (id ASC)); +INSERT INTO test_identity VALUES (1), (2), (3); +ALTER TABLE test_identity ADD COLUMN id2 int GENERATED ALWAYS AS IDENTITY; +INSERT INTO test_identity VALUES (4, 4); -- should fail +INSERT INTO test_identity OVERRIDING SYSTEM VALUE VALUES (4, 4); +SELECT * FROM test_identity; + +-- +-- Test ALTER TABLE ... DROP COLUMN on a primary key column. +-- +-- basic test +CREATE TABLE test_drop_pk_column (id int PRIMARY KEY, v int); +INSERT INTO test_drop_pk_column VALUES (1, 1), (2, 2), (3, 3); +ALTER TABLE test_drop_pk_column DROP COLUMN id; +\d test_drop_pk_column; +INSERT INTO test_drop_pk_column VALUES (1); +SELECT * FROM test_drop_pk_column ORDER BY v; +-- test composite PK +CREATE TABLE test_drop_pk_column_composite (id int, v int, t int, PRIMARY KEY (id HASH, v ASC)); +INSERT INTO test_drop_pk_column_composite VALUES (1, 1, 1), (2, 2, 2), (3, 3, 3); +ALTER TABLE test_drop_pk_column_composite DROP COLUMN id; +\d test_drop_pk_column_composite; +INSERT INTO test_drop_pk_column_composite VALUES (1, 1); +SELECT * FROM test_drop_pk_column_composite ORDER BY v; +-- test partitioned table +CREATE TABLE test_drop_pk_column_part (id int PRIMARY KEY, v int) PARTITION BY RANGE (id); +CREATE TABLE test_drop_pk_column_part1 PARTITION OF test_drop_pk_column_part FOR VALUES FROM (1) TO (10); +ALTER TABLE test_drop_pk_column_part DROP COLUMN id; -- should fail. +-- test rewrite on child table with a primary key column. +CREATE TABLE parent(a int, c int) PARTITION BY RANGE(a); +CREATE TABLE child(a int, c int, primary key (c)); +ALTER TABLE parent ATTACH PARTITION child FOR VALUES FROM (1) TO (100); +ALTER TABLE parent DROP COLUMN c; +INSERT INTO parent VALUES (1); +SELECT * FROM parent; + +-- +-- Test index rewrites during ALTER TYPE when the base table is not rewritten. +-- +-- test that indexes are correctly rebuilt when collation changes. +-- without rebuilding, queries can return incorrect results. +CREATE TABLE collation_test (name varchar COLLATE "en_US"); +CREATE INDEX idx_collation ON collation_test(name ASC); +-- insert test data with special characters +INSERT INTO collation_test VALUES ('abc'), (U&'\00E4bc'), ('xyz'); +-- query before collation change +SET enable_seqscan = off; +SET enable_sort = off; +-- should return: xyz +SELECT encode(name::bytea, 'escape') as utf8_bytes + FROM collation_test WHERE name > 'x'; +-- store the table's relfilenode before ALTER TYPE +CREATE TEMP TABLE table_info AS + SELECT relfilenode FROM pg_class WHERE relname = 'collation_test'; +-- store the index's relfilenode and OID before ALTER TYPE +CREATE TEMP TABLE index_info AS + SELECT relfilenode, oid FROM pg_class WHERE relname = 'idx_collation'; +-- change collation (no table rewrite needed) +ALTER TABLE collation_test ALTER COLUMN name TYPE varchar COLLATE "C"; +-- check if table was rewritten (relfilenode changed) -- we expect it to not be rewritten +SELECT + CASE + WHEN old.relfilenode = new.relfilenode + THEN 'Table not rewritten' + ELSE 'Table rewritten' + END as result +FROM table_info old, pg_class new +WHERE new.relname = 'collation_test'; +-- check if index was rebuilt -- we expect it to be rebuilt (new oid and relfilenode) +SELECT + CASE WHEN old.relfilenode = new.relfilenode THEN 'relfilenode reused' ELSE 'relfilenode changed' END, + CASE WHEN old.oid = new.oid THEN 'OID reused' ELSE 'OID changed' END +FROM index_info old, pg_class new +WHERE new.relname = 'idx_collation'; +-- query after collation change should return correct results +-- should return: xyz and 'U&'\00E4bc'' +\set explain 'EXPLAIN (COSTS OFF)' +\set Q1 '/*+ IndexScan(collation_test idx_collation) */' +\set Q2 '/*+ SeqScan(collation_test) */' +\set Pnext :iter_Q2 +SELECT $$ +:P :Q +SELECT encode(name::bytea, 'escape') as utf8_bytes +FROM collation_test WHERE name > 'x' ORDER BY name; +$$ AS query \gset +\i :iter_P2 +DROP TABLE table_info; +DROP TABLE index_info; +RESET enable_seqscan; +RESET enable_sort; + +-- test that indexes are NOT unnecessarily rebuilt when compatible. +CREATE TABLE compatible_test (val varchar(10)); +CREATE INDEX idx_compatible ON compatible_test(val); +INSERT INTO compatible_test VALUES ('abc'), ('def'); +-- store index relfilenode, OID, and atttypmod before ALTER TYPE. +CREATE TEMP TABLE index_info AS + SELECT c.relfilenode, c.oid, a.atttypmod + FROM pg_class c, pg_attribute a + WHERE c.relname = 'idx_compatible' AND a.attrelid = c.oid AND a.attnum = 1; +ALTER TABLE compatible_test ALTER COLUMN val TYPE varchar(100); +-- index should be reused: same relfilenode, same OID, but atttypmod updated. +SELECT + CASE WHEN old.relfilenode = new.relfilenode THEN 'relfilenode reused' ELSE 'relfilenode changed' END, + CASE WHEN old.oid = new.oid THEN 'OID reused' ELSE 'OID changed' END, + CASE WHEN old.atttypmod <> a.atttypmod THEN 'atttypmod updated' ELSE 'atttypmod unchanged' END +FROM index_info old, pg_class new, pg_attribute a +WHERE new.relname = 'idx_compatible' AND a.attrelid = new.oid AND a.attnum = 1; +-- index scan should return correct results after in-place update. +SET enable_seqscan = off; +SELECT * FROM compatible_test WHERE val = 'abc'; +RESET enable_seqscan; +-- test duplicate insert into unique index after ALTER TYPE without rewrite. +CREATE TABLE test (t varchar(10)); +CREATE UNIQUE INDEX ON test(t); +INSERT INTO test VALUES ('hi'); +ALTER TABLE test ALTER COLUMN t TYPE varchar(20); +INSERT INTO test VALUES ('hi'); + +-- test unique constraint, multi-column index, expression index, and mixed +-- compatible/incompatible in a single table. +CREATE TABLE inplace_test (a varchar(10) UNIQUE, b varchar(10) COLLATE "en_US"); +CREATE INDEX idx_inplace_ab ON inplace_test(a, b); +CREATE INDEX idx_inplace_expr ON inplace_test(lower(b)); +INSERT INTO inplace_test VALUES ('x', 'y'); +CREATE TEMP TABLE inplace_info AS + SELECT c.relname, c.oid, c.relfilenode FROM pg_class c + WHERE c.relname IN ('inplace_test_a_key', 'idx_inplace_ab', 'idx_inplace_expr'); +CREATE TEMP TABLE con_info AS + SELECT oid, conindid FROM pg_constraint WHERE conname = 'inplace_test_a_key'; +CREATE TEMP TABLE ab_attr AS + SELECT attnum, atttypmod FROM pg_attribute + WHERE attrelid = 'idx_inplace_ab'::regclass AND attnum > 0; +-- compatible alter on a, incompatible (collation change) on b. +ALTER TABLE inplace_test ALTER COLUMN a TYPE varchar(50), + ALTER COLUMN b TYPE varchar(50) COLLATE "C"; +-- unique constraint on a: OID and backing index should be reused. +SELECT + CASE + WHEN old.oid = new.oid AND old.conindid = new.conindid + THEN 'constraint reused' + ELSE 'constraint changed' + END as result +FROM con_info old, pg_constraint new +WHERE new.conname = 'inplace_test_a_key'; +-- unique constraint is still enforced. +INSERT INTO inplace_test VALUES ('x', 'z'); +-- unique constraint index reused, expression index rebuilt. +SELECT m.relname, + CASE WHEN m.relfilenode = new.relfilenode THEN 'relfilenode reused' ELSE 'relfilenode changed' END, + CASE WHEN m.oid = new.oid THEN 'OID reused' ELSE 'OID changed' END +FROM inplace_info m +JOIN pg_class new ON new.relname = m.relname +ORDER BY m.relname; +-- multi-column index: column a's atttypmod changed, column b's changed too +-- (b was incompatible so the whole index was rebuilt). +SELECT m.attnum, + CASE WHEN m.atttypmod = new.atttypmod THEN 'unchanged' ELSE 'changed' END as result +FROM ab_attr m +JOIN pg_attribute new ON new.attrelid = 'idx_inplace_ab'::regclass AND new.attnum = m.attnum +ORDER BY m.attnum; + +-- temp table: follows PG behavior. Compatible index is reused, incompatible +-- index is rebuilt. +CREATE TEMP TABLE temp_test (a varchar(10), b varchar(10) COLLATE "en_US"); +CREATE INDEX idx_temp_a ON temp_test(a); +CREATE INDEX idx_temp_b ON temp_test(b); +CREATE TEMP TABLE temp_info AS + SELECT relname, oid, relfilenode FROM pg_class WHERE relname IN ('idx_temp_a', 'idx_temp_b'); +ALTER TABLE temp_test ALTER COLUMN a TYPE varchar(50), + ALTER COLUMN b TYPE varchar(50) COLLATE "C"; +SELECT o.relname, + CASE WHEN o.relfilenode = new.relfilenode THEN 'relfilenode reused' ELSE 'relfilenode changed' END, + CASE WHEN o.oid = new.oid THEN 'OID reused' ELSE 'OID changed' END +FROM temp_info o +JOIN pg_class new ON new.relname = o.relname +ORDER BY o.relname; + +-- partitioned table: compatible index reused in place for parent and children. +-- child partition has columns in different order to test attnum mapping. +CREATE TABLE part_parent (a varchar(10), b int) PARTITION BY RANGE (b); +CREATE TABLE part_child_raw (b int, a varchar(10)); +ALTER TABLE part_parent ATTACH PARTITION part_child_raw FOR VALUES FROM (1) TO (100); +CREATE INDEX idx_part_a ON part_parent(a); +INSERT INTO part_parent VALUES ('x', 1); +CREATE TEMP TABLE part_idx_info AS + SELECT c.relname, c.oid, c.relfilenode + FROM pg_class c, pg_index i + WHERE i.indexrelid = c.oid + AND i.indrelid IN ('part_parent'::regclass, 'part_child_raw'::regclass); +ALTER TABLE part_parent ALTER COLUMN a TYPE varchar(50); +-- both parent and child index should be reused. +SELECT m.relname, + CASE WHEN m.relfilenode = new.relfilenode THEN 'relfilenode reused' ELSE 'relfilenode changed' END, + CASE WHEN m.oid = new.oid THEN 'OID reused' ELSE 'OID changed' END +FROM part_idx_info m +JOIN pg_class new ON new.relname = m.relname +ORDER BY m.relname; + +-- inherited table (non-partition): compatible index reused in place. +CREATE TABLE inh_parent (a varchar(10)); +CREATE TABLE inh_child () INHERITS (inh_parent); +CREATE INDEX idx_inh_parent ON inh_parent(a); +CREATE INDEX idx_inh_child ON inh_child(a); +CREATE TEMP TABLE inh_idx_info AS + SELECT c.relname, c.oid, c.relfilenode + FROM pg_class c + WHERE c.relname IN ('idx_inh_parent', 'idx_inh_child'); +ALTER TABLE inh_parent ALTER COLUMN a TYPE varchar(50); +-- both parent and child index should be reused. +SELECT m.relname, + CASE WHEN m.relfilenode = new.relfilenode THEN 'relfilenode reused' ELSE 'relfilenode changed' END, + CASE WHEN m.oid = new.oid THEN 'OID reused' ELSE 'OID changed' END +FROM inh_idx_info m +JOIN pg_class new ON new.relname = m.relname +ORDER BY m.relname; diff --git a/src/test/regress/sql/yb.orig.alter_type.sql b/src/test/regress/sql/yb.orig.alter_type.sql new file mode 100644 index 0000000000000..054b8d719938c --- /dev/null +++ b/src/test/regress/sql/yb.orig.alter_type.sql @@ -0,0 +1,77 @@ +-- verify ALTER TYPE ... RENAME TO works correctly +CREATE TYPE bogus AS ENUM('good'); + +ALTER TYPE bogus RENAME TO bogon; + +SELECT 'good'::bogus; -- fail +SELECT 'good'::bogon; + +DROP TYPE bogon; + +-- test ALTER table column's TYPE ... RENAME TO works correctly +CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic'); +CREATE TABLE holidays ( + num_weeks integer, + happiness_level happiness +); +INSERT INTO holidays(num_weeks,happiness_level) VALUES (4, 'happy'); +INSERT INTO holidays(num_weeks,happiness_level) VALUES (6, 'very happy'); +SELECT * FROM holidays ORDER BY num_weeks; + +ALTER TYPE happiness RENAME TO new_name; +\dT+ new_name +\d holidays + +INSERT INTO holidays(num_weeks,happiness_level) VALUES (8, 'ecstatic'); +SELECT * FROM holidays ORDER BY num_weeks; + +DROP TYPE happiness; -- fail +DROP TYPE new_name; -- fail +DROP TYPE new_name CASCADE; + +\d holidays +SELECT * FROM holidays ORDER BY num_weeks; + +DROP TABLE holidays; + +-- test ALTER table PRIMARY KEY column's TYPE ... RENAME TO works correctly +CREATE TYPE happiness AS ENUM ('happy', 'very happy'); +CREATE TABLE holidays ( + num_weeks integer, + happiness_level happiness PRIMARY KEY +); +\d holidays + +INSERT INTO holidays(num_weeks,happiness_level) VALUES (4, 'happy'); +INSERT INTO holidays(num_weeks,happiness_level) VALUES (6, 'very happy'); +SELECT * FROM holidays ORDER BY num_weeks; + +ALTER TYPE happiness RENAME TO new_name; +\dT+ new_name + +ALTER TYPE new_name ADD VALUE 'ecstatic'; +\dT+ new_name + +INSERT INTO holidays(num_weeks,happiness_level) VALUES (8, 'ecstatic'); +SELECT * FROM holidays ORDER BY num_weeks; + +DROP TYPE new_name; -- fail +DROP TYPE new_name CASCADE; -- requires table rewrite (happiness_level is a key column) +ALTER TABLE holidays DROP CONSTRAINT holidays_pkey; -- fail + +\d holidays +SELECT * FROM holidays ORDER BY num_weeks; + +DROP TABLE holidays; + +-- test ALTER TYPE ... SET SCHEMA +CREATE SCHEMA s1; +CREATE TYPE s1.test_type AS ENUM('bad', 'good'); +CREATE TABLE test_table (a s1.test_type); +INSERT INTO test_table VALUES ('good'); +CREATE SCHEMA s2; +ALTER TYPE s1.test_type SET SCHEMA s2; +INSERT INTO test_table VALUES ('bad'); +SELECT * FROM test_table ORDER BY a; +DROP SCHEMA s1 CASCADE; +SELECT * FROM test_table ORDER BY a; diff --git a/src/test/regress/sql/yb.orig.analyze.sql b/src/test/regress/sql/yb.orig.analyze.sql new file mode 100644 index 0000000000000..61ce43f428d7d --- /dev/null +++ b/src/test/regress/sql/yb.orig.analyze.sql @@ -0,0 +1,178 @@ +-- avoid bit-exact output here because operations may not be bit-exact. +SET extra_float_digits = 0; + +-- Create new tables +CREATE TABLE x(a int PRIMARY KEY, b text); +CREATE TABLE y(a int PRIMARY KEY, b text); + +INSERT INTO x + SELECT i, chr(ascii('A') + i) FROM generate_series(0, 24) as s(i); +INSERT INTO y + SELECT i, CASE i % 4 WHEN 1 THEN 'odd' WHEN 2 THEN 'two' WHEN 3 THEN 'odd' END + FROM generate_series(1, 25000) as s(i); + +-- Check statistics is not there +SELECT + relname, + reltuples, + staattnum, + stanullfrac, + stawidth, + stadistinct +FROM + pg_class LEFT JOIN pg_statistic ON pg_class.oid = starelid +WHERE + relname IN ('x', 'y') +ORDER BY + relname, staattnum; + +-- Explain queries without statistics +EXPLAIN SELECT * FROM x; +EXPLAIN SELECT * FROM y; +EXPLAIN SELECT DISTINCT b FROM y; +EXPLAIN SELECT * FROM x, y WHERE x.a = y.a; +EXPLAIN SELECT * FROM y, x WHERE x.a = y.a; + +ANALYZE x, y; + +-- Check statistic is there +SELECT + relname, + reltuples, + staattnum, + stanullfrac, + stawidth, + stadistinct +FROM + pg_class LEFT JOIN pg_statistic ON pg_class.oid = starelid +WHERE + relname IN ('x', 'y') +ORDER BY + relname, staattnum; + +-- Explain queries with statistics +EXPLAIN SELECT * FROM x; +EXPLAIN SELECT * FROM y; +EXPLAIN SELECT DISTINCT b FROM y; +EXPLAIN SELECT * FROM x, y WHERE x.a = y.a; +EXPLAIN SELECT * FROM y, x WHERE x.a = y.a; + +-- Modify tables +INSERT INTO x VALUES (25, 'Z'); +DELETE FROM y WHERE a % 10 = 0; +ALTER TABLE y ADD COLUMN c int; +UPDATE y SET c = LENGTH(b) WHERE b IS NOT NULL; + +ANALYZE x; +ANALYZE y; + +-- Check updated statistics +SELECT + relname, + reltuples, + staattnum, + stanullfrac, + stawidth, + stadistinct +FROM + pg_class LEFT JOIN pg_statistic ON pg_class.oid = starelid +WHERE + relname IN ('x', 'y') +ORDER BY + relname, staattnum; + +create temp table prtx1 (a integer, b integer, c integer) + partition by range (a); +create temp table prtx1_1 partition of prtx1 for values from (1) to (11); +create temp table prtx1_2 partition of prtx1 for values from (11) to (21); +create temp table prtx1_3 partition of prtx1 for values from (21) to (31); +insert into prtx1 select 1 + i%30, i, i + from generate_series(1,1000) i; +analyze prtx1; + +-- Verify analyze works after dropping a table column. +ALTER TABLE x DROP COLUMN b; +ANALYZE x; +SELECT + relname, + reltuples, + staattnum, + stanullfrac, + stawidth, + stadistinct +FROM + pg_class LEFT JOIN pg_statistic ON pg_class.oid = starelid +WHERE + relname = 'x' +ORDER BY + relname, staattnum; + +-- Cleanup +DROP TABLE x; +DROP TABLE y; + +-- Analyze all tables +ANALYZE; + + +-- Analyze partitioned table +CREATE TABLE t_part (i int, a int, b int) PARTITION BY RANGE (i); +CREATE TABLE t_part1 PARTITION OF t_part FOR VALUES FROM (1) TO (501); +CREATE TABLE t_part2 PARTITION OF t_part FOR VALUES FROM (501) TO (601); +CREATE TABLE t_part3 PARTITION OF t_part FOR VALUES FROM (601) TO (MAXVALUE) + PARTITION BY RANGE (a); +CREATE TABLE t_part3s1 PARTITION OF t_part3 FOR VALUES FROM (10) TO (51); +CREATE TABLE t_part3s2 PARTITION OF t_part3 FOR VALUES FROM (51) TO (401); +CREATE TABLE t_part3s3 PARTITION OF t_part3 DEFAULT; + +INSERT INTO t_part + SELECT i, a, b + FROM ( + SELECT + i, a, + CASE + WHEN i BETWEEN 1 AND 500 THEN + i % 20 + 1 + WHEN i BETWEEN 501 AND 600 THEN + i % 100 + 1 + WHEN i >= 601 THEN + CASE + WHEN a BETWEEN 10 AND 50 THEN + i + WHEN a BETWEEN 51 AND 400 THEN + NULL + ELSE + CASE WHEN i % 20 = 0 THEN i END + END + ELSE + i % 20 + 1 + END AS b + FROM ( + SELECT i, i % 500 + 1 AS a + FROM generate_series(1, 10000) i + ) vv + ) v; + +-- Same size of sample from each (sub)partition +ANALYZE t_part; + +SELECT relname, attname, reltuples, stadistinct, stanullfrac + FROM pg_statistic s, pg_class c, pg_attribute a + WHERE c.oid = starelid + AND c.oid = attrelid + AND attnum = staattnum + AND relname LIKE 't_part%' + ORDER BY starelid, attnum; + +-- Varying size of sample based on previously collected reltuples +ANALYZE t_part; + +SELECT relname, attname, reltuples, stadistinct, stanullfrac + FROM pg_statistic s, pg_class c, pg_attribute a + WHERE c.oid = starelid + AND c.oid = attrelid + AND attnum = staattnum + AND relname LIKE 't_part%' + ORDER BY starelid, attnum; + +DROP TABLE t_part; diff --git a/src/test/regress/sql/yb.orig.analyze_system_tables.sql b/src/test/regress/sql/yb.orig.analyze_system_tables.sql new file mode 100644 index 0000000000000..b013142113b35 --- /dev/null +++ b/src/test/regress/sql/yb.orig.analyze_system_tables.sql @@ -0,0 +1,63 @@ +ANALYZE pg_aggregate; +ANALYZE pg_am; +ANALYZE pg_amop; +ANALYZE pg_amproc; +ANALYZE pg_attrdef; +ANALYZE pg_attribute; +ANALYZE pg_auth_members; +ANALYZE pg_authid; +ANALYZE pg_cast; +ANALYZE pg_class; +ANALYZE pg_collation; +ANALYZE pg_constraint; +ANALYZE pg_conversion; +ANALYZE pg_database; +ANALYZE pg_db_role_setting; +ANALYZE pg_default_acl; +ANALYZE pg_depend; +ANALYZE pg_description; +ANALYZE pg_enum; +ANALYZE pg_event_trigger; +ANALYZE pg_extension; +ANALYZE pg_foreign_data_wrapper; +ANALYZE pg_foreign_server; +ANALYZE pg_foreign_table; +ANALYZE pg_index; +ANALYZE pg_inherits; +ANALYZE pg_init_privs; +ANALYZE pg_language; +ANALYZE pg_largeobject; +ANALYZE pg_largeobject_metadata; +ANALYZE pg_namespace; +ANALYZE pg_opclass; +ANALYZE pg_operator; +ANALYZE pg_opfamily; +ANALYZE pg_partitioned_table; +ANALYZE pg_policy; +ANALYZE pg_proc; +ANALYZE pg_publication; +ANALYZE pg_publication_rel; +ANALYZE pg_range; +ANALYZE pg_replication_origin; +ANALYZE pg_rewrite; +ANALYZE pg_seclabel; +ANALYZE pg_sequence; +ANALYZE pg_shdepend; +ANALYZE pg_shdescription; +ANALYZE pg_shseclabel; +ANALYZE pg_statistic; +ANALYZE pg_statistic_ext; +ANALYZE pg_subscription; +ANALYZE pg_subscription_rel; +ANALYZE pg_tablespace; +ANALYZE pg_transform; +ANALYZE pg_trigger; +ANALYZE pg_ts_config; +ANALYZE pg_ts_config_map; +ANALYZE pg_ts_dict; +ANALYZE pg_ts_parser; +ANALYZE pg_ts_template; +ANALYZE pg_type; +ANALYZE pg_user_mapping; +ANALYZE pg_yb_catalog_version; +ANALYZE pg_yb_tablegroup; diff --git a/src/test/regress/sql/yb.orig.async.sql b/src/test/regress/sql/yb.orig.async.sql new file mode 100644 index 0000000000000..94956489d2d2f --- /dev/null +++ b/src/test/regress/sql/yb.orig.async.sql @@ -0,0 +1,12 @@ +-- +-- YB LISTEN/NOTIFY tests +-- + +-- LISTEN should not depend on yugabyte role +\c - postgres; +ALTER ROLE yugabyte RENAME TO yugabyte_async_test; +LISTEN c; +-- reset +ALTER ROLE yugabyte_async_test RENAME TO yugabyte; + +-- Add new tests before the above test. diff --git a/src/test/regress/sql/yb.orig.base_type.sql b/src/test/regress/sql/yb.orig.base_type.sql new file mode 100644 index 0000000000000..921efb45137bc --- /dev/null +++ b/src/test/regress/sql/yb.orig.base_type.sql @@ -0,0 +1,418 @@ +-- +-- Create base types exercising various parameters. +-- + +\set VERBOSITY terse + +-- typlen:128, typbyval:f, typalign:c +CREATE TYPE bigname ( + INPUT = bigname_in, + OUTPUT = bigname_out, + INTERNALLENGTH = 128, + ALIGNMENT = char, + STORAGE = plain +); +CREATE TABLE bigname_table (t bigname); +INSERT INTO bigname_table (t) + VALUES ('AaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaABCcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccC'); +INSERT INTO bigname_table (t) + VALUES ('AaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaABCcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccCB'); +INSERT INTO bigname_table (t) + VALUES ('AaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaABCcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccCBD'); +SELECT * FROM bigname_table; +DROP TABLE bigname_table; +DROP TYPE bigname CASCADE; + +-- typlen:64, typbyval:f, typalign:c +CREATE TYPE name_type; +CREATE FUNCTION name_type_in(cstring) RETURNS name_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'namein'; +CREATE FUNCTION name_type_out(name_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'nameout'; +CREATE TYPE name_type ( + INPUT = name_type_in, + OUTPUT = name_type_out, + LIKE = name +); +CREATE TABLE name_table (t name_type); +INSERT INTO name_table (t) + VALUES ('AaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaA'); +INSERT INTO name_table (t) + VALUES ('AaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAB'); +INSERT INTO name_table (t) + VALUES ('AaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaABC'); +SELECT * FROM name_table; +DROP TABLE name_table; +DROP TYPE name_type CASCADE; + +-- typlen:32, typbyval:f, typalign:d +CREATE TYPE lseg_type; +CREATE FUNCTION lseg_type_in(cstring) RETURNS lseg_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'lseg_in'; +CREATE FUNCTION lseg_type_out(lseg_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'lseg_out'; +CREATE TYPE lseg_type ( + INPUT = lseg_type_in, + OUTPUT = lseg_type_out, + LIKE = lseg +); +CREATE TABLE lseg_table (t lseg_type); +INSERT INTO lseg_table (t) + VALUES ('[(1, 2), (3, 4)]'); +SELECT * FROM lseg_table; +DROP TABLE lseg_table; +DROP TYPE lseg_type CASCADE; + +-- typlen:24, typbyval:f, typalign:d +CREATE TYPE line_type; +CREATE FUNCTION line_type_in(cstring) RETURNS line_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'line_in'; +CREATE FUNCTION line_type_out(line_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'line_out'; +CREATE TYPE line_type ( + INPUT = line_type_in, + OUTPUT = line_type_out, + LIKE = line +); +CREATE TABLE line_table (t line_type); +INSERT INTO line_table (t) + VALUES ('{1, 2, 3}'); +SELECT * FROM line_table; +DROP TABLE line_table; +DROP TYPE line_type CASCADE; + +-- typlen:16, typbyval:f, typalign:d +CREATE TYPE point_type; +CREATE FUNCTION point_type_in(cstring) RETURNS point_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'point_in'; +CREATE FUNCTION point_type_out(point_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'point_out'; +CREATE TYPE point_type ( + INPUT = point_type_in, + OUTPUT = point_type_out, + LIKE = point +); +CREATE TABLE point_table (t point_type); +INSERT INTO point_table (t) + VALUES ('(1, 2)'); +SELECT * FROM point_table; +DROP TABLE point_table; +DROP TYPE point_type CASCADE; + +-- typlen:16, typbyval:f, typalign:c +CREATE TYPE uuid_type; +CREATE FUNCTION uuid_type_in(cstring) RETURNS uuid_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'uuid_in'; +CREATE FUNCTION uuid_type_out(uuid_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'uuid_out'; +CREATE TYPE uuid_type ( + INPUT = uuid_type_in, + OUTPUT = uuid_type_out, + LIKE = uuid +); +CREATE TABLE uuid_table (t uuid_type); +INSERT INTO uuid_table (t) + VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'); +SELECT * FROM uuid_table; +DROP TABLE uuid_table; +DROP TYPE uuid_type CASCADE; + +-- typlen:12, typbyval:f, typalign:d +CREATE TYPE timetz_type; +CREATE FUNCTION timetz_type_in(cstring) RETURNS timetz_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'timetz_in'; +CREATE FUNCTION timetz_type_out(timetz_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'timetz_out'; +CREATE TYPE timetz_type ( + INPUT = timetz_type_in, + OUTPUT = timetz_type_out, + LIKE = timetz +); +CREATE TABLE timetz_table (t timetz_type); +-- Inspired by https://stackoverflow.com/questions/29993956/ +INSERT INTO timetz_table (t) + VALUES ('2015-05-01 11:25:00 America/Caracas'); +SELECT * FROM timetz_table; +DROP TABLE timetz_table; +DROP TYPE timetz_type CASCADE; + +-- typlen:8, typbyval:f, typalign:i +CREATE TYPE macaddr8_type; +CREATE FUNCTION macaddr8_type_in(cstring) RETURNS macaddr8_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'macaddr8_in'; +CREATE FUNCTION macaddr8_type_out(macaddr8_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'macaddr8_out'; +CREATE TYPE macaddr8_type ( + INPUT = macaddr8_type_in, + OUTPUT = macaddr8_type_out, + LIKE = macaddr8 +); +CREATE TABLE macaddr8_table (t macaddr8_type); +INSERT INTO macaddr8_table (t) + VALUES ('08:00:2b:01:02:03:04:05'); +SELECT * FROM macaddr8_table; +DROP TABLE macaddr8_table; +DROP TYPE macaddr8_type CASCADE; + +-- typlen:8, typbyval:t, typalign:d +CREATE TYPE int8_type; +CREATE FUNCTION int8_type_in(cstring) RETURNS int8_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int8in'; +CREATE FUNCTION int8_type_out(int8_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int8out'; +CREATE TYPE int8_type ( + INPUT = int8_type_in, + OUTPUT = int8_type_out, + LIKE = int8 +); +CREATE TABLE int8_table (t int8_type); +INSERT INTO int8_table (t) + VALUES ('9223372036854775807'); +INSERT INTO int8_table (t) + VALUES ('9223372036854775808'); +SELECT * FROM int8_table; +DROP TABLE int8_table; +DROP TYPE int8_type CASCADE; + +-- typlen:8, typbyval:t, typalign:d +CREATE TYPE float8_type; +CREATE FUNCTION float8_type_in(cstring) RETURNS float8_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'float8in'; +CREATE FUNCTION float8_type_out(float8_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'float8out'; +CREATE TYPE float8_type ( + INPUT = float8_type_in, + OUTPUT = float8_type_out, + LIKE = float8 +); +CREATE TABLE float8_table (t float8_type); +INSERT INTO float8_table (t) + VALUES ('1.23456789'); +SELECT * FROM float8_table; +DROP TABLE float8_table; +DROP TYPE float8_type CASCADE; + +-- typlen:6, typbyval:f, typalign:i +CREATE TYPE macaddr_type; +CREATE FUNCTION macaddr_type_in(cstring) RETURNS macaddr_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'macaddr_in'; +CREATE FUNCTION macaddr_type_out(macaddr_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'macaddr_out'; +CREATE TYPE macaddr_type ( + INPUT = macaddr_type_in, + OUTPUT = macaddr_type_out, + LIKE = macaddr +); +CREATE TABLE macaddr_table (t macaddr_type); +INSERT INTO macaddr_table (t) + VALUES ('08:00:2b:01:02:03'); +SELECT * FROM macaddr_table; +DROP TABLE macaddr_table; +DROP TYPE macaddr_type CASCADE; + +-- typlen:6, typbyval:f, typalign:s +CREATE TYPE tid_type; +CREATE FUNCTION tid_type_in(cstring) RETURNS tid_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'tidin'; +CREATE FUNCTION tid_type_out(tid_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'tidout'; +CREATE TYPE tid_type ( + INPUT = tid_type_in, + OUTPUT = tid_type_out, + LIKE = tid +); +CREATE TABLE tid_table (t tid_type); +INSERT INTO tid_table (t) + VALUES ('(0, 1)'); +SELECT * FROM tid_table; +DROP TABLE tid_table; +DROP TYPE tid_type CASCADE; + +-- typlen:4, typbyval:t, typalign:d +CREATE TYPE int4_type; +CREATE FUNCTION int4_type_in(cstring) RETURNS int4_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int4in'; +CREATE FUNCTION int4_type_out(int4_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int4out'; +CREATE TYPE int4_type ( + INPUT = int4_type_in, + OUTPUT = int4_type_out, + LIKE = int4 +); +CREATE TABLE int4_table (t int4_type); +INSERT INTO int4_table (t) + VALUES ('2147483647'); +INSERT INTO int4_table (t) + VALUES ('2147483648'); +SELECT * FROM int4_table; +DROP TABLE int4_table; +DROP TYPE int4_type CASCADE; + +-- typlen:2, typbyval:t, typalign:d +CREATE TYPE int2_type; +CREATE FUNCTION int2_type_in(cstring) RETURNS int2_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int2in'; +CREATE FUNCTION int2_type_out(int2_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int2out'; +CREATE TYPE int2_type ( + INPUT = int2_type_in, + OUTPUT = int2_type_out, + LIKE = int2 +); +CREATE TABLE int2_table (t int2_type); +INSERT INTO int2_table (t) + VALUES ('32767'); +INSERT INTO int2_table (t) + VALUES ('32768'); +SELECT * FROM int2_table; +DROP TABLE int2_table; +DROP TYPE int2_type CASCADE; + +-- typlen:1, typbyval:t, typalign:c +CREATE TYPE bool_type; +CREATE FUNCTION bool_type_in(cstring) RETURNS bool_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'boolin'; +CREATE FUNCTION bool_type_out(bool_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'boolout'; +CREATE TYPE bool_type ( + INPUT = bool_type_in, + OUTPUT = bool_type_out, + LIKE = bool +); +CREATE TABLE bool_table (t bool_type); +INSERT INTO bool_table (t) + VALUES ('true'); +SELECT * FROM bool_table; +DROP TABLE bool_table; +DROP TYPE bool_type CASCADE; + +-- typlen:1, typbyval:t, typalign:c +CREATE TYPE char_type; +CREATE FUNCTION char_type_in(cstring) RETURNS char_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'charin'; +CREATE FUNCTION char_type_out(char_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'charout'; +CREATE TYPE char_type ( + INPUT = char_type_in, + OUTPUT = char_type_out, + INTERNALLENGTH = 1, + PASSEDBYVALUE, + ALIGNMENT = char, + STORAGE = plain +); +CREATE TABLE char_table (t char_type); +INSERT INTO char_table (t) + VALUES ('t'); +SELECT * FROM char_table; +DROP TABLE char_table; +DROP TYPE char_type CASCADE; + +-- typlen:-1, typbyval:f, typalign:i +CREATE TYPE bytea_type; +CREATE FUNCTION bytea_type_in(cstring) RETURNS bytea_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'byteain'; +CREATE FUNCTION bytea_type_out(bytea_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'byteaout'; +CREATE TYPE bytea_type ( + INPUT = bytea_type_in, + OUTPUT = bytea_type_out, + LIKE = bytea +); +CREATE TABLE bytea_table (t bytea_type); +INSERT INTO bytea_table (t) + VALUES (E'\\xDEADBEEF'); +SELECT * FROM bytea_table; +DROP TABLE bytea_table; +DROP TYPE bytea_type CASCADE; + +-- typlen:-1, typbyval:f, typalign:i +CREATE TYPE cidr_type; +CREATE FUNCTION cidr_type_in(cstring) RETURNS cidr_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'cidr_in'; +CREATE FUNCTION cidr_type_out(cidr_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'cidr_out'; +CREATE TYPE cidr_type ( + INPUT = cidr_type_in, + OUTPUT = cidr_type_out, + LIKE = cidr +); +CREATE TABLE cidr_table (t cidr_type); +INSERT INTO cidr_table (t) + VALUES ('192.168.100.128/25'); +SELECT * FROM cidr_table; +DROP TABLE cidr_table; +DROP TYPE cidr_type CASCADE; + +-- typlen:-1, typbyval:f, typalign:i +CREATE TYPE int2vector_type; +CREATE FUNCTION int2vector_type_in(cstring) RETURNS int2vector_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int2vectorin'; +CREATE FUNCTION int2vector_type_out(int2vector_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int2vectorout'; +CREATE TYPE int2vector_type ( + INPUT = int2vector_type_in, + OUTPUT = int2vector_type_out, + LIKE = int2vector +); +CREATE TABLE int2vector_table (t int2vector_type); +INSERT INTO int2vector_table (t) + VALUES ('1 2 3'); +SELECT * FROM int2vector_table; +DROP TABLE int2vector_table; +DROP TYPE int2vector_type CASCADE; + +-- typlen:-1, typbyval:f, typalign:i +CREATE TYPE json_type; +CREATE FUNCTION json_type_in(cstring) RETURNS json_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'json_in'; +CREATE FUNCTION json_type_out(json_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'json_out'; +CREATE TYPE json_type ( + INPUT = json_type_in, + OUTPUT = json_type_out, + LIKE = json +); +CREATE TABLE json_table (t json_type); +INSERT INTO json_table (t) + VALUES ('{"a": [1, 2, 3]}'); +SELECT * FROM json_table; +DROP TABLE json_table; +DROP TYPE json_type CASCADE; + +-- typlen:-1, typbyval:f, typalign:i +CREATE TYPE text_type; +CREATE FUNCTION text_type_in(cstring) RETURNS text_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'textin'; +CREATE FUNCTION text_type_out(text_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'textout'; +CREATE TYPE text_type ( + INPUT = text_type_in, + OUTPUT = text_type_out, + LIKE = text +); +CREATE TABLE text_table (t text_type); +INSERT INTO text_table (t) + VALUES ('thisistext'); +SELECT * FROM text_table; +DROP TABLE text_table; +DROP TYPE text_type CASCADE; + +-- typlen:-2, typbyval:f, typalign:i +CREATE TYPE cstring_type; +CREATE FUNCTION cstring_type_in(cstring) RETURNS cstring_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'cstring_in'; +CREATE FUNCTION cstring_type_out(cstring_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'cstring_out'; +CREATE TYPE cstring_type ( + INPUT = cstring_type_in, + OUTPUT = cstring_type_out, + LIKE = cstring +); +CREATE TABLE cstring_table (t cstring_type); +INSERT INTO cstring_table (t) + VALUES ('thisiscstring'); +SELECT * FROM cstring_table; +DROP TABLE cstring_table; +DROP TYPE cstring_type CASCADE; + +\set VERBOSITY default diff --git a/src/test/regress/sql/yb.orig.beta_extensions.sql b/src/test/regress/sql/yb.orig.beta_extensions.sql new file mode 100644 index 0000000000000..adfd1c664138b --- /dev/null +++ b/src/test/regress/sql/yb.orig.beta_extensions.sql @@ -0,0 +1,7 @@ +-- Test that pg_stat_monitor shows beta warning when ysql_beta_features is false +SET client_min_messages = warning; + +-- Test installing pg_stat_monitor gives beta warning +CREATE EXTENSION pg_stat_monitor; +-- Clean up +DROP EXTENSION pg_stat_monitor; diff --git a/src/test/regress/sql/yb.orig.bitmap_scan_plan.sql b/src/test/regress/sql/yb.orig.bitmap_scan_plan.sql new file mode 100644 index 0000000000000..eeeb0059486af --- /dev/null +++ b/src/test/regress/sql/yb.orig.bitmap_scan_plan.sql @@ -0,0 +1,108 @@ +set client_min_messages = 'warning'; +drop database if exists colocateddb; +create database colocateddb with colocation = on; +alter database colocateddb set yb_enable_cbo = on; +alter database colocateddb set yb_enable_bitmapscan = on; +alter database colocateddb set yb_fetch_row_limit = 0; +alter database colocateddb set yb_fetch_size_limit = '1MB'; +-- Disable parallelization until bitmap parallel scan is supported +alter database colocateddb set yb_parallel_range_rows = 0; + +\c colocateddb + +-- Create procedures and functions, store test queries in a table +-- so we can reuse them in multiple setups. + +create procedure create_tables(modifier text) +language plpgsql as +$$ +begin + -- Create a table with unique columns and non-unique columns whose values are + -- uncorrelated one another. + execute + 'create table r (pk int, a int, b int, c int, d int, v char(666),'|| + ' primary key (pk asc))' || modifier; + create index nonconcurrently r_a on r (a asc); + create index nonconcurrently r_b on r (b asc); + create index nonconcurrently r_c on r (c asc); + create index nonconcurrently r_d on r (d asc); + + -- pk, a: unique + -- b, c, d: frequency of each distinct value is exactly the same in each column + -- the values in these columns are not correlated one another. + insert into r + select + i * 500 + j, + i * 500 + j, + (i % 100) + 1, + (j % 250) + 1, + ((i # x'7FF'::int) % 100) + 1, + sha512(('x'||i)::bytea)::bpchar||lpad(sha512((j||'y')::bytea)::bpchar, 536, '#') + from + generate_series(0, 999) i, generate_series(1, 500) j; +end; +$$; + +create function explain(text) returns setof text +language plpgsql as +$$ +declare + n int := 0; + ln text; +begin + return next $1; + return next '----------------'; + for ln in + execute format('explain (costs off, summary off) %s', $1) + loop + return next ln; + n := n + 1; + end loop; + return next format('(%s rows)', n); + return next ''; +end; +$$; + +create table queries (id serial, query text, primary key (id asc)); +insert into queries values + (DEFAULT, 'select b, c from r where b = 1 and c in (50, 60, 70, 80, 90, 100)'), + (DEFAULT, 'select b, c from r where b <= 500 and c in (50, 60, 70, 80, 90, 100)'), + (DEFAULT, 'select b, c from r where b <= 500 and c in (50, 60, 70, 80, 90, 100) and d <= 2'), + (DEFAULT, 'select b, c from r where c in (50, 60, 70, 80, 90, 100) or c <= 5'); + + +\pset footer off + +-- +-- Non-colocated tables +-- + +call create_tables('with (colocation = off)'); +analyze r; + +select explain(query) from queries order by id; + +-- try again with additional index +create index nonconcurrently i_r_b_acd on r (b asc) include (a, c, d); +analyze r; +select explain(query) from queries order by id; + +-- +-- Colocated tables +-- + +drop table r; +call create_tables('with (colocation = on)'); +analyze r; + +select explain(query) from queries order by id; + +-- try again with additional index +create index nonconcurrently i_r_b_acd on r (b asc) include (a, c, d); +analyze r; +select explain(query) from queries order by id; + + +\pset footer on +\c yugabyte +drop database colocateddb with (force); diff --git a/src/test/regress/sql/yb.orig.bitmap_scans.sql b/src/test/regress/sql/yb.orig.bitmap_scans.sql new file mode 100644 index 0000000000000..338c408fcceeb --- /dev/null +++ b/src/test/regress/sql/yb.orig.bitmap_scans.sql @@ -0,0 +1,441 @@ +-- +-- YB Bitmap Scans (bitmap index scans + YB bitmap table scans) +-- +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 + +SET yb_enable_bitmapscan = true; +SET enable_bitmapscan = true; + +CREATE TABLE simple (k INT PRIMARY KEY, ind_a INT, ind_b INT); +INSERT INTO simple SELECT i, i * 2, i * 3 FROM generate_series(1, 10) i; +CREATE INDEX ON simple(ind_a ASC); +CREATE INDEX ON simple(ind_b ASC); + +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +\set Q1 '/*+ BitmapScan(simple) */' +\set query ':P :Q1 SELECT * FROM simple WHERE k = 1;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM simple WHERE ind_a < 5 ORDER BY k;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM simple WHERE ind_a BETWEEN 2 AND 6 OR ind_b > 25 ORDER BY k;' +\i :iter_P2 + +-- +-- test cases where we could skip fetching the table rows (TODO: #22044) +-- +-- this query does not need a recheck, so we don't need to fetch the rows for the COUNT(*) +\set explain 'EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF)' +\set query ':P :Q1 SELECT COUNT(*) FROM simple WHERE ind_a = 2 OR ind_b < 10;' +\i :iter_P2 + +-- when we require the rows, notice that the YB Bitmap Table Scan sends a table read request +\set query ':explain :Q1 SELECT * FROM simple WHERE ind_a = 2 OR ind_b < 10;' +:query + +-- this query has a recheck condition, so we need to fetch the rows +-- TODO(#29894): seems "test_skip" in the hint should be "simple". +/*+ BitmapScan(simple) Set(yb_enable_expression_pushdown false) */ EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF) +SELECT COUNT(*) FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0); +/*+ BitmapScan(test_skip) Set(yb_enable_expression_pushdown false) */ +SELECT COUNT(*) FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0); + +-- when the expression can be pushed down, we don't need a recheck but we do +-- still need to send the request. +\set query ':P :Q1 SELECT COUNT(*) FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0);' +\i :iter_P2 + +-- other aggregates may require the rows +\set query ':P :Q1 SELECT SUM(ind_a) FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0);' +\i :iter_P2 +\set query ':P :Q1 SELECT MAX(ind_a) FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0);' +\i :iter_P2 + +-- when we don't need the actual value, we can avoid fetching +\set query ':P :Q1 SELECT 1 FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0);' +\i :iter_P2 +\set query ':explain :Q1 SELECT random() FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0);' +:query + +-- +-- test primary key queries +-- +\set query ':P :Q1 SELECT * FROM simple WHERE k = 1 OR ind_a = 2;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM simple WHERE k IN (1, 2) OR ind_a IN (4, 6) ORDER BY k;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM simple WHERE k = 1 OR k = 2 OR ind_a = 4 OR ind_a = 6 ORDER BY k;' +\i :iter_P2 + +-- test non-existent results +\set query ':P :Q1 SELECT COUNT(*) FROM simple WHERE k = 2000 OR ind_a < 0;' +\i :iter_P2 + +-- +-- test unsatisfiable conditions +-- + +\set query ':explain :Q1 SELECT * FROM simple WHERE (k <= 1 AND k = 2);' +:query +\set query ':explain :Q1 SELECT * FROM simple WHERE (k = 1 AND k = 2) OR ind_b = 0;' +:query +\set query ':explain :Q1 SELECT * FROM simple WHERE (ind_a <= 1 AND ind_a = 2);' +:query +\set query ':explain :Q1 SELECT * FROM simple WHERE (ind_a = 2 AND ind_a = 2) OR ind_b = 0;' +:query + +-- +-- #21793: test row compare expressions +-- +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +\set query ':P :Q1 SELECT * FROM simple WHERE (ind_a, ind_a) <= (4, 1) OR ind_a IS NULL;' +\i :iter_P2 + +-- +-- #22062: variable not found in subplan target list +-- +EXPLAIN (COSTS OFF) /*+ BitmapScan(simple) */ +SELECT * FROM simple AS tbl_1 JOIN simple AS tbl_2 ON tbl_1.ind_a = tbl_2.ind_a OR (tbl_1.ind_b = tbl_2.ind_a AND tbl_1.ind_a = 2); + +-- +-- #22065: test pushdowns on Subplans +-- +\set Q1 '/*+ BitmapScan(simple_1) BitmapScan(simple) */' +\set query ':P :Q1 SELECT * FROM simple WHERE ind_a = 2 AND ind_a <> (SELECT ind_a FROM simple WHERE ind_b = 1);' +\i :iter_P2 + +-- +-- test UPDATE +-- + +\set Q1 '/*+ BitmapScan(simple) */' +\set query ':explain :Q1 UPDATE simple SET ind_a = NULL WHERE ind_a < 10 OR ind_b > 25;' +:query + +\set query ':P :Q1 SELECT ind_a, ind_b FROM simple WHERE ind_a < 10 OR ind_a IS NULL OR ind_b < 15 ORDER BY k;' +\i :iter_P2 + +-- NOTE: this GUC cannot be set using pg_hint_plan Set(...) because it is +-- currently used during execution, not just planning. +SET yb_test_skip_binding_scan_keys = true; + +\i :iter_P2 + +RESET yb_test_skip_binding_scan_keys; + +-- use Bitmap Scan to delete rows and validate their deletion +\set query ':explain :Q1 DELETE FROM simple WHERE ind_a IS NULL OR ind_b < 15;' +:query + +\set query ':P :Q1 SELECT ind_a, ind_b FROM simple WHERE ind_a IS NULL OR ind_b < 15 ORDER BY k;' +\i :iter_P2 + + +-- +-- test indexes on multiple columns / indexes with additional columns +-- +CREATE TABLE multi (a INT, b INT, c INT, h INT, PRIMARY KEY (a ASC, b ASC)); +CREATE INDEX ON multi (c ASC) INCLUDE (a); +CREATE INDEX ON multi (h HASH) INCLUDE (a); +CREATE INDEX ON multi (b ASC, c ASC); +INSERT INTO multi SELECT i, i * 2, i * 3, i * 4 FROM generate_series(1, 1000) i; + +\set explain 'EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF)' +\set Q1 '/*+ BitmapScan(multi) */' +\set query ':P :Q1 SELECT * FROM multi WHERE a < 2 OR b > 1997 ORDER BY a;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM multi WHERE c BETWEEN 10 AND 15 AND a < 30 ORDER BY a;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM multi WHERE a < 2 OR b > 1997 OR c BETWEEN 10 AND 15 OR h = 8 ORDER BY a;' +\i :iter_P2 + +-- try some slightly complex nested logical operands queries +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +\set query ':P :Q1 SELECT * FROM multi WHERE a < 2 OR (b > 1797 AND (c BETWEEN 2709 AND 2712 OR c = 2997)) ORDER BY a;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM multi WHERE (a < 3 AND a % 2 = 0) OR (b IN (10, 270, 1800) AND (c < 20 OR c > 2500)) ORDER BY a;' +\i :iter_P2 + +-- +-- test limits +-- +SET yb_fetch_row_limit = 100; +SET yb_fetch_size_limit = 0; + +\set explain 'EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF)' +\set query ':explain :Q1 SELECT * FROM multi WHERE a < 200 LIMIT 10;' +:query + +SET yb_fetch_row_limit = 0; +SET yb_fetch_size_limit = '1kB'; + +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +:query + +RESET yb_fetch_row_limit; +RESET yb_fetch_size_limit; + +-- +-- test exceeding work_mem +-- +INSERT INTO multi SELECT i, i * 2, i * 3, i * 4 FROM generate_series(1001, 10000) i; + +SET work_mem TO '4MB'; -- does not exceed work_mem + +\set query ':explain :Q1 SELECT * FROM multi WHERE a < 5000 OR (b > 0 AND (c < 3000 OR c > 27000)) ORDER BY a;' +:query + +SET work_mem TO '4GB'; -- does not exceed very large work_mem + +:query + +SET work_mem TO '100kB'; +-- normal case +:query + +-- verify remote filters apply to the table scan when we've exceeded work_mem +-- hint2: verify local filters still apply when pushdown is disabled +\set Q2 '/*+ BitmapScan(multi) Set(yb_enable_expression_pushdown false) */' +\set query ':explain :Q SELECT * FROM multi WHERE a < 5000 OR (b > 0 AND (c < 3000 OR c > 27000) AND b % 2 = 0) ORDER BY a;' +\i :iter_Q2 + +-- where the first bitmap index scan exceeds work_mem +\set query ':explain :Q1 SELECT * FROM multi WHERE a < 5000 OR (b > 0 AND (c < 3000 OR c > 27000)) ORDER BY a;' +:query + +-- where the second bitmap index scan exceeds work_mem +\set query ':explain :Q1 SELECT * FROM multi WHERE a < 1000 OR (b > 0 AND (c < 15000 OR c > 27000)) ORDER BY a;' +:query + +-- where the third bitmap index scan exceeds work_mem +\set query ':explain :Q1 SELECT * FROM multi WHERE a < 1000 OR (b > 0 AND (c < 3000 OR c > 15000)) ORDER BY a;' +:query + +-- check aggregate pushdown +SET yb_enable_expression_pushdown = true; +SET yb_enable_index_aggregate_pushdown = true; +\set query ':P :Q1 SELECT COUNT(*) FROM multi WHERE a > 10;' +\i :iter_P2 + +SET yb_enable_expression_pushdown = false; +SET yb_enable_index_aggregate_pushdown = true; +\set query ':P :Q1 SELECT COUNT(*) FROM multi WHERE a > 10;' +\i :iter_P2 + +SET yb_enable_expression_pushdown = false; +SET yb_enable_index_aggregate_pushdown = false; +\set query ':P :Q1 SELECT COUNT(*) FROM multi WHERE a > 10;' +\i :iter_P2 + +SET yb_enable_expression_pushdown = true; +SET yb_enable_index_aggregate_pushdown = false; +\set query ':P :Q1 SELECT COUNT(*) FROM multi WHERE a > 10;' +\i :iter_P2 + +RESET yb_enable_index_aggregate_pushdown; +RESET yb_enable_expression_pushdown; + +RESET work_mem; + +-- +-- test remote pushdown +-- + +\set explain 'EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF)' +\set Q1 '/*+ BitmapScan(multi multi_b_c_idx) */' +\set Q2 '/*+ BitmapScan(multi multi_b_c_idx) Set(yb_enable_expression_pushdown false) */' +\set query ':P :Q SELECT * FROM multi WHERE (b < 10 AND b % 4 = 0) ORDER BY b;' +\set Pnext :iter_Q2 +\i :iter_P2 + +\set Q1 '/*+ BitmapScan(multi) */' +\set Q2 '/*+ BitmapScan(multi) Set(yb_enable_expression_pushdown false) */' +\set query ':P :Q SELECT * FROM multi WHERE (a < 5 AND a % 2 = 0) OR (c <= 10 AND a % 3 = 0) ORDER BY a;' +\i :iter_P2 + +-- +-- test recheck index conditions +-- +create table recheck_test (int4_col int4, text_col TEXT, bigint_col bigint); +create index on recheck_test (int4_col ASC); +create index on recheck_test (text_col ASC); +create index on recheck_test (bigint_col ASC); + +insert into recheck_test VALUES + (1, 'a', 1), + (2, 'b', 2), + (3, 'c', 3), + (4, 'd', 4), + (5, 'e', 5), + (6, 'f', 6), + (7, 'g', 7), + (8, 'h', 2147483647), + (9, 'i', 2147483648); + +-- #21930: test recheck conditions on text columns +\set Q1 '/*+ BitmapScan(t) */' +SELECT $$ +:P :Q1 SELECT text_col FROM recheck_test AS t WHERE text_col = 'i' AND text_col < 'j'; +$$ AS query \gset +\set Pnext :iter_query +\i :iter_P2 + +-- PxR: explain/run x target (int4_col vs COUNT(*)) +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +\set R1 'int4_col' +\set R2 'COUNT(*)' +\set Pnext :iter_R2 + +\set query ':P :Q1 SELECT :R FROM recheck_test t WHERE int4_col < 3 AND int4_col IN (2, 5, 6);' +\i :iter_P2 +\set query ':P :Q1 SELECT :R FROM recheck_test t WHERE int4_col IN (2, 5, 6) AND int4_col < 3;' +\i :iter_P2 + +\set query ':P :Q1 SELECT :R FROM recheck_test t WHERE int4_col < 3 AND int4_col = 5;' +\i :iter_P2 +\set query ':P :Q1 SELECT :R FROM recheck_test t WHERE int4_col = 5 AND int4_col < 3;' +\i :iter_P2 + +-- PxQxR: explain/run x expression_pushdown x target +-- test where casting may cause us to require recheck +\set explain 'EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF)' +\set Q1 '/*+ BitmapScan(t) */' +\set Q2 '/*+ BitmapScan(t) Set(yb_enable_expression_pushdown false) */' +\set Pnext :iter_Q2 +\set Qnext :iter_R2 + +\set query ':P :Q SELECT :R FROM recheck_test t WHERE int4_col = 2147483647;' +\i :iter_P2 +\set query ':P :Q SELECT :R FROM recheck_test t WHERE int4_col = 2147483648;' +\i :iter_P2 + +\set Q1 '/*+ NestLoop(s t) BitmapScan(t) Set(yb_bnl_batch_size 1) */' +\set Q2 '/*+ NestLoop(s t) BitmapScan(t) Set(yb_bnl_batch_size 1) Set(yb_enable_expression_pushdown false) */' +\set Qnext :iter_query +\set query ':explain :Q SELECT s.bigint_col, t.int4_col FROM recheck_test s JOIN recheck_test t ON s.bigint_col = t.int4_col;' +\i :iter_Q2 + +-- +-- #22622: test local recheck of condition for a column that is not a target +-- +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +\set Q1 '/*+ IndexScan(t) */' +-- the second condition is necessary to force a recheck +-- the first condition is rechecked locally, so k2 must be fetched. +\set Q2 '/*+ BitmapScan(t) */' +SELECT $$ +:explain :Q select int4_col from recheck_test t where text_col like 'a%' AND text_col IN ('a', 'b', 'c'); +$$ AS query \gset +\i :iter_Q2 + +-- also check for aggregate pushdowns +SELECT $$ +:P :Q select count(*) from recheck_test t where text_col like 'a%' AND text_col IN ('a', 'b', 'c'); +$$ AS query \gset +\set Pnext :iter_Q2 +\i :iter_P2 + +-- +-- test pk of different types +-- +CREATE TABLE pk_text (k_text varchar PRIMARY KEY, v_int INT); +CREATE INDEX ON pk_text(v_int ASC); +INSERT INTO pk_text SELECT i::text, i FROM generate_series(1, 100) i; + +\set explain 'EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF)' +\set Q1 '/*+ BitmapScan(pk_text) */' +SELECT $$ +:P :Q1 SELECT * FROM pk_text WHERE v_int = 12 OR k_text = '12'; +$$ AS query \gset +\set Pnext :iter_query +\i :iter_P2 + +SELECT $$ +:P :Q1 SELECT * FROM pk_text WHERE (v_int < 10 AND v_int % 2 = 1) OR (k_text LIKE '999%' AND k_text LIKE '%5') ORDER BY v_int; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P :Q1 SELECT * FROM pk_text WHERE v_int < 5 OR k_text < '11' ORDER BY v_int; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P :Q1 SELECT * FROM pk_text WHERE v_int IN (12, 13) OR k_text IN ('11', '12') ORDER BY v_int; +$$ AS query \gset +\i :iter_P2 + +-- test count +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +SELECT $$ +:P :Q1 SELECT COUNT(*) FROM pk_text WHERE v_int IN (12, 13) OR k_text IN ('11', '12'); +$$ AS query \gset +\i :iter_P2 + +-- test non-existent results +SELECT $$ +:P :Q1 SELECT COUNT(*) FROM pk_text WHERE v_int = 2000 OR k_text = 'nonexistent'; +$$ AS query \gset +\i :iter_P2 + +-- +-- BitmapAnd +-- These tests require CBO because the basic cost model does not properly cost +-- remote filters, so BitmapAnds were comparatively more expensive. +-- +-- Hints can tell the planner to use a Bitmap Scan, but they cannot tell the +-- planner how to design the plan. The planner chooses for itself how it should +-- apply remote filters, Bitmap Ands, Bitmap Ors. For these queries, we are +-- interested in testing the behaviour of a BitmapAnd, not testing the planner's +-- ability to choose between a bitmap scan and a sequential scan. IF the planner +-- must choose a bitmap scan, what plan does it choose? Does that plan work? +-- +SET yb_enable_base_scans_cost_model = true; + +CREATE TABLE test_and (a INT, b INT, c INT); +CREATE INDEX ON test_and (a ASC); +CREATE INDEX ON test_and (b ASC); +CREATE INDEX ON test_and (c ASC); +INSERT INTO test_and SELECT i, j, k FROM generate_series(1, 20) i, generate_series(1, 20) j, generate_series(1, 20) k; +ANALYZE test_and; + +\set explain 'EXPLAIN (ANALYZE, SUMMARY OFF, COSTS OFF)' +\set Q1 '/*+ BitmapScan(t) */' +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 6 AND b < 6;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 6 AND c < 6;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE b < 6 AND c < 6;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 6 AND b < 6 AND c < 7;' +:query + +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 10 AND b < 10;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 10 AND c < 10;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE b < 10 AND c < 10;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 10 AND b < 10 AND c < 10;' +:query + +-- complex nested queries +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 5 AND (b < 3 OR b > 16);' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE (b < 3 AND a < 5) OR (b > 16 AND a < 5);' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE (b < 3 AND a < 5) OR (b > 16 AND a < 6);' +:query + +RESET yb_enable_base_scans_cost_model; +RESET enable_bitmapscan; diff --git a/src/test/regress/sql/yb.orig.bitmap_scans_colo.sql b/src/test/regress/sql/yb.orig.bitmap_scans_colo.sql new file mode 100644 index 0000000000000..939c642ec5193 --- /dev/null +++ b/src/test/regress/sql/yb.orig.bitmap_scans_colo.sql @@ -0,0 +1,443 @@ +-- +-- YB Bitmap Scans on a Colocated DB (bitmap index scans + YB bitmap table scans) +-- +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 + +CREATE DATABASE colo WITH colocation = true; +\c colo; +SET yb_enable_bitmapscan = true; +SET enable_bitmapscan = true; + +CREATE TABLE simple (k INT PRIMARY KEY, ind_a INT, ind_b INT); +INSERT INTO simple SELECT i, i * 2, i * 3 FROM generate_series(1, 10) i; +CREATE INDEX ON simple(ind_a ASC); +CREATE INDEX ON simple(ind_b ASC); + +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +\set Q1 '/*+ BitmapScan(simple) */' +\set query ':P :Q1 SELECT * FROM simple WHERE k = 1;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM simple WHERE ind_a < 5 ORDER BY k;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM simple WHERE ind_a BETWEEN 2 AND 6 OR ind_b > 25 ORDER BY k;' +\i :iter_P2 + +-- +-- test cases where we could skip fetching the table rows (TODO: #22044) +-- +-- this query does not need a recheck, so we don't need to fetch the rows for the COUNT(*) +\set explain 'EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF)' +\set query ':P :Q1 SELECT COUNT(*) FROM simple WHERE ind_a = 2 OR ind_b < 10;' +\i :iter_P2 + +-- when we require the rows, notice that the YB Bitmap Table Scan sends a table read request +\set query ':explain :Q1 SELECT * FROM simple WHERE ind_a = 2 OR ind_b < 10;' +:query + +-- this query has a recheck condition, so we need to fetch the rows +-- TODO(#29894): seems "test_skip" in the hint should be "simple". +/*+ BitmapScan(simple) Set(yb_enable_expression_pushdown false) */ EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF) +SELECT COUNT(*) FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0); +/*+ BitmapScan(test_skip) Set(yb_enable_expression_pushdown false) */ +SELECT COUNT(*) FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0); + +-- when the expression can be pushed down, we don't need a recheck but we do +-- still need to send the request. +\set query ':P :Q1 SELECT COUNT(*) FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0);' +\i :iter_P2 + +-- other aggregates may require the rows +\set query ':P :Q1 SELECT SUM(ind_a) FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0);' +\i :iter_P2 +\set query ':P :Q1 SELECT MAX(ind_a) FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0);' +\i :iter_P2 + +-- when we don't need the actual value, we can avoid fetching +\set query ':P :Q1 SELECT 1 FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0);' +\i :iter_P2 +\set query ':explain :Q1 SELECT random() FROM simple WHERE ind_a = 2 OR (ind_b < 10 AND ind_b % 2 = 0);' +:query + +-- +-- test primary key queries +-- +\set query ':P :Q1 SELECT * FROM simple WHERE k = 1 OR ind_a = 2;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM simple WHERE k IN (1, 2) OR ind_a IN (4, 6) ORDER BY k;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM simple WHERE k = 1 OR k = 2 OR ind_a = 4 OR ind_a = 6 ORDER BY k;' +\i :iter_P2 + +-- test non-existent results +\set query ':P :Q1 SELECT COUNT(*) FROM simple WHERE k = 2000 OR ind_a < 0;' +\i :iter_P2 + +-- +-- test unsatisfiable conditions +-- + +\set query ':explain :Q1 SELECT * FROM simple WHERE (k <= 1 AND k = 2);' +:query +\set query ':explain :Q1 SELECT * FROM simple WHERE (k = 1 AND k = 2) OR ind_b = 0;' +:query +\set query ':explain :Q1 SELECT * FROM simple WHERE (ind_a <= 1 AND ind_a = 2);' +:query +\set query ':explain :Q1 SELECT * FROM simple WHERE (ind_a = 2 AND ind_a = 2) OR ind_b = 0;' +:query + +-- +-- #21793: test row compare expressions +-- +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +\set query ':P :Q1 SELECT * FROM simple WHERE (ind_a, ind_a) <= (4, 1) OR ind_a IS NULL;' +\i :iter_P2 + +-- +-- #22062: variable not found in subplan target list +-- +EXPLAIN (COSTS OFF) /*+ BitmapScan(simple) */ +SELECT * FROM simple AS tbl_1 JOIN simple AS tbl_2 ON tbl_1.ind_a = tbl_2.ind_a OR (tbl_1.ind_b = tbl_2.ind_a AND tbl_1.ind_a = 2); + +-- +-- #22065: test pushdowns on Subplans +-- +\set Q1 '/*+ BitmapScan(simple_1) BitmapScan(simple) */' +\set query ':P :Q1 SELECT * FROM simple WHERE ind_a = 2 AND ind_a <> (SELECT ind_a FROM simple WHERE ind_b = 1);' +\i :iter_P2 + +-- +-- test UPDATE +-- + +\set Q1 '/*+ BitmapScan(simple) */' +\set query ':explain :Q1 UPDATE simple SET ind_a = NULL WHERE ind_a < 10 OR ind_b > 25;' +:query + +\set query ':P :Q1 SELECT ind_a, ind_b FROM simple WHERE ind_a < 10 OR ind_a IS NULL OR ind_b < 15 ORDER BY k;' +\i :iter_P2 + +-- NOTE: this GUC cannot be set using pg_hint_plan Set(...) because it is +-- currently used during execution, not just planning. +SET yb_test_skip_binding_scan_keys = true; + +\i :iter_P2 + +RESET yb_test_skip_binding_scan_keys; + +-- use Bitmap Scan to delete rows and validate their deletion +\set query ':explain :Q1 DELETE FROM simple WHERE ind_a IS NULL OR ind_b < 15;' +:query + +\set query ':P :Q1 SELECT ind_a, ind_b FROM simple WHERE ind_a IS NULL OR ind_b < 15 ORDER BY k;' +\i :iter_P2 + + +-- +-- test indexes on multiple columns / indexes with additional columns +-- +CREATE TABLE multi (a INT, b INT, c INT, h INT, PRIMARY KEY (a ASC, b ASC)); +CREATE INDEX ON multi (c ASC) INCLUDE (a); +CREATE INDEX ON multi (h ASC) INCLUDE (a); +CREATE INDEX ON multi (b ASC, c ASC); +INSERT INTO multi SELECT i, i * 2, i * 3, i * 4 FROM generate_series(1, 1000) i; + +\set explain 'EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF)' +\set Q1 '/*+ BitmapScan(multi) */' +\set query ':P :Q1 SELECT * FROM multi WHERE a < 2 OR b > 1997 ORDER BY a;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM multi WHERE c BETWEEN 10 AND 15 AND a < 30 ORDER BY a;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM multi WHERE a < 2 OR b > 1997 OR c BETWEEN 10 AND 15 OR h = 8 ORDER BY a;' +\i :iter_P2 + +-- try some slightly complex nested logical operands queries +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +\set query ':P :Q1 SELECT * FROM multi WHERE a < 2 OR (b > 1797 AND (c BETWEEN 2709 AND 2712 OR c = 2997)) ORDER BY a;' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM multi WHERE (a < 3 AND a % 2 = 0) OR (b IN (10, 270, 1800) AND (c < 20 OR c > 2500)) ORDER BY a;' +\i :iter_P2 + +-- +-- test limits +-- +SET yb_fetch_row_limit = 100; +SET yb_fetch_size_limit = 0; + +\set explain 'EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF)' +\set query ':explain :Q1 SELECT * FROM multi WHERE a < 200 LIMIT 10;' +:query + +SET yb_fetch_row_limit = 0; +SET yb_fetch_size_limit = '1kB'; + +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +:query + +RESET yb_fetch_row_limit; +RESET yb_fetch_size_limit; + +-- +-- test exceeding work_mem +-- +INSERT INTO multi SELECT i, i * 2, i * 3, i * 4 FROM generate_series(1001, 10000) i; + +SET work_mem TO '4MB'; -- does not exceed work_mem + +\set query ':explain :Q1 SELECT * FROM multi WHERE a < 5000 OR (b > 0 AND (c < 3000 OR c > 27000)) ORDER BY a;' +:query + +SET work_mem TO '4GB'; -- does not exceed very large work_mem + +:query + +SET work_mem TO '100kB'; +-- normal case +:query + +-- verify remote filters apply to the table scan when we've exceeded work_mem +-- hint2: verify local filters still apply when pushdown is disabled +\set Q2 '/*+ BitmapScan(multi) Set(yb_enable_expression_pushdown false) */' +\set query ':explain :Q SELECT * FROM multi WHERE a < 5000 OR (b > 0 AND (c < 3000 OR c > 27000) AND b % 2 = 0) ORDER BY a;' +\i :iter_Q2 + +-- where the first bitmap index scan exceeds work_mem +\set query ':explain :Q1 SELECT * FROM multi WHERE a < 5000 OR (b > 0 AND (c < 3000 OR c > 27000)) ORDER BY a;' +:query + +-- where the second bitmap index scan exceeds work_mem +\set query ':explain :Q1 SELECT * FROM multi WHERE a < 1000 OR (b > 0 AND (c < 15000 OR c > 27000)) ORDER BY a;' +:query + +-- where the third bitmap index scan exceeds work_mem +\set query ':explain :Q1 SELECT * FROM multi WHERE a < 1000 OR (b > 0 AND (c < 3000 OR c > 15000)) ORDER BY a;' +:query + +-- check aggregate pushdown +SET yb_enable_expression_pushdown = true; +SET yb_enable_index_aggregate_pushdown = true; +\set query ':P :Q1 SELECT COUNT(*) FROM multi WHERE a > 10;' +\i :iter_P2 + +SET yb_enable_expression_pushdown = false; +SET yb_enable_index_aggregate_pushdown = true; +\set query ':P :Q1 SELECT COUNT(*) FROM multi WHERE a > 10;' +\i :iter_P2 + +SET yb_enable_expression_pushdown = false; +SET yb_enable_index_aggregate_pushdown = false; +\set query ':P :Q1 SELECT COUNT(*) FROM multi WHERE a > 10;' +\i :iter_P2 + +SET yb_enable_expression_pushdown = true; +SET yb_enable_index_aggregate_pushdown = false; +\set query ':P :Q1 SELECT COUNT(*) FROM multi WHERE a > 10;' +\i :iter_P2 + +RESET yb_enable_index_aggregate_pushdown; +RESET yb_enable_expression_pushdown; + +RESET work_mem; + +-- +-- test remote pushdown +-- + +\set explain 'EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF)' +\set Q1 '/*+ BitmapScan(multi multi_b_c_idx) */' +\set Q2 '/*+ BitmapScan(multi multi_b_c_idx) Set(yb_enable_expression_pushdown false) */' +\set query ':P :Q SELECT * FROM multi WHERE (b < 10 AND b % 4 = 0) ORDER BY b;' +\set Pnext :iter_Q2 +\i :iter_P2 + +\set Q1 '/*+ BitmapScan(multi) */' +\set Q2 '/*+ BitmapScan(multi) Set(yb_enable_expression_pushdown false) */' +\set query ':P :Q SELECT * FROM multi WHERE (a < 5 AND a % 2 = 0) OR (c <= 10 AND a % 3 = 0) ORDER BY a;' +\i :iter_P2 + +-- +-- test recheck index conditions +-- +create table recheck_test (int4_col int4, text_col TEXT, bigint_col bigint); +create index on recheck_test (int4_col ASC); +create index on recheck_test (text_col ASC); +create index on recheck_test (bigint_col ASC); + +insert into recheck_test VALUES + (1, 'a', 1), + (2, 'b', 2), + (3, 'c', 3), + (4, 'd', 4), + (5, 'e', 5), + (6, 'f', 6), + (7, 'g', 7), + (8, 'h', 2147483647), + (9, 'i', 2147483648); + +-- #21930: test recheck conditions on text columns +\set Q1 '/*+ BitmapScan(t) */' +SELECT $$ +:P :Q1 SELECT text_col FROM recheck_test AS t WHERE text_col = 'i' AND text_col < 'j'; +$$ AS query \gset +\set Pnext :iter_query +\i :iter_P2 + +-- PxR: explain/run x target (int4_col vs COUNT(*)) +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +\set R1 'int4_col' +\set R2 'COUNT(*)' +\set Pnext :iter_R2 + +\set query ':P :Q1 SELECT :R FROM recheck_test t WHERE int4_col < 3 AND int4_col IN (2, 5, 6);' +\i :iter_P2 +\set query ':P :Q1 SELECT :R FROM recheck_test t WHERE int4_col IN (2, 5, 6) AND int4_col < 3;' +\i :iter_P2 + +\set query ':P :Q1 SELECT :R FROM recheck_test t WHERE int4_col < 3 AND int4_col = 5;' +\i :iter_P2 +\set query ':P :Q1 SELECT :R FROM recheck_test t WHERE int4_col = 5 AND int4_col < 3;' +\i :iter_P2 + +-- PxQxR: explain/run x expression_pushdown x target +-- test where casting may cause us to require recheck +\set explain 'EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF)' +\set Q1 '/*+ BitmapScan(t) */' +\set Q2 '/*+ BitmapScan(t) Set(yb_enable_expression_pushdown false) */' +\set Pnext :iter_Q2 +\set Qnext :iter_R2 + +\set query ':P :Q SELECT :R FROM recheck_test t WHERE int4_col = 2147483647;' +\i :iter_P2 +\set query ':P :Q SELECT :R FROM recheck_test t WHERE int4_col = 2147483648;' +\i :iter_P2 + +\set Q1 '/*+ NestLoop(s t) BitmapScan(t) Set(yb_bnl_batch_size 1) */' +\set Q2 '/*+ NestLoop(s t) BitmapScan(t) Set(yb_bnl_batch_size 1) Set(yb_enable_expression_pushdown false) */' +\set Qnext :iter_query +\set query ':explain :Q SELECT s.bigint_col, t.int4_col FROM recheck_test s JOIN recheck_test t ON s.bigint_col = t.int4_col;' +\i :iter_Q2 + +-- +-- #22622: test local recheck of condition for a column that is not a target +-- +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +\set Q1 '/*+ IndexScan(t) */' +-- the second condition is necessary to force a recheck +-- the first condition is rechecked locally, so k2 must be fetched. +\set Q2 '/*+ BitmapScan(t) */' +SELECT $$ +:explain :Q select int4_col from recheck_test t where text_col like 'a%' AND text_col IN ('a', 'b', 'c'); +$$ AS query \gset +\i :iter_Q2 + +-- also check for aggregate pushdowns +SELECT $$ +:P :Q select count(*) from recheck_test t where text_col like 'a%' AND text_col IN ('a', 'b', 'c'); +$$ AS query \gset +\set Pnext :iter_Q2 +\i :iter_P2 + +-- +-- test pk of different types +-- +CREATE TABLE pk_text (k_text varchar PRIMARY KEY, v_int INT); +CREATE INDEX ON pk_text(v_int ASC); +INSERT INTO pk_text SELECT i::text, i FROM generate_series(1, 100) i; + +\set explain 'EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF)' +\set Q1 '/*+ BitmapScan(pk_text) */' +SELECT $$ +:P :Q1 SELECT * FROM pk_text WHERE v_int = 12 OR k_text = '12'; +$$ AS query \gset +\set Pnext :iter_query +\i :iter_P2 + +SELECT $$ +:P :Q1 SELECT * FROM pk_text WHERE (v_int < 10 AND v_int % 2 = 1) OR (k_text LIKE '999%' AND k_text LIKE '%5') ORDER BY v_int; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P :Q1 SELECT * FROM pk_text WHERE v_int < 5 OR k_text < '11' ORDER BY v_int; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P :Q1 SELECT * FROM pk_text WHERE v_int IN (12, 13) OR k_text IN ('11', '12') ORDER BY v_int; +$$ AS query \gset +\i :iter_P2 + +-- test count +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +SELECT $$ +:P :Q1 SELECT COUNT(*) FROM pk_text WHERE v_int IN (12, 13) OR k_text IN ('11', '12'); +$$ AS query \gset +\i :iter_P2 + +-- test non-existent results +SELECT $$ +:P :Q1 SELECT COUNT(*) FROM pk_text WHERE v_int = 2000 OR k_text = 'nonexistent'; +$$ AS query \gset +\i :iter_P2 + +-- +-- BitmapAnd +-- These tests require CBO because the basic cost model does not properly cost +-- remote filters, so BitmapAnds were comparatively more expensive. +-- +-- Hints can tell the planner to use a Bitmap Scan, but they cannot tell the +-- planner how to design the plan. The planner chooses for itself how it should +-- apply remote filters, Bitmap Ands, Bitmap Ors. For these queries, we are +-- interested in testing the behaviour of a BitmapAnd, not testing the planner's +-- ability to choose between a bitmap scan and a sequential scan. IF the planner +-- must choose a bitmap scan, what plan does it choose? Does that plan work? +-- +SET yb_enable_base_scans_cost_model = true; + +CREATE TABLE test_and (a INT, b INT, c INT); +CREATE INDEX ON test_and (a ASC); +CREATE INDEX ON test_and (b ASC); +CREATE INDEX ON test_and (c ASC); +INSERT INTO test_and SELECT i, j, k FROM generate_series(1, 20) i, generate_series(1, 20) j, generate_series(1, 20) k; +ANALYZE test_and; + +\set explain 'EXPLAIN (ANALYZE, SUMMARY OFF, COSTS OFF)' +\set Q1 '/*+ BitmapScan(t) */' +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 6 AND b < 6;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 6 AND c < 6;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE b < 6 AND c < 6;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 6 AND b < 6 AND c < 7;' +:query + +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 10 AND b < 10;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 10 AND c < 10;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE b < 10 AND c < 10;' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 10 AND b < 10 AND c < 10;' +:query + +-- complex nested queries +\set query ':explain :Q1 SELECT * FROM test_and t WHERE a < 5 AND (b < 3 OR b > 16);' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE (b < 3 AND a < 5) OR (b > 16 AND a < 5);' +:query +\set query ':explain :Q1 SELECT * FROM test_and t WHERE (b < 3 AND a < 5) OR (b > 16 AND a < 6);' +:query + +RESET yb_enable_base_scans_cost_model; +RESET enable_bitmapscan; diff --git a/src/test/regress/sql/yb.orig.bitmap_scans_distinct.sql b/src/test/regress/sql/yb.orig.bitmap_scans_distinct.sql new file mode 100644 index 0000000000000..58a684d35d6c0 --- /dev/null +++ b/src/test/regress/sql/yb.orig.bitmap_scans_distinct.sql @@ -0,0 +1,54 @@ +-- +-- test distinct bitmap scans (distinct pushdown is not supported by bitmap scans) +-- +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' + +SET yb_enable_bitmapscan = true; + +CREATE TABLE test_distinct (r1 INT, r2 INT, r3 INT, v INT, PRIMARY KEY(r1 ASC, r2 ASC, r3 ASC)) SPLIT AT VALUES ((1, 1, 500)); +INSERT INTO test_distinct (SELECT 1, i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); +-- Add one more distinct value to catch bugs that arise only with more than one distinct value. +INSERT INTO test_distinct (SELECT 2, i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); + +SET yb_enable_distinct_pushdown = true; +SET enable_bitmapscan = false; + +\set query ':P SELECT DISTINCT r1 FROM test_distinct WHERE r1 < 2 ORDER BY r1;' +\i :iter_P2 +\set query ':P SELECT DISTINCT r1, r2 FROM test_distinct WHERE r1 < 2 OR r2 < 3 ORDER BY r1, r2;' +\i :iter_P2 +\set query ':P SELECT DISTINCT r1, r2 FROM test_distinct WHERE r1 < 2 OR r2 < 3 OR r3 < 2 ORDER BY r1, r2;' +\i :iter_P2 + +RESET enable_bitmapscan; + +\set Q1 '/*+ BitmapScan(test_distinct) */' +\set query ':P :Q1 SELECT DISTINCT r1 FROM test_distinct WHERE r1 < 2 ORDER BY r1;' +\i :iter_P2 +\set query ':P :Q1 SELECT DISTINCT r1, r2 FROM test_distinct WHERE r1 < 2 OR r2 < 3 ORDER BY r1, r2;' +\i :iter_P2 +\set query ':P :Q1 SELECT DISTINCT r1, r2 FROM test_distinct WHERE r1 < 2 OR r2 < 3 OR r3 < 2 ORDER BY r1, r2;' +\i :iter_P2 + +SET yb_enable_distinct_pushdown TO false; +SET enable_bitmapscan = false; + +\set query ':P SELECT DISTINCT r1 FROM test_distinct WHERE r1 < 2 ORDER BY r1;' +\i :iter_P2 +\set query ':P SELECT DISTINCT r1, r2 FROM test_distinct WHERE r1 < 2 OR r2 < 3 ORDER BY r1, r2;' +\i :iter_P2 + +RESET enable_bitmapscan; + +\set query ':P :Q1 SELECT DISTINCT r1 FROM test_distinct WHERE r1 < 2 ORDER BY r1;' +\i :iter_P2 +\set query ':P :Q1 SELECT DISTINCT r1, r2 FROM test_distinct WHERE r1 < 2 OR r2 < 3 ORDER BY r1, r2;' +\i :iter_P2 + +RESET yb_enable_distinct_pushdown; +DROP TABLE test_distinct; diff --git a/src/test/regress/sql/yb.orig.bitmap_scans_flags.sql b/src/test/regress/sql/yb.orig.bitmap_scans_flags.sql new file mode 100644 index 0000000000000..8d3020ebb014a --- /dev/null +++ b/src/test/regress/sql/yb.orig.bitmap_scans_flags.sql @@ -0,0 +1,44 @@ +-- +-- Test disabling YB Bitmap Scans +-- +-- for each combination of yb_enable_bitmapscan and enable_bitmapscan, try +-- 1. a case where the planner chooses bitmap scans +-- 2. a case where we tell the planner to use bitmap scans +-- +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename + +CREATE TABLE test_disable(a int, b int); +CREATE INDEX ON test_disable(a ASC); +CREATE INDEX ON test_disable(b ASC); + +CREATE TEMP TABLE tmp_test_disable(a int, b int); +CREATE INDEX ON tmp_test_disable(a ASC); +CREATE INDEX ON tmp_test_disable(b ASC); + +\set P1 'test_disable' +\set P2 'tmp_test_disable' +\set Q1 +\set bitmap_hint_iter_Q2 :abs_srcdir '/yb_commands/bitmap_hint_iter_Q2.sql' +\set Pnext :bitmap_hint_iter_Q2 +SELECT $$EXPLAIN (COSTS OFF) :Q SELECT * FROM :P WHERE a < 5 OR b < 5;$$ AS query \gset + +SET yb_enable_bitmapscan = true; +SET enable_bitmapscan = true; +\i :iter_P2 + +SET yb_enable_bitmapscan = true; +SET enable_bitmapscan = false; +\i :iter_P2 + +SET yb_enable_bitmapscan = false; +SET enable_bitmapscan = true; +\i :iter_P2 + +SET yb_enable_bitmapscan = false; +SET enable_bitmapscan = false; +\i :iter_P2 + +RESET enable_bitmapscan; +RESET yb_enable_bitmapscan; diff --git a/src/test/regress/sql/yb.orig.bitmap_scans_joins.sql b/src/test/regress/sql/yb.orig.bitmap_scans_joins.sql new file mode 100644 index 0000000000000..2335b72940419 --- /dev/null +++ b/src/test/regress/sql/yb.orig.bitmap_scans_joins.sql @@ -0,0 +1,263 @@ +-- +-- Joins with YB Bitmap Scans (bitmap index scans + YB bitmap table scans) +-- +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' + +SET enable_bitmapscan = true; +SET yb_enable_bitmapscan = true; +SET yb_prefer_bnl = false; + +CREATE TABLE joina (k INT, a INT, b INT, PRIMARY KEY (k ASC)); +CREATE INDEX ON joina (a ASC); +CREATE INDEX ON joina (b ASC); + +CREATE TABLE joinb (k INT, c INT, d INT, PRIMARY KEY (k ASC)); +CREATE INDEX ON joinb (c ASC); +CREATE INDEX ON joinb (d ASC); + +INSERT INTO joina SELECT i, i * 2, i * 3 FROM generate_series(1, 10) i; +INSERT INTO joinb SELECT i, i * 2, i * 3 FROM generate_series(1, 10) i; + +-- +-- Test Bitmap Scan as Outer Join table -- +-- + +-- join PK to PK +\set Q1 '/*+ NestLoop(joina joinb) Leading(joina joinb) BitmapScan(joina) */' +\set Q2 '/*+ YbBatchedNL(joina joinb) Leading(joina joinb) BitmapScan(joina) */' +\set query ':P :Q SELECT * FROM joina JOIN joinb ON joina.k = joinb.k WHERE joina.a < 10 OR joina.b < 15 ORDER BY joina.a;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- join index col to PK +\set query ':P :Q SELECT * FROM joina JOIN joinb ON joina.a = joinb.k WHERE joina.a < 10 OR joina.b < 15 ORDER BY joina.a;' +\i :iter_P2 + +-- join PK to index col +\set query ':P :Q SELECT * FROM joina JOIN joinb ON joina.k = joinb.c WHERE joina.a < 10 OR joina.b < 15 ORDER BY joina.a;' +\i :iter_P2 + +-- join index col to index col +\set query ':P :Q SELECT * FROM joina JOIN joinb ON joina.a = joinb.c WHERE joina.a < 10 OR joina.b < 15 ORDER BY joina.a;' +\i :iter_P2 + +-- +-- Test Bitmap Scan as Inner Join table -- +-- + +-- join PK to PK +\set Q1 '/*+ NestLoop(joina joinb) Leading(joina joinb) BitmapScan(joinb) */' +\set Q2 '/*+ MergeJoin(joina joinb) Leading(joina joinb) BitmapScan(joinb) */' +\set Q3 '/*+ HashJoin(joina joinb) Leading(joina joinb) BitmapScan(joinb) */' +\set Q4 '/*+ YbBatchedNL(joina joinb) Leading(joina joinb) BitmapScan(joinb) */' +\set query ':P :Q SELECT * FROM joina JOIN joinb ON joina.k = joinb.k WHERE joina.a < 10 OR joina.b < 15 OR joinb.c < 10 OR joinb.d < 15 ORDER BY joina.a;' +\set Pnext :iter_Q4 +\i :iter_P2 + +-- join index col to PK +\set query ':P :Q SELECT * FROM joina JOIN joinb ON joina.a = joinb.k WHERE joina.a < 10 OR joina.b < 15 OR joinb.c < 10 OR joinb.d < 15 ORDER BY joina.a;' +\i :iter_P2 + +-- join PK to index col +\set query ':P :Q SELECT * FROM joina JOIN joinb ON joina.k = joinb.c WHERE joina.a < 10 OR joina.b < 15 OR joinb.c < 10 OR joinb.d < 15 ORDER BY joina.a;' +\i :iter_P2 + +-- join index col to index col +\set query ':P :Q SELECT * FROM joina JOIN joinb ON joina.a = joinb.c WHERE joina.a < 10 OR joina.b < 15 OR joinb.c < 10 OR joinb.d < 15 ORDER BY joina.a;' +\i :iter_P2 + +-- +-- Test joins where one of the paths is never executed +-- +\set Q1 '/*+ BitmapScan(joinb) */' +SELECT $$ +:P :Q1 +SELECT joina.a, + (SELECT joinb.c FROM joinb WHERE (joina.k = joinb.k OR joinb.d = 1) + AND joina.b = -1) -- unsatisfiable + FROM joina ORDER BY joina.a; +$$ AS query \gset +\set Pnext :iter_query +\i :iter_P2 + +-- +-- test joins with a function scan +-- +-- insert entries into pg_yb_tablegroup and analyze so the planner does not +-- think the table is empty. +CREATE TABLEGROUP tg1; +CREATE TABLEGROUP tg2; +ANALYZE pg_yb_tablegroup; + +\set Q1 '/*+ BitmapScan(gr) */' +SELECT $$ +:P :Q1 + SELECT grpname, is_colocated + FROM pg_catalog.yb_table_properties(16384) p +LEFT JOIN pg_catalog.pg_yb_tablegroup gr + ON gr.oid = p.tablegroup_oid; +$$ AS query \gset +\i :iter_P2 + +DROP TABLEGROUP tg1; +DROP TABLEGROUP tg2; + +-- +-- test where a join filter should be required +-- +CREATE TABLE test_join_filter(a INT, b INT, v VARCHAR); +CREATE INDEX ON test_join_filter (a ASC); +CREATE INDEX ON test_join_filter (b ASC); +CREATE INDEX ON test_join_filter (v ASC); +INSERT INTO test_join_filter VALUES (1, 4, 'v'), (2, 62, 'v'), (3, 7, 'c'), (4, 1, NULL), (5, 0, 'x'), + (6, 7, 'i'), (7, 7, 'e'), (8, 1, 'p'), (9, 7, 's'), (10, 1, 'j'), + (11, 5, 'z'), (12, 2, 'c'), (13, 0, 'a'), (14, 1, 'q'), (15, 8, 'y'), + (16, 1, NULL), (17, 1, 'r'), (18, 9, 'v'), (19, 1, NULL), (20, 5, 'r'); + +-- we need a join filter here because the final scan does not contain all quals +\set Q1 '/*+ BitmapScan(table2) SeqScan(table1) Leading(((table3 table2) table1)) */' +-- we don't need a join filter here because the final scan does satisfy all quals (because its a bitmap scan) +\set Q2 '/*+ BitmapScan(table1) Leading(((table3 table2) table1)) */' +SELECT $$ +:explain :Q + SELECT table1.v, table1.b + FROM test_join_filter AS table1 +INNER JOIN (( test_join_filter AS table2 INNER JOIN test_join_filter AS table3 ON (( table3.v = table2.v ) OR ( table3.b = table2.a ) ) ) ) + ON (( table3.a >= table2.a ) AND (table3.a <> table2.b ) ) + WHERE ( table1.v = 'g' AND table1.v = 's' ) OR table1.a <= table2.b; +$$ AS query \gset +\i :iter_Q2 + +-- +-- Rescans where we don't need the actual rows +-- This test is based off #21526 identified by the random query generator. +-- To speed up the test, don't bother creating new tables. +-- +\set Q1 '/*+ BitmapScan(joina) */' +\set Q2 '/*+ Set(enable_bitmapscan false) */' +\set query ':P :Q SELECT * FROM joinb WHERE EXISTS (SELECT FROM joina WHERE joina.a >= joinb.d) OR joinb.c = 1 ORDER BY joinb.k;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- +-- GHI #26210 +-- Test that BitmapIndexScan's StorageFilter is properly set upon rescan +-- +\set explain 'EXPLAIN (COSTS OFF)' +\set Q1 '/*+ BitmapScan(b) */' +\set query ':P :Q1 SELECT b.* FROM (VALUES (2,2), (12,12), (4,4), (16,16), (6,6), (20,20), (8,8)) v(c1, c2) JOIN joinb b ON v.c1 = b.c WHERE b.c + 5 > v.c2;' +\set Pnext :iter_query +\i :iter_P2 +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' + +-- +-- BitmapAnd tests +-- This is issue #21495 identified by the random query generator. +-- +CREATE TABLE bb ( + pk serial, + col_int_nokey integer, + col_int_key integer, + col_date_key date, + col_date_nokey date, + col_time_key time without time zone, + col_time_nokey time without time zone, + col_datetime_key timestamp without time zone, + col_datetime_nokey timestamp without time zone, + col_varchar_key character varying(1), + col_varchar_nokey character varying(1), + CONSTRAINT bb_pkey PRIMARY KEY(pk ASC) +); + +CREATE INDEX bb_int_key ON bb USING lsm (col_int_key ASC); + +CREATE TABLE c ( + pk serial, + col_int_nokey integer, + col_int_key integer, + col_date_key date, + col_date_nokey date, + col_time_key time without time zone, + col_time_nokey time without time zone, + col_datetime_key timestamp without time zone, + col_datetime_nokey timestamp without time zone, + col_varchar_key character varying(1), + col_varchar_nokey character varying(1), + CONSTRAINT c_pkey PRIMARY KEY(pk ASC) +); + +CREATE INDEX c_int_key ON c USING lsm (col_int_key ASC); +CREATE INDEX c_varchar_key ON c USING lsm (col_varchar_key ASC, col_int_key ASC); + +COPY bb (pk, col_int_nokey, col_int_key, col_date_key, col_date_nokey, col_time_key, col_time_nokey, col_datetime_key, col_datetime_nokey, col_varchar_key, col_varchar_nokey) FROM stdin; +10 7 8 2004-10-02 2004-10-02 04:07:22.028954 04:07:22.028954 2032-10-08 00:00:00 2032-10-08 00:00:00 g g +\. + +COPY c (pk, col_int_nokey, col_int_key, col_date_key, col_date_nokey, col_time_key, col_time_nokey, col_datetime_key, col_datetime_nokey, col_varchar_key, col_varchar_nokey) FROM stdin; +1 2 4 1984-12-05 1984-12-05 22:34:09.023306 22:34:09.023306 1900-01-01 00:00:00 1900-01-01 00:00:00 v v +2 150 62 2021-03-27 2021-03-27 14:26:02.007788 14:26:02.007788 1979-01-03 10:33:32.027981 1979-01-03 10:33:32.027981 v v +3 \N 7 2025-04-09 2025-04-09 14:03:03.042673 14:03:03.042673 2027-11-28 00:50:27.051028 2027-11-28 00:50:27.051028 c c +4 2 1 1972-05-13 1972-05-13 01:46:09.016386 01:46:09.016386 2003-10-09 19:53:04.008332 2003-10-09 19:53:04.008332 \N \N +5 5 0 1992-05-06 1992-05-06 16:21:18.052408 16:21:18.052408 2027-11-08 21:02:12.009395 2027-11-08 21:02:12.009395 x x +6 3 7 1977-03-03 1977-03-03 18:56:33.027423 18:56:33.027423 1974-04-01 00:00:00 1974-04-01 00:00:00 i i +7 1 7 1973-12-28 1973-12-28 \N \N 1900-01-01 00:00:00 1900-01-01 00:00:00 e e +8 4 1 2035-10-20 2035-10-20 09:29:08.048031 09:29:08.048031 1973-07-12 00:00:00 1973-07-12 00:00:00 p p +9 \N 7 2004-04-09 2004-04-09 19:11:10.040728 19:11:10.040728 2006-04-04 01:21:01.040391 2006-04-04 01:21:01.040391 s s +10 2 1 2021-12-25 2021-12-25 11:57:26.013363 11:57:26.013363 1900-01-01 00:00:00 1900-01-01 00:00:00 j j +11 6 5 1900-01-01 1900-01-01 00:39:46.041355 00:39:46.041355 1984-03-05 03:41:18.061978 1984-03-05 03:41:18.061978 z z +12 6 2 \N \N 03:28:15.007081 03:28:15.007081 1989-08-03 11:33:04.049998 1989-08-03 11:33:04.049998 c c +13 8 0 1900-01-01 1900-01-01 06:44:18.007092 06:44:18.007092 2010-04-28 21:44:45.050791 2010-04-28 21:44:45.050791 a a +14 2 1 2027-01-16 2027-01-16 14:36:39.062494 14:36:39.062494 1972-04-06 00:00:00 1972-04-06 00:00:00 q q +15 6 8 1900-01-01 1900-01-01 18:42:45.053707 18:42:45.053707 2017-04-18 00:00:00 2017-04-18 00:00:00 y y +16 8 1 2012-11-23 2012-11-23 02:57:29.012755 02:57:29.012755 2009-12-18 19:39:55.005399 2009-12-18 19:39:55.005399 \N \N +17 3 1 2005-11-04 2005-11-04 16:46:13.01546 16:46:13.01546 2011-08-01 12:19:39.028493 2011-08-01 12:19:39.028493 r r +18 3 9 1994-03-12 1994-03-12 19:39:02.040624 19:39:02.040624 1995-09-25 21:29:06.004058 1995-09-25 21:29:06.004058 v v +19 9 1 2013-06-22 2013-06-22 \N \N 2010-09-20 09:11:48.065041 2010-09-20 09:11:48.065041 \N \N +20 6 5 1995-10-10 1995-10-10 20:58:33.049572 20:58:33.049572 2020-03-27 09:32:04.056959 2020-03-27 09:32:04.056959 r r +\. + +\set Q1 '/*+ BitmapScan(subquery1_t1) */' +\set Q2 '/*+ Set(enable_bitmapscan false) */' +\set query ':P :Q SELECT table1.pk AS pk FROM ( ( SELECT SUBQUERY1_t1 .* FROM ( C AS SUBQUERY1_t1 INNER JOIN BB ON ( BB.col_int_key = SUBQUERY1_t1.pk ) ) ) AS table1 JOIN ( SELECT * FROM C ) AS table2 ON ( table2.col_varchar_key = table1.col_varchar_key ) ) WHERE table1.col_int_key IN ( SELECT col_int_nokey FROM C AS C WHERE C.col_varchar_key != table2.col_varchar_key AND C.col_varchar_nokey >= table2.col_varchar_nokey ) AND table1.pk = table2 .col_int_key OR table1.col_int_key = table2.col_int_key;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- +-- Semi Join +-- +-- TODO(#29894): only the explains are hinted but the runs should also be. +/*+ BitmapScan(joinb) */ EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF) +SELECT joina.a FROM joina WHERE EXISTS (SELECT FROM joinb WHERE joinb.c >= joina.b) ORDER BY joina.a; +SELECT joina.a FROM joina WHERE EXISTS (SELECT FROM joinb WHERE joinb.c >= joina.b) ORDER BY joina.a; +/*+ Set(enable_bitmapscan false) */ EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF) +SELECT joina.a FROM joina WHERE EXISTS (SELECT FROM joinb WHERE joinb.c >= joina.b) ORDER BY joina.a; +SELECT joina.a FROM joina WHERE EXISTS (SELECT FROM joinb WHERE joinb.c >= joina.b) ORDER BY joina.a; + +-- +-- Anti Join +-- +-- TODO(#29894): only the explains are hinted but the runs should also be. +/*+ BitmapScan(joinb) */ EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF) +SELECT joina.a FROM joina WHERE NOT EXISTS (SELECT FROM joinb WHERE joinb.c >= joina.b) ORDER BY joina.a; +SELECT joina.a FROM joina WHERE NOT EXISTS (SELECT FROM joinb WHERE joinb.c >= joina.b) ORDER BY joina.a; +/*+ Set(enable_bitmapscan false) */ EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF) +SELECT joina.a FROM joina WHERE NOT EXISTS (SELECT FROM joinb WHERE joinb.c >= joina.b) ORDER BY joina.a; +SELECT joina.a FROM joina WHERE NOT EXISTS (SELECT FROM joinb WHERE joinb.c >= joina.b) ORDER BY joina.a; + +-- +-- System Table Join where we don't require any values from the Bitmap table +-- +\set Q1 '/*+ NestLoop(c ns) SeqScan(c) BitmapScan(ns) */' +SELECT $$ +:P :Q1 SELECT c.relname FROM pg_class c, pg_namespace ns WHERE ns.oid = c.relnamespace AND c.relname = 'pg_class'; +$$ AS query \gset +\set Pnext :iter_query +\i :iter_P2 + +RESET enable_bitmapscan; +RESET yb_prefer_bnl; diff --git a/src/test/regress/sql/yb.orig.bitmap_scans_system.sql b/src/test/regress/sql/yb.orig.bitmap_scans_system.sql new file mode 100644 index 0000000000000..0d8dc68e8364c --- /dev/null +++ b/src/test/regress/sql/yb.orig.bitmap_scans_system.sql @@ -0,0 +1,38 @@ +-- +-- YB Bitmap Scans on System Tables (bitmap index scans + YB bitmap table scans) +-- +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF)' +\set Q1 '/*+ BitmapScan(pg_authid) */' + +SET yb_enable_bitmapscan = true; +SET enable_bitmapscan = true; + +SELECT $$ +:P :Q1 SELECT rolname FROM pg_authid WHERE rolname LIKE 'pg_%' OR rolname LIKE 'yb_%' ORDER BY rolname; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P :Q1 +SELECT spcname FROM pg_tablespace WHERE spcowner NOT IN ( + SELECT oid FROM pg_roles WHERE rolname = 'postgres' OR rolname LIKE 'pg_%' OR rolname LIKE 'yb_%'); +$$ AS query \gset +\i :iter_P2 + +SET yb_enable_expression_pushdown = false; + +SELECT $$ +:P :Q1 +SELECT spcname FROM pg_tablespace WHERE spcowner NOT IN ( + SELECT oid FROM pg_roles WHERE rolname = 'postgres' OR rolname LIKE 'pg_%' OR rolname LIKE 'yb_%'); +$$ AS query \gset +\i :iter_P2 + +RESET yb_enable_expression_pushdown; +RESET yb_enable_bitmapscan; +RESET enable_bitmapscan; diff --git a/src/test/regress/sql/yb.orig.boolean.sql b/src/test/regress/sql/yb.orig.boolean.sql new file mode 100644 index 0000000000000..8ffacb27cab70 --- /dev/null +++ b/src/test/regress/sql/yb.orig.boolean.sql @@ -0,0 +1,31 @@ +CREATE TABLE bools (b bool); +INSERT INTO bools VALUES (null); +CREATE INDEX NONCONCURRENTLY asc_nulls_last ON bools (b ASC NULLS LAST); -- = (b ASC) +CREATE INDEX NONCONCURRENTLY asc_nulls_first ON bools (b ASC NULLS FIRST); +CREATE INDEX NONCONCURRENTLY desc_nulls_last ON bools (b DESC NULLS LAST); +CREATE INDEX NONCONCURRENTLY desc_nulls_first ON bools (b DESC NULLS FIRST); -- = (b DESC) + +/*+IndexOnlyScan(bools asc_nulls_last)*/ SELECT * FROM bools; +/*+IndexOnlyScan(bools asc_nulls_first)*/ SELECT * FROM bools; +/*+IndexOnlyScan(bools desc_nulls_last)*/ SELECT * FROM bools; +/*+IndexOnlyScan(bools desc_nulls_first)*/ SELECT * FROM bools; + +/*+IndexOnlyScan(bools asc_nulls_last)*/ SELECT FROM bools; +/*+IndexOnlyScan(bools asc_nulls_last)*/ SELECT count(*) FROM bools; + +/*+IndexScan(bools)*/ EXPLAIN (COSTS OFF) SELECT b FROM bools WHERE b; +/*+IndexScan(bools)*/ SELECT b FROM bools WHERE b; + +CREATE TABLE boolpart (a bool) PARTITION BY LIST (a); +CREATE TABLE boolpart_default PARTITION OF boolpart DEFAULT; +CREATE TABLE boolpart_t PARTITION OF boolpart FOR VALUES IN ('true'); +CREATE TABLE boolpart_f PARTITION OF boolpart FOR VALUES IN ('false'); +INSERT INTO boolpart (a) VALUES + (true), + (false), + (true), + (false), + (NULL); +EXPLAIN (COSTS OFF) SELECT * FROM boolpart WHERE a = false; +SELECT * FROM boolpart WHERE a = false; +DROP TABLE boolpart; diff --git a/src/test/regress/sql/yb.orig.buffering_with_sql_errors.sql b/src/test/regress/sql/yb.orig.buffering_with_sql_errors.sql new file mode 100644 index 0000000000000..1d361b25861c3 --- /dev/null +++ b/src/test/regress/sql/yb.orig.buffering_with_sql_errors.sql @@ -0,0 +1,539 @@ +-- +-- Tests to ensure that YSQL buffering (i.e., logic in pg_operation_buffer.cc and +-- related files) doesn't break any semantics of error handling in functions +-- and procedures. +-- +-- ***************************************************************************** +-- * Exception handling in PLPGSQL +-- ***************************************************************************** +-- +-- PLPGSQL exception blocks allow a user to execute a block of statements such +-- that if an error occurs, the changes to the database are undone/ reverted and +-- user-specified error-handling is invoked. +-- +-- The changes to the database are undone as follows - an internal savepoint is +-- registered before any code in the exception block is executed. If any error +-- occurs, it is caught and the savepoint is rolled back and released. This +-- helps revert any modifications to the database. +-- +-- However, there are some statements which don't modify the database, but have +-- other side-effects. These are not undone even if an exception occurs. A +-- simple example of this is the "return next;" statement in plpgsql. Once data +-- is sent to the user it can't be undone. +-- ***************************************************************************** +-- +-- ***************************************************************************** +-- * YSQL Buffering +-- ***************************************************************************** +-- YSQL buffers operations to tserver (writes specifically) unless it hits some +-- condition that forces it to flush the buffer and wait for the response. Some +-- conditions that force waiting for a buffer response are - completion of a txn, +-- completion of an exception handling block in plpgsql, a read operation, or a +-- write to a key which already has a buffered write. +-- +-- With buffering, execution can move on to later statements unless a flush and +-- response wait is required based on the conditions above. For example - with +-- autocommit mode off, writes for a statement (like INSERT/UPDATE) are not +-- flushed until required. Instead, they are buffered. This is okay because, +-- even before flushing, we would know the number of inserts/updates to be done +-- and return that number to the user client (as "INSERT x"). If an error occurs +-- in the rpc, it will anyway be caught in some later flush, but before the txn +-- commits. +-- +-- Allowing execution to move on to later statements without waiting for the +-- actual work to be done on the tablet servers helps improve performance by +-- buffering and reduce latency. +-- ***************************************************************************** +-- +-- +-- As seen in gh issue #12184, incorrect behaviour is observed with YSQL +-- buffering when an exception that occurs due to some statement's rpcs is seen +-- after a later statement which has non-reversible side-effect(s) has also been +-- executed. Buffered operations should be flushed and waited for before +-- executing any statements with non-reversible side effects in the same +-- transaction. The following tests ensure this for the various cases. +-- +-- 1(a) PL/pgsql: ensure statements with non-reversible side effects (i.e., non +-- transactional work) are not executed if an ealier statement caused an +-- exception. +-- +-- (this test case is one of Bryn's example in Github issue #12184) +-- +-- TODO: The constraint_name is not populated for unique constraint violations in stacked +-- diagnostics. This is being tracked by github issue #13501 +SET yb_speculatively_execute_pl_statements TO true; +drop function if exists f(text) cascade; +drop table if exists t cascade; +create table t(k serial primary key, v varchar(5) not null); +create unique index t_v_unq on t(v); +insert into t(v) values ('dog'), ('cat'), ('frog'); + +create function f(new_v in text) + returns table(z text) + security definer + language plpgsql +as $body$ +declare + sqlstate_ text not null := ''; + constraint_ text not null := ''; + msg text not null := ''; +begin + z := 'Inserting "'||new_v||'"'; return next; + begin + insert into t(v) values (new_v); + z := 'No exception'; return next; + exception + when others then + get stacked diagnostics + sqlstate_ = returned_sqlstate, + constraint_ = constraint_name, + msg = message_text; + if sqlstate_ = '23505' then + z := 'unique_violation'; return next; + z := 'constraint: '||constraint_; return next; + z := 'message: '||msg; return next; + else + z := 'others'; return next; + z := 'sqlstate: '||sqlstate_; return next; + z := 'message: '||msg; return next; + end if; + end; +end; +$body$; + +select z from f('ant'); +select z from f('dog'); +select z from f('elephant'); + +-- 1(b) PL/pgsql: another example from Bryn in Github issue #12184. This one has +-- a for loop within the exception block. + +drop table if exists t cascade; +create table t(k serial primary key, v varchar(3) not null unique); + +drop function if exists f(boolean); +create function f(bug in boolean) + returns table(z text) + language plpgsql +as $body$ +declare + n int not null := 0; +begin + z := ''; return next; + begin + for i in 1..3 loop + n := n + 1; + z := n::text; + return next; + insert into t(v) values(z); + end loop; + exception + when others then null; + end; + + z := ''; return next; + begin + for i in 1..3 loop + n := n + 1; + z := case + when bug and (i = 2) then '1' + when (not bug) and (i = 2) then 'abcd' + else n::text + end; + return next; + insert into t(v) values(z); + end loop; + exception + when string_data_right_truncation then + z := 'string_data_right_truncation handled'; + return next; + when unique_violation then + z := 'unique_violation handled'; + return next; + end; + + z := ''; return next; + begin + for i in 1..3 loop + n := n + 1; + z := n::text; + return next; + insert into t(v) values(z); + end loop; + exception + when others then null; + end; +end; +$body$; + +select z from f(bug=>false); +select v from t order by k; +truncate t; +select z from f(bug=>true); +select v from t order by k; + +-- 1(c) PL/pgsql: the statement that does non-reversible side effects (i.e., +-- non-transactional work) is in a nested function. + +drop table if exists t cascade; +create table t(k serial primary key, v varchar(500) not null unique); +insert into t(v) values ('dog'); +create or replace function f_inner_that_handles_exception(new_v in text) + returns table(z text) + language plpgsql +as $body$ +declare + sqlstate_ text not null := ''; + constraint_ text not null := ''; + msg text not null := ''; +begin + begin + z := 'return next was executed after insert, this was not expected'; + insert into t(v) values (new_v); + return next; + exception + when unique_violation then + get stacked diagnostics + sqlstate_ = returned_sqlstate, + constraint_ = constraint_name, + msg = message_text; + z := 'unique_violation in f_inner_that_handles_exception constraint: ' || constraint_ || ' sqlstate: ' || sqlstate_ || ' message: '|| msg; + return next; + when others then + raise; + end; +end; +$body$; + +create or replace function f_inner_that_passes_exception(new_v in text) + returns table(z text) + language plpgsql +as $body$ +begin + insert into t(v) values (new_v); +end; +$body$; + +create or replace function f_outer() + returns table(z text) + language plpgsql +as $body$ +declare + sqlstate_ text not null := ''; + constraint_ text not null := ''; + msg text not null := ''; +begin + begin -- (1) ensure that the statement after "insert" in the inner function is not executed + insert into t(v) select f_inner_that_handles_exception('dog'); + z := 'No exception'; return next; + exception + when others then + raise; + end; + begin -- (2) similar to (1), the inner function handles the exception an returns the same string. But that will should lead to another unique index violation in the below INSERT. + insert into t(v) select f_inner_that_handles_exception('dog'); + z := 'No exception'; return next; + exception + when unique_violation then + get stacked diagnostics + sqlstate_ = returned_sqlstate, + constraint_ = constraint_name, + msg = message_text; + z := 'unique_violation in f_outer constraint: ' || constraint_ || ' sqlstate: ' || sqlstate_ || ' message: '|| msg; + return next; + when others then + raise; + end; + begin -- (3) ensure that the statement after below "insert" is not executed since inner function passes on the exception + insert into t(v) select f_inner_that_passes_exception('dog'); + z := 'No exception'; return next; + exception + when unique_violation then + get stacked diagnostics + sqlstate_ = returned_sqlstate, + constraint_ = constraint_name, + msg = message_text; + z := 'unique_violation in f_outer constraint: ' || constraint_ || ' sqlstate: ' || sqlstate_ || ' message: '|| msg; + return next; + when others then + raise; + end; +end; +$body$; + +select f_outer(); +select * from t; + +-- 2. SQL functions: ensure statements with non-reversible side effects (i.e., +-- non transactional work) are not executed if an earlier statement caused an +-- exception. + +prepare dummy_query as select * from t; + +create or replace function f(new_v in text) + returns table(z text) + language sql +as $body$ + insert into t(v) values ('dog'); + deallocate dummy_query; + select v from t; +$body$; + +select f('dog'); +execute dummy_query; -- this should find the prepared statement and run fine + +-- 3. Miscellaneous test cases from #6429. Just ensures that a function ends +-- silently when expected to, if a raised exception is ignored in the +-- handler. + +drop table if exists t cascade; +create table t(k int primary key, v int not null); +create unique index t_v_unq on t(v); +insert into t(k, v) values (1, 1); + +insert into t(k, v) values (2, 1); + +do $body$ +begin + insert into t(k, v) values (2, 1); +exception + when unique_violation then null; +end; +$body$; + +do $body$ +begin + insert into t(k, v) values (2, 1); + assert false, 'Logic error. "unique_violation" not caught.'; +exception + when unique_violation then null; +end; +$body$; + +-- 4. Miscellaneous test cases from #8326. + +drop table if exists t; +create table t(k varchar(3) primary key); +insert into t(k) values('dog'); + +do $body$ +begin + insert into t(k) values('frog'); +exception when others then + raise info 'exception caught'; +end; +$body$; + +do $body$ +begin + raise unique_violation; +exception when others then + raise info 'exception caught'; +end; +$body$; + +do $body$ +begin + insert into t(k) values('dog'); +exception when others then + raise info 'exception caught'; +end; +$body$; + +-- +-- Test to validate that PL/pgSQL function triggers can be executed without +-- additional flushes. +-- +CREATE TABLE t_test (k INT PRIMARY KEY, v INT); +CREATE TABLE t_audit (k INT); + +CREATE OR REPLACE FUNCTION sample_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + vtext VARCHAR; +BEGIN + -- Perform a READ that does not require a storage lookup. + SELECT current_setting('client_encoding', TRUE) INTO vtext; + + -- Perform a WRITE that also does not require a storage lookup. + INSERT INTO t_audit VALUES (NEW.k); + RETURN NEW; +END; +$$; + +CREATE TRIGGER trigger_bi BEFORE INSERT ON t_test FOR EACH ROW EXECUTE FUNCTION sample_trigger(); + +BEGIN ISOLATION LEVEL READ COMMITTED; +-- The query below must be executed in a single flush. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (11, 11), (12, 12); +COMMIT; + +BEGIN ISOLATION LEVEL REPEATABLE READ; +-- The query below must be executed in a single flush. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (1, 1), (2, 2); +COMMIT; + +BEGIN ISOLATION LEVEL SERIALIZABLE; +-- The query below must be executed in a single flush. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (3, 3), (4, 4); +COMMIT; + +DROP TRIGGER trigger_bi ON t_test; +CREATE TRIGGER trigger_ai AFTER INSERT ON t_test FOR EACH ROW EXECUTE FUNCTION sample_trigger(); + +BEGIN ISOLATION LEVEL READ COMMITTED; +-- The query below must be executed in a single flush. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (13, 13), (14, 14); +COMMIT; + +BEGIN ISOLATION LEVEL REPEATABLE READ; +-- The query below must be executed in a single flush. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (5, 5), (6, 6); +COMMIT; + +BEGIN ISOLATION LEVEL SERIALIZABLE; +-- The query below must be executed in a single flush. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (7, 7), (8, 8); +COMMIT; + +SELECT * FROM t_test ORDER BY k; + +CREATE TEMP TABLE t_temp (k INT, v INT); +CREATE TABLE t_other (k INT, v INT); +TRUNCATE t_test; +TRUNCATE t_audit; + +-- +-- Test to validate flushing logic in PL/pgSQL functions +-- + +-- Multiple writes (to the same and different tables) should be batched. +CREATE OR REPLACE FUNCTION sample_trigger() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + INSERT INTO t_audit VALUES (NEW.k); + INSERT INTO t_temp VALUES (NEW.k, NEW.v); + INSERT INTO t_audit VALUES (NEW.k + 100), (NEW.k + 101); + INSERT INTO t_other VALUES (NEW.k, NEW.v); + RETURN NEW; +END; $$; + +-- Write to the table once to get any catalog lookups out of the way. +INSERT INTO t_test VALUES (100, 100); + +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (21, 21), (22, 22); + +-- SELECTs should not interfere with the buffering logic as long as they do not +-- require a storage lookup. +CREATE OR REPLACE FUNCTION sample_trigger() RETURNS trigger LANGUAGE plpgsql AS $$ +DECLARE + vtext VARCHAR; + vint INT; +BEGIN + INSERT INTO t_audit VALUES (NEW.k); + SELECT current_setting('client_encoding', TRUE) INTO vtext; + INSERT INTO t_audit VALUES (NEW.k + 100), (NEW.k + 101); + SELECT SUM(k) + SUM(v) FROM t_temp WHERE k > 10 INTO vint; + INSERT INTO t_other VALUES (NEW.k, NEW.v); + RETURN NEW; +END; $$; + +INSERT INTO t_test VALUES (101, 101); +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (23, 23), (24, 24); + +-- Conditional statements should automatically trigger a flush irrespective of +-- the condition. +CREATE OR REPLACE FUNCTION sample_trigger() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + INSERT INTO t_audit VALUES (NEW.k); + IF TRUE THEN + INSERT INTO t_temp VALUES (NEW.k, NEW.v); + ELSE + INSERT INTO t_temp VALUES (NEW.k + 100, NEW.v + 100); + END IF; + INSERT INTO t_other VALUES (NEW.k, NEW.v); + RETURN NEW; +END; $$; + +INSERT INTO t_test VALUES (102, 102); +-- The query below should incur flushes with the following contents: +-- 1. t_test: (25, 25), (26, 26) +-- t_audit: (25) +-- 2. t_temp: (25, 25) +-- t_other: (25, 25) +-- t_audit: (26) +-- 3. t_temp: (26, 26) +-- t_other: (26, 26) +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (25, 25), (26, 26); + +CREATE OR REPLACE FUNCTION sample_trigger() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + INSERT INTO t_audit VALUES (NEW.k); + FOR i IN 1..2 LOOP + INSERT INTO t_temp VALUES (NEW.k, NEW.v); + END LOOP; + INSERT INTO t_other VALUES (NEW.k, NEW.v); + RETURN NEW; +END; $$; + +INSERT INTO t_test VALUES (103, 103); +-- The query below should incur flushes with the following contents: +-- 1. t_test: (27, 27), (28, 28) +-- t_audit: (27) +-- 2. t_temp: (27, 27) +-- t_temp: (27, 27) +-- t_other: (27, 27) +-- t_audit: (28) +-- 3. t_temp: (28, 28) +-- t_temp: (28, 28) +-- t_other: (28, 28) +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (27, 27), (28, 28); + +-- Similarly statements executed in an exception block should also +-- automatically trigger a flush. +CREATE OR REPLACE FUNCTION sample_trigger() RETURNS trigger LANGUAGE plpgsql AS $$ +DECLARE + vint INT; +BEGIN + BEGIN + INSERT INTO t_audit VALUES (NEW.k); + SELECT 1 / 0 INTO vint; -- This will raise an exception. + EXCEPTION + WHEN division_by_zero THEN + INSERT INTO t_audit VALUES (NEW.k + 100); + INSERT INTO t_other VALUES (NEW.k, NEW.v); + INSERT INTO t_temp VALUES (NEW.k + 100, NEW.v + 100); + END; + RETURN NEW; +END; $$; + +INSERT INTO t_test VALUES (104, 104); +-- The query below should incur flushes with the following contents: +-- 1. t_test: (29, 29), (30, 30) +-- 2. t_audit: (29) +-- 3. t_audit: (129) +-- t_other: (29, 29) +-- t_temp: (129, 129) +-- 4. t_audit: (30) +-- 5. t_audit: (130) +-- t_other: (30, 30) +-- t_temp: (130, 130) + +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (29, 29), (30, 30); + +SELECT * FROM t_test ORDER BY k; +SELECT * FROM t_other ORDER BY k; +SELECT * FROM t_temp ORDER BY k; +SELECT * FROM t_audit ORDER BY k; + +RESET yb_speculatively_execute_pl_statements; + +-- GH-28101: Test that a user of yb_db_admin role can set the speculative execution flags +SET SESSION ROLE yb_db_admin; +SET yb_speculatively_execute_pl_statements TO true; +SET yb_whitelist_extra_statements_for_pl_speculative_execution TO true; + +SET SESSION ROLE yb_extension; +SET yb_speculatively_execute_pl_statements TO true; +SET yb_whitelist_extra_statements_for_pl_speculative_execution TO true; + diff --git a/src/test/regress/sql/yb.orig.bytea.sql b/src/test/regress/sql/yb.orig.bytea.sql new file mode 100644 index 0000000000000..b3b05784bcef3 --- /dev/null +++ b/src/test/regress/sql/yb.orig.bytea.sql @@ -0,0 +1,51 @@ +SET bytea_output TO hex; +SELECT E'\\xDeAdBeEf'::bytea; +SELECT E'\\x De Ad Be Ef '::bytea; +SELECT E'\\xDeAdBeE'::bytea; +SELECT E'\\xDeAdBeEx'::bytea; +SELECT E'\\xDe00BeEf'::bytea; +SELECT E'DeAdBeEf'::bytea; +SELECT E'De\\000dBeEf'::bytea; +SELECT E'De\123dBeEf'::bytea; +SELECT E'De\\123dBeEf'::bytea; +SELECT E'De\\678dBeEf'::bytea; + +SET bytea_output TO escape; +SELECT E'\\xDeAdBeEf'::bytea; +SELECT E'\\x De Ad Be Ef '::bytea; +SELECT E'\\xDe00BeEf'::bytea; +SELECT E'DeAdBeEf'::bytea; +SELECT E'De\\000dBeEf'::bytea; +SELECT E'De\\123dBeEf'::bytea; + +SET bytea_output TO hex; + +-- bytea in value + +CREATE TABLE binary_valued (k INT PRIMARY KEY, b bytea); + +INSERT INTO binary_valued (k, b) VALUES (0, decode('', 'hex')); +INSERT INTO binary_valued (k, b) VALUES (1, decode('00', 'hex')); +INSERT INTO binary_valued (k, b) VALUES (2, decode('001122', 'hex')); +INSERT INTO binary_valued (k, b) VALUES (3, decode('11220033', 'hex')); +INSERT INTO binary_valued (k, b) VALUES (4, decode('deadbeef00deadbeef', 'hex')); + +SELECT * FROM binary_valued ORDER BY k; + +DROP TABLE binary_valued; + +-- bytea in key + +CREATE TABLE binary_keyed(k bytea PRIMARY KEY, v INT); + +INSERT INTO binary_keyed (k, v) VALUES (decode('', 'hex'), 0); +INSERT INTO binary_keyed (k, v) VALUES (decode('00', 'hex'), 1); +INSERT INTO binary_keyed (k, v) VALUES (decode('001122', 'hex'), 2); +INSERT INTO binary_keyed (k, v) VALUES (decode('11220033', 'hex'), 3); +INSERT INTO binary_keyed (k, v) VALUES (decode('deadbeef00deadbeef', 'hex'), 4); +INSERT INTO binary_keyed (k, v) VALUES (decode('0000', 'hex'), 5); +INSERT INTO binary_keyed (k, v) VALUES (decode('01', 'hex'), 6); + +SELECT * FROM binary_keyed ORDER BY k; + +DROP TABLE binary_keyed; diff --git a/src/test/regress/sql/yb.orig.cacheinfo.sql b/src/test/regress/sql/yb.orig.cacheinfo.sql new file mode 100644 index 0000000000000..a0237be449799 --- /dev/null +++ b/src/test/regress/sql/yb.orig.cacheinfo.sql @@ -0,0 +1 @@ +SELECT * FROM yb_cacheinfo(); diff --git a/src/test/regress/sql/yb.orig.catalog_cache_hashing.sql b/src/test/regress/sql/yb.orig.catalog_cache_hashing.sql new file mode 100644 index 0000000000000..15b04de27caa8 --- /dev/null +++ b/src/test/regress/sql/yb.orig.catalog_cache_hashing.sql @@ -0,0 +1,211 @@ +/* + * This test checks if the hash function used for the catalog cache has changed. + * It runs the hash function on a set of up to 100 keys for each catalog cache-- + * the expected output has the hash values from when this test was created. + * If the test fails, it indicates that the hash function used for the catalog + * cache has changed and we need to increment YbSharedInvalCatcacheMsgVersion + * to force a full catalog cache refresh. + * + * Because some system tables have a large number of rows, this test limits the + * number of rows checked to 100 for each catalog cache. + * + * To prevent this test from failing when a system object is added, we store a list + * of all the keys we expect to test for each catalog cache in yb_catcache_keys.data. + * The test loads this file and hashes these keys, comparing the output with the expected + * output from when this test was created. This ensures that any changes to the system + * catalog will not affect this test. + */ + +-- Table for the keys we will hash in the test. +CREATE TEMP TABLE catcache_keys ( + catcache_name text, + keys jsonb +); + +-- Load the keys from the data file. +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/data/yb_catcache_keys.data' +COPY catcache_keys FROM :'filename'; + +-- This table will store the results of hashing the keys. +DROP TABLE IF EXISTS catcache_hash_results; +CREATE TEMP TABLE catcache_hash_results ( + catcache_name text, + keys jsonb, + hashval bigint +); + +DO $$ +DECLARE + info record; + attrec record; + attnums int[] := ARRAY[0, 0, 0, 0]; + cache_key text[] := ARRAY['NULL','NULL','NULL','NULL']; + fkey text; + casttxt text; + i int; + fkeys record; +BEGIN + -- Outer loop: iterate over the catalog caches. + FOR info IN + SELECT + c.cache_id, + c.name AS catcache_name, + c.reloid, + c.nkeys, + c.key1, + c.key2, + c.key3, + c.key4 + FROM yb_cacheinfo() c + ORDER BY c.name + LOOP + attnums[1] := info.key1; + attnums[2] := info.key2; + attnums[3] := info.key3; + attnums[4] := info.key4; + + -- Middle loop: Iterate over the keys for the current catalog cache. + FOR fkeys IN + SELECT keys + FROM catcache_keys + WHERE catcache_name = info.catcache_name + LOOP + FOR i IN 1..4 LOOP + /* + * Construct the the expressions for the keys we will hash, + * as an array of text. Each element is the name of a column + * in the table. The array always has 4 elements. If there are + * fewer than 4 keys, the remaining elements are 'NULL'. + * + * Example key: + * {(('pg_ddl_command_send')::name),(('32')::oidvector),(('11')::oid),"NULL"} + */ + IF attnums[i] = 0 OR i > info.nkeys THEN + cache_key[i] := 'NULL'; + ELSE + SELECT attname, atttypid + INTO attrec + FROM pg_attribute + WHERE attrelid = info.reloid + AND attnum = attnums[i] + AND NOT attisdropped; + + fkey := 'f' || i; -- JSON fields are f1, f2, f3, f4, etc. + + IF attrec.atttypid = 'pg_catalog.oidvector'::regtype THEN + -- oidvector expects a space-separated list of numeric OIDs. + -- The JSON is typically an array like ["2281","2282"]. + -- We'll extract that array and join with a space. + BEGIN + SELECT COALESCE(string_agg(elem, ' '), '') + INTO casttxt + FROM jsonb_array_elements_text(fkeys.keys->fkey) elem; + EXCEPTION + -- If the JSON is not an array (e.g. single OID), just cast directly. + WHEN others THEN + casttxt := fkeys.keys->>fkey; + END; + + casttxt := '((' || quote_literal(casttxt) || ')::oidvector)'; + + -- For some objects, PostgreSQL automatically converts the OID to the object name as a string. + -- This is an issue because multiple functions can have the same name, so we need to use the + -- raw OID as an integer to disambiguate. + ELSIF attrec.atttypid IN ( + 'pg_catalog.regclass'::regtype, + 'pg_catalog.regproc'::regtype, + 'pg_catalog.regtype'::regtype, + 'pg_catalog.regoper'::regtype, + 'pg_catalog.regprocedure'::regtype, + 'pg_catalog.regrole'::regtype + ) + THEN + casttxt := '((' || quote_literal(fkeys.keys->>fkey) || ')::oid)'; + ELSE + -- For other types, we just cast to the type of the column. + casttxt := '((' + || quote_literal(fkeys.keys->>fkey) + || ')::' + || format_type(attrec.atttypid, NULL) + || ')'; + END IF; + + cache_key[i] := casttxt; + END IF; + END LOOP; + + /* + * Construct the query to hash the keys and insert the results into the catcache_hash_results table. + * This query takes one key from the catcache_keys table, casts the key columns to the correct types, + * hashes the key, and inserts the result into the catcache_hash_results table. + * + * Example generated query: + * INSERT INTO catcache_hash_results (catcache_name, keys, hashval) + * VALUES ( + * 'PROCNAMEARGSNSP', -- info.catcache_name + * '{"f1": "pg_ddl_command_send", "f2": ["32"], "f3": "11", "f4": null}'::jsonb, -- fkeys.keys + * catalog_cache_compute_hash_tuple( + * ROW( + * 44, -- info.cache_id + * ('pg_ddl_command_send')::name, + * ('32')::oidvector, + * ('11')::oid, + * NULL + * ) + * ) + * ); + */ + + EXECUTE format($q$ + INSERT INTO catcache_hash_results (catcache_name, keys, hashval) + VALUES ( + %L, -- info.catcache_name + %L::jsonb, -- fkeys.keys + catalog_cache_compute_hash_tuple( + ROW( + %s, -- info.cache_id + %s, %s, %s, %s -- cache_key[1], cache_key[2], cache_key[3], cache_key[4] + ) + ) + ) + $q$, + info.catcache_name, + fkeys.keys::text, + info.cache_id, + cache_key[1], + cache_key[2], + cache_key[3], + cache_key[4]); + END LOOP; + END LOOP; +END +$$; + +-- Verify that we're testing all of the caches and +-- the right number of keys for each catalog cache. +SELECT COUNT(DISTINCT catcache_name) FROM catcache_keys; + +SELECT catcache_name, COUNT(*) +FROM catcache_keys +GROUP BY catcache_name +ORDER BY catcache_name; + +-- Use extended output to make the diff easier to read. +\x + +-- Finally, select all the results that we computed. +SELECT + catcache_keys.catcache_name, + catcache_keys.keys, + results.hashval +FROM catcache_keys +JOIN catcache_hash_results results + ON catcache_keys.catcache_name = results.catcache_name + AND catcache_keys.keys = results.keys +-- Temp schemas change their names on every run. +WHERE NOT ( + catcache_keys.keys->>'f1' LIKE 'pg_temp_%' + OR catcache_keys.keys->>'f1' LIKE 'pg_toast_temp_%' +) +ORDER BY catcache_keys.catcache_name, catcache_keys.keys; diff --git a/src/test/regress/sql/yb.orig.catalog_version.sql b/src/test/regress/sql/yb.orig.catalog_version.sql new file mode 100644 index 0000000000000..bae911247fcea --- /dev/null +++ b/src/test/regress/sql/yb.orig.catalog_version.sql @@ -0,0 +1,508 @@ +-- +-- This file is to check the effect of DDL statements on the catalog version +-- stored in pg_yb_catalog_table. In particular, not all DDL query causes +-- changes in catalog. For example, a grant statement may not really change +-- anything if the same privileges are already granted. In such cases it is +-- benefitial to avoid catalog version increment to prevent catalog cache +-- update which may overload yb-master if there are a large number of active +-- Postgres connections. +-- +-- +\set template1_db_oid 1 +\set db_oid 'CASE WHEN (select count(*) from pg_yb_catalog_version) = 1 THEN :template1_db_oid ELSE (SELECT oid FROM pg_database WHERE datname = \'yugabyte\') END' +-- Set the catalog versions to known, fixed values before running the test. +-- This is done so that the test output isn't affected by queries that run +-- run during test setup. +SET yb_non_ddl_txn_for_sys_tables_allowed TO on; +UPDATE pg_yb_catalog_version SET current_version = 10000, + last_breaking_version = 10000 WHERE db_oid = :db_oid; +RESET yb_non_ddl_txn_for_sys_tables_allowed; +-- Build a SQL query that works with per-database catalog version mode enabled +-- or disabled. +\set display_catalog_version 'SELECT current_version, last_breaking_version FROM pg_yb_catalog_version WHERE db_oid = :db_oid' +-- Display the initial catalog version. +:display_catalog_version; + +-- The next CREATE ROLE will not increment current_version. +CREATE ROLE cv_test_role; +:display_catalog_version; + +-- The next CREATE ROLE fails and should not cause any catalog version change. +CREATE ROLE cv_test_role; +:display_catalog_version; + +-- The next CREATE ROLE will increment current_version. +CREATE ROLE cv_test_role2 IN ROLE cv_test_role; +:display_catalog_version; + +-- The next CREATE ROLE will increment current_version. +CREATE ROLE cv_test_role3 ADMIN cv_test_role; +:display_catalog_version; + +-- The next CREATE ROLE will increment current_version. +CREATE ROLE cv_test_role4 ROLE cv_test_role2, cv_test_role3; +:display_catalog_version; + +-- The next CREATE DATABASE will not increment current_version. +CREATE DATABASE cv_test_database; +:display_catalog_version; + +-- The next GRANT CONNECT will increment current_version. +GRANT CONNECT ON DATABASE cv_test_database TO cv_test_role; +:display_catalog_version; + +-- The next GRANT CONNECT should not cause any catalog version change. +GRANT CONNECT ON DATABASE cv_test_database TO cv_test_role; +:display_catalog_version; + +-- The next REVOKE CONNECT is a "breaking" catalog change. It will increment +-- both current_version and last_breaking_version. +REVOKE CONNECT ON DATABASE cv_test_database from cv_test_role; +:display_catalog_version; + +-- The next REVOKE CONNECT should not cause any catalog version change. +REVOKE CONNECT ON DATABASE cv_test_database from cv_test_role; +:display_catalog_version; + +-- The next CREATE TABLE should increment current_version. +CREATE TABLE cv_test_table(id int); +:display_catalog_version; + +-- The next GRANT SELECT will increment current_version. +GRANT SELECT ON cv_test_table TO cv_test_role; +:display_catalog_version; + +-- The next GRANT SELECT should not cause any catalog version change. +GRANT SELECT ON cv_test_table TO cv_test_role; +:display_catalog_version; + +-- The next REVOKE SELECT is a "breaking" catalog change. It will increment +-- both current_version and last_breaking_version. +REVOKE SELECT ON cv_test_table FROM cv_test_role; +:display_catalog_version; + +-- The next REVOKE SELECT should not cause any catalog version change. +REVOKE SELECT ON cv_test_table FROM cv_test_role; +:display_catalog_version; + +-- The next CREATE FOREIGN DATA WRAPPER will increment current_version. +CREATE FOREIGN DATA WRAPPER cv_test_fdw_wrapper; +-- The next GRANT USAGE will increment current_version. +GRANT USAGE ON FOREIGN DATA WRAPPER cv_test_fdw_wrapper TO cv_test_role; +:display_catalog_version; + +-- The next GRANT USAGE should not cause any catalog version change. +GRANT USAGE ON FOREIGN DATA WRAPPER cv_test_fdw_wrapper TO cv_test_role; +:display_catalog_version; + +-- The next REVOKE USAGE is a "breaking" catalog change. It will increment +-- both current_version and last_breaking_version. +REVOKE USAGE ON FOREIGN DATA WRAPPER cv_test_fdw_wrapper FROM cv_test_role; +:display_catalog_version; + +-- The next REVOKE USAGE should not cause any catalog version change. +REVOKE USAGE ON FOREIGN DATA WRAPPER cv_test_fdw_wrapper FROM cv_test_role; +:display_catalog_version; + +-- The next CREATE SERVER will increment current_version. +CREATE SERVER cv_test_fdw_server FOREIGN DATA WRAPPER cv_test_fdw_wrapper; +-- The next GRANT USAGE will increment current_version. +GRANT USAGE ON FOREIGN SERVER cv_test_fdw_server TO cv_test_role; +:display_catalog_version; + +-- The next GRANT USAGE should not cause any catalog version change. +GRANT USAGE ON FOREIGN SERVER cv_test_fdw_server TO cv_test_role; +:display_catalog_version; + +-- The next REVOKE USAGE is a "breaking" catalog change. It will increment +-- both current_version and last_breaking_version. +REVOKE USAGE ON FOREIGN SERVER cv_test_fdw_server FROM cv_test_role; +:display_catalog_version; + +-- The next REVOKE USAGE should not cause any catalog version change. +REVOKE USAGE ON FOREIGN SERVER cv_test_fdw_server FROM cv_test_role; +:display_catalog_version; + +-- The next CREATE FUNCTION will increment current_version. +CREATE FUNCTION cv_test_function() RETURNS int AS $$ SELECT 1 $$ LANGUAGE sql; + +-- The next GRANT EXECUTE will increment current_version. +GRANT EXECUTE ON FUNCTION cv_test_function TO cv_test_role; +:display_catalog_version; + +-- The next GRANT EXECUTE should not cause any catalog version change. +GRANT EXECUTE ON FUNCTION cv_test_function TO cv_test_role; +:display_catalog_version; + +-- The next REVOKE EXECUTE is a "breaking" catalog change. It will increment +-- both current_version and last_breaking_version. +REVOKE EXECUTE ON FUNCTION cv_test_function from cv_test_role; +:display_catalog_version; + +-- The next REVOKE EXECUTE should not cause any catalog version change. +REVOKE EXECUTE ON FUNCTION cv_test_function from cv_test_role; +:display_catalog_version; + +-- The next GRANT USAGE will increment current_version. +GRANT USAGE ON LANGUAGE sql TO cv_test_role; +:display_catalog_version; + +-- The next GRANT USAGE should not cause any catalog version change. +GRANT USAGE ON LANGUAGE sql TO cv_test_role; +:display_catalog_version; + +-- The next REVOKE USAGE is a "breaking" catalog change. It will increment +-- both current_version and last_breaking_version. +REVOKE USAGE ON LANGUAGE sql from cv_test_role; +:display_catalog_version; + +-- The next REVOKE USAGE should not cause any catalog version change. +REVOKE USAGE ON LANGUAGE sql from cv_test_role; +:display_catalog_version; + +SET yb_non_ddl_txn_for_sys_tables_allowed=1; +SELECT lo_create(1001); +SET yb_non_ddl_txn_for_sys_tables_allowed=0; + +-- The next GRANT SELECT will increment current_version. +GRANT SELECT ON LARGE OBJECT 1001 TO cv_test_role; +:display_catalog_version; + +-- The next GRANT SELECT should not cause any catalog version change. +GRANT SELECT ON LARGE OBJECT 1001 TO cv_test_role; +:display_catalog_version; + +-- The next REVOKE SELECT is a "breaking" catalog change. It will increment +-- both current_version and last_breaking_version. +REVOKE SELECT ON LARGE OBJECT 1001 from cv_test_role; +:display_catalog_version; + +-- The next REVOKE SELECT should not cause any catalog version change. +REVOKE SELECT ON LARGE OBJECT 1001 from cv_test_role; +:display_catalog_version; + +-- The next GRANT USAGE will increment current_version. +GRANT USAGE ON SCHEMA public TO cv_test_role; +:display_catalog_version; + +-- The next GRANT USAGE should not cause any catalog version change. +GRANT USAGE ON SCHEMA public TO cv_test_role; +:display_catalog_version; + +-- The next REVOKE USAGE is a "breaking" catalog change. It will increment +-- both current_version and last_breaking_version. +REVOKE USAGE ON SCHEMA public from cv_test_role; +:display_catalog_version; + +-- The next REVOKE USAGE should not cause any catalog version change. +REVOKE USAGE ON SCHEMA public from cv_test_role; +:display_catalog_version; + +-- The next CREATE TABLEGROUP will increment current_version. +CREATE TABLEGROUP cv_test_tablegroup; + +-- The next GRANT CREATE will increment current_version. +GRANT CREATE ON TABLEGROUP cv_test_tablegroup TO cv_test_role; +:display_catalog_version; + +-- The next GRANT CREATE should not cause any catalog version change. +GRANT CREATE ON TABLEGROUP cv_test_tablegroup TO cv_test_role; +:display_catalog_version; + +-- The next REVOKE CREATE is a "breaking" catalog change. It will increment +-- both current_version and last_breaking_version. +REVOKE CREATE ON TABLEGROUP cv_test_tablegroup from cv_test_role; +:display_catalog_version; + +-- The next REVOKE CREATE should not cause any catalog version change. +REVOKE CREATE ON TABLEGROUP cv_test_tablegroup from cv_test_role; +:display_catalog_version; + +-- The next CREATE TABLESPACE will increment current_version. +CREATE TABLESPACE cv_test_tablespace WITH (replica_placement= + '{"num_replicas":1,"placement_blocks":[{"cloud":"c1","region":"r1","zone":"z1","min_num_replicas":1}]}'); + +-- The next GRANT CREATE will increment current_version. +GRANT CREATE ON TABLESPACE cv_test_tablespace TO cv_test_role; +:display_catalog_version; + +-- The next GRANT CREATE should not cause any catalog version change. +GRANT CREATE ON TABLESPACE cv_test_tablespace TO cv_test_role; +:display_catalog_version; + +-- The next REVOKE CREATE is a "breaking" catalog change. It will increment +-- both current_version and last_breaking_version. +REVOKE CREATE ON TABLESPACE cv_test_tablespace from cv_test_role; +:display_catalog_version; + +-- The next REVOKE CREATE should not cause any catalog version change. +REVOKE CREATE ON TABLESPACE cv_test_tablespace from cv_test_role; +:display_catalog_version; + +-- The next CREATE TYPE will increment current_version. +CREATE TYPE cv_test_type AS (a int, b text); + +-- The next GRANT USAGE will increment current_version. +GRANT USAGE ON TYPE cv_test_type TO cv_test_role; +:display_catalog_version; + +-- The next GRANT USAGE should not cause any catalog version change. +GRANT USAGE ON TYPE cv_test_type TO cv_test_role; +:display_catalog_version; + +-- The next REVOKE USAGE is a "breaking" catalog change. It will increment +-- both current_version and last_breaking_version. +REVOKE USAGE ON TYPE cv_test_type from cv_test_role; +:display_catalog_version; + +-- The next REVOKE USAGE should not cause any catalog version change. +REVOKE USAGE ON TYPE cv_test_type from cv_test_role; +:display_catalog_version; + +-- CREATE TABLE should increment current_version. +CREATE TABLE t_check (col INT CHECK (col > 0)); +:display_catalog_version; +CREATE TABLE t_not_null (col INT NOT NULL); +:display_catalog_version; +CREATE TABLE t_primary_key (col INT PRIMARY KEY); +:display_catalog_version; +CREATE TABLE t_sequence (col SERIAL, value TEXT); +:display_catalog_version; +CREATE TABLE t_unique (col INT UNIQUE); +:display_catalog_version; +CREATE TABLE t_identity (col INT GENERATED ALWAYS AS IDENTITY); +:display_catalog_version; +CREATE TABLE t_primary_key_sequence_identity (c1 INT PRIMARY KEY, c2 SERIAL, c3 INT GENERATED ALWAYS AS IDENTITY); +:display_catalog_version; + +-- The CREATE TABLE with FOREIGN KEY will increment current_version. +CREATE TABLE t1 (col INT UNIQUE); +CREATE TABLE t2 (col INT REFERENCES t1(col)); +:display_catalog_version; + +-- The ALTER TABLE will increment current_version. +ALTER TABLE t1 ADD COLUMN val INT; +:display_catalog_version; + +-- The CREATE PROCEDURE will not increment current_version. +CREATE PROCEDURE test() AS $$ BEGIN INSERT INTO t1 VALUES(1); END; $$ LANGUAGE 'plpgsql'; +:display_catalog_version; +-- The CALL to PROCEDURE will not increment current_version. +CALL test(); +:display_catalog_version; + +-- The CREATE FUNCTION will increment current_version. +CREATE TABLE evt_trig_table (id serial PRIMARY KEY, evt_trig_name text); +CREATE OR REPLACE FUNCTION evt_trig_fn() RETURNS event_trigger AS $$ BEGIN INSERT INTO evt_trig_table (evt_trig_name) VALUES (TG_EVENT); END; $$ LANGUAGE plpgsql; +:display_catalog_version; +-- The CREATE EVENT TRIGGER will increment current_version. +CREATE EVENT TRIGGER evt_ddl_start ON ddl_command_start EXECUTE PROCEDURE evt_trig_fn(); +:display_catalog_version; +-- The DDLs proceeding the trigger will increment current_version based on the command's individual behaviour. +-- The ALTER TABLE will increment current_version. +ALTER TABLE evt_trig_table ADD COLUMN val INT; +:display_catalog_version; +-- The CREATE TABLE will not increment current_version. +CREATE TABLE post_trigger_table (id INT); +:display_catalog_version; + +-- The execution on atomic SPI context function will not increment current_version. +CREATE FUNCTION atomic_spi(INT) RETURNS INT AS $$ +DECLARE TOTAL INT; +BEGIN + CREATE TEMP TABLE temp_table(a INT); + INSERT INTO temp_table VALUES($1); + INSERT INTO temp_table VALUES(1); + SELECT SUM(a) INTO TOTAL FROM temp_table; + DROP TABLE temp_table; + RETURN TOTAL; +END +$$ LANGUAGE PLPGSQL; + +SELECT atomic_spi(1); +:display_catalog_version; +SELECT atomic_spi(2); +:display_catalog_version; + +-- The execution on non-atomic SPI context will not increment current_version. +CREATE TABLE non_atomic_spi(a INT, b INT); +CREATE PROCEDURE proc(x INT, y INT) LANGUAGE PLPGSQL AS $$ +BEGIN FOR i IN 0..x LOOP INSERT INTO non_atomic_spi(a, b) VALUES (i, y); +IF i % 2 = 0 THEN COMMIT; +ELSE ROLLBACK; +END IF; +END LOOP; +END $$; +CREATE PROCEDURE p1() LANGUAGE PLPGSQL AS $$ BEGIN CALL p2(); END $$; +CREATE PROCEDURE p2() LANGUAGE PLPGSQL AS $$ BEGIN CALL proc(2, 2); END $$; + +CALL p1(); +:display_catalog_version; + +-- Verify that variants of CREATE TABLE AS SELECT do not increment catalog version. +CREATE TABLE t (id INT); +SELECT id INTO TEMPORARY TABLE temp FROM t; +:display_catalog_version; +CREATE TEMPORARY TABLE temp2 AS SELECT id FROM t; +:display_catalog_version; +CREATE TABLE t_1 AS SELECT id FROM t; +:display_catalog_version; + +-- Verify that catalog version gets incremented if CREATE TABLE AS invokes other +-- DDL operations. +CREATE TABLE t_2 (id INT); +GRANT ALL ON t_2 TO public; +CREATE OR REPLACE FUNCTION f1() RETURNS float8 AS $$ +BEGIN + ALTER TABLE t_2 ADD COLUMN v2 int; + REVOKE SELECT ON t_2 FROM public; + RETURN 1; +END$$ LANGUAGE plpgsql; +:display_catalog_version; +CREATE TABLE t_3 AS SELECT c FROM (SELECT 1 AS c, f1()) AS s; +:display_catalog_version; + +-- The next GRANT SELECT will increment current_version. +GRANT SELECT (rolname, rolsuper) ON pg_authid TO CURRENT_USER; +:display_catalog_version; + +-- The next GRANT SELECT used to increment current_version due to evt_ddl_start +-- which causes an insert into evt_trig_table table. However, evt_trig_table is +-- not a catalog table. It is a user table and writing to a user table does not +-- change catalog state so should not need to increment catalog version. As of +-- 2024-10-15, we simply detect whether there is any write under DDL mode and +-- when we make the write to evt_trig_table, the GRANT SELECT DDL execution is +-- in progress. An improvement is to detect we write to catalog table under DDL +-- mode. +-- With invalidation messages, we detect that the GRANT SELECT DDL statement +-- does not generate any invalidation messages at all, which implies no catalog +-- cache entries need to be invalidated. Therefore this GRANT SELECT no longer +-- increments current_version. +GRANT SELECT (rolname, rolsuper) ON pg_authid TO CURRENT_USER; +:display_catalog_version; + +DROP EVENT TRIGGER evt_ddl_start; +:display_catalog_version; + +-- The next GRANT SELECT should not cause any catalog version change. +GRANT SELECT (rolname, rolsuper) ON pg_authid TO CURRENT_USER; +:display_catalog_version; + +-- Verify REFRESH MATERIALIZED VIEW is not a breaking change. +CREATE TABLE base (t int); +CREATE MATERIALIZED VIEW mv AS SELECT * FROM base; +CREATE UNIQUE INDEX ON mv(t); +:display_catalog_version; +-- nonconcurrent refreshes should bump catalog version. +REFRESH MATERIALIZED VIEW mv; +:display_catalog_version; +-- concurrent refreshes should increment current_version. +INSERT INTO base VALUES (1); -- TODO(#26677): remove this workaround. +REFRESH MATERIALIZED VIEW CONCURRENTLY mv; +:display_catalog_version; + +-- Verify ANALYZE increments catalog version once for every involved table +-- and is not a breaking change. +CREATE TABLE analyze_table (t int); +CREATE TABLE analyze_table2 (t int); +INSERT INTO analyze_table select generate_series(1,100); +INSERT INTO analyze_table2 select generate_series(1,100); +ANALYZE analyze_table, analyze_table2; +:display_catalog_version; +SELECT relname, reltuples FROM pg_class WHERE relname = 'analyze_table' OR relname = 'analyze_table2' ORDER BY relname; +SELECT min(t), max(t) FROM analyze_table; +SELECT min(t), max(t) FROM analyze_table2; + +-- Verify no-op ALTER ROLE +CREATE ROLE test_role; +:display_catalog_version; +-- The next ALTER ROLE should increment current_version. +ALTER ROLE test_role CONNECTION LIMIT 1; +:display_catalog_version; +-- The next ALTER ROLE should increment current_version. +ALTER ROLE test_role CONNECTION LIMIT -1; +:display_catalog_version; +-- The next ALTER ROLE should not increment current_version. +ALTER ROLE test_role CONNECTION LIMIT -1; +:display_catalog_version; +-- The next ALTER ROLE should increment current_version. +ALTER ROLE test_role PASSWORD '123'; +:display_catalog_version; +-- The next ALTER ROLE should increment current_version. +ALTER ROLE test_role PASSWORD NULL; +:display_catalog_version; +-- The next ALTER ROLE should increment current_version. +ALTER ROLE test_role VALID UNTIL 'May 4 12:00:00 2015 +1'; +:display_catalog_version; +-- The next ALTER ROLE should not increment current_version. +ALTER ROLE test_role VALID UNTIL 'May 4 12:00:00 2015 +1'; +:display_catalog_version; +-- The next ALTER ROLE should increment current_version. +ALTER ROLE test_role VALID UNTIL 'infinity'; +:display_catalog_version; +-- The next ALTER ROLE should not increment current_version. +ALTER ROLE test_role VALID UNTIL 'infinity'; +:display_catalog_version; +-- The next ALTER ROLE should increment current_version. +ALTER ROLE test_role CREATEROLE; +:display_catalog_version; +-- The next ALTER ROLE should increment current_version. +ALTER ROLE test_role CREATEDB; +:display_catalog_version; +-- The next ALTER ROLE should not increment current_version. +ALTER ROLE test_role CREATEROLE CREATEDB; +:display_catalog_version; +-- The next ALTER ROLE should not increment current_version. +ALTER ROLE test_role; +:display_catalog_version; + +-- Sanity test yb_increment_db_catalog_version_with_inval_messages +-- Clear pg_yb_invalidation_messages for any left over messages from previous DDLs. +SET yb_non_ddl_txn_for_sys_tables_allowed=1; +DELETE FROM pg_yb_invalidation_messages; +SET yb_non_ddl_txn_for_sys_tables_allowed=0; +\set display_all 'SELECT datname, current_version, last_breaking_version FROM pg_yb_catalog_version JOIN pg_database ON db_oid = oid ORDER BY datname; SELECT datname, current_version, messages FROM pg_yb_invalidation_messages JOIN pg_database ON db_oid = oid ORDER BY datname' +SET yb_non_ddl_txn_for_sys_tables_allowed TO on; +SET yb_disable_catalog_version_check TO on; + +-- Messages expire after 10 seconds. +SELECT yb_increment_db_catalog_version_with_inval_messages(:db_oid, false, '', 10); +:display_all; +SELECT yb_increment_db_catalog_version_with_inval_messages(:db_oid, false, null, 10); +:display_all; +SELECT yb_increment_db_catalog_version_with_inval_messages(:db_oid, true, '', 10); +:display_all; +SELECT yb_increment_db_catalog_version_with_inval_messages(:db_oid, true, null, 10); +:display_all; +-- Wait for the old message row to expire. +SELECT pg_sleep(15); +SELECT yb_increment_db_catalog_version_with_inval_messages(:db_oid, false, '', 10); +:display_all; +SELECT yb_increment_db_catalog_version_with_inval_messages(:db_oid, true, '', 10); +:display_all; + +-- Wait for the old message row to expire. +SELECT pg_sleep(15); + +-- Sanity test yb_increment_all_db_catalog_versions_with_inval_messages +SELECT yb_increment_all_db_catalog_versions_with_inval_messages(:db_oid, false, '', 10); +:display_all; +SELECT yb_increment_all_db_catalog_versions_with_inval_messages(:db_oid, false, null, 10); +:display_all; +SELECT yb_increment_all_db_catalog_versions_with_inval_messages(:db_oid, true, '', 10); +:display_all; +SELECT yb_increment_all_db_catalog_versions_with_inval_messages(:db_oid, true, null, 10); +:display_all; +-- Wait for the old message row to expire. +SELECT pg_sleep(15); +SELECT yb_increment_all_db_catalog_versions_with_inval_messages(:db_oid, false, '', 10); +:display_all; +SELECT yb_increment_all_db_catalog_versions_with_inval_messages(:db_oid, true, '', 10); +:display_all; + +-- Without specifying tables, ANALYZE update statistics for all tables. +-- Verify catalog version bumps once for every table. +ANALYZE; +:display_catalog_version; +-- It is best to add any new test before this block ('ANALYZE;'). This minimizes the +-- changes required to the expected output when introducing a new catalog table. diff --git a/src/test/regress/sql/yb.orig.catalog_version_increment.sql b/src/test/regress/sql/yb.orig.catalog_version_increment.sql new file mode 100644 index 0000000000000..a0dcbfaadb6df --- /dev/null +++ b/src/test/regress/sql/yb.orig.catalog_version_increment.sql @@ -0,0 +1,90 @@ +-- Test yb_test_make_all_ddl_statements_incrementing GUC. +-- All of the following DDL statements should increment current_version. +\set template1_db_oid 1 +\set db_oid 'CASE WHEN (select count(*) from pg_yb_catalog_version) = 1 THEN :template1_db_oid ELSE (SELECT oid FROM pg_database WHERE datname = \'yugabyte\') END' +-- Set the catalog versions to known, fixed values before running the test. +-- This is done so that the test output isn't affected by queries that run +-- run during test setup. +SET yb_non_ddl_txn_for_sys_tables_allowed TO on; +UPDATE pg_yb_catalog_version SET current_version = 10000, + last_breaking_version = 10000 WHERE db_oid = :db_oid; +RESET yb_non_ddl_txn_for_sys_tables_allowed; +-- Build a SQL query that works with per-database catalog version mode enabled +-- or disabled. +\set display_catalog_version 'SELECT current_version, last_breaking_version FROM pg_yb_catalog_version WHERE db_oid = :db_oid' +:display_catalog_version; + +CREATE PROFILE cv_test_profile_guc LIMIT FAILED_LOGIN_ATTEMPTS 2; +:display_catalog_version; + +CREATE TABLE t_trunc_guc(id int); +:display_catalog_version; + +INSERT INTO t_trunc_guc VALUES (1); + +SET yb_enable_alter_table_rewrite = OFF; +TRUNCATE t_trunc_guc; +:display_catalog_version; + +CREATE VIEW v_guc AS SELECT 1 AS a; +:display_catalog_version; + +CREATE ROLE cv_test_role_guc; +:display_catalog_version; + +ALTER ROLE cv_test_role_guc WITH PASSWORD '123'; +:display_catalog_version; + +CREATE ROLE cv_test_role_opts_guc LOGIN CREATEDB CREATEROLE INHERIT CONNECTION LIMIT 5; +:display_catalog_version; + +CREATE TABLE t_ctas_guc AS SELECT 1 AS id; +:display_catalog_version; + +CREATE SEQUENCE s_guc; +:display_catalog_version; + +DISCARD ALL; +:display_catalog_version; + +CREATE TEMP TABLE temp_to_drop_guc(id int); +:display_catalog_version; + +CREATE INDEX idx_guc ON temp_to_drop_guc(id); +:display_catalog_version; + +ALTER TABLE temp_to_drop_guc ADD COLUMN val int; +:display_catalog_version; + +DROP TABLE temp_to_drop_guc; +:display_catalog_version; + +-- Additional DDLs that should increment under yb_test_make_all_ddl_statements_incrementing=true. +CREATE OPERATOR ==# ( + LEFTARG = int, + RIGHTARG = int, + PROCEDURE = int4eq +); +:display_catalog_version; + +CREATE AGGREGATE cv_test_agg(int) ( + SFUNC = int4pl, + STYPE = int, + INITCOND = 0 +); +:display_catalog_version; + +CREATE TYPE cv_type_enum AS ENUM ('a','b'); +:display_catalog_version; + +CREATE TEXT SEARCH CONFIGURATION cv_ts_conf ( COPY = pg_catalog.simple ); +:display_catalog_version; + +CREATE TEXT SEARCH DICTIONARY cv_ts_dict ( TEMPLATE = pg_catalog.simple ); +:display_catalog_version; + +CREATE COLLATION cv_coll (lc_collate = 'C', lc_ctype = 'C'); +:display_catalog_version; + +COMMENT ON TABLE t_ctas_guc IS 'x'; +:display_catalog_version; diff --git a/src/test/regress/sql/yb.orig.char.sql b/src/test/regress/sql/yb.orig.char.sql new file mode 100644 index 0000000000000..c3bab311f6806 --- /dev/null +++ b/src/test/regress/sql/yb.orig.char.sql @@ -0,0 +1,84 @@ +CREATE TABLE yb_char_in_value (k INT PRIMARY KEY, c CHAR); + +INSERT INTO yb_char_in_value VALUES (1, 'a'); +INSERT INTO yb_char_in_value VALUES (2, 'b'); +INSERT INTO yb_char_in_value VALUES (3, 'c'); +INSERT INTO yb_char_in_value VALUES (4, 'd'); +INSERT INTO yb_char_in_value VALUES (5, 'e'); +INSERT INTO yb_char_in_value VALUES (10, NULL); +INSERT INTO yb_char_in_value VALUES (11, chr(1040)); +INSERT INTO yb_char_in_value VALUES (12, chr(1041)); +INSERT INTO yb_char_in_value VALUES (13, chr(1042)); +INSERT INTO yb_char_in_value VALUES (14, chr(1043)); +INSERT INTO yb_char_in_value VALUES (15, chr(1044)); + +SELECT * FROM yb_char_in_value ORDER BY k; +SELECT * FROM yb_char_in_value ORDER BY c; +SELECT * FROM yb_char_in_value WHERE k = 1; +SELECT * FROM yb_char_in_value WHERE c = 'a'; +SELECT * FROM yb_char_in_value WHERE c = chr(1041); + +DROP TABLE yb_char_in_value; + +CREATE TABLE yb_char_in_key (ck CHAR PRIMARY KEY); + +INSERT INTO yb_char_in_key VALUES ('a'); +INSERT INTO yb_char_in_key VALUES ('b'); +INSERT INTO yb_char_in_key VALUES ('c'); +INSERT INTO yb_char_in_key VALUES ('d'); +INSERT INTO yb_char_in_key VALUES ('e'); +INSERT INTO yb_char_in_key VALUES (chr(1040)); +INSERT INTO yb_char_in_key VALUES (chr(1041)); +INSERT INTO yb_char_in_key VALUES (chr(1042)); +INSERT INTO yb_char_in_key VALUES (chr(1043)); +INSERT INTO yb_char_in_key VALUES (chr(1044)); + +SELECT * FROM yb_char_in_key ORDER BY ck; +SELECT * FROM yb_char_in_key WHERE ck = 'a'; +SELECT * FROM yb_char_in_key WHERE ck = chr(1042); + +set yb_enable_expression_pushdown to off; +SELECT * FROM yb_char_in_key ORDER BY ck; +SELECT * FROM yb_char_in_key WHERE ck = 'a'; +SELECT * FROM yb_char_in_key WHERE ck = chr(1042); +set yb_enable_expression_pushdown to on; + +DROP TABLE yb_char_in_key; + +-- +-- Now test longer arrays of char +-- + +CREATE TABLE yb_charn_in_value(f1 char(4)); + +INSERT INTO yb_charn_in_value(f1) VALUES ('a'); +INSERT INTO yb_charn_in_value(f1) VALUES ('ab'); +INSERT INTO yb_charn_in_value(f1) VALUES ('abc'); +INSERT INTO yb_charn_in_value(f1) VALUES ('abc '); +INSERT INTO yb_charn_in_value(f1) VALUES (' abc'); +INSERT INTO yb_charn_in_value(f1) VALUES ('abcd'); +INSERT INTO yb_charn_in_value(f1) VALUES ('abcde'); +INSERT INTO yb_charn_in_value(f1) VALUES ('abcd '); + +SELECT * FROM yb_charn_in_value ORDER BY f1; +SELECT * FROM yb_charn_in_value WHERE f1 = 'ab'; +SELECT * FROM yb_charn_in_value WHERE f1 = 'abcd'; + +DROP TABLE yb_charn_in_value; + +CREATE TABLE yb_charn_in_key(f1 char(4) PRIMARY KEY); + +INSERT INTO yb_charn_in_key(f1) VALUES ('a'); +INSERT INTO yb_charn_in_key(f1) VALUES ('ab'); +INSERT INTO yb_charn_in_key(f1) VALUES ('abc'); +INSERT INTO yb_charn_in_key(f1) VALUES ('abc '); +INSERT INTO yb_charn_in_key(f1) VALUES (' abc'); +INSERT INTO yb_charn_in_key(f1) VALUES ('abcd'); +INSERT INTO yb_charn_in_key(f1) VALUES ('abcde'); +INSERT INTO yb_charn_in_key(f1) VALUES ('abcd '); + +SELECT * FROM yb_charn_in_key ORDER BY f1; +SELECT * FROM yb_charn_in_key WHERE f1 = 'ab'; +SELECT * FROM yb_charn_in_key WHERE f1 = 'abcd'; + +DROP TABLE yb_charn_in_key; diff --git a/src/test/regress/sql/yb.orig.collate.icu.utf8.sql b/src/test/regress/sql/yb.orig.collate.icu.utf8.sql new file mode 100644 index 0000000000000..1e829483338a5 --- /dev/null +++ b/src/test/regress/sql/yb.orig.collate.icu.utf8.sql @@ -0,0 +1,363 @@ +CREATE SCHEMA collate_tests; +SET search_path = collate_tests; +SET yb_test_collation = false; + + +CREATE TABLE collate_test1 ( + a int, + b text COLLATE "en-x-icu" NOT NULL +); + +\d collate_test1 + +CREATE TABLE collate_test_fail ( + a int, + b text COLLATE "ja_JP.eucjp-x-icu" +); + +CREATE TABLE collate_test_fail ( + a int, + b text COLLATE "foo-x-icu" +); + +CREATE TABLE collate_test_fail ( + a int COLLATE "en-x-icu", + b text +); + +CREATE TABLE collate_test_like ( + LIKE collate_test1 +); + +\d collate_test_like + +CREATE TABLE collate_test2 ( + a int, + b text COLLATE "sv-x-icu" +); + +CREATE TABLE collate_test3 ( + a int, + b text COLLATE "C" +); + +INSERT INTO collate_test1 VALUES (1, 'abc'), (2, 'äbc'), (3, 'bbc'), (4, 'ABC'); +INSERT INTO collate_test2 SELECT * FROM collate_test1; +INSERT INTO collate_test3 SELECT * FROM collate_test1; + +-- Repeat the tests from yb.port.collate.icu.utf8 with expression pushdown disabled +set yb_enable_expression_pushdown to off; +EXPLAIN (costs off) SELECT * FROM collate_test1 WHERE b >= 'bbc' order by 1; +EXPLAIN (costs off) SELECT * FROM collate_test2 WHERE b >= 'bbc' order by 1; +EXPLAIN (costs off) SELECT * FROM collate_test3 WHERE b >= 'bbc' order by 1; +EXPLAIN (costs off) SELECT * FROM collate_test3 WHERE b >= 'BBC' order by 1; + +SELECT * FROM collate_test1 WHERE b >= 'bbc' order by 1; +SELECT * FROM collate_test2 WHERE b >= 'bbc' order by 1; +SELECT * FROM collate_test3 WHERE b >= 'bbc' order by 1; +SELECT * FROM collate_test3 WHERE b >= 'BBC' order by 1; + +EXPLAIN (costs off) SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' order by 1; +EXPLAIN (costs off) SELECT * FROM collate_test1 WHERE b >= 'bbc' COLLATE "C" order by 1; +EXPLAIN (costs off) SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "C" order by 1; +EXPLAIN (costs off) SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "en-x-icu" order by 1; + +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' order by 1; +SELECT * FROM collate_test1 WHERE b >= 'bbc' COLLATE "C" order by 1; +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "C" order by 1; +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "en-x-icu" order by 1; + +-- Test remote filters with collations - if DocDB uses a different collation, the filter will return +-- unexpected results. +set yb_enable_expression_pushdown to on; + +EXPLAIN (costs off) SELECT * FROM collate_test1 WHERE upper(b) = 'ÄBC'; +EXPLAIN (costs off) SELECT * FROM collate_test2 WHERE upper(b) = 'ÄBC'; +EXPLAIN (costs off) SELECT * FROM collate_test3 WHERE upper(b) = 'ÄBC'; + +SELECT * FROM collate_test1 WHERE upper(b) = 'ÄBC'; +SELECT * FROM collate_test2 WHERE upper(b) = 'ÄBC'; +SELECT * FROM collate_test3 WHERE upper(b) = 'ÄBC'; + +-- repeat the tests with expression pushdown disabled +set yb_enable_expression_pushdown to off; +EXPLAIN (costs off) SELECT * FROM collate_test1 WHERE upper(b) = 'ÄBC'; +EXPLAIN (costs off) SELECT * FROM collate_test2 WHERE upper(b) = 'ÄBC'; +EXPLAIN (costs off) SELECT * FROM collate_test3 WHERE upper(b) = 'ÄBC'; + +SELECT * FROM collate_test1 WHERE upper(b) = 'ÄBC'; +SELECT * FROM collate_test2 WHERE upper(b) = 'ÄBC'; +SELECT * FROM collate_test3 WHERE upper(b) = 'ÄBC'; + +CREATE TABLE tab1(id varchar(10)); +INSERT INTO tab1 values ('aaaa'); +\d tab1 + +-- test rewrites +CREATE OR REPLACE FUNCTION trig_rewrite() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'rewriting table'; +END; +$$; +create event trigger event_rewrite on table_rewrite + execute procedure trig_rewrite(); + +ALTER TABLE tab1 ALTER COLUMN id SET DATA TYPE varchar(5) COLLATE "en-US-x-icu"; -- rewrite +\d tab1 + +CREATE TABLE tab2(id varchar(10) COLLATE "en-US-x-icu"); +CREATE INDEX tab2_id_idx on tab2(id COLLATE "C" desc); +INSERT INTO tab2 VALUES ('aaaa'); +\d tab2 +ALTER TABLE tab2 alter COLUMN id SET DATA TYPE varchar(20) COLLATE "en-US-x-icu"; -- no rewrite +\d tab2 + +SELECT CASE WHEN v LIKE '%linux%' THEN 'true' ELSE 'false' END as linux FROM (SELECT version() as v) as v \gset +\if :linux + \set en_us_collname "en_US.utf8" + \set zh_cn_collname "zh_CN.utf8" + \set fr_fr_collname "fr_FR.utf8" + \set utf8_result 'aaa ZZZ' + \set posix_result 'ZZZ aaa' +\else + \set en_us_collname "en_US.UTF-8" + \set zh_cn_collname "zh_CN.UTF-8" + \set fr_fr_collname "fr_FR.UTF-8" + \set utf8_result 'ZZZ aaa' + \set posix_result 'ZZZ aaa' +\endif + +\set default_result 'ZZZ aaa äää' +\set ucs_basic_result 'ZZZ aaa äää' +\set en_us_x_icu_result 'aaa äää ZZZ' + +-- test YB default db +\c yugabyte +CREATE DATABASE test_default_db +TEMPLATE template0; +\c test_default_db +CREATE TABLE tab(id text); +INSERT INTO tab VALUES ('aaa'), ('äää'), ('ZZZ'); +SELECT string_agg(id, ' ') as id FROM (SELECT id from tab ORDER BY id) as id \gset +SELECT :'id' = :'default_result'; + +EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM tab WHERE upper(id) = 'ÄÄÄ'; +SELECT COUNT(*) FROM tab WHERE upper(id) = 'ÄÄÄ'; +/*+ Set(yb_enable_expression_pushdown off) */ SELECT COUNT(*) FROM tab WHERE upper(id) = 'ÄÄÄ'; + +-- test YB "POSIX" db +\c yugabyte +CREATE DATABASE test_posix_db +LOCALE "POSIX" +TEMPLATE template0; +\c test_posix_db +CREATE TABLE tab(id text); +INSERT INTO tab VALUES ('aaa'), ('äää'), ('ZZZ'); +SELECT string_agg(id, ' ') as id FROM (SELECT id from tab ORDER BY id) as id \gset +SELECT :'id' = :'posix_result'; + +EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM tab WHERE upper(id) = 'ÄÄÄ'; +SELECT COUNT(*) FROM tab WHERE upper(id) = 'ÄÄÄ'; + +-- test YB "ucs_basic" db +\c yugabyte +CREATE DATABASE test_ucs_basic_db +LOCALE "ucs_basic" +TEMPLATE template0; + +-- test YB "ucs_basic" table +\c yugabyte +CREATE TABLE tab2(id text COLLATE "ucs_basic"); +INSERT INTO tab2 VALUES ('aaa'), ('äää'), ('ZZZ'); +SELECT string_agg(id, ' ') as id FROM (SELECT id from tab2 ORDER BY id) as id \gset +SELECT :'id' = :'ucs_basic_result'; + +EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM tab2 WHERE upper(id) = 'ÄÄÄ'; +SELECT COUNT(*) FROM tab2 WHERE upper(id) = 'ÄÄÄ'; + +-- test YB en_US.UTF-8 db +\c yugabyte +CREATE DATABASE test_en_us_utf8_db +LOCALE "en_US.UTF-8" +TEMPLATE template0; +\c test_en_us_utf8_db +CREATE TABLE tab(id text); +INSERT INTO tab VALUES ('aaa'), ('äää'), ('ZZZ'); +SELECT string_agg(id, ' ') as id FROM (SELECT id from tab ORDER BY id) as id \gset +SELECT :'id' = :'utf8_result'; + +EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM tab WHERE upper(id) = 'ÄÄÄ'; +SELECT COUNT(*) FROM tab WHERE upper(id) = 'ÄÄÄ'; + +-- test YB en-US-x-icu +\c yugabyte +CREATE DATABASE test_en_us_x_icu_db +LOCALE_PROVIDER icu +ICU_LOCALE "en-US-x-icu" +LOCALE "en_US.UTF-8" +TEMPLATE template0; +\c test_en_us_x_icu_db +CREATE TABLE tab(id text); +INSERT INTO tab VALUES ('aaa'), ('äää'), ('ZZZ'); +SELECT string_agg(id, ' ') as id FROM (SELECT id from tab ORDER BY id) as id \gset +SELECT :'id' = :'en_us_x_icu_result'; + +EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM tab WHERE upper(id) = 'ÄÄÄ'; +SELECT COUNT(*) FROM tab WHERE upper(id) = 'ÄÄÄ'; + +-- test YB en_US.utf8 db, LOCALE overridden by LC_COLLATE and LC_CTYPE +\c yugabyte +CREATE DATABASE test_en_us_utf8_db2 +LOCALE "blar" +LC_COLLATE :en_us_collname +LC_CTYPE :en_us_collname +TEMPLATE template0; +\c test_en_us_utf8_db2 +CREATE TABLE tab(id text); +INSERT INTO tab VALUES ('aaa'), ('äää'), ('ZZZ'); +SELECT string_agg(id, ' ') as id FROM (SELECT id from tab ORDER BY id) as id \gset +SELECT :'id' = :'utf8_result'; + +EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM tab WHERE upper(id) = 'ÄÄÄ'; +SELECT COUNT(*) FROM tab WHERE upper(id) = 'ÄÄÄ'; + +-- test YB blar db, invalid locale name +\c yugabyte +CREATE DATABASE test_blar_db +LC_COLLATE "blar" +LC_CTYPE "blar" +TEMPLATE template0; + +-- test YB blar db, invalid locale name +\c yugabyte +CREATE DATABASE test_blar_db +LOCALE "blar" +TEMPLATE template0; + +-- test YB zh_CN.utf8 db, unsupported locale name +\c yugabyte +SET yb_test_collation = TRUE; +CREATE DATABASE test_zh_cn_utf8_db +LC_COLLATE :zh_cn_collname +LC_CTYPE :zh_cn_collname +TEMPLATE template0; + +-- test YB zh_CN.utf8 db, unsupported locale name +\c yugabyte +SET yb_test_collation = TRUE; +CREATE DATABASE test_zh_cn_utf8_db +LOCALE :zh_cn_collname +TEMPLATE template0; + +-- test YB blar collation, unsupported locale name +CREATE COLLATION blar1 (LOCALE = 'blar'); +CREATE COLLATION blar2 (LC_COLLATE = 'blar'); +CREATE COLLATION blar3 (LC_CTYPE = 'blar'); +CREATE COLLATION blar4 (LC_COLLATE = 'blar', LC_CTYPE = 'blar'); + +-- test YB fr_FR.utf8 collation, unsupported locale name +CREATE COLLATION french1 (LOCALE = :fr_fr_collname); +CREATE COLLATION french2 (LC_COLLATE = :fr_fr_collname, LC_CTYPE = 'blar'); +CREATE COLLATION french3 (LC_COLLATE = 'blar', LC_CTYPE = :fr_fr_collname); +CREATE COLLATION french4 (LC_COLLATE = :fr_fr_collname, LC_CTYPE = :fr_fr_collname); + +-- test YB en_US.utf8 collation, supported locale name +CREATE COLLATION english1 (LOCALE = :en_us_collname); +CREATE COLLATION english2 (LC_COLLATE = :en_us_collname, LC_CTYPE = :en_us_collname); +CREATE COLLATION english3 (LC_COLLATE = 'POSIX', LC_CTYPE = :en_us_collname); +CREATE COLLATION english4 (LC_COLLATE = :en_us_collname, LC_CTYPE = 'POSIX'); + +-- test YB en_US.utf8 collation, unsupported locale name +CREATE COLLATION english5 (LC_COLLATE = :fr_fr_collname, LC_CTYPE = :en_us_collname); +CREATE COLLATION english6 (LC_COLLATE = :en_us_collname, LC_CTYPE = :fr_fr_collname); + +-- test YB restrictions +\c yugabyte +SET search_path = collate_tests; +CREATE DATABASE test_db LC_COLLATE = "en-US-x-icu" TEMPLATE template0; -- fail; + +CREATE TABLE tab3(id char(10) COLLATE "en-US-x-icu"); +CREATE INDEX tab3_id_idx ON tab3(id bpchar_pattern_ops asc); -- fail; +\d tab3 +CREATE INDEX tab3_id_idx ON tab3(id COLLATE "C" asc); -- ok; +\d tab3 + +CREATE TABLE tab4(id varchar(10) COLLATE "en-US-x-icu"); +CREATE INDEX tab4_id_idx ON tab4(id varchar_pattern_ops asc); -- fail; +\d tab4 +CREATE INDEX tab4_id_idx ON tab4(id COLLATE "C" asc); -- ok; +\d tab4 + +CREATE TABLE tab5(id text COLLATE "en-US-x-icu"); +CREATE INDEX tab5_id_idx ON tab5(id text_pattern_ops asc); -- fail; +\d tab5 +CREATE INDEX tab5_id_idx ON tab5(id COLLATE "C" asc); -- ok; +\d tab5 + +-- No index scan when collation does not match +CREATE TABLE collate_filter_pushdown (k text COLLATE "C", v text, PRIMARY KEY(k hash)); +INSERT INTO collate_filter_pushdown (SELECT s, s FROM generate_series(1,1000) s); +EXPLAIN SELECT * from collate_filter_pushdown where k = 'A' COLLATE "C"; -- should push down filter and result in Index scan. +EXPLAIN SELECT * from collate_filter_pushdown where k = 'A' COLLATE "en_US"; -- should NOT push down filter and result in Seq scan. + +-- test collation versions (to ensure that YB behaves as PG, update the logs +-- when merging with future PG release because it makes error case changes). +-- Create database without LOCALE/LC_COLLATE does not set datcollversion. +\c yugabyte +CREATE DATABASE test_db1; +CREATE DATABASE test_db2 TEMPLATE template0; +SELECT datcollversion IS NULL FROM pg_database WHERE datname = 'test_db1'; +SELECT datcollversion IS NULL FROM pg_database WHERE datname = 'test_db2'; + +-- Create database LOCALE/LC_COLLATE will set datcollversion. +CREATE DATABASE test_db3 LOCALE 'en_US.UTF-8' TEMPLATE template0; +CREATE DATABASE test_db4 LC_COLLATE 'en_US.UTF-8' TEMPLATE template0; +SELECT datcollversion FROM pg_database WHERE datname = 'test_db3'; +SELECT datcollversion FROM pg_database WHERE datname = 'test_db4'; + +-- By default collation version is auto-computed by PG from the libc or libicu libraries +-- provided by the underlining operating system. +-- Create database with a fake datcollversion 'blar' to test PG's collation version mismatch +-- warning mechanism. This simulate a situation where the DB was created using one version +-- of OS and after OS upgrade PG computes a new collation version. +CREATE DATABASE test_db5 LOCALE 'en_US.UTF-8' COLLATION_VERSION = 'blar' TEMPLATE template0; +SELECT datcollversion FROM pg_database WHERE datname = 'test_db5'; +-- Should see a collation version mismatch warning. +\c test_db5 +-- On seeing the collation version mismatch warning message, the user should rebuild all +-- the affected objects. After that we can refresh the database collation to get rid of +-- the collation version mismatch warning message. +ALTER DATABASE test_db5 REFRESH COLLATION VERSION; +SELECT datcollversion FROM pg_database WHERE datname = 'test_db5'; +-- Should no longer see a collation version mismatch warning. +\c test_db5 +\c yugabyte +-- Test collation version has no effect for C collation. +-- Should not see a collation version mismatch warning. +CREATE COLLATION test_coll1 (LOCALE = "C", VERSION = 'blar'); +SELECT collversion FROM pg_collation WHERE collname = 'test_coll1'; +CREATE TABLE test_tab1(id text COLLATE test_coll1); + +-- PG 15 gives an error of "invalid collation version change", so does YB. +ALTER COLLATION test_coll1 REFRESH VERSION; + +SELECT collversion FROM pg_collation WHERE collname = 'test_coll1'; + +-- Create a new collation with a fake version 'blar' to test PG's collation version mismatch +-- warning mechanism. +CREATE COLLATION test_coll2 (LOCALE = :en_us_collname, VERSION = 'blar'); +SELECT collversion FROM pg_collation WHERE collname = 'test_coll2'; +CREATE TABLE test_tab2(id text COLLATE test_coll2); +ALTER COLLATION test_coll2 REFRESH VERSION; +SELECT collversion FROM pg_collation WHERE collname = 'test_coll2'; + +-- Simulate OS upgrade that changed builtin collation version. +SET yb_test_collation = TRUE; +CREATE TABLE test_tab3(id text COLLATE "en-x-icu"); +SELECT collversion FROM pg_collation WHERE collname = 'en-x-icu'; +INSERT INTO test_tab3 VALUES ('aaa'), ('ZZZ'); +SELECT id FROM test_tab3 ORDER BY id; +ALTER COLLATION "en-x-icu" REFRESH VERSION; +SELECT collversion FROM pg_collation WHERE collname = 'en-x-icu'; +SELECT id FROM test_tab3 ORDER BY id; diff --git a/src/test/regress/sql/yb.orig.colocated_tables_with_tablespaces.sql b/src/test/regress/sql/yb.orig.colocated_tables_with_tablespaces.sql new file mode 100644 index 0000000000000..6d2794e6db650 --- /dev/null +++ b/src/test/regress/sql/yb.orig.colocated_tables_with_tablespaces.sql @@ -0,0 +1,372 @@ +-- Colocated Tests with Tablespaces +CREATE TABLESPACE x WITH (replica_placement='{"num_replicas" : 1, "placement_blocks" : [{"cloud" : "cloud1" , "region" : "region1", "zone" : "zone1", "min_num_replicas" : 1}]}'); +CREATE DATABASE colocation_test colocation = true; +\c colocation_test + +CREATE TABLESPACE tsp1 WITH (replica_placement = '{"num_replicas": 3, "placement_blocks" : [{"cloud" : "cloud1", "region" : "region1", "zone" : "zone1", "min_num_replicas" : 1}, {"cloud" : "cloud1", "region": "region1", "zone" : "zone2", "min_num_replicas" : 1}, {"cloud" : "cloud1", "region": "region1", "zone" : "zone3", "min_num_replicas" : 1}]}'); + +CREATE TABLESPACE tsp2 WITH (replica_placement = '{"num_replicas": 3, "placement_blocks" : [{"cloud" : "cloud1", "region" : "region2", "zone" : "zone1", "min_num_replicas" : 1}, {"cloud" : "cloud1", "region": "region2", "zone" : "zone2", "min_num_replicas" : 1}, {"cloud" : "cloud1", "region": "region2", "zone" : "zone3", "min_num_replicas" : 1}]}'); + +CREATE TABLESPACE tsp3 WITH (replica_placement = '{"num_replicas": 3, "placement_blocks" : [{"cloud" : "cloud1", "region" : "region3", "zone" : "zone1", "min_num_replicas" : 1}, {"cloud" : "cloud1", "region": "region3", "zone" : "zone2", "min_num_replicas" : 1}, {"cloud" : "cloud1", "region": "region3", "zone" : "zone3", "min_num_replicas" : 1}]}'); + +-- Should succeed in creating colocated partitioned tables with tablespace +CREATE TABLE partitioned_table (name varchar, region varchar, PRIMARY KEY(name, region)) PARTITION BY LIST(region); +CREATE TABLE partition_in_dc1 PARTITION OF partitioned_table FOR VALUES IN ('region1') TABLESPACE tsp1; +CREATE TABLE partition_in_dc2 PARTITION OF partitioned_table FOR VALUES IN ('region2') TABLESPACE tsp2; +CREATE TABLE partition_in_dc3 PARTITION OF partitioned_table FOR VALUES IN ('region3') TABLESPACE tsp3; +-- 4 implicit tablegroups must be created +SELECT COUNT(*) FROM pg_yb_tablegroup; +-- 6 tablespaces(1 Default + 1 Global + 4 Defined) should have been created +SELECT COUNT(*) FROM pg_tablespace; +-- The created implicit tablegroups must use the specified tablespaces +SELECT y.spcname FROM pg_tablespace as y JOIN pg_yb_tablegroup as x ON x.grptablespace = y.oid; +DROP TABLE partitioned_table; +-- Implicit tablegroups must be deleted since it has no tables +SELECT COUNT(*) FROM pg_yb_tablegroup; + +-- Test non-colocated children tables of a partitioned table +CREATE TABLE partitioned_table2 (name varchar, region varchar, PRIMARY KEY(name, region)) PARTITION BY LIST(region); +CREATE TABLE partition_in_dc1_2 PARTITION OF partitioned_table2 FOR VALUES IN ('region1') WITH (COLOCATION=FALSE) TABLESPACE tsp1; +\d partition_in_dc1_2; +CREATE TABLE partition_in_dc2_2 PARTITION OF partitioned_table2 FOR VALUES IN ('region2') TABLESPACE tsp2; +\d partition_in_dc2_2; +DROP TABLE partitioned_table2; + +-- Users must be able to create tables in the implicit tablegroup as long as they have required permission. +-- Requires permission to create table in schema and to create a table in the tablespace +CREATE ROLE user1; +CREATE ROLE user2; +CREATE SCHEMA s1; +CREATE SCHEMA s2; +ALTER SCHEMA s1 OWNER TO user1; +ALTER SCHEMA s2 OWNER TO user2; +GRANT CREATE ON TABLESPACE tsp1 TO user1; +GRANT CREATE ON TABLESPACE tsp2 TO user2; +SELECT COUNT(*) FROM pg_yb_tablegroup; +SET SESSION ROLE user1; +CREATE TABLE s1.t1 (a INT) TABLESPACE tsp1; +CREATE TABLE s1.t2 (a INT) TABLESPACE tsp2; +SELECT COUNT(*) FROM pg_yb_tablegroup; +SET SESSION ROLE user2; +CREATE TABLE s2.t3 (a INT) TABLESPACE tsp2; +CREATE TABLE s2.t4 (a INT) TABLESPACE tsp1; +CREATE TABLE s1.t4 (a INT) TABLESPACE tsp2; +SELECT COUNT(*) FROM pg_yb_tablegroup; +SET SESSION ROLE user1; +DROP TABLE s1.t1; +-- Implicit tablegroup must be deleted since it has no tables +SELECT COUNT(*) FROM pg_yb_tablegroup; +SET SESSION ROLE user2; +DROP TABLE s2.t3; +-- Implicit tablegroup must be deleted since it has no tables +SELECT COUNT(*) FROM pg_yb_tablegroup; +RESET ROLE; + +-- Must be able to create colocated materialized view with tablespace +CREATE TABLE base_table (a INT PRIMARY KEY); +INSERT INTO base_table SELECT * FROM generate_series(1, 100); +CREATE MATERIALIZED VIEW m TABLESPACE tsp1 AS SELECT * FROM base_table; +-- 2 implicit tablegroup must be created +SELECT COUNT(*) FROM pg_yb_tablegroup; +-- The implicit tablegroup must use tablespace tsp1 +SELECT y.spcname FROM pg_tablespace as y JOIN pg_yb_tablegroup as x ON x.grptablespace = y.oid; +DROP MATERIALIZED VIEW m; +DROP TABLE base_table; +-- Implicit tablegroup must be deleted since it has no tables +SELECT COUNT(*) FROM pg_yb_tablegroup; + +-- Test colocated indexes +CREATE TABLE table_1(a INT PRIMARY KEY, b INT, c INT) TABLESPACE tsp2; +-- Indexes should use default implicit tablegroup if no tablespace is specified +CREATE INDEX coloc_index1 on table_1(a); +CREATE INDEX coloc_index2 on table_1(b) TABLESPACE tsp1; +CREATE INDEX coloc_index3 on table_1(c) TABLESPACE tsp2; +SELECT COUNT(*) FROM pg_yb_tablegroup; +INSERT INTO table_1 VALUES (1,1,1), (2,2,2), (3,3,3), (4,3,4), (5,5,4); +SELECT * FROM table_1 WHERE a = 2 ORDER BY b; +SELECT * FROM table_1 WHERE b = 3 ORDER BY c; +SELECT * FROM table_1 WHERE c = 4 ORDER BY a; +EXPLAIN (COSTS OFF) SELECT * FROM table_1 WHERE a = 2; +EXPLAIN (COSTS OFF) SELECT * FROM table_1 WHERE b = 3; +EXPLAIN (COSTS OFF) SELECT * FROM table_1 WHERE c = 4; +-- Test if pg_depend entries of the indexes and the table are being updated. +SELECT string_agg(c.relname, ', ' ORDER BY c.relname) AS relation_names +FROM pg_class c +JOIN pg_depend d ON c.oid = d.objid +JOIN pg_yb_tablegroup tg ON d.refobjid = tg.oid +WHERE c.relname IN ('table_1', 'coloc_index1', 'coloc_index2', 'coloc_index3') +GROUP BY tg.grpname +ORDER BY relation_names; + +DROP INDEX coloc_index1; +DROP INDEX coloc_index2; +DROP INDEX coloc_index3; +DROP TABLE table_1; +-- Implicit tablegroup must be deleted since it has no tables +SELECT COUNT(*) FROM pg_yb_tablegroup; + +-- Describing colocated table with tablespace must show both colocation and tablespace info +CREATE TABLE table_1 (a INT PRIMARY KEY) TABLESPACE tsp1; +\d+ table_1 +DROP TABLE table_1; + +-- Table Created without colocation should not be colocated +CREATE TABLE table_1 (a INT PRIMARY KEY) with (COLOCATION = false); +\d+ table_1 +DROP TABLE table_1; + +-- Implicit tablegroup must be deleted since it has no tables +SELECT COUNT(*) FROM pg_yb_tablegroup; + +-- Test ALTER TABLE/INDEX/MATERIALIZED VIEW ALL IN TABLESPACE ... SET TABLESPACE ... CASCADE +-- Since all three variations behave similarly we expect similar output +CREATE TABLE table1(a INT PRIMARY KEY, b INT, c INT) TABLESPACE tsp1; +CREATE TABLE table2(a INT PRIMARY KEY, b INT, c INT) WITH (colocation=false) TABLESPACE tsp1; +CREATE INDEX index1 on table1(b) TABLESPACE tsp1; +CREATE MATERIALIZED VIEW col_mv TABLESPACE tsp1 AS SELECT table1.a,table2.c FROM table1,table2 WHERE table1.c = table2.c; +CREATE INDEX index2 on table1(c) TABLESPACE tsp2; +CREATE MATERIALIZED VIEW m TABLESPACE tsp2 AS SELECT * FROM table1; +CREATE TABLE table3(a int PRIMARY KEY, b int, c int) TABLESPACE tsp2; + +-- Since we are moving colocated relations, this ALTER should fail +ALTER TABLE ALL IN TABLESPACE tsp1 SET TABLESPACE tsp3; + +-- Since there are existing relations present in tsp2, this ALTER should fail +ALTER TABLE ALL IN TABLESPACE tsp1 SET TABLESPACE tsp2 CASCADE; + +-- View all relations present in pg_class in tablespace tsp1, tsp2 and tsp3 +SELECT c.relname, ts.spcname FROM + pg_class c + JOIN pg_tablespace ts ON c.reltablespace = ts.oid +WHERE + ts.spcname IN ('tsp1', 'tsp2', 'tsp3') +ORDER BY + c.relname; + +-- There are no relations present in tsp3, this would succeed and move all relations to tsp3. +ALTER TABLE ALL IN TABLESPACE tsp1 SET TABLESPACE tsp3 CASCADE; + +--Verify if the tablespaces for the queries have been updated. +SELECT c.relname, ts.spcname FROM + pg_class c + JOIN pg_tablespace ts ON c.reltablespace = ts.oid +WHERE + ts.spcname IN ('tsp1', 'tsp2', 'tsp3') +ORDER BY + c.relname; + +ALTER INDEX ALL IN TABLESPACE tsp2 SET TABLESPACE tsp1 CASCADE; + +SELECT c.relname, ts.spcname FROM + pg_class c + JOIN pg_tablespace ts ON c.reltablespace = ts.oid +WHERE + ts.spcname IN ('tsp1', 'tsp2', 'tsp3') +ORDER BY + c.relname; + +ALTER MATERIALIZED VIEW ALL IN TABLESPACE tsp3 SET TABLESPACE tsp2 CASCADE; + +SELECT c.relname, ts.spcname FROM + pg_class c + JOIN pg_tablespace ts ON c.reltablespace = ts.oid +WHERE + ts.spcname IN ('tsp1', 'tsp2', 'tsp3') +ORDER BY + c.relname; + +-- Verify that dependency entries from tablegroup->tablespace, table->tablegroup are getting updated properly, only entries for colocated relations are added in pg_shdepend +SELECT u.relname, s.spcname FROM pg_class AS u, pg_depend AS v, pg_shdepend AS x, pg_tablespace AS s WHERE +v.objid = u.oid AND +v.refclassid = 'pg_yb_tablegroup'::regclass::oid AND +x.objid = v.refobjid AND +x.refclassid = 'pg_tablespace'::regclass::oid AND +s.oid = x.refobjid +ORDER BY u.relname; + +DROP MATERIALIZED VIEW m; +DROP MATERIALIZED VIEW col_mv; +DROP INDEX index1; +DROP INDEX index2; +DROP TABLE table1; +DROP TABLE table2; +DROP TABLE table3; + +-- Test ALTER TABLE ALL IN TABLESPACE ... COLLOCATED WITH ... SET TABLESPACE ... CASCADE +CREATE TABLE table1(a INT PRIMARY KEY, b INT, c INT) TABLESPACE tsp1; +CREATE TABLE table2(a INT PRIMARY KEY, b INT, c INT) WITH (colocation=false) TABLESPACE tsp1; +CREATE INDEX index1 on table1(b) TABLESPACE tsp1; +CREATE INDEX index2 on table1(c) TABLESPACE tsp1; +CREATE MATERIALIZED VIEW col_mv TABLESPACE tsp1 AS SELECT table1.a,table2.c FROM table1,table2 WHERE table1.c = table2.c; +CREATE MATERIALIZED VIEW m TABLESPACE tsp2 AS SELECT * FROM table1; +CREATE TABLE table3(a int PRIMARY KEY, b int, c int) TABLESPACE tsp2; + +-- Since there are existing relations present in tsp2, this ALTER should fail +ALTER TABLE ALL IN TABLESPACE tsp1 COLOCATED WITH table1 SET TABLESPACE tsp2 CASCADE; + +-- View all relations present in pg_class in tablespace tsp1, tsp2 and tsp3 +SELECT c.relname, ts.spcname FROM + pg_class c + JOIN pg_tablespace ts ON c.reltablespace = ts.oid +WHERE + ts.spcname IN ('tsp1', 'tsp2', 'tsp3') +ORDER BY + c.relname; + +-- There are no relations present in tsp3, this would succeed and move relations colocated with t1 to tsp3. +ALTER TABLE ALL IN TABLESPACE tsp1 COLOCATED WITH table1 SET TABLESPACE tsp3 CASCADE; + +--Verify if the tablespaces for the queries have been updated. +SELECT c.relname, ts.spcname FROM + pg_class c + JOIN pg_tablespace ts ON c.reltablespace = ts.oid +WHERE + ts.spcname IN ('tsp1', 'tsp2', 'tsp3') +ORDER BY + c.relname; + +-- Verify that dependency entries are updated accordingly in pg_shdepend +SELECT u.relname, s.spcname FROM pg_class AS u, pg_depend AS v, pg_shdepend AS x, pg_tablespace AS s WHERE +v.objid = u.oid AND +v.refclassid = 'pg_yb_tablegroup'::regclass::oid AND +x.objid = v.refobjid AND +x.refclassid = 'pg_tablespace'::regclass::oid AND +s.oid = x.refobjid +ORDER BY u.relname; + +DROP MATERIALIZED VIEW m; +DROP MATERIALIZED VIEW col_mv; +DROP INDEX index1; +DROP INDEX index2; +DROP TABLE table1; +DROP TABLE table2; +DROP TABLE table3; + +-- Test non-colocated Tables in a colocated database +CREATE TABLE table1(a INT PRIMARY KEY, b INT, c INT) WITH (colocation=false) TABLESPACE tsp1; +CREATE TABLE table2(a INT PRIMARY KEY, b INT, c INT) WITH (colocation=false) TABLESPACE tsp2; +CREATE TABLE table3(a INT PRIMARY KEY, b INT, c INT) TABLESPACE tsp3; + +ALTER TABLE ALL IN TABLESPACE tsp1 SET TABLESPACE tsp2; +--Verify if the tablespaces for the queries have been updated. +SELECT c.relname, ts.spcname FROM + pg_class c + JOIN pg_tablespace ts ON c.reltablespace = ts.oid +WHERE + ts.spcname IN ('tsp1', 'tsp2', 'tsp3') +ORDER BY + c.relname; + +ALTER TABLE ALL in TABLESPACE tsp2 SET TABLESPACE tsp3; +--Verify if the tablespaces for the queries have been updated. +SELECT c.relname, ts.spcname FROM + pg_class c + JOIN pg_tablespace ts ON c.reltablespace = ts.oid +WHERE + ts.spcname IN ('tsp1', 'tsp2', 'tsp3') +ORDER BY + c.relname; + +-- Verify that dependency entries are updated accordingly in pg_shdepend, non-colocated relations are not added in the pg_shdepend table +SELECT u.relname, s.spcname FROM pg_class AS u, pg_depend AS v, pg_shdepend AS x, pg_tablespace AS s WHERE +v.objid = u.oid AND +v.refclassid = 'pg_yb_tablegroup'::regclass::oid AND +x.objid = v.refobjid AND +x.refclassid = 'pg_tablespace'::regclass::oid AND +s.oid = x.refobjid +ORDER BY u.relname; + +-- This command should fail +ALTER TABLE ALL IN TABLESPACE tsp2 COLOCATED WITH table1 SET TABLESPACE tsp3 CASCADE; + +DROP TABLE table1; +DROP TABLE table2; +DROP TABLE table3; + +-- Explicitly specifying pg_default tablespace must not create a new tablegroup +CREATE TABLE table1(a int); +SELECT grpname, grpowner, grptablespace, grpacl, grpoptions FROM pg_yb_tablegroup; +CREATE TABLE table2(a int) TABLESPACE pg_default; +SELECT grpname, grpowner, grptablespace, grpacl, grpoptions FROM pg_yb_tablegroup; +DROP TABLE table1; +DROP TABLE table2; + +-- In pg_yb_tablegroup grpname should always be colocation_ (i.e. colocation_) +CREATE TABLE table1(a INT PRIMARY KEY, b INT, c INT) TABLESPACE tsp1; +CREATE TABLE table2(a INT PRIMARY KEY, b INT, c INT) TABLESPACE tsp1; +SELECT COUNT(*) FROM pg_yb_tablegroup WHERE grpname != 'default' AND (grpname = ('colocation_' || grptablespace)) ; +ALTER TABLE ALL IN TABLESPACE tsp1 SET TABLESPACE tsp3 CASCADE; +SELECT COUNT(*) FROM pg_yb_tablegroup WHERE grpname != 'default' AND (grpname = ('colocation_' || grptablespace)) ; +DROP TABLE table1; +DROP TABLE table2; + +-- Should not be able to move a single colocated table to other tablespace +CREATE TABLE t1(a int) TABLESPACE tsp1; +CREATE TABLE t2(a int) TABLESPACE tsp1; +ALTER TABLE t1 SET TABLESPACE tsp2; +DROP TABLE t1; +DROP TABLE t2; + +-- A tablespace should not be dropped if any colocated tables are dependent on it +CREATE TABLE t1 (a int) TABLESPACE tsp1; +CREATE TABLE t2 (a int) TABLESPACE tsp1; +DROP TABLESPACE tsp1; +DROP TABLE t1; +DROP TABLESPACE tsp1; +DROP TABLE t2; +DROP TABLESPACE tsp1; +DROP TABLESPACE tsp2; +DROP TABLESPACE tsp3; + +CREATE TABLESPACE tsp1 WITH (replica_placement = '{ +"num_replicas": 1, +"placement_blocks": [ + { + "cloud" : "cloud1", + "region" : "region1", + "zone" : "zone1", + "min_num_replicas" : 1 + }]}'); + +CREATE TABLESPACE tsp2 WITH (replica_placement = '{ +"num_replicas": 1, +"placement_blocks": [ + { + "cloud" : "cloud1", + "region" : "region1", + "zone" : "zone1", + "min_num_replicas" : 1 + }]}'); + +CREATE TABLE t1 (a int) TABLESPACE tsp1; +CREATE TABLE t2 (a int) TABLESPACE tsp2; + +-- Check that dependency entries from tablegroup->tablespace, table->tablegroup are getting created properly +-- Here we perform joins on pg_class, pg_depends, pg_shdepend and pg_tablespace to find +-- 1. Corresponding tablegroup of a colocated table +-- 2. Corresponding tablespace of the tablegroup +SELECT s.spcname FROM pg_class AS u, pg_depend AS v, pg_shdepend AS x, pg_tablespace AS s WHERE +-- Relation name is t1 +u.relname = 't1' AND +-- Find table -> tablegroup entry in pg_depends by matching table oid and refclassid of tablegroup +v.objid = u.oid AND +v.refclassid = 'pg_yb_tablegroup'::regclass::oid AND +-- Find tablegroup -> tablespace entry in shdepends by matching tablegroup oid from pg_depend and refclassid of tablespace +x.objid = v.refobjid AND +x.refclassid = 'pg_tablespace'::regclass::oid AND +-- Find the tablespace name by matching tablespace oid (found using pg_shdepend) with pg_tablespace. +s.oid = x.refobjid; + +SELECT s.spcname FROM pg_class AS u, pg_depend AS v, pg_shdepend AS x, pg_tablespace AS s WHERE +-- Relation name is t2 +u.relname = 't2' AND +-- Find table -> tablegroup entry in pg_depends by matching table oid and refclassid of tablegroup +v.objid = u.oid AND +v.refclassid = 'pg_yb_tablegroup'::regclass::oid AND +-- Find tablegroup -> tablespace entry in shdepends by matching tablegroup oid from pg_depend and refclassid of tablespace +x.objid = v.refobjid AND +x.refclassid = 'pg_tablespace'::regclass::oid AND +-- Find the tablespace name by matching tablespace oid (found using pg_shdepend) with pg_tablespace. +s.oid = x.refobjid; +\c yugabyte +DROP DATABASE colocation_test; diff --git a/src/test/regress/sql/yb.orig.compound_key.sql b/src/test/regress/sql/yb.orig.compound_key.sql new file mode 100644 index 0000000000000..7f852a4092374 --- /dev/null +++ b/src/test/regress/sql/yb.orig.compound_key.sql @@ -0,0 +1,21 @@ +-- Test primary key column order differs table creation column order -- +CREATE TABLE st_keys(k4 int, k3 int, k2 int, k1 int, value int, PRIMARY KEY(k1, k2, k3, k4)); +CREATE INDEX ON st_keys(k4); +CREATE UNIQUE INDEX ON st_keys(value); +INSERT INTO st_keys VALUES(4, 3, 2, 1, 100), (40, 30, 20, 10, 200), (400, 300, 200, 100, 300); +SELECT * FROM st_keys ORDER BY k1; +EXPLAIN(COSTS OFF) SELECT * FROM st_keys WHERE value = 200 ORDER BY k1; +SELECT * FROM st_keys WHERE value = 200 ORDER BY k1; +EXPLAIN(COSTS OFF) SELECT * FROM st_keys WHERE k4 = 400 ORDER BY k1; +SELECT * FROM st_keys WHERE k4 = 400 ORDER BY k1; + +UPDATE st_keys SET value=0 WHERE k2 = 20; +SELECT k1, k2, k3, k4, value FROM st_keys ORDER BY k1; +DELETE FROM st_keys WHERE k4 = 400; +SELECT value, k4, k3, k2, k1 FROM st_keys ORDER BY k1; + +CREATE TABLE dt_keys(k3 int, k2 text, k1 int, PRIMARY KEY(k1, k2, k3)); +INSERT INTO dt_keys VALUES(3, 'value_2', 1); +INSERT INTO dt_keys(k1, k2, k3) VALUES(10, 'value_20', 30); +SELECT * FROM dt_keys ORDER BY k1; +SELECT k1, k2, k3 FROM dt_keys ORDER BY k1; diff --git a/src/test/regress/sql/yb.orig.copy_55k_rows.sql b/src/test/regress/sql/yb.orig.copy_55k_rows.sql new file mode 100644 index 0000000000000..de7e3cb7d5925 --- /dev/null +++ b/src/test/regress/sql/yb.orig.copy_55k_rows.sql @@ -0,0 +1,13 @@ +-- +-- COPY +-- + +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +\set filename :abs_srcdir '/data/all-airport-codes.csv' +COPY airports FROM :'filename' CSV HEADER; +-- Workaround: Sleeping 2 minutes for docdb to complete its background tasks (issue #4855). +-- Don't sleep more than 60 seconds each to avoid any RPC issues. +SELECT pg_sleep(60); +SELECT pg_sleep(60); diff --git a/src/test/regress/sql/yb.orig.create_function.sql b/src/test/regress/sql/yb.orig.create_function.sql new file mode 100644 index 0000000000000..63fcdb1ed6a08 --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_function.sql @@ -0,0 +1,45 @@ +-- +-- Yugabyte created functions +-- + +-- directory path and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +CREATE FUNCTION bigname_in(cstring) + RETURNS bigname + AS :'regresslib' + LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION bigname_out(bigname) + RETURNS cstring + AS :'regresslib' + LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION yb_run_spi(text, int) + RETURNS int8 + AS :'regresslib' + LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION yb_cacheinfo() + RETURNS TABLE ( + cache_id int, + name text, + reloid oid, + indoid oid, + nkeys int, + key1 int, + key2 int, + key3 int, + key4 int, + nbuckets int + ) + AS :'regresslib' + LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION catalog_cache_compute_hash_tuple(record) + RETURNS bigint + AS :'regresslib' + LANGUAGE C STRICT IMMUTABLE; diff --git a/src/test/regress/sql/yb.orig.create_index.sql b/src/test/regress/sql/yb.orig.create_index.sql new file mode 100644 index 0000000000000..2da643bd53994 --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_index.sql @@ -0,0 +1,539 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (COSTS OFF)' + +-- +-- Create index on existing table with data +-- +CREATE TABLE index_test (col1 int, col2 int); +INSERT INTO index_test (col1, col2) VALUES (1, 100); +INSERT INTO index_test (col1, col2) VALUES (2, 200); + +CREATE INDEX index_test_idx1 ON index_test(col1); +CREATE INDEX index_test_idx2 ON index_test(col1, col2); + +DROP INDEX index_test_idx1; +DROP INDEX index_test_idx2; +DROP TABLE index_test; + +CREATE TABLE test_index (v1 INT, v2 INT, v3 INT); +CREATE INDEX ON test_index (v1); +CREATE INDEX ON test_index (v2, v3); +INSERT INTO test_index VALUES (1, 11, 21), (2, 12, 22), (3, 13, 23), (4, 14, 24), (5, 15, 25); + +-- Verify order by on indexed column +SELECT * FROM test_index ORDER BY v1; + +-- Verify delete with hash value in index +DELETE FROM test_index WHERE v2 = 12 OR v2 = 13; +SELECT * FROM test_index ORDER BY v1; + +-- Verify different WHERE conditions are supported. +SELECT * FROM test_index WHERE v1 IS NULL; +SELECT * FROM test_index WHERE v1 IS NOT NULL ORDER BY v1; +SELECT * FROM test_index WHERE v1 IN (1, 2, 3); + + +-- Verify indexes on system catalog tables are updated properly + +CREATE TABLE test_sys_catalog_update (k int primary key, v int); + +SELECT $$ +:P SELECT relname FROM pg_class WHERE relname = 'test_sys_catalog_update'; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT typname FROM pg_type WHERE typname = 'test_sys_catalog_update'; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT attname, atttypid FROM pg_attribute WHERE attname = 'v'; +$$ AS query \gset +\i :iter_P2 + +ALTER TABLE test_sys_catalog_update RENAME TO test_sys_catalog_update_new; +ALTER TABLE test_sys_catalog_update_new RENAME COLUMN v TO w; + +SELECT relname FROM pg_class WHERE relname = 'test_sys_catalog_update'; +SELECT typname FROM pg_type WHERE typname = 'test_sys_catalog_update'; +SELECT attname, atttypid FROM pg_attribute WHERE attname = 'v'; + +SELECT relname FROM pg_class WHERE relname = 'test_sys_catalog_update_new'; +SELECT typname FROM pg_type WHERE typname = 'test_sys_catalog_update_new'; +SELECT attname, atttypid FROM pg_attribute WHERE attname = 'w'; + +-- Test primary key as index +CREATE TABLE t1 (h INT, r INT, v1 INT, v2 INT, PRIMARY KEY (h hash, r)); +CREATE INDEX ON t1 (v1); +CREATE UNIQUE INDEX ON t1 (v1, v2); +CREATE TABLE t2 (h INT, r INT, v1 INT, v2 INT, PRIMARY KEY ((h) hash, r)); + +\d t1 +\d t2 + +INSERT INTO t1 VALUES (1, 1, 11, 11), (1, 2, 11, 12); +INSERT INTO t2 VALUES (1, 1, 21, 21); + +-- The following 2 inserts should produce error due to duplicate primary key / unique index value +INSERT INTO t1 VALUES (1, 1, 99, 99); +INSERT INTO t1 VALUES (1, 3, 11, 11); + +INSERT INTO t1 VALUES (1, 3, 11, 13), (2, 1, 12, 13), (2, 2, 12, 14); + +\set query ':P SELECT * FROM t1 ORDER BY h, r;' +\i :iter_P2 + +\set query ':P SELECT * FROM t1 WHERE h = 1 ORDER BY r;' +\i :iter_P2 + +\set query ':P SELECT * FROM t1 WHERE yb_hash_code(h) = yb_hash_code(1) ORDER BY r;' +\i :iter_P2 + +\set query ':P SELECT * FROM t1 WHERE h > 1 ORDER BY h, r;' +\i :iter_P2 + +\set query ':P SELECT * FROM t1 WHERE h = 1 AND r = 1;' +\i :iter_P2 + +\set query ':P SELECT * FROM t1 WHERE yb_hash_code(h) = yb_hash_code(1) AND r = 1;' +\i :iter_P2 + +\set query ':P SELECT * FROM t1 WHERE v1 = 11 ORDER BY h, r;' +\i :iter_P2 + +-- Disabled this test because we do not have proper stats. We return the same cost estimate +-- for indexes t1_v1_idx and t1_v1_v2_idx and Postgres will be either of them at random. +-- \set query ':P SELECT * FROM t1 WHERE v1 = 11 AND v2 = 11;' +-- \i :iter_P2 + +\set query ':P SELECT t1.h, t1.r, t1.v1, t2.v1 FROM t1, t2 WHERE t1.h = t2.h AND t1.r = t2.r;' +\i :iter_P2 + +-- +-- NULL value in index +-- +CREATE TABLE null_index(k int, v int); +CREATE INDEX null_index_v ON null_index(v); +INSERT INTO null_index(k) VALUES(1); +INSERT INTO null_index VALUES(2, NULL); +INSERT INTO null_index VALUES(3, 3); + +SELECT * FROM null_index ORDER BY k; +SELECT * FROM null_index WHERE v IS NULL ORDER BY k; +SELECT * FROM null_index WHERE v IS NOT NULL ORDER BY k; + +-- +-- NULL value in unique index +-- +CREATE TABLE null_unique_index(k int, v int); +CREATE UNIQUE INDEX ON null_unique_index(k); +INSERT INTO null_unique_index(v) values(1); +INSERT INTO null_unique_index values (NULL, 2), (3, 3), (4, 4); +INSERT INTO null_unique_index values(4, 5); -- fail +EXPLAIN(COSTS OFF) SELECT * FROM null_unique_index WHERE k = 4; +EXPLAIN(COSTS OFF) SELECT * FROM null_unique_index WHERE k IS NULL ORDER BY k; +EXPLAIN(COSTS OFF) SELECT * FROM null_unique_index WHERE k IS NOT NULL ORDER BY k; +SELECT * FROM null_unique_index WHERE k = 4; +SELECT * FROM null_unique_index WHERE k IS NULL ORDER BY v; +SELECT * FROM null_unique_index WHERE k IS NOT NULL ORDER BY v; +DELETE FROM null_unique_index WHERE k = 3; +SELECT * FROM null_unique_index WHERE k IS NULL ORDER BY v; +SELECT * FROM null_unique_index WHERE k IS NOT NULL ORDER BY v; +\set query ':P DELETE FROM null_unique_index WHERE k IS NULL;' +\i :iter_P2 +SELECT * FROM null_unique_index ORDER BY v; +INSERT INTO null_unique_index values (NULL, 2), (3, 3), (NULL, 5); +\set query ':P UPDATE null_unique_index SET k = NULL WHERE k IS NOT NULL;' +\i :iter_P2 +SELECT * FROM null_unique_index ORDER BY v; + +-- Test index update with UPDATE and DELETE +CREATE TABLE test_unique (k int PRIMARY KEY, v1 int, v2 int); +CREATE UNIQUE INDEX ON test_unique (v1); +CREATE INDEX ON test_unique (v2); + +-- Insert a row +INSERT INTO test_unique VALUES (1, 1, 1); +SELECT * FROM test_unique; + +-- UPDATE a row and verify the content of associated indexes via index-only scan +UPDATE test_unique SET v1 = 2 WHERE k = 1; +SELECT v1 FROM test_unique WHERE v1 IN (1, 2); +SELECT v2 FROM test_unique WHERE v2 IN (1, 2); + +-- DELETE a row and verify the content of associated indexes via index-only scan +DELETE FROM test_unique WHERE k = 1; +SELECT v1 FROM test_unique WHERE v1 IN (1, 2); +SELECT v2 FROM test_unique WHERE v2 IN (1, 2); + +-- Insert 2 rows of the affected v1 values. Make sure both can be inserted +-- with no duplicate key violation. +INSERT INTO test_unique VALUES (1, 1, 1); +INSERT INTO test_unique VALUES (2, 2, 2); +SELECT * FROM test_unique; + +-- Test cascade-truncate indexes +CREATE TABLE test_truncate (a int PRIMARY KEY, b int); +CREATE UNIQUE INDEX test_truncate_index ON test_truncate (b); + +INSERT INTO test_truncate VALUES (1, 2); +INSERT INTO test_truncate VALUES (2, 2); + +\set query ':P SELECT b FROM test_truncate WHERE b = 2;' +\i :iter_P2 + +TRUNCATE test_truncate; +SELECT b FROM test_truncate WHERE b = 2; + +INSERT INTO test_truncate VALUES (2, 2); +INSERT INTO test_truncate VALUES (1, 2); + +DROP TABLE test_truncate; + +-- Test index methods +CREATE TABLE test_method (k int PRIMARY KEY, v int); +CREATE INDEX ON test_method USING btree (v); +CREATE INDEX ON test_method USING hash (v); +CREATE INDEX ON test_method USING foo (v); +\d test_method +DROP TABLE test_method; + +-- Test include columns +CREATE TABLE test_include (c1 int, c2 int, c3 int); +INSERT INTO test_include VALUES (1, 1, 1), (1, 2, 2), (2, 2, 2), (3, 3, 3); +-- Expect duplicate key error +CREATE UNIQUE INDEX ON test_include (c1) include (c2); +\d test_include +DROP INDEX test_include_c1_c2_idx; +DELETE FROM test_include WHERE c1 = 1 AND c2 = 2; +CREATE UNIQUE INDEX ON test_include (c1) include (c2); +\set query ':P SELECT c1, c2 FROM test_include WHERE c1 = 1;' +\i :iter_P2 +\d test_include +-- Verify the included column is updated in both the base table and the index. Use WHERE condition +-- on c1 below to force the scan on the index vs. base table. Select the non-included column c3 in +-- the other case to force the use of sequential scan on the base table. +UPDATE test_include SET c2 = 22 WHERE c1 = 2; +\set query ':P SELECT c1, c2 FROM test_include WHERE c1 > 0 ORDER BY c2;' +\i :iter_P2 +\set query ':P SELECT * FROM test_include ORDER BY c2;' +\i :iter_P2 +UPDATE test_include SET c2 = NULL WHERE c1 = 1; +-- TODO(mihnea) Disabled temporarily due to issue #1611 +-- UPDATE test_include SET c2 = 33 WHERE c2 = 3; +DELETE FROM test_include WHERE c1 = 2; +SELECT c1, c2 FROM test_include WHERE c1 > 0 ORDER BY c2; +SELECT * FROM test_include ORDER BY c2; + +-- Test SPLIT INTO +CREATE TABLE test_split ( + h1 int, h2 int, r1 int, r2 int, v1 int, v2 int, + PRIMARY KEY ((h1, h2) HASH, r1, r2)); +CREATE INDEX ON test_split (h2 HASH, r2, r1) SPLIT INTO 20 TABLETS; +CREATE INDEX ON test_split ((r1,r2) HASH) SPLIT INTO 20 TABLETS; +CREATE INDEX ON test_split (h2) SPLIT INTO 20 TABLETS; +\d test_split + +-- These should fail +CREATE INDEX ON test_split (r1 ASC) SPLIT INTO 20 TABLETS; +CREATE INDEX ON test_split (h2 ASC, r1) SPLIT INTO 20 TABLETS; +CREATE INDEX ON test_split (h1 HASH) SPLIT INTO 10000 TABLETS; + +-- Test hash methods +CREATE TABLE test_method ( + h1 int, h2 int, r1 int, r2 int, v1 int, v2 int, + PRIMARY KEY ((h1, h2) HASH, r1, r2)); +CREATE INDEX ON test_method (h2 HASH, r2, r1); +CREATE INDEX ON test_method (r1, r2); +CREATE UNIQUE INDEX ON test_method (v1, v2); +CREATE INDEX ON test_method ((h1, h2) HASH, r2, r1); +CREATE INDEX ON test_method ((h2, h1), r2 DESC, r1); +CREATE UNIQUE INDEX ON test_method ((h1, (h2 % 8)) HASH, r2, r1); +\d test_method + +-- These should issue NOTICE and verify pg_get_indexdef() output doesn't have +-- NULLS FIRST/NULLS LAST +CREATE INDEX ON test_method (h1 HASH NULLS FIRST); +SELECT pg_get_indexdef('test_method_h1_idx'::regclass); +CREATE INDEX ON test_method (h1 HASH NULLS LAST); +SELECT pg_get_indexdef('test_method_h1_idx1'::regclass); +CREATE INDEX ON test_method (h1 NULLS LAST); +SELECT pg_get_indexdef('test_method_h1_idx2'::regclass); +CREATE INDEX ON test_method (h1 NULLS LAST); +SELECT pg_get_indexdef('test_method_h1_idx3'::regclass); +CREATE INDEX ON test_method ((h1 % 8) HASH NULLS FIRST); +SELECT pg_get_indexdef('test_method_expr_idx'::regclass); +CREATE INDEX ON test_method ((h1 % 8) HASH NULLS LAST); +SELECT pg_get_indexdef('test_method_expr_idx1'::regclass); +CREATE INDEX ON test_method ((h1 % 8) NULLS FIRST); +SELECT pg_get_indexdef('test_method_expr_idx2'::regclass); +CREATE INDEX ON test_method ((h1 % 8) NULLS LAST); +SELECT pg_get_indexdef('test_method_expr_idx3'::regclass); +\d test_method +DROP INDEX test_method_expr_idx; +DROP INDEX test_method_expr_idx1; +DROP INDEX test_method_expr_idx2; +DROP INDEX test_method_expr_idx3; +DROP INDEX test_method_h1_idx; +DROP INDEX test_method_h1_idx1; +DROP INDEX test_method_h1_idx2; +DROP INDEX test_method_h1_idx3; + +-- Test should not issue NOTICE +CREATE INDEX ON test_method (r1 ASC NULLS FIRST, r2 ASC NULLS LAST); +CREATE INDEX ON test_method (r1 DESC NULLS FIRST, r2 DESC NULLS LAST); + +CREATE DATABASE colocation_test colocation = true; +\c colocation_test +CREATE TABLE test_method (r1 int, r2 int, v1 int, v2 int, + PRIMARY KEY (r1, r2)); +CREATE INDEX ON test_method (r1 NULLS FIRST); +CREATE INDEX ON test_method (r1 NULLS LAST); +\d test_method +\c yugabyte +DROP DATABASE colocation_test; + +CREATE TABLEGROUP tbl_group; +CREATE TABLE tbl_group_tbl (r1 int, r2 int, v1 int, v2 int, + PRIMARY KEY (r1, r2)) TABLEGROUP tbl_group; +CREATE INDEX idx_tbl_group_tbl ON tbl_group_tbl (r1 NULLS FIRST); +CREATE INDEX idx2_tbl_group_tbl ON tbl_group_tbl (r1 NULLS LAST); +\d tbl_group_tbl +DROP TABLE tbl_group_tbl; +DROP TABLEGROUP tbl_group; + +-- These should fail +CREATE INDEX ON test_method (h1 HASH, h2 HASH, r2, r1); +CREATE INDEX ON test_method (r1, h1 HASH); +CREATE INDEX ON test_method (() HASH); +CREATE INDEX ON test_method (()); +CREATE INDEX ON test_method (r1 DESC, (h2, h1)); +CREATE INDEX ON test_method ((h1, h2) HASH NULLS FIRST); +CREATE INDEX ON test_method ((h1, h2) HASH NULLS LAST); +CREATE INDEX ON test_method ((h1 % 8, h2) HASH NULLS FIRST); +CREATE INDEX ON test_method ((h1 % 8, h2) HASH NULLS LAST); + +INSERT INTO test_method VALUES + (1, 1, 1, 1, 1, 11), + (1, 1, 1, 2, 2, 12), + (1, 1, 2, 1, 3, 13), + (1, 1, 2, 2, 4, 14), + (1, 2, 1, 1, 5, 15), + (1, 2, 1, 2, 6, 16), + (1, 2, 2, 1, 7, 17), + (1, 2, 2, 2, 8, 18), + (2, 0, 1, 1, 9, 19), + (2, 1, 1, 2, 10, 20); + +-- Test scans using different indexes. Verify order by. +\set query ':P SELECT * FROM test_method ORDER BY h1, h2;' +\i :iter_P2 +\set query ':P SELECT * FROM test_method WHERE h1 = 1 AND h2 = 1 ORDER BY r1, r2;' +\i :iter_P2 +\set query ':P SELECT * FROM test_method ORDER BY r1, r2;' +\i :iter_P2 +\set query ':P SELECT * FROM test_method WHERE v1 > 5 ORDER BY v1, v2;' +\i :iter_P2 +\set query ':P SELECT * FROM test_method WHERE h2 = 2 ORDER BY r1, r2;' +\i :iter_P2 +\set query ':P SELECT * FROM test_method WHERE h2 = 1 AND h1 = 1 ORDER BY r2 DESC, r1;' +\i :iter_P2 +\set query ':P SELECT * FROM test_method WHERE h2 = 1 AND h1 = 1 ORDER BY r2, r1;' +\i :iter_P2 +\set query ':P SELECT * FROM test_method WHERE h2 % 8 = 2 AND h1 = 1 ORDER BY r2, r1;' +\i :iter_P2 + +-- Test update using a hash index +\set query ':P UPDATE test_method SET v2 = v2 + 10 WHERE h2 = 2;' +\i :iter_P2 +SELECT * FROM test_method ORDER BY h1, h2; +SELECT * FROM test_method ORDER BY r1, r2; + +-- Test delete using a unique index +\set query ':P DELETE FROM test_method WHERE v1 = 5 AND v2 = 25;' +\i :iter_P2 + +-- Test delete using the primary key +\set query ':P DELETE FROM test_method WHERE h1 = 2 AND h2 = 0;' +\i :iter_P2 + +SELECT * FROM test_method ORDER BY h1, h2; + +-- Test update using a unique index on hashed expr +UPDATE test_method SET h2 = 258 WHERE h2 % 8 = 1 AND h1 = 2; +SELECT * FROM test_method ORDER BY h1, h2; + +-- This should fail +UPDATE test_method SET h2 = 257 WHERE h2 % 8 = 2 AND h1 = 1; + +-- Test insert using a unique index on hashed expr +-- This should fail +INSERT INTO test_method VALUES (1, 10, 2, 2, 8, 100); + +-- Test hash with extra parenthesis on a single column +CREATE INDEX ON test_method ((h2) HASH); +\d test_method +\set query ':P SELECT * FROM test_method WHERE h2 = 258;' +\i :iter_P2 +DROP TABLE test_method; + +-- Test more HASH key cases in PRIMARY KEY +CREATE TABLE test_hash ( + h1 int, h2 int, r1 int, r2 int, v1 int, v2 int); + +-- These should fail +ALTER TABLE test_hash ADD PRIMARY KEY ((h1 % 8, h2) HASH, r1, r2); +ALTER TABLE test_hash ADD PRIMARY KEY ((h1, h2) HASH, (r1 + r2) DESC); +-- Extra parenthesis turns the column group into a RowExpr - should fail +ALTER TABLE test_hash ADD PRIMARY KEY (((h1, h2)) HASH); +ALTER TABLE test_hash ADD PRIMARY KEY (((h1, h2))); + +-- This should succeed +CREATE UNIQUE INDEX test_hash_h1_h2mod8_r2_r1_idx ON test_hash ((h1, (h2 % 8)) HASH, r2, r1); +-- This should fail +ALTER TABLE test_hash ADD PRIMARY KEY USING INDEX test_hash_h1_h2mod8_r2_r1_idx; + +-- These CREATE TABLE statements should fail +DROP TABLE test_hash; +CREATE TABLE test_hash ( + h1 int, h2 int, r1 int, r2 int, v1 int, v2 int, + PRIMARY KEY (r1, (h1, h2) HASH)); + +CREATE TABLE test_hash ( + h1 int, h2 int, r1 int, r2 int, v1 int, v2 int, + PRIMARY KEY ((h1 + 666) HASH)); + +CREATE TABLE test_hash ( + h1 int, h2 int, r1 int, r2 int, v1 int, v2 int, + PRIMARY KEY ((h1, (h2 + 666)) HASH, r1, r2)); + +-- Test ordering on split indexes +CREATE TABLE tbl(k SERIAL PRIMARY KEY, v INT); +CREATE INDEX ON tbl(v ASC) SPLIT AT VALUES((10), (20), (30)); +INSERT INTO tbl(v) VALUES + (5), (6), (16), (15), (25), (26), (36), (35), (46), (10), (20), (30), (40), (6), (16), (26); +\set query ':P SELECT * FROM tbl ORDER BY v ASC;' +\i :iter_P2 +\set query ':P SELECT * FROM tbl ORDER BY v DESC;' +\i :iter_P2 +\set query ':P SELECT v FROM tbl WHERE v > 10 and v <= 40 ORDER BY v DESC;' +\i :iter_P2 +\set query ':P SELECT v FROM tbl WHERE v > 10 and v <= 40 ORDER BY v ASC;' +\i :iter_P2 + +DROP TABLE tbl; +CREATE TABLE tbl(k SERIAL PRIMARY KEY, v INT); +CREATE UNIQUE INDEX ON tbl(v DESC) SPLIT AT VALUES((30), (20), (10)); +INSERT INTO tbl(v) VALUES + (5), (6), (16), (15), (25), (26), (36), (35), (46), (10), (20), (30), (40); +\set query ':P SELECT * FROM tbl ORDER BY v ASC;' +\i :iter_P2 +\set query ':P SELECT * FROM tbl ORDER BY v DESC;' +\i :iter_P2 +\set query ':P SELECT v FROM tbl WHERE v >= 10 and v < 40 ORDER BY v ASC;' +\i :iter_P2 +\set query ':P SELECT v FROM tbl WHERE v >= 10 and v < 40 ORDER BY v DESC;' +\i :iter_P2 + +-- Test creating indexes with (table_oid = x) +CREATE TABLE test_index_with_oids (v1 INT, v2 INT, v3 INT); +INSERT INTO test_index_with_oids VALUES (1, 11, 21), (2, 12, 22), (3, 13, 23), (4, 14, 24), (5, 15, 25); + +-- Test with variable = false +CREATE INDEX index_with_table_oid ON test_index_with_oids (v1) with (table_oid = 1111111); +-- Turn on variable and test +set yb_enable_create_with_table_oid=1; +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = 0); +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = -1); +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = 123); +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = 'test'); + +CREATE INDEX index_with_table_oid ON test_index_with_oids (v1) with (table_oid = 1111111); +select relname, oid from pg_class where relname = 'index_with_table_oid'; +SELECT * FROM test_index_with_oids ORDER BY v1; + +CREATE INDEX index_with_duplicate_table_oid ON test_index_with_oids (v1) with (table_oid = 1111111); + +-- Test NULLS [NOT] DISTINCT WITH (...) +CREATE INDEX index_with_nulls_and_table_oid ON test_index_with_oids (v2) NULLS NOT DISTINCT with (table_oid = 2222222); +CREATE UNIQUE INDEX index_with_nulls_and_row_type_oid ON test_index_with_oids (v3) NULLS DISTINCT with (row_type_oid = 3333333); +\d test_index_with_oids +SELECT * FROM get_table_indexes('test_index_with_oids'); + +set yb_format_funcs_include_yb_metadata=1; +\d test_index_with_oids +SELECT * FROM get_table_indexes('test_index_with_oids'); + +set yb_format_funcs_include_yb_metadata=0; +set yb_enable_create_with_table_oid=0; + +-- Test creating index nonconcurrently (i.e. without online schema migration) +CREATE TABLE test_index_nonconcurrently (i INT, t TEXT); +INSERT INTO test_index_nonconcurrently VALUES (generate_series(1, 10), 'a'); + +CREATE INDEX NONCONCURRENTLY ON test_index_nonconcurrently (i); +EXPLAIN (COSTS OFF) SELECT i FROM test_index_nonconcurrently WHERE i = 1; +SELECT * FROM test_index_nonconcurrently WHERE i = 1; +DROP INDEX test_index_nonconcurrently_i_idx; + +CREATE UNIQUE INDEX NONCONCURRENTLY ON test_index_nonconcurrently (i); +EXPLAIN (COSTS OFF) SELECT i FROM test_index_nonconcurrently WHERE i = 1; +INSERT INTO test_index_nonconcurrently VALUES (1, 'b'); +DROP INDEX test_index_nonconcurrently_i_idx; + +INSERT INTO test_index_nonconcurrently VALUES (1, 'b'); +CREATE UNIQUE INDEX NONCONCURRENTLY ON test_index_nonconcurrently (i); + +DROP TABLE test_index_nonconcurrently; + +-- Verify that creating indexes on a YB table does not update table stats. +CREATE TABLE test_stats (i INT); +INSERT INTO test_stats VALUES (1), (2), (3); +ANALYZE test_stats; +SELECT reltuples FROM pg_class WHERE relname = 'test_stats'; +CREATE INDEX CONCURRENTLY ON test_stats(i); +SELECT reltuples FROM pg_class WHERE relname = 'test_stats'; +CREATE INDEX NONCONCURRENTLY ON test_stats(i); +SELECT reltuples FROM pg_class WHERE relname = 'test_stats'; +DROP TABLE test_stats; + +-- Split options on a partitioned table or its indexes should not be copied +-- for a newly attached partition. +CREATE TABLE test_part (a INT, PRIMARY KEY(a ASC)) PARTITION BY RANGE (a) + SPLIT AT VALUES ((5), (10)); -- split options are ignored. +CREATE TABLE test_part_1 PARTITION OF test_part FOR VALUES FROM (1) TO (5); +SELECT yb_get_range_split_clause('test_part_1'::regclass); +CREATE INDEX test_part_idx ON test_part(a) SPLIT INTO 5 TABLETS; +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('test_part_1_a_idx'::regclass); +CREATE TABLE test_part_2 PARTITION OF test_part DEFAULT; +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('test_part_2_a_idx'::regclass); + +-- Test creating temp index using lsm. +CREATE TEMP TABLE test_temp_lsm (i int); +CREATE INDEX ON test_temp_lsm USING lsm (i); + +-- temp tables +CREATE TEMPORARY TABLE temp_table(a int, b int); +CREATE INDEX CONCURRENTLY temp_index ON temp_table(a); + +-- Cleanup. +DISCARD TEMP; + +-- Test complex index names. +CREATE TABLE "Test Table" (col1 INT PRIMARY KEY, col2 INT); +CREATE INDEX "Test Index '" ON "Test Table"(col2); +CREATE INDEX "Test 'Index'" ON "Test Table"(col2); +CREATE INDEX "Test Index """ ON "Test Table"(col2); +CREATE INDEX "Test ""Index""" ON "Test Table"(col2); +CREATE INDEX "Test_""_index_WITH_""""_different'_'quotes' and spaces" ON "Test Table"(col2); +\d "Test Table" +\d "Test Index '" +\d "Test 'Index'" +\d "Test Index """ +\d "Test ""Index""" +\d "Test_""_index_WITH_""""_different'_'quotes' and spaces" diff --git a/src/test/regress/sql/yb.orig.create_index_large_table_backfill_index.sql b/src/test/regress/sql/yb.orig.create_index_large_table_backfill_index.sql new file mode 100644 index 0000000000000..67640d10ea8a2 --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_index_large_table_backfill_index.sql @@ -0,0 +1,14 @@ +-- +-- CREATE_INDEX +-- +-- Index on airport names. +CREATE INDEX airports_idx_name ON airports(name ASC); + +-- Index for SELECTing ybctid of the same airport country using HASH key. +CREATE INDEX airport_type_hash_idx ON airports(type HASH, iso_country ASC, ident ASC); + +-- Index for SELECTing ybctid of the same airport type using RANGE key. +CREATE INDEX airport_name_type_ident_range_idx ON airports(name ASC, type ASC, ident ASC); + +-- Index for checking correctness when using secondary index. +CREATE INDEX airport_type_region_idx ON airports((type, iso_region) HASH, ident ASC); diff --git a/src/test/regress/sql/yb.orig.create_index_update_reltuples.sql b/src/test/regress/sql/yb.orig.create_index_update_reltuples.sql new file mode 100644 index 0000000000000..fd680041966d3 --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_index_update_reltuples.sql @@ -0,0 +1,422 @@ +-- #25394 : Update pg_class.reltuples after CREATE INDEX + +--------------------------------- +-- Tests with feature disabled +--------------------------------- +SET yb_enable_update_reltuples_after_create_index = OFF; + +---------------------------- +-- Concurrent Index Creation +---------------------------- + +-- Create a table and insert 100 rows, do not run ANALYZE. +CREATE TABLE test_25394 (id INT, val INT, tags text[]); +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(1,100) AS i; + +-- Reltuples should be -1 for the base table. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes. Reltuples for the indexes should be 0. +CREATE INDEX CONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX CONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Drop the index. +DROP INDEX test_25394_val_idx; +DROP INDEX test_25394_tags_idx; + +-- After ANALYZE, reltuples should be 100. +ANALYZE test_25394; +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes again. Index reltuples should not be set. +CREATE INDEX CONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX CONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Insert another 100 rows to the table. +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(101,200) AS i; + +-- Since ANALYZE is not called, reltuples should still be 100. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create another index. Base table reltuples should not be updated. Index reltuples should be 0. +CREATE INDEX CONCURRENTLY test_25394_k_idx ON test_25394(val); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create partial indexes +CREATE INDEX CONCURRENTLY test_25394_partial_val_idx ON test_25394(val) WHERE id > 100; +CREATE INDEX CONCURRENTLY test_25394_partial_tags_idx ON test_25394 using gin (tags) WHERE id > 100; + +-- Base table Reltuples should not be updated. Index reltuples should be 0. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Cleanup +DROP TABLE test_25394; + +-------------------------------- +-- Non-concurrent Index Creation +-------------------------------- + +-- Before this fix, reltuples for the index were updated in non-concurrent +-- index creation, but the base table reltuples were left unchanged. After this +-- fix, both the base table and index reltuples are updated. + +-- Create a table and insert 100 rows, do not run ANALYZE. +CREATE TABLE test_25394 (id INT, val INT, tags text[]); +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(1,100) AS i; + +-- Reltuples should be -1 for the base table. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes. Reltuples for the indexes should be set, but base table should have -1. +CREATE INDEX NONCONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX NONCONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Drop the index. +DROP INDEX test_25394_val_idx; +DROP INDEX test_25394_tags_idx; + +-- After ANALYZE, reltuples should be 100. +ANALYZE test_25394; +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes again. Index reltuples should be set. +CREATE INDEX NONCONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX NONCONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Insert another 100 rows to the table. +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(101,200) AS i; + +-- Since ANALYZE is not called, reltuples should still be 100. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create another index. Base table reltuples should not be updated. Index reltuples should be set. +-- Reltuples for old indexes will not be updated. +CREATE INDEX NONCONCURRENTLY test_25394_k_idx ON test_25394(val); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create partial indexes +CREATE INDEX NONCONCURRENTLY test_25394_partial_val_idx ON test_25394(val) WHERE id > 100; +CREATE INDEX NONCONCURRENTLY test_25394_partial_tags_idx ON test_25394 using gin (tags) WHERE id > 100; + +-- Base table Reltuples should not be updated. Index reltuples should be set. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Cleanup +DROP TABLE test_25394; + +----------------------------------------------- +-- Concurrent Index Creation on Temporary Table +----------------------------------------------- + +-- The behavior for temporary tables has not changed. After create index, the +-- reltuples for the index and the base table are set to the actual number +-- of tuples. + +-- Create a table and insert 100 rows, do not run ANALYZE. +CREATE TEMP TABLE test_25394 (id INT, val INT, tags text[]); +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(1,100) AS i; + +-- Reltuples should be -1 for the base table. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes. Reltuples are set to actual number of rows in the table and indexes. +CREATE INDEX CONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX CONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Drop the index. +DROP INDEX test_25394_val_idx; +DROP INDEX test_25394_tags_idx; + +-- After ANALYZE, reltuples should be 100. +ANALYZE test_25394; +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes again. Reltuples are set to actual number of rows in the table and indexes. +CREATE INDEX CONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX CONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Insert another 100 rows to the table. +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(101,200) AS i; + +-- Since ANALYZE is not called, reltuples should still be 100. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create another index. Reltuples for base table and new index should be 200. +-- Reltuples for old indexes will not be updated. +CREATE INDEX CONCURRENTLY test_25394_k_idx ON test_25394(val); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create partial indexes +CREATE INDEX CONCURRENTLY test_25394_partial_val_idx ON test_25394(val) WHERE id > 100; +CREATE INDEX CONCURRENTLY test_25394_partial_tags_idx ON test_25394 using gin (tags) WHERE id > 100; + +-- Reltuples for the partial indexes should be set correctly. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Cleanup +DROP TABLE test_25394; + +--------------------------------------------------- +-- Non-concurrent Index Creation on Temporary Table +--------------------------------------------------- + +-- Create a table and insert 100 rows, do not run ANALYZE. +CREATE TEMP TABLE test_25394 (id INT, val INT, tags text[]); +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(1,100) AS i; + +-- Reltuples should be -1 for the base table. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes. Reltuples are set to actual number of rows in the table and indexes. +CREATE INDEX NONCONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX NONCONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Drop the index. +DROP INDEX test_25394_val_idx; +DROP INDEX test_25394_tags_idx; + +-- After ANALYZE, reltuples should be 100. +ANALYZE test_25394; +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes again. Reltuples are set to actual number of rows in the table and indexes. +CREATE INDEX NONCONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX NONCONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Insert another 100 rows to the table. +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(101,200) AS i; + +-- Since ANALYZE is not called, reltuples should still be 100. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create another index. Reltuples for base table and new index should be 200. +-- Reltuples for old indexes will not be updated. +CREATE INDEX NONCONCURRENTLY test_25394_k_idx ON test_25394(val); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create partial indexes +CREATE INDEX NONCONCURRENTLY test_25394_partial_val_idx ON test_25394(val) WHERE id > 100; +CREATE INDEX NONCONCURRENTLY test_25394_partial_tags_idx ON test_25394 using gin (tags) WHERE id > 100; + +-- Reltuples for the partial indexes should be set correctly. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Cleanup +DROP TABLE test_25394; + +-------------------------------- +-- Tests with feature enabled +-------------------------------- + +SET yb_enable_update_reltuples_after_create_index = ON; + +---------------------------- +-- Concurrent Index Creation +---------------------------- + +-- Create a table and insert 100 rows, do not run ANALYZE. +CREATE TABLE test_25394 (id INT, val INT, tags text[]); +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(1,100) AS i; + +-- Reltuples should be -1 for the base table. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes. Reltuples should be set to actual number or rows in the base table or index. +CREATE INDEX CONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX CONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Drop the index. +DROP INDEX test_25394_val_idx; +DROP INDEX test_25394_tags_idx; + +-- After ANALYZE, base table reltuples should be 100. +ANALYZE test_25394; +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes again. Reltuples should be set to actual number of rows in the base table or index. +CREATE INDEX CONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX CONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Insert another 100 rows to the table. +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(101,200) AS i; + +-- Since ANALYZE is not called, reltuples should still be 100. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create another index. Base table reltuples should be updated and new index should have 200 reltuples. +CREATE INDEX CONCURRENTLY test_25394_k_idx ON test_25394(val); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create partial indexes +CREATE INDEX CONCURRENTLY test_25394_partial_val_idx ON test_25394(val) WHERE id > 100; +CREATE INDEX CONCURRENTLY test_25394_partial_tags_idx ON test_25394 using gin (tags) WHERE id > 100; + +-- Base table Reltuples should not be updated. Index reltuples should be 0. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Cleanup +DROP TABLE test_25394; + +-------------------------------- +-- Non-concurrent Index Creation +-------------------------------- + +-- Create a table and insert 100 rows, do not run ANALYZE. +CREATE TABLE test_25394 (id INT, val INT, tags text[]); +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(1,100) AS i; + +-- Reltuples should be -1 for the base table. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes. Reltuples should be set to actual number or rows in the base table or index. +CREATE INDEX NONCONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX NONCONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Drop the index. +DROP INDEX test_25394_val_idx; +DROP INDEX test_25394_tags_idx; + +-- After ANALYZE, base table reltuples should be 100. +ANALYZE test_25394; +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes again. Reltuples should be set to actual number of rows in the base table or index. +CREATE INDEX NONCONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX NONCONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Insert another 100 rows to the table. +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(101,200) AS i; + +-- Since ANALYZE is not called, reltuples should still be 100. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create another index. Base table reltuples should be updated and new index should have 200 reltuples. +-- Reltuples for old indexes will not be updated. +CREATE INDEX NONCONCURRENTLY test_25394_k_idx ON test_25394(val); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create partial indexes +CREATE INDEX NONCONCURRENTLY test_25394_partial_val_idx ON test_25394(val) WHERE id > 100; +CREATE INDEX NONCONCURRENTLY test_25394_partial_tags_idx ON test_25394 using gin (tags) WHERE id > 100; + +-- Index reltuples should be set to actual number of rows in the index. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Cleanup +DROP TABLE test_25394; + +----------------------------------------------- +-- Concurrent Index Creation on Temporary Table +----------------------------------------------- + +-- The behavior for temporary tables has not changed. After create index, the +-- reltuples for the index and the base table are set to the actual number +-- of tuples. + +-- Create a table and insert 100 rows, do not run ANALYZE. +CREATE TEMP TABLE test_25394 (id INT, val INT, tags text[]); +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(1,100) AS i; + +-- Reltuples should be -1 for the base table. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes. Reltuples are set to actual number of rows in the table and indexes. +CREATE INDEX CONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX CONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Drop the index. +DROP INDEX test_25394_val_idx; +DROP INDEX test_25394_tags_idx; + +-- After ANALYZE, reltuples should be 100. +ANALYZE test_25394; +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes again. Reltuples are set to actual number of rows in the table and indexes. +CREATE INDEX CONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX CONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Insert another 100 rows to the table. +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(101,200) AS i; + +-- Since ANALYZE is not called, reltuples should still be 100. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create another index. Reltuples for base table and new index should be 200. +-- Reltuples for old indexes will not be updated. +CREATE INDEX CONCURRENTLY test_25394_k_idx ON test_25394(val); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create partial indexes +CREATE INDEX CONCURRENTLY test_25394_partial_val_idx ON test_25394(val) WHERE id > 100; +CREATE INDEX CONCURRENTLY test_25394_partial_tags_idx ON test_25394 using gin (tags) WHERE id > 100; + +-- Reltuples for the partial indexes should be set correctly. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Cleanup +DROP TABLE test_25394; + +--------------------------------------------------- +-- Non-concurrent Index Creation on Temporary Table +--------------------------------------------------- + +-- Create a table and insert 100 rows, do not run ANALYZE. +CREATE TEMP TABLE test_25394 (id INT, val INT, tags text[]); +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(1,100) AS i; + +-- Reltuples should be -1 for the base table. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes. Reltuples are set to actual number of rows in the table and indexes. +CREATE INDEX NONCONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX NONCONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Drop the index. +DROP INDEX test_25394_val_idx; +DROP INDEX test_25394_tags_idx; + +-- After ANALYZE, reltuples should be 100. +ANALYZE test_25394; +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create indexes again. Reltuples are set to actual number of rows in the table and indexes. +CREATE INDEX NONCONCURRENTLY test_25394_val_idx ON test_25394(val); +CREATE INDEX NONCONCURRENTLY test_25394_tags_idx ON test_25394 using gin (tags); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Insert another 100 rows to the table. +INSERT INTO test_25394 SELECT i, i*10, array[md5(random()::text), md5(random()::text)] FROM generate_series(101,200) AS i; + +-- Since ANALYZE is not called, reltuples should still be 100. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create another index. Reltuples for base table and new index should be 200. +-- Reltuples for old indexes will not be updated. +CREATE INDEX NONCONCURRENTLY test_25394_k_idx ON test_25394(val); +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Create partial indexes +CREATE INDEX NONCONCURRENTLY test_25394_partial_val_idx ON test_25394(val) WHERE id > 100; +CREATE INDEX NONCONCURRENTLY test_25394_partial_tags_idx ON test_25394 using gin (tags) WHERE id > 100; + +-- Reltuples for the partial indexes should be set correctly. +SELECT relname, reltuples FROM pg_class WHERE relname like 'test_25394%' order by relname; + +-- Cleanup +DROP TABLE test_25394; diff --git a/src/test/regress/sql/yb.orig.create_language.sql b/src/test/regress/sql/yb.orig.create_language.sql new file mode 100644 index 0000000000000..d28800b20048a --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_language.sql @@ -0,0 +1,11 @@ +-- +-- YB_FEATURE Testsuite: CREATE LANGUAGE +-- +DROP EXTENSION plpgsql CASCADE; +CREATE LANGUAGE plpgsql; +CREATE FUNCTION test() RETURNS INTEGER AS $$begin return 1; end$$ LANGUAGE plpgsql; +SELECT * FROM test(); +DROP LANGUAGE plpgsql CASCADE; +DROP EXTENSION plpgsql CASCADE; +-- leave the cluster in a clean state +CREATE EXTENSION plpgsql; diff --git a/src/test/regress/sql/yb.orig.create_large_table.sql b/src/test/regress/sql/yb.orig.create_large_table.sql new file mode 100644 index 0000000000000..1418a5d73afe1 --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_large_table.sql @@ -0,0 +1,16 @@ +-- +-- CREATE_TABLE +-- +CREATE TABLE airports(ident TEXT, + type TEXT, + name TEXT, + elevation_ft INT, + continent TEXT, + iso_country TEXT, + iso_region TEXT, + municipality TEXT, + gps_code TEXT, + iata_code TEXT, + local_code TEXT, + coordinates TEXT, + PRIMARY KEY (ident)); diff --git a/src/test/regress/sql/yb.orig.create_large_table_indexes.sql b/src/test/regress/sql/yb.orig.create_large_table_indexes.sql new file mode 100644 index 0000000000000..41d8cf13aea72 --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_large_table_indexes.sql @@ -0,0 +1,34 @@ +-- +-- Table definition +-- +CREATE TABLE airports(ident TEXT, + type TEXT, + name TEXT, + elevation_ft INT, + continent TEXT, + iso_country TEXT, + iso_region TEXT, + municipality TEXT, + gps_code TEXT, + iata_code TEXT, + local_code TEXT, + coordinates TEXT, + PRIMARY KEY (iso_region HASH, ident ASC)); + +-- CREATE INDEX airports_idx ON airports(ident, +-- type, +-- name, +-- elevation_ft, +-- continent, +-- iso_country, +-- iso_region, +-- municipality, +-- gps_code, +-- iata_code, +-- local_code, +-- coordinates); + +CREATE INDEX airports_idx1 ON airports(iso_region hash, name DESC); +CREATE INDEX airports_idx2 ON airports(iso_region ASC, gps_code ASC); +CREATE INDEX airports_idx3 ON airports((iso_region, type) HASH, coordinates, ident, name) + INCLUDE (gps_code); diff --git a/src/test/regress/sql/yb.orig.create_large_table_tablet_split.sql b/src/test/regress/sql/yb.orig.create_large_table_tablet_split.sql new file mode 100644 index 0000000000000..39d3390abb2fb --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_large_table_tablet_split.sql @@ -0,0 +1,16 @@ +-- +-- CREATE_TABLE +-- +CREATE TABLE airports(ident TEXT, + type TEXT, + name TEXT, + elevation_ft INT, + continent TEXT, + iso_country TEXT, + iso_region TEXT, + municipality TEXT, + gps_code TEXT, + iata_code TEXT, + local_code TEXT, + coordinates TEXT, + PRIMARY KEY (ident)) SPLIT INTO 1 TABLETS; diff --git a/src/test/regress/sql/yb.orig.create_table.sql b/src/test/regress/sql/yb.orig.create_table.sql new file mode 100644 index 0000000000000..b3913e9d4774a --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_table.sql @@ -0,0 +1,642 @@ +-- +-- YB_TABLE Testsuite: Testing DDL Statments for TABLE. +-- + +-- +-- CREATE TABLE AS SELECT +-- +CREATE TABLE table_create_org( + col_smallint SMALLINT, + col_integer INTEGER, + col_bigint BIGINT, + col_real REAL, + col_double DOUBLE PRECISION, + col_char CHARACTER(7), + col_varchar VARCHAR(7), + col_text TEXT, + col_bytea BYTEA, + col_timestamp TIMESTAMP(2), + col_timestamp_tz TIMESTAMP WITH TIME ZONE, + col_bool BOOLEAN, + col_array_int INTEGER[], + col_array_text TEXT[], + PRIMARY KEY(col_smallint)); +-- +INSERT INTO table_create_org VALUES( + 1, + 1, + 1, + 1.1, + 1.1, + 'one', + 'one', + 'one', + E'\\x11F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'January 1, 2019 01:01:01.1111', + 'January 1, 2019 01:01:01.1111 PST AD', + TRUE, + '{ 1, 1, 1 }', + '{ "one", "one", "one" }'); +INSERT INTO table_create_org VALUES( + 11, + 1, + 1, + 1.1, + 1.1, + 'one', + 'one', + 'one', + E'\\x11F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'January 1, 2019 01:01:01.1111', + 'January 1, 2019 01:01:01.1111 PST AD', + TRUE, + '{ 1, 1, 1 }', + '{ "one", "one", "one" }'); +-- +INSERT INTO table_create_org VALUES( + 2, + 2, + 2, + 2.2, + 2.2, + 'two', + 'two', + 'two', + E'\\x22F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'February 2, 2019 02:02:02.2222', + 'February 2, 2019 02:02:02.2222 PST AD', + TRUE, + '{ 2, 2, 2 }', + '{ "two", "two", "two" }'); +INSERT INTO table_create_org VALUES( + 12, + 2, + 2, + 2.2, + 2.2, + 'two', + 'two', + 'two', + E'\\x22F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'February 2, 2019 02:02:02.2222', + 'February 2, 2019 02:02:02.2222 PST AD', + TRUE, + '{ 2, 2, 2 }', + '{ "two", "two", "two" }'); +-- +INSERT INTO table_create_org VALUES( + 3, + 3, + 3, + 3.3, + 3.3, + 'three', + 'three', + 'three', + E'\\x33F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'March 3, 2019 03:03:03.3333', + 'March 3, 2019 03:03:03.3333 PST AD', + TRUE, + '{ 3, 3, 3 }', + '{ "three", "three", "three" }'); +INSERT INTO table_create_org VALUES( + 13, + 3, + 3, + 3.3, + 3.3, + 'three', + 'three', + 'three', + E'\\x33F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'March 3, 2019 03:03:03.3333', + 'March 3, 2019 03:03:03.3333 PST AD', + TRUE, + '{ 3, 3, 3 }', + '{ "three", "three", "three" }'); +-- +INSERT INTO table_create_org VALUES( + 4, + 4, + 4, + 4.4, + 4.4, + 'four', + 'four', + 'four', + E'\\x44F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'April 4, 2019 04:04:04.4444', + 'April 4, 2019 04:04:04.4444 PST AD', + TRUE, + '{ 4, 4, 4 }', + '{ "four", "four", "four" }'); +INSERT INTO table_create_org VALUES( + 14, + 4, + 4, + 4.4, + 4.4, + 'four', + 'four', + 'four', + E'\\x44F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'April 4, 2019 04:04:04.4444', + 'April 4, 2019 04:04:04.4444 PST AD', + TRUE, + '{ 4, 4, 4 }', + '{ "four", "four", "four" }'); +-- +INSERT INTO table_create_org VALUES( + 5, + 5, + 5, + 5.5, + 5.5, + 'five', + 'five', + 'five', + E'\\x55F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'May 5, 2019 05:05:05.5555', + 'May 5, 2019 05:05:05.5555 PST AD', + TRUE, + '{ 5, 5, 5 }', + '{ "five", "five", "five" }'); +INSERT INTO table_create_org VALUES( + 15, + 5, + 5, + 5.5, + 5.5, + 'five', + 'five', + 'five', + E'\\x55F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'May 5, 2019 05:05:05.5555', + 'May 5, 2019 05:05:05.5555 PST AD', + TRUE, + '{ 5, 5, 5 }', + '{ "five", "five", "five" }'); +-- +INSERT INTO table_create_org VALUES( + 6, + 6, + 6, + 6.6, + 6.6, + 'six', + 'six', + 'six', + E'\\x66F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'June 6, 2019 06:06:06.6666', + 'June 6, 2019 06:06:06.6666 PST AD', + TRUE, + '{ 6, 6, 6 }', + '{ "six", "six", "six" }'); +INSERT INTO table_create_org VALUES( + 16, + 6, + 6, + 6.6, + 6.6, + 'six', + 'six', + 'six', + E'\\x66F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'June 6, 2019 06:06:06.6666', + 'June 6, 2019 06:06:06.6666 PST AD', + TRUE, + '{ 6, 6, 6 }', + '{ "six", "six", "six" }'); +-- +INSERT INTO table_create_org VALUES( + 7, + 7, + 7, + 7.7, + 7.7, + 'seven', + 'seven', + 'seven', + E'\\x77F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'July 7, 2019 07:07:07.7777', + 'July 7, 2019 07:07:07.7777 PST AD', + TRUE, + '{ 7, 7, 7 }', + '{ "seven", "seven", "seven" }'); +INSERT INTO table_create_org VALUES( + 17, + 7, + 7, + 7.7, + 7.7, + 'seven', + 'seven', + 'seven', + E'\\x77F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'July 7, 2019 07:07:07.7777', + 'July 7, 2019 07:07:07.7777 PST AD', + TRUE, + '{ 7, 7, 7 }', + '{ "seven", "seven", "seven" }'); +-- +INSERT INTO table_create_org VALUES( + 8, + 8, + 8, + 8.8, + 8.8, + 'eight', + 'eight', + 'eight', + E'\\x88F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'August 8, 2019 08:08:08.8888', + 'August 8, 2019 08:08:08.8888 PST AD', + TRUE, + '{ 8, 8, 8 }', + '{ "eight", "eight", "eight" }'); +INSERT INTO table_create_org VALUES( + 18, + 8, + 8, + 8.8, + 8.8, + 'eight', + 'eight', + 'eight', + E'\\x88F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'August 8, 2019 08:08:08.8888', + 'August 8, 2019 08:08:08.8888 PST AD', + TRUE, + '{ 8, 8, 8 }', + '{ "eight", "eight", "eight" }'); +-- +INSERT INTO table_create_org VALUES( + 9, + 9, + 9, + 9.9, + 9.9, + 'nine', + 'nine', + 'nine', + E'\\x99F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'September 9, 2019 09:09:09.9999', + 'September 9, 2019 09:09:09.9999 PST AD', + TRUE, + '{ 9, 9, 9 }', + '{ "nine", "nine", "nine" }'); +INSERT INTO table_create_org VALUES( + 19, + 9, + 9, + 9.9, + 9.9, + 'nine', + 'nine', + 'nine', + E'\\x99F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'September 9, 2019 09:09:09.9999', + 'September 9, 2019 09:09:09.9999 PST AD', + TRUE, + '{ 9, 9, 9 }', + '{ "nine", "nine", "nine" }'); +-- +CREATE TABLE table_create_ctas_nodata AS SELECT * FROM table_create_org WITH NO DATA; +-- +SELECT * FROM table_create_ctas_nodata; +-- +CREATE TABLE table_create_ctas2_nodata + AS SELECT col_smallint id, + col_text "name", + col_array_int AS private_data, + col_array_text AS information + FROM table_create_org + WITH NO DATA; +-- +SELECT * FROM table_create_ctas2_nodata; +-- +CREATE TABLE table_create_ctas AS SELECT * FROM table_create_org; +-- +SELECT * FROM table_create_ctas ORDER BY col_smallint; +-- +CREATE TABLE table_create_ctas2 + AS SELECT col_smallint id, + col_text "name", + col_array_int AS private_data, + col_array_text AS information + FROM table_create_org + WHERE col_smallint < 11; +-- +SELECT * FROM table_create_ctas2 ORDER BY id; + +-- +-- CREATE TABLE WITH +-- +CREATE TABLE patient ( + name text, + age int4, + dob date +) WITH (fillfactor=40); + +CREATE TABLE planetrip ( + origin text, + dest text, + day date, + depart time +) WITH (user_catalog_table=true); + +CREATE TABLE client ( + name text, + phonenum int8, + deadline date +) WITH (oids=false); + +-- +-- CREATE TABLE SPLIT +-- +CREATE TABLE tbl1 ( + a int4 primary key +) SPLIT (INTO 20 TABLETS); + +CREATE TABLE tbl1_5 ( + a int4, + primary key(a asc) +) SPLIT (INTO 20 TABLETS); + +CREATE TABLE tbl2 ( + a int4, + primary key (a asc) +) SPLIT AT VALUES ((4), (25), (100)); + +CREATE TABLE tbl3 ( + a int4, + primary key (a asc) +) SPLIT AT VALUES ((25), (100), (4)); + +CREATE TABLE tbl4 ( + a int4, + b text, + primary key (a asc, b) +) SPLIT AT VALUES ((1, 'c'), (1, 'cb'), (2, 'a')); + +CREATE TABLE tbl5 ( + a int4, + b text, + primary key (b asc) +) SPLIT AT VALUES (('a'), ('aba'), ('ac')); + +CREATE TABLE tbl6 ( + a int4, + b text, + primary key (b asc) +) SPLIT AT VALUES (('a'), (2, 'aba'), ('ac')); + +CREATE TABLE tbl7 ( + a int4, + primary key (a asc) +) SPLIT AT VALUES (('a'), ('b'), ('c')); + +CREATE TABLE tbl8 ( + a text, + primary key (a asc) +) SPLIT AT VALUES ((100), (1000), (10000)); + +CREATE TABLE tbl9 ( + a int4, + primary key (a hash) +) SPLIT AT VALUES ((100), (1000), (10000)); + +CREATE TEMPORARY TABLE tbl10 ( + a int4 primary key +) SPLIT INTO 20 TABLETS; + +CREATE TABLE tbl11 ( + a int4, + b int4, + c int4, + primary key (a asc, b desc) +) SPLIT AT VALUES ((-7, 1), (0, 0), (23, 4)); + +CREATE TABLE tbl12 ( + a int4, + b text, + primary key (b desc) +) SPLIT AT VALUES (('bienvenidos'), ('goodbye'), ('hello'), ('hola')); + +CREATE TABLE tbl13 ( + a text, + b date, + c time +) SPLIT INTO 9 TABLETS; + +CREATE TABLE tbl14 ( + a int4, + primary key (a asc) +) SPLIT AT VALUES ((MINVALUE), (0), (MAXVALUE)); + +CREATE TABLE tbl15 ( + a int4, + b int4, + c int4, + primary key (a asc, b desc) +) SPLIT AT VALUES ((-10), (0, 0), (23, 4), (50)); + +-- This is invalid because split rows do not honor column b's ordering +CREATE TABLE tbl16( + a int, + b int, + primary key(a asc, b asc) +) SPLIT AT VALUES((100), (200, 5), (200)); + +-- This is invalid because split rows do not honor column b's ordering +CREATE TABLE tbl16( + a int, + b int, + primary key(a asc, b asc nulls first) +) SPLIT AT VALUES((100), (200, 5), (200)); + +-- This is invalid because split rows do not honor column b's ordering +CREATE TABLE tbl16( + a int, + b int, + primary key(a asc, b asc nulls last) +) SPLIT AT VALUES((100), (200, 5), (200)); + +CREATE TABLE tbl16( + a int, + b int, + primary key(a asc, b desc) +) SPLIT AT VALUES((100), (200), (200, 5)); + +CREATE TABLE tbl17( + a int, + b int, + primary key(a asc, b desc nulls first) +) SPLIT AT VALUES((100), (200), (200, 5)); + +CREATE TABLE tbl18( + a int, + b int, + primary key(a asc, b desc nulls last) +) SPLIT AT VALUES((100), (200), (200, 5)); + +-- This is invalid because we cannot have duplicate split rows +CREATE TABLE tbl19( + a int, + b int, + primary key(a asc, b desc nulls last) +) SPLIT AT VALUES((100), (200, 5), (200, 5)); + +CREATE TABLE tbl20 ( + a int4, + primary key (a hash) +); + +CREATE INDEX ind20 on tbl20(a) SPLIT AT VALUES ((100)); + +CREATE TABLE tbl21 ( + a int4, + b int4, + primary key (a hash) +); + +CREATE INDEX ind21 on tbl21(b) SPLIT AT VALUES ((100)); + +-- Test ordering on splitted tables +CREATE TABLE ordered_asc( + k INT, + PRIMARY KEY(k ASC) +) SPLIT AT VALUES((10), (20), (30)); +INSERT INTO ordered_asc VALUES + (5), (6), (16), (15), (25), (26), (36), (35), (46), (10), (20), (30); +EXPLAIN (COSTS OFF) SELECT * FROM ordered_asc ORDER BY k ASC; +SELECT * FROM ordered_asc ORDER BY k ASC; +EXPLAIN (COSTS OFF) SELECT * FROM ordered_asc ORDER BY k DESC; +SELECT * FROM ordered_asc ORDER BY k DESC; +EXPLAIN (COSTS OFF) SELECT k FROM ordered_asc WHERE k > 10 and k < 40 ORDER BY k DESC; +SELECT k FROM ordered_asc WHERE k > 10 and k < 40 ORDER BY k DESC; + +CREATE TABLE ordered_desc( + k INT, + PRIMARY KEY(k DESC) +) SPLIT AT VALUES((30), (20), (10)); +INSERT INTO ordered_desc VALUES + (5), (6), (16), (15), (25), (26), (36), (35), (46), (10), (20), (30); +EXPLAIN (COSTS OFF) SELECT * FROM ordered_desc ORDER BY k ASC; +SELECT * FROM ordered_desc ORDER BY k ASC; +EXPLAIN (COSTS OFF) SELECT * FROM ordered_desc ORDER BY k DESC; +SELECT * FROM ordered_desc ORDER BY k DESC; +EXPLAIN (COSTS OFF) SELECT k FROM ordered_desc WHERE k > 10 and k < 40 ORDER BY k ASC; +SELECT k FROM ordered_desc WHERE k > 10 and k < 40 ORDER BY k ASC; + +-- Test create ... with (table_oid = x) +set yb_enable_create_with_table_oid=1; +create table with_invalid_table_oid (a int) with (table_oid = 0); +create table with_invalid_table_oid (a int) with (table_oid = -1); +create table with_invalid_table_oid (a int) with (table_oid = 123); +create table with_invalid_table_oid (a int) with (table_oid = 4294967296); +create table with_invalid_table_oid (a int) with (table_oid = 'test'); + +create table with_table_oid (a int) with (table_oid = 4294967295); +select relname, oid from pg_class where relname = 'with_table_oid'; + +create table with_table_oid_duplicate (a int) with (table_oid = 4294967295); + +-- Test temp tables with (table_oid = x) +begin; +create temp table with_table_oid_temp (a int) with (table_oid = 1234568); +select relname, oid from pg_class where relname = 'with_table_oid_temp'; +end; +-- Creating another temp table with the same oid should fail +create temp table with_table_oid_temp_3 (a int) with (table_oid = 1234568); +-- Creating a regular table with the same oid should fail +create table with_table_oid_2 (a int) with (table_oid = 1234568); +select relname, oid from pg_class where relname = 'with_table_oid_2'; + +-- Test with session variable off +set yb_enable_create_with_table_oid=0; +create table with_table_oid_variable_false (a int) with (table_oid = 55555); +RESET yb_enable_create_with_table_oid; + +-- CREATE TABLE with implicit UNIQUE INDEX shouldn't spout a notice about it +-- being nonconcurrent. +BEGIN; +CREATE TABLE tab_with_unique (i int, UNIQUE (i)); +COMMIT; + +-- Test temp table/view are automatically dropped. +-- TODO: Remove DISCARD TEMP after the fix of #14519 +DISCARD TEMP; +\c yugabyte +create temporary table temp_tab(a int); +create temporary view temp_view as select * from temp_tab; +select count(*) from pg_class where relname = 'temp_tab'; +select count(*) from pg_class where relname = 'temp_view'; +\c yugabyte +-- Wait some time for the last session to finish dropping temp table/view automatically. +select pg_sleep(5); +select count(*) from pg_class where relname = 'temp_tab'; +select count(*) from pg_class where relname = 'temp_view'; + +-- Test EXPLAIN ANALYZE + CREATE TABLE AS. Use EXECUTE to hide the output since it won't be stable. +DO $$ +BEGIN + EXECUTE 'EXPLAIN ANALYZE CREATE TABLE tbl_as_1 AS SELECT 1'; +END$$; + +SELECT * FROM tbl_as_1; + +-- Test EXPLAIN ANALYZE on a table containing secondary index with a wide column. +-- Use EXECUTE to hide the output since it won't be stable. +CREATE TABLE wide_table (id INT, data TEXT); +CREATE INDEX wide_table_idx ON wide_table(id, data); +INSERT INTO wide_table (id, data) VALUES (10, REPEAT('1234567890', 1000000)); +DO $$ +BEGIN + EXECUTE 'EXPLAIN ANALYZE SELECT data FROM wide_table WHERE id = 10'; +END$$; + +DROP TABLE wide_table; + +-- Apply the same check for varchar column +CREATE TABLE wide_table (id INT, data VARCHAR); +CREATE INDEX wide_table_idx ON wide_table(id, data); +INSERT INTO wide_table (id, data) VALUES (10, REPEAT('1234567890', 1000000)); +DO $$ +BEGIN + EXECUTE 'EXPLAIN ANALYZE SELECT data FROM wide_table WHERE id = 10'; +END$$; + +DROP TABLE wide_table; + +-- Test CREATE TABLE PARTITION OF with CONSTRAINT .. PRIMARY KEY +CREATE TABLE t ( + id uuid NOT NULL, + geo_partition character varying NOT NULL +) +PARTITION BY LIST (geo_partition); +CREATE TABLE t1 PARTITION OF t ( + CONSTRAINT t1_pkey PRIMARY KEY ((id) HASH) +) +FOR VALUES IN ('1'); + +-- Test CREATE TABLE with 1000 columns +CREATE TABLE table_1000_cols (id INT primary key, col1 TEXT, col2 TEXT, col3 TEXT, col4 TEXT, col5 TEXT, col6 TEXT, col7 TEXT, col8 TEXT, col9 TEXT, col10 TEXT, col11 TEXT, col12 TEXT, col13 TEXT, col14 TEXT, col15 TEXT, col16 TEXT, col17 TEXT, col18 TEXT, col19 TEXT, col20 TEXT, col21 TEXT, col22 TEXT, col23 TEXT, col24 TEXT, col25 TEXT, col26 TEXT, col27 TEXT, col28 TEXT, col29 TEXT, col30 TEXT, col31 TEXT, col32 TEXT, col33 TEXT, col34 TEXT, col35 TEXT, col36 TEXT, col37 TEXT, col38 TEXT, col39 TEXT, col40 TEXT, col41 TEXT, col42 TEXT, col43 TEXT, col44 TEXT, col45 TEXT, col46 TEXT, col47 TEXT, col48 TEXT, col49 TEXT, col50 TEXT, col51 TEXT, col52 TEXT, col53 TEXT, col54 TEXT, col55 TEXT, col56 TEXT, col57 TEXT, col58 TEXT, col59 TEXT, col60 TEXT, col61 TEXT, col62 TEXT, col63 TEXT, col64 TEXT, col65 TEXT, col66 TEXT, col67 TEXT, col68 TEXT, col69 TEXT, col70 TEXT, col71 TEXT, col72 TEXT, col73 TEXT, col74 TEXT, col75 TEXT, col76 TEXT, col77 TEXT, col78 TEXT, col79 TEXT, col80 TEXT, col81 TEXT, col82 TEXT, col83 TEXT, col84 TEXT, col85 TEXT, col86 TEXT, col87 TEXT, col88 TEXT, col89 TEXT, col90 TEXT, col91 TEXT, col92 TEXT, col93 TEXT, col94 TEXT, col95 TEXT, col96 TEXT, col97 TEXT, col98 TEXT, col99 TEXT, col100 TEXT, col101 TEXT, col102 TEXT, col103 TEXT, col104 TEXT, col105 TEXT, col106 TEXT, col107 TEXT, col108 TEXT, col109 TEXT, col110 TEXT, col111 TEXT, col112 TEXT, col113 TEXT, col114 TEXT, col115 TEXT, col116 TEXT, col117 TEXT, col118 TEXT, col119 TEXT, col120 TEXT, col121 TEXT, col122 TEXT, col123 TEXT, col124 TEXT, col125 TEXT, col126 TEXT, col127 TEXT, col128 TEXT, col129 TEXT, col130 TEXT, col131 TEXT, col132 TEXT, col133 TEXT, col134 TEXT, col135 TEXT, col136 TEXT, col137 TEXT, col138 TEXT, col139 TEXT, col140 TEXT, col141 TEXT, col142 TEXT, col143 TEXT, col144 TEXT, col145 TEXT, col146 TEXT, col147 TEXT, col148 TEXT, col149 TEXT, col150 TEXT, col151 TEXT, col152 TEXT, col153 TEXT, col154 TEXT, col155 TEXT, col156 TEXT, col157 TEXT, col158 TEXT, col159 TEXT, col160 TEXT, col161 TEXT, col162 TEXT, col163 TEXT, col164 TEXT, col165 TEXT, col166 TEXT, col167 TEXT, col168 TEXT, col169 TEXT, col170 TEXT, col171 TEXT, col172 TEXT, col173 TEXT, col174 TEXT, col175 TEXT, col176 TEXT, col177 TEXT, col178 TEXT, col179 TEXT, col180 TEXT, col181 TEXT, col182 TEXT, col183 TEXT, col184 TEXT, col185 TEXT, col186 TEXT, col187 TEXT, col188 TEXT, col189 TEXT, col190 TEXT, col191 TEXT, col192 TEXT, col193 TEXT, col194 TEXT, col195 TEXT, col196 TEXT, col197 TEXT, col198 TEXT, col199 TEXT, col200 TEXT, col201 TEXT, col202 TEXT, col203 TEXT, col204 TEXT, col205 TEXT, col206 TEXT, col207 TEXT, col208 TEXT, col209 TEXT, col210 TEXT, col211 TEXT, col212 TEXT, col213 TEXT, col214 TEXT, col215 TEXT, col216 TEXT, col217 TEXT, col218 TEXT, col219 TEXT, col220 TEXT, col221 TEXT, col222 TEXT, col223 TEXT, col224 TEXT, col225 TEXT, col226 TEXT, col227 TEXT, col228 TEXT, col229 TEXT, col230 TEXT, col231 TEXT, col232 TEXT, col233 TEXT, col234 TEXT, col235 TEXT, col236 TEXT, col237 TEXT, col238 TEXT, col239 TEXT, col240 TEXT, col241 TEXT, col242 TEXT, col243 TEXT, col244 TEXT, col245 TEXT, col246 TEXT, col247 TEXT, col248 TEXT, col249 TEXT, col250 TEXT, col251 TEXT, col252 TEXT, col253 TEXT, col254 TEXT, col255 TEXT, col256 TEXT, col257 TEXT, col258 TEXT, col259 TEXT, col260 TEXT, col261 TEXT, col262 TEXT, col263 TEXT, col264 TEXT, col265 TEXT, col266 TEXT, col267 TEXT, col268 TEXT, col269 TEXT, col270 TEXT, col271 TEXT, col272 TEXT, col273 TEXT, col274 TEXT, col275 TEXT, col276 TEXT, col277 TEXT, col278 TEXT, col279 TEXT, col280 TEXT, col281 TEXT, col282 TEXT, col283 TEXT, col284 TEXT, col285 TEXT, col286 TEXT, col287 TEXT, col288 TEXT, col289 TEXT, col290 TEXT, col291 TEXT, col292 TEXT, col293 TEXT, col294 TEXT, col295 TEXT, col296 TEXT, col297 TEXT, col298 TEXT, col299 TEXT, col300 TEXT, col301 TEXT, col302 TEXT, col303 TEXT, col304 TEXT, col305 TEXT, col306 TEXT, col307 TEXT, col308 TEXT, col309 TEXT, col310 TEXT, col311 TEXT, col312 TEXT, col313 TEXT, col314 TEXT, col315 TEXT, col316 TEXT, col317 TEXT, col318 TEXT, col319 TEXT, col320 TEXT, col321 TEXT, col322 TEXT, col323 TEXT, col324 TEXT, col325 TEXT, col326 TEXT, col327 TEXT, col328 TEXT, col329 TEXT, col330 TEXT, col331 TEXT, col332 TEXT, col333 TEXT, col334 TEXT, col335 TEXT, col336 TEXT, col337 TEXT, col338 TEXT, col339 TEXT, col340 TEXT, col341 TEXT, col342 TEXT, col343 TEXT, col344 TEXT, col345 TEXT, col346 TEXT, col347 TEXT, col348 TEXT, col349 TEXT, col350 TEXT, col351 TEXT, col352 TEXT, col353 TEXT, col354 TEXT, col355 TEXT, col356 TEXT, col357 TEXT, col358 TEXT, col359 TEXT, col360 TEXT, col361 TEXT, col362 TEXT, col363 TEXT, col364 TEXT, col365 TEXT, col366 TEXT, col367 TEXT, col368 TEXT, col369 TEXT, col370 TEXT, col371 TEXT, col372 TEXT, col373 TEXT, col374 TEXT, col375 TEXT, col376 TEXT, col377 TEXT, col378 TEXT, col379 TEXT, col380 TEXT, col381 TEXT, col382 TEXT, col383 TEXT, col384 TEXT, col385 TEXT, col386 TEXT, col387 TEXT, col388 TEXT, col389 TEXT, col390 TEXT, col391 TEXT, col392 TEXT, col393 TEXT, col394 TEXT, col395 TEXT, col396 TEXT, col397 TEXT, col398 TEXT, col399 TEXT, col400 TEXT, col401 TEXT, col402 TEXT, col403 TEXT, col404 TEXT, col405 TEXT, col406 TEXT, col407 TEXT, col408 TEXT, col409 TEXT, col410 TEXT, col411 TEXT, col412 TEXT, col413 TEXT, col414 TEXT, col415 TEXT, col416 TEXT, col417 TEXT, col418 TEXT, col419 TEXT, col420 TEXT, col421 TEXT, col422 TEXT, col423 TEXT, col424 TEXT, col425 TEXT, col426 TEXT, col427 TEXT, col428 TEXT, col429 TEXT, col430 TEXT, col431 TEXT, col432 TEXT, col433 TEXT, col434 TEXT, col435 TEXT, col436 TEXT, col437 TEXT, col438 TEXT, col439 TEXT, col440 TEXT, col441 TEXT, col442 TEXT, col443 TEXT, col444 TEXT, col445 TEXT, col446 TEXT, col447 TEXT, col448 TEXT, col449 TEXT, col450 TEXT, col451 TEXT, col452 TEXT, col453 TEXT, col454 TEXT, col455 TEXT, col456 TEXT, col457 TEXT, col458 TEXT, col459 TEXT, col460 TEXT, col461 TEXT, col462 TEXT, col463 TEXT, col464 TEXT, col465 TEXT, col466 TEXT, col467 TEXT, col468 TEXT, col469 TEXT, col470 TEXT, col471 TEXT, col472 TEXT, col473 TEXT, col474 TEXT, col475 TEXT, col476 TEXT, col477 TEXT, col478 TEXT, col479 TEXT, col480 TEXT, col481 TEXT, col482 TEXT, col483 TEXT, col484 TEXT, col485 TEXT, col486 TEXT, col487 TEXT, col488 TEXT, col489 TEXT, col490 TEXT, col491 TEXT, col492 TEXT, col493 TEXT, col494 TEXT, col495 TEXT, col496 TEXT, col497 TEXT, col498 TEXT, col499 TEXT, col500 TEXT, col501 TEXT, col502 TEXT, col503 TEXT, col504 TEXT, col505 TEXT, col506 TEXT, col507 TEXT, col508 TEXT, col509 TEXT, col510 TEXT, col511 TEXT, col512 TEXT, col513 TEXT, col514 TEXT, col515 TEXT, col516 TEXT, col517 TEXT, col518 TEXT, col519 TEXT, col520 TEXT, col521 TEXT, col522 TEXT, col523 TEXT, col524 TEXT, col525 TEXT, col526 TEXT, col527 TEXT, col528 TEXT, col529 TEXT, col530 TEXT, col531 TEXT, col532 TEXT, col533 TEXT, col534 TEXT, col535 TEXT, col536 TEXT, col537 TEXT, col538 TEXT, col539 TEXT, col540 TEXT, col541 TEXT, col542 TEXT, col543 TEXT, col544 TEXT, col545 TEXT, col546 TEXT, col547 TEXT, col548 TEXT, col549 TEXT, col550 TEXT, col551 TEXT, col552 TEXT, col553 TEXT, col554 TEXT, col555 TEXT, col556 TEXT, col557 TEXT, col558 TEXT, col559 TEXT, col560 TEXT, col561 TEXT, col562 TEXT, col563 TEXT, col564 TEXT, col565 TEXT, col566 TEXT, col567 TEXT, col568 TEXT, col569 TEXT, col570 TEXT, col571 TEXT, col572 TEXT, col573 TEXT, col574 TEXT, col575 TEXT, col576 TEXT, col577 TEXT, col578 TEXT, col579 TEXT, col580 TEXT, col581 TEXT, col582 TEXT, col583 TEXT, col584 TEXT, col585 TEXT, col586 TEXT, col587 TEXT, col588 TEXT, col589 TEXT, col590 TEXT, col591 TEXT, col592 TEXT, col593 TEXT, col594 TEXT, col595 TEXT, col596 TEXT, col597 TEXT, col598 TEXT, col599 TEXT, col600 TEXT, col601 TEXT, col602 TEXT, col603 TEXT, col604 TEXT, col605 TEXT, col606 TEXT, col607 TEXT, col608 TEXT, col609 TEXT, col610 TEXT, col611 TEXT, col612 TEXT, col613 TEXT, col614 TEXT, col615 TEXT, col616 TEXT, col617 TEXT, col618 TEXT, col619 TEXT, col620 TEXT, col621 TEXT, col622 TEXT, col623 TEXT, col624 TEXT, col625 TEXT, col626 TEXT, col627 TEXT, col628 TEXT, col629 TEXT, col630 TEXT, col631 TEXT, col632 TEXT, col633 TEXT, col634 TEXT, col635 TEXT, col636 TEXT, col637 TEXT, col638 TEXT, col639 TEXT, col640 TEXT, col641 TEXT, col642 TEXT, col643 TEXT, col644 TEXT, col645 TEXT, col646 TEXT, col647 TEXT, col648 TEXT, col649 TEXT, col650 TEXT, col651 TEXT, col652 TEXT, col653 TEXT, col654 TEXT, col655 TEXT, col656 TEXT, col657 TEXT, col658 TEXT, col659 TEXT, col660 TEXT, col661 TEXT, col662 TEXT, col663 TEXT, col664 TEXT, col665 TEXT, col666 TEXT, col667 TEXT, col668 TEXT, col669 TEXT, col670 TEXT, col671 TEXT, col672 TEXT, col673 TEXT, col674 TEXT, col675 TEXT, col676 TEXT, col677 TEXT, col678 TEXT, col679 TEXT, col680 TEXT, col681 TEXT, col682 TEXT, col683 TEXT, col684 TEXT, col685 TEXT, col686 TEXT, col687 TEXT, col688 TEXT, col689 TEXT, col690 TEXT, col691 TEXT, col692 TEXT, col693 TEXT, col694 TEXT, col695 TEXT, col696 TEXT, col697 TEXT, col698 TEXT, col699 TEXT, col700 TEXT, col701 TEXT, col702 TEXT, col703 TEXT, col704 TEXT, col705 TEXT, col706 TEXT, col707 TEXT, col708 TEXT, col709 TEXT, col710 TEXT, col711 TEXT, col712 TEXT, col713 TEXT, col714 TEXT, col715 TEXT, col716 TEXT, col717 TEXT, col718 TEXT, col719 TEXT, col720 TEXT, col721 TEXT, col722 TEXT, col723 TEXT, col724 TEXT, col725 TEXT, col726 TEXT, col727 TEXT, col728 TEXT, col729 TEXT, col730 TEXT, col731 TEXT, col732 TEXT, col733 TEXT, col734 TEXT, col735 TEXT, col736 TEXT, col737 TEXT, col738 TEXT, col739 TEXT, col740 TEXT, col741 TEXT, col742 TEXT, col743 TEXT, col744 TEXT, col745 TEXT, col746 TEXT, col747 TEXT, col748 TEXT, col749 TEXT, col750 TEXT, col751 TEXT, col752 TEXT, col753 TEXT, col754 TEXT, col755 TEXT, col756 TEXT, col757 TEXT, col758 TEXT, col759 TEXT, col760 TEXT, col761 TEXT, col762 TEXT, col763 TEXT, col764 TEXT, col765 TEXT, col766 TEXT, col767 TEXT, col768 TEXT, col769 TEXT, col770 TEXT, col771 TEXT, col772 TEXT, col773 TEXT, col774 TEXT, col775 TEXT, col776 TEXT, col777 TEXT, col778 TEXT, col779 TEXT, col780 TEXT, col781 TEXT, col782 TEXT, col783 TEXT, col784 TEXT, col785 TEXT, col786 TEXT, col787 TEXT, col788 TEXT, col789 TEXT, col790 TEXT, col791 TEXT, col792 TEXT, col793 TEXT, col794 TEXT, col795 TEXT, col796 TEXT, col797 TEXT, col798 TEXT, col799 TEXT, col800 TEXT, col801 TEXT, col802 TEXT, col803 TEXT, col804 TEXT, col805 TEXT, col806 TEXT, col807 TEXT, col808 TEXT, col809 TEXT, col810 TEXT, col811 TEXT, col812 TEXT, col813 TEXT, col814 TEXT, col815 TEXT, col816 TEXT, col817 TEXT, col818 TEXT, col819 TEXT, col820 TEXT, col821 TEXT, col822 TEXT, col823 TEXT, col824 TEXT, col825 TEXT, col826 TEXT, col827 TEXT, col828 TEXT, col829 TEXT, col830 TEXT, col831 TEXT, col832 TEXT, col833 TEXT, col834 TEXT, col835 TEXT, col836 TEXT, col837 TEXT, col838 TEXT, col839 TEXT, col840 TEXT, col841 TEXT, col842 TEXT, col843 TEXT, col844 TEXT, col845 TEXT, col846 TEXT, col847 TEXT, col848 TEXT, col849 TEXT, col850 TEXT, col851 TEXT, col852 TEXT, col853 TEXT, col854 TEXT, col855 TEXT, col856 TEXT, col857 TEXT, col858 TEXT, col859 TEXT, col860 TEXT, col861 TEXT, col862 TEXT, col863 TEXT, col864 TEXT, col865 TEXT, col866 TEXT, col867 TEXT, col868 TEXT, col869 TEXT, col870 TEXT, col871 TEXT, col872 TEXT, col873 TEXT, col874 TEXT, col875 TEXT, col876 TEXT, col877 TEXT, col878 TEXT, col879 TEXT, col880 TEXT, col881 TEXT, col882 TEXT, col883 TEXT, col884 TEXT, col885 TEXT, col886 TEXT, col887 TEXT, col888 TEXT, col889 TEXT, col890 TEXT, col891 TEXT, col892 TEXT, col893 TEXT, col894 TEXT, col895 TEXT, col896 TEXT, col897 TEXT, col898 TEXT, col899 TEXT, col900 TEXT, col901 TEXT, col902 TEXT, col903 TEXT, col904 TEXT, col905 TEXT, col906 TEXT, col907 TEXT, col908 TEXT, col909 TEXT, col910 TEXT, col911 TEXT, col912 TEXT, col913 TEXT, col914 TEXT, col915 TEXT, col916 TEXT, col917 TEXT, col918 TEXT, col919 TEXT, col920 TEXT, col921 TEXT, col922 TEXT, col923 TEXT, col924 TEXT, col925 TEXT, col926 TEXT, col927 TEXT, col928 TEXT, col929 TEXT, col930 TEXT, col931 TEXT, col932 TEXT, col933 TEXT, col934 TEXT, col935 TEXT, col936 TEXT, col937 TEXT, col938 TEXT, col939 TEXT, col940 TEXT, col941 TEXT, col942 TEXT, col943 TEXT, col944 TEXT, col945 TEXT, col946 TEXT, col947 TEXT, col948 TEXT, col949 TEXT, col950 TEXT, col951 TEXT, col952 TEXT, col953 TEXT, col954 TEXT, col955 TEXT, col956 TEXT, col957 TEXT, col958 TEXT, col959 TEXT, col960 TEXT, col961 TEXT, col962 TEXT, col963 TEXT, col964 TEXT, col965 TEXT, col966 TEXT, col967 TEXT, col968 TEXT, col969 TEXT, col970 TEXT, col971 TEXT, col972 TEXT, col973 TEXT, col974 TEXT, col975 TEXT, col976 TEXT, col977 TEXT, col978 TEXT, col979 TEXT, col980 TEXT, col981 TEXT, col982 TEXT, col983 TEXT, col984 TEXT, col985 TEXT, col986 TEXT, col987 TEXT, col988 TEXT, col989 TEXT, col990 TEXT, col991 TEXT, col992 TEXT, col993 TEXT, col994 TEXT, col995 TEXT, col996 TEXT, col997 TEXT, col998 TEXT, col999 TEXT, col1000 TEXT); + +-- Test that LZ4 TOAST compression is enabled +SET default_toast_compression = 'lz4'; +CREATE TEMP TABLE default_compression_table ( + a TEXT +); +INSERT INTO default_compression_table VALUES (repeat('a', 10000)); +SELECT pg_column_compression(a) FROM default_compression_table; + +-- Test specifying lz4 compression when pglz is the default +SET default_toast_compression = 'pglz'; +SHOW default_toast_compression; +CREATE TEMP TABLE lz4_table ( + a TEXT COMPRESSION lz4 +); +INSERT INTO lz4_table VALUES (repeat('a', 10000)); +SELECT pg_column_compression(a) FROM lz4_table; +SHOW default_toast_compression; diff --git a/src/test/regress/sql/yb.orig.create_table_55k_secondary_index.sql b/src/test/regress/sql/yb.orig.create_table_55k_secondary_index.sql new file mode 100644 index 0000000000000..a6251ce1e9cd0 --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_table_55k_secondary_index.sql @@ -0,0 +1,47 @@ +-- +-- Table definition +-- +CREATE TABLE airports(ident TEXT, + type TEXT, + name TEXT, + elevation_ft INT, + continent TEXT, + iso_country CHAR(2), + iso_region CHAR(7), + municipality TEXT, + gps_code TEXT, + iata_code TEXT, + local_code TEXT, + coordinates TEXT, + PRIMARY KEY (ident)); + +-- Index for SELECTing ybctid of the same airport country using HASH key. +CREATE INDEX airport_type_hash_idx ON airports(type HASH, iso_country ASC, ident ASC); + +-- Index for SELECTing ybctid of the same airport type using RANGE key. +CREATE INDEX airport_type_range_idx ON airports(name ASC, type ASC, ident ASC); + +-- Index for checking correctness when using secondary index. +CREATE INDEX airport_type_region_idx ON airports((type, iso_region) HASH, ident ASC); + +-- +-- SELECT using a hash index, "airport_type_hash_idx", to collect ybctids. +-- + +-- The following select use batches of ybctid that are selected from "airport_type_hash_idx" +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE type = 'large_airport' AND iso_country > '0'; + +-- The following select use one ybctid at a time to query data. +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE ident IN + (SELECT ident FROM airports WHERE type = 'large_airport' AND iso_country > '0'); + +-- +-- SELECT using a range index, "airport_type_range_idx", to collet ybctids. +-- + +-- The following select use batches of ybctid that are selected from "airport_type_range_idx" +EXPLAIN (COSTS OFF) SELECT elevation_ft FROM airports WHERE type = 'large_airport' AND name > '0'; + +-- The following select use one ybctid at a time to query data. +EXPLAIN (COSTS OFF) SELECT elevation_ft FROM airports WHERE ident IN + (SELECT ident FROM airports WHERE type = 'large_airport' AND name > '0'); diff --git a/src/test/regress/sql/yb.orig.create_table_like.sql b/src/test/regress/sql/yb.orig.create_table_like.sql new file mode 100644 index 0000000000000..c1cfc79f5f559 --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_table_like.sql @@ -0,0 +1,214 @@ +-- Tablespace. +-- Source table is in a tablespace. +CREATE TABLESPACE testtablespace WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud1","region":"datacenter1", "zone":"rack1", "min_num_replicas":1}]}'); +CREATE TABLE tbsptest1 (id int CHECK (id > 0) PRIMARY KEY, b text UNIQUE, c int DEFAULT 0) TABLESPACE testtablespace; +-- The tablespace is never copied over to the target table. +CREATE TABLE tbsptest2 (LIKE tbsptest1 INCLUDING ALL); +\d+ tbsptest2 +-- Target table is in a tablespace. +CREATE TABLE tbsptest3 (LIKE tbsptest2 INCLUDING ALL) TABLESPACE testtablespace; +\d+ tbsptest3 +DROP TABLE tbsptest3, tbsptest2, tbsptest1 CASCADE; +DROP TABLESPACE testtablespace; + +-- Test variations of primary key. +CREATE TABLE testpk(h1 int, h2 text, d text, a int, value text, PRIMARY KEY ((h1, h2) HASH, d DESC, a ASC)); +CREATE TABLE testlikepk(LIKE testpk INCLUDING INDEXES); +\d+ testlikepk +CREATE TABLE testlikenopk(LIKE testpk); +\d+ testlikenopk; +DROP TABLE testpk, testlikepk, testlikenopk CASCADE; + +-- Test adding SPLIT AT syntax with copied PK. +CREATE TABLE testsplitat ( + a INT, + b TEXT, + PRIMARY KEY(a ASC, b ASC)); +CREATE TABLE testlikesplitat(LIKE testsplitat INCLUDING INDEXES) SPLIT AT VALUES((-100, 'bar'), (250, 'foo')); +CREATE INDEX ON testlikesplitat(a ASC) SPLIT AT VALUES ((10), (20)); +\d+ testlikesplitat +SELECT yb_get_range_split_clause('testlikesplitat'::regclass); +SELECT yb_get_range_split_clause('testlikesplitat_a_idx'::regclass); + +-- Test adding SPLIT INTO syntax with copied PK. +CREATE TABLE testsplitinto(a INT, b text, PRIMARY KEY((a, b) HASH)); +CREATE TABLE testlikesplitinto(LIKE testsplitinto INCLUDING INDEXES) SPLIT INTO 2 TABLETS; +CREATE INDEX ON testlikesplitinto(a) SPLIT INTO 5 TABLETS; +\d+ testlikesplitinto +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('testlikesplitinto'::regclass); +SELECT num_tablets, num_hash_key_columns + FROM yb_table_properties('testlikesplitinto_a_idx'::regclass); + +-- Split info is not copied. +CREATE TABLE neg_splitat (LIKE testlikesplitat INCLUDING ALL); +SELECT yb_get_range_split_clause('neg_splitat'::regclass); +SELECT yb_get_range_split_clause('neg_splitat_a_idx'::regclass); +CREATE TABLE neg_splitinto (LIKE testlikesplitinto INCLUDING ALL); +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('neg_splitinto'::regclass); +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('neg_splitinto_a_idx'::regclass); +DROP TABLE testsplitat, testlikesplitat, testsplitinto, testlikesplitinto, neg_splitat, neg_splitinto CASCADE; + +-- Test variations of unique key index. +CREATE TABLE testunique(h1 int, h2 text, d text, a int, value text); +CREATE UNIQUE INDEX hashidx ON testunique using lsm (h1, h2) INCLUDE (a); +CREATE UNIQUE INDEX rangeidx ON testunique using lsm (d DESC, a) INCLUDE (h1); +CREATE UNIQUE INDEX hashrangeidx ON testunique using lsm ((h1, h2) HASH, a, d DESC) INCLUDE (value); + +CREATE TABLE testlikeunique(LIKE testunique INCLUDING INDEXES); +\d+ testlikeunique +DROP TABLE testunique; + +CREATE TABLE liketest1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text DEFAULT 'abc'); +CREATE INDEX liketest1_b ON liketest1 (b); +CREATE INDEX liketest1_ab ON liketest1 ((a || b)); +COMMENT ON COLUMN liketest1.a IS 'A'; +COMMENT ON COLUMN liketest1.b IS 'B'; +COMMENT ON CONSTRAINT liketest1_a_check ON liketest1 IS 'a_check'; +COMMENT ON INDEX liketest1_pkey IS 'index pkey'; +COMMENT ON INDEX liketest1_b IS 'index b'; + +CREATE TABLE liketest2 (c text CHECK (length(c) > 2), d text, PRIMARY KEY(c ASC)); +CREATE INDEX liketest2_d ON liketest2 (d); +COMMENT ON COLUMN liketest2.c IS 'C'; +COMMENT ON COLUMN liketest2.d IS 'D'; +COMMENT ON CONSTRAINT liketest2_c_check ON liketest2 IS 'c_check'; +COMMENT ON INDEX liketest2_pkey IS 'index pkey'; +COMMENT ON INDEX liketest2_d IS 'index c'; + +-- Test INCLUDING COMMENTS. +CREATE OR REPLACE FUNCTION get_comments(in text) RETURNS TABLE (object_type text, obj_name name, comments text) +AS $$ +SELECT 'column' AS object_type, column_name AS obj_name, col_description(table_name::regclass::oid, ordinal_position) AS comments FROM information_schema.columns WHERE table_name=$1 +UNION +SELECT 'index' AS object_type, relname AS obj_name, obj_description(oid) AS comments FROM pg_class WHERE oid IN (SELECT indexrelid FROM pg_index WHERE indrelid=$1::regclass::oid) +UNION +SELECT 'constraint' AS object_type, conname AS obj_name, obj_description(oid) AS comments FROM pg_constraint WHERE conrelid=$1::regclass::oid +ORDER BY obj_name, object_type +$$ LANGUAGE SQL; + +-- Without specifying INCLUDING COMMENTS, comments are not copied. +CREATE TABLE comments1 (LIKE liketest1 INCLUDING INDEXES, LIKE liketest2 INCLUDING CONSTRAINTS); +SELECT * FROM get_comments('comments1'); +-- Comments are copied over if INCLUDING COMMENTS/INCLUDING ALL is specified. +CREATE TABLE comments2(LIKE liketest1 INCLUDING INDEXES INCLUDING COMMENTS, LIKE liketest2 INCLUDING CONSTRAINTS INCLUDING COMMENTS); +SELECT * FROM get_comments('comments2'); +CREATE TABLE comments3(LIKE liketest1 INCLUDING ALL); +SELECT * FROM get_comments('comments3'); +DROP TABLE comments1, comments2, comments3 CASCADE; +DROP FUNCTION get_comments; + +-- Test INCLUDING STATISTICS. +CREATE STATISTICS liketest1_stat ON a,b FROM liketest1; +CREATE TABLE neg_stats_test (LIKE liketest1); +\d+ neg_stats_test; +CREATE TABLE stats_test1 (LIKE liketest1 INCLUDING STATISTICS); +\d+ stats_test1 +CREATE TABLE stats_test2 (LIKE liketest1 INCLUDING ALL); +\d+ stats_test2 +DROP TABLE neg_stats_test, stats_test1, stats_test2 CASCADE; +DROP STATISTICS liketest1_stat; + +-- Test Tablegroup. +CREATE TABLEGROUP tgroup1; +-- Create table using LIKE clause in a tablegroup. +CREATE TABLE testtgroup1 (LIKE liketest1 INCLUDING DEFAULTS, LIKE liketest2 INCLUDING CONSTRAINTS) TABLEGROUP tgroup1; +\d+ testtgroup1 +-- Fail because liketest2 has a hash-partitioned index. +CREATE TABLE testtgroup2 (LIKE liketest2 INCLUDING ALL) TABLEGROUP tgroup1; +DROP INDEX liketest2_d; +CREATE INDEX liketest2_d ON liketest2 (d ASC); +-- Passes now. +CREATE TABLE testtgroup2 (LIKE liketest2 INCLUDING ALL) TABLEGROUP tgroup1; +\d+ testtgroup2 + +-- Create table using LIKE clause from a table in a tablegroup. The tablegroup clause is not copied over. +CREATE TABLE neg_tgroup (LIKE testtgroup2 INCLUDING ALL); +\d+ neg_tgroup; + +-- Cleanup +DROP TABLE liketest1, liketest2, testtgroup1, testtgroup2, neg_tgroup CASCADE; +DROP TABLEGROUP tgroup1 CASCADE; + +-- Colocation +CREATE DATABASE colocation_test colocation = true; +\c colocation_test +CREATE TABLE colocate_source (k int, v1 text DEFAULT 'hello world', v2 int CHECK (v2 > 0), v3 float, PRIMARY KEY (k ASC)); +CREATE TABLE uncolocated_target_test (LIKE colocate_source INCLUDING ALL) with (colocation = false); +\d uncolocated_target_test +CREATE TABLE colocated_target_test (LIKE colocate_source INCLUDING ALL); +\d colocated_target_test +-- cannot colocate hash-partitioned table. +CREATE TABLE hash_k (i int primary key) with (colocation = false); +CREATE TABLE hash_k_fail_test (LIKE hash_k INCLUDING ALL); +CREATE TABLE hash_k_test (LIKE hash_k INCLUDING ALL) with (colocation = false); +\d hash_k_test +\c yugabyte +DROP DATABASE colocation_test; + +-- When using LIKE clause on a source table with duplicate indexes, the target table has duplicate +-- indexes as well. +CREATE TABLE test_dupe_idx (hashkey int, asckey text, desckey text); +CREATE INDEX h1 ON test_dupe_idx(hashkey); +CREATE INDEX h2 ON test_dupe_idx(hashkey); +CREATE INDEX a1 ON test_dupe_idx(asckey ASC); +CREATE INDEX a2 ON test_dupe_idx(asckey ASC); +CREATE INDEX d1 ON test_dupe_idx(desckey DESC); +CREATE INDEX d2 ON test_dupe_idx(desckey DESC); +CREATE TABLE test_dupe_idx_like (LIKE test_dupe_idx INCLUDING ALL); +\d test_dupe_idx_like +DROP TABLE test_dupe_idx; + +-- LIKE clause with temp tables. +-- Test using LIKE clause where the source table is a temp table. +CREATE TEMP TABLE temptest (k int PRIMARY KEY, v1 text DEFAULT 'hello world', v2 int CHECK (v2 > 0), v3 float UNIQUE); +CREATE INDEX ON temptest(v1); +CREATE TABLE liketemptest (LIKE temptest INCLUDING ALL); +\d liketemptest +DROP TABLE liketemptest; +-- Test using LIKE clause where source and target tables are temp tables. +CREATE TEMP TABLE liketemptest (LIKE temptest INCLUDING ALL); +-- \d liketemptest has unstable output as the temporary schemaname contains +-- the tserver uuid. Use regexp_replace to change it to pg_temp_x so that the +-- result is stable. +select current_setting('data_directory') || 'describe.out' as desc_output_file +\gset +\o :desc_output_file +\d liketemptest +\o +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); + +-- Test using LIKE clause where the target table is a temp table. +CREATE TEMP TABLE gin_test (a int[]); +CREATE INDEX ON gin_test USING GIN (a); +CREATE TABLE gin_like_test (LIKE gin_test INCLUDING ALL); +\d gin_like_test +CREATE INDEX ON gin_test (a); +CREATE TABLE gin_like_test_idx (LIKE gin_test INCLUDING ALL); +DROP TABLE liketemptest, temptest, gin_test, gin_like_test; + +-- Source is a VIEW. +CREATE TABLE test_table(k INT PRIMARY KEY, v INT); +CREATE VIEW test_view AS SELECT k FROM test_table WHERE v = 10; +CREATE TABLE like_view (LIKE test_view INCLUDING ALL); +\d like_view +DROP VIEW test_view; +DROP TABLE test_table, like_view CASCADE; + +-- Source is a FOREIGN TABLE. +CREATE EXTENSION file_fdw; +CREATE SERVER s1 FOREIGN DATA WRAPPER file_fdw; +CREATE FOREIGN TABLE test_foreign (id int CHECK (id > 0), b int DEFAULT 0) SERVER s1 OPTIONS ( filename 'foo'); +CREATE TABLE test_foreign_like (LIKE test_foreign INCLUDING ALL); +\d test_foreign_like +DROP FOREIGN TABLE test_foreign; +DROP SERVER s1; +DROP EXTENSION file_fdw CASCADE; +DROP TABLE test_foreign_like; + +-- Source is a COMPOSITE TYPE. +CREATE TYPE type_pair AS (f1 INT, f2 INT); +CREATE TYPE type_pair_with_int AS (f1 type_pair, f2 int); +CREATE TABLE test_like_type (LIKE type_pair_with_int); +\d test_like_type +DROP TABLE test_like_type; +DROP TYPE type_pair_with_int, type_pair CASCADE; diff --git a/src/test/regress/sql/yb.orig.create_table_scattering_keys.sql b/src/test/regress/sql/yb.orig.create_table_scattering_keys.sql new file mode 100644 index 0000000000000..927ba1d974a98 --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_table_scattering_keys.sql @@ -0,0 +1,20 @@ +-- +-- Table definition +-- Testing PrimaryKey and IndexKey whose columns are not in the same order with the table. +-- +CREATE TABLE airports(ident TEXT, + type TEXT, + name TEXT, + elevation_ft INT, + continent TEXT, + iso_country TEXT, + iso_region TEXT, + municipality TEXT, + gps_code TEXT, + iata_code TEXT, + local_code TEXT, + coordinates TEXT, + PRIMARY KEY (iso_region, name, type, ident)); + +CREATE INDEX airports_scatter_idx ON airports((iso_region, type) HASH, coordinates, ident, name) + INCLUDE (gps_code); diff --git a/src/test/regress/sql/yb.orig.create_type.sql b/src/test/regress/sql/yb.orig.create_type.sql new file mode 100644 index 0000000000000..48afda449d165 --- /dev/null +++ b/src/test/regress/sql/yb.orig.create_type.sql @@ -0,0 +1,193 @@ +-- +-- Create composite type +-- +CREATE TYPE composite_type AS ( + i INT, + v VARCHAR +); +\dT+ composite_type; + +SELECT typname FROM pg_type WHERE typname LIKE '%composite_type'; + +SELECT (1, 'a')::composite_type; + +-- Following should error +CREATE TABLE composite_table_indexed (a composite_type PRIMARY KEY, b BOOLEAN); +CREATE TABLE composite_table (a composite_type, b BOOLEAN); +INSERT INTO composite_table (a, b) VALUES ((2, 'bb')::composite_type, TRUE); +INSERT INTO composite_table (a) VALUES ((3, 'ccc')::composite_type); +INSERT INTO composite_table (b) VALUES (FALSE); +SELECT * FROM composite_table ORDER BY a; + +-- +-- Create enum type +-- +CREATE TYPE enum_type AS ENUM ('baa', 'caw', 'moo'); +\dT+ enum_type; + +SELECT typname FROM pg_type WHERE typname LIKE '%enum_type'; + +SELECT 'moo'::enum_type; + +CREATE TABLE enum_table (a enum_type, b BOOLEAN); +INSERT INTO enum_table (a) VALUES ('baa'); +INSERT INTO enum_table (a, b) VALUES ('caw', FALSE); +INSERT INTO enum_table (b) VALUES (TRUE); +SELECT * FROM enum_table ORDER BY a; + +-- +-- Create range type +-- +CREATE TYPE range_type AS RANGE ( + subtype = float +); +\dT+ range_type; + +SELECT typname FROM pg_type WHERE typname LIKE '%range_type'; + +SELECT '[1.111, 2.222]'::range_type; + +-- Following should error +CREATE TABLE range_table_indexed (a range_type PRIMARY KEY, b BOOLEAN); +CREATE TABLE range_table (a range_type, b BOOLEAN); +INSERT INTO range_table (a) VALUES ('[3.0, 4.8]'); +-- Following should error +INSERT INTO range_table (a) VALUES ('[6.0, 3.8]'); +INSERT INTO range_table (b) VALUES (TRUE); +INSERT INTO range_table (a, b) VALUES ('[1.4, 3.3]', FALSE); +SELECT * FROM range_table ORDER BY a; + +-- Borrowed from https://www.postgresql.org/docs/11/rangetypes.html#RANGETYPES-BUILTIN +-- Containment +SELECT int4range(10, 20) @> 3; + +-- Overlaps +SELECT numrange(11.1, 22.2) && numrange(20.0, 30.0); + +-- Extract the upper bound +SELECT upper(int8range(15, 25)); + +-- Compute the intersection +SELECT int4range(10, 20) * int4range(15, 25); + +-- Is the range empty? +SELECT isempty(numrange(1, 5)); + +-- +-- Create base type +-- +-- Taken from https://stackoverflow.com/questions/45188301/ +CREATE TYPE base_type; +CREATE FUNCTION base_type_in(cstring) RETURNS base_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int2in'; +CREATE FUNCTION base_type_out(base_type) RETURNS cstring + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int2out'; +CREATE FUNCTION base_type_recv(internal) RETURNS base_type + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int2recv'; +CREATE FUNCTION base_type_send(base_type) RETURNS bytea + LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'int2send'; +CREATE TYPE base_type ( + INPUT = base_type_in, + OUTPUT = base_type_out, + RECEIVE = base_type_recv, + SEND = base_type_send, + LIKE = smallint, + CATEGORY = 'N', + PREFERRED = FALSE, + DELIMITER = ',', + COLLATABLE = FALSE +); +\dT+ base_type; + +SELECT typname FROM pg_type WHERE typname LIKE '%base_type'; + +SELECT '32767'::base_type; + +-- Following should error +SELECT '32768'::base_type; +-- Following should error +CREATE TABLE ints_indexed (a base_type PRIMARY KEY, b BOOLEAN); +CREATE TABLE base_table (a base_type, b BOOLEAN); +INSERT INTO base_table (b) VALUES (FALSE); +INSERT INTO base_table (a, b) VALUES ('1'::base_type, TRUE); +INSERT INTO base_table (a) VALUES ('22'::base_type); +SELECT * FROM base_table ORDER BY a; +SELECT * FROM base_table ORDER BY CAST(a AS text); + +-- +-- Summary +-- +SELECT typname FROM pg_type WHERE oid > 16000 ORDER BY oid DESC LIMIT 18; + +-- +-- Drop composite type +-- +-- Following should error +DROP TYPE composite_type; +DROP TYPE composite_type CASCADE; +\dT+ composite_type; + +SELECT oid, typname FROM pg_type WHERE typname LIKE '%composite_type'; + +-- Following should error +SELECT (4, 'dddd')::composite_type; +-- Following should error +SELECT * FROM composite_table ORDER BY a; +SELECT * FROM composite_table ORDER BY b; + +-- +-- Drop enum type +-- +DROP TYPE enum_type; +DROP TYPE enum_type CASCADE; +\dT+ enum_type; + +SELECT oid, typname FROM pg_type WHERE typname LIKE '%enum_type'; + +-- Following should error +SELECT 'caw'::enum_type; +-- Following should error +SELECT * FROM enum_table ORDER BY a; +SELECT * FROM enum_table ORDER BY b; + +-- +-- Drop range type +-- +DROP TYPE range_type; +DROP TYPE range_type CASCADE; +\dT+ range_type; + +SELECT oid, typname FROM pg_type WHERE typname LIKE '%range_type'; + +-- Following should error +SELECT '[1.1, 2.2]'::range_type; +-- Following should error +SELECT * FROM range_table ORDER BY a; +SELECT * FROM range_table ORDER BY b; + +-- +-- Drop base type +-- +\set VERBOSITY terse +DROP TYPE base_type; +DROP TYPE base_type CASCADE; +\set VERBOSITY default +\dT+ base_type; + +SELECT oid, typname FROM pg_type WHERE typname LIKE '%base_type'; + +-- Following should error +SELECT '333'::base_type; +-- Following should error +SELECT * FROM base_table ORDER BY CAST(a AS text); +SELECT * FROM base_table ORDER BY b; + +-- +-- Summary +-- +SELECT typname FROM pg_type WHERE oid > 16000 ORDER BY oid DESC LIMIT 8; + +-- +-- Cleanup +-- +DROP TABLE composite_table, enum_table, range_table, base_table; diff --git a/src/test/regress/sql/yb.orig.ctid.sql b/src/test/regress/sql/yb.orig.ctid.sql new file mode 100644 index 0000000000000..25c343e8c2851 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ctid.sql @@ -0,0 +1,372 @@ +-- hash pk +create table ybctid_test_hash(k int primary key, v text) split into 1 tablets; +insert into ybctid_test_hash values (1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'); +select ybctid, * from ybctid_test_hash; +------------- +-- tid scan +------------- +explain (costs off) +select k, v from ybctid_test_hash where ybctid = '\x47121048800000012121'; +select k, v from ybctid_test_hash where ybctid = '\x47121048800000012121'; +-- key for 5 is not in the table +explain (costs off) +select k, v from ybctid_test_hash where ybctid = '\x470a7348800000052121'; +select k, v from ybctid_test_hash where ybctid = '\x470a7348800000052121'; +-- key is invalid +explain (costs off) +select k, v from ybctid_test_hash where ybctid = '\x0123456789abcdef'; +select k, v from ybctid_test_hash where ybctid = '\x0123456789abcdef'; +-- or semantics +explain (costs off) +select k, v from ybctid_test_hash where ybctid = '\x47121048800000012121' or ybctid = '\x47c0c448800000022121'; +select k, v from ybctid_test_hash where ybctid = '\x47121048800000012121' or ybctid = '\x47c0c448800000022121'; +explain (costs off) +select k, v from ybctid_test_hash where ybctid = '\x47121048800000012121' or ybctid = '\x470a7348800000052121'; +select k, v from ybctid_test_hash where ybctid = '\x47121048800000012121' or ybctid = '\x470a7348800000052121'; +explain (costs off) +select k, v from ybctid_test_hash where ybctid = '\x47121048800000012121' or ybctid = '\x47121048800000012121' or ybctid = '\x0123456789abcdef'; +select k, v from ybctid_test_hash where ybctid = '\x47121048800000012121' or ybctid = '\x47121048800000012121' or ybctid = '\x0123456789abcdef'; +-- in +explain (costs off) +select k, v from ybctid_test_hash where ybctid IN ('\x47fca048800000032121', '\x479eaf48800000042121'); +select k, v from ybctid_test_hash where ybctid IN ('\x47fca048800000032121', '\x479eaf48800000042121'); +explain (costs off) +select k, v from ybctid_test_hash where ybctid IN ('\x47fca048800000032121', '\x470a7348800000052121'); +select k, v from ybctid_test_hash where ybctid IN ('\x47fca048800000032121', '\x470a7348800000052121'); +explain (costs off) +select k, v from ybctid_test_hash where ybctid IN ('\x47fca048800000032121', '\x0123456789abcdef', '\x47fca048800000032121'); +select k, v from ybctid_test_hash where ybctid IN ('\x47fca048800000032121', '\x0123456789abcdef', '\x47fca048800000032121'); +-- or + in +explain (costs off) +select k, v from ybctid_test_hash where ybctid = '\x47121048800000012121' or ybctid = '\x47c0c448800000022121' + or ybctid IN ('\x47fca048800000032121', '\x479eaf48800000042121'); +select k, v from ybctid_test_hash where ybctid = '\x47121048800000012121' or ybctid = '\x47c0c448800000022121' + or ybctid IN ('\x47fca048800000032121', '\x479eaf48800000042121'); +explain (costs off) +select k, v from ybctid_test_hash where ybctid = '\x470a7348800000052121' or ybctid = '\x47c0c448800000022121' + or ybctid IN ('\x470a7348800000052121', '\x479eaf48800000042121'); +select k, v from ybctid_test_hash where ybctid = '\x470a7348800000052121' or ybctid = '\x47c0c448800000022121' + or ybctid IN ('\x470a7348800000052121', '\x479eaf48800000042121'); +explain (costs off) +select k, v from ybctid_test_hash where ybctid = '\x0123456789abcdef' or ybctid = '\x47c0c448800000022121' or ybctid = '\x47c0c448800000022121' + or ybctid IN ('\x0123456789abcdef', '\x479eaf48800000042121', '\x479eaf48800000042121', '\x47c0c448800000022121'); +select k, v from ybctid_test_hash where ybctid = '\x0123456789abcdef' or ybctid = '\x47c0c448800000022121' or ybctid = '\x47c0c448800000022121' + or ybctid IN ('\x0123456789abcdef', '\x479eaf48800000042121', '\x479eaf48800000042121', '\x47c0c448800000022121'); +-- expression pushdown +explain (costs off) +select k, v from ybctid_test_hash where + ybctid IN ('\x47121048800000012121', '\x47c0c448800000022121', '\x47fca048800000032121', '\x479eaf48800000042121', '\x470a7348800000052121') and v like '%e'; +select k, v from ybctid_test_hash where + ybctid IN ('\x47121048800000012121', '\x47c0c448800000022121', '\x47fca048800000032121', '\x479eaf48800000042121', '\x470a7348800000052121') and v like '%e'; +set yb_enable_expression_pushdown to false; +explain (costs off) +select k, v from ybctid_test_hash where + ybctid IN ('\x47121048800000012121', '\x47c0c448800000022121', '\x47fca048800000032121', '\x479eaf48800000042121', '\x470a7348800000052121') and v like '%e'; +select k, v from ybctid_test_hash where + ybctid IN ('\x47121048800000012121', '\x47c0c448800000022121', '\x47fca048800000032121', '\x479eaf48800000042121', '\x470a7348800000052121') and v like '%e'; +reset yb_enable_expression_pushdown; +-- aggregate pushdown +explain (costs off) +select count(*), sum(k) from ybctid_test_hash where + ybctid IN ('\x47121048800000012121', '\x47c0c448800000022121', '\x47fca048800000032121', '\x479eaf48800000042121', '\x470a7348800000052121'); +select count(*), sum(k) from ybctid_test_hash where + ybctid IN ('\x47121048800000012121', '\x47c0c448800000022121', '\x47fca048800000032121', '\x479eaf48800000042121', '\x470a7348800000052121'); +explain (costs off) +select count(*), sum(k) from ybctid_test_hash where + ybctid IN ('\x47121048800000012121', '\x47c0c448800000022121', '\x47fca048800000032121', '\x479eaf48800000042121', '\x470a7348800000052121') and v like '%e'; +select count(*), sum(k) from ybctid_test_hash where + ybctid IN ('\x47121048800000012121', '\x47c0c448800000022121', '\x47fca048800000032121', '\x479eaf48800000042121', '\x470a7348800000052121') and v like '%e'; +set yb_enable_expression_pushdown to false; +explain (costs off) +select count(*), sum(k) from ybctid_test_hash where + ybctid IN ('\x47121048800000012121', '\x47c0c448800000022121', '\x47fca048800000032121', '\x479eaf48800000042121', '\x470a7348800000052121') and v like '%e'; +select count(*), sum(k) from ybctid_test_hash where + ybctid IN ('\x47121048800000012121', '\x47c0c448800000022121', '\x47fca048800000032121', '\x479eaf48800000042121', '\x470a7348800000052121') and v like '%e'; +reset yb_enable_expression_pushdown; +-- join +create table ybctid_test_hash_master(k_int int, k_data bytea, primary key(k_int asc)); +insert into ybctid_test_hash_master values (1, '\x47121048800000012121'), (2, '\x47c0c448800000022121'), (3, '\x47fca048800000032121'), + (4, '\x479eaf48800000042121'), (5, '\x470a7348800000052121'); +explain (costs off) +/*+ NestLoop(ybctid_test_hash_master ybctid_test_hash) Leading((ybctid_test_hash_master ybctid_test_hash)) */ select k_int, v from ybctid_test_hash_master join ybctid_test_hash on k_data = ybctid_test_hash.ybctid; +/*+ NestLoop(ybctid_test_hash_master ybctid_test_hash) Leading((ybctid_test_hash_master ybctid_test_hash)) */ select k_int, v from ybctid_test_hash_master join ybctid_test_hash on k_data = ybctid_test_hash.ybctid; +drop table ybctid_test_hash_master; +select ybctid, k, v from ybctid_test_hash order by ybctid; +create index on ybctid_test_hash(v); +explain (costs off) +select ybctid, * from ybctid_test_hash where v = 'one'; +select ybctid, * from ybctid_test_hash where v = 'one'; +drop table ybctid_test_hash; +-- range pk +create table ybctid_test_range(k int, v text, primary key(k asc)); +insert into ybctid_test_range values (1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'); +select ybctid, * from ybctid_test_range; +------------- +-- tid scan +------------- +explain (costs off) +select k, v from ybctid_test_range where ybctid = '\x488000000121'; +select k, v from ybctid_test_range where ybctid = '\x488000000121'; +-- key for 5 is not in the table +explain (costs off) +select k, v from ybctid_test_range where ybctid = '\x488000000521'; +select k, v from ybctid_test_range where ybctid = '\x488000000521'; +-- key is invalid +explain (costs off) +select k, v from ybctid_test_range where ybctid = '\x0123456789abcdef'; +select k, v from ybctid_test_range where ybctid = '\x0123456789abcdef'; +-- or semantics +explain (costs off) +select k, v from ybctid_test_range where ybctid = '\x488000000121' or ybctid = '\x488000000221'; +select k, v from ybctid_test_range where ybctid = '\x488000000121' or ybctid = '\x488000000221'; +explain (costs off) +select k, v from ybctid_test_range where ybctid = '\x488000000121' or ybctid = '\x488000000521'; +select k, v from ybctid_test_range where ybctid = '\x488000000121' or ybctid = '\x488000000521'; +explain (costs off) +select k, v from ybctid_test_range where ybctid = '\x488000000121' or ybctid = '\x488000000121' or ybctid = '\x0123456789abcdef'; +select k, v from ybctid_test_range where ybctid = '\x488000000121' or ybctid = '\x488000000121' or ybctid = '\x0123456789abcdef'; +-- in +explain (costs off) +select k, v from ybctid_test_range where ybctid IN ('\x488000000321', '\x488000000421'); +select k, v from ybctid_test_range where ybctid IN ('\x488000000321', '\x488000000421'); +explain (costs off) +select k, v from ybctid_test_range where ybctid IN ('\x488000000321', '\x488000000521'); +select k, v from ybctid_test_range where ybctid IN ('\x488000000321', '\x488000000521'); +explain (costs off) +select k, v from ybctid_test_range where ybctid IN ('\x488000000321', '\x0123456789abcdef', '\x488000000321'); +select k, v from ybctid_test_range where ybctid IN ('\x488000000321', '\x0123456789abcdef', '\x488000000321'); +-- or + in +explain (costs off) +select k, v from ybctid_test_range where ybctid = '\x488000000121' or ybctid = '\x488000000221' + or ybctid IN ('\x488000000321', '\x488000000421'); +select k, v from ybctid_test_range where ybctid = '\x488000000121' or ybctid = '\x488000000221' + or ybctid IN ('\x488000000321', '\x488000000421'); +explain (costs off) +select k, v from ybctid_test_range where ybctid = '\x488000000521' or ybctid = '\x488000000221' + or ybctid IN ('\x488000000521', '\x488000000421'); +select k, v from ybctid_test_range where ybctid = '\x488000000521' or ybctid = '\x488000000221' + or ybctid IN ('\x488000000521', '\x488000000421'); +explain (costs off) +select k, v from ybctid_test_range where ybctid = '\x0123456789abcdef' or ybctid = '\x488000000221' or ybctid = '\x488000000221' + or ybctid IN ('\x0123456789abcdef', '\x488000000421', '\x488000000421', '\x488000000221'); +select k, v from ybctid_test_range where ybctid = '\x0123456789abcdef' or ybctid = '\x488000000221' or ybctid = '\x488000000221' + or ybctid IN ('\x0123456789abcdef', '\x488000000421', '\x488000000421', '\x488000000221'); +-- expression pushdown +explain (costs off) +select k, v from ybctid_test_range where + ybctid IN ('\x488000000121', '\x488000000221', '\x488000000321', '\x488000000421', '\x488000000521') and v like '%e'; +select k, v from ybctid_test_range where + ybctid IN ('\x488000000121', '\x488000000221', '\x488000000321', '\x488000000421', '\x488000000521') and v like '%e'; +set yb_enable_expression_pushdown to false; +explain (costs off) +select k, v from ybctid_test_range where + ybctid IN ('\x488000000121', '\x488000000221', '\x488000000321', '\x488000000421', '\x488000000521') and v like '%e'; +select k, v from ybctid_test_range where + ybctid IN ('\x488000000121', '\x488000000221', '\x488000000321', '\x488000000421', '\x488000000521') and v like '%e'; +reset yb_enable_expression_pushdown; +-- aggregate pushdown +explain (costs off) +select count(*), sum(k) from ybctid_test_range where + ybctid IN ('\x488000000121', '\x488000000221', '\x488000000321', '\x488000000421', '\x488000000521'); +select count(*), sum(k) from ybctid_test_range where + ybctid IN ('\x488000000121', '\x488000000221', '\x488000000321', '\x488000000421', '\x488000000521'); +explain (costs off) +select count(*), sum(k) from ybctid_test_range where + ybctid IN ('\x488000000121', '\x488000000221', '\x488000000321', '\x488000000421', '\x488000000521') and v like '%e'; +select count(*), sum(k) from ybctid_test_range where + ybctid IN ('\x488000000121', '\x488000000221', '\x488000000321', '\x488000000421', '\x488000000521') and v like '%e'; +set yb_enable_expression_pushdown to false; +explain (costs off) +select count(*), sum(k) from ybctid_test_range where + ybctid IN ('\x488000000121', '\x488000000221', '\x488000000321', '\x488000000421', '\x488000000521') and v like '%e'; +select count(*), sum(k) from ybctid_test_range where + ybctid IN ('\x488000000121', '\x488000000221', '\x488000000321', '\x488000000421', '\x488000000521') and v like '%e'; +reset yb_enable_expression_pushdown; +-- join +create table ybctid_test_range_master(k_int int, k_data bytea, primary key(k_int desc)); +insert into ybctid_test_range_master values (1, '\x488000000121'), (2, '\x488000000221'), (3, '\x488000000321'), + (4, '\x488000000421'), (5, '\x488000000521'); +explain (costs off) +/*+ NestLoop(ybctid_test_range_master ybctid_test_range) Leading((ybctid_test_range_master ybctid_test_range)) */ select k_int, v from ybctid_test_range_master join ybctid_test_range on k_data = ybctid_test_range.ybctid; +/*+ NestLoop(ybctid_test_range_master ybctid_test_range) Leading((ybctid_test_range_master ybctid_test_range)) */ select k_int, v from ybctid_test_range_master join ybctid_test_range on k_data = ybctid_test_range.ybctid; +drop table ybctid_test_range_master; +select ybctid, k, v from ybctid_test_range order by ybctid; +create index on ybctid_test_range(v asc); +explain (costs off) +select ybctid, * from ybctid_test_range where v = 'one'; +select ybctid, * from ybctid_test_range where v = 'one'; +drop table ybctid_test_range; +-- hash + range pk +create table ybctid_test_hash_range(k1 int, k2 int, k3 text, v text, + primary key((k1, k2) hash, k3 asc)); +insert into ybctid_test_hash_range values (1, 4, 'one', 'four'), (2, 3, 'two', 'three'), + (3, 2, 'three', 'two'), (4, 1, 'four', 'one'); +select ybctid, * from ybctid_test_hash_range; +explain (costs off) +select k1, k2, k3, v from ybctid_test_hash_range where ybctid = '\x4707b64880000003488000000221537468726565000021'; +select k1, k2, k3, v from ybctid_test_hash_range where ybctid = '\x4707b64880000003488000000221537468726565000021'; +explain (costs off) +select k1, k2, k3, v from ybctid_test_hash_range where + ybctid in ('\x47a9014880000001488000000421536f6e65000021', '\x47801748800000024880000003215374776f000021'); +select k1, k2, k3, v from ybctid_test_hash_range where + ybctid in ('\x47a9014880000001488000000421536f6e65000021', '\x47801748800000024880000003215374776f000021'); +explain (costs off) +select k1, k2, k3, v from ybctid_test_hash_range where + ybctid = '\x47a9014880000001488000000421536f6e65000021' or + ybctid = '\x0123456789abcdef' or + ybctid in ('\x47a9014880000001488000000421536f6e65000021', '\x47801748800000024880000003215374776f000021', '\x4783434880000004488000000121536f6e65000021'); +select k1, k2, k3, v from ybctid_test_hash_range where + ybctid = '\x47a9014880000001488000000421536f6e65000021' or + ybctid = '\x0123456789abcdef' or + ybctid in ('\x47a9014880000001488000000421536f6e65000021', '\x47801748800000024880000003215374776f000021', '\x4783434880000004488000000121536f6e65000021'); +select ybctid, k1, k2, k3, v from ybctid_test_hash_range order by ybctid; +create index on ybctid_test_hash_range(v desc); +explain (costs off) +select ybctid, * from ybctid_test_hash_range where v = 'one'; +select ybctid, * from ybctid_test_hash_range where v = 'one'; +drop table ybctid_test_hash_range; +-- no pk, ybctid are random, test can't show them or sort by them +create table ybctid_test_nopk(k int, v text); +insert into ybctid_test_nopk values (1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'); +CREATE OR REPLACE FUNCTION rows_by_ybctid() RETURNS TABLE (kp int, vp text) +AS $$ +DECLARE rowid bytea; +BEGIN + FOR rowid IN select ybctid from ybctid_test_nopk order by k + LOOP + RETURN QUERY select k, v from ybctid_test_nopk where ybctid = rowid; + END LOOP; +END +$$ LANGUAGE plpgsql; +select * from rows_by_ybctid(); +drop function rows_by_ybctid; +drop table ybctid_test_nopk; +-- colocated tables +create database codb colocation = true; +\c codb +-- with pk +create table ybctid_test_with_pk(k int, v text, primary key(k asc)) with (colocation=true); +insert into ybctid_test_with_pk values (1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'); +select ybctid, * from ybctid_test_with_pk; +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid = '\x488000000121'; +select k, v from ybctid_test_with_pk where ybctid = '\x488000000121'; +-- key for 5 is not in the table +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid = '\x488000000521'; +select k, v from ybctid_test_with_pk where ybctid = '\x488000000521'; +-- key is invalid +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid = '\x0123456789abcdef'; +select k, v from ybctid_test_with_pk where ybctid = '\x0123456789abcdef'; +-- or semantics +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid = '\x488000000121' or ybctid = '\x488000000221'; +select k, v from ybctid_test_with_pk where ybctid = '\x488000000121' or ybctid = '\x488000000221'; +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid = '\x488000000121' or ybctid = '\x488000000521'; +select k, v from ybctid_test_with_pk where ybctid = '\x488000000121' or ybctid = '\x488000000521'; +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid = '\x488000000121' or ybctid = '\x488000000121' or ybctid = '\x0123456789abcdef'; +select k, v from ybctid_test_with_pk where ybctid = '\x488000000121' or ybctid = '\x488000000121' or ybctid = '\x0123456789abcdef'; +-- in +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid IN ('\x488000000321', '\x488000000421'); +select k, v from ybctid_test_with_pk where ybctid IN ('\x488000000321', '\x488000000421'); +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid IN ('\x488000000321', '\x488000000521'); +select k, v from ybctid_test_with_pk where ybctid IN ('\x488000000321', '\x488000000521'); +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid IN ('\x488000000321', '\x0123456789abcdef', '\x488000000321'); +select k, v from ybctid_test_with_pk where ybctid IN ('\x488000000321', '\x0123456789abcdef', '\x488000000321'); +-- or + in +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid = '\x488000000121' or ybctid = '\x488000000221' + or ybctid IN ('\x488000000321', '\x488000000421'); +select k, v from ybctid_test_with_pk where ybctid = '\x488000000121' or ybctid = '\x488000000221' + or ybctid IN ('\x488000000321', '\x488000000421'); +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid = '\x488000000521' or ybctid = '\x488000000221' + or ybctid IN ('\x488000000521', '\x488000000421'); +select k, v from ybctid_test_with_pk where ybctid = '\x488000000521' or ybctid = '\x488000000221' + or ybctid IN ('\x488000000521', '\x488000000421'); +explain (costs off) +select k, v from ybctid_test_with_pk where ybctid = '\x0123456789abcdef' or ybctid = '\x488000000221' or ybctid = '\x488000000221' + or ybctid IN ('\x0123456789abcdef', '\x488000000421', '\x488000000421', '\x488000000221'); +select k, v from ybctid_test_with_pk where ybctid = '\x0123456789abcdef' or ybctid = '\x488000000221' or ybctid = '\x488000000221' + or ybctid IN ('\x0123456789abcdef', '\x488000000421', '\x488000000421', '\x488000000221'); +select ybctid, k, v from ybctid_test_with_pk order by ybctid; +drop table ybctid_test_with_pk; +-- without pk +create table ybctid_test_without_pk(k int, v text) with (colocation=true); +insert into ybctid_test_without_pk values (1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'); +CREATE OR REPLACE FUNCTION rows_by_ybctid() RETURNS TABLE (kp int, vp text) +AS $$ +DECLARE rowid bytea; +BEGIN + FOR rowid IN select ybctid from ybctid_test_without_pk order by k + LOOP + RETURN QUERY select k, v from ybctid_test_without_pk where ybctid = rowid; + END LOOP; +END +$$ LANGUAGE plpgsql; +select * from rows_by_ybctid(); +drop function rows_by_ybctid; +drop table ybctid_test_without_pk; + +-- +-- Test ybctid in RETURNING clause of DML queries +-- +\c yugabyte +CREATE TABLE ybctid_test_hash_range (k1 INT, k2 TEXT, v INT, PRIMARY KEY (k1 HASH, k2 ASC)); +-- Basic DML queries +-- Queries that return > 1 row are wrapped in a CTE to impose an ordering on the returned rows. +INSERT INTO ybctid_test_hash_range VALUES (1, 'one', 1), (2, 'two', 2), (3, 'three', 3), (4, 'four', 4) RETURNING *, ybctid; +WITH cte AS (UPDATE ybctid_test_hash_range SET v = v + 1 WHERE k1 < 10 RETURNING *, ybctid) +SELECT * FROM cte ORDER BY k1, k2; +UPDATE ybctid_test_hash_range SET k2 = k2 || '_updated', v = v + 1 WHERE k1 = 2 RETURNING *, ybctid; +UPDATE ybctid_test_hash_range SET k1 = k1 + 5, k2 = k2 || '_updated' WHERE k2 = 'three' RETURNING *, ybctid; +UPDATE ybctid_test_hash_range SET k1 = k1 + 5 WHERE ybctid = '\x479eaf48800000042153666f7572000021' RETURNING *, ybctid; +WITH cte AS (DELETE FROM ybctid_test_hash_range WHERE k1 < 10 RETURNING *, ybctid) +SELECT * FROM cte ORDER BY k1, k2; +-- Single shard queries +INSERT INTO ybctid_test_hash_range VALUES (5, 'five', 5) RETURNING *, ybctid; +UPDATE ybctid_test_hash_range SET v = v + 1 WHERE k1 = 5 AND k2 = 'five' RETURNING k1, k2, ybctid; +DELETE FROM ybctid_test_hash_range WHERE k1 = 5 AND k2 = 'five' RETURNING k2, ybctid; +-- INSERT ... ON CONFLICT queries +INSERT INTO ybctid_test_hash_range VALUES (6, 'six', 6), (7, 'seven', 7) ON CONFLICT (k1, k2) DO NOTHING RETURNING *, ybctid; +INSERT INTO ybctid_test_hash_range VALUES (7, 'seven', 7), (8, 'eight', 8) ON CONFLICT (k1, k2) DO NOTHING RETURNING *, ybctid; +INSERT INTO ybctid_test_hash_range VALUES (9, 'nine', 9), (8, 'eight', 8) ON CONFLICT (k1, k2) DO UPDATE SET v = EXCLUDED.v + 1 RETURNING *, ybctid; +-- Expressions in the RETURNING clause +INSERT INTO ybctid_test_hash_range VALUES (10, 'ten', 10), (11, 'eleven', 11) + RETURNING (k2 || '-' || k1::TEXT) AS keys, (k1 + v) AS nums, (ybctid || '-' || ybctid) AS dblybctid, ybctid; +UPDATE ybctid_test_hash_range SET v = v + 1 WHERE k1 = 10 + RETURNING (k2 || '-' || k1::TEXT) AS keys, (k1 + v) AS nums, (ybctid || '-' || ybctid) AS dblybctid, ybctid; +UPDATE ybctid_test_hash_range SET k2 = k2 || '_updated' WHERE k1 = 11 + RETURNING (k2 || '-' || k1::TEXT) AS keys, (k1 + v) AS nums, (ybctid || '-' || ybctid) AS dblybctid, ybctid; +DELETE FROM ybctid_test_hash_range WHERE k1 = 10 + RETURNING (k2 || '-' || k1::TEXT) AS keys, (k1 + v) AS nums, (ybctid || '-' || ybctid) AS dblybctid, ybctid; +DELETE FROM ybctid_test_hash_range WHERE ybctid = '\x471c99488000000b2153656c6576656e5f75706461746564000021' + RETURNING (k2 || '-' || k1::TEXT) AS keys, (k1 + v) AS nums, (ybctid || '-' || ybctid) AS dblybctid, ybctid; +-- Triggers modifying key columns +CREATE OR REPLACE FUNCTION increment_k1() RETURNS TRIGGER AS $$ +BEGIN + IF TG_OP = 'DELETE' THEN + OLD.k1 = OLD.k1 + 2; + RETURN OLD; + ELSE + NEW.k1 = NEW.k1 + 2; + RETURN NEW; + END IF; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER increment_k1_trigger BEFORE INSERT OR UPDATE OR DELETE ON ybctid_test_hash_range FOR EACH ROW EXECUTE PROCEDURE increment_k1(); +-- Returned ybctid should have 0x20 +INSERT INTO ybctid_test_hash_range VALUES (30, 'thirty', 30) RETURNING *, ybctid; +-- Returned ybctid should have 0x22 +UPDATE ybctid_test_hash_range SET k2 = k2 || '_updated', v = v + 1 WHERE k1 = 32 RETURNING *, ybctid; +-- Returned ybctid should have 0x22 +DELETE FROM ybctid_test_hash_range WHERE k1 > 30 AND k1 < 40 RETURNING *, ybctid; + +SELECT * FROM ybctid_test_hash_range ORDER BY k1; +DROP TABLE ybctid_test_hash_range; diff --git a/src/test/regress/sql/yb.orig.cursor.sql b/src/test/regress/sql/yb.orig.cursor.sql new file mode 100644 index 0000000000000..f75c3bb292ff8 --- /dev/null +++ b/src/test/regress/sql/yb.orig.cursor.sql @@ -0,0 +1,138 @@ +-- +-- Tests basic CURSOR support. +-- +CREATE TABLE test_cursor (id int PRIMARY KEY, name varchar); +-- +-- Use CURSOR for SELECT in anonymous SQL block. +-- +INSERT INTO test_cursor VALUES (1, 'one'), (2, 'two'), (3, 'three'); +BEGIN; + DECLARE c_sql CURSOR FOR SELECT * FROM test_cursor; + FETCH ALL FROM c_sql; +END; +-- CURSOR in anonymous PL/PGSQL block. +DO LANGUAGE plpgsql $$ +DECLARE + c_pl CURSOR FOR SELECT * FROM test_cursor; + row RECORD; +BEGIN + OPEN c_pl; + LOOP + FETCH FROM c_pl INTO row; + EXIT WHEN NOT FOUND; + RAISE NOTICE 'FETCHING a row: %', row; + END LOOP; +END $$; +-- CURSOR in PL/PGSQL procedure. +CREATE OR REPLACE PROCEDURE proc() LANGUAGE plpgsql AS $body$ +DECLARE + c_proc CURSOR FOR SELECT * FROM test_cursor; + c_ref REFCURSOR; + row1 RECORD; + row2 RECORD; +BEGIN + OPEN c_proc; + OPEN c_ref FOR SELECT * FROM test_cursor; + LOOP + FETCH FROM c_proc INTO row1; + EXIT WHEN NOT FOUND; + FETCH FROM c_ref INTO row2; + EXIT WHEN NOT FOUND; + RAISE NOTICE 'FETCHING a row: %, %', row1, row2; + END LOOP; +END +$body$; +-- +CALL proc(); +-- +DELETE FROM test_cursor; +-- +-- Issue 6627 CURSOR query read time should be set at DECLARE or OPEN instead of FETCH. +-- +-- CURSOR in SQL block. +BEGIN; + INSERT INTO test_cursor VALUES (1, 'one'); + DECLARE c CURSOR FOR SELECT * FROM test_cursor; + INSERT INTO test_cursor VALUES (2, 'two'), (3, 'three'); + -- BUG: Only one row should be output by the following FETCH, but there's 3. + FETCH ALL FROM c; +ROLLBACK; +-- CURSOR in PL/PGSQL block (Issue 6627). +DO LANGUAGE plpgsql $$ +DECLARE + c_pl CURSOR FOR SELECT * FROM test_cursor; + row RECORD; +BEGIN + INSERT INTO test_cursor VALUES (1, 'one'); + OPEN c_pl; + INSERT INTO test_cursor VALUES (2, 'two'), (3, 'three'); + -- BUG: Only one row should be output by the following LOOP, but there's 3. + LOOP + FETCH FROM c_pl INTO row; + EXIT WHEN NOT FOUND; + RAISE NOTICE 'FETCHING a row: %', row; + END LOOP; + DELETE FROM test_cursor; +END $$; +-- CURSOR in PL/PGSQL procedure (Issue 6627). +CREATE OR REPLACE PROCEDURE proc() LANGUAGE plpgsql AS $body$ +DECLARE + c_proc CURSOR FOR SELECT * FROM test_cursor; + row RECORD; +BEGIN + INSERT INTO test_cursor VALUES (1, 'one'); + OPEN c_proc; + INSERT INTO test_cursor VALUES (2, 'two'), (3, 'three'); + -- BUG: Only one row should be output by the following LOOP, but there's 3. + LOOP + FETCH FROM c_proc INTO row; + EXIT WHEN NOT FOUND; + RAISE NOTICE 'FETCHING a row: %', row; + END LOOP; + DELETE FROM test_cursor; +END +$body$; +-- +CALL proc(); +-- +-- Use CURSOR to call FUNCTION in anonymous block. +-- +CREATE FUNCTION cnt() RETURNS BIGINT AS +'select count(*) from test_cursor' LANGUAGE sql; +-- +CREATE FUNCTION cnt_v() RETURNS BIGINT AS +'select count(*) from test_cursor' LANGUAGE sql volatile; +-- +CREATE FUNCTION cnt_s() RETURNS BIGINT AS +'select count(*) from test_cursor' LANGUAGE sql stable; +-- +CREATE FUNCTION cnt_i() RETURNS BIGINT AS +'select count(*) from test_cursor' LANGUAGE sql immutable; +-- +BEGIN; + INSERT INTO test_cursor VALUES (1, 1), (2, 2), (3, 3); + DECLARE c_cnt CURSOR FOR SELECT cnt(), cnt_v(), cnt_s(), cnt_i(); + FETCH ALL FROM c_cnt; +ROLLBACK; +-- +-- Issue 6629 STABLE execution time should be set at DECLARE or OPEN instead of FETCH. +-- +BEGIN; + INSERT INTO test_cursor VALUES (1, 'one'); + DECLARE c_cnt CURSOR FOR SELECT cnt(), cnt_v(), cnt_s(), cnt_i(); + INSERT INTO test_cursor VALUES (2, 'two'), (3, 'three'); + -- BUG: Only one row should be output by the following FETCH. + FETCH ALL FROM c_cnt; +ROLLBACK; +-- +-- CURSOR for SELECT constant +-- +DECLARE c_const CURSOR WITH HOLD FOR SELECT 1 as const; +FETCH FROM c_const; +CLOSE c_const; +-- +-- DISCARDed CURSOR +-- +DECLARE c_discard CURSOR WITH HOLD FOR SELECT 1; +DISCARD ALL; +FETCH FROM c_discard; diff --git a/src/test/regress/sql/yb.orig.cursor_preserve_in_txn_limit.sql b/src/test/regress/sql/yb.orig.cursor_preserve_in_txn_limit.sql new file mode 100644 index 0000000000000..48704bb37edb9 --- /dev/null +++ b/src/test/regress/sql/yb.orig.cursor_preserve_in_txn_limit.sql @@ -0,0 +1,24 @@ +-- Test to ensure that the same in_txn_limit_ht is used for all FETCHes on a cursor. See +-- ReadHybridTime for information about in_txn_limit_ht. +-- +-- An in_txn_limit_ht will be picked on the first FETCH performed by a cursor. The same should be +-- used for all future FETCHes on the cursor. This ensures that any data written by the same +-- transaction after the first FETCH isn't visible to the future FETCHes. +-- +-- NOTE: a low ysql_prefetch_limit is needed to ensure the test is reliable i.e., it should fail if +-- the second FETCH doesn't use the same in_txn_limit as the first one. If the ysql_prefetch_limit +-- is higher than the table size, the first FETCH would end up marking the cursor scan as completed +-- and hence return no rows in the second FETCH even if a new in_txn_limit was to be used. + +CREATE TABLE t1(k INT, PRIMARY KEY(k ASC)) SPLIT AT VALUES((2)); +INSERT INTO t1 SELECT i FROM generate_series(1, 2) AS i; +BEGIN; +DECLARE c1 SCROLL CURSOR FOR SELECT * FROM t1; +INSERT INTO t1 VALUES(3); -- rows inserted before the first FETCH will be visible +INSERT INTO t1 VALUES(4); +FETCH 1 FROM c1; +INSERT INTO t1 VALUES(5); -- rows inserted after the first FETCH should not be visible +INSERT INTO t1 VALUES(6); +SELECT count(*) FROM t1; -- this statement uses a new in_txn_limit and hence sees all rows +FETCH 10 FROM c1; +ROLLBACK; \ No newline at end of file diff --git a/src/test/regress/sql/yb.orig.ddl_exceptions.sql b/src/test/regress/sql/yb.orig.ddl_exceptions.sql new file mode 100644 index 0000000000000..a797d538a5a44 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_exceptions.sql @@ -0,0 +1,92 @@ +-- Test interaction between "New Table" optimizations and PL/pgSQL exceptions. +-- Exception blocks create internal subtransactions (savepoints). +-- Since direct writes to Regular DB cannot be rolled back to a savepoint, +-- these cases should gracefully fall back to standard intent-based writes. + +-- 1. Rollback of a subtransaction (Exception caught) +-- Verifies that data written inside a failing exception block is actually removed. +BEGIN; + +CREATE TABLE exception_test (id INT PRIMARY KEY, val TEXT); + +CREATE OR REPLACE PROCEDURE test_exception_rollback() AS $$ +BEGIN + INSERT INTO exception_test VALUES (1, 'initial'); + + BEGIN + -- This block creates a SUBTRANSACTION. + INSERT INTO exception_test VALUES (2, 'should be rolled back'); + + -- Force an error + RAISE EXCEPTION 'intentional error'; + EXCEPTION WHEN OTHERS THEN + -- Error caught, subtransaction rolls back. + RAISE NOTICE 'Caught error: %', SQLERRM; + END; +END; +$$ LANGUAGE plpgsql; + +CALL test_exception_rollback(); + +-- Success Criteria: +-- Row 1 should exist. +-- Row 2 MUST NOT exist. +SELECT * FROM exception_test ORDER BY id; + +ROLLBACK; + +-- 2. Nested Exceptions +-- Verifies behavior across multiple levels of savepoints. +BEGIN; + +CREATE TABLE nested_exception_test (id INT PRIMARY KEY, val TEXT); + +CREATE OR REPLACE PROCEDURE nested_exception_logic() AS $$ +BEGIN + INSERT INTO nested_exception_test VALUES (1, 'level 0'); + + BEGIN + INSERT INTO nested_exception_test VALUES (2, 'level 1'); + BEGIN + INSERT INTO nested_exception_test VALUES (3, 'level 2'); + RAISE EXCEPTION 'fail level 2'; + EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Caught level 2'; + END; + EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Caught level 1'; + END; +END; +$$ LANGUAGE plpgsql; + +CALL nested_exception_logic(); + +-- Success Criteria: +-- Rows 1 and 2 should exist. +-- Row 3 MUST NOT exist. +SELECT * FROM nested_exception_test ORDER BY id; + +ROLLBACK; + +-- 3. Savepoint combined with Schema Evolution +-- Tests that the system doesn't get confused when metadata changes +-- occur alongside subtransactions. +BEGIN; + +CREATE TABLE savepoint_metadata_test (id INT PRIMARY KEY); + +SAVEPOINT s1; +-- Perform DDL after a savepoint +ALTER TABLE savepoint_metadata_test ADD COLUMN info TEXT; + +-- Try to insert. The system should detect we are in a subtransaction +-- (due to the savepoint) and use standard intents. +INSERT INTO savepoint_metadata_test VALUES (1, 'data'); + +ROLLBACK TO SAVEPOINT s1; + +-- Success Criteria: +-- Table should exist, but column 'info' and Row 1 should be gone. +SELECT * FROM savepoint_metadata_test; + +ROLLBACK; diff --git a/src/test/regress/sql/yb.orig.ddl_rc_rollback.sql b/src/test/regress/sql/yb.orig.ddl_rc_rollback.sql new file mode 100644 index 0000000000000..64a735c90f827 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_rc_rollback.sql @@ -0,0 +1,92 @@ +SET default_transaction_isolation = 'read committed'; +-- These tests verify that any YB optimizations do not break +-- Postgres-standard behavior for procedures, exceptions, and restarts. + +-------------------------------------------------------------------------------- +-- TEST 1: Nested Procedures and Schema Evolution +-------------------------------------------------------------------------------- +BEGIN; +CREATE TABLE proc_schema_test (id INT PRIMARY KEY); + +CREATE OR REPLACE PROCEDURE alter_and_load(p_id INT, p_new_val TEXT) AS $$ +BEGIN + ALTER TABLE proc_schema_test ADD COLUMN extra_info TEXT; + INSERT INTO proc_schema_test VALUES (p_id, p_new_val); +END; +$$ LANGUAGE plpgsql; + +CALL alter_and_load(1, 'Snapshot A'); + +SELECT * FROM proc_schema_test; +ROLLBACK; + +-------------------------------------------------------------------------------- +-- TEST 2: Multi-row VALUES list with CHECK constraint failure +-------------------------------------------------------------------------------- +BEGIN; +CREATE TABLE atomicity_values_test ( + id INT PRIMARY KEY, + val INT CHECK (val > 0) +); + +-- Attempt to insert 3 rows; the 3rd fails. +SAVEPOINT s1; +INSERT INTO atomicity_values_test VALUES (1, 10), (2, 20), (3, -1); +ROLLBACK TO SAVEPOINT s1; + +-- Expected count: 0 +SELECT count(*) AS values_test_count FROM atomicity_values_test; +ROLLBACK; + +-------------------------------------------------------------------------------- +-- TEST 3: Subtransaction / Exception Rollback +-------------------------------------------------------------------------------- +BEGIN; +CREATE TABLE exception_test (id INT PRIMARY KEY, val TEXT); + +CREATE OR REPLACE PROCEDURE test_exception_rollback() AS $$ +BEGIN + INSERT INTO exception_test VALUES (1, 'initial'); + BEGIN + INSERT INTO exception_test VALUES (2, 'should be rolled back'); + RAISE EXCEPTION 'intentional error'; + EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Caught error: %', SQLERRM; + END; +END; +$$ LANGUAGE plpgsql; + +CALL test_exception_rollback(); + +-- Expected: Only ID 1 exists. +SELECT * FROM exception_test ORDER BY id; +ROLLBACK; + +-------------------------------------------------------------------------------- +-- TEST 4: "The Mega-Flush" Read Committed Atomicity +-------------------------------------------------------------------------------- +CREATE TABLE data_source AS SELECT generate_series(1, 1000) AS val; + +BEGIN; +CREATE TABLE target_table (id INT PRIMARY KEY); + +-- Generate 1,000,000 rows to force multiple RPC flushes. +-- Failure occurs at the very end of the statement. +DO $$ +BEGIN + INSERT INTO target_table (id) + SELECT + CASE + WHEN s1.val * 1000 + s2.val < 999999 THEN s1.val * 1000 + s2.val + ELSE 1 / 0 + END + FROM data_source s1, data_source s2; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Caught mega-flush error: %', SQLERRM; +END $$; + +-- Expected 0. +SELECT count(*) AS leaked_row_count FROM target_table; + +ROLLBACK; +RESET default_transaction_isolation; diff --git a/src/test/regress/sql/yb.orig.ddl_savepoint.sql b/src/test/regress/sql/yb.orig.ddl_savepoint.sql new file mode 100644 index 0000000000000..0780d7bed6417 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_savepoint.sql @@ -0,0 +1,235 @@ +-- Rollback of CREATE TABLE +BEGIN; +SAVEPOINT a; +CREATE TABLE test_table (a INT); +ROLLBACK TO a; +SELECT * FROM test_table; +ROLLBACK; +SELECT * FROM test_table; + +-- Rollback with ALTER TABLE (ADD and DROP COLUMN). +BEGIN; +CREATE TABLE test_table (a INT, b INT); +SAVEPOINT a; +ALTER TABLE test_table ADD COLUMN c TEXT; +SAVEPOINT b; +ALTER TABLE test_table ADD COLUMN d TEXT; +ALTER TABLE test_table DROP COLUMN b; +ROLLBACK TO b; +\d test_table +ROLLBACK TO a; +\d test_table +ROLLBACK; +\d test_table + +-- Drop Table DDL + DML Rollback +BEGIN; +CREATE TABLE test_table (a INT, b INT); +INSERT INTO test_table VALUES (1, 2); +SAVEPOINT a; +INSERT INTO test_table (a, b) VALUES (3, 4); +DROP TABLE test_table; +ROLLBACK TO a; +\d test_table +ROLLBACK; + +-- Rollback of CREATE INDEX +BEGIN; +CREATE TABLE test_table (a INT, b INT); +SAVEPOINT a; +CREATE INDEX ON test_table(a); +ROLLBACK TO a; +\di +ROLLBACK; + +-- Rollback of DROP + CREATE TABLE with the same name works. +BEGIN; +CREATE TABLE test_table (a INT, b INT); +SAVEPOINT svpt; +DROP TABLE test_table; +CREATE TABLE test_table (a INT, c INT, d INT); +ROLLBACK TO SAVEPOINT svpt; +SELECT * FROM test_table; +ROLLBACK; + +-- Rollback of TRUNCATE +BEGIN; +CREATE TABLE test_table (a INT, b INT); +INSERT INTO test_table VALUES (1, 1); +INSERT INTO test_table VALUES (2, 2); +INSERT INTO test_table VALUES (3, 3); +SELECT * FROM test_table ORDER BY a; +SAVEPOINT svpt; +TRUNCATE test_table; +SELECT * FROM test_table ORDER BY a; +ROLLBACK TO SAVEPOINT svpt; +SELECT * FROM test_table ORDER BY a; +ROLLBACK; + +-- #28955: CREATE INDEX (separate DDL transaction) works when savepoint is enabled. +CREATE TABLE employees(id INT PRIMARY KEY, code VARCHAR(20) UNIQUE, department VARCHAR(50)); +CREATE INDEX idx_department ON employees(department); +DROP TABLE employees; + +-- #28956: Transaction doesn't self abort +CREATE TABLE projects (id INT PRIMARY KEY, project_name VARCHAR(100)); +INSERT INTO projects (id, project_name) VALUES (101, 'Project Alpha'); +BEGIN ISOLATION LEVEL REPEATABLE READ; +UPDATE projects SET project_name = 'Project Phoenix' WHERE id = 101; +SAVEPOINT sp_mixed; +CREATE TABLE project_logs (log_id INT, entry TEXT); +INSERT INTO project_logs VALUES (1, 'Initial log'); +-- This will drop table project_logs but shouldn't abort the entire transaction. +ROLLBACK TO SAVEPOINT sp_mixed; +SELECT project_name FROM projects WHERE id = 101; + +-- #28957: Rollback to savepoint of ALTER TABLE should complete and not run forever +CREATE TABLE departments ( + dept_id INT PRIMARY KEY, + dept_name VARCHAR(50) +); +CREATE TABLE employees ( + emp_id INT PRIMARY KEY, + emp_name VARCHAR(50), + department_id INT, + FOREIGN KEY (department_id) REFERENCES departments(dept_id) +); +INSERT INTO departments VALUES (1, 'Engineering'), (2, 'Sales'); +INSERT INTO employees VALUES (101, 'Alice', 1), (102, 'Bob', 2); +BEGIN; +SAVEPOINT sp_initial_state; +UPDATE employees SET department_id = 2 WHERE emp_name = 'Alice'; +CREATE INDEX idx_emp_dept ON employees(department_id); +SAVEPOINT sp_after_index; +ALTER TABLE employees ADD COLUMN start_date DATE; +INSERT INTO employees (emp_id, emp_name, department_id, start_date) VALUES (103, 'Charlie', 1, '2025-10-15'); +SAVEPOINT sp_after_alter; +TRUNCATE TABLE departments; -- This will fail +ROLLBACK TO SAVEPOINT sp_after_alter; +ROLLBACK TO SAVEPOINT sp_after_index; +ROLLBACK TO SAVEPOINT sp_initial_state; +COMMIT; + +-- #29013: Savepoint rollback due to failure in first DDL statement of a +-- transaction block doesn't lead to a crash. +BEGIN; +SAVEPOINT sp_before_constraint_change; +ALTER TABLE employees DROP CONSTRAINT positive_salary; +COMMIT; + +-- #29373: Transaction doesn't self abort when index deletion skipped due to base table deletion. +CREATE TABLE txn_self_abort_base (id INT PRIMARY KEY, val INT, category TEXT); +INSERT INTO txn_self_abort_base (id, val, category) SELECT i, i*10, 'A' FROM generate_series(1, 10) i; +CREATE MATERIALIZED VIEW txn_self_abort_mv AS SELECT id, val, category FROM txn_self_abort_base; +CREATE UNIQUE INDEX txn_self_abort_mv_idx ON txn_self_abort_mv (id); +INSERT INTO txn_self_abort_base (id, val, category) SELECT i, i*10, 'B' FROM generate_series(11, 20) i; +BEGIN ISOLATION LEVEL REPEATABLE READ; +SAVEPOINT s1_start; +REFRESH MATERIALIZED VIEW CONCURRENTLY txn_self_abort_mv; +SAVEPOINT s2_refreshed_concurrently; +INSERT INTO txn_self_abort_base (id, val, category) SELECT i, i*10, 'C' FROM generate_series(21, 30) i; +REFRESH MATERIALIZED VIEW txn_self_abort_mv; +SAVEPOINT s3_refreshed_normal; +DELETE FROM txn_self_abort_base WHERE category = 'A'; +REFRESH MATERIALIZED VIEW CONCURRENTLY txn_self_abort_mv; +SAVEPOINT s4_refreshed_final; +INSERT INTO txn_self_abort_base (id, val, category) SELECT i, i*10, 'D' FROM generate_series(31, 40) i; +REFRESH MATERIALIZED VIEW txn_self_abort_mv; +-- This will drop and recreate txn_self_abort_mv_idx as part of table rewrite +-- but it shouldn't lead to self abort. +ROLLBACK TO SAVEPOINT s4_refreshed_final; +SELECT COUNT(*) FROM txn_self_abort_base; +ROLLBACK; + +-- #29414 +CREATE TABLE test_table (id INT PRIMARY KEY, val TEXT); +INSERT INTO test_table SELECT i, 'val' || i FROM generate_series(1, 1000) i; +BEGIN ISOLATION LEVEL REPEATABLE READ; +SAVEPOINT s1_start; +CREATE MATERIALIZED VIEW test_table_mv AS SELECT * FROM test_table; +SAVEPOINT s2_created_mv; +INSERT INTO test_table SELECT i, 'val' || i FROM generate_series(1001, 2000) i; +REFRESH MATERIALIZED VIEW test_table_mv; +SAVEPOINT s3_refreshed_mv; +CREATE INDEX test_table_idx_mv ON test_table_mv (val); +SELECT indexname FROM pg_indexes WHERE indexname = 'test_table_idx_mv'; +ROLLBACK TO SAVEPOINT s3_refreshed_mv; +ROLLBACK; + +-- #29538: No Schema version mismatch in case of ALTER TABLE +CREATE TABLE schema_version_mismatch_table ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + val TEXT +); +BEGIN ISOLATION LEVEL REPEATABLE READ; +SAVEPOINT s1_start; +ALTER TABLE schema_version_mismatch_table ALTER COLUMN id RESTART WITH 100; +INSERT INTO schema_version_mismatch_table (val) VALUES ('c'); +SELECT id FROM schema_version_mismatch_table WHERE val = 'c'; +SAVEPOINT s2_restarted; +ALTER TABLE schema_version_mismatch_table ALTER COLUMN id DROP IDENTITY; +INSERT INTO schema_version_mismatch_table (id, val) VALUES (500, 'd'); +ROLLBACK TO SAVEPOINT s2_restarted; +INSERT INTO schema_version_mismatch_table (val) VALUES ('e'); +SELECT id FROM schema_version_mismatch_table WHERE val = 'e'; +ROLLBACK TO SAVEPOINT s1_start; +INSERT INTO schema_version_mismatch_table (val) VALUES ('f'); +SELECT id FROM schema_version_mismatch_table WHERE val = 'f'; +ROLLBACK; + +-- #29881 +CREATE TABLE _33_s_1_data (id INT PRIMARY KEY); +CREATE TABLE _33_s_1_audit ( + log_id SERIAL PRIMARY KEY, + data_id INT, + notes TEXT +); +CREATE FUNCTION _33_func_audit() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO _33_s_1_audit (data_id, notes) VALUES (NEW.id, 'V1'); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER _33_trig_data +AFTER INSERT ON _33_s_1_data +FOR EACH ROW EXECUTE FUNCTION _33_func_audit(); +CREATE PROCEDURE _33_sp_insert(val INT) LANGUAGE SQL AS $$ + INSERT INTO _33_s_1_data (id) VALUES (val); +$$; +CALL _33_sp_insert(1); +SELECT COUNT(*) FROM _33_s_1_audit WHERE notes = 'V1'; +BEGIN; +SAVEPOINT s1_start; +CREATE OR REPLACE FUNCTION _33_func_audit() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO _33_s_1_audit (data_id, notes) VALUES (NEW.id, 'V2'); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE OR REPLACE PROCEDURE _33_sp_insert(val INT) LANGUAGE SQL AS $$ + INSERT INTO _33_s_1_data (id) VALUES (val * 100); +$$; +CALL _33_sp_insert(2); +SELECT COUNT(*) FROM _33_s_1_data WHERE id = 200; +SELECT COUNT(*) FROM _33_s_1_audit WHERE notes = 'V2'; +SAVEPOINT s2_v2_created; +DROP TRIGGER _33_trig_data ON _33_s_1_data; +CALL _33_sp_insert(3); +SELECT COUNT(*) FROM _33_s_1_data WHERE id = 300; +SELECT COUNT(*) FROM _33_s_1_audit WHERE notes = 'V2'; +ROLLBACK TO SAVEPOINT s2_v2_created; +SELECT COUNT(*) FROM pg_trigger WHERE tgname = '_33_trig_data'; +CALL _33_sp_insert(4); +SELECT COUNT(*) FROM _33_s_1_audit WHERE notes = 'V2'; +ROLLBACK TO SAVEPOINT s1_start; +SELECT COUNT(*) FROM _33_s_1_data WHERE id > 1; +SELECT COUNT(*) FROM _33_s_1_audit WHERE notes = 'V2'; +CALL _33_sp_insert(5); +SELECT COUNT(*) FROM _33_s_1_data WHERE id = 5; +SELECT COUNT(*) FROM _33_s_1_audit WHERE notes = 'V1'; +select * from _33_s_1_audit; +select * from _33_s_1_audit; +COMMIT; +SELECT id FROM _33_s_1_data ORDER BY id; +SELECT COUNT(*) FROM _33_s_1_audit WHERE notes = 'V1'; +SELECT COUNT(*) FROM _33_s_1_audit WHERE notes = 'V2'; diff --git a/src/test/regress/sql/yb.orig.ddl_savepoint_toggle.sql b/src/test/regress/sql/yb.orig.ddl_savepoint_toggle.sql new file mode 100644 index 0000000000000..538040ca5bc51 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_savepoint_toggle.sql @@ -0,0 +1,89 @@ +-- Start a fresh transaction +BEGIN; + +-- 1. Create the table inside the txn. +CREATE TABLE toggle_test (id INT PRIMARY KEY, val TEXT); + +INSERT INTO toggle_test VALUES (1, 'Regular DB'); + +-- 2. ENTER SAVEPOINT +SAVEPOINT s1; +INSERT INTO toggle_test VALUES (2, 'Intents DB'); + +-- 3. RELEASE SAVEPOINT +-- The sub-transaction is "merged" into the main txn. +RELEASE SAVEPOINT s1; + +-- 4. THE SWITCHBACK CHECK +INSERT INTO toggle_test VALUES (3, 'Regular DB'); + +-- 5. TRIGGER A PARTIAL FAILURE +-- We use a savepoint to catch a failure. +SAVEPOINT s2; +INSERT INTO toggle_test VALUES (4, 1/0); -- Division by zero +ROLLBACK TO SAVEPOINT s2; + +-- 6. COMMIT THE REST +COMMIT; + +-- FINAL VERIFICATION +SELECT * FROM toggle_test ORDER BY id; +DROP TABLE toggle_test; + +-- Start a fresh transaction +BEGIN; + +-- Table created in txn. +CREATE TABLE toggle_test (id INT PRIMARY KEY, val TEXT); + +INSERT INTO toggle_test VALUES (1, 'Regular DB'); + +-- 2. ENTER FIRST SAVEPOINT +SAVEPOINT s1; +INSERT INTO toggle_test VALUES (2, 'Intents DB (Sub-txn)'); +RELEASE SAVEPOINT s1; + +-- 3. THE CRITICAL TEST ZONE +-- We create a second savepoint BEFORE ID 3. +SAVEPOINT s2; + +INSERT INTO toggle_test VALUES (3, 'Regular DB'); + +-- 4. TRIGGER FAILURE & ROLLBACK +-- This failure forces us to revert to s2. +INSERT INTO toggle_test VALUES (4, 1/0); +ROLLBACK TO SAVEPOINT s2; + +-- 5. COMMIT +COMMIT; + +-- FINAL VERIFICATION +SELECT * FROM toggle_test ORDER BY id; +DROP TABLE toggle_test; + + +-- Start a fresh transaction +BEGIN; + +-- 1. Create the table +CREATE TABLE switchback_test (id INT PRIMARY KEY); + +INSERT INTO switchback_test VALUES (1); + +-- 2. Enter and Release a Savepoint +-- This forces the session to handle Intents. +SAVEPOINT s1; +INSERT INTO switchback_test VALUES (2); -- Must go to Intents +RELEASE SAVEPOINT s1; + +-- 3. THE SWITCHBACK +-- We are back at top level. +INSERT INTO switchback_test VALUES (3); + +-- 4. THE READ CHECK +SELECT * FROM switchback_test ORDER BY id; + +-- 5. COMMIT & FINAL CHECK +COMMIT; +SELECT * FROM switchback_test ORDER BY id; +DROP TABLE switchback_test; diff --git a/src/test/regress/sql/yb.orig.ddl_txn_block.sql b/src/test/regress/sql/yb.orig.ddl_txn_block.sql new file mode 100644 index 0000000000000..ab63aadc1e64a --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_txn_block.sql @@ -0,0 +1,407 @@ +CREATE TABLE test1 (id int PRIMARY KEY); + +-- Test rollback of DDL+DML transaction block. +BEGIN; +CREATE TABLE test2 (id int); +INSERT INTO test1 VALUES (2); +ROLLBACK; + +-- No rows in test1. +SELECT * FROM test1; +-- test2 does not exist. +SELECT * FROM test2; + +-- Test commit of DDL+DML transaction block. +BEGIN; +CREATE TABLE test2 (id int); +INSERT INTO test1 VALUES (2); +COMMIT; + +SELECT * FROM test1; +SELECT * FROM test2; + +-- Test rollback of a block with multiple DDLs +BEGIN; +CREATE TABLE test3 (id int); +CREATE TABLE test4 (id int PRIMARY KEY, b int); +ALTER TABLE test1 ADD COLUMN value text; +INSERT INTO test1 VALUES (3, 'text'); +ROLLBACK; + +SELECT * FROM test3; +SELECT * FROM test4; +SELECT * FROM test1; + +-- Test commit of a block with multiple DDLs +BEGIN; +CREATE TABLE test3 (id int); +CREATE TABLE test4 (id int); +ALTER TABLE test1 ADD COLUMN value text; +INSERT INTO test1 VALUES (3, 'text'); +COMMIT; + +SELECT * FROM test3; +SELECT * FROM test4; +SELECT * FROM test1; + +-- Same test as above but the first statement is a DML +BEGIN; +INSERT INTO test1 VALUES (5, 'text'); +CREATE TABLE test5 (id int); +CREATE TABLE test6 (id int); +ALTER TABLE test1 ADD COLUMN value1 text; +INSERT INTO test1 VALUES (4, 'text', 'text2'); +ROLLBACK; + +SELECT * FROM test5; +SELECT * FROM test6; +SELECT * FROM test1; + +BEGIN; +INSERT INTO test1 VALUES (5, 'text'); +CREATE TABLE test5 (id int); +CREATE TABLE test6 (id int); +ALTER TABLE test1 ADD COLUMN value1 text; +INSERT INTO test1 VALUES (4, 'text', 'text2'); +COMMIT; + +SELECT * FROM test5; +SELECT * FROM test6; +SELECT * FROM test1; + +CREATE INDEX ON test1(value); +SELECT value FROM test1 WHERE value='text'; + +-- Test that schema version bump in case of alter table rollback is handled. +CREATE TABLE test7 (a int primary key, b int); +BEGIN; +INSERT INTO test7 VALUES (1, 1); +INSERT INTO test7 VALUES (2, 2); +ALTER TABLE test7 ADD COLUMN c int; +INSERT INTO test7 VALUES (3, 3, 3); +ROLLBACK; +BEGIN; +INSERT INTO test7 VALUES (1, 1); +COMMIT; +SELECT * FROM test7; + +SET allow_system_table_mods = on; +BEGIN; +-- Truncate system table inside a transaction block. +TRUNCATE pg_extension; +ROLLBACK; +RESET allow_system_table_mods; + +SET yb_enable_alter_table_rewrite = off; +BEGIN; +-- Truncate user table inside a transaction block with table rewrite disabled. +TRUNCATE test7; +ROLLBACK; +RESET yb_enable_alter_table_rewrite; + +-- Rollback CREATE, DROP and CREATE TABLE with same name in a transaction block. +BEGIN; +CREATE TABLE test8 (a int primary key, b int); +INSERT INTO test8 VALUES (1, 1); +SELECT * FROM test8; +DROP TABLE test8; +CREATE TABLE test8 (c int primary key, d int); +INSERT INTO test8 VALUES (10, 10); +ROLLBACK; +SELECT * FROM test8; + +-- Same test as above but with COMMIT. +BEGIN; +CREATE TABLE test8 (a int primary key, b int); +INSERT INTO test8 VALUES (1, 1); +SELECT * FROM test8; +DROP TABLE test8; +CREATE TABLE test8 (c int primary key, d int); +INSERT INTO test8 VALUES (10, 10); +COMMIT; +SELECT * FROM test8; + +-- Rollback of DROP TABLE. +CREATE TABLE test9 (a int primary key, b int); +INSERT INTO test9 VALUES (1, 1); +BEGIN; +INSERT INTO test9 VALUES (2, 2); +SELECT * FROM test9; +DROP TABLE test9; +ROLLBACK; +SELECT * FROM test9; + +-- Rollback of CREATE INDEX should work. +CREATE TABLE test10(id INT PRIMARY KEY, val TEXT); +BEGIN; +CREATE INDEX test10_idx ON test10(val); +\d+ test10; +ROLLBACK; +\d+ test10; + +-- TODO(#3109): CREATE and DROP database are already being tested in various +-- other regress tests. This is being tested here since +-- yb_ddl_transaction_block_enabled is false for all of them. +-- Remove this once yb_ddl_transaction_block_enabled is true by +-- default. +create database k1; +drop database k1; + +CREATE SEQUENCE regtest_seq; +BEGIN; +DROP SEQUENCE regtest_seq; +COMMIT; + +CREATE TABLE test11(id INT PRIMARY KEY, val TEXT); +INSERT INTO test11 VALUES (1, 'text'); +BEGIN; +TRUNCATE test11; +TRUNCATE test11; +SELECT * FROM test11; +ROLLBACK; +SELECT * FROM test11; + +-- Savepoint allowed without any DDL. +CREATE TABLE test12 (a int primary key, b int); +BEGIN; +INSERT INTO test12 VALUES (1, 1); +SAVEPOINT test12_sp; +INSERT INTO test12 VALUES (2, 2); +SELECT * FROM test12; +ROLLBACK TO SAVEPOINT test12_sp; +COMMIT; +SELECT * FROM test12; + +-- DDL after Savepoint disallowed. +BEGIN; +INSERT INTO test12 VALUES (3, 3); +SAVEPOINT test12_sp; +CREATE TABLE test13 (a int primary key, b int); +ROLLBACK; + +BEGIN; +CREATE TEMPORARY TABLE temp_table ( + a INT PRIMARY KEY +) ON COMMIT DELETE ROWS; +INSERT INTO temp_table VALUES (1); +INSERT INTO temp_table VALUES (2); +SELECT * FROM temp_table; +COMMIT; +SELECT * FROM temp_table; + +BEGIN; +CREATE TEMP TABLE temp_table_commit_drop ( + id INT PRIMARY KEY +) +ON COMMIT DROP; +INSERT INTO temp_table_commit_drop VALUES (1); +SELECT * FROM temp_table_commit_drop; +COMMIT; +SELECT * FROM temp_table_commit_drop; +ANALYZE test1, test2, test3; +CREATE INDEX test1_idx ON test1(id); + +CREATE TABLE sales_data ( + sale_id INT, + sale_date DATE, + amount DECIMAL(10, 2) +) PARTITION BY RANGE (sale_date); +CREATE TABLE sales_data_202401 PARTITION OF sales_data + FOR VALUES FROM ('2024-01-01') TO ('2024-02-01'); +INSERT INTO sales_data (sale_id, sale_date, amount) VALUES + (1, '2024-01-10', 100.50), + (2, '2024-01-25', 75.20); +CREATE TABLE sales_data_202402 PARTITION OF sales_data + FOR VALUES FROM ('2024-02-01') TO ('2024-03-01'); +INSERT INTO sales_data (sale_id, sale_date, amount) VALUES + (3, '2024-02-05', 120.00); +ALTER TABLE sales_data DETACH PARTITION sales_data_202401 CONCURRENTLY; + +-- #27859: Statements which do not require a Perform call before DocDB schema +-- changes should succeed. +CREATE TABLE test_14 ( + id INT PRIMARY KEY, + name TEXT, + val INT +); +INSERT INTO test_14 (id, name, val) VALUES +(1, 'Alice', 100), +(2, 'Bob', 200); +CREATE ROLE test_14_user LOGIN; +ALTER TABLE test_14 OWNER TO test_14_user; + +-- #27859: Same test case as above but with enable/disable trigger. +CREATE TABLE test_trigger_table ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + age INT, + email TEXT, + updated_at TIMESTAMPTZ DEFAULT NOW() +); +CREATE TABLE trigger_log ( + action TEXT, + row_id INT, + changed_at TIMESTAMPTZ DEFAULT NOW() +); +CREATE OR REPLACE FUNCTION log_changes() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO trigger_log(action, row_id) + VALUES (TG_OP, NEW.id); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER trg_log_changes +AFTER INSERT OR UPDATE OR DELETE ON test_trigger_table +FOR EACH ROW +EXECUTE FUNCTION log_changes(); +INSERT INTO test_trigger_table(name, age, email) VALUES ('Alice', 30, 'alice@example.com'); +ALTER TABLE test_trigger_table DISABLE TRIGGER trg_log_changes; +INSERT INTO test_trigger_table(name, age, email) VALUES ('Charlie', 28, 'charlie@example.com'); +ALTER TABLE test_trigger_table ENABLE TRIGGER trg_log_changes; +SELECT id, name, age, email FROM test_trigger_table; +SELECT action, row_id FROM trigger_log; + +-- #29058: REINDEX of partitioned table works. +CREATE TABLE test_partitioned (i int, j int) PARTITION BY LIST (j); +CREATE INDEX NONCONCURRENTLY ON test_partitioned (i); +CREATE TABLE test_partitioned_odd PARTITION OF test_partitioned FOR VALUES IN (1, 3, 5, 7, 9); +CREATE TABLE test_partitioned_even PARTITION OF test_partitioned FOR VALUES IN (2, 4, 6, 8); +INSERT INTO test_partitioned SELECT (2 * g), g FROM generate_series(1, 9) g; +-- Mark all partitions are invalid before REINDEX. +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'test_partitioned_i_idx'::regclass; +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'test_partitioned_odd_i_idx'::regclass; +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'test_partitioned_even_i_idx'::regclass; +\c +-- https://github.com/yugabyte/yugabyte-db/issues/29534 +-- The previous \c can be too fast due to incremental relcache refresh optimization. +-- This can cause read restart because a read time of the next REINDEX statement +-- is selected using safetime mechanism which is slightly in the past. If the read +-- time picked is older than the write time of the above UPDATE pg_index statement, +-- we will see restart read error, which is intercepted by PG and shows up as +-- "ERROR: Restarting a DDL transaction not supported". +-- To avoid read restart error, sleep 2 seconds to allow safetime to advance past +-- the write time of the above UPDATE statement. +SELECT pg_sleep(2); +REINDEX INDEX test_partitioned_i_idx; + +\c +-- #29424 +CREATE TABLE _33_s_1_data (id INT PRIMARY KEY); + +CREATE TABLE _33_s_1_audit (log_id SERIAL PRIMARY KEY, data_id INT, notes TEXT); + +CREATE FUNCTION _33_func_audit() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO _33_s_1_audit (data_id, notes) VALUES (NEW.id, 'V1'); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER _33_trig_data +AFTER INSERT ON _33_s_1_data +FOR EACH ROW EXECUTE FUNCTION _33_func_audit(); + +CREATE PROCEDURE _33_sp_insert(val INT) LANGUAGE SQL AS $$ +INSERT INTO _33_s_1_data (id) VALUES (val); +$$; + +CALL _33_sp_insert(1); + +SELECT COUNT(*) FROM _33_s_1_audit WHERE notes = 'V1'; + +BEGIN ISOLATION LEVEL REPEATABLE READ; + +CREATE OR REPLACE FUNCTION _33_func_audit() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO _33_s_1_audit (data_id, notes) VALUES (NEW.id, 'V2'); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE PROCEDURE _33_sp_insert(val INT) LANGUAGE SQL AS $$ +INSERT INTO _33_s_1_data (id) VALUES (val * 100); +$$; + +CALL _33_sp_insert(2); + +SELECT COUNT(*) FROM _33_s_1_data WHERE id = 200; + +SELECT COUNT(*) FROM _33_s_1_audit WHERE notes = 'V2'; + +SELECT * FROM _33_s_1_audit WHERE notes = 'V2'; + +CREATE OR REPLACE PROCEDURE _33_sp_insert(val INT) LANGUAGE SQL AS $$ +INSERT INTO _33_s_1_data (id) VALUES (val * 200); +$$; + +CREATE OR REPLACE FUNCTION _33_func_audit() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO _33_s_1_audit (data_id, notes) VALUES (NEW.id, 'V3'); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CALL _33_sp_insert(3); + +SELECT * FROM _33_s_1_audit WHERE notes = 'V3'; + +-- #29325: Failed ALTER TABLE ALTER TYPE should not lead to a crash in +-- transaction abort. +CREATE TABLE int4_table(id SERIAL, c1 int4, PRIMARY KEY (id ASC)); +ALTER TABLE int4_table ALTER c1 TYPE int8; +INSERT INTO int4_table(c1) VALUES (2 ^ 40); +ALTER TABLE int4_table ALTER c1 TYPE int4; -- should fail. + +-- #30109: duplicate key value violates unique constraint +\c +-- The bug only exists when yb_enable_concurrent_ddl = true +-- We also test yb_enable_concurrent_ddl = false to ensure +-- correctness in both cases. +SET yb_enable_concurrent_ddl = true; + +CREATE TABLE test_table1(); +CREATE OR REPLACE PROCEDURE test_alter1() +LANGUAGE plpgsql +AS $$ +BEGIN + EXECUTE 'ALTER TABLE test_table1 ADD COLUMN id_1 int DEFAULT (random() * 1000000)::int'; + COMMIT; + EXECUTE 'ALTER TABLE test_table1 ADD COLUMN id_2 int DEFAULT (random() * 1000000)::int'; + COMMIT; +END; +$$; +CALL test_alter1(); +\c +SET yb_enable_concurrent_ddl = false; + +CREATE TABLE test_table2(); +CREATE OR REPLACE PROCEDURE test_alter2() +LANGUAGE plpgsql +AS $$ +BEGIN + EXECUTE 'ALTER TABLE test_table2 ADD COLUMN id_1 int DEFAULT (random() * 1000000)::int'; + COMMIT; + EXECUTE 'ALTER TABLE test_table2 ADD COLUMN id_2 int DEFAULT (random() * 1000000)::int'; + COMMIT; +END; +$$; +CALL test_alter2(); +\c +-- end of test of #30109 + +-- Test rollback of in-place index pg_attribute update during ALTER TYPE. +CREATE TABLE test_idx_rollback (val varchar(10)); +CREATE INDEX idx_rollback ON test_idx_rollback(val); +ALTER TABLE test_idx_rollback ALTER COLUMN val TYPE varchar(100); +SELECT atttypmod FROM pg_attribute + WHERE attrelid = 'idx_rollback'::regclass AND attnum = 1; +BEGIN; +ALTER TABLE test_idx_rollback ALTER COLUMN val TYPE varchar(200); +ROLLBACK; +-- atttypmod should revert to 104 (varchar(100)) after rollback. +SELECT atttypmod FROM pg_attribute + WHERE attrelid = 'idx_rollback'::regclass AND attnum = 1; diff --git a/src/test/regress/sql/yb.orig.ddl_txn_copy.sql b/src/test/regress/sql/yb.orig.ddl_txn_copy.sql new file mode 100644 index 0000000000000..02b4c7d6df0b0 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_txn_copy.sql @@ -0,0 +1,35 @@ +-- ----------------------------------------------------------------------------- +-- TEST 1: COPY Failure and Rollback +-- ----------------------------------------------------------------------------- +BEGIN; +CREATE TABLE copy_rollback_test (id INT PRIMARY KEY, name TEXT); + +-- Attempt a copy that fails halfway due to a malformed integer +-- Use a temporary table or file-based simulation +COPY copy_rollback_test FROM STDIN; +1 Alice +2 Bob +three Charlie +\. + +-- On failure, everything should be cleaned up. +ROLLBACK; + +SELECT count(*) FROM copy_rollback_test; + +-- ----------------------------------------------------------------------------- +-- TEST 2: Successful COPY Visibility +-- ----------------------------------------------------------------------------- +BEGIN; +CREATE TABLE copy_vis_test (id INT PRIMARY KEY); + +COPY copy_vis_test FROM STDIN; +1 +2 +3 +\. + +-- Verify immediate visibility after COPY +SELECT count(*) FROM copy_vis_test; + +COMMIT; diff --git a/src/test/regress/sql/yb.orig.ddl_txn_event_triggers.sql b/src/test/regress/sql/yb.orig.ddl_txn_event_triggers.sql new file mode 100644 index 0000000000000..402964c9aa783 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_txn_event_triggers.sql @@ -0,0 +1,199 @@ +-- Test trigger behavior on tables created within the same transaction block. +-- This ensures that the trigger execution engine, catalog lookups, and +-- recursive I/O are consistent for uncommitted objects. + +-- Setup: An existing table for global auditing +CREATE TABLE global_audit_log ( + msg TEXT, + created_at TIMESTAMPTZ DEFAULT '2026-01-01 00:00:00+00' +); + +-- 1. AFTER ROW TRIGGER: Writing to an EXISTING table +BEGIN; +CREATE TABLE trg_test_existing (id INT PRIMARY KEY, val TEXT); + +CREATE OR REPLACE FUNCTION audit_to_existing() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO global_audit_log (msg) VALUES ('Row added: ' || NEW.val); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_audit_existing +AFTER INSERT ON trg_test_existing +FOR EACH ROW EXECUTE FUNCTION audit_to_existing(); + +INSERT INTO trg_test_existing VALUES (1, 'Data A'); + +SELECT * FROM trg_test_existing; +SELECT msg FROM global_audit_log; +ROLLBACK; + +-- 2. AFTER ROW TRIGGER: Writing to ANOTHER NEW table +BEGIN; +CREATE TABLE source_new (id INT PRIMARY KEY, val TEXT); +CREATE TABLE target_new (id INT PRIMARY KEY, source_val TEXT); + +CREATE OR REPLACE FUNCTION write_to_new_table() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO target_new VALUES (NEW.id, NEW.val); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_double_new +AFTER INSERT ON source_new +FOR EACH ROW EXECUTE FUNCTION write_to_new_table(); + +INSERT INTO source_new VALUES (10, 'Data B'); + +SELECT * FROM source_new; +SELECT * FROM target_new; +ROLLBACK; + +-- 3. BEFORE ROW TRIGGER: Modifying payload on NEW table +BEGIN; +CREATE TABLE before_test (id INT PRIMARY KEY, val TEXT); + +CREATE OR REPLACE FUNCTION modify_payload() RETURNS TRIGGER AS $$ +BEGIN + NEW.val := UPPER(NEW.val); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_before +BEFORE INSERT ON before_test +FOR EACH ROW EXECUTE FUNCTION modify_payload(); + +INSERT INTO before_test VALUES (5, 'lowercase'); + +SELECT * FROM before_test; +ROLLBACK; + +-- 4. UNIQUE CONSTRAINT WITH BEFORE TRIGGER MODIFICATION +-- Ensures the index update correctly captures values modified by triggers. +BEGIN; +CREATE TABLE trigger_unique_test (id INT PRIMARY KEY, code TEXT); +CREATE UNIQUE INDEX idx_code_unique ON trigger_unique_test (code); + +CREATE OR REPLACE FUNCTION normalize_code() RETURNS TRIGGER AS $$ +BEGIN + NEW.code := UPPER(NEW.code); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_normalize +BEFORE INSERT ON trigger_unique_test +FOR EACH ROW EXECUTE FUNCTION normalize_code(); + +-- Insert 'abc', trigger turns it into 'ABC' +INSERT INTO trigger_unique_test VALUES (1, 'abc'); +SELECT * FROM trigger_unique_test WHERE code = 'ABC'; + +-- This should fail because 'abc' was normalized to 'ABC' +INSERT INTO trigger_unique_test VALUES (2, 'ABC'); +ROLLBACK; + +-- 5. RECURSIVE TRIGGER: Writing to SELF +BEGIN; +CREATE TABLE self_recursive (id INT PRIMARY KEY, val TEXT, is_auto BOOLEAN DEFAULT false); + +CREATE OR REPLACE FUNCTION write_to_self() RETURNS TRIGGER AS $$ +BEGIN + IF NEW.is_auto = false THEN + INSERT INTO self_recursive VALUES (NEW.id + 100, 'Auto: ' || NEW.val, true); + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_self +AFTER INSERT ON self_recursive +FOR EACH ROW EXECUTE FUNCTION write_to_self(); + +INSERT INTO self_recursive VALUES (1, 'Original'); + +SELECT * FROM self_recursive ORDER BY id; +ROLLBACK; + +-- 6. CROSS-ROW DEPENDENCY (THE "SWAP" TEST) +BEGIN; +CREATE TABLE trigger_swap_test (id INT PRIMARY KEY, val INT); +CREATE UNIQUE INDEX idx_val_unique ON trigger_swap_test (val); + +CREATE OR REPLACE FUNCTION plus_one() RETURNS TRIGGER AS $$ +BEGIN + NEW.val := NEW.val + 1; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_plus_one +BEFORE INSERT ON trigger_swap_test +FOR EACH ROW EXECUTE FUNCTION plus_one(); + +-- Session inserts 10, trigger makes it 11 +INSERT INTO trigger_swap_test VALUES (1, 10); +-- Session inserts 11, trigger makes it 12 +INSERT INTO trigger_swap_test VALUES (2, 11); + +SELECT * FROM trigger_swap_test ORDER BY id; +ROLLBACK; + +-- Test: Statement-level trigger on a new table during a bulk insert. +BEGIN; +CREATE TABLE stmt_test (id INT PRIMARY KEY); +CREATE TABLE stmt_log (log_msg TEXT); + +-- Function for statement-level trigger (returns NULL as per PG standards) +CREATE OR REPLACE FUNCTION log_stmt_action() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO stmt_log VALUES ('Bulk Insert Performed on stmt_test'); + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +-- Trigger defined for STATEMENT, not ROW +CREATE TRIGGER trg_stmt_bulk +AFTER INSERT ON stmt_test +FOR EACH STATEMENT EXECUTE FUNCTION log_stmt_action(); + +-- Perform a bulk insert (5 rows) +-- In DocDB, this might be a single RPC or a batched set of writes. +INSERT INTO stmt_test SELECT generate_series(1, 5); + +-- Verify: 5 rows in data table, but only 1 row in log table +SELECT COUNT(*) FROM stmt_test; +SELECT * FROM stmt_log; +ROLLBACK; + +-- Test: INSTEAD OF trigger on a View that redirects to a newly created table. +BEGIN; +-- 1. Create the physical storage (New Table) +CREATE TABLE trigger_view_target (id INT PRIMARY KEY, val TEXT); + +-- 2. Create the View on that table +CREATE VIEW v_trigger_test AS SELECT * FROM trigger_view_target; + +-- 3. Create the INSTEAD OF logic +CREATE OR REPLACE FUNCTION redirect_to_table() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO trigger_view_target VALUES (NEW.id, 'Redirected: ' || NEW.val); + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_instead_of +INSTEAD OF INSERT ON v_trigger_test +FOR EACH ROW EXECUTE FUNCTION redirect_to_table(); + +-- 4. Perform the INSERT on the VIEW +INSERT INTO v_trigger_test VALUES (100, 'Original Input'); + +-- 5. Verify: The view (and underlying table) should show the modified data +SELECT * FROM v_trigger_test; +SELECT * FROM trigger_view_target; + +ROLLBACK; diff --git a/src/test/regress/sql/yb.orig.ddl_txn_fk.sql b/src/test/regress/sql/yb.orig.ddl_txn_fk.sql new file mode 100644 index 0000000000000..195bf0ea2ae0c --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_txn_fk.sql @@ -0,0 +1,68 @@ +-- ----------------------------------------------------------------------------- +-- SCENARIO 1: Parent exists, Child is new +-- ----------------------------------------------------------------------------- +CREATE TABLE fk_parent_exists (id INT PRIMARY KEY); +INSERT INTO fk_parent_exists VALUES (1); + +BEGIN; +-- Child is new +CREATE TABLE fk_child_new ( + id INT PRIMARY KEY, + p_id INT REFERENCES fk_parent_exists(id) +); + +-- Should succeed: Parent 1 exists in Regular DB +INSERT INTO fk_child_new VALUES (100, 1); + +-- Should fail: Parent 99 does not exist +INSERT INTO fk_child_new VALUES (101, 99); + +ROLLBACK; + +-- ----------------------------------------------------------------------------- +-- SCENARIO 2: Both Parent and Child created in the same Transaction +-- ----------------------------------------------------------------------------- +-- This tests if the FK check on 'child' can see the uncommitted 'direct' writes +-- to 'parent'. +BEGIN; + +CREATE TABLE fk_parent_txn (id INT PRIMARY KEY); +CREATE TABLE fk_child_txn ( + id INT PRIMARY KEY, + p_id INT REFERENCES fk_parent_txn(id) +); + +-- 1. Insert into parent +INSERT INTO fk_parent_txn VALUES (1); + +-- 2. Insert into child +-- This requires the FK check to look into fk_parent_txn's Regular DB entries. +INSERT INTO fk_child_txn VALUES (10, 1); + +-- 3. Trigger a failure to ensure both roll back physically +SAVEPOINT sp1; +INSERT INTO fk_child_txn VALUES (11, 99); -- FK Violation +ROLLBACK TO SAVEPOINT sp1; + +-- 4. Verify visibility within the transaction +SELECT count(*) FROM fk_child_txn; + +COMMIT; + +-- ----------------------------------------------------------------------------- +-- SCENARIO 3: Circular/Self-Referencing FK in same Transaction +-- ----------------------------------------------------------------------------- +BEGIN; + +CREATE TABLE fk_self_ref ( + id INT PRIMARY KEY, + parent_id INT REFERENCES fk_self_ref(id) +); + +-- Inserting a row that references itself. +INSERT INTO fk_self_ref VALUES (1, 1); + +-- Should fail +INSERT INTO fk_self_ref VALUES (2, 3); + +COMMIT; diff --git a/src/test/regress/sql/yb.orig.ddl_txn_indices.sql b/src/test/regress/sql/yb.orig.ddl_txn_indices.sql new file mode 100644 index 0000000000000..28b93c7830eed --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_txn_indices.sql @@ -0,0 +1,81 @@ +-- ----------------------------------------------------------------------------- +-- TEST 1: Orphan Index Entry Check (Failure Rollback) +-- ----------------------------------------------------------------------------- +-- Verifies that a statement failure cleaned up index entries. +BEGIN; +CREATE TABLE idx_test (id INT PRIMARY KEY, val INT); +CREATE INDEX ON idx_test(val); + +-- Trigger a failure late in the statement. +-- Rows 1-100 will land in Regular DB (if optimized). +INSERT INTO idx_test (id, val) +SELECT i, CASE WHEN i <= 100 THEN i ELSE 1/0 END +FROM generate_series(1, 101) i; + +-- On error, the txn is aborted. +ROLLBACK; + +-- Verify index is cleaned up. Index-only scan ensures we check the index tablet. +SET enable_seqscan = off; +SELECT count(val) FROM idx_test; +RESET enable_seqscan; + +-- ----------------------------------------------------------------------------- +-- TEST 2: Multi-Statement Index Visibility +-- ----------------------------------------------------------------------------- +-- Verifies that a write to an index is visible to subsequent index-scans in +-- the same transaction (checks HT advancement). +BEGIN; +CREATE TABLE idx_vis_test (id INT PRIMARY KEY, val INT); +CREATE INDEX ON idx_vis_test(val); + +-- Statement 1: Write directly to Regular DB +INSERT INTO idx_vis_test VALUES (1, 10), (2, 20); + +-- Statement 2: Read via Index +-- Must advance HT to see previous statement's index writes. +SET enable_seqscan = off; +SELECT * FROM idx_vis_test WHERE val = 20; +RESET enable_seqscan; + +COMMIT; + +-- ----------------------------------------------------------------------------- +-- TEST 3: Unique Constraint Violation (Non-PK) +-- ----------------------------------------------------------------------------- +-- Tests the "Read-Before-Write" path. The second insert must see the +-- first insert's data in the Regular DB to trigger the conflict. +BEGIN; + +CREATE TABLE unique_test ( + id INT PRIMARY KEY, + val INT UNIQUE +); + +-- First Insert +INSERT INTO unique_test VALUES (1, 100); + +-- Second Insert (Must collide with the value in Regular DB) +-- If this succeeds, your HT logic has caused data corruption. +INSERT INTO unique_test VALUES (2, 100); + +ROLLBACK; + +-- ----------------------------------------------------------------------------- +-- TEST 4: Partial Failure in Multi-Row Unique Insert +-- ----------------------------------------------------------------------------- +-- Tests statement-level atomicity when a single batch contains a conflict. +BEGIN; +CREATE TABLE unique_multi_test (id INT PRIMARY KEY, val INT UNIQUE); + +-- Statement fails on the 3rd row. +-- Rows 1 and 2 must not be visible after the statement fails. +INSERT INTO unique_multi_test VALUES + (1, 10), + (2, 20), + (3, 10); + +-- If the statement rollback fails, these might persist. +SELECT count(*) FROM unique_multi_test; + +ROLLBACK; diff --git a/src/test/regress/sql/yb.orig.ddl_txn_isolation.sql b/src/test/regress/sql/yb.orig.ddl_txn_isolation.sql new file mode 100644 index 0000000000000..5254a9e9e2c6c --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_txn_isolation.sql @@ -0,0 +1,207 @@ +-- Test isolation and atomicity for DDL and DML combined in a transaction block. +-- This suite ensures that objects created within a transaction are handled correctly +-- by the catalog and storage layers during various manipulation scenarios. + +-- BASIC CRUD ON NEWLY CREATED TABLE +-- Ensure we can perform all DML operations on a table that doesn't yet exist globally. +BEGIN; +CREATE TABLE txn_isolation_test (id INT PRIMARY KEY, val TEXT, v2 INT); +INSERT INTO txn_isolation_test VALUES (1, 'initial', 10); + +-- Verify internal visibility: Should see 1 row +SELECT * FROM txn_isolation_test; + +UPDATE txn_isolation_test SET val = 'updated' WHERE id = 1; +-- Verify update: Should see 'updated' +SELECT * FROM txn_isolation_test; + +DELETE FROM txn_isolation_test WHERE id = 1; +-- Verify delete: Should see 0 rows +SELECT COUNT(*) FROM txn_isolation_test; +ROLLBACK; + +-- UNIQUE CONSTRAINT ENFORCEMENT +-- Verify that uniqueness is enforced within the transaction even if the index is new. +BEGIN; +CREATE TABLE txn_unique_test (id INT PRIMARY KEY, email TEXT); +CREATE UNIQUE INDEX ON txn_unique_test (email); + +INSERT INTO txn_unique_test VALUES (1, 'test@yugabyte.com'); + +-- This update should succeed (self-overwrite) +UPDATE txn_unique_test SET email = 'test@yugabyte.com' WHERE id = 1; +SELECT * FROM txn_unique_test; + +-- This should fail with a duplicate key error +-- We use a sub-transaction (anonymous block) or just let it fail and rollback. +INSERT INTO txn_unique_test VALUES (2, 'test@yugabyte.com'); +ROLLBACK; + +-- SELECT FOR UPDATE (LOCKING) ON NEW TABLE +-- Verify that locking works correctly on rows that have not yet been committed. +BEGIN; +CREATE TABLE txn_lock_test (id INT PRIMARY KEY, status TEXT); +INSERT INTO txn_lock_test VALUES (1, 'locked'); + +-- Should return the row and place a strong lock +SELECT * FROM txn_lock_test WHERE id = 1 FOR UPDATE; + +UPDATE txn_lock_test SET status = 'processed' WHERE id = 1; +COMMIT; + +-- Post-commit check +SELECT * FROM txn_lock_test; + +-- SIMPLE SAVEPOINT AND ATOMICITY +-- Verify that partial rollbacks correctly restore the state of a new table. +BEGIN; +CREATE TABLE txn_savepoint_test (a INT PRIMARY KEY); +INSERT INTO txn_savepoint_test VALUES (1); + +SAVEPOINT sp1; + INSERT INTO txn_savepoint_test VALUES (2); + INSERT INTO txn_savepoint_test VALUES (3); + SELECT COUNT(*) FROM txn_savepoint_test; -- Should see 3 +ROLLBACK TO SAVEPOINT sp1; + +-- Should only see row 1 +SELECT * FROM txn_savepoint_test ORDER BY a; +COMMIT; + +-- IDENTITY AND SEQUENCE BEHAVIOR +-- Verify that sequences created in-transaction work with new tables. +BEGIN; +CREATE TABLE txn_identity_test ( + id SERIAL PRIMARY KEY, + name TEXT +); +INSERT INTO txn_identity_test (name) VALUES ('Alpha'), ('Beta'); +SELECT * FROM txn_identity_test ORDER BY id; +ROLLBACK; + +-- DDL INTERACTION: ADDING COLUMNS AFTER INSERT +-- Verify that schema changes to a new table interact correctly with existing uncommitted data. +BEGIN; +CREATE TABLE txn_alter_test (id INT PRIMARY KEY); +INSERT INTO txn_alter_test VALUES (1); + +ALTER TABLE txn_alter_test ADD COLUMN new_col TEXT DEFAULT 'default_val'; +-- Should see the default value for the existing row +SELECT * FROM txn_alter_test; + +INSERT INTO txn_alter_test VALUES (2, 'custom_val'); +SELECT * FROM txn_alter_test ORDER BY id; +COMMIT; + +-- More involved save point test case +CREATE OR REPLACE FUNCTION process_work1() RETURNS void AS $$ +BEGIN + CREATE TABLE users1 (first_name TEXT, last_name TEXT); + CREATE TABLE audit_log1 ( + id INT PRIMARY KEY, + msg TEXT + ); + INSERT INTO users1 SELECT md5(random()::text), md5(random()::text) FROM + (SELECT * FROM generate_series(1,10000) AS id) AS id; + INSERT INTO audit_log1 VALUES (1, 'Starting the main process...'); -- This is part of the main transaction. + + -- >>> PostgreSQL creates an internal SAVEPOINT here <<< + BEGIN + INSERT INTO audit_log1 VALUES (2, 'Processing a sub-task...'); + + -- This next line will fail because of the primary key constraint (id=1 already exists) + INSERT INTO audit_log1 VALUES (1, 'This will cause a unique_violation error!'); + + EXCEPTION + WHEN unique_violation THEN + -- >>> PostgreSQL has already performed an internal ROLLBACK TO SAVEPOINT before this + -- code runs <<< + RAISE NOTICE 'Caught a duplicate key! The sub-task was rolled back.'; + END; + -- >>> If the block had succeeded, an internal RELEASE SAVEPOINT would happen here <<< + + INSERT INTO audit_log1 VALUES (3, 'Main process finished.'); -- This runs because the error was handled. +END; +$$ LANGUAGE plpgsql; +-- Clear the table and run the function +SELECT process_work1(); + +-- Check the final state of the table +SELECT * FROM audit_log1 ORDER BY id; +SELECT COUNT(*) FROM users1; + + +-- create table users2 is outside begin exception block +-- insert into users2 is inside begin exception block +CREATE OR REPLACE FUNCTION process_work2() RETURNS void AS $$ +BEGIN + CREATE TABLE users2 (first_name TEXT, last_name TEXT); + CREATE TABLE audit_log2 ( + id INT PRIMARY KEY, + msg TEXT + ); + INSERT INTO audit_log2 VALUES (1, 'Starting the main process...'); -- This is part of the main transaction. + + -- >>> PostgreSQL creates an internal SAVEPOINT here <<< + BEGIN + INSERT INTO users2 SELECT md5(random()::text), md5(random()::text) FROM + (SELECT * FROM generate_series(1,10000) AS id) AS id; + INSERT INTO audit_log2 VALUES (2, 'Processing a sub-task...'); + + -- This next line will fail because of the primary key constraint (id=1 already exists) + INSERT INTO audit_log2 VALUES (1, 'This will cause a unique_violation error!'); + + EXCEPTION + WHEN unique_violation THEN + -- >>> PostgreSQL has already performed an internal ROLLBACK TO SAVEPOINT before this + -- code runs <<< + RAISE NOTICE 'Caught a duplicate key! The sub-task was rolled back.'; + END; + -- >>> If the block had succeeded, an internal RELEASE SAVEPOINT would happen here <<< + + INSERT INTO audit_log2 VALUES (3, 'Main process finished.'); -- This runs because the error was handled. +END; +$$ LANGUAGE plpgsql; +-- Clear the table and run the function +SELECT process_work2(); + +-- Check the final state of the table +SELECT * FROM audit_log2 ORDER BY id; +SELECT COUNT(*) FROM users2; + +-- both create table users3 and insert into users3 are inside begin exception block +CREATE OR REPLACE FUNCTION process_work3() RETURNS void AS $$ +BEGIN + CREATE TABLE audit_log3 ( + id INT PRIMARY KEY, + msg TEXT + ); + INSERT INTO audit_log3 VALUES (1, 'Starting the main process...'); -- This is part of the main transaction. + + -- >>> PostgreSQL creates an internal SAVEPOINT here <<< + BEGIN + CREATE TABLE users3 (first_name TEXT, last_name TEXT); + INSERT INTO users3 SELECT md5(random()::text), md5(random()::text) FROM + (SELECT * FROM generate_series(1,10000) AS id) AS id; + INSERT INTO audit_log3 VALUES (2, 'Processing a sub-task...'); + + -- This next line will fail because of the primary key constraint (id=1 already exists) + INSERT INTO audit_log3 VALUES (1, 'This will cause a unique_violation error!'); + + EXCEPTION + WHEN unique_violation THEN + -- >>> PostgreSQL has already performed an internal ROLLBACK TO SAVEPOINT before this + -- code runs <<< + RAISE NOTICE 'Caught a duplicate key! The sub-task was rolled back.'; + END; + -- >>> If the block had succeeded, an internal RELEASE SAVEPOINT would happen here <<< + + INSERT INTO audit_log3 VALUES (3, 'Main process finished.'); -- This runs because the error was handled. +END; +$$ LANGUAGE plpgsql; +-- Clear the table and run the function +SELECT process_work3(); + +-- Check the final state of the table +SELECT * FROM audit_log3 ORDER BY id; +SELECT COUNT(*) FROM users3; diff --git a/src/test/regress/sql/yb.orig.ddl_txn_procedures.sql b/src/test/regress/sql/yb.orig.ddl_txn_procedures.sql new file mode 100644 index 0000000000000..225f583974c94 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_txn_procedures.sql @@ -0,0 +1,93 @@ +-- Test transactional behavior and metadata consistency within Stored Procedures. +-- Verifies that data modifications and schema changes are correctly handled +-- when performed on tables created within the same uncommitted transaction. + +-- 1. Simple Procedure DML on a new table +-- Verifies that a procedure can modify a table created in the current transaction. +BEGIN; + +CREATE TABLE proc_test (id INT PRIMARY KEY, val TEXT); + +CREATE OR REPLACE PROCEDURE insert_data(p_id INT, p_val TEXT) AS $$ +BEGIN + INSERT INTO proc_test VALUES (p_id, p_val); +END; +$$ LANGUAGE plpgsql; + +CALL insert_data(1, 'from procedure'); + +SELECT * FROM proc_test; + +ROLLBACK; + +-- 2. Nested Procedure Calls +-- Verifies that transactional context and data visibility propagate +-- through multiple levels of the procedure call stack. +BEGIN; + +CREATE TABLE nested_proc_test (id INT PRIMARY KEY, val INT); + +CREATE OR REPLACE PROCEDURE inner_proc(p_id INT) AS $$ +BEGIN + UPDATE nested_proc_test SET val = val * 2 WHERE id = p_id; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE PROCEDURE outer_proc(p_id INT) AS $$ +BEGIN + INSERT INTO nested_proc_test VALUES (p_id, 10); + CALL inner_proc(p_id); +END; +$$ LANGUAGE plpgsql; + +CALL outer_proc(100); + +SELECT * FROM nested_proc_test; -- Expected: (100, 20) + +ROLLBACK; + +-- 3. Procedure with multiple DML types +-- Verifies that different types of data modifications (Insert, Update, Delete) +-- all maintain consistency when targeting a table new to the transaction. +BEGIN; + +CREATE TABLE proc_multi_test (id INT PRIMARY KEY, val INT); + +CREATE OR REPLACE PROCEDURE complex_logic(p_id INT) AS $$ +BEGIN + INSERT INTO proc_multi_test VALUES (p_id, 1); + UPDATE proc_multi_test SET val = val + 9 WHERE id = p_id; + DELETE FROM proc_multi_test WHERE id = p_id AND val < 5; +END; +$$ LANGUAGE plpgsql; + +CALL complex_logic(1); + +SELECT * FROM proc_multi_test; -- Expected: (1, 10) + +ROLLBACK; + +-- 4. Procedure with Schema Evolution (ALTER TABLE) +-- Verifies that data modifications correctly adapt to schema changes +-- that occur within a procedure for a table created in the same transaction. +BEGIN; + +CREATE TABLE proc_schema_test (id INT PRIMARY KEY); + +CREATE OR REPLACE PROCEDURE alter_and_load(p_id INT, p_new_val TEXT) AS $$ +BEGIN + -- Alter the table created in this same transaction. + -- This tests local catalog versioning and metadata resolution. + ALTER TABLE proc_schema_test ADD COLUMN extra_info TEXT; + + -- Insert into the table using the updated schema. + INSERT INTO proc_schema_test VALUES (p_id, p_new_val); +END; +$$ LANGUAGE plpgsql; + +CALL alter_and_load(1, 'Snapshot A'); + +-- Verify the column exists and the data is correctly mapped to the new schema. +SELECT * FROM proc_schema_test; + +ROLLBACK; diff --git a/src/test/regress/sql/yb.orig.ddl_txn_recursive_functions.sql b/src/test/regress/sql/yb.orig.ddl_txn_recursive_functions.sql new file mode 100644 index 0000000000000..a3f9a89e3e6a6 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_txn_recursive_functions.sql @@ -0,0 +1,114 @@ +-- Test recursive DML and nested execution contexts on newly created tables. +-- These tests ensure that storage operations (including deletions and truncations) +-- behave correctly when both the data and the logic are uncommitted. + +-- 1. UPDATE inside a function during a SELECT +-- Verifies "Read-Your-Own-Writes" consistency in a nested context. +BEGIN; + +CREATE TABLE recursive_update_test ( + id INT PRIMARY KEY, + val INT, + status TEXT DEFAULT 'original' +); + +CREATE OR REPLACE FUNCTION update_and_return(row_id INT) +RETURNS INT AS $$ +BEGIN + UPDATE recursive_update_test + SET val = val + 10, + status = 'modified_by_fn' + WHERE id = row_id; + + RETURN row_id; +END; +$$ LANGUAGE plpgsql; + +INSERT INTO recursive_update_test VALUES (1, 100), (2, 200); + +-- Parent scan and nested update both targeting the same tablets. +SELECT id, val, update_and_return(id) FROM recursive_update_test ORDER BY id; + +-- Verify modifications are visible. +SELECT * FROM recursive_update_test ORDER BY id; + +ROLLBACK; + +-- 2. INSERT inside a function during a SELECT +-- Verifies table growth handling during an active scan. +BEGIN; + +CREATE TABLE recursive_insert_test (id INT PRIMARY KEY); + +CREATE OR REPLACE FUNCTION insert_another(current_id INT) +RETURNS INT AS $$ +BEGIN + IF current_id < 10 THEN + INSERT INTO recursive_insert_test VALUES (current_id + 10); + END IF; + RETURN current_id; +END; +$$ LANGUAGE plpgsql; + +INSERT INTO recursive_insert_test VALUES (1); + +-- Standard visibility usually prevents the current scan from seeing the new row. +SELECT id, insert_another(id) FROM recursive_insert_test; + +-- Verify the row was indeed added. +SELECT * FROM recursive_insert_test ORDER BY id; + +ROLLBACK; + +-- 3. DELETE inside a function during a SELECT +-- Verifies that tombstones written to uncommitted tables are correctly handled. +BEGIN; + +CREATE TABLE recursive_delete_test ( + id INT PRIMARY KEY, + val TEXT +); + +CREATE OR REPLACE FUNCTION delete_next_row(current_id INT) +RETURNS TEXT AS $$ +BEGIN + -- Delete the "next" row in the sequence. + DELETE FROM recursive_delete_test WHERE id = current_id + 1; + RETURN 'processed ' || current_id; +END; +$$ LANGUAGE plpgsql; + +INSERT INTO recursive_delete_test VALUES (1, 'keep'), (2, 'delete me'), (3, 'keep'); + +-- When processing row 1, the function deletes row 2. +SELECT id, delete_next_row(id) FROM recursive_delete_test ORDER BY id; + +-- Verify row 2 is gone. +SELECT * FROM recursive_delete_test ORDER BY id; + +ROLLBACK; + +-- 4. TRUNCATE inside a function +-- High-stress test: clearing storage mid-scan. +BEGIN; + +CREATE TABLE truncate_recursive_test (id INT PRIMARY KEY); +INSERT INTO truncate_recursive_test SELECT generate_series(1, 10); + +CREATE OR REPLACE FUNCTION nuke_table(input_id INT) +RETURNS INT AS $$ +BEGIN + -- Only truncate on the first iteration. + IF input_id = 1 THEN + TRUNCATE truncate_recursive_test; + END IF; + RETURN input_id; +END; +$$ LANGUAGE plpgsql; + +-- The scan is mid-flight when the table is truncated. +SELECT id, nuke_table(id) FROM truncate_recursive_test ORDER BY id; + +SELECT COUNT(*) FROM truncate_recursive_test; + +ROLLBACK; diff --git a/src/test/regress/sql/yb.orig.ddl_txn_triggers.sql b/src/test/regress/sql/yb.orig.ddl_txn_triggers.sql new file mode 100644 index 0000000000000..402964c9aa783 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_txn_triggers.sql @@ -0,0 +1,199 @@ +-- Test trigger behavior on tables created within the same transaction block. +-- This ensures that the trigger execution engine, catalog lookups, and +-- recursive I/O are consistent for uncommitted objects. + +-- Setup: An existing table for global auditing +CREATE TABLE global_audit_log ( + msg TEXT, + created_at TIMESTAMPTZ DEFAULT '2026-01-01 00:00:00+00' +); + +-- 1. AFTER ROW TRIGGER: Writing to an EXISTING table +BEGIN; +CREATE TABLE trg_test_existing (id INT PRIMARY KEY, val TEXT); + +CREATE OR REPLACE FUNCTION audit_to_existing() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO global_audit_log (msg) VALUES ('Row added: ' || NEW.val); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_audit_existing +AFTER INSERT ON trg_test_existing +FOR EACH ROW EXECUTE FUNCTION audit_to_existing(); + +INSERT INTO trg_test_existing VALUES (1, 'Data A'); + +SELECT * FROM trg_test_existing; +SELECT msg FROM global_audit_log; +ROLLBACK; + +-- 2. AFTER ROW TRIGGER: Writing to ANOTHER NEW table +BEGIN; +CREATE TABLE source_new (id INT PRIMARY KEY, val TEXT); +CREATE TABLE target_new (id INT PRIMARY KEY, source_val TEXT); + +CREATE OR REPLACE FUNCTION write_to_new_table() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO target_new VALUES (NEW.id, NEW.val); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_double_new +AFTER INSERT ON source_new +FOR EACH ROW EXECUTE FUNCTION write_to_new_table(); + +INSERT INTO source_new VALUES (10, 'Data B'); + +SELECT * FROM source_new; +SELECT * FROM target_new; +ROLLBACK; + +-- 3. BEFORE ROW TRIGGER: Modifying payload on NEW table +BEGIN; +CREATE TABLE before_test (id INT PRIMARY KEY, val TEXT); + +CREATE OR REPLACE FUNCTION modify_payload() RETURNS TRIGGER AS $$ +BEGIN + NEW.val := UPPER(NEW.val); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_before +BEFORE INSERT ON before_test +FOR EACH ROW EXECUTE FUNCTION modify_payload(); + +INSERT INTO before_test VALUES (5, 'lowercase'); + +SELECT * FROM before_test; +ROLLBACK; + +-- 4. UNIQUE CONSTRAINT WITH BEFORE TRIGGER MODIFICATION +-- Ensures the index update correctly captures values modified by triggers. +BEGIN; +CREATE TABLE trigger_unique_test (id INT PRIMARY KEY, code TEXT); +CREATE UNIQUE INDEX idx_code_unique ON trigger_unique_test (code); + +CREATE OR REPLACE FUNCTION normalize_code() RETURNS TRIGGER AS $$ +BEGIN + NEW.code := UPPER(NEW.code); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_normalize +BEFORE INSERT ON trigger_unique_test +FOR EACH ROW EXECUTE FUNCTION normalize_code(); + +-- Insert 'abc', trigger turns it into 'ABC' +INSERT INTO trigger_unique_test VALUES (1, 'abc'); +SELECT * FROM trigger_unique_test WHERE code = 'ABC'; + +-- This should fail because 'abc' was normalized to 'ABC' +INSERT INTO trigger_unique_test VALUES (2, 'ABC'); +ROLLBACK; + +-- 5. RECURSIVE TRIGGER: Writing to SELF +BEGIN; +CREATE TABLE self_recursive (id INT PRIMARY KEY, val TEXT, is_auto BOOLEAN DEFAULT false); + +CREATE OR REPLACE FUNCTION write_to_self() RETURNS TRIGGER AS $$ +BEGIN + IF NEW.is_auto = false THEN + INSERT INTO self_recursive VALUES (NEW.id + 100, 'Auto: ' || NEW.val, true); + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_self +AFTER INSERT ON self_recursive +FOR EACH ROW EXECUTE FUNCTION write_to_self(); + +INSERT INTO self_recursive VALUES (1, 'Original'); + +SELECT * FROM self_recursive ORDER BY id; +ROLLBACK; + +-- 6. CROSS-ROW DEPENDENCY (THE "SWAP" TEST) +BEGIN; +CREATE TABLE trigger_swap_test (id INT PRIMARY KEY, val INT); +CREATE UNIQUE INDEX idx_val_unique ON trigger_swap_test (val); + +CREATE OR REPLACE FUNCTION plus_one() RETURNS TRIGGER AS $$ +BEGIN + NEW.val := NEW.val + 1; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_plus_one +BEFORE INSERT ON trigger_swap_test +FOR EACH ROW EXECUTE FUNCTION plus_one(); + +-- Session inserts 10, trigger makes it 11 +INSERT INTO trigger_swap_test VALUES (1, 10); +-- Session inserts 11, trigger makes it 12 +INSERT INTO trigger_swap_test VALUES (2, 11); + +SELECT * FROM trigger_swap_test ORDER BY id; +ROLLBACK; + +-- Test: Statement-level trigger on a new table during a bulk insert. +BEGIN; +CREATE TABLE stmt_test (id INT PRIMARY KEY); +CREATE TABLE stmt_log (log_msg TEXT); + +-- Function for statement-level trigger (returns NULL as per PG standards) +CREATE OR REPLACE FUNCTION log_stmt_action() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO stmt_log VALUES ('Bulk Insert Performed on stmt_test'); + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +-- Trigger defined for STATEMENT, not ROW +CREATE TRIGGER trg_stmt_bulk +AFTER INSERT ON stmt_test +FOR EACH STATEMENT EXECUTE FUNCTION log_stmt_action(); + +-- Perform a bulk insert (5 rows) +-- In DocDB, this might be a single RPC or a batched set of writes. +INSERT INTO stmt_test SELECT generate_series(1, 5); + +-- Verify: 5 rows in data table, but only 1 row in log table +SELECT COUNT(*) FROM stmt_test; +SELECT * FROM stmt_log; +ROLLBACK; + +-- Test: INSTEAD OF trigger on a View that redirects to a newly created table. +BEGIN; +-- 1. Create the physical storage (New Table) +CREATE TABLE trigger_view_target (id INT PRIMARY KEY, val TEXT); + +-- 2. Create the View on that table +CREATE VIEW v_trigger_test AS SELECT * FROM trigger_view_target; + +-- 3. Create the INSTEAD OF logic +CREATE OR REPLACE FUNCTION redirect_to_table() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO trigger_view_target VALUES (NEW.id, 'Redirected: ' || NEW.val); + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_instead_of +INSTEAD OF INSERT ON v_trigger_test +FOR EACH ROW EXECUTE FUNCTION redirect_to_table(); + +-- 4. Perform the INSERT on the VIEW +INSERT INTO v_trigger_test VALUES (100, 'Original Input'); + +-- 5. Verify: The view (and underlying table) should show the modified data +SELECT * FROM v_trigger_test; +SELECT * FROM trigger_view_target; + +ROLLBACK; diff --git a/src/test/regress/sql/yb.orig.ddl_txn_visibility.sql b/src/test/regress/sql/yb.orig.ddl_txn_visibility.sql new file mode 100644 index 0000000000000..810ba9896be8a --- /dev/null +++ b/src/test/regress/sql/yb.orig.ddl_txn_visibility.sql @@ -0,0 +1,80 @@ +-- ----------------------------------------------------------------------------- +-- TEST 1: Multi-Statement Read-Your-Own-Write (RYOW) +-- ----------------------------------------------------------------------------- +BEGIN; + +CREATE TABLE ryow_test (id INT PRIMARY KEY, val TEXT); + +INSERT INTO ryow_test SELECT i, 'init' FROM generate_series(1, 100) i; + +-- If in_txn_limit_ht is not updated by Statement 1, Statement 2 might +-- use a read-time that is logically BEFORE Statement 1's write-time. +SELECT count(*) FROM ryow_test; + +-- Statement 3: Update based on previous write +UPDATE ryow_test SET val = 'updated' WHERE id = 50; + +-- Verification +SELECT id, val FROM ryow_test WHERE id = 50; + +COMMIT; + +-- ----------------------------------------------------------------------------- +-- TEST 2: Self-Referencing Insert (INSERT INTO ... SELECT FROM self) +-- ----------------------------------------------------------------------------- +BEGIN; + +CREATE TABLE self_insert_test (id INT PRIMARY KEY); + +INSERT INTO self_insert_test SELECT i FROM generate_series(1, 10) i; + +-- The SELECT must find the first 10 rows in the Regular DB. +-- The INSERT then writes 10 new rows (ID 11-20) to the Regular DB. +-- This must NOT loop indefinitely. +INSERT INTO self_insert_test SELECT id + 10 FROM self_insert_test; + +-- Final count should be exactly 20. +SELECT count(*) FROM self_insert_test; + +COMMIT; + +-- ----------------------------------------------------------------------------- +-- TEST 3: Cross-Statement Visibility with Mixed Operations +-- ----------------------------------------------------------------------------- +BEGIN; +CREATE TABLE mixed_visibility_test (id INT PRIMARY KEY, balance INT); + +-- First batch +INSERT INTO mixed_visibility_test VALUES (1, 100), (2, 200); + +-- Use a statement that references the data to perform a calculation +INSERT INTO mixed_visibility_test +SELECT 3, sum(balance) FROM mixed_visibility_test; + +SELECT * FROM mixed_visibility_test ORDER BY id; +COMMIT; + +-- ----------------------------------------------------------------------------- +-- TEST 4: The "Infinite Increment" Test +-- ----------------------------------------------------------------------------- +BEGIN; +CREATE TABLE increment_test (id INT PRIMARY KEY, val INT); + +-- Initial Data: Single row +INSERT INTO increment_test VALUES (1, 10); + +-- Scenario: +-- We want to increment 'val' by 1 for any row where val < 100. +-- 1. Scan finds val=10. 10 < 100 is TRUE. +-- 2. UPDATE writes val=11 directly to Regular DB (Skip Intents). +-- 3. If the scan is NOT isolated, it might find val=11 later. +-- 4. 11 < 100 is still TRUE. +-- 5. UPDATE writes val=12... and so on. + +UPDATE increment_test SET val = val + 1 WHERE val < 100; + +-- EXPECTED (Correct Isolation): val = 11 +-- ACTUAL (If Isolation Fails): val = 100 (or the query loops/times out) +SELECT * FROM increment_test; + +COMMIT; diff --git a/src/test/regress/sql/yb.orig.depend.sql b/src/test/regress/sql/yb.orig.depend.sql new file mode 100644 index 0000000000000..efecaf02ae5c9 --- /dev/null +++ b/src/test/regress/sql/yb.orig.depend.sql @@ -0,0 +1,10 @@ +CREATE TABLE test_table(k INT PRIMARY KEY, v INT); +CREATE VIEW test_view AS SELECT k FROM test_table WHERE v = 10; +CREATE INDEX ON test_view (k); +CREATE TYPE type_pair AS (f1 INT, f2 INT); +CREATE TYPE type_pair_with_int AS (f1 type_pair, f2 int); +DROP TABLE test_table; -- should fail +DROP TABLE test_table CASCADE; +DROP TYPE type_pair; -- should fail +DROP TYPE type_pair CASCADE; +DROP TYPE type_pair_with_int; diff --git a/src/test/regress/sql/yb.orig.dependency.sql b/src/test/regress/sql/yb.orig.dependency.sql new file mode 100644 index 0000000000000..180046ba6425c --- /dev/null +++ b/src/test/regress/sql/yb.orig.dependency.sql @@ -0,0 +1,24 @@ +-- +-- Validate yb_db_admin can reassign object ownership +-- +CREATE ROLE regress_user1; +CREATE ROLE regress_user2; +CREATE ROLE regress_user3; +CREATE ROLE non_sys_superuser SUPERUSER; +GRANT regress_user1 TO regress_user3; +GRANT regress_user2 TO regress_user3; +SET SESSION AUTHORIZATION yb_db_admin; +REASSIGN OWNED BY regress_user1 TO regress_user2; +REASSIGN OWNED BY regress_user2 TO regress_user1; +-- should fail, cannot reassign objects owned by superuser +REASSIGN OWNED BY postgres TO yb_db_admin; +-- should fail, assigning from superuser to non-superuser +REASSIGN OWNED BY non_sys_superuser TO yb_db_admin; +-- should fail, user needs privileges on both old and new role +SET SESSION AUTHORIZATION regress_user1; +REASSIGN OWNED BY regress_user1 TO regress_user2; +REASSIGN OWNED BY regress_user2 TO regress_user1; +-- should succeed, user has privileges on both old and new role +SET SESSION AUTHORIZATION regress_user3; +REASSIGN OWNED BY regress_user1 TO regress_user2; +REASSIGN OWNED BY regress_user2 TO regress_user1; diff --git a/src/test/regress/sql/yb.orig.distinct_pushdown_base.sql b/src/test/regress/sql/yb.orig.distinct_pushdown_base.sql new file mode 100644 index 0000000000000..b919d55508c9b --- /dev/null +++ b/src/test/regress/sql/yb.orig.distinct_pushdown_base.sql @@ -0,0 +1,152 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)' + +-- Split at 1, ... to ensure that the value r1 = 1 is present in more than one tablet. +-- See #18101. +CREATE TABLE t(r1 INT, r2 INT, r3 INT, v INT, PRIMARY KEY(r1 ASC, r2 ASC, r3 ASC)) SPLIT AT VALUES ((1, 1, 500)); +INSERT INTO t (SELECT 1, i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); +-- Add one more distinct value to catch bugs that arise only with more than one distinct value. +INSERT INTO t (SELECT 2, i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); + +-- Test the flag. +SET yb_enable_distinct_pushdown TO off; + +-- Do not pick Distinct Index Scan since the flag is off. +-- XXX: Results may not be consistent (no explicit ordering). +\set query ':P SELECT DISTINCT r1 FROM t;' +\i :iter_P2 + +-- Turn the flag back on. +SET yb_enable_distinct_pushdown TO on; + +-- Pick Distinct Index Scan. +\set query ':P SELECT DISTINCT r1 FROM t;' +\i :iter_P2 + +-- Test a larger prefix. +\set query ':P SELECT DISTINCT r1, r2 FROM t;' +\i :iter_P2 + +-- Even though the index scan does not return distinct values of r2, using +-- a Distinct Index Scan can still help retrieve fewer rows from storage. +-- Observe that this behavior deviates from ORDER BY where partial sorts +-- are not useful but partial DISTINCT scans are still worth it. +\set query ':P SELECT DISTINCT r2 FROM t;' +\i :iter_P2 + +-- Limit clauses. +-- Limit goes after DISTINCT (this includes the Unique node). +\set query ':P SELECT DISTINCT r1 FROM t LIMIT 2;' +\i :iter_P2 + +-- Now, test other data types. + +-- Test floating point data. +CREATE TABLE tr(r1 REAL, r2 REAL, PRIMARY KEY(r1 ASC, r2 ASC)); +INSERT INTO tr (SELECT 0.5, i FROM GENERATE_SERIES(1, 1000) AS i); + +\set query ':P SELECT DISTINCT r1 FROM tr;' +\i :iter_P2 + +DROP TABLE tr; + +-- Test text data as well. +CREATE TABLE ts(r1 TEXT, r2 TEXT, v TEXT, PRIMARY KEY(r1 ASC, r2 ASC)); +INSERT INTO ts (SELECT 'uniq', i::TEXT, 'value' FROM GENERATE_SERIES(1, 1000) AS i); + +\set query ':P SELECT DISTINCT r1 FROM ts;' +\i :iter_P2 + +DROP TABLE ts; + +-- Ensure that Distinct Index Scan is not generated for a non-LSM index. +-- Non-LSM indexes such as GIN do not necessarily support distinct index scan. +CREATE TABLE vectors (v tsvector, k SERIAL PRIMARY KEY); +INSERT INTO vectors SELECT to_tsvector('simple', 'filler') FROM generate_series(1, 10); + +CREATE INDEX NONCONCURRENTLY igin ON vectors USING ybgin (v); + +-- Avoid fetching primary key and fetch secondary key instead since +-- there is already an LSM index on the primary key and LSM supports distinct index scan. +\set query ':P SELECT DISTINCT v FROM vectors;' +\i :iter_P2 + +DROP INDEX igin; +DROP TABLE vectors; + +-- Test distinct index scans in scenarios where user provides explicit ordering. + +-- Start off easy with forward and backward scans. +\set query ':P SELECT DISTINCT r1 FROM t ORDER BY r1;' +\i :iter_P2 +\set query ':P SELECT DISTINCT r1 FROM t ORDER BY r1 DESC;' +\i :iter_P2 + +-- Now, try a larger prefix. +\set query ':P SELECT DISTINCT r1, r2 FROM t ORDER BY r1, r2;' +\i :iter_P2 +\set query ':P SELECT DISTINCT r1, r2 FROM t ORDER BY r1 DESC, r2 DESC;' +\i :iter_P2 + +-- Now, we try only a subset of the prefix. +-- Picking a Distinct Index Scan for such cases can still be useful since +-- the storage layer retrieves and returns fewer rows overall. +\set query ':P SELECT DISTINCT r1, r2 FROM t ORDER BY r2;' +\i :iter_P2 + +-- m in tm refers to mixed ordering. +-- Sort order does not matter when distinct-ifying the columns. +-- Hence, generate Distinct Index Scans even for keys with a mix of +-- ascending and descending orders in the LSM index. +CREATE TABLE tm(r1 INT, r2 INT, r3 INT, v INT, PRIMARY KEY(r1 DESC, r2 ASC, r3 ASC)); +INSERT INTO tm (SELECT i%3, 2-i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); + +-- Test both forward and backwards scans. +\set query ':P SELECT DISTINCT r1, r2 FROM tm;' +\i :iter_P2 +-- This is a backwards scan because of how the keys are ordered in the primary index. +\set query ':P SELECT DISTINCT r1, r2 FROM tm ORDER BY r1, r2 DESC;' +\i :iter_P2 + +DROP TABLE tm; + +-- Aggregates. +-- Unless the aggregate is pushed down into distinct index scan, +-- cannot currently push down DISTINCT. +\set query ':P SELECT DISTINCT r1, COUNT(r1) FROM t GROUP BY r1;' +\i :iter_P2 + +-- Window Funcs. +-- Same reasoning applies to window funcs as well. +\set query ':P SELECT DISTINCT r1, COUNT(r1) OVER (PARTITION BY r1) FROM t;' +\i :iter_P2 + +SELECT DISTINCT r1 FROM t WHERE r1 = 1 AND r2 IN (0, 1); + +-- DISTINCT ON query for regression purposes. +SELECT DISTINCT ON (r1) r1, r2 FROM t ORDER BY r1, r2; + +DROP TABLE t; + +-- #22822: Include columns of an index are not sorted. +CREATE TABLE kv(k INT, v1 INT, v2 INT); +CREATE INDEX kv_idx ON kv (k ASC) INCLUDE (v1, v2); +INSERT INTO kv (SELECT i%10, i%10, i%10 FROM GENERATE_SERIES(1, 100) AS i); + +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) +SELECT DISTINCT k, v1, v2 FROM kv; + +SELECT DISTINCT k, v1, v2 FROM kv ORDER BY k; + +DROP TABLE kv; +-- Test #22923 +CREATE TABLE demo(k INT, x INT, v INT, a INT, d DATE, PRIMARY KEY(k,x,v,d)); + +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF) +SELECT v FROM (SELECT DISTINCT v FROM demo WHERE k=1 AND x=1) foo; + +DROP TABLE demo; diff --git a/src/test/regress/sql/yb.orig.distinct_pushdown_join.sql b/src/test/regress/sql/yb.orig.distinct_pushdown_join.sql new file mode 100644 index 0000000000000..4f8b04ded821e --- /dev/null +++ b/src/test/regress/sql/yb.orig.distinct_pushdown_join.sql @@ -0,0 +1,163 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)' + +-- Split at 1, ... to ensure that the value r1 = 1 is present in more than one tablet. +-- See #18101. +CREATE TABLE t(r1 INT, r2 INT, r3 INT, v INT, PRIMARY KEY(r1 ASC, r2 ASC, r3 ASC)) SPLIT AT VALUES ((1, 1, 500)); +INSERT INTO t (SELECT 1, i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); +-- Add one more distinct value to catch bugs that arise only with more than one distinct value. +INSERT INTO t (SELECT 10, i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); + +-- Start with CROSS/INNER/LEFT/RIGHT/FULL joins. +-- CROSS JOIN +\set query ':P SELECT DISTINCT t1.r1, t2.r1 FROM t t1 CROSS JOIN t t2;' +\i :iter_P2 +-- INNER JOIN +\set query ':P SELECT DISTINCT t1.r1 FROM t t1 INNER JOIN t t2 USING (r1);' +\i :iter_P2 +-- In the Distinct Index Scan of t2, there are 7 rows, not 6, because the tablet split ends up with 1, 1 represented in two tablets. +\set query ':P SELECT DISTINCT t1.r1 FROM t t1 INNER JOIN t t2 ON t1.r1 = t2.r2;' +\i :iter_P2 +-- LEFT JOIN +\set query ':P SELECT DISTINCT t1.r1 FROM t t1 LEFT JOIN t t2 ON t1.r1 = t2.r2;' +\i :iter_P2 +-- RIGHT JOIN +\set query ':P SELECT DISTINCT t1.r1 FROM t t1 RIGHT JOIN t t2 ON t1.r1 = t2.r2;' +\i :iter_P2 +-- FULL JOIN +\set query ':P SELECT DISTINCT t1.r1, t2.r2 FROM t t1 FULL JOIN t t2 ON t1.r1 = t2.r2;' +\i :iter_P2 + +-- Now, let's test various join predicate types. +-- Range predicates. +\set query ':P SELECT DISTINCT t1.r1 FROM t t1 JOIN t t2 ON t1.r1 < t2.r2;' +\i :iter_P2 +\set query ':P SELECT DISTINCT t2.r1 FROM t t1 JOIN t t2 ON t1.r1 < t2.r2;' +\i :iter_P2 +-- "DISTINCT" Semijoin. These queries could be optimized by extending our distinctness analysis. +\set query ':P SELECT DISTINCT r1 FROM t WHERE r2 IN (SELECT r1 FROM t);' +\i :iter_P2 +-- Join clauses have volatile functions. Do not use a Distinct Index Scan in this case. +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT DISTINCT t1.r1 FROM t t1 JOIN t t2 ON t1.r1 + RANDOM() < t2.r1 + RANDOM(); +SELECT DISTINCT t1.r1 FROM t t1 JOIN t t2 ON t1.r1 + RANDOM() < t2.r1 + RANDOM(); +-- Targets have volatile functions. Do not use a Distinct Index Scan in this case. +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT DISTINCT t1.r1 * RANDOM() FROM t t1 JOIN t t2 USING (r1); + +-- Join methods - Merge/Hash/Nestloop. +\set Q1 '/*+MergeJoin(t1 t2)*/' +\set Q2 '/*+HashJoin(t1 t2)*/' +\set Q3 '/*+Nestloop(t1 t2)*/' +\set query ':P :Q SELECT DISTINCT r1 FROM t t1 JOIN t t2 USING (r1);' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- Test queries for whether they need a HashAggregate or a Unique node on top of the join plan. +-- Pushdown distinct only into the relation which has no volatile clause. +-- Require additional distinctification on top when distinct is pushed down to only one of the relations. +\set query ':P SELECT DISTINCT t1.r1 FROM t t1 JOIN t t2 ON t1.r1 = t2.r2 WHERE t1.r1 + RANDOM() < 5;' +\set Pnext :iter_query +\i :iter_P2 +\set query ':P SELECT DISTINCT t1.r1 FROM t t1 JOIN t t2 USING (r1) JOIN t t3 USING (r1) WHERE t3.r1 + RANDOM() < 5;' +\i :iter_P2 +-- Target list order does not matter. +-- In vanilla postgres, the order of the target list matters, i.e. SELECT DISTINCT t1.r1, t2.r1 does not generate the same plan as SELECT DISTINCT t2.r1, t1.r1. +-- Original order. +\set query ':P SELECT DISTINCT t1.r1, t2.r1 FROM t t1 JOIN t t2 USING (r1);' +\i :iter_P2 +-- Permuted order. +\set query ':P SELECT DISTINCT t2.r1, t1.r1 FROM t t1 JOIN t t2 USING (r1);' +\i :iter_P2 +-- Original order. +\set query ':P SELECT DISTINCT t1.r1, t2.r1, t2.r2 FROM t t1 JOIN t t2 USING (r1);' +\i :iter_P2 +-- Permuted order. +\set query ':P SELECT DISTINCT t2.r2, t1.r1, t2.r1 FROM t t1 JOIN t t2 USING (r1);' +\i :iter_P2 +-- t1.r2 = t2.r2 and t2.r2 = 2 => t1.r2 = 2. +-- Moreover, constants are excluded from the prefix, so r2 is not in either distinct index scan prefix. +\set query ':P SELECT DISTINCT t1.r2 FROM t t1 JOIN t t2 USING (r2) WHERE t2.r2 = 2;' +\i :iter_P2 +-- Check constants for distinctness as well. +\set Q1 '/*+ Seqscan(t1) */' +\set query ':P :Q1 SELECT DISTINCT t1.r1 FROM t t1 JOIN t t2 USING (r1) WHERE t2.r1 = 1;' +\i :iter_P2 + +-- Try a hash partitioned table now. +CREATE TABLE th(h1 INT, h2 INT, r1 INT, r2 INT, v INT, PRIMARY KEY((h1, h2) HASH, r1 ASC, r2 ASC)) SPLIT INTO 16 TABLETS; +INSERT INTO th (SELECT 1, i%3, 2-i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); +INSERT INTO th (SELECT 10, i%3, 2-i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); + +-- Try self join on the hash partitioned table. +\set Q1 '/*+MergeJoin(t1 t2)*/' +\set Q2 '/*+HashJoin(t1 t2)*/' +\set Q3 '/*+Nestloop(t1 t2)*/' +\set query ':P :Q SELECT DISTINCT h1, h2 FROM th t1 JOIN th t2 USING (h1, h2);' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- Try join across hash and range partitioned tables. +\set Q1 '/*+MergeJoin(th t)*/' +\set Q2 '/*+HashJoin(th t)*/' +\set Q3 '/*+Nestloop(th t)*/' +\set query ':P :Q SELECT DISTINCT th.h1, th.h2 FROM th JOIN t ON th.h1 = t.r1 AND th.h2 = t.r2;' +\i :iter_P2 + +DROP TABLE th; + +-- Secondary index only scan. +CREATE INDEX irv ON t(v, r1); + +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT DISTINCT v FROM t t1 JOIN t t2 USING (v); + +DROP INDEX irv; + +DROP TABLE t; + +-- Regression test case for GitHub issue #20827 + +CREATE TABLE t1 (col_int_key int, pad int, primary key(col_int_key asc, pad asc)); +INSERT INTO t1 (select 3, i from generate_series(1, 1000) i); +INSERT INTO t1 (select 4, i from generate_series(1, 1000) i); +ANALYZE t1; + +CREATE TABLE t2 (pk int, col_int int, primary key(pk asc)); +INSERT INTO t2 (SELECT i, i FROM generate_series(1, 1000) i); +ANALYZE t2; + +\set Q1 '/*+ Set(enable_mergejoin off) Set(enable_hashjoin off) Set(enable_material off) */' +\set query ':P :Q1 SELECT DISTINCT t2.pk FROM t1 JOIN t2 ON t1.col_int_key = t2.col_int WHERE t2.pk < 5;' +\set Pnext :iter_query +\i :iter_P2 + +DROP TABLE t1; +DROP TABLE t2; + +-- Regression test for #20893 + +CREATE TABLE t (k1 INT, k2 INT, PRIMARY KEY(k1 ASC, k2 ASC)); + +-- Protect against memory corruption bug when not using ANALYZE. +SELECT DISTINCT a1.k1 FROM t a1 RIGHT OUTER JOIN t a2 RIGHT OUTER JOIN t a3 +USING (k1) USING (k1) WHERE a2.k1 = 5 AND a3.k1 = 5 GROUP BY a1.k1 ORDER BY a1.k1; + +INSERT INTO t (SELECT i%10, i FROM GENERATE_SERIES(1, 10000) AS i); +ANALYZE t; + +-- Protect against memory corruption bug when using ANALYZE. +SELECT DISTINCT a1.k1 FROM t a1 RIGHT OUTER JOIN t a2 USING (k1) WHERE a2.k1 = 5 GROUP BY a1.k1 ORDER BY a1.k1; + +-- Grouping Sets with distinct. +SELECT DISTINCT t.k1 FROM t GROUP BY GROUPING SETS ((k1), (k1)) ORDER BY k1; + +DROP TABLE t; + +CREATE TABLE th (h1 INT, r1 INT, PRIMARY KEY(h1 HASH, r1 ASC)); + +-- Protect against memory corruption bug when using hash partitioned table. +SELECT DISTINCT a1.h1 FROM th a1, th a2, th a3 where a1.h1 < a2.h1 and a2.h1 = a3.h1 and a2.h1 = 5 AND a3.h1 = 5 GROUP BY a1.h1; + +DROP TABLE th; diff --git a/src/test/regress/sql/yb.orig.distinct_pushdown_pred.sql b/src/test/regress/sql/yb.orig.distinct_pushdown_pred.sql new file mode 100644 index 0000000000000..116d570267fdf --- /dev/null +++ b/src/test/regress/sql/yb.orig.distinct_pushdown_pred.sql @@ -0,0 +1,325 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)' + +-- Split at 1, ... to ensure that the value r1 = 1 is present in more than one tablet. +-- See #18101. +CREATE TABLE t(r1 INT, r2 INT, r3 INT, r4 INT, v INT, PRIMARY KEY(r1 ASC, r2 ASC, r3 ASC, r4 ASC)) SPLIT AT VALUES ((1, 1, 1, 500)); +INSERT INTO t (SELECT 1, i%3, 2-i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); +-- Add one more distinct value to catch bugs that arise only with more than one distinct value. +INSERT INTO t (SELECT 2, i%3, 2-i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); + +-- These tests illustrate some similarities and differences uniqkeys have with sortkeys. +-- +-- Many of the differences arise from the fact that DISTINCT eliminates rows while +-- ORDER BY does not do that by itself. This fact has an important consequence that +-- sorting commutes with filters while uniq does not necessarily do that. +-- +-- Differences +-- =========== +-- 1) uniqkeys are still useful even if only a subset of the index prefix is queried. +-- However, ORDER BY requires the complete prefix of the index to be requested +-- a partial sort is not useful for further sorting. +-- On the other hand, a partial DISTINCT helps eliminate irrelevant rows early on. +-- 2) Equivalence keys have limited utility with uniqkeys. +-- The most obvious example is when r1 = r2. Do not exclude r2 from the distinct prefix +-- since distinct values of r1 may not retrieve correct values of r2 from LSM. +-- This is because the scan module picks the first DISTINCT r1 row and not +-- the ones where r1 = r2. +-- While this behavior may change in the future to account for non-index predicates, +-- it still cannot rule out local predicates on the postgres side. +-- On the other hand, ORDER BY r1, r2 can be reduced to ORDER BY r1 irrespective +-- of additional predicates when r1 = r2. +-- All that said, r1 = r2 AND r2 = 1 is equivalent to r1 = 1 AND r2 = 1. +-- 3) Another major difference is in the handling of constants. Constants can be completely +-- eliminated in ORDER BY. For example, r2 = 1 ORDER BY r1, r2 is equivalent to ORDER BY r1 +-- since there at most one distinct value of r2, so no explicit sorting is necessary. +-- However, the table might have multiple tuples with r2 = 1 and distinct values of r1. +-- Hence, a unique node is still necessary even when r2 is excluded from the prefix. +-- 4) Moreover, it is neither necessary nor sufficient for r2 to be equivalent to constant. +-- Instead, the scan requires that r2 = 1 be an index clause since otherwise the corresponding +-- tuple may be missed by nature of the distinct index scan. One example is when an index scan occurs +-- as part of the inner relation of a nested loop join operation. +-- More generally, a distinct index scan can be used even with Merge and Hash Joins +-- when the inner relation tuples must be unique. +-- 5) As another example, a DISTINCT on a non key column cannot be eliminated despite being +-- equal to a constant, e.g. v = 1, since currently only index clauses seep past +-- the DISTINCT operation. +-- 6) Distinct Index Scan may also be generated in the presence of simple non-volatile targets +-- and clauses. This is unlike sort where volatile clauses do not affect sort order. +-- However, the scan must generate distinct values of all target and clause references +-- (bar several exceptions). +-- 7) Adding onto comment (2), when r1 = r2, while DocDB cannot use a prefix of length 1, +-- postgres itself can treat r1 and r2 as equivalent. So query DISTINCT r1 is the same as +-- DISTINCT r2. + +-- Do not eliminate r2, see comment (2) above. +-- However, since r1 is equivalent to r2, the query still requests a distinct prefix regardless, see comment (7). +\set query ':P SELECT DISTINCT r2 FROM t WHERE r1 = r2;' +\i :iter_P2 +\set query ':P SELECT DISTINCT r1, r2 FROM t WHERE r1 = r2;' +\i :iter_P2 + +-- Eliminate r3 when equal to a constant, see comment (3) above. +\set query ':P SELECT DISTINCT r3 FROM t WHERE r3 = 1;' +\i :iter_P2 +-- Out-of-range predicate. +\set query ':P SELECT DISTINCT r3 FROM t WHERE r3 = 5;' +\i :iter_P2 +-- Other targets. +\set query ':P SELECT DISTINCT r2, r3 FROM t WHERE r3 = 1;' +\i :iter_P2 +\set query ':P SELECT DISTINCT r2, r3 FROM t WHERE r3 = 5;' +\i :iter_P2 + +-- Can infer that r2 = r3, r3 = 1 <=> r2 = 1, r3 = 1. +-- Thus eliminating both r2 and r3. +\set query ':P SELECT DISTINCT r2, r3 FROM t WHERE r2 = r3 AND r3 = 1;' +\i :iter_P2 + +-- Cannot eliminate a non-index key that is equal to a constant, see comment (5) above. +\set query ':P SELECT DISTINCT v FROM t WHERE v = 1;' +\i :iter_P2 +\set query ':P SELECT DISTINCT r1, v FROM t WHERE v = 1;' +\i :iter_P2 + +-- Non-index predicates, see comment (6) above. +-- r1 = r2 tested above is a non-index predicate as well. +-- Here are some more tests to include more expressions. + +-- Support product expressions. +\set query ':P SELECT DISTINCT r1 FROM t WHERE r1 * r1 = 1;' +\i :iter_P2 +\set query ':P SELECT DISTINCT r1 FROM t WHERE r2 * r2 = 1;' +\i :iter_P2 + +-- Support expressions in targets. +\set query ':P SELECT DISTINCT r1 * r2 AS r FROM t;' +\i :iter_P2 + +-- Support expressions that can have duplicate values even when the arguments are DISTINCT. +\set query ':P SELECT DISTINCT 0 * r1 AS r FROM t;' +\i :iter_P2 + +-- Do not generate distinct index paths in the presence of volatile expressions. +-- In general, volatile expressions may have side effects, so they need to be run +-- for each tuple. Cannot use a distinct index scan in that case. +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT DISTINCT r1 FROM t WHERE r1 * RANDOM() > 1; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT DISTINCT r1 * RANDOM() AS r FROM t; + +-- Test range clauses as well while here. +\set query ':P SELECT DISTINCT r1 FROM t WHERE r1 > 1;' +\i :iter_P2 +-- r2 need not be part of the distinct prefix since the only clause is an index clause +-- and index clauses currently execute before DISTINCT. +\set query ':P SELECT DISTINCT r1 FROM t WHERE r2 > 1;' +\i :iter_P2 +-- Cannot eliminate r2 here since the query requires all distinct values of r2 and there isn't +-- one distinct value of r2 unlike constants. +\set query ':P SELECT DISTINCT r2 FROM t WHERE r2 < 5;' +\i :iter_P2 + +-- Now, execute some scalar array operations. +\set query ':P SELECT DISTINCT r1 FROM t WHERE r1 IN (1, 2);' +\i :iter_P2 +-- Out-of-range query. +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT DISTINCT r1 FROM t WHERE r1 IN (1, 2); +SELECT DISTINCT r1 FROM t WHERE r1 IN (3, 5); +-- Do not include index clause references in the prefix. +\set query ':P SELECT DISTINCT r1 FROM t WHERE r2 IN (1, 2);' +\i :iter_P2 +-- Eliminate r2 from the prefix since it is a constant. +\set query ':P SELECT DISTINCT r1, r2 FROM t WHERE r1 IN (1, 2) AND r2 = 2;' +\i :iter_P2 + +-- LSM indexes support IN index clauses on lower key columns and still support +-- sorting. +\set query ':P SELECT DISTINCT r1, r2 FROM t WHERE r2 IN (1, 2) ORDER BY r1, r2;' +\i :iter_P2 + +-- Unique node still necessary for range columns equal to constant. +\set query ':P SELECT DISTINCT r1 FROM t WHERE r1 = 1;' +\i :iter_P2 + +DROP TABLE t; + +-- h in th refers to hash table. +CREATE TABLE th(h1 INT, h2 INT, r1 INT, r2 INT, v INT, PRIMARY KEY((h1, h2) HASH, r1 ASC, r2 ASC)) SPLIT INTO 16 TABLETS; +INSERT INTO th (SELECT 1, i%3, 2-i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); +INSERT INTO th (SELECT 2, i%3, 2-i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); + +-- Implementing DISTINCT support for hash columns requires that +-- +-- 1) Distinct Prefix spans all hash columns. While DocDB stores hash columns +-- in sorted order in the index, DocDB also prepends a hash_code that is a +-- hash of all the hash columns. This structure makes it so that skipping +-- over duplicate prefix of hash columns is simply not possible. +-- For example, consider a scenario where the first column is degenerate +-- with all values being equal. The iterator cannot skip to the end since +-- the hash code changes with changing values of the second hash column. +-- 2) Hash ordering is leakproof. Despite all the differences mentioned +-- between sorting and uniq, these two operations are closely related. +-- DocDB is able to skip over duplicate elements precisely because the +-- columns are sorted by the prefix. Supporting hash columns for uniq +-- exposes the risk of considering hash columns for sorting. It does not +-- help that the hash columns are deceptively sorted. The hash_code column +-- at the start makes it so that the leading hash column is not sorted +-- similar to how a lower range column is not sorted. Except a query can +-- request an ordering of the index prefix but not the hash code. +-- 3) Unlike tables with a leading range column, tables with leading hash +-- columns are separated cleanly by virtue of hash code. Hence, no unique +-- node is necessary here on top unlike range column exclusive prefixes. + +-- All hash columns. +-- Isn't necessary to stick a unique node on top since hash columns +-- separate cleanly across tables. +\set query ':P SELECT DISTINCT h1, h2 FROM th;' +\i :iter_P2 + +-- Strict prefix of hash columns. +\set query ':P SELECT DISTINCT h1 FROM th;' +\i :iter_P2 + +-- Subset of the prefix of all hash columns. +\set query ':P SELECT DISTINCT h2 FROM th;' +\i :iter_P2 + +-- Both hash and range columns. +-- Prefix is still not sorted just because a range column is selected. +\set query ':P SELECT DISTINCT h1, h2, r1 FROM th;' +\i :iter_P2 + +-- Range columns only. +-- Includes hash columns since they come first. +\set query ':P SELECT DISTINCT r1 FROM th;' +\i :iter_P2 + +-- Avoid classifying hash columns as sortable. +-- Guard rails meant to prevent DISTINCT logic from +-- marking hash columns as sortable. +\set query ':P SELECT DISTINCT h1 FROM th ORDER BY h1;' +\i :iter_P2 +-- Once all the hash columns are set, range columns are returned in sorted order as usual. +\set query ':P SELECT DISTINCT r1 FROM th WHERE h1 = 1 AND h2 = 1 ORDER BY r1;' +\i :iter_P2 +-- Not the case if any of the hash columns are not set. +\set query ':P SELECT DISTINCT r1 FROM th WHERE h1 = 1 ORDER BY r1;' +\i :iter_P2 + +-- Hash columns constant. +\set query ':P SELECT DISTINCT h1, h2 FROM th WHERE h1 = 1 AND h2 = 1;' +\i :iter_P2 + +-- Range column constant +\set query ':P SELECT DISTINCT h1, h2 FROM th WHERE r1 = 1;' +\i :iter_P2 +\set query ':P SELECT DISTINCT r1 FROM th WHERE r1 = 1;' +\i :iter_P2 + +DROP TABLE th; + +-- Split at 1, ... to ensure that the value r1 = 1 is present in more than one tablet. +-- See #18101. +CREATE TABLE t(r1 INT, r2 INT, r3 INT, v INT, PRIMARY KEY(r1 ASC, r2 ASC, r3 ASC)) SPLIT AT VALUES ((1, 1, 500)); +INSERT INTO t (SELECT 1, i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); +-- Add one more distinct value to catch bugs that arise only with more than one distinct value. +INSERT INTO t (SELECT 2, i%3, i, NULL FROM GENERATE_SERIES(1, 1000) AS i); + +-- Only new thing here is that secondary key columns can be NULL. +-- It should not matter whether or not NULLS come first in the index or last. +-- Cover a fair amount of test cases regardless. + +CREATE INDEX irv ON t (v ASC, r1 ASC); + +-- Start off easy. +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT DISTINCT v FROM t; +SELECT DISTINCT v FROM t LIMIT 10; + +-- Check if ORDER BY works as usual with DISTINCT. +-- Might as well fish for a backwards scan. +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT DISTINCT v FROM t ORDER BY v DESC; +SELECT DISTINCT v FROM t ORDER BY v DESC LIMIT 10; + +-- Cover a case where no index is applicable. +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT DISTINCT v, r2 FROM t; +SELECT DISTINCT v, r2 FROM t LIMIT 10; + +DROP INDEX irv; + +-- Secondary index is useful for DISTINCT regardless of whether NULLS are first or last. +CREATE INDEX irv ON t(v ASC NULLS FIRST, r1 ASC); + +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT DISTINCT v FROM t; +SELECT DISTINCT v FROM t LIMIT 10; + +DROP INDEX irv; + +DROP TABLE t; + +-- Now, turn to table with hash columns. +CREATE TABLE th(h1 INT, h2 INT, r1 INT, r2 int, v INT, PRIMARY KEY((h1, h2) HASH, r1 ASC, r2 ASC)) SPLIT INTO 16 TABLETS; +INSERT INTO th (SELECT 1, i%3, 2-i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); +INSERT INTO th (SELECT 2, i%3, 2-i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); + +CREATE INDEX ihv ON th((v, h1) HASH); + +-- Go easy again. Picks the index ihv for distinct index scan. +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT DISTINCT v FROM th; +SELECT DISTINCT v FROM th LIMIT 10; + +DROP INDEX ihv; + +DROP TABLE th; + +-- Split at 1, ... to ensure that the value r1 = 1 is present in more than one tablet. +-- See #18101. +CREATE TABLE t(r1 INT, r2 INT, r3 INT, v INT, PRIMARY KEY(r1 ASC, r2 ASC, r3 ASC)) SPLIT AT VALUES ((1, 1, 500)); +INSERT INTO t (SELECT 1, i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); +-- Add one more distinct value to catch bugs that arise only with more than one distinct value. +INSERT INTO t (SELECT 2, i%3, i, i/3 FROM GENERATE_SERIES(1, 1000) AS i); + +-- Joins are not supported at the moment. +-- +-- 1) Unique node added on top of base index scans directly for distinct prefixes +-- that are range column exclusive, see #18101. +-- Usually, Unique nodes are added only after all the joins are done but not so here. +-- 2) Unlike sort, distinct keys are simply a union of base relation keys in joins. +-- 3) Similarly, unlike sort, distinct can permute its targets. +-- 4) Unlike sort, a prefix of distinct keys are not distinct. + +-- Basic self inner join. +-- MergeJoin. +SELECT DISTINCT r1 FROM t t1 JOIN t t2 USING (r1); +-- Now with a larger prefix. +SELECT DISTINCT r1, r2 FROM t t1 JOIN t t2 USING (r1, r2); +-- DISTINCT works with permuted targets. +SELECT DISTINCT r2, r1 FROM t t1 JOIN t t2 USING (r1, r2); +-- MergeJoin => requires a sort of both r1 and r2, and join condition is applied with join and not index scan. +-- Implies that prefix length is 2 and not 1 despite the fact that the output does not contain r2. +SELECT DISTINCT r1 FROM t t1 JOIN t t2 USING (r1, r2); + +-- NestLoop join. +SELECT DISTINCT r1 FROM t t1 JOIN t t2 USING (r1) WHERE t2.r1 = 1 AND t2.r2 = 1 AND t2.r3 = 1; + +DROP TABLE t; + +CREATE TABLE sample(a int, b int, primary key(a asc, b asc)); +INSERT INTO sample VALUES (1,1), (1,2); +DELETE FROM sample where b = 1; + +\set query ':P SELECT DISTINCT a FROM sample WHERE a > 0;' +\i :iter_P2 + +DROP TABLE sample; + +-- See issue #22615 +CREATE TABLE t(r1 INT, r2 INT, PRIMARY KEY(r1 ASC, r2 ASC)); +INSERT INTO t (SELECT i, 1 FROM GENERATE_SERIES(1, 100) AS i); + +SELECT DISTINCT r1 FROM t WHERE r1 IN (1, 10) ORDER BY r1 DESC; +SELECT DISTINCT r1 FROM t WHERE r1 IN (1, 10, 20) ORDER BY r1 DESC; + +DROP TABLE t; diff --git a/src/test/regress/sql/yb.orig.dml.sql b/src/test/regress/sql/yb.orig.dml.sql new file mode 100644 index 0000000000000..6aa9ec10dc4d0 --- /dev/null +++ b/src/test/regress/sql/yb.orig.dml.sql @@ -0,0 +1,122 @@ +-- +-- Yugabyte-owned file for testing DML queries that do not fall into more +-- specific buckets (yb_dml_*.sql). +-- + +-- GH-22967: Test that a Value Scan fed by a Sub Plan does not cause an INSERT +-- to crash. The query relies on the fact the EXISTS .. IN expression does not +-- get constant-folded and is evaluated in its own Sub Plan. +CREATE TABLE GH_22967 (k INT4 PRIMARY KEY); +EXPLAIN (COSTS OFF) INSERT INTO GH_22967 VALUES ((EXISTS(SELECT 1) in (SELECT true))::INT4), (-10); +INSERT INTO GH_22967 VALUES ((EXISTS(SELECT 1) in (SELECT true))::INT4), (-10); + + +-- Test that foreign key constraints are enforced +CREATE TABLE customers ( + customer_id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + email VARCHAR(100) UNIQUE NOT NULL +); + +CREATE TABLE orders ( + order_id SERIAL PRIMARY KEY, + order_date DATE NOT NULL, + amount DECIMAL(10, 2) NOT NULL, + customer_id INTEGER NOT NULL, + FOREIGN KEY (customer_id) REFERENCES customers(customer_id) +); + +INSERT INTO customers (name, email) VALUES +('Alice Johnson', 'alice@example.com'), +('Bob Smith', 'bob@example.com'); + +INSERT INTO orders (order_date, amount, customer_id) VALUES +('2023-10-01', 250.00, 1), +('2023-10-02', 450.50, 2); + +-- Attempt to insert an order with a non-existent customer +INSERT INTO orders (order_date, amount, customer_id) VALUES +('2023-10-03', 300.00, 3); + +-- Attempt to delete a customer that still has orders +DELETE FROM customers WHERE customer_id = 2; + +-- Test cascading deletes +DROP TABLE orders; +CREATE TABLE orders_cascade ( + order_id SERIAL PRIMARY KEY, + order_date DATE NOT NULL, + amount DECIMAL(10, 2) NOT NULL, + customer_id INTEGER NOT NULL, + FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE CASCADE +); +INSERT INTO orders_cascade (order_date, amount, customer_id) VALUES +('2023-10-01', 250.00, 1), +('2023-10-02', 450.50, 2); +DELETE FROM customers WHERE customer_id = 2; +SELECT * FROM orders_cascade; + + +-- Test adding foreign key constraint using ALTER TABLE ADD CONSTRAINT +CREATE TABLE customers_test ( + customer_id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL +); + +CREATE TABLE orders_test ( + order_id SERIAL PRIMARY KEY, + customer_id INTEGER, + name VARCHAR(100) NOT NULL +); + +-- Add foreign key constraint using ALTER TABLE +ALTER TABLE orders_test +ADD CONSTRAINT fk_orders_customers +FOREIGN KEY (customer_id) REFERENCES customers_test(customer_id); + +-- Insert valid data +INSERT INTO customers_test (name) VALUES ('Customer 1'), ('Customer 2'); +INSERT INTO orders_test (customer_id, name) VALUES (1, 'Order 1'), (2, 'Order 2'); + +-- Attempt to insert a child with a non-existent customer +INSERT INTO orders_test (customer_id, name) VALUES (3, 'Order 3'); + +-- Attempt to delete a customer that still has orders +DELETE FROM customers_test WHERE customer_id = 2; + +-- Test that invalidation of the foreign key cache works +ALTER TABLE orders_test DROP CONSTRAINT fk_orders_customers; +INSERT INTO orders_test (customer_id, name) VALUES (3, 'Order 3'); + +-- GH-26464: Test deleting rows across multiple partitions of a partitioned table +CREATE TABLE base (k INT PRIMARY KEY, v INT) PARTITION BY RANGE (k); +CREATE TABLE part1 PARTITION OF base FOR VALUES FROM (11) TO (21); +CREATE TABLE part2 PARTITION OF base FOR VALUES FROM (21) TO (31); +CREATE TABLE part3 PARTITION OF base FOR VALUES FROM (31) TO (41); +CREATE TABLE part4 (k INT PRIMARY KEY, v INT) PARTITION BY RANGE (k); +CREATE TABLE part4a PARTITION OF part4 FOR VALUES FROM (41) TO (46); +CREATE TABLE part4b PARTITION OF part4 FOR VALUES FROM (46) TO (51); +ALTER TABLE base ATTACH PARTITION part4 FOR VALUES FROM (41) TO (51); + +CREATE INDEX ON part3 (v); +CREATE INDEX ON part4b (v); + +INSERT INTO base VALUES (11, 11), (22, 22), (23, 23), (33, 33), (34, 44), (44, 44), (47 ,47); + +-- Two partitions that individually satisfy single shard constraints. +DELETE FROM base WHERE k IN (11, 22); +-- One satisfies the single shard constraint, the other does not. +DELETE FROM base WHERE k IN (23, 33); +-- Partitions that are on different levels of the tree, some of which satisfy +-- single shard constraints. +DELETE FROM base WHERE k IN (34, 44, 47); + +SELECT * FROM base ORDER BY k; + +-- GH-30373 +-- The scans set "reference row marks" which should NOT be explained as part of EXPLAIN's output. +CREATE TABLE gh30373 (id INT PRIMARY KEY); +INSERT INTO gh30373 VALUES(1); +BEGIN ISOLATION LEVEL SERIALIZABLE; +EXPLAIN (ANALYZE ON) UPDATE gh30373 a SET id = b.id+1 FROM gh30373 b WHERE a.id=b.id; +ROLLBACK; diff --git a/src/test/regress/sql/yb.orig.dml_derived_equalities.sql b/src/test/regress/sql/yb.orig.dml_derived_equalities.sql new file mode 100644 index 0000000000000..c15a9f30a4610 --- /dev/null +++ b/src/test/regress/sql/yb.orig.dml_derived_equalities.sql @@ -0,0 +1,173 @@ +-- Enable CBO +SET yb_enable_optimizer_statistics = true; +SET yb_enable_base_scans_cost_model = true; + +SET yb_enable_derived_equalities = true; + +-- Point lookups on single relation +CREATE TABLE orders ( + order_id int, + user_id int, + created_at timestamptz, + bucket_id int GENERATED ALWAYS AS (yb_hash_code(user_id::text || order_id::text) % 5) STORED, + PRIMARY KEY (bucket_id ASC, user_id ASC, order_id ASC) +); + +-- Secondary index with expression (not generated column) +CREATE INDEX orders_created_idx ON orders((yb_hash_code(created_at) % 3) ASC, created_at ASC); + +INSERT INTO orders VALUES + (1, 100, '2024-01-01'::timestamptz), + (2, 100, '2024-01-02'::timestamptz); + +ANALYZE orders; + +-- Multi-column bucket (bucket depends on user_id AND order_id) +EXPLAIN (COSTS OFF) SELECT * FROM orders WHERE user_id = 100 AND order_id = 1; +SELECT * FROM orders WHERE user_id = 100 AND order_id = 1; + +-- Expression index +EXPLAIN (COSTS OFF) SELECT /*+ IndexScan(orders orders_created_idx) */ * FROM orders WHERE created_at = '2024-01-02'::timestamptz; +SELECT * FROM orders WHERE created_at = '2024-01-02'::timestamptz; + +-- Stable expression +EXPLAIN (COSTS OFF) SELECT /*+ IndexScan(orders orders_created_idx) */ * FROM orders WHERE created_at = date_trunc('day', '2024-01-02 15:30:12.789'::timestamptz); +SELECT * FROM orders WHERE created_at = date_trunc('day', '2024-01-02 15:30:12.789'::timestamptz); + +-- Parameterized query +PREPARE order_query AS SELECT * FROM orders WHERE user_id = $1 AND order_id = $2; +EXPLAIN (COSTS OFF) EXECUTE order_query(100, 2); +EXECUTE order_query(100, 2); + +DROP TABLE orders; + +-- Tests for joins +CREATE TABLE users ( + id int, + email text +); + +INSERT INTO users VALUES (50, 'user50@test.com'), (60, 'user60@test.com'), (70, 'user70@test.com'), (80, 'user80@test.com'); + +-- Table 1: No bucket (baseline) +CREATE TABLE orders_no_bucket ( + order_id int, + user_id int +); + +CREATE INDEX orders_no_bucket_user_id_idx ON orders_no_bucket(user_id ASC); + +INSERT INTO orders_no_bucket VALUES (1, 50), (2, 50), (3, 60), (4, 70); + +-- Table 2: With generated column in PRIMARY KEY +CREATE TABLE orders_gen ( + order_id int, + user_id int, + bucket_id int GENERATED ALWAYS AS (yb_hash_code(user_id) % 5) STORED, + PRIMARY KEY (bucket_id ASC, user_id ASC, order_id ASC) +); + +INSERT INTO orders_gen VALUES (1, 50), (2, 50), (3, 60), (4, 70); + +-- Table 3: With expression index +CREATE TABLE orders_expr ( + order_id int, + user_id int +); + +CREATE INDEX orders_expr_idx ON orders_expr((yb_hash_code(user_id) % 3) ASC, user_id ASC); + +INSERT INTO orders_expr VALUES (1, 50), (2, 50), (3, 60), (4, 70); + +ANALYZE users, orders_no_bucket, orders_gen, orders_expr; + +SET yb_prefer_bnl = false; +SET yb_enable_batchednl = false; + +-- Baseline NL +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o orders_no_bucket_user_id_idx) */ * FROM users u JOIN orders_no_bucket o ON o.user_id = u.id; + +-- NL with generated column (should show bucket condition) +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u JOIN orders_gen o ON o.user_id = u.id; + +-- NL with expression index (should show bucket condition) +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o orders_expr_idx) */ * FROM users u JOIN orders_expr o ON o.user_id = u.id; + +SET yb_prefer_bnl = true; +SET yb_enable_batchednl = true; + +-- Baseline BNL +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o orders_no_bucket_user_id_idx) */ * FROM users u JOIN orders_no_bucket o ON o.user_id = u.id; + +-- BNL with generated column (should show bucket condition) +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u JOIN orders_gen o ON o.user_id = u.id; + +-- TODO: BNL is not supported when the index clause is on an expression column +-- This will pick NL +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o orders_expr_idx) */ * FROM users u JOIN orders_expr o ON o.user_id = u.id; + +-- With WHERE clause filter +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u JOIN orders_gen o ON o.user_id = u.id WHERE u.id = 50; +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u JOIN orders_gen o ON o.user_id = u.id WHERE u.id IN (50, 60, 70); + +-- Condition in ON clause +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u JOIN orders_gen o ON o.user_id = u.id AND u.id = 50; +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u JOIN orders_gen o ON o.user_id = u.id AND u.id IN (50, 60, 70); + +-- Equality conditions in outer joins + +-- Left join with WHERE on non-nullable side +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u LEFT JOIN orders_gen o ON o.user_id = u.id WHERE u.id = 80; +SELECT * FROM users u LEFT JOIN orders_gen o ON o.user_id = u.id WHERE u.id = 80 ORDER BY u.id, o.order_id; + +-- Left join with WHERE on nullable side +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u LEFT JOIN orders_gen o ON o.user_id = u.id WHERE o.user_id = 50; +SELECT * FROM users u LEFT JOIN orders_gen o ON o.user_id = u.id WHERE o.user_id = 50 ORDER BY u.id, o.order_id; + +-- Left join with WHERE in ON clause +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u LEFT JOIN orders_gen o ON o.user_id = u.id AND o.user_id = 50; +SELECT * FROM users u LEFT JOIN orders_gen o ON o.user_id = u.id AND o.user_id = 50 ORDER BY u.id, o.order_id; + +-- Left join with expression index +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o orders_expr_idx) */ * FROM users u LEFT JOIN orders_expr o ON o.user_id = u.id WHERE u.id = 50; +SELECT * FROM users u LEFT JOIN orders_expr o ON o.user_id = u.id WHERE u.id = 50 ORDER BY u.id, o.order_id; + +-- Full join with WHERE on either side +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u FULL JOIN orders_gen o ON o.user_id = u.id WHERE u.id = 50; +SELECT * FROM users u FULL JOIN orders_gen o ON o.user_id = u.id WHERE u.id = 50 ORDER BY u.id, o.order_id; + +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u FULL JOIN orders_gen o ON o.user_id = u.id WHERE o.user_id = 60; +SELECT * FROM users u FULL JOIN orders_gen o ON o.user_id = u.id WHERE o.user_id = 60 ORDER BY u.id, o.order_id; + +-- Full join with WHERE on both sides +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o) */ * FROM users u FULL JOIN orders_gen o ON o.user_id = u.id WHERE u.id = 50 AND o.user_id = 50; + +-- Full join with expression index +EXPLAIN (COSTS OFF) SELECT /*+ NestLoop(u o) IndexScan(o orders_expr_idx) */ * FROM users u FULL JOIN orders_expr o ON o.user_id = u.id WHERE u.id = 50; +SELECT * FROM users u FULL JOIN orders_expr o ON o.user_id = u.id WHERE u.id = 50 ORDER BY u.id, o.order_id; + +-- Multi-way joins +CREATE TABLE t1 (x int); +CREATE TABLE t2 (x int, bucket_id int GENERATED ALWAYS AS (yb_hash_code(x) % 5) STORED, + PRIMARY KEY (bucket_id, x)); +CREATE TABLE t3 (x int); + +INSERT INTO t1 SELECT generate_series(1, 1000); +INSERT INTO t2 SELECT generate_series(1, 1000); +INSERT INTO t3 SELECT generate_series(1, 1000); + +ANALYZE t1, t2, t3; +SET enable_hashjoin = false; +SET enable_mergejoin = false; + +EXPLAIN (COSTS OFF) SELECT * FROM t1 JOIN t2 ON t2.x = t1.x JOIN t3 ON t3.x = t1.x; +SET join_collapse_limit = 1; +EXPLAIN (COSTS OFF) SELECT * FROM t3 JOIN t2 ON t2.x = t3.x JOIN t1 ON t1.x = t3.x; +RESET join_collapse_limit; + +-- Cleanup +DROP TABLE users; +DROP TABLE orders_no_bucket; +DROP TABLE orders_gen; +DROP TABLE orders_expr; +DROP TABLE t1, t2, t3; diff --git a/src/test/regress/sql/yb.orig.dml_insert_conflict.sql b/src/test/regress/sql/yb.orig.dml_insert_conflict.sql new file mode 100644 index 0000000000000..f5d5581e05927 --- /dev/null +++ b/src/test/regress/sql/yb.orig.dml_insert_conflict.sql @@ -0,0 +1,85 @@ +-- +-- insert...on conflict on constraint +-- +CREATE TABLE tab (i int PRIMARY KEY, j int CONSTRAINT tab_j_constraint UNIQUE); +CREATE UNIQUE INDEX ON tab (j); +CREATE UNIQUE INDEX tab_idx ON tab (j); +-- Insert some data +INSERT INTO tab VALUES (1, 1); +INSERT INTO tab VALUES (200, 200); +-- Use generated constraint name for primary key. +INSERT INTO tab VALUES (1, 7) ON CONFLICT ON CONSTRAINT tab_pkey DO UPDATE + SET j = tab.j + excluded.j; +-- Use select to verify result. +SELECT * FROM tab; +-- Use system generated name for unique index. +INSERT INTO tab VALUES (1, 200) ON CONFLICT ON CONSTRAINT tab_j_idx DO UPDATE + SET j = tab.j + excluded.j; +-- Error: Name of index is not a constraint name. +INSERT INTO tab VALUES (1, 1) ON CONFLICT ON CONSTRAINT tab_idx DO NOTHING; +-- Use conflict for unique column +INSERT INTO tab VALUES (1, 200) ON CONFLICT (j) DO UPDATE + SET j = tab.j + excluded.j; +-- Use SELECT to verify result. +SELECT * FROM tab; +-- Use conflict for unique constraint - noop +INSERT INTO tab VALUES (1, 400) ON CONFLICT ON CONSTRAINT tab_j_constraint DO NOTHING; +-- Use SELECT to verify result. +SELECT * FROM tab; +-- Use conflict for unique constraint - update +INSERT INTO tab VALUES (1, 400) ON CONFLICT ON CONSTRAINT tab_j_constraint DO UPDATE + SET j = tab.j + excluded.j; +-- Use SELECT to verify result. +SELECT * FROM tab; + +/* Taken from "arrays" test, changing temp table to YB table. */ +set enable_seqscan to off; +set enable_bitmapscan to off; + +-- test ON CONFLICT DO UPDATE with arrays +create table arr_pk_tbl (pk int4 primary key, f1 int[]); +insert into arr_pk_tbl values (1, '{1,2,3}'); +insert into arr_pk_tbl values (1, '{3,4,5}') on conflict (pk) + do update set f1[1] = excluded.f1[1], f1[3] = excluded.f1[3] + returning pk, f1; +insert into arr_pk_tbl(pk, f1[1:2]) values (1, '{6,7,8}') on conflict (pk) + do update set f1[1] = excluded.f1[1], + f1[2] = excluded.f1[2], + f1[3] = excluded.f1[3] + returning pk, f1; + +-- note: if above selects don't produce the expected tuple order, +-- then you didn't get an indexscan plan, and something is busted. +reset enable_seqscan; +reset enable_bitmapscan; + +/* Taken from "insert_conflict" test, changing YB table to temp table. */ +create temp table selfconflict (f1 int primary key, f2 int); + +begin transaction isolation level read committed; +insert into selfconflict values (1,1), (1,2) on conflict do nothing; +commit; + +begin transaction isolation level repeatable read; +insert into selfconflict values (2,1), (2,2) on conflict do nothing; +commit; + +begin transaction isolation level serializable; +insert into selfconflict values (3,1), (3,2) on conflict do nothing; +commit; + +begin transaction isolation level read committed; +insert into selfconflict values (4,1), (4,2) on conflict(f1) do update set f2 = 0; +commit; + +begin transaction isolation level repeatable read; +insert into selfconflict values (5,1), (5,2) on conflict(f1) do update set f2 = 0; +commit; + +begin transaction isolation level serializable; +insert into selfconflict values (6,1), (6,2) on conflict(f1) do update set f2 = 0; +commit; + +select * from selfconflict; + +drop table selfconflict; diff --git a/src/test/regress/sql/yb.orig.dml_key_pushdown.sql b/src/test/regress/sql/yb.orig.dml_key_pushdown.sql new file mode 100644 index 0000000000000..f103f33911929 --- /dev/null +++ b/src/test/regress/sql/yb.orig.dml_key_pushdown.sql @@ -0,0 +1,118 @@ +-- +-- KEY Pushdown Processing. +-- This file tests keys whose column ordering is different from their ordering in table. +-- Pushdown expression is in a different test. +-- +-- Test key-ordering with small tables. +CREATE TABLE tab(c1 INT, c2 INT, c3 INT, c4 INT, c5 INT, c6 INT, c7 INT, + PRIMARY KEY (c2, c6, c5, c4)); + +INSERT INTO tab VALUES (1, 2, 3, 4, 5, 6, 7), + (1, 2, 3, 41, 51, 61, 7), (1, 2, 3, 41, 51, 62, 7), (1, 2, 3, 41, 51, 63, 7), + (1, 2, 3, 41, 52, 61, 7), (1, 2, 3, 41, 52, 62, 7), (1, 2, 3, 41, 52, 63, 7), + (1, 2, 3, 41, 53, 61, 7), (1, 2, 3, 41, 53, 62, 7), (1, 2, 3, 41, 53, 63, 7), + (1, 2, 3, 42, 51, 61, 7), (1, 2, 3, 42, 51, 62, 7), (1, 2, 3, 42, 51, 63, 7), + (1, 2, 3, 42, 52, 61, 7), (1, 2, 3, 42, 52, 62, 7), (1, 2, 3, 42, 52, 63, 7), + (1, 2, 3, 42, 53, 61, 7), (1, 2, 3, 42, 53, 62, 7), (1, 2, 3, 42, 53, 63, 7), + (1, 2, 3, 43, 51, 61, 7), (1, 2, 3, 43, 51, 62, 7), (1, 2, 3, 43, 51, 63, 7), + (1, 2, 3, 43, 52, 61, 7), (1, 2, 3, 43, 52, 62, 7), (1, 2, 3, 43, 52, 63, 7), + (1, 2, 3, 43, 53, 61, 7), (1, 2, 3, 43, 53, 62, 7), (1, 2, 3, 43, 53, 63, 7); + +SELECT * FROM tab WHERE c2 = 2 AND c6 = 6 AND c5 = 5 AND c4 = 4; + +SELECT * FROM tab WHERE c2 = 2; + +SELECT * FROM tab WHERE c2 = 2 AND c6 = 6; + +SELECT * FROM tab WHERE c2 = 2 AND c5 = 5; + +SELECT * FROM tab WHERE c2 = 2 AND c4 = 4; + +SELECT * FROM tab WHERE c2 = 2 AND c6 = 6 AND c5 = 5; + +SELECT * FROM tab WHERE c2 = 2 AND c6 = 6 AND c4 = 4; + +SELECT * FROM tab WHERE c2 = 2 AND c5 = 5 AND c4 = 4; + +-- Test correctness of value ordering. +SELECT * FROM tab WHERE c2 = 2 ORDER BY c4, c5, c6; + +SELECT * FROM tab WHERE c2 = 2 ORDER BY c4, c5, c6 DESC; + +SELECT * FROM tab WHERE c2 = 2 ORDER BY c4, c5 DESC, c6; + +SELECT * FROM tab WHERE c2 = 2 ORDER BY c4, c5 DESC, c6 DESC; + +SELECT * FROM tab WHERE c2 = 2 ORDER BY c4 DESC, c5, c6; + +SELECT * FROM tab WHERE c2 = 2 ORDER BY c4 DESC, c5, c6 DESC; + +SELECT * FROM tab WHERE c2 = 2 ORDER BY c4 DESC, c5 DESC, c6; + +SELECT * FROM tab WHERE c2 = 2 ORDER BY c4 DESC, c5 DESC, c6 DESC; + +-- Test Primary Key whose column-ordering is different from the table. +SELECT * FROM airports WHERE + iso_region = 'US-MN' AND + name = 'Battle Lake Municipal Airport' AND + type = 'small_airport' AND + ident = '00MN'; + +SELECT * FROM airports WHERE + ident = '00MN' AND + name = 'Battle Lake Municipal Airport' AND + type = 'small_airport' AND + iso_region = 'US-MN'; + +SELECT * FROM airports WHERE + iso_region = 'US-MN' AND + type = 'heliport' + ORDER BY ident; + +SELECT * FROM airports WHERE + iso_region = 'US-MN' AND + name = 'Battle Lake Municipal Airport'; + +SELECT * FROM airports WHERE + iso_region = 'US-MN' AND + ident = '00MN'; + +SELECT * FROM airports WHERE + iso_region = 'US-MN' AND + name = 'Battle Lake Municipal Airport' AND + type = 'small_airport'; + +SELECT * FROM airports WHERE + iso_region = 'US-MN' AND + name = 'Battle Lake Municipal Airport' AND + ident = '00MN'; + +SELECT * FROM airports WHERE + iso_region = 'US-MN' AND + type = 'small_airport' AND + ident = '00MN'; + +-- Test Secondary Key whose column ordering is different from the table. +SELECT * FROM airports WHERE + iso_region = 'US-MS' AND type = 'heliport' AND + coordinates = '-88.55889892578125, 30.53030014038086' AND + ident = '10MS' AND + name = 'Daniel Emergency Response Team Heliport'; + +SELECT * FROM airports WHERE + iso_region = 'US-MS' AND type = 'heliport' + ORDER BY ident; + +SELECT * FROM airports WHERE + iso_region = 'US-MS' AND type = 'heliport' AND + coordinates = '-88.55889892578125, 30.53030014038086'; + +SELECT * FROM airports WHERE + iso_region = 'US-MS' AND type = 'heliport' AND + coordinates = '-88.55889892578125, 30.53030014038086' AND + ident = '10MS'; + +SELECT * FROM airports WHERE + iso_region = 'US-MS' AND type = 'heliport' AND + coordinates = '-88.55889892578125, 30.53030014038086' AND + name = 'Daniel Emergency Response Team Heliport'; diff --git a/src/test/regress/sql/yb.orig.dml_pushdown.sql b/src/test/regress/sql/yb.orig.dml_pushdown.sql new file mode 100644 index 0000000000000..5dd9b8bcb961a --- /dev/null +++ b/src/test/regress/sql/yb.orig.dml_pushdown.sql @@ -0,0 +1,1075 @@ +-- +-- Test that single-row UPDATE/DELETEs bypass scan. +-- +CREATE TABLE single_row (k int primary key, v1 int, v2 int); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE, VERBOSE TRUE) DELETE FROM single_row WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) DELETE FROM single_row WHERE k = 1 RETURNING k; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) DELETE FROM single_row WHERE k = 1 RETURNING v1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) DELETE FROM single_row WHERE k IN (1); +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE, VERBOSE TRUE) DELETE FROM single_row; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) DELETE FROM single_row WHERE k = 1 and v1 = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) DELETE FROM single_row WHERE v1 = 1 and v2 = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) DELETE FROM single_row WHERE k > 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) DELETE FROM single_row WHERE k != 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) DELETE FROM single_row WHERE k IN (1, 2); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = 1 WHERE k = 1 RETURNING k, v1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = 1, v2 = 1 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = v1 + v2 WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = v2 + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = 1, v2 = v1 + v2 WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = v2 + 1, v2 = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = 1, v2 = 2 WHERE k = 1 RETURNING k, v1, v2; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = 1, v2 = 2 WHERE k = 1 RETURNING *; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = v1 + 1, v2 = 2 WHERE k = 1 RETURNING v2; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = 1 WHERE k IN (1); +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = 3 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = power(2, 3 - 1) WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = v1 + 3 WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = v1 * 2 WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = power(2, 3 - k) WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = 1 WHERE k = 1 RETURNING v2; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = v1 + 1 WHERE k = 1 RETURNING v1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = 1 WHERE k = 1 RETURNING *; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v1 = CASE WHEN random() < 0.1 THEN 0 ELSE 1 END WHERE k = 1; +EXPLAIN (COSTS FALSE, VERBOSE TRUE) UPDATE single_row SET v2 = CASE WHEN v1 % 2 = 0 THEN v2 * 3 ELSE v2 *2 END WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k = 1 and v2 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k > 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k != 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k IN (1, 2); +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k % 2 = 0; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v2 = CASE v1 WHEN 1 THEN v2 * 2 ELSE v2 END WHERE k = 1; + +-- +-- Test single-row UPDATE/DELETE execution. +-- +INSERT INTO single_row VALUES (1, 1, 1); + +UPDATE single_row SET v1 = 2 WHERE k = 1; +SELECT * FROM single_row; + +UPDATE single_row SET v1 = v1 * 2 WHERE k = 1; +SELECT * FROM single_row; + +UPDATE single_row SET v2 = CASE WHEN v1 % 2 = 0 THEN v2 * 3 ELSE v2 *2 END WHERE k = 1; +SELECT * FROM single_row; + +DELETE FROM single_row WHERE k = 1; +SELECT * FROM single_row; + +-- +-- Test UPDATE/DELETEs of non-existent data return no rows. +-- +UPDATE single_row SET v1 = 1 WHERE k = 100 RETURNING k; +DELETE FROM single_row WHERE k = 100 RETURNING k; +SELECT * FROM single_row; + +-- +-- Test prepared statements. +-- +INSERT INTO single_row VALUES (1, 1, 1); + +PREPARE single_row_update (int, int, int) AS + UPDATE single_row SET v1 = $2, v2 = $3 WHERE k = $1; + +PREPARE single_row_delete (int) AS + DELETE FROM single_row WHERE k = $1; + +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 2, 2); +EXECUTE single_row_update (1, 2, 2); +SELECT * FROM single_row; + +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +EXECUTE single_row_delete (1); +SELECT * FROM single_row; + +-- +-- Test returning clauses. +-- +INSERT INTO single_row VALUES (1, 1, 1); + +UPDATE single_row SET v1 = 2, v2 = 2 WHERE k = 1 RETURNING v1, v2, k; +SELECT * FROM single_row; + +UPDATE single_row SET v1 = 3, v2 = 3 WHERE k = 1 RETURNING *; +SELECT * FROM single_row; + +UPDATE single_row SET v1 = v1 + 1 WHERE k = 1 RETURNING v1; +SELECT * FROM single_row; + +UPDATE single_row SET v1 = v1 + 1, v2 = 4 WHERE k = 1 RETURNING v2; +SELECT * FROM single_row; + +DELETE FROM single_row WHERE k = 1 RETURNING k; +SELECT * FROM single_row; + +--- +--- Test in transaction block. +--- +INSERT INTO single_row VALUES (1, 1, 1); + +BEGIN; +EXPLAIN (COSTS FALSE) DELETE FROM single_row WHERE k = 1; +DELETE FROM single_row WHERE k = 1; +END; + +SELECT * FROM single_row; + +-- Test UPDATE/DELETE of non-existing rows. +BEGIN; +DELETE FROM single_row WHERE k = 1; +UPDATE single_row SET v1 = 2 WHERE k = 1; +END; + +SELECT * FROM single_row; + +--- +--- Test WITH clause. +--- +INSERT INTO single_row VALUES (1, 1, 1); + +EXPLAIN (COSTS FALSE) WITH temp AS (UPDATE single_row SET v1 = 2 WHERE k = 1) + UPDATE single_row SET v1 = 2 WHERE k = 1; + +WITH temp AS (UPDATE single_row SET v1 = 2 WHERE k = 1) + UPDATE single_row SET v1 = 2 WHERE k = 1; + +SELECT * FROM single_row; + +EXPLAIN (COSTS FALSE) WITH temp AS (UPDATE single_row SET v1 = v1 + 1 WHERE k = 1 RETURNING k, v1) + UPDATE single_row SET v2 = temp.v1 FROM temp WHERE single_row.k = temp.k; + +WITH temp AS (UPDATE single_row SET v1 = v1 + 1 WHERE k = 1 RETURNING k, v1) + UPDATE single_row SET v2 = temp.v1 FROM temp WHERE single_row.k = temp.k; + +SELECT * FROM single_row; + +-- Update row that doesn't exist. +WITH temp AS (UPDATE single_row SET v1 = 2 WHERE k = 2) + UPDATE single_row SET v1 = 2 WHERE k = 2; + +SELECT * FROM single_row; + +WITH temp AS (UPDATE single_row SET v1 = v1 + 1 WHERE k = 2 RETURNING k, v1) + UPDATE single_row SET v2 = temp.v1 FROM temp WHERE single_row.k = temp.k; + +SELECT * FROM single_row; + +-- GHI 23461 +CREATE TABLE single_row_changes(k int, v1 int); +INSERT INTO single_row_changes VALUES (1, 1), (1, 2); + +WITH deleted_rows AS ( + DELETE FROM single_row_changes + WHERE k = 1 + RETURNING v1 +) +UPDATE single_row +SET v1 = v1 + (SELECT COALESCE(SUM(v1), 0) FROM deleted_rows) +WHERE k = 1; + +SELECT * FROM single_row; + +-- Updates on a table with a secondary index +CREATE INDEX single_row_v2_idx ON single_row(v2); +EXPLAIN (COSTS FALSE) WITH temp AS (UPDATE single_row SET v1 = v1 + 1 WHERE k = 1 RETURNING k, v1) + UPDATE single_row SET v2 = temp.v1 FROM temp WHERE single_row.k = temp.k; +EXPLAIN (COSTS FALSE) WITH temp AS (UPDATE single_row SET v2 = v1 + 1 WHERE k = 1 RETURNING k, v1) + UPDATE single_row SET v2 = temp.v1 FROM temp WHERE single_row.k = temp.k; +DROP INDEX single_row_v2_idx; + +-- Updates on a table with a trigger +CREATE FUNCTION notify_single_row_v1_update_trigger() RETURNS trigger AS $$ +BEGIN + RAISE NOTICE 'v1 has been updated'; + RETURN NEW; +END $$ language plpgsql; +CREATE TRIGGER notify_single_row_v1_update AFTER UPDATE ON single_row + FOR EACH ROW WHEN (OLD.v1 != NEW.v1) + EXECUTE FUNCTION notify_single_row_v1_update_trigger(); +EXPLAIN (COSTS FALSE) WITH temp AS (UPDATE single_row SET v2 = v1 + 1 WHERE k = 1 RETURNING k, v1) + UPDATE single_row SET v2 = temp.v1 FROM temp WHERE single_row.k = temp.k; +DROP TRIGGER notify_single_row_v1_update ON single_row; +DROP FUNCTION notify_single_row_v1_update_trigger; + +-- Adding secondary index should force re-planning, which would +-- then not choose single-row plan due to the secondary index. +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +CREATE INDEX single_row_index ON single_row (v1); +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +DROP INDEX single_row_index; + +-- Same as above but for UPDATE. +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 1, 1); +CREATE INDEX single_row_index ON single_row (v1); +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 1, 1); +DROP INDEX single_row_index; + +-- Adding BEFORE DELETE row triggers should do the same as secondary index. +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +CREATE TRIGGER single_row_delete_trigger BEFORE DELETE ON single_row + FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +-- UPDATE should still use single-row since trigger does not apply to it. +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 1, 1); +DROP TRIGGER single_row_delete_trigger ON single_row; + +-- Adding BEFORE UPDATE row triggers should do the same as secondary index. +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 1, 1); +CREATE TRIGGER single_row_update_trigger BEFORE UPDATE ON single_row + FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 1, 1); +-- DELETE should still use single-row since trigger does not apply to it. +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +DROP TRIGGER single_row_update_trigger ON single_row; + +-- +-- Test stored procedure +-- +CREATE PROCEDURE update_single_row_proc(pk int, new_v1 int, inc_v2 int) AS $$ + UPDATE single_row SET v1 = new_v1, v2 = v2 + inc_v2 WHERE k = pk; +$$ LANGUAGE SQL; +CALL update_single_row_proc(1, 2, 3); +SELECT * FROM single_row WHERE k = 1; +DROP PROCEDURE update_single_row_proc; + +-- +-- Test table with composite primary key. +-- +CREATE TABLE single_row_comp_key (v int, k1 int, k2 int, PRIMARY KEY (k1 HASH, k2 ASC)); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1 and k2 = 1 RETURNING k1, k2; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1 and k2 = 1 RETURNING v; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k2 = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE v = 1 and k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1 AND k2 < 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1 AND k2 != 1; + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = 1 WHERE k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = k1 + 1 WHERE k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = 1 WHERE k1 = 1 and k2 = 1 RETURNING k1, k2, v; +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = 1 + 2 WHERE k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = v + 1 WHERE k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 3 - 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = ceil(3 - 2.5) WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 3 - v1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 3 - k WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = v1 - k WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = 1 WHERE k1 = 1 and k2 = 1 and v = 1; + +-- Test the impact of functions on single row optimization. +CREATE TABLE single_row_function_test (k INTEGER NOT NULL, date_pk TIMESTAMPTZ, random_field INTEGER, v varchar, created_at TIMESTAMP, PRIMARY KEY(k, date_pk)); + +-- Verify that using unsupported non-immutable functions in the WHERE clause disables the use of fast path. +EXPLAIN (COSTS OFF) UPDATE single_row_function_test SET random_field = 2 WHERE k = 1 AND date_pk = timeofday()::TIMESTAMP; + +-- Verify that using unsupported non-immutable functions to assign values does not disable the use of fast path. +EXPLAIN (COSTS OFF) UPDATE single_row_function_test SET v=getpgusername() WHERE k = 1 AND date_pk = NOW(); + +-- Verify that using supported non-immutable functions (like random(), NOW(), timestamp, timestampz etc) which do not perform reads or writes to the database +-- to assign values/WHERE clause does not prevent the use of fast-path. +EXPLAIN (COSTS OFF) UPDATE single_row_function_test SET created_at = '2022-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE, random_field = ceil(random())::int WHERE date_pk = NOW() AND k = 1; + +-- Verify that even if the function used in the WHERE clause is a supported but volatile function, the fast path is still disabled. +EXPLAIN (COSTS OFF) UPDATE single_row_function_test SET created_at = '2022-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE, random_field = ceil(random())::int WHERE date_pk = NOW() AND k = random()::int; +DROP TABLE single_row_function_test; + +-- Test execution. +INSERT INTO single_row_comp_key VALUES (1, 2, 3); + +UPDATE single_row_comp_key SET v = 2 WHERE k1 = 2 and k2 = 3; +SELECT * FROM single_row_comp_key; + +-- try switching around the order, reversing value/key +DELETE FROM single_row_comp_key WHERE 2 = k2 and 3 = k1; +SELECT * FROM single_row_comp_key; +DELETE FROM single_row_comp_key WHERE 3 = k2 and 2 = k1; +SELECT * FROM single_row_comp_key; + +-- +-- Test table with non-standard const type. +-- +CREATE TABLE single_row_complex (k bigint PRIMARY KEY, v float); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex WHERE k = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex WHERE k = 1 RETURNING k; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex WHERE k = 1 RETURNING v; +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex WHERE k = 1 and v = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex WHERE v = 1; + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 1 WHERE k = 1 RETURNING k, v; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 1 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = v + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 3 * (v + 3 - 2) WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = k + 1 WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 1 WHERE k = 1 and v = 1; + +-- Test execution. +INSERT INTO single_row_complex VALUES (1, 1); + +UPDATE single_row_complex SET v = 2 WHERE k = 1; +SELECT * FROM single_row_complex; + +UPDATE single_row_complex SET v = 3 * (v + 3 - 2) WHERE k = 1; +SELECT * FROM single_row_complex; + +DELETE FROM single_row_complex WHERE k = 1; +SELECT * FROM single_row_complex; + +-- +-- Test table with non-const type. +-- +CREATE TABLE single_row_array (k int primary key, arr int []); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array WHERE k = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array WHERE k = 1 RETURNING k; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array WHERE k = 1 RETURNING arr; +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array WHERE k = 1 and arr[1] = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array WHERE arr[1] = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_array SET arr[1] = 1 WHERE k = 1; + +-- Test execution. +INSERT INTO single_row_array VALUES (1, ARRAY [1, 2, 3]); + +DELETE FROM single_row_array WHERE k = 1; +SELECT * FROM single_row_array; + +-- +-- Test update with complex returning clause expressions +-- +CREATE TYPE two_int AS (first integer, second integer); +CREATE TYPE two_text AS (first_text text, second_text text); +CREATE TABLE single_row_complex_returning (k int primary key, v1 int, v2 text, v3 two_text, array_int int[], v5 int); +CREATE FUNCTION assign_one_plus_param_to_v1(integer) RETURNS integer + AS 'UPDATE single_row_complex_returning SET v1 = $1 + 1 WHERE k = 1 RETURNING $1 * 2;' + LANGUAGE SQL; +CREATE FUNCTION assign_one_plus_param_to_v1_hard(integer) RETURNS two_int + AS 'UPDATE single_row_complex_returning SET v1 = $1 + 1 WHERE k = 1 RETURNING $1 * 2, v5 + 1;' + LANGUAGE SQL; + +-- Below statements should all USE single-row. +-- (1) Constant +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING 1; +-- (2) Column reference +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING v2, v3, array_int; +-- (3) Subscript +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING array_int[1]; +-- (4) Field selection +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING (v3).first_text; +-- (5) Immutable Operator Invocation +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING v2||'abc'; +-- (6) Immutable Function Call +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING power(v5, 2); +-- (7) Type Cast +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING v5::text; +-- (8) Collation Expression +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING v2 COLLATE "C"; +-- (9) Array Constructor +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING ARRAY[[v1,2,v5], [2,3,v5+1]]; +-- (10) Row Constructor +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING ROW(1,v2,v3,v5); +-- (11) Scalar Subquery +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING (SELECT MAX(v5)+1 from single_row_complex_returning); +-- (12) Mutable function +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING assign_one_plus_param_to_v1(1); +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING assign_one_plus_param_to_v1(v1); +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING assign_one_plus_param_to_v1_hard(v1); + +-- Test execution +INSERT INTO single_row_complex_returning VALUES (1, 1, 'xyz', ('a','b'), '{11, 11, 11}', 1); + +UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING 1, v2, array_int[1], (v3).first_text; +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING v2||'abc', power(v5, 2), v5::text, v2 COLLATE "C"; +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING ARRAY[[v1,2,v5], [2,3,v5+1]]; +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING ROW(1,v2,v3,v5); +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING (SELECT MAX(v5)+1 from single_row_complex_returning); +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING assign_one_plus_param_to_v1(1); +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING assign_one_plus_param_to_v1(v1); +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = v1 + 1 WHERE k = 1 RETURNING assign_one_plus_param_to_v1_hard(v1); +SELECT * FROM single_row_complex_returning; + +-- +-- Test table without a primary key. +-- +CREATE TABLE single_row_no_primary_key (a int, b int); + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_no_primary_key; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_no_primary_key WHERE a = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_no_primary_key WHERE a = 1 and b = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_no_primary_key SET a = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_no_primary_key SET b = 1 WHERE a = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_no_primary_key SET b = 1 WHERE b = 1; + +-- +-- Test table with range primary key (ASC). +-- +CREATE TABLE single_row_range_asc_primary_key (k int, v int, primary key (k ASC)); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_range_asc_primary_key WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = 1 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = ceil(2.5 + power(2,2)) WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = v + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = v + k WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = abs(5 - k) WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = 1 WHERE k > 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = 1 WHERE k != 1; + +-- Test execution +INSERT INTO single_row_range_asc_primary_key(k,v) values (1,1), (2,2), (3,3), (4,4); + +UPDATE single_row_range_asc_primary_key SET v = 10 WHERE k = 1; +SELECT * FROM single_row_range_asc_primary_key; +UPDATE single_row_range_asc_primary_key SET v = v + 1 WHERE k = 2; +SELECT * FROM single_row_range_asc_primary_key; +UPDATE single_row_range_asc_primary_key SET v = -3 WHERE k < 4 AND k >= 3; +SELECT * FROM single_row_range_asc_primary_key; +UPDATE single_row_range_asc_primary_key SET v = ceil(2.5 + power(2,2)) WHERE k = 4; +SELECT * FROM single_row_range_asc_primary_key; +DELETE FROM single_row_range_asc_primary_key WHERE k < 3; +SELECT * FROM single_row_range_asc_primary_key; +DELETE FROM single_row_range_asc_primary_key WHERE k = 4; +SELECT * FROM single_row_range_asc_primary_key; + +-- +-- Test table with range primary key (DESC). +-- +CREATE TABLE single_row_range_desc_primary_key (k int, v int, primary key (k DESC)); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_range_desc_primary_key WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = 1 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = ceil(2.5 + power(2,2)) WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = v + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = k + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = abs(5 - k) WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = 1 WHERE k > 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = 1 WHERE k != 1; + +-- Test execution +INSERT INTO single_row_range_desc_primary_key(k,v) values (1,1), (2,2), (3,3), (4,4); + +UPDATE single_row_range_desc_primary_key SET v = 10 WHERE k = 1; +SELECT * FROM single_row_range_desc_primary_key; +UPDATE single_row_range_desc_primary_key SET v = v + 1 WHERE k = 2; +SELECT * FROM single_row_range_desc_primary_key; +UPDATE single_row_range_desc_primary_key SET v = -3 WHERE k < 4 AND k >= 3; +SELECT * FROM single_row_range_desc_primary_key; +UPDATE single_row_range_desc_primary_key SET v = ceil(2.5 + power(2,2)) WHERE k = 4; +SELECT * FROM single_row_range_desc_primary_key; +DELETE FROM single_row_range_desc_primary_key WHERE k < 3; +SELECT * FROM single_row_range_desc_primary_key; +DELETE FROM single_row_range_desc_primary_key WHERE k = 4; +SELECT * FROM single_row_range_desc_primary_key; + +-- +-- Test tables with constraints. +-- +CREATE TABLE single_row_not_null_constraints (k int PRIMARY KEY, v1 int NOT NULL, v2 int NOT NULL); +CREATE TABLE single_row_check_constraints (k int PRIMARY KEY, v1 int NOT NULL, v2 int CHECK (v2 >= 0)); +CREATE TABLE single_row_check_constraints2 (k int PRIMARY KEY, v1 int NOT NULL, v2 int CHECK (v1 >= v2)); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_not_null_constraints WHERE k = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_check_constraints WHERE k = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_check_constraints2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_not_null_constraints SET v1 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_not_null_constraints SET v2 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_not_null_constraints SET v2 = v2 + null WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_check_constraints SET v1 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_check_constraints SET v2 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_check_constraints2 SET v1 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_check_constraints2 SET v2 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_not_null_constraints SET v2 = v2 + 3 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_not_null_constraints SET v1 = abs(v1), v2 = power(v2,2) WHERE k = 1; + +-- Test execution. +INSERT INTO single_row_not_null_constraints(k,v1, v2) values (1,1,1), (2,2,2), (3,3,3); +UPDATE single_row_not_null_constraints SET v1 = 2 WHERE k = 1; +UPDATE single_row_not_null_constraints SET v2 = v2 + 3 WHERE k = 1; +DELETE FROM single_row_not_null_constraints where k = 3; +SELECT * FROM single_row_not_null_constraints ORDER BY k; +UPDATE single_row_not_null_constraints SET v1 = abs(v1), v2 = power(v2,2) WHERE k = 1; +SELECT * FROM single_row_not_null_constraints ORDER BY k; +-- Should fail constraint check. +UPDATE single_row_not_null_constraints SET v2 = v2 + null WHERE k = 1; +-- Should update 0 rows (non-existent key). +UPDATE single_row_not_null_constraints SET v2 = v2 + 2 WHERE k = 4; +SELECT * FROM single_row_not_null_constraints ORDER BY k; + + +INSERT INTO single_row_check_constraints(k,v1, v2) values (1,1,1), (2,2,2), (3,3,3); +UPDATE single_row_check_constraints SET v1 = 2 WHERE k = 1; +UPDATE single_row_check_constraints SET v2 = 3 WHERE k = 1; +UPDATE single_row_check_constraints SET v2 = -3 WHERE k = 1; +DELETE FROM single_row_check_constraints where k = 3; +SELECT * FROM single_row_check_constraints ORDER BY k; + +INSERT INTO single_row_check_constraints2(k,v1, v2) values (1,1,1), (2,2,2), (3,3,3); +UPDATE single_row_check_constraints2 SET v1 = 2 WHERE k = 1; +UPDATE single_row_check_constraints2 SET v2 = 3 WHERE k = 1; +UPDATE single_row_check_constraints2 SET v2 = 1 WHERE k = 1; +DELETE FROM single_row_check_constraints2 where k = 3; +SELECT * FROM single_row_check_constraints2 ORDER BY k; + +-- +-- Test table with decimal. +-- +CREATE TABLE single_row_decimal (k int PRIMARY KEY, v1 decimal, v2 decimal(10,2), v3 int); +CREATE FUNCTION next_v3(int) returns int language sql as $$ + SELECT v3 + 1 FROM single_row_decimal WHERE k = $1; +$$; + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = v2 + 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = v3 + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = 3 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = next_v3(1) WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = v3 + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + null WHERE k = 2; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = null + v2 WHERE k = 2; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = v3 + 4 * (null - 5) WHERE k = 2; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v2 + 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = k + 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = k - v3 WHERE k = 1; + +-- Test execution. +INSERT INTO single_row_decimal(k, v1, v2, v3) values (1,1.5,1.5,1), (2,2.5,2.5,2), (3,null, null,null); +SELECT * FROM single_row_decimal ORDER BY k; +UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = v3 + 1 WHERE k = 1; +-- v2 should be rounded to 2 decimals. +SELECT * FROM single_row_decimal ORDER BY k; + +UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = 3 WHERE k = 1; +SELECT * FROM single_row_decimal ORDER BY k; +UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = next_v3(1) WHERE k = 1; +SELECT * FROM single_row_decimal ORDER BY k; + +-- Test null arguments, all expressions should evaluate to null. +UPDATE single_row_decimal SET v1 = v1 + null WHERE k = 2; +UPDATE single_row_decimal SET v2 = null + v2 WHERE k = 2; +UPDATE single_row_decimal SET v3 = v3 + 4 * (null - 5) WHERE k = 2; +SELECT * FROM single_row_decimal ORDER BY k; + +-- Test null values, all expressions should evaluate to null. +UPDATE single_row_decimal SET v1 = v1 + 1.555 WHERE k = 3; +UPDATE single_row_decimal SET v2 = v2 + 1.555 WHERE k = 3; +UPDATE single_row_decimal SET v3 = v3 + 1 WHERE k = 3; +SELECT * FROM single_row_decimal ORDER BY k; + +-- +-- Test table with foreign key constraint. +-- Should still try single-row if (and only if) the FK column is not updated. +-- +CREATE TABLE single_row_decimal_fk(k int PRIMARY KEY); +INSERT INTO single_row_decimal_fk(k) VALUES (1), (2), (3), (4); +ALTER TABLE single_row_decimal ADD FOREIGN KEY (v3) REFERENCES single_row_decimal_fk(k); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = 1.555 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + 1.555 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = 1.555 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = v2 + 1.555 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v2 + 1.555 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = k + 1.555 WHERE k = 4; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = 1 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = v3 + 1 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = k - v3 WHERE k = 4; + +-- Test execution. +INSERT INTO single_row_decimal(k, v1, v2, v3) values (4,4.5,4.5,4); + +SELECT * FROM single_row_decimal ORDER BY k; +UPDATE single_row_decimal SET v1 = v1 + 4.555 WHERE k = 4; +UPDATE single_row_decimal SET v2 = v2 + 4.555 WHERE k = 4; +UPDATE single_row_decimal SET v3 = v3 + 4 WHERE k = 4; +-- v2 should be rounded to 2 decimals. +SELECT * FROM single_row_decimal ORDER BY k; + +-- +-- Test table with indexes. +-- Should use single-row for expressions if (and only if) the indexed columns are not updated. +-- +CREATE TABLE single_row_index(k int PRIMARY KEY, v1 smallint, v2 smallint, v3 smallint); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v1 = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v1 = v1 + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v2 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v2 = v2 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v3 = 3 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v3 = v3 + 3 WHERE k = 1; + +CREATE INDEX single_row_index_idx on single_row_index(v1) include (v3); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v2 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v2 = v2 + 2 WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v1 = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v1 = v1 + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v3 = 3 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v3 = v3 + 3 WHERE k = 1; + +-- Test execution. +INSERT INTO single_row_index(k, v1, v2, v3) VALUES (1,0,0,0), (2,2,32000,2); + +SELECT * FROM single_row_index ORDER BY k; +UPDATE single_row_index SET v1 = 1 WHERE k = 1; +UPDATE single_row_index SET v2 = 2 WHERE k = 1; +UPDATE single_row_index SET v3 = 3 WHERE k = 1; +SELECT * FROM single_row_index ORDER BY k; +UPDATE single_row_index SET v1 = v1 + 1 WHERE k = 1; +UPDATE single_row_index SET v2 = v2 + 2 WHERE k = 1; +UPDATE single_row_index SET v3 = v3 + 3 WHERE k = 1; +SELECT * FROM single_row_index ORDER BY k; + +-- Test error reporting (overflow). +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v2 = v2 + 1000 WHERE k = 2; +UPDATE single_row_index SET v2 = v2 + 1000 WHERE k = 2; + +-- Test column ordering. +CREATE TABLE single_row_col_order(a int, b int, c int, d int, e int, primary key(d, b)); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS OFF) UPDATE single_row_col_order SET c = 6, a = 2, e = 10 WHERE b = 2 and d = 4; +EXPLAIN (COSTS OFF) UPDATE single_row_col_order SET c = c * c, a = a * 2, e = power(e, 2) WHERE b = 2 and d = 4; +EXPLAIN (COSTS OFF) DELETE FROM single_row_col_order WHERE b = 2 and d = 4; +EXPLAIN (COSTS OFF) UPDATE single_row_col_order SET c = 6, a = c + 2, e = 10 WHERE b = 2 and d = 4; +EXPLAIN (COSTS OFF) UPDATE single_row_col_order SET c = c * b, a = a * 2, e = power(e, 2) WHERE b = 2 and d = 4; + +-- Test execution. +INSERT INTO single_row_col_order(a,b,c,d,e) VALUES (1,2,3,4,5), (2,3,4,5,6); + +UPDATE single_row_col_order SET c = 6, a = 2, e = 10 WHERE b = 2 and d = 4; +SELECT * FROM single_row_col_order ORDER BY d, b; + +UPDATE single_row_col_order SET c = c * c, a = a * 2, e = power(e, 2) WHERE d = 4 and b = 2; +SELECT * FROM single_row_col_order ORDER BY d, b; + +DELETE FROM single_row_col_order WHERE b = 2 and d = 4; +SELECT * FROM single_row_col_order ORDER BY d, b; + +-- +-- Test single-row with default values +-- + +CREATE TABLE single_row_default_col(k int PRIMARY KEY, a int default 10, b int default 20 not null, c int); + +------------------ +-- Test Planning + +EXPLAIN (COSTS FALSE) UPDATE single_row_default_col SET a = 3 WHERE k = 2; +EXPLAIN (COSTS FALSE) UPDATE single_row_default_col SET b = 3 WHERE k = 2; +EXPLAIN (COSTS FALSE) UPDATE single_row_default_col SET a = 3, c = 4 WHERE k = 2; +EXPLAIN (COSTS FALSE) UPDATE single_row_default_col SET b = NULL, c = 5 WHERE k = 3; +EXPLAIN (COSTS FALSE) UPDATE single_row_default_col SET a = 3, b = 3, c = 3 WHERE k = 2; + +------------------ +-- Test Execution + +-- Insert should use defaults for missing columns. +INSERT INTO single_row_default_col(k, a, b, c) VALUES (1, 1, 1, 1); +INSERT INTO single_row_default_col(k, c) VALUES (2, 2); +INSERT INTO single_row_default_col(k, a, c) VALUES (3, NULL, 3); +INSERT INTO single_row_default_col(k, a, c) VALUES (4, NULL, 4); + +-- Setting b to null should not be allowed. +INSERT INTO single_row_default_col(k, a, b, c) VALUES (5, 5, NULL, 5); + +SELECT * FROM single_row_default_col ORDER BY k; + +-- Updates should not modify the existing (non-updated) values. +UPDATE single_row_default_col SET b = 3 WHERE k = 2; +SELECT * FROM single_row_default_col ORDER BY k; + +UPDATE single_row_default_col SET a = 3, c = 4 WHERE k = 2; +SELECT * FROM single_row_default_col ORDER BY k; + +-- a should stay null (because it was explicitly set). +UPDATE single_row_default_col SET b = 4, c = 5 WHERE k = 3; +SELECT * FROM single_row_default_col ORDER BY k; + +UPDATE single_row_default_col SET a = 4 WHERE k = 3; +SELECT * FROM single_row_default_col ORDER BY k; + +-- Setting b to null should not be allowed. +UPDATE single_row_default_col SET b = NULL, c = 5 WHERE k = 3; +SELECT * FROM single_row_default_col ORDER BY k; + +ALTER TABLE single_row_default_col ALTER COLUMN a SET DEFAULT 30; + +-- Insert should use the new default. +INSERT INTO single_row_default_col(k, c) VALUES (5, 5); +SELECT * FROM single_row_default_col ORDER BY k; + +-- Updates should not modify the existing (non-updated) values. +UPDATE single_row_default_col SET a = 4 WHERE k = 4; +SELECT * FROM single_row_default_col ORDER BY k; + +UPDATE single_row_default_col SET c = 6, b = 5 WHERE k = 5; +SELECT * FROM single_row_default_col ORDER BY k; + +UPDATE single_row_default_col SET c = 7, b = 7, a = 7 WHERE k = 5; +SELECT * FROM single_row_default_col ORDER BY k; + +-- Setting b to null should not be allowed. +UPDATE single_row_default_col SET b = NULL, c = 5 WHERE k = 3; +SELECT * FROM single_row_default_col ORDER BY k; + +-- +-- Test single-row with partial index +-- + +CREATE TABLE single_row_partial_index(k SERIAL PRIMARY KEY, value INT NOT NULL, status INT NOT NULL); +CREATE UNIQUE INDEX ON single_row_partial_index(value ASC) WHERE status = 10; +INSERT INTO single_row_partial_index(value, status) VALUES(1, 10), (2, 20), (3, 10), (4, 30); +EXPLAIN (COSTS OFF) SELECT * FROM single_row_partial_index WHERE status = 10; +SELECT * FROM single_row_partial_index WHERE status = 10; +EXPLAIN (COSTS OFF) UPDATE single_row_partial_index SET status = 10 WHERE k = 2; +UPDATE single_row_partial_index SET status = 10 WHERE k = 2; +SELECT * FROM single_row_partial_index WHERE status = 10; +EXPLAIN (COSTS OFF) UPDATE single_row_partial_index SET status = 9 WHERE k = 1; +UPDATE single_row_partial_index SET status = 9 WHERE k = 1; +SELECT * FROM single_row_partial_index WHERE status = 10; + +-- +-- Test single-row with index expression +-- + +CREATE TABLE single_row_expression_index(k SERIAL PRIMARY KEY, value text NOT NULL); +CREATE UNIQUE INDEX ON single_row_expression_index(lower(value) ASC); +INSERT INTO single_row_expression_index(value) VALUES('aBc'), ('deF'), ('HIJ'); +EXPLAIN (COSTS OFF) SELECT * FROM single_row_expression_index WHERE lower(value)='def'; +SELECT * FROM single_row_expression_index WHERE lower(value)='def'; +EXPLAIN (COSTS OFF) UPDATE single_row_expression_index SET value = 'kLm' WHERE k = 2; +UPDATE single_row_expression_index SET value = 'kLm' WHERE k = 2; +SELECT * FROM single_row_expression_index WHERE lower(value)='def'; +SELECT * FROM single_row_expression_index WHERE lower(value)='klm'; + +-- +-- Test array types. +-- + +----------------------------------- +-- int[] arrays. + +CREATE TABLE array_t1(k int PRIMARY KEY, arr int[]); +INSERT INTO array_t1(k, arr) VALUES (1, '{1, 2, 3, 4}'::int[]); +SELECT * FROM array_t1 ORDER BY k; + +-- the || operator. +UPDATE array_t1 SET arr = arr||'{5, 6}'::int[] WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +-- array_cat(). +UPDATE array_t1 SET arr = array_cat(arr, '{7, 8}'::int[]) WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +-- array_append(). +UPDATE array_t1 SET arr = array_append(arr, 9::int) WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +-- array_prepend(). +UPDATE array_t1 SET arr = array_prepend(0::int, arr) WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +-- array_remove(). +UPDATE array_t1 SET arr = array_remove(arr, 4) WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +-- array_replace(). +UPDATE array_t1 SET arr = array_replace(arr, 7, 77) WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +----------------------------------- +-- text[] arrays. + +CREATE TABLE array_t2(k int PRIMARY KEY, arr text[]); +INSERT INTO array_t2(k, arr) VALUES (1, '{a, b}'::text[]); +SELECT * FROM array_t2 ORDER BY k; + +UPDATE array_t2 SET arr = array_replace(arr, 'b', 'p') WHERE k = 1; +SELECT * FROM array_t2 ORDER BY k; + +UPDATE array_t2 SET arr = '{x, y, z}'::text[] WHERE k = 1; +SELECT * FROM array_t2 ORDER BY k; + +UPDATE array_t2 SET arr[2] = 'q' where k = 1; +SELECT * FROM array_t2 ORDER BY k; + +----------------------------------- +-- Arrays of composite types. + +CREATE TYPE rt as (f1 int, f2 text); +CREATE TABLE array_t3(k int PRIMARY KEY, arr rt[] NOT NULL); +INSERT INTO array_t3(k, arr) VALUES (1, '{"(1,a)", "(2,b)"}'::rt[]); +SELECT * FROM array_t3 ORDER BY k; + +UPDATE array_t3 SET arr = '{"(1,c)", "(2,d)"}'::rt[] WHERE k = 1; +SELECT * FROM array_t3 ORDER BY k; + +UPDATE array_t3 SET arr[2] = '(2,e)'::rt WHERE k = 1; +SELECT * FROM array_t3 ORDER BY k; + +UPDATE array_t3 SET arr = array_replace(arr, '(1,c)', '(1,p)') WHERE k = 1; +SELECT * FROM array_t3 ORDER BY k; + +----------------------------------- +-- Test more builtin array types. +-- INT2ARRAYOID, FLOAT8ARRAYOID, CHARARRAYOID. + +CREATE TABLE array_t4(k int PRIMARY KEY, arr1 int2[], arr2 double precision[], arr3 char[]); +INSERT INTO array_t4(k, arr1, arr2, arr3) VALUES (1, '{1, 2, 3}'::int2[], '{1.5, 2.25, 3.25}'::float[], '{a, b, c}'::char[]); +SELECT * FROM array_t4 ORDER BY k; + +-- array_replace(). +UPDATE array_t4 SET arr1 = array_replace(arr1, 2::int2, 22::int2) WHERE k = 1; +UPDATE array_t4 SET arr2 = array_replace(arr2, 2.25::double precision, 22.25::double precision) WHERE k = 1; +UPDATE array_t4 SET arr3 = array_replace(arr3, 'b'::char, 'x'::char) WHERE k = 1; +SELECT * FROM array_t4 ORDER BY k; + +-- array_cat(). +UPDATE array_t4 SET arr1 = array_cat(arr1, '{4, 5}'::int2[]) WHERE k = 1; +UPDATE array_t4 SET arr2 = array_cat(arr2, '{4.5, 5.25}'::double precision[][]) WHERE k = 1; +UPDATE array_t4 SET arr3 = array_cat(arr3, '{d, e, f}'::char[]) WHERE k = 1; +SELECT * FROM array_t4 ORDER BY k; + +-- array_prepend(). +UPDATE array_t4 SET arr1 = array_prepend(0::int2, arr1), + arr2 = array_prepend(0.5::double precision, arr2), + arr3 = array_prepend('z'::char, arr3) WHERE k = 1; +SELECT * FROM array_t4 ORDER BY k; + +-- array_remove(). +UPDATE array_t4 SET arr1 = array_remove(arr1, 3::int2), + arr2 = array_remove(arr2, 3.25::double precision), + arr3 = array_remove(arr3, 'c'::char) WHERE k = 1; +SELECT * FROM array_t4 ORDER BY k; + +----------------------------------- +-- Test json types. + +CREATE TABLE json_t1(k int PRIMARY KEY, json1 json, json2 jsonb); + +INSERT INTO json_t1 (k, json1, json2) VALUES (1, '["a", 1]'::json, '["b", 2]'::jsonb); +SELECT * FROM json_t1; + +UPDATE json_t1 SET json1 = json1 -> 0, json2 = json2||'["c", 3]'::jsonb WHERE k = 1; +SELECT * FROM json_t1; + +----------------------------------- +-- Test crypto hash functions. +CREATE TABLE crypto_hash_t1(k int PRIMARY KEY, v1 bytea, v2 text, md5v1 text, md5v2 text, sha1 bytea, sha2 bytea, sha3 bytea, sha4 bytea); +INSERT INTO crypto_hash_t1(k, v1, v2) values (1, 'foo'::bytea, 'bar'::text); +UPDATE crypto_hash_t1 SET md5v1 = md5(v1) WHERE k = 1; +UPDATE crypto_hash_t1 SET md5v2 = md5(v2) WHERE k = 1; +UPDATE crypto_hash_t1 SET sha1 = sha224(v1) WHERE k = 1; +UPDATE crypto_hash_t1 SET sha2 = sha256(v1) WHERE k = 1; +UPDATE crypto_hash_t1 SET sha3 = sha384(v1) WHERE k = 1; +UPDATE crypto_hash_t1 SET sha4 = sha512(v1) WHERE k = 1; + +SELECT * FROM crypto_hash_t1 WHERE md5v1 = md5(v1); +SELECT * FROM crypto_hash_t1 WHERE md5v2 = md5(v2); +SELECT * FROM crypto_hash_t1 WHERE sha1 = sha224(v1); +SELECT * FROM crypto_hash_t1 WHERE sha2 = sha256(v1); +SELECT * FROM crypto_hash_t1 WHERE sha3 = sha384(v1); +SELECT * FROM crypto_hash_t1 WHERE sha4 = sha512(v1); + +----------------------------------- +-- Test now() pushdown. +CREATE TABLE x (t timestamptz); +BEGIN; +INSERT INTO x VALUES (now()); +INSERT INTO x VALUES (CURRENT_TIMESTAMP); +-- both of these queries should return the same result +SELECT COUNT(*) FROM x WHERE t = now(); +SELECT COUNT(*) FROM x WHERE t = CURRENT_TIMESTAMP; +-- both of these filters should be pushed down +EXPLAIN SELECT * FROM x WHERE t = now(); +EXPLAIN SELECT * FROM x WHERE t = CURRENT_TIMESTAMP; +COMMIT; + +-- Test different SQL Value Functions. +CREATE TABLE time_test ( + ts timestamptz, + ts_prec timestamptz(3), + t timetz, + t_prec timetz(2), + local_ts timestamp, + local_ts_prec timestamp(3), + local_t time, + local_t_prec time(2), + d date +); +CREATE TABLE other_sql_value_functions ( + current_user_val name, + session_user_val name, + user_val name, + current_role_val name, + current_catalog_val name, + current_schema_val name +); +BEGIN; +-- Test time/date SQL value functions +INSERT INTO time_test VALUES ( + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP(3), + CURRENT_TIME, + CURRENT_TIME(2), + LOCALTIMESTAMP, + LOCALTIMESTAMP(3), + LOCALTIME, + LOCALTIME(2), + CURRENT_DATE +); +EXPLAIN (COSTS OFF) SELECT * FROM time_test WHERE ts = CURRENT_TIMESTAMP; +EXPLAIN (COSTS OFF) SELECT * FROM time_test WHERE ts_prec = CURRENT_TIMESTAMP(3); +EXPLAIN (COSTS OFF) SELECT * FROM time_test WHERE t = CURRENT_TIME; +EXPLAIN (COSTS OFF) SELECT * FROM time_test WHERE t_prec = CURRENT_TIME(2); +EXPLAIN (COSTS OFF) SELECT * FROM time_test WHERE d = CURRENT_DATE; +EXPLAIN (COSTS OFF) SELECT * FROM time_test WHERE ts = CURRENT_TIMESTAMP AND d = CURRENT_DATE; +SELECT COUNT(*) FROM time_test WHERE ts = CURRENT_TIMESTAMP; +SELECT COUNT(*) FROM time_test WHERE ts_prec = CURRENT_TIMESTAMP(3); +SELECT COUNT(*) FROM time_test WHERE t = CURRENT_TIME; +SELECT COUNT(*) FROM time_test WHERE t_prec = CURRENT_TIME(2); +SELECT COUNT(*) FROM time_test WHERE d = CURRENT_DATE; +SELECT COUNT(*) FROM time_test WHERE ts = CURRENT_TIMESTAMP AND d = CURRENT_DATE; +-- Test LOCALTIME variants (without timezone) +EXPLAIN SELECT * FROM time_test WHERE local_t = LOCALTIME; +EXPLAIN SELECT * FROM time_test WHERE local_t_prec = LOCALTIME(2); +EXPLAIN SELECT * FROM time_test WHERE local_ts = LOCALTIMESTAMP; +EXPLAIN SELECT * FROM time_test WHERE local_ts_prec = LOCALTIMESTAMP(3); +SELECT COUNT(*) FROM time_test WHERE local_t = LOCALTIME; +SELECT COUNT(*) FROM time_test WHERE local_t_prec = LOCALTIME(2); +SELECT COUNT(*) FROM time_test WHERE local_ts = LOCALTIMESTAMP; +SELECT COUNT(*) FROM time_test WHERE local_ts_prec = LOCALTIMESTAMP(3); +-- Test user/role/schema SQL value functions +INSERT INTO other_sql_value_functions VALUES ( + CURRENT_USER, + SESSION_USER, + USER, + CURRENT_ROLE, + CURRENT_CATALOG, + CURRENT_SCHEMA +); +EXPLAIN (COSTS OFF) SELECT * FROM other_sql_value_functions WHERE current_user_val = CURRENT_USER; +EXPLAIN (COSTS OFF) SELECT * FROM other_sql_value_functions WHERE session_user_val = SESSION_USER; +EXPLAIN (COSTS OFF) SELECT * FROM other_sql_value_functions WHERE user_val = USER; +EXPLAIN (COSTS OFF) SELECT * FROM other_sql_value_functions WHERE current_role_val = CURRENT_ROLE; +EXPLAIN (COSTS OFF) SELECT * FROM other_sql_value_functions WHERE current_catalog_val = CURRENT_CATALOG; +EXPLAIN (COSTS OFF) SELECT * FROM other_sql_value_functions WHERE current_schema_val = CURRENT_SCHEMA; +EXPLAIN (COSTS OFF) SELECT * FROM other_sql_value_functions WHERE current_user_val = CURRENT_USER AND current_schema_val = CURRENT_SCHEMA; +SELECT current_user_val FROM other_sql_value_functions WHERE current_user_val = CURRENT_USER; +SELECT session_user_val FROM other_sql_value_functions WHERE session_user_val = SESSION_USER; +SELECT user_val FROM other_sql_value_functions WHERE user_val = USER; +SELECT current_role_val FROM other_sql_value_functions WHERE current_role_val = CURRENT_ROLE; +SELECT current_catalog_val FROM other_sql_value_functions WHERE current_catalog_val = CURRENT_CATALOG; +SELECT current_schema_val FROM other_sql_value_functions WHERE current_schema_val = CURRENT_SCHEMA; +SELECT * FROM other_sql_value_functions WHERE current_user_val = CURRENT_USER AND current_schema_val = CURRENT_SCHEMA; +COMMIT; +----------------------------------- +-- Cleanup. +DROP FUNCTION next_v3; +DROP FUNCTION assign_one_plus_param_to_v1; +DROP FUNCTION assign_one_plus_param_to_v1_hard; +DROP TABLE single_row; +DROP TABLE single_row_comp_key; +DROP TABLE single_row_complex; +DROP TABLE single_row_array; +DROP TABLE single_row_complex_returning; +DROP TABLE single_row_no_primary_key; +DROP TABLE single_row_range_asc_primary_key; +DROP TABLE single_row_range_desc_primary_key; +DROP TABLE single_row_not_null_constraints; +DROP TABLE single_row_check_constraints; +DROP TABLE single_row_check_constraints2; +DROP TABLE single_row_decimal; +DROP TABLE single_row_decimal_fk; +DROP TABLE single_row_index; +DROP TABLE single_row_col_order; +DROP TABLE single_row_default_col; +DROP TABLE single_row_partial_index; +DROP TABLE single_row_expression_index; +DROP TABLE array_t1; +DROP TABLE array_t2; +DROP TABLE array_t3; +DROP TABLE array_t4; +DROP TABLE json_t1; +DROP TABLE crypto_hash_t1; +DROP TYPE rt; +DROP TYPE two_int; +DROP TYPE two_text; +DROP TABLE time_test; +DROP TABLE other_sql_value_functions; + +-- Timezone expression pushdown, checks that this works when expression pushdown is on. +CREATE TABLE tz_expr_pushdown(k int primary key, updated_at timestamptz); +SET yb_enable_expression_pushdown=on; +INSERT INTO tz_expr_pushdown VALUES (1, '2022-01-01 01:00:00+00'); +EXPLAIN (COSTS OFF) SELECT k FROM tz_expr_pushdown + WHERE EXTRACT(HOUR FROM updated_at AT TIME ZONE 'UTC') >= 1; +DROP TABLE tz_expr_pushdown; diff --git a/src/test/regress/sql/yb.orig.dml_read_time.sql b/src/test/regress/sql/yb.orig.dml_read_time.sql new file mode 100644 index 0000000000000..b0ed847ee4d5c --- /dev/null +++ b/src/test/regress/sql/yb.orig.dml_read_time.sql @@ -0,0 +1,61 @@ +-- +-- Read & write the same table in one statement. +-- +CREATE TABLE read_time_test1(i INT, t TEXT); +INSERT INTO read_time_test1 VALUES(1, 'one'); +INSERT INTO read_time_test1 SELECT * FROM read_time_test1; +INSERT INTO read_time_test1 SELECT * FROM read_time_test1; +INSERT INTO read_time_test1 SELECT * FROM read_time_test1; +INSERT INTO read_time_test1 SELECT * FROM read_time_test1; +INSERT INTO read_time_test1 SELECT * FROM read_time_test1; +INSERT INTO read_time_test1 SELECT * FROM read_time_test1; +SELECT count(*) FROM read_time_test1; +-- +-- Write table and its UNIQUE index. +-- +CREATE TABLE read_time_test2(i INT PRIMARY KEY, j INT UNIQUE, k INT); +INSERT INTO read_time_test2 VALUES (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), + (6, 6, 6), (7, 7, 7), (8, 8, 8), (9, 9, 9); +UPDATE read_time_test2 SET k = 777; +SELECT * FROM read_time_test2 ORDER BY i; +-- +-- DELETE and UPDATE the same table in one transaction. +-- +CREATE TABLE read_time_test3(i INT PRIMARY KEY, j INT); +INSERT INTO read_time_test3 VALUES (1), (2), (3), (4); +BEGIN; +DELETE FROM read_time_test3 WHERE i > 0; +UPDATE read_time_test3 SET j = 10 WHERE i = 1; +END; +SELECT * FROM read_time_test3 ORDER BY i; +-- +-- DELETE and INSERT the same table in one transaction. +-- +CREATE TABLE read_time_test4(i INT PRIMARY KEY, j INT); +INSERT INTO read_time_test4 VALUES (1), (2), (3), (4); +BEGIN; +DELETE FROM read_time_test4; +INSERT INTO read_time_test4 VALUES (1), (2), (3), (4); +END; +SELECT * FROM read_time_test4 ORDER BY i; +-- +-- Combine test cases into one transaction. +-- It appears DELETE has a bug, so the following test is disabled for now. +-- + +-- CREATE TABLE read_time_test(i INT, t TEXT, j INT UNIQUE, k INT, PRIMARY KEY(i, j)); +-- BEGIN; +-- INSERT INTO read_time_test VALUES (1, 'one', 1), (2, 'two', 2), (3, 'three', 3), (4, 'four', 4); +-- DELETE FROM read_time_test WHERE i = 1; +-- INSERT INTO read_time_test VALUES (1, 'one', 1, 1); +-- INSERT INTO read_time_test VALUES (11, 'one', 11, 11); +-- SELECT * FROM read_time_test ORDER BY i; +-- DELETE FROM read_time_test WHERE i < 10; +-- INSERT INTO read_time_test VALUES (1, 'one', 1), (2, 'two', 2), (3, 'three', 3), (4, 'four', 4); +-- SELECT * FROM read_time_test ORDER BY i, t; +-- UPDATE read_time_test SET k = 20; +-- UPDATE read_time_test SET i = 20 WHERE i < 11; +-- UPDATE read_time_test SET i = 30 WHERE i = 11; +-- SELECT * FROM read_time_test ORDER BY i, t; +-- END; +-- SELECT * FROM read_time_test ORDER BY i, t; diff --git a/src/test/regress/sql/yb.orig.dml_scankey.sql b/src/test/regress/sql/yb.orig.dml_scankey.sql new file mode 100644 index 0000000000000..c857c56dcdeb6 --- /dev/null +++ b/src/test/regress/sql/yb.orig.dml_scankey.sql @@ -0,0 +1,76 @@ +-- +-- Test index scan using both hash code and row comparison filter. +-- +CREATE TABLE hashcode_rowfilter (i int, j int, k int); +CREATE INDEX ON hashcode_rowfilter (k, j, i); +INSERT INTO hashcode_rowfilter VALUES (2, 3, 4), (1, 2, 4), (3, 4, 5); +\set filter 'yb_hash_code(k) = yb_hash_code(4) AND row(j, i) > row(2, 3)' +/*+IndexScan(hashcode_rowfilter)*/ +SELECT * FROM hashcode_rowfilter WHERE :filter; +/*+IndexOnlyScan(hashcode_rowfilter)*/ +SELECT k FROM hashcode_rowfilter WHERE :filter; + +SELECT * FROM hashcode_rowfilter + WHERE yb_hash_code(row(j, i)) = yb_hash_code(row(2, 1)); + +\set explain 'EXPLAIN (costs off)' +\set filter 'row(i, yb_hash_code(k)) > row(1, yb_hash_code(4))' +\set query 'SELECT * FROM hashcode_rowfilter WHERE :filter ORDER BY j' +\set is '/*+IndexScan(hashcode_rowfilter)*/' +-- TODO(#18347): fix output. +:explain :query; :explain :is :query; :query; :is :query; + +-- +-- Test index scan where filter is too large. +-- +CREATE TABLE large_filter (i int); +CREATE INDEX ON large_filter (i ASC); +INSERT INTO large_filter VALUES (0), (1); +\set ten 'i>0 AND i>0 AND i>0 AND i>0 AND i>0 AND i>0 AND i>0 AND i>0 AND i>0 AND i>0' +\set sixty ':ten AND :ten AND :ten AND :ten AND :ten AND :ten' +-- 64 scan keys: +\set filter ':sixty AND i>0 AND i>0 AND i>0 AND i>0' +\set query 'SELECT i FROM large_filter WHERE :filter' +:explain :query; :query; +-- 65 scan keys: +\set filter ':sixty AND i>0 AND i>0 AND i>0 AND i>0 AND i>0' +:explain :query; :query; + +-- +-- Test index scan where row comparison filter is too large. +-- +\set ten0 '0,0,0,0,0,0,0,0,0,0' +\set teni 'i,i,i,i,i,i,i,i,i,i' +\set sixty0 ':ten0,:ten0,:ten0,:ten0,:ten0,:ten0' +\set sixtyi ':teni,:teni,:teni,:teni,:teni,:teni' +-- 64 scan keys (1 header + 63 arguments): +\set filter 'row(:sixtyi,i,i,i) > row(:sixty0,0,0,0)' +:explain :query; :query; +-- 65 scan keys (1 header + 64 arguments): +\set filter 'row(:sixtyi,i,i,i,i) > row(:sixty0,0,0,0,0)' +:explain :query; :query; + +-- +-- Hash code filter should not count towards the limit. +-- +\set query 'SELECT * FROM hashcode_rowfilter WHERE :filter ORDER BY j' +\set filter 'row(:sixtyi,i,i,i) > row(:sixty0,0,0,0) AND yb_hash_code(k) = yb_hash_code(4)' +:explain :is :query; :is :query; +-- TODO(#18347): fix output. +\set filter 'row(:sixtyi,i,i,i,yb_hash_code(k)) >= row(:sixty0,0,0,0,0)' +:explain :is :query; :is :query; + +-- +-- Hash code filter should have no limit. +-- +\set one 'yb_hash_code(k) >= 0' +\set two ':one AND :one' +\set ten ':two AND :two AND :two AND :two AND :two' +\set sixty ':ten AND :ten AND :ten AND :ten AND :ten AND :ten' +-- 70 hash code scan keys: +\set filter ':sixty AND :ten' +\set query 'SELECT * FROM hashcode_rowfilter WHERE :filter ORDER BY i' +:explain :is :query; :is :query; +-- #18360 +\set one 'yb_hash_code(k) > -1' +:explain :is :query; :is :query; diff --git a/src/test/regress/sql/yb.orig.dml_single_row.sql b/src/test/regress/sql/yb.orig.dml_single_row.sql new file mode 100644 index 0000000000000..4bff313b9e85b --- /dev/null +++ b/src/test/regress/sql/yb.orig.dml_single_row.sql @@ -0,0 +1,1053 @@ +-- Regression tests for UPDATE/DELETE single row operations. +-- Expression pushdown is disabled. +SET yb_enable_expression_pushdown to off; + +CREATE OR REPLACE PROCEDURE check_fast_path_txn(query TEXT, expected BOOL) LANGUAGE PLPGSQL AS +$$ +DECLARE + output JSON; + txn_type TEXT; +BEGIN + -- Note that buffered writes may not be flushed at the the statement boundary for + -- statements in stored procedures/functions, and are instead done so before the start of other + -- statements in the procedure or at the end of the procedure. + EXECUTE 'EXPLAIN (ANALYZE, DIST, COSTS OFF, FORMAT JSON)' || query INTO output; + SELECT json_extract_path(output->0, 'Transaction')::TEXT INTO txn_type; + ASSERT (CASE WHEN txn_type = '"Fast Path"' THEN TRUE ELSE FALSE END) = expected; +END; +$$; + +-- +-- Test that single-row UPDATE/DELETEs bypass scan. +-- +CREATE TABLE single_row (k int primary key, v1 int, v2 int); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row WHERE k = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row WHERE k = 1 RETURNING k; +EXPLAIN (COSTS FALSE) DELETE FROM single_row WHERE k = 1 RETURNING v1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row WHERE k IN (1); +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row; +EXPLAIN (COSTS FALSE) DELETE FROM single_row WHERE k = 1 and v1 = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row WHERE v1 = 1 and v2 = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row WHERE k > 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row WHERE k != 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row WHERE k IN (1, 2); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k = 1 RETURNING k, v1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1, v2 = 1 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1, v2 = 2 WHERE k = 1 RETURNING k, v1, v2; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1, v2 = 2 WHERE k = 1 RETURNING *; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k IN (1); +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 3 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = power(2, 3 - 1) WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k = 1 RETURNING v2; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k = 1 RETURNING *; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = CASE WHEN random() < 0.1 THEN 0 ELSE 1 END WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = v1 + 3 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = v1 * 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = v1 + 1 WHERE k = 1 RETURNING v1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = v1 + v2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = v2 + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1, v2 = v1 + v2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = v2 + 1, v2 = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = v1 + 1, v2 = 2 WHERE k = 1 RETURNING v2; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k = 1 and v2 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k > 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k != 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k IN (1, 2); +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = power(2, 3 - k) WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 1 WHERE k % 2 = 0; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v2 = CASE WHEN v1 % 2 = 0 THEN v2 * 3 ELSE v2 *2 END WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v2 = CASE v1 WHEN 1 THEN v2 * 2 ELSE v2 END WHERE k = 1; + +-- +-- Test single-row UPDATE/DELETE execution. +-- +INSERT INTO single_row VALUES (1, 1, 1); + +UPDATE single_row SET v1 = 2 WHERE k = 1; +SELECT * FROM single_row; + +UPDATE single_row SET v1 = v1 * 2 WHERE k = 1; +SELECT * FROM single_row; + +UPDATE single_row SET v2 = CASE WHEN v1 % 2 = 0 THEN v2 * 3 ELSE v2 *2 END WHERE k = 1; +SELECT * FROM single_row; + +DELETE FROM single_row WHERE k = 1; +SELECT * FROM single_row; + +-- +-- Test UPDATE/DELETEs of non-existent data return no rows. +-- +UPDATE single_row SET v1 = 1 WHERE k = 100 RETURNING k; +DELETE FROM single_row WHERE k = 100 RETURNING k; +SELECT * FROM single_row; + +-- +-- Test prepared statements. +-- +INSERT INTO single_row VALUES (1, 1, 1); + +PREPARE single_row_update (int, int, int) AS + UPDATE single_row SET v1 = $2, v2 = $3 WHERE k = $1; + +PREPARE single_row_delete (int) AS + DELETE FROM single_row WHERE k = $1; + +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 2, 2); +EXECUTE single_row_update (1, 2, 2); +SELECT * FROM single_row; + +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +EXECUTE single_row_delete (1); +SELECT * FROM single_row; + +-- +-- Test returning clauses. +-- +INSERT INTO single_row VALUES (1, 1, 1); + +UPDATE single_row SET v1 = 2, v2 = 2 WHERE k = 1 RETURNING v1, v2, k; +SELECT * FROM single_row; + +UPDATE single_row SET v1 = 3, v2 = 3 WHERE k = 1 RETURNING *; +SELECT * FROM single_row; + +UPDATE single_row SET v1 = v1 + 1 WHERE k = 1 RETURNING v1; +SELECT * FROM single_row; + +UPDATE single_row SET v1 = v1 + 1, v2 = 4 WHERE k = 1 RETURNING v2; +SELECT * FROM single_row; + +DELETE FROM single_row WHERE k = 1 RETURNING k; +SELECT * FROM single_row; + +--- +--- Test in transaction block. +--- +INSERT INTO single_row VALUES (1, 1, 1); + +BEGIN; +EXPLAIN (COSTS FALSE) DELETE FROM single_row WHERE k = 1; +DELETE FROM single_row WHERE k = 1; +END; + +SELECT * FROM single_row; + +-- Test UPDATE/DELETE of non-existing rows. +BEGIN; +DELETE FROM single_row WHERE k = 1; +UPDATE single_row SET v1 = 2 WHERE k = 1; +END; + +SELECT * FROM single_row; + +--- +--- Test WITH clause. +--- +INSERT INTO single_row VALUES (1, 1, 1); + +EXPLAIN (COSTS FALSE) WITH temp AS (UPDATE single_row SET v1 = 2 WHERE k = 1) + UPDATE single_row SET v1 = 2 WHERE k = 1; + +WITH temp AS (UPDATE single_row SET v1 = 2 WHERE k = 1) + UPDATE single_row SET v1 = 2 WHERE k = 1; + +SELECT * FROM single_row; + +-- Update row that doesn't exist. +WITH temp AS (UPDATE single_row SET v1 = 2 WHERE k = 2) + UPDATE single_row SET v1 = 2 WHERE k = 2; + +SELECT * FROM single_row; + +-- Test for case when leftop is non-Var (GH#26536) +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE single_row SET v1 = 1 WHERE yb_hash_code(k) = yb_hash_code(1); -- not single row path +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE single_row SET v1 = 1 WHERE k = 1 AND yb_hash_code(k) = yb_hash_code(1); -- not single row path +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE single_row SET v1 = 1 WHERE 1 = k; -- single row path + +-- Adding secondary index should force re-planning, which would +-- then not choose single-row plan due to the secondary index. +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +CREATE INDEX single_row_index ON single_row (v1); +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +DROP INDEX single_row_index; + +-- Same as above but for UPDATE. +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 1, 1); +CREATE INDEX single_row_index ON single_row (v1); +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 1, 1); +DROP INDEX single_row_index; + +-- Adding BEFORE DELETE row triggers should do the same as secondary index. +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +CREATE TRIGGER single_row_delete_trigger BEFORE DELETE ON single_row + FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +-- UPDATE should still use single-row since trigger does not apply to it. +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 1, 1); +DROP TRIGGER single_row_delete_trigger ON single_row; + +-- Adding BEFORE UPDATE row triggers should do the same as secondary index. +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 1, 1); +CREATE TRIGGER single_row_update_trigger BEFORE UPDATE ON single_row + FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); +EXPLAIN (COSTS FALSE) EXECUTE single_row_update (1, 1, 1); +-- DELETE should still use single-row since trigger does not apply to it. +EXPLAIN (COSTS FALSE) EXECUTE single_row_delete (1); +DROP TRIGGER single_row_update_trigger ON single_row; + +-- +-- Test table with composite primary key. +-- +CREATE TABLE single_row_comp_key (v int, k1 int, k2 int, PRIMARY KEY (k1 HASH, k2 ASC)); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1 and k2 = 1 RETURNING k1, k2; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1 and k2 = 1 RETURNING v; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k2 = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE v = 1 and k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1 AND k2 < 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_comp_key WHERE k1 = 1 AND k2 != 1; + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = 1 WHERE k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = 1 WHERE k1 = 1 and k2 = 1 RETURNING k1, k2, v; +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = 1 + 2 WHERE k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 3 - 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = ceil(3 - 2.5) WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = v + 1 WHERE k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = k1 + 1 WHERE k1 = 1 and k2 = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_comp_key SET v = 1 WHERE k1 = 1 and k2 = 1 and v = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 3 - v1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = 3 - k WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row SET v1 = v1 - k WHERE k = 1; + +-- Test the impact of functions on single row optimization. +CREATE TABLE single_row_function_test (k INTEGER NOT NULL, date_pk TIMESTAMPTZ, random_field INTEGER, v varchar, created_at TIMESTAMP, PRIMARY KEY(k, date_pk)); + +-- Verify that using unsupported non-immutable functions in the WHERE clause disables the use of fast path. +EXPLAIN (COSTS OFF) UPDATE single_row_function_test SET random_field = 2 WHERE k = 1 AND date_pk = timeofday()::TIMESTAMP; + +-- Verify that using unsupported non-immutable functions to assign values does not disable the use of fast path. +EXPLAIN (COSTS OFF) UPDATE single_row_function_test SET v=getpgusername() WHERE k = 1 AND date_pk = NOW(); + +-- Verify that using supported non-immutable functions (like random(), NOW(), timestamp, timestampz etc) which do not perform reads or writes to the database +-- to assign values/WHERE clause does not prevent the use of fast-path. +EXPLAIN (COSTS OFF) UPDATE single_row_function_test SET created_at = '2022-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE, random_field = ceil(random())::int WHERE date_pk = NOW() AND k = 1; + +-- Verify that even if the function used in the WHERE clause is a supported but volatile function, the fast path is still disabled. +EXPLAIN (COSTS OFF) UPDATE single_row_function_test SET created_at = '2022-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE, random_field = ceil(random())::int WHERE date_pk = NOW() AND k = random()::int; +DROP TABLE single_row_function_test; + +-- Test execution. +INSERT INTO single_row_comp_key VALUES (1, 2, 3); + +UPDATE single_row_comp_key SET v = 2 WHERE k1 = 2 and k2 = 3; +SELECT * FROM single_row_comp_key; + +-- try switching around the order, reversing value/key +DELETE FROM single_row_comp_key WHERE 2 = k2 and 3 = k1; +SELECT * FROM single_row_comp_key; +DELETE FROM single_row_comp_key WHERE 3 = k2 and 2 = k1; +SELECT * FROM single_row_comp_key; + +-- +-- Test table with non-standard const type. +-- +CREATE TABLE single_row_complex (k bigint PRIMARY KEY, v float); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex WHERE k = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex WHERE k = 1 RETURNING k; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex WHERE k = 1 RETURNING v; +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex WHERE k = 1 and v = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_complex WHERE v = 1; + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 1 WHERE k = 1 RETURNING k, v; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 1 + 2 WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = k + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 1 WHERE k = 1 and v = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = v + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_complex SET v = 3 * (v + 3 - 2) WHERE k = 1; + +-- Test execution. +INSERT INTO single_row_complex VALUES (1, 1); + +UPDATE single_row_complex SET v = 2 WHERE k = 1; +SELECT * FROM single_row_complex; + +UPDATE single_row_complex SET v = 3 * (v + 3 - 2) WHERE k = 1; +SELECT * FROM single_row_complex; + +DELETE FROM single_row_complex WHERE k = 1; +SELECT * FROM single_row_complex; + +-- +-- Test table with non-const type. +-- +CREATE TABLE single_row_array (k int primary key, arr int []); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array WHERE k = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array WHERE k = 1 RETURNING k; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array WHERE k = 1 RETURNING arr; +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array WHERE k = 1 and arr[1] = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_array WHERE arr[1] = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_array SET arr[1] = 1 WHERE k = 1; + +-- Test execution. +INSERT INTO single_row_array VALUES (1, ARRAY [1, 2, 3]); + +DELETE FROM single_row_array WHERE k = 1; +SELECT * FROM single_row_array; + +-- +-- Test update with complex returning clause expressions +-- +CREATE TYPE two_int AS (first integer, second integer); +CREATE TYPE two_text AS (first_text text, second_text text); +CREATE TABLE single_row_complex_returning (k int primary key, v1 int, v2 text, v3 two_text, array_int int[], v5 int); +CREATE FUNCTION assign_one_plus_param_to_v1(integer) RETURNS integer + AS 'UPDATE single_row_complex_returning SET v1 = $1 + 1 WHERE k = 1 RETURNING $1 * 2;' + LANGUAGE SQL; +CREATE FUNCTION assign_one_plus_param_to_v1_hard(integer) RETURNS two_int + AS 'UPDATE single_row_complex_returning SET v1 = $1 + 1 WHERE k = 1 RETURNING $1 * 2, v5 + 1;' + LANGUAGE SQL; + +-- Below statements should all USE single-row. +-- (1) Constant +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 1 WHERE k = 1 RETURNING 1; +-- (2) Column reference +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 2 WHERE k = 1 RETURNING v2, v3, array_int; +-- (3) Subscript +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 3 WHERE k = 1 RETURNING array_int[1]; +-- (4) Field selection +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 4 WHERE k = 1 RETURNING (v3).first_text; +-- (5) Immutable Operator Invocation +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 5 WHERE k = 1 RETURNING v2||'abc'; +-- (6) Immutable Function Call +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 6 WHERE k = 1 RETURNING power(v5, 2); +-- (7) Type Cast +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 7 WHERE k = 1 RETURNING v5::text; +-- (8) Collation Expression +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 8 WHERE k = 1 RETURNING v2 COLLATE "C"; +-- (9) Array Constructor +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 9 WHERE k = 1 RETURNING ARRAY[[v1,2,v5], [2,3,v5+1]]; +-- (10) Row Constructor +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 10 WHERE k = 1 RETURNING ROW(1,v2,v3,v5); +-- (11) Scalar Subquery +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 11 WHERE k = 1 RETURNING (SELECT MAX(v5)+1 from single_row_complex_returning); +-- (12) Mutable function +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 12 WHERE k = 1 RETURNING assign_one_plus_param_to_v1(1); +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 12 WHERE k = 1 RETURNING assign_one_plus_param_to_v1(v1); +EXPLAIN (COSTS FALSE) UPDATE single_row_complex_returning SET v1 = 12 WHERE k = 1 RETURNING assign_one_plus_param_to_v1_hard(v1); + +-- Test execution +INSERT INTO single_row_complex_returning VALUES (1, 1, 'xyz', ('a','b'), '{11, 11, 11}', 1); + +UPDATE single_row_complex_returning SET v1 = 1 WHERE k = 1 RETURNING 1, v2, array_int[1], (v3).first_text; +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = 2 WHERE k = 1 RETURNING v2||'abc', power(v5, 2), v5::text, v2 COLLATE "C"; +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = 3 WHERE k = 1 RETURNING ARRAY[[v1,2,v5], [2,3,v5+1]]; +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = 4 WHERE k = 1 RETURNING ROW(1,v2,v3,v5); +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = 5 WHERE k = 1 RETURNING (SELECT MAX(v5)+1 from single_row_complex_returning); +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = 6 WHERE k = 1 RETURNING assign_one_plus_param_to_v1(1); +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = 7 WHERE k = 1 RETURNING assign_one_plus_param_to_v1(v1); +SELECT * FROM single_row_complex_returning; + +UPDATE single_row_complex_returning SET v1 = 8 WHERE k = 1 RETURNING assign_one_plus_param_to_v1_hard(v1); +SELECT * FROM single_row_complex_returning; + +-- +-- Test table without a primary key. +-- +CREATE TABLE single_row_no_primary_key (a int, b int); + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_no_primary_key; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_no_primary_key WHERE a = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_no_primary_key WHERE a = 1 and b = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_no_primary_key SET a = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_no_primary_key SET b = 1 WHERE a = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_no_primary_key SET b = 1 WHERE b = 1; + +-- +-- Test table with range primary key (ASC). +-- +CREATE TABLE single_row_range_asc_primary_key (k int, v int, primary key (k ASC)); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_range_asc_primary_key WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = 1 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = ceil(2.5 + power(2,2)) WHERE k = 4; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = 1 WHERE k > 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = 1 WHERE k != 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = v + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = v + k WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_asc_primary_key SET v = abs(5 - k) WHERE k = 1; + +-- Test execution +INSERT INTO single_row_range_asc_primary_key(k,v) values (1,1), (2,2), (3,3), (4,4); + +UPDATE single_row_range_asc_primary_key SET v = 10 WHERE k = 1; +SELECT * FROM single_row_range_asc_primary_key; +UPDATE single_row_range_asc_primary_key SET v = v + 1 WHERE k = 2; +SELECT * FROM single_row_range_asc_primary_key; +UPDATE single_row_range_asc_primary_key SET v = -3 WHERE k < 4 AND k >= 3; +SELECT * FROM single_row_range_asc_primary_key; +UPDATE single_row_range_asc_primary_key SET v = ceil(2.5 + power(2,2)) WHERE k = 4; +SELECT * FROM single_row_range_asc_primary_key; +DELETE FROM single_row_range_asc_primary_key WHERE k < 3; +SELECT * FROM single_row_range_asc_primary_key; +DELETE FROM single_row_range_asc_primary_key WHERE k = 4; +SELECT * FROM single_row_range_asc_primary_key; + +-- +-- Test table with range primary key (DESC). +-- +CREATE TABLE single_row_range_desc_primary_key (k int, v int, primary key (k DESC)); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_range_desc_primary_key WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = 1 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = ceil(2.5 + power(2,2)) WHERE k = 4; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = 1 WHERE k > 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = 1 WHERE k != 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = v + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = k + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_range_desc_primary_key SET v = abs(5 - k) WHERE k = 1; + +-- Test execution +INSERT INTO single_row_range_desc_primary_key(k,v) values (1,1), (2,2), (3,3), (4,4); + +UPDATE single_row_range_desc_primary_key SET v = 10 WHERE k = 1; +SELECT * FROM single_row_range_desc_primary_key; +UPDATE single_row_range_desc_primary_key SET v = v + 1 WHERE k = 2; +SELECT * FROM single_row_range_desc_primary_key; +UPDATE single_row_range_desc_primary_key SET v = -3 WHERE k < 4 AND k >= 3; +SELECT * FROM single_row_range_desc_primary_key; +UPDATE single_row_range_desc_primary_key SET v = ceil(2.5 + power(2,2)) WHERE k = 4; +SELECT * FROM single_row_range_desc_primary_key; +DELETE FROM single_row_range_desc_primary_key WHERE k < 3; +SELECT * FROM single_row_range_desc_primary_key; +DELETE FROM single_row_range_desc_primary_key WHERE k = 4; +SELECT * FROM single_row_range_desc_primary_key; + +-- +-- Test tables with constraints. +-- +CREATE TABLE single_row_not_null_constraints (k int PRIMARY KEY, v1 int NOT NULL, v2 int NOT NULL); +CREATE TABLE single_row_check_constraints (k int PRIMARY KEY, v1 int NOT NULL, v2 int CHECK (v2 >= 0)); +CREATE TABLE single_row_check_constraints2 (k int PRIMARY KEY, v1 int NOT NULL, v2 int CHECK (v1 >= v2)); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) DELETE FROM single_row_not_null_constraints WHERE k = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_check_constraints WHERE k = 1; +EXPLAIN (COSTS FALSE) DELETE FROM single_row_check_constraints2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_not_null_constraints SET v1 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_not_null_constraints SET v2 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_not_null_constraints SET v2 = v2 + null WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_not_null_constraints SET v2 = v2 + 3 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_not_null_constraints SET v1 = abs(v1), v2 = power(v2,2) WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_check_constraints SET v1 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_check_constraints SET v2 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_check_constraints2 SET v1 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_check_constraints2 SET v2 = 2 WHERE k = 1; + +-- Test execution. +INSERT INTO single_row_not_null_constraints(k,v1, v2) values (1,1,1), (2,2,2), (3,3,3); +UPDATE single_row_not_null_constraints SET v1 = 2 WHERE k = 1; +UPDATE single_row_not_null_constraints SET v2 = v2 + 3 WHERE k = 1; +DELETE FROM single_row_not_null_constraints where k = 3; +SELECT * FROM single_row_not_null_constraints ORDER BY k; +UPDATE single_row_not_null_constraints SET v1 = abs(v1), v2 = power(v2,2) WHERE k = 1; +SELECT * FROM single_row_not_null_constraints ORDER BY k; +-- Should fail constraint check. +UPDATE single_row_not_null_constraints SET v2 = v2 + null WHERE k = 1; +-- Should update 0 rows (non-existent key). +UPDATE single_row_not_null_constraints SET v2 = v2 + 2 WHERE k = 4; +SELECT * FROM single_row_not_null_constraints ORDER BY k; + + +INSERT INTO single_row_check_constraints(k,v1, v2) values (1,1,1), (2,2,2), (3,3,3); +UPDATE single_row_check_constraints SET v1 = 2 WHERE k = 1; +UPDATE single_row_check_constraints SET v2 = 3 WHERE k = 1; +UPDATE single_row_check_constraints SET v2 = -3 WHERE k = 1; +DELETE FROM single_row_check_constraints where k = 3; +SELECT * FROM single_row_check_constraints ORDER BY k; + +INSERT INTO single_row_check_constraints2(k,v1, v2) values (1,1,1), (2,2,2), (3,3,3); +UPDATE single_row_check_constraints2 SET v1 = 2 WHERE k = 1; +UPDATE single_row_check_constraints2 SET v2 = 3 WHERE k = 1; +UPDATE single_row_check_constraints2 SET v2 = 1 WHERE k = 1; +DELETE FROM single_row_check_constraints2 where k = 3; +SELECT * FROM single_row_check_constraints2 ORDER BY k; + +-- +-- Test table with decimal. +-- +CREATE TABLE single_row_decimal (k int PRIMARY KEY, v1 decimal, v2 decimal(10,2), v3 int); +CREATE FUNCTION next_v3(int) returns int language sql as $$ + SELECT v3 + 1 FROM single_row_decimal WHERE k = $1; +$$; + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + null WHERE k = 2; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = null + v2 WHERE k = 2; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = v3 + 4 * (null - 5) WHERE k = 2; +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = v2 + 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = v3 + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = 3 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = next_v3(1) WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = v3 + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v2 + 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = k + 1.555 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = k - v3 WHERE k = 1; + +-- Test execution. +INSERT INTO single_row_decimal(k, v1, v2, v3) values (1,1.5,1.5,1), (2,2.5,2.5,2), (3,null, null,null); +SELECT * FROM single_row_decimal ORDER BY k; +UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = v3 + 1 WHERE k = 1; +-- v2 should be rounded to 2 decimals. +SELECT * FROM single_row_decimal ORDER BY k; + +UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = 3 WHERE k = 1; +SELECT * FROM single_row_decimal ORDER BY k; +UPDATE single_row_decimal SET v1 = v1 + 1.555, v2 = v2 + 1.555, v3 = next_v3(1) WHERE k = 1; +SELECT * FROM single_row_decimal ORDER BY k; + +-- Test null arguments, all expressions should evaluate to null. +UPDATE single_row_decimal SET v1 = v1 + null WHERE k = 2; +UPDATE single_row_decimal SET v2 = null + v2 WHERE k = 2; +UPDATE single_row_decimal SET v3 = v3 + 4 * (null - 5) WHERE k = 2; +SELECT * FROM single_row_decimal ORDER BY k; + +-- Test null values, all expressions should evaluate to null. +UPDATE single_row_decimal SET v1 = v1 + 1.555 WHERE k = 3; +UPDATE single_row_decimal SET v2 = v2 + 1.555 WHERE k = 3; +UPDATE single_row_decimal SET v3 = v3 + 1 WHERE k = 3; +SELECT * FROM single_row_decimal ORDER BY k; + +-- +-- Test table with foreign key constraint. +-- Should still try single-row if (and only if) the FK column is not updated. +-- +CREATE TABLE single_row_decimal_fk(k int PRIMARY KEY); +INSERT INTO single_row_decimal_fk(k) VALUES (1), (2), (3), (4); +ALTER TABLE single_row_decimal ADD FOREIGN KEY (v3) REFERENCES single_row_decimal_fk(k); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = 1.555 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = 1.555 WHERE k = 4; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v1 + 1.555 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = v2 + 1.555 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = 1 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = v3 + 1 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v1 = v2 + 1.555 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v2 = k + 1.555 WHERE k = 4; +EXPLAIN (COSTS FALSE) UPDATE single_row_decimal SET v3 = k - v3 WHERE k = 4; + +-- Test execution. +INSERT INTO single_row_decimal(k, v1, v2, v3) values (4,4.5,4.5,4); + +SELECT * FROM single_row_decimal ORDER BY k; +UPDATE single_row_decimal SET v1 = v1 + 4.555 WHERE k = 4; +UPDATE single_row_decimal SET v2 = v2 + 4.555 WHERE k = 4; +UPDATE single_row_decimal SET v3 = v3 + 4 WHERE k = 4; +-- v2 should be rounded to 2 decimals. +SELECT * FROM single_row_decimal ORDER BY k; + +-- +-- Test table with indexes. +-- Should use single-row for expressions if (and only if) the indexed columns are not updated. +-- +CREATE TABLE single_row_index(k int PRIMARY KEY, v1 smallint, v2 smallint, v3 smallint); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v1 = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v2 = 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v3 = 3 WHERE k = 1; +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v1 = v1 + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v2 = v2 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v3 = v3 + 3 WHERE k = 1; + +CREATE INDEX single_row_index_idx on single_row_index(v1) include (v3); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v2 = 2 WHERE k = 1; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v2 = v2 + 2 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v1 = 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v1 = v1 + 1 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v3 = 3 WHERE k = 1; +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v3 = v3 + 3 WHERE k = 1; + +-- Test execution. +INSERT INTO single_row_index(k, v1, v2, v3) VALUES (1,0,0,0), (2,2,32000,2); + +SELECT * FROM single_row_index ORDER BY k; +UPDATE single_row_index SET v1 = 1 WHERE k = 1; +UPDATE single_row_index SET v2 = 2 WHERE k = 1; +UPDATE single_row_index SET v3 = 3 WHERE k = 1; +SELECT * FROM single_row_index ORDER BY k; +UPDATE single_row_index SET v1 = v1 + 1 WHERE k = 1; +UPDATE single_row_index SET v2 = v2 + 2 WHERE k = 1; +UPDATE single_row_index SET v3 = v3 + 3 WHERE k = 1; +SELECT * FROM single_row_index ORDER BY k; + +-- Test error reporting (overflow). +EXPLAIN (COSTS FALSE) UPDATE single_row_index SET v2 = v2 + 1000 WHERE k = 2; +UPDATE single_row_index SET v2 = v2 + 1000 WHERE k = 2; + +-- Test column ordering. +CREATE TABLE single_row_col_order(a int, b int, c int, d int, e int, primary key(d, b)); + +-- Below statements should all USE single-row. +EXPLAIN (COSTS OFF) UPDATE single_row_col_order SET c = 6, a = 2, e = 10 WHERE b = 2 and d = 4; +EXPLAIN (COSTS OFF) UPDATE single_row_col_order SET c = c * c, a = a * 2, e = power(e, 2) WHERE b = 2 and d = 4; +EXPLAIN (COSTS OFF) DELETE FROM single_row_col_order WHERE b = 2 and d = 4; + +-- Below statements should all NOT USE single-row. +EXPLAIN (COSTS OFF) UPDATE single_row_col_order SET c = 6, a = c + 2, e = 10 WHERE b = 2 and d = 4; +EXPLAIN (COSTS OFF) UPDATE single_row_col_order SET c = c * b, a = a * 2, e = power(e, 2) WHERE b = 2 and d = 4; + +-- Test execution. +INSERT INTO single_row_col_order(a,b,c,d,e) VALUES (1,2,3,4,5), (2,3,4,5,6); + +UPDATE single_row_col_order SET c = 6, a = 2, e = 10 WHERE b = 2 and d = 4; +SELECT * FROM single_row_col_order ORDER BY d, b; + +UPDATE single_row_col_order SET c = c * c, a = a * 2, e = power(e, 2) WHERE d = 4 and b = 2; +SELECT * FROM single_row_col_order ORDER BY d, b; + +DELETE FROM single_row_col_order WHERE b = 2 and d = 4; +SELECT * FROM single_row_col_order ORDER BY d, b; + +-- +-- Test single-row with default values +-- + +CREATE TABLE single_row_default_col(k int PRIMARY KEY, a int default 10, b int default 20 not null, c int); + +------------------ +-- Test Planning + +EXPLAIN (COSTS FALSE) UPDATE single_row_default_col SET a = 3 WHERE k = 2; +EXPLAIN (COSTS FALSE) UPDATE single_row_default_col SET b = 3 WHERE k = 2; +EXPLAIN (COSTS FALSE) UPDATE single_row_default_col SET a = 3, c = 4 WHERE k = 2; +EXPLAIN (COSTS FALSE) UPDATE single_row_default_col SET b = NULL, c = 5 WHERE k = 3; +EXPLAIN (COSTS FALSE) UPDATE single_row_default_col SET a = 3, b = 3, c = 3 WHERE k = 2; + +------------------ +-- Test Execution + +-- Insert should use defaults for missing columns. +INSERT INTO single_row_default_col(k, a, b, c) VALUES (1, 1, 1, 1); +INSERT INTO single_row_default_col(k, c) VALUES (2, 2); +INSERT INTO single_row_default_col(k, a, c) VALUES (3, NULL, 3); +INSERT INTO single_row_default_col(k, a, c) VALUES (4, NULL, 4); + +-- Setting b to null should not be allowed. +INSERT INTO single_row_default_col(k, a, b, c) VALUES (5, 5, NULL, 5); + +SELECT * FROM single_row_default_col ORDER BY k; + +-- Updates should not modify the existing (non-updated) values. +UPDATE single_row_default_col SET b = 3 WHERE k = 2; +SELECT * FROM single_row_default_col ORDER BY k; + +UPDATE single_row_default_col SET a = 3, c = 4 WHERE k = 2; +SELECT * FROM single_row_default_col ORDER BY k; + +-- a should stay null (because it was explicitly set). +UPDATE single_row_default_col SET b = 4, c = 5 WHERE k = 3; +SELECT * FROM single_row_default_col ORDER BY k; + +UPDATE single_row_default_col SET a = 4 WHERE k = 3; +SELECT * FROM single_row_default_col ORDER BY k; + +-- Setting b to null should not be allowed. +UPDATE single_row_default_col SET b = NULL, c = 5 WHERE k = 3; +SELECT * FROM single_row_default_col ORDER BY k; + +ALTER TABLE single_row_default_col ALTER COLUMN a SET DEFAULT 30; + +-- Insert should use the new default. +INSERT INTO single_row_default_col(k, c) VALUES (5, 5); +SELECT * FROM single_row_default_col ORDER BY k; + +-- Updates should not modify the existing (non-updated) values. +UPDATE single_row_default_col SET a = 4 WHERE k = 4; +SELECT * FROM single_row_default_col ORDER BY k; + +UPDATE single_row_default_col SET c = 6, b = 5 WHERE k = 5; +SELECT * FROM single_row_default_col ORDER BY k; + +UPDATE single_row_default_col SET c = 7, b = 7, a = 7 WHERE k = 5; +SELECT * FROM single_row_default_col ORDER BY k; + +-- Setting b to null should not be allowed. +UPDATE single_row_default_col SET b = NULL, c = 5 WHERE k = 3; +SELECT * FROM single_row_default_col ORDER BY k; + +-- +-- Test single-row with partial index +-- + +CREATE TABLE single_row_partial_index(k SERIAL PRIMARY KEY, value INT NOT NULL, status INT NOT NULL); +CREATE UNIQUE INDEX ON single_row_partial_index(value ASC) WHERE status = 10; +INSERT INTO single_row_partial_index(value, status) VALUES(1, 10), (2, 20), (3, 10), (4, 30); +EXPLAIN (COSTS OFF) SELECT * FROM single_row_partial_index WHERE status = 10; +SELECT * FROM single_row_partial_index WHERE status = 10; +EXPLAIN (COSTS OFF) UPDATE single_row_partial_index SET status = 10 WHERE k = 2; +UPDATE single_row_partial_index SET status = 10 WHERE k = 2; +SELECT * FROM single_row_partial_index WHERE status = 10; +EXPLAIN (COSTS OFF) UPDATE single_row_partial_index SET status = 9 WHERE k = 1; +UPDATE single_row_partial_index SET status = 9 WHERE k = 1; +SELECT * FROM single_row_partial_index WHERE status = 10; + +-- +-- Test single-row with index expression +-- + +CREATE TABLE single_row_expression_index(k SERIAL PRIMARY KEY, value text NOT NULL); +CREATE UNIQUE INDEX ON single_row_expression_index(lower(value) ASC); +INSERT INTO single_row_expression_index(value) VALUES('aBc'), ('deF'), ('HIJ'); +EXPLAIN (COSTS OFF) SELECT * FROM single_row_expression_index WHERE lower(value)='def'; +SELECT * FROM single_row_expression_index WHERE lower(value)='def'; +EXPLAIN (COSTS OFF) UPDATE single_row_expression_index SET value = 'kLm' WHERE k = 2; +UPDATE single_row_expression_index SET value = 'kLm' WHERE k = 2; +SELECT * FROM single_row_expression_index WHERE lower(value)='def'; +SELECT * FROM single_row_expression_index WHERE lower(value)='klm'; + +-- +-- Test array types. +-- + +----------------------------------- +-- int[] arrays. + +CREATE TABLE array_t1(k int PRIMARY KEY, arr int[]); +INSERT INTO array_t1(k, arr) VALUES (1, '{1, 2, 3, 4}'::int[]); +SELECT * FROM array_t1 ORDER BY k; + +-- the || operator. +UPDATE array_t1 SET arr = arr||'{5, 6}'::int[] WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +-- array_cat(). +UPDATE array_t1 SET arr = array_cat(arr, '{7, 8}'::int[]) WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +-- array_append(). +UPDATE array_t1 SET arr = array_append(arr, 9::int) WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +-- array_prepend(). +UPDATE array_t1 SET arr = array_prepend(0::int, arr) WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +-- array_remove(). +UPDATE array_t1 SET arr = array_remove(arr, 4) WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +-- array_replace(). +UPDATE array_t1 SET arr = array_replace(arr, 7, 77) WHERE k = 1; +SELECT * FROM array_t1 ORDER BY k; + +----------------------------------- +-- text[] arrays. + +CREATE TABLE array_t2(k int PRIMARY KEY, arr text[]); +INSERT INTO array_t2(k, arr) VALUES (1, '{a, b}'::text[]); +SELECT * FROM array_t2 ORDER BY k; + +UPDATE array_t2 SET arr = array_replace(arr, 'b', 'p') WHERE k = 1; +SELECT * FROM array_t2 ORDER BY k; + +UPDATE array_t2 SET arr = '{x, y, z}'::text[] WHERE k = 1; +SELECT * FROM array_t2 ORDER BY k; + +UPDATE array_t2 SET arr[2] = 'q' where k = 1; +SELECT * FROM array_t2 ORDER BY k; + +----------------------------------- +-- Arrays of composite types. + +CREATE TYPE rt as (f1 int, f2 text); +CREATE TABLE array_t3(k int PRIMARY KEY, arr rt[] NOT NULL); +INSERT INTO array_t3(k, arr) VALUES (1, '{"(1,a)", "(2,b)"}'::rt[]); +SELECT * FROM array_t3 ORDER BY k; + +UPDATE array_t3 SET arr = '{"(1,c)", "(2,d)"}'::rt[] WHERE k = 1; +SELECT * FROM array_t3 ORDER BY k; + +UPDATE array_t3 SET arr[2] = '(2,e)'::rt WHERE k = 1; +SELECT * FROM array_t3 ORDER BY k; + +UPDATE array_t3 SET arr = array_replace(arr, '(1,c)', '(1,p)') WHERE k = 1; +SELECT * FROM array_t3 ORDER BY k; + +----------------------------------- +-- Test more builtin array types. +-- INT2ARRAYOID, FLOAT8ARRAYOID, CHARARRAYOID. + +CREATE TABLE array_t4(k int PRIMARY KEY, arr1 int2[], arr2 double precision[], arr3 char[]); +INSERT INTO array_t4(k, arr1, arr2, arr3) VALUES (1, '{1, 2, 3}'::int2[], '{1.5, 2.25, 3.25}'::float[], '{a, b, c}'::char[]); +SELECT * FROM array_t4 ORDER BY k; + +-- array_replace(). +UPDATE array_t4 SET arr1 = array_replace(arr1, 2::int2, 22::int2) WHERE k = 1; +UPDATE array_t4 SET arr2 = array_replace(arr2, 2.25::double precision, 22.25::double precision) WHERE k = 1; +UPDATE array_t4 SET arr3 = array_replace(arr3, 'b'::char, 'x'::char) WHERE k = 1; +SELECT * FROM array_t4 ORDER BY k; + +-- array_cat(). +UPDATE array_t4 SET arr1 = array_cat(arr1, '{4, 5}'::int2[]) WHERE k = 1; +UPDATE array_t4 SET arr2 = array_cat(arr2, '{4.5, 5.25}'::double precision[][]) WHERE k = 1; +UPDATE array_t4 SET arr3 = array_cat(arr3, '{d, e, f}'::char[]) WHERE k = 1; +SELECT * FROM array_t4 ORDER BY k; + +-- array_prepend(). +UPDATE array_t4 SET arr1 = array_prepend(0::int2, arr1), + arr2 = array_prepend(0.5::double precision, arr2), + arr3 = array_prepend('z'::char, arr3) WHERE k = 1; +SELECT * FROM array_t4 ORDER BY k; + +-- array_remove(). +UPDATE array_t4 SET arr1 = array_remove(arr1, 3::int2), + arr2 = array_remove(arr2, 3.25::double precision), + arr3 = array_remove(arr3, 'c'::char) WHERE k = 1; +SELECT * FROM array_t4 ORDER BY k; + +----------------------------------- +-- Test json types. + +CREATE TABLE json_t1(k int PRIMARY KEY, json1 json, json2 jsonb); + +INSERT INTO json_t1 (k, json1, json2) VALUES (1, '["a", 1]'::json, '["b", 2]'::jsonb); +SELECT * FROM json_t1; + +UPDATE json_t1 SET json1 = json1 -> 0, json2 = json2||'["c", 3]'::jsonb WHERE k = 1; +SELECT * FROM json_t1; + +----------------------------------- +-- Test for https://github.com/yugabyte/yugabyte-db/issues/11346. +-- Original test case. +CREATE TABLE t0(c0 money, PRIMARY KEY(c0)); +INSERT INTO t0(c0) VALUES(CAST(1.38073114E9 AS MONEY)); +DELETE FROM t0 WHERE (((0.14198202)::MONEY)>=((0.14222479)::MONEY)); +-- Confirm that multi-row delete isn't done incorrectly as single row delete. +CREATE TABLE multi_row (k int primary key, v1 int, v2 int); +INSERT INTO multi_row VALUES (1, 1, 1); +INSERT INTO multi_row VALUES (2, 2, 2); +INSERT INTO multi_row VALUES (3, 3, 3); +SELECT * FROM multi_row; +-- Test if delete on top of YBSeqScan works properly (GHI #14103) +EXPLAIN (COSTS FALSE) /*+ SeqScan(multi_row) */ DELETE FROM multi_row WHERE k < 2; +/*+ SeqScan(multi_row) */ DELETE FROM multi_row WHERE k < 2; +SELECT * FROM multi_row; + +EXPLAIN (COSTS FALSE) DELETE FROM multi_row WHERE 2::MONEY <= 2::MONEY; +DELETE FROM multi_row WHERE 2::MONEY <= 2::MONEY; +SELECT * FROM multi_row; + +-- Test for case when parent and child column nos don't match. +-- GH issue: https://github.com/yugabyte/yugabyte-db/issues/23857. +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE pk2 (b int, a int NOT NULL); +ALTER TABLE pk2 DROP COLUMN b; +ALTER TABLE pk ATTACH PARTITION pk2 FOR VALUES FROM (1000) TO (2000); +INSERT into pk VALUES (1000); +EXPLAIN(costs off) UPDATE pk SET a = 1002 WHERE a = 1000; +UPDATE pk SET a = 1002 WHERE a = 1000; + +-- Test single-shard transactions in a partition table. +-- The absence of flushes and storage scans in the EXPLAIN plan indicate the use +-- of single-shard transactions. +CREATE TABLE p_test (k INT, v INT, PRIMARY KEY (k)) PARTITION BY RANGE (k); +CREATE TABLE p_test_1 PARTITION OF p_test FOR VALUES FROM (0) TO (10); +CREATE TABLE p_test_2 PARTITION OF p_test FOR VALUES FROM (10) TO (20); +CREATE TABLE p_test_3 PARTITION OF p_test FOR VALUES FROM (20) TO (30); + +-- Create an index on one of the partitions to ensure that it is not applicable +-- for single-shard transactions. +CREATE INDEX NONCONCURRENTLY ON p_test_3 (v); + +-- Inserting a single row into a single partition (without indexes) should be +-- eligible for single-shard. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO p_test VALUES (1, 1); +-- Inserting multiple rows should not be eligible for single-shard, even if all +-- inserts may end up in the same partition. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO p_test VALUES (11, 11), (12, 12); +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO p_test_1 VALUES (2, 2), (3, 3); +-- Inserts spanning multiple partitions should be ineligible for single-shard. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO p_test VALUES (4, 4), (13, 13); +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO p_test VALUES (14, 14), (5, 5); +-- Inserts involving p_test_3 should be ineligible for single-shard. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO p_test VALUES (21, 21); +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO p_test VALUES (15, 15), (22, 22); +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO p_test VALUES (23, 23), (16, 16); +-- Plans having multiple modify-table operations should be ineligible for single-shard. +EXPLAIN (ANALYZE, DIST, COSTS OFF) +WITH cte AS (INSERT INTO p_test VALUES (6, 6) RETURNING k) +INSERT INTO p_test (SELECT cte.k + 1, cte.k + 1 FROM cte); +EXPLAIN (ANALYZE, DIST, COSTS OFF) +WITH cte AS (INSERT INTO p_test_2 VALUES (17, 17) RETURNING k) +INSERT INTO p_test (SELECT cte.k + 1, cte.k + 1 FROM cte); +EXPLAIN (ANALYZE, DIST, COSTS OFF) +WITH cte AS (INSERT INTO p_test VALUES (9, 9) RETURNING k) +INSERT INTO p_test (SELECT cte.k + 10, cte.k + 10 FROM cte); +EXPLAIN (ANALYZE, DIST, COSTS OFF) +WITH cte AS (INSERT INTO p_test VALUES (8, 8) RETURNING k) +INSERT INTO p_test (SELECT cte.k + 20, cte.k + 20 FROM cte); + +SELECT * FROM p_test ORDER BY k; +DELETE FROM p_test WHERE k % 10 >= 5; + +-- Cross partition updates should never be eligible for single-shard. +-- Row movement between partitions that have no indexes or triggers. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE p_test SET k = k + 15 WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE p_test SET k = k + 15 WHERE k IN (2, 3); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE p_test SET k = (k + 15) % 20 WHERE k IN (4, 14); +-- Row movement between partitions, one of which has indexes or triggers. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE p_test SET k = (k + 11) % 30 WHERE k IN (13, 23); +INSERT INTO p_test VALUES (1, 1); +DELETE FROM p_test WHERE k % 10 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE p_test SET k = (k + 11) % 30 WHERE k IN (21, 1, 11); + +SELECT * FROM p_test ORDER BY k; + +-- +-- Test to validate the transaction type in EXPLAIN. +-- +CREATE TABLE t_simple(k INT PRIMARY KEY, v INT); +CREATE TEMP TABLE t_temp(k INT PRIMARY KEY, v INT); + +-- Writing multiple rows should make a transaction ineligible for single-shard. +CALL check_fast_path_txn('INSERT INTO t_simple (SELECT i, i FROM generate_series(1, 10) AS i)', false); +-- Writing a single row should make a transaction eligible for single-shard. +CALL check_fast_path_txn('INSERT INTO t_simple VALUES (11, 11)', true); +CALL check_fast_path_txn('UPDATE t_simple SET v = 12 WHERE k = 11', true); +CALL check_fast_path_txn('DELETE FROM t_simple WHERE k = 11', true); +-- Reads are not single-shard transactions. +CALL check_fast_path_txn('SELECT * FROM t_simple', false); +-- Combinations of reads and writes should not be single-shard either. +CALL check_fast_path_txn('DELETE FROM t_simple WHERE k < 2', false); +-- Operations involving only temporary tables do not use DocDB transactions and +-- so should not be eligible to be classified as single-shard transactions. +CALL check_fast_path_txn('INSERT INTO t_temp (SELECT i, i FROM generate_series(1, 10) AS i)', false); +CALL check_fast_path_txn('INSERT INTO t_temp VALUES (11, 11)', false); +CALL check_fast_path_txn('UPDATE t_temp SET v = 12 WHERE k = 11', false); +CALL check_fast_path_txn('DELETE FROM t_temp WHERE k = 11', false); +CALL check_fast_path_txn('SELECT * FROM t_temp', false); +CALL check_fast_path_txn('DELETE FROM t_temp WHERE k < 2', false); +-- Operations involving both temporary and non-temporary tables should also +-- not be eligible for single-shard transactions as this optimization is +-- currently not supported when multiple tables are involved. +CALL check_fast_path_txn('INSERT INTO t_simple (SELECT k + 10, v + 10 FROM t_temp WHERE k = 1)', false); +CALL check_fast_path_txn('INSERT INTO t_temp (SELECT k, v FROM t_simple WHERE k = 11)', false); +-- TODO(kramanathan): The following query currently crashes. Uncomment when fixed. +-- WITH cte AS (INSERT INTO t_temp VALUES (13, 13) RETURNING *) INSERT INTO t_simple (SELECT * FROM cte); +-- Queries in an explicit transaction block should not be labeled as single-shard transactions. +BEGIN; +CALL check_fast_path_txn('INSERT INTO t_simple VALUES (15, 15)', false); +CALL check_fast_path_txn('UPDATE t_simple SET v = 16 WHERE k = 15', false); +CALL check_fast_path_txn('DELETE FROM t_simple WHERE k = 15', false); +COMMIT; + +SELECT * FROM t_simple ORDER BY k; +SELECT * FROM t_temp ORDER BY k; + +-- Cleanup. +DROP FUNCTION next_v3; +DROP FUNCTION assign_one_plus_param_to_v1; +DROP FUNCTION assign_one_plus_param_to_v1_hard; +DROP TABLE single_row; +DROP TABLE single_row_comp_key; +DROP TABLE single_row_complex; +DROP TABLE single_row_array; +DROP TABLE single_row_complex_returning; +DROP TABLE single_row_no_primary_key; +DROP TABLE single_row_range_asc_primary_key; +DROP TABLE single_row_range_desc_primary_key; +DROP TABLE single_row_not_null_constraints; +DROP TABLE single_row_check_constraints; +DROP TABLE single_row_check_constraints2; +DROP TABLE single_row_decimal; +DROP TABLE single_row_decimal_fk; +DROP TABLE single_row_index; +DROP TABLE single_row_col_order; +DROP TABLE single_row_default_col; +DROP TABLE single_row_partial_index; +DROP TABLE single_row_expression_index; +DROP TABLE array_t1; +DROP TABLE array_t2; +DROP TABLE array_t3; +DROP TABLE array_t4; +DROP TABLE json_t1; +DROP TABLE p_test; +DROP TABLE pk; +DROP TABLE t_simple; +DROP TABLE t_temp; +DROP TYPE rt; +DROP TYPE two_int; +DROP TYPE two_text; diff --git a/src/test/regress/sql/yb.orig.dml_systable_scan.sql b/src/test/regress/sql/yb.orig.dml_systable_scan.sql new file mode 100644 index 0000000000000..99416924015bb --- /dev/null +++ b/src/test/regress/sql/yb.orig.dml_systable_scan.sql @@ -0,0 +1,103 @@ +-- +-- KEY Pushdown Processing. +-- This file tests key-pushdown for system table scan. +-- +-- Different from UserTable, system tables and its indexes are centralized in one tablet. To take +-- advantage of this fact, systable-scan queries the data using an INDEX key in one operation. +-- Normally it'd take two operations, one to select ROWID and another to select actual data. +-- +-- Test forward scan. +EXPLAIN (COSTS OFF) SELECT classid, objid, objsubid, refclassid, refobjid, deptype FROM pg_depend + WHERE deptype != 'p' AND deptype != 'e' AND deptype != 'i' + ORDER BY classid, objid, objsubid + LIMIT 2; + +-- We cannot run the following SELECT on test because different platforms have different system +-- catalog data. +-- +-- SELECT classid, objid, objsubid, refclassid, refobjid, deptype FROM pg_depend +-- WHERE deptype != 'p' AND deptype != 'e' AND deptype != 'i' +-- ORDER BY classid, objid, objsubid +-- LIMIT 2; + +-- Test reverse scan. +EXPLAIN (COSTS OFF) SELECT classid, objid, objsubid, refclassid, refobjid, deptype FROM pg_depend + WHERE deptype != 'p' AND deptype != 'e' AND deptype != 'i' + ORDER BY classid DESC, objid DESC, objsubid DESC + LIMIT 2; + +-- We cannot run the following SELECT on test because different platforms have different system +-- catalog data. +-- +-- SELECT classid, objid, objsubid, refclassid, refobjid, deptype FROM pg_depend +-- WHERE deptype != 'p' AND deptype != 'e' AND deptype != 'i' +-- ORDER BY classid DESC, objid DESC, objsubid DESC +-- LIMIT 2; + +-- +-- Test complex systable scans. +-- + +-- Existing db oid (template1). +SELECT * FROM pg_database WHERE datname = (SELECT datname FROM pg_database WHERE oid = 1); +SELECT * FROM pg_database WHERE datname IN (SELECT datname FROM pg_database WHERE oid = 1); + +-- Invalid (non-existing) db. +SELECT * FROM pg_database WHERE datname = (SELECT datname FROM pg_database WHERE oid = 0); +SELECT * FROM pg_database WHERE datname IN (SELECT datname FROM pg_database WHERE oid = 0); + +-- This is a query done by the pg_admin dashboard, testing compatiblity here. + +-- Existing db oid (template1). +SELECT 'session_stats' AS chart_name, row_to_json(t) AS chart_data +FROM (SELECT + (SELECT count(*) FROM pg_stat_activity WHERE datname = (SELECT datname FROM pg_database WHERE oid = 1)) AS "Total", + (SELECT count(*) FROM pg_stat_activity WHERE state = 'active' AND datname = (SELECT datname FROM pg_database WHERE oid = 1)) AS "Active", + (SELECT count(*) FROM pg_stat_activity WHERE state = 'idle' AND datname = (SELECT datname FROM pg_database WHERE oid = 1)) AS "Idle" +) t +UNION ALL +SELECT 'tps_stats' AS chart_name, row_to_json(t) AS chart_data +FROM (SELECT + (SELECT sum(xact_commit) + sum(xact_rollback) FROM pg_stat_database WHERE datname = (SELECT datname FROM pg_database WHERE oid = 1)) AS "Transactions", + (SELECT sum(xact_commit) FROM pg_stat_database WHERE datname = (SELECT datname FROM pg_database WHERE oid = 1)) AS "Commits", + (SELECT sum(xact_rollback) FROM pg_stat_database WHERE datname = (SELECT datname FROM pg_database WHERE oid = 1)) AS "Rollbacks" +) t; + +-- Invalid (non-existing) db. +SELECT 'session_stats' AS chart_name, row_to_json(t) AS chart_data +FROM (SELECT + (SELECT count(*) FROM pg_stat_activity WHERE datname = (SELECT datname FROM pg_database WHERE oid = 0)) AS "Total", + (SELECT count(*) FROM pg_stat_activity WHERE state = 'active' AND datname = (SELECT datname FROM pg_database WHERE oid = 0)) AS "Active", + (SELECT count(*) FROM pg_stat_activity WHERE state = 'idle' AND datname = (SELECT datname FROM pg_database WHERE oid = 0)) AS "Idle" +) t +UNION ALL +SELECT 'tps_stats' AS chart_name, row_to_json(t) AS chart_data +FROM (SELECT + (SELECT sum(xact_commit) + sum(xact_rollback) FROM pg_stat_database WHERE datname = (SELECT datname FROM pg_database WHERE oid = 0)) AS "Transactions", + (SELECT sum(xact_commit) FROM pg_stat_database WHERE datname = (SELECT datname FROM pg_database WHERE oid = 0)) AS "Commits", + (SELECT sum(xact_rollback) FROM pg_stat_database WHERE datname = (SELECT datname FROM pg_database WHERE oid = 0)) AS "Rollbacks" +) t; + +-- Test NULL returned by function. + +-- Mark the function as stable to ensure pushdown. +CREATE OR REPLACE FUNCTION test_null_pushdown() +RETURNS Name AS $$ +BEGIN +return null; +END; +$$ LANGUAGE plpgsql STABLE; + +-- Expect pushdown in all cases. +EXPLAIN (costs off) /*+IndexScan(pg_database)*/ SELECT * FROM pg_database WHERE datname = test_null_pushdown(); +EXPLAIN (costs off) /*+IndexScan(pg_database)*/ SELECT * FROM pg_database WHERE datname IN (test_null_pushdown()); +EXPLAIN (costs off) /*+IndexScan(pg_database)*/ SELECT * FROM pg_database WHERE datname IN ('template1', test_null_pushdown(), 'template0'); + +-- Test execution. +/*+IndexScan(pg_database)*/ SELECT * FROM pg_database WHERE datname = test_null_pushdown(); +/*+IndexScan(pg_database)*/ SELECT * FROM pg_database WHERE datname IN (test_null_pushdown()); +-- Test null mixed with valid (existing) options. +/*+IndexScan(pg_database)*/ SELECT * FROM pg_database WHERE datname IN ('template1', test_null_pushdown(), 'template0'); +-- Test null(s) mixed with invalid (existing) options. +SELECT * FROM pg_database WHERE datname IN ('non_existing_db1', test_null_pushdown(), 'non_existing_db2', test_null_pushdown()); + diff --git a/src/test/regress/sql/yb.orig.drop_table.sql b/src/test/regress/sql/yb.orig.drop_table.sql new file mode 100644 index 0000000000000..d5d3211bf3e55 --- /dev/null +++ b/src/test/regress/sql/yb.orig.drop_table.sql @@ -0,0 +1,17 @@ +-- +-- YB_TABLE Testsuite: Testing DDL Statments for TABLE DROP. +-- +-- Test: Drop Multiple Tables +CREATE TABLE table_drop_test1(a int, b int); +CREATE TABLE table_drop_test2(c int, d int); +DROP TABLE table_drop_test1, table_drop_test2; +-- +-- Test: Drop Too Many Tables and Ensure Error +CREATE TABLE table_drop_test1(a int, b int); +DROP TABLE table_drop_test1, table_drop_test2; +INSERT INTO table_drop_test1 VALUES(1,2); +-- +-- Test: Drop Too Many Tables IF EXISTS and Ensure NOTICE +DROP TABLE IF EXISTS table_drop_test1, table_drop_test2; +INSERT INTO table_drop_test1 VALUES(1,2); +-- diff --git a/src/test/regress/sql/yb.orig.errors.sql b/src/test/regress/sql/yb.orig.errors.sql new file mode 100644 index 0000000000000..8f528779b04e5 --- /dev/null +++ b/src/test/regress/sql/yb.orig.errors.sql @@ -0,0 +1,102 @@ +-- Check for unsupported system columns. +CREATE TABLE test_tab1(id INT); +INSERT INTO test_tab1 VALUES (1) RETURNING ctid; +INSERT INTO test_tab1 VALUES (2) RETURNING cmin; +INSERT INTO test_tab1 VALUES (3) RETURNING cmax; +INSERT INTO test_tab1 VALUES (4) RETURNING xmin; +INSERT INTO test_tab1 VALUES (5) RETURNING xmax; +EXPLAIN (VERBOSE, COSTS OFF) SELECT ctid FROM test_tab1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT cmin FROM test_tab1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT cmax FROM test_tab1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT xmin FROM test_tab1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT xmax FROM test_tab1; +SELECT ctid FROM test_tab1; +SELECT cmin FROM test_tab1; +SELECT cmax FROM test_tab1; +SELECT xmin FROM test_tab1; +SELECT xmax FROM test_tab1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT ctid FROM test_tab1 WHERE id = 1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT cmin FROM test_tab1 WHERE id = 2; +EXPLAIN (VERBOSE, COSTS OFF) SELECT cmax FROM test_tab1 WHERE id = 3; +EXPLAIN (VERBOSE, COSTS OFF) SELECT xmin FROM test_tab1 WHERE id = 4; +EXPLAIN (VERBOSE, COSTS OFF) SELECT xmax FROM test_tab1 WHERE id = 5; +SELECT ctid FROM test_tab1 WHERE id = 1; +SELECT cmin FROM test_tab1 WHERE id = 2; +SELECT cmax FROM test_tab1 WHERE id = 3; +SELECT xmin FROM test_tab1 WHERE id = 4; +SELECT xmax FROM test_tab1 WHERE id = 5; +-- With primary key. +CREATE TABLE test_tab2(id INT, PRIMARY KEY(id)); +INSERT INTO test_tab2 VALUES (1) RETURNING ctid; +INSERT INTO test_tab2 VALUES (2) RETURNING cmin; +INSERT INTO test_tab2 VALUES (3) RETURNING cmax; +INSERT INTO test_tab2 VALUES (4) RETURNING xmin; +INSERT INTO test_tab2 VALUES (5) RETURNING xmax; +EXPLAIN (VERBOSE, COSTS OFF) SELECT ctid FROM test_tab2 WHERE id = 1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT cmin FROM test_tab2 WHERE id = 2; +EXPLAIN (VERBOSE, COSTS OFF) SELECT cmax FROM test_tab2 WHERE id = 3; +EXPLAIN (VERBOSE, COSTS OFF) SELECT xmin FROM test_tab2 WHERE id = 4; +EXPLAIN (VERBOSE, COSTS OFF) SELECT xmax FROM test_tab2 WHERE id = 5; +SELECT ctid FROM test_tab2 WHERE id = 1; +SELECT cmin FROM test_tab2 WHERE id = 2; +SELECT cmax FROM test_tab2 WHERE id = 3; +SELECT xmin FROM test_tab2 WHERE id = 4; +SELECT xmax FROM test_tab2 WHERE id = 5; +-- All system columns should work for temp TABLE. +CREATE temp TABLE test_temp_tab(id INT, PRIMARY KEY(id)); +INSERT INTO test_temp_tab VALUES (1) RETURNING ctid; +INSERT INTO test_temp_tab VALUES (2) RETURNING cmin; +INSERT INTO test_temp_tab VALUES (3) RETURNING cmax; +/* YB TODO: uncomment with issue #17154. +-- Since xmin and xmax output can easily change, don't directly output them. +WITH yb_with AS ( +INSERT INTO test_temp_tab VALUES (4) RETURNING xmin +) SELECT count(*) FROM yb_with; +WITH yb_with AS ( +INSERT INTO test_temp_tab VALUES (5) RETURNING xmax +) SELECT count(*) FROM yb_with; +*/ +EXPLAIN (VERBOSE, COSTS OFF) SELECT ctid FROM test_temp_tab; +EXPLAIN (VERBOSE, COSTS OFF) SELECT cmin FROM test_temp_tab; +EXPLAIN (VERBOSE, COSTS OFF) SELECT cmax FROM test_temp_tab; +-- Since xmin and xmax output can easily change, don't directly output them. +EXPLAIN (VERBOSE, COSTS OFF) +WITH yb_with AS ( +SELECT xmin FROM test_temp_tab +) SELECT count(*) FROM yb_with; +EXPLAIN (VERBOSE, COSTS OFF) +WITH yb_with AS ( +SELECT xmax FROM test_temp_tab +) SELECT count(*) FROM yb_with; +SELECT ctid FROM test_temp_tab; +SELECT cmin FROM test_temp_tab; +SELECT cmax FROM test_temp_tab; +-- Since xmin and xmax output can easily change, don't directly output them. +WITH yb_with AS ( +SELECT xmin FROM test_temp_tab +) SELECT count(*) FROM yb_with; +WITH yb_with AS ( +SELECT xmax FROM test_temp_tab +) SELECT count(*) FROM yb_with; +EXPLAIN (VERBOSE, COSTS OFF) SELECT ctid FROM test_temp_tab WHERE id = 1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT cmin FROM test_temp_tab WHERE id = 2; +EXPLAIN (VERBOSE, COSTS OFF) SELECT cmax FROM test_temp_tab WHERE id = 3; +-- Since xmin and xmax output can easily change, don't directly output them. +EXPLAIN (VERBOSE, COSTS OFF) +WITH yb_with AS ( +SELECT xmin FROM test_temp_tab WHERE id = 4 +) SELECT count(*) FROM yb_with; +EXPLAIN (VERBOSE, COSTS OFF) +WITH yb_with AS ( +SELECT xmax FROM test_temp_tab WHERE id = 5 +) SELECT count(*) FROM yb_with; +SELECT ctid FROM test_temp_tab WHERE id = 1; +SELECT cmin FROM test_temp_tab WHERE id = 2; +SELECT cmax FROM test_temp_tab WHERE id = 3; +-- Since xmin and xmax output can easily change, don't directly output them. +WITH yb_with AS ( +SELECT xmin FROM test_temp_tab WHERE id = 4 +) SELECT count(*) FROM yb_with; +WITH yb_with AS ( +SELECT xmax FROM test_temp_tab WHERE id = 5 +) SELECT count(*) FROM yb_with; diff --git a/src/test/regress/sql/yb.orig.event_trigger.sql b/src/test/regress/sql/yb.orig.event_trigger.sql new file mode 100644 index 0000000000000..e74ad866ca47a --- /dev/null +++ b/src/test/regress/sql/yb.orig.event_trigger.sql @@ -0,0 +1,102 @@ + +-- Test event trigger ordering. +-- If more than one event trigger is defined for a particular event, +-- they will fire in alphabetical order by trigger name. + +-- Create the event trigger functions for logging. +-- Since event trigger functions cannot take arguments (and do not support +-- the tg_name variable) we have to create individual functions. +-- Make sure to create them in non-alphabetical order. +CREATE OR REPLACE FUNCTION test_event_trigger_ybc() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'Calling trigger ybc on %', tg_event; +END; +$$; + +CREATE OR REPLACE FUNCTION test_event_trigger_xyz() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'Calling trigger xbc on %', tg_event; +END; +$$; + +CREATE OR REPLACE FUNCTION test_event_trigger_abc() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'Calling trigger abc on %', tg_event; +END; +$$; + +CREATE OR REPLACE FUNCTION test_event_trigger_foo() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'Calling trigger foo on %', tg_event; +END; +$$; + +CREATE OR REPLACE FUNCTION test_event_trigger_bar() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'Calling trigger bar on %', tg_event; +END; +$$; + +CREATE OR REPLACE FUNCTION test_event_trigger_truncate() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'Calling trigger truncate on %', tg_event; +END; +$$; + +-- Create the ddl_start event triggers. +-- Make sure to create them in non-alphabetical order. +-- (and different from functions order). +CREATE EVENT TRIGGER foo ON ddl_command_start EXECUTE PROCEDURE test_event_trigger_foo(); +CREATE EVENT TRIGGER ybc ON ddl_command_start EXECUTE PROCEDURE test_event_trigger_ybc(); +CREATE EVENT TRIGGER bar ON ddl_command_start EXECUTE PROCEDURE test_event_trigger_bar(); +CREATE EVENT TRIGGER xyz ON ddl_command_start EXECUTE PROCEDURE test_event_trigger_xyz(); +CREATE EVENT TRIGGER abc ON ddl_command_start EXECUTE PROCEDURE test_event_trigger_abc(); +CREATE EVENT TRIGGER truncate_abc ON ddl_command_start + WHEN TAG in ('TRUNCATE TABLE') + EXECUTE PROCEDURE test_event_trigger_truncate(); + +-- Create the ddl_end event triggers. +-- Make sure to create them in non-alphabetical order +-- (and different from functions or start triggers order). +CREATE EVENT TRIGGER end_xyz ON ddl_command_end EXECUTE PROCEDURE test_event_trigger_xyz(); +CREATE EVENT TRIGGER end_bar ON ddl_command_end EXECUTE PROCEDURE test_event_trigger_bar(); +CREATE EVENT TRIGGER end_ybc ON ddl_command_end EXECUTE PROCEDURE test_event_trigger_ybc(); +CREATE EVENT TRIGGER end_abc ON ddl_command_end EXECUTE PROCEDURE test_event_trigger_abc(); +CREATE EVENT TRIGGER end_foo ON ddl_command_end EXECUTE PROCEDURE test_event_trigger_foo(); +CREATE EVENT TRIGGER end_truncate_abc ON ddl_command_end + WHEN TAG in ('TRUNCATE TABLE') + EXECUTE PROCEDURE test_event_trigger_truncate(); + +CREATE TABLE test(a int PRIMARY KEY, b int); + +INSERT INTO test values (1,2); +INSERT INTO test values (2,3); +INSERT INTO test values (3,4); +SELECT * FROM test; + +-- Verify TRUNCATE TABLE does not fire ddl_command_start triggers unless +-- a explicit TAG has been set. +TRUNCATE TABLE test; + +DROP TABLE test; + +-- Verify that yb_db_admin role can use event triggers. +CREATE ROLE superuser_role SUPERUSER; +CREATE ROLE non_superuser_role; +SET SESSION AUTHORIZATION yb_db_admin; +CREATE EVENT TRIGGER admin_foo ON ddl_command_start EXECUTE PROCEDURE test_event_trigger_foo(); +CREATE EVENT TRIGGER admin_bar ON ddl_command_end EXECUTE PROCEDURE test_event_trigger_bar(); +ALTER EVENT TRIGGER admin_foo DISABLE; +ALTER EVENT TRIGGER admin_foo ENABLE REPLICA; +ALTER EVENT TRIGGER admin_foo ENABLE ALWAYS; +ALTER EVENT TRIGGER admin_foo RENAME TO admin_foo_new; +ALTER EVENT TRIGGER admin_foo_new OWNER TO superuser_role; +ALTER EVENT TRIGGER admin_foo_new OWNER TO non_superuser_role; +ALTER EVENT TRIGGER admin_foo_new OWNER TO yb_db_admin; +DROP EVENT TRIGGER admin_foo_new; diff --git a/src/test/regress/sql/yb.orig.explain.sql b/src/test/regress/sql/yb.orig.explain.sql new file mode 100644 index 0000000000000..6a04c885acbab --- /dev/null +++ b/src/test/regress/sql/yb.orig.explain.sql @@ -0,0 +1,119 @@ +CREATE TABLE p1 (k INT PRIMARY KEY); +CREATE TABLE p2 (k INT PRIMARY KEY); +CREATE TABLE t_test (k INT PRIMARY KEY, + p1_fk INT REFERENCES p1(k), + p2_fk INT REFERENCES p2(k) DEFERRABLE INITIALLY DEFERRED); + +-- Load some data into each of the tables +INSERT INTO p1 (SELECT generate_series(1, 15)); +INSERT INTO p2 (SELECT generate_series(1, 15)); + +-- Commit stats are not displayed by default +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_test VALUES (1, 1, 1); + +-- When enabled, commit stats should include read requests indicating the deferred foreign key check +EXPLAIN (ANALYZE, DIST, COMMIT, COSTS OFF) INSERT INTO t_test VALUES (2, 2, 2); + +-- Commit stats should not be shown if EXPLAIN is invoked within an explicit transaction +BEGIN; +EXPLAIN (ANALYZE, DIST, COMMIT, COSTS OFF) INSERT INTO t_test VALUES (3, 3, 3); +ROLLBACK; + +-- Commit stats should be displayed for the last statement in a multi-statement query +-- when autocommit is turned on. This is only applicable to the simple query protocol +-- which is used by the regress tests. +EXPLAIN (ANALYZE, DIST, COMMIT, COSTS OFF) INSERT INTO t_test VALUES (4, 4, 4) \; EXPLAIN (ANALYZE, DIST, COMMIT, COSTS OFF) INSERT INTO t_test VALUES (5, 5, 5); + +\set AUTOCOMMIT OFF +-- However, when autocommit is turned off, commit stats should not be displayed as nothing is committed. +EXPLAIN (ANALYZE, DIST, COMMIT, COSTS OFF) INSERT INTO t_test VALUES (6, 6, 6) \; EXPLAIN (ANALYZE, DIST, COMMIT, COSTS OFF) INSERT INTO t_test VALUES (7, 7, 7); +ROLLBACK; +\set AUTOCOMMIT ON + +-- Commit stats should not be displayed if there is nothing to display. +EXPLAIN (ANALYZE, DIST, COMMIT, COSTS OFF) INSERT INTO p1 VALUES (101); + +-- Commit stats should respect the other options provided to EXPLAIN. No commit stats should be +-- displayed for any of the following queries. +EXPLAIN (COSTS OFF) INSERT INTO t_test VALUES (8, 8, 8); +EXPLAIN (COMMIT, COSTS OFF) INSERT INTO t_test VALUES (8, 8, 8); +EXPLAIN (ANALYZE, COMMIT, COSTS OFF) INSERT INTO t_test VALUES (9, 9, 9); +EXPLAIN (ANALYZE, DIST, COMMIT, SUMMARY OFF, COSTS OFF) INSERT INTO t_test VALUES (10, 10, 10); + +-- Commit stats should also respect the format option. +EXPLAIN (ANALYZE, DIST, COMMIT, COSTS OFF, FORMAT JSON) INSERT INTO t_test VALUES (11, 11, 11); + +SELECT * FROM t_test ORDER BY k; + +-- DEBUG without DIST should produce a warning and no debug metrics +EXPLAIN (ANALYZE, DEBUG, COSTS OFF) SELECT * FROM p1 WHERE k = 1; + +-- DEBUG with yb_explain_hide_non_deterministic_fields should produce a warning and no debug metrics +SHOW yb_explain_hide_non_deterministic_fields; +EXPLAIN (ANALYZE, DEBUG, DIST, COSTS OFF) SELECT * FROM p1 WHERE k = 1; + + +-- #30768 Check duplicate fields +SET client_min_messages TO 'warning'; + +SET yb_explain_hide_non_deterministic_fields = off; +SET yb_enable_cbo = on; + +DROP FUNCTION IF EXISTS get_duplicate_json_keys CASCADE; +CREATE FUNCTION get_duplicate_json_keys(j json) +RETURNS SETOF text AS $$ +DECLARE + v json; +BEGIN + -- 1. If it's an object, check for duplicates at the current level + IF json_typeof(j) = 'object' THEN + RETURN QUERY + SELECT keys + FROM json_object_keys(j) AS keys + GROUP BY keys + HAVING COUNT(*) > 1; + + -- 2. Recurse into all nested values of the object + FOR v IN SELECT value FROM json_each(j) LOOP + RETURN QUERY SELECT get_duplicate_json_keys(v); + END LOOP; + + -- 3. If it's an array, recurse into all array elements + ELSIF json_typeof(j) = 'array' THEN + FOR v IN SELECT value FROM json_array_elements(j) LOOP + RETURN QUERY SELECT get_duplicate_json_keys(v); + END LOOP; + END IF; +END; +$$ LANGUAGE plpgsql; + +RESET client_min_messages; +DO $$ +DECLARE + explain_json json; + dup_key text; + found_duplicates boolean := false; +BEGIN + -- Capture the EXPLAIN (FORMAT JSON) output into our json variable. + EXECUTE '/*+ IndexScan(p1) */'|| + 'EXPLAIN (ANALYZE, DIST, DEBUG, COSTS OFF, SUMMARY OFF, FORMAT JSON)'|| + 'SELECT * from p1 where k = 1' + INTO explain_json; + + -- Look for duplicates + FOR dup_key IN SELECT DISTINCT * FROM get_duplicate_json_keys(explain_json) + LOOP + found_duplicates := true; + RAISE WARNING 'Duplicate key found in EXPLAIN output: "%"', dup_key; + END LOOP; + + IF found_duplicates THEN + RAISE EXCEPTION 'Test Failed: EXPLAIN output contains duplicated fields.'; + ELSE + RAISE NOTICE 'Test Passed: No duplicated fields detected.'; + END IF; +END; +$$; + +RESET yb_explain_hide_non_deterministic_fields; +RESET yb_enable_cbo; diff --git a/src/test/regress/sql/yb.orig.explicit_row_lock_batching.sql b/src/test/regress/sql/yb.orig.explicit_row_lock_batching.sql new file mode 100644 index 0000000000000..42792259797d4 --- /dev/null +++ b/src/test/regress/sql/yb.orig.explicit_row_lock_batching.sql @@ -0,0 +1,267 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF)' + +-- +-- YB tests for lock batching +-- + +-- Enable and set lock batching size +SET yb_explicit_row_locking_batch_size = 1024; + +CREATE TABLE yb_locks_t(k INT PRIMARY KEY); +INSERT INTO yb_locks_t SELECT i FROM generate_series(1, 15) AS i; + +CREATE TABLE yb_locks_t2(k1 INT, k2 INT, k3 INT, v INT, PRIMARY KEY(k1, k2, k3)); +INSERT INTO yb_locks_t2 SELECT x, x + 1, x + 2, x + 3 FROM generate_series(1, 13, 4) AS s(x); + +CREATE TABLE yb_locks_tasc(k INT, PRIMARY KEY (k ASC)); +INSERT INTO yb_locks_tasc SELECT i FROM generate_series(1, 3) AS i; + +-- Test plain (unlocked case). +\set query ':P SELECT * FROM yb_locks_t WHERE k = 5;' +\i :iter_P2 + +-- Test single-RPC select+lock (no LockRows node). +\set query ':P SELECT * FROM yb_locks_t WHERE k = 5 FOR UPDATE;' +\i :iter_P2 + +-- Test other types of locking. +\set query ':P SELECT * FROM yb_locks_t WHERE k = 5 FOR SHARE;' +\i :iter_P2 + +\set query ':P SELECT * FROM yb_locks_t WHERE k = 5 FOR NO KEY UPDATE;' +\i :iter_P2 + +\set query ':P SELECT * FROM yb_locks_t WHERE k = 5 FOR KEY SHARE;' +\i :iter_P2 + +-- Test LockRows node (more RPCs), and scan is unlocked. +\set query ':P SELECT * FROM yb_locks_t FOR UPDATE;' +\i :iter_P2 + +-- Test NOWAIT (should batch) +\set query ':P SELECT * FROM yb_locks_t FOR UPDATE NOWAIT;' +\i :iter_P2 + +-- Test SKIP LOCKED (shouldn't batch) +\set query ':P SELECT * FROM yb_locks_t FOR UPDATE SKIP LOCKED;' +\i :iter_P2 + +-- Test different values of yb_explicit_row_locking_batch_size +-- Disabled +SET yb_explicit_row_locking_batch_size = 1; +\set query ':P SELECT * FROM yb_locks_t FOR UPDATE;' +\i :iter_P2 + +-- Invalid value +SET yb_explicit_row_locking_batch_size = 0; + +-- Value greater than write buffer and maximum number of rows read in an RPC +SET yb_explicit_row_locking_batch_size = 10000; +\set query ':P SELECT * FROM yb_locks_t FOR UPDATE;' +\i :iter_P2 + +-- Revert back to original recommended value +SET yb_explicit_row_locking_batch_size = 1024; + +-- Test with multi-column primary key. +\set query ':P SELECT * FROM yb_locks_t2 WHERE k1 = 1 AND k2 = 2 AND k3 = 3 FOR UPDATE;' +\i :iter_P2 + +-- Test with partial column set for primary key (should use LockRows). +\set query ':P SELECT * FROM yb_locks_t2 WHERE k1 = 1 AND k2 = 2 FOR UPDATE;' +\i :iter_P2 + +-- Test LockRows node is used for join. +\set query ':P SELECT * FROM yb_locks_t2, yb_locks_t WHERE yb_locks_t2.k1 = yb_locks_t.k FOR UPDATE;' +\i :iter_P2 + +-- Test simple join with top-level locking +\set query ':P SELECT * FROM yb_locks_t JOIN yb_locks_t2 ON yb_locks_t.k = yb_locks_t2.k1 WHERE yb_locks_t.k > 0 AND yb_locks_t.k <= 5 FOR UPDATE;' +\i :iter_P2 + +-- Test join with leaf-level locking (sub-query) +\set query ':P SELECT * FROM yb_locks_t JOIN (SELECT * FROM yb_locks_t2 FOR UPDATE) AS z ON yb_locks_t.k = z.k1 WHERE yb_locks_t.k > 0 AND yb_locks_t.k <= 5;' +\i :iter_P2 + +-- Test when limit returns less than filtered query +\set query ':P SELECT * FROM yb_locks_t WHERE yb_locks_t.k < 6 FOR UPDATE LIMIT 3;' +\i :iter_P2 + +-- Test limit with a Sort plan node between LockRows and Scan node +\set query ':P SELECT * FROM yb_locks_t ORDER BY k LIMIT 13 FOR UPDATE;' +\i :iter_P2 + +-- Test when multiple limit nodes +\set query ':P SELECT * FROM (SELECT * FROM yb_locks_t LIMIT 13 FOR UPDATE) AS y JOIN (SELECT * FROM yb_locks_t2 LIMIT 3 FOR UPDATE) AS z ON y.k = z.k1;' +\i :iter_P2 + +-- Test when FOR UPDATE is supposed to acquire less locks than the limit (1 row on yb_locks_t, but all limited rows on yb_locks_t2) +\set query ':P SELECT * FROM (SELECT * FROM yb_locks_t ORDER BY k LIMIT 10 FOR UPDATE) AS y JOIN (SELECT * FROM yb_locks_t2 ORDER BY k1 LIMIT 3 FOR UPDATE) AS z ON y.k = z.k1 LIMIT 1;' +\i :iter_P2 + +-- Test with CTE subquery locking +\set query ':P WITH cte AS (SELECT * FROM yb_locks_t FOR UPDATE) SELECT * FROM cte;' +\i :iter_P2 + +-- Test with multiple CTE subquery locking +\set query ':P WITH cte_yb_locks_t AS (SELECT * FROM yb_locks_t FOR UPDATE), cte_yb_locks_t2 AS (SELECT * FROM yb_locks_t2 FOR UPDATE) SELECT * FROM cte_yb_locks_t JOIN cte_yb_locks_t2 ON cte_yb_locks_t.k = cte_yb_locks_t2.k1 LIMIT 2;' +\i :iter_P2 + +-- Test top level limit with multiple CTE subquery locking +\set query ':P WITH cte_yb_locks_t AS (SELECT * FROM yb_locks_t FOR UPDATE), cte_yb_locks_t2 AS (SELECT * FROM yb_locks_t2 FOR UPDATE) SELECT * FROM cte_yb_locks_t JOIN cte_yb_locks_t2 ON cte_yb_locks_t.k = cte_yb_locks_t2.k1 LIMIT 2;' +\i :iter_P2 + +-- Test LockRows node is used with ASC table when YB Sequential Scan is used. +\set Q1 '/*+ SeqScan(yb_locks_tasc) */' +\set query ':P :Q1 SELECT * FROM yb_locks_tasc WHERE k = 1 FOR UPDATE;' +\i :iter_P2 + +-- In isolation level SERIALIZABLE, all locks are done during scans. +BEGIN ISOLATION LEVEL SERIALIZABLE; + +-- Test same locking as for REPEATABLE READ (default isolation). +\set query ':P SELECT * FROM yb_locks_t WHERE k = 5 FOR UPDATE;' +\i :iter_P2 + +-- Test no LockRows node for sequential scan. +\set query ':P SELECT * FROM yb_locks_t FOR UPDATE;' +\i :iter_P2 + +-- Test no LockRows node for join. +\set query ':P SELECT * FROM yb_locks_t2, yb_locks_t WHERE yb_locks_t2.k1 = yb_locks_t.k FOR UPDATE;' +\i :iter_P2 + +-- Test locking, and no LockRows node, when using an ASC table and YB Sequential Scan. +-- (No WHERE clause.) +\set Q1 '/*+ SeqScan(yb_locks_tasc) */' +\set query ':P :Q1 SELECT * FROM yb_locks_tasc FOR UPDATE;' +\i :iter_P2 + +-- For an ASC table, should lock inline, with no LockRows node. +\set query ':P SELECT * FROM yb_locks_tasc ORDER BY k FOR UPDATE;' +\i :iter_P2 + +COMMIT; + +-- Test with single-RPC select+lock turned off. +SET yb_lock_pk_single_rpc TO OFF; + +\set query ':P SELECT * FROM yb_locks_t WHERE k = 5 FOR UPDATE;' +\i :iter_P2 + +-- Test that with the yb_lock_pk_single_rpc off, SERIALIZABLE still locks during the scan +-- (no LockRows). +BEGIN ISOLATION LEVEL SERIALIZABLE; +\set query ':P SELECT * FROM yb_locks_t WHERE k = 5 FOR UPDATE;' +\i :iter_P2 +COMMIT; + +SET yb_lock_pk_single_rpc TO ON; + +CREATE INDEX ON yb_locks_t2 (v); + +-- Test with an index. We use a LockRows node for an index. +\set query ':P SELECT * FROM yb_locks_t2 WHERE v = 4 FOR UPDATE;' +\i :iter_P2 + +-- Test only the indexed column. +\set query ':P SELECT v FROM yb_locks_t2 WHERE v = 4 FOR UPDATE;' +\i :iter_P2 + +-- Isolation level SERIALIZABLE still locks with the scan though (no LockRows). +BEGIN ISOLATION LEVEL SERIALIZABLE; + +\set query ':P SELECT * FROM yb_locks_t2 WHERE v = 4 FOR UPDATE;' +\i :iter_P2 + +COMMIT; + +-- Test partitions. +CREATE TABLE yb_locks_partition(a char PRIMARY KEY) PARTITION BY LIST (a); +CREATE TABLE yb_locks_partition_default PARTITION OF yb_locks_partition DEFAULT; +CREATE TABLE yb_locks_partition_a PARTITION OF yb_locks_partition FOR VALUES IN ('a'); + +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) SELECT * FROM yb_locks_partition WHERE a = 'a' FOR UPDATE; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) SELECT * FROM yb_locks_partition WHERE a = 'b' FOR UPDATE; + +BEGIN ISOLATION LEVEL SERIALIZABLE; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) SELECT * FROM yb_locks_partition WHERE a = 'a' FOR UPDATE; +COMMIT; + +-- Test JSON. +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF, FORMAT JSON) +SELECT * FROM yb_locks_t WHERE k = 5 FOR UPDATE; + +-- Test that prepared statements made in isolation level RR with a LockRows node do not +-- crash when executed in isolation level SERIALIZABLE. +SET yb_lock_pk_single_rpc TO OFF; +-- Store prepared plans right away. +SET yb_test_planner_custom_plan_threshold to 1; +PREPARE yb_locks_plan_rr(INT) AS SELECT * FROM yb_locks_t WHERE k = $1 FOR UPDATE; +\set query 'EXECUTE yb_locks_plan_rr(1);' +:query +-- The $1 in the EXPLAIN output tells you it's a stored plan. +\set query ':explain EXECUTE yb_locks_plan_rr(1);' +:query + +BEGIN ISOLATION LEVEL SERIALIZABLE; +-- The LockRows node has a "no-op" annotation. +\set query ':P EXECUTE yb_locks_plan_rr(1);' +\i :iter_P2 +-- In JSON mode, the LockRows node has an "Executes" field set to false. +\set query 'EXECUTE yb_locks_plan_rr(1);' +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF, FORMAT JSON) +:query +COMMIT; + +-- Test that prepared statements made in isolation level SERIALIZABLE, but for a PK, are +-- able to lock PK when run in RR and RC. +SET yb_lock_pk_single_rpc TO ON; +BEGIN ISOLATION LEVEL SERIALIZABLE; +PREPARE yb_locks_plan_ser(INT) AS SELECT * FROM yb_locks_t WHERE k = $1 FOR UPDATE; +\set query ':P EXECUTE yb_locks_plan_ser(1);' +\i :iter_P2 +COMMIT; + +\set query ':P EXECUTE yb_locks_plan_ser(1);' +\i :iter_P2 + +-- Test that prepared statements made in isolation level SERIALIZABLE, for a non-PK, have +-- a LockRows node that functions in RR and RC. +BEGIN ISOLATION LEVEL SERIALIZABLE; +PREPARE yb_locks_plan_ser_all(INT) AS SELECT * FROM yb_locks_t FOR UPDATE; +\set query ':P EXECUTE yb_locks_plan_ser_all(1);' +\i :iter_P2 +COMMIT; +EXECUTE yb_locks_plan_ser_all(1); +EXPLAIN (COSTS OFF) +EXECUTE yb_locks_plan_ser_all(1); + +-- Test table with Postgres side filtering +CREATE TABLE yb_events ( + id SERIAL PRIMARY KEY, + event_name TEXT, + event_time TIMESTAMP WITH TIME ZONE +); +INSERT INTO yb_events(event_name, event_time) +SELECT 'Future Event ' || i, NOW() + (i || ' days')::interval +FROM generate_series(1, 10) AS s(i); +INSERT INTO yb_events(event_name, event_time) +SELECT 'Past Event ' || i, NOW() - (i || ' days')::interval +FROM generate_series(1, 10) AS s(i); + +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) +SELECT * FROM yb_events WHERE event_time > NOW(); + +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) +SELECT * FROM yb_events WHERE event_time < NOW(); + +-- Reset +SET yb_lock_pk_single_rpc TO DEFAULT; +SET yb_test_planner_custom_plan_threshold TO DEFAULT; +DROP TABLE yb_locks_t, yb_locks_t2, yb_locks_tasc, yb_locks_partition, yb_events; diff --git a/src/test/regress/sql/yb.orig.explicit_row_lock_planning.sql b/src/test/regress/sql/yb.orig.explicit_row_lock_planning.sql new file mode 100644 index 0000000000000..aec286908b634 --- /dev/null +++ b/src/test/regress/sql/yb.orig.explicit_row_lock_planning.sql @@ -0,0 +1,184 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (COSTS OFF)' + +-- +-- YB tests for locking +-- + +CREATE TABLE yb_locks_t (k int PRIMARY KEY); +INSERT INTO yb_locks_t VALUES (1),(2),(3),(4),(5); + +CREATE TABLE yb_locks_t2 (k1 int, k2 int, k3 int, v int, PRIMARY KEY(k1, k2, k3)); +INSERT INTO yb_locks_t2 VALUES (1,2,3,4),(5,6,7,8); + +CREATE TABLE yb_locks_tasc (k int, PRIMARY KEY (k ASC)); +INSERT INTO yb_locks_tasc VALUES (1),(2),(3); + +SET yb_lock_pk_single_rpc TO ON; + +-- Test plain (unlocked case). +\set query ':P SELECT * FROM yb_locks_t WHERE k=5;' +\i :iter_P2 + +-- Test single-RPC select+lock (no LockRows node). +\set query ':P SELECT * FROM yb_locks_t WHERE k=5 FOR UPDATE;' +\i :iter_P2 + +-- Test other types of locking. +\set query ':P SELECT * FROM yb_locks_t WHERE k=5 FOR SHARE;' +\i :iter_P2 + +\set query ':P SELECT * FROM yb_locks_t WHERE k=5 FOR NO KEY UPDATE;' +\i :iter_P2 + +\set query ':P SELECT * FROM yb_locks_t WHERE k=5 FOR KEY SHARE;' +\i :iter_P2 + +-- Test LockRows node (more RPCs), and scan is unlocked. +\set query ':P SELECT * FROM yb_locks_t FOR UPDATE;' +\i :iter_P2 + +-- Test with multi-column primary key. +\set query ':P SELECT * FROM yb_locks_t2 WHERE k1=1 AND k2=2 AND k3=3 FOR UPDATE;' +\i :iter_P2 + +-- Test with partial column set for primary key (should use LockRows). +\set query ':P SELECT * FROM yb_locks_t2 WHERE k1=1 AND k2=2 FOR UPDATE;' +\i :iter_P2 + +-- Test LockRows node is used for join. +\set query ':P SELECT * FROM yb_locks_t2, yb_locks_t WHERE yb_locks_t2.k1 = yb_locks_t.k FOR UPDATE;' +\i :iter_P2 + +-- Test LockRows node is used with ASC table when YB Sequential Scan is used. +\set Q1 '/*+ SeqScan(yb_locks_tasc) */' +\set query ':P :Q1 SELECT * FROM yb_locks_tasc WHERE k=1 FOR UPDATE;' +\i :iter_P2 + +-- In isolation level SERIALIZABLE, all locks are done during scans. +BEGIN ISOLATION LEVEL SERIALIZABLE; + +-- Test same locking as for REPEATABLE READ (default isolation). +\set query ':P SELECT * FROM yb_locks_t WHERE k=5 FOR UPDATE;' +\i :iter_P2 + +-- Test no LockRows node for sequential scan. +\set query ':P SELECT * FROM yb_locks_t FOR UPDATE;' +\i :iter_P2 + +-- Test no LockRows node for join. +\set query ':P SELECT * FROM yb_locks_t2, yb_locks_t WHERE yb_locks_t2.k1 = yb_locks_t.k FOR UPDATE;' +\i :iter_P2 + +-- Test locking, and no LockRows node, when using an ASC table and YB Sequential Scan. +-- (No WHERE clause.) +\set Q1 '/*+ SeqScan(yb_locks_tasc) */' +\set query ':P :Q1 SELECT * FROM yb_locks_tasc FOR UPDATE;' +\i :iter_P2 + +-- For an ASC table, should lock inline, with no LockRows node. +\set query ':P SELECT * FROM yb_locks_tasc ORDER BY k FOR UPDATE;' +\i :iter_P2 + +COMMIT; + +-- Test with single-RPC select+lock turned off. +SET yb_lock_pk_single_rpc TO OFF; + +\set query ':P SELECT * FROM yb_locks_t WHERE k=5 FOR UPDATE;' +\i :iter_P2 + +-- Test that with the yb_lock_pk_single_rpc off, SERIALIZABLE still locks during the scan +-- (no LockRows). +BEGIN ISOLATION LEVEL SERIALIZABLE; +\set query ':P SELECT * FROM yb_locks_t WHERE k=5 FOR UPDATE;' +\i :iter_P2 +COMMIT; + +SET yb_lock_pk_single_rpc TO ON; + +CREATE INDEX ON yb_locks_t2 (v); + +-- Test with an index. We use a LockRows node for an index. +\set query ':P SELECT * FROM yb_locks_t2 WHERE v=4 FOR UPDATE;' +\i :iter_P2 + +-- Test only the indexed column. +\set query ':P SELECT v FROM yb_locks_t2 WHERE v=4 FOR UPDATE;' +\i :iter_P2 + +-- Isolation level SERIALIZABLE still locks with the scan though (no LockRows). +BEGIN ISOLATION LEVEL SERIALIZABLE; + +\set query ':P SELECT * FROM yb_locks_t2 WHERE v=4 FOR UPDATE;' +\i :iter_P2 + +COMMIT; + +-- Test partitions. +CREATE TABLE yb_locks_partition (a char PRIMARY KEY) PARTITION BY LIST (a); +CREATE TABLE yb_locks_partition_default PARTITION OF yb_locks_partition DEFAULT; +CREATE TABLE yb_locks_partition_a PARTITION OF yb_locks_partition FOR VALUES IN ('a'); + +EXPLAIN (COSTS OFF) SELECT * FROM yb_locks_partition WHERE a = 'a' FOR UPDATE; +EXPLAIN (COSTS OFF) SELECT * FROM yb_locks_partition WHERE a = 'b' FOR UPDATE; + +BEGIN ISOLATION LEVEL SERIALIZABLE; +EXPLAIN (COSTS OFF) SELECT * FROM yb_locks_partition WHERE a = 'a' FOR UPDATE; +COMMIT; + +-- Test JSON. +EXPLAIN (COSTS OFF, FORMAT JSON) +SELECT * FROM yb_locks_t WHERE k=5 FOR UPDATE; + +-- Test that prepared statements made in isolation level RR with a LockRows node do not +-- crash when executed in isolation level SERIALIZABLE. +SET yb_lock_pk_single_rpc TO OFF; +-- Store prepared plans right away. +SET yb_test_planner_custom_plan_threshold to 1; +PREPARE yb_locks_plan_rr (int) AS SELECT * FROM yb_locks_t WHERE k=$1 FOR UPDATE; +\set query 'EXECUTE yb_locks_plan_rr(1);' +:query +-- The $1 in the EXPLAIN output tells you it's a stored plan. +\set query ':explain EXECUTE yb_locks_plan_rr(1);' +:query + +BEGIN ISOLATION LEVEL SERIALIZABLE; +-- The LockRows node has a "no-op" annotation. +\set query ':P EXECUTE yb_locks_plan_rr(1);' +\i :iter_P2 +-- In JSON mode, the LockRows node has an "Executes" field set to false. +EXPLAIN (COSTS OFF, FORMAT JSON) +EXECUTE yb_locks_plan_rr(1); +COMMIT; + +-- Test that prepared statements made in isolation level SERIALIZABLE, but for a PK, are +-- able to lock PK when run in RR and RC. +SET yb_lock_pk_single_rpc TO ON; +BEGIN ISOLATION LEVEL SERIALIZABLE; +PREPARE yb_locks_plan_ser (int) AS SELECT * FROM yb_locks_t WHERE k=$1 FOR UPDATE; +\set query ':P EXECUTE yb_locks_plan_ser(1);' +\i :iter_P2 +COMMIT; + +\set query ':P EXECUTE yb_locks_plan_ser(1);' +\i :iter_P2 + +-- Test that prepared statements made in isolation level SERIALIZABLE, for a non-PK, have +-- a LockRows node that functions in RR and RC. +BEGIN ISOLATION LEVEL SERIALIZABLE; +PREPARE yb_locks_plan_ser_all (int) AS SELECT * FROM yb_locks_t FOR UPDATE; +\set query ':P EXECUTE yb_locks_plan_ser_all(1);' +\i :iter_P2 +COMMIT; +\set query ':P EXECUTE yb_locks_plan_ser_all(1);' +\i :iter_P2 + +-- Reset +SET yb_lock_pk_single_rpc TO DEFAULT; +SET yb_test_planner_custom_plan_threshold TO DEFAULT; +DROP TABLE yb_locks_t, yb_locks_t2, yb_locks_tasc, yb_locks_partition; diff --git a/src/test/regress/sql/yb.orig.extensions.sql b/src/test/regress/sql/yb.orig.extensions.sql new file mode 100644 index 0000000000000..105e23f665e7d --- /dev/null +++ b/src/test/regress/sql/yb.orig.extensions.sql @@ -0,0 +1,160 @@ +SET client_min_messages = warning; +DROP DATABASE if exists test_yb_extensions; +CREATE DATABASE test_yb_extensions; +\c test_yb_extensions + +-- Testing pgcrypto. +create extension pgcrypto; + +select digest('xyz', 'sha1'); + +-- Using fixed salt to make test repeatable. +select crypt('new password', '$1$7kF93Vc4'); + +-- Using count to make test repeatable. +select count(gen_random_uuid()); + +-- Testing fuzzystrmatch. +create extension fuzzystrmatch schema public; + +select public.levenshtein('YugaByte', 'yugabyte'); + +select metaphone('yugabyte', 8); + +-- Clean up. +drop extension pgcrypto; + +-- Expect failure since function should be removed. +select digest('xyz', 'sha1'); + +drop extension fuzzystrmatch; + +-- Expect failure since function should be removed. +select public.levenshtein('YugaByte', 'yugabyte'); + +-- Testing pg_stat_statements; +select pg_stat_statements_reset(); +select pg_get_userbyid(userid),datname,query,calls,rows,shared_blks_hit,shared_blks_read,shared_blks_dirtied,shared_blks_written,local_blks_hit,local_blks_read,local_blks_dirtied,local_blks_written,temp_blks_read,temp_blks_written,blk_read_time + from pg_stat_statements join pg_database on dbid = oid order by query; + +create table test(a int, b float); +insert into test(a,b) values (5,10); +select pg_get_userbyid(userid),datname,query,calls,rows,shared_blks_hit,shared_blks_read,shared_blks_dirtied,shared_blks_written,local_blks_hit,local_blks_read,local_blks_dirtied,local_blks_written,temp_blks_read,temp_blks_written,blk_read_time + from pg_stat_statements join pg_database on dbid = oid order by query; +insert into test(a,b) values (15,20); +select pg_get_userbyid(userid),datname,query,calls,rows,shared_blks_hit,shared_blks_read,shared_blks_dirtied,shared_blks_written,local_blks_hit,local_blks_read,local_blks_dirtied,local_blks_written,temp_blks_read,temp_blks_written,blk_read_time + from pg_stat_statements join pg_database on dbid = oid order by query; +-- SeqScan forces YbSeqScan node with NodeTag near the end of the list in nodes.h +explain (analyze, costs off, summary off, timing off) /*+SeqScan(test)*/ select a, b from test; +-- GHI 14498: different queryid may indicate NodeTag added to the middle of the list +-- Please make sure it is appended +select queryid, query from pg_stat_statements where query like 'explain %'; +drop table test; + +-- Testing uuid-ossp +create extension "uuid-ossp"; + +select uuid_generate_v5('00000000-0000-0000-0000-000000000000', 'yugabyte'); + +-- generated values are random, so to ensure deterministic output ignore the actual values. +select uuid_generate_v1() != uuid_generate_v4(); + +-- Testing CVE-2023-39417 +CREATE SCHEMA "has space"; +CREATE SCHEMA has$dollar; + +CREATE EXTENSION yb_test_extension SCHEMA has$dollar; +CREATE EXTENSION yb_test_extension SCHEMA "has space"; + +\c yugabyte + +DROP DATABASE test_yb_extensions WITH (FORCE); + +-- +-- GH-26857 - Validate that the yb_extension role has the necessary privileges +-- to create/alter/delete extensions. +-- +CREATE USER non_db_admin_user INHERIT CREATEROLE CREATEDB BYPASSRLS LOGIN; +GRANT pg_read_all_stats, pg_signal_backend, yb_extension, yb_fdw, yb_db_admin TO non_db_admin_user; +GRANT CREATE ON SCHEMA public to non_db_admin_user WITH GRANT OPTION; + +-- Set up objects required to alter the extension. +CREATE SCHEMA test_schema; +GRANT CREATE ON SCHEMA test_schema to non_db_admin_user WITH GRANT OPTION; + +CREATE FUNCTION test_func (a INT) RETURNS INT +LANGUAGE PLPGSQL AS $$ +BEGIN + RETURN a; +END; +$$; + +ALTER FUNCTION test_func (a INT) OWNER TO non_db_admin_user; +CREATE ACCESS METHOD dummy_am_yb_user TYPE INDEX HANDLER bthandler; + +\c yugabyte non_db_admin_user +-- A non-admin user is not allowed to create/drop access methods unless it is a +-- part of an extension script. Postgres does not have a command to alter an +-- access method. +CREATE ACCESS METHOD dummy_am_non_db_admin_user TYPE INDEX HANDLER bthandler; +DROP ACCESS METHOD dummy_am_non_db_admin_user; + +CREATE EXTENSION vector; +ALTER EXTENSION vector SET SCHEMA test_schema; +-- A non-admin user is not allowed to create/drop access methods as part of +-- altering an extension. +ALTER EXTENSION vector ADD ACCESS METHOD dummy_am_yb_user; +ALTER EXTENSION vector ADD ACCESS METHOD am_not_found; +ALTER EXTENSION vector DROP ACCESS METHOD dummy_am_yb_user; +ALTER EXTENSION vector ADD FUNCTION test_func(INT); +ALTER EXTENSION vector DROP FUNCTION func_not_found(TEXT); +ALTER EXTENSION vector DROP FUNCTION test_func(INT); + +-- Test [NO] DEPENDS ON EXTENSION +CREATE OR REPLACE FUNCTION get_extension_deps(mask text) +RETURNS TABLE (procedure_name regprocedure, + extension_name name, + dependency_type "char") LANGUAGE SQL AS +$$ + SELECT p.oid::regprocedure, + e.extname, + d.deptype + FROM pg_proc p + JOIN pg_depend d ON d.objid = p.oid + JOIN pg_extension e ON d.refobjid = e.oid + WHERE p.proname ~ mask; +$$; + +CREATE FUNCTION cp_testfunc1(a int) RETURNS int LANGUAGE SQL AS +$$ SELECT a $$; + +CREATE TABLE cp_test (a int, b text); + +CREATE PROCEDURE ptest1(x text) LANGUAGE SQL AS +$$ INSERT INTO cp_test VALUES (1, x); $$; + +\df cp_testfunc1 +\df ptest1 +SELECT * FROM get_extension_deps('test'); + +ALTER FUNCTION cp_testfunc1 DEPENDS ON EXTENSION vector; +ALTER ROUTINE cp_testfunc1 DEPENDS ON EXTENSION vector; +SELECT * FROM get_extension_deps('test'); +ALTER PROCEDURE ptest1 DEPENDS ON EXTENSION vector; +ALTER ROUTINE ptest1 DEPENDS ON EXTENSION vector; +SELECT * FROM get_extension_deps('test'); + +ALTER FUNCTION cp_testfunc1 NO DEPENDS ON EXTENSION vector; +ALTER ROUTINE cp_testfunc1 NO DEPENDS ON EXTENSION vector; +SELECT * FROM get_extension_deps('test'); +ALTER PROCEDURE ptest1 NO DEPENDS ON EXTENSION vector; +ALTER ROUTINE ptest1 NO DEPENDS ON EXTENSION vector; +SELECT * FROM get_extension_deps('test'); + +DROP ROUTINE cp_testfunc1(int); +DROP PROCEDURE ptest1; +DROP TABLE cp_test; +DROP FUNCTION IF EXISTS get_extension_deps(text); + +DROP EXTENSION vector CASCADE; +\c yugabyte diff --git a/src/test/regress/sql/yb.orig.feature_alter_rename.sql b/src/test/regress/sql/yb.orig.feature_alter_rename.sql new file mode 100644 index 0000000000000..c766d3a7757fe --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_alter_rename.sql @@ -0,0 +1,39 @@ + +-- +-- ALTER RENAME +-- rename column, table, sequence, database +-- + +create database test_rename; +create database test_rename1; +\c test_rename + +create table foo(a int primary key, b int); +insert into foo (a, b) values (1, 2); +\d +\d foo + +alter table foo rename column b to c; +select a, b from foo; -- fail +select a, c from foo; +insert into foo (a, b) values (2, 3); -- fail +insert into foo (a, c) values (3, 4); +\d foo + +alter table foo rename to bar; +select * from foo; -- fail +select * from bar; +\d + +create sequence test_seq; +\d test_seq +alter sequence test_seq rename to test_seq_renamed; +select nextval('test_seq'); -- fail +select nextval('test_seq_renamed'); +\d test_seq +\d test_seq_renamed + +\c test_rename1; +alter database test_rename rename to test_rename2; +alter database test_rename2 rename to postgres; -- fail +\l diff --git a/src/test/regress/sql/yb.orig.feature_alter_table.sql b/src/test/regress/sql/yb.orig.feature_alter_table.sql new file mode 100644 index 0000000000000..7a704d9af5250 --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_alter_table.sql @@ -0,0 +1,526 @@ +-- +-- ALTER_TABLE +-- add, rename, drop, alter type of attribute +-- + +CREATE TABLE tmp (initial int4); + +ALTER TABLE tmp1 ADD COLUMN xmin integer; -- fails + +ALTER TABLE tmp ADD COLUMN b name; + +ALTER TABLE tmp ADD COLUMN c text; + +ALTER TABLE tmp ADD COLUMN d float8; + +ALTER TABLE tmp ADD COLUMN e float4; + +ALTER TABLE tmp ADD COLUMN f int2; + +ALTER TABLE tmp ADD COLUMN i char; + +ALTER TABLE tmp ADD COLUMN k int4; + +ALTER TABLE tmp ADD COLUMN m xid; + +ALTER TABLE tmp ADD COLUMN n oidvector; + +ALTER TABLE tmp ADD COLUMN v timestamp; + +ALTER TABLE tmp ADD COLUMN y float4[]; + +ALTER TABLE tmp ADD COLUMN z int2[]; + +INSERT INTO tmp (b, c, d, e, f, i, k, m, n, v, y, z) + VALUES ('name', 'text', 4.1, 4.1, 2, 'c', 314159, '512', + '1 2 3 4 5 6 7 8', 'epoch', '{1.0,2.0,3.0,4.0}', '{1,2,3,4}'); + +SELECT * FROM tmp; + +DROP TABLE tmp; + +-- alter table / drop column tests +-- try altering system catalogs, should fail +alter table pg_class drop column relname; + +-- try altering non-existent table, should fail +alter table nosuchtable drop column bar; + +-- test dropping columns +create table atacc1 (a int4 not null, b int4, c int4 not null, d int4); +insert into atacc1 values (1, 2, 3, 4); +alter table atacc1 drop a; +alter table atacc1 drop a; + +-- SELECTs +select * from atacc1; +select * from atacc1 order by a; +select * from atacc1 order by "........pg.dropped.1........"; +select * from atacc1 group by a; +select * from atacc1 group by "........pg.dropped.1........"; +select atacc1.* from atacc1; +select a from atacc1; +select atacc1.a from atacc1; +select b,c,d from atacc1; +select a,b,c,d from atacc1; +select * from atacc1 where a = 1; +select "........pg.dropped.1........" from atacc1; +select atacc1."........pg.dropped.1........" from atacc1; +select "........pg.dropped.1........",b,c,d from atacc1; +select * from atacc1 where "........pg.dropped.1........" = 1; + +-- UPDATEs +update atacc1 set a = 3; +update atacc1 set b = 2 where a = 3; +update atacc1 set "........pg.dropped.1........" = 3; +update atacc1 set b = 2 where "........pg.dropped.1........" = 3; + +-- INSERTs +insert into atacc1 values (10, 11, 12, 13); +insert into atacc1 values (default, 11, 12, 13); +insert into atacc1 values (11, 12, 13); +insert into atacc1 (a) values (10); +insert into atacc1 (a) values (default); +insert into atacc1 (a,b,c,d) values (10,11,12,13); +insert into atacc1 (a,b,c,d) values (default,11,12,13); +insert into atacc1 (b,c,d) values (11,12,13); +insert into atacc1 ("........pg.dropped.1........") values (10); +insert into atacc1 ("........pg.dropped.1........") values (default); +insert into atacc1 ("........pg.dropped.1........",b,c,d) values (10,11,12,13); +insert into atacc1 ("........pg.dropped.1........",b,c,d) values (default,11,12,13); + +-- DELETEs +delete from atacc1 where a = 3; +delete from atacc1 where "........pg.dropped.1........" = 3; +delete from atacc1; + +-- try dropping a non-existent column, should fail +alter table atacc1 drop bar; + +-- try dropping the oid column, should succeed +alter table atacc1 drop oid; + +-- try dropping the xmin column, should fail +alter table atacc1 drop xmin; + +-- try creating a view and altering that, should fail +create view myview as select * from atacc1; +select * from myview; +alter table myview drop d; +drop view myview; + +-- test some commands to make sure they fail on the dropped column +create index "testing_idx" on atacc1(a); +create index "testing_idx" on atacc1("........pg.dropped.1........"); +alter table atacc1 rename a to z; +alter table atacc1 add constraint checka check (a >= 0); +alter table if exists atacc1 add constraint checka check (a >= 0); + +-- test create as and select into +insert into atacc1 values (21, 22, 23); +create table test1 as select * from atacc1; +select * from test1; +drop table test1; +select * into test2 from atacc1; +select * from test2; +drop table test2; +select * into unlogged table test3 from atacc1; +select * from test3; +drop table test3; + +-- test constraints +alter table atacc1 add constraint checkb check (b < 0); -- should fail +alter table if exists atacc1 add constraint checkb check (b < 0); -- should fail +alter table atacc1 add constraint checkb check (b > 0); +alter table atacc1 add constraint checkb2 check (b > 10); +alter table atacc1 add constraint checkb3 check (b > 10); +insert into atacc1 values (5, 5, 5); -- should fail +alter table atacc1 drop constraint checkb2; +alter table if exists atacc1 add constraint checkb2 check (b > 10); +insert into atacc1 values (5, 5, 5); -- should fail +alter table atacc1 drop constraint checkb2; +insert into atacc1 values (5, 5, 5); +alter table atacc1 drop constraint checkb; +alter table atacc1 drop constraint if exists checkb2; +alter table atacc1 drop constraint checkb2; +alter table atacc1 drop constraint if exists checkb3; +delete from atacc1 where b = 5; + +-- test rename +alter table atacc1 rename b to d; -- should fail: d already exists +alter table atacc1 rename b to f; +alter table atacc1 rename column f to e; + +alter table if exists doesnt_exist_tab rename b to f; +alter table if exists doesnt_exist_tab rename column f to e; + +select * from atacc1; + +-- try dropping all columns +alter table atacc1 drop c; +alter table atacc1 drop d; +alter table atacc1 drop b; +select * from atacc1; + +drop table atacc1; + +-- test dropping primary key constraints +CREATE TABLE with_simple_pk_i (i int PRIMARY KEY); +ALTER TABLE with_simple_pk_i DROP CONSTRAINT with_simple_pk_i_pkey; +INSERT INTO with_simple_pk_i VALUES (1); +INSERT INTO with_simple_pk_i VALUES (1); +DROP TABLE with_simple_pk_i; +-- +CREATE TABLE with_simple_pk_ij (i int, j int, PRIMARY KEY(i, j)); +ALTER TABLE with_simple_pk_ij DROP CONSTRAINT with_simple_pk_ij_pkey; +INSERT INTO with_simple_pk_ij VALUES (1, 1); +INSERT INTO with_simple_pk_ij VALUES (1, 1); +DROP TABLE with_simple_pk_ij; +-- +CREATE TABLE with_named_pk_i (i int CONSTRAINT named_pk_i PRIMARY KEY); +ALTER TABLE with_named_pk_i DROP CONSTRAINT named_pk_i; +INSERT INTO with_named_pk_i VALUES (1); +INSERT INTO with_named_pk_i VALUES (1); +DROP TABLE with_named_pk_i; +-- +CREATE TABLE with_named_pk_ij (i int, j int, CONSTRAINT named_pk_ij PRIMARY KEY (i HASH, j ASC)); +ALTER TABLE with_named_pk_ij DROP CONSTRAINT named_pk_ij; +INSERT INTO with_named_pk_ij VALUES (1, 1); +INSERT INTO with_named_pk_ij VALUES (1, 1); +DROP TABLE with_named_pk_ij; + +-- +-- rename +-- +CREATE TABLE tmp (regtable int); + +ALTER TABLE tmp RENAME TO tmp_new; + +SELECT * FROM tmp_new; +SELECT * FROM tmp; -- should fail + +DROP TABLE tmp_new; + +-- alter table / alter column [set/drop] not null tests +-- try altering system catalogs, should fail +alter table pg_class alter column relname drop not null; +alter table pg_class alter relname set not null; + +-- try altering non-existent table, should fail +alter table non_existent alter column bar set not null; +alter table non_existent alter column bar drop not null; + +-- try altering non-existent table with IF EXISTS clause, should pass with a notice message +alter table if exists non_existent alter column bar set not null; +alter table if exists non_existent alter column bar drop not null; +alter table if exists non_existent + add constraint some_constraint_name foreign key (k) references atacc1; + +-- test setting columns to null and not null and vice versa +-- test checking for null values and primary key +create table atacc1tmp (test int not null PRIMARY KEY); +alter table atacc1tmp alter column test drop not null; +drop table atacc1tmp; + +create table atacc1 (test int not null); +alter table atacc1 alter column test drop not null; +insert into atacc1 values (null); +alter table atacc1 alter test set not null; +delete from atacc1; +alter table atacc1 alter test set not null; + +-- try altering a non-existent column, should fail +alter table atacc1 alter bar set not null; +alter table atacc1 alter bar drop not null; + +-- -- try altering the oid column, should fail +-- alter table atacc1 alter oid set not null; +-- alter table atacc1 alter oid drop not null; + +-- try creating a view and altering that, should fail +create view myview as select * from atacc1; +alter table myview alter column test drop not null; +alter table myview alter column test set not null; +drop view myview; + +drop table atacc1; + +-- test setting and removing default values +create table def_test ( + c1 int4 default 5, + c2 text default 'initial_default' +); +insert into def_test default values; +alter table def_test alter column c1 drop default; +insert into def_test default values; +alter table def_test alter column c2 drop default; +insert into def_test default values; +alter table def_test alter column c1 set default 10; +alter table def_test alter column c2 set default 'new_default'; +insert into def_test default values; +select * from def_test order by c1, c2; + +-- set defaults to an incorrect type: this should fail +alter table def_test alter column c1 set default 'wrong_datatype'; +alter table def_test alter column c2 set default 20; + +-- set defaults on a non-existent column: this should fail +alter table def_test alter column c3 set default 30; + +-- set defaults on views: we need to create a view, add a rule +-- to allow insertions into it, and then alter the view to add +-- a default +create view def_view_test as select * from def_test; +create rule def_view_test_ins as + on insert to def_view_test + do instead insert into def_test select new.*; +insert into def_view_test default values; +alter table def_view_test alter column c1 set default 45; +insert into def_view_test default values; +alter table def_view_test alter column c2 set default 'view_default'; +insert into def_view_test default values; +select * from def_view_test ORDER BY c1 NULLS FIRST, c2 NULLS FIRST; + +drop rule def_view_test_ins on def_view_test; +drop view def_view_test; +drop table def_test; + +-- test CREATE or REPLACE VIEW +create or replace view v as select 17 as c1; +select * from v; +create or replace view v as select 42 as c1; +select * from v; +create or replace view v as select 11 as + c1, 12 AS c2, 13 AS c3; -- testing replace/alter view by adding columns +select * from v; +drop view v; +-- test ADD COLUMN IF NOT EXISTS +CREATE TABLE test_add_column(c1 integer); +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN c2 integer; +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN c2 integer; -- fail because c2 already exists +ALTER TABLE ONLY test_add_column + ADD COLUMN c2 integer; -- fail because c2 already exists +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN IF NOT EXISTS c2 integer; -- skipping because c2 already exists +ALTER TABLE ONLY test_add_column + ADD COLUMN IF NOT EXISTS c2 integer; -- skipping because c2 already exists +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN c2 integer, -- fail because c2 already exists + ADD COLUMN c3 integer; +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists + ADD COLUMN c3 integer; -- fail because c3 already exists +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists + ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists + ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists + ADD COLUMN c4 integer; +\d test_add_column +DROP TABLE test_add_column; + +-- test Add/Set/Drop Identity +CREATE TABLE test_identity( + c1 integer NOT NULL, + c2 integer NOT NULL +); + +-- test ADD GENERATED ALWAYS +ALTER TABLE test_identity ALTER COLUMN c1 ADD GENERATED ALWAYS AS IDENTITY; +\d test_identity; +INSERT INTO test_identity (c1, c2) VALUES (0, 0); +INSERT INTO test_identity (c2) VALUES (1); +INSERT INTO test_identity (c2) VALUES (2); +-- test DROP IDENTITY +ALTER TABLE test_identity ALTER COLUMN c1 DROP IDENTITY; +\d test_identity; +ALTER TABLE test_identity ALTER COLUMN c1 DROP IDENTITY; +\d test_identity; + +-- test ADD GENERATED BY DEFAULT +ALTER TABLE test_identity ALTER COLUMN c1 ADD GENERATED BY DEFAULT AS IDENTITY; +\d test_identity; +INSERT INTO test_identity (c1, c2) VALUES (1000, 3); +INSERT INTO test_identity (c2) VALUES (4); +INSERT INTO test_identity (c2) VALUES (5); +-- test DROP IDENTITY IF EXISTS +ALTER TABLE test_identity ALTER COLUMN c1 DROP IDENTITY IF EXISTS; +\d test_identity; +ALTER TABLE test_identity ALTER COLUMN c1 DROP IDENTITY IF EXISTS; +\d test_identity; + +-- test SET GENERATED +ALTER TABLE test_identity ALTER COLUMN c1 ADD GENERATED BY DEFAULT AS IDENTITY (INCREMENT 10 MINVALUE 10); +\d test_identity; +ALTER TABLE test_identity ALTER c1 SET GENERATED ALWAYS; +\d test_identity; +ALTER TABLE test_identity ALTER c1 SET GENERATED BY DEFAULT; +\d test_identity; +INSERT INTO test_identity (c2) VALUES (6); +INSERT INTO test_identity (c2) VALUES (7); +ALTER TABLE test_identity ALTER c1 SET INCREMENT 20; +INSERT INTO test_identity (c2) VALUES (8); +INSERT INTO test_identity (c2) VALUES (9); +ALTER TABLE test_identity ALTER c1 RESTART; +INSERT INTO test_identity (c2) VALUES (10); +INSERT INTO test_identity (c2) VALUES (11); +ALTER TABLE test_identity ALTER c1 SET INCREMENT 50 RESTART WITH 100; +INSERT INTO test_identity (c2) VALUES (12); +INSERT INTO test_identity (c2) VALUES (13); +SELECT * FROM test_identity ORDER BY c2; + +DROP TABLE test_identity; +-- Test that updating table after dropping a column does not result in spurious error #1969 +create table test_update_dropped(a int, b int); +insert into test_update_dropped(a, b) values(1, 1); +update test_update_dropped set a = 2 where a = 1; +alter table test_update_dropped drop column b; +update test_update_dropped set a = 3 where a = 2; +\d test_update_dropped; +select * from test_update_dropped; +DROP TABLE test_update_dropped; + +-- Test that deleting table with a returning clause after dropping a column +-- does not result in spurious error #2938. +create table test_delete_dropped(a int, b int); +insert into test_delete_dropped(a, b) values(1, 1), (2, 2); +alter table test_delete_dropped drop column b; +delete from test_delete_dropped where a = 1 returning *; +\d test_delete_dropped; +select * from test_delete_dropped; +DROP TABLE test_delete_dropped; + +-- Test ALTER TABLE _ ALTER COLUMN _ TYPE _ +create table test_alter_column_type(a varchar(1)); +alter table test_alter_column_type add column b name; +alter table test_alter_column_type add column c text; +alter table test_alter_column_type add column d char(1); +alter table test_alter_column_type add column e varbit(1); +alter table test_alter_column_type add column f int; +alter table test_alter_column_type add column g varchar; +alter table test_alter_column_type add column h varbit; +insert into test_alter_column_type values ('a', 'b', 'c', 'd', B'1', 1, 'g', B'1'); +select * from test_alter_column_type; +\d test_alter_column_type + +alter table test_alter_column_type alter column a type varchar(1); +alter table test_alter_column_type alter column a type varchar(5); +alter table test_alter_column_type alter column a type varchar(1); +alter table test_alter_column_type alter column a type char(10); + +alter table test_alter_column_type alter column b type name; +alter table test_alter_column_type alter column b type varchar(100); + +alter table test_alter_column_type alter column c type text; +alter table test_alter_column_type alter column c type varchar(100); + +alter table test_alter_column_type alter column d type char(1); +alter table test_alter_column_type alter column d type varchar(100); +alter table test_alter_column_type alter column d type char(100); + +alter table test_alter_column_type alter column e type varbit(1); +alter table test_alter_column_type alter column e type varbit(5); +alter table test_alter_column_type alter column e type varbit(1); + +alter table test_alter_column_type alter column f type varchar(100); +alter table test_alter_column_type alter column f type varbit(100); --fails +alter table test_alter_column_type alter column f type int; --fails + +alter table test_alter_column_type alter column g type varchar; +alter table test_alter_column_type alter column g type varchar(5); +alter table test_alter_column_type alter column g type text; + +alter table test_alter_column_type alter column h type varbit; +alter table test_alter_column_type alter column h type varbit(5); + +insert into test_alter_column_type values ('abcde', '-', '-', '-', B'10101', 0, '-', B'0'); --fails +insert into test_alter_column_type values ('abcde', '-', '-', '-', B'1', 0, '-', B'0'); + +select * from test_alter_column_type order by a; +\d test_alter_column_type + +alter table test_alter_column_type alter column a type varchar; +alter table test_alter_column_type alter column e type varbit; + +\d test_alter_column_type + +DROP TABLE test_alter_column_type; + + +-- Test ALTER TABLE _ ALTER COLUMN _ TYPE _ with an index (Issue #6113) +create table test_alter_column_type_with_index(i int primary key, a varchar(50)); + +alter table test_alter_column_type_with_index alter column a type varchar(100); +insert into test_alter_column_type_with_index values (1, 'abcde'), (2, 'fghij'); + +create index on test_alter_column_type_with_index(a); + +alter table test_alter_column_type_with_index alter column a type varchar(100); +alter table test_alter_column_type_with_index alter column a type varchar(200); + +insert into test_alter_column_type_with_index values (3, 'klmno'), (4, 'pqrst'); +select * from test_alter_column_type_with_index order by a; +\d test_alter_column_type_with_index + +DROP TABLE test_alter_column_type_with_index; +-- Test #5543 by exercising cases where ALTER command fails due to constraint +-- check validation, causing rollback of ALTER operation on DocDB. +CREATE TABLE foobar (key text, value text); +INSERT INTO foobar VALUES ('key', 'value'); +ALTER TABLE foobar ADD COLUMN v2 text not null; -- fails due to not null constraint +ALTER TABLE foobar ADD COLUMN v2 text not null DEFAULT 'abc'; -- passes +DROP TABLE foobar; +-- +-- Check that attaching or detaching a partitioned partition correctly leads +-- to its partitions' constraint being updated to reflect the parent's +-- newly added/removed constraint +create table target_parted (a int, b int) partition by list (a); +create table attach_parted (a int, b int) partition by list (b); +create table attach_parted_part1 partition of attach_parted for values in (1); +-- insert a row directly into the leaf partition so that its partition +-- constraint is built and stored in the relcache +insert into attach_parted_part1 values (1, 1); +-- the following better invalidate the partition constraint of the leaf +-- partition too... +alter table target_parted attach partition attach_parted for values in (1); +-- ...such that the following insert fails +insert into attach_parted_part1 values (2, 1); +-- ...and doesn't when the partition is detached along with its own partition +alter table target_parted detach partition attach_parted; +insert into attach_parted_part1 values (2, 1); + +CREATE TABLE demo (i int); +INSERT INTO demo VALUES (1); +CREATE UNIQUE INDEX demoi ON demo(i); +ALTER TABLE demo ADD CONSTRAINT demoi UNIQUE USING INDEX demoi; +INSERT INTO demo VALUES (1); +ALTER TABLE demo DROP CONSTRAINT demoi; +INSERT INTO demo VALUES (1); +SELECT * FROM demo; + +-- Test dropping a primary key column with sequence generator +-- does not delete the associated sequence. +CREATE TABLE tbl_serial_primary_key (k serial PRIMARY KEY, v text); +ALTER TABLE tbl_serial_primary_key DROP COLUMN k; +INSERT INTO tbl_serial_primary_key(v) VALUES ('ABC'); +SELECT * FROM tbl_serial_primary_key; +DROP TABLE tbl_serial_primary_key; + +-- Test LOGGED / UNLOGGED, SET, RESET +CREATE UNLOGGED TABLE test_tbl (i int); -- ok, UNLOGGED is ignored +ALTER TABLE test_tbl SET UNLOGGED; -- ok, ignored +ALTER TABLE test_tbl SET LOGGED; -- ok + +ALTER TABLE test_tbl SET (fillfactor = 101); -- fails: out of limit +ALTER TABLE test_tbl SET (fillfactor = 100); -- ok +ALTER TABLE test_tbl RESET (fillfactor); -- ok diff --git a/src/test/regress/sql/yb.orig.feature_colocation.sql b/src/test/regress/sql/yb.orig.feature_colocation.sql new file mode 100644 index 0000000000000..33f0124d60e52 --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_colocation.sql @@ -0,0 +1,554 @@ +-- +-- Colocation +-- + +-- CREATE TABLE on non-colocated database + +CREATE TABLE tab_colo (a INT) WITH (colocation = true); +CREATE TABLE tab_noco (a INT) WITH (colocation = false); +DROP TABLE tab_noco; + +-- CREATE DATABASE colocated + +CREATE DATABASE colocation_test colocation = true; +\c colocation_test +CREATE TABLE e (id int PRIMARY KEY, first_name TEXT) WITH (colocation = true) SPLIT INTO 10 TABLETS; + +-- CREATE TABLE + +CREATE TABLE tab_nonkey (a INT); +\d tab_nonkey +CREATE TABLE tab_key (a INT PRIMARY KEY); +\d tab_key +CREATE TABLE tab_range (a INT, PRIMARY KEY (a ASC)); +CREATE TABLE tab_range_nonkey (a INT, b INT, PRIMARY KEY (a ASC)); +-- opt out of using colocated tablet +CREATE TABLE tab_nonkey_noco (a INT) WITH (colocation = false); +-- colocated tables with no primary keys should not be hash partitioned +CREATE TABLE split_table ( a integer, b text ) SPLIT INTO 4 TABLETS; +-- multi column primary key table +CREATE TABLE tab_range_range (a INT, b INT, PRIMARY KEY (a, b DESC)); +CREATE TABLE tab_range_colo (a INT, PRIMARY KEY (a ASC)) WITH (colocation = true); + +INSERT INTO tab_range (a) VALUES (0), (1), (2); +INSERT INTO tab_range (a, b) VALUES (0, '0'); -- fail +INSERT INTO tab_range_nonkey (a, b) VALUES (0, '0'), (1, '1'); +INSERT INTO tab_nonkey_noco (a) VALUES (0), (1), (2), (3); +INSERT INTO tab_range_range (a, b) VALUES (0, 0), (0, 1), (1, 0), (1, 1); +INSERT INTO tab_range_colo (a) VALUES (0), (1), (2), (3); + +SELECT * FROM tab_range; +SELECT * FROM tab_range WHERE a = 2; +SELECT * FROM tab_range WHERE n = '0'; -- fail +SELECT * FROM tab_range_nonkey; +SELECT * FROM tab_nonkey_noco ORDER BY a ASC; +SELECT * FROM tab_range_range; +SELECT * FROM tab_range_colo; + +BEGIN; +INSERT INTO tab_range_colo VALUES (4); +SELECT * FROM tab_range_colo; +ROLLBACK; +BEGIN; +INSERT INTO tab_range_colo VALUES (5); +COMMIT; +SELECT * FROM tab_range_colo; + +INSERT INTO tab_range_colo VALUES (6), (6); + +-- CREATE INDEX + +-- table with index +CREATE TABLE tab_range_nonkey2 (a INT, b INT, PRIMARY KEY (a ASC)); +CREATE INDEX idx_range ON tab_range_nonkey2 (a); +\d tab_range_nonkey2 +INSERT INTO tab_range_nonkey2 (a, b) VALUES (0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +EXPLAIN (COSTS OFF) SELECT * FROM tab_range_nonkey2 WHERE a = 1; +SELECT * FROM tab_range_nonkey2 WHERE a = 1; +/*+IndexScan(tab_range_nonkey2 idx_range)*/EXPLAIN (COSTS OFF) SELECT * FROM tab_range_nonkey2 WHERE a <= 3; +/*+IndexScan(tab_range_nonkey2 idx_range)*/SELECT * FROM tab_range_nonkey2 WHERE a <= 3; +UPDATE tab_range_nonkey2 SET b = b + 1 WHERE a > 3; +SELECT * FROM tab_range_nonkey2; +DELETE FROM tab_range_nonkey2 WHERE a > 3; +SELECT * FROM tab_range_nonkey2; + +-- colocated table with non-colocated index +CREATE TABLE tab_range_nonkey3 (a INT, b INT, PRIMARY KEY (a ASC)); +CREATE INDEX idx_range_colo ON tab_range_nonkey3 (a) WITH (colocation = true); + +-- colocated table with colocated index +CREATE TABLE tab_range_nonkey4 (a INT, b INT, PRIMARY KEY (a ASC)); +CREATE INDEX idx_range_noco ON tab_range_nonkey4 (a) WITH (colocation = false); + +-- non-colocated table with index +CREATE TABLE tab_range_nonkey_noco (a INT, b INT, PRIMARY KEY (a ASC)) WITH (colocation = false); +CREATE INDEX idx_range2 ON tab_range_nonkey_noco (a); +INSERT INTO tab_range_nonkey_noco (a, b) VALUES (0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +EXPLAIN (COSTS OFF) SELECT * FROM tab_range_nonkey_noco WHERE a = 1; +SELECT * FROM tab_range_nonkey_noco WHERE a = 1; +EXPLAIN (COSTS OFF) SELECT * FROM tab_range_nonkey_noco WHERE a <= 3; +SELECT * FROM tab_range_nonkey_noco WHERE a <= 3; +UPDATE tab_range_nonkey_noco SET b = b + 1 WHERE a > 3; +SELECT * FROM tab_range_nonkey_noco; +DELETE FROM tab_range_nonkey_noco WHERE a > 3; +SELECT * FROM tab_range_nonkey_noco; + +create table p_out(a int); +insert into p_out values (1), (2), (3), (4); +create index on p_out(a asc); + +create table p_in(a int, primary key(a asc)); +insert into p_in values (4); +/*+Set(yb_prefer_bnl false) IndexScan(p_out) NestLoop(p_out p_in) Leading((p_out p_in))*/ explain (analyze, costs off, summary off, timing off, dist) select * from p_out, p_in where p_out.a = p_in.a limit 1; +drop table p_out; +drop table p_in; + +-- more tables and indexes +CREATE TABLE tab_range_nonkey_noco2 (a INT, b INT, PRIMARY KEY (a ASC)) WITH (colocation = false); +CREATE INDEX idx_range3 ON tab_range_nonkey_noco2 (a); +INSERT INTO tab_range_nonkey_noco2 (a, b) VALUES (0, 0); +CREATE TABLE tab_range_nonkey_noco3 (a INT, b INT, PRIMARY KEY (a ASC)) WITH (colocation = false); +CREATE INDEX idx_range4 ON tab_range_nonkey_noco3 (a); +CREATE TABLE tab_range_nonkey5 (a INT, b INT, PRIMARY KEY (a ASC)); +CREATE INDEX idx_range5 ON tab_range_nonkey5 (a); +CREATE TABLE tbl (r1 INT, r2 INT, v1 INT, v2 INT, +PRIMARY KEY (r1, r2)); +CREATE INDEX idx_hash1 on tbl (r1 HASH); +CREATE INDEX idx_hash2 on tbl ((r1, r2) HASH); +CREATE INDEX idx_hash3 on tbl (r1 HASH, r2 ASC); +CREATE UNIQUE INDEX unique_idx_hash1 on tbl (r1 HASH); +CREATE UNIQUE INDEX unique_idx_hash2 on tbl ((r1, r2) HASH); +CREATE UNIQUE INDEX unique_idx_hash3 on tbl (r1 HASH, r2 ASC); +\d tbl +-- Make sure nothing bad happens to UNIQUE constraints after disabling HASH columns +-- for colocated indexes +CREATE TABLE tbl2 (r1 int PRIMARY KEY, r2 int, v1 int, v2 int, UNIQUE(v1)); +ALTER TABLE tbl2 ADD CONSTRAINT unique_v2_tbl2 UNIQUE(v2); +\d tbl2 + +DROP TABLE tbl, tbl2; + +-- colocated table with unique index +CREATE TABLE tab_nonkey2 (a INT) WITH (colocation = true); +CREATE UNIQUE INDEX idx_range6 ON tab_nonkey2 (a); + +\dt +\di + +-- TRUNCATE TABLE + +-- truncate colocated table with default index +TRUNCATE TABLE tab_range; +SELECT * FROM tab_range; +INSERT INTO tab_range VALUES (4); +SELECT * FROM tab_range; +INSERT INTO tab_range VALUES (1); +INSERT INTO tab_range VALUES (2), (5); +SELECT * FROM tab_range; +DELETE FROM tab_range WHERE a = 2; +TRUNCATE TABLE tab_range; +SELECT * FROM tab_range; +INSERT INTO tab_range VALUES (2); +SELECT * FROM tab_range; + +TRUNCATE TABLE tab_range; + +-- truncate non-colocated table without index +TRUNCATE TABLE tab_nonkey_noco; +SELECT * FROM tab_nonkey_noco; + +-- truncate colocated table with explicit index +TRUNCATE TABLE tab_range_nonkey2; +SELECT * FROM tab_range_nonkey2; + +-- truncate non-colocated table with explicit index +TRUNCATE TABLE tab_range_nonkey_noco2; +SELECT * FROM tab_range_nonkey_noco2; + +-- insert and truncate colocated table with explicit index +INSERT INTO tab_nonkey2 VALUES (1), (2), (3); +EXPLAIN (COSTS OFF) SELECT a FROM tab_nonkey2 ORDER BY a; +SELECT a FROM tab_nonkey2 ORDER BY a; +TRUNCATE TABLE tab_nonkey2; +SELECT a FROM tab_nonkey2 ORDER BY a; +INSERT INTO tab_nonkey2 VALUES (2), (4); +SELECT a FROM tab_nonkey2 ORDER BY a; + +\dt +\di + +-- ALTER TABLE +INSERT INTO tab_range (a) VALUES (0), (1), (2); +INSERT INTO tab_range_nonkey2 (a, b) VALUES (0, 0), (1, 1); + +SELECT * FROM tab_range; +SELECT * FROM tab_range_nonkey2; + +-- Alter colocated tables +ALTER TABLE tab_range ADD COLUMN x INT; +ALTER TABLE tab_range_nonkey2 DROP COLUMN b; + +SELECT * FROM tab_range; +SELECT * FROM tab_range_nonkey2; + +ALTER TABLE tab_range_nonkey2 RENAME TO tab_range_nonkey2_renamed; +SELECT * FROM tab_range_nonkey2_renamed; +SELECT * FROM tab_range_nonkey2; + +-- Alter colocated table ADD PRIMARY KEY +CREATE TABLE tbl_no_pk (k INT, v INT) WITH (colocation = true); +\d tbl_no_pk +INSERT INTO tbl_no_pk (k, v) VALUES (1, 1), (2, 2), (3, 3); +ALTER TABLE tbl_no_pk ADD PRIMARY KEY (k ASC); +\d tbl_no_pk +SELECT * FROM tbl_no_pk ORDER BY k; + +-- Alter colocated table "DROP PRIMARY KEY" +ALTER TABLE tbl_no_pk DROP CONSTRAINT tbl_no_pk_pkey; +\d tbl_no_pk +SELECT * FROM tbl_no_pk ORDER BY k; +DROP TABLE tbl_no_pk; + +-- DROP TABLE + +-- drop colocated table with default index +DROP TABLE tab_range; +SELECT * FROM tab_range; + +-- drop non-colocated table without index +DROP TABLE tab_nonkey_noco; +SELECT * FROM tab_nonkey_noco; + +--- drop colocated table with explicit index +DROP TABLE tab_range_nonkey2_renamed; +SELECT * FROM tab_range_nonkey2_renamed; + +-- drop non-colocated table with explicit index +DROP TABLE tab_range_nonkey_noco2; +SELECT * FROM tab_range_nonkey_noco2; + +-- DROP INDEX + +-- drop index on non-colocated table +DROP INDEX idx_range2; +EXPLAIN (COSTS OFF) SELECT * FROM tab_range_nonkey_noco WHERE a = 1; + +-- drop index on colocated table +DROP INDEX idx_range5; +EXPLAIN (COSTS OFF) SELECT * FROM tab_range_nonkey5 WHERE a = 1; + +\dt +\di + +-- Test colocated tables/indexes with SPLIT INTO/SPLIT AT +CREATE TABLE invalid_tbl_split_into (k INT) SPLIT INTO 10 TABLETS; +CREATE TABLE invalid_tbl_split_at (k INT) SPLIT AT VALUES ((100)); +CREATE TABLE test_tbl (k INT); +CREATE INDEX invalid_idx_split_into ON test_tbl (k) SPLIT INTO 10 TABLETS; +CREATE INDEX invalid_idx_split_at ON test_tbl (k) SPLIT AT VALUES ((100)); +DROP TABLE test_tbl; + +-- Test colocated partitioned table and partition tables +CREATE TABLE partitioned_table ( + k1 INT, + v1 INT, + v2 TEXT +) +PARTITION BY HASH (k1) +WITH (colocation_id='123456'); +SELECT * FROM yb_table_properties('partitioned_table'::regclass::oid); + +CREATE TABLE table_partition PARTITION OF partitioned_table +FOR VALUES WITH (modulus 2, remainder 0) +WITH (colocation_id='234567'); +SELECT * FROM yb_table_properties('table_partition'::regclass::oid); + +-- (new) Colocation test: Parent and child partitions are non-colocated +CREATE TABLE partitioned_table_2 ( + k1 INT, + v1 INT, + v2 TEXT, + PRIMARY KEY (k1 HASH) +) +PARTITION BY HASH (k1) +WITH (colocation=false); + +CREATE TABLE partitioned_table_2_0 PARTITION OF partitioned_table_2 +FOR VALUES WITH (modulus 2, remainder 0) WITH (colocation=false); + +CREATE TABLE partitioned_table_2_1 PARTITION OF partitioned_table_2 +FOR VALUES WITH (modulus 2, remainder 1) WITH (colocation=false); + +INSERT INTO partitioned_table_2 (SELECT * FROM generate_series(1,10)); + +SELECT num_tablets, num_hash_key_columns, is_colocated FROM yb_table_properties('partitioned_table_2'::regclass::oid); +SELECT num_tablets, num_hash_key_columns, is_colocated FROM yb_table_properties('partitioned_table_2_0'::regclass::oid); +SELECT num_tablets, num_hash_key_columns, is_colocated FROM yb_table_properties('partitioned_table_2_1'::regclass::oid); +SELECT relname, reloptions FROM pg_class WHERE relname LIKE '%partitioned_table_2%' ORDER BY relname; + +-- (new) Colocation test: Parent partitioned table is colocated +-- child partitions can be colocated or non-colocated +CREATE TABLE partitioned_table_3 ( + k1 INT, + v1 INT, + v2 TEXT +) +PARTITION BY RANGE (k1); + +-- non-colocated hash partitioned child partition +CREATE TABLE partitioned_table_3_0 PARTITION OF partitioned_table_3(k1, v1, v2, PRIMARY KEY(k1 HASH)) +FOR VALUES FROM (0) TO (10) WITH (colocation=false); + +-- colocated child partition using default db settings +CREATE TABLE partitioned_table_3_1 PARTITION OF partitioned_table_3 +FOR VALUES FROM (11) TO (20); + +-- colocated range partitioned child partition +CREATE TABLE partitioned_table_3_def PARTITION OF partitioned_table_3(k1, v1, v2, PRIMARY KEY(k1 ASC)) +DEFAULT WITH (colocation=true); + +INSERT INTO partitioned_table_3 (SELECT * FROM generate_series(1,10)); + +SELECT num_tablets, num_hash_key_columns, is_colocated FROM yb_table_properties('partitioned_table_3'::regclass::oid); +SELECT num_tablets, num_hash_key_columns, is_colocated FROM yb_table_properties('partitioned_table_3_0'::regclass::oid); +SELECT num_tablets, num_hash_key_columns, is_colocated FROM yb_table_properties('partitioned_table_3_1'::regclass::oid); +SELECT num_tablets, num_hash_key_columns, is_colocated FROM yb_table_properties('partitioned_table_3_def'::regclass::oid); +SELECT relname, reloptions FROM pg_class WHERE relname LIKE '%partitioned_table_3%' ORDER BY relname; + +-- drop database +\c yugabyte +DROP DATABASE colocation_test; + +-- Test syntax change as a result of Colocation GA change +-- Fail: only one of 'colocation' and 'colocated' options can be specified in CREATE DATABASE +CREATE DATABASE colocation_test colocated = true colocation = true; +-- Succeed with deprecated warning: create a colocated database using old syntax +CREATE DATABASE colocation_test colocated = true; +DROP DATABASE colocation_test; +-- Succeed: create a colocated database using new syntax +CREATE DATABASE colocation_test colocation = true; +\c colocation_test + +-- Fail: only one of 'colocation' and 'colocated' options can be specified in CREATE TABLE +CREATE TABLE tbl_colocated_colocation (k INT, v INT) +WITH (colocation = true, colocated = true); +-- Succeed with deprecated warning: create a colocated table using old syntax +CREATE TABLE tbl_colocated (k INT, v INT) WITH (colocated = true); +-- Succeed: create a colocated table using new syntax +CREATE TABLE tbl_colocation (k INT, v INT) WITH (colocation = true); +-- Check colocated table footer +\d tbl_colocation +-- Create and describe a table opt out of colocation +CREATE TABLE tbl_no_colocation (k INT, v INT) WITH (colocation = false); +\d tbl_no_colocation + +-- Drop database +\c yugabyte +DROP DATABASE colocation_test; + +-- Test Colocation GA special characteristics different from legacy colocated database +CREATE DATABASE colocation_test colocation = true; +\c colocation_test + +-- Lazily create the default implicit tablegroup +SELECT * FROM pg_yb_tablegroup; +CREATE TABLE tbl (k INT, v INT); +SELECT * FROM pg_yb_tablegroup; + +-- Check for dependency between colocated table and default tablegroup +SELECT * FROM pg_depend, pg_yb_tablegroup WHERE classid = 'pg_class'::regclass +AND objid = 'tbl'::regclass AND refclassid = 'pg_yb_tablegroup'::regclass +AND refobjid = pg_yb_tablegroup.oid AND grpname = 'default'; +DROP TABLEGROUP "default"; + +-- The default tablegroup cannot be dropped +DROP TABLEGROUP "default" CASCADE; +DROP TABLE tbl; +DROP TABLEGROUP "default"; + +-- Cannot set privileges of an implicit tablegroup +CREATE ROLE test_role; +GRANT CREATE ON TABLEGROUP "default" TO test_role; +REVOKE CREATE ON TABLEGROUP "default" FROM test_role; + +-- Any user can create tables/indexes in an implicit tablegroup +GRANT CREATE ON SCHEMA public TO test_role; +SET SESSION AUTHORIZATION test_role; +CREATE TABLE test_role_table (k INT PRIMARY KEY, v TEXT); +SELECT * FROM pg_tables WHERE tablename = 'test_role_table'; +CREATE UNIQUE INDEX unique_idx ON test_role_table(v); +SELECT rolname FROM pg_roles JOIN pg_class +ON pg_roles.oid = pg_class.relowner WHERE pg_class.relname = 'unique_idx'; +RESET SESSION AUTHORIZATION; + +-- The default tablegroup cannot be used explicitly +CREATE TABLE invalid_tbl (k int) TABLEGROUP "default"; + +-- Test distinct pushdown for colocated table +CREATE TABLE tbl_colo(r1 INT, r2 INT, r3 INT, r4 INT, r5 INT) WITH (colocation = TRUE); +CREATE INDEX ON tbl_colo(r1 asc, r3 asc, r5 asc); +INSERT INTO tbl_colo (SELECT 1, i, i, i, i FROM generate_series(1, 100) AS i); +INSERT INTO tbl_colo (SELECT 2, i, i, i, i FROM generate_series(1, 100) AS i); +INSERT INTO tbl_colo (SELECT 3, i, i, i, i FROM generate_series(1, 100) AS i); +EXPLAIN (COSTS OFF) SELECT DISTINCT r1 FROM tbl_colo WHERE r3 <= 1; +SELECT DISTINCT r1 FROM tbl_colo WHERE r3 <= 1; +/*+Set(enable_hashagg false)*/ EXPLAIN (COSTS OFF) SELECT DISTINCT r1 FROM tbl_colo WHERE r3 <= 1; +/*+Set(enable_hashagg false)*/ SELECT DISTINCT r1 FROM tbl_colo WHERE r3 <= 1; + +-- Drop database +\c yugabyte +DROP DATABASE colocation_test; + +-- Test Colocated Materialized View describe +CREATE DATABASE colocation_test WITH colocation = true; +\c colocation_test +CREATE TABLE t1 (a INT PRIMARY KEY); +CREATE MATERIALIZED VIEW m1 AS SELECT * FROM t1; +CREATE MATERIALIZED VIEW m2 with (colocation = true) AS SELECT * FROM t1; +CREATE MATERIALIZED VIEW m3 with (colocation = false) AS SELECT * FROM t1; + +\d m1 +\d m2 +\d m3 + +\c yugabyte +DROP DATABASE colocation_test; + +-- Test Colocated Materialized View +CREATE DATABASE colocation_test WITH colocation = true; +\c colocation_test +CREATE TABLE t1 (a INT PRIMARY KEY) WITH (colocation = true); +CREATE TABLE t2 (b INT PRIMARY KEY) WITH (colocation = false); +CREATE MATERIALIZED VIEW m0 WITH (colocation = true) as SELECT * FROM t1; +CREATE MATERIALIZED VIEW m1 WITH (colocation = false) as SELECT * FROM t1; +CREATE MATERIALIZED VIEW m2 WITH (colocation = true) as SELECT * FROM t2; +CREATE MATERIALIZED VIEW m3 WITH (colocation = false) as SELECT * FROM t2; +CREATE MATERIALIZED VIEW m4 WITH (colocation = true) as SELECT * FROM t1, t2; +CREATE MATERIALIZED VIEW m5 WITH (colocation = false) as SELECT * FROM t1, t2; +select is_colocated from yb_table_properties('m0'::regclass); +select is_colocated from yb_table_properties('m1'::regclass); +select is_colocated from yb_table_properties('m2'::regclass); +select is_colocated from yb_table_properties('m3'::regclass); +select is_colocated from yb_table_properties('m4'::regclass); +select is_colocated from yb_table_properties('m5'::regclass); + +-- Test yb_use_hash_splitting_by_default=false (Postgres compatibility) +CREATE OR REPLACE FUNCTION get_table_indexes(table_name text) + RETURNS TABLE( + relname name, + indisprimary boolean, + indisunique boolean, + indexdef text, + constraintdef text + ) AS $$ +BEGIN + RETURN QUERY EXECUTE + 'SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), ' || + 'pg_catalog.pg_get_constraintdef(con.oid, true) ' || + 'FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i ' || + 'LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN (''p'',''u'',''x'')) ' || + 'WHERE c.oid = ' || quote_literal(table_name) || '::regclass AND c.oid = i.indrelid AND i.indexrelid = c2.oid ' || + 'ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname'; +END; +$$ LANGUAGE plpgsql; + +-- With HASH as default, creating a colocated table or index with SPLIT AT should fail +CREATE INDEX ON t1 (b) SPLIT AT VALUES((10), (20), (30)); +CREATE TABLE split_fail(a int primary key, b int) SPLIT AT VALUES ((1000),(2000),(3000)); + +SET yb_use_hash_splitting_by_default = false; +CREATE TABLE foo(a int primary key, b int, c int); +SELECT * FROM get_table_indexes('foo'); + +CREATE TABLE bar(a int, b int, c int, primary key(a, b)); +SELECT * FROM get_table_indexes('bar'); + +CREATE TABLE baz(a int, b int, c int, primary key((a,b) HASH, c)); + + +CREATE TABLE qux(a int, b int, c int); + +ALTER TABLE qux ADD PRIMARY KEY(a); +SELECT * FROM get_table_indexes('qux'); +ALTER TABLE qux DROP CONSTRAINT qux_pkey; + +ALTER TABLE qux ADD PRIMARY KEY(a,b); +SELECT * FROM get_table_indexes('qux'); +ALTER TABLE qux DROP CONSTRAINT qux_pkey; + +ALTER TABLE qux ADD PRIMARY KEY(a,b ASC); +SELECT * FROM get_table_indexes('qux'); +ALTER TABLE qux DROP CONSTRAINT qux_pkey; + +ALTER TABLE qux ADD PRIMARY KEY(a,b HASH); + +CREATE INDEX ON qux (b); +CREATE INDEX ON qux (b HASH); +CREATE INDEX ON qux (c ASC); +CREATE INDEX ON qux ((a, c), b); +CREATE INDEX ON qux ((a, c)); +CREATE INDEX ON qux (a, b); +CREATE INDEX ON qux (b, c ASC); +CREATE INDEX ON qux (b, c HASH); + +SELECT * FROM get_table_indexes('qux'); + +-- With ASC as default, creating a colocated table or index with SPLIT AT should fail +CREATE INDEX ON qux (b) SPLIT AT VALUES((10), (20), (30)); +CREATE TABLE split_table(a int primary key, b int) SPLIT AT VALUES ((1000),(2000),(3000)); + +-- Test Table Rewrite operations with table colocation option +CREATE TABLE non_col_tbl_without_pk (k INT) WITH (colocation=0); +INSERT INTO non_col_tbl_without_pk SELECT generate_series(1, 100); +ALTER TABLE non_col_tbl_without_pk ADD PRIMARY KEY(k); +\d non_col_tbl_without_pk +SELECT min(k), max(k) FROM non_col_tbl_without_pk; + +CREATE TABLE col_tbl_without_pk (k INT) WITH (colocation=1); +INSERT INTO col_tbl_without_pk SELECT generate_series(1, 10); +ALTER TABLE col_tbl_without_pk ADD PRIMARY KEY(k); +\d col_tbl_without_pk +SELECT sum(k) FROM col_tbl_without_pk; + +CREATE MATERIALIZED VIEW non_col_mv WITH (colocation=0) AS SELECT * FROM col_tbl_without_pk WHERE k % 2 = 0; +CREATE MATERIALIZED VIEW col_mv WITH (colocation=1) AS SELECT * FROM col_tbl_without_pk WHERE k % 2 = 1; +SELECT sum(k) FROM non_col_mv; +SELECT sum(k) FROM col_mv; +INSERT INTO col_tbl_without_pk VALUES (11); +REFRESH MATERIALIZED VIEW non_col_mv; +REFRESH MATERIALIZED VIEW col_mv; +SELECT sum(k) FROM non_col_mv; +SELECT sum(k) FROM col_mv; + +-- Test table partitions with colocation option +CREATE TABLE partitioned_table (k INT) PARTITION BY LIST (k); +CREATE TABLE col_table_partition PARTITION OF partitioned_table FOR VALUES IN (2) WITH (COLOCATION=TRUE); +\d col_table_partition +CREATE TABLE non_col_table_partition PARTITION OF partitioned_table FOR VALUES IN (4) WITH (COLOCATION=FALSE); +\d non_col_table_partition + +INSERT INTO partitioned_table VALUES (2), (4); +SELECT * FROM col_table_partition; +SELECT * FROM non_col_table_partition; + +-- Test NULLS [NOT] DISTINCT WITH +CREATE TABLE tbl3 (k int, v int); +CREATE UNIQUE INDEX tbl3_k_u ON tbl3(k) NULLS NOT DISTINCT WITH (colocation_id=2929292929); +CREATE INDEX tbl3_v_rtoid ON tbl3(v) NULLS DISTINCT WITH (row_type_oid = 1111111, colocation_id=2828282828); + +set yb_enable_create_with_table_oid = on; +CREATE UNIQUE INDEX tbl3_v_u_toid ON tbl3(v) NULLS NOT DISTINCT WITH (table_oid = 2222222, colocation_id=2727272727); +CREATE INDEX tbl3_k_many_oids ON tbl3(k) NULLS NOT DISTINCT WITH (colocation_id=2626262626, row_type_oid = 3333333, table_oid = 4444444); +set yb_enable_create_with_table_oid = off; + +\d tbl3 +SELECT * FROM get_table_indexes('tbl3'); + +-- Test include_yb_metadata = true +SET yb_format_funcs_include_yb_metadata = on; +\d tbl3 +SELECT * FROM get_table_indexes('tbl3'); +SET yb_format_funcs_include_yb_metadata = off; + +\c yugabyte +DROP DATABASE colocation_test; diff --git a/src/test/regress/sql/yb.orig.feature_config.sql b/src/test/regress/sql/yb.orig.feature_config.sql new file mode 100644 index 0000000000000..577820f2d95c1 --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_config.sql @@ -0,0 +1,123 @@ +-- +-- YB_FEATURE Testsuite +-- An introduction on whether or not a feature is supported in YugaByte. +-- This test suite does not go in depth for each command. +-- +-- SET / RESET / SHOW configuration +-- Implementation-wise, Postgres groups configuration variables by their datatypes. +-- There are five groups listed in file "guc.c". +-- * In this test, we use one variable from each group in our tests. +-- * In the future, each variable and its affect should be tested properly because each variable +-- is associated with a different set of operators (function oid). +-- +-- Steps in this test +-- * First show the existing values. +-- * Change them in a transaction. +-- * Show the new values. +-- * Reset to default. +-- +------------------------------------------------------------------------------- +-- Show default values +------------------------------------------------------------------------------- +-- Variables of Type Boolean +SHOW enable_seqscan; +SHOW ssl; +-- Variables of Type Int +SHOW deadlock_timeout; +SHOW ssl_renegotiation_limit; +-- Variables of Type Real +SHOW seq_page_cost; +-- Variables of Type String +SHOW dynamic_library_path; +SHOW ssl_ciphers; +-- Variables of Type Enum +SHOW bytea_output; +------------------------------------------------------------------------------- +-- Some variables can only be set at the startup time. +------------------------------------------------------------------------------- +BEGIN; + SET ssl = false; + SET ssl_renegotiation_limit = 7; + SET ssl_ciphers = "HIGH:MEDIUM"; +END; +------------------------------------------------------------------------------- +-- SET LOCAL (Value lifetime: Transaction) +------------------------------------------------------------------------------- +BEGIN; + SET LOCAL enable_seqscan = FALSE; + SET LOCAL deadlock_timeout = 10000; + SET LOCAL seq_page_cost = 7.7; + SET LOCAL dynamic_library_path = "/tmp/data"; + SET LOCAL bytea_output = "escape"; +-- SET LOCAL: Show values while inside the transaction. + SHOW enable_seqscan; + SHOW deadlock_timeout; + SHOW seq_page_cost; + SHOW dynamic_library_path; + SHOW bytea_output; +END; +------------------------------------------------------------------------------- +-- SET LOCAL: Show values after the transaction ended. +-- All set values should go back to DEFAULT +------------------------------------------------------------------------------- +SHOW enable_seqscan; +SHOW deadlock_timeout; +SHOW seq_page_cost; +SHOW dynamic_library_path; +SHOW bytea_output; +------------------------------------------------------------------------------- +-- SET SESSION (Value lifetime: Session) +------------------------------------------------------------------------------- +BEGIN; + SET enable_seqscan = FALSE; + SET deadlock_timeout = 10000; + SET seq_page_cost = 7.7; + SET dynamic_library_path = "/tmp/data"; + SET bytea_output = "escape"; +-- SET SESSION: Show values while inside the transaction. + SHOW enable_seqscan; + SHOW deadlock_timeout; + SHOW seq_page_cost; + SHOW dynamic_library_path; + SHOW bytea_output; +END; +------------------------------------------------------------------------------- +-- SET SESSION: Show values after the transaction ended. +-- All set values should stay till end of session. +------------------------------------------------------------------------------- +SHOW enable_seqscan; +SHOW deadlock_timeout; +SHOW seq_page_cost; +SHOW dynamic_library_path; +SHOW bytea_output; +------------------------------------------------------------------------------- +-- RESET all variables to default valuse. +------------------------------------------------------------------------------- +BEGIN; + RESET ALL; +END; +SHOW enable_seqscan; +SHOW deadlock_timeout; +SHOW seq_page_cost; +SHOW dynamic_library_path; +SHOW bytea_output; +------------------------------------------------------------------------------- +-- Check ROLLBACK affect due to errors. +------------------------------------------------------------------------------- +BEGIN; + SET enable_seqscan = FALSE; + SET deadlock_timeout = 10000; + SET seq_page_cost = 7.7; + SET dynamic_library_path = "/tmp/data"; + SET bytea_output = "escape"; + ROLLBACK; +END; +SHOW enable_seqscan; +SHOW deadlock_timeout; +SHOW seq_page_cost; +SHOW dynamic_library_path; +SHOW bytea_output; +------------------------------------------------------------------------------- +-- To be safe, RESET variables before exit test. +------------------------------------------------------------------------------- +RESET ALL; diff --git a/src/test/regress/sql/yb.orig.feature_copy.sql b/src/test/regress/sql/yb.orig.feature_copy.sql new file mode 100644 index 0000000000000..ed3281f7c5216 --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_copy.sql @@ -0,0 +1,464 @@ +-- +-- YB_FEATURE Testsuite: COPY +-- +CREATE TABLE x ( + a int, + b int, + c text, + d text, + e text +); + +COPY x (a, b, c, d, e) from stdin; +9999 \N \\N \NN \N +10000 21 31 41 51 +\. + +COPY x (b, d) from stdin; +1 test_1 +\. + +COPY x (b, d) from stdin; +2 test_2 +3 test_3 +4 test_4 +5 test_5 +\. + +COPY x (a, b, c, d, e) from stdin; +10001 22 32 42 52 +10002 23 33 43 53 +10003 24 34 44 54 +10004 25 35 45 55 +10005 26 36 46 56 +\. + +-- Note: expect more rows with Connection Manager, pg_stat_progress_copy stores one row per backend +SELECT relid::regclass, command, yb_status, type, bytes_processed, bytes_total, + tuples_processed, tuples_excluded FROM pg_stat_progress_copy; + +-- non-existent column in column list: should fail +COPY x (xyz) from stdin; + +-- too many columns in column list: should fail +COPY x (a, b, c, d, e, d, c) from stdin; + +-- missing data: should fail +COPY x from stdin; + +\. +COPY x from stdin; +2000 230 23 23 +\. +COPY x from stdin; +2001 231 \N \N +\. + +-- extra data: should fail +COPY x from stdin; +2002 232 40 50 60 70 80 +\. + +-- various COPY options: delimiters, NULL string, encoding +COPY x (b, c, d, e) from stdin delimiter ',' null 'x'; +x,45,80,90 +x,\x,\\x,\\\x +x,\,,\\\,,\\ +\. + +COPY x from stdin WITH DELIMITER AS ';' NULL AS ''; +3000;;c;; +\. + +COPY x from stdin WITH DELIMITER AS ':' NULL AS E'\\X' ENCODING 'sql_ascii'; +4000:\X:C:\X:\X +4001:1:empty:: +4002:2:null:\X:\X +4003:3:Backslash:\\:\\ +4004:4:BackslashX:\\X:\\X +4005:5:N:\N:\N +4006:6:BackslashN:\\N:\\N +4007:7:XX:\XX:\XX +4008:8:Delimiter:\::\: +\. + +-- check results of copy in +SELECT * FROM x ORDER BY a,b,c,d; + +-- check copy out +COPY (SELECT * FROM x ORDER BY a,b,c,d) TO stdout; +COPY (SELECT c,e FROM x ORDER BY a,b,c,d) TO stdout; +COPY (SELECT b,e FROM x ORDER BY a,b,c,d) TO stdout WITH NULL 'I''m null'; + +CREATE TABLE y ( + col1 text, + col2 text +); + +INSERT INTO y VALUES ('Jackson, Sam', E'\\h'); +INSERT INTO y VALUES ('It is "perfect".',E'\t'); +INSERT INTO y VALUES ('', NULL); + +COPY (SELECT * FROM y ORDER BY col1,col2) TO stdout WITH CSV; +COPY (SELECT * FROM y ORDER BY col1,col2) TO stdout WITH CSV QUOTE '''' DELIMITER '|'; +COPY (SELECT * FROM y ORDER BY col1,col2) TO stdout WITH CSV FORCE QUOTE col2 ESCAPE E'\\' ENCODING 'sql_ascii'; +COPY (SELECT * FROM y ORDER BY col1,col2) TO stdout WITH CSV FORCE QUOTE *; + +-- Repeat above tests with new 9.0 option syntax + +COPY (SELECT * FROM y ORDER BY col1,col2) TO stdout (FORMAT CSV); +COPY (SELECT * FROM y ORDER BY col1,col2) TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|'); +COPY (SELECT * FROM y ORDER BY col1,col2) TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\'); +COPY (SELECT * FROM y ORDER BY col1,col2) TO stdout (FORMAT CSV, FORCE_QUOTE *); + +\copy (SELECT * FROM y ORDER BY col1,col2) TO stdout (FORMAT CSV) +\copy (SELECT * FROM y ORDER BY col1,col2) TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|') +\copy (SELECT * FROM y ORDER BY col1,col2) TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\') +\copy (SELECT * FROM y ORDER BY col1,col2) TO stdout (FORMAT CSV, FORCE_QUOTE *) + +--test that we read consecutive LFs properly + +CREATE TABLE testnl (a int, b text, c int); + +COPY testnl FROM stdin CSV; +1,"a field with two LFs + +inside",2 +\. + +-- test end of copy marker +CREATE TABLE testeoc (a text); + +COPY testeoc FROM stdin CSV; +a\. +\.b +c\.d +"\." +\. + +-- test handling of nonstandard null marker that violates escaping rules + +CREATE TABLE testnull (a int, b text); +INSERT INTO testnull VALUES (1, E'\\0'), (NULL, NULL); + +COPY (SELECT * FROM testnull ORDER BY a,b) TO stdout WITH NULL AS E'\\0'; + +COPY testnull FROM stdin WITH NULL AS E'\\0'; +42 \\0 +\0 \0 +\. + +SELECT * FROM testnull ORDER BY a,b; + +-- Test FORCE_NOT_NULL and FORCE_NULL options +CREATE TABLE forcetest ( + a INT NOT NULL, + b TEXT NOT NULL, + c TEXT, + d TEXT, + e TEXT +); +\pset null NULL +-- should succeed with no effect ("b" remains an empty string, "c" remains NULL) +BEGIN; +COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b), FORCE_NULL(c)); +1,,"" +\. +COMMIT; +SELECT b, c FROM forcetest WHERE a = 1; +-- should succeed, FORCE_NULL and FORCE_NOT_NULL can be both specified +BEGIN; +COPY forcetest (a, b, c, d) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(c,d), FORCE_NULL(c,d)); +2,'a',,"" +\. +COMMIT; +SELECT c, d FROM forcetest WHERE a = 2; +-- should fail with not-null constraint violation +BEGIN; +COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b), FORCE_NOT_NULL(c)); +3,,"" +\. +ROLLBACK; +-- should fail with "not referenced by COPY" error +BEGIN; +COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b)); +ROLLBACK; +-- should fail with "not referenced by COPY" error +BEGIN; +COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b)); +ROLLBACK; + +CREATE TABLE t(k INT PRIMARY KEY, v INT); +CREATE UNIQUE INDEX ON t(v); + +-- should fail, non unique primary key +COPY t FROM stdin; +1 1 +2 2 +2 3 +4 4 +\. + +SELECT relid::regclass, command, yb_status, type, bytes_processed, bytes_total, + tuples_processed, tuples_excluded FROM pg_stat_progress_copy; + +-- should fail, non unique index +COPY t FROM stdin; +1 1 +2 2 +3 2 +4 4 +\. + +-- Note: expect more rows with Connection Manager, pg_stat_progress_copy stores one row per backend +SELECT relid::regclass, command, yb_status, type, bytes_processed, bytes_total, + tuples_processed, tuples_excluded FROM pg_stat_progress_copy; + +SELECT COUNT(*) FROM t; + +-- Test COPY FROM on combination of cases + +-- Create trigger functions +create or replace function noticeBefore() returns trigger as $$begin raise notice 'b: %', new.b; return NEW; end$$ language plpgsql; +create or replace function noticeAfter() returns trigger as $$begin raise notice 'a: %', new.b; return NEW; end$$ language plpgsql; + +-- Test before and after row insert trigger on partition table +create table q (a int not null, b int) partition by list (a); +create table q1 (b int, a int not null); +create trigger trigBefore_q1 before insert on q1 for each row execute procedure noticeBefore(); +create trigger trigAfter_q1 after insert on q1 for each row execute procedure noticeAfter(); +alter table q attach partition q1 for values in (1, 2); +copy q from stdin; +1 5 +2 6 +1 7 +\. + +-- should fail, no partition exists for value 3 +copy q from stdin; +3 0 +\. +select * from q ORDER BY a,b; + +-- Test before and after row insert trigger on multiple partition tables +create table s (a int not null, b int) partition by list (a); +create table s1 (b int, a int not null); +create table s2 (a int not null, b int); +create table s3 (a int not null, b int); +create trigger trigBefore_s1 before insert on s1 for each row execute procedure noticeBefore(); +create trigger trigAfter_s2 after insert on s2 for each row execute procedure noticeAfter(); +create trigger trigAfter_s3 after insert on s3 for each row execute procedure noticeAfter(); +alter table s attach partition s1 for values in (1, 2); +alter table s attach partition s2 for values in (3); +alter table s attach partition s3 for values in (4); +copy s from stdin; +1 5 +2 6 +3 7 +4 8 +2 9 +\. + +-- should fail, no partition exists for value 6 +copy s from stdin; +6 10 +\. + +select * from s ORDER BY a,b; + +-- Test before and after row insert trigger on nested partition with depth 2 and constraint +create table r (a int check (a > 0), b int) partition by range(a); +create table r1 partition of r for values from (1) to (5) partition by list (b); +create table r2 partition of r1 for values in (3); +create table r3 partition of r1 for values in (4); +create trigger trigAfter_r2 after insert on r2 for each row execute procedure noticeAfter(); +create trigger trigAfter_r3 before insert on r3 for each row execute procedure noticeBefore(); +copy r from stdin; +1 3 +2 4 +4 3 +\. + +-- should fail, 5 is exclusive no invalid partition value +copy r from stdin; +5 3 +\. + +select * from r ORDER BY a,b; + +-- Test before and after row insert trigger on nested partition with depth 3 and constraint +create table w (a int check (a > 0), b int) partition by list (a); +create table w1 partition of w for values in (1, 2, 3, 4, 5, 6, 7, 8) partition by list (b); +create table w2 partition of w for values in (9, 10); +create table w3 partition of w1 for values in (1, 2); +create table w4 partition of w1 for values in (4, 5, 6, 7) partition by list (a); +create table w5 partition of w4 for values in (3, 8, 9, 10); +create or replace function before_w1() returns trigger as $$begin raise notice 'b_w1: %', new.a; return NEW; end$$ language plpgsql; +create or replace function after_w2() returns trigger as $$begin raise notice 'a_w2: %', new.a; return NEW; end$$ language plpgsql; +create or replace function before_w3() returns trigger as $$begin raise notice 'b_w3: %', new.b; return NEW; end$$ language plpgsql; +create or replace function before_w4() returns trigger as $$begin raise notice 'b_w4: %', new.b; return NEW; end$$ language plpgsql; +create or replace function after_w5() returns trigger as $$begin raise notice 'a_w5: %', new.a; return NEW; end$$ language plpgsql; +create trigger trigAfter_w2 after insert on w2 for each row execute procedure after_w2(); +create trigger trigBefore_w3 before insert on w3 for each row execute procedure before_w3(); +create trigger trigAfter_w5 after insert on w5 for each row execute procedure after_w5(); +-- prints at each leaf partitions +copy w from stdin; +8 6 +8 2 +9 1 +3 7 +\. + +-- should fail, 5 cannot fall under partition w5 (a should be between 8 to 10) +copy w from stdin; +2 6 +\. + +select * from w ORDER BY a,b; + +-- Test before row insert trigger with check constraint on partition table +create table p (a int check (a > 0), b int) partition by list (a); +create table p1 partition of p for values in (1); +create trigger trigBefore_p1 before insert on p1 for each row execute procedure noticeBefore(); +copy p from stdin; +1 5 +1 6 +\. + +-- should fail, fails constraint +copy p from stdin; +0 1 +\. + +select * from p ORDER BY a,b; + +-- Test index and auto generated column on partition table +create table u (a serial, b int) partition by list (a); +create table u1 partition of u for values in (1, 2, 3, 4, 5); +create unique index key on u (a); +copy u from stdin; +3 5 +4 6 +\. + +copy u (b) from stdin; +7 +8 +\. + +-- should fail, a duplicates +copy u from stdin; +1 9 +\. + +select * from u ORDER BY a,b; + +-- Test after row insert trigger with check constraint, index, and auto generated column on partition table +create table v (a int default 1 check (a > 0), b serial) partition by list (a); +create table v1 partition of v for values in (1, 2); +create unique index key2 on v (a, b); +create trigger trigAfter_v1 after insert on v1 for each row execute procedure noticeAfter(); +copy v from stdin; +1 5 +1 6 +1 7 +\. + +copy v (b) from stdin; +8 +9 +10 +\. + +-- should fail, duplicate key (1, 5) +copy v (b) from stdin; +5 +\. + +select * from v ORDER BY a,b; + +-- Test various copy options +create table copy_options (a int primary key, b int); + +-- skip +copy copy_options from stdin with (format csv, skip 2); +1,1 +2,2 +3,3 +4,4 +5,5 +\. + +select * from copy_options order by a; + +-- skip with invalid rows being included +truncate copy_options; +copy copy_options from stdin with (format csv, skip 3); +1 +2 +##,## +4,4 +5,5 +\. + +select * from copy_options order by a; + +-- replace +truncate copy_options; +insert into copy_options (a, b) values (1, 0), (2, 1), (3, 2); +select a, b from copy_options order by a; + +copy copy_options from stdin with (format csv, replace); +1,1 +2,2 +3,3 +4,4 +5,5 +\. + +select * from copy_options order by a; + +-- DISABLE_FK_CHECK +create table ref_table(a int primary key); +insert into ref_table(a) values (1), (2), (3); +create table main_table(a int references ref_table, b int); +copy main_table from stdin with (format csv, disable_fk_check); +1,1 +2,2 +3,3 +4,4 +5,5 +\. + +select * from main_table order by a; + +-- FREEZE +truncate copy_options; +copy copy_options from stdin with (format csv, freeze); +1,1 +2,2 +3,3 +4,4 +5,5 +\. + +select * from copy_options order by a; + +-- clean up +DROP TABLE forcetest; +DROP TABLE x; +DROP TABLE y; +DROP TABLE testnl; +DROP TABLE testeoc; +DROP TABLE testnull; +DROP TABLE t; +DROP TABLE q; +DROP TABLE s; +DROP TABLE r; +DROP TABLE w; +DROP TABLE p; +DROP TABLE u; +DROP TABLE v; +DROP TABLE copy_options; +DROP TABLE main_table; +DROP TABLE ref_table; diff --git a/src/test/regress/sql/yb.orig.feature_copyselect.sql b/src/test/regress/sql/yb.orig.feature_copyselect.sql new file mode 100644 index 0000000000000..57de2c206bd36 --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_copyselect.sql @@ -0,0 +1,96 @@ +-- +-- Test cases for COPY (select) TO +-- +create table test1 (id int, t text); +insert into test1 (id, t) values (1, 'a'); +insert into test1 (id, t) values (2, 'b'); +insert into test1 (id,t) values (3, 'c'); +insert into test1 (id,t) values (4, 'd'); +insert into test1 (id,t) values (5, 'e'); + +create table test2 (id int, t text); +insert into test2 (id, t) values (1, 'A'); +insert into test2 (id, t) values (2, 'B'); +insert into test2 (id, t) values (3, 'C'); +insert into test2 (id, t) values (4, 'D'); +insert into test2 (id, t) values (5, 'E'); + +create view v_test1 +as select 'v_'||t from test1; + +-- +-- Test COPY table TO +-- +copy (select * from test1 order by id) to stdout; +-- +-- This should fail +-- +copy v_test1 to stdout; +-- +-- Test COPY (select) TO +-- +copy (select t from test1 where id=1) to stdout; +-- +-- Test COPY (select for update) TO +-- +copy (select t from test1 where id=3) to stdout; +-- +-- This should fail +-- +copy (select t into temp test3 from test1 where id=3) to stdout; +-- +-- This should fail +-- +copy (select * from test1) from stdin; +-- +-- This should fail +-- +copy (select * from test1) (t,id) to stdout; +-- +-- Test JOIN +-- +copy (select * from test1 join test2 using (id) order by id) to stdout; +-- +-- Test UNION SELECT +-- +copy (select t from test1 where id = 1 UNION select * from v_test1 ORDER BY 1) to stdout; +-- +-- Test subselect +-- +copy (select * from (select t from test1 where id = 1 UNION select * from v_test1 ORDER BY 1) t1) to stdout; +-- +-- Test headers, CSV and quotes +-- +copy (select t from test1 where id = 1) to stdout csv header force quote t; +-- +-- Test psql builtins, plain table +-- +\copy (select * from test1 order by id) to stdout +-- +-- This should fail +-- +\copy v_test1 to stdout +-- +-- Test \copy (select ...) +-- +\copy (select "id",'id','id""'||t,(id + 1)*id,t,"test1"."t" from test1 where id=3) to stdout +-- +-- Drop everything +-- +drop table test2; +drop view v_test1; +drop table test1; + +-- psql handling of COPY in multi-command strings +copy (select 1) to stdout\; select 1/0; -- row, then error +select 1/0\; copy (select 1) to stdout; -- error only +copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; -- 1 2 3 + +create table test3 (c int); +select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 1 +1 +\. +2 +\. +select * from test3 order by c; +drop table test3; diff --git a/src/test/regress/sql/yb.orig.feature_db.sql b/src/test/regress/sql/yb.orig.feature_db.sql new file mode 100644 index 0000000000000..fa785e25294ed --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_db.sql @@ -0,0 +1,45 @@ +-- +-- Test case for CREATE DATABASE unsupported options +-- +CREATE DATABASE test IS_TEMPLATE = TRUE; +CREATE DATABASE test LC_COLLATE = "C"; +CREATE DATABASE test LC_CTYPE = "C"; +CREATE DATABASE test ENCODING = LATIN5; +CREATE DATABASE test TABLESPACE = some_space; + +-- +-- Test case for CREATE DATABASE supported options +-- +CREATE DATABASE test_1; +CREATE DATABASE test_2 TEMPLATE = template0 IS_TEMPLATE = FALSE; +CREATE DATABASE test_3 TEMPLATE = DEFAULT IS_TEMPLATE = DEFAULT LC_COLLATE = DEFAULT LC_CTYPE = DEFAULT ENCODING = DEFAULT TABLESPACE = DEFAULT ALLOW_CONNECTIONS = FALSE CONNECTION LIMIT = 10; +CREATE DATABASE test_4 ENCODING = UNICODE; +CREATE DATABASE test_5 ENCODING = UTF8; + +-- +-- Test case for ALTER DATABASE unsupported options +-- +ALTER DATABASE test_1 IS_TEMPLATE = true; +ALTER DATABASE test_1 TABLESPACE = some_space; +ALTER DATABASE test_1 SET TABLESPACE some_space; + +-- +-- Test case for ALTER DATABASE supported options +-- +ALTER DATABASE test_1 ALLOW_CONNECTIONS = FALSE CONNECTION LIMIT = 10; + +-- +-- DROPPING DATABASE cannot be done within a transaction +-- + +BEGIN TRANSACTION; +DROP DATABASE test_1; +END TRANSACTION; + +-- +-- DROPPING DATABASE IF EXISTS +-- + +DROP DATABASE test_1; +DROP DATABASE IF EXISTS test_1; +DROP DATABASE test_1; diff --git a/src/test/regress/sql/yb.orig.feature_delete.sql b/src/test/regress/sql/yb.orig.feature_delete.sql new file mode 100644 index 0000000000000..fdc1f5a5a1b3e --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_delete.sql @@ -0,0 +1,43 @@ +-- +-- YB_FEATURE Testsuite: DELETE +-- An introduction on whether or not a feature is supported in YugaByte. +-- This test suite does not go in depth for each command. +-- +-- Prepare two identical tables of all supported primitive types. +-- +-- INSERT values to be deleted +-- +INSERT INTO feature_tab_dml VALUES( + 88, + 88, + 88, + 88.88, + 88.88, + 'eight', + 'eight', + 'eight', + E'\\x88F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'August 8, 2019 08:08:08.8888', + 'August 8, 2019 08:08:08.8888 PST AD', + TRUE, + '{ 88, 88, 88 }', + '{ "eight", "eight", "eight" }'); +INSERT INTO feature_tab_dml_identifier VALUES(88, 'eighty eight'); +-- +-- Select rows before deleting. +SELECT * FROM feature_tab_dml WHERE col_smallint = 88; +-- +-- DELETE Statement +-- +DELETE FROM feature_tab_dml + WHERE col_smallint = 88 + RETURNING + col_smallint, + col_bigint, + col_real, + col_double, + DATE_TRUNC('day', col_timestamp_tz) expr_date, + col_array_text[1]; +-- +-- Select rows after deleting. +SELECT * FROM feature_tab_dml WHERE col_smallint = 88; diff --git a/src/test/regress/sql/yb.orig.feature_hash_types.sql b/src/test/regress/sql/yb.orig.feature_hash_types.sql new file mode 100644 index 0000000000000..3d0d54e97806f --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_hash_types.sql @@ -0,0 +1,133 @@ +-- +-- YB_FEATURE Testsuite +-- An introduction on whether or not a feature is supported in YugaByte. +-- This test suite does not go in depth for each command. +-- +-- Numeric Types +CREATE TABLE ft_h_tab_smallint (feature_col SMALLINT PRIMARY KEY); +CREATE TABLE ft_h_tab_integer (feature_col INTEGER PRIMARY KEY); +CREATE TABLE ft_h_tab_bigint (feature_col BIGINT PRIMARY KEY); +CREATE TABLE ft_h_tab_real (feature_col REAL PRIMARY KEY); +CREATE TABLE ft_h_tab_double_precision (feature_col DOUBLE PRECISION PRIMARY KEY); +CREATE TABLE ft_h_tab_decimal (feature_col DECIMAL PRIMARY KEY); +CREATE TABLE ft_h_tab_numeric (feature_col NUMERIC PRIMARY KEY); +CREATE TABLE ft_h_tab_smallserial (feature_col SMALLSERIAL PRIMARY KEY); +CREATE TABLE ft_h_tab_serial (feature_col SERIAL PRIMARY KEY); +CREATE TABLE ft_h_tab_bigserial (feature_col BIGSERIAL PRIMARY KEY); +-- +-- Monetary Types +CREATE TABLE ft_h_tab_money (feature_col MONEY PRIMARY KEY); +-- +-- Character Types +CREATE TABLE ft_h_tab_character_varying (feature_col CHARACTER VARYING(10) PRIMARY KEY); +CREATE TABLE ft_h_tab_varchar (feature_col VARCHAR(10) PRIMARY KEY); +CREATE TABLE ft_h_tab_character (feature_col CHARACTER(10) PRIMARY KEY); +CREATE TABLE ft_h_tab_char (feature_col CHAR(10) PRIMARY KEY); +CREATE TABLE ft_h_tab_text (feature_col TEXT PRIMARY KEY); +-- +-- Binary Types +CREATE TABLE ft_h_tab_bytea (feature_col BYTEA PRIMARY KEY); +-- +-- Date Time Types +CREATE TABLE ft_h_tab_timestamp (feature_col TIMESTAMP(2) PRIMARY KEY); +CREATE TABLE ft_h_tab_timestamp_with_time_zone (feature_col TIMESTAMP WITH TIME ZONE PRIMARY KEY); +CREATE TABLE ft_h_tab_date (feature_col DATE PRIMARY KEY); +CREATE TABLE ft_h_tab_time (feature_col TIME(2) PRIMARY KEY); +CREATE TABLE ft_h_tab_time_with_time_zone (feature_col TIME(2) WITH TIME ZONE PRIMARY KEY); +CREATE TABLE ft_h_tab_interval_ym (feature_col INTERVAL YEAR TO MONTH PRIMARY KEY); +CREATE TABLE ft_h_tab_interval_ds (feature_col INTERVAL DAY TO SECOND(2) PRIMARY KEY); +-- +-- Boolean Type +CREATE TABLE ft_h_tab_bool (feature_col BOOLEAN PRIMARY KEY); +-- +-- Enumerated Types +CREATE TYPE feature_h_enum AS ENUM('one', 'two', 'three'); +CREATE TABLE ft_h_tab_enum (feature_col feature_h_enum PRIMARY KEY); +-- +-- Geometric Types +CREATE TABLE ft_h_tab_point (feature_col POINT PRIMARY KEY); +CREATE TABLE ft_h_tab_line (feature_col LINE PRIMARY KEY); +CREATE TABLE ft_h_tab_lseg (feature_col LSEG PRIMARY KEY); +CREATE TABLE ft_h_tab_box (feature_col BOX PRIMARY KEY); +CREATE TABLE ft_h_tab_path (feature_col PATH PRIMARY KEY); +CREATE TABLE ft_h_tab_polygon (feature_col POLYGON PRIMARY KEY); +CREATE TABLE ft_h_tab_circle (feature_col CIRCLE PRIMARY KEY); +-- +-- Network Address Types +CREATE TABLE ft_h_tab_cidr (feature_col CIDR PRIMARY KEY); +CREATE TABLE ft_h_tab_inet (feature_col INET PRIMARY KEY); +CREATE TABLE ft_h_tab_macaddr (feature_col MACADDR PRIMARY KEY); +CREATE TABLE ft_h_tab_macaddr8 (feature_col MACADDR8 PRIMARY KEY); +-- +-- Bit String Types +CREATE TABLE ft_h_tab_bit (feature_col BIT PRIMARY KEY); +CREATE TABLE ft_h_tab_bit_varying (feature_col BIT VARYING(10) PRIMARY KEY); +-- +-- Text Search Types +CREATE TABLE ft_h_tab_tsvector (feature_col TSVECTOR PRIMARY KEY); +CREATE TABLE ft_h_tab_tsquery (feature_col TSQUERY PRIMARY KEY); +-- +-- UUID Type +CREATE TABLE ft_h_tab_uuid (feature_col UUID PRIMARY KEY); +-- +-- XML Type +CREATE TABLE ft_h_tab_xml (feature_col XML PRIMARY KEY); +-- +-- Arrays +CREATE TABLE ft_h_tab_array_int (feature_col INTEGER[] PRIMARY KEY); +CREATE TABLE ft_h_tab_array_text (feature_col TEXT[] PRIMARY KEY); +-- +-- Composite Types +CREATE TYPE feature_h_struct AS(id INTEGER, name TEXT); +CREATE TABLE ft_h_tab_struct (feature_col feature_h_struct PRIMARY KEY); +-- +-- JSON Types +CREATE TABLE ft_h_tab_json (feature_col JSON PRIMARY KEY); +-- +-- Range Types +CREATE TYPE feature_h_range AS RANGE(subtype=INTEGER); +CREATE TABLE ft_h_tab_range (feature_col feature_h_range PRIMARY KEY); +-- +-- Domain Types +CREATE DOMAIN feature_h_domain AS INTEGER CHECK (VALUE > 0); +CREATE TABLE ft_h_tab_domain (feature_col feature_h_domain PRIMARY KEY); +-- +-- Object Identifier Types +CREATE TABLE ft_h_tab_oid (feature_col OID PRIMARY KEY); +CREATE TABLE ft_h_tab_regproc (feature_col REGPROC PRIMARY KEY); +CREATE TABLE ft_h_tab_regprocedure (feature_col REGPROCEDURE PRIMARY KEY); +CREATE TABLE ft_h_tab_regoper (feature_col REGOPER PRIMARY KEY); +CREATE TABLE ft_h_tab_regoperator (feature_col REGOPERATOR PRIMARY KEY); +CREATE TABLE ft_h_tab_regclass (feature_col REGCLASS PRIMARY KEY); +CREATE TABLE ft_h_tab_regtype (feature_col REGTYPE PRIMARY KEY); +CREATE TABLE ft_h_tab_regrole (feature_col REGROLE PRIMARY KEY); +CREATE TABLE ft_h_tab_regnamespace (feature_col REGNAMESPACE PRIMARY KEY); +CREATE TABLE ft_h_tab_regconfig (feature_col REGCONFIG PRIMARY KEY); +CREATE TABLE ft_h_tab_regdictionary (feature_col REGDICTIONARY PRIMARY KEY); +CREATE TABLE ft_h_tab_xid (feature_col XID PRIMARY KEY); +CREATE TABLE ft_h_tab_cid (feature_col CID PRIMARY KEY); +CREATE TABLE ft_h_tab_tid (feature_col TID PRIMARY KEY); +-- +-- pg_lsn Type +CREATE TABLE ft_h_tab_pg_lsn (feature_col PG_LSN PRIMARY KEY); +-- +-- Pseudo-Types +CREATE TABLE ft_h_tab_any (feature_col ANY PRIMARY KEY); +CREATE TABLE ft_h_tab_anyelement (feature_col ANYELEMENT PRIMARY KEY); +CREATE TABLE ft_h_tab_anyarray (feature_col ANYARRAY PRIMARY KEY); +CREATE TABLE ft_h_tab_anynonarray (feature_col ANYNONARRAY PRIMARY KEY); +CREATE TABLE ft_h_tab_anyenum (feature_col ANYENUM PRIMARY KEY); +CREATE TABLE ft_h_tab_anyrange (feature_col ANYRANGE PRIMARY KEY); +CREATE TABLE ft_h_tab_cstring (feature_col CSTRING PRIMARY KEY); +CREATE TABLE ft_h_tab_internal (feature_col INTERNAL PRIMARY KEY); +CREATE TABLE ft_h_tab_language_handler (feature_col LANGUAGE_HANDLER PRIMARY KEY); +CREATE TABLE ft_h_tab_fdw_handler (feature_col FDW_HANDLER PRIMARY KEY); +CREATE TABLE ft_h_tab_index_am_handler (feature_col INDEX_AM_HANDLER PRIMARY KEY); +CREATE TABLE ft_h_tab_tsm_handler (feature_col TSM_HANDLER PRIMARY KEY); +CREATE TABLE ft_h_tab_record (feature_col RECORD PRIMARY KEY); +CREATE TABLE ft_h_tab_trigger (feature_col TRIGGER PRIMARY KEY); +CREATE TABLE ft_h_tab_event_trigger (feature_col EVENT_TRIGGER PRIMARY KEY); +CREATE TABLE ft_h_tab_pg_ddl_command (feature_col PG_DDL_COMMAND PRIMARY KEY); +CREATE TABLE ft_h_tab_void (feature_col VOID PRIMARY KEY); +-- TODO(jason): uncomment when issue #1975 is closed. +-- CREATE TABLE ft_h_tab_unknown (feature_col UNKNOWN); diff --git a/src/test/regress/sql/yb.orig.feature_insert.sql b/src/test/regress/sql/yb.orig.feature_insert.sql new file mode 100644 index 0000000000000..fe402d1a98355 --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_insert.sql @@ -0,0 +1,420 @@ +-- +-- YB_FEATURE Testsuite: INSERT +-- An introduction on whether or not a feature is supported in YugaByte. +-- This test suite does not go in depth for each command. +-- +-- Prepare two identical tables of all supported primitive types. +CREATE TABLE feature_tab_dml( + col_smallint SMALLINT, + col_integer INTEGER, + col_bigint BIGINT, + col_real REAL, + col_double DOUBLE PRECISION, + col_char CHARACTER(7), + col_varchar VARCHAR(7), + col_text TEXT, + col_bytea BYTEA, + col_timestamp TIMESTAMP(2), + col_timestamp_tz TIMESTAMP WITH TIME ZONE, + col_bool BOOLEAN, + col_array_int INTEGER[], + col_array_text TEXT[], + PRIMARY KEY(col_smallint)); +-- +CREATE TABLE feature_tab_dml_identifier( + col_id SMALLINT, + col_name TEXT, + PRIMARY KEY(col_id)); +-- +CREATE TABLE feature_tab_dml_copy_data AS SELECT * FROM feature_tab_dml; +CREATE TABLE feature_tab_dml_copy_no_data AS SELECT * FROM feature_tab_dml WITH NO DATA; +-- +-- INSERT VALUES +-- +INSERT INTO feature_tab_dml VALUES( + 1, + 1, + 1, + 1.1, + 1.1, + 'one', + 'one', + 'one', + E'\\x11F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'January 1, 2019 01:11:11.1111', + 'January 1, 2019 01:11:11.1111 PST AD', + TRUE, + '{ 1, 1, 1 }', + '{ "one", "one", "one" }'); +INSERT INTO feature_tab_dml_identifier VALUES(1, 'one'); +INSERT INTO feature_tab_dml VALUES( + 11, + 1, + 1, + 1.1, + 1.1, + 'one', + 'one', + 'one', + E'\\x11F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'January 1, 2019 01:11:11.1111', + 'January 1, 2019 01:11:11.1111 PST AD', + TRUE, + '{ 1, 1, 1 }', + '{ "one", "one", "one" }'); +INSERT INTO feature_tab_dml_identifier VALUES(11, 'eleven'); +-- Duplicate INSERT +INSERT INTO feature_tab_dml VALUES( + 1, + 1, + 1, + 1.1, + 1.1, + 'one', + 'one', + 'one', + E'\\x11F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'January 1, 2019 01:01:01.1111', + 'January 1, 2019 01:01:01.1111 PST AD', + TRUE, + '{ 1, 1, 1 }', + '{ "one", "one", "one" }'); +-- +INSERT INTO feature_tab_dml VALUES( + 2, + 2, + 2, + 2.2, + 2.2, + 'two', + 'two', + 'two', + E'\\x22F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'February 2, 2019 02:02:02.2222', + 'February 2, 2019 02:02:02.2222 PST AD', + TRUE, + '{ 2, 2, 2 }', + '{ "two", "two", "two" }'); +INSERT INTO feature_tab_dml_identifier VALUES(2, 'two'); +INSERT INTO feature_tab_dml VALUES( + 12, + 2, + 2, + 2.2, + 2.2, + 'two', + 'two', + 'two', + E'\\x22F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'February 2, 2019 02:02:02.2222', + 'February 2, 2019 02:02:02.2222 PST AD', + TRUE, + '{ 2, 2, 2 }', + '{ "two", "two", "two" }'); +INSERT INTO feature_tab_dml_identifier VALUES(12, 'twelve'); +-- +INSERT INTO feature_tab_dml VALUES( + 3, + 3, + 3, + 3.3, + 3.3, + 'three', + 'three', + 'three', + E'\\x33F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'March 3, 2019 03:03:03.3333', + 'March 3, 2019 03:03:03.3333 PST AD', + TRUE, + '{ 3, 3, 3 }', + '{ "three", "three", "three" }'); +INSERT INTO feature_tab_dml_identifier VALUES(3, 'three'); +INSERT INTO feature_tab_dml VALUES( + 13, + 3, + 3, + 3.3, + 3.3, + 'three', + 'three', + 'three', + E'\\x33F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'March 3, 2019 03:03:03.3333', + 'March 3, 2019 03:03:03.3333 PST AD', + TRUE, + '{ 3, 3, 3 }', + '{ "three", "three", "three" }'); +INSERT INTO feature_tab_dml_identifier VALUES(13, 'thirteen'); +-- +INSERT INTO feature_tab_dml VALUES( + 4, + 4, + 4, + 4.4, + 4.4, + 'four', + 'four', + 'four', + E'\\x44F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'April 4, 2019 04:04:04.4444', + 'April 4, 2019 04:04:04.4444 PST AD', + TRUE, + '{ 4, 4, 4 }', + '{ "four", "four", "four" }'); +INSERT INTO feature_tab_dml_identifier VALUES(4, 'four'); +INSERT INTO feature_tab_dml VALUES( + 14, + 4, + 4, + 4.4, + 4.4, + 'four', + 'four', + 'four', + E'\\x44F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'April 4, 2019 04:04:04.4444', + 'April 4, 2019 04:04:04.4444 PST AD', + TRUE, + '{ 4, 4, 4 }', + '{ "four", "four", "four" }'); +INSERT INTO feature_tab_dml_identifier VALUES(14, 'fourteen'); +-- +INSERT INTO feature_tab_dml VALUES( + 5, + 5, + 5, + 5.5, + 5.5, + 'five', + 'five', + 'five', + E'\\x55F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'May 5, 2019 05:05:05.5555', + 'May 5, 2019 05:05:05.5555 PST AD', + TRUE, + '{ 5, 5, 5 }', + '{ "five", "five", "five" }'); +INSERT INTO feature_tab_dml_identifier VALUES(5, 'five'); +INSERT INTO feature_tab_dml VALUES( + 15, + 5, + 5, + 5.5, + 5.5, + 'five', + 'five', + 'five', + E'\\x55F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'May 5, 2019 05:05:05.5555', + 'May 5, 2019 05:05:05.5555 PST AD', + TRUE, + '{ 5, 5, 5 }', + '{ "five", "five", "five" }'); +INSERT INTO feature_tab_dml_identifier VALUES(15, 'fifteen'); +-- +INSERT INTO feature_tab_dml VALUES( + 6, + 6, + 6, + 6.6, + 6.6, + 'six', + 'six', + 'six', + E'\\x66F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'June 6, 2019 06:06:06.6666', + 'June 6, 2019 06:06:06.6666 PST AD', + TRUE, + '{ 6, 6, 6 }', + '{ "six", "six", "six" }'); +INSERT INTO feature_tab_dml_identifier VALUES(6, 'six'); +INSERT INTO feature_tab_dml VALUES( + 16, + 6, + 6, + 6.6, + 6.6, + 'six', + 'six', + 'six', + E'\\x66F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'June 6, 2019 06:06:06.6666', + 'June 6, 2019 06:06:06.6666 PST AD', + TRUE, + '{ 6, 6, 6 }', + '{ "six", "six", "six" }'); +INSERT INTO feature_tab_dml_identifier VALUES(16, 'sixteen'); +-- +INSERT INTO feature_tab_dml VALUES( + 7, + 7, + 7, + 7.7, + 7.7, + 'seven', + 'seven', + 'seven', + E'\\x77F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'July 7, 2019 07:07:07.7777', + 'July 7, 2019 07:07:07.7777 PST AD', + TRUE, + '{ 7, 7, 7 }', + '{ "seven", "seven", "seven" }'); +INSERT INTO feature_tab_dml_identifier VALUES(7, 'seven'); +INSERT INTO feature_tab_dml VALUES( + 17, + 7, + 7, + 7.7, + 7.7, + 'seven', + 'seven', + 'seven', + E'\\x77F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'July 7, 2019 07:07:07.7777', + 'July 7, 2019 07:07:07.7777 PST AD', + TRUE, + '{ 7, 7, 7 }', + '{ "seven", "seven", "seven" }'); +INSERT INTO feature_tab_dml_identifier VALUES(17, 'seventeen'); +-- +INSERT INTO feature_tab_dml VALUES( + 8, + 8, + 8, + 8.8, + 8.8, + 'eight', + 'eight', + 'eight', + E'\\x88F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'August 8, 2019 08:08:08.8888', + 'August 8, 2019 08:08:08.8888 PST AD', + TRUE, + '{ 8, 8, 8 }', + '{ "eight", "eight", "eight" }'); +INSERT INTO feature_tab_dml_identifier VALUES(8, 'eight'); +INSERT INTO feature_tab_dml VALUES( + 18, + 8, + 8, + 8.8, + 8.8, + 'eight', + 'eight', + 'eight', + E'\\x88F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'August 8, 2019 08:08:08.8888', + 'August 8, 2019 08:08:08.8888 PST AD', + TRUE, + '{ 8, 8, 8 }', + '{ "eight", "eight", "eight" }'); +INSERT INTO feature_tab_dml_identifier VALUES(18, 'eighteen'); +-- +INSERT INTO feature_tab_dml VALUES( + 9, + 9, + 9, + 9.9, + 9.9, + 'nine', + 'nine', + 'nine', + E'\\x99F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'September 9, 2019 09:09:09.9999', + 'September 9, 2019 09:09:09.9999 PST AD', + TRUE, + '{ 9, 9, 9 }', + '{ "nine", "nine", "nine" }'); +INSERT INTO feature_tab_dml_identifier VALUES(9, 'nine'); +INSERT INTO feature_tab_dml VALUES( + 19, + 9, + 9, + 9.9, + 9.9, + 'nine', + 'nine', + 'nine', + E'\\x99F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'September 9, 2019 09:09:09.9999', + 'September 9, 2019 09:09:09.9999 PST AD', + TRUE, + '{ 9, 9, 9 }', + '{ "nine", "nine", "nine" }'); +INSERT INTO feature_tab_dml_identifier VALUES(19, 'nineteen'); +-- +INSERT INTO feature_tab_dml( + col_smallint, + col_bigint, + col_real, + col_char, + col_bytea, + col_timestamp_tz, + col_bool, + col_array_int, + col_array_text) + VALUES ( + 10, + 0, + 0.0, + 'zero', + E'\\x00F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'October 10, 2019 00:00:00.0000 PST AD', + TRUE, + '{ 0, 0, 0 }', + '{ "zero", "zero", "zero" }') + RETURNING + col_smallint, + col_bigint, + col_real, + col_double, + DATE_TRUNC('day', col_timestamp_tz) expr_date, + col_array_text[1]; +INSERT INTO feature_tab_dml_identifier VALUES(0, 'zero'); +-- +-- Use SELECT To Validate INSERT +-- +SELECT * FROM feature_tab_dml ORDER BY col_smallint; +-- +-- INSERT ON CONFLICT +-- +INSERT INTO feature_tab_dml as t VALUES( + 10, + 0, + 0, + 0.0, + 0.0, + 'ten', + 'ten', + 'ten', + E'\\x00F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'October 10, 2019 00:00:00.0000', + 'October 10, 2019 00:00:00.0000 PST AD', + TRUE, + '{ 0, 0, 0 }', + '{ "ten", "ten", "ten" }') + + ON CONFLICT (col_smallint) DO UPDATE SET + col_integer = t.col_integer + 10, + col_bigint = t.col_bigint + 10, + col_real = t.col_real + 10.10, + col_double = t.col_double + 10.10, + col_char = 'ten', + col_varchar = 'ten', + col_text = 'ten', + col_bytea = E'\\x10F1E2D3C4B5A6079889706A5B4C3D2E1F', + col_timestamp = 'October 10, 2019 10:10:10.0000', + col_timestamp_tz = 'October 10, 2019 10:10:10.0000 PST AD', + col_array_int = '{ 10, 10, 10 }', + col_array_text = '{ "ten", "ten", "ten" }' + WHERE t.* != excluded.*; +INSERT INTO feature_tab_dml_identifier VALUES(10, 'ten'); +-- +-- Use SELECT To Validate INSERT ON CONFLICT +-- +SELECT * FROM feature_tab_dml ORDER BY col_smallint; +-- +-- INSERT WITH OVERRIDING +-- Need testing once supported. +-- diff --git a/src/test/regress/sql/yb.orig.feature_legacy_colocation.sql b/src/test/regress/sql/yb.orig.feature_legacy_colocation.sql new file mode 100644 index 0000000000000..db9d41ca2a2d7 --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_legacy_colocation.sql @@ -0,0 +1,303 @@ +-- +-- Colocation regress test for legacy colocated databases +-- The regress test is the same as yb_feature_colocation except it doesn't cover new colocation +-- behaviors +-- + +-- CREATE TABLE on non-colocated database + +CREATE TABLE tab_colo (a INT) WITH (colocation = true); +CREATE TABLE tab_noco (a INT) WITH (colocation = false); +DROP TABLE tab_noco; + +-- CREATE DATABASE colocated + +CREATE DATABASE colocation_test colocation = true; +\c colocation_test +CREATE TABLE e (id int PRIMARY KEY, first_name TEXT) WITH (colocation = true) SPLIT INTO 10 TABLETS; + +-- CREATE TABLE + +CREATE TABLE tab_nonkey (a INT); +\d tab_nonkey +CREATE TABLE tab_key (a INT PRIMARY KEY); +\d tab_key +CREATE TABLE tab_range (a INT, PRIMARY KEY (a ASC)); +CREATE TABLE tab_range_nonkey (a INT, b INT, PRIMARY KEY (a ASC)); +-- opt out of using colocated tablet +CREATE TABLE tab_nonkey_noco (a INT) WITH (colocation = false); +-- colocated tables with no primary keys should not be hash partitioned +CREATE TABLE split_table ( a integer, b text ) SPLIT INTO 4 TABLETS; +-- multi column primary key table +CREATE TABLE tab_range_range (a INT, b INT, PRIMARY KEY (a, b DESC)); +CREATE TABLE tab_range_colo (a INT, PRIMARY KEY (a ASC)) WITH (colocation = true); + +INSERT INTO tab_range (a) VALUES (0), (1), (2); +INSERT INTO tab_range (a, b) VALUES (0, '0'); -- fail +INSERT INTO tab_range_nonkey (a, b) VALUES (0, '0'), (1, '1'); +INSERT INTO tab_nonkey_noco (a) VALUES (0), (1), (2), (3); +INSERT INTO tab_range_range (a, b) VALUES (0, 0), (0, 1), (1, 0), (1, 1); +INSERT INTO tab_range_colo (a) VALUES (0), (1), (2), (3); + +SELECT * FROM tab_range; +SELECT * FROM tab_range WHERE a = 2; +SELECT * FROM tab_range WHERE n = '0'; -- fail +SELECT * FROM tab_range_nonkey; +SELECT * FROM tab_nonkey_noco ORDER BY a ASC; +SELECT * FROM tab_range_range; +SELECT * FROM tab_range_colo; + +BEGIN; +INSERT INTO tab_range_colo VALUES (4); +SELECT * FROM tab_range_colo; +ROLLBACK; +BEGIN; +INSERT INTO tab_range_colo VALUES (5); +COMMIT; +SELECT * FROM tab_range_colo; + +INSERT INTO tab_range_colo VALUES (6), (6); + +-- CREATE INDEX + +-- table with index +CREATE TABLE tab_range_nonkey2 (a INT, b INT, PRIMARY KEY (a ASC)); +CREATE INDEX idx_range ON tab_range_nonkey2 (a); +\d tab_range_nonkey2 +INSERT INTO tab_range_nonkey2 (a, b) VALUES (0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +EXPLAIN (COSTS OFF) SELECT * FROM tab_range_nonkey2 WHERE a = 1; +SELECT * FROM tab_range_nonkey2 WHERE a = 1; +UPDATE tab_range_nonkey2 SET b = b + 1 WHERE a > 3; +SELECT * FROM tab_range_nonkey2; +DELETE FROM tab_range_nonkey2 WHERE a > 3; +SELECT * FROM tab_range_nonkey2; + +-- colocated table with non-colocated index +CREATE TABLE tab_range_nonkey3 (a INT, b INT, PRIMARY KEY (a ASC)); +CREATE INDEX idx_range_colo ON tab_range_nonkey3 (a) WITH (colocation = true); + +-- colocated table with colocated index +CREATE TABLE tab_range_nonkey4 (a INT, b INT, PRIMARY KEY (a ASC)); +CREATE INDEX idx_range_noco ON tab_range_nonkey4 (a) WITH (colocation = false); + +-- non-colocated table with index +CREATE TABLE tab_range_nonkey_noco (a INT, b INT, PRIMARY KEY (a ASC)) WITH (colocation = false); +CREATE INDEX idx_range2 ON tab_range_nonkey_noco (a); +INSERT INTO tab_range_nonkey_noco (a, b) VALUES (0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +EXPLAIN (COSTS OFF) SELECT * FROM tab_range_nonkey_noco WHERE a = 1; +SELECT * FROM tab_range_nonkey_noco WHERE a = 1; +UPDATE tab_range_nonkey_noco SET b = b + 1 WHERE a > 3; +SELECT * FROM tab_range_nonkey_noco; +DELETE FROM tab_range_nonkey_noco WHERE a > 3; +SELECT * FROM tab_range_nonkey_noco; + +-- more tables and indexes +CREATE TABLE tab_range_nonkey_noco2 (a INT, b INT, PRIMARY KEY (a ASC)) WITH (colocation = false); +CREATE INDEX idx_range3 ON tab_range_nonkey_noco2 (a); +INSERT INTO tab_range_nonkey_noco2 (a, b) VALUES (0, 0); +CREATE TABLE tab_range_nonkey_noco3 (a INT, b INT, PRIMARY KEY (a ASC)) WITH (colocation = false); +CREATE INDEX idx_range4 ON tab_range_nonkey_noco3 (a); +CREATE TABLE tab_range_nonkey5 (a INT, b INT, PRIMARY KEY (a ASC)); +CREATE INDEX idx_range5 ON tab_range_nonkey5 (a); +CREATE TABLE tbl (r1 INT, r2 INT, v1 INT, v2 INT, +PRIMARY KEY (r1, r2)); +CREATE INDEX idx_hash1 on tbl (r1 HASH); +CREATE INDEX idx_hash2 on tbl ((r1, r2) HASH); +CREATE INDEX idx_hash3 on tbl (r1 HASH, r2 ASC); +CREATE UNIQUE INDEX unique_idx_hash1 on tbl (r1 HASH); +CREATE UNIQUE INDEX unique_idx_hash2 on tbl ((r1, r2) HASH); +CREATE UNIQUE INDEX unique_idx_hash3 on tbl (r1 HASH, r2 ASC); +\d tbl +-- Make sure nothing bad happens to UNIQUE constraints after disabling HASH columns +-- for colocated indexes +CREATE TABLE tbl2 (r1 int PRIMARY KEY, r2 int, v1 int, v2 int, UNIQUE(v1)); +ALTER TABLE tbl2 ADD CONSTRAINT unique_v2_tbl2 UNIQUE(v2); +\d tbl2 + +DROP TABLE tbl, tbl2; + +-- colocated table with unique index +CREATE TABLE tab_nonkey2 (a INT) WITH (colocation = true); +CREATE UNIQUE INDEX idx_range6 ON tab_nonkey2 (a); + +\dt +\di + +-- TRUNCATE TABLE + +-- truncate colocated table with default index +TRUNCATE TABLE tab_range; +SELECT * FROM tab_range; +INSERT INTO tab_range VALUES (4); +SELECT * FROM tab_range; +INSERT INTO tab_range VALUES (1); +INSERT INTO tab_range VALUES (2), (5); +SELECT * FROM tab_range; +DELETE FROM tab_range WHERE a = 2; +TRUNCATE TABLE tab_range; +SELECT * FROM tab_range; +INSERT INTO tab_range VALUES (2); +SELECT * FROM tab_range; + +TRUNCATE TABLE tab_range; + +-- truncate non-colocated table without index +TRUNCATE TABLE tab_nonkey_noco; +SELECT * FROM tab_nonkey_noco; + +-- truncate colocated table with explicit index +TRUNCATE TABLE tab_range_nonkey2; +SELECT * FROM tab_range_nonkey2; + +-- truncate non-colocated table with explicit index +TRUNCATE TABLE tab_range_nonkey_noco2; +SELECT * FROM tab_range_nonkey_noco2; + +-- insert and truncate colocated table with explicit index +INSERT INTO tab_nonkey2 VALUES (1), (2), (3); +EXPLAIN (COSTS OFF) SELECT a FROM tab_nonkey2 ORDER BY a; +SELECT a FROM tab_nonkey2 ORDER BY a; +TRUNCATE TABLE tab_nonkey2; +SELECT a FROM tab_nonkey2 ORDER BY a; +INSERT INTO tab_nonkey2 VALUES (2), (4); +SELECT a FROM tab_nonkey2 ORDER BY a; + +\dt +\di + +-- ALTER TABLE +INSERT INTO tab_range (a) VALUES (0), (1), (2); +INSERT INTO tab_range_nonkey2 (a, b) VALUES (0, 0), (1, 1); + +SELECT * FROM tab_range; +SELECT * FROM tab_range_nonkey2; + +-- Alter colocated tables +ALTER TABLE tab_range ADD COLUMN x INT; +ALTER TABLE tab_range_nonkey2 DROP COLUMN b; + +SELECT * FROM tab_range; +SELECT * FROM tab_range_nonkey2; + +ALTER TABLE tab_range_nonkey2 RENAME TO tab_range_nonkey2_renamed; +SELECT * FROM tab_range_nonkey2_renamed; +SELECT * FROM tab_range_nonkey2; + +-- DROP TABLE + +-- drop colocated table with default index +DROP TABLE tab_range; +SELECT * FROM tab_range; + +-- drop non-colocated table without index +DROP TABLE tab_nonkey_noco; +SELECT * FROM tab_nonkey_noco; + +--- drop colocated table with explicit index +DROP TABLE tab_range_nonkey2_renamed; +SELECT * FROM tab_range_nonkey2_renamed; + +-- drop non-colocated table with explicit index +DROP TABLE tab_range_nonkey_noco2; +SELECT * FROM tab_range_nonkey_noco2; + +-- DROP INDEX + +-- drop index on non-colocated table +DROP INDEX idx_range2; +EXPLAIN (COSTS OFF) SELECT * FROM tab_range_nonkey_noco WHERE a = 1; + +-- drop index on colocated table +DROP INDEX idx_range5; +EXPLAIN (COSTS OFF) SELECT * FROM tab_range_nonkey5 WHERE a = 1; + +\dt +\di + +-- Test colocated tables/indexes with SPLIT INTO/SPLIT AT +CREATE TABLE invalid_tbl_split_into (k INT) SPLIT INTO 10 TABLETS; +CREATE TABLE invalid_tbl_split_at (k INT) SPLIT AT VALUES ((100)); +CREATE TABLE test_tbl (k INT); +CREATE INDEX invalid_idx_split_into ON test_tbl (k) SPLIT INTO 10 TABLETS; +CREATE INDEX invalid_idx_split_at ON test_tbl (k) SPLIT AT VALUES ((100)); +DROP TABLE test_tbl; + +-- Test colocated partitioned table and partition tables +CREATE TABLE partitioned_table ( + k1 INT, + v1 INT, + v2 TEXT +) +PARTITION BY HASH (k1) +WITH (colocation_id='123456'); +SELECT * FROM yb_table_properties('partitioned_table'::regclass::oid); + +CREATE TABLE table_partition PARTITION OF partitioned_table +FOR VALUES WITH (modulus 2, remainder 0) +WITH (colocation_id='234567'); +SELECT * FROM yb_table_properties('table_partition'::regclass::oid); + +-- drop database +\c yugabyte +DROP DATABASE colocation_test; + +-- Test syntax change as a result of Colocation GA change +-- Fail: only one of 'colocation' and 'colocated' options can be specified in CREATE DATABASE +CREATE DATABASE colocation_test colocated = true colocation = true; +-- Succeed with deprecated warning: create a colocated database using old syntax +CREATE DATABASE colocation_test colocated = true; +DROP DATABASE colocation_test; +-- Succeed: create a colocated database using new syntax +CREATE DATABASE colocation_test colocation = true; +\c colocation_test + +-- Fail: only one of 'colocation' and 'colocated' options can be specified in CREATE TABLE +CREATE TABLE tbl_colocated_colocation (k INT, v INT) +WITH (colocation = true, colocated = true); +-- Succeed with deprecated warning: create a colocated table using old syntax +CREATE TABLE tbl_colocated (k INT, v INT) WITH (colocated = true); +-- Succeed: create a colocated table using new syntax +CREATE TABLE tbl_colocation (k INT, v INT) WITH (colocation = true); +-- Check colocated table footer +\d tbl_colocation +-- Create and describe a table opt out of colocation +CREATE TABLE tbl_no_colocation (k INT, v INT) WITH (colocation = false); +\d tbl_no_colocation + +-- Test table rewrite operations with legacy colocation. +-- Suppress NOTICE messages during table rewrite operations. +SET client_min_messages TO WARNING; +CREATE TABLE base (col int, col2 int); +CREATE INDEX base_idx ON base(col2); +INSERT INTO base VALUES (1, 3), (2, 2), (3, 1); +ALTER TABLE base ADD PRIMARY KEY (col HASH); -- should fail. +ALTER TABLE base + ADD PRIMARY KEY (col), ADD COLUMN col3 float DEFAULT random(); +ALTER TABLE base ALTER COLUMN col2 TYPE int2; +ALTER TABLE base ADD COLUMN col4 SERIAL; +SELECT col, col2, col4 FROM base; +SELECT col, col2, col4 FROM base WHERE col2 = 1; +ALTER TABLE base DROP CONSTRAINT base_pkey; +SELECT col, col2, col4 FROM base ORDER BY col; +\d+ base; +SELECT num_tablets, num_hash_key_columns, is_colocated FROM + yb_table_properties('base_idx'::regclass); +CREATE TABLE base2 (col int, col2 int) WITH (COLOCATION=false); +CREATE INDEX base2_idx ON base2(col2); +INSERT INTO base2 VALUES (1, 3), (2, 2), (3, 1); +ALTER TABLE base2 + ADD PRIMARY KEY (col ASC), ADD COLUMN col3 float DEFAULT random(); +ALTER TABLE base2 ALTER COLUMN col2 TYPE int2; +ALTER TABLE base2 ADD COLUMN col4 SERIAL; +SELECT col, col2, col4 FROM base2; +SELECT col, col2, col4 FROM base2 WHERE col2 = 1; +ALTER TABLE base2 DROP CONSTRAINT base2_pkey; +SELECT col, col2, col4 FROM base2 ORDER BY col; +\d+ base2; +SELECT num_tablets, num_hash_key_columns, is_colocated FROM + yb_table_properties('base2_idx'::regclass); + +-- Drop database +\c yugabyte +DROP DATABASE colocation_test; +SELECT count(*) FROM pg_database WHERE datname = 'colocation_test'; diff --git a/src/test/regress/sql/yb.orig.feature_pk_split.sql b/src/test/regress/sql/yb.orig.feature_pk_split.sql new file mode 100644 index 0000000000000..9229f82c1a7bc --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_pk_split.sql @@ -0,0 +1,195 @@ +-- +-- YB_FEATURE_PARTITIONING Testsuite +-- An introduction on whether or not a feature is supported in YugaByte. +-- This test suite does not go in depth for each command. +-- +-- Testing basic SPLIT AT functionalities. +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (COSTS OFF)' + +-- +-- Table with RANGE primary key. +-- +CREATE TABLE feature_pk_split ( + col_integer INTEGER, + col_varchar VARCHAR(100), + col_text TEXT, + col_double DOUBLE PRECISION, + PRIMARY KEY (col_integer ASC, col_varchar ASC)) + SPLIT AT VALUES ((1, 'a'), (10, 'e'), (100, 'i'), (1000, 'o'), (10000, 'u')); +-- +-- Secondary index for some of the splits. +-- This work needs optimization. +-- +CREATE INDEX idx_small ON feature_pk_split (col_double ASC) WHERE col_double <= 9; +CREATE INDEX idx_large ON feature_pk_split (col_double ASC) WHERE col_double >= 10; +-- +-- INSERT at least 1 row for each partition. +-- +INSERT INTO feature_pk_split + VALUES ( -1, '-', 'partition 1', 1 ), + ( 0, 'm', 'partition 1', 2 ), + ( 1, '9', 'partition 1', 3 ), + + ( 1, 'a', 'partition 2', 4 ), + ( 5, 'm', 'partition 2', 5 ), + ( 10, 'd', 'partition 2', 6 ), + + ( 10, 'e', 'partition 3', 7 ), + ( 50, 'a', 'partition 3', 8 ), + ( 100, 'h', 'partition 3', 9 ), + + ( 100, 'i', 'partition 4', 10 ), + ( 500, 'm', 'partition 4', 11 ), + ( 1000, 'n', 'partition 4', 12 ), + + ( 1000, 'o', 'partition 5', 13 ), + ( 5000, 'm', 'partition 5', 14 ), + ( 10000, 't', 'partition 5', 15 ), + + ( 10000, 'u', 'partition 6', 16 ), + ( 50000, 'm', 'partition 6', 17 ), + ( 100000, 'z', 'partition 6', 18 ); +-- +-- Full scan. +-- +\set query ':P SELECT * FROM feature_pk_split;' +\i :iter_P2 +-- +-- Full scan with conditional operators. +-- +-- Operator `=` +SELECT $$ +:P SELECT * FROM feature_pk_split WHERE col_text = 'partition 3'; +$$ AS query \gset +\i :iter_P2 +-- Operator `IN` +SELECT $$ +:P SELECT * FROM feature_pk_split WHERE col_text IN ('partition 2', 'partition 5'); +$$ AS query \gset +\i :iter_P2 +-- Operator `<=` +\set query ':P SELECT * FROM feature_pk_split WHERE col_double <= 10;' +\i :iter_P2 +-- Operator `AND` +SELECT $$ +:P SELECT * FROM feature_pk_split WHERE col_text >= 'partition 3' AND col_double <= 10; +$$ AS query \gset +\i :iter_P2 +-- +-- Full scan with aggregate functions. +-- +\set query ':P SELECT COUNT(*) FROM feature_pk_split;' +\i :iter_P2 +\set query ':P SELECT MAX(col_integer) FROM feature_pk_split;' +\i :iter_P2 +\set query ':P SELECT MIN(col_varchar) FROM feature_pk_split;' +\i :iter_P2 +\set query ':P SELECT AVG(col_double) FROM feature_pk_split;' +\i :iter_P2 +-- +-- Primary key scan. +-- This work needs to be optimized. +-- +SELECT $$ +:P SELECT * FROM feature_pk_split WHERE col_integer = 50 AND col_varchar = 'a'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM feature_pk_split + WHERE col_integer >= 500 AND col_integer <= 5000 AND + col_varchar >= 'a' AND col_varchar <= 'n' + ORDER BY col_integer, col_varchar; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT COUNT(*) FROM feature_pk_split WHERE col_integer = 50 AND col_varchar = 'a'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT COUNT(*) FROM feature_pk_split + WHERE col_integer >= 500 AND col_integer <= 5000 AND + col_varchar >= 'a' AND col_varchar <= 'n'; +$$ AS query \gset +\i :iter_P2 +-- +-- Secondary key scan. +-- This work needs to be optimized. +-- +-- Scan one tablet. +\set query ':P SELECT * FROM feature_pk_split WHERE col_double < 2;' +\i :iter_P2 +-- Scan two tablets. +\set query ':P SELECT * FROM feature_pk_split WHERE col_double <= 5;' +\i :iter_P2 +-- Scan three tablets. +\set query ':P SELECT * FROM feature_pk_split WHERE col_double <= 8;' +\i :iter_P2 +-- Scan four tablets. +\set query ':P SELECT * FROM feature_pk_split WHERE col_double <= 11;' +\i :iter_P2 +-- Scan five tablets. +\set query ':P SELECT * FROM feature_pk_split WHERE col_double <= 14;' +\i :iter_P2 +-- Scan six tablets. +\set query ':P SELECT * FROM feature_pk_split WHERE col_double <= 17;' +\i :iter_P2 +-- Scan all tablets. +\set query ':P SELECT * FROM feature_pk_split WHERE col_double <= 100;' +\i :iter_P2 +-- Index only scan. +\set query ':P SELECT col_double FROM feature_pk_split WHERE col_double <= 8;' +\i :iter_P2 +-- +-- Table that has min & max split values. +-- * Using 3 splits: (1, MAX), (10, MIN), and (100, MIN). +-- * Unspecified split values are defaulted to MINVALUE. +-- SPLIT (10) is (10, MIN) +-- +CREATE TABLE feature_pk_split_min_max ( + col_integer INTEGER, + col_varchar VARCHAR(100), + col_text TEXT, + col_double DOUBLE PRECISION, + PRIMARY KEY (col_integer ASC, col_varchar ASC)) + SPLIT AT VALUES ((1, MAXVALUE), (10), (100, MINVALUE)); +-- +-- INSERT 2 rows to each partition. +-- +INSERT INTO feature_pk_split_min_max + VALUES ( 0, '-', 'partition 1', 2 ), + ( 1, 'z', 'partition 1', 2 ), + + ( 2, '-', 'partition 2', 3 ), + ( 3, '-', 'partition 2', 3 ), + ( 9, 'z', 'partition 2', 3 ), + + ( 10, '-', 'partition 3', 4 ), + ( 20, '-', 'partition 3', 4 ), + ( 30, '-', 'partition 3', 4), + ( 99, 'z', 'partition 3', 4 ), + + ( 100, '-', 'partition 4', 5 ), + ( 200, '-', 'partition 4', 5 ), + ( 300, '-', 'partition 4', 5 ), + ( 400, '-', 'partition 4', 5 ), + ( 999, 'z', 'partition 4', 5 ); +-- +-- SELECT from each partition. +-- TODO(neil) To complete this test, server must provide a method to track tablet information for +-- each row. Currently, this is verified by tracking number rows per tablet during development. +-- +-- All rows must be from partition 1: (nan) < PKey < (1, max) +SELECT * FROM feature_pk_split_min_max WHERE col_integer <= 1; +-- All rows must be from partition 2: (1, max) <= PKey < (10, min) +SELECT * FROM feature_pk_split_min_max WHERE col_integer > 1 AND col_integer < 10; +-- All rows must be from partition3: (10, min) <= PKey < (100, min) +SELECT * FROM feature_pk_split_min_max WHERE col_integer >= 10 AND col_integer < 100; +-- All rows must be from partition 4: (100, min) <= PKey < (nan) +SELECT * FROM feature_pk_split_min_max WHERE col_integer >= 100; diff --git a/src/test/regress/sql/yb.orig.feature_pk_split_desc.sql b/src/test/regress/sql/yb.orig.feature_pk_split_desc.sql new file mode 100644 index 0000000000000..b83767f424f26 --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_pk_split_desc.sql @@ -0,0 +1,200 @@ +-- +-- YB_FEATURE_PARTITIONING Testsuite +-- An introduction on whether or not a feature is supported in YugaByte. +-- This test suite does not go in depth for each command. +-- +-- Testing basic SPLIT AT functionalities. +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (COSTS OFF)' + +-- +-- Table with RANGE primary key. +-- +CREATE TABLE feature_pk_split_desc ( + col_integer INTEGER, + col_varchar VARCHAR(100), + col_text TEXT, + col_double DOUBLE PRECISION, + PRIMARY KEY (col_integer DESC, col_varchar DESC)) + SPLIT AT VALUES ((10000, 'u'), (1000, 'o'), (100, 'i'), (10, 'e'), (1, 'a')); +-- +-- Secondary index for some of the splits. +-- This work needs optimization. +-- +CREATE INDEX idx_small_desc ON feature_pk_split_desc (col_double DESC) WHERE col_double <= 9; +CREATE INDEX idx_large_desc ON feature_pk_split_desc (col_double DESC) WHERE col_double >= 10; +-- +-- INSERT at least 1 row for each partition. +-- +INSERT INTO feature_pk_split_desc + VALUES ( -1, '-', 'partition 1', 1 ), + ( 0, 'm', 'partition 1', 2 ), + ( 1, '9', 'partition 1', 3 ), + + ( 1, 'a', 'partition 2', 4 ), + ( 5, 'm', 'partition 2', 5 ), + ( 10, 'd', 'partition 2', 6 ), + + ( 10, 'e', 'partition 3', 7 ), + ( 50, 'a', 'partition 3', 8 ), + ( 100, 'h', 'partition 3', 9 ), + + ( 100, 'i', 'partition 4', 10 ), + ( 500, 'm', 'partition 4', 11 ), + ( 1000, 'n', 'partition 4', 12 ), + + ( 1000, 'o', 'partition 5', 13 ), + ( 5000, 'm', 'partition 5', 14 ), + ( 10000, 't', 'partition 5', 15 ), + + ( 10000, 'u', 'partition 6', 16 ), + ( 50000, 'm', 'partition 6', 17 ), + ( 100000, 'z', 'partition 6', 18 ); +-- +-- Full scan. +-- +\set query ':P SELECT * FROM feature_pk_split_desc;' +\i :iter_P2 +-- +-- Full scan with conditional operators. +-- +-- Operator `=` +SELECT $$ +:P SELECT * FROM feature_pk_split_desc WHERE col_text = 'partition 3'; +$$ AS query \gset +\i :iter_P2 +-- Operator `IN` +SELECT $$ +:P SELECT * FROM feature_pk_split_desc WHERE col_text IN ('partition 2', 'partition 5'); +$$ AS query \gset +\i :iter_P2 +-- Operator `<=` +\set query ':P SELECT * FROM feature_pk_split_desc WHERE col_double <= 10;' +\i :iter_P2 +-- Operator `AND` +SELECT $$ +:P SELECT * FROM feature_pk_split_desc WHERE col_text >= 'partition 3' AND col_double <= 10; +$$ AS query \gset +\i :iter_P2 +-- +-- Full scan with aggregate functions. +-- +\set query ':P SELECT COUNT(*) FROM feature_pk_split_desc;' +\i :iter_P2 +\set query ':P SELECT MAX(col_integer) FROM feature_pk_split_desc;' +\i :iter_P2 +\set query ':P SELECT MIN(col_varchar) FROM feature_pk_split_desc;' +\i :iter_P2 +\set query ':P SELECT AVG(col_double) FROM feature_pk_split_desc;' +\i :iter_P2 +-- +-- Primary key scan. +-- This work needs to be optimized. +-- +SELECT $$ +:P SELECT * FROM feature_pk_split_desc WHERE col_integer = 50 AND col_varchar = 'a'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM feature_pk_split_desc + WHERE col_integer >= 500 AND col_integer <= 5000 AND + col_varchar >= 'a' AND col_varchar <= 'n' + ORDER BY col_integer, col_varchar; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT COUNT(*) FROM feature_pk_split_desc WHERE col_integer = 50 AND col_varchar = 'a'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT COUNT(*) FROM feature_pk_split_desc + WHERE col_integer >= 500 AND col_integer <= 5000 AND + col_varchar >= 'a' AND col_varchar <= 'n'; +$$ AS query \gset +\i :iter_P2 +-- +-- Secondary key scan. +-- This work needs to be optimized. +-- +-- Scan one tablet. +\set query ':P SELECT * FROM feature_pk_split_desc WHERE col_double < 2;' +\i :iter_P2 +-- Scan two tablets. +\set query ':P SELECT * FROM feature_pk_split_desc WHERE col_double <= 5;' +\i :iter_P2 +-- Scan three tablets. +\set query ':P SELECT * FROM feature_pk_split_desc WHERE col_double <= 8;' +\i :iter_P2 +-- Scan four tablets. +\set query ':P SELECT * FROM feature_pk_split_desc WHERE col_double <= 11;' +\i :iter_P2 +-- Scan five tablets. +\set query ':P SELECT * FROM feature_pk_split_desc WHERE col_double <= 14;' +\i :iter_P2 +-- Scan six tablets. +\set query ':P SELECT * FROM feature_pk_split_desc WHERE col_double <= 17;' +\i :iter_P2 +-- Scan all tablets. +\set query ':P SELECT * FROM feature_pk_split_desc WHERE col_double <= 100;' +\i :iter_P2 +-- Index only scan. +\set query ':P SELECT col_double FROM feature_pk_split_desc WHERE col_double <= 8;' +\i :iter_P2 +-- +-- Table that has min & max split values. +-- * Using 3 splits: (1, MAX), (10, MIN), and (100, MIN). +-- * Unspecified split values are defaulted to MINVALUE. +-- SPLIT (10) is (10, MIN) +-- +CREATE TABLE feature_pk_split_desc_min_max ( + col_integer INTEGER, + col_varchar VARCHAR(100), + col_text TEXT, + col_double DOUBLE PRECISION, + PRIMARY KEY (col_integer DESC, col_varchar DESC)) + SPLIT AT VALUES ((100, MINVALUE), (10), (1, MAXVALUE)); +-- +-- INSERT 2 rows to each partition. +-- +INSERT INTO feature_pk_split_desc_min_max + VALUES ( 0, '-', 'partition 1', 2 ), + ( 1, 'z', 'partition 1', 2 ), + + ( 2, '-', 'partition 2', 3 ), + ( 3, '-', 'partition 2', 3 ), + ( 9, 'z', 'partition 2', 3 ), + + ( 10, '-', 'partition 3', 4 ), + ( 20, '-', 'partition 3', 4 ), + ( 30, '-', 'partition 3', 4), + ( 99, 'z', 'partition 3', 4 ), + + ( 100, '-', 'partition 4', 5 ), + ( 200, '-', 'partition 4', 5 ), + ( 300, '-', 'partition 4', 5 ), + ( 400, '-', 'partition 4', 5 ), + ( 999, 'z', 'partition 4', 5 ); +-- +-- SELECT from each partition. +-- TODO(neil) To complete this test, server must provide a method to track tablet information for +-- each row. Currently, this is verified by tracking number rows per tablet during development. +-- +-- All rows must be from partition 1: (nan) < PKey < (1, max) +\set explain 'EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF)' +\set query ':P SELECT * FROM feature_pk_split_desc_min_max WHERE col_integer <= 1 ORDER BY col_integer DESC;' +\i :iter_P2 +-- All rows must be from partition 2: (1, max) <= PKey < (10, min) +\set query ':P SELECT * FROM feature_pk_split_desc_min_max WHERE col_integer > 1 AND col_integer < 10 ORDER BY col_integer DESC;' +\i :iter_P2 +-- All rows must be from partition3: (10, min) <= PKey < (100, min) +\set query ':P SELECT * FROM feature_pk_split_desc_min_max WHERE col_integer >= 10 AND col_integer < 100 ORDER BY col_integer DESC;' +\i :iter_P2 +-- All rows must be from partition 4: (100, min) <= PKey < (nan) +\set query ':P SELECT * FROM feature_pk_split_desc_min_max WHERE col_integer >= 100 ORDER BY col_integer DESC;' +\i :iter_P2 diff --git a/src/test/regress/sql/yb.orig.feature_range_types.sql b/src/test/regress/sql/yb.orig.feature_range_types.sql new file mode 100644 index 0000000000000..e2e88ee608a4b --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_range_types.sql @@ -0,0 +1,169 @@ +-- +-- YB_FEATURE_PARTITIONING Testsuite +-- An introduction on whether or not a feature is supported in YugaByte. +-- This test suite does not go in depth for each command. +-- +-- Numeric Types +CREATE TABLE ft_r_tab_smallint (feature_col SMALLINT, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_integer (feature_col INTEGER, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_bigint (feature_col BIGINT, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_real (feature_col REAL, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_double_precision (feature_col DOUBLE PRECISION, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_decimal (feature_col DECIMAL, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_numeric (feature_col NUMERIC, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_smallserial (feature_col SMALLSERIAL, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_serial (feature_col SERIAL, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_bigserial (feature_col BIGSERIAL, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +-- +-- Monetary Types +CREATE TABLE ft_r_tab_money (feature_col MONEY, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('$100.99')); +-- +-- Character Types +CREATE TABLE ft_r_tab_character_varying (feature_col CHARACTER VARYING(10), PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('text value')); +CREATE TABLE ft_r_tab_varchar (feature_col VARCHAR(10), PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('text value')); +CREATE TABLE ft_r_tab_character (feature_col CHARACTER(10), PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('text value')); +CREATE TABLE ft_r_tab_char (feature_col CHAR(10), PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('text value')); +CREATE TABLE ft_r_tab_text (feature_col TEXT, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('text value')); +-- +-- Binary Types +CREATE TABLE ft_r_tab_bytea (feature_col BYTEA, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('text value')); +-- +-- Date Time Types +CREATE TABLE ft_r_tab_timestamp (feature_col TIMESTAMP(2), PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('1234-01-30 07:08:09')); +CREATE TABLE ft_r_tab_timestamp_with_time_zone (feature_col TIMESTAMP WITH TIME ZONE, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('1234-01-30 07:08:09+06')); +CREATE TABLE ft_r_tab_date (feature_col DATE, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('1234-01-30')); +CREATE TABLE ft_r_tab_time (feature_col TIME(2), PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('07:08:09')); +CREATE TABLE ft_r_tab_time_with_time_zone (feature_col TIME(2) WITH TIME ZONE, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_interval_ym (feature_col INTERVAL YEAR TO MONTH, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_interval_ds (feature_col INTERVAL DAY TO SECOND(2), PRIMARY KEY(feature_col ASC)); +-- +-- Boolean Type +CREATE TABLE ft_r_tab_bool (feature_col BOOLEAN, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('true')); +-- +-- Enumerated Types +CREATE TYPE feature_r_enum AS ENUM('one', 'two', 'three'); +CREATE TABLE ft_r_tab_enum (feature_col feature_r_enum, PRIMARY KEY(feature_col ASC)); +-- +-- Geometric Types +CREATE TABLE ft_r_tab_point (feature_col POINT, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_line (feature_col LINE, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_lseg (feature_col LSEG, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_box (feature_col BOX, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_path (feature_col PATH, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_polygon (feature_col POLYGON, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_circle (feature_col CIRCLE, PRIMARY KEY(feature_col ASC)); +-- +-- Network Address Types +CREATE TABLE ft_r_tab_cidr (feature_col CIDR, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_inet (feature_col INET, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_macaddr (feature_col MACADDR, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_macaddr8 (feature_col MACADDR8, PRIMARY KEY(feature_col ASC)); +-- +-- Bit String Types +CREATE TABLE ft_r_tab_bit (feature_col BIT, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_bit_varying (feature_col BIT VARYING(10), PRIMARY KEY(feature_col ASC)); +-- +-- Text Search Types +CREATE TABLE ft_r_tab_tsvector (feature_col TSVECTOR, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_tsquery (feature_col TSQUERY, PRIMARY KEY(feature_col ASC)); +-- +-- UUID Type +CREATE TABLE ft_r_tab_uuid (feature_col UUID, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('12345678-1234-5678-1234-567812345678')); +-- +-- XML Type +CREATE TABLE ft_r_tab_xml (feature_col XML, PRIMARY KEY(feature_col ASC)); +-- +-- Arrays +CREATE TABLE ft_r_tab_array_int (feature_col INTEGER[], PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_array_text (feature_col TEXT[], PRIMARY KEY(feature_col ASC)); +-- +-- Composite Types +CREATE TYPE feature_r_struct AS(id INTEGER, name TEXT); +CREATE TABLE ft_r_tab_struct (feature_col feature_r_struct, PRIMARY KEY(feature_col ASC)); +-- +-- JSON Types +CREATE TABLE ft_r_tab_json (feature_col JSON, PRIMARY KEY(feature_col ASC)); +-- +-- Range Types +CREATE TYPE feature_r_range AS RANGE(subtype=INTEGER); +CREATE TABLE ft_r_tab_range (feature_col feature_r_range, PRIMARY KEY(feature_col ASC)); +-- +-- Domain Types +CREATE DOMAIN feature_r_domain AS INTEGER CHECK (VALUE > 0); +CREATE TABLE ft_r_tab_domain (feature_col feature_r_domain, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +-- +-- Object Identifier Types +CREATE TABLE ft_r_tab_oid (feature_col OID, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_regproc (feature_col REGPROC, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_regprocedure (feature_col REGPROCEDURE, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100100)); +CREATE TABLE ft_r_tab_regoper (feature_col REGOPER, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_regoperator (feature_col REGOPERATOR, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_regclass (feature_col REGCLASS, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_regtype (feature_col REGTYPE, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_regrole (feature_col REGROLE, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_regnamespace (feature_col REGNAMESPACE, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_regconfig (feature_col REGCONFIG, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_regdictionary (feature_col REGDICTIONARY, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES ((100)); +CREATE TABLE ft_r_tab_xid (feature_col XID, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_cid (feature_col CID, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_tid (feature_col TID, PRIMARY KEY(feature_col ASC)); +-- +-- pg_lsn Type +CREATE TABLE ft_r_tab_pg_lsn (feature_col PG_LSN, PRIMARY KEY(feature_col ASC)) + SPLIT AT VALUES (('1/2345678')); +-- +-- Pseudo-Types +CREATE TABLE ft_r_tab_any (feature_col ANY, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_anyelement (feature_col ANYELEMENT, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_anyarray (feature_col ANYARRAY, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_anynonarray (feature_col ANYNONARRAY, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_anyenum (feature_col ANYENUM, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_anyrange (feature_col ANYRANGE, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_cstring (feature_col CSTRING, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_internal (feature_col INTERNAL, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_language_handler (feature_col LANGUAGE_HANDLER, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_fdw_handler (feature_col FDW_HANDLER, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_index_am_handler (feature_col INDEX_AM_HANDLER, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_tsm_handler (feature_col TSM_HANDLER, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_record (feature_col RECORD, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_trigger (feature_col TRIGGER, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_event_trigger (feature_col EVENT_TRIGGER, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_pg_ddl_command (feature_col PG_DDL_COMMAND, PRIMARY KEY(feature_col ASC)); +CREATE TABLE ft_r_tab_void (feature_col VOID, PRIMARY KEY(feature_col ASC)); +-- TODO(jason): uncomment when issue #1975 is closed. +-- CREATE TABLE ft_r_tab_unknown (feature_col UNKNOWN); diff --git a/src/test/regress/sql/yb.orig.feature_select.sql b/src/test/regress/sql/yb.orig.feature_select.sql new file mode 100644 index 0000000000000..d1c23a42c7d94 --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_select.sql @@ -0,0 +1,125 @@ +-- +-- YB_FEATURE Testsuite: SELECT +-- An introduction on whether or not a feature is supported in YugaByte. +-- This test suite does not go in depth for each command. +-- +-- SELECT Statements +-- +-- Select all. +SELECT * FROM feature_tab_dml_identifier ORDER BY col_id; +-- +-- Select rows 1-5 and 11-15 from the table. +SELECT * FROM feature_tab_dml ORDER BY col_smallint LIMIT 5; +SELECT * FROM feature_tab_dml ORDER BY col_smallint OFFSET 10 FETCH FIRST 5 ROWS ONLY; +-- +-- Select distinct +SELECT DISTINCT + col_smallint, + col_bigint, + col_real, + col_char, + col_bytea, + col_timestamp_tz, + col_bool, + col_array_int, + col_array_text + FROM feature_tab_dml + WHERE col_smallint < 20 + ORDER BY col_bigint, col_smallint; +SELECT DISTINCT + col_bigint, + col_real, + col_char, + col_bytea, + col_timestamp_tz, + col_bool, + col_array_int, + col_array_text + FROM feature_tab_dml + WHERE col_smallint < 20 + ORDER BY col_bigint; +-- +-- GROUP BY and HAVING clauses. +SELECT + col_bigint, + COUNT(col_smallint), + AVG(col_real), + SUM(col_array_int[1]), + SUM(col_double) + FROM feature_tab_dml + GROUP BY col_bigint + ORDER BY col_bigint; +-- +SELECT + col_bigint, + COUNT(col_smallint), + AVG(col_real), + SUM(col_array_int[1]), + SUM(col_double) + FROM feature_tab_dml + GROUP BY col_bigint HAVING SUM(col_double) < 18 + ORDER BY col_bigint; +-- +-- WINDOW +SELECT DISTINCT + rank() OVER yuga_win yuga_rank, + count(col_integer) OVER yuga_win, + sum(col_bigint) OVER yuga_win, + avg(col_real) OVER yuga_win + FROM feature_tab_dml + WINDOW yuga_win AS (PARTITION BY col_integer ORDER BY col_smallint) + ORDER BY yuga_rank, count, sum, avg; +-- +-- JOIN Test Cases +-- Table Join +SELECT + t1.col_smallint, + t1.col_array_text, + t2.col_name + FROM feature_tab_dml t1, feature_tab_dml_identifier t2 + WHERE t1.col_smallint = t2.col_id AND t2.col_name = t1.col_array_text[2] + ORDER BY t1.col_smallint; +-- +SELECT + t1.col_smallint, + t1.col_array_text, + t2.col_name + FROM feature_tab_dml t1, feature_tab_dml_identifier t2 + WHERE t1.col_smallint = t2.col_id AND + (t2.col_name = 'nine' OR t2.col_name = 'seven') + ORDER BY t1.col_smallint; +-- UNION +SELECT col_smallint Employee_ID, + col_text Employee_Name + FROM feature_tab_dml + WHERE col_smallint < 15 + UNION (SELECT col_id Employee_ID, + col_name Employee_Name + FROM feature_tab_dml_identifier + WHERE col_id > 5) + ORDER BY Employee_ID; +-- INTERSECT +SELECT col_smallint Employee_ID, + col_text Employee_Name + FROM feature_tab_dml + WHERE col_smallint < 15 + INTERSECT (SELECT col_id Employee_ID, + col_name Employee_Name + FROM feature_tab_dml_identifier + WHERE col_id > 5) + ORDER BY Employee_ID; +-- EXCEPT +SELECT col_smallint Employee_ID, + col_text Employee_Name + FROM feature_tab_dml WHERE col_smallint < 15 + EXCEPT (SELECT col_id Employee_ID, + col_name Employee_Name + FROM feature_tab_dml_identifier + WHERE col_id > 5) + ORDER BY Employee_ID; + +-- GHI #24512 +create table test (a bigint, b bigint); +insert into test (a, b) select i, 2024000000000 + i from generate_series(1, 1000) as i; +select max(sub.b) from (select b from test where (b > 2024000000000 AND b < 2025000000000) OFFSET 0 ) as sub; +drop table test; diff --git a/src/test/regress/sql/yb.orig.feature_temp.sql b/src/test/regress/sql/yb.orig.feature_temp.sql new file mode 100644 index 0000000000000..04254c95018fb --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_temp.sql @@ -0,0 +1,539 @@ +-- +-- TEMP +-- Test temp relations and indexes +-- + +-- test temp table/index masking + +CREATE TABLE temptest(col int); + +CREATE INDEX i_temptest ON temptest(col); + +CREATE TEMP TABLE temptest(tcol int); + +CREATE INDEX i_temptest ON temptest(tcol); + +SELECT * FROM temptest; + +DROP TABLE temptest; + +SELECT * FROM temptest; + +DROP TABLE temptest; + +-- test different syntaxes for temp table creation + +CREATE TEMPORARY TABLE a (z int); + +DROP TABLE a; + +CREATE LOCAL TEMPORARY TABLE b (z int); + +DROP TABLE b; + +CREATE LOCAL TEMP TABLE c (z int); + +DROP TABLE c; + +-- GLOBAL temp table is not supported. + +CREATE GLOBAL TEMP TABLE d (z int); + +-- test temp table selects + +CREATE TABLE temptest(col int); + +INSERT INTO temptest VALUES (1); + +CREATE TEMP TABLE temptest(tcol float); + +INSERT INTO temptest VALUES (2.1); + +SELECT * FROM temptest; + +-- test temp table truncate + +TRUNCATE temptest; + +SELECT * FROM temptest; + +DROP TABLE temptest; + +SELECT * FROM temptest; + +DROP TABLE temptest; + +-- test temp table CREATE .. AS + +CREATE TABLE test (a int); + +INSERT INTO test VALUES (1); + +CREATE TEMP TABLE temp AS SELECT * FROM test; + +SELECT * FROM test; + +-- test ON COMMIT DELETE ROWS + +CREATE TEMP TABLE temptest(col int) ON COMMIT DELETE ROWS; + +BEGIN; +INSERT INTO temptest VALUES (1); +INSERT INTO temptest VALUES (2); + +SELECT * FROM temptest; +COMMIT; + +SELECT * FROM temptest; + +DROP TABLE temptest; + +BEGIN; +CREATE TEMP TABLE temptest(col) ON COMMIT DELETE ROWS AS SELECT 1; + +SELECT * FROM temptest; +COMMIT; + +SELECT * FROM temptest; + +DROP TABLE temptest; + +-- Test ON COMMIT DROP + +BEGIN; + +CREATE TEMP TABLE temptest(col int) ON COMMIT DROP; + +INSERT INTO temptest VALUES (1); +INSERT INTO temptest VALUES (2); + +SELECT * FROM temptest; +COMMIT; + +SELECT * FROM temptest; + +BEGIN; +CREATE TEMP TABLE temptest(col) ON COMMIT DROP AS SELECT 1; + +SELECT * FROM temptest; +COMMIT; + +SELECT * FROM temptest; + +-- Test it with a CHECK condition that produces a toasted pg_constraint entry +BEGIN; +do $$ +begin + execute format($cmd$ + CREATE TEMP TABLE temptest (col text CHECK (col < %L)) ON COMMIT DROP + $cmd$, + (SELECT string_agg(g.i::text || ':' || random()::text, '|') + FROM generate_series(1, 100) g(i))); +end$$; + +SELECT * FROM temptest; +COMMIT; + +SELECT * FROM temptest; + +-- ON COMMIT is only allowed for TEMP + +CREATE TABLE temptest(col int) ON COMMIT DELETE ROWS; +CREATE TABLE temptest(col) ON COMMIT DELETE ROWS AS SELECT 1; + +-- test on commit drop on a temp table with an index and pk +BEGIN; + +CREATE TEMP TABLE temptest(col int primary key) ON COMMIT DROP; +CREATE INDEX NONCONCURRENTLY temptest_idx ON temptest(col); + +COMMIT; +SELECT * FROM temptest; + +-- test temp table updation + +CREATE TEMP TABLE test (a int); + +INSERT INTO test VALUES (1); + +INSERT INTO test VALUES (2); + +INSERT INTO test VALUES (1); + +UPDATE test SET a=3 WHERE a=1; + +SELECT * FROM test; + +-- test temp table row deletion + +DELETE FROM test; + +SELECT * FROM test; + +-- test ALTER TABLE on a temp table + +ALTER TABLE test ADD COLUMN d int; + +ALTER TABLE test ADD CONSTRAINT uniq UNIQUE(d); + +INSERT INTO test (d) VALUES (1); + +INSERT INTO test (d) VALUES (1); -- should fail + +SELECT * FROM test; + +ALTER TABLE test DROP COLUMN d; + +DELETE FROM test; + +ALTER TABLE test ADD COLUMN b int; + +SELECT * FROM test; + +ALTER TABLE test RENAME COLUMN b to c; + +SELECT * FROM test; + +ALTER TABLE test ADD CONSTRAINT checkc CHECK (c >= 0); + +INSERT INTO test (c) VALUES (-1); -- should fail + +INSERT INTO test (c) VALUES (1); + +ALTER TABLE test DROP COLUMN c; + +SELECT * FROM test; + +-- test COPY on a temp table + +COPY test (a) FROM stdin; +1 +\. + +SELECT * FROM test; + +COPY test (a) TO stdout; + +-- test transactions with temp tables + +BEGIN; + +INSERT INTO x VALUES (1); -- should fail + +INSERT INTO test VALUES (2); + +END; + +SELECT * FROM test; + +BEGIN; + +INSERT INTO test VALUES (2); + +INSERT INTO x VALUES (1); -- should fail + +END; + +SELECT * FROM test; + +BEGIN; + +INSERT INTO test VALUES (2); + +ROLLBACK; + +SELECT * FROM test; + +BEGIN; + +INSERT INTO test VALUES (2); + +COMMIT; + +SELECT * FROM test; + +CREATE TABLE x (y int check (y >= 0)); + +BEGIN; + +INSERT INTO test VALUES (2); + +INSERT INTO x VALUES (1); + +COMMIT; + +SELECT * FROM x; + +SELECT * FROM test; + +DELETE FROM x; + +DELETE FROM test; + +BEGIN; + +INSERT INTO test VALUES (1); + +INSERT INTO x VALUES (1); + +INSERT INTO x VALUES (-1); -- constraint violation, should fail + +COMMIT; + +SELECT * FROM x; + +SELECT * FROM test; + +DROP TABLE test; + +DROP TABLE x; + +-- test temp table deletion + +CREATE TEMP TABLE temptest (col int); + +\c + +SELECT * FROM temptest; + +-- Sleep a second as a workaround due to bug github #1469. + +SELECT pg_sleep(10); + +-- test temp table deletion + +CREATE TEMP TABLE temptest (col int); + +CREATE VIEW tempview AS SELECT * FROM temptest; + +\c + +SELECT * FROM tempview; + +SELECT * FROM temptest; + +-- Sleep a second as a workaround due to bug github #1469. + +SELECT pg_sleep(10); + +-- test temp table with indexes +CREATE TEMP TABLE temptest (k int PRIMARY KEY, v1 int, v2 int); +CREATE UNIQUE INDEX ON temptest (v1); +CREATE INDEX ON temptest USING hash (v2); + +-- \d temptest has unstable output as the temporary schemaname contains +-- the tserver uuid. Use regexp_replace to change it to pg_temp_x so that the +-- result is stable. +select current_setting('data_directory') || 'describe.out' as desc_output_file +\gset +\o :desc_output_file +\d temptest +\o +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); + +INSERT INTO temptest VALUES (1, 2, 3), (4, 5, 6); +INSERT INTO temptest VALUES (2, 2, 3); + +SELECT * FROM temptest WHERE k IN (1, 4) ORDER BY k; + +UPDATE temptest SET v1 = 0 WHERE k = 1; + +SELECT * FROM temptest ORDER BY k; + +DELETE FROM temptest WHERE k = 4; + +SELECT * FROM temptest WHERE k IN (1, 4) ORDER BY k; + +-- test temp table primary key duplicate error. +INSERT INTO temptest VALUES (100, 200, 300); +INSERT INTO temptest VALUES (100, 200, 300); +INSERT INTO temptest VALUES (101, 201, 301), (101, 201, 301); +DROP TABLE temptest; + +-- test temp table with primary index scan. +CREATE TEMP TABLE temptest (a INT PRIMARY KEY); +EXPLAIN (COSTS OFF) SELECT a FROM temptest WHERE a = 1; + +-- test temp table being used to update YB table. +CREATE TABLE test1 (x int, y int, z int); +INSERT INTO test1 VALUES (1, 2, 3); +CREATE TEMP TABLE test2 as table test1; +UPDATE test1 SET z = 2 FROM test2 WHERE test1.x = test2.x; +SELECT * FROM test1; + +-- test temp table ORDER BY and WHERE clause after failed insertion into unique indexed column +CREATE TEMP TABLE IF NOT EXISTS t1(c0 TEXT DEFAULT '3.61.4.60' PRIMARY KEY NOT NULL, c1 DECIMAL);; +INSERT INTO t1(c1) VALUES(0.835), (0.703); +SELECT t1.c0 FROM t1 ORDER BY t1.c0; +SELECT t1.c0 FROM t1 where c0 in ('3.61.4.60'); + +-- test temp table UPDATE on unique indexed column +CREATE temp TABLE t4(c0 DECIMAL NULL, UNIQUE(c0)); +INSERT INTO t4(c0) VALUES(0.03); +UPDATE t4 SET c0 = (0.05) WHERE t4.c0 = 0.03; +SELECT ALL t4.c0 FROM t4 ORDER BY t4.c0 ASC; + +CREATE TEMP TABLE tempt (k int PRIMARY KEY, v1 int, v2 int); +CREATE UNIQUE INDEX ON tempt (v1); +INSERT INTO tempt VALUES (1, 2, 3), (4, 5, 6); +INSERT INTO tempt VALUES (2, 2, 3); +SELECT * FROM tempt ORDER BY k; + +-- types in temp schema +set search_path = pg_temp, public; +create domain pg_temp.nonempty as text check (value <> ''); +-- function-syntax invocation of types matches rules for functions +select nonempty(''); +select pg_temp.nonempty(''); +-- other syntax matches rules for tables +select ''::nonempty; + +reset search_path; + +-- table rewrite on an empty temp table +create temp table p(f1 int, f2 int); +alter table p alter column f2 type bigint; +select * from p; +-- tests for row locking on temp tables +CREATE TEMP TABLE locktest(f1 int, f2 text); +INSERT INTO locktest VALUES (1, 'one'), (2, 'two'), (3, 'three'); +SELECT * FROM locktest ORDER BY f1 FOR UPDATE; +SELECT * FROM locktest WHERE f1 = 2 FOR UPDATE; +SELECT * FROM locktest WHERE f1 IN (SELECT f1 FROM locktest WHERE f1 < 3) FOR UPDATE; +SELECT a.f1, a.f2 FROM locktest a JOIN locktest b ON a.f1 = b.f1 WHERE a.f1 = 1 FOR UPDATE; +BEGIN; +SELECT * FROM locktest WHERE f1 = 3 FOR UPDATE; +ROLLBACK; +CREATE TEMP TABLE locktest_join(id int, note text); +INSERT INTO locktest_join VALUES (2, 'x'), (3, 'y'), (4, 'z'); +SELECT l.f1, l.f2, j.note FROM locktest l JOIN locktest_join j ON l.f1 = j.id FOR UPDATE; +CREATE TABLE regtable(id int, name text); +INSERT INTO regtable VALUES (1, 'a'), (2, 'b'), (3, 'c'); +CREATE TEMP TABLE temptable(id int, note text); +INSERT INTO temptable VALUES (1, 'x'), (2, 'y'), (4, 'z'); +SELECT r.id, r.name FROM regtable r WHERE r.id IN + (SELECT id FROM temptable FOR UPDATE) ORDER BY r.id; + + +-- Original test from insert.sql +-- tuple larger than fillfactor - temp table +-- +CREATE TEMP TABLE large_tuple_test (a int, b text) WITH (fillfactor = 10); +ALTER TABLE large_tuple_test ALTER COLUMN b SET STORAGE plain; + +-- create page w/ free space in range [nearlyEmptyFreeSpace, MaxHeapTupleSize) +INSERT INTO large_tuple_test (select 1, NULL); + +-- should still fit on the page +INSERT INTO large_tuple_test (select 2, repeat('a', 1000)); +SELECT pg_size_pretty(pg_relation_size('large_tuple_test'::regclass, 'main')); + +-- add small record to the second page +INSERT INTO large_tuple_test (select 3, NULL); + +-- now this tuple won't fit on the second page, but the insert should +-- still succeed by extending the relation +INSERT INTO large_tuple_test (select 4, repeat('a', 8126)); + +DROP TABLE large_tuple_test; + +-- ported from strings.sql specifically for temp tables +-- test substr with toasted text values +-- +SET bytea_output TO escape; +CREATE TEMP TABLE toasttest(f1 text); + +insert into toasttest values(repeat('1234567890',10000)); +insert into toasttest values(repeat('1234567890',10000)); + +-- +-- Ensure that some values are uncompressed, to test the faster substring +-- operation used in that case +-- +alter table toasttest alter column f1 set storage external; +insert into toasttest values(repeat('1234567890',10000)); +insert into toasttest values(repeat('1234567890',10000)); + +-- If the starting position is zero or less, then return from the start of the string +-- adjusting the length to be consistent with the "negative start" per SQL. +SELECT substr(f1, -1, 5) from toasttest; + +-- If the length is less than zero, an ERROR is thrown. +SELECT substr(f1, 5, -1) from toasttest; + +-- If no third argument (length) is provided, the length to the end of the +-- string is assumed. +SELECT substr(f1, 99995) from toasttest; + +-- If start plus length is > string length, the result is truncated to +-- string length +SELECT substr(f1, 99995, 10) from toasttest; + +TRUNCATE TABLE toasttest; +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +-- expect >0 blocks +SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty + FROM pg_class where relname = 'toasttest'; + +TRUNCATE TABLE toasttest; +ALTER TABLE toasttest set (toast_tuple_target = 4080); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +-- expect 0 blocks +SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty + FROM pg_class where relname = 'toasttest'; + +DROP TABLE toasttest; + +-- +-- test substr with toasted bytea values +-- +CREATE TEMP TABLE toasttest(f1 bytea); + +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); + +-- +-- Ensure that some values are uncompressed, to test the faster substring +-- operation used in that case +-- +alter table toasttest alter column f1 set storage external; +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); + +-- If the starting position is zero or less, then return from the start of the string +-- adjusting the length to be consistent with the "negative start" per SQL. +SELECT substr(f1, -1, 5) from toasttest; + +-- If the length is less than zero, an ERROR is thrown. +SELECT substr(f1, 5, -1) from toasttest; + +-- If no third argument (length) is provided, the length to the end of the +-- string is assumed. +SELECT substr(f1, 99995) from toasttest; + +-- If start plus length is > string length, the result is truncated to +-- string length +SELECT substr(f1, 99995, 10) from toasttest; + +DROP TABLE toasttest; + +-- test internally compressing datums + +-- this tests compressing a datum to a very small size which exercises a +-- corner case in packed-varlena handling: even though small, the compressed +-- datum must be given a 4-byte header because there are no bits to indicate +-- compression in a 1-byte header + +CREATE TEMP TABLE toasttest (c char(4096)); +INSERT INTO toasttest VALUES('x'); +SELECT length(c), c::text FROM toasttest; +SELECT c FROM toasttest; +DROP TABLE toasttest; + +RESET bytea_output; diff --git a/src/test/regress/sql/yb.orig.feature_types.sql b/src/test/regress/sql/yb.orig.feature_types.sql new file mode 100644 index 0000000000000..16968270b76c8 --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_types.sql @@ -0,0 +1,133 @@ +-- +-- YB_FEATURE Testsuite +-- An introduction on whether or not a feature is supported in YugaByte. +-- This test suite does not go in depth for each command. +-- +-- Numeric Types +CREATE TABLE feature_tab_smallint (feature_col SMALLINT); +CREATE TABLE feature_tab_integer (feature_col INTEGER); +CREATE TABLE feature_tab_bigint (feature_col BIGINT); +CREATE TABLE feature_tab_real (feature_col REAL); +CREATE TABLE feature_tab_double_precision (feature_col DOUBLE PRECISION); +CREATE TABLE feature_tab_decimal (feature_col DECIMAL); +CREATE TABLE feature_tab_numeric (feature_col NUMERIC); +CREATE TABLE feature_tab_smallserial (feature_col SMALLSERIAL); +CREATE TABLE feature_tab_serial (feature_col SERIAL); +CREATE TABLE feature_tab_bigserial (feature_col BIGSERIAL); +-- +-- Monetary Types +CREATE TABLE feature_tab_money (feature_col MONEY); +-- +-- Character Types +CREATE TABLE feature_tab_character_varying (feature_col CHARACTER VARYING(10)); +CREATE TABLE feature_tab_varchar (feature_col VARCHAR(10)); +CREATE TABLE feature_tab_character (feature_col CHARACTER(10)); +CREATE TABLE feature_tab_char (feature_col CHAR(10)); +CREATE TABLE feature_tab_text (feature_col TEXT); +-- +-- Binary Types +CREATE TABLE feature_tab_bytea (feature_col BYTEA); +-- +-- Date Time Types +CREATE TABLE feature_tab_timestamp (feature_col TIMESTAMP(2)); +CREATE TABLE feature_tab_timestamp_with_time_zone (feature_col TIMESTAMP WITH TIME ZONE); +CREATE TABLE feature_tab_date (feature_col DATE); +CREATE TABLE feature_tab_time (feature_col TIME(2)); +CREATE TABLE feature_tab_time_with_time_zone (feature_col TIME(2) WITH TIME ZONE); +CREATE TABLE feature_tab_interval_ym (feature_col INTERVAL YEAR TO MONTH); +CREATE TABLE feature_tab_interval_ds (feature_col INTERVAL DAY TO SECOND(2)); +-- +-- Boolean Type +CREATE TABLE feature_tab_bool (feature_col BOOLEAN); +-- +-- Enumerated Types +CREATE TYPE feature_enum AS ENUM('one', 'two', 'three'); +CREATE TABLE feature_tab_enum (feature_col feature_enum); +-- +-- Geometric Types +CREATE TABLE feature_tab_point (feature_col POINT); +CREATE TABLE feature_tab_line (feature_col LINE); +CREATE TABLE feature_tab_lseg (feature_col LSEG); +CREATE TABLE feature_tab_box (feature_col BOX); +CREATE TABLE feature_tab_path (feature_col PATH); +CREATE TABLE feature_tab_polygon (feature_col POLYGON); +CREATE TABLE feature_tab_circle (feature_col CIRCLE); +-- +-- Network Address Types +CREATE TABLE feature_tab_cidr (feature_col CIDR); +CREATE TABLE feature_tab_inet (feature_col INET); +CREATE TABLE feature_tab_macaddr (feature_col MACADDR); +CREATE TABLE feature_tab_macaddr8 (feature_col MACADDR8); +-- +-- Bit String Types +CREATE TABLE feature_tab_bit (feature_col BIT); +CREATE TABLE feature_tab_bit_varying (feature_col BIT VARYING(10)); +-- +-- Text Search Types +CREATE TABLE feature_tab_tsvector (feature_col TSVECTOR); +CREATE TABLE feature_tab_tsquery (feature_col TSQUERY); +-- +-- UUID Type +CREATE TABLE feature_tab_uuid (feature_col UUID); +-- +-- XML Type +CREATE TABLE feature_tab_xml (feature_col XML); +-- +-- Arrays +CREATE TABLE feature_tab_array_int (feature_col INTEGER[]); +CREATE TABLE feature_tab_array_text (feature_col TEXT[]); +-- +-- Composite Types +CREATE TYPE feature_struct AS(id INTEGER, name TEXT); +CREATE TABLE feature_tab_struct (feature_col feature_struct); +-- +-- JSON Types +CREATE TABLE feature_tab_json (feature_col JSON); +-- +-- Range Types +CREATE TYPE feature_range AS RANGE(subtype=INTEGER); +CREATE TABLE feature_tab_range (feature_col feature_range); +-- +-- Domain Types +CREATE DOMAIN feature_domain AS INTEGER CHECK (VALUE > 0); +CREATE TABLE feature_tab_domain (feature_col feature_domain); +-- +-- Object Identifier Types +CREATE TABLE feature_tab_oid (feature_col OID); +CREATE TABLE feature_tab_regproc (feature_col REGPROC); +CREATE TABLE feature_tab_regprocedure (feature_col REGPROCEDURE); +CREATE TABLE feature_tab_regoper (feature_col REGOPER); +CREATE TABLE feature_tab_regoperator (feature_col REGOPERATOR); +CREATE TABLE feature_tab_regclass (feature_col REGCLASS); +CREATE TABLE feature_tab_regtype (feature_col REGTYPE); +CREATE TABLE feature_tab_regrole (feature_col REGROLE); +CREATE TABLE feature_tab_regnamespace (feature_col REGNAMESPACE); +CREATE TABLE feature_tab_regconfig (feature_col REGCONFIG); +CREATE TABLE feature_tab_regdictionary (feature_col REGDICTIONARY); +CREATE TABLE feature_tab_xid (feature_col XID); +CREATE TABLE feature_tab_cid (feature_col CID); +CREATE TABLE feature_tab_tid (feature_col TID); +-- +-- pg_lsn Type +CREATE TABLE feature_tab_pg_lsn (feature_col PG_LSN); +-- +-- Pseudo-Types +CREATE TABLE feature_tab_any (feature_col ANY); +CREATE TABLE feature_tab_anyelement (feature_col ANYELEMENT); +CREATE TABLE feature_tab_anyarray (feature_col ANYARRAY); +CREATE TABLE feature_tab_anynonarray (feature_col ANYNONARRAY); +CREATE TABLE feature_tab_anyenum (feature_col ANYENUM); +CREATE TABLE feature_tab_anyrange (feature_col ANYRANGE); +CREATE TABLE feature_tab_cstring (feature_col CSTRING); +CREATE TABLE feature_tab_internal (feature_col INTERNAL); +CREATE TABLE feature_tab_language_handler (feature_col LANGUAGE_HANDLER); +CREATE TABLE feature_tab_fdw_handler (feature_col FDW_HANDLER); +CREATE TABLE feature_tab_index_am_handler (feature_col INDEX_AM_HANDLER); +CREATE TABLE feature_tab_tsm_handler (feature_col TSM_HANDLER); +CREATE TABLE feature_tab_record (feature_col RECORD); +CREATE TABLE feature_tab_trigger (feature_col TRIGGER); +CREATE TABLE feature_tab_event_trigger (feature_col EVENT_TRIGGER); +CREATE TABLE feature_tab_pg_ddl_command (feature_col PG_DDL_COMMAND); +CREATE TABLE feature_tab_void (feature_col VOID); +-- TODO(jason): uncomment when issue #1975 is closed. +-- CREATE TABLE feature_tab_unknown (feature_col UNKNOWN); diff --git a/src/test/regress/sql/yb.orig.feature_update.sql b/src/test/regress/sql/yb.orig.feature_update.sql new file mode 100644 index 0000000000000..4592c98d379ee --- /dev/null +++ b/src/test/regress/sql/yb.orig.feature_update.sql @@ -0,0 +1,142 @@ +-- +-- YB_FEATURE Testsuite: UPDATE +-- An introduction on whether or not a feature is supported in YugaByte. +-- This test suite does not go in depth for each command. +-- +-- Prepare two identical tables of all supported primitive types. +-- +-- INSERT values to be updated +-- +INSERT INTO feature_tab_dml VALUES( + 77, + 1, + 1, + 1.1, + 1.1, + 'one', + 'one', + 'one', + E'\\x11F1E2D3C4B5A6079889706A5B4C3D2E1F', + 'January 1, 2019 01:11:11.1111', + 'January 1, 2019 01:11:11.1111 PST AD', + TRUE, + '{ 1, 1, 1 }', + '{ "one", "one", "one" }'); +INSERT INTO feature_tab_dml_identifier VALUES(77, 'seventy seven'); +-- +-- UPDATE Statement +-- +UPDATE feature_tab_dml + SET + col_integer = 77, + col_bigint = 77, + col_real = 77.77, + col_double = 77.77, + col_char = 'seven', + col_varchar = 'seven', + col_text = 'seven', + col_bytea = E'\\x77F1E2D3C4B5A6079889706A5B4C3D2E1F', + col_timestamp = 'July 7, 2019 07:07:07.7777', + col_timestamp_tz = 'July 7, 2019 07:07:07.7777 PST AD', + col_bool = TRUE, + col_array_int = '{ 77, 77, 77 }', + col_array_text = '{ "seven", "seven", "seven" }' + + WHERE + col_smallint = 77 + RETURNING + col_smallint, + col_bigint, + col_real, + col_double, + DATE_TRUNC('day', col_timestamp_tz) expr_date, + col_array_text[1]; +-- +-- Select updated rows. +-- +SELECT * FROM feature_tab_dml WHERE col_smallint = 77; +-- +-- INSERT empty row to check updating null values. +-- +INSERT INTO feature_tab_dml VALUES(78); +UPDATE feature_tab_dml + SET + col_integer = 78, + col_bigint = 78, + col_real = 78.78, + col_double = 78.78, + col_char = 'eight', + col_varchar = 'eight', + col_text = 'eight', + col_bytea = E'\\x78F1E2D3C4B5A6079889706A5B4C3D2E1F', + col_timestamp = 'July 8, 2019 08:08:08.7878', + col_timestamp_tz = 'July 8, 2019 08:08:08.7878 PST AD', + col_bool = FALSE, + col_array_int = '{ 78, 78, 78 }', + col_array_text = '{ "eight", "eight", "eight" }' + WHERE + col_smallint = 78; +-- +-- Select updated rows. +-- +SELECT * FROM feature_tab_dml WHERE col_smallint = 78; +-- +-- UPDATE existing values to null. +-- +UPDATE feature_tab_dml + SET + col_integer = null, + col_bigint = null, + col_real = null, + col_double = null, + col_char = null, + col_varchar = null, + col_text = null, + col_bytea = null, + col_timestamp = null, + col_timestamp_tz = null, + col_bool = null, + col_array_int = null, + col_array_text = null + WHERE + col_smallint = 78; +-- +-- Select updated rows. +-- +SELECT * FROM feature_tab_dml WHERE col_smallint = 78; + +-- +-- Test RETURNING clause with indexes and foreign keys. +-- +CREATE TABLE feature_tab_dml_returning ( + id SERIAL PRIMARY KEY, + a TEXT UNIQUE, + b timestamp with time zone +); + +INSERT INTO feature_tab_dml_returning (a) values ('foo'); +SELECT * FROM feature_tab_dml_returning; + +-- Using `NOW()` because it is not an immutable function, so it needs to be +-- evaluated during execution. +-- Do not select NOW() value to ensure expected output is stable, only +-- check query succeeds and b is not null (same for queries below). +UPDATE feature_tab_dml_returning SET b = NOW() where id = 1 RETURNING id, a, b IS NOT NULL; +SELECT id, a, b IS NOT NULL FROM feature_tab_dml_returning; + +-- +-- Add a table referencing column 'a'. +-- +CREATE TABLE feature_tab_dml_returning_fk ( + id SERIAL NOT NULL PRIMARY KEY, + x TEXT NOT NULL, + y TEXT REFERENCES feature_tab_dml_returning(a) NOT NULL +); + +-- Should succeed because no fkey references 'foo' +UPDATE feature_tab_dml_returning SET a = 'bar', b = NOW() where id = 1 RETURNING id, a, b IS NOT NULL; +SELECT id, a, b IS NOT NULL FROM feature_tab_dml_returning; + +INSERT INTO feature_tab_dml_returning_fk (x, y) VALUES ('x', 'bar'); +-- Should fail the foreign key check. +UPDATE feature_tab_dml_returning SET a = 'bar2', b = NOW() where id = 1 RETURNING id, a, b IS NOT NULL; diff --git a/src/test/regress/sql/yb.orig.fetch_limits.sql b/src/test/regress/sql/yb.orig.fetch_limits.sql new file mode 100644 index 0000000000000..a3c9d4541d1c3 --- /dev/null +++ b/src/test/regress/sql/yb.orig.fetch_limits.sql @@ -0,0 +1,94 @@ +-- If size limit is exceeded, last row has to be rescanned. +-- with too large row and too low size limit it means rescan of the each row, +-- except the last on the tablet. +-- Use explicit number of tablets to achieve predictable number of rows scanned +CREATE TABLE t_large(a int primary key, v varchar(1000)) SPLIT INTO 3 TABLETS; +INSERT INTO t_large SELECT g, REPEAT('x', 1000) FROM generate_series(1, 5000) g; +CREATE INDEX t_large_v_idx_asc ON t_large(v ASC); + +CREATE TABLE t_medium (a int primary key, v varchar(100)) SPLIT INTO 3 TABLETS; +INSERT INTO t_medium SELECT g, REPEAT('x', 100) FROM generate_series(1, 5000) g; +CREATE INDEX t_medium_v_idx_asc ON t_medium(v ASC); + +CREATE TABLE t_small (a int primary key, v varchar(25)) SPLIT INTO 3 TABLETS; +INSERT INTO t_small SELECT g, REPEAT('x', 25) FROM generate_series(1, 5000) g; +CREATE INDEX t_small_v_idx_asc ON t_small(v ASC); + +-- #### default limits just use the row limit #### +-- seq scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_large; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_medium; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_small; +-- index scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_large WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_medium WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_small WHERE v > ''; +-- index only scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_large WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_medium WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_small WHERE v > ''; + +-- #### size limit is less than row size #### +SET yb_fetch_size_limit TO '256'; +-- seq scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_large; +-- index scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_large WHERE v > ''; +-- index only scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_large WHERE v > ''; + +-- #### bounded by size limit when row limit is higher #### +SET yb_fetch_size_limit TO '1kB'; +-- seq scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_large; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_medium; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_small; +-- index scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_large WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_medium WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_small WHERE v > ''; +-- index only scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_large WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_medium WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_small WHERE v > ''; + +SET yb_fetch_row_limit = 10000; +SET yb_fetch_size_limit = '1MB'; +-- seq scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_large; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_medium; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_small; +-- index scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_large WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_medium WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_small WHERE v > ''; +-- index only scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_large WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_medium WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_small WHERE v > ''; + +-- #### bounded by whatever is hit first: row or size limit #### +SET yb_fetch_size_limit TO '500kB'; +-- seq scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_large; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_medium; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_small; +-- index scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_large WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_medium WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_small WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM t_small WHERE v > '' LIMIT 1; +-- index only scan +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_large WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_medium WHERE v > ''; +EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT v FROM t_small WHERE v > ''; + +-- +-- #22648: Avoid duplicate rows for index-scans where the table request does not fit into 1 response +-- +create table test_correct(a int, b int, c text); +insert into test_correct select i, i, repeat('1234567890', 100) from generate_series(1, 20) i; +create index on test_correct(a asc); +set yb_fetch_size_limit = '1kB'; +explain (analyze, costs off, timing off, summary off) select * from test_correct where a < 20 and b % 5 = 0; +select a, b, substring(c, 0, 10) from test_correct where a < 20 and b % 5 = 0 order by a; diff --git a/src/test/regress/sql/yb.orig.fetch_limits_joins.sql b/src/test/regress/sql/yb.orig.fetch_limits_joins.sql new file mode 100644 index 0000000000000..6c03d8ed342c2 --- /dev/null +++ b/src/test/regress/sql/yb.orig.fetch_limits_joins.sql @@ -0,0 +1,57 @@ +-- #### nested loop joins #### +SET yb_fetch_size_limit = 0; +SET yb_fetch_row_limit = 1024; +SET yb_bnl_batch_size = 1; +-- the default behaviour sends 6 RPCs for the outer table +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) SELECT * FROM t_large JOIN t_small ON t_large.a = t_small.a; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) SELECT * FROM t_small JOIN t_large ON t_large.a = t_small.a; + +SET yb_fetch_size_limit = '50kB'; +SET yb_fetch_row_limit = 10000; +-- we require more requests to collect the rows of the outer table when it is large. +-- the inner table is unchanged at 1 RPC per row. +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) SELECT * FROM t_large JOIN t_small ON t_large.a = t_small.a; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) SELECT * FROM t_small JOIN t_large ON t_large.a = t_small.a; + +-- #### batch nested loop joins #### +SET yb_bnl_batch_size = 1024; +-- now we can request large amounts of rows from the inner table - these are still bound by the size limit +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) SELECT * FROM t_large JOIN t_small ON t_large.a = t_small.a; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) SELECT * FROM t_small JOIN t_large ON t_large.a = t_small.a; + +SET yb_fetch_size_limit = '500kB'; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) SELECT * FROM t_large JOIN t_small ON t_large.a = t_small.a; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) SELECT * FROM t_small JOIN t_large ON t_large.a = t_small.a; + +-- YB_TODO: tests here onwards fail because proper join is not successfully chosen +SET yb_bnl_batch_size = 1; -- disable BNLJ + +-- #### merge joins #### +SET yb_fetch_row_limit = 1024; +SET yb_fetch_size_limit = 0; + +-- the default behaviour sends 6 RPCs per table +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) /*+ MergeJoin(t_small t_large) */ SELECT * FROM t_large JOIN t_small ON t_large.a = t_small.a; + +SET yb_fetch_row_limit = 0; +-- size limit affects both tables in a Merge Join +SET yb_fetch_size_limit = '500kB'; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) /*+ MergeJoin(t_small t_large) */ SELECT * FROM t_large JOIN t_small ON t_large.a = t_small.a; +SET yb_fetch_size_limit = '50kB'; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) /*+ MergeJoin(t_small t_large) */ SELECT * FROM t_large JOIN t_small ON t_large.a = t_small.a; + +-- #### hash joins #### +SET yb_fetch_row_limit = 1024; +SET yb_fetch_size_limit = 0; +-- the default behaviour sends 6 RPCs per table +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) /*+ HashJoin(t_small t_large) */ SELECT * FROM t_small JOIN t_large ON t_large.a = t_small.a; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) /*+ HashJoin(t_small t_large) */ SELECT * FROM t_large JOIN t_small ON t_large.a = t_small.a; + +SET yb_fetch_row_limit = 0; +-- size limit affects both tables in a HashJoin +SET yb_fetch_size_limit = '500kB'; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) /*+ HashJoin(t_small t_large) */ SELECT * FROM t_small JOIN t_large ON t_large.a = t_small.a; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) /*+ HashJoin(t_small t_large) */ SELECT * FROM t_large JOIN t_small ON t_large.a = t_small.a; +SET yb_fetch_size_limit = '50kB'; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) /*+ HashJoin(t_small t_large) */ SELECT * FROM t_small JOIN t_large ON t_large.a = t_small.a; +EXPLAIN (ANALYZE ON, DIST ON, COSTS OFF) /*+ HashJoin(t_small t_large) */ SELECT * FROM t_large JOIN t_small ON t_large.a = t_small.a; diff --git a/src/test/regress/sql/yb.orig.foreign_data.sql b/src/test/regress/sql/yb.orig.foreign_data.sql new file mode 100644 index 0000000000000..412c255c88c7d --- /dev/null +++ b/src/test/regress/sql/yb.orig.foreign_data.sql @@ -0,0 +1,16 @@ +CREATE FOREIGN DATA WRAPPER dummy; + +CREATE SCHEMA foreign_schema; +CREATE SERVER s0 FOREIGN DATA WRAPPER dummy; + +CREATE TABLE fd_pt2 ( + c1 integer NOT NULL, + c2 text, + c3 date +) PARTITION BY LIST (c1); +CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1) + SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); + +-- TRUNCATE doesn't work on foreign tables, either directly or recursively +TRUNCATE fd_pt2_1; -- ERROR +TRUNCATE fd_pt2; -- ERROR diff --git a/src/test/regress/sql/yb.orig.foreign_key.sql b/src/test/regress/sql/yb.orig.foreign_key.sql new file mode 100644 index 0000000000000..7cceb585deeab --- /dev/null +++ b/src/test/regress/sql/yb.orig.foreign_key.sql @@ -0,0 +1,575 @@ +-- +-- FOREIGN KEY (YB-added tests) +-- +-- TODO: Run this test with REPEATABLE READ isolation level: +-- https://github.com/yugabyte/yugabyte-db/issues/2604 +-- +-- MATCH FULL +-- +-- First test, check and cascade +-- + +CREATE TABLE ITABLE ( ptest1 int, ptest2 text ); +CREATE UNIQUE INDEX ITABLE_IDX ON ITABLE(ptest1); +CREATE TABLE FKTABLE ( ftest1 int REFERENCES ITABLE(ptest1) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int ); + +-- Insert test data into ITABLE +INSERT INTO ITABLE VALUES (1, 'Test1'); +INSERT INTO ITABLE VALUES (2, 'Test2'); +INSERT INTO ITABLE VALUES (3, 'Test3'); +INSERT INTO ITABLE VALUES (4, 'Test4'); +INSERT INTO ITABLE VALUES (5, 'Test5'); + +-- Insert successful rows into FK TABLE +INSERT INTO FKTABLE VALUES (1, 2); +INSERT INTO FKTABLE VALUES (2, 3); +INSERT INTO FKTABLE VALUES (3, 4); +INSERT INTO FKTABLE VALUES (NULL, 1); + +-- Insert a failed row into FK TABLE +INSERT INTO FKTABLE VALUES (100, 2); + +-- Check FKTABLE +SELECT * FROM FKTABLE ORDER BY ftest1; + +-- Delete a row from ITABLE +DELETE FROM ITABLE WHERE ptest1=1; + +-- Check FKTABLE for removal of matched row +SELECT * FROM FKTABLE ORDER BY ftest1; + +-- Update a row from ITABLE +UPDATE ITABLE SET ptest1=1 WHERE ptest1=2; + +-- Check FKTABLE for update of matched row +SELECT * FROM FKTABLE ORDER BY ftest1; + +DROP TABLE FKTABLE; +DROP TABLE ITABLE; + +-- +-- check set NULL and table constraint on multiple columns +-- +CREATE TABLE ITABLE ( ptest1 int, ptest2 int, ptest3 text); +CREATE UNIQUE INDEX ITABLE_IDX ON ITABLE(ptest1, ptest2); + +CREATE TABLE FKTABLE ( ftest1 int, ftest2 int, ftest3 int, CONSTRAINT constrname FOREIGN KEY(ftest1, ftest2) + REFERENCES ITABLE(ptest1, ptest2) MATCH FULL ON DELETE SET NULL ON UPDATE SET NULL); + + +-- Insert test data into ITABLE +INSERT INTO ITABLE VALUES (1, 2, 'Test1'); +INSERT INTO ITABLE VALUES (1, 3, 'Test1-2'); +INSERT INTO ITABLE VALUES (2, 4, 'Test2'); +INSERT INTO ITABLE VALUES (3, 6, 'Test3'); +INSERT INTO ITABLE VALUES (4, 8, 'Test4'); +INSERT INTO ITABLE VALUES (5, 10, 'Test5'); + +-- Insert successful rows into FK TABLE +INSERT INTO FKTABLE VALUES (1, 2, 4); +INSERT INTO FKTABLE VALUES (1, 3, 5); +INSERT INTO FKTABLE VALUES (2, 4, 8); +INSERT INTO FKTABLE VALUES (3, 6, 12); +INSERT INTO FKTABLE VALUES (NULL, NULL, 0); + +-- Insert failed rows into FK TABLE +INSERT INTO FKTABLE VALUES (100, 2, 4); +INSERT INTO FKTABLE VALUES (2, 2, 4); +INSERT INTO FKTABLE VALUES (NULL, 2, 4); +INSERT INTO FKTABLE VALUES (1, NULL, 4); + +-- Check FKTABLE +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Delete a row from ITABLE +DELETE FROM ITABLE WHERE ptest1=1 and ptest2=2; + +-- Check that values of matched row are set to null. +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Delete another row from ITABLE +DELETE FROM ITABLE WHERE ptest1=5 and ptest2=10; + +-- Check FKTABLE (should be no change) +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Update a row from ITABLE +UPDATE ITABLE SET ptest1=1 WHERE ptest1=2; + +-- Check FKTABLE for update of matched row +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Test truncate + +-- Should fail due to dependency on FKTABLE. +TRUNCATE ITABLE; + +-- Should truncate both ITABLE and FKTABLE. +TRUNCATE ITABLE CASCADE; + +-- Check that both tables are now empty. +SELECT * FROM ITABLE ORDER BY ptest1, ptest2; +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2, ftest3; + +DROP TABLE ITABLE CASCADE; +DROP TABLE FKTABLE; + +-- UPDATE with multiple foreign keys -- +CREATE TABLE pk(k INT PRIMARY KEY, x INT UNIQUE, y INT UNIQUE); +CREATE TABLE fk_primary(k INT REFERENCES pk(k)); +CREATE TABLE fk_unique_x(x INT REFERENCES pk(x)); +CREATE TABLE fk_unique_y(y INT REFERENCES pk(y)); +INSERT INTO pk VALUES(1, 10, 100); + +UPDATE pk SET x = 11 WHERE x = 10; +UPDATE pk SET y = 101 WHERE y = 100; + +INSERT INTO fk_unique_x VALUES(11); +INSERT INTO fk_unique_y VALUES(101); + +UPDATE pk SET x = 12 WHERE x = 11; +UPDATE pk SET y = 102 WHERE y = 101; + +DROP TABLE fk_unique_y; +DROP TABLE fk_unique_x; +DROP TABLE fk_primary; +DROP TABLE pk; + +-- DEFERRABLE foreign key constraints +CREATE TABLE parent(k INT PRIMARY KEY); +CREATE TABLE child(k INT PRIMARY KEY, p_fk INT REFERENCES parent INITIALLY DEFERRED); + +BEGIN; +INSERT INTO child VALUES (1, 1); +INSERT INTO child VALUES (2, 2); +COMMIT; -- should fail + +SELECT * FROM child ORDER BY k; + +BEGIN; +INSERT INTO child VALUES (1, 1); +INSERT INTO child VALUES (2, 1); +INSERT INTO child VALUES (3, 2), (4, 3), (5, 4); +INSERT INTO parent VALUES (1); +INSERT INTO parent VALUES (2), (3), (4); +COMMIT; + +SELECT * FROM parent ORDER BY k; +SELECT * FROM child ORDER BY k; + +DELETE FROM child; +DELETE FROM parent; + +CREATE TABLE grand_child(k INT PRIMARY KEY, c_fk INT REFERENCES child INITIALLY DEFERRED); + +BEGIN; +INSERT INTO grand_child VALUES (1, 1), (2, 2), (3, 3); +INSERT INTO child VALUES (2, 2), (3, 3); +INSERT INTO parent VALUES (1), (2); +COMMIT; -- should fail + +BEGIN; +INSERT INTO grand_child VALUES (1, 1), (2, 2), (3, 3); +INSERT INTO child VALUES (2, 2), (3, 3), (1, 1); +INSERT INTO parent VALUES (3), (2), (1); +COMMIT; + +SELECT * FROM parent ORDER BY k; +SELECT * FROM child ORDER BY k; +SELECT * FROM grand_child ORDER BY k; + +BEGIN; +UPDATE grand_child SET c_fk = 4 WHERE k = 1; +INSERT INTO child VALUES (4, 4); +INSERT INTO parent VALUES (5); +COMMIT; -- should fail + +BEGIN; +UPDATE grand_child SET c_fk = 4 WHERE k = 1; +INSERT INTO child VALUES (4, 4); +INSERT INTO parent VALUES (4); +COMMIT; + +SELECT * FROM parent ORDER BY k; +SELECT * FROM child ORDER BY k; +SELECT * FROM grand_child ORDER BY k; + +DROP TABLE grand_child; +DROP TABLE child; +DELETE FROM parent; + +CREATE TABLE child(k INT PRIMARY KEY, p_fk INT REFERENCES parent INITIALLY DEFERRED); +CREATE TABLE grand_child(k INT PRIMARY KEY, c_fk INT REFERENCES child); + +BEGIN; +INSERT INTO child VALUES (1, 1), (2, 2); +INSERT INTO grand_child VALUES (1, 1), (2, 2); +INSERT INTO parent VALUES (1); +COMMIT; -- should fail + +BEGIN; +INSERT INTO child VALUES (1, 1), (2, 2); +INSERT INTO grand_child VALUES (1, 1), (2, 2); +INSERT INTO parent VALUES (1); +INSERT INTO parent VALUES (2); +COMMIT; + +SELECT * FROM parent ORDER BY k; +SELECT * FROM child ORDER BY k; +SELECT * FROM grand_child ORDER BY k; + +DROP TABLE grand_child; +DROP TABLE child; +DROP TABLE parent; + +CREATE TABLE mother(k INT PRIMARY KEY); +CREATE TABLE father(k INT PRIMARY KEY); +CREATE TABLE child( + k INT PRIMARY KEY, + m_fk INT REFERENCES mother INITIALLY DEFERRED, + f_fk INT REFERENCES father INITIALLY DEFERRED); + +BEGIN; +INSERT INTO child VALUES (1, 1, 1), (2, 2, 2), (3, 3, 3); +INSERT INTO mother VALUES (1), (2); +INSERT INTO father VALUES (1), (2), (3); +COMMIT; -- should fail + +BEGIN; +INSERT INTO child VALUES (1, 1, 1), (2, 2, 2); +INSERT INTO mother VALUES (1), (2); +INSERT INTO father VALUES (3), (2), (1); +COMMIT; + +SELECT * FROM child ORDER BY k; +SELECT * FROM mother ORDER by k; +SELECT * FROM father ORDER BY k; + +BEGIN; +UPDATE child SET m_fk = 4, f_fk = 4 WHERE k = 1; +INSERT INTO mother VALUES (5), (4); +INSERT INTO father VALUES (4), (5); +COMMIT; + +SELECT * FROM child ORDER BY k; +SELECT * FROM mother ORDER by k; +SELECT * FROM father ORDER BY k; + +DROP TABLE child; +DELETE FROM mother; +DELETE FROM father; + +CREATE TABLE child( + k INT PRIMARY KEY, + m_fk INT REFERENCES mother, + f_fk INT REFERENCES father INITIALLY DEFERRED); + +BEGIN; +INSERT INTO mother VALUES (1), (2); +INSERT INTO child VALUES (1, 1, 1), (2, 2, 2), (3, 3, 3); -- should fail +COMMIT; + +BEGIN; +INSERT INTO mother VALUES (1), (2); +INSERT INTO child VALUES (1, 1, 1), (2, 2, 2); +INSERT INTO father VALUES (1); +COMMIT; -- should fail + +BEGIN; +INSERT INTO mother VALUES (1), (2); +INSERT INTO child VALUES (1, 1, 1), (2, 2, 2); +INSERT INTO father VALUES (3), (2), (1); +COMMIT; + +SELECT * FROM child ORDER BY k; +SELECT * FROM mother ORDER by k; +SELECT * FROM father ORDER BY k; + +DROP TABLE child; +DELETE FROM mother; +DELETE FROM father; + +CREATE TABLE child( + k INT PRIMARY KEY, + m_fk INT REFERENCES mother INITIALLY DEFERRED, + f_fk INT REFERENCES father); + +BEGIN; +INSERT INTO father VALUES (1), (2); +INSERT INTO child VALUES (1, 1, 1), (2, 2, 2), (3, 3, 3); -- should fail +COMMIT; + +BEGIN; +INSERT INTO father VALUES (1), (2); +INSERT INTO child VALUES (1, 1, 1), (2, 2, 2); +INSERT INTO mother VALUES (1); +COMMIT; -- should fail + +BEGIN; +INSERT INTO father VALUES (1), (2); +INSERT INTO child VALUES (1, 1, 1), (2, 2, 2); +INSERT INTO mother VALUES (3), (2), (1); +COMMIT; + +SELECT * FROM child ORDER BY k; +SELECT * FROM mother ORDER by k; +SELECT * FROM father ORDER BY k; + +BEGIN; +UPDATE child SET f_fk = 4 WHERE k = 1; -- should fail +COMMIT; + +BEGIN; +UPDATE child SET m_fk = 4 WHERE k = 1; +INSERT INTO mother VALUES (4); +COMMIT; + +SELECT * FROM child ORDER BY k; +SELECT * FROM mother ORDER by k; + +DROP TABLE child; +DROP TABLE mother; +DROP TABLE father; + +CREATE TABLE p1(k INT PRIMARY KEY); +CREATE TABLE p2(k INT PRIMARY KEY); +CREATE TABLE p3(k INT PRIMARY KEY); +CREATE TABLE p4(k INT PRIMARY KEY); +CREATE TABLE c( + k INT PRIMARY KEY, + p1_fk INT REFERENCES p1 INITIALLY DEFERRED, + p2_fk INT REFERENCES p2, + p3_fk INT REFERENCES p3 INITIALLY DEFERRED, + p4_fk INT REFERENCES p4); + +BEGIN; +INSERT INTO p2 VALUES (1), (2); +INSERT INTO c VALUES (1, 1, 1, 1, 1); -- should fail +COMMIT; + +BEGIN; +INSERT INTO p2 VALUES (1), (2); +INSERT INTO p4 VALUES (1); +INSERT INTO c VALUES (1, 1, 1, 1, 1); +INSERT INTO p3 VALUES (1); +COMMIT; -- should fail + +BEGIN; +INSERT INTO p2 VALUES (1), (2); +INSERT INTO p4 VALUES (1), (2); +INSERT INTO c VALUES (1, 1, 1, 1, 1), (2, 2, 2, 2, 2); +INSERT INTO p3 VALUES (2), (1); +INSERT INTO p1 VALUES (3), (2), (1); +COMMIT; + +SELECT * FROM p1 ORDER BY k; +SELECT * FROM p2 ORDER BY k; +SELECT * FROM p3 ORDER BY k; +SELECT * FROM p4 ORDER BY k; +SELECT * FROM c ORDER BY k; + +-- check distributed storage counters in case of multiple foreign keys +TRUNCATE c; +EXPLAIN (ANALYZE ON, DIST ON, COMMIT ON, COSTS OFF) +INSERT INTO c VALUES(1, 1, 1, 1, 1); + +CREATE TABLE parent(k INT PRIMARY KEY, v INT UNIQUE); +CREATE TABLE child_1(k INT PRIMARY KEY, v INT REFERENCES parent(k)); +CREATE TABLE child_2(k INT PRIMARY KEY, v INT REFERENCES parent(k)); + +DO $$ +BEGIN + INSERT INTO parent values(1, 10); + INSERT INTO child_1 values(20, 1); + DELETE FROM child_1; + DELETE FROM parent; + INSERT INTO child_2 values(30, 1); -- should fail +END $$; + +DROP TABLE child_1; +DROP TABLE child_2; + +CREATE TABLE child_1(k INT PRIMARY KEY, v INT REFERENCES parent(v)); +CREATE TABLE child_2(k INT PRIMARY KEY, v INT REFERENCES parent(v)); + +DO $$ +BEGIN + INSERT INTO parent values(1, 10); + INSERT INTO child_2 values(30, 10); + DELETE FROM child_2; + DELETE FROM parent; + INSERT INTO child_1 values(20, 10); -- should fail +END $$; + +DROP TABLE child_1; + +CREATE TABLE child_1(k INT PRIMARY KEY, v INT REFERENCES parent(k)); + +INSERT INTO parent SELECT s, 1000000 + s FROM generate_series(1, 10000) AS s; +INSERT INTO child_1 SELECT s, s FROM generate_series(1, 10001) AS s; -- should fail +INSERT INTO child_2 SELECT s, 1000000 + s FROM generate_series(1, 10001) AS s; -- should fail + +INSERT INTO child_1 SELECT s, s FROM generate_series(1, 10000) AS s; +INSERT INTO child_2 SELECT s, 1000000 + s FROM generate_series(1, 10000) AS s; + +UPDATE child_1 SET v = v * 2 WHERE k <= 5001; -- should fail +UPDATE child_2 SET v = (v - 1000000) * 2 + 1000000 WHERE k <= 5001; -- should fail + +UPDATE child_1 SET v = v * 2 WHERE k < 5001; +UPDATE child_2 SET v = (v - 1000000) * 2 + 1000000 WHERE k < 5001; + +-- UNIQUE NULLS NOT DISTINCT +CREATE TABLE pk(a INT PRIMARY KEY, b INT, c INT, UNIQUE NULLS NOT DISTINCT (b, c)); + +CREATE TABLE fk_simple(b INT, c INT, FOREIGN KEY (b,c) REFERENCES pk(b, c) MATCH SIMPLE); +INSERT INTO fk_simple VALUES (NULL, 1); +INSERT INTO fk_simple VALUES (1, 1); +INSERT INTO fk_simple VALUES (NULL, NULL); + +CREATE TABLE fk_full(b INT, c INT, FOREIGN KEY (b,c) REFERENCES pk(b, c) MATCH FULL); +INSERT INTO fk_full VALUES (NULL, 1); +INSERT INTO fk_full VALUES (1, 1); +INSERT INTO fk_full VALUES (NULL, NULL); + +DROP TABLE pk, fk_simple, fk_full; + +-- Test foreign key on unique index such that column orders in fk and unique constraints differ. +CREATE TABLE pk(id INT, name INT, add INT, PRIMARY KEY (id, name, add), UNIQUE (name, id)); +CREATE TABLE fk(id INT, name INT, FOREIGN KEY(id, name) REFERENCES pk(id, name)); +INSERT INTO pk VALUES (1, 500, 1000); +INSERT INTO fk VALUES (1, 500); +INSERT INTO fk VALUES (500, 1); -- should fail +SELECT * from fk; +DROP TABLE pk, fk; + +--- Test foreign key referencing partitioned table + +-- Base case +CREATE TABLE pk(id INT PRIMARY KEY) PARTITION BY RANGE(id); +CREATE TABLE pk_1_100 PARTITION OF pk FOR VALUES FROM (1) TO (100); +CREATE TABLE pk_101_200 PARTITION OF pk FOR VALUES FROM (101) TO (200); +CREATE TABLE fk(id INT REFERENCES pk(id)); + +INSERT INTO pk VALUES (1), (105); +INSERT INTO fk VALUES (1); +INSERT INTO fk VALUES (105); +INSERT INTO fk VALUES (110); -- should fail +INSERT INTO fk VALUES (200); -- should fail +SELECT * FROM fk ORDER BY id; + +DROP TABLE pk, fk; + +-- Foreign key constraint on unique index +CREATE TABLE pk(a INT, b INT UNIQUE, PRIMARY KEY (a, b)) PARTITION BY RANGE(b); +CREATE TABLE pk_1_100 PARTITION OF pk FOR VALUES FROM (1) TO (100); +CREATE TABLE pk_101_200 PARTITION OF pk FOR VALUES FROM (101) TO (200); +CREATE TABLE fk(b INT REFERENCES pk(b)); + +INSERT INTO pk VALUES (1, 1), (105, 105); +INSERT INTO fk VALUES (1); +INSERT INTO fk VALUES (105); +INSERT INTO fk VALUES (110); -- should fail +INSERT INTO fk VALUES (200); -- should fail +SELECT * FROM fk ORDER BY b; + +DROP TABLE pk, fk; + +-- PK root and leaf partition have different column orders +CREATE TABLE pk(a INT, b INT, c INT, d INT, PRIMARY KEY(a, c)) PARTITION BY RANGE(a); +CREATE TABLE pk_1_100(a INT NOT NULL, c INT NOT NULL, d INT, b INT); +ALTER TABLE pk ATTACH PARTITION pk_1_100 FOR VALUES FROM (1) TO (100); +CREATE TABLE fk(a INT, c INT, FOREIGN KEY (a, c) REFERENCES pk(a, c)); + +INSERT INTO pk VALUES (1, 100, 20, 150), (1, 100, 21, 150); +INSERT INTO fk VALUES (1, 20); +INSERT INTO fk VALUES (150, 20); -- should fail +SELECT * FROM fk; + +CREATE TABLE fk2(a INT, c INT, FOREIGN KEY (a, c) REFERENCES pk(a, c) DEFERRABLE INITIALLY DEFERRED); +INSERT INTO fk2 VALUES (1, 20), (1, 21); +SELECT * FROM fk2 ORDER BY a, c; + +DROP TABLE pk, fk, fk2; + +-- Using index, PK root and leaf partition have different column orders +CREATE TABLE pk(a INT, b INT, c INT, d INT, PRIMARY KEY(a, b), UNIQUE(a, c)) PARTITION BY RANGE(a); +CREATE TABLE pk_1_100(a INT NOT NULL, c INT NOT NULL, d INT, b INT NOT NULL); +ALTER TABLE pk ATTACH PARTITION pk_1_100 FOR VALUES FROM (1) TO (100); +CREATE TABLE fk(b INT, d INT, a INT, c INT, FOREIGN KEY (a, c) REFERENCES pk(a, c)); + +INSERT INTO pk VALUES (1, 100, 20, 150); +INSERT INTO fk(a, c) VALUES (1, 20); +INSERT INTO fk(a, c) VALUES (1, 100); -- should fail +INSERT INTO fk(a, c) VALUES (150, 20); -- should fail +SELECT * FROM fk; + +DROP TABLE pk, fk; + +-- Both PK and FK are partitioned +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES FROM (0) TO (1000) PARTITION BY RANGE (a); +CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES FROM (0) TO (10); +CREATE TABLE fk (a int) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (0) TO (750) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (0) TO (10); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk; +INSERT into fk VALUES (1); -- should fail +INSERT into fk VALUES (20); -- should fail +INSERT into pk values (5); +INSERT into fk values (5); +SELECT * FROM fk; + +DROP TABLE pk, fk; + +-- Multiple partitioned PKs referenced +CREATE TABLE pk(a INT, b INT UNIQUE, PRIMARY KEY (a, b)) PARTITION BY RANGE(b); +CREATE TABLE pk_1_100 PARTITION OF pk FOR VALUES FROM (1) TO (100); +CREATE TABLE pk_101_200 PARTITION OF pk FOR VALUES FROM (101) TO (200); + +CREATE TABLE pk2(a INT, b INT UNIQUE, PRIMARY KEY (a, b)) PARTITION BY RANGE(b); +CREATE TABLE pk2_1_100 PARTITION OF pk2 FOR VALUES FROM (1) TO (100); +CREATE TABLE pk2_101_200 PARTITION OF pk2 FOR VALUES FROM (101) TO (200); + +CREATE TABLE fk(b INT REFERENCES pk(b), b2 INT REFERENCES pk2(b)); + +INSERT INTO pk VALUES (1, 1); +INSERT INTO pk2 VALUES (1, 1); +INSERT INTO fk VALUES (1, 1); +INSERT INTO fk VALUES (10, 1); -- should fail +INSERT INTO fk VALUES (1, 10); -- should fail +SELECT * from fk; + +DROP TABLE pk, pk2, fk; + +-- Test foreign key constraint validation at the time of constraint creation +CREATE TABLE pk(a INT, b INT, c INT, d INT, PRIMARY KEY(a, c)) PARTITION BY RANGE(a); +CREATE TABLE pk_1_100 PARTITION OF pk FOR VALUES FROM (1) TO (100); +INSERT INTO pk VALUES (1, 100, 20, 150); + +CREATE TABLE fk(a INT, c INT); +INSERT INTO fk VALUES (1, 20); +ALTER TABLE fk ADD FOREIGN KEY (a, c) REFERENCES pk(a, c); + +DROP TABLE pk, fk; + +CREATE TABLE pk(a INT, b INT, c INT, d INT, PRIMARY KEY(a, c)) PARTITION BY RANGE(a); +CREATE TABLE pk_1_100(a INT NOT NULL, c INT NOT NULL, d INT, b INT); +ALTER TABLE pk ATTACH PARTITION pk_1_100 FOR VALUES FROM (1) TO (100); +INSERT INTO pk VALUES (1, 100, 20, 150); + +CREATE TABLE fk(a INT, c INT); +INSERT INTO fk VALUES (1, 20); +ALTER TABLE fk ADD FOREIGN KEY (a, c) REFERENCES pk(a, c); + +DROP TABLE pk, fk; + +-- test updating a subset of foreign constraint keys. +CREATE TABLE pk(a INT, b INT, PRIMARY KEY (a, b)); +CREATE TABLE fk(a INT, b INT, FOREIGN KEY(a, b) REFERENCES pk); +INSERT INTO pk VALUES (1, 1); +INSERT INTO fk VALUES (1, 1); +UPDATE fk set a = a + 1; -- should fail +INSERT INTO pk VALUES (5, 1); +UPDATE fk set a = a + 4; -- should pass +SELECT * from fk; +DROP TABLE pk, fk; diff --git a/src/test/regress/sql/yb.orig.function.sql b/src/test/regress/sql/yb.orig.function.sql new file mode 100644 index 0000000000000..37e0e34da9912 --- /dev/null +++ b/src/test/regress/sql/yb.orig.function.sql @@ -0,0 +1,58 @@ +CREATE USER regress_alter_generic_user3; +CREATE USER regress_alter_generic_user2; +CREATE USER regress_alter_generic_user1 IN ROLE regress_alter_generic_user3; +CREATE SCHEMA alt_nsp1; +GRANT ALL ON SCHEMA alt_nsp1 TO PUBLIC; +SET search_path = alt_nsp1, public; + +--- +--- Verify yb_db_admin can CREATE and DROP functions +--- +CREATE FUNCTION other_func(int) RETURNS int LANGUAGE sql + AS 'SELECT $1 + 1'; +SET SESSION AUTHORIZATION yb_db_admin; +CREATE FUNCTION admin_func(int) RETURNS int LANGUAGE sql + AS 'SELECT $1 + 1'; +CREATE FUNCTION admin_func_leakproof(int) RETURNS int LANGUAGE sql -- not allowed + LEAKPROOF AS 'SELECT $1 + 1'; +CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$ + BEGIN + RETURN i + 1; + END; +$$ LANGUAGE plpgsql; +CREATE FUNCTION language_func() RETURNS uuid -- C functions aren't allowed + LANGUAGE c STRICT PARALLEL SAFE + AS '$libdir/uuid-ossp', 'uuid_generate_v1'; +DROP FUNCTION admin_func(int); +DROP FUNCTION other_func(int); +DROP FUNCTION language_func(); -- does not exist +RESET SESSION AUTHORIZATION; + +--- +--- Validate yb_db_admin role can ALTER function +--- +CREATE FUNCTION alt_func1(int) RETURNS int LANGUAGE sql + AS 'SELECT $1 + 1'; +SET SESSION AUTHORIZATION yb_db_admin; +ALTER FUNCTION alt_func1(int) OWNER TO regress_alter_generic_user1; +ALTER FUNCTION alt_func1(int) RENAME TO func_renamed; +ALTER FUNCTION func_renamed(int) SET SCHEMA alt_nsp1; +ALTER FUNCTION func_renamed(int) LEAKPROOF; -- not allowed +ALTER FUNCTION func_renamed(int) NOT LEAKPROOF; +-- validate regress_alter_generic_user2 can operate on function +SET SESSION AUTHORIZATION regress_alter_generic_user1; +ALTER FUNCTION func_renamed(int) OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER FUNCTION func_renamed(int) OWNER TO regress_alter_generic_user3; -- OK +ALTER FUNCTION func_renamed(int) RENAME TO func_renamed2; +ALTER FUNCTION func_renamed2(int) SET SCHEMA alt_nsp1; +ALTER FUNCTION func_renamed2(int) LEAKPROOF; -- not allowed +ALTER FUNCTION func_renamed2(int) NOT LEAKPROOF; + +--- +--- Clean up +--- +RESET SESSION AUTHORIZATION; +DROP SCHEMA alt_nsp1 CASCADE; +DROP USER regress_alter_generic_user1; +DROP USER regress_alter_generic_user2; +DROP USER regress_alter_generic_user3; diff --git a/src/test/regress/sql/yb.orig.functional_index_multiple_column.sql b/src/test/regress/sql/yb.orig.functional_index_multiple_column.sql new file mode 100644 index 0000000000000..6e8d49bc2cb3e --- /dev/null +++ b/src/test/regress/sql/yb.orig.functional_index_multiple_column.sql @@ -0,0 +1,152 @@ +CREATE TABLE t AS SELECT x, x AS y, x AS z FROM generate_series(1, 10) x; + +CREATE INDEX t_hcx_hash_y_hash_idx ON t ((yb_hash_code(x), y) HASH); +-- Query #2 should use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y > 4; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y IN (5, 6); -- this one uses index successfully on master (because it has recheck) +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y > 4; + +DROP INDEX t_hcx_hash_y_hash_idx; +CREATE INDEX t_hcx_asc_y_asc_idx ON t (yb_hash_code(x) ASC, y ASC); +-- These should all use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y > 4; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y > 4; + +DROP INDEX t_hcx_asc_y_asc_idx; +CREATE INDEX t_hcx_hash_y_asc_idx ON t (yb_hash_code(x) HASH, y ASC); +-- Query #2 and #4 should use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y > 4; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y > 4; + +DROP INDEX t_hcx_hash_y_asc_idx; +CREATE INDEX t_y_hash_hcx_asc_idx ON t (y HASH, yb_hash_code(x) ASC); +-- Query #2 and #3 should use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y > 4; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y > 4; + +DROP INDEX t_y_hash_hcx_asc_idx; +CREATE INDEX t_cbrtx_hash_y_hash_idx ON t ((cbrt(x), y) HASH); +-- Query #2 should use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) < 1.2 AND y < 2; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) = 1 AND y = 1; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) < 1.2 AND y = 1; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) = 1 AND y < 2; + +DROP INDEX t_cbrtx_hash_y_hash_idx; +CREATE INDEX t_cbrtx_asc_y_asc_idx ON t (cbrt(x) ASC, y ASC); +-- These should all use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) < 1.2 AND y < 2; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) = 1 AND y = 1; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) < 1.2 AND y = 1; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) = 1 AND y < 2; + +DROP INDEX t_cbrtx_asc_y_asc_idx; +CREATE INDEX t_cbrtx_hash_y_asc_idx ON t (cbrt(x) HASH, y ASC); +-- Query #2 and #4 should use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) < 1.2 AND y < 2; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) = 1 AND y = 1; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) < 1.2 AND y = 1; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) = 1 AND y < 2; + +DROP INDEX t_cbrtx_hash_y_asc_idx; +CREATE INDEX t_y_hash_cbrtx_asc_idx ON t (y HASH, cbrt(x) ASC); +-- Query #2 and #3 should use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) < 1.2 AND y < 2; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) = 1 AND y = 1; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) < 1.2 AND y = 1; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT cbrt(x), y FROM t WHERE cbrt(x) = 1 AND y < 2; + +DROP INDEX t_y_hash_cbrtx_asc_idx; +CREATE INDEX t_x_hash_y_hash_idx ON t ((x, y) HASH); +-- Query #2 should be able to use the index, but doesn't (even before this diff) +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y > 4; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y > 4; + +DROP INDEX t_x_hash_y_hash_idx; +CREATE INDEX t_x_hash_y_asc_idx ON t (x HASH, y ASC); +-- All should use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y > 4; +-- EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y IN (5, 6); -- TODO: #17043 +-- EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y IN (5, 6); -- TODO: #17043 +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y > 4; + +DROP INDEX t_x_hash_y_asc_idx; +CREATE INDEX t_y_hash_x_asc_idx ON t (y HASH, x ASC); +-- Query #2 and #3 should use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y > 4; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y > 4; + +DROP INDEX t_y_hash_x_asc_idx; +CREATE INDEX t_x_asc_y_asc_idx ON t (x ASC, y ASC); +-- These should all use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y > 4; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y > 4; + +DROP INDEX t_x_asc_y_asc_idx; +CREATE INDEX t_x_hash_y_hashcode ON t (x HASH, yb_hash_code(y)); +-- These should all be able to use the index, with queries #1-4 needing to filter to satisfy the y constraint +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y > 4; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y > 4; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), yb_hash_code(y) FROM t WHERE yb_hash_code(x) < 4000 AND yb_hash_code(y) > 4; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), yb_hash_code(y) FROM t WHERE yb_hash_code(x) = 2675 AND yb_hash_code(y) = 2675; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), yb_hash_code(y) FROM t WHERE yb_hash_code(x) < 4000 AND yb_hash_code(y) = 2675; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), yb_hash_code(y) FROM t WHERE yb_hash_code(x) = 2675 AND yb_hash_code(y) > 4; + +DROP INDEX t_x_hash_y_hashcode; +CREATE INDEX t_x_hash_yz_hashcode ON t (x HASH, yb_hash_code(y, z)); +-- These should all be able to use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675; +-- These should be all be able to use the index, with a filter to satisfy the y constraint +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y > 4; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) < 4000 AND y IN (5, 6); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y > 4; +-- These should all be able to use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), yb_hash_code(y, z) FROM t WHERE yb_hash_code(x) > 40000 AND yb_hash_code(y, z) < 2500; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), yb_hash_code(y, z) FROM t WHERE yb_hash_code(x) = 40623 AND yb_hash_code(y, z) = 2181; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), yb_hash_code(y, z) FROM t WHERE yb_hash_code(x) > 40000 AND yb_hash_code(y, z) < 2500; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x), yb_hash_code(y, z) FROM t WHERE yb_hash_code(x) = 40623 AND yb_hash_code(y, z) = 2181; + +DROP INDEX t_x_hash_yz_hashcode; +CREATE INDEX t_xy_yb_hash_code ON t (yb_hash_code(x, y)); +-- Of the following, only the query with an equality clause on yb_hash_code(x, y) should be able to use the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x, x) FROM t WHERE yb_hash_code(x, x) = 2181; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x, x) FROM t WHERE yb_hash_code(x, x) < 2500; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x, y) FROM t WHERE yb_hash_code(x, y) = 2181; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x, y) FROM t WHERE yb_hash_code(x, y) < 2500; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(y, x) FROM t WHERE yb_hash_code(y, x) = 2181; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(y, x) FROM t WHERE yb_hash_code(y, x) < 2500; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(y, y) FROM t WHERE yb_hash_code(y, y) = 2181; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(y, y) FROM t WHERE yb_hash_code(y, y) < 2500; + +DROP INDEX t_xy_yb_hash_code; +-- In general, a query on yb_hash_code(x) < 4000 could use either the (x HASH) +-- column of the index or the (yb_hash_code(x) ASC) part of the index. +-- We want to ensure that both are allowed, and the presence of one does not +-- forbid the use of the other. In this index, even though we have yb_hash_code(x|y) columns, +-- we won't use them because the index is ordered by (x HASH, y ASC) first. +CREATE INDEX t_complex_idx ON t (y HASH, y ASC, yb_hash_code(x) ASC, yb_hash_code(y) ASC); +-- These queries will not use the index because yb_hash_code(x) is too deep in the index +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x) FROM t WHERE yb_hash_code(x) < 4000; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(x) FROM t WHERE yb_hash_code(x) = 2675; +-- These queries will use the index because the (y HASH) column can answer the query. +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(y) FROM t WHERE yb_hash_code(y) < 4000; +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT yb_hash_code(y) FROM t WHERE yb_hash_code(y) = 2675; + +DROP TABLE t; diff --git a/src/test/regress/sql/yb.orig.functional_indexes.sql b/src/test/regress/sql/yb.orig.functional_indexes.sql new file mode 100644 index 0000000000000..9d07d59e4b36c --- /dev/null +++ b/src/test/regress/sql/yb.orig.functional_indexes.sql @@ -0,0 +1,229 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE)' + +CREATE TABLE test_with_pk (a INT PRIMARY KEY, h INT); +INSERT INTO test_with_pk SELECT x, x FROM generate_series(1, 10) x; + +-- This test verifies: +-- * yb_hash_code range queries +-- * yb_hash_code equality queries +-- * cbrt range queries +-- * cbrt equality queries +-- on the following cases +-- * on the primary key +-- * on a regular column +-- * on a regular column that has a hash index +-- * on a regular column that has an asc index +-- * on a regular column that has a hash index on yb_hash_code and a hash +-- index on cbrt +-- * on a regular column that has an asc index on yb_hash_code and an asc +-- index on cbrt +-- cbrt was selected as an example of a simple, standard function, not because +-- of any special attributes. For each of these 4 * 6 cases, they are tested +-- with seqscans disabled and with seqscans enabled. This documents their +-- preferred plan, and also ensures that if the query planner allows them to +-- use an index, the result is still correct. + +---- PRIMARY KEY ---- +SET enable_seqscan = false; + +-- we can pushdown the yb_hash_code call to the pk index +\set query ':P SELECT yb_hash_code(a) FROM test_with_pk WHERE yb_hash_code(a) < 4000;' +\i :iter_P2 + +-- we can pushdown the yb_hash_code call to the pk index +\set query ':P SELECT yb_hash_code(a) FROM test_with_pk WHERE yb_hash_code(a) = 2675;' +\i :iter_P2 + +\set query ':P SELECT cbrt(a) FROM test_with_pk WHERE cbrt(a) < 1.2;' +\i :iter_P2 + +\set query ':P SELECT cbrt(a) FROM test_with_pk WHERE cbrt(a) = 1;' +\i :iter_P2 + +SET enable_seqscan = true; + +-- we can pushdown the yb_hash_code call to the pk index +\set query ':P SELECT yb_hash_code(a) FROM test_with_pk WHERE yb_hash_code(a) < 4000;' +\i :iter_P2 + +-- we can pushdown the yb_hash_code call to the pk index +\set query ':P SELECT yb_hash_code(a) FROM test_with_pk WHERE yb_hash_code(a) = 2675;' +\i :iter_P2 + +\set query ':P SELECT cbrt(a) FROM test_with_pk WHERE cbrt(a) < 1.2;' +\i :iter_P2 + +\set query ':P SELECT cbrt(a) FROM test_with_pk WHERE cbrt(a) = 1;' +\i :iter_P2 + +---- REGULAR COLUMN ---- +SET enable_seqscan = false; +-- for these queries, we use seqscan (even when disabled) +-- because there is no other option + +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) < 4000;' +\i :iter_P2 + +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) = 2675;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) < 1.2;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) = 1;' +\i :iter_P2 + +---- HASH INDEX ON THE COLUMN ---- +CREATE INDEX t_b_hash_idx ON test_with_pk(h); +SET enable_seqscan = false; + +-- we can pushdown yb_hash_code(h) on a hash index on h because the index is ordered by hash code +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) < 4000;' +\i :iter_P2 + +-- we can pushdown yb_hash_code(h) on a hash index on h because the index is ordered by hash code +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) = 2675;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) < 1.2;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) = 1;' +\i :iter_P2 + +SET enable_seqscan = true; + +-- we prefer using the index over a seq scan +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) < 4000;' +\i :iter_P2 + +-- we prefer using the index over a seq scan +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) = 2675;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) < 1.2;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) = 1;' +\i :iter_P2 + +---- ASC INDEX ON THE COLUMN ---- +DROP INDEX t_b_hash_idx; +CREATE INDEX t_b_asc_idx ON test_with_pk(h ASC); + +SET enable_seqscan = false; + +-- cannot pushdown a range yb_hash_code(h) clause on a ascending index on h because the index is not ordered by hash code +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) < 4000;' +\i :iter_P2 + +-- cannot pushdown an equality yb_hash_code(h) clause on a ascending index on h because the index is not ordered by hash code +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) = 2675;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) < 1.2;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) = 1;' +\i :iter_P2 + +SET enable_seqscan = true; + +-- we prefer filtering rows from the seq scan instead of the index scan +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) < 4000;' +\i :iter_P2 + +-- we prefer filtering rows from the seq scan instead of the index scan +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) = 2675;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) < 1.2;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) = 1;' +\i :iter_P2 + +---- HASH INDEX ON yb_hash_code(h), HASH INDEX ON cbrt(h) ---- +DROP INDEX t_b_asc_idx; +CREATE INDEX t_b_hash_code_idx ON test_with_pk(yb_hash_code(h)); +CREATE INDEX t_b_cbrt_idx ON test_with_pk(cbrt(h)); + +SET enable_seqscan = false; + +-- cannot use a hash index for a range clause on yb_hash_code(h) because it's ordered by yb_hash_code(yb_hash_code(h)) +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) < 4000;' +\i :iter_P2 + +-- can use a hash index on yb_hash_code for a yb_hash_code equality clause +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) = 2675;' +\i :iter_P2 + +-- cannot use a hash index on cbrt for a cbrt range clause +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) < 1.2;' +\i :iter_P2 + +-- can use a hash index on cbrt for a cbrt equality clause +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) = 1;' +\i :iter_P2 + +SET enable_seqscan = true; + +-- cannot use a hash index for a range clause on yb_hash_code(h) because it's ordered by yb_hash_code(yb_hash_code(h)) +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) < 4000;' +\i :iter_P2 + +-- can use a hash index on yb_hash_code for a yb_hash_code equality clause +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) = 2675;' +\i :iter_P2 + +-- cannot use a hash index on cbrt for a cbrt range clause +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) < 1.2;' +\i :iter_P2 + +-- can use a hash index on cbrt for a cbrt equality clause +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) = 1;' +\i :iter_P2 + +---- ASC INDEX ON yb_hash_code(h), ASC INDEX on cbrt(h) ---- +DROP INDEX t_b_hash_code_idx; +DROP INDEX t_b_cbrt_idx; +CREATE INDEX t_b_hash_code_asc_idx ON test_with_pk(yb_hash_code(h) ASC); +CREATE INDEX t_b_cbrt_asc_idx ON test_with_pk(cbrt(h) ASC); + +SET enable_seqscan = false; + +-- can use the ascending index on yb_hash_code for an equality clause +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) < 4000;' +\i :iter_P2 + +-- can use the ascending index on yb_hash_code for an equality clause +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) = 2675;' +\i :iter_P2 + +-- can use the ascending index on cbrt for an equality clause +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) < 1.2;' +\i :iter_P2 + +-- can use the ascending index on cbrt for an equality clause +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) = 1;' +\i :iter_P2 + +SET enable_seqscan = true; +-- for each case, we prefer to use the indexes instead of a seq scan + +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) < 4000;' +\i :iter_P2 + +\set query ':P SELECT yb_hash_code(h) FROM test_with_pk WHERE yb_hash_code(h) = 2675;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) < 1.2;' +\i :iter_P2 + +\set query ':P SELECT cbrt(h) FROM test_with_pk WHERE cbrt(h) = 1;' +\i :iter_P2 diff --git a/src/test/regress/sql/yb.orig.generated.sql b/src/test/regress/sql/yb.orig.generated.sql new file mode 100644 index 0000000000000..ded99a0a89f08 --- /dev/null +++ b/src/test/regress/sql/yb.orig.generated.sql @@ -0,0 +1,192 @@ +DROP TABLE IF EXISTS t0_gen; +DROP TABLE IF EXISTS t1_gen; +CREATE TABLE t0_gen (k INT PRIMARY KEY, v1 INT, v2 INT, v1_gen TEXT GENERATED ALWAYS AS ('Text ' || v1::text) STORED); +CREATE TABLE t1_gen (k INT PRIMARY KEY, k_gen TEXT GENERATED ALWAYS AS ('Text ' || k::text) STORED, v1 INT, v2 INT, v3 INT, v1_v2_gen INT GENERATED ALWAYS AS (v1 + v2) STORED); +INSERT INTO t0_gen (k, v1, v2) (SELECT i, i, i FROM generate_series(1, 10) AS i); +INSERT INTO t1_gen (k, v1, v2, v3) (SELECT i, i, i, NULL FROM generate_series(1, 10) AS i); +SELECT * FROM t0_gen ORDER BY k; +SELECT * FROM t1_gen ORDER BY k; + +-- Updates that don't involve generated columns + columns they depend on should +-- eligible for single-row optimization or expression pushdown. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t0_gen SET v2 = v2 + 1 WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t0_gen SET v2 = (random() * 10)::int WHERE k = 2; +-- Updates involving generated columns should not be eligible for single-row optimization +-- and should not be pushed down. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t0_gen SET v1 = v1 + 1 WHERE k = 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t0_gen SET v1 = v2 + 1, v2 = v1 + 1 WHERE k = 4; +SELECT * FROM t0_gen WHERE k NOT IN (2) ORDER BY k; + +CREATE INDEX NONCONCURRENTLY v1_gen_idx ON t0_gen (v1_gen) WHERE k > 100; +-- Modifytable operations must correctly work with indexes on generated columns. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t0_gen VALUES (101, 101, 101), (102, 102, 102); +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t0_gen (k, v2) VALUES (103, 103); +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t0_gen (k, v1) VALUES (104, 104); +SELECT * FROM t0_gen WHERE k >= 100 ORDER BY k; +EXPLAIN (COSTS OFF) /*+ IndexOnlyScan(t0_gen v1_gen_idx) */ SELECT COUNT(v1_gen) FROM t0_gen WHERE k > 100; +-- COUNT(col) will skip over NULL values. +/*+ IndexOnlyScan(t0_gen v1_gen_idx) */ SELECT COUNT(v1_gen) FROM t0_gen WHERE k > 100; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t0_gen SET v1 = v1 + 1, v2 = v2 + 1 WHERE k > 100; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t0_gen SET v2 = v2 - 1 WHERE k > 100; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t0_gen SET k = k + 10 WHERE k > 100; +SELECT * FROM t0_gen WHERE k > 100 ORDER BY k; +/*+ IndexOnlyScan(t0_gen v1_gen_idx) */ SELECT COUNT(v1_gen) FROM t0_gen WHERE k > 100; +EXPLAIN (ANALYZE, DIST, COSTS OFF) DELETE FROM t0_gen WHERE k > 110 AND k < 112; +EXPLAIN (ANALYZE, DIST, COSTS OFF) DELETE FROM t0_gen WHERE v1_gen = 'Text 103'; +EXPLAIN (ANALYZE, DIST, COSTS OFF) DELETE FROM t0_gen WHERE v1_gen IS NULL; +SELECT * FROM t0_gen WHERE k > 100 ORDER BY k; +/*+ IndexOnlyScan(t0_gen v1_gen_idx) */ SELECT COUNT(v1_gen) FROM t0_gen WHERE k > 100; + +-- Modifytable operations must correctly work with multi-column indexes involving generated columns. +CREATE INDEX NONCONCURRENTLY v2_v1_gen_idx ON t0_gen (v2, v1_gen) WHERE k > 200; +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t0_gen VALUES (201, 201, 201), (202, 202, 202); +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t0_gen (k, v2) VALUES (203, 203); +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t0_gen (k, v1) VALUES (204, 204); +SELECT * FROM t0_gen WHERE k > 200 ORDER BY k; +EXPLAIN (COSTS OFF) /*+ IndexOnlyScan(t0_gen v2_v1_gen_idx) */ SELECT COUNT(v1_gen) FROM t0_gen WHERE k > 200; +/*+ IndexOnlyScan(t0_gen v2_v1_gen_idx) */ SELECT COUNT(v1_gen) FROM t0_gen WHERE k > 200; +-- The following query updates both indexes on the table. Rows corresponding to k = 201, 202, 204 +-- need entries in both indexes to be deleted and reinserted, while rows corresponding to k = 203 +-- needs only the entry in v2_v1_gen_idx to be updated because the entry in v1_gen_idx evaluates to +-- NULL both before and after the update. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t0_gen SET v1 = v1 + 1, v2 = v2 + 1 WHERE k > 200; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t0_gen SET v2 = v2 - 1 WHERE k > 200; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t0_gen SET v1 = v1 + 1 WHERE k = 204; +SELECT * FROM t0_gen WHERE k > 200 ORDER BY k; +/*+ IndexOnlyScan(t0_gen v2_v1_gen_idx) */ SELECT COUNT(v1_gen) FROM t0_gen WHERE k > 200; +EXPLAIN (ANALYZE, DIST, COSTS OFF) DELETE FROM t0_gen WHERE k = 201; +EXPLAIN (ANALYZE, DIST, COSTS OFF) DELETE FROM t0_gen WHERE k > 200 AND (v1 < 203 OR v1 IS NULL); +EXPLAIN (ANALYZE, DIST, COSTS OFF) DELETE FROM t0_gen WHERE k > 100 AND v2 < 203; +SELECT * FROM t0_gen WHERE k > 200 ORDER BY k; +/*+ IndexOnlyScan(t0_gen v2_v1_gen_idx) */ SELECT COUNT(v1_gen) FROM t0_gen WHERE k > 200; +/*+ IndexOnlyScan(t0_gen v1_gen_idx) */ SELECT COUNT(v1_gen) FROM t0_gen WHERE k > 100; + +-- Update operations involving a generated column that depends on multiple columns. +-- If the update operations involves any of the columns that a generated column +-- depends on, then the query should not be eligible for single-row optimization +-- or expression pushdown on those columns. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v1 = v1 + 1 WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v2 = 2 WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v1 = v2 + 1, v2 = v1 + 1 WHERE k = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v3 = 2 WHERE k = 2; +SELECT * FROM t1_gen WHERE k IN (1, 2) ORDER BY k; + +CREATE INDEX NONCONCURRENTLY t1_gen_v1_v2_gen ON t1_gen (v1_v2_gen); +CREATE INDEX NONCONCURRENTLY ON t1_gen (k_gen) WHERE k < 100; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v1 = v2 + 1, v2 = v1 + 1 WHERE k = 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v1 = v2, v3 = v3 + 1 WHERE k = 4; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v1 = k + 1 WHERE k = 5; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET k = k + 10 WHERE k = 6; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET k = k + 10, v1 = v1 + 5, v2 = v2 + 5 WHERE k = 7; +SELECT * FROM t1_gen ORDER BY k; + +-- Generated columns should work well with BEFORE UPDATE triggers. +CREATE OR REPLACE FUNCTION increment_v1() RETURNS TRIGGER +LANGUAGE PLPGSQL AS $$ +BEGIN + NEW.v1 = NEW.v1 + 1; + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION increment_v2() RETURNS TRIGGER +LANGUAGE PLPGSQL AS $$ +BEGIN + NEW.v2 = NEW.v2 + 1; + RETURN NEW; +END; +$$; + +-- Naming convention: zz_ prefix to ensure that the function is executed last. +CREATE OR REPLACE FUNCTION zz_gen_col_notice() RETURNS TRIGGER +LANGUAGE PLPGSQL AS $$ +BEGIN + RAISE NOTICE '% ROW trigger on generated column invoked', TG_argv[0]; + RETURN NEW; +END; +$$; + +CREATE TRIGGER t1_gen_v1_trigger BEFORE UPDATE ON t1_gen FOR EACH ROW EXECUTE FUNCTION increment_v1(); +BEGIN; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v3 = v3 + 1 WHERE k = 8; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v1 = v1 + 1, v2 = v2 + 1 WHERE k = 9; +SELECT * FROM t1_gen WHERE k > 7 ORDER BY k; +ROLLBACK; + +-- Chaining of triggers with generated columns involved. +CREATE TRIGGER t1_gen_v2_trigger BEFORE UPDATE OF v2 ON t1_gen FOR EACH ROW EXECUTE FUNCTION increment_v2(); +BEGIN; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v2 = v2, v3 = v3 + 1 WHERE k = 8; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v1 = v1 + 1, v2 = v2 + 1 WHERE k = 9; +SELECT * FROM t1_gen WHERE k > 7 ORDER BY k; +ROLLBACK; + +-- Triggers should fire on generated columns. +CREATE TRIGGER t1_gen_before_gen BEFORE UPDATE OF v1_v2_gen ON t1_gen FOR EACH ROW EXECUTE FUNCTION zz_gen_col_notice('BEFORE'); +CREATE TRIGGER t1_gen_after_gen AFTER UPDATE OF v1_v2_gen ON t1_gen FOR EACH ROW EXECUTE FUNCTION zz_gen_col_notice('AFTER'); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t1_gen SET v3 = v3 + 1 WHERE k = 10; +SELECT * FROM t1_gen WHERE k > 7 ORDER BY k; + +-- Partitioned tables +CREATE TABLE part ( + amount NUMERIC, + double_amount NUMERIC GENERATED ALWAYS AS (amount * 2) STORED +) partition by range(amount); + +CREATE TABLE part_1_100 partition of part for values from (1) to (100); +CREATE TABLE part_2_200 partition of part for values from (101) to (200); +INSERT INTO part VALUES (1), (101); +INSERT INTO part_1_100 VALUES (2), (3); +INSERT INTO part_2_200 VALUES (102), (103); +SELECT * FROM part ORDER BY amount; +SELECT * FROM part_1_100 ORDER BY amount; +SELECT * FROM part_2_200 ORDER BY amount; + +ALTER TABLE part_1_100 ALTER COLUMN double_amount DROP EXPRESSION; -- error +ALTER TABLE part ALTER COLUMN double_amount DROP EXPRESSION; +\d part +\d part_1_100 +\d part_2_200 +INSERT INTO part VALUES (4), (104); +SELECT * FROM part ORDER BY amount; +DROP TABLE part; + +--- DROP COLUMN CASCASE should DROP dependent columns + +-- base case +CREATE TABLE table1(id INT, c1 INT, stored_col INT GENERATED ALWAYS AS (c1 * 2) STORED); +ALTER TABLE table1 DROP COLUMN c1; -- error +ALTER TABLE table1 DROP COLUMN c1 CASCADE; +ALTER TABLE table1 ADD COLUMN stored_col INT; +ALTER TABLE table1 ADD COLUMN c1 INT; +DROP TABLE table1; + +-- generated column is PK +CREATE TABLE table1(id INT, c1 INT, stored_col INT GENERATED ALWAYS AS (c1 * 2) STORED, PRIMARY KEY (stored_col)); +ALTER TABLE table1 DROP COLUMN c1 CASCADE; +ALTER TABLE table1 ADD COLUMN stored_col INT; +DROP TABLE table1; + +-- partitioned table +CREATE TABLE part(a INT, b INT, c INT GENERATED ALWAYS AS (b * 2) STORED) PARTITION BY RANGE (a); +CREATE TABLE part1_100 PARTITION OF part FOR VALUES FROM (1) TO (100); +ALTER TABLE part DROP COLUMN b CASCADE; +ALTER TABLE part ADD COLUMN c INT; +DROP TABLE part; + +-- partitioned table where generated col is PK +CREATE TABLE part(a INT, b INT, c INT GENERATED ALWAYS AS (b * 2) STORED, PRIMARY KEY (c, a)) PARTITION BY RANGE (a); +CREATE TABLE part1_100 PARTITION OF part FOR VALUES FROM (1) TO (100); +ALTER TABLE part DROP COLUMN b CASCADE; +ALTER TABLE part ADD COLUMN c INT; +DROP TABLE part; + +-- partitioned table, generated col is PK but only in leaf +CREATE TABLE part(a INT, b INT, c INT GENERATED ALWAYS AS (b * 2) STORED) PARTITION BY RANGE (a); +CREATE TABLE part1_100(a INT, b INT, c INT GENERATED ALWAYS AS (b * 2) STORED, PRIMARY KEY (c)); +ALTER TABLE part ATTACH PARTITION part1_100 FOR VALUES FROM (1) TO (100); +CREATE TABLE part2_200 PARTITION OF part FOR VALUES FROM (101) TO (200); +ALTER TABLE part DROP COLUMN b CASCADE; +ALTER TABLE part ADD COLUMN c INT; +ALTER TABLE part ADD COLUMN b INT; +DROP TABLE part; diff --git a/src/test/regress/sql/yb.orig.get_current_transaction_priority.sql b/src/test/regress/sql/yb.orig.get_current_transaction_priority.sql new file mode 100644 index 0000000000000..895dd3f0cbc38 --- /dev/null +++ b/src/test/regress/sql/yb.orig.get_current_transaction_priority.sql @@ -0,0 +1,133 @@ +-- Test functionality of the pg_proc yb_get_current_transaction_priority(). It helps fetch the +-- priority of a distributed transaction in Yugabyte if one has been started. If a distributed +-- transaction hasn't been started, it returns 0. +-- +-- The priority space of distributed transactions is uint64_t. The 64 bit range is split into 2 +-- priority buckets - +-- 1. Normal priority bucket: +-- [yb::kRegularTxnLowerBound, yb::kRegularTxnUpperBound] i.e., 0 to uint32_t_max-1 +-- 2. High priority bucket: +-- [yb::kHighPriTxnLowerBound, yb::kHighPriTxnUpperBound] i.e., uint32_t_max to uint64_t_max +-- +-- All transactions are usually randomly assigned a priority in the first bucket (normal). In case a +-- transaction has the first query which takes a FOR UPDATE/ FOR SHARE/ FOR NO KEY UPDATE explicit +-- row lock using SELECT, it is randomly assigned a priority from the high priority bucket. +-- +-- Apart from the above rule, there are two other user configurable session variables that can help +-- control the priority assigned to transaction is a specific session. These are +-- yb_transaction_priority_lower_bound and yb_transaction_priority_upper_bound. These help set +-- lower and upper bounds on the randomly assigned priority a transaction should receive from the +-- respective bucket that applies to it. For ease of use, the bounds are expressed as a float +-- such that the numerical ranges of a bucket are proportionally map to floats from 0-1. Also note +-- that the same floating point bounds apply to both buckets. +-- +-- For example, if yb_transaction_priority_lower_bound=0.5 and +-- yb_transaction_priority_upper_bound=0.75: +-- 1. a transaction that is assigned a priority from the normal bucket will get one from +-- the 0.5-0.75 marks such that the [yb::kRegularTxnLowerBound, yb::kRegularTxnUpperBound] +-- range proportionally maps to 0-1. +-- 2. a transaction that is assigned a priority from the high priority bucket will get one from +-- the 0.5-0.75 marks such that the [yb::kHighPriTxnLowerBound, yb::kHighPriTxnUpperBound] +-- range proportionally maps to 0-1. +-- +-- The priority returned by yb_get_current_transaction_priority consists of a pair of two values - +-- 1. A float between 0-1 inclusive with 9 decimal units of precision that such that it +-- proportionally maps to the priority assigned in the range of the priority bucket the +-- transaction belongs in. +-- 2. The bucket in which the transaction's priority lies - Normal or High priority. +-- +-- Since, priorities are uniformly randomly assigned between the configured lower and upper +-- bounds based on yb_transaction_priority_lower_bound and yb_transaction_priority_upper_bound, to +-- test reliably, we set the yb_transaction_priority_lower_bound and +-- yb_transaction_priority_upper_bound to be the same. This forces the transaction to pick exactly +-- that priority mark in the bucket that applies. +-- +-- NOTE: As an exception, if a transaction is assigned the highest priority possible +-- i.e., kHighPriTxnUpperBound, then a single value "Highest priority transaction" is returned +-- without any float. + +CREATE FUNCTION yb_get_current_transaction_priority_platform_independent() +RETURNS TABLE (priority NUMERIC(3, 2), category TEXT) +LANGUAGE SQL +AS $$ + SELECT + CASE + WHEN v::TEXT ~ '^\d+(\.\d+)? ' THEN + substring(v::TEXT, 1, position(' ' IN v::TEXT) - 1)::NUMERIC(3, 2) + ELSE + NULL + END AS priority, + CASE + WHEN v::TEXT ~ '^\d+(\.\d+)? ' THEN + substring(v::TEXT, position(' ' IN v::TEXT) + 1) + ELSE + v::TEXT + END AS category + FROM yb_get_current_transaction_priority() AS v; +$$; +SET yb_transaction_priority_lower_bound = 0.4; +SET yb_transaction_priority_upper_bound = 0.4; + +CREATE TABLE test (k INT PRIMARY KEY, priority NUMERIC(3, 2), category VARCHAR(100)); +INSERT INTO test (k, priority) VALUES (1, '1'); + +-- (1) Check that transaction priority is 0 until a distributed txn is started. +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); -- 0 since a distributed transaction hasn't started +SELECT * FROM test; -- this is a read-only operation and doesn't start a distributed transaction +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); -- still 0 +INSERT INTO test VALUES (2, '2'); -- start a distributed txn +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); -- non-zero now +COMMIT; + +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); -- 0 since a distributed transaction hasn't started +SELECT * FROM test; -- reads start a distributed txn in serializable isolation level +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); +COMMIT; + +-- (2) Showing yb_transaction_priority outside a transaction block +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); + +-- (3) Normal priority +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; +INSERT INTO test (k, priority, category) SELECT 3, * FROM yb_get_current_transaction_priority_platform_independent(); -- starts a distributed transaction +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); +INSERT INTO test SELECT 4, * FROM yb_get_current_transaction_priority_platform_independent(); +SELECT * FROM test ORDER BY k; +COMMIT; + +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +INSERT INTO test SELECT 5, * FROM yb_get_current_transaction_priority_platform_independent(); -- starts a distributed transaction +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); +INSERT INTO test SELECT 6, * FROM yb_get_current_transaction_priority_platform_independent(); +SELECT * FROM test ORDER BY k; +COMMIT; + +-- (4) High priority +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; +SELECT * FROM test WHERE k = 1 FOR UPDATE; -- starts a distributed transaction in high pri bucket +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); +INSERT INTO test SELECT 7, * FROM yb_get_current_transaction_priority_platform_independent(); +SELECT * FROM test ORDER BY k; +COMMIT; + +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +SELECT * FROM test WHERE k = 1 FOR UPDATE; -- starts a distributed transaction in high pri bucket +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); +INSERT INTO test SELECT 8, * FROM yb_get_current_transaction_priority_platform_independent(); +SELECT * FROM test ORDER BY k; +COMMIT; + +-- (5) Highest priority +SET yb_transaction_priority_upper_bound = 1; +SET yb_transaction_priority_lower_bound = 1; +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; +SELECT * FROM test WHERE k = 1 FOR UPDATE; +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); +COMMIT; + +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +SELECT * FROM test WHERE k = 1 FOR UPDATE; +SELECT * FROM yb_get_current_transaction_priority_platform_independent(); +COMMIT; diff --git a/src/test/regress/sql/yb.orig.get_range_split_clause.sql b/src/test/regress/sql/yb.orig.get_range_split_clause.sql new file mode 100644 index 0000000000000..8662995e70f77 --- /dev/null +++ b/src/test/regress/sql/yb.orig.get_range_split_clause.sql @@ -0,0 +1,539 @@ +-- Tests for yb_get_range_split_clause: Verify the new YB function to get correct +-- range-split SPLIT AT (%s) clause. + +\pset null '(null)' + +-- Test edge cases & system tables +SELECT yb_get_range_split_clause(null); + +SELECT yb_get_range_split_clause(0); + +SELECT yb_get_range_split_clause('pg_class'::regclass); + +-- Control the message level sent to the client to make this test suite more robust for oid changes. +-- Don't output NOTICE: relation with oid is not backed by YB for some cases. +SET client_min_messages = 'WARNING'; + +-- Test system view, view, YB materialized view +SELECT yb_get_range_split_clause('pg_roles'::regclass); + +CREATE TABLE view_test (a INT); +CREATE VIEW my_view AS SELECT * FROM view_test; +SELECT yb_get_range_split_clause('my_view'::regclass); +DROP VIEW my_view; + +CREATE MATERIALIZED VIEW my_materialized_view AS SELECT * FROM view_test; +SELECT yb_get_range_split_clause('my_materialized_view'::regclass); +DROP MATERIALIZED VIEW my_materialized_view; +DROP TABLE view_test; + +RESET client_min_messages; +SET yb_enable_create_with_table_oid = true; + +-- Test temp table +CREATE TEMP TABLE temp_tbl ( + a INT, + PRIMARY KEY(a ASC) +) WITH (table_oid = 20000) SPLIT AT VALUES((-100), (250)); +SELECT yb_get_range_split_clause('temp_tbl'::regclass); +DROP TABLE temp_tbl; + +-- Test table with no primary key. +CREATE TABLE no_pk_tbl ( + a INT, + b TEXT +) WITH (table_oid = 20003); +SELECT yb_get_range_split_clause('no_pk_tbl'::regclass); +DROP TABLE no_pk_tbl; + +-- Test hash-partitioned table. +CREATE TABLE hash_partitioned_tbl (k INT PRIMARY KEY) WITH (table_oid = 20006) SPLIT INTO 5 TABLETS; +SELECT yb_get_range_split_clause('hash_partitioned_tbl'::regclass); +DROP TABLE hash_partitioned_tbl; + +-- Test range-partitioned table. +-- Test range-partitioned table with no SPLIT AT clause +CREATE TABLE range_no_split_at_tbl ( + a INT, + PRIMARY KEY(a ASC) +); +SELECT yb_get_range_split_clause('range_no_split_at_tbl'::regclass); +DROP TABLE range_no_split_at_tbl; + +-- Test basic cases +CREATE TABLE tbl ( + a INT, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES((-100), (250)); +SELECT yb_get_range_split_clause('tbl'::regclass); +DROP TABLE tbl; + +CREATE TABLE tbl2 ( + a TEXT, + b INT, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES(('bar'), ('foo')); +SELECT yb_get_range_split_clause('tbl2'::regclass); +DROP TABLE tbl2; + +CREATE TABLE tbl_with_many_numeric_types ( + a SMALLINT, + b BIGINT, + c REAL, + d DOUBLE PRECISION, + e DECIMAL, + f NUMERIC, + g SMALLSERIAL, + h SERIAL, + i BIGSERIAL, + PRIMARY KEY(a ASC, b ASC, c ASC, d ASC, e ASC, f ASC, g ASC, h ASC, i ASC) +) SPLIT AT VALUES((100, 100, 100, 100, 100, 100, 100, 100, 100)); +SELECT yb_get_range_split_clause('tbl_with_many_numeric_types'::regclass); +DROP TABLE tbl_with_many_numeric_types; + +CREATE TABLE tbl_with_many_character_types ( + a CHARACTER VARYING(10), + b VARCHAR(10), + c CHARACTER(10), + d CHAR(10), + e TEXT, + PRIMARY KEY(a ASC, b ASC, c ASC, d ASC, e ASC) +) SPLIT AT VALUES(('a', 'bb', 'ccc', 'dddd', 'eeeee')); +SELECT yb_get_range_split_clause('tbl_with_many_character_types'::regclass); +DROP TABLE tbl_with_many_character_types; + +-- Test MIN & MAX boundary case +CREATE TABLE tbl3 ( + a INT, + b INT, + c INT, + d INT, + PRIMARY KEY(a ASC, b ASC, c ASC, d ASC) +) SPLIT AT VALUES((100), (200, MINVALUE, MINVALUE, 5), (400, MAXVALUE)); +SELECT yb_get_range_split_clause('tbl3'::regclass); +DROP TABLE tbl3; + +-- Test type cast case +CREATE TABLE tbl4 ( + a TEXT, + b INT, + c INT, + d TEXT, + PRIMARY KEY(a ASC, b ASC, c ASC, d ASC) +) SPLIT AT VALUES((100), (200, MINVALUE, MINVALUE, 5), (400, MAXVALUE)); +SELECT yb_get_range_split_clause('tbl4'::regclass); +DROP TABLE tbl4; + +-- Test monetary type +CREATE TABLE tbl5 ( + a MONEY, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES (('$100.99')); +SELECT yb_get_range_split_clause('tbl5'::regclass); +DROP TABLE tbl5; + +-- Test binary type +CREATE TABLE tbl6 ( + a BYTEA, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES (('value')); +SELECT yb_get_range_split_clause('tbl6'::regclass); +SELECT 'value'::bytea; -- verify the same binary string output +DROP TABLE tbl6; + +CREATE TABLE escape_binary ( + a BYTEA, + PRIMARY KEY (a ASC) +) SPLIT AT VALUES ((E'\x7F\U0001ffff')); +SELECT yb_get_range_split_clause('escape_binary'::regclass); +SELECT E'\x7F\U0001ffff'::bytea = E'\\x7ff09fbfbf'::bytea; +DROP TABLE escape_binary; + +-- Test date time type +SET timezone = '0'; +SET datestyle = 'ISO'; + +CREATE TABLE tbl_with_many_date_time_types ( + a TIMESTAMP(2), + b TIMESTAMP WITH TIME ZONE, + c DATE, + d TIME(2), + PRIMARY KEY(a ASC, b ASC, c ASC, d ASC) +) SPLIT AT VALUES (('1234-01-30 07:08:09', '1234-01-30 07:08:09+06', '1234-01-30', '07:08:09')); +SELECT yb_get_range_split_clause('tbl_with_many_date_time_types'::regclass); + +SET timezone = '2'; +SELECT yb_get_range_split_clause('tbl_with_many_date_time_types'::regclass); + +DROP TABLE tbl_with_many_date_time_types; +RESET timezone; +RESET datestyle; + +-- Test boolean type +CREATE TABLE tbl7 ( + a BOOLEAN, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES (('true')); +SELECT yb_get_range_split_clause('tbl7'::regclass); +DROP TABLE tbl7; + +-- Test uuid type +CREATE TABLE tbl8 ( + a UUID, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES (('12345678-1234-5678-1234-567812345678')); +SELECT yb_get_range_split_clause('tbl8'::regclass); +DROP TABLE tbl8; + +-- Test object identifier type +CREATE TABLE tbl_with_many_object_identifier_types ( + a OID, + b REGPROC, + c REGPROCEDURE, + d REGOPER, + e REGOPERATOR, + f REGCLASS, + g REGTYPE, + h REGROLE, + i REGNAMESPACE, + j REGCONFIG, + k REGDICTIONARY, + PRIMARY KEY(a ASC, b ASC, c ASC, d ASC, e ASC, f ASC, g ASC, h ASC, i ASC, j ASC, k ASC) +) SPLIT AT VALUES ((100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100)); +SELECT yb_get_range_split_clause('tbl_with_many_object_identifier_types'::regclass); +DROP TABLE tbl_with_many_object_identifier_types; + +-- Test pg_lsn type +CREATE TABLE tbl9 ( + a PG_LSN, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES (('1/2345678')); +SELECT yb_get_range_split_clause('tbl9'::regclass); +DROP TABLE tbl9; + +-- Test Infinity +CREATE TABLE tbl_floating_point_infinity ( + a DOUBLE PRECISION, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES (('-Infinity'), (100), (200), ('Infinity')); +SELECT yb_get_range_split_clause('tbl_floating_point_infinity'::regclass); +DROP TABLE tbl_floating_point_infinity; + +-- Test NaN +CREATE TABLE tbl_floating_point_nan ( + a DOUBLE PRECISION, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES ((100), (200), ('NaN')); +SELECT yb_get_range_split_clause('tbl_floating_point_nan'::regclass); +DROP TABLE tbl_floating_point_nan; +-- verify the correctness of split points order +CREATE TABLE tbl_has_nan_value ( + a DOUBLE PRECISION +); +INSERT INTO tbl_has_nan_value VALUES ('NAN'), (100), (200); +SELECT * FROM tbl_has_nan_value ORDER BY a ASC; +DROP TABLE tbl_has_nan_value; + +CREATE TABLE tbl_numeric_nan ( + a NUMERIC, + PRIMARY KEY(a DESC) +) SPLIT AT VALUES (('NaN'), (200), (100)); + +-- Test Enumerated type +CREATE TYPE enum_type AS ENUM('one', 'two', 'three', 'four', 'five', 'six', 'seven'); +CREATE TABLE tbl_enum ( + a enum_type, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES (('two'), ('four'), ('six')); +SELECT yb_get_range_split_clause('tbl_enum'::regclass); +DROP TABLE tbl_enum; +DROP TYPE enum_type; + +-- Test strings containing single-quoted, double-quoted, backslashes +CREATE TABLE tbl_with_special_strings ( + a TEXT, + PRIMARY KEY(a DESC) +) SPLIT AT VALUES(('D si''ngle quo''tes'), ('C double" quotes'), (E'B back\\slashes'), (E'A all'' combo\\ of them"')); +SELECT yb_get_range_split_clause('tbl_with_special_strings'::regclass); +DROP TABLE tbl_with_special_strings; + +-- When standard_conforming_strings = ON, backslash is treated as literal character. +-- standard_conforming_strings = ON is the default value +CREATE TABLE tbl_with_special_strings2 ( + a TEXT, + PRIMARY KEY(a DESC) +) SPLIT AT VALUES(('D si''ngle quo''tes'), ('C double" quotes'), ('B back\slashes'), ('A all'' combo\ of them"')); +SELECT yb_get_range_split_clause('tbl_with_special_strings2'::regclass); +DROP TABLE tbl_with_special_strings2; + +-- When standard_conforming_strings = OFF, backslashs are treated as escapes. +SET standard_conforming_strings = OFF; + +-- Incorrect escapes ignore backslashs +SELECT 'B back\slashes'; +SELECT E'B back\slashes'; + +CREATE TABLE tbl_with_special_strings3 ( + a TEXT, + PRIMARY KEY(a DESC) +) SPLIT AT VALUES(('D si''ngle quo''tes'), ('C double" quotes'), ('B back\slashes'), ('A all'' combo\ of them"')); +SELECT yb_get_range_split_clause('tbl_with_special_strings3'::regclass); +DROP TABLE tbl_with_special_strings3; + +-- Test proper escapes +CREATE TABLE tbl_with_special_strings4 ( + a TEXT, + PRIMARY KEY(a DESC) +) SPLIT AT VALUES((E'C\tC'), (E'B\x42'), (E'A\\A')); +SELECT yb_get_range_split_clause('tbl_with_special_strings4'::regclass); +DROP TABLE tbl_with_special_strings4; + +CREATE TABLE tbl_with_special_strings5 ( + a TEXT, + PRIMARY KEY(a DESC) +) SPLIT AT VALUES((E'A\nB')); +SELECT yb_get_range_split_clause('tbl_with_special_strings5'::regclass); + +-- verify correctness of tbl_with_special_strings5 +SELECT yb_get_range_split_clause('tbl_with_special_strings5'::regclass)::TEXT = 'SPLIT AT VALUES ((''' || E'A\nB' || '''))'; +DROP TABLE tbl_with_special_strings5; + +RESET standard_conforming_strings; + +-- Test multibyte character +-- Note: \u7F57 = '罗' = \xe7\xbd\x97, \u7FBD = '羽' = \xe7\xbe\xbd, \u8000 = '耀' = \xe8\x80\x80 +CREATE TABLE tbl_multibyte ( + a TEXT, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES((E'\u7F57'), ('羽'), (E'\xe8\x80\x80')); +SELECT yb_get_range_split_clause('tbl_multibyte'::regclass); + +-- Test index +CREATE TABLE tbl10 (a TEXT); +CREATE INDEX tbl10_idx ON tbl10 ( + a ASC +) SPLIT AT VALUES (('bar'), ('foo'), ('qux')); +SELECT yb_get_range_split_clause('tbl10_idx'::regclass); +DROP INDEX tbl10_idx; +DROP TABLE tbl10; + +CREATE TABLE tbl11 (a TEXT, b INT, c INT, d INT); +CREATE UNIQUE INDEX tbl11_idx ON tbl11 ( + a ASC, b ASC, c ASC, d ASC +) SPLIT AT VALUES (('bar', 1, 1, 1), ('foo', 2, 2, 2), ('qux', 3, 3, 3)); +SELECT yb_get_range_split_clause('tbl11_idx'::regclass); +DROP INDEX tbl11_idx; +DROP TABLE tbl11; + +-- Test GIN index +CREATE TABLE vectors (i serial PRIMARY KEY, v tsvector); +CREATE INDEX vectors_idx ON vectors USING ybgin (v) SPLIT AT VALUES ( + (MINVALUE), ('ccc'), ('fff'), (MAXVALUE)); +SELECT yb_get_range_split_clause('vectors_idx'::regclass); +DROP INDEX vectors_idx; +DROP TABLE vectors; + +CREATE TABLE int_arrays (i serial PRIMARY KEY, a INT[]); +CREATE INDEX int_arrays_idx ON int_arrays USING ybgin (a) SPLIT AT VALUES ( + (MINVALUE), (2), (4), (6), (MAXVALUE)); +SELECT yb_get_range_split_clause('int_arrays_idx'::regclass); +DROP INDEX int_arrays_idx; +DROP TABLE int_arrays; + +CREATE TABLE text_arrays (i serial PRIMARY KEY, a TEXT[]); +CREATE INDEX text_arrays_idx ON text_arrays USING ybgin (a) SPLIT AT VALUES ( + (MINVALUE), ('aa'), ('bb'), ('zz'), (MAXVALUE)); +SELECT yb_get_range_split_clause('text_arrays_idx'::regclass); +DROP INDEX text_arrays_idx; +DROP TABLE text_arrays; + +-- Test serial +CREATE TABLE tbl_serial_pk ( + a SERIAL, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES((-100), (200)); +SELECT yb_get_range_split_clause('tbl_serial_pk'::regclass); +DROP TABLE tbl_serial_pk; + +-- Test collation +CREATE TABLE tbl_collation_pk ( + a TEXT COLLATE "C", + PRIMARY KEY(a ASC) +) SPLIT AT VALUES(('bar'), ('foo')); +SELECT yb_get_range_split_clause('tbl_collation_pk'::regclass); +DROP TABLE tbl_collation_pk; + +CREATE TABLE tbl_collation_pk2 ( + a TEXT COLLATE "en-x-icu", + PRIMARY KEY(a ASC) +) SPLIT AT VALUES(('äbc'), ('bbc')); +SELECT yb_get_range_split_clause('tbl_collation_pk2'::regclass); +DROP TABLE tbl_collation_pk2; + +CREATE TABLE tbl_collation_pk3 ( + a TEXT COLLATE "C", + PRIMARY KEY(a ASC) +) SPLIT AT VALUES(('bbc'), ('äbc')); +SELECT yb_get_range_split_clause('tbl_collation_pk3'::regclass); +DROP TABLE tbl_collation_pk3; + +-- Test colocated table +CREATE DATABASE colocation_test colocation = true; +\c colocation_test +CREATE TABLE tbl_colocated_range ( + a INT, + b TEXT, + PRIMARY KEY(a ASC, b ASC) +) SPLIT AT VALUES((-100, 'bar'), (250, 'foo')); + +CREATE TABLE tbl_colocated_hash ( + a INT PRIMARY KEY +) SPLIT INTO 2 TABLETS; + +CREATE TABLE tbl_colocated ( + a INT, + b TEXT, + PRIMARY KEY(a ASC, b ASC) +); +SELECT yb_get_range_split_clause('tbl_colocated'::regclass); + +\c yugabyte +DROP DATABASE colocation_test; + +-- Test tablegroup +CREATE TABLEGROUP tbl_group; + +CREATE TABLE tbl_group_split_at ( + a INT, + PRIMARY KEY(a ASC) +) SPLIT AT VALUES((-100), (250)) TABLEGROUP tbl_group; + +CREATE TABLE tbl_group_table ( + a INT, + PRIMARY KEY(a ASC) +) TABLEGROUP tbl_group; +SELECT yb_get_range_split_clause('tbl_group_table'::regclass); +DROP TABLE tbl_group_table; + +DROP TABLEGROUP tbl_group; + +-- Test range split using expressions +CREATE TABLE range_split_expressions ( + k1 INT, + k2 TEXT, + PRIMARY KEY(k1 ASC, k2 DESC) +) SPLIT AT VALUES ((1 + 2, substr('abc', 1, 2)), (factorial(4), repeat('d', 5))); +SELECT yb_get_range_split_clause('range_split_expressions'::regclass); + +-- Test the scenario where primary key columns' ordering in PRIMARY KEY +-- constraint is different from the key column's ordering in CREATE TABLE +-- statement. +CREATE TABLE column_ordering_mismatch ( + k2 TEXT, + v DOUBLE PRECISION, + k1 INT, + PRIMARY KEY (k1 ASC, k2 ASC) +) SPLIT AT VALUES((1, '1'), (100, '100')); +SELECT yb_get_range_split_clause('column_ordering_mismatch'::regclass); +DROP TABLE column_ordering_mismatch; + +-- Test table PRIMARY KEY with INCLUDE clause +CREATE TABLE tbl_with_include_clause ( + k2 TEXT, + v DOUBLE PRECISION, + k1 INT, + PRIMARY KEY (k1 ASC, k2 ASC) INCLUDE (v) +) SPLIT AT VALUES((1, '1'), (100, '100')); +SELECT yb_get_range_split_clause('tbl_with_include_clause'::regclass); +DROP TABLE tbl_with_include_clause; + +-- Test index SPLIT AT with INCLUDE clause +CREATE TABLE test_tbl ( + a TEXT, + b DOUBLE PRECISION, + PRIMARY KEY (a ASC) +) SPLIT AT VALUES(('11')); +CREATE INDEX test_idx on test_tbl( + b ASC +) INCLUDE (a) SPLIT AT VALUES ((1.1)); +SELECT yb_get_range_split_clause('test_idx'::regclass); +DROP INDEX test_idx; +DROP TABLE test_tbl; + +-- Test index SPLIT AT with INCLUDE clause +CREATE TABLE test_tbl ( + a INT, + b TEXT, + c CHAR, + d BOOLEAN, + e REAL, + PRIMARY KEY (a ASC, b ASC) +) SPLIT AT VALUES((1, '111')); +CREATE INDEX test_idx on test_tbl( + a ASC, + b ASC, + c ASC +) INCLUDE (d, e) SPLIT AT VALUES ((1, '11', '1')); +SELECT yb_get_range_split_clause('test_idx'::regclass); +DROP INDEX test_idx; +DROP TABLE test_tbl; + +-- Test index SPLIT AT with INCLUDE clause +CREATE TABLE test_tbl ( + a INT, + b INT, + c INT, + d INT, + e INT, + PRIMARY KEY (a DESC, b ASC) +) SPLIT AT VALUES((1, 1)); +CREATE INDEX test_idx on test_tbl( + a ASC, + b DESC +) INCLUDE (c, d, e) SPLIT AT VALUES ((1, 1)); +SELECT yb_get_range_split_clause('test_idx'::regclass); +DROP INDEX test_idx; +DROP TABLE test_tbl; + +-- Test secondary index with duplicate columns and backwards order columns +CREATE TABLE test_tbl ( + k1 INT, + k2 TEXT, + k3 DOUBLE PRECISION +); +CREATE INDEX test_idx on test_tbl ( + k3 ASC, + k2 ASC, + k1 ASC, + k3 DESC +) SPLIT AT VALUES ((1.1, '11', 1, 1.1), (3.3, '33', 3, 3.3)); +SELECT yb_get_range_split_clause('test_idx'::regclass); +DROP INDEX test_idx; +DROP TABLE test_tbl; + +-- Test partitioned table +CREATE TABLE test_part (a INT, PRIMARY KEY(a ASC)) PARTITION BY RANGE (a) SPLIT AT VALUES ((5), (10)); +SELECT yb_get_range_split_clause('test_part'::regclass); + +CREATE TABLE IF NOT EXISTS test_part2 (a INT, PRIMARY KEY(a ASC)) PARTITION BY RANGE (a) SPLIT AT VALUES ((5), (10)); +SELECT yb_get_range_split_clause('test_part2'::regclass); +DROP TABLE test_part2; + +CREATE TYPE test_type AS (a INT, b INT); +CREATE TABLE test_part3 OF test_type (PRIMARY KEY(a ASC)) PARTITION BY RANGE (a) SPLIT AT VALUES ((5), (10)); +SELECT yb_get_range_split_clause('test_part3'::regclass); +DROP TABLE test_part3; + +CREATE TABLE IF NOT EXISTS test_part4 OF test_type (PRIMARY KEY(a ASC)) PARTITION BY RANGE (a) SPLIT AT VALUES ((5), (10)); +SELECT yb_get_range_split_clause('test_part4'::regclass); +DROP TABLE test_part4; +DROP TYPE test_type; + +CREATE TABLE test_part5 PARTITION OF test_part FOR VALUES FROM (1) to (100) PARTITION BY RANGE (a) SPLIT AT VALUES ((5), (10)); +SELECT yb_get_range_split_clause('test_part5'::regclass); +DROP TABLE test_part5; + +CREATE TABLE IF NOT EXISTS test_part6 PARTITION OF test_part FOR VALUES FROM (101) to (200) PARTITION BY RANGE (a) SPLIT AT VALUES ((105), (110)); +SELECT yb_get_range_split_clause('test_part6'::regclass); +DROP TABLE test_part6; +DROP TABLE test_part; diff --git a/src/test/regress/sql/yb.orig.gin.sql b/src/test/regress/sql/yb.orig.gin.sql new file mode 100644 index 0000000000000..a1c2350554a72 --- /dev/null +++ b/src/test/regress/sql/yb.orig.gin.sql @@ -0,0 +1,193 @@ +-- +-- Yugabyte-owned test for gin access method. +-- + +-- Disable sequential scan so that bitmap index scan is always chosen. +SET enable_seqscan = off; +SET enable_bitmapscan = on; + +-- +-- Create temp tables because gin access method is only supported on temporary +-- tables. +-- +CREATE TEMP TABLE vectors (i serial PRIMARY KEY, v tsvector); +CREATE TEMP TABLE arrays (i serial PRIMARY KEY, a int[]); +CREATE TEMP TABLE jsonbs (i serial PRIMARY KEY, j jsonb); +CREATE TEMP TABLE multi (v tsvector, a1 text[], a2 float[], j1 jsonb, j2 jsonb); +CREATE TEMP TABLE expression (v tsvector, a text[], j jsonb); +CREATE TEMP TABLE partial (v tsvector, a text[], j jsonb); + +-- +-- tsvector +-- +INSERT INTO vectors (v) VALUES + (to_tsvector('simple', 'a bb ccc')), + (to_tsvector('simple', 'bb a e i o u')), + (to_tsvector('simple', 'ccd')); +CREATE INDEX ON vectors USING gin (v); -- test ambuild +INSERT INTO vectors (v) VALUES + (to_tsvector('simple', 'a a a a a a')), + (to_tsvector('simple', 'cc')); -- test aminsert +-- test amgetbitmap +EXPLAIN (costs off) SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'a'); +SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'a'); +EXPLAIN (costs off) SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'a & e'); +SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'a & e'); +EXPLAIN (costs off) SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'bb | cc'); +SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'bb | cc'); +EXPLAIN (costs off) SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'bb & !ccc'); +SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'bb & !ccc'); +EXPLAIN (costs off) SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'cc:*'); +SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'cc:*'); +EXPLAIN (costs off) SELECT count(*) FROM vectors WHERE v @@ to_tsquery('simple', 'a'); +SELECT count(*) FROM vectors WHERE v @@ to_tsquery('simple', 'a'); + +-- +-- array +-- +INSERT INTO arrays (a) VALUES + ('{1, 3, 5}'), + (ARRAY[2, 3, 5]), + ('{7, 1, 6, 4}'); +CREATE INDEX ON arrays USING gin (a); -- test ambuild +INSERT INTO arrays (a) VALUES + ('{3}'), + ('{3, 3, 3}'), + ('{10, 20, 30}'); -- test aminsert +-- test amgetbitmap +EXPLAIN (costs off) SELECT * FROM arrays WHERE a && '{1, 100, 3}'; +SELECT * FROM arrays WHERE a && '{1, 100, 3}'; +EXPLAIN (costs off) SELECT * FROM arrays WHERE a @> '{5, 3}'; +SELECT * FROM arrays WHERE a @> '{5, 3}'; +EXPLAIN (costs off) SELECT * FROM arrays WHERE a <@ '{5, 4, 3, 2}'; +SELECT * FROM arrays WHERE a <@ '{5, 4, 3, 2}'; +EXPLAIN (costs off) SELECT * FROM arrays WHERE a = '{3}'; +SELECT * FROM arrays WHERE a = '{3}'; +EXPLAIN (costs off) SELECT count(*) FROM arrays WHERE a = '{3}'; +SELECT count(*) FROM arrays WHERE a = '{3}'; + +-- +-- jsonb +-- +INSERT INTO jsonbs (j) VALUES + ('{"aaa":[1,2.00,3]}'), + ('{"ggg":"aaa"}'), + ('["bbb", "aaa"]'); +CREATE INDEX ON jsonbs USING gin (j); -- test ambuild +INSERT INTO jsonbs (j) VALUES + ('"aaa"'), + ('3.0'), + ('{"aaa":{"bbb":[2,4], "ccc":{"ddd":6}}, "eee":8}'); -- test aminsert +-- test amgetbitmap +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j ? 'aaa'; +SELECT * FROM jsonbs WHERE j ? 'aaa'; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j ?| '{"ggg", "eee"}'; +SELECT * FROM jsonbs WHERE j ?| '{"ggg", "eee"}'; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j ?& '{"aaa", "eee"}'; +SELECT * FROM jsonbs WHERE j ?& '{"aaa", "eee"}'; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j @> '{"bbb":[4]}'; +SELECT * FROM jsonbs WHERE j @> '{"bbb":[4]}'; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j @> '{"aaa":{"bbb":[4]}}'; +SELECT * FROM jsonbs WHERE j @> '{"aaa":{"bbb":[4]}}'; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ == 2)'; +SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ == 2)'; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j @@ '$.ggg starts with "aa"'; +SELECT * FROM jsonbs WHERE j @@ '$.ggg starts with "aa"'; +EXPLAIN (costs off) SELECT count(*) FROM jsonbs WHERE j ? 'aaa'; +SELECT count(*) FROM jsonbs WHERE j ? 'aaa'; + +-- +-- jsonb_path +-- +DROP INDEX jsonbs_j_idx; +CREATE INDEX ON jsonbs USING gin (j jsonb_path_ops); -- test ambuild +INSERT INTO jsonbs (j) VALUES + ('{"aaa":{"bbb":[2], "ccc":{"ddd":6}}, "eee":[]}'); -- test aminsert +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j ? 'aaa'; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j ?| '{"ggg", "eee"}'; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j ?& '{"aaa", "eee"}'; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j @> '{"aaa":{"bbb":[4]}}'; +SELECT * FROM jsonbs WHERE j @> '{"aaa":{"bbb":[4]}}'; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ == 2)'; +SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ == 2)'; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j @@ '$.ggg starts with "aa"'; +SELECT * FROM jsonbs WHERE j @@ '$.ggg starts with "aa"'; + +-- +-- Multicolumn +-- +INSERT INTO multi VALUES + (to_tsvector('simple', 'a b'), ARRAY['c', 'd'], ARRAY[5.0, 6.1], '{"f":3}', + '{"r":[3,6.5]}'), + (to_tsvector('simple', '0'), ARRAY['0'], ARRAY[0], '0', '0'); +CREATE INDEX ON multi USING gin (v, a1); -- test ambuild +CREATE INDEX ON multi USING gin (a2, a2); -- test ambuild +CREATE INDEX ON multi USING gin (j1 jsonb_ops, j2 jsonb_path_ops); -- test ambuild +INSERT INTO multi VALUES + (to_tsvector('simple', 'c d'), ARRAY['a'], ARRAY[5, 6.1], '{"f":5}', + '{"r":[3.0,6.5]}'); -- test aminsert +EXPLAIN (costs off) SELECT count(*) FROM multi WHERE v @@ 'a' or a1 && ARRAY['a']; +SELECT count(*) FROM multi WHERE v @@ 'a' or a1 && ARRAY['a']; +EXPLAIN (costs off) SELECT count(*) FROM multi WHERE a2 && ARRAY[5.0]::float[]; +SELECT count(*) FROM multi WHERE a2 && ARRAY[5.0]::float[]; +EXPLAIN (costs off) SELECT count(*) FROM multi WHERE j1 ? 'f' and j2 @> '{"r":[3.0]}'; +SELECT count(*) FROM multi WHERE j1 ? 'f' and j2 @> '{"r":[3.0]}'; + +-- +-- Expression index +-- +INSERT INTO expression VALUES + (to_tsvector('simple', 'a b c'), ARRAY['d', 'e', 'f'], '{"g":["h","i"]}'); +CREATE INDEX ON expression USING gin (tsvector_to_array(v)); -- test ambuild +CREATE INDEX ON expression USING gin (array_to_tsvector(a)); -- test ambuild +CREATE INDEX ON expression USING gin ( + jsonb_to_tsvector('simple', j, '["string"]')); -- test ambuild +INSERT INTO expression VALUES + (to_tsvector('simple', 'a a'), ARRAY['d', 'd'], '{"g":"g"}'); -- test aminsert +EXPLAIN (costs off) SELECT count(*) FROM expression WHERE tsvector_to_array(v) && ARRAY['b']; +SELECT count(*) FROM expression WHERE tsvector_to_array(v) && ARRAY['b']; +EXPLAIN (costs off) SELECT count(*) FROM expression WHERE array_to_tsvector(a) @@ 'e'; +SELECT count(*) FROM expression WHERE array_to_tsvector(a) @@ 'e'; +EXPLAIN (costs off) SELECT count(*) FROM expression + WHERE jsonb_to_tsvector('simple', j, '["string"]') @@ 'h'; +SELECT count(*) FROM expression + WHERE jsonb_to_tsvector('simple', j, '["string"]') @@ 'h'; + +-- +-- Partial index +-- +INSERT INTO partial VALUES + (to_tsvector('simple', 'a a'), ARRAY['d', 'e', 'f'], '{"g":["h","i"]}'); +CREATE INDEX ON partial USING gin (v) WHERE v @@ 'c'; -- test ambuild +CREATE INDEX ON partial USING gin (a) WHERE j @> '{"g":["i"]}'; -- test ambuild +CREATE INDEX ON partial USING gin (j) WHERE a && ARRAY['f']; -- test ambuild +INSERT INTO partial VALUES + (to_tsvector('simple', 'a b c'), ARRAY['d', 'd'], '{"g":"g"}'); -- test aminsert +EXPLAIN (costs off) SELECT count(*) FROM partial WHERE v @@ 'b'; +EXPLAIN (costs off) SELECT count(*) FROM partial WHERE v @@ 'c'; +SELECT count(*) FROM partial WHERE v @@ 'c'; +EXPLAIN (costs off) SELECT count(*) FROM partial WHERE a && ARRAY['e']; +EXPLAIN (costs off) SELECT count(*) FROM partial + WHERE a && ARRAY['e'] and j @> '{"g":["i"]}'; +SELECT count(*) FROM partial + WHERE a && ARRAY['e'] and j @> '{"g":["i"]}'; +EXPLAIN (costs off) SELECT count(*) FROM partial + WHERE j @? '$.g[*] ? (@ == "h")'; +EXPLAIN (costs off) SELECT count(*) FROM partial + WHERE j @? '$.g[*] ? (@ == "h")' and a && ARRAY['f']; +SELECT count(*) FROM partial + WHERE j @? '$.g[*] ? (@ == "h")' and a && ARRAY['f']; + +-- Cleanup +DISCARD TEMP; + +-- +-- Try creating gin index on Yugabyte table. +-- +CREATE TABLE vectors (i serial PRIMARY KEY, v tsvector); +CREATE INDEX ON vectors USING gin (v); + +-- Cleanup +DROP TABLE vectors; +RESET enable_seqscan; +RESET enable_bitmapscan; diff --git a/src/test/regress/sql/yb.orig.guc.sql b/src/test/regress/sql/yb.orig.guc.sql new file mode 100644 index 0000000000000..56bb5e3329a67 --- /dev/null +++ b/src/test/regress/sql/yb.orig.guc.sql @@ -0,0 +1,173 @@ +-- Check transaction priority bounds. + +set log_error_verbosity = default; + +-- Values should be in interval [0,1] (inclusive). +-- Invalid values. +set yb_transaction_priority_upper_bound = 2; +set yb_transaction_priority_lower_bound = -1; + +-- Valid values. +set yb_transaction_priority_upper_bound = 1; +set yb_transaction_priority_lower_bound = 0; +set yb_transaction_priority_lower_bound = 0.3; +set yb_transaction_priority_upper_bound = 0.7; + +-- Lower bound should be less or equal to upper bound. +-- Invalid values. +set yb_transaction_priority_upper_bound = 0.2; +set yb_transaction_priority_lower_bound = 0.8; + +-- Valid values. +set yb_transaction_priority_upper_bound = 0.3; +set yb_transaction_priority_upper_bound = 0.6; +set yb_transaction_priority_lower_bound = 0.4; +set yb_transaction_priority_lower_bound = 0.6; + +-- Test yb_fetch_row_limit +set yb_fetch_row_limit = 100; +show yb_fetch_row_limit; + +set yb_fetch_row_limit = -1; -- ERROR since yb_fetch_row_limit must be non-negative. + +-- Test yb_fetch_size_limit +set yb_fetch_size_limit = '2MB'; +show yb_fetch_size_limit; +set yb_fetch_size_limit = 789; +show yb_fetch_size_limit; +set yb_fetch_size_limit = 2048; +show yb_fetch_size_limit; + +set yb_fetch_size_limit = -1; -- ERROR since yb_fetch_size_limit must be non-negative. + +-- Check enable_seqscan, enable_indexscan, enable_indexonlyscan for YB scans. +CREATE TABLE test_scan (i int, j int); +CREATE INDEX NONCONCURRENTLY ON test_scan (j); + +-- We want to know when disable_cost is added, but we don't want to depend on +-- the exact cost value. +CREATE OR REPLACE FUNCTION get_plan_details(stmt text) RETURNS TABLE ( + scan_type text, + disabled boolean +) AS +$_$ +DECLARE + ret text; + first_line text; +BEGIN + EXECUTE format('EXPLAIN (FORMAT text) %s', stmt) INTO ret; + first_line := split_part(ret, E'\n', 1); -- Extract the first line + -- return (first two words of the line, is_disabled) + RETURN QUERY + SELECT trim(split_part(first_line, ' ', 1) || ' ' || split_part(first_line, ' ', 2)) AS scan_type, + first_line SIMILAR TO '%cost=1[0-9]{10}%' AS disabled; +END; +$_$ +LANGUAGE plpgsql; + +set yb_enable_bitmapscan = on; +set enable_seqscan = on; +set enable_indexscan = on; +set enable_indexonlyscan = on; +set enable_bitmapscan = on; +SELECT * FROM get_plan_details('SELECT * FROM test_scan;'); +SELECT * FROM get_plan_details('SELECT * FROM test_scan WHERE j = 1;'); +SELECT * FROM get_plan_details('SELECT j FROM test_scan;'); +set enable_seqscan = on; +set enable_indexscan = off; +set enable_bitmapscan = off; +SELECT * FROM get_plan_details('SELECT * FROM test_scan;'); +SELECT * FROM get_plan_details('SELECT * FROM test_scan WHERE j = 1;'); +SELECT * FROM get_plan_details('SELECT j FROM test_scan;'); +set enable_seqscan = off; +set enable_indexscan = off; +set enable_bitmapscan = off; +SELECT * FROM get_plan_details('SELECT * FROM test_scan;'); +SELECT * FROM get_plan_details('SELECT * FROM test_scan WHERE j = 1;'); +SELECT * FROM get_plan_details('SELECT j FROM test_scan;'); +set enable_seqscan = off; +set enable_indexscan = on; +set enable_bitmapscan = off; +SELECT * FROM get_plan_details('SELECT * FROM test_scan;'); +SELECT * FROM get_plan_details('SELECT * FROM test_scan WHERE j = 1;'); +SELECT * FROM get_plan_details('SELECT j FROM test_scan;'); +set enable_seqscan = off; +set enable_indexscan = off; +set enable_bitmapscan = on; +SELECT * FROM get_plan_details('SELECT * FROM test_scan;'); +SELECT * FROM get_plan_details('SELECT * FROM test_scan WHERE j = 1;'); +SELECT * FROM get_plan_details('SELECT j FROM test_scan;'); +set enable_indexonlyscan = off; +SELECT * FROM get_plan_details('SELECT j FROM test_scan;'); + +DROP FUNCTION get_plan_details; + +-- Show transaction priority. As it is not possible to have a deterministic +-- yb_transaction_priority, we set yb_transaction_priority_lower_bound and +-- yb_transaction_priority_upper_bound to be the same, which forces +-- yb_transaction_priority to be equal to those two. +set yb_transaction_priority_lower_bound = 0.4; +set yb_transaction_priority_upper_bound = 0.4; +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; +INSERT INTO test_scan (i, j) values (1, 1), (2, 2), (3, 3); +show yb_transaction_priority; +COMMIT; + +-- Trying to set yb_transaction_priority will be an error +set yb_transaction_priority = 0.3; -- ERROR + +-- High priority transaction +set yb_transaction_priority_lower_bound = 0.4; +set yb_transaction_priority_upper_bound = 0.4; +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; +SELECT i, j FROM test_scan WHERE i = 1 FOR UPDATE; +show yb_transaction_priority; +COMMIT; + +-- Highest priority transaction +set yb_transaction_priority_upper_bound = 1; +set yb_transaction_priority_lower_bound = 1; +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; +SELECT i, j FROM test_scan WHERE i = 1 FOR UPDATE; +show yb_transaction_priority; +COMMIT; + +-- Showing yb_transaction_priority outside a transaction block +show yb_transaction_priority; + +-- SET LOCAL is restricted by a function SET option +create or replace function myfunc(int) returns text as $$ +begin + set local work_mem = '2MB'; + return current_setting('work_mem'); +end $$ +language plpgsql +set work_mem = '1MB'; + +select myfunc(0), current_setting('work_mem'); + +-- test SET unrecognized parameter +SET foo = false; -- no such setting + +-- test temp_file_limit default +SHOW temp_file_limit; +-- test temp_File_limit update +SET temp_file_limit="100MB"; +SHOW temp_file_limit; +SET temp_file_limit=-1; +SHOW temp_file_limit; + +-- test `yb_db_admin` role can set and reset yb_db_admin-allowed PGC_SUSET variables +SET SESSION AUTHORIZATION yb_db_admin; +SHOW session_replication_role; +SET session_replication_role TO replica; +SHOW session_replication_role; +RESET session_replication_role; +SHOW session_replication_role; +-- test `yb_db_admin` role cannot set and reset other PGC_SUSET variables +SET track_functions TO TRACK_FUNC_PL; +RESET track_functions; + +-- cleanup +RESET foo; +RESET yb_enable_bitmapscan; diff --git a/src/test/regress/sql/yb.orig.hash_func.sql b/src/test/regress/sql/yb.orig.hash_func.sql new file mode 100644 index 0000000000000..89c91f72b7639 --- /dev/null +++ b/src/test/regress/sql/yb.orig.hash_func.sql @@ -0,0 +1,231 @@ +-- +-- Test hash functions +-- +-- When the salt is 0, the extended hash function should produce a result +-- whose low 32 bits match the standard hash function. When the salt is +-- not 0, we should get a different result. +-- + +SELECT v as value, hashint2(v)::bit(32) as standard, + hashint2extended(v, 0)::bit(32) as extended0, + hashint2extended(v, 1)::bit(32) as extended1 +FROM (VALUES (0::int2), (1::int2), (17::int2), (42::int2)) x(v) +WHERE hashint2(v)::bit(32) != hashint2extended(v, 0)::bit(32) + OR hashint2(v)::bit(32) = hashint2extended(v, 1)::bit(32); + +SELECT v as value, hashint4(v)::bit(32) as standard, + hashint4extended(v, 0)::bit(32) as extended0, + hashint4extended(v, 1)::bit(32) as extended1 +FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) +WHERE hashint4(v)::bit(32) != hashint4extended(v, 0)::bit(32) + OR hashint4(v)::bit(32) = hashint4extended(v, 1)::bit(32); + +SELECT v as value, hashint8(v)::bit(32) as standard, + hashint8extended(v, 0)::bit(32) as extended0, + hashint8extended(v, 1)::bit(32) as extended1 +FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) +WHERE hashint8(v)::bit(32) != hashint8extended(v, 0)::bit(32) + OR hashint8(v)::bit(32) = hashint8extended(v, 1)::bit(32); + +SELECT v as value, hashfloat4(v)::bit(32) as standard, + hashfloat4extended(v, 0)::bit(32) as extended0, + hashfloat4extended(v, 1)::bit(32) as extended1 +FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) +WHERE hashfloat4(v)::bit(32) != hashfloat4extended(v, 0)::bit(32) + OR hashfloat4(v)::bit(32) = hashfloat4extended(v, 1)::bit(32); + +SELECT v as value, hashfloat8(v)::bit(32) as standard, + hashfloat8extended(v, 0)::bit(32) as extended0, + hashfloat8extended(v, 1)::bit(32) as extended1 +FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) +WHERE hashfloat8(v)::bit(32) != hashfloat8extended(v, 0)::bit(32) + OR hashfloat8(v)::bit(32) = hashfloat8extended(v, 1)::bit(32); + +SELECT v as value, hashoid(v)::bit(32) as standard, + hashoidextended(v, 0)::bit(32) as extended0, + hashoidextended(v, 1)::bit(32) as extended1 +FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) +WHERE hashoid(v)::bit(32) != hashoidextended(v, 0)::bit(32) + OR hashoid(v)::bit(32) = hashoidextended(v, 1)::bit(32); + +SELECT v as value, hashchar(v)::bit(32) as standard, + hashcharextended(v, 0)::bit(32) as extended0, + hashcharextended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::"char"), ('1'), ('x'), ('X'), ('p'), ('N')) x(v) +WHERE hashchar(v)::bit(32) != hashcharextended(v, 0)::bit(32) + OR hashchar(v)::bit(32) = hashcharextended(v, 1)::bit(32); + +SELECT v as value, hashname(v)::bit(32) as standard, + hashnameextended(v, 0)::bit(32) as extended0, + hashnameextended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), + ('muop28x03'), ('yi3nm0d73')) x(v) +WHERE hashname(v)::bit(32) != hashnameextended(v, 0)::bit(32) + OR hashname(v)::bit(32) = hashnameextended(v, 1)::bit(32); + +SELECT v as value, hashtext(v)::bit(32) as standard, + hashtextextended(v, 0)::bit(32) as extended0, + hashtextextended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), + ('muop28x03'), ('yi3nm0d73')) x(v) +WHERE hashtext(v)::bit(32) != hashtextextended(v, 0)::bit(32) + OR hashtext(v)::bit(32) = hashtextextended(v, 1)::bit(32); + +SELECT v as value, hashoidvector(v)::bit(32) as standard, + hashoidvectorextended(v, 0)::bit(32) as extended0, + hashoidvectorextended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::oidvector), ('0 1 2 3 4'), ('17 18 19 20'), + ('42 43 42 45'), ('550273 550273 570274'), + ('207112489 207112499 21512 2155 372325 1363252')) x(v) +WHERE hashoidvector(v)::bit(32) != hashoidvectorextended(v, 0)::bit(32) + OR hashoidvector(v)::bit(32) = hashoidvectorextended(v, 1)::bit(32); + +SELECT v as value, hash_aclitem(v)::bit(32) as standard, + hash_aclitem_extended(v, 0)::bit(32) as extended0, + hash_aclitem_extended(v, 1)::bit(32) as extended1 +FROM (SELECT DISTINCT(relacl[1]) FROM pg_class LIMIT 10) x(v) +WHERE hash_aclitem(v)::bit(32) != hash_aclitem_extended(v, 0)::bit(32) + OR hash_aclitem(v)::bit(32) = hash_aclitem_extended(v, 1)::bit(32); + +SELECT v as value, hashmacaddr(v)::bit(32) as standard, + hashmacaddrextended(v, 0)::bit(32) as extended0, + hashmacaddrextended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::macaddr), ('08:00:2b:01:02:04'), ('08:00:2b:01:02:04'), + ('e2:7f:51:3e:70:49'), ('d6:a9:4a:78:1c:d5'), + ('ea:29:b1:5e:1f:a5')) x(v) +WHERE hashmacaddr(v)::bit(32) != hashmacaddrextended(v, 0)::bit(32) + OR hashmacaddr(v)::bit(32) = hashmacaddrextended(v, 1)::bit(32); + +SELECT v as value, hashinet(v)::bit(32) as standard, + hashinetextended(v, 0)::bit(32) as extended0, + hashinetextended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::inet), ('192.168.100.128/25'), ('192.168.100.0/8'), + ('172.168.10.126/16'), ('172.18.103.126/24'), ('192.188.13.16/32')) x(v) +WHERE hashinet(v)::bit(32) != hashinetextended(v, 0)::bit(32) + OR hashinet(v)::bit(32) = hashinetextended(v, 1)::bit(32); + +SELECT v as value, hash_numeric(v)::bit(32) as standard, + hash_numeric_extended(v, 0)::bit(32) as extended0, + hash_numeric_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (0), (1.149484958), (17.149484958), (42.149484958), + (149484958.550273), (2071124898672)) x(v) +WHERE hash_numeric(v)::bit(32) != hash_numeric_extended(v, 0)::bit(32) + OR hash_numeric(v)::bit(32) = hash_numeric_extended(v, 1)::bit(32); + +SELECT v as value, hashmacaddr8(v)::bit(32) as standard, + hashmacaddr8extended(v, 0)::bit(32) as extended0, + hashmacaddr8extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::macaddr8), ('08:00:2b:01:02:04:36:49'), + ('08:00:2b:01:02:04:f0:e8'), ('e2:7f:51:3e:70:49:16:29'), + ('d6:a9:4a:78:1c:d5:47:32'), ('ea:29:b1:5e:1f:a5')) x(v) +WHERE hashmacaddr8(v)::bit(32) != hashmacaddr8extended(v, 0)::bit(32) + OR hashmacaddr8(v)::bit(32) = hashmacaddr8extended(v, 1)::bit(32); + +SELECT v as value, hash_array(v)::bit(32) as standard, + hash_array_extended(v, 0)::bit(32) as extended0, + hash_array_extended(v, 1)::bit(32) as extended1 +FROM (VALUES ('{0}'::int4[]), ('{0,1,2,3,4}'), ('{17,18,19,20}'), + ('{42,34,65,98}'), ('{550273,590027, 870273}'), + ('{207112489, 807112489}')) x(v) +WHERE hash_array(v)::bit(32) != hash_array_extended(v, 0)::bit(32) + OR hash_array(v)::bit(32) = hash_array_extended(v, 1)::bit(32); + +SELECT v as value, hashbpchar(v)::bit(32) as standard, + hashbpcharextended(v, 0)::bit(32) as extended0, + hashbpcharextended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), + ('muop28x03'), ('yi3nm0d73')) x(v) +WHERE hashbpchar(v)::bit(32) != hashbpcharextended(v, 0)::bit(32) + OR hashbpchar(v)::bit(32) = hashbpcharextended(v, 1)::bit(32); + +SELECT v as value, time_hash(v)::bit(32) as standard, + time_hash_extended(v, 0)::bit(32) as extended0, + time_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::time), ('11:09:59'), ('1:09:59'), ('11:59:59'), + ('7:9:59'), ('5:15:59')) x(v) +WHERE time_hash(v)::bit(32) != time_hash_extended(v, 0)::bit(32) + OR time_hash(v)::bit(32) = time_hash_extended(v, 1)::bit(32); + +SELECT v as value, timetz_hash(v)::bit(32) as standard, + timetz_hash_extended(v, 0)::bit(32) as extended0, + timetz_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::timetz), ('00:11:52.518762-07'), ('00:11:52.51762-08'), + ('00:11:52.62-01'), ('00:11:52.62+01'), ('11:59:59+04')) x(v) +WHERE timetz_hash(v)::bit(32) != timetz_hash_extended(v, 0)::bit(32) + OR timetz_hash(v)::bit(32) = timetz_hash_extended(v, 1)::bit(32); + +SELECT v as value, interval_hash(v)::bit(32) as standard, + interval_hash_extended(v, 0)::bit(32) as extended0, + interval_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::interval), + ('5 month 7 day 46 minutes'), ('1 year 7 day 46 minutes'), + ('1 year 7 month 20 day 46 minutes'), ('5 month'), + ('17 year 11 month 7 day 9 hours 46 minutes 5 seconds')) x(v) +WHERE interval_hash(v)::bit(32) != interval_hash_extended(v, 0)::bit(32) + OR interval_hash(v)::bit(32) = interval_hash_extended(v, 1)::bit(32); + +SELECT v as value, timestamp_hash(v)::bit(32) as standard, + timestamp_hash_extended(v, 0)::bit(32) as extended0, + timestamp_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::timestamp), ('2017-08-22 00:09:59.518762'), + ('2015-08-20 00:11:52.51762-08'), + ('2017-05-22 00:11:52.62-01'), + ('2013-08-22 00:11:52.62+01'), ('2013-08-22 11:59:59+04')) x(v) +WHERE timestamp_hash(v)::bit(32) != timestamp_hash_extended(v, 0)::bit(32) + OR timestamp_hash(v)::bit(32) = timestamp_hash_extended(v, 1)::bit(32); + +SELECT v as value, uuid_hash(v)::bit(32) as standard, + uuid_hash_extended(v, 0)::bit(32) as extended0, + uuid_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::uuid), ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'), + ('5a9ba4ac-8d6f-11e7-bb31-be2e44b06b34'), + ('99c6705c-d939-461c-a3c9-1690ad64ed7b'), + ('7deed3ca-8d6f-11e7-bb31-be2e44b06b34'), + ('9ad46d4f-6f2a-4edd-aadb-745993928e1e')) x(v) +WHERE uuid_hash(v)::bit(32) != uuid_hash_extended(v, 0)::bit(32) + OR uuid_hash(v)::bit(32) = uuid_hash_extended(v, 1)::bit(32); + +SELECT v as value, pg_lsn_hash(v)::bit(32) as standard, + pg_lsn_hash_extended(v, 0)::bit(32) as extended0, + pg_lsn_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::pg_lsn), ('16/B374D84'), ('30/B374D84'), + ('255/B374D84'), ('25/B379D90'), ('900/F37FD90')) x(v) +WHERE pg_lsn_hash(v)::bit(32) != pg_lsn_hash_extended(v, 0)::bit(32) + OR pg_lsn_hash(v)::bit(32) = pg_lsn_hash_extended(v, 1)::bit(32); + +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); +SELECT v as value, hashenum(v)::bit(32) as standard, + hashenumextended(v, 0)::bit(32) as extended0, + hashenumextended(v, 1)::bit(32) as extended1 +FROM (VALUES ('sad'::mood), ('ok'), ('happy')) x(v) +WHERE hashenum(v)::bit(32) != hashenumextended(v, 0)::bit(32) + OR hashenum(v)::bit(32) = hashenumextended(v, 1)::bit(32); +DROP TYPE mood; + +SELECT v as value, jsonb_hash(v)::bit(32) as standard, + jsonb_hash_extended(v, 0)::bit(32) as extended0, + jsonb_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::jsonb), + ('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'), + ('{"foo": [true, "bar"], "tags": {"e": 1, "f": null}}'), + ('{"g": {"h": "value"}}')) x(v) +WHERE jsonb_hash(v)::bit(32) != jsonb_hash_extended(v, 0)::bit(32) + OR jsonb_hash(v)::bit(32) = jsonb_hash_extended(v, 1)::bit(32); + +SELECT v as value, hash_range(v)::bit(32) as standard, + hash_range_extended(v, 0)::bit(32) as extended0, + hash_range_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (int4range(10, 20)), (int4range(23, 43)), + (int4range(5675, 550273)), + (int4range(550274, 1550274)), (int4range(1550275, 208112489))) x(v) +WHERE hash_range(v)::bit(32) != hash_range_extended(v, 0)::bit(32) + OR hash_range(v)::bit(32) = hash_range_extended(v, 1)::bit(32); + +-- +-- Check special cases for specific data types +-- +SELECT hashfloat4('0'::float4) = hashfloat4('-0'::float4) AS t; +SELECT hashfloat4('NaN'::float4) = hashfloat4(-'NaN'::float4) AS t; +SELECT hashfloat8('0'::float8) = hashfloat8('-0'::float8) AS t; +SELECT hashfloat8('NaN'::float8) = hashfloat8(-'NaN'::float8) AS t; +SELECT hashfloat4('NaN'::float4) = hashfloat8('NaN'::float8) AS t; diff --git a/src/test/regress/sql/yb.orig.hash_in_queries.sql b/src/test/regress/sql/yb.orig.hash_in_queries.sql new file mode 100644 index 0000000000000..5bb0142f0524c --- /dev/null +++ b/src/test/regress/sql/yb.orig.hash_in_queries.sql @@ -0,0 +1,224 @@ +-- Testing IN queries on hash keys +set yb_enable_hash_batch_in = false; +CREATE TABLE test_method (h1 int, a int, b int, c int, d int, e int, PRIMARY KEY (h1 HASH)); +INSERT INTO test_method VALUES (1, 1, 1, 1, 1, 11), (2, 1, 1, 2, 2, 12), (19, 1, 2, 1, 3, 13), (3, 1, 2, 2, 4, 14), (4, 2, 1, 1, 5, 15), (5, 2, 1, 2, 6, 16), (6, 2, 2, 1, 7, 17), (7, 2, 2, 2, 8, 18), (8, 2, 999, 2, 8, 18), (9, 0, 1, 1, 9, 19), (10, 1, 1, 2, 10, 20), (11, 3, 1, 1, 1, 11), (12, 3, 1, 2, 2, 12), (13, 3, 2, 1, 3, 13), (14, 3, 2, 2, 4, 14), (15, 4, 1, 1, 1, 11), (16, 4, 1, 2, 2, 12), (17, 4, 2, 1, 3, 13), (18, 4, 2, 2, 4, 14); +SELECT * FROM test_method where h1 = 1; +SELECT * FROM test_method where h1 = 1 AND a = 1; +SELECT * FROM test_method where h1 = 1 AND b = 2; +SELECT * FROM test_method where h1 = 2 AND a = 1 AND b = 1; +SELECT * FROM test_method where h1 = 1 AND a IN (1, 2, 3, 4); +SELECT * FROM test_method where h1 = 1 AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); + +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4); +SELECT * FROM test_method where h1 IN (1, 2, 3, 4); + +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND a = 1; +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND a = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND yb_hash_code(h1) < 8192; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND a = 1 AND yb_hash_code(h1) < 8192; + +set yb_enable_hash_batch_in = true; +SELECT * FROM test_method where h1 = 1; +SELECT * FROM test_method where h1 = 1 AND a = 1; +SELECT * FROM test_method where h1 = 1 AND b = 2; +SELECT * FROM test_method where h1 = 2 AND a = 1 AND b = 1; +SELECT * FROM test_method where h1 = 1 AND a IN (1, 2, 3, 4); +SELECT * FROM test_method where h1 = 1 AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4); +SELECT * FROM test_method where h1 IN (1, 2, 3, 4); + +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND a = 1; +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND a = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND yb_hash_code(h1) < 8192; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND a = 1 AND yb_hash_code(h1) < 8192; +DROP TABLE test_method; + +-- Testing IN queries on range keys +set yb_enable_hash_batch_in = false; +CREATE TABLE test_method (r1 int, a int, b int, c int, d int, e int, PRIMARY KEY (r1 ASC)); +INSERT INTO test_method VALUES (1, 1, 1, 1, 1, 11), (2, 1, 1, 2, 2, 12), (19, 1, 2, 1, 3, 13), (3, 1, 2, 2, 4, 14), (4, 2, 1, 1, 5, 15), (5, 2, 1, 2, 6, 16), (6, 2, 2, 1, 7, 17), (7, 2, 2, 2, 8, 18), (8, 2, 999, 2, 8, 18), (9, 0, 1, 1, 9, 19), (10, 1, 1, 2, 10, 20), (11, 3, 1, 1, 1, 11), (12, 3, 1, 2, 2, 12), (13, 3, 2, 1, 3, 13), (14, 3, 2, 2, 4, 14), (15, 4, 1, 1, 1, 11), (16, 4, 1, 2, 2, 12), (17, 4, 2, 1, 3, 13), (18, 4, 2, 2, 4, 14); +SELECT * FROM test_method where r1 = 1; +SELECT * FROM test_method where r1 = 1 AND a = 1; +SELECT * FROM test_method where r1 = 1 AND b = 2; +SELECT * FROM test_method where r1 = 2 AND a = 1 AND b = 1; +SELECT * FROM test_method where r1 = 1 AND a IN (1, 2, 3, 4); +SELECT * FROM test_method where r1 = 1 AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); +SELECT * FROM test_method where r1 IN (1, 2, 3, 4); +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND a = 1; +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND b = 2; +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND a = 1 AND b = 1; +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND a IN (1, 2, 3, 4); +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); +set yb_enable_hash_batch_in = true; +SELECT * FROM test_method where r1 = 1; +SELECT * FROM test_method where r1 = 1 AND a = 1; +SELECT * FROM test_method where r1 = 1 AND b = 2; +SELECT * FROM test_method where r1 = 2 AND a = 1 AND b = 1; +SELECT * FROM test_method where r1 = 1 AND a IN (1, 2, 3, 4); +SELECT * FROM test_method where r1 = 1 AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); +SELECT * FROM test_method where r1 IN (1, 2, 3, 4); +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND a = 1; +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND b = 2; +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND a = 1 AND b = 1; +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND a IN (1, 2, 3, 4); +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); +DROP TABLE test_method; + +-- Testing IN queries on multi column hash keys +CREATE TABLE test_method (h1 int, h2 int, a int, b int, v1 int, v2 int, PRIMARY KEY ((h1, h2) HASH)); +INSERT INTO test_method VALUES (1, 1, 1, 1, 1, 11), (2, 1, 1, 2, 2, 12), (19, 1, 2, 1, 3, 13), (3, 1, 2, 2, 4, 14), (4, 2, 1, 1, 5, 15), (5, 2, 1, 2, 6, 16), (6, 2, 2, 1, 7, 17), (7, 2, 2, 2, 8, 18), (8, 2, 999, 2, 8, 18); +INSERT INTO test_method VALUES (9, 0, 1, 1, 9, 19), (10, 1, 1, 2, 10, 20), (11, 3, 1, 1, 1, 11), (12, 3, 1, 2, 2, 12), (13, 3, 2, 1, 3, 13), (14, 3, 2, 2, 4, 14), (15, 4, 1, 1, 1, 11), (16, 4, 1, 2, 2, 12), (17, 4, 2, 1, 3, 13), (18, 4, 2, 2, 4, 14); +set yb_enable_hash_batch_in = false; +SELECT * FROM test_method where h1 = 1 ; +SELECT * FROM test_method where h1 = 1 AND h2 = 2; +SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND a = 1; +SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND a = 1 AND b = 2; +SELECT * FROM test_method where h1 = 1 AND h2 = 1 AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2); +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2); +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND yb_hash_code(h1, h2) < 8192; + +set yb_enable_hash_batch_in = true; +SELECT * FROM test_method where h1 = 1 ; +SELECT * FROM test_method where h1 = 1 AND h2 = 2; +SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND a = 1; +SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND a = 1 AND b = 2; +SELECT * FROM test_method where h1 = 1 AND h2 = 1 AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2); +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2); +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND yb_hash_code(h1, h2) < 8192; +DROP TABLE test_method; + +-- Testing IN queries on multi column range keys +CREATE TABLE test_method (r1 int, r2 int, a int, b int, v1 int, v2 int, PRIMARY KEY (r1 ASC, r2 ASC)); +INSERT INTO test_method VALUES (1, 1, 1, 1, 1, 11), (2, 1, 1, 2, 2, 12), (19, 1, 2, 1, 3, 13), (3, 1, 2, 2, 4, 14), (4, 2, 1, 1, 5, 15), (5, 2, 1, 2, 6, 16), (6, 2, 2, 1, 7, 17), (7, 2, 2, 2, 8, 18), (8, 2, 999, 2, 8, 18); +INSERT INTO test_method VALUES (9, 0, 1, 1, 9, 19), (10, 1, 1, 2, 10, 20), (11, 3, 1, 1, 1, 11), (12, 3, 1, 2, 2, 12), (13, 3, 2, 1, 3, 13), (14, 3, 2, 2, 4, 14), (15, 4, 1, 1, 1, 11), (16, 4, 1, 2, 2, 12), (17, 4, 2, 1, 3, 13), (18, 4, 2, 2, 4, 14); +set yb_enable_hash_batch_in = false; +SELECT * FROM test_method where r1 = 1 ; +SELECT * FROM test_method where r1 = 1 AND r2 = 2; +SELECT * FROM test_method where r1 = 1 AND r2 = 2 AND a = 1; +SELECT * FROM test_method where r1 = 1 AND r2 = 2 AND a = 1 AND b = 2; +SELECT * FROM test_method where r1 = 1 AND r2 = 1 AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND r2 IN (1, 2); +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND r2 IN (1, 2) AND a = 1; +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND r2 IN (1, 2) AND a = 1 AND b = 2; +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND r2 IN (1, 2) AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); +set yb_enable_hash_batch_in = true; +SELECT * FROM test_method where r1 = 1 ; +SELECT * FROM test_method where r1 = 1 AND r2 = 2; +SELECT * FROM test_method where r1 = 1 AND r2 = 2 AND a = 1; +SELECT * FROM test_method where r1 = 1 AND r2 = 2 AND a = 1 AND b = 2; +SELECT * FROM test_method where r1 = 1 AND r2 = 1 AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND r2 IN (1, 2); +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND r2 IN (1, 2) AND a = 1; +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND r2 IN (1, 2) AND a = 1 AND b = 2; +SELECT * FROM test_method where r1 IN (1, 2, 3, 4) AND r2 IN (1, 2) AND a IN (1, 2, 3, 4) AND b IN (1, 2, 3, 4); +DROP TABLE test_method; + + +-- Testing IN queries on multi column hash and range keys +CREATE TABLE test_method (h1 int, h2 int, r1 int, r2 int, v1 int, v2 int, PRIMARY KEY ((h1, h2) HASH, r1, r2)); +INSERT INTO test_method VALUES (1, 1, 1, 1, 1, 11), (1, 1, 1, 2, 2, 12), (1, 1, 2, 1, 3, 13), (1, 1, 2, 2, 4, 14), (1, 2, 1, 1, 5, 15), (1, 2, 1, 2, 6, 16), (1, 2, 2, 1, 7, 17), (1, 2, 2, 2, 8, 18), (1, 2, 999, 2, 8, 18); +INSERT INTO test_method VALUES (2, 0, 1, 1, 9, 19), (2, 1, 1, 2, 10, 20), (1, 3, 1, 1, 1, 11), (1, 3, 1, 2, 2, 12), (1, 3, 2, 1, 3, 13), (1, 3, 2, 2, 4, 14), (1, 4, 1, 1, 1, 11), (1, 4, 1, 2, 2, 12), (1, 4, 2, 1, 3, 13), (1, 4, 2, 2, 4, 14); + +set yb_enable_hash_batch_in = false; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 = 1 ; +SELECT * FROM test_method where h1 = 1 ; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 = 1 AND h2 = 2; +SELECT * FROM test_method where h1 = 1 AND h2 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND r1 = 1; +SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND r1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND r1 = 1 AND r2 = 2; +SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND r1 = 1 AND r2 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 = 1 AND h2 = 1 AND r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4); +SELECT * FROM test_method where h1 = 1 AND h2 = 1 AND r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4); +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2); +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2); +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1; +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1 AND r2 = 2; +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1 AND r2 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4); +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4); +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND yb_hash_code(h1, h2) < 8192; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1 AND yb_hash_code(h1, h2) < 8192; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1 AND r2 = 2 AND yb_hash_code(h1, h2) < 8192; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4) AND yb_hash_code(h1, h2) < 8192; + +set yb_enable_hash_batch_in = true; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 = 1 ; +SELECT * FROM test_method where h1 = 1 ; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 = 1 AND h2 = 2; +SELECT * FROM test_method where h1 = 1 AND h2 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND r1 = 1; +SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND r1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND r1 = 1 AND r2 = 2; +SELECT * FROM test_method where h1 = 1 AND h2 = 2 AND r1 = 1 AND r2 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 = 1 AND h2 = 1 AND r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4); +SELECT * FROM test_method where h1 = 1 AND h2 = 1 AND r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4); +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2); +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2); +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1; +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1 AND r2 = 2; +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1 AND r2 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4); +SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4); +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND yb_hash_code(h1, h2) < 8192; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1 AND yb_hash_code(h1, h2) < 8192; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 = 1 AND r2 = 2 AND yb_hash_code(h1, h2) < 8192; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method where h1 IN (1, 2, 3, 4) AND h2 IN (1, 2) AND r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4) AND yb_hash_code(h1, h2) < 8192; +DROP TABLE test_method; + +CREATE TABLE test_method (h1 int, h2 int, h3 int, h4 int, v1 int, v2 int, PRIMARY KEY ((h1, h2, h3, h4) HASH)); +INSERT INTO test_method VALUES (1, 1, 1, 1, 1, 11), (1, 1, 1, 2, 2, 12), (1, 1, 2, 1, 3, 13), (1, 1, 2, 2, 4, 14), (1, 2, 1, 1, 5, 15), (1, 2, 1, 2, 6, 16), (1, 2, 2, 1, 7, 17), (1, 2, 2, 2, 8, 18), (1, 2, 999, 2, 8, 18); +INSERT INTO test_method VALUES (2, 0, 1, 1, 9, 19), (2, 1, 1, 2, 10, 20), (1, 3, 1, 1, 1, 11), (1, 3, 1, 2, 2, 12), (1, 3, 2, 1, 3, 13), (1, 3, 2, 2, 4, 14), (1, 4, 1, 1, 1, 11), (1, 4, 1, 2, 2, 12), (1, 4, 2, 1, 3, 13), (1, 4, 2, 2, 4, 14); +-- Set a high work_mem limit so we don't limit the size of each request batch +SET work_mem = 2147483647; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method WHERE h1 IN (1,2,3,4,5,6,7,8,9,0) AND h2 IN (1,2,3,4,5,6,7,8,9,0) AND h3 IN (1,2,3,4,5,6,7,8,9,0) AND h4 IN (1,2,3,4,5,6,7,8,9,0); + +-- Set a low work_mem limit so we limit the size of each request batch and cause +-- more requests to be sent out. +SET work_mem = 64; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM test_method WHERE h1 IN (1,2,3,4,5,6,7,8,9,0) AND h2 IN (1,2,3,4,5,6,7,8,9,0) AND h3 IN (1,2,3,4,5,6,7,8,9,0) AND h4 IN (1,2,3,4,5,6,7,8,9,0); +DROP TABLE test_method; + +CREATE TABLE sample (h int primary key); +INSERT INTO sample VALUES (24), (262095); +SELECT * FROM sample WHERE h = ANY('{{{24, 101}, {911, 262095}}}') ORDER BY h; +DROP TABLE sample; + +CREATE TABLE in_with_single_asc_key (r INT, v INT, PRIMARY KEY(r ASC)) SPLIT AT VALUES((100)); +INSERT INTO in_with_single_asc_key VALUES(1, 1), (3, 3), (2, 2), (101, 101), (102, 102), (103, 103); +SELECT * FROM in_with_single_asc_key WHERE r IN (1, 3, 2, 102, 103, 101) ORDER BY r ASC; +SELECT * FROM in_with_single_asc_key WHERE r IN (1, 3, 2, 102, 103, 101) ORDER BY r DESC; +DROP TABLE in_with_single_asc_key; + +CREATE TABLE in_with_single_desc_key (r INT, v INT, PRIMARY KEY(r DESC)) SPLIT AT VALUES((100)); +INSERT INTO in_with_single_desc_key VALUES(1, 1), (3, 3), (2, 2), (101, 101), (102, 102), (103, 103); +SELECT * FROM in_with_single_desc_key WHERE r IN (1, 3, 2, 102, 103, 101) ORDER BY r ASC; +SELECT * FROM in_with_single_desc_key WHERE r IN (1, 3, 2, 102, 103, 101) ORDER BY r DESC; +DROP TABLE in_with_single_desc_key; + +CREATE TABLE in_with_compound_asc_key (r1 INT, r2 INT, v INT, PRIMARY KEY(r1 ASC, r2 ASC)) SPLIT AT VALUES((1, 100)); +INSERT INTO in_with_compound_asc_key VALUES(1, 1, 1), (1, 3, 3), (1, 2, 2), (1, 101, 101), (1, 102, 102), (1, 103, 103); +SELECT * FROM in_with_compound_asc_key WHERE r1 = 1 AND r2 IN (1, 3, 2, 102, 103, 101) ORDER BY r1 DESC, r2 DESC; +SELECT * FROM in_with_compound_asc_key WHERE r1 = 1 AND r2 IN (1, 3, 2, 102, 103, 101) ORDER BY r1 DESC, r2 ASC; +SELECT * FROM in_with_compound_asc_key WHERE r1 = 1 AND r2 IN (1, 3, 2, 102, 103, 101) ORDER BY r1 ASC, r2 DESC; +SELECT * FROM in_with_compound_asc_key WHERE r1 = 1 AND r2 IN (1, 3, 2, 102, 103, 101) ORDER BY r1 ASC, r2 ASC; +DROP TABLE in_with_compound_asc_key; + +CREATE TABLE in_with_compound_desc_key (r1 INT, r2 INT, v INT, PRIMARY KEY(r1 ASC, r2 DESC)) SPLIT AT VALUES((1, 100)); +INSERT INTO in_with_compound_desc_key VALUES(1, 1, 1), (1, 3, 3), (1, 2, 2), (1, 101, 101), (1, 102, 102), (1, 103, 103); +SELECT * FROM in_with_compound_desc_key WHERE r1 = 1 AND r2 IN (1, 3, 2, 102, 103, 101) ORDER BY r1 DESC, r2 DESC; +SELECT * FROM in_with_compound_desc_key WHERE r1 = 1 AND r2 IN (1, 3, 2, 102, 103, 101) ORDER BY r1 DESC, r2 ASC; +SELECT * FROM in_with_compound_desc_key WHERE r1 = 1 AND r2 IN (1, 3, 2, 102, 103, 101) ORDER BY r1 ASC, r2 DESC; +SELECT * FROM in_with_compound_desc_key WHERE r1 = 1 AND r2 IN (1, 3, 2, 102, 103, 101) ORDER BY r1 ASC, r2 ASC; +DROP TABLE in_with_compound_desc_key; + +-- GHI #29041 +create table test_29041(id varchar primary key, f1 varchar, f2 timestamptz, f3 timestamptz) split into 6 tablets; +insert into test_29041 select 'first_col_filler' || i::varchar, 'second_col_filler' || i::varchar, now(), now() + '10 days'::interval from generate_series(1, 100000) i; +SET work_mem = '256MB'; +explain (analyze, dist) select * from test_29041 where id = ANY (array(select 'first_col_filler' || i::varchar from generate_series(1, 10000) i)) and yb_hash_code(id) >= 0 and yb_hash_code(id) < 8192; +drop table test_29041; diff --git a/src/test/regress/sql/yb.orig.hdr_percentile.sql b/src/test/regress/sql/yb.orig.hdr_percentile.sql new file mode 100644 index 0000000000000..606a7722f9aa0 --- /dev/null +++ b/src/test/regress/sql/yb.orig.hdr_percentile.sql @@ -0,0 +1,18 @@ +-- Testing hdr percentile function +SELECT yb_get_percentile('[{"[384.0,409.6)": 5}, {"[768.0,819.2)": 4}, {"[1126.4,1228.8)": 1}]', 50); +SELECT yb_get_percentile('[{"[384.0,409.6)": 5}, {"[768.0,819.2)": 4}, {"[1126.4,1228.8)": 1}]', 90); +SELECT yb_get_percentile('[{"[384.0,409.6)": 5}, {"[768.0,819.2)": 4}, {"[1126.4,1228.8)": 1}]', 99); +SELECT yb_get_percentile('[{"[384.0,409.6)": 5}, {"[768.0,819.2)": 4}, {"[1126.4,1228.8)": 1}]', 0); +SELECT yb_get_percentile('[{"[384.0,409.6)": 5}, {"[768.0,819.2)": 4}, {"[1126.4,1228.8)": 1}]', -0.1); +SELECT yb_get_percentile('[{"[384.0,409.6)": 5}, {"[768.0,819.2)": 4}, {"[1126.4,1228.8)": 1}]', 8892.3); +SELECT yb_get_percentile('[]', 90); +SELECT yb_get_percentile('[{"[-2.8,2e4)": 8}]', -10); +SELECT yb_get_percentile('[{"[-2.8,2e4)": 8}]', 90); +SELECT yb_get_percentile('[{"[-1.1e-3,5000)": 5}, {}]', -10); +SELECT yb_get_percentile('[{"[-1.1e-3,5000)": 5}, {}]', 100); +SELECT yb_get_percentile('[{"[12,)": 8}]', 0); +SELECT yb_get_percentile('[{"[12,)": 8}]', 50); +SELECT yb_get_percentile('[{"[12,)": 8}]', 100); +SELECT yb_get_percentile('[{"[1,2)": 5}, {"[3,4)": 4}, {"[5,)": 1}]', 50); +SELECT yb_get_percentile('[{"[1,2)": 5}, {"[3,4)": 4}, {"[5,)": 1}]', 90); +SELECT yb_get_percentile('[{"[1,2)": 5}, {"[3,4)": 4}, {"[5,)": 1}]', 99); diff --git a/src/test/regress/sql/yb.orig.hints.sql b/src/test/regress/sql/yb.orig.hints.sql new file mode 100644 index 0000000000000..8b4ff7dcecf59 --- /dev/null +++ b/src/test/regress/sql/yb.orig.hints.sql @@ -0,0 +1,491 @@ +\set ECHO all +SET client_min_messages = warning; +drop schema if exists yb_hints cascade; +create schema yb_hints; +reset all; +set search_path to yb_hints; + +-- Turn on internal hint generation testing. For all query without hints, +-- hints will be generated and used to generate a plan that should be +-- identical (in terms on plan shape and access/join methods) to the one +-- the planner returned for the query originally. +set pg_hint_plan.yb_enable_internal_hint_test to on; + +-- Want to issue a warning if we find bad hints. +set pg_hint_plan.yb_bad_hint_mode to warn; + +create table t0 (a0 int, b0 int, c0 int, d0 int, e0 int, f0 int, g0 int, h0 int, i0 int, j0 int, k0 int, ch0 char(5), vch0 varchar(5), unn0 int unique not null, primary key(unn0)); +insert into t0 values(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '00000', '00000', 0); +insert into t0 values(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, '11111', '11111', 1); +insert into t0 values(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, '22222', '22222', 2); +insert into t0 values(3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, '33333', '33333', 3); +insert into t0 values(3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, '3333', '3333', 4); +insert into t0 values(null, null, null, null, null, null, null, null, null, null, null, null, null, 5); +insert into t0 values(6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, '66666', '66666', 6); +insert into t0 values(7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, '77777', '77777', 7); +insert into t0 values(7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, '7777', '7777', 8); +insert into t0 values(null, null, null, null, null, null, null, null, null, null, null, null, null, 9); +create table t1 (a1 int, b1 int, c1 int, d1 int, ch1 char(5), vch1 varchar(5), unn1 int unique not null, primary key(unn1)); +insert into t1 select a0, b0, c0, d0, ch0, vch0, unn0 from t0; +insert into t1 select a0, b0, c0, d0, ch0, vch0, unn0+10 from t0; +create index t1_a1_asc_idx on t1(a1); +create index t1_a1_asc_b1_asc_idx on t1(a1 asc, b1 asc); +create index t1_a1_desc_idx on t1(a1 desc); +create table t2 (a2 int, b2 int, c2 int, d2 int, ch2 char(5), vch2 varchar(5), unn2 int unique not null, primary key(unn2)); +create index t2_a2_idx on t2(a2) ; +insert into t2 select * from t1; +insert into t2 select a1, b1, c1, d1, ch1, vch1, unn1+20 from t1; +insert into t2 select a1, null, c1, null, ch1, vch1, unn1+40 from t1; +insert into t2 select null, b1, null, d1, ch1, vch1, unn1+60 from t1; +create table t3 (a3 int, b3 int, c3 int, d3 int, ch3 char(5), vch3 varchar(5), unn3 int unique not null, primary key(unn3)); +insert into t3 select * from t2; +insert into t3 select a0, b0, c0, d0, ch0, vch0, unn0+80 from t0; +create table t4 (a4 int, b4 int, c4 int, d4 int, ch4 char(5), vch4 varchar(5), unn4 int unique not null, primary key(unn4)); +insert into t4 select * from t3; +insert into t4 select a0, b0, c0, d0, ch0, vch0, unn0+90 from t0; +create table t5 (a5 int, b5 int, c5 int, d5 int, ch5 char(5), vch5 varchar(5), unn5 int unique not null, primary key(unn5)); +insert into t5 select * from t4; +insert into t5 select a0, b0, c0, d0, ch0, vch0, unn0+100 from t0; +create table t6 (a6 int, b6 int, c6 int, d6 int, ch6 char(5), vch6 varchar(5), unn6 int unique not null, primary key(unn6)); +insert into t6 select * from t5; +insert into t6 select a0, b0, c0, d0, ch0, vch0, unn0+110 from t0; +create table t7 (a7 int, b7 int, c7 int, d7 int, ch7 char(5), vch7 varchar(5), unn7 int unique not null, primary key(unn7)); +insert into t7 select * from t6; +insert into t7 select a0, b0, c0, d0, ch0, vch0, unn0+120 from t0; +create table t8 (a8 int, b8 int, c8 int, d8 int, ch8 char(5), vch8 varchar(5), unn8 int unique not null, primary key(unn8)); +insert into t8 select * from t7; +insert into t8 select a0, b0, c0, d0, ch0, vch0, unn0+130 from t0; +create table t9 (a9 int, b9 int, c9 int, d9 int, ch9 char(5), vch9 varchar(5), unn9 int unique not null, primary key(unn9)); +insert into t9 select * from t8; +insert into t9 select a0, b0, c0, d0, ch0, vch0, unn0+140 from t0; +create table t10 (a10 int, b10 int, c10 int, d10 int, ch10 char(5), vch10 varchar(5), unn10 int unique not null, primary key(unn10)); +insert into t10 select * from t9; +insert into t10 select a0, b0, c0, d0, ch0, vch0, unn0+150 from t0; + +CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600); +CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES FROM (250) TO (500); +INSERT INTO prt1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 2 = 0; +CREATE INDEX iprt1_p1_a on prt1_p1(a); +CREATE INDEX iprt1_p1_a_asc on prt1_p1(a asc); +CREATE INDEX iprt1_p2_a on prt1_p2(a); +CREATE INDEX iprt1_p3_a on prt1_p3(a); + +CREATE TABLE prt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES FROM (250) TO (500); +CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 3 = 0; +CREATE INDEX iprt2_p1_b on prt2_p1(b); +CREATE INDEX iprt2_p1_b_desc on prt2_p1(b desc); +CREATE INDEX iprt2_p2_b on prt2_p2(b); +CREATE INDEX iprt2_p3_b on prt2_p3(b); + +CREATE TABLE tbl (c1 INT, c2 INT, PRIMARY KEY (c1 ASC, c2 ASC)); + +analyze t0 ; +analyze t1 ; +analyze t2 ; +analyze t3 ; +analyze t4 ; +analyze t5 ; +analyze t6 ; +analyze t7 ; +analyze t8 ; +analyze t9 ; +analyze t10 ; +ANALYZE prt1; +ANALYZE prt2; +ANALYZE tbl; + +-- Simple query. +explain (hints on, costs off) select * from t1 where a1=1; + +-- Simple join query. +explain (hints on, costs on) select * from t1, t2 where a1<5 and b1=b2 order by a1 asc; + +-- Force an index and change the join order. +/*+ Leading((t1 t2)) SeqScan(t2) IndexScan(t1 t1_a1_desc_idx) NestLoop(t2 t1) */ explain (hints on, costs off) select * from t1, t2 where a1<5 and b1=b2 order by a1 asc; + +-- OK since t1 and t2 do not have to be directly joined. Should not generate warnings. +/*+ noNestLoop(t1 t2) */ explain (costs off) select max(a1) from t1 join t2 on a1 1) + where ss1.loc = ss1.lat) as ss2 +where i41.a1 > 0; + +-- Outer joins and VALUES clause. +explain (costs off, hints on) +select a1, b1, a2, b2 from + t1 + inner join t2 + left join (select v1.x2, v2.y1, 11 AS d1 + from (values(1,0)) v1(x1,x2) + left join (values(3,1)) v2(y1,y2) + on v1.x1 = v2.y2) subq1 + on b2 = subq1.x2 + on unn1 = subq1.d1 + left join t3 + on subq1.y1 = unn2 +where unn1 < 4 and ch1 > ch2; + +-- Hints with CTEs. Should work. +/*+ Leading((y x)) Leading(((t2 t1) t0)) */ explain (hints on, costs off) with cte1 as (select a0, a1 from t0, t1, t2 where a0=a1 and a0=a2) select count(*) from cte1 x, cte1 y where x.a0=y.a0 and x.a1$2 and a1=a2;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; + +-- Scalar function should be OK. +explain (hints on, costs off) select * from t1, t2, t3 where a1$2 and a1=a2;' + LANGUAGE SQL; + +-- Table function should be OK. +explain (hints on, costs off) select f2 from t1, t2, t3, func2(1, 2) funky where a1a3; + +-- Test hints affecting NestLoop and YBBatchedNL join methods. + +-- Turn on BNL preference. +set yb_prefer_bnl to on; + +-- Should get NestLoop or YBBatchedNL based on cost. +/*+ NestLoop(t1 t2) */ explain select a1 from t1 join t2 on a1=a2 order by a1; + +-- Should get HashJoin or MergeJoin since hint prevents both NestLoop and +-- YBBatchedNL. +/*+ NoNestLoop(t1 t2) */ explain select a1 from t1 join t2 on a1=a2 order by a1; + +-- Should get YBBatchedNL. +/*+ YBBatchedNL(t1 t2) */ explain select a1 from t1 join t2 on a1=a2 order by a1; + +-- Should get NestLoop, HashJoin, or MergeJoin. +/*+ NoYBBatchedNL(t1 t2) */ explain select a1 from t1 join t2 on a1=a2 order by a1; + +-- Turn off BNL preference. +set yb_prefer_bnl to off; + +-- Should get NestLoop. +/*+ NestLoop(t1 t2) */ explain select a1 from t1 join t2 on a1=a2 order by a1; + +-- Should get HashJoin, MergeJoin, or YBBatchedNL. +/*+ NoNestLoop(t1 t2) */ explain select a1 from t1 join t2 on a1=a2 order by a1; + +-- Should get YBBatchedNL. +/*+ YBBatchedNL(t1 t2) */ explain select a1 from t1 join t2 on a1=a2 order by a1; + +-- Should get NestLoop, HashJoin, or MergeJoin. +/*+ NoYBBatchedNL(t1 t2) */ explain select a1 from t1 join t2 on a1=a2 order by a1; + +-- Should get HashJoin. +/*+ NoYBBatchedNL(t1 t2) set(enable_nestloop off) set(enable_mergejoin off) */ explain select a1 from t1 join t2 on a1=a2 + order by a1; + +reset yb_prefer_bnl; + +-- GH28072 - Make sure the internal hint test passes. +set yb_enable_parallel_append = on; +explain (hints on, costs off) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; +reset yb_enable_parallel_append; + +-- NEGATIVE TESTS + +-- Try to use a bad index. Should get warnings. +/*+ Leading((t2 t1)) SeqScan(t2) IndexScan(t1 badIndex) YbBatchedNL(t2 t1) */ explain (hints off, costs off) select * from t1, t2 where a1=1 and b1=b2; + +-- Specify a bad index with a valid one. Should get warnings. +/*+ Leading((t2 t1)) SeqScan(t2) IndexScan(t1 t1_a1_asc_b1_asc_idx badIndex) YbBatchedNL(t2 t1) */ explain (hints off, costs off) select * from t1, t2 where a1=1 and b1=b2; + +-- Specify a bad index with a valid one but bad name comes first. Should get warnings. +/*+ Leading((t2 t1)) SeqScan(t2) IndexScan(t1 badIndex t1_a1_asc_b1_asc_idx) YbBatchedNL(t2 t1) */ explain (hints off, costs off) select * from t1, t2 where a1=1 and b1=b2; + +-- Make index invalid for read and try to use it. Should get a warning (same as a missing index). +set yb_non_ddl_txn_for_sys_tables_allowed = true; +update pg_index set indisready=true, indisvalid = false where indrelid = 't1'::regclass; +/*+ IndexScan(t1 t1_a1_desc_idx) */ explain (costs off) select * from t1 where a1<5; +update pg_index set indisready=true, indisvalid = true where indrelid = 't1'::regclass; +set yb_non_ddl_txn_for_sys_tables_allowed = false; + +-- Turn on replanning. Now should see warnings for bad index and replanning message. +set pg_hint_plan.yb_bad_hint_mode to replan; +/*+ Leading((t1 t2)) SeqScan(t2) IndexScan(t1 badIndex) NestLoop(t2 t1) */ explain (hints on, costs off) select * from t1, t2 where a1<5 and b1=b2 order by a1 asc; +set pg_hint_plan.yb_bad_hint_mode to warn; + +-- Bad table name 'x'. Should generate warnings. +/*+ Leading((t2 t1)) SeqScan(t2) IndexScan(t1 t1_a1_asc_b1_asc_idx) YbBatchedNL(t2 x) */ explain (hints on, costs off) select * from t1, t2 where a1=1 and b1=b2; + +-- No plan can be found using the hint. Should generate warnings. +/*+ noSeqScan(tab) */ explain (hints on, costs off) select count(*) from t2 tab where b2<10; + +-- No plan can be found using the hint. Should generate warnings. +/*+ set(enable_seqscan off) */ explain (hints on, costs off) select count(*) from t2 tab where b2<10; + +-- This hint makes sense but currently there cannot be > 1 join method hint for the same set of tables so will give warnings/errors. +-- Since pg_hint_plan.yb_bad_hint_mode==WARN, the noNestLoop hint is marked as a duplicate (and is not used) but the +-- NoYbBatchedNL hint remains valid and is used. (This is an artifact of how the hint processing works.) +-- Therefore, for this query and hints, NestLoop is a valid choice but BNL is not. +/*+ noNestLoop(t1 t2) NoYbBatchedNL(t1 t2) */ explain (hints on, costs off) select max(a1) from t1 join t2 on a1=a2; + +-- Try to force t0-t1 join. Should see errors/warnings since this is not a legal join order. +/*+ Leading(((t0 t1) t2)) */ explain (hints on, costs off, uids on) select count(*) from t0 left join (t1 join t2 on a1=a2) on a0=a1; + +-- Syntax error. Should see warnings/error. +/*+ nestLoop(t4 t5 */ explain (hints on, costs off) select count(*) from t4 full join t5 on a4=a5; + +-- Fix the syntax error and try again. No joy since NLJ cannot be used for FULL join. Will give warnings. +/*+ nestLoop(t4 t5) */ explain (hints on, costs off, uids on) select count(*) from t4 full join t5 on a4=a5; + +-- Cannot do a ROJ so should get a warning. +/*+ leading((t4 t5)) nestLoop(t4 t5) */ explain (costs on, uids on) select count(*) from t4 right join t5 on a4=a5; + +-- Try to use hash join without an equality predicate. Should get an error, re-plan, and get first 5 rows. +set pg_hint_plan.yb_bad_hint_mode to replan; +/*+ hashJoin(t3 t4) */ select unn3, unn4 from t3 join t4 on a3a3; + +drop schema yb_hints cascade; + +-- Test fix for incorrect pruning of joins. +drop schema if exists yb26670 cascade; +create schema yb26670; +set search_path to yb26670; + +create table t0(c0 int4range , c1 BIT VARYING(40) ); +create table t1(c0 DECIMAL ); +create table t2(c0 bytea , c1 REAL ); +create table t3(c0 inet , c1 int4range ) WITHOUT OIDS ; +create table t4(c0 TEXT ); +create temporary view v6(c0) AS (SELECT '132.63.53.50' FROM t2*, t1*, t3, t4*, t0* WHERE lower_inf(((((t0.c0)*(t3.c1)))+(((t3.c1)+(t3.c1))))) LIMIT 2444285747789238479); + +-- Can't generate hints here because the final plan has a join replaced by a RESULT node. +explain (hints on) SELECT MAX((0.6002056)::MONEY) FROM t1*, ONLY t0, ONLY v6 LEFT OUTER JOIN t4* ON TRUE RIGHT OUTER JOIN t2* ON FALSE GROUP BY - (+ (strpos(t4.c0, v6.c0))); +SELECT MAX((0.6002056)::MONEY) FROM t1*, ONLY t0, ONLY v6 LEFT OUTER JOIN t4* ON TRUE RIGHT OUTER JOIN t2* ON FALSE GROUP BY - (+ (strpos(t4.c0, v6.c0))); + +drop schema yb26670 cascade; + +\set ECHO none diff --git a/src/test/regress/sql/yb.orig.index_including.sql b/src/test/regress/sql/yb.orig.index_including.sql new file mode 100644 index 0000000000000..0576d593eb55e --- /dev/null +++ b/src/test/regress/sql/yb.orig.index_including.sql @@ -0,0 +1,9 @@ +-- Unique index and unique constraint. Must fail setting non-unique index as constraint. +CREATE TABLE tbl_include_non_unique (c1 int, c2 int, c3 int, c4 int); +CREATE INDEX tbl_include_non_unique_idx on tbl_include_non_unique (c1); +ALTER TABLE tbl_include_non_unique ADD CONSTRAINT constr_non_unique UNIQUE USING INDEX tbl_include_non_unique_idx; + +-- Unique index and unique constraint. Must fail setting DESC unique index as constraint. +CREATE TABLE tbl_include_desc_unique (c1 int, c2 int, c3 int, c4 int); +CREATE UNIQUE INDEX tbl_include_desc_unique_idx on tbl_include_desc_unique (c1 DESC); +ALTER TABLE tbl_include_desc_unique ADD CONSTRAINT constr_desc_unique UNIQUE USING INDEX tbl_include_desc_unique_idx; diff --git a/src/test/regress/sql/yb.orig.index_only_scan_decoded_pk.sql b/src/test/regress/sql/yb.orig.index_only_scan_decoded_pk.sql new file mode 100644 index 0000000000000..c638f49b9b40c --- /dev/null +++ b/src/test/regress/sql/yb.orig.index_only_scan_decoded_pk.sql @@ -0,0 +1,507 @@ +-- Test Index Only Scan with decoded PK columns decoded from ybidxbasectid. +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (COSTS OFF)' +SET yb_enable_cbo = on; +SET yb_enable_primary_key_decode_from_index = on; + +-- Test: hash sharded single-column primary key +CREATE TABLE t_hash(id bigserial PRIMARY KEY, v text); +INSERT INTO t_hash(v) SELECT 'val_' || i FROM generate_series(1, 5) i; +CREATE INDEX t_hash_v ON t_hash(v); +ANALYZE t_hash; + +SELECT $$ +:P SELECT id FROM t_hash WHERE v = 'val_1'; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT id, v FROM t_hash WHERE v = 'val_3'; +$$ AS query \gset +\i :iter_P2 + +:explain SELECT v FROM t_hash WHERE v = 'val_1'; + +-- Test: fallback to another scan type for non-PK/non-index columns +ALTER TABLE t_hash ADD COLUMN extra int DEFAULT 0; +:explain SELECT id, extra FROM t_hash WHERE v = 'val_1'; + +-- Test: range-sharded PK, range scan, primary key scan unchanged +CREATE TABLE t_range(id int, v text, PRIMARY KEY(id ASC)); +INSERT INTO t_range SELECT i, 'val_' || i FROM generate_series(1, 20) i; +CREATE INDEX t_range_v ON t_range(v ASC); +ANALYZE t_range; + +SELECT $$ +:P SELECT id FROM t_range WHERE v = 'val_5'; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT id, v FROM t_range WHERE v >= 'val_10' AND v < 'val_15' ORDER BY v; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT id FROM t_range WHERE id = 5; +$$ AS query \gset +\i :iter_P2 + +-- Test: composite primary key (multi-column) +CREATE TABLE t_composite(a int, b int, c text, v text, PRIMARY KEY(a, b ASC)); +INSERT INTO t_composite SELECT i / 10, i % 10, 'c_' || i, 'v_' || i FROM generate_series(1, 20) i; +CREATE INDEX t_composite_v ON t_composite(v); +ANALYZE t_composite; + +SELECT $$ +:P SELECT a, b FROM t_composite WHERE v = 'v_15'; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT a, b, v FROM t_composite WHERE v = 'v_15'; +$$ AS query \gset +\i :iter_P2 + +:explain SELECT a, b, c FROM t_composite WHERE v = 'v_15'; + +-- Test: primary key column already included in secondary index +CREATE TABLE t_pk_in_idx(id int PRIMARY KEY, v1 int, v2 int); +INSERT INTO t_pk_in_idx SELECT i, i * 10, i * 100 FROM generate_series(1, 10) i; +CREATE INDEX t_pk_in_idx_v1_id ON t_pk_in_idx(v1, id); +ANALYZE t_pk_in_idx; + +SELECT $$ +:P SELECT id FROM t_pk_in_idx WHERE v1 = 100; +$$ AS query \gset +\i :iter_P2 + +:explain SELECT id, v2 FROM t_pk_in_idx WHERE v1 = 100; + +-- Test: unique secondary index +CREATE TABLE t_uniq(id int PRIMARY KEY, email text UNIQUE, name text); +INSERT INTO t_uniq SELECT i, 'user' || i || '@test.com', 'name_' || i FROM generate_series(1, 10) i; +ANALYZE t_uniq; + +SELECT $$ +:P SELECT id FROM t_uniq WHERE email = 'user5@test.com'; +$$ AS query \gset +\i :iter_P2 + +:explain SELECT id, name FROM t_uniq WHERE email = 'user5@test.com'; + +-- Test: partial index +CREATE TABLE t_partial(id int PRIMARY KEY, status text, val int); +INSERT INTO t_partial SELECT i, CASE WHEN i % 2 = 0 THEN 'active' ELSE 'inactive' END, i FROM generate_series(1, 20) i; +CREATE INDEX t_partial_val ON t_partial(val) WHERE status = 'active'; +ANALYZE t_partial; + +:explain SELECT id FROM t_partial WHERE val > 15 AND status = 'active'; +SELECT id FROM t_partial WHERE val > 15 AND status = 'active' ORDER BY id; + +:explain SELECT id FROM t_partial WHERE val > 15; + +-- Test: multi-column secondary index, multiple rows returned +CREATE TABLE t_multi_col(id int PRIMARY KEY, a int, b int, c int); +INSERT INTO t_multi_col SELECT i, i % 5, i % 10, i FROM generate_series(1, 20) i; +CREATE INDEX t_multi_col_ab ON t_multi_col(a, b ASC); +ANALYZE t_multi_col; + +SELECT $$ +:P SELECT id FROM t_multi_col WHERE a = 2 AND b = 2; +$$ AS query \gset +\i :iter_P2 + +:explain SELECT id, a, b FROM t_multi_col WHERE a = 2; + +:explain SELECT id, c FROM t_multi_col WHERE a = 2 AND b = 2; + +SELECT id FROM t_multi_col WHERE a = 3 ORDER BY id; + +-- Test: hash-sharded composite primary key +CREATE TABLE t_hash_comp(h1 int, h2 int, v int, PRIMARY KEY((h1, h2) HASH)); +INSERT INTO t_hash_comp SELECT i, i + 100, i * 10 FROM generate_series(1, 10) i; +CREATE INDEX t_hash_comp_v ON t_hash_comp(v); +ANALYZE t_hash_comp; + +SELECT $$ +:P SELECT h1, h2 FROM t_hash_comp WHERE v = 50; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT h1, h2, v FROM t_hash_comp WHERE v = 50; +$$ AS query \gset +\i :iter_P2 + +-- Test: mixed hash + range composite primary key +CREATE TABLE t_mixed_pk(h int, r int, v text, PRIMARY KEY(h HASH, r ASC)); +INSERT INTO t_mixed_pk SELECT i / 5, i, 'v_' || i FROM generate_series(1, 10) i; +CREATE INDEX t_mixed_pk_v ON t_mixed_pk(v ASC); +ANALYZE t_mixed_pk; + +SELECT $$ +:P SELECT h, r FROM t_mixed_pk WHERE v = 'v_7'; +$$ AS query \gset +\i :iter_P2 + +-- Test: filter on decoded PK column (pushdown disabled) +SELECT $$ +:P SELECT id FROM t_range WHERE v = 'val_5' AND id % 2 = 1; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT id, v FROM t_range WHERE v >= 'val_10' AND v < 'val_20' AND id % 3 = 1 ORDER BY v; +$$ AS query \gset +\i :iter_P2 + +-- Test: aggregates on decoded PK columns (pushdown disabled) +SELECT $$ +:P SELECT COUNT(id) FROM t_range WHERE v >= 'val_10' AND v < 'val_20'; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT MIN(id), MAX(id) FROM t_range WHERE v >= 'val_10' AND v < 'val_20'; +$$ AS query \gset +\i :iter_P2 + +-- Test: filters on physical columns still push down +SELECT $$ +:P SELECT id FROM t_range WHERE v >= 'val_10' AND v < 'val_15'; +$$ AS query \gset +\i :iter_P2 + +-- Test: aggregates on physical index columns still push down +SELECT $$ +:P SELECT COUNT(*) FROM t_range WHERE v >= 'val_10' AND v < 'val_20'; +$$ AS query \gset +\i :iter_P2 + +-- Test: NULL index column with primary key decoded +CREATE TABLE t_null_idx(id int PRIMARY KEY, v int); +INSERT INTO t_null_idx VALUES (1, NULL), (2, 10), (3, NULL), (4, 20); +CREATE INDEX t_null_idx_v ON t_null_idx(v ASC); +ANALYZE t_null_idx; + +:explain SELECT id FROM t_null_idx WHERE v IS NULL; +SELECT id FROM t_null_idx WHERE v IS NULL ORDER BY id; + +SELECT $$ +:P SELECT id FROM t_null_idx WHERE v = 10; +$$ AS query \gset +\i :iter_P2 + +-- Test: different primary key data types +CREATE TABLE t_text_pk(id text PRIMARY KEY, v int); +INSERT INTO t_text_pk SELECT 'key_' || i, i FROM generate_series(1, 5) i; +CREATE INDEX t_text_pk_v ON t_text_pk(v); +ANALYZE t_text_pk; + +SELECT $$ +:P SELECT id FROM t_text_pk WHERE v = 3; +$$ AS query \gset +\i :iter_P2 + +CREATE TABLE t_uuid_pk(id uuid PRIMARY KEY DEFAULT gen_random_uuid(), v int); +INSERT INTO t_uuid_pk(v) SELECT i FROM generate_series(1, 5) i; +CREATE INDEX t_uuid_pk_v ON t_uuid_pk(v); +ANALYZE t_uuid_pk; + +:explain SELECT id FROM t_uuid_pk WHERE v = 3; + +CREATE TABLE t_float_pk(id float4 PRIMARY KEY, v int); +INSERT INTO t_float_pk SELECT i * 1.5, i FROM generate_series(1, 5) i; +CREATE INDEX t_float_pk_v ON t_float_pk(v); +ANALYZE t_float_pk; + +SELECT $$ +:P SELECT id FROM t_float_pk WHERE v = 3; +$$ AS query \gset +\i :iter_P2 + +CREATE TABLE t_double_pk(id float8 PRIMARY KEY, v int); +INSERT INTO t_double_pk SELECT i * 1.1, i FROM generate_series(1, 5) i; +CREATE INDEX t_double_pk_v ON t_double_pk(v); +ANALYZE t_double_pk; + +SELECT $$ +:P SELECT id FROM t_double_pk WHERE v = 3; +$$ AS query \gset +\i :iter_P2 + +CREATE TABLE t_ts_pk(id timestamp PRIMARY KEY, v int); +INSERT INTO t_ts_pk SELECT '2024-01-01'::timestamp + (i || ' hours')::interval, i + FROM generate_series(1, 5) i; +CREATE INDEX t_ts_pk_v ON t_ts_pk(v); +ANALYZE t_ts_pk; + +SELECT $$ +:P SELECT id FROM t_ts_pk WHERE v = 3; +$$ AS query \gset +\i :iter_P2 + +CREATE TABLE t_bool_pk(id boolean, v int, extra int, PRIMARY KEY(id, extra)); +INSERT INTO t_bool_pk VALUES (true, 1, 1), (false, 2, 2), (true, 3, 3), (false, 4, 4); +CREATE INDEX t_bool_pk_v ON t_bool_pk(v); +ANALYZE t_bool_pk; + +SELECT $$ +:P SELECT id, extra FROM t_bool_pk WHERE v = 3; +$$ AS query \gset +\i :iter_P2 + +-- Test: Join where the join key is a decoded decoded PK column. +CREATE TABLE t_join_parent(tag_id int, label text, PRIMARY KEY(tag_id ASC)); +INSERT INTO t_join_parent SELECT i, 'label_' || i FROM generate_series(1, 500) i; +ANALYZE t_join_parent; + +CREATE TABLE t_join_child(item_id int, tag int, name text, PRIMARY KEY(item_id ASC)); +INSERT INTO t_join_child SELECT i, i % 50, 'name_' || i FROM generate_series(1, 1000) i; +CREATE INDEX t_join_child_tag ON t_join_child(tag ASC); +ANALYZE t_join_child; + +SELECT $$ +:P SELECT i.item_id, t.label FROM t_join_child i JOIN t_join_parent t ON i.item_id = t.tag_id WHERE i.tag = 1 ORDER BY i.item_id; +$$ AS query \gset +\i :iter_P2 + +-- Test: join with filter on decoded PK column (pushdown disabled) +SELECT $$ +:P SELECT i.item_id, t.label FROM t_join_child i JOIN t_join_parent t ON i.item_id = t.tag_id WHERE i.tag = 1 AND i.item_id % 500 = 1 ORDER BY i.item_id; +$$ AS query \gset +\i :iter_P2 + +-- Test: optimizer should prefer INCLUDE index over decoded pk index due to pushdown limitations +CREATE TABLE t_include_cmp(id int, v int, pad text, PRIMARY KEY(id ASC)); +INSERT INTO t_include_cmp SELECT i, i % 100, repeat('x', 20) + FROM generate_series(1, 10000) i; +CREATE INDEX idx_decoded_pk ON t_include_cmp(v ASC); +CREATE INDEX idx_include_pk ON t_include_cmp(v ASC) INCLUDE (id); +ANALYZE t_include_cmp; + +SELECT $$ +:P SELECT id FROM t_include_cmp WHERE v = 50 AND id > 5000; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT COUNT(id) FROM t_include_cmp WHERE v = 50; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT COUNT(id) FROM t_include_cmp WHERE v >= 20 AND v < 80; +$$ AS query \gset +\i :iter_P2 + +-- Test: partial INCLUDE with both decoded and included pk columns. +CREATE TABLE t_partial_include(h1 int, h2 int, v int, extra text, PRIMARY KEY(h1 ASC, h2 ASC)); +INSERT INTO t_partial_include + SELECT (i / 100) + 1, i, i % 50, 'pad_' || i FROM generate_series(1, 1000) i; +CREATE INDEX t_partial_include_v ON t_partial_include(v ASC) INCLUDE (h1); +ANALYZE t_partial_include; + +SELECT $$ +:P SELECT h1, h2 FROM t_partial_include WHERE v = 25 AND h1 > 5 AND h2 > 800 ORDER BY h2; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT COUNT(h1), COUNT(h2) FROM t_partial_include WHERE v >= 20 AND v < 30; +$$ AS query \gset +\i :iter_P2 + +-- Test: join on table with included pk column +SELECT $$ +:P SELECT c.h1, c.h2, p.label FROM t_partial_include c JOIN t_join_parent p ON c.h1 = p.tag_id WHERE c.v = 25 ORDER BY c.h2; +$$ AS query \gset +\i :iter_P2 + +-- Test: expression index +CREATE TABLE t_expr(id int PRIMARY KEY, name text); +INSERT INTO t_expr SELECT i, 'Name_' || i FROM generate_series(1, 10) i; +CREATE INDEX t_expr_lower ON t_expr(lower(name)); +ANALYZE t_expr; + +SELECT $$ +:P SELECT id FROM t_expr WHERE lower(name) = 'name_5'; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT id, lower(name) FROM t_expr WHERE lower(name) = 'name_3'; +$$ AS query \gset +\i :iter_P2 + +-- Test: IN queries with decoded PK +SELECT $$ +:P SELECT id FROM t_range WHERE v IN ('val_3', 'val_7', 'val_15') ORDER BY id; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT id FROM t_multi_col WHERE a IN (1, 3) AND b = 2 ORDER BY id; +$$ AS query \gset +\i :iter_P2 + +-- Test: LIKE queries with decoded PK +SELECT $$ +:P SELECT id FROM t_range WHERE v LIKE 'val_1%' ORDER BY v; +$$ AS query \gset +\i :iter_P2 + +-- Test: partial index with composite columns +CREATE TABLE t_partial_comp(id int PRIMARY KEY, cat text, sub int, val int); +INSERT INTO t_partial_comp + SELECT i, CASE WHEN i % 3 = 0 THEN 'A' WHEN i % 3 = 1 THEN 'B' ELSE 'C' END, + i % 5, i + FROM generate_series(1, 30) i; +CREATE INDEX t_partial_comp_idx ON t_partial_comp(sub, val ASC) WHERE cat = 'A'; +ANALYZE t_partial_comp; + +SELECT $$ +:P SELECT id FROM t_partial_comp WHERE cat = 'A' AND sub = 2 AND val > 10 ORDER BY id; +$$ AS query \gset +\i :iter_P2 + +:explain SELECT id, val FROM t_partial_comp WHERE cat = 'A' AND sub = 0; + +-- Test: index on jsonb key expression +CREATE TABLE t_jsonb_expr(id int PRIMARY KEY, data jsonb); +INSERT INTO t_jsonb_expr SELECT i, jsonb_build_object('key', i, 'val', 'x') + FROM generate_series(1, 500) i; +CREATE INDEX t_jsonb_expr_key ON t_jsonb_expr(((data->>'key')::int)); +ANALYZE t_jsonb_expr; + +SELECT $$ +:P SELECT id FROM t_jsonb_expr WHERE (data->>'key')::int = 5; +$$ AS query \gset +\i :iter_P2 + +-- Test: temporary table should not use decoded PK +CREATE TEMP TABLE t_temp(id int PRIMARY KEY, v int); +INSERT INTO t_temp SELECT i, i * 10 FROM generate_series(1, 10) i; +CREATE INDEX t_temp_v ON t_temp(v); +ANALYZE t_temp; + +SELECT $$ +:P SELECT id FROM t_temp WHERE v = 50; +$$ AS query \gset +\i :iter_P2 + +DROP TABLE t_temp; + +-- Test: GIN index should not use decoded PK +CREATE TABLE t_gin(id int PRIMARY KEY, tags jsonb); +INSERT INTO t_gin SELECT i, ('["tag_' || i || '"]')::jsonb FROM generate_series(1, 10) i; +CREATE INDEX t_gin_tags ON t_gin USING gin(tags); +ANALYZE t_gin; + +:explain SELECT id FROM t_gin WHERE tags @> '"tag_5"'; + +-- Test: vector index should not use decoded PK +CREATE EXTENSION IF NOT EXISTS vector; +CREATE TABLE t_vector(id int PRIMARY KEY, emb vector(3)); +INSERT INTO t_vector SELECT i, ('[' || i || ',0,0]')::vector FROM generate_series(1, 10) i; +CREATE INDEX t_vector_emb ON t_vector USING ybhnsw (emb vector_l2_ops); + +:explain SELECT id FROM t_vector ORDER BY emb <-> '[5,0,0]' LIMIT 3; + +-- Test: yb_enable_primary_key_decode_from_index = off disables decoded PK. +SET yb_enable_primary_key_decode_from_index = off; + +SELECT $$ +:P SELECT id FROM t_range WHERE v = 'val_5'; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT a, b FROM t_composite WHERE v = 'v_15'; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT COUNT(id) FROM t_range WHERE v >= 'val_10' AND v < 'val_20'; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT id FROM t_range WHERE v = 'val_5' AND id % 2 = 1; +$$ AS query \gset +\i :iter_P2 + +SELECT $$ +:P SELECT i.item_id, t.label FROM t_join_child i JOIN t_join_parent t ON i.item_id = t.tag_id WHERE i.tag = 1 ORDER BY i.item_id; +$$ AS query \gset +\i :iter_P2 + +-- Cleanup non-colocated tables before switching database. +DROP TABLE t_expr CASCADE; +DROP TABLE t_partial_comp CASCADE; +DROP TABLE t_include_cmp CASCADE; +DROP TABLE t_partial_include CASCADE; +DROP TABLE t_hash CASCADE; +DROP TABLE t_range CASCADE; +DROP TABLE t_composite CASCADE; +DROP TABLE t_pk_in_idx CASCADE; +DROP TABLE t_uniq CASCADE; +DROP TABLE t_partial CASCADE; +DROP TABLE t_multi_col CASCADE; +DROP TABLE t_hash_comp CASCADE; +DROP TABLE t_mixed_pk CASCADE; +DROP TABLE t_null_idx CASCADE; +DROP TABLE t_text_pk CASCADE; +DROP TABLE t_uuid_pk CASCADE; +DROP TABLE t_float_pk CASCADE; +DROP TABLE t_double_pk CASCADE; +DROP TABLE t_ts_pk CASCADE; +DROP TABLE t_bool_pk CASCADE; +DROP TABLE t_gin CASCADE; +DROP TABLE t_vector CASCADE; +DROP EXTENSION vector; +DROP TABLE t_jsonb_expr CASCADE; +DROP TABLE t_join_parent CASCADE; +DROP TABLE t_join_child CASCADE; + +-- Test: colocated tables with decoded PK +CREATE DATABASE colo_ios_test WITH COLOCATION = true; +\c colo_ios_test +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (COSTS OFF)' +SET yb_enable_cbo = on; +SET yb_enable_primary_key_decode_from_index = on; + +CREATE TABLE t_colocated(id int PRIMARY KEY, v int, extra text); +INSERT INTO t_colocated SELECT i, i % 20, 'pad' FROM generate_series(1, 100) i; +CREATE INDEX t_colocated_v ON t_colocated(v ASC); +ANALYZE t_colocated; + +-- Test: Index Only Scan with decoded PK on colocated table. +SELECT $$ +:P SELECT id FROM t_colocated WHERE v = 5; +$$ AS query \gset +\i :iter_P2 + +-- Test: range scan on colocated table. +SELECT $$ +:P SELECT id, v FROM t_colocated WHERE v >= 10 AND v < 15 ORDER BY v; +$$ AS query \gset +\i :iter_P2 + +-- Test: filter on decoded PK should not push down on colocated table. +:explain SELECT id FROM t_colocated WHERE v = 5 AND id > 50; + +-- Test: fallback to another scan type when non-index column is needed. +:explain SELECT id, extra FROM t_colocated WHERE v = 5; + +DROP TABLE t_colocated CASCADE; diff --git a/src/test/regress/sql/yb.orig.index_recheck.sql b/src/test/regress/sql/yb.orig.index_recheck.sql new file mode 100644 index 0000000000000..89785f0f02af1 --- /dev/null +++ b/src/test/regress/sql/yb.orig.index_recheck.sql @@ -0,0 +1,188 @@ +--- +--- YB Index Recheck Test +--- +--- Cases: +--- 1. Conditions that are trying to bind to a column that already has a higher +--- priority condition bound to it. We prioritize equality filters over +--- inequality filters. +--- 2. Incomplete bindings to hash keys. +--- 3. Conditions whose operands don’t match in type. (including UDTs) +--- 4. Unsupported RowComparisonExpressions + +\set explain_analyze 'EXPLAIN (ANALYZE, DIST, SUMMARY OFF, TIMING OFF, COSTS OFF)' +SET enable_seqscan = off; + +--- - --- +--- 1 --- Columns with a higher priority condition +--- - --- + +CREATE TABLE t_multiple_binds(a int, b int, primary key(a asc, b asc)); +CREATE INDEX t_a_b_idx ON t_multiple_binds(a ASC, b ASC); +INSERT INTO t_multiple_binds SELECT i, j FROM generate_series(1, 10) i, generate_series(1, 10) j; +-- These two can send all conditions because the conditions are on seperate keys. No recheck required. +:explain_analyze /*+ IndexScan(t t_multiple_binds_pkey) */ SELECT * FROM t_multiple_binds t WHERE a IN (1,2) and b <= 5; +:explain_analyze /*+ IndexScan(t t_a_b_idx) */ SELECT * FROM t_multiple_binds t WHERE a IN (1,2) and b <= 5; +-- These can only bind one condition to the key, so recheck is required. +:explain_analyze /*+ IndexScan(t t_multiple_binds_pkey) */ SELECT * FROM t_multiple_binds t WHERE a <= 7 AND a = 8; +:explain_analyze /*+ IndexScan(t t_multiple_binds_pkey) */ SELECT * FROM t_multiple_binds t WHERE b <= 7 AND b = 8; +:explain_analyze /*+ IndexScan(t t_a_b_idx) */ SELECT * FROM t_multiple_binds t WHERE a <= 7 AND a = 8; +:explain_analyze /*+ IndexScan(t t_a_b_idx) */ SELECT * FROM t_multiple_binds t WHERE b <= 7 AND b = 8; + +DROP TABLE t_multiple_binds; + +--- - --- +--- 2 --- Incomplete bindings to hash partition keys (#26742) +--- - --- + +CREATE TABLE t_incomplete_binds (h1 int, h2 int, r1 int, r2 int, s serial PRIMARY KEY); +INSERT INTO t_incomplete_binds SELECT i, j, i, j FROM generate_series(1, 10) i, generate_series(1, 10) j; +CREATE INDEX t_hash_hash ON t_incomplete_binds((h1, h2) HASH); +CREATE INDEX t_hash_range ON t_incomplete_binds(h1, r2 ASC); +CREATE INDEX t_range_range ON t_incomplete_binds(r1 ASC, r2 ASC); + +-- full bindings (no recheck required) +:explain_analyze SELECT * FROM t_incomplete_binds WHERE h1 = 1 AND h2 = 5; +:explain_analyze SELECT * FROM t_incomplete_binds WHERE h1 = 1 AND r2 < 5; +:explain_analyze SELECT * FROM t_incomplete_binds WHERE r1 < 1 AND r2 < 5; + +-- partial bindings (recheck required) +:explain_analyze /*+ IndexScan(t t_hash_hash) */ SELECT * FROM t_incomplete_binds t WHERE h2 = 5; +:explain_analyze /*+ IndexScan(t t_hash_range) */ SELECT * FROM t_incomplete_binds t WHERE r2 < 5; +:explain_analyze /*+ IndexScan(t t_range_range) */ SELECT * FROM t_incomplete_binds t WHERE r2 < 5; + +DROP TABLE t_incomplete_binds; + +--- - --- +--- 3 --- Cross type index comparisons +--- - --- + +----- integer comparisons ---- +CREATE TABLE t_int(i2 int2, i4 int4, i8 int8, f4 float4, f8 float8, + s serial PRIMARY KEY); +CREATE INDEX t_int_i2 ON t_int(i2 ASC); +CREATE INDEX t_int_i4 ON t_int(i4 ASC); +CREATE INDEX t_int_i8 ON t_int(i8 ASC); +INSERT INTO t_int VALUES ( 1, 1, 1); +INSERT INTO t_int VALUES ( 2, 2, 3); +INSERT INTO t_int VALUES (-32768, -2147483648, -9223372036854775808); +INSERT INTO t_int VALUES ( 32767, 2147483647, 9223372036854775807); + +-- these are each within range, so no recheck is required +:explain_analyze SELECT * FROM t_int WHERE i2 = 1; +:explain_analyze SELECT * FROM t_int WHERE i2 > 1; +:explain_analyze SELECT * FROM t_int WHERE i2 < 1; +-- these are each out of range, so recheck is required +:explain_analyze SELECT * FROM t_int WHERE i2 = 32768; +:explain_analyze SELECT * FROM t_int WHERE i2 < 32768; +:explain_analyze SELECT * FROM t_int WHERE i2 > 32768; +:explain_analyze SELECT * FROM t_int WHERE i2 IN (1, 2, 32767, 32768); + +-- these are each within range, so no recheck is required +:explain_analyze SELECT * FROM t_int WHERE i4 = 1; +:explain_analyze SELECT * FROM t_int WHERE i4 > 1; +:explain_analyze SELECT * FROM t_int WHERE i4 < 1; +-- these are each out of range, so recheck is required +:explain_analyze SELECT * FROM t_int WHERE i4 = 2147483648; +:explain_analyze SELECT * FROM t_int WHERE i4 > 2147483648; +:explain_analyze SELECT * FROM t_int WHERE i4 < 2147483648; +:explain_analyze SELECT * FROM t_int WHERE i4 IN (1, 2, 2147483647, 2147483648); + +-- these are each within range, so no recheck is required +:explain_analyze SELECT * FROM t_int WHERE i8 = 1; +:explain_analyze SELECT * FROM t_int WHERE i8 > 1; +:explain_analyze SELECT * FROM t_int WHERE i8 < 1; +-- these are each out of range, so recheck is required +:explain_analyze SELECT * FROM t_int WHERE i8 = 9223372036854775809; +:explain_analyze SELECT * FROM t_int WHERE i8 > 9223372036854775809; +:explain_analyze SELECT * FROM t_int WHERE i8 < 9223372036854775809; +:explain_analyze SELECT * FROM t_int WHERE i8 IN (1, 2, 9223372036854775807, 9223372036854775808); + +DROP TABLE t_int; + +----- float comparisons ---- +CREATE TABLE t_float(f4 float4, f8 float8, s serial PRIMARY KEY); +CREATE INDEX t_float_f4 ON t_float(f4 ASC); +CREATE INDEX t_float_f8 ON t_float(f8 ASC); + +INSERT INTO t_float VALUES (1, 1); +INSERT INTO t_float VALUES (1.2, 1.2); +INSERT INTO t_float VALUES (1.2345, 1.2345); +INSERT INTO t_float VALUES (1.23456, 1.23456); +INSERT INTO t_float VALUES (1.234567, 1.234567); +INSERT INTO t_float VALUES (1.2345678, 1.2345678); +INSERT INTO t_float VALUES (1.23456789, 1.23456789); +INSERT INTO t_float VALUES (1.234567890, 1.234567890); +INSERT INTO t_float VALUES (1.2345678901, 1.2345678901); +INSERT INTO t_float VALUES (1.23456789012, 1.23456789012); +INSERT INTO t_float VALUES (1.234567890123, 1.234567890123); +INSERT INTO t_float VALUES (1.2345678901234, 1.2345678901234); +INSERT INTO t_float VALUES (1.23456789012345, 1.23456789012345); +INSERT INTO t_float VALUES (1.234567890123456, 1.234567890123456); +INSERT INTO t_float VALUES (1.2345678901234567, 1.2345678901234567); +INSERT INTO t_float VALUES (1.23456789012345678, 1.23456789012345678); + + +:explain_analyze SELECT * FROM t_float WHERE f4 = 1.23456789; +:explain_analyze SELECT * FROM t_float WHERE f4 < 1.23456789; +:explain_analyze SELECT * FROM t_float WHERE f4 > 1.23456789; + +:explain_analyze SELECT * FROM t_float WHERE f4 = 1.2345678901234567; +:explain_analyze SELECT * FROM t_float WHERE f4 < 1.2345678901234567; +:explain_analyze SELECT * FROM t_float WHERE f4 > 1.2345678901234566; + +:explain_analyze SELECT * FROM t_float WHERE f8 = 1.2345678901234567; +:explain_analyze SELECT * FROM t_float WHERE f8 < 1.2345678901234567; +:explain_analyze SELECT * FROM t_float WHERE f8 > 1.2345678901234566; + +DROP TABLE t_float; + +-- TODO: add tests for casting between dates and timestamp + +----- text comparisons ---- (#24384) +CREATE TABLE t_name(v name); +CREATE TABLE t_text(v text); +CREATE INDEX t_name_range ON t_name(v ASC); +CREATE INDEX t_text_range ON t_text(v ASC); + +INSERT INTO t_name VALUES ('aa'), ('ab'), ('ac'), ('ad'), ('ba'), ('bb'), ('bc'), ('bd'); +INSERT INTO t_text VALUES ('aa'), ('ab'), ('ac'), ('ad'), ('ba'), ('bb'), ('bc'), ('bd'); + +:explain_analyze SELECT * FROM t_name WHERE v >= 'b'::name; +:explain_analyze SELECT * FROM t_name WHERE v >= 'b'::text; +:explain_analyze SELECT * FROM t_name WHERE v LIKE 'a%'; + +:explain_analyze SELECT * FROM t_text WHERE v >= 'b'::name; +:explain_analyze SELECT * FROM t_text WHERE v >= 'b'::text; +:explain_analyze SELECT * FROM t_text WHERE v LIKE 'a%'; + +DROP TABLE t_name, t_text; + +----- domain comparisons ---- (#26726) +CREATE DOMAIN positive_int AS INT CHECK (VALUE > 0); +CREATE DOMAIN non_empty_text AS TEXT CHECK (VALUE <> ''); + +CREATE TABLE t_domain_int(placeholder int, i positive_int, s serial PRIMARY KEY); +CREATE TABLE t_domain_text(placeholder int, t non_empty_text, s serial PRIMARY KEY); + +CREATE INDEX ON t_domain_int(i ASC); +CREATE INDEX ON t_domain_text(t ASC); + +INSERT INTO t_domain_int SELECT i, i FROM generate_series(1, 10) i; +INSERT INTO t_domain_text SELECT i, i::text FROM generate_series(1, 10) i; + +:explain_analyze SELECT * FROM t_domain_int WHERE i = 1; +:explain_analyze SELECT * FROM t_domain_text WHERE t = '1'; + +DROP TABLE t_domain_int, t_domain_text; +DROP DOMAIN positive_int, non_empty_text; + +--- +--- test a query that has multiple recheck types. +--- + +CREATE TABLE test_multiple_rechecks (a int, b int, c int); +CREATE INDEX ON test_multiple_rechecks (a ASC, b ASC, c ASC); +INSERT INTO test_multiple_rechecks SELECT i, j, k FROM generate_series(1, 10) i, generate_series(1, 10) j, generate_series(1, 10) k; +:explain_analyze /*+ IndexScan(t) */ SELECT * FROM test_multiple_rechecks t WHERE ROW(a, b) < ROW(4, 4) AND c IN (1, 2, 3, 4, 5, 6) AND c < 4; + +DROP TABLE test_multiple_rechecks; diff --git a/src/test/regress/sql/yb.orig.index_scan.sql b/src/test/regress/sql/yb.orig.index_scan.sql new file mode 100644 index 0000000000000..7436c9d523438 --- /dev/null +++ b/src/test/regress/sql/yb.orig.index_scan.sql @@ -0,0 +1,631 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (COSTS OFF)' + +-- Test primary key ordering +CREATE TABLE pk_asc(k int, v int, PRIMARY KEY(k ASC)); +INSERT INTO pk_asc VALUES (20, 2),(30, 3),(10, 1); +SELECT * FROM pk_asc; +\set query ':P SELECT * FROM pk_asc ORDER BY k;' +\i :iter_P2 +\set query ':P SELECT * FROM pk_asc ORDER BY k DESC;' +\i :iter_P2 +\set query ':P SELECT * FROM pk_asc ORDER BY k NULLS FIRST;' +\i :iter_P2 + +CREATE TABLE pk_desc(k int, v int, PRIMARY KEY(k DESC NULLS LAST)); +INSERT INTO pk_desc VALUES (20, 12),(30, 13),(10, 11); +SELECT * FROM pk_desc; +\set query ':P SELECT * FROM pk_desc ORDER BY k;' +\i :iter_P2 +\set query ':P SELECT * FROM pk_desc ORDER BY k DESC;' +\i :iter_P2 +\set query ':P SELECT * FROM pk_desc ORDER BY k NULLS FIRST;' +\i :iter_P2 + +SELECT k FROM pk_desc WHERE k < 30 AND k > 10; +-- NOTE: this GUC cannot be set using pg_hint_plan Set(...) because it is +-- currently used during execution, not just planning. +SET yb_test_skip_binding_scan_keys = true; +SELECT k FROM pk_desc WHERE k < 30 AND k > 10; +RESET yb_test_skip_binding_scan_keys; + +CREATE TABLE pk_multi(h int, r int, v text, PRIMARY KEY(h, r DESC)); +INSERT INTO pk_multi(h, r, v) VALUES (1, 0, '1-0'),(1, 1, '1-1'),(1, 2, '1-2'),(1, 3, '1-3'); +\set query ':P SELECT * FROM pk_multi WHERE h = 1;' +\i :iter_P2 + +-- We should still get correct results even if hash key is unset +\set Q1 '/*+IndexScan(pk_multi pk_multi_pkey)*/' +\set query ':P :Q1 SELECT * FROM pk_multi WHERE r IN (5,3,9,2);' +\i :iter_P2 + +\set query ':P SELECT * FROM pk_multi WHERE yb_hash_code(h) = yb_hash_code(1);' +\i :iter_P2 + +CREATE TABLE inn_hash(k int PRIMARY KEY, v int) SPLIT INTO 1 TABLETS; +CREATE INDEX ON inn_hash(v ASC); +INSERT INTO inn_hash VALUES (1,NULL),(2,102),(3,NULL),(4,104),(5,105),(6,NULL); +SELECT * FROM inn_hash WHERE v IS NOT NULL; +-- NOTE: this GUC cannot be set using pg_hint_plan Set(...) because it is +-- currently used during execution, not just planning. +SET yb_test_skip_binding_scan_keys = true; +SELECT * FROM inn_hash WHERE v IS NOT NULL; +RESET yb_test_skip_binding_scan_keys; + +-- Test unique secondary index ordering +CREATE TABLE usc_asc(k int, v int); +CREATE UNIQUE INDEX ON usc_asc(v ASC NULLS FIRST); +INSERT INTO usc_asc VALUES (44, NULL),(22, 20),(33, 30),(11, 10),(44, NULL); +\set query ':P SELECT * FROM usc_asc ORDER BY v;' +\i :iter_P2 +\set query ':P SELECT * FROM usc_asc ORDER BY v DESC NULLS LAST;' +\i :iter_P2 +\set query ':P SELECT * FROM usc_asc ORDER BY v NULLS FIRST;' +\i :iter_P2 + +CREATE TABLE usc_multi_asc(k int, r int, v int) SPLIT INTO 1 TABLETS; +CREATE INDEX ON usc_multi_asc(k, r ASC NULLS FIRST); +INSERT INTO usc_multi_asc(k, r, v) VALUES (1, 10, 1),(1, NULL, 2),(1, 20, 3); +\set query ':P SELECT * FROM usc_multi_asc WHERE k = 1;' +\i :iter_P2 + +-- Test non-unique secondary index ordering +CREATE TABLE sc_desc(k int, v int); +CREATE INDEX ON sc_desc(v DESC NULLS LAST); +INSERT INTO sc_desc VALUES (4, NULL),(2, 20),(3, 30),(1, 10),(4, NULL); +\set query ':P SELECT * FROM sc_desc ORDER BY v;' +\i :iter_P2 +\set query ':P SELECT * FROM sc_desc ORDER BY v DESC NULLS LAST;' +\i :iter_P2 +\set query ':P SELECT * FROM sc_desc ORDER BY v NULLS FIRST;' +\i :iter_P2 + +CREATE TABLE sc_multi_desc(k int, r int, v int) SPLIT INTO 1 TABLETS; +CREATE INDEX ON sc_multi_desc(k, r DESC); +INSERT INTO sc_multi_desc(k, r, v) VALUES (1, 10, 10),(1, 10, 10),(1, NULL, 2),(1, 20, 3); +\set query ':P SELECT * FROM sc_multi_desc WHERE k = 1;' +\i :iter_P2 + +-- Testing for the case in issue #12481 +CREATE INDEX range_ind ON sc_multi_desc(v ASC, r ASC); +\set query ':P SELECT v,r FROM sc_multi_desc WHERE v IN (2,4) and r is null;' +\i :iter_P2 + +-- Test NULLS last ordering. +CREATE TABLE sc_desc_nl(h int, r int, v int) SPLIT INTO 1 TABLETS; +CREATE INDEX on sc_desc_nl(h HASH, r DESC NULLS LAST); +INSERT INTO sc_desc_nl(h,r,v) values (1,1,1), (1,2,2), (1,3,3), (1,4,4), (1,5,5), (1, null, 6); +-- Rows should be ordered DESC NULLS LAST by r. +SELECT * FROM sc_desc_nl WHERE h = 1; +SELECT * FROM sc_desc_nl WHERE yb_hash_code(h) = yb_hash_code(1); +SELECT * FROM sc_desc_nl WHERE h = 1 AND r >= 2; +SELECT * FROM sc_desc_nl WHERE yb_hash_code(h) = yb_hash_code(1) AND r >= 2; +SELECT * FROM sc_desc_nl WHERE h = 1 AND r < 4; +SELECT * FROM sc_desc_nl WHERE yb_hash_code(h) = yb_hash_code(1) AND r < 4; +SELECT * FROM sc_desc_nl WHERE h = 1 AND r > 1 AND r <= 4; +SELECT * FROM sc_desc_nl WHERE yb_hash_code(h) = yb_hash_code(1) AND r > 1 AND r <= 4; + +-- >/>=/=/<=/< null is never true per SQL semantics. +SELECT * FROM sc_desc_nl WHERE h = 1 AND r = null; +SELECT * FROM sc_desc_nl WHERE yb_hash_code(h) = yb_hash_code(1) AND r = null; +SELECT * FROM sc_desc_nl WHERE h = 1 AND r >= null; +SELECT * FROM sc_desc_nl WHERE yb_hash_code(h) = yb_hash_code(1) AND r >= null; +SELECT * FROM sc_desc_nl WHERE h = 1 AND r > null; +SELECT * FROM sc_desc_nl WHERE yb_hash_code(h) = yb_hash_code(1) AND r > null; +SELECT * FROM sc_desc_nl WHERE h = 1 AND r <= null; +SELECT * FROM sc_desc_nl WHERE yb_hash_code(h) = yb_hash_code(1) AND r <= null; +SELECT * FROM sc_desc_nl WHERE h = 1 AND r < null; +SELECT * FROM sc_desc_nl WHERE yb_hash_code(h) = yb_hash_code(1) AND r < null; + +-- IS NULL should be pushed down and return the expected result. +\set query ':P SELECT * FROM sc_desc_nl WHERE h = 1 AND r IS null;' +\i :iter_P2 + +\set query ':P SELECT * FROM sc_desc_nl WHERE yb_hash_code(h) = yb_hash_code(1) AND r IS null;' +\i :iter_P2 + +DROP TABLE sc_desc_nl; + +-------------------------------------- +-- Testing Selective Updation of Indices +-------------------------------------- +-- create table with lot of columns +create table test (pk int primary key, col2 int, col3 int, col4 int, col5 int, +col6 int, col7 name, col8 int, col9 int); +insert into test values(1,1,1,1,1,1,'Aa',1,99); +insert into test values(2,2,2,2,2,2,'Bb',2,99); +insert into test values(3,3,3,3,3,3,'Cc',3,99); +insert into test values(4,4,4,4,4,4,'Dd',4,99); +insert into test values(5,5,5,5,5,5,'Ee',5,88); +insert into test values(6,6,6,6,6,6,'Ff',6,88); + +-- Creating indices with included columns +create index idx_col3 on test(col3) include (col4,col5,col6); +create index idx_col5 on test(col5) include (col6,col7); +-- Ordering is disallowed for included columns +create index on test(col5) include (col6 hash, col7); + +-- Performing a few updates and checking if subsequent commands exhibit expected behavior +update test set col3=11, col4=11 where pk=1; +select * from test; + +-- testing partial index on where clause +create index idx_col9 on test(col9) where col9 = 88; +update test set col9=199 where pk=2; +update test set col9=199 where pk=5; +select * from test; +\set query ':P select * from test where col9 = 88;' +\i :iter_P2 +\set query ':P select * from test where col9 = 99;' +\i :iter_P2 + +-- testing index on expressions +create index idx_col7 ON test(col7); +SELECT $$ +:P select * from test where col7 = 'Dd'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:explain select * from test where lower(col7) = 'dd'; +$$ AS query \gset +:query +drop index idx_col7; +create index idx_col7 ON test(lower(col7)); +update test set col7='DdD' where pk=4; +SELECT $$ +:P select * from test where lower(col7) = lower('DdD'); +$$ AS query \gset +\i :iter_P2 +select * from test; + +-- testing multi-column indices +create index idx_col4_idx_col5_idx_col6 on test(col4, col5, col6); +update test set col4=112 where pk=1; +\set query ':P SELECT * FROM test WHERE col4 = 112;' +\i :iter_P2 + +update test set col4=222, col5=223 where pk=2; +\set query ':P SELECT * FROM test WHERE col4 = 222 and col5 = 223;' +\i :iter_P2 + +update test set col4=232, col5=345, col6=456 where pk=3; +\set query ':P SELECT * FROM test WHERE col4 = 232 and col5 = 345 and col6 = 456;' +\i :iter_P2 +\set query ':P SELECT * FROM test WHERE col5 = 345;' +\i :iter_P2 + +update test set col5=444, col6=35 where pk=4; +\set query ':P SELECT * FROM test WHERE col5 = 444 and col6 = 35;' +\i :iter_P2 + +update test set col6=5554 where pk=5; +\set query ':P SELECT * FROM test WHERE col6 = 5554;' +\i :iter_P2 + +-- test index only scan with non-target column refs in qual (github issue #9176) +-- baseline, col5 is in target columns +\set query ':P SELECT col4, col5 FROM test WHERE col4 = 232 and col5 % 3 = 0;' +\i :iter_P2 +-- same lines are expected without col5 in the target list +\set query ':P SELECT col4 FROM test WHERE col4 = 232 and col5 % 3 = 0;' +\i :iter_P2 + +-- test index scans where the filter trivially rejects everything and +-- no request should be sent to DocDB +\set explain 'EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE)' +SELECT $$ +:P SELECT * FROM test WHERE col3 = ANY('{}'); +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM test WHERE col3 = ANY('{NULL}'); +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT col3 FROM test WHERE col3 = ANY('{NULL}'); +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM test WHERE col3 = ANY('{NULL, NULL}'); +$$ AS query \gset +\i :iter_P2 + +-- testing update on primary key +update test set pk=17 where pk=1; +update test set pk=25, col4=777 where pk=2; +select * from test; +\set explain 'EXPLAIN (COSTS OFF)' +\set query ':P select * from test where pk=17;' +\i :iter_P2 +\set query ':P select * from test where pk=25;' +\i :iter_P2 + +-- test a query after first connecting +\c +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM test WHERE col3 = ANY('{}'); + +-- test index scan where the column type does not match value type +CREATE TABLE pk_real(c0 REAL, PRIMARY KEY(c0 asc)); +INSERT INTO pk_real(c0) VALUES(0.4); +\set query ':P SELECT ALL pk_real.c0 FROM pk_real WHERE ((0.6)>(pk_real.c0));' +\i :iter_P2 +-- 0.4::FLOAT4 is not equal to 0.4::DOUBLE PRECISION +\set query ':P SELECT ALL pk_real.c0 FROM pk_real WHERE pk_real.c0 = ANY(ARRAY[0.6, 0.4]);' +\i :iter_P2 +INSERT INTO pk_real(c0) VALUES(0.5); +-- 0.5::FLOAT4 is equal to 0.5::DOUBLE PRECISION +\set query ':P SELECT ALL pk_real.c0 FROM pk_real WHERE pk_real.c0 = 0.5;' +\i :iter_P2 + +CREATE TABLE pk_smallint(c0 SMALLINT, PRIMARY KEY(c0 asc)); +INSERT INTO pk_smallint VALUES(123), (-123); +\set query ':P SELECT c0 FROM pk_smallint WHERE (65568 > c0);' +\i :iter_P2 +\set query ':P SELECT c0 FROM pk_smallint WHERE (c0 > -65539);' +\i :iter_P2 +\set query ':P SELECT c0 FROM pk_smallint WHERE (c0 = ANY(ARRAY[-65539, 65568]));' +\i :iter_P2 + +-- test any/some/all +create TABLE pk_int(c0 int, primary key(c0 ASC)); +INSERT INTO pk_int VALUES (1), (2), (3), (4); +SELECT * FROM pk_int WHERE c0 IN (3, 4); +SELECT * FROM pk_int WHERE c0 NOT IN (3, 4); +SELECT * FROM pk_int WHERE c0 < ANY(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 <= ANY(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 = ANY(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 >= ANY(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 > ANY(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 < SOME(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 <= SOME(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 = SOME(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 >= SOME(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 > SOME(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 < ALL(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 <= ALL(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 = ALL(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 >= ALL(ARRAY[3, 4]); +SELECT * FROM pk_int WHERE c0 > ALL(ARRAY[3, 4]); + +-- test row comparison expressions +CREATE TABLE pk_range_int_asc (r1 INT, r2 INT, r3 INT, v INT, PRIMARY KEY(r1 asc, r2 asc, r3 asc)); +INSERT INTO pk_range_int_asc SELECT i/25, (i/5) % 5, i % 5, i FROM generate_series(1, 125) AS i; +\set explain 'EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE)' +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) <= (2,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, v, r3) <= (2,3,60,1);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) < (2,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) <= (3,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) >= (3,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) > (3,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) >= (1,4,5) AND (r1, r2, r3) <= (2,4,5);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) >= (1,2,3) AND (r1, r2, r3) <= (1,3,2) AND r3 IN (3,2,6);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) >= (1,1,5) AND (r1, r2, r3) <= (1,4,5) ORDER BY r1 DESC, r2 DESC, r3 DESC;' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) = (1,6,5);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) <= (1,6,5) AND (r1,r2,r3) < (1,6,5);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) <= (1,1,5) AND (r1,r2,r3) < (1,2,4) AND (r1,r2,r3) > (1,2,3) AND (r1,r2,r3) < (1,2,4) AND (r1,r2,r3) >= (1,2,3);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r2, r3) <= (1,1,5) AND (r1, r2, r3) > (1,2,3) AND v IS NOT NULL;' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r3) <= (1,3) AND (r1,r2) < (1,3) AND (r1,r2) >= (1,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_asc WHERE (r1, r3) <= (1,3) AND (r1,r2) < (1,3) AND (r1,r2) >= (1,2) AND (r1,r2,r3) = (1,2,3);' +\i :iter_P2 +DROP TABLE pk_range_int_asc; + +-- test row comparison expressions where we have differing column orderings +CREATE TABLE pk_range_asc_desc_asc (r1 BIGINT, r2 INT, r3 INT, v INT, PRIMARY KEY(r1 asc, r2 desc, r3 asc)); +INSERT INTO pk_range_asc_desc_asc SELECT i/25, (i/5) % 5, i % 5, i FROM generate_series(1, 125) AS i; +\set query ':P SELECT * FROM pk_range_asc_desc_asc WHERE (r1, r2, r3) <= (2,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_asc_desc_asc WHERE (r1, r2, v, r3) <= (2,3,60,1);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_asc_desc_asc WHERE (r1, r2, r3) >= (1,7,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_asc_desc_asc WHERE (r1, r2, r3) > (8,7,3);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_asc_desc_asc WHERE (r1, r2, r3) >= (1,7,2) AND (r1, r2, r3) <= (3,2,1);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_asc_desc_asc WHERE (r1, r2, r3) >= (1,7,2) AND (r1, r2, r3) <= (3,2,1) ORDER BY r1 DESC, r2 ASC, r3 DESC;' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_asc_desc_asc WHERE (r1, r3) <= (1,3) AND (r1,r2) < (1,3) AND (r1,r2) >= (1,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_asc_desc_asc WHERE (r1, r3) <= (1,3) AND (r1,r2) < (1,3) AND (r1,r2) >= (1,2) AND (r1,r2,r3) = (1,2,3);' +\i :iter_P2 +DROP TABLE pk_range_asc_desc_asc; + +CREATE TABLE pk_range_desc_asc_desc (r1 BIGINT, r2 INT, r3 INT, v INT, PRIMARY KEY(r1 desc, r2 asc, r3 desc)); +INSERT INTO pk_range_desc_asc_desc SELECT i/25, (i/5) % 5, i % 5, i FROM generate_series(1, 125) AS i; +\set query ':P SELECT * FROM pk_range_desc_asc_desc WHERE (r1, r2, r3) <= (2,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_desc_asc_desc WHERE (r1, r2, v, r3) <= (2,3,60,1);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_desc_asc_desc WHERE (r1, r2, r3) >= (1,7,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_desc_asc_desc WHERE (r1, r2, r3) >= (1,7,2) AND (r1, r2, r3) <= (3,2,1);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_desc_asc_desc WHERE (r1, r2, r3) >= (1,7,2) AND (r1, r2, r3) <= (3,2,1) ORDER BY r1 DESC, r2 ASC, r3 DESC;' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_desc_asc_desc WHERE (r1, r3) <= (1,3) AND (r1,r2) < (1,3) AND (r1,r2) >= (1,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_desc_asc_desc WHERE (r1, r3) <= (1,3) AND (r1,r2) < (1,3) AND (r1,r2) >= (1,2) AND (r1,r2,r3) = (1,2,3);' +\i :iter_P2 +DROP TABLE pk_range_desc_asc_desc; + +CREATE TABLE pk_range_int_desc (r1 INT, r2 INT, r3 INT, v INT, PRIMARY KEY(r1 desc, r2 desc, r3 desc)); +INSERT INTO pk_range_int_desc SELECT i/25, (i/5) % 5, i % 5, i FROM generate_series(1, 125) AS i; +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, r3) <= (2,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, v, r3) <= (2,3,60,1);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, r3) < (2,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, r3) <= (3,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, r3) >= (3,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, r3) > (3,3,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, r3) >= (1,4,5) AND (r1, r2, r3) <= (2,4,5);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, r3) >= (1,2,3) AND (r1, r2, r3) <= (1,3,2) AND r3 IN (3,2,6);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, r3) >= (1,4,5) AND (r1, r2, r3) <= (1,6,5) ORDER BY r1 ASC, r2 ASC, r3 ASC;' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, r3) = (1,6,5);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, r3) <= (1,6,5) AND (r1,r2,r3) < (1,6,5);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r2, r3) <= (1,1,5) AND (r1,r2,r3) < (1,2,4) AND (r1,r2,r3) > (1,2,3) AND (r1,r2,r3) < (1,2,4) AND (r1,r2,r3) >= (1,2,3);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r3) <= (1,3) AND (r1,r2) < (1,3) AND (r1,r2) >= (1,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE (r1, r3) <= (1,3) AND (r1,r2) < (1,3) AND (r1,r2) >= (1,2) AND (r1,r2,r3) = (1,2,3);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_desc WHERE r2 IN (1,3,5) ORDER BY r1 DESC, r2 DESC LIMIT 10;' +\i :iter_P2 +DROP TABLE pk_range_int_desc; + +CREATE TABLE pk_range_int_text (r1 INT, r2 TEXT, r3 BIGINT, v INT, PRIMARY KEY(r1 asc, r2 asc, r3 asc)); +INSERT INTO pk_range_int_text SELECT i/25, concat('abc', ((i/5) % 5)::TEXT), i % 5, i FROM generate_series(1, 125) AS i; +\set query ':P SELECT * FROM pk_range_int_text WHERE (r1, r2, r3) <= (2,''ab2''::text,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_text WHERE (r1, r2, v, r3) <= (2,''abc3''::text,60,1);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_text WHERE (r1, r2, r3) < (2,''abc3''::text,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_text WHERE (r1, r2, r3) <= (3,''abb3''::text,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_text WHERE (r1, r2, r3) >= (3,''abc3''::text,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_text WHERE (r1, r2, r3) > (3,''abc3''::text,2);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_text WHERE (r1, r2, r3) >= (1,''abc4''::text,5) AND (r1, r2, r3) <= (2,''abc4''::text,5);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_text WHERE (r1, r2, r3) >= (1,''abc2''::text,3) AND (r1, r2, r3) <= (1,''abc3''::text,2) AND r3 IN (3,2,6);' +\i :iter_P2 +\set query ':P SELECT * FROM pk_range_int_text WHERE (r1, r2, r3) >= (1,''ab''::text,5) AND (r1, r2, r3) <= (1,''abcd''::text,5) ORDER BY r1 ASC, r2 ASC, r3 ASC;' +\i :iter_P2 +DROP TABLE pk_range_int_text; + +CREATE TABLE null_test(a int, b int); +CREATE INDEX ON null_test(a asc, b asc); +INSERT INTO null_test VALUES (NULL, 9), (9, NULL), (9,8), (10,9); +\set query ':P SELECT * FROM null_test WHERE (a,b) >= (9, 8);' +\i :iter_P2 + +\set query ':P SELECT * FROM null_test WHERE (a,b) <= (9, 8);' +\i :iter_P2 + +\set query ':P SELECT * FROM null_test WHERE (a,b) <= (9, 8);' +\i :iter_P2 + +\set query ':P SELECT * FROM null_test WHERE (a,b) >= (8, 9);' +\i :iter_P2 + +\set query ':P SELECT * FROM null_test WHERE (a,b) >= (8, 9);' +\set Q1 '/*+ IndexOnlyScan(null_test) */' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM null_test WHERE a > 8 OR (a = 8 AND b >= NULL);' +\i :iter_P2 + +\set query ':P :Q1 SELECT * FROM null_test t1 JOIN null_test t2 ON (t1.a, t1.b) >= (t2.a, t2.b);' +\set Q1 '/*+ Set(enable_material OFF) Leading((t1 t2)) IndexOnlyScan(t1) IndexOnlyScan(t2) */' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM null_test t1 JOIN null_test t2 ON t1.a > t2.a OR (t1.a = t2.a AND t1.b >= t2.b);' +\i :iter_P2 +DROP TABLE null_test; + +-- make sure row comparisons don't operate on hash keys yet +CREATE TABLE pk_hash_range_int (h int, r1 int, r2 int, r3 int, PRIMARY KEY(h hash, r1 asc, r2 asc, r3 asc)); +INSERT INTO pk_hash_range_int SELECT i/25, (i/5) % 5, i % 5, i FROM generate_series(1, 125) AS i; +\set Q1 '/*+ IndexScan(pk_hash_range_int) */' +\set query ':P :Q1 SELECT * FROM pk_hash_range_int WHERE (r1, r2) <= (3, 2);' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM pk_hash_range_int WHERE h = 1 AND (r1, r2) <= (3, 2) AND r1 <= 2;' +\i :iter_P2 +\set query ':P :Q1 SELECT sum(r1) FROM pk_hash_range_int WHERE h = 1 AND (r1, r2) <= (3, 2) AND r1 <= 2;' +\i :iter_P2 +DROP TABLE pk_hash_range_int; + +-- Test index SPLIT AT with INCLUDE clause +CREATE TABLE test_tbl ( + a INT, + b INT, + PRIMARY KEY (a ASC) +) SPLIT AT VALUES((1)); +CREATE INDEX test_idx on test_tbl( + b ASC +) INCLUDE (a) SPLIT AT VALUES ((1)); +INSERT INTO test_tbl VALUES (1, 2),(2, 1),(4, 3),(5, 4); +\set query ':P SELECT a, b FROM test_tbl WHERE a = 4;' +\i :iter_P2 +\set query ':P SELECT a, b FROM test_tbl WHERE b = 4;' +\i :iter_P2 +DROP INDEX test_idx; +DROP TABLE test_tbl; + +-- (#21004) The tests in this section validate that computation of range bounds +-- is skipped while prechecking an Index-only Scan's rescan condition. +DROP TABLE IF EXISTS aa; +DROP TABLE IF EXISTS bb; +CREATE TABLE aa (col_varchar_key VARCHAR(1), col_varchar_nokey VARCHAR(1)); +CREATE TABLE bb (col_varchar_key VARCHAR(1), col_varchar_nokey VARCHAR(1)); +INSERT INTO aa VALUES ('g', 'g'); +INSERT INTO bb VALUES ('g', 'g'); +CREATE INDEX bb_varchar_key ON bb (col_varchar_key ASC); +-- Correlation between two different tables +EXPLAIN (COSTS OFF) SELECT ( + SELECT + COUNT(*) + FROM + bb AS subquery_t1 + WHERE + subquery_t1.col_varchar_key <= table2.col_varchar_nokey AND + subquery_t1.col_varchar_key <= table2.col_varchar_nokey +) AS field1 +FROM + aa AS table2; +-- Correlation of the same table +SELECT ( + SELECT + COUNT(*) + FROM + bb AS subquery_t1 + WHERE + subquery_t1.col_varchar_key >= table1.col_varchar_nokey AND + subquery_t1.col_varchar_key > 'a' +) AS field1 +FROM + bb AS table1; + +create table sample(a int, b int, primary key(a asc, b asc)); +insert into sample values (1,1); +insert into sample values (1,2); +insert into sample values (2,1); + +\set explain 'EXPLAIN (COSTS OFF)' +\set query ':P select * from sample where b < 2 and b >= 2;' +\i :iter_P2 + +\set query ':P select * from sample where b >= 2 and b < 2;' +\i :iter_P2 + +\set query ':P select * from sample where b < 2 and b >= 2;' +\i :iter_P2 + +\set query ':P select * from sample where b <= 2 and b < 2 and b >= 2;' +\i :iter_P2 + +\set query ':P select * from sample where b <= 2 and b >= 2;' +\i :iter_P2 + +\set query ':P select * from sample where b <= 3 and b <= 2 and b >= 2;' +\i :iter_P2 + +drop table sample; + +-- GHI 21451 HASH Index on multiple expressions +drop table IF EXISTS t1; +create table t1 (c1 bigint, c2 jsonb, primary key ((c1))); +insert into t1 (c1,c2) values (1,'{"c3":1,"c4":1}'); +insert into t1 (c1,c2) values (2,'{"c3":2,"c4":2}'); +create index t1_idx on t1 (((c2->>'c3'), (c2->>'c4')) hash); + +select * from t1; +\set Q1 '/*+IndexScan(t1 t1_idx)*/' +SELECT $$ +:P :Q1 select * from t1 where (c2->>'c3') = '4'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P :Q1 select * from t1 where (c2->>'c4') = '4'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P :Q1 select * from t1 where (c2->>'c3') = '4' and (c2->>'c4') = '4'; +$$ AS query \gset +\i :iter_P2 + +drop table t1; + +create table sample(a int, primary key(a asc)); +insert into sample values (0); +select * from sample where a = x'8000000000000000'::bigint; +drop table sample; + +create table sample(a int2, primary key(a asc)); +insert into sample values (0); +select * from sample where a = x'8000000000000000'::bigint; +select * from sample where a = x'80000000'::int; +drop table sample; + +-- GHI 22832 possible fetch of a dropped column +create table t1(k1 int, k2 int, v0 int, v1 int, v2 int, primary key ((k1, k2) hash)); +alter table t1 drop column v0; +create index on t1(k1, k2, v1); +-- NOTE: this GUC cannot be set using pg_hint_plan Set(...) because it is +-- currently used during execution, not just planning. +SET yb_test_skip_binding_scan_keys = true; +\set query ':P select v2 from t1 where k1 = 1 and k2 > 0 and v1 IN (1, 2);' +\i :iter_P2 +RESET yb_test_skip_binding_scan_keys; +drop table t1; + +-- Test row compares with shuffled column orders (tests internal attnum handling). +create table t1(y int, a uuid, z text, b timestamptz, PRIMARY KEY(b asc, y, a, z)); +insert into t1 (a, b, y, z) values ('00000000-0000-0000-0000-000000000000', '2021-01-01 00:00:00', 2, 'foo'); +insert into t1 (a, b, y, z) values ('00000000-0000-0000-0000-000000000001', '2021-01-01 00:00:00', 1, 'bar'); +insert into t1 (a, b, y, z) values ('00000000-0000-0000-0000-000000000001', '2021-01-01 00:00:00', 2, 'foo'); +SELECT $$ +:P select * from t1 where (b, a) >= ('2021-01-01 00:00:00', '00000000-0000-0000-0000-000000000001') and (y, z) >= (2, 'baz'); +$$ AS query \gset +\i :iter_P2 +drop table t1; + +-- GHI #26717 IndexOnlyScan recheck - selecting a constant +create table simple (i int); +create index on simple(i, i asc); +insert into simple values (1); +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)' +\set Q1 '/*+ IndexOnlyScan(simple) */' +SELECT $$ +:P :Q1 SELECT 'a' FROM simple WHERE i < 5; +$$ AS query \gset +\i :iter_P2 + +-- GHI #26713 IndexOnlyScan recheck - selecting an aggregate +\set Q1 '/*+ IndexOnlyScan(simple) */' +\set query ':P :Q1 SELECT COUNT(*) FROM simple WHERE i < 5;' +\i :iter_P2 + +-- GHI #26705 IndexOnlyScan recheck - selecting nothing from the table +\set Q1 '/*+ IndexOnlyScan(s2) */' +\set query ':P :Q1 SELECT s1.* FROM simple s1, simple s2 WHERE s2.i < 5;' +\i :iter_P2 + +-- set a size limit. this indicates the size of the response +set yb_fetch_row_limit = 0; +set yb_fetch_size_limit = '3kB'; +insert into simple select * from generate_series(2, 1000); +explain (analyze, dist, costs off, timing off, summary off) +/*+ IndexOnlyScan(simple) */ SELECT 'a' FROM simple WHERE i < 5; + +reset yb_fetch_row_limit; +reset yb_fetch_size_limit; + +drop table simple; diff --git a/src/test/regress/sql/yb.orig.index_scan_null_asc.sql b/src/test/regress/sql/yb.orig.index_scan_null_asc.sql new file mode 100644 index 0000000000000..1cb58443edbb5 --- /dev/null +++ b/src/test/regress/sql/yb.orig.index_scan_null_asc.sql @@ -0,0 +1,15 @@ +-- Test null non-hash scan key + +-- the test tables are created in yb.orig.index_scan_null_create + +SET client_min_messages=error; + +DROP INDEX IF EXISTS i_nulltest_a; +CREATE INDEX i_nulltest_a ON nulltest (a ASC); + +DROP INDEX IF EXISTS i_nulltest_ba; +CREATE INDEX i_nulltest_ba ON nulltest (b ASC, a ASC); + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/index_scan_null_queries.sql' +\i :filename diff --git a/src/test/regress/sql/yb.orig.index_scan_null_create.sql b/src/test/regress/sql/yb.orig.index_scan_null_create.sql new file mode 100644 index 0000000000000..371d3b24fc373 --- /dev/null +++ b/src/test/regress/sql/yb.orig.index_scan_null_create.sql @@ -0,0 +1,11 @@ +-- Create tables for the null scan key tests +-- +-- As of 2023-06-21, the tables will default to 3 tablets, but in case those +-- defaults change, explicitly set the numbers here. The number of tablets +-- affects the number of requests shown in EXPLAIN DIST. + +CREATE TABLE nulltest (a int, b int, s serial PRIMARY KEY) SPLIT INTO 3 TABLETS; +INSERT INTO nulltest VALUES (null, null), (null, 1), (1, null), (1, 1); + +CREATE TABLE nulltest2 (x int, y int, s serial PRIMARY KEY) SPLIT INTO 3 TABLETS; +INSERT INTO nulltest2 VALUES (null, null); diff --git a/src/test/regress/sql/yb.orig.index_scan_null_hash.sql b/src/test/regress/sql/yb.orig.index_scan_null_hash.sql new file mode 100644 index 0000000000000..d2eca56aebe8e --- /dev/null +++ b/src/test/regress/sql/yb.orig.index_scan_null_hash.sql @@ -0,0 +1,15 @@ +-- Test null hash scan key + +-- the test tables are created in yb.orig.index_scan_null_create + +SET client_min_messages=error; + +DROP INDEX IF EXISTS i_nulltest_a; +CREATE INDEX i_nulltest_a ON nulltest (a HASH); + +DROP INDEX IF EXISTS i_nulltest_ba; +CREATE INDEX i_nulltest_ba ON nulltest ((b, a) HASH); + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/index_scan_null_queries.sql' +\i :filename diff --git a/src/test/regress/sql/yb.orig.index_selectivity.sql b/src/test/regress/sql/yb.orig.index_selectivity.sql new file mode 100644 index 0000000000000..d17b7031b62ce --- /dev/null +++ b/src/test/regress/sql/yb.orig.index_selectivity.sql @@ -0,0 +1,101 @@ +-- +-- INDEX SELECTIVITY +-- +-- +-- The following queries can use any index depending on cost estimator. +-- +EXPLAIN (COSTS OFF) SELECT count(*) FROM airports WHERE iso_region = 'US-CA'; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY ident; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY ident LIMIT 1; +-- +-- The following query should use PRIMARY KEY index. +-- +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' LIMIT 1; +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' ORDER BY ident LIMIT 1; +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' ORDER BY ident ASC LIMIT 1; +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' ORDER BY ident DESC LIMIT 1; +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' ORDER BY iso_region, ident LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND ident >= '4' LIMIT 2; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND ident < '4' LIMIT 2; +-- +-- The following query should use "airports_idx1" index +-- +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' ORDER BY name DESC LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' ORDER BY name ASC LIMIT 1; +-- +-- The following query should use "airports_idx2" index +-- +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' ORDER BY gps_code ASC LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' ORDER BY gps_code DESC LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports ORDER BY iso_region, gps_code LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports ORDER BY iso_region ASC, gps_code ASC LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports ORDER BY iso_region DESC, gps_code DESC LIMIT 1; +-- +-- The following query should use "airports_idx3" index +-- +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates, ident, name LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates ASC, ident, name LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates DESC, ident DESC, name DESC LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates, ident, name ASC LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates, ident LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates LIMIT 1; +-- +-- The following query also use "airports_idx3" index but not fully-covered. +-- +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates, ident, name LIMIT 1; +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates ASC, ident, name LIMIT 1; +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates DESC, ident DESC, name DESC LIMIT 1; +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates, ident, name ASC LIMIT 1; +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates, ident LIMIT 1; +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates LIMIT 1; +-- +-- The following query should use "airports_idx3" index without LIMIT. +-- +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates, ident, name; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates, ident; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates; +-- +-- The following query should use "airports_idx3" index but not fully-covered and no LIMIT. +-- +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates, ident, name; +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates, ident; +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates; +-- +-- The following queries cannot be optimized. Either WHERE clause is missing or its given filter +-- cannot be optimized. +-- +-- No where clause +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports; +-- Use '!=' on hash column. +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region != 'US-CA'; +-- ORDER BY hash column. +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' ORDER BY type, coordinates; +-- ORDER BY in wrong direction. +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports ORDER BY iso_region ASC, gps_code DESC LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates DESC, ident, name LIMIT 1; +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' AND type = 'small_airport' + ORDER BY coordinates, ident, name DESC LIMIT 1; +-- HASH column is not completely specified while ordering by RANGE column +EXPLAIN (COSTS OFF) SELECT gps_code FROM airports WHERE iso_region = 'US-CA' + ORDER BY type, coordinates LIMIT 1; diff --git a/src/test/regress/sql/yb.orig.indexing_order.sql b/src/test/regress/sql/yb.orig.indexing_order.sql new file mode 100644 index 0000000000000..7706ccacd2217 --- /dev/null +++ b/src/test/regress/sql/yb.orig.indexing_order.sql @@ -0,0 +1,232 @@ +CREATE OR REPLACE FUNCTION get_table_indexes(table_name text) +RETURNS TABLE( + relname name, + indisprimary boolean, + indisunique boolean, + indexdef text, + constraintdef text +) AS $$ +BEGIN + RETURN QUERY EXECUTE + 'SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), ' || + 'pg_catalog.pg_get_constraintdef(con.oid, true) ' || + 'FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i ' || + 'LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN (''p'',''u'',''x'')) ' || + 'WHERE c.oid = ' || quote_literal(table_name) || '::regclass AND c.oid = i.indrelid AND i.indexrelid = c2.oid ' || + 'ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname'; +END; +$$ LANGUAGE plpgsql; + +SET yb_use_hash_splitting_by_default = true; -- should default to true +CREATE TABLE hash_table(a int primary key, b int, c int); -- column a should be HASH +SELECT * FROM get_table_indexes('hash_table'); + +CREATE TABLE hash_with_asc(a int, b int, c int, primary key(a, b)); -- column a HASH column b ASC +SELECT * FROM get_table_indexes('hash_with_asc'); + +CREATE TABLE mixed_hash_with_asc(a int, b int, c int, primary key((a,b) HASH, c)); -- column a+b HASH column c ASC +SELECT * FROM get_table_indexes('mixed_hash_with_asc'); + +CREATE TABLE multi_index_hash_default(a int, b int, c int); + +ALTER TABLE multi_index_hash_default ADD PRIMARY KEY(a); -- a HASH +SELECT * FROM get_table_indexes('multi_index_hash_default'); +ALTER TABLE multi_index_hash_default DROP CONSTRAINT multi_index_hash_default_pkey; + +ALTER TABLE multi_index_hash_default ADD PRIMARY KEY(a,b); -- a HASH b ASC +SELECT * FROM get_table_indexes('multi_index_hash_default'); +ALTER TABLE multi_index_hash_default DROP CONSTRAINT multi_index_hash_default_pkey; + +ALTER TABLE multi_index_hash_default ADD PRIMARY KEY(a,b ASC); +SELECT * FROM get_table_indexes('multi_index_hash_default'); +ALTER TABLE multi_index_hash_default DROP CONSTRAINT multi_index_hash_default_pkey; + +ALTER TABLE multi_index_hash_default ADD PRIMARY KEY(a,b HASH); -- error + +ALTER TABLE multi_index_hash_default ADD PRIMARY KEY(a, b); + +CREATE INDEX ON multi_index_hash_default (b); -- HASH +CREATE INDEX ON multi_index_hash_default (b HASH); +CREATE INDEX ON multi_index_hash_default (c ASC); +CREATE INDEX ON multi_index_hash_default ((a, c), b); +CREATE INDEX ON multi_index_hash_default ((a, c)); +CREATE INDEX ON multi_index_hash_default (a, b); +CREATE INDEX ON multi_index_hash_default (b, c ASC); +CREATE INDEX ON multi_index_hash_default (b, c HASH); -- error + +SELECT * FROM get_table_indexes('multi_index_hash_default'); + +-- With HASH as default, creating a table or index with SPLIT AT should fail +CREATE INDEX ON multi_index_hash_default (c) SPLIT AT VALUES((10), (20), (30)); +CREATE TABLE split_fail(a int primary key, b int) SPLIT AT VALUES ((1000),(2000),(3000)); + +-- Confirm that create table like creates the same sort order even after toggling yb_use_hash_splitting_by_default +SET yb_use_hash_splitting_by_default = false; + +CREATE TABLE like_hash_default_table(like multi_index_hash_default including all); + +-- Indexes should have the same sort order +SELECT * FROM get_table_indexes('like_hash_default_table'); + +-- Will be recreated again later +DROP TABLE mixed_hash_with_asc; + +CREATE TABLE asc_table(a int primary key, b int, c int); -- column a should be ASC +SELECT * FROM get_table_indexes('asc_table'); + +CREATE TABLE asc_two_column_table(a int, b int, c int, primary key(a, b)); -- column a ASC column b ASC +SELECT * FROM get_table_indexes('asc_two_column_table'); + +CREATE TABLE mixed_hash_with_asc(a int, b int, c int, primary key((a,b) HASH, c)); -- column a+b HASH column c ASC +SELECT * FROM get_table_indexes('mixed_hash_with_asc'); + +CREATE TABLE multi_index_asc_default(a int, b int, c int); + +ALTER TABLE multi_index_asc_default ADD PRIMARY KEY(a); -- a ASC +SELECT * FROM get_table_indexes('multi_index_asc_default'); +ALTER TABLE multi_index_asc_default DROP CONSTRAINT multi_index_asc_default_pkey; + +ALTER TABLE multi_index_asc_default ADD PRIMARY KEY(a,b); -- a ASC b ASC +SELECT * FROM get_table_indexes('multi_index_asc_default'); +ALTER TABLE multi_index_asc_default DROP CONSTRAINT multi_index_asc_default_pkey; + +ALTER TABLE multi_index_asc_default ADD PRIMARY KEY(a,b ASC); +SELECT * FROM get_table_indexes('multi_index_asc_default'); +ALTER TABLE multi_index_asc_default DROP CONSTRAINT multi_index_asc_default_pkey; + +ALTER TABLE multi_index_asc_default ADD PRIMARY KEY(a,b HASH); -- error + +ALTER TABLE multi_index_asc_default ADD PRIMARY KEY(a, b); + +CREATE INDEX ON multi_index_asc_default (b); -- ASC +CREATE INDEX ON multi_index_asc_default (b HASH); +CREATE INDEX ON multi_index_asc_default (c ASC); +CREATE INDEX ON multi_index_asc_default ((a, c), b); +CREATE INDEX ON multi_index_asc_default ((a, c)); +CREATE INDEX ON multi_index_asc_default (a, b); +CREATE INDEX ON multi_index_asc_default (b, c ASC); +CREATE INDEX ON multi_index_asc_default (b, c HASH); -- error + +SELECT * FROM get_table_indexes('multi_index_asc_default'); + +-- With ASC as default, creating a table with SPLIT AT should succeed +CREATE TABLE split_table(a int primary key, b int) SPLIT AT VALUES ((1000),(2000),(3000)); +CREATE INDEX ON split_table (b) SPLIT AT VALUES((10), (20), (30)); + +SELECT * FROM get_table_indexes('split_table'); + +-- Confirm that create table like creates the same sort order even after toggling yb_use_hash_splitting_by_default +SET yb_use_hash_splitting_by_default = true; + +CREATE TABLE like_asc_default_table(like multi_index_asc_default including all); + +-- Indexes should have the same sort order +SELECT * FROM get_table_indexes('like_asc_default_table'); + +SET yb_use_hash_splitting_by_default = false; + +-- Partitioned table +CREATE TABLE range_partitioned_example +( + id serial, + date_recorded date, + data text, + PRIMARY KEY (id, date_recorded) +) PARTITION BY RANGE (date_recorded); + +CREATE TABLE range_partitioned_example_y2023 PARTITION OF range_partitioned_example + FOR VALUES FROM ('2023-01-01') TO ('2024-01-01'); + +CREATE TABLE range_partitioned_example_y2024 PARTITION OF range_partitioned_example + FOR VALUES FROM ('2024-01-01') TO ('2025-01-01'); + +CREATE INDEX ON range_partitioned_example (date_recorded); + +SELECT * FROM get_table_indexes('range_partitioned_example'); +SELECT * FROM get_table_indexes('range_partitioned_example_y2023'); +SELECT * FROM get_table_indexes('range_partitioned_example_y2024'); + +-- GIN +CREATE TABLE gin_example (id serial PRIMARY KEY, data jsonb); + +CREATE INDEX data_gin ON gin_example USING gin(data); + +SELECT * FROM get_table_indexes('gin_example'); + +-- Temp table +CREATE TEMP TABLE temp_example (id serial, name text, value int, primary key(id HASH)); -- fails + +CREATE TEMP TABLE temp_example (id serial, name text, value int, primary key(id, name HASH)); -- fails + +CREATE TEMP TABLE temp_example (id serial, name text, value int, primary key((id, name), value)); -- fails + +CREATE TEMP TABLE temp_example (id serial PRIMARY KEY, name text, value int); + +CREATE INDEX temp_value_lsm ON temp_example USING lsm(value); -- fails +CREATE INDEX temp_value_idx ON temp_example (value); + +SELECT * FROM get_table_indexes('temp_example'); + +CREATE TABLE foo(a int, b int, c int, primary key (a HASH, b HASH, c ASC)); +CREATE TABLE foo(a int, b int, c int, primary key (a, b HASH, c)); +CREATE TABLE foo(a int, b int, c int, primary key ((a, b) ASC, c)); + +-- Creating this table with 3 tablets should not work when yb_use_hash_splitting_by_default = false +-- because the primary key is a range partition. +CREATE TABLE foo(a int primary key, b int) SPLIT (INTO 3 TABLETS); -- fails +CREATE TABLE foo(a int, b int, primary key(a)) SPLIT (INTO 3 TABLETS); -- fails +CREATE TABLE foo(a int, b int, primary key(a HASH)) SPLIT (INTO 3 TABLETS); -- succeeds + +SET yb_use_hash_splitting_by_default = true; + +CREATE TABLE bar(a int primary key, b int) SPLIT (INTO 3 TABLETS); -- succeeds +CREATE TABLE baz(a int, b int, primary key(a)) SPLIT (INTO 3 TABLETS); -- succeeds + +DROP TABLE foo, bar, baz; + +-- TABLEGROUP tests +CREATE TABLEGROUP postgres_ordering; + +SET yb_use_hash_splitting_by_default = false; + +CREATE TABLE tg_hash_key (a int, b int, c int, primary key((a,b) HASH, c)) TABLEGROUP postgres_ordering; -- fails +CREATE TABLE tg_hash_key (a int, b int, c int, primary key((a,b), c)) TABLEGROUP postgres_ordering; -- fails +CREATE TABLE tg_hash_key (a int, b int, c int, primary key(a HASH)) TABLEGROUP postgres_ordering; -- fails + +CREATE TABLE tablegroup_table (a int, b int, c int, primary key(a)) TABLEGROUP postgres_ordering; -- succeeds + +SELECT * FROM get_table_indexes('tablegroup_table'); + +ALTER TABLE tablegroup_table DROP CONSTRAINT tablegroup_table_pkey; +ALTER TABLE tablegroup_table ADD PRIMARY KEY (a); + +CREATE INDEX ON tablegroup_table (b); -- ASC +CREATE INDEX ON tablegroup_table (b HASH); -- error +CREATE INDEX ON tablegroup_table (c ASC); +CREATE INDEX ON tablegroup_table ((a, c), b); -- error +CREATE INDEX ON tablegroup_table ((a, c)); --error +CREATE INDEX ON tablegroup_table (a, b); +CREATE INDEX ON tablegroup_table (b, c ASC); +CREATE INDEX ON tablegroup_table (b, c HASH); -- error + +SELECT * FROM get_table_indexes('tablegroup_table'); + +CREATE TABLE like_tablegroup_table (like tablegroup_table including all) TABLEGROUP postgres_ordering; + +SELECT * FROM get_table_indexes('like_tablegroup_table'); + +SET yb_use_hash_splitting_by_default = true; + +CREATE TABLE like_tablegroup_table2 (like tablegroup_table including all) TABLEGROUP postgres_ordering; + +-- With ASC as default, creating a table in a tablegroup with SPLIT AT should fail +CREATE TABLE split_fail(a int primary key, b int) + SPLIT AT VALUES ((1000),(2000),(3000)) + TABLEGROUP postgres_ordering; + +CREATE TABLE like_hash_table (like hash_table including all) TABLEGROUP postgres_ordering; + +SET yb_use_hash_splitting_by_default = false; + +CREATE TABLE like_hash_table (like hash_table including all) TABLEGROUP postgres_ordering; + diff --git a/src/test/regress/sql/yb.orig.inherit.sql b/src/test/regress/sql/yb.orig.inherit.sql new file mode 100644 index 0000000000000..626bbec19c501 --- /dev/null +++ b/src/test/regress/sql/yb.orig.inherit.sql @@ -0,0 +1,18 @@ +-- Test non-inheritable primary key constraints +-- primary key on inh parent does not recurse +CREATE TABLE parent(id int PRIMARY KEY, c1 int); +CREATE TABLE child (c2 int) INHERITS (parent); +-- unique constraint on inh parent does not recurse +ALTER TABLE parent ADD CONSTRAINT parent_uniq_c1 UNIQUE(c1); +\d+ parent +\d+ child +ALTER TABLE parent DROP CONSTRAINT parent_pkey; +\d+ parent +\d+ child +ALTER TABLE parent DROP CONSTRAINT parent_uniq_c1; +\d+ parent +\d+ child +-- Test combining alter table cmds with inherit #27263 +ALTER TABLE child NO INHERIT parent, ADD COLUMN c3 int; +SELECT * FROM child; +DROP TABLE parent, child; diff --git a/src/test/regress/sql/yb.orig.insert_on_conflict.sql b/src/test/regress/sql/yb.orig.insert_on_conflict.sql new file mode 100644 index 0000000000000..89eb75190e174 --- /dev/null +++ b/src/test/regress/sql/yb.orig.insert_on_conflict.sql @@ -0,0 +1,608 @@ +CREATE TABLE ab_tab (a int, b int); +INSERT INTO ab_tab SELECT g, g + 10 FROM generate_series(1, 10) g; + +--- Basic +CREATE UNIQUE INDEX NONCONCURRENTLY ah_idx ON ab_tab (a HASH); +-- Ending with no conflict. +INSERT INTO ab_tab VALUES (generate_series(-4, 5)) ON CONFLICT DO NOTHING; +SELECT * FROM ab_tab WHERE a < 1 OR a > 10 ORDER BY a, b; +-- Ending with conflict. +INSERT INTO ab_tab VALUES (generate_series(6, 15)) ON CONFLICT DO NOTHING; +SELECT * FROM ab_tab WHERE a < 1 OR a > 10 ORDER BY a, b; +-- Reset. +DELETE FROM ab_tab WHERE a < 1 OR a > 10; + +--- RETURNING +INSERT INTO ab_tab VALUES (generate_series(-3, 13)) ON CONFLICT DO NOTHING RETURNING (a % 5); +SELECT * FROM ab_tab WHERE a < 1 OR a > 10 ORDER BY a, b; +-- Reset. +DELETE FROM ab_tab WHERE a < 1 OR a > 10; + +--- DO UPDATE +BEGIN; +INSERT INTO ab_tab VALUES (generate_series(-3, 13)) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.a; +SELECT * FROM ab_tab ORDER BY a, b; +-- Reset. +ROLLBACK; + +--- DO UPDATE with existing row +BEGIN; +INSERT INTO ab_tab VALUES (generate_series(-3, 13)) ON CONFLICT (a) DO UPDATE SET b = ab_tab.a + 1; +SELECT * FROM ab_tab ORDER BY a, b; +-- Reset. +ROLLBACK; + +--- DO UPDATE with RETURNING +BEGIN; +-- Column b should be returned as NULL for rows a = [-3, 0] +INSERT INTO ab_tab VALUES (generate_series(-3, 5)) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.a RETURNING b, (b % 5); +-- Column b should be returned as a + 1 for rows a = [6, 10] and NULL for a = [11, 13] +INSERT INTO ab_tab AS old VALUES (generate_series(6, 13)) ON CONFLICT (a) DO UPDATE SET b = old.a + 1 RETURNING a, (b % 5); +SELECT * FROM ab_tab ORDER BY a, b; +ROLLBACK; + +--- Accessing the EXCLUDED row from the RETURNING clause should be disallowed +INSERT INTO ab_tab VALUES (generate_series(-3, 13)) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.a RETURNING EXCLUDED.b, b, (b % 5); + +--- SPI +BEGIN; +SELECT yb_run_spi($$ + INSERT INTO ab_tab VALUES (1) ON CONFLICT DO NOTHING RETURNING a +$$, 1); +SELECT count(*), min(a), max(a) FROM ab_tab; +SELECT yb_run_spi($$ + INSERT INTO ab_tab VALUES (null) ON CONFLICT DO NOTHING RETURNING a +$$, 1); +SELECT count(*), min(a), max(a) FROM ab_tab; +SELECT yb_run_spi($$ + INSERT INTO ab_tab VALUES (generate_series(-10, 20)) ON CONFLICT DO NOTHING RETURNING b +$$, 100); +SELECT count(*), min(a), max(a) FROM ab_tab; +SELECT yb_run_spi($$ + INSERT INTO ab_tab VALUES (generate_series(-20, 30)) ON CONFLICT DO NOTHING RETURNING b, a +$$, 15); +SELECT count(*), min(a), max(a) FROM ab_tab; +SELECT yb_run_spi($$ + INSERT INTO ab_tab VALUES (generate_series(-30, 40)) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.a RETURNING (a + b) +$$, 45); +SELECT count(*), min(a), max(a) FROM ab_tab; +ABORT; + +--- Multiple arbiter indexes +CREATE UNIQUE INDEX NONCONCURRENTLY br_idx ON ab_tab (b ASC); +-- No constraint specification. +-- (1, 1): conflict on i +-- (10, 10): conflict on both i and j +-- (15, 15): conflict on j +INSERT INTO ab_tab VALUES (1, 1), (-30, -30), (10, 10), (15, 15), (30, 30) ON CONFLICT DO NOTHING; +SELECT * FROM ab_tab WHERE a < 1 OR a > 10 ORDER BY a, b; +-- Yes constraint specification. +INSERT INTO ab_tab VALUES (2, 2) ON CONFLICT (a) DO NOTHING; +INSERT INTO ab_tab VALUES (2, 2) ON CONFLICT (b) DO NOTHING; +SELECT * FROM ab_tab WHERE a < 1 OR a > 10 ORDER BY a, b; + +--- GH-25240 (multiple arbiter indexes) +CREATE TABLE t_multiple (c CHAR, n NUMERIC); +CREATE UNIQUE INDEX NONCONCURRENTLY i1 ON t_multiple (c) WHERE n % 2 = 0; +CREATE UNIQUE INDEX NONCONCURRENTLY i2 ON t_multiple (c) WHERE n < 10; +INSERT INTO t_multiple VALUES ('a', 1), ('b', 2); +-- ('c', false::INT::NUMERIC): not found in either index and satisfies predicate of both indexes. +-- Result: inserted in main table and both indexes. +-- ('d', 7), ('e', 14): not found in either index and satisfies predicate of only one index. +-- Result: inserted in main table and only one index. +INSERT INTO t_multiple VALUES ('c', false::INT::NUMERIC), ('d', 7), ('e', 14) ON CONFLICT DO NOTHING; +-- ('d', 6), ('e', 4): found in one index and satisfies predicate of both indexes. +-- Result: not inserted. +INSERT INTO t_multiple VALUES ('d', 6), ('e', 4) ON CONFLICT DO NOTHING; +-- ('d', 13), ('e', 15): found in one index and satisfies predicate of neither index. +-- Result: inserted into main table, but not indexes. +INSERT INTO t_multiple VALUES ('d', 13), ('e', 15) ON CONFLICT DO NOTHING; +-- ('b', 4): found in both indexes and satisfies predicate of both indexes. +-- Result: not inserted. +-- ('b', 12), ('b', 1): found in both indexes and satisfies predicate of one index. +-- Result: not inserted. +-- ('b', 13): found in both indexes and satisfies predicate of neither index. +-- Result: inserted into main table, but not indexes. +INSERT INTO t_multiple VALUES ('b', 4), ('b', 12), ('b', 1), ('b', 13) ON CONFLICT DO NOTHING; +-- ('a', 12), ('e', 7): found in one index and satisfies predicate of other index. +-- Result: inserted into main table and 'other' index. +INSERT INTO t_multiple VALUES ('a', 12), ('e', 7) ON CONFLICT DO NOTHING; +-- ('f', 7), ('g', 12): satisfies predicate of only one index. +-- Result: inserted in main table and only one index. +-- ('f', 8), ('g', 8): found in one index and satisfies predicate of both indexes. +-- Result: not inserted. +-- ('f', 5), ('g', 14): found in one index and satisfies predicate of same index. +-- Result: not inserted. +-- ('f', 16), ('g', 7): found in one index and satisfies predicate of other index. +-- Result: inserted in main table and 'other' index. +-- ('f', 13), ('g', 13): found in both indexes and satisfies predicate of neither index. +-- Result: inserted into main table, but not indexes. +-- ('f', 2), ('g', 2): found in both indexes and satisfies predicate of both indexes. +-- Result: not inserted. +INSERT INTO t_multiple VALUES + ('f', 7), ('g', 12), + ('f', 8), ('g', 8), + ('f', 5), ('g', 14), + ('f', 16), ('g', 7), + ('f', 13), ('g', 13), + ('f', 2), ('g', 2) + ON CONFLICT DO NOTHING; +SELECT * FROM t_multiple WHERE n % 2 = 0 ORDER BY c, n; +SELECT * FROM t_multiple WHERE n < 10 ORDER BY c, n; +SELECT * FROM t_multiple ORDER BY c, n; +TRUNCATE t_multiple; +DROP INDEX i2; +-- Create a duplicate index to exercise the multiple arbiter index code path for +-- DO UPDATE in the context of unique indexes. +CREATE UNIQUE INDEX NONCONCURRENTLY i1_copy ON t_multiple (c) WHERE n % 2 = 0; +INSERT INTO t_multiple VALUES ('a', 1), ('b', 2); +-- ('a', 1): not found in the indexes, and proposed updated value into does not satisfy the predicate. +-- Result: inserted into main table, but not the indexes. +-- ('b', 2): found in the indexes, and updated value satisfies the predicate. +-- Result: updated in main table and indexes. +EXPLAIN (COSTS OFF) INSERT INTO t_multiple VALUES ('a', 1), ('b', 2) ON CONFLICT (c) WHERE n % 2 = 0 DO UPDATE SET n = EXCLUDED.n + 2; +INSERT INTO t_multiple VALUES ('a', 1), ('b', 2) ON CONFLICT (c) WHERE n % 2 = 0 DO UPDATE SET n = EXCLUDED.n + 2; +SELECT * FROM t_multiple WHERE n % 2 = 0 ORDER BY c, n; +-- ('a', 1): not found in the indexes, and proposed updated value satisfies the predicate. +-- Result: inserted in main table, but not the indexes. +-- ('b', 2): found in the indexes, and updated value does not satisfy the predicate. +-- Result: updated in main table and deleted from the indexes. +INSERT INTO t_multiple VALUES ('a', 1), ('b', 2) ON CONFLICT (c) WHERE n % 2 = 0 DO UPDATE SET n = EXCLUDED.n + 3; +-- first ('b', 2): not found in the indexes. +-- Result: inserted into main table and indexes. +-- second ('b', 2): found in the indexes, and contains same index key as the first row. +-- Result: not inserted. +INSERT INTO t_multiple VALUES ('b', 2), ('b', 2) ON CONFLICT (c) WHERE n % 2 = 0 DO UPDATE SET n = EXCLUDED.n + 3; +SELECT * FROM t_multiple WHERE n % 2 = 0 ORDER BY c, n; +SELECT * FROM t_multiple ORDER BY c, n; +DROP TABLE t_multiple; + +--- Multiple unique indexes but single arbiter index +INSERT INTO ab_tab VALUES (21, 21), (22, 23); +-- (24, 21) conflicts on b but not a and should produce a unique constraint violation. +INSERT INTO ab_tab VALUES (24, 21) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b; +-- (22, 22) conflicts on a but not b and should produce a unique constraint violation. +INSERT INTO ab_tab VALUES (22, 22) ON CONFLICT (b) DO UPDATE SET b = EXCLUDED.b; +-- Reset. +DELETE FROM ab_tab WHERE a < 1 OR a > 10; +DROP INDEX ah_idx; +DROP INDEX br_idx; + +--- Multicolumn index +CREATE UNIQUE INDEX NONCONCURRENTLY bharbr_idx ON ab_tab (b HASH, a DESC, b ASC); +BEGIN; +INSERT INTO ab_tab VALUES (0, 10), (1, 11), (2, 12), (2, 13) ON CONFLICT DO NOTHING; +SELECT * FROM ab_tab WHERE a < 1 OR a > 10 ORDER BY a, b; +-- Reset. +ROLLBACK; +DROP INDEX bharbr_idx; + +--- Many-to-one expression index +CREATE UNIQUE INDEX NONCONCURRENTLY expr_idx ON ab_tab (((a + b) % 100)); +-- Conflicts: +-- 12: existing (1, 11), several values mapping to that are inserted +-- 77: two such values are inserted, only one goes through +-- 98: no conflict +BEGIN; +INSERT INTO ab_tab VALUES (12, 0), (112, 0), (99, 99), (100, 12), (77, 0), (78, -1) ON CONFLICT (((a + b) % 100)) DO NOTHING; +SELECT * FROM ab_tab WHERE a < 1 OR a > 10 ORDER BY a, b; +ROLLBACK; +-- Same with DO UPDATE. +BEGIN; +INSERT INTO ab_tab VALUES (12, 0), (112, 0), (99, 99), (100, 12), (77, 0), (78, -1) ON CONFLICT (((a + b) % 100)) DO UPDATE SET b = 1010 + EXCLUDED.b; +SELECT * FROM ab_tab WHERE a < 1 OR a > 10 ORDER BY a, b; +ROLLBACK; +-- Reset. +DROP INDEX expr_idx; + +--- Partial indexes +CREATE UNIQUE INDEX NONCONCURRENTLY b1_idx ON ab_tab (b) WHERE a % 10 = 1; +CREATE UNIQUE INDEX NONCONCURRENTLY b2_idx ON ab_tab (b) WHERE a % 10 = 2; +-- Conflicts: +-- b1_idx: existing (1, 11), conflicts with (101, 11) +-- b2_idx: existing (2, 12), conflicts with (202, 12) +INSERT INTO ab_tab VALUES (100, 11), (101, 11), (102, 11), (201, 201), (202, 12) ON CONFLICT DO NOTHING; +SELECT * FROM ab_tab WHERE a < 1 OR a > 10 ORDER BY a, b; +-- No partial index matches. +INSERT INTO ab_tab VALUES (55, 55), (66, 66), (77, 77), (88, 88) ON CONFLICT DO NOTHING; +SELECT * FROM ab_tab WHERE a < 1 OR a > 10 ORDER BY a, b; +-- Reset. +DELETE FROM ab_tab WHERE a < 1 OR a > 10; + +--- Index predicate matching +CREATE UNIQUE INDEX NONCONCURRENTLY bfull_idx ON ab_tab (b); +BEGIN; +INSERT INTO ab_tab VALUES (101, 101), (102, 102); +-- Index predicate should satisfy both the partial index (b1_idx) as well as the full index (bfull_idx). +EXPLAIN (COSTS OFF) INSERT INTO ab_tab VALUES (101, 101), (201, 201) ON CONFLICT (b) WHERE a % 10 = 1 DO NOTHING; +INSERT INTO ab_tab VALUES (101, 101), (201, 201) ON CONFLICT (b) WHERE a % 10 = 1 DO NOTHING RETURNING *, a % 10 AS modulo; +-- Index predicate should satisfy b1_idx and bfull_idx but the inserted row should satisfy only bfull_idx. +EXPLAIN (COSTS OFF) INSERT INTO ab_tab VALUES (102, 102), (202, 202) ON CONFLICT (b) WHERE a % 10 = 1 DO NOTHING; +INSERT INTO ab_tab VALUES (102, 102) ON CONFLICT (b) WHERE a % 10 = 1 DO NOTHING RETURNING *, a % 10 AS modulo; -- should return nothing +INSERT INTO ab_tab VALUES (202, 202) ON CONFLICT (b) WHERE a % 10 = 1 DO NOTHING RETURNING *, a % 10 AS modulo; -- should return row +SELECT * FROM ab_tab WHERE a < 1 OR a > 10 ORDER BY a, b; +ROLLBACK; +-- Exclude the full index by specifying the partial index as a constraint. +-- Unique constraints cannot be defined on partial indexes. +CREATE UNIQUE INDEX NONCONCURRENTLY b3_idx ON ab_tab (b) WHERE a % 10 = 3; +ALTER TABLE ab_tab ADD CONSTRAINT b3_idx_constr UNIQUE USING INDEX b3_idx; +DROP INDEX b3_idx; +DROP INDEX bfull_idx; +INSERT INTO ab_tab VALUES (101, 101), (102, 102); +BEGIN; +-- Index predicate corresponding to b1_idx does not satisfy the inserted row +INSERT INTO ab_tab VALUES (103, 103) ON CONFLICT (b) WHERE a % 10 = 1 DO NOTHING RETURNING *, a % 10 AS modulo; +-- However, a different partial index and should produce a unique constraint violation. +INSERT INTO ab_tab VALUES (102, 102) ON CONFLICT (b) WHERE a % 10 = 1 DO NOTHING; +ROLLBACK; +-- Reset. +DELETE FROM ab_tab WHERE a < 1 OR a > 10; +DROP INDEX b1_idx; +DROP INDEX b2_idx; + +--- Defaults +CREATE TABLE ioc_defaults (a INT, b INT DEFAULT 42, c INT DEFAULT NULL); +INSERT INTO ioc_defaults (a) VALUES (1); +CREATE UNIQUE INDEX NONCONCURRENTLY ioc_defaults_b_idx ON ioc_defaults (b); +INSERT INTO ioc_defaults VALUES (1) ON CONFLICT DO NOTHING RETURNING *; +INSERT INTO ioc_defaults VALUES (1), (1) ON CONFLICT (b) DO UPDATE SET b = ioc_defaults.b + 1 RETURNING *; +-- Not modifying the default value should produce an error. +-- TODO(kramanathan): Uncomment when RETURNING is supported by batch insert on conflict. +-- INSERT INTO ioc_defaults VALUES (1), (1) ON CONFLICT (b) DO UPDATE SET c = ioc_defaults.a + 1 RETURNING *; +SELECT * FROM ioc_defaults ORDER BY b, c; +DROP INDEX ioc_defaults_b_idx; +TRUNCATE ioc_defaults; + +--- NULLS DISTINCT +CREATE UNIQUE INDEX NONCONCURRENTLY ah_idx ON ab_tab (a HASH); +INSERT INTO ab_tab VALUES (null, null); +INSERT INTO ab_tab VALUES (null, null) ON CONFLICT DO NOTHING; +-- Multiple rows with NULL values should semantically be treated as distinct rows. +INSERT INTO ab_tab VALUES (null, 1), (null, 2) ON CONFLICT DO NOTHING; +INSERT INTO ab_tab VALUES (null, 1), (null, 2) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b; +SELECT * FROM ab_tab WHERE a IS NULL ORDER BY b; +-- Similarly, columns with default NULL values should be treated as distinct rows. +CREATE UNIQUE INDEX NONCONCURRENTLY ioc_defaults_bc_idx ON ioc_defaults (b, c); +INSERT INTO ioc_defaults (a) VALUES (1); +INSERT INTO ioc_defaults VALUES (1), (1) ON CONFLICT (b, c) DO UPDATE SET a = EXCLUDED.a; +SELECT * FROM ioc_defaults ORDER BY b, c; +-- Reset. +DELETE FROM ab_tab WHERE a IS null; +DROP INDEX ioc_defaults_bc_idx; +TRUNCATE ioc_defaults; + +--- NULLS NOT DISTINCT +CREATE TABLE ab_tab2 (a int, b int); +CREATE UNIQUE INDEX NONCONCURRENTLY ah_idx2 ON ab_tab2 (a HASH) NULLS NOT DISTINCT; +INSERT INTO ab_tab2 VALUES (null, 1); +INSERT INTO ab_tab2 VALUES (null, 2) ON CONFLICT DO NOTHING; +SELECT * FROM ab_tab2; +INSERT INTO ab_tab2 VALUES (null, 3) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b; +SELECT * FROM ab_tab2; +INSERT INTO ab_tab2 VALUES (null, 4), (null, 5) ON CONFLICT DO NOTHING; +SELECT * FROM ab_tab2; +INSERT INTO ab_tab2 VALUES (null, 6), (null, 7) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b; +SELECT * FROM ab_tab2 ORDER BY a, b; +INSERT INTO ab_tab2 VALUES (null, 8), (null, 9) ON CONFLICT (a) DO UPDATE SET a = EXCLUDED.b; +SELECT * FROM ab_tab2 ORDER BY a, b; +INSERT INTO ab_tab2 VALUES (null, 10), (null, 11), (null, 12) ON CONFLICT (a) DO UPDATE SET a = EXCLUDED.b; +SELECT * FROM ab_tab2 ORDER BY a, b; +DROP TABLE ab_tab2; +-- Index key attributes > 1 +CREATE TABLE abc_tab (a int, b int, c int); +CREATE UNIQUE INDEX NONCONCURRENTLY abh_idx ON abc_tab ((a, b) HASH) NULLS NOT DISTINCT; +INSERT INTO abc_tab VALUES (123, null, 1), (456, null, 1), (null, null, 1); +INSERT INTO abc_tab VALUES (123, null, 2), (456, null, 2), (null, null, 2) ON CONFLICT DO NOTHING; +SELECT * FROM abc_tab ORDER BY a, b; +INSERT INTO abc_tab VALUES (123, null, 2), (456, null, 2), (null, null, 2) ON CONFLICT (a, b) DO UPDATE SET c = EXCLUDED.c; +SELECT * FROM abc_tab ORDER BY a, b; +DROP TABLE abc_tab; +-- Default NULL values +CREATE UNIQUE INDEX NONCONCURRENTLY ioc_defaults_bc_idx ON ioc_defaults (b, c) NULLS NOT DISTINCT; +INSERT INTO ioc_defaults VALUES (1); +INSERT INTO ioc_defaults VALUES (2) ON CONFLICT (b, c) DO UPDATE SET a = EXCLUDED.a; +SELECT * FROM ioc_defaults; +INSERT INTO ioc_defaults VALUES (3), (4) ON CONFLICT (b, c) DO UPDATE SET a = EXCLUDED.a; +SELECT * FROM ioc_defaults ORDER BY a; +DROP INDEX ioc_defaults_bc_idx; +TRUNCATE ioc_defaults; + +--- Partitioned table +CREATE TABLE pp (i serial, j int, UNIQUE (j)) PARTITION BY RANGE (j); +CREATE TABLE pp1 PARTITION OF pp FOR VALUES FROM (0) TO (10); +CREATE TABLE pp2 PARTITION OF pp FOR VALUES FROM (10) TO (20); +CREATE TABLE pp3 PARTITION OF pp FOR VALUES FROM (20) TO (30); +CREATE TABLE pp4 (i serial, j int, UNIQUE (j)) PARTITION BY RANGE (j); +CREATE TABLE pp44 PARTITION OF pp4 FOR VALUES FROM (30) TO (35); +CREATE TABLE pp49 PARTITION OF pp4 FOR VALUES FROM (35) TO (40); +ALTER TABLE pp ATTACH PARTITION pp4 FOR VALUES FROM (30) TO (40); +INSERT INTO pp (j) SELECT g * 17 % 40 FROM generate_series(1, 20) g; +SELECT * FROM pp ORDER BY i; +BEGIN; +INSERT INTO pp (j) SELECT g * 7 % 40 FROM generate_series(1, 40) g ON CONFLICT DO NOTHING; +SELECT * FROM pp ORDER BY i; +ABORT; +BEGIN; +INSERT INTO pp (j) SELECT g * 7 % 40 FROM generate_series(1, 40) g ON CONFLICT (j) DO UPDATE SET i = EXCLUDED.i + 100; +SELECT * FROM pp ORDER BY i % 100; +ABORT; + +--- Partitioned table with TEXT partition key +CREATE TABLE staff (id SERIAL, name TEXT, department TEXT, PRIMARY KEY (name HASH, department ASC)) PARTITION BY LIST (department); +CREATE TABLE staff_sales PARTITION OF staff FOR VALUES IN ('Sales'); +CREATE TABLE staff_engineering PARTITION OF staff FOR VALUES IN ('Engineering'); +CREATE TABLE staff_finance PARTITION OF staff FOR VALUES IN ('Finance'); +INSERT INTO staff (name, department) VALUES ('Eve Adams', 'Sales'), ('Frank Green', 'Engineering'); +INSERT INTO staff (name, department) VALUES ('Eve Adams', 'Sales'), ('Frank Green', 'Engineering') ON CONFLICT (name, department) DO NOTHING; +-- Cross-partition updates should be disallowed. +INSERT INTO staff (name, department) VALUES ('Eve Adams', 'Sales'), ('Frank Green', 'Engineering') ON CONFLICT (name, department) DO UPDATE SET department = 'Finance'; +INSERT INTO staff (name, department) VALUES ('Eve Adams', 'Sales'), ('Gwen Smith', 'Engineering') ON CONFLICT (name, department) DO UPDATE SET name = staff.name || ' (CONFLICT)'; +SELECT name, department FROM staff ORDER BY id; + +--- Complex types +CREATE TYPE complex_enum AS ENUM ('bob', 'cob', 'hob'); +CREATE TABLE complex_table (t timestamp UNIQUE, + b box, + v1 varchar(5) UNIQUE, + v2 varchar UNIQUE, + x text, + n numeric UNIQUE, + d decimal, + e complex_enum, + PRIMARY KEY (d, x, e DESC)); +CREATE UNIQUE INDEX NONCONCURRENTLY ON complex_table (n, e); +INSERT INTO complex_table VALUES ('2024-08-22 07:00:00+07'::timestamptz, + '(1, 2, 3, 4)', + 'abc', + 'def', + 'hij', + 12.34, + 56.78, + 'cob'); +INSERT INTO complex_table VALUES ('2024-08-22 06:00:00+06'::timestamptz, + '(5, 6, 7, 8)', + 'def', + 'hij', + 'abc', + 56.78, + 12.34, + 'bob') ON CONFLICT DO NOTHING; +SELECT count(*) FROM complex_table; + +--- ON CONFLICT DO UPDATE varlen type +CREATE TABLE varlen (t text, b bytea GENERATED ALWAYS AS (bytea(t)) STORED, UNIQUE (t)); +CREATE INDEX NONCONCURRENTLY ON varlen (b); +INSERT INTO varlen VALUES ('a'); +INSERT INTO varlen VALUES ('a'), ('a') ON CONFLICT (t) DO UPDATE SET t = 'b'; +INSERT INTO varlen VALUES ('a'), ('a') ON CONFLICT (t) DO UPDATE SET t = EXCLUDED.t || 'z'; +INSERT INTO varlen VALUES ('az'), ('az') ON CONFLICT (t) DO UPDATE SET t = varlen.t || 'z'; +SELECT * FROM varlen ORDER BY t; +-- Reset. +TRUNCATE varlen; + +--- Generated column as arbiter index +CREATE UNIQUE INDEX NONCONCURRENTLY ON varlen (b); +INSERT INTO varlen VALUES ('a'); +INSERT INTO varlen VALUES ('a'), ('a') ON CONFLICT (b) DO UPDATE SET t = 'b'; +INSERT INTO varlen VALUES ('a'), ('a') ON CONFLICT (b) DO UPDATE SET t = EXCLUDED.t || 'z'; +INSERT INTO varlen VALUES ('az'), ('az') ON CONFLICT (b) DO UPDATE SET t = varlen.t || 'z'; + +SELECT * FROM varlen ORDER BY t; + +--- ON CONFLICT DO UPDATE edge cases with PRIMARY KEY as arbiter index +CREATE TABLE ioc (i int, PRIMARY KEY (i ASC)); +BEGIN; +-- INSERT i=1, UPDATE i=1 to 21 +INSERT INTO ioc VALUES (1), (1) ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + 20; +ROLLBACK; +INSERT INTO ioc VALUES (1); +BEGIN; +-- UPDATE i=1 to 21, INSERT i=1 +INSERT INTO ioc VALUES (1), (1) ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + 20; +TABLE ioc; +ROLLBACK; +BEGIN; +-- INSERT i=20, UPDATE i=20 to 40 +INSERT INTO ioc VALUES (20), (20) ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + 20; +ROLLBACK; +BEGIN; +-- INSERT i=20, UPDATE i=1 to 20 +INSERT INTO ioc VALUES (20), (1) ON CONFLICT (i) DO UPDATE SET i = 20; +ROLLBACK; +BEGIN; +-- UPDATE i=1 to 20, UPDATE i=20 to 20 +INSERT INTO ioc VALUES (1), (20) ON CONFLICT (i) DO UPDATE SET i = 20; +ROLLBACK; +-- Reset. +DROP TABLE ioc; + +--- ON CONFLICT DO UPDATE edge cases with secondary index as arbiter index +CREATE TABLE ioc (i TEXT, j INT UNIQUE); +BEGIN; +-- INSERT j=1, UPDATE j=1 to 21 +INSERT INTO ioc VALUES ('row-1', 1), ('row-2', 1) ON CONFLICT (j) DO UPDATE SET j = EXCLUDED.j + 20; +ROLLBACK; +INSERT INTO ioc VALUES ('row-1', 1); +BEGIN; +-- UPDATE j=1 to 21, INSERT j=1 +INSERT INTO ioc VALUES ('row-1', 1), ('row-2', 1) ON CONFLICT (j) DO UPDATE SET j = EXCLUDED.j + 20; +SELECT * FROM ioc ORDER BY i, j; +ROLLBACK; +BEGIN; +-- INSERT j=20, UPDATE j=20 to 40 +INSERT INTO ioc VALUES ('row-2', 20), ('row-2', 20) ON CONFLICT (j) DO UPDATE SET j = EXCLUDED.j + 20; +ROLLBACK; +BEGIN; +-- INSERT j=20, UPDATE j=1 to 20 +INSERT INTO ioc VALUES ('row-2', 20), ('row-3', 1) ON CONFLICT (j) DO UPDATE SET j = 20; +ROLLBACK; +BEGIN; +-- UPDATE j=1 to 20, UPDATE j=20 to 20 +INSERT INTO ioc VALUES ('row-2', 1), ('row-3', 20) ON CONFLICT (j) DO UPDATE SET j = 20; +ROLLBACK; +-- Reset. +DROP TABLE ioc; + +--- UPDATE SET edge case +CREATE TABLE texts (t text PRIMARY KEY); +CREATE FUNCTION agg_texts() RETURNS text AS $$SELECT 'agg=[' || string_agg(t, ',') || ']' FROM texts$$ LANGUAGE sql; +INSERT INTO texts VALUES ('i'), ('j') ON CONFLICT (t) DO UPDATE SET t = agg_texts(); +INSERT INTO texts VALUES ('i'), ('j') ON CONFLICT (t) DO UPDATE SET t = agg_texts(); +SELECT * FROM texts ORDER BY t; + +--- UPDATE SET unmodified primary key +CREATE TABLE table_unmodified_pk (i INT PRIMARY KEY, j INT); +INSERT INTO table_unmodified_pk VALUES (1, 1); +INSERT INTO table_unmodified_pk AS old VALUES (1, 1), (1, 4) ON CONFLICT (i) DO UPDATE SET j = old.j + 1; + +--- UPDATE SET unmodified secondary index +CREATE TABLE table_skip_index (i INT UNIQUE, j INT); +INSERT INTO table_skip_index VALUES (1, 1); +SET yb_enable_inplace_index_update TO true; +INSERT INTO table_skip_index AS old VALUES (1, 1), (1, 4) ON CONFLICT (i) DO UPDATE SET j = old.j + 1; +RESET yb_enable_inplace_index_update; + +--- ON CONFLICT DO UPDATE YbExecDoUpdateIndexTuple +CREATE TABLE index_update (a int PRIMARY KEY, b int); +INSERT INTO index_update VALUES (1, 2); +INSERT INTO index_update VALUES (1, 3) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b; + +--- Before row triggers +CREATE OR REPLACE FUNCTION loggingfunc() RETURNS trigger AS $$ + DECLARE + count int; + BEGIN + SELECT count(*) INTO count FROM pp; + RAISE NOTICE '% % % % i=% count=%', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, new.i, count; + RETURN NEW; + END; +$$ LANGUAGE plpgsql; +-- Trigger on parent table should disable batching for child tables. +CREATE TRIGGER loggingtrig BEFORE INSERT ON pp FOR EACH ROW EXECUTE PROCEDURE loggingfunc(); +BEGIN; +INSERT INTO pp (j) SELECT g * 19 % 40 FROM generate_series(1, 5) g ON CONFLICT DO NOTHING; +SELECT * FROM pp ORDER BY i; +ABORT; + +--- After row triggers +CREATE TABLE trigger_test (i int2, PRIMARY KEY (i ASC)); +-- This test is derived from TestPgUpdatePrimaryKey.java. +CREATE TABLE triggers_fired (name text, fired int, PRIMARY KEY (name)); +CREATE OR REPLACE FUNCTION log_trigger() RETURNS trigger AS $$ + BEGIN + UPDATE triggers_fired SET fired = triggers_fired.fired + 1 WHERE name = TG_NAME; + RETURN NEW; + END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER ai AFTER INSERT ON trigger_test FOR EACH ROW EXECUTE PROCEDURE log_trigger(); +CREATE TRIGGER au AFTER UPDATE ON trigger_test FOR EACH ROW EXECUTE PROCEDURE log_trigger(); +INSERT INTO triggers_fired VALUES ('ai', 0), ('au', 0); +INSERT INTO trigger_test VALUES (1); +INSERT INTO trigger_test VALUES (1), (2), (1), (3) ON CONFLICT DO NOTHING; +TABLE triggers_fired; +TABLE trigger_test; +INSERT INTO trigger_test VALUES (1), (2), (1), (3) ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + 10; +TABLE triggers_fired; +TABLE trigger_test; + +--- Foreign key +CREATE TABLE parent_table (n numeric, t text, b bool, PRIMARY KEY (t, n ASC)); +CREATE TABLE child_table (k numeric, n numeric, t text, CONSTRAINT fk FOREIGN KEY (t, n) REFERENCES parent_table, PRIMARY KEY (k ASC)); +INSERT INTO parent_table VALUES (1, '1', true), (2, '2', true); +INSERT INTO child_table VALUES (0, 1, '1') ON CONFLICT DO NOTHING; +INSERT INTO child_table VALUES (0, 1, '1') ON CONFLICT (k) DO UPDATE SET k = 1; +TABLE child_table; +INSERT INTO child_table VALUES (0, 2, '1') ON CONFLICT (k) DO UPDATE SET k = 1; +INSERT INTO child_table VALUES (1, 2, '1') ON CONFLICT (k) DO UPDATE SET t = '2'; +TABLE child_table; +INSERT INTO parent_table VALUES (2, '2', false), (1, '1', false) ON CONFLICT (t, n) DO UPDATE SET t = EXCLUDED.t || EXCLUDED.t; +INSERT INTO parent_table VALUES (2, '2', false) ON CONFLICT (t, n) DO UPDATE SET t = EXCLUDED.t || EXCLUDED.t; +SELECT * FROM parent_table ORDER BY t, n; +TRUNCATE parent_table CASCADE; +INSERT INTO parent_table (n, t) SELECT g, '' FROM generate_series(0, 5) g; +INSERT INTO child_table (n, t, k) SELECT g, '', g FROM generate_series(0, 4) g; +-- NO ACTION +ALTER TABLE child_table DROP CONSTRAINT fk; +ALTER TABLE child_table ADD CONSTRAINT fk FOREIGN KEY (t, n) REFERENCES parent_table (t, n) ON DELETE NO ACTION ON UPDATE NO ACTION; +BEGIN; +INSERT INTO parent_table (n, t) SELECT g, '' FROM generate_series(0, 5) g + ON CONFLICT (n, t) DO UPDATE SET n = EXCLUDED.n - 1; +TABLE parent_table; +TABLE child_table; +ABORT; +-- RESTRICT +ALTER TABLE child_table DROP CONSTRAINT fk; +ALTER TABLE child_table ADD CONSTRAINT fk FOREIGN KEY (t, n) REFERENCES parent_table (t, n) ON DELETE RESTRICT ON UPDATE RESTRICT; +BEGIN; +INSERT INTO parent_table (n, t) SELECT g, '' FROM generate_series(0, 5) g + ON CONFLICT (n, t) DO UPDATE SET n = EXCLUDED.n - 1; +TABLE parent_table; +TABLE child_table; +ABORT; +-- CASCADE + self-referential NO ACTION +ALTER TABLE child_table DROP CONSTRAINT fk; +ALTER TABLE child_table ADD CONSTRAINT fk FOREIGN KEY (t, n) REFERENCES parent_table (t, n) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE child_table ADD CONSTRAINT u UNIQUE (n); +ALTER TABLE child_table ADD CONSTRAINT self_fk FOREIGN KEY (k) REFERENCES child_table (n) ON DELETE NO ACTION ON UPDATE NO ACTION; +BEGIN; +INSERT INTO parent_table (n, t) SELECT g, '' FROM generate_series(0, 5) g + ON CONFLICT (n, t) DO UPDATE SET n = EXCLUDED.n - 1; +TABLE parent_table; +TABLE child_table; +ABORT; +-- CASCADE + self-referential CASCADE +ALTER TABLE child_table DROP CONSTRAINT self_fk; +ALTER TABLE child_table ADD CONSTRAINT self_fk FOREIGN KEY (k) REFERENCES child_table (n) ON DELETE CASCADE ON UPDATE CASCADE; +BEGIN; +INSERT INTO parent_table (n, t) SELECT g, '' FROM generate_series(0, 5) g + ON CONFLICT (n, t) DO UPDATE SET n = EXCLUDED.n - 1; +TABLE parent_table; +TABLE child_table; +ABORT; +-- CASCADE + no-update/no-delete self-referential +ALTER TABLE child_table DROP CONSTRAINT self_fk; +ALTER TABLE child_table DROP CONSTRAINT u; +ALTER TABLE child_table ADD CONSTRAINT self_fk FOREIGN KEY (n) REFERENCES child_table (k) ON DELETE CASCADE ON UPDATE CASCADE; +BEGIN; +INSERT INTO parent_table (n, t) VALUES (-1, 'other'); +INSERT INTO child_table (n, t, k) VALUES (-1, 'other', -1); +INSERT INTO parent_table (n, t) SELECT g, '' FROM generate_series(0, 5) g + ON CONFLICT (n, t) DO UPDATE SET n = EXCLUDED.n - 1; +TABLE parent_table; +TABLE child_table; +ABORT; + +--- GH-25070 +CREATE TABLE main (a INT, b TEXT, PRIMARY KEY (a, b)); +CREATE TABLE copy (a INT, b TEXT, PRIMARY KEY (b, a)); +INSERT INTO main (SELECT i, 'name_' || i FROM generate_series(1, 10) AS i); +INSERT INTO copy (SELECT a, b FROM main) ON CONFLICT DO NOTHING; +TABLE copy ORDER BY a; +INSERT INTO copy (SELECT a, b FROM main) ON CONFLICT (b, a) DO UPDATE SET b = 'replaced_' || EXCLUDED.b; +TABLE copy ORDER BY a; + +--- GH-25296 +CREATE TABLE dupidx (a int); +CREATE UNIQUE INDEX ON dupidx (a); +CREATE UNIQUE INDEX ON dupidx (a); +INSERT INTO dupidx VALUES (1); +INSERT INTO dupidx VALUES (1) ON CONFLICT DO NOTHING; +TABLE dupidx; +CREATE TABLE multidx (a int, b int); +CREATE UNIQUE INDEX ON multidx (a); +CREATE UNIQUE INDEX ON multidx (b); +INSERT INTO multidx VALUES (1, 2); +INSERT INTO multidx VALUES (1, 2) ON CONFLICT DO NOTHING; +TABLE multidx; + +--- GH-25836 +CREATE TABLE it (i int2 PRIMARY KEY, t text); +WITH w AS ( + INSERT INTO it SELECT g, repeat(g::text, g % 1000) FROM generate_series(1, 5000) g + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING t +) SELECT * FROM w LIMIT 5; +WITH w AS ( + INSERT INTO it SELECT g, repeat(g::text, g % 1000) FROM generate_series(1001, 4000) g + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING t +) SELECT * FROM w LIMIT 5; +SELECT count(*), sign(i) FROM it GROUP BY (sign(i)); diff --git a/src/test/regress/sql/yb.orig.insert_on_conflict_multiplan.sql b/src/test/regress/sql/yb.orig.insert_on_conflict_multiplan.sql new file mode 100644 index 0000000000000..ea350c1cc2a07 --- /dev/null +++ b/src/test/regress/sql/yb.orig.insert_on_conflict_multiplan.sql @@ -0,0 +1,646 @@ +-- +-- INSERT ON CONFLICT read batching with multiple plans involved, typically +-- through triggers or WITH statements. +-- + +-- Setup +CREATE TABLE parttrigtab_parent (i int, p bool, PRIMARY KEY (i, p), CHECK ((i % 2)::bool = p)) PARTITION BY LIST (p); +CREATE TABLE parttrigtab_even PARTITION OF parttrigtab_parent FOR VALUES IN (false); +CREATE TABLE parttrigtab_odd PARTITION OF parttrigtab_parent FOR VALUES IN (true); +INSERT INTO parttrigtab_parent SELECT g, (g % 2)::bool FROM generate_series(1, 3) g; +CREATE TABLE noparttrigtab (i int PRIMARY KEY); +INSERT INTO noparttrigtab VALUES (1), (2), (3); +CREATE OR REPLACE FUNCTION parttrigfunc_createdtrig() RETURNS trigger AS $$ + DECLARE + count int; + BEGIN + SELECT count(*) INTO count FROM parttrigtab_parent; + RAISE NOTICE '% % % % i=% count=%', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, new.i, count; + RETURN NEW; + END; +$$ LANGUAGE plpgsql; + +-- Which comes first: CREATE TRIGGER or loading parttrigtab_even? If +-- parttrigtab_even is loaded first, then it will be loaded thinking there are +-- no triggers; otherwise, it will be loaded with knowledge of the newly +-- created trigger. When batch size is >= 3, parttrigtab_even will be loaded +-- before the first flush (flush of the first value triggers the ON CONFLICT DO +-- UPDATE which causes the WITH statement to execute and cause CREATE TRIGGER), +-- so parttrigtab_even would not know about the newly created trigger. +BEGIN; +CREATE OR REPLACE FUNCTION noparttrigfunc() RETURNS trigger AS $$ + DECLARE + count int; + BEGIN + SELECT count(*) INTO count FROM parttrigtab_parent; + RAISE NOTICE '% % % % i=% count=%', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, new.i, count; + DROP TRIGGER IF EXISTS parttrigtrig_createdtrig ON parttrigtab_even; + CREATE TRIGGER parttrigtrig_createdtrig BEFORE INSERT ON parttrigtab_even FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_createdtrig(); + DROP TRIGGER IF EXISTS parttrigtrig_createdtrig ON parttrigtab_odd; + CREATE TRIGGER parttrigtrig_createdtrig BEFORE INSERT ON parttrigtab_odd FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_createdtrig(); + RETURN NEW; + END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER noparttrigtrig BEFORE INSERT ON noparttrigtab FOR EACH ROW EXECUTE PROCEDURE noparttrigfunc(); +-- nopart triggers: BEFORE INSERT +-- odd triggers: (none) +-- even triggers: (create_trigger creates AFTER ROW trigger) +-- insert order: create_trigger +-- ^ +-- value, value +-- / +-- odd, odd, even, even +WITH w AS ( + INSERT INTO noparttrigtab SELECT g FROM generate_series(1, 2) g ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{1, 3, 2, 4}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i * (SELECT -min(i) FROM w); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- How much of a LIMITed WITH statement is processed on demand and in +-- ExecPostprocessPlan? The | in the diagram below denotes where processing +-- should pause and resume, were batching disabled. With batching enabled, +-- extra values may be processed: +-- batch size 2: no issue +-- batch size 3: one extra value, 2, is consumed. This causes the 2 to be part +-- of the batch read from the table, so it is believed that 2 exists in the +-- table, even though by the time we actually flush this 2 (which is during +-- ExecPostprocessPlan), the existing 2 was ON CONFLICT DO UPDATEd by the main +-- statement. The misinformation causes this 2 to take the ON CONFLICT DO +-- UPDATE path, so the output shows both 2s as taking the ON CONFLICT DO UPDATE +-- path. +-- batch size >=4: the odd value is reached, which causes the parttrigtrig_odd +-- table to get loaded with knowledge that a trigger named parttrigtrig_odd +-- exists. But by the time we get around to executing the insertion of this +-- odd value (which is during ExecPostprocessPlan), we already ran the ALTER +-- TRIGGER from the main statement, so that trigger is no longer named +-- parttrigtrig_odd and therefore runs into an error. Interestingly, TG_NAME +-- is still considered 'parttrigtrig_odd' even though the trigger actually is +-- named 'parttrigtrig_odd2'. +CREATE OR REPLACE FUNCTION parttrigfunc_altertrigonodd() RETURNS trigger AS $$ + DECLARE + count int; + BEGIN + SELECT count(*) INTO count FROM parttrigtab_parent; + RAISE NOTICE '% % % % i=% count=%', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, new.i, count; + IF TG_NAME = 'parttrigtrig_odd' THEN + ALTER TRIGGER parttrigtrig_odd ON parttrigtab_odd RENAME TO parttrigtrig_odd2; + END IF; + RETURN NEW; + END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER parttrigtrig_odd BEFORE INSERT ON parttrigtab_odd FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_altertrigonodd(); +BEGIN; +-- odd triggers: BEFORE INSERT (alter_trigger alters it) +-- even triggers: (none) +-- insert order: even, even | even, odd, even +-- / +-- / alter_trigger +-- / ^ +-- even, even, even, even, odd +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{44, 66, 2, 1111, 88}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 6, 1}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) - 66 FROM (SELECT i FROM w LIMIT 2) l) + 1000; +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; +DROP TRIGGER parttrigtrig_odd ON parttrigtab_odd; + +-- More LIMITed WITH statement getting processed on demand when reading tuples +-- from input and in ON CONFLICT DO UPDATE, and in ExecPostprocessPlan. +-- batch size 2: no issue +-- batch size 3: one extra value, 2, is consumed. This causes the 2 to be part +-- of the batch read from the table, so it is believed that 2 exists in the +-- table, even though by the time we actually flush this 2 (which is during g=6 +-- input), the existing 2 was ON CONFLICT DO UPDATEd by the main statement. +-- The misinformation causes this 2 to take the ON CONFLICT DO UPDATE path, so +-- the output shows both 2s as taking the ON CONFLICT DO UPDATE path. +-- batch size >=4: g=6 value is reached, which causes the WITH statement's 2 to +-- take the ON CONFLICT DO UPDATE path and the main statement's 2 to get +-- inserted normally. +BEGIN; +-- odd triggers: (none) +-- even triggers: (none) +-- insert order: odd | even | even, odd | even, odd | odd +-- \ / \ \ +-- \/ \ \ +-- even, even, even, even, even, even +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{1, 100, 2, 3, 6, 5, 7}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = -EXCLUDED.i RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT n, (n % 2)::bool FROM (SELECT CASE + WHEN g = 2 THEN (SELECT i + 3 FROM w LIMIT 1) -- 2 + WHEN g = 6 THEN (SELECT min(abs(i)) FROM (SELECT i FROM w LIMIT 4) l) -- 1 + WHEN g = 10 THEN -(SELECT min(i) FROM (SELECT i FROM w LIMIT 6) l) -- 3 + ELSE g + END AS n FROM generate_series(0, 10, 2) g) ns ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) FROM (SELECT i FROM w LIMIT 2) l); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- This trigger is set for the remaining test cases. +CREATE OR REPLACE FUNCTION parttrigfunc_insert8() RETURNS trigger AS $$ + DECLARE + count int; + BEGIN + SELECT count(*) INTO count FROM parttrigtab_parent; + RAISE NOTICE '% % % % i=% count=%', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, new.i, count; + SELECT count(*) INTO count FROM parttrigtab_parent WHERE i = 8; + IF count = 0 THEN + INSERT INTO parttrigtab_parent VALUES (8, false); + ELSE + UPDATE parttrigtab_parent SET i = i + 100 WHERE i = 8; + END IF; + RETURN NEW; + END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER parttrigtrig_insert8 BEFORE INSERT ON parttrigtab_odd FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_insert8(); + +-- Once an odd value is encountered, batching should pause at that point and +-- flush whatever was accumulated up to that point before resuming. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- even, even, even, even, even, odd +INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 6, 8, 1}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10; +SELECT * FROM parttrigtab_parent ORDER BY (i % 10); +ABORT; + +-- Once an odd value is encountered, batching should pause at that point and +-- flush whatever was accumulated up to that point before resuming + WITH +-- statement. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- even, even, even, even, odd +-- / +-- / even +-- / ^ +-- even, even, even, even, odd +-- Note: for the following query, upstream PG 15.2 gives 11 rows +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{2, 44, 66, 88, 1111}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 6, 1}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) - 111 FROM w); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- Once an odd value is encountered, batching should pause at that point and +-- flush whatever was accumulated up to that point before resuming + LIMITed +-- WITH statement. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- even, even | even, even, odd +-- / +-- / even +-- / ^ +-- even, even, even, even, odd +-- Note: for the following query, upstream PG 15.2 gives 11 rows +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{2, 44, 66, 88, 1111}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 6, 1}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) - 44 FROM (SELECT i FROM w LIMIT 2) l) + 1000; +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- Once an odd value is encountered, batching should pause at that point and +-- flush whatever was accumulated up to that point before resuming + +-- unreferenced WITH statement. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- | even, even, even, even, odd +-- +-- even +-- ^ +-- even, even, even, even, odd +-- Note: for the following query, upstream PG 15.2 gives 11 rows +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{2, 44, 66, 88, 1111}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 22, 4, 6, 11}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) / 0 FROM w); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- Value that has both BEFORE ROW trigger and triggers WITH statement through +-- ON CONFLICT DO UPDATE. If not for global tracking of inserted keys, this +-- could give a different result with the below. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- even, even, even, even, odd +-- / +-- even / +-- ^ / +-- even, odd, even, even, even +-- Note: for the following query, upstream PG 15.2 gives ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{2, 44, 66, 8, 1111}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 1, 4, 6, 2}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) - 111 FROM w); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- Value that has both BEFORE ROW trigger and triggers WITH statement through +-- ON CONFLICT DO UPDATE. If not for global tracking of inserted keys, this +-- could give a different result with the above. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- even, even, even, odd, even +-- / +-- even / +-- ^ / +-- even, odd, even, even, even +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{2, 44, 66, 1111, 8}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 1, 4, 6, 2}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) - 111 FROM w); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- Update the trigger function to use ON CONFLICT, then run the same test cases. +CREATE OR REPLACE FUNCTION parttrigfunc_insert8() RETURNS trigger AS $$ + DECLARE + count int; + BEGIN + SELECT count(*) INTO count FROM parttrigtab_parent; + RAISE NOTICE '% % % % i=% count=%', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, new.i, count; + INSERT INTO parttrigtab_parent VALUES (8, false) ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 100; + RETURN NEW; + END; +$$ LANGUAGE plpgsql; + +-- Once an odd value is encountered, batching should pause at that point and +-- flush whatever was accumulated up to that point before resuming. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- even, even, even, even, even, odd +INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 6, 8, 1}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10; +SELECT * FROM parttrigtab_parent ORDER BY (i % 10); +ABORT; + +-- Once an odd value is encountered, batching should pause at that point and +-- flush whatever was accumulated up to that point before resuming + WITH +-- statement. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- even, even, even, even, odd +-- / +-- / even +-- / ^ +-- even, even, even, even, odd +-- Note: for the following query, upstream PG 15.2 gives 11 rows +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{2, 44, 66, 88, 1111}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 6, 1}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) - 111 FROM w); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- Once an odd value is encountered, batching should pause at that point and +-- flush whatever was accumulated up to that point before resuming + LIMITed +-- WITH statement. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- even, even | even, even, odd +-- / +-- / even +-- / ^ +-- even, even, even, even, odd +-- Note: for the following query, upstream PG 15.2 gives 11 rows +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{2, 44, 66, 88, 1111}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 6, 1}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) - 44 FROM (SELECT i FROM w LIMIT 2) l) + 1000; +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- Once an odd value is encountered, batching should pause at that point and +-- flush whatever was accumulated up to that point before resuming + +-- unreferenced WITH statement. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- | even, even, even, even, odd +-- +-- even +-- ^ +-- even, even, even, even, odd +-- Note: for the following query, upstream PG 15.2 gives 11 rows +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{2, 44, 66, 88, 1111}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 22, 4, 6, 11}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) / 0 FROM w); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- Value that has both BEFORE ROW trigger and triggers WITH statement through +-- ON CONFLICT DO UPDATE. If not for global tracking of inserted keys, this +-- could give a different result with the below. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- even, even, even, even, odd +-- / +-- even / +-- ^ / +-- even, odd, even, even, even +-- Note: for the following query, upstream PG 15.2 gives ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{2, 44, 66, 8, 1111}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 1, 4, 6, 2}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) - 111 FROM w); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- Value that has both BEFORE ROW trigger and triggers WITH statement through +-- ON CONFLICT DO UPDATE. If not for global tracking of inserted keys, this +-- could give a different result with the above. +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: (none) +-- insert order: even +-- ^ +-- even, even, even, odd, even +-- / +-- even / +-- ^ / +-- even, odd, even, even, even +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{2, 44, 66, 1111, 8}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 1, 4, 6, 2}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) - 111 FROM w); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- CREATE TRIGGER is visible to the parttrigtab_even table opened in the +-- following trigger execution only because that opens the relation fresh. +CREATE OR REPLACE FUNCTION parttrigfunc_createtrigoneven() RETURNS trigger AS $$ + DECLARE + count int; + BEGIN + SELECT count(*) INTO count FROM parttrigtab_parent; + RAISE NOTICE '% % % % i=% count=%', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, new.i, count; + DROP TRIGGER IF EXISTS parttrigtrig_createdtrig ON parttrigtab_even; + CREATE TRIGGER parttrigtrig_createdtrig BEFORE INSERT ON parttrigtab_even FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_createdtrig(); + RETURN NEW; + END; +$$ LANGUAGE plpgsql; +BEGIN; +CREATE TRIGGER parttrigtrig_createtrigoneven AFTER INSERT ON parttrigtab_even FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_createtrigoneven(); +-- odd triggers: BEFORE INSERT +-- even triggers: AFTER INSERT, (create_trigger creates BEFORE ROW trigger) +-- insert order: even +-- ^ v +-- ^ create_trigger +-- ^ +-- ^ notice +-- ^ ^ +-- ^ even +-- ^ ^ +-- even, even, even, odd, even, odd +INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 1, 6, 3}'::int[]) u ON CONFLICT DO NOTHING; +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- CREATE TRIGGER is visible to the parttrigtab_even table opened in the +-- following trigger execution only because that opens the relation fresh. +BEGIN; +CREATE TRIGGER parttrigtrig_createtrigoneven AFTER INSERT ON parttrigtab_even FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_createtrigoneven(); +-- odd triggers: BEFORE INSERT +-- even triggers: AFTER INSERT, (create_trigger creates BEFORE ROW trigger) +-- insert order: even +-- ^ v +-- ^ create_trigger +-- ^ +-- even, even, even, odd +-- / +-- / notice +-- / ^ +-- / even +-- / ^ +-- even, even, even, odd, even, odd +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{44, 66, 88, 1111}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 1, 6, 3}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) - 111 FROM w); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- CREATE TRIGGER is visible to the parttrigtab_even table opened in the three +-- places that freshly open that table. +BEGIN; +CREATE TRIGGER parttrigtrig_createtrigoneven AFTER INSERT ON parttrigtab_even FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_createtrigoneven(); +-- odd triggers: BEFORE INSERT +-- even triggers: AFTER INSERT, (create_trigger creates BEFORE ROW trigger) +-- insert order: even +-- ^ v +-- ^ create_trigger +-- ^ +-- ^ even +-- ^ ^ +-- ^ ^ even, even, even +-- ^ ^ / +-- ^ ^ / even +-- ^ ^/ ^ +-- odd, odd, even, odd +-- / +-- even, even, even, even +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{44, 66, -1000}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +), w2 AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{5, 1, 1000, 3}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT min(i) FROM w) RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 6}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) FROM w2); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- CREATE TRIGGER is visible to the parttrigtab_even table opened in the three +-- places that freshly open that table. +BEGIN; +CREATE TRIGGER parttrigtrig_createtrigoneven AFTER INSERT ON parttrigtab_even FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_createtrigoneven(); +-- odd triggers: BEFORE INSERT +-- even triggers: AFTER INSERT, (create_trigger creates BEFORE ROW trigger) +-- even +-- ^ v +-- ^ create_trigger +-- ^ +-- ^ even, even, even +-- ^ / +-- ^ / even +-- ^/ ^ +-- even, odd, even, odd +-- / +-- / even +-- / ^ +-- even, even, even, odd +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{44, 66, -1000}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +), w2 AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{6, 1, 1000, 3}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT min(i) FROM w) RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 5}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) FROM w2); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- CREATE TRIGGER is visible to the parttrigtab_even table opened in the two +-- places that freshly open that table. +CREATE OR REPLACE FUNCTION parttrigfunc_createtrigonodd() RETURNS trigger AS $$ + DECLARE + count int; + BEGIN + SELECT count(*) INTO count FROM parttrigtab_parent; + RAISE NOTICE '% % % % i=% count=%', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, new.i, count; + DROP TRIGGER IF EXISTS parttrigtrig_createdtrig ON parttrigtab_odd; + CREATE TRIGGER parttrigtrig_createdtrig AFTER INSERT ON parttrigtab_odd FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_createdtrig(); + RETURN NEW; + END; +$$ LANGUAGE plpgsql; +BEGIN; +CREATE TRIGGER parttrigtrig_createtrigonodd AFTER INSERT ON parttrigtab_even FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_createtrigonodd(); +-- odd triggers: BEFORE INSERT, (create_trigger creates AFTER ROW trigger) +-- even triggers: AFTER INSERT +-- insert order: even +-- ^ v +-- ^ create_trigger +-- ^ +-- even, even, even, odd +-- / +-- / even even +-- / ^ ^ +-- even, even, even, odd, even, odd +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{44, 66, 88, 1111}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 1, 6, 3}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) - 111 FROM w); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- DROP TRIGGER is visible to the parttrigtab_even table opened in the two +-- places that freshly open that table. +CREATE OR REPLACE FUNCTION parttrigfunc_dropself() RETURNS trigger AS $$ + BEGIN + RAISE NOTICE 'placeholder'; + RETURN NEW; + END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER parttrigtrig_dropself BEFORE INSERT ON parttrigtab_even FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_dropself(); +CREATE OR REPLACE FUNCTION parttrigfunc_dropself() RETURNS trigger AS $$ + DECLARE + count int; + BEGIN + SELECT count(*) INTO count FROM parttrigtab_parent; + RAISE NOTICE '% % % % i=% count=%', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, new.i, count; + DROP TRIGGER IF EXISTS parttrigtrig_dropself ON parttrigtab_even; + RETURN NEW; + END; +$$ LANGUAGE plpgsql; +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: BEFORE INSERT (drop_trigger deletes it) +-- insert order: drop_trigger even even +-- ^ ^ ^ +-- even, even, even, odd, even, odd +INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 4, 1, 2, 6, 3}'::int[]) u ON CONFLICT DO NOTHING; +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; +DROP TRIGGER IF EXISTS parttrigtrig_dropself ON parttrigtab_even; + +-- DROP TRIGGER is visible to the parttrigtab_even table opened in the two +-- places that freshly open that table. +CREATE TRIGGER parttrigtrig_dropself BEFORE INSERT ON parttrigtab_even FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_dropself(); +BEGIN; +-- odd triggers: BEFORE INSERT +-- even triggers: BEFORE INSERT (drop_trigger deletes it) +-- insert order: drop_trigger +-- ^ +-- even even +-- ^ ^ +-- odd, even, even, even, even, odd +INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{1, 2, 4, 0, 6, 3}'::int[]) u ON CONFLICT DO NOTHING; +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; +DROP TRIGGER IF EXISTS parttrigtrig_dropself ON parttrigtab_even; + +-- CREATE TRIGGER is visible to the parttrigtab_even table opened in the three +-- places that freshly open that table. +BEGIN; +CREATE OR REPLACE FUNCTION noparttrigfunc() RETURNS trigger AS $$ + DECLARE + count int; + BEGIN + SELECT count(*) INTO count FROM parttrigtab_parent; + RAISE NOTICE '% % % % i=% count=%', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, new.i, count; + DROP TRIGGER IF EXISTS parttrigtrig_createdtrig ON parttrigtab_even; + CREATE TRIGGER parttrigtrig_createdtrig AFTER INSERT ON parttrigtab_even FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_createdtrig(); + RETURN NEW; + END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER noparttrigtrig BEFORE INSERT ON noparttrigtab FOR EACH ROW EXECUTE PROCEDURE noparttrigfunc(); +-- nopart triggers: BEFORE INSERT +-- odd triggers: BEFORE INSERT +-- even triggers: (create_trigger creates AFTER ROW trigger) +-- insert order: create_trigger even +-- ^ ^ +-- ^ even, even, odd +-- ^ / +-- ^ / +-- ^/ +-- value, value, value, value +-- / +-- / even +-- / ^ +-- even, even, even, odd +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{44, -1000, 3}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +), w2 AS ( + INSERT INTO noparttrigtab SELECT g FROM generate_series(1, 4) g ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + (SELECT min(i) FROM w) RETURNING i +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 1}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) + 996 FROM w2); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; + +-- CREATE TRIGGER is visible to the parttrigtab_even table opened in the three +-- places that freshly open that table. +BEGIN; +CREATE OR REPLACE FUNCTION noparttrigfunc() RETURNS trigger AS $$ + DECLARE + count int; + BEGIN + SELECT count(*) INTO count FROM parttrigtab_parent; + RAISE NOTICE '% % % % i=% count=%', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, new.i, count; + DROP TRIGGER IF EXISTS parttrigtrig_createdtrig ON parttrigtab_even; + CREATE TRIGGER parttrigtrig_createdtrig BEFORE INSERT ON parttrigtab_even FOR EACH ROW EXECUTE PROCEDURE parttrigfunc_createdtrig(); + RETURN NEW; + END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER noparttrigtrig BEFORE INSERT ON noparttrigtab FOR EACH ROW EXECUTE PROCEDURE noparttrigfunc(); +-- nopart triggers: BEFORE INSERT +-- odd triggers: BEFORE INSERT +-- even triggers: (create_trigger creates BEFORE ROW trigger) +-- insert order: create_trigger even +-- ^ ^ +-- ^ even, even, odd +-- ^ / +-- ^ / +-- ^/ +-- value, value, value, value +-- / +-- / even +-- / ^ +-- even, even, even, odd +WITH w AS ( + INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{44, -1000, 3}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + 10 RETURNING i, p +), w2 AS ( + INSERT INTO noparttrigtab SELECT g FROM generate_series(1, 4) g ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + (SELECT min(i) FROM w) RETURNING i +) INSERT INTO parttrigtab_parent SELECT u, (u % 2)::bool FROM unnest('{0, 2, 4, 1}'::int[]) u ON CONFLICT (i, p) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) + 996 FROM w2); +SELECT * FROM parttrigtab_parent ORDER BY i; +ABORT; diff --git a/src/test/regress/sql/yb.orig.insert_on_conflict_with.sql b/src/test/regress/sql/yb.orig.insert_on_conflict_with.sql new file mode 100644 index 0000000000000..cbf1b5ea2cace --- /dev/null +++ b/src/test/regress/sql/yb.orig.insert_on_conflict_with.sql @@ -0,0 +1,62 @@ +CREATE TABLE with_a (i int, PRIMARY KEY (i DESC)); +CREATE TABLE with_b (i int, PRIMARY KEY (i ASC)); +INSERT INTO with_a VALUES (generate_series(1, 10)); +INSERT INTO with_b VALUES (generate_series(11, 20)); + +BEGIN; +WITH w(i) AS ( + INSERT INTO with_a VALUES (generate_series(1, 11)) ON CONFLICT (i) DO NOTHING RETURNING i +) INSERT INTO with_b VALUES (generate_series(1, 15)) ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + (SELECT i FROM w); +TABLE with_a; +TABLE with_b; +ABORT; + +BEGIN; +WITH w(i) AS ( + INSERT INTO with_a VALUES (generate_series(1, 11)) ON CONFLICT (i) DO NOTHING RETURNING i +) INSERT INTO with_a VALUES (generate_series(1, 15)) ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + (SELECT i FROM w); +TABLE with_a; +ABORT; + +BEGIN; +WITH w(i) AS ( + INSERT INTO with_a VALUES (generate_series(6, 11)) ON CONFLICT (i) DO NOTHING RETURNING i +) INSERT INTO with_a VALUES (generate_series(10, 15)) ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + (SELECT i FROM w); +TABLE with_a; +ABORT; + +BEGIN; +WITH w(i) AS ( + INSERT INTO with_a VALUES (11) RETURNING i +) INSERT INTO with_a VALUES (generate_series(10, 15)) ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + (SELECT i FROM w); +TABLE with_a; +ABORT; + +BEGIN; +WITH w(i) AS ( + DELETE FROM with_a WHERE i = 10 RETURNING i +) INSERT INTO with_a VALUES (generate_series(9, 15)) ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + (SELECT i FROM w); +TABLE with_a; +ABORT; + +BEGIN; +WITH w(i) AS ( + INSERT INTO with_a VALUES (generate_series(6, 11)) ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + 100 RETURNING i +) INSERT INTO with_a SELECT CASE + WHEN u < 12 THEN u + WHEN u < 14 THEN -(u + (SELECT max(i) FROM w)) + ELSE u + END FROM generate_series(9, 15) u ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + 10; +TABLE with_a; +ABORT; + +BEGIN; +WITH w(i) AS ( + INSERT INTO with_a VALUES (generate_series(6, 10)) ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + 100 RETURNING i +) INSERT INTO with_a SELECT CASE + WHEN u < 11 THEN u + WHEN u < 13 THEN -(u + (SELECT max(i) FROM w)) + ELSE u + END FROM generate_series(10, 15) u ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + 10; +TABLE with_a; +ABORT; diff --git a/src/test/regress/sql/yb.orig.insert_on_conflict_with_explain.sql b/src/test/regress/sql/yb.orig.insert_on_conflict_with_explain.sql new file mode 100644 index 0000000000000..1566e961cc3c7 --- /dev/null +++ b/src/test/regress/sql/yb.orig.insert_on_conflict_with_explain.sql @@ -0,0 +1,58 @@ +-- This file contains tests that use EXPLAIN in conjunction with INSERT ... ON CONFLICT. +-- The mapping between this test's output and outfiles is as follows: +-- > yb.orig.insert_on_conflict_with_explain.out: INSERT ... ON CONFLICT read batching disabled. +-- > yb.orig.insert_on_conflict_with_explain_1.out: INSERT ... ON CONFLICT read batching enabled, batch size = 1. +-- > yb.orig.insert_on_conflict_with_explain_2.out: INSERT ... ON CONFLICT read batching enabled, batch size = 2. +-- > yb.orig.insert_on_conflict_with_explain_3.out: INSERT ... ON CONFLICT read batching enabled, batch size > 2. + +--- GH-28234: IndexOnlyScan support for INSERT ... ON CONFLICT ... DO NOTHING +CREATE TABLE t_ios (k INT PRIMARY KEY, v1 INT, v2 INT); +CREATE UNIQUE INDEX NONCONCURRENTLY t_ios_v1 ON t_ios (v1); +INSERT INTO t_ios (k, v1, v2) (SELECT i, 10 - i, i FROM generate_series(1, 10) AS i); + +-- No conflict with either index. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (11, 11, 11), (12, 12, 12) ON CONFLICT DO NOTHING; +-- Conflict with exactly one index. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (1, 13, 100), (2, 14, 101) ON CONFLICT DO NOTHING; +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (1, 13, 100), (2, 14, 101) ON CONFLICT (k) DO NOTHING; +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (1, 13, 100), (2, 14, 101) ON CONFLICT (v1) DO NOTHING; +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (13, 1, 100), (14, 2, 101) ON CONFLICT DO NOTHING; +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (13, 1, 100), (14, 2, 101) ON CONFLICT (v1) DO NOTHING; +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (13, 1, 100), (14, 2, 101) ON CONFLICT (k) DO NOTHING; +-- Conflict with different rows in both indexes. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (1, 1, 100), (2, 2, 101) ON CONFLICT DO NOTHING; + +DROP INDEX t_ios_v1; +CREATE UNIQUE INDEX NONCONCURRENTLY t_ios_v1_v2 ON t_ios (v1, v2); + +-- The IndexOnlyScan should also work with multi-column indexes. +-- No conflict with multi-column index. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (100, 5, 100), (101, 100, 5) ON CONFLICT (v1, v2) DO NOTHING; +-- Conflict with multi-column index. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (102, 3, 7) ON CONFLICT (v1, v2) DO NOTHING; +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (102, 3, 7) ON CONFLICT DO NOTHING; +-- Conflict with non-arbiter index +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (1, 1, 100) ON CONFLICT (v1, v2) DO NOTHING; +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios VALUES (1, 1, 100) ON CONFLICT DO NOTHING; + +SELECT * FROM t_ios ORDER BY k; + +-- IndexOnlyScan for INSERT ... ON CONFLICT ... DO NOTHING should also work +-- with partitioned tables. +CREATE TABLE t_ios_base (k INT PRIMARY KEY, v1 INT) PARTITION BY RANGE (k); +CREATE TABLE t_ios_p1 PARTITION OF t_ios_base FOR VALUES FROM (0) TO (10); +CREATE TABLE t_ios_p2 PARTITION OF t_ios_base FOR VALUES FROM (10) TO (20); + +INSERT INTO t_ios_base VALUES (1, 1), (2, 2), (11, 11), (12, 12); + +CREATE UNIQUE INDEX idx_ios_p1_v1 ON t_ios_p1 (k, v1); +-- Query should error out when not all partitions have an arbiter index. +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios_base VALUES (1, 1), (11, 11) ON CONFLICT (k, v1) DO NOTHING; +-- Query should also error out when the indexes on the partition are not part of the same heirarchy. +CREATE UNIQUE INDEX idx_ios_p2_v1 ON t_ios_p2 (k, v1); +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios_base VALUES (1, 1), (11, 11) ON CONFLICT (k, v1) DO NOTHING; +DROP INDEX idx_ios_p1_v1; +DROP INDEX idx_ios_p2_v1; +-- IndexOnlyScan should be performed when all partitions share an arbiter index that is part of the same heirarchy. +CREATE UNIQUE INDEX idx_ios_base_v1 ON t_ios_base (k, v1); +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO t_ios_base VALUES (1, 1), (11, 11) ON CONFLICT (k, v1) DO NOTHING; diff --git a/src/test/regress/sql/yb.orig.insert_on_conflict_with_spi.sql b/src/test/regress/sql/yb.orig.insert_on_conflict_with_spi.sql new file mode 100644 index 0000000000000..403bd2d7addbe --- /dev/null +++ b/src/test/regress/sql/yb.orig.insert_on_conflict_with_spi.sql @@ -0,0 +1,153 @@ +-- +-- Depends on yb.orig.insert_on_conflict_with test. +-- + +BEGIN; +-- With batch size >= 3, get cannot affect row a second time between the two 2s. +-- insert order: | x, x +-- +-- x | x | x | x | x, x +-- \ / \ \ +-- \/ \ \ +-- x, x, x, x, x +WITH w1 AS ( + INSERT INTO with_a VALUES + (44), + (45) + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i +), w2 AS ( + INSERT INTO with_a VALUES + (1), + (100), + (2), + (5), + (25), + (3) + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i +) INSERT INTO with_a VALUES + (0), + ((SELECT i + 3 FROM w2 LIMIT 1)), -- 2 + ((SELECT abs(min(i)) FROM (SELECT i FROM w2 LIMIT 3) l)), -- 1 + (4), + ((SELECT abs(min(i)) FROM (SELECT i FROM w2 LIMIT 4) l)) -- 5 +ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) FROM (SELECT i FROM w2 LIMIT 2) l) RETURNING i; +TABLE with_a; +ABORT; + +-- Same thing with yb_run_spi. +BEGIN; +-- insert order: | x, x +-- +-- x | x | x | x, x, x +-- \ / \ +-- \/ \ +-- x, x, x +SELECT yb_run_spi($$ +WITH w1 AS ( + INSERT INTO with_a VALUES + (44), + (45) + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i +), w2 AS ( + INSERT INTO with_a VALUES + (1), + (100), + (2), + (5), + (25), + (3) + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i +) INSERT INTO with_a VALUES + (0), + ((SELECT i + 3 FROM w2 LIMIT 1)), -- 2 + ((SELECT abs(min(i)) FROM (SELECT i FROM w2 LIMIT 3) l)), -- 1 + (4), -- unreachable + ((SELECT abs(min(i)) FROM (SELECT i FROM w2 LIMIT 4) l)) -- unreachable +ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) FROM (SELECT i FROM w2 LIMIT 2) l) RETURNING i; +$$, 3); +TABLE with_a; +ABORT; + +-- More yb_run_spi. +BEGIN; +-- insert order: x, x, x +-- | x, x, x +-- x | x | x | x, x, x +-- \ / \ +-- \/ \ +-- | x, x, x +SELECT yb_run_spi($$ +WITH w1 AS ( + SELECT yb_run_spi($q1$ + INSERT INTO with_a VALUES + (44), -- unreachable + (45) -- unreachable + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i + $q1$, 1) +), w2 AS ( + INSERT INTO with_a VALUES + (1), + (100), + (2), + (5), + (25), + ((SELECT yb_run_spi($q2$ + INSERT INTO with_a VALUES (50), (51), (52) + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i; + $q2$, 5))) -- 3 + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i +) INSERT INTO with_a VALUES + (0), + ((SELECT i + 3 FROM w2 LIMIT 1)), -- 2 + ((SELECT abs(min(i)) FROM (SELECT i FROM w2 LIMIT 3) l)), -- 1 + (4), -- unreachable + ((SELECT abs(min(i)) FROM (SELECT i FROM w2 LIMIT 4) l)), -- unreachable + ((SELECT yb_run_spi($q3$ + INSERT INTO with_a VALUES (50), (51), (52) + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i; + $q3$, 5))) -- unreachable +ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) FROM (SELECT i FROM w2 LIMIT 2) l) RETURNING i; +$$, 3); +TABLE with_a; +ABORT; + +-- Same thing without outer yb_run_spi. +BEGIN; +-- insert order: x, x, x +-- | x, x, x +-- x | x | x | x | x, x +-- \ / \ \ +-- \/ \ \ +-- | x, x, x, x, x, x +WITH w1 AS ( + SELECT yb_run_spi($q1$ + INSERT INTO with_a VALUES + (44), -- unreachable + (45) -- unreachable + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i + $q1$, 1) +), w2 AS ( + INSERT INTO with_a VALUES + (1), + (100), + (2), + (5), + (25), + ((SELECT yb_run_spi($q2$ + INSERT INTO with_a VALUES (50), (51), (52) + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i; + $q2$, 5))) -- 3 + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i +) INSERT INTO with_a VALUES + (0), + ((SELECT i + 3 FROM w2 LIMIT 1)), -- 2 + ((SELECT abs(min(i)) FROM (SELECT i FROM w2 LIMIT 3) l)), -- 1 + (4), + ((SELECT abs(min(i)) FROM (SELECT i FROM w2 LIMIT 4) l)), -- 5 + ((SELECT yb_run_spi($q3$ + INSERT INTO with_a VALUES (50), (51), (52) + ON CONFLICT (i) DO UPDATE SET i = -EXCLUDED.i RETURNING i; + $q3$, 5))) -- 3 +ON CONFLICT (i) DO UPDATE SET i = EXCLUDED.i + (SELECT max(i) FROM (SELECT i FROM w2 LIMIT 2) l) RETURNING i; +TABLE with_a; +ABORT; diff --git a/src/test/regress/sql/yb.orig.int2_array.sql b/src/test/regress/sql/yb.orig.int2_array.sql new file mode 100644 index 0000000000000..499836c2a8b8e --- /dev/null +++ b/src/test/regress/sql/yb.orig.int2_array.sql @@ -0,0 +1,24 @@ +create table int2_array_test ( + k int primary key, + smallint_arr smallint[], + int2_arr int2[] + ); + +insert into int2_array_test (k, smallint_arr, int2_arr) values ( + 1, '{32767,0,-32768}','{-32768,0,32767}'); + +select * from int2_array_test; + +-- Invalid values for smallint +insert into int2_array_test (k, smallint_arr, int2_arr) values (2, '{-32769}', NULL); +insert into int2_array_test (k, smallint_arr, int2_arr) values (2, '{32768}', NULL); +insert into int2_array_test (k, smallint_arr, int2_arr) values (2, NULL, '{-32769}'); +insert into int2_array_test (k, smallint_arr, int2_arr) values (2, NULL, '{32768}'); + +insert into int2_array_test (k, smallint_arr, int2_arr) values ( + 10, '{-10000,1,2,3,10000}', NULL); +insert into int2_array_test (k, smallint_arr, int2_arr) values ( + 11, NULL, '{-10000,1,2,3,10000}'); +select * from int2_array_test order by k; + +drop table int2_array_test; \ No newline at end of file diff --git a/src/test/regress/sql/yb.orig.int4_array.sql b/src/test/regress/sql/yb.orig.int4_array.sql new file mode 100644 index 0000000000000..1aa9c195be63f --- /dev/null +++ b/src/test/regress/sql/yb.orig.int4_array.sql @@ -0,0 +1,24 @@ +create table int4_array_test ( + k int primary key, + int_arr int[], + int4_arr int4[] + ); + +insert into int4_array_test (k, int_arr, int4_arr) values ( + 1, '{2147483647,0,-2147483648}','{-2147483648,0,2147483647}'); + +select * from int4_array_test; + + -- Invalid values for int +insert into int4_array_test (k, int_arr, int4_arr) values (2, '{-2147483649}', NULL); +insert into int4_array_test (k, int_arr, int4_arr) values (2, '{2147483648}', NULL); +insert into int4_array_test (k, int_arr, int4_arr) values (2, NULL, '{-2147483649}'); +insert into int4_array_test (k, int_arr, int4_arr) values (2, NULL, '{2147483648}'); + +insert into int4_array_test (k, int_arr, int4_arr) values ( + 10, '{-1000000000,1,2,3,1000000000}', NULL); +insert into int4_array_test (k, int_arr, int4_arr) values ( + 11, NULL, '{-1000000000,1,2,3,1000000000}'); +select * from int4_array_test order by k; + +drop table int4_array_test; \ No newline at end of file diff --git a/src/test/regress/sql/yb.orig.internal_subtransactions.sql b/src/test/regress/sql/yb.orig.internal_subtransactions.sql new file mode 100644 index 0000000000000..d8d4e23dce255 --- /dev/null +++ b/src/test/regress/sql/yb.orig.internal_subtransactions.sql @@ -0,0 +1,170 @@ +-- +-- Internal Subtransactions & Procedure Exception Handling +-- +-- An error caught by the exception handler rolls back the changes made in the +-- block body but not anything else. + + +-- Test abort subtransaction upon error. Execute SELECT statement after +-- each INSERT to avoid influence from operation buffering. + +CREATE TABLE subtrans_foo (k int PRIMARY KEY, v int UNIQUE); + +DO $body$ +DECLARE + row record; +BEGIN + INSERT INTO subtrans_foo (k, v) VALUES (1, 42); + SELECT * INTO row FROM subtrans_foo; + BEGIN + INSERT INTO subtrans_foo (k, v) VALUES (2, 42); + SELECT * INTO row FROM subtrans_foo; + EXCEPTION + WHEN unique_violation THEN + RAISE NOTICE 'unique violation 1'; + END; + + BEGIN + INSERT INTO subtrans_foo (k, v) VALUES (3, 42); + SELECT * INTO row FROM subtrans_foo; + EXCEPTION + WHEN unique_violation THEN + RAISE NOTICE 'unique violation 2'; + END; +END; +$body$; + +-- Full table scan +SELECT /*+ SeqScan(t) */ * FROM subtrans_foo t WHERE k > 0 AND v > 0 ORDER BY k; +EXPLAIN (COSTS OFF) +SELECT /*+ SeqScan(t) */ * FROM subtrans_foo t WHERE k > 0 AND v > 0 ORDER BY k; + +-- PK index scan +SELECT /*+ IndexScan(t subtrans_foo_pkey) */ * FROM subtrans_foo t WHERE k > 0 AND v > 0 ORDER BY k; +EXPLAIN (COSTS OFF) +SELECT /*+ IndexScan(t subtrans_foo_pkey) */ * FROM subtrans_foo t WHERE k > 0 AND v > 0 ORDER BY k; + +-- Index scan using the secondary index that triggered the unique violation +SELECT /*+ IndexScan(t subtrans_foo_v_key) */ * FROM subtrans_foo t WHERE k > 0 AND v > 0 ORDER BY k; +EXPLAIN (COSTS OFF) +SELECT /*+ IndexScan(t subtrans_foo_v_key) */ * FROM subtrans_foo t WHERE k > 0 AND v > 0 ORDER BY k; + + +-- Test that each buffering eligible operations are executed in the intended +-- procedure block section. + +CREATE TABLE subtrans_test (k int PRIMARY KEY, v text NOT NULL); + +-- Single block +INSERT INTO subtrans_test (k, v) VALUES (1, 'dog'); +DO $body$ +BEGIN + INSERT INTO subtrans_test (k, v) VALUES (2, 'cat'); + INSERT INTO subtrans_test (k, v) VALUES (1, 'frog'); -- error + INSERT INTO subtrans_test (k, v) VALUES (3, 'bull'); +EXCEPTION WHEN unique_violation THEN + RAISE NOTICE 'caught unique violation'; +END; +$body$; +SELECT * FROM subtrans_test ORDER BY 1; + + +-- Nested block combinations + +-- Erroneous statement then error-free inner block +TRUNCATE TABLE subtrans_test; +INSERT INTO subtrans_test (k, v) VALUES (1, 'dog'); +DO $body$ +BEGIN + INSERT INTO subtrans_test (k, v) VALUES (2, 'cat'); + INSERT INTO subtrans_test (k, v) VALUES (1, 'frog'); -- error + BEGIN + INSERT INTO subtrans_test (k, v) VALUES (3, 'bull'); + EXCEPTION WHEN unique_violation THEN -- force subtransaction creation + RAISE NOTICE '*** FAILED ***'; + END; + INSERT INTO subtrans_test (k, v) VALUES (4, 'bear'); +EXCEPTION WHEN unique_violation THEN + RAISE NOTICE 'caught unique violation'; +END; +$body$; +SELECT * FROM subtrans_test ORDER BY 1; + + +-- Error-free inner block then erroneous statement +TRUNCATE TABLE subtrans_test; +INSERT INTO subtrans_test (k, v) VALUES (1, 'dog'); +DO $body$ +BEGIN + INSERT INTO subtrans_test (k, v) VALUES (2, 'cat'); + BEGIN + INSERT INTO subtrans_test (k, v) VALUES (3, 'bull'); + EXCEPTION WHEN unique_violation THEN -- force subtransaction creation + RAISE NOTICE '*** FAILED ***'; + END; + INSERT INTO subtrans_test (k, v) VALUES (4, 'bear'); + INSERT INTO subtrans_test (k, v) VALUES (1, 'frog'); -- error +EXCEPTION WHEN unique_violation THEN + RAISE NOTICE 'caught unique violation'; +END; +$body$; +SELECT * FROM subtrans_test ORDER BY 1; + + +-- Erroneous statement in the inner block +TRUNCATE TABLE subtrans_test; +INSERT INTO subtrans_test (k, v) VALUES (1, 'dog'); +DO $body$ +BEGIN + INSERT INTO subtrans_test (k, v) VALUES (2, 'cat'); + BEGIN + INSERT INTO subtrans_test (k, v) VALUES (3, 'bull'); + INSERT INTO subtrans_test (k, v) VALUES (1, 'frog'); -- error + EXCEPTION WHEN unique_violation THEN + RAISE NOTICE 'caught unique violation'; + END; + INSERT INTO subtrans_test (k, v) VALUES (4, 'bear'); +EXCEPTION WHEN unique_violation THEN + RAISE NOTICE '*** FAILED ***'; +END; +$body$; +SELECT * FROM subtrans_test ORDER BY 1; + + +-- Erroneous statement in the inner block, caught by the outer block handler +TRUNCATE TABLE subtrans_test; +INSERT INTO subtrans_test (k, v) VALUES (1, 'dog'); +DO $body$ +BEGIN + INSERT INTO subtrans_test (k, v) VALUES (2, 'cat'); + BEGIN + INSERT INTO subtrans_test (k, v) VALUES (3, 'bull'); + INSERT INTO subtrans_test (k, v) VALUES (1, 'frog'); -- error + EXCEPTION WHEN not_null_violation THEN -- no matching handler + RAISE NOTICE '*** FAILED ***'; + END; + INSERT INTO subtrans_test (k, v) VALUES (4, 'bear'); +EXCEPTION WHEN unique_violation THEN + RAISE NOTICE 'caught unique violation'; +END; +$body$; +SELECT * FROM subtrans_test ORDER BY 1; + + +-- Test calling a stored function from DDL statement + +CREATE OR REPLACE FUNCTION inverse(float8) RETURNS float8 AS +$$ +BEGIN + RETURN 1::float8 / $1; +EXCEPTION WHEN others THEN + RAISE NOTICE 'caught exception % %', sqlstate, sqlerrm; + RETURN 'NaN'; +END$$ LANGUAGE plpgsql; + +CREATE TABLE ddl_subtrans_test (c1 float8); +INSERT INTO ddl_subtrans_test VALUES (0); +ALTER TABLE ddl_subtrans_test ADD CONSTRAINT c1_not_nan CHECK (inverse(c1) <> 'NaN'); +-- Verify that the check constraint has not been added +\d ddl_subtrans_test +DROP TABLE ddl_subtrans_test; diff --git a/src/test/regress/sql/yb.orig.interval.sql b/src/test/regress/sql/yb.orig.interval.sql new file mode 100644 index 0000000000000..ff0105df5e9f4 --- /dev/null +++ b/src/test/regress/sql/yb.orig.interval.sql @@ -0,0 +1,29 @@ +-- +-- Part of yb.port.interval modified to use YB table instead of temp table. +-- + +SET DATESTYLE = 'ISO'; +SET IntervalStyle to postgres; + +-- Test intervals that are large enough to overflow 64 bits in comparisons +CREATE TABLE INTERVAL_TBL_OF (f1 interval); -- YB: avoid TEMP to exercise YB table +INSERT INTO INTERVAL_TBL_OF (f1) VALUES + ('2147483647 days 2147483647 months'), + ('2147483647 days -2147483648 months'), + ('1 year'), + ('-2147483648 days 2147483647 months'), + ('-2147483648 days -2147483648 months'); + +SELECT r1.*, r2.* + FROM INTERVAL_TBL_OF r1, INTERVAL_TBL_OF r2 + WHERE r1.f1 > r2.f1 + ORDER BY r1.f1, r2.f1; + +CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1); +SET enable_seqscan TO false; +EXPLAIN (COSTS OFF) +SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1; +SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1; +RESET enable_seqscan; + +DROP TABLE INTERVAL_TBL_OF; diff --git a/src/test/regress/sql/yb.orig.is_local_table.sql b/src/test/regress/sql/yb.orig.is_local_table.sql new file mode 100644 index 0000000000000..ea403ccda42f6 --- /dev/null +++ b/src/test/regress/sql/yb.orig.is_local_table.sql @@ -0,0 +1,147 @@ +-- Tests for yb_is_local_table: Verify that use of this function in different cases +-- ensures that only data FROM the local region is picked. + +-- Test setup. +-- The cluster has been setup with 4 nodes: cloud1.region1.zone1, cloud1.region1.zone2, +-- cloud1.region2.zone2, cloud2.region2.zone2. +-- By default, we connect to the node with cloud1.region1.zone1, therefore tables in +-- cloud1.region1 are considered local. + +-- Create two tablespaces, one cloud local and the other region local. +CREATE TABLESPACE regionlocal WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1}]}'); +CREATE TABLESPACE cloudlocal WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1}]}'); + +-- Sanity test with LIST partition hierarchy. +CREATE TABLE list_partitioned (partkey char) PARTITION BY LIST(partkey); +-- Default partition is local. +CREATE TABLE lp_default_local PARTITION OF list_partitioned DEFAULT TABLESPACE regionlocal; +-- Partition without a custom tablespace. +CREATE TABLE lp_ad_notbsp PARTITION OF list_partitioned FOR VALUES in ('a', 'd'); +-- Local partition. +CREATE TABLE lp_bc_local PARTITION OF list_partitioned FOR VALUES in ('b', 'c') TABLESPACE regionlocal; +-- Null partition, remote. +CREATE TABLE lp_null_remote PARTITION OF list_partitioned FOR VALUES in (null) TABLESPACE cloudlocal; +-- Sub-partitions, one local and one remote. +CREATE TABLE lp_efgh PARTITION OF list_partitioned FOR VALUES in ('e', 'f', 'g', 'h') PARTITION BY LIST(partkey); +CREATE TABLE lp_ef_notbsp PARTITION OF lp_efgh FOR VALUES in ('e', 'f'); +CREATE TABLE lp_gh_local PARTITION OF lp_efgh FOR VALUES in ('g', 'h') TABLESPACE regionlocal; + +EXPLAIN (COSTS OFF) SELECT * FROM list_partitioned WHERE yb_is_local_table(tableoid); + +-- Sanity test with HASH partition hierarchy. +CREATE TABLE hash_partitioned (partkey int, partkey2 int) PARTITION BY HASH(partkey); +CREATE TABLE hp0_local PARTITION OF hash_partitioned FOR VALUES WITH (modulus 3, remainder 0) TABLESPACE regionlocal; +CREATE TABLE hp1_remote PARTITION OF hash_partitioned FOR VALUES WITH (modulus 3, remainder 1) TABLESPACE cloudlocal; +-- Sub partitions, one local and the other remote. +CREATE TABLE hp2 PARTITION OF hash_partitioned FOR VALUES WITH (modulus 3, remainder 2) PARTITION BY HASH(partkey); +CREATE TABLE hp2_1_local PARTITION OF hp2 FOR VALUES WITH (modulus 2, remainder 0) TABLESPACE regionlocal; +CREATE TABLE hp2_2_notbsp PARTITION OF hp2 FOR VALUES WITH (modulus 2, remainder 1); + +EXPLAIN (COSTS OFF) SELECT * FROM hash_partitioned WHERE yb_is_local_table(tableoid); + +-- Sanity test with RANGE partition hierarchy. +CREATE TABLE range_partitioned (partkey int, partvalue int) PARTITION BY RANGE (partkey); +-- Remote default partition. +CREATE TABLE rp_default_remote PARTITION OF range_partitioned DEFAULT TABLESPACE cloudlocal; +-- One local and one remote partition. +CREATE TABLE rp_10_15_remote PARTITION OF range_partitioned FOR VALUES FROM (10) TO (15) TABLESPACE cloudlocal; +CREATE TABLE rp_15_20_local PARTITION OF range_partitioned FOR VALUES FROM (15) TO (20) TABLESPACE regionlocal; +--Sub-partitions, one local and one remote. +CREATE TABLE rp_1_10 PARTITION OF range_partitioned FOR VALUES FROM (1) TO (10) PARTITION BY RANGE(partkey); +CREATE TABLE rp_1_5_notbsp PARTITION OF rp_1_10 FOR VALUES FROM (1) TO (5); +CREATE TABLE rp_5_10_local PARTITION OF rp_1_10 FOR VALUES FROM (5) TO (10) TABLESPACE regionlocal; + +EXPLAIN (COSTS OFF) SELECT * FROM range_partitioned WHERE yb_is_local_table(tableoid); + +-- Sanity test with JOIN across partition hierarchies. +-- The function is invoked only across hash_partitioned, so scan all partitions of +-- range_partitioned, but only local partitions of hash_partitioned. +EXPLAIN (COSTS OFF) SELECT * FROM range_partitioned INNER JOIN hash_partitioned ON range_partitioned.partkey = hash_partitioned.partkey WHERE yb_is_local_table(hash_partitioned.tableoid); + +-- TODO (deepthi): The following query should prune out the local partition but it does not. +-- Fix this in a later patch. +EXPLAIN (COSTS OFF) SELECT * FROM range_partitioned WHERE NOT yb_is_local_table(tableoid); + +-- TODO (deepthi): The following query should result in an IndexOnly scan but currently it does not. +-- Fix this in a later patch. +CREATE INDEX rp_idx ON range_partitioned(partvalue); +-- Because of varying costs, sometimes seq scan may be picked instead of IndexScan. For the purposes +-- of this test, it does not matter either way. Disable seq scan for this test to make the test +-- deterministic. +SET enable_seqscan=false; +EXPLAIN (COSTS OFF) SELECT partvalue FROM range_partitioned WHERE yb_is_local_table(tableoid) AND partvalue = 5; +SET enable_seqscan=true; + +-- Update test. +EXPLAIN (COSTS OFF) UPDATE range_partitioned SET partvalue = 5 WHERE yb_is_local_table(tableoid); +EXPLAIN (COSTS OFF) UPDATE list_partitioned SET partkey = 5 WHERE yb_is_local_table(tableoid); +EXPLAIN (COSTS OFF) UPDATE hash_partitioned SET partkey = 5 WHERE yb_is_local_table(tableoid); + +-- Sanity test for multi-key partitioning. +CREATE TABLE multikey (a int, b int) PARTITION BY RANGE (a, b); +CREATE TABLE multikey_default_local PARTITION OF multikey DEFAULT TABLESPACE regionlocal; +CREATE TABLE multikey0 PARTITION OF multikey FOR VALUES FROM (minvalue, minvalue) TO (1, minvalue) TABLESPACE cloudlocal; +CREATE TABLE multikey1_local PARTITION OF multikey FOR VALUES FROM (1, minvalue) TO (1, 1) TABLESPACE regionlocal; +CREATE TABLE multikey2 PARTITION OF multikey FOR VALUES FROM (1, 1) TO (2, minvalue); +CREATE TABLE multikey3_local PARTITION OF multikey FOR VALUES FROM (2, minvalue) TO (2, 1) TABLESPACE regionlocal; +CREATE TABLE multikey4 PARTITION OF multikey FOR VALUES FROM (2, 1) TO (2, maxvalue) TABLESPACE cloudlocal; +CREATE TABLE multikey5 PARTITION OF multikey FOR VALUES FROM (2, maxvalue) TO (maxvalue, maxvalue); + +-- Query with one partition key and function. +EXPLAIN (COSTS OFF) SELECT * FROM multikey WHERE a < 2 AND yb_is_local_table(tableoid); +-- Query with both partition keys. +EXPLAIN (COSTS OFF) SELECT * FROM multikey WHERE a = 2 AND b < 1 AND yb_is_local_table(tableoid); +-- Query with null check. +EXPLAIN (COSTS OFF) SELECT * FROM multikey WHERE b is null AND yb_is_local_table(tableoid); + +-- Sanity Test for 3 types of JOINs. +-- Create prt1, a partition hierarchy with multiples of 20 in the range 0 to 600. +CREATE TABLE prt1 (a int) PARTITION BY RANGE(a); +CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt1_p2_local PARTITION OF prt1 FOR VALUES FROM (250) TO (500) TABLESPACE regionlocal; +CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600); +INSERT INTO prt1 SELECT i FROM generate_series(0, 599) i WHERE i % 20 = 0; + +-- Create prt2 with same partition hierarchy as prt1 but with multiples of 30. +CREATE TABLE prt2 ( b int) PARTITION BY RANGE(b); +CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt2_p2_local PARTITION OF prt2 FOR VALUES FROM (250) TO (500) TABLESPACE regionlocal; +CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600); +INSERT INTO prt2 SELECT i FROM generate_series(0, 599) i WHERE i % 30 = 0; + +-- Inner Join. Pick local tables with a < 450. +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND yb_is_local_table(t1.tableoid) ORDER BY t1.a, t2.b; +SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND yb_is_local_table(t1.tableoid) ORDER BY t1.a, t2.b; + +-- Left Join. +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE yb_is_local_table(prt2.tableoid)) t2 ON t1.a = t2.b ORDER BY t1.a, t2.b; +SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE yb_is_local_table(prt2.tableoid)) t2 ON t1.a = t2.b ORDER BY t1.a, t2.b; + +-- Full Join. +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE yb_is_local_table(prt1.tableoid)) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b ORDER BY t1.a, t2.b; +SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE yb_is_local_table(prt1.tableoid)) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b ORDER BY t1.a, t2.b; + +-- Semi-join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE yb_is_local_table(t2.tableoid)) ORDER BY t1.a; + +-- TEMP tables. +CREATE TEMP TABLE temp_partitioned (partkey char) PARTITION BY LIST(partkey); +CREATE TEMP TABLE tp1 PARTITION OF temp_partitioned FOR VALUES in ('a', 'd'); +CREATE TEMP TABLE tp2 PARTITION OF temp_partitioned FOR VALUES in ('b', 'c'); + +EXPLAIN (COSTS OFF) SELECT * FROM temp_partitioned WHERE yb_is_local_table(tableoid); + +-- Cleanup. +DROP TABLE range_partitioned; +DROP TABLE list_partitioned; +DROP TABLE hash_partitioned; +DROP TABLE multikey; +DROP TABLE prt1; +DROP TABLE prt2; +DROP TABLE temp_partitioned; +DROP TABLESPACE regionlocal; +DROP TABLESPACE cloudlocal; diff --git a/src/test/regress/sql/yb.orig.join_batching.sql b/src/test/regress/sql/yb.orig.join_batching.sql new file mode 100644 index 0000000000000..b0d43d39f233a --- /dev/null +++ b/src/test/regress/sql/yb.orig.join_batching.sql @@ -0,0 +1,1161 @@ +-- +-- Test BNL plan correctness +-- + +SET enable_bitmapscan = false; -- TODO(#20573): update bitmap scan cost model + +-- Force BNL +SET enable_hashjoin = off; +SET enable_mergejoin = off; +SET enable_seqscan = off; +SET enable_material = off; +SET yb_prefer_bnl = on; +SET enable_nestloop = off; + +SET yb_bnl_batch_size = 3; + +-------------------------------------------------------- +-- Try some queries from yb.port.join with BNL forced -- +-------------------------------------------------------- + +CREATE TABLE J1_TBL ( + i integer, + j integer, + t text +); + +CREATE TABLE J2_TBL ( + i integer, + k integer +); + + +INSERT INTO J1_TBL VALUES (1, 4, 'one'); +INSERT INTO J1_TBL VALUES (2, 3, 'two'); +INSERT INTO J1_TBL VALUES (3, 2, 'three'); +INSERT INTO J1_TBL VALUES (4, 1, 'four'); +INSERT INTO J1_TBL VALUES (5, 0, 'five'); +INSERT INTO J1_TBL VALUES (6, 6, 'six'); +INSERT INTO J1_TBL VALUES (7, 7, 'seven'); +INSERT INTO J1_TBL VALUES (8, 8, 'eight'); +INSERT INTO J1_TBL VALUES (0, NULL, 'zero'); +INSERT INTO J1_TBL VALUES (NULL, NULL, 'null'); +INSERT INTO J1_TBL VALUES (NULL, 0, 'zero'); + +INSERT INTO J2_TBL VALUES (1, -1); +INSERT INTO J2_TBL VALUES (2, 2); +INSERT INTO J2_TBL VALUES (3, -3); +INSERT INTO J2_TBL VALUES (2, 4); +INSERT INTO J2_TBL VALUES (5, -5); +INSERT INTO J2_TBL VALUES (5, -5); +INSERT INTO J2_TBL VALUES (0, NULL); +INSERT INTO J2_TBL VALUES (NULL, NULL); +INSERT INTO J2_TBL VALUES (NULL, 0); + +-- +-- CORRELATION NAMES +-- Make sure that table/column aliases are supported +-- before diving into more complex join syntax. +-- + +SELECT '' AS "xxx", * + FROM J1_TBL AS tx order by 1, 2, 3, 4; + +SELECT '' AS "xxx", * + FROM J1_TBL tx order by 1, 2, 3, 4; + +SELECT '' AS "xxx", * + FROM J1_TBL AS t1 (a, b, c) order by 1, 2, 3, 4; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) order by 1, 2, 3, 4; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) order by 1, 2, 3, 4, 5, 6; + +SELECT '' AS "xxx", t1.a, t2.e + FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) + WHERE t1.a = t2.d order by 1, 2, 3; + + +-- +-- +-- Inner joins (equi-joins) +-- +-- + +-- +-- Inner joins (equi-joins) with USING clause +-- The USING syntax changes the shape of the resulting table +-- by including a column in the USING clause only once in the result. +-- + +-- Inner equi-join on specified column +SELECT '' AS "xxx", * + FROM J1_TBL INNER JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + +-- Same as above, slightly different syntax +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, d) USING (a) + ORDER BY a, d; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, b) USING (b) + ORDER BY b, t1.a; + + +-- +-- NATURAL JOIN +-- Inner equi-join on all columns with the same name +-- + +SELECT '' AS "xxx", * + FROM J1_TBL NATURAL JOIN J2_TBL order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (a, d) order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a) order by 1, 2, 3, 4, 5; + +-- mismatch number of columns +-- currently, Postgres will fill in with underlying names +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b) NATURAL JOIN J2_TBL t2 (a) order by 1, 2, 3, 4, 5; + + +-- +-- Inner joins (equi-joins) +-- + +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.i) order by 1, 2, 3, 4, 5, 6; + +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k) order by 1, 2, 3, 4, 5, 6; + + +-- +-- Non-equi-joins +-- + +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i <= J2_TBL.k) order by 1, 2, 3, 4, 5, 6; + + +-- +-- Outer joins +-- Note that OUTER is a noise word +-- + +SELECT '' AS "xxx", * + FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i) + ORDER BY i, k, t; + +SELECT '' AS "xxx", * + FROM J1_TBL LEFT JOIN J2_TBL USING (i) + ORDER BY i, k, t; + +SELECT '' AS "xxx", * + FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL RIGHT JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i) + ORDER BY i, k, t; + +SELECT '' AS "xxx", * + FROM J1_TBL FULL JOIN J2_TBL USING (i) + ORDER BY i, k, t; + +SELECT '' AS "xxx", * + FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (k = 1) order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (i = 1) order by 1, 2, 3, 4, 5; + +-- +-- semijoin selectivity for <> +-- +-- explain (costs off) +-- select * from int4_tbl i4, tenk1 a +-- where exists(select * from tenk1 b +-- where a.twothousand = b.twothousand and a.fivethous <> b.fivethous) +-- and i4.f1 = a.tenthous; + + +-- +-- More complicated constructs +-- + +-- +-- Multiway full join +-- + +CREATE TABLE t1 (name TEXT, n INTEGER); +CREATE TABLE t2 (name TEXT, n INTEGER); +CREATE TABLE t3 (name TEXT, n INTEGER); + +INSERT INTO t1 VALUES ( 'bb', 11 ); +INSERT INTO t2 VALUES ( 'bb', 12 ); +INSERT INTO t2 VALUES ( 'cc', 22 ); +INSERT INTO t2 VALUES ( 'ee', 42 ); +INSERT INTO t3 VALUES ( 'bb', 13 ); +INSERT INTO t3 VALUES ( 'cc', 23 ); +INSERT INTO t3 VALUES ( 'dd', 33 ); + +SELECT * FROM t1 FULL JOIN t2 USING (name) FULL JOIN t3 USING (name) order by 1, 2, 3, 4; + +-- +-- Test interactions of join syntax and subqueries +-- + +-- Basic cases (we expect planner to pull up the subquery here) +SELECT * FROM +(SELECT * FROM t2) as s2 +INNER JOIN +(SELECT * FROM t3) s3 +USING (name) order by 1, 2, 3; + +SELECT * FROM +(SELECT * FROM t2) as s2 +LEFT JOIN +(SELECT * FROM t3) s3 +USING (name) order by 1, 2, 3; + +SELECT * FROM +(SELECT * FROM t2) as s2 +FULL JOIN +(SELECT * FROM t3) s3 +USING (name) order by 1, 2, 3; + +-- Cases with non-nullable expressions in subquery results; +-- make sure these go to null as expected +SELECT * FROM +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL INNER JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5; + +SELECT * FROM +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL LEFT JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5; + +SELECT * FROM +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL FULL JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4; + +SELECT * FROM +(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1 +NATURAL INNER JOIN +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL INNER JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5, 6, 7; + +SELECT * FROM +(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1 +NATURAL FULL JOIN +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL FULL JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5, 6, 7; + +SELECT * FROM +(SELECT name, n as s1_n FROM t1) as s1 +NATURAL FULL JOIN + (SELECT * FROM + (SELECT name, n as s2_n FROM t2) as s2 + NATURAL FULL JOIN + (SELECT name, n as s3_n FROM t3) as s3 + ) ss2 order by 1, 2, 3, 4; + +SELECT * FROM +(SELECT name, n as s1_n FROM t1) as s1 +NATURAL FULL JOIN + (SELECT * FROM + (SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 + NATURAL FULL JOIN + (SELECT name, n as s3_n FROM t3) as s3 + ) ss2 order by 1, 2, 3, 4, 5; + + +-- Test for propagation of nullability constraints into sub-joins + +create temp table x (x1 int, x2 int); +insert into x values (1,11); +insert into x values (2,22); +insert into x values (3,null); +insert into x values (4,44); +insert into x values (5,null); + +create temp table y (y1 int, y2 int); +insert into y values (1,111); +insert into y values (2,222); +insert into y values (3,333); +insert into y values (4,null); + +select * from x order by 1, 2; +select * from y order by 1, 2; + +select * from x left join y on (x1 = y1 and x2 is not null) order by 1, 2, 3, 4; +select * from x left join y on (x1 = y1 and y2 is not null) order by 1, 2, 3, 4; + +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) order by 1, 2, 3, 4, 5, 6; +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1 and x2 is not null) order by 1, 2, 3, 4, 5, 6; +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1 and y2 is not null) order by 1, 2, 3, 4, 5, 6; +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1 and xx2 is not null) order by 1, 2, 3, 4, 5, 6; +-- these should NOT give the same answers as above +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) where (x2 is not null) order by 1; +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) where (y2 is not null) order by 1; +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) where (xx2 is not null) order by 1; + +------------------------------- +-- Original tests start here -- +------------------------------- + +CREATE TABLE p1 (a int, b int, c varchar, primary key(a,b)); +INSERT INTO p1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 2 = 0; +CREATE INDEX p1_b_idx ON p1 (b ASC); +ANALYZE p1; + +CREATE TABLE p2 (a int, b int, c varchar, primary key(a,b)); +INSERT INTO p2 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 3 = 0; +ANALYZE p2; + +CREATE TABLE p3 (a int, b int, c varchar, primary key(a,b)); +INSERT INTO p3 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 5 = 0; +ANALYZE p3; + +CREATE TABLE p4 (a int, b int, c varchar, primary key(a,b)); +INSERT INTO p4 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 7 = 0; +ANALYZE p4; + +CREATE TABLE p5 (a int, b int, c varchar, primary key(a asc,b asc)); +INSERT INTO p5 SELECT i / 10, i % 10, to_char(i, 'FM0000') FROM generate_series(0, 599) i; +CREATE INDEX p5_hash ON p5((a,b) hash); +CREATE INDEX p5_hash_asc ON p5(a hash, b asc); +ANALYZE p5; + + +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 JOIN p2 t2 ON t1.a = t2.a WHERE t1.a <= 100 AND t2.a <= 100; +SELECT * FROM p1 t1 JOIN p2 t2 ON t1.a = t2.a WHERE t1.a <= 100 AND t2.a <= 100; + +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 JOIN p2 t2 ON t1.a = t2.a + 1 WHERE t1.a <= 100 AND t2.a <= 100; +SELECT * FROM p1 t1 JOIN p2 t2 ON t1.a = t2.a + 1 WHERE t1.a <= 100 AND t2.a <= 100; + +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 JOIN p2 t2 ON t1.a - 1 = t2.a + 1 WHERE t1.a <= 100 AND t2.a <= 100; +SELECT * FROM p1 t1 JOIN p2 t2 ON t1.a - 1 = t2.a + 1 WHERE t1.a <= 100 AND t2.a <= 100; + +-- Batching on compound clauses +/*+ Leading((p2 p1)) */ EXPLAIN (ANALYZE, SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT * FROM p1 JOIN p2 ON p1.a = p2.b AND p2.a = p1.b; +/*+ Leading((p2 p1)) */ SELECT * FROM p1 JOIN p2 ON p1.a = p2.b AND p2.a = p1.b; + +explain (costs off) select * from p1 left join p5 on p1.a - 1 = p5.a and p1.b - 1 = p5.b where p1.a <= 30; +select * from p1 left join p5 on p1.a - 1 = p5.a and p1.b - 1 = p5.b where p1.a <= 30; + +-- Batching should still be disabled if there is a filter +-- clause on a batched relation. +/*+ set(enable_seqscan on) IndexScan(p1 p1_b_idx) Leading((p2 p1)) */ EXPLAIN (COSTS OFF) SELECT * FROM p1 JOIN p2 ON p1.a = p2.b AND p2.a = p1.b; + +/*+ set(enable_seqscan on) IndexScan(p1 p1_b_idx) Leading((p2 p3)) */ EXPLAIN (COSTS OFF) SELECT * FROM p1, p2, p3 where p1.a = p3.a AND p2.a = p3.a and p1.b = p2.b; + +/*+ set(enable_seqscan on) Leading((p2 p1)) */ EXPLAIN (COSTS OFF) SELECT * FROM p1 JOIN p2 ON p1.a = p2.b AND p1.b < p2.b + 1; + +/*+IndexScan(p5 p5_hash)*/explain (costs off) select * from p1 left join p5 on p1.a - 1 = p5.a and p1.b - 1 = p5.b where p1.a <= 30; +/*+IndexScan(p5 p5_hash)*/ select * from p1 left join p5 on p1.a - 1 = p5.a and p1.b - 1 = p5.b where p1.a <= 30; + +/*+IndexScan(p5 p5_hash_asc)*/explain (costs off) select * from p1 left join p5 on p1.a - 1 = p5.a and p1.b - 1 = p5.b where p1.a <= 30; +/*+IndexScan(p5 p5_hash_asc)*/ select * from p1 left join p5 on p1.a - 1 = p5.a and p1.b - 1 = p5.b where p1.a <= 30; + +/*+ Leading((p2 p1)) */ EXPLAIN (COSTS OFF) SELECT * FROM p1 JOIN p2 ON p1.a = p2.b AND p2.a = p1.b AND p1.a = p2.a; + +/*+ Leading((p2 p1)) */ SELECT * FROM p1 JOIN p2 ON p1.a = p2.b AND p2.a = p1.b AND p1.a = p2.a; + +/*+ set(enable_seqscan true) Leading((p2 p1)) IndexScan(p1 p1_b_idx) */ EXPLAIN (COSTS OFF) SELECT * FROM p1 JOIN p2 ON p1.a = p2.b AND p2.a = p1.b; + +CREATE TABLE t10 (r1 int, r2 int, r3 int, r4 int); + +INSERT INTO t10 + SELECT DISTINCT + i1, i2+5, i3, i4 + FROM generate_series(1, 5) i1, + generate_series(1, 5) i2, + generate_series(1, 5) i3, + generate_series(1, 10) i4; + +CREATE index i_t ON t10 (r1 ASC, r2 ASC, r3 ASC, r4 ASC); + +CREATE TABLE t11 (c1 int, c3 int, x int); +INSERT INTO t11 VALUES (1,2,0), (1,3,0), (5,2,0), (5,3,0), (5,4,0); + +CREATE TABLE t12 (c4 int, c2 int, y int); +INSERT INTO t12 VALUES (3,7,0),(6,9,0),(9,7,0),(4,9,0); + +EXPLAIN (COSTS OFF) /*+ Leading((t12 (t11 t10))) Set(enable_seqscan true) */ SELECT t10.* FROM t12, t11, t10 WHERE x = y AND c1 = r1 AND c2 = r2 AND c3 = r3 AND c4 = r4 order by c1, c2, c3, c4; + +/*+ Leading((t12 (t11 t10))) Set(enable_seqscan true) */ SELECT t10.* FROM t12, t11, t10 WHERE x = y AND c1 = r1 AND c2 = r2 AND c3 = r3 AND c4 = r4 order by c1, c2, c3, c4; + +DROP TABLE t10; +DROP TABLE t11; +DROP TABLE t12; + +CREATE TABLE strtable(a varchar(26), b varchar(23), primary key(a, b)); +CREATE TABLE strtable2(a varchar(26), b varchar(23), primary key(a, b)); +INSERT INTO strtable VALUES ('123', 'abc'), ('1234', 'abcd'), ('123', 'pqr'); +INSERT INTO strtable2 VALUES ('123', 'abc'), ('123', 'abcd'), ('123', 'pqr'); + +EXPLAIN (COSTS OFF) SELECT * FROM strtable, strtable2 WHERE strtable.a = strtable2.a; +SELECT * FROM strtable, strtable2 WHERE strtable.a = strtable2.a; + +EXPLAIN (COSTS OFF) SELECT * FROM strtable, strtable2 WHERE strtable.a = strtable2.a AND strtable.b = strtable2.b; +SELECT * FROM strtable, strtable2 WHERE strtable.a = strtable2.a AND strtable.b = strtable2.b; + +DROP TABLE strtable; +DROP TABLE strtable2; + +create table q1 (a double precision, b double precision, primary key (a, b)); +insert into q1 values (12.34, 99.99), (12.345, 99.99); +create table q2 (a decimal(6, 2), b decimal(6, 2), primary key (a, b)); +insert into q2 values (12.34, 99.99), (12.345, 99.99); +explain (costs off) select * from q1, q2 where q1.a = q2.a and q1.b = q2.b; +select * from q1, q2 where q1.a = q2.a and q1.b = q2.b; + +create table q3 (a char(6), b char(6), primary key (a, b)); +insert into q3 values ('abc', 'def'), ('xyz', 'uvw'); +create table q4 (a varchar(6), b varchar(6), primary key (a, b)); +insert into q4 values ('abc ', 'def '), ('xyz', 'uvw'); +explain (costs off) select * from q3, q4 where q3.a = q4.a and q3.b = q4.b; +select * from q3, q4 where q3.a = q4.a and q3.b = q4.b; + +explain (costs off) select * from q1, q2 where q1.a::decimal = q2.a and q1.b::decimal = q2.b; +select * from q1, q2 where q1.a::decimal = q2.a and q1.b::decimal = q2.b; +explain (costs off) select * from q1, q2 where q1.a = q2.a::decimal and q1.b = q2.b::decimal; +select * from q1, q2 where q1.a = q2.a::decimal and q1.b = q2.b::decimal; +explain (costs off) select * from q3, q4 where q3.a::text = q4.a and q3.b::text = q4.b; +select * from q3, q4 where q3.a::text = q4.a and q3.b::text = q4.b; +explain (costs off) select * from q3, q4 where q3.a = q4.a::text and q3.b = q4.b::text; +select * from q3, q4 where q3.a = q4.a::text and q3.b = q4.b::text; + +explain (costs off) /*+ Leading((q3 q4)) IndexScan(q4) */select * from q3, q4 where q3.a = q4.a and q3.b = q4.b; +/*+ Leading((q3 q4)) IndexScan(q4) */select * from q3, q4 where q3.a = q4.a and q3.b = q4.b; + +explain (costs off) /*+ Leading((q3 q4)) IndexScan(q4) */select * from q3, q4 where q3.a::text = q4.a and q3.b::text = q4.b; +/*+ Leading((q3 q4)) IndexScan(q4) */select * from q3, q4 where q3.a::text = q4.a and q3.b::text = q4.b; + +drop table q1; +drop table q2; +drop table q3; +drop table q4; + +create table d1(a int, primary key(a)); +create table d2(a int, primary key(a)); +create table d3(a int, primary key(a)); +create table d4(a int, primary key(a)); + +/*+Leading(((d2 (d3 d4)) d1))*/ explain (costs off) select * from d1,d2,d3,d4 where d1.a = d3.a and d2.a = d3.a and d4.a = d2.a; + +drop table d1; +drop table d2; +drop table d3; +drop table d4; + +create table test ( + id uuid NOT NULL, + num int4 NOT NULL, + PRIMARY KEY ((id)HASH, num DESC) +); +insert into test(id, num) VALUES +('774cee8f-f0e9-4c46-8f3d-3b5e7db8b839'::uuid, 1); + +/*+ Set(yb_bnl_batch_size 3) */ explain (costs off) +select * from test t1 +join test t2 on (t1.id = t2.id and t1.num = t2.num) +where t1.id in ( + '774cee8f-f0e9-4c46-8f3d-3b5e7db8b839'::uuid, + '884cee8f-f0e9-4c46-8f3d-3b5e7db8b847'::uuid); + +/*+ Set(yb_bnl_batch_size 3) */ +select * from test t1 +join test t2 on (t1.id = t2.id and t1.num = t2.num) +where t1.id in ( + '774cee8f-f0e9-4c46-8f3d-3b5e7db8b839'::uuid, + '884cee8f-f0e9-4c46-8f3d-3b5e7db8b847'::uuid); + +drop table test; + +EXPLAIN (COSTS OFF) SELECT * FROM p3 t3 LEFT OUTER JOIN (SELECT t1.a as a FROM p1 t1 JOIN p2 t2 ON t1.a = t2.b WHERE t1.a <= 100 AND t2.a <= 100) s ON t3.a = s.a WHERE t3.a <= 30; +SELECT * FROM p3 t3 LEFT OUTER JOIN (SELECT t1.a as a FROM p1 t1 JOIN p2 t2 ON t1.a = t2.b WHERE t1.a <= 100 AND t2.a <= 100) s ON t3.a = s.a WHERE t3.a <= 30; + +EXPLAIN (COSTS OFF) SELECT * FROM p3 t3 RIGHT OUTER JOIN (SELECT t1.a as a FROM p1 t1 JOIN p2 t2 ON t1.a = t2.b WHERE t1.b <= 10 AND t2.b <= 15) s ON t3.a = s.a; +SELECT * FROM p3 t3 RIGHT OUTER JOIN (SELECT t1.a as a FROM p1 t1 JOIN p2 t2 ON t1.a = t2.b WHERE t1.b <= 10 AND t2.b <= 15) s ON t3.a = s.a; + +-- anti join-- +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 WHERE NOT EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.a) AND t1.a <= 40; +SELECT * FROM p1 t1 WHERE NOT EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.a) AND t1.a <= 40; + +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 WHERE NOT EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.b) AND t1.a <= 40; +SELECT * FROM p1 t1 WHERE NOT EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.b) AND t1.a <= 40; + +-- semi join-- +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 WHERE EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.a) AND t1.a <= 40; +SELECT * FROM p1 t1 WHERE EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.a) AND t1.a <= 40; + +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 WHERE EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.b) AND t1.a <= 40; +SELECT * FROM p1 t1 WHERE EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.b) AND t1.a <= 40; + +CREATE TABLE int2type (a int2, PRIMARY KEY(a ASC)); +INSERT INTO int2type VALUES (1), (4), (555), (-33), (6923); + +-- testing batching on join conditions across different types (int2, int4) +/*+Leading((i2 p))*/ EXPLAIN (COSTS OFF) SELECT * FROM int2type i2 JOIN p1 p ON i2.a = p.a; +/*+Leading((i2 p))*/ SELECT * FROM int2type i2 JOIN p1 p ON i2.a = p.a; +/*+Leading((p i2))*/ EXPLAIN (COSTS OFF) SELECT * FROM int2type i2 JOIN p1 p ON i2.a = p.a; +/*+Leading((p i2))*/ SELECT * FROM int2type i2 JOIN p1 p ON i2.a = p.a; +DROP TABLE int2type; + +create table floattable1(a float8, b float4, primary key(a asc, b asc)); +create table floattable2(a float4, b float8, primary key(a asc, b asc)); +insert into floattable2 values (1.12312, 29401.212931231); +insert into floattable2 values (1.12312, 2941.212931231); +insert into floattable1 select * from floattable2; +explain (costs off) /*+Leading((f2 f1)) YbBatchedNL(f1 f2)*/ select * from floattable1 f1, floattable2 f2 where f1.a = f2.a; +/*+Leading((f2 f1)) YbBatchedNL(f1 f2)*/ select * from floattable1 f1, floattable2 f2 where f1.a = f2.a; + +-- We don't support float4 IN yet +explain (costs off) /*+Leading((f1 f2)) YbBatchedNL(f1 f2)*/ select * from floattable1 f1, floattable2 f2 where f1.a = f2.a; +/*+Leading((f1 f2)) YbBatchedNL(f1 f2)*/ select * from floattable1 f1, floattable2 f2 where f1.a = f2.a; + +drop table floattable1; +drop table floattable2; + +create table ss1(a bigint, b int, primary key(b asc, a asc)); +create table ss2(a int, b bigint, primary key(a asc, b asc)); +-- 0x8000000000000001 should not join with 1 and 0x8000000000000002 should not join with 2 +insert into ss1 values (1,2), (3,4), (x'8000000000000001'::bigint, 1); +insert into ss2 values (1,2), (3,4), (1,1), (1, x'8000000000000002'::bigint); +explain (costs off) /*+Leading((ss1 ss2))*/ select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b; +/*+Leading((ss1 ss2))*/ select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b; +explain (costs off) /*+Leading((ss2 ss1))*/ select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b; +/*+Leading((ss2 ss1))*/ select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b; +explain (costs off) /*+Leading((ss2 ss1))*/ select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b and ss1.b < 10; +/*+Leading((ss2 ss1))*/ select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b and ss1.b < 10; +drop table ss1; +drop table ss2; + +create table ss1(a varchar(16), b int4); +insert into ss1 values ('abc', 123), ('abc', 124); +insert into ss1 values ('xyz', 123), ('xyz', 124); +insert into ss1 values ('ijk', 123), ('xyz', 456); +create table ss2(a varchar(16), b int8); +create index ss2_ind on ss2(a asc, b asc); +insert into ss2 values ('abc', 123), ('abc', 124); +insert into ss2 values ('xyz', 123), ('xyz', 124); +/*+YbBatchedNL(ss1 ss2) Leading((ss1 ss2)) IndexOnlyScan(ss2 ss2_ind)*/ explain (costs off) select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b and ss2.b <= 100000 order by 1,2; +/*+YbBatchedNL(ss1 ss2) Leading((ss1 ss2)) IndexOnlyScan(ss2 ss2_ind)*/ select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b and ss2.b <= 100000 order by 1,2; +drop table ss1; +drop table ss2; + +create table ss1(a int, b float8, primary key(a asc, b asc)); +create table ss2(a bigint, b float4, primary key(a asc, b asc)); +insert into ss1 values (1, 0.0), (1, 0.5), (3, 0.625), (2, 0.625 + POW(2, -25)), (2, 0.625); +insert into ss2 values (1, 0.0), (1, 0.5), (x'8000000000000001'::bigint, 0.5), (2, 0.625); +explain (costs off) /*+YbBatchedNL(ss1 ss2) Leading((ss2 ss1))*/ select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b; +/*+YbBatchedNL(ss1 ss2) Leading((ss2 ss1))*/ select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b; +explain (costs off) /*+YbBatchedNL(ss1 ss2) Leading((ss1 ss2))*/ select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b; +/*+YbBatchedNL(ss1 ss2) Leading((ss1 ss2))*/ select * from ss1, ss2 where ss1.a = ss2.a and ss1.b = ss2.b; +drop table ss1; +drop table ss2; + +create table ss1(a timestamp primary key); +create table ss2(a date primary key); +insert into ss1 values (to_timestamp('27 Nov 2100 08:14:30', 'DD MON YYYY HH:MI:SS')), (to_timestamp('27 Nov 2100', 'DD MON YYYY')); +insert into ss2 values (to_date('27 Nov 2100 08:14:30', 'DD MON YYYY')), (to_date('27 Feb 2010 08:14:30', 'DD MON YYYY')); +explain (costs off) /*+YbBatchedNL(ss1 ss2) Leading((ss2 ss1))*/ select * from ss1, ss2 where ss1.a = ss2.a; +/*+YbBatchedNL(ss1 ss2) Leading((ss2 ss1))*/ select * from ss1, ss2 where ss1.a = ss2.a; +explain (costs off) /*+YbBatchedNL(ss1 ss2) Leading((ss1 ss2))*/ select * from ss1, ss2 where ss1.a = ss2.a; +/*+YbBatchedNL(ss1 ss2) Leading((ss1 ss2))*/ select * from ss1, ss2 where ss1.a = ss2.a; +drop table ss1; +drop table ss2; + +create table intable(a int, b text, primary key(a asc, b asc)); +create table out(c1 int, c2 text); +create index out_ind on out(c2 asc NULLS FIRST); +insert into intable values (1, 'abcd'); +insert into out values (1, NULL), (1, 'abcd'); +/*+Leading((out intable)) IndexScan(out out_ind)*/ explain (costs off) select * from out, intable where out.c1 = intable.a and out.c2 = intable.b and intable.a <= 4; +/*+Leading((out intable)) IndexScan(out out_ind)*/ select * from out, intable where out.c1 = intable.a and out.c2 = intable.b and intable.a <= 4; +drop table intable; +drop table out; + +create table outtable(a int); +insert into outtable values (1), (2), (3); + +create table intable(a int primary key); +insert into intable values (2), (3), (4), (6); + +explain (costs off) select * FROM outtable t1 left outer join intable t2 on t1.a = t2.a where t1.a = 1 or t2.a = 2 order by t1.a; + +select * FROM outtable t1 left outer join intable t2 on t1.a = t2.a where t1.a = 1 or t2.a = 2 order by t1.a; + +explain (costs off) select * FROM outtable t1 left outer join intable t2 on t1.a = t2.a where t1.a >= t2.a or t1.a = 1 order by t1.a; + +select * FROM outtable t1 left outer join intable t2 on t1.a = t2.a where t1.a >= t2.a or t1.a = 1 order by t1.a; + +explain (costs off) select * FROM outtable t1 left outer join intable t2 on t1.a = t2.a where (t1.a + 1 > t2.a or t1.a = 1) order by t1.a; + +select * FROM outtable t1 left outer join intable t2 on t1.a = t2.a where (t1.a + 1 > t2.a or t1.a = 1) order by t1.a; + +-- The problematic join filter is pushdownable to the nullable side. +-- BNL not allowed here. +explain (costs off) select * FROM outtable t1 left outer join intable t2 on t1.a = t2.a and (t1.a + 1 > t2.a or t1.a = 1) order by t1.a; + +select * FROM outtable t1 left outer join intable t2 on t1.a = t2.a where (t1.a + 1 > t2.a or t1.a = 1) order by t1.a; + +drop table outtable; +drop table intable; + +CREATE TABLE q1(a int); +CREATE TABLE q2(a int); +CREATE TABLE q3(a int primary key); + +-- We shouldn't be producing dangerous plans that have join clauses +-- that involve more than one rel on a side. Leading hint should not be +-- respected here. +-- See issue #17150 +/*+Set(enable_mergejoin false) Set(enable_hashjoin false) Set(enable_material false) Leading((q1 (q2 q3)))*/explain (costs off) select * from q1, q2, q3 where q3.a = q2.a + q1.a; + +-- This join is not dangerous as the clause q3.a = q2.a + q1.a +-- will actually receive a cross product of q1 and q2 here. +/*+Set(enable_mergejoin false) Set(enable_hashjoin false) Set(enable_material false) Leading(((q1 q2) q3))*/explain (costs off) select * from q1, q2, q3 where q3.a = q2.a + q1.a; + +DROP TABLE q1; +DROP TABLE q2; +DROP TABLE q3; + +set yb_bnl_batch_size to 10; +explain (costs off) select * from p1 a join p2 b on a.a = b.a join p3 c on b.a = c.a join p4 d on a.b = d.b where a.b = 10 ORDER BY a.a, b.a, c.a, d.a; +select * from p1 a join p2 b on a.a = b.a join p3 c on b.a = c.a join p4 d on a.b = d.b where a.b = 10 ORDER BY a.a, b.a, c.a, d.a; + +DROP TABLE p1; +DROP TABLE p2; +DROP TABLE p3; +DROP TABLE p4; +DROP TABLE p5; + + + +CREATE TABLE s1(r1 int, r2 int, r3 int); +CREATE TABLE s2(r1 int, r2 int, r3 int); +CREATE TABLE s3(r1 int, r2 int); +CREATE INDEX ON s3 (r1 asc, r2 asc); + +INSERT INTO s1 select i,i,i from generate_series(1,10) i; +INSERT INTO s2 select i,i,i from generate_series(1,10) i; +INSERT INTO s3 select i,i from generate_series(1,100) i; +/*+Set(enable_nestloop true) Set(enable_seqscan true) Set(yb_bnl_batch_size 3) Leading((s2 (s1 s3))) YbBatchedNL(s1 s3)*/ explain (costs off) select s3.* from s1, s2, s3 where s3.r1 = s1.r1 and s3.r2 = s2.r2 and s1.r3 = s2.r3 order by s3.r1, s3.r2; +/*+Set(enable_nestloop true) Set(enable_seqscan true) Set(yb_bnl_batch_size 3) Leading((s2 (s1 s3))) YbBatchedNL(s1 s3)*/ select s3.* from s1, s2, s3 where s3.r1 = s1.r1 and s3.r2 = s2.r2 and s1.r3 = s2.r3 order by s3.r1, s3.r2; + +DROP TABLE s3; +DROP TABLE s2; +DROP TABLE s1; + +CREATE TABLE r1(a int, PRIMARY KEY(a ASC)); +CREATE TABLE r2(a int); + +/*+Leading(r2 r1)*/ +EXPLAIN (COSTS OFF) +SELECT * +FROM r1 +RIGHT OUTER JOIN ( + (SELECT + NULL::int AS nullcol + FROM + r2 + LIMIT 1)) AS r2 +ON (r1.a = r2.nullcol); + +SELECT * +FROM r1 +RIGHT OUTER JOIN ( + (SELECT + NULL::int AS nullcol + FROM + r2 + LIMIT 1)) AS r2 +ON (r1.a = r2.nullcol); + +DROP TABLE r1; +DROP TABLE r2; + +create table s1(a int, primary key (a asc)); +create table s2(a int, primary key (a asc)); +create table s3(a int, primary key (a asc)); + +insert into s1 values (24), (25); +insert into s2 values (24), (25); +insert into s3 values (24), (25); + +explain (costs off) /*+set(yb_bnl_batch_size 3) Leading(( ( s1 s2 ) s3 )) MergeJoin(s1 s2)*/select * from s1 left outer join s2 +on s1.a = s2.a left outer join s3 on s2.a = s3.a where s1.a > 20; + +/*+set(yb_bnl_batch_size 3) Leading(( ( s1 s2 ) s3 )) MergeJoin(s1 s2)*/ select * from s1 left outer join s2 +on s1.a = s2.a left outer join s3 on s2.a = s3.a where s1.a > 20; + +drop table s1; +drop table s2; +drop table s3; + +SET yb_bnl_batch_size = 3; + +-- Testing column groups in HybridScanChoices +create table test2 (a int, pp int, b int, pp2 int, c int, primary key(a asc, pp asc, b asc, pp2 asc, c asc)); +insert into test2 values (1,0, 2,0,1), (2,0, 3,0,3), (2,0,3,0,5); +create table test1 (a int, pp int, b int, pp2 int, c int, primary key(a asc, pp asc, b asc, pp2 asc, c asc)); +insert into test1 values (1,0,2,0,1), (1,0,2,0,2), (2,0,3,0,3), (2,0,4,0,4), (2,0,4,0,5), (2,0,4,0,6); +explain (costs off) select * from test1 p1 join test2 p2 on p1.a = p2.a AND p1.b = p2.b AND p1.c = p2.c; +select * from test1 p1 join test2 p2 on p1.a = p2.a AND p1.b = p2.b AND p1.c = p2.c; +drop table test1; +drop table test2; + +-- Test on unhashable join operations. These should use the tuplestore +-- strategy. +CREATE TABLE m1 (a money, primary key(a asc)); +INSERT INTO m1 SELECT i*2 FROM generate_series(1, 2000) i; + +CREATE TABLE m2 (a money, primary key(a asc)); +INSERT INTO m2 SELECT i*5 FROM generate_series(1, 2000) i; + +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM m1 t1 JOIN m2 t2 ON t1.a = t2.a WHERE t1.a <= 50::money; +SELECT * FROM m1 t1 JOIN m2 t2 ON t1.a = t2.a WHERE t1.a <= 50::money; + +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM m2 t1 LEFT JOIN m1 t2 ON t1.a = t2.a WHERE t1.a <= 50::money; +SELECT * FROM m2 t1 LEFT JOIN m1 t2 ON t1.a = t2.a WHERE t1.a <= 50::money; + +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM m2 t1 WHERE NOT EXISTS (SELECT 1 FROM m1 t2 WHERE t1.a = t2.a) AND t1.a <= 50::money; +SELECT * FROM m2 t1 WHERE NOT EXISTS (SELECT 1 FROM m1 t2 WHERE t1.a = t2.a) AND t1.a <= 50::money; + +DROP TABLE m1; +DROP TABLE m2; + +create table q1 (c1 int, c2 int, primary key (c1 asc, c2 asc)); +create table q2 (c1 int, c2 int, primary key (c2 hash, c1 asc)); +insert into q1 select i, i / 4 from generate_series(0, 199) i; +insert into q2 select i, i / 2 from generate_series(0, 999) i; + +analyze q1; +analyze q2; + +SET yb_bnl_batch_size = 3; + +-- Make sure a sort node is inserted above a batched NL join when appropriate + +explain (costs off) select q1.c1 from q1 join q2 on q1.c2 = q2.c2 order by q1.c1 limit 10; +select q1.c1 from q1 join q2 on q1.c2 = q2.c2 order by q1.c1 limit 10; + +explain (costs off) select q1.c1 from q1 join q2 on q1.c2 = q2.c2 order by q1.c1 DESC limit 10; +select q1.c1 from q1 join q2 on q1.c2 = q2.c2 order by q1.c1 DESC limit 10; + +explain (costs off) select q2.c1, q1.c1 from q1 join q2 on q1.c2 = q2.c2 order by q1.c1 limit 10; +select q2.c1, q1.c1 from q1 join q2 on q1.c2 = q2.c2 order by q1.c1 limit 10; + +create index on q2(c1 asc); +create table q3(c1 int, c2 int, c3 int, primary key(c3 desc)); +insert into q3 select i, i, i from generate_series(0,999) i; + +/*+Leading((q3 q2))*/explain (costs off) select q3.c3, q2.c2 from q2, q3 where q3.c3 = q2.c1 order by 1 desc limit 10; +/*+Leading((q3 q2))*/select q3.c3, q2.c2 from q2, q3 where q3.c3 = q2.c1 order by 1 desc limit 10; + +drop index q2_c1_idx; +drop table q3; +CREATE TABLE q1nulls (a int, b int); +CREATE INDEX ON q1nulls (a ASC NULLS FIRST, b DESC NULLS LAST); +INSERT INTO q1nulls SELECT i/10, i % 10 from generate_series(1, 100) i; +INSERT INTO q1nulls VALUES (null, 9), (null, 8), (null, 8); +EXPLAIN (COSTS OFF) SELECT q1nulls.a, q1nulls.b FROM q1nulls, q2 WHERE q1nulls.b = q2.c2 ORDER BY q1nulls.a ASC LIMIT 10; +SELECT q1nulls.a, q1nulls.b FROM q1nulls, q2 WHERE q1nulls.b = q2.c2 ORDER BY q1nulls.a ASC LIMIT 10; + +EXPLAIN (COSTS OFF) SELECT q1nulls.a, q1nulls.b FROM q1nulls, q2 WHERE q1nulls.b = q2.c2 ORDER BY q1nulls.a ASC NULLS FIRST LIMIT 10; +SELECT q1nulls.a, q1nulls.b FROM q1nulls, q2 WHERE q1nulls.b = q2.c2 ORDER BY q1nulls.a ASC NULLS FIRST LIMIT 10; + +EXPLAIN (COSTS OFF) SELECT q1nulls.a, q1nulls.b FROM q1nulls, q2 WHERE q1nulls.b = q2.c2 ORDER BY q1nulls.a ASC NULLS FIRST, q1nulls.b DESC LIMIT 10; +SELECT q1nulls.a, q1nulls.b FROM q1nulls, q2 WHERE q1nulls.b = q2.c2 ORDER BY q1nulls.a ASC NULLS FIRST, q1nulls.b DESC LIMIT 10; + +EXPLAIN (COSTS OFF) SELECT q1nulls.a, q1nulls.b FROM q1nulls, q2 WHERE q1nulls.b = q2.c2 ORDER BY q1nulls.a ASC NULLS FIRST, q1nulls.b DESC NULLS LAST LIMIT 10; +SELECT q1nulls.a, q1nulls.b FROM q1nulls, q2 WHERE q1nulls.b = q2.c2 ORDER BY q1nulls.a ASC NULLS FIRST, q1nulls.b DESC NULLS LAST LIMIT 10; + +DROP TABLE q1nulls; + +create table q3(a int, b int, c name, primary key(a,b)); +create index q3_range on q3(a asc); + +/*+Set(enable_hashjoin off) Set(enable_mergejoin off) Set(yb_bnl_batch_size 3) Set(enable_seqscan off) Set(enable_material off)*/ explain (costs off) select * from q1 p1 left join (SELECT p2.c1 as a1, p3.a as a2 from q2 p2 join q3 p3 on true) j1 on j1.a1 = p1.c1; + +-- this should not be a batched NL join as it contains an unbatchable clause +-- (j1.a2 <= p1.c1) even though the batchable clause (j1.a1 = p1.c1) is also +-- present + +/*+Set(enable_hashjoin off) Set(enable_mergejoin off) Set(yb_bnl_batch_size 3) Set(enable_seqscan off) Set(enable_material off)*/ explain (costs off) select * from q1 p1 left join (SELECT p2.c1 as a1, p3.a as a2 from q2 p2 join q3 p3 on true) j1 on j1.a1 = p1.c1 and j1.a2 <= p1.c1; + +/*+Set(enable_hashjoin off) Set(enable_mergejoin off) Set(yb_bnl_batch_size 3) Set(enable_seqscan on) Set(enable_material off) Leading((q3 (q2 q1)))*/ explain (costs off) select * from q1, q2, q3 where q1.c1 = q2.c1 and q3.a = q1.c2; + +explain (costs off) SELECT * FROM q1, q2 where pg_backend_pid() >= 0 and q1.c1 = q2.c1; + +DROP TABLE q1; +DROP TABLE q2; +DROP TABLE q3; + +-- GHI #25917 +create table names (namespace_id int, name_id smallint, name_value text); +create index on names(name_id); +create table name_refs (namespace_id int, ref_names smallint[], ref_comment text); +insert into names values + (1, 1, 'One'), (1, 3, 'Three'), (1, 5, 'Five'), (1, 7, 'Seven'), (1, 9, 'Nine'), + (10, 1, 'Eleven'), (10, 4, 'Fourteen'), (10, 7, 'Seventeen'); +insert into name_refs values + (1, '{3, 7, 1}', 'All found'), (1, '{9, 2}', 'Some found'), (1, '{0, 2, 8, 4}', 'None found'), + (10, '{3, 7, 1}', 'Other namespace'), (10, '{}', 'Empty refs'); +/*+ NestLoop(r n) Leading((pos (r n))) IndexScan(n) Set(yb_bnl_batch_size 1024) */ +explain (analyze, costs off, timing off, summary off) +SELECT r.ref_comment, n.name_value +FROM names n, name_refs r, generate_series(1, 32) pos(i) +WHERE n.namespace_id = r.namespace_id AND n.name_id = r.ref_names[pos.i]; +drop table names; +drop table name_refs; + +create table ss1(a int, primary key(a asc)); +insert into ss1 select generate_series(1,5); +create table ss2(a int, b int, primary key(a asc, b asc)); +insert into ss2 select i, i from generate_series(1,5) i; +insert into ss2 select i, i+1 from generate_series(1,5) i; +analyze ss1; +analyze ss2; +/*+Set(enable_seqscan off) Set(yb_bnl_batch_size 1024) Leading((ss1 ss2))*/ explain (costs off) select * from ss1, ss2 where ss1.a = ss2.a order by ss1.a limit 10; +/*+Set(enable_seqscan off) Set(yb_bnl_batch_size 1024) Leading((ss1 ss2))*/ select * from ss1, ss2 where ss1.a = ss2.a order by ss1.a limit 10; + +drop table ss1; +drop table ss2; + +create table q1(a int, b int); +create table q2(a int, b int); +create index on q2(b, a); +insert into q1 values (1,2), (1,4), (6,7), (2,0), (10,24), (4,2); +insert into q2 values (2,1), (6,6), (2,4), (0,2), (1,2), (2,2), (4,2); + +/*+Leading((q1 q2))*/ explain (costs off) select * from q1,q2 where q1.a = q2.a and q1.b = q2.b order by q1.a; +/*+Leading((q1 q2))*/ select * from q1,q2 where q1.a = q2.a and q1.b = q2.b order by q1.a; + +drop table q1; +drop table q2; + +create table g1(h int, r int, primary key(h hash, r asc)); +create table g2(h int, r int, primary key(h hash, r asc)); +create table main(h1 int, h2 int, r1 int, r2 int, primary key((h1,h2) hash, r1 asc, r2 asc)); +insert into main select i/1000, (i/100) % 10, (i/10) % 10, i % 10 from generate_series(1,9999) i; +insert into g1 values (1,3), (5,7); +insert into g2 values (2,4), (6,8); + +/*+Leading((g1 (g2 main))) Set(enable_hashjoin off) Set(enable_mergejoin off) Set(yb_bnl_batch_size 3) Set(enable_material off) Set(enable_seqscan on)*/ explain (costs off) select main.* from g1,g2,main where main.h1 = g1.h and main.h2 = g2.h and main.r2 = g1.r and main.r1 = g2.r; + +/*+Leading((g1 (g2 main))) Set(enable_hashjoin off) Set(enable_mergejoin off) Set(yb_bnl_batch_size 3) Set(enable_material off) Set(enable_seqscan on)*/ select main.* from g1,g2,main where main.h1 = g1.h and main.h2 = g2.h and main.r2 = g1.r and main.r1 = g2.r; + +drop table g1; +drop table g2; +drop table main; + +create table oidtable(a oid, primary key(a asc)); +create table int4table(a int4, primary key(a asc)); +insert into oidtable select i from generate_series(1,20) i where i % 2 = 0; +insert into int4table select i from generate_series(1,20) i where i % 3 = 0; +/*+Set(enable_hashjoin off) Set(enable_mergejoin off) Set(yb_bnl_batch_size 3) Set(enable_seqscan off) Set(enable_material off) Leading((oidtable int4table))*/explain (costs off) select * from oidtable, int4table where oidtable. +a = int4table.a; +/*+Set(enable_hashjoin off) Set(enable_mergejoin off) Set(yb_bnl_batch_size 3) Set(enable_seqscan off) Set(enable_material off) Leading((oidtable int4table))*/ select * from oidtable, int4table where oidtable.a = int4table.a; +drop table oidtable; +drop table int4table; + +CREATE TABLE p_inner(a int, b text, PRIMARY KEY(b asc, a asc)); +CREATE TABLE p_outer(a bigint, b text); +INSERT INTO p_outer VALUES (0, '1'); +INSERT INTO p_inner VALUES (0, '1'); +EXPLAIN (COSTS OFF) SELECT * FROM p_outer, p_inner where p_outer.a = p_inner.a AND p_outer.b = p_inner.b; +SELECT * FROM p_outer, p_inner where p_outer.a = p_inner.a AND p_outer.b = p_inner.b; +DROP TABLE p_inner; +DROP TABLE p_outer; + +create table ss1(a int); +create table ss2(a int); +create table ss3(a int, b int, primary key(a asc, b asc)); + +-- Should not result in an illegal BNL on the inner side +/*+Leading((ss1 (ss2 ss3)))*/ explain (costs off) select * from ss1, ss2, ss3 where ss3.a = ss2.a and ss3.b = ss1.a and ss3.a <> ss2.a; +/*+Leading((ss1 (ss2 ss3)))*/ select * from ss1, ss2, ss3 where ss3.a = ss2.a and ss3.b = ss1.a and ss3.a <> ss2.a; +drop table ss1; +drop table ss2; +drop table ss3; + + +create table p1(a int, primary key(a asc)); +create table p2(a int, primary key(a asc)); +create table p3(a int, primary key(a asc)); + +insert into p1 select generate_series(1, 1000); +insert into p2 select generate_series(1, 1000); +insert into p3 select generate_series(1, 1000); + +analyze p1; +analyze p2; +analyze p3; + +set yb_prefer_bnl = off; + +-- The following hints try to force an illegal BNL +/*+YbBatchedNL(p1 p2 p3) YbBatchedNL(p2 p3) Leading((p1 (p2 p3))) IndexScan(p3)*/explain (costs off) select * from p1, p2, p3 where p1.a + 1 = p2.a and +p3.a = p1.a + p2.a; + +/*+YbBatchedNL(p1 p2 p3) NestLoop(p2 p3) Leading((p1 (p2 p3))) IndexScan(p3)*/explain (costs off) select * from p1, p2, p3 where p1.a + 1 = p2.a and +p3.a = p1.a + p2.a; + +/*+NestLoop(p1 p2 p3) NestLoop(p2 p3) Leading((p1 (p2 p3))) IndexScan(p3)*/explain (costs off) select * from p1, p2, p3 where p1.a + 1 = p2.a and +p3.a = p1.a + p2.a; + +-- This is a legal BNL +/*+YbBatchedNL(p1 p2 p3) Leading(((p1 p2) p3)) IndexScan(p3)*/explain (costs off) select * from p1, p2, p3 where p1.a + 1 = p2.a and +p3.a = p1.a + p2.a; + +set yb_prefer_bnl = on; + +drop table p1; +drop table p2; +drop table p3; + +-- Test the scenarios where parameterized column values from the outer most +-- loop (x1) are used at difference nesting levels (x2 and x3). +CREATE TABLE x1 (a int PRIMARY KEY, b int); +CREATE INDEX i_x1_b ON x1 (b); +INSERT INTO x1 VALUES (1, 0), (2, 1), (3, 0), (4, 1), (5, 2), (6, 3); + +CREATE TABLE x2 (a int PRIMARY KEY, b int); +CREATE INDEX i_x2_b ON x2 (b); +INSERT INTO x2 VALUES (1, 0), (2, 1), (3, 0), (4, 1); + +CREATE TABLE x3 (a int PRIMARY KEY, b int); +CREATE INDEX i_x3_b ON x3 (b); +INSERT INTO x3 VALUES (1, 0), (2, 1), (5, 2), (6, 3); + +ANALYZE x1; +ANALYZE x2; +ANALYZE x3; + +-- Before 8ac82f4247 (2.21.0.0-b227), the planner was producing incorrect plans +-- for the following queries, but the results happened to be correct with smaller +-- batch size e.g. 2, 3, etc. +SET yb_bnl_batch_size = 10; + +EXPLAIN (COSTS OFF) +SELECT * FROM x1 LEFT JOIN LATERAL ( + SELECT * FROM x2 LEFT JOIN LATERAL ( + SELECT x3.b FROM x3 WHERE x3.a = x1.a AND x3.b = x2.b LIMIT ALL + ) AS v1 ON true + WHERE x2.a = x1.a +) v2 ON true +ORDER BY 1, 2, 3, 4, 5; + +SELECT * FROM x1 LEFT JOIN LATERAL ( + SELECT * FROM x2 LEFT JOIN LATERAL ( + SELECT x3.b FROM x3 WHERE x3.a = x1.a AND x3.b = x2.b LIMIT ALL + ) AS v1 ON true + WHERE x2.a = x1.a +) v2 ON true +ORDER BY 1, 2, 3, 4, 5; + + +EXPLAIN (COSTS OFF) +SELECT * FROM x1 LEFT JOIN LATERAL ( + SELECT * FROM x2 LEFT JOIN x1 AS x4 ON x2.a = x4.a + LEFT JOIN LATERAL ( + SELECT x3.b FROM x3 WHERE x3.b = x1.b LIMIT ALL + ) AS v1 ON true + WHERE x2.a = x1.a +) v2 ON true +ORDER BY 1, 2, 3, 4, 5, 6, 7; + +SELECT * FROM x1 LEFT JOIN LATERAL ( + SELECT * FROM x2 LEFT JOIN x1 AS x4 ON x2.a = x4.a + LEFT JOIN LATERAL ( + SELECT x3.b FROM x3 WHERE x3.b = x1.b LIMIT ALL + ) AS v1 ON true + WHERE x2.a = x1.a +) v2 ON true +ORDER BY 1, 2, 3, 4, 5, 6, 7; + + +DROP TABLE x1; +DROP TABLE x2; +DROP TABLE x3; + +SET yb_bnl_batch_size = 3; +create table ss1(a varchar(1024), b char(10)); +create table ss2(a varchar(1024)); +create index on ss2 (a asc); +explain (costs off) select * from ss1 t1, ss2 t2 where t1.a = t2.a and t1.b = t2.a; +drop table ss1; +drop table ss2; + +CREATE TABLE ss1(a int); +CREATE TABLE ss2(a int); +CREATE TABLE ss3(a int, b int, c int, primary key(a asc, b asc)); + +/*+Set(enable_hashjoin OFF) Set(enable_mergejoin OFF) Set(enable_material OFF) Leading((ss2 (ss1 ss3_1))) +*/EXPLAIN (COSTS OFF) SELECT * +FROM ss1, ss2, ss3 +WHERE ss1.a = ss3.a and ss3.c <= (SELECT a FROM ss3 where a < ss2.a + ss1.a limit 1) and ss3.b <= ss2.a; + +/*+Set(enable_hashjoin OFF) Set(enable_mergejoin OFF) Set(enable_material OFF) Leading(((ss1 ss2) ss3)) +*/SELECT * +FROM ss1, ss2, ss3 +WHERE ss1.a = ss3.a and ss3.c <= (SELECT a FROM ss3 where a < ss2.a + ss1.a limit 1) and ss3.b <= ss2.a; + +DROP TABLE ss1; +DROP TABLE ss2; +DROP TABLE ss3; + +CREATE TABLE other(a int); +CREATE INDEX ON other(a asc); +CREATE TABLE ss1(a int); +CREATE TABLE ss2(a int, b int); +CREATE INDEX ON ss2(a asc, b asc); + +INSERT INTO ss1 VALUES (0), (0); +INSERT INTO ss2 VALUES (0, 0), (0, 0); +INSERT INTO other VALUES (1), (1); + +/*+Set(enable_hashjoin OFF) Set(enable_mergejoin OFF) Set(enable_material OFF) */EXPLAIN (COSTS OFF) +SELECT * +FROM ( + SELECT ss1.a as a, other.a as othera + FROM ss1 + CROSS JOIN other +) AS cross_join +LEFT OUTER JOIN ss2 ON ss2.a = ( + SELECT other.a + FROM other, ss1 + WHERE other.a = ss1.a + cross_join.a limit 1 +); + +/*+Set(enable_hashjoin OFF) Set(enable_mergejoin OFF) Set(enable_material OFF) */ +SELECT * +FROM ( + SELECT ss1.a as a, other.a as othera + FROM ss1 + CROSS JOIN other +) AS cross_join +LEFT OUTER JOIN ss2 ON ss2.a = ( + SELECT other.a + FROM other, ss1 + WHERE other.a = ss1.a + cross_join.a limit 1 +); + +DROP TABLE other; +DROP TABLE ss1; +DROP TABLE ss2; + + +-- Tests for EXPLAIN output of BNL +-- These tests ensure we don't resolve fieldnames for batched expressions in Index Cond +CREATE FUNCTION public.dummy(OUT a integer, OUT b integer) +RETURNS SETOF record +LANGUAGE sql +IMMUTABLE PARALLEL SAFE STRICT +AS 'SELECT 1, 1'; +EXPLAIN (COSTS OFF) SELECT 1 FROM pg_type t, (SELECT dummy() as x) AS ss WHERE t.oid = (ss.x).a; + +CREATE TABLE tbl (c1 INT, c2 INT, PRIMARY KEY (c1 ASC, c2 ASC)); +/*+Set(enable_hashjoin off) Set(enable_mergejoin off) Set(yb_bnl_batch_size 3) Set(enable_material off)*/ EXPLAIN (COSTS OFF) SELECT 1 FROM tbl, (SELECT dummy() as x) AS ss, (SELECT dummy() as x2) AS ss2 WHERE tbl.c1 = (ss.x).a AND tbl.c2 = (ss2.x2).a; + +CREATE TABLE tbl2 (c1 int, c2 int, PRIMARY KEY(c1 ASC, c2 ASC)); +/*+Set(enable_hashjoin off) Set(enable_mergejoin off) Set(yb_bnl_batch_size 3) Set(enable_material off) NestLoop(tbl tbl2) YbBatchedNL(ss tbl tbl2) IndexScan(tbl) SeqScan(tbl2)*/ +EXPLAIN (COSTS OFF) SELECT 1 FROM (SELECT dummy() as x) AS ss LEFT JOIN (SELECT tbl.c1 FROM tbl, tbl2) j1 ON j1.c1 = (ss.x).a; + +/*+Set(enable_mergejoin off) Set(enable_hashjoin off) Set(yb_bnl_batch_size 3) Set(enable_material off) YbBatchedNL(tbl2 ss tbl) NestLoop(ss tbl) IndexScan(tbl)*/ +EXPLAIN (COSTS OFF) SELECT 1 FROM (SELECT dummy() as x) AS ss, tbl, tbl2 WHERE tbl2.c1 = (ss.x).a AND (ss.x).a < 40 AND tbl.c1 = ANY(ARRAY[(ss.x).a, (ss.x).a]); + +create table ss1 (a int, b int); +create table ss2(a int, b int); +create table ss3(a int, b int); +create index on ss3(a asc); +insert into ss1 values (1,1), (1,2); +insert into ss2 values (1,1), (1,2); +insert into ss3 values (1,1), (1,3); + +explain (costs off) /*+Set(enable_hashjoin off) +Set(enable_material off) +Set(enable_mergejoin off) +Set(yb_bnl_batch_size 1024) +NestLoop(ss1 ss2) Rows(ss1 ss2 #1024)*/select ss1.*, p.* from ss1, ss2, ss3 p where ss1.a = ss2.a and ss1.b = ss2.b and p.b <= ss2.b + 1 and p.a = ss1.a order by 1,2,3,4; + +/*+Set(enable_hashjoin off) +Set(enable_material off) +Set(enable_mergejoin off) +Set(yb_bnl_batch_size 1024) +NestLoop(ss1 ss2) Rows(ss1 ss2 #1024)*/select ss1.*, p.* from ss1, ss2, ss3 p where ss1.a = ss2.a and ss1.b = ss2.b and p.b <= ss2.b + 1 and p.a = ss1.a order by 1,2,3,4; + +drop table ss1; +drop table ss2; +drop table ss3; + +-- 25651 BNL taking mixed heap and minimal tuples from the outer plan tree +create table ot1(k int primary key, v int); +create table ot2(k int, v int, primary key(k asc)); +create table it0(k int primary key, v int); +insert into ot1 values (11, 11), (33, 33), (55, 55), (77, 77), (99, 99); +insert into ot2 values (22, 22), (44, 44), (66, 66), (88, 88); +insert into it0 select i, i from generate_series(1, 10000) i; +analyze it0, ot1, ot2; +explain +select * from (select k, v from ot1 union all select k, v from ot2) s join it0 on s.k = it0.k order by s.k; +select * from (select k, v from ot1 union all select k, v from ot2) s join it0 on s.k = it0.k order by s.k; +drop table ot1; +drop table ot2; +drop table it0; + + +-- #25251: Ensure no Memoize under BNL +create table r (pk int primary key, a int); +create index on r (a); +insert into r select i, i from generate_series(1, 1000) i; + +create table s (pk int, x int, y int, primary key (pk asc)); +insert into s select i, i % 3, 2 from generate_series(1, 10) i; + +analyze r, s; + +explain (costs off) +/*+ Leading((s r)) */ +select * from s join r on a = x; + +explain (costs off) +/*+ Leading((s r)) */ +select * from s join r on a = y; + +-- Memoize under NL +explain (costs off) +/*+ Leading((s r)) NoYbBatchedNL(s r) */ +select * from s join r on a = x; + +explain (costs off) +/*+ Leading((s r)) NoYbBatchedNL(s r) */ +select * from s join r on a = y; + + +drop table r, s; diff --git a/src/test/regress/sql/yb.orig.join_batching_parallel.sql b/src/test/regress/sql/yb.orig.join_batching_parallel.sql new file mode 100644 index 0000000000000..9f709847cb414 --- /dev/null +++ b/src/test/regress/sql/yb.orig.join_batching_parallel.sql @@ -0,0 +1,201 @@ +-- +-- Test parallel BNL plan correctness +-- + +-- Create colocated tables so we can test parallel plans, too. +\c yugabyte +set client_min_messages = 'warning'; +drop database if exists colocateddb with (force); +create database colocateddb with colocation = on; +\c colocateddb + +create table r (a int, x char(10), data text, primary key(a)) +with (colocation = on); + +create table s (b int, data text, primary key(b)) +with (colocation = on); + +create table t (a int, b int, data text, primary key(a, b)) +with (colocation = on); + +create table u (a int, b int, c int, data text, primary key(a, b, c)) +with (colocation = on); + +insert into r + select i, rpad((i%2)::bpchar||(i%3)::bpchar, 10, '*'), lpad(i::bpchar, 5000, '#')::text + from generate_series(1, 100) i; + +insert into s select i, lpad(i::bpchar, 5000, '#')::text from generate_series(1, 200) i; + +insert into t + select r.a, s.b, lpad((r.a+s.b)::bpchar, 5000, '#')::text from r, s + where ((r.a + s.b * 5000) % (200000/10000)) = 0; + +insert into u + select i / 10, i / 10, i, lpad(i::text, 5000, '#')::text from generate_series(1, 1000) i; + +analyze r, s, t, u; + + +-- Force BNL by default +SET enable_hashjoin = off; +SET enable_mergejoin = off; +SET enable_seqscan = off; +SET enable_material = off; +SET yb_prefer_bnl = on; +SET enable_nestloop = off; + +SET yb_bnl_batch_size = 3; + + +set yb_enable_cbo = on; + + +set yb_parallel_range_rows = 10; +set yb_parallel_range_size = '1MB'; + +-- Make serial plans look more expensive +set yb_fetch_row_limit = 0; +set yb_fetch_size_limit = '1GB'; + +-- Workaround for https://github.com/yugabyte/yugabyte-db/issues/28510 +set join_collapse_limit=1; + + +-- +-- #28112: BNL joining `s` and `u` must not have mixture of batched and +-- unbatched variables from `r` and `t`. +-- +-- Note: join condition push down to the outer relation of inner BNL in +-- cascaded BNL setup not supported yet: +-- https://github.com/yugabyte/yugabyte-db/issues/28847 +-- + +-- Parallel plan +/*+ + Leading(((r t) (s u))) + IndexScan(r) + IndexScan(s) + IndexScan(t) + IndexScan(u) + YbBatchedNL(r t) + YbBatchedNL(s u) + YbBatchedNL(r s t u) +*/ +explain (costs off) +select t.a, s.b, u.c +from + (r join t on r.a = t.a) + join (s join u on s.b = u.b) on s.b = t.b and r.a = u.a +where r.x like '%0%'; + +-- Save the parallel execution results +create temporary table bnl_px (a int, b int, c int); +/*+ + Leading(((r t) (s u))) + IndexScan(r) + IndexScan(s) + IndexScan(t) + IndexScan(u) + YbBatchedNL(r t) + YbBatchedNL(s u) + YbBatchedNL(r s t u) +*/ +insert into bnl_px +select t.a, s.b, u.c +from + (r join t on r.a = t.a) + join (s join u on s.b = u.b) on s.b = t.b and r.a = u.a +where r.x like '%0%'; + +-- Check stats of the results +select count(*) nrows, + count(a) a_cnt, count(distinct a) a_ndv, min(a) a_min, max(a) a_max, + count(b) b_cnt, count(distinct b) b_ndv, min(b) b_min, max(b) b_max, + count(c) c_cnt, count(distinct c) c_ndv, min(c) c_min, max(c) c_max + from bnl_px; + + +-- Serial plan + +set max_parallel_workers = 0; +set max_parallel_workers_per_gather = 0; + +/*+ + Leading(((r t) (s u))) + IndexScan(r) + IndexScan(s) + IndexScan(t) + IndexScan(u) + YbBatchedNL(r t) + YbBatchedNL(s u) + YbBatchedNL(r s t u) +*/ +explain (costs off) +select t.a, s.b, u.c +from + (r join t on r.a = t.a) + join (s join u on s.b = u.b) on s.b = t.b and r.a = u.a +where r.x like '%0%'; + +-- Save the serial execution results +create temporary table bnl_sx (a int, b int, c int); +/*+ + Leading(((r t) (s u))) + IndexScan(r) + IndexScan(s) + IndexScan(t) + IndexScan(u) + YbBatchedNL(r t) + YbBatchedNL(s u) + YbBatchedNL(r s t u) +*/ +insert into bnl_sx +select t.a, s.b, u.c +from + (r join t on r.a = t.a) + join (s join u on s.b = u.b) on s.b = t.b and r.a = u.a +where r.x like '%0%'; + +reset max_parallel_workers; +reset max_parallel_workers_per_gather; + + +-- Cross-check the results +select * from bnl_px +except +select * from bnl_sx; + +select * from bnl_sx +except +select * from bnl_px; + + +-- Compare against non-BNL plan results +reset all; +set yb_enable_cbo = on; +set yb_bnl_batch_size = 1; + +explain (costs off) +select t.a, s.b, u.c +from + (r join t on r.a = t.a) + join (s join u on s.b = u.b) on s.b = t.b and r.a = u.a +where r.x like '%0%'; + +create temporary table nl (a int, b int, c int); + +insert into nl +select t.a, s.b, u.c +from + (r join t on r.a = t.a) + join (s join u on s.b = u.b) on s.b = t.b and r.a = u.a +where r.x like '%0%'; + +select * from nl +except +select * from bnl_sx; + +select * from bnl_sx +except +select * from nl; diff --git a/src/test/regress/sql/yb.orig.join_batching_plans.sql b/src/test/regress/sql/yb.orig.join_batching_plans.sql new file mode 100644 index 0000000000000..6bb2e774c0464 --- /dev/null +++ b/src/test/regress/sql/yb.orig.join_batching_plans.sql @@ -0,0 +1,306 @@ +-- +-- Test plan choices involving BNL +-- +CREATE TABLE p1 (a int, b int, c varchar, primary key(a,b)); +INSERT INTO p1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 2 = 0; +CREATE INDEX p1_b_idx ON p1 (b ASC); +ANALYZE p1; + +CREATE TABLE p2 (a int, b int, c varchar, primary key(a,b)); +INSERT INTO p2 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 3 = 0; +ANALYZE p2; + +CREATE TABLE p3 (a int, b int, c varchar, primary key(a,b)); +INSERT INTO p3 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 5 = 0; +ANALYZE p3; + +CREATE TABLE p4 (a int, b int, c varchar, primary key(a,b)); +INSERT INTO p4 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 7 = 0; +ANALYZE p4; + +CREATE TABLE p5 (a int, b int, c varchar, primary key(a asc,b asc)); +INSERT INTO p5 SELECT i / 10, i % 10, to_char(i, 'FM0000') FROM generate_series(0, 599) i; +CREATE INDEX p5_hash ON p5((a,b) hash); +CREATE INDEX p5_hash_asc ON p5(a hash, b asc); +ANALYZE p5; + +SET yb_enable_optimizer_statistics = on; +SET yb_enable_base_scans_cost_model = on; +SET yb_prefer_bnl = off; + +-- We're testing nested loop join batching in this file +SET yb_bnl_batch_size = 1024; + +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 JOIN p2 t2 ON t1.a = t2.a WHERE t1.a <= 100 AND t2.a <= 100; + +/*+NoYbBatchedNL(t1 t2)*/ EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 JOIN p2 t2 ON t1.a = t2.a WHERE t1.a <= 100 AND t2.a <= 100; + +/*+NestLoop(t1 t2)*/ EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 JOIN p2 t2 ON t1.a = t2.a WHERE t1.a <= 100 AND t2.a <= 100; + +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 JOIN p2 t2 ON t1.a = t2.a + 1 WHERE t1.a <= 100 AND t2.a <= 100; + +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 JOIN p2 t2 ON t1.a - 1 = t2.a + 1 WHERE t1.a <= 100 AND t2.a <= 100; + +-- Batching on compound clauses +/*+ Leading((p2 p1)) */ EXPLAIN (COSTS OFF) SELECT * FROM p1 JOIN p2 ON p1.a = p2.b AND p2.a = p1.b; + +/*+ Leading((p2 p1)) YbBatchedNL(p2 p1)*/ EXPLAIN (COSTS OFF) SELECT * FROM p1 JOIN p2 ON p1.a = p2.b AND p2.a = p1.b; + +explain (costs off) select * from p1 left join p5 on p1.a - 1 = p5.a and p1.b - 1 = p5.b where p1.a <= 30; + +/*+NoYbBatchedNL(p1 p5)*/explain (costs off) select * from p1 left join p5 on p1.a - 1 = p5.a and p1.b - 1 = p5.b where p1.a <= 30; + +/*+IndexScan(p5 p5_hash)*/explain (costs off) select * from p1 left join p5 on p1.a - 1 = p5.a and p1.b - 1 = p5.b where p1.a <= 30; + +/*+IndexScan(p5 p5_hash_asc)*/explain (costs off) select * from p1 left join p5 on p1.a - 1 = p5.a and p1.b - 1 = p5.b where p1.a <= 30; + +EXPLAIN (COSTS OFF) SELECT * FROM p1 JOIN p2 ON p1.a = p2.b AND p2.a = p1.b; + +CREATE TABLE t10 (r1 int, r2 int, r3 int, r4 int); + +INSERT INTO t10 + SELECT DISTINCT + i1, i2+5, i3, i4 + FROM generate_series(1, 5) i1, + generate_series(1, 5) i2, + generate_series(1, 5) i3, + generate_series(1, 10) i4; + +CREATE index i_t ON t10 (r1 ASC, r2 ASC, r3 ASC, r4 ASC); + +CREATE TABLE t11 (c1 int, c3 int, x int); +INSERT INTO t11 VALUES (1,2,0), (1,3,0), (5,2,0), (5,3,0), (5,4,0); + +CREATE TABLE t12 (c4 int, c2 int, y int); +INSERT INTO t12 VALUES (3,7,0),(6,9,0),(9,7,0),(4,9,0); +ANALYZE; + +EXPLAIN (COSTS OFF) SELECT t10.* FROM t12, t11, t10 WHERE x = y AND c1 = r1 AND c2 = r2 AND c3 = r3 AND c4 = r4 order by c1, c2, c3, c4; + +DROP TABLE t10; +DROP TABLE t11; +DROP TABLE t12; + +EXPLAIN (COSTS OFF) SELECT * FROM p3 t3 LEFT OUTER JOIN (SELECT t1.a as a FROM p1 t1 JOIN p2 t2 ON t1.a = t2.b WHERE t1.a <= 100 AND t2.a <= 100) s ON t3.a = s.a WHERE t3.a <= 30; + +EXPLAIN (COSTS OFF) SELECT * FROM p3 t3 RIGHT OUTER JOIN (SELECT t1.a as a FROM p1 t1 JOIN p2 t2 ON t1.a = t2.b WHERE t1.b <= 10 AND t2.b <= 15) s ON t3.a = s.a; + +/*+YbBatchedNL(t1 t2) Leading(((t1 t2) t3))*/ EXPLAIN (COSTS OFF) SELECT * FROM p3 t3 RIGHT OUTER JOIN (SELECT t1.a as a FROM p1 t1 JOIN p2 t2 ON t1.a = t2.b WHERE t1.b <= 10 AND t2.b <= 15) s ON t3.a = s.a; + +-- anti join-- +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 WHERE NOT EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.a) AND t1.a <= 40; + +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 WHERE NOT EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.b) AND t1.a <= 40; + +-- semi join-- +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 WHERE EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.a) AND t1.a <= 40; + +/*+NoYbBatchedNL(t1 t2)*/ EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 WHERE EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.a) AND t1.a <= 40; + +EXPLAIN (COSTS OFF) SELECT * FROM p1 t1 WHERE EXISTS (SELECT 1 FROM p2 t2 WHERE t1.a = t2.b) AND t1.a <= 40; + +explain (costs off) select * from p1 a join p2 b on a.a = b.a join p3 c on b.a = c.a join p4 d on a.b = d.b where a.b = 10 ORDER BY a.a, b.a, c.a, d.a; + +/*+NoYbBatchedNL(a c)*/ explain (costs off) select * from p1 a join p2 b on a.a = b.a join p3 c on b.a = c.a join p4 d on a.b = d.b where a.b = 10 ORDER BY a.a, b.a, c.a, d.a; + +CREATE INDEX p1_a_asc ON p1(a asc); +CREATE INDEX p2_a_asc ON p2(a asc); +ANALYZE; + +-- Since we don't have many rows in p1, p2 it isn't too bad to use the extra +-- sort operator imposed by nested loop join batching. +explain (costs off) select * from p1, p2 where p1.a = p2.a order by p2.a asc; + +/*+YbBatchedNL(p1 p2)*/ explain (costs off) select * from p1, p2 where p1.a = p2.a order by p2.a asc; + +INSERT INTO p1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(600, 200000) i WHERE i % 2 = 0; +INSERT INTO p2 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(600, 500000) i WHERE i % 3 = 0; +ANALYZE; + +-- After we have inserted many rows into each table, we expect that other +-- join methods that preserve the sort order of its input relations to be better. +explain (costs off) select * from p1, p2 where p1.a = p2.a order by p2.a asc; + +-- However, removing the ordering constraint in this query allows us to prefer +-- the batched nested loop join option again. +-- Commenting this test until CBO is updated. +-- explain (costs off) select * from p1, p2 where p1.a = p2.a; + +DROP TABLE p1; +DROP TABLE p2; +DROP TABLE p3; +DROP TABLE p4; +DROP TABLE p5; + +CREATE TABLE s1(r1 int, r2 int, r3 int); +CREATE TABLE s2(r1 int, r2 int, r3 int); +CREATE TABLE s3(r1 int, r2 int); +CREATE INDEX ON s3 (r1 asc, r2 asc); + +INSERT INTO s1 select i,i,i from generate_series(1,10) i; +INSERT INTO s2 select i,i,i from generate_series(1,10) i; +INSERT INTO s3 select i,i from generate_series(1,100) i; +ANALYZE; +explain (costs off) select s3.* from s1, s2, s3 where s3.r1 = s1.r1 and s3.r2 = s2.r2 and s1.r3 = s2.r3 order by s3.r1, s3.r2; + +DROP TABLE s3; +DROP TABLE s2; +DROP TABLE s1; + +create table s1(a int, primary key (a asc)); +create table s2(a int, primary key (a asc)); +create table s3(a int, primary key (a asc)); + +insert into s1 select generate_series(1,10); +insert into s2 select generate_series(1,10); +insert into s3 select generate_series(1,10); +ANALYZE; + +explain (costs off) /*+Leading(( ( s1 s2 ) s3 )) MergeJoin(s1 s2)*/select * from s1 left outer join s2 +on s1.a = s2.a left outer join s3 on s2.a = s3.a where s1.a < 5; + +drop table s1; +drop table s2; +drop table s3; + +create table test2 (a int, pp int, b int, pp2 int, c int, primary key(a asc, pp asc, b asc, pp2 asc, c asc)); +insert into test2 values (1,0, 2,0,1), (2,0, 3,0,3), (2,0,3,0,5); +create table test1 (a int, pp int, b int, pp2 int, c int, primary key(a asc, pp asc, b asc, pp2 asc, c asc)); +insert into test1 values (1,0,2,0,1), (1,0,2,0,2), (2,0,3,0,3), (2,0,4,0,4), (2,0,4,0,5), (2,0,4,0,6); +ANALYZE; +explain (costs off) /*+IndexScan(p2)*/ select * from test1 p1 join test2 p2 on p1.a = p2.a AND p1.b = p2.b AND p1.c = p2.c; +explain (costs off) /*+IndexScan(p2) YbBatchedNL(p1 p2)*/ select * from test1 p1 join test2 p2 on p1.a = p2.a AND p1.b = p2.b AND p1.c = p2.c; +drop table test1; +drop table test2; + +-- Test on unhashable join operations. These should use the tuplestore +-- strategy. +CREATE TABLE m1 (a money, primary key(a asc)); +INSERT INTO m1 SELECT i*2 FROM generate_series(1, 2000) i; + +CREATE TABLE m2 (a money, primary key(a asc)); +INSERT INTO m2 SELECT i*5 FROM generate_series(1, 2000) i; +ANALYZE; + +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM m1 t1 JOIN m2 t2 ON t1.a = t2.a WHERE t1.a <= 50::money; + +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM m2 t1 LEFT JOIN m1 t2 ON t1.a = t2.a WHERE t1.a <= 50::money; + +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM m2 t1 WHERE NOT EXISTS (SELECT 1 FROM m1 t2 WHERE t1.a = t2.a) AND t1.a <= 50::money; + +DROP TABLE m1; +DROP TABLE m2; + +create table q1 (c1 int, c2 int, primary key (c1 asc, c2 asc)); +create table q2 (c1 int, c2 int, primary key (c2 hash, c1 asc)); +insert into q1 select i, i / 4 from generate_series(0, 199) i; +insert into q2 select i, i / 2 from generate_series(0, 999) i; + +analyze q1; +analyze q2; + +-- Make sure a sort node is inserted above a batched NL join when appropriate + +explain (costs off) select q1.c1 from q1 join q2 on q1.c2 = q2.c2 order by q1.c1 limit 10; + +explain (costs off) select q2.c1, q1.c1 from q1 join q2 on q1.c2 = q2.c2 order by q1.c1 limit 10; + +create table q3(a int, b int, c name, primary key(a,b)); +create index q3_range on q3(a asc); + +explain (costs off) select * from q1 p1 left join (SELECT p2.c1 as a1, p3.a as a2 from q2 p2 join q3 p3 on true) j1 on j1.a1 = p1.c1; + +-- this should not be a batched NL join as it contains an unbatchable clause +-- (j1.a2 <= p1.c1) even though the batchable clause (j1.a1 = p1.c1) is also +-- present + +explain (costs off) select * from q1 p1 left join (SELECT p2.c1 as a1, p3.a as a2 from q2 p2 join q3 p3 on true) j1 on j1.a1 = p1.c1 and j1.a2 <= p1.c1; + +DROP TABLE q1; +DROP TABLE q2; +DROP TABLE q3; + +create table tab1 (id int primary key, r1 int); +insert into tab1 select generate_series(1,1000); +analyze tab1; +create table tab2 (id int primary key, r1 int, r2 int not null) partition by range (id); +create table tab2_p0 partition of tab2 default; +create table tab2_p1 partition of tab2 for values from (minvalue) to (10); +create table tab2_p2 partition of tab2 for values from (10) to (20); +create table tab2_p3 partition of tab2 for values from (20) to (maxvalue); +create index i_tab2_r2 on tab2 (r2 asc); + +SET yb_bnl_batch_size = 1024; + +explain (costs off) /*+ Leading((tab1 tab2)) IndexScan(tab2) NestLoop(tab1 tab2) */ select * from tab1 join tab2 on tab1.r1 = tab2.r2; +drop table tab2; +drop table tab1; + +create table tab1(c1 int primary key); +insert into tab1 select generate_series(1, 10000); +create table tab2(c1 int, c2 int); +insert into tab2 select i/10, i%10 from generate_series(1, 100000) i; +create index on tab2 (c1); +analyze tab1; +analyze tab2; +explain (costs off) /*+ Leading((tab1 tab2)) YbBatchedNL(tab1 tab2)*/ select * from tab1, tab2 where tab1.c1 = tab2.c1; +drop table tab1; +drop table tab2; + +CREATE TABLE t_part (i int, a int, b int) PARTITION BY RANGE (i); +CREATE TABLE t_part1 PARTITION OF t_part FOR VALUES FROM (1) TO (200); +CREATE TABLE t_part2 PARTITION OF t_part FOR VALUES FROM (200) TO (400); +CREATE TABLE t_part3 PARTITION OF t_part FOR VALUES FROM (400) TO (600); +CREATE TABLE t_part4 PARTITION OF t_part FOR VALUES FROM (600) TO (MAXVALUE); + +CREATE INDEX ON t_part (b); + +INSERT INTO t_part SELECT (i % 200) + 1, ((i-1) / 4) * 4, i FROM generate_series(1, 2500) i; +INSERT INTO t_part SELECT (i % 200) + 201, ((i-1) / 4) * 4 + 1, i FROM generate_series(1, 2500) i; +INSERT INTO t_part SELECT (i % 200) + 401, ((i-1) / 4) * 4 + 2, i FROM generate_series(1, 2500) i; +INSERT INTO t_part SELECT (i % 200) + 601, ((i-1) / 4) * 4 + 3, i FROM generate_series(1, 2500) i; + +CREATE TABLE t_nopart (i int, a int, b int); +INSERT INTO t_nopart SELECT * FROM t_part; + +ANALYZE t_part; +ANALYZE t_nopart; + +/*+ + Leading((np p)) + IndexScan(p) + Set(yb_enable_batchednl on) + Set(yb_bnl_batch_size 1024) + Set(enable_nestloop off) + Set(enable_hashjoin off) + Set(enable_mergejoin off) + Set(enable_material off) + Set(yb_prefer_bnl on) +*/ +EXPLAIN (COSTS OFF)SELECT * FROM t_nopart np JOIN t_part p ON np.a = p.b and np.b < 1000 and p.i <= 201; + +-- Test BNL with temporary tables +-- BNL should not be used when inner relation is temp +-- BNL should work fine when outer relation is temp + +CREATE TABLE reg_bnl (a int, b int, PRIMARY KEY(a ASC)); +INSERT INTO reg_bnl SELECT i, i % 25 FROM generate_series(1, 100) i; +CREATE INDEX reg_bnl_b_idx ON reg_bnl(b ASC); + +CREATE TEMP TABLE tmp_bnl (a int, b int); +INSERT INTO tmp_bnl SELECT i, i % 25 FROM generate_series(1, 100) i; +CREATE INDEX tmp_bnl_b_idx ON tmp_bnl(b ASC); + +ANALYZE reg_bnl; +ANALYZE tmp_bnl; + +-- Case 1: Inner is temp table - should NOT use BNL +/*+YbBatchedNL(reg_bnl tmp_bnl) Leading((reg_bnl tmp_bnl))*/ +EXPLAIN (COSTS OFF) SELECT * FROM reg_bnl JOIN tmp_bnl ON reg_bnl.b = tmp_bnl.b WHERE reg_bnl.a <= 50; + +-- Case 2: Outer is temp table - BNL should work fine +/*+YbBatchedNL(tmp_bnl reg_bnl) Leading((tmp_bnl reg_bnl))*/ +EXPLAIN (COSTS OFF) SELECT * FROM tmp_bnl JOIN reg_bnl ON tmp_bnl.b = reg_bnl.b WHERE tmp_bnl.a <= 50; + +DROP TABLE tmp_bnl; +DROP TABLE reg_bnl; diff --git a/src/test/regress/sql/yb.orig.large_copy.sql b/src/test/regress/sql/yb.orig.large_copy.sql new file mode 100644 index 0000000000000..9844462357e61 --- /dev/null +++ b/src/test/regress/sql/yb.orig.large_copy.sql @@ -0,0 +1,9 @@ +-- +-- COPY large file +-- + +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +\set filename :abs_srcdir '/data/airport-codes.csv' +COPY airports FROM :'filename' CSV HEADER; diff --git a/src/test/regress/sql/yb.orig.large_copy_abort.sql b/src/test/regress/sql/yb.orig.large_copy_abort.sql new file mode 100644 index 0000000000000..b3138044d9764 --- /dev/null +++ b/src/test/regress/sql/yb.orig.large_copy_abort.sql @@ -0,0 +1,32 @@ +-- +-- COPY large corrupt file +-- + +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +TRUNCATE TABLE airports; + +-- should fail once it reaches corrupt input on last line +\set filename :abs_srcdir '/data/airport-codes-corrupt.csv' +COPY airports FROM :'filename' CSV HEADER; + +-- table should be empty +SELECT COUNT(*) FROM airports; + +-- +-- Verify COPY fails if duplicate key error is hit. +-- +\set filename :abs_srcdir '/data/airport-codes.csv' +COPY airports FROM :'filename' CSV HEADER; +DELETE FROM airports WHERE ident != '9LA6'; + +-- should fail with duplicate key error +\set filename :abs_srcdir '/data/airport-codes.csv' +COPY airports FROM :'filename' CSV HEADER; + +-- table should just have one row +SELECT COUNT(*) FROM airports; + +-- prepare for next tests +TRUNCATE TABLE airports; diff --git a/src/test/regress/sql/yb.orig.large_copy_ddl.sql b/src/test/regress/sql/yb.orig.large_copy_ddl.sql new file mode 100644 index 0000000000000..04611890c9c35 --- /dev/null +++ b/src/test/regress/sql/yb.orig.large_copy_ddl.sql @@ -0,0 +1,16 @@ +-- +-- COPY large file while performing DDL in same txn +-- + +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +BEGIN; + +CREATE INDEX ON airports (type); +CREATE INDEX ON airports (name); + +\set filename :abs_srcdir '/data/airport-codes.csv' +COPY airports FROM :'filename' CSV HEADER; + +COMMIT; diff --git a/src/test/regress/sql/yb.orig.legacy_join_plans.sql b/src/test/regress/sql/yb.orig.legacy_join_plans.sql new file mode 100644 index 0000000000000..7cae09b241a2e --- /dev/null +++ b/src/test/regress/sql/yb.orig.legacy_join_plans.sql @@ -0,0 +1,149 @@ +-- +-- Legacy mode Join Plans +-- + +set client_min_messages = 'warning'; + +drop table if exists t1, t2, t2r; + +create table t1 (id int, a int); +create unique index nonconcurrently i_t1_id on t1 (id asc); +insert into t1 select i, i / 4 from generate_series(1, 1000) i; + +create table t2 (k1 int, k2 int, k3 int, k4 int, k5 int, v boolean, primary key (k1 hash)); +create unique index nonconcurrently i_t2_k2 on t2 (k2 hash); +create index nonconcurrently i_t2_k3 on t2 (k3 hash); +create unique index nonconcurrently i_t2_k4k5 on t2 (k4 hash, k5 asc); +create index nonconcurrently i_t2_v on t2 (v hash); +insert into t2 select i, i, i % 10000, i % 10000, (i / 10000) * 10000, (i%2) = 1 from generate_series(1, 50000) i; + +create table t2r (k1 int, k2 int, k3 int, k4 int, k5 int, v boolean, primary key (k1 asc)); +create unique index nonconcurrently i_t2r_k2 on t2r (k2 asc); +create index nonconcurrently i_t2r_k3 on t2r (k3 asc); +create unique index nonconcurrently i_t2r_k4k5 on t2r (k4 asc, k5 asc); +create index nonconcurrently i_t2r_v on t2r (v asc); +insert into t2r select i, i, i % 10000, i % 10000, (i / 10000) * 10000, (i%2) = 1 from generate_series(1, 50000) i; + + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/legacy_join_plan_queries.sql' +\set costsfilename :abs_srcdir '/yb_commands/legacy_join_plan_queries_with_costs.sql' + +set yb_ignore_bool_cond_for_legacy_estimate = on; + +-- Set GUC parameters to restore PG11 behavior +set enable_memoize = false; +set hash_mem_multiplier = 1.0; + +---------------- +-- Unanalyzed -- +---------------- + +-- +-- Pre-2024.1 behavior (20.2.x and earlier) +-- + +set yb_enable_cbo = legacy_bnl_mode; + +-- Pre-2024.1 defaults +set yb_bnl_batch_size = 1; +set yb_prefer_bnl = off; +\i :filename + +-- BNL enabled +set yb_bnl_batch_size = 1024; +set yb_prefer_bnl = on; +\i :filename + + +-- +-- 2024.1 and beyond +-- + +set yb_enable_cbo = legacy_mode; + +-- BNL enabled by default since 2024.1 +set yb_bnl_batch_size = 1024; +set yb_prefer_bnl = on; +\i :filename + + +-------------- +-- Analyzed -- +-------------- + +analyze t1; +analyze t2; +analyze t2r; + +-- +-- Pre-2024.1 behavior (20.2.x and earlier) +-- + +set yb_enable_cbo = legacy_ignore_stats_bnl_mode; + +-- Pre-2024.1 defaults +set yb_bnl_batch_size = 1; +set yb_prefer_bnl = off; +\i :filename + +set yb_bnl_batch_size = 1024; +set yb_prefer_bnl = on; +\i :filename + + +set yb_enable_cbo = legacy_stats_bnl_mode; + +-- Pre-2024.1 defaults +set yb_bnl_batch_size = 1; +set yb_prefer_bnl = off; +\i :filename + +set yb_bnl_batch_size = 1024; +set yb_prefer_bnl = on; +\i :filename + + +-- +-- 2024.1 and beyond +-- + +set yb_enable_cbo = off; + +set yb_bnl_batch_size = 1024; +set yb_prefer_bnl = on; +\i :filename + +set yb_bnl_batch_size = 1; +set yb_prefer_bnl = off; +\i :filename + + +set yb_enable_cbo = legacy_stats_mode; + +set yb_bnl_batch_size = 1; +set yb_prefer_bnl = off; +\i :filename + +set yb_bnl_batch_size = 1024; +set yb_prefer_bnl = on; +\i :filename + + +-- +-- not only the plans but the cost/row count estimates should never change +-- + +-- Pre-2024.1, BNL enabled +set yb_enable_cbo = legacy_ignore_stats_bnl_mode; +set yb_bnl_batch_size = 1024; +set yb_prefer_bnl = on; +\i :costsfilename + + +-- 2024.1 and beyond +set yb_enable_cbo = off; +\i :costsfilename + + +drop table t1, t2, t2r; diff --git a/src/test/regress/sql/yb.orig.levenshtein_pushdown.sql b/src/test/regress/sql/yb.orig.levenshtein_pushdown.sql new file mode 100644 index 0000000000000..5edbcbd3269f1 --- /dev/null +++ b/src/test/regress/sql/yb.orig.levenshtein_pushdown.sql @@ -0,0 +1,180 @@ +-- Test levenshtein function pushdown to DocDB +-- Verifies that all levenshtein variants use Storage Filter and return correct results. + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)' + +CREATE TABLE test_levenshtein (id SERIAL PRIMARY KEY, name TEXT); +INSERT INTO test_levenshtein (name) VALUES ('GUMBO'), ('GAMBOL'), ('JUMBO'), ('BUMBO'), ('HELLO'), ('WORLD'); + +-- Test levenshtein(text, text) push down +\set query ':P SELECT name FROM test_levenshtein WHERE levenshtein(name, ''GUMBO'') < 3 ORDER BY name;' +\i :iter_P2 + +-- Test levenshtein(text, text, int, int, int) push down +\set query ':P SELECT name FROM test_levenshtein WHERE levenshtein(name, ''GUMBO'', 1, 1, 2) < 3 ORDER BY name;' +\i :iter_P2 + +-- Test levenshtein_less_equal(text, text, int) push down +\set query ':P SELECT name FROM test_levenshtein WHERE levenshtein_less_equal(name, ''GUMBO'', 2) <= 2 ORDER BY name;' +\i :iter_P2 + +-- Test levenshtein_less_equal(text, text, int, int, int, int) push down +\set query ':P SELECT name FROM test_levenshtein WHERE levenshtein_less_equal(name, ''GUMBO'', 1, 1, 2, 2) <= 2 ORDER BY name;' +\i :iter_P2 + +-- Test exact match (distance = 0) +\set query ':P SELECT name FROM test_levenshtein WHERE levenshtein(name, ''GUMBO'') = 0;' +\i :iter_P2 + +-- Test no matches case +\set query ':P SELECT name FROM test_levenshtein WHERE levenshtein(name, ''XXXXX'') < 2;' +\i :iter_P2 + +-- Test SELECT with levenshtein function call +\set query ':P SELECT levenshtein(''GUMBO'', ''GAMBOL'');' +\i :iter_P2 + +-- Test levenshtein in complex expression +\set query ':P SELECT name FROM test_levenshtein WHERE levenshtein(name, ''GUMBO'') + length(name) < 10 ORDER BY name;' +\i :iter_P2 + +-- Create fuzzystrmatch extension with levenshtein functions in the public schema +-- This version of levenshtein will not be pushed down to DocDB +CREATE EXTENSION IF NOT EXISTS fuzzystrmatch SCHEMA public; + +-- Test levenshtein with schema qualified +\set query ':P SELECT name FROM test_levenshtein WHERE public.levenshtein(name, ''GUMBO'') < 3 ORDER BY name;' +\i :iter_P2 + +-- Test levenshtein with setting search_path +-- There is a default prioritized scan of pg_catalog over other schemas, +-- so levenshtein functions will still be pushed down when search_path is set to public +SET search_path = public; +\set query ':P SELECT name FROM test_levenshtein WHERE levenshtein(name, ''GUMBO'') < 3 ORDER BY name;' +\i :iter_P2 +-- If pg_catalog is explicitly put after other schemas in the search_path, +-- levenshtein calls will then refer to the non built-in version +SET search_path = public, pg_catalog; +\set query ':P SELECT name FROM test_levenshtein WHERE levenshtein(name, ''GUMBO'') < 3 ORDER BY name;' +\i :iter_P2 + +-- Cleanup +RESET search_path; +DROP TABLE test_levenshtein; + +-- Test LIMIT optimization with levenshtein pushdown (from github #29299) +-- This tests early exit when LIMIT is present with pushed-down filter + +-- Helper function to normalize text for fuzzy matching +CREATE OR REPLACE FUNCTION sort_words(t text) +RETURNS text +LANGUAGE sql +IMMUTABLE +RETURNS NULL ON NULL INPUT +AS $$ +WITH tokens AS ( + SELECT lower(x) AS tok + FROM regexp_split_to_table(t, E'\\s+') AS x + WHERE x <> '' + ORDER BY 1 +) +SELECT string_agg(tok, ' ') +FROM tokens +$$; + +DROP TABLE IF EXISTS t1 CASCADE; + +CREATE TABLE t1 ( + id bigserial PRIMARY KEY, + c1 int NOT NULL, + c2 timestamptz NOT NULL, + c3 text, + c4 text +); + +INSERT INTO t1 (c1, c2, c3, c4) +SELECT + 100, + ts, + n, + sort_words(n) +FROM ( + SELECT + (timestamp '2025-03-01' + (g*interval '1 minute')) AS ts, + CASE (g % 12) + WHEN 0 THEN 'body95855317' + WHEN 1 THEN '95855317 body' + WHEN 2 THEN 'b0dy95855317' + WHEN 3 THEN 'body 95855317' + WHEN 4 THEN 'bod y95855317' + WHEN 5 THEN 'john doe' + WHEN 6 THEN 'mary jane' + WHEN 7 THEN 'body9585531' + WHEN 8 THEN 'buddy95855317' + WHEN 9 THEN '95855317body' + WHEN 10 THEN 'alpha beta' + ELSE 'totally different' + END AS n + FROM generate_series(0, 9999) AS g +) s +WHERE ts < '2025-09-01'; + +INSERT INTO t1 (c1, c2, c3, c4) +SELECT + 200, + ts, + n, + sort_words(n) +FROM ( + SELECT + (timestamp '2025-03-01' + (g*interval '2 minute')) AS ts, + CASE (g % 6) + WHEN 0 THEN 'other customer' + WHEN 1 THEN 'alice' + WHEN 2 THEN 'bob' + WHEN 3 THEN 'charlie' + WHEN 4 THEN 'david' + ELSE 'eve' + END AS n + FROM generate_series(0, 3000) AS g +) s +WHERE ts < '2025-09-01'; + +-- Test expression index with levenshtein uses Index Cond without Sort +CREATE INDEX t1_levenshtein_idx ON t1 (levenshtein(c4, 'body95855317') asc); +\set query ':P SELECT c3 FROM t1 WHERE levenshtein(c4, ''body95855317'') < 3 ORDER BY levenshtein(c4, ''body95855317'') LIMIT 5;' +\i :iter_P2 +DROP INDEX t1_levenshtein_idx; + +CREATE INDEX ON t1 (c1 asc, c2); +CREATE INDEX ON t1 (c1 asc, c2, c3); + +-- This query should use Index Scan with Storage Index Filter and exit early due to LIMIT +SELECT $$ +:P +SELECT count(x.agg_field) AS aggr +FROM ( + SELECT t.c3 AS agg_field + FROM t1 AS t + WHERE t.c1 = 100 + AND t.c3 IS NOT NULL + AND t.c3 != '' + AND t.c3 != 'body95855317' + AND ((length('body95855317') + length(t.c3) + - levenshtein(sort_words('body95855317'), t.c4))::float + / (length('body95855317') + length(t.c3)))::float >= 0.85 + AND t.c2 >= '2025-03-01' + AND t.c2 < '2025-09-01' + LIMIT 3 +) AS x; +$$ AS query \gset +\i :iter_P2 + +-- Cleanup +DROP TABLE t1 CASCADE; +DROP FUNCTION sort_words(text); diff --git a/src/test/regress/sql/yb.orig.local_tablets.sql b/src/test/regress/sql/yb.orig.local_tablets.sql new file mode 100644 index 0000000000000..0de37c581011f --- /dev/null +++ b/src/test/regress/sql/yb.orig.local_tablets.sql @@ -0,0 +1,12 @@ +CREATE TABLE test_table (k INT PRIMARY KEY, v INT) SPLIT INTO 2 TABLETS; + +SELECT + table_name, + namespace_name, + table_type, + ysql_schema_name, + partition_key_start, + partition_key_end +FROM yb_local_tablets +WHERE table_name = 'test_table' +ORDER BY partition_key_start NULLS FIRST; diff --git a/src/test/regress/sql/yb.orig.lock_row_for_update.sql b/src/test/regress/sql/yb.orig.lock_row_for_update.sql new file mode 100644 index 0000000000000..780c4d7a47cd1 --- /dev/null +++ b/src/test/regress/sql/yb.orig.lock_row_for_update.sql @@ -0,0 +1,77 @@ +CREATE TABLE yb_row_locking +( + hk int, + rk int, + v1 int, + v2 int, + PRIMARY KEY(hk HASH, rk) +); + +CREATE OR REPLACE FUNCTION get_lock_status() +RETURNS TABLE( + locktype text, + relation text, + mode text[], + granted boolean, + is_explicit boolean, + hash_cols text[], + range_cols text[], + attnum smallint, + has_column_id bool, + multiple_rows_locked boolean +) AS $$ +BEGIN + RETURN QUERY + SELECT l.locktype, + l.relation::regclass::text, + l.mode, + l.granted, + l.is_explicit, + l.hash_cols, + l.range_cols, + l.attnum, + CASE WHEN l.column_id IS NOT NULL THEN true ELSE false END, + l.multiple_rows_locked + FROM yb_lock_status(null, null) l + ORDER BY l.relation::regclass::text, l.transaction_id, l.hash_cols NULLS FIRST, + l.range_cols NULLS FIRST, l.column_id NULLS FIRST; +END; +$$ LANGUAGE plpgsql; + +INSERT INTO yb_row_locking VALUES(1,1,1,1); + +-- When updating a row in any isolation level it should result in a STRONG_READ (row lock) on the +-- primary key. +BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; +UPDATE yb_row_locking SET v1 = 2 WHERE hk = 1 AND rk = 1; +SELECT * FROM get_lock_status(); +COMMIT; + +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; +UPDATE yb_row_locking SET v1 = 3 WHERE hk = 1 AND rk = 1; +SELECT * FROM get_lock_status(); +COMMIT; + +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +UPDATE yb_row_locking SET v1 = 4 WHERE hk = 1 AND rk = 1; +SELECT * FROM get_lock_status(); +COMMIT; + +-- When a value isn't updated we should not write locks to the intentdb in REPEATABLE READ and READ +-- COMMITTED. +BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; +UPDATE yb_row_locking SET v1 = 2 WHERE hk = 2 AND rk = 2; +SELECT * FROM get_lock_status(); -- no locks +COMMIT; + +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; +UPDATE yb_row_locking SET v1 = 3 WHERE hk = 2 AND rk = 2; +SELECT * FROM get_lock_status(); -- no locks +COMMIT; + +-- In SERIALIZABLE we need to lock the rows that we have attempted to read even when nothing has +-- been updated +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +UPDATE yb_row_locking SET v1 = 4 WHERE hk = 2 AND rk = 2; +SELECT * FROM get_lock_status(); -- lock +COMMIT; diff --git a/src/test/regress/sql/yb.orig.lock_status.sql b/src/test/regress/sql/yb.orig.lock_status.sql new file mode 100644 index 0000000000000..a8decb364ac15 --- /dev/null +++ b/src/test/regress/sql/yb.orig.lock_status.sql @@ -0,0 +1,266 @@ +CREATE TABLE yb_lock_tests +( + k1 int, + k2 int, + r1 int, + r2 text, + v1 text, + v2 text, + PRIMARY KEY((k1, k2) HASH, r1,r2) +) SPLIT INTO 2 TABLETS; + +CREATE UNIQUE INDEX yb_lock_tests_k1_k2 ON yb_lock_tests (k1,k2) SPLIT INTO 2 TABLETS; + +CREATE FUNCTION is_between_now_and_clock_timestamp(input_time timestamptz) +RETURNS boolean +AS $$ +BEGIN + RETURN input_time >= now() AND input_time <= clock_timestamp(); +END; +$$ LANGUAGE plpgsql; + + +CREATE +OR REPLACE FUNCTION validate_and_return_lock_status(input_relation oid, input_transaction_id uuid, + OUT locktype text, + OUT relation text, OUT mode text[], OUT granted boolean, + OUT fastpath boolean, OUT valid_waitstart boolean, + OUT valid_waitend boolean, OUT has_node boolean, + OUT has_tablet_id boolean, + OUT has_transaction_id boolean, + OUT valid_subtransaction_id boolean, + OUT has_status_tablet_id boolean, + OUT is_explicit boolean, + OUT hash_cols text[], + OUT range_cols text[], OUT attnum smallint, OUT column_id integer, + OUT multiple_rows_locked boolean, OUT num_blocking int4) + RETURNS SETOF record +AS +$$ +DECLARE + difference record; +BEGIN + FOR difference IN + SELECT + l.locktype, + l.database, + l.relation, + l.pid, + array_to_string(l.mode, ','), + l.granted, + l.fastpath, + l.waitstart, + l.waitend, + CASE WHEN l.node IS NOT NULL THEN to_jsonb(l.node) ELSE 'null'::jsonb END AS node, + CASE WHEN l.tablet_id IS NOT NULL THEN to_jsonb(l.tablet_id) ELSE 'null'::jsonb END AS tablet_id, + CASE WHEN l.transaction_id IS NOT NULL THEN to_jsonb(l.transaction_id) ELSE 'null'::jsonb END AS transaction_id, + CASE WHEN l.subtransaction_id IS NOT NULL THEN to_jsonb(l.subtransaction_id) ELSE 'null'::jsonb END AS subtransaction_id, + CASE WHEN l.is_explicit IS NOT NULL THEN to_jsonb(l.is_explicit) ELSE 'null'::jsonb END AS is_explicit, + CASE WHEN l.hash_cols IS NOT NULL OR l.range_cols IS NOT NULL THEN to_jsonb(l.hash_cols || l.range_cols) ELSE 'null'::jsonb END AS cols, + CASE WHEN l.attnum IS NOT NULL THEN to_jsonb(l.attnum) ELSE 'null'::jsonb END AS attnum, + CASE WHEN l.column_id IS NOT NULL THEN to_jsonb(l.column_id) ELSE 'null'::jsonb END AS column_id, + CASE WHEN l.multiple_rows_locked IS NOT NULL THEN to_jsonb(l.multiple_rows_locked) ELSE 'null'::jsonb END AS multiple_rows_locked, + CASE WHEN l.blocked_by IS NOT NULL THEN to_jsonb(l.blocked_by) ELSE 'null'::jsonb END AS blocked_by + FROM + yb_lock_status(null, null) l + EXCEPT + SELECT + p.locktype, + p.database, + p.relation, + p.pid, + p.mode, + p.granted, + p.fastpath, + p.waitstart, + p.waitend, + p.ybdetails->'node', + p.ybdetails->'tablet_id', + p.ybdetails->'transactionid', + p.ybdetails->'subtransaction_id', + p.ybdetails->'is_explicit', + p.ybdetails->'keyrangedetails'->'cols', + p.ybdetails->'keyrangedetails'->'attnum', + p.ybdetails->'keyrangedetails'->'column_id', + p.ybdetails->'keyrangedetails'->'multiple_rows_locked', + p.ybdetails->'blocked_by' + FROM pg_locks p + LOOP + RAISE EXCEPTION 'There is a difference in the output of pg_locks and yb_lock_status. The difference is: %', difference; + END LOOP; + + RETURN QUERY SELECT l.locktype, + l.relation::regclass::text, + l.mode, + l.granted, + l.fastpath, + is_between_now_and_clock_timestamp(l.waitstart) as valid_waitstart, + is_between_now_and_clock_timestamp(l.waitend) as valid_waitend, + CASE WHEN l.node IS NOT NULL THEN true ELSE false END as has_node, + CASE WHEN l.tablet_id IS NOT NULL THEN true ELSE FALSE END as has_tablet_id, + CASE WHEN l.transaction_id IS NOT NULL THEN true ELSE FALSE END as has_transaction_id, + (l.subtransaction_id > 0) as valid_subtransaction_id, + CASE WHEN l.status_tablet_id IS NOT NULL THEN true ELSE FALSE END as has_status_tablet_id, + l.is_explicit, + l.hash_cols, + l.range_cols, + l.attnum, + l.column_id, + l.multiple_rows_locked, + array_length(l.blocked_by, 1) + -- TODO: Add the relation arg when we support querying by relation + FROM yb_lock_status(null, input_transaction_id) l + WHERE l.relation = input_relation + ORDER BY l.relation::regclass::text, l.transaction_id, l.hash_cols NULLS FIRST, + l.range_cols NULLS FIRST, l.column_id NULLS FIRST; +END ; +$$ LANGUAGE plpgsql; + +-- Basic queries +SELECT true::bool FROM yb_lock_status(null, null); +SELECT true::bool FROM yb_lock_status('yb_lock_tests'::regclass, null); +SELECT true::bool FROM yb_lock_status('yb_lock_tests'::regclass::int4, null); +SELECT true::bool FROM yb_lock_status(null, 'bogus'); +SELECT true::bool FROM yb_lock_status(null, '10000000-2000-3000-1000-400000000000'); +SELECT true::bool FROM yb_lock_status('yb_lock_tests'::regclass, '10000000-2000-3000-1000-400000000000'); + +-- READ COMMITTED +-- Basic insert +BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; +INSERT INTO yb_lock_tests VALUES (1, 1, 1, 'one', 1, 1); +INSERT INTO yb_lock_tests VALUES (2, 2, 2, 'two', 2, 2); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +COMMIT; + +-- Basic Column Update +BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; +UPDATE yb_lock_tests SET v1 = 2 WHERE k1 = 1 AND k2 = 1; +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +ABORT; + +-- Basic primary key update +BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; +UPDATE yb_lock_tests SET r1 = 2 WHERE k1 = 1 AND k2 = 1; +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +ABORT; + +BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; +UPDATE yb_lock_tests SET k2 = 2 WHERE k1 = 1 AND k2 = 1; +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +ABORT; + +-- SELECT FOR SHARE +BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; +SELECT * FROM yb_lock_tests FOR SHARE; +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +COMMIT; + +-- SELECT FOR KEY SHARE +BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; +SELECT * FROM yb_lock_tests FOR KEY SHARE; +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +COMMIT; + +-- SELECT FOR UPDATE +BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; +SELECT * FROM yb_lock_tests FOR UPDATE; +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +ABORT; + +-- SERIALIZABLE tests +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +SELECT * from yb_lock_tests; +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +COMMIT; + +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +SELECT * from yb_lock_tests where k1 = 1; +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +COMMIT; + +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +SELECT * from yb_lock_tests where k1 = 1 and k2 = 1; +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +COMMIT; + +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +SELECT * from yb_lock_tests where k1 = 1 and k2 = 1 and r1 = 1 and r2 = 'one'; +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +COMMIT; + +-- Foreign key reference +CREATE TABLE fk_reference +( + k1 int, + k2 int, + r1 int, + r2 text, + FOREIGN KEY (k1, k2, r1, r2) REFERENCES yb_lock_tests (k1, k2, r1, r2), + PRIMARY KEY (k1, k2, r1, r2) +); + +BEGIN; +INSERT INTO fk_reference VALUES(1,1,1,'one'); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests'::regclass, null); +SELECT * FROM validate_and_return_lock_status('fk_reference'::regclass, null); +SELECT * FROM validate_and_return_lock_status('yb_lock_tests_k1_k2'::regclass, null); +COMMIT; + +-- When a number of rows are inserted +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; +INSERT INTO yb_lock_tests SELECT i, i, i, 'value', i, i from generate_series(10, 20) i; +-- yb_lock_status returns entries from all tablets in the table +-- TODO: Remove WHERE when we support the relation argument +SELECT COUNT(DISTINCT(tablet_id)) FROM yb_lock_status('yb_lock_tests'::regclass, null) + WHERE relation = 'yb_lock_tests'::regclass; +-- Validate node field +SELECT COUNT(DISTINCT (node)) > 0 AS valid_node +FROM yb_lock_status(null, null) l + INNER JOIN yb_servers() s ON l.node = s.uuid; + +ABORT; + +-- Validate attnum +CREATE TABLE attno_test_table(a int, b int, c int, d int, e int, f int, PRIMARY KEY((e, f) hash, b)); +INSERT INTO attno_test_table VALUES (1,1,1,1,1,1); + +-- Because the primary key is on (e,f,b), the first attnum 'a' should be the after the primary key in docdb +BEGIN; UPDATE attno_test_table SET a = 2 WHERE e = 1 AND f = 1 AND b = 1; +SELECT l.relation::regclass, a.attname, l.locktype, l.mode, l.hash_cols, l.range_cols, l.attnum, l.column_id +FROM yb_lock_status(null,null) l LEFT JOIN pg_attribute a ON a.attrelid = l.relation AND a.attnum = l.attnum; +ABORT; + +-- 'c' should be the second non-key column in docdb, and the second attnum +BEGIN; UPDATE attno_test_table SET c = 2 WHERE e = 1 AND f = 1 AND b = 1; +SELECT l.relation::regclass, a.attname, l.locktype, l.mode, l.hash_cols, l.range_cols, l.attnum, l.column_id +FROM yb_lock_status(null,null) l LEFT JOIN pg_attribute a ON a.attrelid = l.relation AND a.attnum = l.attnum; +ABORT; + +ALTER TABLE attno_test_table DROP COLUMN c; +-- 'd' is the fourth attnum, which should not have changed when we dropped column 'c' +BEGIN; UPDATE attno_test_table SET d = 2 WHERE e = 1 AND f = 1 AND b = 1; +SELECT l.relation::regclass, a.attname, l.locktype, l.mode, l.hash_cols, l.range_cols, l.attnum, l.column_id +FROM yb_lock_status(null,null) l LEFT JOIN pg_attribute a ON a.attrelid = l.relation AND a.attnum = l.attnum; +ABORT; + +ALTER TABLE attno_test_table ADD COLUMN c text; +-- After re-adding 'c', it should be the last column both in docdb and in pg_attribute +BEGIN; UPDATE attno_test_table SET c = 'test' WHERE e = 1 AND f = 1 AND b = 1; +SELECT l.relation::regclass, a.attname, l.locktype, l.mode, l.hash_cols, l.range_cols, l.attnum, l.column_id +FROM yb_lock_status(null,null) l LEFT JOIN pg_attribute a ON a.attrelid = l.relation AND a.attnum = l.attnum; +ABORT; + +-- Should not see any values +SELECT * FROM validate_and_return_lock_status(null, null); + diff --git a/src/test/regress/sql/yb.orig.matview.sql b/src/test/regress/sql/yb.orig.matview.sql new file mode 100644 index 0000000000000..c3bd98a177e8e --- /dev/null +++ b/src/test/regress/sql/yb.orig.matview.sql @@ -0,0 +1,356 @@ +-- +-- YB tests for materialized views +-- +CREATE TABLE t_simple (k INT PRIMARY KEY); +INSERT INTO t_simple (SELECT i FROM generate_series(1, 10) AS i); + +-- GH-26205: Test that PostgreSQL XID increases by 1 for a concurrent refresh +-- and remains the same for a non-concurrent refresh. Placing this at the top +-- of the file to ensure that this is close to being the first set of commands +-- that are executed upon cluster creation and there is no variability in the +-- XIDs generated. +CREATE MATERIALIZED VIEW mv_xid_test AS (SELECT * FROM t_simple); +CREATE UNIQUE INDEX ON mv_xid_test (k); +SELECT age('3'::xid); +REFRESH MATERIALIZED VIEW CONCURRENTLY mv_xid_test; +SELECT age('3'::xid); +REFRESH MATERIALIZED VIEW mv_xid_test; +SELECT age('3'::xid); +-- A few variations of the above test. +BEGIN ISOLATION LEVEL REPEATABLE READ; +REFRESH MATERIALIZED VIEW mv_xid_test; +INSERT INTO t_simple VALUES (11), (12); +REFRESH MATERIALIZED VIEW CONCURRENTLY mv_xid_test; +SELECT COUNT(*) FROM mv_xid_test; +COMMIT; +SELECT age('3'::xid); + +BEGIN ISOLATION LEVEL READ COMMITTED; +REFRESH MATERIALIZED VIEW CONCURRENTLY mv_xid_test; +REFRESH MATERIALIZED VIEW mv_xid_test; +INSERT INTO t_simple VALUES (13), (14); +SELECT COUNT(*) FROM mv_xid_test; +COMMIT; +-- The XID should jump by 2 in Read Committed mode as the concurrent refresh of +-- the matview is run as a sub-transaction. So, an additional XID is assigned to +-- its parent. In release builds where Read Committed isolation is disabled, the +-- XID should increase by 1. +SELECT age('3'::xid); + +CREATE TABLE test_yb (col int); +INSERT INTO test_yb VALUES (null); +CREATE UNLOGGED MATERIALIZED VIEW unlogged_mv_yb AS SELECT * FROM test_yb; -- not supported +CREATE MATERIALIZED VIEW mtest_yb AS SELECT * FROM test_yb; +CREATE UNIQUE INDEX ON mtest_yb(col); +REFRESH MATERIALIZED VIEW NONCONCURRENTLY mtest_yb; +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb; -- should fail +CREATE TABLE pg_temp_foo (col int); +INSERT INTO pg_temp_foo values (1); +SELECT * FROM pg_temp_foo; +CREATE TABLE pg_temp__123 (col int); +INSERT INTO pg_temp__123 values (1); +SELECT * from pg_temp__123; +DROP TABLE test_yb CASCADE; + +-- Alter materialized view - rename matview and rename columns +CREATE TABLE test_yb (id int NOT NULL PRIMARY KEY, type text NOT NULL, val numeric NOT NULL); +INSERT INTO test_yb VALUES (1, 'xyz', 2); +CREATE MATERIALIZED VIEW mtest_yb AS SELECT * FROM test_yb; +CREATE UNIQUE INDEX unique_IDX ON mtest_YB(id); +ALTER MATERIALIZED VIEW mtest_yb RENAME TO mtest_yb1; +SELECT * FROM mtest_yb; -- error +SELECT * from mtest_yb1; -- ok +REFRESH MATERIALIZED VIEW mtest_yb1; +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb1; +ALTER MATERIALIZED VIEW mtest_yb1 RENAME TO mtest_yb2; +SELECT * from mtest_yb2; +REFRESH MATERIALIZED VIEW mtest_yb2; +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb2; +ALTER MATERIALIZED VIEW mtest_yb2 RENAME val TO total; -- test Alter Rename Column +SELECT * FROM mtest_yb2; +DROP TABLE test_yb CASCADE; + +-- Alter materialized view - change owner +CREATE TABLE test_yb (id int NOT NULL PRIMARY KEY, type text NOT NULL, val numeric NOT NULL); +INSERT INTO test_yb VALUES (1, 'xyz', 2); +CREATE MATERIALIZED VIEW mtest_yb AS SELECT * FROM test_yb; +CREATE UNIQUE INDEX unique_IDX ON mtest_yb(id); +CREATE ROLE test_mv_user; +SET ROLE test_mv_user; +REFRESH MATERIALIZED VIEW mtest_yb; -- error +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb; -- error +SET ROLE yugabyte; +ALTER MATERIALIZED VIEW mtest_yb OWNER TO test_mv_user; +REFRESH MATERIALIZED VIEW mtest_yb; -- error +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb; -- error +ALTER TABLE test_yb OWNER TO test_mv_user; +REFRESH MATERIALIZED VIEW mtest_yb; -- ok +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb; -- ok +ALTER MATERIALIZED VIEW mtest_yb OWNER TO SESSION_USER; +ALTER TABLE test_yb OWNER TO SESSION_USER; +REFRESH MATERIALIZED VIEW mtest_yb; -- ok +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb; -- ok +ALTER MATERIALIZED VIEW mtest_yb RENAME val TO amt; +ALTER MATERIALIZED VIEW mtest_yb RENAME TO mtest_yb1; +CREATE ROLE test_mv_superuser SUPERUSER; +ALTER MATERIALIZED VIEW mtest_yb1 OWNER TO test_mv_superuser; +REFRESH MATERIALIZED VIEW mtest_yb1; -- ok +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb1; -- ok +ALTER MATERIALIZED VIEW mtest_yb1 OWNER TO CURRENT_USER; +DROP ROLE test_mv_user; +DROP ROLE test_mv_superuser; +DROP TABLE test_yb CASCADE; + +-- Test special characters in an attribute's name +CREATE TABLE test_yb ("xyzID''\\b" int NOT NULL, "y" int); +INSERT INTO test_yb VALUES (1); +CREATE MATERIALIZED VIEW mtest_yb AS SELECT * FROM test_yb WITH NO DATA; +CREATE UNIQUE INDEX ON mtest_yb("xyzID''\\b"); +REFRESH MATERIALIZED VIEW mtest_yb; +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb; +DROP TABLE test_yb CASCADE; + +-- Test with special characters in the base relation's name +CREATE TABLE "test_YB''\\b" ("xyzid" int NOT NULL); +INSERT INTO "test_YB''\\b" VALUES (1); +CREATE MATERIALIZED VIEW mtest_yb AS SELECT * FROM "test_YB''\\b" WITH NO DATA; +CREATE UNIQUE INDEX ON mtest_yb("xyzid"); +REFRESH MATERIALIZED VIEW mtest_yb; +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb; +DROP TABLE "test_YB''\\b" CASCADE; + +-- Test with special characters in the matview's name +CREATE TABLE test_yb ("xyzid" int NOT NULL); +INSERT INTO test_yb VALUES (1); +CREATE MATERIALIZED VIEW "mtest_YB''\\b" AS SELECT * FROM test_yb WITH NO DATA; +CREATE UNIQUE INDEX ON mtest_YB("xyzid"); +REFRESH MATERIALIZED VIEW mtest_YB; +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_YB; +DROP TABLE test_yb CASCADE; + +-- Test with special characters in the unique index's name +CREATE TABLE test_yb ("xyzid" int NOT NULL); +INSERT INTO test_yb VALUES (1); +CREATE MATERIALIZED VIEW mtest_yb AS SELECT * FROM test_yb WITH NO DATA; +CREATE UNIQUE INDEX "unique_IDX''\\b" ON mtest_YB("xyzid"); +REFRESH MATERIALIZED VIEW mtest_yb; +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb; +DROP TABLE test_yb CASCADE; + +-- Test with unicode characters +CREATE TABLE test_yb ("U&'\0022hi\0022'" int NOT NULL); +INSERT INTO test_yb VALUES (1); +CREATE MATERIALIZED VIEW mtest_yb AS SELECT * FROM test_yb WITH NO DATA; +CREATE UNIQUE INDEX unique_IDX ON mtest_YB("U&'\0022hi\0022'"); +REFRESH MATERIALIZED VIEW mtest_yb; +REFRESH MATERIALIZED VIEW CONCURRENTLY mtest_yb; +DROP TABLE test_yb CASCADE; + +-- Test with unicode characters from table +CREATE TABLE test_yb ("xyzid" int NOT NULL); +INSERT INTO test_yb VALUES (1); +CREATE MATERIALIZED VIEW "mtest_YB''\\b" AS SELECT * FROM test_yb WITH NO DATA; +CREATE UNIQUE INDEX ON "mtest_YB''\\b"("xyzid"); +REFRESH MATERIALIZED VIEW "mtest_YB''\\b"; +REFRESH MATERIALIZED VIEW CONCURRENTLY "mtest_YB''\\b"; +DROP TABLE test_yb CASCADE; + +-- Materialized view of a materialized view +CREATE TABLE test_yb ("xyzid" int NOT NULL); +INSERT INTO test_yb VALUES (1); +CREATE MATERIALIZED VIEW "mtest_YB''\\b" AS SELECT * FROM test_yb WITH NO DATA; +CREATE MATERIALIZED VIEW "mtest_YB''\\b\\b" AS SELECT * FROM "mtest_YB''\\b" WITH NO DATA; +CREATE UNIQUE INDEX ON "mtest_YB''\\b\\b"("xyzid"); +REFRESH MATERIALIZED VIEW "mtest_YB''\\b"; +REFRESH MATERIALIZED VIEW "mtest_YB''\\b\\b"; +REFRESH MATERIALIZED VIEW CONCURRENTLY "mtest_YB''\\b\\b"; +DROP TABLE test_yb CASCADE; + +-- Materialized view of a regular view +CREATE TABLE mvtest_t (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL); +CREATE VIEW mvtest_tv AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type; +CREATE MATERIALIZED VIEW mvtest_tm2 AS SELECT * FROM mvtest_tv; +SELECT * FROM mvtest_tm2; +DROP VIEW mvtest_tv CASCADE; + +-- SELECT FOR SHARE on Materialized view +CREATE MATERIALIZED VIEW mvtest_tm AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type; +REFRESH MATERIALIZED VIEW mvtest_tm WITH NO DATA; +SELECT * FROM mvtest_tm FOR SHARE; +DROP TABLE mvtest_t CASCADE; + +-- Materialized view with GIN Indexes +create table mvtest_t3 (id int NOT NULL PRIMARY KEY, a integer[] not null); +INSERT INTO mvtest_t3 VALUES +(1, ARRAY[1, 2, 3, 4, 5]), +(2, ARRAY[1, 2, 3, 4, 5]), +(3, ARRAY[1, 2, 3, 4, 5]), +(4, ARRAY[1, 2, 3, 4, 5]); +create index on mvtest_t3 using ybgin (a); +CREATE MATERIALIZED VIEW mvtest_tv5 AS SELECT a[1], sum(id) FROM mvtest_t3 GROUP BY a[1]; +select * from mvtest_tv5; +CREATE MATERIALIZED VIEW mvtest_tv6 AS SELECT * FROM mvtest_t3; +create index on mvtest_tv6 using ybgin (a); +select a[1] from mvtest_tv6; + +CREATE TABLE arrays (a int[], k serial PRIMARY KEY); +CREATE INDEX NONCONCURRENTLY ON arrays USING ybgin (a); +INSERT INTO arrays VALUES +('{1,1,6}'), +('{1,6,1}'), +('{2,3,6}'), +('{2,5,8}'), +('{null}'), +('{}'), +(null); +INSERT INTO arrays SELECT '{0}' FROM generate_series(1, 1000); +CREATE MATERIALIZED VIEW mvtest_tv7 AS SELECT * FROM arrays; +explain (costs off) select * from mvtest_tv7 where a @> '{6}'; +CREATE INDEX NONCONCURRENTLY ON mvtest_tv7 using ybgin (a); +explain (costs off) select * from mvtest_tv7 where a @> '{6}'; +select * from mvtest_tv7 where a @> '{6}' order by k; +INSERT INTO arrays SELECT '{0}' FROM generate_series(1, 1000); +INSERT INTO arrays VALUES +('{6,6,6}'), +('{7,6,7}'); +refresh materialized view mvtest_tv7; +select * from mvtest_tv7 where a @> '{6}' order by k; + +-- Materialized view with Collation +CREATE TABLE collate_test_POSIX ( + a int, + b text COLLATE "POSIX" NOT NULL +); +CREATE MATERIALIZED VIEW mv_collate_test_POSIX AS SELECT * FROM collate_test_POSIX; +INSERT INTO collate_test_POSIX VALUES (1, 'abc'), (2, 'Abc'), (3, 'bbc'), (4, 'ABD'), (5, 'zzz'), (6, 'ZZZ'); +REFRESH MATERIALIZED VIEW mv_collate_test_POSIX; +SELECT * FROM mv_collate_test_POSIX ORDER BY b; +SELECT * FROM mv_collate_test_POSIX ORDER BY b COLLATE "en-US-x-icu"; +CREATE MATERIALIZED VIEW mv_collate_test_explicit_collation AS SELECT b COLLATE "en-US-x-icu" FROM collate_test_POSIX; +SELECT * FROM mv_collate_test_explicit_collation ORDER BY b; + +-- Test EXPLAIN ANALYZE + CREATE MATERIALIZED VIEW AS. +-- Use EXECUTE to hide the output since it won't be stable. +DO $$ +BEGIN + EXECUTE 'EXPLAIN ANALYZE CREATE MATERIALIZED VIEW view_as_1 AS SELECT 1'; +END$$; + +SELECT * FROM view_as_1; + +-- Colocated materialized view +CREATE DATABASE mydb WITH colocation = true; +\c mydb; +CREATE TABLE base (col int); +CREATE MATERIALIZED VIEW mv AS SELECT * FROM base; +CREATE UNIQUE INDEX mv_idx ON mv(col); +SELECT * FROM mv; +INSERT INTO base VALUES (1); +REFRESH MATERIALIZED VIEW mv; +SELECT num_tablets, num_hash_key_columns, is_colocated FROM + yb_table_properties('mv'::regclass); +SELECT num_tablets, num_hash_key_columns, is_colocated FROM + yb_table_properties('mv_idx'::regclass); +SELECT * FROM mv; +INSERT INTO base VALUES (2); +REFRESH MATERIALIZED VIEW CONCURRENTLY mv; +SELECT * FROM mv ORDER BY col; +DROP MATERIALIZED VIEW mv; +SELECT * FROM mv; -- should fail. +CREATE MATERIALIZED VIEW mv WITH (COLOCATION=false) AS SELECT * FROM base; +CREATE UNIQUE INDEX mv_idx ON mv(col); +SELECT * FROM mv ORDER BY col; +INSERT INTO base VALUES (3); +REFRESH MATERIALIZED VIEW mv; +SELECT num_tablets, num_hash_key_columns, is_colocated FROM + yb_table_properties('mv'::regclass); +SELECT num_tablets, num_hash_key_columns, is_colocated FROM + yb_table_properties('mv_idx'::regclass); +SELECT * FROM mv ORDER BY col; +INSERT INTO base VALUES (4); +REFRESH MATERIALIZED VIEW CONCURRENTLY mv; +SELECT * FROM mv ORDER BY col; +DROP MATERIALIZED VIEW mv; +SELECT * FROM mv; -- should fail. + +-- Tablegroup materialized view +CREATE DATABASE testdb; +\c testdb; +CREATE TABLEGROUP test_tg; +CREATE TABLE test_t (col int) TABLEGROUP test_tg; +CREATE MATERIALIZED VIEW mv AS SELECT * FROM test_t; +SELECT * FROM mv; +INSERT INTO test_t VALUES (1); +REFRESH MATERIALIZED VIEW mv; +SELECT * FROM mv; +INSERT INTO test_t VALUES (2); +CREATE UNIQUE INDEX ON mv(col); +REFRESH MATERIALIZED VIEW CONCURRENTLY mv; +SELECT * FROM mv ORDER BY col; +DROP MATERIALIZED VIEW mv; +SELECT * FROM mv; + +-- Split options on indexes should not be copied after a non-concurrent refresh +-- on a matview. +\c yugabyte; +CREATE TABLE test_yb(t int, j int); +CREATE MATERIALIZED VIEW mv AS SELECT * FROM test_yb; +CREATE INDEX idx ON mv(t) SPLIT INTO 5 TABLETS; +REFRESH MATERIALIZED VIEW mv; +SELECT num_tablets, num_hash_key_columns FROM yb_table_properties('idx'::regclass); + +-- Matview with tablespace +DROP MATERIALIZED VIEW mv; +CREATE TABLESPACE mv_tblspace1 LOCATION '/data'; +CREATE TABLESPACE mv_tblspace2 LOCATION '/data'; +CREATE MATERIALIZED VIEW mv TABLESPACE mv_tblspace1 AS SELECT * FROM test_yb; +\d mv; +ALTER MATERIALIZED VIEW mv SET TABLESPACE mv_tblspace2; +\d mv; + +-- Matview with temp tables + transactions +CREATE TEMPORARY TABLE t_temp (a INT, b INT); +BEGIN; +INSERT INTO t_temp VALUES (1, 1), (2, 2); +REFRESH MATERIALIZED VIEW CONCURRENTLY mv_xid_test; +SELECT COUNT(*) FROM t_temp; +COMMIT; +SELECT COUNT(*) FROM t_temp; + +-- Rolling back operations on temp tables should also roll back any +-- transactional side effects of the corresponding postgres transaction. +BEGIN; +SAVEPOINT sp1; +INSERT INTO t_temp VALUES (3, 3), (4, 4); +SAVEPOINT sp2; +ROLLBACK TO sp1; +REFRESH MATERIALIZED VIEW CONCURRENTLY mv_xid_test; +COMMIT; +SELECT COUNT(*) FROM t_temp; + +BEGIN; +SAVEPOINT sp1; +REFRESH MATERIALIZED VIEW mv_xid_test; +SAVEPOINT sp2; +INSERT INTO t_temp VALUES (3, 3), (4, 4); +ROLLBACK TO sp2; +COMMIT; +SELECT COUNT(*) FROM t_temp; + +-- Matview with prepared statements + temp tables +PREPARE temp_stmt AS INSERT INTO t_temp VALUES (5, 5), (6, 6); +BEGIN; +REFRESH MATERIALIZED VIEW CONCURRENTLY mv_xid_test; +SAVEPOINT sp1; +EXECUTE temp_stmt; +ROLLBACK TO sp1; +EXECUTE temp_stmt; +COMMIT; +SELECT COUNT(*) FROM t_temp; + +-- Concurrent refresh on an empty matview +CREATE TABLE base_empty (t int); +CREATE MATERIALIZED VIEW mv_empty AS SELECT * FROM base_empty; +CREATE UNIQUE INDEX ON mv_empty(t); +REFRESH MATERIALIZED VIEW CONCURRENTLY mv_empty; +SELECT * FROM mv_empty; diff --git a/src/test/regress/sql/yb.orig.merge_scan_colocated.sql b/src/test/regress/sql/yb.orig.merge_scan_colocated.sql new file mode 100644 index 0000000000000..08f767aaab10f --- /dev/null +++ b/src/test/regress/sql/yb.orig.merge_scan_colocated.sql @@ -0,0 +1,657 @@ +-- +-- See yb_merge_scan_schedule for details about the test. +-- + +\c co + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/merge_scan_setup.sql' +\i :filename + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) LIMIT 5;' +\i :iter_Q2 + +-- No limit +\set query ':explain :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3, r4;' +\i :iter_Q2 + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2 DESC, r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Partial forward scan +\set query ':P :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3 DESC, r4, n LIMIT 5;' +\i :iter_P2 + +-- Partial backward scan +\set query ':P :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2 DESC, r3, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets +\set query ':P :Q SELECT r5, 1, r5 FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2 DESC, r3, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- DISTINCT +\set query ':P :Q SELECT DISTINCT r2, r3 FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3 LIMIT 5;' +\i :iter_P2 + +-- GROUP BY +\set query ':P :Q SELECT COUNT(*), r2, r3 FROM r5n WHERE r1 IN (0, 1, 2, 3) GROUP BY r2, r3 ORDER BY r2, r3 LIMIT 5;' +\i :iter_P2 + +-- sort, IN +-- Merge scan should not be used. +\set query ':explain :Q SELECT r1, n, r2, r3 FROM r5n WHERE r2 IN (0, 1, 2) ORDER BY r1, n LIMIT 5;' +\i :iter_Q2 + +-- _, IN, sort... +-- Merge scan should not be used. +\set query ':explain :Q SELECT r3, r4, r5, n, r2, r1 FROM r5n WHERE r2 IN (0, 1, 2) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- IN, sort, _, sort... +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (0, 1, 2) ORDER BY r2, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, IN, sort... +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r1 IN (0, 1, 2) AND r2 IN (3, 4, 5) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN/=, sort... +-- Merge scan should not be used. +\set query ':P :Q SELECT r1, r2, r3, r4, r5, n FROM r5n WHERE r1 = 2 AND r1 IN (0, 2, 4, 6, 8) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, IN/sort, IN, sort... +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 IN (1, 9) AND r3 IN (5, 6, 7, 8) ORDER BY r2, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, =, IN, sort... +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 = 7 AND r3 IN (5, 6, 7, 8) ORDER BY r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, =/sort, IN, sort... +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 = 7 AND r3 IN (5, 6, 7, 8) ORDER BY r2, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, =/sort, IN, sort... (out-of-order sort) +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 = 7 AND r3 IN (5, 6, 7, 8) ORDER BY r4, r5, r2, n LIMIT 5;' +\i :iter_P2 + +-- IN, =/sort, IN/sort, sort... +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 = 7 AND r3 IN (5, 6, 7, 8) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, IN/=/IN, sort... +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (0, 1, 2, 3, 4, 5, 6) AND r2 IN (0, 1, 2) AND r2 = 2 AND r2 IN (1, 2, 3) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN/=/IN, IN, sort... +\set query ':P :Q SELECT r1, r3, r4, r5, n, r2 FROM r5n WHERE r2 IN (0, 1, 2, 3, 4, 5, 6) AND r1 IN (0, 1, 2) AND r1 = 2 AND r1 IN (1, 2, 3) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- =, IN/IN, sort... +\set query ':P :Q SELECT r1, r3, r4, r5, n, r2 FROM r5n WHERE r1 = 7 AND r2 IN (1, 2, 3, 4) AND r2 IN (0, 2, 4, 6, 8) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- =, not-IN/IN, sort... +\set query ':P :Q SELECT r1, r3, r4, r5, n, r2 FROM r5n WHERE r1 = 7 AND r2 IN (1, 2, 3, 4) AND r2 NOT IN (0, 2, 4, 6, 8) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN/>/<, sort... +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 > 1 AND r1 IN (0, 2, 4, 6, 8) AND r1 < 7 ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Not-IN, sort... +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 NOT IN (0, 2, 4, 6, 8) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- =ANY, sort... +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 = ANY (ARRAY[[[0], [2]], [[4], [6]], [[8], [10]]]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- >ANY, sort... +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 > ANY (ARRAY[0, 2, 4, 6, 8]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- >=ALL, sort... +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 >= ALL (ARRAY[0, 2, 4, 6, 8]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- IN, sort, =, sort +\set query ':P :Q SELECT r3, r2, r4, n, r1 FROM r5n WHERE r1 IN (0, 1, 2, 3, 4, 5) AND r3 = 2 ORDER BY r2, r4, n LIMIT 5;' +\i :iter_P2 + +-- =, sort, IN, sort +-- Merge scan should not be used. +\set query ':explain :Q SELECT r1, r2, r4, n, r3 FROM r5n WHERE r1 = 2 AND r3 IN (0, 1, 2, 3, 4, 5) ORDER BY r2, r4, n LIMIT 5;' +\i :iter_Q2 + +-- IN, sort, IN, sort... +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (0, 1, 2) AND r3 IN (3, 4, 5) ORDER BY r2, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, sort, IN/sort, sort... +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (0, 1, 2) AND r3 IN (3, 4, 5) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to first key non-sort column +-- TODO(#29030): this should use merge scan. +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r2 IN (7, 8, 9) AND r2 = r1 ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to first key sort column +\set query ':P :Q SELECT r1, r3, r4, r5, n, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND r2 IN (6, 0, 5) AND r1 = r3 ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to first key sort column (v2) +\set query ':P :Q SELECT r1, r3, r4, r5, n, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND r2 IN (6, 0, 5) AND r1 = r3 ORDER BY r1, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to last key sort column +\set query ':P :Q SELECT r3, r4, r1, r5, n, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND r2 IN (6, 0, 5) AND r1 = r5 ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to last key non-sort column +\set query ':P :Q SELECT r3, r4, n, r1, r5, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND r2 IN (6, 0, 5) AND r1 = r5 ORDER BY r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to last non-key sort column +\set query ':P :Q SELECT r3, r4, r5, r1, n, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND r2 IN (6, 0, 5) AND r1 = n ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- =-var equivalence prefix +-- Merge scan should not be used. +\set query ':explain :Q SELECT r4, r5, n, r3, r1, r2 FROM r5n WHERE r3 IN (7, 8, 9) AND r1 = r2 ORDER BY r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- =-var equivalence suffix +-- Merge scan should not be used. +\set query ':explain :Q SELECT r4, r5, n, r1, r2, r3 FROM r5n WHERE r1 IN (7, 8, 9) AND r2 = r3 ORDER BY r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- OR clause +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r2 IN (0, 1, 2, 3, 4, 5, 6) AND r1 IN (0, 1, 2) AND (r1 = 2 OR r1 IN (1, 2, 3)) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Another OR clause +-- Merge scan should not be used. +\set query ':explain :Q SELECT r3, r4, r5, n, r2, r1 FROM r5n WHERE r2 IN (0, 1, 2, 3, 4, 5, 6) AND (r1 IN (0, 1, 2) OR r1 = 2 OR r1 IN (1, 2, 3)) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Cross-type SAOP: compatible type +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 = ANY (ARRAY[0, 2, 4, 6, 8]::int8[]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Cross-type SAOP: incompatible type +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 = ANY (ARRAY[0, 2, 4, 6, 8]::float[]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Typecasted LHS +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1::text = ANY (ARRAY[0, 2, 4, 6, 8]::text[]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- ArrayExpr containing FuncExpr +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (0, 1, random()::int) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- ArrayExpr containing OpExpr +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, 100 + random()::int) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- ArrayExpr containing Param +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, (SELECT count(*) FROM r5n)) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Row IN without constant table +-- TODO(#29032): this should use merge scan. +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE (r1, r2) IN ((1, 2), (3, 4)) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Row IN with constant table +-- TODO(#29032): this should use merge scan. +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE row(r1, r2) IN (values (1, 2), (3, 4)) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Subquery with ORDER inside +\set query ':P :Q SELECT r2, r3, r4, n FROM (SELECT r2, r3, r4, n FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3, r4, n LIMIT 5) sub;' +\i :iter_P2 + +-- Subquery with ORDER outside +\set query ':P :Q SELECT r2, r3, r4, n FROM (SELECT r2, r3, r4, n FROM r5n WHERE r1 IN (0, 1, 2, 3)) sub ORDER BY r2, r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- NULL in IN +-- TODO(#29073) after culling array, number of streams should be 3. +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, 2, null, 3) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Only NULLs in IN +-- Third hint is to use merge scan as the second hint ends up using sort. +-- TODO(#29073): after culling array, maybe the third hint should not use SAOP +-- merge. +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (null, null, null) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\set Q3 '/*+Set(enable_sort off) Set(yb_max_merge_scan_streams 64)*/' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- Empty array +-- Third hint is to use merge scan as the second hint ends up using sort. +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 = ANY(''{}'') ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Non-const in RHS (like var ref) +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, r2, 2) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Large number of streams +\set query ':P :Q SELECT r4, r5, n, r1, r2, r3 FROM r5n WHERE r1 IN (0, 1, 2, 3) AND r2 IN (0, 1, 2, 3) AND r3 IN (0, 1, 2, 3) ORDER BY r4, r5, n LIMIT 5;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- Single IN hitting limit +-- Merge scan should not be used. +\set on '/*+Set(yb_max_merge_scan_streams 5)*/' +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (0, 2, 4, 6, 8, 10) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Double IN hitting limit +-- Merge scan should not be used. +\set query ':explain :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r1 IN (0, 2, 4) AND r2 IN (6, 8) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Triple IN hitting limit before realizing limit could be satisfied with 0x +-- multiplier. +-- Third hint is to encourage use of merge scan like other similar empty array +-- test cases. +-- Merge scan should not be used. +\set query ':explain :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r1 IN (0, 2, 4) AND r2 IN (6, 8) AND r3 = ANY(''{}'') ORDER BY r4, r5, n LIMIT 5;' +\set Q3 '/*+Set(enable_sort off) Set(yb_max_merge_scan_streams 5)*/' +\i :iter_Q3 + +-- Triple IN avoiding limit because of 0x multiplier. +-- Third hint is to use merge scan as the second hint ends up using sort. +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r1 IN (0, 2, 4) AND r2 = ANY(''{}'') AND r3 IN (6, 8) ORDER BY r4, r5, n LIMIT 5;' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- Choose lowest cardinality IN +-- TODO(#20899): this test needs to be reconsidered when fixing this issue. +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6) AND r1 IN (3, 4, 5, 6, 7) AND r1 IN (5, 6, 7, 8, 9, 10) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- Duplicates in IN +-- TODO(#29073): after culling array, this should use merge scan. +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, 1, 1, 2, 3, 3, 4, 5, 5, 5, 1) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- (Reset the limit change) +\set on '/*+Set(yb_max_merge_scan_streams 64)*/' + +-- #30096: Merge scan shouldn't be used in a parallel scan. +\set query ':explain :Q SELECT * FROM r5n WHERE r1 IN (0, 2, 4) AND r2 IN (6, 8) ORDER BY r3, r4, r5;' +\set Q3 '/*+Parallel(r5n 2) Set(yb_enable_parallel_scan_range_sharded true) Set(yb_parallel_range_rows 1) Set(yb_max_merge_scan_streams 0)*/' +\set Q4 '/*+Parallel(r5n 2) Set(yb_enable_parallel_scan_range_sharded true) Set(yb_parallel_range_rows 1) Set(yb_max_merge_scan_streams 64)*/' +\i :iter_Q4 + +-- Same thing with backwards scan. +\set query ':explain :Q SELECT * FROM r5n WHERE r1 IN (0, 2, 4) AND r2 IN (6, 8) ORDER BY r3 DESC, r4 DESC, r5 DESC;' +\i :iter_Q4 + +-- +-- Secondary index +-- +CREATE INDEX NONCONCURRENTLY ON r5n (r2 ASC, r3, r4, r5); + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) LIMIT 5;' +\i :iter_Q2 + +-- No limit +-- TODO(#29078): this likely should use merge scan. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3, r4, r5;' +\i :iter_Q2 + +-- Following queries send various numbers of requests/scan various number of rows +-- due to non-deterministic order of equal rows in merge sort, hide their DIST +\set explain 'EXPLAIN (ANALYZE, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets +\set query ':P :Q SELECT r5, 1, r5 FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- (Reset the explain change) +\set explain 'EXPLAIN (ANALYZE, DIST, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- Secondary index scan VS merge PK scan +\set query ':P :Q SELECT r2, r3, r4, n, r1 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5) AND r2 = 4 ORDER BY r3, r4, n LIMIT 5;' +-- When PgGate does not have to make a separate round trip to the secondary index, the secondary index scan +-- becomes viable alternative to the merge PK scan. It wins in this case, so we use the third hint to +-- force the use of the primary key. +\set Q3 '/*+IndexScan(r5n r5n_pkey) Set(yb_max_merge_scan_streams 64)*/' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- Merge secondary index scan VS merge PK scan +-- Third hint is to use the secondary index as the second hint ends up using +-- the PK index. +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4) ORDER BY r3, r4, r5, n LIMIT 5;' +\set Q3 '/*+IndexScan(r5n r5n_r2_r3_r4_r5_idx) Set(yb_max_merge_scan_streams 64)*/' +\i :iter_P2 + +-- (Drop this index) +DROP INDEX r5n_r2_r3_r4_r5_idx; + +-- +-- Custom opclass secondary index +-- +CREATE OPERATOR =#= ( + leftarg = int8, + rightarg = int8, + procedure = int8eq, + commutator = =#=, + negator = <> +); +CREATE FUNCTION my_int8_sort(int8,int8) RETURNS int LANGUAGE sql AS $$ + SELECT CASE WHEN $1 = $2 THEN 0 WHEN $1 > $2 THEN 1 ELSE -1 END; +$$; +CREATE OPERATOR CLASS test_int8_ops FOR TYPE int8 USING lsm AS + OPERATOR 1 < (int8,int8), OPERATOR 2 <= (int8,int8), + OPERATOR 3 =#= (int8,int8), OPERATOR 4 >= (int8,int8), + OPERATOR 5 > (int8,int8), FUNCTION 1 my_int8_sort(int8,int8); +-- For numeric, treat 0 as highest value. +CREATE FUNCTION my_numeric_lt(numeric,numeric) RETURNS bool LANGUAGE sql AS $$ + SELECT CASE + WHEN $1 = 0 THEN false + WHEN $2 = 0 THEN true + ELSE $1 < $2 + END; +$$; +CREATE FUNCTION my_numeric_le(numeric,numeric) RETURNS bool LANGUAGE sql AS $$ + SELECT CASE + WHEN $1 = $2 THEN true + WHEN $1 = 0 THEN false + WHEN $2 = 0 THEN true + ELSE $1 <= $2 + END; +$$; +CREATE FUNCTION my_numeric_ge(numeric,numeric) RETURNS bool LANGUAGE sql AS $$ + SELECT CASE + WHEN $1 = $2 THEN true + WHEN $2 = 0 THEN false + WHEN $1 = 0 THEN true + ELSE $1 >= $2 + END; +$$; +CREATE FUNCTION my_numeric_gt(numeric,numeric) RETURNS bool LANGUAGE sql AS $$ + SELECT CASE + WHEN $2 = 0 THEN false + WHEN $1 = 0 THEN true + ELSE $1 > $2 + END; +$$; +CREATE OPERATOR #<# ( + leftarg = numeric, + rightarg = numeric, + procedure = my_numeric_lt, + commutator = #>#, + negator = #>=# +); +CREATE OPERATOR #<=# ( + leftarg = numeric, + rightarg = numeric, + procedure = my_numeric_le, + commutator = #>=#, + negator = #># +); +CREATE OPERATOR #=# ( + leftarg = numeric, + rightarg = numeric, + procedure = numeric_eq, + commutator = #=#, + negator = <> +); +CREATE OPERATOR #>=# ( + leftarg = numeric, + rightarg = numeric, + procedure = my_numeric_ge, + commutator = #<=#, + negator = #<# +); +CREATE OPERATOR #># ( + leftarg = numeric, + rightarg = numeric, + procedure = my_numeric_gt, + commutator = #<#, + negator = #<=# +); +CREATE FUNCTION my_numeric_sort(numeric,numeric) RETURNS int LANGUAGE sql AS $$ + SELECT CASE + WHEN $1 = $2 THEN 0 + WHEN $1 = 0 THEN 1 + WHEN $2 = 0 THEN -1 + WHEN $1 > $2 THEN 1 + ELSE -1 + END; +$$; +CREATE OPERATOR CLASS test_numeric_ops FOR TYPE numeric USING lsm AS + OPERATOR 1 #<# (numeric,numeric), OPERATOR 2 #<=# (numeric,numeric), + OPERATOR 3 #=# (numeric,numeric), OPERATOR 4 #>=# (numeric,numeric), + OPERATOR 5 #># (numeric,numeric), FUNCTION 1 my_numeric_sort(numeric,numeric); +CREATE INDEX NONCONCURRENTLY ON r5n (r2 test_int8_ops ASC, r3, r4 test_numeric_ops, r5); + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 =#= ANY(ARRAY[0, 1, 2, 3]) LIMIT 5;' +\i :iter_Q2 + +-- Forward scan +-- TODO(#29383): the output is incorrect where it relies on DocDB order. +-- \set query ':P :Q SELECT * FROM r5n WHERE r2 =#= ANY(ARRAY[0, 1, 2, 3]) ORDER BY r3, r4 USING #<#, n LIMIT 5;' +-- \set Pnext :iter_Q2 +-- \i :iter_P2 + +-- Backward scan +-- TODO(#29383): the output is incorrect where it relies on DocDB order. +-- \set query ':P :Q SELECT * FROM r5n WHERE r2 =#= ANY(ARRAY[0, 1, 2, 3]) ORDER BY r3 DESC, r4 USING #>#, n LIMIT 5;' +-- \i :iter_P2 + +-- (Drop this index) +DROP INDEX r5n_r2_r3_r4_r5_idx; + +-- +-- INCLUDE index +-- +CREATE INDEX NONCONCURRENTLY ON r5n (r2 ASC, r3, r4, r5) INCLUDE (r1, n); + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) LIMIT 5;' +\i :iter_Q2 + +-- Following queries send various numbers of requests/scan various number of rows +-- due to non-deterministic order of equal rows in merge sort, hide their DIST +\set explain 'EXPLAIN (ANALYZE, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3, r4, n LIMIT 5;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets +\set query ':P :Q SELECT n, 1, n FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- (Reset the explain change) +\set explain 'EXPLAIN (ANALYZE, DIST, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- Secondary index only scan VS merge PK scan +\set query ':P :Q SELECT r2, r3, r4, n, r1 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5) ORDER BY r2, r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- (Drop this index) +DROP INDEX r5n_r2_r3_r4_r5_r1_n_idx; + +-- +-- Expression prefix secondary index +-- +CREATE INDEX NONCONCURRENTLY ON r5n ((greatest(r2, r3, r4) - least(r2, r3, r4)) ASC, r2, r3, r4); + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE (greatest(r2, r3, r4) - least(r2, r3, r4)) IN (0, 2) LIMIT 5;' +\i :iter_Q2 + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n WHERE (greatest(r2, r3, r4) - least(r2, r3, r4)) IN (0, 2) ORDER BY r2, r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM r5n WHERE (greatest(r2, r3, r4) - least(r2, r3, r4)) IN (0, 2) ORDER BY r2 DESC, r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets +\set query ':P :Q SELECT (greatest(r2, r3, r4) - least(r2, r3, r4)), 1, (greatest(r2, r3, r4) - least(r2, r3, r4)) FROM r5n WHERE (greatest(r2, r3, r4) - least(r2, r3, r4)) IN (0, 2) ORDER BY r2 DESC, r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Secondary index scan VS merge PK scan +-- Third hint is to use the PK index as the second hint ends up using the +-- expression index. +\set query ':P :Q SELECT (greatest(r2, r3, r4) - least(r2, r3, r4)), r2, r3, r4, n, r1 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5) AND (greatest(r2, r3, r4) - least(r2, r3, r4)) = 4 ORDER BY r2, r3, r4, n LIMIT 5;' +\set Q3 '/*+IndexScan(r5n r5n_pkey) Set(yb_max_merge_scan_streams 64)*/' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- Merge secondary index scan VS merge PK scan +-- Third hint is to use the PK index as the second hint ends up using the +-- expression index. +\set query ':P :Q SELECT r2, r3, r4, n, r1, (greatest(r2, r3, r4) - least(r2, r3, r4)) FROM r5n WHERE r1 IN (1, 2, 3, 4) AND (greatest(r2, r3, r4) - least(r2, r3, r4)) IN (1, 2, 3, 4) ORDER BY r2, r3, r4, n LIMIT 5;' +\set Q3 '/*+IndexScan(r5n r5n_pkey) Set(yb_max_merge_scan_streams 64)*/' +\i :iter_P2 + +-- (Drop this index) +DROP INDEX r5n_expr_r2_r3_r4_idx; + +-- +-- Expression suffix secondary index +-- Expressions in a secondary colocated index are useless for merge scan +-- +CREATE INDEX NONCONCURRENTLY ON r5n (r2 ASC, r3 DESC, (-r4)); + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 2) ORDER BY r3 DESC, -r4, n LIMIT 5;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 2) ORDER BY r3 ASC, -r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets +\set query ':P :Q SELECT -r4, 1, -r4 FROM r5n WHERE r2 IN (0, 2) ORDER BY r3 ASC, -r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- (Drop this index) +DROP INDEX r5n_r2_r3_expr_idx; + +-- +-- Duplicate columns secondary index +-- +CREATE INDEX NONCONCURRENTLY ON r5n (r2 ASC, (r3 + r4), r2 DESC, (r3 + r4), r2); + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) LIMIT 5;' +\i :iter_Q2 + +-- Following queries send various numbers of requests/scan various number of rows +-- due to non-deterministic order of equal rows in merge sort, hide their DIST +\set explain 'EXPLAIN (ANALYZE, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- Forward scan +-- Order by an expression in the embedded index +-- Merge scan should not be used. +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4), n LIMIT 5;' +\i :iter_P2 + +-- Forward scan (v2) +-- Merge scan is not used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4), r2 DESC, n LIMIT 5;' +\i :iter_Q2 + +-- Backward scan +-- Order by an expression in the embedded index +-- Merge scan should not be used. +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4) DESC, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan (v2) +-- Merge scan is not used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4) DESC, r2, n LIMIT 5;' +\i :iter_Q2 + +-- Targets +-- Order by an expression in the embedded index +-- Merge scan should not be used. +\set query ':P :Q SELECT r5, 1, r5 FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4) DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets (v2) +-- Merge scan is not used. +\set query ':explain :Q SELECT r5, 1, r5 FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4) DESC, r2, n LIMIT 5;' +\i :iter_Q2 + +-- (Reset the explain change) +\set explain 'EXPLAIN (ANALYZE, DIST, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- (Drop this index) +DROP INDEX r5n_r2_expr_r21_expr1_r22_idx; + +-- +-- EXPLAIN FORMAT JSON +-- +CREATE FUNCTION get_explain_property(query text, property text) RETURNS jsonb LANGUAGE plpgsql AS $$ +DECLARE + plan jsonb; +BEGIN + FOR plan IN EXECUTE 'EXPLAIN (ANALYZE, DIST, FORMAT JSON, VERBOSE)' || query LOOP + RETURN plan->0->'Plan'->property; + END LOOP; +END $$; + +SELECT get_explain_property(hint || 'SELECT * FROM r5n WHERE r1 IN (1, 2, 3) ORDER BY r2', property) +FROM unnest(ARRAY['/*+Set(yb_max_merge_scan_streams 0)*/', + '/*+Set(yb_max_merge_scan_streams 64)*/']) AS hint, + unnest(ARRAY['Merge Sort Key', + 'Merge Stream Key', + 'Merge Streams', + 'Merge Cond']) AS property; diff --git a/src/test/regress/sql/yb.orig.merge_scan_derive.sql b/src/test/regress/sql/yb.orig.merge_scan_derive.sql new file mode 100644 index 0000000000000..522b5d4132766 --- /dev/null +++ b/src/test/regress/sql/yb.orig.merge_scan_derive.sql @@ -0,0 +1,223 @@ +-- +-- Test YB's automatic insertion of scalar-array-op conditions for merge scan. +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/merge_scan_setup.sql' +\i :filename + +\set off '/*+Set(enable_parallel_append off) Set(yb_max_merge_scan_streams 0)*/' +\set on '/*+Set(enable_parallel_append off) Set(yb_max_merge_scan_streams 64)*/' + +SET yb_enable_derived_saops = true; + +-- +-- Derive from generated column +-- + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM bkt_tbl;' +\i :iter_Q2 + +-- No limit +\set query ':explain :Q SELECT * FROM bkt_tbl ORDER BY r1, r2, r3;' +\i :iter_Q2 + +-- Forward scan +\set query ':P :Q SELECT * FROM bkt_tbl ORDER BY r1, r2, r3, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM bkt_tbl ORDER BY r1 DESC, r2 DESC, r3 DESC, n LIMIT 5;' +\i :iter_P2 + +-- DISTINCT +\set query ':P :Q SELECT DISTINCT r1, r2, r3 FROM bkt_tbl ORDER BY r1, r2, r3 LIMIT 5;' +\i :iter_P2 + +-- GROUP BY +\set query ':P :Q SELECT COUNT(*), r1, r2, r3 FROM bkt_tbl GROUP BY r1, r2, r3 ORDER BY r1, r2, r3 LIMIT 5;' +\i :iter_P2 + +-- Explicit SAOP smaller than derived SAOP +\set query ':P :Q SELECT r1, r2, r3, n, bkt FROM bkt_tbl WHERE bkt IN (1, 2) ORDER BY r1, r2, r3, n LIMIT 5;' +\i :iter_P2 + +-- Explicit SAOP larger than derived SAOP +\set query ':P :Q SELECT r1, r2, r3, n, bkt FROM bkt_tbl WHERE bkt IN (1, 2, 3, 4) ORDER BY r1, r2, r3, n LIMIT 5;' +\i :iter_P2 + +CREATE INDEX NONCONCURRENTLY ON bkt_tbl (bkt ASC, r3, r2, r1) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (2, 2, 2, 2), + (3)); + +-- No limit +-- TODO(#29078): this likely should use merge scan. +\set query ':explain :Q SELECT * FROM bkt_tbl ORDER BY r3, r2, r1;' +\i :iter_Q2 + +-- Forward scan +\set query ':P :Q SELECT * FROM bkt_tbl ORDER BY r3, r2, r1, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM bkt_tbl ORDER BY r3 DESC, r2 DESC, r1 DESC, n LIMIT 5;' +\i :iter_P2 + +-- +-- Derive from secondary index expression, expression being a prefix +-- +CREATE INDEX NONCONCURRENTLY ON r5n ((yb_hash_code(r2, r3, r4) % 3) ASC, r2, r3, r4) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (2, 2, 2, 2), + (3)); + +-- No limit +-- TODO(#29078): this likely should use merge scan. +\set query ':explain :Q SELECT * FROM r5n ORDER BY r2, r3;' +\i :iter_Q2 + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n ORDER BY r2, r3, n LIMIT 5;' +\i :iter_P2 + +-- Explicit SAOP smaller than derived SAOP +\set query ':P :Q SELECT r2, r3, n, yb_hash_code(r2, r3, r4) % 3 FROM r5n WHERE yb_hash_code(r2, r3, r4) % 3 in (0, 2) ORDER BY r2, r3, n LIMIT 5;' +\i :iter_P2 + +-- Explicit SAOP larger than derived SAOP +\set query ':P :Q SELECT r2, r3, n, yb_hash_code(r2, r3, r4) % 3 FROM r5n WHERE yb_hash_code(r2, r3, r4) % 3 in (0, 2, 4, 8) ORDER BY r2, r3, n LIMIT 5;' +\i :iter_P2 + +-- Expression bound to constant +-- Merge scan should not be used. +\set query ':explain :Q SELECT yb_hash_code(r2, r3, r4) % 3, r2, r3, n FROM r5n WHERE yb_hash_code(r2, r3, r4) % 3 = 1 ORDER BY r2, r3, n LIMIT 5;' +\i :iter_Q2 + +-- Range filter; no ORDER BY +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 > 1 LIMIT 5;' +\i :iter_Q2 + +-- (Drop this index) +DROP INDEX r5n_expr_r2_r3_r4_idx; + +-- +-- Derive from secondary index expression, expression not being a prefix, +-- modulus being negative. +-- +CREATE INDEX NONCONCURRENTLY ON r5n (r1 ASC, (yb_hash_code(r1, r3, r4) % -5) ASC, r3, r4) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (2, 2, 2, 2), + (3)); + +-- Expression in sort, so not derived +-- Merge scan should not be used. +\set query ':explain :Q SELECT r1, yb_hash_code(r1, r3, r4) % -5, r3, n FROM r5n WHERE r1 = 1 ORDER BY yb_hash_code(r1, r3, r4) % -5, r3, n LIMIT 5;' +\i :iter_Q2 + +-- Expression in sort, so not derived (v2) +\set query ':P :Q SELECT yb_hash_code(r1, r3, r4) % -5, r3, n, r1 FROM r5n WHERE r1 in (0, 1, 2) ORDER BY yb_hash_code(r1, r3, r4) % -5, r3, n LIMIT 5;' +\i :iter_P2 + +-- Derived +-- Third hint is to use the expression index. +\set query ':P :Q SELECT r1, r3, r4, n FROM r5n WHERE r1 = 1 ORDER BY r3, r4, n LIMIT 5;' +\set Q3 '/*+IndexScan(r5n r5n_r1_expr_r3_r4_idx) Set(enable_parallel_append off) Set(yb_max_merge_scan_streams 64)*/' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- Following queries send various numbers of requests/scan various number of +-- rows due to non-deterministic order of equal rows in merge sort. Hide their +-- DIST. +\set explain 'EXPLAIN (ANALYZE, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- Derived with multiple stream keys +\set query ':P :Q SELECT r3, r4, n, r1 FROM r5n WHERE r1 IN (0, 1, 2) ORDER BY r3, r4, n LIMIT 5;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- (Reset the explain change) +\set explain 'EXPLAIN (ANALYZE, DIST, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- (Drop this index) +DROP INDEX r5n_r1_expr_r3_r4_idx; + +-- +-- Derive from partitioned secondary index expression +-- +CREATE INDEX NONCONCURRENTLY ON parent ((yb_hash_code(r2, r3) % 3) ASC, r2, r3) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (3)); + +-- Parent +\set query ':P :Q SELECT r2, r3, n FROM parent ORDER BY r2, r3, n LIMIT 5;' +\i :iter_P2 + +-- Child +\set query ':P :Q SELECT r2, r3, n FROM child1 ORDER BY r2, r3, n LIMIT 5;' +\i :iter_P2 + +-- Grandchild +\set query ':P :Q SELECT r2, r3, n FROM child1b ORDER BY r2, r3, n LIMIT 5;' +\i :iter_P2 + +-- (Drop this index) +DROP INDEX parent_expr_r2_r3_idx; + +-- +-- Joins +-- +CREATE INDEX NONCONCURRENTLY ON r5n ((yb_hash_code(r2, r3, r4) % 3) ASC, r2, r3, r4) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (2, 2, 2, 2), + (3)); +CREATE INDEX NONCONCURRENTLY ON parent ((yb_hash_code(r2, r3) % 3) ASC, r2, r3) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (3)); + +-- Derive from r5n +\set query ':P :Q SELECT r5n.r2, r5n.r3, parent.r3, r5n.n, parent.n FROM parent JOIN r5n ON r5n.r3 = parent.r3 ORDER BY r5n.r2 LIMIT 5;' +\i :iter_P2 + +-- Derive from parent +\set query ':P :Q SELECT parent.r2, r5n.r3, parent.r3, r5n.n, parent.n FROM parent JOIN r5n ON r5n.r3 = parent.r3 ORDER BY parent.r2 LIMIT 5;' +\i :iter_P2 + +-- 2-hop =-const equivalence +-- Third hint is to show that when a JOIN doesn't care about the ordering of +-- the index scan, Merge scan is not used. +-- Fourth hint is to show merge scan in a merge join. +-- Fifth hint is to show merge scan in the inner side of a merge join. +-- TODO(#29030): fifth hint should use merge scan. +\set query ':explain :Q SELECT DISTINCT ON (r5n.r3, r5n.r4) parent.p1, r5n.r2, r5n.r3, parent.r3, r5n.n, parent.n FROM r5n JOIN parent ON r5n.r3 = parent.r3 WHERE parent.p1 = 9 AND r5n.r2 IN (3, 4, 5) LIMIT 5;' +\set Q3 '/*+Leading((parent r5n)) Set(enable_parallel_append off) Set(yb_max_merge_scan_streams 64)*/' +\set Q4 '/*+MergeJoin(parent r5n) Set(enable_parallel_append off) Set(yb_max_merge_scan_streams 64)*/' +\set Q5 '/*+MergeJoin(parent r5n) Leading((parent r5n)) Set(enable_parallel_append off) Set(yb_max_merge_scan_streams 64)*/' +\i :iter_Q5 diff --git a/src/test/regress/sql/yb.orig.merge_scan_hash.sql b/src/test/regress/sql/yb.orig.merge_scan_hash.sql new file mode 100644 index 0000000000000..58ef832746f46 --- /dev/null +++ b/src/test/regress/sql/yb.orig.merge_scan_hash.sql @@ -0,0 +1,65 @@ +-- +-- See yb_merge_scan_schedule for details about the test. +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/merge_scan_setup.sql' +\i :filename + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM h3r2n WHERE h1 = 6 AND h2 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND h3 = 1 LIMIT 5;' +\i :iter_Q2 + +-- =, IN, sort... +-- Merge scan should not be used. +\set query ':explain :Q SELECT h1, h3, r1, r2, n, h2 FROM h3r2n WHERE h1 = 6 AND h2 IN (1, 3, 5, 7, 9) ORDER BY h3, r1, r2, n LIMIT 5;' +\i :iter_Q2 + +-- =, IN, =, sort... +\set query ':P :Q SELECT h1, h3, r1, r2, n, h2 FROM h3r2n WHERE h1 = 1 AND h2 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND h3 = 1 ORDER BY r1, r2, n LIMIT 5;' +\i :iter_P2 + +-- =, IN, =/sort, sort... +\set query ':P :Q SELECT h1, h3, r1, r2, n, h2 FROM h3r2n WHERE h1 = 1 AND h2 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND h3 = 1 ORDER BY h3, r1, r2, n LIMIT 5;' +\i :iter_P2 + +-- =, =, IN, sort... +\set query ':P :Q SELECT h1, h2, r1, r2, n, h3 FROM h3r2n WHERE h1 = 1 AND h2 = 8 AND h3 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) ORDER BY r1, r2, n LIMIT 5;' +\i :iter_P2 + +-- =, =, IN/sort, sort... +-- Merge scan should not be used. +\set query ':explain :Q SELECT h1, h2, h3, r1, r2, n FROM h3r2n WHERE h1 = 1 AND h2 = 8 AND h3 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) ORDER BY h3, r1, r2, n LIMIT 5;' +\i :iter_Q2 + +-- =/sort, IN, IN, sort... +\set query ':P :Q SELECT h1, r1, r2, n, h2, h3 FROM h3r2n WHERE h1 = 2 AND h2 IN (3, 4, 5, 6, 7) AND h3 IN (6, 7, 8, 9, 10) ORDER BY h1, r1, r2, n LIMIT 5;' +\i :iter_P2 + +/* TODO(#29079): uncomment when fixing this issue. +-- yb_hash_code equality +\set query ':P :Q SELECT r1, r2, n, yb_hash_code(h1, h2, h3), h1, h2, h3 FROM h3r2n WHERE yb_hash_code(h1, h2, h3) = 28655 AND h1 IN (5, 7) AND h2 IN (1, 3, 7) AND h3 IN (3, 6, 7) ORDER BY r1, r2, n LIMIT 5;' +\i :iter_P2 + +-- yb_hash_code inequality +\set query ':P :Q SELECT r1, r2, n, yb_hash_code(h1, h2, h3), h1, h2, h3 FROM h3r2n WHERE yb_hash_code(h1, h2, h3) < 12283 AND h1 IN (5, 7) AND h2 IN (1, 3, 7) AND h3 IN (3, 6, 7) ORDER BY r1, r2, n LIMIT 5;' +\i :iter_P2 +*/; -- semicolon to avoid bleeding hints + +-- yb_hash_code IN +-- Third hint is to use merge scan as the second hint ends up using sort. +\set query ':P :Q SELECT r1, r2, n, yb_hash_code(h1, h2, h3), h1, h2, h3 FROM h3r2n WHERE yb_hash_code(h1, h2, h3) IN (17834, 28655, 32412) AND h1 IN (5, 7) AND h2 IN (1, 3, 7) AND h3 IN (3, 6, 7) ORDER BY r1, r2, n LIMIT 5;' +\set Q3 '/*+Set(enable_sort off) Set(yb_max_merge_scan_streams 64)*/' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- #30096: Merge scan shouldn't be used in a parallel scan. +\set query ':explain :Q SELECT * FROM h3r2n WHERE h1 = 1 AND h2 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND h3 = 1 ORDER BY r1, r2;' +\set Q3 '/*+Parallel(h3r2n 2) Set(yb_enable_parallel_scan_hash_sharded true) Set(yb_parallel_range_rows 1) Set(yb_max_merge_scan_streams 0)*/' +\set Q4 '/*+Parallel(h3r2n 2) Set(yb_enable_parallel_scan_hash_sharded true) Set(yb_parallel_range_rows 1) Set(yb_max_merge_scan_streams 64)*/' +\i :iter_Q4 + +-- Same thing with backwards scan. +\set query ':explain :Q SELECT * FROM h3r2n WHERE h1 = 1 AND h2 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND h3 = 1 ORDER BY r1 DESC, r2 DESC;' +\i :iter_Q4 diff --git a/src/test/regress/sql/yb.orig.merge_scan_join.sql b/src/test/regress/sql/yb.orig.merge_scan_join.sql new file mode 100644 index 0000000000000..23597dd6a04c4 --- /dev/null +++ b/src/test/regress/sql/yb.orig.merge_scan_join.sql @@ -0,0 +1,66 @@ +-- +-- See yb_merge_scan_schedule for details about the test. Do not sort on the +-- r5n.n column because that ruins the plans. +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/merge_scan_setup.sql' +\i :filename + +-- Basic join without order +-- Third hint is to show hash join rightfully does not use merge scan. +-- Fourth hint is to show merge scan in a merge join. +-- Fifth hint is to show merge scan in the inner side of a merge join. +\set query ':explain :Q SELECT DISTINCT ON (a.r3, a.r4) * FROM r5n a JOIN r5n b ON a.r3 = b.r1 WHERE a.r1 = 9 AND a.r2 IN (3, 4, 5) LIMIT 5;' +\set Q3 '/*+HashJoin(a b) Set(yb_max_merge_scan_streams 64)*/' +\set Q4 '/*+MergeJoin(a b) Set(yb_max_merge_scan_streams 64)*/' +\set Q5 '/*+MergeJoin(a b) Leading((b a)) Set(yb_max_merge_scan_streams 64)*/' +\i :iter_Q5 + +-- Basic join with order +-- TODO(pg16): PG commit b592422095655a64d638f541df784b19b8ecf8ad enables +-- incremental sort over MergeJoin. +\set query ':P :Q SELECT DISTINCT ON (a.r3, a.r4) * FROM r5n a JOIN r5n b ON a.r3 = b.r1 WHERE a.r1 = 9 AND a.r2 IN (3, 4, 5) ORDER BY a.r3, a.r4, a.n, b.n LIMIT 5;' +\set Pnext :iter_Q5 +\i :iter_P2 + +-- Non-const equivalence prefix +-- Merge scan should not be used. +\set query ':explain :Q SELECT DISTINCT ON (r5n.r3, r5n.r4) r5n.r3, r5n.r4, r5n.r5, r5n.r2, r5n.r1, h3r2n.r1 FROM r5n JOIN h3r2n ON r5n.r1 = h3r2n.r1 WHERE r5n.r2 IN (3, 4, 5) ORDER BY r5n.r3, r5n.r4, r5n.r5 LIMIT 5;' +\i :iter_Q2 + +-- Non-const equivalence suffix +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n JOIN h3r2n ON r5n.r4 = h3r2n.r1 AND r5n.r5 = h3r2n.r2 WHERE r5n.r1 = 1 AND r5n.r2 IN (6, 7, 8, 9, 10) AND r5n.r3 IN (1, 2, 3, 4, 5) LIMIT 5;' +\i :iter_Q2 + +-- 2-hop =-const equivalence +-- Third hint is to show that when a JOIN doesn't care about the ordering of +-- the index scan, Merge scan is not used. +-- Fourth hint is to show merge scan in a merge join. +-- Fifth hint is to show merge scan in the inner side of a merge join. +-- TODO(#29030): fifth hint should use merge scan. +\set query ':P :Q SELECT DISTINCT ON (r5n.r3, r5n.r4) r5n.r1, h3r2n.r1, r5n.r3, r5n.r4, r5n.r5, r5n.r2 FROM r5n JOIN h3r2n ON r5n.r1 = h3r2n.r1 AND r5n.r3 = h3r2n.h3 WHERE h3r2n.r1 = 9 AND r5n.r2 IN (3, 4, 5) LIMIT 5;' +\set Q3 '/*+Leading((h3r2n r5n)) Set(yb_max_merge_scan_streams 64)*/' +\set Q4 '/*+MergeJoin(h3r2n r5n) Set(yb_max_merge_scan_streams 64)*/' +\set Q5 '/*+MergeJoin(h3r2n r5n) Leading((h3r2n r5n)) Set(yb_max_merge_scan_streams 64)*/' +\i :iter_P2 + +-- Same as above with no DISTINCT +-- Merge scan should not be used. +\set query ':explain :Q SELECT r5n.r1, h3r2n.r1, r5n.r3, r5n.r4, r5n.r5, r5n.r2 FROM r5n JOIN h3r2n ON r5n.r1 = h3r2n.r1 AND r5n.r3 = h3r2n.h3 WHERE h3r2n.r1 = 9 AND r5n.r2 IN (3, 4, 5) LIMIT 5;' +\i :iter_Q2 + +-- Multi-hop =-const equivalence +-- Third hint is to show that when a JOIN doesn't care about the ordering of +-- the index scan, Merge scan is not used. +\set query ':P :Q SELECT DISTINCT ON (r5n.r3, r5n.r4) r5n.r1, h3r2n.r1, parent.p1, r5n.r3, r5n.r4, r5n.r5, r5n.r2 FROM r5n JOIN h3r2n ON r5n.r1 = h3r2n.r1 JOIN parent ON h3r2n.h1 = parent.p1 WHERE parent.p1 = 6 AND h3r2n.h1 = h3r2n.r1 AND r5n.r2 IN (3, 4, 5) ORDER BY r5n.r3, r5n.r4, r5n.r5 LIMIT 5;' +\set Q3 '/*+Leading(((h3r2n r5n) parent)) Set(yb_max_merge_scan_streams 64)*/' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- 2-hop IN-const equivalence +-- TODO(#29030): this should use merge scan; may want to use :run3. +\set query ':P :Q SELECT DISTINCT ON (r5n.r3, r5n.r4) r5n.r1, h3r2n.r1, r5n.r3, r5n.r4, r5n.r5, r5n.r2 FROM r5n JOIN h3r2n ON r5n.r1 = h3r2n.r1 WHERE h3r2n.r1 IN (1, 2) AND r5n.r2 IN (3, 4, 5) ORDER BY r5n.r3, r5n.r4, r5n.r5 LIMIT 5;' +\set Pnext :iter_Q2 +\i :iter_P2 diff --git a/src/test/regress/sql/yb.orig.merge_scan_partition.sql b/src/test/regress/sql/yb.orig.merge_scan_partition.sql new file mode 100644 index 0000000000000..4485fbb37fa26 --- /dev/null +++ b/src/test/regress/sql/yb.orig.merge_scan_partition.sql @@ -0,0 +1,41 @@ +-- +-- See yb_merge_scan_schedule for details about the test. +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/merge_scan_setup.sql' +\i :filename + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM parent WHERE r1 IN (0, 1, 2, 3) LIMIT 5;' +\i :iter_Q2 + +-- Forward scan +\set query ':P :Q SELECT * FROM parent WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3, p1, p2, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM parent WHERE r1 IN (0, 1, 2, 3) ORDER BY r2 DESC, r3 DESC, p1 DESC, p2 DESC, n LIMIT 5;' +\i :iter_P2 + +-- +-- Partitioned index +-- +CREATE INDEX ON parent (p2 ASC, r2, p1, r3, r1); + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM parent WHERE p2 IN (0, 1, 2, 3) LIMIT 5;' +\i :iter_Q2 + +-- Forward scan +\set query ':P :Q SELECT * FROM parent WHERE p2 IN (0, 1, 2, 3) ORDER BY r2, p1, r3, r1, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM parent WHERE p2 IN (0, 1, 2, 3) ORDER BY r2 DESC, p1 DESC, r3 DESC, r1 DESC, n LIMIT 5;' +\i :iter_P2 + +-- (Drop this index) +DROP INDEX parent_p2_r2_p1_r3_r1_idx; diff --git a/src/test/regress/sql/yb.orig.merge_scan_range.sql b/src/test/regress/sql/yb.orig.merge_scan_range.sql new file mode 100644 index 0000000000000..b18e7f740d475 --- /dev/null +++ b/src/test/regress/sql/yb.orig.merge_scan_range.sql @@ -0,0 +1,686 @@ +-- +-- See yb_merge_scan_schedule for details about the test. +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/merge_scan_setup.sql' +\i :filename + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) LIMIT 5;' +\i :iter_Q2 + +-- No limit +\set query ':explain :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3, r4;' +\i :iter_Q2 + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2 DESC, r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Partial forward scan +\set query ':P :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3 DESC, r4, n LIMIT 5;' +\i :iter_P2 + +-- Partial backward scan +\set query ':P :Q SELECT * FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2 DESC, r3, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets +\set query ':P :Q SELECT r5, 1, r5 FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2 DESC, r3, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- DISTINCT +\set query ':P :Q SELECT DISTINCT r2, r3 FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3 LIMIT 5;' +\i :iter_P2 + +-- GROUP BY +\set query ':P :Q SELECT COUNT(*), r2, r3 FROM r5n WHERE r1 IN (0, 1, 2, 3) GROUP BY r2, r3 ORDER BY r2, r3 LIMIT 5;' +\i :iter_P2 + +-- sort, IN +-- Merge scan should not be used. +\set query ':explain :Q SELECT r1, n, r2, r3 FROM r5n WHERE r2 IN (0, 1, 2) ORDER BY r1, n LIMIT 5;' +\i :iter_Q2 + +-- _, IN, sort... +-- Merge scan should not be used. +\set query ':explain :Q SELECT r3, r4, r5, n, r2, r1 FROM r5n WHERE r2 IN (0, 1, 2) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- IN, sort, _, sort... +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (0, 1, 2) ORDER BY r2, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, IN, sort... +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r1 IN (0, 1, 2) AND r2 IN (3, 4, 5) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN/=, sort... +-- Merge scan should not be used. +\set query ':P :Q SELECT r1, r2, r3, r4, r5, n FROM r5n WHERE r1 = 2 AND r1 IN (0, 2, 4, 6, 8) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, IN/sort, IN, sort... +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 IN (1, 9) AND r3 IN (5, 6, 7, 8) ORDER BY r2, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, =, IN, sort... +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 = 7 AND r3 IN (5, 6, 7, 8) ORDER BY r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, =/sort, IN, sort... +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 = 7 AND r3 IN (5, 6, 7, 8) ORDER BY r2, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, =/sort, IN, sort... (out-of-order sort) +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 = 7 AND r3 IN (5, 6, 7, 8) ORDER BY r4, r5, r2, n LIMIT 5;' +\i :iter_P2 + +-- IN, =/sort, IN/sort, sort... +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 = 7 AND r3 IN (5, 6, 7, 8) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, IN/=/IN, sort... +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (0, 1, 2, 3, 4, 5, 6) AND r2 IN (0, 1, 2) AND r2 = 2 AND r2 IN (1, 2, 3) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN/=/IN, IN, sort... +\set query ':P :Q SELECT r1, r3, r4, r5, n, r2 FROM r5n WHERE r2 IN (0, 1, 2, 3, 4, 5, 6) AND r1 IN (0, 1, 2) AND r1 = 2 AND r1 IN (1, 2, 3) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- =, IN/IN, sort... +\set query ':P :Q SELECT r1, r3, r4, r5, n, r2 FROM r5n WHERE r1 = 7 AND r2 IN (1, 2, 3, 4) AND r2 IN (0, 2, 4, 6, 8) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- =, not-IN/IN, sort... +\set query ':P :Q SELECT r1, r3, r4, r5, n, r2 FROM r5n WHERE r1 = 7 AND r2 IN (1, 2, 3, 4) AND r2 NOT IN (0, 2, 4, 6, 8) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN/>/<, sort... +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 > 1 AND r1 IN (0, 2, 4, 6, 8) AND r1 < 7 ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Not-IN, sort... +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 NOT IN (0, 2, 4, 6, 8) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- =ANY, sort... +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 = ANY (ARRAY[[[0], [2]], [[4], [6]], [[8], [10]]]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- >ANY, sort... +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 > ANY (ARRAY[0, 2, 4, 6, 8]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- >=ALL, sort... +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 >= ALL (ARRAY[0, 2, 4, 6, 8]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- IN, sort, =, sort +\set query ':P :Q SELECT r3, r2, r4, n, r1 FROM r5n WHERE r1 IN (0, 1, 2, 3, 4, 5) AND r3 = 2 ORDER BY r2, r4, n LIMIT 5;' +\i :iter_P2 + +-- =, sort, IN, sort +-- Merge scan should not be used. +\set query ':explain :Q SELECT r1, r2, r4, n, r3 FROM r5n WHERE r1 = 2 AND r3 IN (0, 1, 2, 3, 4, 5) ORDER BY r2, r4, n LIMIT 5;' +\i :iter_Q2 + +-- IN, sort, IN, sort... +\set query ':P :Q SELECT r2, r4, r5, n, r1, r3 FROM r5n WHERE r1 IN (0, 1, 2) AND r3 IN (3, 4, 5) ORDER BY r2, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN, sort, IN/sort, sort... +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (0, 1, 2) AND r3 IN (3, 4, 5) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to first key non-sort column +-- TODO(#29030): this should use merge scan. +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r2 IN (7, 8, 9) AND r2 = r1 ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to first key sort column +\set query ':P :Q SELECT r1, r3, r4, r5, n, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND r2 IN (6, 0, 5) AND r1 = r3 ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to first key sort column (v2) +\set query ':P :Q SELECT r1, r3, r4, r5, n, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND r2 IN (6, 0, 5) AND r1 = r3 ORDER BY r1, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to last key sort column +\set query ':P :Q SELECT r3, r4, r1, r5, n, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND r2 IN (6, 0, 5) AND r1 = r5 ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to last key non-sort column +\set query ':P :Q SELECT r3, r4, n, r1, r5, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND r2 IN (6, 0, 5) AND r1 = r5 ORDER BY r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- IN equivalence to last non-key sort column +\set query ':P :Q SELECT r3, r4, r5, r1, n, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6, 7, 8, 9) AND r2 IN (6, 0, 5) AND r1 = n ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- =-var equivalence prefix +-- Merge scan should not be used. +\set query ':explain :Q SELECT r4, r5, n, r3, r1, r2 FROM r5n WHERE r3 IN (7, 8, 9) AND r1 = r2 ORDER BY r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- =-var equivalence suffix +-- Merge scan should not be used. +\set query ':explain :Q SELECT r4, r5, n, r1, r2, r3 FROM r5n WHERE r1 IN (7, 8, 9) AND r2 = r3 ORDER BY r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- OR clause +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r2 IN (0, 1, 2, 3, 4, 5, 6) AND r1 IN (0, 1, 2) AND (r1 = 2 OR r1 IN (1, 2, 3)) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Another OR clause +-- Merge scan should not be used. +\set query ':explain :Q SELECT r3, r4, r5, n, r2, r1 FROM r5n WHERE r2 IN (0, 1, 2, 3, 4, 5, 6) AND (r1 IN (0, 1, 2) OR r1 = 2 OR r1 IN (1, 2, 3)) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Cross-type SAOP: compatible type +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 = ANY (ARRAY[0, 2, 4, 6, 8]::int8[]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Cross-type SAOP: incompatible type +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 = ANY (ARRAY[0, 2, 4, 6, 8]::float[]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Typecasted LHS +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1::text = ANY (ARRAY[0, 2, 4, 6, 8]::text[]) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- ArrayExpr containing FuncExpr +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (0, 1, random()::int) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- ArrayExpr containing OpExpr +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, 100 + random()::int) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- ArrayExpr containing Param +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, (SELECT count(*) FROM r5n)) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Row IN without constant table +-- TODO(#29032): this should use merge scan. +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE (r1, r2) IN ((1, 2), (3, 4)) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Row IN with constant table +-- TODO(#29032): this should use merge scan. +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE row(r1, r2) IN (values (1, 2), (3, 4)) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Subquery with ORDER inside +\set query ':P :Q SELECT r2, r3, r4, n FROM (SELECT r2, r3, r4, n FROM r5n WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3, r4, n LIMIT 5) sub;' +\i :iter_P2 + +-- Subquery with ORDER outside +\set query ':P :Q SELECT r2, r3, r4, n FROM (SELECT r2, r3, r4, n FROM r5n WHERE r1 IN (0, 1, 2, 3)) sub ORDER BY r2, r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- NULL in IN +-- TODO(#29073) after culling array, number of streams should be 3. +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, 2, null, 3) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Only NULLs in IN +-- Third hint is to use merge scan as the second hint ends up using sort. +-- TODO(#29073): after culling array, maybe the third hint should not use SAOP +-- merge. +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (null, null, null) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\set Q3 '/*+Set(enable_sort off) Set(yb_max_merge_scan_streams 64)*/' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- Empty array +-- Third hint is to use merge scan as the second hint ends up using sort. +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 = ANY(''{}'') ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- Non-const in RHS (like var ref) +-- Merge scan should not be used. +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, r2, 2) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Large number of streams +\set query ':P :Q SELECT r4, r5, n, r1, r2, r3 FROM r5n WHERE r1 IN (0, 1, 2, 3) AND r2 IN (0, 1, 2, 3) AND r3 IN (0, 1, 2, 3) ORDER BY r4, r5, n LIMIT 5;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- Single IN hitting limit +-- Merge scan should not be used. +\set on '/*+Set(yb_max_merge_scan_streams 5)*/' +\set query ':explain :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (0, 2, 4, 6, 8, 10) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Double IN hitting limit +-- Merge scan should not be used. +\set query ':explain :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r1 IN (0, 2, 4) AND r2 IN (6, 8) ORDER BY r3, r4, r5, n LIMIT 5;' +\i :iter_Q2 + +-- Triple IN hitting limit before realizing limit could be satisfied with 0x +-- multiplier. +-- Third hint is to encourage use of merge scan like other similar empty array +-- test cases. +-- Merge scan should not be used. +\set query ':explain :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r1 IN (0, 2, 4) AND r2 IN (6, 8) AND r3 = ANY(''{}'') ORDER BY r4, r5, n LIMIT 5;' +\set Q3 '/*+Set(enable_sort off) Set(yb_max_merge_scan_streams 5)*/' +\i :iter_Q3 + +-- Triple IN avoiding limit because of 0x multiplier. +-- Third hint is to use merge scan as the second hint ends up using sort. +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r1 IN (0, 2, 4) AND r2 = ANY(''{}'') AND r3 IN (6, 8) ORDER BY r4, r5, n LIMIT 5;' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- Choose lowest cardinality IN +-- TODO(#20899): this test needs to be reconsidered when fixing this issue. +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5, 6) AND r1 IN (3, 4, 5, 6, 7) AND r1 IN (5, 6, 7, 8, 9, 10) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- Duplicates in IN +-- TODO(#29073): after culling array, this should use merge scan. +\set query ':P :Q SELECT r2, r3, r4, r5, n, r1 FROM r5n WHERE r1 IN (1, 1, 1, 2, 3, 3, 4, 5, 5, 5, 1) ORDER BY r2, r3, r4, r5, n LIMIT 5;' +\i :iter_P2 + +-- (Reset the limit change) +\set on '/*+Set(yb_max_merge_scan_streams 64)*/' + +-- #30096: Merge scan shouldn't be used in a parallel scan. +\set query ':explain :Q SELECT * FROM r5n WHERE r1 IN (0, 2, 4) AND r2 IN (6, 8) ORDER BY r3, r4, r5;' +\set Q3 '/*+Parallel(r5n 2) Set(yb_enable_parallel_scan_range_sharded true) Set(yb_parallel_range_rows 1) Set(yb_max_merge_scan_streams 0)*/' +\set Q4 '/*+Parallel(r5n 2) Set(yb_enable_parallel_scan_range_sharded true) Set(yb_parallel_range_rows 1) Set(yb_max_merge_scan_streams 64)*/' +\i :iter_Q4 + +-- Same thing with backwards scan. +\set query ':explain :Q SELECT * FROM r5n WHERE r1 IN (0, 2, 4) AND r2 IN (6, 8) ORDER BY r3 DESC, r4 DESC, r5 DESC;' +\i :iter_Q4 + +-- +-- Secondary index +-- +CREATE INDEX NONCONCURRENTLY ON r5n (r2 ASC, r3, r4, r5) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (2, 2, 2, 2), + (3)); + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) LIMIT 5;' +\i :iter_Q2 + +-- No limit +-- TODO(#29078): this likely should use merge scan. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3, r4, r5;' +\i :iter_Q2 + +-- Following queries send various numbers of requests/scan various number of rows +-- due to non-deterministic order of equal rows in merge sort, hide their DIST +\set explain 'EXPLAIN (ANALYZE, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets +\set query ':P :Q SELECT r5, 1, r5 FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- (Reset the explain change) +\set explain 'EXPLAIN (ANALYZE, DIST, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- Secondary index scan VS merge PK scan +\set query ':P :Q SELECT r2, r3, r4, n, r1 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5) AND r2 = 4 ORDER BY r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- Merge secondary index scan VS merge PK scan +-- Third hint is to use the secondary index as the second hint ends up using +-- the PK index. +\set query ':P :Q SELECT r3, r4, r5, n, r1, r2 FROM r5n WHERE r1 IN (1, 2, 3, 4) AND r2 IN (1, 2, 3, 4) ORDER BY r3, r4, r5, n LIMIT 5;' +\set Q3 '/*+IndexScan(r5n r5n_r2_r3_r4_r5_idx) Set(yb_max_merge_scan_streams 64)*/' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- (Drop this index) +DROP INDEX r5n_r2_r3_r4_r5_idx; + +-- +-- Custom opclass secondary index +-- +CREATE OPERATOR =#= ( + leftarg = int8, + rightarg = int8, + procedure = int8eq, + commutator = =#=, + negator = <> +); +CREATE FUNCTION my_int8_sort(int8,int8) RETURNS int LANGUAGE sql AS $$ + SELECT CASE WHEN $1 = $2 THEN 0 WHEN $1 > $2 THEN 1 ELSE -1 END; +$$; +CREATE OPERATOR CLASS test_int8_ops FOR TYPE int8 USING lsm AS + OPERATOR 1 < (int8,int8), OPERATOR 2 <= (int8,int8), + OPERATOR 3 =#= (int8,int8), OPERATOR 4 >= (int8,int8), + OPERATOR 5 > (int8,int8), FUNCTION 1 my_int8_sort(int8,int8); +-- For numeric, treat 0 as highest value. +CREATE FUNCTION my_numeric_lt(numeric,numeric) RETURNS bool LANGUAGE sql AS $$ + SELECT CASE + WHEN $1 = 0 THEN false + WHEN $2 = 0 THEN true + ELSE $1 < $2 + END; +$$; +CREATE FUNCTION my_numeric_le(numeric,numeric) RETURNS bool LANGUAGE sql AS $$ + SELECT CASE + WHEN $1 = $2 THEN true + WHEN $1 = 0 THEN false + WHEN $2 = 0 THEN true + ELSE $1 <= $2 + END; +$$; +CREATE FUNCTION my_numeric_ge(numeric,numeric) RETURNS bool LANGUAGE sql AS $$ + SELECT CASE + WHEN $1 = $2 THEN true + WHEN $2 = 0 THEN false + WHEN $1 = 0 THEN true + ELSE $1 >= $2 + END; +$$; +CREATE FUNCTION my_numeric_gt(numeric,numeric) RETURNS bool LANGUAGE sql AS $$ + SELECT CASE + WHEN $2 = 0 THEN false + WHEN $1 = 0 THEN true + ELSE $1 > $2 + END; +$$; +CREATE OPERATOR #<# ( + leftarg = numeric, + rightarg = numeric, + procedure = my_numeric_lt, + commutator = #>#, + negator = #>=# +); +CREATE OPERATOR #<=# ( + leftarg = numeric, + rightarg = numeric, + procedure = my_numeric_le, + commutator = #>=#, + negator = #># +); +CREATE OPERATOR #=# ( + leftarg = numeric, + rightarg = numeric, + procedure = numeric_eq, + commutator = #=#, + negator = <> +); +CREATE OPERATOR #>=# ( + leftarg = numeric, + rightarg = numeric, + procedure = my_numeric_ge, + commutator = #<=#, + negator = #<# +); +CREATE OPERATOR #># ( + leftarg = numeric, + rightarg = numeric, + procedure = my_numeric_gt, + commutator = #<#, + negator = #<=# +); +CREATE FUNCTION my_numeric_sort(numeric,numeric) RETURNS int LANGUAGE sql AS $$ + SELECT CASE + WHEN $1 = $2 THEN 0 + WHEN $1 = 0 THEN 1 + WHEN $2 = 0 THEN -1 + WHEN $1 > $2 THEN 1 + ELSE -1 + END; +$$; +CREATE OPERATOR CLASS test_numeric_ops FOR TYPE numeric USING lsm AS + OPERATOR 1 #<# (numeric,numeric), OPERATOR 2 #<=# (numeric,numeric), + OPERATOR 3 #=# (numeric,numeric), OPERATOR 4 #>=# (numeric,numeric), + OPERATOR 5 #># (numeric,numeric), FUNCTION 1 my_numeric_sort(numeric,numeric); +CREATE INDEX NONCONCURRENTLY ON r5n (r2 test_int8_ops ASC, r3, r4 test_numeric_ops, r5) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (2, 2, 2, 2), + (3)); + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 =#= ANY(ARRAY[0, 1, 2, 3]) LIMIT 5;' +\i :iter_Q2 + +-- Forward scan +-- TODO(#29383): the output is incorrect where it relies on DocDB order. +-- \set query ':P :Q SELECT * FROM r5n WHERE r2 =#= ANY(ARRAY[0, 1, 2, 3]) ORDER BY r3, r4 USING #<#, n LIMIT 5;' +-- \set Pnext :iter_Q2 +-- \i :iter_P2 + +-- Backward scan +-- TODO(#29383): the output is incorrect where it relies on DocDB order. +-- \set query ':P :Q SELECT * FROM r5n WHERE r2 =#= ANY(ARRAY[0, 1, 2, 3]) ORDER BY r3 DESC, r4 USING #>#, n LIMIT 5;' +-- \i :iter_P2 + +-- (Drop this index) +DROP INDEX r5n_r2_r3_r4_r5_idx; + +-- +-- INCLUDE index +-- +CREATE INDEX NONCONCURRENTLY ON r5n (r2 ASC, r3, r4, r5) INCLUDE (r1, n) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (2, 2, 2, 2), + (3)); + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) LIMIT 5;' +\i :iter_Q2 + +-- Following queries send various numbers of requests/scan various number of rows +-- due to non-deterministic order of equal rows in merge sort, hide their DIST +\set explain 'EXPLAIN (ANALYZE, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3, r4, n LIMIT 5;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets +\set query ':P :Q SELECT n, 1, n FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- (Reset the explain change) +\set explain 'EXPLAIN (ANALYZE, DIST, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- Secondary index only scan VS merge PK scan +\set query ':P :Q SELECT r2, r3, r4, n, r1 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5) ORDER BY r2, r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- (Drop this index) +DROP INDEX r5n_r2_r3_r4_r5_r1_n_idx; + +-- +-- Expression prefix secondary index +-- +CREATE INDEX NONCONCURRENTLY ON r5n ((greatest(r2, r3, r4) - least(r2, r3, r4)) ASC, r2, r3, r4) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (2, 2, 2, 2), + (3)); + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE (greatest(r2, r3, r4) - least(r2, r3, r4)) IN (0, 2) LIMIT 5;' +\i :iter_Q2 + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n WHERE (greatest(r2, r3, r4) - least(r2, r3, r4)) IN (0, 2) ORDER BY r2, r3, r4, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM r5n WHERE (greatest(r2, r3, r4) - least(r2, r3, r4)) IN (0, 2) ORDER BY r2 DESC, r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets +\set query ':P :Q SELECT (greatest(r2, r3, r4) - least(r2, r3, r4)), 1, (greatest(r2, r3, r4) - least(r2, r3, r4)) FROM r5n WHERE (greatest(r2, r3, r4) - least(r2, r3, r4)) IN (0, 2) ORDER BY r2 DESC, r3 DESC, r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Secondary index scan VS merge PK scan +-- Third hint is to use the PK index as the second hint ends up using the +-- expression index. +\set query ':P :Q SELECT (greatest(r2, r3, r4) - least(r2, r3, r4)), r2, r3, r4, n, r1 FROM r5n WHERE r1 IN (1, 2, 3, 4, 5) AND (greatest(r2, r3, r4) - least(r2, r3, r4)) = 4 ORDER BY r2, r3, r4, n LIMIT 5;' +\set Q3 '/*+IndexScan(r5n r5n_pkey) Set(yb_max_merge_scan_streams 64)*/' +\set Pnext :iter_Q3 +\i :iter_P2 + +-- Merge secondary index scan VS merge PK scan +-- Third hint is to use the PK index as the second hint ends up using the +-- expression index. +\set query ':P :Q SELECT r2, r3, r4, n, r1, (greatest(r2, r3, r4) - least(r2, r3, r4)) FROM r5n WHERE r1 IN (1, 2, 3, 4) AND (greatest(r2, r3, r4) - least(r2, r3, r4)) IN (1, 2, 3, 4) ORDER BY r2, r3, r4, n LIMIT 5;' +\set Q3 '/*+IndexScan(r5n r5n_pkey) Set(yb_max_merge_scan_streams 64)*/' +\i :iter_P2 + +-- (Drop this index) +DROP INDEX r5n_expr_r2_r3_r4_idx; + +-- +-- Expression suffix secondary index +-- +CREATE INDEX NONCONCURRENTLY ON r5n (r2 ASC, (-r3), (-r4)) +SPLIT AT VALUES ( + (1), + (2), + (2, -2), + (2, -2, -2), + (3)); + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 2) ORDER BY -r3, -r4, n LIMIT 5;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- Backward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 2) ORDER BY -r3 DESC, -r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets +\set query ':P :Q SELECT -r4, 1, -r4 FROM r5n WHERE r2 IN (0, 2) ORDER BY -r3 DESC, -r4 DESC, n LIMIT 5;' +\i :iter_P2 + +-- (Drop this index) +DROP INDEX r5n_r2_expr_expr1_idx; + +-- +-- Duplicate columns secondary index +-- +CREATE INDEX NONCONCURRENTLY ON r5n (r2 ASC, (r3 + r4), r2 DESC, (r3 + r4), r2) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (2, 2, 2, 2), + (2, 2, 2, 2, 2), + (3)); + +-- No order +-- Merge scan should not be used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) LIMIT 5;' +\i :iter_Q2 + +-- Following queries send various numbers of requests/scan various number of rows +-- due to non-deterministic order of equal rows in merge sort, hide their DIST +\set explain 'EXPLAIN (ANALYZE, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- Forward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4), n LIMIT 5;' +\i :iter_P2 + +-- Forward scan (v2) +-- Merge scan is not used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4), r2 DESC, n LIMIT 5;' +\i :iter_Q2 + +-- Backward scan +\set query ':P :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4) DESC, n LIMIT 5;' +\i :iter_P2 + +-- Backward scan (v2) +-- Merge scan is not used. +\set query ':explain :Q SELECT * FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4) DESC, r2, n LIMIT 5;' +\i :iter_Q2 + +-- Targets +\set query ':P :Q SELECT r5, 1, r5 FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4) DESC, n LIMIT 5;' +\i :iter_P2 + +-- Targets (v2) +-- Merge scan is not used. +\set query ':explain :Q SELECT r5, 1, r5 FROM r5n WHERE r2 IN (0, 1, 2, 3) ORDER BY (r3 + r4) DESC, r2, n LIMIT 5;' +\i :iter_Q2 + +-- (Reset the explain change) +\set explain 'EXPLAIN (ANALYZE, DIST, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' + +-- (Drop this index) +DROP INDEX r5n_r2_expr_r21_expr1_r22_idx; + +-- +-- EXPLAIN FORMAT JSON +-- +CREATE FUNCTION get_explain_property(query text, property text) RETURNS jsonb LANGUAGE plpgsql AS $$ +DECLARE + plan jsonb; +BEGIN + FOR plan IN EXECUTE 'EXPLAIN (ANALYZE, DIST, FORMAT JSON, VERBOSE)' || query LOOP + RETURN plan->0->'Plan'->property; + END LOOP; +END $$; + +SELECT get_explain_property(hint || 'SELECT * FROM r5n WHERE r1 IN (1, 2, 3) ORDER BY r2', property) +FROM unnest(ARRAY['/*+Set(yb_max_merge_scan_streams 0)*/', + '/*+Set(yb_max_merge_scan_streams 64)*/']) AS hint, + unnest(ARRAY['Merge Sort Key', + 'Merge Stream Key', + 'Merge Streams', + 'Merge Cond']) AS property; diff --git a/src/test/regress/sql/yb.orig.merge_scan_setup.sql b/src/test/regress/sql/yb.orig.merge_scan_setup.sql new file mode 100644 index 0000000000000..66c114b14792b --- /dev/null +++ b/src/test/regress/sql/yb.orig.merge_scan_setup.sql @@ -0,0 +1,165 @@ +-- +-- See yb_merge_scan_schedule for details about the test. +-- This is the setup to load the data used in tests later in the schedule. +-- + +-- +-- Range +-- +CREATE TABLE r5n ( + n int GENERATED ALWAYS AS ((r1 + r2 * 10 + r3 * 100 + r4 * 1000 + r5 * 10000)::int) STORED, + r5 float8, + r3 int, + r1 int2, + r2 int8, + r4 numeric, + PRIMARY KEY (r1 ASC, r2, r3, r4, r5)) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (2, 2, 2, 2), + (2, 2, 2, 2, 2), + (3)); +WITH g(i) AS ( + SELECT generate_series(0, 9) +), rows AS ( + INSERT INTO r5n (r1, r2, r3, r4, r5) + SELECT a.i, b.i, c.i, d.i, e.i + FROM g a, g b, g c, g d, g e + WHERE yb_hash_code(a.i, b.i, c.i, d.i, e.i) / 65535::float < 0.05 + RETURNING 1 +) SELECT count(*) FROM rows; + +-- +-- Hash +-- +CREATE TABLE h3r2n ( + n int GENERATED ALWAYS AS ((h1 + h2 * 10 + h3 * 100 + r1 * 1000 + r2 * 10000)::int) STORED, + r1 int2, + r2 float8, + h1 int2, + h2 int8, + h3 int, + PRIMARY KEY ((h1, h2, h3) HASH, r1, r2)) +SPLIT INTO 9 TABLETS; +INSERT INTO h3r2n (h1, h2, h3, r1, r2) SELECT r1, r2, r3, r4, r5 FROM r5n; + +-- +-- TODO(#9958): when btree_gin is supported, test it. +-- + +-- +-- Partitioned table +-- +CREATE TABLE parent ( + n int GENERATED ALWAYS AS ((r1 + r2 * 10 + r3 * 100 + p1 * 1000 + p2 * 10000)::int) STORED, + r3 int, + p1 int2, + r1 int2, + r2 int8, + p2 float8, + PRIMARY KEY (r1 ASC, r2, r3, p1, p2)) +PARTITION BY RANGE (p1, p2); +CREATE TABLE child1 ( + p1 int2, + r3 int, + r1 int2, + n int GENERATED ALWAYS AS ((r1 + r2 * 10 + r3 * 100 + p1 * 1000 + p2 * 10000)::int) STORED, + r2 int8, + p2 float8, + PRIMARY KEY (r1 ASC, r2, r3, p1, p2)) +PARTITION BY RANGE (p2, p1); +CREATE TABLE child1a ( + r1 int2, + n int GENERATED ALWAYS AS ((r1 + r2 * 10 + r3 * 100 + p1 * 1000 + p2 * 10000)::int) STORED, + r2 int8, + p2 float8, + p1 int2, + r3 int, + PRIMARY KEY (r1 ASC, r2, r3, p1, p2)); +CREATE TABLE child1b ( + r2 int8, + r3 int, + p2 float8, + p1 int2, + n int GENERATED ALWAYS AS ((r1 + r2 * 10 + r3 * 100 + p1 * 1000 + p2 * 10000)::int) STORED, + r1 int2, + PRIMARY KEY (r1 ASC, r2, r3, p1, p2)); +CREATE TABLE child2 ( + r2 int8, + r1 int2, + p1 int2, + p2 float8, + r3 int, + n int GENERATED ALWAYS AS ((r1 + r2 * 10 + r3 * 100 + p1 * 1000 + p2 * 10000)::int) STORED, + PRIMARY KEY (r1 ASC, r2, r3, p1, p2)); +CREATE TABLE child3 ( + r3 int, + r1 int2, + n int GENERATED ALWAYS AS ((r1 + r2 * 10 + r3 * 100 + p1 * 1000 + p2 * 10000)::int) STORED, + p2 float8, + r2 int8, + p1 int2, + PRIMARY KEY (r1 ASC, r2, r3, p1, p2)); +ALTER TABLE parent ATTACH PARTITION child1 FOR VALUES FROM (0, 0) TO (3, 6); +ALTER TABLE child1 ATTACH PARTITION child1a FOR VALUES FROM (0, 0) TO (5, 2); +ALTER TABLE child1 ATTACH PARTITION child1b FOR VALUES FROM (5, 2) TO (maxvalue, maxvalue); +ALTER TABLE parent ATTACH PARTITION child2 FOR VALUES FROM (3, 6) TO (7, 4); +ALTER TABLE parent ATTACH PARTITION child3 FOR VALUES FROM (7, 4) TO (9, 10); +INSERT INTO parent (r1, r2, r3, p1, p2) SELECT r1, r2, r3, r4, r5 FROM r5n; + +-- +-- Bucketed PK +-- +CREATE TABLE bkt_tbl ( + n int GENERATED ALWAYS AS ((r1 + r2 * 10 + r3 * 100 + r4 * 1000 + r5 * 10000)::int) STORED, + r5 float8, + r3 int, + r1 int2, + r2 int8, + r4 numeric, + bkt int GENERATED ALWAYS AS (yb_hash_code(r3, r2, r4, r5, r1) % 3) STORED, + PRIMARY KEY (bkt ASC, r1, r2, r3, r4, r5)) +SPLIT AT VALUES ( + (1), + (2), + (2, 2), + (2, 2, 2), + (2, 2, 2, 2), + (2, 2, 2, 2, 2), + (3)); +INSERT INTO bkt_tbl (r1, r2, r3, r4, r5) SELECT r1, r2, r3, r4, r5 FROM r5n; + +-- +-- Analyze +-- +ANALYZE r5n, h3r2n, parent, bkt_tbl; + +-- +-- Colocated +-- +CREATE DATABASE co WITH colocation = true; +\c co + +CREATE TABLE r5n ( + n int GENERATED ALWAYS AS ((r1 + r2 * 10 + r3 * 100 + r4 * 1000 + r5 * 10000)::int) STORED, + r5 float8, + r3 int, + r1 int2, + r2 int8, + r4 numeric, + PRIMARY KEY (r1 ASC, r2, r3, r4, r5)); +WITH g(i) AS ( + SELECT generate_series(0, 9) +), rows AS ( + INSERT INTO r5n (r1, r2, r3, r4, r5) + SELECT a.i, b.i, c.i, d.i, e.i + FROM g a, g b, g c, g d, g e + WHERE yb_hash_code(a.i, b.i, c.i, d.i, e.i) / 65535::float < 0.05 + RETURNING 1 +) SELECT count(*) FROM rows; + +ANALYZE r5n; + diff --git a/src/test/regress/sql/yb.orig.merge_scan_temp.sql b/src/test/regress/sql/yb.orig.merge_scan_temp.sql new file mode 100644 index 0000000000000..b5fb92aa8005c --- /dev/null +++ b/src/test/regress/sql/yb.orig.merge_scan_temp.sql @@ -0,0 +1,24 @@ +-- +-- See yb_merge_scan_schedule for details about the test. +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/merge_scan_setup.sql' +\i :filename + +CREATE TEMP TABLE tmp ( + r1 int2, + r2 int8, + r3 int, + r4 numeric, + r5 float8, + n int GENERATED ALWAYS AS ((r1 + r2 * 10 + r3 * 100 + r4 * 1000 + r5 * 10000)::int) STORED, + PRIMARY KEY (r1, r2, r3, r4, r5)); +INSERT INTO tmp SELECT r1, r2, r3, r4, r5 FROM r5n; + +-- Temp table +-- Merge scan should not be used. +SET enable_bitmapscan = off; +\set query ':explain :Q SELECT * FROM tmp WHERE r1 IN (0, 1, 2, 3) ORDER BY r2, r3, r4, n LIMIT 5;' +\i :iter_Q2 +RESET enable_bitmapscan; diff --git a/src/test/regress/sql/yb.orig.money.sql b/src/test/regress/sql/yb.orig.money.sql new file mode 100644 index 0000000000000..d1fe06c6f311a --- /dev/null +++ b/src/test/regress/sql/yb.orig.money.sql @@ -0,0 +1,98 @@ +-- +-- MONEY +-- + +-- +-- Test various formats +-- +DELETE FROM money_data; +INSERT INTO money_data VALUES ('0'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('-100'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('2.0001'); +SELECT * FROM money_data; +SELECT m + '0.0099' FROM money_data; +SELECT m + '0.0098' FROM money_data; +SELECT m + '0.0050' FROM money_data; +SELECT m + '0.0049' FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('5,.06'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('$3.0001'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('$40'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('1,2'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('1,23'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('100,120'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('100,23'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('1000,23'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('1,000,000.12'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('2,000.00012'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('$3,000.00012'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('$4,000,000.12'); +SELECT * FROM money_data; + +-- documented minimums and maximums +DELETE FROM money_data; +INSERT INTO money_data VALUES ('-92233720368547758.08'); +SELECT * FROM money_data; + +DELETE FROM money_data; +INSERT INTO money_data VALUES ('92233720368547758.07'); +SELECT * FROM money_data; + +-- +-- Test for PRIMARY KEY +-- + +CREATE TABLE money_data_with_pk(id MONEY PRIMARY KEY, val money); +INSERT INTO money_data_with_pk VALUES ('1.1','-11.11'); +INSERT INTO money_data_with_pk VALUES ('2.2','-22.22'); +INSERT INTO money_data_with_pk VALUES ('3.3','-33.33'); +SELECT * FROM money_data_with_pk ORDER BY id; +SELECT VAL FROM money_data_with_pk WHERE id = '$2.2'; + +-- ASC/DESC check +SELECT * FROM money_data_with_pk ORDER BY val ASC; +SELECT * FROM money_data_with_pk ORDER BY val DESC; + +CREATE TEMP TABLE IF NOT EXISTS t0(c0 money ); +INSERT INTO t0(c0) VALUES((0.3528332)::MONEY); +UPDATE t0 SET c0 = (0.7406399)::MONEY WHERE (((0.023219043)::MONEY) BETWEEN (CAST(0.19029781 AS MONEY)) AND (CAST(0.0984419 AS MONEY))) IS FALSE; diff --git a/src/test/regress/sql/yb.orig.no_explicit_target.sql b/src/test/regress/sql/yb.orig.no_explicit_target.sql new file mode 100644 index 0000000000000..e44b65d77277d --- /dev/null +++ b/src/test/regress/sql/yb.orig.no_explicit_target.sql @@ -0,0 +1,79 @@ +-- #### Test scans on the table, primary key index and the secondary index without targets #### -- +CREATE TABLE t_kv(k int, v int, PRIMARY KEY(k ASC)); +INSERT INTO t_kv SELECT x, x FROM generate_series(1, 10) AS x; +INSERT INTO t_kv VALUES (11, 1); -- test duplicate key +INSERT INTO t_kv VALUES (12, NULL); -- test null values + +-- yb seq scan +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ SeqScan(t_kv) */ SELECT k FROM t_kv; +/*+ SeqScan(t_kv) */ SELECT k FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ SeqScan(t_kv) */ SELECT FROM t_kv; +/*+ SeqScan(t_kv) */ SELECT FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ SeqScan(t_kv) */ SELECT count(1) FROM t_kv; +/*+ SeqScan(t_kv) */ SELECT count(1) FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ SeqScan(t_kv) */ SELECT count(*) FROM t_kv; +/*+ SeqScan(t_kv) */ SELECT count(*) FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ SeqScan(t_kv) */ SELECT 1 FROM t_kv; +/*+ SeqScan(t_kv) */ SELECT 1 FROM t_kv; + +-- seq scan +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT FROM t_kv; +SELECT FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT k FROM t_kv; +SELECT k FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT count(1) FROM t_kv; +SELECT count(1) FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT count(*) FROM t_kv; +SELECT count(*) FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT 1 FROM t_kv; +SELECT 1 FROM t_kv; + +-- index only scan on the primary index +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv) */ SELECT FROM t_kv; +/*+ IndexOnlyScan(t_kv) */ SELECT FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv) */ SELECT k FROM t_kv; +/*+ IndexOnlyScan(t_kv) */ SELECT k FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv) */ SELECT count(1) FROM t_kv; +/*+ IndexOnlyScan(t_kv) */ SELECT count(1) FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv) */ SELECT count(*) FROM t_kv; +/*+ IndexOnlyScan(t_kv) */ SELECT count(*) FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv) */ SELECT 1 FROM t_kv; +/*+ IndexOnlyScan(t_kv) */ SELECT 1 FROM t_kv; + +CREATE INDEX t_vi ON t_kv (v asc); + +-- index only scan on the secondary index +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv t_vi) */ SELECT FROM t_kv; +/*+ IndexOnlyScan(t_kv t_vi) */ SELECT FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv t_vi) */ SELECT k FROM t_kv; +/*+ IndexOnlyScan(t_kv t_vi) */ SELECT k FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv t_vi) */ SELECT count(1) FROM t_kv; +/*+ IndexOnlyScan(t_kv t_vi) */ SELECT count(1) FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv t_vi) */ SELECT count(*) FROM t_kv; +/*+ IndexOnlyScan(t_kv t_vi) */ SELECT count(*) FROM t_kv; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv t_vi) */ SELECT 1 FROM t_kv; +/*+ IndexOnlyScan(t_kv t_vi) */ SELECT 1 FROM t_kv; + +-- #### Test Deleting #### -- + +-- seq scan +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) DELETE FROM t_kv; +INSERT INTO t_kv SELECT x, x FROM generate_series(1, 10) AS x; +SELECT * FROM t_kv; + +-- yb seq scan +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ SeqScan(t_kv) */ DELETE FROM t_kv; +INSERT INTO t_kv SELECT x, x FROM generate_series(1, 10) AS x; +/*+ SeqScan(t_kv) */ SELECT * FROM t_kv; + +-- index only scan on the primary index +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv t_kv_pkey) */ DELETE FROM t_kv; +INSERT INTO t_kv SELECT x, x FROM generate_series(1, 10) AS x; +/*+ IndexOnlyScan(t_kv t_kv_pkey) */ SELECT * FROM t_kv; + +-- index only scan on the secondary index +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) /*+ IndexOnlyScan(t_kv t_vi) */ DELETE FROM t_kv; +INSERT INTO t_kv SELECT x, x FROM generate_series(1, 10) AS x; +/*+ IndexOnlyScan(t_kv t_vi) */ SELECT * FROM t_kv; + +DROP TABLE t_kv; diff --git a/src/test/regress/sql/yb.orig.non_txn_copy.sql b/src/test/regress/sql/yb.orig.non_txn_copy.sql new file mode 100644 index 0000000000000..842ae2ca33f47 --- /dev/null +++ b/src/test/regress/sql/yb.orig.non_txn_copy.sql @@ -0,0 +1,129 @@ +-- YB COPY TEST + +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +-- Create table. +CREATE TABLE onek ( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +); + +-- +-- Test non-txn COPY on regular table. +-- +\set filename :abs_srcdir '/data/onek.data' +COPY onek FROM :'filename'; + +-- Verify data is there. +SELECT COUNT(*) FROM onek; + +-- Clear the data. +TRUNCATE onek; + +-- +-- Test non-txn COPY on a table with an index. +-- +CREATE INDEX ON onek(unique1); +\set filename :abs_srcdir '/data/onek.data' +COPY onek FROM :'filename'; + +-- Verify data is there. +SELECT COUNT(*) FROM onek; + +-- Verify non-transactional copy on hash, range +-- and list partitioned tables. +CREATE TABLE onek_hash( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +) PARTITION BY HASH(unique1); + +CREATE TABLE onek_hash0 PARTITION OF onek_hash FOR VALUES WITH (modulus 2, remainder 0); +CREATE TABLE onek_hash1 PARTITION OF onek_hash FOR VALUES WITH (modulus 2, remainder 1); + +\set filename :abs_srcdir '/data/onek.data' +COPY onek_hash FROM :'filename'; + +SELECT COUNT(*) FROM onek_hash; + +CREATE TABLE onek_range( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +) PARTITION BY RANGE(unique2); + +CREATE TABLE onek_range0 PARTITION OF onek_range FOR VALUES FROM (0) TO (500); +CREATE TABLE onek_range1 PARTITION OF onek_range FOR VALUES FROM (500) TO (1000); + +\set filename :abs_srcdir '/data/onek.data' +COPY onek_range FROM :'filename'; + +SELECT COUNT(*) FROM onek_range; + +CREATE TABLE onek_list( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +) PARTITION BY LIST(two); + +CREATE TABLE onek_list0 PARTITION OF onek_list FOR VALUES IN (0); +CREATE TABLE onek_list1 PARTITION OF onek_list FOR VALUES IN (1); + +\set filename :abs_srcdir '/data/onek.data' +COPY onek_list FROM :'filename'; + +SELECT COUNT(*) FROM onek_list; diff --git a/src/test/regress/sql/yb.orig.numeric.sql b/src/test/regress/sql/yb.orig.numeric.sql new file mode 100644 index 0000000000000..396eac03dc0d6 --- /dev/null +++ b/src/test/regress/sql/yb.orig.numeric.sql @@ -0,0 +1,29 @@ +CREATE TABLE num_data (id int4, val numeric(210,10)); + +BEGIN TRANSACTION; +INSERT INTO num_data VALUES (0, '0'); +INSERT INTO num_data VALUES (1, '0'); +INSERT INTO num_data VALUES (2, '-34338492.215397047'); +INSERT INTO num_data VALUES (3, '4.31'); +INSERT INTO num_data VALUES (4, '7799461.4119'); +INSERT INTO num_data VALUES (5, '16397.038491'); +INSERT INTO num_data VALUES (6, '93901.57763026'); +INSERT INTO num_data VALUES (7, '-83028485'); +INSERT INTO num_data VALUES (8, '74881'); +INSERT INTO num_data VALUES (9, '-24926804.045047420'); +COMMIT TRANSACTION; + +-- ASC/DESC check +SELECT * FROM num_data ORDER BY val ASC, id ASC; +SELECT * FROM num_data ORDER BY val DESC, id DESC; + +-- +-- Test for PRIMARY KEY +-- + +CREATE TABLE num_data_with_pk(id NUMERIC PRIMARY KEY, val numeric); +INSERT INTO num_data_with_pk VALUES ('1.1','-11.11'); +INSERT INTO num_data_with_pk VALUES ('2.2','-22.22'); +INSERT INTO num_data_with_pk VALUES ('3.3','-33.33'); +SELECT * FROM num_data_with_pk ORDER BY id; +SELECT VAL FROM num_data_with_pk WHERE id = '2.2'; diff --git a/src/test/regress/sql/yb.orig.obj_properties_functions.sql b/src/test/regress/sql/yb.orig.obj_properties_functions.sql new file mode 100644 index 0000000000000..0f6043c5f93c9 --- /dev/null +++ b/src/test/regress/sql/yb.orig.obj_properties_functions.sql @@ -0,0 +1,45 @@ +-- Tests for yb_obj_properties_functions: Verify new YB functions to get table +-- or database properties. +-- +-- Test NON-COLOCATED database. +-- +-- Test yb_is_database_colocated. +SELECT yb_is_database_colocated(); + +-- Test yb_table_properties. +CREATE TABLE temp_tbl (h1 INT, h2 INT, PRIMARY KEY((h1, h2))) SPLIT INTO 7 TABLETS; + +SELECT * FROM yb_table_properties('temp_tbl'::regclass); + +-- Cleanup. +DROP TABLE temp_tbl; + +-- +-- Test COLOCATED database. +-- +CREATE DATABASE test_yb_obj_props_clc COLOCATION = true; + +\c test_yb_obj_props_clc +-- +-- Test yb_is_database_colocated. +SELECT yb_is_database_colocated(); +-- +-- Test yb_table_properties. +CREATE TABLE clc_temp_tbl (h INT PRIMARY KEY) WITH (colocation=true); +CREATE TABLE clc_temp_tbl_2 (h INT PRIMARY KEY) WITH (colocation=true, colocation_id=100500); + +SELECT + c.relname, + props.num_tablets, + props.num_hash_key_columns, + props.is_colocated, + grp.grpname, + props.colocation_id +FROM pg_class c, yb_table_properties(c.oid) props +JOIN pg_yb_tablegroup grp ON grp.oid = props.tablegroup_oid +WHERE c.relname LIKE 'clc_%' AND c.relkind <> 'i' ORDER BY c.oid; + +-- Cleanup. +DROP TABLE clc_temp_tbl; +\c yugabyte +DROP DATABASE test_yb_obj_props_clc; diff --git a/src/test/regress/sql/yb.orig.oid.sql b/src/test/regress/sql/yb.orig.oid.sql new file mode 100644 index 0000000000000..a5e625ea8815d --- /dev/null +++ b/src/test/regress/sql/yb.orig.oid.sql @@ -0,0 +1,38 @@ +CREATE TABLE oid_table( + id OID PRIMARY KEY, + value OID, + tag INT +); + +INSERT INTO oid_table (id, value, tag) VALUES (0, 10, 0); +INSERT INTO oid_table (id, value, tag) VALUES (1, 21, 1); +INSERT INTO oid_table (id, value, tag) VALUES (1000, 1010, 2); +INSERT INTO oid_table (id, value, tag) VALUES (1000000, 1000010, 3); +INSERT INTO oid_table (id, value, tag) VALUES (1000000000, 1000000010, 4); +INSERT INTO oid_table (id, value, tag) VALUES (2000000000, 2000000020, 5); +INSERT INTO oid_table (id, value, tag) VALUES (2147483647, 2147483647, 6); +INSERT INTO oid_table (id, value, tag) VALUES (2147483648, 2147483648, 7); +INSERT INTO oid_table (id, value, tag) VALUES (2147483649, 2147483649, 8); +INSERT INTO oid_table (id, value, tag) VALUES (4294967295, 4294967295, 9); +INSERT INTO oid_table (id, value, tag) VALUES (4294967296, 4294967296, 10); +INSERT INTO oid_table (id, value, tag) VALUES (4294967297, 4294967297, 11); + +INSERT INTO oid_table (id, value, tag) VALUES (-1, -10, 100); +INSERT INTO oid_table (id, value, tag) VALUES (-2, -21, 101); +INSERT INTO oid_table (id, value, tag) VALUES (-1000, -1010, 102); +INSERT INTO oid_table (id, value, tag) VALUES (-1000000, -1000010, 103); +INSERT INTO oid_table (id, value, tag) VALUES (-1000000000, -1000000010, 104); +INSERT INTO oid_table (id, value, tag) VALUES (-2000000000, -2000000020, 105); +INSERT INTO oid_table (id, value, tag) VALUES (-2147483647, -2147483647, 106); +INSERT INTO oid_table (id, value, tag) VALUES (-2147483648, -2147483648, 107); +INSERT INTO oid_table (id, value, tag) VALUES (-2147483649, -2147483649, 108); +INSERT INTO oid_table (id, value, tag) VALUES (-4294967295, -4294967295, 109); +INSERT INTO oid_table (id, value, tag) VALUES (-4294967296, -4294967296, 110); +INSERT INTO oid_table (id, value, tag) VALUES (-4294967297, -4294967297, 111); + +SELECT * FROM oid_table ORDER BY id; +SELECT * FROM oid_table ORDER BY id DESC; +SELECT * FROM oid_table ORDER BY value; +SELECT * FROM oid_table ORDER BY value DESC; + +DROP TABLE oid_table; diff --git a/src/test/regress/sql/yb.orig.operator_class.sql b/src/test/regress/sql/yb.orig.operator_class.sql new file mode 100644 index 0000000000000..6ead244a592d8 --- /dev/null +++ b/src/test/regress/sql/yb.orig.operator_class.sql @@ -0,0 +1,79 @@ +-- Create user type +CREATE TYPE user_type AS ( + i INT, + v VARCHAR +); + +SELECT typname FROM pg_type WHERE typname LIKE '%user_type'; + +CREATE TABLE user_table (id bigserial PRIMARY KEY, val user_type); + +INSERT INTO user_table(val) VALUES ((1, 'aaaa')::user_type); +INSERT INTO user_table(val) VALUES ((10, 'aa')::user_type); +INSERT INTO user_table(val) VALUES ((2, 'b')::user_type); + +-- Default ordering will be based on first base type ie. (i INT) +SELECT * FROM user_table ORDER BY val; + +-- Create custom operators and comparison function for user_type +CREATE FUNCTION ut_lt(user_type, user_type) RETURNS boolean +AS $$BEGIN return $1.v < $2.v; END$$ +LANGUAGE plpgsql immutable; + +CREATE FUNCTION ut_lte(user_type, user_type) RETURNS boolean +AS $$BEGIN return $1.v <= $2.v; END$$ +LANGUAGE plpgsql immutable; + +CREATE FUNCTION ut_eq(user_type, user_type) RETURNS boolean +AS $$BEGIN return $1.v = $2.v; END$$ +LANGUAGE plpgsql immutable; + +CREATE FUNCTION ut_gt(user_type, user_type) RETURNS boolean +AS $$BEGIN return $1.v > $2.v; END$$ +LANGUAGE plpgsql immutable; + +CREATE FUNCTION ut_gte(user_type, user_type) RETURNS boolean +AS $$BEGIN return $1.v >= $2.v; END$$ +LANGUAGE plpgsql immutable; + +CREATE OPERATOR < (procedure = ut_lt, + leftarg = user_type, rightarg = user_type); + +CREATE OPERATOR <= (procedure = ut_lte, + leftarg = user_type, rightarg = user_type); + +CREATE OPERATOR = (procedure = ut_eq, + leftarg = user_type, rightarg = user_type); + +CREATE OPERATOR > (procedure = ut_gt, + leftarg = user_type, rightarg = user_type); + +CREATE OPERATOR >= (procedure = ut_gte, + leftarg = user_type, rightarg = user_type); + +CREATE FUNCTION ut_cmp(user_type, user_type) RETURNS INT +AS $$BEGIN + IF $1.v < $2.v THEN + RETURN -1; + ELSIF $1.v > $2.v THEN + RETURN 1; + END IF; + RETURN 0; +END$$ +LANGUAGE plpgsql immutable; + +-- Create an operator class binding the above functions to user_type +CREATE OPERATOR CLASS user_type_op_class_1 +DEFAULT FOR TYPE user_type USING LSM +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 >, + FUNCTION 1 ut_cmp(user_type, user_type); + +-- Ensure that ORDER BY user data type works +SELECT * FROM user_table ORDER BY val; + +DROP OPERATOR CLASS user_type_op_class_1 USING lsm; diff --git a/src/test/regress/sql/yb.orig.optimizer_guc.sql b/src/test/regress/sql/yb.orig.optimizer_guc.sql new file mode 100644 index 0000000000000..1deb131a11d5a --- /dev/null +++ b/src/test/regress/sql/yb.orig.optimizer_guc.sql @@ -0,0 +1,317 @@ +-- +-- test yb_enable_cbo and legacy counterpart value sync +-- + +set client_min_messages = warning; +drop function if exists check_optimizer_guc; + +create function check_optimizer_guc() returns text +language plpgsql as +$$ +declare + cbo text; + bscm text; + stats text; + vals text; + valid_vals text[] := '{"(on, on, on)"}'::text[] || + '{"(on, on, off)"}'::text[] || + '{"(legacy_mode, off, off)"}'::text[] || + '{"(off, off, off)"}'::text[] || /* legacy ignore stats */ + '{"(legacy_stats_mode, off, on)"}'::text[] || + '{"(legacy_bnl_mode, off, off)"}'::text[] || + '{"(legacy_stats_bnl_mode, off, on)"}'::text[] || + '{"(legacy_ignore_stats_bnl_mode, off, off)"}'::text[]; +begin + execute 'show yb_enable_cbo' into cbo; + execute 'show yb_enable_base_scans_cost_model' into bscm; + execute 'show yb_enable_optimizer_statistics' into stats; + vals := '(' || cbo || ', ' || bscm || ', ' || stats || ')'; + if vals = any(valid_vals) then + return vals; + else + return '*** Invalid value combination: ' || vals; + end if; +end; +$$; + + +reset yb_enable_cbo; +reset yb_enable_base_scans_cost_model; +reset yb_enable_optimizer_statistics; + +select check_optimizer_guc() "(cbo, bscm, stats)"; + +-------------------------- +-- change yb_enable_cbo -- +-------------------------- + +set yb_enable_cbo = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_stats_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_stats_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_stats_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_stats_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_ignore_stats_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + + +-------------------------------- +-- turn on/off old parameters -- +-------------------------------- + +-- (bscm, stats) off, off -> stats = on +set yb_enable_optimizer_statistics = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +-- (bscm, stats) off, on -> bscm = on +set yb_enable_base_scans_cost_model = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +-- (bscm, stats) on, on -> bscm = off +set yb_enable_base_scans_cost_model = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +-- (bscm, stats) off, on -> stats = off +set yb_enable_optimizer_statistics = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +-- (bscm, stats) off, off -> bscm = on +set yb_enable_base_scans_cost_model = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +-- (bscm, stats) on, off -> stats = on +set yb_enable_optimizer_statistics = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +-- (bscm, stats) on, on -> stats = off +set yb_enable_optimizer_statistics = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +-- (bscm, stats) on, off -> bscm = off +set yb_enable_base_scans_cost_model = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + + +----------------------------------------------------------------------------- +-- no legacy_((ignore_)stats_)bnl_mode after changing either bscm or stats -- +----------------------------------------------------------------------------- + +set yb_enable_cbo = legacy_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_base_scans_cost_model = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_base_scans_cost_model = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_optimizer_statistics = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_optimizer_statistics = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + + +set yb_enable_cbo = legacy_stats_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_base_scans_cost_model = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_stats_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_base_scans_cost_model = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_stats_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_optimizer_statistics = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_stats_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_optimizer_statistics = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + + +set yb_enable_cbo = legacy_ignore_stats_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_base_scans_cost_model = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_ignore_stats_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_base_scans_cost_model = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_ignore_stats_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_optimizer_statistics = on; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_cbo = legacy_ignore_stats_bnl_mode; +select check_optimizer_guc() "(cbo, bscm, stats)"; + +set yb_enable_optimizer_statistics = off; +select check_optimizer_guc() "(cbo, bscm, stats)"; + + +--------------------- +-- boolean aliases -- +--------------------- + +set yb_enable_cbo = true; +show yb_enable_cbo; +set yb_enable_cbo = false; +show yb_enable_cbo; +set yb_enable_cbo = yes; +show yb_enable_cbo; +set yb_enable_cbo = no; +show yb_enable_cbo; +set yb_enable_cbo = 1; +show yb_enable_cbo; +set yb_enable_cbo = 0; +show yb_enable_cbo; + + +----------- +-- error -- +----------- + +set yb_enable_cbo = oui; + + +---------------------------------------------------------------- +-- yb_enable_parallel_append - enable_parallel_append mapping -- +---------------------------------------------------------------- +show yb_enable_parallel_append; +show enable_parallel_append; +set yb_enable_parallel_append = off; +show enable_parallel_append; +set enable_parallel_append = on; +show yb_enable_parallel_append; + + +--------------------------- +-- test GUC_EXPLAIN flag -- +--------------------------- + +reset all; + +explain (settings, costs off) +select * from pg_class; + +set yb_enable_cbo = on; + +explain (settings, costs off) +select * from pg_class; + +set yb_enable_cbo = legacy_stats_mode; + +explain (settings, costs off) +select * from pg_class; + +set yb_bnl_optimize_first_batch = off; +set yb_prefer_bnl = off; +set yb_enable_batchednl = off; +set yb_enable_geolocation_costing = off; +set yb_enable_distinct_pushdown = off; +set yb_test_skip_binding_scan_keys = on; +set yb_enable_bitmapscan = on; +set yb_bnl_batch_size = 1; +set yb_fetch_row_limit = 0; +set yb_fetch_size_limit = '2kB'; +set yb_parallel_range_rows = 10; +set yb_parallel_range_size = '1kB'; +set yb_network_fetch_cost = 100; + +explain (settings, costs off) +select * from pg_class; + + +--------------------------------------------------------------------- +-- reset all then try a few yb_ parameters that won't affect plans -- +--------------------------------------------------------------------- + +reset all; + +explain (settings, costs off) +select * from pg_class; + +set yb_enable_memory_tracking = off; +set yb_enable_planner_trace = on; + +explain (settings, costs off) +select * from pg_class; diff --git a/src/test/regress/sql/yb.orig.parallel_append.sql b/src/test/regress/sql/yb.orig.parallel_append.sql new file mode 100644 index 0000000000000..3365bc8e301b1 --- /dev/null +++ b/src/test/regress/sql/yb.orig.parallel_append.sql @@ -0,0 +1,69 @@ +create table t(k bigint primary key, v text); +create table t1(k bigint primary key, v text); +create table t2(k bigint primary key, v text); +create table lp (a char) partition by list (a); +create table lp_default partition of lp default; +create table lp_ef partition of lp for values in ('e', 'f'); +create table lp_ad partition of lp for values in ('a', 'd'); +create table lp_bc partition of lp for values in ('b', 'c'); +create table lp_null partition of lp for values in (null); + +insert into t values (2, 'value_t_2'), (3, 'value_t_3'); +insert into t1 values (1, 'value_t1_1'), (2, 'value_t1_2'); +insert into t2 values (3, 'value_t2_3'), (4, 'value_t2_4'); + +set yb_enable_base_scans_cost_model to true; +set enable_parallel_append to false; +set parallel_setup_cost to 0; +set parallel_tuple_cost to 0; + +-- simple union +explain (costs off) +select * from t1 union select * from t2; +select * from t1 union select * from t2; + +-- simple union all +explain (costs off) +select * from t1 union all select * from t2; +select * from t1 union all select * from t2; + +-- joins union +explain (costs off) +select * from t, t1 where t.k = t1.k union select * from t, t2 where t.k = t2.k; +select * from t, t1 where t.k = t1.k union select * from t, t2 where t.k = t2.k; + +-- joins union all +explain (costs off) +select * from t, t1 where t.k = t1.k union all select * from t, t2 where t.k = t2.k; +select * from t, t1 where t.k = t1.k union all select * from t, t2 where t.k = t2.k; + +-- partitioned table +explain (costs off) +select * from lp; +select * from lp; + +set enable_parallel_append to true; + +-- do not execute those parallel queries, there are known issues with YB PA +explain (costs off) +select * from t1 union select * from t2; + +-- simple union all +explain (costs off) +select * from t1 union all select * from t2; + +-- joins union +explain (costs off) +select * from t, t1 where t.k = t1.k union select * from t, t2 where t.k = t2.k; + +-- joins union all +explain (costs off) +select * from t, t1 where t.k = t1.k union all select * from t, t2 where t.k = t2.k; + +-- partitioned table +explain (costs off) +select * from lp; + +-- union of non-YB relations (#21733) +explain (costs off) +/*+ Set(enable_seqscan OFF) */ select 'l' UNION ALL (SELECT 'g') ORDER BY 1; diff --git a/src/test/regress/sql/yb.orig.parallel_big_colocated.sql b/src/test/regress/sql/yb.orig.parallel_big_colocated.sql new file mode 100644 index 0000000000000..a8ae62734e79a --- /dev/null +++ b/src/test/regress/sql/yb.orig.parallel_big_colocated.sql @@ -0,0 +1,79 @@ +CREATE DATABASE pctest colocation = true; +\c pctest + +-- large table to ensure multiple parallel ranges +CREATE TABLE pcustomer ( + pc_id int primary key, + pc_name text, + pc_address text, + pc_phone text, + pc_email text, + pc_acctbalance numeric(15,2)) WITH (colocation = true); +CREATE INDEX on pcustomer(pc_phone ASC); +INSERT INTO pcustomer +SELECT i, -- pc_id + 'Customer #' || i::text, -- pc_name + (1234 + i * 2)::text || ' Unknown Str., Some Random City, XX, ' || (567 * i % 89991 + 10001)::text, -- pc_address + '(' || (i % 399 + 100)::text || ')' || ((i + 13) % 897 + 101)::text || '-' || ((i + 4321) % 8999 + 1000)::text, -- pc_phone + 'customer' || i::text || '@example.com', -- pc_email + random() * 25000.0 - 10000.0 +FROM generate_series(1, 500000) i; +ANALYZE pcustomer; + +set yb_parallel_range_rows to 10000; +set yb_enable_base_scans_cost_model to true; + +EXPLAIN (costs off) +SELECT pc_id, pc_address, pc_phone, pc_email FROM pcustomer WHERE pc_name = 'Customer #42'; +SELECT pc_id, pc_address, pc_phone, pc_email FROM pcustomer WHERE pc_name = 'Customer #42'; + +-- parallel query in transaction and subtransaction +BEGIN; + +UPDATE pcustomer SET pc_acctbalance = 0 WHERE pc_id >= 40 AND pc_id < 50; +EXPLAIN (costs off) +SELECT * FROM pcustomer WHERE pc_name LIKE 'Customer #4_'; +SELECT * FROM pcustomer WHERE pc_name LIKE 'Customer #4_'; + +SAVEPOINT svp; +UPDATE pcustomer SET pc_acctbalance = 100 WHERE pc_id = 42; +EXPLAIN (costs off) +SELECT * FROM pcustomer WHERE pc_name LIKE 'Customer #4_'; +SELECT * FROM pcustomer WHERE pc_name LIKE 'Customer #4_'; + +COMMIT; + +-- backward scan +set enable_sort to false; +EXPLAIN (costs off) +SELECT pc_id, pc_address, pc_phone, pc_email FROM pcustomer WHERE pc_name like 'Customer #4_' ORDER BY pc_phone DESC; +SELECT pc_id, pc_address, pc_phone, pc_email FROM pcustomer WHERE pc_name like 'Customer #4_' ORDER BY pc_phone DESC; +reset enable_sort; + +-- forced parallel scan by PK +/*+ Parallel(pcustomer 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pcustomer WHERE pc_id = 42; +/*+ Parallel(pcustomer 2 hard) */ +SELECT * FROM pcustomer WHERE pc_id = 42; +EXPLAIN (costs off) +/*+ Parallel(pcustomer 2 hard) */ +SELECT pc_id, pc_address, pc_phone, pc_email FROM pcustomer WHERE pc_id > 24 and pc_id < 42 order by pc_id; +/*+ Parallel(pcustomer 2 hard) */ +SELECT pc_id, pc_address, pc_phone, pc_email FROM pcustomer WHERE pc_id > 24 and pc_id < 42 order by pc_id; + +-- parallel scan by secondary index +EXPLAIN (costs off) +/*+ Parallel(pcustomer 2 hard) */ +SELECT pc_id, pc_address, pc_phone, pc_email FROM pcustomer WHERE pc_phone like '(408)101-%'; +/*+ Parallel(pcustomer 2 hard) */ +SELECT pc_id, pc_address, pc_phone, pc_email FROM pcustomer WHERE pc_phone like '(408)101-%'; + +EXPLAIN (costs off) +/*+ Parallel(pcustomer 2 hard) */ +SELECT pc_id, pc_address, pc_phone, pc_email FROM pcustomer WHERE pc_phone IN ('(125)139-5346', '(128)142-5349', '(142)156-5363', '(129)143-5350'); +/*+ Parallel(pcustomer 2 hard) */ +SELECT pc_id, pc_address, pc_phone, pc_email FROM pcustomer WHERE pc_phone IN ('(125)139-5346', '(128)142-5349', '(142)156-5363', '(129)143-5350'); + +DROP TABLE pcustomer; + diff --git a/src/test/regress/sql/yb.orig.parallel_colocated.sql b/src/test/regress/sql/yb.orig.parallel_colocated.sql new file mode 100644 index 0000000000000..810d323ed91c3 --- /dev/null +++ b/src/test/regress/sql/yb.orig.parallel_colocated.sql @@ -0,0 +1,378 @@ +-- +-- PARALLEL queries to colocated tables +-- + +CREATE DATABASE pctest colocation = true; +\c pctest + +-- create tables +CREATE TABLE pctest1(k int primary key, a int, b int, c int, d text) + WITH (colocation = true); +CREATE TABLE pctest2(k int primary key, a int, b int, c int, d text) + WITH (colocation = true); +CREATE UNIQUE INDEX ON pctest1(a); +CREATE INDEX ON pctest1(c); +CREATE INDEX ON pctest2(b); +INSERT INTO pctest1 + SELECT i, 1000 - i, i/3, i%50, 'Value' || i::text FROM generate_series(1, 1000) i; +INSERT INTO pctest2 + SELECT i, 200 + i, i/5, i%10, 'Other value ' || i::text FROM generate_series(1, 200) i; +ANALYZE pctest1, pctest2; + +-- set smaller parallel interval to produce more ranges +set yb_parallel_range_size to 1024; + +-- enable parallel query for YB tables +set yb_parallel_range_rows to 1; +set yb_enable_base_scans_cost_model to true; + +-- parallel bitmap scan not supported yet +set enable_bitmapscan = false; + +-- encourage use of parallel plans for the remaining tests by gucs +-- since the "hard" option of Parallel hint is broken. (#26181) +set parallel_setup_cost=0; +set parallel_tuple_cost=0; + +-- Parallel sequential scan +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE d LIKE 'Value_9'; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE d LIKE 'Value_9'; + +-- with aggregates +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT count(*) FROM pctest1 WHERE d LIKE 'Value_9'; +/*+ Parallel(pctest1 2 hard) */ +SELECT count(*) FROM pctest1 WHERE d LIKE 'Value_9'; + +-- with sort +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY b DESC; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY b DESC; + +-- with grouping +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT b, count(*) FROM pctest1 WHERE d LIKE 'Value9%' GROUP BY b; +/*+ Parallel(pctest1 2 hard) */ +SELECT b, count(*) FROM pctest1 WHERE d LIKE 'Value9%' GROUP BY b; + +-- Parallel index scan +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE k < 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE k < 10; + +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY k; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY k; + +--secondary index +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE c = 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE c = 10; + +--secondary index +EXPLAIN (costs off) +SELECT * FROM pctest1 ORDER BY a LIMIT 10; +SELECT * FROM pctest1 ORDER BY a LIMIT 10; + +-- with aggregates +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT count(*) FROM pctest1 WHERE k > 123; +/*+ Parallel(pctest1 2 hard) */ +SELECT count(*) FROM pctest1 WHERE k > 123; + +-- index only +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT a FROM pctest1 WHERE a < 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT a FROM pctest1 WHERE a < 10; + +-- with grouping +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT c, count(*) FROM pctest1 WHERE c > 40 GROUP BY c; +/*+ Parallel(pctest1 2 hard) */ +SELECT c, count(*) FROM pctest1 WHERE c > 40 GROUP BY c; + +-- Joins +-- Nest loop +/*+ + Set(enable_mergejoin off) Set(enable_hashjoin off) + Set(yb_bnl_batch_size 1) Set(enable_material off) +*/ +EXPLAIN (costs off) +SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.a = pctest2.b and pctest1.a % 10 = 0; +/*+ + Set(enable_mergejoin off) Set(enable_hashjoin off) + Set(yb_bnl_batch_size 1) Set(enable_material off) +*/ +SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.a = pctest2.b and pctest1.a % 10 = 0; +EXPLAIN (costs off) +/*+YbBatchedNL(pctest1 pctest2) Parallel(pctest1 2 hard)*/ +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +/*+YbBatchedNL(pctest1 pctest2) Parallel(pctest1 2 hard)*/ +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; + +-- Hash join +set enable_mergejoin to false; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT pctest1.k, pctest2.k FROM pctest1 JOIN pctest2 USING (a, c); +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.k, pctest2.k FROM pctest1 JOIN pctest2 USING (a, c); +reset enable_mergejoin; + +-- Merge join +set enable_hashjoin to false; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +reset enable_hashjoin; + +-- Subquery +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT x, d FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) ss RIGHT JOIN + (values (15),(16),(17)) v(x) on ss.b = v.x ORDER BY x; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT x, d FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) ss RIGHT JOIN + (values (15),(16),(17)) v(x) on ss.b = v.x ORDER BY x; + +/*+ + Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) + Parallel(pctest3 2 hard) Parallel(pctest4 2 hard) + */ +EXPLAIN (costs off) +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest4.* FROM pctest1 pctest3, pctest2 pctest4 + WHERE pctest3.k = pctest4.k AND pctest3.b = pctest4.b) s2 ON s1.b = s2.c; +/*+ + Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) + Parallel(pctest3 2 hard) Parallel(pctest4 2 hard) + */ +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest4.* FROM pctest1 pctest3, pctest2 pctest4 + WHERE pctest3.k = pctest4.k AND pctest3.b = pctest4.b) s2 ON s1.b = s2.c; + +-- no parallelism +EXPLAIN (costs off) +SELECT * from pctest2 + WHERE b < (SELECT avg(b) / 20 FROM pctest1 WHERE c = pctest2.c); +SELECT * from pctest2 + WHERE b < (SELECT avg(b) / 20 FROM pctest1 WHERE c = pctest2.c); + +-- passing parameters to workers +/*+ Parallel(pctest1 2 hard) Parallel(pctest1_1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT * from pctest2 + WHERE c IN (SELECT b FROM pctest1 WHERE d LIKE 'Value_9') + AND b < (SELECT avg(b) FROM pctest1 WHERE d LIKE 'Value_9'); +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT * from pctest2 + WHERE c IN (SELECT b FROM pctest1 WHERE d LIKE 'Value_9') + AND b < (SELECT avg(b) FROM pctest1 WHERE d LIKE 'Value_9'); + +-- test rescan cases +set enable_material = false; + +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +select * from + (SELECT count(*) FROM pctest1 WHERE b > 10) ss + right join (values (1),(2),(3)) v(x) on true; +select * from + (SELECT count(*) FROM pctest1 WHERE b > 10) ss + right join (values (1),(2),(3)) v(x) on true; + +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +select * from + (SELECT count(*) FROM pctest1 WHERE c > 10) ss + right join (values (1),(2),(3)) v(x) on true; +select * from + (SELECT count(*) FROM pctest1 WHERE c > 10) ss + right join (values (1),(2),(3)) v(x) on true; + +reset enable_material; + +-- Modify table (no parallelism) +EXPLAIN (costs off) +UPDATE pctest1 SET b = 0 WHERE d LIKE 'Value_9'; +UPDATE pctest1 SET b = 0 WHERE d LIKE 'Value_9'; +SELECT count(*) FROM pctest1 WHERE b = 0; + +EXPLAIN (costs off) +DELETE FROM pctest1 WHERE d LIKE 'Value_8'; +DELETE FROM pctest1 WHERE d LIKE 'Value_8'; +SELECT count(*) FROM pctest1; + +-- index scan with aggregates pushdown such that #atts being pushed down > #atts in relation +CREATE TABLE pctest3(k int primary key, a int unique) WITH (colocation = true); +INSERT INTO pctest3 SELECT i, i FROM generate_series(1, 1000) i; +ANALYZE pctest3; +/*+ Parallel(pctest3 2 hard) */ +EXPLAIN (costs off) SELECT count(*), max(k), min(k) FROM pctest3 WHERE k > 123; +/*+ Parallel(pctest3 2 hard) */ +SELECT count(*), max(k), min(k) FROM pctest3 WHERE k > 123; + +-- Try with parallel hints instead of setting costing GUCs. +reset parallel_setup_cost; +reset parallel_tuple_cost; + +-- Parallel index scan +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE k < 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE k < 10; + +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY k; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY k; + +--secondary index +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE c = 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE c = 10; + +--secondary index +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 ORDER BY a LIMIT 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 ORDER BY a LIMIT 10; + +-- with aggregates +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT count(*) FROM pctest1 WHERE k > 123; +/*+ Parallel(pctest1 2 hard) */ +SELECT count(*) FROM pctest1 WHERE k > 123; + +-- index only +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT a FROM pctest1 WHERE a < 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT a FROM pctest1 WHERE a < 10; + +-- with grouping +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT c, count(*) FROM pctest1 WHERE c > 40 GROUP BY c; +/*+ Parallel(pctest1 2 hard) */ +SELECT c, count(*) FROM pctest1 WHERE c > 40 GROUP BY c; + +-- Joins +-- Nest loop +/*+ + Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) + Set(enable_mergejoin off) Set(enable_hashjoin off) + Set(yb_bnl_batch_size 1) Set(enable_material off) +*/ +EXPLAIN (costs off) +SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.a = pctest2.b and pctest1.a % 10 = 0; +/*+ + Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) + Set(enable_mergejoin off) Set(enable_hashjoin off) + Set(yb_bnl_batch_size 1) Set(enable_material off) +*/ +SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.a = pctest2.b and pctest1.a % 10 = 0; +EXPLAIN (costs off) +/*+YbBatchedNL(pctest1 pctest2) Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +/*+YbBatchedNL(pctest1 pctest2) Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; + +-- Hash join +set enable_mergejoin to false; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT pctest1.k, pctest2.k FROM pctest1 JOIN pctest2 USING (a, c) ORDER BY pctest1.k, pctest2.k; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.k, pctest2.k FROM pctest1 JOIN pctest2 USING (a, c) ORDER BY pctest1.k, pctest2.k; +reset enable_mergejoin; + +-- Merge join +set enable_hashjoin to false; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +reset enable_hashjoin; + +-- Subquery +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT x, d FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) ss RIGHT JOIN + (values (15),(16),(17)) v(x) on ss.b = v.x ORDER BY x; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT x, d FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) ss RIGHT JOIN + (values (15),(16),(17)) v(x) on ss.b = v.x ORDER BY x; + +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest2.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.b = pctest2.b) s2 ON s1.b = s2.c; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest2.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.b = pctest2.b) s2 ON s1.b = s2.c; + +-- index only scan with aggregates pushdown such that #atts being pushed down > #atts in relation +/*+ Parallel(pctest3 3 hard) */ +EXPLAIN (costs off) SELECT count(*), max(a), min(a) FROM pctest3 WHERE a > 123; +/*+ Parallel(pctest3 3 hard) */ +SELECT count(*), max(a), min(a) FROM pctest3 WHERE a > 123; + +DROP TABLE pctest1; +DROP TABLE pctest2; +DROP TABLE pctest3; diff --git a/src/test/regress/sql/yb.orig.parallel_hashed.sql b/src/test/regress/sql/yb.orig.parallel_hashed.sql new file mode 100644 index 0000000000000..35fafc689ad25 --- /dev/null +++ b/src/test/regress/sql/yb.orig.parallel_hashed.sql @@ -0,0 +1,279 @@ +-- +-- PARALLEL queries to hash sharded tables +-- + +-- create tables +CREATE TABLE pctest1(k int, a int, b int, c int, d text, primary key(k hash)); +CREATE TABLE pctest2(k int, a int, b int, c int, d text, primary key(k hash)); +CREATE UNIQUE INDEX ON pctest1(a hash); +CREATE INDEX ON pctest1(c hash) split into 3 tablets; +CREATE INDEX ON pctest2(b hash) split into 3 tablets; +INSERT INTO pctest1 + SELECT i, 1000 - i, i/3, i%50, 'Value' || i::text FROM generate_series(1, 1000) i; +INSERT INTO pctest2 + SELECT i, 200 + i, i/5, i%10, 'Other value ' || i::text FROM generate_series(1, 200) i; +ANALYZE pctest1, pctest2; + +set yb_enable_parallel_scan_hash_sharded to true; +-- set smaller parallel interval to produce more ranges +set yb_parallel_range_size to 1024; + +-- enable parallel query for YB tables +set yb_parallel_range_rows to 1; +set yb_enable_cbo = on; + +-- parallel bitmap scan not supported yet +set enable_bitmapscan = false; + +-- Parallel sequential scan +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE d LIKE 'Value_9'; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE d LIKE 'Value_9'; + +-- with aggregates +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT count(*) FROM pctest1 WHERE d LIKE 'Value_9'; +/*+ Parallel(pctest1 2 hard) */ +SELECT count(*) FROM pctest1 WHERE d LIKE 'Value_9'; + +-- with sort +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY b DESC; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY b DESC; + +-- with grouping +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT b, count(*) FROM pctest1 WHERE d LIKE 'Value9%' GROUP BY b; +/*+ Parallel(pctest1 2 hard) */ +SELECT b, count(*) FROM pctest1 WHERE d LIKE 'Value9%' GROUP BY b; + +-- Parallel index scan +--secondary index +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE c = 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE c = 10; + +-- with aggregates +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT count(*) FROM pctest1 WHERE k > 123; +/*+ Parallel(pctest1 2 hard) */ +SELECT count(*) FROM pctest1 WHERE k > 123; + +-- index only +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT a FROM pctest1 WHERE a < 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT a FROM pctest1 WHERE a < 10; + +-- with grouping +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT c, count(*) FROM pctest1 WHERE c > 40 GROUP BY c; +/*+ Parallel(pctest1 2 hard) */ +SELECT c, count(*) FROM pctest1 WHERE c > 40 GROUP BY c; + +-- Subquery +/*+ + Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) + Parallel(pctest3 2 hard) Parallel(pctest4 2 hard) + */ +EXPLAIN (costs off) +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest4.* FROM pctest1 pctest3, pctest2 pctest4 + WHERE pctest3.k = pctest4.k AND pctest3.b = pctest4.b) s2 ON s1.b = s2.c +ORDER BY s1.k; +/*+ + Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) + Parallel(pctest3 2 hard) Parallel(pctest4 2 hard) + */ +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest4.* FROM pctest1 pctest3, pctest2 pctest4 + WHERE pctest3.k = pctest4.k AND pctest3.b = pctest4.b) s2 ON s1.b = s2.c +ORDER BY s1.k; + +-- no parallelism +EXPLAIN (costs off) +SELECT * from pctest2 + WHERE b < (SELECT avg(b) / 20 FROM pctest1 WHERE c = pctest2.c); +SELECT * from pctest2 + WHERE b < (SELECT avg(b) / 20 FROM pctest1 WHERE c = pctest2.c); + +-- passing parameters to workers +/*+ Parallel(pctest1 2 hard) Parallel(pctest1_1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT * from pctest2 + WHERE c IN (SELECT b FROM pctest1 WHERE d LIKE 'Value_9') + AND b < (SELECT avg(b) FROM pctest1 WHERE d LIKE 'Value_9'); +/*+ Parallel(pctest1 2 hard) Parallel(pctest1_1 2 hard) Parallel(pctest2 2 hard) */ +SELECT * from pctest2 + WHERE c IN (SELECT b FROM pctest1 WHERE d LIKE 'Value_9') + AND b < (SELECT avg(b) FROM pctest1 WHERE d LIKE 'Value_9'); + +-- test rescan cases +set enable_material = false; + +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +select * from + (SELECT count(*) FROM pctest1 WHERE b > 10) ss + right join (values (1),(2),(3)) v(x) on true; +select * from + (SELECT count(*) FROM pctest1 WHERE b > 10) ss + right join (values (1),(2),(3)) v(x) on true; + +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +select * from + (SELECT count(*) FROM pctest1 WHERE c > 10) ss + right join (values (1),(2),(3)) v(x) on true; +select * from + (SELECT count(*) FROM pctest1 WHERE c > 10) ss + right join (values (1),(2),(3)) v(x) on true; + +reset enable_material; + +-- Modify table (no parallelism) +EXPLAIN (costs off) +UPDATE pctest1 SET b = 0 WHERE d LIKE 'Value_9'; +UPDATE pctest1 SET b = 0 WHERE d LIKE 'Value_9'; +SELECT count(*) FROM pctest1 WHERE b = 0; + +EXPLAIN (costs off) +DELETE FROM pctest1 WHERE d LIKE 'Value_8'; +DELETE FROM pctest1 WHERE d LIKE 'Value_8'; +SELECT count(*) FROM pctest1; + +-- index scan with aggregates pushdown such that #atts being pushed down > #atts in relation +CREATE TABLE pctest3(k int primary key, a int unique); +INSERT INTO pctest3 SELECT i, i FROM generate_series(1, 1000) i; +ANALYZE pctest3; +/*+ Parallel(pctest3 2 hard) */ +EXPLAIN (costs off) SELECT count(*), max(k), min(k) FROM pctest3 WHERE k > 123; +/*+ Parallel(pctest3 2 hard) */ +SELECT count(*), max(k), min(k) FROM pctest3 WHERE k > 123; + +-- Parallel index scan +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE k < 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE k < 10; + +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY k; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY k; + +--secondary index +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 ORDER BY a LIMIT 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 ORDER BY a LIMIT 10; + +-- Joins +-- Nest loop +/*+ + Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) + Set(enable_mergejoin off) Set(enable_hashjoin off) + Set(yb_bnl_batch_size 1) Set(enable_material off) +*/ +EXPLAIN (costs off) +SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.a = pctest2.b and pctest1.a % 10 = 0; +-- TODO row order varies between the runs +-- /*+ +-- Set(enable_mergejoin off) Set(enable_hashjoin off) +-- Set(yb_bnl_batch_size 1) Set(enable_material off) +-- */ +-- SELECT pctest1.* FROM pctest1, pctest2 +-- WHERE pctest1.a = pctest2.b and pctest1.a % 10 = 0; +EXPLAIN (costs off) +/*+YbBatchedNL(pctest1 pctest2) Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +/*+YbBatchedNL(pctest1 pctest2) Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; + +-- Hash join +set enable_mergejoin to false; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT pctest1.k, pctest2.k FROM pctest1 JOIN pctest2 USING (a, c) ORDER BY pctest1.k, pctest2.k; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.k, pctest2.k FROM pctest1 JOIN pctest2 USING (a, c) ORDER BY pctest1.k, pctest2.k; +reset enable_mergejoin; + +-- Merge join +set enable_hashjoin to false; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +reset enable_hashjoin; + +-- Subquery +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT x, d FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) ss RIGHT JOIN + (values (15),(16),(17)) v(x) on ss.b = v.x ORDER BY x; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT x, d FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) ss RIGHT JOIN + (values (15),(16),(17)) v(x) on ss.b = v.x ORDER BY x; + +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest2.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.b = pctest2.b) s2 ON s1.b = s2.c; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest2.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.b = pctest2.b) s2 ON s1.b = s2.c; + +-- index only scan with aggregates pushdown such that #atts being pushed down > #atts in relation +/*+ Parallel(pctest3 3 hard) */ +EXPLAIN (costs off) SELECT count(*), max(a), min(a) FROM pctest3 WHERE a > 123; +/*+ Parallel(pctest3 3 hard) */ +SELECT count(*), max(a), min(a) FROM pctest3 WHERE a > 123; + +-- GHI #30204 support for conditions on yb_hash_code() +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) SELECT * FROM pctest1 WHERE yb_hash_code(k) >= 512 AND yb_hash_code(k) < 1024; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE yb_hash_code(k) >= 512 AND yb_hash_code(k) < 1024; +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) SELECT * FROM pctest1 WHERE yb_hash_code(c) >= 1024 AND yb_hash_code(c) < 2048; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE yb_hash_code(c) >= 1024 AND yb_hash_code(c) < 2048; + +DROP TABLE pctest1; +DROP TABLE pctest2; +DROP TABLE pctest3; diff --git a/src/test/regress/sql/yb.orig.parallel_plans.sql b/src/test/regress/sql/yb.orig.parallel_plans.sql new file mode 100644 index 0000000000000..4381a84e0b16b --- /dev/null +++ b/src/test/regress/sql/yb.orig.parallel_plans.sql @@ -0,0 +1,271 @@ +SET client_min_messages = 'warning'; + +DROP DATABASE IF EXISTS colocated_db WITH (force); + +CREATE DATABASE colocated_db WITH colocation = on; + +ALTER DATABASE colocated_db SET yb_enable_optimizer_statistics = on; +ALTER DATABASE colocated_db SET yb_enable_base_scans_cost_model = on; +ALTER DATABASE colocated_db SET yb_fetch_row_limit = 0; +ALTER DATABASE colocated_db SET yb_fetch_size_limit = '1MB'; +ALTER DATABASE colocated_db SET yb_parallel_range_rows = 10000; +ALTER DATABASE colocated_db SET yb_parallel_range_size = '1MB'; + +\c colocated_db + +SET client_min_messages = 'warning'; + +-- Create a table with the same value appearing 100 times each in column +-- k1, k2 and k3. Each column’s values are independently shuffled to +-- prevent correlations between columns or with the indexes. + +CREATE TABLE t1m (id int, k1 int, k2 int, k3 int, v char(1536), + PRIMARY KEY (id ASC)) WITH (COLOCATION = on); + +CREATE INDEX NONCONCURRENTLY t1m_k1k2k3 ON t1m (k1 ASC, k2 ASC, k3 ASC); + + +-- To avoid temporary file limit error, store shuffled column values in separate +-- temp tables first then join them together. + +CREATE TEMPORARY TABLE tmp1 (id int, v int, PRIMARY KEY (id)); +CREATE TEMPORARY TABLE tmp2 (id int, v int, PRIMARY KEY (id)); +CREATE TEMPORARY TABLE tmp3 (id int, v int, PRIMARY KEY (id)); + +SELECT setseed(0.777); + +SET work_mem = '256MB'; + +-- k1, k2, k3: Each distinct value repeats 100 times each +INSERT INTO tmp1 + SELECT row_number() OVER (ORDER BY random()) AS id, + i % (1000000 / 100) + 1 AS v FROM generate_series(1, 1000000) i; + +INSERT INTO tmp2 + SELECT row_number() OVER (ORDER BY random()) AS id, + i % (1000000 / 100) + 1 AS v FROM generate_series(1, 1000000) i; + +INSERT INTO tmp3 + SELECT row_number() OVER (ORDER BY random()) AS id, + i % (1000000 / 100) + 1 AS v FROM generate_series(1, 1000000) i; + +ANALYZE tmp1, tmp2, tmp3; + +/*+ + Leading(((tmp1 tmp2) tmp3)) + MergeJoin(tmp1 tmp2) + MergeJoin(tmp1 tmp2 tmp3) +*/ +INSERT INTO t1m + SELECT row_number() OVER (), tmp1.v, tmp2.v, tmp3.v, + lpad(sha512((tmp1.v#tmp2.v#tmp3.v)::bpchar::bytea)::bpchar, 1536, '-') + FROM tmp1 JOIN tmp2 USING (id) JOIN tmp3 USING(id); + +ANALYZE t1m; + + +\c colocated_db + +-- +-- Should choose SERIAL seq scan +-- + +EXPLAIN (COSTS off, SUMMARY off) +SELECT id, k1 FROM t1m t; + + +-- +-- Should choose SERIAL index scan +-- + +EXPLAIN (COSTS off, SUMMARY off) +SELECT id, k1, k2, k3, length(v) FROM t1m t WHERE id BETWEEN 500000-(100/2-1) AND 500000+(100/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT id, k1, k2, k3, length(v) FROM t1m t WHERE id BETWEEN 500000-(400/2-1) AND 500000+(400/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT k1, k2, k3 FROM t1m t WHERE id BETWEEN 500000-(100/2-1) AND 500000+(100/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT k1, k2, k3 FROM t1m t WHERE id BETWEEN 500000-(400/2-1) AND 500000+(400/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT k1, k2, k3 FROM t1m t WHERE id BETWEEN 500000-(20000/2-1) AND 500000+(20000/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT k1, k2, k3 FROM t1m t WHERE id BETWEEN 500000-(50000/2-1) AND 500000+(50000/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT 0 FROM t1m t WHERE id BETWEEN 500000-(20000/2-1) AND 500000+(20000/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT 0 FROM t1m t WHERE id BETWEEN 500000-(50000/2-1) AND 500000+(50000/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT 0 FROM t1m t WHERE id BETWEEN 500000-(100000/2-1) AND 500000+(100000/2); + + +-- +-- Should choose SERIAL index only scan +-- + +EXPLAIN (COSTS off, SUMMARY off) +SELECT k1, k2, k3 FROM t1m t WHERE k1 = 5000; + +EXPLAIN (COSTS off, SUMMARY off) +SELECT k1, k2, k3 FROM t1m t WHERE k1 BETWEEN 5000-(4/2 - 1) AND 5000+(4/2); + + +-- +-- Should choose SERIAL append plan +-- + +EXPLAIN (COSTS off, SUMMARY off) +SELECT sum(k1), sum(k2), sum(k3), avg(k1+id), avg(k2+k3) +FROM (SELECT id, k1, k2, k3 FROM t1m t1 WHERE id BETWEEN 500000-(10/2-1) AND 500000+(10/2) + UNION ALL + SELECT -1, k1, k2, k3 FROM t1m t2 WHERE k1 = 5000 + UNION ALL + SELECT -2, k1, k2, k3 len FROM t1m t3 WHERE id = 100 + UNION ALL + SELECT -3, k1, k2, k3 FROM t1m t4 WHERE k1 = 4500 +) v; + +EXPLAIN (COSTS off, SUMMARY off) +SELECT sum(k1), sum(k2), sum(k3), avg(k1+id), avg(k2+k3) +FROM (SELECT id, k1, k2, k3 FROM t1m t1 WHERE id BETWEEN 500000-(10/2-1) AND 500000+(10/2) + UNION ALL + SELECT -1, k1, k2, k3 FROM t1m t2 WHERE k1 = 5000 + UNION ALL + SELECT -2, k1, k2, k3 len FROM t1m t3 WHERE id = 100 + UNION ALL + SELECT -3, k1, k2, k3 FROM t1m t4 WHERE k1 <= 300 +) v; + + +-- +-- Should choose PARALLEL seq scan +-- + +EXPLAIN (COSTS off, SUMMARY off) +SELECT id, k1, k2, k3, length(v) FROM t1m t; + + +-- +-- Should choose PARALLEL index scan +-- + +EXPLAIN (COSTS off, SUMMARY off) +SELECT id, k1, k2, k3, length(v) FROM t1m t WHERE k1 BETWEEN 5000-(200/2 - 1) AND 5000+(200/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT id, k1, k2, k3, length(v) FROM t1m t WHERE k2 BETWEEN 5000-(500/2 - 1) AND 5000+(500/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT id, k1, k2, k3, length(v) FROM t1m t WHERE k1 BETWEEN 5000-(1000/2 - 1) AND 5000+(1000/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT id, k1, k2, k3, length(v) FROM t1m t WHERE k2 BETWEEN 5000-(1000/2 - 1) AND 5000+(1000/2); + +EXPLAIN (COSTS off, SUMMARY off) +SELECT id, k1, k2, k3, length(v) FROM t1m t ORDER BY id; + + +-- +-- Should choose PARALLEL index only scan +-- + +EXPLAIN (COSTS off, SUMMARY off) +SELECT k1, k2, k3 FROM t1m t WHERE k3 BETWEEN 5000-(1000/2 - 1) AND 5000+(1000/2); + + +-- +-- Should choose PARALLEL append/gather plan +-- + +EXPLAIN (COSTS off, SUMMARY off) +SELECT sum(k1), sum(k2), sum(k3), avg(k1+id), avg(k2+k3) +FROM (SELECT id, k1, k2, k3 FROM t1m t1 WHERE id BETWEEN 500000-(10/2-1) AND 500000+(10/2) + UNION ALL + SELECT -1, k1, k2, k3 FROM t1m t2 WHERE k3 = 5000 + UNION ALL + SELECT -2, k1, k2, k3 len FROM t1m t3 WHERE id = 100 + UNION ALL + SELECT -3, k1, k2, k3 FROM t1m t4 WHERE k3 <= 300 +) v; + + +-- +-- Should choose index only scan by default, and should be parallelized +-- with parallel_tuple_cost set to 0.01. These queries run faster in parallel +-- but only marginally, so it's better not to parallelize them by default. +-- + +EXPLAIN (COSTS off, SUMMARY off) +SELECT k1, k2, k3 FROM t1m t; +/*+ Set(parallel_tuple_cost 0.01) */ +EXPLAIN (COSTS off, SUMMARY off) +SELECT k1, k2, k3 FROM t1m t; + +EXPLAIN (COSTS off, SUMMARY off) +SELECT 0 FROM t1m t; +/*+ Set(parallel_tuple_cost 0.01) */ +EXPLAIN (COSTS off, SUMMARY off) +SELECT 0 FROM t1m t; + + +-- +-- Correctness tests +-- + +/* + * Check sanity of cost values (not NaN, +/-Inf, etc.) with very small + * yb_parallel_range_size. + * + * The following function borrowed from explain.sql. + */ +-- To produce stable regression test output, it's usually necessary to +-- ignore details such as exact costs or row counts. These filter +-- functions replace changeable output details with fixed strings. + +-- To produce valid JSON output, replace numbers with "0" or "0.0" not "N" +create function explain_filter_to_json(text) returns jsonb +language plpgsql as +$$ +declare + data text := ''; + ln text; +begin + for ln in execute $1 + loop + -- Replace any numeric word with just '0' + ln := regexp_replace(ln, '\m\d+\M', '0', 'g'); + data := data || ln; + end loop; + return data::jsonb; +end; +$$; + + +CREATE TABLE t10k (id int, k1 int, k2 int, k3 int, v char(1536), + PRIMARY KEY (id ASC)) WITH (COLOCATION = on); +INSERT INTO t10k SELECT * FROM t1m WHERE id <= 10000; +-- Ensure no stats even if we start auto-analyzing in the future. +SELECT yb_reset_analyze_statistics('t10k'::regclass); + +BEGIN; + SET LOCAL yb_parallel_range_size = 10; + SET LOCAL yb_parallel_range_rows = 1; + SET LOCAL yb_enable_base_scans_cost_model = on; + + SET LOCAL parallel_setup_cost = 0; + SET LOCAL parallel_tuple_cost = 0; + SET LOCAL enable_bitmapscan = off; + + SELECT explain_filter_to_json('EXPLAIN (FORMAT json, SUMMARY off) SELECT * FROM t10k WHERE v LIKE ''Value_9'' ORDER BY k2 DESC') #> '{0, "Plan", "Total Cost"}'; + +ROLLBACK; + + +DROP TABLE t1m, t10k; diff --git a/src/test/regress/sql/yb.orig.parallel_ranged.sql b/src/test/regress/sql/yb.orig.parallel_ranged.sql new file mode 100644 index 0000000000000..334c7b4c382f1 --- /dev/null +++ b/src/test/regress/sql/yb.orig.parallel_ranged.sql @@ -0,0 +1,367 @@ +-- +-- PARALLEL queries to range sharded tables +-- + +-- create tables +CREATE TABLE pctest1(k int, a int, b int, c int, d text, primary key(k asc)); +CREATE TABLE pctest2(k int, a int, b int, c int, d text, primary key(k asc)); +CREATE UNIQUE INDEX ON pctest1(a asc); +CREATE INDEX ON pctest1(c asc); +CREATE INDEX ON pctest2(b asc); +INSERT INTO pctest1 + SELECT i, 1000 - i, i/3, i%50, 'Value' || i::text FROM generate_series(1, 1000) i; +INSERT INTO pctest2 + SELECT i, 200 + i, i/5, i%10, 'Other value ' || i::text FROM generate_series(1, 200) i; +ANALYZE pctest1, pctest2; + +set yb_enable_parallel_scan_range_sharded to true; +-- set smaller parallel interval to produce more ranges +set yb_parallel_range_size to 1024; + +-- enable parallel query for YB tables +set yb_parallel_range_rows to 1; +set yb_enable_cbo = on; + +-- parallel bitmap scan not supported yet +set enable_bitmapscan = false; + +-- Parallel sequential scan +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE d LIKE 'Value_9'; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE d LIKE 'Value_9'; + +-- with aggregates +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT count(*) FROM pctest1 WHERE d LIKE 'Value_9'; +/*+ Parallel(pctest1 2 hard) */ +SELECT count(*) FROM pctest1 WHERE d LIKE 'Value_9'; + +-- with sort +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY b DESC; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY b DESC; + +-- with grouping +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT b, count(*) FROM pctest1 WHERE d LIKE 'Value9%' GROUP BY b; +/*+ Parallel(pctest1 2 hard) */ +SELECT b, count(*) FROM pctest1 WHERE d LIKE 'Value9%' GROUP BY b; + +-- Parallel index scan +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE k < 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE k < 10; + +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY k; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY k; + +--secondary index +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE c = 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE c = 10; + +--secondary index +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 ORDER BY a LIMIT 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 ORDER BY a LIMIT 10; + +-- with aggregates +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT count(*) FROM pctest1 WHERE k > 123; +/*+ Parallel(pctest1 2 hard) */ +SELECT count(*) FROM pctest1 WHERE k > 123; + +-- index only +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT a FROM pctest1 WHERE a < 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT a FROM pctest1 WHERE a < 10; + +-- with grouping +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT c, count(*) FROM pctest1 WHERE c > 40 GROUP BY c; +/*+ Parallel(pctest1 2 hard) */ +SELECT c, count(*) FROM pctest1 WHERE c > 40 GROUP BY c; + +-- Joins +-- Nest loop +/*+ + Set(enable_mergejoin off) Set(enable_hashjoin off) + Set(yb_bnl_batch_size 1) Set(enable_material off) +*/ +EXPLAIN (costs off) +SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.a = pctest2.b and pctest1.a % 10 = 0; +/*+ + Set(enable_mergejoin off) Set(enable_hashjoin off) + Set(yb_bnl_batch_size 1) Set(enable_material off) +*/ +SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.a = pctest2.b and pctest1.a % 10 = 0; +EXPLAIN (costs off) +/*+YbBatchedNL(pctest1 pctest2) Parallel(pctest1 2 hard)*/ +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +/*+YbBatchedNL(pctest1 pctest2) Parallel(pctest1 2 hard)*/ +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; + +-- Hash join +set enable_mergejoin to false; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT pctest1.k, pctest2.k FROM pctest1 JOIN pctest2 USING (a, c); +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.k, pctest2.k FROM pctest1 JOIN pctest2 USING (a, c); +reset enable_mergejoin; + +-- Merge join +set enable_hashjoin to false; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +reset enable_hashjoin; + +-- Subquery +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT x, d FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) ss RIGHT JOIN + (values (15),(16),(17)) v(x) on ss.b = v.x ORDER BY x; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT x, d FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) ss RIGHT JOIN + (values (15),(16),(17)) v(x) on ss.b = v.x ORDER BY x; + +/*+ + Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) + Parallel(pctest3 2 hard) Parallel(pctest4 2 hard) + */ +EXPLAIN (costs off) +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest4.* FROM pctest1 pctest3, pctest2 pctest4 + WHERE pctest3.k = pctest4.k AND pctest3.b = pctest4.b) s2 ON s1.b = s2.c; +/*+ + Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) + Parallel(pctest3 2 hard) Parallel(pctest4 2 hard) + */ +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest4.* FROM pctest1 pctest3, pctest2 pctest4 + WHERE pctest3.k = pctest4.k AND pctest3.b = pctest4.b) s2 ON s1.b = s2.c; + +-- no parallelism +EXPLAIN (costs off) +SELECT * from pctest2 + WHERE b < (SELECT avg(b) / 20 FROM pctest1 WHERE c = pctest2.c); +SELECT * from pctest2 + WHERE b < (SELECT avg(b) / 20 FROM pctest1 WHERE c = pctest2.c); + +-- passing parameters to workers +/*+ Parallel(pctest1 2 hard) Parallel(pctest1_1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT * from pctest2 + WHERE c IN (SELECT b FROM pctest1 WHERE d LIKE 'Value_9') + AND b < (SELECT avg(b) FROM pctest1 WHERE d LIKE 'Value_9'); +/*+ Parallel(pctest1 2 hard) Parallel(pctest1_1 2 hard) Parallel(pctest2 2 hard) */ +SELECT * from pctest2 + WHERE c IN (SELECT b FROM pctest1 WHERE d LIKE 'Value_9') + AND b < (SELECT avg(b) FROM pctest1 WHERE d LIKE 'Value_9'); + +-- test rescan cases +set enable_material = false; + +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +select * from + (SELECT count(*) FROM pctest1 WHERE b > 10) ss + right join (values (1),(2),(3)) v(x) on true; +select * from + (SELECT count(*) FROM pctest1 WHERE b > 10) ss + right join (values (1),(2),(3)) v(x) on true; + +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +select * from + (SELECT count(*) FROM pctest1 WHERE c > 10) ss + right join (values (1),(2),(3)) v(x) on true; +select * from + (SELECT count(*) FROM pctest1 WHERE c > 10) ss + right join (values (1),(2),(3)) v(x) on true; + +reset enable_material; + +-- Modify table (no parallelism) +EXPLAIN (costs off) +UPDATE pctest1 SET b = 0 WHERE d LIKE 'Value_9'; +UPDATE pctest1 SET b = 0 WHERE d LIKE 'Value_9'; +SELECT count(*) FROM pctest1 WHERE b = 0; + +EXPLAIN (costs off) +DELETE FROM pctest1 WHERE d LIKE 'Value_8'; +DELETE FROM pctest1 WHERE d LIKE 'Value_8'; +SELECT count(*) FROM pctest1; + +-- index scan with aggregates pushdown such that #atts being pushed down > #atts in relation +CREATE TABLE pctest3(k int, a int unique, primary key(k asc)); +INSERT INTO pctest3 SELECT i, i FROM generate_series(1, 1000) i; +ANALYZE pctest3; +/*+ Parallel(pctest3 2 hard) */ +EXPLAIN (costs off) SELECT count(*), max(k), min(k) FROM pctest3 WHERE k > 123; +/*+ Parallel(pctest3 2 hard) */ +SELECT count(*), max(k), min(k) FROM pctest3 WHERE k > 123; + +-- Parallel index scan +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE k < 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE k < 10; + +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY k; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE d LIKE 'Value_9' ORDER BY k; + +--secondary index +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 WHERE c = 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 WHERE c = 10; + +--secondary index +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM pctest1 ORDER BY a LIMIT 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT * FROM pctest1 ORDER BY a LIMIT 10; + +-- with aggregates +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT count(*) FROM pctest1 WHERE k > 123; +/*+ Parallel(pctest1 2 hard) */ +SELECT count(*) FROM pctest1 WHERE k > 123; + +-- index only +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT a FROM pctest1 WHERE a < 10; +/*+ Parallel(pctest1 2 hard) */ +SELECT a FROM pctest1 WHERE a < 10; + +-- with grouping +/*+ Parallel(pctest1 2 hard) */ +EXPLAIN (costs off) +SELECT c, count(*) FROM pctest1 WHERE c > 40 GROUP BY c; +/*+ Parallel(pctest1 2 hard) */ +SELECT c, count(*) FROM pctest1 WHERE c > 40 GROUP BY c; + +-- Joins +-- Nest loop +/*+ + Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) + Set(enable_mergejoin off) Set(enable_hashjoin off) + Set(yb_bnl_batch_size 1) Set(enable_material off) +*/ +EXPLAIN (costs off) +SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.a = pctest2.b and pctest1.a % 10 = 0; +/*+ + Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) + Set(enable_mergejoin off) Set(enable_hashjoin off) + Set(yb_bnl_batch_size 1) Set(enable_material off) +*/ +SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.a = pctest2.b and pctest1.a % 10 = 0; +EXPLAIN (costs off) +/*+YbBatchedNL(pctest1 pctest2) Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +/*+YbBatchedNL(pctest1 pctest2) Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; + +-- Hash join +set enable_mergejoin to false; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT pctest1.k, pctest2.k FROM pctest1 JOIN pctest2 USING (a, c) ORDER BY pctest1.k, pctest2.k; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.k, pctest2.k FROM pctest1 JOIN pctest2 USING (a, c) ORDER BY pctest1.k, pctest2.k; +reset enable_mergejoin; + +-- Merge join +set enable_hashjoin to false; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT pctest1.*, pctest2.k FROM pctest1, pctest2 + WHERE pctest1.c = 42 AND pctest1.k = pctest2.k ORDER BY pctest1.k; +reset enable_hashjoin; + +-- Subquery +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT x, d FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) ss RIGHT JOIN + (values (15),(16),(17)) v(x) on ss.b = v.x ORDER BY x; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT x, d FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) ss RIGHT JOIN + (values (15),(16),(17)) v(x) on ss.b = v.x ORDER BY x; + +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +EXPLAIN (costs off) +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest2.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.b = pctest2.b) s2 ON s1.b = s2.c; +/*+ Parallel(pctest1 2 hard) Parallel(pctest2 2 hard) */ +SELECT * FROM + (SELECT pctest1.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.c = pctest2.c) s1 JOIN + (SELECT pctest2.* FROM pctest1, pctest2 + WHERE pctest1.k = pctest2.k AND pctest1.b = pctest2.b) s2 ON s1.b = s2.c; + +-- index only scan with aggregates pushdown such that #atts being pushed down > #atts in relation +/*+ Parallel(pctest3 3 hard) */ +EXPLAIN (costs off) SELECT count(*), max(a), min(a) FROM pctest3 WHERE a > 123; +/*+ Parallel(pctest3 3 hard) */ +SELECT count(*), max(a), min(a) FROM pctest3 WHERE a > 123; + +DROP TABLE pctest1; +DROP TABLE pctest2; +DROP TABLE pctest3; diff --git a/src/test/regress/sql/yb.orig.parallel_snapshot_switching.sql b/src/test/regress/sql/yb.orig.parallel_snapshot_switching.sql new file mode 100644 index 0000000000000..c0fa8f962090c --- /dev/null +++ b/src/test/regress/sql/yb.orig.parallel_snapshot_switching.sql @@ -0,0 +1,60 @@ +-- +-- Test snapshot switching in parallel workers (GH #30588). +-- +-- In Read Committed isolation, calling a PL/pgSQL function that performs a +-- sub-query triggers a fresh transaction snapshot. When this happens inside +-- parallel workers, they independently create and switch to new snapshots. +-- + +SET max_parallel_workers_per_gather = 4; +SET parallel_setup_cost = 0; +SET parallel_tuple_cost = 0; +SET yb_parallel_range_rows = 1; +SET yb_enable_cbo = on; + +CREATE TABLE pss_test (k INT PRIMARY KEY, v TEXT) SPLIT INTO 5 TABLETS; +CREATE TABLE pss_dummy (k INT PRIMARY KEY, v INT); +INSERT INTO pss_test SELECT i, 'value_' || i FROM generate_series(1, 10000) i; +ALTER TABLE pss_test SET (parallel_workers = 4); +ANALYZE pss_test; + +-- This function triggers a snapshot switch on each invocation in RC isolation, +-- because the SELECT inside the function acquires a new snapshot. +CREATE OR REPLACE FUNCTION pss_snapshot_fn(n INT) RETURNS INT AS $$ +DECLARE + v INT; +BEGIN + SELECT pss_dummy.v INTO v FROM pss_dummy WHERE k = n; + RETURN n; +END; +$$ LANGUAGE plpgsql; +ALTER FUNCTION pss_snapshot_fn(int) PARALLEL SAFE; + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parallel_snapshot_switching_query.sql' + +SET DEFAULT_TRANSACTION_ISOLATION = 'READ COMMITTED'; +\i :filename + +SET DEFAULT_TRANSACTION_ISOLATION = 'REPEATABLE READ'; +\i :filename + +SET DEFAULT_TRANSACTION_ISOLATION = 'SERIALIZABLE'; +\i :filename + +-- Test with Gather Merge (sorted parallel output). +\set filename :abs_srcdir '/yb_commands/parallel_snapshot_switching_query_with_gather_merge.sql' +SET DEFAULT_TRANSACTION_ISOLATION = 'READ COMMITTED'; +\i :filename + +SET DEFAULT_TRANSACTION_ISOLATION = 'REPEATABLE READ'; +\i :filename + +SET DEFAULT_TRANSACTION_ISOLATION = 'SERIALIZABLE'; +\i :filename + +RESET max_parallel_workers_per_gather; +RESET parallel_setup_cost; +RESET parallel_tuple_cost; +RESET yb_parallel_range_rows; +RESET yb_enable_cbo; diff --git a/src/test/regress/sql/yb.orig.partition_aggregate.sql b/src/test/regress/sql/yb.orig.partition_aggregate.sql new file mode 100644 index 0000000000000..6ceb3a04b21b3 --- /dev/null +++ b/src/test/regress/sql/yb.orig.partition_aggregate.sql @@ -0,0 +1,131 @@ +-- +-- Inspired by upstream PG's partition_aggregate test, modified to exercise +-- YB's aggregate pushdown. Practically, this involves two main changes: +-- - Remove GROUP BY clauses as GROUP BY disables aggregate pushdown. This +-- also means removing HAVING, ORDER BY, and non-aggregate targets. Since +-- this transformation can map two different queries in the original test to +-- the same query here, skip the duplicates. +-- - Skip queries on dummy relations (e.g. subselects). +-- Keep code comments the same as in the original test for better diffing, even +-- though the comments may not match the queries. +-- + +\set explain 'EXPLAIN (COSTS OFF)' +\set hintfalse '/*+Set(enable_partitionwise_aggregate false)*/' +\set hinttrue '/*+Set(enable_partitionwise_aggregate true)*/' +\set run ':explain :hintfalse :query; :explain :hinttrue :query; :hintfalse :query; :hinttrue :query;' + +-- Enable partitionwise aggregate, which by default is disabled. +SET enable_partitionwise_aggregate TO true; +-- Enable partitionwise join, which by default is disabled. +SET enable_partitionwise_join TO true; +-- Disable parallel plans. +SET max_parallel_workers_per_gather TO 0; +-- Disable incremental sort, which can influence selected plans due to fuzz factor. +SET enable_incremental_sort TO off; + +-- +-- Tests for list partitioned tables. +-- +CREATE TABLE pagg_tab (a int, b int, c text, d int) PARTITION BY LIST(c); +CREATE TABLE pagg_tab_p1 PARTITION OF pagg_tab FOR VALUES IN ('0000', '0001', '0002', '0003', '0004'); +CREATE TABLE pagg_tab_p2 PARTITION OF pagg_tab FOR VALUES IN ('0005', '0006', '0007', '0008'); +CREATE TABLE pagg_tab_p3 PARTITION OF pagg_tab FOR VALUES IN ('0009', '0010', '0011'); +INSERT INTO pagg_tab SELECT i % 20, i % 30, to_char(i % 12, 'FM0000'), i % 30 FROM generate_series(0, 2999) i; +ANALYZE pagg_tab; + +-- When GROUP BY clause matches; full aggregation is performed for each partition. +\set query 'SELECT sum(a), avg(b), count(*), min(a), max(b) FROM pagg_tab' +:run + + +-- JOIN query + +CREATE TABLE pagg_tab1(x int, y int) PARTITION BY RANGE(x); +CREATE TABLE pagg_tab1_p1 PARTITION OF pagg_tab1 FOR VALUES FROM (0) TO (10); +CREATE TABLE pagg_tab1_p2 PARTITION OF pagg_tab1 FOR VALUES FROM (10) TO (20); +CREATE TABLE pagg_tab1_p3 PARTITION OF pagg_tab1 FOR VALUES FROM (20) TO (30); + +CREATE TABLE pagg_tab2(x int, y int) PARTITION BY RANGE(y); +CREATE TABLE pagg_tab2_p1 PARTITION OF pagg_tab2 FOR VALUES FROM (0) TO (10); +CREATE TABLE pagg_tab2_p2 PARTITION OF pagg_tab2 FOR VALUES FROM (10) TO (20); +CREATE TABLE pagg_tab2_p3 PARTITION OF pagg_tab2 FOR VALUES FROM (20) TO (30); + +INSERT INTO pagg_tab1 SELECT i % 30, i % 20 FROM generate_series(0, 299, 2) i; +INSERT INTO pagg_tab2 SELECT i % 20, i % 30 FROM generate_series(0, 299, 3) i; + +ANALYZE pagg_tab1; +ANALYZE pagg_tab2; + +-- When GROUP BY clause matches; full aggregation is performed for each partition. +\set query 'SELECT sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y' +:run + +-- Check with whole-row reference; partitionwise aggregation does not apply +\set query 'SELECT sum(t1.y), count(t1) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y' +:run + +-- Check with LEFT/RIGHT/FULL OUTER JOINs which produces NULL values for +-- aggregation + +-- LEFT JOIN, should produce partial partitionwise aggregation plan as +-- GROUP BY is on nullable column +\set query 'SELECT sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y' +:run + +-- RIGHT JOIN, should produce full partitionwise aggregation plan as +-- GROUP BY is on non-nullable column +\set query 'SELECT sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y' +:run + +-- FULL JOIN, should produce partial partitionwise aggregation plan as +-- GROUP BY is on nullable column +\set query 'SELECT sum(b.x) FROM pagg_tab1 a FULL OUTER JOIN pagg_tab2 b ON a.x = b.y' +:run + + +-- Partition by multiple columns + +CREATE TABLE pagg_tab_m (a int, b int, c int) PARTITION BY RANGE(a, ((a+b)/2)); +CREATE TABLE pagg_tab_m_p1 PARTITION OF pagg_tab_m FOR VALUES FROM (0, 0) TO (12, 12); +CREATE TABLE pagg_tab_m_p2 PARTITION OF pagg_tab_m FOR VALUES FROM (12, 12) TO (22, 22); +CREATE TABLE pagg_tab_m_p3 PARTITION OF pagg_tab_m FOR VALUES FROM (22, 22) TO (30, 30); +INSERT INTO pagg_tab_m SELECT i % 30, i % 40, i % 50 FROM generate_series(0, 2999) i; +ANALYZE pagg_tab_m; + +-- Partial aggregation as GROUP BY clause does not match with PARTITION KEY +\set query 'SELECT sum(b), avg(c), count(*) FROM pagg_tab_m' +:run + + +-- Test with multi-level partitioning scheme + +CREATE TABLE pagg_tab_ml (a int, b int, c text) PARTITION BY RANGE(a); +CREATE TABLE pagg_tab_ml_p1 PARTITION OF pagg_tab_ml FOR VALUES FROM (0) TO (12); +CREATE TABLE pagg_tab_ml_p2 PARTITION OF pagg_tab_ml FOR VALUES FROM (12) TO (20) PARTITION BY LIST (c); +CREATE TABLE pagg_tab_ml_p2_s1 PARTITION OF pagg_tab_ml_p2 FOR VALUES IN ('0000', '0001', '0002'); +CREATE TABLE pagg_tab_ml_p2_s2 PARTITION OF pagg_tab_ml_p2 FOR VALUES IN ('0003'); + +-- This level of partitioning has different column positions than the parent +CREATE TABLE pagg_tab_ml_p3(b int, c text, a int) PARTITION BY RANGE (b); +CREATE TABLE pagg_tab_ml_p3_s1(c text, a int, b int); +CREATE TABLE pagg_tab_ml_p3_s2 PARTITION OF pagg_tab_ml_p3 FOR VALUES FROM (7) TO (10); + +ALTER TABLE pagg_tab_ml_p3 ATTACH PARTITION pagg_tab_ml_p3_s1 FOR VALUES FROM (0) TO (7); +ALTER TABLE pagg_tab_ml ATTACH PARTITION pagg_tab_ml_p3 FOR VALUES FROM (20) TO (30); + +INSERT INTO pagg_tab_ml SELECT i % 30, i % 10, to_char(i % 4, 'FM0000') FROM generate_series(0, 29999) i; +ANALYZE pagg_tab_ml; + +-- For Parallel Append +SET max_parallel_workers_per_gather TO 2; +SET parallel_setup_cost = 0; + +-- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY +-- for level 1 only. For subpartitions, GROUP BY clause does not match with +-- PARTITION KEY, but still we do not see a partial aggregation as array_agg() +-- is not partial agg safe. +\set query 'SELECT sum(b), count(*) FROM pagg_tab_ml' +:run + +RESET parallel_setup_cost; diff --git a/src/test/regress/sql/yb.orig.partition_alter.sql b/src/test/regress/sql/yb.orig.partition_alter.sql new file mode 100644 index 0000000000000..e06bfb036a5cd --- /dev/null +++ b/src/test/regress/sql/yb.orig.partition_alter.sql @@ -0,0 +1,37 @@ +-- Create a partitioned hierarchy of LIST, RANGE and HASH. +CREATE TABLE root_list_parent (list_part_key char, hash_part_key int, range_part_key int) PARTITION BY LIST(list_part_key); +CREATE TABLE hash_parent PARTITION OF root_list_parent FOR VALUES in ('a', 'b') PARTITION BY HASH (hash_part_key); +CREATE TABLE range_parent PARTITION OF hash_parent FOR VALUES WITH (modulus 1, remainder 0) PARTITION BY RANGE (range_part_key); +CREATE TABLE child_partition PARTITION OF range_parent FOR VALUES FROM (1) TO (5); +INSERT INTO root_list_parent VALUES ('a', 1, 2); + +-- Add a column to the parent table, verify that selecting data still works. +ALTER TABLE root_list_parent ADD COLUMN foo VARCHAR(2); +SELECT * FROM root_list_parent; + +-- Alter column type at the parent table. +ALTER TABLE root_list_parent ALTER COLUMN foo TYPE VARCHAR(3); +INSERT INTO root_list_parent VALUES ('a', 1, 2, 'abc'); +SELECT * FROM root_list_parent ORDER BY foo; + +-- Drop a column from the parent table, verify that selecting data still works. +ALTER TABLE root_list_parent DROP COLUMN foo; +SELECT * FROM root_list_parent; + +-- Retry adding a column after error. +ALTER TABLE root_list_parent ADD COLUMN foo text not null; -- fails due to not null constraint +ALTER TABLE root_list_parent ADD COLUMN foo text not null DEFAULT 'abc'; -- passes + +-- Rename a column belonging to the parent table. +ALTER TABLE root_list_parent RENAME COLUMN list_part_key TO list_part_key_renamed; +SELECT * FROM child_partition ORDER BY foo; +TRUNCATE root_list_parent; + +-- Add constraint to the parent table, verify that it reflects on the child partition. +ALTER TABLE root_list_parent ADD CONSTRAINT constraint_test UNIQUE (list_part_key_renamed, hash_part_key, range_part_key, foo); +INSERT INTO child_partition VALUES ('a', 1, 2), ('a', 1, 2); + +-- Remove constraint from the parent table, verify that it reflects on the child partition. +ALTER TABLE root_list_parent DROP CONSTRAINT constraint_test; +INSERT INTO child_partition VALUES ('a', 1, 2), ('a', 1, 2); +SELECT * FROM root_list_parent ORDER BY foo; diff --git a/src/test/regress/sql/yb.orig.partition_fk.sql b/src/test/regress/sql/yb.orig.partition_fk.sql new file mode 100644 index 0000000000000..d6531f3f4f21f --- /dev/null +++ b/src/test/regress/sql/yb.orig.partition_fk.sql @@ -0,0 +1,59 @@ +-- +-- Test PARTITIONed table interaction with FK constraints +-- + +CREATE TABLE referenced (a int PRIMARY KEY); + + +-- FK constraint attached to a partitioned table itself + +CREATE TABLE partitioned (a int) PARTITION BY LIST (a); +CREATE TABLE partitioned_part PARTITION OF partitioned FOR VALUES IN (123); +ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES referenced; + +INSERT INTO partitioned (a) VALUES (123); +SELECT tableoid::regclass, a FROM partitioned ORDER BY a; + +INSERT INTO partitioned_part (a) VALUES (123); +SELECT tableoid::regclass, a FROM partitioned ORDER BY a; + +DROP TABLE partitioned CASCADE; + + +-- FK constraint attached to a table partition + +CREATE TABLE partitioned (a int) PARTITION BY LIST (a); +CREATE TABLE partitioned_part PARTITION OF partitioned FOR VALUES IN (123); +ALTER TABLE partitioned_part ADD FOREIGN KEY (a) REFERENCES referenced; + +INSERT INTO partitioned (a) VALUES (123); +SELECT tableoid::regclass, a FROM partitioned ORDER BY a; + +INSERT INTO partitioned_part (a) VALUES (123); +SELECT tableoid::regclass, a FROM partitioned ORDER BY a; + +DROP TABLE partitioned CASCADE; + + +-- FK constraint attached to intermediate partition in a two-level partitioned scheme + +CREATE TABLE partitioned (a int) PARTITION BY LIST (a); +CREATE TABLE partitioned_lvl1 PARTITION OF partitioned FOR VALUES IN (123) PARTITION BY LIST (a); +CREATE TABLE partitioned_lvl2 PARTITION OF partitioned_lvl1 FOR VALUES IN (123); + +ALTER TABLE partitioned_lvl1 ADD FOREIGN KEY (a) REFERENCES referenced; + +INSERT INTO partitioned (a) VALUES (123); +SELECT tableoid::regclass, a FROM partitioned ORDER BY a; + +INSERT INTO partitioned_lvl1 (a) VALUES (123); +SELECT tableoid::regclass, a FROM partitioned ORDER BY a; + +INSERT INTO partitioned_lvl2 (a) VALUES (123); +SELECT tableoid::regclass, a FROM partitioned ORDER BY a; + +DROP TABLE partitioned CASCADE; + +-- Cleanup + +DROP TABLE referenced; diff --git a/src/test/regress/sql/yb.orig.partition_out_of_order.sql b/src/test/regress/sql/yb.orig.partition_out_of_order.sql new file mode 100644 index 0000000000000..8406fb0372499 --- /dev/null +++ b/src/test/regress/sql/yb.orig.partition_out_of_order.sql @@ -0,0 +1,122 @@ +-- Create a table with columns of different types and sizes. +CREATE TABLE base (a INT4 PRIMARY KEY, b TEXT, c BOOL) PARTITION BY RANGE (a); +-- Attach partitions with columns in different order. +CREATE TABLE part1 (c BOOL, b TEXT, a INT4 NOT NULL); +CREATE TABLE part2 (b TEXT, a INT4 PRIMARY KEY, c BOOL); +CREATE TABLE part3 (a INT4 UNIQUE NOT NULL, c BOOL, b TEXT); +ALTER TABLE base ATTACH PARTITION part1 FOR VALUES FROM (11) TO (21); +ALTER TABLE base ATTACH PARTITION part2 FOR VALUES FROM (21) TO (31); +ALTER TABLE base ATTACH PARTITION part3 FOR VALUES FROM (31) TO (41); + +CREATE TABLE part4 (a BOOL, c TEXT, b INT4 PRIMARY KEY); +-- Error: names and types of columns should match +ALTER TABLE base ATTACH PARTITION part4 FOR VALUES FROM (41) TO (51); +DROP TABLE part4; + +CREATE TABLE part5 (c INT4 PRIMARY KEY, a TEXT, B BOOL); +-- Error: names and types of columns should match +ALTER TABLE base ATTACH PARTITION part5 FOR VALUES FROM (51) TO (61); +DROP TABLE part5; + +-- Attach a hierarchy of partitions with columns in different order +CREATE TABLE part6 (b TEXT, a INT NOT NULL, c BOOL) PARTITION BY LIST (a); +ALTER TABLE base ATTACH PARTITION part6 FOR VALUES FROM (61) TO (71); +CREATE TABLE part6a (a INT NOT NULL, b TEXT, c BOOL); +CREATE TABLE part6b (c BOOL, b TEXT, a INT NOT NULL); +ALTER TABLE part6 ATTACH PARTITION part6a FOR VALUES IN ((61), (62), (63), (64), (65)); +ALTER TABLE part6 ATTACH PARTITION part6b FOR VALUES IN + ((66), (67), (68), (69), (70)); + +CREATE TABLE partmisc (a INT NOT NULL, b TEXT, c BOOL); +ALTER TABLE base ATTACH PARTITION partmisc FOR VALUES FROM (100) TO (1000); + +-- Inserts should be routed to the correct partition and inserted in the format +-- of the child partition. +INSERT INTO base VALUES (11, 'foo', TRUE); +INSERT INTO base VALUES (12, 'bar', TRUE), (21, 'baz', FALSE), (31, 'qux', TRUE); +INSERT INTO base (b, c, a) VALUES ('abc', 0::bool, 13), ('caz', TRUE, 22), ('xuq', FALSE, 32); +INSERT INTO base (b, a, c) VALUES ('part6a-abc', 61, FALSE), ('part6b-abc', 66, TRUE), ('partmisc-abc', 100, TRUE); +-- Error: When the main table is specified, the columns in the input should match the main table +-- rather than the partition to which the insert is routed. +INSERT INTO base VALUES (FALSE, 'def', 14); +-- Inserts that fetch values from a SELECT must also be of the correct format. +INSERT INTO base (SELECT b, c, a + 12 FROM part1); + +BEGIN; +INSERT INTO base (a, b, c) (SELECT a + 12, b, c FROM part1); +INSERT INTO base (SELECT a + 12, b, c FROM part2); +SELECT * FROM base ORDER BY a; +ROLLBACK; + +SELECT * FROM base ORDER BY a; + +-- INSERTs that directly insert into a child partition. +INSERT INTO part1 (a, b) VALUES (14, 'not-def'); +INSERT INTO part2 VALUES ('daz', 26, TRUE), ('faz', 27, FALSE); +INSERT INTO part3 (SELECT a + 12, c, b FROM part2 WHERE a IN (26, 27)); + +SELECT * FROM base ORDER BY a; + +-- INSERT .. ON CONFLICT DO NOTHING cases +INSERT INTO base VALUES (11, 'doodoo', FALSE) ON CONFLICT DO NOTHING; +INSERT INTO base (a, b, c) VALUES (21, 'caz', TRUE) ON CONFLICT (a) DO NOTHING; +INSERT INTO base (c, b, a) VALUES (TRUE, 'abc', 26), (TRUE, 'abc', 26) ON CONFLICT (a) DO NOTHING; +INSERT INTO base (a, b, c) VALUES (11, 'aaa', TRUE), (31, 'ccc', FALSE), (21, 'bbb', TRUE) ON CONFLICT (a) DO NOTHING; +INSERT INTO base (a, b, c) VALUES (61, 'part6a-def', FALSE), (67, 'part6b-def', TRUE) ON CONFLICT DO NOTHING; +INSERT INTO part1 (a, b) VALUES (14, 'not-abc') ON CONFLICT DO NOTHING; +INSERT INTO part2 VALUES ('not-daz', 26, FALSE) ON CONFLICT (a) DO NOTHING; +INSERT INTO part3 (SELECT a + 6, c, b FROM part2 WHERE a IN (26, 27)) ON CONFLICT DO NOTHING; + +SELECT * FROM base ORDER BY a; + +-- INSERT .. ON CONFLICT DO UPDATE cases +-- Update partitioned column +INSERT INTO base VALUES (11, 'doodoo', FALSE), (12, 'zoozoo', FALSE), (21, 'bazbaz', FALSE) ON CONFLICT (a) DO UPDATE SET a = EXCLUDED.a; +INSERT INTO base VALUES (11, 'doodoo', FALSE), (12, 'zoozoo', FALSE), (21, 'bazbaz', FALSE) ON CONFLICT (a) DO UPDATE SET a = base.a; +INSERT INTO base (a, b) VALUES (15, 'ghi'), (62, 'part6a-def'), (100, 'partmisc-abc') ON CONFLICT (a) DO UPDATE SET a = EXCLUDED.a + 1; +-- Update both partitioned and non-partitioned columns +INSERT INTO base AS old (a, b, c) VALUES (11, 'doodoo', FALSE), (12, 'zoozoo', FALSE), (21, 'bazbaz', FALSE) ON CONFLICT (a) DO UPDATE SET b = old.b || 'z'; +INSERT INTO base AS old (b, c, a) VALUES ('bar', TRUE, 13), ('baz', FALSE, 22), ('qux', TRUE, 33) ON CONFLICT (a) DO UPDATE SET b = old.b || 'z'; +INSERT INTO base AS old (a) VALUES (22), (32), (62) ON CONFLICT (a) DO UPDATE SET b = old.b || 'y', a = EXCLUDED.a; +-- Update multiple columns in the column list format +INSERT INTO base AS old (a) VALUES (22), (32), (62) ON CONFLICT (a) DO UPDATE SET (b, a, c) = (old.b || 'y', EXCLUDED.a, NOT old.c); +-- Update columns with implicit type casting +INSERT INTO base (a, b, c) VALUES (38, 'dazzle', TRUE) ON CONFLICT (a) DO UPDATE SET (c, b, a) = (0::BOOL, 'dazz'::VARCHAR, 38.0); +-- Perform constraint checking as a constraint +INSERT INTO base AS old (a) VALUES (11), (102), (68) ON CONFLICT ON CONSTRAINT base_pkey DO UPDATE SET b = old.b || 'z'; +-- Error: Moving between partitions should be recognized and disallowed. +INSERT INTO base VALUES(11, 'aaa', TRUE), (21, 'bbb', TRUE) ON CONFLICT (a) DO UPDATE SET a = base.a + 15; +INSERT INTO base VALUES(61, 'aaa', TRUE), (66, 'bbb', TRUE) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b || 'z', a = base.a + 5; + +INSERT INTO part1 VALUES (TRUE, 'def', 14), (TRUE, 'ghi', 15) ON CONFLICT (a) DO UPDATE SET b = 'not-' || part1.b; +INSERT INTO part3 AS old (a, b, c) VALUES (31, 'qux', TRUE), (39, 'fax', TRUE) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b || 'x'; +INSERT INTO part6 VALUES ('part6a-def', 61, FALSE), ('part6b-def', 66, TRUE) ON CONFLICT (a) DO UPDATE SET (b, c) = (EXCLUDED.b || 'z', NOT EXCLUDED.c); + +SELECT * FROM base ORDER BY a; + +-- UPDATE cases +-- Updates affecting a single partition to the correct partition and in the correct format. +UPDATE base SET a = a, b = 'y' || b WHERE a IN (21, 31); +-- Updates spanning multiples partitions must supply tuples in the correct format. +UPDATE base SET b = 'updated-' || b WHERE a % 10 = 2; +UPDATE base SET (c, b) = ((c::INT + 1)::BOOL, 'test-' || b) WHERE a % 10 = 6; +-- Updates affecting the partition key should be handled correctly when the +-- affected row moves between partitions. +UPDATE base SET a = a + 10, b = b || 'z' WHERE a IN (15, 27, 101); +-- Updates targeting a specific partition should work as expected. +UPDATE part1 SET c = NOT c, a = a + 2 WHERE a BETWEEN 60 AND 65 RETURNING *; -- no rows affected +UPDATE part6a SET c = NOT c, a = a + 2 WHERE a BETWEEN 60 AND 65 RETURNING c, b, a; +UPDATE part2 SET b = 'y' || b WHERE a % 10 = 1; +UPDATE part3 SET (b, c) = (b || 'z', NOT c) WHERE a >= 33 AND a <= 38; + +SELECT * FROM base ORDER BY a; +TRUNCATE base; + +-- GH-25147: Test UPDATE projections with partitions out-of-order. +INSERT INTO base VALUES (11, 'foo', TRUE), (21, 'bar', TRUE), (31, 'baz', FALSE); +UPDATE base SET (a, b) = (SELECT base.a, base.b || 'z'); +UPDATE base SET (b, c, a) = (SELECT base.b || 'y', NOT base.c, base.a); +UPDATE base SET (b, c, a) = (SELECT base.b || 'y', base.c, base.a + 1); +INSERT INTO base (a, b) VALUES (12, 'foo'), (32, 'baz') ON CONFLICT(a) DO UPDATE SET (a, b) = (SELECT EXCLUDED.a, base.b || 'x'); + +SELECT * FROM base ORDER BY a; diff --git a/src/test/regress/sql/yb.orig.partition_pk.sql b/src/test/regress/sql/yb.orig.partition_pk.sql new file mode 100644 index 0000000000000..0453c62f3c459 --- /dev/null +++ b/src/test/regress/sql/yb.orig.partition_pk.sql @@ -0,0 +1,123 @@ +-- Verify that for all 3 types of partitioning, the primary key +-- is correctly inherited by the partitions. +-- Hash partitioning where primary key contains both hash and range components. +CREATE TABLE test(h1 int NOT NULL, + h2 int NOT NULL, + h3 int NOT NULL, + v1 int, + primary key(h1, h2, h3)) +PARTITION BY HASH(h1); + +-- Create a partition table. +CREATE TABLE test_1 PARTITION of test FOR VALUES WITH (modulus 2, remainder 0); + +-- Create a table without a primary key and attach it as a partition. +CREATE TABLE test_2(h1 int NOT NULL, + h2 int NOT NULL, + h3 int NOT NULL, + v1 int); +ALTER TABLE test ATTACH PARTITION test_2 FOR VALUES WITH (modulus 2, remainder 1); + +\d test_1; +\d test_2; + +INSERT INTO test VALUES (1, 4, 3); + +-- Fail because primary key constraint is violated. +INSERT INTO test VALUES (1, 4, 3); +EXPLAIN (COSTS OFF) SELECT * FROM test WHERE h1 = 1 AND h2 = 4 AND h3 = 3; +EXPLAIN (COSTS OFF) SELECT * FROM test WHERE h1 = 1 ORDER BY h2; +EXPLAIN (COSTS OFF) SELECT * FROM test WHERE h1 = 1 AND h2 = 4 ORDER BY h3; + +-- LIST partitioning where primary key contain only hash components. +CREATE TABLE person ( + person_id int not null, + country text, + name text, + age int, + PRIMARY KEY((person_id, country) HASH)) +PARTITION BY LIST (country); + +CREATE TABLE person_americas + PARTITION OF person + FOR VALUES IN ('United States', 'Brazil', 'Mexico', 'Columbia'); + +CREATE TABLE person_apac ( + person_id int not null, + country text not null, + name text, + age int); +ALTER TABLE person ATTACH PARTITION person_apac FOR VALUES IN ('India', 'Singapore'); + +\d person_americas; +\d person_apac; + +INSERT INTO person_americas VALUES (1, 'United States', 'Jane Doe', 23); +-- Fail due to primary key constraint failure. +INSERT INTO person_americas VALUES (1, 'United States', 'Jane Doe', 23); + +EXPLAIN (COSTS OFF) SELECT * FROM person WHERE person_id=1 AND country='United States'; + +-- Range partitioning where primary key contains only range components. +CREATE TABLE parted (a int, b text, PRIMARY KEY (a ASC, b DESC)) PARTITION BY RANGE(a); +CREATE TABLE part_a_1_5 PARTITION OF parted (a, b) FOR VALUES FROM (1) TO (5); +CREATE TABLE part_a_5_10 PARTITION OF parted (a, b) FOR VALUES FROM (5) TO (10); + +\d part_a_1_5; +\d part_a_5_10; + +INSERT INTO parted VALUES (1, '1'); + +-- Fail +INSERT INTO parted VALUES (1, '1'); +EXPLAIN (COSTS OFF) SELECT * FROM parted WHERE a = 1 ORDER BY b DESC; +EXPLAIN (COSTS OFF) SELECT * FROM parted ORDER BY a; + +-- Test creating a partition with a different primary key. +CREATE TABLE part_a_15_20 PARTITION OF parted (a, b, PRIMARY KEY(a HASH)) FOR VALUES FROM (15) TO (20); + +-- Test attaching a partition with a different primary key. +CREATE TABLE part_a_20_25 (a int, b text NOT NULL, PRIMARY KEY (a ASC)); +ALTER TABLE parted ATTACH PARTITION part_a_20_25 FOR VALUES FROM (20) TO (25); + +-- Create a partition table without a primary key and attach it as a partition. +CREATE TABLE part_a_25_40 (a int NOT NULL, b text NOT NULL) PARTITION BY range (a); +CREATE TABLE part_a_25_35 PARTITION OF part_a_25_40 FOR VALUES FROM (25) TO (35) PARTITION BY range (a); +CREATE TABLE part_a_25_30 PARTITION OF part_a_25_35 FOR VALUES FROM (25) TO (30); +CREATE TABLE part_a_30_35 PARTITION OF part_a_25_35 FOR VALUES FROM (30) TO (35); +CREATE TABLE part_a_35_40 PARTITION OF part_a_25_40 FOR VALUES FROM (35) TO (40); +ALTER TABLE parted ATTACH PARTITION part_a_25_40 FOR VALUES FROM (25) TO (40); +\d part_a_25_40; +\d part_a_25_35; +\d part_a_25_30; +INSERT INTO parted VALUES (26, '26'); +INSERT INTO parted VALUES (26, '26'); -- should fail. +INSERT INTO parted VALUES (31, '31'); +INSERT INTO parted VALUES (31, '31'); -- should fail. +INSERT INTO parted VALUES (36, '36'); +INSERT INTO parted VALUES (36, '36'); -- should fail. +-- Test a complicated situation where the attribute numbers of partition tables +-- and partitioned tables are different. +CREATE TABLE col_order_change ( + a text, + b bigint, + c numeric, + d int, + e varchar, + PRIMARY KEY ((a,b) HASH, c ASC, d DESC) +) PARTITION BY RANGE (a, b); + +CREATE TABLE col_order_change_part (e varchar, c numeric, a text, b bigint, d int NOT NULL); +ALTER TABLE col_order_change_part DROP COLUMN e, DROP COLUMN c, DROP COLUMN a; +ALTER TABLE col_order_change_part ADD COLUMN c numeric NOT NULL, ADD COLUMN e varchar NOT NULL, ADD COLUMN a text NOT NULL; +ALTER TABLE col_order_change_part DROP COLUMN b; +ALTER TABLE col_order_change_part ADD COLUMN b bigint NOT NULL; +ALTER TABLE col_order_change ATTACH PARTITION col_order_change_part FOR VALUES FROM ('a', 10) TO ('a', 20); +\d col_order_change_part; + +-- Cleanup +DROP TABLE col_order_change; +DROP TABLE parted; +DROP TABLE person; +DROP TABLE test; +DROP TABLE part_a_20_25; diff --git a/src/test/regress/sql/yb.orig.partition_prune.sql b/src/test/regress/sql/yb.orig.partition_prune.sql new file mode 100644 index 0000000000000..3986aa373691b --- /dev/null +++ b/src/test/regress/sql/yb.orig.partition_prune.sql @@ -0,0 +1,47 @@ +set yb_enable_bitmapscan = true; + +create table ab (a int not null, b int not null) partition by list (a); +create table ab_a2 partition of ab for values in(2) partition by list (b); +create table ab_a2_b1 partition of ab_a2 for values in (1); +create table ab_a2_b2 partition of ab_a2 for values in (2); +create table ab_a2_b3 partition of ab_a2 for values in (3); +create table ab_a1 partition of ab for values in(1) partition by list (b); +create table ab_a1_b1 partition of ab_a1 for values in (1); +create table ab_a1_b2 partition of ab_a1 for values in (2); +create table ab_a1_b3 partition of ab_a1 for values in (3); +create table ab_a3 partition of ab for values in(3) partition by list (b); +create table ab_a3_b1 partition of ab_a3 for values in (1); +create table ab_a3_b2 partition of ab_a3 for values in (2); +create table ab_a3_b3 partition of ab_a3 for values in (3); + +create index ab_a2_b1_a_idx on ab_a2_b1 (a); +create index ab_a2_b2_a_idx on ab_a2_b2 (a); +create index ab_a2_b3_a_idx on ab_a2_b3 (a); +create index ab_a1_b1_a_idx on ab_a1_b1 (a); +create index ab_a1_b2_a_idx on ab_a1_b2 (a); +create index ab_a1_b3_a_idx on ab_a1_b3 (a); +create index ab_a3_b1_a_idx on ab_a3_b1 (a); +create index ab_a3_b2_a_idx on ab_a3_b2 (a); +create index ab_a3_b3_a_idx on ab_a3_b3 (a); + +insert into ab values (1, 1), (2, 1), (3, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3); + +set enable_bitmapscan to true; + +-- Test Bitmap Scans +create index on ab (b ASC); +explain (analyze, costs off, summary off, timing off) +/*+ bitmapscan(ab) */ select * from ab where (ab.a = 1 AND ab.b = 1); + +-- all ab_a(1|2)_b(1|2) partitions +explain (analyze, costs off, summary off, timing off) +/*+ bitmapscan(ab) */ select * from ab where (ab.a = 1 AND ab.b = 1) OR (ab.a = 2 AND ab.b = 2); + +-- all ab_a1_* and ab_*_b1 tables +explain (analyze, costs off, summary off, timing off) +/*+ bitmapscan(ab) */ select * from ab where (ab.a = 1 OR ab.b = 1); + +reset enable_bitmapscan; +reset yb_enable_bitmapscan; + +drop table ab; diff --git a/src/test/regress/sql/yb.orig.partition_prune_plancache.sql b/src/test/regress/sql/yb.orig.partition_prune_plancache.sql new file mode 100644 index 0000000000000..ddee8cc501e2d --- /dev/null +++ b/src/test/regress/sql/yb.orig.partition_prune_plancache.sql @@ -0,0 +1,81 @@ +-- Test Setup. +-- Create a range-partitioning hierarchy with multiple keys and default partitions. +CREATE TABLE rp (a int, b int, c varchar, d text, PRIMARY KEY(a,b,c)) PARTITION BY RANGE(a); +CREATE TABLE rp_p1 PARTITION OF rp FOR VALUES FROM (0) TO (100); +CREATE TABLE rp_sub PARTITION OF rp FOR VALUES FROM (100) TO (200) PARTITION BY RANGE (b, c); +CREATE TABLE rp_p2 PARTITION OF rp_sub FOR VALUES FROM (0, 'a') TO (100, 'j'); +CREATE TABLE rp_p3 PARTITION OF rp_sub DEFAULT; + +-- Create a list-partitioning hierarchy with multiple keys, NULL partitions and default partitions. +CREATE TABLE lp (a int, b int, c varchar, d text) PARTITION BY LIST(a); +CREATE INDEX ON lp(a); +CREATE TABLE lp_p1 PARTITION OF lp FOR VALUES IN (0, 1, 2, 3, 4, 5); +CREATE TABLE lp_sub PARTITION OF lp FOR VALUES IN (6, 7, 8, 9, 10) PARTITION BY LIST (b); +CREATE TABLE lp_p2 PARTITION OF lp_sub FOR VALUES IN (null); +CREATE TABLE lp_p3 PARTITION OF lp_sub FOR VALUES IN (1, 2); + +-- Create a non-partitioned table. +CREATE TABLE np (a int, b int, c varchar, d text); + +-- Set the number of times custom plans are chosen over generic plans unconditionally. +-- This means that the first time a prepared statement is executed, it will always be +-- executed with a custom plan. Cost comparison between custom and generic plans will +-- take effect at the second invocation. +SET yb_test_planner_custom_plan_threshold=1; + +-- Note: The presence of actual values provided to the bound parameters in EXPLAIN +-- output indicates a custom plan. The presence of symbols like '$1' in the EXPLAIN +-- output indicates that a generic plan was chosen. +-- SELECT query where one of the partition key values is a bound parameter and the other is +-- not a bound parameter. +PREPARE t1(int) AS SELECT * FROM rp WHERE a=$1 AND b=3 AND c='1'; +EXPLAIN EXECUTE t1(1); +EXPLAIN EXECUTE t1(1); + +-- Turn off favoring custom plan over generic plan based on partition pruning. +SET yb_planner_custom_plan_for_partition_pruning=false; +EXPLAIN EXECUTE t1(1); + +-- Turn it back on. +SET yb_planner_custom_plan_for_partition_pruning=true; + +-- UPDATE list partitioned table using a JOIN with a non-partitioned table. +PREPARE t2(int) AS UPDATE np SET d = 1 FROM lp WHERE lp.a = np.a AND lp.a = $1; +EXPLAIN(COSTS OFF) EXECUTE t2(1); +EXPLAIN(COSTS OFF) EXECUTE t2(1); + +-- DELETE range partitioned table using a JOIN with a non-partitioned table. +PREPARE t3(int) AS DELETE FROM rp USING np WHERE rp.a = np.a AND rp.a = $1; +EXPLAIN EXECUTE t3(1); +EXPLAIN EXECUTE t3(1); + +-- Subquery test where the outer query has a partition key as a bound parameter. +PREPARE t4 AS SELECT * FROM rp WHERE rp.a = $1 AND rp.b IN (SELECT b FROM np WHERE a < 5) ; +EXPLAIN EXECUTE t4(1); +EXPLAIN EXECUTE t4(1); + +-- Subquery test whether the inner query has a partition key as a bound parameter. +PREPARE t5(int) AS SELECT * FROM np WHERE np.a = 1 AND np.b IN (SELECT b FROM lp WHERE a=$1 AND c='1'); +EXPLAIN EXECUTE t5(1); +EXPLAIN EXECUTE t5(1); + +-- CTE on partitioned tables. +PREPARE t6(int) AS +WITH x AS (UPDATE lp SET d=1 WHERE a = $1 returning a, b, c, d) +SELECT * FROM rp INNER JOIN x ON rp.a = x.a; +EXPLAIN EXECUTE t6(1); +EXPLAIN EXECUTE t6(1); + +-- Generic plan must be chosen for SELECTs/UPDATEs/DELETEs if the number of partitions +-- pruned by generic plan is equal to that of custom plan. +PREPARE t7(char) AS SELECT * FROM rp WHERE a=1 AND b=1 AND c=$1; +EXPLAIN EXECUTE t7('a'); +EXPLAIN EXECUTE t7('a'); + +PREPARE t8(char) AS UPDATE lp SET d=3 WHERE a=1 AND b=1 AND c=$1; +EXPLAIN EXECUTE t8('a'); +EXPLAIN EXECUTE t8('a'); + +PREPARE t9(int) AS DELETE FROM np WHERE a < $1; +EXPLAIN EXECUTE t9(1); +EXPLAIN EXECUTE t9(1); diff --git a/src/test/regress/sql/yb.orig.perf_secondary_index_scan.sql b/src/test/regress/sql/yb.orig.perf_secondary_index_scan.sql new file mode 100644 index 0000000000000..537727c2c9d61 --- /dev/null +++ b/src/test/regress/sql/yb.orig.perf_secondary_index_scan.sql @@ -0,0 +1,128 @@ +-- +-- This file is to check correctness of data after applying secondary index scan optimization. +-- +-- +-- Logical test on small size table, using column-ref expression index. +-- * The text_books test case is commented out because DocDB misbehaved on ASAN build. +-- * This test has been split to yb.orig.secondary_index_scan.sql to be run in different session. +-- * See Github issue #4488. Once the issue is fixed we can merge them back together. +-- +-- CREATE TABLE text_books ( id int PRIMARY KEY, author text, year int); +-- CREATE INDEX text_books_author_first_name ON text_books (author); +-- +-- INSERT INTO text_books (id, author, year) +-- VALUES (1, '{ "first_name": "William", "last_name": "Shakespeare" }', 1623); +-- INSERT INTO text_books (id, author, year) +-- VALUES (2, '{ "first_name": "William", "last_name": "Shakespeare" }', 1603); +-- INSERT INTO text_books (id, author, year) +-- VALUES (3, '{ "first_name": "Charles", "last_name": "Dickens" }', 1838); +-- INSERT INTO text_books (id, author, year) +-- VALUES (4, '{ "first_name": "Charles", "last_name": "Dickens" }', 1950); +-- INSERT INTO text_books (id, author, year) +-- VALUES (5, '{ "first_name": "Stephen", "last_name": "Hawking" }', 1988); +-- +-- EXPLAIN (COSTS OFF) SELECT id FROM text_books WHERE author = 'Hello World' ORDER BY year; +-- SELECT id FROM text_books WHERE author = 'Hello World' ORDER BY year; +-- EXPLAIN (COSTS OFF) SELECT id FROM text_books +-- WHERE author = '{ "first_name": "William", "last_name": "Shakespeare" }' ORDER BY year; +-- SELECT id FROM text_books +-- WHERE author = '{ "first_name": "William", "last_name": "Shakespeare" }' ORDER BY year; +-- Drop INDEX and run again. +-- DROP index text_books_author_first_name; +-- EXPLAIN (COSTS OFF) SELECT id FROM text_books WHERE author = 'Hello World' ORDER BY year; +-- SELECT id FROM text_books WHERE author = 'Hello World' ORDER BY year; +-- EXPLAIN (COSTS OFF) SELECT id FROM text_books +-- WHERE author = '{ "first_name": "William", "last_name": "Shakespeare" }' ORDER BY year; +-- SELECT id FROM text_books +-- WHERE author = '{ "first_name": "William", "last_name": "Shakespeare" }' ORDER BY year; +-- +-- Logical test on small size table, using JSONB expression index. +-- +-- CREATE TABLE books ( id int PRIMARY KEY, details jsonb ); +-- CREATE INDEX books_author_first_name ON books ((details->'author'->>'first_name')); +-- INSERT INTO books (id, details) +-- VALUES (1, '{ "name": "Macbeth", +-- "author": { "first_name": "William", "last_name": "Shakespeare" }, +-- "year": 1623, +-- "editors": ["John", "Elizabeth", "Jeff"] }'); +-- INSERT INTO books (id, details) +-- VALUES (2, '{ "name": "Hamlet", +-- "author": { "first_name": "William", "last_name": "Shakespeare" }, +-- "year": 1603, +-- "editors": ["Lysa", "Mark", "Robert"] }'); +-- INSERT INTO books (id, details) +-- VALUES (3, '{ "name": "Oliver Twist", +-- "author": { "first_name": "Charles", "last_name": "Dickens" }, +-- "year": 1838, +-- "genre": "novel", +-- "editors": ["Mark", "Tony", "Britney"] }'); +-- INSERT INTO books (id, details) +-- VALUES (4, '{ "name": "Great Expectations", +-- "author": { "first_name": "Charles", "last_name": "Dickens" }, +-- "year": 1950, +-- "genre": "novel", +-- "editors": ["Robert", "John", "Melisa"] }'); +-- INSERT INTO books (id, details) +-- VALUES (5, '{ "name": "A Brief History of Time", +-- "author": { "first_name": "Stephen", "last_name": "Hawking" }, +-- "year": 1988, +-- "genre": "science", +-- "editors": ["Melisa", "Mark", "John"] }'); +-- EXPLAIN (COSTS OFF) SELECT id FROM books WHERE details->'author'->>'first_name' = 'Hello World' +-- ORDER BY details->>'name'; +-- SELECT id FROM books WHERE details->'author'->>'first_name' = 'Hello World' +-- ORDER BY details->>'name'; +-- EXPLAIN (COSTS OFF) SELECT id FROM books WHERE details->'author'->>'first_name' = 'Charles' +-- ORDER BY details->>'name'; +-- SELECT id FROM books WHERE details->'author'->>'first_name' = 'Charles' +-- ORDER BY details->>'name'; +-- Drop INDEX and run again. +-- DROP index books_author_first_name; +-- EXPLAIN (COSTS OFF) SELECT id FROM books WHERE details->'author'->>'first_name' = 'Hello World' +-- ORDER BY details->>'name'; +-- SELECT id FROM books WHERE details->'author'->>'first_name' = 'Hello World' +-- ORDER BY details->>'name'; +-- EXPLAIN (COSTS OFF) SELECT id FROM books WHERE details->'author'->>'first_name' = 'Charles' +-- ORDER BY details->>'name'; +-- SELECT id FROM books WHERE details->'author'->>'first_name' = 'Charles' +-- ORDER BY details->>'name'; +-- +-- Logical test on larger size table. +-- Table definition +-- +-- CREATE TABLE airports(ident TEXT, +-- type TEXT, +-- name TEXT, +-- elevation_ft INT, +-- continent TEXT, +-- iso_country CHAR(2), +-- iso_region CHAR(7), +-- municipality TEXT, +-- gps_code TEXT, +-- iata_code TEXT, +-- local_code TEXT, +-- coordinates TEXT, +-- PRIMARY KEY (ident)); +-- +-- Index for SELECTing ybctid of the same airport country using HASH key. +-- CREATE INDEX airport_type_region_idx ON airports((type, iso_region) HASH, ident ASC); +-- +-- +-- The following queries are to ensure the data is in indexing order. +-- NOTE: In the above indexes, range column "ident" is in ASC. +-- +-- Column 'ident' should be sorted in ASC for this SELECT +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE type = 'closed' AND iso_region = 'US-CA'; +SELECT * FROM airports WHERE type = 'closed' AND iso_region = 'US-CA'; +-- +-- This query the first 10 rows. +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE type = 'medium_airport' AND iso_region = 'US-CA' + ORDER BY ident LIMIT 10; +SELECT * FROM airports WHERE type = 'medium_airport' AND iso_region = 'US-CA' + ORDER BY ident LIMIT 10; +-- +-- This query the last 10 rows. +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE type = 'medium_airport' AND iso_region = 'US-CA' + ORDER BY ident DESC LIMIT 10; +SELECT * FROM airports WHERE type = 'medium_airport' AND iso_region = 'US-CA' + ORDER BY ident DESC LIMIT 10; diff --git a/src/test/regress/sql/yb.orig.pg_stat.sql b/src/test/regress/sql/yb.orig.pg_stat.sql new file mode 100644 index 0000000000000..842ea9aa9ed29 --- /dev/null +++ b/src/test/regress/sql/yb.orig.pg_stat.sql @@ -0,0 +1,228 @@ +-- Test for PG_STAT +-- test FOR LSM idx_scan in pg_stat_user_indexes +create table maintable(c1 INT, c2 TEXT, PRIMARY KEY(c1)); +insert into maintable (c1, c2) values (4, 'bob'); +create index maintable_idx on maintable (c2) include (c1); +/*+IndexOnlyScan(maintable_idx)*/ +select * from maintable where c2='bob'; +-- need to sleep for over half a second here since updates to pgstat is hardcoded to 500 milliseconds +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='maintable_idx'; + +/*+IndexScan(maintable)*/ +select * from maintable where c2='bob'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='maintable_idx'; + +-- negative case where we don't use index scan +/*+SeqScan(maintable)*/ +select * from maintable; +select * from maintable where c2='bob'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='maintable_idx'; + +-- test case for primary key scan +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='maintable_pkey'; +/*+IndexScan(maintable)*/ +select * from maintable where c1=4; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='maintable_idx'; +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='maintable_pkey'; + +-- test case for transaction abort +begin; +/*+IndexScan(maintable)*/ +select * from maintable where c2='bob'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='maintable_idx'; +abort; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='maintable_idx'; + +-- test for table view +create view maintable_view as select * from maintable; +select * from maintable_view where c2='bob'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='maintable_idx'; + +-- test for materialized table view +create materialized view materialized_maintable_view as select * from maintable where c2='bob'; +select * from materialized_maintable_view where c2='bob'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='maintable_idx'; + +-- test for refreshing materialized table view +insert into maintable (c1, c2) values (6, 'sol'); +/*+IndexScan(maintable) IndexScan(materialized_maintable_view)*/ +refresh materialized view materialized_maintable_view; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='maintable_idx'; + +-- test for refreshing materialized table view with index +create index materialized_view_idx on materialized_maintable_view (c2) include (c1); +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='materialized_view_idx'; +select * from materialized_maintable_view where c2='bob'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='materialized_view_idx'; +refresh materialized view materialized_maintable_view; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='materialized_view_idx'; + +-- test for joined table +create table table2 (c1 INT PRIMARY KEY, c2 TEXT); +insert into table2 (c1, c2) values (8, 'bob'); +create index table2_index on table2 (c2) include (c1); +/*+IndexScan(table2) */ +explain (costs off) select maintable.c1, table2.c1, table2.c2 from maintable, table2 where table2.c2=maintable.c2; +/*+IndexScan(table2) */ +select maintable.c1, table2.c1, table2.c2 from maintable, table2 where table2.c2=maintable.c2; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname in ('maintable_idx', 'table2_index') order by (indexrelname); + +/*+IndexScan(maintable)*/ +explain (costs off) select table2.c1, table2.c2, maintable.c1 from table2, maintable where table2.c2=maintable.c2; +/*+IndexScan(maintable)*/ +select table2.c1, table2.c2, maintable.c1 from table2, maintable where table2.c2=maintable.c2; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname in ('maintable_idx', 'table2_index') order by (indexrelname); + +-- test for multitablet table +create table multitablet_table (c1 INT PRIMARY KEY, c2 TEXT) split into 3 tablets; +insert into multitablet_table (c1, c2) values (9, 'caledonia'); +create index multitablet_table_index on multitablet_table (c2) include (c1); +select * from multitablet_table where c2='caledonia'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='multitablet_table_index'; + +-- test for alter table primary key +create table basic_table (c1 INT, c2 TEXT); +insert into basic_table (c1, c2) values (6, '9'); +create index basic_table_idx1 on basic_table (c1) include (c2); +create index basic_table_idx2 on basic_table (c2) include (c1); +select * from basic_table where c1=6; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname ~ 'basic_table_idx.' order by (indexrelname); +select * from basic_table where c2='9'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname ~ 'basic_table_idx.' order by (indexrelname); + +alter table basic_table add primary key (c1); + +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname ~ 'basic_table_idx.' or indexrelname='basic_table_pkey' order by (indexrelname); +select * from basic_table where c1=6; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname ~ 'basic_table_idx.' or indexrelname='basic_table_pkey' order by (indexrelname); +select * from basic_table where c2='9'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname ~ 'basic_table_idx.' or indexrelname='basic_table_pkey' order by (indexrelname); + +-- test for temporary table index +create temporary table temp_table (c1 INT PRIMARY KEY, c2 TEXT); +insert into temp_table (c1, c2) values (9, 'penguin'); +create index temp_index on temp_table (c2) include (c1); +/*+IndexScan(temp_table)*/ +explain (costs off) select * from temp_table where c2='penguin'; +/*+IndexScan(temp_table)*/ +select * from temp_table where c2='penguin'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='temp_index'; + +-- test for partitioned tables +create table partitioned_table (k INT, v TEXT) partition by range (k); +create table p1 partition of partitioned_table + for values from (1) to (3); +create table p2 partition of partitioned_table + for values from (3) to (5); +create table p3 partition of partitioned_table + for values from (5) to (7); +insert into partitioned_table (k, v) values (2, '2'); +insert into partitioned_table (k, v) values (4, '2'); +insert into partitioned_table (k, v) values (6, '2'); +create index partitioned_idx on partitioned_table (v) include (k); +select * from partitioned_table where v='2' order by (k); + +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname ~ 'p._v_k_idx' order by (indexrelname); + +drop index if exists partitioned_idx; +create index p1idx on p1 (v) include (k); +create index p2idx on p2 (v) include (k); +create index p3idx on p3 (v) include (k); +select * from partitioned_table where v='2' order by (k); + +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname ~ 'p.idx' order by (indexrelname); + +-- test for GIN idx_scan increment +create table pendtest (ts tsvector); +create index pendtest_idx on pendtest using gin(ts); +insert into pendtest values (to_tsvector('Lore ipsum')); +/*+IndexScan(pendtest)*/ +select * from pendtest where 'ipsu:*'::tsquery @@ ts; + +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='pendtest_idx'; + +-- negative case where we don't use index scan +/*+SeqScan(pendtest)*/ +select * from pendtest; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='pendtest_idx'; + +-- temp index for GIN table +drop table if exists pendtest cascade; +create temporary table pendtest (ts tsvector); +create index pendtest_idx on pendtest using gin(ts); +insert into pendtest values (to_tsvector('Lore ipsum')); +-- this should be an index scan but is for some reason still a sequential scan +-- we will need to fix this during planning phase, ignores pg_hint_plan +/*+IndexScan(pendtest)*/ +explain (costs off) select * from pendtest where ts @@ to_tsquery('ipsu:*'); +/*+IndexScan(pendtest)*/ +select * from pendtest where ts @@ to_tsquery('ipsu:*'); + +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='pendtest_idx'; + +-- negative case where we don't use index scan +/*+SeqScan(pendtest)*/ +explain (costs off) select * from pendtest; +/*+SeqScan(pendtest)*/ +select * from pendtest; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname='pendtest_idx'; + +-- test for colocated table +create database colocated_db with colocation = true; +-- TODO: pg_sleep is a workaround, remove it after fixing of #14519 +select pg_sleep(3); +\c colocated_db; +-- TODO: pg_sleep is a workaround, remove it after fixing of #14519 +select pg_sleep(3); +create table mycolocatedtable (c1 INT PRIMARY KEY, c2 TEXT, c3 INT); +insert into mycolocatedtable (c1, c2, c3) values (6, '9', 8); +create index mycolocatedtable_index1 on mycolocatedtable (c2); +create index mycolocatedtable_index2 on mycolocatedtable (c3); +select * from mycolocatedtable where c2='9'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname ~ 'mycolocatedtable_index.' order by (indexrelname); +select * from mycolocatedtable where c3=8; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname ~ 'mycolocatedtable_index.' order by (indexrelname); + +-- test for tablegroup +create database test_db; +\c test_db; +create tablegroup test_tg; +create table test_t (c1 INT PRIMARY KEY, c2 TEXT, c3 INT) tablegroup test_tg; +insert into test_t (c1, c2, c3) values (6, '9', 8); +create index test_index1 on test_t (c2); +create index test_index2 on test_t (c3); +select * from test_t where c2='9'; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname ~ 'test_index.' order by (indexrelname); +select * from test_t where c3=8; +select pg_sleep(1); +select indexrelname,idx_scan from pg_stat_user_indexes where indexrelname ~ 'test_index.' order by (indexrelname); \ No newline at end of file diff --git a/src/test/regress/sql/yb.orig.pg_stat_backend.sql b/src/test/regress/sql/yb.orig.pg_stat_backend.sql new file mode 100644 index 0000000000000..9cf5f030e1c8a --- /dev/null +++ b/src/test/regress/sql/yb.orig.pg_stat_backend.sql @@ -0,0 +1,73 @@ +-- +-- Test pg_stat backend functions. +-- +-- Avoid outputting pid, dbid, catalog_version since those can vary between +-- runs. +-- + +-- Test pg_stat_activity view. The four rows correspond to checkpointer, +-- this connection, the java test, and the ASH collector. +SELECT datname, usename, state, query, backend_type, + catalog_version IS NOT null AS has_catalog_snapshot + FROM pg_stat_activity ORDER BY usename; + +-- Test yb_pg_stat_get_backend_catalog_version. +SELECT beid, + backend_type, + yb_pg_stat_get_backend_catalog_version(beid) IS NOT NULL AS has_catalog_snapshot +FROM pg_stat_get_backend_idset() beid +JOIN pg_stat_activity s ON pg_stat_get_backend_pid(beid) = s.pid; + +-- Test that yb_pg_stat_get_backend_catalog_version for this backend matches +-- yb_catalog_version. +SELECT beid, + backend_type, + yb_catalog_version() - be_catalog_version AS catalog_version_diff +FROM pg_stat_get_backend_idset() AS beid +JOIN pg_stat_activity s ON pg_stat_get_backend_pid(beid) = s.pid +CROSS JOIN yb_pg_stat_get_backend_catalog_version(beid) AS be_catalog_version +WHERE be_catalog_version IS NOT NULL; + +-- Test pg_stat_get_backend_dbid for backends with yugabyte database. +SELECT beid, + backend_type, + d.datname AS database_name +FROM pg_stat_get_backend_idset() AS beid +JOIN pg_database d ON pg_stat_get_backend_dbid(beid) = d.oid +JOIN pg_stat_activity s ON pg_stat_get_backend_pid(beid) = s.pid +WHERE d.datname = current_database() +ORDER BY beid; + +-- Test pg_stat_get_backend_dbid for backends with postgres database after +-- switching current connection to postgres database. +\c postgres +SELECT beid, + backend_type, + d.datname AS database_name +FROM pg_stat_get_backend_idset() AS beid +JOIN pg_database d ON pg_stat_get_backend_dbid(beid) = d.oid +JOIN pg_stat_activity s ON pg_stat_get_backend_pid(beid) = s.pid +WHERE d.datname = current_database() +ORDER BY beid; + + +-- Test yb_pg_stat_get_backend_catalog_version follows yb_catalog_version +-- during DDLs. +CREATE TABLE actual_expected (actual int, expected int); +ALTER TABLE actual_expected ADD UNIQUE (actual); +ALTER TABLE actual_expected ADD UNIQUE (expected); +INSERT INTO actual_expected + SELECT catalog_version, yb_catalog_version() + FROM pg_stat_get_backend_idset() beid, + yb_pg_stat_get_backend_catalog_version(beid) catalog_version, + pg_stat_get_backend_pid(beid) pid + WHERE pid = pg_backend_pid(); +CREATE TABLE tmp (i int); +DROP TABLE tmp; +INSERT INTO actual_expected + SELECT catalog_version, yb_catalog_version() + FROM pg_stat_get_backend_idset() beid, + yb_pg_stat_get_backend_catalog_version(beid) catalog_version, + pg_stat_get_backend_pid(beid) pid + WHERE pid = pg_backend_pid(); +SELECT actual = expected AS check FROM actual_expected; diff --git a/src/test/regress/sql/yb.orig.planner_base_scans_cost_model.sql b/src/test/regress/sql/yb.orig.planner_base_scans_cost_model.sql new file mode 100644 index 0000000000000..cd5e495b2b51d --- /dev/null +++ b/src/test/regress/sql/yb.orig.planner_base_scans_cost_model.sql @@ -0,0 +1,110 @@ +-- This file tests the YB CBO base scans cost model in non-colocated tables. +SET yb_enable_base_scans_cost_model = ON; +SET yb_enable_optimizer_statistics = ON; +CREATE TABLE test (v1 INT, v2 INT, v3 INT); +CREATE INDEX test_index ON test ((v1) HASH, v2 ASC) INCLUDE (v3); +EXPLAIN (COSTS OFF) SELECT * FROM test WHERE v2 > 100; +SELECT * FROM test WHERE v2 > 100; +DROP TABLE test; + + +-------------------------------------------------------------------------------- +-- #24496 : Bad costs for index scan when ANALYZE is not run +-------------------------------------------------------------------------------- +CREATE TABLE t1 (k1 INT, v1 INT, PRIMARY KEY (k1 ASC)); +INSERT INTO t1 (SELECT s, s FROM generate_series(1, 100000) s); +CREATE INDEX t1_v1 ON t1 (v1 ASC); +SET yb_enable_base_scans_cost_model = ON; +/*+ IndexScan(t1) */ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE k1 > 80000; +/*+ IndexOnlyScan(t1 t1_v1) */ EXPLAIN (COSTS OFF) SELECT v1 FROM t1; +/*+ IndexOnlyScan(t1 t1_v1) */ EXPLAIN (COSTS OFF) SELECT v1 FROM t1 WHERE v1 < 50000; + +-- ANALYZE produces a rough estimate of the number of rows. This can make +-- the test flaky. To stabilize the test we write to pg_class.reltuples manually. +SET yb_non_ddl_txn_for_sys_tables_allowed = on; +UPDATE pg_class SET reltuples=100000 WHERE relname LIKE 't1%'; +UPDATE pg_yb_catalog_version SET current_version=current_version+1 WHERE db_oid=1; +SET yb_non_ddl_txn_for_sys_tables_allowed = off; + +\c yugabyte +SELECT reltuples FROM pg_class where relname LIKE 't1%'; + +SET yb_enable_base_scans_cost_model = ON; +/*+ IndexScan(t1) */ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE k1 > 80000; +/*+ IndexOnlyScan(t1 t1_v1) */ EXPLAIN (COSTS OFF) SELECT v1 FROM t1; +/*+ IndexOnlyScan(t1 t1_v1) */ EXPLAIN (COSTS OFF) SELECT v1 FROM t1 WHERE v1 < 50000; + + +-------------------------------------------------------------------------------- +-- #24916 : Partial Index clause is not included in estimate for data transfer costs +-------------------------------------------------------------------------------- +CREATE TABLE t_24916 (v1 INT, v2 INT, v3 TEXT); +INSERT INTO t_24916 SELECT s1, s2, repeat('a', 10000) FROM generate_series(1, 40) s1, generate_series(1, 40) s2; +CREATE INDEX t_24916_partial_idx ON t_24916 (v2 ASC) WHERE v1 = 1; +CREATE INDEX t_24916_full_idx_v1_v2 ON t_24916 (v1 ASC, v2 ASC); +ANALYZE t_24916; +SET yb_enable_base_scans_cost_model = ON; + +-- Partial Index Scan should be preferred over full index scan or seq scan +EXPLAIN (COSTS OFF) SELECT * FROM t_24916 WHERE v1 = 1 AND v2 < 5; + +-------------------------------------------------------------------------------- +-- #23709 : YB vs. PG operator cost disparity causing optimizer to choose +-- slower disk sort plan over ordered index without explicit sort +-------------------------------------------------------------------------------- + +CREATE TABLE t_23709 (a int, b int, c int, d char(512), g int, t timestamp, primary key (a ASC)); +CREATE INDEX idx_t_23709_g_t_a ON t_23709 (g ASC, t DESC, a DESC); +CREATE INDEX idx_t_23709_t_g_a ON t_23709 (t DESC, g ASC, a DESC); + +-- Simulating statatistics instead of adding 4M rows, to make test run faster +-- and to avoid flakiness. +SET yb_non_ddl_txn_for_sys_tables_allowed = ON; + +UPDATE pg_class SET reltuples = 4000000.0, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_23709' OR relname = 't_23709_pkey'); +UPDATE pg_class SET reltuples = 4000000.0, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_23709_pkey' OR relname = 't_23709_pkey_pkey'); +UPDATE pg_class SET reltuples = 4000000.0, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'idx_t_23709_g_t_a' OR relname = 'idx_t_23709_g_t_a_pkey'); +UPDATE pg_class SET reltuples = 4000000.0, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'idx_t_23709_t_g_a' OR relname = 'idx_t_23709_t_g_a_pkey'); + +DELETE FROM pg_statistic WHERE starelid = 'public.t_23709'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 'a'); +INSERT INTO pg_statistic VALUES ('public.t_23709'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 'a'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0012466755}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{38, 42048, 81030, 120971, 161377, 198497, 237264, 278640, 318384, 358048, 395704, 435410, 474581, 515079, 553855, 595183, 633155, 670749, 713409, 755043, 797430, 837684, 879094, 923587, 963310, 1007316, 1046548, 1086825, 1128777, 1168887, 1208482, 1249097, 1289714, 1330255, 1372393, 1410885, 1451233, 1488918, 1528422, 1569157, 1608540, 1644234, 1682107, 1719635, 1758212, 1797026, 1839579, 1883798, 1923662, 1963896, 2000589, 2040171, 2084192, 2124639, 2162946, 2199029, 2242735, 2285812, 2330026, 2369318, 2408734, 2450429, 2490533, 2528832, 2566602, 2608123, 2648280, 2689266, 2731865, 2769815, 2810569, 2851840, 2888430, 2922850, 2963154, 2999105, 3037417, 3076571, 3114611, 3153881, 3194706, 3228791, 3266011, 3304785, 3343986, 3385912, 3428375, 3472589, 3516122, 3556998, 3595811, 3634195, 3675248, 3714389, 3756138, 3795568, 3837211, 3878330, 3918931, 3959480, 3999979}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_23709'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 'b'); +INSERT INTO pg_statistic VALUES ('public.t_23709'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 'b'), False::boolean, 0::real, 4::integer, -0.20015675::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.0027559977}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{38, 7739, 15336, 23350, 30685, 38354, 46075, 54400, 62194, 69870, 77506, 85736, 93782, 101635, 109027, 117334, 125644, 132661, 140716, 148665, 157135, 164881, 173173, 180968, 189040, 197776, 205804, 214177, 222135, 230329, 238083, 246638, 256057, 263936, 272210, 280502, 288276, 296161, 304858, 313355, 321602, 329881, 337441, 345818, 353430, 361680, 369551, 378196, 385826, 392486, 400190, 408168, 416335, 424445, 432504, 440766, 448629, 455445, 463183, 471705, 480099, 488591, 497126, 504552, 512105, 519894, 527503, 535118, 543299, 552734, 560343, 567753, 575626, 582638, 589793, 598024, 605760, 613188, 620890, 629354, 638527, 646786, 654883, 662297, 670157, 678307, 686414, 694652, 703195, 711046, 718938, 726963, 735214, 743405, 750743, 759359, 768206, 776302, 784058, 792127, 799979}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_23709'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 'c'); +INSERT INTO pg_statistic VALUES ('public.t_23709'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 'c'), False::boolean, 0::real, 4::integer, -0.138014::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.011019879}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{30, 5823, 11256, 17596, 23326, 29385, 34942, 40945, 46608, 52937, 57858, 62943, 68351, 74251, 80349, 85469, 91213, 97384, 103292, 108680, 115083, 120390, 125774, 131403, 137561, 143659, 149269, 155254, 160847, 166335, 172551, 178222, 183992, 189727, 195519, 200805, 206744, 212116, 218417, 223904, 229120, 234377, 239941, 245931, 251227, 257087, 262732, 268535, 273666, 279756, 285148, 290744, 296649, 302737, 308762, 314831, 319930, 325804, 331157, 336619, 342524, 348934, 354355, 360011, 365816, 371670, 377459, 383210, 388602, 394189, 399880, 405685, 411543, 417095, 422656, 429027, 434465, 440666, 446032, 451517, 457254, 462642, 468217, 473541, 479415, 484920, 490533, 496466, 501592, 506789, 512873, 518930, 525055, 530902, 536870, 542749, 549144, 554935, 560704, 565852, 571414}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_23709'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 'g'); +INSERT INTO pg_statistic VALUES ('public.t_23709'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 'g'), False::boolean, 0::real, 4::integer, -0.1243445::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00023303942}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{39, 5016, 9982, 15144, 19706, 24891, 29596, 34775, 39660, 44457, 49977, 54778, 59643, 64636, 69725, 75269, 80442, 84942, 89715, 94915, 100124, 104709, 109450, 114893, 119837, 124732, 129182, 133879, 138808, 143923, 148774, 153753, 159053, 163900, 168466, 173677, 178629, 183544, 187906, 192973, 198249, 202955, 207523, 212397, 217580, 222477, 227484, 231992, 237107, 242257, 247625, 252424, 257507, 262377, 267987, 272740, 277757, 282761, 287905, 292969, 298056, 303362, 308523, 313913, 318707, 323936, 329477, 334592, 339684, 344867, 349915, 354823, 359751, 364710, 369330, 374909, 379826, 384962, 389762, 394977, 399783, 404915, 409735, 414590, 419736, 424649, 429572, 434522, 439886, 445171, 450336, 456068, 460832, 465540, 470434, 475175, 480306, 485356, 490126, 495412, 499993}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_23709'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 'd'); +INSERT INTO pg_statistic VALUES ('public.t_23709'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 'd'), False::boolean, 0::real, 516::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 1058::oid, 1058::oid, 0::oid, 0::oid, 0::oid, 100::oid, 100::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.012379769}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1000055-1000056", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1039938-1039939", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1079009-1079010", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1121403-1121404", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1161169-1161170", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1201985-1201986", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1241351-1241352", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1280869-1280870", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1323327-1323328", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1366390-1366391", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1403896-1403897", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1442894-1442895", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1480181-1480182", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1520854-1520855", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1561171-1561172", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1601424-1601425", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1638367-1638368", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1675369-1675370", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1712883-1712884", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1750382-1750383", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1789491-1789492", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1832670-1832671", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1875843-1875844", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1916071-1916072", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1957080-1957081", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1994218-1994219", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2032373-2032374", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2076341-2076342", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2116900-2116901", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2156687-2156688", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2190546-2190547", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2234754-2234755", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2277930-2277931", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2321538-2321539", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2361455-2361456", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2402352-2402353", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2442055-2442056", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2484034-2484035", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2522025-2522026", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2558095-2558096", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2601527-2601528", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2640213-2640214", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2680977-2680978", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2724149-2724150", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2762350-2762351", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2800646-2800647", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2845294-2845295", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2881583-2881584", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2917451-2917452", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2956886-2956887", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2991924-2991925", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3030003-3030004", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3069028-3069029", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3106523-3106524", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3146288-3146289", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3187820-3187821", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3222363-3222364", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3258110-3258111", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3297416-3297417", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3337454-3337455", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3377102-3377103", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3421496-3421497", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3464953-3464954", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3507221-3507222", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3550305-3550306", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3589452-3589453", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3628016-3628017", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3666894-3666895", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3707698-3707699", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3748300-3748301", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3788417-3788418", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3827335-3827336", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3871275-3871276", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3911157-3911158", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3950783-3950784", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3992127-3992128", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx130634-130635", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx171211-171212", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx209534-209535", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx249222-249223", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx290371-290372", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx329771-329772", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx368614-368615", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx407680-407681", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx447321-447322", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx485195-485196", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx526616-526617", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx564912-564913", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx606052-606053", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx645311-645312", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx684504-684505", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx724798-724799", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx768473-768474", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx809308-809309", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx849006-849007", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx892251-892252", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx933676-933677", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx978369-978370", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx29536-29537", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx68001-68002", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx38-39"}', 'pg_catalog.bpchar'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_23709'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 't'); +INSERT INTO pg_statistic VALUES ('public.t_23709'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_23709'::regclass and a.attname = 't'), False::boolean, 0::real, 8::integer, -0.25548226::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 2062::oid, 2062::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.004064531}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{"2022-11-08T13:28:00", "2022-11-15T00:08:00", "2022-11-21T15:01:00", "2022-11-28T18:36:00", "2022-12-05T08:22:00", "2022-12-12T12:13:00", "2022-12-19T12:43:00", "2022-12-26T16:51:00", "2023-01-02T15:51:00", "2023-01-09T12:58:00", "2023-01-16T07:25:00", "2023-01-23T00:18:00", "2023-01-30T03:14:00", "2023-02-06T06:38:00", "2023-02-13T13:44:00", "2023-02-20T02:18:00", "2023-02-27T06:56:00", "2023-03-06T10:53:00", "2023-03-14T05:59:00", "2023-03-21T15:19:00", "2023-03-28T18:38:00", "2023-04-04T15:46:00", "2023-04-11T04:23:00", "2023-04-18T04:58:00", "2023-04-25T13:33:00", "2023-05-02T11:24:00", "2023-05-09T19:18:00", "2023-05-16T04:52:00", "2023-05-23T08:30:00", "2023-05-30T08:23:00", "2023-06-05T22:20:00", "2023-06-13T06:03:00", "2023-06-20T02:28:00", "2023-06-27T13:46:00", "2023-07-03T20:27:00", "2023-07-10T13:16:00", "2023-07-17T11:02:00", "2023-07-24T03:21:00", "2023-07-30T14:29:00", "2023-08-06T15:20:00", "2023-08-13T02:21:00", "2023-08-20T04:22:00", "2023-08-26T19:43:00", "2023-09-02T18:03:00", "2023-09-10T11:21:00", "2023-09-17T01:35:00", "2023-09-24T06:05:00", "2023-09-30T21:26:00", "2023-10-07T14:49:00", "2023-10-14T17:06:00", "2023-10-21T06:29:00", "2023-10-28T17:32:00", "2023-11-04T13:07:00", "2023-11-11T02:39:00", "2023-11-18T00:37:00", "2023-11-25T10:50:00", "2023-12-02T23:11:00", "2023-12-10T07:07:00", "2023-12-16T15:01:00", "2023-12-23T20:00:00", "2023-12-30T18:55:00", "2024-01-06T21:46:00", "2024-01-13T11:17:00", "2024-01-20T18:21:00", "2024-01-27T06:02:00", "2024-02-03T07:32:00", "2024-02-10T05:17:00", "2024-02-17T08:02:00", "2024-02-24T12:20:00", "2024-03-02T11:16:00", "2024-03-09T12:18:00", "2024-03-16T20:18:00", "2024-03-24T10:38:00", "2024-03-31T02:55:00", "2024-04-07T06:15:00", "2024-04-13T23:40:00", "2024-04-21T06:58:00", "2024-04-28T05:04:00", "2024-05-05T01:14:00", "2024-05-11T17:18:00", "2024-05-18T03:29:00", "2024-05-25T03:01:00", "2024-05-31T04:19:00", "2024-06-06T23:21:00", "2024-06-13T12:49:00", "2024-06-21T03:19:00", "2024-06-27T23:55:00", "2024-07-04T21:08:00", "2024-07-11T03:22:00", "2024-07-18T13:12:00", "2024-07-25T05:24:00", "2024-08-01T16:05:00", "2024-08-07T23:46:00", "2024-08-15T17:58:00", "2024-08-21T22:54:00", "2024-08-28T19:33:00", "2024-09-05T01:36:00", "2024-09-12T07:31:00", "2024-09-19T07:48:00", "2024-09-25T20:50:00", "2024-10-02T23:22:00"}', 'pg_catalog.timestamp'::regtype, -1)::anyarray, NULL, NULL, NULL); + +update pg_yb_catalog_version set current_version=current_version+1 where db_oid=1; +SET yb_non_ddl_txn_for_sys_tables_allowed = OFF; + +-- Before fixing the issue, the optimizer would pick Index Scan of +-- `idx_t_23709_t_g_a` followed by Sort to perform the GROUP BY operation. +-- Sorting is expensive and this is a suboptimal plan. +-- After this fix, the optimizer should pick Index Only Scan of +-- `idx_t_23709_g_t_a` even if it involves skip-scan. +EXPLAIN (COSTS OFF) SELECT g, max(a) FROM t_23709 WHERE t <= '2024-09-30 00:00:00 +0000' GROUP BY g; + +DROP TABLE t_23709; + +-------------------------------------------------------------------------------- +-- #26235 : Primary Index scan cost higher than Sequential cost in small tables +-------------------------------------------------------------------------------- + +CREATE TABLE t_26235 (k1 INT, v1 INT, PRIMARY KEY (k1 ASC)); +INSERT INTO t_26235 (SELECT s, s FROM generate_series(1, 10) s); +ANALYZE t_26235; +SET yb_enable_base_scans_cost_model = ON; + +-- Following querie should pick primary index scan over seq scan +EXPLAIN (COSTS OFF) SELECT * FROM t_26235 WHERE k1 < 1; +EXPLAIN (COSTS OFF) SELECT * FROM t_26235 WHERE k1 < 5; +EXPLAIN (COSTS OFF) SELECT * FROM t_26235 WHERE k1 < 10; + +-- Without filter, seq scan should be preferred +EXPLAIN (COSTS OFF) SELECT * FROM t_26235; + diff --git a/src/test/regress/sql/yb.orig.planner_base_scans_cost_model_colocated.sql b/src/test/regress/sql/yb.orig.planner_base_scans_cost_model_colocated.sql new file mode 100644 index 0000000000000..6777cb3a21c39 --- /dev/null +++ b/src/test/regress/sql/yb.orig.planner_base_scans_cost_model_colocated.sql @@ -0,0 +1,35 @@ +-- This file tests the YB CBO base scans cost model in colocated tables. +CREATE DATABASE base_scans_cost_model WITH COLOCATION = TRUE; +\c base_scans_cost_model + +SET yb_enable_base_scans_cost_model = ON; +SET yb_enable_optimizer_statistics = ON; + +-- #18689 : Occasional SIGSEGV in queries with inequality filters on multiple +-- index columns +CREATE TABLE test_18689 (k1 INT, k2 INT, k3 INT, PRIMARY KEY (k1, k2, k3)); +INSERT INTO test_18689 (SELECT s, s, s FROM generate_series(1, 10000) s); +ANALYZE test_18689; +/*+ IndexScan(test_18689) */ SELECT count(*) FROM test_18689 WHERE k1 > 5 and k1 < 10; +/*+ IndexScan(test_18689) */ SELECT count(*) FROM test_18689 WHERE k1 > 5 and k1 < 10 and k2 > 5; +/*+ IndexScan(test_18689) */ SELECT count(*) FROM test_18689 WHERE k1 > 5 and k1 < 10 and k2 > 5 and k2 < 10; +/*+ IndexScan(test_18689) */ SELECT count(*) FROM test_18689 WHERE k1 > 5 and k1 < 10 and k2 > 5 and k2 < 10 and k3 > 5; +/*+ IndexScan(test_18689) */ SELECT count(*) FROM test_18689 WHERE k1 > 5 and k1 < 10 and k2 > 5 and k2 < 10 and k3 > 5 and k3 < 10; + +-- #20892 : Divide by 0 error in some queries with new cost model when yb_fetch_size_limit is enforced +CREATE TABLE test_20892 (k INT, v VARCHAR(1024)); +INSERT INTO test_20892 (SELECT s, repeat(md5(s::text), 32) FROM generate_series(1, 100) s); +ANALYZE test_20892; +set yb_fetch_row_limit = 1024; +set yb_fetch_size_limit = '1MB'; +explain (analyze, costs off, summary off, timing off) SELECT 0 FROM test_20892; +SELECT count(*) FROM test_20892; +\d test_20892 + +set yb_fetch_row_limit = 100; +set yb_fetch_size_limit = '10kB'; +explain (analyze, costs off, summary off, timing off) SELECT 0 FROM test_20892; +SELECT count(*) FROM test_20892; +\d test_20892 + +DROP TABLE test_20892; diff --git a/src/test/regress/sql/yb.orig.planner_heuristic_selectivity.sql b/src/test/regress/sql/yb.orig.planner_heuristic_selectivity.sql new file mode 100644 index 0000000000000..81e5b3396daa6 --- /dev/null +++ b/src/test/regress/sql/yb.orig.planner_heuristic_selectivity.sql @@ -0,0 +1,83 @@ +CREATE TABLE test (h1 INT, h2 INT, a1 INT, v1 INT, PRIMARY KEY ((h1, h2) HASH, a1 ASC)); +CREATE INDEX test_a1 ON test (a1 HASH); +INSERT INTO test (SELECT s, s, s, s FROM generate_series(1, 100000) s); + +-- ANALYZE has not been called and actual number of rows in the table are not known. +SELECT reltuples FROM pg_class WHERE relname = 'test'; + +-- Filter on full key of unique index, expect 1 row. +EXPLAIN SELECT * FROM test WHERE h1 = 1 AND h2 = 1 AND a1 = 1; + +-- Filter on full key of non-unique index, expect 10 rows. +EXPLAIN SELECT * FROM test WHERE a1 = 1; + +-- Filter on only the hash columns in unique index, expect 100 rows +EXPLAIN SELECT * FROM test WHERE h1 = 1 AND h2 = 1; + +-- Filter on only a partial key, expect sequential scan with 1000 rows +EXPLAIN SELECT * FROM test WHERE h1 = 1; + +-- Filter on row with no index, expect sequential scan with 1000 rows +EXPLAIN SELECT * FROM test WHERE v1 = 1; + +-- No flter, expect sequential scan with 1000 rows +EXPLAIN SELECT * FROM test; + +-- ANALYZE produces a rough estimate of the number of rows. This can make +-- the test flaky, so to simulate ANALYZE we write to pg_class.reltuples manually. +SET yb_non_ddl_txn_for_sys_tables_allowed = on; +UPDATE pg_class SET reltuples=100000 WHERE relname='test'; +UPDATE pg_yb_catalog_version SET current_version=current_version+1 WHERE db_oid=1; +-- Adding a sleep because changes to catalog need to be propogated asynchronously. +select pg_sleep(1); +SET yb_non_ddl_txn_for_sys_tables_allowed = off; +SELECT reltuples FROM pg_class where relname = 'test'; + +-- Filter on full key of unique index, expect 1 row. +EXPLAIN SELECT * FROM test WHERE h1 = 1 AND h2 = 1 AND a1 = 1; + +-- Filter on full key of non-unique index, expect 1% rows ie. 1000 rows +EXPLAIN SELECT * FROM test WHERE a1 = 1; + +-- Filter on only the hash columns in unique index, expect 10% rows ie. 10000 rows +EXPLAIN SELECT * FROM test WHERE h1 = 1 AND h2 = 1; + +-- Filter on only a partial key, expect sequential scan with 100% rows +EXPLAIN SELECT * FROM test WHERE h1 = 1; + +-- Filter on row with no index, expect sequential scan with 100% rows +EXPLAIN SELECT * FROM test WHERE v1 = 1; + +-- No flter, expect sequential scan with 100% rows +EXPLAIN SELECT * FROM test; + +DROP TABLE test cascade; + +-- #13526 : Hard coded heuristic selectivity leads to poor query plan when +-- applied to actual statistics in the cost model. +CREATE TABLE t1 (h1 INT, a1 INT, v1 INT, PRIMARY KEY (h1 HASH, a1 ASC)); +CREATE TABLE t2 (h1 INT, v1 INT, PRIMARY KEY (h1 HASH)); +INSERT INTO t1 SELECT s, s, s FROM generate_series(1, 300000) s; +INSERT INTO t2 SELECT s, s FROM generate_series(1, 300000) s; + +-- Without statistics, each table is assumed to have 1000 rows only. +-- Filter is propagated to t2.h1 = 123. h1 is the primary key in t2, and +-- the filter should produce 1 row. +EXPLAIN SELECT * FROM t1 LEFT JOIN t2 on t1.h1 = t2.h1 where t1.h1 = 123; + +-- With statistics, before this change the filter on t2 was estimated to +-- produce 300 rows because hard coded selectvity of 0.001 was applied to +-- 300000 rows. After the fix, the index scan on t2 should produce 1 row. +-- ANALYZE produces a rough estimate of the number of rows. This can make +-- the test flaky, so to simulate ANALYZE we write to pg_class.reltuples manually. +SET yb_non_ddl_txn_for_sys_tables_allowed = on; +UPDATE pg_class SET reltuples=300000 WHERE relname='t1'; +UPDATE pg_class SET reltuples=300000 WHERE relname='t2'; +UPDATE pg_yb_catalog_version SET current_version=current_version+1 WHERE db_oid=1; +-- Adding a sleep because changes to catalog need to be propogated asynchronously. +select pg_sleep(1); +SET yb_non_ddl_txn_for_sys_tables_allowed = off; + +EXPLAIN SELECT * FROM t1 LEFT JOIN t2 on t1.h1 = t2.h1 where t1.h1 = 123; + +DROP TABLE t1, t2 cascade; diff --git a/src/test/regress/sql/yb.orig.planner_indexes.sql b/src/test/regress/sql/yb.orig.planner_indexes.sql new file mode 100644 index 0000000000000..9ae351b367249 --- /dev/null +++ b/src/test/regress/sql/yb.orig.planner_indexes.sql @@ -0,0 +1,193 @@ +-------------------------------------- +-- Hash-partitioned tables/indexes. +-------------------------------------- + +CREATE TABLE t1(h int, r int, v1 int, v2 int, v3 int, primary key(h HASH, r ASC)); +CREATE INDEX t1_v1_v2_idx on t1(v1 HASH, v2 ASC); +CREATE UNIQUE INDEX t1_v3_uniq_idx on t1(v3 HASH); +INSERT INTO t1 VALUES (1,1,1,1,1), (1,2,1,2,5), (5,2,8,9,0), (3,4,2,2,2), (8,2,4,5,9); + +-------------------------------------- +-- Test unique vs non-unique indexes. + +-- Expect to use t1_v3_uniq_idx because will guarantee a single-row scan (due to uniqueness). +EXPLAIN SELECT * FROM t1 WHERE v1 = 1 and v2 = 1 and v3 = 1; + +-- Expect to use t1_v1_v2_idx because the inequality condition on v3 (HASH) is not useful. +EXPLAIN SELECT * FROM t1 WHERE v1 = 1 and v2 = 1 and v3 > 1; +EXPLAIN SELECT * FROM t1 WHERE v1 = 1 and v2 > 1 and v3 > 1; + +-------------------------------------- +-- Test covered vs uncovered indexes. + +-- Should prioritize the pkey index because it covers all columns (index only scan). +EXPLAIN SELECT * FROM t1 WHERE h = 1 and v1 = 1; +SELECT * FROM t1 WHERE h = 1 and v1 = 1; + +EXPLAIN SELECT * FROM t1 WHERE yb_hash_code(h) = yb_hash_code(1) and v1 = 1; +SELECT * FROM t1 WHERE yb_hash_code(h) = yb_hash_code(1) and v1 = 1; + +EXPLAIN SELECT * FROM t1 WHERE h = 1 and r = 2 and v1 = 1 and v2 = 2; +SELECT * FROM t1 WHERE h = 1 and r = 2 and v1 = 1 and v2 = 2; + +EXPLAIN SELECT * FROM t1 WHERE yb_hash_code(h) = yb_hash_code(1) and r = 2 and v1 = 1 and v2 = 2; +SELECT * FROM t1 WHERE yb_hash_code(h) = yb_hash_code(1) and r = 2 and v1 = 1 and v2 = 2; + +-- Should prioritize the t1_v1_v2_idx because it is fully specified. +EXPLAIN SELECT * FROM t1 WHERE h = 1 and v1 = 1 and v2 = 2; +SELECT * FROM t1 WHERE h = 1 and v1 = 1 and v2 = 2; + +EXPLAIN SELECT * FROM t1 WHERE yb_hash_code(h) = yb_hash_code(1) and v1 = 1 and v2 = 2; +SELECT * FROM t1 WHERE yb_hash_code(h) = yb_hash_code(1) and v1 = 1 and v2 = 2; + +-------------------------------------- +-- Test partial indexes. + +-- Should use t1_v1_v2_idx because conditions partly match. +EXPLAIN SELECT * FROM t1 WHERE v1 = 1 AND v3 > 1; +EXPLAIN SELECT * FROM t1 WHERE v1 = 1 AND v3 > 2; +EXPLAIN SELECT * FROM t1 WHERE v1 = 1 AND v3 > 3; + +CREATE INDEX t1_v1_v2_uniq_partial_idx ON t1(v1, v2) WHERE v3 > 2; + +-- Should still use t1_v1_v2_idx because partial index condition does not match. +EXPLAIN SELECT * FROM t1 WHERE v1 = 1 AND v3 > 1; + +-- Should both use t1_v1_v2_uniq_partial_idx. +EXPLAIN SELECT * FROM t1 WHERE v1 = 1 AND v3 > 2; +EXPLAIN SELECT * FROM t1 WHERE v1 = 1 AND v3 > 3; + +-------------------------------------- +-- Multiple hash columns. + +CREATE TABLE t4(h1 int, h2 int, v1 int, primary key((h1, h2) hash)); +INSERT INTO t4 (SELECT s, s, s FROM generate_series(1,1000) s); + +-- Should use index scan when equality conditions use all hash columns +EXPLAIN SELECT * from t4 where h1 = 1 and h2 = 2; +EXPLAIN SELECT * from t4 where h1 = yb_hash_code(1) and h2 = 2; +EXPLAIN SELECT * from t4 where h1 = yb_hash_code(1) and h2 = yb_hash_code(2); + +-- Should not use index scan when the filter does not have equality on all hash columns +EXPLAIN SELECT * from t4 where h1 = 1; +EXPLAIN SELECT * from t4 where h1 = 1 and h2 > 10; +EXPLAIN SELECT * from t4 where h1 > 1 and h2 > 10; +EXPLAIN SELECT * from t4 where h1 > 1 and h2 = 10; +EXPLAIN SELECT * from t4 where h1 = 1 or h2 = 2; + +-------------------------------------- +-- Range-partitioned tables/indexes. +-------------------------------------- + +CREATE TABLE t2(h int, r int, v1 int, v2 int, v3 int, primary key(h ASC, r ASC)); +CREATE INDEX t2_v1_v2_idx on t2(v1 ASC, v2 ASC); +CREATE UNIQUE INDEX t2_v3_uniq_idx on t2(v3 ASC); + +-- Expect to use t2_v3_uniq_idx because will guarantee a single-row scan (due to uniqueness). +EXPLAIN SELECT * FROM t2 WHERE v1 = 1 and v2 = 1 and v3 = 1; + +-- Expect to use t2_v1_v2_idx because condition partly matches. +EXPLAIN SELECT * FROM t2 WHERE v1 >= 1 and v2 > 1; + +-------------------------------------- +-- Test covered vs uncovered indexes. + +-- Should prioritize the pkey index because it covers all columns (index only scan). +EXPLAIN SELECT * FROM t2 WHERE h = 1 and v1 = 1; +EXPLAIN SELECT * FROM t2 WHERE h = 1 and r = 2 and v1 = 1 and v2 = 2; + +-- Should prioritize the t1_v1_v2_idx because it is fully specified. +EXPLAIN SELECT * FROM t2 WHERE h = 1 and v1 = 1 and v2 = 2; + +-------------------------------------- +-- Test partial indexes. + +DROP INDEX t2_v3_uniq_idx; + +-- Should use t1_v1_v2_idx because conditions partly match. +EXPLAIN SELECT * FROM t2 WHERE v1 = 1 AND v3 > 1; +EXPLAIN SELECT * FROM t2 WHERE v1 = 1 AND v3 > 2; +EXPLAIN SELECT * FROM t2 WHERE v1 = 1 AND v3 > 3; + +CREATE INDEX t2_v1_v2_uniq_partial_idx ON t2(v1, v2) WHERE v3 > 2; + +-- Should still use t1_v1_v2_idx because partial index condition does not match. +EXPLAIN SELECT * FROM t2 WHERE v1 = 1 AND v3 > 1; + +-- Should both use t1_v1_v2_uniq_partial_idx. +EXPLAIN SELECT * FROM t2 WHERE v1 = 1 AND v3 > 2; +EXPLAIN SELECT * FROM t2 WHERE v1 = 1 AND v3 > 3; + +-------------------------------------- +-- Backwards vs forward scans. +-------------------------------------- + +CREATE TABLE t3(k int primary key, v1 int, v2 int); +CREATE INDEX asc_idx on t3(v1 HASH, v2 ASC); +CREATE INDEX desc_idx on t3(v1 HASH, v2 DESC); + +-- Should use asc index. +EXPLAIN SELECT * FROM t3 WHERE v1 = 1 ORDER BY v2 ASC; +EXPLAIN SELECT * FROM t3 WHERE v1 = 1 AND v2 > 3 ORDER BY v2 ASC; +EXPLAIN SELECT * FROM t3 WHERE v1 = 1 AND v2 <= 3 ORDER BY v2 ASC; + +-- Should use desc index. +EXPLAIN SELECT * FROM t3 WHERE v1 = 1 ORDER BY v2 DESC; +EXPLAIN SELECT * FROM t3 WHERE v1 = 1 AND v2 > 3 ORDER BY v2 DESC; +EXPLAIN SELECT * FROM t3 WHERE v1 = 1 AND v2 <= 3 ORDER BY v2 DESC; + +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +DROP TABLE t4; + +-- Issue #21133 +CREATE TABLE bb ( + pk serial NOT NULL, + col_int_key integer, + CONSTRAINT bb_pkey PRIMARY KEY(pk ASC) +); + +CREATE TABLE c ( + pk serial NOT NULL, + col_int_key integer, + CONSTRAINT c_pkey PRIMARY KEY(pk ASC) +); + +CREATE TABLE cc ( + pk serial NOT NULL, + col_int_nokey integer, + col_int_key integer, + CONSTRAINT cc_pkey PRIMARY KEY(pk ASC) +); +CREATE INDEX bb_int_key ON bb (col_int_key ASC); +CREATE INDEX c_int_key ON c (col_int_key ASC); +CREATE INDEX cc_int_key ON cc (col_int_key ASC); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT +FROM + CC AS table1 + JOIN CC AS table2 ON ( + table2.col_int_key = table1.pk + ) +WHERE + table2.col_int_key < ( + SELECT + col_int_nokey + FROM + CC + WHERE + col_int_nokey IN ( + SELECT + CHILD_SUBQUERY1_t1.pk + FROM + BB AS CHILD_SUBQUERY1_t1 + JOIN C AS CHILD_SUBQUERY1_t2 ON ( + CHILD_SUBQUERY1_t2.pk = CHILD_SUBQUERY1_t1.col_int_key + ) + WHERE + CHILD_SUBQUERY1_t2.col_int_key <= table1.pk + ) + ); +DROP TABLE bb; +DROP TABLE c; +DROP TABLE cc; diff --git a/src/test/regress/sql/yb.orig.planner_join_order.sql b/src/test/regress/sql/yb.orig.planner_join_order.sql new file mode 100644 index 0000000000000..1bd2b62307611 --- /dev/null +++ b/src/test/regress/sql/yb.orig.planner_join_order.sql @@ -0,0 +1,71 @@ +CREATE TABLE t_10000 (id int PRIMARY KEY, val int); +CREATE TABLE t_1000 (id int PRIMARY KEY, val int); +CREATE TABLE t_100 (id int PRIMARY KEY, val int); +CREATE TABLE t_1000000 (id int PRIMARY KEY, val int); + +INSERT INTO t_10000 SELECT i, i FROM (SELECT generate_series(1, 10000) i) t; +INSERT INTO t_1000 SELECT i, i FROM (SELECT generate_series(1, 1000) i) t; +INSERT INTO t_100 SELECT i, i FROM (SELECT generate_series(1, 100) i) t; +INSERT INTO t_1000000 SELECT i, i FROM (SELECT generate_series(1, 1000000) i) t; + +CREATE OR REPLACE PROCEDURE update_sizes(t_10000_size int, t_1000_size int, t_100_size int, t_1000000_size int) +LANGUAGE plpgsql +AS $$ +BEGIN + SET yb_non_ddl_txn_for_sys_tables_allowed=1; + BEGIN + UPDATE pg_class SET reltuples=t_10000_size WHERE relname='t_10000'; + UPDATE pg_class SET reltuples=t_1000_size WHERE relname='t_1000'; + UPDATE pg_class SET reltuples=t_100_size WHERE relname='t_100'; + UPDATE pg_class SET reltuples=t_1000000_size WHERE relname='t_1000000'; + UPDATE pg_yb_catalog_version SET current_version=current_version+1 WHERE db_oid=1; + EXCEPTION + -- Workaround for [#10405], exception clause makes buffers flushed + WHEN OTHERS THEN + SET yb_non_ddl_txn_for_sys_tables_allowed=0; + END; + SET yb_non_ddl_txn_for_sys_tables_allowed=0; +END; +$$; +-- Updating table sizes enables the query planner to choose appropriate join orders. In other words, +-- the larger table is chosen as the inner table if the larger table fits in memory. +-- In this example, t_1000 is always being chosen as the the inner table because it's larger. +call update_sizes(10000, 1000, 100, 1000000); +-- Executing a sleep for a small amount of time for the size changes to be reflected. +select pg_sleep(1); +EXPLAIN (COSTS false) SELECT * FROM t_1000, t_100 WHERE t_1000.id = t_100.id; +EXPLAIN (COSTS false) SELECT * FROM t_100, t_1000 WHERE t_100.id = t_1000.id; +EXPLAIN (COSTS false) SELECT * FROM t_10000, t_1000000 WHERE t_10000.id = t_1000000.id; +EXPLAIN (COSTS false) SELECT * FROM t_1000000, t_10000 WHERE t_1000000.id = t_10000.id; +-- Here we, reset the table size to -1. In this situation, query planner is unaware of table sizes +-- Hence, it will choose the inner table based on the order in which it appears in the query +-- irrespective of the actual size. +call update_sizes(-1, -1, -1, -1); +select pg_sleep(1); +EXPLAIN (COSTS false) SELECT * FROM t_1000, t_100 WHERE t_1000.id = t_100.id; +EXPLAIN (COSTS false) SELECT * FROM t_100, t_1000 WHERE t_100.id = t_1000.id; +EXPLAIN (COSTS false) SELECT * FROM t_10000, t_1000000 WHERE t_10000.id = t_1000000.id; +EXPLAIN (COSTS false) SELECT * FROM t_1000000, t_10000 WHERE t_1000000.id = t_10000.id; +-- Updating table sizes arranges join orders based on table sizes. Updating costs enables the query +-- planner to prioritize NestedLoop vs HashJoins during different occasions based on the size of +-- the tables. In the second example, due to varying sizes of the table, the join order changes. +-- The query planner tries to optimize the join by prioritizing joining smaller tables with larger +-- tables. In this example, it joins t_1000 and t_100 and then subsequently joins the result with +-- t_10000. However, when table sizes are unknown, it joins t_1000 and t_10000 and its resultant +-- with t_100. +call update_sizes(10000, 1000, 100, 1000000); +select pg_sleep(1); +EXPLAIN (COSTS false) SELECT * FROM t_1000000 t4 INNER JOIN t_100 t1 ON t4.id = t1.id + INNER JOIN t_1000 t2 ON t1.id = t2.id + INNER JOIN t_10000 t3 ON t2.id = t3.id; +EXPLAIN (COSTS false) SELECT * FROM t_10000 t3 INNER JOIN t_1000 t2 ON t3.id = t2.id + INNER JOIN t_100 t1 ON t2.val = t1.val + WHERE t3.id < 10; +call update_sizes(-1, -1, -1, -1); +select pg_sleep(1); +EXPLAIN (COSTS false) SELECT * FROM t_1000000 t4 INNER JOIN t_100 t1 ON t4.id = t1.id + INNER JOIN t_1000 t2 ON t1.id = t2.id + INNER JOIN t_10000 t3 ON t2.id = t3.id; +EXPLAIN (COSTS false) SELECT * FROM t_10000 t3 INNER JOIN t_1000 t2 ON t3.id = t2.id + INNER JOIN t_100 t1 ON t2.val = t1.val + WHERE t3.id < 10; diff --git a/src/test/regress/sql/yb.orig.planner_joins.sql b/src/test/regress/sql/yb.orig.planner_joins.sql new file mode 100644 index 0000000000000..e3b513bfd1d53 --- /dev/null +++ b/src/test/regress/sql/yb.orig.planner_joins.sql @@ -0,0 +1,261 @@ +-------------------------------------- +-- Set up tables. +-------------------------------------- +SET enable_bitmapscan = false; -- TODO(#20573): update bitmap scan cost model + +CREATE TABLE t1(h int, r int, v1 int, v2 int, v3 int, primary key(h HASH, r ASC)); +CREATE INDEX t1_v1_v2_idx on t1(v1 HASH, v2 ASC); +CREATE UNIQUE INDEX t1_v3_uniq_idx on t1(v3 HASH); +INSERT INTO t1 VALUES (1,2,4,9,2), (2,3,2,4,6); + +CREATE TABLE t2(h int, r int, v1 int, v2 int, v3 int, primary key(h HASH, r ASC)); +CREATE INDEX t2_v1_v2_idx on t2(v1 HASH, v2 ASC); +CREATE UNIQUE INDEX t2_v3_uniq_idx on t2(v3 HASH); +INSERT INTO t2 VALUES (5,5,4,9,2), (1,2,4,3,4), (2,3,4,5,6), (2,4,4,2,3); + +CREATE TABLE t3(h int, r int, v1 int, v2 int, v3 int, primary key(h ASC, r ASC)); +CREATE INDEX t3_v1_v2_idx on t3(v1 ASC, v2 ASC); +CREATE UNIQUE INDEX t3_v3_uniq_idx on t3(v3 ASC); +INSERT INTO t3 VALUES (1,2,4,5,7), (1,3,8,6,1), (4,3,7,3,2); + +CREATE TABLE t4(h int, r int, v1 int, v2 int, primary key(h ASC, r ASC)); + +-- Should make use of eq transitivity and use pkey on both tables. +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h and t1.r = t2.r +WHERE t1.h = 2 and t2.r = 3; +SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h and t1.r = t2.r +WHERE t1.h = 2 and t2.r = 3; + +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h and t1.r = t2.r +WHERE yb_hash_code(t1.h) = yb_hash_code(2) and t2.r = 3; +SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h and t1.r = t2.r +WHERE yb_hash_code(t1.h) = yb_hash_code(2) and t2.r = 3; + +-- Should make use of eq transitivity and use full pkey on t2 and partial pkey on t1. +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h +WHERE t1.h = 2 and t2.r = 3; +SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h +WHERE t1.h = 2 and t2.r = 3; + +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h +WHERE yb_hash_code(t1.h) = yb_hash_code(2) and t2.r = 3; +SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h +WHERE yb_hash_code(t1.h) = yb_hash_code(2) and t2.r = 3; + +-- Should use pkey index on t1 (a) and t2_v1_v2_idx (due to join condition). +EXPLAIN (COSTS OFF) SELECT * +FROM t1 as a + JOIN t1 as b on a.h = b.v1 +WHERE a.h = 2 and a.r = 3; +SELECT * +FROM t1 as a + JOIN t1 as b on a.h = b.v1 +WHERE a.h = 2 and a.r = 3; + +EXPLAIN (COSTS OFF) SELECT * +FROM t1 as a + JOIN t1 as b on yb_hash_code(a.h) = yb_hash_code(b.v1) +WHERE yb_hash_code(a.h) = yb_hash_code(2) and a.r = 3; +SELECT * +FROM t1 as a + JOIN t1 as b on yb_hash_code(a.h) = yb_hash_code(b.v1) +WHERE yb_hash_code(a.h) = yb_hash_code(2) and a.r = 3; + +-- Should make use of eq transitivity and use pkey on all 3 tables (then sort 1 row in memory). +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h and t1.r = t2.r + JOIN t3 on t2.h = t3.h and t1.r = t3.r +WHERE t1.h = 1 and t3.r = 2 +ORDER BY t3.v3 DESC; +SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h and t1.r = t2.r + JOIN t3 on t2.h = t3.h and t1.r = t3.r +WHERE t1.h = 1 and t3.r = 2 +ORDER BY t3.v3 DESC; + +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h and t1.r = t2.r + JOIN t3 on t2.h = t3.h and t1.r = t3.r +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t3.r = 2 +ORDER BY t3.v3 DESC; + +SELECT * +FROM t1 + JOIN t2 on t1.h = t2.h and t1.r = t2.r + JOIN t3 on t2.h = t3.h and t1.r = t3.r +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t3.r = 2 +ORDER BY t3.v3 DESC; + +SELECT * +FROM t1 + JOIN t2 on yb_hash_code(t1.h) = yb_hash_code(t2.h) and t1.r = t2.r + JOIN t3 on t2.h = t3.h and t1.r = t3.r +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t3.r = 2 +ORDER BY t3.v3 DESC; + +-- Should use v3_uniq_idx on t3 and t2 and v1_v2_idx (on partial key) for t1. +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + JOIN t2 on t1.v1 = t2.v1 + JOIN t3 on t2.v3 = t3.v3 +WHERE t1.h = 1 and t3.v3 = 2 +ORDER BY t3.v3 ASC; +SELECT * +FROM t1 + JOIN t2 on t1.v1 = t2.v1 + JOIN t3 on t2.v3 = t3.v3 +WHERE t1.h = 1 and t3.v3 = 2 +ORDER BY t3.v3 ASC; + +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + JOIN t2 on t1.v1 = t2.v1 + JOIN t3 on t2.v3 = t3.v3 +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t3.v3 = 2 +ORDER BY t3.v3 ASC; +SELECT * +FROM t1 + JOIN t2 on t1.v1 = t2.v1 + JOIN t3 on t2.v3 = t3.v3 +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t3.v3 = 2 +ORDER BY t3.v3 ASC; + +-- Should still use same indexes as above, only t3.v1 > 5 condition for filtering. +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + JOIN t2 on t1.v1 = t2.v1 + JOIN t3 on t2.v3 = t3.v3 +WHERE t1.h = 1 and t3.v3 = 2 and t3.v1 > 5 +ORDER BY t3.v3 ASC; +SELECT * +FROM t1 + JOIN t2 on t1.v1 = t2.v1 + JOIN t3 on t2.v3 = t3.v3 +WHERE t1.h = 1 and t3.v3 = 2 and t3.v1 > 5 +ORDER BY t3.v3 ASC; + +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + JOIN t2 on t1.v1 = t2.v1 + JOIN t3 on t2.v3 = t3.v3 +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t3.v3 = 2 and t3.v1 > 5 +ORDER BY t3.v3 ASC; +SELECT * +FROM t1 + JOIN t2 on t1.v1 = t2.v1 + JOIN t3 on t2.v3 = t3.v3 +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t3.v3 = 2 and t3.v1 > 5 +ORDER BY t3.v3 ASC; + +-- Should use pkey on t1, then v3_uniq_idx on t2 and v1_v2_idx on t3. +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE t1.h = 1 and t1.r = 2 +ORDER BY t3.v3 DESC; +SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE t1.h = 1 and t1.r = 2 +ORDER BY t3.v3 DESC; + +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t1.r = 2 +ORDER BY t3.v3 DESC; +SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t1.r = 2 +ORDER BY t3.v3 DESC; + +-- Should still use same indexes as above, only use t2.r IN condition for filtering. +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE t1.h = 1 and t1.r = 2 and t2.r IN (3,4,5) +ORDER BY t3.v3 DESC; +SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE t1.h = 1 and t1.r = 2 and t2.r IN (3,4,5) +ORDER BY t3.v3 DESC; + +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t1.r = 2 and t2.r IN (3,4,5) +ORDER BY t3.v3 DESC; +SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t1.r = 2 and t2.r IN (3,4,5) +ORDER BY t3.v3 DESC; + +-- Should still use same indexes as above, but use the IN condition on v2 for t3.v1_v2_idx. +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE t1.h = 1 and t1.r = 2 and t3.v2 IN (3,4,5) +ORDER BY t3.v3 DESC; +SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE t1.h = 1 and t1.r = 2 and t3.v2 IN (3,4,5) +ORDER BY t3.v3 DESC; + +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t1.r = 2 and t3.v2 IN (3,4,5) +ORDER BY t3.v3 DESC; +SELECT * +FROM t1 + FULL JOIN t2 on t1.v3 = t2.v3 + FULL JOIN t3 on t1.v1 = t3.v1 and t3.v1 = t1.v1 +WHERE yb_hash_code(t1.h) = yb_hash_code(1) and t1.r = 2 and t3.v2 IN (3,4,5) +ORDER BY t3.v3 DESC; + +-- Should use merge join for FULL, primary key on t3 and sort t1 +EXPLAIN (COSTS OFF) SELECT * +FROM t1 + FULL JOIN t3 on t1.h = t3.h; + +-- Should use merge join for FULL, primary key on both t3 and t4 and materialize the inner +EXPLAIN (COSTS OFF) SELECT * +FROM t4 + FULL JOIN t3 on t4.h = t3.h; + +-- Clean up +DROP TABLE t1, t2, t3, t4 CASCADE; diff --git a/src/test/regress/sql/yb.orig.planner_size_estimates.sql b/src/test/regress/sql/yb.orig.planner_size_estimates.sql new file mode 100644 index 0000000000000..39b03a54bb6d3 --- /dev/null +++ b/src/test/regress/sql/yb.orig.planner_size_estimates.sql @@ -0,0 +1,26 @@ +CREATE TABLE s_10000 (id int, r int, val1 int, val2 int, val3 int, val4 int, PRIMARY KEY (id, r)); +CREATE TABLE s_1000 (id int PRIMARY KEY, val int); +CREATE TABLE s_100 (id int PRIMARY KEY, val int); + +INSERT INTO s_10000 SELECT i, i, i, i % 1000, i % 100, i % 10 FROM (SELECT generate_series(1, 10000) i) t; +INSERT INTO s_1000 SELECT i, i FROM (SELECT generate_series(1, 1000) i) t; +INSERT INTO s_100 SELECT i, i FROM (SELECT generate_series(1, 100) i) t; + +SET yb_enable_optimizer_statistics = FALSE; + +ANALYZE; +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/planner_size_estimates_cmds.sql' +\i :filename + +-- When we have table statistics enabled, we use Postgres's selectivity functions to estimate how +-- many rows a relation will return, taking into account the supplied constraints. +SET yb_enable_optimizer_statistics = TRUE; + +\i :filename + +EXPLAIN SELECT * FROM s_10000 WHERE val1 < -100000; +EXPLAIN SELECT * FROM s_10000 WHERE id < -100000; +SET enable_indexscan = FALSE; +EXPLAIN SELECT * FROM s_10000 WHERE val1 < -100000; +EXPLAIN SELECT * FROM s_10000 WHERE id < -100000; diff --git a/src/test/regress/sql/yb.orig.planner_taqo_seek_next_estimation.sql b/src/test/regress/sql/yb.orig.planner_taqo_seek_next_estimation.sql new file mode 100644 index 0000000000000..d464f206f7ae6 --- /dev/null +++ b/src/test/regress/sql/yb.orig.planner_taqo_seek_next_estimation.sql @@ -0,0 +1,249 @@ +CREATE DATABASE taqo_seek_next_estimation with colocation = true; +\c taqo_seek_next_estimation +-- A grace period of 2s is added to every RPC (controlled by gflag 'pg_client_extra_timeout_ms'). +-- To avoid crossing the max RPC timeout threshold (7200s), account for the above in setting +-- the statement timeout. +SET statement_timeout = '7198s'; +-- CREATE QUERIES +create table t1 (k1 int, PRIMARY KEY (k1 asc)); +create table t2 (k1 int, k2 int, PRIMARY KEY (k1 asc, k2 asc)); +create table t3 (k1 int, k2 int, k3 int, PRIMARY KEY (k1 asc, k2 asc, k3 asc)); +create table t4 (k1 int, k2 int, k3 int, k4 int, PRIMARY KEY (k1 asc, k2 asc, k3 asc, k4 asc)); +create table t5 (k1 int, k2 int, k3 int, k4 int, k5 int, PRIMARY KEY (k1 asc, k2 asc, k3 asc, k4 asc, k5 asc)); +create table tcor_1 (k1 int, k2 int, k3 int, k4 int); +create index tcor_1_idx on tcor_1 (k1 asc, k2 asc, k3 asc, k4 asc); +create statistics stx_tcor_1 on k1, k2, k3, k4 FROM tcor_1; +create table tcor_2 (k1 int, k2 int, k3 int, k4 int); +create index tcor_2_idx on tcor_2 (k1 asc, k2 asc, k3 asc, k4 asc); +create statistics stx_tcor_2 on k1, k2, k3, k4 FROM tcor_2; +CREATE TABLE tcor_3 (k1 INT, k2 INT, k3 INT); +CREATE INDEX tcor_3_idx on tcor_3 (k1 ASC, k2 ASC, k3 ASC); +CREATE STATISTICS stx_tcor_3 on k1, k2, k3 FROM tcor_3; + +SET yb_non_ddl_txn_for_sys_tables_allowed = ON; + +UPDATE pg_class SET reltuples = 20, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't1' OR relname = 't1_pkey'); +UPDATE pg_class SET reltuples = 20, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't1_pkey' OR relname = 't1_pkey_pkey'); +UPDATE pg_class SET reltuples = 400, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't2' OR relname = 't2_pkey'); +UPDATE pg_class SET reltuples = 400, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't2_pkey' OR relname = 't2_pkey_pkey'); +UPDATE pg_class SET reltuples = 8000, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't3' OR relname = 't3_pkey'); +UPDATE pg_class SET reltuples = 8000, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't3_pkey' OR relname = 't3_pkey_pkey'); +UPDATE pg_class SET reltuples = 160000, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't4' OR relname = 't4_pkey'); +UPDATE pg_class SET reltuples = 160000, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't4_pkey' OR relname = 't4_pkey_pkey'); +UPDATE pg_class SET reltuples = 3200000.0, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't5' OR relname = 't5_pkey'); +UPDATE pg_class SET reltuples = 3200000.0, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't5_pkey' OR relname = 't5_pkey_pkey'); +UPDATE pg_class SET reltuples = 12500, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tcor_1' OR relname = 'tcor_1_pkey'); +UPDATE pg_class SET reltuples = 12500, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tcor_1_idx' OR relname = 'tcor_1_idx_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tcor_2' OR relname = 'tcor_2_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tcor_2_idx' OR relname = 'tcor_2_idx_pkey'); +UPDATE pg_class SET reltuples = 171700, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tcor_3' OR relname = 'tcor_3_pkey'); +UPDATE pg_class SET reltuples = 171700, relpages = 0, relallvisible = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tcor_3_idx' OR relname = 'tcor_3_idx_pkey'); + +DELETE FROM pg_statistic WHERE starelid = 'public.t1'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t1'::regclass and a.attname = 'k1'); +INSERT INTO pg_statistic VALUES ('public.t1'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t1'::regclass and a.attname = 'k1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{1}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t2'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t2'::regclass and a.attname = 'k1'); +INSERT INTO pg_statistic VALUES ('public.t2'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t2'::regclass and a.attname = 'k1'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05}'::real[], '{1}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t2'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t2'::regclass and a.attname = 'k2'); +INSERT INTO pg_statistic VALUES ('public.t2'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t2'::regclass and a.attname = 'k2'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05}'::real[], '{0.09975062}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t3'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t3'::regclass and a.attname = 'k1'); +INSERT INTO pg_statistic VALUES ('public.t3'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t3'::regclass and a.attname = 'k1'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05}'::real[], '{1}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t3'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t3'::regclass and a.attname = 'k2'); +INSERT INTO pg_statistic VALUES ('public.t3'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t3'::regclass and a.attname = 'k2'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05}'::real[], '{0.09975623}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t3'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t3'::regclass and a.attname = 'k3'); +INSERT INTO pg_statistic VALUES ('public.t3'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t3'::regclass and a.attname = 'k3'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05}'::real[], '{0.052493438}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t4'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t4'::regclass and a.attname = 'k1'); +INSERT INTO pg_statistic VALUES ('public.t4'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t4'::regclass and a.attname = 'k1'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.051733334, 0.051533334, 0.051333334, 0.051266667, 0.050933335, 0.0508, 0.050533332, 0.050533332, 0.050466668, 0.050433334, 0.0502, 0.0502, 0.049933333, 0.0498, 0.049266666, 0.0488, 0.048733335, 0.048333332, 0.048033334, 0.047133334}'::real[], '{1}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{8, 13, 19, 3, 1, 10, 5, 11, 16, 18, 6, 20, 4, 9, 14, 7, 17, 12, 15, 2}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t4'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t4'::regclass and a.attname = 'k2'); +INSERT INTO pg_statistic VALUES ('public.t4'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t4'::regclass and a.attname = 'k2'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.051933333, 0.0519, 0.0516, 0.051533334, 0.050766665, 0.050766665, 0.050333332, 0.0503, 0.0501, 0.0501, 0.049966667, 0.0499, 0.049733333, 0.049233332, 0.049133334, 0.049133334, 0.0488, 0.048566666, 0.048466668, 0.047733333}'::real[], '{0.10424456}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{16, 14, 6, 15, 2, 17, 4, 19, 1, 5, 20, 13, 12, 18, 3, 9, 7, 8, 10, 11}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t4'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t4'::regclass and a.attname = 'k3'); +INSERT INTO pg_statistic VALUES ('public.t4'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t4'::regclass and a.attname = 'k3'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.0528, 0.051633332, 0.0516, 0.051033333, 0.051, 0.0508, 0.050366666, 0.0503, 0.0501, 0.050066665, 0.049866665, 0.0497, 0.04963333, 0.0496, 0.0491, 0.0489, 0.0488, 0.048733335, 0.048133332, 0.047833335}'::real[], '{0.05457672}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{13, 6, 4, 15, 1, 16, 17, 20, 11, 9, 19, 18, 14, 8, 7, 5, 10, 3, 2, 12}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t4'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t4'::regclass and a.attname = 'k4'); +INSERT INTO pg_statistic VALUES ('public.t4'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t4'::regclass and a.attname = 'k4'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.052633334, 0.051633332, 0.051066667, 0.050766665, 0.050633334, 0.050633334, 0.050533332, 0.050466668, 0.050466668, 0.0503, 0.050166667, 0.0499, 0.0498, 0.049733333, 0.0496, 0.0493, 0.0489, 0.0488, 0.047733333, 0.046933334}'::real[], '{0.047732644}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 8, 13, 2, 4, 5, 20, 3, 14, 16, 18, 15, 7, 17, 6, 10, 9, 19, 12, 11}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t5'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t5'::regclass and a.attname = 'k1'); +INSERT INTO pg_statistic VALUES ('public.t5'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t5'::regclass and a.attname = 'k1'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.052233335, 0.0516, 0.051433332, 0.0514, 0.0508, 0.0508, 0.05073333, 0.050633334, 0.050633334, 0.050633334, 0.050333332, 0.050166667, 0.049866665, 0.049833335, 0.049766667, 0.048566666, 0.0485, 0.047733333, 0.047666665, 0.046666667}'::real[], '{1}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 4, 2, 5, 13, 15, 19, 7, 14, 16, 3, 11, 9, 12, 17, 1, 18, 20, 8, 6}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t5'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t5'::regclass and a.attname = 'k2'); +INSERT INTO pg_statistic VALUES ('public.t5'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t5'::regclass and a.attname = 'k2'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.051966667, 0.051933333, 0.0518, 0.050966665, 0.050933335, 0.0507, 0.0507, 0.050366666, 0.050133333, 0.049866665, 0.0498, 0.049533334, 0.0495, 0.049433332, 0.049266666, 0.049233332, 0.0492, 0.049133334, 0.047966667, 0.047566667}'::real[], '{0.11118402}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{16, 18, 13, 5, 14, 6, 10, 19, 8, 7, 17, 2, 9, 20, 12, 4, 1, 11, 15, 3}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t5'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t5'::regclass and a.attname = 'k3'); +INSERT INTO pg_statistic VALUES ('public.t5'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t5'::regclass and a.attname = 'k3'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.0528, 0.0521, 0.052, 0.0515, 0.050866667, 0.050633334, 0.0503, 0.050233334, 0.050166667, 0.0501, 0.049966667, 0.0495, 0.0494, 0.0493, 0.0492, 0.048933335, 0.048766665, 0.0484, 0.048333332, 0.0475}'::real[], '{0.04169254}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 20, 8, 10, 19, 17, 13, 9, 7, 12, 1, 2, 14, 11, 16, 18, 3, 5, 15, 4}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t5'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t5'::regclass and a.attname = 'k4'); +INSERT INTO pg_statistic VALUES ('public.t5'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t5'::regclass and a.attname = 'k4'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.052366666, 0.051733334, 0.051666666, 0.051133335, 0.0507, 0.0503, 0.05026667, 0.050133333, 0.050033335, 0.05, 0.05, 0.049666665, 0.049566668, 0.049533334, 0.049466666, 0.0493, 0.048633333, 0.048566666, 0.0485, 0.048433334}'::real[], '{0.05410762}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{20, 2, 19, 7, 11, 14, 13, 17, 5, 1, 16, 15, 8, 18, 12, 3, 10, 6, 4, 9}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t5'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t5'::regclass and a.attname = 'k5'); +INSERT INTO pg_statistic VALUES ('public.t5'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t5'::regclass and a.attname = 'k5'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.051466666, 0.051233333, 0.051166665, 0.050833333, 0.050766665, 0.05073333, 0.05073333, 0.050633334, 0.050566666, 0.050166667, 0.0501, 0.050033335, 0.04963333, 0.049566668, 0.0494, 0.0493, 0.048933335, 0.0489, 0.0489, 0.046933334}'::real[], '{0.040591933}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{15, 6, 9, 2, 19, 4, 13, 16, 1, 12, 14, 3, 11, 10, 20, 5, 8, 7, 17, 18}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.tcor_1'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_1'::regclass and a.attname = 'k1'); +INSERT INTO pg_statistic VALUES ('public.tcor_1'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_1'::regclass and a.attname = 'k1'), False::boolean, 0::real, 4::integer, 11::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.09992}'::real[], '{0.08880379}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.tcor_1'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_1'::regclass and a.attname = 'k2'); +INSERT INTO pg_statistic VALUES ('public.tcor_1'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_1'::regclass and a.attname = 'k2'), False::boolean, 0::real, 4::integer, 51::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.01992}'::real[], '{0.008795089}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.tcor_1'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_1'::regclass and a.attname = 'k3'); +INSERT INTO pg_statistic VALUES ('public.tcor_1'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_1'::regclass and a.attname = 'k3'), False::boolean, 0::real, 4::integer, 251::real, 1::smallint, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004, 0.004}'::real[], NULL::real[], '{-0.0074793934}'::real[], NULL::real[], NULL::real[], array_in('{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100}', 'pg_catalog.int4'::regtype, -1)::anyarray, array_in('{0, 101, 103, 104, 106, 107, 109, 110, 112, 113, 115, 116, 118, 119, 121, 122, 124, 125, 127, 128, 130, 131, 133, 134, 136, 137, 139, 140, 142, 143, 145, 146, 148, 149, 151, 152, 154, 155, 157, 158, 160, 161, 163, 164, 166, 167, 169, 170, 172, 173, 175, 176, 178, 179, 181, 182, 184, 185, 187, 188, 190, 191, 193, 194, 196, 197, 199, 200, 202, 203, 205, 206, 208, 209, 211, 212, 214, 215, 217, 218, 220, 221, 223, 224, 226, 227, 229, 230, 232, 233, 235, 236, 238, 239, 241, 242, 244, 245, 247, 248, 250}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.tcor_1'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_1'::regclass and a.attname = 'k4'); +INSERT INTO pg_statistic VALUES ('public.tcor_1'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_1'::regclass and a.attname = 'k4'), False::boolean, 0::real, 4::integer, 50::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02}'::real[], '{0.021729141}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.tcor_2'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_2'::regclass and a.attname = 'k1'); +INSERT INTO pg_statistic VALUES ('public.tcor_2'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_2'::regclass and a.attname = 'k1'), False::boolean, 0::real, 4::integer, 18::real, 1::smallint, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.18936667, 0.18833333, 0.15046667, 0.1499, 0.09246667, 0.09243333, 0.045433335, 0.0443, 0.017966667, 0.017866667, 0.0046, 0.004166667, 0.0012, 0.0009666667}'::real[], NULL::real[], '{0.14629024}'::real[], NULL::real[], NULL::real[], array_in('{0, -1, 1, -2, 2, -3, -4, 3, -5, 4, -6, 5, -7, 6}', 'pg_catalog.int4'::regtype, -1)::anyarray, array_in('{-9, -8, 7, 8}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.tcor_2'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_2'::regclass and a.attname = 'k2'); +INSERT INTO pg_statistic VALUES ('public.tcor_2'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_2'::regclass and a.attname = 'k2'), False::boolean, 0::real, 4::integer, 17::real, 1::smallint, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.1924, 0.19173333, 0.15066667, 0.14873333, 0.09173334, 0.0902, 0.045633335, 0.044233333, 0.0161, 0.015466667, 0.0054, 0.0051, 0.0012666667, 0.001}'::real[], NULL::real[], '{0.14077911}'::real[], NULL::real[], NULL::real[], array_in('{-1, 0, 1, -2, 2, -3, -4, 3, -5, 4, 5, -6, -7, 6}', 'pg_catalog.int4'::regtype, -1)::anyarray, array_in('{-8, 7, 8}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.tcor_2'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_2'::regclass and a.attname = 'k3'); +INSERT INTO pg_statistic VALUES ('public.tcor_2'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_2'::regclass and a.attname = 'k3'), False::boolean, 0::real, 4::integer, 18::real, 1::smallint, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.19436666, 0.1893, 0.14933333, 0.14896667, 0.0927, 0.091133334, 0.045466665, 0.0434, 0.016366666, 0.0161, 0.0050333333, 0.005, 0.0012666667, 0.0010333334}'::real[], NULL::real[], '{0.14582634}'::real[], NULL::real[], NULL::real[], array_in('{0, -1, 1, -2, -3, 2, -4, 3, -5, 4, -6, 5, 6, -7}', 'pg_catalog.int4'::regtype, -1)::anyarray, array_in('{-9, -8, 7, 8}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.tcor_2'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_2'::regclass and a.attname = 'k4'); +INSERT INTO pg_statistic VALUES ('public.tcor_2'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_2'::regclass and a.attname = 'k4'), False::boolean, 0::real, 4::integer, 17::real, 1::smallint, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.1918, 0.18863334, 0.15426667, 0.14693333, 0.0936, 0.0908, 0.044466667, 0.044333335, 0.0168, 0.015133333, 0.0058333334, 0.005133333, 0.001, 0.00093333336}'::real[], NULL::real[], '{0.12975658}'::real[], NULL::real[], NULL::real[], array_in('{-1, 0, -2, 1, 2, -3, 3, -4, -5, 4, 5, -6, 6, -7}', 'pg_catalog.int4'::regtype, -1)::anyarray, array_in('{-9, 7, 7}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.tcor_3'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_3'::regclass and a.attname = 'k1'); +INSERT INTO pg_statistic VALUES ('public.tcor_3'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_3'::regclass and a.attname = 'k1'), False::boolean, 0::real, 4::integer, 98::real, 1::smallint, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.0305, 0.029366666, 0.028533334, 0.027166666, 0.027033333, 0.026466666, 0.0262, 0.025466667, 0.025066666, 0.025, 0.023066666, 0.022766666, 0.0224, 0.0223, 0.022066666, 0.021333333, 0.0212, 0.020433333, 0.0201, 0.019233333, 0.0187, 0.018566666, 0.0178, 0.0174, 0.016666668, 0.016633334, 0.016366666, 0.015733333, 0.015533334, 0.014733333, 0.014433334, 0.013933334, 0.0139, 0.0134333335, 0.012633333, 0.012233334, 0.011666667, 0.011566667, 0.0112, 0.011033333, 0.010866666, 0.010566667, 0.0105, 0.009433334, 0.0091, 0.008933334, 0.0083, 0.0082, 0.008033333, 0.007866667, 0.0077333334, 0.0075333333, 0.007333333, 0.0063333334, 0.0058, 0.005633333, 0.0055333334, 0.0054, 0.005266667, 0.004766667, 0.0047, 0.0046666665, 0.0045333332, 0.004466667, 0.0038333333, 0.0035666667, 0.0034333332, 0.0034, 0.0029666666, 0.0029333334, 0.0028666668, 0.0022666666, 0.0021333334, 0.0020666667, 0.0019666667, 0.0018333333, 0.0017333333, 0.0017, 0.0015333333, 0.0015, 0.0012, 0.0010666667, 0.001, 0.0008, 0.00076666666, 0.00066666666, 0.00053333334, 0.00053333334}'::real[], NULL::real[], '{0.02486376}'::real[], NULL::real[], NULL::real[], array_in('{99, 100, 96, 97, 98, 93, 95, 94, 92, 91, 88, 89, 90, 87, 86, 84, 83, 85, 82, 81, 80, 79, 78, 77, 73, 76, 75, 70, 72, 74, 71, 69, 68, 67, 65, 66, 63, 61, 59, 62, 64, 58, 60, 56, 57, 55, 52, 50, 54, 49, 53, 51, 48, 47, 45, 46, 42, 43, 44, 41, 38, 40, 37, 39, 36, 33, 34, 32, 35, 30, 31, 27, 29, 26, 24, 25, 23, 28, 22, 21, 20, 18, 19, 16, 17, 14, 11, 15}', 'pg_catalog.int4'::regtype, -1)::anyarray, array_in('{1, 6, 7, 9, 9, 10, 12, 12, 13, 13}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.tcor_3'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_3'::regclass and a.attname = 'k2'); +INSERT INTO pg_statistic VALUES ('public.tcor_3'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_3'::regclass and a.attname = 'k2'), False::boolean, 0::real, 4::integer, 100::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.0158, 0.015433333, 0.015233333, 0.015233333, 0.015, 0.014966667, 0.014833333, 0.014766667, 0.014733333, 0.014733333, 0.014666666, 0.014666666, 0.014633333, 0.0145333335, 0.014366667, 0.014366667, 0.014266667, 0.014266667, 0.014233333, 0.014166667, 0.014166667, 0.014166667, 0.014066666, 0.0139, 0.013766667, 0.013733333, 0.013666667, 0.013633333, 0.013633333, 0.0136, 0.013333334, 0.0133, 0.0131, 0.013033333, 0.013033333, 0.012933333, 0.012933333, 0.012866667, 0.012666667, 0.012666667, 0.012633333, 0.0125, 0.012433333, 0.012433333, 0.0122, 0.011733334, 0.011666667, 0.0115, 0.0115, 0.0113, 0.011266666, 0.011166667, 0.0107, 0.0105, 0.010433333, 0.0103, 0.0101666665, 0.0101, 0.009766666, 0.009433334, 0.0092, 0.0088, 0.008766667, 0.008633333, 0.008433334, 0.008433334, 0.0084, 0.008333334, 0.008233333, 0.008233333, 0.0081, 0.008033333, 0.0078, 0.0072666667, 0.0064, 0.0063333334, 0.006233333, 0.0061666667, 0.0061666667, 0.006, 0.0059666666, 0.0054666665, 0.004766667, 0.0046, 0.0043, 0.0041333335, 0.0035333333, 0.0035, 0.0034666667, 0.0032, 0.003, 0.0024666667, 0.0022666666, 0.0018, 0.0016333334, 0.0016, 0.0014, 0.0010333334, 0.00056666665, 0.0005}'::real[], '{0.024258668}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{50, 59, 47, 54, 62, 49, 44, 57, 42, 48, 38, 43, 55, 52, 45, 46, 56, 64, 68, 51, 53, 61, 41, 39, 37, 36, 66, 35, 65, 40, 63, 58, 67, 29, 33, 32, 60, 34, 30, 69, 71, 72, 31, 73, 75, 25, 74, 26, 70, 28, 22, 27, 76, 79, 78, 24, 23, 77, 21, 82, 20, 83, 81, 84, 18, 85, 80, 16, 15, 17, 86, 14, 19, 87, 90, 88, 10, 11, 13, 12, 89, 91, 92, 9, 93, 8, 95, 6, 94, 5, 7, 96, 4, 97, 3, 98, 99, 2, 100, 1}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.tcor_3'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_3'::regclass and a.attname = 'k3'); +INSERT INTO pg_statistic VALUES ('public.tcor_3'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.tcor_3'::regclass and a.attname = 'k3'), False::boolean, 0::real, 4::integer, 98::real, 1::smallint, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.029266667, 0.028866667, 0.0276, 0.027566666, 0.026766667, 0.026633333, 0.026366666, 0.026, 0.025866667, 0.025733333, 0.024833333, 0.0233, 0.022333333, 0.021733332, 0.021066668, 0.021066668, 0.020766666, 0.020633332, 0.019433333, 0.019033333, 0.019033333, 0.018833334, 0.018266667, 0.018133333, 0.017533334, 0.0164, 0.016333334, 0.015866667, 0.0145, 0.0145, 0.0143, 0.0143, 0.014, 0.012933333, 0.0129, 0.012666667, 0.011766667, 0.0114, 0.0112, 0.0109, 0.0105, 0.009966667, 0.009666666, 0.009466667, 0.0093, 0.0090333335, 0.008333334, 0.0082, 0.008133333, 0.008, 0.0073, 0.0068666665, 0.0067666667, 0.006733333, 0.006366667, 0.0063, 0.0062, 0.0056666667, 0.0056, 0.0049, 0.004833333, 0.0048, 0.0043666665, 0.004, 0.0037, 0.0036333334, 0.0034333332, 0.0032333334, 0.0030666667, 0.0028333333, 0.0025333334, 0.0025, 0.0022, 0.0021666666, 0.002, 0.0018666667, 0.0016666667, 0.0016, 0.0015666666, 0.0015333333, 0.0011333333, 0.0011, 0.0011, 0.00083333335, 0.00076666666, 0.0006, 0.0006, 0.00046666668, 0.0004, 0.00036666667, 0.00036666667}'::real[], NULL::real[], '{0.030713478}'::real[], NULL::real[], NULL::real[], array_in('{1, 4, 2, 3, 5, 6, 10, 9, 8, 7, 13, 11, 12, 15, 17, 18, 14, 16, 19, 20, 24, 22, 21, 26, 23, 25, 27, 28, 29, 34, 30, 32, 31, 37, 33, 35, 36, 39, 38, 41, 43, 40, 45, 44, 42, 46, 47, 48, 50, 49, 51, 53, 52, 55, 56, 54, 57, 59, 58, 63, 60, 62, 61, 64, 66, 68, 65, 70, 67, 69, 71, 74, 75, 72, 73, 77, 76, 80, 79, 78, 83, 82, 84, 81, 85, 86, 87, 88, 89, 90, 92}', 'pg_catalog.int4'::regtype, -1)::anyarray, array_in('{91, 91, 93, 94, 95, 96, 98}', 'pg_catalog.int4'::regtype, -1)::anyarray, NULL, NULL); +update pg_yb_catalog_version set current_version=current_version+1 where db_oid=1; +SET yb_non_ddl_txn_for_sys_tables_allowed = OFF; + + +SET pg_hint_plan.enable_hint = ON; +SET pg_hint_plan.debug_print = ON; +SET client_min_messages TO log; +SET pg_hint_plan.message_level = debug; +SET temp_file_limit="8182MB"; +set yb_bnl_batch_size = 1024; set yb_enable_base_scans_cost_model = true; +SET yb_enable_optimizer_statistics = true; +-- Query Hash: fc9a1677584c556c9ae8b2a510738d25 +EXPLAIN (COSTS OFF) SELECT * FROM t4 WHERE k1 IN (4, 8, 12) AND k4 IN (4, 8, 12, 16); +-- Query Hash: 9274124195113877a48cd356a9b7dfe1 +EXPLAIN (COSTS OFF) SELECT * FROM t4 WHERE k2 IN (4, 8, 12, 16) AND k4 IN (4, 8, 12); +-- Query Hash: 2e3933a314ffab2ec57eab1f65bc99a7 +EXPLAIN (COSTS OFF) SELECT * FROM t5 WHERE k2 IN (4, 8, 12, 16) AND k4 IN (4, 8, 12); +-- Query Hash: e7f63532c5f21e987dcf70c5b62f5cb8 +EXPLAIN (COSTS OFF) SELECT * FROM t4 WHERE k1 IN (4, 8, 12, 16) AND k2 IN (4, 8, 12, 16); +-- Query Hash: 035ed29bec9cf48d0cfccab9ea75393a +EXPLAIN (COSTS OFF) SELECT * FROM t4 WHERE k1 = 4 and k2 IN (4, 8, 12, 16); +-- Query Hash: d5c50c8a7ac34a8901e60dbb379e3f4b +EXPLAIN (COSTS OFF) SELECT * FROM t4 WHERE k1 IN (4, 8, 12, 16) and k2 = 4; +-- Query Hash: 8e96c9a0b2caf93157b338bbd13ee9fa +EXPLAIN (COSTS OFF) SELECT * FROM t4 WHERE k3 IN (4, 8, 12, 16) and k4 = 4; +-- Query Hash: 98672ebc721b9dabcd751ae8d1d70bfe +EXPLAIN (COSTS OFF) SELECT * FROM t2 WHERE k1 >= 4 and k1 < 5 and k2 IN (4, 8, 12, 16); +-- Query Hash: 4e3d6cdf2d3dfd8c9661d9db1e06d06f +EXPLAIN (COSTS OFF) SELECT * FROM t2 WHERE k1 >= 4 and k1 < 6 and k2 IN (4, 8, 12, 16); +-- Query Hash: b3c453f75f2bfa75e8934afb1d0a000e +EXPLAIN (COSTS OFF) SELECT * FROM t2 WHERE k1 >= 4 and k1 < 14 and k2 IN (4, 8, 12, 16); +-- Query Hash: c7fd96b45a1a90a438c5c8b51b88bf2f +EXPLAIN (COSTS OFF) SELECT * FROM t3 WHERE k1 >= 4 and k1 < 7 and k3 IN (4, 8, 12, 16); +-- Query Hash: 7c3d5db060949e54e6135d9a0d8ea919 +EXPLAIN (COSTS OFF) SELECT * FROM t4 WHERE k2 >= 4 and k2 < 7 and k4 IN (4, 8, 12); +-- Query Hash: 42a855f52bd3a79f8fb3814af23cc20d +EXPLAIN (COSTS OFF) SELECT * FROM t5 WHERE k2 >= 4 and k2 < 7 and k4 IN (4, 8, 12); +-- Query Hash: 0e9d33c69acb0551957d04fc0fcc5aa1 +EXPLAIN (COSTS OFF) SELECT * FROM t5 WHERE k2 IN (4,8,12,16) and k3 <= 4 and k3 > 7 and k4 IN (4, 8, 12); +-- Query Hash: 8b07844ffb72e7e079ec3f59c0986f88 +EXPLAIN (COSTS OFF) SELECT * FROM t4 WHERE k3 >= 4 and k3 < 14; +-- Query Hash: 7431fd75b284e9732a0fe4b7d6efc8a2 +EXPLAIN (COSTS OFF) SELECT * FROM t4 WHERE k1 >= 4 and k1 < 14 and k3 >= 4 and k3 < 14; +-- Query Hash: 0bbbb91ea7ed546300a103f7ebf911b0 +EXPLAIN (COSTS OFF) SELECT * FROM t5 WHERE k2 >= 4 and k2 < 14 and k4 >= 4 and k4 < 14; +-- Query Hash: a2d5901879e1db5f406b2dff1ce91e8e +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE k1 >= 4 and k1 < 14; +-- Query Hash: 61f218667861386932c71486de344a8f +EXPLAIN (COSTS OFF) SELECT * FROM t2 WHERE k1 >= 4 and k1 < 14; +-- Query Hash: 4fc63a754558f81b3ea788b8ab66b2dd +EXPLAIN (COSTS OFF) SELECT * FROM t3 WHERE k1 >= 4 and k1 < 14; +-- Query Hash: 24482e5009140b10ee7738b1f978e9f6 +EXPLAIN (COSTS OFF) SELECT * FROM t4 WHERE k1 >= 4 and k1 < 14; +-- Query Hash: f02c4a2ecd531f683479251b7d3439ce +EXPLAIN (COSTS OFF) SELECT * FROM t5 WHERE k1 >= 4 and k1 < 14; +-- Query Hash: a2d5901879e1db5f406b2dff1ce91e8e +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE k1 >= 4 and k1 < 14; +-- Query Hash: 4772a64709ca191fa2b2096f159e5f3f +EXPLAIN (COSTS OFF) SELECT * FROM t2 WHERE k2 >= 4 and k2 < 14; +-- Query Hash: 24dc55c74b331ad4fd956ac9b6400d8d +EXPLAIN (COSTS OFF) SELECT * FROM t3 WHERE k3 >= 4 and k3 < 14; +-- Query Hash: f425b12c4a6d8d734c69e50afeff04ed +EXPLAIN (COSTS OFF) SELECT * FROM t4 WHERE k4 >= 4 and k4 < 14; +-- Query Hash: 7d901f98ab781317763d9915875de118 , !BEST_PLAN_FOUND +EXPLAIN (COSTS OFF) SELECT * FROM t5 WHERE k5 >= 4 and k5 < 14; +-- Query Hash: b5562a3ef44a737b2846f0975cf9e1c7 +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE k1 IN (4, 8); +-- Query Hash: 6a8ead9b8b122eea0e5460236bc6c19b , !BEST_PLAN_FOUND +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE k1 IN (4, 8, 12); +-- Query Hash: d7eedc71bffb36fa0152da8c08bf7808 +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE k1 IN (4, 8, 12, 16); +-- Query Hash: 3a022df927f53a2864f9d95af72fee58 +EXPLAIN (COSTS OFF) SELECT * FROM t2 WHERE k1 IN (4, 8, 12, 16); +-- Query Hash: 8c7bf857d9e088af7a4b5c904c5b8610 +EXPLAIN (COSTS OFF) SELECT * FROM t2 WHERE k2 IN (4, 8, 12, 16); +-- Query Hash: 5d91fd582d63d5cb7df1e3face8067d5 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_1 WHERE k1 < 5; +-- Query Hash: 979b459461ba7b840826695627007334 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_1 WHERE k2 < 25; +-- Query Hash: 9a8420c43a0a8e1661e5d1ac9bdd62b0 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_1 WHERE k3 < 125; +-- Query Hash: 3324c9ff1941acab32f5415abd90c0c4 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_1 WHERE k4 < 25; +-- Query Hash: adbcbd6fb6decb44cd3d786bf910e5d4 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_1 WHERE k1 < 5 AND k2 < 25; +-- Query Hash: 25bd3b427c6c6786ffd4bb1ac43d50c9 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_1 WHERE k1 < 5 AND k3 < 125; +-- Query Hash: c51689b307246763709d95761488c01e +EXPLAIN (COSTS OFF) SELECT * FROM tcor_1 WHERE k1 < 5 AND k4 < 25; +-- Query Hash: f2621d436cb67e9e38c733ba36c6e3bd +EXPLAIN (COSTS OFF) SELECT * FROM tcor_1 WHERE k2 < 25 AND k3 < 125; +-- Query Hash: c60cbde271e15ebbb324c337b8219e11 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_1 WHERE k2 < 25 AND k4 < 25; +-- Query Hash: 9be0c8a2b6797d336f5d43b76e18be46 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_1 WHERE k3 < 125 AND k4 < 25; +-- Query Hash: 4fe07880e09bf7812010c348aae8a57d +EXPLAIN (COSTS OFF) SELECT * FROM tcor_1 WHERE k1 < 5 AND k2 < 25 AND k3 < 125 AND k4 < 25; +-- Query Hash: aaa7d9c764b2723a7495d33f609ab200 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_2 WHERE k1 < 0; +-- Query Hash: 540441b5cf77f76d10f510ba04556e3d +EXPLAIN (COSTS OFF) SELECT * FROM tcor_2 WHERE k2 < 0; +-- Query Hash: 3b525b570874f819873f09cd386b2c10 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_2 WHERE k3 < 0; +-- Query Hash: 90c6e6321ee74099b07569d8aa7e9f05 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_2 WHERE k4 < 0; +-- Query Hash: 4d05ca97a12862a4a513929f249524ea +EXPLAIN (COSTS OFF) SELECT * FROM tcor_2 WHERE k1 < 0 AND k2 < 0; +-- Query Hash: 89c00f63e767513c6aa3d08cb9b55f0b +EXPLAIN (COSTS OFF) SELECT * FROM tcor_2 WHERE k1 < 0 AND k3 < 0; +-- Query Hash: cc71e17fa039883b7e8fd219533c8285 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_2 WHERE k1 < 0 AND k4 < 0; +-- Query Hash: 60229f8796efe228e27d959b0ea59781 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_2 WHERE k2 < 0 AND k3 < 0; +-- Query Hash: 1a883df555044f269f4dc699156a8594 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_2 WHERE k2 < 0 AND k4 < 0; +-- Query Hash: 4566c9cfd7224af62bb14a7dd6cf429f +EXPLAIN (COSTS OFF) SELECT * FROM tcor_2 WHERE k3 < 0 AND k4 < 0; +-- Query Hash: 18f5729f5f6a432245c96e7c6ce13cbe +EXPLAIN (COSTS OFF) SELECT * FROM tcor_2 WHERE k1 < 0 AND k2 < 0 AND k3 < 0 AND k4 < 0; +-- Query Hash: 51d11a69af237d21a0f439680ae11e86 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k1 > 50 and k3 > 50; +-- Query Hash: b0418f7b0f73ba0a1e09526703b0ec60 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k2 > 50 and k3 > 50; +-- Query Hash: 162202d912755f260a7ba158aaa656e7 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k1 > 50 and k2 > 50; +-- Query Hash: 92961ecf4a4a096b80054048f778d525 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k1 = 50 AND k3 > 50; +-- Query Hash: 375c8ae3f86b5dbba917c7108e42b918 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k1 = 50 AND k3 > 50 AND k3 < 80; +-- Query Hash: d729eb647c6857e30897b4b4a6fec1be +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k2 = 50 AND k3 > 50; +-- Query Hash: b57c1e01bf6b6658b86167fc6df13845 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k2 = 50 AND k3 > 50 AND k3 < 80; +-- Query Hash: 024fb5b577416303e3777892ea2f02c9 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k3 = 50 AND k2 > 50; +-- Query Hash: d271787b745e12b73f1d71efdf25856c +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k3 = 50 AND k2 > 50 AND k2 < 80; +-- Query Hash: 16a4b2ae22250f600cc8ddd60a443049 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k2 IN (50, 60, 70, 80) AND k3 > 50; +-- Query Hash: 32a970e0cc5bed5898970ed39860a744 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k2 IN (50, 60, 70, 80) AND k3 > 50 AND k3 < 80; +-- Query Hash: 4ae624260329d91797989cfec1fe32b3 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k1 IN (50, 60, 70, 80) AND k2 IN (50, 60, 70, 80); +-- Query Hash: 17c4b34da306f3aab063529002052144 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k1 IN (50, 60, 70, 80) AND k3 IN (50, 60, 70, 80); +-- Query Hash: b457f2828cc6c6beded8e0b71b63a624 +EXPLAIN (COSTS OFF) SELECT * FROM tcor_3 WHERE k2 IN (50, 60, 70, 80) AND k3 IN (50, 60, 70, 80); +-- DROP QUERIES; +DROP TABLE IF EXISTS t1 CASCADE; +DROP TABLE IF EXISTS t2 CASCADE; +DROP TABLE IF EXISTS t3 CASCADE; +DROP TABLE IF EXISTS t4 CASCADE; +DROP TABLE IF EXISTS t5 CASCADE; +DROP TABLE IF EXISTS tcor_1 CASCADE; +DROP TABLE IF EXISTS tcor_2 CASCADE; +DROP TABLE IF EXISTS tcor_3 CASCADE; diff --git a/src/test/regress/sql/yb.orig.planner_taqo_tuning_tests.sql b/src/test/regress/sql/yb.orig.planner_taqo_tuning_tests.sql new file mode 100644 index 0000000000000..57677350c775d --- /dev/null +++ b/src/test/regress/sql/yb.orig.planner_taqo_tuning_tests.sql @@ -0,0 +1,888 @@ +CREATE DATABASE taqo_tuning_tests with colocation = true; +\c taqo_tuning_tests +-- A grace period of 2s is added to every RPC (controlled by gflag 'pg_client_extra_timeout_ms'). +-- To avoid crossing the max RPC timeout threshold (7200s), account for the above in setting +-- the statement timeout. +SET statement_timeout = '7198s'; +-- CREATE QUERIES +CREATE TABLE t_range_100k (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE INDEX tr100kv1 ON t_range_100k (v1 ASC); +CREATE TABLE t_range_200k (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE INDEX tr200kv1 ON t_range_200k (v1 ASC); +CREATE TABLE t_range_300k (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE INDEX tr300kv1 ON t_range_300k (v1 ASC); +CREATE TABLE t_range_400k (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE INDEX tr400kv1 ON t_range_400k (v1 ASC); +CREATE TABLE t_range_500k (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE INDEX tr500kv1 ON t_range_500k (v1 ASC); +CREATE TABLE t_range_600k (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE INDEX tr600kv1 ON t_range_600k (v1 ASC); +CREATE TABLE t_range_700k (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE INDEX tr700kv1 ON t_range_700k (v1 ASC); +CREATE TABLE t_range_800k (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE INDEX tr800kv1 ON t_range_800k (v1 ASC); +CREATE TABLE t_range_900k (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE INDEX tr900kv1 ON t_range_900k (v1 ASC); +CREATE TABLE t_range_1m (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE INDEX tr1mv1 ON t_range_1m (v1 ASC); +CREATE TABLE t_range_100k_1update (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE TABLE t_range_100k_2update (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE TABLE t_range_100k_3update (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE TABLE t_range_100k_4update (id INT, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (id ASC)); +CREATE TABLE t_range_100k_1column (id INT PRIMARY KEY); +CREATE TABLE t_range_100k_2column (id INT, v1 INT, PRIMARY KEY (id ASC)); +CREATE TABLE t_range_100k_3column (id INT, v1 INT, v2 INT, PRIMARY KEY (id ASC)); +CREATE TABLE t_range_100k_4column (id INT, v1 INT, v2 INT, v3 INT, PRIMARY KEY (id ASC)); +CREATE TABLE t_range_1m_4keys (k1 INT, k2 INT, k3 INT, k4 INT, v1 INT, PRIMARY KEY (k1 ASC, k2 ASC, k3 ASC, k4 ASC)); +CREATE TABLE t_int_100k (v1 int, v2 int, v3 int, v4 int, v5 int, v6 int, v7 int, v8 int); +CREATE TABLE t_numeric_100k (v1 numeric(12,6), v2 numeric(12,6), v3 numeric(12,6), v4 numeric(12,6), v5 numeric(12,6), v6 numeric(12,6), v7 numeric(12,6), v8 numeric(12,6)); +CREATE TABLE t_real_100k (v1 real, v2 real, v3 real, v4 real, v5 real, v6 real, v7 real, v8 real); +CREATE TABLE t_char4_100k (v1 char(4), v2 char(4), v3 char(4), v4 char(4), v5 char(4), v6 char(4), v7 char(4), v8 char(4)); +CREATE TABLE t_char8_100k (v1 char(8), v2 char(8), v3 char(8), v4 char(8), v5 char(8), v6 char(8), v7 char(8), v8 char(8)); +CREATE TABLE t_char16_100k (v1 char(16), v2 char(16), v3 char(16), v4 char(16), v5 char(16), v6 char(16), v7 char(16), v8 char(16)); + +SET yb_non_ddl_txn_for_sys_tables_allowed = ON; +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k' OR relname = 't_range_100k_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_1column' OR relname = 't_range_100k_1column_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_1column_pkey' OR relname = 't_range_100k_1column_pkey_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_1update' OR relname = 't_range_100k_1update_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_1update_pkey' OR relname = 't_range_100k_1update_pkey_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_2column' OR relname = 't_range_100k_2column_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_2column_pkey' OR relname = 't_range_100k_2column_pkey_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_2update' OR relname = 't_range_100k_2update_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_2update_pkey' OR relname = 't_range_100k_2update_pkey_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_3column' OR relname = 't_range_100k_3column_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_3column_pkey' OR relname = 't_range_100k_3column_pkey_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_3update' OR relname = 't_range_100k_3update_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_3update_pkey' OR relname = 't_range_100k_3update_pkey_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_4column' OR relname = 't_range_100k_4column_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_4column_pkey' OR relname = 't_range_100k_4column_pkey_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_4update' OR relname = 't_range_100k_4update_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_4update_pkey' OR relname = 't_range_100k_4update_pkey_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_100k_pkey' OR relname = 't_range_100k_pkey_pkey'); +UPDATE pg_class SET reltuples = 1000000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_1m' OR relname = 't_range_1m_pkey'); +UPDATE pg_class SET reltuples = 1000000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_1m_4keys' OR relname = 't_range_1m_4keys_pkey'); +UPDATE pg_class SET reltuples = 1000000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_1m_4keys_pkey' OR relname = 't_range_1m_4keys_pkey_pkey'); +UPDATE pg_class SET reltuples = 1000000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_1m_pkey' OR relname = 't_range_1m_pkey_pkey'); +UPDATE pg_class SET reltuples = 200000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_200k' OR relname = 't_range_200k_pkey'); +UPDATE pg_class SET reltuples = 200000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_200k_pkey' OR relname = 't_range_200k_pkey_pkey'); +UPDATE pg_class SET reltuples = 300000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_300k' OR relname = 't_range_300k_pkey'); +UPDATE pg_class SET reltuples = 300000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_300k_pkey' OR relname = 't_range_300k_pkey_pkey'); +UPDATE pg_class SET reltuples = 400000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_400k' OR relname = 't_range_400k_pkey'); +UPDATE pg_class SET reltuples = 400000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_400k_pkey' OR relname = 't_range_400k_pkey_pkey'); +UPDATE pg_class SET reltuples = 500000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_500k' OR relname = 't_range_500k_pkey'); +UPDATE pg_class SET reltuples = 500000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_500k_pkey' OR relname = 't_range_500k_pkey_pkey'); +UPDATE pg_class SET reltuples = 600000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_600k' OR relname = 't_range_600k_pkey'); +UPDATE pg_class SET reltuples = 600000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_600k_pkey' OR relname = 't_range_600k_pkey_pkey'); +UPDATE pg_class SET reltuples = 700000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_700k' OR relname = 't_range_700k_pkey'); +UPDATE pg_class SET reltuples = 700000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_700k_pkey' OR relname = 't_range_700k_pkey_pkey'); +UPDATE pg_class SET reltuples = 800000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_800k' OR relname = 't_range_800k_pkey'); +UPDATE pg_class SET reltuples = 800000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_800k_pkey' OR relname = 't_range_800k_pkey_pkey'); +UPDATE pg_class SET reltuples = 900000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_900k' OR relname = 't_range_900k_pkey'); +UPDATE pg_class SET reltuples = 900000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 't_range_900k_pkey' OR relname = 't_range_900k_pkey_pkey'); +UPDATE pg_class SET reltuples = 100000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tr100kv1' OR relname = 'tr100kv1_pkey'); +UPDATE pg_class SET reltuples = 1000000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tr1mv1' OR relname = 'tr1mv1_pkey'); +UPDATE pg_class SET reltuples = 200000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tr200kv1' OR relname = 'tr200kv1_pkey'); +UPDATE pg_class SET reltuples = 300000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tr300kv1' OR relname = 'tr300kv1_pkey'); +UPDATE pg_class SET reltuples = 400000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tr400kv1' OR relname = 'tr400kv1_pkey'); +UPDATE pg_class SET reltuples = 500000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tr500kv1' OR relname = 'tr500kv1_pkey'); +UPDATE pg_class SET reltuples = 600000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tr600kv1' OR relname = 'tr600kv1_pkey'); +UPDATE pg_class SET reltuples = 700000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tr700kv1' OR relname = 'tr700kv1_pkey'); +UPDATE pg_class SET reltuples = 800000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tr800kv1' OR relname = 'tr800kv1_pkey'); +UPDATE pg_class SET reltuples = 900000, relpages = 0 WHERE relnamespace = 'public'::regnamespace AND (relname = 'tr900kv1' OR relname = 'tr900kv1_pkey'); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0870054364}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 1039, 2029, 3032, 4007, 5098, 6148, 7188, 8129, 9056, 10061, 11094, 12187, 13133, 14219, 15088, 16100, 17124, 18111, 19100, 20105, 21038, 21951, 22975, 23947, 24893, 25944, 26897, 27929, 28868, 29846, 30845, 31778, 32830, 33758, 34748, 35751, 36752, 37747, 38846, 39846, 40871, 41873, 42876, 43802, 44750, 45716, 46726, 47840, 48832, 49845, 50844, 51843, 52761, 53713, 54685, 55661, 56671, 57600, 58592, 59622, 60680, 61571, 62549, 63617, 64633, 65599, 66620, 67668, 68715, 69617, 70677, 71666, 72743, 73671, 74632, 75638, 76583, 77632, 78650, 79594, 80614, 81529, 82573, 83640, 84639, 85648, 86677, 87759, 88760, 89771, 90831, 91927, 92964, 93933, 94844, 95875, 96912, 97918, 99007, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0870054364}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 1039, 2029, 3032, 4007, 5098, 6148, 7188, 8129, 9056, 10061, 11094, 12187, 13133, 14219, 15088, 16100, 17124, 18111, 19100, 20105, 21038, 21951, 22975, 23947, 24893, 25944, 26897, 27929, 28868, 29846, 30845, 31778, 32830, 33758, 34748, 35751, 36752, 37747, 38846, 39846, 40871, 41873, 42876, 43802, 44750, 45716, 46726, 47840, 48832, 49845, 50844, 51843, 52761, 53713, 54685, 55661, 56671, 57600, 58592, 59622, 60680, 61571, 62549, 63617, 64633, 65599, 66620, 67668, 68715, 69617, 70677, 71666, 72743, 73671, 74632, 75638, 76583, 77632, 78650, 79594, 80614, 81529, 82573, 83640, 84639, 85648, 86677, 87759, 88760, 89771, 90831, 91927, 92964, 93933, 94844, 95875, 96912, 97918, 99007, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0870054364}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 1039, 2029, 3032, 4007, 5098, 6148, 7188, 8129, 9056, 10061, 11094, 12187, 13133, 14219, 15088, 16100, 17124, 18111, 19100, 20105, 21038, 21951, 22975, 23947, 24893, 25944, 26897, 27929, 28868, 29846, 30845, 31778, 32830, 33758, 34748, 35751, 36752, 37747, 38846, 39846, 40871, 41873, 42876, 43802, 44750, 45716, 46726, 47840, 48832, 49845, 50844, 51843, 52761, 53713, 54685, 55661, 56671, 57600, 58592, 59622, 60680, 61571, 62549, 63617, 64633, 65599, 66620, 67668, 68715, 69617, 70677, 71666, 72743, 73671, 74632, 75638, 76583, 77632, 78650, 79594, 80614, 81529, 82573, 83640, 84639, 85648, 86677, 87759, 88760, 89771, 90831, 91927, 92964, 93933, 94844, 95875, 96912, 97918, 99007, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0870054364}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 1039, 2029, 3032, 4007, 5098, 6148, 7188, 8129, 9056, 10061, 11094, 12187, 13133, 14219, 15088, 16100, 17124, 18111, 19100, 20105, 21038, 21951, 22975, 23947, 24893, 25944, 26897, 27929, 28868, 29846, 30845, 31778, 32830, 33758, 34748, 35751, 36752, 37747, 38846, 39846, 40871, 41873, 42876, 43802, 44750, 45716, 46726, 47840, 48832, 49845, 50844, 51843, 52761, 53713, 54685, 55661, 56671, 57600, 58592, 59622, 60680, 61571, 62549, 63617, 64633, 65599, 66620, 67668, 68715, 69617, 70677, 71666, 72743, 73671, 74632, 75638, 76583, 77632, 78650, 79594, 80614, 81529, 82573, 83640, 84639, 85648, 86677, 87759, 88760, 89771, 90831, 91927, 92964, 93933, 94844, 95875, 96912, 97918, 99007, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0870054364}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 1039, 2029, 3032, 4007, 5098, 6148, 7188, 8129, 9056, 10061, 11094, 12187, 13133, 14219, 15088, 16100, 17124, 18111, 19100, 20105, 21038, 21951, 22975, 23947, 24893, 25944, 26897, 27929, 28868, 29846, 30845, 31778, 32830, 33758, 34748, 35751, 36752, 37747, 38846, 39846, 40871, 41873, 42876, 43802, 44750, 45716, 46726, 47840, 48832, 49845, 50844, 51843, 52761, 53713, 54685, 55661, 56671, 57600, 58592, 59622, 60680, 61571, 62549, 63617, 64633, 65599, 66620, 67668, 68715, 69617, 70677, 71666, 72743, 73671, 74632, 75638, 76583, 77632, 78650, 79594, 80614, 81529, 82573, 83640, 84639, 85648, 86677, 87759, 88760, 89771, 90831, 91927, 92964, 93933, 94844, 95875, 96912, 97918, 99007, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_200k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_200k'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_200k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_200k'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0271998309}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{23, 2012, 4129, 6130, 8268, 10318, 12185, 14124, 16249, 18185, 20097, 22225, 24408, 26353, 28435, 30503, 32425, 34570, 36839, 38868, 40820, 42910, 45034, 47046, 49058, 51023, 53073, 55062, 57107, 59147, 61200, 63354, 65438, 67483, 69596, 71659, 73580, 75498, 77414, 79254, 81203, 83172, 85096, 87032, 89063, 91024, 93106, 95064, 96934, 98825, 100718, 102668, 104792, 106991, 108771, 110577, 112644, 114758, 116897, 118866, 120786, 122540, 124447, 126230, 128123, 130069, 132168, 134159, 136236, 138249, 140168, 142185, 144233, 146243, 148342, 150347, 152392, 154420, 156475, 158332, 160328, 162177, 164198, 166100, 168217, 170149, 172177, 174200, 176165, 178141, 180284, 182184, 184253, 186446, 188409, 190450, 192374, 194338, 196138, 198074, 199993}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_200k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_200k'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_200k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_200k'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0271998309}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{23, 2012, 4129, 6130, 8268, 10318, 12185, 14124, 16249, 18185, 20097, 22225, 24408, 26353, 28435, 30503, 32425, 34570, 36839, 38868, 40820, 42910, 45034, 47046, 49058, 51023, 53073, 55062, 57107, 59147, 61200, 63354, 65438, 67483, 69596, 71659, 73580, 75498, 77414, 79254, 81203, 83172, 85096, 87032, 89063, 91024, 93106, 95064, 96934, 98825, 100718, 102668, 104792, 106991, 108771, 110577, 112644, 114758, 116897, 118866, 120786, 122540, 124447, 126230, 128123, 130069, 132168, 134159, 136236, 138249, 140168, 142185, 144233, 146243, 148342, 150347, 152392, 154420, 156475, 158332, 160328, 162177, 164198, 166100, 168217, 170149, 172177, 174200, 176165, 178141, 180284, 182184, 184253, 186446, 188409, 190450, 192374, 194338, 196138, 198074, 199993}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_200k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_200k'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_200k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_200k'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0271998309}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{23, 2012, 4129, 6130, 8268, 10318, 12185, 14124, 16249, 18185, 20097, 22225, 24408, 26353, 28435, 30503, 32425, 34570, 36839, 38868, 40820, 42910, 45034, 47046, 49058, 51023, 53073, 55062, 57107, 59147, 61200, 63354, 65438, 67483, 69596, 71659, 73580, 75498, 77414, 79254, 81203, 83172, 85096, 87032, 89063, 91024, 93106, 95064, 96934, 98825, 100718, 102668, 104792, 106991, 108771, 110577, 112644, 114758, 116897, 118866, 120786, 122540, 124447, 126230, 128123, 130069, 132168, 134159, 136236, 138249, 140168, 142185, 144233, 146243, 148342, 150347, 152392, 154420, 156475, 158332, 160328, 162177, 164198, 166100, 168217, 170149, 172177, 174200, 176165, 178141, 180284, 182184, 184253, 186446, 188409, 190450, 192374, 194338, 196138, 198074, 199993}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_200k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_200k'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_200k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_200k'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0271998309}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{23, 2012, 4129, 6130, 8268, 10318, 12185, 14124, 16249, 18185, 20097, 22225, 24408, 26353, 28435, 30503, 32425, 34570, 36839, 38868, 40820, 42910, 45034, 47046, 49058, 51023, 53073, 55062, 57107, 59147, 61200, 63354, 65438, 67483, 69596, 71659, 73580, 75498, 77414, 79254, 81203, 83172, 85096, 87032, 89063, 91024, 93106, 95064, 96934, 98825, 100718, 102668, 104792, 106991, 108771, 110577, 112644, 114758, 116897, 118866, 120786, 122540, 124447, 126230, 128123, 130069, 132168, 134159, 136236, 138249, 140168, 142185, 144233, 146243, 148342, 150347, 152392, 154420, 156475, 158332, 160328, 162177, 164198, 166100, 168217, 170149, 172177, 174200, 176165, 178141, 180284, 182184, 184253, 186446, 188409, 190450, 192374, 194338, 196138, 198074, 199993}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_200k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_200k'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_200k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_200k'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0271998309}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{23, 2012, 4129, 6130, 8268, 10318, 12185, 14124, 16249, 18185, 20097, 22225, 24408, 26353, 28435, 30503, 32425, 34570, 36839, 38868, 40820, 42910, 45034, 47046, 49058, 51023, 53073, 55062, 57107, 59147, 61200, 63354, 65438, 67483, 69596, 71659, 73580, 75498, 77414, 79254, 81203, 83172, 85096, 87032, 89063, 91024, 93106, 95064, 96934, 98825, 100718, 102668, 104792, 106991, 108771, 110577, 112644, 114758, 116897, 118866, 120786, 122540, 124447, 126230, 128123, 130069, 132168, 134159, 136236, 138249, 140168, 142185, 144233, 146243, 148342, 150347, 152392, 154420, 156475, 158332, 160328, 162177, 164198, 166100, 168217, 170149, 172177, 174200, 176165, 178141, 180284, 182184, 184253, 186446, 188409, 190450, 192374, 194338, 196138, 198074, 199993}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_300k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_300k'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_300k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_300k'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0105920387}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 3052, 6032, 9038, 12086, 15111, 18211, 21345, 24417, 27684, 30692, 33806, 36489, 39569, 42599, 45494, 48540, 51778, 54744, 57950, 61120, 63921, 67452, 70618, 73645, 76502, 79575, 82358, 85130, 88207, 91350, 94192, 97106, 99821, 102916, 105951, 108868, 111902, 114874, 117903, 120813, 123706, 126622, 129512, 132401, 135624, 138644, 141815, 144896, 147893, 150649, 153664, 156448, 159476, 162578, 165671, 168581, 171468, 174423, 177379, 180539, 183332, 186530, 189545, 192233, 195335, 198247, 201249, 203920, 206911, 209875, 212993, 215826, 218912, 221692, 224947, 228107, 230976, 234116, 237348, 240591, 243495, 246339, 249206, 252247, 255116, 258460, 261779, 264698, 267723, 270670, 273646, 276770, 279576, 282428, 285387, 288320, 291276, 293972, 296977, 299979}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_300k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_300k'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_300k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_300k'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0105920387}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 3052, 6032, 9038, 12086, 15111, 18211, 21345, 24417, 27684, 30692, 33806, 36489, 39569, 42599, 45494, 48540, 51778, 54744, 57950, 61120, 63921, 67452, 70618, 73645, 76502, 79575, 82358, 85130, 88207, 91350, 94192, 97106, 99821, 102916, 105951, 108868, 111902, 114874, 117903, 120813, 123706, 126622, 129512, 132401, 135624, 138644, 141815, 144896, 147893, 150649, 153664, 156448, 159476, 162578, 165671, 168581, 171468, 174423, 177379, 180539, 183332, 186530, 189545, 192233, 195335, 198247, 201249, 203920, 206911, 209875, 212993, 215826, 218912, 221692, 224947, 228107, 230976, 234116, 237348, 240591, 243495, 246339, 249206, 252247, 255116, 258460, 261779, 264698, 267723, 270670, 273646, 276770, 279576, 282428, 285387, 288320, 291276, 293972, 296977, 299979}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_300k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_300k'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_300k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_300k'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0105920387}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 3052, 6032, 9038, 12086, 15111, 18211, 21345, 24417, 27684, 30692, 33806, 36489, 39569, 42599, 45494, 48540, 51778, 54744, 57950, 61120, 63921, 67452, 70618, 73645, 76502, 79575, 82358, 85130, 88207, 91350, 94192, 97106, 99821, 102916, 105951, 108868, 111902, 114874, 117903, 120813, 123706, 126622, 129512, 132401, 135624, 138644, 141815, 144896, 147893, 150649, 153664, 156448, 159476, 162578, 165671, 168581, 171468, 174423, 177379, 180539, 183332, 186530, 189545, 192233, 195335, 198247, 201249, 203920, 206911, 209875, 212993, 215826, 218912, 221692, 224947, 228107, 230976, 234116, 237348, 240591, 243495, 246339, 249206, 252247, 255116, 258460, 261779, 264698, 267723, 270670, 273646, 276770, 279576, 282428, 285387, 288320, 291276, 293972, 296977, 299979}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_300k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_300k'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_300k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_300k'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0105920387}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 3052, 6032, 9038, 12086, 15111, 18211, 21345, 24417, 27684, 30692, 33806, 36489, 39569, 42599, 45494, 48540, 51778, 54744, 57950, 61120, 63921, 67452, 70618, 73645, 76502, 79575, 82358, 85130, 88207, 91350, 94192, 97106, 99821, 102916, 105951, 108868, 111902, 114874, 117903, 120813, 123706, 126622, 129512, 132401, 135624, 138644, 141815, 144896, 147893, 150649, 153664, 156448, 159476, 162578, 165671, 168581, 171468, 174423, 177379, 180539, 183332, 186530, 189545, 192233, 195335, 198247, 201249, 203920, 206911, 209875, 212993, 215826, 218912, 221692, 224947, 228107, 230976, 234116, 237348, 240591, 243495, 246339, 249206, 252247, 255116, 258460, 261779, 264698, 267723, 270670, 273646, 276770, 279576, 282428, 285387, 288320, 291276, 293972, 296977, 299979}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_300k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_300k'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_300k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_300k'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0105920387}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 3052, 6032, 9038, 12086, 15111, 18211, 21345, 24417, 27684, 30692, 33806, 36489, 39569, 42599, 45494, 48540, 51778, 54744, 57950, 61120, 63921, 67452, 70618, 73645, 76502, 79575, 82358, 85130, 88207, 91350, 94192, 97106, 99821, 102916, 105951, 108868, 111902, 114874, 117903, 120813, 123706, 126622, 129512, 132401, 135624, 138644, 141815, 144896, 147893, 150649, 153664, 156448, 159476, 162578, 165671, 168581, 171468, 174423, 177379, 180539, 183332, 186530, 189545, 192233, 195335, 198247, 201249, 203920, 206911, 209875, 212993, 215826, 218912, 221692, 224947, 228107, 230976, 234116, 237348, 240591, 243495, 246339, 249206, 252247, 255116, 258460, 261779, 264698, 267723, 270670, 273646, 276770, 279576, 282428, 285387, 288320, 291276, 293972, 296977, 299979}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_400k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_400k'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_400k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_400k'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0189471394}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 3843, 7645, 11711, 15350, 19278, 23264, 27811, 31908, 35724, 39644, 43411, 47364, 51515, 55794, 59774, 63966, 67804, 72182, 76063, 80006, 84006, 87487, 91210, 95018, 98577, 102521, 106573, 110600, 114604, 118503, 122677, 126536, 130701, 134482, 138322, 142198, 145763, 149827, 153864, 157850, 161793, 166081, 169976, 174145, 178094, 182014, 185972, 189931, 194132, 198345, 202267, 206482, 210356, 214332, 218446, 222254, 226844, 230947, 234913, 238713, 242595, 246483, 250717, 254501, 258549, 262642, 266768, 270663, 274828, 279019, 283393, 287478, 291622, 295852, 299893, 304002, 308305, 312283, 316605, 320493, 324597, 328426, 332619, 336891, 340926, 345338, 349191, 353095, 357081, 360550, 364619, 368500, 372513, 376042, 379916, 384761, 388821, 392669, 396471, 399988}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_400k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_400k'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_400k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_400k'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0189471394}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 3843, 7645, 11711, 15350, 19278, 23264, 27811, 31908, 35724, 39644, 43411, 47364, 51515, 55794, 59774, 63966, 67804, 72182, 76063, 80006, 84006, 87487, 91210, 95018, 98577, 102521, 106573, 110600, 114604, 118503, 122677, 126536, 130701, 134482, 138322, 142198, 145763, 149827, 153864, 157850, 161793, 166081, 169976, 174145, 178094, 182014, 185972, 189931, 194132, 198345, 202267, 206482, 210356, 214332, 218446, 222254, 226844, 230947, 234913, 238713, 242595, 246483, 250717, 254501, 258549, 262642, 266768, 270663, 274828, 279019, 283393, 287478, 291622, 295852, 299893, 304002, 308305, 312283, 316605, 320493, 324597, 328426, 332619, 336891, 340926, 345338, 349191, 353095, 357081, 360550, 364619, 368500, 372513, 376042, 379916, 384761, 388821, 392669, 396471, 399988}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_400k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_400k'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_400k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_400k'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0189471394}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 3843, 7645, 11711, 15350, 19278, 23264, 27811, 31908, 35724, 39644, 43411, 47364, 51515, 55794, 59774, 63966, 67804, 72182, 76063, 80006, 84006, 87487, 91210, 95018, 98577, 102521, 106573, 110600, 114604, 118503, 122677, 126536, 130701, 134482, 138322, 142198, 145763, 149827, 153864, 157850, 161793, 166081, 169976, 174145, 178094, 182014, 185972, 189931, 194132, 198345, 202267, 206482, 210356, 214332, 218446, 222254, 226844, 230947, 234913, 238713, 242595, 246483, 250717, 254501, 258549, 262642, 266768, 270663, 274828, 279019, 283393, 287478, 291622, 295852, 299893, 304002, 308305, 312283, 316605, 320493, 324597, 328426, 332619, 336891, 340926, 345338, 349191, 353095, 357081, 360550, 364619, 368500, 372513, 376042, 379916, 384761, 388821, 392669, 396471, 399988}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_400k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_400k'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_400k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_400k'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0189471394}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 3843, 7645, 11711, 15350, 19278, 23264, 27811, 31908, 35724, 39644, 43411, 47364, 51515, 55794, 59774, 63966, 67804, 72182, 76063, 80006, 84006, 87487, 91210, 95018, 98577, 102521, 106573, 110600, 114604, 118503, 122677, 126536, 130701, 134482, 138322, 142198, 145763, 149827, 153864, 157850, 161793, 166081, 169976, 174145, 178094, 182014, 185972, 189931, 194132, 198345, 202267, 206482, 210356, 214332, 218446, 222254, 226844, 230947, 234913, 238713, 242595, 246483, 250717, 254501, 258549, 262642, 266768, 270663, 274828, 279019, 283393, 287478, 291622, 295852, 299893, 304002, 308305, 312283, 316605, 320493, 324597, 328426, 332619, 336891, 340926, 345338, 349191, 353095, 357081, 360550, 364619, 368500, 372513, 376042, 379916, 384761, 388821, 392669, 396471, 399988}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_400k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_400k'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_400k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_400k'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0189471394}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 3843, 7645, 11711, 15350, 19278, 23264, 27811, 31908, 35724, 39644, 43411, 47364, 51515, 55794, 59774, 63966, 67804, 72182, 76063, 80006, 84006, 87487, 91210, 95018, 98577, 102521, 106573, 110600, 114604, 118503, 122677, 126536, 130701, 134482, 138322, 142198, 145763, 149827, 153864, 157850, 161793, 166081, 169976, 174145, 178094, 182014, 185972, 189931, 194132, 198345, 202267, 206482, 210356, 214332, 218446, 222254, 226844, 230947, 234913, 238713, 242595, 246483, 250717, 254501, 258549, 262642, 266768, 270663, 274828, 279019, 283393, 287478, 291622, 295852, 299893, 304002, 308305, 312283, 316605, 320493, 324597, 328426, 332619, 336891, 340926, 345338, 349191, 353095, 357081, 360550, 364619, 368500, 372513, 376042, 379916, 384761, 388821, 392669, 396471, 399988}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_500k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_500k'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_500k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_500k'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.00116640364}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 5261, 9684, 14521, 19420, 24575, 30037, 34793, 40034, 44801, 49510, 54587, 59271, 64489, 69564, 74609, 79555, 84210, 89596, 94899, 99876, 105128, 110072, 115259, 120200, 125589, 130699, 135982, 140798, 145871, 150759, 155645, 160418, 165689, 170573, 176080, 181201, 186663, 191524, 196397, 201477, 207001, 211780, 217109, 222146, 227161, 232273, 237122, 241705, 246180, 251375, 256382, 261496, 266091, 270864, 275903, 280925, 285350, 290303, 295938, 300879, 305971, 310888, 315645, 320428, 326214, 331179, 336155, 341505, 345951, 350522, 355216, 360241, 365617, 370814, 375422, 380468, 385981, 390503, 394894, 399869, 404532, 410065, 414847, 419609, 424472, 429713, 434391, 439379, 444452, 449567, 454854, 460008, 465391, 469952, 474842, 480334, 485167, 490249, 495330, 499993}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_500k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_500k'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_500k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_500k'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.00116640364}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 5261, 9684, 14521, 19420, 24575, 30037, 34793, 40034, 44801, 49510, 54587, 59271, 64489, 69564, 74609, 79555, 84210, 89596, 94899, 99876, 105128, 110072, 115259, 120200, 125589, 130699, 135982, 140798, 145871, 150759, 155645, 160418, 165689, 170573, 176080, 181201, 186663, 191524, 196397, 201477, 207001, 211780, 217109, 222146, 227161, 232273, 237122, 241705, 246180, 251375, 256382, 261496, 266091, 270864, 275903, 280925, 285350, 290303, 295938, 300879, 305971, 310888, 315645, 320428, 326214, 331179, 336155, 341505, 345951, 350522, 355216, 360241, 365617, 370814, 375422, 380468, 385981, 390503, 394894, 399869, 404532, 410065, 414847, 419609, 424472, 429713, 434391, 439379, 444452, 449567, 454854, 460008, 465391, 469952, 474842, 480334, 485167, 490249, 495330, 499993}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_500k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_500k'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_500k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_500k'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.00116640364}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 5261, 9684, 14521, 19420, 24575, 30037, 34793, 40034, 44801, 49510, 54587, 59271, 64489, 69564, 74609, 79555, 84210, 89596, 94899, 99876, 105128, 110072, 115259, 120200, 125589, 130699, 135982, 140798, 145871, 150759, 155645, 160418, 165689, 170573, 176080, 181201, 186663, 191524, 196397, 201477, 207001, 211780, 217109, 222146, 227161, 232273, 237122, 241705, 246180, 251375, 256382, 261496, 266091, 270864, 275903, 280925, 285350, 290303, 295938, 300879, 305971, 310888, 315645, 320428, 326214, 331179, 336155, 341505, 345951, 350522, 355216, 360241, 365617, 370814, 375422, 380468, 385981, 390503, 394894, 399869, 404532, 410065, 414847, 419609, 424472, 429713, 434391, 439379, 444452, 449567, 454854, 460008, 465391, 469952, 474842, 480334, 485167, 490249, 495330, 499993}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_500k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_500k'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_500k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_500k'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.00116640364}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 5261, 9684, 14521, 19420, 24575, 30037, 34793, 40034, 44801, 49510, 54587, 59271, 64489, 69564, 74609, 79555, 84210, 89596, 94899, 99876, 105128, 110072, 115259, 120200, 125589, 130699, 135982, 140798, 145871, 150759, 155645, 160418, 165689, 170573, 176080, 181201, 186663, 191524, 196397, 201477, 207001, 211780, 217109, 222146, 227161, 232273, 237122, 241705, 246180, 251375, 256382, 261496, 266091, 270864, 275903, 280925, 285350, 290303, 295938, 300879, 305971, 310888, 315645, 320428, 326214, 331179, 336155, 341505, 345951, 350522, 355216, 360241, 365617, 370814, 375422, 380468, 385981, 390503, 394894, 399869, 404532, 410065, 414847, 419609, 424472, 429713, 434391, 439379, 444452, 449567, 454854, 460008, 465391, 469952, 474842, 480334, 485167, 490249, 495330, 499993}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_500k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_500k'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_500k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_500k'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.00116640364}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 5261, 9684, 14521, 19420, 24575, 30037, 34793, 40034, 44801, 49510, 54587, 59271, 64489, 69564, 74609, 79555, 84210, 89596, 94899, 99876, 105128, 110072, 115259, 120200, 125589, 130699, 135982, 140798, 145871, 150759, 155645, 160418, 165689, 170573, 176080, 181201, 186663, 191524, 196397, 201477, 207001, 211780, 217109, 222146, 227161, 232273, 237122, 241705, 246180, 251375, 256382, 261496, 266091, 270864, 275903, 280925, 285350, 290303, 295938, 300879, 305971, 310888, 315645, 320428, 326214, 331179, 336155, 341505, 345951, 350522, 355216, 360241, 365617, 370814, 375422, 380468, 385981, 390503, 394894, 399869, 404532, 410065, 414847, 419609, 424472, 429713, 434391, 439379, 444452, 449567, 454854, 460008, 465391, 469952, 474842, 480334, 485167, 490249, 495330, 499993}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_600k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_600k'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_600k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_600k'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00888641737}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 5958, 12116, 18382, 23963, 29470, 35537, 41694, 47563, 53807, 59538, 65523, 71985, 77842, 83709, 90390, 96854, 102945, 109095, 115333, 121475, 127464, 133735, 139814, 145670, 151260, 157148, 163259, 169390, 175793, 181810, 187796, 193598, 200012, 205510, 211160, 217469, 223373, 228859, 234573, 240784, 247197, 253235, 259481, 265814, 271945, 277902, 284144, 289908, 295950, 301972, 308023, 314084, 320376, 325860, 331910, 337669, 344134, 350121, 355978, 362007, 368492, 374204, 380072, 385813, 392161, 397327, 403168, 409138, 415322, 421381, 427316, 433689, 439402, 444953, 450249, 456257, 461860, 467828, 474081, 480474, 485665, 491540, 497576, 504059, 509720, 515523, 522185, 527972, 534487, 540413, 546241, 552423, 557746, 563728, 568885, 574925, 581418, 587984, 594078, 599999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_600k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_600k'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_600k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_600k'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00888641737}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 5958, 12116, 18382, 23963, 29470, 35537, 41694, 47563, 53807, 59538, 65523, 71985, 77842, 83709, 90390, 96854, 102945, 109095, 115333, 121475, 127464, 133735, 139814, 145670, 151260, 157148, 163259, 169390, 175793, 181810, 187796, 193598, 200012, 205510, 211160, 217469, 223373, 228859, 234573, 240784, 247197, 253235, 259481, 265814, 271945, 277902, 284144, 289908, 295950, 301972, 308023, 314084, 320376, 325860, 331910, 337669, 344134, 350121, 355978, 362007, 368492, 374204, 380072, 385813, 392161, 397327, 403168, 409138, 415322, 421381, 427316, 433689, 439402, 444953, 450249, 456257, 461860, 467828, 474081, 480474, 485665, 491540, 497576, 504059, 509720, 515523, 522185, 527972, 534487, 540413, 546241, 552423, 557746, 563728, 568885, 574925, 581418, 587984, 594078, 599999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_600k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_600k'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_600k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_600k'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00888641737}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 5958, 12116, 18382, 23963, 29470, 35537, 41694, 47563, 53807, 59538, 65523, 71985, 77842, 83709, 90390, 96854, 102945, 109095, 115333, 121475, 127464, 133735, 139814, 145670, 151260, 157148, 163259, 169390, 175793, 181810, 187796, 193598, 200012, 205510, 211160, 217469, 223373, 228859, 234573, 240784, 247197, 253235, 259481, 265814, 271945, 277902, 284144, 289908, 295950, 301972, 308023, 314084, 320376, 325860, 331910, 337669, 344134, 350121, 355978, 362007, 368492, 374204, 380072, 385813, 392161, 397327, 403168, 409138, 415322, 421381, 427316, 433689, 439402, 444953, 450249, 456257, 461860, 467828, 474081, 480474, 485665, 491540, 497576, 504059, 509720, 515523, 522185, 527972, 534487, 540413, 546241, 552423, 557746, 563728, 568885, 574925, 581418, 587984, 594078, 599999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_600k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_600k'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_600k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_600k'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00888641737}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 5958, 12116, 18382, 23963, 29470, 35537, 41694, 47563, 53807, 59538, 65523, 71985, 77842, 83709, 90390, 96854, 102945, 109095, 115333, 121475, 127464, 133735, 139814, 145670, 151260, 157148, 163259, 169390, 175793, 181810, 187796, 193598, 200012, 205510, 211160, 217469, 223373, 228859, 234573, 240784, 247197, 253235, 259481, 265814, 271945, 277902, 284144, 289908, 295950, 301972, 308023, 314084, 320376, 325860, 331910, 337669, 344134, 350121, 355978, 362007, 368492, 374204, 380072, 385813, 392161, 397327, 403168, 409138, 415322, 421381, 427316, 433689, 439402, 444953, 450249, 456257, 461860, 467828, 474081, 480474, 485665, 491540, 497576, 504059, 509720, 515523, 522185, 527972, 534487, 540413, 546241, 552423, 557746, 563728, 568885, 574925, 581418, 587984, 594078, 599999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_600k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_600k'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_600k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_600k'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00888641737}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 5958, 12116, 18382, 23963, 29470, 35537, 41694, 47563, 53807, 59538, 65523, 71985, 77842, 83709, 90390, 96854, 102945, 109095, 115333, 121475, 127464, 133735, 139814, 145670, 151260, 157148, 163259, 169390, 175793, 181810, 187796, 193598, 200012, 205510, 211160, 217469, 223373, 228859, 234573, 240784, 247197, 253235, 259481, 265814, 271945, 277902, 284144, 289908, 295950, 301972, 308023, 314084, 320376, 325860, 331910, 337669, 344134, 350121, 355978, 362007, 368492, 374204, 380072, 385813, 392161, 397327, 403168, 409138, 415322, 421381, 427316, 433689, 439402, 444953, 450249, 456257, 461860, 467828, 474081, 480474, 485665, 491540, 497576, 504059, 509720, 515523, 522185, 527972, 534487, 540413, 546241, 552423, 557746, 563728, 568885, 574925, 581418, 587984, 594078, 599999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_700k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_700k'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_700k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_700k'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.000511761638}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{29, 7376, 14502, 21453, 28406, 35460, 42346, 48435, 55245, 61897, 69544, 77151, 84643, 91621, 98717, 105442, 111645, 119061, 125936, 132629, 139534, 146153, 153441, 160487, 167580, 174439, 181216, 188218, 195169, 202337, 209587, 216585, 223526, 230869, 238094, 244257, 250776, 257632, 264076, 270852, 277978, 284652, 292011, 299318, 306165, 313991, 320804, 327924, 335672, 342586, 350116, 357545, 364634, 371467, 377677, 384911, 391347, 397968, 404567, 411404, 418903, 426057, 432745, 440160, 447423, 454341, 461416, 468771, 475398, 482159, 488733, 495793, 503237, 510130, 516842, 523994, 531608, 538898, 546042, 553254, 560043, 567383, 574520, 582374, 589410, 595831, 602163, 609385, 616104, 623263, 630783, 638157, 644576, 651168, 658179, 665685, 671999, 678717, 685213, 692264, 699975}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_700k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_700k'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_700k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_700k'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.000511761638}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{29, 7376, 14502, 21453, 28406, 35460, 42346, 48435, 55245, 61897, 69544, 77151, 84643, 91621, 98717, 105442, 111645, 119061, 125936, 132629, 139534, 146153, 153441, 160487, 167580, 174439, 181216, 188218, 195169, 202337, 209587, 216585, 223526, 230869, 238094, 244257, 250776, 257632, 264076, 270852, 277978, 284652, 292011, 299318, 306165, 313991, 320804, 327924, 335672, 342586, 350116, 357545, 364634, 371467, 377677, 384911, 391347, 397968, 404567, 411404, 418903, 426057, 432745, 440160, 447423, 454341, 461416, 468771, 475398, 482159, 488733, 495793, 503237, 510130, 516842, 523994, 531608, 538898, 546042, 553254, 560043, 567383, 574520, 582374, 589410, 595831, 602163, 609385, 616104, 623263, 630783, 638157, 644576, 651168, 658179, 665685, 671999, 678717, 685213, 692264, 699975}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_700k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_700k'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_700k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_700k'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.000511761638}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{29, 7376, 14502, 21453, 28406, 35460, 42346, 48435, 55245, 61897, 69544, 77151, 84643, 91621, 98717, 105442, 111645, 119061, 125936, 132629, 139534, 146153, 153441, 160487, 167580, 174439, 181216, 188218, 195169, 202337, 209587, 216585, 223526, 230869, 238094, 244257, 250776, 257632, 264076, 270852, 277978, 284652, 292011, 299318, 306165, 313991, 320804, 327924, 335672, 342586, 350116, 357545, 364634, 371467, 377677, 384911, 391347, 397968, 404567, 411404, 418903, 426057, 432745, 440160, 447423, 454341, 461416, 468771, 475398, 482159, 488733, 495793, 503237, 510130, 516842, 523994, 531608, 538898, 546042, 553254, 560043, 567383, 574520, 582374, 589410, 595831, 602163, 609385, 616104, 623263, 630783, 638157, 644576, 651168, 658179, 665685, 671999, 678717, 685213, 692264, 699975}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_700k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_700k'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_700k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_700k'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.000511761638}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{29, 7376, 14502, 21453, 28406, 35460, 42346, 48435, 55245, 61897, 69544, 77151, 84643, 91621, 98717, 105442, 111645, 119061, 125936, 132629, 139534, 146153, 153441, 160487, 167580, 174439, 181216, 188218, 195169, 202337, 209587, 216585, 223526, 230869, 238094, 244257, 250776, 257632, 264076, 270852, 277978, 284652, 292011, 299318, 306165, 313991, 320804, 327924, 335672, 342586, 350116, 357545, 364634, 371467, 377677, 384911, 391347, 397968, 404567, 411404, 418903, 426057, 432745, 440160, 447423, 454341, 461416, 468771, 475398, 482159, 488733, 495793, 503237, 510130, 516842, 523994, 531608, 538898, 546042, 553254, 560043, 567383, 574520, 582374, 589410, 595831, 602163, 609385, 616104, 623263, 630783, 638157, 644576, 651168, 658179, 665685, 671999, 678717, 685213, 692264, 699975}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_700k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_700k'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_700k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_700k'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.000511761638}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{29, 7376, 14502, 21453, 28406, 35460, 42346, 48435, 55245, 61897, 69544, 77151, 84643, 91621, 98717, 105442, 111645, 119061, 125936, 132629, 139534, 146153, 153441, 160487, 167580, 174439, 181216, 188218, 195169, 202337, 209587, 216585, 223526, 230869, 238094, 244257, 250776, 257632, 264076, 270852, 277978, 284652, 292011, 299318, 306165, 313991, 320804, 327924, 335672, 342586, 350116, 357545, 364634, 371467, 377677, 384911, 391347, 397968, 404567, 411404, 418903, 426057, 432745, 440160, 447423, 454341, 461416, 468771, 475398, 482159, 488733, 495793, 503237, 510130, 516842, 523994, 531608, 538898, 546042, 553254, 560043, 567383, 574520, 582374, 589410, 595831, 602163, 609385, 616104, 623263, 630783, 638157, 644576, 651168, 658179, 665685, 671999, 678717, 685213, 692264, 699975}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_800k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_800k'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_800k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_800k'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00364427851}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{23, 7774, 16016, 23473, 31348, 39462, 47345, 54873, 63729, 71743, 80066, 87208, 95677, 103973, 111792, 119357, 127118, 135004, 142854, 151024, 159971, 168404, 177508, 185933, 194190, 202207, 210283, 217595, 225323, 233199, 241066, 249001, 257245, 266031, 273415, 281257, 289514, 297941, 306709, 314715, 322861, 330165, 337979, 345955, 354829, 362431, 370063, 377682, 385414, 394428, 401786, 409831, 417537, 425221, 434299, 442325, 449377, 457466, 465707, 473999, 482285, 490327, 498298, 506032, 513374, 520932, 529200, 537109, 544852, 553458, 561303, 569829, 577647, 585363, 593617, 602019, 609771, 617503, 625119, 633081, 640982, 648599, 656231, 664149, 672602, 680502, 688703, 696533, 704186, 712387, 719761, 727586, 735343, 743374, 751043, 760207, 767856, 775725, 784145, 792335, 799975}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_800k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_800k'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_800k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_800k'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00364427851}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{23, 7774, 16016, 23473, 31348, 39462, 47345, 54873, 63729, 71743, 80066, 87208, 95677, 103973, 111792, 119357, 127118, 135004, 142854, 151024, 159971, 168404, 177508, 185933, 194190, 202207, 210283, 217595, 225323, 233199, 241066, 249001, 257245, 266031, 273415, 281257, 289514, 297941, 306709, 314715, 322861, 330165, 337979, 345955, 354829, 362431, 370063, 377682, 385414, 394428, 401786, 409831, 417537, 425221, 434299, 442325, 449377, 457466, 465707, 473999, 482285, 490327, 498298, 506032, 513374, 520932, 529200, 537109, 544852, 553458, 561303, 569829, 577647, 585363, 593617, 602019, 609771, 617503, 625119, 633081, 640982, 648599, 656231, 664149, 672602, 680502, 688703, 696533, 704186, 712387, 719761, 727586, 735343, 743374, 751043, 760207, 767856, 775725, 784145, 792335, 799975}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_800k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_800k'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_800k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_800k'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00364427851}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{23, 7774, 16016, 23473, 31348, 39462, 47345, 54873, 63729, 71743, 80066, 87208, 95677, 103973, 111792, 119357, 127118, 135004, 142854, 151024, 159971, 168404, 177508, 185933, 194190, 202207, 210283, 217595, 225323, 233199, 241066, 249001, 257245, 266031, 273415, 281257, 289514, 297941, 306709, 314715, 322861, 330165, 337979, 345955, 354829, 362431, 370063, 377682, 385414, 394428, 401786, 409831, 417537, 425221, 434299, 442325, 449377, 457466, 465707, 473999, 482285, 490327, 498298, 506032, 513374, 520932, 529200, 537109, 544852, 553458, 561303, 569829, 577647, 585363, 593617, 602019, 609771, 617503, 625119, 633081, 640982, 648599, 656231, 664149, 672602, 680502, 688703, 696533, 704186, 712387, 719761, 727586, 735343, 743374, 751043, 760207, 767856, 775725, 784145, 792335, 799975}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_800k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_800k'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_800k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_800k'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00364427851}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{23, 7774, 16016, 23473, 31348, 39462, 47345, 54873, 63729, 71743, 80066, 87208, 95677, 103973, 111792, 119357, 127118, 135004, 142854, 151024, 159971, 168404, 177508, 185933, 194190, 202207, 210283, 217595, 225323, 233199, 241066, 249001, 257245, 266031, 273415, 281257, 289514, 297941, 306709, 314715, 322861, 330165, 337979, 345955, 354829, 362431, 370063, 377682, 385414, 394428, 401786, 409831, 417537, 425221, 434299, 442325, 449377, 457466, 465707, 473999, 482285, 490327, 498298, 506032, 513374, 520932, 529200, 537109, 544852, 553458, 561303, 569829, 577647, 585363, 593617, 602019, 609771, 617503, 625119, 633081, 640982, 648599, 656231, 664149, 672602, 680502, 688703, 696533, 704186, 712387, 719761, 727586, 735343, 743374, 751043, 760207, 767856, 775725, 784145, 792335, 799975}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_800k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_800k'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_800k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_800k'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00364427851}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{23, 7774, 16016, 23473, 31348, 39462, 47345, 54873, 63729, 71743, 80066, 87208, 95677, 103973, 111792, 119357, 127118, 135004, 142854, 151024, 159971, 168404, 177508, 185933, 194190, 202207, 210283, 217595, 225323, 233199, 241066, 249001, 257245, 266031, 273415, 281257, 289514, 297941, 306709, 314715, 322861, 330165, 337979, 345955, 354829, 362431, 370063, 377682, 385414, 394428, 401786, 409831, 417537, 425221, 434299, 442325, 449377, 457466, 465707, 473999, 482285, 490327, 498298, 506032, 513374, 520932, 529200, 537109, 544852, 553458, 561303, 569829, 577647, 585363, 593617, 602019, 609771, 617503, 625119, 633081, 640982, 648599, 656231, 664149, 672602, 680502, 688703, 696533, 704186, 712387, 719761, 727586, 735343, 743374, 751043, 760207, 767856, 775725, 784145, 792335, 799975}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_900k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_900k'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_900k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_900k'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00545889838}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{72, 8899, 18885, 27496, 36382, 45032, 53388, 62861, 72434, 80858, 89456, 98773, 108157, 117056, 125803, 134483, 143120, 152080, 160080, 169160, 177542, 186705, 195405, 204566, 213498, 221565, 230262, 239125, 249069, 257284, 266025, 275008, 283614, 292775, 302730, 311663, 320819, 329767, 338048, 347664, 356004, 365671, 374154, 382140, 391390, 401529, 410894, 420320, 429361, 438386, 447867, 457611, 466666, 476120, 484819, 493395, 502839, 512044, 521098, 529963, 539094, 548008, 556426, 566412, 576123, 584769, 593825, 603677, 613803, 621733, 631488, 640720, 649338, 658988, 667874, 676793, 685724, 694724, 703406, 712676, 721246, 729646, 738099, 747071, 755935, 764239, 772746, 781294, 790496, 799075, 809355, 819017, 827469, 837030, 845903, 853967, 862903, 871965, 881445, 890188, 899989}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_900k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_900k'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_900k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_900k'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00545889838}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{72, 8899, 18885, 27496, 36382, 45032, 53388, 62861, 72434, 80858, 89456, 98773, 108157, 117056, 125803, 134483, 143120, 152080, 160080, 169160, 177542, 186705, 195405, 204566, 213498, 221565, 230262, 239125, 249069, 257284, 266025, 275008, 283614, 292775, 302730, 311663, 320819, 329767, 338048, 347664, 356004, 365671, 374154, 382140, 391390, 401529, 410894, 420320, 429361, 438386, 447867, 457611, 466666, 476120, 484819, 493395, 502839, 512044, 521098, 529963, 539094, 548008, 556426, 566412, 576123, 584769, 593825, 603677, 613803, 621733, 631488, 640720, 649338, 658988, 667874, 676793, 685724, 694724, 703406, 712676, 721246, 729646, 738099, 747071, 755935, 764239, 772746, 781294, 790496, 799075, 809355, 819017, 827469, 837030, 845903, 853967, 862903, 871965, 881445, 890188, 899989}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_900k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_900k'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_900k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_900k'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00545889838}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{72, 8899, 18885, 27496, 36382, 45032, 53388, 62861, 72434, 80858, 89456, 98773, 108157, 117056, 125803, 134483, 143120, 152080, 160080, 169160, 177542, 186705, 195405, 204566, 213498, 221565, 230262, 239125, 249069, 257284, 266025, 275008, 283614, 292775, 302730, 311663, 320819, 329767, 338048, 347664, 356004, 365671, 374154, 382140, 391390, 401529, 410894, 420320, 429361, 438386, 447867, 457611, 466666, 476120, 484819, 493395, 502839, 512044, 521098, 529963, 539094, 548008, 556426, 566412, 576123, 584769, 593825, 603677, 613803, 621733, 631488, 640720, 649338, 658988, 667874, 676793, 685724, 694724, 703406, 712676, 721246, 729646, 738099, 747071, 755935, 764239, 772746, 781294, 790496, 799075, 809355, 819017, 827469, 837030, 845903, 853967, 862903, 871965, 881445, 890188, 899989}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_900k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_900k'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_900k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_900k'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00545889838}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{72, 8899, 18885, 27496, 36382, 45032, 53388, 62861, 72434, 80858, 89456, 98773, 108157, 117056, 125803, 134483, 143120, 152080, 160080, 169160, 177542, 186705, 195405, 204566, 213498, 221565, 230262, 239125, 249069, 257284, 266025, 275008, 283614, 292775, 302730, 311663, 320819, 329767, 338048, 347664, 356004, 365671, 374154, 382140, 391390, 401529, 410894, 420320, 429361, 438386, 447867, 457611, 466666, 476120, 484819, 493395, 502839, 512044, 521098, 529963, 539094, 548008, 556426, 566412, 576123, 584769, 593825, 603677, 613803, 621733, 631488, 640720, 649338, 658988, 667874, 676793, 685724, 694724, 703406, 712676, 721246, 729646, 738099, 747071, 755935, 764239, 772746, 781294, 790496, 799075, 809355, 819017, 827469, 837030, 845903, 853967, 862903, 871965, 881445, 890188, 899989}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_900k'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_900k'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_900k'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_900k'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{-0.00545889838}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{72, 8899, 18885, 27496, 36382, 45032, 53388, 62861, 72434, 80858, 89456, 98773, 108157, 117056, 125803, 134483, 143120, 152080, 160080, 169160, 177542, 186705, 195405, 204566, 213498, 221565, 230262, 239125, 249069, 257284, 266025, 275008, 283614, 292775, 302730, 311663, 320819, 329767, 338048, 347664, 356004, 365671, 374154, 382140, 391390, 401529, 410894, 420320, 429361, 438386, 447867, 457611, 466666, 476120, 484819, 493395, 502839, 512044, 521098, 529963, 539094, 548008, 556426, 566412, 576123, 584769, 593825, 603677, 613803, 621733, 631488, 640720, 649338, 658988, 667874, 676793, 685724, 694724, 703406, 712676, 721246, 729646, 738099, 747071, 755935, 764239, 772746, 781294, 790496, 799075, 809355, 819017, 827469, 837030, 845903, 853967, 862903, 871965, 881445, 890188, 899989}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_1m'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_1m'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.00867218431}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{50, 10301, 19742, 29753, 40473, 50870, 60223, 69933, 79802, 89633, 99399, 108838, 118163, 129187, 138630, 148295, 159643, 169670, 179684, 189249, 199900, 210255, 220572, 230196, 239088, 250128, 259484, 269955, 280119, 289461, 298903, 308195, 318752, 328684, 338962, 348306, 358046, 368329, 377318, 386848, 397458, 407872, 417256, 427480, 436986, 446191, 455584, 466391, 476623, 486564, 497167, 507145, 517222, 526882, 536789, 548013, 558570, 569180, 579433, 588914, 598763, 609379, 619787, 630077, 640056, 649285, 659315, 669918, 681162, 691393, 701515, 712608, 722641, 733071, 743670, 753730, 762735, 772724, 783113, 793847, 803836, 813729, 822982, 832776, 842948, 853012, 861805, 871072, 881479, 890763, 901053, 911471, 921555, 931682, 942200, 951962, 962256, 971206, 981023, 990733, 999995}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_1m'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_1m'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.00867218431}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{50, 10301, 19742, 29753, 40473, 50870, 60223, 69933, 79802, 89633, 99399, 108838, 118163, 129187, 138630, 148295, 159643, 169670, 179684, 189249, 199900, 210255, 220572, 230196, 239088, 250128, 259484, 269955, 280119, 289461, 298903, 308195, 318752, 328684, 338962, 348306, 358046, 368329, 377318, 386848, 397458, 407872, 417256, 427480, 436986, 446191, 455584, 466391, 476623, 486564, 497167, 507145, 517222, 526882, 536789, 548013, 558570, 569180, 579433, 588914, 598763, 609379, 619787, 630077, 640056, 649285, 659315, 669918, 681162, 691393, 701515, 712608, 722641, 733071, 743670, 753730, 762735, 772724, 783113, 793847, 803836, 813729, 822982, 832776, 842948, 853012, 861805, 871072, 881479, 890763, 901053, 911471, 921555, 931682, 942200, 951962, 962256, 971206, 981023, 990733, 999995}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_1m'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_1m'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.00867218431}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{50, 10301, 19742, 29753, 40473, 50870, 60223, 69933, 79802, 89633, 99399, 108838, 118163, 129187, 138630, 148295, 159643, 169670, 179684, 189249, 199900, 210255, 220572, 230196, 239088, 250128, 259484, 269955, 280119, 289461, 298903, 308195, 318752, 328684, 338962, 348306, 358046, 368329, 377318, 386848, 397458, 407872, 417256, 427480, 436986, 446191, 455584, 466391, 476623, 486564, 497167, 507145, 517222, 526882, 536789, 548013, 558570, 569180, 579433, 588914, 598763, 609379, 619787, 630077, 640056, 649285, 659315, 669918, 681162, 691393, 701515, 712608, 722641, 733071, 743670, 753730, 762735, 772724, 783113, 793847, 803836, 813729, 822982, 832776, 842948, 853012, 861805, 871072, 881479, 890763, 901053, 911471, 921555, 931682, 942200, 951962, 962256, 971206, 981023, 990733, 999995}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_1m'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_1m'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.00867218431}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{50, 10301, 19742, 29753, 40473, 50870, 60223, 69933, 79802, 89633, 99399, 108838, 118163, 129187, 138630, 148295, 159643, 169670, 179684, 189249, 199900, 210255, 220572, 230196, 239088, 250128, 259484, 269955, 280119, 289461, 298903, 308195, 318752, 328684, 338962, 348306, 358046, 368329, 377318, 386848, 397458, 407872, 417256, 427480, 436986, 446191, 455584, 466391, 476623, 486564, 497167, 507145, 517222, 526882, 536789, 548013, 558570, 569180, 579433, 588914, 598763, 609379, 619787, 630077, 640056, 649285, 659315, 669918, 681162, 691393, 701515, 712608, 722641, 733071, 743670, 753730, 762735, 772724, 783113, 793847, 803836, 813729, 822982, 832776, 842948, 853012, 861805, 871072, 881479, 890763, 901053, 911471, 921555, 931682, 942200, 951962, 962256, 971206, 981023, 990733, 999995}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_1m'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_1m'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.00867218431}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{50, 10301, 19742, 29753, 40473, 50870, 60223, 69933, 79802, 89633, 99399, 108838, 118163, 129187, 138630, 148295, 159643, 169670, 179684, 189249, 199900, 210255, 220572, 230196, 239088, 250128, 259484, 269955, 280119, 289461, 298903, 308195, 318752, 328684, 338962, 348306, 358046, 368329, 377318, 386848, 397458, 407872, 417256, 427480, 436986, 446191, 455584, 466391, 476623, 486564, 497167, 507145, 517222, 526882, 536789, 548013, 558570, 569180, 579433, 588914, 598763, 609379, 619787, 630077, 640056, 649285, 659315, 669918, 681162, 691393, 701515, 712608, 722641, 733071, 743670, 753730, 762735, 772724, 783113, 793847, 803836, 813729, 822982, 832776, 842948, 853012, 861805, 871072, 881479, 890763, 901053, 911471, 921555, 931682, 942200, 951962, 962256, 971206, 981023, 990733, 999995}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_1update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1update'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_1update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1update'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0862450376}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{3, 988, 2000, 3010, 4079, 5027, 5987, 6990, 7949, 8928, 9973, 11033, 12023, 12940, 13870, 14932, 15913, 16936, 17963, 18941, 19951, 20877, 21840, 22853, 23912, 24903, 25864, 26757, 27680, 28678, 29640, 30730, 31717, 32670, 33709, 34697, 35689, 36819, 37834, 38930, 40007, 40949, 41926, 42900, 43952, 44948, 45943, 46995, 47989, 49047, 50095, 51131, 52125, 53094, 54086, 55052, 56038, 57017, 58036, 59016, 59954, 60913, 61925, 62928, 64009, 64959, 65921, 66946, 68004, 68963, 69997, 71014, 71957, 73015, 74070, 75099, 76208, 77130, 78096, 79115, 80068, 81057, 81996, 82982, 83976, 84936, 85912, 86917, 87921, 89047, 90020, 90976, 92051, 93035, 93984, 94908, 95852, 96936, 97949, 98993, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_1update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1update'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_1update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1update'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0862450376}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{4, 989, 2001, 3011, 4080, 5028, 5988, 6991, 7950, 8929, 9974, 11034, 12024, 12941, 13871, 14933, 15914, 16937, 17964, 18942, 19952, 20878, 21841, 22854, 23913, 24904, 25865, 26758, 27681, 28679, 29641, 30731, 31718, 32671, 33710, 34698, 35690, 36820, 37835, 38931, 40008, 40950, 41927, 42901, 43953, 44949, 45944, 46996, 47990, 49048, 50096, 51132, 52126, 53095, 54087, 55053, 56039, 57018, 58037, 59017, 59955, 60914, 61926, 62929, 64010, 64960, 65922, 66947, 68005, 68964, 69998, 71015, 71958, 73016, 74071, 75100, 76209, 77131, 78097, 79116, 80069, 81058, 81997, 82983, 83977, 84937, 85913, 86918, 87922, 89048, 90021, 90977, 92052, 93036, 93985, 94909, 95853, 96937, 97950, 98994, 100000}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_1update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1update'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_1update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1update'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0862450376}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{3, 988, 2000, 3010, 4079, 5027, 5987, 6990, 7949, 8928, 9973, 11033, 12023, 12940, 13870, 14932, 15913, 16936, 17963, 18941, 19951, 20877, 21840, 22853, 23912, 24903, 25864, 26757, 27680, 28678, 29640, 30730, 31717, 32670, 33709, 34697, 35689, 36819, 37834, 38930, 40007, 40949, 41926, 42900, 43952, 44948, 45943, 46995, 47989, 49047, 50095, 51131, 52125, 53094, 54086, 55052, 56038, 57017, 58036, 59016, 59954, 60913, 61925, 62928, 64009, 64959, 65921, 66946, 68004, 68963, 69997, 71014, 71957, 73015, 74070, 75099, 76208, 77130, 78096, 79115, 80068, 81057, 81996, 82982, 83976, 84936, 85912, 86917, 87921, 89047, 90020, 90976, 92051, 93035, 93984, 94908, 95852, 96936, 97949, 98993, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_1update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1update'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_1update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1update'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0862450376}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{3, 988, 2000, 3010, 4079, 5027, 5987, 6990, 7949, 8928, 9973, 11033, 12023, 12940, 13870, 14932, 15913, 16936, 17963, 18941, 19951, 20877, 21840, 22853, 23912, 24903, 25864, 26757, 27680, 28678, 29640, 30730, 31717, 32670, 33709, 34697, 35689, 36819, 37834, 38930, 40007, 40949, 41926, 42900, 43952, 44948, 45943, 46995, 47989, 49047, 50095, 51131, 52125, 53094, 54086, 55052, 56038, 57017, 58036, 59016, 59954, 60913, 61925, 62928, 64009, 64959, 65921, 66946, 68004, 68963, 69997, 71014, 71957, 73015, 74070, 75099, 76208, 77130, 78096, 79115, 80068, 81057, 81996, 82982, 83976, 84936, 85912, 86917, 87921, 89047, 90020, 90976, 92051, 93035, 93984, 94908, 95852, 96936, 97949, 98993, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_1update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1update'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_1update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1update'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0862450376}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{3, 988, 2000, 3010, 4079, 5027, 5987, 6990, 7949, 8928, 9973, 11033, 12023, 12940, 13870, 14932, 15913, 16936, 17963, 18941, 19951, 20877, 21840, 22853, 23912, 24903, 25864, 26757, 27680, 28678, 29640, 30730, 31717, 32670, 33709, 34697, 35689, 36819, 37834, 38930, 40007, 40949, 41926, 42900, 43952, 44948, 45943, 46995, 47989, 49047, 50095, 51131, 52125, 53094, 54086, 55052, 56038, 57017, 58036, 59016, 59954, 60913, 61925, 62928, 64009, 64959, 65921, 66946, 68004, 68963, 69997, 71014, 71957, 73015, 74070, 75099, 76208, 77130, 78096, 79115, 80068, 81057, 81996, 82982, 83976, 84936, 85912, 86917, 87921, 89047, 90020, 90976, 92051, 93035, 93984, 94908, 95852, 96936, 97949, 98993, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_2update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2update'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_2update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2update'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0926508233}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 1018, 2074, 3138, 4111, 5034, 5905, 6871, 7773, 8737, 9748, 10877, 11919, 12836, 13848, 14843, 15824, 16948, 17935, 18895, 19896, 20849, 21921, 22828, 23826, 24756, 25658, 26707, 27798, 28759, 29746, 30693, 31611, 32619, 33669, 34622, 35550, 36506, 37516, 38575, 39546, 40555, 41587, 42591, 43581, 44584, 45618, 46627, 47630, 48702, 49709, 50704, 51779, 52755, 53811, 54812, 55856, 56935, 57944, 58982, 60001, 61007, 62044, 63059, 64072, 65081, 66056, 67087, 68118, 69035, 69990, 70920, 71986, 72965, 73999, 74959, 75996, 76989, 77935, 78937, 79926, 80909, 81938, 82940, 83975, 84997, 85966, 86914, 87918, 88888, 89979, 90901, 91942, 92970, 94071, 95129, 96083, 97045, 98100, 99022, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_2update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2update'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_2update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2update'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0926508233}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{11, 1019, 2075, 3139, 4112, 5035, 5906, 6872, 7774, 8738, 9749, 10878, 11920, 12837, 13849, 14844, 15825, 16949, 17936, 18896, 19897, 20850, 21922, 22829, 23827, 24757, 25659, 26708, 27799, 28760, 29747, 30694, 31612, 32620, 33670, 34623, 35551, 36507, 37517, 38576, 39547, 40556, 41588, 42592, 43582, 44585, 45619, 46628, 47631, 48703, 49710, 50705, 51780, 52756, 53812, 54813, 55857, 56936, 57945, 58983, 60002, 61008, 62045, 63060, 64073, 65082, 66057, 67088, 68119, 69036, 69991, 70921, 71987, 72966, 74000, 74960, 75997, 76990, 77936, 78938, 79927, 80910, 81939, 82941, 83976, 84998, 85967, 86915, 87919, 88889, 89980, 90902, 91943, 92971, 94072, 95130, 96084, 97046, 98101, 99023, 100000}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_2update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2update'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_2update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2update'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0926508233}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{11, 1019, 2075, 3139, 4112, 5035, 5906, 6872, 7774, 8738, 9749, 10878, 11920, 12837, 13849, 14844, 15825, 16949, 17936, 18896, 19897, 20850, 21922, 22829, 23827, 24757, 25659, 26708, 27799, 28760, 29747, 30694, 31612, 32620, 33670, 34623, 35551, 36507, 37517, 38576, 39547, 40556, 41588, 42592, 43582, 44585, 45619, 46628, 47631, 48703, 49710, 50705, 51780, 52756, 53812, 54813, 55857, 56936, 57945, 58983, 60002, 61008, 62045, 63060, 64073, 65082, 66057, 67088, 68119, 69036, 69991, 70921, 71987, 72966, 74000, 74960, 75997, 76990, 77936, 78938, 79927, 80910, 81939, 82941, 83976, 84998, 85967, 86915, 87919, 88889, 89980, 90902, 91943, 92971, 94072, 95130, 96084, 97046, 98101, 99023, 100000}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_2update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2update'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_2update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2update'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0926508233}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 1018, 2074, 3138, 4111, 5034, 5905, 6871, 7773, 8737, 9748, 10877, 11919, 12836, 13848, 14843, 15824, 16948, 17935, 18895, 19896, 20849, 21921, 22828, 23826, 24756, 25658, 26707, 27798, 28759, 29746, 30693, 31611, 32619, 33669, 34622, 35550, 36506, 37516, 38575, 39546, 40555, 41587, 42591, 43581, 44584, 45618, 46627, 47630, 48702, 49709, 50704, 51779, 52755, 53811, 54812, 55856, 56935, 57944, 58982, 60001, 61007, 62044, 63059, 64072, 65081, 66056, 67087, 68118, 69035, 69990, 70920, 71986, 72965, 73999, 74959, 75996, 76989, 77935, 78937, 79926, 80909, 81938, 82940, 83975, 84997, 85966, 86914, 87918, 88888, 89979, 90901, 91942, 92970, 94071, 95129, 96083, 97045, 98100, 99022, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_2update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2update'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_2update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2update'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0926508233}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{10, 1018, 2074, 3138, 4111, 5034, 5905, 6871, 7773, 8737, 9748, 10877, 11919, 12836, 13848, 14843, 15824, 16948, 17935, 18895, 19896, 20849, 21921, 22828, 23826, 24756, 25658, 26707, 27798, 28759, 29746, 30693, 31611, 32619, 33669, 34622, 35550, 36506, 37516, 38575, 39546, 40555, 41587, 42591, 43581, 44584, 45618, 46627, 47630, 48702, 49709, 50704, 51779, 52755, 53811, 54812, 55856, 56935, 57944, 58982, 60001, 61007, 62044, 63059, 64072, 65081, 66056, 67087, 68118, 69035, 69990, 70920, 71986, 72965, 73999, 74959, 75996, 76989, 77935, 78937, 79926, 80909, 81938, 82940, 83975, 84997, 85966, 86914, 87918, 88888, 89979, 90901, 91942, 92970, 94071, 95129, 96083, 97045, 98100, 99022, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_3update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3update'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_3update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3update'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0909232274}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{3, 978, 1937, 2882, 3883, 4806, 5922, 6917, 7890, 8900, 9917, 10923, 11873, 12884, 13898, 14881, 15872, 16886, 17882, 18885, 19870, 20851, 21857, 22873, 23873, 24851, 25896, 26851, 27910, 28837, 29847, 30911, 31924, 32959, 33880, 34848, 35861, 36797, 37768, 38789, 39763, 40750, 41762, 42763, 43823, 44783, 45764, 46833, 47848, 48853, 49901, 50881, 51841, 52787, 53702, 54763, 55774, 56767, 57788, 58815, 59898, 60893, 61879, 62865, 63866, 64898, 65868, 66822, 67788, 68822, 69871, 70823, 71779, 72772, 73711, 74723, 75658, 76721, 77658, 78713, 79731, 80708, 81725, 82735, 83749, 84665, 85710, 86716, 87786, 88861, 89856, 90848, 91874, 92882, 94028, 94966, 95910, 96918, 97981, 98967, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_3update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3update'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_3update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3update'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0909232274}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{4, 979, 1938, 2883, 3884, 4807, 5923, 6918, 7891, 8901, 9918, 10924, 11874, 12885, 13899, 14882, 15873, 16887, 17883, 18886, 19871, 20852, 21858, 22874, 23874, 24852, 25897, 26852, 27911, 28838, 29848, 30912, 31925, 32960, 33881, 34849, 35862, 36798, 37769, 38790, 39764, 40751, 41763, 42764, 43824, 44784, 45765, 46834, 47849, 48854, 49902, 50882, 51842, 52788, 53703, 54764, 55775, 56768, 57789, 58816, 59899, 60894, 61880, 62866, 63867, 64899, 65869, 66823, 67789, 68823, 69872, 70824, 71780, 72773, 73712, 74724, 75659, 76722, 77659, 78714, 79732, 80709, 81726, 82736, 83750, 84666, 85711, 86717, 87787, 88862, 89857, 90849, 91875, 92883, 94029, 94967, 95911, 96919, 97982, 98968, 100000}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_3update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3update'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_3update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3update'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0909232274}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{4, 979, 1938, 2883, 3884, 4807, 5923, 6918, 7891, 8901, 9918, 10924, 11874, 12885, 13899, 14882, 15873, 16887, 17883, 18886, 19871, 20852, 21858, 22874, 23874, 24852, 25897, 26852, 27911, 28838, 29848, 30912, 31925, 32960, 33881, 34849, 35862, 36798, 37769, 38790, 39764, 40751, 41763, 42764, 43824, 44784, 45765, 46834, 47849, 48854, 49902, 50882, 51842, 52788, 53703, 54764, 55775, 56768, 57789, 58816, 59899, 60894, 61880, 62866, 63867, 64899, 65869, 66823, 67789, 68823, 69872, 70824, 71780, 72773, 73712, 74724, 75659, 76722, 77659, 78714, 79732, 80709, 81726, 82736, 83750, 84666, 85711, 86717, 87787, 88862, 89857, 90849, 91875, 92883, 94029, 94967, 95911, 96919, 97982, 98968, 100000}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_3update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3update'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_3update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3update'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0909232274}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{4, 979, 1938, 2883, 3884, 4807, 5923, 6918, 7891, 8901, 9918, 10924, 11874, 12885, 13899, 14882, 15873, 16887, 17883, 18886, 19871, 20852, 21858, 22874, 23874, 24852, 25897, 26852, 27911, 28838, 29848, 30912, 31925, 32960, 33881, 34849, 35862, 36798, 37769, 38790, 39764, 40751, 41763, 42764, 43824, 44784, 45765, 46834, 47849, 48854, 49902, 50882, 51842, 52788, 53703, 54764, 55775, 56768, 57789, 58816, 59899, 60894, 61880, 62866, 63867, 64899, 65869, 66823, 67789, 68823, 69872, 70824, 71780, 72773, 73712, 74724, 75659, 76722, 77659, 78714, 79732, 80709, 81726, 82736, 83750, 84666, 85711, 86717, 87787, 88862, 89857, 90849, 91875, 92883, 94029, 94967, 95911, 96919, 97982, 98968, 100000}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_3update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3update'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_3update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3update'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0909232274}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{3, 978, 1937, 2882, 3883, 4806, 5922, 6917, 7890, 8900, 9917, 10923, 11873, 12884, 13898, 14881, 15872, 16886, 17882, 18885, 19870, 20851, 21857, 22873, 23873, 24851, 25896, 26851, 27910, 28837, 29847, 30911, 31924, 32959, 33880, 34848, 35861, 36797, 37768, 38789, 39763, 40750, 41762, 42763, 43823, 44783, 45764, 46833, 47848, 48853, 49901, 50881, 51841, 52787, 53702, 54763, 55774, 56767, 57788, 58815, 59898, 60893, 61879, 62865, 63866, 64898, 65868, 66822, 67788, 68822, 69871, 70823, 71779, 72772, 73711, 74723, 75658, 76721, 77658, 78713, 79731, 80708, 81725, 82735, 83749, 84665, 85710, 86716, 87786, 88861, 89856, 90848, 91874, 92882, 94028, 94966, 95910, 96918, 97981, 98967, 99999}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_4update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4update'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_4update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4update'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0806034207}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{5, 941, 1976, 3026, 4069, 5025, 6011, 7026, 8035, 8996, 10065, 11100, 11986, 13056, 14097, 15092, 16095, 17100, 18145, 19120, 20137, 21189, 22058, 23062, 24005, 24957, 25894, 26911, 27913, 28904, 29902, 30895, 31885, 32928, 34020, 34979, 35925, 36892, 37987, 38979, 40024, 40954, 42027, 43040, 44102, 45104, 46153, 47188, 48176, 49116, 50122, 51053, 52016, 52917, 53897, 55020, 56062, 57051, 58102, 59038, 59907, 60871, 61866, 62878, 63847, 64854, 65881, 66917, 67948, 68957, 69957, 70944, 71961, 72860, 73881, 74806, 75826, 76781, 77783, 78859, 79946, 80896, 81883, 82927, 83937, 84991, 86006, 87010, 87992, 89075, 90064, 91069, 92061, 93089, 94102, 95058, 96006, 97017, 98047, 99032, 100000}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_4update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4update'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_4update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4update'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0806034207}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 942, 1977, 3027, 4070, 5026, 6012, 7027, 8036, 8997, 10066, 11101, 11987, 13057, 14098, 15093, 16096, 17101, 18146, 19121, 20138, 21190, 22059, 23063, 24006, 24958, 25895, 26912, 27914, 28905, 29903, 30896, 31886, 32929, 34021, 34980, 35926, 36893, 37988, 38980, 40025, 40955, 42028, 43041, 44103, 45105, 46154, 47189, 48177, 49117, 50123, 51054, 52017, 52918, 53898, 55021, 56063, 57052, 58103, 59039, 59908, 60872, 61867, 62879, 63848, 64855, 65882, 66918, 67949, 68958, 69958, 70945, 71962, 72861, 73882, 74807, 75827, 76782, 77784, 78860, 79947, 80897, 81884, 82928, 83938, 84992, 86007, 87011, 87993, 89076, 90065, 91070, 92062, 93090, 94103, 95059, 96007, 97018, 98048, 99033, 100001}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_4update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4update'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_4update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4update'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0806034207}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 942, 1977, 3027, 4070, 5026, 6012, 7027, 8036, 8997, 10066, 11101, 11987, 13057, 14098, 15093, 16096, 17101, 18146, 19121, 20138, 21190, 22059, 23063, 24006, 24958, 25895, 26912, 27914, 28905, 29903, 30896, 31886, 32929, 34021, 34980, 35926, 36893, 37988, 38980, 40025, 40955, 42028, 43041, 44103, 45105, 46154, 47189, 48177, 49117, 50123, 51054, 52017, 52918, 53898, 55021, 56063, 57052, 58103, 59039, 59908, 60872, 61867, 62879, 63848, 64855, 65882, 66918, 67949, 68958, 69958, 70945, 71962, 72861, 73882, 74807, 75827, 76782, 77784, 78860, 79947, 80897, 81884, 82928, 83938, 84992, 86007, 87011, 87993, 89076, 90065, 91070, 92062, 93090, 94103, 95059, 96007, 97018, 98048, 99033, 100001}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_4update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4update'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_4update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4update'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0806034207}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 942, 1977, 3027, 4070, 5026, 6012, 7027, 8036, 8997, 10066, 11101, 11987, 13057, 14098, 15093, 16096, 17101, 18146, 19121, 20138, 21190, 22059, 23063, 24006, 24958, 25895, 26912, 27914, 28905, 29903, 30896, 31886, 32929, 34021, 34980, 35926, 36893, 37988, 38980, 40025, 40955, 42028, 43041, 44103, 45105, 46154, 47189, 48177, 49117, 50123, 51054, 52017, 52918, 53898, 55021, 56063, 57052, 58103, 59039, 59908, 60872, 61867, 62879, 63848, 64855, 65882, 66918, 67949, 68958, 69958, 70945, 71962, 72861, 73882, 74807, 75827, 76782, 77784, 78860, 79947, 80897, 81884, 82928, 83938, 84992, 86007, 87011, 87993, 89076, 90065, 91070, 92062, 93090, 94103, 95059, 96007, 97018, 98048, 99033, 100001}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_4update'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4update'::regclass and a.attname = 'v4'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_4update'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4update'::regclass and a.attname = 'v4'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0806034207}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 942, 1977, 3027, 4070, 5026, 6012, 7027, 8036, 8997, 10066, 11101, 11987, 13057, 14098, 15093, 16096, 17101, 18146, 19121, 20138, 21190, 22059, 23063, 24006, 24958, 25895, 26912, 27914, 28905, 29903, 30896, 31886, 32929, 34021, 34980, 35926, 36893, 37988, 38980, 40025, 40955, 42028, 43041, 44103, 45105, 46154, 47189, 48177, 49117, 50123, 51054, 52017, 52918, 53898, 55021, 56063, 57052, 58103, 59039, 59908, 60872, 61867, 62879, 63848, 64855, 65882, 66918, 67949, 68958, 69958, 70945, 71962, 72861, 73882, 74807, 75827, 76782, 77784, 78860, 79947, 80897, 81884, 82928, 83938, 84992, 86007, 87011, 87993, 89076, 90065, 91070, 92062, 93090, 94103, 95059, 96007, 97018, 98048, 99033, 100001}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_1column'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1column'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_1column'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_1column'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0900141001}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{3, 1000, 2000, 2977, 3979, 5012, 6042, 6969, 8040, 9069, 10093, 11062, 12036, 13108, 14206, 15240, 16235, 17280, 18308, 19254, 20370, 21405, 22300, 23283, 24335, 25401, 26380, 27508, 28532, 29442, 30429, 31389, 32343, 33337, 34365, 35409, 36503, 37546, 38598, 39549, 40586, 41550, 42621, 43581, 44617, 45629, 46686, 47644, 48580, 49533, 50551, 51622, 52580, 53571, 54504, 55482, 56542, 57546, 58587, 59485, 60472, 61385, 62371, 63352, 64389, 65268, 66281, 67272, 68236, 69212, 70216, 71221, 72208, 73149, 74121, 75086, 76066, 76899, 77863, 78855, 79982, 80976, 81917, 82942, 83956, 84967, 85936, 86981, 88040, 88982, 90078, 91082, 92112, 93092, 94027, 95012, 96045, 97002, 98046, 99047, 100000}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_2column'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2column'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_2column'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2column'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0989670679}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 1009, 1980, 2897, 3935, 4965, 5900, 6914, 7720, 8723, 9721, 10784, 11859, 12785, 13792, 14787, 15780, 16819, 17782, 18835, 19857, 20736, 21843, 22862, 23882, 24912, 25964, 26956, 27873, 28904, 29900, 30985, 32019, 33076, 34087, 35084, 36119, 37089, 38047, 39145, 40229, 41243, 42238, 43222, 44155, 45185, 46181, 47280, 48270, 49281, 50339, 51370, 52332, 53295, 54333, 55226, 56180, 57159, 58107, 59022, 59992, 60951, 61935, 62861, 63890, 64973, 66022, 67003, 68044, 68968, 69953, 70940, 72004, 72953, 73973, 75046, 76050, 77023, 77977, 79008, 80052, 81073, 82069, 83076, 84028, 84997, 85981, 87038, 88034, 89002, 90052, 91064, 92000, 92989, 94006, 94961, 95992, 96988, 98059, 99047, 100000}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_2column'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2column'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_2column'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_2column'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0989670679}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{2, 1009, 1980, 2897, 3935, 4965, 5900, 6914, 7720, 8723, 9721, 10784, 11859, 12785, 13792, 14787, 15780, 16819, 17782, 18835, 19857, 20736, 21843, 22862, 23882, 24912, 25964, 26956, 27873, 28904, 29900, 30985, 32019, 33076, 34087, 35084, 36119, 37089, 38047, 39145, 40229, 41243, 42238, 43222, 44155, 45185, 46181, 47280, 48270, 49281, 50339, 51370, 52332, 53295, 54333, 55226, 56180, 57159, 58107, 59022, 59992, 60951, 61935, 62861, 63890, 64973, 66022, 67003, 68044, 68968, 69953, 70940, 72004, 72953, 73973, 75046, 76050, 77023, 77977, 79008, 80052, 81073, 82069, 83076, 84028, 84997, 85981, 87038, 88034, 89002, 90052, 91064, 92000, 92989, 94006, 94961, 95992, 96988, 98059, 99047, 100000}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_3column'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3column'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_3column'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3column'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0827239379}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 1027, 1992, 2918, 4052, 5079, 6107, 7109, 8081, 9132, 10079, 11087, 12130, 13061, 14031, 14932, 15971, 16925, 17872, 18883, 19810, 20797, 21736, 22707, 23671, 24657, 25605, 26499, 27458, 28515, 29463, 30394, 31407, 32424, 33441, 34518, 35514, 36566, 37612, 38608, 39730, 40823, 41861, 42840, 43871, 44892, 45824, 46879, 47909, 48908, 49934, 50895, 51983, 53004, 53965, 54943, 55893, 56869, 57857, 58866, 59834, 60853, 61881, 62865, 63832, 64825, 65835, 66837, 67747, 68829, 69858, 70950, 71957, 72944, 73929, 75055, 76100, 77193, 78179, 79146, 80159, 81179, 82171, 83175, 84156, 85112, 86140, 87126, 88191, 89199, 90095, 91163, 92131, 93128, 94195, 95088, 96058, 96978, 97988, 99063, 99989}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_3column'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3column'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_3column'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3column'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0827239379}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 1027, 1992, 2918, 4052, 5079, 6107, 7109, 8081, 9132, 10079, 11087, 12130, 13061, 14031, 14932, 15971, 16925, 17872, 18883, 19810, 20797, 21736, 22707, 23671, 24657, 25605, 26499, 27458, 28515, 29463, 30394, 31407, 32424, 33441, 34518, 35514, 36566, 37612, 38608, 39730, 40823, 41861, 42840, 43871, 44892, 45824, 46879, 47909, 48908, 49934, 50895, 51983, 53004, 53965, 54943, 55893, 56869, 57857, 58866, 59834, 60853, 61881, 62865, 63832, 64825, 65835, 66837, 67747, 68829, 69858, 70950, 71957, 72944, 73929, 75055, 76100, 77193, 78179, 79146, 80159, 81179, 82171, 83175, 84156, 85112, 86140, 87126, 88191, 89199, 90095, 91163, 92131, 93128, 94195, 95088, 96058, 96978, 97988, 99063, 99989}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_3column'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3column'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_3column'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_3column'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.0827239379}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{1, 1027, 1992, 2918, 4052, 5079, 6107, 7109, 8081, 9132, 10079, 11087, 12130, 13061, 14031, 14932, 15971, 16925, 17872, 18883, 19810, 20797, 21736, 22707, 23671, 24657, 25605, 26499, 27458, 28515, 29463, 30394, 31407, 32424, 33441, 34518, 35514, 36566, 37612, 38608, 39730, 40823, 41861, 42840, 43871, 44892, 45824, 46879, 47909, 48908, 49934, 50895, 51983, 53004, 53965, 54943, 55893, 56869, 57857, 58866, 59834, 60853, 61881, 62865, 63832, 64825, 65835, 66837, 67747, 68829, 69858, 70950, 71957, 72944, 73929, 75055, 76100, 77193, 78179, 79146, 80159, 81179, 82171, 83175, 84156, 85112, 86140, 87126, 88191, 89199, 90095, 91163, 92131, 93128, 94195, 95088, 96058, 96978, 97988, 99063, 99989}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_4column'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4column'::regclass and a.attname = 'id'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_4column'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4column'::regclass and a.attname = 'id'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.090488717}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 1027, 1974, 3030, 4109, 5088, 6187, 7141, 8137, 9148, 10106, 11173, 12144, 13194, 14173, 15167, 16148, 17201, 18258, 19217, 20207, 21255, 22214, 23271, 24321, 25244, 26272, 27250, 28261, 29280, 30240, 31223, 32181, 33109, 34157, 35048, 36087, 37076, 38060, 39031, 39941, 40858, 41885, 42927, 43895, 44819, 45815, 46781, 47703, 48635, 49670, 50737, 51753, 52667, 53699, 54701, 55804, 56807, 57738, 58751, 59744, 60697, 61659, 62723, 63744, 64775, 65777, 66739, 67797, 68807, 69841, 70807, 71906, 72845, 73857, 74821, 75788, 76700, 77675, 78676, 79712, 80725, 81704, 82689, 83731, 84803, 85797, 86803, 87724, 88750, 89833, 90823, 91852, 92911, 93896, 94889, 95892, 96959, 97972, 98954, 99998}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_4column'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4column'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_4column'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4column'::regclass and a.attname = 'v1'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.090488717}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 1027, 1974, 3030, 4109, 5088, 6187, 7141, 8137, 9148, 10106, 11173, 12144, 13194, 14173, 15167, 16148, 17201, 18258, 19217, 20207, 21255, 22214, 23271, 24321, 25244, 26272, 27250, 28261, 29280, 30240, 31223, 32181, 33109, 34157, 35048, 36087, 37076, 38060, 39031, 39941, 40858, 41885, 42927, 43895, 44819, 45815, 46781, 47703, 48635, 49670, 50737, 51753, 52667, 53699, 54701, 55804, 56807, 57738, 58751, 59744, 60697, 61659, 62723, 63744, 64775, 65777, 66739, 67797, 68807, 69841, 70807, 71906, 72845, 73857, 74821, 75788, 76700, 77675, 78676, 79712, 80725, 81704, 82689, 83731, 84803, 85797, 86803, 87724, 88750, 89833, 90823, 91852, 92911, 93896, 94889, 95892, 96959, 97972, 98954, 99998}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_4column'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4column'::regclass and a.attname = 'v2'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_4column'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4column'::regclass and a.attname = 'v2'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.090488717}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 1027, 1974, 3030, 4109, 5088, 6187, 7141, 8137, 9148, 10106, 11173, 12144, 13194, 14173, 15167, 16148, 17201, 18258, 19217, 20207, 21255, 22214, 23271, 24321, 25244, 26272, 27250, 28261, 29280, 30240, 31223, 32181, 33109, 34157, 35048, 36087, 37076, 38060, 39031, 39941, 40858, 41885, 42927, 43895, 44819, 45815, 46781, 47703, 48635, 49670, 50737, 51753, 52667, 53699, 54701, 55804, 56807, 57738, 58751, 59744, 60697, 61659, 62723, 63744, 64775, 65777, 66739, 67797, 68807, 69841, 70807, 71906, 72845, 73857, 74821, 75788, 76700, 77675, 78676, 79712, 80725, 81704, 82689, 83731, 84803, 85797, 86803, 87724, 88750, 89833, 90823, 91852, 92911, 93896, 94889, 95892, 96959, 97972, 98954, 99998}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_100k_4column'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4column'::regclass and a.attname = 'v3'); +INSERT INTO pg_statistic VALUES ('public.t_range_100k_4column'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_100k_4column'::regclass and a.attname = 'v3'), False::boolean, 0::real, 4::integer, -1::real, 2::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 97::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], '{0.090488717}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{6, 1027, 1974, 3030, 4109, 5088, 6187, 7141, 8137, 9148, 10106, 11173, 12144, 13194, 14173, 15167, 16148, 17201, 18258, 19217, 20207, 21255, 22214, 23271, 24321, 25244, 26272, 27250, 28261, 29280, 30240, 31223, 32181, 33109, 34157, 35048, 36087, 37076, 38060, 39031, 39941, 40858, 41885, 42927, 43895, 44819, 45815, 46781, 47703, 48635, 49670, 50737, 51753, 52667, 53699, 54701, 55804, 56807, 57738, 58751, 59744, 60697, 61659, 62723, 63744, 64775, 65777, 66739, 67797, 68807, 69841, 70807, 71906, 72845, 73857, 74821, 75788, 76700, 77675, 78676, 79712, 80725, 81704, 82689, 83731, 84803, 85797, 86803, 87724, 88750, 89833, 90823, 91852, 92911, 93896, 94889, 95892, 96959, 97972, 98954, 99998}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_1m_4keys'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m_4keys'::regclass and a.attname = 'k1'); +INSERT INTO pg_statistic VALUES ('public.t_range_1m_4keys'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m_4keys'::regclass and a.attname = 'k1'), False::boolean, 0::real, 4::integer, 10::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.102133334, 0.101333335, 0.100766666, 0.100633331, 0.100599997, 0.0999666676, 0.0999666676, 0.0992333367, 0.0983333364, 0.0970333368}'::real[], '{0.0963156596}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{4, 10, 6, 5, 7, 3, 9, 1, 2, 8}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_1m_4keys'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m_4keys'::regclass and a.attname = 'k2'); +INSERT INTO pg_statistic VALUES ('public.t_range_1m_4keys'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m_4keys'::regclass and a.attname = 'k2'), False::boolean, 0::real, 4::integer, 20::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.0533999987, 0.0531666651, 0.0522333346, 0.0515666679, 0.0511000007, 0.0504666679, 0.0502999984, 0.0502333343, 0.0502333343, 0.0500666648, 0.0499666668, 0.0497666672, 0.0497666672, 0.0493000001, 0.0491000004, 0.0486666672, 0.0483999997, 0.048233334, 0.0472000018, 0.0468333326}'::real[], '{0.0578667633}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{7, 9, 6, 16, 17, 8, 11, 14, 20, 12, 13, 1, 10, 3, 2, 4, 19, 18, 5, 15}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_1m_4keys'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m_4keys'::regclass and a.attname = 'k3'); +INSERT INTO pg_statistic VALUES ('public.t_range_1m_4keys'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m_4keys'::regclass and a.attname = 'k3'), False::boolean, 0::real, 4::integer, 50::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.0221666675, 0.0217666663, 0.0213333331, 0.0212666672, 0.0209999997, 0.0209666658, 0.0209666658, 0.0208999999, 0.020866666, 0.0206000004, 0.0205333326, 0.0205000006, 0.0204666667, 0.0204000007, 0.0204000007, 0.0203333329, 0.0203333329, 0.0203000009, 0.020266667, 0.0202333331, 0.0201999992, 0.0201999992, 0.0200999994, 0.0200999994, 0.0200333335, 0.0199999996, 0.0199666675, 0.0198333338, 0.019766666, 0.019766666, 0.019733334, 0.0197000001, 0.0195666663, 0.0194666665, 0.0194333326, 0.0194333326, 0.0194333326, 0.0194000006, 0.0193666667, 0.0193666667, 0.0193333328, 0.0193333328, 0.0193000007, 0.0191333331, 0.0190333333, 0.0190333333, 0.0190333333, 0.018666666, 0.0184666663, 0.0182000007}'::real[], '{0.0300768502}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{19, 13, 27, 22, 49, 11, 37, 30, 10, 28, 40, 23, 39, 1, 44, 32, 33, 20, 36, 48, 12, 46, 9, 41, 7, 6, 18, 4, 2, 8, 34, 3, 24, 17, 14, 26, 45, 35, 29, 50, 15, 47, 5, 25, 21, 38, 43, 42, 16, 31}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_1m_4keys'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m_4keys'::regclass and a.attname = 'k4'); +INSERT INTO pg_statistic VALUES ('public.t_range_1m_4keys'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m_4keys'::regclass and a.attname = 'k4'), False::boolean, 0::real, 4::integer, 100::real, 1::smallint, 3::smallint, 0::smallint, 0::smallint, 0::smallint, 96::oid, 97::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, '{0.0117333336, 0.0114333332, 0.0113000004, 0.0109999999, 0.010966667, 0.0109333331, 0.0109000001, 0.0109000001, 0.0109000001, 0.0108666662, 0.0108333332, 0.0108333332, 0.0108000003, 0.0107666664, 0.0107333334, 0.0107333334, 0.0107000005, 0.0106666666, 0.0106333336, 0.0105666667, 0.0105333338, 0.0105333338, 0.0105333338, 0.0104666669, 0.0104666669, 0.010433333, 0.0104, 0.0104, 0.0104, 0.0103333332, 0.0103333332, 0.0103333332, 0.0103333332, 0.0102666663, 0.0102666663, 0.0102333333, 0.0102333333, 0.0101666665, 0.0101666665, 0.0101666665, 0.0101333335, 0.0100999996, 0.0100999996, 0.0100999996, 0.0100999996, 0.0100999996, 0.0100666666, 0.0100666666, 0.00999999978, 0.00996666681, 0.00993333291, 0.00993333291, 0.00993333291, 0.00993333291, 0.00989999995, 0.00989999995, 0.00983333308, 0.00983333308, 0.00980000012, 0.00976666622, 0.00976666622, 0.00973333325, 0.00973333325, 0.00970000029, 0.00970000029, 0.00966666639, 0.00966666639, 0.00963333342, 0.00963333342, 0.00956666656, 0.00956666656, 0.00956666656, 0.00956666656, 0.00953333359, 0.00953333359, 0.00953333359, 0.00949999969, 0.00949999969, 0.00946666673, 0.00946666673, 0.00943333376, 0.00943333376, 0.00939999986, 0.00939999986, 0.0093666669, 0.009333333, 0.00930000003, 0.00926666707, 0.00926666707, 0.00923333317, 0.00923333317, 0.00916666631, 0.00910000037, 0.00899999961, 0.00899999961, 0.00893333368, 0.00889999978, 0.00886666682, 0.00883333292, 0.00879999995}'::real[], '{0.00645504193}'::real[], NULL::real[], NULL::real[], NULL::real[], array_in('{65, 85, 3, 60, 25, 12, 23, 32, 82, 27, 28, 76, 68, 57, 9, 73, 62, 61, 100, 39, 17, 54, 96, 59, 93, 15, 21, 31, 51, 22, 41, 46, 90, 26, 44, 48, 77, 18, 55, 78, 34, 10, 29, 49, 58, 64, 2, 56, 79, 24, 13, 43, 97, 99, 45, 98, 71, 80, 67, 6, 81, 14, 92, 33, 72, 70, 94, 63, 84, 4, 35, 87, 95, 8, 86, 91, 20, 37, 42, 89, 19, 50, 30, 83, 74, 52, 36, 1, 66, 16, 38, 47, 7, 5, 53, 11, 88, 40, 69, 75}', 'int4'::regtype, -1)::anyarray, NULL, NULL, NULL); +DELETE FROM pg_statistic WHERE starelid = 'public.t_range_1m_4keys'::regclass AND staattnum = (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m_4keys'::regclass and a.attname = 'v1'); +INSERT INTO pg_statistic VALUES ('public.t_range_1m_4keys'::regclass, (SELECT a.attnum FROM pg_attribute a WHERE a.attrelid = 'public.t_range_1m_4keys'::regclass and a.attname = 'v1'), False::boolean, 1::real, 4::integer, 0::real, 0::smallint, 0::smallint, 0::smallint, 0::smallint, 0::smallint, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, 0::oid, NULL::real[], NULL::real[], NULL::real[], NULL::real[], NULL::real[], NULL, NULL, NULL, NULL); +UPDATE pg_yb_catalog_version +SET current_version = current_version + 1 +WHERE db_oid = ( + SELECT oid FROM pg_database WHERE datname = current_database() +); + +SET yb_non_ddl_txn_for_sys_tables_allowed = OFF; + +SET pg_hint_plan.enable_hint = ON; +SET pg_hint_plan.debug_print = ON; +SET pg_hint_plan.message_level = debug; +SET temp_file_limit="8182MB"; +SET yb_enable_optimizer_statistics = true; set yb_bnl_batch_size = 1024; set yb_enable_base_scans_cost_model = true; +-- Query Hash: 0ae1b2c7ad80ee87ea1a825725fc1b96 +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 2000; +-- Query Hash: cb836288722b7f2122d706f659be5d9c +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 4000; +-- Query Hash: 9fb48d99472378a037a811caf7c9f996 +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 6000; +-- Query Hash: 36ccbbab6ea9639cfe76e729f1cdfeea +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 8000; +-- Query Hash: 8fd8b333bb580d2725c637d3a61b7fa0 +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 10000; +-- Query Hash: 23163a76a92b03714752e71a0bb300cc +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 12000; +-- Query Hash: a7a1a762f96b9445990d3057904a8f9b +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 14000; +-- Query Hash: 13026bfac847da1ec9f13fdaad5c39cf +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 16000; +-- Query Hash: 90e1ac9dfdbd77fa5ad96b1fe3526a41 +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 18000; +-- Query Hash: 90c2ad6894acac53c20efdd886e0fc03 +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 20000; +-- Query Hash: 6a7aec1003782e720c65dc3f71aea7d9 +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 30000; +-- Query Hash: 4fd602403563a4e0f3e41c5ba997e69d +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 40000; +-- Query Hash: 7851294ed00745c200035bdcd0c1255d +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 50000; +-- Query Hash: 71dd3caa7a09c5c998aebea00ee660b9 +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 60000; +-- Query Hash: e0de7a3bdbda621312f9228b7e3e037f +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 70000; +-- Query Hash: cf003cee7f2565711393de7bdd8e8011 +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 80000; +-- Query Hash: 4c031d60065425d8f8d6b8c73e7e8619 +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 90000; +-- Query Hash: dc90be6cc64fd55c55c71587766b5d46 +EXPLAIN (COSTS OFF) select * from t_range_100k order by id desc limit 100000; +-- Query Hash: d0f652bb9a03d4d0079382c8a675ddf5 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_char4_100k limit 40960; +-- Query Hash: 00e91b30ef109150e085eccecd169799 +EXPLAIN (COSTS OFF) SELECT v1, v2 FROM t_char4_100k limit 20480; +-- Query Hash: 2f633a7b6847d1b1f71d16509675ba5c +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4 FROM t_char4_100k limit 10240; +-- Query Hash: b21a31bdafc618945ec67c144587cad6 +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4, v5, v6, v7, v8 FROM t_char4_100k limit 5120; +-- Query Hash: 14737481d661ae29bbf9c3248e627803 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_char8_100k limit 40960; +-- Query Hash: 6c4e495181e65578bddfa9cc82dce4be +EXPLAIN (COSTS OFF) SELECT v1, v2 FROM t_char8_100k limit 20480; +-- Query Hash: 31106354dd73743581a298fb292373fe +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4 FROM t_char8_100k limit 10240; +-- Query Hash: adb2fabb5dba6de5021647f0dcd506ba +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4, v5, v6, v7, v8 FROM t_char8_100k limit 5120; +-- Query Hash: 3ea29929b4cdc5cfe987519cfc418dc1 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_char16_100k limit 40960; +-- Query Hash: 29e65871465d1e4fd8e3d57d9d82cc5a +EXPLAIN (COSTS OFF) SELECT v1, v2 FROM t_char16_100k limit 20480; +-- Query Hash: 867c2d4aefb45b79edce4a60c0117472 +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4 FROM t_char16_100k limit 10240; +-- Query Hash: 2e621b26de4391e611d02be624e0cc08 +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4, v5, v6, v7, v8 FROM t_char16_100k limit 5120; +-- Query Hash: 447c4552a30004c9748c19f967c090a9 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_int_100k limit 40960; +-- Query Hash: 42a05c9206ce8da9e8e280b855ef8267 +EXPLAIN (COSTS OFF) SELECT v1, v2 FROM t_int_100k limit 20480; +-- Query Hash: e70dcebc0338929507048f3d8ee117a6 +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4 FROM t_int_100k limit 10240; +-- Query Hash: 9d09c8d070ba139698c7dd036e8bd213 +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4, v5, v6, v7, v8 FROM t_int_100k limit 5120; +-- Query Hash: 342457e804c0bd74a91e1dd7b2a39129 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_numeric_100k limit 40960; +-- Query Hash: c6690513dd017b932bd5405fab3a45f3 +EXPLAIN (COSTS OFF) SELECT v1, v2 FROM t_numeric_100k limit 20480; +-- Query Hash: 48f6a609f0b240efb26d9e24022269e5 +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4 FROM t_numeric_100k limit 10240; +-- Query Hash: 2e12b7d787ee03f9184d287c5150bbc3 +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4, v5, v6, v7, v8 FROM t_numeric_100k limit 5120; +-- Query Hash: 3058b97e0c5b78a7afcc14b01dc7a0f1 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_real_100k limit 40960; +-- Query Hash: f180e2b23646b995be28029bd68a1c50 +EXPLAIN (COSTS OFF) SELECT v1, v2 FROM t_real_100k limit 20480; +-- Query Hash: dcc707d034bab4f4b8bf6a45d2b54d19 +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4 FROM t_real_100k limit 10240; +-- Query Hash: 32dd0af7a84cabeec056261ba6fb8257 +EXPLAIN (COSTS OFF) SELECT v1, v2, v3, v4, v5, v6, v7, v8 FROM t_real_100k limit 5120; +-- Query Hash: cd9bc3bcc61879e6b7ead7f119f8b650 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k1 < 8 and k2 > 5 and k2 < 10; +-- Query Hash: f09230315434bedd219aa7fba6db004f +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k1 < 6 and k2 > 5 and k2 < 10; +-- Query Hash: 5e7aaac9622911765387f8a403652652 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k1 < 4 and k2 > 5 and k2 < 10; +-- Query Hash: 42e507de3e899aa2f4471f33658eda38 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k1 < 6 and k2 > 2 and k2 < 4; +-- Query Hash: c3e8d9fdd585b75bcfe4ff49cabd0620 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k1 < 6 and k2 > 2 and k2 < 6; +-- Query Hash: 1641bdf9ac44abb025df69b446bb49fd +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k1 < 6 and k2 > 2 and k2 < 8; +-- Query Hash: fbfa49193ce4f186220af1bdc3e6638d +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k1 < 6 and k2 > 2 and k2 < 10; +-- Query Hash: c2d7945ad852b2c7a69ba4a4faa40879 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k1 < 6 and k2 > 2 and k2 < 12; +-- Query Hash: d7d241a4b06d90116058b1ce2a77b906 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k1 < 6 and k2 > 2 and k2 < 14; +-- Query Hash: 6e74e56336dcb6bdd7e551b0165e71cc +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k1 < 6 and k2 > 2 and k2 < 16; +-- Query Hash: a725b425b9ebccf156b2011f92ebf306 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k1 < 6 and k2 > 2 and k2 < 18; +-- Query Hash: c17e4e0a745b63c2006e87d41d86be6e +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k; +-- Query Hash: c17e4e0a745b63c2006e87d41d86be6e +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k; +-- Query Hash: c17e4e0a745b63c2006e87d41d86be6e +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k; +-- Query Hash: c17e4e0a745b63c2006e87d41d86be6e +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k; +-- Query Hash: c17e4e0a745b63c2006e87d41d86be6e +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k; +-- Query Hash: c17e4e0a745b63c2006e87d41d86be6e +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k; +-- Query Hash: c17e4e0a745b63c2006e87d41d86be6e +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k; +-- Query Hash: 8317546c890921478dd4eb572fe1270a +EXPLAIN (COSTS OFF) SELECT * from t_range_100k_1update; +-- Query Hash: 5b00b70a296886f5b741d87c7bc0ea54 +EXPLAIN (COSTS OFF) SELECT * from t_range_100k_2update; +-- Query Hash: 0e236ed77f7c1359efb8198ffa770818 +EXPLAIN (COSTS OFF) SELECT * from t_range_100k_3update; +-- Query Hash: 501774dc18519edbab8ccc2d77eba142 +EXPLAIN (COSTS OFF) SELECT * from t_range_100k_4update; +-- Query Hash: c84c26737002659979e72554dcc9ce60 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 0; +-- Query Hash: c07f7e18d9f2631cab48c5b81b101312 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 1; +-- Query Hash: 641f7f11c6bdc12df3067ae6f1d8a4f5 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 10; +-- Query Hash: 09c5aed4c09b9f521d3f28ad63bc310c +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 100; +-- Query Hash: 5760478f38fc87bd0152788749a4586e +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 1000; +-- Query Hash: 529f973b79f95acc61d0a5c32bb55824 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 10000; +-- Query Hash: c05a9bfe5e1fe6ef91d453ec1118cda0 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 20000; +-- Query Hash: a106c678a75dcd3708128f504f85e28d +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 30000; +-- Query Hash: 0b2b6d21e96196374fcfa1d361df678f +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 40000; +-- Query Hash: 3958617c6e5d8594ce9c1ed933990a21 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 50000; +-- Query Hash: 4f3c06b7a4c058a585be7f8742088bd9 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 60000; +-- Query Hash: 5a5f3454601ef6d9c839a1a4156877b3 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 70000; +-- Query Hash: 986db5347ddb81dcd6d98e320aa33f04 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 72000; +-- Query Hash: f3f30a71ac9b1ea16a02df36af4353ef +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 74000; +-- Query Hash: 19ada938c02ed684fd0677fc7aed7bc7 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 76000; +-- Query Hash: 4f80c5b0c11ac167fb34a28a251f1194 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 78000; +-- Query Hash: 4e55bca06a13c7c3d8a5e10eca420b18 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 80000; +-- Query Hash: 4c74d9b590210aaaa72ccdb149d91085 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 82000; +-- Query Hash: cd089249d1c61cdcc5ca790d5b653688 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 84000; +-- Query Hash: 5f4039a90e0f55a00b2673363f37e962 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 86000; +-- Query Hash: 77265cc8960831f9c3ae98bbb219ba3b +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 88000; +-- Query Hash: 251f2300e2c1fec654e64f1541d5708a +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 90000; +-- Query Hash: ce27c95232d85f7234f1617d0691e48b +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 92000; +-- Query Hash: efe0ba7993a60e6f0574444a42ec7300 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 94000; +-- Query Hash: de34f39ebd3fbac61ea83aec9c0e65fa +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 96000; +-- Query Hash: 4813e70171561585ad8308463c444d19 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 98000; +-- Query Hash: 3d16e92a207f6d5d7d3da4add741ea21 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where id > 100000; +-- Query Hash: e5f7b926682df47cc192858544459088 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k WHERE v2 > 50000; +-- Query Hash: 30b82aa9c0e04be16f3f2ccd778011e6 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_200k WHERE v2 > 150000; +-- Query Hash: c99a4af78095152c68d5f4ed1941e179 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_300k WHERE v2 > 250000; +-- Query Hash: ca207cbfdfbf4964c434a8bb2914fcf7 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_400k WHERE v2 > 350000; +-- Query Hash: 53881b165cc5c50447266e009dc54346 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_500k WHERE v2 > 450000; +-- Query Hash: 0be2581fc7c05d3db604730c17885f72 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_600k WHERE v2 > 550000; +-- Query Hash: 0483a9a645be16c05b92dc089d9fcc70 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_700k WHERE v2 > 650000; +-- Query Hash: 711c576a096fc60cbbf3501d43ba0a69 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_800k WHERE v2 > 750000; +-- Query Hash: 4500bbeaa12888e34ff56ae095ca596c +EXPLAIN (COSTS OFF) SELECT * FROM t_range_900k WHERE v2 > 850000; +-- Query Hash: 8db178b6fed52d74c431982c308aa970 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE v2 > 950000; +-- Query Hash: 3b3c3c56402325a5fd092b3d88a4a44b +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k WHERE v2 > 50000 AND v3 < 1000000; +-- Query Hash: 4e18e9cd252aa256ec82a9278d9cf704 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_200k WHERE v2 > 150000 AND v3 < 1000000; +-- Query Hash: efa5fc5ae8601bdb273dd013b041fa5d +EXPLAIN (COSTS OFF) SELECT * FROM t_range_300k WHERE v2 > 250000 AND v3 < 1000000; +-- Query Hash: 55e9c7fc37dc7fed69d921a6510bcb9d +EXPLAIN (COSTS OFF) SELECT * FROM t_range_400k WHERE v2 > 350000 AND v3 < 1000000; +-- Query Hash: ac813fd1a5152b25c15b065ae4617bb5 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_500k WHERE v2 > 450000 AND v3 < 1000000; +-- Query Hash: 90947514c5132f84726f568d05d60688 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_600k WHERE v2 > 550000 AND v3 < 1000000; +-- Query Hash: 927022d1ba8523808ef30b00112a46e6 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_700k WHERE v2 > 650000 AND v3 < 1000000; +-- Query Hash: ec58f4f11fdb4404bc9d4a3d4078b5ae +EXPLAIN (COSTS OFF) SELECT * FROM t_range_800k WHERE v2 > 750000 AND v3 < 1000000; +-- Query Hash: 732641e32668a5e274d6c9dafea02fdf +EXPLAIN (COSTS OFF) SELECT * FROM t_range_900k WHERE v2 > 850000 AND v3 < 1000000; +-- Query Hash: 99f734ee2fbbb4cb95c2661da742d47c +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE v2 > 950000 AND v3 < 1000000; +-- Query Hash: 914b005e42e071e7544901db91613ae8 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k WHERE v2 > 50000 AND v3 < 1000000 AND v4 > 0; +-- Query Hash: 4445dfd88e004aced6dbd48edec0516a +EXPLAIN (COSTS OFF) SELECT * FROM t_range_200k WHERE v2 > 150000 AND v3 < 1000000 AND v4 > 0; +-- Query Hash: 65e1815681a4f03e0af14dc9fa133eaf +EXPLAIN (COSTS OFF) SELECT * FROM t_range_300k WHERE v2 > 250000 AND v3 < 1000000 AND v4 > 0; +-- Query Hash: e179415e1cedf769c687ddf1baf8f4da +EXPLAIN (COSTS OFF) SELECT * FROM t_range_400k WHERE v2 > 350000 AND v3 < 1000000 AND v4 > 0; +-- Query Hash: 592389fcf9ecc3a05dae99c370f988ae +EXPLAIN (COSTS OFF) SELECT * FROM t_range_500k WHERE v2 > 450000 AND v3 < 1000000 AND v4 > 0; +-- Query Hash: 80ec102ac6323bb89a3e2f90d456dc87 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_600k WHERE v2 > 550000 AND v3 < 1000000 AND v4 > 0; +-- Query Hash: 3c644840a86173902a9c47d304b923f6 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_700k WHERE v2 > 650000 AND v3 < 1000000 AND v4 > 0; +-- Query Hash: 55fcbe9f70aa45f7e184178e5cd799ea +EXPLAIN (COSTS OFF) SELECT * FROM t_range_800k WHERE v2 > 750000 AND v3 < 1000000 AND v4 > 0; +-- Query Hash: 6536b253b090d114cf2e89c3c9c3cd0c +EXPLAIN (COSTS OFF) SELECT * FROM t_range_900k WHERE v2 > 850000 AND v3 < 1000000 AND v4 > 0; +-- Query Hash: 06bff16d48cfda0d3a4d1db810dfb818 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE v2 > 950000 AND v3 < 1000000 AND v4 > 0; +-- Query Hash: 59e6de5247cc4e29c81c208f9e5a32f3 +EXPLAIN (COSTS OFF) SELECT id FROM t_range_100k; +-- Query Hash: 955ceeb7eab89c8ca040c661c0965be8 +EXPLAIN (COSTS OFF) SELECT id, v1 FROM t_range_100k; +-- Query Hash: 42d81bec3c706d4c29fb55948949dd72 +EXPLAIN (COSTS OFF) SELECT id, v1, v2 FROM t_range_100k; +-- Query Hash: 18e88409247c909ab6c198996f0cc1d1 +EXPLAIN (COSTS OFF) SELECT id, v1, v2, v3 FROM t_range_100k; +-- Query Hash: 94bd8339397c9535d9a9880818de44e0 +EXPLAIN (COSTS OFF) SELECT id, v1, v2, v3, v4 FROM t_range_100k; +-- Query Hash: 752c4b16a3e07738ec2b3dbd219cc0c9 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 0; +-- Query Hash: bdc22699c4775408c6a6f4bf4e038e10 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 1; +-- Query Hash: 4ef60164be568bad49d0e66d4d9c2986 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 10; +-- Query Hash: cce05c28cffe5309dd567cd176fd728a +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 100; +-- Query Hash: 74d65f8d13aa6a893a449f12186e5c10 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 1000; +-- Query Hash: 5e51f314c3954af68beb0fb3a699ab00 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 10000; +-- Query Hash: caa8a3ce320cb9ec26c70d87b93d1a49 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 20000; +-- Query Hash: 7002953b0aac4fe5a0ebe54672e8e557 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 30000; +-- Query Hash: f59033c5f0cdcf1a61ec2d1294498723 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 40000; +-- Query Hash: 34415093794c0bee911d97722bd3b015 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 50000; +-- Query Hash: ac57ffed7f517fe626541e9377a9fb1d +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 60000; +-- Query Hash: d219120944637d070cd8b838064c485f +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 70000; +-- Query Hash: c7d782cdd7fcc2c484e0187ddf1c43df +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 72000; +-- Query Hash: 147d440fc645807cc00ac11420080ac9 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 74000; +-- Query Hash: eef5fb62d364ea0b48697009cb911b58 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 76000; +-- Query Hash: 91d98635ffb11f56f280745ab54f570e +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 78000; +-- Query Hash: bf0d45764d65ec4810ab2dea43a34338 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 80000; +-- Query Hash: ceabbc55afb9a4a4193778607789e9aa +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 82000; +-- Query Hash: 7d6da8d19e40e895afc1d59725f4149b +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 84000; +-- Query Hash: 46d57c485e42cf6160e4f6766c61d70e +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 86000; +-- Query Hash: e3fec7fef395927d9f122ef652d4e9c3 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 88000; +-- Query Hash: f522aab9d236d3ed75ab2352fb500db6 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 90000; +-- Query Hash: 9ced59c340c481755ad25e614605cca5 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 92000; +-- Query Hash: 0ef9530c49a611869b810b515dc58a2f +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 94000; +-- Query Hash: 0a8b5bdec387c90eae29d8ce92512fb6 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 96000; +-- Query Hash: 62ef41e12087e95fda2d543e1462c85e +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 98000; +-- Query Hash: 0a188489b59c5498c04ae3dd3725e05d +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k where v1 > 100000; +-- Query Hash: d93f6e4d8c79ed9ff1777a8346135486 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 0; +-- Query Hash: ad243137d04da666112652bc4daee5bb +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 1; +-- Query Hash: bfeb69f5dd583f251a20785be7fff7f8 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 10; +-- Query Hash: 6c0176a4d1a4d77205315f39efea625c +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 100; +-- Query Hash: 1bfe63056ba35bb615395193fed411e6 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 1000; +-- Query Hash: e198dc948aa15d57b33863d33bf9b06f +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 10000; +-- Query Hash: 5a842f8a81919bff6c9c35ef4741d13e +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 20000; +-- Query Hash: 6f4dd53d940b74114b3a55283cd94b6d +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 30000; +-- Query Hash: 2bb1bc884f74c2b6e7641434bb422fd2 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 40000; +-- Query Hash: 18b4c66c6e69728e277bcc0b98c14dc4 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 50000; +-- Query Hash: 2daa6672150bf317ddd08fb75bdf82b4 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 60000; +-- Query Hash: d7702857e136de07e622647d58d7b06c +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 70000; +-- Query Hash: 42d7a5992cea72bbc2a48e7166e46522 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 72000; +-- Query Hash: 83baff4e3a9851892eb754fcc191b396 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 74000; +-- Query Hash: e9ec5166d77f198f360ab93eea091feb +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 76000; +-- Query Hash: 56dff92acb689e82625486ea525fc3be +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 78000; +-- Query Hash: 9d150416c5e9f1bafef0c02900e2f348 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 80000; +-- Query Hash: 6e8c5256e03caadf87ef9ca5518c45cb +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 82000; +-- Query Hash: 1461dc3913b71a82bf2a7fd78e1d272e +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 84000; +-- Query Hash: f257496ae8eb45582a7b37a71e2aaa41 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 86000; +-- Query Hash: 1add8be714520d65ccc6b29b3df67b23 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 88000; +-- Query Hash: 93882cc33b315e91d15864fae16cc319 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 90000; +-- Query Hash: a64735e1f218be12a9447f3807d3f18c +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 92000; +-- Query Hash: ca543d4e151c1639704fef9181e4e5f6 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 94000; +-- Query Hash: 85236d3d0a38f8ac2d221cc68a16b56d +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 96000; +-- Query Hash: 7436a2799d3b13a9bbf0147a994e87c9 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 98000; +-- Query Hash: 347246381eaacf8d14a762997e0f7773 +EXPLAIN (COSTS OFF) SELECT v1 FROM t_range_100k where v1 > 100000; +-- Query Hash: c2ec8c4aaf27f063ac4ec2836c0e0c75 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k WHERE id in (1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999); +-- Query Hash: ae6ed708dd8b308817f5045a172349b6 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_200k WHERE id in (1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999); +-- Query Hash: e8e944b6d706d78e27e59bb90b95e6f2 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_400k WHERE id in (1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999); +-- Query Hash: 4fffcb14e09b5888af5d4dbec8cfa0d2 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_800k WHERE id in (1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999); +-- Query Hash: 8658bcd07d24cb2920170703af3c8d61 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k WHERE id in (1000,1002,1004,1006,1008,1010,1012,1014,1016,1018,1020,1022,1024,1026,1028,1030,1032,1034,1036,1038,1040,1042,1044,1046,1048,1050,1052,1054,1056,1058,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086,1088,1090,1092,1094,1096,1098,1100,1102,1104,1106,1108,1110,1112,1114,1116,1118,1120,1122,1124,1126,1128,1130,1132,1134,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156,1158,1160,1162,1164,1166,1168,1170,1172,1174,1176,1178,1180,1182,1184,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204,1206,1208,1210,1212,1214,1216,1218,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246,1248,1250,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286,1288,1290,1292,1294,1296,1298,1300,1302,1304,1306,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326,1328,1330,1332,1334,1336,1338,1340,1342,1344,1346,1348,1350,1352,1354,1356,1358,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386,1388,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410,1412,1414,1416,1418,1420,1422,1424,1426,1428,1430,1432,1434,1436,1438,1440,1442,1444,1446,1448,1450,1452,1454,1456,1458,1460,1462,1464,1466,1468,1470,1472,1474,1476,1478,1480,1482,1484,1486,1488,1490,1492,1494,1496,1498,1500,1502,1504,1506,1508,1510,1512,1514,1516,1518,1520,1522,1524,1526,1528,1530,1532,1534,1536,1538,1540,1542,1544,1546,1548,1550,1552,1554,1556,1558,1560,1562,1564,1566,1568,1570,1572,1574,1576,1578,1580,1582,1584,1586,1588,1590,1592,1594,1596,1598,1600,1602,1604,1606,1608,1610,1612,1614,1616,1618,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638,1640,1642,1644,1646,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688,1690,1692,1694,1696,1698,1700,1702,1704,1706,1708,1710,1712,1714,1716,1718,1720,1722,1724,1726,1728,1730,1732,1734,1736,1738,1740,1742,1744,1746,1748,1750,1752,1754,1756,1758,1760,1762,1764,1766,1768,1770,1772,1774,1776,1778,1780,1782,1784,1786,1788,1790,1792,1794,1796,1798,1800,1802,1804,1806,1808,1810,1812,1814,1816,1818,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862,1864,1866,1868,1870,1872,1874,1876,1878,1880,1882,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926,1928,1930,1932,1934,1936,1938,1940,1942,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964,1966,1968,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988,1990,1992,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2028,2030,2032,2034,2036,2038,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068,2070,2072,2074,2076,2078,2080,2082,2084,2086,2088,2090,2092,2094,2096,2098,2100,2102,2104,2106,2108,2110,2112,2114,2116,2118,2120,2122,2124,2126,2128,2130,2132,2134,2136,2138,2140,2142,2144,2146,2148,2150,2152,2154,2156,2158,2160,2162,2164,2166,2168,2170,2172,2174,2176,2178,2180,2182,2184,2186,2188,2190,2192,2194,2196,2198,2200,2202,2204,2206,2208,2210,2212,2214,2216,2218,2220,2222,2224,2226,2228,2230,2232,2234,2236,2238,2240,2242,2244,2246,2248,2250,2252,2254,2256,2258,2260,2262,2264,2266,2268,2270,2272,2274,2276,2278,2280,2282,2284,2286,2288,2290,2292,2294,2296,2298,2300,2302,2304,2306,2308,2310,2312,2314,2316,2318,2320,2322,2324,2326,2328,2330,2332,2334,2336,2338,2340,2342,2344,2346,2348,2350,2352,2354,2356,2358,2360,2362,2364,2366,2368,2370,2372,2374,2376,2378,2380,2382,2384,2386,2388,2390,2392,2394,2396,2398,2400,2402,2404,2406,2408,2410,2412,2414,2416,2418,2420,2422,2424,2426,2428,2430,2432,2434,2436,2438,2440,2442,2444,2446,2448,2450,2452,2454,2456,2458,2460,2462,2464,2466,2468,2470,2472,2474,2476,2478,2480,2482,2484,2486,2488,2490,2492,2494,2496,2498,2500,2502,2504,2506,2508,2510,2512,2514,2516,2518,2520,2522,2524,2526,2528,2530,2532,2534,2536,2538,2540,2542,2544,2546,2548,2550,2552,2554,2556,2558,2560,2562,2564,2566,2568,2570,2572,2574,2576,2578,2580,2582,2584,2586,2588,2590,2592,2594,2596,2598,2600,2602,2604,2606,2608,2610,2612,2614,2616,2618,2620,2622,2624,2626,2628,2630,2632,2634,2636,2638,2640,2642,2644,2646,2648,2650,2652,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2674,2676,2678,2680,2682,2684,2686,2688,2690,2692,2694,2696,2698,2700,2702,2704,2706,2708,2710,2712,2714,2716,2718,2720,2722,2724,2726,2728,2730,2732,2734,2736,2738,2740,2742,2744,2746,2748,2750,2752,2754,2756,2758,2760,2762,2764,2766,2768,2770,2772,2774,2776,2778,2780,2782,2784,2786,2788,2790,2792,2794,2796,2798,2800,2802,2804,2806,2808,2810,2812,2814,2816,2818,2820,2822,2824,2826,2828,2830,2832,2834,2836,2838,2840,2842,2844,2846,2848,2850,2852,2854,2856,2858,2860,2862,2864,2866,2868,2870,2872,2874,2876,2878,2880,2882,2884,2886,2888,2890,2892,2894,2896,2898,2900,2902,2904,2906,2908,2910,2912,2914,2916,2918,2920,2922,2924,2926,2928,2930,2932,2934,2936,2938,2940,2942,2944,2946,2948,2950,2952,2954,2956,2958,2960,2962,2964,2966,2968,2970,2972,2974,2976,2978,2980,2982,2984,2986,2988,2990,2992,2994,2996,2998); +-- Query Hash: ec919662380311400303d6a17ff490eb +EXPLAIN (COSTS OFF) SELECT * FROM t_range_200k WHERE id in (1000,1002,1004,1006,1008,1010,1012,1014,1016,1018,1020,1022,1024,1026,1028,1030,1032,1034,1036,1038,1040,1042,1044,1046,1048,1050,1052,1054,1056,1058,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086,1088,1090,1092,1094,1096,1098,1100,1102,1104,1106,1108,1110,1112,1114,1116,1118,1120,1122,1124,1126,1128,1130,1132,1134,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156,1158,1160,1162,1164,1166,1168,1170,1172,1174,1176,1178,1180,1182,1184,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204,1206,1208,1210,1212,1214,1216,1218,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246,1248,1250,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286,1288,1290,1292,1294,1296,1298,1300,1302,1304,1306,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326,1328,1330,1332,1334,1336,1338,1340,1342,1344,1346,1348,1350,1352,1354,1356,1358,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386,1388,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410,1412,1414,1416,1418,1420,1422,1424,1426,1428,1430,1432,1434,1436,1438,1440,1442,1444,1446,1448,1450,1452,1454,1456,1458,1460,1462,1464,1466,1468,1470,1472,1474,1476,1478,1480,1482,1484,1486,1488,1490,1492,1494,1496,1498,1500,1502,1504,1506,1508,1510,1512,1514,1516,1518,1520,1522,1524,1526,1528,1530,1532,1534,1536,1538,1540,1542,1544,1546,1548,1550,1552,1554,1556,1558,1560,1562,1564,1566,1568,1570,1572,1574,1576,1578,1580,1582,1584,1586,1588,1590,1592,1594,1596,1598,1600,1602,1604,1606,1608,1610,1612,1614,1616,1618,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638,1640,1642,1644,1646,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688,1690,1692,1694,1696,1698,1700,1702,1704,1706,1708,1710,1712,1714,1716,1718,1720,1722,1724,1726,1728,1730,1732,1734,1736,1738,1740,1742,1744,1746,1748,1750,1752,1754,1756,1758,1760,1762,1764,1766,1768,1770,1772,1774,1776,1778,1780,1782,1784,1786,1788,1790,1792,1794,1796,1798,1800,1802,1804,1806,1808,1810,1812,1814,1816,1818,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862,1864,1866,1868,1870,1872,1874,1876,1878,1880,1882,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926,1928,1930,1932,1934,1936,1938,1940,1942,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964,1966,1968,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988,1990,1992,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2028,2030,2032,2034,2036,2038,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068,2070,2072,2074,2076,2078,2080,2082,2084,2086,2088,2090,2092,2094,2096,2098,2100,2102,2104,2106,2108,2110,2112,2114,2116,2118,2120,2122,2124,2126,2128,2130,2132,2134,2136,2138,2140,2142,2144,2146,2148,2150,2152,2154,2156,2158,2160,2162,2164,2166,2168,2170,2172,2174,2176,2178,2180,2182,2184,2186,2188,2190,2192,2194,2196,2198,2200,2202,2204,2206,2208,2210,2212,2214,2216,2218,2220,2222,2224,2226,2228,2230,2232,2234,2236,2238,2240,2242,2244,2246,2248,2250,2252,2254,2256,2258,2260,2262,2264,2266,2268,2270,2272,2274,2276,2278,2280,2282,2284,2286,2288,2290,2292,2294,2296,2298,2300,2302,2304,2306,2308,2310,2312,2314,2316,2318,2320,2322,2324,2326,2328,2330,2332,2334,2336,2338,2340,2342,2344,2346,2348,2350,2352,2354,2356,2358,2360,2362,2364,2366,2368,2370,2372,2374,2376,2378,2380,2382,2384,2386,2388,2390,2392,2394,2396,2398,2400,2402,2404,2406,2408,2410,2412,2414,2416,2418,2420,2422,2424,2426,2428,2430,2432,2434,2436,2438,2440,2442,2444,2446,2448,2450,2452,2454,2456,2458,2460,2462,2464,2466,2468,2470,2472,2474,2476,2478,2480,2482,2484,2486,2488,2490,2492,2494,2496,2498,2500,2502,2504,2506,2508,2510,2512,2514,2516,2518,2520,2522,2524,2526,2528,2530,2532,2534,2536,2538,2540,2542,2544,2546,2548,2550,2552,2554,2556,2558,2560,2562,2564,2566,2568,2570,2572,2574,2576,2578,2580,2582,2584,2586,2588,2590,2592,2594,2596,2598,2600,2602,2604,2606,2608,2610,2612,2614,2616,2618,2620,2622,2624,2626,2628,2630,2632,2634,2636,2638,2640,2642,2644,2646,2648,2650,2652,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2674,2676,2678,2680,2682,2684,2686,2688,2690,2692,2694,2696,2698,2700,2702,2704,2706,2708,2710,2712,2714,2716,2718,2720,2722,2724,2726,2728,2730,2732,2734,2736,2738,2740,2742,2744,2746,2748,2750,2752,2754,2756,2758,2760,2762,2764,2766,2768,2770,2772,2774,2776,2778,2780,2782,2784,2786,2788,2790,2792,2794,2796,2798,2800,2802,2804,2806,2808,2810,2812,2814,2816,2818,2820,2822,2824,2826,2828,2830,2832,2834,2836,2838,2840,2842,2844,2846,2848,2850,2852,2854,2856,2858,2860,2862,2864,2866,2868,2870,2872,2874,2876,2878,2880,2882,2884,2886,2888,2890,2892,2894,2896,2898,2900,2902,2904,2906,2908,2910,2912,2914,2916,2918,2920,2922,2924,2926,2928,2930,2932,2934,2936,2938,2940,2942,2944,2946,2948,2950,2952,2954,2956,2958,2960,2962,2964,2966,2968,2970,2972,2974,2976,2978,2980,2982,2984,2986,2988,2990,2992,2994,2996,2998); +-- Query Hash: 167d1922c9b95e4de0a29a6f520cfaae +EXPLAIN (COSTS OFF) SELECT * FROM t_range_400k WHERE id in (1000,1002,1004,1006,1008,1010,1012,1014,1016,1018,1020,1022,1024,1026,1028,1030,1032,1034,1036,1038,1040,1042,1044,1046,1048,1050,1052,1054,1056,1058,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086,1088,1090,1092,1094,1096,1098,1100,1102,1104,1106,1108,1110,1112,1114,1116,1118,1120,1122,1124,1126,1128,1130,1132,1134,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156,1158,1160,1162,1164,1166,1168,1170,1172,1174,1176,1178,1180,1182,1184,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204,1206,1208,1210,1212,1214,1216,1218,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246,1248,1250,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286,1288,1290,1292,1294,1296,1298,1300,1302,1304,1306,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326,1328,1330,1332,1334,1336,1338,1340,1342,1344,1346,1348,1350,1352,1354,1356,1358,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386,1388,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410,1412,1414,1416,1418,1420,1422,1424,1426,1428,1430,1432,1434,1436,1438,1440,1442,1444,1446,1448,1450,1452,1454,1456,1458,1460,1462,1464,1466,1468,1470,1472,1474,1476,1478,1480,1482,1484,1486,1488,1490,1492,1494,1496,1498,1500,1502,1504,1506,1508,1510,1512,1514,1516,1518,1520,1522,1524,1526,1528,1530,1532,1534,1536,1538,1540,1542,1544,1546,1548,1550,1552,1554,1556,1558,1560,1562,1564,1566,1568,1570,1572,1574,1576,1578,1580,1582,1584,1586,1588,1590,1592,1594,1596,1598,1600,1602,1604,1606,1608,1610,1612,1614,1616,1618,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638,1640,1642,1644,1646,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688,1690,1692,1694,1696,1698,1700,1702,1704,1706,1708,1710,1712,1714,1716,1718,1720,1722,1724,1726,1728,1730,1732,1734,1736,1738,1740,1742,1744,1746,1748,1750,1752,1754,1756,1758,1760,1762,1764,1766,1768,1770,1772,1774,1776,1778,1780,1782,1784,1786,1788,1790,1792,1794,1796,1798,1800,1802,1804,1806,1808,1810,1812,1814,1816,1818,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862,1864,1866,1868,1870,1872,1874,1876,1878,1880,1882,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926,1928,1930,1932,1934,1936,1938,1940,1942,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964,1966,1968,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988,1990,1992,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2028,2030,2032,2034,2036,2038,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068,2070,2072,2074,2076,2078,2080,2082,2084,2086,2088,2090,2092,2094,2096,2098,2100,2102,2104,2106,2108,2110,2112,2114,2116,2118,2120,2122,2124,2126,2128,2130,2132,2134,2136,2138,2140,2142,2144,2146,2148,2150,2152,2154,2156,2158,2160,2162,2164,2166,2168,2170,2172,2174,2176,2178,2180,2182,2184,2186,2188,2190,2192,2194,2196,2198,2200,2202,2204,2206,2208,2210,2212,2214,2216,2218,2220,2222,2224,2226,2228,2230,2232,2234,2236,2238,2240,2242,2244,2246,2248,2250,2252,2254,2256,2258,2260,2262,2264,2266,2268,2270,2272,2274,2276,2278,2280,2282,2284,2286,2288,2290,2292,2294,2296,2298,2300,2302,2304,2306,2308,2310,2312,2314,2316,2318,2320,2322,2324,2326,2328,2330,2332,2334,2336,2338,2340,2342,2344,2346,2348,2350,2352,2354,2356,2358,2360,2362,2364,2366,2368,2370,2372,2374,2376,2378,2380,2382,2384,2386,2388,2390,2392,2394,2396,2398,2400,2402,2404,2406,2408,2410,2412,2414,2416,2418,2420,2422,2424,2426,2428,2430,2432,2434,2436,2438,2440,2442,2444,2446,2448,2450,2452,2454,2456,2458,2460,2462,2464,2466,2468,2470,2472,2474,2476,2478,2480,2482,2484,2486,2488,2490,2492,2494,2496,2498,2500,2502,2504,2506,2508,2510,2512,2514,2516,2518,2520,2522,2524,2526,2528,2530,2532,2534,2536,2538,2540,2542,2544,2546,2548,2550,2552,2554,2556,2558,2560,2562,2564,2566,2568,2570,2572,2574,2576,2578,2580,2582,2584,2586,2588,2590,2592,2594,2596,2598,2600,2602,2604,2606,2608,2610,2612,2614,2616,2618,2620,2622,2624,2626,2628,2630,2632,2634,2636,2638,2640,2642,2644,2646,2648,2650,2652,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2674,2676,2678,2680,2682,2684,2686,2688,2690,2692,2694,2696,2698,2700,2702,2704,2706,2708,2710,2712,2714,2716,2718,2720,2722,2724,2726,2728,2730,2732,2734,2736,2738,2740,2742,2744,2746,2748,2750,2752,2754,2756,2758,2760,2762,2764,2766,2768,2770,2772,2774,2776,2778,2780,2782,2784,2786,2788,2790,2792,2794,2796,2798,2800,2802,2804,2806,2808,2810,2812,2814,2816,2818,2820,2822,2824,2826,2828,2830,2832,2834,2836,2838,2840,2842,2844,2846,2848,2850,2852,2854,2856,2858,2860,2862,2864,2866,2868,2870,2872,2874,2876,2878,2880,2882,2884,2886,2888,2890,2892,2894,2896,2898,2900,2902,2904,2906,2908,2910,2912,2914,2916,2918,2920,2922,2924,2926,2928,2930,2932,2934,2936,2938,2940,2942,2944,2946,2948,2950,2952,2954,2956,2958,2960,2962,2964,2966,2968,2970,2972,2974,2976,2978,2980,2982,2984,2986,2988,2990,2992,2994,2996,2998); +-- Query Hash: 7f0864c159fb1f2bfe93fc67977c0dfa +EXPLAIN (COSTS OFF) SELECT * FROM t_range_800k WHERE id in (1000,1002,1004,1006,1008,1010,1012,1014,1016,1018,1020,1022,1024,1026,1028,1030,1032,1034,1036,1038,1040,1042,1044,1046,1048,1050,1052,1054,1056,1058,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086,1088,1090,1092,1094,1096,1098,1100,1102,1104,1106,1108,1110,1112,1114,1116,1118,1120,1122,1124,1126,1128,1130,1132,1134,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156,1158,1160,1162,1164,1166,1168,1170,1172,1174,1176,1178,1180,1182,1184,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204,1206,1208,1210,1212,1214,1216,1218,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246,1248,1250,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286,1288,1290,1292,1294,1296,1298,1300,1302,1304,1306,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326,1328,1330,1332,1334,1336,1338,1340,1342,1344,1346,1348,1350,1352,1354,1356,1358,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386,1388,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410,1412,1414,1416,1418,1420,1422,1424,1426,1428,1430,1432,1434,1436,1438,1440,1442,1444,1446,1448,1450,1452,1454,1456,1458,1460,1462,1464,1466,1468,1470,1472,1474,1476,1478,1480,1482,1484,1486,1488,1490,1492,1494,1496,1498,1500,1502,1504,1506,1508,1510,1512,1514,1516,1518,1520,1522,1524,1526,1528,1530,1532,1534,1536,1538,1540,1542,1544,1546,1548,1550,1552,1554,1556,1558,1560,1562,1564,1566,1568,1570,1572,1574,1576,1578,1580,1582,1584,1586,1588,1590,1592,1594,1596,1598,1600,1602,1604,1606,1608,1610,1612,1614,1616,1618,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638,1640,1642,1644,1646,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688,1690,1692,1694,1696,1698,1700,1702,1704,1706,1708,1710,1712,1714,1716,1718,1720,1722,1724,1726,1728,1730,1732,1734,1736,1738,1740,1742,1744,1746,1748,1750,1752,1754,1756,1758,1760,1762,1764,1766,1768,1770,1772,1774,1776,1778,1780,1782,1784,1786,1788,1790,1792,1794,1796,1798,1800,1802,1804,1806,1808,1810,1812,1814,1816,1818,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862,1864,1866,1868,1870,1872,1874,1876,1878,1880,1882,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926,1928,1930,1932,1934,1936,1938,1940,1942,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964,1966,1968,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988,1990,1992,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2028,2030,2032,2034,2036,2038,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068,2070,2072,2074,2076,2078,2080,2082,2084,2086,2088,2090,2092,2094,2096,2098,2100,2102,2104,2106,2108,2110,2112,2114,2116,2118,2120,2122,2124,2126,2128,2130,2132,2134,2136,2138,2140,2142,2144,2146,2148,2150,2152,2154,2156,2158,2160,2162,2164,2166,2168,2170,2172,2174,2176,2178,2180,2182,2184,2186,2188,2190,2192,2194,2196,2198,2200,2202,2204,2206,2208,2210,2212,2214,2216,2218,2220,2222,2224,2226,2228,2230,2232,2234,2236,2238,2240,2242,2244,2246,2248,2250,2252,2254,2256,2258,2260,2262,2264,2266,2268,2270,2272,2274,2276,2278,2280,2282,2284,2286,2288,2290,2292,2294,2296,2298,2300,2302,2304,2306,2308,2310,2312,2314,2316,2318,2320,2322,2324,2326,2328,2330,2332,2334,2336,2338,2340,2342,2344,2346,2348,2350,2352,2354,2356,2358,2360,2362,2364,2366,2368,2370,2372,2374,2376,2378,2380,2382,2384,2386,2388,2390,2392,2394,2396,2398,2400,2402,2404,2406,2408,2410,2412,2414,2416,2418,2420,2422,2424,2426,2428,2430,2432,2434,2436,2438,2440,2442,2444,2446,2448,2450,2452,2454,2456,2458,2460,2462,2464,2466,2468,2470,2472,2474,2476,2478,2480,2482,2484,2486,2488,2490,2492,2494,2496,2498,2500,2502,2504,2506,2508,2510,2512,2514,2516,2518,2520,2522,2524,2526,2528,2530,2532,2534,2536,2538,2540,2542,2544,2546,2548,2550,2552,2554,2556,2558,2560,2562,2564,2566,2568,2570,2572,2574,2576,2578,2580,2582,2584,2586,2588,2590,2592,2594,2596,2598,2600,2602,2604,2606,2608,2610,2612,2614,2616,2618,2620,2622,2624,2626,2628,2630,2632,2634,2636,2638,2640,2642,2644,2646,2648,2650,2652,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2674,2676,2678,2680,2682,2684,2686,2688,2690,2692,2694,2696,2698,2700,2702,2704,2706,2708,2710,2712,2714,2716,2718,2720,2722,2724,2726,2728,2730,2732,2734,2736,2738,2740,2742,2744,2746,2748,2750,2752,2754,2756,2758,2760,2762,2764,2766,2768,2770,2772,2774,2776,2778,2780,2782,2784,2786,2788,2790,2792,2794,2796,2798,2800,2802,2804,2806,2808,2810,2812,2814,2816,2818,2820,2822,2824,2826,2828,2830,2832,2834,2836,2838,2840,2842,2844,2846,2848,2850,2852,2854,2856,2858,2860,2862,2864,2866,2868,2870,2872,2874,2876,2878,2880,2882,2884,2886,2888,2890,2892,2894,2896,2898,2900,2902,2904,2906,2908,2910,2912,2914,2916,2918,2920,2922,2924,2926,2928,2930,2932,2934,2936,2938,2940,2942,2944,2946,2948,2950,2952,2954,2956,2958,2960,2962,2964,2966,2968,2970,2972,2974,2976,2978,2980,2982,2984,2986,2988,2990,2992,2994,2996,2998); +-- Query Hash: 6353cb90c7847f4060d03446e2af142c +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k WHERE id in (1000,1003,1006,1009,1012,1015,1018,1021,1024,1027,1030,1033,1036,1039,1042,1045,1048,1051,1054,1057,1060,1063,1066,1069,1072,1075,1078,1081,1084,1087,1090,1093,1096,1099,1102,1105,1108,1111,1114,1117,1120,1123,1126,1129,1132,1135,1138,1141,1144,1147,1150,1153,1156,1159,1162,1165,1168,1171,1174,1177,1180,1183,1186,1189,1192,1195,1198,1201,1204,1207,1210,1213,1216,1219,1222,1225,1228,1231,1234,1237,1240,1243,1246,1249,1252,1255,1258,1261,1264,1267,1270,1273,1276,1279,1282,1285,1288,1291,1294,1297,1300,1303,1306,1309,1312,1315,1318,1321,1324,1327,1330,1333,1336,1339,1342,1345,1348,1351,1354,1357,1360,1363,1366,1369,1372,1375,1378,1381,1384,1387,1390,1393,1396,1399,1402,1405,1408,1411,1414,1417,1420,1423,1426,1429,1432,1435,1438,1441,1444,1447,1450,1453,1456,1459,1462,1465,1468,1471,1474,1477,1480,1483,1486,1489,1492,1495,1498,1501,1504,1507,1510,1513,1516,1519,1522,1525,1528,1531,1534,1537,1540,1543,1546,1549,1552,1555,1558,1561,1564,1567,1570,1573,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651,1654,1657,1660,1663,1666,1669,1672,1675,1678,1681,1684,1687,1690,1693,1696,1699,1702,1705,1708,1711,1714,1717,1720,1723,1726,1729,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759,1762,1765,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1906,1909,1912,1915,1918,1921,1924,1927,1930,1933,1936,1939,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990,1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023,2026,2029,2032,2035,2038,2041,2044,2047,2050,2053,2056,2059,2062,2065,2068,2071,2074,2077,2080,2083,2086,2089,2092,2095,2098,2101,2104,2107,2110,2113,2116,2119,2122,2125,2128,2131,2134,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2167,2170,2173,2176,2179,2182,2185,2188,2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251,2254,2257,2260,2263,2266,2269,2272,2275,2278,2281,2284,2287,2290,2293,2296,2299,2302,2305,2308,2311,2314,2317,2320,2323,2326,2329,2332,2335,2338,2341,2344,2347,2350,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383,2386,2389,2392,2395,2398,2401,2404,2407,2410,2413,2416,2419,2422,2425,2428,2431,2434,2437,2440,2443,2446,2449,2452,2455,2458,2461,2464,2467,2470,2473,2476,2479,2482,2485,2488,2491,2494,2497,2500,2503,2506,2509,2512,2515,2518,2521,2524,2527,2530,2533,2536,2539,2542,2545,2548,2551,2554,2557,2560,2563,2566,2569,2572,2575,2578,2581,2584,2587,2590,2593,2596,2599,2602,2605,2608,2611,2614,2617,2620,2623,2626,2629,2632,2635,2638,2641,2644,2647,2650,2653,2656,2659,2662,2665,2668,2671,2674,2677,2680,2683,2686,2689,2692,2695,2698,2701,2704,2707,2710,2713,2716,2719,2722,2725,2728,2731,2734,2737,2740,2743,2746,2749,2752,2755,2758,2761,2764,2767,2770,2773,2776,2779,2782,2785,2788,2791,2794,2797,2800,2803,2806,2809,2812,2815,2818,2821,2824,2827,2830,2833,2836,2839,2842,2845,2848,2851,2854,2857,2860,2863,2866,2869,2872,2875,2878,2881,2884,2887,2890,2893,2896,2899,2902,2905,2908,2911,2914,2917,2920,2923,2926,2929,2932,2935,2938,2941,2944,2947,2950,2953,2956,2959,2962,2965,2968,2971,2974,2977,2980,2983,2986,2989,2992,2995,2998,3001,3004,3007,3010,3013,3016,3019,3022,3025,3028,3031,3034,3037,3040,3043,3046,3049,3052,3055,3058,3061,3064,3067,3070,3073,3076,3079,3082,3085,3088,3091,3094,3097,3100,3103,3106,3109,3112,3115,3118,3121,3124,3127,3130,3133,3136,3139,3142,3145,3148,3151,3154,3157,3160,3163,3166,3169,3172,3175,3178,3181,3184,3187,3190,3193,3196,3199,3202,3205,3208,3211,3214,3217,3220,3223,3226,3229,3232,3235,3238,3241,3244,3247,3250,3253,3256,3259,3262,3265,3268,3271,3274,3277,3280,3283,3286,3289,3292,3295,3298,3301,3304,3307,3310,3313,3316,3319,3322,3325,3328,3331,3334,3337,3340,3343,3346,3349,3352,3355,3358,3361,3364,3367,3370,3373,3376,3379,3382,3385,3388,3391,3394,3397,3400,3403,3406,3409,3412,3415,3418,3421,3424,3427,3430,3433,3436,3439,3442,3445,3448,3451,3454,3457,3460,3463,3466,3469,3472,3475,3478,3481,3484,3487,3490,3493,3496,3499,3502,3505,3508,3511,3514,3517,3520,3523,3526,3529,3532,3535,3538,3541,3544,3547,3550,3553,3556,3559,3562,3565,3568,3571,3574,3577,3580,3583,3586,3589,3592,3595,3598,3601,3604,3607,3610,3613,3616,3619,3622,3625,3628,3631,3634,3637,3640,3643,3646,3649,3652,3655,3658,3661,3664,3667,3670,3673,3676,3679,3682,3685,3688,3691,3694,3697,3700,3703,3706,3709,3712,3715,3718,3721,3724,3727,3730,3733,3736,3739,3742,3745,3748,3751,3754,3757,3760,3763,3766,3769,3772,3775,3778,3781,3784,3787,3790,3793,3796,3799,3802,3805,3808,3811,3814,3817,3820,3823,3826,3829,3832,3835,3838,3841,3844,3847,3850,3853,3856,3859,3862,3865,3868,3871,3874,3877,3880,3883,3886,3889,3892,3895,3898,3901,3904,3907,3910,3913,3916,3919,3922,3925,3928,3931,3934,3937,3940,3943,3946,3949,3952,3955,3958,3961,3964,3967,3970,3973,3976,3979,3982,3985,3988,3991,3994,3997); +-- Query Hash: 28ccced2188f69d563157387613f7174 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_200k WHERE id in (1000,1003,1006,1009,1012,1015,1018,1021,1024,1027,1030,1033,1036,1039,1042,1045,1048,1051,1054,1057,1060,1063,1066,1069,1072,1075,1078,1081,1084,1087,1090,1093,1096,1099,1102,1105,1108,1111,1114,1117,1120,1123,1126,1129,1132,1135,1138,1141,1144,1147,1150,1153,1156,1159,1162,1165,1168,1171,1174,1177,1180,1183,1186,1189,1192,1195,1198,1201,1204,1207,1210,1213,1216,1219,1222,1225,1228,1231,1234,1237,1240,1243,1246,1249,1252,1255,1258,1261,1264,1267,1270,1273,1276,1279,1282,1285,1288,1291,1294,1297,1300,1303,1306,1309,1312,1315,1318,1321,1324,1327,1330,1333,1336,1339,1342,1345,1348,1351,1354,1357,1360,1363,1366,1369,1372,1375,1378,1381,1384,1387,1390,1393,1396,1399,1402,1405,1408,1411,1414,1417,1420,1423,1426,1429,1432,1435,1438,1441,1444,1447,1450,1453,1456,1459,1462,1465,1468,1471,1474,1477,1480,1483,1486,1489,1492,1495,1498,1501,1504,1507,1510,1513,1516,1519,1522,1525,1528,1531,1534,1537,1540,1543,1546,1549,1552,1555,1558,1561,1564,1567,1570,1573,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651,1654,1657,1660,1663,1666,1669,1672,1675,1678,1681,1684,1687,1690,1693,1696,1699,1702,1705,1708,1711,1714,1717,1720,1723,1726,1729,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759,1762,1765,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1906,1909,1912,1915,1918,1921,1924,1927,1930,1933,1936,1939,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990,1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023,2026,2029,2032,2035,2038,2041,2044,2047,2050,2053,2056,2059,2062,2065,2068,2071,2074,2077,2080,2083,2086,2089,2092,2095,2098,2101,2104,2107,2110,2113,2116,2119,2122,2125,2128,2131,2134,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2167,2170,2173,2176,2179,2182,2185,2188,2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251,2254,2257,2260,2263,2266,2269,2272,2275,2278,2281,2284,2287,2290,2293,2296,2299,2302,2305,2308,2311,2314,2317,2320,2323,2326,2329,2332,2335,2338,2341,2344,2347,2350,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383,2386,2389,2392,2395,2398,2401,2404,2407,2410,2413,2416,2419,2422,2425,2428,2431,2434,2437,2440,2443,2446,2449,2452,2455,2458,2461,2464,2467,2470,2473,2476,2479,2482,2485,2488,2491,2494,2497,2500,2503,2506,2509,2512,2515,2518,2521,2524,2527,2530,2533,2536,2539,2542,2545,2548,2551,2554,2557,2560,2563,2566,2569,2572,2575,2578,2581,2584,2587,2590,2593,2596,2599,2602,2605,2608,2611,2614,2617,2620,2623,2626,2629,2632,2635,2638,2641,2644,2647,2650,2653,2656,2659,2662,2665,2668,2671,2674,2677,2680,2683,2686,2689,2692,2695,2698,2701,2704,2707,2710,2713,2716,2719,2722,2725,2728,2731,2734,2737,2740,2743,2746,2749,2752,2755,2758,2761,2764,2767,2770,2773,2776,2779,2782,2785,2788,2791,2794,2797,2800,2803,2806,2809,2812,2815,2818,2821,2824,2827,2830,2833,2836,2839,2842,2845,2848,2851,2854,2857,2860,2863,2866,2869,2872,2875,2878,2881,2884,2887,2890,2893,2896,2899,2902,2905,2908,2911,2914,2917,2920,2923,2926,2929,2932,2935,2938,2941,2944,2947,2950,2953,2956,2959,2962,2965,2968,2971,2974,2977,2980,2983,2986,2989,2992,2995,2998,3001,3004,3007,3010,3013,3016,3019,3022,3025,3028,3031,3034,3037,3040,3043,3046,3049,3052,3055,3058,3061,3064,3067,3070,3073,3076,3079,3082,3085,3088,3091,3094,3097,3100,3103,3106,3109,3112,3115,3118,3121,3124,3127,3130,3133,3136,3139,3142,3145,3148,3151,3154,3157,3160,3163,3166,3169,3172,3175,3178,3181,3184,3187,3190,3193,3196,3199,3202,3205,3208,3211,3214,3217,3220,3223,3226,3229,3232,3235,3238,3241,3244,3247,3250,3253,3256,3259,3262,3265,3268,3271,3274,3277,3280,3283,3286,3289,3292,3295,3298,3301,3304,3307,3310,3313,3316,3319,3322,3325,3328,3331,3334,3337,3340,3343,3346,3349,3352,3355,3358,3361,3364,3367,3370,3373,3376,3379,3382,3385,3388,3391,3394,3397,3400,3403,3406,3409,3412,3415,3418,3421,3424,3427,3430,3433,3436,3439,3442,3445,3448,3451,3454,3457,3460,3463,3466,3469,3472,3475,3478,3481,3484,3487,3490,3493,3496,3499,3502,3505,3508,3511,3514,3517,3520,3523,3526,3529,3532,3535,3538,3541,3544,3547,3550,3553,3556,3559,3562,3565,3568,3571,3574,3577,3580,3583,3586,3589,3592,3595,3598,3601,3604,3607,3610,3613,3616,3619,3622,3625,3628,3631,3634,3637,3640,3643,3646,3649,3652,3655,3658,3661,3664,3667,3670,3673,3676,3679,3682,3685,3688,3691,3694,3697,3700,3703,3706,3709,3712,3715,3718,3721,3724,3727,3730,3733,3736,3739,3742,3745,3748,3751,3754,3757,3760,3763,3766,3769,3772,3775,3778,3781,3784,3787,3790,3793,3796,3799,3802,3805,3808,3811,3814,3817,3820,3823,3826,3829,3832,3835,3838,3841,3844,3847,3850,3853,3856,3859,3862,3865,3868,3871,3874,3877,3880,3883,3886,3889,3892,3895,3898,3901,3904,3907,3910,3913,3916,3919,3922,3925,3928,3931,3934,3937,3940,3943,3946,3949,3952,3955,3958,3961,3964,3967,3970,3973,3976,3979,3982,3985,3988,3991,3994,3997); +-- Query Hash: c5e2c98de808494dabf3ee86d6b10194 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_400k WHERE id in (1000,1003,1006,1009,1012,1015,1018,1021,1024,1027,1030,1033,1036,1039,1042,1045,1048,1051,1054,1057,1060,1063,1066,1069,1072,1075,1078,1081,1084,1087,1090,1093,1096,1099,1102,1105,1108,1111,1114,1117,1120,1123,1126,1129,1132,1135,1138,1141,1144,1147,1150,1153,1156,1159,1162,1165,1168,1171,1174,1177,1180,1183,1186,1189,1192,1195,1198,1201,1204,1207,1210,1213,1216,1219,1222,1225,1228,1231,1234,1237,1240,1243,1246,1249,1252,1255,1258,1261,1264,1267,1270,1273,1276,1279,1282,1285,1288,1291,1294,1297,1300,1303,1306,1309,1312,1315,1318,1321,1324,1327,1330,1333,1336,1339,1342,1345,1348,1351,1354,1357,1360,1363,1366,1369,1372,1375,1378,1381,1384,1387,1390,1393,1396,1399,1402,1405,1408,1411,1414,1417,1420,1423,1426,1429,1432,1435,1438,1441,1444,1447,1450,1453,1456,1459,1462,1465,1468,1471,1474,1477,1480,1483,1486,1489,1492,1495,1498,1501,1504,1507,1510,1513,1516,1519,1522,1525,1528,1531,1534,1537,1540,1543,1546,1549,1552,1555,1558,1561,1564,1567,1570,1573,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651,1654,1657,1660,1663,1666,1669,1672,1675,1678,1681,1684,1687,1690,1693,1696,1699,1702,1705,1708,1711,1714,1717,1720,1723,1726,1729,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759,1762,1765,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1906,1909,1912,1915,1918,1921,1924,1927,1930,1933,1936,1939,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990,1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023,2026,2029,2032,2035,2038,2041,2044,2047,2050,2053,2056,2059,2062,2065,2068,2071,2074,2077,2080,2083,2086,2089,2092,2095,2098,2101,2104,2107,2110,2113,2116,2119,2122,2125,2128,2131,2134,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2167,2170,2173,2176,2179,2182,2185,2188,2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251,2254,2257,2260,2263,2266,2269,2272,2275,2278,2281,2284,2287,2290,2293,2296,2299,2302,2305,2308,2311,2314,2317,2320,2323,2326,2329,2332,2335,2338,2341,2344,2347,2350,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383,2386,2389,2392,2395,2398,2401,2404,2407,2410,2413,2416,2419,2422,2425,2428,2431,2434,2437,2440,2443,2446,2449,2452,2455,2458,2461,2464,2467,2470,2473,2476,2479,2482,2485,2488,2491,2494,2497,2500,2503,2506,2509,2512,2515,2518,2521,2524,2527,2530,2533,2536,2539,2542,2545,2548,2551,2554,2557,2560,2563,2566,2569,2572,2575,2578,2581,2584,2587,2590,2593,2596,2599,2602,2605,2608,2611,2614,2617,2620,2623,2626,2629,2632,2635,2638,2641,2644,2647,2650,2653,2656,2659,2662,2665,2668,2671,2674,2677,2680,2683,2686,2689,2692,2695,2698,2701,2704,2707,2710,2713,2716,2719,2722,2725,2728,2731,2734,2737,2740,2743,2746,2749,2752,2755,2758,2761,2764,2767,2770,2773,2776,2779,2782,2785,2788,2791,2794,2797,2800,2803,2806,2809,2812,2815,2818,2821,2824,2827,2830,2833,2836,2839,2842,2845,2848,2851,2854,2857,2860,2863,2866,2869,2872,2875,2878,2881,2884,2887,2890,2893,2896,2899,2902,2905,2908,2911,2914,2917,2920,2923,2926,2929,2932,2935,2938,2941,2944,2947,2950,2953,2956,2959,2962,2965,2968,2971,2974,2977,2980,2983,2986,2989,2992,2995,2998,3001,3004,3007,3010,3013,3016,3019,3022,3025,3028,3031,3034,3037,3040,3043,3046,3049,3052,3055,3058,3061,3064,3067,3070,3073,3076,3079,3082,3085,3088,3091,3094,3097,3100,3103,3106,3109,3112,3115,3118,3121,3124,3127,3130,3133,3136,3139,3142,3145,3148,3151,3154,3157,3160,3163,3166,3169,3172,3175,3178,3181,3184,3187,3190,3193,3196,3199,3202,3205,3208,3211,3214,3217,3220,3223,3226,3229,3232,3235,3238,3241,3244,3247,3250,3253,3256,3259,3262,3265,3268,3271,3274,3277,3280,3283,3286,3289,3292,3295,3298,3301,3304,3307,3310,3313,3316,3319,3322,3325,3328,3331,3334,3337,3340,3343,3346,3349,3352,3355,3358,3361,3364,3367,3370,3373,3376,3379,3382,3385,3388,3391,3394,3397,3400,3403,3406,3409,3412,3415,3418,3421,3424,3427,3430,3433,3436,3439,3442,3445,3448,3451,3454,3457,3460,3463,3466,3469,3472,3475,3478,3481,3484,3487,3490,3493,3496,3499,3502,3505,3508,3511,3514,3517,3520,3523,3526,3529,3532,3535,3538,3541,3544,3547,3550,3553,3556,3559,3562,3565,3568,3571,3574,3577,3580,3583,3586,3589,3592,3595,3598,3601,3604,3607,3610,3613,3616,3619,3622,3625,3628,3631,3634,3637,3640,3643,3646,3649,3652,3655,3658,3661,3664,3667,3670,3673,3676,3679,3682,3685,3688,3691,3694,3697,3700,3703,3706,3709,3712,3715,3718,3721,3724,3727,3730,3733,3736,3739,3742,3745,3748,3751,3754,3757,3760,3763,3766,3769,3772,3775,3778,3781,3784,3787,3790,3793,3796,3799,3802,3805,3808,3811,3814,3817,3820,3823,3826,3829,3832,3835,3838,3841,3844,3847,3850,3853,3856,3859,3862,3865,3868,3871,3874,3877,3880,3883,3886,3889,3892,3895,3898,3901,3904,3907,3910,3913,3916,3919,3922,3925,3928,3931,3934,3937,3940,3943,3946,3949,3952,3955,3958,3961,3964,3967,3970,3973,3976,3979,3982,3985,3988,3991,3994,3997); +-- Query Hash: 0d543f722cec0306f1c612a1664f31c3 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_800k WHERE id in (1000,1003,1006,1009,1012,1015,1018,1021,1024,1027,1030,1033,1036,1039,1042,1045,1048,1051,1054,1057,1060,1063,1066,1069,1072,1075,1078,1081,1084,1087,1090,1093,1096,1099,1102,1105,1108,1111,1114,1117,1120,1123,1126,1129,1132,1135,1138,1141,1144,1147,1150,1153,1156,1159,1162,1165,1168,1171,1174,1177,1180,1183,1186,1189,1192,1195,1198,1201,1204,1207,1210,1213,1216,1219,1222,1225,1228,1231,1234,1237,1240,1243,1246,1249,1252,1255,1258,1261,1264,1267,1270,1273,1276,1279,1282,1285,1288,1291,1294,1297,1300,1303,1306,1309,1312,1315,1318,1321,1324,1327,1330,1333,1336,1339,1342,1345,1348,1351,1354,1357,1360,1363,1366,1369,1372,1375,1378,1381,1384,1387,1390,1393,1396,1399,1402,1405,1408,1411,1414,1417,1420,1423,1426,1429,1432,1435,1438,1441,1444,1447,1450,1453,1456,1459,1462,1465,1468,1471,1474,1477,1480,1483,1486,1489,1492,1495,1498,1501,1504,1507,1510,1513,1516,1519,1522,1525,1528,1531,1534,1537,1540,1543,1546,1549,1552,1555,1558,1561,1564,1567,1570,1573,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651,1654,1657,1660,1663,1666,1669,1672,1675,1678,1681,1684,1687,1690,1693,1696,1699,1702,1705,1708,1711,1714,1717,1720,1723,1726,1729,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759,1762,1765,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1906,1909,1912,1915,1918,1921,1924,1927,1930,1933,1936,1939,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990,1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023,2026,2029,2032,2035,2038,2041,2044,2047,2050,2053,2056,2059,2062,2065,2068,2071,2074,2077,2080,2083,2086,2089,2092,2095,2098,2101,2104,2107,2110,2113,2116,2119,2122,2125,2128,2131,2134,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2167,2170,2173,2176,2179,2182,2185,2188,2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251,2254,2257,2260,2263,2266,2269,2272,2275,2278,2281,2284,2287,2290,2293,2296,2299,2302,2305,2308,2311,2314,2317,2320,2323,2326,2329,2332,2335,2338,2341,2344,2347,2350,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383,2386,2389,2392,2395,2398,2401,2404,2407,2410,2413,2416,2419,2422,2425,2428,2431,2434,2437,2440,2443,2446,2449,2452,2455,2458,2461,2464,2467,2470,2473,2476,2479,2482,2485,2488,2491,2494,2497,2500,2503,2506,2509,2512,2515,2518,2521,2524,2527,2530,2533,2536,2539,2542,2545,2548,2551,2554,2557,2560,2563,2566,2569,2572,2575,2578,2581,2584,2587,2590,2593,2596,2599,2602,2605,2608,2611,2614,2617,2620,2623,2626,2629,2632,2635,2638,2641,2644,2647,2650,2653,2656,2659,2662,2665,2668,2671,2674,2677,2680,2683,2686,2689,2692,2695,2698,2701,2704,2707,2710,2713,2716,2719,2722,2725,2728,2731,2734,2737,2740,2743,2746,2749,2752,2755,2758,2761,2764,2767,2770,2773,2776,2779,2782,2785,2788,2791,2794,2797,2800,2803,2806,2809,2812,2815,2818,2821,2824,2827,2830,2833,2836,2839,2842,2845,2848,2851,2854,2857,2860,2863,2866,2869,2872,2875,2878,2881,2884,2887,2890,2893,2896,2899,2902,2905,2908,2911,2914,2917,2920,2923,2926,2929,2932,2935,2938,2941,2944,2947,2950,2953,2956,2959,2962,2965,2968,2971,2974,2977,2980,2983,2986,2989,2992,2995,2998,3001,3004,3007,3010,3013,3016,3019,3022,3025,3028,3031,3034,3037,3040,3043,3046,3049,3052,3055,3058,3061,3064,3067,3070,3073,3076,3079,3082,3085,3088,3091,3094,3097,3100,3103,3106,3109,3112,3115,3118,3121,3124,3127,3130,3133,3136,3139,3142,3145,3148,3151,3154,3157,3160,3163,3166,3169,3172,3175,3178,3181,3184,3187,3190,3193,3196,3199,3202,3205,3208,3211,3214,3217,3220,3223,3226,3229,3232,3235,3238,3241,3244,3247,3250,3253,3256,3259,3262,3265,3268,3271,3274,3277,3280,3283,3286,3289,3292,3295,3298,3301,3304,3307,3310,3313,3316,3319,3322,3325,3328,3331,3334,3337,3340,3343,3346,3349,3352,3355,3358,3361,3364,3367,3370,3373,3376,3379,3382,3385,3388,3391,3394,3397,3400,3403,3406,3409,3412,3415,3418,3421,3424,3427,3430,3433,3436,3439,3442,3445,3448,3451,3454,3457,3460,3463,3466,3469,3472,3475,3478,3481,3484,3487,3490,3493,3496,3499,3502,3505,3508,3511,3514,3517,3520,3523,3526,3529,3532,3535,3538,3541,3544,3547,3550,3553,3556,3559,3562,3565,3568,3571,3574,3577,3580,3583,3586,3589,3592,3595,3598,3601,3604,3607,3610,3613,3616,3619,3622,3625,3628,3631,3634,3637,3640,3643,3646,3649,3652,3655,3658,3661,3664,3667,3670,3673,3676,3679,3682,3685,3688,3691,3694,3697,3700,3703,3706,3709,3712,3715,3718,3721,3724,3727,3730,3733,3736,3739,3742,3745,3748,3751,3754,3757,3760,3763,3766,3769,3772,3775,3778,3781,3784,3787,3790,3793,3796,3799,3802,3805,3808,3811,3814,3817,3820,3823,3826,3829,3832,3835,3838,3841,3844,3847,3850,3853,3856,3859,3862,3865,3868,3871,3874,3877,3880,3883,3886,3889,3892,3895,3898,3901,3904,3907,3910,3913,3916,3919,3922,3925,3928,3931,3934,3937,3940,3943,3946,3949,3952,3955,3958,3961,3964,3967,3970,3973,3976,3979,3982,3985,3988,3991,3994,3997); +-- Query Hash: f59aff38836fd18f7660e9d299b323d5 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: 46c5f5b6c31d62cf0eedf95fa5869254 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_200k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: 82e1555fc50aae7a4634243c4179f8f4 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_400k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: f075a79f20910f7f9fb02cf29d58e408 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_800k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: 2444a94079c56124c3e124b23ca914e5 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023); +-- Query Hash: 6ec5b30f1571b8d2a011100c8e0c7a11 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1002,1004,1006,1008,1010,1012,1014,1016,1018,1020,1022,1024,1026,1028,1030,1032,1034,1036,1038,1040,1042,1044,1046,1048,1050,1052,1054,1056,1058,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086,1088,1090,1092,1094,1096,1098,1100,1102,1104,1106,1108,1110,1112,1114,1116,1118,1120,1122,1124,1126,1128,1130,1132,1134,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156,1158,1160,1162,1164,1166,1168,1170,1172,1174,1176,1178,1180,1182,1184,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204,1206,1208,1210,1212,1214,1216,1218,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246,1248,1250,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286,1288,1290,1292,1294,1296,1298,1300,1302,1304,1306,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326,1328,1330,1332,1334,1336,1338,1340,1342,1344,1346,1348,1350,1352,1354,1356,1358,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386,1388,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410,1412,1414,1416,1418,1420,1422,1424,1426,1428,1430,1432,1434,1436,1438,1440,1442,1444,1446,1448,1450,1452,1454,1456,1458,1460,1462,1464,1466,1468,1470,1472,1474,1476,1478,1480,1482,1484,1486,1488,1490,1492,1494,1496,1498,1500,1502,1504,1506,1508,1510,1512,1514,1516,1518,1520,1522,1524,1526,1528,1530,1532,1534,1536,1538,1540,1542,1544,1546,1548,1550,1552,1554,1556,1558,1560,1562,1564,1566,1568,1570,1572,1574,1576,1578,1580,1582,1584,1586,1588,1590,1592,1594,1596,1598,1600,1602,1604,1606,1608,1610,1612,1614,1616,1618,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638,1640,1642,1644,1646,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688,1690,1692,1694,1696,1698,1700,1702,1704,1706,1708,1710,1712,1714,1716,1718,1720,1722,1724,1726,1728,1730,1732,1734,1736,1738,1740,1742,1744,1746,1748,1750,1752,1754,1756,1758,1760,1762,1764,1766,1768,1770,1772,1774,1776,1778,1780,1782,1784,1786,1788,1790,1792,1794,1796,1798,1800,1802,1804,1806,1808,1810,1812,1814,1816,1818,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862,1864,1866,1868,1870,1872,1874,1876,1878,1880,1882,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926,1928,1930,1932,1934,1936,1938,1940,1942,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964,1966,1968,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988,1990,1992,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2028,2030,2032,2034,2036,2038,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068,2070,2072,2074,2076,2078,2080,2082,2084,2086,2088,2090,2092,2094,2096,2098,2100,2102,2104,2106,2108,2110,2112,2114,2116,2118,2120,2122,2124,2126,2128,2130,2132,2134,2136,2138,2140,2142,2144,2146,2148,2150,2152,2154,2156,2158,2160,2162,2164,2166,2168,2170,2172,2174,2176,2178,2180,2182,2184,2186,2188,2190,2192,2194,2196,2198,2200,2202,2204,2206,2208,2210,2212,2214,2216,2218,2220,2222,2224,2226,2228,2230,2232,2234,2236,2238,2240,2242,2244,2246,2248,2250,2252,2254,2256,2258,2260,2262,2264,2266,2268,2270,2272,2274,2276,2278,2280,2282,2284,2286,2288,2290,2292,2294,2296,2298,2300,2302,2304,2306,2308,2310,2312,2314,2316,2318,2320,2322,2324,2326,2328,2330,2332,2334,2336,2338,2340,2342,2344,2346,2348,2350,2352,2354,2356,2358,2360,2362,2364,2366,2368,2370,2372,2374,2376,2378,2380,2382,2384,2386,2388,2390,2392,2394,2396,2398,2400,2402,2404,2406,2408,2410,2412,2414,2416,2418,2420,2422,2424,2426,2428,2430,2432,2434,2436,2438,2440,2442,2444,2446,2448,2450,2452,2454,2456,2458,2460,2462,2464,2466,2468,2470,2472,2474,2476,2478,2480,2482,2484,2486,2488,2490,2492,2494,2496,2498,2500,2502,2504,2506,2508,2510,2512,2514,2516,2518,2520,2522,2524,2526,2528,2530,2532,2534,2536,2538,2540,2542,2544,2546,2548,2550,2552,2554,2556,2558,2560,2562,2564,2566,2568,2570,2572,2574,2576,2578,2580,2582,2584,2586,2588,2590,2592,2594,2596,2598,2600,2602,2604,2606,2608,2610,2612,2614,2616,2618,2620,2622,2624,2626,2628,2630,2632,2634,2636,2638,2640,2642,2644,2646,2648,2650,2652,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2674,2676,2678,2680,2682,2684,2686,2688,2690,2692,2694,2696,2698,2700,2702,2704,2706,2708,2710,2712,2714,2716,2718,2720,2722,2724,2726,2728,2730,2732,2734,2736,2738,2740,2742,2744,2746,2748,2750,2752,2754,2756,2758,2760,2762,2764,2766,2768,2770,2772,2774,2776,2778,2780,2782,2784,2786,2788,2790,2792,2794,2796,2798,2800,2802,2804,2806,2808,2810,2812,2814,2816,2818,2820,2822,2824,2826,2828,2830,2832,2834,2836,2838,2840,2842,2844,2846,2848,2850,2852,2854,2856,2858,2860,2862,2864,2866,2868,2870,2872,2874,2876,2878,2880,2882,2884,2886,2888,2890,2892,2894,2896,2898,2900,2902,2904,2906,2908,2910,2912,2914,2916,2918,2920,2922,2924,2926,2928,2930,2932,2934,2936,2938,2940,2942,2944,2946,2948,2950,2952,2954,2956,2958,2960,2962,2964,2966,2968,2970,2972,2974,2976,2978,2980,2982,2984,2986,2988,2990,2992,2994,2996,2998,3000,3002,3004,3006,3008,3010,3012,3014,3016,3018,3020,3022,3024,3026,3028,3030,3032,3034,3036,3038,3040,3042,3044,3046); +-- Query Hash: ba1ed806efe431ffb9055da4e7369d8d +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1003,1006,1009,1012,1015,1018,1021,1024,1027,1030,1033,1036,1039,1042,1045,1048,1051,1054,1057,1060,1063,1066,1069,1072,1075,1078,1081,1084,1087,1090,1093,1096,1099,1102,1105,1108,1111,1114,1117,1120,1123,1126,1129,1132,1135,1138,1141,1144,1147,1150,1153,1156,1159,1162,1165,1168,1171,1174,1177,1180,1183,1186,1189,1192,1195,1198,1201,1204,1207,1210,1213,1216,1219,1222,1225,1228,1231,1234,1237,1240,1243,1246,1249,1252,1255,1258,1261,1264,1267,1270,1273,1276,1279,1282,1285,1288,1291,1294,1297,1300,1303,1306,1309,1312,1315,1318,1321,1324,1327,1330,1333,1336,1339,1342,1345,1348,1351,1354,1357,1360,1363,1366,1369,1372,1375,1378,1381,1384,1387,1390,1393,1396,1399,1402,1405,1408,1411,1414,1417,1420,1423,1426,1429,1432,1435,1438,1441,1444,1447,1450,1453,1456,1459,1462,1465,1468,1471,1474,1477,1480,1483,1486,1489,1492,1495,1498,1501,1504,1507,1510,1513,1516,1519,1522,1525,1528,1531,1534,1537,1540,1543,1546,1549,1552,1555,1558,1561,1564,1567,1570,1573,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651,1654,1657,1660,1663,1666,1669,1672,1675,1678,1681,1684,1687,1690,1693,1696,1699,1702,1705,1708,1711,1714,1717,1720,1723,1726,1729,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759,1762,1765,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1906,1909,1912,1915,1918,1921,1924,1927,1930,1933,1936,1939,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990,1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023,2026,2029,2032,2035,2038,2041,2044,2047,2050,2053,2056,2059,2062,2065,2068,2071,2074,2077,2080,2083,2086,2089,2092,2095,2098,2101,2104,2107,2110,2113,2116,2119,2122,2125,2128,2131,2134,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2167,2170,2173,2176,2179,2182,2185,2188,2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2224,2227,2230,2233,2236,2239,2242,2245,2248,2251,2254,2257,2260,2263,2266,2269,2272,2275,2278,2281,2284,2287,2290,2293,2296,2299,2302,2305,2308,2311,2314,2317,2320,2323,2326,2329,2332,2335,2338,2341,2344,2347,2350,2353,2356,2359,2362,2365,2368,2371,2374,2377,2380,2383,2386,2389,2392,2395,2398,2401,2404,2407,2410,2413,2416,2419,2422,2425,2428,2431,2434,2437,2440,2443,2446,2449,2452,2455,2458,2461,2464,2467,2470,2473,2476,2479,2482,2485,2488,2491,2494,2497,2500,2503,2506,2509,2512,2515,2518,2521,2524,2527,2530,2533,2536,2539,2542,2545,2548,2551,2554,2557,2560,2563,2566,2569,2572,2575,2578,2581,2584,2587,2590,2593,2596,2599,2602,2605,2608,2611,2614,2617,2620,2623,2626,2629,2632,2635,2638,2641,2644,2647,2650,2653,2656,2659,2662,2665,2668,2671,2674,2677,2680,2683,2686,2689,2692,2695,2698,2701,2704,2707,2710,2713,2716,2719,2722,2725,2728,2731,2734,2737,2740,2743,2746,2749,2752,2755,2758,2761,2764,2767,2770,2773,2776,2779,2782,2785,2788,2791,2794,2797,2800,2803,2806,2809,2812,2815,2818,2821,2824,2827,2830,2833,2836,2839,2842,2845,2848,2851,2854,2857,2860,2863,2866,2869,2872,2875,2878,2881,2884,2887,2890,2893,2896,2899,2902,2905,2908,2911,2914,2917,2920,2923,2926,2929,2932,2935,2938,2941,2944,2947,2950,2953,2956,2959,2962,2965,2968,2971,2974,2977,2980,2983,2986,2989,2992,2995,2998,3001,3004,3007,3010,3013,3016,3019,3022,3025,3028,3031,3034,3037,3040,3043,3046,3049,3052,3055,3058,3061,3064,3067,3070,3073,3076,3079,3082,3085,3088,3091,3094,3097,3100,3103,3106,3109,3112,3115,3118,3121,3124,3127,3130,3133,3136,3139,3142,3145,3148,3151,3154,3157,3160,3163,3166,3169,3172,3175,3178,3181,3184,3187,3190,3193,3196,3199,3202,3205,3208,3211,3214,3217,3220,3223,3226,3229,3232,3235,3238,3241,3244,3247,3250,3253,3256,3259,3262,3265,3268,3271,3274,3277,3280,3283,3286,3289,3292,3295,3298,3301,3304,3307,3310,3313,3316,3319,3322,3325,3328,3331,3334,3337,3340,3343,3346,3349,3352,3355,3358,3361,3364,3367,3370,3373,3376,3379,3382,3385,3388,3391,3394,3397,3400,3403,3406,3409,3412,3415,3418,3421,3424,3427,3430,3433,3436,3439,3442,3445,3448,3451,3454,3457,3460,3463,3466,3469,3472,3475,3478,3481,3484,3487,3490,3493,3496,3499,3502,3505,3508,3511,3514,3517,3520,3523,3526,3529,3532,3535,3538,3541,3544,3547,3550,3553,3556,3559,3562,3565,3568,3571,3574,3577,3580,3583,3586,3589,3592,3595,3598,3601,3604,3607,3610,3613,3616,3619,3622,3625,3628,3631,3634,3637,3640,3643,3646,3649,3652,3655,3658,3661,3664,3667,3670,3673,3676,3679,3682,3685,3688,3691,3694,3697,3700,3703,3706,3709,3712,3715,3718,3721,3724,3727,3730,3733,3736,3739,3742,3745,3748,3751,3754,3757,3760,3763,3766,3769,3772,3775,3778,3781,3784,3787,3790,3793,3796,3799,3802,3805,3808,3811,3814,3817,3820,3823,3826,3829,3832,3835,3838,3841,3844,3847,3850,3853,3856,3859,3862,3865,3868,3871,3874,3877,3880,3883,3886,3889,3892,3895,3898,3901,3904,3907,3910,3913,3916,3919,3922,3925,3928,3931,3934,3937,3940,3943,3946,3949,3952,3955,3958,3961,3964,3967,3970,3973,3976,3979,3982,3985,3988,3991,3994,3997,4000,4003,4006,4009,4012,4015,4018,4021,4024,4027,4030,4033,4036,4039,4042,4045,4048,4051,4054,4057,4060,4063,4066,4069); +-- Query Hash: d928fd8ee21dca9352127853c7736c00 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996,5000,5004,5008,5012,5016,5020,5024,5028,5032,5036,5040,5044,5048,5052,5056,5060,5064,5068,5072,5076,5080,5084,5088,5092); +-- Query Hash: af8f366ef942a5bd39311c440b01c88d +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1008,1016,1024,1032,1040,1048,1056,1064,1072,1080,1088,1096,1104,1112,1120,1128,1136,1144,1152,1160,1168,1176,1184,1192,1200,1208,1216,1224,1232,1240,1248,1256,1264,1272,1280,1288,1296,1304,1312,1320,1328,1336,1344,1352,1360,1368,1376,1384,1392,1400,1408,1416,1424,1432,1440,1448,1456,1464,1472,1480,1488,1496,1504,1512,1520,1528,1536,1544,1552,1560,1568,1576,1584,1592,1600,1608,1616,1624,1632,1640,1648,1656,1664,1672,1680,1688,1696,1704,1712,1720,1728,1736,1744,1752,1760,1768,1776,1784,1792,1800,1808,1816,1824,1832,1840,1848,1856,1864,1872,1880,1888,1896,1904,1912,1920,1928,1936,1944,1952,1960,1968,1976,1984,1992,2000,2008,2016,2024,2032,2040,2048,2056,2064,2072,2080,2088,2096,2104,2112,2120,2128,2136,2144,2152,2160,2168,2176,2184,2192,2200,2208,2216,2224,2232,2240,2248,2256,2264,2272,2280,2288,2296,2304,2312,2320,2328,2336,2344,2352,2360,2368,2376,2384,2392,2400,2408,2416,2424,2432,2440,2448,2456,2464,2472,2480,2488,2496,2504,2512,2520,2528,2536,2544,2552,2560,2568,2576,2584,2592,2600,2608,2616,2624,2632,2640,2648,2656,2664,2672,2680,2688,2696,2704,2712,2720,2728,2736,2744,2752,2760,2768,2776,2784,2792,2800,2808,2816,2824,2832,2840,2848,2856,2864,2872,2880,2888,2896,2904,2912,2920,2928,2936,2944,2952,2960,2968,2976,2984,2992,3000,3008,3016,3024,3032,3040,3048,3056,3064,3072,3080,3088,3096,3104,3112,3120,3128,3136,3144,3152,3160,3168,3176,3184,3192,3200,3208,3216,3224,3232,3240,3248,3256,3264,3272,3280,3288,3296,3304,3312,3320,3328,3336,3344,3352,3360,3368,3376,3384,3392,3400,3408,3416,3424,3432,3440,3448,3456,3464,3472,3480,3488,3496,3504,3512,3520,3528,3536,3544,3552,3560,3568,3576,3584,3592,3600,3608,3616,3624,3632,3640,3648,3656,3664,3672,3680,3688,3696,3704,3712,3720,3728,3736,3744,3752,3760,3768,3776,3784,3792,3800,3808,3816,3824,3832,3840,3848,3856,3864,3872,3880,3888,3896,3904,3912,3920,3928,3936,3944,3952,3960,3968,3976,3984,3992,4000,4008,4016,4024,4032,4040,4048,4056,4064,4072,4080,4088,4096,4104,4112,4120,4128,4136,4144,4152,4160,4168,4176,4184,4192,4200,4208,4216,4224,4232,4240,4248,4256,4264,4272,4280,4288,4296,4304,4312,4320,4328,4336,4344,4352,4360,4368,4376,4384,4392,4400,4408,4416,4424,4432,4440,4448,4456,4464,4472,4480,4488,4496,4504,4512,4520,4528,4536,4544,4552,4560,4568,4576,4584,4592,4600,4608,4616,4624,4632,4640,4648,4656,4664,4672,4680,4688,4696,4704,4712,4720,4728,4736,4744,4752,4760,4768,4776,4784,4792,4800,4808,4816,4824,4832,4840,4848,4856,4864,4872,4880,4888,4896,4904,4912,4920,4928,4936,4944,4952,4960,4968,4976,4984,4992,5000,5008,5016,5024,5032,5040,5048,5056,5064,5072,5080,5088,5096,5104,5112,5120,5128,5136,5144,5152,5160,5168,5176,5184,5192,5200,5208,5216,5224,5232,5240,5248,5256,5264,5272,5280,5288,5296,5304,5312,5320,5328,5336,5344,5352,5360,5368,5376,5384,5392,5400,5408,5416,5424,5432,5440,5448,5456,5464,5472,5480,5488,5496,5504,5512,5520,5528,5536,5544,5552,5560,5568,5576,5584,5592,5600,5608,5616,5624,5632,5640,5648,5656,5664,5672,5680,5688,5696,5704,5712,5720,5728,5736,5744,5752,5760,5768,5776,5784,5792,5800,5808,5816,5824,5832,5840,5848,5856,5864,5872,5880,5888,5896,5904,5912,5920,5928,5936,5944,5952,5960,5968,5976,5984,5992,6000,6008,6016,6024,6032,6040,6048,6056,6064,6072,6080,6088,6096,6104,6112,6120,6128,6136,6144,6152,6160,6168,6176,6184,6192,6200,6208,6216,6224,6232,6240,6248,6256,6264,6272,6280,6288,6296,6304,6312,6320,6328,6336,6344,6352,6360,6368,6376,6384,6392,6400,6408,6416,6424,6432,6440,6448,6456,6464,6472,6480,6488,6496,6504,6512,6520,6528,6536,6544,6552,6560,6568,6576,6584,6592,6600,6608,6616,6624,6632,6640,6648,6656,6664,6672,6680,6688,6696,6704,6712,6720,6728,6736,6744,6752,6760,6768,6776,6784,6792,6800,6808,6816,6824,6832,6840,6848,6856,6864,6872,6880,6888,6896,6904,6912,6920,6928,6936,6944,6952,6960,6968,6976,6984,6992,7000,7008,7016,7024,7032,7040,7048,7056,7064,7072,7080,7088,7096,7104,7112,7120,7128,7136,7144,7152,7160,7168,7176,7184,7192,7200,7208,7216,7224,7232,7240,7248,7256,7264,7272,7280,7288,7296,7304,7312,7320,7328,7336,7344,7352,7360,7368,7376,7384,7392,7400,7408,7416,7424,7432,7440,7448,7456,7464,7472,7480,7488,7496,7504,7512,7520,7528,7536,7544,7552,7560,7568,7576,7584,7592,7600,7608,7616,7624,7632,7640,7648,7656,7664,7672,7680,7688,7696,7704,7712,7720,7728,7736,7744,7752,7760,7768,7776,7784,7792,7800,7808,7816,7824,7832,7840,7848,7856,7864,7872,7880,7888,7896,7904,7912,7920,7928,7936,7944,7952,7960,7968,7976,7984,7992,8000,8008,8016,8024,8032,8040,8048,8056,8064,8072,8080,8088,8096,8104,8112,8120,8128,8136,8144,8152,8160,8168,8176,8184,8192,8200,8208,8216,8224,8232,8240,8248,8256,8264,8272,8280,8288,8296,8304,8312,8320,8328,8336,8344,8352,8360,8368,8376,8384,8392,8400,8408,8416,8424,8432,8440,8448,8456,8464,8472,8480,8488,8496,8504,8512,8520,8528,8536,8544,8552,8560,8568,8576,8584,8592,8600,8608,8616,8624,8632,8640,8648,8656,8664,8672,8680,8688,8696,8704,8712,8720,8728,8736,8744,8752,8760,8768,8776,8784,8792,8800,8808,8816,8824,8832,8840,8848,8856,8864,8872,8880,8888,8896,8904,8912,8920,8928,8936,8944,8952,8960,8968,8976,8984,8992,9000,9008,9016,9024,9032,9040,9048,9056,9064,9072,9080,9088,9096,9104,9112,9120,9128,9136,9144,9152,9160,9168,9176,9184); +-- Query Hash: f0de79f07f4f6df232396000f8f27b85 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1016,1032,1048,1064,1080,1096,1112,1128,1144,1160,1176,1192,1208,1224,1240,1256,1272,1288,1304,1320,1336,1352,1368,1384,1400,1416,1432,1448,1464,1480,1496,1512,1528,1544,1560,1576,1592,1608,1624,1640,1656,1672,1688,1704,1720,1736,1752,1768,1784,1800,1816,1832,1848,1864,1880,1896,1912,1928,1944,1960,1976,1992,2008,2024,2040,2056,2072,2088,2104,2120,2136,2152,2168,2184,2200,2216,2232,2248,2264,2280,2296,2312,2328,2344,2360,2376,2392,2408,2424,2440,2456,2472,2488,2504,2520,2536,2552,2568,2584,2600,2616,2632,2648,2664,2680,2696,2712,2728,2744,2760,2776,2792,2808,2824,2840,2856,2872,2888,2904,2920,2936,2952,2968,2984,3000,3016,3032,3048,3064,3080,3096,3112,3128,3144,3160,3176,3192,3208,3224,3240,3256,3272,3288,3304,3320,3336,3352,3368,3384,3400,3416,3432,3448,3464,3480,3496,3512,3528,3544,3560,3576,3592,3608,3624,3640,3656,3672,3688,3704,3720,3736,3752,3768,3784,3800,3816,3832,3848,3864,3880,3896,3912,3928,3944,3960,3976,3992,4008,4024,4040,4056,4072,4088,4104,4120,4136,4152,4168,4184,4200,4216,4232,4248,4264,4280,4296,4312,4328,4344,4360,4376,4392,4408,4424,4440,4456,4472,4488,4504,4520,4536,4552,4568,4584,4600,4616,4632,4648,4664,4680,4696,4712,4728,4744,4760,4776,4792,4808,4824,4840,4856,4872,4888,4904,4920,4936,4952,4968,4984,5000,5016,5032,5048,5064,5080,5096,5112,5128,5144,5160,5176,5192,5208,5224,5240,5256,5272,5288,5304,5320,5336,5352,5368,5384,5400,5416,5432,5448,5464,5480,5496,5512,5528,5544,5560,5576,5592,5608,5624,5640,5656,5672,5688,5704,5720,5736,5752,5768,5784,5800,5816,5832,5848,5864,5880,5896,5912,5928,5944,5960,5976,5992,6008,6024,6040,6056,6072,6088,6104,6120,6136,6152,6168,6184,6200,6216,6232,6248,6264,6280,6296,6312,6328,6344,6360,6376,6392,6408,6424,6440,6456,6472,6488,6504,6520,6536,6552,6568,6584,6600,6616,6632,6648,6664,6680,6696,6712,6728,6744,6760,6776,6792,6808,6824,6840,6856,6872,6888,6904,6920,6936,6952,6968,6984,7000,7016,7032,7048,7064,7080,7096,7112,7128,7144,7160,7176,7192,7208,7224,7240,7256,7272,7288,7304,7320,7336,7352,7368,7384,7400,7416,7432,7448,7464,7480,7496,7512,7528,7544,7560,7576,7592,7608,7624,7640,7656,7672,7688,7704,7720,7736,7752,7768,7784,7800,7816,7832,7848,7864,7880,7896,7912,7928,7944,7960,7976,7992,8008,8024,8040,8056,8072,8088,8104,8120,8136,8152,8168,8184,8200,8216,8232,8248,8264,8280,8296,8312,8328,8344,8360,8376,8392,8408,8424,8440,8456,8472,8488,8504,8520,8536,8552,8568,8584,8600,8616,8632,8648,8664,8680,8696,8712,8728,8744,8760,8776,8792,8808,8824,8840,8856,8872,8888,8904,8920,8936,8952,8968,8984,9000,9016,9032,9048,9064,9080,9096,9112,9128,9144,9160,9176,9192,9208,9224,9240,9256,9272,9288,9304,9320,9336,9352,9368,9384,9400,9416,9432,9448,9464,9480,9496,9512,9528,9544,9560,9576,9592,9608,9624,9640,9656,9672,9688,9704,9720,9736,9752,9768,9784,9800,9816,9832,9848,9864,9880,9896,9912,9928,9944,9960,9976,9992,10008,10024,10040,10056,10072,10088,10104,10120,10136,10152,10168,10184,10200,10216,10232,10248,10264,10280,10296,10312,10328,10344,10360,10376,10392,10408,10424,10440,10456,10472,10488,10504,10520,10536,10552,10568,10584,10600,10616,10632,10648,10664,10680,10696,10712,10728,10744,10760,10776,10792,10808,10824,10840,10856,10872,10888,10904,10920,10936,10952,10968,10984,11000,11016,11032,11048,11064,11080,11096,11112,11128,11144,11160,11176,11192,11208,11224,11240,11256,11272,11288,11304,11320,11336,11352,11368,11384,11400,11416,11432,11448,11464,11480,11496,11512,11528,11544,11560,11576,11592,11608,11624,11640,11656,11672,11688,11704,11720,11736,11752,11768,11784,11800,11816,11832,11848,11864,11880,11896,11912,11928,11944,11960,11976,11992,12008,12024,12040,12056,12072,12088,12104,12120,12136,12152,12168,12184,12200,12216,12232,12248,12264,12280,12296,12312,12328,12344,12360,12376,12392,12408,12424,12440,12456,12472,12488,12504,12520,12536,12552,12568,12584,12600,12616,12632,12648,12664,12680,12696,12712,12728,12744,12760,12776,12792,12808,12824,12840,12856,12872,12888,12904,12920,12936,12952,12968,12984,13000,13016,13032,13048,13064,13080,13096,13112,13128,13144,13160,13176,13192,13208,13224,13240,13256,13272,13288,13304,13320,13336,13352,13368,13384,13400,13416,13432,13448,13464,13480,13496,13512,13528,13544,13560,13576,13592,13608,13624,13640,13656,13672,13688,13704,13720,13736,13752,13768,13784,13800,13816,13832,13848,13864,13880,13896,13912,13928,13944,13960,13976,13992,14008,14024,14040,14056,14072,14088,14104,14120,14136,14152,14168,14184,14200,14216,14232,14248,14264,14280,14296,14312,14328,14344,14360,14376,14392,14408,14424,14440,14456,14472,14488,14504,14520,14536,14552,14568,14584,14600,14616,14632,14648,14664,14680,14696,14712,14728,14744,14760,14776,14792,14808,14824,14840,14856,14872,14888,14904,14920,14936,14952,14968,14984,15000,15016,15032,15048,15064,15080,15096,15112,15128,15144,15160,15176,15192,15208,15224,15240,15256,15272,15288,15304,15320,15336,15352,15368,15384,15400,15416,15432,15448,15464,15480,15496,15512,15528,15544,15560,15576,15592,15608,15624,15640,15656,15672,15688,15704,15720,15736,15752,15768,15784,15800,15816,15832,15848,15864,15880,15896,15912,15928,15944,15960,15976,15992,16008,16024,16040,16056,16072,16088,16104,16120,16136,16152,16168,16184,16200,16216,16232,16248,16264,16280,16296,16312,16328,16344,16360,16376,16392,16408,16424,16440,16456,16472,16488,16504,16520,16536,16552,16568,16584,16600,16616,16632,16648,16664,16680,16696,16712,16728,16744,16760,16776,16792,16808,16824,16840,16856,16872,16888,16904,16920,16936,16952,16968,16984,17000,17016,17032,17048,17064,17080,17096,17112,17128,17144,17160,17176,17192,17208,17224,17240,17256,17272,17288,17304,17320,17336,17352,17368); +-- Query Hash: f8da829631ba24b3db7241bc649461a8 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1032,1064,1096,1128,1160,1192,1224,1256,1288,1320,1352,1384,1416,1448,1480,1512,1544,1576,1608,1640,1672,1704,1736,1768,1800,1832,1864,1896,1928,1960,1992,2024,2056,2088,2120,2152,2184,2216,2248,2280,2312,2344,2376,2408,2440,2472,2504,2536,2568,2600,2632,2664,2696,2728,2760,2792,2824,2856,2888,2920,2952,2984,3016,3048,3080,3112,3144,3176,3208,3240,3272,3304,3336,3368,3400,3432,3464,3496,3528,3560,3592,3624,3656,3688,3720,3752,3784,3816,3848,3880,3912,3944,3976,4008,4040,4072,4104,4136,4168,4200,4232,4264,4296,4328,4360,4392,4424,4456,4488,4520,4552,4584,4616,4648,4680,4712,4744,4776,4808,4840,4872,4904,4936,4968,5000,5032,5064,5096,5128,5160,5192,5224,5256,5288,5320,5352,5384,5416,5448,5480,5512,5544,5576,5608,5640,5672,5704,5736,5768,5800,5832,5864,5896,5928,5960,5992,6024,6056,6088,6120,6152,6184,6216,6248,6280,6312,6344,6376,6408,6440,6472,6504,6536,6568,6600,6632,6664,6696,6728,6760,6792,6824,6856,6888,6920,6952,6984,7016,7048,7080,7112,7144,7176,7208,7240,7272,7304,7336,7368,7400,7432,7464,7496,7528,7560,7592,7624,7656,7688,7720,7752,7784,7816,7848,7880,7912,7944,7976,8008,8040,8072,8104,8136,8168,8200,8232,8264,8296,8328,8360,8392,8424,8456,8488,8520,8552,8584,8616,8648,8680,8712,8744,8776,8808,8840,8872,8904,8936,8968,9000,9032,9064,9096,9128,9160,9192,9224,9256,9288,9320,9352,9384,9416,9448,9480,9512,9544,9576,9608,9640,9672,9704,9736,9768,9800,9832,9864,9896,9928,9960,9992,10024,10056,10088,10120,10152,10184,10216,10248,10280,10312,10344,10376,10408,10440,10472,10504,10536,10568,10600,10632,10664,10696,10728,10760,10792,10824,10856,10888,10920,10952,10984,11016,11048,11080,11112,11144,11176,11208,11240,11272,11304,11336,11368,11400,11432,11464,11496,11528,11560,11592,11624,11656,11688,11720,11752,11784,11816,11848,11880,11912,11944,11976,12008,12040,12072,12104,12136,12168,12200,12232,12264,12296,12328,12360,12392,12424,12456,12488,12520,12552,12584,12616,12648,12680,12712,12744,12776,12808,12840,12872,12904,12936,12968,13000,13032,13064,13096,13128,13160,13192,13224,13256,13288,13320,13352,13384,13416,13448,13480,13512,13544,13576,13608,13640,13672,13704,13736,13768,13800,13832,13864,13896,13928,13960,13992,14024,14056,14088,14120,14152,14184,14216,14248,14280,14312,14344,14376,14408,14440,14472,14504,14536,14568,14600,14632,14664,14696,14728,14760,14792,14824,14856,14888,14920,14952,14984,15016,15048,15080,15112,15144,15176,15208,15240,15272,15304,15336,15368,15400,15432,15464,15496,15528,15560,15592,15624,15656,15688,15720,15752,15784,15816,15848,15880,15912,15944,15976,16008,16040,16072,16104,16136,16168,16200,16232,16264,16296,16328,16360,16392,16424,16456,16488,16520,16552,16584,16616,16648,16680,16712,16744,16776,16808,16840,16872,16904,16936,16968,17000,17032,17064,17096,17128,17160,17192,17224,17256,17288,17320,17352,17384,17416,17448,17480,17512,17544,17576,17608,17640,17672,17704,17736,17768,17800,17832,17864,17896,17928,17960,17992,18024,18056,18088,18120,18152,18184,18216,18248,18280,18312,18344,18376,18408,18440,18472,18504,18536,18568,18600,18632,18664,18696,18728,18760,18792,18824,18856,18888,18920,18952,18984,19016,19048,19080,19112,19144,19176,19208,19240,19272,19304,19336,19368,19400,19432,19464,19496,19528,19560,19592,19624,19656,19688,19720,19752,19784,19816,19848,19880,19912,19944,19976,20008,20040,20072,20104,20136,20168,20200,20232,20264,20296,20328,20360,20392,20424,20456,20488,20520,20552,20584,20616,20648,20680,20712,20744,20776,20808,20840,20872,20904,20936,20968,21000,21032,21064,21096,21128,21160,21192,21224,21256,21288,21320,21352,21384,21416,21448,21480,21512,21544,21576,21608,21640,21672,21704,21736,21768,21800,21832,21864,21896,21928,21960,21992,22024,22056,22088,22120,22152,22184,22216,22248,22280,22312,22344,22376,22408,22440,22472,22504,22536,22568,22600,22632,22664,22696,22728,22760,22792,22824,22856,22888,22920,22952,22984,23016,23048,23080,23112,23144,23176,23208,23240,23272,23304,23336,23368,23400,23432,23464,23496,23528,23560,23592,23624,23656,23688,23720,23752,23784,23816,23848,23880,23912,23944,23976,24008,24040,24072,24104,24136,24168,24200,24232,24264,24296,24328,24360,24392,24424,24456,24488,24520,24552,24584,24616,24648,24680,24712,24744,24776,24808,24840,24872,24904,24936,24968,25000,25032,25064,25096,25128,25160,25192,25224,25256,25288,25320,25352,25384,25416,25448,25480,25512,25544,25576,25608,25640,25672,25704,25736,25768,25800,25832,25864,25896,25928,25960,25992,26024,26056,26088,26120,26152,26184,26216,26248,26280,26312,26344,26376,26408,26440,26472,26504,26536,26568,26600,26632,26664,26696,26728,26760,26792,26824,26856,26888,26920,26952,26984,27016,27048,27080,27112,27144,27176,27208,27240,27272,27304,27336,27368,27400,27432,27464,27496,27528,27560,27592,27624,27656,27688,27720,27752,27784,27816,27848,27880,27912,27944,27976,28008,28040,28072,28104,28136,28168,28200,28232,28264,28296,28328,28360,28392,28424,28456,28488,28520,28552,28584,28616,28648,28680,28712,28744,28776,28808,28840,28872,28904,28936,28968,29000,29032,29064,29096,29128,29160,29192,29224,29256,29288,29320,29352,29384,29416,29448,29480,29512,29544,29576,29608,29640,29672,29704,29736,29768,29800,29832,29864,29896,29928,29960,29992,30024,30056,30088,30120,30152,30184,30216,30248,30280,30312,30344,30376,30408,30440,30472,30504,30536,30568,30600,30632,30664,30696,30728,30760,30792,30824,30856,30888,30920,30952,30984,31016,31048,31080,31112,31144,31176,31208,31240,31272,31304,31336,31368,31400,31432,31464,31496,31528,31560,31592,31624,31656,31688,31720,31752,31784,31816,31848,31880,31912,31944,31976,32008,32040,32072,32104,32136,32168,32200,32232,32264,32296,32328,32360,32392,32424,32456,32488,32520,32552,32584,32616,32648,32680,32712,32744,32776,32808,32840,32872,32904,32936,32968,33000,33032,33064,33096,33128,33160,33192,33224,33256,33288,33320,33352,33384,33416,33448,33480,33512,33544,33576,33608,33640,33672,33704,33736); +-- Query Hash: 63a381a2e9fbaf5fc8674e3b48484155 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1064,1128,1192,1256,1320,1384,1448,1512,1576,1640,1704,1768,1832,1896,1960,2024,2088,2152,2216,2280,2344,2408,2472,2536,2600,2664,2728,2792,2856,2920,2984,3048,3112,3176,3240,3304,3368,3432,3496,3560,3624,3688,3752,3816,3880,3944,4008,4072,4136,4200,4264,4328,4392,4456,4520,4584,4648,4712,4776,4840,4904,4968,5032,5096,5160,5224,5288,5352,5416,5480,5544,5608,5672,5736,5800,5864,5928,5992,6056,6120,6184,6248,6312,6376,6440,6504,6568,6632,6696,6760,6824,6888,6952,7016,7080,7144,7208,7272,7336,7400,7464,7528,7592,7656,7720,7784,7848,7912,7976,8040,8104,8168,8232,8296,8360,8424,8488,8552,8616,8680,8744,8808,8872,8936,9000,9064,9128,9192,9256,9320,9384,9448,9512,9576,9640,9704,9768,9832,9896,9960,10024,10088,10152,10216,10280,10344,10408,10472,10536,10600,10664,10728,10792,10856,10920,10984,11048,11112,11176,11240,11304,11368,11432,11496,11560,11624,11688,11752,11816,11880,11944,12008,12072,12136,12200,12264,12328,12392,12456,12520,12584,12648,12712,12776,12840,12904,12968,13032,13096,13160,13224,13288,13352,13416,13480,13544,13608,13672,13736,13800,13864,13928,13992,14056,14120,14184,14248,14312,14376,14440,14504,14568,14632,14696,14760,14824,14888,14952,15016,15080,15144,15208,15272,15336,15400,15464,15528,15592,15656,15720,15784,15848,15912,15976,16040,16104,16168,16232,16296,16360,16424,16488,16552,16616,16680,16744,16808,16872,16936,17000,17064,17128,17192,17256,17320,17384,17448,17512,17576,17640,17704,17768,17832,17896,17960,18024,18088,18152,18216,18280,18344,18408,18472,18536,18600,18664,18728,18792,18856,18920,18984,19048,19112,19176,19240,19304,19368,19432,19496,19560,19624,19688,19752,19816,19880,19944,20008,20072,20136,20200,20264,20328,20392,20456,20520,20584,20648,20712,20776,20840,20904,20968,21032,21096,21160,21224,21288,21352,21416,21480,21544,21608,21672,21736,21800,21864,21928,21992,22056,22120,22184,22248,22312,22376,22440,22504,22568,22632,22696,22760,22824,22888,22952,23016,23080,23144,23208,23272,23336,23400,23464,23528,23592,23656,23720,23784,23848,23912,23976,24040,24104,24168,24232,24296,24360,24424,24488,24552,24616,24680,24744,24808,24872,24936,25000,25064,25128,25192,25256,25320,25384,25448,25512,25576,25640,25704,25768,25832,25896,25960,26024,26088,26152,26216,26280,26344,26408,26472,26536,26600,26664,26728,26792,26856,26920,26984,27048,27112,27176,27240,27304,27368,27432,27496,27560,27624,27688,27752,27816,27880,27944,28008,28072,28136,28200,28264,28328,28392,28456,28520,28584,28648,28712,28776,28840,28904,28968,29032,29096,29160,29224,29288,29352,29416,29480,29544,29608,29672,29736,29800,29864,29928,29992,30056,30120,30184,30248,30312,30376,30440,30504,30568,30632,30696,30760,30824,30888,30952,31016,31080,31144,31208,31272,31336,31400,31464,31528,31592,31656,31720,31784,31848,31912,31976,32040,32104,32168,32232,32296,32360,32424,32488,32552,32616,32680,32744,32808,32872,32936,33000,33064,33128,33192,33256,33320,33384,33448,33512,33576,33640,33704,33768,33832,33896,33960,34024,34088,34152,34216,34280,34344,34408,34472,34536,34600,34664,34728,34792,34856,34920,34984,35048,35112,35176,35240,35304,35368,35432,35496,35560,35624,35688,35752,35816,35880,35944,36008,36072,36136,36200,36264,36328,36392,36456,36520,36584,36648,36712,36776,36840,36904,36968,37032,37096,37160,37224,37288,37352,37416,37480,37544,37608,37672,37736,37800,37864,37928,37992,38056,38120,38184,38248,38312,38376,38440,38504,38568,38632,38696,38760,38824,38888,38952,39016,39080,39144,39208,39272,39336,39400,39464,39528,39592,39656,39720,39784,39848,39912,39976,40040,40104,40168,40232,40296,40360,40424,40488,40552,40616,40680,40744,40808,40872,40936,41000,41064,41128,41192,41256,41320,41384,41448,41512,41576,41640,41704,41768,41832,41896,41960,42024,42088,42152,42216,42280,42344,42408,42472,42536,42600,42664,42728,42792,42856,42920,42984,43048,43112,43176,43240,43304,43368,43432,43496,43560,43624,43688,43752,43816,43880,43944,44008,44072,44136,44200,44264,44328,44392,44456,44520,44584,44648,44712,44776,44840,44904,44968,45032,45096,45160,45224,45288,45352,45416,45480,45544,45608,45672,45736,45800,45864,45928,45992,46056,46120,46184,46248,46312,46376,46440,46504,46568,46632,46696,46760,46824,46888,46952,47016,47080,47144,47208,47272,47336,47400,47464,47528,47592,47656,47720,47784,47848,47912,47976,48040,48104,48168,48232,48296,48360,48424,48488,48552,48616,48680,48744,48808,48872,48936,49000,49064,49128,49192,49256,49320,49384,49448,49512,49576,49640,49704,49768,49832,49896,49960,50024,50088,50152,50216,50280,50344,50408,50472,50536,50600,50664,50728,50792,50856,50920,50984,51048,51112,51176,51240,51304,51368,51432,51496,51560,51624,51688,51752,51816,51880,51944,52008,52072,52136,52200,52264,52328,52392,52456,52520,52584,52648,52712,52776,52840,52904,52968,53032,53096,53160,53224,53288,53352,53416,53480,53544,53608,53672,53736,53800,53864,53928,53992,54056,54120,54184,54248,54312,54376,54440,54504,54568,54632,54696,54760,54824,54888,54952,55016,55080,55144,55208,55272,55336,55400,55464,55528,55592,55656,55720,55784,55848,55912,55976,56040,56104,56168,56232,56296,56360,56424,56488,56552,56616,56680,56744,56808,56872,56936,57000,57064,57128,57192,57256,57320,57384,57448,57512,57576,57640,57704,57768,57832,57896,57960,58024,58088,58152,58216,58280,58344,58408,58472,58536,58600,58664,58728,58792,58856,58920,58984,59048,59112,59176,59240,59304,59368,59432,59496,59560,59624,59688,59752,59816,59880,59944,60008,60072,60136,60200,60264,60328,60392,60456,60520,60584,60648,60712,60776,60840,60904,60968,61032,61096,61160,61224,61288,61352,61416,61480,61544,61608,61672,61736,61800,61864,61928,61992,62056,62120,62184,62248,62312,62376,62440,62504,62568,62632,62696,62760,62824,62888,62952,63016,63080,63144,63208,63272,63336,63400,63464,63528,63592,63656,63720,63784,63848,63912,63976,64040,64104,64168,64232,64296,64360,64424,64488,64552,64616,64680,64744,64808,64872,64936,65000,65064,65128,65192,65256,65320,65384,65448,65512,65576,65640,65704,65768,65832,65896,65960,66024,66088,66152,66216,66280,66344,66408,66472); +-- Query Hash: e979851191d7883496b42c049695fbd9 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1128,1256,1384,1512,1640,1768,1896,2024,2152,2280,2408,2536,2664,2792,2920,3048,3176,3304,3432,3560,3688,3816,3944,4072,4200,4328,4456,4584,4712,4840,4968,5096,5224,5352,5480,5608,5736,5864,5992,6120,6248,6376,6504,6632,6760,6888,7016,7144,7272,7400,7528,7656,7784,7912,8040,8168,8296,8424,8552,8680,8808,8936,9064,9192,9320,9448,9576,9704,9832,9960,10088,10216,10344,10472,10600,10728,10856,10984,11112,11240,11368,11496,11624,11752,11880,12008,12136,12264,12392,12520,12648,12776,12904,13032,13160,13288,13416,13544,13672,13800,13928,14056,14184,14312,14440,14568,14696,14824,14952,15080,15208,15336,15464,15592,15720,15848,15976,16104,16232,16360,16488,16616,16744,16872,17000,17128,17256,17384,17512,17640,17768,17896,18024,18152,18280,18408,18536,18664,18792,18920,19048,19176,19304,19432,19560,19688,19816,19944,20072,20200,20328,20456,20584,20712,20840,20968,21096,21224,21352,21480,21608,21736,21864,21992,22120,22248,22376,22504,22632,22760,22888,23016,23144,23272,23400,23528,23656,23784,23912,24040,24168,24296,24424,24552,24680,24808,24936,25064,25192,25320,25448,25576,25704,25832,25960,26088,26216,26344,26472,26600,26728,26856,26984,27112,27240,27368,27496,27624,27752,27880,28008,28136,28264,28392,28520,28648,28776,28904,29032,29160,29288,29416,29544,29672,29800,29928,30056,30184,30312,30440,30568,30696,30824,30952,31080,31208,31336,31464,31592,31720,31848,31976,32104,32232,32360,32488,32616,32744,32872,33000,33128,33256,33384,33512,33640,33768,33896,34024,34152,34280,34408,34536,34664,34792,34920,35048,35176,35304,35432,35560,35688,35816,35944,36072,36200,36328,36456,36584,36712,36840,36968,37096,37224,37352,37480,37608,37736,37864,37992,38120,38248,38376,38504,38632,38760,38888,39016,39144,39272,39400,39528,39656,39784,39912,40040,40168,40296,40424,40552,40680,40808,40936,41064,41192,41320,41448,41576,41704,41832,41960,42088,42216,42344,42472,42600,42728,42856,42984,43112,43240,43368,43496,43624,43752,43880,44008,44136,44264,44392,44520,44648,44776,44904,45032,45160,45288,45416,45544,45672,45800,45928,46056,46184,46312,46440,46568,46696,46824,46952,47080,47208,47336,47464,47592,47720,47848,47976,48104,48232,48360,48488,48616,48744,48872,49000,49128,49256,49384,49512,49640,49768,49896,50024,50152,50280,50408,50536,50664,50792,50920,51048,51176,51304,51432,51560,51688,51816,51944,52072,52200,52328,52456,52584,52712,52840,52968,53096,53224,53352,53480,53608,53736,53864,53992,54120,54248,54376,54504,54632,54760,54888,55016,55144,55272,55400,55528,55656,55784,55912,56040,56168,56296,56424,56552,56680,56808,56936,57064,57192,57320,57448,57576,57704,57832,57960,58088,58216,58344,58472,58600,58728,58856,58984,59112,59240,59368,59496,59624,59752,59880,60008,60136,60264,60392,60520,60648,60776,60904,61032,61160,61288,61416,61544,61672,61800,61928,62056,62184,62312,62440,62568,62696,62824,62952,63080,63208,63336,63464,63592,63720,63848,63976,64104,64232,64360,64488,64616,64744,64872,65000,65128,65256,65384,65512,65640,65768,65896,66024,66152,66280,66408,66536,66664,66792,66920,67048,67176,67304,67432,67560,67688,67816,67944,68072,68200,68328,68456,68584,68712,68840,68968,69096,69224,69352,69480,69608,69736,69864,69992,70120,70248,70376,70504,70632,70760,70888,71016,71144,71272,71400,71528,71656,71784,71912,72040,72168,72296,72424,72552,72680,72808,72936,73064,73192,73320,73448,73576,73704,73832,73960,74088,74216,74344,74472,74600,74728,74856,74984,75112,75240,75368,75496,75624,75752,75880,76008,76136,76264,76392,76520,76648,76776,76904,77032,77160,77288,77416,77544,77672,77800,77928,78056,78184,78312,78440,78568,78696,78824,78952,79080,79208,79336,79464,79592,79720,79848,79976,80104,80232,80360,80488,80616,80744,80872,81000,81128,81256,81384,81512,81640,81768,81896,82024,82152,82280,82408,82536,82664,82792,82920,83048,83176,83304,83432,83560,83688,83816,83944,84072,84200,84328,84456,84584,84712,84840,84968,85096,85224,85352,85480,85608,85736,85864,85992,86120,86248,86376,86504,86632,86760,86888,87016,87144,87272,87400,87528,87656,87784,87912,88040,88168,88296,88424,88552,88680,88808,88936,89064,89192,89320,89448,89576,89704,89832,89960,90088,90216,90344,90472,90600,90728,90856,90984,91112,91240,91368,91496,91624,91752,91880,92008,92136,92264,92392,92520,92648,92776,92904,93032,93160,93288,93416,93544,93672,93800,93928,94056,94184,94312,94440,94568,94696,94824,94952,95080,95208,95336,95464,95592,95720,95848,95976,96104,96232,96360,96488,96616,96744,96872,97000,97128,97256,97384,97512,97640,97768,97896,98024,98152,98280,98408,98536,98664,98792,98920,99048,99176,99304,99432,99560,99688,99816,99944,100072,100200,100328,100456,100584,100712,100840,100968,101096,101224,101352,101480,101608,101736,101864,101992,102120,102248,102376,102504,102632,102760,102888,103016,103144,103272,103400,103528,103656,103784,103912,104040,104168,104296,104424,104552,104680,104808,104936,105064,105192,105320,105448,105576,105704,105832,105960,106088,106216,106344,106472,106600,106728,106856,106984,107112,107240,107368,107496,107624,107752,107880,108008,108136,108264,108392,108520,108648,108776,108904,109032,109160,109288,109416,109544,109672,109800,109928,110056,110184,110312,110440,110568,110696,110824,110952,111080,111208,111336,111464,111592,111720,111848,111976,112104,112232,112360,112488,112616,112744,112872,113000,113128,113256,113384,113512,113640,113768,113896,114024,114152,114280,114408,114536,114664,114792,114920,115048,115176,115304,115432,115560,115688,115816,115944,116072,116200,116328,116456,116584,116712,116840,116968,117096,117224,117352,117480,117608,117736,117864,117992,118120,118248,118376,118504,118632,118760,118888,119016,119144,119272,119400,119528,119656,119784,119912,120040,120168,120296,120424,120552,120680,120808,120936,121064,121192,121320,121448,121576,121704,121832,121960,122088,122216,122344,122472,122600,122728,122856,122984,123112,123240,123368,123496,123624,123752,123880,124008,124136,124264,124392,124520,124648,124776,124904,125032,125160,125288,125416,125544,125672,125800,125928,126056,126184,126312,126440,126568,126696,126824,126952,127080,127208,127336,127464,127592,127720,127848,127976,128104,128232,128360,128488,128616,128744,128872,129000,129128,129256,129384,129512,129640,129768,129896,130024,130152,130280,130408,130536,130664,130792,130920,131048,131176,131304,131432,131560,131688,131816,131944); +-- Query Hash: 4777b3359cf3c919e54e6a6368b5daec +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1256,1512,1768,2024,2280,2536,2792,3048,3304,3560,3816,4072,4328,4584,4840,5096,5352,5608,5864,6120,6376,6632,6888,7144,7400,7656,7912,8168,8424,8680,8936,9192,9448,9704,9960,10216,10472,10728,10984,11240,11496,11752,12008,12264,12520,12776,13032,13288,13544,13800,14056,14312,14568,14824,15080,15336,15592,15848,16104,16360,16616,16872,17128,17384,17640,17896,18152,18408,18664,18920,19176,19432,19688,19944,20200,20456,20712,20968,21224,21480,21736,21992,22248,22504,22760,23016,23272,23528,23784,24040,24296,24552,24808,25064,25320,25576,25832,26088,26344,26600,26856,27112,27368,27624,27880,28136,28392,28648,28904,29160,29416,29672,29928,30184,30440,30696,30952,31208,31464,31720,31976,32232,32488,32744,33000,33256,33512,33768,34024,34280,34536,34792,35048,35304,35560,35816,36072,36328,36584,36840,37096,37352,37608,37864,38120,38376,38632,38888,39144,39400,39656,39912,40168,40424,40680,40936,41192,41448,41704,41960,42216,42472,42728,42984,43240,43496,43752,44008,44264,44520,44776,45032,45288,45544,45800,46056,46312,46568,46824,47080,47336,47592,47848,48104,48360,48616,48872,49128,49384,49640,49896,50152,50408,50664,50920,51176,51432,51688,51944,52200,52456,52712,52968,53224,53480,53736,53992,54248,54504,54760,55016,55272,55528,55784,56040,56296,56552,56808,57064,57320,57576,57832,58088,58344,58600,58856,59112,59368,59624,59880,60136,60392,60648,60904,61160,61416,61672,61928,62184,62440,62696,62952,63208,63464,63720,63976,64232,64488,64744,65000,65256,65512,65768,66024,66280,66536,66792,67048,67304,67560,67816,68072,68328,68584,68840,69096,69352,69608,69864,70120,70376,70632,70888,71144,71400,71656,71912,72168,72424,72680,72936,73192,73448,73704,73960,74216,74472,74728,74984,75240,75496,75752,76008,76264,76520,76776,77032,77288,77544,77800,78056,78312,78568,78824,79080,79336,79592,79848,80104,80360,80616,80872,81128,81384,81640,81896,82152,82408,82664,82920,83176,83432,83688,83944,84200,84456,84712,84968,85224,85480,85736,85992,86248,86504,86760,87016,87272,87528,87784,88040,88296,88552,88808,89064,89320,89576,89832,90088,90344,90600,90856,91112,91368,91624,91880,92136,92392,92648,92904,93160,93416,93672,93928,94184,94440,94696,94952,95208,95464,95720,95976,96232,96488,96744,97000,97256,97512,97768,98024,98280,98536,98792,99048,99304,99560,99816,100072,100328,100584,100840,101096,101352,101608,101864,102120,102376,102632,102888,103144,103400,103656,103912,104168,104424,104680,104936,105192,105448,105704,105960,106216,106472,106728,106984,107240,107496,107752,108008,108264,108520,108776,109032,109288,109544,109800,110056,110312,110568,110824,111080,111336,111592,111848,112104,112360,112616,112872,113128,113384,113640,113896,114152,114408,114664,114920,115176,115432,115688,115944,116200,116456,116712,116968,117224,117480,117736,117992,118248,118504,118760,119016,119272,119528,119784,120040,120296,120552,120808,121064,121320,121576,121832,122088,122344,122600,122856,123112,123368,123624,123880,124136,124392,124648,124904,125160,125416,125672,125928,126184,126440,126696,126952,127208,127464,127720,127976,128232,128488,128744,129000,129256,129512,129768,130024,130280,130536,130792,131048,131304,131560,131816,132072,132328,132584,132840,133096,133352,133608,133864,134120,134376,134632,134888,135144,135400,135656,135912,136168,136424,136680,136936,137192,137448,137704,137960,138216,138472,138728,138984,139240,139496,139752,140008,140264,140520,140776,141032,141288,141544,141800,142056,142312,142568,142824,143080,143336,143592,143848,144104,144360,144616,144872,145128,145384,145640,145896,146152,146408,146664,146920,147176,147432,147688,147944,148200,148456,148712,148968,149224,149480,149736,149992,150248,150504,150760,151016,151272,151528,151784,152040,152296,152552,152808,153064,153320,153576,153832,154088,154344,154600,154856,155112,155368,155624,155880,156136,156392,156648,156904,157160,157416,157672,157928,158184,158440,158696,158952,159208,159464,159720,159976,160232,160488,160744,161000,161256,161512,161768,162024,162280,162536,162792,163048,163304,163560,163816,164072,164328,164584,164840,165096,165352,165608,165864,166120,166376,166632,166888,167144,167400,167656,167912,168168,168424,168680,168936,169192,169448,169704,169960,170216,170472,170728,170984,171240,171496,171752,172008,172264,172520,172776,173032,173288,173544,173800,174056,174312,174568,174824,175080,175336,175592,175848,176104,176360,176616,176872,177128,177384,177640,177896,178152,178408,178664,178920,179176,179432,179688,179944,180200,180456,180712,180968,181224,181480,181736,181992,182248,182504,182760,183016,183272,183528,183784,184040,184296,184552,184808,185064,185320,185576,185832,186088,186344,186600,186856,187112,187368,187624,187880,188136,188392,188648,188904,189160,189416,189672,189928,190184,190440,190696,190952,191208,191464,191720,191976,192232,192488,192744,193000,193256,193512,193768,194024,194280,194536,194792,195048,195304,195560,195816,196072,196328,196584,196840,197096,197352,197608,197864,198120,198376,198632,198888,199144,199400,199656,199912,200168,200424,200680,200936,201192,201448,201704,201960,202216,202472,202728,202984,203240,203496,203752,204008,204264,204520,204776,205032,205288,205544,205800,206056,206312,206568,206824,207080,207336,207592,207848,208104,208360,208616,208872,209128,209384,209640,209896,210152,210408,210664,210920,211176,211432,211688,211944,212200,212456,212712,212968,213224,213480,213736,213992,214248,214504,214760,215016,215272,215528,215784,216040,216296,216552,216808,217064,217320,217576,217832,218088,218344,218600,218856,219112,219368,219624,219880,220136,220392,220648,220904,221160,221416,221672,221928,222184,222440,222696,222952,223208,223464,223720,223976,224232,224488,224744,225000,225256,225512,225768,226024,226280,226536,226792,227048,227304,227560,227816,228072,228328,228584,228840,229096,229352,229608,229864,230120,230376,230632,230888,231144,231400,231656,231912,232168,232424,232680,232936,233192,233448,233704,233960,234216,234472,234728,234984,235240,235496,235752,236008,236264,236520,236776,237032,237288,237544,237800,238056,238312,238568,238824,239080,239336,239592,239848,240104,240360,240616,240872,241128,241384,241640,241896,242152,242408,242664,242920,243176,243432,243688,243944,244200,244456,244712,244968,245224,245480,245736,245992,246248,246504,246760,247016,247272,247528,247784,248040,248296,248552,248808,249064,249320,249576,249832,250088,250344,250600,250856,251112,251368,251624,251880,252136,252392,252648,252904,253160,253416,253672,253928,254184,254440,254696,254952,255208,255464,255720,255976,256232,256488,256744,257000,257256,257512,257768,258024,258280,258536,258792,259048,259304,259560,259816,260072,260328,260584,260840,261096,261352,261608,261864,262120,262376,262632,262888); +-- Query Hash: 29adb2092878b6b245bee82f409a5fc3 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1512,2024,2536,3048,3560,4072,4584,5096,5608,6120,6632,7144,7656,8168,8680,9192,9704,10216,10728,11240,11752,12264,12776,13288,13800,14312,14824,15336,15848,16360,16872,17384,17896,18408,18920,19432,19944,20456,20968,21480,21992,22504,23016,23528,24040,24552,25064,25576,26088,26600,27112,27624,28136,28648,29160,29672,30184,30696,31208,31720,32232,32744,33256,33768,34280,34792,35304,35816,36328,36840,37352,37864,38376,38888,39400,39912,40424,40936,41448,41960,42472,42984,43496,44008,44520,45032,45544,46056,46568,47080,47592,48104,48616,49128,49640,50152,50664,51176,51688,52200,52712,53224,53736,54248,54760,55272,55784,56296,56808,57320,57832,58344,58856,59368,59880,60392,60904,61416,61928,62440,62952,63464,63976,64488,65000,65512,66024,66536,67048,67560,68072,68584,69096,69608,70120,70632,71144,71656,72168,72680,73192,73704,74216,74728,75240,75752,76264,76776,77288,77800,78312,78824,79336,79848,80360,80872,81384,81896,82408,82920,83432,83944,84456,84968,85480,85992,86504,87016,87528,88040,88552,89064,89576,90088,90600,91112,91624,92136,92648,93160,93672,94184,94696,95208,95720,96232,96744,97256,97768,98280,98792,99304,99816,100328,100840,101352,101864,102376,102888,103400,103912,104424,104936,105448,105960,106472,106984,107496,108008,108520,109032,109544,110056,110568,111080,111592,112104,112616,113128,113640,114152,114664,115176,115688,116200,116712,117224,117736,118248,118760,119272,119784,120296,120808,121320,121832,122344,122856,123368,123880,124392,124904,125416,125928,126440,126952,127464,127976,128488,129000,129512,130024,130536,131048,131560,132072,132584,133096,133608,134120,134632,135144,135656,136168,136680,137192,137704,138216,138728,139240,139752,140264,140776,141288,141800,142312,142824,143336,143848,144360,144872,145384,145896,146408,146920,147432,147944,148456,148968,149480,149992,150504,151016,151528,152040,152552,153064,153576,154088,154600,155112,155624,156136,156648,157160,157672,158184,158696,159208,159720,160232,160744,161256,161768,162280,162792,163304,163816,164328,164840,165352,165864,166376,166888,167400,167912,168424,168936,169448,169960,170472,170984,171496,172008,172520,173032,173544,174056,174568,175080,175592,176104,176616,177128,177640,178152,178664,179176,179688,180200,180712,181224,181736,182248,182760,183272,183784,184296,184808,185320,185832,186344,186856,187368,187880,188392,188904,189416,189928,190440,190952,191464,191976,192488,193000,193512,194024,194536,195048,195560,196072,196584,197096,197608,198120,198632,199144,199656,200168,200680,201192,201704,202216,202728,203240,203752,204264,204776,205288,205800,206312,206824,207336,207848,208360,208872,209384,209896,210408,210920,211432,211944,212456,212968,213480,213992,214504,215016,215528,216040,216552,217064,217576,218088,218600,219112,219624,220136,220648,221160,221672,222184,222696,223208,223720,224232,224744,225256,225768,226280,226792,227304,227816,228328,228840,229352,229864,230376,230888,231400,231912,232424,232936,233448,233960,234472,234984,235496,236008,236520,237032,237544,238056,238568,239080,239592,240104,240616,241128,241640,242152,242664,243176,243688,244200,244712,245224,245736,246248,246760,247272,247784,248296,248808,249320,249832,250344,250856,251368,251880,252392,252904,253416,253928,254440,254952,255464,255976,256488,257000,257512,258024,258536,259048,259560,260072,260584,261096,261608,262120,262632,263144,263656,264168,264680,265192,265704,266216,266728,267240,267752,268264,268776,269288,269800,270312,270824,271336,271848,272360,272872,273384,273896,274408,274920,275432,275944,276456,276968,277480,277992,278504,279016,279528,280040,280552,281064,281576,282088,282600,283112,283624,284136,284648,285160,285672,286184,286696,287208,287720,288232,288744,289256,289768,290280,290792,291304,291816,292328,292840,293352,293864,294376,294888,295400,295912,296424,296936,297448,297960,298472,298984,299496,300008,300520,301032,301544,302056,302568,303080,303592,304104,304616,305128,305640,306152,306664,307176,307688,308200,308712,309224,309736,310248,310760,311272,311784,312296,312808,313320,313832,314344,314856,315368,315880,316392,316904,317416,317928,318440,318952,319464,319976,320488,321000,321512,322024,322536,323048,323560,324072,324584,325096,325608,326120,326632,327144,327656,328168,328680,329192,329704,330216,330728,331240,331752,332264,332776,333288,333800,334312,334824,335336,335848,336360,336872,337384,337896,338408,338920,339432,339944,340456,340968,341480,341992,342504,343016,343528,344040,344552,345064,345576,346088,346600,347112,347624,348136,348648,349160,349672,350184,350696,351208,351720,352232,352744,353256,353768,354280,354792,355304,355816,356328,356840,357352,357864,358376,358888,359400,359912,360424,360936,361448,361960,362472,362984,363496,364008,364520,365032,365544,366056,366568,367080,367592,368104,368616,369128,369640,370152,370664,371176,371688,372200,372712,373224,373736,374248,374760,375272,375784,376296,376808,377320,377832,378344,378856,379368,379880,380392,380904,381416,381928,382440,382952,383464,383976,384488,385000,385512,386024,386536,387048,387560,388072,388584,389096,389608,390120,390632,391144,391656,392168,392680,393192,393704,394216,394728,395240,395752,396264,396776,397288,397800,398312,398824,399336,399848,400360,400872,401384,401896,402408,402920,403432,403944,404456,404968,405480,405992,406504,407016,407528,408040,408552,409064,409576,410088,410600,411112,411624,412136,412648,413160,413672,414184,414696,415208,415720,416232,416744,417256,417768,418280,418792,419304,419816,420328,420840,421352,421864,422376,422888,423400,423912,424424,424936,425448,425960,426472,426984,427496,428008,428520,429032,429544,430056,430568,431080,431592,432104,432616,433128,433640,434152,434664,435176,435688,436200,436712,437224,437736,438248,438760,439272,439784,440296,440808,441320,441832,442344,442856,443368,443880,444392,444904,445416,445928,446440,446952,447464,447976,448488,449000,449512,450024,450536,451048,451560,452072,452584,453096,453608,454120,454632,455144,455656,456168,456680,457192,457704,458216,458728,459240,459752,460264,460776,461288,461800,462312,462824,463336,463848,464360,464872,465384,465896,466408,466920,467432,467944,468456,468968,469480,469992,470504,471016,471528,472040,472552,473064,473576,474088,474600,475112,475624,476136,476648,477160,477672,478184,478696,479208,479720,480232,480744,481256,481768,482280,482792,483304,483816,484328,484840,485352,485864,486376,486888,487400,487912,488424,488936,489448,489960,490472,490984,491496,492008,492520,493032,493544,494056,494568,495080,495592,496104,496616,497128,497640,498152,498664,499176,499688,500200,500712,501224,501736,502248,502760,503272,503784,504296,504808,505320,505832,506344,506856,507368,507880,508392,508904,509416,509928,510440,510952,511464,511976,512488,513000,513512,514024,514536,515048,515560,516072,516584,517096,517608,518120,518632,519144,519656,520168,520680,521192,521704,522216,522728,523240,523752,524264,524776); +-- Query Hash: d22c2e8fce342bd83398e5c1574c5b23 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,21480,41960,62440,82920,103400,123880,144360,164840,185320,205800,226280,246760,267240,287720,308200,328680,349160,369640,390120,410600,431080,451560,472040,492520,513000,533480,553960,574440,594920,615400,635880); +-- Query Hash: 233effbf5f668d651f11a4ab853a6b00 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,11240,21480,31720,41960,52200,62440,72680,82920,93160,103400,113640,123880,134120,144360,154600,164840,175080,185320,195560,205800,216040,226280,236520,246760,257000,267240,277480,287720,297960,308200,318440,328680,338920,349160,359400,369640,379880,390120,400360,410600,420840,431080,441320,451560,461800,472040,482280,492520,502760,513000,523240,533480,543720,553960,564200,574440,584680,594920,605160,615400,625640,635880,646120); +-- Query Hash: 84b5c9fc4a13ea5ec97d1cbd9309221a +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,6120,11240,16360,21480,26600,31720,36840,41960,47080,52200,57320,62440,67560,72680,77800,82920,88040,93160,98280,103400,108520,113640,118760,123880,129000,134120,139240,144360,149480,154600,159720,164840,169960,175080,180200,185320,190440,195560,200680,205800,210920,216040,221160,226280,231400,236520,241640,246760,251880,257000,262120,267240,272360,277480,282600,287720,292840,297960,303080,308200,313320,318440,323560,328680,333800,338920,344040,349160,354280,359400,364520,369640,374760,379880,385000,390120,395240,400360,405480,410600,415720,420840,425960,431080,436200,441320,446440,451560,456680,461800,466920,472040,477160,482280,487400,492520,497640,502760,507880,513000,518120,523240,528360,533480,538600,543720,548840,553960,559080,564200,569320,574440,579560,584680,589800,594920,600040,605160,610280,615400,620520,625640,630760,635880,641000,646120,651240); +-- Query Hash: 01c06e209b2d767dd50cb075188a2096 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,3560,6120,8680,11240,13800,16360,18920,21480,24040,26600,29160,31720,34280,36840,39400,41960,44520,47080,49640,52200,54760,57320,59880,62440,65000,67560,70120,72680,75240,77800,80360,82920,85480,88040,90600,93160,95720,98280,100840,103400,105960,108520,111080,113640,116200,118760,121320,123880,126440,129000,131560,134120,136680,139240,141800,144360,146920,149480,152040,154600,157160,159720,162280,164840,167400,169960,172520,175080,177640,180200,182760,185320,187880,190440,193000,195560,198120,200680,203240,205800,208360,210920,213480,216040,218600,221160,223720,226280,228840,231400,233960,236520,239080,241640,244200,246760,249320,251880,254440,257000,259560,262120,264680,267240,269800,272360,274920,277480,280040,282600,285160,287720,290280,292840,295400,297960,300520,303080,305640,308200,310760,313320,315880,318440,321000,323560,326120,328680,331240,333800,336360,338920,341480,344040,346600,349160,351720,354280,356840,359400,361960,364520,367080,369640,372200,374760,377320,379880,382440,385000,387560,390120,392680,395240,397800,400360,402920,405480,408040,410600,413160,415720,418280,420840,423400,425960,428520,431080,433640,436200,438760,441320,443880,446440,449000,451560,454120,456680,459240,461800,464360,466920,469480,472040,474600,477160,479720,482280,484840,487400,489960,492520,495080,497640,500200,502760,505320,507880,510440,513000,515560,518120,520680,523240,525800,528360,530920,533480,536040,538600,541160,543720,546280,548840,551400,553960,556520,559080,561640,564200,566760,569320,571880,574440,577000,579560,582120,584680,587240,589800,592360,594920,597480,600040,602600,605160,607720,610280,612840,615400,617960,620520,623080,625640,628200,630760,633320,635880,638440,641000,643560,646120,648680,651240,653800); +-- Query Hash: 7fbe567a05d79f23888cb6ccadd6875f +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,2280,3560,4840,6120,7400,8680,9960,11240,12520,13800,15080,16360,17640,18920,20200,21480,22760,24040,25320,26600,27880,29160,30440,31720,33000,34280,35560,36840,38120,39400,40680,41960,43240,44520,45800,47080,48360,49640,50920,52200,53480,54760,56040,57320,58600,59880,61160,62440,63720,65000,66280,67560,68840,70120,71400,72680,73960,75240,76520,77800,79080,80360,81640,82920,84200,85480,86760,88040,89320,90600,91880,93160,94440,95720,97000,98280,99560,100840,102120,103400,104680,105960,107240,108520,109800,111080,112360,113640,114920,116200,117480,118760,120040,121320,122600,123880,125160,126440,127720,129000,130280,131560,132840,134120,135400,136680,137960,139240,140520,141800,143080,144360,145640,146920,148200,149480,150760,152040,153320,154600,155880,157160,158440,159720,161000,162280,163560,164840,166120,167400,168680,169960,171240,172520,173800,175080,176360,177640,178920,180200,181480,182760,184040,185320,186600,187880,189160,190440,191720,193000,194280,195560,196840,198120,199400,200680,201960,203240,204520,205800,207080,208360,209640,210920,212200,213480,214760,216040,217320,218600,219880,221160,222440,223720,225000,226280,227560,228840,230120,231400,232680,233960,235240,236520,237800,239080,240360,241640,242920,244200,245480,246760,248040,249320,250600,251880,253160,254440,255720,257000,258280,259560,260840,262120,263400,264680,265960,267240,268520,269800,271080,272360,273640,274920,276200,277480,278760,280040,281320,282600,283880,285160,286440,287720,289000,290280,291560,292840,294120,295400,296680,297960,299240,300520,301800,303080,304360,305640,306920,308200,309480,310760,312040,313320,314600,315880,317160,318440,319720,321000,322280,323560,324840,326120,327400,328680,329960,331240,332520,333800,335080,336360,337640,338920,340200,341480,342760,344040,345320,346600,347880,349160,350440,351720,353000,354280,355560,356840,358120,359400,360680,361960,363240,364520,365800,367080,368360,369640,370920,372200,373480,374760,376040,377320,378600,379880,381160,382440,383720,385000,386280,387560,388840,390120,391400,392680,393960,395240,396520,397800,399080,400360,401640,402920,404200,405480,406760,408040,409320,410600,411880,413160,414440,415720,417000,418280,419560,420840,422120,423400,424680,425960,427240,428520,429800,431080,432360,433640,434920,436200,437480,438760,440040,441320,442600,443880,445160,446440,447720,449000,450280,451560,452840,454120,455400,456680,457960,459240,460520,461800,463080,464360,465640,466920,468200,469480,470760,472040,473320,474600,475880,477160,478440,479720,481000,482280,483560,484840,486120,487400,488680,489960,491240,492520,493800,495080,496360,497640,498920,500200,501480,502760,504040,505320,506600,507880,509160,510440,511720,513000,514280,515560,516840,518120,519400,520680,521960,523240,524520,525800,527080,528360,529640,530920,532200,533480,534760,536040,537320,538600,539880,541160,542440,543720,545000,546280,547560,548840,550120,551400,552680,553960,555240,556520,557800,559080,560360,561640,562920,564200,565480,566760,568040,569320,570600,571880,573160,574440,575720,577000,578280,579560,580840,582120,583400,584680,585960,587240,588520,589800,591080,592360,593640,594920,596200,597480,598760,600040,601320,602600,603880,605160,606440,607720,609000,610280,611560,612840,614120,615400,616680,617960,619240,620520,621800,623080,624360,625640,626920,628200,629480,630760,632040,633320,634600,635880,637160,638440,639720,641000,642280,643560,644840,646120,647400,648680,649960,651240,652520,653800,655080); +-- Query Hash: 777f07dd51183ae5e812b49bde3b5f3d +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1640,2280,2920,3560,4200,4840,5480,6120,6760,7400,8040,8680,9320,9960,10600,11240,11880,12520,13160,13800,14440,15080,15720,16360,17000,17640,18280,18920,19560,20200,20840,21480,22120,22760,23400,24040,24680,25320,25960,26600,27240,27880,28520,29160,29800,30440,31080,31720,32360,33000,33640,34280,34920,35560,36200,36840,37480,38120,38760,39400,40040,40680,41320,41960,42600,43240,43880,44520,45160,45800,46440,47080,47720,48360,49000,49640,50280,50920,51560,52200,52840,53480,54120,54760,55400,56040,56680,57320,57960,58600,59240,59880,60520,61160,61800,62440,63080,63720,64360,65000,65640,66280,66920,67560,68200,68840,69480,70120,70760,71400,72040,72680,73320,73960,74600,75240,75880,76520,77160,77800,78440,79080,79720,80360,81000,81640,82280,82920,83560,84200,84840,85480,86120,86760,87400,88040,88680,89320,89960,90600,91240,91880,92520,93160,93800,94440,95080,95720,96360,97000,97640,98280,98920,99560,100200,100840,101480,102120,102760,103400,104040,104680,105320,105960,106600,107240,107880,108520,109160,109800,110440,111080,111720,112360,113000,113640,114280,114920,115560,116200,116840,117480,118120,118760,119400,120040,120680,121320,121960,122600,123240,123880,124520,125160,125800,126440,127080,127720,128360,129000,129640,130280,130920,131560,132200,132840,133480,134120,134760,135400,136040,136680,137320,137960,138600,139240,139880,140520,141160,141800,142440,143080,143720,144360,145000,145640,146280,146920,147560,148200,148840,149480,150120,150760,151400,152040,152680,153320,153960,154600,155240,155880,156520,157160,157800,158440,159080,159720,160360,161000,161640,162280,162920,163560,164200,164840,165480,166120,166760,167400,168040,168680,169320,169960,170600,171240,171880,172520,173160,173800,174440,175080,175720,176360,177000,177640,178280,178920,179560,180200,180840,181480,182120,182760,183400,184040,184680,185320,185960,186600,187240,187880,188520,189160,189800,190440,191080,191720,192360,193000,193640,194280,194920,195560,196200,196840,197480,198120,198760,199400,200040,200680,201320,201960,202600,203240,203880,204520,205160,205800,206440,207080,207720,208360,209000,209640,210280,210920,211560,212200,212840,213480,214120,214760,215400,216040,216680,217320,217960,218600,219240,219880,220520,221160,221800,222440,223080,223720,224360,225000,225640,226280,226920,227560,228200,228840,229480,230120,230760,231400,232040,232680,233320,233960,234600,235240,235880,236520,237160,237800,238440,239080,239720,240360,241000,241640,242280,242920,243560,244200,244840,245480,246120,246760,247400,248040,248680,249320,249960,250600,251240,251880,252520,253160,253800,254440,255080,255720,256360,257000,257640,258280,258920,259560,260200,260840,261480,262120,262760,263400,264040,264680,265320,265960,266600,267240,267880,268520,269160,269800,270440,271080,271720,272360,273000,273640,274280,274920,275560,276200,276840,277480,278120,278760,279400,280040,280680,281320,281960,282600,283240,283880,284520,285160,285800,286440,287080,287720,288360,289000,289640,290280,290920,291560,292200,292840,293480,294120,294760,295400,296040,296680,297320,297960,298600,299240,299880,300520,301160,301800,302440,303080,303720,304360,305000,305640,306280,306920,307560,308200,308840,309480,310120,310760,311400,312040,312680,313320,313960,314600,315240,315880,316520,317160,317800,318440,319080,319720,320360,321000,321640,322280,322920,323560,324200,324840,325480,326120,326760,327400,328040,328680,329320,329960,330600,331240,331880,332520,333160,333800,334440,335080,335720,336360,337000,337640,338280,338920,339560,340200,340840,341480,342120,342760,343400,344040,344680,345320,345960,346600,347240,347880,348520,349160,349800,350440,351080,351720,352360,353000,353640,354280,354920,355560,356200,356840,357480,358120,358760,359400,360040,360680,361320,361960,362600,363240,363880,364520,365160,365800,366440,367080,367720,368360,369000,369640,370280,370920,371560,372200,372840,373480,374120,374760,375400,376040,376680,377320,377960,378600,379240,379880,380520,381160,381800,382440,383080,383720,384360,385000,385640,386280,386920,387560,388200,388840,389480,390120,390760,391400,392040,392680,393320,393960,394600,395240,395880,396520,397160,397800,398440,399080,399720,400360,401000,401640,402280,402920,403560,404200,404840,405480,406120,406760,407400,408040,408680,409320,409960,410600,411240,411880,412520,413160,413800,414440,415080,415720,416360,417000,417640,418280,418920,419560,420200,420840,421480,422120,422760,423400,424040,424680,425320,425960,426600,427240,427880,428520,429160,429800,430440,431080,431720,432360,433000,433640,434280,434920,435560,436200,436840,437480,438120,438760,439400,440040,440680,441320,441960,442600,443240,443880,444520,445160,445800,446440,447080,447720,448360,449000,449640,450280,450920,451560,452200,452840,453480,454120,454760,455400,456040,456680,457320,457960,458600,459240,459880,460520,461160,461800,462440,463080,463720,464360,465000,465640,466280,466920,467560,468200,468840,469480,470120,470760,471400,472040,472680,473320,473960,474600,475240,475880,476520,477160,477800,478440,479080,479720,480360,481000,481640,482280,482920,483560,484200,484840,485480,486120,486760,487400,488040,488680,489320,489960,490600,491240,491880,492520,493160,493800,494440,495080,495720,496360,497000,497640,498280,498920,499560,500200,500840,501480,502120,502760,503400,504040,504680,505320,505960,506600,507240,507880,508520,509160,509800,510440,511080,511720,512360,513000,513640,514280,514920,515560,516200,516840,517480,518120,518760,519400,520040,520680,521320,521960,522600,523240,523880,524520,525160,525800,526440,527080,527720,528360,529000,529640,530280,530920,531560,532200,532840,533480,534120,534760,535400,536040,536680,537320,537960,538600,539240,539880,540520,541160,541800,542440,543080,543720,544360,545000,545640,546280,546920,547560,548200,548840,549480,550120,550760,551400,552040,552680,553320,553960,554600,555240,555880,556520,557160,557800,558440,559080,559720,560360,561000,561640,562280,562920,563560,564200,564840,565480,566120,566760,567400,568040,568680,569320,569960,570600,571240,571880,572520,573160,573800,574440,575080,575720,576360,577000,577640,578280,578920,579560,580200,580840,581480,582120,582760,583400,584040,584680,585320,585960,586600,587240,587880,588520,589160,589800,590440,591080,591720,592360,593000,593640,594280,594920,595560,596200,596840,597480,598120,598760,599400,600040,600680,601320,601960,602600,603240,603880,604520,605160,605800,606440,607080,607720,608360,609000,609640,610280,610920,611560,612200,612840,613480,614120,614760,615400,616040,616680,617320,617960,618600,619240,619880,620520,621160,621800,622440,623080,623720,624360,625000,625640,626280,626920,627560,628200,628840,629480,630120,630760,631400,632040,632680,633320,633960,634600,635240,635880,636520,637160,637800,638440,639080,639720,640360,641000,641640,642280,642920,643560,644200,644840,645480,646120,646760,647400,648040,648680,649320,649960,650600,651240,651880,652520,653160,653800,654440,655080,655720); +-- Query Hash: 409a0e3ed7a8e7b2104cae43159a3666 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1320,1640,1960,2280,2600,2920,3240,3560,3880,4200,4520,4840,5160,5480,5800,6120,6440,6760,7080,7400,7720,8040,8360,8680,9000,9320,9640,9960,10280,10600,10920,11240,11560,11880,12200,12520,12840,13160,13480,13800,14120,14440,14760,15080,15400,15720,16040,16360,16680,17000,17320,17640,17960,18280,18600,18920,19240,19560,19880,20200,20520,20840,21160,21480,21800,22120,22440,22760,23080,23400,23720,24040,24360,24680,25000,25320,25640,25960,26280,26600,26920,27240,27560,27880,28200,28520,28840,29160,29480,29800,30120,30440,30760,31080,31400,31720,32040,32360,32680,33000,33320,33640,33960,34280,34600,34920,35240,35560,35880,36200,36520,36840,37160,37480,37800,38120,38440,38760,39080,39400,39720,40040,40360,40680,41000,41320,41640,41960,42280,42600,42920,43240,43560,43880,44200,44520,44840,45160,45480,45800,46120,46440,46760,47080,47400,47720,48040,48360,48680,49000,49320,49640,49960,50280,50600,50920,51240,51560,51880,52200,52520,52840,53160,53480,53800,54120,54440,54760,55080,55400,55720,56040,56360,56680,57000,57320,57640,57960,58280,58600,58920,59240,59560,59880,60200,60520,60840,61160,61480,61800,62120,62440,62760,63080,63400,63720,64040,64360,64680,65000,65320,65640,65960,66280,66600,66920,67240,67560,67880,68200,68520,68840,69160,69480,69800,70120,70440,70760,71080,71400,71720,72040,72360,72680,73000,73320,73640,73960,74280,74600,74920,75240,75560,75880,76200,76520,76840,77160,77480,77800,78120,78440,78760,79080,79400,79720,80040,80360,80680,81000,81320,81640,81960,82280,82600,82920,83240,83560,83880,84200,84520,84840,85160,85480,85800,86120,86440,86760,87080,87400,87720,88040,88360,88680,89000,89320,89640,89960,90280,90600,90920,91240,91560,91880,92200,92520,92840,93160,93480,93800,94120,94440,94760,95080,95400,95720,96040,96360,96680,97000,97320,97640,97960,98280,98600,98920,99240,99560,99880,100200,100520,100840,101160,101480,101800,102120,102440,102760,103080,103400,103720,104040,104360,104680,105000,105320,105640,105960,106280,106600,106920,107240,107560,107880,108200,108520,108840,109160,109480,109800,110120,110440,110760,111080,111400,111720,112040,112360,112680,113000,113320,113640,113960,114280,114600,114920,115240,115560,115880,116200,116520,116840,117160,117480,117800,118120,118440,118760,119080,119400,119720,120040,120360,120680,121000,121320,121640,121960,122280,122600,122920,123240,123560,123880,124200,124520,124840,125160,125480,125800,126120,126440,126760,127080,127400,127720,128040,128360,128680,129000,129320,129640,129960,130280,130600,130920,131240,131560,131880,132200,132520,132840,133160,133480,133800,134120,134440,134760,135080,135400,135720,136040,136360,136680,137000,137320,137640,137960,138280,138600,138920,139240,139560,139880,140200,140520,140840,141160,141480,141800,142120,142440,142760,143080,143400,143720,144040,144360,144680,145000,145320,145640,145960,146280,146600,146920,147240,147560,147880,148200,148520,148840,149160,149480,149800,150120,150440,150760,151080,151400,151720,152040,152360,152680,153000,153320,153640,153960,154280,154600,154920,155240,155560,155880,156200,156520,156840,157160,157480,157800,158120,158440,158760,159080,159400,159720,160040,160360,160680,161000,161320,161640,161960,162280,162600,162920,163240,163560,163880,164200,164520,164840,165160,165480,165800,166120,166440,166760,167080,167400,167720,168040,168360,168680,169000,169320,169640,169960,170280,170600,170920,171240,171560,171880,172200,172520,172840,173160,173480,173800,174120,174440,174760,175080,175400,175720,176040,176360,176680,177000,177320,177640,177960,178280,178600,178920,179240,179560,179880,180200,180520,180840,181160,181480,181800,182120,182440,182760,183080,183400,183720,184040,184360,184680,185000,185320,185640,185960,186280,186600,186920,187240,187560,187880,188200,188520,188840,189160,189480,189800,190120,190440,190760,191080,191400,191720,192040,192360,192680,193000,193320,193640,193960,194280,194600,194920,195240,195560,195880,196200,196520,196840,197160,197480,197800,198120,198440,198760,199080,199400,199720,200040,200360,200680,201000,201320,201640,201960,202280,202600,202920,203240,203560,203880,204200,204520,204840,205160,205480,205800,206120,206440,206760,207080,207400,207720,208040,208360,208680,209000,209320,209640,209960,210280,210600,210920,211240,211560,211880,212200,212520,212840,213160,213480,213800,214120,214440,214760,215080,215400,215720,216040,216360,216680,217000,217320,217640,217960,218280,218600,218920,219240,219560,219880,220200,220520,220840,221160,221480,221800,222120,222440,222760,223080,223400,223720,224040,224360,224680,225000,225320,225640,225960,226280,226600,226920,227240,227560,227880,228200,228520,228840,229160,229480,229800,230120,230440,230760,231080,231400,231720,232040,232360,232680,233000,233320,233640,233960,234280,234600,234920,235240,235560,235880,236200,236520,236840,237160,237480,237800,238120,238440,238760,239080,239400,239720,240040,240360,240680,241000,241320,241640,241960,242280,242600,242920,243240,243560,243880,244200,244520,244840,245160,245480,245800,246120,246440,246760,247080,247400,247720,248040,248360,248680,249000,249320,249640,249960,250280,250600,250920,251240,251560,251880,252200,252520,252840,253160,253480,253800,254120,254440,254760,255080,255400,255720,256040,256360,256680,257000,257320,257640,257960,258280,258600,258920,259240,259560,259880,260200,260520,260840,261160,261480,261800,262120,262440,262760,263080,263400,263720,264040,264360,264680,265000,265320,265640,265960,266280,266600,266920,267240,267560,267880,268200,268520,268840,269160,269480,269800,270120,270440,270760,271080,271400,271720,272040,272360,272680,273000,273320,273640,273960,274280,274600,274920,275240,275560,275880,276200,276520,276840,277160,277480,277800,278120,278440,278760,279080,279400,279720,280040,280360,280680,281000,281320,281640,281960,282280,282600,282920,283240,283560,283880,284200,284520,284840,285160,285480,285800,286120,286440,286760,287080,287400,287720,288040,288360,288680,289000,289320,289640,289960,290280,290600,290920,291240,291560,291880,292200,292520,292840,293160,293480,293800,294120,294440,294760,295080,295400,295720,296040,296360,296680,297000,297320,297640,297960,298280,298600,298920,299240,299560,299880,300200,300520,300840,301160,301480,301800,302120,302440,302760,303080,303400,303720,304040,304360,304680,305000,305320,305640,305960,306280,306600,306920,307240,307560,307880,308200,308520,308840,309160,309480,309800,310120,310440,310760,311080,311400,311720,312040,312360,312680,313000,313320,313640,313960,314280,314600,314920,315240,315560,315880,316200,316520,316840,317160,317480,317800,318120,318440,318760,319080,319400,319720,320040,320360,320680,321000,321320,321640,321960,322280,322600,322920,323240,323560,323880,324200,324520,324840,325160,325480,325800,326120,326440,326760,327080,327400,327720,328040,328360,328680,329000,329320,329640,329960,330280,330600,330920,331240,331560,331880,332200,332520,332840,333160,333480,333800,334120,334440,334760,335080,335400,335720,336040,336360,336680,337000,337320,337640,337960,338280,338600,338920,339240,339560,339880,340200,340520,340840,341160,341480,341800,342120,342440,342760,343080,343400,343720,344040,344360,344680,345000,345320,345640,345960,346280,346600,346920,347240,347560,347880,348200,348520,348840,349160,349480,349800,350120,350440,350760,351080,351400,351720,352040,352360,352680,353000,353320,353640,353960,354280,354600,354920,355240,355560,355880,356200,356520,356840,357160,357480,357800,358120,358440,358760,359080,359400,359720,360040,360360,360680,361000,361320,361640,361960,362280,362600,362920,363240,363560,363880,364200,364520,364840,365160,365480,365800,366120,366440,366760,367080,367400,367720,368040,368360,368680,369000,369320,369640,369960,370280,370600,370920,371240,371560,371880,372200,372520,372840,373160,373480,373800,374120,374440,374760,375080,375400,375720,376040,376360,376680,377000,377320,377640,377960,378280,378600,378920,379240,379560,379880,380200,380520,380840,381160,381480,381800,382120,382440,382760,383080,383400,383720,384040,384360,384680,385000,385320,385640,385960,386280,386600,386920,387240,387560,387880,388200,388520,388840,389160,389480,389800,390120,390440,390760,391080,391400,391720,392040,392360,392680,393000,393320,393640,393960,394280,394600,394920,395240,395560,395880,396200,396520,396840,397160,397480,397800,398120,398440,398760,399080,399400,399720,400040,400360,400680,401000,401320,401640,401960,402280,402600,402920,403240,403560,403880,404200,404520,404840,405160,405480,405800,406120,406440,406760,407080,407400,407720,408040,408360,408680,409000,409320,409640,409960,410280,410600,410920,411240,411560,411880,412200,412520,412840,413160,413480,413800,414120,414440,414760,415080,415400,415720,416040,416360,416680,417000,417320,417640,417960,418280,418600,418920,419240,419560,419880,420200,420520,420840,421160,421480,421800,422120,422440,422760,423080,423400,423720,424040,424360,424680,425000,425320,425640,425960,426280,426600,426920,427240,427560,427880,428200,428520,428840,429160,429480,429800,430120,430440,430760,431080,431400,431720,432040,432360,432680,433000,433320,433640,433960,434280,434600,434920,435240,435560,435880,436200,436520,436840,437160,437480,437800,438120,438440,438760,439080,439400,439720,440040,440360,440680,441000,441320,441640,441960,442280,442600,442920,443240,443560,443880,444200,444520,444840,445160,445480,445800,446120,446440,446760,447080,447400,447720,448040,448360,448680,449000,449320,449640,449960,450280,450600,450920,451240,451560,451880,452200,452520,452840,453160,453480,453800,454120,454440,454760,455080,455400,455720,456040,456360,456680,457000,457320,457640,457960,458280,458600,458920,459240,459560,459880,460200,460520,460840,461160,461480,461800,462120,462440,462760,463080,463400,463720,464040,464360,464680,465000,465320,465640,465960,466280,466600,466920,467240,467560,467880,468200,468520,468840,469160,469480,469800,470120,470440,470760,471080,471400,471720,472040,472360,472680,473000,473320,473640,473960,474280,474600,474920,475240,475560,475880,476200,476520,476840,477160,477480,477800,478120,478440,478760,479080,479400,479720,480040,480360,480680,481000,481320,481640,481960,482280,482600,482920,483240,483560,483880,484200,484520,484840,485160,485480,485800,486120,486440,486760,487080,487400,487720,488040,488360,488680,489000,489320,489640,489960,490280,490600,490920,491240,491560,491880,492200,492520,492840,493160,493480,493800,494120,494440,494760,495080,495400,495720,496040,496360,496680,497000,497320,497640,497960,498280,498600,498920,499240,499560,499880,500200,500520,500840,501160,501480,501800,502120,502440,502760,503080,503400,503720,504040,504360,504680,505000,505320,505640,505960,506280,506600,506920,507240,507560,507880,508200,508520,508840,509160,509480,509800,510120,510440,510760,511080,511400,511720,512040,512360,512680,513000,513320,513640,513960,514280,514600,514920,515240,515560,515880,516200,516520,516840,517160,517480,517800,518120,518440,518760,519080,519400,519720,520040,520360,520680,521000,521320,521640,521960,522280,522600,522920,523240,523560,523880,524200,524520,524840,525160,525480,525800,526120,526440,526760,527080,527400,527720,528040,528360,528680,529000,529320,529640,529960,530280,530600,530920,531240,531560,531880,532200,532520,532840,533160,533480,533800,534120,534440,534760,535080,535400,535720,536040,536360,536680,537000,537320,537640,537960,538280,538600,538920,539240,539560,539880,540200,540520,540840,541160,541480,541800,542120,542440,542760,543080,543400,543720,544040,544360,544680,545000,545320,545640,545960,546280,546600,546920,547240,547560,547880,548200,548520,548840,549160,549480,549800,550120,550440,550760,551080,551400,551720,552040,552360,552680,553000,553320,553640,553960,554280,554600,554920,555240,555560,555880,556200,556520,556840,557160,557480,557800,558120,558440,558760,559080,559400,559720,560040,560360,560680,561000,561320,561640,561960,562280,562600,562920,563240,563560,563880,564200,564520,564840,565160,565480,565800,566120,566440,566760,567080,567400,567720,568040,568360,568680,569000,569320,569640,569960,570280,570600,570920,571240,571560,571880,572200,572520,572840,573160,573480,573800,574120,574440,574760,575080,575400,575720,576040,576360,576680,577000,577320,577640,577960,578280,578600,578920,579240,579560,579880,580200,580520,580840,581160,581480,581800,582120,582440,582760,583080,583400,583720,584040,584360,584680,585000,585320,585640,585960,586280,586600,586920,587240,587560,587880,588200,588520,588840,589160,589480,589800,590120,590440,590760,591080,591400,591720,592040,592360,592680,593000,593320,593640,593960,594280,594600,594920,595240,595560,595880,596200,596520,596840,597160,597480,597800,598120,598440,598760,599080,599400,599720,600040,600360,600680,601000,601320,601640,601960,602280,602600,602920,603240,603560,603880,604200,604520,604840,605160,605480,605800,606120,606440,606760,607080,607400,607720,608040,608360,608680,609000,609320,609640,609960,610280,610600,610920,611240,611560,611880,612200,612520,612840,613160,613480,613800,614120,614440,614760,615080,615400,615720,616040,616360,616680,617000,617320,617640,617960,618280,618600,618920,619240,619560,619880,620200,620520,620840,621160,621480,621800,622120,622440,622760,623080,623400,623720,624040,624360,624680,625000,625320,625640,625960,626280,626600,626920,627240,627560,627880,628200,628520,628840,629160,629480,629800,630120,630440,630760,631080,631400,631720,632040,632360,632680,633000,633320,633640,633960,634280,634600,634920,635240,635560,635880,636200,636520,636840,637160,637480,637800,638120,638440,638760,639080,639400,639720,640040,640360,640680,641000,641320,641640,641960,642280,642600,642920,643240,643560,643880,644200,644520,644840,645160,645480,645800,646120,646440,646760,647080,647400,647720,648040,648360,648680,649000,649320,649640,649960,650280,650600,650920,651240,651560,651880,652200,652520,652840,653160,653480,653800,654120,654440,654760,655080,655400,655720,656040); +-- Query Hash: b1ce7d8c906b7a9b11e97d49e8c80ad2 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1160,1320,1480,1640,1800,1960,2120,2280,2440,2600,2760,2920,3080,3240,3400,3560,3720,3880,4040,4200,4360,4520,4680,4840,5000,5160,5320,5480,5640,5800,5960,6120,6280,6440,6600,6760,6920,7080,7240,7400,7560,7720,7880,8040,8200,8360,8520,8680,8840,9000,9160,9320,9480,9640,9800,9960,10120,10280,10440,10600,10760,10920,11080,11240,11400,11560,11720,11880,12040,12200,12360,12520,12680,12840,13000,13160,13320,13480,13640,13800,13960,14120,14280,14440,14600,14760,14920,15080,15240,15400,15560,15720,15880,16040,16200,16360,16520,16680,16840,17000,17160,17320,17480,17640,17800,17960,18120,18280,18440,18600,18760,18920,19080,19240,19400,19560,19720,19880,20040,20200,20360,20520,20680,20840,21000,21160,21320,21480,21640,21800,21960,22120,22280,22440,22600,22760,22920,23080,23240,23400,23560,23720,23880,24040,24200,24360,24520,24680,24840,25000,25160,25320,25480,25640,25800,25960,26120,26280,26440,26600,26760,26920,27080,27240,27400,27560,27720,27880,28040,28200,28360,28520,28680,28840,29000,29160,29320,29480,29640,29800,29960,30120,30280,30440,30600,30760,30920,31080,31240,31400,31560,31720,31880,32040,32200,32360,32520,32680,32840,33000,33160,33320,33480,33640,33800,33960,34120,34280,34440,34600,34760,34920,35080,35240,35400,35560,35720,35880,36040,36200,36360,36520,36680,36840,37000,37160,37320,37480,37640,37800,37960,38120,38280,38440,38600,38760,38920,39080,39240,39400,39560,39720,39880,40040,40200,40360,40520,40680,40840,41000,41160,41320,41480,41640,41800,41960,42120,42280,42440,42600,42760,42920,43080,43240,43400,43560,43720,43880,44040,44200,44360,44520,44680,44840,45000,45160,45320,45480,45640,45800,45960,46120,46280,46440,46600,46760,46920,47080,47240,47400,47560,47720,47880,48040,48200,48360,48520,48680,48840,49000,49160,49320,49480,49640,49800,49960,50120,50280,50440,50600,50760,50920,51080,51240,51400,51560,51720,51880,52040,52200,52360,52520,52680,52840,53000,53160,53320,53480,53640,53800,53960,54120,54280,54440,54600,54760,54920,55080,55240,55400,55560,55720,55880,56040,56200,56360,56520,56680,56840,57000,57160,57320,57480,57640,57800,57960,58120,58280,58440,58600,58760,58920,59080,59240,59400,59560,59720,59880,60040,60200,60360,60520,60680,60840,61000,61160,61320,61480,61640,61800,61960,62120,62280,62440,62600,62760,62920,63080,63240,63400,63560,63720,63880,64040,64200,64360,64520,64680,64840,65000,65160,65320,65480,65640,65800,65960,66120,66280,66440,66600,66760,66920,67080,67240,67400,67560,67720,67880,68040,68200,68360,68520,68680,68840,69000,69160,69320,69480,69640,69800,69960,70120,70280,70440,70600,70760,70920,71080,71240,71400,71560,71720,71880,72040,72200,72360,72520,72680,72840,73000,73160,73320,73480,73640,73800,73960,74120,74280,74440,74600,74760,74920,75080,75240,75400,75560,75720,75880,76040,76200,76360,76520,76680,76840,77000,77160,77320,77480,77640,77800,77960,78120,78280,78440,78600,78760,78920,79080,79240,79400,79560,79720,79880,80040,80200,80360,80520,80680,80840,81000,81160,81320,81480,81640,81800,81960,82120,82280,82440,82600,82760,82920,83080,83240,83400,83560,83720,83880,84040,84200,84360,84520,84680,84840,85000,85160,85320,85480,85640,85800,85960,86120,86280,86440,86600,86760,86920,87080,87240,87400,87560,87720,87880,88040,88200,88360,88520,88680,88840,89000,89160,89320,89480,89640,89800,89960,90120,90280,90440,90600,90760,90920,91080,91240,91400,91560,91720,91880,92040,92200,92360,92520,92680,92840,93000,93160,93320,93480,93640,93800,93960,94120,94280,94440,94600,94760,94920,95080,95240,95400,95560,95720,95880,96040,96200,96360,96520,96680,96840,97000,97160,97320,97480,97640,97800,97960,98120,98280,98440,98600,98760,98920,99080,99240,99400,99560,99720,99880,100040,100200,100360,100520,100680,100840,101000,101160,101320,101480,101640,101800,101960,102120,102280,102440,102600,102760,102920,103080,103240,103400,103560,103720,103880,104040,104200,104360,104520,104680,104840,105000,105160,105320,105480,105640,105800,105960,106120,106280,106440,106600,106760,106920,107080,107240,107400,107560,107720,107880,108040,108200,108360,108520,108680,108840,109000,109160,109320,109480,109640,109800,109960,110120,110280,110440,110600,110760,110920,111080,111240,111400,111560,111720,111880,112040,112200,112360,112520,112680,112840,113000,113160,113320,113480,113640,113800,113960,114120,114280,114440,114600,114760,114920,115080,115240,115400,115560,115720,115880,116040,116200,116360,116520,116680,116840,117000,117160,117320,117480,117640,117800,117960,118120,118280,118440,118600,118760,118920,119080,119240,119400,119560,119720,119880,120040,120200,120360,120520,120680,120840,121000,121160,121320,121480,121640,121800,121960,122120,122280,122440,122600,122760,122920,123080,123240,123400,123560,123720,123880,124040,124200,124360,124520,124680,124840,125000,125160,125320,125480,125640,125800,125960,126120,126280,126440,126600,126760,126920,127080,127240,127400,127560,127720,127880,128040,128200,128360,128520,128680,128840,129000,129160,129320,129480,129640,129800,129960,130120,130280,130440,130600,130760,130920,131080,131240,131400,131560,131720,131880,132040,132200,132360,132520,132680,132840,133000,133160,133320,133480,133640,133800,133960,134120,134280,134440,134600,134760,134920,135080,135240,135400,135560,135720,135880,136040,136200,136360,136520,136680,136840,137000,137160,137320,137480,137640,137800,137960,138120,138280,138440,138600,138760,138920,139080,139240,139400,139560,139720,139880,140040,140200,140360,140520,140680,140840,141000,141160,141320,141480,141640,141800,141960,142120,142280,142440,142600,142760,142920,143080,143240,143400,143560,143720,143880,144040,144200,144360,144520,144680,144840,145000,145160,145320,145480,145640,145800,145960,146120,146280,146440,146600,146760,146920,147080,147240,147400,147560,147720,147880,148040,148200,148360,148520,148680,148840,149000,149160,149320,149480,149640,149800,149960,150120,150280,150440,150600,150760,150920,151080,151240,151400,151560,151720,151880,152040,152200,152360,152520,152680,152840,153000,153160,153320,153480,153640,153800,153960,154120,154280,154440,154600,154760,154920,155080,155240,155400,155560,155720,155880,156040,156200,156360,156520,156680,156840,157000,157160,157320,157480,157640,157800,157960,158120,158280,158440,158600,158760,158920,159080,159240,159400,159560,159720,159880,160040,160200,160360,160520,160680,160840,161000,161160,161320,161480,161640,161800,161960,162120,162280,162440,162600,162760,162920,163080,163240,163400,163560,163720,163880,164040,164200,164360,164520,164680,164840,165000,165160,165320,165480,165640,165800,165960,166120,166280,166440,166600,166760,166920,167080,167240,167400,167560,167720,167880,168040,168200,168360,168520,168680,168840,169000,169160,169320,169480,169640,169800,169960,170120,170280,170440,170600,170760,170920,171080,171240,171400,171560,171720,171880,172040,172200,172360,172520,172680,172840,173000,173160,173320,173480,173640,173800,173960,174120,174280,174440,174600,174760,174920,175080,175240,175400,175560,175720,175880,176040,176200,176360,176520,176680,176840,177000,177160,177320,177480,177640,177800,177960,178120,178280,178440,178600,178760,178920,179080,179240,179400,179560,179720,179880,180040,180200,180360,180520,180680,180840,181000,181160,181320,181480,181640,181800,181960,182120,182280,182440,182600,182760,182920,183080,183240,183400,183560,183720,183880,184040,184200,184360,184520,184680,184840,185000,185160,185320,185480,185640,185800,185960,186120,186280,186440,186600,186760,186920,187080,187240,187400,187560,187720,187880,188040,188200,188360,188520,188680,188840,189000,189160,189320,189480,189640,189800,189960,190120,190280,190440,190600,190760,190920,191080,191240,191400,191560,191720,191880,192040,192200,192360,192520,192680,192840,193000,193160,193320,193480,193640,193800,193960,194120,194280,194440,194600,194760,194920,195080,195240,195400,195560,195720,195880,196040,196200,196360,196520,196680,196840,197000,197160,197320,197480,197640,197800,197960,198120,198280,198440,198600,198760,198920,199080,199240,199400,199560,199720,199880,200040,200200,200360,200520,200680,200840,201000,201160,201320,201480,201640,201800,201960,202120,202280,202440,202600,202760,202920,203080,203240,203400,203560,203720,203880,204040,204200,204360,204520,204680,204840,205000,205160,205320,205480,205640,205800,205960,206120,206280,206440,206600,206760,206920,207080,207240,207400,207560,207720,207880,208040,208200,208360,208520,208680,208840,209000,209160,209320,209480,209640,209800,209960,210120,210280,210440,210600,210760,210920,211080,211240,211400,211560,211720,211880,212040,212200,212360,212520,212680,212840,213000,213160,213320,213480,213640,213800,213960,214120,214280,214440,214600,214760,214920,215080,215240,215400,215560,215720,215880,216040,216200,216360,216520,216680,216840,217000,217160,217320,217480,217640,217800,217960,218120,218280,218440,218600,218760,218920,219080,219240,219400,219560,219720,219880,220040,220200,220360,220520,220680,220840,221000,221160,221320,221480,221640,221800,221960,222120,222280,222440,222600,222760,222920,223080,223240,223400,223560,223720,223880,224040,224200,224360,224520,224680,224840,225000,225160,225320,225480,225640,225800,225960,226120,226280,226440,226600,226760,226920,227080,227240,227400,227560,227720,227880,228040,228200,228360,228520,228680,228840,229000,229160,229320,229480,229640,229800,229960,230120,230280,230440,230600,230760,230920,231080,231240,231400,231560,231720,231880,232040,232200,232360,232520,232680,232840,233000,233160,233320,233480,233640,233800,233960,234120,234280,234440,234600,234760,234920,235080,235240,235400,235560,235720,235880,236040,236200,236360,236520,236680,236840,237000,237160,237320,237480,237640,237800,237960,238120,238280,238440,238600,238760,238920,239080,239240,239400,239560,239720,239880,240040,240200,240360,240520,240680,240840,241000,241160,241320,241480,241640,241800,241960,242120,242280,242440,242600,242760,242920,243080,243240,243400,243560,243720,243880,244040,244200,244360,244520,244680,244840,245000,245160,245320,245480,245640,245800,245960,246120,246280,246440,246600,246760,246920,247080,247240,247400,247560,247720,247880,248040,248200,248360,248520,248680,248840,249000,249160,249320,249480,249640,249800,249960,250120,250280,250440,250600,250760,250920,251080,251240,251400,251560,251720,251880,252040,252200,252360,252520,252680,252840,253000,253160,253320,253480,253640,253800,253960,254120,254280,254440,254600,254760,254920,255080,255240,255400,255560,255720,255880,256040,256200,256360,256520,256680,256840,257000,257160,257320,257480,257640,257800,257960,258120,258280,258440,258600,258760,258920,259080,259240,259400,259560,259720,259880,260040,260200,260360,260520,260680,260840,261000,261160,261320,261480,261640,261800,261960,262120,262280,262440,262600,262760,262920,263080,263240,263400,263560,263720,263880,264040,264200,264360,264520,264680,264840,265000,265160,265320,265480,265640,265800,265960,266120,266280,266440,266600,266760,266920,267080,267240,267400,267560,267720,267880,268040,268200,268360,268520,268680,268840,269000,269160,269320,269480,269640,269800,269960,270120,270280,270440,270600,270760,270920,271080,271240,271400,271560,271720,271880,272040,272200,272360,272520,272680,272840,273000,273160,273320,273480,273640,273800,273960,274120,274280,274440,274600,274760,274920,275080,275240,275400,275560,275720,275880,276040,276200,276360,276520,276680,276840,277000,277160,277320,277480,277640,277800,277960,278120,278280,278440,278600,278760,278920,279080,279240,279400,279560,279720,279880,280040,280200,280360,280520,280680,280840,281000,281160,281320,281480,281640,281800,281960,282120,282280,282440,282600,282760,282920,283080,283240,283400,283560,283720,283880,284040,284200,284360,284520,284680,284840,285000,285160,285320,285480,285640,285800,285960,286120,286280,286440,286600,286760,286920,287080,287240,287400,287560,287720,287880,288040,288200,288360,288520,288680,288840,289000,289160,289320,289480,289640,289800,289960,290120,290280,290440,290600,290760,290920,291080,291240,291400,291560,291720,291880,292040,292200,292360,292520,292680,292840,293000,293160,293320,293480,293640,293800,293960,294120,294280,294440,294600,294760,294920,295080,295240,295400,295560,295720,295880,296040,296200,296360,296520,296680,296840,297000,297160,297320,297480,297640,297800,297960,298120,298280,298440,298600,298760,298920,299080,299240,299400,299560,299720,299880,300040,300200,300360,300520,300680,300840,301000,301160,301320,301480,301640,301800,301960,302120,302280,302440,302600,302760,302920,303080,303240,303400,303560,303720,303880,304040,304200,304360,304520,304680,304840,305000,305160,305320,305480,305640,305800,305960,306120,306280,306440,306600,306760,306920,307080,307240,307400,307560,307720,307880,308040,308200,308360,308520,308680,308840,309000,309160,309320,309480,309640,309800,309960,310120,310280,310440,310600,310760,310920,311080,311240,311400,311560,311720,311880,312040,312200,312360,312520,312680,312840,313000,313160,313320,313480,313640,313800,313960,314120,314280,314440,314600,314760,314920,315080,315240,315400,315560,315720,315880,316040,316200,316360,316520,316680,316840,317000,317160,317320,317480,317640,317800,317960,318120,318280,318440,318600,318760,318920,319080,319240,319400,319560,319720,319880,320040,320200,320360,320520,320680,320840,321000,321160,321320,321480,321640,321800,321960,322120,322280,322440,322600,322760,322920,323080,323240,323400,323560,323720,323880,324040,324200,324360,324520,324680,324840,325000,325160,325320,325480,325640,325800,325960,326120,326280,326440,326600,326760,326920,327080,327240,327400,327560,327720,327880,328040,328200,328360,328520,328680,328840,329000,329160,329320,329480,329640,329800,329960,330120,330280,330440,330600,330760,330920,331080,331240,331400,331560,331720,331880,332040,332200,332360,332520,332680,332840,333000,333160,333320,333480,333640,333800,333960,334120,334280,334440,334600,334760,334920,335080,335240,335400,335560,335720,335880,336040,336200,336360,336520,336680,336840,337000,337160,337320,337480,337640,337800,337960,338120,338280,338440,338600,338760,338920,339080,339240,339400,339560,339720,339880,340040,340200,340360,340520,340680,340840,341000,341160,341320,341480,341640,341800,341960,342120,342280,342440,342600,342760,342920,343080,343240,343400,343560,343720,343880,344040,344200,344360,344520,344680,344840,345000,345160,345320,345480,345640,345800,345960,346120,346280,346440,346600,346760,346920,347080,347240,347400,347560,347720,347880,348040,348200,348360,348520,348680,348840,349000,349160,349320,349480,349640,349800,349960,350120,350280,350440,350600,350760,350920,351080,351240,351400,351560,351720,351880,352040,352200,352360,352520,352680,352840,353000,353160,353320,353480,353640,353800,353960,354120,354280,354440,354600,354760,354920,355080,355240,355400,355560,355720,355880,356040,356200,356360,356520,356680,356840,357000,357160,357320,357480,357640,357800,357960,358120,358280,358440,358600,358760,358920,359080,359240,359400,359560,359720,359880,360040,360200,360360,360520,360680,360840,361000,361160,361320,361480,361640,361800,361960,362120,362280,362440,362600,362760,362920,363080,363240,363400,363560,363720,363880,364040,364200,364360,364520,364680,364840,365000,365160,365320,365480,365640,365800,365960,366120,366280,366440,366600,366760,366920,367080,367240,367400,367560,367720,367880,368040,368200,368360,368520,368680,368840,369000,369160,369320,369480,369640,369800,369960,370120,370280,370440,370600,370760,370920,371080,371240,371400,371560,371720,371880,372040,372200,372360,372520,372680,372840,373000,373160,373320,373480,373640,373800,373960,374120,374280,374440,374600,374760,374920,375080,375240,375400,375560,375720,375880,376040,376200,376360,376520,376680,376840,377000,377160,377320,377480,377640,377800,377960,378120,378280,378440,378600,378760,378920,379080,379240,379400,379560,379720,379880,380040,380200,380360,380520,380680,380840,381000,381160,381320,381480,381640,381800,381960,382120,382280,382440,382600,382760,382920,383080,383240,383400,383560,383720,383880,384040,384200,384360,384520,384680,384840,385000,385160,385320,385480,385640,385800,385960,386120,386280,386440,386600,386760,386920,387080,387240,387400,387560,387720,387880,388040,388200,388360,388520,388680,388840,389000,389160,389320,389480,389640,389800,389960,390120,390280,390440,390600,390760,390920,391080,391240,391400,391560,391720,391880,392040,392200,392360,392520,392680,392840,393000,393160,393320,393480,393640,393800,393960,394120,394280,394440,394600,394760,394920,395080,395240,395400,395560,395720,395880,396040,396200,396360,396520,396680,396840,397000,397160,397320,397480,397640,397800,397960,398120,398280,398440,398600,398760,398920,399080,399240,399400,399560,399720,399880,400040,400200,400360,400520,400680,400840,401000,401160,401320,401480,401640,401800,401960,402120,402280,402440,402600,402760,402920,403080,403240,403400,403560,403720,403880,404040,404200,404360,404520,404680,404840,405000,405160,405320,405480,405640,405800,405960,406120,406280,406440,406600,406760,406920,407080,407240,407400,407560,407720,407880,408040,408200,408360,408520,408680,408840,409000,409160,409320,409480,409640,409800,409960,410120,410280,410440,410600,410760,410920,411080,411240,411400,411560,411720,411880,412040,412200,412360,412520,412680,412840,413000,413160,413320,413480,413640,413800,413960,414120,414280,414440,414600,414760,414920,415080,415240,415400,415560,415720,415880,416040,416200,416360,416520,416680,416840,417000,417160,417320,417480,417640,417800,417960,418120,418280,418440,418600,418760,418920,419080,419240,419400,419560,419720,419880,420040,420200,420360,420520,420680,420840,421000,421160,421320,421480,421640,421800,421960,422120,422280,422440,422600,422760,422920,423080,423240,423400,423560,423720,423880,424040,424200,424360,424520,424680,424840,425000,425160,425320,425480,425640,425800,425960,426120,426280,426440,426600,426760,426920,427080,427240,427400,427560,427720,427880,428040,428200,428360,428520,428680,428840,429000,429160,429320,429480,429640,429800,429960,430120,430280,430440,430600,430760,430920,431080,431240,431400,431560,431720,431880,432040,432200,432360,432520,432680,432840,433000,433160,433320,433480,433640,433800,433960,434120,434280,434440,434600,434760,434920,435080,435240,435400,435560,435720,435880,436040,436200,436360,436520,436680,436840,437000,437160,437320,437480,437640,437800,437960,438120,438280,438440,438600,438760,438920,439080,439240,439400,439560,439720,439880,440040,440200,440360,440520,440680,440840,441000,441160,441320,441480,441640,441800,441960,442120,442280,442440,442600,442760,442920,443080,443240,443400,443560,443720,443880,444040,444200,444360,444520,444680,444840,445000,445160,445320,445480,445640,445800,445960,446120,446280,446440,446600,446760,446920,447080,447240,447400,447560,447720,447880,448040,448200,448360,448520,448680,448840,449000,449160,449320,449480,449640,449800,449960,450120,450280,450440,450600,450760,450920,451080,451240,451400,451560,451720,451880,452040,452200,452360,452520,452680,452840,453000,453160,453320,453480,453640,453800,453960,454120,454280,454440,454600,454760,454920,455080,455240,455400,455560,455720,455880,456040,456200,456360,456520,456680,456840,457000,457160,457320,457480,457640,457800,457960,458120,458280,458440,458600,458760,458920,459080,459240,459400,459560,459720,459880,460040,460200,460360,460520,460680,460840,461000,461160,461320,461480,461640,461800,461960,462120,462280,462440,462600,462760,462920,463080,463240,463400,463560,463720,463880,464040,464200,464360,464520,464680,464840,465000,465160,465320,465480,465640,465800,465960,466120,466280,466440,466600,466760,466920,467080,467240,467400,467560,467720,467880,468040,468200,468360,468520,468680,468840,469000,469160,469320,469480,469640,469800,469960,470120,470280,470440,470600,470760,470920,471080,471240,471400,471560,471720,471880,472040,472200,472360,472520,472680,472840,473000,473160,473320,473480,473640,473800,473960,474120,474280,474440,474600,474760,474920,475080,475240,475400,475560,475720,475880,476040,476200,476360,476520,476680,476840,477000,477160,477320,477480,477640,477800,477960,478120,478280,478440,478600,478760,478920,479080,479240,479400,479560,479720,479880,480040,480200,480360,480520,480680,480840,481000,481160,481320,481480,481640,481800,481960,482120,482280,482440,482600,482760,482920,483080,483240,483400,483560,483720,483880,484040,484200,484360,484520,484680,484840,485000,485160,485320,485480,485640,485800,485960,486120,486280,486440,486600,486760,486920,487080,487240,487400,487560,487720,487880,488040,488200,488360,488520,488680,488840,489000,489160,489320,489480,489640,489800,489960,490120,490280,490440,490600,490760,490920,491080,491240,491400,491560,491720,491880,492040,492200,492360,492520,492680,492840,493000,493160,493320,493480,493640,493800,493960,494120,494280,494440,494600,494760,494920,495080,495240,495400,495560,495720,495880,496040,496200,496360,496520,496680,496840,497000,497160,497320,497480,497640,497800,497960,498120,498280,498440,498600,498760,498920,499080,499240,499400,499560,499720,499880,500040,500200,500360,500520,500680,500840,501000,501160,501320,501480,501640,501800,501960,502120,502280,502440,502600,502760,502920,503080,503240,503400,503560,503720,503880,504040,504200,504360,504520,504680,504840,505000,505160,505320,505480,505640,505800,505960,506120,506280,506440,506600,506760,506920,507080,507240,507400,507560,507720,507880,508040,508200,508360,508520,508680,508840,509000,509160,509320,509480,509640,509800,509960,510120,510280,510440,510600,510760,510920,511080,511240,511400,511560,511720,511880,512040,512200,512360,512520,512680,512840,513000,513160,513320,513480,513640,513800,513960,514120,514280,514440,514600,514760,514920,515080,515240,515400,515560,515720,515880,516040,516200,516360,516520,516680,516840,517000,517160,517320,517480,517640,517800,517960,518120,518280,518440,518600,518760,518920,519080,519240,519400,519560,519720,519880,520040,520200,520360,520520,520680,520840,521000,521160,521320,521480,521640,521800,521960,522120,522280,522440,522600,522760,522920,523080,523240,523400,523560,523720,523880,524040,524200,524360,524520,524680,524840,525000,525160,525320,525480,525640,525800,525960,526120,526280,526440,526600,526760,526920,527080,527240,527400,527560,527720,527880,528040,528200,528360,528520,528680,528840,529000,529160,529320,529480,529640,529800,529960,530120,530280,530440,530600,530760,530920,531080,531240,531400,531560,531720,531880,532040,532200,532360,532520,532680,532840,533000,533160,533320,533480,533640,533800,533960,534120,534280,534440,534600,534760,534920,535080,535240,535400,535560,535720,535880,536040,536200,536360,536520,536680,536840,537000,537160,537320,537480,537640,537800,537960,538120,538280,538440,538600,538760,538920,539080,539240,539400,539560,539720,539880,540040,540200,540360,540520,540680,540840,541000,541160,541320,541480,541640,541800,541960,542120,542280,542440,542600,542760,542920,543080,543240,543400,543560,543720,543880,544040,544200,544360,544520,544680,544840,545000,545160,545320,545480,545640,545800,545960,546120,546280,546440,546600,546760,546920,547080,547240,547400,547560,547720,547880,548040,548200,548360,548520,548680,548840,549000,549160,549320,549480,549640,549800,549960,550120,550280,550440,550600,550760,550920,551080,551240,551400,551560,551720,551880,552040,552200,552360,552520,552680,552840,553000,553160,553320,553480,553640,553800,553960,554120,554280,554440,554600,554760,554920,555080,555240,555400,555560,555720,555880,556040,556200,556360,556520,556680,556840,557000,557160,557320,557480,557640,557800,557960,558120,558280,558440,558600,558760,558920,559080,559240,559400,559560,559720,559880,560040,560200,560360,560520,560680,560840,561000,561160,561320,561480,561640,561800,561960,562120,562280,562440,562600,562760,562920,563080,563240,563400,563560,563720,563880,564040,564200,564360,564520,564680,564840,565000,565160,565320,565480,565640,565800,565960,566120,566280,566440,566600,566760,566920,567080,567240,567400,567560,567720,567880,568040,568200,568360,568520,568680,568840,569000,569160,569320,569480,569640,569800,569960,570120,570280,570440,570600,570760,570920,571080,571240,571400,571560,571720,571880,572040,572200,572360,572520,572680,572840,573000,573160,573320,573480,573640,573800,573960,574120,574280,574440,574600,574760,574920,575080,575240,575400,575560,575720,575880,576040,576200,576360,576520,576680,576840,577000,577160,577320,577480,577640,577800,577960,578120,578280,578440,578600,578760,578920,579080,579240,579400,579560,579720,579880,580040,580200,580360,580520,580680,580840,581000,581160,581320,581480,581640,581800,581960,582120,582280,582440,582600,582760,582920,583080,583240,583400,583560,583720,583880,584040,584200,584360,584520,584680,584840,585000,585160,585320,585480,585640,585800,585960,586120,586280,586440,586600,586760,586920,587080,587240,587400,587560,587720,587880,588040,588200,588360,588520,588680,588840,589000,589160,589320,589480,589640,589800,589960,590120,590280,590440,590600,590760,590920,591080,591240,591400,591560,591720,591880,592040,592200,592360,592520,592680,592840,593000,593160,593320,593480,593640,593800,593960,594120,594280,594440,594600,594760,594920,595080,595240,595400,595560,595720,595880,596040,596200,596360,596520,596680,596840,597000,597160,597320,597480,597640,597800,597960,598120,598280,598440,598600,598760,598920,599080,599240,599400,599560,599720,599880,600040,600200,600360,600520,600680,600840,601000,601160,601320,601480,601640,601800,601960,602120,602280,602440,602600,602760,602920,603080,603240,603400,603560,603720,603880,604040,604200,604360,604520,604680,604840,605000,605160,605320,605480,605640,605800,605960,606120,606280,606440,606600,606760,606920,607080,607240,607400,607560,607720,607880,608040,608200,608360,608520,608680,608840,609000,609160,609320,609480,609640,609800,609960,610120,610280,610440,610600,610760,610920,611080,611240,611400,611560,611720,611880,612040,612200,612360,612520,612680,612840,613000,613160,613320,613480,613640,613800,613960,614120,614280,614440,614600,614760,614920,615080,615240,615400,615560,615720,615880,616040,616200,616360,616520,616680,616840,617000,617160,617320,617480,617640,617800,617960,618120,618280,618440,618600,618760,618920,619080,619240,619400,619560,619720,619880,620040,620200,620360,620520,620680,620840,621000,621160,621320,621480,621640,621800,621960,622120,622280,622440,622600,622760,622920,623080,623240,623400,623560,623720,623880,624040,624200,624360,624520,624680,624840,625000,625160,625320,625480,625640,625800,625960,626120,626280,626440,626600,626760,626920,627080,627240,627400,627560,627720,627880,628040,628200,628360,628520,628680,628840,629000,629160,629320,629480,629640,629800,629960,630120,630280,630440,630600,630760,630920,631080,631240,631400,631560,631720,631880,632040,632200,632360,632520,632680,632840,633000,633160,633320,633480,633640,633800,633960,634120,634280,634440,634600,634760,634920,635080,635240,635400,635560,635720,635880,636040,636200,636360,636520,636680,636840,637000,637160,637320,637480,637640,637800,637960,638120,638280,638440,638600,638760,638920,639080,639240,639400,639560,639720,639880,640040,640200,640360,640520,640680,640840,641000,641160,641320,641480,641640,641800,641960,642120,642280,642440,642600,642760,642920,643080,643240,643400,643560,643720,643880,644040,644200,644360,644520,644680,644840,645000,645160,645320,645480,645640,645800,645960,646120,646280,646440,646600,646760,646920,647080,647240,647400,647560,647720,647880,648040,648200,648360,648520,648680,648840,649000,649160,649320,649480,649640,649800,649960,650120,650280,650440,650600,650760,650920,651080,651240,651400,651560,651720,651880,652040,652200,652360,652520,652680,652840,653000,653160,653320,653480,653640,653800,653960,654120,654280,654440,654600,654760,654920,655080,655240,655400,655560,655720,655880,656040,656200); +-- Query Hash: f59aff38836fd18f7660e9d299b323d5 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: 46c5f5b6c31d62cf0eedf95fa5869254 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_200k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: 56bd35e852de4f083ab337edbb90de02 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_300k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: 82e1555fc50aae7a4634243c4179f8f4 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_400k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: c7b0f030ec157796f709324ac699f8ca +EXPLAIN (COSTS OFF) SELECT * FROM t_range_500k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: 208c5f0105e7a8b6a730af61d64f09b6 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_600k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: b67207a2e805a361f7f1e8dc8a925fdd +EXPLAIN (COSTS OFF) SELECT * FROM t_range_700k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: f075a79f20910f7f9fb02cf29d58e408 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_800k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: f9605d447096d011773f1c73bdd599c1 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_900k WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: 38847518cd55e24add1e8125e00d837d +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m WHERE id in (1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056,1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100,1104,1108,1112,1116,1120,1124,1128,1132,1136,1140,1144,1148,1152,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192,1196,1200,1204,1208,1212,1216,1220,1224,1228,1232,1236,1240,1244,1248,1252,1256,1260,1264,1268,1272,1276,1280,1284,1288,1292,1296,1300,1304,1308,1312,1316,1320,1324,1328,1332,1336,1340,1344,1348,1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632,1636,1640,1644,1648,1652,1656,1660,1664,1668,1672,1676,1680,1684,1688,1692,1696,1700,1704,1708,1712,1716,1720,1724,1728,1732,1736,1740,1744,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908,1912,1916,1920,1924,1928,1932,1936,1940,1944,1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2044,2048,2052,2056,2060,2064,2068,2072,2076,2080,2084,2088,2092,2096,2100,2104,2108,2112,2116,2120,2124,2128,2132,2136,2140,2144,2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212,2216,2220,2224,2228,2232,2236,2240,2244,2248,2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348,2352,2356,2360,2364,2368,2372,2376,2380,2384,2388,2392,2396,2400,2404,2408,2412,2416,2420,2424,2428,2432,2436,2440,2444,2448,2452,2456,2460,2464,2468,2472,2476,2480,2484,2488,2492,2496,2500,2504,2508,2512,2516,2520,2524,2528,2532,2536,2540,2544,2548,2552,2556,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2612,2616,2620,2624,2628,2632,2636,2640,2644,2648,2652,2656,2660,2664,2668,2672,2676,2680,2684,2688,2692,2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744,2748,2752,2756,2760,2764,2768,2772,2776,2780,2784,2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904,2908,2912,2916,2920,2924,2928,2932,2936,2940,2944,2948,2952,2956,2960,2964,2968,2972,2976,2980,2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024,3028,3032,3036,3040,3044,3048,3052,3056,3060,3064,3068,3072,3076,3080,3084,3088,3092,3096,3100,3104,3108,3112,3116,3120,3124,3128,3132,3136,3140,3144,3148,3152,3156,3160,3164,3168,3172,3176,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3284,3288,3292,3296,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340,3344,3348,3352,3356,3360,3364,3368,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420,3424,3428,3432,3436,3440,3444,3448,3452,3456,3460,3464,3468,3472,3476,3480,3484,3488,3492,3496,3500,3504,3508,3512,3516,3520,3524,3528,3532,3536,3540,3544,3548,3552,3556,3560,3564,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3760,3764,3768,3772,3776,3780,3784,3788,3792,3796,3800,3804,3808,3812,3816,3820,3824,3828,3832,3836,3840,3844,3848,3852,3856,3860,3864,3868,3872,3876,3880,3884,3888,3892,3896,3900,3904,3908,3912,3916,3920,3924,3928,3932,3936,3940,3944,3948,3952,3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016,4020,4024,4028,4032,4036,4040,4044,4048,4052,4056,4060,4064,4068,4072,4076,4080,4084,4088,4092,4096,4100,4104,4108,4112,4116,4120,4124,4128,4132,4136,4140,4144,4148,4152,4156,4160,4164,4168,4172,4176,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4236,4240,4244,4248,4252,4256,4260,4264,4268,4272,4276,4280,4284,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324,4328,4332,4336,4340,4344,4348,4352,4356,4360,4364,4368,4372,4376,4380,4384,4388,4392,4396,4400,4404,4408,4412,4416,4420,4424,4428,4432,4436,4440,4444,4448,4452,4456,4460,4464,4468,4472,4476,4480,4484,4488,4492,4496,4500,4504,4508,4512,4516,4520,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672,4676,4680,4684,4688,4692,4696,4700,4704,4708,4712,4716,4720,4724,4728,4732,4736,4740,4744,4748,4752,4756,4760,4764,4768,4772,4776,4780,4784,4788,4792,4796,4800,4804,4808,4812,4816,4820,4824,4828,4832,4836,4840,4844,4848,4852,4856,4860,4864,4868,4872,4876,4880,4884,4888,4892,4896,4900,4904,4908,4912,4916,4920,4924,4928,4932,4936,4940,4944,4948,4952,4956,4960,4964,4968,4972,4976,4980,4984,4988,4992,4996); +-- Query Hash: d5f0641093478e5f0b19a2ec7ffae628 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_100k; +-- Query Hash: e32ea0054063b7f9da6790d6b43e3e14 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_200k; +-- Query Hash: 51ffbbbc6f751bbf05ec20aa593df357 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_300k; +-- Query Hash: 414bc79c9feb2c1e483df2ff6710eeb3 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_400k; +-- Query Hash: 5186260d46a8629cb982536eb1e8d47a +EXPLAIN (COSTS OFF) SELECT * FROM t_range_500k; +-- Query Hash: 86146d544add7223efb80b51fea24f3e +EXPLAIN (COSTS OFF) SELECT * FROM t_range_600k; +-- Query Hash: 543e7054ff0985546eba651cacdd7cd5 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_700k; +-- Query Hash: ba9e3cac2281ddb6e71bd2b60952e141 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_800k; +-- Query Hash: 617a8679fae5aa26d3942ccd35821d56 +EXPLAIN (COSTS OFF) SELECT * FROM t_range_900k; +-- Query Hash: c5b9e1399314a6fa9af7fb5ef7ba1b5c +EXPLAIN (COSTS OFF) SELECT * FROM t_range_1m; +-- Query Hash: 6782ec0a70c9b0057a867f5a9d4fdec9 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 8 and k3 > 25; +-- Query Hash: 46ee88c4cf3cc9100efbf4f832104181 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 6 and k3 > 25; +-- Query Hash: 5ec1eb90e55dd23062de1c7750d888b8 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 4 and k3 > 25; +-- Query Hash: faf3f9445a2654e57844adeded436636 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k3 > 25; +-- Query Hash: 1af651a29f1688df12a550eed28672dc +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 8 and k4 > 50; +-- Query Hash: 0c17350a17751c683dacfa255cea2f71 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 6 and k4 > 50; +-- Query Hash: 9e47ca10636190cab07be143409de054 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 4 and k4 > 50; +-- Query Hash: 9b1b21a5fcca373c1df1e4d693a49970 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k1 > 2 and k4 > 50; +-- Query Hash: 6eeeb4cf9792335fb323fad590c362d6 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 18; +-- Query Hash: 749f6622277c98bcabe614dafc9bbb7c +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 16; +-- Query Hash: 24ab6373e7fb82633fcb27fcbd5e92ba +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 14; +-- Query Hash: 7a388dceff137f19bff2206ac4a223a1 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 12; +-- Query Hash: 95d2aebc17ca7109525fa49c29b33fd5 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 10; +-- Query Hash: cc8af9fcd8f4b41506e51c806dc276e2 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 8; +-- Query Hash: 8d7622ea332ab0642503b6675c85bbd2 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 6; +-- Query Hash: 9e733ddf04603d90df7bd6be0f9ca68f +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 4; +-- Query Hash: 134839bf742ef0351ae3d8d9e27c9071 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 2; +-- Query Hash: 621b1d19a4894bc1c137a996a76e1a5c +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 18 and k3 > 25; +-- Query Hash: 3c1283a1e00329f213152377fba8e65c +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 16 and k3 > 25; +-- Query Hash: 8e871cd09cce7c18e0aabcfe36d4eebd +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 14 and k3 > 25; +-- Query Hash: 5a05f945a5c1f8427614ed3b82963cd2 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 12 and k3 > 25; +-- Query Hash: bb5f50cbcf26558026fac22b8e5ce7da +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 10 and k3 > 25; +-- Query Hash: 501d3ec2d58fd9d148b735215c56446c +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 8 and k3 > 25; +-- Query Hash: dafbaeab31b1694b893d8b8baab3923d +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 6 and k3 > 25; +-- Query Hash: e05de12af1815c0b2a32e3955fa8c139 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 4 and k3 > 25; +-- Query Hash: 34c27f4bdd6d6cbaa55e59bf9e989cad +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 2 and k3 > 25; +-- Query Hash: 03b0670ffdd7c23c7afa4e5ae4212f18 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 18 and k4 > 50; +-- Query Hash: 229e0938179d3988db83954dc814f2bd +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 16 and k4 > 50; +-- Query Hash: 4386349448042f3a0047613957cc50c2 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 14 and k4 > 50; +-- Query Hash: c15dac149d252f5546859da88099be82 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 12 and k4 > 50; +-- Query Hash: 64b95f170e843f36ce6efe140bd513b0 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 10 and k4 > 50; +-- Query Hash: 8cda36e9e012aefd91ae3f609bd0f2f1 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 8 and k4 > 50; +-- Query Hash: b264ae0e672d9b6374016d6a6dae6c70 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 6 and k4 > 50; +-- Query Hash: 0e42b1b6b3003fa02659e057af27778a +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 4 and k4 > 50; +-- Query Hash: 54c039d8ab16a53ccb6715a728116140 +EXPLAIN (COSTS OFF) select * from t_range_1m_4keys where k2 > 2 and k4 > 50; +-- Query Hash: 449b5109309f3bf9a546b20ae35e1e76 +EXPLAIN (COSTS OFF) SELECT id FROM t_range_100k_1column; +-- Query Hash: 63a6b7904579ced587b4434d0f39d57d +EXPLAIN (COSTS OFF) SELECT id FROM t_range_100k_2column; +-- Query Hash: c0b15a10a5f474e90c8f67b1f03d359a +EXPLAIN (COSTS OFF) SELECT id FROM t_range_100k_3column; +-- Query Hash: d1a0798243884c3c9606e26db91d0031 +EXPLAIN (COSTS OFF) SELECT id FROM t_range_100k_4column; +-- Query Hash: 59e6de5247cc4e29c81c208f9e5a32f3 +EXPLAIN (COSTS OFF) SELECT id FROM t_range_100k; +-- Query Hash: b7e2b50a9b41efaebefb79dd43ef269c +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 99990; +-- Query Hash: f9373b83e1cfacf7c9cf80cceb1c7f0f +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 99900; +-- Query Hash: 357081f91736d2ff5e9936fee3e8fedd +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 99800; +-- Query Hash: 698f609142df0e0bf93395e05a1556a5 +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 99600; +-- Query Hash: 6bba00a1f39bf37341734382da29d146 +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 99200; +-- Query Hash: 340f2ffe88d75bd132be8bf9115124fa +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 99000; +-- Query Hash: 7f4d0300e2b0866fa2d38c06b4c9cbec +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 98000; +-- Query Hash: 48683ce6ccfba55f1c6e8e7fdd8d1845 +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 96000; +-- Query Hash: 03116268b05f2c133f32c50553b18e80 +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 92000; +-- Query Hash: e97809d9da4483e1f219d27653ec0203 +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 90000; +-- Query Hash: f077961b82832e65dc7117379267656d +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 80000; +-- Query Hash: 8461640eefa124b442bda3bca958ef19 +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 70000; +-- Query Hash: 4082b8f91e6ff1d1e8689a4f6980d91c +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 60000; +-- Query Hash: 4176c4bcbf3370770382aeb78f4e7e86 +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 50000; +-- Query Hash: a177fa17c56d623e4957504058165dc3 +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 40000; +-- Query Hash: ad308fcef469d0182caae8a36e99891e +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 30000; +-- Query Hash: d9108e995210c55b60549aebfebc70f0 +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 20000; +-- Query Hash: 2a74d235202052554e1d95396b999872 +EXPLAIN (COSTS OFF) SELECT 0 FROM t_int_100k WHERE v1 > 10000; +-- DROP QUERIES; +DROP TABLE IF EXISTS t_range_100k CASCADE; +DROP TABLE IF EXISTS t_range_200k CASCADE; +DROP TABLE IF EXISTS t_range_300k CASCADE; +DROP TABLE IF EXISTS t_range_400k CASCADE; +DROP TABLE IF EXISTS t_range_500k CASCADE; +DROP TABLE IF EXISTS t_range_600k CASCADE; +DROP TABLE IF EXISTS t_range_700k CASCADE; +DROP TABLE IF EXISTS t_range_800k CASCADE; +DROP TABLE IF EXISTS t_range_900k CASCADE; +DROP TABLE IF EXISTS t_range_1m CASCADE; +DROP TABLE IF EXISTS t_range_100k_1update CASCADE; +DROP TABLE IF EXISTS t_range_100k_2update CASCADE; +DROP TABLE IF EXISTS t_range_100k_3update CASCADE; +DROP TABLE IF EXISTS t_range_100k_4update CASCADE; +DROP TABLE IF EXISTS t_range_100k_1column CASCADE; +DROP TABLE IF EXISTS t_range_100k_2column CASCADE; +DROP TABLE IF EXISTS t_range_100k_3column CASCADE; +DROP TABLE IF EXISTS t_range_100k_4column CASCADE; +DROP TABLE IF EXISTS t_range_1m_4keys CASCADE; +DROP TABLE IF EXISTS t_int_100k CASCADE; +DROP TABLE IF EXISTS t_numeric_100k CASCADE; +DROP TABLE IF EXISTS t_real_100k CASCADE; +DROP TABLE IF EXISTS t_char4_100k CASCADE; +DROP TABLE IF EXISTS t_char8_100k CASCADE; +DROP TABLE IF EXISTS t_char16_100k CASCADE; + diff --git a/src/test/regress/sql/yb.orig.plpgsql.sql b/src/test/regress/sql/yb.orig.plpgsql.sql new file mode 100644 index 0000000000000..7ba95165a650d --- /dev/null +++ b/src/test/regress/sql/yb.orig.plpgsql.sql @@ -0,0 +1,419 @@ +-- +-- Assure that some of the record tests taken from `plpgsql.sql` work not just +-- for temporary tables but also regular tables. (Functions are taken from +-- `plpgsql.sql`.) +-- +create temp table foo (f1 int, f2 int); +insert into foo values (1, 2), (3, 4), (5, 6), (5, 6), (7, 8), (9, 10); +select * from foo order by f1; + +create table bar (f1 int, f2 int); +insert into bar values (1, 2), (3, 4), (5, 6), (5, 6), (7, 8), (9, 10); +select * from bar order by f1; + +create or replace function stricttest1() returns void as $$ +declare x record; +begin + -- should work + select * from foo where f1 = 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +create or replace function stricttest2() returns void as $$ +declare x record; +begin + -- should work + select * from bar where f1 = 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +create or replace function stricttest3() returns void as $$ +declare x record; +begin + -- too many rows, no params + select * from foo where f1 > 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +create or replace function stricttest4() returns void as $$ +declare x record; +begin + -- too many rows, no params + select * from bar where f1 > 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select stricttest1(); + +select stricttest2(); + +select stricttest3(); +select stricttest4(); +-- +-- Cleanup +-- +DROP TABLE foo; +DROP TABLE bar; +DROP FUNCTION stricttest1(), stricttest2(), stricttest3(), stricttest4(); + +-- Fail because collate and cursor are not supported. +create or replace function unsupported1() returns void as $$ +declare a text collate "en_US"; +begin +end$$ language plpgsql; + +create table test(k int, v int); + +create procedure intermediate_commit() as $$ +begin + insert into test values(1, 1); + insert into test values(2, 2); + commit; + insert into test values(3, 3); +end$$ LANGUAGE plpgsql; + +call intermediate_commit(); + +select * from test order by k; + +do $$ +begin + insert into test values(4, 4); + commit; + insert into test values(5, 5); + insert into test values(6, 6); +end $$; + +select * from test order by k; + +do $$ +begin + insert into test values(7, 7); + -- commit inserting (7, 7) row and start new transaction automatically. + commit; + insert into test values(8, 8); + rollback; + -- only insertion of (8, 8) row is rolled back. + -- new transaction is started automatically, next row will be inserted. + insert into test values(9, 9); +end $$; + +select * from test order by k; + +create procedure p(a inout int) + language plpgsql +as $body$ +begin + a := a + 1; +end; +$body$; + +do $body$ +declare + a int := 10; +begin + call p(a); + raise info '%', a::text; +end; +$body$; +-- check exit out of outermost block +do $$ +<> +begin + <> + begin + exit outerblock; + raise notice 'should not get here'; + end; + raise notice 'should not get here, either'; +end$$; + +-- Self replacing function test +CREATE OR REPLACE FUNCTION f1(int) +RETURNS FLOAT8 AS $$ +DECLARE + -- Define the DDL statement for the new version of the function as a single text string + ddl_statement TEXT := $Q$ + CREATE OR REPLACE FUNCTION f1(int) RETURNS FLOAT8 AS $F$ + BEGIN + -- This is the code for the NEW version (V2) + RETURN 2.0::FLOAT8 / $1; + END; + $F$ LANGUAGE plpgsql; + $Q$; +BEGIN + -- 1. Execute the DDL dynamically. This replaces the function for the next call. + EXECUTE ddl_statement; + + -- 2. Execute the code for the CURRENT version (V1) + RETURN 1.0::FLOAT8 / $1; +END; +$$ LANGUAGE plpgsql; + +-- Step 1: Create table t1 by calling V1 (which also creates V2) +CREATE TABLE t1 AS SELECT f1(1); + +-- Step 2: The table T1 stores the result of V1 +SELECT * FROM t1; + +-- Step 3: Call the function again. It will now execute V2. +SELECT f1(2); + +-- Self replacing procedure test +CREATE TABLE proc_log ( + run_order INT, + msg TEXT +); + +CREATE OR REPLACE PROCEDURE self_modifying_proc() +LANGUAGE plpgsql +AS $$ +DECLARE + -- The definition for Version 2 + v_new_definition TEXT := $Q$ + CREATE OR REPLACE PROCEDURE self_modifying_proc() + LANGUAGE plpgsql + AS $V2$ + BEGIN + -- Version 2 inserts '2' as the order + INSERT INTO proc_log (run_order, msg) VALUES (2, 'v2-executed'); + END; + $V2$; + $Q$; +BEGIN + -- Version 1 logic + EXECUTE v_new_definition; + + -- Version 1 inserts '1' as the order + INSERT INTO proc_log (run_order, msg) VALUES (1, 'v1-executed'); +END; +$$; + +-- First execution (runs V1, creates V2) +CALL self_modifying_proc(); + +-- Second execution (runs V2) +CALL self_modifying_proc(); + +-- Final verification for your Unit Test assertion +SELECT run_order, msg FROM proc_log ORDER BY run_order; + +-- Create or replace function multiple times within a transaction block +-- Initial Setup +CREATE OR REPLACE FUNCTION version_test() RETURNS TEXT AS $$ +BEGIN + RETURN 'Initial Version'; +END; +$$ LANGUAGE plpgsql; + +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; + + -- 1. Call the initial version + SELECT version_test() AS run_1; + + -- 2. Replace with Version A + CREATE OR REPLACE FUNCTION version_test() RETURNS TEXT AS $$ + BEGIN + RETURN 'Version A (Modified inside TX)'; + END; + $$ LANGUAGE plpgsql; + + -- 3. Call again - PostgreSQL will see the new definition + SELECT version_test() AS run_2; + + -- 4. Replace with Version B + CREATE OR REPLACE FUNCTION version_test() RETURNS TEXT AS $$ + BEGIN + RETURN 'Version B (Final inside TX)'; + END; + $$ LANGUAGE plpgsql; + + -- 5. Call one last time + SELECT version_test() AS run_3; + +COMMIT; + +-- 6. Call outside the ddl transaction block +SELECT version_test() AS run_4; + +-- Create or replace procedure multiple times within a transaction block +-- Initial Setup: Create the log table and the first version of the procedure +CREATE TABLE procedure_test_log (run_id INT, output TEXT); + +CREATE OR REPLACE PROCEDURE multi_version_proc(p_id INT) +LANGUAGE plpgsql +AS $$ +BEGIN + INSERT INTO procedure_test_log VALUES (p_id, 'Initial Version'); +END; +$$; + +-- Start the Repeatable Read Transaction +BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; + + -- 1. Call the initial version + CALL multi_version_proc(1); + + -- 2. Replace with Version A + CREATE OR REPLACE PROCEDURE multi_version_proc(p_id INT) + LANGUAGE plpgsql + AS $$ + BEGIN + INSERT INTO procedure_test_log VALUES (p_id, 'Version A - Modified in TX'); + END; + $$; + + -- 3. Call again - The session cache is invalidated and reloads the new DDL + CALL multi_version_proc(2); + + -- 4. Replace with Version B + CREATE OR REPLACE PROCEDURE multi_version_proc(p_id INT) + LANGUAGE plpgsql + AS $$ + BEGIN + INSERT INTO procedure_test_log VALUES (p_id, 'Version B - Final in TX'); + END; + $$; + + -- 5. Call the final version + CALL multi_version_proc(3); + +COMMIT; + +-- 6. Call the final version outside the ddl transaction block +CALL multi_version_proc(4); + +-- Verification for Unit Test +SELECT * FROM procedure_test_log ORDER BY run_id; + +--- Test ALTER ... SET SCHEMA +CREATE SCHEMA schema1; +ALTER PROCEDURE intermediate_commit SET SCHEMA schema1; +call schema1.intermediate_commit(); + +ALTER ROUTINE f1 SET SCHEMA schema1; +SELECT schema1.f1(2); + +--- Test ALTER ROUTINE ... SET/RESET +SELECT current_setting('work_mem'); + +CREATE FUNCTION report_guc(text) RETURNS text AS +$$ select current_setting($1) $$ LANGUAGE SQL +SET work_mem = '1MB'; + +SELECT report_guc('work_mem'), current_setting('work_mem'); + +ALTER ROUTINE report_guc(text) SET work_mem = '2MB'; +SELECT report_guc('work_mem'), current_setting('work_mem'); + +ALTER ROUTINE report_guc(text) SET work_mem = DEFAULT; +SELECT report_guc('work_mem'), current_setting('work_mem'); + +ALTER ROUTINE report_guc(text) SET work_mem FROM CURRENT; +SELECT report_guc('work_mem'), current_setting('work_mem'); + +ALTER ROUTINE report_guc(text) RESET work_mem; +SELECT report_guc('work_mem'), current_setting('work_mem'); + +ALTER ROUTINE report_guc(text) RESET ALL; +SELECT report_guc('work_mem'), current_setting('work_mem'); + +ALTER ROUTINE report_guc(text) SET work_mem = '2MB' RESTRICT; +SELECT report_guc('work_mem'), current_setting('work_mem'); + +ALTER ROUTINE report_guc(text) SET work_mem = DEFAULT RESTRICT; +SELECT report_guc('work_mem'), current_setting('work_mem'); + +ALTER ROUTINE report_guc(text) SET work_mem FROM CURRENT RESTRICT; +SELECT report_guc('work_mem'), current_setting('work_mem'); + +ALTER ROUTINE report_guc(text) RESET work_mem RESTRICT; +SELECT report_guc('work_mem'), current_setting('work_mem'); + +ALTER ROUTINE report_guc(text) RESET ALL RESTRICT; +SELECT report_guc('work_mem'), current_setting('work_mem'); + +--- Test ALTER PROCEDURE ... SET/RESET +CREATE PROCEDURE print_guc(text) AS +$$ +begin + raise info '%', current_setting($1); +end; +$$ LANGUAGE plpgsql +SET work_mem = '1MB'; + +call print_guc('work_mem'); + +ALTER PROCEDURE print_guc(text) SET work_mem = '2MB'; +call print_guc('work_mem'); + +ALTER PROCEDURE print_guc(text) SET work_mem = DEFAULT; +call print_guc('work_mem'); + +ALTER PROCEDURE print_guc(text) SET work_mem FROM CURRENT; +call print_guc('work_mem'); + +ALTER PROCEDURE print_guc(text) RESET work_mem; +call print_guc('work_mem'); + +ALTER PROCEDURE print_guc(text) RESET ALL; +call print_guc('work_mem'); + +ALTER PROCEDURE print_guc(text) SET work_mem = '2MB' RESTRICT; +call print_guc('work_mem'); + +ALTER PROCEDURE print_guc(text) SET work_mem = DEFAULT RESTRICT; +call print_guc('work_mem'); + +ALTER PROCEDURE print_guc(text) SET work_mem FROM CURRENT RESTRICT; +call print_guc('work_mem'); + +ALTER PROCEDURE print_guc(text) RESET work_mem RESTRICT; +call print_guc('work_mem'); + +ALTER PROCEDURE print_guc(text) RESET ALL RESTRICT; +call print_guc('work_mem'); + +--- Test ALTER { ROUTINE | PROCEDURE } ... SECURITY { DEFINER | INVOKER } +CREATE TABLE secret_data (id int, info text); +INSERT INTO secret_data VALUES (1, 'Top Secret'); +SELECT * FROM secret_data; + +CREATE OR REPLACE PROCEDURE change_data(new_info text) AS +$$BEGIN + UPDATE secret_data SET info = new_info WHERE id = 1; +END$$ LANGUAGE plpgsql; +ALTER PROCEDURE change_data(text) SET search_path = public; + +CREATE ROLE worker; +REVOKE ALL ON secret_data FROM worker; +GRANT EXECUTE ON PROCEDURE change_data(text) TO worker; + +SET ROLE worker; +CALL change_data('Hacked by worker'); -- error: permission denied +RESET ROLE; +SELECT * FROM secret_data; + +ALTER PROCEDURE change_data(text) SECURITY DEFINER; +ALTER PROCEDURE change_data(text) SECURITY DEFINER RESTRICT; +ALTER PROCEDURE change_data(text) EXTERNAL SECURITY DEFINER; +ALTER PROCEDURE change_data(text) EXTERNAL SECURITY DEFINER RESTRICT; +ALTER ROUTINE change_data(text) SECURITY DEFINER; +ALTER ROUTINE change_data(text) SECURITY DEFINER RESTRICT; +ALTER ROUTINE change_data(text) EXTERNAL SECURITY DEFINER; +ALTER ROUTINE change_data(text) EXTERNAL SECURITY DEFINER RESTRICT; + +SET ROLE worker; +CALL change_data('Changed via Security Definer'); +RESET ROLE; +SELECT * FROM secret_data; + +ALTER PROCEDURE change_data(text) SECURITY INVOKER; +ALTER PROCEDURE change_data(text) SECURITY INVOKER RESTRICT; +ALTER PROCEDURE change_data(text) EXTERNAL SECURITY INVOKER; +ALTER PROCEDURE change_data(text) EXTERNAL SECURITY INVOKER RESTRICT; +ALTER ROUTINE change_data(text) SECURITY INVOKER; +ALTER ROUTINE change_data(text) SECURITY INVOKER RESTRICT; +ALTER ROUTINE change_data(text) EXTERNAL SECURITY INVOKER; +ALTER ROUTINE change_data(text) EXTERNAL SECURITY INVOKER RESTRICT; + +SET ROLE worker; +CALL change_data('Try Security Invoker'); -- error: permission denied +RESET ROLE; +SELECT * FROM secret_data; diff --git a/src/test/regress/sql/yb.orig.profile.sql b/src/test/regress/sql/yb.orig.profile.sql new file mode 100644 index 0000000000000..979dba904fa25 --- /dev/null +++ b/src/test/regress/sql/yb.orig.profile.sql @@ -0,0 +1,67 @@ +-- +-- YB_PROFILE Testsuite: Testing statements for profiles. +-- +-- +-- pg_catalog alterations. Validate columns of pg_yb_profile and oids. +-- +\d pg_yb_profile +SELECT oid, relname, reltype, relnatts FROM pg_class WHERE relname IN ('pg_yb_profile', 'pg_yb_profile_oid_index'); +SELECT oid, typname, typrelid FROM pg_type WHERE typname LIKE 'pg_yb_profile'; +SELECT pg_describe_object('pg_yb_profile'::regclass::oid, oid, 0) FROM pg_yb_profile; + +-- +-- CREATE PROFILE +-- +SELECT oid, prfname, prfmaxfailedloginattempts FROM pg_catalog.pg_yb_profile ORDER BY oid; + +CREATE PROFILE test_profile LIMIT FAILED_LOGIN_ATTEMPTS 3; + +SELECT prfname, prfmaxfailedloginattempts FROM pg_catalog.pg_yb_profile ORDER BY OID; + +-- Fail because it is a duplicate name +CREATE PROFILE test_profile LIMIT FAILED_LOGIN_ATTEMPTS 4; + +-- Fail because -ve numbers are not allowed +CREATE PROFILE test_profile LIMIT FAILED_LOGIN_ATTEMPTS -1; + +-- +-- DROP PROFILE +-- + +DROP PROFILE test_profile; + +-- fail: does not exist +DROP PROFILE test_profile; + +-- +-- Test IF EXISTS clause +-- + +DROP PROFILE IF EXISTS non_existing; + +CREATE PROFILE exists_profile LIMIT FAILED_LOGIN_ATTEMPTS 3; +DROP PROFILE IF EXISTS exists_profile; + +-- +-- Test \h CREATE PROFILE +-- + +\h CREATE PROFILE + +-- +-- Test \h DROP PROFILE +-- + +\h DROP PROFILE + +-- +-- Test \h ALTER USER +-- + +\h ALTER USER + +-- +-- Test \h ALTER ROLE +-- + +\h ALTER ROLE \ No newline at end of file diff --git a/src/test/regress/sql/yb.orig.profile_permissions.sql b/src/test/regress/sql/yb.orig.profile_permissions.sql new file mode 100644 index 0000000000000..1be5d63117e39 --- /dev/null +++ b/src/test/regress/sql/yb.orig.profile_permissions.sql @@ -0,0 +1,64 @@ +-- +-- YB_ROLE_PROFILE Testsuite: Testing ownership and access for profiles. +-- + +CREATE USER user_1; +CREATE USER user_2 SUPERUSER; +CREATE USER user_3; +CREATE USER restricted_user; + +GRANT yb_db_admin TO user_3 WITH ADMIN OPTION; + +CREATE PROFILE existing_profile LIMIT FAILED_LOGIN_ATTEMPTS 3; + +\c yugabyte user_1 + +-- None of these commands should be allowed to a normal user +CREATE PROFILE test_profile_1 LIMIT FAILED_LOGIN_ATTEMPTS 3; +ALTER USER restricted_user PROFILE test_profile_1; +ALTER USER restricted_user ACCOUNT LOCK; +ALTER USER restricted_user ACCOUNT UNLOCK; +ALTER USER restricted_user NOPROFILE; +DROP PROFILE existing_profile; + +-- user_2 can execute these commands as it is a super user. +\c yugabyte user_2 +CREATE PROFILE test_profile_2 LIMIT FAILED_LOGIN_ATTEMPTS 3; +ALTER USER restricted_user PROFILE test_profile_2; +SELECT rolprfstatus, rolprffailedloginattempts, rolname, prfname FROM + pg_catalog.pg_yb_role_profile rp JOIN pg_catalog.pg_roles rol ON rp.rolprfrole = rol.oid + JOIN pg_catalog.pg_yb_profile lp ON rp.rolprfprofile = lp.oid; + +ALTER USER restricted_user ACCOUNT LOCK; +ALTER USER restricted_user ACCOUNT UNLOCK; + +ALTER USER restricted_user NOPROFILE; +DROP PROFILE test_profile_2; +DROP PROFILE existing_profile; + +-- Recreate profile for next test +\c yugabyte yugabyte +CREATE PROFILE existing_profile LIMIT FAILED_LOGIN_ATTEMPTS 3; + +-- user_3 can execute these commands as it is a yb_db_admin. + +\c yugabyte user_3 +CREATE PROFILE test_profile_3 LIMIT FAILED_LOGIN_ATTEMPTS 3; +ALTER USER restricted_user PROFILE test_profile_3; +SELECT rolprfstatus, rolprffailedloginattempts, rolname, prfname FROM + pg_catalog.pg_yb_role_profile rp JOIN pg_catalog.pg_roles rol ON rp.rolprfrole = rol.oid + JOIN pg_catalog.pg_yb_profile lp ON rp.rolprfprofile = lp.oid; + +ALTER USER restricted_user ACCOUNT LOCK; +ALTER USER restricted_user ACCOUNT UNLOCK; + +ALTER USER restricted_user NOPROFILE; + +DROP PROFILE test_profile_3; +DROP PROFILE existing_profile; + +\c yugabyte yugabyte +DROP USER restricted_user; +DROP USER user_1; +DROP USER user_2; +DROP USER user_3; diff --git a/src/test/regress/sql/yb.orig.publication.sql b/src/test/regress/sql/yb.orig.publication.sql new file mode 100644 index 0000000000000..782d344c2e591 --- /dev/null +++ b/src/test/regress/sql/yb.orig.publication.sql @@ -0,0 +1,94 @@ +-- +-- PUBLICATION +-- +CREATE ROLE regress_publication_user LOGIN SUPERUSER; +SET SESSION AUTHORIZATION 'regress_publication_user'; + +CREATE TABLE testpub_tbl1 (id serial primary key, data text); +CREATE TABLE testpub_tbl2 (id int primary key, data text); +CREATE TABLE testpub_tbl3_nopk (foo int, bar int); +CREATE TABLE testpub_tbl4_nopk (foo int, bar int); + +CREATE PUBLICATION testpub FOR ALL TABLES; + +-- testpub_tbl3_nopk should get filtered out. +SELECT * FROM pg_publication_tables; + +-- errors out since tables without pk are unsupported. +CREATE PUBLICATION testpub_explicit_list FOR TABLE testpub_tbl2, testpub_tbl3_nopk; + +DROP PUBLICATION testpub; +DROP TABLE testpub_tbl1; +DROP TABLE testpub_tbl2; +DROP TABLE testpub_tbl3_nopk; +DROP TABLE testpub_tbl4_nopk; + +RESET SESSION AUTHORIZATION; +DROP ROLE regress_publication_user; + +-- Test yb_db_admin role can create publications with ALL TABLES +CREATE SCHEMA my_schema; +CREATE ROLE regress_yb_db_admin_user LOGIN; +SET SESSION AUTHORIZATION 'regress_yb_db_admin_user'; +--should fail +CREATE PUBLICATION testpub_all_tables FOR ALL TABLES; +RESET SESSION AUTHORIZATION; +GRANT CREATE ON DATABASE yugabyte TO regress_yb_db_admin_user; +GRANT yb_db_admin TO regress_yb_db_admin_user WITH ADMIN OPTION; +SET SESSION AUTHORIZATION 'regress_yb_db_admin_user'; +--should succeed +CREATE PUBLICATION testpub_all_tables FOR ALL TABLES; +RESET SESSION AUTHORIZATION; +DROP PUBLICATION testpub_all_tables; +RESET SESSION AUTHORIZATION; + +CREATE ROLE regress_publication_superuser1 LOGIN SUPERUSER; +CREATE ROLE regress_yb_db_user1 LOGIN; +CREATE ROLE regress_yb_db_user2 LOGIN; +SET SESSION AUTHORIZATION 'regress_publication_superuser1'; +CREATE PUBLICATION pub_all_tables1 FOR ALL TABLES; +--should succeed +ALTER PUBLICATION pub_all_tables1 OWNER TO regress_yb_db_user1; +SET SESSION AUTHORIZATION 'regress_yb_db_user1'; +--should fail +ALTER PUBLICATION pub_all_tables1 OWNER TO regress_yb_db_user2; +RESET SESSION AUTHORIZATION; +CREATE PUBLICATION pub_all_tables2 FOR ALL TABLES; +CREATE ROLE regress_yb_db_admin_user2 LOGIN; +GRANT CREATE ON DATABASE yugabyte TO regress_yb_db_admin_user2; +GRANT yb_db_admin TO regress_yb_db_admin_user2 WITH ADMIN OPTION; +SET SESSION AUTHORIZATION 'regress_yb_db_admin_user'; +--should succeed +ALTER PUBLICATION pub_all_tables2 OWNER TO regress_yb_db_admin_user2; +ALTER PUBLICATION pub_all_tables2 OWNER TO regress_yb_db_user1; +RESET SESSION AUTHORIZATION; + +-- #22490: row filter with REPLICA IDENTITY DEFAULT should allow UPDATE/DELETE +-- when the filter only references replica identity (PK) columns. +CREATE TABLE yb_pub_rf_t(a int, b int, c text, PRIMARY KEY(a,c)); +ALTER TABLE yb_pub_rf_t REPLICA IDENTITY DEFAULT; +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION yb_pub_rf FOR TABLE yb_pub_rf_t WHERE (a > 5 AND c = 'NSW'); +RESET client_min_messages; + +INSERT INTO yb_pub_rf_t VALUES (6, 106, 'NSW'); +UPDATE yb_pub_rf_t SET b = 999 WHERE a = 6 AND c = 'NSW'; +DELETE FROM yb_pub_rf_t WHERE a = 6 AND c = 'NSW'; + +DROP PUBLICATION yb_pub_rf; +DROP TABLE yb_pub_rf_t; + +-- #22555: row filter with REPLICA IDENTITY CHANGE should allow UPDATE/DELETE +-- when the filter only references replica identity (PK) columns. +CREATE TABLE yb_pub_rf_change(a int, b int, c text, PRIMARY KEY(a,c)); +ALTER TABLE yb_pub_rf_change REPLICA IDENTITY CHANGE; +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION yb_pub_rf_change FOR TABLE yb_pub_rf_change WHERE (a > 5 AND c = 'NSW'); +RESET client_min_messages; + +INSERT INTO yb_pub_rf_change VALUES (6, 106, 'NSW'); +UPDATE yb_pub_rf_change SET b = 999 WHERE a = 6 AND c = 'NSW'; +DELETE FROM yb_pub_rf_change WHERE a = 6 AND c = 'NSW'; + +DROP PUBLICATION yb_pub_rf_change; +DROP TABLE yb_pub_rf_change; diff --git a/src/test/regress/sql/yb.orig.query_consistent_snapshot.sql b/src/test/regress/sql/yb.orig.query_consistent_snapshot.sql new file mode 100644 index 0000000000000..41a5612824d54 --- /dev/null +++ b/src/test/regress/sql/yb.orig.query_consistent_snapshot.sql @@ -0,0 +1,60 @@ +-- Verify that SELECTs of a single query don't read data written by the same SQL query. +CREATE table test1(id int primary key, value int); +CREATE table test2(id int primary key, value int); +INSERT INTO test1 VALUES(1, 1); +INSERT INTO test2 VALUES(2, 2); +EXPLAIN (COSTS OFF) INSERT INTO test2 SELECT id + 2, value FROM test1 UNION ALL SELECT id + 2, value FROM test2; +INSERT INTO test2 SELECT id + 2, value FROM test1 UNION ALL SELECT id + 2, value FROM test2; +SELECT * FROM test2 ORDER BY id; + +-- Verify above behavior for Index Scans. +CREATE INDEX ON test1(value); +CREATE INDEX ON test2(value); +EXPLAIN (COSTS OFF) INSERT INTO test2 SELECT id + 4, value + 1 FROM test1 WHERE value=1 UNION ALL SELECT id + 4, value FROM test2 WHERE value=2; +INSERT INTO test2 SELECT id + 4, value + 1 FROM test1 WHERE value=1 UNION ALL SELECT id + 4, value FROM test2 WHERE value=2; +SELECT * FROM test2 ORDER BY id; + +-- Verify above behavior for Index Only Scans. +EXPLAIN (COSTS OFF) INSERT INTO test2 SELECT 9, value FROM test1 WHERE value=1 UNION ALL SELECT 10, value FROM test2 WHERE value=1; +INSERT INTO test2 SELECT 9, value FROM test1 WHERE value=1 UNION ALL SELECT 10, value FROM test2 WHERE value=1; +SELECT * FROM test2 ORDER BY id; + +-- Verify above behavior for Primary-key index scans. +EXPLAIN (COSTS OFF) INSERT INTO test2 SELECT id + 10, value FROM test1 WHERE id=1 UNION ALL SELECT id+2, value FROM test2 WHERE id=11; +INSERT INTO test2 SELECT id + 10, value FROM test1 WHERE id=1 UNION ALL SELECT id+2, value FROM test2 WHERE id=11; +SELECT * FROM test2 ORDER BY id; + +-- Verify above behavior within transaction block. +BEGIN; +INSERT INTO test2 SELECT id + 11, value FROM test1 WHERE id=1 UNION ALL SELECT id+2, value FROM test2 WHERE id=12; +SELECT * FROM test2 ORDER BY id; +COMMIT; + +-- Verify above behavior within SQL functions. +CREATE FUNCTION testfunc() RETURNS SETOF test2 AS ' +INSERT INTO test2 SELECT id + 12, value FROM test1 WHERE id=1 UNION ALL SELECT id+2, value FROM test2 WHERE id=13; +SELECT * FROM test2 ORDER BY id; +' LANGUAGE SQL; + +SELECT * FROM testfunc(); + +-- Verify above behavior within Procedures. +CREATE PROCEDURE testproc() +LANGUAGE SQL +AS $$ +INSERT INTO test2 SELECT id + 13, value FROM test1 WHERE id=1 UNION ALL SELECT id+2, value FROM test2 WHERE id=14; +INSERT INTO test2 SELECT id + 1, value FROM test2 WHERE id = 14; +$$; + +CALL testproc(); +SELECT * FROM test2 ORDER BY id; + +-- The below test case is for gh issue #10142. As part of this bug, if a write was performed before the first +-- read of the SQL statement, the write would be visible to the read. This was because the in_txn_limit (see +-- ReadHybridTimePB for details) for the reads of the SQL statement was being picked on the first read op +-- issued by the statement and not at the first op (which was a write in this case). +-- +-- TODO: Uncomment this once the TODO in PgDocOp::SendRequestImpl is fixed. +-- create table test_no_pk (v int); +-- with ins AS (insert into test_no_pk values (5) returning *) select * from test_no_pk, ins; -- should return 0 rows +-- select * from test_no_pk; diff --git a/src/test/regress/sql/yb.orig.reindex.sql b/src/test/regress/sql/yb.orig.reindex.sql new file mode 100644 index 0000000000000..be988d5930b8d --- /dev/null +++ b/src/test/regress/sql/yb.orig.reindex.sql @@ -0,0 +1,331 @@ +-- +-- YB REINDEX test +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (costs off)' + +-- +-- PART 0: Init +-- +SET yb_enable_create_with_table_oid TO on; +CREATE TABLE yb (i int PRIMARY KEY, j int) WITH (table_oid = 54321); +CREATE INDEX NONCONCURRENTLY ON yb (j); + +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'pg_class_oid_index'::regclass; +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'pg_depend_depender_index'::regclass; +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'yb_j_idx'::regclass; +SELECT oid AS db_oid FROM pg_database WHERE datname = ( + SELECT CASE + WHEN COUNT(*) = 1 THEN 'template1' + ELSE current_database() END FROM pg_yb_catalog_version) \gset +SELECT +$force_cache_refresh$ +SET yb_non_ddl_txn_for_sys_tables_allowed TO on; +UPDATE pg_yb_catalog_version + SET current_version = current_version + 1, + last_breaking_version = current_version + 1 + WHERE db_oid = :db_oid; +RESET yb_non_ddl_txn_for_sys_tables_allowed; +DO +$$ +BEGIN + PERFORM pg_sleep(5); +END; +$$; +$force_cache_refresh$ AS force_cache_refresh \gset +-- Force cache refresh. The UPDATE pg_yb_catalog_version trick doesn't seem to +-- work for pg_depend_depender_index, but updating the pg_yb_catalog_version +-- and then reconnecting does (simply reconnecting doesn't result in a cache +-- refresh when the tserver response cache is enabled). Since reconnecting +-- releases temp tables, do this refresh first before creating temp tables. +:force_cache_refresh +\c + +CREATE TEMP TABLE tmp (i int PRIMARY KEY, j int); +CREATE INDEX ON tmp (j); + +SELECT relname FROM pg_class c JOIN pg_index i on c.oid = i.indexrelid + WHERE indisvalid = false; + +-- +-- PART 1: simple REINDEX on system catalog, temporary, and yb objects +-- + +-- REINDEX SYSTEM/DATABASE expects current database name. +SELECT current_database(); +REINDEX SYSTEM yugabyte; -- current database +REINDEX SYSTEM template0; -- different database +REINDEX DATABASE yugabyte; -- current database +REINDEX DATABASE template0; -- different database +REINDEX SCHEMA pg_catalog; +REINDEX SCHEMA pg_temp; +REINDEX SCHEMA public; +REINDEX TABLE pg_class; -- system table with pk +REINDEX TABLE pg_depend; -- system table without pk +REINDEX TABLE tmp; +REINDEX TABLE yb; +REINDEX INDEX pg_class_oid_index; -- system pk index +REINDEX INDEX pg_depend_depender_index; -- system secondary index +REINDEX INDEX tmp_pkey; +REINDEX INDEX tmp_j_idx; +REINDEX INDEX yb_pkey; +REINDEX INDEX yb_j_idx; + +-- Any rebuilt indexes should now be public. +SELECT relname FROM pg_class c JOIN pg_index i on c.oid = i.indexrelid + WHERE indisvalid = false; + +-- +-- PART 2: scans before/after REINDEX +-- + +INSERT INTO yb SELECT g, -g FROM generate_series(1, 10) g; +INSERT INTO tmp SELECT g, -g FROM generate_series(1, 10) g; + +-- 1. initial state +\set Q1 '/*+SeqScan(pg_depend) */' +\set Q2 '/*+IndexScan(pg_depend pg_depend_depender_index) */' +SELECT $$ +:P :Q +SELECT deptype FROM pg_depend + WHERE classid = 'pg_class'::regclass and objid = 'yb'::regclass; +$$ AS query \gset +\set Pnext :iter_Q2 +\i :iter_P2 +\set Q1 '/*+SeqScan(tmp) */' +\set Q2 '/*+BitmapScan(tmp tmp_j_idx) */' +\set query ':P :Q SELECT i FROM tmp WHERE j = -5;' +\i :iter_P2 +\set Q1 '/*+SeqScan(yb) */' +\set Q2 '/*+IndexScan(yb yb_j_idx) */' +\set query ':P :Q SELECT i FROM yb WHERE j = -5;' +\i :iter_P2 + +-- 2. corruption (for temp index) +-- +-- Fully test the temp index first because system indexes require require a +-- reconnect, which releases the temp table. +-- +-- Disable reads/writes to the index. +UPDATE pg_index SET indislive = false, indisready = false, indisvalid = false + WHERE indexrelid = 'tmp_j_idx'::regclass; +--- Force cache refresh. +:force_cache_refresh +-- Do update that goes to table but doesn't go to index. +UPDATE tmp SET i = 11 WHERE j = -5; +-- Enable reads/writes to the index. +UPDATE pg_index SET indislive = true, indisready = true, indisvalid = true + WHERE indexrelid = 'tmp_j_idx'::regclass; +--- Force cache refresh. +:force_cache_refresh +-- Show the corruption. +/*+SeqScan(tmp) */ +SELECT i FROM tmp WHERE j = -5; +/*+IndexScan(tmp tmp_j_idx) */ +SELECT i FROM tmp WHERE j = -5; +-- Disable reads/writes to the index. +UPDATE pg_index SET indislive = false, indisready = false, indisvalid = false + WHERE indexrelid = 'tmp_j_idx'::regclass; +--- Force cache refresh. +:force_cache_refresh + +-- 3. reindex (for temp index) +REINDEX INDEX tmp_j_idx; + +-- 4. verification (for temp index) +\set Q1 '/*+SeqScan(tmp) */' +-- Somehow, IndexScan hint plan fails to work at this point. Force usage of +-- the index by setting enable_seqscan and enable_bitmapscan to off. +\set Q2 '/*+Set(enable_seqscan off) Set(enable_bitmapscan off) */' +\set query ':P :Q SELECT i FROM tmp WHERE j = -5;' +\i :iter_P2 + +-- 5. corruption (for YB indexes) +-- +-- Now, test the YB-backed indexes. +-- +-- Disable reads/writes to the indexes. +UPDATE pg_index SET indislive = false, indisready = false, indisvalid = false + WHERE indexrelid = 'pg_depend_depender_index'::regclass; +UPDATE pg_index SET indislive = false, indisready = false, indisvalid = false + WHERE indexrelid = 'yb_j_idx'::regclass; +-- Force cache refresh. +:force_cache_refresh +\c +-- Do updates that go to tables but don't go to indexes. +SET yb_non_ddl_txn_for_sys_tables_allowed TO on; +-- For non-pk tables, the ybctid cannot be changed through UPDATE. Use the +-- following DELETE + INSERT instead. +WITH w AS ( + DELETE FROM pg_depend + WHERE classid = 'pg_class'::regclass and objid = 'yb'::regclass + RETURNING *) +INSERT INTO pg_depend SELECT * FROM w; +RESET yb_non_ddl_txn_for_sys_tables_allowed; +UPDATE yb SET i = 11 WHERE j = -5; +-- Enable reads/writes to the indexes. +UPDATE pg_index SET indislive = true, indisready = true, indisvalid = true + WHERE indexrelid = 'pg_depend_depender_index'::regclass; +UPDATE pg_index SET indislive = true, indisready = true, indisvalid = true + WHERE indexrelid = 'yb_j_idx'::regclass; +-- Force cache refresh. +:force_cache_refresh +\c +-- Show the corruptions. +/*+SeqScan(pg_depend) */ +SELECT deptype FROM pg_depend + WHERE classid = 'pg_class'::regclass and objid = 'yb'::regclass; +/*+IndexScan(pg_depend pg_depend_depender_index) */ +SELECT deptype FROM pg_depend + WHERE classid = 'pg_class'::regclass and objid = 'yb'::regclass; +/*+SeqScan(yb) */ +SELECT i FROM yb WHERE j = -5; +/*+IndexScan(yb yb_j_idx) */ +SELECT i FROM yb WHERE j = -5; +-- Disable reads to the indexes. +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'pg_depend_depender_index'::regclass; +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'yb_j_idx'::regclass; +-- Force cache refresh. +:force_cache_refresh +\c +-- 6. reindex (for YB indexes) +REINDEX INDEX pg_depend_depender_index; +REINDEX INDEX yb_j_idx; + +-- 7. verification (for YB indexes) +\set Q1 '/*+SeqScan(pg_depend) */' +\set Q2 '/*+IndexScan(pg_depend pg_depend_depender_index) */' +SELECT $$ +:P :Q +SELECT deptype FROM pg_depend + WHERE classid = 'pg_class'::regclass and objid = 'yb'::regclass; +$$ AS query \gset +\i :iter_P2 +\set Q1 '/*+SeqScan(yb) */' +\set Q2 '/*+IndexScan(yb yb_j_idx) */' +\set query ':P :Q SELECT i FROM yb WHERE j = -5;' +\i :iter_P2 + +-- +-- PART 3: misc +-- + +-- public index +REINDEX INDEX pg_depend_depender_index; +REINDEX INDEX yb_j_idx; + +-- VERBOSE option +\set VERBOSITY terse +REINDEX (VERBOSE) SYSTEM yugabyte; +REINDEX (VERBOSE) DATABASE yugabyte; +REINDEX (VERBOSE) SCHEMA public; +REINDEX (VERBOSE) TABLE yb; +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'yb_j_idx'::regclass; +\c +REINDEX (VERBOSE) INDEX yb_j_idx; +\set VERBOSITY default + +-- unsupported command/options +REINDEX INDEX CONCURRENTLY yb_j_idx; +REINDEX (VERBOSE) INDEX CONCURRENTLY yb_j_idx; +REINDEX (CONCURRENTLY) INDEX yb_j_idx; +REINDEX (tablespace somespace) INDEX yb_j_idx; + +-- shared system index +REINDEX INDEX pg_database_datname_index; -- fail +-- make sure index isn't broken after failure +/*+IndexOnlyScan(pg_database pg_database_datname_index)*/ +SELECT datname from pg_database WHERE datname LIKE 'template%'; + +-- colocation (via tablegroup) +CREATE TABLEGROUP g; +CREATE TABLE ing (i int PRIMARY KEY, j int) TABLEGROUP g; +CREATE INDEX NONCONCURRENTLY ON ing (j ASC); +INSERT INTO ing SELECT g, -g FROM generate_series(1, 10) g; +\set Q1 '/*+IndexScan(ing ing_j_idx)*/' +\set query ':P :Q1 SELECT i FROM ing WHERE j < -8 ORDER BY i;' +\set Pnext :iter_query +\i :iter_P2 +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'ing_j_idx'::regclass; +\c +REINDEX INDEX ing_j_idx; +\i :iter_P2 +\set query ':P :Q1 SELECT i FROM ing WHERE j = -9;' +\i :iter_P2 +DROP TABLE ing; + +-- matview +CREATE MATERIALIZED VIEW mv AS SELECT * FROM yb; +CREATE INDEX NONCONCURRENTLY ON mv (j ASC); +\set Q1 '/*+IndexScan(mv mv_j_idx)*/' +\set query ':P :Q1 SELECT i FROM mv WHERE j > -3 ORDER BY i;' +\i :iter_P2 +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'mv_j_idx'::regclass; +\c +REINDEX INDEX mv_j_idx; +\i :iter_P2 +DROP MATERIALIZED VIEW mv; + +-- partitioned table +CREATE TABLE parted (i int, j int) PARTITION BY LIST (j); +CREATE INDEX NONCONCURRENTLY ON parted (i); +CREATE TABLE parted_odd PARTITION OF parted FOR VALUES IN (1, 3, 5, 7, 9); +CREATE TABLE parted_even PARTITION OF parted FOR VALUES IN (2, 4, 6, 8); +INSERT INTO parted SELECT (2 * g), g FROM generate_series(1, 9) g; +\set Q1 '/*+IndexOnlyScan(parted parted_i_idx)*/' +\set query ':P :Q1 SELECT i FROM parted WHERE i = (2 * 5);' +\i :iter_P2 +\set Q1 '/*+IndexOnlyScan(parted_odd parted_odd_i_idx)*/' +\set query ':P :Q1 SELECT i FROM parted_odd WHERE i = (2 * 5);' +\i :iter_P2 + +-- REINDEX fails if not all child indexes are marked invalid +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'parted_i_idx'::regclass; +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'parted_odd_i_idx'::regclass; +\c +REINDEX INDEX parted_i_idx; -- fail +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'parted_even_i_idx'::regclass; +\c +-- Mark parted_odd_i_idx as invalid again because the above failed REINDEX INDEX +-- statement marks it as valid. This happens because each partition starts its +-- own transaction. The 'parted_odd_i_idx' partition gets processed first and +-- completes successfully before we get the error for 'parted_even_i_idx'. +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'parted_odd_i_idx'::regclass; +\c +REINDEX INDEX parted_i_idx; -- succeed +-- we can also reindex just a child index +UPDATE pg_index SET indisvalid = false + WHERE indexrelid = 'parted_odd_i_idx'::regclass; +\c +REINDEX INDEX parted_odd_i_idx; + +\set Q1 '/*+IndexOnlyScan(parted parted_i_idx)*/' +\set query ':P :Q1 SELECT i FROM parted WHERE i = (2 * 5);' +\i :iter_P2 +\set Q1 '/*+IndexOnlyScan(parted_odd parted_odd_i_idx)*/' +\set query ':P :Q1 SELECT i FROM parted_odd WHERE i = (2 * 5);' +\i :iter_P2 +DROP TABLE parted; + +-- +-- PART -1: cleanup +-- + +DROP TABLE yb; diff --git a/src/test/regress/sql/yb.orig.replica_identity.sql b/src/test/regress/sql/yb.orig.replica_identity.sql new file mode 100644 index 0000000000000..daa1819a832eb --- /dev/null +++ b/src/test/regress/sql/yb.orig.replica_identity.sql @@ -0,0 +1,30 @@ +CREATE TABLE test_replica_identity ( + id serial primary key, + keya text not null, + keyb text not null, + nonkey text +); + +---- +-- Make sure yb specific replica identity modes work +---- + +ALTER TABLE test_replica_identity REPLICA IDENTITY CHANGE; +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; +\d+ test_replica_identity + +-- YB Note: Make sure replica identity is retained after table rewrite operations +ALTER TABLE test_replica_identity REPLICA IDENTITY FULL; +ALTER TABLE test_replica_identity DROP CONSTRAINT test_replica_identity_pkey; +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; + + +-- YB Note: Check default replica identity of temp table +CREATE TEMPORARY TABLE test_temporary_replica_identity ( + a INT PRIMARY KEY, + b INT +); +SELECT relreplident FROM pg_class WHERE oid = 'test_temporary_replica_identity'::regclass; + +DROP TABLE test_temporary_replica_identity; +DROP TABLE test_replica_identity; diff --git a/src/test/regress/sql/yb.orig.replication_slot.sql b/src/test/regress/sql/yb.orig.replication_slot.sql new file mode 100644 index 0000000000000..02e3f553b5e95 --- /dev/null +++ b/src/test/regress/sql/yb.orig.replication_slot.sql @@ -0,0 +1,60 @@ +-- +-- REPLICATION SLOT +-- +CREATE ROLE regress_replicationslot_user LOGIN SUPERUSER; +CREATE ROLE regress_replicationslot_replication_user WITH REPLICATION; +CREATE ROLE regress_replicationslot_dummy; + +SET SESSION AUTHORIZATION 'regress_replicationslot_user'; + +SELECT * FROM pg_create_logical_replication_slot('testslot1', 'pgoutput', false); +SELECT * FROM pg_create_logical_replication_slot('testslot2', 'pgoutput', false); +SELECT * FROM pg_create_logical_replication_slot('testslot_test_decoding', 'test_decoding', false); +SELECT * FROM pg_create_logical_replication_slot('testslot_hybrid_time', 'yboutput', false, false, 'HYBRID_TIME'); + +-- Cannot do SELECT * since yb_stream_id, yb_restart_commit_ht changes across runs. +SELECT slot_name, plugin, slot_type, database, temporary, active, + active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, yb_lsn_type +FROM pg_replication_slots +ORDER BY slot_name; + +-- drop the replication slot and create with same name again. +SELECT * FROM pg_drop_replication_slot('testslot1'); +-- TODO(#19263): Change the slot to temporary once supported. +SELECT * FROM pg_create_logical_replication_slot('testslot1', 'pgoutput', false); + +-- unsupported cases +SELECT * FROM pg_create_logical_replication_slot('testslot_unsupported_plugin', 'unsupported_plugin', false); +SELECT * FROM pg_create_logical_replication_slot('testslot_unsupported_temporary', 'pgoutput', true); +SELECT * FROM pg_create_physical_replication_slot('testslot_unsupported_physical', true, false); + +-- creating replication slot with same name fails. +SELECT * FROM pg_create_logical_replication_slot('testslot1', 'pgoutput', false); + +-- success since user has 'replication' role +SET ROLE regress_replicationslot_replication_user; +SELECT * FROM pg_create_logical_replication_slot('testslot3', 'pgoutput', false); +RESET ROLE; + +-- fail - must have replication or superuser role +SET ROLE regress_replicationslot_dummy; +SELECT * FROM pg_create_logical_replication_slot('testslot4', 'pgoutput', false); +RESET ROLE; + +-- drop replication slots +SELECT * FROM pg_drop_replication_slot('testslot1'); +SELECT * FROM pg_drop_replication_slot('testslot2'); +SELECT * FROM pg_drop_replication_slot('testslot3'); +SELECT * FROM pg_drop_replication_slot('testslot_test_decoding'); +SELECT * FROM pg_drop_replication_slot('testslot_hybrid_time'); +SELECT slot_name, plugin, slot_type, database, temporary, active, + active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn +FROM pg_replication_slots; + +-- drop non-existent replication slot +SELECT * FROM pg_drop_replication_slot('testslot_nonexistent'); + +RESET SESSION AUTHORIZATION; +DROP ROLE regress_replicationslot_user; +DROP ROLE regress_replicationslot_replication_user; +DROP ROLE regress_replicationslot_dummy; diff --git a/src/test/regress/sql/yb.orig.reset_analyze.sql b/src/test/regress/sql/yb.orig.reset_analyze.sql new file mode 100644 index 0000000000000..2eb0bef0d584b --- /dev/null +++ b/src/test/regress/sql/yb.orig.reset_analyze.sql @@ -0,0 +1,356 @@ +\c yugabyte yugabyte +SET client_min_messages = warning; +DROP DATABASE IF EXISTS reset_analyze_test; +DROP USER IF EXISTS yb_su; +DROP USER IF EXISTS yb_user1; +DROP USER IF EXISTS yb_user2; +DROP USER IF EXISTS yb_user3; + +-- Create test users +CREATE USER yb_su superuser; +CREATE USER yb_user1; +CREATE USER yb_user2; +CREATE USER yb_user3; + +-- Create a database +CREATE DATABASE reset_analyze_test OWNER = yb_user1; +ALTER DATABASE reset_analyze_test SET yb_enable_optimizer_statistics=ON; +ALTER DATABASE reset_analyze_test SET yb_enable_base_scans_cost_model=ON; + +-- Grant permissions for this database. +\c reset_analyze_test yugabyte +GRANT ALL ON SCHEMA public TO yb_user1; +GRANT ALL ON SCHEMA public TO yb_user2; +GRANT ALL ON SCHEMA public TO yb_user3; + +-- Create tables owned by each user +\c - yb_user1 +CREATE SEQUENCE seq_u1; +CREATE TABLE table_u1 (id int, c1 int, c2 int); +INSERT INTO table_u1 + SELECT nextval('seq_u1'), i % 4, i / 2 FROM generate_series(1, 10) i; +CREATE INDEX NONCONCURRENTLY table_u1_c2_idx on table_u1 (c2, c1); +CREATE TABLE partitioned_u1 (id int, c1 int, c2 int) PARTITION BY RANGE (c2); +CREATE TABLE part1_u1 PARTITION OF partitioned_u1 FOR VALUES FROM (minvalue) TO (5); +CREATE TABLE part2_u1 PARTITION OF partitioned_u1 FOR VALUES FROM (5) TO (maxvalue); +CREATE INDEX NONCONCURRENTLY part_u1_c2_c1_idx ON partitioned_u1 (c2, c1); +INSERT INTO partitioned_u1 SELECT * FROM table_u1; +CREATE MATERIALIZED VIEW mv_u1 AS SELECT t1.c1, t2.c2 FROM table_u1 t1 JOIN table_u1 t2 ON t1.id = t2.c1; +CREATE INDEX NONCONCURRENTLY mv_u1_c1 ON mv_u1 (c1); +CREATE STATISTICS sta_table_u1 ON c2, c1 FROM table_u1; + +\c - yb_user2 +CREATE SEQUENCE seq_u2; +CREATE TABLE table_u2 (id int, c1 int, c2 int); +INSERT INTO table_u2 + SELECT nextval('seq_u2'), i % 4, i / 2 FROM generate_series(1, 10) i; +CREATE INDEX NONCONCURRENTLY table_u2_c2_idx on table_u2 (c2, c1); +CREATE TABLE partitioned_u2 (id int, c1 int, c2 int) PARTITION BY RANGE (c2); +CREATE TABLE part1_u2 PARTITION OF partitioned_u2 FOR VALUES FROM (minvalue) TO (5); +CREATE TABLE part2_u2 PARTITION OF partitioned_u2 FOR VALUES FROM (5) TO (maxvalue); +CREATE INDEX NONCONCURRENTLY part_u2_c2_c1_idx ON partitioned_u2 (c2, c1); +INSERT INTO partitioned_u2 SELECT * FROM table_u2; +CREATE MATERIALIZED VIEW mv_u2 AS SELECT t1.c1, t2.c2 FROM table_u2 t1 JOIN table_u2 t2 ON t1.id = t2.c1; +CREATE INDEX NONCONCURRENTLY mv_u2_c1 ON mv_u2 (c1); +CREATE STATISTICS sta_table_u2 ON c1, c2 FROM table_u2; + +\c - yb_user3 +CREATE SEQUENCE seq_u3; +CREATE TABLE table_u3 (id int, c1 int, c2 int); +INSERT INTO table_u3 + SELECT nextval('seq_u3'), i % 4, i / 2 FROM generate_series(1, 10) i; +CREATE INDEX NONCONCURRENTLY table_u3_c2_idx on table_u3 (c2, c1); +CREATE TABLE partitioned_u3 (id int, c1 int, c2 int) PARTITION BY RANGE (c2); +CREATE TABLE part1_u3 PARTITION OF partitioned_u3 FOR VALUES FROM (minvalue) TO (5); +CREATE TABLE part2_u3 PARTITION OF partitioned_u3 FOR VALUES FROM (5) TO (maxvalue); +CREATE INDEX NONCONCURRENTLY part_u3_c2_c1_idx ON partitioned_u3 (c2, c1); +INSERT INTO partitioned_u3 SELECT * FROM table_u3; +CREATE MATERIALIZED VIEW mv_u3 AS SELECT t1.c1, t2.c2 FROM table_u3 t1 JOIN table_u3 t2 ON t1.id = t2.c1; +CREATE INDEX NONCONCURRENTLY mv_u3_c1 ON mv_u3 (c1); +CREATE STATISTICS sta_table_u3 ON c1, c2 FROM table_u3; + +\c - yb_su +CREATE SEQUENCE seq_su; +CREATE TABLE table_su (id int, c1 int, c2 int); +INSERT INTO table_su + SELECT nextval('seq_su'), i % 4, i / 2 FROM generate_series(1, 10) i; +CREATE INDEX NONCONCURRENTLY table_su_c2_idx on table_su (c2, c1); +CREATE TABLE partitioned_su (id int, c1 int, c2 int) PARTITION BY RANGE (c2); +CREATE TABLE part1_su PARTITION OF partitioned_su FOR VALUES FROM (minvalue) TO (5); +CREATE TABLE part2_su PARTITION OF partitioned_su FOR VALUES FROM (5) TO (maxvalue); +CREATE INDEX NONCONCURRENTLY part_su_c2_c1_idx ON partitioned_su (c2, c1); +INSERT INTO partitioned_su SELECT * FROM table_su; +CREATE MATERIALIZED VIEW mv_su AS SELECT t1.c1, t2.c2 FROM table_su t1 JOIN table_su t2 ON t1.id = t2.c1; +CREATE INDEX NONCONCURRENTLY mv_su_c1 ON mv_su (c1); +CREATE STATISTICS sta_table_su ON c2, c1 FROM table_su; + + +-- Create views and procedures for verifications +SET client_min_messages = warning; + +CREATE OR REPLACE VIEW all_stats AS + SELECT c.oid AS id, rolname AS owner, nspname AS schemaname, relname, + relkind AS kind, relisshared AS shared, reltuples, + ncolstats, xn_distinct, xdependencies + FROM pg_class c + JOIN pg_namespace nc ON nc.oid = relnamespace + JOIN pg_authid r ON r.oid = relowner + JOIN ( + SELECT + attrelid, + count(stadistinct) AS ncolstats + FROM pg_attribute a + LEFT JOIN pg_statistic s + ON starelid = attrelid AND staattnum = attnum + GROUP BY attrelid + ) AS cstats ON attrelid = c.oid + LEFT JOIN ( + SELECT + stxrelid, + string_agg(stxdndistinct::text, ', ' ORDER BY stxkind, stxkeys) + AS xn_distinct, + string_agg(stxddependencies, ', ' ORDER BY stxkind, stxkeys) + AS xdependencies + FROM pg_statistic_ext e JOIN pg_statistic_ext_data d ON e.oid = d.stxoid + GROUP BY stxrelid + ) AS xstats ON stxrelid = attrelid; + +GRANT SELECT ON all_stats TO PUBLIC; + + +DROP TABLE IF EXISTS x_stats; +CREATE TABLE x_stats(id oid, owner name, schemaname name, relname name, + kind "char", shared boolean, reltuples real, ncolstats bigint, + xn_distinct text, xdependencies text, PRIMARY KEY (id)); +INSERT INTO x_stats SELECT * FROM all_stats; + +CREATE OR REPLACE PROCEDURE record_stats() AS +$$ +BEGIN + DELETE FROM x_stats; + INSERT INTO x_stats SELECT * FROM all_stats; +END +$$ +LANGUAGE plpgsql +SECURITY DEFINER; + +CREATE OR REPLACE VIEW diff_stats AS + SELECT + CASE WHEN t.id IS NULL THEN '-' WHEN t0.id IS NULL THEN '+' END AS d, + coalesce(t0.owner, t.owner) AS owner, + coalesce(t0.schemaname, t.schemaname) AS schemaname, + coalesce(t0.relname, t.relname) AS relname, + coalesce(t0.kind, t.kind) AS k, + coalesce(t0.shared, t.shared) AS s, + coalesce(t0.reltuples, t.reltuples) AS reltuples, + coalesce(t0.ncolstats, t.ncolstats) AS ncolstats, + coalesce(t0.xn_distinct, t.xn_distinct) AS xndv, + coalesce(t0.xdependencies, t.xdependencies) AS xdep + FROM x_stats AS t0 FULL JOIN all_stats AS t + ON t0.id = t.id + AND t0.reltuples = t.reltuples + AND t0.ncolstats = t.ncolstats + WHERE t.id IS NULL OR t0.id IS NULL +ORDER BY 2, 3, 4, 5, 1 DESC LIMIT ALL; + +GRANT SELECT ON diff_stats TO PUBLIC; + + +-- Superuser +\c - yb_su +CALL record_stats(); +-- reltuples of some system catalog tables and indexes may be collected during +-- the template DB creation. Clear them in the recorded stats to pretend they +-- didn't exist. +UPDATE x_stats SET (reltuples, ncolstats) = (-1, 0) WHERE kind IN ('r', 'i') AND reltuples >= 0; + +ANALYZE; +SELECT yb_reset_analyze_statistics(null); +SELECT * FROM diff_stats; + + +-- DB owner +\c - yb_user1 +SET client_min_messages = error; +ANALYZE; +SELECT yb_reset_analyze_statistics(null); +SELECT * FROM diff_stats; + +-- Non-superuser shouldn't be able to reset stats on shared relations +\c - yb_su +ANALYZE; +\c - yb_user1 +CALL record_stats(); +SELECT yb_reset_analyze_statistics(null); +SELECT * FROM diff_stats WHERE s = true; + + +-- Non-DB owner +\c - yb_su +SELECT yb_reset_analyze_statistics(null); +\c - yb_user2 +CALL record_stats(); +SET client_min_messages = error; +ANALYZE; +SELECT yb_reset_analyze_statistics(null); +SELECT * FROM diff_stats; + + +-- Non-DB owner shouldn't be able to reset stats on other users' tables +\c - yb_user2 +SET client_min_messages = error; +ANALYZE; +\c - yb_user3 +CALL record_stats(); +SELECT yb_reset_analyze_statistics(null); +SELECT * FROM diff_stats WHERE owner <> 'yb_user3'; + + +--- Single relation, superuser +\c - yb_su +ANALYZE; +\c - yb_su +CALL record_stats(); +SELECT yb_reset_analyze_statistics('pg_sequence'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('pg_database'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_su'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_u1'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_u2'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_u3'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + + +--- Single relation, DB owner +\c - yb_su +ANALYZE; +\c - yb_user1 +CALL record_stats(); +SELECT yb_reset_analyze_statistics('pg_sequence'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('pg_database'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_su'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_u1'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_u2'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_u3'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + + +-- Single relation, non-DB owner +\c - yb_su +ANALYZE; +\c - yb_user2 +CALL record_stats(); +SELECT yb_reset_analyze_statistics('pg_sequence'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('pg_database'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_su'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_u1'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_u2'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + +CALL record_stats(); +SELECT yb_reset_analyze_statistics('table_u3'::regclass); +SELECT * FROM diff_stats + WHERE relname ~* '(pg_database|pg_sequence).*' + OR owner IN ('yb_su', 'yb_user1', 'yb_user2', 'yb_user3'); + + +-- Error or do nothing +\c - yb_su +ANALYZE; +CALL record_stats(); + +SELECT yb_reset_analyze_statistics(); -- fail +SELECT * FROM diff_stats; + +SELECT yb_reset_analyze_statistics(-1); +SELECT * FROM diff_stats; + +SELECT yb_reset_analyze_statistics(0); +SELECT * FROM diff_stats; + +SELECT yb_reset_analyze_statistics((SELECT max(oid) FROM pg_class WHERE relkind <> 'r')); +SELECT * FROM diff_stats; + + +-- Clean up +\c yugabyte yugabyte +DROP DATABASE reset_analyze_test WITH (FORCE); +DROP USER yb_su; +DROP USER yb_user1; +DROP USER yb_user2; +DROP USER yb_user3; diff --git a/src/test/regress/sql/yb.orig.role_profile.sql b/src/test/regress/sql/yb.orig.role_profile.sql new file mode 100644 index 0000000000000..eaaceb3f43953 --- /dev/null +++ b/src/test/regress/sql/yb.orig.role_profile.sql @@ -0,0 +1,86 @@ +-- +-- YB_ROLE_PROFILE Testsuite: Testing statements for connecting roles and profiles. +-- + +-- +-- pg_catalog alterations. Validate columns of pg_yb_role_profile and oids. +-- +\d pg_yb_role_profile +SELECT oid, relname, reltype, relnatts FROM pg_class WHERE relname IN ('pg_yb_role_profile', 'pg_yb_role_profile_oid_index'); +SELECT oid, typname, typrelid FROM pg_type WHERE typname LIKE 'pg_yb_role_profile'; + +-- +-- CREATE PROFILE +-- +CREATE PROFILE test_profile LIMIT FAILED_LOGIN_ATTEMPTS 3; +CREATE USER restricted_user; + +-- fail: cannot lock/unlock a role that is not attached +ALTER USER restricted_user ACCOUNT UNLOCK; +ALTER USER restricted_user ACCOUNT LOCK; + +-- Can connect when no profiles are setup +\c yugabyte restricted_user +\c yugabyte yugabyte + +-- fail: Cannot attach to a non-existent profile +ALTER USER restricted_user PROFILE non_existent; + +-- Attach role to a profile +SELECT prfname, prfmaxfailedloginattempts FROM pg_catalog.pg_yb_profile ORDER BY OID; +SELECT rolname from pg_catalog.pg_roles WHERE rolname = 'restricted_user'; +ALTER USER restricted_user PROFILE test_profile; +-- +-- Ensure dependencies betwee pg_yb_role_profile & pg_yb_profile +-- and pg_yb_role_profile & pg_roles is setup correctly +-- There should be one row +SELECT count(*) FROM pg_yb_role_profile; +-- One row in pg_shdepend for the role +SELECT count(*) FROM pg_shdepend shdep + JOIN pg_roles rol on rol.oid = shdep.objid + WHERE shdep.deptype = 'f' and rol.rolname = 'restricted_user'; + +-- Can connect when attached to a profile +\c yugabyte restricted_user +\c yugabyte yugabyte + +SELECT rolprfstatus, rolprffailedloginattempts, rolname, prfname FROM + pg_catalog.pg_yb_role_profile rp JOIN pg_catalog.pg_roles rol ON rp.rolprfrole = rol.oid + JOIN pg_catalog.pg_yb_profile lp ON rp.rolprfprofile = lp.oid; + +ALTER USER restricted_user ACCOUNT LOCK; +SELECT rolprfstatus, rolprffailedloginattempts, rolname, prfname FROM + pg_catalog.pg_yb_role_profile rp JOIN pg_catalog.pg_roles rol ON rp.rolprfrole = rol.oid + JOIN pg_catalog.pg_yb_profile lp ON rp.rolprfprofile = lp.oid; + +ALTER USER restricted_user ACCOUNT UNLOCK; +SELECT rolprfstatus, rolprffailedloginattempts, rolname, prfname FROM + pg_catalog.pg_yb_role_profile rp JOIN pg_catalog.pg_roles rol ON rp.rolprfrole = rol.oid + JOIN pg_catalog.pg_yb_profile lp ON rp.rolprfprofile = lp.oid; + +-- Associating a role to the same profile is a no-op +ALTER USER restricted_user PROFILE test_profile; + +-- fail: Cannot drop a profile that has a role associated with it +DROP PROFILE test_profile; + +-- Remove the association of a role to a profile +ALTER USER restricted_user NOPROFILE; +SELECT rolprfstatus, rolprffailedloginattempts, rolname, prfname FROM + pg_catalog.pg_yb_role_profile rp JOIN pg_catalog.pg_roles rol ON rp.rolprfrole = rol.oid + JOIN pg_catalog.pg_yb_profile lp ON rp.rolprfprofile = lp.oid; +-- +-- Ensure dependencies betwee pg_yb_role_profile & pg_yb_profile +-- and pg_yb_role_profile & pg_roles is setup correctly +-- There should be zero rows +SELECT count(*) FROM pg_yb_role_profile; +-- pg_shdepend for the role +SELECT count(*) FROM pg_shdepend shdep + JOIN pg_roles rol on rol.oid = shdep.objid + WHERE shdep.deptype = 'f' and rol.rolname = 'restricted_user'; + +-- Can drop user as it removes dependencies automatically. +DROP USER restricted_user; +SELECT count(*) FROM pg_yb_role_profile; + +DROP PROFILE test_profile; diff --git a/src/test/regress/sql/yb.orig.roles.sql b/src/test/regress/sql/yb.orig.roles.sql new file mode 100644 index 0000000000000..7987db8fdda45 --- /dev/null +++ b/src/test/regress/sql/yb.orig.roles.sql @@ -0,0 +1,98 @@ +-- +-- Test yb_extension role +-- +CREATE USER regress_priv_user; +CREATE OPERATOR FAMILY alt_opf1 USING hash; +SET SESSION AUTHORIZATION regress_priv_user; +CREATE EXTENSION pgcrypto; -- should fail +CREATE EXTENSION orafce; -- should fail +\c - +GRANT yb_extension TO regress_priv_user; +SET SESSION AUTHORIZATION regress_priv_user; +CREATE EXTENSION pgcrypto; +ALTER EXTENSION pgcrypto UPDATE TO '1.3'; +DROP EXTENSION pgcrypto; +CREATE EXTENSION pg_trgm WITH VERSION '1.3'; +ALTER EXTENSION pg_trgm UPDATE TO '1.4'; +DROP EXTENSION pg_trgm; +ALTER OPERATOR FAMILY alt_opf1 USING hash ADD -- should fail + OPERATOR 1 < (int1, int2); +\c - +DROP USER regress_priv_user; + +-- +-- Test yb_fdw role +-- +CREATE USER regress_priv_user1; +CREATE USER regress_priv_user2; +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE FOREIGN DATA WRAPPER useless; -- should fail +\c - +GRANT yb_fdw TO regress_priv_user1 WITH ADMIN OPTION; +GRANT yb_extension TO regress_priv_user1; + +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE FOREIGN DATA WRAPPER useless; +ALTER FOREIGN DATA WRAPPER useless NO VALIDATOR; +ALTER FOREIGN DATA WRAPPER useless OWNER TO regress_priv_user2; -- should fail +GRANT yb_fdw TO regress_priv_user2; +ALTER FOREIGN DATA WRAPPER useless OWNER TO regress_priv_user2; +CREATE SERVER s1 FOREIGN DATA WRAPPER useless; -- should fail, since the owner changed +SET SESSION AUTHORIZATION regress_priv_user2; +CREATE SERVER s1 FOREIGN DATA WRAPPER useless; +ALTER SERVER s1 OPTIONS (host 'foo', dbname 'foodb'); +DROP SERVER s1; +DROP FOREIGN DATA WRAPPER useless; + +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE EXTENSION file_fdw; +CREATE SERVER s1 FOREIGN DATA WRAPPER file_fdw; +-- should not allow the user to set the filename/program options as they require more privileges +CREATE FOREIGN TABLE test (x int) SERVER s1 OPTIONS ( filename 'foo'); +CREATE FOREIGN TABLE test (x int) SERVER s1 OPTIONS ( program 'foo'); +DROP SERVER s1; +DROP EXTENSION file_fdw; + +\c - +DROP USER regress_priv_user1; +DROP USER regress_priv_user2; + +-- test password change with BYPASSRLS attribute +CREATE ROLE user1 BYPASSRLS; +ALTER ROLE user1 PASSWORD 'password'; +DROP ROLE user1; + +-- +-- Test yb_db_admin role +-- +-- verify yb_db_admin role can change bypassrls attribute +CREATE ROLE regress_test_user1; +SET SESSION ROLE yb_db_admin; +ALTER ROLE regress_test_user1 WITH BYPASSRLS; +CREATE ROLE regress_test_user2 WITH BYPASSRLS; +ALTER ROLE regress_test_user2 WITH NOBYPASSRLS; + +-- clean up +SET SESSION ROLE yugabyte; +DROP ROLE regress_test_user1; +DROP ROLE regress_test_user2; + +-- +-- Test YB Managed admin role +-- +RESET SESSION AUTHORIZATION; +CREATE USER regress_priv_user; +GRANT yb_extension TO regress_priv_user; +GRANT yb_fdw TO regress_priv_user; +GRANT yb_db_admin TO regress_priv_user WITH ADMIN OPTION; +SET SESSION AUTHORIZATION regress_priv_user; +CREATE EXTENSION PGAudit; +ALTER EXTENSION PGAudit UPDATE TO '1.7.1'; +DROP EXTENSION PGAudit; +CREATE EXTENSION orafce; +ALTER EXTENSION orafce UPDATE TO '4.9'; +DROP EXTENSION orafce; +-- removing yb_db_admin role should result in error +REVOKE yb_db_admin FROM regress_priv_user; +CREATE EXTENSION PGAudit; +DROP EXTENSION PGAudit; diff --git a/src/test/regress/sql/yb.orig.row_estimates.sql b/src/test/regress/sql/yb.orig.row_estimates.sql new file mode 100644 index 0000000000000..1a9c83694092f --- /dev/null +++ b/src/test/regress/sql/yb.orig.row_estimates.sql @@ -0,0 +1,448 @@ +-- Test row count estimates + +set client_min_messages = 'warning'; +drop function if exists check_estimated_rows, get_estimated_rows; + +-- following plpgsql functions borrowed from vanilla pg tests + +-- from gin.sql, removed guc settings and ANALYZE option +create function explain_query_json(query_sql text) +returns table (explain_line json) +language plpgsql as +$$ +begin + return query execute 'EXPLAIN (FORMAT json) ' || query_sql; +end; +$$; + +-- from stats_ext.sql, removed the analyze option. +-- check the number of estimated/actual rows in the top node +create function check_estimated_rows(text) returns table (estimated int, actual int) +language plpgsql as +$$ +declare + ln text; + tmp text[]; + first_row bool := true; +begin + for ln in + execute format('explain analyze %s', $1) + loop + if first_row then + first_row := false; + tmp := regexp_match(ln, 'rows=(\d*) .* rows=(\d*)'); + return query select tmp[1]::int, tmp[2]::int; + end if; + end loop; +end; +$$; + +-- modified the above to return estimates only +create function get_estimated_rows(text) returns table (estimated int) +language plpgsql as +$$ +declare + ln text; + tmp text[]; + first_row bool := true; +begin + for ln in + execute format('explain %s', $1) + loop + if first_row then + first_row := false; + tmp := regexp_match(ln, 'rows=(\d*)'); + return query select tmp[1]::int; + end if; + end loop; +end; +$$; + + +-- create and populate the test tables with uniformly distributed values +-- to make the estimates predictable. +drop table if exists r, s; +create table r (pk int, a int, b int, c char(10), d int, e int, bl bool, v char(666), primary key (pk asc)); +create table s (x int, y int, z char(10)); +create index i_r_a on r (a asc); +create index i_r_a_ge_1k on r (a asc) where a >= 1000; +create unique index i_r_b on r (b asc); +create index i_r_c on r (c asc); +create index i_r_bl on r (bl hash); + +insert into r + select + i, i / 10, i, + -- 'aaa', 'aab', 'aac', ... + concat(chr((((i-1)/26/26) % 26) + ascii('a')), + chr((((i-1)/26) % 26) + ascii('a')), + chr(((i-1) % 26) + ascii('a'))), + i, i / 10, + i % 2 = 1, + sha512(('x'||i)::bytea)::bpchar||lpad(sha512((i||'y')::bytea)::bpchar, 536, '#') + from generate_series(1, 12345) i; + +insert into s + select + i / 3, i, + concat(chr((((i-1)/26/26) % 26) + ascii('a')), + chr((((i-1)/26) % 26) + ascii('a')), + chr(((i-1) % 26) + ascii('a'))) + from generate_series(1, 123) i; + + +-- store test queries in a table +drop table if exists queries; +create table queries (id serial, hard boolean, query text, primary key (id asc)); +insert into queries values + (DEFAULT, false, 'select * from r'), + (DEFAULT, false, 'select * from r where pk = 123'), + (DEFAULT, false, 'select * from r where pk < 123'), + (DEFAULT, false, 'select * from r where pk > 123'), + (DEFAULT, false, 'select * from r where pk <= 123'), + (DEFAULT, false, 'select * from r where pk >= 123'), + (DEFAULT, false, 'select * from r where pk <> 123'), + (DEFAULT, false, 'select * from r where pk between 123 and 456'), + (DEFAULT, false, 'select * from r where a = 123'), + (DEFAULT, false, 'select * from r where a < 123'), + (DEFAULT, false, 'select * from r where a > 123'), + (DEFAULT, false, 'select * from r where a <= 123'), + (DEFAULT, false, 'select * from r where a >= 123'), + (DEFAULT, false, 'select * from r where a <> 123'), + (DEFAULT, false, 'select * from r where a between 123 and 456'), + (DEFAULT, false, 'select * from r where b = 123'), + (DEFAULT, false, 'select * from r where b < 123'), + (DEFAULT, false, 'select * from r where b > 123'), + (DEFAULT, false, 'select * from r where b <= 123'), + (DEFAULT, false, 'select * from r where b >= 123'), + (DEFAULT, false, 'select * from r where b <> 123'), + (DEFAULT, false, 'select * from r where b between 123 and 456'), + (DEFAULT, false, 'select * from r where d = 123'), + (DEFAULT, false, 'select * from r where d < 123'), + (DEFAULT, false, 'select * from r where d > 123'), + (DEFAULT, false, 'select * from r where d <= 123'), + (DEFAULT, false, 'select * from r where d >= 123'), + (DEFAULT, false, 'select * from r where d <> 123'), + (DEFAULT, false, 'select * from r where d between 123 and 456'), + (DEFAULT, false, 'select * from r where e = 123'), + (DEFAULT, false, 'select * from r where e < 123'), + (DEFAULT, false, 'select * from r where e > 123'), + (DEFAULT, false, 'select * from r where e <= 123'), + (DEFAULT, false, 'select * from r where e >= 123'), + (DEFAULT, false, 'select * from r where e <> 123'), + (DEFAULT, false, 'select * from r where e between 123 and 456'), + (DEFAULT, false, 'select * from r where c = ''abc'''), + (DEFAULT, true, 'select * from r where c < ''ab'''), + (DEFAULT, true, 'select * from r where c > ''ab'''), + (DEFAULT, true, 'select * from r where c <= ''ab'''), + (DEFAULT, true, 'select * from r where c >= ''ab'''), + (DEFAULT, false, 'select * from r where c <> ''abc'''), + (DEFAULT, true, 'select * from r where c between ''ab'' and ''bz'''), + (DEFAULT, true, 'select * from r where c like ''ab%'''), + (DEFAULT, true, 'select * from r where c not like ''ab%'''), + (DEFAULT, false, 'select count(*) from r group by pk'), + (DEFAULT, false, 'select count(*) from r group by a'), + (DEFAULT, false, 'select count(*) from r group by b'), + (DEFAULT, false, 'select count(*) from r group by c'), + (DEFAULT, false, 'select count(*) from r group by d'), + (DEFAULT, false, 'select count(*) from r group by e'), + (DEFAULT, false, 'select distinct pk from r'), + (DEFAULT, false, 'select distinct a from r'), + (DEFAULT, false, 'select distinct b from r'), + (DEFAULT, false, 'select distinct c from r'), + (DEFAULT, false, 'select distinct d from r'), + (DEFAULT, false, 'select distinct e from r'), + (DEFAULT, true, 'select * from r, s where pk = x'), + (DEFAULT, true, 'select * from r, s where pk = y'), + (DEFAULT, true, 'select * from r, s where a = x'), + (DEFAULT, true, 'select * from r, s where a = y'), + (DEFAULT, true, 'select * from r, s where b = x'), + (DEFAULT, true, 'select * from r, s where b = y'), + (DEFAULT, true, 'select * from r, s where d = x'), + (DEFAULT, true, 'select * from r, s where d = y'), + (DEFAULT, true, 'select * from r, s where e = x'), + (DEFAULT, true, 'select * from r, s where e = y'), + (DEFAULT, true, 'select * from r, s where c = z'), + (DEFAULT, true, 'select * from r, s where pk < x'), + (DEFAULT, true, 'select * from r, s where pk < y'), + (DEFAULT, true, 'select * from r, s where a < x'), + (DEFAULT, true, 'select * from r, s where a < y'), + (DEFAULT, true, 'select * from r, s where b < x'), + (DEFAULT, true, 'select * from r, s where b < y'), + (DEFAULT, true, 'select * from r, s where d < x'), + (DEFAULT, true, 'select * from r, s where d < y'), + (DEFAULT, true, 'select * from r, s where e < x'), + (DEFAULT, true, 'select * from r, s where e < y'), + (DEFAULT, true, 'select * from r, s where c < z'), + (DEFAULT, true, 'select * from r, s where x < pk'), + (DEFAULT, true, 'select * from r, s where y < pk'), + (DEFAULT, true, 'select * from r, s where x < a'), + (DEFAULT, true, 'select * from r, s where y < a'), + (DEFAULT, true, 'select * from r, s where x < b'), + (DEFAULT, true, 'select * from r, s where y < b'), + (DEFAULT, true, 'select * from r, s where x < d'), + (DEFAULT, true, 'select * from r, s where y < d'), + (DEFAULT, true, 'select * from r, s where x < e'), + (DEFAULT, true, 'select * from r, s where y < e'), + (DEFAULT, true, 'select * from r, s where z < c'), + (DEFAULT, false, 'select * from r where a = 10 and e = 10'), + (DEFAULT, false, 'select 0 from r group by a, e'), + (DEFAULT, false, 'select distinct a, e from r'), + (DEFAULT, false, 'select * from r where b % 5 = 0'), + (DEFAULT, false, 'select distinct b % 5 from r'), + (DEFAULT, false, 'select 0 from r group by b % 5'), + (DEFAULT, false, 'select 0 from r where a between 1000 and 1200'); + + +-- +-- check estimates with different yb_enable_cbo values +-- + +drop table if exists off_before_analyze, off_after_analyze; +drop table if exists on_before_analyze, on_after_analyze; +drop table if exists legacy_before_analyze, legacy_after_analyze; +drop table if exists legacy_stats_before_analyze, legacy_stats_after_analyze; +drop table if exists cbo_estimates; + +-- save the estimates before analyze + +set yb_enable_cbo = off; +create temporary table off_before_analyze as + select id, query, get_estimated_rows(query) rows + from queries q; + +set yb_enable_cbo = on; +create temporary table on_before_analyze as + select id, query, get_estimated_rows(query) rows + from queries q; + +set yb_enable_cbo = legacy_mode; +create temporary table legacy_before_analyze as + select id, query, get_estimated_rows(query) rows + from queries q; + +set yb_enable_cbo = legacy_stats_mode; +create temporary table legacy_stats_before_analyze as + select id, query, get_estimated_rows(query) rows + from queries q; + + +-- create extended stats then analyze + +create statistics r_a_e on a, e from r; +create statistics r_bmod5 on (b % 5) from r; +analyze r, s; + + +-- save the estimates after analyze + +set yb_enable_cbo = off; +create temporary table off_after_analyze as + select id, query, get_estimated_rows(query) rows + from queries q; + +set yb_enable_cbo = on; +create temporary table on_after_analyze as + select id, query, get_estimated_rows(query) rows + from queries q; + +set yb_enable_cbo = legacy_mode; +create temporary table legacy_after_analyze as + select id, query, get_estimated_rows(query) rows + from queries q; + +set yb_enable_cbo = legacy_stats_mode; +create temporary table legacy_stats_after_analyze as + select id, query, get_estimated_rows(query) rows + from queries q; + + +-- see how the estimates change before vs. after analyze + +--- no predicate --- +-- off: default -> unchanged +select b.id, b.query, b.rows before, a.rows after + from off_before_analyze b join off_after_analyze a using (id) + where id = 1 order by id; + +-- on, legacy, legacy_stats: default -> accurate +select b.id, b.query, b.rows before, a.rows after + from on_before_analyze b join on_after_analyze a using (id) + where id = 1 order by id; + +select b.id, b.query, b.rows before, a.rows after + from legacy_before_analyze b join legacy_after_analyze a using (id) + where id = 1 order by id; + +select b.id, b.query, b.rows before, a.rows after + from legacy_stats_before_analyze b join legacy_stats_after_analyze a using (id) + where id = 1 order by id; + + +--- equality --- +-- off: +-- unique indexed: 1 -> unchanged +-- non-unique indexed: 1% (YBC_SINGLE_KEY_SELECTIVITY) of 1000 -> unchanged +-- unindexed: 1000 (predicate ignored) -> unchanged +select b.id, b.query, b.rows before, a.rows after + from off_before_analyze b join off_after_analyze a using (id) + where b.query ~ 'p*[kabde] = 123' order by id; + +-- on, legacy_stats: +-- unique indexed: 1 -> 1 (unchanged, accurate) +-- others (non-unique indexed & unindexed): 5% (DEFAULT_EQ_SEL) of 1000 -> accurate +select b.id, b.query, b.rows before, a.rows after + from on_before_analyze b join on_after_analyze a using (id) + where b.query ~ 'p*[kabde] = 123' order by id; + +select b.id, b.query, b.rows before, a.rows after + from legacy_stats_before_analyze b join legacy_stats_after_analyze a using (id) + where b.query ~ 'p*[kabde] = 123' order by id; + +-- legacy: +-- unique indexed: 1 -> 1 (unchanged, accurate) +-- non-unique indexed: 1% of 1000 -> 1% of 12345 (predicate ignored) +-- unindexed: 1000 -> 12345 (predicate ignored) +select b.id, b.query, b.rows before, a.rows after + from legacy_before_analyze b join legacy_after_analyze a using (id) + where b.query ~ 'p*[kabde] = 123' order by id; + + +--- group by --- +-- off: +-- unique indexed: 1000 -> unchanged +-- others: 200 (DEFAULT_NUM_DISTINCT) -> unchanged +select b.id, b.query, b.rows before, a.rows after + from off_before_analyze b join off_after_analyze a using (id) + where b.query ~ 'group by p*[kabde]$' order by id; + +-- on, legacy_stats, legacy: +-- unique indexed: 1000 -> 1000 (unchanged, accurate) +-- others: 200 -> accurate +select b.id, b.query, b.rows before, a.rows after + from on_before_analyze b join on_after_analyze a using (id) + where b.query ~ 'group by p*[kabde]$' order by id; + +select b.id, b.query, b.rows before, a.rows after + from legacy_stats_before_analyze b join legacy_stats_after_analyze a using (id) + where b.query ~ 'group by p*[kabde]$' order by id; + +select b.id, b.query, b.rows before, a.rows after + from legacy_before_analyze b join legacy_after_analyze a using (id) + where b.query ~ 'group by p*[kabde]$' order by id; + + +--- equi-join --- +-- off: +-- unique indexed: 1000 -> unchanged +-- others: 1000 * 1000 * (1/200) -> unchanged +select b.id, b.query, b.rows before, a.rows after + from off_before_analyze b join off_after_analyze a using (id) + where b.query ~ 'where p*[kabde] = [xy]' order by id; + +-- on, legacy_stats, legacy: +-- unique indexed: 1000 -> 123 (accurate) +-- others: 1000 * 1000 * (1/200) -> 123 or 1230 (accurate) +select b.id, b.query, b.rows before, a.rows after + from on_before_analyze b join on_after_analyze a using (id) + where b.query ~ 'where p*[kabde] = [xy]' order by id; + +select b.id, b.query, b.rows before, a.rows after + from legacy_stats_before_analyze b join legacy_stats_after_analyze a using (id) + where b.query ~ 'where p*[kabde] = [xy]' order by id; + +select b.id, b.query, b.rows before, a.rows after + from legacy_before_analyze b join legacy_after_analyze a using (id) + where b.query ~ 'where p*[kabde] = [xy]' order by id; + + +-- should return no row as the estimates match +select * +from legacy_before_analyze t1 join off_before_analyze t2 using (id) +where t1.rows <> t2.rows; + +select * +from off_before_analyze t1 join off_after_analyze t2 using (id) +where t1.rows <> t2.rows; + + +-- +-- check CBO estimates for easy queries +-- + +set yb_enable_cbo = on; + +create temporary table cbo_estimates ( + id int, + query text, + rows int[] +); + +insert into cbo_estimates + select + id, query, + string_to_array(trim(both '()' from check_estimated_rows(query)::bpchar), ',')::int[] + from queries where hard = false; + +-- should return no row +select id, query, rows[1] estimated, rows[2] actual +from cbo_estimates +where abs(rows[1] - rows[2]) > 2 + and abs(rows[1] - rows[2])/least(rows[1], rows[2])::float > 0.005; + + +-- +-- test yb_ignore_bool_cond_for_legacy_estimate +-- +-- print the costs and row count estimates that should never change in +-- legacy modes. +-- + +begin; +set local yb_enable_cbo = off; + +set local yb_ignore_bool_cond_for_legacy_estimate = off; +explain (costs on, summary off) +select * from r where not bl; +explain (costs on, summary off) +select * from r where not bl and a = 1; +explain (costs on, summary off) +select * from r where not bl and b = 1; + +set local yb_ignore_bool_cond_for_legacy_estimate = on; +explain (costs on, summary off) +select * from r where not bl; +explain (costs on, summary off) +select * from r where not bl and a = 1; +explain (costs on, summary off) +select * from r where not bl and b = 1; + +set local enable_seqscan = off; +explain (costs on, summary off) +select * from r where not bl; +rollback; + +-- +-- test selectivity estimates +-- +analyze r, s; + +set yb_enable_cbo = on; +set yb_enable_bitmapscan = on; +set enable_bitmapscan = on; + +-- parameterized filter condition in Bitmap Table Scan. +-- the selectivity should be close to DEFAULT_INEQ_SEL (0.3333333333333333). +select + bts->'Node Type' bmts, + bts->'Storage Filter' bmts_filter, + round((bts->'Plan Rows')::text::numeric / (bts->'Plans'->0->'Plan Rows')::text::numeric, 2) sel +from + explain_query_json($$/*+ Leading((s r)) NestLoop(s r) BitmapScan(r) Set(yb_bnl_batch_size 1) */select * from r, s where (a = x or b <= 300) and a + b >= y$$) js, + lateral to_json(js.explain_line->0->'Plan'->'Plans'->1) bts; + +explain (costs off) +/*+ Leading((s r)) NestLoop(s r) BitmapScan(r) Set(yb_bnl_batch_size 1) */select * from r, s where (a = x or b <= 300) and a + b >= y; + + +drop table if exists r, s, queries; diff --git a/src/test/regress/sql/yb.orig.rowsecurity.sql b/src/test/regress/sql/yb.orig.rowsecurity.sql new file mode 100644 index 0000000000000..5efc7159ca3b1 --- /dev/null +++ b/src/test/regress/sql/yb.orig.rowsecurity.sql @@ -0,0 +1,17 @@ +-- Verify yb_db_admin can alter rows in tables it does not own +CREATE TABLE foo(a INT); +INSERT INTO foo VALUES (0); +INSERT INTO foo VALUES (1); +INSERT INTO foo VALUES (2); +SET SESSION AUTHORIZATION yb_db_admin; +SELECT * from foo ORDER BY a; +DELETE FROM foo WHERE a = 2; +SELECT * from foo ORDER BY a; +UPDATE foo SET a = 5 WHERE a = 0; +SELECT * from foo ORDER BY a; +TRUNCATE foo; +SELECT * from foo ORDER BY a; +-- Check yb_db_admin cannot alter sys tables +UPDATE pg_shdepend SET dbid=1 WHERE dbid=0; +DELETE from pg_shdepend WHERE dbid=0; +TRUNCATE pg_shdepend; diff --git a/src/test/regress/sql/yb.orig.schema.sql b/src/test/regress/sql/yb.orig.schema.sql new file mode 100644 index 0000000000000..7f25534db38ad --- /dev/null +++ b/src/test/regress/sql/yb.orig.schema.sql @@ -0,0 +1,112 @@ +-- +-- SCHEMA +-- + +-- Create 2 schemas with table of the same name in each. +CREATE SCHEMA S1; +CREATE SCHEMA S2; + +CREATE TABLE S1.TBL (a1 int PRIMARY KEY); +CREATE TABLE S2.TBL (a2 text PRIMARY KEY); + +-- Insert values into the tables and verify both can be queried. +INSERT INTO S1.TBL VALUES (1); +INSERT INTO S2.TBL VALUES ('a'); + +SELECT * FROM S1.TBL; +SELECT * FROM S2.TBL; + +-- Drop one table and verify the other still exists. +DROP TABLE S1.TBL; +SELECT * FROM S2.TBL; + +DROP TABLE S2.TBL; + +-- Test ALTER TABLE [IF EXISTS].. SET SCHEMA.. +CREATE TABLE S1.TBL2 (a1 int PRIMARY KEY, a2 int); +CREATE INDEX IDX2 ON S1.TBL2(a2); + +INSERT INTO S1.TBL2 VALUES (2, 2); +SELECT * FROM S1.TBL2; +\d S1.TBL2; + +ALTER TABLE S1.TBL2 SET SCHEMA S2; +SELECT * FROM S1.TBL2; +SELECT * FROM S2.TBL2; +\d S2.TBL2; +\d S2.IDX2; + +DROP TABLE S2.TBL2; +SELECT * FROM S2.TBL2; + +ALTER TABLE S2.TBL2 SET SCHEMA S1; -- the table was deleted +ALTER TABLE IF EXISTS S2.TBL2 SET SCHEMA S1; -- OK + +-- for partitioned table +CREATE TABLE S1.P_TBL (k INT PRIMARY KEY, value TEXT) PARTITION BY RANGE(k); +CREATE TABLE S1.P_TBL_1 PARTITION OF S1.P_TBL FOR VALUES FROM (1) TO (3); +CREATE TABLE S1.P_TBL_DEFAULT PARTITION OF S1.P_TBL DEFAULT; +CREATE INDEX P_TBL_K_IDX on S1.P_TBL(k); + +ALTER TABLE S1.P_TBL SET SCHEMA S2; +\d+ S2.P_TBL + +DROP TABLE S2.P_TBL; + +-- for temp table +CREATE TEMP TABLE TMP_TBL (a INT PRIMARY KEY); + +ALTER TABLE TMP_TBL SET SCHEMA S2; + +DROP TABLE TMP_TBL; + +-- Test ALTER SEQUENCE [IF EXISTS].. SET SCHEMA.. +CREATE SEQUENCE S1.TEST_SEQ; +SELECT nextval('S1.TEST_SEQ'); +\d S1.TEST_SEQ; + +select c.relname as name, n.nspname as schema, r.rolname as owner +from pg_class as c + inner join pg_namespace as n on c.relnamespace = n.oid + inner join pg_roles as r on c.relowner = r.oid +where c.relname ~ 'test_seq'; + +ALTER SEQUENCE S1.TEST_SEQ SET SCHEMA S2; +SELECT nextval('S1.TEST_SEQ'); +SELECT nextval('S2.TEST_SEQ'); +\d S1.TEST_SEQ; +\d S2.TEST_SEQ; + +ALTER SEQUENCE S2.TEST_SEQ OWNER TO CURRENT_USER; +ALTER SEQUENCE S2.TEST_SEQ OWNER TO SESSION_USER; +ALTER SEQUENCE S2.TEST_SEQ OWNER TO postgres; + +select c.relname as name, n.nspname as schema, r.rolname as owner +from pg_class as c + inner join pg_namespace as n on c.relnamespace = n.oid + inner join pg_roles as r on c.relowner = r.oid +where c.relname ~ 'test_seq'; + +DROP SEQUENCE S2.TEST_SEQ; +SELECT * FROM S2.TEST_SEQ; + +ALTER TABLE S2.TEST_SEQ SET SCHEMA S1; -- the sequence was deleted +ALTER TABLE IF EXISTS S2.TEST_SEQ SET SCHEMA S1; -- OK + +-- verify yb_db_admin role can manage schemas like a superuser +CREATE SCHEMA test_ns_schema_other; +CREATE ROLE test_regress_user1; +SET SESSION AUTHORIZATION yb_db_admin; +ALTER SCHEMA test_ns_schema_other RENAME TO test_ns_schema_other_new; +ALTER SCHEMA test_ns_schema_other_new OWNER TO test_regress_user1; +DROP SCHEMA test_ns_schema_other_new; +-- verify that the objects were dropped +SELECT COUNT(*) FROM pg_class WHERE relnamespace = + (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_other_new'); +CREATE SCHEMA test_ns_schema_yb_db_admin; +ALTER SCHEMA test_ns_schema_yb_db_admin RENAME TO test_ns_schema_yb_db_admin_new; +ALTER SCHEMA test_ns_schema_yb_db_admin_new OWNER TO test_regress_user1; +DROP SCHEMA test_ns_schema_yb_db_admin_new; +-- verify that the objects were dropped +SELECT COUNT(*) FROM pg_class WHERE relnamespace = + (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_yb_db_admin_new'); diff --git a/src/test/regress/sql/yb.orig.secondary_index_scan.sql b/src/test/regress/sql/yb.orig.secondary_index_scan.sql new file mode 100644 index 0000000000000..16d746b2fff5c --- /dev/null +++ b/src/test/regress/sql/yb.orig.secondary_index_scan.sql @@ -0,0 +1,102 @@ +-- +-- This file is to check correctness of data after applying secondary index scan optimization. +-- +-- +-- Logical test on small size table, using column-ref expression index. +-- +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (COSTS OFF)' + +CREATE TABLE text_books ( id int PRIMARY KEY, author text, year int); +CREATE INDEX text_books_author_first_name ON text_books (author); +-- +INSERT INTO text_books (id, author, year) + VALUES (1, '{ "first_name": "William", "last_name": "Shakespeare" }', 1623); +INSERT INTO text_books (id, author, year) + VALUES (2, '{ "first_name": "William", "last_name": "Shakespeare" }', 1603); +INSERT INTO text_books (id, author, year) + VALUES (3, '{ "first_name": "Charles", "last_name": "Dickens" }', 1838); +INSERT INTO text_books (id, author, year) + VALUES (4, '{ "first_name": "Charles", "last_name": "Dickens" }', 1950); +INSERT INTO text_books (id, author, year) + VALUES (5, '{ "first_name": "Stephen", "last_name": "Hawking" }', 1988); +-- +SELECT $$ +:P SELECT id FROM text_books WHERE author = 'Hello World' ORDER BY year; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT id FROM text_books + WHERE author = '{ "first_name": "William", "last_name": "Shakespeare" }' ORDER BY year; +$$ AS query \gset +\i :iter_P2 +-- Drop INDEX and run again. +DROP index text_books_author_first_name; +SELECT $$ +:P SELECT id FROM text_books WHERE author = 'Hello World' ORDER BY year; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT id FROM text_books + WHERE author = '{ "first_name": "William", "last_name": "Shakespeare" }' ORDER BY year; +$$ AS query \gset +\i :iter_P2 +-- +-- Logical test on small size table, using JSONB expression index. +-- +CREATE TABLE books ( id int PRIMARY KEY, details jsonb ); +CREATE INDEX books_author_first_name ON books ((details->'author'->>'first_name')); +INSERT INTO books (id, details) + VALUES (1, '{ "name": "Macbeth", + "author": { "first_name": "William", "last_name": "Shakespeare" }, + "year": 1623, + "editors": ["John", "Elizabeth", "Jeff"] }'); +INSERT INTO books (id, details) + VALUES (2, '{ "name": "Hamlet", + "author": { "first_name": "William", "last_name": "Shakespeare" }, + "year": 1603, + "editors": ["Lysa", "Mark", "Robert"] }'); +INSERT INTO books (id, details) + VALUES (3, '{ "name": "Oliver Twist", + "author": { "first_name": "Charles", "last_name": "Dickens" }, + "year": 1838, + "genre": "novel", + "editors": ["Mark", "Tony", "Britney"] }'); +INSERT INTO books (id, details) + VALUES (4, '{ "name": "Great Expectations", + "author": { "first_name": "Charles", "last_name": "Dickens" }, + "year": 1950, + "genre": "novel", + "editors": ["Robert", "John", "Melisa"] }'); +INSERT INTO books (id, details) + VALUES (5, '{ "name": "A Brief History of Time", + "author": { "first_name": "Stephen", "last_name": "Hawking" }, + "year": 1988, + "genre": "science", + "editors": ["Melisa", "Mark", "John"] }'); +SELECT $$ +:P SELECT id FROM books WHERE details->'author'->>'first_name' = 'Hello World' + ORDER BY details->>'name'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT id FROM books WHERE details->'author'->>'first_name' = 'Charles' + ORDER BY details->>'name'; +$$ AS query \gset +\i :iter_P2 +-- Drop INDEX and run again. +DROP index books_author_first_name; +SELECT $$ +:P SELECT id FROM books WHERE details->'author'->>'first_name' = 'Hello World' + ORDER BY details->>'name'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT id FROM books WHERE details->'author'->>'first_name' = 'Charles' + ORDER BY details->>'name'; +$$ AS query \gset +\i :iter_P2 diff --git a/src/test/regress/sql/yb.orig.select.sql b/src/test/regress/sql/yb.orig.select.sql new file mode 100644 index 0000000000000..c49cc8df2598f --- /dev/null +++ b/src/test/regress/sql/yb.orig.select.sql @@ -0,0 +1,275 @@ +-- +-- SELECT +-- + +CREATE TABLE nr (i int, j int, PRIMARY KEY (j ASC)); +CREATE INDEX ON nr (i ASC); +INSERT INTO nr VALUES (1, 2), (3, 4); +BEGIN; +INSERT INTO nr VALUES (null, 6); +SELECT i FROM nr ORDER BY i; +SELECT i FROM nr ORDER BY i NULLS FIRST; +SELECT i FROM nr ORDER BY i DESC; +SELECT i FROM nr ORDER BY i DESC NULLS LAST; +ROLLBACK; + +CREATE TABLE nr2 (i int, j int, PRIMARY KEY (j ASC)); +CREATE INDEX ON nr2 (i DESC); +INSERT INTO nr2 VALUES (1, 2), (3, 4); +BEGIN; +INSERT INTO nr2 VALUES (null, 6); +SELECT i FROM nr2 ORDER BY i; +SELECT i FROM nr2 ORDER BY i NULLS FIRST; +SELECT i FROM nr2 ORDER BY i DESC; +SELECT i FROM nr2 ORDER BY i DESC NULLS LAST; +ROLLBACK; + +-- +-- Test reverse scans with limit. +-- +CREATE TABLE reverse_scan_test ( + h BIGINT, + r INT, + PRIMARY KEY(h, r ASC) +); + +INSERT INTO reverse_scan_test +VALUES (1, 1), + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (1, 6), + (1, 7), + (1, 8), + (1, 9), + (1, 10), + (1, 11), + (1, 12), + (1, 13), + (1, 14); + +-- Check ascending order. +SELECT r FROM reverse_scan_test WHERE h = 1; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint); + +SELECT r FROM reverse_scan_test WHERE h = 1 AND r > 2 and r < 9; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint) AND r > 2 and r < 9; + +SELECT r FROM reverse_scan_test WHERE h = 1 AND r > 2 and r < 9 LIMIT 5; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint) AND r > 2 and r < 9 LIMIT 5; + +SELECT r FROM reverse_scan_test WHERE h = 1 AND r >= 2 and r <= 13; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint) AND r >= 2 and r <= 13; + +SELECT r FROM reverse_scan_test WHERE h = 1 AND r >= 2 and r <= 13 LIMIT 10; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint) AND r >= 2 and r <= 13 LIMIT 10; + +-- Test desc order (reverse scan). +SELECT r FROM reverse_scan_test WHERE h = 1 ORDER BY r DESC; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint) ORDER BY r DESC; + +SELECT r FROM reverse_scan_test WHERE h = 1 ORDER BY r DESC LIMIT 5; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint) ORDER BY r DESC LIMIT 5; + +SELECT r FROM reverse_scan_test WHERE h = 1 AND r < 9 ORDER BY r DESC LIMIT 5; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint) AND r < 9 ORDER BY r DESC LIMIT 5; + +SELECT r FROM reverse_scan_test WHERE h = 1 AND r > 1 AND r < 14 ORDER BY r DESC; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint) AND r > 1 AND r < 14 ORDER BY r DESC; + +SELECT r FROM reverse_scan_test WHERE h = 1 AND r > 1 AND r < 14 ORDER BY r DESC LIMIT 9; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint) AND r > 1 AND r < 14 ORDER BY r DESC LIMIT 9; + +SELECT r FROM reverse_scan_test WHERE h = 1 AND r >= 3 and r <= 13 ORDER BY r DESC; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint) AND r >= 3 and r <= 13 ORDER BY r DESC; + +SELECT r FROM reverse_scan_test WHERE h = 1 AND r >= 3 and r <= 13 ORDER BY r DESC LIMIT 8; + +SELECT r FROM reverse_scan_test WHERE yb_hash_code(h) = yb_hash_code(1::bigint) AND r >= 3 and r <= 13 ORDER BY r DESC LIMIT 8; + +PREPARE myplan AS SELECT * FROM reverse_scan_test LIMIT $1; + +-- Execute myplan > 5 times. After 5 times the plan should default to the +-- generic plan +EXECUTE myplan(0); +EXPLAIN EXECUTE myplan(null); +EXECUTE myplan(1); +EXECUTE myplan(2); +EXECUTE myplan(3); +-- generic plan with limit node should be used +EXPLAIN EXECUTE myplan(null); +EXECUTE myplan(null); +EXECUTE myplan(0); +EXECUTE myplan(1); + +-- +-- For https://github.com/YugaByte/yugabyte-db/issues/10254 +-- +CREATE TABLE t(h INT, r INT, PRIMARY KEY(h, r ASC)); +INSERT INTO t VALUES(1, 1), (1, 3); +SELECT * FROM t WHERE h = 1 AND r in(1, 3) FOR KEY SHARE; +-- On this query postgres process stucked in an infinite loop. +SELECT * FROM t WHERE h = 1 AND r IN (1, 2, 3) FOR KEY SHARE; + +-- Testing distinct pushdown, see #16552 +-- TODO(tanuj): add back ANALYZE when #16633 is fixed. +EXPLAIN (SUMMARY OFF, TIMING OFF, COSTS OFF) SELECT DISTINCT att.attname as name, att.attnum as OID, pg_catalog.format_type(ty.oid,NULL) AS datatype, + att.attnotnull as not_null, att.atthasdef as has_default_val + FROM pg_catalog.pg_attribute att + JOIN pg_catalog.pg_type ty ON ty.oid=atttypid + WHERE + att.attnum > 0 + AND att.attisdropped IS FALSE + ORDER BY att.attnum LIMIT 1; + +-- check system columns in YbSeqScan(#18485) +explain (costs off) +/*+ SeqScan(nr2) */ SELECT tableoid::regclass, * from nr2 where i = 1; +/*+ SeqScan(nr2) */ SELECT tableoid::regclass, * from nr2 where i = 1; + +-- +-- For https://github.com/YugaByte/yugabyte-db/issues/20316 +-- +create table a (a int); +insert into a values (1),(2),(3),(4); + +create table b (b int, a int); +insert into b values (1, 1),(2, 1),(3, 2),(4, 4); + +create table c (c int); +insert into c values (1),(2),(3); + +create table d (b int, c int); +insert into d values (1, 1),(1, 2),(1, 3),(2, 1),(3, 2),(3, 3); + +explain (verbose, costs off) +select + a.a, + ( + select sum(t.v0) + from ( + select c.c as v0 + from c + where c.c in ( + select d.c + from d + join b + on b.b = d.b + where b.a = a.a + ) + ) as t + ) +from a +order by a.a; +select + a.a, + ( + select sum(t.v0) + from ( + select c.c as v0 + from c + where c.c in ( + select d.c + from d + join b + on b.b = d.b + where b.a = a.a + ) + ) as t + ) +from a +order by a.a; + +explain (verbose, costs off) +select + a.a, + ( + select sum(t.v0) + from ( + select c.c as v0 + from c + where c.c in ( + select d.c + from d + join b + on b.b = d.b + where b.a = a.a + ) + ) as t + ) +from a +where a in (1, 2) +order by a.a; +select + a.a, + ( + select sum(t.v0) + from ( + select c.c as v0 + from c + where c.c in ( + select d.c + from d + join b + on b.b = d.b + where b.a = a.a + ) + ) as t + ) +from a +where a in (1, 2) +order by a.a; + +-- #23287 string_to_text pushdown +create table string_to_text_test(data text); +insert into string_to_text_test values ('foo bar'); +explain (costs off) +select * from string_to_text_test where 'foo' = ANY(string_to_array(data, ' ')); +select * from string_to_text_test where 'foo' = ANY(string_to_array(data, ' ')); +drop table string_to_text_test; + +-- #22533 storage index filter in non-trivial subquery +CREATE TABLE site (id int PRIMARY KEY); +CREATE TABLE address (id int PRIMARY KEY, site_id int, recorded_at timestamp); +CREATE INDEX idx_address_problem ON address (site_id HASH) INCLUDE (recorded_at); +explain (costs off) +SELECT s.id, aa.addresses +FROM site s +JOIN ( + SELECT + array_agg(json_build_object( + 'id', a.id, + 'site_id', a.site_id)) AS addresses + FROM address a + WHERE a.site_id = 1 + AND a.recorded_at IS NULL + ) aa ON true; + +-- try with bitmap scans +/*+ Set(yb_enable_bitmapscan true) Set(enable_indexscan false) Set(enable_seqscan false) */ +explain (costs off) +SELECT s.id, aa.addresses +FROM site s +JOIN ( + SELECT + array_agg(json_build_object( + 'id', a.id, + 'site_id', a.site_id)) AS addresses + FROM address a + WHERE a.site_id = 1 + AND a.recorded_at IS NULL + ) aa ON true; +DROP TABLE site; +DROP TABLE address; diff --git a/src/test/regress/sql/yb.orig.select_large.sql b/src/test/regress/sql/yb.orig.select_large.sql new file mode 100644 index 0000000000000..dad60bc05a569 --- /dev/null +++ b/src/test/regress/sql/yb.orig.select_large.sql @@ -0,0 +1,13 @@ +-- +-- SELECT +-- +-- SELECT with LIMIT to check if prefetching hurts performance for a small set of data. +SELECT 1 FROM airports LIMIT 1; +-- Check aggregate function performance for a large set or rows. +SELECT COUNT(*) FROM airports; +-- SELECT ALL to check if prefetching is working fine for a large set of data. +SELECT * FROM airports ORDER BY ident; +-- SELECT ALL with WHERE condition on PRIMARY KEY for a large set of data. +SELECT * FROM airports WHERE ident < '04' AND ident > '01' ORDER BY ident; +-- SELECT ALL with WHERE condition on non PRIMARY KEY for a large set of data. +SELECT * FROM airports WHERE iso_region = 'US-CA' ORDER BY ident; diff --git a/src/test/regress/sql/yb.orig.select_large_abort.sql b/src/test/regress/sql/yb.orig.select_large_abort.sql new file mode 100644 index 0000000000000..9b2ba81818b8c --- /dev/null +++ b/src/test/regress/sql/yb.orig.select_large_abort.sql @@ -0,0 +1,5 @@ +-- +-- SELECT +-- +-- should be empty since commit failed +SELECT COUNT(*) FROM airports; diff --git a/src/test/regress/sql/yb.orig.select_large_ddl.sql b/src/test/regress/sql/yb.orig.select_large_ddl.sql new file mode 100644 index 0000000000000..bf5e6475a104d --- /dev/null +++ b/src/test/regress/sql/yb.orig.select_large_ddl.sql @@ -0,0 +1,6 @@ +-- +-- SELECT +-- +-- table should be full and have indexes created +SELECT COUNT(*) FROM airports; +SELECT * FROM pg_indexes WHERE tablename='airports'; diff --git a/src/test/regress/sql/yb.orig.select_large_index_backfill.sql b/src/test/regress/sql/yb.orig.select_large_index_backfill.sql new file mode 100644 index 0000000000000..3670804b4641c --- /dev/null +++ b/src/test/regress/sql/yb.orig.select_large_index_backfill.sql @@ -0,0 +1,26 @@ +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE type = 'closed' AND iso_region = 'US-CA'; +SELECT * FROM airports WHERE type = 'closed' AND iso_region = 'US-CA'; +-- +-- This queries the first 10 rows. +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE type = 'small_airport' AND iso_region = 'US-CA' + ORDER BY ident LIMIT 10; +SELECT * FROM airports WHERE type = 'small_airport' AND iso_region = 'US-CA' + ORDER BY ident LIMIT 10; +-- +-- This queries the last 10 rows. +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE type = 'small_airport' AND iso_region = 'US-CA' + ORDER BY ident DESC LIMIT 10; +SELECT * FROM airports WHERE type = 'small_airport' AND iso_region = 'US-CA' + ORDER BY ident DESC LIMIT 10; +-- +-- This queries the first 10 rows with names in a specified range. +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE name > 'H' AND name < 'R' AND iso_region = 'US-CA' + ORDER BY ident LIMIT 10; +SELECT * FROM airports WHERE name > 'H' AND name < 'R' AND iso_region = 'US-CA' + ORDER BY ident LIMIT 10; +-- +-- This queries the last 10 rows with names in a specified range. +EXPLAIN (COSTS OFF) SELECT * FROM airports WHERE name > 'H' AND name < 'R' AND iso_region = 'US-CA' + ORDER BY ident DESC LIMIT 10; +SELECT * FROM airports WHERE name > 'H' AND name < 'R' AND iso_region = 'US-CA' + ORDER BY ident DESC LIMIT 10; diff --git a/src/test/regress/sql/yb.orig.select_no_pushdown.sql b/src/test/regress/sql/yb.orig.select_no_pushdown.sql new file mode 100644 index 0000000000000..9f3a925bba7ae --- /dev/null +++ b/src/test/regress/sql/yb.orig.select_no_pushdown.sql @@ -0,0 +1,233 @@ +-- Test disabled expression pushdown in scans +SET yb_enable_expression_pushdown to off; +-- For environment independent output of timestamps +SET timezone to 'UTC'; + +CREATE TABLE pushdown_test(k int primary key, i1 int, t1 text, ts1 timestamp, ts2 timestamp with time zone, r1 numrange, a1 int[]); +INSERT INTO pushdown_test VALUES (1, 1, 'value1', '2021-11-11 11:11:11', '2021-11-11 11:11:11+1', '[0, 0)', '{1, NULL, 42}'); +INSERT INTO pushdown_test VALUES (2, 202, 'value2', NULL, '2222-02-22 22:22:22-7', numrange(-1, NULL), '{-2, 2, 122, -122}'); + +-- Simple expression (column = constant) +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE i1 = 1; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE t1 LIKE 'val%'; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE ts1 < '2021-11-11 11:11:12'; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE ts2 < '2021-11-11 11:11:10Z'; + +SELECT * FROM pushdown_test WHERE i1 = 1; +SELECT * FROM pushdown_test WHERE t1 LIKE 'val%'; +SELECT * FROM pushdown_test WHERE ts1 < '2021-11-11 11:11:12'; +SELECT * FROM pushdown_test WHERE ts2 < '2021-11-11 11:11:10Z'; + +-- Simple function on one column +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE isfinite(ts1); + +SELECT * FROM pushdown_test WHERE isfinite(ts1); + +-- Simple function on multiple columns +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE left(t1, i1) = 'v'; + +SELECT * FROM pushdown_test WHERE left(t1, i1) = 'v'; + +-- Functions safe for pushdown (yb_safe_funcs_for_pushdown.c) +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE i1 < 10 + random() * 90; + +SELECT * FROM pushdown_test WHERE i1 < 10 + random() * 90; + +-- Null test +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE ts1 IS NULL; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE ts1 IS NOT NULL; + +SELECT * FROM pushdown_test WHERE ts1 IS NULL; +SELECT * FROM pushdown_test WHERE ts1 IS NOT NULL; + +-- Boolean expression +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE i1 = 1 OR NOT isfinite(ts1) AND ts2 > '2001-01-01 01:01:01-7'::timestamptz; + +SELECT * FROM pushdown_test WHERE i1 = 1 OR NOT isfinite(ts1) AND ts2 > '2001-01-01 01:01:01-7'::timestamptz; + +-- Case expression +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE CASE WHEN i1 % 2 = 0 THEN ts1 < '2021-11-12' WHEN i1 % 2 = 1 THEN ts2 > '2022-01-01 00:00:00-7' END; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE CASE i1 % 2 WHEN 0 THEN ts1 < '2021-11-12' WHEN 1 THEN ts2 > '2022-01-01 00:00:00-7' END; + +SELECT * FROM pushdown_test WHERE CASE WHEN i1 % 2 = 0 THEN ts1 < '2021-11-12' WHEN i1 % 2 = 1 THEN ts2 > '2022-01-01 00:00:00-7' END; + +-- Aggregates +EXPLAIN (COSTS FALSE) SELECT count(*) FROM pushdown_test; +EXPLAIN (COSTS FALSE) SELECT count(*) FROM pushdown_test WHERE i1 = 1; + +SELECT count(*) FROM pushdown_test; +SELECT count(*) FROM pushdown_test WHERE i1 = 1; + +-- Parameter +PREPARE s AS SELECT * FROM pushdown_test WHERE i1 = $1; + +EXPLAIN (COSTS FALSE) EXECUTE s(1); + +EXECUTE s(1); + +DEALLOCATE s; + +-- Join +CREATE TABLE pushdown_lookup(k int primary key, tag text); +INSERT INTO pushdown_lookup VALUES (1, 'foo'), (2, 'bar'), (3, 'baz'); + +EXPLAIN (COSTS FALSE) SELECT t.t1 FROM pushdown_test t, pushdown_lookup l WHERE t.k = l.k AND l.tag = 'foo'; + +SELECT t.t1 FROM pushdown_test t, pushdown_lookup l WHERE t.k = l.k AND l.tag = 'foo'; + +-- Negative test cases (expressions should not be pushed down) +-- Not immutable functions +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE t1 = concat('value', i1::text); + +SELECT * FROM pushdown_test WHERE t1 = concat('value', i1::text); + +-- Index scan +CREATE INDEX pushdown_test_i1 ON pushdown_test(i1); +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE i1 = 1; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE i1 = 1 AND t1 = 'value1'; + +SELECT * FROM pushdown_test WHERE i1 = 1; +SELECT * FROM pushdown_test WHERE i1 = 1 AND t1 = 'value1'; + +-- Records, ranges, arrays +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE isempty(r1); +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE a1[2] = 2; + +SELECT * FROM pushdown_test WHERE isempty(r1); +SELECT * FROM pushdown_test WHERE a1[2] = 2; + +-- Pseudo types +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_test WHERE num_nulls(variadic a1) > 0; + +SELECT * FROM pushdown_test WHERE num_nulls(variadic a1) > 0; + +-- Composite datatype +CREATE TYPE pair AS (first int, second int); +CREATE TABLE pushdown_composite(k int primary key, v pair); +INSERT INTO pushdown_composite VALUES (1, (2, 3)); + +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_composite WHERE (v).first = 2; + +SELECT * FROM pushdown_composite WHERE (v).first = 2; + +-- Enum datatype +CREATE TYPE color AS ENUM('red', 'green', 'blue'); +CREATE TABLE pushdown_enum(k int, c color, x int); +INSERT INTO pushdown_enum VALUES (1, 'red', 255); + +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_enum WHERE c = 'red'; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_enum WHERE c::text = 'red'; + +SELECT * FROM pushdown_enum WHERE c = 'red'; +SELECT * FROM pushdown_enum WHERE c::text = 'red'; + +-- Collation +CREATE TABLE pushdown_collation(k int primary key, v text COLLATE "ucs_basic"); +INSERT INTO pushdown_collation VALUES (1, 'foo'); + +-- Do not pushdown operation on column with collation other than C +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_collation WHERE v = 'foo'; +SELECT * FROM pushdown_collation WHERE v = 'foo'; + +CREATE TABLE pushdown_index(k1 int, k2 int, v1 int, v2 int, v3 int, v4 int, v5 text, primary key (k1, k2)); +CREATE INDEX pushdown_index_v1_v2_v3v4_idx ON pushdown_index(v1, v2, (v3 + v4) ASC); +INSERT INTO pushdown_index VALUES (1, 1, 1, 1, 1, 0, 'row 1'); +INSERT INTO pushdown_index VALUES (1, 2, 3, 4, 5, 6, 'row 2'); +INSERT INTO pushdown_index VALUES (1, 20, 20, 20, 20, 20, 'row 3'); +INSERT INTO pushdown_index VALUES (2, 1, 1, 2, 3, 4, 'row 4'); +INSERT INTO pushdown_index VALUES (2, 2, 1, 3, 2, 1, 'row 5'); +INSERT INTO pushdown_index VALUES (2, 20, 1, 20, 40, 50, 'row 6'); +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_index WHERE k1 = 1 AND k2 = 2; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_index WHERE k1 = 1 AND k2/10 = 0; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_index WHERE k1 = 1 AND k2 = v1; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_index WHERE k1 = 1 AND k2 = v1 AND CASE v2 % 2 WHEN 0 THEN v3 < 0 WHEN 1 THEN v3 > 0 END; +EXPLAIN (COSTS FALSE) SELECT v2 FROM pushdown_index WHERE v1 = 1; +EXPLAIN (COSTS FALSE) SELECT v2 FROM pushdown_index WHERE v1 = 1 AND v2 > v1; +EXPLAIN (COSTS FALSE) SELECT v2, v3, v4 FROM pushdown_index WHERE v1 = 1 AND v2 > v1; +EXPLAIN (COSTS FALSE) SELECT v2 FROM pushdown_index WHERE v1 = 1 AND v2 = v3 + v4; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_index WHERE v1 = 1 AND v2 > v1 AND v3 > v2; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_index WHERE v1 = 1 AND v2 > v1 AND v3 > v2 AND CASE v3 % 2 WHEN 0 THEN v4 < 0 WHEN 1 THEN v4 > 0 END; +SELECT * FROM pushdown_index WHERE k1 = 1 AND k2 = 2; +SELECT * FROM pushdown_index WHERE k1 = 1 AND k2/10 = 0; +SELECT * FROM pushdown_index WHERE k1 = 1 AND k2 = v1; +SELECT * FROM pushdown_index WHERE k1 = 1 AND k2 = v1 AND CASE v2 % 2 WHEN 0 THEN v3 < 0 WHEN 1 THEN v3 > 0 END; +SELECT v2 FROM pushdown_index WHERE v1 = 1; +SELECT v2 FROM pushdown_index WHERE v1 = 1 AND v2 > v1; +SELECT v2, v3, v4 FROM pushdown_index WHERE v1 = 1 AND v2 > v1; +SELECT v2 FROM pushdown_index WHERE v1 = 1 AND v2 = v3 + v4; +SELECT * FROM pushdown_index WHERE v1 = 1 AND v2 > v1 AND v3 > v2; +SELECT * FROM pushdown_index WHERE v1 = 1 AND v2 > v1 AND v3 > v2 AND CASE v3 % 2 WHEN 0 THEN v4 < 0 WHEN 1 THEN v4 > 0 END; + +PREPARE pk_param AS SELECT * FROM pushdown_index WHERE k1 = 1 AND k2 = v1 + $1; +EXPLAIN (COSTS FALSE) EXECUTE pk_param(0); +EXECUTE pk_param(0); +DEALLOCATE pk_param; + +PREPARE si_param AS SELECT * FROM pushdown_index WHERE v1 = 1 AND v2 > v1 + $1 AND v3 > v2 + $2; +EXPLAIN (COSTS FALSE) EXECUTE si_param(0, 0); +EXECUTE si_param(0, 0); +DEALLOCATE si_param; + +-- Index scan with storage filter on a system table +EXPLAIN (COSTS FALSE) SELECT relname, relkind FROM pg_class WHERE relname LIKE 'pushdown_c%'; +SELECT relname, relkind FROM pg_class WHERE relname LIKE 'pushdown_c%'; + +-- Index scan with storage filter on a range table +CREATE TABLE pushdown_range(k1 text, v1 int, v2 int, primary key(k1 asc)) SPLIT AT VALUES (('2 '), ('3 '), ('4 '), ('5 '), ('6 '), ('7 '), ('8 '), ('9 ')); +INSERT INTO pushdown_range SELECT i::text, i, i FROM generate_series(1,100) AS i; +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_range WHERE k1 IN ('11', '17', '33', '42', '87') AND (v1 = 17 OR v2 = 87); +SELECT * FROM pushdown_range WHERE k1 IN ('11', '17', '33', '42', '87') AND (v1 = 17 OR v2 = 87); +EXPLAIN (COSTS FALSE) SELECT * FROM pushdown_range WHERE v1 > 5 AND v2 < 33 ORDER BY k1; +SELECT * FROM pushdown_range WHERE v1 > 5 AND v2 < 33 ORDER BY k1; + +-- Plan for the lateral join leverages PARAM_EXEC params +CREATE TABLE tlateral1 (a int, b int, c varchar); +INSERT INTO tlateral1 SELECT i, i % 25, to_char(i % 4, 'FM0000') FROM generate_series(0, 599, 2) i; +CREATE TABLE tlateral2 (a int, b int, c varchar); +INSERT INTO tlateral2 SELECT i % 25, i, to_char(i % 4, 'FM0000') FROM generate_series(0, 599, 3) i; +ANALYZE tlateral1, tlateral2; +EXPLAIN (COSTS FALSE) SELECT * FROM tlateral1 t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM tlateral1 t2 JOIN tlateral2 t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss ON t1.a = ss.t2a WHERE t1.b = 0 ORDER BY t1.a; +SELECT * FROM tlateral1 t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM tlateral1 t2 JOIN tlateral2 t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss ON t1.a = ss.t2a WHERE t1.b = 0 ORDER BY t1.a; + +-- Test PARAM_EXEC pushdown when parameters are coming from a subplan +CREATE TABLE tmaster(k int primary key, v1 int, v2 int); +INSERT INTO tmaster VALUES (1, 1, 1), (2, 2, 2), (3, 3, 3); +CREATE TABLE tsub(k int primary key, v1 int, v2 int); +INSERT INTO tsub VALUES (1, 2, 3), (4, 5, 6); +EXPLAIN (COSTS FALSE) SELECT * FROM tmaster WHERE v1 = (SELECT v1 FROM tsub WHERE v2 = 3); +SELECT * FROM tmaster WHERE v1 = (SELECT v1 FROM tsub WHERE v2 = 3); + +-- Test rescan of a subquery with pushdown of PARAM_EXEC parameters +CREATE TABLE tidxrescan1(k1 int, k2 int, v1 int, v2 int, primary key (k1 hash, k2 asc)); +CREATE TABLE tidxrescan2(k1 int primary key, v1 int); +CREATE TABLE tidxrescan3(k1 int, v1 int); +CREATE INDEX ON tidxrescan3(k1) INCLUDE (v1); + +INSERT INTO tidxrescan1 VALUES (1,1,2,3), (1,2,4,5); +INSERT INTO tidxrescan2 VALUES (1,2), (2,2); +INSERT INTO tidxrescan3 VALUES (1,2), (2,2); + +EXPLAIN (COSTS FALSE) SELECT t1.k2, t1.v1, (SELECT t2.v1 FROM tidxrescan2 t2 WHERE t2.k1 = t1.k2 AND t2.v1 = t1.v1) FROM tidxrescan1 t1 WHERE t1.k1 = 1; +SELECT t1.k2, t1.v1, (SELECT t2.v1 FROM tidxrescan2 t2 WHERE t2.k1 = t1.k2 AND t2.v1 = t1.v1) FROM tidxrescan1 t1 WHERE t1.k1 = 1; +SELECT t1.k2, t1.v1, (SELECT t2.v1 FROM tidxrescan2 t2 WHERE t2.k1 = t1.k2 AND t2.v1 = t1.v1) FROM tidxrescan1 t1 WHERE t1.k1 = 1 ORDER BY t1.k2 DESC; + +EXPLAIN (COSTS FALSE) SELECT t1.k2, t1.v1, (SELECT t2.v1 FROM tidxrescan3 t2 WHERE t2.k1 = t1.k2 AND t2.v1 = t1.v1) FROM tidxrescan1 t1 WHERE t1.k1 = 1; +SELECT t1.k2, t1.v1, (SELECT t2.v1 FROM tidxrescan3 t2 WHERE t2.k1 = t1.k2 AND t2.v1 = t1.v1) FROM tidxrescan1 t1 WHERE t1.k1 = 1; +SELECT t1.k2, t1.v1, (SELECT t2.v1 FROM tidxrescan3 t2 WHERE t2.k1 = t1.k2 AND t2.v1 = t1.v1) FROM tidxrescan1 t1 WHERE t1.k1 = 1 ORDER BY t1.k2 DESC; + +DROP TABLE tidxrescan1; +DROP TABLE tidxrescan2; +DROP TABLE tidxrescan3; +DROP TABLE tmaster; +DROP TABLE tsub; +DROP TABLE tlateral1; +DROP TABLE tlateral2; +DROP TABLE pushdown_range; +DROP TABLE pushdown_index; +DROP TABLE pushdown_test; +DROP TABLE pushdown_lookup; +DROP TABLE pushdown_composite; +DROP TABLE pushdown_enum; +DROP TABLE pushdown_collation; +DROP TYPE pair; +DROP TYPE color; diff --git a/src/test/regress/sql/yb.orig.select_parallel.sql b/src/test/regress/sql/yb.orig.select_parallel.sql new file mode 100644 index 0000000000000..ca1ea3fd95554 --- /dev/null +++ b/src/test/regress/sql/yb.orig.select_parallel.sql @@ -0,0 +1,489 @@ +-- +-- PARALLEL +-- + +-- TODO as of GHI 20637 some of tests below do not use parallel query, since +-- it is currently restricted to colocated tables. Eventually we will enable +-- it for distributed tables and update expected results. + +create function sp_parallel_restricted(int) returns int as + $$begin return $1; end$$ language plpgsql parallel restricted; + +-- enable parallel query for YB tables +set yb_parallel_range_rows to 1; +set yb_enable_base_scans_cost_model to true; + +-- 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; + +-- Note: The test below was copied from upstream `select_parallel.sql`. +-- Contrary to the original comment, it *does* show a Parallel Append. +-- See: https://github.com/postgres/postgres/blob/e6dfd068ed453b8690551dac700d57fbf32ba187/src/test/regress/expected/select_parallel.out#L156 + +-- Parallel Append is not to be used when the subpath depends on the outer param +create table part_pa_test(a int, b int) partition by range(a); +create table part_pa_test_p1 partition of part_pa_test for values from (minvalue) to (0); +create table part_pa_test_p2 partition of part_pa_test for values from (0) to (maxvalue); +explain (costs off) + select (select max((select pa1.b from part_pa_test pa1 where pa1.a = pa2.a))) + from part_pa_test pa2; +drop table part_pa_test; + +-- test with leader participation disabled +set parallel_leader_participation = off; +explain (costs off) + select count(*) from tenk1 where stringu1 = 'GRAAAA'; +select count(*) from tenk1 where stringu1 = 'GRAAAA'; + +-- test with leader participation disabled, but no workers available (so +-- the leader will have to run the plan despite the setting) +set max_parallel_workers = 0; +explain (costs off) + select count(*) from tenk1 where stringu1 = 'GRAAAA'; +select count(*) from tenk1 where stringu1 = 'GRAAAA'; + +reset max_parallel_workers; +reset parallel_leader_participation; + +-- test that parallel_restricted function doesn't run in worker +explain (verbose, costs off) +select sp_parallel_restricted(unique1) from tenk1 + where stringu1 = 'GRAAAA' order by 1; + +-- test parallel plan when group by expression is in target list. +explain (costs off) + select length(stringu1) from tenk1 group by length(stringu1); +select length(stringu1) from tenk1 group by length(stringu1); + +explain (costs off) + select stringu1, count(*) from tenk1 group by stringu1 order by stringu1; + +-- test that parallel plan for aggregates is not selected when +-- target list contains parallel restricted clause. +explain (costs off) + select sum(sp_parallel_restricted(unique1)) from tenk1 + group by(sp_parallel_restricted(unique1)); + +-- test prepared statement +prepare tenk1_count(integer) As select count((unique1)) from tenk1 where hundred > $1; +explain (costs off) execute tenk1_count(1); +execute tenk1_count(1); +deallocate tenk1_count; + +-- test parallel plans for queries containing un-correlated subplans. +explain (costs off) + select count(*) from tenk1 where (two, four) not in + (select hundred, thousand from tenk2 where thousand > 100); +select count(*) from tenk1 where (two, four) not in + (select hundred, thousand from tenk2 where thousand > 100); +-- this is not parallel-safe due to use of random() within SubLink's testexpr: +explain (costs off) + select * from tenk1 where (unique1 + random())::integer not in + (select ten from tenk2); + +-- test parallel plan for a query containing initplan. +set enable_indexscan = off; +set enable_indexonlyscan = off; +set enable_bitmapscan = off; + +explain (costs off) + select count(*) from tenk1 + where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2); +select count(*) from tenk1 + where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2); + +reset enable_indexscan; +reset enable_indexonlyscan; +reset enable_bitmapscan; + +-- test parallel index scans. +set enable_seqscan to off; +set enable_bitmapscan to off; + +explain (costs off) + select count((unique1)) from tenk1 where hundred > 1; +select count((unique1)) from tenk1 where hundred > 1; + +-- test parallel index-only scans. +explain (costs off) + select count(*) from tenk1 where thousand > 95; +select count(*) from tenk1 where thousand > 95; + +-- test rescan cases too +set enable_material = false; + +explain (costs off) +select * from + (select count(unique1) from tenk1 where hundred > 10) ss + right join (values (1),(2),(3)) v(x) on true; +select * from + (select count(unique1) from tenk1 where hundred > 10) ss + right join (values (1),(2),(3)) v(x) on true; + +explain (costs off) +select * from + (select count(*) from tenk1 where thousand > 99) ss + right join (values (1),(2),(3)) v(x) on true; +select * from + (select count(*) from tenk1 where thousand > 99) ss + right join (values (1),(2),(3)) v(x) on true; + +reset enable_material; +reset enable_seqscan; +reset enable_bitmapscan; + +-- test parallel merge join path. +set enable_hashjoin to off; +set enable_nestloop to off; + +explain (costs off) + select count(*) from tenk1, tenk2 where tenk1.unique1 = tenk2.unique1; +select count(*) from tenk1, tenk2 where tenk1.unique1 = tenk2.unique1; + +reset enable_hashjoin; +reset enable_nestloop; + +-- test gather merge +set enable_hashagg = false; + +explain (costs off) + select count(*) from tenk1 group by twenty; + +select count(*) from tenk1 group by twenty; + +--test expressions in targetlist are pushed down for gather merge +create function sp_simple_func(var1 integer) returns integer +as $$ +begin + return var1 + 10; +end; +$$ language plpgsql PARALLEL SAFE; + +explain (costs off, verbose) + select ten, sp_simple_func(ten) from tenk1 where ten < 100 order by ten; + +drop function sp_simple_func(integer); + +-- test handling of SRFs in targetlist (bug in 10.0) + +explain (costs off) + select count(*), generate_series(1,2) from tenk1 group by twenty; + +select count(*), generate_series(1,2) from tenk1 group by twenty; + +-- test gather merge with parallel leader participation disabled +set parallel_leader_participation = off; + +explain (costs off) + select count(*) from tenk1 group by twenty; + +select count(*) from tenk1 group by twenty; + +reset parallel_leader_participation; + +--test rescan behavior of gather merge +set enable_material = false; + +explain (costs off) +select * from + (select string4, count(unique2) + from tenk1 group by string4 order by string4) ss + right join (values (1),(2),(3)) v(x) on true; + +select * from + (select string4, count(unique2) + from tenk1 group by string4 order by string4) ss + right join (values (1),(2),(3)) v(x) on true; + +reset enable_material; + +reset enable_hashagg; + +-- check parallelized int8 aggregate (bug #14897) +explain (costs off) +select avg(unique1::int8) from tenk1; + +select avg(unique1::int8) from tenk1; + +-- gather merge test with a LIMIT +explain (costs off) + select fivethous from tenk1 order by fivethous limit 4; + +select fivethous from tenk1 order by fivethous limit 4; + +-- gather merge test with 0 worker +set max_parallel_workers = 0; +explain (costs off) + select string4 from tenk1 order by string4 limit 5; +select string4 from tenk1 order by string4 limit 5; + +-- gather merge test with 0 workers, with parallel leader +-- participation disabled (the leader will have to run the plan +-- despite the setting) +set parallel_leader_participation = off; +explain (costs off) + select string4 from tenk1 order by string4 limit 5; +select string4 from tenk1 order by string4 limit 5; + +reset parallel_leader_participation; +reset max_parallel_workers; + +BEGIN; + +-- exercise record typmod remapping between backends +CREATE FUNCTION make_record(n int) + RETURNS RECORD LANGUAGE plpgsql PARALLEL SAFE AS +$$ +BEGIN + RETURN CASE n + WHEN 1 THEN ROW(1) + WHEN 2 THEN ROW(1, 2) + WHEN 3 THEN ROW(1, 2, 3) + WHEN 4 THEN ROW(1, 2, 3, 4) + ELSE ROW(1, 2, 3, 4, 5) + END; +END; +$$; +SAVEPOINT settings; +SET LOCAL force_parallel_mode = 1; +SELECT make_record(x) FROM (SELECT generate_series(1, 5) x) ss ORDER BY x; +ROLLBACK TO SAVEPOINT settings; +DROP function make_record(n int); + +-- test the sanity of parallel query after the active role is dropped. +drop role if exists regress_parallel_worker; +create role regress_parallel_worker; +set role regress_parallel_worker; +reset session authorization; +drop role regress_parallel_worker; +set force_parallel_mode = 1; +select count(*) from tenk1; +reset force_parallel_mode; +reset role; + +-- Window function calculation can't be pushed to workers. +explain (costs off, verbose) + select count(*) from tenk1 a where (unique1, two) in + (select unique1, row_number() over() from tenk1 b); + + +-- LIMIT/OFFSET within sub-selects can't be pushed to workers. +explain (costs off) + select * from tenk1 a where two in + (select two from tenk1 b where stringu1 like '%AAAA' limit 3); + +-- to increase the parallel query test coverage +SAVEPOINT settings; +SET LOCAL force_parallel_mode = 1; +EXPLAIN (analyze, timing off, summary off, costs off) SELECT * FROM tenk1; +ROLLBACK TO SAVEPOINT settings; + +-- provoke error in worker +-- (make the error message long enough to require multiple bufferloads) +SAVEPOINT settings; +SET LOCAL force_parallel_mode = 1; +select (stringu1 || repeat('abcd', 5000))::int2 from tenk1 where unique1 = 1; +ROLLBACK TO SAVEPOINT settings; + +-- test interaction with set-returning functions +SAVEPOINT settings; + +-- multiple subqueries under a single Gather node +-- must set parallel_setup_cost > 0 to discourage multiple Gather nodes +SET LOCAL parallel_setup_cost = 10; +EXPLAIN (COSTS OFF) +SELECT unique1 FROM tenk1 WHERE fivethous = tenthous + 1 +UNION ALL +SELECT unique1 FROM tenk1 WHERE fivethous = tenthous + 1; +ROLLBACK TO SAVEPOINT settings; + +-- can't use multiple subqueries under a single Gather node due to initPlans +EXPLAIN (COSTS OFF) +SELECT unique1 FROM tenk1 WHERE fivethous = + (SELECT unique1 FROM tenk1 WHERE fivethous = 1 LIMIT 1) +UNION ALL +SELECT unique1 FROM tenk1 WHERE fivethous = + (SELECT unique2 FROM tenk1 WHERE fivethous = 1 LIMIT 1) +ORDER BY 1; + +-- test passing expanded-value representations to workers +CREATE FUNCTION make_some_array(int,int) returns int[] as +$$declare x int[]; + begin + x[1] := $1; + x[2] := $2; + return x; + end$$ language plpgsql parallel safe; +CREATE TABLE fooarr(f1 text, f2 int[], f3 text); +INSERT INTO fooarr VALUES('1', ARRAY[1,2], 'one'); + +PREPARE pstmt(text, int[]) AS SELECT * FROM fooarr WHERE f1 = $1 AND f2 = $2; +EXPLAIN (COSTS OFF) EXECUTE pstmt('1', make_some_array(1,2)); +EXECUTE pstmt('1', make_some_array(1,2)); +DEALLOCATE pstmt; + +-- test interaction between subquery and partial_paths +SET LOCAL min_parallel_table_scan_size TO 0; +CREATE VIEW tenk1_vw_sec WITH (security_barrier) AS SELECT * FROM tenk1; +EXPLAIN (COSTS OFF) +SELECT 1 FROM tenk1_vw_sec + WHERE (SELECT sum(f1) FROM int4_tbl WHERE f1 < unique1) < 100; +rollback; + +-- GHI 21320 +CREATE TABLE c (pk integer NOT NULL, col_varchar_key character varying(1), col_varchar_nokey character varying(1), CONSTRAINT c_pkey PRIMARY KEY(pk ASC)); +CREATE TABLE d (pk integer NOT NULL, col_varchar_key character varying(1), col_varchar_nokey character varying(1), CONSTRAINT d_pkey PRIMARY KEY(pk ASC)); +CREATE TABLE dummy (i integer); +COPY c (pk, col_varchar_key, col_varchar_nokey) FROM stdin; +1 v v +2 v v +3 c c +4 \N \N +5 x x +6 i i +7 e e +8 p p +9 s s +10 j j +11 z z +12 c c +13 a a +14 q q +15 y y +16 \N \N +17 r r +18 v v +19 \N \N +20 r r +\. + +COPY d (pk, col_varchar_key, col_varchar_nokey) FROM stdin; +1 c c +2 c c +3 q q +4 g g +5 e e +6 l l +7 \N \N +8 v v +9 c c +10 u u +11 x x +12 x x +13 x x +14 l l +15 e e +16 s s +17 k k +18 m m +19 x x +20 s s +21 h h +22 u u +23 x x +24 l l +25 p p +26 i i +27 u u +28 i i +29 i i +30 e e +31 h h +32 f f +33 \N \N +34 p p +35 n n +36 h h +37 m m +38 x x +39 d d +40 d d +41 t t +42 \N \N +43 \N \N +44 v v +45 u u +46 p p +47 o o +48 v v +49 m m +50 x x +51 n n +52 b b +53 \N \N +54 r r +55 v v +56 a a +57 u u +58 \N \N +59 b b +60 s s +61 t t +62 b b +63 m m +64 v v +65 n n +66 j j +67 \N \N +68 \N \N +69 h h +70 k k +71 k k +72 \N \N +73 n n +74 e e +75 s s +76 w w +77 y y +78 z z +79 b b +80 f f +81 s s +82 d d +83 \N \N +84 d d +85 n n +86 i i +87 \N \N +88 h h +89 d d +90 c c +91 i i +92 t t +93 g g +94 q q +95 l l +96 n n +97 z z +98 n n +99 r r +100 p p +\. + +COPY dummy (i) FROM stdin; +0 +\. + +/*+ Set(enable_hashjoin off) Set(enable_mergejoin off) Set(enable_material off) */ +EXPLAIN (analyze, timing off, summary off, costs off) SELECT +FROM + D AS table2 + JOIN C AS table3 ON ( + table3.col_varchar_nokey = table2.col_varchar_key + ) +WHERE + table2.col_varchar_nokey <> ANY ( + SELECT + 'j' + FROM + DUMMY + UNION + ALL + SELECT + 'r' + FROM + DUMMY + ); diff --git a/src/test/regress/sql/yb.orig.stat.sql b/src/test/regress/sql/yb.orig.stat.sql new file mode 100644 index 0000000000000..86fe7b6ebf8ac --- /dev/null +++ b/src/test/regress/sql/yb.orig.stat.sql @@ -0,0 +1,102 @@ +-- Test for YBSTAT +-- Verifies behavior for yb created system views and tables. + +-- Testing to see if yb_terminated_queries is populated correctly +-- For TEMP_FILE_LIMIT under the SET configurations +-- These tests are with superuser, so we can observe the terminated queries +-- across databases, not just the one we're connected to. +SET work_mem TO 64; +SET temp_file_limit TO 0; +SELECT * FROM generate_series(0, 1000000); +SELECT 'bob' FROM generate_series(0, 1000000); + +-- This sleep is necessary to allow PGSTAT_STAT_INTERVAL (500) milliseconds to pass. +-- In that time, the statistics in the backend would be updated and we will read updated +-- statistics. +SELECT pg_sleep(1); + +\d yb_terminated_queries +SELECT databasename, termination_reason, query_text FROM yb_terminated_queries; +SELECT databasename, termination_reason, query_text FROM yb_terminated_queries WHERE databasename = 'yugabyte'; + +CREATE DATABASE db2; +\c db2 + +SELECT databasename, termination_reason, query_text FROM yb_terminated_queries; + +SET work_mem TO 64; +SET temp_file_limit TO 0; +SELECT * FROM generate_series(0, 1000001); +SELECT pg_sleep(1); +SELECT databasename, termination_reason, query_text FROM yb_terminated_queries; + +SELECT 'We were taught in this modern age that science and mathematics is the pinnacle of human achievement.' +'Yet, in our complacency, we began to neglect the very thing which our ancestors had once done: to challenge the process.' +'We need to stand back and critically analyze what we do and doing so would allow us to become better and so much more.' +FROM generate_series(0, 1000000); + +SELECT pg_sleep(1); +SELECT databasename, termination_reason, query_text FROM yb_terminated_queries; +SELECT databasename, termination_reason, query_text FROM yb_terminated_queries WHERE databasename = 'yugabyte'; +SELECT databasename, termination_reason, query_text FROM yb_terminated_queries WHERE databasename = 'db2'; +SELECT query_text, length(query_text) AS query_length FROM yb_terminated_queries; + +-- Test permissions for different roles +\c yugabyte +SELECT databasename, termination_reason, query_text FROM yb_terminated_queries; + +CREATE ROLE test_user WITH login; +\c yugabyte test_user +SELECT * FROM generate_series(0, 100000002); +SELECT pg_sleep(1); +SELECT + D.datname AS databasename, + S.query_text AS query_text +FROM yb_pg_stat_get_queries(null) AS S +LEFT JOIN pg_database AS D ON (S.db_oid = D.oid) ORDER BY S.db_oid; + +\c yugabyte yugabyte +GRANT pg_read_all_stats TO test_user; +\c yugabyte test_user +SELECT + D.datname AS databasename, + S.query_text AS query_text +FROM yb_pg_stat_get_queries(null) AS S +LEFT JOIN pg_database AS D ON (S.db_oid = D.oid) ORDER BY S.db_oid; + +\c yugabyte yugabyte +REVOKE pg_read_all_stats FROM test_user; +GRANT yb_db_admin TO test_user; +\c yugabyte test_user +SELECT + D.datname AS databasename, + S.query_text AS query_text +FROM yb_pg_stat_get_queries(null) AS S +LEFT JOIN pg_database AS D ON (S.db_oid = D.oid) ORDER BY S.db_oid; + +\c yugabyte yugabyte +REVOKE yb_db_admin FROM test_user; + +ALTER ROLE test_user WITH superuser; +ALTER ROLE test_user WITH createdb; + +\c yugabyte test_user +CREATE DATABASE test_user_database; +\c test_user_database test_user + +SET work_mem TO 128; + +-- Some shenanigans with temp_file_limit means you need to be superuser +-- to change the value of this config. +SET temp_file_limit TO 0; +show temp_file_limit; + +SELECT * FROM generate_series(0, 1234567); +SELECT databasename, termination_reason, query_text FROM yb_terminated_queries ORDER BY databasename; + +-- Drop the superuser privilege as we want to see if we would only see the terminated query +-- of our created database only. +ALTER user test_user WITH nosuperuser; + +SELECT pg_sleep(1); +SELECT databasename, termination_reason, query_text FROM yb_terminated_queries; diff --git a/src/test/regress/sql/yb.orig.system_targets.sql b/src/test/regress/sql/yb.orig.system_targets.sql new file mode 100644 index 0000000000000..974e39b3b5719 --- /dev/null +++ b/src/test/regress/sql/yb.orig.system_targets.sql @@ -0,0 +1,18 @@ +CREATE TABLE t_normal (k INT PRIMARY KEY, v INT); +CREATE TEMP TABLE t_temp(k INT PRIMARY KEY, v INT); +INSERT INTO t_normal VALUES (1,1); +INSERT INTO t_temp VALUES (1,1); + +-- check system columns in YbSeqScan (#18485) +explain (costs off) +/*+ SeqScan(t_normal) */ SELECT tableoid::regclass, * FROM t_normal; +/*+ SeqScan(t_normal) */ SELECT tableoid::regclass, * FROM t_normal; +/*+ SeqScan(t_normal) */ SELECT xmin, * FROM t_normal; +/*+ SeqScan(t_normal) */ SELECT xmax, * FROM t_normal; +/*+ SeqScan(t_normal) */ SELECT ctid, * FROM t_normal; + +-- check system columns in YbSeqScan on temp tables +SELECT tableoid::regclass, * FROM t_temp; +SELECT xmin, * FROM t_temp; +SELECT xmax, * FROM t_temp; +SELECT ctid, * FROM t_temp; diff --git a/src/test/regress/sql/yb.orig.tablegroup.sql b/src/test/regress/sql/yb.orig.tablegroup.sql new file mode 100644 index 0000000000000..9ffab04bd157f --- /dev/null +++ b/src/test/regress/sql/yb.orig.tablegroup.sql @@ -0,0 +1,205 @@ +-- +-- YB_TABLEGROUP Testsuite: Testing Statments for TABLEGROUP. +-- +\h CREATE TABLEGROUP +\h ALTER TABLEGROUP +\h DROP TABLEGROUP + +-- +-- pg_catalog alterations. Validate columns of pg_yb_tablegroup and oids. +-- +\d pg_yb_tablegroup +SELECT oid, relname, reltype, relnatts FROM pg_class WHERE relname IN ('pg_yb_tablegroup', 'pg_yb_tablegroup_oid_index'); +SELECT oid, typname, typrelid FROM pg_type WHERE typname LIKE 'pg_yb_tablegroup'; +-- +-- CREATE TABLEGROUP +-- + +CREATE TABLEGROUP tgroup1; +CREATE TABLEGROUP tgroup2; +CREATE TABLEGROUP tgroup3; +CREATE TABLE tgroup_test1 (col1 int, col2 int) TABLEGROUP tgroup1; +CREATE INDEX ON tgroup_test1(col2); +CREATE TABLE tgroup_test2 (col1 int, col2 int) TABLEGROUP tgroup1; +CREATE TABLE nogroup (col1 int) NO TABLEGROUP; -- fail +CREATE TABLE nogroup (col1 int); +-- TODO(alex): Create a table with a primary key +SELECT grpname FROM pg_yb_tablegroup; + +SELECT cl.relname, tg.grpname + FROM pg_class cl, yb_table_properties(cl.oid) props + LEFT JOIN pg_yb_tablegroup tg ON tg.oid = props.tablegroup_oid + WHERE cl.oid >= 16384 + ORDER BY cl.relname; + +CREATE TABLE tgroup_test3 (col1 int, col2 int) TABLEGROUP tgroup2; +CREATE INDEX ON tgroup_test3(col1); + +SELECT cl.relname, tg.grpname + FROM pg_class cl, yb_table_properties(cl.oid) props + LEFT JOIN pg_yb_tablegroup tg ON tg.oid = props.tablegroup_oid + WHERE cl.oid >= 16384 + ORDER BY cl.relname; + +-- These should fail. +CREATE TABLEGROUP tgroup1; +CREATE TABLE tgroup_test (col1 int, col2 int) TABLEGROUP bad_tgroupname; +CREATE TABLE tgroup_optout (col1 int, col2 int) WITH (colocation=false) TABLEGROUP tgroup1; +CREATE TABLE tgroup_optout (col1 int, col2 int) WITH (colocation=true) TABLEGROUP tgroup1; +CREATE TABLE tgroup_optout (col1 int, col2 int) WITH (colocation=false) TABLEGROUP bad_tgroupname; +CREATE TEMP TABLE tgroup_temp (col1 int, col2 int) TABLEGROUP tgroup1; +CREATE INDEX ON tgroup_test3(col1) TABLEGROUP tgroup1; -- fail +CREATE INDEX ON tgroup_test3(col1) NO TABLEGROUP; -- fail + +-- +-- Cannot drop dependent objects +-- +CREATE USER alice; +CREATE USER bob; + +CREATE TABLEGROUP alice_grp OWNER alice; +CREATE TABLEGROUP alice_grp_2 OWNER alice; +CREATE TABLEGROUP alice_grp_3 OWNER alice; +CREATE TABLE bob_table (a INT) TABLEGROUP alice_grp; +CREATE TABLE bob_table_2 (a INT) TABLEGROUP alice_grp_2; +CREATE TABLE bob_table_3 (a INT) TABLEGROUP alice_grp_3; +CREATE TABLE alice_table (a INT); +ALTER TABLE bob_table OWNER TO bob; +ALTER TABLE bob_table_2 OWNER TO bob; +ALTER TABLE bob_table_3 OWNER TO bob; +ALTER TABLE alice_table OWNER TO alice; + +-- Fails +\set VERBOSITY terse \\ -- suppress dependency details. +DROP TABLEGROUP alice_grp; -- because bob_table depends on alice_grp +DROP USER alice; -- because alice still owns tablegroups +DROP OWNED BY alice; -- because bob's tables depend on alice's tablegroups +\set VERBOSITY default + +-- Succeeds +DROP TABLEGROUP alice_grp_3 CASCADE; +DROP TABLE bob_table; + +SELECT relname FROM pg_class WHERE relname LIKE 'bob%'; + +DROP TABLEGROUP alice_grp; -- bob_table is gone, so alice_grp has no deps +DROP OWNED BY alice CASCADE; -- CASCADE means we are allowed to drop bob's tables. We'll notify about any entities that she doesn't own. +DROP USER alice; -- we dropped all of alice's entities, so we can remove her + +SELECT relname FROM pg_class WHERE relname LIKE 'bob%'; + +-- +-- Usage of WITH tablegroup_oid reloption, this legacy syntax no longer works. +-- + +CREATE TABLE tgroup_with1 (col1 int, col2 int) WITH (tablegroup_oid=16385); +CREATE TABLE tgroup_with3 (col1 int, col2 int) WITH (tablegroup_oid=16385) TABLEGROUP tgroup1; + +CREATE INDEX ON tgroup_test1(col1) WITH (tablegroup_oid=123); + +-- +-- Usage of SPLIT clause with TABLEGROUP should fail +-- +CREATE TABLE tgroup_split (col1 int PRIMARY KEY) SPLIT INTO 3 TABLETS TABLEGROUP tgroup1; +CREATE TABLE tgroup_split (col1 int, col2 text) SPLIT INTO 3 TABLETS TABLEGROUP tgroup1; +CREATE INDEX ON tgroup_test1(col1) SPLIT AT VALUES((10), (20), (30)); + +-- +-- Test CREATE INDEX with hash columns +-- +CREATE TABLEGROUP tgroup_hash_index_test; +CREATE TABLE tbl (r1 int, r2 int, v1 int, v2 int, +PRIMARY KEY (r1, r2)) TABLEGROUP tgroup_hash_index_test; +CREATE INDEX idx_tbl ON tbl (r1 hash); +CREATE INDEX idx2_tbl ON tbl ((r1, r2) hash); +CREATE INDEX idx3_tbl ON tbl (r1 hash, r2 asc); +CREATE UNIQUE INDEX unique_idx_tbl ON tbl (r1 hash); +CREATE UNIQUE INDEX unique_idx2_tbl ON tbl ((r1, r2) hash); +CREATE UNIQUE INDEX unique_idx3_tbl ON tbl (r1 hash, r2 asc); +\d tbl + +-- Make sure nothing bad happens to UNIQUE constraints after disabling HASH columns +-- for tablegroup indexes +CREATE TABLE tbl2 (r1 int PRIMARY KEY, r2 int, v1 int, v2 int, UNIQUE(v1)) +TABLEGROUP tgroup_hash_index_test; +ALTER TABLE tbl2 ADD CONSTRAINT unique_v2_tbl2 UNIQUE(v2); +\d tbl2 + +DROP TABLE tbl, tbl2; +DROP TABLEGROUP tgroup_hash_index_test; + +-- +-- Test describes +-- +CREATE TABLE tgroup_test4 (col1 int, col2 int) TABLEGROUP tgroup2; +CREATE INDEX ON tgroup_test4(col1); +CREATE INDEX ON tgroup_test4(col2); +-- Add comments +COMMENT ON TABLEGROUP tgroup1 IS 'Comment for Tablegroup 1'; +COMMENT ON TABLEGROUP tgroup2 IS 'Comment for Tablegroup 2'; +\dgr +\dgr+ +\dgrt +\dgrt+ +COMMENT ON TABLEGROUP tgroup2 IS NULL; +\dgr+ tgroup2 +\dgrt tgroup2 + +-- Describe table +\d tgroup_test2 +\d tgroup_test4 +\d tgroup_test4_col1_idx +CREATE TABLEGROUP tgroup_describe1; +CREATE TABLEGROUP tgroup_describe2; +CREATE TABLE tgroup_describe (col1 int) TABLEGROUP tgroup_describe1; +CREATE INDEX ON tgroup_describe(col1); +\d tgroup_describe +-- +-- DROP TABLEGROUP +-- + +DROP TABLEGROUP tgroup3; +-- These should fail. CREATE TABLE is to check that the row entry was deleted from pg_yb_tablegroup. +CREATE TABLE tgroup_test5 (col1 int, col2 int) TABLEGROUP tgroup3; +\set VERBOSITY terse \\ -- suppress dependency details. +DROP TABLEGROUP tgroup1; +DROP TABLEGROUP IF EXISTS tgroup1; +\set VERBOSITY default +DROP TABLEGROUP bad_tgroupname; +-- This drop should work now. +DROP TABLE tgroup_test1; +DROP TABLE tgroup_test2; +DROP TABLEGROUP tgroup1; +DROP TABLEGROUP IF EXISTS tgroup1; +-- Create a tablegroup with the name of a dropped tablegroup. +CREATE TABLEGROUP tgroup1; +DROP TABLEGROUP IF EXISTS tgroup1; +CREATE TABLEGROUP tgroup1; + +-- +-- Assigning a tablespace to a tablegroup +-- +CREATE TABLESPACE tblspc WITH (replica_placement='{"num_replicas": 1, "placement_blocks": [{"cloud":"cloud1","region":"datacenter1","zone":"rack1","min_num_replicas":1}]}'); + +-- These should fail +CREATE TABLEGROUP grp1 TABLESPACE nonexistentspc; +CREATE TABLEGROUP grp2 TABLESPACE pg_global; +-- These should succeeed +CREATE TABLEGROUP grp3 TABLESPACE tblspc; + +SET default_tablespace = "tblspc"; +CREATE TABLEGROUP grp4; +SET default_tablespace = ''; + +CREATE TABLE tgroup_test6 (col1 int, col2 int) TABLEGROUP grp3; +\dgr+ +\dgrt+ + +-- +-- Interactions with colocated database. +-- + +CREATE DATABASE db_colocated colocation=true; +\c db_colocated +-- This should fail. +CREATE TABLEGROUP tgroup1; diff --git a/src/test/regress/sql/yb.orig.tablegroup_alter.sql b/src/test/regress/sql/yb.orig.tablegroup_alter.sql new file mode 100644 index 0000000000000..a3b6576e32378 --- /dev/null +++ b/src/test/regress/sql/yb.orig.tablegroup_alter.sql @@ -0,0 +1,47 @@ +-- +-- Tests for altering tablegroups. Includes name / owner changes. +-- Will eventually include tests on pulling tables out of tablegroups / altering the tablegroup of a table etc. +-- +CREATE DATABASE db_alter_tablegroup; +\c db_alter_tablegroup + +-- Test rename +CREATE TABLEGROUP alter_tgroup1; +CREATE TABLEGROUP alter_tgroup2; + +SELECT grpname FROM pg_yb_tablegroup ORDER BY grpname; +ALTER TABLEGROUP alter_tgroup1 RENAME TO alter_tgroup_try_alt; +SELECT grpname FROM pg_yb_tablegroup ORDER BY grpname; +ALTER TABLEGROUP alter_tgroup_try_alt RENAME TO alter_tgroup_alt; +SELECT grpname FROM pg_yb_tablegroup ORDER BY grpname; + +ALTER TABLEGROUP alter_tgroup2 RENAME TO alter_tgroup_alt; -- fail +ALTER TABLEGROUP alter_tgroup2 RENAME TO alter_tgroup2; -- fail +SELECT grpname FROM pg_yb_tablegroup ORDER BY grpname; +ALTER TABLEGROUP alter_tgroup_not_exists RENAME TO alter_tgroup_not_exists; -- fail + +-- Test alter owner +CREATE USER u1; +CREATE USER u2; +CREATE TABLEGROUP alter_tgroup3 OWNER u1; +CREATE TABLEGROUP alter_tgroup4 OWNER u1; +CREATE TABLEGROUP alter_tgroup5; +CREATE TABLEGROUP alter_tgroup6 OWNER u1; + +ALTER TABLEGROUP alter_tgroup3 OWNER TO u2; +ALTER TABLEGROUP alter_tgroup4 OWNER TO u3; -- fail +ALTER TABLEGROUP alter_tgroup_not_exists OWNER TO u1; -- fail + +\c db_alter_tablegroup u1 +DROP TABLEGROUP alter_tgroup3; -- fail +DROP TABLEGROUP alter_tgroup4; +ALTER TABLEGROUP alter_tgroup5 OWNER TO u1; -- fail + +\c db_alter_tablegroup u2 +DROP TABLEGROUP alter_tgroup3; + +\c db_alter_tablegroup yugabyte_test + +DROP USER u1; -- fails because u1 still owns alter_tgroup6 +ALTER TABLEGROUP alter_tgroup6 OWNER TO u2; +DROP USER u1; -- succeeds diff --git a/src/test/regress/sql/yb.orig.tablegroup_colocation_id.sql b/src/test/regress/sql/yb.orig.tablegroup_colocation_id.sql new file mode 100644 index 0000000000000..8afd364a78af5 --- /dev/null +++ b/src/test/regress/sql/yb.orig.tablegroup_colocation_id.sql @@ -0,0 +1,117 @@ +CREATE DATABASE test_tablegroups_colocation_id; +\c test_tablegroups_colocation_id + +CREATE VIEW table_props AS + SELECT c.relname, props.is_colocated, tg.grpname AS tablegroup, props.colocation_id + FROM pg_class c, yb_table_properties(c.oid) props + LEFT JOIN pg_yb_tablegroup tg ON tg.oid = props.tablegroup_oid + WHERE c.relname LIKE 'tg%' + ORDER BY c.relname; + +CREATE TABLEGROUP tg1; +CREATE TABLEGROUP tg2; + +-- +-- There should be no collision between: +-- (a) different colocation IDs within the same tablegroup, +-- (b) same colocation IDs across different tablegroups. +-- + +CREATE TABLE tg1_table1(v1 int PRIMARY KEY) WITH (colocation_id=20001) TABLEGROUP tg1; +CREATE TABLE tg1_table2(v1 int PRIMARY KEY, v2 text) WITH (colocation_id=20002) TABLEGROUP tg1; +CREATE TABLE tg1_table3(v1 int PRIMARY KEY, v2 int, v3 text) WITH (colocation_id=100500) TABLEGROUP tg1; + +CREATE TABLE tg2_table1(v1 text PRIMARY KEY) WITH (colocation_id=20001) TABLEGROUP tg2; +CREATE TABLE tg2_table2(v1 text PRIMARY KEY, v2 int) WITH (colocation_id=20002) TABLEGROUP tg2; + +INSERT INTO tg1_table1 VALUES (110), (111), (112); +INSERT INTO tg1_table2 VALUES (120, '120t'), (121, '121t'), (122, '122t'); +INSERT INTO tg1_table3 VALUES (130, 1300, '130t'), (131, 1310, '131t'), (132, 1320, '132t'); + +INSERT INTO tg2_table1 VALUES ('210t'), ('211t'), ('212t'); +INSERT INTO tg2_table2 VALUES ('220t', 120), ('221t', 121), ('222t', 122); + +\d tg1_table3 +\d tg2_table1 +SELECT * FROM table_props; + +SELECT * FROM tg1_table1; +SELECT * FROM tg1_table2; +SELECT * FROM tg1_table3; +SELECT * FROM tg2_table1; +SELECT * FROM tg2_table2; + +DROP TABLE tg1_table1, tg1_table2, tg1_table3, tg2_table1, tg2_table2; + +-- +-- Various invalid cases. +-- + +CREATE TABLE tg1_table_valid(v1 int PRIMARY KEY) WITH (colocation_id=20001) TABLEGROUP tg1; -- Success +CREATE TABLE tg1_table_invalid(v1 int PRIMARY KEY) WITH (colocation_id=20001) TABLEGROUP tg1; +CREATE TABLE tg1_table_invalid(v1 int UNIQUE WITH (colocation_id=40002)) WITH (colocation_id=40002) TABLEGROUP tg1; + +SELECT * FROM table_props; + +DROP TABLE IF EXISTS tg1_table_valid, tg1_table_invalid; + +-- +-- Test colocated tables with indexes. +-- + +CREATE TABLE tg1_table1( + v1 int, + v2 int UNIQUE WITH (colocation_id=20002), + v3 text +) WITH (colocation_id=20001) TABLEGROUP tg1; + +INSERT INTO tg1_table1 VALUES (1, 1, 'v1'), (1, 1, 'v1'); -- Fail +INSERT INTO tg1_table1 VALUES (1, 1, 'v2'), (1, 2, 'v2'), (3, 3, 'v2'), (4, 4, 'v2'); + +CREATE INDEX tg1_table1_v1_idx ON tg1_table1 (v1) WITH (colocation_id=20003); + +\d tg1_table1 +SELECT * FROM table_props; + +-- Make sure we're using index only scans and index scans respectively. +EXPLAIN (COSTS OFF) SELECT v1 FROM tg1_table1 WHERE v1 IN (1, 2, 4); +EXPLAIN (COSTS OFF) SELECT v2 FROM tg1_table1 WHERE v2 IN (1, 2, 4); +EXPLAIN (COSTS OFF) SELECT * FROM tg1_table1 WHERE v1 IN (1, 2, 4) ORDER BY v1, v2, v3; +EXPLAIN (COSTS OFF) SELECT * FROM tg1_table1 WHERE v2 IN (1, 2, 4) ORDER BY v1, v2, v3; + +SELECT v1 FROM tg1_table1 WHERE v1 IN (1, 2, 4); +SELECT v2 FROM tg1_table1 WHERE v2 IN (1, 2, 4); +SELECT * FROM tg1_table1 WHERE v1 IN (1, 2, 4) ORDER BY v1, v2, v3; +SELECT * FROM tg1_table1 WHERE v2 IN (1, 2, 4) ORDER BY v1, v2, v3; + +DELETE FROM tg1_table1 WHERE v1 = 1; +DELETE FROM tg1_table1 WHERE v2 = 3; + +SELECT v1 FROM tg1_table1 WHERE v1 IN (1, 2, 4); +SELECT v2 FROM tg1_table1 WHERE v2 IN (1, 2, 4); +SELECT * FROM tg1_table1 WHERE v1 IN (1, 2, 4) ORDER BY v1, v2, v3; +SELECT * FROM tg1_table1 WHERE v2 IN (1, 2, 4) ORDER BY v1, v2, v3; + +TRUNCATE TABLE tg1_table1; + +SELECT v1 FROM tg1_table1 WHERE v1 IN (1, 2, 4); +SELECT v2 FROM tg1_table1 WHERE v2 IN (1, 2, 4); +SELECT * FROM tg1_table1 WHERE v1 IN (1, 2, 4) ORDER BY v1, v2, v3; +SELECT * FROM tg1_table1 WHERE v2 IN (1, 2, 4) ORDER BY v1, v2, v3; + +INSERT INTO tg1_table1 VALUES (1, 1, 'v3'), (444, 4, 'v3'); + +SELECT v1 FROM tg1_table1 WHERE v1 IN (1, 2, 4); +SELECT v2 FROM tg1_table1 WHERE v2 IN (1, 2, 4); +SELECT * FROM tg1_table1 WHERE v1 IN (1, 2, 4) ORDER BY v1, v2, v3; +SELECT * FROM tg1_table1 WHERE v2 IN (1, 2, 4) ORDER BY v1, v2, v3; + +DROP TABLE tg1_table1; + +-- +-- Cleanup +-- + +\c yugabyte +DROP DATABASE test_tablegroups_colocation_id; +SELECT count(*) FROM pg_database WHERE datname = 'test_tablegroups_colocation_id'; diff --git a/src/test/regress/sql/yb.orig.tablegroup_deprecation.sql b/src/test/regress/sql/yb.orig.tablegroup_deprecation.sql new file mode 100644 index 0000000000000..bd8d20ccfd41f --- /dev/null +++ b/src/test/regress/sql/yb.orig.tablegroup_deprecation.sql @@ -0,0 +1,9 @@ +-- +-- Tablegroup Deprecation Warning Test +-- +CREATE TABLEGROUP tg; +CREATE TABLE tbl (k INT, v INT) TABLEGROUP tg; +ALTER TABLEGROUP tg OWNER TO postgres; +ALTER TABLEGROUP tg RENAME TO tg2; +DROP TABLE tbl; +DROP TABLEGROUP tg2; diff --git a/src/test/regress/sql/yb.orig.tablegroup_dml.sql b/src/test/regress/sql/yb.orig.tablegroup_dml.sql new file mode 100644 index 0000000000000..d2b3261a2bda3 --- /dev/null +++ b/src/test/regress/sql/yb.orig.tablegroup_dml.sql @@ -0,0 +1,144 @@ +-- +-- Tablegroups +-- + +-- CREATE DATABASE test_tablegroups + +CREATE DATABASE test_tablegroups; +\c test_tablegroups + +-- CREATE TABLEGROUP + +CREATE TABLEGROUP tg_test1; +CREATE TABLEGROUP tg_test2; + +-- CREATE TABLE +-- No primary key +CREATE TABLE tab_nonkey (a INT) TABLEGROUP tg_test1; +\d tab_nonkey +-- Hash partitioned will fail +CREATE TABLE tab_key (a INT, PRIMARY KEY(a HASH)) TABLEGROUP tg_test1; +\d tab_key +-- Range primary key +CREATE TABLE tab_range (a INT, PRIMARY KEY (a ASC)) TABLEGROUP tg_test1; +CREATE TABLE tab_range_multicol (a INT, b INT, PRIMARY KEY (a ASC, b DESC)) TABLEGROUP tg_test1; +-- do not use tablegroup +CREATE TABLE tab_nonkey_nogrp (a INT); +CREATE TABLE tab_hash_nogrp (a INT PRIMARY KEY); + +-- INSERT +INSERT INTO tab_range (a) VALUES (0), (1), (2); +INSERT INTO tab_range (a, b) VALUES (0, '0'); -- fail +INSERT INTO tab_range_multicol (a, b) VALUES (0, '0'), (1, '1'); +INSERT INTO tab_nonkey_nogrp (a) VALUES (0), (1), (2), (3); +INSERT INTO tab_hash_nogrp (a) VALUES (0), (1), (2), (3); + +-- SELECT +SELECT * FROM tab_range; +SELECT * FROM tab_range WHERE a = 2; +SELECT * FROM tab_range WHERE n = '0'; -- fail +SELECT * FROM tab_range_multicol; +SELECT * FROM tab_nonkey_nogrp ORDER BY a ASC; +SELECT * FROM tab_hash_nogrp ORDER BY a ASC; + +BEGIN; +INSERT INTO tab_range (a) VALUES (4); +ROLLBACK; +SELECT * FROM tab_range; +BEGIN; +INSERT INTO tab_range (a) VALUES (5); +COMMIT; +SELECT * FROM tab_range; + +INSERT INTO tab_range VALUES (6), (6); -- fail + +-- CREATE INDEX + +-- table with index in the tablegroup +CREATE TABLE tab_range2 (a INT, b INT) TABLEGROUP tg_test2; +CREATE INDEX idx_range2 ON tab_range2 (a); +\d tab_range2 +INSERT INTO tab_range2 (a, b) VALUES (0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +EXPLAIN (COSTS OFF) SELECT * FROM tab_range2 WHERE a = 1; +SELECT * FROM tab_range2 WHERE a = 1; +UPDATE tab_range2 SET b = b + 1 WHERE a > 3; +SELECT * FROM tab_range2 ORDER BY a; +DELETE FROM tab_range2 WHERE a > 3; +SELECT * FROM tab_range2 ORDER BY a; + +-- table with no tablegroup with index +CREATE TABLE tab_range_nogrp (a INT, b INT, PRIMARY KEY (a ASC)); +CREATE INDEX idx_range_nogrp ON tab_range_nogrp (a); + +\dt +\di + +-- TRUNCATE TABLE + +-- truncate tablegroup table with default index +TRUNCATE TABLE tab_range; +SELECT * FROM tab_range; +INSERT INTO tab_range VALUES (4); +SELECT * FROM tab_range; +INSERT INTO tab_range VALUES (1); +INSERT INTO tab_range VALUES (2), (5); +SELECT * FROM tab_range; +DELETE FROM tab_range WHERE a = 2; +SELECT * FROM tab_range; + +TRUNCATE TABLE tab_range; + +-- truncate tablegroup table with explicit index +TRUNCATE TABLE tab_range2; +SELECT * FROM tab_range2; + +-- ALTER TABLE +CREATE TABLE tab_range_alter (a INT, b INT, PRIMARY KEY (a ASC)) TABLEGROUP tg_test1; +INSERT INTO tab_range (a) VALUES (0), (1), (2); +INSERT INTO tab_range_alter (a, b) VALUES (0, 0), (1, 1); + +SELECT * FROM tab_range; +SELECT * FROM tab_range_alter; + +-- Alter tablegrouped tables +ALTER TABLE tab_range ADD COLUMN x INT; +ALTER TABLE tab_range_alter DROP COLUMN b; + +SELECT * FROM tab_range; +SELECT * FROM tab_range_alter; + +ALTER TABLE tab_range_alter RENAME TO tab_range_alter_renamed; +SELECT * FROM tab_range_alter_renamed; +SELECT * FROM tab_range_alter; + +-- DROP TABLE / INDEX + +-- drop table that is in a tablegroup with default index +DROP TABLE tab_range; +SELECT * FROM tab_range; + +-- drop non-colocated table without index +DROP TABLE tab_nonkey_nogrp; +SELECT * FROM tab_nonkey_nogrp; + +-- drop index on tablegrouped table +DROP INDEX idx_range2; +EXPLAIN SELECT * FROM tab_range2 WHERE a = 1; + +--- drop colocated table with dropped index +DROP TABLE tab_range2; +SELECT * FROM tab_range2; + +\dt +\di + +-- DROP TABLEGROUP +\set VERBOSITY terse \\ -- suppress dependency details. +DROP TABLEGROUP tg_test1; -- fail +\set VERBOSITY default +DROP TABLEGROUP tg_test2; + +-- drop database +\c yugabyte +DROP DATABASE test_tablegroups; +SELECT count(*) FROM pg_database WHERE datname = 'test_tablegroups'; diff --git a/src/test/regress/sql/yb.orig.tablegroup_partitioned_table.sql b/src/test/regress/sql/yb.orig.tablegroup_partitioned_table.sql new file mode 100644 index 0000000000000..6958ea7383366 --- /dev/null +++ b/src/test/regress/sql/yb.orig.tablegroup_partitioned_table.sql @@ -0,0 +1,15 @@ +CREATE DATABASE test_tablegroup_partitioned_tables; +\c test_tablegroup_partitioned_tables +CREATE TABLEGROUP tg1; +CREATE TABLE prt (id int PRIMARY KEY, v int) PARTITION BY RANGE (id) TABLEGROUP tg1; +CREATE TABLE prt_p1 PARTITION OF prt FOR VALUES FROM (1) TO (2); +CREATE TABLE IF NOT EXISTS prt_p2 PARTITION OF prt FOR VALUES FROM (2) TO (3); +\dgrt +CREATE TABLE prt_p3 PARTITION OF prt FOR VALUES FROM (3) TO (4) TABLEGROUP tg1; +CREATE TABLE IF NOT EXISTS prt_p4 PARTITION OF prt FOR VALUES FROM (4) TO (5) TABLEGROUP tg1; + +CREATE TABLE prt2 (id int PRIMARY KEY, v int) PARTITION BY RANGE (id) WITH (colocation_id='666666') TABLEGROUP tg1; +SELECT * FROM yb_table_properties('prt2'::regclass::oid); + +CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (1) TO (2) WITH (colocation_id='7777777'); +SELECT * FROM yb_table_properties('prt2_p1'::regclass::oid); diff --git a/src/test/regress/sql/yb.orig.tablegroup_permissions.sql b/src/test/regress/sql/yb.orig.tablegroup_permissions.sql new file mode 100644 index 0000000000000..e51ade3a6c0c9 --- /dev/null +++ b/src/test/regress/sql/yb.orig.tablegroup_permissions.sql @@ -0,0 +1,148 @@ +-- +-- YB_TABLEGROUP Testsuite: Testing permissions for TABLEGROUP. +-- +CREATE DATABASE test_tablegroup; +\c test_tablegroup + +-- +-- CREATE TABLEGROUP +-- +CREATE USER user_1; +GRANT CREATE ON SCHEMA public TO user_1; +CREATE USER user_2 SUPERUSER; +CREATE USER user_3; +GRANT CREATE ON SCHEMA public TO user_3; +CREATE USER user_4; +GRANT CREATE ON SCHEMA public TO user_4; +GRANT CREATE ON DATABASE test_tablegroup TO user_3; +CREATE TABLEGROUP owned_by1 OWNER user_1; +CREATE TABLEGROUP supergroup; + +-- +-- Operations with user_3 +-- +\c test_tablegroup user_3 +-- Create tablegroup should succeed as well as dropping that tablegroup. +CREATE TABLEGROUP owned_by3; +DROP TABLEGROUP owned_by3; +CREATE TABLEGROUP owned_by1_created_by3 OWNER user_1; +-- However these should fail. +\set VERBOSITY terse \\ -- suppress dependency details. +DROP TABLEGROUP supergroup; +DROP TABLEGROUP owned_by1; +\set VERBOSITY default + +-- +-- Operations with user_2 +-- +\c test_tablegroup user_2 +-- All operations should succeed. +CREATE TABLEGROUP owned_by2; +CREATE TABLEGROUP owned_by1_created_by2 OWNER user_1; +DROP TABLEGROUP supergroup; +CREATE TABLE a(i text) TABLEGROUP owned_by2; + +-- +-- Operations with user_1 +-- +\c test_tablegroup user_1 +-- These will fail +\set VERBOSITY terse \\ -- suppress dependency details. +DROP TABLEGROUP owned_by2; +\set VERBOSITY default +CREATE TABLEGROUP owned_by1_created_by1; +-- However these should succed +DROP TABLEGROUP owned_by1; +DROP TABLEGROUP owned_by1_created_by2; +CREATE TABLE b(i text) TABLEGROUP owned_by1_created_by3; +-- Create table/index with a tablegroup should default to no privs. Only owner/superuser. +-- This should fail. +CREATE TABLE c(i text) TABLEGROUP owned_by2; + +-- +-- GRANT/REVOKE tests +-- +\c test_tablegroup yugabyte +-- Grant privs to user_1 and user_3 and revoke from user_2 (but still test that superuser supersedes) +CREATE TABLEGROUP test_grant; +GRANT CREATE ON TABLEGROUP test_grant TO user_1 WITH GRANT OPTION; +REVOKE ALL ON TABLEGROUP test_grant FROM user_2; +GRANT ALL ON TABLEGROUP test_grant TO user_3; + +-- +-- Operations with user_4 (no perms) +-- +\c test_tablegroup user_4 +-- This will fail +CREATE TABLE d(i text) TABLEGROUP test_grant; + +-- +-- Operations with user_3 (all perms = ACL_CREATE) +-- +\c test_tablegroup user_3 +-- This will succeed +CREATE TABLE e(i text) TABLEGROUP test_grant; +CREATE INDEX ON e(i); + +-- +-- Operations with user_2 (no direct perms but superuser should bypass aclcheck) +-- +\c test_tablegroup user_2 +-- This will succeed +CREATE TABLE f(i text) TABLEGROUP test_grant; +CREATE INDEX ON f(i); + +-- +-- Operations with user_1 (create perms), also has GRANT OPTION +-- +\c test_tablegroup user_1 +-- This will also succeed +CREATE TABLE g(i text) TABLEGROUP test_grant; +GRANT CREATE ON TABLEGROUP test_grant TO user_4; + +-- +-- Operations with user_4 - now has CREATE +-- +\c test_tablegroup user_4 +-- This will now succeed +CREATE TABLE d(i text) TABLEGROUP test_grant; + +-- +-- Now REVOKE privs and ensure the users no longer have access. +-- +\c test_tablegroup yugabyte +REVOKE CREATE ON TABLEGROUP test_grant FROM user_1 CASCADE; +REVOKE GRANT OPTION FOR CREATE ON TABLEGROUP test_grant FROM user_1; +REVOKE ALL ON TABLEGROUP test_grant FROM user_3; + +-- +-- Operations with user_3 (no perms now) +-- +\c test_tablegroup user_3 +-- This will fail +CREATE TABLE h(i text) TABLEGROUP test_grant; +CREATE INDEX ON e(i); + +-- +-- Operations with user_1 (no perms now, no grant option) +-- +\c test_tablegroup user_1 +-- This will fail +CREATE TABLE i(i text) TABLEGROUP test_grant; +GRANT CREATE ON TABLEGROUP test_grant TO user_3; + +-- +-- ALTER DEFAULT PRIVILEGES tests +-- +\c yugabyte yugabyte +-- Grant user_1 CREATE for all future tablegroups +ALTER DEFAULT PRIVILEGES GRANT CREATE ON TABLEGROUPS TO user_1; +CREATE TABLEGROUP test_adef; +\ddp +-- Operations with user_1. Should work without explicit grant for test_adef. +\c yugabyte user_1 +CREATE TABLE i(i text) TABLEGROUP test_adef; +-- Revoke user_1 CREATE default +\c yugabyte yugabyte +ALTER DEFAULT PRIVILEGES REVOKE CREATE ON TABLEGROUPS FROM user_1; +\ddp diff --git a/src/test/regress/sql/yb.orig.tablespaces.sql b/src/test/regress/sql/yb.orig.tablespaces.sql new file mode 100644 index 0000000000000..e1bba5de679cc --- /dev/null +++ b/src/test/regress/sql/yb.orig.tablespaces.sql @@ -0,0 +1,739 @@ +-- Create Tablespace API tests +-- Negative test cases +-- Create Tablespace should not work if neither LOCATION nor options +-- are specified +CREATE TABLESPACE x; +CREATE TABLESPACE x OWNER yugabyte; + +-- Ill formed JSON +CREATE TABLESPACE x WITH (replica_placement='[{"cloud"}]'); + +-- num_replicas field missing. +CREATE TABLESPACE x WITH (replica_placement='{"placement_blocks":[{"cloud":"cloud1","region":"r1","zone":"z1","min_num_replicas":3}]}'); + +-- missing zone field +CREATE TABLESPACE x WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"r1","min_num_replicas":3}]}'); + +-- missing region field +CREATE TABLESPACE x WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","zone":"z1","min_num_replicas":3}]}'); + +-- missing cloud field +CREATE TABLESPACE x WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"region":"r1","zone":"z1","min_num_replicas":3}]}'); + +-- wildcard cloud field is not allowed +CREATE TABLESPACE x WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"*","region":"r1","zone":"z1","min_num_replicas":3}]}'); + +-- wildcard region with specific zone is not allowed +CREATE TABLESPACE x WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"*","zone":"z1","min_num_replicas":3}]}'); + +-- Invalid value for num_replicas. +CREATE TABLESPACE x WITH (replica_placement='{"num_replicas":"three", "placement_blocks":[{"cloud":"cloud1","region":"r1","zone":"z1","min_num_replicas":3}]}'); + +-- Invalid value for min_num_replicas. +CREATE TABLESPACE x WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"r1","zone":"z1","min_num_replicas":"three"}]}'); + +-- Sum of min_num_replicas greater than num_replicas. +CREATE TABLESPACE y WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"r1","zone":"z1","min_num_replicas":2},{"cloud":"cloud2","region":"r2", "zone":"z2", "min_num_replicas":2}]}'); + +-- Positive cases +-- Tablespace without replica_placement option. +CREATE TABLESPACE x LOCATION '/data'; + +-- placement_blocks field missing is allowed +CREATE TABLESPACE no_placement_blocks WITH (replica_placement='{"num_replicas":3}'); + +-- Sum of min_num_replicas lesser than num_replicas. +CREATE TABLESPACE y WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"r1","zone":"z1","min_num_replicas":1},{"cloud":"cloud2","region":"r2", "zone":"z2", "min_num_replicas":1}]}'); + +-- Sum of min_num_replicas equal to num_replicas. +CREATE TABLESPACE z WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"r1","zone":"z1","min_num_replicas":1},{"cloud":"cloud2","region":"r2", "zone":"z2", "min_num_replicas":2}]}'); + +-- wildcard region +CREATE TABLESPACE wildcard_region_ts WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"*","zone":"*","min_num_replicas":3}]}'); + +-- wildcard zone +CREATE TABLESPACE wildcard_zone_ts WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"r1","zone": "*","min_num_replicas":3}]}'); + +-- describe command +\db + +CREATE TABLEGROUP grp TABLESPACE x; +-- Fail, not empty +\set VERBOSITY terse \\ -- suppress dependency details. +DROP TABLESPACE x; +\set VERBOSITY default +DROP TABLEGROUP grp; +-- Should succeed, empty now +DROP TABLESPACE x; +DROP TABLESPACE y; + +-- Ensure that we ignore extra keys for potential downgrade scenarios. +CREATE TABLESPACE extra_keys WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"r1","zone":"z1","min_num_replicas":3,"extra_key":"extra_value"}]}'); + +-- create a tablespace using WITH clause +CREATE TABLESPACE regress_tblspacewith WITH (some_nonexistent_parameter = true); -- fail +CREATE TABLESPACE regress_tblspacewith WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"r1","zone":"z1","min_num_replicas":1},{"cloud":"cloud2","region":"r2", "zone":"z2", "min_num_replicas":2}]}'); -- ok + +-- check to see if WITH clause parameter was used +SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith'; + +DROP TABLESPACE regress_tblspacewith; + +-- create a tablespace we can use +CREATE TABLESPACE regress_tblspace LOCATION '/data'; +CREATE TABLESPACE regress_tblspace_2 LOCATION '/data'; + +-- try setting and resetting some properties for the new tablespace +ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1); + +-- Enable these tests after ALTER is supported. +/* +ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail +ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail +ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok +*/ + +-- create a schema we can use +CREATE SCHEMA testschema; + +-- create a tablespace not satisfied by the cluster +CREATE TABLESPACE invalid_tablespace WITH (replica_placement='{"num_replicas": 1, "placement_blocks":[{"cloud":"nonexistent","region":"nowhere","zone":"area51","min_num_replicas":1}]}'); + +-- try using unsatisfiable tablespace +CREATE TABLE testschema.foo (i int) TABLESPACE invalid_tablespace; -- fail +CREATE TABLE testschema.foo (i int) TABLESPACE regress_tblspace; +ALTER TABLE testschema.foo SET TABLESPACE invalid_tablespace; -- fail +DROP TABLE testschema.foo; +DROP TABLESPACE invalid_tablespace; + +-- try a table +CREATE TABLE testschema.foo (i int) TABLESPACE regress_tblspace; +SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c + where c.reltablespace = t.oid AND c.relname = 'foo'; + +INSERT INTO testschema.foo VALUES(1); +INSERT INTO testschema.foo VALUES(2); + +-- tables from dynamic sources +CREATE TABLE testschema.asselect TABLESPACE regress_tblspace AS SELECT 1; +SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c + where c.reltablespace = t.oid AND c.relname = 'asselect'; + +PREPARE selectsource(int) AS SELECT $1; +CREATE TABLE testschema.asexecute TABLESPACE regress_tblspace + AS EXECUTE selectsource(2); +/* +SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c + where c.reltablespace = t.oid AND c.relname = 'asexecute'; +*/ + +-- Create table with primary key. +CREATE TABLE testschema.foo_pk (i int, PRIMARY KEY(i)) TABLESPACE regress_tblspace; +\d testschema.foo_pk_pkey +\d testschema.foo_pk; +-- Fail, cannot ALTER INDEX SET TABLESPACE for primary key index. +ALTER INDEX testschema.foo_pk_pkey SET TABLESPACE regress_tblspace_2; + +-- Create table with primary key after default tablespace is changed. +SET default_tablespace TO regress_tblspace; +CREATE TABLE testschema.foo_pk_default_tblspc (i int, PRIMARY KEY(i)); +\d testschema.foo_pk_default_tblspc_pkey; +\d testschema.foo_pk_default_tblspc; +-- Fail, cannot ALTER INDEX SET TABLESPACE for primary key index. +ALTER INDEX testschema.foo_pk_pkey SET TABLESPACE pg_default; +SET default_tablespace TO ''; + +-- Verify that USING INDEX TABLESPACE is not supported for primary keys. +CREATE TABLE testschema.using_index1 (a int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace); +CREATE TABLE testschema.using_index1 (a int, PRIMARY KEY(a) USING INDEX TABLESPACE regress_tblspace); + +-- Verify that USING INDEX TABLESPACE is supported for other constraints. +CREATE TABLE testschema.using_index2 (a int UNIQUE USING INDEX TABLESPACE regress_tblspace); +CREATE TABLE testschema.using_index3 (a int, UNIQUE(a) USING INDEX TABLESPACE regress_tblspace); +\d testschema.using_index2; +\d testschema.using_index3; + +ALTER INDEX testschema.using_index2_a_key SET TABLESPACE pg_default; +\d testschema.using_index2; + +-- index +CREATE INDEX foo_idx on testschema.foo(i) TABLESPACE regress_tblspace; +SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c + where c.reltablespace = t.oid AND c.relname = 'foo_idx'; + +-- partitioned table +CREATE TABLE testschema.part (a int) PARTITION BY LIST (a); +CREATE TABLE testschema.part12 PARTITION OF testschema.part FOR VALUES IN(1,2) PARTITION BY LIST (a) TABLESPACE regress_tblspace; +CREATE TABLE testschema.part12_1 PARTITION OF testschema.part12 FOR VALUES IN (1); +ALTER TABLE testschema.part12 SET TABLESPACE pg_default; +CREATE TABLE testschema.part12_2 PARTITION OF testschema.part12 FOR VALUES IN (2); +-- Ensure part12_1 defaulted to regress_tblspace and part12_2 defaulted to pg_default. +SELECT relname, spcname FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_tablespace t ON c.reltablespace = t.oid + where c.relname LIKE 'part%' order by relname; +DROP TABLE testschema.part; + +-- temporary table +-- Fail, cannot set tablespaces for temp tables +CREATE TEMPORARY TABLE temptest (a INT) TABLESPACE regress_tblspace; +CREATE TEMPORARY TABLE temptest (a INT); +-- Fail, cannot set tablespaces for temp tables +ALTER TABLE temptest SET TABLESPACE regress_tblspace; +-- Fail, cannot set tablespace for temp indexes +CREATE INDEX temp_idx_tblspc ON temptest(a) TABLESPACE regress_tblspace; +CREATE INDEX temp_idx ON temptest(a); +-- Fail, cannot set tablespaces for temp tables +ALTER INDEX temp_idx SET TABLESPACE regress_tblspace; +DROP TABLE temptest; + +-- Fail, cannot set tablespaces for temp tables +CREATE TEMPORARY TABLE tempparttest (a int) PARTITION BY LIST (a) TABLESPACE regress_tblspace; +CREATE TEMPORARY TABLE tempparttest (a int) PARTITION BY LIST (a); +-- Fail, cannot set tablespaces for temp tables +ALTER TABLE tempparttest SET TABLESPACE regress_tblspace; +DROP TABLE tempparttest; + +-- partitioned index +CREATE TABLE testschema.part (a int) PARTITION BY LIST (a); +CREATE TABLE testschema.part1 PARTITION OF testschema.part FOR VALUES IN (1); +CREATE INDEX part_a_idx ON testschema.part (a) TABLESPACE regress_tblspace; +CREATE TABLE testschema.part2 PARTITION OF testschema.part FOR VALUES IN (2); +SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c + where c.reltablespace = t.oid AND c.relname LIKE 'part%_idx' ORDER BY relname; + +CREATE TABLE testschema.part34 PARTITION OF testschema.part FOR VALUES IN (3, 4) PARTITION BY LIST (a); +CREATE TABLE testschema.part3 PARTITION OF testschema.part34 FOR VALUES IN (3); +ALTER INDEX testschema.part34_a_idx SET TABLESPACE pg_default; +CREATE TABLE testschema.part4 PARTITION OF testschema.part34 FOR VALUES IN (4); +SELECT relname, spcname FROM pg_catalog.pg_class c LEFT OUTER JOIN pg_catalog.pg_tablespace t + ON c.reltablespace = t.oid WHERE c.relname LIKE 'part%_idx' ORDER BY relname; + +-- check that default_tablespace doesn't affect ALTER TABLE index rebuilds +CREATE TABLE testschema.test_default_tab(id bigint) TABLESPACE regress_tblspace; +INSERT INTO testschema.test_default_tab VALUES (1); +CREATE INDEX test_index1 on testschema.test_default_tab (id); +CREATE INDEX test_index2 on testschema.test_default_tab (id) TABLESPACE regress_tblspace; +\d testschema.test_index1 +\d testschema.test_index2 +-- use a custom tablespace for default_tablespace +SET default_tablespace TO regress_tblspace; +-- tablespace should not change if no rewrite +ALTER TABLE testschema.test_default_tab ALTER id TYPE bigint; +\d testschema.test_index1 +\d testschema.test_index2 +SELECT * FROM testschema.test_default_tab; +-- tablespace should not change even if there is an index rewrite +ALTER TABLE testschema.test_default_tab ALTER id TYPE int; +\d testschema.test_index1 +\d testschema.test_index2 +SELECT * FROM testschema.test_default_tab; +-- now use the default tablespace for default_tablespace +SET default_tablespace TO ''; +-- tablespace should not change if no rewrite +ALTER TABLE testschema.test_default_tab ALTER id TYPE int; +\d testschema.test_index1 +\d testschema.test_index2 +-- tablespace should not change even if there is an index rewrite +ALTER TABLE testschema.test_default_tab ALTER id TYPE bigint; +\d testschema.test_index1 +\d testschema.test_index2 +DROP TABLE testschema.test_default_tab; +-- check that default_tablespace affects index additions in ALTER TABLE +CREATE TABLE testschema.test_tab(id int) TABLESPACE regress_tblspace; +INSERT INTO testschema.test_tab VALUES (1); +SET default_tablespace TO regress_tblspace; +ALTER TABLE testschema.test_tab ADD CONSTRAINT test_tab_unique UNIQUE (id); +SET default_tablespace TO ''; +ALTER TABLE testschema.test_tab ADD CONSTRAINT test_tab_pkey PRIMARY KEY (id); +\d testschema.test_tab_unique +\d testschema.test_tab_pkey +\d testschema.test_tab; +SELECT * FROM testschema.test_tab; +DROP TABLE testschema.test_tab; + +-- let's try moving a table from one place to another +CREATE TABLE testschema.atable AS VALUES (1), (2); +CREATE UNIQUE INDEX anindex ON testschema.atable(column1); +ALTER TABLE testschema.atable SET TABLESPACE regress_tblspace; +ALTER INDEX testschema.anindex SET TABLESPACE regress_tblspace; +\d testschema.atable +ALTER INDEX testschema.part_a_idx SET TABLESPACE pg_global; +\d testschema.part +ALTER INDEX testschema.part_a_idx SET TABLESPACE pg_default; +\d testschema.part +ALTER INDEX testschema.part_a_idx SET TABLESPACE regress_tblspace; +\d testschema.part +INSERT INTO testschema.atable VALUES(3); +INSERT INTO testschema.atable VALUES(1); +SELECT COUNT(*) FROM testschema.atable; + +-- No such tablespace +CREATE TABLE bar (i int) TABLESPACE regress_nosuchspace; + +-- Fail, not empty +\set VERBOSITY terse \\ -- suppress dependency details. +DROP TABLESPACE regress_tblspace; +\set VERBOSITY default + +CREATE ROLE regress_tablespace_user1 login; +CREATE ROLE regress_tablespace_user2 login; +GRANT USAGE ON SCHEMA testschema TO regress_tablespace_user2; + +ALTER TABLESPACE regress_tblspace OWNER TO regress_tablespace_user1; + +CREATE TABLE testschema.tablespace_acl (c int); +-- new owner lacks permission to create this index from scratch +CREATE INDEX k ON testschema.tablespace_acl (c) TABLESPACE regress_tblspace; +ALTER TABLE testschema.tablespace_acl OWNER TO regress_tablespace_user2; + +SET SESSION ROLE regress_tablespace_user2; +CREATE TABLE tablespace_table (i int) TABLESPACE regress_tblspace; -- fail +ALTER TABLE testschema.tablespace_acl ALTER c TYPE bigint; +RESET ROLE; + +ALTER TABLESPACE regress_tblspace RENAME TO regress_tblspace_renamed; +/* +ALTER TABLE ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; +ALTER INDEX ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; + +-- Should show notice that nothing was done +ALTER TABLE ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; + +-- Should succeed +DROP TABLESPACE regress_tblspace_renamed; +*/ +\set VERBOSITY terse \\ -- suppress cascade details +DROP SCHEMA testschema CASCADE; +\set VERBOSITY default +DROP ROLE regress_tablespace_user1; +DROP TABLESPACE regress_tblspace; +DROP ROLE regress_tablespace_user1; +DROP ROLE regress_tablespace_user2; + +-- Colocated Tests +CREATE TABLESPACE x WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1}]}'); +CREATE DATABASE colocation_test colocation = true; +\c colocation_test +-- Should succeed in setting tablespace on a table in a colocated database +CREATE TABLE tab_key (a INT) TABLESPACE x; +-- Should succeed in setting tablespace on a table in a colocated database when opted out +CREATE TABLE tab_nonkey (a INT) WITH (COLOCATION = false) TABLESPACE x; +-- cleanup +DROP TABLE tab_key; +DROP TABLE tab_nonkey; +\c yugabyte +DROP DATABASE colocation_test; + +-- Verify that tablespaces cannot be set on partitioned tables. +CREATE TABLE list_partitioned (partkey char) PARTITION BY LIST(partkey) TABLESPACE x; +-- Cleanup. +DROP TABLE list_partitioned; +DROP TABLESPACE x; + +/* +Testing to make sure that an index on a "near" tablespace whose placements are +all on the current cloud/region/zone is preferred over "far" indexes. +*/ +CREATE TABLESPACE near WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1}]}'); +CREATE TABLESPACE far WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud2","region":"region1", "zone":"zone1", "min_num_replicas":1}]}'); +CREATE TABLESPACE regionlocal WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1}]}'); +CREATE TABLESPACE cloudlocal WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1}]}'); +CREATE TABLE foo(x int, y int); +CREATE UNIQUE INDEX good ON foo(x) INCLUDE (y) TABLESPACE near; +CREATE UNIQUE INDEX regionlocal_ind ON foo(x) INCLUDE (y) TABLESPACE regionlocal; +CREATE UNIQUE INDEX cloudlocal_ind ON foo(x) INCLUDE (y) TABLESPACE cloudlocal; +CREATE UNIQUE INDEX bad ON foo(x) INCLUDE (y) TABLESPACE far; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +SET yb_enable_geolocation_costing = off; +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +SET yb_enable_geolocation_costing = on; +DROP INDEX good; +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +DROP INDEX regionlocal_ind; +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; + +DROP TABLE foo; + +CREATE TABLE foo(id int primary key, val int); +CREATE UNIQUE INDEX bad ON foo(id) INCLUDE (val) TABLESPACE far; +CREATE UNIQUE INDEX good ON foo(id) INCLUDE (val) TABLESPACE near; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE id = 5; + +DROP TABLE foo; +DROP TABLESPACE far; +DROP TABLESPACE near; +DROP TABLESPACE regionlocal; +DROP TABLESPACE cloudlocal; + +-- Verify yb_db_admin role can use tablespace +-- Create objects not owned by yb_db_admin +CREATE TABLESPACE tblspace_other WITH (replica_placement='{"num_replicas": 1, "placement_blocks": [{"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1}]}'); +CREATE TABLE tbl_other(x int, y int); +SET SESSION ROLE yb_db_admin; +-- Verify yb_db_admin role can CREATE tablespace +CREATE TABLESPACE tblspace WITH (replica_placement='{"num_replicas": 1, "placement_blocks": [{"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1}]}'); +-- Verify yb_db_admin role can CREATE table with tablespace +CREATE TABLE tbl (x int, y int) TABLESPACE tblspace; +DROP TABLE tbl; +-- Verify yb_db_admin role can assign tablespace to tables +ALTER TABLE tbl_other SET TABLESPACE tblspace_other; +CREATE TABLE tbl (x int, y int); +ALTER TABLE tbl SET TABLESPACE tblspace; +DROP TABLE tbl; +-- Verify yb_db_admin_role can assign tablespace to index +CREATE TABLE tbl(x int, y int); +CREATE INDEX idx ON tbl(x) TABLESPACE tblspace; +CREATE INDEX idx2 ON tbl_other(x) TABLESPACE tblspace; +-- Verify yb_db_admin role cannot ALTER tablespace +ALTER TABLESPACE tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1); +-- Verify yb_db_admin role can DROP tablespace +DROP TABLE tbl; +DROP TABLE tbl_other; +DROP TABLESPACE tblspace; +DROP TABLESPACE tblspace_other; + +-- Test leader_preference +-- Empty leader_preference +CREATE TABLESPACE LP WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":}]}'); + +-- Negative leader_preference +CREATE TABLESPACE LP WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":-1}]}'); + +-- Zero as leader_preference +CREATE TABLESPACE LP WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":0}]}'); + +-- No leader_preference 1 +CREATE TABLESPACE LP WITH (replica_placement='{"num_replicas":1, "placement_blocks":[{"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":2}]}'); + +-- No leader_preference 2 +CREATE TABLESPACE LP WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"r1","zone":"z1","min_num_replicas":1,"leader_preference":1},{"cloud":"cloud2","region":"r2", "zone":"z2", "min_num_replicas":1,"leader_preference":1},{"cloud":"cloud2","region":"r2", "zone":"z3", "min_num_replicas":1,"leader_preference":3}]}'); + +-- No leader_preference 2 and no preference set +CREATE TABLESPACE LP WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"r1","zone":"z1","min_num_replicas":1,"leader_preference":1},{"cloud":"cloud2","region":"r2", "zone":"z2", "min_num_replicas":1},{"cloud":"cloud2","region":"r2", "zone":"z3", "min_num_replicas":1,"leader_preference":3}]}'); + +-- Positive case +-- Some zones with no leader_preference set +CREATE TABLESPACE LP WITH (replica_placement='{"num_replicas":3, "placement_blocks":[{"cloud":"cloud1","region":"r1","zone":"z1","min_num_replicas":1,"leader_preference":1},{"cloud":"cloud2","region":"r2", "zone":"z2", "min_num_replicas":1},{"cloud":"cloud2","region":"r2", "zone":"z3", "min_num_replicas":1}]}'); +-- Valid case +CREATE TABLESPACE valid_tablespace WITH (replica_placement='{"num_replicas":2,"placement_blocks":[{"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":1},{"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":2}]}'); +CREATE TABLE foo (i int) TABLESPACE valid_tablespace; +CREATE TABLE bar(i int); +ALTER TABLE bar SET TABLESPACE valid_tablespace; +DROP TABLE foo; +DROP TABLE bar; +DROP TABLESPACE valid_tablespace; +DROP TABLESPACE LP; + +-- Test ALTER TABLE/INDEX/MATERILIZED VIEW ALL IN TABLESPACE ... SET TABLESPACE ... + +CREATE TABLESPACE tsp1 WITH (replica_placement = '{"num_replicas": 1, "placement_blocks": [{ "cloud" : "cloud1", "region" : "region1", "zone" : "zone1", "min_num_replicas" : 1 }]}'); +CREATE TABLESPACE tsp2 WITH (replica_placement = '{"num_replicas": 1, "placement_blocks": [{ "cloud" : "cloud2", "region" : "region1", "zone" : "zone1", "min_num_replicas" : 1 }]}'); +CREATE TABLESPACE tsp3 WITH (replica_placement = '{"num_replicas": 1, "placement_blocks": [{ "cloud" : "cloud1", "region" : "region1", "zone" : "zone2", "min_num_replicas" : 1 }]}'); + +CREATE TABLE table_1(a int PRIMARY KEY, b int, c int) TABLESPACE tsp1; +CREATE TABLE table_2(a int PRIMARY KEY, b int, c int) TABLESPACE tsp1; +CREATE INDEX index_1 ON table_1(b) TABLESPACE tsp2; +CREATE MATERIALIZED VIEW mat_view TABLESPACE tsp1 AS SELECT * FROM table_1; + +-- Moving Table will also move the Primary Key indexes as the primary key index is an intrinsic part of its base table itself. +ALTER TABLE ALL IN TABLESPACE tsp1 SET TABLESPACE tsp2; +ALTER INDEX ALL IN TABLESPACE tsp2 SET TABLESPACE tsp1; +ALTER MATERIALIZED VIEW ALL IN TABLESPACE tsp1 SET TABLESPACE tsp3; + +-- View all relations present in pg_class in tablespace tsp1, tsp2 and tsp3 and if the relations were moved to the intended tablespaces. +SELECT c.relname, ts.spcname FROM + pg_class c + JOIN pg_tablespace ts ON c.reltablespace = ts.oid +WHERE + ts.spcname IN ('tsp1', 'tsp2', 'tsp3') +ORDER BY + c.relname; + +DROP MATERIALIZED VIEW mat_view; +DROP INDEX index_1; +DROP TABLE table_1; +DROP TABLE table_2; +DROP TABLESPACE tsp1; +DROP TABLESPACE tsp2; +DROP TABLESPACE tsp3; + +/* +Testing that an index in a tablespace with leader preference on a closer placement is preferred +over one on a tablespace with leader preference on a farther placement. +*/ + +CREATE TABLE foo(x int, y int); + +CREATE TABLESPACE zone_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1}]}'); + +CREATE TABLESPACE region_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1}]}'); + +CREATE TABLESPACE cloud_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1}]}'); + +CREATE UNIQUE INDEX zone_pref_ind ON foo(x) INCLUDE (y) TABLESPACE zone_pref; +CREATE UNIQUE INDEX region_pref_ind ON foo(x) INCLUDE (y) TABLESPACE region_pref; +CREATE UNIQUE INDEX cloud_pref_ind ON foo(x) INCLUDE (y) TABLESPACE cloud_pref; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +SET yb_enable_geolocation_costing = off; +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +SET yb_enable_geolocation_costing = on; +DROP INDEX zone_pref_ind; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +DROP INDEX region_pref_ind; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +DROP INDEX cloud_pref_ind; + +DROP TABLE foo; +DROP TABLESPACE zone_pref; +DROP TABLESPACE region_pref; +DROP TABLESPACE cloud_pref; + +/* +Testing that a leader preference > 1 is ignored in distance cost calculations. +*/ + +CREATE TABLE foo(x int, y int); + +CREATE TABLESPACE zone_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1,"leader_preference":2}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1,"leader_preference":3}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":4}]}'); + +CREATE TABLESPACE region_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":4}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1,"leader_preference":2}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":3}]}'); + +CREATE TABLESPACE cloud_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":3}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1,"leader_preference":4}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":2}]}'); + +CREATE UNIQUE INDEX zone_pref_ind ON foo(x) INCLUDE (y) TABLESPACE zone_pref; +CREATE UNIQUE INDEX region_pref_ind ON foo(x) INCLUDE (y) TABLESPACE region_pref; +CREATE UNIQUE INDEX cloud_pref_ind ON foo(x) INCLUDE (y) TABLESPACE cloud_pref; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +DROP INDEX zone_pref_ind; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +DROP INDEX region_pref_ind; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +DROP INDEX cloud_pref_ind; + +DROP TABLE foo; +DROP TABLESPACE zone_pref; +DROP TABLESPACE region_pref; +DROP TABLESPACE cloud_pref; + +/* +Testing that a tablespace with leader preference is preferred over one with +no leader preferences, when they have the same placements. +*/ + +CREATE TABLE foo(x int, y int); + +CREATE TABLESPACE no_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1}]}'); + +CREATE TABLESPACE has_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1}]}'); + +CREATE UNIQUE INDEX no_pref_ind ON foo(x) INCLUDE (y) TABLESPACE no_pref; +CREATE UNIQUE INDEX has_pref_ind ON foo(x) INCLUDE (y) TABLESPACE has_pref; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; + +DROP TABLE foo; +DROP TABLESPACE no_pref; +DROP TABLESPACE has_pref; + +/* +Testing that more specific leader-preferenced placements are preferred over +less specific ones. +*/ + +CREATE TABLE foo(x int, y int); + +CREATE TABLESPACE most_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1}]}'); + +CREATE TABLESPACE some_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1}]}'); + +CREATE TABLESPACE few_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1}]}'); + +CREATE UNIQUE INDEX few_pref_ind ON foo(x) INCLUDE (y) TABLESPACE few_pref; +CREATE UNIQUE INDEX some_pref_ind ON foo(x) INCLUDE (y) TABLESPACE some_pref; +CREATE UNIQUE INDEX most_pref_ind ON foo(x) INCLUDE (y) TABLESPACE most_pref; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +DROP INDEX few_pref_ind; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +DROP INDEX some_pref_ind; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +DROP INDEX most_pref_ind; + +DROP TABLE foo; +DROP TABLESPACE most_pref; +DROP TABLESPACE some_pref; +DROP TABLESPACE few_pref; + +/* +Testing that a tablespace with leader preference on a close and far placement is NOT +preferred over a tablespace with leader preference on just a medium-distance placement. + +We estimate based on the worst case, which means that having a far leader-preferred placement +leads to a large cost. +*/ +CREATE TABLE foo(x int, y int); + +CREATE TABLESPACE close_far_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1}]}'); + +CREATE TABLESPACE medium_pref + WITH (replica_placement='{"num_replicas": 4, "placement_blocks": [ + {"cloud":"cloud1","region":"region1","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud1","region":"region1","zone":"zone2","min_num_replicas":1,"leader_preference":1}, + {"cloud":"cloud1","region":"region2","zone":"zone1","min_num_replicas":1}, + {"cloud":"cloud2","region":"region1","zone":"zone1","min_num_replicas":1}]}'); + +CREATE UNIQUE INDEX close_far_pref_ind ON foo(x) INCLUDE (y) TABLESPACE close_far_pref; +CREATE UNIQUE INDEX medium_pref_ind ON foo(x) INCLUDE (y) TABLESPACE medium_pref; + +EXPLAIN (COSTS OFF) SELECT * FROM foo WHERE x = 5; +DROP TABLE foo; +DROP TABLESPACE close_far_pref; +DROP TABLESPACE medium_pref; + +/* Disallow setting tablespace for ALTER TABLE ADD CONSTRAINT PRIMARY KEY. */ +CREATE TABLESPACE ts1 LOCATION ''; +CREATE TABLE t1 (a int, b int); +ALTER TABLE t1 + ADD CONSTRAINT t1_pkey + PRIMARY KEY (a) + USING INDEX TABLESPACE ts1; +DROP TABLE t1; +DROP TABLESPACE ts1; + +/* + * Check that pg_shdepend is updated correctly when various tablespace + * operations are performed. + */ +CREATE TABLESPACE ts_dep1 LOCATION '/data'; +CREATE TABLESPACE ts_dep2 LOCATION '/data'; +CREATE TABLE dep_t (a int PRIMARY KEY) TABLESPACE ts_dep1; + +-- Query to get the dependencies for the table/index. +PREPARE dep_shdeps AS +SELECT CASE c.relkind WHEN 'r' THEN 'table' WHEN 'i' THEN 'index' ELSE c.relkind::text END AS objtype, + c.relname, ts.spcname +FROM pg_shdepend d +JOIN pg_class c ON d.classid = 'pg_class'::regclass AND d.objid = c.oid +JOIN pg_tablespace ts ON d.refclassid = 'pg_tablespace'::regclass AND d.refobjid = ts.oid +WHERE d.dbid = (SELECT oid FROM pg_database WHERE datname = current_database()) + AND c.relnamespace = 'public'::regnamespace + AND c.relname LIKE 'dep\_%' ESCAPE '\' +ORDER BY c.relname; + +-- Query to check reltablespace in pg_class for the table/index. +PREPARE dep_relts AS +SELECT CASE c.relkind WHEN 'r' THEN 'table' WHEN 'i' THEN 'index' ELSE c.relkind::text END AS objtype, + c.relname, ts.spcname +FROM pg_class c +LEFT JOIN pg_tablespace ts ON c.reltablespace = ts.oid +WHERE c.relnamespace = 'public'::regnamespace + AND c.relname LIKE 'dep\_%' ESCAPE '\' +ORDER BY c.relname; + +-- Verify dependencies for table after creation. +EXECUTE dep_shdeps; +EXECUTE dep_relts; + +-- Move the table to ts_dep2, which should also move the PK index +ALTER TABLE dep_t SET TABLESPACE ts_dep2; +EXECUTE dep_shdeps; +EXECUTE dep_relts; + +-- Move all relations in ts_dep2 back to ts_dep1 +ALTER TABLE ALL IN TABLESPACE ts_dep2 SET TABLESPACE ts_dep1; +EXECUTE dep_shdeps; +EXECUTE dep_relts; + +-- Drop the table and verify dependencies are removed +DROP TABLE dep_t; +EXECUTE dep_shdeps; +EXECUTE dep_relts; + +-- Table rewrites: dropping and re-adding a primary key +CREATE TABLE dep_rw (a int PRIMARY KEY) TABLESPACE ts_dep1; +EXECUTE dep_shdeps; +EXECUTE dep_relts; + +-- Drop the primary key (rewrite) and verify the index dependency disappears +ALTER TABLE dep_rw DROP CONSTRAINT dep_rw_pkey; +EXECUTE dep_shdeps; +EXECUTE dep_relts; + +-- Re-add the primary key (rewrite) and verify the index dependency reappears +ALTER TABLE dep_rw ADD PRIMARY KEY(a); +EXECUTE dep_shdeps; +EXECUTE dep_relts; + +DROP TABLE dep_rw; +DROP TABLESPACE ts_dep2; +DROP TABLESPACE ts_dep1; diff --git a/src/test/regress/sql/yb.orig.tablet_metadata.sql b/src/test/regress/sql/yb.orig.tablet_metadata.sql new file mode 100644 index 0000000000000..2d814826154b1 --- /dev/null +++ b/src/test/regress/sql/yb.orig.tablet_metadata.sql @@ -0,0 +1,64 @@ +-- Test that data from multiple tables is returned +CREATE TABLE test_table_1 (k INT PRIMARY KEY, v INT) SPLIT INTO 2 TABLETS; +CREATE TABLE test_table_2 (k INT, v INT, PRIMARY KEY (k asc)); + +SELECT + relname, + db_name, + start_hash_code, + end_hash_code +FROM yb_tablet_metadata WHERE relname IN ('test_table_1', 'test_table_2') +ORDER BY start_hash_code NULLS FIRST; + +-- Test that we are able to join with yb_servers() +SELECT + ytm.relname, + ytm.db_name, + ytm.start_hash_code, + ytm.end_hash_code, + ys.cloud, + ys.region, + ys.zone +FROM yb_tablet_metadata ytm +JOIN yb_servers() ys + ON split_part(ytm.leader, ':', 1) = ys.host + AND split_part(ytm.leader, ':', 2)::int = ys.port +WHERE ytm.relname IN ('test_table_1', 'test_table_2') +ORDER BY ytm.start_hash_code NULLS FIRST; + +-- Test that data from multiple databases is returned +CREATE DATABASE test_db; +\c test_db + +CREATE TABLE test_table_1 (k INT PRIMARY KEY, v INT) SPLIT INTO 2 TABLETS; +CREATE TABLE test_table_2 (k INT, v INT, PRIMARY KEY (k asc)); + +SELECT + relname, + db_name, + start_hash_code, + end_hash_code +FROM yb_tablet_metadata +WHERE + relname IN ('test_table_1', 'test_table_2') + AND db_name IN ('test_db', 'yugabyte') +ORDER BY db_name, start_hash_code NULLS FIRST; + +-- Test that data from colocated tables is returned +CREATE DATABASE colocated_db WITH COLOCATION = true; +\c colocated_db + +CREATE TABLE test_table_1 (k INT PRIMARY KEY, v INT); +CREATE TABLE test_table_2 (k INT, v INT, PRIMARY KEY (k asc)); +CREATE TABLE test_table_3 (k INT PRIMARY KEY, v INT) WITH (COLOCATION = false) SPLIT INTO 2 TABLETS; + +SELECT + relname, + db_name, + start_hash_code, + end_hash_code +FROM yb_tablet_metadata +WHERE + relname IN ('test_table_1', 'test_table_2', 'test_table_3') + AND db_name = 'colocated_db' +ORDER BY start_hash_code NULLS FIRST; diff --git a/src/test/regress/sql/yb.orig.time.sql b/src/test/regress/sql/yb.orig.time.sql new file mode 100644 index 0000000000000..18b368662af57 --- /dev/null +++ b/src/test/regress/sql/yb.orig.time.sql @@ -0,0 +1,35 @@ +CREATE TABLE TIME_TBL (f1 time(2)); + +INSERT INTO TIME_TBL VALUES ('00:00'); +INSERT INTO TIME_TBL VALUES ('01:00'); +-- as of 7.4, timezone spec should be accepted and ignored +INSERT INTO TIME_TBL VALUES ('02:03 PST'); +INSERT INTO TIME_TBL VALUES ('11:59 EDT'); +INSERT INTO TIME_TBL VALUES ('12:00'); +INSERT INTO TIME_TBL VALUES ('12:01'); +INSERT INTO TIME_TBL VALUES ('23:59'); +INSERT INTO TIME_TBL VALUES ('11:59:59.99 PM'); +INSERT INTO TIME_TBL VALUES ('24:00'); +INSERT INTO TIME_TBL VALUES ('23:59:59.999'); + +INSERT INTO TIME_TBL VALUES ('2003-03-07 15:36:39 America/New_York'); +INSERT INTO TIME_TBL VALUES ('2003-07-07 15:36:39 America/New_York'); + +-- Out of range values +INSERT INTO TIME_TBL VALUES ('25:00'); +INSERT INTO TIME_TBL VALUES ('24:01'); + +-- ASC/DESC check +SELECT * FROM TIME_TBL ORDER BY f1 ASC; +SELECT * FROM TIME_TBL ORDER BY f1 DESC; + +-- +-- Test for PRIMARY KEY +-- + +CREATE TABLE time_data_with_pk(id TIME PRIMARY KEY, val time); +INSERT INTO time_data_with_pk VALUES ('01:01:01','01:11:21'); +INSERT INTO time_data_with_pk VALUES ('02:02:02','02:12:22'); +INSERT INTO time_data_with_pk VALUES ('03:03:03','03:13:23'); +SELECT * FROM time_data_with_pk ORDER BY id; +SELECT VAL FROM time_data_with_pk WHERE id = '02:02:02'; diff --git a/src/test/regress/sql/yb.orig.transactions.sql b/src/test/regress/sql/yb.orig.transactions.sql new file mode 100644 index 0000000000000..1e54f48987446 --- /dev/null +++ b/src/test/regress/sql/yb.orig.transactions.sql @@ -0,0 +1,34 @@ + +CREATE TABLE test (a int); + +-- Write operation in READ ONLY transaction +BEGIN; +SET TRANSACTION READ ONLY; +INSERT INTO test VALUES(1); -- fail +END; + +-- Write operation in aborted READ WRITE transaction +BEGIN; +SET TRANSACTION READ WRITE; +INSERT INTO test VALUES(1); +SELECT COUNT(*) FROM test; -- equals 1 +ABORT; + +SELECT COUNT(*) FROM test; -- equals 0 + +-- Write operation in committed READ WRITE transaction +BEGIN; +SET TRANSACTION READ WRITE; +INSERT INTO test VALUES(1); +SELECT COUNT(*) FROM test; -- equals 1 +END; + +SELECT COUNT(*) FROM test; -- equals 1 + +-- Check alternative syntax +START TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE; +INSERT INTO test VALUES(1); +SELECT COUNT(*) FROM test; -- equals 2 +ABORT; + +SELECT COUNT(*) FROM test; -- equals 1 diff --git a/src/test/regress/sql/yb.orig.triggers.sql b/src/test/regress/sql/yb.orig.triggers.sql new file mode 100644 index 0000000000000..0f38a19b9d21f --- /dev/null +++ b/src/test/regress/sql/yb.orig.triggers.sql @@ -0,0 +1,367 @@ +-- This test's output has temporary tables' schema names that contain the +-- tserver uuid, which can lead to unstable results. +-- Use replace_temp_schema_name to change the schema name to pg_temp_x so that +-- the result is stable. +select current_setting('data_directory') || '/describe.out' as desc_output_file +\gset +\set replace_temp_schema_name 'select regexp_replace(pg_read_file(:\'desc_output_file\'), \'pg_temp_.{32}_\\d+\', \'pg_temp_x\', \'g\')' + +-- Test views on temporary tables. +create temp table main_table(a int, b int); + +CREATE OR REPLACE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS ' +BEGIN + RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; + RETURN NULL; +END;'; + +CREATE TRIGGER before_upd_a_row_trig BEFORE UPDATE OF a ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_a_row'); +CREATE TRIGGER after_upd_b_row_trig AFTER UPDATE OF b ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_b_row'); +CREATE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row'); + +CREATE TRIGGER before_upd_a_stmt_trig BEFORE UPDATE OF a ON main_table +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_upd_a_stmt'); +CREATE TRIGGER after_upd_b_stmt_trig AFTER UPDATE OF b ON main_table +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_upd_b_stmt'); + +CREATE temp VIEW main_view AS SELECT a, b FROM main_table; + +-- VIEW trigger function +CREATE OR REPLACE FUNCTION view_trigger() RETURNS trigger +LANGUAGE plpgsql AS $$ +declare + argstr text := ''; +begin + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; + + raise notice '% % % % (%)', TG_RELNAME, TG_WHEN, TG_OP, TG_LEVEL, argstr; + + if TG_LEVEL = 'ROW' then + if TG_OP = 'INSERT' then + raise NOTICE 'NEW: %', NEW; + INSERT INTO main_table VALUES (NEW.a, NEW.b); + RETURN NEW; + end if; + + if TG_OP = 'UPDATE' then + raise NOTICE 'OLD: %, NEW: %', OLD, NEW; + UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b; + if NOT FOUND then RETURN NULL; end if; + RETURN NEW; + end if; + + if TG_OP = 'DELETE' then + raise NOTICE 'OLD: %', OLD; + DELETE FROM main_table WHERE a = OLD.a AND b = OLD.b; + if NOT FOUND then RETURN NULL; end if; + RETURN OLD; + end if; + end if; + + RETURN NULL; +end; +$$; + +-- Before row triggers aren't allowed on views +CREATE TRIGGER invalid_trig BEFORE INSERT ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row'); + +CREATE TRIGGER invalid_trig BEFORE UPDATE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row'); + +CREATE TRIGGER invalid_trig BEFORE DELETE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row'); + +-- After row triggers aren't allowed on views +CREATE TRIGGER invalid_trig AFTER INSERT ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row'); + +CREATE TRIGGER invalid_trig AFTER UPDATE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row'); + +CREATE TRIGGER invalid_trig AFTER DELETE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row'); + +-- Truncate triggers aren't allowed on views +CREATE TRIGGER invalid_trig BEFORE TRUNCATE ON main_view +EXECUTE PROCEDURE trigger_func('before_tru_row'); + +CREATE TRIGGER invalid_trig AFTER TRUNCATE ON main_view +EXECUTE PROCEDURE trigger_func('before_tru_row'); + +-- INSTEAD OF triggers aren't allowed on tables +CREATE TRIGGER invalid_trig INSTEAD OF INSERT ON main_table +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins'); + +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_table +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd'); + +CREATE TRIGGER invalid_trig INSTEAD OF DELETE ON main_table +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del'); + +-- Don't support WHEN clauses with INSTEAD OF triggers +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view +FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE view_trigger('instead_of_upd'); + +-- Don't support column-level INSTEAD OF triggers +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE OF a ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd'); + +-- Don't support statement-level INSTEAD OF triggers +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view +EXECUTE PROCEDURE view_trigger('instead_of_upd'); + +-- Valid INSTEAD OF triggers +CREATE TRIGGER instead_of_insert_trig INSTEAD OF INSERT ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins'); + +CREATE TRIGGER instead_of_update_trig INSTEAD OF UPDATE ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd'); + +CREATE TRIGGER instead_of_delete_trig INSTEAD OF DELETE ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del'); + +-- Valid BEFORE statement VIEW triggers +CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_ins_stmt'); + +CREATE TRIGGER before_upd_stmt_trig BEFORE UPDATE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_upd_stmt'); + +CREATE TRIGGER before_del_stmt_trig BEFORE DELETE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_del_stmt'); + +-- Valid AFTER statement VIEW triggers +CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_ins_stmt'); + +CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_upd_stmt'); + +CREATE TRIGGER after_del_stmt_trig AFTER DELETE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_del_stmt'); + +\set QUIET false + +-- Insert into view using trigger + +INSERT INTO main_view VALUES (20, 30); +INSERT INTO main_view VALUES (21, 31) RETURNING a, b; + +-- Table trigger will prevent updates +UPDATE main_view SET b = 31 WHERE a = 20; +UPDATE main_view SET b = 32 WHERE a = 21 AND b = 31 RETURNING a, b; + +-- Remove table trigger to allow updates +DROP TRIGGER before_upd_a_row_trig ON main_table; +UPDATE main_view SET b = 31 WHERE a = 20; +UPDATE main_view SET b = 32 WHERE a = 21 AND b = 31 RETURNING a, b; +SELECT * from main_view order by a, b; + +-- Before and after stmt triggers should fire even when no rows are affected +UPDATE main_view SET b = 0 WHERE false; + +-- Delete from view using trigger +DELETE FROM main_view WHERE a IN (10,20); +DELETE FROM main_view WHERE a = 31 RETURNING a, b; +SELECT * from main_view order by a, b; + +\set QUIET true + +-- Describe view should list triggers +\o :desc_output_file +\d main_view +\o +:replace_temp_schema_name; + +-- Test dropping view triggers +DROP TRIGGER instead_of_insert_trig ON main_view; +DROP TRIGGER instead_of_delete_trig ON main_view; +\o :desc_output_file +\d main_view +\o +:replace_temp_schema_name; + +-- Test alter (rename) triggers +ALTER TRIGGER after_ins_stmt_trig ON main_view RENAME TO after_ins_stmt_trig_new_name; +\o :desc_output_file +\d main_view +\o +:replace_temp_schema_name; + +DROP VIEW main_view; +DROP TABLE main_table; + +-- Test self-referential triggers (on non-temp table). +-- Note: The YugaByte behavior in this case is different from vanilla Postgres. +-- * Vanilla Postgres would throw an error when a row is modified by a before-row trigger +-- and also by the main statement itself. +-- * However, in YugaByte this is allowed and both changes are applied to the row in the +-- expected order (i.e. the before trigger is applied first). This includes the case when +-- the statement (latter) change would override the changes made by the before trigger +-- For example, in the delete example below, the parent's row(s) will be deleted by the main statement +-- after being updated (to decrement nchildren) by the before trigger (of the child row). +create table self_ref_trigger ( + id int primary key, + parent int references self_ref_trigger, + data text, + nchildren int not null default 0 +); + +create function self_ref_trigger_ins_func() + returns trigger language plpgsql as +$$ +begin + if new.parent is not null then + update self_ref_trigger set nchildren = nchildren + 1 + where id = new.parent; + end if; + return new; +end; +$$; +create trigger self_ref_trigger_ins_trig before insert on self_ref_trigger + for each row execute procedure self_ref_trigger_ins_func(); + +create function self_ref_trigger_del_func() + returns trigger language plpgsql as +$$ +begin + if old.parent is not null then + update self_ref_trigger set nchildren = nchildren - 1 + where id = old.parent; + end if; + return old; +end; +$$; +create trigger self_ref_trigger_del_trig before delete on self_ref_trigger + for each row execute procedure self_ref_trigger_del_func(); + +create index nonconcurrently on self_ref_trigger (parent asc); +create index nonconcurrently on self_ref_trigger (data asc); +create index nonconcurrently on self_ref_trigger (nchildren asc); + +insert into self_ref_trigger values (1, null, 'root'); +insert into self_ref_trigger values (2, 1, 'root child A'); +insert into self_ref_trigger values (3, 1, 'root child B'); +insert into self_ref_trigger values (4, 2, 'grandchild 1'); +insert into self_ref_trigger values (5, 3, 'grandchild 2'); + +update self_ref_trigger set data = 'root!' where id = 1; +update self_ref_trigger set nchildren = nchildren + 1; +update self_ref_trigger set nchildren = nchildren - 1; + +-- Check that all indexes are consistent with the table (see #20648). +\set ordering '' +\set query 'SELECT * FROM (select * from self_ref_trigger :ordering LIMIT ALL) ybview ORDER BY id' +\set run 'EXPLAIN (costs off) :query; :query' +:run; +\set ordering 'order by parent' +:run; +\set ordering 'order by data' +:run; +\set ordering 'order by nchildren' +:run; + +delete from self_ref_trigger where id in (2, 4); + +\set ordering '' +:run; + +delete from self_ref_trigger; + +:run; + +drop table self_ref_trigger; +drop function self_ref_trigger_ins_func(); +drop function self_ref_trigger_del_func(); + +CREATE TABLE incremental_key(h INT, r INT, v1 INT, v2 INT, PRIMARY KEY(h, r ASC)); + +CREATE OR REPLACE FUNCTION increment_key() RETURNS TRIGGER +LANGUAGE PLPGSQL AS $$ +BEGIN + NEW.r = NEW.r + 1; + RETURN NEW; +END; +$$; + +CREATE TRIGGER increment_key_trigger BEFORE UPDATE ON incremental_key +FOR EACH ROW EXECUTE PROCEDURE increment_key(); + +INSERT INTO incremental_key VALUES(1, 1, 1, 1); +SELECT * FROM incremental_key; +UPDATE incremental_key SET v1 = 10 WHERE h = 1; +SELECT * FROM incremental_key; + +UPDATE incremental_key SET v1 = 10 WHERE yb_hash_code(h) = yb_hash_code(1); +SELECT * FROM incremental_key; + +DROP TABLE incremental_key; +DROP FUNCTION increment_key; + +CREATE TABLE incremental_value(h INT, r INT, v1 INT, v2 INT, PRIMARY KEY(h, r ASC)); + +CREATE OR REPLACE FUNCTION increment_value() RETURNS TRIGGER +LANGUAGE PLPGSQL AS $$ +BEGIN + NEW.v2 = NEW.v2 + 1; + RETURN NEW; +END; +$$; + +CREATE TRIGGER increment_value_trigger BEFORE UPDATE ON incremental_value +FOR EACH ROW EXECUTE PROCEDURE increment_value(); + +INSERT INTO incremental_value VALUES(1, 1, 1, 1); +SELECT * FROM incremental_value; +UPDATE incremental_value SET v1 = 10 WHERE h = 1; +SELECT * FROM incremental_value; + +DROP TABLE incremental_value; +DROP FUNCTION increment_value; + +-- Verify yb_db_admin can alter triggers on tables it does not own +CREATE TABLE foo(a INT); +CREATE TABLE bar(b INT); +CREATE OR REPLACE FUNCTION some_func() RETURNS TRIGGER +LANGUAGE PLPGSQL AS $$ +BEGIN + INSERT INTO bar(b) VALUES (1); + RETURN NEW; +END; +$$; +SET SESSION AUTHORIZATION yb_db_admin; +-- Alter trigger +CREATE TRIGGER example_trigger AFTER INSERT ON foo FOR EACH ROW EXECUTE PROCEDURE some_func(); +ALTER TRIGGER example_trigger ON foo RENAME TO example_trigger_new; +DROP TRIGGER example_trigger_new ON foo; +-- Recreate trigger +CREATE TRIGGER example_trigger AFTER INSERT ON foo FOR EACH ROW EXECUTE PROCEDURE some_func(); +RESET SESSION AUTHORIZATION; +-- Verify trigger was run +INSERT INTO foo VALUES (0); +SELECT * from bar; +-- cleanup +DROP TABLE foo; +DROP TABLE bar; + +-- Verify yb_db_admin can alter system triggers +CREATE TABLE foo(a INT UNIQUE); +CREATE TABLE bar(b INT); +ALTER TABLE bar ADD CONSTRAINT baz FOREIGN KEY (b) REFERENCES foo(a); +SET SESSION AUTHORIZATION yb_db_admin; +ALTER TABLE bar ENABLE TRIGGER ALL; +ALTER TABLE bar DISABLE TRIGGER ALL; +ALTER TABLE pg_shdepend ENABLE TRIGGER ALL; +-- cleanup +DROP TABLE bar; +DROP TABLE foo; +RESET SESSION AUTHORIZATION diff --git a/src/test/regress/sql/yb.orig.truncate.sql b/src/test/regress/sql/yb.orig.truncate.sql new file mode 100644 index 0000000000000..d761f32dd15ee --- /dev/null +++ b/src/test/regress/sql/yb.orig.truncate.sql @@ -0,0 +1,62 @@ +CREATE TABLE test_truncate (k int generated by default as identity, v int); + +-- test TRUNCATE ... CONTINUE IDENTITY +INSERT INTO test_truncate(v) VALUES (101), (102), (103); +SELECT * FROM test_truncate ORDER BY k; + +TRUNCATE TABLE test_truncate CONTINUE IDENTITY; +SELECT * FROM test_truncate ORDER BY k; + +INSERT INTO test_truncate(v) VALUES (101), (102), (103); +SELECT * FROM test_truncate ORDER BY k; + +-- test TRUNCATE ... RESTART IDENTITY +TRUNCATE TABLE test_truncate RESTART IDENTITY; +SELECT * FROM test_truncate ORDER BY k; + +INSERT INTO test_truncate(v) VALUES (101), (102), (103); +SELECT * FROM test_truncate ORDER BY k; + +DROP TABLE test_truncate; + +-- test TRUNCATE on partitioned tables +CREATE TABLE test_truncate (k int, v int, PRIMARY KEY (k ASC)) PARTITION BY RANGE (k); +CREATE TABLE test_truncate_1 PARTITION OF test_truncate FOR VALUES FROM (1) TO (5); +CREATE TABLE test_truncate_2 PARTITION OF test_truncate FOR VALUES FROM (5) TO (11); +INSERT INTO test_truncate(k, v) VALUES (generate_series(1, 10), generate_series(1, 10)); +TRUNCATE test_truncate; +SELECT * FROM test_truncate; +INSERT INTO test_truncate(k, v) VALUES (generate_series(1, 10), generate_series(1, 10)); +SELECT * FROM test_truncate; +DROP TABLE test_truncate; + +-- test TRUNCATE on colocated tables +CREATE DATABASE mydb WITH colocation=true; +\c mydb +CREATE TABLE test_truncate (k int, v int); +INSERT INTO test_truncate(k, v) VALUES (1, 1), (2, 2), (3, 3); +TRUNCATE test_truncate; +SELECT * FROM test_truncate; +DROP TABLE test_truncate; + +-- test TRUNCATE CASCADE +CREATE TABLE test_truncate (k int PRIMARY KEY, v int); +CREATE TABLE test_truncate_child (k int, v int, FOREIGN KEY (k) REFERENCES test_truncate(k)); +INSERT INTO test_truncate(k, v) VALUES (1, 1), (2, 2), (3, 3); +INSERT INTO test_truncate_child(k, v) VALUES (1, 1), (2, 2), (3, 3); +SELECT * FROM test_truncate_child ORDER BY k; +TRUNCATE test_truncate CASCADE; +SELECT * FROM test_truncate; +SELECT * FROM test_truncate_child; + +-- test TRUNCATE on system tables. +TRUNCATE pg_extension; -- should fail. +SET allow_system_table_mods = on; +TRUNCATE pg_extension; +SELECT * FROM pg_extension; + +-- test TRUNCATE on temp tables. +CREATE TEMP TABLE test_truncate_temp (k int, v int); +INSERT INTO test_truncate_temp(k, v) VALUES (1, 1), (2, 2), (3, 3); +TRUNCATE test_truncate_temp; +SELECT * FROM test_truncate_temp; diff --git a/src/test/regress/sql/yb.orig.ts_remote_filter.sql b/src/test/regress/sql/yb.orig.ts_remote_filter.sql new file mode 100644 index 0000000000000..94275bea81d38 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ts_remote_filter.sql @@ -0,0 +1,64 @@ +CREATE TABLE test_remote_filter(a INT, t TEXT, ts TSVECTOR, tq TSQUERY, jb JSONB, js JSON); + +INSERT INTO test_remote_filter VALUES + (1, 'fat', to_tsvector('fat cat'), to_tsquery('cat'), '{"a": 1}', '{"b": 1}'), + (2, 'bat', to_tsvector('fat cat'), to_tsquery('cat'), '{"a": 2}', '{"b": 2}'), + (3, 'cat', to_tsvector('fat cat'), to_tsquery('cat'), '{"a": 3}', '{"b": 3}'), + (4, 'hat', to_tsvector('fat cat'), to_tsquery('cat'), '{"a": 4}', '{"b": 4}'); + +-- test pushdown of col @@ tsvector +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE tq @@ 'cat'::tsvector; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE tq @@ ts; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE tq @@ to_tsvector(t); +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE tq @@ to_tsvector(jb); +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE tq @@ to_tsvector(js); + +-- test pushdown of const @@ tsvector +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE 'cat'::tsquery @@ 'cat'::tsvector; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE 'cat'::tsquery @@ ts; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE 'cat'::tsquery @@ to_tsvector(t); +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE 'cat'::tsquery @@ to_tsvector(jb); +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE 'cat'::tsquery @@ to_tsvector(js); +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE to_tsquery('simple', 'cat') @@ 'cat'::tsvector; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE to_tsquery('simple', 'cat') @@ ts; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE to_tsquery('simple', 'cat') @@ to_tsvector(t); +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE to_tsquery('simple', 'cat') @@ to_tsvector(jb); +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE to_tsquery('simple', 'cat') @@ to_tsvector(js); + +-- test pushdown of to_tsquery @@ col +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE to_tsquery(t) @@ ts; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE plainto_tsquery(t) @@ ts; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE phraseto_tsquery(t) @@ ts; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE websearch_to_tsquery(t) @@ ts; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE to_tsquery('simple', t) @@ ts; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE plainto_tsquery('simple', t) @@ ts; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE phraseto_tsquery('simple', t) @@ ts; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE websearch_to_tsquery('simple', t) @@ ts; + +-- test pushdown of to_tsquery @@ const +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE to_tsquery(t) @@ 'cat'::tsvector; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE plainto_tsquery(t) @@ 'cat'::tsvector; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE phraseto_tsquery(t) @@ 'cat'::tsvector; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE websearch_to_tsquery(t) @@ 'cat'::tsvector; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE to_tsquery('simple', t) @@ 'cat'::tsvector; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE plainto_tsquery('simple', t) @@ 'cat'::tsvector; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE phraseto_tsquery('simple', t) @@ 'cat'::tsvector; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE websearch_to_tsquery('simple', t) @@ 'cat'::tsvector; + +-- test pushdown of ts_headline(col, col) +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE ts_headline(t, tq) = t; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE ts_headline(jb, tq)->>'a' = '1'; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE ts_headline(js, tq)->>'b' = '1'; + +-- test pushdown of ts_headline(const, const) +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE ts_headline('fat', 'cat'::tsquery) = t; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE ts_headline('{"a": 1}'::jsonb, 'cat'::tsquery)->>'a' = '1'; +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_remote_filter WHERE ts_headline('{"b": 1}'::json, 'cat'::tsquery)->>'b' = '1'; + +-- test function +CREATE OR REPLACE FUNCTION fn_test_pushdown(TEXT) +RETURNS SETOF test_remote_filter AS $sql$ + SELECT * FROM test_remote_filter WHERE to_tsquery('simple',$1) @@ ts; +$sql$ LANGUAGE SQL; + +EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM fn_test_pushdown('cat'); diff --git a/src/test/regress/sql/yb.orig.tsdicts.sql b/src/test/regress/sql/yb.orig.tsdicts.sql new file mode 100644 index 0000000000000..4d2e5bf701f9b --- /dev/null +++ b/src/test/regress/sql/yb.orig.tsdicts.sql @@ -0,0 +1,29 @@ +-- +-- YB non-ported tests on text search configurations and dictionaries. +-- + +CREATE TEXT SEARCH CONFIGURATION ybtscfg (COPY=english); +\dF+ ybtscfg + +-- Test ALTER TEXT SEARCH CONFIGURATION syntax variations that aren't tested in +-- upstream postgres tests. +SELECT to_tsvector('ybtscfg', 'cafe 123'); +ALTER TEXT SEARCH CONFIGURATION ybtscfg DROP MAPPING FOR int, uint; +SELECT to_tsvector('ybtscfg', 'cafe 123'); +ALTER TEXT SEARCH CONFIGURATION ybtscfg DROP MAPPING FOR int, uint; +ALTER TEXT SEARCH CONFIGURATION ybtscfg DROP MAPPING IF EXISTS FOR int, uint; +\dF+ ybtscfg + +ALTER TEXT SEARCH CONFIGURATION ybtscfg ADD MAPPING FOR uint + WITH danish_stem; +SELECT to_tsvector('ybtscfg', 'cafe 123'); +ALTER TEXT SEARCH CONFIGURATION ybtscfg ADD MAPPING FOR uint + WITH danish_stem; +\dF+ ybtscfg + +ALTER TEXT SEARCH CONFIGURATION ybtscfg ALTER MAPPING FOR asciiword, int, uint + REPLACE english_stem WITH french_stem; +SELECT to_tsvector('ybtscfg', 'cafe 123'); +ALTER TEXT SEARCH CONFIGURATION ybtscfg ALTER MAPPING FOR asciiword, int, uint + REPLACE english_stem WITH french_stem; +\dF+ ybtscfg diff --git a/src/test/regress/sql/yb.orig.update_large.sql b/src/test/regress/sql/yb.orig.update_large.sql new file mode 100644 index 0000000000000..3686a56a8d368 --- /dev/null +++ b/src/test/regress/sql/yb.orig.update_large.sql @@ -0,0 +1,5 @@ +-- Check update operations on very large table doesn't cause OOM +CREATE TABLE test(k INT PRIMARY KEY, v1 TEXT, v2 TEXT); +INSERT INTO test SELECT i, md5(random()::text), md5(random()::text) from generate_series(1, 500000) AS i; +UPDATE test SET v1 = v2; +DELETE FROM test; diff --git a/src/test/regress/sql/yb.orig.update_optimize_base.sql b/src/test/regress/sql/yb.orig.update_optimize_base.sql new file mode 100644 index 0000000000000..acb9dc0ebe0d6 --- /dev/null +++ b/src/test/regress/sql/yb.orig.update_optimize_base.sql @@ -0,0 +1,475 @@ +SET yb_fetch_row_limit TO 1024; +SET yb_update_num_cols_to_compare TO 50; +SET yb_update_max_cols_size_to_compare TO 10240; + +-- This test requires the t-server preview/auto flag 'ysql_yb_update_optimizations_infra' to be enabled. + +-- CREATE functions that can be triggered upon update to modify various columns +CREATE OR REPLACE FUNCTION no_update() RETURNS TRIGGER +LANGUAGE PLPGSQL AS $$ +BEGIN + RAISE NOTICE 'Trigger "no_update" invoked'; + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION increment_v1() RETURNS TRIGGER +LANGUAGE PLPGSQL AS $$ +BEGIN + NEW.v1 = NEW.v1 + 1; + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION increment_v3() RETURNS TRIGGER +LANGUAGE PLPGSQL AS $$ +BEGIN + NEW.v3 = NEW.v3 + 1; + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION update_all() RETURNS TRIGGER +LANGUAGE PLPGSQL AS $$ +BEGIN + NEW.h = NEW.h + 1024; + NEW.v1 = NEW.v1 + 1024; + NEW.v2 = NEW.v2 + 1; + RETURN NEW; +END; +$$; + +-- CREATE a simple table with only a primary key +DROP TABLE IF EXISTS pkey_only_table; +CREATE TABLE pkey_only_table (h INT PRIMARY KEY, v INT); +INSERT INTO pkey_only_table (SELECT i, i FROM generate_series(1, 1024) AS i); + +-- A simple point update without involving the primary key +-- This query does not go through the distributed transaction path +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET v = v + 1 WHERE h = 1; + +-- Point updates that include the primary key in the targetlist +-- These queries do not go through the distributed transaction path either +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET h = v - 1, v = v + 1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET h = h, v = v + 1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET h = 1, v = v + 1 WHERE h = 1; + +-- Queries affecting a range of rows +-- Since the primary key is not specified in its entirety, these use the distributed txns +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET h = h, v = v + 1 WHERE h > 10 AND h < 15; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET h = v, v = v + 1 WHERE h > 20 AND h < 25; + +-- Query that updates the primary key. This should involve multiple flushes +-- over a distributed transaction. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET h = h + 1024, v = v + 1 WHERE h < 5; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET h = h + 1024, v = v + 1 WHERE h < 2000; + +DROP TABLE pkey_only_table; + +-- CREATE a table with no primary key, but having a secondary indexes. +DROP TABLE IF EXISTS secindex_only_table; +CREATE TABLE secindex_only_table (v1 INT, v2 INT, v3 INT); +INSERT INTO secindex_only_table (SELECT i, i, i FROM generate_series(1, 1024) AS i); + +-- Add an index on v1 +CREATE INDEX NONCONCURRENTLY secindex_only_table_v1 ON secindex_only_table (v1); + +-- Updates not involving the secondary index should not have multiple flushes. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v3 = v2 + 1 WHERE v1 = 1; +-- This specifically tests the case where there is no overlap between the index +-- and columns that are potentially modified. The index should be added to a +-- skip list without further consideration +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2, v3 = v3 WHERE v2 = 1; + +-- Point updates that include the column referenced by the index in the targetlist. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1, v3 = v3 + 1 WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v2, v3 = v3 + 1 WHERE v1 = 1; + +-- Queries affecting a range of rows +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1, v3 = v3 + 1 WHERE v1 < 5; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v2, v3 = v3 + 1 WHERE v1 < 5; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1, v3 = v3 + 1 WHERE v2 < 5; + +-- Special case where no column is affected. We should still see a flush in order +-- to acquire necessary row lock on the main table. This is similar to SELECT FOR UPDATEs. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1 WHERE v1 = 5; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1 WHERE v1 < 5; + +-- Add a second index to the table which is a multi-column index +CREATE INDEX NONCONCURRENTLY secindex_only_table_v1_v2 ON secindex_only_table (v1, v2); + +-- Queries that affect only one of the two indexes +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2, v3 = v3 + 1 WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2, v3 = v3 WHERE v2 = 1; + +-- Same as above but queries that actually modify one of the indexes exclusively. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2 + 1, v3 = v3 WHERE v1 = 15; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2 + 1, v3 = v3 WHERE v2 = 16; + +-- Queries that cover both indexes. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1, v3 = v3 WHERE v1 = 15; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1, v2 = v2 + 1, v3 = v3 WHERE v1 = 15; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1 + 1, v2 = v2 + 1, v3 = v3 WHERE v1 = 15; + +-- Add a simple trigger to the table. +CREATE TRIGGER secindex_only_table_no_update BEFORE UPDATE ON secindex_only_table FOR EACH ROW EXECUTE FUNCTION no_update(); + +-- Repeat the above queries to validate that the number of flushes do not change. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2, v3 = v3 + 1 WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2, v3 = v3 WHERE v2 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2 + 1, v3 = v3 WHERE v1 = 25; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1, v2 = v2 + 1, v3 = v3 WHERE v1 = 25; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1 + 1, v2 = v2 + 1, v3 = v3 WHERE v1 = 25; + +-- Add a trigger that modifies v3 (no indexes on it) +CREATE TRIGGER secindex_only_table_increment_v3 BEFORE UPDATE ON secindex_only_table FOR EACH ROW EXECUTE FUNCTION increment_v3(); +-- Read the values corresponding to v1 = 1. +SELECT * FROM secindex_only_table WHERE v1 = 1; + +-- Repeat a subset of the above queries to validate that no extra flushes are required. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2, v3 = v3 + 1 WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2 + 1, v3 = v3 WHERE v1 = 35; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1, v2 = v2 + 1, v3 = v3 WHERE v1 = 35; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1 + 1, v2 = v2 + 1, v3 = v3 WHERE v1 = 35; + +-- Read the values corresponding to v1 = 1 again to validate that v3 is incremented twice: +-- once by the query, once by the trigger. +SELECT * FROM secindex_only_table WHERE v1 = 1; + +-- Add a trigger that modifies v1 (has two indexes on it) +CREATE TRIGGER secindex_only_table_increment_v1 BEFORE UPDATE ON secindex_only_table FOR EACH ROW EXECUTE FUNCTION increment_v1(); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2, v3 = v3 + 1 WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2, v3 = v3 WHERE v2 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1, v2 = v2 + 1, v3 = v3 WHERE v1 = 45; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1 + 1, v2 = v2 + 1, v3 = v3 WHERE v1 = 55; + +-- Read the values to confirm that there is/are: +-- No row corresponding to v1 = 45 +-- Two rows for v1 = 46 +-- One row for v1 = 47 +-- No rows for v1 = 55 +-- One row for v1 = 46 +-- Two rows for v1 = 47 +SELECT * FROM secindex_only_table WHERE v1 IN (45, 46, 47, 55, 56, 57) ORDER BY v1, v2; + +-- Query that updates a range of values between 61 and 70. +-- TODO: Validate this +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v2 = v2 + 1, v3 = v3 WHERE v1 > 60 AND v1 <= 70; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1 + 1, v3 = v3 + 1 WHERE v1 > 70 AND v1 <= 80; + +DROP TABLE secindex_only_table; + +-- CREATE a table with no primary key or secondary indexes. +DROP TABLE IF EXISTS no_index_table; +CREATE TABLE no_index_table (h INT, v INT); +INSERT INTO no_index_table (SELECT i, i FROM generate_series(1, 1024) AS i); + +-- Point update queries. Irrespective of whether the columns in the targetlist +-- are modified, we should see flushes updating the row. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE no_index_table SET h = h, v = v WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE no_index_table SET h = h WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE no_index_table SET h = h + 1 WHERE h = 10; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE no_index_table SET v = v + 1 WHERE v = 10; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE no_index_table SET h = v + 1, v = h + 1 WHERE v = 20; + +-- CREATE a hierarchy of tables of the order a <-- b <-- c where 'a' is the base +-- table, 'b' references 'a' and is a reference to 'c', and so on. +DROP TABLE IF EXISTS a_test; +DROP TABLE IF EXISTS b1_test; +DROP TABLE IF EXISTS b2_test; +DROP TABLE IF EXISTS c1_test; +DROP TABLE IF EXISTS c2_test; + +CREATE TABLE a_test (h INT PRIMARY KEY, v1 INT UNIQUE, v2 INT); +CREATE INDEX NONCONCURRENTLY a_v1 ON a_test (v1); +CREATE TABLE b1_test (h INT PRIMARY KEY REFERENCES a_test (h), v1 INT UNIQUE REFERENCES a_test (v1), v2 INT); +CREATE INDEX NONCONCURRENTLY b1_v1 ON b1_test (v1); +CREATE TABLE b2_test (h INT PRIMARY KEY REFERENCES a_test (v1), v1 INT REFERENCES a_test (h), v2 INT); +CREATE INDEX NONCONCURRENTLY b2_v1 ON b2_test (v1); + +INSERT INTO a_test (SELECT i, i, i FROM generate_series(1, 1024) AS i); +INSERT INTO b1_test (SELECT i, i, i FROM generate_series(1, 1024) AS i); +INSERT INTO b2_test (SELECT i, i, i FROM generate_series(1, 1024) AS i); + +-- Point update queries on table 'a'. Should skip 'referenced-by' constraints on +-- b-level tables. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE a_test SET h = h, v1 = h, v2 = v2 + 1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE a_test SET v1 = v1, v2 = v2 + 1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE a_test SET v1 = v1, v2 = v2 + 1 WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE a_test SET v1 = v1, v2 = v2 + 1 WHERE v1 = h AND h = 2; + +-- Same as above but update the rows to validate 'referenced-by' constraints. +INSERT INTO a_test (SELECT i, i, i FROM generate_series(1025, 1034) AS i); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE a_test SET h = h, v1 = v1 + 1 WHERE h = 1034; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE a_test SET h = h + 1, v1 = v1 + 1 WHERE h = 1034; + +-- +-- The following sections tests updates when the update optimization is turned +-- off. This is to prevent regressions. +-- +SET yb_update_num_cols_to_compare TO 0; +DROP TABLE IF EXISTS base_table1; +CREATE TABLE base_table1 (k INT PRIMARY KEY, v1 INT, v2 INT); +INSERT INTO base_table1 (SELECT i, i, i FROM generate_series(1, 10) AS i); + +-- This query below should follow the transaction fast path. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET v1 = v1 + 1 WHERE k = 1; + +-- The queries below should use the distributed transaction path. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET k = v1 - 1, v1 = v1 + 1 WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET k = 1, v2 = v2 + 1 WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET k = k, v1 = v1 + 1 WHERE k > 5 AND k < 15; + +-- Adding a trigger should automatically make the table use the distributed +-- transaction path. +CREATE TRIGGER base_table1_no_update BEFORE UPDATE ON base_table1 FOR EACH ROW EXECUTE FUNCTION no_update(); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET v1 = v1 + 1 WHERE k = 1; +DROP TRIGGER base_table1_no_update ON base_table1; + +-- Add a trigger that updates values of v1. +CREATE TRIGGER base_table1_increment_v1 BEFORE UPDATE ON base_table1 FOR EACH ROW EXECUTE FUNCTION increment_v1(); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET v2 = v2 + 1 WHERE k = 1; +-- v1 should be updated twice +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET v1 = v1 + 1 WHERE k = 1; +DROP TRIGGER base_table1_increment_v1 ON base_table1; + +-- Drop the trigger and adding a secondary index should produce the same result. +CREATE INDEX NONCONCURRENTLY base_table1_v1 ON base_table1 (v1); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET k = 1, v2 = v2 + 1 WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET v1 = v1 + 1 WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET v1 = v1 WHERE k = 1; + +-- Validate that the values have indeed been updated. +SELECT * FROM base_table1 WHERE k = 1; +-- Use an index-only scan to validate that the index has been updated. +SELECT v1 FROM base_table1 WHERE k = 1; + +DROP TABLE base_table1; + +-- Turn the GUC back on for the remaining tests +SET yb_update_num_cols_to_compare TO 50; +SET yb_update_max_cols_size_to_compare TO 10240; + +-- +-- The following section contains a set of sanity tests for colocated tables/databases. +-- +CREATE DATABASE codb colocation = true; +\c codb +SET yb_fetch_row_limit TO 1024; +SET yb_update_num_cols_to_compare TO 50; +SET yb_update_max_cols_size_to_compare TO 10240; + +DROP TABLE IF EXISTS base_table1; +CREATE TABLE base_table1 (k INT PRIMARY KEY, v1 INT, v2 INT) WITH (colocation = true); +INSERT INTO base_table1 (SELECT i, i, i FROM generate_series(1, 10) AS i); + +-- This query below should follow the transaction fast path. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET v1 = v1 + 1 WHERE k = 1; + +-- The queries below should use the distributed transaction path. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET k = v1 - 1, v1 = v1 + 1 WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET k = 1, v2 = v2 + 1 WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE base_table1 SET k = k, v1 = v1 + 1 WHERE k > 5 AND k < 15; + +DROP TABLE base_table1; + +\c yugabyte +SET yb_fetch_row_limit TO 1024; +SET yb_update_num_cols_to_compare TO 50; +SET yb_update_max_cols_size_to_compare TO 10240; + +-- The queries below test self-referential foreign keys +DROP TABLE IF EXISTS ancestry; +CREATE TABLE ancestry (key TEXT PRIMARY KEY, value TEXT, parent TEXT, gparent TEXT); +ALTER TABLE ancestry ADD CONSTRAINT fk_self_parent_to_key FOREIGN KEY (parent) REFERENCES ancestry (key); +ALTER TABLE ancestry ADD CONSTRAINT fk_self_gparent_to_key FOREIGN KEY (gparent) REFERENCES ancestry (key); + +-- Insert the family tree +INSERT INTO ancestry VALUES ('root', 'Dave', NULL, NULL); +INSERT INTO ancestry VALUES ('ggparent1', 'Adam', 'root', NULL); +INSERT INTO ancestry VALUES ('ggparent2', 'Barry', 'root', NULL); +INSERT INTO ancestry VALUES ('gparent1', 'Claudia', 'ggparent1', 'root'); +INSERT INTO ancestry VALUES ('gparent2', 'Donatello', 'ggparent1', 'root'); +INSERT INTO ancestry VALUES ('gparent3', 'Eddie', 'ggparent2', 'root'); +INSERT INTO ancestry VALUES ('parent1', 'Farooq', 'gparent1', 'ggparent1'); +INSERT INTO ancestry VALUES ('parent2', 'Govind', 'gparent1', 'ggparent2'); +INSERT INTO ancestry VALUES ('child1', 'Harry', 'parent1', 'gparent1'); +INSERT INTO ancestry VALUES ('child2', 'Ivan', 'parent2', 'gparent1'); + +-- Query modifying the value column should not trigger referential constraint check. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE ancestry SET value = 'David' WHERE value = 'Dave'; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE ancestry SET key = 'root', value = 'David' WHERE key = 'root'; + +-- Query modifying one's parent should trigger trigger the referential +-- constraint in only one direction to see if the parent exists. Grandparent +-- checks in both directions should be skipped. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE ancestry SET parent = 'ggparent2', gparent = 'root' WHERE value = 'Donatello'; +-- This query should throw an error as the parent doesn't exist +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE ancestry SET parent = 'ggparent23', gparent = 'root' WHERE key = 'gparent2'; +-- Query modifying the key to the row should trigger checks in only the children +-- and grandchildren. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE ancestry SET key = 'gparent30' WHERE value = 'Eddie'; +-- Same query, no modification, no triggers +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE ancestry SET key = 'gparent30' WHERE value = 'Eddie'; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE ancestry SET key = 'gparent3' WHERE key = 'gparent30'; + +DROP TABLE ancestry; + +--- +--- The following sections contains sanity test for prepared statements and plan caching +--- +CREATE TABLE IF NOT EXISTS t_parent(k INT UNIQUE); +CREATE TABLE IF NOT EXISTS t_simple(k INT PRIMARY KEY, v1 INT REFERENCES t_parent(k), v2 INT, v3 INT, v4 INT); + +CREATE INDEX NONCONCURRENTLY t_simple_v1 ON t_simple (v1); +CREATE INDEX NONCONCURRENTLY t_simple_v2 ON t_simple (v2); +INSERT INTO t_parent (SELECT i FROM generate_series(1, 100) AS i); +INSERT INTO t_simple (SELECT i, i, i, i, i FROM generate_series(1, 10) AS i); + +-- Test that adding an index on a column updated by the query causes replanning +-- and recomputation of columns to be compared. +PREPARE updateplan1 (INT, INT, INT, INT, INT) AS UPDATE t_simple SET v1 = $2, v2 = $3, v3 = $4, v4 = $5 WHERE k = $1; +-- Step 1: Execute query 5 times to ensure that the plan is cached. +EXECUTE updateplan1 (1, 1, 1, 1, 1); +EXECUTE updateplan1 (1, 1, 1, 1, 1); +EXECUTE updateplan1 (1, 1, 1, 1, 1); +EXECUTE updateplan1 (1, 1, 1, 1, 1); +EXECUTE updateplan1 (1, 1, 1, 1, 1); + +EXPLAIN (ANALYZE, DIST, COSTS OFF) EXECUTE updateplan1 (1, 1, 1, 1, 1); +-- Step 2: Add an index and do not update the column on which the index is defined. +-- If the cached plan is used, additional index write requests will not be be +-- optimized as v3 does not have any other entities on it. +CREATE INDEX NONCONCURRENTLY t_simple_v3 ON t_simple (v3); +EXPLAIN (ANALYZE, DIST, COSTS OFF) EXECUTE updateplan1 (1, 1, 1, 1, 1); + + +-- Test that dropping an index on a column updated by the query causes replanning +-- and recomputation of columns to be compared. +-- Step 1: Cache the previous plan. +EXECUTE updateplan1 (1, 1, 1, 1, 1); +EXECUTE updateplan1 (1, 1, 1, 1, 1); +EXECUTE updateplan1 (1, 1, 1, 1, 1); +EXECUTE updateplan1 (1, 1, 1, 1, 1); + +-- Step 2: Drop the index and update the column on which the index was defined. +-- If the cached plan is used, we will run out of budget to compare v3 resulting +-- in additional index write requests. +SET yb_update_num_cols_to_compare TO 2; +DROP INDEX t_simple_v2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) EXECUTE updateplan1 (1, 1, 2, 1, 1); + +EXECUTE updateplan1 (1, 1, 2, 1, 1); +EXECUTE updateplan1 (1, 1, 2, 1, 1); +EXECUTE updateplan1 (1, 1, 2, 1, 1); +EXECUTE updateplan1 (1, 1, 2, 1, 1); + +SET yb_update_num_cols_to_compare TO 50; + +-- Similar to the above, test that adding a foreign key causes replanning. +ALTER TABLE t_simple ADD CONSTRAINT fk_t_simple_v2 FOREIGN KEY (v2) REFERENCES t_parent(k); +-- Updating v2 should not produce a foreign key check when the value of the column +-- remains unchanged. +EXPLAIN (ANALYZE, DIST, COSTS OFF) EXECUTE updateplan1 (1, 1, 2, 1, 1); + +EXECUTE updateplan1 (1, 1, 2, 1, 1); +EXECUTE updateplan1 (1, 1, 2, 1, 1); +EXECUTE updateplan1 (1, 1, 2, 1, 1); +EXECUTE updateplan1 (1, 1, 2, 1, 1); + +-- Similar to the above, test that dropping a foreign key causes replanning. +SET yb_update_num_cols_to_compare TO 2; +ALTER TABLE t_simple DROP CONSTRAINT fk_t_simple_v2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) EXECUTE updateplan1 (1, 1, 2, 1, 1); + +DEALLOCATE updateplan1; + +DROP TABLE t_simple; +DROP TABLE t_parent; + +-- +-- #25075: Test INSERT with UPDATE ON CONFLICT DO UPDATE +-- +DROP TABLE IF EXISTS ioc_table; +CREATE TABLE ioc_table (id text, uuid_col uuid, name text, primary key ((id, name), uuid_col)); +CREATE UNIQUE INDEX NONCONCURRENTLY ioc_table_name_id_uidx ON ioc_table (name, id); +CREATE UNIQUE INDEX NONCONCURRENTLY ioc_table_name_uidx ON ioc_table (name); +CREATE UNIQUE INDEX NONCONCURRENTLY ioc_table_uuid_col_uidx ON ioc_table (uuid_col); + +CREATE EXTENSION "uuid-ossp"; +-- insert +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO ioc_table VALUES ('user-id-1-1', uuid_generate_v4(), 'Test Name 1') + ON CONFLICT (id, name) DO UPDATE SET uuid_col = EXCLUDED.uuid_col; +-- conflict: update +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO ioc_table VALUES ('user-id-1-1', uuid_generate_v4(), 'Test Name 1') + ON CONFLICT (id, name) DO UPDATE SET uuid_col = EXCLUDED.uuid_col; +-- conflict: update again +EXPLAIN (ANALYZE, DIST, COSTS OFF) INSERT INTO ioc_table VALUES ('user-id-1-1', uuid_generate_v4(), 'Test Name 1') + ON CONFLICT (id, name) DO UPDATE SET uuid_col = EXCLUDED.uuid_col; +DROP TABLE IF EXISTS ioc_table; + +-- +-- GH-27948: Test that DELETE + (re)INSERT of the same row can be performed in a single flush +-- +CREATE TABLE json_table (k INT PRIMARY KEY, v1 JSONB, v2 JSONB, v3 INT); +INSERT INTO json_table (SELECT + i AS k, + ('{"a": ' || i || ', "b": ' || 100 + i || ', "c": ' || 200 + i || ', "d": ' || 300 + i || '}')::jsonb AS v1, + ('{"a": ' || i || ', "b": ' || 100 + i || ', "c": ' || 200 + i || ', "d": ' || 300 + i || '}')::jsonb AS v2, + i AS v3 + FROM generate_series(1, 5) AS i); + +CREATE INDEX NONCONCURRENTLY json_table_v1 ON json_table (CAST((v1->>'a') AS INT)); +CREATE UNIQUE INDEX NONCONCURRENTLY json_table_v2 ON json_table (CAST((v2->>'a') AS INT)); + +-- Updating the subkey having an index should allow the 'skip redundant update' optimization to kick in. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_table SET v1 = jsonb_set(v1, '{a}', to_jsonb(1)) WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_table SET v2 = jsonb_set(v2, '{a}', to_jsonb(1)) WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_table SET v1 = jsonb_set(v1, '{a}', to_jsonb(1)), v2 = jsonb_set(v2, '{a}', to_jsonb(1)) WHERE k = 2; +-- Updating a subkey not having an index should allow the optimization to push down the DELETE + INSERT in a single flush. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_table SET v1 = jsonb_set(v1, '{b}', to_jsonb(k + 110)) WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_table SET v2 = jsonb_set(v2, '{b}', to_jsonb(k + 110)) WHERE k = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_table SET v1 = jsonb_set(v1, '{b}', to_jsonb(k + 120)), v2 = jsonb_set(v2, '{b}', to_jsonb(k + 120)) WHERE k = 2; + +SELECT * FROM json_table WHERE k IN (1, 2) ORDER BY k; + +CREATE UNIQUE INDEX NONCONCURRENTLY ON json_table (CAST((v1->>'c') AS INT), CAST((v1->>'d') AS INT)); +CREATE INDEX NONCONCURRENTLY ON json_table (CAST((v1->>'c') AS INT), CAST((v2->>'c') AS INT)); + +-- Updating any subkey of a multi-subkey index should allow the optimization to push down the DELETE + INSERT in a single flush. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_table SET v1 = jsonb_set(v1, '{d}', to_jsonb(k + 310)) WHERE k = 3; +-- Updating any subkey should update the corresponding indexes. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_table SET v1 = jsonb_set(v1, '{c}', to_jsonb(k + 210)) WHERE k = 3; + +SELECT * FROM json_table WHERE k IN (3, 4) ORDER BY k; + +-- Multi-row updates. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_table SET v1 = jsonb_set(v1, '{c}', to_jsonb(v1->>'c')) WHERE k < 10; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_table SET v2 = jsonb_set(v2, '{c}', to_jsonb((v2->>'c')::INT + 1)) WHERE k < 10; + +SELECT * FROM json_table ORDER BY k; + +CREATE TABLE staircase (k INT, PRIMARY KEY (k ASC)); +INSERT INTO staircase (SELECT i FROM generate_series(1, 100) AS i); +-- When not optimized, the following query converges to this pattern: +-- Flush k: INSERT (n-2), DELETE (n), INSERT (n-1), DELETE (n+1), +-- Flush k + 1: INSERT (n), DELETE (n+2), INSERT (n+1), DELETE (n+3) +-- and so on. +-- Unique constraint violations are not raised because every insert maps to a row that doesn't exist. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE staircase SET k = k - 1; + +-- UPDATEs on GIN indexes +CREATE TABLE gin_table (k INT PRIMARY KEY, v INT[]); +CREATE INDEX gin_table_idx ON gin_table USING gin (v); +INSERT INTO gin_table (SELECT i, ARRAY[1, 3, 100+i] from generate_series(1, 10) AS i); + +-- Updating a single element in the array column will trigger updates to all other elements +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE gin_table SET v[3] = k + 100 + 1 WHERE k < 30; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE gin_table SET v[3] = v[3] + 1 WHERE v[1] = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE gin_table SET v[1] = v[1] + 1, v[2] = v[1] + 3 WHERE v[1] = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE gin_table SET k = k + 10; + +-- Updates to unmodified elements should still be carried out; but in a single flush. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE gin_table SET v[3] = k + 90 + 2 WHERE k < 30; + +SELECT * FROM gin_table ORDER BY k; diff --git a/src/test/regress/sql/yb.orig.update_optimize_index_updates.sql b/src/test/regress/sql/yb.orig.update_optimize_index_updates.sql new file mode 100644 index 0000000000000..e0e5a1f076d3a --- /dev/null +++ b/src/test/regress/sql/yb.orig.update_optimize_index_updates.sql @@ -0,0 +1,262 @@ +SET yb_fetch_row_limit TO 1024; + +-- +-- Tests to validate index updates in a table with no primary key. +-- +DROP TABLE IF EXISTS no_pkey_table; +CREATE TABLE no_pkey_table (v1 INT, v2 INT, v3 INT, v4 INT); +CREATE INDEX NONCONCURRENTLY no_pkey_v1 ON no_pkey_table (v1 HASH); +CREATE INDEX NONCONCURRENTLY no_pkey_v2_hash_v3 ON no_pkey_table (v2 HASH) INCLUDE (v3); +CREATE INDEX NONCONCURRENTLY no_pkey_v2_range_v3 ON no_pkey_table (v2 ASC) INCLUDE (v3); + +INSERT INTO no_pkey_table (SELECT i, i, i, i FROM generate_series(1, 10) AS i); + +-- Updating a column with no indexes should not require index writes +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE no_pkey_table SET v4 = v4 + 1 WHERE v1 = 1; +-- Updating the key columns of an index should require a DELETE + INSERT +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE no_pkey_table SET v2 = v2 + 1 WHERE v1 = 1; +-- Updating non-key columns of an index should only require an UPDATE +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE no_pkey_table SET v3 = v3 + 1 WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE no_pkey_table SET v3 = v3 + 1, v4 = v4 + 1 WHERE v1 = 1; +-- Updating a mix of key and non-key columns of an index should require a DELETE + INSERT +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE no_pkey_table SET v2 = v2 + 1, v3 = v3 + 1 WHERE v1 = 1; + +-- Validate the updates above using both SeqScan and IndexOnlyScan +/*+ SeqScan(no_pkey_table) */ SELECT * FROM no_pkey_table WHERE v1 = 1 ORDER BY v1; +/*+ IndexOnlyScan(no_pkey_table no_pkey_v2_hash_v3) */ SELECT v2, v3 FROM no_pkey_table WHERE v2 = 3 ORDER BY (v2, v3); +/*+ IndexOnlyScan(no_pkey_table no_pkey_v2_range_v3) */ SELECT v2, v3 FROM no_pkey_table WHERE v2 = 3 ORDER BY (v2, v3); + +DROP TABLE IF EXISTS t_simple; +CREATE TABLE t_simple (k1 INT, k2 INT NULL, v1 INT, v2 INT, v3 INT, v4 INT, PRIMARY KEY (k1, k2)); +INSERT INTO t_simple (SELECT i, i, i, i, i, i FROM generate_series(1, 10) AS i); + +-- +-- Vanilla tests to validate index updates in a table with a primary key. +-- +CREATE INDEX NONCONCURRENTLY simple_v1 ON t_simple (v1 HASH); +CREATE INDEX NONCONCURRENTLY simple_v2_hash_v3 ON t_simple (v2 HASH) INCLUDE (v3); +CREATE INDEX NONCONCURRENTLY simple_v2_range_v3 ON t_simple (v2 ASC) INCLUDE (v3); + +-- Updating a column with no indexes should not require index writes +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v4 = v4 + 1 WHERE k1 = 1; +-- Updating the key columns of an index should require a DELETE + INSERT +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = v2 + 1 WHERE k1 = 1; +-- Updating non-key columns of an index should only require an UPDATE +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v3 = v3 + 1 WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v3 = v3 + 1, v4 = v4 + 1 WHERE k1 = 1; +-- Updating a mix of key and non-key columns of an index should require a DELETE + INSERT +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = v2 + 1, v3 = v3 + 1 WHERE k1 = 1; + +-- Validate the updates above using both SeqScan and IndexOnlyScan +/*+ SeqScan(t_simple) */ SELECT * FROM t_simple WHERE k1 = 1 ORDER BY (k1, k2); +/*+ IndexOnlyScan(t_simple simple_v2_hash_v3) */ SELECT v2, v3 FROM t_simple WHERE v2 = 3 ORDER BY (v2, v3); +/*+ IndexOnlyScan(t_simple simple_v2_range_v3) */ SELECT v2, v3 FROM t_simple WHERE v2 = 3 ORDER BY (v2, v3); + +-- Updating the primary key columns should require a DELETE + INSERT on the main table +-- as well as on non-unique indexes. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k1 = k1 + 10, v3 = v3 + 1 WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k2 = k2 + 10, v3 = v3 + 1 WHERE k1 = 11; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k1 = 12, k2 = 22, v3 = v3 + 1 WHERE k1 < 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k1 = k1 + 10, k2 = k2 + 10, v2 = v2 + 1 WHERE k2 = 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k1 = k1 + 10, k2 = k2 + 10, v2 = v2 + 1, v3 = v3 + 1 WHERE k2 = 4; + +/*+ SeqScan(t_simple) */ SELECT * FROM t_simple WHERE k1 > 10 ORDER BY (k1, k2); +/*+ IndexOnlyScan(t_simple simple_v2_hash_v3) */ SELECT v2, v3 FROM t_simple WHERE v2 <= 5 ORDER BY (v2, v3); +/*+ IndexOnlyScan(t_simple simple_v2_range_v3) */ SELECT v2, v3 FROM t_simple WHERE v2 <= 5 ORDER BY (v2, v3); +/*+ IndexScan(simple_v2_hash_v3) */ SELECT * FROM t_simple WHERE v2 <= 5 ORDER BY (v2, v3); +/*+ IndexScan(simple_v2_range_v3) */ SELECT * FROM t_simple WHERE v2 <= 5 ORDER BY (v2, v3); + +DROP INDEX simple_v1; +DROP INDEX simple_v2_hash_v3; +DROP INDEX simple_v2_range_v3; + +-- +-- Vanilla tests to validate index updates in unique indexes. +-- +TRUNCATE t_simple; +INSERT INTO t_simple (SELECT i, i, i, i, i, i FROM generate_series(1, 10) AS i); +CREATE UNIQUE INDEX NONCONCURRENTLY simple_v2_hash_v3 ON t_simple (v2 HASH) INCLUDE (v3); +CREATE UNIQUE INDEX NONCONCURRENTLY simple_v2_range_v3 ON t_simple (v2 ASC) INCLUDE (v3); + +-- Updating the key columns of a unique index should require a DELETE + INSERT +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = v2 + 10 WHERE k1 = 1; +-- Updating non-key columns of an index should only require an UPDATE +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v3 = v3 + 1 WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v3 = v3 + 1, v4 = v4 + 1 WHERE k1 = 1; +-- Updating a mix of key and non-key columns of an index should require a DELETE + INSERT +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = v2 + 10, v3 = v3 + 1 WHERE k1 = 1; + +-- Updating the primary key columns should require a DELETE + INSERT on the main table +-- but only an UPDATE on a non-unique index. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k1 = k1 + 10, v3 = v3 + 1 WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k2 = k2 + 10, v3 = v3 + 1 WHERE k1 = 11; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k1 = 12, k2 = 22, v3 = v3 + 1 WHERE k1 < 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k1 = k1 + 10, k2 = k2 + 10, v2 = v2 + 10 WHERE k2 = 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k1 = k1 + 10, k2 = k2 + 10, v2 = v2 + 10, v3 = v3 + 1 WHERE k2 = 4; + +/*+ SeqScan(t_simple) */ SELECT * FROM t_simple WHERE k1 > 10 ORDER BY (k1, k2); +/*+ IndexOnlyScan(t_simple simple_v2_hash_v3) */ SELECT v2, v3 FROM t_simple WHERE v2 <= 5 ORDER BY (v2, v3); +/*+ IndexOnlyScan(t_simple simple_v2_range_v3) */ SELECT v2, v3 FROM t_simple WHERE v2 <= 5 ORDER BY (v2, v3); +/*+ IndexScan(simple_v2_hash_v3) */ SELECT * FROM t_simple WHERE v2 <= 5 ORDER BY (v2, v3); +/*+ IndexScan(simple_v2_range_v3) */ SELECT * FROM t_simple WHERE v2 <= 5 ORDER BY (v2, v3); + +DROP INDEX simple_v2_hash_v3; +DROP INDEX simple_v2_range_v3; + +-- +-- Tests to validate multi-column index update behavior +-- +TRUNCATE t_simple; +INSERT INTO t_simple (SELECT i, i, i, i, i, i FROM generate_series(1, 10) AS i); +CREATE INDEX NONCONCURRENTLY simple_v1_v2_v3 ON t_simple ((v1, v2) HASH) INCLUDE (v3); +-- Updating any of the key columns of the non-unique index should require a DELETE + INSERT +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = v1 + 10, v2 = v2 + 10 WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = v1 + 10, v3 = v3 + 1 WHERE k1 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = v1 + 10, v3 = v3 + 1 WHERE k1 = 3; +-- Similaryly, updating the primary key should require a DELETE + INSERT +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k2 = k2 + 10, v3 = v3 + 1 WHERE k1 = 4; +-- Updating non-key columns of the index should only require an UPDATE +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v3 = v3 + 1, v4 = v4 + 1 WHERE k1 = 5; + +/*+ IndexOnlyScan(t_simple simple_v1_v2_v3) */ SELECT v1, v2, v3 FROM t_simple WHERE v2 <= 5 ORDER BY (v1, v2); +/*+ IndexScan(simple_v1_v2_v3) */ SELECT * FROM t_simple WHERE k1 <= 5 ORDER BY (v1, v2); + +DROP INDEX simple_v1_v2_v3; + +-- Create an index that has columns out of order and repeat the test above. +CREATE INDEX NONCONCURRENTLY out_of_order_v1_v2_v3 ON t_simple ((v3, v1) HASH) INCLUDE (v2); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = v1 + 10, v2 = v2 + 10 WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = v1 + 10, v3 = v3 + 1 WHERE k1 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = v1 + 10, v3 = v3 + 1 WHERE k1 = 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k2 = k2 + 10, v2 = v2 + 1 WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = v2 + 1, v4 = v4 + 1 WHERE k1 = 1; + +/*+ IndexOnlyScan(t_simple out_of_order_v1_v2_v3) */ SELECT v1, v2, v3 FROM t_simple WHERE v2 <= 5 ORDER BY (v1, v2); +/*+ IndexScan(out_of_order_v1_v2_v3) */ SELECT * FROM t_simple WHERE k1 <= 5 ORDER BY (v1, v2); + +DROP INDEX out_of_order_v1_v2_v3; + +-- Range indexes + +-- +-- Tests to validate index update behavior when the index contains NULL values +-- +CREATE INDEX NONCONCURRENTLY simple_v1_v2_v3 ON t_simple (v1, v2) INCLUDE (v3); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = NULL WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = NULL, v3 = NULL WHERE k1 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v3 = NULL, v4 = NULL WHERE k1 = 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = NULL WHERE k1 = 4; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = NULL, v2 = NULL, v3 = NULL WHERE k1 = 5; + +/*+ IndexOnlyScan(t_simple simple_v1_v2_v3) */ SELECT v1, v2, v3 FROM t_simple WHERE v1 <= 25 OR v1 IS NULL ORDER BY (v1, v2); +/*+ IndexScan(simple_v1_v2_v3) */ SELECT * FROM t_simple WHERE k1 <= 5 ORDER BY (v1, v2); + +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = 1 WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = 2, v3 = k1 WHERE k1 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v4 = k1 - k2 + v1, v3 = 3 WHERE k1 = 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = 4 WHERE k1 = 4; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = 5, v2 = 5, v3 = 5 WHERE k1 = 5; + +/*+ IndexOnlyScan(t_simple simple_v1_v2_v3) */ SELECT v1, v2, v3 FROM t_simple WHERE v1 <= 25 OR v1 IS NULL ORDER BY (v1, v2); +/*+ IndexScan(simple_v1_v2_v3) */ SELECT * FROM t_simple WHERE k1 <= 5 ORDER BY (v1, v2); + +DROP INDEX simple_v1_v2_v3; + +-- Create a unique index with nullable values and repeat the tests above. +CREATE INDEX NONCONCURRENTLY simple_unique_v1_v2_v3 ON t_simple (v1, v2) INCLUDE (v3); +-- Setting any of the primary key columns to NULL should be done via a single UPDATE +-- Setting any of the secondary index key columns to NULL will still require the delete + update +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = NULL WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = NULL, v3 = NULL WHERE k1 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v3 = NULL, v4 = NULL WHERE k1 = 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = NULL WHERE k1 = 4; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = NULL, v2 = NULL, v3 = NULL WHERE k1 = 5; + +/*+ IndexOnlyScan(t_simple simple_unique_v1_v2_v3) */ SELECT v1, v2, v3 FROM t_simple WHERE v1 <= 25 OR v1 IS NULL ORDER BY (v1, v2); +/*+ IndexScan(simple_unique_v1_v2_v3) */ SELECT * FROM t_simple WHERE k1 <= 5 ORDER BY (v1, v2); + +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = 1 WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = 2, v3 = k1 WHERE k1 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v4 = k1 - k2 + v1, v3 = 3 WHERE k1 = 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = 4 WHERE k1 = 4; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = 5, v2 = 5, v3 = 5 WHERE k1 = 5; + +/*+ IndexOnlyScan(t_simple simple_unique_v1_v2_v3) */ SELECT v1, v2, v3 FROM t_simple WHERE v1 <= 25 OR v1 IS NULL ORDER BY (v1, v2); +/*+ IndexScan(simple_unique_v1_v2_v3) */ SELECT * FROM t_simple WHERE k1 <= 5 ORDER BY (v1, v2); + +DROP INDEX simple_unique_v1_v2_v3; + +-- +-- Tests to validate index update behavior for partial indexes +-- +TRUNCATE t_simple; +INSERT INTO t_simple (SELECT i, i, i, i, i, i FROM generate_series(1, 10) AS i); +CREATE INDEX NONCONCURRENTLY simple_partial_or ON t_simple (v1, v2) INCLUDE (v3) WHERE v1 < 5 OR v2 < 10; +CREATE INDEX NONCONCURRENTLY simple_partial_and ON t_simple (v1, v2) INCLUDE (v3) WHERE v1 < 5 AND v2 < 10; + +-- The row must be deleted from both indexes +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = 11, v2 = 10 WHERE k1 = 1; +-- The rows must be deleted from the AND index but not the OR index +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = 12, v2 = 8 WHERE k1 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = 15 WHERE k1 = 3; +-- Modifying the INCLUDE columns should have no impact on the index +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v3 = v3 + 10 WHERE k1 = 6; +-- The row must be inserted into both indexes +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = 5, v2 = -5 WHERE k1 = 1; +-- The row must be inserted into one of the indexes but not the other +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = 4 WHERE k1 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = 4, v2 = 8, v3 = 100 WHERE k1 = 7; +-- Modifying the primary key columns should neither delete nor insert into the index +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k2 = k1 + 10, v3 = v3 + 1 WHERE k1 = 8; + +/*+ IndexOnlyScan(t_simple simple_partial_or) */ SELECT v1, v2, v3 FROM t_simple WHERE v1 < 5 OR v2 < 10 ORDER BY (v1, v2); +/*+ IndexScan(simple_unique_v1_v2_v3) */ SELECT * FROM t_simple WHERE v1 < 5 OR v2 < 10 ORDER BY (v1, v2); +/*+ IndexOnlyScan(t_simple simple_partial_and) */ SELECT v1, v2, v3 FROM t_simple WHERE v1 < 5 AND v2 < 10 ORDER BY (v1, v2); +/*+ IndexScan(simple_unique_v1_v2_v3) */ SELECT * FROM t_simple WHERE v1 < 5 AND v2 < 10 ORDER BY (v1, v2); +SELECT * FROM t_simple; + +DROP INDEX simple_partial_or; +DROP INDEX simple_partial_and; + +-- +-- Tests to validate index update behavior for expression indexes +-- +TRUNCATE t_simple; +INSERT INTO t_simple (SELECT i, i, i, i, i, i FROM generate_series(1, 10) AS i); +CREATE UNIQUE INDEX NONCONCURRENTLY simple_expr ON t_simple ((v1 + 10)) INCLUDE (v3); + +-- Updating any of the columns making up the expression should require a DELETE + INSERT +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = 11 WHERE k1 = 1; +-- Any other column update should only require an UPDATE +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k2 = k2 + 1 WHERE k1 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k1 = k1 + 10 WHERE k1 = 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k2 = k2 + 5, v3 = v3 + 5 WHERE k1 = 4; + +/*+ IndexScan(simple_expr) */ SELECT *, v1 + 10 FROM t_simple WHERE v1 + 10 IN (21, 12, 13, 14, 15, 16, 17, 18, 19, 20) ORDER BY (v1, v2); +SELECT * FROM t_simple; + +-- CREATE INDEX NONCONCURRENTLY complex_expr_v2 ON t_simple ((v1 * v2), v3) INCLUDE (v4); + +-- +-- Tests to validate multiple include columns +-- +TRUNCATE t_simple; +INSERT INTO t_simple (SELECT i, i, i, i, i, i FROM generate_series(1, 10) AS i); +CREATE UNIQUE INDEX NONCONCURRENTLY multi_include ON t_simple (v1, v2) INCLUDE (v4, v3, v2); + +-- Updating any of the key columns should require a DELETE + INSERT +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = 11 WHERE k1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v2 = v2 + 1 WHERE k1 = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v1 = v2 + 10, v2 = v1 + 1 WHERE k1 = 3; +-- Any other column update should only require an UPDATE +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v3 = v3 + 1 WHERE k1 = 4; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v3 = v4, v4 = v3 WHERE k1 = 5; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET v4 = v2 + 1 WHERE k1 = 6; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k1 = k1 + 10, k2 = k2 + 1 WHERE k1 = 7; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE t_simple SET k1 = k1 + 10, k2 = k2 + 1 WHERE k1 = 8; + +EXPLAIN (VERBOSE) /*+ IndexOnlyScan(t_simple multi_include) */ SELECT v1, v2, v3, v4 FROM t_simple ORDER BY (v1, v2); +/*+ IndexOnlyScan(t_simple multi_include) */ SELECT v1, v2, v3, v4 FROM t_simple ORDER BY (v1, v2); +EXPLAIN (VERBOSE) /*+ IndexScan(multi_include) */ SELECT * FROM t_simple ORDER BY (v1, v2); +/*+ IndexScan(multi_include) */ SELECT * FROM t_simple ORDER BY (v1, v2); +SELECT * FROM t_simple; diff --git a/src/test/regress/sql/yb.orig.update_optimize_indices.sql b/src/test/regress/sql/yb.orig.update_optimize_indices.sql new file mode 100644 index 0000000000000..41b1c5809dfe6 --- /dev/null +++ b/src/test/regress/sql/yb.orig.update_optimize_indices.sql @@ -0,0 +1,283 @@ +SET yb_fetch_row_limit TO 1024; +SET yb_update_num_cols_to_compare TO 50; +SET yb_update_max_cols_size_to_compare TO 10240; + +-- This test requires the t-server preview/auto flag 'ysql_yb_update_optimizations_infra' to be enabled. + +-- CREATE a table with a primary key and no secondary indexes +DROP TABLE IF EXISTS pkey_only_table; +CREATE TABLE pkey_only_table (h INT PRIMARY KEY, v1 INT, v2 INT); +INSERT INTO pkey_only_table (SELECT i, i, i FROM generate_series(1, 10240) AS i); +EXPLAIN (ANALYZE, DIST, COSTS OFF) SELECT * FROM pkey_only_table; + +-- Updating non-index column should be done in a single op +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET v1 = 1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET v1 = 1, v2 = 1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET v1 = 2 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET v1 = 3, v2 = 3 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET v1 = v1 + 1, v2 = v1 + 1 WHERE h = 1; + +-- Setting index column in the update should trigger a read, but no update of the index +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET h = 1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET v1 = 1, h = 1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET v1 = 1, h = h WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE pkey_only_table SET v1 = 1, h = v1 WHERE h = 1; + +-- CREATE a table with a multi-column secondary index +DROP TABLE IF EXISTS secindex_only_table; +CREATE TABLE secindex_only_table (h INT, v1 INT, v2 INT, v3 INT); +CREATE INDEX NONCONCURRENTLY secindex_only_table_v1_v2 ON secindex_only_table((v1, v2) HASH); +INSERT INTO secindex_only_table (SELECT i, i, i, i FROM generate_series(1, 10240) AS i); + +-- Setting the secondary index columns in the should trigger a read, but no update of the index +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = 1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = h WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = h, h = h WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = h, h = v1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = h, h = v1 + v2 - h WHERE h = 1; + +-- Same cases as above, but not providing the primary key +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = 1 WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = v1 WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = h WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = h, h = h WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = h, h = v1 WHERE v1 = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET v1 = h, h = v1 + v2 - h WHERE v1 = 1; + +-- Queries with non-leading secondary index +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE secindex_only_table SET h = h WHERE h = 1; +-- + +-- CREATE a table having secondary indices with NULL values +DROP TABLE IF EXISTS nullable_table; +CREATE TABLE nullable_table (h INT PRIMARY KEY, v1 INT, v2 INT, v3 INT); +CREATE INDEX NONCONCURRENTLY nullable_table_v1_v2 ON nullable_table ((v1, v2) HASH); +INSERT INTO nullable_table (SELECT i, NULL, NULL, NULL FROM generate_series(1, 100) AS i); + +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE nullable_table SET v1 = NULL WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE nullable_table SET v1 = NULL, v2 = NULL WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE nullable_table SET h = 1, v1 = NULL, v2 = NULL WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE nullable_table SET h = h, v1 = NULL, v2 = NULL WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE nullable_table SET h = h, v1 = NULL, v2 = NULL WHERE v1 = NULL; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE nullable_table SET h = h, v1 = NULL, v2 = NULL WHERE v1 = NULL OR v2 = NULL; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE nullable_table SET h = h, v1 = NULL, v2 = NULL WHERE v1 = NULL AND v2 = NULL; + +-- CREATE a table having secondary indices in a colocated database. + + +-- CREATE a table having indexes of multiple data types +DROP TABLE IF EXISTS number_type_table; +CREATE TABLE number_type_table (h INT PRIMARY KEY, v1 INT2, v2 INT4, v3 INT8, v4 BIGINT, v5 FLOAT4, v6 FLOAT8, v7 SERIAL, v8 BIGSERIAL, v9 NUMERIC(10, 4), v10 NUMERIC(100, 40)); +CREATE INDEX NONCONCURRENTLY number_type_table_v1_v2 ON number_type_table (v1 ASC, v2 DESC); +CREATE INDEX NONCONCURRENTLY number_type_table_v3 ON number_type_table (v3 HASH); +CREATE INDEX NONCONCURRENTLY number_type_table_v4_v5_v6 ON number_type_table ((v4, v5) HASH, v6 ASC); +CREATE INDEX NONCONCURRENTLY number_type_table_v7_v8 ON number_type_table (v7 HASH, v8 DESC); +CREATE INDEX NONCONCURRENTLY number_type_table_v9_v10 ON number_type_table (v9 HASH) INCLUDE (v10, v1); + +INSERT INTO number_type_table(h, v1, v2, v3, v4, v5, v6, v9, v10) VALUES (0, 1, 2, 3, 4, 5.0, 6.000001, '-9.1234', '-10.123455789'); +INSERT INTO number_type_table(h, v1, v2, v3, v4, v5, v6, v9, v10) VALUES (10, 11, 12, 13, 14, 15.01, 16.000002, '-19.1234', '-20.123455789'); + +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE number_type_table SET h = 0, v1 = 1, v2 = 2, v3 = 3, v4 = 4, v5 = 5, v6 = 6.000001, v7 = 1, v8 = 1, v9 = '-9.12344', v10 = '-10.123455789' WHERE h = 0; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE number_type_table SET h = h + 1, v1 = 1, v2 = 2, v3 = 3, v4 = 4, v5 = 5, v6 = 6.000001, v7 = 1, v8 = 1, v9 = '-9.12344', v10 = '-10.123455789' WHERE h = 0; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE number_type_table SET h = h + 1, v1 = 1, v2 = 2, v3 = 3, v4 = 4, v5 = 5, v6 = 6.000001, v7 = 1, v8 = 1, v9 = '-9.12344', v10 = '-10.123455789'; + +CREATE TYPE mood_type AS ENUM ('sad', 'ok', 'happy'); +DROP TABLE IF EXISTS non_number_type_table; +CREATE TABLE non_number_type_table(tscol TIMESTAMP PRIMARY KEY, varcharcol VARCHAR(8), charcol CHAR(8), textcol TEXT, linecol LINE, ipcol CIDR, uuidcol UUID, enumcol mood_type); +CREATE INDEX NONCONCURRENTLY non_number_type_table_mixed ON non_number_type_table(tscol, varcharcol); +CREATE INDEX NONCONCURRENTLY non_number_type_table_text ON non_number_type_table((varcharcol, charcol) HASH, textcol ASC); +CREATE INDEX NONCONCURRENTLY non_number_type_table_text_hash ON non_number_type_table(textcol HASH); +-- This should fail as indexes on geometric and network types are not yet supported +CREATE INDEX NONCONCURRENTLY non_number_type_table_text_geom_ip ON non_number_type_table(linecol ASC, ipcol DESC); +CREATE INDEX NONCONCURRENTLY non_number_type_table_uuid_enum ON non_number_type_table(uuidcol ASC, enumcol DESC); + +INSERT INTO non_number_type_table VALUES('1999-01-08 04:05:06 -8:00', 'varchar1', 'charpad', 'I am batman', '{1, 2, 3}'::line, '1.2.3.0/24'::cidr, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'happy'::mood_type); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE non_number_type_table SET tscol = 'January 8 04:05:06 1999 PST', varcharcol = 'varchar1', charcol = 'charpad', textcol = 'I am not batman :(', linecol = '{1, 2, 3}'::line, ipcol = '1.2.3'::cidr, uuidcol = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, enumcol = 'happy' WHERE tscol = 'January 8 07:05:06 1999 EST'; + +DROP TABLE pkey_only_table; +DROP TABLE secindex_only_table; +DROP TABLE nullable_table; +DROP TABLE non_number_type_table; +DROP TABLE number_type_table; + +--- +--- Test to validate behavior of equality comparisons across data types +--- +-- Test for json data types +CREATE TABLE json_types_table (h INT PRIMARY KEY, v1 INT, v2 JSONB, v3 JSON); +CREATE INDEX NONCONCURRENTLY ON json_types_table (v1) INCLUDE (v2, v3); + +INSERT INTO json_types_table VALUES (1, 1, '{"a": 1, "b": 2}'::jsonb, '{"a": 1, "b": 2}'::json); +INSERT INTO json_types_table VALUES (2, 2, '{"b": 2, "a": 1}', '{"b": 2, "a": 1}'); + +-- Equality comparisons on data type with no equality operator (json) should succeed +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_types_table SET v1 = 1 WHERE h = 1; +-- Two different representations of the same data type should be considered equal +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_types_table SET v1 = 1, v2 = '{"b": 2, "a": 1}'::jsonb WHERE h = 1; +-- JSONB is normalized, while JSON is not, causing an index update +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_types_table SET v1 = 1, v3 = '{"b": 2, "a": 1}'::json WHERE h = 1; +-- One with white spaces +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_types_table SET v1 = 1, v2 = '{ "b" : 2 , "a" : 1}'::jsonb WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_types_table SET v1 = 1, v2 = '{"b":2,"a":1}'::json WHERE h = 1; , +-- Casting via not specifying type +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_types_table SET v1 = 1, v2 = '{"b": 2, "a": 1}', v3 = '{"a": 1, "b": 1}' WHERE h = 2; +-- Casting via specifying input as a castable type +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_types_table SET v1 = 1, v2 = '{"b": 2, "a": 1}'::json, v3 = '{"a": 1, "b": 2}'::jsonb WHERE h = 2; +-- Casting an un-normalized JSONB to JSON should cause an index update as the input will never be normalized +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE json_types_table SET v1 = 1, v2 = '{"b": 2, "a": 1}'::json, v3 = '{"a": 1, "b": 2}'::jsonb WHERE h = 2; + +-- TODO(kramanathan): PG 15 has jsonpath support. Add tests for jsonpath + +-- Tests for geometric data types +CREATE TABLE geometric_types_table (h INT PRIMARY KEY, v1 INT, v2 POINT, v3 LINE, v4 LSEG, v5 BOX, v6 PATH, v7 POLYGON, v8 CIRCLE); +CREATE INDEX NONCONCURRENTLY ON geometric_types_table (v1) INCLUDE (v2, v3, v4, v5, v6, v7, v8); +INSERT INTO geometric_types_table VALUES (1, 1, '(3.0, 4.0)'::point, '[(1.0, 2.0), (3.0, 4.0)]'::line, '[(1, 2), (3, 4)]'::lseg, '(1, 2), (3, 4)'::box, '[(1, 2), (3, 4)]'::path, '((1, 2), (3, 4), (5, 6))'::polygon, '<(1, 2), 3>'::circle); + +-- Convert point and line from float representation to int representation (implicit casting) +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v2 = '(3, 4)'::point, v3 = '[(1.0, 2), (3, 4.0)]'::line WHERE h = 1; +-- Individual coordinates are stored as float8. Test cases where input has higher precision +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v2 = '(2.999999999999999999, 3.999999999999999999)'::point, v3 = '[(0.999999999999999999, 2.00), (2.999999999999999999, 4)]'::line WHERE h = 1; +-- Alternate line and line segment representations; note that line segment is directed +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v3 = '[(3, 4), (1, 2)]'::line, v4 = '[(1, 2), (3, 4)]'::lseg WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v3 = '((3, 4), (1, 2))'::line, v4 = '((1, 2), (3, 4))'::lseg WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v3 = '(3, 4), (1, 2)'::line, v4 = '(1, 2), (3, 4)'::lseg WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v3 = '3, 4, 1, 2'::line, v4 = '1, 2, 3, 4'::lseg WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v3 = '[(3, 4), (1, 2)]'::line, v4 = '[(1, 2), (3, 4)]'::lseg WHERE h = 1; +-- Line is equivalent to x - y + 1 = 0 => A = 1, B = -1, C = 1 +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v3 = '{1, -1, 1}'::line WHERE h = 1; +-- A line can be represented by other points that lie outside the boundary of the supplied points +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v3 = '[(5, 6), (7, 8)]'::line WHERE h = 1; +-- Alternate box representations +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v5 = '(3, 2), (1, 4)'::box WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v5 = '((3, 2), (1, 4))'::box WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v5 = '3, 2, 1, 4'::box WHERE h = 1; +-- Alterate representations of a circle +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v8 = '(1, 2), 3'::circle WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v8 = '((1, 2), 3)'::circle WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE geometric_types_table SET v1 = 1, v8 = '1, 2, 3'::circle WHERE h = 1; + +-- Test for array data types +CREATE TABLE array_types_table (h INT PRIMARY KEY, v1 INT, v2 INT4[], v3 FLOAT4[], v4 TEXT[], v5 JSONB[], v6 BYTEA[]); +CREATE INDEX NONCONCURRENTLY ON array_types_table (v1) INCLUDE (v2, v3, v4, v5, v6); +INSERT INTO array_types_table VALUES (1, 1, ARRAY[1, 2, 3], ARRAY[1.0, 2.0, 3.0], ARRAY['a', 'b', 'c'], ARRAY['{"a": 1, "b": 2}', '{"c": 3, "d": 4}']::jsonb[], ARRAY['abc'::bytea, 'def'::bytea, 'ghi'::bytea]); + +-- Replace the entire array +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE array_types_table SET v1 = 1, v2 = '{1, 2, 3}', v3 = '{1, 2, 3}', v4 = '{"a", "b", "c"}' WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE array_types_table SET v1 = 1, v2 = ARRAY[1, 2, 3], v3 = ARRAY[1, 2, 3], v4 = ARRAY['a', 'b', 'c'] WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE array_types_table SET v1 = 1, v5 = ARRAY['{"a": 1, "b": 2}', '{"c": 3, "d": 4}']::jsonb[], v6 = ARRAY['abc', 'def', 'ghi']::bytea[] WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE array_types_table SET v1 = 1, v6 = ARRAY['\x616263'::bytea, '\x646566'::bytea, '\x676869'::bytea] WHERE h = 1; +-- Replace specific elements in the array +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE array_types_table SET v1 = 1, v2[2] = 2, v3[2] = 1.999999999999999999, v4[3] = 'c' WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE array_types_table SET v1 = 1, v6[1] = '\x616263'::bytea WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE array_types_table SET v1 = 1, v2[2:3] = '{2, 3}', v3[1:2] = '{0.999999999999999999, 1.999999999999999999}', v4[1:3] = ARRAY['a', 'b', 'c'] WHERE h = 1; + +-- Test for type modifiers +-- REAL has 6 decimal digits of precision, NUMERIC has type modifiers 'precision' and 'scale'. +-- Precision is the total number of digits, scale is the number of digits after the decimal point. +SET extra_float_digits TO 0; +CREATE TABLE numeric_table (h INT PRIMARY KEY, v1 REAL, v2 NUMERIC(4, 2), v3 NUMERIC(10, 4), v4 FLOAT4); +CREATE INDEX NONCONCURRENTLY ON numeric_table (v1) INCLUDE (v2, v3, v4); +INSERT INTO numeric_table VALUES (1, 1.234567, 12.34, 1.234567, 1.23456789012); +SELECT * FROM numeric_table; + +-- Query to check that the storage representation remains identical after type modifiers have been applied. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE numeric_table SET v1 = 1.2345670, v2 = 12.344, v4 = 1.23456789567 WHERE h = 1; +-- Query to check that casting from a wider type to a narrower type results in "rounding" behavior. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE numeric_table SET v1 = 1.2345670, v2 = 12.336, v3 = 1.234650 WHERE h = 1; +-- Casting from infinity to infinity should not cause a change in representation. +-- Note that NUMERIC does not support infinity prior to PG 14. +INSERT INTO numeric_table VALUES (2, 'inf', 'Infinity', '-Infinity', '-inf'); +INSERT INTO numeric_table VALUES (2, 'inf', 0, 0, '-inf'); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE numeric_table SET v1 = 'inf'::real + 1, v4 = '-Infinity'::float4 + 1 WHERE h = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE numeric_table SET v1 = v1 + 'inf'::real + 1, v4 = v4 + '-Infinity'::float4 + 1 WHERE h = 2; +-- Casting from infinity to NaN should however cause a change in representation. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE numeric_table SET v1 = v1 - 'inf'::real, v4 = v4 - '-Infinity'::float4 WHERE h = 2; +SELECT * FROM numeric_table; +-- Casting from NaN to NaN should not cause a change in representation. +-- Similarly, note that NaN is not supported for NUMERIC types in Yugabyte (GH-711) does not support NaN prior to PG 14. +-- This should error out +INSERT INTO numeric_table VALUES (3, 'nan', 'NaN', 'naN', 'NaN'); +INSERT INTO numeric_table VALUES (3, 'nan', 0, 0, 'NaN'); +-- ('inf'::real - 'inf'::real) produces a different representation of NaN than 'NaN'::real in some +-- compilers/optimization levels. So the query below updates the index in some cases, but not in others. +-- Commenting it out to avoid test failures. +-- EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE numeric_table SET v1 = 'inf'::real - 'inf'::real, v4 = v4 - '-Infinity'::float4 - '+Inf'::float4 WHERE h = 3; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE numeric_table SET v1 = v1 - 'inf'::real, v4 = v4 - '-Infinity'::float4 - '+Inf'::float4 WHERE h = 3; +SELECT * FROM numeric_table; + +-- Test for casting between data types +CREATE TABLE number_table (h INT PRIMARY KEY, v1 INT, v2 INT4, v3 INT8, v4 FLOAT4, v5 FLOAT8); +CREATE INDEX NONCONCURRENTLY ON number_table (v1) INCLUDE (v2, v3, v4, v5); +INSERT INTO number_table VALUES (1, 1, 1, 1, 1.0, 1.0); + +-- Casting from a larger type to a smaller type should either retain the same representation +-- if the value is identical or produce an overflow. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE number_table SET v2 = (2147483647 + 1)::int8 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE number_table SET v4 = (3.4e39)::float8 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE number_table SET v2 = (1)::int8, v4 = (1.0)::float8 WHERE h = 1; + +-- Casting from a smaller to a larger type should either retain the same representation when the +-- value is identical. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE number_table SET v3 = (1)::int4, v5 = (1.0)::float4 WHERE h = 1; + +-- Casting between types should not cause an index update if the value remains the same. +INSERT INTO number_table VALUES (2, 2, 17, 15, 17.25, 15.75); +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE number_table SET v2 = (17.0)::float4, v3 = (15.0)::float8 WHERE h = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE number_table SET v2 = 17.0, v3 = 15.0 WHERE h = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE number_table SET v4 = 17::int4 + 0.25::float(8), v5 = 15::int8 + 0.75::float(4) WHERE h = 2; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE number_table SET v4 = 17 + 0.25, v5 = 15 + 0.75 WHERE h = 2; + +-- Test for padding in text data types. +CREATE TABLE text_table (h INT PRIMARY KEY, v1 INT, v2 TEXT, v3 CHAR(8), v4 VARCHAR(8)); +CREATE INDEX NONCONCURRENTLY ON text_table (v1) INCLUDE (v2, v3, v4); +INSERT INTO text_table VALUES (1, 1, 'some-text', 'abcdef', 'abcde'); + +-- The following queries should not cause an index update as the text type's value remains unmodified +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE text_table SET v2 = 'some-text', v3 = 'abcdef', v4 = 'abcde' WHERE h = 1; +-- Postgres does not support NULL characters in strings. The padding does not end up adding any characters. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE text_table SET v2 = rpad('some-text'::char(12), 12, ''), v3 = rpad('abcdef'::char(8), 8, ''), v4 = rpad('abcde'::char(8), 8, '') WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE text_table SET v2 = lpad('some-text'::text, 12, ''), v3 = lpad('abcdef'::text, 8, ''), v4 = lpad('abcde'::text, 8, '') WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE text_table SET v2 = ''::char(4) || 'some-text' || ''::char(4), v3 = 'abcdef' || ''::char(2), v4 = 'abcde' || ''::char(3) WHERE h = 1; + +-- The following query ends up updating the index because postgres truncates the trailing NULL characters. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE text_table SET v4 = 'abcde'::char(8) || '000' WHERE h = 1; +SELECT * FROM text_table; + +-- Test for composite data types +CREATE TYPE complex1 AS (v1 INT4, v2 FLOAT4, v3 TEXT, v4 JSONB, v5 BYTEA); +CREATE TABLE composite_table (h INT PRIMARY KEY, v INT, c1 complex1); +CREATE INDEX NONCONCURRENTLY ON composite_table (v) INCLUDE (c1); + +INSERT INTO composite_table VALUES (1, 1, ROW(100, 123.45, 'some-text', '{"a": 1, "b": 2}'::jsonb, '{"c": 3, "d": 4}'::bytea)); + +-- The following queries should not cause an index update as the composite type's value remains unmodified +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET c1.v1 = 100 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET v = 1, c1.v3 = 'some-text' WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET c1.v2 = 123.45, c1.v5 = '{"c": 3, "d": 4}'::bytea, c1.v4 = '{"a": 1, "b": 2}'::jsonb WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET c1 = ROW(100, 123.45, 'some-text', '{"a": 1, "b": 2}'::jsonb, '{"c": 3, "d": 4}'::bytea) WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET c1 = (100, 123.45, 'some-text', '{"a": 1, "b": 2}'::jsonb, '{"c": 3, "d": 4}'::bytea) WHERE h = 1; +-- Queries with implicit casting +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET c1 = (100, 123.45, 'some-text', '{"a": 1, "b": 2}', '{"c": 3, "d": 4}') WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET c1 = (100.40, 123.45, 'some-text', '{"b": 2, "a": 1}', '{"c": 3, "d": 4}') WHERE h = 1; + +SELECT * FROM composite_table; + +-- The following queries should produce an index update as the composite type's value is modified +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET c1.v1 = (c1).v2 + 1 WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET v = v + 1, c1.v3 = 'some-text' WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET v = v + 1, c1.v3 = 'someother-text' WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET c1.v2 = 123.451, c1.v5 = '{"d": 4, "c": 3}'::bytea, c1.v4 = '{"a": 2, "b": 1}'::jsonb WHERE h = 1; +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE composite_table SET c1 = ROW((v) + 100, (c1).v1 + 123.45, 'someother-text', '{"a": 2, "b": 1}'::jsonb, '{"d": 4, "c": 3}'::bytea) WHERE h = 1; + +SELECT * FROM composite_table; + +DROP TABLE composite_table; +DROP TABLE text_table; +DROP TABLE number_table; +DROP TABLE numeric_table; +DROP TABLE array_types_table; +DROP TABLE geometric_types_table; +DROP TABLE json_types_table; + +RESET extra_float_digits; diff --git a/src/test/regress/sql/yb.orig.update_optimize_triggers.sql b/src/test/regress/sql/yb.orig.update_optimize_triggers.sql new file mode 100644 index 0000000000000..f7db7f64f0153 --- /dev/null +++ b/src/test/regress/sql/yb.orig.update_optimize_triggers.sql @@ -0,0 +1,159 @@ +SET yb_fetch_row_limit TO 1024; +SET yb_update_num_cols_to_compare TO 50; +SET yb_update_max_cols_size_to_compare TO 10240; + +-- This test requires the t-server preview/auto flag 'ysql_yb_update_optimizations_infra' to be enabled. + +CREATE OR REPLACE FUNCTION musical_chair() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + -- Increment a specifc column based on the modulo of the row supplied + RAISE NOTICE 'Musical chairs invoked with h = %', NEW.h; + IF OLD.h % 4 = 0 THEN + NEW.h = NEW.h + 40; + ELSIF OLD.h % 4 = 1 THEN + NEW.v1 = NEW.v1 + 1; + ELSIF OLD.h % 4 = 2 THEN + NEW.v2 = NEW.v2 + 1; + ELSE + NEW.v3 = NEW.v3 + 1; + END IF; + RETURN NEW; +END; +$$; + +-- CREATE a table that contains the columns specified in the above function. +DROP TABLE IF EXISTS mchairs_table; +CREATE TABLE mchairs_table (h INT PRIMARY KEY, v1 INT, v2 INT, v3 INT); +INSERT INTO mchairs_table (SELECT i, i, i, i FROM generate_series(1, 12) AS i); + +-- Create some indexes to test the behavior of the updates +CREATE INDEX NONCONCURRENTLY mchairs_v1_v2 ON mchairs_table (v1 ASC, v2 DESC); +CREATE INDEX NONCONCURRENTLY mchairs_v3 ON mchairs_table (v3 HASH); + +-- Add the trigger that plays musical chairs with the above table +CREATE TRIGGER mchairs_table_trigger BEFORE UPDATE ON mchairs_table FOR EACH ROW EXECUTE FUNCTION musical_chair(); + +-- The value of v1 should be incremented twice, index v1_v2 should be updated. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE mchairs_table SET v1 = v1 + 1 WHERE h = 1; +-- The value of v1 should be updated again, indexes v1_v2, v3 should be updated. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE mchairs_table SET h = h, v3 = v3 + 1 WHERE h = 1; +-- Multi-row scenario affecting 4 successive rows exactly with 4 flushes. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE mchairs_table SET h = h, v1 = v1, v2 = v3, v3 = v2 WHERE h > 8 AND h <= 12; + +-- Validate the updates +SELECT * FROM mchairs_table ORDER BY h; + +-- The decrement of v1 should be offset by the before row trigger. No indexes +-- should be updated. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE mchairs_table SET v1 = v1 - 1 WHERE h = 1; +-- Same as above but for the primary key +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE mchairs_table SET h = h - 40, v1 = v1 + 1 WHERE h = 4; +-- A subtle variation of the above to test the scenario that the decrement of +-- h is applied on the value of h that is returned by the before row trigger. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE mchairs_table SET h = h - 1, v1 = v1 + 1 WHERE h = 4; + +-- Multi-row scenario. +EXPLAIN (ANALYZE, DIST, COSTS OFF) UPDATE mchairs_table SET v1 = v1 - 1, v2 = v2 - 1, v3 = v3 - 1 WHERE h > 4 AND h <= 8; + +-- Again, validate the updates +SELECT * FROM mchairs_table ORDER BY h; + +DROP TABLE mchairs_table; + +--- +--- Test to validate the behavior of after row triggers that are conditional on +--- specific columns (AFTER UPDATE OF ). +--- +DROP TABLE IF EXISTS t_simple; +CREATE TABLE t_simple (h INT PRIMARY KEY, v1 INT, v2 INT, v3 INT, v4 INT); +CREATE INDEX NONCONCURRENTLY ON t_simple (v1); +CREATE INDEX NONCONCURRENTLY ON t_simple (v2, v3); +INSERT INTO t_simple (SELECT i, i, i, i, i FROM generate_series(1, 10) AS i); + +CREATE OR REPLACE FUNCTION update_v1() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'update_v1 invoked with v1 = % where h = %', NEW.v1, NEW.h; + NEW.v1 = NEW.v1 + 1; + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION notice_v1_update() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'New value of v1 = % where h = %', NEW.v1, NEW.h; + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION update_v2() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'update_v2 invoked with v2 = % where h = %', NEW.v2, NEW.h; + NEW.v2 = NEW.v2 + 1; + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION notice_v2_update() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'New value of v2 = % where h = %', NEW.v2, NEW.h; + RETURN NEW; +END; +$$; + +CREATE TRIGGER after_update_v1 AFTER UPDATE OF v1 ON t_simple FOR EACH ROW EXECUTE FUNCTION notice_v1_update(); +CREATE TRIGGER after_update_v2 AFTER UPDATE OF v2 ON t_simple FOR EACH ROW EXECUTE FUNCTION notice_v2_update(); + +-- Appropriate AFTER ROW triggers fire when columns are mentioned in the SET clause. +UPDATE t_simple SET v1 = v1 + 1 WHERE h = 1; +UPDATE t_simple SET v1 = v1 + 1, v2 = v2 + 1 WHERE h = 2; +-- It shouldn't matter that the columns are unmodified by the query. If they are +-- in the set clause, appropriate triggers should fire. +UPDATE t_simple SET v2 = v2 WHERE h = 3; +UPDATE t_simple SET v1 = v2, v2 = v1 WHERE h = 4; + +-- AFTER ROW triggers do not fire in any of the cases below. +UPDATE t_simple SET v3 = v3 + 1 WHERE h = 5; +UPDATE t_simple SET v4 = v1, v3 = v2 WHERE h = 6; +UPDATE t_simple SET h = h + 10 WHERE h > 9; + +CREATE TRIGGER update_v1 BEFORE UPDATE ON t_simple FOR EACH ROW EXECUTE FUNCTION update_v1(); +CREATE TRIGGER update_v2 BEFORE UPDATE ON t_simple FOR EACH ROW EXECUTE FUNCTION update_v2(); +UPDATE t_simple SET v3 = v3 + 1 WHERE h = 7; +UPDATE t_simple SET v4 = v1 + 1 WHERE h = 8; +UPDATE t_simple SET h = h + 10, v4 = v4 + 1 WHERE h = 9; + +-- AFTER ROW triggers should fire in the following cases even though the +-- BEFORE ROW triggers nullify the modification. +UPDATE t_simple SET v1 = v1 - 1 WHERE h = 1; +UPDATE t_simple SET v2 = 2 WHERE h = 2; +UPDATE t_simple SET v1 = v2 - 1, v2 = v1 - 1 WHERE h = 3; +UPDATE t_simple SET v1 = h, v2 = v1 WHERE h IN (4, 5, 6); + +SELECT * FROM t_simple ORDER BY h; + +--- +--- Test to validate the behavior of statement triggers. +--- +CREATE OR REPLACE FUNCTION update_statement_trigger() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + -- Should be all NULLs + RAISE NOTICE 'statement trigger (%) invoked with row = %', TG_ARGV[0], NEW; + RETURN NULL; +END; +$$; + +CREATE TRIGGER before_update_statement BEFORE UPDATE ON t_simple FOR EACH STATEMENT EXECUTE FUNCTION update_statement_trigger('BEFORE'); +-- No AFTER ROW triggers should fire. +UPDATE t_simple SET v3 = v3 - 1 WHERE h = 4; + +-- Appropriate AFTER ROW triggers should fire. +CREATE TRIGGER after_update_statement_v1_v2 AFTER UPDATE OF v1, v2 ON t_simple FOR EACH STATEMENT EXECUTE FUNCTION update_statement_trigger('AFTER v1, v2'); +UPDATE t_simple SET v1 = v1 + 1 WHERE h = 5; +UPDATE t_simple SET v1 = v2, v2 = v1 WHERE h = 6; +UPDATE t_simple SET v1 = 7, v2 = 7, v3 = 7 WHERE h = 7; +UPDATE t_simple SET h = h + 10, v4 = v4 + 1 WHERE h = 8; + +-- Test the behavior of triggers when multiple rows are involved. +UPDATE t_simple SET v1 = v2 - 1, v2 = v3 - 1, v3 = v1 - 1 WHERE h > 7; + +SELECT * FROM t_simple ORDER BY h; diff --git a/src/test/regress/sql/yb.orig.util.sql b/src/test/regress/sql/yb.orig.util.sql new file mode 100644 index 0000000000000..6104e118fe1d4 --- /dev/null +++ b/src/test/regress/sql/yb.orig.util.sql @@ -0,0 +1,4 @@ +select yb_log_catcache_stats(pg_backend_pid()); + +select yb_log_catcache_stats(0); + diff --git a/src/test/regress/sql/yb.orig.view.sql b/src/test/regress/sql/yb.orig.view.sql new file mode 100644 index 0000000000000..8952a5fe91f91 --- /dev/null +++ b/src/test/regress/sql/yb.orig.view.sql @@ -0,0 +1,40 @@ +-- +-- YB tests for views +-- + +CREATE TABLE test (a int, b int, c int DEFAULT 5); +INSERT INTO test VALUES (generate_series(1, 5), generate_series(1, 5)); +CREATE VIEW test_view AS SELECT * FROM test ORDER BY a, b; +SELECT * FROM test_view; +CREATE UNLOGGED VIEW unlogged_test_view AS SELECT * FROM test; -- fails + +-- Tests for ALTER VIEW. +-- test ALTER VIEW ... ALTER COLUMN ... SET/DROP DEFAULT +ALTER VIEW test_view ALTER COLUMN c SET DEFAULT 10; +INSERT INTO test (a, b) VALUES (6, 6); +INSERT INTO test_view (a, b) VALUES (7, 7); +SELECT * FROM test_view; +ALTER VIEW test_view ALTER COLUMN c DROP DEFAULT; +INSERT INTO test (a, b) VALUES (8, 8); +SELECT * FROM test_view; +ALTER VIEW IF EXISTS non_existent_view ALTER COLUMN c SET DEFAULT 10; +ALTER VIEW IF EXISTS non_existent_view ALTER COLUMN c DROP DEFAULT; +-- test ALTER VIEW ... OWNER TO +CREATE ROLE test_role; +ALTER VIEW test_view OWNER TO test_role; +SELECT * FROM test_view; +ALTER VIEW test_view OWNER TO CURRENT_USER; +SELECT * FROM test_view; +ALTER VIEW IF EXISTS non_existent_view OWNER TO test_role; +-- test ALTER VIEW ... RENAME +ALTER VIEW test_view RENAME TO test_view_renamed; +SELECT * FROM test_view_renamed; +ALTER VIEW IF EXISTS non_existent_view RENAME TO non_existent_view_renamed; +ALTER VIEW test_view_renamed RENAME TO test_view; +-- test ALTER VIEW ... SET SCHEMA +CREATE SCHEMA test_schema; +ALTER VIEW test_view SET SCHEMA test_schema; +SELECT * FROM test_schema.test_view; +ALTER VIEW test_schema.test_view SET SCHEMA public; +SELECT * FROM test_view; +ALTER VIEW IF EXISTS non_existent_view SET SCHEMA test_schema; diff --git a/src/test/regress/sql/yb.orig.with.sql b/src/test/regress/sql/yb.orig.with.sql new file mode 100644 index 0000000000000..f06868832b2dd --- /dev/null +++ b/src/test/regress/sql/yb.orig.with.sql @@ -0,0 +1,44 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (COSTS OFF)' + +/* Test A */ +drop table if exists a; +drop table if exists b; +create table a (i int unique); +create table b (i int unique); +insert into a values (1); +insert into b values (2); + +SELECT ':P with w(i) as ( + insert into a values (1) on conflict on constraint a_i_key do update set i = 10 returning i +) insert into b values (2) on conflict on constraint b_i_key do update set i = (select 20 from w);' +AS query \gset +\i :iter_P2 + +/* Test B */ +drop table if exists a; +create table a (i int unique); +insert into a values (1), (2); + +SELECT ':P with w(i) as ( + insert into a values (1) on conflict on constraint a_i_key do update set i = 10 returning i +) insert into a values (2) on conflict on constraint a_i_key do update set i = (select 20 from w);' +AS query \gset +\i :iter_P2 + +/* Test C */ +drop table if exists a; +create table a (i int unique); +insert into a values (1), (2), (3); + +SELECT ':P with w(i) as ( + insert into a values (1) on conflict on constraint a_i_key do update set i = 10 returning i +), x(i) as ( + insert into a values (2) on conflict on constraint a_i_key do update set i = 20 returning i +) insert into a values (3) on conflict on constraint a_i_key do update set i = (select 30 from w);' +AS query \gset +\i :iter_P2 diff --git a/src/test/regress/sql/yb.orig.yb_get_tablet_for_key.sql b/src/test/regress/sql/yb.orig.yb_get_tablet_for_key.sql new file mode 100644 index 0000000000000..e8f2afec38c99 --- /dev/null +++ b/src/test/regress/sql/yb.orig.yb_get_tablet_for_key.sql @@ -0,0 +1,344 @@ +-- ============================================================================= +-- Tests for yb_get_tablet_for_key(db_name, table_oid, row_values) +-- Returns the tablet ID that would contain the given key (same-DB and cross-DB). +-- row_values must be a non-NULL record with at least one column. +-- ============================================================================= + +-- ----------------------------------------------------------------------------- +-- 1. Error cases +-- ----------------------------------------------------------------------------- + +-- Index: must use base table +CREATE TABLE tablet_key_test_idx (k INT PRIMARY KEY, v INT); +CREATE INDEX tablet_key_test_idx_v ON tablet_key_test_idx(v); +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_test_idx_v'::regclass::oid, ROW(1)); + +-- TODO(#30318): Partitioned parent OID not supported +CREATE TABLE tablet_key_partitioned (k INT PRIMARY KEY, v INT) PARTITION BY RANGE (k); +CREATE TABLE tablet_key_partitioned_p1 PARTITION OF tablet_key_partitioned FOR VALUES FROM (1) TO (100); +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_partitioned'::regclass::oid, ROW(50)) IS NOT NULL as ok; + +-- Too many values +CREATE TABLE tablet_key_simple (v INT, k INT PRIMARY KEY); +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_simple'::regclass::oid, ROW(1, 2, 3)); + +-- NULL in primary key +CREATE TABLE tablet_key_nulltest (v INT, k1 INT, k2 INT, PRIMARY KEY (k1, k2)); +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_nulltest'::regclass::oid, ROW(1, NULL::int)); + +-- NULL in non-primary key (full row with v=NULL) should succeed +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_nulltest' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_nulltest'::regclass::oid, ROW(NULL::int, 1, 2)) +) AS null_non_pk_ok; + +-- Wrong value count (partial PK not allowed for hash) +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_nulltest'::regclass::oid, ROW(1::int)); + +-- Type mismatch (wrong type on PK column k, which is PG column 2) +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_simple'::regclass::oid, ROW(2, 'not_an_int'::text)); + +-- Type mismatch (text passed for timestamptz PK) +CREATE TABLE tbl (a timestamptz PRIMARY KEY, k int, v int); +SELECT yb_get_tablet_for_key(current_database(), 'tbl'::regclass::oid, ROW('not a timestamp')); + +-- NULL as the only value (PK column is NULL) +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_simple'::regclass::oid, ROW(NULL)); + +-- ----------------------------------------------------------------------------- +-- 2. Hash table: success and metadata check +-- ----------------------------------------------------------------------------- + +CREATE TABLE tablet_key_hash (v TEXT, k INT PRIMARY KEY) SPLIT INTO 3 TABLETS; +INSERT INTO tablet_key_hash VALUES ('one', 1), ('hundred', 100), ('thousand', 1000); + +-- Returned tablet_id must exist in yb_tablet_metadata for this table +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_hash' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_hash'::regclass::oid, ROW('one', 1)) +) AS ok; + +SELECT COUNT(*) BETWEEN 1 AND 3 AS all_in_metadata +FROM yb_tablet_metadata ytm +WHERE ytm.relname = 'tablet_key_hash' + AND ytm.tablet_id IN ( + yb_get_tablet_for_key(current_database(), 'tablet_key_hash'::regclass::oid, ROW('one', 1)), + yb_get_tablet_for_key(current_database(), 'tablet_key_hash'::regclass::oid, ROW('hundred', 100)), + yb_get_tablet_for_key(current_database(), 'tablet_key_hash'::regclass::oid, ROW('thousand', 1000)) + ); + +-- PK-only (only k): same tablet as full row +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_hash'::regclass::oid, ROW(1)) = + yb_get_tablet_for_key(current_database(), 'tablet_key_hash'::regclass::oid, ROW('one', 1)) + AS pk_only_same_tablet; + +-- ----------------------------------------------------------------------------- +-- 3. Range table: success, distinct tablets, metadata +-- ----------------------------------------------------------------------------- + +CREATE TABLE tablet_key_range (v TEXT, k INT, PRIMARY KEY (k ASC)) SPLIT AT VALUES ((100), (200)); +INSERT INTO tablet_key_range VALUES ('first', 50), ('second', 150), ('third', 250); + +-- Returned tablet_id must exist in yb_tablet_metadata for this table +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_range' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_range'::regclass::oid, ROW('first', 50)) +) AS ok; + +SELECT COUNT(DISTINCT tablet_id) = 3 AS three_tablets +FROM ( + SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_range'::regclass::oid, ROW('first', 50)) AS tablet_id + UNION ALL + SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_range'::regclass::oid, ROW('second', 150)) + UNION ALL + SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_range'::regclass::oid, ROW('third', 250)) +) t; + +SELECT COUNT(*) = 3 AS all_in_metadata +FROM yb_tablet_metadata ytm +WHERE ytm.relname = 'tablet_key_range' + AND ytm.tablet_id IN ( + yb_get_tablet_for_key(current_database(), 'tablet_key_range'::regclass::oid, ROW('first', 50)), + yb_get_tablet_for_key(current_database(), 'tablet_key_range'::regclass::oid, ROW('second', 150)), + yb_get_tablet_for_key(current_database(), 'tablet_key_range'::regclass::oid, ROW('third', 250)) + ); + +-- ----------------------------------------------------------------------------- +-- 4. Range + partial PK (prefix key lookup) +-- ----------------------------------------------------------------------------- + +CREATE TABLE tablet_key_range_composite ( + data TEXT, region TEXT, id INT, ts TIMESTAMP, + PRIMARY KEY (region ASC, id ASC, ts ASC) +) SPLIT AT VALUES (('east', 1000, '2024-06-01'), ('west', 1000, '2024-06-01')); +INSERT INTO tablet_key_range_composite VALUES + ('data1', 'alpha', 500, '2024-01-01'), + ('data2', 'east', 2000, '2024-07-01'), + ('data3', 'west', 5000, '2024-12-01'); + +-- Each returned tablet_id must exist in yb_tablet_metadata for this table +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_range_composite' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_range_composite'::regclass::oid, ROW('alpha')) +) AS in_metadata +UNION ALL +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_range_composite' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_range_composite'::regclass::oid, ROW('east', 2000)) +) +UNION ALL +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_range_composite' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_range_composite'::regclass::oid, + ROW('west'::text, 5000, '2024-12-01'::timestamp)) +); + +SELECT COUNT(*) = 3 AS all_in_metadata +FROM yb_tablet_metadata ytm +WHERE ytm.relname = 'tablet_key_range_composite' + AND ytm.tablet_id IN ( + yb_get_tablet_for_key(current_database(), 'tablet_key_range_composite'::regclass::oid, ROW('alpha')), + yb_get_tablet_for_key(current_database(), 'tablet_key_range_composite'::regclass::oid, ROW('east', 2000)), + yb_get_tablet_for_key(current_database(), 'tablet_key_range_composite'::regclass::oid, ROW('west'::text, 5000, '2024-12-01'::timestamp)) + ); + +-- ----------------------------------------------------------------------------- +-- 5a. Hash + range (compound key) +-- ----------------------------------------------------------------------------- + +CREATE TABLE tablet_key_hash_range (v TEXT, h INT, r INT, PRIMARY KEY (h HASH, r ASC)) SPLIT INTO 3 TABLETS; +INSERT INTO tablet_key_hash_range VALUES ('a', 1, 100), ('b', 2, 200), ('c', 3, 300); + +-- Returned tablet_id must exist in yb_tablet_metadata for this table +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_hash_range' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_hash_range'::regclass::oid, ROW('a', 1, 100)) +) AS in_metadata; + +SELECT COUNT(*) >= 1 AS in_metadata +FROM yb_tablet_metadata ytm +WHERE ytm.relname = 'tablet_key_hash_range' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_hash_range'::regclass::oid, ROW('a', 1, 100)); + +-- ----------------------------------------------------------------------------- +-- 5b. Two hash + two range keys, first column non-PK: valid key prefixes only +-- ----------------------------------------------------------------------------- +-- Table: (v, h1, h2, r1, r2), PK ((h1, h2) HASH, r1 ASC, r2 ASC). Values in key order. +-- Range keys can only be given after all hash keys are complete: 0h0r, 1h0r, 2h0r, 2h1r, 2h2r. +CREATE TABLE tablet_key_2h2r ( + v TEXT, h1 INT, h2 INT, r1 INT, r2 INT, + PRIMARY KEY ((h1, h2) HASH, r1 ASC, r2 ASC) +) SPLIT INTO 2 TABLETS; +INSERT INTO tablet_key_2h2r VALUES ('nopk', 1, 2, 10, 20); + +-- 0 hash, 0 range: 0 values +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_2h2r'::regclass::oid, ROW()); +-- 1 hash, 0 range: 1 value (h1 only) +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_2h2r'::regclass::oid, ROW(1)); +-- 2 hash, 0 range: 2 values (h1, h2) +-- TODO(#30518): Hash keys in hash-range table not supported. +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_2h2r'::regclass::oid, ROW(1, 2)); +-- 2 hash, 1 range: 3 values (h1, h2, r1) +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_2h2r'::regclass::oid, ROW(1, 2, 10)); +-- 2 hash, 2 range: 4 values (full key) - success +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_2h2r' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_2h2r'::regclass::oid, ROW(1, 2, 10, 20)) +) AS full_key_ok; +-- Full row (5 values: v, h1, h2, r1, r2) - success +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_2h2r' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_2h2r'::regclass::oid, ROW('nopk', 1, 2, 10, 20)) +) AS full_row_ok; + +-- ----------------------------------------------------------------------------- +-- 5c. Multi-column PK with full row in PG order (reordering: PG order != DocDB order) +-- ----------------------------------------------------------------------------- +-- Table columns (PG order): a, b, c. PK is (a, c). DocDB order: a, c, b. +-- Passing full row in PG order (a, b, c) is reordered to (a, c, b) in pggate. +CREATE TABLE tablet_key_reorder (a INT, b TEXT, c INT, PRIMARY KEY (a, c)) SPLIT INTO 2 TABLETS; +INSERT INTO tablet_key_reorder VALUES (1, 'mid', 2), (10, 'x', 20); + +-- PK only (already in DocDB order): returned tablet_id must exist in yb_tablet_metadata +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_reorder' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_reorder'::regclass::oid, ROW(1, 2)) +) AS pk_only_ok; + +-- Full row in PG definition order (a, b, c) - tests reordering logic +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_reorder' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_reorder'::regclass::oid, ROW(1, 'mid', 2)) +) AS full_row_ok; + +-- Same key: full row and PK-only must resolve to same tablet +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_reorder'::regclass::oid, ROW(1, 2)) = + yb_get_tablet_for_key(current_database(), 'tablet_key_reorder'::regclass::oid, ROW(1, 'mid', 2)) AS same_tablet; + +-- ----------------------------------------------------------------------------- +-- 6. Other types: UUID, TEXT (implicit), TIMESTAMP (3 tablets) +-- ----------------------------------------------------------------------------- + +CREATE TABLE tablet_key_uuid (data TEXT, id UUID PRIMARY KEY) SPLIT INTO 2 TABLETS; +INSERT INTO tablet_key_uuid VALUES ('data1', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid); +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_uuid' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_uuid'::regclass::oid, + ROW('data1', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid)) +) AS ok; + +CREATE TABLE tablet_key_text (value INT, name TEXT, PRIMARY KEY (name ASC)) SPLIT AT VALUES (('m')); +INSERT INTO tablet_key_text VALUES (1, 'apple'), (2, 'zebra'); +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_text' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_text'::regclass::oid, ROW(1, 'apple')) +) AS ok; +SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_text'::regclass::oid, ROW(1, 'apple')) <> + yb_get_tablet_for_key(current_database(), 'tablet_key_text'::regclass::oid, ROW(2, 'zebra')) AS different_tablets; + +CREATE TABLE tablet_key_ts (v TEXT, ts TIMESTAMP, PRIMARY KEY (ts ASC)) SPLIT AT VALUES (('2024-06-01'), ('2024-09-01')); +INSERT INTO tablet_key_ts VALUES ('a', '2024-03-15'::timestamp), ('b', '2024-07-15'::timestamp), ('c', '2024-11-15'::timestamp); +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'tablet_key_ts' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'tablet_key_ts'::regclass::oid, ROW('a', '2024-03-15'::timestamp)) +) AS ok; +SELECT COUNT(DISTINCT tablet_id) = 3 AS three_tablets +FROM ( + SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_ts'::regclass::oid, ROW('a', '2024-03-15'::timestamp)) AS tablet_id + UNION ALL SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_ts'::regclass::oid, ROW('b', '2024-07-15'::timestamp)) + UNION ALL SELECT yb_get_tablet_for_key(current_database(), 'tablet_key_ts'::regclass::oid, ROW('c', '2024-11-15'::timestamp)) +) t; + +-- ----------------------------------------------------------------------------- +-- 7. Cross-database: success and error cases +-- ----------------------------------------------------------------------------- + +SELECT current_database() AS orig_db \gset +CREATE DATABASE tablet_key_crossdb_test; +\c tablet_key_crossdb_test + +CREATE TABLE crossdb_t (v TEXT, id INT PRIMARY KEY) SPLIT INTO 2 TABLETS; +INSERT INTO crossdb_t VALUES ('a', 1), ('b', 2); +SELECT 'crossdb_t'::regclass::oid AS crossdb_oid \gset + +\c :orig_db + +-- Cross-db: yb_tablet_metadata filters via JOIN to pg_class (current DB only), +-- so tablets from other DBs are not visible; we only check non-null here. +SELECT yb_get_tablet_for_key('tablet_key_crossdb_test', :crossdb_oid, ROW('a', 1)) IS NOT NULL AS crossdb_ok; + +SELECT yb_get_tablet_for_key('nonexistent_database', :crossdb_oid, ROW('a', 1)); +SELECT yb_get_tablet_for_key('tablet_key_crossdb_test', :crossdb_oid, ROW('a', 'x'::text)); +SELECT yb_get_tablet_for_key('tablet_key_crossdb_test', :crossdb_oid, ROW('a', NULL::int)); + +\c postgres +DROP DATABASE IF EXISTS tablet_key_crossdb_test; +\c :orig_db + +-- ----------------------------------------------------------------------------- +-- 8. Partitioned parent table: parent returns tablet_id but it is dummy (GH #30318) +-- See https://github.com/yugabyte/yugabyte-db/issues/30318 +-- Child partition OID returns correct tablet_id; parent OID returns non-null but +-- not the actual tablet for the key. This test documents current behavior until +-- the issue is resolved. +-- ----------------------------------------------------------------------------- +CREATE TABLE pt_parent_test (v TEXT, k INT, PRIMARY KEY (k)) PARTITION BY RANGE (k); +CREATE TABLE pt_parent_test_p1 PARTITION OF pt_parent_test FOR VALUES FROM (0) TO (100); +INSERT INTO pt_parent_test VALUES ('x', 50); + +-- Parent OID: currently returns a tablet_id (dummy; not the actual tablet for k=50) +SELECT yb_get_tablet_for_key(current_database(), 'pt_parent_test'::regclass::oid, ROW(50)) IS NOT NULL AS parent_returns_tablet_id; + +-- Child OID: returns correct tablet_id present in yb_tablet_metadata +SELECT EXISTS ( + SELECT 1 FROM yb_tablet_metadata ytm + WHERE ytm.relname = 'pt_parent_test_p1' + AND ytm.tablet_id = yb_get_tablet_for_key(current_database(), 'pt_parent_test_p1'::regclass::oid, ROW('x', 50)) +) AS child_tablet_in_metadata; + +-- ----------------------------------------------------------------------------- +-- 9. TODO(#30518): Colocated tables not yet supported +-- ----------------------------------------------------------------------------- + +CREATE DATABASE tablet_key_colocated_test WITH COLOCATION = true; +\c tablet_key_colocated_test + +CREATE TABLE colocated_t (k INT PRIMARY KEY, v TEXT); +SELECT yb_get_tablet_for_key(current_database(), 'colocated_t'::regclass::oid, ROW(1, 'a')); + +\c :orig_db +DROP DATABASE IF EXISTS tablet_key_colocated_test; + +-- ----------------------------------------------------------------------------- +-- 10. Cleanup +-- ----------------------------------------------------------------------------- + +DROP TABLE IF EXISTS tablet_key_test_idx CASCADE; +DROP TABLE IF EXISTS tablet_key_partitioned CASCADE; +DROP TABLE IF EXISTS tablet_key_simple CASCADE; +DROP TABLE IF EXISTS tablet_key_nulltest CASCADE; +DROP TABLE IF EXISTS tbl CASCADE; +DROP TABLE IF EXISTS tablet_key_hash CASCADE; +DROP TABLE IF EXISTS tablet_key_range CASCADE; +DROP TABLE IF EXISTS tablet_key_range_composite CASCADE; +DROP TABLE IF EXISTS tablet_key_hash_range CASCADE; +DROP TABLE IF EXISTS tablet_key_2h2r CASCADE; +DROP TABLE IF EXISTS tablet_key_reorder CASCADE; +DROP TABLE IF EXISTS tablet_key_uuid CASCADE; +DROP TABLE IF EXISTS tablet_key_text CASCADE; +DROP TABLE IF EXISTS tablet_key_ts CASCADE; +DROP TABLE IF EXISTS pt_parent_test CASCADE; diff --git a/src/test/regress/sql/yb.orig.yb_hash_code.sql b/src/test/regress/sql/yb.orig.yb_hash_code.sql new file mode 100644 index 0000000000000..e6998177af068 --- /dev/null +++ b/src/test/regress/sql/yb.orig.yb_hash_code.sql @@ -0,0 +1,318 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE)' + +-- test yb_hash_code as a function +SELECT yb_hash_code(1,2,3); +SELECT yb_hash_code(1,2,'abc'::text); + +-- test on unsupported primary key types +SELECT yb_hash_code('asdf'); +SELECT yb_hash_code('{"a": {"b":{"c": "foo"}}}'::jsonb); +SELECT yb_hash_code(ARRAY[1,2,3]); + +-- test basic filtering on different datatypes with yb_hash_code +CREATE TABLE test_table_int (x INT PRIMARY KEY); +INSERT INTO test_table_int SELECT generate_series(1, 20); +SELECT yb_hash_code(x), x FROM test_table_int; +DROP TABLE test_table_int; + +CREATE TABLE test_table_real (x REAL PRIMARY KEY); +INSERT INTO test_table_real SELECT generate_series(1, 20); +SELECT yb_hash_code(x), x FROM test_table_real; +DROP TABLE test_table_real; + +CREATE TABLE test_table_double (x DOUBLE PRECISION PRIMARY KEY); +INSERT INTO test_table_double SELECT generate_series(1, 20); +SELECT yb_hash_code(x), x FROM test_table_double; +DROP TABLE test_table_double; + +CREATE TABLE test_table_small (x SMALLINT PRIMARY KEY); +INSERT INTO test_table_small SELECT generate_series(1, 20); +SELECT yb_hash_code(x), x FROM test_table_small; +DROP TABLE test_table_small; + +CREATE TABLE test_table_text (x TEXT PRIMARY KEY); +INSERT INTO test_table_text SELECT generate_series(800001, 800020); +SELECT yb_hash_code(x), x FROM test_table_text; +DROP TABLE test_table_text; + +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); +SELECT yb_hash_code('sad'::mood); +SELECT yb_hash_code('happy'::mood); +CREATE TABLE test_table_mood (x mood, y INT, PRIMARY KEY((x,y) HASH)); +INSERT INTO test_table_mood VALUES ('sad'::mood, 1), ('happy'::mood, 4), +('ok'::mood, 4), ('sad'::mood, 34), ('ok'::mood, 23); +SELECT yb_hash_code(x,y), * FROM test_table_mood; +DROP TABLE test_table_mood; +DROP TYPE mood; + +-- test basic pushdown on a table with one primary hash key column +CREATE TABLE test_table_one_primary (x INT PRIMARY KEY, y INT); +INSERT INTO test_table_one_primary SELECT i,i FROM generate_series(1, 10000) i; +\set query ':P SELECT * FROM test_table_one_primary WHERE yb_hash_code(x) = 10427;' +\i :iter_P2 +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM test_table_one_primary WHERE yb_hash_code(x) < 512; +SELECT * FROM test_table_one_primary WHERE yb_hash_code(x) < 512 LIMIT 5; +\set query ':P SELECT x, yb_hash_code(x) FROM test_table_one_primary WHERE x IN (1, 2, 3, 4) AND yb_hash_code(x) < 50000 ORDER BY x;' +\i :iter_P2 +\set query ':P SELECT yb_hash_code(x) FROM test_table_one_primary WHERE yb_hash_code(x) <= 20 AND yb_hash_code(x) < 9;' +\i :iter_P2 +\set query ':P SELECT yb_hash_code(x) FROM test_table_one_primary WHERE yb_hash_code(x) <= 20 AND yb_hash_code(x) > 90;' +\i :iter_P2 + +-- this should not be pushed down as (x,y) is not a hash primary key yet +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM test_table_one_primary WHERE yb_hash_code(x, y) < 512; +SELECT * FROM test_table_one_primary WHERE yb_hash_code(x, y) < 512 LIMIT 5; + +-- pushdown on yb_hash_code(x,y) should work after this index +-- on (x,y) hash is created +CREATE INDEX test_secondary ON test_table_one_primary ((x, y) HASH); +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM test_table_one_primary WHERE yb_hash_code(x, y) < 512; +SELECT * FROM test_table_one_primary WHERE yb_hash_code(x, y) < 512 LIMIT 5; +\set query ':P SELECT * FROM test_table_one_primary WHERE yb_hash_code(x, y) = 10;' +\i :iter_P2 +\set query ':P SELECT yb_hash_code(x, y) FROM test_table_one_primary WHERE yb_hash_code(x, y) <= 20 AND yb_hash_code(x, y) < 11;' +\i :iter_P2 +\set query ':P SELECT yb_hash_code(x, y) FROM test_table_one_primary WHERE yb_hash_code(x, y) <= 20 AND yb_hash_code(x, y) > 110;' +\i :iter_P2 + + +-- testing with a qualification on yb_hash_code(x) and x +\set query ':P SELECT * FROM test_table_one_primary WHERE yb_hash_code(x) < 512 AND x < 90;' +\i :iter_P2 + +-- should not be pushed down as the selectivity of this filter is too high +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM test_table_one_primary WHERE yb_hash_code(x, y) < 60000; + +-- should select the more selective index on x +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM test_table_one_primary WHERE yb_hash_code(x, y) < 60000 AND yb_hash_code(x) > 60000; + +-- this should not be pushed down as the order of (x,y) is not correct +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM test_table_one_primary WHERE yb_hash_code(y,x) < 512; +SELECT * FROM test_table_one_primary WHERE yb_hash_code(y,x) < 512 LIMIT 7; + +-- should not be pushed down as we don't support pushdown on IN filters yet +\set query ':P SELECT * FROM test_table_one_primary WHERE yb_hash_code(x, y) IN (1, 200, 326);' +\i :iter_P2 + +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * FROM test_table_one_primary WHERE yb_hash_code(x, y) BETWEEN 4 AND 512; +SELECT * FROM test_table_one_primary WHERE yb_hash_code(x, y) BETWEEN 4 AND 512 LIMIT 5; +DROP TABLE test_table_one_primary; + +-- testing pushdown where the hash column is of type text +CREATE TABLE text_table (hr text, ti text, tj text, i int, j int, primary key (hr)); +INSERT INTO text_table SELECT i::TEXT, i::TEXT, i::TEXT, i, i FROM generate_series(1,10000) i; +\set query ':P SELECT * FROM text_table WHERE yb_hash_code(hr) = 30;' +\i :iter_P2 + +-- testing pushdown on a secondary index with a text hash column +CREATE INDEX textidx ON text_table (tj); +\set query ':P SELECT * FROM text_table WHERE yb_hash_code(tj) = 63;' +\i :iter_P2 +\set query ':P SELECT * FROM text_table WHERE yb_hash_code(tj) <= 63;' +\i :iter_P2 +\set query ':P SELECT tj FROM text_table WHERE yb_hash_code(tj) <= 63;' +\i :iter_P2 +\set query ':P SELECT hr FROM text_table WHERE yb_hash_code(tj) < 63;' +\i :iter_P2 +\set query ':P SELECT tj FROM text_table WHERE 63 >= yb_hash_code(tj);' +\i :iter_P2 +DROP TABLE text_table; + +-- testing on a table with multiple hash key columns on +-- multiple types +CREATE TABLE test_table_multi_col_key(h1 BIGINT, h2 FLOAT, h3 TEXT, r1 TIMESTAMPTZ, r2 DOUBLE PRECISION, v1 INT, v2 DATE, v3 BOOLEAN, PRIMARY KEY ((h1, h2, h3) HASH, r1, r2)); +INSERT INTO test_table_multi_col_key SELECT i::BIGINT, i::FLOAT, i::TEXT, '2018-12-18 04:59:54-08'::TIMESTAMPTZ, i::DOUBLE PRECISION, i::INT, '2016-06-02'::DATE,(i%2)::BOOLEAN FROM generate_series(1, 10000) i; +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(h1,h2,h3) < 60;' +\i :iter_P2 + +-- limit and order by +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(h1,h2,h3) < 60 LIMIT 3;' +\i :iter_P2 +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(h1,h2,h3) < 60 ORDER BY h1;' +\i :iter_P2 +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(h1,h2,h3) < 60 ORDER BY h1 LIMIT 3;' +\i :iter_P2 + +-- create an index with the same set of primary keys as +-- the primary index +CREATE INDEX multi_key_index_1 ON test_table_multi_col_key((h1,h2,h3) HASH, r1 ASC, r2 ASC); + +-- create other indexes on other columsn that are not +-- hashed in the primary index +CREATE INDEX multi_key_index_2 ON test_table_multi_col_key((r1, r2, v1) HASH, v3, v2); +CREATE INDEX multi_key_index_3 ON test_table_multi_col_key((r1, r2, v2, v3) HASH, v1); + +-- index only scan on multi_key_index +\set query ':P SELECT h1 from test_table_multi_col_key WHERE yb_hash_code(h1,h2,h3) < 60;' +\i :iter_P2 + +-- index scan on primary key index +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(h1,h2,h3) < 60;' +\i :iter_P2 + +-- sequential scan as the selectivity of this filter is +-- high +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(h1,h2,h3) < 60000 LIMIT 10;' +\i :iter_P2 + +-- testing pushdown where the input to the yb_hash_code +-- does not match any index hash key + +-- sequential scan as no index has (h1,h2,h3,v1) as the +-- hash key +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(h1,h2,h3,v1) < 60 LIMIT 10;' +\i :iter_P2 + +-- sequential scan as no index has (h1,h3,h2) as the +-- hash key +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(h1,h3,h2) < 60 LIMIT 10;' +\i :iter_P2 + +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(r1,v3,v2,r2) < 60 LIMIT 10;' +\i :iter_P2 + +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(r2,r1,v1) < 60 LIMIT 10;' +\i :iter_P2 + +-- pushdown for multi_key_index_2 and multi_key_index_3 +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(r1,r2,v1) < 60 LIMIT 10;' +\i :iter_P2 + +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(r1,r2,v2,v3) < 60 LIMIT 10;' +\i :iter_P2 + +-- cost model tests to make sure that pushdown occurs on the +-- most selective yb_hash_code filter +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * from test_table_multi_col_key WHERE yb_hash_code(r1,r2,v2,v3) < 600 AND yb_hash_code(h1,h2,h3) < 65500 AND yb_hash_code(r1, r2, v1) > 5500; +SELECT * from test_table_multi_col_key WHERE yb_hash_code(r1,r2,v2,v3) < 600 AND yb_hash_code(h1,h2,h3) < 65500 AND yb_hash_code(r1, r2, v1) > 5500 LIMIT 10; + +\set query ':P SELECT * from test_table_multi_col_key WHERE yb_hash_code(r1,r2,v2,v3) < 600 AND yb_hash_code(h1,h2,h3) > 65500 AND yb_hash_code(r1, r2, v1) > 5500;' +\i :iter_P2 + +-- all given filters here have very high selectivity so this +-- should be a sequential scan +EXPLAIN (COSTS OFF, TIMING OFF, SUMMARY OFF, ANALYZE) SELECT * from test_table_multi_col_key WHERE yb_hash_code(r1,r2,v2,v3) > 600 AND yb_hash_code(h1,h2,h3) < 65500 AND yb_hash_code(r1, r2, v1) > 5500; +SELECT * from test_table_multi_col_key WHERE yb_hash_code(r1,r2,v2,v3) > 600 AND yb_hash_code(h1,h2,h3) < 65500 AND yb_hash_code(r1, r2, v1) > 5500 LIMIT 10; + +DROP TABLE test_table_multi_col_key; + +-- test recheck of index only scan with yb_hash_code +CREATE TABLE test_index_only_scan_recheck(k INT PRIMARY KEY, v1 INT, v2 INT, v3 INT, v4 INT); +CREATE INDEX ON test_index_only_scan_recheck(v4) INCLUDE (v1); +INSERT INTO test_index_only_scan_recheck SELECT s, s, s, s, s FROM generate_series(1, 100) AS s; +\set query ':P SELECT v1, yb_hash_code(v4) FROM test_index_only_scan_recheck WHERE v4 IN (1, 2, 3) AND yb_hash_code(v4) < 50000;' +\i :iter_P2 + +DROP TABLE test_index_only_scan_recheck; + +-- Issue #17043 +CREATE TABLE t as select x, x as y from generate_series(1, 10) x; +CREATE INDEX t_x_hash_y_asc_idx ON t (x HASH, y ASC); +\set query ':P SELECT yb_hash_code(x), y FROM t WHERE yb_hash_code(x) = 2675 AND y IN (5, 6);' +\i :iter_P2 +DROP TABLE t; + +CREATE TABLE tt (i int, j int); +CREATE INDEX ON tt (i, j); + +-- Issue #18360 (yb_hash_code compared to constant out of the range [0..65535]) +INSERT INTO tt VALUES (1, 2); +-- Negative values +\set explain 'EXPLAIN (COSTS OFF)' +\set Q1 '/*+IndexScan(tt)*/' +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) > -1;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) >= -2;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) = -3;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) < -4;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) <= -5;' +\i :iter_P2 + +-- Higher than upper bound values +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) > 65536;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) >= 65537;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) = 65538;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) < 65539;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) <= 65540;' +\i :iter_P2 + +-- Values other than int4 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) > -2147483649;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) >= -2147483650;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) = -2147483651;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) < -2147483652;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) <= -2147483653;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) > 9223372036854775808;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) >= 9223372036854775809;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) = 9223372036854775810;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) < 9223372036854775811;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) <= 9223372036854775812;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) > -0.01;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) >= 123456.78;' +\i :iter_P2 +\set query ':P :Q1 SELECT * FROM tt WHERE yb_hash_code(i) = 3.14;' +\i :iter_P2 + +-- GH18347 : yb_hash_code() in row constructor in predicate with an inequality fails +TRUNCATE TABLE tt; +INSERT INTO tt VALUES(0, 0); +INSERT INTO tt VALUES(0, 1); +INSERT INTO tt VALUES(0, 2); +INSERT INTO tt VALUES(0, 3); +INSERT INTO tt VALUES(1, 0); +INSERT INTO tt VALUES(1, 1); +INSERT INTO tt VALUES(2, 2); +INSERT INTO tt VALUES(3, 3); +INSERT INTO tt VALUES(2147483647, 0); +INSERT INTO tt VALUES(2147483647, 1); +INSERT INTO tt VALUES(2147483647, 2); +INSERT INTO tt VALUES(2147483647, 3); +INSERT INTO tt VALUES(-2147483648, 0); +INSERT INTO tt VALUES(-2147483648, 1); +INSERT INTO tt VALUES(-2147483648, 2); +INSERT INTO tt VALUES(-2147483648, 3); + +-- Failing query pattern. +\set Q1 '/*+IndexOnlyScan(tt)*/' +\set Q2 '/*+SeqScan(tt)*/' +\set query ':P :Q SELECT i, j, yb_hash_code(i) hash_code_i, yb_hash_code(1) hash_code_1 FROM tt WHERE row(j, yb_hash_code(i)) > row(1, yb_hash_code(1)) ORDER BY 1, 2;' +\set Pnext :iter_Q2 +\i :iter_P2 + +-- Try variation. +\set query ':P :Q SELECT i, j, yb_hash_code(i) hash_code_i, yb_hash_code(2) hash_code_2 FROM tt WHERE row(j, yb_hash_code(i)) < row(1, yb_hash_code(2)) ORDER BY 1, 2;' +\i :iter_P2 + +-- Try a 1 element IN with row constructor. Should get an index scan since this turns into an equality. +\set query ':P :Q SELECT i, j, yb_hash_code(i) hash_code_i, yb_hash_code(1) hash_code_1 FROM tt WHERE row(j, yb_hash_code(i)) IN (row(1, yb_hash_code(1))) ORDER BY 1, 2;' +\i :iter_P2 + +-- Try an IN with yb_hash_code(). +-- TODO(#23362): expect index condition to be pushed down rather than PG filter. +\set query ':P :Q SELECT i, j, yb_hash_code(i) hash_code_i, yb_hash_code(1) hash_code_1, yb_hash_code(2) hash_code_2 FROM tt WHERE yb_hash_code(i) IN (yb_hash_code(1), yb_hash_code(2)) ORDER BY 1, 2;' +\i :iter_P2 + +drop table tt; diff --git a/src/test/regress/sql/yb.orig.yb_index_check.sql b/src/test/regress/sql/yb.orig.yb_index_check.sql new file mode 100644 index 0000000000000..a1ba0797366e8 --- /dev/null +++ b/src/test/regress/sql/yb.orig.yb_index_check.sql @@ -0,0 +1,214 @@ +SET yb_bnl_batch_size = 3; + +-- Basic test +CREATE TABLE abcd(a int primary key, b int, c int, d int) SPLIT INTO 1 TABLETS; +CREATE INDEX abcd_b_c_d_idx ON abcd (b ASC) INCLUDE (c, d); +INSERT INTO abcd SELECT i, i, i, i FROM generate_series(1, 10) i; +EXPLAIN (ANALYZE, DIST, COSTS OFF, SUMMARY OFF) SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); +DROP TABLE abcd; + +-- Set up +CREATE TABLE abcd(a int primary key, b int, c int, d int); +CREATE INDEX abcd_b_c_d_idx ON abcd (b ASC) INCLUDE (c, d); +INSERT INTO abcd SELECT i, i, i, i FROM generate_series(1, 10) i; + +-- Partial index +CREATE INDEX abcd_b_c_idx ON abcd(b) INCLUDE (c) WHERE d > 5; +SELECT yb_index_check('abcd_b_c_idx'::regclass::oid); + +CREATE OR REPLACE FUNCTION double_value(input_value NUMERIC) +RETURNS NUMERIC AS $$ +BEGIN + RETURN input_value * 2; +END; +$$ LANGUAGE plpgsql IMMUTABLE; + +CREATE INDEX abcd_b_c_idx1 ON abcd(b) INCLUDE (c) WHERE double_value(d) > 5; +SELECT yb_index_check('abcd_b_c_idx1'::regclass::oid); + +-- Expression index +CREATE INDEX abcd_expr_expr1_d_idx ON abcd ((2*c) ASC, (2*b) ASC) INCLUDE (d); +SELECT yb_index_check('abcd_expr_expr1_d_idx'::regclass::oid); +CREATE INDEX abcd_double_value_d_idx ON abcd (double_value(c) ASC) INCLUDE (d); +SELECT yb_index_check('abcd_double_value_d_idx'::regclass::oid); + +-- Unique index +CREATE UNIQUE INDEX abcd_b_c_d_idx1 ON abcd (b ASC) INCLUDE (c, d); +SELECT yb_index_check('abcd_b_c_d_idx1'::regclass::oid); + +-- NULLS NOT DISTINCT +CREATE UNIQUE INDEX abcd_b_c_d_idx2 ON abcd (b ASC) INCLUDE (c, d) NULLS NOT DISTINCT; +SELECT yb_index_check('abcd_b_c_d_idx2'::regclass::oid); + +-- Index does not exist +SELECT yb_index_check('nonexisting_idx'::regclass::oid); + +-- PK index +SELECT yb_index_check('abcd_pkey'::regclass::oid); + +-- Non-LSM index +CREATE TEMP TABLE mytemp(a int unique); +SELECT yb_index_check('mytemp_a_key'::regclass::oid); + +-- Non index relation oid +SELECT yb_index_check('abcd'::regclass::oid); + +-- Types without equality operator +CREATE TABLE json_table (a TEXT, b JSON, c INT); +CREATE INDEX json_table_a_b_idx ON json_table(a) INCLUDE (b); +INSERT INTO json_table VALUES ('test', '{"test1":"test2", "test3": 4}', 1); +SELECT yb_index_check('json_table_a_b_idx'::regclass::oid); + +-- Inconsistent Indexes +SELECT oid AS db_oid FROM pg_database WHERE datname = ( + SELECT CASE + WHEN COUNT(*) = 1 THEN 'template1' + ELSE current_database() END FROM pg_yb_catalog_version) \gset +SELECT +$force_cache_refresh$ +SET yb_non_ddl_txn_for_sys_tables_allowed TO on; +UPDATE pg_yb_catalog_version + SET current_version = current_version + 1, + last_breaking_version = current_version + 1 + WHERE db_oid = :db_oid; +RESET yb_non_ddl_txn_for_sys_tables_allowed; +DO +$$ +BEGIN + PERFORM pg_sleep(1); +END; +$$; +$force_cache_refresh$ AS force_cache_refresh \gset + +-- Missing Index Row +UPDATE pg_index SET indisready = FALSE, indisvalid = FALSE, indislive = FALSE WHERE indexrelid = 'abcd_b_c_d_idx'::regclass; +:force_cache_refresh +INSERT INTO abcd VALUES (11, 11, 11, 11); +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); +UPDATE pg_index SET indisready = TRUE, indisvalid = TRUE, indislive = TRUE WHERE indexrelid = 'abcd_b_c_d_idx'::regclass; +:force_cache_refresh +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); + +DELETE FROM abcd WHERE a = 11; -- Reset +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); + +-- Spurious Index Row +UPDATE pg_index SET indisready = FALSE, indisvalid = FALSE, indislive = FALSE WHERE indexrelid = 'abcd_b_c_d_idx'::regclass; +:force_cache_refresh +DELETE FROM abcd WHERE a = 1; +UPDATE pg_index SET indisready = TRUE, indisvalid = TRUE, indislive = TRUE WHERE indexrelid = 'abcd_b_c_d_idx'::regclass; +:force_cache_refresh +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); + +INSERT INTO abcd VALUES (1, 1, 1, 1); +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); + +-- Spurious Index Row with NULL Index Attributes +INSERT INTO abcd VALUES (11, NULL, NULL, NULL); +UPDATE pg_index SET indisready = FALSE, indisvalid = FALSE, indislive = FALSE WHERE indexrelid = 'abcd_b_c_d_idx'::regclass; +:force_cache_refresh +DELETE FROM abcd WHERE a = 11; +UPDATE pg_index SET indisready = TRUE, indisvalid = TRUE, indislive = TRUE WHERE indexrelid = 'abcd_b_c_d_idx'::regclass; +:force_cache_refresh +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); + +INSERT INTO abcd VALUES (11, NULL, NULL, NULL); -- Reset +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); + +-- Inconsistent Non-Key Column +UPDATE pg_index SET indisready = FALSE, indisvalid = FALSE, indislive = FALSE WHERE indexrelid = 'abcd_b_c_d_idx'::regclass; +:force_cache_refresh +UPDATE abcd SET d = d + 1 WHERE a = 1; +UPDATE pg_index SET indisready = TRUE, indisvalid = TRUE, indislive = TRUE WHERE indexrelid = 'abcd_b_c_d_idx'::regclass; +:force_cache_refresh +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); + +UPDATE abcd SET d = 1 WHERE a = 1; +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); + +-- Inconsistent Key Column +UPDATE pg_index SET indisready = FALSE, indisvalid = FALSE, indislive = FALSE WHERE indexrelid = 'abcd_b_c_d_idx'::regclass; +:force_cache_refresh +UPDATE abcd SET b = 9999 WHERE a = 1; +UPDATE pg_index SET indisready = TRUE, indisvalid = TRUE, indislive = TRUE WHERE indexrelid = 'abcd_b_c_d_idx'::regclass; +:force_cache_refresh +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); + +UPDATE abcd SET b = 1 WHERE a = 1; +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); + +-- Spurious Row Due to Partial Index +UPDATE pg_index SET indisready = FALSE, indisvalid = FALSE, indislive = FALSE WHERE indexrelid = 'abcd_b_c_idx'::regclass; +:force_cache_refresh +UPDATE abcd SET d = 5 WHERE a = 6; +UPDATE pg_index SET indisready = TRUE, indisvalid = TRUE, indislive = TRUE WHERE indexrelid = 'abcd_b_c_idx'::regclass; +:force_cache_refresh +SELECT yb_index_check('abcd_b_c_idx'::regclass::oid); + +UPDATE abcd SET d = 6 WHERE a = 6; +SELECT yb_index_check('abcd_b_c_idx'::regclass::oid); + +-- Index of a partitioned table +CREATE TABLE part(a int, b int, c int, d int) PARTITION BY RANGE(a); +CREATE INDEX ON part(b) include (c, d); +CREATE TABLE part_1_20 PARTITION OF part FOR VALUES FROM(1) TO (20); +CREATE TABLE part_2 PARTITION OF part FOR VALUES FROM(20) TO (60) PARTITION BY RANGE(a); +CREATE TABLE part_20_40 PARTITION OF part_2 FOR VALUES FROM(20) TO (40); +CREATE TABLE part_40_60 PARTITION OF part_2 FOR VALUES FROM(40) TO (60); +INSERT INTO part SELECT i, i, i, i FROM generate_series(1, 59) i; + +SELECT yb_index_check('part_b_c_d_idx'::regclass::oid); +SELECT yb_index_check('part_1_20_b_c_d_idx'::regclass::oid); +SELECT yb_index_check('part_2_b_c_d_idx'::regclass::oid); +SELECT yb_index_check('part_20_40_b_c_d_idx'::regclass::oid); +SELECT yb_index_check('part_40_60_b_c_d_idx'::regclass::oid); + +-- Index of materialized view +CREATE MATERIALIZED VIEW matview AS SELECT * FROM abcd; +CREATE INDEX matview_b_idx ON matview (b); +SELECT yb_index_check('matview_b_idx'::regclass); +-- Execute the same command second time to check if the dummy index fields are free'd up. +-- See yb_dummy_baserel_index_open()/yb_free_dummy_baserel_index(). +SELECT yb_index_check('matview_b_idx'::regclass); + +-- GIN indexes (not support yet) +CREATE TABLE vectors (i serial PRIMARY KEY, v tsvector); +CREATE INDEX ON vectors USING ybgin (v) where i > 5; +SELECT yb_index_check('vectors_v_idx'::regclass::oid); + +-- Variable length data type +CREATE TABLE test_bytea(a int primary key, b int, c bytea); +CREATE INDEX test_bytea_b_c_idx ON test_bytea (b ASC) INCLUDE (c); +CREATE INDEX test_bytea_c_b_idx ON test_bytea (c) INCLUDE (b); +INSERT INTO test_bytea (a, b, c) VALUES (1, 42, E'\\x48656c6c6f20576f726c64'); +INSERT INTO test_bytea (a, b, c) VALUES (2, 42, 'test'); +SELECT yb_index_check('test_bytea_b_c_idx'::regclass::oid); +SELECT yb_index_check('test_bytea_c_b_idx'::regclass::oid); + +-- Inside a transaction block +BEGIN; +SELECT yb_index_check('abcd_b_c_d_idx'::regclass::oid); +COMMIT; + +-- Test with more data +INSERT INTO abcd SELECT i, i, i, i FROM generate_series(12, 2000) i; +INSERT INTO abcd values (2002, 2002, 1, 2); +INSERT INTO abcd values (2003, 2003, 3, NULL); +INSERT INTO abcd values (2004, 2004, NULL, 4); +SELECT yb_index_check('abcd_b_c_idx'::regclass::oid); + +-- Index of a colocated relation +CREATE DATABASE colocateddb COLOCATION = TRUE; +\c colocateddb +SET yb_bnl_batch_size = 3; +CREATE TABLE abcd1(a int primary key, b int, c int, d int); +CREATE INDEX abcd1_b_c_d_idx ON abcd1(b ASC) INCLUDE (c, d); +INSERT INTO abcd1 SELECT i, i, i, i FROM generate_series(1, 1000) i; + +CREATE TABLE abcd2(a int primary key, b int, c int, d int); +CREATE INDEX abcd2_b_c_d_idx ON abcd2(b ASC) INCLUDE (c, d); +INSERT INTO abcd2 SELECT i, i, i, i FROM generate_series(1, 1000) i; + +SELECT yb_index_check('abcd1_b_c_d_idx'::regclass::oid); +SELECT yb_index_check('abcd2_b_c_d_idx'::regclass::oid); diff --git a/src/test/regress/sql/yb.orig.ybgin.sql b/src/test/regress/sql/yb.orig.ybgin.sql new file mode 100644 index 0000000000000..32b73317d59dc --- /dev/null +++ b/src/test/regress/sql/yb.orig.ybgin.sql @@ -0,0 +1,248 @@ +-- +-- Yugabyte-owned test for ybgin access method. +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (costs off)' + +-- Always choose index scan. +SET enable_seqscan = off; +SET enable_bitmapscan = on; +SET yb_test_ybgin_disable_cost_factor = 0.5; + +-- +-- Create non-temp table which uses Yugabyte storage. +-- +CREATE TABLE vectors (i serial PRIMARY KEY, v tsvector); +CREATE TABLE arrays (i serial PRIMARY KEY, a int[]); +CREATE TABLE jsonbs (i serial PRIMARY KEY, j jsonb); +CREATE TABLE multi (v tsvector, a1 text[], a2 float[], j1 jsonb, j2 jsonb); +CREATE TABLE expression (v tsvector, a text[], j jsonb); +CREATE TABLE partial (v tsvector, a text[], j jsonb); + +-- +-- tsvector +-- +INSERT INTO vectors (v) VALUES + (to_tsvector('simple', 'a bb ccc')), + (to_tsvector('simple', 'bb a e i o u')), + (to_tsvector('simple', 'ccd')); +CREATE INDEX ON vectors USING ybgin (v); -- test ambuild +INSERT INTO vectors (v) VALUES + (to_tsvector('simple', 'a a a a a a')), + (to_tsvector('simple', 'cc')); -- test aminsert +-- test amgetbitmap +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'a') ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'a & e') ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'bb | cc') ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'bb & !ccc') ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ to_tsquery('simple', 'cc:*') ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT count(*) FROM vectors WHERE v @@ to_tsquery('simple', 'a'); +$$ AS query \gset +\i :iter_P2 + +-- +-- array +-- +INSERT INTO arrays (a) VALUES + ('{1, 3, 5}'), + (ARRAY[2, 3, 5]), + ('{7, 1, 6, 4}'); +CREATE INDEX ON arrays USING ybgin (a); -- test ambuild +INSERT INTO arrays (a) VALUES + ('{3}'), + ('{3, 3, 3}'), + ('{10, 20, 30}'); -- test aminsert +-- test amgetbitmap +SELECT $$ +:P SELECT * FROM arrays WHERE a && '{1, 100, 3}' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM arrays WHERE a @> '{5, 3}' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM arrays WHERE a <@ '{5, 4, 3, 2}' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM arrays WHERE a = '{3}' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT count(*) FROM arrays WHERE a = '{3}'; +$$ AS query \gset +\i :iter_P2 + +-- +-- jsonb +-- +INSERT INTO jsonbs (j) VALUES + ('{"aaa":[1,2.00,3]}'), + ('{"ggg":"aaa"}'), + ('["bbb", "aaa"]'); +CREATE INDEX ON jsonbs USING ybgin (j); -- test ambuild +INSERT INTO jsonbs (j) VALUES + ('"aaa"'), + ('3.0'), + ('{"aaa":{"bbb":[2,4], "ccc":{"ddd":6}}, "eee":8}'); -- test aminsert +-- test amgetbitmap +SELECT $$ +:P SELECT * FROM jsonbs WHERE j ? 'aaa' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM jsonbs WHERE j ?| '{"ggg", "eee"}' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM jsonbs WHERE j ?& '{"aaa", "eee"}' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @> '{"bbb":[4]}' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @> '{"aaa":{"bbb":[4]}}' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ == 2)' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @@ '$.ggg starts with "aa"' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT count(*) FROM jsonbs WHERE j ? 'aaa'; +$$ AS query \gset +\i :iter_P2 + +-- +-- jsonb_path +-- +DROP INDEX jsonbs_j_idx; +CREATE INDEX ON jsonbs USING ybgin (j jsonb_path_ops); -- test ambuild +INSERT INTO jsonbs (j) VALUES + ('{"aaa":{"bbb":[2], "ccc":{"ddd":6}}, "eee":[]}'); -- test aminsert +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j ? 'aaa' ORDER BY i; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j ?| '{"ggg", "eee"}' ORDER BY i; +EXPLAIN (costs off) SELECT * FROM jsonbs WHERE j ?& '{"aaa", "eee"}' ORDER BY i; +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @> '{"aaa":{"bbb":[4]}}' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ == 2)' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @@ '$.ggg starts with "aa"' ORDER BY i; +$$ AS query \gset +\i :iter_P2 + +-- +-- Multicolumn +-- +INSERT INTO multi VALUES + (to_tsvector('simple', 'a b'), ARRAY['c', 'd'], ARRAY[5.0, 6.1], '{"f":3}', + '{"r":[3,6.5]}'), + (to_tsvector('simple', '0'), ARRAY['0'], ARRAY[0], '0', '0'); +CREATE INDEX ON multi USING ybgin (v, a1); -- test ambuild +CREATE INDEX ON multi USING ybgin (a2, a2); -- test ambuild +CREATE INDEX ON multi USING ybgin (j1 jsonb_ops, j2 jsonb_path_ops); -- test ambuild + +-- +-- Expression index +-- +INSERT INTO expression VALUES + (to_tsvector('simple', 'a b c'), ARRAY['d', 'e', 'f'], '{"g":["h","i"]}'); +CREATE INDEX ON expression USING ybgin (tsvector_to_array(v)); -- test ambuild +CREATE INDEX ON expression USING ybgin (array_to_tsvector(a)); -- test ambuild +CREATE INDEX ON expression USING ybgin ( + jsonb_to_tsvector('simple', j, '["string"]')); -- test ambuild +INSERT INTO expression VALUES + (to_tsvector('simple', 'a a'), ARRAY['d', 'd'], '{"g":"g"}'); -- test aminsert +SELECT $$ +:P SELECT count(*) FROM expression WHERE tsvector_to_array(v) && ARRAY['b']; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT count(*) FROM expression WHERE array_to_tsvector(a) @@ 'e'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT count(*) FROM expression WHERE jsonb_to_tsvector('simple', j, '["string"]') @@ 'h'; +$$ AS query \gset +\i :iter_P2 + +-- +-- Partial index +-- +INSERT INTO partial VALUES + (to_tsvector('simple', 'a a'), ARRAY['d', 'e', 'f'], '{"g":["h","i"]}'); +CREATE INDEX ON partial USING ybgin (v) WHERE v @@ 'c'; -- test ambuild +CREATE INDEX ON partial USING ybgin (a) WHERE j @> '{"g":["i"]}'; -- test ambuild +CREATE INDEX ON partial USING ybgin (j) WHERE a && ARRAY['f']; -- test ambuild +INSERT INTO partial VALUES + (to_tsvector('simple', 'a b c'), ARRAY['d', 'd'], '{"g":"g"}'); -- test aminsert +SELECT $$ +:explain SELECT count(*) FROM partial WHERE v @@ 'b'; +$$ AS query \gset +:query +SELECT $$ +:P SELECT count(*) FROM partial WHERE v @@ 'c'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:explain SELECT count(*) FROM partial WHERE a && ARRAY['e']; +$$ AS query \gset +:query +SELECT $$ +:P SELECT count(*) FROM partial WHERE a && ARRAY['e'] and j @> '{"g":["i"]}'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:explain SELECT count(*) FROM partial WHERE j @? '$.g[*] ? (@ == "h")'; +$$ AS query \gset +:query +SELECT $$ +:P SELECT count(*) FROM partial WHERE j @? '$.g[*] ? (@ == "h")' and a && ARRAY['f']; +$$ AS query \gset +\i :iter_P2 + +-- Don't clean up the tables as they'll be used in later tests. + +-- +-- Try creating ybgin index on temp table. +-- +CREATE TEMP TABLE vectors (i serial PRIMARY KEY, v tsvector); +CREATE INDEX ON vectors USING ybgin (v); + +-- Cleanup +DISCARD TEMP; +RESET enable_seqscan; +RESET enable_bitmapscan; diff --git a/src/test/regress/sql/yb.orig.ybgin_cost_estimate.sql b/src/test/regress/sql/yb.orig.ybgin_cost_estimate.sql new file mode 100644 index 0000000000000..faffddf9f678d --- /dev/null +++ b/src/test/regress/sql/yb.orig.ybgin_cost_estimate.sql @@ -0,0 +1,67 @@ +-- +-- Yugabyte-owned test for ybgin index access method and cost estimates. +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (costs off)' + +SET enable_seqscan = off; +SHOW yb_test_ybgin_disable_cost_factor; +\set Q1 +\set Q2 '/*+SET(yb_test_ybgin_disable_cost_factor 0.5)*/' + +-- Given what is currently supported, these queries would fail, so they should +-- use sequential scan. +-- GIN_SEARCH_MODE_ALL +\set query ':explain :Q SELECT * FROM vectors WHERE v @@ to_tsquery(''simple'', ''!aa'');' +\i :iter_Q2 +-- multiple scan keys +\set query ':explain :Q SELECT * FROM vectors WHERE v @@ ''a'' and v @@ ''bb'';' +\i :iter_Q2 +-- multiple required scan entries +-- TODO(jason): this kind of query is hard to detect during cost estimation. +-- Though it can be done, it would be a lot of work, and in that case, it is +-- probably better to spend the time supporting the query instead. So it +-- currently will suggest index scan, unfortunately. +\set query ':explain :Q SELECT * FROM vectors WHERE v @@ to_tsquery(''simple'', ''aa | bb'');' +\i :iter_Q2 +-- GIN_CAT_NULL_KEY +-- TODO(jason): this kind of query is hard to detect during cost estimation. +-- Though it can be done, it would be a lot of work, and in that case, it is +-- probably better to spend the time supporting the query instead. So it +-- currently will suggest index scan, unfortunately. +\set query ':explain :Q SELECT * FROM arrays WHERE a @> ''{null}'';' +\i :iter_Q2 +CREATE INDEX NONCONCURRENTLY idx_partial ON arrays + USING ybgin (a) + WHERE a <@ '{1}' or a @> '{}' or a is null; +-- GIN_SEARCH_MODE_INCLUDE_EMPTY +\set query ':explain :Q SELECT * FROM arrays WHERE a <@ ''{1}'';' +\i :iter_Q2 +-- GIN_SEARCH_MODE_ALL +\set query ':explain :Q SELECT * FROM arrays WHERE a @> ''{}'';' +\i :iter_Q2 +-- GIN_SEARCH_MODE_EVERYTHING, GIN_CAT_NULL_ITEM +\set query ':explain :Q SELECT * FROM arrays WHERE a is null;' +\i :iter_Q2 +-- Cleanup +DROP INDEX idx_partial; +-- multiple required scan entries +-- TODO(jason): this kind of query is hard to detect during cost estimation. +-- Though it can be done, it would be a lot of work, and in that case, it is +-- probably better to spend the time supporting the query instead. So it +-- currently will suggest index scan, unfortunately. +\set query ':explain :Q SELECT * FROM jsonbs WHERE j ?| ''{"ggg", "eee"}'';' +\i :iter_Q2 +-- GIN_SEARCH_MODE_ALL +\set query ':explain :Q SELECT * FROM jsonbs WHERE j @? ''$.aaa[*] ? (@ > 2)'';' +\i :iter_Q2 + +-- On the other hand, these queries would succeed, so they should use index +-- scan. +\set query ':explain :Q SELECT * FROM vectors WHERE v @@ to_tsquery(''simple'', ''(aa | bb) & (cc & dd)'');' +\i :iter_Q2 diff --git a/src/test/regress/sql/yb.orig.ybgin_misc.sql b/src/test/regress/sql/yb.orig.ybgin_misc.sql new file mode 100644 index 0000000000000..598fd0bc10564 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ybgin_misc.sql @@ -0,0 +1,274 @@ +-- +-- Yugabyte-owned test on ybgin index access method for things not covered +-- elsewhere. +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (costs off)' + +-- Always choose index scan. +SET enable_seqscan = off; +SET yb_test_ybgin_disable_cost_factor = 0.5; + +-- Set jsonbs to have jsonb_ops index, not jsonb_path_ops index. +DROP INDEX jsonbs_j_idx; +CREATE INDEX ON jsonbs USING ybgin (j); + +-- +-- DELETE/UPDATE +-- + +-- tsvector +INSERT INTO vectors (v) VALUES ('a b c i k'), ('abc j k'); +SELECT $$ +:P UPDATE vectors SET v = ts_delete(v, '{c, j}'::text[]) WHERE v @@ 'k'; +$$ AS query \gset +\i :iter_P2 +SELECT v FROM vectors WHERE v @@ 'k' ORDER BY i; +DELETE FROM vectors WHERE v @@ 'k'; +-- jsonb +INSERT INTO jsonbs (j) VALUES ('[4,7,"sh",3,"du"]'), ('{"du":[7,9], "sh":13}'); +SELECT $$ +:P UPDATE jsonbs SET j = j - 'sh' WHERE j ? 'du'; +$$ AS query \gset +\i :iter_P2 +SELECT j FROM jsonbs WHERE j ? 'du' ORDER BY i; +DELETE FROM jsonbs WHERE j ? 'du'; + +-- +-- Long strings. +-- +DO $$ +DECLARE + c int; + s text := '0000000000' + || '1111111111' + || '2222222222' + || '3333333333' + || '4444444444' + || '5555555555' + || '6666666666' + || '7777777777' + || '8888888888' + || '9999999999' + || 'aaaaaaaaaa' + || 'bbbbbbbbbb' + || 'cccccccccc' + || 'dddddddddd' + || 'eeeeeeeeee' + || 'ffffffffff'; +BEGIN + -- tsvector_ops + INSERT INTO vectors (v) VALUES (to_tsvector('simple', s)); + SELECT count(*) FROM vectors WHERE v @@ to_tsquery('simple', s) INTO c; + RAISE NOTICE 'tsvector_ops count: %', c; + DELETE FROM vectors WHERE v @@ to_tsquery('simple', s); + -- jsonb_ops + INSERT INTO jsonbs (j) VALUES (('"' || s || '"')::jsonb); + SELECT count(*) FROM jsonbs WHERE j ? s INTO c; + RAISE NOTICE 'jsonb_ops count: %', c; + DELETE FROM jsonbs WHERE j ? s; +END $$; + +-- +-- Prefix match without greater string. +-- +INSERT INTO vectors (v) VALUES + (E'\x7F\x7F\x7F'), + (E'\x7F\x7F\x7Fa'), + (E'\x7F\x7F\x7F\x7F'), + (E'\x7F\x7F'); +SELECT $$ +:P SELECT v FROM vectors WHERE v @@ E'\x7F\x7F\x7F:*' ORDER BY i; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P DELETE FROM vectors WHERE v @@ E'\x7F:*'; +$$ AS query \gset +\i :iter_P2 + +-- +-- Prefix match with other odd characters. +-- +INSERT INTO vectors (v) VALUES + (E'\x7F\u00bf'), + (E'\x7F\u00c0'), + (E'\x7F\u00cf'), + (E'\x7F\u00d0'), + (E'\x7F\u00df'), + (E'\x7F\u00e0'), + (E'\x7F\u00ef'), + (E'\x7F\u00f0'), + (E'\x7F\u00ff'), + (E'\x7F\u0100'), + (E'\x7F\u013f'), + (E'\x7F\u0140'), + (E'\x7F\u017f'), + (E'\x7F\u0180'), + (E'\x7F\u07ff'), + (E'\x7F\u0800'), + (E'\x7F\ufeff'), + (E'\x7F\uff00'), + (E'\x7F\uffff'), + (E'\x7F\U00010000'), + (E'\x7F\U0001ffff'), + (E'\x7F\U00100000'), + (E'\x7F\U0010ffff'), + (E'\x7F\U0010ffff\x7F'); +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\u00bf:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\u00cf:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\u00df:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\u00ef:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\u00ff:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\u013f:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\u017f:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\u07ff:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\ufeff:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\uffff:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\U0001ffff:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F\U0010ffff:*'; +SELECT count(*) FROM vectors WHERE v @@ E'\x7F:*'; +DELETE FROM vectors WHERE v @@ E'\x7F:*'; + +-- +-- Three ways to query tsvectors containing both of two specific words. +-- + +-- AND inside tsquery +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ 'a & bb'; +$$ AS query \gset +\i :iter_P2 +-- AND between '@@' ops +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ 'a' and v @@ 'bb'; +$$ AS query \gset +\i :iter_P2 +-- INTERSECT between '@@' selects +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ 'a' INTERSECT SELECT * FROM vectors WHERE v @@ 'bb'; +$$ AS query \gset +\i :iter_P2 + +-- +-- Three ways to query tsvectors containing at least one of two specific words. +-- + +-- OR inside tsquery +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ 'a | c'; +$$ AS query \gset +\i :iter_P2 +-- OR between '@@' ops +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ 'a' or v @@ 'c'; +$$ AS query \gset +\i :iter_P2 +-- UNION between '@@' selects +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ 'a' UNION SELECT * FROM vectors WHERE v @@ 'c'; +$$ AS query \gset +\i :iter_P2 + +-- +-- Complicated query +-- + +-- Top-level disjunction where the last item is simple +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ '((a & e) | (ccc & ccd)) & o'; +$$ AS query \gset +\i :iter_P2 +-- Top-level disjunction where the last item is complex +SELECT $$ +:P SELECT * FROM vectors WHERE v @@ 'o & ((a & e) | (ccc & ccd))'; +$$ AS query \gset +\i :iter_P2 +-- TODO(jason): add top-level conjunction tests + +-- +-- Index build without online schema change +-- +CREATE INDEX NONCONCURRENTLY idx_nonconcurrent ON jsonbs USING ybgin (j jsonb_path_ops); +DROP INDEX idx_nonconcurrent; + +-- +-- Various array types +-- +CREATE TABLE manyarrays ( + a text[], + b int2[], + c char[], + d oidvector[], + e pg_lsn[], + f timestamptz[]); +INSERT INTO manyarrays VALUES ( + ARRAY['abc', '123'], + ARRAY[123, -456], + ARRAY['g', 'h', 'i'], + ARRAY['123456 0', '4294967295', '1 2 4']::oidvector[], + ARRAY['0/12345', 'AC/FD', '0/0']::pg_lsn[], + ARRAY[make_timestamptz(1970, 1, 2, 3, 4, 5.6, 'UTC'), + make_timestamptz(1971, 2, 3, 4, 5, 6.7, 'UTC')]); +CREATE INDEX NONCONCURRENTLY ON manyarrays USING ybgin (a); +CREATE INDEX NONCONCURRENTLY ON manyarrays USING ybgin (b); +CREATE INDEX NONCONCURRENTLY ON manyarrays USING ybgin (c); +CREATE INDEX NONCONCURRENTLY ON manyarrays USING ybgin (d); +CREATE INDEX NONCONCURRENTLY ON manyarrays USING ybgin (e); +CREATE INDEX NONCONCURRENTLY ON manyarrays USING ybgin (f); + +-- +-- Column ordering +-- + +-- ASC +CREATE INDEX idx_hash ON jsonbs USING ybgin (j ASC); +-- DESC +CREATE INDEX idx_hash ON jsonbs USING ybgin (j DESC); +-- HASH +CREATE INDEX idx_hash ON jsonbs USING ybgin (j HASH); +-- NULLS FIRST +CREATE INDEX idx_hash ON jsonbs USING ybgin (j NULLS FIRST); +-- NULLS LAST +CREATE INDEX idx_hash ON jsonbs USING ybgin (j NULLS LAST); + +-- +-- Colocation +-- + +-- Setup +CREATE TABLEGROUP g; +-- Colocated table and index +CREATE TABLE garrays (i serial PRIMARY KEY, a int[]) TABLEGROUP g; +INSERT INTO garrays (a) VALUES ('{11, 22}'); +CREATE INDEX ON garrays USING ybgin (a); +INSERT INTO garrays (a) VALUES ('{22, 33}'), ('{33, 11}'); +SELECT $$ +:P SELECT * FROM garrays WHERE a && '{11}'; +$$ AS query \gset +\i :iter_P2 +-- Cleanup +DROP TABLE garrays; +DROP TABLEGROUP g; + +-- +-- ALTER TABLE ... COLUMN +-- + +-- Setup +CREATE TABLE altercoltab (a int[], i int); +CREATE INDEX NONCONCURRENTLY ON altercoltab USING ybgin (a); +INSERT INTO altercoltab VALUES ('{1}', 2); +-- Test +ALTER TABLE altercoltab DROP COLUMN i; +SELECT * FROM altercoltab WHERE a && '{1}'; +ALTER TABLE altercoltab ADD COLUMN j int; +SELECT * FROM altercoltab WHERE a && '{1}'; +ALTER TABLE altercoltab RENAME COLUMN j TO k; +SELECT * FROM altercoltab WHERE a && '{1}'; +-- Cleanup +DROP TABLE altercoltab; diff --git a/src/test/regress/sql/yb.orig.ybgin_nulls.sql b/src/test/regress/sql/yb.orig.ybgin_nulls.sql new file mode 100644 index 0000000000000..33a5f5aa85e16 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ybgin_nulls.sql @@ -0,0 +1,11 @@ +-- +-- Yugabyte-owned test for ybgin index access method and null categories. +-- + +-- GIN_CAT_NULL_KEY +INSERT INTO arrays (a) VALUES ('{null}'); +SELECT * FROM arrays WHERE a @> '{null}'; +-- GIN_CAT_EMPTY_ITEM +INSERT INTO arrays (a) VALUES ('{}'); +-- GIN_CAT_NULL_ITEM +INSERT INTO arrays (a) VALUES (null); diff --git a/src/test/regress/sql/yb.orig.ybgin_operators.sql b/src/test/regress/sql/yb.orig.ybgin_operators.sql new file mode 100644 index 0000000000000..b0aca7d6f44fe --- /dev/null +++ b/src/test/regress/sql/yb.orig.ybgin_operators.sql @@ -0,0 +1,282 @@ +-- +-- Yugabyte-owned test for covering ybgin operators. The yb.orig.ybgin test has +-- complete coverage for tsvector and anyarray types, so this will focus on +-- jsonb. Operators taken from +-- . +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (costs off)' + +-- Always choose index scan. +SET enable_seqscan = off; +SET yb_test_ybgin_disable_cost_factor = 0.5; + +-- +-- jsonb_ops +-- + +-- Setup +INSERT INTO jsonbs (j) VALUES ('{"aaa":[-1,2.5,"5"], "date":"2021-06-30"}'); +DROP INDEX jsonbs_j_idx; +CREATE INDEX ON jsonbs USING ybgin (j); +-- jsonpath: number + number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ + 2 == 4)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: + number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (+@ == 5)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: number - number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ - 2 == 3)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: - number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa.bbb[*] ? (-@ < -3)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: number * number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ * 2 == 4)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: number / number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ / 2 == 1)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: number % number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ % 2 == 1)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . type() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$[*] ? (@.type() == "string")'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . size() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @@ '$.aaa.size() == 3'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . double() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @@ '$.double() * 3 == 9'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . ceiling() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@.ceiling() == 2)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . floor() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@.floor() == 2)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . abs() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@.abs() == 1)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . datetime() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @@ '$.date.datetime() < "2021-07-01".datetime()'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . datetime(template) +-- (skip this) +-- jsonpath: object . keyvalue() +-- (skip this) +-- jsonpath: value == value +-- (skip this) +-- jsonpath: value != value +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ != 1)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value < value +-- (skip this) +-- jsonpath: value <= value +-- (skip this) +-- jsonpath: value > value +-- (skip this) +-- jsonpath: value >= value +-- (skip this) +-- jsonpath: boolean && boolean +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ != -1 && @ != 2.5)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: boolean || boolean +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ == -1 || @ == 1)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: ! boolean +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (!(@ > 0))'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: boolean is unknown +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? ((@ == "1") is unknown)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: string like_regex string +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ like_regex "^[4-7]+$")'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: string starts with string +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ starts with "5")'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: exists ( path_expression ) +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.* ? (exists (@ ? (@[*] < 0 || @[*] > 5)))'; +$$ AS query \gset +\i :iter_P2 + +-- +-- jsonb_path_ops +-- + +-- Setup +DROP INDEX jsonbs_j_idx; +CREATE INDEX ON jsonbs USING ybgin (j jsonb_path_ops); +-- jsonpath: number + number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ + 2 == 4)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: + number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (+@ == 5)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: number - number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ - 2 == 3)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: - number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa.bbb[*] ? (-@ < -3)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: number * number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ * 2 == 4)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: number / number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ / 2 == 1)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: number % number +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ % 2 == 1)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . type() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$[*] ? (@.type() == "string")'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . size() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @@ '$.aaa.size() == 3'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . double() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @@ '$.double() * 3 == 9'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . ceiling() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@.ceiling() == 2)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . floor() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@.floor() == 2)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . abs() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@.abs() == 1)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . datetime() +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @@ '$.date.datetime() < "2021-07-01".datetime()'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value . datetime(template) +-- (skip this) +-- jsonpath: object . keyvalue() +-- (skip this) +-- jsonpath: value == value +-- (skip this) +-- jsonpath: value != value +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ != 1)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: value < value +-- (skip this) +-- jsonpath: value <= value +-- (skip this) +-- jsonpath: value > value +-- (skip this) +-- jsonpath: value >= value +-- (skip this) +-- jsonpath: boolean && boolean +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ != -1 && @ != 2.5)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: boolean || boolean +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ == -1 || @ == 1)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: ! boolean +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (!(@ > 0))'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: boolean is unknown +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ is unknown)'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: string like_regex string +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ like_regex "^[4-7]+$")'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: string starts with string +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.aaa[*] ? (@ starts with "5")'; +$$ AS query \gset +\i :iter_P2 +-- jsonpath: exists ( path_expression ) +SELECT $$ +:P SELECT * FROM jsonbs WHERE j @? '$.* ? (exists (@ ? (@[*] < 0 || @[*] > 5)))'; +$$ AS query \gset +\i :iter_P2 diff --git a/src/test/regress/sql/yb.orig.ybgin_pushdown.sql b/src/test/regress/sql/yb.orig.ybgin_pushdown.sql new file mode 100644 index 0000000000000..a67d0e040f45b --- /dev/null +++ b/src/test/regress/sql/yb.orig.ybgin_pushdown.sql @@ -0,0 +1,101 @@ +-- +-- Yugabyte-owned test on ybgin index with expression pushdown +-- + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (costs off)' + +CREATE TABLE gin_pushdown( + id varchar(64) not null, + guid varchar(64) not null unique, + status varchar(64), + json_content jsonb not null, + primary key (id) +); + +-- gin index +CREATE INDEX gin_pushdown_json_content_idx ON gin_pushdown USING ybgin (json_content jsonb_path_ops); + +INSERT INTO gin_pushdown + SELECT x::text, x::text, x::text, ('{"refs": [{"val":"'||x||'"}, {"val":"'||x+1||'"}]}')::jsonb + FROM generate_series (1, 10) x; + +-- Find rows using gin index +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE json_content @> '{"refs": [{"val":"9"}]}'; +$$ AS query \gset +\i :iter_P2 + +-- Use pushdown filter +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE json_content @> '{"refs": [{"val":"9"}]}' AND status <> '9'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE json_content @> '{"refs": [{"val":"9"}]}' AND status = '9'; +$$ AS query \gset +\i :iter_P2 + +-- Pushdown filter that may seem to be pushed with the index scan, however ybgin index +-- does not store the indexed value, hence filter goes to the main relation +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE json_content @> '{"refs": [{"val":"9"}]}' AND json_content->'refs'->0->'val' <> '"9"'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE json_content @> '{"refs": [{"val":"9"}]}' AND json_content->'refs'->0->'val' = '"9"'; +$$ AS query \gset +\i :iter_P2 + +-- Expression that does not refer any columns can go to the index. +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE json_content @> '{"refs": [{"val":"9"}]}' AND random() > 2.0; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE json_content @> '{"refs": [{"val":"9"}]}' AND random() < 2.0; +$$ AS query \gset +\i :iter_P2 + +-- Find row using regular index +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE guid = '9'; +$$ AS query \gset +\i :iter_P2 + +-- Use pushdown filter +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE guid = '9' AND status <> '9'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE guid = '9' AND status = '9'; +$$ AS query \gset +\i :iter_P2 + +-- Pushdown filter that goes with the index scan, since json_content is included +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE guid = '9' AND json_content->'refs'->0->'val' <> '"9"'; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE guid = '9' AND json_content->'refs'->0->'val' = '"9"'; +$$ AS query \gset +\i :iter_P2 + +-- Expression that does not refer any columns can go to the index. +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE guid = '9' AND random() > 2.0; +$$ AS query \gset +\i :iter_P2 +SELECT $$ +:P SELECT * FROM gin_pushdown WHERE guid = '9' AND random() < 2.0; +$$ AS query \gset +\i :iter_P2 + +-- Cleanup +DROP TABLE gin_pushdown; diff --git a/src/test/regress/sql/yb.orig.ybgin_search_modes.sql b/src/test/regress/sql/yb.orig.ybgin_search_modes.sql new file mode 100644 index 0000000000000..4c5ef469fcff0 --- /dev/null +++ b/src/test/regress/sql/yb.orig.ybgin_search_modes.sql @@ -0,0 +1,21 @@ +-- +-- Yugabyte-owned test for ybgin index access method and search modes. +-- + +-- Always choose index scan. +SET enable_seqscan = off; +SET yb_test_ybgin_disable_cost_factor = 0.5; + +CREATE INDEX NONCONCURRENTLY idx_partial ON arrays + USING ybgin (a) + WHERE a <@ '{1}' or a @> '{}' or a is null; + +-- GIN_SEARCH_MODE_INCLUDE_EMPTY +SELECT * FROM arrays WHERE a <@ '{1}'; +-- GIN_SEARCH_MODE_ALL +SELECT * FROM arrays WHERE a @> '{}'; +-- GIN_SEARCH_MODE_EVERYTHING, GIN_CAT_NULL_ITEM +SELECT * FROM arrays WHERE a is null; + +-- Cleanup +DROP INDEX idx_partial; diff --git a/src/test/regress/sql/yb.port.advisory_lock.sql b/src/test/regress/sql/yb.port.advisory_lock.sql new file mode 100644 index 0000000000000..d41d76aa59c5f --- /dev/null +++ b/src/test/regress/sql/yb.port.advisory_lock.sql @@ -0,0 +1,166 @@ +-- +-- ADVISORY LOCKS +-- + +SELECT oid AS datoid FROM pg_database WHERE datname = current_database() \gset + +BEGIN; + +SELECT + pg_advisory_xact_lock(1), pg_advisory_xact_lock_shared(2), + pg_advisory_xact_lock(1, 1), pg_advisory_xact_lock_shared(2, 2); + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT locktype, classid, objid, objsubid, mode, granted + FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid + ORDER BY classid, objid, objsubid; + + +-- pg_advisory_unlock_all() shouldn't release xact locks +SELECT pg_advisory_unlock_all(); + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid; + + +-- can't unlock xact locks +SELECT + pg_advisory_unlock(1), pg_advisory_unlock_shared(2), + pg_advisory_unlock(1, 1), pg_advisory_unlock_shared(2, 2); + + +-- automatically release xact locks at commit +COMMIT; + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid; + + +BEGIN; + +-- holding both session and xact locks on the same objects, xact first +SELECT + pg_advisory_xact_lock(1), pg_advisory_xact_lock_shared(2), + pg_advisory_xact_lock(1, 1), pg_advisory_xact_lock_shared(2, 2); + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT locktype, classid, objid, objsubid, mode, granted + FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid + ORDER BY classid, objid, objsubid; + +SELECT + pg_advisory_lock(1), pg_advisory_lock_shared(2), + pg_advisory_lock(1, 1), pg_advisory_lock_shared(2, 2); + +ROLLBACK; + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT locktype, classid, objid, objsubid, mode, granted + FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid + ORDER BY classid, objid, objsubid; + + +-- unlocking session locks +SELECT + pg_advisory_unlock(1), pg_advisory_unlock(1), + pg_advisory_unlock_shared(2), pg_advisory_unlock_shared(2), + pg_advisory_unlock(1, 1), pg_advisory_unlock(1, 1), + pg_advisory_unlock_shared(2, 2), pg_advisory_unlock_shared(2, 2); + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid; + + +BEGIN; + +-- holding both session and xact locks on the same objects, session first +SELECT + pg_advisory_lock(1), pg_advisory_lock_shared(2), + pg_advisory_lock(1, 1), pg_advisory_lock_shared(2, 2); + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT locktype, classid, objid, objsubid, mode, granted + FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid + ORDER BY classid, objid, objsubid; + +SELECT + pg_advisory_xact_lock(1), pg_advisory_xact_lock_shared(2), + pg_advisory_xact_lock(1, 1), pg_advisory_xact_lock_shared(2, 2); + +ROLLBACK; + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT locktype, classid, objid, objsubid, mode, granted + FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid + ORDER BY classid, objid, objsubid; + + +-- releasing all session locks +SELECT pg_advisory_unlock_all(); + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid; + + +BEGIN; + +-- grabbing txn locks multiple times + +SELECT + pg_advisory_xact_lock(1), pg_advisory_xact_lock(1), + pg_advisory_xact_lock_shared(2), pg_advisory_xact_lock_shared(2), + pg_advisory_xact_lock(1, 1), pg_advisory_xact_lock(1, 1), + pg_advisory_xact_lock_shared(2, 2), pg_advisory_xact_lock_shared(2, 2); + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +-- YB: TODO(GH#26179): ensure pg_locks shows only one advisory lock per key +SELECT locktype, classid, objid, objsubid, mode, granted + FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid + ORDER BY classid, objid, objsubid; + +COMMIT; + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid; + +-- grabbing session locks multiple times + +SELECT + pg_advisory_lock(1), pg_advisory_lock(1), + pg_advisory_lock_shared(2), pg_advisory_lock_shared(2), + pg_advisory_lock(1, 1), pg_advisory_lock(1, 1), + pg_advisory_lock_shared(2, 2), pg_advisory_lock_shared(2, 2); + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +-- YB: TODO(GH#26179): ensure pg_locks shows only one advisory lock per key +SELECT locktype, classid, objid, objsubid, mode, granted + FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid + ORDER BY classid, objid, objsubid; + +SELECT + pg_advisory_unlock(1), pg_advisory_unlock(1), + pg_advisory_unlock_shared(2), pg_advisory_unlock_shared(2), + pg_advisory_unlock(1, 1), pg_advisory_unlock(1, 1), + pg_advisory_unlock_shared(2, 2), pg_advisory_unlock_shared(2, 2); + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid; + +-- .. and releasing them all at once + +SELECT + pg_advisory_lock(1), pg_advisory_lock(1), + pg_advisory_lock_shared(2), pg_advisory_lock_shared(2), + pg_advisory_lock(1, 1), pg_advisory_lock(1, 1), + pg_advisory_lock_shared(2, 2), pg_advisory_lock_shared(2, 2); + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +-- YB: TODO(GH#26179): ensure pg_locks shows only one advisory lock per key +SELECT locktype, classid, objid, objsubid, mode, granted + FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid + ORDER BY classid, objid, objsubid; + +SELECT pg_advisory_unlock_all(); + +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure advisory lock tablets are propagated(via the transaction heartbeat) +SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid; diff --git a/src/test/regress/sql/yb.port.aggregates.sql b/src/test/regress/sql/yb.port.aggregates.sql new file mode 100644 index 0000000000000..336249eace3d3 --- /dev/null +++ b/src/test/regress/sql/yb.port.aggregates.sql @@ -0,0 +1,1289 @@ +-- +-- AGGREGATES +-- + +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +-- avoid bit-exact output here because operations may not be bit-exact. +SET extra_float_digits = 0; +-- YB: explicitly set enable_partitionwise_aggregate to original pg default. +SET enable_partitionwise_aggregate to false; + +-- prepare some test data +CREATE TABLE aggtest ( + a int2, + b float4 +); + +\set filename :abs_srcdir '/data/agg.data' +COPY aggtest FROM :'filename'; + +ANALYZE aggtest; + + +SELECT avg(four) AS avg_1 FROM onek; + +SELECT avg(a) AS avg_32 FROM aggtest WHERE a < 100; + +-- In 7.1, avg(float4) is computed using float8 arithmetic. +-- Round the result to 3 digits to avoid platform-specific results. + +SELECT avg(b)::numeric(10,3) AS avg_107_943 FROM aggtest; + +SELECT avg(gpa) AS avg_3_4 FROM ONLY student; + + +SELECT sum(four) AS sum_1500 FROM onek; +SELECT sum(a) AS sum_198 FROM aggtest; +SELECT sum(b) AS avg_431_773 FROM aggtest; +SELECT sum(gpa) AS avg_6_8 FROM ONLY student; + +SELECT max(four) AS max_3 FROM onek; +SELECT max(a) AS max_100 FROM aggtest; +SELECT max(aggtest.b) AS max_324_78 FROM aggtest; +SELECT max(student.gpa) AS max_3_7 FROM student; + +SELECT stddev_pop(b) FROM aggtest; +SELECT stddev_samp(b) FROM aggtest; +SELECT var_pop(b) FROM aggtest; +SELECT var_samp(b) FROM aggtest; + +SELECT stddev_pop(b::numeric) FROM aggtest; +SELECT stddev_samp(b::numeric) FROM aggtest; +SELECT var_pop(b::numeric) FROM aggtest; +SELECT var_samp(b::numeric) FROM aggtest; + +-- population variance is defined for a single tuple, sample variance +-- is not +SELECT var_pop(1.0::float8), var_samp(2.0::float8); +SELECT stddev_pop(3.0::float8), stddev_samp(4.0::float8); +SELECT var_pop('inf'::float8), var_samp('inf'::float8); +SELECT stddev_pop('inf'::float8), stddev_samp('inf'::float8); +SELECT var_pop('nan'::float8), var_samp('nan'::float8); +SELECT stddev_pop('nan'::float8), stddev_samp('nan'::float8); +SELECT var_pop(1.0::float4), var_samp(2.0::float4); +SELECT stddev_pop(3.0::float4), stddev_samp(4.0::float4); +SELECT var_pop('inf'::float4), var_samp('inf'::float4); +SELECT stddev_pop('inf'::float4), stddev_samp('inf'::float4); +SELECT var_pop('nan'::float4), var_samp('nan'::float4); +SELECT stddev_pop('nan'::float4), stddev_samp('nan'::float4); +SELECT var_pop(1.0::numeric), var_samp(2.0::numeric); +SELECT stddev_pop(3.0::numeric), stddev_samp(4.0::numeric); +SELECT var_pop('inf'::numeric), var_samp('inf'::numeric); +SELECT stddev_pop('inf'::numeric), stddev_samp('inf'::numeric); +SELECT var_pop('nan'::numeric), var_samp('nan'::numeric); +SELECT stddev_pop('nan'::numeric), stddev_samp('nan'::numeric); + +-- verify correct results for null and NaN inputs +select sum(null::int4) from generate_series(1,3); +select sum(null::int8) from generate_series(1,3); +select sum(null::numeric) from generate_series(1,3); +select sum(null::float8) from generate_series(1,3); +select avg(null::int4) from generate_series(1,3); +select avg(null::int8) from generate_series(1,3); +select avg(null::numeric) from generate_series(1,3); +select avg(null::float8) from generate_series(1,3); +select sum('NaN'::numeric) from generate_series(1,3); +select avg('NaN'::numeric) from generate_series(1,3); + +-- verify correct results for infinite inputs +SELECT sum(x::float8), avg(x::float8), var_pop(x::float8) +FROM (VALUES ('1'), ('infinity')) v(x); +SELECT sum(x::float8), avg(x::float8), var_pop(x::float8) +FROM (VALUES ('infinity'), ('1')) v(x); +SELECT sum(x::float8), avg(x::float8), var_pop(x::float8) +FROM (VALUES ('infinity'), ('infinity')) v(x); +SELECT sum(x::float8), avg(x::float8), var_pop(x::float8) +FROM (VALUES ('-infinity'), ('infinity')) v(x); +SELECT sum(x::float8), avg(x::float8), var_pop(x::float8) +FROM (VALUES ('-infinity'), ('-infinity')) v(x); +SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric) +FROM (VALUES ('1'), ('infinity')) v(x); +SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric) +FROM (VALUES ('infinity'), ('1')) v(x); +SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric) +FROM (VALUES ('infinity'), ('infinity')) v(x); +SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric) +FROM (VALUES ('-infinity'), ('infinity')) v(x); +SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric) +FROM (VALUES ('-infinity'), ('-infinity')) v(x); + +-- test accuracy with a large input offset +SELECT avg(x::float8), var_pop(x::float8) +FROM (VALUES (100000003), (100000004), (100000006), (100000007)) v(x); +SELECT avg(x::float8), var_pop(x::float8) +FROM (VALUES (7000000000005), (7000000000007)) v(x); + +-- SQL2003 binary aggregates +SELECT regr_count(b, a) FROM aggtest; +SELECT regr_sxx(b, a) FROM aggtest; +SELECT regr_syy(b, a) FROM aggtest; +SELECT regr_sxy(b, a) FROM aggtest; +SELECT regr_avgx(b, a), regr_avgy(b, a) FROM aggtest; +SELECT regr_r2(b, a) FROM aggtest; +SELECT regr_slope(b, a), regr_intercept(b, a) FROM aggtest; +SELECT covar_pop(b, a), covar_samp(b, a) FROM aggtest; +SELECT corr(b, a) FROM aggtest; + +-- check single-tuple behavior +SELECT covar_pop(1::float8,2::float8), covar_samp(3::float8,4::float8); +SELECT covar_pop(1::float8,'inf'::float8), covar_samp(3::float8,'inf'::float8); +SELECT covar_pop(1::float8,'nan'::float8), covar_samp(3::float8,'nan'::float8); + +-- test accum and combine functions directly +CREATE TABLE regr_test (x float8, y float8); +INSERT INTO regr_test VALUES (10,150),(20,250),(30,350),(80,540),(100,200); +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test WHERE x IN (10,20,30,80); +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test; +SELECT float8_accum('{4,140,2900}'::float8[], 100); +SELECT float8_regr_accum('{4,140,2900,1290,83075,15050}'::float8[], 200, 100); +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test WHERE x IN (10,20,30); +SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) +FROM regr_test WHERE x IN (80,100); +SELECT float8_combine('{3,60,200}'::float8[], '{0,0,0}'::float8[]); +SELECT float8_combine('{0,0,0}'::float8[], '{2,180,200}'::float8[]); +SELECT float8_combine('{3,60,200}'::float8[], '{2,180,200}'::float8[]); +SELECT float8_regr_combine('{3,60,200,750,20000,2000}'::float8[], + '{0,0,0,0,0,0}'::float8[]); +SELECT float8_regr_combine('{0,0,0,0,0,0}'::float8[], + '{2,180,200,740,57800,-3400}'::float8[]); +SELECT float8_regr_combine('{3,60,200,750,20000,2000}'::float8[], + '{2,180,200,740,57800,-3400}'::float8[]); +DROP TABLE regr_test; + +-- test count, distinct +SELECT count(four) AS cnt_1000 FROM onek; +SELECT count(DISTINCT four) AS cnt_4 FROM onek; + +select ten, count(*), sum(four) from onek +group by ten order by ten; + +select ten, count(four), sum(DISTINCT four) from onek +group by ten order by ten; + +-- user-defined aggregates +SELECT newavg(four) AS avg_1 FROM onek; +SELECT newsum(four) AS sum_1500 FROM onek; +SELECT newcnt(four) AS cnt_1000 FROM onek; +SELECT newcnt(*) AS cnt_1000 FROM onek; +SELECT oldcnt(*) AS cnt_1000 FROM onek; +SELECT sum2(q1,q2) FROM int8_tbl; + +-- test for outer-level aggregates + +-- this should work +select ten, sum(distinct four) from onek a +group by ten +having exists (select 1 from onek b where sum(distinct a.four) = b.four); + +-- this should fail because subquery has an agg of its own in WHERE +select ten, sum(distinct four) from onek a +group by ten +having exists (select 1 from onek b + where sum(distinct a.four + b.four) = b.four); + +-- Test handling of sublinks within outer-level aggregates. +-- Per bug report from Daniel Grace. +select + (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1))) +from tenk1 o; + +-- Test handling of Params within aggregate arguments in hashed aggregation. +-- Per bug report from Jeevan Chalke. +explain (verbose, costs off) +select s1, s2, sm +from generate_series(1, 3) s1, + lateral (select s2, sum(s1 + s2) sm + from generate_series(1, 3) s2 group by s2) ss +order by 1, 2; +select s1, s2, sm +from generate_series(1, 3) s1, + lateral (select s2, sum(s1 + s2) sm + from generate_series(1, 3) s2 group by s2) ss +order by 1, 2; + +explain (verbose, costs off) +select array(select sum(x+y) s + from generate_series(1,3) y group by y order by s) + from generate_series(1,3) x; +select array(select sum(x+y) s + from generate_series(1,3) y group by y order by s) + from generate_series(1,3) x; + +-- +-- test for bitwise integer aggregates +-- +CREATE TEMPORARY TABLE bitwise_test( + i2 INT2, + i4 INT4, + i8 INT8, + i INTEGER, + x INT2, + y BIT(4) +); + +-- empty case +SELECT + BIT_AND(i2) AS "?", + BIT_OR(i4) AS "?", + BIT_XOR(i8) AS "?" +FROM bitwise_test; + +COPY bitwise_test FROM STDIN NULL 'null'; +1 1 1 1 1 B0101 +3 3 3 null 2 B0100 +7 7 7 3 4 B1100 +\. + +SELECT + BIT_AND(i2) AS "1", + BIT_AND(i4) AS "1", + BIT_AND(i8) AS "1", + BIT_AND(i) AS "?", + BIT_AND(x) AS "0", + BIT_AND(y) AS "0100", + + BIT_OR(i2) AS "7", + BIT_OR(i4) AS "7", + BIT_OR(i8) AS "7", + BIT_OR(i) AS "?", + BIT_OR(x) AS "7", + BIT_OR(y) AS "1101", + + BIT_XOR(i2) AS "5", + BIT_XOR(i4) AS "5", + BIT_XOR(i8) AS "5", + BIT_XOR(i) AS "?", + BIT_XOR(x) AS "7", + BIT_XOR(y) AS "1101" +FROM bitwise_test; + +-- +-- test boolean aggregates +-- +-- first test all possible transition and final states + +SELECT + -- boolean and transitions + -- null because strict + booland_statefunc(NULL, NULL) IS NULL AS "t", + booland_statefunc(TRUE, NULL) IS NULL AS "t", + booland_statefunc(FALSE, NULL) IS NULL AS "t", + booland_statefunc(NULL, TRUE) IS NULL AS "t", + booland_statefunc(NULL, FALSE) IS NULL AS "t", + -- and actual computations + booland_statefunc(TRUE, TRUE) AS "t", + NOT booland_statefunc(TRUE, FALSE) AS "t", + NOT booland_statefunc(FALSE, TRUE) AS "t", + NOT booland_statefunc(FALSE, FALSE) AS "t"; + +SELECT + -- boolean or transitions + -- null because strict + boolor_statefunc(NULL, NULL) IS NULL AS "t", + boolor_statefunc(TRUE, NULL) IS NULL AS "t", + boolor_statefunc(FALSE, NULL) IS NULL AS "t", + boolor_statefunc(NULL, TRUE) IS NULL AS "t", + boolor_statefunc(NULL, FALSE) IS NULL AS "t", + -- actual computations + boolor_statefunc(TRUE, TRUE) AS "t", + boolor_statefunc(TRUE, FALSE) AS "t", + boolor_statefunc(FALSE, TRUE) AS "t", + NOT boolor_statefunc(FALSE, FALSE) AS "t"; + +CREATE TEMPORARY TABLE bool_test( + b1 BOOL, + b2 BOOL, + b3 BOOL, + b4 BOOL); + +-- empty case +SELECT + BOOL_AND(b1) AS "n", + BOOL_OR(b3) AS "n" +FROM bool_test; + +COPY bool_test FROM STDIN NULL 'null'; +TRUE null FALSE null +FALSE TRUE null null +null TRUE FALSE null +\. + +SELECT + BOOL_AND(b1) AS "f", + BOOL_AND(b2) AS "t", + BOOL_AND(b3) AS "f", + BOOL_AND(b4) AS "n", + BOOL_AND(NOT b2) AS "f", + BOOL_AND(NOT b3) AS "t" +FROM bool_test; + +SELECT + EVERY(b1) AS "f", + EVERY(b2) AS "t", + EVERY(b3) AS "f", + EVERY(b4) AS "n", + EVERY(NOT b2) AS "f", + EVERY(NOT b3) AS "t" +FROM bool_test; + +SELECT + BOOL_OR(b1) AS "t", + BOOL_OR(b2) AS "t", + BOOL_OR(b3) AS "f", + BOOL_OR(b4) AS "n", + BOOL_OR(NOT b2) AS "f", + BOOL_OR(NOT b3) AS "t" +FROM bool_test; + +-- +-- Test cases that should be optimized into indexscans instead of +-- the generic aggregate implementation. +-- + +-- Basic cases +explain (costs off) + select min(unique1) from tenk1; +select min(unique1) from tenk1; +explain (costs off) + select max(unique1) from tenk1; +select max(unique1) from tenk1; +explain (costs off) + select max(unique1) from tenk1 where unique1 < 42; +select max(unique1) from tenk1 where unique1 < 42; +explain (costs off) + select max(unique1) from tenk1 where unique1 > 42; +select max(unique1) from tenk1 where unique1 > 42; + +-- the planner may choose a generic aggregate here if parallel query is +-- enabled, since that plan will be parallel safe and the "optimized" +-- plan, which has almost identical cost, will not be. we want to test +-- the optimized plan, so temporarily disable parallel query. +begin; +set local max_parallel_workers_per_gather = 0; +explain (costs off) + select max(unique1) from tenk1 where unique1 > 42000; +select max(unique1) from tenk1 where unique1 > 42000; +rollback; + +-- multi-column index (uses tenk1_thous_tenthous) +explain (costs off) + select max(tenthous) from tenk1 where thousand = 33; +select max(tenthous) from tenk1 where thousand = 33; +explain (costs off) + select min(tenthous) from tenk1 where thousand = 33; +select min(tenthous) from tenk1 where thousand = 33; + +-- check parameter propagation into an indexscan subquery +explain (costs off) + select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt + from int4_tbl; +-- YB note: add consistent ordering that matches upstream: +-- 1. mainly order by absolute value of f1. +-- 2. order positives before negatives by adding an offset of -1/+1 using the +-- sign function. +select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt + from int4_tbl order by abs(f1) - sign(f1); -- YB ordering + +-- check some cases that were handled incorrectly in 8.3.0 +explain (costs off) + select distinct max(unique2) from tenk1; +select distinct max(unique2) from tenk1; +explain (costs off) + select max(unique2) from tenk1 order by 1; +select max(unique2) from tenk1 order by 1; +explain (costs off) + select max(unique2) from tenk1 order by max(unique2); +select max(unique2) from tenk1 order by max(unique2); +explain (costs off) + select max(unique2) from tenk1 order by max(unique2)+1; +select max(unique2) from tenk1 order by max(unique2)+1; +explain (costs off) + select max(unique2), generate_series(1,3) as g from tenk1 order by g desc; +select max(unique2), generate_series(1,3) as g from tenk1 order by g desc; + +-- interesting corner case: constant gets optimized into a seqscan +explain (costs off) + select max(100) from tenk1; +select max(100) from tenk1; + +-- try it on an inheritance tree +create table minmaxtest(f1 int); +create table minmaxtest1() inherits (minmaxtest); +create table minmaxtest2() inherits (minmaxtest); +create table minmaxtest3() inherits (minmaxtest); +create index minmaxtesti on minmaxtest(f1); +create index minmaxtest1i on minmaxtest1(f1); +create index minmaxtest2i on minmaxtest2(f1 desc); +create index minmaxtest3i on minmaxtest3(f1) where f1 is not null; + +insert into minmaxtest values(11), (12); +insert into minmaxtest1 values(13), (14); +insert into minmaxtest2 values(15), (16); +insert into minmaxtest3 values(17), (18); + +explain (costs off) + select min(f1), max(f1) from minmaxtest; +select min(f1), max(f1) from minmaxtest; + +-- DISTINCT doesn't do anything useful here, but it shouldn't fail +/*+ Set(enable_hashagg off) */ explain (costs off) -- YB: disable hashagg to get the same plan as PG + select distinct min(f1), max(f1) from minmaxtest; +/*+ Set(enable_hashagg off) */ -- YB: disable hashagg to get the same plan as PG +select distinct min(f1), max(f1) from minmaxtest; + +\set VERBOSITY terse \\ -- YB: suppress cascade details +drop table minmaxtest cascade; +\set VERBOSITY default \\ -- YB + +-- DISTINCT can also trigger wrong answers with hash aggregation (bug #18465) +begin; +set local enable_sort = off; +-- YB note: add consistent ordering that matches upstream: +-- 1. mainly order by absolute value of f1. +-- 2. order positives before negatives by adding an offset of -1/+1 using the +-- sign function. +explain (costs off) + select f1, (select distinct min(t1.f1) from int4_tbl t1 where t1.f1 = t0.f1) + from int4_tbl t0 order by abs(f1) - sign(f1); -- YB: ordering +select f1, (select distinct min(t1.f1) from int4_tbl t1 where t1.f1 = t0.f1) +from int4_tbl t0 order by abs(f1) - sign(f1); -- YB: ordering +rollback; + +-- check for correct detection of nested-aggregate errors +select max(min(unique1)) from tenk1; +select (select max(min(unique1)) from int8_tbl) from tenk1; + +-- +-- Test removal of redundant GROUP BY columns +-- + +create temp table t1 (a int, b int, c int, d int, primary key (a, b)); +create temp table t2 (x int, y int, z int, primary key (x, y)); +create temp table t3 (a int, b int, c int, primary key(a, b) deferrable); + +-- Non-primary-key columns can be removed from GROUP BY +explain (costs off) select * from t1 group by a,b,c,d; + +-- No removal can happen if the complete PK is not present in GROUP BY +explain (costs off) select a,c from t1 group by a,c,d; + +-- Test removal across multiple relations +/*+ HashJoin(t1 t2) Set(enable_sort false) */ explain (costs off) select * -- YB: force hash join + hash agg +from t1 inner join t2 on t1.a = t2.x and t1.b = t2.y +group by t1.a,t1.b,t1.c,t1.d,t2.x,t2.y,t2.z; + +-- Test case where t1 can be optimized but not t2 +/*+ HashJoin(t1 t2) Set(enable_sort false) */ explain (costs off) select t1.*,t2.x,t2.z -- YB: force hash join + hash agg +from t1 inner join t2 on t1.a = t2.x and t1.b = t2.y +group by t1.a,t1.b,t1.c,t1.d,t2.x,t2.z; + +-- Cannot optimize when PK is deferrable +explain (costs off) select * from t3 group by a,b,c; + +create temp table t1c () inherits (t1); + +-- Ensure we don't remove any columns when t1 has a child table +explain (costs off) select * from t1 group by a,b,c,d; + +-- Okay to remove columns if we're only querying the parent. +explain (costs off) select * from only t1 group by a,b,c,d; + +create temp table p_t1 ( + a int, + b int, + c int, + d int, + primary key(a,b) +) partition by list(a); +create temp table p_t1_1 partition of p_t1 for values in(1); +create temp table p_t1_2 partition of p_t1 for values in(2); + +-- Ensure we can remove non-PK columns for partitioned tables. +explain (costs off) select * from p_t1 group by a,b,c,d; + +drop table t1 cascade; +drop table t2; +drop table t3; +drop table p_t1; + +-- +-- Test GROUP BY matching of join columns that are type-coerced due to USING +-- + +create temp table t1(f1 int, f2 bigint); +create temp table t2(f1 bigint, f22 bigint); + +select f1 from t1 left join t2 using (f1) group by f1; +select f1 from t1 left join t2 using (f1) group by t1.f1; +select t1.f1 from t1 left join t2 using (f1) group by t1.f1; +-- only this one should fail: +select t1.f1 from t1 left join t2 using (f1) group by f1; + +drop table t1, t2; + +-- +-- Test combinations of DISTINCT and/or ORDER BY +-- + +select array_agg(a order by b) + from (values (1,4),(2,3),(3,1),(4,2)) v(a,b); +select array_agg(a order by a) + from (values (1,4),(2,3),(3,1),(4,2)) v(a,b); +select array_agg(a order by a desc) + from (values (1,4),(2,3),(3,1),(4,2)) v(a,b); +select array_agg(b order by a desc) + from (values (1,4),(2,3),(3,1),(4,2)) v(a,b); + +select array_agg(distinct a) + from (values (1),(2),(1),(3),(null),(2)) v(a); +select array_agg(distinct a order by a) + from (values (1),(2),(1),(3),(null),(2)) v(a); +select array_agg(distinct a order by a desc) + from (values (1),(2),(1),(3),(null),(2)) v(a); +select array_agg(distinct a order by a desc nulls last) + from (values (1),(2),(1),(3),(null),(2)) v(a); + +-- multi-arg aggs, strict/nonstrict, distinct/order by + +select aggfstr(a,b,c) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); +select aggfns(a,b,c) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); + +select aggfstr(distinct a,b,c) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,3) i; +select aggfns(distinct a,b,c) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,3) i; + +select aggfstr(distinct a,b,c order by b) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,3) i; +select aggfns(distinct a,b,c order by b) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,3) i; + +-- test specific code paths + +select aggfns(distinct a,a,c order by c using ~<~,a) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,2) i; +select aggfns(distinct a,a,c order by c using ~<~) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,2) i; +select aggfns(distinct a,a,c order by a) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,2) i; +select aggfns(distinct a,b,c order by a,c using ~<~,b) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,2) i; + +-- check node I/O via view creation and usage, also deparsing logic + +create view agg_view1 as + select aggfns(a,b,c) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); + +select * from agg_view1; +select pg_get_viewdef('agg_view1'::regclass); + +create or replace view agg_view1 as + select aggfns(distinct a,b,c) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,3) i; + +select * from agg_view1; +select pg_get_viewdef('agg_view1'::regclass); + +create or replace view agg_view1 as + select aggfns(distinct a,b,c order by b) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,3) i; + +select * from agg_view1; +select pg_get_viewdef('agg_view1'::regclass); + +create or replace view agg_view1 as + select aggfns(a,b,c order by b+1) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); + +select * from agg_view1; +select pg_get_viewdef('agg_view1'::regclass); + +create or replace view agg_view1 as + select aggfns(a,a,c order by b) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); + +select * from agg_view1; +select pg_get_viewdef('agg_view1'::regclass); + +create or replace view agg_view1 as + select aggfns(a,b,c order by c using ~<~) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); + +select * from agg_view1; +select pg_get_viewdef('agg_view1'::regclass); + +create or replace view agg_view1 as + select aggfns(distinct a,b,c order by a,c using ~<~,b) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,2) i; + +select * from agg_view1; +select pg_get_viewdef('agg_view1'::regclass); + +drop view agg_view1; + +-- incorrect DISTINCT usage errors + +select aggfns(distinct a,b,c order by i) + from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i; +select aggfns(distinct a,b,c order by a,b+1) + from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i; +select aggfns(distinct a,b,c order by a,b,i,c) + from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i; +select aggfns(distinct a,a,c order by a,b) + from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i; + +-- string_agg tests +select string_agg(a,',') from (values('aaaa'),('bbbb'),('cccc')) g(a); +select string_agg(a,',') from (values('aaaa'),(null),('bbbb'),('cccc')) g(a); +select string_agg(a,'AB') from (values(null),(null),('bbbb'),('cccc')) g(a); +select string_agg(a,',') from (values(null),(null)) g(a); + +-- check some implicit casting cases, as per bug #5564 +select string_agg(distinct f1, ',' order by f1) from varchar_tbl; -- ok +select string_agg(distinct f1::text, ',' order by f1) from varchar_tbl; -- not ok +select string_agg(distinct f1, ',' order by f1::text) from varchar_tbl; -- not ok +select string_agg(distinct f1::text, ',' order by f1::text) from varchar_tbl; -- ok + +-- string_agg bytea tests +create table bytea_test_table(v bytea); + +select string_agg(v, '') from bytea_test_table; + +insert into bytea_test_table values(decode('ff','hex')); + +select string_agg(v, '') from bytea_test_table; + +insert into bytea_test_table values(decode('aa','hex')); + +select string_agg(v, '') from bytea_test_table; +select string_agg(v, NULL) from bytea_test_table; +select string_agg(v, decode('ee', 'hex')) from bytea_test_table; + +drop table bytea_test_table; + +-- FILTER tests + +select min(unique1) filter (where unique1 > 100) from tenk1; + +select sum(1/ten) filter (where ten > 0) from tenk1; + +select ten, sum(distinct four) filter (where four::text ~ '123') from onek a +group by ten; + +select ten, sum(distinct four) filter (where four > 10) from onek a +group by ten +having exists (select 1 from onek b where sum(distinct a.four) = b.four); + +select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') +from (values ('a', 'b')) AS v(foo,bar); + +-- outer reference in FILTER (PostgreSQL extension) +select (select count(*) + from (values (1)) t0(inner_c)) +from (values (2),(3)) t1(outer_c); -- inner query is aggregation query +select (select count(*) filter (where outer_c <> 0) + from (values (1)) t0(inner_c)) +from (values (2),(3)) t1(outer_c); -- outer query is aggregation query +select (select count(inner_c) filter (where outer_c <> 0) + from (values (1)) t0(inner_c)) +from (values (2),(3)) t1(outer_c); -- inner query is aggregation query +select + (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) + filter (where o.unique1 < 10)) +from tenk1 o; -- outer query is aggregation query + +-- subquery in FILTER clause (PostgreSQL extension) +select sum(unique1) FILTER (WHERE + unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1; + +-- exercise lots of aggregate parts with FILTER +select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1) + from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), + generate_series(1,2) i; + +-- check handling of bare boolean Var in FILTER +select max(0) filter (where b1) from bool_test; +select (select max(0) filter (where b1)) from bool_test; + +-- check for correct detection of nested-aggregate errors in FILTER +select max(unique1) filter (where sum(ten) > 0) from tenk1; +select (select max(unique1) filter (where sum(ten) > 0) from int8_tbl) from tenk1; +select max(unique1) filter (where bool_or(ten > 0)) from tenk1; +select (select max(unique1) filter (where bool_or(ten > 0)) from int8_tbl) from tenk1; + + +-- ordered-set aggregates + +select p, percentile_cont(p) within group (order by x::float8) +from generate_series(1,5) x, + (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) +group by p order by p; + +select p, percentile_cont(p order by p) within group (order by x) -- error +from generate_series(1,5) x, + (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) +group by p order by p; + +select p, sum() within group (order by x::float8) -- error +from generate_series(1,5) x, + (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) +group by p order by p; + +select p, percentile_cont(p,p) -- error +from generate_series(1,5) x, + (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) +group by p order by p; + +select percentile_cont(0.5) within group (order by b) from aggtest; +select percentile_cont(0.5) within group (order by b), sum(b) from aggtest; +select percentile_cont(0.5) within group (order by thousand) from tenk1; +select percentile_disc(0.5) within group (order by thousand) from tenk1; +select rank(3) within group (order by x) +from (values (1),(1),(2),(2),(3),(3),(4)) v(x); +select cume_dist(3) within group (order by x) +from (values (1),(1),(2),(2),(3),(3),(4)) v(x); +select percent_rank(3) within group (order by x) +from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x); +select dense_rank(3) within group (order by x) +from (values (1),(1),(2),(2),(3),(3),(4)) v(x); + +select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) +from tenk1; +select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) +from tenk1; +select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) +from tenk1; +select percentile_cont(array[0,1,0.25,0.75,0.5,1,0.3,0.32,0.35,0.38,0.4]) within group (order by x) +from generate_series(1,6) x; + +select ten, mode() within group (order by string4) from tenk1 group by ten; + +select percentile_disc(array[0.25,0.5,0.75]) within group (order by x) +from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x); + +-- check collation propagates up in suitable cases: +select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX")) + from (values ('fred'),('jim')) v(x); + +-- ordered-set aggs created with CREATE AGGREGATE +select test_rank(3) within group (order by x) +from (values (1),(1),(2),(2),(3),(3),(4)) v(x); +select test_percentile_disc(0.5) within group (order by thousand) from tenk1; + +-- ordered-set aggs can't use ungrouped vars in direct args: +select rank(x) within group (order by x) from generate_series(1,5) x; + +-- outer-level agg can't use a grouped arg of a lower level, either: +select array(select percentile_disc(a) within group (order by x) + from (values (0.3),(0.7)) v(a) group by a) + from generate_series(1,5) g(x); + +-- agg in the direct args is a grouping violation, too: +select rank(sum(x)) within group (order by x) from generate_series(1,5) x; + +-- hypothetical-set type unification and argument-count failures: +select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x); +select rank(3) within group (order by stringu1,stringu2) from tenk1; +select rank('fred') within group (order by x) from generate_series(1,5) x; +select rank('adam'::text collate "C") within group (order by x collate "POSIX") + from (values ('fred'),('jim')) v(x); +-- hypothetical-set type unification successes: +select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x); +select rank('3') within group (order by x) from generate_series(1,5) x; + +-- divide by zero check +select percent_rank(0) within group (order by x) from generate_series(1,0) x; + +-- deparse and multiple features: +create view aggordview1 as +select ten, + percentile_disc(0.5) within group (order by thousand) as p50, + percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px, + rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred) + from tenk1 + group by ten order by ten; + +select pg_get_viewdef('aggordview1'); +select * from aggordview1 order by ten; +drop view aggordview1; + +-- variadic aggregates +select least_agg(q1,q2) from int8_tbl; +select least_agg(variadic array[q1,q2]) from int8_tbl; + +select cleast_agg(q1,q2) from int8_tbl; +select cleast_agg(4.5,f1) from int4_tbl; +select cleast_agg(variadic array[4.5,f1]) from int4_tbl; +select pg_typeof(cleast_agg(variadic array[4.5,f1])) from int4_tbl; + +-- test aggregates with common transition functions share the same states +begin work; + +create type avg_state as (total bigint, count bigint); + +create or replace function avg_transfn(state avg_state, n int) returns avg_state as +$$ +declare new_state avg_state; +begin + raise notice 'avg_transfn called with %', n; + if state is null then + if n is not null then + new_state.total := n; + new_state.count := 1; + return new_state; + end if; + return null; + elsif n is not null then + state.total := state.total + n; + state.count := state.count + 1; + return state; + end if; + + return null; +end +$$ language plpgsql; + +create function avg_finalfn(state avg_state) returns int4 as +$$ +begin + if state is null then + return NULL; + else + return state.total / state.count; + end if; +end +$$ language plpgsql; + +create function sum_finalfn(state avg_state) returns int4 as +$$ +begin + if state is null then + return NULL; + else + return state.total; + end if; +end +$$ language plpgsql; + +create aggregate my_avg(int4) +( + stype = avg_state, + sfunc = avg_transfn, + finalfunc = avg_finalfn +); + +create aggregate my_sum(int4) +( + stype = avg_state, + sfunc = avg_transfn, + finalfunc = sum_finalfn +); + +-- aggregate state should be shared as aggs are the same. +select my_avg(one),my_avg(one) from (values(1),(3)) t(one); + +-- aggregate state should be shared as transfn is the same for both aggs. +select my_avg(one),my_sum(one) from (values(1),(3)) t(one); + +-- same as previous one, but with DISTINCT, which requires sorting the input. +select my_avg(distinct one),my_sum(distinct one) from (values(1),(3),(1)) t(one); + +-- shouldn't share states due to the distinctness not matching. +select my_avg(distinct one),my_sum(one) from (values(1),(3)) t(one); + +-- shouldn't share states due to the filter clause not matching. +select my_avg(one) filter (where one > 1),my_sum(one) from (values(1),(3)) t(one); + +-- this should not share the state due to different input columns. +select my_avg(one),my_sum(two) from (values(1,2),(3,4)) t(one,two); + +-- exercise cases where OSAs share state +select + percentile_cont(0.5) within group (order by a), + percentile_disc(0.5) within group (order by a) +from (values(1::float8),(3),(5),(7)) t(a); + +select + percentile_cont(0.25) within group (order by a), + percentile_disc(0.5) within group (order by a) +from (values(1::float8),(3),(5),(7)) t(a); + +-- these can't share state currently +select + rank(4) within group (order by a), + dense_rank(4) within group (order by a) +from (values(1),(3),(5),(7)) t(a); + +-- test that aggs with the same sfunc and initcond share the same agg state +create aggregate my_sum_init(int4) +( + stype = avg_state, + sfunc = avg_transfn, + finalfunc = sum_finalfn, + initcond = '(10,0)' +); + +create aggregate my_avg_init(int4) +( + stype = avg_state, + sfunc = avg_transfn, + finalfunc = avg_finalfn, + initcond = '(10,0)' +); + +create aggregate my_avg_init2(int4) +( + stype = avg_state, + sfunc = avg_transfn, + finalfunc = avg_finalfn, + initcond = '(4,0)' +); + +-- state should be shared if INITCONDs are matching +select my_sum_init(one),my_avg_init(one) from (values(1),(3)) t(one); + +-- Varying INITCONDs should cause the states not to be shared. +select my_sum_init(one),my_avg_init2(one) from (values(1),(3)) t(one); + +rollback; + +-- test aggregate state sharing to ensure it works if one aggregate has a +-- finalfn and the other one has none. +begin work; + +create or replace function sum_transfn(state int4, n int4) returns int4 as +$$ +declare new_state int4; +begin + raise notice 'sum_transfn called with %', n; + if state is null then + if n is not null then + new_state := n; + return new_state; + end if; + return null; + elsif n is not null then + state := state + n; + return state; + end if; + + return null; +end +$$ language plpgsql; + +create function halfsum_finalfn(state int4) returns int4 as +$$ +begin + if state is null then + return NULL; + else + return state / 2; + end if; +end +$$ language plpgsql; + +create or replace aggregate my_sum(int4) -- YB: add REPLACE because DDL is not transactional +( + stype = int4, + sfunc = sum_transfn +); + +create aggregate my_half_sum(int4) +( + stype = int4, + sfunc = sum_transfn, + finalfunc = halfsum_finalfn +); + +-- Agg state should be shared even though my_sum has no finalfn +select my_sum(one),my_half_sum(one) from (values(1),(2),(3),(4)) t(one); + +rollback; + + +-- test that the aggregate transition logic correctly handles +-- transition / combine functions returning NULL + +-- First test the case of a normal transition function returning NULL +BEGIN; +CREATE FUNCTION balkifnull(int8, int4) +RETURNS int8 +STRICT +LANGUAGE plpgsql AS $$ +BEGIN + IF $1 IS NULL THEN + RAISE 'erroneously called with NULL argument'; + END IF; + RETURN NULL; +END$$; + +CREATE AGGREGATE balk(int4) +( + SFUNC = balkifnull(int8, int4), + STYPE = int8, + PARALLEL = SAFE, + INITCOND = '0' +); + +SELECT balk(hundred) FROM tenk1; + +ROLLBACK; + +/* YB: parallelism cannot be forced, so we cannot test the parallel aggregate combiner function. +-- Secondly test the case of a parallel aggregate combiner function +-- returning NULL. For that use normal transition function, but a +-- combiner function returning NULL. +BEGIN; +CREATE FUNCTION balkifnull(int8, int8) +RETURNS int8 +PARALLEL SAFE +STRICT +LANGUAGE plpgsql AS $$ +BEGIN + IF $1 IS NULL THEN + RAISE 'erroneously called with NULL argument'; + END IF; + RETURN NULL; +END$$; + +CREATE AGGREGATE balk(int4) +( + SFUNC = int4_sum(int8, int4), + STYPE = int8, + COMBINEFUNC = balkifnull(int8, int8), + PARALLEL = SAFE, + INITCOND = '0' +); + +-- force use of parallelism +ALTER TABLE tenk1 set (parallel_workers = 4); +SET LOCAL parallel_setup_cost=0; +SET LOCAL max_parallel_workers_per_gather=4; + +EXPLAIN (COSTS OFF) SELECT balk(hundred) FROM tenk1; +SELECT balk(hundred) FROM tenk1; + +ROLLBACK; +*/ -- YB + +-- test coverage for aggregate combine/serial/deserial functions +BEGIN; + +SET parallel_setup_cost = 0; +SET parallel_tuple_cost = 0; +SET min_parallel_table_scan_size = 0; +SET max_parallel_workers_per_gather = 4; +SET parallel_leader_participation = off; +SET enable_indexonlyscan = off; + +-- YB: the plan differs because parallelism is not supported in non-colocated DBs +-- variance(int4) covers numeric_poly_combine +-- sum(int8) covers int8_avg_combine +-- regr_count(float8, float8) covers int8inc_float8_float8 and aggregates with > 1 arg +EXPLAIN (COSTS OFF, VERBOSE) +SELECT variance(unique1::int4), sum(unique1::int8), regr_count(unique1::float8, unique1::float8) +FROM (SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1) u; + +SELECT variance(unique1::int4), sum(unique1::int8), regr_count(unique1::float8, unique1::float8) +FROM (SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1) u; + +-- YB: the plan differs because parallelism is not supported in non-colocated DBs +-- variance(int8) covers numeric_combine +-- avg(numeric) covers numeric_avg_combine +EXPLAIN (COSTS OFF, VERBOSE) +SELECT variance(unique1::int8), avg(unique1::numeric) +FROM (SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1) u; + +SELECT variance(unique1::int8), avg(unique1::numeric) +FROM (SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1 + UNION ALL SELECT * FROM tenk1) u; + +ROLLBACK; + +-- test coverage for dense_rank +SELECT dense_rank(x) WITHIN GROUP (ORDER BY x) FROM (VALUES (1),(1),(2),(2),(3),(3)) v(x) GROUP BY (x) ORDER BY 1; + + +-- Ensure that the STRICT checks for aggregates does not take NULLness +-- of ORDER BY columns into account. See bug report around +-- 2a505161-2727-2473-7c46-591ed108ac52@email.cz +SELECT min(x ORDER BY y) FROM (VALUES(1, NULL)) AS d(x,y); +SELECT min(x ORDER BY y) FROM (VALUES(1, 2)) AS d(x,y); + +-- check collation-sensitive matching between grouping expressions +select v||'a', case v||'a' when 'aa' then 1 else 0 end, count(*) + from unnest(array['a','b']) u(v) + group by v||'a' order by 1; +select v||'a', case when v||'a' = 'aa' then 1 else 0 end, count(*) + from unnest(array['a','b']) u(v) + group by v||'a' order by 1; + +-- Make sure that generation of HashAggregate for uniqification purposes +-- does not lead to array overflow due to unexpected duplicate hash keys +-- see CAFeeJoKKu0u+A_A9R9316djW-YW3-+Gtgvy3ju655qRHR3jtdA@mail.gmail.com +set enable_memoize to off; +explain (costs off) + select 1 from tenk1 + where (hundred, thousand) in (select twothousand, twothousand from onek); +reset enable_memoize; + +-- +-- Hash Aggregation Spill tests +-- + +set enable_sort=false; +set work_mem='64kB'; + +select unique1, count(*), sum(twothousand) from tenk1 +group by unique1 +having sum(fivethous) > 4975 +order by sum(twothousand); + +set work_mem to default; +set enable_sort to default; + +-- +-- Compare results between plans using sorting and plans using hash +-- aggregation. Force spilling in both cases by setting work_mem low. +-- + +set work_mem='64kB'; + +create table agg_data_2k as +select g from generate_series(0, 1999) g; +analyze agg_data_2k; + +create table agg_data_20k as +select g from generate_series(0, 19999) g; +analyze agg_data_20k; + +-- Produce results with sorting. + +set enable_hashagg = false; + +set jit_above_cost = 0; + +explain (costs off) +select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3 + from agg_data_20k group by g%10000; + +create table agg_group_1 as +select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3 + from agg_data_20k group by g%10000; + +create table agg_group_2 as +select * from + (values (100), (300), (500)) as r(a), + lateral ( + select (g/2)::numeric as c1, + array_agg(g::numeric) as c2, + count(*) as c3 + from agg_data_2k + where g < r.a + group by g/2) as s; + +set jit_above_cost to default; + +create table agg_group_3 as +select (g/2)::numeric as c1, sum(7::int4) as c2, count(*) as c3 + from agg_data_2k group by g/2; + +create table agg_group_4 as +select (g/2)::numeric as c1, array_agg(g::numeric) as c2, count(*) as c3 + from agg_data_2k group by g/2; + +-- Produce results with hash aggregation + +set enable_hashagg = true; +set enable_sort = false; + +set jit_above_cost = 0; + +explain (costs off) +select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3 + from agg_data_20k group by g%10000; + +create table agg_hash_1 as +select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3 + from agg_data_20k group by g%10000; + +create table agg_hash_2 as +select * from + (values (100), (300), (500)) as r(a), + lateral ( + select (g/2)::numeric as c1, + array_agg(g::numeric) as c2, + count(*) as c3 + from agg_data_2k + where g < r.a + group by g/2) as s; + +set jit_above_cost to default; + +create table agg_hash_3 as +select (g/2)::numeric as c1, sum(7::int4) as c2, count(*) as c3 + from agg_data_2k group by g/2; + +create table agg_hash_4 as +select (g/2)::numeric as c1, array_agg(g::numeric) as c2, count(*) as c3 + from agg_data_2k group by g/2; + +set enable_sort = true; +set work_mem to default; + +-- Compare group aggregation results to hash aggregation results + +(select * from agg_hash_1 except select * from agg_group_1) + union all +(select * from agg_group_1 except select * from agg_hash_1); + +(select * from agg_hash_2 except select * from agg_group_2) + union all +(select * from agg_group_2 except select * from agg_hash_2); + +(select * from agg_hash_3 except select * from agg_group_3) + union all +(select * from agg_group_3 except select * from agg_hash_3); + +(select * from agg_hash_4 except select * from agg_group_4) + union all +(select * from agg_group_4 except select * from agg_hash_4); + +drop table agg_group_1; +drop table agg_group_2; +drop table agg_group_3; +drop table agg_group_4; +drop table agg_hash_1; +drop table agg_hash_2; +drop table agg_hash_3; +drop table agg_hash_4; diff --git a/src/test/regress/sql/yb.port.alter_generic.sql b/src/test/regress/sql/yb.port.alter_generic.sql new file mode 100644 index 0000000000000..452cb63adb27f --- /dev/null +++ b/src/test/regress/sql/yb.port.alter_generic.sql @@ -0,0 +1,637 @@ +-- +-- Test for ALTER some_object {RENAME TO, OWNER TO, SET SCHEMA} +-- + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +CREATE FUNCTION test_opclass_options_func(internal) + RETURNS void + AS :'regresslib', 'test_opclass_options_func' + LANGUAGE C; + +-- Clean up in case a prior regression run failed +SET client_min_messages TO 'warning'; + +DROP ROLE IF EXISTS regress_alter_generic_user1; +DROP ROLE IF EXISTS regress_alter_generic_user2; +DROP ROLE IF EXISTS regress_alter_generic_user3; + +RESET client_min_messages; + +CREATE USER regress_alter_generic_user3; +CREATE USER regress_alter_generic_user2; +CREATE USER regress_alter_generic_user1 IN ROLE regress_alter_generic_user3; + +CREATE SCHEMA alt_nsp1; +CREATE SCHEMA alt_nsp2; + +GRANT ALL ON SCHEMA alt_nsp1, alt_nsp2 TO public; + +SET search_path = alt_nsp1, public; + +-- +-- Function and Aggregate +-- +SET SESSION AUTHORIZATION regress_alter_generic_user1; +CREATE FUNCTION alt_func1(int) RETURNS int LANGUAGE sql + AS 'SELECT $1 + 1'; +CREATE FUNCTION alt_func2(int) RETURNS int LANGUAGE sql + AS 'SELECT $1 - 1'; +CREATE AGGREGATE alt_agg1 ( + sfunc1 = int4pl, basetype = int4, stype1 = int4, initcond = 0 +); +CREATE AGGREGATE alt_agg2 ( + sfunc1 = int4mi, basetype = int4, stype1 = int4, initcond = 0 +); +ALTER AGGREGATE alt_func1(int) RENAME TO alt_func3; -- failed (not aggregate) +ALTER AGGREGATE alt_func1(int) OWNER TO regress_alter_generic_user3; -- failed (not aggregate) +ALTER AGGREGATE alt_func1(int) SET SCHEMA alt_nsp2; -- failed (not aggregate) + +ALTER FUNCTION alt_func1(int) RENAME TO alt_func2; -- failed (name conflict) +ALTER FUNCTION alt_func1(int) RENAME TO alt_func3; -- OK +ALTER FUNCTION alt_func2(int) OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER FUNCTION alt_func2(int) OWNER TO regress_alter_generic_user3; -- OK +ALTER FUNCTION alt_func2(int) SET SCHEMA alt_nsp1; -- OK, already there +ALTER FUNCTION alt_func2(int) SET SCHEMA alt_nsp2; -- OK + +ALTER AGGREGATE alt_agg1(int) RENAME TO alt_agg2; -- failed (name conflict) +ALTER AGGREGATE alt_agg1(int) RENAME TO alt_agg3; -- OK +ALTER AGGREGATE alt_agg2(int) OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER AGGREGATE alt_agg2(int) OWNER TO regress_alter_generic_user3; -- OK +ALTER AGGREGATE alt_agg2(int) SET SCHEMA alt_nsp2; -- OK + +SET SESSION AUTHORIZATION regress_alter_generic_user2; +CREATE FUNCTION alt_func1(int) RETURNS int LANGUAGE sql + AS 'SELECT $1 + 2'; +CREATE FUNCTION alt_func2(int) RETURNS int LANGUAGE sql + AS 'SELECT $1 - 2'; +CREATE AGGREGATE alt_agg1 ( + sfunc1 = int4pl, basetype = int4, stype1 = int4, initcond = 100 +); +CREATE AGGREGATE alt_agg2 ( + sfunc1 = int4mi, basetype = int4, stype1 = int4, initcond = -100 +); + +ALTER FUNCTION alt_func3(int) RENAME TO alt_func4; -- failed (not owner) +ALTER FUNCTION alt_func1(int) RENAME TO alt_func4; -- OK +ALTER FUNCTION alt_func3(int) OWNER TO regress_alter_generic_user2; -- failed (not owner) +ALTER FUNCTION alt_func2(int) OWNER TO regress_alter_generic_user3; -- failed (no role membership) +ALTER FUNCTION alt_func3(int) SET SCHEMA alt_nsp2; -- failed (not owner) +ALTER FUNCTION alt_func2(int) SET SCHEMA alt_nsp2; -- failed (name conflicts) + +ALTER AGGREGATE alt_agg3(int) RENAME TO alt_agg4; -- failed (not owner) +ALTER AGGREGATE alt_agg1(int) RENAME TO alt_agg4; -- OK +ALTER AGGREGATE alt_agg3(int) OWNER TO regress_alter_generic_user2; -- failed (not owner) +ALTER AGGREGATE alt_agg2(int) OWNER TO regress_alter_generic_user3; -- failed (no role membership) +ALTER AGGREGATE alt_agg3(int) SET SCHEMA alt_nsp2; -- failed (not owner) +ALTER AGGREGATE alt_agg2(int) SET SCHEMA alt_nsp2; -- failed (name conflict) + +RESET SESSION AUTHORIZATION; + +SELECT n.nspname, proname, prorettype::regtype, prokind, a.rolname + FROM pg_proc p, pg_namespace n, pg_authid a + WHERE p.pronamespace = n.oid AND p.proowner = a.oid + AND n.nspname IN ('alt_nsp1', 'alt_nsp2') + ORDER BY nspname, proname; + +-- +-- We would test collations here, but it's not possible because the error +-- messages tend to be nonportable. +-- + +-- +-- Conversion +-- +SET SESSION AUTHORIZATION regress_alter_generic_user1; +CREATE CONVERSION alt_conv1 FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8; +/* YB: uncomment when CREATE CONVERSION is supported +CREATE CONVERSION alt_conv2 FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8; + +ALTER CONVERSION alt_conv1 RENAME TO alt_conv2; -- failed (name conflict) +ALTER CONVERSION alt_conv1 RENAME TO alt_conv3; -- OK +ALTER CONVERSION alt_conv2 OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER CONVERSION alt_conv2 OWNER TO regress_alter_generic_user3; -- OK +ALTER CONVERSION alt_conv2 SET SCHEMA alt_nsp2; -- OK + +SET SESSION AUTHORIZATION regress_alter_generic_user2; +CREATE CONVERSION alt_conv1 FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8; +CREATE CONVERSION alt_conv2 FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8; + +ALTER CONVERSION alt_conv3 RENAME TO alt_conv4; -- failed (not owner) +ALTER CONVERSION alt_conv1 RENAME TO alt_conv4; -- OK +ALTER CONVERSION alt_conv3 OWNER TO regress_alter_generic_user2; -- failed (not owner) +ALTER CONVERSION alt_conv2 OWNER TO regress_alter_generic_user3; -- failed (no role membership) +ALTER CONVERSION alt_conv3 SET SCHEMA alt_nsp2; -- failed (not owner) +ALTER CONVERSION alt_conv2 SET SCHEMA alt_nsp2; -- failed (name conflict) +*/ -- YB + +RESET SESSION AUTHORIZATION; + +/* YB: uncomment when CREATE CONVERSION is supported +SELECT n.nspname, c.conname, a.rolname + FROM pg_conversion c, pg_namespace n, pg_authid a + WHERE c.connamespace = n.oid AND c.conowner = a.oid + AND n.nspname IN ('alt_nsp1', 'alt_nsp2') + ORDER BY nspname, conname; +*/ -- YB + +-- +-- Foreign Data Wrapper and Foreign Server +-- +CREATE FOREIGN DATA WRAPPER alt_fdw1; +CREATE FOREIGN DATA WRAPPER alt_fdw2; + +CREATE SERVER alt_fserv1 FOREIGN DATA WRAPPER alt_fdw1; +CREATE SERVER alt_fserv2 FOREIGN DATA WRAPPER alt_fdw2; + +ALTER FOREIGN DATA WRAPPER alt_fdw1 RENAME TO alt_fdw2; -- failed (name conflict) +ALTER FOREIGN DATA WRAPPER alt_fdw1 RENAME TO alt_fdw3; -- OK + +ALTER SERVER alt_fserv1 RENAME TO alt_fserv2; -- failed (name conflict) +ALTER SERVER alt_fserv1 RENAME TO alt_fserv3; -- OK + +SELECT fdwname FROM pg_foreign_data_wrapper WHERE fdwname like 'alt_fdw%' ORDER BY fdwname; -- YB: add ordering +SELECT srvname FROM pg_foreign_server WHERE srvname like 'alt_fserv%' ORDER BY srvname; -- YB: add ordering + +-- +-- Procedural Language +-- +CREATE LANGUAGE alt_lang1 HANDLER plpgsql_call_handler; +CREATE LANGUAGE alt_lang2 HANDLER plpgsql_call_handler; + +ALTER LANGUAGE alt_lang1 OWNER TO regress_alter_generic_user1; -- OK +ALTER LANGUAGE alt_lang2 OWNER TO regress_alter_generic_user2; -- OK + +SET SESSION AUTHORIZATION regress_alter_generic_user1; +ALTER LANGUAGE alt_lang1 RENAME TO alt_lang2; -- failed (name conflict) +/* YB: uncomment when ALTER LANGUAGE RENAME TO is supported +ALTER LANGUAGE alt_lang2 RENAME TO alt_lang3; -- failed (not owner) +ALTER LANGUAGE alt_lang1 RENAME TO alt_lang3; -- OK + +ALTER LANGUAGE alt_lang2 OWNER TO regress_alter_generic_user3; -- failed (not owner) +ALTER LANGUAGE alt_lang3 OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER LANGUAGE alt_lang3 OWNER TO regress_alter_generic_user3; -- OK +*/ -- YB + +RESET SESSION AUTHORIZATION; +/* YB: uncomment when ALTER LANGUAGE RENAME TO is supported +SELECT lanname, a.rolname + FROM pg_language l, pg_authid a + WHERE l.lanowner = a.oid AND l.lanname like 'alt_lang%' + ORDER BY lanname; +*/ -- YB + +-- +-- Operator +-- +SET SESSION AUTHORIZATION regress_alter_generic_user1; + +CREATE OPERATOR @-@ ( leftarg = int4, rightarg = int4, procedure = int4mi ); +CREATE OPERATOR @+@ ( leftarg = int4, rightarg = int4, procedure = int4pl ); + +ALTER OPERATOR @+@(int4, int4) OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER OPERATOR @+@(int4, int4) OWNER TO regress_alter_generic_user3; -- OK +ALTER OPERATOR @-@(int4, int4) SET SCHEMA alt_nsp2; -- OK + +SET SESSION AUTHORIZATION regress_alter_generic_user2; + +CREATE OPERATOR @-@ ( leftarg = int4, rightarg = int4, procedure = int4mi ); + +ALTER OPERATOR @+@(int4, int4) OWNER TO regress_alter_generic_user2; -- failed (not owner) +ALTER OPERATOR @-@(int4, int4) OWNER TO regress_alter_generic_user3; -- failed (no role membership) +ALTER OPERATOR @+@(int4, int4) SET SCHEMA alt_nsp2; -- failed (not owner) +-- can't test this: the error message includes the raw oid of namespace +-- ALTER OPERATOR @-@(int4, int4) SET SCHEMA alt_nsp2; -- failed (name conflict) + +RESET SESSION AUTHORIZATION; + +SELECT n.nspname, oprname, a.rolname, + oprleft::regtype, oprright::regtype, oprcode::regproc + FROM pg_operator o, pg_namespace n, pg_authid a + WHERE o.oprnamespace = n.oid AND o.oprowner = a.oid + AND n.nspname IN ('alt_nsp1', 'alt_nsp2') + ORDER BY nspname, oprname; + +-- +-- OpFamily and OpClass +-- +CREATE OPERATOR FAMILY alt_opf1 USING hash; +CREATE OPERATOR FAMILY alt_opf2 USING hash; +ALTER OPERATOR FAMILY alt_opf1 USING hash OWNER TO regress_alter_generic_user1; +ALTER OPERATOR FAMILY alt_opf2 USING hash OWNER TO regress_alter_generic_user1; + +CREATE OPERATOR CLASS alt_opc1 FOR TYPE uuid USING hash AS STORAGE uuid; +CREATE OPERATOR CLASS alt_opc2 FOR TYPE uuid USING hash AS STORAGE uuid; +ALTER OPERATOR CLASS alt_opc1 USING hash OWNER TO regress_alter_generic_user1; +ALTER OPERATOR CLASS alt_opc2 USING hash OWNER TO regress_alter_generic_user1; + +SET SESSION AUTHORIZATION regress_alter_generic_user1; + +ALTER OPERATOR FAMILY alt_opf1 USING hash RENAME TO alt_opf2; -- failed (name conflict) +ALTER OPERATOR FAMILY alt_opf1 USING hash RENAME TO alt_opf3; -- OK +ALTER OPERATOR FAMILY alt_opf2 USING hash OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER OPERATOR FAMILY alt_opf2 USING hash OWNER TO regress_alter_generic_user3; -- OK +ALTER OPERATOR FAMILY alt_opf2 USING hash SET SCHEMA alt_nsp2; -- OK + +ALTER OPERATOR CLASS alt_opc1 USING hash RENAME TO alt_opc2; -- failed (name conflict) +/* YB: uncomment when ALTER OPERATOR CLASS is supported +ALTER OPERATOR CLASS alt_opc1 USING hash RENAME TO alt_opc3; -- OK +ALTER OPERATOR CLASS alt_opc2 USING hash OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER OPERATOR CLASS alt_opc2 USING hash OWNER TO regress_alter_generic_user3; -- OK +ALTER OPERATOR CLASS alt_opc2 USING hash SET SCHEMA alt_nsp2; -- OK + +RESET SESSION AUTHORIZATION; + +CREATE OPERATOR FAMILY alt_opf1 USING hash; +CREATE OPERATOR FAMILY alt_opf2 USING hash; +ALTER OPERATOR FAMILY alt_opf1 USING hash OWNER TO regress_alter_generic_user2; +ALTER OPERATOR FAMILY alt_opf2 USING hash OWNER TO regress_alter_generic_user2; + +CREATE OPERATOR CLASS alt_opc1 FOR TYPE macaddr USING hash AS STORAGE macaddr; +CREATE OPERATOR CLASS alt_opc2 FOR TYPE macaddr USING hash AS STORAGE macaddr; +ALTER OPERATOR CLASS alt_opc1 USING hash OWNER TO regress_alter_generic_user2; +ALTER OPERATOR CLASS alt_opc2 USING hash OWNER TO regress_alter_generic_user2; + +SET SESSION AUTHORIZATION regress_alter_generic_user2; + +ALTER OPERATOR FAMILY alt_opf3 USING hash RENAME TO alt_opf4; -- failed (not owner) +ALTER OPERATOR FAMILY alt_opf1 USING hash RENAME TO alt_opf4; -- OK +ALTER OPERATOR FAMILY alt_opf3 USING hash OWNER TO regress_alter_generic_user2; -- failed (not owner) +ALTER OPERATOR FAMILY alt_opf2 USING hash OWNER TO regress_alter_generic_user3; -- failed (no role membership) +ALTER OPERATOR FAMILY alt_opf3 USING hash SET SCHEMA alt_nsp2; -- failed (not owner) +ALTER OPERATOR FAMILY alt_opf2 USING hash SET SCHEMA alt_nsp2; -- failed (name conflict) + +ALTER OPERATOR CLASS alt_opc3 USING hash RENAME TO alt_opc4; -- failed (not owner) +ALTER OPERATOR CLASS alt_opc1 USING hash RENAME TO alt_opc4; -- OK +ALTER OPERATOR CLASS alt_opc3 USING hash OWNER TO regress_alter_generic_user2; -- failed (not owner) +ALTER OPERATOR CLASS alt_opc2 USING hash OWNER TO regress_alter_generic_user3; -- failed (no role membership) +ALTER OPERATOR CLASS alt_opc3 USING hash SET SCHEMA alt_nsp2; -- failed (not owner) +ALTER OPERATOR CLASS alt_opc2 USING hash SET SCHEMA alt_nsp2; -- failed (name conflict) +*/ -- YB + +RESET SESSION AUTHORIZATION; + +/* YB: uncomment when ALTER OPERATOR CLASS is supported +SELECT nspname, opfname, amname, rolname + FROM pg_opfamily o, pg_am m, pg_namespace n, pg_authid a + WHERE o.opfmethod = m.oid AND o.opfnamespace = n.oid AND o.opfowner = a.oid + AND n.nspname IN ('alt_nsp1', 'alt_nsp2') + AND NOT opfname LIKE 'alt_opc%' + ORDER BY nspname, opfname; + +SELECT nspname, opcname, amname, rolname + FROM pg_opclass o, pg_am m, pg_namespace n, pg_authid a + WHERE o.opcmethod = m.oid AND o.opcnamespace = n.oid AND o.opcowner = a.oid + AND n.nspname IN ('alt_nsp1', 'alt_nsp2') + ORDER BY nspname, opcname; +*/ -- YB + +-- ALTER OPERATOR FAMILY ... ADD/DROP + +-- Should work. Textbook case of CREATE / ALTER ADD / ALTER DROP / DROP +BEGIN TRANSACTION; +CREATE OPERATOR FAMILY alt_opf4 USING btree; +ALTER OPERATOR FAMILY alt_opf4 USING btree ADD + -- int4 vs int2 + OPERATOR 1 < (int4, int2) , + OPERATOR 2 <= (int4, int2) , + OPERATOR 3 = (int4, int2) , + OPERATOR 4 >= (int4, int2) , + OPERATOR 5 > (int4, int2) , + FUNCTION 1 btint42cmp(int4, int2); + +ALTER OPERATOR FAMILY alt_opf4 USING btree DROP + -- int4 vs int2 + OPERATOR 1 (int4, int2) , + OPERATOR 2 (int4, int2) , + OPERATOR 3 (int4, int2) , + OPERATOR 4 (int4, int2) , + OPERATOR 5 (int4, int2) , + FUNCTION 1 (int4, int2) ; +DROP OPERATOR FAMILY alt_opf4 USING btree; +ROLLBACK; + +-- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP +CREATE OPERATOR FAMILY alt_opf4 USING btree; +ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD OPERATOR 1 < (int4, int2); -- invalid indexing_method +ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5 +ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5 +ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types +ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- invalid options parsing function +ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5 +ALTER OPERATOR FAMILY alt_opf4 USING btree ADD STORAGE invalid_storage; -- Ensure STORAGE is not a part of ALTER OPERATOR FAMILY +DROP OPERATOR FAMILY alt_opf4 USING btree; + +-- Should fail. Need to be SUPERUSER to do ALTER OPERATOR FAMILY .. ADD / DROP +BEGIN TRANSACTION; +CREATE ROLE regress_alter_generic_user5 NOSUPERUSER; +CREATE OPERATOR FAMILY alt_opf5 USING btree; +SET ROLE regress_alter_generic_user5; +ALTER OPERATOR FAMILY alt_opf5 USING btree ADD OPERATOR 1 < (int4, int2), FUNCTION 1 btint42cmp(int4, int2); +RESET ROLE; +DROP OPERATOR FAMILY alt_opf5 USING btree; +ROLLBACK; +DROP OPERATOR FAMILY alt_opf5 USING btree; -- YB: workaround for lack of transactional DDL + +-- Should fail. Need rights to namespace for ALTER OPERATOR FAMILY .. ADD / DROP +BEGIN TRANSACTION; +CREATE ROLE regress_alter_generic_user6; +CREATE SCHEMA alt_nsp6; +REVOKE ALL ON SCHEMA alt_nsp6 FROM regress_alter_generic_user6; +CREATE OPERATOR FAMILY alt_nsp6.alt_opf6 USING btree; +SET ROLE regress_alter_generic_user6; +ALTER OPERATOR FAMILY alt_nsp6.alt_opf6 USING btree ADD OPERATOR 1 < (int4, int2); +ROLLBACK; + +-- Should fail. Only two arguments required for ALTER OPERATOR FAMILY ... DROP OPERATOR +CREATE OPERATOR FAMILY alt_opf7 USING btree; +ALTER OPERATOR FAMILY alt_opf7 USING btree ADD OPERATOR 1 < (int4, int2); +ALTER OPERATOR FAMILY alt_opf7 USING btree DROP OPERATOR 1 (int4, int2, int8); +DROP OPERATOR FAMILY alt_opf7 USING btree; + +-- Should work. During ALTER OPERATOR FAMILY ... DROP OPERATOR +-- when left type is the same as right type, a DROP with only one argument type should work +CREATE OPERATOR FAMILY alt_opf8 USING btree; +ALTER OPERATOR FAMILY alt_opf8 USING btree ADD OPERATOR 1 < (int4, int4); +DROP OPERATOR FAMILY alt_opf8 USING btree; + +-- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY +CREATE OPERATOR FAMILY alt_opf9 USING gist; +ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops; +DROP OPERATOR FAMILY alt_opf9 USING gist; + +-- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY +CREATE OPERATOR FAMILY alt_opf10 USING btree; +ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops; +DROP OPERATOR FAMILY alt_opf10 USING btree; + +-- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY +CREATE OPERATOR FAMILY alt_opf11 USING gist; +ALTER OPERATOR FAMILY alt_opf11 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops; +ALTER OPERATOR FAMILY alt_opf11 USING gist DROP OPERATOR 1 (int4, int4); +DROP OPERATOR FAMILY alt_opf11 USING gist; + +-- Should fail. btree comparison functions should return INTEGER in ALTER OPERATOR FAMILY ... ADD FUNCTION +BEGIN TRANSACTION; +CREATE OPERATOR FAMILY alt_opf12 USING btree; +CREATE FUNCTION fn_opf12 (int4, int2) RETURNS BIGINT AS 'SELECT NULL::BIGINT;' LANGUAGE SQL; +ALTER OPERATOR FAMILY alt_opf12 USING btree ADD FUNCTION 1 fn_opf12(int4, int2); +DROP OPERATOR FAMILY alt_opf12 USING btree; +ROLLBACK; +DROP FUNCTION fn_opf12 (int4, int2); -- YB: workaround for lack of transactional DDL +DROP OPERATOR FAMILY alt_opf12 USING btree; -- YB: workaround for lack of transactional DDL + +-- Should fail. hash comparison functions should return INTEGER in ALTER OPERATOR FAMILY ... ADD FUNCTION +BEGIN TRANSACTION; +CREATE OPERATOR FAMILY alt_opf13 USING hash; +CREATE FUNCTION fn_opf13 (int4) RETURNS BIGINT AS 'SELECT NULL::BIGINT;' LANGUAGE SQL; +ALTER OPERATOR FAMILY alt_opf13 USING hash ADD FUNCTION 1 fn_opf13(int4); +DROP OPERATOR FAMILY alt_opf13 USING hash; +ROLLBACK; +DROP FUNCTION fn_opf13 (int4); -- YB: workaround for lack of transactional DDL +DROP OPERATOR FAMILY alt_opf13 USING hash; -- YB: workaround for lack of transactional DDL + +-- Should fail. btree comparison functions should have two arguments in ALTER OPERATOR FAMILY ... ADD FUNCTION +BEGIN TRANSACTION; +CREATE OPERATOR FAMILY alt_opf14 USING btree; +CREATE FUNCTION fn_opf14 (int4) RETURNS BIGINT AS 'SELECT NULL::BIGINT;' LANGUAGE SQL; +ALTER OPERATOR FAMILY alt_opf14 USING btree ADD FUNCTION 1 fn_opf14(int4); +DROP OPERATOR FAMILY alt_opf14 USING btree; +ROLLBACK; +DROP FUNCTION fn_opf14 (int4); -- YB: workaround for lack of transactional DDL +DROP OPERATOR FAMILY alt_opf14 USING btree; -- YB: workaround for lack of transactional DDL + +-- Should fail. hash comparison functions should have one argument in ALTER OPERATOR FAMILY ... ADD FUNCTION +BEGIN TRANSACTION; +CREATE OPERATOR FAMILY alt_opf15 USING hash; +CREATE FUNCTION fn_opf15 (int4, int2) RETURNS BIGINT AS 'SELECT NULL::BIGINT;' LANGUAGE SQL; +ALTER OPERATOR FAMILY alt_opf15 USING hash ADD FUNCTION 1 fn_opf15(int4, int2); +DROP OPERATOR FAMILY alt_opf15 USING hash; +ROLLBACK; +DROP FUNCTION fn_opf15 (int4, int2); -- YB: workaround for lack of transactional DDL +DROP OPERATOR FAMILY alt_opf15 USING hash; -- YB: workaround for lack of transactional DDL + +-- Should fail. In gist throw an error when giving different data types for function argument +-- without defining left / right type in ALTER OPERATOR FAMILY ... ADD FUNCTION +CREATE OPERATOR FAMILY alt_opf16 USING gist; +ALTER OPERATOR FAMILY alt_opf16 USING gist ADD FUNCTION 1 btint42cmp(int4, int2); +DROP OPERATOR FAMILY alt_opf16 USING gist; + +-- Should fail. duplicate operator number / function number in ALTER OPERATOR FAMILY ... ADD FUNCTION +CREATE OPERATOR FAMILY alt_opf17 USING btree; +ALTER OPERATOR FAMILY alt_opf17 USING btree ADD OPERATOR 1 < (int4, int4), OPERATOR 1 < (int4, int4); -- operator # appears twice in same statement +ALTER OPERATOR FAMILY alt_opf17 USING btree ADD OPERATOR 1 < (int4, int4); -- operator 1 requested first-time +ALTER OPERATOR FAMILY alt_opf17 USING btree ADD OPERATOR 1 < (int4, int4); -- operator 1 requested again in separate statement +ALTER OPERATOR FAMILY alt_opf17 USING btree ADD + OPERATOR 1 < (int4, int2) , + OPERATOR 2 <= (int4, int2) , + OPERATOR 3 = (int4, int2) , + OPERATOR 4 >= (int4, int2) , + OPERATOR 5 > (int4, int2) , + FUNCTION 1 btint42cmp(int4, int2) , + FUNCTION 1 btint42cmp(int4, int2); -- procedure 1 appears twice in same statement +ALTER OPERATOR FAMILY alt_opf17 USING btree ADD + OPERATOR 1 < (int4, int2) , + OPERATOR 2 <= (int4, int2) , + OPERATOR 3 = (int4, int2) , + OPERATOR 4 >= (int4, int2) , + OPERATOR 5 > (int4, int2) , + FUNCTION 1 btint42cmp(int4, int2); -- procedure 1 appears first time +ALTER OPERATOR FAMILY alt_opf17 USING btree ADD + OPERATOR 1 < (int4, int2) , + OPERATOR 2 <= (int4, int2) , + OPERATOR 3 = (int4, int2) , + OPERATOR 4 >= (int4, int2) , + OPERATOR 5 > (int4, int2) , + FUNCTION 1 btint42cmp(int4, int2); -- procedure 1 requested again in separate statement +DROP OPERATOR FAMILY alt_opf17 USING btree; + + +-- Should fail. Ensure that DROP requests for missing OPERATOR / FUNCTIONS +-- return appropriate message in ALTER OPERATOR FAMILY ... DROP OPERATOR / FUNCTION +CREATE OPERATOR FAMILY alt_opf18 USING btree; +ALTER OPERATOR FAMILY alt_opf18 USING btree DROP OPERATOR 1 (int4, int4); +ALTER OPERATOR FAMILY alt_opf18 USING btree ADD + OPERATOR 1 < (int4, int2) , + OPERATOR 2 <= (int4, int2) , + OPERATOR 3 = (int4, int2) , + OPERATOR 4 >= (int4, int2) , + OPERATOR 5 > (int4, int2) , + FUNCTION 1 btint42cmp(int4, int2); +-- Should fail. Not allowed to have cross-type equalimage function. +ALTER OPERATOR FAMILY alt_opf18 USING btree + ADD FUNCTION 4 (int4, int2) btequalimage(oid); +ALTER OPERATOR FAMILY alt_opf18 USING btree DROP FUNCTION 2 (int4, int4); +DROP OPERATOR FAMILY alt_opf18 USING btree; + +-- Should fail. Invalid opclass options function (#5) specifications. +CREATE OPERATOR FAMILY alt_opf19 USING btree; +ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 test_opclass_options_func(internal, text[], bool); +ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 (int4) btint42cmp(int4, int2); +ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 (int4, int2) btint42cmp(int4, int2); +ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 (int4) test_opclass_options_func(internal); -- Ok +ALTER OPERATOR FAMILY alt_opf19 USING btree DROP FUNCTION 5 (int4, int4); +DROP OPERATOR FAMILY alt_opf19 USING btree; + +-- +-- Statistics +-- +SET SESSION AUTHORIZATION regress_alter_generic_user1; +CREATE TABLE alt_regress_1 (a INTEGER, b INTEGER); +CREATE STATISTICS alt_stat1 ON a, b FROM alt_regress_1; +CREATE STATISTICS alt_stat2 ON a, b FROM alt_regress_1; + +ALTER STATISTICS alt_stat1 RENAME TO alt_stat2; -- failed (name conflict) +ALTER STATISTICS alt_stat1 RENAME TO alt_stat3; -- OK +ALTER STATISTICS alt_stat2 OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER STATISTICS alt_stat2 OWNER TO regress_alter_generic_user3; -- OK +ALTER STATISTICS alt_stat2 SET SCHEMA alt_nsp2; -- OK + +SET SESSION AUTHORIZATION regress_alter_generic_user2; +CREATE TABLE alt_regress_2 (a INTEGER, b INTEGER); +CREATE STATISTICS alt_stat1 ON a, b FROM alt_regress_2; +CREATE STATISTICS alt_stat2 ON a, b FROM alt_regress_2; + +ALTER STATISTICS alt_stat3 RENAME TO alt_stat4; -- failed (not owner) +ALTER STATISTICS alt_stat1 RENAME TO alt_stat4; -- OK +ALTER STATISTICS alt_stat3 OWNER TO regress_alter_generic_user2; -- failed (not owner) +ALTER STATISTICS alt_stat2 OWNER TO regress_alter_generic_user3; -- failed (no role membership) +ALTER STATISTICS alt_stat3 SET SCHEMA alt_nsp2; -- failed (not owner) +ALTER STATISTICS alt_stat2 SET SCHEMA alt_nsp2; -- failed (name conflict) + +RESET SESSION AUTHORIZATION; +SELECT nspname, stxname, rolname + FROM pg_statistic_ext s, pg_namespace n, pg_authid a + WHERE s.stxnamespace = n.oid AND s.stxowner = a.oid + AND n.nspname in ('alt_nsp1', 'alt_nsp2') + ORDER BY nspname, stxname; + +-- +-- Text Search Dictionary +-- +SET SESSION AUTHORIZATION regress_alter_generic_user1; +CREATE TEXT SEARCH DICTIONARY alt_ts_dict1 (template=simple); +CREATE TEXT SEARCH DICTIONARY alt_ts_dict2 (template=simple); + +ALTER TEXT SEARCH DICTIONARY alt_ts_dict1 RENAME TO alt_ts_dict2; -- failed (name conflict) +ALTER TEXT SEARCH DICTIONARY alt_ts_dict1 RENAME TO alt_ts_dict3; -- OK +ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 OWNER TO regress_alter_generic_user3; -- OK +ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 SET SCHEMA alt_nsp2; -- OK + +SET SESSION AUTHORIZATION regress_alter_generic_user2; +CREATE TEXT SEARCH DICTIONARY alt_ts_dict1 (template=simple); +CREATE TEXT SEARCH DICTIONARY alt_ts_dict2 (template=simple); + +ALTER TEXT SEARCH DICTIONARY alt_ts_dict3 RENAME TO alt_ts_dict4; -- failed (not owner) +ALTER TEXT SEARCH DICTIONARY alt_ts_dict1 RENAME TO alt_ts_dict4; -- OK +ALTER TEXT SEARCH DICTIONARY alt_ts_dict3 OWNER TO regress_alter_generic_user2; -- failed (not owner) +ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 OWNER TO regress_alter_generic_user3; -- failed (no role membership) +ALTER TEXT SEARCH DICTIONARY alt_ts_dict3 SET SCHEMA alt_nsp2; -- failed (not owner) +ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 SET SCHEMA alt_nsp2; -- failed (name conflict) + +RESET SESSION AUTHORIZATION; + +SELECT nspname, dictname, rolname + FROM pg_ts_dict t, pg_namespace n, pg_authid a + WHERE t.dictnamespace = n.oid AND t.dictowner = a.oid + AND n.nspname in ('alt_nsp1', 'alt_nsp2') + ORDER BY nspname, dictname; + +-- +-- Text Search Configuration +-- +SET SESSION AUTHORIZATION regress_alter_generic_user1; +CREATE TEXT SEARCH CONFIGURATION alt_ts_conf1 (copy=english); +CREATE TEXT SEARCH CONFIGURATION alt_ts_conf2 (copy=english); + +ALTER TEXT SEARCH CONFIGURATION alt_ts_conf1 RENAME TO alt_ts_conf2; -- failed (name conflict) +ALTER TEXT SEARCH CONFIGURATION alt_ts_conf1 RENAME TO alt_ts_conf3; -- OK +ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 OWNER TO regress_alter_generic_user3; -- OK +ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 SET SCHEMA alt_nsp2; -- OK + +SET SESSION AUTHORIZATION regress_alter_generic_user2; +CREATE TEXT SEARCH CONFIGURATION alt_ts_conf1 (copy=english); +CREATE TEXT SEARCH CONFIGURATION alt_ts_conf2 (copy=english); + +ALTER TEXT SEARCH CONFIGURATION alt_ts_conf3 RENAME TO alt_ts_conf4; -- failed (not owner) +ALTER TEXT SEARCH CONFIGURATION alt_ts_conf1 RENAME TO alt_ts_conf4; -- OK +ALTER TEXT SEARCH CONFIGURATION alt_ts_conf3 OWNER TO regress_alter_generic_user2; -- failed (not owner) +ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 OWNER TO regress_alter_generic_user3; -- failed (no role membership) +ALTER TEXT SEARCH CONFIGURATION alt_ts_conf3 SET SCHEMA alt_nsp2; -- failed (not owner) +ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 SET SCHEMA alt_nsp2; -- failed (name conflict) + +RESET SESSION AUTHORIZATION; + +SELECT nspname, cfgname, rolname + FROM pg_ts_config t, pg_namespace n, pg_authid a + WHERE t.cfgnamespace = n.oid AND t.cfgowner = a.oid + AND n.nspname in ('alt_nsp1', 'alt_nsp2') + ORDER BY nspname, cfgname; + +-- +-- Text Search Template +-- +CREATE TEXT SEARCH TEMPLATE alt_ts_temp1 (lexize=dsimple_lexize); +CREATE TEXT SEARCH TEMPLATE alt_ts_temp2 (lexize=dsimple_lexize); + +ALTER TEXT SEARCH TEMPLATE alt_ts_temp1 RENAME TO alt_ts_temp2; -- failed (name conflict) +ALTER TEXT SEARCH TEMPLATE alt_ts_temp1 RENAME TO alt_ts_temp3; -- OK +ALTER TEXT SEARCH TEMPLATE alt_ts_temp2 SET SCHEMA alt_nsp2; -- OK + +CREATE TEXT SEARCH TEMPLATE alt_ts_temp2 (lexize=dsimple_lexize); +ALTER TEXT SEARCH TEMPLATE alt_ts_temp2 SET SCHEMA alt_nsp2; -- failed (name conflict) + +-- invalid: non-lowercase quoted identifiers +CREATE TEXT SEARCH TEMPLATE tstemp_case ("Init" = init_function); + +SELECT nspname, tmplname + FROM pg_ts_template t, pg_namespace n + WHERE t.tmplnamespace = n.oid AND nspname like 'alt_nsp%' + ORDER BY nspname, tmplname; + +-- +-- Text Search Parser +-- + +CREATE TEXT SEARCH PARSER alt_ts_prs1 + (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype); +CREATE TEXT SEARCH PARSER alt_ts_prs2 + (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype); + +ALTER TEXT SEARCH PARSER alt_ts_prs1 RENAME TO alt_ts_prs2; -- failed (name conflict) +ALTER TEXT SEARCH PARSER alt_ts_prs1 RENAME TO alt_ts_prs3; -- OK +ALTER TEXT SEARCH PARSER alt_ts_prs2 SET SCHEMA alt_nsp2; -- OK + +CREATE TEXT SEARCH PARSER alt_ts_prs2 + (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype); +ALTER TEXT SEARCH PARSER alt_ts_prs2 SET SCHEMA alt_nsp2; -- failed (name conflict) + +-- invalid: non-lowercase quoted identifiers +CREATE TEXT SEARCH PARSER tspars_case ("Start" = start_function); + +SELECT nspname, prsname + FROM pg_ts_parser t, pg_namespace n + WHERE t.prsnamespace = n.oid AND nspname like 'alt_nsp%' + ORDER BY nspname, prsname; + +--- +--- Cleanup resources +--- +DROP FOREIGN DATA WRAPPER alt_fdw2 CASCADE; +DROP FOREIGN DATA WRAPPER alt_fdw3 CASCADE; + +DROP LANGUAGE alt_lang2 CASCADE; +DROP LANGUAGE alt_lang3 CASCADE; -- YB: fails due to above commenting out + +DROP SCHEMA alt_nsp1 CASCADE; -- YB: output different due to above commenting out +DROP SCHEMA alt_nsp2 CASCADE; -- YB: output different due to above commenting out + +DROP USER regress_alter_generic_user1; -- YB: fails due to above commenting out +DROP USER regress_alter_generic_user2; +DROP USER regress_alter_generic_user3; diff --git a/src/test/regress/sql/yb.port.alter_table.sql b/src/test/regress/sql/yb.port.alter_table.sql new file mode 100644 index 0000000000000..cd5f8130e6353 --- /dev/null +++ b/src/test/regress/sql/yb.port.alter_table.sql @@ -0,0 +1,2726 @@ +-- +-- ALTER_TABLE +-- + +-- Clean up in case a prior regression run failed +SET client_min_messages TO 'warning'; +DROP ROLE IF EXISTS regress_alter_table_user1; +RESET client_min_messages; + +CREATE USER regress_alter_table_user1; + +-- +-- add attribute +-- + +CREATE TABLE attmp (initial int4); + +COMMENT ON TABLE attmp_wrong IS 'table comment'; +COMMENT ON TABLE attmp IS 'table comment'; +COMMENT ON TABLE attmp IS NULL; + +ALTER TABLE attmp ADD COLUMN xmin integer; -- fails + +ALTER TABLE attmp ADD COLUMN a int4 default 3; + +ALTER TABLE attmp ADD COLUMN b name; + +ALTER TABLE attmp ADD COLUMN c text; + +ALTER TABLE attmp ADD COLUMN d float8; + +ALTER TABLE attmp ADD COLUMN e float4; + +ALTER TABLE attmp ADD COLUMN f int2; + +ALTER TABLE attmp ADD COLUMN g polygon; + +ALTER TABLE attmp ADD COLUMN i char; + +ALTER TABLE attmp ADD COLUMN k int4; + +ALTER TABLE attmp ADD COLUMN l tid; + +ALTER TABLE attmp ADD COLUMN m xid; + +ALTER TABLE attmp ADD COLUMN n oidvector; + +--ALTER TABLE attmp ADD COLUMN o lock; +ALTER TABLE attmp ADD COLUMN p boolean; + +ALTER TABLE attmp ADD COLUMN q point; + +ALTER TABLE attmp ADD COLUMN r lseg; + +ALTER TABLE attmp ADD COLUMN s path; + +ALTER TABLE attmp ADD COLUMN t box; + +ALTER TABLE attmp ADD COLUMN v timestamp; + +ALTER TABLE attmp ADD COLUMN w interval; + +ALTER TABLE attmp ADD COLUMN x float8[]; + +ALTER TABLE attmp ADD COLUMN y float4[]; + +ALTER TABLE attmp ADD COLUMN z int2[]; + +INSERT INTO attmp (a, b, c, d, e, f, g, i, k, l, m, n, p, q, r, s, t, + v, w, x, y, z) + VALUES (4, 'name', 'text', 4.1, 4.1, 2, '(4.1,4.1,3.1,3.1)', + 'c', + 314159, '(1,1)', '512', + '1 2 3 4 5 6 7 8', true, '(1.1,1.1)', '(4.1,4.1,3.1,3.1)', + '(0,2,4.1,4.1,3.1,3.1)', '(4.1,4.1,3.1,3.1)', + 'epoch', '01:00:10', '{1.0,2.0,3.0,4.0}', '{1.0,2.0,3.0,4.0}', '{1,2,3,4}'); + +SELECT * FROM attmp; + +DROP TABLE attmp; + +-- the wolf bug - schema mods caused inconsistent row descriptors +CREATE TABLE attmp ( + initial int4 +); + +ALTER TABLE attmp ADD COLUMN a int4; + +ALTER TABLE attmp ADD COLUMN b name; + +ALTER TABLE attmp ADD COLUMN c text; + +ALTER TABLE attmp ADD COLUMN d float8; + +ALTER TABLE attmp ADD COLUMN e float4; + +ALTER TABLE attmp ADD COLUMN f int2; + +ALTER TABLE attmp ADD COLUMN g polygon; + +ALTER TABLE attmp ADD COLUMN i char; + +ALTER TABLE attmp ADD COLUMN k int4; + +ALTER TABLE attmp ADD COLUMN l tid; + +ALTER TABLE attmp ADD COLUMN m xid; + +ALTER TABLE attmp ADD COLUMN n oidvector; + +--ALTER TABLE attmp ADD COLUMN o lock; +ALTER TABLE attmp ADD COLUMN p boolean; + +ALTER TABLE attmp ADD COLUMN q point; + +ALTER TABLE attmp ADD COLUMN r lseg; + +ALTER TABLE attmp ADD COLUMN s path; + +ALTER TABLE attmp ADD COLUMN t box; + +ALTER TABLE attmp ADD COLUMN v timestamp; + +ALTER TABLE attmp ADD COLUMN w interval; + +ALTER TABLE attmp ADD COLUMN x float8[]; + +ALTER TABLE attmp ADD COLUMN y float4[]; + +ALTER TABLE attmp ADD COLUMN z int2[]; + +INSERT INTO attmp (a, b, c, d, e, f, g, i, k, l, m, n, p, q, r, s, t, + v, w, x, y, z) + VALUES (4, 'name', 'text', 4.1, 4.1, 2, '(4.1,4.1,3.1,3.1)', + 'c', + 314159, '(1,1)', '512', + '1 2 3 4 5 6 7 8', true, '(1.1,1.1)', '(4.1,4.1,3.1,3.1)', + '(0,2,4.1,4.1,3.1,3.1)', '(4.1,4.1,3.1,3.1)', + 'epoch', '01:00:10', '{1.0,2.0,3.0,4.0}', '{1.0,2.0,3.0,4.0}', '{1,2,3,4}'); + +SELECT * FROM attmp; + +CREATE INDEX attmp_idx ON attmp (a, (d + e), b); + +ALTER INDEX attmp_idx ALTER COLUMN 0 SET STATISTICS 1000; + +ALTER INDEX attmp_idx ALTER COLUMN 1 SET STATISTICS 1000; + +ALTER INDEX attmp_idx ALTER COLUMN 2 SET STATISTICS 1000; + +\d+ attmp_idx + +ALTER INDEX attmp_idx ALTER COLUMN 3 SET STATISTICS 1000; + +ALTER INDEX attmp_idx ALTER COLUMN 4 SET STATISTICS 1000; + +ALTER INDEX attmp_idx ALTER COLUMN 2 SET STATISTICS -1; + +DROP TABLE attmp; + + +-- +-- rename - check on both non-temp and temp tables +-- +CREATE TABLE attmp (regtable int); +CREATE TEMP TABLE attmp (attmptable int); + +ALTER TABLE attmp RENAME TO attmp_new; + +SELECT * FROM attmp; +SELECT * FROM attmp_new; + +ALTER TABLE attmp RENAME TO attmp_new2; + +SELECT * FROM attmp; -- should fail +SELECT * FROM attmp_new; +SELECT * FROM attmp_new2; + +DROP TABLE attmp_new; +DROP TABLE attmp_new2; + +-- check rename of partitioned tables and indexes also +CREATE TABLE part_attmp (a int primary key) partition by range (a); +CREATE TABLE part_attmp1 PARTITION OF part_attmp FOR VALUES FROM (0) TO (100); +ALTER INDEX part_attmp_pkey RENAME TO part_attmp_index; +ALTER INDEX part_attmp1_pkey RENAME TO part_attmp1_index; +ALTER TABLE part_attmp RENAME TO part_at2tmp; +ALTER TABLE part_attmp1 RENAME TO part_at2tmp1; +SET ROLE regress_alter_table_user1; +ALTER INDEX part_attmp_index RENAME TO fail; +ALTER INDEX part_attmp1_index RENAME TO fail; +ALTER TABLE part_at2tmp RENAME TO fail; +ALTER TABLE part_at2tmp1 RENAME TO fail; +RESET ROLE; +DROP TABLE part_at2tmp; + +-- +-- check renaming to a table's array type's autogenerated name +-- (the array type's name should get out of the way) +-- +CREATE TABLE attmp_array (id int); +CREATE TABLE attmp_array2 (id int); +SELECT typname FROM pg_type WHERE oid = 'attmp_array[]'::regtype; +SELECT typname FROM pg_type WHERE oid = 'attmp_array2[]'::regtype; +ALTER TABLE attmp_array2 RENAME TO _attmp_array; +SELECT typname FROM pg_type WHERE oid = 'attmp_array[]'::regtype; +SELECT typname FROM pg_type WHERE oid = '_attmp_array[]'::regtype; +DROP TABLE _attmp_array; +DROP TABLE attmp_array; + +-- renaming to table's own array type's name is an interesting corner case +CREATE TABLE attmp_array (id int); +SELECT typname FROM pg_type WHERE oid = 'attmp_array[]'::regtype; +ALTER TABLE attmp_array RENAME TO _attmp_array; +SELECT typname FROM pg_type WHERE oid = '_attmp_array[]'::regtype; +DROP TABLE _attmp_array; + +-- ALTER TABLE ... RENAME on non-table relations +-- renaming indexes (FIXME: this should probably test the index's functionality) +ALTER INDEX IF EXISTS __onek_unique1 RENAME TO attmp_onek_unique1; +ALTER INDEX IF EXISTS __attmp_onek_unique1 RENAME TO onek_unique1; + +ALTER INDEX onek_unique1 RENAME TO attmp_onek_unique1; +ALTER INDEX attmp_onek_unique1 RENAME TO onek_unique1; + +SET ROLE regress_alter_table_user1; +ALTER INDEX onek_unique1 RENAME TO fail; -- permission denied +RESET ROLE; + +-- renaming index should rename constraint as well +ALTER TABLE onek ADD CONSTRAINT onek_unique1_constraint UNIQUE (unique1); +ALTER INDEX onek_unique1_constraint RENAME TO onek_unique1_constraint_foo; +ALTER TABLE onek DROP CONSTRAINT onek_unique1_constraint_foo; + +-- renaming constraint +ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= 0); +ALTER TABLE onek RENAME CONSTRAINT onek_check_constraint TO onek_check_constraint_foo; +ALTER TABLE onek DROP CONSTRAINT onek_check_constraint_foo; + +-- renaming constraint should rename index as well +ALTER TABLE onek ADD CONSTRAINT onek_unique1_constraint UNIQUE (unique1); +DROP INDEX onek_unique1_constraint; -- to see whether it's there +ALTER TABLE onek RENAME CONSTRAINT onek_unique1_constraint TO onek_unique1_constraint_foo; +DROP INDEX onek_unique1_constraint_foo; -- to see whether it's there +ALTER TABLE onek DROP CONSTRAINT onek_unique1_constraint_foo; + +-- renaming constraints vs. inheritance +CREATE TABLE constraint_rename_test (a int CONSTRAINT con1 CHECK (a > 0), b int, c int); +\d constraint_rename_test +CREATE TABLE constraint_rename_test2 (a int CONSTRAINT con1 CHECK (a > 0), d int) INHERITS (constraint_rename_test); +\d constraint_rename_test2 +ALTER TABLE constraint_rename_test2 RENAME CONSTRAINT con1 TO con1foo; -- fail +ALTER TABLE ONLY constraint_rename_test RENAME CONSTRAINT con1 TO con1foo; -- fail +ALTER TABLE constraint_rename_test RENAME CONSTRAINT con1 TO con1foo; -- ok +\d constraint_rename_test +\d constraint_rename_test2 +ALTER TABLE constraint_rename_test ADD CONSTRAINT con2 CHECK (b > 0) NO INHERIT; +ALTER TABLE ONLY constraint_rename_test RENAME CONSTRAINT con2 TO con2foo; -- ok +ALTER TABLE constraint_rename_test RENAME CONSTRAINT con2foo TO con2bar; -- ok +\d constraint_rename_test +\d constraint_rename_test2 +ALTER TABLE constraint_rename_test ADD CONSTRAINT con3 PRIMARY KEY (a); +ALTER TABLE constraint_rename_test RENAME CONSTRAINT con3 TO con3foo; -- ok +\d constraint_rename_test +\d constraint_rename_test2 +DROP TABLE constraint_rename_test2; +DROP TABLE constraint_rename_test; +ALTER TABLE IF EXISTS constraint_not_exist RENAME CONSTRAINT con3 TO con3foo; -- ok +ALTER TABLE IF EXISTS constraint_rename_test ADD CONSTRAINT con4 UNIQUE (a); + +-- renaming constraints with cache reset of target relation +CREATE TABLE constraint_rename_cache (a int, + CONSTRAINT chk_a CHECK (a > 0), + PRIMARY KEY (a)); +ALTER TABLE constraint_rename_cache + RENAME CONSTRAINT chk_a TO chk_a_new; +ALTER TABLE constraint_rename_cache + RENAME CONSTRAINT constraint_rename_cache_pkey TO constraint_rename_pkey_new; +CREATE TABLE like_constraint_rename_cache + (LIKE constraint_rename_cache INCLUDING ALL); +\d like_constraint_rename_cache +DROP TABLE constraint_rename_cache; +DROP TABLE like_constraint_rename_cache; + +-- FOREIGN KEY CONSTRAINT adding TEST + +CREATE TABLE attmp2 (a int primary key); + +CREATE TABLE attmp3 (a int, b int); + +CREATE TABLE attmp4 (a int, b int, unique(a,b)); + +CREATE TABLE attmp5 (a int, b int); + +-- Insert rows into attmp2 (pktable) +INSERT INTO attmp2 values (1); +INSERT INTO attmp2 values (2); +INSERT INTO attmp2 values (3); +INSERT INTO attmp2 values (4); + +-- Insert rows into attmp3 +INSERT INTO attmp3 values (1,10); +INSERT INTO attmp3 values (1,20); +INSERT INTO attmp3 values (5,50); + +-- Try (and fail) to add constraint due to invalid source columns +ALTER TABLE attmp3 add constraint attmpconstr foreign key(c) references attmp2 match full; + +-- Try (and fail) to add constraint due to invalid destination columns explicitly given +ALTER TABLE attmp3 add constraint attmpconstr foreign key(a) references attmp2(b) match full; + +-- Try (and fail) to add constraint due to invalid data +ALTER TABLE attmp3 add constraint attmpconstr foreign key (a) references attmp2 match full; + +-- Delete failing row +DELETE FROM attmp3 where a=5; + +-- Try (and succeed) +ALTER TABLE attmp3 add constraint attmpconstr foreign key (a) references attmp2 match full; +ALTER TABLE attmp3 drop constraint attmpconstr; + +INSERT INTO attmp3 values (5,50); + +-- Try NOT VALID and then VALIDATE CONSTRAINT, but fails. Delete failure then re-validate +ALTER TABLE attmp3 add constraint attmpconstr foreign key (a) references attmp2 match full NOT VALID; +ALTER TABLE attmp3 validate constraint attmpconstr; + +-- Delete failing row +DELETE FROM attmp3 where a=5; + +-- Try (and succeed) and repeat to show it works on already valid constraint +ALTER TABLE attmp3 validate constraint attmpconstr; +ALTER TABLE attmp3 validate constraint attmpconstr; + +-- Try a non-verified CHECK constraint +ALTER TABLE attmp3 ADD CONSTRAINT b_greater_than_ten CHECK (b > 10); -- fail +ALTER TABLE attmp3 ADD CONSTRAINT b_greater_than_ten CHECK (b > 10) NOT VALID; -- succeeds +ALTER TABLE attmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- fails +DELETE FROM attmp3 WHERE NOT b > 10; +ALTER TABLE attmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- succeeds +ALTER TABLE attmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- succeeds + +-- Test inherited NOT VALID CHECK constraints +select * from attmp3; +CREATE TABLE attmp6 () INHERITS (attmp3); +CREATE TABLE attmp7 () INHERITS (attmp3); + +INSERT INTO attmp6 VALUES (6, 30), (7, 16); +ALTER TABLE attmp3 ADD CONSTRAINT b_le_20 CHECK (b <= 20) NOT VALID; +ALTER TABLE attmp3 VALIDATE CONSTRAINT b_le_20; -- fails +DELETE FROM attmp6 WHERE b > 20; +ALTER TABLE attmp3 VALIDATE CONSTRAINT b_le_20; -- succeeds + +-- An already validated constraint must not be revalidated +CREATE FUNCTION boo(int) RETURNS int IMMUTABLE STRICT LANGUAGE plpgsql AS $$ BEGIN RAISE NOTICE 'boo: %', $1; RETURN $1; END; $$; +INSERT INTO attmp7 VALUES (8, 18); +ALTER TABLE attmp7 ADD CONSTRAINT identity CHECK (b = boo(b)); +ALTER TABLE attmp3 ADD CONSTRAINT IDENTITY check (b = boo(b)) NOT VALID; +ALTER TABLE attmp3 VALIDATE CONSTRAINT identity; + +-- A NO INHERIT constraint should not be looked for in children during VALIDATE CONSTRAINT +create table parent_noinh_convalid (a int); +create table child_noinh_convalid () inherits (parent_noinh_convalid); +insert into parent_noinh_convalid values (1); +insert into child_noinh_convalid values (1); +alter table parent_noinh_convalid add constraint check_a_is_2 check (a = 2) no inherit not valid; +-- fail, because of the row in parent +alter table parent_noinh_convalid validate constraint check_a_is_2; +delete from only parent_noinh_convalid; +-- ok (parent itself contains no violating rows) +alter table parent_noinh_convalid validate constraint check_a_is_2; +select convalidated from pg_constraint where conrelid = 'parent_noinh_convalid'::regclass and conname = 'check_a_is_2'; +-- cleanup +drop table parent_noinh_convalid, child_noinh_convalid; + +-- Try (and fail) to create constraint from attmp5(a) to attmp4(a) - unique constraint on +-- attmp4 is a,b + +ALTER TABLE attmp5 add constraint attmpconstr foreign key(a) references attmp4(a) match full; + +DROP TABLE attmp7; + +DROP TABLE attmp6; + +DROP TABLE attmp5; + +DROP TABLE attmp4; + +DROP TABLE attmp3; + +DROP TABLE attmp2; + +-- NOT VALID with plan invalidation -- ensure we don't use a constraint for +-- exclusion until validated +set constraint_exclusion TO 'partition'; +create table nv_parent (d date, check (false) no inherit not valid); +-- not valid constraint added at creation time should automatically become valid +\d nv_parent + +create table nv_child_2010 () inherits (nv_parent); +create table nv_child_2011 () inherits (nv_parent); +alter table nv_child_2010 add check (d between '2010-01-01'::date and '2010-12-31'::date) not valid; +alter table nv_child_2011 add check (d between '2011-01-01'::date and '2011-12-31'::date) not valid; +explain (costs off) select * from nv_parent where d between '2011-08-01' and '2011-08-31'; +create table nv_child_2009 (check (d between '2009-01-01'::date and '2009-12-31'::date)) inherits (nv_parent); +explain (costs off) select * from nv_parent where d between '2011-08-01'::date and '2011-08-31'::date; +explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date; +-- after validation, the constraint should be used +alter table nv_child_2011 VALIDATE CONSTRAINT nv_child_2011_d_check; +explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date; + +-- add an inherited NOT VALID constraint +alter table nv_parent add check (d between '2001-01-01'::date and '2099-12-31'::date) not valid; +\d nv_child_2009 +-- we leave nv_parent and children around to help test pg_dump logic + +-- Foreign key adding test with mixed types + +-- Note: these tables are TEMP to avoid name conflicts when this test +-- is run in parallel with foreign_key.sql. + +CREATE TEMP TABLE PKTABLE (ptest1 int PRIMARY KEY); +INSERT INTO PKTABLE VALUES(42); +CREATE TEMP TABLE FKTABLE (ftest1 inet); +-- This next should fail, because int=inet does not exist +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; +-- This should also fail for the same reason, but here we +-- give the column name +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable(ptest1); +DROP TABLE FKTABLE; +-- This should succeed, even though they are different types, +-- because int=int8 exists and is a member of the integer opfamily +CREATE TEMP TABLE FKTABLE (ftest1 int8); +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +DROP TABLE FKTABLE; +-- This should fail, because we'd have to cast numeric to int which is +-- not an implicit coercion (or use numeric=numeric, but that's not part +-- of the integer opfamily) +CREATE TEMP TABLE FKTABLE (ftest1 numeric); +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; +-- On the other hand, this should work because int implicitly promotes to +-- numeric, and we allow promotion on the FK side +CREATE TEMP TABLE PKTABLE (ptest1 numeric PRIMARY KEY); +INSERT INTO PKTABLE VALUES(42); +CREATE TEMP TABLE FKTABLE (ftest1 int); +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; + +CREATE TEMP TABLE PKTABLE (ptest1 int, ptest2 inet, + PRIMARY KEY(ptest1, ptest2)); +-- This should fail, because we just chose really odd types +CREATE TEMP TABLE FKTABLE (ftest1 cidr, ftest2 timestamp); +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1, ftest2) references pktable; +DROP TABLE FKTABLE; +-- Again, so should this... +CREATE TEMP TABLE FKTABLE (ftest1 cidr, ftest2 timestamp); +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1, ftest2) + references pktable(ptest1, ptest2); +DROP TABLE FKTABLE; +-- This fails because we mixed up the column ordering +CREATE TEMP TABLE FKTABLE (ftest1 int, ftest2 inet); +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1, ftest2) + references pktable(ptest2, ptest1); +-- As does this... +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest2, ftest1) + references pktable(ptest1, ptest2); +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; + +-- Test that ALTER CONSTRAINT updates trigger deferrability properly + +CREATE TEMP TABLE PKTABLE (ptest1 int primary key); +CREATE TEMP TABLE FKTABLE (ftest1 int); + +ALTER TABLE FKTABLE ADD CONSTRAINT fknd FOREIGN KEY(ftest1) REFERENCES pktable + ON DELETE CASCADE ON UPDATE NO ACTION NOT DEFERRABLE; +ALTER TABLE FKTABLE ADD CONSTRAINT fkdd FOREIGN KEY(ftest1) REFERENCES pktable + ON DELETE CASCADE ON UPDATE NO ACTION DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE FKTABLE ADD CONSTRAINT fkdi FOREIGN KEY(ftest1) REFERENCES pktable + ON DELETE CASCADE ON UPDATE NO ACTION DEFERRABLE INITIALLY IMMEDIATE; + +ALTER TABLE FKTABLE ADD CONSTRAINT fknd2 FOREIGN KEY(ftest1) REFERENCES pktable + ON DELETE CASCADE ON UPDATE NO ACTION DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE FKTABLE ALTER CONSTRAINT fknd2 NOT DEFERRABLE; +ALTER TABLE FKTABLE ADD CONSTRAINT fkdd2 FOREIGN KEY(ftest1) REFERENCES pktable + ON DELETE CASCADE ON UPDATE NO ACTION NOT DEFERRABLE; +ALTER TABLE FKTABLE ALTER CONSTRAINT fkdd2 DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE FKTABLE ADD CONSTRAINT fkdi2 FOREIGN KEY(ftest1) REFERENCES pktable + ON DELETE CASCADE ON UPDATE NO ACTION NOT DEFERRABLE; +ALTER TABLE FKTABLE ALTER CONSTRAINT fkdi2 DEFERRABLE INITIALLY IMMEDIATE; + +SELECT conname, tgfoid::regproc, tgtype, tgdeferrable, tginitdeferred +FROM pg_trigger JOIN pg_constraint con ON con.oid = tgconstraint +WHERE tgrelid = 'pktable'::regclass +ORDER BY 1,2,3; +SELECT conname, tgfoid::regproc, tgtype, tgdeferrable, tginitdeferred +FROM pg_trigger JOIN pg_constraint con ON con.oid = tgconstraint +WHERE tgrelid = 'fktable'::regclass +ORDER BY 1,2,3; + +-- temp tables should go away by themselves, need not drop them. + +-- test check constraint adding + +create table atacc1 ( test int ); +-- add a check constraint +alter table atacc1 add constraint atacc_test1 check (test>3); +-- should fail +insert into atacc1 (test) values (2); +-- should succeed +insert into atacc1 (test) values (4); +drop table atacc1; + +-- let's do one where the check fails when added +create table atacc1 ( test int ); +-- insert a soon to be failing row +insert into atacc1 (test) values (2); +-- add a check constraint (fails) +alter table atacc1 add constraint atacc_test1 check (test>3); +insert into atacc1 (test) values (4); +drop table atacc1; + +-- let's do one where the check fails because the column doesn't exist +create table atacc1 ( test int ); +-- add a check constraint (fails) +alter table atacc1 add constraint atacc_test1 check (test1>3); +drop table atacc1; + +-- something a little more complicated +create table atacc1 ( test int, test2 int, test3 int); +-- add a check constraint (fails) +alter table atacc1 add constraint atacc_test1 check (test+test23), test2 int); +alter table atacc1 add check (test2>test); +-- should fail for $2 +insert into atacc1 (test2, test) values (3, 4); +drop table atacc1; + +-- inheritance related tests +create table atacc1 (test int); +create table atacc2 (test2 int); +create table atacc3 (test3 int) inherits (atacc1, atacc2); +alter table atacc2 add constraint foo check (test2>0); +-- fail and then succeed on atacc2 +insert into atacc2 (test2) values (-3); +insert into atacc2 (test2) values (3); +-- fail and then succeed on atacc3 +insert into atacc3 (test2) values (-3); +insert into atacc3 (test2) values (3); +drop table atacc3; +drop table atacc2; +drop table atacc1; + +-- same things with one created with INHERIT +create table atacc1 (test int); +create table atacc2 (test2 int); +create table atacc3 (test3 int) inherits (atacc1, atacc2); +alter table atacc3 no inherit atacc2; +-- fail +alter table atacc3 no inherit atacc2; +-- make sure it really isn't a child +insert into atacc3 (test2) values (3); +select test2 from atacc2; +-- fail due to missing constraint +alter table atacc2 add constraint foo check (test2>0); +alter table atacc3 inherit atacc2; +-- fail due to missing column +alter table atacc3 rename test2 to testx; +alter table atacc3 inherit atacc2; +-- fail due to mismatched data type +alter table atacc3 add test2 bool; +alter table atacc3 inherit atacc2; +alter table atacc3 drop test2; +-- succeed +alter table atacc3 add test2 int; +update atacc3 set test2 = 4 where test2 is null; +alter table atacc3 add constraint foo check (test2>0); +alter table atacc3 inherit atacc2; +-- fail due to duplicates and circular inheritance +alter table atacc3 inherit atacc2; +alter table atacc2 inherit atacc3; +alter table atacc2 inherit atacc2; +-- test that we really are a child now (should see 4 not 3 and cascade should go through) +select test2 from atacc2; +drop table atacc2 cascade; +drop table atacc1; + +-- adding only to a parent is allowed as of 9.2 + +create table atacc1 (test int); +create table atacc2 (test2 int) inherits (atacc1); +-- ok: +alter table atacc1 add constraint foo check (test>0) no inherit; +-- check constraint is not there on child +insert into atacc2 (test) values (-3); +-- check constraint is there on parent +insert into atacc1 (test) values (-3); +insert into atacc1 (test) values (3); +-- fail, violating row: +alter table atacc2 add constraint foo check (test>0) no inherit; +drop table atacc2; +drop table atacc1; + +-- test unique constraint adding + +create table atacc1 ( test int ) ; +-- add a unique constraint +alter table atacc1 add constraint atacc_test1 unique (test); +-- insert first value +insert into atacc1 (test) values (2); +-- should fail +insert into atacc1 (test) values (2); +-- should succeed +insert into atacc1 (test) values (4); +-- try to create duplicates via alter table using - should fail +alter table atacc1 alter column test type integer using 0; +drop table atacc1; + +-- let's do one where the unique constraint fails when added +create table atacc1 ( test int ); +-- insert soon to be failing rows +insert into atacc1 (test) values (2); +insert into atacc1 (test) values (2); +-- add a unique constraint (fails) +alter table atacc1 add constraint atacc_test1 unique (test); +insert into atacc1 (test) values (3); +drop table atacc1; + +-- let's do one where the unique constraint fails +-- because the column doesn't exist +create table atacc1 ( test int ); +-- add a unique constraint (fails) +alter table atacc1 add constraint atacc_test1 unique (test1); +drop table atacc1; + +-- something a little more complicated +create table atacc1 ( test int, test2 int); +-- add a unique constraint +alter table atacc1 add constraint atacc_test1 unique (test, test2); +-- insert initial value +insert into atacc1 (test,test2) values (4,4); +-- should fail +insert into atacc1 (test,test2) values (4,4); +-- should all succeed +insert into atacc1 (test,test2) values (4,5); +insert into atacc1 (test,test2) values (5,4); +insert into atacc1 (test,test2) values (5,5); +drop table atacc1; + +-- lets do some naming tests +create table atacc1 (test int, test2 int, unique(test)); +alter table atacc1 add unique (test2); +-- should fail for @@ second one @@ +insert into atacc1 (test2, test) values (3, 3); +insert into atacc1 (test2, test) values (2, 3); +drop table atacc1; + +-- test primary key constraint adding + +create table atacc1 ( id serial, test int) ; +-- add a primary key constraint +alter table atacc1 add constraint atacc_test1 primary key (test); +-- insert first value +insert into atacc1 (test) values (2); +-- should fail +insert into atacc1 (test) values (2); +-- should succeed +insert into atacc1 (test) values (4); +-- inserting NULL should fail +insert into atacc1 (test) values(NULL); +-- try adding a second primary key (should fail) +alter table atacc1 add constraint atacc_oid1 primary key(id); +-- drop first primary key constraint +alter table atacc1 drop constraint atacc_test1 restrict; +-- try adding a primary key on oid (should succeed) +alter table atacc1 add constraint atacc_oid1 primary key(id); +drop table atacc1; + +-- let's do one where the primary key constraint fails when added +create table atacc1 ( test int ); +-- insert soon to be failing rows +insert into atacc1 (test) values (2); +insert into atacc1 (test) values (2); +-- add a primary key (fails) +alter table atacc1 add constraint atacc_test1 primary key (test); +insert into atacc1 (test) values (3); +drop table atacc1; + +-- let's do another one where the primary key constraint fails when added +create table atacc1 ( test int ); +-- insert soon to be failing row +insert into atacc1 (test) values (NULL); +-- add a primary key (fails) +alter table atacc1 add constraint atacc_test1 primary key (test); +insert into atacc1 (test) values (3); +drop table atacc1; + +-- let's do one where the primary key constraint fails +-- because the column doesn't exist +create table atacc1 ( test int ); +-- add a primary key constraint (fails) +alter table atacc1 add constraint atacc_test1 primary key (test1); +drop table atacc1; + +-- adding a new column as primary key to a non-empty table. +-- should fail unless the column has a non-null default value. +create table atacc1 ( test int ); +insert into atacc1 (test) values (0); +-- add a primary key column without a default (fails). +alter table atacc1 add column test2 int primary key; +-- now add a primary key column with a default (succeeds). +alter table atacc1 add column test2 int default 0 primary key; +drop table atacc1; + +-- this combination used to have order-of-execution problems (bug #15580) +create table atacc1 (a int); +insert into atacc1 values(1); +alter table atacc1 + add column b float8 not null default random(), + add primary key(a); +drop table atacc1; + +-- additionally, we've seen issues with foreign key validation not being +-- properly delayed until after a table rewrite. Check that works ok. +create table atacc1 (a int primary key); +alter table atacc1 add constraint atacc1_fkey foreign key (a) references atacc1 (a) not valid; +alter table atacc1 validate constraint atacc1_fkey, alter a type bigint; +drop table atacc1; + +-- we've also seen issues with check constraints being validated at the wrong +-- time when there's a pending table rewrite. +create table atacc1 (a bigint, b int); +insert into atacc1 values(1,1); +alter table atacc1 add constraint atacc1_chk check(b = 1) not valid; +alter table atacc1 validate constraint atacc1_chk, alter a type int; +drop table atacc1; + +-- same as above, but ensure the constraint violation is detected +create table atacc1 (a bigint, b int); +insert into atacc1 values(1,2); +alter table atacc1 add constraint atacc1_chk check(b = 1) not valid; +alter table atacc1 validate constraint atacc1_chk, alter a type int; +drop table atacc1; + +-- something a little more complicated +create table atacc1 ( test int, test2 int); +-- add a primary key constraint +alter table atacc1 add constraint atacc_test1 primary key (test, test2); +-- try adding a second primary key - should fail +alter table atacc1 add constraint atacc_test2 primary key (test); +-- insert initial value +insert into atacc1 (test,test2) values (4,4); +-- should fail +insert into atacc1 (test,test2) values (4,4); +insert into atacc1 (test,test2) values (NULL,3); +insert into atacc1 (test,test2) values (3, NULL); +insert into atacc1 (test,test2) values (NULL,NULL); +-- should all succeed +insert into atacc1 (test,test2) values (4,5); +insert into atacc1 (test,test2) values (5,4); +insert into atacc1 (test,test2) values (5,5); +drop table atacc1; + +-- lets do some naming tests +create table atacc1 (test int, test2 int, primary key(test)); +-- only first should succeed +insert into atacc1 (test2, test) values (3, 3); +insert into atacc1 (test2, test) values (2, 3); +insert into atacc1 (test2, test) values (1, NULL); +drop table atacc1; + +-- alter table / alter column [set/drop] not null tests +-- try altering system catalogs, should fail +alter table pg_class alter column relname drop not null; +alter table pg_class alter relname set not null; + +-- try altering non-existent table, should fail +alter table non_existent alter column bar set not null; +alter table non_existent alter column bar drop not null; + +-- test setting columns to null and not null and vice versa +-- test checking for null values and primary key +create table atacc1 (test int not null); +alter table atacc1 add constraint "atacc1_pkey" primary key (test); +alter table atacc1 alter column test drop not null; +alter table atacc1 drop constraint "atacc1_pkey"; +alter table atacc1 alter column test drop not null; +insert into atacc1 values (null); +alter table atacc1 alter test set not null; +delete from atacc1; +alter table atacc1 alter test set not null; + +-- try altering a non-existent column, should fail +alter table atacc1 alter bar set not null; +alter table atacc1 alter bar drop not null; + +-- try creating a view and altering that, should fail +create view myview as select * from atacc1; +alter table myview alter column test drop not null; +alter table myview alter column test set not null; +drop view myview; + +drop table atacc1; + +-- set not null verified by constraints +create table atacc1 (test_a int, test_b int); +insert into atacc1 values (null, 1); +-- constraint not cover all values, should fail +alter table atacc1 add constraint atacc1_constr_or check(test_a is not null or test_b < 10); +alter table atacc1 alter test_a set not null; +alter table atacc1 drop constraint atacc1_constr_or; +-- not valid constraint, should fail +alter table atacc1 add constraint atacc1_constr_invalid check(test_a is not null) not valid; +alter table atacc1 alter test_a set not null; +alter table atacc1 drop constraint atacc1_constr_invalid; +-- with valid constraint +update atacc1 set test_a = 1; +alter table atacc1 add constraint atacc1_constr_a_valid check(test_a is not null); +alter table atacc1 alter test_a set not null; +delete from atacc1; + +insert into atacc1 values (2, null); +alter table atacc1 alter test_a drop not null; +-- test multiple set not null at same time +-- test_a checked by atacc1_constr_a_valid, test_b should fail by table scan +alter table atacc1 alter test_a set not null, alter test_b set not null; +-- commands order has no importance +alter table atacc1 alter test_b set not null, alter test_a set not null; + +-- valid one by table scan, one by check constraints +update atacc1 set test_b = 1; +alter table atacc1 alter test_b set not null, alter test_a set not null; + +alter table atacc1 alter test_a drop not null, alter test_b drop not null; +-- both column has check constraints +alter table atacc1 add constraint atacc1_constr_b_valid check(test_b is not null); +alter table atacc1 alter test_b set not null, alter test_a set not null; +drop table atacc1; + +-- test inheritance +create table parent (a int); +create table child (b varchar(255)) inherits (parent); + +alter table parent alter a set not null; +insert into parent values (NULL); +insert into child (a, b) values (NULL, 'foo'); +alter table parent alter a drop not null; +insert into parent values (NULL); +insert into child (a, b) values (NULL, 'foo'); +alter table only parent alter a set not null; +alter table child alter a set not null; +delete from parent; +alter table only parent alter a set not null; +insert into parent values (NULL); +alter table child alter a set not null; +insert into child (a, b) values (NULL, 'foo'); +delete from child; +alter table child alter a set not null; +insert into child (a, b) values (NULL, 'foo'); +drop table child; +drop table parent; + +-- test setting and removing default values +create table def_test ( + c1 int4 default 5, + c2 text default 'initial_default' +); +insert into def_test default values; +alter table def_test alter column c1 drop default; +insert into def_test default values; +alter table def_test alter column c2 drop default; +insert into def_test default values; +alter table def_test alter column c1 set default 10; +alter table def_test alter column c2 set default 'new_default'; +insert into def_test default values; +select * from def_test; + +-- set defaults to an incorrect type: this should fail +alter table def_test alter column c1 set default 'wrong_datatype'; +alter table def_test alter column c2 set default 20; + +-- set defaults on a non-existent column: this should fail +alter table def_test alter column c3 set default 30; + +-- set defaults on views: we need to create a view, add a rule +-- to allow insertions into it, and then alter the view to add +-- a default +create view def_view_test as select * from def_test; +create rule def_view_test_ins as + on insert to def_view_test + do instead insert into def_test select new.*; +insert into def_view_test default values; +alter table def_view_test alter column c1 set default 45; +insert into def_view_test default values; +alter table def_view_test alter column c2 set default 'view_default'; +insert into def_view_test default values; +select * from def_view_test; + +drop rule def_view_test_ins on def_view_test; +drop view def_view_test; +drop table def_test; + +-- alter table / drop column tests +-- try altering system catalogs, should fail +alter table pg_class drop column relname; + +-- try altering non-existent table, should fail +alter table nosuchtable drop column bar; + +-- test dropping columns +create table atacc1 (a int4 not null, b int4, c int4 not null, d int4); +insert into atacc1 values (1, 2, 3, 4); +alter table atacc1 drop a; +alter table atacc1 drop a; + +-- SELECTs +select * from atacc1; +select * from atacc1 order by a; +select * from atacc1 order by "........pg.dropped.1........"; +select * from atacc1 group by a; +select * from atacc1 group by "........pg.dropped.1........"; +select atacc1.* from atacc1; +select a from atacc1; +select atacc1.a from atacc1; +select b,c,d from atacc1; +select a,b,c,d from atacc1; +select * from atacc1 where a = 1; +select "........pg.dropped.1........" from atacc1; +select atacc1."........pg.dropped.1........" from atacc1; +select "........pg.dropped.1........",b,c,d from atacc1; +select * from atacc1 where "........pg.dropped.1........" = 1; + +-- UPDATEs +update atacc1 set a = 3; +update atacc1 set b = 2 where a = 3; +update atacc1 set "........pg.dropped.1........" = 3; +update atacc1 set b = 2 where "........pg.dropped.1........" = 3; + +-- INSERTs +insert into atacc1 values (10, 11, 12, 13); +insert into atacc1 values (default, 11, 12, 13); +insert into atacc1 values (11, 12, 13); +insert into atacc1 (a) values (10); +insert into atacc1 (a) values (default); +insert into atacc1 (a,b,c,d) values (10,11,12,13); +insert into atacc1 (a,b,c,d) values (default,11,12,13); +insert into atacc1 (b,c,d) values (11,12,13); +insert into atacc1 ("........pg.dropped.1........") values (10); +insert into atacc1 ("........pg.dropped.1........") values (default); +insert into atacc1 ("........pg.dropped.1........",b,c,d) values (10,11,12,13); +insert into atacc1 ("........pg.dropped.1........",b,c,d) values (default,11,12,13); + +-- DELETEs +delete from atacc1 where a = 3; +delete from atacc1 where "........pg.dropped.1........" = 3; +delete from atacc1; + +-- try dropping a non-existent column, should fail +alter table atacc1 drop bar; + +-- try removing an oid column, should succeed (as it's nonexistent) +alter table atacc1 SET WITHOUT OIDS; + +-- try adding an oid column, should fail (not supported) +alter table atacc1 SET WITH OIDS; + +-- try dropping the xmin column, should fail +alter table atacc1 drop xmin; + +-- try creating a view and altering that, should fail +create view myview as select * from atacc1; +select * from myview; +alter table myview drop d; +drop view myview; + +-- test some commands to make sure they fail on the dropped column +analyze atacc1(a); +analyze atacc1("........pg.dropped.1........"); +vacuum analyze atacc1(a); +vacuum analyze atacc1("........pg.dropped.1........"); +comment on column atacc1.a is 'testing'; +comment on column atacc1."........pg.dropped.1........" is 'testing'; +alter table atacc1 alter a set storage plain; +alter table atacc1 alter "........pg.dropped.1........" set storage plain; +alter table atacc1 alter a set statistics 0; +alter table atacc1 alter "........pg.dropped.1........" set statistics 0; +alter table atacc1 alter a set default 3; +alter table atacc1 alter "........pg.dropped.1........" set default 3; +alter table atacc1 alter a drop default; +alter table atacc1 alter "........pg.dropped.1........" drop default; +alter table atacc1 alter a set not null; +alter table atacc1 alter "........pg.dropped.1........" set not null; +alter table atacc1 alter a drop not null; +alter table atacc1 alter "........pg.dropped.1........" drop not null; +alter table atacc1 rename a to x; +alter table atacc1 rename "........pg.dropped.1........" to x; +alter table atacc1 add primary key(a); +alter table atacc1 add primary key("........pg.dropped.1........"); +alter table atacc1 add unique(a); +alter table atacc1 add unique("........pg.dropped.1........"); +alter table atacc1 add check (a > 3); +alter table atacc1 add check ("........pg.dropped.1........" > 3); +create table atacc2 (id int4 unique); +alter table atacc1 add foreign key (a) references atacc2(id); +alter table atacc1 add foreign key ("........pg.dropped.1........") references atacc2(id); +alter table atacc2 add foreign key (id) references atacc1(a); +alter table atacc2 add foreign key (id) references atacc1("........pg.dropped.1........"); +drop table atacc2; +create index "testing_idx" on atacc1(a); +create index "testing_idx" on atacc1("........pg.dropped.1........"); + +-- test create as and select into +insert into atacc1 values (21, 22, 23); +create table attest1 as select * from atacc1; +select * from attest1; +drop table attest1; +select * into attest2 from atacc1; +select * from attest2; +drop table attest2; + +-- try dropping all columns +alter table atacc1 drop c; +alter table atacc1 drop d; +alter table atacc1 drop b; +select * from atacc1; + +drop table atacc1; + +-- test constraint error reporting in presence of dropped columns +create table atacc1 (id serial primary key, value int check (value < 10)); +insert into atacc1(value) values (100); +alter table atacc1 drop column value; +alter table atacc1 add column value int check (value < 10); +insert into atacc1(value) values (100); +insert into atacc1(id, value) values (null, 0); +drop table atacc1; + +-- test inheritance +create table parent (a int, b int, c int); +insert into parent values (1, 2, 3); +alter table parent drop a; +create table child (d varchar(255)) inherits (parent); +insert into child values (12, 13, 'testing'); + +select * from parent; +select * from child; +alter table parent drop c; +select * from parent; +select * from child; + +drop table child; +drop table parent; + +-- check error cases for inheritance column merging +create table parent (a float8, b numeric(10,4), c text collate "C"); + +create table child (a float4) inherits (parent); -- fail +create table child (b decimal(10,7)) inherits (parent); -- fail +create table child (c text collate "POSIX") inherits (parent); -- fail +create table child (a double precision, b decimal(10,4)) inherits (parent); + +drop table child; +drop table parent; + +-- test copy in/out +create table attest (a int4, b int4, c int4); +insert into attest values (1,2,3); +alter table attest drop a; +copy attest to stdout; +copy attest(a) to stdout; +copy attest("........pg.dropped.1........") to stdout; +copy attest from stdin; +10 11 12 +\. +select * from attest; +copy attest from stdin; +21 22 +\. +select * from attest; +copy attest(a) from stdin; +copy attest("........pg.dropped.1........") from stdin; +copy attest(b,c) from stdin; +31 32 +\. +select * from attest; +drop table attest; + +-- test inheritance + +create table dropColumn (a int, b int, e int); +create table dropColumnChild (c int) inherits (dropColumn); +create table dropColumnAnother (d int) inherits (dropColumnChild); + +-- these two should fail +alter table dropColumnchild drop column a; +alter table only dropColumnChild drop column b; + + + +-- these three should work +alter table only dropColumn drop column e; +alter table dropColumnChild drop column c; +alter table dropColumn drop column a; + +create table renameColumn (a int); +create table renameColumnChild (b int) inherits (renameColumn); +create table renameColumnAnother (c int) inherits (renameColumnChild); + +-- these three should fail +alter table renameColumnChild rename column a to d; +alter table only renameColumnChild rename column a to d; +alter table only renameColumn rename column a to d; + +-- these should work +alter table renameColumn rename column a to d; +alter table renameColumnChild rename column b to a; + +-- these should work +alter table if exists doesnt_exist_tab rename column a to d; +alter table if exists doesnt_exist_tab rename column b to a; + +-- this should work +alter table renameColumn add column w int; + +-- this should fail +alter table only renameColumn add column x int; + + +-- Test corner cases in dropping of inherited columns + +create table p1 (f1 int, f2 int); +create table c1 (f1 int not null) inherits(p1); + +-- should be rejected since c1.f1 is inherited +alter table c1 drop column f1; +/* YB: #26094 issue with inherits +-- should work +alter table p1 drop column f1; +-- c1.f1 is still there, but no longer inherited +select f1 from c1; +alter table c1 drop column f1; +select f1 from c1; + +drop table p1 cascade; + +create table p1 (f1 int, f2 int); +create table c1 () inherits(p1); + +-- should be rejected since c1.f1 is inherited +alter table c1 drop column f1; +alter table p1 drop column f1; +-- c1.f1 is dropped now, since there is no local definition for it +select f1 from c1; +*/ -- YB +drop table p1 cascade; + +create table p1 (f1 int, f2 int); +create table c1 () inherits(p1); + +-- should be rejected since c1.f1 is inherited +alter table c1 drop column f1; +alter table only p1 drop column f1; +-- c1.f1 is NOT dropped, but must now be considered non-inherited +alter table c1 drop column f1; + +drop table p1 cascade; + +create table p1 (f1 int, f2 int); +create table c1 (f1 int not null) inherits(p1); + +-- should be rejected since c1.f1 is inherited +alter table c1 drop column f1; +alter table only p1 drop column f1; +-- c1.f1 is still there, but no longer inherited +alter table c1 drop column f1; + +drop table p1 cascade; + +create table p1(id int, name text); +create table p2(id2 int, name text, height int); +create table c1(age int) inherits(p1,p2); +create table gc1() inherits (c1); + +select relname, attname, attinhcount, attislocal +from pg_class join pg_attribute on (pg_class.oid = pg_attribute.attrelid) +where relname in ('p1','p2','c1','gc1') and attnum > 0 and not attisdropped +order by relname, attnum; + +-- should work +alter table only p1 drop column name; +-- should work. Now c1.name is local and inhcount is 0. +/* YB: #26094 issue with inherit +alter table p2 drop column name; +*/ -- YB +-- should be rejected since its inherited +alter table gc1 drop column name; +/* YB: #26094 issue with inherits +-- should work, and drop gc1.name along +alter table c1 drop column name; +-- should fail: column does not exist +alter table gc1 drop column name; +*/ -- YB +-- should work and drop the attribute in all tables +alter table p2 drop column height; + +-- IF EXISTS test +create table dropColumnExists (); +alter table dropColumnExists drop column non_existing; --fail +alter table dropColumnExists drop column if exists non_existing; --succeed + +select relname, attname, attinhcount, attislocal +from pg_class join pg_attribute on (pg_class.oid = pg_attribute.attrelid) +where relname in ('p1','p2','c1','gc1') and attnum > 0 and not attisdropped +order by relname, attnum; -- YB output differs due to commented lines above + +drop table p1, p2 cascade; + +-- test attinhcount tracking with merged columns + +create table depth0(); +create table depth1(c text) inherits (depth0); +create table depth2() inherits (depth1); +/* YB: #26094 issue with inherits +alter table depth0 add c text; + +select attrelid::regclass, attname, attinhcount, attislocal +from pg_attribute +where attnum > 0 and attrelid::regclass in ('depth0', 'depth1', 'depth2') +order by attrelid::regclass::text, attnum; + +-- test renumbering of child-table columns in inherited operations + +create table p1 (f1 int); +create table c1 (f2 text, f3 int) inherits (p1); + +alter table p1 add column a1 int check (a1 > 0); +alter table p1 add column f2 text; + +insert into p1 values (1,2,'abc'); +insert into c1 values(11,'xyz',33,0); -- should fail +insert into c1 values(11,'xyz',33,22); + +select * from p1; +update p1 set a1 = a1 + 1, f2 = upper(f2); +select * from p1; +*/ -- YB +drop table p1 cascade; + +-- test that operations with a dropped column do not try to reference +-- its datatype + +create domain mytype as text; +create temp table foo (f1 text, f2 mytype, f3 text); + +insert into foo values('bb','cc','dd'); +select * from foo; + +drop domain mytype cascade; + +select * from foo; +insert into foo values('qq','rr'); +select * from foo; +update foo set f3 = 'zz'; +select * from foo; +select f3,max(f1) from foo group by f3; + +-- Simple tests for alter table column type +alter table foo alter f1 TYPE integer; -- fails +alter table foo alter f1 TYPE varchar(10); + +create table anothertab (atcol1 serial8, atcol2 boolean, + constraint anothertab_chk check (atcol1 <= 3)); + +insert into anothertab (atcol1, atcol2) values (default, true); +insert into anothertab (atcol1, atcol2) values (default, false); +select * from anothertab; + +alter table anothertab alter column atcol1 type boolean; -- fails +alter table anothertab alter column atcol1 type boolean using atcol1::int; -- fails +alter table anothertab alter column atcol1 type integer; + +select * from anothertab order by atcol1; /* YB order changes due to rewrite */ + +insert into anothertab (atcol1, atcol2) values (45, null); -- fails +insert into anothertab (atcol1, atcol2) values (default, null); + +select * from anothertab order by atcol1; /* YB order changes due to rewrite */ + +alter table anothertab alter column atcol2 type text + using case when atcol2 is true then 'IT WAS TRUE' + when atcol2 is false then 'IT WAS FALSE' + else 'IT WAS NULL!' end; + +select * from anothertab order by atcol1; /* YB order changes due to rewrite */ +alter table anothertab alter column atcol1 type boolean + using case when atcol1 % 2 = 0 then true else false end; -- fails +alter table anothertab alter column atcol1 drop default; +alter table anothertab alter column atcol1 type boolean + using case when atcol1 % 2 = 0 then true else false end; -- fails +alter table anothertab drop constraint anothertab_chk; +alter table anothertab drop constraint anothertab_chk; -- fails +alter table anothertab drop constraint IF EXISTS anothertab_chk; -- succeeds + +alter table anothertab alter column atcol1 type boolean + using case when atcol1 % 2 = 0 then true else false end; + +select * from anothertab order by atcol2; /* YB order changes due to rewrite */ + +drop table anothertab; + +-- Test index handling in alter table column type (cf. bugs #15835, #15865) +/* YB: not supported exclude using +create table anothertab(f1 int primary key, f2 int unique, + f3 int, f4 int, f5 int); +alter table anothertab + add exclude using btree (f3 with =); +alter table anothertab + add exclude using btree (f4 with =) where (f4 is not null); +alter table anothertab + add exclude using btree (f4 with =) where (f5 > 0); +alter table anothertab + add unique(f1,f4); +create index on anothertab(f2,f3); +create unique index on anothertab(f4); + +\d anothertab +alter table anothertab alter column f1 type bigint; +alter table anothertab + alter column f2 type bigint, + alter column f3 type bigint, + alter column f4 type bigint; +alter table anothertab alter column f5 type bigint; +\d anothertab + +drop table anothertab; +*/ -- YB + +-- test that USING expressions are parsed before column alter type / drop steps +create table another (f1 int, f2 text, f3 text); + +insert into another values(1, 'one', 'uno'); +insert into another values(2, 'two', 'due'); +insert into another values(3, 'three', 'tre'); + +select * from another; + +/* YB: #26195 +alter table another + alter f1 type text using f2 || ' and ' || f3 || ' more', + alter f2 type bigint using f1 * 10, + drop column f3; + +select * from another; +*/ -- YB +drop table another; + +-- Create an index that skips WAL, then perform a SET DATA TYPE that skips +-- rewriting the index. +begin; +create table skip_wal_skip_rewrite_index (c varchar(10) primary key); +alter table skip_wal_skip_rewrite_index alter c type varchar(20); +commit; + +-- We disallow changing table's row type if it's used for storage +create table at_tab1 (a int, b text); +create table at_tab2 (x int, y at_tab1); +alter table at_tab1 alter column b type varchar; -- fails +drop table at_tab2; +-- Use of row type in an expression is defended differently +create table at_tab2 (x int, y text, check((x,y)::at_tab1 = (1,'42')::at_tab1)); +alter table at_tab1 alter column b type varchar; -- allowed, but ... +insert into at_tab2 values(1,'42'); -- ... this will fail +drop table at_tab1, at_tab2; +-- Check it for a partitioned table, too +create table at_tab1 (a int, b text) partition by list(a); +create table at_tab2 (x int, y at_tab1); +alter table at_tab1 alter column b type varchar; -- fails +drop table at_tab1, at_tab2; + +-- Alter column type that's part of a partitioned index +create table at_partitioned (a int, b text) partition by range (a); +create table at_part_1 partition of at_partitioned for values from (0) to (1000); +insert into at_partitioned values (512, '0.123'); +create table at_part_2 (b text, a int); +insert into at_part_2 values ('1.234', 1024); +create index on at_partitioned (b); +create index on at_partitioned (a); +\d at_part_1 +\d at_part_2 +alter table at_partitioned attach partition at_part_2 for values from (1000) to (2000); +\d at_part_2 +alter table at_partitioned alter column b type numeric using b::numeric; +\d at_part_1 +\d at_part_2 +drop table at_partitioned; + +-- Alter column type when no table rewrite is required +-- Also check that comments are preserved +create table at_partitioned(id int, name varchar(64), unique (id, name)) + partition by hash(id); +comment on constraint at_partitioned_id_name_key on at_partitioned is 'parent constraint'; +comment on index at_partitioned_id_name_key is 'parent index'; +create table at_partitioned_0 partition of at_partitioned + for values with (modulus 2, remainder 0); +comment on constraint at_partitioned_0_id_name_key on at_partitioned_0 is 'child 0 constraint'; +comment on index at_partitioned_0_id_name_key is 'child 0 index'; +create table at_partitioned_1 partition of at_partitioned + for values with (modulus 2, remainder 1); +comment on constraint at_partitioned_1_id_name_key on at_partitioned_1 is 'child 1 constraint'; +comment on index at_partitioned_1_id_name_key is 'child 1 index'; +insert into at_partitioned values(1, 'foo'); +insert into at_partitioned values(3, 'bar'); + +create temp table old_oids as + select relname, oid as oldoid, relfilenode as oldfilenode + from pg_class where relname like 'at_partitioned%'; + +select relname, + c.oid = oldoid as orig_oid, + case relfilenode + when 0 then 'none' + when c.oid then 'own' + when oldfilenode then 'orig' + else 'OTHER' + end as storage, + obj_description(c.oid, 'pg_class') as desc + from pg_class c left join old_oids using (relname) + where relname like 'at_partitioned%' + order by relname; + +select conname, obj_description(oid, 'pg_constraint') as desc + from pg_constraint where conname like 'at_partitioned%' + order by conname; + +alter table at_partitioned alter column name type varchar(127); + +-- Note: these tests currently show the wrong behavior for comments :-( +-- YB: In YB, the child indexes' comments are actually preserved, since the +-- indexes were reused. We diverge from PG here. + +select relname, + c.oid = oldoid as orig_oid, + case relfilenode + when 0 then 'none' + when c.oid then 'own' + when oldfilenode then 'orig' + else 'OTHER' + end as storage, + obj_description(c.oid, 'pg_class') as desc + from pg_class c left join old_oids using (relname) + where relname like 'at_partitioned%' + order by relname; + +select conname, obj_description(oid, 'pg_constraint') as desc + from pg_constraint where conname like 'at_partitioned%' + order by conname; + +-- Don't remove this DROP, it exposes bug #15672 +drop table at_partitioned; + +-- disallow recursive containment of row types +create temp table recur1 (f1 int); +alter table recur1 add column f2 recur1; -- fails +alter table recur1 add column f2 recur1[]; -- fails +create domain array_of_recur1 as recur1[]; +alter table recur1 add column f2 array_of_recur1; -- fails +create temp table recur2 (f1 int, f2 recur1); +alter table recur1 add column f2 recur2; -- fails +alter table recur1 add column f2 int; +alter table recur1 alter column f2 type recur2; -- fails + +-- SET STORAGE may need to add a TOAST table +create temp table test_storage (a text); -- YB use temp tables instead of regular tables +select reltoastrelid <> 0 as has_toast_table + from pg_class where oid = 'test_storage'::regclass; +alter table test_storage alter a set storage plain; +-- rewrite table to remove its TOAST table; need a non-constant column default +alter table test_storage add b int default random()::int; +select reltoastrelid <> 0 as has_toast_table + from pg_class where oid = 'test_storage'::regclass; +alter table test_storage alter a set storage extended; -- re-add TOAST table +select reltoastrelid <> 0 as has_toast_table + from pg_class where oid = 'test_storage'::regclass; + +-- test that SET STORAGE propagates to index correctly +create index test_storage_idx on test_storage (b, a); +alter table test_storage alter column a set storage external; +select current_setting('data_directory') || 'describe.out' as desc_output_file -- YB remove temp table schema name UUID +\gset +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ test_storage +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); -- YB remove temp table schema name UUID +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ test_storage_idx +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); -- YB remove temp table schema name UUID + +-- ALTER COLUMN TYPE with a check constraint and a child table (bug #13779) +CREATE TABLE test_inh_check (a float check (a > 10.2), b float); +CREATE TABLE test_inh_check_child() INHERITS(test_inh_check); +\d test_inh_check +\d test_inh_check_child +select relname, conname, coninhcount, conislocal, connoinherit + from pg_constraint c, pg_class r + where relname like 'test_inh_check%' and c.conrelid = r.oid + order by 1, 2; +ALTER TABLE test_inh_check ALTER COLUMN a TYPE numeric; +\d test_inh_check +\d test_inh_check_child +select relname, conname, coninhcount, conislocal, connoinherit + from pg_constraint c, pg_class r + where relname like 'test_inh_check%' and c.conrelid = r.oid + order by 1, 2; +-- also try noinherit, local, and local+inherited cases +ALTER TABLE test_inh_check ADD CONSTRAINT bnoinherit CHECK (b > 100) NO INHERIT; +ALTER TABLE test_inh_check_child ADD CONSTRAINT blocal CHECK (b < 1000); +ALTER TABLE test_inh_check_child ADD CONSTRAINT bmerged CHECK (b > 1); +ALTER TABLE test_inh_check ADD CONSTRAINT bmerged CHECK (b > 1); +\d test_inh_check +\d test_inh_check_child +select relname, conname, coninhcount, conislocal, connoinherit + from pg_constraint c, pg_class r + where relname like 'test_inh_check%' and c.conrelid = r.oid + order by 1, 2; +ALTER TABLE test_inh_check ALTER COLUMN b TYPE numeric; +\d test_inh_check +\d test_inh_check_child +select relname, conname, coninhcount, conislocal, connoinherit + from pg_constraint c, pg_class r + where relname like 'test_inh_check%' and c.conrelid = r.oid + order by 1, 2; + +-- ALTER COLUMN TYPE with different schema in children +-- Bug at https://postgr.es/m/20170102225618.GA10071@telsasoft.com +CREATE TABLE test_type_diff (f1 int); +CREATE TABLE test_type_diff_c (extra smallint) INHERITS (test_type_diff); +ALTER TABLE test_type_diff ADD COLUMN f2 int; +INSERT INTO test_type_diff_c VALUES (1, 2, 3); +ALTER TABLE test_type_diff ALTER COLUMN f2 TYPE bigint USING f2::bigint; + +CREATE TABLE test_type_diff2 (int_two int2, int_four int4, int_eight int8); +CREATE TABLE test_type_diff2_c1 (int_four int4, int_eight int8, int_two int2); +CREATE TABLE test_type_diff2_c2 (int_eight int8, int_two int2, int_four int4); +CREATE TABLE test_type_diff2_c3 (int_two int2, int_four int4, int_eight int8); +ALTER TABLE test_type_diff2_c1 INHERIT test_type_diff2; +ALTER TABLE test_type_diff2_c2 INHERIT test_type_diff2; +ALTER TABLE test_type_diff2_c3 INHERIT test_type_diff2; +INSERT INTO test_type_diff2_c1 VALUES (1, 2, 3); +INSERT INTO test_type_diff2_c2 VALUES (4, 5, 6); +INSERT INTO test_type_diff2_c3 VALUES (7, 8, 9); +ALTER TABLE test_type_diff2 ALTER COLUMN int_four TYPE int8 USING int_four::int8; +-- whole-row references are disallowed +ALTER TABLE test_type_diff2 ALTER COLUMN int_four TYPE int4 USING (pg_column_size(test_type_diff2)); + +-- check for rollback of ANALYZE corrupting table property flags (bug #11638) +/* YB: DDL rollback is not supported +CREATE TABLE check_fk_presence_1 (id int PRIMARY KEY, t text); +CREATE TABLE check_fk_presence_2 (id int REFERENCES check_fk_presence_1, t text); +BEGIN; +ALTER TABLE check_fk_presence_2 DROP CONSTRAINT check_fk_presence_2_id_fkey; +ANALYZE check_fk_presence_2; +ROLLBACK; +\d check_fk_presence_2 +DROP TABLE check_fk_presence_1, check_fk_presence_2; +*/ -- YB + +-- check column addition within a view (bug #14876) +create table at_base_table(id int, stuff text); +insert into at_base_table values (23, 'skidoo'); +create view at_view_1 as select * from at_base_table bt; +create view at_view_2 as select *, to_json(v1) as j from at_view_1 v1; +\d+ at_view_1 +\d+ at_view_2 +explain (verbose, costs off) select * from at_view_2; +select * from at_view_2; + +create or replace view at_view_1 as select *, 2+2 as more from at_base_table bt; +\d+ at_view_1 +\d+ at_view_2 +explain (verbose, costs off) select * from at_view_2; +select * from at_view_2; + +drop view at_view_2; +drop view at_view_1; +drop table at_base_table; + +-- related case (bug #17811) +begin; +create temp table t1 as select * from int8_tbl; +create temp view v1 as select 1::int8 as q1; +create temp view v2 as select * from v1; +create or replace temp view v1 with (security_barrier = true) + as select * from t1; + +create temp table log (q1 int8, q2 int8); +create rule v1_upd_rule as on update to v1 + do also insert into log values (new.*); + +update v2 set q1 = q1 + 1 where q1 = 123; + +select * from t1; +select * from log; +rollback; + +-- check adding a column not itself requiring a rewrite, together with +-- a column requiring a default (bug #16038) + +-- ensure that rewrites aren't silently optimized away, removing the +-- value of the test +CREATE FUNCTION check_ddl_rewrite(p_tablename regclass, p_ddl text) +RETURNS boolean +LANGUAGE plpgsql AS $$ +DECLARE + v_relfilenode oid; +BEGIN + v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename; + + EXECUTE p_ddl; + + RETURN v_relfilenode <> (SELECT relfilenode FROM pg_class WHERE oid = p_tablename); +END; +$$; + +CREATE TABLE rewrite_test(col text); +INSERT INTO rewrite_test VALUES ('something'); +INSERT INTO rewrite_test VALUES (NULL); + +-- empty[12] don't need rewrite, but notempty[12]_rewrite will force one +SELECT check_ddl_rewrite('rewrite_test', $$ + ALTER TABLE rewrite_test + ADD COLUMN empty1 text, + ADD COLUMN notempty1_rewrite serial; +$$); +SELECT check_ddl_rewrite('rewrite_test', $$ + ALTER TABLE rewrite_test + ADD COLUMN notempty2_rewrite serial, + ADD COLUMN empty2 text; +$$); +-- also check that fast defaults cause no problem, first without rewrite +SELECT check_ddl_rewrite('rewrite_test', $$ + ALTER TABLE rewrite_test + ADD COLUMN empty3 text, + ADD COLUMN notempty3_norewrite int default 42; +$$); +SELECT check_ddl_rewrite('rewrite_test', $$ + ALTER TABLE rewrite_test + ADD COLUMN notempty4_norewrite int default 42, + ADD COLUMN empty4 text; +$$); +-- then with rewrite +SELECT check_ddl_rewrite('rewrite_test', $$ + ALTER TABLE rewrite_test + ADD COLUMN empty5 text, + ADD COLUMN notempty5_norewrite int default 42, + ADD COLUMN notempty5_rewrite serial; +$$); +SELECT check_ddl_rewrite('rewrite_test', $$ + ALTER TABLE rewrite_test + ADD COLUMN notempty6_rewrite serial, + ADD COLUMN empty6 text, + ADD COLUMN notempty6_norewrite int default 42; +$$); + +-- cleanup +DROP FUNCTION check_ddl_rewrite(regclass, text); +DROP TABLE rewrite_test; + +-- +-- lock levels +-- +drop type lockmodes; +create type lockmodes as enum ( + 'SIReadLock' +,'AccessShareLock' +,'RowShareLock' +,'RowExclusiveLock' +,'ShareUpdateExclusiveLock' +,'ShareLock' +,'ShareRowExclusiveLock' +,'ExclusiveLock' +,'AccessExclusiveLock' +); + +drop view my_locks; +create or replace view my_locks as +select case when c.relname like 'pg_toast%' then 'pg_toast' else c.relname end, max(mode::lockmodes) as max_lockmode +from pg_locks l join pg_class c on l.relation = c.oid +where virtualtransaction = ( + select virtualtransaction + from pg_locks) +and locktype = 'relation' +and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog') +and c.relname != 'my_locks' +group by c.relname; + +create table alterlock (f1 int primary key, f2 text); +insert into alterlock values (1, 'foo'); +create table alterlock2 (f3 int primary key, f1 int); +insert into alterlock2 values (1, 1); + +begin; alter table alterlock alter column f2 set statistics 150; +select * from my_locks order by 1; +rollback; + +begin; alter table alterlock cluster on alterlock_pkey; +select * from my_locks order by 1; +commit; + +begin; alter table alterlock set without cluster; +select * from my_locks order by 1; +commit; + +begin; alter table alterlock set (fillfactor = 100); +select * from my_locks order by 1; +commit; + +begin; alter table alterlock reset (fillfactor); +select * from my_locks order by 1; +commit; + +begin; alter table alterlock set (toast.autovacuum_enabled = off); +select * from my_locks order by 1; +commit; + +begin; alter table alterlock set (autovacuum_enabled = off); +select * from my_locks order by 1; +commit; + +begin; alter table alterlock alter column f2 set (n_distinct = 1); +select * from my_locks order by 1; +rollback; + +-- test that mixing options with different lock levels works as expected +begin; alter table alterlock set (autovacuum_enabled = off, fillfactor = 80); +select * from my_locks order by 1; +commit; + +begin; alter table alterlock alter column f2 set storage extended; +select * from my_locks order by 1; +rollback; + +-- TODO(jason): uncomment when doing issue #9106 +-- begin; alter table alterlock alter column f2 set default 'x'; +-- select * from my_locks order by 1; +-- rollback; + +-- +-- alter object set schema +-- + +create schema alter1; +create schema alter2; + +create table alter1.t1(f1 serial primary key, f2 int check (f2 > 0)); + +create type alter1.ctype as (f1 int, f2 text); + +create text search parser alter1.prs(start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype); +create text search configuration alter1.cfg(parser = alter1.prs); + +alter table alter1.t1 set schema alter1; -- no-op, same schema +alter table alter1.t1 set schema alter2; +alter text search parser alter1.prs set schema alter2; +alter text search configuration alter1.cfg set schema alter2; +alter type alter1.ctype set schema alter1; -- no-op, same schema +alter type alter1.ctype set schema alter2; + +-- clean up +drop schema alter2 cascade; + +-- +-- typed tables: OF / NOT OF +-- + +CREATE TYPE tt_t0 AS (z inet, x int, y numeric(8,2)); +ALTER TYPE tt_t0 DROP ATTRIBUTE z; +-- YB: since we can't ALTER TYPE yet, work around by recreating the type. +DROP TYPE tt_t0; +CREATE TYPE tt_t0 AS (x int, y numeric(8,2)); +CREATE TABLE tt0 (x int NOT NULL, y numeric(8,2)); -- OK +CREATE TABLE tt1 (x int, y bigint); -- wrong base type +CREATE TABLE tt2 (x int, y numeric(9,2)); -- wrong typmod +CREATE TABLE tt3 (y numeric(8,2), x int); -- wrong column order +CREATE TABLE tt4 (x int); -- too few columns +CREATE TABLE tt5 (x int, y numeric(8,2), z int); -- too few columns +CREATE TABLE tt6 () INHERITS (tt0); -- can't have a parent +CREATE TABLE tt7 (x int, q text, y numeric(8,2)); +ALTER TABLE tt7 DROP q; -- OK + +ALTER TABLE tt0 OF tt_t0; +ALTER TABLE tt1 OF tt_t0; +ALTER TABLE tt2 OF tt_t0; +ALTER TABLE tt3 OF tt_t0; +ALTER TABLE tt4 OF tt_t0; +ALTER TABLE tt5 OF tt_t0; +ALTER TABLE tt6 OF tt_t0; +ALTER TABLE tt7 OF tt_t0; + +CREATE TYPE tt_t1 AS (x int, y numeric(8,2)); +ALTER TABLE tt7 OF tt_t1; -- reassign an already-typed table +ALTER TABLE tt7 NOT OF; +\d tt7 + +/* YB: fails in debug build due to #26227 +-- make sure we can drop a constraint on the parent but it remains on the child +CREATE TABLE test_drop_constr_parent (c text CHECK (c IS NOT NULL)); +CREATE TABLE test_drop_constr_child () INHERITS (test_drop_constr_parent); +ALTER TABLE ONLY test_drop_constr_parent DROP CONSTRAINT "test_drop_constr_parent_c_check"; +-- should fail +INSERT INTO test_drop_constr_child (c) VALUES (NULL); +DROP TABLE test_drop_constr_parent CASCADE; +*/ -- YB + +-- +-- IF EXISTS test +-- +ALTER TABLE IF EXISTS tt8 ADD COLUMN f int; +ALTER TABLE IF EXISTS tt8 ADD CONSTRAINT xxx PRIMARY KEY(f); +ALTER TABLE IF EXISTS tt8 ADD CHECK (f BETWEEN 0 AND 10); +ALTER TABLE IF EXISTS tt8 ALTER COLUMN f SET DEFAULT 0; +ALTER TABLE IF EXISTS tt8 RENAME COLUMN f TO f1; +ALTER TABLE IF EXISTS tt8 SET SCHEMA alter2; + +CREATE TABLE tt8(a int); +CREATE SCHEMA alter2; + +ALTER TABLE IF EXISTS tt8 ADD COLUMN f int; +ALTER TABLE IF EXISTS tt8 ADD CONSTRAINT xxx PRIMARY KEY(f); +ALTER TABLE IF EXISTS tt8 ADD CHECK (f BETWEEN 0 AND 10); +ALTER TABLE IF EXISTS tt8 ALTER COLUMN f SET DEFAULT 0; +ALTER TABLE IF EXISTS tt8 RENAME COLUMN f TO f1; +ALTER TABLE IF EXISTS tt8 SET SCHEMA alter2; + +\d alter2.tt8 + +DROP TABLE alter2.tt8; +DROP SCHEMA alter2; + +-- +-- Check conflicts between index and CHECK constraint names +-- +CREATE TABLE tt9(c integer); +ALTER TABLE tt9 ADD CHECK(c > 1); +ALTER TABLE tt9 ADD CHECK(c > 2); -- picks nonconflicting name +ALTER TABLE tt9 ADD CONSTRAINT foo CHECK(c > 3); +ALTER TABLE tt9 ADD CONSTRAINT foo CHECK(c > 4); -- fail, dup name +ALTER TABLE tt9 ADD UNIQUE(c); +ALTER TABLE tt9 ADD UNIQUE(c); -- picks nonconflicting name +ALTER TABLE tt9 ADD CONSTRAINT tt9_c_key UNIQUE(c); -- fail, dup name +ALTER TABLE tt9 ADD CONSTRAINT foo UNIQUE(c); -- fail, dup name +ALTER TABLE tt9 ADD CONSTRAINT tt9_c_key CHECK(c > 5); -- fail, dup name +ALTER TABLE tt9 ADD CONSTRAINT tt9_c_key2 CHECK(c > 6); +ALTER TABLE tt9 ADD UNIQUE(c); -- picks nonconflicting name +\d tt9 +DROP TABLE tt9; + + +-- Check that comments on constraints and indexes are not lost at ALTER TABLE. +CREATE TABLE comment_test ( + id int, + positive_col int CHECK (positive_col > 0), + indexed_col int, + CONSTRAINT comment_test_pk PRIMARY KEY (id)); +CREATE INDEX comment_test_index ON comment_test(indexed_col); + +COMMENT ON COLUMN comment_test.id IS 'Column ''id'' on comment_test'; +COMMENT ON INDEX comment_test_index IS 'Simple index on comment_test'; +COMMENT ON CONSTRAINT comment_test_positive_col_check ON comment_test IS 'CHECK constraint on comment_test.positive_col'; +COMMENT ON CONSTRAINT comment_test_pk ON comment_test IS 'PRIMARY KEY constraint of comment_test'; +COMMENT ON INDEX comment_test_pk IS 'Index backing the PRIMARY KEY of comment_test'; + +SELECT col_description('comment_test'::regclass, 1) as comment; +SELECT indexrelid::regclass::text as index, obj_description(indexrelid, 'pg_class') as comment FROM pg_index where indrelid = 'comment_test'::regclass ORDER BY 1, 2; +SELECT conname as constraint, obj_description(oid, 'pg_constraint') as comment FROM pg_constraint where conrelid = 'comment_test'::regclass ORDER BY 1, 2; + +-- Change the datatype of all the columns. ALTER TABLE is optimized to not +-- rebuild an index if the new data type is binary compatible with the old +-- one. Check do a dummy ALTER TABLE that doesn't change the datatype +-- first, to test that no-op codepath, and another one that does. +ALTER TABLE comment_test ALTER COLUMN indexed_col SET DATA TYPE int; +ALTER TABLE comment_test ALTER COLUMN indexed_col SET DATA TYPE text; +ALTER TABLE comment_test ALTER COLUMN id SET DATA TYPE int; +ALTER TABLE comment_test ALTER COLUMN id SET DATA TYPE text; +ALTER TABLE comment_test ALTER COLUMN positive_col SET DATA TYPE int; +ALTER TABLE comment_test ALTER COLUMN positive_col SET DATA TYPE bigint; + +-- Check that the comments are intact. +SELECT col_description('comment_test'::regclass, 1) as comment; +SELECT indexrelid::regclass::text as index, obj_description(indexrelid, 'pg_class') as comment FROM pg_index where indrelid = 'comment_test'::regclass ORDER BY 1, 2; +SELECT conname as constraint, obj_description(oid, 'pg_constraint') as comment FROM pg_constraint where conrelid = 'comment_test'::regclass ORDER BY 1, 2; + +-- Check compatibility for foreign keys and comments. This is done +-- separately as rebuilding the column type of the parent leads +-- to an error and would reduce the test scope. +CREATE TABLE comment_test_child ( + id text CONSTRAINT comment_test_child_fk REFERENCES comment_test); +CREATE INDEX comment_test_child_fk ON comment_test_child(id); +COMMENT ON COLUMN comment_test_child.id IS 'Column ''id'' on comment_test_child'; +COMMENT ON INDEX comment_test_child_fk IS 'Index backing the FOREIGN KEY of comment_test_child'; +COMMENT ON CONSTRAINT comment_test_child_fk ON comment_test_child IS 'FOREIGN KEY constraint of comment_test_child'; + +-- Change column type of parent +ALTER TABLE comment_test ALTER COLUMN id SET DATA TYPE text; +ALTER TABLE comment_test ALTER COLUMN id SET DATA TYPE int USING id::integer; + +-- Comments should be intact +SELECT col_description('comment_test_child'::regclass, 1) as comment; +SELECT indexrelid::regclass::text as index, obj_description(indexrelid, 'pg_class') as comment FROM pg_index where indrelid = 'comment_test_child'::regclass ORDER BY 1, 2; +SELECT conname as constraint, obj_description(oid, 'pg_constraint') as comment FROM pg_constraint where conrelid = 'comment_test_child'::regclass ORDER BY 1, 2; + +-- Check that we map relation oids to filenodes and back correctly. Only +-- display bad mappings so the test output doesn't change all the time. A +-- filenode function call can return NULL for a relation dropped concurrently +-- with the call's surrounding query, so ignore a NULL mapped_oid for +-- relations that no longer exist after all calls finish. +CREATE TEMP TABLE filenode_mapping AS +SELECT + oid, mapped_oid, reltablespace, relfilenode, relname +FROM pg_class, + pg_filenode_relation(reltablespace, pg_relation_filenode(oid)) AS mapped_oid +WHERE relkind IN ('r', 'i', 'S', 't', 'm') AND mapped_oid IS DISTINCT FROM oid AND relname NOT LIKE 'pg_%'; /* YB: relfilenode is 0 for pg catalog tables in YB */ + +SELECT m.* FROM filenode_mapping m LEFT JOIN pg_class c ON c.oid = m.oid +WHERE (c.oid IS NOT NULL OR m.mapped_oid IS NOT NULL) AND m.relname NOT LIKE 'pg_%'; /* YB: relfilenode is 0 for pg catalog tables in YB */ + +-- Checks on creating and manipulation of user defined relations in +-- pg_catalog. + +/* YB: creating user tables in system catalog doesn't work +SHOW allow_system_table_mods; +-- disallowed because of search_path issues with pg_dump +CREATE TABLE pg_catalog.new_system_table(); +-- instead create in public first, move to catalog +CREATE TABLE new_system_table(id serial primary key, othercol text); +ALTER TABLE new_system_table SET SCHEMA pg_catalog; +ALTER TABLE new_system_table SET SCHEMA public; +ALTER TABLE new_system_table SET SCHEMA pg_catalog; +-- will be ignored -- already there: +ALTER TABLE new_system_table SET SCHEMA pg_catalog; +ALTER TABLE new_system_table RENAME TO old_system_table; +CREATE INDEX old_system_table__othercol ON old_system_table (othercol); +INSERT INTO old_system_table(othercol) VALUES ('somedata'), ('otherdata'); +UPDATE old_system_table SET id = -id; +DELETE FROM old_system_table WHERE othercol = 'somedata'; +TRUNCATE old_system_table; +ALTER TABLE old_system_table DROP CONSTRAINT new_system_table_pkey; +ALTER TABLE old_system_table DROP COLUMN othercol; +DROP TABLE old_system_table; +*/ -- YB + +-- set logged +CREATE UNLOGGED TABLE unlogged1(f1 SERIAL PRIMARY KEY, f2 TEXT); -- has sequence, toast +-- check relpersistence of an unlogged table +/* YB: LOGGED is only supported syntactically +SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1' +UNION ALL +SELECT r.relname || ' toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^unlogged1' +UNION ALL +SELECT r.relname || ' toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^unlogged1' +ORDER BY relname; +*/ -- YB +CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- foreign key +CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self-referencing foreign key +ALTER TABLE unlogged3 SET LOGGED; -- skip self-referencing foreign key +/* YB: LOGGED is only supported syntactically +ALTER TABLE unlogged2 SET LOGGED; -- fails because a foreign key to an unlogged table exists +*/ -- YB +ALTER TABLE unlogged1 SET LOGGED; +/* YB: LOGGED is only supported syntactically +-- check relpersistence of an unlogged table after changing to permanent +SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1' +UNION ALL +SELECT r.relname || ' toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^unlogged1' +UNION ALL +SELECT r.relname || ' toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^unlogged1' +ORDER BY relname; +*/ -- YB +ALTER TABLE unlogged1 SET LOGGED; -- silently do nothing +DROP TABLE unlogged3; +DROP TABLE unlogged2; +DROP TABLE unlogged1; + +-- set unlogged +CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT); -- has sequence, toast +/* YB: LOGGED is only supported syntactically +-- check relpersistence of a permanent table +SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1' +UNION ALL +SELECT r.relname || ' toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^logged1' +UNION ALL +SELECT r.relname ||' toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^logged1' +ORDER BY relname; +*/ -- YB +CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- foreign key +CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self-referencing foreign key +ALTER TABLE logged1 SET UNLOGGED; -- fails because a foreign key from a permanent table exists +ALTER TABLE logged3 SET UNLOGGED; -- skip self-referencing foreign key +ALTER TABLE logged2 SET UNLOGGED; +ALTER TABLE logged1 SET UNLOGGED; +/* YB: LOGGED is only supported syntactically +-- check relpersistence of a permanent table after changing to unlogged +SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1' +UNION ALL +SELECT r.relname || ' toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^logged1' +UNION ALL +SELECT r.relname || ' toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^logged1' +ORDER BY relname; +ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing +*/ -- YB +DROP TABLE logged3; +DROP TABLE logged2; +DROP TABLE logged1; + +-- test ADD COLUMN IF NOT EXISTS +CREATE TABLE test_add_column(c1 integer); +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN c2 integer; +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN c2 integer; -- fail because c2 already exists +ALTER TABLE ONLY test_add_column + ADD COLUMN c2 integer; -- fail because c2 already exists +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN IF NOT EXISTS c2 integer; -- skipping because c2 already exists +ALTER TABLE ONLY test_add_column + ADD COLUMN IF NOT EXISTS c2 integer; -- skipping because c2 already exists +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN c2 integer, -- fail because c2 already exists + ADD COLUMN c3 integer primary key; +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists + ADD COLUMN c3 integer primary key; +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists + ADD COLUMN IF NOT EXISTS c3 integer primary key; -- skipping because c3 already exists +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists + ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists + ADD COLUMN c4 integer REFERENCES test_add_column; +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column; +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 8); +\d test_add_column +ALTER TABLE test_add_column + ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10); +\d test_add_column* +DROP TABLE test_add_column; +\d test_add_column* + +-- assorted cases with multiple ALTER TABLE steps +CREATE TABLE ataddindex(f1 INT); +INSERT INTO ataddindex VALUES (42), (43); +CREATE UNIQUE INDEX ataddindexi0 ON ataddindex(f1); +ALTER TABLE ataddindex + ADD PRIMARY KEY USING INDEX ataddindexi0, + ALTER f1 TYPE BIGINT; +\d ataddindex +DROP TABLE ataddindex; + +/* YB: exclude constraint is not supported +CREATE TABLE ataddindex(f1 VARCHAR(10)); +INSERT INTO ataddindex(f1) VALUES ('foo'), ('a'); +ALTER TABLE ataddindex + ALTER f1 SET DATA TYPE TEXT, + ADD EXCLUDE ((f1 LIKE 'a') WITH =); +\d ataddindex +DROP TABLE ataddindex; +*/ -- YB + +CREATE TABLE ataddindex(id int, ref_id int); +ALTER TABLE ataddindex + ADD PRIMARY KEY (id), + ADD FOREIGN KEY (ref_id) REFERENCES ataddindex; +\d ataddindex +DROP TABLE ataddindex; + +CREATE TABLE ataddindex(id int, ref_id int); +ALTER TABLE ataddindex + ADD UNIQUE (id), + ADD FOREIGN KEY (ref_id) REFERENCES ataddindex (id); +\d ataddindex +DROP TABLE ataddindex; + +-- unsupported constraint types for partitioned tables +CREATE TABLE partitioned ( + a int, + b int +) PARTITION BY RANGE (a, (a+b+1)); +/* YB does not support exclusion constraints +ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&); +*/ -- YB + +-- cannot drop column that is part of the partition key +ALTER TABLE partitioned DROP COLUMN a; +ALTER TABLE partitioned ALTER COLUMN a TYPE char(5); +ALTER TABLE partitioned DROP COLUMN b; +ALTER TABLE partitioned ALTER COLUMN b TYPE char(5); + +-- partitioned table cannot participate in regular inheritance +CREATE TABLE nonpartitioned ( + a int, + b int +); +ALTER TABLE partitioned INHERIT nonpartitioned; +ALTER TABLE nonpartitioned INHERIT partitioned; + +-- cannot add NO INHERIT constraint to partitioned tables +ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT; + +DROP TABLE partitioned, nonpartitioned; + +-- +-- ATTACH PARTITION +-- + +-- check that target table is partitioned +CREATE TABLE unparted ( + a int +); +CREATE TABLE fail_part (like unparted); +ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a'); +DROP TABLE unparted, fail_part; + +-- check that partition bound is compatible +CREATE TABLE list_parted ( + a int NOT NULL, + b char(2) COLLATE "C", + CONSTRAINT check_a CHECK (a > 0) +) PARTITION BY LIST (a); +CREATE TABLE fail_part (LIKE list_parted); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10); +DROP TABLE fail_part; + +-- check that the table being attached exists +ALTER TABLE list_parted ATTACH PARTITION nonexistent FOR VALUES IN (1); + +-- check ownership of the source table +CREATE ROLE regress_test_me; +CREATE ROLE regress_test_not_me; +CREATE TABLE not_owned_by_me (LIKE list_parted); +ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me; +SET SESSION AUTHORIZATION regress_test_me; +CREATE TABLE owned_by_me ( + a int +) PARTITION BY LIST (a); +ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1); +RESET SESSION AUTHORIZATION; +DROP TABLE owned_by_me, not_owned_by_me; +DROP ROLE regress_test_not_me; +DROP ROLE regress_test_me; + +-- check that the table being attached is not part of regular inheritance +CREATE TABLE parent (LIKE list_parted); +CREATE TABLE child () INHERITS (parent); +-- Uncomment the following when INHERITS is supported #5956. +/* +ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1); +ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1); +*/ +DROP TABLE parent CASCADE; + +-- check any TEMP-ness +CREATE TEMP TABLE temp_parted (a int) PARTITION BY LIST (a); +CREATE TABLE perm_part (a int); +ALTER TABLE temp_parted ATTACH PARTITION perm_part FOR VALUES IN (1); +DROP TABLE temp_parted, perm_part; + +-- check that the table being attached is not a typed table +CREATE TYPE mytype AS (a int); +CREATE TABLE fail_part OF mytype; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TYPE mytype CASCADE; + +-- check that the table being attached has only columns present in the parent +CREATE TABLE fail_part (like list_parted, c int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check that the table being attached has every column of the parent +CREATE TABLE fail_part (a int NOT NULL); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check that columns match in type, collation and NOT NULL status +CREATE TABLE fail_part ( + b char(3), + a int NOT NULL +); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "POSIX"; +-- Uncomment when ALTER TABLE COLLATE is supported (#1013) +-- ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check that the table being attached has all constraints of the parent +CREATE TABLE fail_part ( + b char(2) COLLATE "C", + a int NOT NULL +); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); + +-- check that the constraint matches in definition with parent's constraint +ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check the attributes and constraints after partition is attached +CREATE TABLE part_1 ( + a int NOT NULL, + b char(2) COLLATE "C", + CONSTRAINT check_a CHECK (a > 0) +); +ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1); +-- attislocal and conislocal are always false for merged attributes and constraints respectively. +SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0; +SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a'; + +-- check that the new partition won't overlap with an existing partition +CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; +-- check that an existing table can be attached as a default partition +CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS); +ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT; +-- check attaching default partition fails if a default partition already +-- exists +CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS); +ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT; + +-- check validation when attaching list partitions +CREATE TABLE list_parted2 ( + a int, + b char +) PARTITION BY LIST (a); + +-- check that violating rows are correctly reported +CREATE TABLE part_2 (LIKE list_parted2); +INSERT INTO part_2 VALUES (3, 'a'); +ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2); + +-- should be ok after deleting the bad row +DELETE FROM part_2; +ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2); + +-- check partition cannot be attached if default has some row for its values +CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT; +INSERT INTO list_parted2_def VALUES (11, 'z'); +CREATE TABLE part_3 (LIKE list_parted2); +ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11); +-- should be ok after deleting the bad row +DELETE FROM list_parted2_def WHERE a = 11; +ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11); + +-- adding constraints that describe the desired partition constraint +-- (or more restrictive) will help skip the validation scan +CREATE TABLE part_3_4 ( + LIKE list_parted2, + CONSTRAINT check_a CHECK (a IN (3)) +); + +-- however, if a list partition does not accept nulls, there should be +-- an explicit NOT NULL constraint on the partition key column for the +-- validation scan to be skipped; +ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4); + +-- adding a NOT NULL constraint will cause the scan to be skipped +ALTER TABLE list_parted2 DETACH PARTITION part_3_4; +ALTER TABLE part_3_4 ALTER a SET NOT NULL; +ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4); + +-- check if default partition scan skipped +ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6)); +CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66); + +-- check validation when attaching range partitions +CREATE TABLE range_parted ( + a int, + b int +) PARTITION BY RANGE (a, b); + +-- check that violating rows are correctly reported +CREATE TABLE part1 ( + a int NOT NULL CHECK (a = 1), + b int NOT NULL CHECK (b >= 1 AND b <= 10) +); +INSERT INTO part1 VALUES (1, 10); +-- Remember the TO bound is exclusive +ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10); + +-- should be ok after deleting the bad row +DELETE FROM part1; +ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10); + +-- adding constraints that describe the desired partition constraint +-- (or more restrictive) will help skip the validation scan +CREATE TABLE part2 ( + a int NOT NULL CHECK (a = 1), + b int NOT NULL CHECK (b >= 10 AND b < 18) +); +ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20); + +-- Create default partition +CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT; + +-- Only one default partition is allowed, hence, following should give error +CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS); +ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT; + +-- Overlapping partitions cannot be attached, hence, following should give error +INSERT INTO partr_def1 VALUES (2, 10); +CREATE TABLE part3 (LIKE range_parted); +ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20); + +-- Attaching partitions should be successful when there are no overlapping rows +ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20); + +-- check that leaf partitions are scanned when attaching a partitioned +-- table +CREATE TABLE part_5 ( + LIKE list_parted2 +) PARTITION BY LIST (b); + +-- check that violating rows are correctly reported +CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a'); +INSERT INTO part_5_a (a, b) VALUES (6, 'a'); +ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5); + +-- delete the faulting row and also add a constraint to skip the scan +DELETE FROM part_5_a WHERE a NOT IN (3); +ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 5); +ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5); +ALTER TABLE list_parted2 DETACH PARTITION part_5; +ALTER TABLE part_5 DROP CONSTRAINT check_a; + +-- scan should again be skipped, even though NOT NULL is now a column property +ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL; +ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5); + +-- Check the case where attnos of the partitioning columns in the table being +-- attached differs from the parent. It should not affect the constraint- +-- checking logic that allows to skip the scan. +CREATE TABLE part_6 ( + c int, + LIKE list_parted2, + CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 6) +); +ALTER TABLE part_6 DROP c; +ALTER TABLE list_parted2 ATTACH PARTITION part_6 FOR VALUES IN (6); + +-- Similar to above, but the table being attached is a partitioned table +-- whose partition has still different attnos for the root partitioning +-- columns. +CREATE TABLE part_7 ( + LIKE list_parted2, + CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7) +) PARTITION BY LIST (b); +CREATE TABLE part_7_a_null ( + c int, + d int, + e int, + LIKE list_parted2, -- 'a' will have attnum = 4 + CONSTRAINT check_b CHECK (b IS NULL OR b = 'a'), + CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7) +); +ALTER TABLE part_7_a_null DROP c, DROP d, DROP e; +ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null); +ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7); + +-- Same example, but check this time that the constraint correctly detects +-- violating rows +ALTER TABLE list_parted2 DETACH PARTITION part_7; +ALTER TABLE part_7 DROP CONSTRAINT check_a; -- thusly, scan won't be skipped +INSERT INTO part_7 (a, b) VALUES (8, null), (9, 'a'); +SELECT tableoid::regclass, a, b FROM part_7 order by a; +ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7); + +-- check that leaf partitions of default partition are scanned when +-- attaching a partitioned table. +ALTER TABLE part_5 DROP CONSTRAINT check_a; +CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a); +CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5); +INSERT INTO part5_def_p1 VALUES (5, 'y'); +CREATE TABLE part5_p1 (LIKE part_5); +ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y'); +-- should be ok after deleting the bad row +DELETE FROM part5_def_p1 WHERE b = 'y'; +ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y'); + +-- check that the table being attached is not already a partition +ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2); + +-- check that circular inheritance is not allowed +ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b'); +ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0); + +-- If a partitioned table being created or an existing table being attached +-- as a partition does not have a constraint that would allow validation scan +-- to be skipped, but an individual partition does, then the partition's +-- validation scan is skipped. +CREATE TABLE quuux (a int, b text) PARTITION BY LIST (a); +CREATE TABLE quuux_default PARTITION OF quuux DEFAULT PARTITION BY LIST (b); +CREATE TABLE quuux_default1 PARTITION OF quuux_default ( + CONSTRAINT check_1 CHECK (a IS NOT NULL AND a = 1) +) FOR VALUES IN ('b'); +CREATE TABLE quuux1 (a int, b text); +ALTER TABLE quuux ATTACH PARTITION quuux1 FOR VALUES IN (1); -- validate! +CREATE TABLE quuux2 (a int, b text); +ALTER TABLE quuux ATTACH PARTITION quuux2 FOR VALUES IN (2); -- skip validation +DROP TABLE quuux1, quuux2; +-- should validate for quuux1, but not for quuux2 +CREATE TABLE quuux1 PARTITION OF quuux FOR VALUES IN (1); +CREATE TABLE quuux2 PARTITION OF quuux FOR VALUES IN (2); +DROP TABLE quuux; + +-- check validation when attaching hash partitions + +-- Use hand-rolled hash functions and operator class to get predictable result +-- on different machines. part_test_int4_ops is defined in insert.sql. + +-- check that the new partition won't overlap with an existing partition +CREATE TABLE hash_parted ( + a int, + b int +) PARTITION BY HASH (a part_test_int4_ops); +CREATE TABLE hpart_1 PARTITION OF hash_parted FOR VALUES WITH (MODULUS 4, REMAINDER 0); +CREATE TABLE fail_part (LIKE hpart_1); +ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 8, REMAINDER 4); +ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 8, REMAINDER 0); +DROP TABLE fail_part; + +-- check validation when attaching hash partitions + +-- check that violating rows are correctly reported +CREATE TABLE hpart_2 (LIKE hash_parted); +INSERT INTO hpart_2 VALUES (3, 0); +ALTER TABLE hash_parted ATTACH PARTITION hpart_2 FOR VALUES WITH (MODULUS 4, REMAINDER 1); + +-- should be ok after deleting the bad row +DELETE FROM hpart_2; +ALTER TABLE hash_parted ATTACH PARTITION hpart_2 FOR VALUES WITH (MODULUS 4, REMAINDER 1); + +-- check that leaf partitions are scanned when attaching a partitioned +-- table +CREATE TABLE hpart_5 ( + LIKE hash_parted +) PARTITION BY LIST (b); + +-- check that violating rows are correctly reported +CREATE TABLE hpart_5_a PARTITION OF hpart_5 FOR VALUES IN ('1', '2', '3'); +INSERT INTO hpart_5_a (a, b) VALUES (7, 1); +ALTER TABLE hash_parted ATTACH PARTITION hpart_5 FOR VALUES WITH (MODULUS 4, REMAINDER 2); + +-- should be ok after deleting the bad row +DELETE FROM hpart_5_a; +ALTER TABLE hash_parted ATTACH PARTITION hpart_5 FOR VALUES WITH (MODULUS 4, REMAINDER 2); + +-- check that the table being attach is with valid modulus and remainder value +CREATE TABLE fail_part(LIKE hash_parted); +ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 0, REMAINDER 1); +ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 8, REMAINDER 8); +ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 3, REMAINDER 2); +DROP TABLE fail_part; + +-- +-- DETACH PARTITION +-- + +-- check that the table is partitioned at all +CREATE TABLE regular_table (a int); +-- YB note: upstream PG shows a different error message because YB hits this +-- one first in a YB codepath (see ATRewriteCatalogs, YBCPrepareAlterTable, +-- ATExecCmd). +ALTER TABLE regular_table DETACH PARTITION any_name; +DROP TABLE regular_table; + +-- check that the partition being detached exists at all +ALTER TABLE list_parted2 DETACH PARTITION part_4; +ALTER TABLE hash_parted DETACH PARTITION hpart_4; + +-- check that the partition being detached is actually a partition of the parent +CREATE TABLE not_a_part (a int); +ALTER TABLE list_parted2 DETACH PARTITION not_a_part; +ALTER TABLE list_parted2 DETACH PARTITION part_1; + +ALTER TABLE hash_parted DETACH PARTITION not_a_part; +DROP TABLE not_a_part; + +-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and +-- attislocal/conislocal is set to true +ALTER TABLE list_parted2 DETACH PARTITION part_3_4; +SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0; +SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a'; +DROP TABLE part_3_4; + +-- check that a detached partition is not dropped on dropping a partitioned table +CREATE TABLE range_parted2 ( + a int +) PARTITION BY RANGE(a); +CREATE TABLE part_rp PARTITION OF range_parted2 FOR VALUES FROM (0) to (100); +ALTER TABLE range_parted2 DETACH PARTITION part_rp; +DROP TABLE range_parted2; +SELECT * from part_rp; +DROP TABLE part_rp; + +-- Check ALTER TABLE commands for partitioned tables and partitions + +-- cannot add/drop column to/from *only* the parent +ALTER TABLE ONLY list_parted2 ADD COLUMN c int; +ALTER TABLE ONLY list_parted2 DROP COLUMN b; + +-- cannot add a column to partition or drop an inherited one +ALTER TABLE part_2 ADD COLUMN c text; +ALTER TABLE part_2 DROP COLUMN b; + +-- Nor rename, alter type +ALTER TABLE part_2 RENAME COLUMN b to c; +ALTER TABLE part_2 ALTER COLUMN b TYPE text; + +-- cannot add/drop NOT NULL or check constraints to *only* the parent, when +-- partitions exist +ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL; +ALTER TABLE ONLY list_parted2 ADD CONSTRAINT check_b CHECK (b <> 'zz'); + +ALTER TABLE list_parted2 ALTER b SET NOT NULL; +ALTER TABLE ONLY list_parted2 ALTER b DROP NOT NULL; +ALTER TABLE list_parted2 ADD CONSTRAINT check_b CHECK (b <> 'zz'); +-- Uncomment when #14570 is fixed +/* +ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_b; + +-- It's alright though, if no partitions are yet created +CREATE TABLE parted_no_parts (a int) PARTITION BY LIST (a); +ALTER TABLE ONLY parted_no_parts ALTER a SET NOT NULL; +ALTER TABLE ONLY parted_no_parts ADD CONSTRAINT check_a CHECK (a > 0); +ALTER TABLE ONLY parted_no_parts ALTER a DROP NOT NULL; +ALTER TABLE ONLY parted_no_parts DROP CONSTRAINT check_a; +DROP TABLE parted_no_parts; +*/ +-- cannot drop inherited NOT NULL or check constraints from partition +ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0); +ALTER TABLE part_2 ALTER b DROP NOT NULL; +ALTER TABLE part_2 DROP CONSTRAINT check_a2; + +-- Doesn't make sense to add NO INHERIT constraints on partitioned tables +ALTER TABLE list_parted2 add constraint check_b2 check (b <> 'zz') NO INHERIT; + +-- check that a partition cannot participate in regular inheritance +CREATE TABLE inh_test () INHERITS (part_2); +CREATE TABLE inh_test (LIKE part_2); +ALTER TABLE inh_test INHERIT part_2; +ALTER TABLE part_2 INHERIT inh_test; + +-- cannot drop or alter type of partition key columns of lower level +-- partitioned tables; for example, part_5, which is list_parted2's +-- partition, is partitioned on b; +ALTER TABLE list_parted2 DROP COLUMN b; +ALTER TABLE list_parted2 ALTER COLUMN b TYPE text; + +-- dropping non-partition key columns should be allowed on the parent table. +ALTER TABLE list_parted DROP COLUMN b; +SELECT * FROM list_parted; + +-- cleanup +DROP TABLE list_parted, list_parted2, range_parted; +DROP TABLE fail_def_part; +DROP TABLE hash_parted; + +-- more tests for certain multi-level partitioning scenarios +create table p (a int, b int) partition by range (a, b); +create table p1 (b int, a int not null) partition by range (b); +create table p11 (like p1); +alter table p11 drop a; +alter table p11 add a int; +alter table p11 drop a; +alter table p11 add a int not null; +-- attnum for key attribute 'a' is different in p, p1, and p11 +select attrelid::regclass, attname, attnum +from pg_attribute +where attname = 'a' + and (attrelid = 'p'::regclass + or attrelid = 'p1'::regclass + or attrelid = 'p11'::regclass) +order by attrelid::regclass::text; + +alter table p1 attach partition p11 for values from (2) to (5); + +insert into p1 (a, b) values (2, 3); +-- check that partition validation scan correctly detects violating rows +alter table p attach partition p1 for values from (1, 2) to (1, 10); + +-- cleanup +drop table p; +drop table p1; + +-- validate constraint on partitioned tables should only scan leaf partitions +create table parted_validate_test (a int) partition by list (a); +create table parted_validate_test_1 partition of parted_validate_test for values in (0, 1); +alter table parted_validate_test add constraint parted_validate_test_chka check (a > 0) not valid; +alter table parted_validate_test validate constraint parted_validate_test_chka; +drop table parted_validate_test; +-- test alter column options +CREATE TABLE attmp(i integer); +INSERT INTO attmp VALUES (1); +ALTER TABLE attmp ALTER COLUMN i SET (n_distinct = 1, n_distinct_inherited = 2); +-- Uncomment when ALTER COLUMN is supported (#1200). +-- ALTER TABLE attmp ALTER COLUMN i RESET (n_distinct_inherited); +-- ANALYZE attmp; +DROP TABLE attmp; + +DROP USER regress_alter_table_user1; + +-- check that violating rows are correctly reported when attaching as the +-- default partition +create table defpart_attach_test (a int) partition by list (a); +create table defpart_attach_test1 partition of defpart_attach_test for values in (1); +create table defpart_attach_test_d (b int, a int); +alter table defpart_attach_test_d drop b; +insert into defpart_attach_test_d values (1), (2); + +-- error because its constraint as the default partition would be violated +-- by the row containing 1 +alter table defpart_attach_test attach partition defpart_attach_test_d default; +delete from defpart_attach_test_d where a = 1; +alter table defpart_attach_test_d add check (a > 1); + +-- should be attached successfully and without needing to be scanned +alter table defpart_attach_test attach partition defpart_attach_test_d default; + +-- check that attaching a partition correctly reports any rows in the default +-- partition that should not be there for the new partition to be attached +-- successfully +create table defpart_attach_test_2 (like defpart_attach_test_d); +alter table defpart_attach_test attach partition defpart_attach_test_2 for values in (2); + +drop table defpart_attach_test; + +-- check combinations of temporary and permanent relations when attaching +-- partitions. +create table perm_part_parent (a int) partition by list (a); +create temp table temp_part_parent (a int) partition by list (a); +create table perm_part_child (a int); +create temp table temp_part_child (a int); +alter table temp_part_parent attach partition perm_part_child default; -- error +alter table perm_part_parent attach partition temp_part_child default; -- error +alter table temp_part_parent attach partition temp_part_child default; -- ok +drop table perm_part_parent cascade; +drop table temp_part_parent cascade; + +-- check that attaching partitions to a table while it is being used is +-- prevented +create table tab_part_attach (a int) partition by list (a); +create or replace function func_part_attach() returns trigger + language plpgsql as $$ + begin + execute 'create table tab_part_attach_1 (a int)'; + execute 'alter table tab_part_attach attach partition tab_part_attach_1 for values in (1)'; + return null; + end $$; +create trigger trig_part_attach before insert on tab_part_attach + for each statement execute procedure func_part_attach(); +insert into tab_part_attach values (1); +drop table tab_part_attach; +drop function func_part_attach(); + +-- test case where the partitioning operator is a SQL function whose +-- evaluation results in the table's relcache being rebuilt partway through +-- the execution of an ATTACH PARTITION command +create function at_test_sql_partop (int4, int4) returns int language sql +as $$ select case when $1 = $2 then 0 when $1 > $2 then 1 else -1 end; $$; +create operator class at_test_sql_partop for type int4 using btree as + operator 1 < (int4, int4), operator 2 <= (int4, int4), + operator 3 = (int4, int4), operator 4 >= (int4, int4), + operator 5 > (int4, int4), function 1 at_test_sql_partop(int4, int4); +-- Fails due to #14575, uncomment the following tests once fixed. +create table at_test_sql_partop (a int) partition by range (a at_test_sql_partop); +/* +create table at_test_sql_partop_1 (a int); +alter table at_test_sql_partop attach partition at_test_sql_partop_1 for values from (0) to (10); +drop table at_test_sql_partop; +*/ +drop operator class at_test_sql_partop using btree; +drop function at_test_sql_partop; +-- +-- Check that attaching or detaching a partitioned partition correctly leads +-- to its partitions' constraint being updated to reflect the parent's +-- newly added/removed constraint +create table target_parted (a int, b int) partition by list (a); +create table attach_parted (a int, b int) partition by list (b); +create table attach_parted_part1 partition of attach_parted for values in (1); +-- insert a row directly into the leaf partition so that its partition +-- constraint is built and stored in the relcache +insert into attach_parted_part1 values (1, 1); +-- the following better invalidate the partition constraint of the leaf +-- partition too... +alter table target_parted attach partition attach_parted for values in (1); +-- ...such that the following insert fails +insert into attach_parted_part1 values (2, 1); +-- ...and doesn't when the partition is detached along with its own partition +alter table target_parted detach partition attach_parted; +insert into attach_parted_part1 values (2, 1); diff --git a/src/test/regress/sql/yb.port.arrays.sql b/src/test/regress/sql/yb.port.arrays.sql new file mode 100644 index 0000000000000..405997c18f4de --- /dev/null +++ b/src/test/regress/sql/yb.port.arrays.sql @@ -0,0 +1,766 @@ +-- +-- ARRAYS +-- + +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +CREATE TABLE arrtest ( + a int2[], + b int4[][][], + c name[], + d text[][], + e float8[], + f char(5)[], + g varchar(5)[] +); + +CREATE TABLE array_op_test ( + seqno int4, + i int4[], + t text[] +); + +\set filename :abs_srcdir '/data/array.data' +COPY array_op_test FROM :'filename'; +ANALYZE array_op_test; + +-- +-- only the 'e' array is 0-based, the others are 1-based. +-- + +INSERT INTO arrtest (a[1:5], b[1:1][1:2][1:2], c, d, f, g) + VALUES ('{1,2,3,4,5}', '{{{0,0},{1,2}}}', '{}', '{}', '{}', '{}'); + +UPDATE arrtest SET e[0] = '1.1'; + +UPDATE arrtest SET e[1] = '2.2'; + +INSERT INTO arrtest (f) + VALUES ('{"too long"}'); + +INSERT INTO arrtest (a, b[1:2][1:2], c, d, e, f, g) + VALUES ('{11,12,23}', '{{3,4},{4,5}}', '{"foobar"}', + '{{"elt1", "elt2"}}', '{"3.4", "6.7"}', + '{"abc","abcde"}', '{"abc","abcde"}'); + +INSERT INTO arrtest (a, b[1:2], c, d[1:2]) + VALUES ('{}', '{3,4}', '{foo,bar}', '{bar,foo}'); + +INSERT INTO arrtest (b[2]) VALUES(now()); -- error, type mismatch + +INSERT INTO arrtest (b[1:2]) VALUES(now()); -- error, type mismatch + +SELECT * FROM arrtest; + +SELECT arrtest.a[1], + arrtest.b[1][1][1], + arrtest.c[1], + arrtest.d[1][1], + arrtest.e[0] + FROM arrtest; + +SELECT a[1], b[1][1][1], c[1], d[1][1], e[0] + FROM arrtest; + +SELECT a[1:3], + b[1:1][1:2][1:2], + c[1:2], + d[1:1][1:2] + FROM arrtest; + +SELECT array_ndims(a) AS a,array_ndims(b) AS b,array_ndims(c) AS c + FROM arrtest; + +SELECT array_dims(a) AS a,array_dims(b) AS b,array_dims(c) AS c + FROM arrtest; + +-- returns nothing +SELECT * + FROM arrtest + WHERE a[1] < 5 and + c = '{"foobar"}'::_name; + +UPDATE arrtest + SET a[1:2] = '{16,25}' + WHERE NOT a = '{}'::_int2; + +UPDATE arrtest + SET b[1:1][1:1][1:2] = '{113, 117}', + b[1:1][1:2][2:2] = '{142, 147}' + WHERE array_dims(b) = '[1:1][1:2][1:2]'; + +UPDATE arrtest + SET c[2:2] = '{"new_word"}' + WHERE array_dims(c) is not null; + +SELECT a,b,c FROM arrtest; + +SELECT a[1:3], + b[1:1][1:2][1:2], + c[1:2], + d[1:1][2:2] + FROM arrtest; + +SELECT b[1:1][2][2], + d[1:1][2] + FROM arrtest; + +INSERT INTO arrtest(a) VALUES('{1,null,3}'); +SELECT a FROM arrtest; +UPDATE arrtest SET a[4] = NULL WHERE a[2] IS NULL; +SELECT a FROM arrtest WHERE a[2] IS NULL; +DELETE FROM arrtest WHERE a[2] IS NULL AND b IS NULL; +SELECT a,b,c FROM arrtest; + +-- test mixed slice/scalar subscripting +select '{{1,2,3},{4,5,6},{7,8,9}}'::int[]; +select ('{{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2]; +select '[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[]; +select ('[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2]; + +-- +-- check subscription corner cases +-- +-- More subscripts than MAXDIM (6) +SELECT ('{}'::int[])[1][2][3][4][5][6][7]; +-- NULL index yields NULL when selecting +SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][NULL][1]; +SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][NULL:1][1]; +SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][1:NULL][1]; +-- NULL index in assignment is an error +UPDATE arrtest + SET c[NULL] = '{"can''t assign"}' + WHERE array_dims(c) is not null; +UPDATE arrtest + SET c[NULL:1] = '{"can''t assign"}' + WHERE array_dims(c) is not null; +UPDATE arrtest + SET c[1:NULL] = '{"can''t assign"}' + WHERE array_dims(c) is not null; +-- Un-subscriptable type +SELECT (now())[1]; + +-- test slices with empty lower and/or upper index +CREATE TEMP TABLE arrtest_s ( + a int2[], + b int2[][] +); +INSERT INTO arrtest_s VALUES ('{1,2,3,4,5}', '{{1,2,3}, {4,5,6}, {7,8,9}}'); +INSERT INTO arrtest_s VALUES ('[0:4]={1,2,3,4,5}', '[0:2][0:2]={{1,2,3}, {4,5,6}, {7,8,9}}'); + +SELECT * FROM arrtest_s; +SELECT a[:3], b[:2][:2] FROM arrtest_s; +SELECT a[2:], b[2:][2:] FROM arrtest_s; +SELECT a[:], b[:] FROM arrtest_s; + +-- updates +UPDATE arrtest_s SET a[:3] = '{11, 12, 13}', b[:2][:2] = '{{11,12}, {14,15}}' + WHERE array_lower(a,1) = 1; +SELECT * FROM arrtest_s; +UPDATE arrtest_s SET a[3:] = '{23, 24, 25}', b[2:][2:] = '{{25,26}, {28,29}}'; +SELECT * FROM arrtest_s; +UPDATE arrtest_s SET a[:] = '{11, 12, 13, 14, 15}'; +SELECT * FROM arrtest_s; +UPDATE arrtest_s SET a[:] = '{23, 24, 25}'; -- fail, too small +INSERT INTO arrtest_s VALUES(NULL, NULL); +UPDATE arrtest_s SET a[:] = '{11, 12, 13, 14, 15}'; -- fail, no good with null + +-- we want to work with a point_tbl that includes a null +-- YB note: but we also want to test YB tables instead of temporary tables, so +-- create a new table in a new namespace instead. +CREATE SCHEMA yb_tmp; +SET search_path TO yb_tmp; +CREATE TABLE point_tbl AS SELECT * FROM public.point_tbl; +INSERT INTO POINT_TBL(f1) VALUES (NULL); + +-- check with fixed-length-array type, such as point +SELECT f1[0:1] FROM POINT_TBL; +SELECT f1[0:] FROM POINT_TBL; +SELECT f1[:1] FROM POINT_TBL; +SELECT f1[:] FROM POINT_TBL; + +-- subscript assignments to fixed-width result in NULL if previous value is NULL +UPDATE point_tbl SET f1[0] = 10 WHERE f1 IS NULL RETURNING *; +INSERT INTO point_tbl(f1[0]) VALUES(0) RETURNING *; +-- NULL assignments get ignored +UPDATE point_tbl SET f1[0] = NULL WHERE f1::text = '(10,10)'::point::text RETURNING *; +-- but non-NULL subscript assignments work +UPDATE point_tbl SET f1[0] = -10, f1[1] = -10 WHERE f1::text = '(10,10)'::point::text RETURNING *; +-- but not to expand the range +UPDATE point_tbl SET f1[3] = 10 WHERE f1::text = '(-10,-10)'::point::text RETURNING *; + +DROP TABLE point_tbl; -- YB note: reset the changes from above. +DROP SCHEMA yb_tmp; -- YB note: reset the changes from above. +RESET search_path; -- YB note: reset the changes from above. + +-- +-- test array extension +-- +CREATE TEMP TABLE arrtest1 (i int[], t text[]); +insert into arrtest1 values(array[1,2,null,4], array['one','two',null,'four']); +select * from arrtest1; +update arrtest1 set i[2] = 22, t[2] = 'twenty-two'; +select * from arrtest1; +update arrtest1 set i[5] = 5, t[5] = 'five'; +select * from arrtest1; +update arrtest1 set i[8] = 8, t[8] = 'eight'; +select * from arrtest1; +update arrtest1 set i[0] = 0, t[0] = 'zero'; +select * from arrtest1; +update arrtest1 set i[-3] = -3, t[-3] = 'minus-three'; +select * from arrtest1; +update arrtest1 set i[0:2] = array[10,11,12], t[0:2] = array['ten','eleven','twelve']; +select * from arrtest1; +update arrtest1 set i[8:10] = array[18,null,20], t[8:10] = array['p18',null,'p20']; +select * from arrtest1; +update arrtest1 set i[11:12] = array[null,22], t[11:12] = array[null,'p22']; +select * from arrtest1; +update arrtest1 set i[15:16] = array[null,26], t[15:16] = array[null,'p26']; +select * from arrtest1; +update arrtest1 set i[-5:-3] = array[-15,-14,-13], t[-5:-3] = array['m15','m14','m13']; +select * from arrtest1; +update arrtest1 set i[-7:-6] = array[-17,null], t[-7:-6] = array['m17',null]; +select * from arrtest1; +update arrtest1 set i[-12:-10] = array[-22,null,-20], t[-12:-10] = array['m22',null,'m20']; +select * from arrtest1; +delete from arrtest1; +insert into arrtest1 values(array[1,2,null,4], array['one','two',null,'four']); +select * from arrtest1; +update arrtest1 set i[0:5] = array[0,1,2,null,4,5], t[0:5] = array['z','p1','p2',null,'p4','p5']; +select * from arrtest1; + +-- +-- array expressions and operators +-- + +-- table creation and INSERTs +CREATE TEMP TABLE arrtest2 (i integer ARRAY[4], f float8[], n numeric[], t text[], d timestamp[]); +INSERT INTO arrtest2 VALUES( + ARRAY[[[113,142],[1,147]]], + ARRAY[1.1,1.2,1.3]::float8[], + ARRAY[1.1,1.2,1.3], + ARRAY[[['aaa','aab'],['aba','abb'],['aca','acb']],[['baa','bab'],['bba','bbb'],['bca','bcb']]], + ARRAY['19620326','19931223','19970117']::timestamp[] +); + +-- some more test data +CREATE TEMP TABLE arrtest_f (f0 int, f1 text, f2 float8); +insert into arrtest_f values(1,'cat1',1.21); +insert into arrtest_f values(2,'cat1',1.24); +insert into arrtest_f values(3,'cat1',1.18); +insert into arrtest_f values(4,'cat1',1.26); +insert into arrtest_f values(5,'cat1',1.15); +insert into arrtest_f values(6,'cat2',1.15); +insert into arrtest_f values(7,'cat2',1.26); +insert into arrtest_f values(8,'cat2',1.32); +insert into arrtest_f values(9,'cat2',1.30); + +CREATE TEMP TABLE arrtest_i (f0 int, f1 text, f2 int); +insert into arrtest_i values(1,'cat1',21); +insert into arrtest_i values(2,'cat1',24); +insert into arrtest_i values(3,'cat1',18); +insert into arrtest_i values(4,'cat1',26); +insert into arrtest_i values(5,'cat1',15); +insert into arrtest_i values(6,'cat2',15); +insert into arrtest_i values(7,'cat2',26); +insert into arrtest_i values(8,'cat2',32); +insert into arrtest_i values(9,'cat2',30); + +-- expressions +SELECT t.f[1][3][1] AS "131", t.f[2][2][1] AS "221" FROM ( + SELECT ARRAY[[[111,112],[121,122],[131,132]],[[211,212],[221,122],[231,232]]] AS f +) AS t; +SELECT ARRAY[[[[[['hello'],['world']]]]]]; +SELECT ARRAY[ARRAY['hello'],ARRAY['world']]; +SELECT ARRAY(select f2 from arrtest_f order by f2) AS "ARRAY"; + +-- with nulls +SELECT '{1,null,3}'::int[]; +SELECT ARRAY[1,NULL,3]; + +-- functions +SELECT array_append(array[42], 6) AS "{42,6}"; +SELECT array_prepend(6, array[42]) AS "{6,42}"; +SELECT array_cat(ARRAY[1,2], ARRAY[3,4]) AS "{1,2,3,4}"; +SELECT array_cat(ARRAY[1,2], ARRAY[[3,4],[5,6]]) AS "{{1,2},{3,4},{5,6}}"; +SELECT array_cat(ARRAY[[3,4],[5,6]], ARRAY[1,2]) AS "{{3,4},{5,6},{1,2}}"; + +SELECT array_position(ARRAY[1,2,3,4,5], 4); +SELECT array_position(ARRAY[5,3,4,2,1], 4); +SELECT array_position(ARRAY[[1,2],[3,4]], 3); +SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon'); +SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'sat'); +SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], NULL); +SELECT array_position(ARRAY['sun','mon','tue','wed','thu',NULL,'fri','sat'], NULL); +SELECT array_position(ARRAY['sun','mon','tue','wed','thu',NULL,'fri','sat'], 'sat'); + +SELECT array_positions(NULL, 10); +SELECT array_positions(NULL, NULL::int); +SELECT array_positions(ARRAY[1,2,3,4,5,6,1,2,3,4,5,6], 4); +SELECT array_positions(ARRAY[[1,2],[3,4]], 4); +SELECT array_positions(ARRAY[1,2,3,4,5,6,1,2,3,4,5,6], NULL); +SELECT array_positions(ARRAY[1,2,3,NULL,5,6,1,2,3,NULL,5,6], NULL); +SELECT array_length(array_positions(ARRAY(SELECT 'AAAAAAAAAAAAAAAAAAAAAAAAA'::text || i % 10 + FROM generate_series(1,100) g(i)), + 'AAAAAAAAAAAAAAAAAAAAAAAAA5'), 1); + +DO $$ +DECLARE + o int; + a int[] := ARRAY[1,2,3,2,3,1,2]; +BEGIN + o := array_position(a, 2); + WHILE o IS NOT NULL + LOOP + RAISE NOTICE '%', o; + o := array_position(a, 2, o + 1); + END LOOP; +END +$$ LANGUAGE plpgsql; + +SELECT array_position('[2:4]={1,2,3}'::int[], 1); +SELECT array_positions('[2:4]={1,2,3}'::int[], 1); + +SELECT + array_position(ids, (1, 1)), + array_positions(ids, (1, 1)) + FROM +(VALUES + (ARRAY[(0, 0), (1, 1)]), + (ARRAY[(1, 1)]) +) AS f (ids); + +-- operators +SELECT a FROM arrtest WHERE b = ARRAY[[[113,142],[1,147]]]; +SELECT NOT ARRAY[1.1,1.2,1.3] = ARRAY[1.1,1.2,1.3] AS "FALSE"; +SELECT ARRAY[1,2] || 3 AS "{1,2,3}"; +SELECT 0 || ARRAY[1,2] AS "{0,1,2}"; +SELECT ARRAY[1,2] || ARRAY[3,4] AS "{1,2,3,4}"; +SELECT ARRAY[[['hello','world']]] || ARRAY[[['happy','birthday']]] AS "ARRAY"; +SELECT ARRAY[[1,2],[3,4]] || ARRAY[5,6] AS "{{1,2},{3,4},{5,6}}"; +SELECT ARRAY[0,0] || ARRAY[1,1] || ARRAY[2,2] AS "{0,0,1,1,2,2}"; +SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; +SELECT ARRAY[1.1] || ARRAY[2,3,4]; +SELECT array_agg(x) || array_agg(x) FROM (VALUES (ROW(1,2)), (ROW(3,4))) v(x); +SELECT ROW(1,2) || array_agg(x) FROM (VALUES (ROW(3,4)), (ROW(5,6))) v(x); + +SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i = '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @> '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; + +SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t <@ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t = '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @> '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t && '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t <@ '{}' ORDER BY seqno; + +-- array casts +SELECT ARRAY[1,2,3]::text[]::int[]::float8[] AS "{1,2,3}"; +SELECT pg_typeof(ARRAY[1,2,3]::text[]::int[]::float8[]) AS "double precision[]"; +SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] AS "{{a,bc},{def,hijk}}"; +SELECT pg_typeof(ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[]) AS "character varying[]"; +SELECT CAST(ARRAY[[[[[['a','bb','ccc']]]]]] as text[]) as "{{{{{{a,bb,ccc}}}}}}"; +SELECT NULL::text[]::int[] AS "NULL"; + +-- scalar op any/all (array) +select 33 = any ('{1,2,3}'); +select 33 = any ('{1,2,33}'); +select 33 = all ('{1,2,33}'); +select 33 >= all ('{1,2,33}'); +-- boundary cases +select null::int >= all ('{1,2,33}'); +select null::int >= all ('{}'); +select null::int >= any ('{}'); +-- cross-datatype +select 33.4 = any (array[1,2,3]); +select 33.4 > all (array[1,2,3]); +-- errors +select 33 * any ('{1,2,3}'); +select 33 * any (44); +-- nulls +select 33 = any (null::int[]); +select null::int = any ('{1,2,3}'); +select 33 = any ('{1,null,3}'); +select 33 = any ('{1,null,33}'); +select 33 = all (null::int[]); +select null::int = all ('{1,2,3}'); +select 33 = all ('{1,null,3}'); +select 33 = all ('{33,null,33}'); +-- nulls later in the bitmap +SELECT -1 != ALL(ARRAY(SELECT NULLIF(g.i, 900) FROM generate_series(1,1000) g(i))); + +-- test indexes on arrays +create temp table arr_tbl (f1 int[] unique); +insert into arr_tbl values ('{1,2,3}'); +insert into arr_tbl values ('{1,2}'); +-- failure expected: +insert into arr_tbl values ('{1,2,3}'); +insert into arr_tbl values ('{2,3,4}'); +insert into arr_tbl values ('{1,5,3}'); +insert into arr_tbl values ('{1,2,10}'); + +set enable_seqscan to off; +set enable_bitmapscan to off; +select * from arr_tbl where f1 > '{1,2,3}' and f1 <= '{1,5,3}'; +select * from arr_tbl where f1 >= '{1,2,3}' and f1 < '{1,5,3}'; + +-- test ON CONFLICT DO UPDATE with arrays +create temp table arr_pk_tbl (pk int4 primary key, f1 int[]); +insert into arr_pk_tbl values (1, '{1,2,3}'); +insert into arr_pk_tbl values (1, '{3,4,5}') on conflict (pk) + do update set f1[1] = excluded.f1[1], f1[3] = excluded.f1[3] + returning pk, f1; +insert into arr_pk_tbl(pk, f1[1:2]) values (1, '{6,7,8}') on conflict (pk) + do update set f1[1] = excluded.f1[1], + f1[2] = excluded.f1[2], + f1[3] = excluded.f1[3] + returning pk, f1; + +-- note: if above selects don't produce the expected tuple order, +-- then you didn't get an indexscan plan, and something is busted. +reset enable_seqscan; +reset enable_bitmapscan; + +-- test [not] (like|ilike) (any|all) (...) +select 'foo' like any (array['%a', '%o']); -- t +select 'foo' like any (array['%a', '%b']); -- f +select 'foo' like all (array['f%', '%o']); -- t +select 'foo' like all (array['f%', '%b']); -- f +select 'foo' not like any (array['%a', '%b']); -- t +select 'foo' not like all (array['%a', '%o']); -- f +select 'foo' ilike any (array['%A', '%O']); -- t +select 'foo' ilike all (array['F%', '%O']); -- t + +-- +-- General array parser tests +-- + +-- none of the following should be accepted +select '{{1,{2}},{2,3}}'::text[]; +select '{{},{}}'::text[]; +select E'{{1,2},\\{2,3}}'::text[]; +select '{{"1 2" x},{3}}'::text[]; +select '{}}'::text[]; +select '{ }}'::text[]; +select array[]; +-- none of the above should be accepted + +-- all of the following should be accepted +select '{}'::text[]; +select '{{{1,2,3,4},{2,3,4,5}},{{3,4,5,6},{4,5,6,7}}}'::text[]; +select '{0 second ,0 second}'::interval[]; +select '{ { "," } , { 3 } }'::text[]; +select ' { { " 0 second " , 0 second } }'::text[]; +select '{ + 0 second, + @ 1 hour @ 42 minutes @ 20 seconds + }'::interval[]; +select array[]::text[]; +select '[0:1]={1.1,2.2}'::float8[]; +-- all of the above should be accepted + +-- tests for array aggregates +CREATE TEMP TABLE arraggtest ( f1 INT[], f2 TEXT[][], f3 FLOAT[]); + +INSERT INTO arraggtest (f1, f2, f3) VALUES +('{1,2,3,4}','{{grey,red},{blue,blue}}','{1.6, 0.0}'); +INSERT INTO arraggtest (f1, f2, f3) VALUES +('{1,2,3}','{{grey,red},{grey,blue}}','{1.6}'); +SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; + +INSERT INTO arraggtest (f1, f2, f3) VALUES +('{3,3,2,4,5,6}','{{white,yellow},{pink,orange}}','{2.1,3.3,1.8,1.7,1.6}'); +SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; + +INSERT INTO arraggtest (f1, f2, f3) VALUES +('{2}','{{black,red},{green,orange}}','{1.6,2.2,2.6,0.4}'); +SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; + +INSERT INTO arraggtest (f1, f2, f3) VALUES +('{4,2,6,7,8,1}','{{red},{black},{purple},{blue},{blue}}',NULL); +SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; + +INSERT INTO arraggtest (f1, f2, f3) VALUES +('{}','{{pink,white,blue,red,grey,orange}}','{2.1,1.87,1.4,2.2}'); +SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; + +-- A few simple tests for arrays of composite types + +create type comptype as (f1 int, f2 text); + +create table comptable (c1 comptype, c2 comptype[]); + +-- XXX would like to not have to specify row() construct types here ... +insert into comptable + values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]); + +-- check that implicitly named array type _comptype isn't a problem +create type _comptype as enum('fooey'); + +select * from comptable; +select c2[2].f2 from comptable; + +drop type _comptype; +drop table comptable; +drop type comptype; + +create or replace function unnest1(anyarray) +returns setof anyelement as $$ +select $1[s] from generate_subscripts($1,1) g(s); +$$ language sql immutable; + +create or replace function unnest2(anyarray) +returns setof anyelement as $$ +select $1[s1][s2] from generate_subscripts($1,1) g1(s1), + generate_subscripts($1,2) g2(s2); +$$ language sql immutable; + +select * from unnest1(array[1,2,3]); +select * from unnest2(array[[1,2,3],[4,5,6]]); + +drop function unnest1(anyarray); +drop function unnest2(anyarray); + +select array_fill(null::integer, array[3,3],array[2,2]); +select array_fill(null::integer, array[3,3]); +select array_fill(null::text, array[3,3],array[2,2]); +select array_fill(null::text, array[3,3]); +select array_fill(7, array[3,3],array[2,2]); +select array_fill(7, array[3,3]); +select array_fill('juhu'::text, array[3,3],array[2,2]); +select array_fill('juhu'::text, array[3,3]); +select a, a = '{}' as is_eq, array_dims(a) + from (select array_fill(42, array[0]) as a) ss; +select a, a = '{}' as is_eq, array_dims(a) + from (select array_fill(42, '{}') as a) ss; +select a, a = '{}' as is_eq, array_dims(a) + from (select array_fill(42, '{}', '{}') as a) ss; +-- raise exception +select array_fill(1, null, array[2,2]); +select array_fill(1, array[2,2], null); +select array_fill(1, array[2,2], '{}'); +select array_fill(1, array[3,3], array[1,1,1]); +select array_fill(1, array[1,2,null]); +select array_fill(1, array[[1,2],[3,4]]); + +select string_to_array('1|2|3', '|'); +select string_to_array('1|2|3|', '|'); +select string_to_array('1||2|3||', '||'); +select string_to_array('1|2|3', ''); +select string_to_array('', '|'); +select string_to_array('1|2|3', NULL); +select string_to_array(NULL, '|') IS NULL; +select string_to_array('abc', ''); +select string_to_array('abc', '', 'abc'); +select string_to_array('abc', ','); +select string_to_array('abc', ',', 'abc'); +select string_to_array('1,2,3,4,,6', ','); +select string_to_array('1,2,3,4,,6', ',', ''); +select string_to_array('1,2,3,4,*,6', ',', '*'); + +select v, v is null as "is null" from string_to_table('1|2|3', '|') g(v); +select v, v is null as "is null" from string_to_table('1|2|3|', '|') g(v); +select v, v is null as "is null" from string_to_table('1||2|3||', '||') g(v); +select v, v is null as "is null" from string_to_table('1|2|3', '') g(v); +select v, v is null as "is null" from string_to_table('', '|') g(v); +select v, v is null as "is null" from string_to_table('1|2|3', NULL) g(v); +select v, v is null as "is null" from string_to_table(NULL, '|') g(v); +select v, v is null as "is null" from string_to_table('abc', '') g(v); +select v, v is null as "is null" from string_to_table('abc', '', 'abc') g(v); +select v, v is null as "is null" from string_to_table('abc', ',') g(v); +select v, v is null as "is null" from string_to_table('abc', ',', 'abc') g(v); +select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',') g(v); +select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',', '') g(v); +select v, v is null as "is null" from string_to_table('1,2,3,4,*,6', ',', '*') g(v); + +select array_to_string(NULL::int4[], ',') IS NULL; +select array_to_string('{}'::int4[], ','); +select array_to_string(array[1,2,3,4,NULL,6], ','); +select array_to_string(array[1,2,3,4,NULL,6], ',', '*'); +select array_to_string(array[1,2,3,4,NULL,6], NULL); +select array_to_string(array[1,2,3,4,NULL,6], ',', NULL); + +select array_to_string(string_to_array('1|2|3', '|'), '|'); + +select array_length(array[1,2,3], 1); +select array_length(array[[1,2,3], [4,5,6]], 0); +select array_length(array[[1,2,3], [4,5,6]], 1); +select array_length(array[[1,2,3], [4,5,6]], 2); +select array_length(array[[1,2,3], [4,5,6]], 3); + +select cardinality(NULL::int[]); +select cardinality('{}'::int[]); +select cardinality(array[1,2,3]); +select cardinality('[2:4]={5,6,7}'::int[]); +select cardinality('{{1,2}}'::int[]); +select cardinality('{{1,2},{3,4},{5,6}}'::int[]); +select cardinality('{{{1,9},{5,6}},{{2,3},{3,4}}}'::int[]); + +-- array_agg(anynonarray) +select array_agg(unique1) from (select unique1 from tenk1 where unique1 < 15 order by unique1) ss; +select array_agg(ten) from (select ten from tenk1 where unique1 < 15 order by unique1) ss; +select array_agg(nullif(ten, 4)) from (select ten from tenk1 where unique1 < 15 order by unique1) ss; +select array_agg(unique1) from tenk1 where unique1 < -15; + +-- array_agg(anyarray) +select array_agg(ar) + from (values ('{1,2}'::int[]), ('{3,4}'::int[])) v(ar); +select array_agg(distinct ar order by ar desc) + from (select array[i / 2] from generate_series(1,10) a(i)) b(ar); +select array_agg(ar) + from (select array_agg(array[i, i+1, i-1]) + from generate_series(1,2) a(i)) b(ar); +select array_agg(array[i+1.2, i+1.3, i+1.4]) from generate_series(1,3) g(i); +select array_agg(array['Hello', i::text]) from generate_series(9,11) g(i); +select array_agg(array[i, nullif(i, 3), i+1]) from generate_series(1,4) g(i); +-- errors +select array_agg('{}'::int[]) from generate_series(1,2); +select array_agg(null::int[]) from generate_series(1,2); +select array_agg(ar) + from (values ('{1,2}'::int[]), ('{3}'::int[])) v(ar); + +select unnest(array[1,2,3]); +select * from unnest(array[1,2,3]); +select unnest(array[1,2,3,4.5]::float8[]); +select unnest(array[1,2,3,4.5]::numeric[]); +select unnest(array[1,2,3,null,4,null,null,5,6]); +select unnest(array[1,2,3,null,4,null,null,5,6]::text[]); +select abs(unnest(array[1,2,null,-3])); +select array_remove(array[1,2,2,3], 2); +select array_remove(array[1,2,2,3], 5); +select array_remove(array[1,NULL,NULL,3], NULL); +select array_remove(array['A','CC','D','C','RR'], 'RR'); +select array_remove(array[1.0, 2.1, 3.3], 1); +select array_remove('{{1,2,2},{1,4,3}}', 2); -- not allowed +select array_remove(array['X','X','X'], 'X') = '{}'; +select array_replace(array[1,2,5,4],5,3); +select array_replace(array[1,2,5,4],5,NULL); +select array_replace(array[1,2,NULL,4,NULL],NULL,5); +select array_replace(array['A','B','DD','B'],'B','CC'); +select array_replace(array[1,NULL,3],NULL,NULL); +select array_replace(array['AB',NULL,'CDE'],NULL,'12'); + +-- array(select array-value ...) +select array(select array[i,i/2] from generate_series(1,5) i); +select array(select array['Hello', i::text] from generate_series(9,11) i); + +-- Insert/update on a column that is array of composite + +create temp table t1 (f1 int8_tbl[]); +insert into t1 (f1[5].q1) values(42); +select * from t1; +update t1 set f1[5].q2 = 43; +select * from t1; + +-- Check that arrays of composites are safely detoasted when needed + +-- TODO(jason): uncomment this section when issue #2003 is closed. +-- create temp table src (f1 text); +-- insert into src +-- select string_agg(random()::text,'') from generate_series(1,10000); +-- create type textandtext as (c1 text, c2 text); +-- create temp table dest (f1 textandtext[]); +-- insert into dest select array[row(f1,f1)::textandtext] from src; +-- select length(md5((f1[1]).c2)) from dest; +-- delete from src; +-- select length(md5((f1[1]).c2)) from dest; +-- truncate table src; +-- drop table src; +-- select length(md5((f1[1]).c2)) from dest; +-- drop table dest; +-- drop type textandtext; + +-- Tests for polymorphic-array form of width_bucket() + +-- this exercises the varwidth and float8 code paths +SELECT + op, + width_bucket(op::numeric, ARRAY[1, 3, 5, 10.0]::numeric[]) AS wb_n1, + width_bucket(op::numeric, ARRAY[0, 5.5, 9.99]::numeric[]) AS wb_n2, + width_bucket(op::numeric, ARRAY[-6, -5, 2.0]::numeric[]) AS wb_n3, + width_bucket(op::float8, ARRAY[1, 3, 5, 10.0]::float8[]) AS wb_f1, + width_bucket(op::float8, ARRAY[0, 5.5, 9.99]::float8[]) AS wb_f2, + width_bucket(op::float8, ARRAY[-6, -5, 2.0]::float8[]) AS wb_f3 +FROM (VALUES + (-5.2), + (-0.0000000001), + (0.000000000001), + (1), + (1.99999999999999), + (2), + (2.00000000000001), + (3), + (4), + (4.5), + (5), + (5.5), + (6), + (7), + (8), + (9), + (9.99999999999999), + (10), + (10.0000000000001) +) v(op); + +-- ensure float8 path handles NaN properly +SELECT + op, + width_bucket(op, ARRAY[1, 3, 9, 'NaN', 'NaN']::float8[]) AS wb +FROM (VALUES + (-5.2::float8), + (4::float8), + (77::float8), + ('NaN'::float8) +) v(op); + +-- these exercise the generic fixed-width code path +SELECT + op, + width_bucket(op, ARRAY[1, 3, 5, 10]) AS wb_1 +FROM generate_series(0,11) as op; + +SELECT width_bucket(now(), + array['yesterday', 'today', 'tomorrow']::timestamptz[]); + +-- corner cases +SELECT width_bucket(5, ARRAY[3]); +SELECT width_bucket(5, '{}'); + +-- error cases +SELECT width_bucket('5'::text, ARRAY[3, 4]::integer[]); +SELECT width_bucket(5, ARRAY[3, 4, NULL]); +SELECT width_bucket(5, ARRAY[ARRAY[1, 2], ARRAY[3, 4]]); + +-- trim_array + +SELECT arr, trim_array(arr, 2) +FROM +(VALUES ('{1,2,3,4,5,6}'::bigint[]), + ('{1,2}'), + ('[10:16]={1,2,3,4,5,6,7}'), + ('[-15:-10]={1,2,3,4,5,6}'), + ('{{1,10},{2,20},{3,30},{4,40}}')) v(arr); + +SELECT trim_array(ARRAY[1, 2, 3], -1); -- fail +SELECT trim_array(ARRAY[1, 2, 3], 10); -- fail +SELECT trim_array(ARRAY[]::int[], 1); -- fail diff --git a/src/test/regress/sql/yb.port.boolean.sql b/src/test/regress/sql/yb.port.boolean.sql new file mode 100644 index 0000000000000..468a487baf7b9 --- /dev/null +++ b/src/test/regress/sql/yb.port.boolean.sql @@ -0,0 +1,233 @@ +-- +-- BOOLEAN +-- + +-- +-- sanity check - if this fails go insane! +-- +SELECT 1 AS one; + + +-- ******************testing built-in type bool******************** + +-- check bool input syntax + +SELECT true AS true; + +SELECT false AS false; + +SELECT bool 't' AS true; + +SELECT bool ' f ' AS false; + +SELECT bool 'true' AS true; + +SELECT bool 'test' AS error; + +SELECT bool 'false' AS false; + +SELECT bool 'foo' AS error; + +SELECT bool 'y' AS true; + +SELECT bool 'yes' AS true; + +SELECT bool 'yeah' AS error; + +SELECT bool 'n' AS false; + +SELECT bool 'no' AS false; + +SELECT bool 'nay' AS error; + +SELECT bool 'on' AS true; + +SELECT bool 'off' AS false; + +SELECT bool 'of' AS false; + +SELECT bool 'o' AS error; + +SELECT bool 'on_' AS error; + +SELECT bool 'off_' AS error; + +SELECT bool '1' AS true; + +SELECT bool '11' AS error; + +SELECT bool '0' AS false; + +SELECT bool '000' AS error; + +SELECT bool '' AS error; + +-- and, or, not in qualifications + +SELECT bool 't' or bool 'f' AS true; + +SELECT bool 't' and bool 'f' AS false; + +SELECT not bool 'f' AS true; + +SELECT bool 't' = bool 'f' AS false; + +SELECT bool 't' <> bool 'f' AS true; + +SELECT bool 't' > bool 'f' AS true; + +SELECT bool 't' >= bool 'f' AS true; + +SELECT bool 'f' < bool 't' AS true; + +SELECT bool 'f' <= bool 't' AS true; + +-- explicit casts to/from text +SELECT 'TrUe'::text::boolean AS true, 'fAlse'::text::boolean AS false; +SELECT ' true '::text::boolean AS true, + ' FALSE'::text::boolean AS false; +SELECT true::boolean::text AS true, false::boolean::text AS false; + +SELECT ' tru e '::text::boolean AS invalid; -- error +SELECT ''::text::boolean AS invalid; -- error + +CREATE TABLE BOOLTBL1 (k INT PRIMARY KEY, f1 bool); + +INSERT INTO BOOLTBL1 (k, f1) VALUES (1, bool 't'); + +INSERT INTO BOOLTBL1 (k, f1) VALUES (2, bool 'True'); + +INSERT INTO BOOLTBL1 (k, f1) VALUES (3, bool 'true'); + + +-- BOOLTBL1 should be full of true's at this point +SELECT '' AS t_3, BOOLTBL1.* FROM BOOLTBL1 ORDER BY BOOLTBL1.k; + + +SELECT '' AS t_3, BOOLTBL1.* + FROM BOOLTBL1 + WHERE f1 = bool 'true' ORDER BY BOOLTBL1.k; + + +SELECT '' AS t_3, BOOLTBL1.* + FROM BOOLTBL1 + WHERE f1 <> bool 'false' ORDER BY BOOLTBL1.k; + +SELECT '' AS zero, BOOLTBL1.* + FROM BOOLTBL1 + WHERE booleq(bool 'false', f1) ORDER BY BOOLTBL1.k; + +INSERT INTO BOOLTBL1 (k, f1) VALUES (4, bool 'f'); + +SELECT '' AS f_1, BOOLTBL1.* + FROM BOOLTBL1 + WHERE f1 = bool 'false' ORDER BY BOOLTBL1.k; + + +CREATE TABLE BOOLTBL2 (k INT PRIMARY KEY, f1 bool); + +INSERT INTO BOOLTBL2 (k, f1) VALUES (1, bool 'f'); + +INSERT INTO BOOLTBL2 (k, f1) VALUES (2, bool 'false'); + +INSERT INTO BOOLTBL2 (k, f1) VALUES (3, bool 'False'); + +INSERT INTO BOOLTBL2 (k, f1) VALUES (4, bool 'FALSE'); + +-- This is now an invalid expression +-- For pre-v6.3 this evaluated to false - thomas 1997-10-23 +INSERT INTO BOOLTBL2 (k, f1) + VALUES (5, bool 'XXX'); + +-- BOOLTBL2 should be full of false's at this point +SELECT '' AS f_4, BOOLTBL2.* FROM BOOLTBL2 ORDER BY BOOLTBL2.k; + + +SELECT '' AS tf_12, BOOLTBL1.*, BOOLTBL2.* + FROM BOOLTBL1, BOOLTBL2 + WHERE BOOLTBL2.f1 <> BOOLTBL1.f1 ORDER BY BOOLTBL1.k, BOOLTBL2.k; + + +SELECT '' AS tf_12, BOOLTBL1.*, BOOLTBL2.* + FROM BOOLTBL1, BOOLTBL2 + WHERE boolne(BOOLTBL2.f1,BOOLTBL1.f1) ORDER BY BOOLTBL1.k, BOOLTBL2.k;; + + +SELECT '' AS ff_4, BOOLTBL1.*, BOOLTBL2.* + FROM BOOLTBL1, BOOLTBL2 + WHERE BOOLTBL2.f1 = BOOLTBL1.f1 and BOOLTBL1.f1 = bool 'false' ORDER BY BOOLTBL1.k, BOOLTBL2.k; + + +SELECT '' AS tf_12_ff_4, BOOLTBL1.*, BOOLTBL2.* + FROM BOOLTBL1, BOOLTBL2 + WHERE BOOLTBL2.f1 = BOOLTBL1.f1 or BOOLTBL1.f1 = bool 'true' + ORDER BY BOOLTBL1.f1, BOOLTBL2.f1, BOOLTBL1.k, BOOLTBL2.k; + +-- +-- SQL syntax +-- Try all combinations to ensure that we get nothing when we expect nothing +-- - thomas 2000-01-04 +-- + +SELECT '' AS "True", f1 + FROM BOOLTBL1 + WHERE f1 IS TRUE ORDER BY BOOLTBL1.k; + +SELECT '' AS "Not False", f1 + FROM BOOLTBL1 + WHERE f1 IS NOT FALSE ORDER BY BOOLTBL1.k; + +SELECT '' AS "False", f1 + FROM BOOLTBL1 + WHERE f1 IS FALSE ORDER BY BOOLTBL1.k; + +SELECT '' AS "Not True", f1 + FROM BOOLTBL1 + WHERE f1 IS NOT TRUE ORDER BY BOOLTBL1.k; + +SELECT '' AS "True", f1 + FROM BOOLTBL2 + WHERE f1 IS TRUE ORDER BY BOOLTBL2.k; + +SELECT '' AS "Not False", f1 + FROM BOOLTBL2 + WHERE f1 IS NOT FALSE ORDER BY BOOLTBL2.k; + +SELECT '' AS "False", f1 + FROM BOOLTBL2 + WHERE f1 IS FALSE ORDER BY BOOLTBL2.k; + +SELECT '' AS "Not True", f1 + FROM BOOLTBL2 + WHERE f1 IS NOT TRUE ORDER BY BOOLTBL2.k; + +-- +-- Tests for BooleanTest +-- +CREATE TABLE BOOLTBL3 (d text, b bool, o int PRIMARY KEY); +INSERT INTO BOOLTBL3 (d, b, o) VALUES ('true', true, 1); +INSERT INTO BOOLTBL3 (d, b, o) VALUES ('false', false, 2); +INSERT INTO BOOLTBL3 (d, b, o) VALUES ('null', null, 3); + +SELECT + d, + b IS TRUE AS istrue, + b IS NOT TRUE AS isnottrue, + b IS FALSE AS isfalse, + b IS NOT FALSE AS isnotfalse, + b IS UNKNOWN AS isunknown, + b IS NOT UNKNOWN AS isnotunknown +FROM booltbl3 ORDER BY o; + +-- +-- Clean up +-- Many tables are retained by the regression test, but these do not seem +-- particularly useful so just get rid of them for now. +-- - thomas 1997-11-30 +-- + +DROP TABLE BOOLTBL1; + +DROP TABLE BOOLTBL2; + +DROP TABLE BOOLTBL3; diff --git a/src/test/regress/sql/yb.port.box.sql b/src/test/regress/sql/yb.port.box.sql new file mode 100644 index 0000000000000..ceae58fc02fc0 --- /dev/null +++ b/src/test/regress/sql/yb.port.box.sql @@ -0,0 +1,283 @@ +-- +-- BOX +-- + +-- +-- box logic +-- o +-- 3 o--|X +-- | o| +-- 2 +-+-+ | +-- | | | | +-- 1 | o-+-o +-- | | +-- 0 +---+ +-- +-- 0 1 2 3 +-- + +-- boxes are specified by two points, given by four floats x1,y1,x2,y2 + + +CREATE TABLE BOX_TBL (f1 box); + +INSERT INTO BOX_TBL (f1) VALUES ('(2.0,2.0,0.0,0.0)'); + +INSERT INTO BOX_TBL (f1) VALUES ('(1.0,1.0,3.0,3.0)'); + +INSERT INTO BOX_TBL (f1) VALUES ('((-8, 2), (-2, -10))'); + + +-- degenerate cases where the box is a line or a point +-- note that lines and points boxes all have zero area +INSERT INTO BOX_TBL (f1) VALUES ('(2.5, 2.5, 2.5,3.5)'); + +INSERT INTO BOX_TBL (f1) VALUES ('(3.0, 3.0,3.0,3.0)'); + +-- badly formatted box inputs +INSERT INTO BOX_TBL (f1) VALUES ('(2.3, 4.5)'); + +INSERT INTO BOX_TBL (f1) VALUES ('[1, 2, 3, 4)'); + +INSERT INTO BOX_TBL (f1) VALUES ('(1, 2, 3, 4]'); + +INSERT INTO BOX_TBL (f1) VALUES ('(1, 2, 3, 4) x'); + +INSERT INTO BOX_TBL (f1) VALUES ('asdfasdf(ad'); + + +SELECT * FROM BOX_TBL; + +SELECT b.*, area(b.f1) as barea + FROM BOX_TBL b; + +-- overlap +SELECT b.f1 + FROM BOX_TBL b + WHERE b.f1 && box '(2.5,2.5,1.0,1.0)'; + +-- left-or-overlap (x only) +SELECT b1.* + FROM BOX_TBL b1 + WHERE b1.f1 &< box '(2.0,2.0,2.5,2.5)'; + +-- right-or-overlap (x only) +SELECT b1.* + FROM BOX_TBL b1 + WHERE b1.f1 &> box '(2.0,2.0,2.5,2.5)'; + +-- left of +SELECT b.f1 + FROM BOX_TBL b + WHERE b.f1 << box '(3.0,3.0,5.0,5.0)'; + +-- area <= +SELECT b.f1 + FROM BOX_TBL b + WHERE b.f1 <= box '(3.0,3.0,5.0,5.0)'; + +-- area < +SELECT b.f1 + FROM BOX_TBL b + WHERE b.f1 < box '(3.0,3.0,5.0,5.0)'; + +-- area = +SELECT b.f1 + FROM BOX_TBL b + WHERE b.f1 = box '(3.0,3.0,5.0,5.0)'; + +-- area > +SELECT b.f1 + FROM BOX_TBL b -- zero area + WHERE b.f1 > box '(3.5,3.0,4.5,3.0)'; + +-- area >= +SELECT b.f1 + FROM BOX_TBL b -- zero area + WHERE b.f1 >= box '(3.5,3.0,4.5,3.0)'; + +-- right of +SELECT b.f1 + FROM BOX_TBL b + WHERE box '(3.0,3.0,5.0,5.0)' >> b.f1; + +-- contained in +SELECT b.f1 + FROM BOX_TBL b + WHERE b.f1 <@ box '(0,0,3,3)'; + +-- contains +SELECT b.f1 + FROM BOX_TBL b + WHERE box '(0,0,3,3)' @> b.f1; + +-- box equality +SELECT b.f1 + FROM BOX_TBL b + WHERE box '(1,1,3,3)' ~= b.f1; + +-- center of box, left unary operator +SELECT @@(b1.f1) AS p + FROM BOX_TBL b1; + +-- wholly-contained +SELECT b1.*, b2.* + FROM BOX_TBL b1, BOX_TBL b2 + WHERE b1.f1 @> b2.f1 and not b1.f1 ~= b2.f1; + +SELECT height(f1), width(f1) FROM BOX_TBL; + +-- +-- Test the SP-GiST index +-- + +CREATE TEMPORARY TABLE box_temp (f1 box); + +INSERT INTO box_temp + SELECT box(point(i, i), point(i * 2, i * 2)) + FROM generate_series(1, 50) AS i; + +CREATE INDEX box_spgist ON box_temp USING spgist (f1); + +INSERT INTO box_temp + VALUES (NULL), + ('(0,0)(0,100)'), + ('(-3,4.3333333333)(40,1)'), + ('(0,100)(0,infinity)'), + ('(-infinity,0)(0,infinity)'), + ('(-infinity,-infinity)(infinity,infinity)'); + +SET enable_seqscan = false; + +SELECT * FROM box_temp WHERE f1 << '(10,20),(30,40)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 << '(10,20),(30,40)'; + +SELECT * FROM box_temp WHERE f1 &< '(10,4.333334),(5,100)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 &< '(10,4.333334),(5,100)'; + +SELECT * FROM box_temp WHERE f1 && '(15,20),(25,30)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 && '(15,20),(25,30)'; + +SELECT * FROM box_temp WHERE f1 &> '(40,30),(45,50)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 &> '(40,30),(45,50)'; + +SELECT * FROM box_temp WHERE f1 >> '(30,40),(40,30)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 >> '(30,40),(40,30)'; + +SELECT * FROM box_temp WHERE f1 <<| '(10,4.33334),(5,100)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 <<| '(10,4.33334),(5,100)'; + +SELECT * FROM box_temp WHERE f1 &<| '(10,4.3333334),(5,1)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 &<| '(10,4.3333334),(5,1)'; + +SELECT * FROM box_temp WHERE f1 |&> '(49.99,49.99),(49.99,49.99)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 |&> '(49.99,49.99),(49.99,49.99)'; + +SELECT * FROM box_temp WHERE f1 |>> '(37,38),(39,40)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 |>> '(37,38),(39,40)'; + +SELECT * FROM box_temp WHERE f1 @> '(10,11),(15,16)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 @> '(10,11),(15,15)'; + +SELECT * FROM box_temp WHERE f1 <@ '(10,15),(30,35)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 <@ '(10,15),(30,35)'; + +SELECT * FROM box_temp WHERE f1 ~= '(20,20),(40,40)'; +EXPLAIN (COSTS OFF) SELECT * FROM box_temp WHERE f1 ~= '(20,20),(40,40)'; + +RESET enable_seqscan; + +DROP INDEX box_spgist; + +-- +-- Test the SP-GiST index on the larger volume of data +-- +CREATE TABLE quad_box_tbl (id int, b box); + +INSERT INTO quad_box_tbl + SELECT (x - 1) * 100 + y, box(point(x * 10, y * 10), point(x * 10 + 5, y * 10 + 5)) + FROM generate_series(1, 100) x, + generate_series(1, 100) y; + +-- insert repeating data to test allTheSame +INSERT INTO quad_box_tbl + SELECT i, '((200, 300),(210, 310))' + FROM generate_series(10001, 11000) AS i; + +INSERT INTO quad_box_tbl +VALUES + (11001, NULL), + (11002, NULL), + (11003, '((-infinity,-infinity),(infinity,infinity))'), + (11004, '((-infinity,100),(-infinity,500))'), + (11005, '((-infinity,-infinity),(700,infinity))'); + +CREATE INDEX quad_box_tbl_idx ON quad_box_tbl USING spgist(b); + +-- get reference results for ORDER BY distance from seq scan +SET enable_seqscan = ON; +SET enable_indexscan = OFF; +SET enable_bitmapscan = OFF; + +CREATE TABLE quad_box_tbl_ord_seq1 AS +SELECT rank() OVER (ORDER BY b <-> point '123,456') n, b <-> point '123,456' dist, id +FROM quad_box_tbl; + +CREATE TABLE quad_box_tbl_ord_seq2 AS +SELECT rank() OVER (ORDER BY b <-> point '123,456') n, b <-> point '123,456' dist, id +FROM quad_box_tbl WHERE b <@ box '((200,300),(500,600))'; + +SET enable_seqscan = OFF; +SET enable_indexscan = ON; +SET enable_bitmapscan = ON; + +SELECT count(*) FROM quad_box_tbl WHERE b << box '((100,200),(300,500))'; +SELECT count(*) FROM quad_box_tbl WHERE b &< box '((100,200),(300,500))'; +SELECT count(*) FROM quad_box_tbl WHERE b && box '((100,200),(300,500))'; +SELECT count(*) FROM quad_box_tbl WHERE b &> box '((100,200),(300,500))'; +SELECT count(*) FROM quad_box_tbl WHERE b >> box '((100,200),(300,500))'; +SELECT count(*) FROM quad_box_tbl WHERE b >> box '((100,200),(300,500))'; +SELECT count(*) FROM quad_box_tbl WHERE b <<| box '((100,200),(300,500))'; +SELECT count(*) FROM quad_box_tbl WHERE b &<| box '((100,200),(300,500))'; +SELECT count(*) FROM quad_box_tbl WHERE b |&> box '((100,200),(300,500))'; +SELECT count(*) FROM quad_box_tbl WHERE b |>> box '((100,200),(300,500))'; +SELECT count(*) FROM quad_box_tbl WHERE b @> box '((201,301),(202,303))'; +SELECT count(*) FROM quad_box_tbl WHERE b <@ box '((100,200),(300,500))'; +SELECT count(*) FROM quad_box_tbl WHERE b ~= box '((200,300),(205,305))'; + +-- test ORDER BY distance +SET enable_indexscan = ON; +SET enable_bitmapscan = OFF; + +EXPLAIN (COSTS OFF) +SELECT rank() OVER (ORDER BY b <-> point '123,456') n, b <-> point '123,456' dist, id +FROM quad_box_tbl; + +CREATE TEMP TABLE quad_box_tbl_ord_idx1 AS +SELECT rank() OVER (ORDER BY b <-> point '123,456') n, b <-> point '123,456' dist, id +FROM quad_box_tbl; + +SELECT * +FROM quad_box_tbl_ord_seq1 seq FULL JOIN quad_box_tbl_ord_idx1 idx + ON seq.n = idx.n AND seq.id = idx.id AND + (seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL) +WHERE seq.id IS NULL OR idx.id IS NULL; + + +EXPLAIN (COSTS OFF) +SELECT rank() OVER (ORDER BY b <-> point '123,456') n, b <-> point '123,456' dist, id +FROM quad_box_tbl WHERE b <@ box '((200,300),(500,600))'; + +CREATE TEMP TABLE quad_box_tbl_ord_idx2 AS +SELECT rank() OVER (ORDER BY b <-> point '123,456') n, b <-> point '123,456' dist, id +FROM quad_box_tbl WHERE b <@ box '((200,300),(500,600))'; + +SELECT * +FROM quad_box_tbl_ord_seq2 seq FULL JOIN quad_box_tbl_ord_idx2 idx + ON seq.n = idx.n AND seq.id = idx.id AND + (seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL) +WHERE seq.id IS NULL OR idx.id IS NULL; + +RESET enable_seqscan; +RESET enable_indexscan; +RESET enable_bitmapscan; diff --git a/src/test/regress/sql/yb.port.case.sql b/src/test/regress/sql/yb.port.case.sql new file mode 100644 index 0000000000000..67181a32affb0 --- /dev/null +++ b/src/test/regress/sql/yb.port.case.sql @@ -0,0 +1,268 @@ +-- +-- CASE +-- Test the case statement +-- + +CREATE TABLE CASE_TBL ( + i integer, + f double precision +); + +CREATE TABLE CASE2_TBL ( + i integer, + j integer +); + +INSERT INTO CASE_TBL VALUES (1, 10.1); +INSERT INTO CASE_TBL VALUES (2, 20.2); +INSERT INTO CASE_TBL VALUES (3, -30.3); +INSERT INTO CASE_TBL VALUES (4, NULL); + +INSERT INTO CASE2_TBL VALUES (1, -1); +INSERT INTO CASE2_TBL VALUES (2, -2); +INSERT INTO CASE2_TBL VALUES (3, -3); +INSERT INTO CASE2_TBL VALUES (2, -4); +INSERT INTO CASE2_TBL VALUES (1, NULL); +INSERT INTO CASE2_TBL VALUES (NULL, -6); + +-- +-- Simplest examples without tables +-- + +SELECT '3' AS "One", + CASE + WHEN 1 < 2 THEN 3 + END AS "Simple WHEN"; + +SELECT '' AS "One", + CASE + WHEN 1 > 2 THEN 3 + END AS "Simple default"; + +SELECT '3' AS "One", + CASE + WHEN 1 < 2 THEN 3 + ELSE 4 + END AS "Simple ELSE"; + +SELECT '4' AS "One", + CASE + WHEN 1 > 2 THEN 3 + ELSE 4 + END AS "ELSE default"; + +SELECT '6' AS "One", + CASE + WHEN 1 > 2 THEN 3 + WHEN 4 < 5 THEN 6 + ELSE 7 + END AS "Two WHEN with default"; + + +SELECT '7' AS "None", + CASE WHEN random() < 0 THEN 1 + END AS "NULL on no matches"; + +-- Constant-expression folding shouldn't evaluate unreachable subexpressions +SELECT CASE WHEN 1=0 THEN 1/0 WHEN 1=1 THEN 1 ELSE 2/0 END; +SELECT CASE 1 WHEN 0 THEN 1/0 WHEN 1 THEN 1 ELSE 2/0 END; + +-- However we do not currently suppress folding of potentially +-- reachable subexpressions +SELECT CASE WHEN i > 100 THEN 1/0 ELSE 0 END FROM case_tbl; + +-- Test for cases involving untyped literals in test expression +SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END; + +-- +-- Examples of targets involving tables +-- + +SELECT + CASE + WHEN i >= 3 THEN i + END AS ">= 3 or Null" + FROM CASE_TBL; + +SELECT + CASE WHEN i >= 3 THEN (i + i) + ELSE i + END AS "Simplest Math" + FROM CASE_TBL; + +SELECT i AS "Value", + CASE WHEN (i < 0) THEN 'small' + WHEN (i = 0) THEN 'zero' + WHEN (i = 1) THEN 'one' + WHEN (i = 2) THEN 'two' + ELSE 'big' + END AS "Category" + FROM CASE_TBL; + +SELECT + CASE WHEN ((i < 0) or (i < 0)) THEN 'small' + WHEN ((i = 0) or (i = 0)) THEN 'zero' + WHEN ((i = 1) or (i = 1)) THEN 'one' + WHEN ((i = 2) or (i = 2)) THEN 'two' + ELSE 'big' + END AS "Category" + FROM CASE_TBL; + +-- +-- Examples of qualifications involving tables +-- + +-- +-- NULLIF() and COALESCE() +-- Shorthand forms for typical CASE constructs +-- defined in the SQL standard. +-- + +SELECT * FROM CASE_TBL WHERE COALESCE(f,i) = 4; + +SELECT * FROM CASE_TBL WHERE NULLIF(f,i) = 2; + +ANALYZE CASE_TBL, CASE2_TBL; -- YB: add ANALYZE because case2_tbl should be leading in the joins below +SELECT COALESCE(a.f, b.i, b.j) + FROM CASE_TBL a, CASE2_TBL b; + +SELECT * + FROM CASE_TBL a, CASE2_TBL b + WHERE COALESCE(a.f, b.i, b.j) = 2; + +SELECT NULLIF(a.i,b.i) AS "NULLIF(a.i,b.i)", + NULLIF(b.i, 4) AS "NULLIF(b.i,4)" + FROM CASE_TBL a, CASE2_TBL b; + +SELECT * + FROM CASE_TBL a, CASE2_TBL b + WHERE COALESCE(f,b.i) = 2; + +-- Tests for constant subexpression simplification + +explain (costs off) +SELECT * FROM CASE_TBL WHERE NULLIF(1, 2) = 2; + +explain (costs off) +SELECT * FROM CASE_TBL WHERE NULLIF(1, 1) IS NOT NULL; + +explain (costs off) +SELECT * FROM CASE_TBL WHERE NULLIF(1, null) = 2; + +-- +-- Examples of updates involving tables +-- + +UPDATE CASE_TBL + SET i = CASE WHEN i >= 3 THEN (- i) + ELSE (2 * i) END; + +SELECT * FROM CASE_TBL; + +UPDATE CASE_TBL + SET i = CASE WHEN i >= 2 THEN (2 * i) + ELSE (3 * i) END; + +SELECT * FROM CASE_TBL; + +WITH ybtmp AS (DELETE FROM CASE_TBL WHERE f = 10.1 RETURNING *) + INSERT INTO CASE_TBL SELECT * FROM ybtmp; -- YB: imitate PG ctid reallocation for the following update +UPDATE CASE_TBL + SET i = CASE WHEN b.i >= 2 THEN (2 * j) + ELSE (3 * j) END + FROM CASE2_TBL b + WHERE j = -CASE_TBL.i; + +SELECT * FROM CASE_TBL; + +-- +-- Nested CASE expressions +-- + +-- This test exercises a bug caused by aliasing econtext->caseValue_isNull +-- with the isNull argument of the inner CASE's CaseExpr evaluation. After +-- evaluating the vol(null) expression in the inner CASE's second WHEN-clause, +-- the isNull flag for the case test value incorrectly became true, causing +-- the third WHEN-clause not to match. The volatile function calls are needed +-- to prevent constant-folding in the planner, which would hide the bug. + +-- Wrap this in a single transaction so the transient '=' operator doesn't +-- cause problems in concurrent sessions +BEGIN; + +CREATE FUNCTION vol(text) returns text as + 'begin return $1; end' language plpgsql volatile; + +SELECT CASE + (CASE vol('bar') + WHEN 'foo' THEN 'it was foo!' + WHEN vol(null) THEN 'null input' + WHEN 'bar' THEN 'it was bar!' END + ) + WHEN 'it was foo!' THEN 'foo recognized' + WHEN 'it was bar!' THEN 'bar recognized' + ELSE 'unrecognized' END; + +-- In this case, we can't inline the SQL function without confusing things. +CREATE DOMAIN foodomain AS text; + +CREATE FUNCTION volfoo(text) returns foodomain as + 'begin return $1::foodomain; end' language plpgsql volatile; + +CREATE FUNCTION inline_eq(foodomain, foodomain) returns boolean as + 'SELECT CASE $2::text WHEN $1::text THEN true ELSE false END' language sql; + +CREATE OPERATOR = (procedure = inline_eq, + leftarg = foodomain, rightarg = foodomain); + +SELECT CASE volfoo('bar') WHEN 'foo'::foodomain THEN 'is foo' ELSE 'is not foo' END; + +ROLLBACK; + +-- Test multiple evaluation of a CASE arg that is a read/write object (#14472) +-- Wrap this in a single transaction so the transient '=' operator doesn't +-- cause problems in concurrent sessions +BEGIN; + +CREATE DOMAIN arrdomain AS int[]; + +CREATE FUNCTION make_ad(int,int) returns arrdomain as + 'declare x arrdomain; + begin + x := array[$1,$2]; + return x; + end' language plpgsql volatile; + +CREATE FUNCTION ad_eq(arrdomain, arrdomain) returns boolean as + 'begin return array_eq($1, $2); end' language plpgsql; + +CREATE OPERATOR = (procedure = ad_eq, + leftarg = arrdomain, rightarg = arrdomain); + +SELECT CASE make_ad(1,2) + WHEN array[2,4]::arrdomain THEN 'wrong' + WHEN array[2,5]::arrdomain THEN 'still wrong' + WHEN array[1,2]::arrdomain THEN 'right' + END; + +ROLLBACK; + +-- Test interaction of CASE with ArrayCoerceExpr (bug #15471) +BEGIN; + +CREATE TYPE casetestenum AS ENUM ('e', 'f', 'g'); + +SELECT + CASE 'foo'::text + WHEN 'foo' THEN ARRAY['a', 'b', 'c', 'd'] || enum_range(NULL::casetestenum)::text[] + ELSE ARRAY['x', 'y'] + END; + +ROLLBACK; + +-- +-- Clean up +-- + +DROP TABLE CASE_TBL; +DROP TABLE CASE2_TBL; diff --git a/src/test/regress/sql/yb.port.collate.icu.utf8.sql b/src/test/regress/sql/yb.port.collate.icu.utf8.sql new file mode 100644 index 0000000000000..44ecd0cb1163a --- /dev/null +++ b/src/test/regress/sql/yb.port.collate.icu.utf8.sql @@ -0,0 +1,492 @@ +/* + * This test is for ICU collations. + */ + +/* skip test if not UTF8 server encoding or no ICU collations installed */ +SELECT getdatabaseencoding() <> 'UTF8' OR + (SELECT count(*) FROM pg_collation WHERE collprovider = 'i') = 0 + AS skip_test \gset +\if :skip_test +\quit +\endif + +SET client_encoding TO UTF8; + +CREATE SCHEMA collate_tests; +SET search_path = collate_tests; + + +CREATE TABLE collate_test1 ( + a int, + b text COLLATE "en-x-icu" NOT NULL +); + +\d collate_test1 + +CREATE TABLE collate_test_fail ( + a int, + b text COLLATE "ja_JP.eucjp-x-icu" +); + +CREATE TABLE collate_test_fail ( + a int, + b text COLLATE "foo-x-icu" +); + +CREATE TABLE collate_test_fail ( + a int COLLATE "en-x-icu", + b text +); + +CREATE TABLE collate_test_like ( + LIKE collate_test1 +); + +\d collate_test_like + +CREATE TABLE collate_test2 ( + a int, + b text COLLATE "sv-x-icu" +); + +CREATE TABLE collate_test3 ( + a int, + b text COLLATE "C" +); + +INSERT INTO collate_test1 VALUES (1, 'abc'), (2, 'äbc'), (3, 'bbc'), (4, 'ABC'); +INSERT INTO collate_test2 SELECT * FROM collate_test1; +INSERT INTO collate_test3 SELECT * FROM collate_test1; + +SELECT * FROM collate_test1 WHERE b >= 'bbc'; +SELECT * FROM collate_test2 WHERE b >= 'bbc'; +SELECT * FROM collate_test3 WHERE b >= 'bbc'; +SELECT * FROM collate_test3 WHERE b >= 'BBC'; + +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc'; +SELECT * FROM collate_test1 WHERE b >= 'bbc' COLLATE "C"; +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "C"; +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "en-x-icu"; + + +CREATE DOMAIN testdomain_sv AS text COLLATE "sv-x-icu"; +CREATE DOMAIN testdomain_i AS int COLLATE "sv-x-icu"; -- fails +CREATE TABLE collate_test4 ( + a int, + b testdomain_sv +); +INSERT INTO collate_test4 SELECT * FROM collate_test1; +SELECT a, b FROM collate_test4 ORDER BY b; + +CREATE TABLE collate_test5 ( + a int, + b testdomain_sv COLLATE "en-x-icu" +); +INSERT INTO collate_test5 SELECT * FROM collate_test1; +SELECT a, b FROM collate_test5 ORDER BY b; + + +SELECT a, b FROM collate_test1 ORDER BY b; +SELECT a, b FROM collate_test2 ORDER BY b; +SELECT a, b FROM collate_test3 ORDER BY b; + +SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C"; + +-- star expansion +SELECT * FROM collate_test1 ORDER BY b; +SELECT * FROM collate_test2 ORDER BY b; +SELECT * FROM collate_test3 ORDER BY b; + +-- constant expression folding +SELECT 'bbc' COLLATE "en-x-icu" > 'äbc' COLLATE "en-x-icu" AS "true"; +SELECT 'bbc' COLLATE "sv-x-icu" > 'äbc' COLLATE "sv-x-icu" AS "false"; + +-- upper/lower + +CREATE TABLE collate_test10 ( + a int, + x text COLLATE "en-x-icu", + y text COLLATE "tr-x-icu" +); + +INSERT INTO collate_test10 VALUES (1, 'hij', 'hij'), (2, 'HIJ', 'HIJ'); + +SELECT a, lower(x), lower(y), upper(x), upper(y), initcap(x), initcap(y) FROM collate_test10; +SELECT a, lower(x COLLATE "C"), lower(y COLLATE "C") FROM collate_test10; + +SELECT a, x, y FROM collate_test10 ORDER BY lower(y), a; + +-- LIKE/ILIKE + +SELECT * FROM collate_test1 WHERE b LIKE 'abc'; +SELECT * FROM collate_test1 WHERE b LIKE 'abc%'; +SELECT * FROM collate_test1 WHERE b LIKE '%bc%'; +SELECT * FROM collate_test1 WHERE b ILIKE 'abc'; +SELECT * FROM collate_test1 WHERE b ILIKE 'abc%'; +SELECT * FROM collate_test1 WHERE b ILIKE '%bc%'; + +SELECT 'Türkiye' COLLATE "en-x-icu" ILIKE '%KI%' AS "true"; +SELECT 'Türkiye' COLLATE "tr-x-icu" ILIKE '%KI%' AS "false"; + +SELECT 'bıt' ILIKE 'BIT' COLLATE "en-x-icu" AS "false"; +SELECT 'bıt' ILIKE 'BIT' COLLATE "tr-x-icu" AS "true"; + +-- The following actually exercises the selectivity estimation for ILIKE. +SELECT relname FROM pg_class WHERE relname ILIKE 'abc%'; + +-- regular expressions + +SELECT * FROM collate_test1 WHERE b ~ '^abc$'; +SELECT * FROM collate_test1 WHERE b ~ '^abc'; +SELECT * FROM collate_test1 WHERE b ~ 'bc'; +SELECT * FROM collate_test1 WHERE b ~* '^abc$'; +SELECT * FROM collate_test1 WHERE b ~* '^abc'; +SELECT * FROM collate_test1 WHERE b ~* 'bc'; + +CREATE TABLE collate_test6 ( + a int, + b text COLLATE "en-x-icu" +); +INSERT INTO collate_test6 VALUES (1, 'abc'), (2, 'ABC'), (3, '123'), (4, 'ab1'), + (5, 'a1!'), (6, 'a c'), (7, '!.;'), (8, ' '), + (9, 'äbç'), (10, 'ÄBÇ'); +SELECT b, + b ~ '^[[:alpha:]]+$' AS is_alpha, + b ~ '^[[:upper:]]+$' AS is_upper, + b ~ '^[[:lower:]]+$' AS is_lower, + b ~ '^[[:digit:]]+$' AS is_digit, + b ~ '^[[:alnum:]]+$' AS is_alnum, + b ~ '^[[:graph:]]+$' AS is_graph, + b ~ '^[[:print:]]+$' AS is_print, + b ~ '^[[:punct:]]+$' AS is_punct, + b ~ '^[[:space:]]+$' AS is_space +FROM collate_test6; + +SELECT 'Türkiye' COLLATE "en-x-icu" ~* 'KI' AS "true"; +SELECT 'Türkiye' COLLATE "tr-x-icu" ~* 'KI' AS "true"; -- true with ICU + +SELECT 'bıt' ~* 'BIT' COLLATE "en-x-icu" AS "false"; +SELECT 'bıt' ~* 'BIT' COLLATE "tr-x-icu" AS "false"; -- false with ICU + +-- The following actually exercises the selectivity estimation for ~*. +SELECT relname FROM pg_class WHERE relname ~* '^abc'; + + +/* not run by default because it requires tr_TR system locale +-- to_char + +SET lc_time TO 'tr_TR'; +SELECT to_char(date '2010-04-01', 'DD TMMON YYYY'); +SELECT to_char(date '2010-04-01', 'DD TMMON YYYY' COLLATE "tr-x-icu"); +*/ + + +-- backwards parsing + +CREATE VIEW collview1 AS SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc'; +CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C"; +CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10; + +SELECT table_name, view_definition FROM information_schema.views + WHERE table_name LIKE 'collview%' ORDER BY 1; + + +-- collation propagation in various expression types + +SELECT a, coalesce(b, 'foo') FROM collate_test1 ORDER BY 2; +SELECT a, coalesce(b, 'foo') FROM collate_test2 ORDER BY 2; +SELECT a, coalesce(b, 'foo') FROM collate_test3 ORDER BY 2; +SELECT a, lower(coalesce(x, 'foo')), lower(coalesce(y, 'foo')) FROM collate_test10; + +SELECT a, b, greatest(b, 'CCC') FROM collate_test1 ORDER BY 3; +SELECT a, b, greatest(b, 'CCC') FROM collate_test2 ORDER BY 3; +SELECT a, b, greatest(b, 'CCC') FROM collate_test3 ORDER BY 3; +SELECT a, x, y, lower(greatest(x, 'foo')), lower(greatest(y, 'foo')) FROM collate_test10; + +SELECT a, nullif(b, 'abc') FROM collate_test1 ORDER BY 2; +SELECT a, nullif(b, 'abc') FROM collate_test2 ORDER BY 2; +SELECT a, nullif(b, 'abc') FROM collate_test3 ORDER BY 2; +SELECT a, lower(nullif(x, 'foo')), lower(nullif(y, 'foo')) FROM collate_test10; + +SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test1 ORDER BY 2; +SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test2 ORDER BY 2; +SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test3 ORDER BY 2; + +CREATE DOMAIN testdomain AS text; +SELECT a, b::testdomain FROM collate_test1 ORDER BY 2; +SELECT a, b::testdomain FROM collate_test2 ORDER BY 2; +SELECT a, b::testdomain FROM collate_test3 ORDER BY 2; +SELECT a, b::testdomain_sv FROM collate_test3 ORDER BY 2; +SELECT a, lower(x::testdomain), lower(y::testdomain) FROM collate_test10; + +SELECT min(b), max(b) FROM collate_test1; +SELECT min(b), max(b) FROM collate_test2; +SELECT min(b), max(b) FROM collate_test3; + +SELECT array_agg(b ORDER BY b) FROM collate_test1; +SELECT array_agg(b ORDER BY b) FROM collate_test2; +SELECT array_agg(b ORDER BY b) FROM collate_test3; + +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test1 ORDER BY 2; +SELECT a, b FROM collate_test2 UNION SELECT a, b FROM collate_test2 ORDER BY 2; +SELECT a, b FROM collate_test3 WHERE a < 4 INTERSECT SELECT a, b FROM collate_test3 WHERE a > 1 ORDER BY 2; +SELECT a, b FROM collate_test3 EXCEPT SELECT a, b FROM collate_test3 WHERE a < 2 ORDER BY 2; + +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3 ORDER BY 2; -- fail +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3; -- ok +SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collate_test3 ORDER BY 2; -- fail +SELECT a, b COLLATE "C" FROM collate_test1 UNION SELECT a, b FROM collate_test3 ORDER BY 2; -- ok +SELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail +SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail + +CREATE TABLE test_u AS SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3; -- fail + +-- ideally this would be a parse-time error, but for now it must be run-time: +select x < y from collate_test10; -- fail +select x || y from collate_test10; -- ok, because || is not collation aware +select x, y from collate_test10 order by x || y; -- not so ok + +-- collation mismatch between recursive and non-recursive term +WITH RECURSIVE foo(x) AS + (SELECT x FROM (VALUES('a' COLLATE "en-x-icu"),('b')) t(x) + UNION ALL + SELECT (x || 'c') COLLATE "de-x-icu" FROM foo WHERE length(x) < 10) +SELECT * FROM foo; + + +-- casting + +SELECT CAST('42' AS text COLLATE "C"); + +SELECT a, CAST(b AS varchar) FROM collate_test1 ORDER BY 2; +SELECT a, CAST(b AS varchar) FROM collate_test2 ORDER BY 2; +SELECT a, CAST(b AS varchar) FROM collate_test3 ORDER BY 2; + + +-- propagation of collation in SQL functions (inlined and non-inlined cases) +-- and plpgsql functions too + +CREATE FUNCTION mylt (text, text) RETURNS boolean LANGUAGE sql + AS $$ select $1 < $2 $$; + +CREATE FUNCTION mylt_noninline (text, text) RETURNS boolean LANGUAGE sql + AS $$ select $1 < $2 limit 1 $$; + +CREATE FUNCTION mylt_plpgsql (text, text) RETURNS boolean LANGUAGE plpgsql + AS $$ begin return $1 < $2; end $$; + +SELECT a.b AS a, b.b AS b, a.b < b.b AS lt, + mylt(a.b, b.b), mylt_noninline(a.b, b.b), mylt_plpgsql(a.b, b.b) +FROM collate_test1 a, collate_test1 b +ORDER BY a.b, b.b; + +SELECT a.b AS a, b.b AS b, a.b < b.b COLLATE "C" AS lt, + mylt(a.b, b.b COLLATE "C"), mylt_noninline(a.b, b.b COLLATE "C"), + mylt_plpgsql(a.b, b.b COLLATE "C") +FROM collate_test1 a, collate_test1 b +ORDER BY a.b, b.b; + + +-- collation override in plpgsql + +CREATE FUNCTION mylt2 (x text, y text) RETURNS boolean LANGUAGE plpgsql AS $$ +declare + xx text := x; + yy text := y; +begin + return xx < yy; +end +$$; + +SELECT mylt2('a', 'B' collate "en-x-icu") as t, mylt2('a', 'B' collate "C") as f; + +CREATE OR REPLACE FUNCTION + mylt2 (x text, y text) RETURNS boolean LANGUAGE plpgsql AS $$ +declare + xx text COLLATE "POSIX" := x; + yy text := y; +begin + return xx < yy; +end +$$; + +SELECT mylt2('a', 'B') as f; +SELECT mylt2('a', 'B' collate "C") as fail; -- conflicting collations +SELECT mylt2('a', 'B' collate "POSIX") as f; + + +-- polymorphism + +SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1; +SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test2)) ORDER BY 1; +SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test3)) ORDER BY 1; + +CREATE FUNCTION dup (anyelement) RETURNS anyelement + AS 'select $1' LANGUAGE sql; + +SELECT a, dup(b) FROM collate_test1 ORDER BY 2; +SELECT a, dup(b) FROM collate_test2 ORDER BY 2; +SELECT a, dup(b) FROM collate_test3 ORDER BY 2; + + +-- indexes + +CREATE INDEX collate_test1_idx1 ON collate_test1 (b); +CREATE INDEX collate_test1_idx2 ON collate_test1 (b COLLATE "C"); +CREATE INDEX collate_test1_idx3 ON collate_test1 ((b COLLATE "C")); -- this is different grammatically +CREATE INDEX collate_test1_idx4 ON collate_test1 (((b||'foo') COLLATE "POSIX")); + +CREATE INDEX collate_test1_idx5 ON collate_test1 (a COLLATE "C"); -- fail +CREATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "C")); -- fail + +SELECT relname, pg_get_indexdef(oid) FROM pg_class WHERE relname LIKE 'collate_test%_idx%' ORDER BY 1; + +set enable_seqscan = off; +explain (costs off) +select * from collate_test1 where b ilike 'abc'; +select * from collate_test1 where b ilike 'abc'; +explain (costs off) +select * from collate_test1 where b ilike 'ABC'; +select * from collate_test1 where b ilike 'ABC'; +reset enable_seqscan; + + +-- schema manipulation commands + +CREATE ROLE regress_test_role; +CREATE SCHEMA test_schema; + +-- We need to do this this way to cope with varying names for encodings: +do $$ +BEGIN + EXECUTE 'CREATE COLLATION test0 (provider = icu, locale = ' || + quote_literal(current_setting('lc_collate')) || ');'; +END +$$; +CREATE COLLATION test0 FROM "C"; -- fail, duplicate name +do $$ +BEGIN + EXECUTE 'CREATE COLLATION test1 (provider = icu, locale = ' || + quote_literal(current_setting('lc_collate')) || ');'; +END +$$; +CREATE COLLATION test3 (provider = icu, lc_collate = 'en_US.utf8'); -- fail, needs "locale" +CREATE COLLATION testx (provider = icu, locale = 'nonsense'); /* never fails with ICU */ DROP COLLATION testx; + +CREATE COLLATION test4 FROM nonsense; +CREATE COLLATION test5 FROM test0; + +SELECT collname FROM pg_collation WHERE collname LIKE 'test%' ORDER BY 1; + +ALTER COLLATION test1 RENAME TO test11; +ALTER COLLATION test0 RENAME TO test11; -- fail +ALTER COLLATION test1 RENAME TO test22; -- fail + +ALTER COLLATION test11 OWNER TO regress_test_role; +ALTER COLLATION test11 OWNER TO nonsense; +ALTER COLLATION test11 SET SCHEMA test_schema; + +COMMENT ON COLLATION test0 IS 'US English'; + +SELECT collname, nspname, obj_description(pg_collation.oid, 'pg_collation') + FROM pg_collation JOIN pg_namespace ON (collnamespace = pg_namespace.oid) + WHERE collname LIKE 'test%' + ORDER BY 1; + +DROP COLLATION test0, test_schema.test11, test5; +DROP COLLATION test0; -- YB: workaround +DROP COLLATION test_schema.test11; -- YB: workaround +DROP COLLATION test5; -- YB: workaround +DROP COLLATION test0; -- fail +DROP COLLATION IF EXISTS test0; + +SELECT collname FROM pg_collation WHERE collname LIKE 'test%'; + +DROP SCHEMA test_schema; +DROP ROLE regress_test_role; + + +-- ALTER + +ALTER COLLATION "en-x-icu" REFRESH VERSION; + +-- also test for database while we are here +SELECT current_database() AS datname \gset +ALTER DATABASE :"datname" REFRESH COLLATION VERSION; + + +-- dependencies + +CREATE COLLATION test0 FROM "C"; + +CREATE TABLE collate_dep_test1 (a int, b text COLLATE test0); +CREATE DOMAIN collate_dep_dom1 AS text COLLATE test0; +CREATE TYPE collate_dep_test2 AS (x int, y text COLLATE test0); +CREATE VIEW collate_dep_test3 AS SELECT text 'foo' COLLATE test0 AS foo; +CREATE TABLE collate_dep_test4t (a int, b text); +CREATE INDEX collate_dep_test4i ON collate_dep_test4t (b COLLATE test0); + +DROP COLLATION test0 RESTRICT; -- fail +DROP COLLATION test0 CASCADE; + +\d collate_dep_test1 +\d collate_dep_test2 + +DROP TABLE collate_dep_test1, collate_dep_test4t; +DROP TYPE collate_dep_test2; + +-- test range types and collations + +create type textrange_c as range(subtype=text, collation="C"); +create type textrange_en_us as range(subtype=text, collation="en-x-icu"); + +select textrange_c('A','Z') @> 'b'::text; +select textrange_en_us('A','Z') @> 'b'::text; + +drop type textrange_c; +drop type textrange_en_us; + + +-- test ICU collation customization + +-- test the attributes handled by icu_set_collation_attributes() + +CREATE COLLATION testcoll_ignore_accents (provider = icu, locale = '@colStrength=primary;colCaseLevel=yes'); +SELECT 'aaá' > 'AAA' COLLATE "und-x-icu", 'aaá' < 'AAA' COLLATE testcoll_ignore_accents; + +CREATE COLLATION testcoll_backwards (provider = icu, locale = '@colBackwards=yes'); +SELECT 'coté' < 'côte' COLLATE "und-x-icu", 'coté' > 'côte' COLLATE testcoll_backwards; + +CREATE COLLATION testcoll_lower_first (provider = icu, locale = '@colCaseFirst=lower'); +CREATE COLLATION testcoll_upper_first (provider = icu, locale = '@colCaseFirst=upper'); +SELECT 'aaa' < 'AAA' COLLATE testcoll_lower_first, 'aaa' > 'AAA' COLLATE testcoll_upper_first; + +CREATE COLLATION testcoll_shifted (provider = icu, locale = '@colAlternate=shifted'); +SELECT 'de-luge' < 'deanza' COLLATE "und-x-icu", 'de-luge' > 'deanza' COLLATE testcoll_shifted; + +CREATE COLLATION testcoll_numeric (provider = icu, locale = '@colNumeric=yes'); +SELECT 'A-21' > 'A-123' COLLATE "und-x-icu", 'A-21' < 'A-123' COLLATE testcoll_numeric; + +CREATE COLLATION testcoll_error1 (provider = icu, locale = '@colNumeric=lower'); + +-- test that attributes not handled by icu_set_collation_attributes() +-- (handled by ucol_open() directly) also work +CREATE COLLATION testcoll_de_phonebook (provider = icu, locale = 'de@collation=phonebook'); +SELECT 'Goldmann' < 'Götz' COLLATE "de-x-icu", 'Goldmann' > 'Götz' COLLATE testcoll_de_phonebook; + + +-- nondeterministic collations + +CREATE COLLATION ctest_det (provider = icu, locale = '', deterministic = true); +CREATE COLLATION ctest_nondet (provider = icu, locale = '', deterministic = false); +-- YB: port more queries when nondeterministic collation is supported + + +-- cleanup +RESET search_path; +SET client_min_messages TO warning; +DROP SCHEMA collate_tests CASCADE; +RESET client_min_messages; + +-- leave a collation for pg_upgrade test +CREATE COLLATION coll_icu_upgrade FROM "und-x-icu"; diff --git a/src/test/regress/sql/yb.port.collate.sql b/src/test/regress/sql/yb.port.collate.sql new file mode 100644 index 0000000000000..a116e0026c0f8 --- /dev/null +++ b/src/test/regress/sql/yb.port.collate.sql @@ -0,0 +1,302 @@ +/* + * This test is intended to pass on all platforms supported by Postgres. + * We can therefore only assume that the default, C, and POSIX collations + * are available --- and since the regression tests are often run in a + * C-locale database, these may well all have the same behavior. But + * fortunately, the system doesn't know that and will treat them as + * incompatible collations. It is therefore at least possible to test + * parser behaviors such as collation conflict resolution. This test will, + * however, be more revealing when run in a database with non-C locale, + * since any departure from C sorting behavior will show as a failure. + */ + +CREATE SCHEMA collate_tests; +SET search_path = collate_tests; + +CREATE TABLE collate_test1 ( + a int, + b text COLLATE "C" NOT NULL +); + +\d collate_test1 + +CREATE TABLE collate_test_fail ( + a int COLLATE "C", + b text +); + +CREATE TABLE collate_test_like ( + LIKE collate_test1 +); + +\d collate_test_like + +CREATE TABLE collate_test2 ( + a int, + b text COLLATE "POSIX" +); + +INSERT INTO collate_test1 VALUES (1, 'abc'), (2, 'Abc'), (3, 'bbc'), (4, 'ABD'); +INSERT INTO collate_test2 SELECT * FROM collate_test1; + +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'abc'; +SELECT * FROM collate_test1 WHERE b >= 'abc' COLLATE "C"; +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'abc' COLLATE "C"; +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "POSIX"; -- fail + +CREATE DOMAIN testdomain_p AS text COLLATE "POSIX"; +CREATE DOMAIN testdomain_i AS int COLLATE "POSIX"; -- fail +CREATE TABLE collate_test4 ( + a int, + b testdomain_p +); +INSERT INTO collate_test4 SELECT * FROM collate_test1; +SELECT a, b FROM collate_test4 ORDER BY b; + +CREATE TABLE collate_test5 ( + a int, + b testdomain_p COLLATE "C" +); +INSERT INTO collate_test5 SELECT * FROM collate_test1; +SELECT a, b FROM collate_test5 ORDER BY b; + + +SELECT a, b FROM collate_test1 ORDER BY b; +SELECT a, b FROM collate_test2 ORDER BY b; + +SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C"; + +-- star expansion +SELECT * FROM collate_test1 ORDER BY b; +SELECT * FROM collate_test2 ORDER BY b; + +-- constant expression folding +SELECT 'bbc' COLLATE "C" > 'Abc' COLLATE "C" AS "true"; +SELECT 'bbc' COLLATE "POSIX" < 'Abc' COLLATE "POSIX" AS "false"; + +-- upper/lower + +CREATE TABLE collate_test10 ( + a int, + x text COLLATE "C", + y text COLLATE "POSIX" +); + +INSERT INTO collate_test10 VALUES (1, 'hij', 'hij'), (2, 'HIJ', 'HIJ'); + +SELECT a, lower(x), lower(y), upper(x), upper(y), initcap(x), initcap(y) FROM collate_test10; +SELECT a, lower(x COLLATE "C"), lower(y COLLATE "C") FROM collate_test10; + +SELECT a, x, y FROM collate_test10 ORDER BY lower(y), a; + +-- backwards parsing + +CREATE VIEW collview1 AS SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc'; +CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C"; +CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10; + +SELECT table_name, view_definition FROM information_schema.views + WHERE table_name LIKE 'collview%' ORDER BY 1; + + +-- collation propagation in various expression types + +SELECT a, coalesce(b, 'foo') FROM collate_test1 ORDER BY 2; +SELECT a, coalesce(b, 'foo') FROM collate_test2 ORDER BY 2; +SELECT a, lower(coalesce(x, 'foo')), lower(coalesce(y, 'foo')) FROM collate_test10; + +SELECT a, b, greatest(b, 'CCC') FROM collate_test1 ORDER BY 3; +SELECT a, b, greatest(b, 'CCC') FROM collate_test2 ORDER BY 3; +SELECT a, x, y, lower(greatest(x, 'foo')), lower(greatest(y, 'foo')) FROM collate_test10; + +SELECT a, nullif(b, 'abc') FROM collate_test1 ORDER BY 2; +SELECT a, nullif(b, 'abc') FROM collate_test2 ORDER BY 2; +SELECT a, lower(nullif(x, 'foo')), lower(nullif(y, 'foo')) FROM collate_test10; + +SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test1 ORDER BY 2; +SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test2 ORDER BY 2; + +CREATE DOMAIN testdomain AS text; +SELECT a, b::testdomain FROM collate_test1 ORDER BY 2; +SELECT a, b::testdomain FROM collate_test2 ORDER BY 2; +SELECT a, b::testdomain_p FROM collate_test2 ORDER BY 2; +SELECT a, lower(x::testdomain), lower(y::testdomain) FROM collate_test10; + +SELECT min(b), max(b) FROM collate_test1; +SELECT min(b), max(b) FROM collate_test2; + +SELECT array_agg(b ORDER BY b) FROM collate_test1; +SELECT array_agg(b ORDER BY b) FROM collate_test2; + +-- In aggregates, ORDER BY expressions don't affect aggregate's collation +SELECT string_agg(x COLLATE "C", y COLLATE "POSIX") FROM collate_test10; -- fail +SELECT array_agg(x COLLATE "C" ORDER BY y COLLATE "POSIX") FROM collate_test10; +SELECT array_agg(a ORDER BY x COLLATE "C", y COLLATE "POSIX") FROM collate_test10; +SELECT array_agg(a ORDER BY x||y) FROM collate_test10; -- fail + +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test1 ORDER BY 2; +SELECT a, b FROM collate_test2 UNION SELECT a, b FROM collate_test2 ORDER BY 2; +SELECT a, b FROM collate_test2 WHERE a < 4 INTERSECT SELECT a, b FROM collate_test2 WHERE a > 1 ORDER BY 2; +SELECT a, b FROM collate_test2 EXCEPT SELECT a, b FROM collate_test2 WHERE a < 2 ORDER BY 2; + +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test2 ORDER BY 2; -- fail +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test2; -- ok +SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collate_test2 ORDER BY 2; -- fail +SELECT a, b COLLATE "C" FROM collate_test1 UNION SELECT a, b FROM collate_test2 ORDER BY 2; -- ok +SELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM collate_test2 ORDER BY 2; -- fail +SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM collate_test2 ORDER BY 2; -- fail + +CREATE TABLE test_u AS SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test2; -- fail + +-- ideally this would be a parse-time error, but for now it must be run-time: +select x < y from collate_test10; -- fail +select x || y from collate_test10; -- ok, because || is not collation aware +select x, y from collate_test10 order by x || y; -- not so ok + +-- collation mismatch between recursive and non-recursive term +WITH RECURSIVE foo(x) AS + (SELECT x FROM (VALUES('a' COLLATE "C"),('b')) t(x) + UNION ALL + SELECT (x || 'c') COLLATE "POSIX" FROM foo WHERE length(x) < 10) +SELECT * FROM foo; + +SELECT a, b, a < b as lt FROM + (VALUES ('a', 'B'), ('A', 'b' COLLATE "C")) v(a,b); + +-- collation mismatch in subselects +SELECT * FROM collate_test10 WHERE (x, y) NOT IN (SELECT y, x FROM collate_test10); -- YB: TODO: find out why output differs with upstream PG +-- now it works with overrides +SELECT * FROM collate_test10 WHERE (x COLLATE "POSIX", y COLLATE "C") NOT IN (SELECT y, x FROM collate_test10); +SELECT * FROM collate_test10 WHERE (x, y) NOT IN (SELECT y COLLATE "C", x COLLATE "POSIX" FROM collate_test10); + +-- casting + +SELECT CAST('42' AS text COLLATE "C"); + +SELECT a, CAST(b AS varchar) FROM collate_test1 ORDER BY 2; +SELECT a, CAST(b AS varchar) FROM collate_test2 ORDER BY 2; + + +-- result of a SQL function + +CREATE FUNCTION vc (text) RETURNS text LANGUAGE sql + AS 'select $1::varchar'; + +SELECT a, b FROM collate_test1 ORDER BY a, vc(b); + + +-- polymorphism + +SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1; +SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test2)) ORDER BY 1; + +CREATE FUNCTION dup (anyelement) RETURNS anyelement + AS 'select $1' LANGUAGE sql; + +SELECT a, dup(b) FROM collate_test1 ORDER BY 2; +SELECT a, dup(b) FROM collate_test2 ORDER BY 2; + + +-- indexes + +CREATE INDEX collate_test1_idx1 ON collate_test1 (b); +CREATE INDEX collate_test1_idx2 ON collate_test1 (b COLLATE "POSIX"); +CREATE INDEX collate_test1_idx3 ON collate_test1 ((b COLLATE "POSIX")); -- this is different grammatically +CREATE INDEX collate_test1_idx4 ON collate_test1 (((b||'foo') COLLATE "POSIX")); + +CREATE INDEX collate_test1_idx5 ON collate_test1 (a COLLATE "POSIX"); -- fail +CREATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "POSIX")); -- fail + +SELECT relname, pg_get_indexdef(oid) FROM pg_class WHERE relname LIKE 'collate_test%_idx%' ORDER BY 1; + + +-- foreign keys + +-- force indexes and mergejoins to be used for FK checking queries, +-- else they might not exercise collation-dependent operators +SET enable_seqscan TO 0; +SET enable_hashjoin TO 0; +SET enable_nestloop TO 0; + +CREATE TABLE collate_test20 (f1 text COLLATE "C" PRIMARY KEY); +INSERT INTO collate_test20 VALUES ('foo'), ('bar'); +CREATE TABLE collate_test21 (f2 text COLLATE "POSIX" REFERENCES collate_test20); +INSERT INTO collate_test21 VALUES ('foo'), ('bar'); +INSERT INTO collate_test21 VALUES ('baz'); -- fail +CREATE TABLE collate_test22 (f2 text COLLATE "POSIX"); +INSERT INTO collate_test22 VALUES ('foo'), ('bar'), ('baz'); +ALTER TABLE collate_test22 ADD FOREIGN KEY (f2) REFERENCES collate_test20; -- fail +DELETE FROM collate_test22 WHERE f2 = 'baz'; +ALTER TABLE collate_test22 ADD FOREIGN KEY (f2) REFERENCES collate_test20; + +RESET enable_seqscan; +RESET enable_hashjoin; +RESET enable_nestloop; + + +-- EXPLAIN + +EXPLAIN (COSTS OFF) + SELECT * FROM collate_test10 ORDER BY x, y; +EXPLAIN (COSTS OFF) + SELECT * FROM collate_test10 ORDER BY x DESC, y COLLATE "C" ASC NULLS FIRST; + + +-- CREATE/DROP COLLATION + +CREATE COLLATION mycoll1 FROM "C"; +CREATE COLLATION mycoll2 ( LC_COLLATE = "POSIX", LC_CTYPE = "POSIX" ); +CREATE COLLATION mycoll3 FROM "default"; -- intentionally unsupported + +DROP COLLATION mycoll1; +CREATE TABLE collate_test23 (f1 text collate mycoll2); +DROP COLLATION mycoll2; -- fail + +-- invalid: non-lowercase quoted identifiers +CREATE COLLATION case_coll ("Lc_Collate" = "POSIX", "Lc_Ctype" = "POSIX"); + +-- 9.1 bug with useless COLLATE in an expression subject to length coercion + +CREATE TEMP TABLE vctable (f1 varchar(25)); +INSERT INTO vctable VALUES ('foo' COLLATE "C"); + + +SELECT collation for ('foo'); -- unknown type - null +SELECT collation for ('foo'::text); +SELECT collation for ((SELECT a FROM collate_test1 LIMIT 1)); -- non-collatable type - error +SELECT collation for ((SELECT b FROM collate_test1 LIMIT 1)); + +-- old bug with not dropping COLLATE when coercing to non-collatable type +CREATE VIEW collate_on_int AS +SELECT c1+1 AS c1p FROM + (SELECT ('4' COLLATE "C")::INT AS c1) ss; +\d+ collate_on_int + +-- Check conflicting or redundant options in CREATE COLLATION +-- LC_COLLATE +CREATE COLLATION coll_dup_chk (LC_COLLATE = "POSIX", LC_COLLATE = "NONSENSE", LC_CTYPE = "POSIX"); +-- LC_CTYPE +CREATE COLLATION coll_dup_chk (LC_CTYPE = "POSIX", LC_CTYPE = "NONSENSE", LC_COLLATE = "POSIX"); +-- PROVIDER +CREATE COLLATION coll_dup_chk (PROVIDER = icu, PROVIDER = NONSENSE, LC_COLLATE = "POSIX", LC_CTYPE = "POSIX"); +-- LOCALE +CREATE COLLATION case_sensitive (LOCALE = '', LOCALE = "NONSENSE"); +-- DETERMINISTIC +CREATE COLLATION coll_dup_chk (DETERMINISTIC = TRUE, DETERMINISTIC = NONSENSE, LOCALE = ''); +-- VERSION +CREATE COLLATION coll_dup_chk (VERSION = '1', VERSION = "NONSENSE", LOCALE = ''); +-- LOCALE conflicts with LC_COLLATE and LC_CTYPE +CREATE COLLATION coll_dup_chk (LC_COLLATE = "POSIX", LC_CTYPE = "POSIX", LOCALE = ''); +-- LOCALE conflicts with LC_COLLATE +CREATE COLLATION coll_dup_chk (LC_COLLATE = "POSIX", LOCALE = ''); +-- LOCALE conflicts with LC_CTYPE +CREATE COLLATION coll_dup_chk (LC_CTYPE = "POSIX", LOCALE = ''); +-- FROM conflicts with any other option +CREATE COLLATION coll_dup_chk (FROM = "C", VERSION = "1"); + +-- +-- Clean up. Many of these table names will be re-used if the user is +-- trying to run any platform-specific collation tests later, so we +-- must get rid of them. +-- +DROP SCHEMA collate_tests CASCADE; diff --git a/src/test/regress/sql/yb.port.combocid.sql b/src/test/regress/sql/yb.port.combocid.sql new file mode 100644 index 0000000000000..4cd35bb965b4e --- /dev/null +++ b/src/test/regress/sql/yb.port.combocid.sql @@ -0,0 +1,125 @@ +-- +-- Tests for some likely failure cases with combo cmin/cmax mechanism +-- +CREATE TEMP TABLE combocidtest (foobar int); + +BEGIN; + +-- a few dummy ops to push up the CommandId counter +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; + +INSERT INTO combocidtest VALUES (1); +INSERT INTO combocidtest VALUES (2); + +SELECT ctid,cmin,* FROM combocidtest; + +-- Not supporting SAVEPOINT yet. +-- SAVEPOINT s1; +-- Commit and restart new transaction here for now. +COMMIT; +BEGIN; + +UPDATE combocidtest SET foobar = foobar + 10; + +-- here we should see only updated tuples +SELECT ctid,cmin,* FROM combocidtest; + +-- Not supporting SAVEPOINT yet. +-- ROLLBACK TO s1; +ROLLBACK; + +-- now we should see old tuples, but with combo CIDs starting at 0 +SELECT ctid,cmin,* FROM combocidtest; + +-- Not supporting SAVEPOINT yet. Comment the commit for now. +-- COMMIT; + +-- combo data is not there anymore, but should still see tuples +SELECT ctid,cmin,* FROM combocidtest; + +-- Test combo cids with portals +BEGIN; + +INSERT INTO combocidtest VALUES (333); + +DECLARE c CURSOR FOR SELECT ctid,cmin,* FROM combocidtest; + +DELETE FROM combocidtest; + +FETCH ALL FROM c; + +ROLLBACK; + +SELECT ctid,cmin,* FROM combocidtest; + +-- check behavior with locked tuples +BEGIN; + +-- a few dummy ops to push up the CommandId counter +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; +INSERT INTO combocidtest SELECT 1 LIMIT 0; + +INSERT INTO combocidtest VALUES (444); + +SELECT ctid,cmin,* FROM combocidtest; + +-- Not supporting SAVEPOINT yet. +-- SAVEPOINT s1; +-- Commit and restart new transaction here for now. +COMMIT; +BEGIN; + +-- this should not affect cmin +SELECT ctid,cmin,* FROM combocidtest FOR UPDATE; +SELECT ctid,cmin,* FROM combocidtest; + +-- but this does +UPDATE combocidtest SET foobar = foobar + 10; + +SELECT ctid,cmin,* FROM combocidtest; + +-- Not supporting SAVEPOINT yet. +-- ROLLBACK TO s1; +ROLLBACK; + +SELECT ctid,cmin,* FROM combocidtest; + +-- Not supporting SAVEPOINT yet. Comment the commit for now. +-- COMMIT; + +SELECT ctid,cmin,* FROM combocidtest; + +-- test for bug reported in +-- CABRT9RC81YUf1=jsmWopcKJEro=VoeG2ou6sPwyOUTx_qteRsg@mail.gmail.com +-- CREATE TABLE IF NOT EXISTS testcase( +-- id int PRIMARY KEY, +-- balance numeric +-- ); +-- INSERT INTO testcase VALUES (1, 0); +-- BEGIN; +-- SELECT * FROM testcase WHERE testcase.id = 1 FOR UPDATE; +-- UPDATE testcase SET balance = balance + 400 WHERE id=1; +-- SAVEPOINT subxact; +-- UPDATE testcase SET balance = balance - 100 WHERE id=1; +-- ROLLBACK TO SAVEPOINT subxact; +-- should return one tuple +-- SELECT * FROM testcase WHERE id = 1 FOR UPDATE; +-- ROLLBACK; +-- DROP TABLE testcase; diff --git a/src/test/regress/sql/yb.port.compression.sql b/src/test/regress/sql/yb.port.compression.sql new file mode 100644 index 0000000000000..5f3789c6b8d6d --- /dev/null +++ b/src/test/regress/sql/yb.port.compression.sql @@ -0,0 +1,197 @@ +\set HIDE_TOAST_COMPRESSION false + +-- ensure we get stable results regardless of installation's default +SET default_toast_compression = 'pglz'; + +-- test creating table with compression method +CREATE TEMP TABLE cmdata(f1 text COMPRESSION pglz); -- YB use temp tables instead of regular tables +CREATE INDEX idx ON cmdata(f1); +INSERT INTO cmdata VALUES(repeat('1234567890', 1000)); +-- YB: \d temptest has unstable output as the temporary schemaname contains +-- the tserver uuid. Use regexp_replace to change it to pg_temp_x so that the +-- result is stable. +select current_setting('data_directory') || 'describe.out' as desc_output_file -- YB remove temp table schema name UUID +\gset +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ cmdata +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); + +CREATE TEMP TABLE cmdata1(f1 TEXT COMPRESSION lz4); -- YB use temp tables instead of regular tables +INSERT INTO cmdata1 VALUES(repeat('1234567890', 1004)); +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ cmdata1 +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); + +-- verify stored compression method in the data +SELECT pg_column_compression(f1) FROM cmdata; +SELECT pg_column_compression(f1) FROM cmdata1; + +-- decompress data slice +SELECT SUBSTR(f1, 200, 5) FROM cmdata; +SELECT SUBSTR(f1, 2000, 50) FROM cmdata1; + +-- copy with table creation +SELECT * INTO cmmove1 FROM cmdata; +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ cmmove1 +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); +SELECT pg_column_compression(f1) FROM cmmove1; + +-- copy to existing table +CREATE TEMP TABLE cmmove3(f1 text COMPRESSION pglz); -- YB use temp tables instead of regular tables +INSERT INTO cmmove3 SELECT * FROM cmdata; +INSERT INTO cmmove3 SELECT * FROM cmdata1; +SELECT pg_column_compression(f1) FROM cmmove3; + +-- test LIKE INCLUDING COMPRESSION +CREATE TEMP TABLE cmdata2 (LIKE cmdata1 INCLUDING COMPRESSION); -- YB use temp tables instead of regular tables +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ cmdata2 +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); +DROP TABLE cmdata2; + +-- try setting compression for incompressible data type +CREATE TEMP TABLE cmdata2 (f1 int COMPRESSION pglz); -- YB use temp tables instead of regular tables + +-- update using datum from different table +CREATE TEMP TABLE cmmove2(f1 text COMPRESSION pglz); -- YB use temp tables instead of regular tables +INSERT INTO cmmove2 VALUES (repeat('1234567890', 1004)); +SELECT pg_column_compression(f1) FROM cmmove2; +UPDATE cmmove2 SET f1 = cmdata1.f1 FROM cmdata1; +SELECT pg_column_compression(f1) FROM cmmove2; + +-- test externally stored compressed data +CREATE OR REPLACE FUNCTION large_val() RETURNS TEXT LANGUAGE SQL AS +'select array_agg(md5(g::text))::text from generate_series(1, 256) g'; +CREATE TEMP TABLE cmdata2 (f1 text COMPRESSION pglz); -- YB use temp tables instead of regular tables +INSERT INTO cmdata2 SELECT large_val() || repeat('a', 4000); +SELECT pg_column_compression(f1) FROM cmdata2; +INSERT INTO cmdata1 SELECT large_val() || repeat('a', 4000); +SELECT pg_column_compression(f1) FROM cmdata1; +SELECT SUBSTR(f1, 200, 5) FROM cmdata1; +SELECT SUBSTR(f1, 200, 5) FROM cmdata2; +DROP TABLE cmdata2; + +--test column type update varlena/non-varlena +CREATE TEMP TABLE cmdata2 (f1 int); -- YB use temp tables instead of regular tables +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ cmdata2 +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); +ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar; +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ cmdata2 +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); +ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer; +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ cmdata2 +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); + +--changing column storage should not impact the compression method +--but the data should not be compressed +ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar; +ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION pglz; +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ cmdata2 +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); +ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain; +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ cmdata2 +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); +INSERT INTO cmdata2 VALUES (repeat('123456789', 800)); +SELECT pg_column_compression(f1) FROM cmdata2; + +/* YB: skip materialized view compression tests as they only apply to regular tables +-- test compression with materialized view +CREATE MATERIALIZED VIEW compressmv(x) AS SELECT * FROM cmdata1; +\d+ compressmv +SELECT pg_column_compression(f1) FROM cmdata1; +SELECT pg_column_compression(x) FROM compressmv; +YB */ + +-- test compression with partition +CREATE TEMP TABLE cmpart(f1 text COMPRESSION lz4) PARTITION BY HASH(f1); -- YB use temp tables instead of regular tables +CREATE TEMP TABLE cmpart1 PARTITION OF cmpart FOR VALUES WITH (MODULUS 2, REMAINDER 0); -- YB use temp tables instead of regular tables +CREATE TEMP TABLE cmpart2(f1 text COMPRESSION pglz); -- YB use temp tables instead of regular tables + +ALTER TABLE cmpart ATTACH PARTITION cmpart2 FOR VALUES WITH (MODULUS 2, REMAINDER 1); +INSERT INTO cmpart VALUES (repeat('123456789', 1004)); +INSERT INTO cmpart VALUES (repeat('123456789', 4004)); +SELECT pg_column_compression(f1) FROM cmpart1; +SELECT pg_column_compression(f1) FROM cmpart2; + +-- test compression with inheritance, error +CREATE TEMP TABLE cminh() INHERITS(cmdata, cmdata1); -- YB use temp tables instead of regular tables +CREATE TEMP TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata); -- YB use temp tables instead of regular tables + +-- test default_toast_compression GUC +SET default_toast_compression = ''; +SET default_toast_compression = 'I do not exist compression'; +SET default_toast_compression = 'lz4'; +SET default_toast_compression = 'pglz'; + +-- test alter compression method +ALTER TABLE cmdata ALTER COLUMN f1 SET COMPRESSION lz4; +INSERT INTO cmdata VALUES (repeat('123456789', 4004)); +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ cmdata +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); +SELECT pg_column_compression(f1) FROM cmdata; + +ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default; +\o :desc_output_file \\ -- YB remove temp table schema name UUID +\d+ cmdata2 +\o \\ -- YB remove temp table schema name UUID +select regexp_replace(pg_read_file(:'desc_output_file'), 'pg_temp_.{32}_\d+', 'pg_temp_x', 'g'); + + +/* YB: skip materialized view compression tests as they only apply to regular tables. +-- test alter compression method for materialized views +ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4; +\d+ compressmv +YB: */ + +-- test alter compression method for partitioned tables +ALTER TABLE cmpart1 ALTER COLUMN f1 SET COMPRESSION pglz; +ALTER TABLE cmpart2 ALTER COLUMN f1 SET COMPRESSION lz4; + +-- new data should be compressed with the current compression method +INSERT INTO cmpart VALUES (repeat('123456789', 1004)); +INSERT INTO cmpart VALUES (repeat('123456789', 4004)); +SELECT pg_column_compression(f1) FROM cmpart1; +SELECT pg_column_compression(f1) FROM cmpart2; + +-- VACUUM FULL does not recompress +SELECT pg_column_compression(f1) FROM cmdata; +VACUUM FULL cmdata; +SELECT pg_column_compression(f1) FROM cmdata; + +-- test expression index +DROP TABLE cmdata2; +CREATE TEMP TABLE cmdata2 (f1 TEXT COMPRESSION pglz, f2 TEXT COMPRESSION lz4); -- YB use temp tables instead of regular tables +CREATE UNIQUE INDEX idx1 ON cmdata2 ((f1 || f2)); +INSERT INTO cmdata2 VALUES((SELECT array_agg(md5(g::TEXT))::TEXT FROM +generate_series(1, 50) g), VERSION()); + +-- check data is ok +SELECT length(f1) FROM cmdata; +SELECT length(f1) FROM cmdata1; +SELECT length(f1) FROM cmmove1; +SELECT length(f1) FROM cmmove2; +SELECT length(f1) FROM cmmove3; + +CREATE TEMP TABLE badcompresstbl (a text COMPRESSION I_Do_Not_Exist_Compression); -- fails, YB use temp tables instead of regular tables +CREATE TEMP TABLE badcompresstbl (a text); -- YB use temp tables instead of regular tables +ALTER TABLE badcompresstbl ALTER a SET COMPRESSION I_Do_Not_Exist_Compression; -- fails, YB use temp tables instead of regular tables +DROP TABLE badcompresstbl; + +\set HIDE_TOAST_COMPRESSION true diff --git a/src/test/regress/sql/yb.port.constraints.sql b/src/test/regress/sql/yb.port.constraints.sql new file mode 100644 index 0000000000000..a3b320c648bfd --- /dev/null +++ b/src/test/regress/sql/yb.port.constraints.sql @@ -0,0 +1,110 @@ +-- TODO: port other PG tests + +CREATE TABLE UNIQUE_TBL (i int UNIQUE NULLS NOT DISTINCT, t text); + +INSERT INTO UNIQUE_TBL VALUES (1, 'one'); +INSERT INTO UNIQUE_TBL VALUES (2, 'two'); +INSERT INTO UNIQUE_TBL VALUES (1, 'three'); -- fail +INSERT INTO UNIQUE_TBL VALUES (4, 'four'); +INSERT INTO UNIQUE_TBL VALUES (5, 'one'); +INSERT INTO UNIQUE_TBL (t) VALUES ('six'); +INSERT INTO UNIQUE_TBL (t) VALUES ('seven'); -- fail +INSERT INTO UNIQUE_TBL (t) VALUES ('eight') ON CONFLICT DO NOTHING; -- no-op + +SELECT * FROM UNIQUE_TBL; + +DROP TABLE UNIQUE_TBL; + +-- +-- Check constraints on INSERT +-- + +CREATE SEQUENCE INSERT_SEQ; + +CREATE TABLE INSERT_TBL (x INT DEFAULT nextval('insert_seq'), + y TEXT DEFAULT '-NULL-', + z INT DEFAULT -1 * currval('insert_seq'), + CONSTRAINT INSERT_TBL_CON CHECK (x >= 3 AND y <> 'check failed' AND x < 8), + CHECK (x + z = 0)); + +INSERT INTO INSERT_TBL(x,z) VALUES (2, -2); + +SELECT * FROM INSERT_TBL; + +SELECT 'one' AS one, nextval('insert_seq'); + +INSERT INTO INSERT_TBL(y) VALUES ('Y'); +INSERT INTO INSERT_TBL(y) VALUES ('Y'); +INSERT INTO INSERT_TBL(x,z) VALUES (1, -2); +INSERT INTO INSERT_TBL(z,x) VALUES (-7, 7); +INSERT INTO INSERT_TBL VALUES (5, 'check failed', -5); +INSERT INTO INSERT_TBL VALUES (7, '!check failed', -7); +INSERT INTO INSERT_TBL(y) VALUES ('-!NULL-'); + +SELECT * FROM INSERT_TBL; + +INSERT INTO INSERT_TBL(y,z) VALUES ('check failed', 4); +INSERT INTO INSERT_TBL(x,y) VALUES (5, 'check failed'); +INSERT INTO INSERT_TBL(x,y) VALUES (5, '!check failed'); +INSERT INTO INSERT_TBL(y) VALUES ('-!NULL-'); + +SELECT * FROM INSERT_TBL; + +SELECT 'seven' AS one, nextval('insert_seq'); + +INSERT INTO INSERT_TBL(y) VALUES ('Y'); + +SELECT 'eight' AS one, currval('insert_seq'); + +-- According to SQL, it is OK to insert a record that gives rise to NULL +-- constraint-condition results. Postgres used to reject this, but it +-- was wrong: +INSERT INTO INSERT_TBL VALUES (null, null, null); + +SELECT * FROM INSERT_TBL; + + +-- +-- Check inheritance of defaults and constraints +-- + +CREATE TABLE INSERT_CHILD (cx INT default 42, + cy INT CHECK (cy > x)) + INHERITS (INSERT_TBL); + +INSERT INTO INSERT_CHILD(x,z,cy) VALUES (7,-7,11); +INSERT INTO INSERT_CHILD(x,z,cy) VALUES (7,-7,6); +INSERT INTO INSERT_CHILD(x,z,cy) VALUES (6,-7,7); +INSERT INTO INSERT_CHILD(x,y,z,cy) VALUES (6,'check failed',-6,7); + +SELECT * FROM INSERT_CHILD; + +DROP TABLE INSERT_CHILD; + +-- +-- Check NO INHERIT type of constraints and inheritance +-- + +CREATE TABLE ATACC1 (TEST INT + CHECK (TEST > 0) NO INHERIT); + +CREATE TABLE ATACC2 (TEST2 INT) INHERITS (ATACC1); +-- check constraint is not there on child +INSERT INTO ATACC2 (TEST) VALUES (-3); +-- check constraint is there on parent +INSERT INTO ATACC1 (TEST) VALUES (-3); +DROP TABLE ATACC1 CASCADE; + +CREATE TABLE ATACC1 (TEST INT, TEST2 INT + CHECK (TEST > 0), CHECK (TEST2 > 10) NO INHERIT); + +CREATE TABLE ATACC2 () INHERITS (ATACC1); +-- check constraint is there on child +INSERT INTO ATACC2 (TEST) VALUES (-3); +-- check constraint is there on parent +INSERT INTO ATACC1 (TEST) VALUES (-3); +-- check constraint is not there on child +INSERT INTO ATACC2 (TEST2) VALUES (3); +-- check constraint is there on parent +INSERT INTO ATACC1 (TEST2) VALUES (3); +DROP TABLE ATACC1 CASCADE; diff --git a/src/test/regress/sql/yb.port.conversion.sql b/src/test/regress/sql/yb.port.conversion.sql new file mode 100644 index 0000000000000..39595d074f424 --- /dev/null +++ b/src/test/regress/sql/yb.port.conversion.sql @@ -0,0 +1,23 @@ +-- +-- create user defined conversion +-- + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +CREATE FUNCTION test_enc_setup() RETURNS void + AS :'regresslib', 'test_enc_setup' + LANGUAGE C STRICT; +SELECT FROM test_enc_setup(); + +CREATE FUNCTION test_enc_conversion(bytea, name, name, bool, validlen OUT int, result OUT bytea) + AS :'regresslib', 'test_enc_conversion' + LANGUAGE C STRICT; + +CREATE USER regress_conversion_user WITH NOCREATEDB NOCREATEROLE; +SET SESSION AUTHORIZATION regress_conversion_user; +CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8; +-- YB: port remaining queries when CREATE CONVERSION is supported diff --git a/src/test/regress/sql/yb.port.copy.sql b/src/test/regress/sql/yb.port.copy.sql new file mode 100644 index 0000000000000..e73725f72df54 --- /dev/null +++ b/src/test/regress/sql/yb.port.copy.sql @@ -0,0 +1,270 @@ +-- +-- COPY +-- + +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +\getenv abs_builddir PG_ABS_BUILDDIR + +--- test copying in CSV mode with various styles +--- of embedded line ending characters + +create temp table copytest ( + style text, + test text, + filler int); + +insert into copytest values('DOS',E'abc\r\ndef',1); +insert into copytest values('Unix',E'abc\ndef',2); +insert into copytest values('Mac',E'abc\rdef',3); +insert into copytest values(E'esc\\ape',E'a\\r\\\r\\\n\\nb',4); + +\set filename :abs_builddir '/results/copytest.csv' +copy copytest to :'filename' csv; + +create temp table copytest2 (like copytest); + +copy copytest2 from :'filename' csv; + +select * from copytest except select * from copytest2; + +truncate copytest2; + +--- same test but with an escape char different from quote char + +copy copytest to :'filename' csv quote '''' escape E'\\'; + +copy copytest2 from :'filename' csv quote '''' escape E'\\'; + +select * from copytest except select * from copytest2; + + +-- test header line feature + +create temp table copytest3 ( + c1 int, + "col with , comma" text, + "col with "" quote" int); + +copy copytest3 from stdin csv header; +this is just a line full of junk that would error out if parsed +1,a,1 +2,b,2 +\. + +copy copytest3 to stdout csv header; + +create temp table copytest4 ( + c1 int, + "colname with tab: " text); + +copy copytest4 from stdin (header); +this is just a line full of junk that would error out if parsed +1 a +2 b +\. + +copy copytest4 to stdout (header); + +-- test copy from with a partitioned table +create table parted_copytest ( + a int, + b int, + c text +) partition by list (b); + +create table parted_copytest_a1 (c text, b int, a int); +create table parted_copytest_a2 (a int, c text, b int); + +alter table parted_copytest attach partition parted_copytest_a1 for values in(1); +alter table parted_copytest attach partition parted_copytest_a2 for values in(2); + +-- We must insert enough rows to trigger multi-inserts. These are only +-- enabled adaptively when there are few enough partition changes. +insert into parted_copytest select x,1,'One' from generate_series(1,1000) x; +insert into parted_copytest select x,2,'Two' from generate_series(1001,1010) x; +insert into parted_copytest select x,1,'One' from generate_series(1011,1020) x; + +\set filename :abs_builddir '/results/parted_copytest.csv' +copy (select * from parted_copytest order by a) to :'filename'; + +truncate parted_copytest; + +copy parted_copytest from :'filename'; + +-- Ensure COPY FREEZE errors for partitioned tables. +begin; +delete from parted_copytest; -- YB: replace truncate with delete because truncate is non transactional +copy parted_copytest from :'filename' (freeze); +rollback; + +select tableoid::regclass,count(*),sum(a) from parted_copytest +group by tableoid order by tableoid::regclass::name; + +truncate parted_copytest; + +-- create before insert row trigger on parted_copytest_a2 +create function part_ins_func() returns trigger language plpgsql as $$ +begin + return new; +end; +$$; + +create trigger part_ins_trig + before insert on parted_copytest_a2 + for each row + execute procedure part_ins_func(); + +copy parted_copytest from :'filename'; + +select tableoid::regclass,count(*),sum(a) from parted_copytest +group by tableoid order by tableoid::regclass::name; + +truncate table parted_copytest; +create index on parted_copytest (b); +drop trigger part_ins_trig on parted_copytest_a2; + +copy parted_copytest from stdin; +1 1 str1 +2 2 str2 +\. + +-- Ensure index entries were properly added during the copy. +select * from parted_copytest where b = 1; +select * from parted_copytest where b = 2; + +drop table parted_copytest; + +-- +-- Progress reporting for COPY +-- +create table tab_progress_reporting ( + name text, + age int4, + location point, + salary int4, + manager name +); + +-- Add a trigger to catch and print the contents of the catalog view +-- pg_stat_progress_copy during data insertion. This allows to test +-- the validation of some progress reports for COPY FROM where the trigger +-- would fire. +create function notice_after_tab_progress_reporting() returns trigger AS +$$ +declare report record; +begin + -- The fields ignored here are the ones that may not remain + -- consistent across multiple runs. The sizes reported may differ + -- across platforms, so just check if these are strictly positive. + with progress_data as ( + select + relid::regclass::text as relname, + command, + type, + bytes_processed > 0 as has_bytes_processed, + bytes_total > 0 as has_bytes_total, + tuples_processed, + tuples_excluded + from pg_stat_progress_copy + where pid = pg_backend_pid()) + select into report (to_jsonb(r)) as value + from progress_data r; + + raise info 'progress: %', report.value::text; + return new; +end; +$$ language plpgsql; + +create trigger check_after_tab_progress_reporting + after insert on tab_progress_reporting + for each statement + execute function notice_after_tab_progress_reporting(); + +-- Generate COPY FROM report with PIPE. +copy tab_progress_reporting from stdin; +sharon 25 (15,12) 1000 sam +sam 30 (10,5) 2000 bill +bill 20 (11,10) 1000 sharon +\. + +-- Generate COPY FROM report with FILE, with some excluded tuples. +truncate tab_progress_reporting; +\set filename :abs_srcdir '/data/emp.data' +copy tab_progress_reporting from :'filename' + where (salary < 2000); + +drop trigger check_after_tab_progress_reporting on tab_progress_reporting; +drop function notice_after_tab_progress_reporting(); +drop table tab_progress_reporting; + +-- Test header matching feature +create table header_copytest ( + a int, + b int, + c text +); +-- Make sure it works with dropped columns +alter table header_copytest drop column c; +alter table header_copytest add column c text; +copy header_copytest to stdout with (header match); +copy header_copytest from stdin with (header wrong_choice); +-- works +copy header_copytest from stdin with (header match); +a b c +1 2 foo +\. +copy header_copytest (c, a, b) from stdin with (header match); +c a b +bar 3 4 +\. +copy header_copytest from stdin with (header match, format csv); +a,b,c +5,6,baz +\. +-- errors +copy header_copytest (c, b, a) from stdin with (header match); +a b c +1 2 foo +\. +copy header_copytest from stdin with (header match); +a b \N +1 2 foo +\. +copy header_copytest from stdin with (header match); +a b +1 2 +\. +copy header_copytest from stdin with (header match); +a b c d +1 2 foo bar +\. +copy header_copytest from stdin with (header match); +a b d +1 2 foo +\. +SELECT * FROM header_copytest ORDER BY a; + +-- Drop an extra column, in the middle of the existing set. +alter table header_copytest drop column b; +-- works +copy header_copytest (c, a) from stdin with (header match); +c a +foo 7 +\. +copy header_copytest (a, c) from stdin with (header match); +a c +8 foo +\. +-- errors +copy header_copytest from stdin with (header match); +a ........pg.dropped.2........ c +1 2 foo +\. +copy header_copytest (a, c) from stdin with (header match); +a c b +1 foo 2 +\. + +SELECT * FROM header_copytest ORDER BY a; +drop table header_copytest; diff --git a/src/test/regress/sql/yb.port.copy2.sql b/src/test/regress/sql/yb.port.copy2.sql new file mode 100644 index 0000000000000..4309b69638f3f --- /dev/null +++ b/src/test/regress/sql/yb.port.copy2.sql @@ -0,0 +1,472 @@ +CREATE TEMP TABLE x ( + a serial, + b int, + c text not null default 'stuff', + d text, + e text +) ; + +CREATE FUNCTION fn_x_before () RETURNS TRIGGER AS ' + BEGIN + NEW.e := ''before trigger fired''::text; + return NEW; + END; +' LANGUAGE plpgsql; + +CREATE FUNCTION fn_x_after () RETURNS TRIGGER AS ' + BEGIN + UPDATE x set e=''after trigger fired'' where c=''stuff''; + return NULL; + END; +' LANGUAGE plpgsql; + +CREATE TRIGGER trg_x_after AFTER INSERT ON x +FOR EACH ROW EXECUTE PROCEDURE fn_x_after(); + +CREATE TRIGGER trg_x_before BEFORE INSERT ON x +FOR EACH ROW EXECUTE PROCEDURE fn_x_before(); + +COPY x (a, b, c, d, e) from stdin; +9999 \N \\N \NN \N +10000 21 31 41 51 +\. + +COPY x (b, d) from stdin; +1 test_1 +\. + +COPY x (b, d) from stdin; +2 test_2 +3 test_3 +4 test_4 +5 test_5 +\. + +COPY x (a, b, c, d, e) from stdin; +10001 22 32 42 52 +10002 23 33 43 53 +10003 24 34 44 54 +10004 25 35 45 55 +10005 26 36 46 56 +\. + +-- non-existent column in column list: should fail +COPY x (xyz) from stdin; + +-- redundant options +COPY x from stdin (format CSV, FORMAT CSV); +COPY x from stdin (freeze off, freeze on); +COPY x from stdin (delimiter ',', delimiter ','); +COPY x from stdin (null ' ', null ' '); +COPY x from stdin (header off, header on); +COPY x from stdin (quote ':', quote ':'); +COPY x from stdin (escape ':', escape ':'); +COPY x from stdin (force_quote (a), force_quote *); +COPY x from stdin (force_not_null (a), force_not_null (b)); +COPY x from stdin (force_null (a), force_null (b)); +COPY x from stdin (convert_selectively (a), convert_selectively (b)); +COPY x from stdin (encoding 'sql_ascii', encoding 'sql_ascii'); + +-- too many columns in column list: should fail +COPY x (a, b, c, d, e, d, c) from stdin; + +-- missing data: should fail +COPY x from stdin; + +\. +COPY x from stdin; +2000 230 23 23 +\. +COPY x from stdin; +2001 231 \N \N +\. + +-- extra data: should fail +COPY x from stdin; +2002 232 40 50 60 70 80 +\. + +-- various COPY options: delimiters, oids, NULL string, encoding +COPY x (b, c, d, e) from stdin delimiter ',' null 'x'; +x,45,80,90 +x,\x,\\x,\\\x +x,\,,\\\,,\\ +\. + +COPY x from stdin WITH DELIMITER AS ';' NULL AS ''; +3000;;c;; +\. + +COPY x from stdin WITH DELIMITER AS ':' NULL AS E'\\X' ENCODING 'sql_ascii'; +4000:\X:C:\X:\X +4001:1:empty:: +4002:2:null:\X:\X +4003:3:Backslash:\\:\\ +4004:4:BackslashX:\\X:\\X +4005:5:N:\N:\N +4006:6:BackslashN:\\N:\\N +4007:7:XX:\XX:\XX +4008:8:Delimiter:\::\: +\. + +COPY x TO stdout WHERE a = 1; +COPY x from stdin WHERE a = 50004; +50003 24 34 44 54 +50004 25 35 45 55 +50005 26 36 46 56 +\. + +COPY x from stdin WHERE a > 60003; +60001 22 32 42 52 +60002 23 33 43 53 +60003 24 34 44 54 +60004 25 35 45 55 +60005 26 36 46 56 +\. + +COPY x from stdin WHERE f > 60003; + +COPY x from stdin WHERE a = max(x.b); + +COPY x from stdin WHERE a IN (SELECT 1 FROM x); + +COPY x from stdin WHERE a IN (generate_series(1,5)); + +COPY x from stdin WHERE a = row_number() over(b); + + +-- check results of copy in +SELECT * FROM x; + +-- check copy out +COPY x TO stdout; +COPY x (c, e) TO stdout; +COPY x (b, e) TO stdout WITH NULL 'I''m null'; + +CREATE TEMP TABLE y ( + col1 text, + col2 text +); + +INSERT INTO y VALUES ('Jackson, Sam', E'\\h'); +INSERT INTO y VALUES ('It is "perfect".',E'\t'); +INSERT INTO y VALUES ('', NULL); + +COPY y TO stdout WITH CSV; +COPY y TO stdout WITH CSV QUOTE '''' DELIMITER '|'; +COPY y TO stdout WITH CSV FORCE QUOTE col2 ESCAPE E'\\' ENCODING 'sql_ascii'; +COPY y TO stdout WITH CSV FORCE QUOTE *; + +-- Repeat above tests with new 9.0 option syntax + +COPY y TO stdout (FORMAT CSV); +COPY y TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|'); +COPY y TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\'); +COPY y TO stdout (FORMAT CSV, FORCE_QUOTE *); + +\copy y TO stdout (FORMAT CSV) +\copy y TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|') +\copy y TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\') +\copy y TO stdout (FORMAT CSV, FORCE_QUOTE *) + +--test that we read consecutive LFs properly + +CREATE TEMP TABLE testnl (a int, b text, c int); + +COPY testnl FROM stdin CSV; +1,"a field with two LFs + +inside",2 +\. + +-- test end of copy marker +CREATE TEMP TABLE testeoc (a text); + +COPY testeoc FROM stdin CSV; +a\. +\.b +c\.d +"\." +\. + +COPY testeoc TO stdout CSV; + +-- test handling of nonstandard null marker that violates escaping rules + +CREATE TEMP TABLE testnull(a int, b text); +INSERT INTO testnull VALUES (1, E'\\0'), (NULL, NULL); + +COPY testnull TO stdout WITH NULL AS E'\\0'; + +COPY testnull FROM stdin WITH NULL AS E'\\0'; +42 \\0 +\0 \0 +\. + +SELECT * FROM testnull; + +BEGIN; +CREATE TABLE vistest (LIKE testeoc); +COPY vistest FROM stdin CSV; +a0 +b +\. +COMMIT; +SELECT * FROM vistest; +BEGIN; +TRUNCATE vistest; +COPY vistest FROM stdin CSV; +a1 +b +\. +SELECT * FROM vistest; +SAVEPOINT s1; +DELETE FROM vistest; -- YB: TRUNCATE is not transactional, so use DELETE instead +COPY vistest FROM stdin CSV; +d1 +e +\. +SELECT * FROM vistest; +COMMIT; +SELECT * FROM vistest; + +BEGIN; +TRUNCATE vistest; +COPY vistest FROM stdin CSV FREEZE; +a2 +b +\. +SELECT * FROM vistest; +SAVEPOINT s1; +DELETE FROM vistest; -- YB: TRUNCATE is not transactional, so use DELETE instead +COPY vistest FROM stdin CSV FREEZE; +d2 +e +\. +SELECT * FROM vistest; +COMMIT; +SELECT * FROM vistest; + +BEGIN; +TRUNCATE vistest; +COPY vistest FROM stdin CSV FREEZE; +x +y +\. +SELECT * FROM vistest; +COMMIT; +TRUNCATE vistest; +COPY vistest FROM stdin CSV FREEZE; +p +g +\. +BEGIN; +TRUNCATE vistest; +SAVEPOINT s1; +COPY vistest FROM stdin CSV FREEZE; +m +k +\. +COMMIT; +BEGIN; +INSERT INTO vistest VALUES ('z'); +SAVEPOINT s1; +DELETE FROM vistest; -- YB: TRUNCATE is not transactional, so use DELETE instead +ROLLBACK TO SAVEPOINT s1; +COPY vistest FROM stdin CSV FREEZE; +d3 +e +\. +COMMIT; +CREATE FUNCTION truncate_in_subxact() RETURNS VOID AS +$$ +BEGIN + DELETE FROM vistest; -- YB: TRUNCATE is not transactional, so use DELETE instead +EXCEPTION + WHEN OTHERS THEN + INSERT INTO vistest VALUES ('subxact failure'); +END; +$$ language plpgsql; +BEGIN; +INSERT INTO vistest VALUES ('z'); +SELECT truncate_in_subxact(); +COPY vistest FROM stdin CSV FREEZE; +d4 +e +\. +SELECT * FROM vistest; +COMMIT; +SELECT * FROM vistest; +-- Test FORCE_NOT_NULL and FORCE_NULL options +CREATE TEMP TABLE forcetest ( + a INT NOT NULL, + b TEXT NOT NULL, + c TEXT, + d TEXT, + e TEXT +); +\pset null NULL +-- should succeed with no effect ("b" remains an empty string, "c" remains NULL) +BEGIN; +COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b), FORCE_NULL(c)); +1,,"" +\. +COMMIT; +SELECT b, c FROM forcetest WHERE a = 1; +-- should succeed, FORCE_NULL and FORCE_NOT_NULL can be both specified +BEGIN; +COPY forcetest (a, b, c, d) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(c,d), FORCE_NULL(c,d)); +2,'a',,"" +\. +COMMIT; +SELECT c, d FROM forcetest WHERE a = 2; +-- should fail with not-null constraint violation +BEGIN; +COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b), FORCE_NOT_NULL(c)); +3,,"" +\. +ROLLBACK; +-- should fail with "not referenced by COPY" error +BEGIN; +COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b)); +ROLLBACK; +-- should fail with "not referenced by COPY" error +BEGIN; +COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b)); +ROLLBACK; +\pset null '' + +-- test case with whole-row Var in a check constraint +create table check_con_tbl (f1 int); +create function check_con_function(check_con_tbl) returns bool as $$ +begin + raise notice 'input = %', row_to_json($1); + return $1.f1 > 0; +end $$ language plpgsql immutable; +alter table check_con_tbl add check (check_con_function(check_con_tbl.*)); +\d+ check_con_tbl +copy check_con_tbl from stdin; +1 +\N +\. +copy check_con_tbl from stdin; +0 +\. +select * from check_con_tbl; + +-- test with RLS enabled. +CREATE ROLE regress_rls_copy_user; +CREATE ROLE regress_rls_copy_user_colperms; +CREATE TABLE rls_t1 (a int, b int, c int); + +COPY rls_t1 (a, b, c) from stdin; +1 4 1 +2 3 2 +3 2 3 +4 1 4 +\. + +CREATE POLICY p1 ON rls_t1 FOR SELECT USING (a % 2 = 0); +ALTER TABLE rls_t1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE rls_t1 FORCE ROW LEVEL SECURITY; + +GRANT SELECT ON TABLE rls_t1 TO regress_rls_copy_user; +GRANT SELECT (a, b) ON TABLE rls_t1 TO regress_rls_copy_user_colperms; + +-- all columns +COPY rls_t1 TO stdout; +COPY rls_t1 (a, b, c) TO stdout; + +-- subset of columns +COPY rls_t1 (a) TO stdout; +COPY rls_t1 (a, b) TO stdout; + +-- column reordering +COPY rls_t1 (b, a) TO stdout; + +SET SESSION AUTHORIZATION regress_rls_copy_user; + +-- all columns +COPY rls_t1 TO stdout; +COPY rls_t1 (a, b, c) TO stdout; + +-- subset of columns +COPY rls_t1 (a) TO stdout; +COPY rls_t1 (a, b) TO stdout; + +-- column reordering +COPY rls_t1 (b, a) TO stdout; + +RESET SESSION AUTHORIZATION; + +SET SESSION AUTHORIZATION regress_rls_copy_user_colperms; + +-- attempt all columns (should fail) +COPY rls_t1 TO stdout; +COPY rls_t1 (a, b, c) TO stdout; + +-- try to copy column with no privileges (should fail) +COPY rls_t1 (c) TO stdout; + +-- subset of columns (should succeed) +COPY rls_t1 (a) TO stdout; +COPY rls_t1 (a, b) TO stdout; + +RESET SESSION AUTHORIZATION; + +/* YB: Copying into a view fails an assert, even wth a INSTEAD OF INSERT trigger. #26541 +-- test with INSTEAD OF INSERT trigger on a view +CREATE TABLE instead_of_insert_tbl(id serial, name text); +CREATE VIEW instead_of_insert_tbl_view AS SELECT ''::text AS str; + +COPY instead_of_insert_tbl_view FROM stdin; -- fail +test1 +\. + +CREATE FUNCTION fun_instead_of_insert_tbl() RETURNS trigger AS $$ +BEGIN + INSERT INTO instead_of_insert_tbl (name) VALUES (NEW.str); + RETURN NULL; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER trig_instead_of_insert_tbl_view + INSTEAD OF INSERT ON instead_of_insert_tbl_view + FOR EACH ROW EXECUTE PROCEDURE fun_instead_of_insert_tbl(); + +COPY instead_of_insert_tbl_view FROM stdin; +test1 +\. + +SELECT * FROM instead_of_insert_tbl; + +-- Test of COPY optimization with view using INSTEAD OF INSERT +-- trigger when relation is created in the same transaction as +-- when COPY is executed. +BEGIN; +CREATE VIEW instead_of_insert_tbl_view_2 as select ''::text as str; +CREATE TRIGGER trig_instead_of_insert_tbl_view_2 + INSTEAD OF INSERT ON instead_of_insert_tbl_view_2 + FOR EACH ROW EXECUTE PROCEDURE fun_instead_of_insert_tbl(); + +COPY instead_of_insert_tbl_view_2 FROM stdin; +test1 +\. + +SELECT * FROM instead_of_insert_tbl; +COMMIT; +*/ -- YB + +-- clean up +DROP TABLE forcetest; +DROP TABLE vistest; +DROP FUNCTION truncate_in_subxact(); +DROP TABLE x, y; +DROP TABLE rls_t1 CASCADE; +DROP ROLE regress_rls_copy_user; +DROP ROLE regress_rls_copy_user_colperms; +DROP FUNCTION fn_x_before(); +DROP FUNCTION fn_x_after(); +DROP TABLE instead_of_insert_tbl; +DROP VIEW instead_of_insert_tbl_view; +DROP VIEW instead_of_insert_tbl_view_2; +DROP FUNCTION fun_instead_of_insert_tbl(); diff --git a/src/test/regress/sql/yb.port.create_am.sql b/src/test/regress/sql/yb.port.create_am.sql new file mode 100644 index 0000000000000..a3e3e17a66af6 --- /dev/null +++ b/src/test/regress/sql/yb.port.create_am.sql @@ -0,0 +1,73 @@ +-- +-- Create access method tests +-- + +-- Make gist2 over gisthandler. In fact, it would be a synonym to gist. +CREATE ACCESS METHOD gist2 TYPE INDEX HANDLER gisthandler; + +-- Verify return type checks for handlers +CREATE ACCESS METHOD bogus TYPE INDEX HANDLER int4in; +CREATE ACCESS METHOD bogus TYPE INDEX HANDLER heap_tableam_handler; + + +-- Try to create gist2 index on fast_emp4000: fail because opclass doesn't exist +CREATE INDEX grect2ind2 ON fast_emp4000 USING gist2 (home_base); -- YB: Should fail because gist is unsupported on YB tables +CREATE TEMP TABLE fast_emp4000 AS SELECT * FROM fast_emp4000; -- YB: workaround using temp table +CREATE INDEX grect2ind2 ON fast_emp4000 USING gist2 (home_base); + +-- Make operator class for boxes using gist2 +CREATE OPERATOR CLASS box_ops DEFAULT + FOR TYPE box USING gist2 AS + OPERATOR 1 <<, + OPERATOR 2 &<, + OPERATOR 3 &&, + OPERATOR 4 &>, + OPERATOR 5 >>, + OPERATOR 6 ~=, + OPERATOR 7 @>, + OPERATOR 8 <@, + OPERATOR 9 &<|, + OPERATOR 10 <<|, + OPERATOR 11 |>>, + OPERATOR 12 |&>, + FUNCTION 1 gist_box_consistent(internal, box, smallint, oid, internal), + FUNCTION 2 gist_box_union(internal, internal), + -- don't need compress, decompress, or fetch functions + FUNCTION 5 gist_box_penalty(internal, internal, internal), + FUNCTION 6 gist_box_picksplit(internal, internal), + FUNCTION 7 gist_box_same(box, box, internal); + +-- Create gist2 index on fast_emp4000 +CREATE INDEX grect2ind2 ON fast_emp4000 USING gist2 (home_base); + +-- Now check the results from plain indexscan; temporarily drop existing +-- index grect2ind to ensure it doesn't capture the plan +-- BEGIN; -- YB: Commented out because DDL in txn is unsupported +DROP INDEX grect2ind; -- YB: fails because CREATE INDEX grect2ind is not yet ported in yb.port.create_index +SET enable_seqscan = OFF; +SET enable_indexscan = ON; +SET enable_bitmapscan = OFF; + +EXPLAIN (COSTS OFF) +SELECT * FROM fast_emp4000 + WHERE home_base <@ '(200,200),(2000,1000)'::box + ORDER BY (home_base[0])[0]; +SELECT * FROM fast_emp4000 + WHERE home_base <@ '(200,200),(2000,1000)'::box + ORDER BY (home_base[0])[0]; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box; +SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; +SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; + +-- ROLLBACK; -- YB: We don't need to rollback as we didn't begin a transaction. + +-- Try to drop access method: fail because of dependent objects +DROP ACCESS METHOD gist2; + +-- Drop access method cascade +DROP ACCESS METHOD gist2 CASCADE; diff --git a/src/test/regress/sql/yb.port.create_function_c.sql b/src/test/regress/sql/yb.port.create_function_c.sql new file mode 100644 index 0000000000000..2be4279f87d0b --- /dev/null +++ b/src/test/regress/sql/yb.port.create_function_c.sql @@ -0,0 +1,40 @@ +-- +-- CREATE_FUNCTION_C +-- +-- This script used to create C functions for other scripts to use. +-- But to get rid of the ordering dependencies that caused, such +-- functions are now made either in test_setup.sql or in the specific +-- test script that needs them. All that remains here is error cases. + +-- directory path and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +-- +-- Check LOAD command. (The alternative of implicitly loading the library +-- is checked in many other test scripts.) +-- +-- TODO Not yet supported in YB. +-- Until it is supported, commenting out the original query and replacing +-- with a stub because the error message contains the local path. +-- (The query will currently fail before processing the path argument anyway.) +-- LOAD :'regresslib'; +LOAD 'regress.so'; + +-- Things that shouldn't work: + +CREATE FUNCTION test1 (int) RETURNS int LANGUAGE C + AS 'nosuchfile'; + +-- To produce stable regression test output, we have to filter the name +-- of the regresslib file out of the error message in this test. +\set VERBOSITY sqlstate +CREATE FUNCTION test1 (int) RETURNS int LANGUAGE C + AS :'regresslib', 'nosuchsymbol'; +\set VERBOSITY default +SELECT regexp_replace(:'LAST_ERROR_MESSAGE', 'file ".*"', 'file "..."'); + +CREATE FUNCTION test1 (int) RETURNS int LANGUAGE internal + AS 'nosuch'; diff --git a/src/test/regress/sql/yb.port.create_function_sql.sql b/src/test/regress/sql/yb.port.create_function_sql.sql new file mode 100644 index 0000000000000..89e9af3a49933 --- /dev/null +++ b/src/test/regress/sql/yb.port.create_function_sql.sql @@ -0,0 +1,421 @@ +-- +-- CREATE_FUNCTION_SQL +-- +-- Assorted tests using SQL-language functions +-- + +-- All objects made in this test are in temp_func_test schema + +CREATE USER regress_unpriv_user; + +CREATE SCHEMA temp_func_test; +GRANT ALL ON SCHEMA temp_func_test TO public; + +SET search_path TO temp_func_test, public; + +-- +-- Make sanity checks on the pg_proc entries created by CREATE FUNCTION +-- + +-- +-- ARGUMENT and RETURN TYPES +-- +CREATE FUNCTION functest_A_1(text, date) RETURNS bool LANGUAGE 'sql' + AS 'SELECT $1 = ''abcd'' AND $2 > ''2001-01-01'''; +CREATE FUNCTION functest_A_2(text[]) RETURNS int LANGUAGE 'sql' + AS 'SELECT $1[1]::int'; +CREATE FUNCTION functest_A_3() RETURNS bool LANGUAGE 'sql' + AS 'SELECT false'; +SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc + WHERE oid in ('functest_A_1'::regproc, + 'functest_A_2'::regproc, + 'functest_A_3'::regproc) ORDER BY proname; + +SELECT functest_A_1('abcd', '2020-01-01'); +SELECT functest_A_2(ARRAY['1', '2', '3']); +SELECT functest_A_3(); + +-- +-- IMMUTABLE | STABLE | VOLATILE +-- +CREATE FUNCTION functest_B_1(int) RETURNS bool LANGUAGE 'sql' + AS 'SELECT $1 > 0'; +CREATE FUNCTION functest_B_2(int) RETURNS bool LANGUAGE 'sql' + IMMUTABLE AS 'SELECT $1 > 0'; +CREATE FUNCTION functest_B_3(int) RETURNS bool LANGUAGE 'sql' + STABLE AS 'SELECT $1 = 0'; +CREATE FUNCTION functest_B_4(int) RETURNS bool LANGUAGE 'sql' + VOLATILE AS 'SELECT $1 < 0'; +SELECT proname, provolatile FROM pg_proc + WHERE oid in ('functest_B_1'::regproc, + 'functest_B_2'::regproc, + 'functest_B_3'::regproc, + 'functest_B_4'::regproc) ORDER BY proname; + +ALTER FUNCTION functest_B_2(int) VOLATILE; +ALTER FUNCTION functest_B_3(int) COST 100; -- unrelated change, no effect +SELECT proname, provolatile FROM pg_proc + WHERE oid in ('functest_B_1'::regproc, + 'functest_B_2'::regproc, + 'functest_B_3'::regproc, + 'functest_B_4'::regproc) ORDER BY proname; + +-- +-- SECURITY DEFINER | INVOKER +-- +CREATE FUNCTION functest_C_1(int) RETURNS bool LANGUAGE 'sql' + AS 'SELECT $1 > 0'; +CREATE FUNCTION functest_C_2(int) RETURNS bool LANGUAGE 'sql' + SECURITY DEFINER AS 'SELECT $1 = 0'; +CREATE FUNCTION functest_C_3(int) RETURNS bool LANGUAGE 'sql' + SECURITY INVOKER AS 'SELECT $1 < 0'; +SELECT proname, prosecdef FROM pg_proc + WHERE oid in ('functest_C_1'::regproc, + 'functest_C_2'::regproc, + 'functest_C_3'::regproc) ORDER BY proname; + +ALTER FUNCTION functest_C_1(int) IMMUTABLE; -- unrelated change, no effect +ALTER FUNCTION functest_C_2(int) SECURITY INVOKER; +ALTER FUNCTION functest_C_3(int) SECURITY DEFINER; +SELECT proname, prosecdef FROM pg_proc + WHERE oid in ('functest_C_1'::regproc, + 'functest_C_2'::regproc, + 'functest_C_3'::regproc) ORDER BY proname; + +-- +-- LEAKPROOF +-- +CREATE FUNCTION functest_E_1(int) RETURNS bool LANGUAGE 'sql' + AS 'SELECT $1 > 100'; +CREATE FUNCTION functest_E_2(int) RETURNS bool LANGUAGE 'sql' + LEAKPROOF AS 'SELECT $1 > 100'; +SELECT proname, proleakproof FROM pg_proc + WHERE oid in ('functest_E_1'::regproc, + 'functest_E_2'::regproc) ORDER BY proname; + +ALTER FUNCTION functest_E_1(int) LEAKPROOF; +ALTER FUNCTION functest_E_2(int) STABLE; -- unrelated change, no effect +SELECT proname, proleakproof FROM pg_proc + WHERE oid in ('functest_E_1'::regproc, + 'functest_E_2'::regproc) ORDER BY proname; + +ALTER FUNCTION functest_E_2(int) NOT LEAKPROOF; -- remove leakproof attribute +SELECT proname, proleakproof FROM pg_proc + WHERE oid in ('functest_E_1'::regproc, + 'functest_E_2'::regproc) ORDER BY proname; + +-- it takes superuser privilege to turn on leakproof, but not to turn off +ALTER FUNCTION functest_E_1(int) OWNER TO regress_unpriv_user; +ALTER FUNCTION functest_E_2(int) OWNER TO regress_unpriv_user; + +SET SESSION AUTHORIZATION regress_unpriv_user; +SET search_path TO temp_func_test, public; +ALTER FUNCTION functest_E_1(int) NOT LEAKPROOF; +ALTER FUNCTION functest_E_2(int) LEAKPROOF; + +CREATE FUNCTION functest_E_3(int) RETURNS bool LANGUAGE 'sql' + LEAKPROOF AS 'SELECT $1 < 200'; -- fail + +RESET SESSION AUTHORIZATION; + +-- +-- CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT +-- +CREATE FUNCTION functest_F_1(int) RETURNS bool LANGUAGE 'sql' + AS 'SELECT $1 > 50'; +CREATE FUNCTION functest_F_2(int) RETURNS bool LANGUAGE 'sql' + CALLED ON NULL INPUT AS 'SELECT $1 = 50'; +CREATE FUNCTION functest_F_3(int) RETURNS bool LANGUAGE 'sql' + RETURNS NULL ON NULL INPUT AS 'SELECT $1 < 50'; +CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql' + STRICT AS 'SELECT $1 = 50'; +SELECT proname, proisstrict FROM pg_proc + WHERE oid in ('functest_F_1'::regproc, + 'functest_F_2'::regproc, + 'functest_F_3'::regproc, + 'functest_F_4'::regproc) ORDER BY proname; + +ALTER FUNCTION functest_F_1(int) IMMUTABLE; -- unrelated change, no effect +ALTER FUNCTION functest_F_2(int) STRICT; +ALTER FUNCTION functest_F_3(int) CALLED ON NULL INPUT; +SELECT proname, proisstrict FROM pg_proc + WHERE oid in ('functest_F_1'::regproc, + 'functest_F_2'::regproc, + 'functest_F_3'::regproc, + 'functest_F_4'::regproc) ORDER BY proname; + + +-- pg_get_functiondef tests + +SELECT pg_get_functiondef('functest_A_1'::regproc); +SELECT pg_get_functiondef('functest_B_3'::regproc); +SELECT pg_get_functiondef('functest_C_3'::regproc); +SELECT pg_get_functiondef('functest_F_2'::regproc); + + +-- +-- SQL-standard body +-- +CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean + LANGUAGE SQL + RETURN a = 'abcd' AND b > '2001-01-01'; +CREATE FUNCTION functest_S_2(a text[]) RETURNS int + RETURN a[1]::int; +CREATE FUNCTION functest_S_3() RETURNS boolean + RETURN false; +CREATE FUNCTION functest_S_3a() RETURNS boolean + BEGIN ATOMIC + ;;RETURN false;; + END; + +CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean + LANGUAGE SQL + BEGIN ATOMIC + SELECT a = 'abcd' AND b > '2001-01-01'; + END; + +CREATE FUNCTION functest_S_13() RETURNS boolean + BEGIN ATOMIC + SELECT 1; + SELECT false; + END; + +-- check display of function arguments in sub-SELECT +CREATE TABLE functest1 (i int); +CREATE FUNCTION functest_S_16(a int, b int) RETURNS void + LANGUAGE SQL + BEGIN ATOMIC + INSERT INTO functest1 SELECT a + $2; + END; + +-- error: duplicate function body +CREATE FUNCTION functest_S_xxx(x int) RETURNS int + LANGUAGE SQL + AS $$ SELECT x * 2 $$ + RETURN x * 3; + +-- polymorphic arguments not allowed in this form +CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement + LANGUAGE SQL + RETURN x[1]; + +-- check reporting of parse-analysis errors +CREATE FUNCTION functest_S_xx(x date) RETURNS boolean + LANGUAGE SQL + RETURN x > 1; + +-- tricky parsing +CREATE FUNCTION functest_S_15(x int) RETURNS boolean +LANGUAGE SQL +BEGIN ATOMIC + select case when x % 2 = 0 then true else false end; +END; + +SELECT functest_S_1('abcd', '2020-01-01'); +SELECT functest_S_2(ARRAY['1', '2', '3']); +SELECT functest_S_3(); + +SELECT functest_S_10('abcd', '2020-01-01'); +SELECT functest_S_13(); + +SELECT pg_get_functiondef('functest_S_1'::regproc); +SELECT pg_get_functiondef('functest_S_2'::regproc); +SELECT pg_get_functiondef('functest_S_3'::regproc); +SELECT pg_get_functiondef('functest_S_3a'::regproc); +SELECT pg_get_functiondef('functest_S_10'::regproc); +SELECT pg_get_functiondef('functest_S_13'::regproc); +SELECT pg_get_functiondef('functest_S_15'::regproc); +SELECT pg_get_functiondef('functest_S_16'::regproc); + +DROP TABLE functest1 CASCADE; + +-- test with views +CREATE TABLE functest3 (a int); +INSERT INTO functest3 VALUES (1), (2); +CREATE VIEW functestv3 AS SELECT * FROM functest3; + +CREATE FUNCTION functest_S_14() RETURNS bigint + RETURN (SELECT count(*) FROM functestv3); + +SELECT functest_S_14(); + +DROP TABLE functest3 CASCADE; + + +-- information_schema tests + +CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo') + RETURNS int + LANGUAGE SQL + AS 'SELECT $1 + $2'; + +CREATE FUNCTION functest_IS_2(out a int, b int default 1) + RETURNS int + LANGUAGE SQL + AS 'SELECT $1'; + +CREATE FUNCTION functest_IS_3(a int default 1, out b int) + RETURNS int + LANGUAGE SQL + AS 'SELECT $1'; + +SELECT routine_name, ordinal_position, parameter_name, parameter_default + FROM information_schema.parameters JOIN information_schema.routines USING (specific_schema, specific_name) + WHERE routine_schema = 'temp_func_test' AND routine_name ~ '^functest_is_' + ORDER BY 1, 2; + +DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int); + +-- routine usage views + +CREATE FUNCTION functest_IS_4a() RETURNS int LANGUAGE SQL AS 'SELECT 1'; +CREATE FUNCTION functest_IS_4b(x int DEFAULT functest_IS_4a()) RETURNS int LANGUAGE SQL AS 'SELECT x'; + +CREATE SEQUENCE functest1; +CREATE FUNCTION functest_IS_5(x int DEFAULT nextval('functest1')) + RETURNS int + LANGUAGE SQL + AS 'SELECT x'; + +CREATE FUNCTION functest_IS_6() + RETURNS int + LANGUAGE SQL + RETURN nextval('functest1'); + +CREATE TABLE functest2 (a int, b int); + +CREATE FUNCTION functest_IS_7() + RETURNS int + LANGUAGE SQL + RETURN (SELECT count(a) FROM functest2); + +SELECT r0.routine_name, r1.routine_name + FROM information_schema.routine_routine_usage rru + JOIN information_schema.routines r0 ON r0.specific_name = rru.specific_name + JOIN information_schema.routines r1 ON r1.specific_name = rru.routine_name + WHERE r0.routine_schema = 'temp_func_test' AND + r1.routine_schema = 'temp_func_test' + ORDER BY 1, 2; +SELECT routine_name, sequence_name FROM information_schema.routine_sequence_usage + WHERE routine_schema = 'temp_func_test' + ORDER BY 1, 2; +SELECT routine_name, table_name, column_name FROM information_schema.routine_column_usage + WHERE routine_schema = 'temp_func_test' + ORDER BY 1, 2; +SELECT routine_name, table_name FROM information_schema.routine_table_usage + WHERE routine_schema = 'temp_func_test' + ORDER BY 1, 2; + +DROP FUNCTION functest_IS_4a CASCADE; +DROP SEQUENCE functest1 CASCADE; +DROP TABLE functest2 CASCADE; + + +-- overload +CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql' + IMMUTABLE AS 'SELECT $1 > 0'; + +DROP FUNCTION functest_b_1; +DROP FUNCTION functest_b_1; -- error, not found +DROP FUNCTION functest_b_2; -- error, ambiguous + + +-- CREATE OR REPLACE tests + +CREATE FUNCTION functest1(a int) RETURNS int LANGUAGE SQL AS 'SELECT $1'; +CREATE OR REPLACE FUNCTION functest1(a int) RETURNS int LANGUAGE SQL WINDOW AS 'SELECT $1'; +CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1'; +DROP FUNCTION functest1(a int); + + +-- inlining of set-returning functions + +CREATE TABLE functest3 (a int); +INSERT INTO functest3 VALUES (1), (2), (3); + +CREATE FUNCTION functest_sri1() RETURNS SETOF int +LANGUAGE SQL +STABLE +AS ' + SELECT * FROM functest3; +'; + +SELECT * FROM functest_sri1(); +EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1(); + +CREATE FUNCTION functest_sri2() RETURNS SETOF int +LANGUAGE SQL +STABLE +BEGIN ATOMIC + SELECT * FROM functest3; +END; + +SELECT * FROM functest_sri2(); +EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2(); + +DROP TABLE functest3 CASCADE; + + +-- Check behavior of VOID-returning SQL functions + +CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS +$$ SELECT a + 1 $$; +SELECT voidtest1(42); + +CREATE FUNCTION voidtest2(a int, b int) RETURNS VOID LANGUAGE SQL AS +$$ SELECT voidtest1(a + b) $$; +SELECT voidtest2(11,22); + +-- currently, we can inline voidtest2 but not voidtest1 +EXPLAIN (verbose, costs off) SELECT voidtest2(11,22); + +CREATE TEMP TABLE sometable(f1 int); + +CREATE FUNCTION voidtest3(a int) RETURNS VOID LANGUAGE SQL AS +$$ INSERT INTO sometable VALUES(a + 1) $$; +SELECT voidtest3(17); + +CREATE FUNCTION voidtest4(a int) RETURNS VOID LANGUAGE SQL AS +$$ INSERT INTO sometable VALUES(a - 1) RETURNING f1 $$; +SELECT voidtest4(39); + +TABLE sometable; + +CREATE FUNCTION voidtest5(a int) RETURNS SETOF VOID LANGUAGE SQL AS +$$ SELECT generate_series(1, a) $$ STABLE; +SELECT * FROM voidtest5(3); + +-- Regression tests for bugs: + +-- Check that arguments that are R/W expanded datums aren't corrupted by +-- multiple uses. This test knows that array_append() returns a R/W datum +-- and will modify a R/W array input in-place. We use SETOF to prevent +-- inlining of the SQL function. +CREATE FUNCTION double_append(anyarray, anyelement) RETURNS SETOF anyarray +LANGUAGE SQL IMMUTABLE AS +$$ SELECT array_append($1, $2) || array_append($1, $2) $$; + +SELECT double_append(array_append(ARRAY[q1], q2), q3) + FROM (VALUES(1,2,3), (4,5,6)) v(q1,q2,q3); + +-- Things that shouldn't work: + +CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL + AS 'SELECT ''not an integer'';'; + +CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL + AS 'not even SQL'; + +CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL + AS 'SELECT 1, 2, 3;'; + +CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL + AS 'SELECT $2;'; + +CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL + AS 'a', 'b'; + +-- Cleanup +DROP SCHEMA temp_func_test CASCADE; +DROP USER regress_unpriv_user; +RESET search_path; diff --git a/src/test/regress/sql/yb.port.create_index.sql b/src/test/regress/sql/yb.port.create_index.sql new file mode 100644 index 0000000000000..682fb146fdc88 --- /dev/null +++ b/src/test/regress/sql/yb.port.create_index.sql @@ -0,0 +1,258 @@ +-- +-- CREATE_INDEX +-- Create ancillary data structures (i.e. indices) +-- + +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +-- +-- BTREE +-- +CREATE INDEX onek_unique1 ON onek USING btree(unique1 int4_ops ASC); + +CREATE INDEX IF NOT EXISTS onek_unique1 ON onek USING btree(unique1 int4_ops ASC); + +CREATE INDEX IF NOT EXISTS ON onek USING btree(unique1 int4_ops ASC); + +CREATE INDEX onek_unique2 ON onek USING btree(unique2 int4_ops ASC); + +CREATE INDEX onek_hundred ON onek USING btree(hundred int4_ops ASC); + +CREATE INDEX onek_stringu1 ON onek USING btree(stringu1 name_ops ASC); + +CREATE INDEX tenk1_unique1 ON tenk1 USING btree(unique1 int4_ops ASC); + +CREATE INDEX tenk1_unique2 ON tenk1 USING btree(unique2 int4_ops ASC); + +CREATE INDEX tenk1_hundred ON tenk1 USING btree(hundred int4_ops ASC); + +CREATE INDEX tenk1_thous_tenthous ON tenk1 (thousand ASC, tenthous ASC); + +CREATE INDEX tenk2_unique1 ON tenk2 USING btree(unique1 int4_ops ASC); + +CREATE INDEX tenk2_unique2 ON tenk2 USING btree(unique2 int4_ops ASC); + +CREATE INDEX tenk2_hundred ON tenk2 USING btree(hundred int4_ops ASC); + +CREATE INDEX rix ON road USING btree (name text_ops ASC); + +CREATE INDEX iix ON ihighway USING btree (name text_ops ASC); + +CREATE INDEX six ON shighway USING btree (name text_ops ASC); + +-- +-- BTREE partial indices +-- +CREATE INDEX onek2_u1_prtl ON onek2 USING btree(unique1 int4_ops ASC) + where unique1 < 20 or unique1 > 980; + +CREATE INDEX onek2_u2_prtl ON onek2 USING btree(unique2 int4_ops ASC) + where stringu1 < 'B'; + +CREATE INDEX onek2_stu1_prtl ON onek2 USING btree(stringu1 name_ops ASC) + where onek2.stringu1 >= 'J' and onek2.stringu1 < 'K'; + +CREATE TABLE slow_emp4000 ( + home_base box +); + +CREATE TABLE fast_emp4000 ( + home_base box +); + +\set filename :abs_srcdir '/data/rect.data' +COPY slow_emp4000 FROM :'filename'; + +INSERT INTO fast_emp4000 SELECT * FROM slow_emp4000; + +ANALYZE slow_emp4000; +ANALYZE fast_emp4000; + +-- +-- GIN over int[] and text[] +-- +-- Note: GIN currently supports only bitmap scans, not plain indexscans +-- YB Note: ybgin uses plain indexscans, not bitmap scans +-- + +CREATE TABLE array_index_op_test ( + seqno int4, + i int4[], + t text[] +); + +\set filename :abs_srcdir '/data/array.data' +COPY array_index_op_test FROM :'filename'; +ANALYZE array_index_op_test; + +SET enable_seqscan = OFF; +SET enable_indexscan = OFF; +SET enable_bitmapscan = ON; + +-- YB note: the following CREATE INDEXes are commented because errdetail varies. +-- TODO(jason): uncomment when working on (issue #9959). +-- CREATE INDEX NONCONCURRENTLY intarrayidx ON array_index_op_test USING gin (i); +-- +-- CREATE INDEX NONCONCURRENTLY textarrayidx ON array_index_op_test USING gin (t); + +-- And try it with a multicolumn GIN index + +DROP INDEX intarrayidx, textarrayidx; +-- TODO(jason): remove the following drops when working on issue #880 +DROP INDEX intarrayidx; +DROP INDEX textarrayidx; + +CREATE INDEX botharrayidx ON array_index_op_test USING gin (i, t); + +RESET enable_seqscan; +RESET enable_indexscan; +RESET enable_bitmapscan; + +-- +-- Try a GIN index with a lot of items with same key. (GIN creates a posting +-- tree when there are enough duplicates) +-- YB Note: ybgin does not use a posting list or tree +-- +CREATE TABLE array_gin_test (a int[]); + +INSERT INTO array_gin_test SELECT ARRAY[1, g%5, g] FROM generate_series(1, 10000) g; + +CREATE INDEX array_gin_test_idx ON array_gin_test USING gin (a); + +SELECT COUNT(*) FROM array_gin_test WHERE a @> '{2}'; + +DROP TABLE array_gin_test; + +-- +-- Test GIN index's reloptions +-- +CREATE INDEX gin_relopts_test ON array_index_op_test USING gin (i) + WITH (FASTUPDATE=on, GIN_PENDING_LIST_LIMIT=128); +\d+ gin_relopts_test + +-- +-- Test unique null behavior +-- +CREATE TABLE unique_tbl (i int, t text); + +CREATE UNIQUE INDEX unique_idx1 ON unique_tbl (i) NULLS DISTINCT; +CREATE UNIQUE INDEX unique_idx2 ON unique_tbl (i) NULLS NOT DISTINCT; + +INSERT INTO unique_tbl VALUES (1, 'one'); +INSERT INTO unique_tbl VALUES (2, 'two'); +INSERT INTO unique_tbl VALUES (3, 'three'); +INSERT INTO unique_tbl VALUES (4, 'four'); +INSERT INTO unique_tbl VALUES (5, 'one'); +INSERT INTO unique_tbl (t) VALUES ('six'); +INSERT INTO unique_tbl (t) VALUES ('seven'); -- error from unique_idx2 + +DROP INDEX unique_idx1, unique_idx2; +DROP INDEX unique_idx1; +DROP INDEX unique_idx2; + +INSERT INTO unique_tbl (t) VALUES ('seven'); + +-- build indexes on filled table +CREATE UNIQUE INDEX unique_idx3 ON unique_tbl (i) NULLS DISTINCT; -- ok +CREATE UNIQUE INDEX unique_idx4 ON unique_tbl (i) NULLS NOT DISTINCT; -- error +DROP INDEX unique_idx4; + +DELETE FROM unique_tbl WHERE t = 'seven'; + +CREATE UNIQUE INDEX unique_idx4 ON unique_tbl (i) NULLS NOT DISTINCT; -- ok now + +\d unique_tbl +\d unique_idx3 +\d unique_idx4 +SELECT pg_get_indexdef('unique_idx3'::regclass); +SELECT pg_get_indexdef('unique_idx4'::regclass); + +DROP TABLE unique_tbl; + +-- +-- Try some concurrent index builds +-- +-- Unfortunately this only tests about half the code paths because there are +-- no concurrent updates happening to the table at the same time. + +CREATE TABLE concur_heap (f1 text, f2 text); +-- empty table +CREATE INDEX CONCURRENTLY concur_index1 ON concur_heap(f2,f1); +CREATE INDEX CONCURRENTLY IF NOT EXISTS concur_index1 ON concur_heap(f2,f1); +INSERT INTO concur_heap VALUES ('a','b'); +INSERT INTO concur_heap VALUES ('b','b'); +-- unique index +CREATE UNIQUE INDEX CONCURRENTLY concur_index2 ON concur_heap(f1); +CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS concur_index2 ON concur_heap(f1); +-- check if constraint is set up properly to be enforced +INSERT INTO concur_heap VALUES ('b','x'); +-- check if constraint is enforced properly at build time +CREATE UNIQUE INDEX CONCURRENTLY concur_index3 ON concur_heap(f2); +-- test that expression indexes and partial indexes work concurrently +CREATE INDEX CONCURRENTLY concur_index4 on concur_heap(f2) WHERE f1='a'; +CREATE INDEX CONCURRENTLY concur_index5 on concur_heap(f2) WHERE f1='x'; +-- here we also check that you can default the index name +CREATE INDEX CONCURRENTLY on concur_heap((f2||f1)); + +-- You can't do a concurrent index build in a transaction +BEGIN; +CREATE INDEX CONCURRENTLY concur_index7 ON concur_heap(f1); +COMMIT; + +-- But you can do a regular index build in a transaction +BEGIN; +CREATE INDEX std_index on concur_heap(f2); +COMMIT; + +-- Failed builds are left invalid by VACUUM FULL, fixed by REINDEX +-- YB note: VACUUM and REINDEX TABLE are not yet supported +VACUUM FULL concur_heap; +REINDEX TABLE concur_heap; + +-- +-- Test ADD CONSTRAINT USING INDEX +-- + +CREATE TABLE cwi_test( a int , b varchar(10), c char); + +-- add some data so that all tests have something to work with. + +INSERT INTO cwi_test VALUES(1, 2), (3, 4), (5, 6); + +CREATE UNIQUE INDEX cwi_uniq_idx ON cwi_test(a , b); +ALTER TABLE cwi_test ADD primary key USING INDEX cwi_uniq_idx; + +\d cwi_test +\d cwi_uniq_idx + +CREATE UNIQUE INDEX cwi_uniq2_idx ON cwi_test(b , a); +ALTER TABLE cwi_test DROP CONSTRAINT cwi_uniq_idx, + ADD CONSTRAINT cwi_replaced_pkey PRIMARY KEY + USING INDEX cwi_uniq2_idx; + +\d cwi_test +\d cwi_replaced_pkey + +DROP INDEX cwi_replaced_pkey; -- Should fail; a constraint depends on it + +DROP TABLE cwi_test; + +-- ADD CONSTRAINT USING INDEX is forbidden on partitioned tables +CREATE TABLE cwi_test(a int) PARTITION BY hash (a); +create unique index on cwi_test (a); +alter table cwi_test add primary key using index cwi_test_a_idx ; +DROP TABLE cwi_test; + +-- +-- REINDEX (VERBOSE) +-- +CREATE TABLE reindex_verbose(id integer primary key); +\set VERBOSITY terse +REINDEX (VERBOSE) TABLE reindex_verbose; +DROP TABLE reindex_verbose; + +-- +-- REINDEX SCHEMA +-- +REINDEX SCHEMA schema_to_reindex; -- failure, schema does not exist diff --git a/src/test/regress/sql/yb.port.create_misc.sql b/src/test/regress/sql/yb.port.create_misc.sql new file mode 100644 index 0000000000000..baecd0f2f9871 --- /dev/null +++ b/src/test/regress/sql/yb.port.create_misc.sql @@ -0,0 +1,260 @@ +-- +-- CREATE_MISC +-- + +-- +-- a is the type root +-- b and c inherit from a (one-level single inheritance) +-- d inherits from b and c (two-level multiple inheritance) +-- e inherits from c (two-level single inheritance) +-- f inherits from e (three-level single inheritance) +-- +CREATE TABLE a_star ( + class char, + a int4 +); + +CREATE TABLE b_star ( + b text +) INHERITS (a_star); + +CREATE TABLE c_star ( + c name +) INHERITS (a_star); + +CREATE TABLE d_star ( + d float8 +) INHERITS (b_star, c_star); + +CREATE TABLE e_star ( + e int2 +) INHERITS (c_star); + +CREATE TABLE f_star ( + f polygon +) INHERITS (e_star); + +INSERT INTO a_star (class, a) VALUES ('a', 1); + +INSERT INTO a_star (class, a) VALUES ('a', 2); + +INSERT INTO a_star (class) VALUES ('a'); + +INSERT INTO b_star (class, a, b) VALUES ('b', 3, 'mumble'::text); + +INSERT INTO b_star (class, a) VALUES ('b', 4); + +INSERT INTO b_star (class, b) VALUES ('b', 'bumble'::text); + +INSERT INTO b_star (class) VALUES ('b'); + +INSERT INTO c_star (class, a, c) VALUES ('c', 5, 'hi mom'::name); + +INSERT INTO c_star (class, a) VALUES ('c', 6); + +INSERT INTO c_star (class, c) VALUES ('c', 'hi paul'::name); + +INSERT INTO c_star (class) VALUES ('c'); + +INSERT INTO d_star (class, a, b, c, d) + VALUES ('d', 7, 'grumble'::text, 'hi sunita'::name, '0.0'::float8); + +INSERT INTO d_star (class, a, b, c) + VALUES ('d', 8, 'stumble'::text, 'hi koko'::name); + +INSERT INTO d_star (class, a, b, d) + VALUES ('d', 9, 'rumble'::text, '1.1'::float8); + +INSERT INTO d_star (class, a, c, d) + VALUES ('d', 10, 'hi kristin'::name, '10.01'::float8); + +INSERT INTO d_star (class, b, c, d) + VALUES ('d', 'crumble'::text, 'hi boris'::name, '100.001'::float8); + +INSERT INTO d_star (class, a, b) + VALUES ('d', 11, 'fumble'::text); + +INSERT INTO d_star (class, a, c) + VALUES ('d', 12, 'hi avi'::name); + +INSERT INTO d_star (class, a, d) + VALUES ('d', 13, '1000.0001'::float8); + +INSERT INTO d_star (class, b, c) + VALUES ('d', 'tumble'::text, 'hi andrew'::name); + +INSERT INTO d_star (class, b, d) + VALUES ('d', 'humble'::text, '10000.00001'::float8); + +INSERT INTO d_star (class, c, d) + VALUES ('d', 'hi ginger'::name, '100000.000001'::float8); + +INSERT INTO d_star (class, a) VALUES ('d', 14); + +INSERT INTO d_star (class, b) VALUES ('d', 'jumble'::text); + +INSERT INTO d_star (class, c) VALUES ('d', 'hi jolly'::name); + +INSERT INTO d_star (class, d) VALUES ('d', '1000000.0000001'::float8); + +INSERT INTO d_star (class) VALUES ('d'); + +INSERT INTO e_star (class, a, c, e) + VALUES ('e', 15, 'hi carol'::name, '-1'::int2); + +INSERT INTO e_star (class, a, c) + VALUES ('e', 16, 'hi bob'::name); + +INSERT INTO e_star (class, a, e) + VALUES ('e', 17, '-2'::int2); + +INSERT INTO e_star (class, c, e) + VALUES ('e', 'hi michelle'::name, '-3'::int2); + +INSERT INTO e_star (class, a) + VALUES ('e', 18); + +INSERT INTO e_star (class, c) + VALUES ('e', 'hi elisa'::name); + +INSERT INTO e_star (class, e) + VALUES ('e', '-4'::int2); + +INSERT INTO f_star (class, a, c, e, f) + VALUES ('f', 19, 'hi claire'::name, '-5'::int2, '(1,3),(2,4)'::polygon); + +INSERT INTO f_star (class, a, c, e) + VALUES ('f', 20, 'hi mike'::name, '-6'::int2); + +INSERT INTO f_star (class, a, c, f) + VALUES ('f', 21, 'hi marcel'::name, '(11,44),(22,55),(33,66)'::polygon); + +INSERT INTO f_star (class, a, e, f) + VALUES ('f', 22, '-7'::int2, '(111,555),(222,666),(333,777),(444,888)'::polygon); + +INSERT INTO f_star (class, c, e, f) + VALUES ('f', 'hi keith'::name, '-8'::int2, + '(1111,3333),(2222,4444)'::polygon); + +INSERT INTO f_star (class, a, c) + VALUES ('f', 24, 'hi marc'::name); + +INSERT INTO f_star (class, a, e) + VALUES ('f', 25, '-9'::int2); + +INSERT INTO f_star (class, a, f) + VALUES ('f', 26, '(11111,33333),(22222,44444)'::polygon); + +INSERT INTO f_star (class, c, e) + VALUES ('f', 'hi allison'::name, '-10'::int2); + +INSERT INTO f_star (class, c, f) + VALUES ('f', 'hi jeff'::name, + '(111111,333333),(222222,444444)'::polygon); + +INSERT INTO f_star (class, e, f) + VALUES ('f', '-11'::int2, '(1111111,3333333),(2222222,4444444)'::polygon); + +INSERT INTO f_star (class, a) VALUES ('f', 27); + +INSERT INTO f_star (class, c) VALUES ('f', 'hi carl'::name); + +INSERT INTO f_star (class, e) VALUES ('f', '-12'::int2); + +INSERT INTO f_star (class, f) + VALUES ('f', '(11111111,33333333),(22222222,44444444)'::polygon); + +INSERT INTO f_star (class) VALUES ('f'); + +-- Analyze the X_star tables for better plan stability in later tests +ANALYZE a_star; +ANALYZE b_star; +ANALYZE c_star; +ANALYZE d_star; +ANALYZE e_star; +ANALYZE f_star; + +-- +-- inheritance stress test +-- +SELECT * FROM a_star*; + +SELECT * + FROM b_star* x + WHERE x.b = text 'bumble' or x.a < 3; + +SELECT class, a + FROM c_star* x + WHERE x.c ~ text 'hi'; + +SELECT class, b, c + FROM d_star* x + WHERE x.a < 100; + +SELECT class, c FROM e_star* x WHERE x.c NOTNULL; + +SELECT * FROM f_star* x WHERE x.c ISNULL; + +-- grouping and aggregation on inherited sets have been busted in the past... + +SELECT sum(a) FROM a_star*; + +SELECT class, sum(a) FROM a_star* GROUP BY class ORDER BY class; + + +ALTER TABLE f_star RENAME COLUMN f TO ff; + +ALTER TABLE e_star* RENAME COLUMN e TO ee; + +ALTER TABLE d_star* RENAME COLUMN d TO dd; + +ALTER TABLE c_star* RENAME COLUMN c TO cc; + +ALTER TABLE b_star* RENAME COLUMN b TO bb; + +ALTER TABLE a_star* RENAME COLUMN a TO aa; + +SELECT class, aa + FROM a_star* x + WHERE aa ISNULL; + +-- As of Postgres 7.1, ALTER implicitly recurses, +-- so this should be same as ALTER a_star* + +ALTER TABLE a_star RENAME COLUMN aa TO foo; + +SELECT class, foo + FROM a_star* x + WHERE x.foo >= 2; + +ALTER TABLE a_star RENAME COLUMN foo TO aa; + +SELECT * + from a_star* + WHERE aa < 1000; + +ALTER TABLE f_star ADD COLUMN f int4; + +UPDATE f_star SET f = 10; + +ALTER TABLE e_star* ADD COLUMN e int4; + +--UPDATE e_star* SET e = 42; + +SELECT * FROM e_star*; + +ALTER TABLE a_star* ADD COLUMN a text; + +/* YB: not supported +-- That ALTER TABLE should have added TOAST tables. +SELECT relname, reltoastrelid <> 0 AS has_toast_table + FROM pg_class + WHERE oid::regclass IN ('a_star', 'c_star') + ORDER BY 1; +*/ -- YB + +--UPDATE b_star* +-- SET a = text 'gazpacho' +-- WHERE aa > 4; + +SELECT class, aa, a FROM a_star*; diff --git a/src/test/regress/sql/yb.port.create_role.sql b/src/test/regress/sql/yb.port.create_role.sql new file mode 100644 index 0000000000000..292dc08797586 --- /dev/null +++ b/src/test/regress/sql/yb.port.create_role.sql @@ -0,0 +1,138 @@ +-- ok, superuser can create users with any set of privileges +CREATE ROLE regress_role_super SUPERUSER; +CREATE ROLE regress_role_admin CREATEDB CREATEROLE REPLICATION BYPASSRLS; + +-- fail, only superusers can create users with these privileges +SET SESSION AUTHORIZATION regress_role_admin; +CREATE ROLE regress_nosuch_superuser SUPERUSER; +CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS; +CREATE ROLE regress_nosuch_replication REPLICATION; +CREATE ROLE regress_nosuch_bypassrls BYPASSRLS; + +-- ok, having CREATEROLE is enough to create users with these privileges +CREATE ROLE regress_createdb CREATEDB; +CREATE ROLE regress_createrole CREATEROLE; +CREATE ROLE regress_login LOGIN; +CREATE ROLE regress_inherit INHERIT; +CREATE ROLE regress_connection_limit CONNECTION LIMIT 5; +CREATE ROLE regress_encrypted_password ENCRYPTED PASSWORD 'foo'; +CREATE ROLE regress_password_null PASSWORD NULL; + +-- ok, backwards compatible noise words should be ignored +CREATE ROLE regress_noiseword SYSID 12345; + +-- fail, cannot grant membership in superuser role +CREATE ROLE regress_nosuch_super IN ROLE regress_role_super; + +-- fail, database owner cannot have members +CREATE ROLE regress_nosuch_dbowner IN ROLE pg_database_owner; + +-- ok, can grant other users into a role +CREATE ROLE regress_inroles ROLE + regress_role_super, regress_createdb, regress_createrole, regress_login, + regress_inherit, regress_connection_limit, regress_encrypted_password, regress_password_null; + +-- fail, cannot grant a role into itself +CREATE ROLE regress_nosuch_recursive ROLE regress_nosuch_recursive; + +-- ok, can grant other users into a role with admin option +CREATE ROLE regress_adminroles ADMIN + regress_role_super, regress_createdb, regress_createrole, regress_login, + regress_inherit, regress_connection_limit, regress_encrypted_password, regress_password_null; + +-- fail, cannot grant a role into itself with admin option +CREATE ROLE regress_nosuch_admin_recursive ADMIN regress_nosuch_admin_recursive; + +-- fail, regress_createrole does not have CREATEDB privilege +SET SESSION AUTHORIZATION regress_createrole; +CREATE DATABASE regress_nosuch_db; + +-- ok, regress_createrole can create new roles +CREATE ROLE regress_plainrole; + +-- ok, roles with CREATEROLE can create new roles with it +CREATE ROLE regress_rolecreator CREATEROLE; + +-- ok, roles with CREATEROLE can create new roles with privilege they lack +CREATE ROLE regress_tenant CREATEDB CREATEROLE LOGIN INHERIT CONNECTION LIMIT 5; + +-- ok, regress_tenant can create objects within the database +SET SESSION AUTHORIZATION regress_tenant; +CREATE TABLE tenant_table (i integer); +CREATE INDEX tenant_idx ON tenant_table(i); +CREATE VIEW tenant_view AS SELECT * FROM pg_catalog.pg_class; +REVOKE ALL PRIVILEGES ON tenant_table FROM PUBLIC; + +-- fail, these objects belonging to regress_tenant +SET SESSION AUTHORIZATION regress_createrole; +DROP INDEX tenant_idx; +ALTER TABLE tenant_table ADD COLUMN t text; +DROP TABLE tenant_table; +ALTER VIEW tenant_view OWNER TO regress_role_admin; +DROP VIEW tenant_view; + +-- fail, cannot take ownership of these objects from regress_tenant +REASSIGN OWNED BY regress_tenant TO regress_createrole; + +-- ok, having CREATEROLE is enough to create roles in privileged roles +CREATE ROLE regress_read_all_data IN ROLE pg_read_all_data; +CREATE ROLE regress_write_all_data IN ROLE pg_write_all_data; +CREATE ROLE regress_monitor IN ROLE pg_monitor; +CREATE ROLE regress_read_all_settings IN ROLE pg_read_all_settings; +CREATE ROLE regress_read_all_stats IN ROLE pg_read_all_stats; +CREATE ROLE regress_stat_scan_tables IN ROLE pg_stat_scan_tables; +CREATE ROLE regress_read_server_files IN ROLE pg_read_server_files; +CREATE ROLE regress_write_server_files IN ROLE pg_write_server_files; +CREATE ROLE regress_execute_server_program IN ROLE pg_execute_server_program; +CREATE ROLE regress_signal_backend IN ROLE pg_signal_backend; + +-- fail, creation of these roles failed above so they do not now exist +SET SESSION AUTHORIZATION regress_role_admin; +DROP ROLE regress_nosuch_superuser; +DROP ROLE regress_nosuch_replication_bypassrls; +DROP ROLE regress_nosuch_replication; +DROP ROLE regress_nosuch_bypassrls; +DROP ROLE regress_nosuch_super; +DROP ROLE regress_nosuch_dbowner; +DROP ROLE regress_nosuch_recursive; +DROP ROLE regress_nosuch_admin_recursive; +DROP ROLE regress_plainrole; + +-- ok, should be able to drop non-superuser roles we created +DROP ROLE regress_createdb; +DROP ROLE regress_createrole; +DROP ROLE regress_login; +DROP ROLE regress_inherit; +DROP ROLE regress_connection_limit; +DROP ROLE regress_encrypted_password; +DROP ROLE regress_password_null; +DROP ROLE regress_noiseword; +DROP ROLE regress_inroles; +DROP ROLE regress_adminroles; +DROP ROLE regress_rolecreator; +DROP ROLE regress_read_all_data; +DROP ROLE regress_write_all_data; +DROP ROLE regress_monitor; +DROP ROLE regress_read_all_settings; +DROP ROLE regress_read_all_stats; +DROP ROLE regress_stat_scan_tables; +DROP ROLE regress_read_server_files; +DROP ROLE regress_write_server_files; +DROP ROLE regress_execute_server_program; +DROP ROLE regress_signal_backend; + +-- fail, role still owns database objects +DROP ROLE regress_tenant; + +-- fail, cannot drop ourself nor superusers +DROP ROLE regress_role_super; +DROP ROLE regress_role_admin; + +-- ok +RESET SESSION AUTHORIZATION; +DROP INDEX tenant_idx; +DROP TABLE tenant_table; +DROP VIEW tenant_view; +DROP ROLE regress_tenant; +DROP ROLE regress_role_admin; +DROP ROLE regress_role_super; diff --git a/src/test/regress/sql/yb.port.create_table.sql b/src/test/regress/sql/yb.port.create_table.sql new file mode 100644 index 0000000000000..f2150b0e8cf6f --- /dev/null +++ b/src/test/regress/sql/yb.port.create_table.sql @@ -0,0 +1,738 @@ +-- +-- CREATE_TABLE +-- + +-- Error cases +-- TODO(jason): uncomment when issue #1975 is closed or closing. +-- CREATE TABLE unknowntab ( +-- u unknown -- fail +-- ); + +CREATE TYPE unknown_comptype AS ( + u unknown -- fail +); + +-- invalid: non-lowercase quoted reloptions identifiers +CREATE TABLE tas_case WITH ("Fillfactor" = 10) AS SELECT 1 a; +CREATE TABLE tas_case (a text) WITH ("Oids" = true); + +CREATE UNLOGGED TABLE unlogged1 (a int primary key); -- OK +CREATE TEMPORARY TABLE unlogged2 (a int primary key); -- OK +SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname; +REINDEX INDEX unlogged2_pkey; +SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname; +DROP TABLE unlogged2; +CREATE TABLE pg_temp.implicitly_temp (a int primary key); -- OK +CREATE TEMP TABLE explicitly_temp (a int primary key); -- also OK +CREATE TEMP TABLE pg_temp.doubly_temp (a int primary key); -- also OK +CREATE TEMP TABLE public.temp_to_perm (a int primary key); -- not OK + +CREATE TABLE as_select1 AS SELECT * FROM pg_class WHERE relkind = 'r'; +CREATE TABLE as_select1 AS SELECT * FROM pg_class WHERE relkind = 'r'; +CREATE TABLE IF NOT EXISTS as_select1 AS SELECT * FROM pg_class WHERE relkind = 'r'; +DROP TABLE as_select1; + +/* YB: CREATE TABLE AS EXECUTE not supported yet +PREPARE select1 AS SELECT 1 as a; +CREATE TABLE as_select1 AS EXECUTE select1; +CREATE TABLE as_select1 AS EXECUTE select1; +SELECT * FROM as_select1; +CREATE TABLE IF NOT EXISTS as_select1 AS EXECUTE select1; +DROP TABLE as_select1; +DEALLOCATE select1; +*/ -- YB + +-- create an extra wide table to test for issues related to that +-- (temporarily hide query, to avoid the long CREATE TABLE stmt) +\set ECHO none +SELECT 'CREATE TABLE extra_wide_table(firstc text, '|| array_to_string(array_agg('c'||i||' bool'),',')||', lastc text);' +FROM generate_series(1, 1100) g(i) +\gexec +\set ECHO all +INSERT INTO extra_wide_table(firstc, lastc) VALUES('first col', 'last col'); +SELECT firstc, lastc FROM extra_wide_table; + +/* YB: OIDS not supported for user tables +-- check that tables with oids cannot be created anymore +CREATE TABLE withoid() WITH OIDS; +CREATE TABLE withoid() WITH (oids); +CREATE TABLE withoid() WITH (oids = true); +*/ -- YB +-- but explicitly not adding oids is still supported +CREATE TEMP TABLE withoutoid() WITHOUT OIDS; DROP TABLE withoutoid; +CREATE TEMP TABLE withoutoid() WITH (oids = false); DROP TABLE withoutoid; + +-- check restriction with default expressions +-- invalid use of column reference in default expressions +CREATE TABLE default_expr_column (id int DEFAULT (id)); +CREATE TABLE default_expr_column (id int DEFAULT (bar.id)); +CREATE TABLE default_expr_agg_column (id int DEFAULT (avg(id))); +-- invalid column definition +CREATE TABLE default_expr_non_column (a int DEFAULT (avg(non_existent))); +-- invalid use of aggregate +CREATE TABLE default_expr_agg (a int DEFAULT (avg(1))); +-- invalid use of subquery +CREATE TABLE default_expr_agg (a int DEFAULT (select 1)); +-- invalid use of set-returning function +CREATE TABLE default_expr_agg (a int DEFAULT (generate_series(1,3))); + +/* YB : savepoints for DDL are not supported +-- Verify that subtransaction rollback restores rd_createSubid. +BEGIN; +CREATE TABLE remember_create_subid (c int); +SAVEPOINT q; DROP TABLE remember_create_subid; ROLLBACK TO q; +COMMIT; +DROP TABLE remember_create_subid; + +-- Verify that subtransaction rollback restores rd_firstRelfilenodeSubid. +CREATE TABLE remember_node_subid (c int); +BEGIN; +ALTER TABLE remember_node_subid ALTER c TYPE bigint; +SAVEPOINT q; DROP TABLE remember_node_subid; ROLLBACK TO q; +COMMIT; +DROP TABLE remember_node_subid; +*/ -- YB +-- +-- Partitioned tables +-- + +-- cannot combine INHERITS and PARTITION BY (although grammar allows) +CREATE TABLE partitioned ( + a int +) INHERITS (some_table) PARTITION BY LIST (a); + +-- cannot use more than 1 column as partition key for list partitioned table +CREATE TABLE partitioned ( + a1 int, + a2 int +) PARTITION BY LIST (a1, a2); -- fail + +/* YB: exclude using GIST not suppored +-- unsupported constraint type for partitioned tables +CREATE TABLE partitioned ( + a int, + EXCLUDE USING gist (a WITH &&) +) PARTITION BY RANGE (a); +*/ -- YB + +-- prevent using prohibited expressions in the key +CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE; +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (retset(a)); +DROP FUNCTION retset(int); + +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE ((avg(a))); + +CREATE TABLE partitioned ( + a int, + b int +) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b))); + +CREATE TABLE partitioned ( + a int +) PARTITION BY LIST ((a LIKE (SELECT 1))); + +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE ((42)); + +CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE; +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (const_func()); +DROP FUNCTION const_func(); + +-- only accept valid partitioning strategy +CREATE TABLE partitioned ( + a int +) PARTITION BY MAGIC (a); + +-- specified column must be present in the table +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (b); + +-- cannot use system columns in partition key +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (xmin); + +-- cannot use pseudotypes +CREATE TABLE partitioned ( + a int, + b int +) PARTITION BY RANGE (((a, b))); +CREATE TABLE partitioned ( + a int, + b int +) PARTITION BY RANGE (a, ('unknown')); + +-- functions in key must be immutable +CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL; +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (immut_func(a)); +DROP FUNCTION immut_func(int); + +-- prevent using columns of unsupported types in key (type must have a btree operator class) +CREATE TABLE partitioned ( + a point +) PARTITION BY LIST (a); +CREATE TABLE partitioned ( + a point +) PARTITION BY LIST (a point_ops); +CREATE TABLE partitioned ( + a point +) PARTITION BY RANGE (a); +CREATE TABLE partitioned ( + a point +) PARTITION BY RANGE (a point_ops); + +-- cannot add NO INHERIT constraints to partitioned tables +CREATE TABLE partitioned ( + a int, + CONSTRAINT check_a CHECK (a > 0) NO INHERIT +) PARTITION BY RANGE (a); + +-- some checks after successful creation of a partitioned table +CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL; + +CREATE TABLE partitioned ( + a int, + b int, + c text, + d text +) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "C"); + +-- check relkind +SELECT relkind FROM pg_class WHERE relname = 'partitioned'; + +-- prevent a function referenced in partition key from being dropped +DROP FUNCTION plusone(int); + +-- partitioned table cannot participate in regular inheritance +CREATE TABLE partitioned2 ( + a int, + b text +) PARTITION BY RANGE ((a+1), substr(b, 1, 5)); +CREATE TABLE fail () INHERITS (partitioned2); + +-- Partition key in describe output +\d partitioned +\d+ partitioned2 + +INSERT INTO partitioned2 VALUES (1, 'hello'); +CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO (100, 'ccccc'); +\d+ part2_1 + +DROP TABLE partitioned, partitioned2; + +-- check reference to partitioned table's rowtype in partition descriptor +create table partitioned (a int, b int) + partition by list ((row(a, b)::partitioned)); +create table partitioned1 + partition of partitioned for values in ('(1,2)'::partitioned); +create table partitioned2 + partition of partitioned for values in ('(2,4)'::partitioned); +explain (costs off) +select * from partitioned where row(a,b)::partitioned = '(1,2)'::partitioned; +drop table partitioned; + +-- whole-row Var in partition key works too +create table partitioned (a int, b int) + partition by list ((partitioned)); +create table partitioned1 + partition of partitioned for values in ('(1,2)'); +create table partitioned2 + partition of partitioned for values in ('(2,4)'); +explain (costs off) +select * from partitioned where partitioned = '(1,2)'::partitioned; +\d+ partitioned1 +drop table partitioned; + +-- check that dependencies of partition columns are handled correctly +create domain intdom1 as int; + +create table partitioned ( + a intdom1, + b text +) partition by range (a); + +alter table partitioned drop column a; -- fail + +drop domain intdom1; -- fail, requires cascade + +drop domain intdom1 cascade; + +table partitioned; -- gone + +-- likewise for columns used in partition expressions +create domain intdom1 as int; + +create table partitioned ( + a intdom1, + b text +) partition by range (plusone(a)); + +alter table partitioned drop column a; -- fail + +drop domain intdom1; -- fail, requires cascade + +drop domain intdom1 cascade; + +table partitioned; -- gone + + +-- +-- Partitions +-- + +-- check partition bound syntax + +CREATE TABLE list_parted ( + a int +) PARTITION BY LIST (a); +CREATE TABLE part_p1 PARTITION OF list_parted FOR VALUES IN ('1'); +CREATE TABLE part_p2 PARTITION OF list_parted FOR VALUES IN (2); +CREATE TABLE part_p3 PARTITION OF list_parted FOR VALUES IN ((2+1)); +CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null); +\d+ list_parted + +-- forbidden expressions for partition bound with list partitioned table +CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename); +CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename.somename); +CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (a); +CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (sum(a)); +CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (sum(somename)); +CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (sum(1)); +CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN ((select 1)); +CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (generate_series(4, 6)); +CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN ((1+1) collate "POSIX"); + +-- syntax does not allow empty list of values for list partitions +CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (); +-- trying to specify range for list partitioned table +CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2); +-- trying to specify modulus and remainder for list partitioned table +CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES WITH (MODULUS 10, REMAINDER 1); + +-- check default partition cannot be created more than once +CREATE TABLE part_default PARTITION OF list_parted DEFAULT; +CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT; + +-- specified literal can't be cast to the partition column data type +CREATE TABLE bools ( + a bool +) PARTITION BY LIST (a); +CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1); +DROP TABLE bools; + +-- specified literal can be cast, and the cast might not be immutable +CREATE TABLE moneyp ( + a money +) PARTITION BY LIST (a); +CREATE TABLE moneyp_10 PARTITION OF moneyp FOR VALUES IN (10); +CREATE TABLE moneyp_11 PARTITION OF moneyp FOR VALUES IN ('11'); +CREATE TABLE moneyp_12 PARTITION OF moneyp FOR VALUES IN (to_char(12, '99')::int); +DROP TABLE moneyp; + +-- cast is immutable +CREATE TABLE bigintp ( + a bigint +) PARTITION BY LIST (a); +CREATE TABLE bigintp_10 PARTITION OF bigintp FOR VALUES IN (10); +-- fails due to overlap: +CREATE TABLE bigintp_10_2 PARTITION OF bigintp FOR VALUES IN ('10'); +DROP TABLE bigintp; + +CREATE TABLE range_parted ( + a date +) PARTITION BY RANGE (a); + +-- forbidden expressions for partition bounds with range partitioned table +CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted + FOR VALUES FROM (somename) TO ('2019-01-01'); +CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted + FOR VALUES FROM (somename.somename) TO ('2019-01-01'); +CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted + FOR VALUES FROM (a) TO ('2019-01-01'); +CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted + FOR VALUES FROM (max(a)) TO ('2019-01-01'); +CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted + FOR VALUES FROM (max(somename)) TO ('2019-01-01'); +CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted + FOR VALUES FROM (max('2019-02-01'::date)) TO ('2019-01-01'); +CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted + FOR VALUES FROM ((select 1)) TO ('2019-01-01'); +CREATE TABLE part_bogus_expr_fail PARTITION OF range_parted + FOR VALUES FROM (generate_series(1, 3)) TO ('2019-01-01'); + +-- trying to specify list for range partitioned table +CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a'); +-- trying to specify modulus and remainder for range partitioned table +CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES WITH (MODULUS 10, REMAINDER 1); +-- each of start and end bounds must have same number of values as the +-- length of the partition key +CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z'); +CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1); + +-- cannot specify null values in range bounds +CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (maxvalue); + +-- trying to specify modulus and remainder for range partitioned table +CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES WITH (MODULUS 10, REMAINDER 1); + +-- check partition bound syntax for the hash partition +CREATE TABLE hash_parted ( + a int +) PARTITION BY HASH (a); +CREATE TABLE hpart_1 PARTITION OF hash_parted FOR VALUES WITH (MODULUS 10, REMAINDER 0); +CREATE TABLE hpart_2 PARTITION OF hash_parted FOR VALUES WITH (MODULUS 50, REMAINDER 1); +CREATE TABLE hpart_3 PARTITION OF hash_parted FOR VALUES WITH (MODULUS 200, REMAINDER 2); +CREATE TABLE hpart_4 PARTITION OF hash_parted FOR VALUES WITH (MODULUS 10, REMAINDER 3); +-- modulus 25 is factor of modulus of 50 but 10 is not a factor of 25. +CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 25, REMAINDER 3); +-- previous modulus 50 is factor of 150 but this modulus is not a factor of next modulus 200. +CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 150, REMAINDER 3); +-- overlapping remainders +CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 100, REMAINDER 3); +-- trying to specify range for the hash partitioned table +CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z'); +-- trying to specify list value for the hash partitioned table +CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000); + +-- trying to create default partition for the hash partitioned table +CREATE TABLE fail_default_part PARTITION OF hash_parted DEFAULT; + +-- check if compatible with the specified parent + +-- cannot create as partition of a non-partitioned table +CREATE TABLE unparted ( + a int +); +CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a'); +CREATE TABLE fail_part PARTITION OF unparted FOR VALUES WITH (MODULUS 2, REMAINDER 1); +DROP TABLE unparted; + +-- cannot create a permanent rel as partition of a temp rel +CREATE TEMP TABLE temp_parted ( + a int +) PARTITION BY LIST (a); +CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a'); +DROP TABLE temp_parted; + +-- check for partition bound overlap and other invalid specifications + +CREATE TABLE list_parted2 ( + a varchar +) PARTITION BY LIST (a); +CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z'); +CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b'); +CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT; + +CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null); +CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c'); +-- check default partition overlap +INSERT INTO list_parted2 VALUES('X'); +CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y'); + +CREATE TABLE range_parted2 ( + a int +) PARTITION BY RANGE (a); + +-- trying to create range partition with empty range +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0); +-- note that the range '[1, 1)' has no elements +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1); + +CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (minvalue) TO (1); +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (minvalue) TO (2); +CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10); +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (-1) TO (1); +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (maxvalue); +CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30); +CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40); +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30); +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50); + +-- Create a default partition for range partitioned table +CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT; + +-- More than one default partition is not allowed, so this should give error +CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT; + +-- Check if the range for default partitions overlap +INSERT INTO range_parted2 VALUES (85); +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90); +CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100); + +-- now check for multi-column range partition key +CREATE TABLE range_parted3 ( + a int, + b int +) PARTITION BY RANGE (a, (b+1)); + +CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, minvalue) TO (0, maxvalue); +CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, minvalue) TO (0, 1); + +CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, 1); +CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10); +CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue); +CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20); +CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT; + +-- cannot create a partition that says column b is allowed to range +-- from -infinity to +infinity, while there exist partitions that have +-- more specific ranges +CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue); + +-- check for partition bound overlap and other invalid specifications for the hash partition +CREATE TABLE hash_parted2 ( + a varchar +) PARTITION BY HASH (a); +CREATE TABLE h2part_1 PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 4, REMAINDER 2); +CREATE TABLE h2part_2 PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 8, REMAINDER 0); +CREATE TABLE h2part_3 PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 8, REMAINDER 4); +CREATE TABLE h2part_4 PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 8, REMAINDER 5); +-- overlap with part_4 +CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 2, REMAINDER 1); +-- modulus must be greater than zero +CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 0, REMAINDER 1); +-- remainder must be greater than or equal to zero and less than modulus +CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 8, REMAINDER 8); + +-- check schema propagation from parent + +CREATE TABLE parted ( + a text, + b int NOT NULL DEFAULT 0, + CONSTRAINT check_a CHECK (length(a) > 0) +) PARTITION BY LIST (a); + +CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a'); + +-- only inherited attributes (never local ones) +SELECT attname, attislocal, attinhcount FROM pg_attribute + WHERE attrelid = 'part_a'::regclass and attnum > 0 + ORDER BY attnum; + +-- able to specify column default, column constraint, and table constraint + +-- first check the "column specified more than once" error +CREATE TABLE part_b PARTITION OF parted ( + b NOT NULL, + b DEFAULT 1, + b CHECK (b >= 0), + CONSTRAINT check_a CHECK (length(a) > 0) +) FOR VALUES IN ('b'); + +CREATE TABLE part_b PARTITION OF parted ( + b NOT NULL DEFAULT 1, + CONSTRAINT check_a CHECK (length(a) > 0), + CONSTRAINT check_b CHECK (b >= 0) +) FOR VALUES IN ('b'); +-- conislocal should be false for any merged constraints, true otherwise +SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass ORDER BY conislocal, coninhcount; + +-- Once check_b is added to the parent, it should be made non-local for part_b +ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0); +SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass; + +-- Neither check_a nor check_b are droppable from part_b +ALTER TABLE part_b DROP CONSTRAINT check_a; +ALTER TABLE part_b DROP CONSTRAINT check_b; + +-- And dropping it from parted should leave no trace of them on part_b, unlike +-- traditional inheritance where they will be left behind, because they would +-- be local constraints. +ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b; +SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass; + +-- specify PARTITION BY for a partition +CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c); +CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR VALUES IN ('c') PARTITION BY RANGE ((b)); + +-- create a level-2 partition +CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10); + +-- check that NOT NULL and default value are inherited correctly +create table parted_notnull_inh_test (a int default 1, b int not null default 0) partition by list (a); +create table parted_notnull_inh_test1 partition of parted_notnull_inh_test (a not null, b default 1) for values in (1); +insert into parted_notnull_inh_test (b) values (null); +-- note that while b's default is overridden, a's default is preserved +\d parted_notnull_inh_test1 +drop table parted_notnull_inh_test; + +-- check that collations are assigned in partition bound expressions +create table parted_boolean_col (a bool, b text) partition by list(a); +create table parted_boolean_less partition of parted_boolean_col + for values in ('foo' < 'bar'); +create table parted_boolean_greater partition of parted_boolean_col + for values in ('foo' > 'bar'); +drop table parted_boolean_col; + +-- check for a conflicting COLLATE clause +create table parted_collate_must_match (a text collate "C", b text collate "C") + partition by range (a); +-- on the partition key +create table parted_collate_must_match1 partition of parted_collate_must_match + (a collate "POSIX") for values from ('a') to ('m'); +-- on another column +create table parted_collate_must_match2 partition of parted_collate_must_match + (b collate "POSIX") for values from ('m') to ('z'); +drop table parted_collate_must_match; + +-- check that non-matching collations for partition bound +-- expressions are coerced to the right collation + +create table test_part_coll_posix (a text) partition by range (a collate "POSIX"); +-- ok, collation is implicitly coerced +create table test_part_coll partition of test_part_coll_posix for values from ('a' collate "C") to ('g'); +-- ok +create table test_part_coll2 partition of test_part_coll_posix for values from ('g') to ('m'); +-- ok, collation is implicitly coerced +create table test_part_coll_cast partition of test_part_coll_posix for values from (name 'm' collate "C") to ('s'); +-- ok; partition collation silently overrides the default collation of type 'name' +create table test_part_coll_cast2 partition of test_part_coll_posix for values from (name 's') to ('z'); + +drop table test_part_coll_posix; + +-- Partition bound in describe output +\d+ part_b + +-- Both partition bound and partition key in describe output +\d+ part_c + +-- a level-2 partition's constraint will include the parent's expressions +\d+ part_c_1_10 + +-- Show partition count in the parent's describe output +-- Tempted to include \d+ output listing partitions with bound info but +-- output could vary depending on the order in which partition oids are +-- returned. +\d parted +\d hash_parted + +-- check that we get the expected partition constraints +CREATE TABLE range_parted4 (a int, b int, c int) PARTITION BY RANGE (abs(a), abs(b), c); +CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE); +\d+ unbounded_range_part +DROP TABLE unbounded_range_part; +CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE); +\d+ range_parted4_1 +CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE); +\d+ range_parted4_2 +CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE); +\d+ range_parted4_3 +DROP TABLE range_parted4; + +-- user-defined operator class in partition key +CREATE FUNCTION my_int4_sort(int4,int4) RETURNS int LANGUAGE sql + AS $$ SELECT CASE WHEN $1 = $2 THEN 0 WHEN $1 > $2 THEN 1 ELSE -1 END; $$; +CREATE OPERATOR CLASS test_int4_ops FOR TYPE int4 USING lsm AS /* YB: btree access method doesn't apply */ + OPERATOR 1 < (int4,int4), OPERATOR 2 <= (int4,int4), + OPERATOR 3 = (int4,int4), OPERATOR 4 >= (int4,int4), + OPERATOR 5 > (int4,int4), FUNCTION 1 my_int4_sort(int4,int4); +CREATE TABLE partkey_t (a int4) PARTITION BY RANGE (a test_int4_ops); +CREATE TABLE partkey_t_1 PARTITION OF partkey_t FOR VALUES FROM (0) TO (1000); +INSERT INTO partkey_t VALUES (100); +INSERT INTO partkey_t VALUES (200); + +-- cleanup +DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3; +DROP TABLE partkey_t, hash_parted, hash_parted2; +DROP OPERATOR CLASS test_int4_ops USING lsm; /* YB: btree access method doesn't apply */ +DROP FUNCTION my_int4_sort(int4,int4); + +-- comments on partitioned tables columns +CREATE TABLE parted_col_comment (a int, b text) PARTITION BY LIST (a); +COMMENT ON TABLE parted_col_comment IS 'Am partitioned table'; +COMMENT ON COLUMN parted_col_comment.a IS 'Partition key'; +SELECT obj_description('parted_col_comment'::regclass); +\d+ parted_col_comment +DROP TABLE parted_col_comment; + +-- list partitioning on array type column +CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a); +CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}'); +\d+ arrlp12 +DROP TABLE arrlp; + +-- partition on boolean column +create table boolspart (a bool) partition by list (a); +create table boolspart_t partition of boolspart for values in (true); +create table boolspart_f partition of boolspart for values in (false); +\d+ boolspart +drop table boolspart; + +-- partitions mixing temporary and permanent relations +create table perm_parted (a int) partition by list (a); +create temporary table temp_parted (a int) partition by list (a); +create table perm_part partition of temp_parted default; -- error +create temp table temp_part partition of perm_parted default; -- error +create temp table temp_part partition of temp_parted default; -- ok +drop table perm_parted cascade; +drop table temp_parted cascade; + +-- check that adding partitions to a table while it is being used is prevented +create table tab_part_create (a int) partition by list (a); +create or replace function func_part_create() returns trigger + language plpgsql as $$ + begin + execute 'create table tab_part_create_1 partition of tab_part_create for values in (1)'; + return null; + end $$; +create trigger trig_part_create before insert on tab_part_create + for each statement execute procedure func_part_create(); +insert into tab_part_create values (1); +drop table tab_part_create; +drop function func_part_create(); + +-- test using a volatile expression as partition bound +create table volatile_partbound_test (partkey timestamp) partition by range (partkey); +create table volatile_partbound_test1 partition of volatile_partbound_test for values from (minvalue) to (current_timestamp); +create table volatile_partbound_test2 partition of volatile_partbound_test for values from (current_timestamp) to (maxvalue); +-- this should go into the partition volatile_partbound_test2 +insert into volatile_partbound_test values (current_timestamp); +select tableoid::regclass from volatile_partbound_test; +drop table volatile_partbound_test; + +-- test the case where a check constraint on default partition allows +-- to avoid scanning it when adding a new partition +create table defcheck (a int, b int) partition by list (b); +create table defcheck_def (a int, c int, b int); +alter table defcheck_def drop c; +alter table defcheck attach partition defcheck_def default; +alter table defcheck_def add check (b <= 0 and b is not null); +create table defcheck_1 partition of defcheck for values in (1, null); + +-- test that complex default partition constraints are enforced correctly +insert into defcheck_def values (0, 0); +create table defcheck_0 partition of defcheck for values in (0); +drop table defcheck; + +-- tests of column drop with partition tables and indexes using +-- predicates and expressions. +create table part_column_drop ( + useless_1 int, + id int, + useless_2 int, + d int, + b int, + useless_3 int +) partition by range (id); +alter table part_column_drop drop column useless_1; +alter table part_column_drop drop column useless_2; +alter table part_column_drop drop column useless_3; +create index part_column_drop_b_pred on part_column_drop(b) where b = 1; +create index part_column_drop_b_expr on part_column_drop((b = 1)); +create index part_column_drop_d_pred on part_column_drop(d) where d = 2; +create index part_column_drop_d_expr on part_column_drop((d = 2)); +create table part_column_drop_1_10 partition of + part_column_drop for values from (1) to (10); +\d part_column_drop +\d part_column_drop_1_10 +drop table part_column_drop; diff --git a/src/test/regress/sql/yb.port.create_table_like.sql b/src/test/regress/sql/yb.port.create_table_like.sql new file mode 100644 index 0000000000000..6630474497c13 --- /dev/null +++ b/src/test/regress/sql/yb.port.create_table_like.sql @@ -0,0 +1,223 @@ +/* Test inheritance of structure (LIKE) */ +CREATE TABLE inhx (xx text DEFAULT 'text'); + +/* + * Test double inheritance + * + * Ensure that defaults are NOT included unless + * INCLUDING DEFAULTS is specified + */ +CREATE TABLE ctla (aa TEXT); +CREATE TABLE ctlb (bb TEXT) INHERITS (ctla); + +CREATE TABLE foo (LIKE nonexistent); + +CREATE TABLE inhe (ee text, LIKE inhx) inherits (ctlb); +INSERT INTO inhe VALUES ('ee-col1', 'ee-col2', DEFAULT, 'ee-col4'); +SELECT * FROM inhe; /* Columns aa, bb, xx value NULL, ee */ +SELECT * FROM inhx; /* Empty set since LIKE inherits structure only */ +SELECT * FROM ctlb; /* Has ee entry */ +SELECT * FROM ctla; /* Has ee entry */ + +CREATE TABLE inhf (LIKE inhx, LIKE inhx); /* Throw error */ + +CREATE TABLE inhf (LIKE inhx INCLUDING DEFAULTS INCLUDING CONSTRAINTS); +INSERT INTO inhf DEFAULT VALUES; +SELECT * FROM inhf; /* Single entry with value 'text' */ + +ALTER TABLE inhx add constraint foo CHECK (xx = 'text'); +ALTER TABLE inhx ADD PRIMARY KEY (xx); +CREATE TABLE inhg (LIKE inhx); /* Doesn't copy constraint */ +INSERT INTO inhg VALUES ('foo'); +DROP TABLE inhg; +CREATE TABLE inhg (x text, LIKE inhx INCLUDING CONSTRAINTS, y text); /* Copies constraints */ +INSERT INTO inhg VALUES ('x', 'text', 'y'); /* Succeeds */ +INSERT INTO inhg VALUES ('x', 'text', 'y'); /* Succeeds -- Unique constraints not copied */ +INSERT INTO inhg VALUES ('x', 'foo', 'y'); /* fails due to constraint */ +SELECT * FROM inhg; /* Two records with three columns in order x=x, xx=text, y=y */ +DROP TABLE inhg; + +CREATE TABLE test_like_id_1 (a bigint GENERATED ALWAYS AS IDENTITY, b text); +\d test_like_id_1 +INSERT INTO test_like_id_1 (b) VALUES ('b1'); +SELECT * FROM test_like_id_1; +CREATE TABLE test_like_id_2 (LIKE test_like_id_1); +\d test_like_id_2 +INSERT INTO test_like_id_2 (b) VALUES ('b2'); +SELECT * FROM test_like_id_2; -- identity was not copied +CREATE TABLE test_like_id_3 (LIKE test_like_id_1 INCLUDING IDENTITY); +\d test_like_id_3 +INSERT INTO test_like_id_3 (b) VALUES ('b3'); +SELECT * FROM test_like_id_3; -- identity was copied and applied +DROP TABLE test_like_id_1, test_like_id_2, test_like_id_3; + +CREATE TABLE test_like_gen_1 (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +\d test_like_gen_1 +INSERT INTO test_like_gen_1 (a) VALUES (1); +SELECT * FROM test_like_gen_1; +CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1); +\d test_like_gen_2 +INSERT INTO test_like_gen_2 (a) VALUES (1); +SELECT * FROM test_like_gen_2; +CREATE TABLE test_like_gen_3 (LIKE test_like_gen_1 INCLUDING GENERATED); +\d test_like_gen_3 +INSERT INTO test_like_gen_3 (a) VALUES (1); +SELECT * FROM test_like_gen_3; +DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3; + +-- also test generated column with a "forward" reference (bug #16342) +CREATE TABLE test_like_4 (b int DEFAULT 42, + c int GENERATED ALWAYS AS (a * 2) STORED, + a int CHECK (a > 0)); +\d test_like_4 +CREATE TABLE test_like_4a (LIKE test_like_4); +CREATE TABLE test_like_4b (LIKE test_like_4 INCLUDING DEFAULTS); +CREATE TABLE test_like_4c (LIKE test_like_4 INCLUDING GENERATED); +CREATE TABLE test_like_4d (LIKE test_like_4 INCLUDING DEFAULTS INCLUDING GENERATED); +\d test_like_4a +INSERT INTO test_like_4a (a) VALUES(11); +SELECT a, b, c FROM test_like_4a; +\d test_like_4b +INSERT INTO test_like_4b (a) VALUES(11); +SELECT a, b, c FROM test_like_4b; +\d test_like_4c +INSERT INTO test_like_4c (a) VALUES(11); +SELECT a, b, c FROM test_like_4c; +\d test_like_4d +INSERT INTO test_like_4d (a) VALUES(11); +SELECT a, b, c FROM test_like_4d; + +-- Test renumbering of Vars when combining LIKE with inheritance +CREATE TABLE test_like_5 (x point, y point, z point); +CREATE TABLE test_like_5x (p int CHECK (p > 0), + q int GENERATED ALWAYS AS (p * 2) STORED); +CREATE TABLE test_like_5c (LIKE test_like_4 INCLUDING ALL) + INHERITS (test_like_5, test_like_5x); +\d test_like_5c + +DROP TABLE test_like_4, test_like_4a, test_like_4b, test_like_4c, test_like_4d; +DROP TABLE test_like_5, test_like_5x, test_like_5c; + +CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */ +INSERT INTO inhg VALUES (5, 10); +INSERT INTO inhg VALUES (20, 10); -- should fail +DROP TABLE inhg; +/* Multiple primary keys creation should fail */ +CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, PRIMARY KEY(x)); /* fails */ +CREATE TABLE inhz (xx text DEFAULT 'text', yy int UNIQUE); +CREATE UNIQUE INDEX inhz_xx_idx on inhz (xx) WHERE xx <> 'test'; +/* Ok to create multiple unique indexes */ +CREATE TABLE inhg (x text UNIQUE, LIKE inhz INCLUDING INDEXES); +INSERT INTO inhg (xx, yy, x) VALUES ('test', 5, 10); +INSERT INTO inhg (xx, yy, x) VALUES ('test', 10, 15); +INSERT INTO inhg (xx, yy, x) VALUES ('foo', 10, 15); -- should fail +DROP TABLE inhg; +DROP TABLE inhz; + +/* Use primary key imported by LIKE for self-referential FK constraint */ +CREATE TABLE inhz (x text REFERENCES inhz, LIKE inhx INCLUDING INDEXES); +\d inhz +DROP TABLE inhz; + +-- including storage and comments +CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text); +CREATE INDEX ctlt1_b_key ON ctlt1 (b); +CREATE INDEX ctlt1_fnidx ON ctlt1 ((a || b)); +CREATE STATISTICS ctlt1_a_b_stat ON a,b FROM ctlt1; +CREATE STATISTICS ctlt1_expr_stat ON (a || b) FROM ctlt1; +COMMENT ON STATISTICS ctlt1_a_b_stat IS 'ab stats'; +COMMENT ON STATISTICS ctlt1_expr_stat IS 'ab expr stats'; +COMMENT ON COLUMN ctlt1.a IS 'A'; +COMMENT ON COLUMN ctlt1.b IS 'B'; +COMMENT ON CONSTRAINT ctlt1_a_check ON ctlt1 IS 't1_a_check'; +COMMENT ON INDEX ctlt1_pkey IS 'index pkey'; +COMMENT ON INDEX ctlt1_b_key IS 'index b_key'; +ALTER TABLE ctlt1 ALTER COLUMN a SET STORAGE MAIN; +-- Uncomment the following when ALTER COLUMN is supported (#1200) + +CREATE TABLE ctlt2 (c text); +ALTER TABLE ctlt2 ALTER COLUMN c SET STORAGE EXTERNAL; +COMMENT ON COLUMN ctlt2.c IS 'C'; + +CREATE TABLE ctlt3 (a text CHECK (length(a) < 5), c text CHECK (length(c) < 7)); +ALTER TABLE ctlt3 ALTER COLUMN c SET STORAGE EXTERNAL; +ALTER TABLE ctlt3 ALTER COLUMN a SET STORAGE MAIN; +CREATE INDEX ctlt3_fnidx ON ctlt3 ((a || c)); +COMMENT ON COLUMN ctlt3.a IS 'A3'; +COMMENT ON COLUMN ctlt3.c IS 'C'; +COMMENT ON CONSTRAINT ctlt3_a_check ON ctlt3 IS 't3_a_check'; + +CREATE TABLE ctlt4 (a text, c text); +ALTER TABLE ctlt4 ALTER COLUMN c SET STORAGE EXTERNAL; + +CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING STORAGE); +\d+ ctlt12_storage +CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS); +\d+ ctlt12_comments +CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1); +\d+ ctlt1_inh +SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass; +CREATE TABLE ctlt13_inh () INHERITS (ctlt1, ctlt3); +\d+ ctlt13_inh +CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1); +\d+ ctlt13_like +SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass; + +CREATE TABLE ctlt_all (LIKE ctlt1 INCLUDING ALL); +\d+ ctlt_all +SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid; +SELECT s.stxname, objsubid, description FROM pg_description, pg_statistic_ext s WHERE classoid = 'pg_statistic_ext'::regclass AND objoid = s.oid AND s.stxrelid = 'ctlt_all'::regclass ORDER BY s.stxname, objsubid; + +CREATE TABLE inh_error1 () INHERITS (ctlt1, ctlt4); +CREATE TABLE inh_error2 (LIKE ctlt4 INCLUDING STORAGE) INHERITS (ctlt1); + +-- Check that LIKE isn't confused by a system catalog of the same name +CREATE TABLE pg_attrdef (LIKE ctlt1 INCLUDING ALL); +\d+ public.pg_attrdef +DROP TABLE public.pg_attrdef; + +-- Check that LIKE isn't confused when new table masks the old, either +/* YB: DDL rollback is not supported +BEGIN; +*/ -- YB +CREATE SCHEMA ctl_schema; +SET search_path = ctl_schema, public; /* YB: DDL rollback is not supported, s/SET LOCAL/SET */ +CREATE TABLE ctlt1 (LIKE ctlt1 INCLUDING ALL); +\d+ ctlt1 +/* YB: DDL rollback is not supported +ROLLBACK; +*/ -- YB + +DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE; +SET search_path = public; /* YB: DDL rollback is not supported, restore search path */ + +-- LIKE must respect NO INHERIT property of constraints +CREATE TABLE noinh_con_copy (a int CHECK (a > 0) NO INHERIT); +CREATE TABLE noinh_con_copy1 (LIKE noinh_con_copy INCLUDING CONSTRAINTS); +\d noinh_con_copy1 + +-- fail, as partitioned tables don't allow NO INHERIT constraints +CREATE TABLE noinh_con_copy1_parted (LIKE noinh_con_copy INCLUDING ALL) + PARTITION BY LIST (a); + +DROP TABLE noinh_con_copy, noinh_con_copy1; + + +/* LIKE with other relation kinds */ + +CREATE TABLE ctlt4 (a int, b text); + +CREATE SEQUENCE ctlseq1; +CREATE TABLE ctlt10 (LIKE ctlseq1); -- fail + +CREATE VIEW ctlv1 AS SELECT * FROM ctlt4; +CREATE TABLE ctlt11 (LIKE ctlv1); +CREATE TABLE ctlt11a (LIKE ctlv1 INCLUDING ALL); + +CREATE TYPE ctlty1 AS (a int, b text); +CREATE TABLE ctlt12 (LIKE ctlty1); + +DROP SEQUENCE ctlseq1; +DROP TYPE ctlty1; +DROP VIEW ctlv1; +DROP TABLE IF EXISTS ctlt4, ctlt10, ctlt11, ctlt11a, ctlt12; diff --git a/src/test/regress/sql/yb.port.create_type.sql b/src/test/regress/sql/yb.port.create_type.sql new file mode 100644 index 0000000000000..c6fc4f9029ca0 --- /dev/null +++ b/src/test/regress/sql/yb.port.create_type.sql @@ -0,0 +1,291 @@ +-- +-- CREATE_TYPE +-- + +-- directory path and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +-- +-- Test the "old style" approach of making the I/O functions first, +-- with no explicit shell type creation. +-- +CREATE FUNCTION widget_in(cstring) + RETURNS widget + AS :'regresslib' + LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION widget_out(widget) + RETURNS cstring + AS :'regresslib' + LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION int44in(cstring) + RETURNS city_budget + AS :'regresslib' + LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION int44out(city_budget) + RETURNS cstring + AS :'regresslib' + LANGUAGE C STRICT IMMUTABLE; + +CREATE TYPE widget ( + internallength = 24, + input = widget_in, + output = widget_out, + typmod_in = numerictypmodin, + typmod_out = numerictypmodout, + alignment = double +); + +CREATE TYPE city_budget ( + internallength = 16, + input = int44in, + output = int44out, + element = int4, + category = 'x', -- just to verify the system will take it + preferred = true -- ditto +); + +-- Test creation and destruction of shell types +CREATE TYPE shell; +CREATE TYPE shell; -- fail, type already present +DROP TYPE shell; +DROP TYPE shell; -- fail, type not exist + +-- also, let's leave one around for purposes of pg_dump testing +CREATE TYPE myshell; + +-- +-- Test type-related default values (broken in releases before PG 7.2) +-- +-- This part of the test also exercises the "new style" approach of making +-- a shell type and then filling it in. +-- +CREATE TYPE int42; +CREATE TYPE text_w_default; + +-- Make dummy I/O routines using the existing internal support for int4, text +CREATE FUNCTION int42_in(cstring) + RETURNS int42 + AS 'int4in' + LANGUAGE internal STRICT IMMUTABLE; +CREATE FUNCTION int42_out(int42) + RETURNS cstring + AS 'int4out' + LANGUAGE internal STRICT IMMUTABLE; +CREATE FUNCTION text_w_default_in(cstring) + RETURNS text_w_default + AS 'textin' + LANGUAGE internal STRICT IMMUTABLE; +CREATE FUNCTION text_w_default_out(text_w_default) + RETURNS cstring + AS 'textout' + LANGUAGE internal STRICT IMMUTABLE; + +CREATE TYPE int42 ( + internallength = 4, + input = int42_in, + output = int42_out, + alignment = int4, + default = 42, + passedbyvalue +); + +CREATE TYPE text_w_default ( + internallength = variable, + input = text_w_default_in, + output = text_w_default_out, + alignment = int4, + default = 'zippo' +); + +CREATE TABLE default_test (f1 text_w_default, f2 int42); + +INSERT INTO default_test DEFAULT VALUES; + +SELECT * FROM default_test; + +-- We need a shell type to test some CREATE TYPE failure cases with +CREATE TYPE bogus_type; + +-- invalid: non-lowercase quoted identifiers +CREATE TYPE bogus_type ( + "Internallength" = 4, + "Input" = int42_in, + "Output" = int42_out, + "Alignment" = int4, + "Default" = 42, + "Passedbyvalue" +); + +-- invalid: input/output function incompatibility +CREATE TYPE bogus_type (INPUT = array_in, + OUTPUT = array_out, + ELEMENT = int, + INTERNALLENGTH = 32); + +DROP TYPE bogus_type; + +-- It no longer is possible to issue CREATE TYPE without making a shell first +CREATE TYPE bogus_type (INPUT = array_in, + OUTPUT = array_out, + ELEMENT = int, + INTERNALLENGTH = 32); + +-- Test stand-alone composite type + +CREATE TYPE default_test_row AS (f1 text_w_default, f2 int42); + +CREATE FUNCTION get_default_test() RETURNS SETOF default_test_row AS ' + SELECT * FROM default_test; +' LANGUAGE SQL; + +SELECT * FROM get_default_test(); + +-- Test comments +COMMENT ON TYPE bad IS 'bad comment'; +COMMENT ON TYPE default_test_row IS 'good comment'; +COMMENT ON TYPE default_test_row IS NULL; +COMMENT ON COLUMN default_test_row.nope IS 'bad comment'; +COMMENT ON COLUMN default_test_row.f1 IS 'good comment'; +COMMENT ON COLUMN default_test_row.f1 IS NULL; + +-- Check shell type create for existing types +CREATE TYPE text_w_default; -- should fail + +DROP TYPE default_test_row CASCADE; + +DROP TABLE default_test; + +-- Check dependencies are established when creating a new type +CREATE TYPE base_type; +CREATE FUNCTION base_fn_in(cstring) RETURNS base_type AS 'boolin' + LANGUAGE internal IMMUTABLE STRICT; +CREATE FUNCTION base_fn_out(base_type) RETURNS cstring AS 'boolout' + LANGUAGE internal IMMUTABLE STRICT; +CREATE TYPE base_type(INPUT = base_fn_in, OUTPUT = base_fn_out); +DROP FUNCTION base_fn_in(cstring); -- error +DROP FUNCTION base_fn_out(base_type); -- error +DROP TYPE base_type; -- error +DROP TYPE base_type CASCADE; + +-- Check usage of typmod with a user-defined type +-- (we have borrowed numeric's typmod functions) + +CREATE TEMP TABLE mytab (foo widget(42,13,7)); -- should fail +CREATE TEMP TABLE mytab (foo widget(42,13)); + +SELECT format_type(atttypid,atttypmod) FROM pg_attribute +WHERE attrelid = 'mytab'::regclass AND attnum > 0; + +-- might as well exercise the widget type while we're here +INSERT INTO mytab VALUES ('(1,2,3)'), ('(-44,5.5,12)'); +TABLE mytab; + +-- and test format_type() a bit more, too +select format_type('varchar'::regtype, 42); +select format_type('bpchar'::regtype, null); +-- this behavior difference is intentional +select format_type('bpchar'::regtype, -1); + +-- Test creation of an operator over a user-defined type + +CREATE FUNCTION pt_in_widget(point, widget) + RETURNS bool + AS :'regresslib' + LANGUAGE C STRICT; + +CREATE OPERATOR <% ( + leftarg = point, + rightarg = widget, + procedure = pt_in_widget, + commutator = >% , + negator = >=% +); + +SELECT point '(1,2)' <% widget '(0,0,3)' AS t, + point '(1,2)' <% widget '(0,0,1)' AS f; + +-- exercise city_budget type +CREATE TABLE city ( + name name, + location box, + budget city_budget +); + +INSERT INTO city VALUES +('Podunk', '(1,2),(3,4)', '100,127,1000'), +('Gotham', '(1000,34),(1100,334)', '123456,127,-1000,6789'); + +TABLE city; + +-- +-- Test CREATE/ALTER TYPE using a type that's compatible with varchar, +-- so we can re-use those support functions +-- +CREATE TYPE myvarchar; + +CREATE FUNCTION myvarcharin(cstring, oid, integer) RETURNS myvarchar +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'varcharin'; + +CREATE FUNCTION myvarcharout(myvarchar) RETURNS cstring +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'varcharout'; + +CREATE FUNCTION myvarcharsend(myvarchar) RETURNS bytea +LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharsend'; + +CREATE FUNCTION myvarcharrecv(internal, oid, integer) RETURNS myvarchar +LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharrecv'; + +-- fail, it's still a shell: +ALTER TYPE myvarchar SET (storage = extended); + +CREATE TYPE myvarchar ( + input = myvarcharin, + output = myvarcharout, + alignment = integer, + storage = main +); + +-- want to check updating of a domain over the target type, too +CREATE DOMAIN myvarchardom AS myvarchar; + +ALTER TYPE myvarchar SET (storage = plain); -- not allowed + +ALTER TYPE myvarchar SET (storage = extended); + +ALTER TYPE myvarchar SET ( + send = myvarcharsend, + receive = myvarcharrecv, + typmod_in = varchartypmodin, + typmod_out = varchartypmodout, + -- these are bogus, but it's safe as long as we don't use the type: + analyze = ts_typanalyze, + subscript = raw_array_subscript_handler +); + +SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout, + typanalyze, typsubscript, typstorage +FROM pg_type WHERE typname = 'myvarchar'; + +SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout, + typanalyze, typsubscript, typstorage +FROM pg_type WHERE typname = '_myvarchar'; + +SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout, + typanalyze, typsubscript, typstorage +FROM pg_type WHERE typname = 'myvarchardom'; + +SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout, + typanalyze, typsubscript, typstorage +FROM pg_type WHERE typname = '_myvarchardom'; + +-- ensure dependencies are straight +DROP FUNCTION myvarcharsend(myvarchar); -- fail +DROP TYPE myvarchar; -- fail + +DROP TYPE myvarchar CASCADE; diff --git a/src/test/regress/sql/yb.port.create_view.sql b/src/test/regress/sql/yb.port.create_view.sql new file mode 100644 index 0000000000000..233854cabb8f6 --- /dev/null +++ b/src/test/regress/sql/yb.port.create_view.sql @@ -0,0 +1,23 @@ +-- +-- CREATE_VIEW +-- Virtual class definitions +-- (this also tests the query rewrite system) +-- + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +CREATE FUNCTION interpt_pp(path, path) + RETURNS point + AS :'regresslib' + LANGUAGE C STRICT; + +CREATE TABLE real_city ( + pop int4, + cname text, + outline path +); diff --git a/src/test/regress/sql/yb.port.drop_if_exists.sql b/src/test/regress/sql/yb.port.drop_if_exists.sql new file mode 100644 index 0000000000000..ac6168b91f831 --- /dev/null +++ b/src/test/regress/sql/yb.port.drop_if_exists.sql @@ -0,0 +1,304 @@ +-- +-- IF EXISTS tests +-- + +-- table (will be really dropped at the end) + +DROP TABLE test_exists; + +DROP TABLE IF EXISTS test_exists; + +CREATE TABLE test_exists (a int, b text); + +-- view + +DROP VIEW test_view_exists; + +DROP VIEW IF EXISTS test_view_exists; + +CREATE VIEW test_view_exists AS select * from test_exists; + +DROP VIEW IF EXISTS test_view_exists; + +DROP VIEW test_view_exists; + +-- index + +DROP INDEX test_index_exists; + +DROP INDEX IF EXISTS test_index_exists; + +CREATE INDEX test_index_exists on test_exists(a); + +DROP INDEX IF EXISTS test_index_exists; + +DROP INDEX test_index_exists; + +-- sequence + +DROP SEQUENCE test_sequence_exists; + +DROP SEQUENCE IF EXISTS test_sequence_exists; + +CREATE SEQUENCE test_sequence_exists; + +DROP SEQUENCE IF EXISTS test_sequence_exists; + +DROP SEQUENCE test_sequence_exists; + +-- schema + +DROP SCHEMA test_schema_exists; + +DROP SCHEMA IF EXISTS test_schema_exists; + +CREATE SCHEMA test_schema_exists; + +DROP SCHEMA IF EXISTS test_schema_exists; + +DROP SCHEMA test_schema_exists; + +-- type + +DROP TYPE test_type_exists; + +DROP TYPE IF EXISTS test_type_exists; + +CREATE type test_type_exists as (a int, b text); + +DROP TYPE IF EXISTS test_type_exists; + +DROP TYPE test_type_exists; + +-- domain + +DROP DOMAIN test_domain_exists; + +DROP DOMAIN IF EXISTS test_domain_exists; + +CREATE domain test_domain_exists as int not null check (value > 0); + +DROP DOMAIN IF EXISTS test_domain_exists; + +DROP DOMAIN test_domain_exists; + +--- +--- role/user/group +--- + +CREATE USER regress_test_u1; +CREATE ROLE regress_test_r1; +CREATE GROUP regress_test_g1; + +DROP USER regress_test_u2; + +DROP USER IF EXISTS regress_test_u1, regress_test_u2; + +DROP USER regress_test_u1; + +DROP ROLE regress_test_r2; + +DROP ROLE IF EXISTS regress_test_r1, regress_test_r2; + +DROP ROLE regress_test_r1; + +DROP GROUP regress_test_g2; + +DROP GROUP IF EXISTS regress_test_g1, regress_test_g2; + +DROP GROUP regress_test_g1; + +-- collation +DROP COLLATION IF EXISTS test_collation_exists; + +-- conversion +DROP CONVERSION test_conversion_exists; +DROP CONVERSION IF EXISTS test_conversion_exists; +CREATE CONVERSION test_conversion_exists + FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8; +DROP CONVERSION test_conversion_exists; + +-- text search parser +DROP TEXT SEARCH PARSER test_tsparser_exists; +DROP TEXT SEARCH PARSER IF EXISTS test_tsparser_exists; + +-- text search dictionary +DROP TEXT SEARCH DICTIONARY test_tsdict_exists; +DROP TEXT SEARCH DICTIONARY IF EXISTS test_tsdict_exists; +CREATE TEXT SEARCH DICTIONARY test_tsdict_exists ( + Template=ispell, + DictFile=ispell_sample, + AffFile=ispell_sample +); +DROP TEXT SEARCH DICTIONARY test_tsdict_exists; + +-- test search template +DROP TEXT SEARCH TEMPLATE test_tstemplate_exists; +DROP TEXT SEARCH TEMPLATE IF EXISTS test_tstemplate_exists; + +-- text search configuration +DROP TEXT SEARCH CONFIGURATION test_tsconfig_exists; +DROP TEXT SEARCH CONFIGURATION IF EXISTS test_tsconfig_exists; +CREATE TEXT SEARCH CONFIGURATION test_tsconfig_exists (COPY=english); +DROP TEXT SEARCH CONFIGURATION test_tsconfig_exists; + +-- extension +DROP EXTENSION test_extension_exists; +DROP EXTENSION IF EXISTS test_extension_exists; + +-- functions +DROP FUNCTION test_function_exists(); +DROP FUNCTION IF EXISTS test_function_exists(); + +DROP FUNCTION test_function_exists(int, text, int[]); +DROP FUNCTION IF EXISTS test_function_exists(int, text, int[]); + +-- aggregate +DROP AGGREGATE test_aggregate_exists(*); +DROP AGGREGATE IF EXISTS test_aggregate_exists(*); + +DROP AGGREGATE test_aggregate_exists(int); +DROP AGGREGATE IF EXISTS test_aggregate_exists(int); + +-- operator +DROP OPERATOR @#@ (int, int); +DROP OPERATOR IF EXISTS @#@ (int, int); +CREATE OPERATOR @#@ + (leftarg = int8, rightarg = int8, procedure = int8xor); +DROP OPERATOR @#@ (int8, int8); + +-- language +DROP LANGUAGE test_language_exists; +DROP LANGUAGE IF EXISTS test_language_exists; + +-- cast +DROP CAST (text AS text); +DROP CAST IF EXISTS (text AS text); + +-- trigger +DROP TRIGGER test_trigger_exists ON test_exists; +DROP TRIGGER IF EXISTS test_trigger_exists ON test_exists; + +DROP TRIGGER test_trigger_exists ON no_such_table; +DROP TRIGGER IF EXISTS test_trigger_exists ON no_such_table; + +DROP TRIGGER test_trigger_exists ON no_such_schema.no_such_table; +DROP TRIGGER IF EXISTS test_trigger_exists ON no_such_schema.no_such_table; + +CREATE TRIGGER test_trigger_exists + BEFORE UPDATE ON test_exists + FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); +DROP TRIGGER test_trigger_exists ON test_exists; + +-- rule +DROP RULE test_rule_exists ON test_exists; +DROP RULE IF EXISTS test_rule_exists ON test_exists; + +DROP RULE test_rule_exists ON no_such_table; +DROP RULE IF EXISTS test_rule_exists ON no_such_table; + +DROP RULE test_rule_exists ON no_such_schema.no_such_table; +DROP RULE IF EXISTS test_rule_exists ON no_such_schema.no_such_table; + +CREATE RULE test_rule_exists AS ON INSERT TO test_exists + DO INSTEAD + INSERT INTO test_exists VALUES (NEW.a, NEW.b || NEW.a::text); +DROP RULE test_rule_exists ON test_exists; + +-- foreign data wrapper +DROP FOREIGN DATA WRAPPER test_fdw_exists; +DROP FOREIGN DATA WRAPPER IF EXISTS test_fdw_exists; + +-- foreign server +DROP SERVER test_server_exists; +DROP SERVER IF EXISTS test_server_exists; + +-- operator class +DROP OPERATOR CLASS test_operator_class USING btree; +DROP OPERATOR CLASS IF EXISTS test_operator_class USING btree; + +DROP OPERATOR CLASS test_operator_class USING no_such_am; +DROP OPERATOR CLASS IF EXISTS test_operator_class USING no_such_am; + +-- operator family +DROP OPERATOR FAMILY test_operator_family USING btree; +DROP OPERATOR FAMILY IF EXISTS test_operator_family USING btree; + +DROP OPERATOR FAMILY test_operator_family USING no_such_am; +DROP OPERATOR FAMILY IF EXISTS test_operator_family USING no_such_am; + +-- access method +DROP ACCESS METHOD no_such_am; +DROP ACCESS METHOD IF EXISTS no_such_am; + +-- drop the table + +DROP TABLE IF EXISTS test_exists; + +DROP TABLE test_exists; + +-- be tolerant with missing schemas, types, etc + +DROP AGGREGATE IF EXISTS no_such_schema.foo(int); +DROP AGGREGATE IF EXISTS foo(no_such_type); +DROP AGGREGATE IF EXISTS foo(no_such_schema.no_such_type); +DROP CAST IF EXISTS (INTEGER AS no_such_type2); +DROP CAST IF EXISTS (no_such_type1 AS INTEGER); +DROP CAST IF EXISTS (INTEGER AS no_such_schema.bar); +DROP CAST IF EXISTS (no_such_schema.foo AS INTEGER); +DROP COLLATION IF EXISTS no_such_schema.foo; +DROP CONVERSION IF EXISTS no_such_schema.foo; +DROP DOMAIN IF EXISTS no_such_schema.foo; +DROP FOREIGN TABLE IF EXISTS no_such_schema.foo; +DROP FUNCTION IF EXISTS no_such_schema.foo(); +DROP FUNCTION IF EXISTS foo(no_such_type); +DROP FUNCTION IF EXISTS foo(no_such_schema.no_such_type); +DROP INDEX IF EXISTS no_such_schema.foo; +DROP MATERIALIZED VIEW IF EXISTS no_such_schema.foo; +DROP OPERATOR IF EXISTS no_such_schema.+ (int, int); +DROP OPERATOR IF EXISTS + (no_such_type, no_such_type); +DROP OPERATOR IF EXISTS + (no_such_schema.no_such_type, no_such_schema.no_such_type); +DROP OPERATOR IF EXISTS # (NONE, no_such_schema.no_such_type); +DROP OPERATOR CLASS IF EXISTS no_such_schema.widget_ops USING btree; +DROP OPERATOR FAMILY IF EXISTS no_such_schema.float_ops USING btree; +DROP RULE IF EXISTS foo ON no_such_schema.bar; +DROP SEQUENCE IF EXISTS no_such_schema.foo; +DROP TABLE IF EXISTS no_such_schema.foo; +DROP TEXT SEARCH CONFIGURATION IF EXISTS no_such_schema.foo; +DROP TEXT SEARCH DICTIONARY IF EXISTS no_such_schema.foo; +DROP TEXT SEARCH PARSER IF EXISTS no_such_schema.foo; +DROP TEXT SEARCH TEMPLATE IF EXISTS no_such_schema.foo; +DROP TRIGGER IF EXISTS foo ON no_such_schema.bar; +DROP TYPE IF EXISTS no_such_schema.foo; +DROP VIEW IF EXISTS no_such_schema.foo; + +-- Check we receive an ambiguous function error when there are +-- multiple matching functions. +CREATE FUNCTION test_ambiguous_funcname(int) returns int as $$ select $1; $$ language sql; +CREATE FUNCTION test_ambiguous_funcname(text) returns text as $$ select $1; $$ language sql; +DROP FUNCTION test_ambiguous_funcname; +DROP FUNCTION IF EXISTS test_ambiguous_funcname; + +-- cleanup +DROP FUNCTION test_ambiguous_funcname(int); +DROP FUNCTION test_ambiguous_funcname(text); + +-- Likewise for procedures. +CREATE PROCEDURE test_ambiguous_procname(int) as $$ begin end; $$ language plpgsql; +CREATE PROCEDURE test_ambiguous_procname(text) as $$ begin end; $$ language plpgsql; +DROP PROCEDURE test_ambiguous_procname; +DROP PROCEDURE IF EXISTS test_ambiguous_procname; + +-- Check we get a similar error if we use ROUTINE instead of PROCEDURE. +DROP ROUTINE IF EXISTS test_ambiguous_procname; + +-- cleanup +DROP PROCEDURE test_ambiguous_procname(int); +DROP PROCEDURE test_ambiguous_procname(text); + +-- This test checks both the functionality of 'if exists' and the syntax +-- of the drop database command. +drop database test_database_exists (force); +drop database test_database_exists with (force); +drop database if exists test_database_exists (force); +drop database if exists test_database_exists with (force); diff --git a/src/test/regress/sql/yb.port.drop_operator.sql b/src/test/regress/sql/yb.port.drop_operator.sql new file mode 100644 index 0000000000000..31f990760729a --- /dev/null +++ b/src/test/regress/sql/yb.port.drop_operator.sql @@ -0,0 +1,60 @@ +CREATE OPERATOR === ( + PROCEDURE = int8eq, + LEFTARG = bigint, + RIGHTARG = bigint, + COMMUTATOR = === +); + +CREATE OPERATOR !== ( + PROCEDURE = int8ne, + LEFTARG = bigint, + RIGHTARG = bigint, + NEGATOR = ===, + COMMUTATOR = !== +); + +DROP OPERATOR !==(bigint, bigint); + +SELECT ctid, oprcom +FROM pg_catalog.pg_operator fk +WHERE oprcom != 0 AND + NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom); +SELECT '' AS ctid, oprcom -- YB: workaround for ctid not supported +FROM pg_catalog.pg_operator fk -- YB +WHERE oprcom != 0 AND -- YB + NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom); -- YB + +SELECT '' AS ctid, oprnegate -- YB: workaround for ctid not supported +FROM pg_catalog.pg_operator fk +WHERE oprnegate != 0 AND + NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate); + +DROP OPERATOR ===(bigint, bigint); + +CREATE OPERATOR <| ( + PROCEDURE = int8lt, + LEFTARG = bigint, + RIGHTARG = bigint +); + +CREATE OPERATOR |> ( + PROCEDURE = int8gt, + LEFTARG = bigint, + RIGHTARG = bigint, + NEGATOR = <|, + COMMUTATOR = <| +); + +DROP OPERATOR |>(bigint, bigint); + +SELECT '' AS ctid, oprcom -- YB: workaround for ctid not supported +FROM pg_catalog.pg_operator fk +WHERE oprcom != 0 AND + NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom); + +SELECT '' AS ctid, oprnegate -- YB: workaround for ctid not supported +FROM pg_catalog.pg_operator fk +WHERE oprnegate != 0 AND + NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate); + +DROP OPERATOR <|(bigint, bigint); diff --git a/src/test/regress/sql/yb.port.enum.sql b/src/test/regress/sql/yb.port.enum.sql new file mode 100644 index 0000000000000..d738c4f723e2c --- /dev/null +++ b/src/test/regress/sql/yb.port.enum.sql @@ -0,0 +1,343 @@ +-- +-- Enum tests +-- + +CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple'); + +-- +-- Did it create the right number of rows? +-- +SELECT COUNT(*) FROM pg_enum WHERE enumtypid = 'rainbow'::regtype; + +-- +-- I/O functions +-- +SELECT 'red'::rainbow; +SELECT 'mauve'::rainbow; + +-- +-- adding new values +-- + +CREATE TYPE planets AS ENUM ( 'venus', 'earth', 'mars' ); + +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY 2; + +ALTER TYPE planets ADD VALUE 'uranus'; + +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY 2; + +ALTER TYPE planets ADD VALUE 'mercury' BEFORE 'venus'; +ALTER TYPE planets ADD VALUE 'saturn' BEFORE 'uranus'; +ALTER TYPE planets ADD VALUE 'jupiter' AFTER 'mars'; +ALTER TYPE planets ADD VALUE 'neptune' AFTER 'uranus'; + +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY 2; + +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY enumlabel::planets; + +-- errors for adding labels +ALTER TYPE planets ADD VALUE + 'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto'; + +ALTER TYPE planets ADD VALUE 'pluto' AFTER 'zeus'; + +-- if not exists tests + +-- existing value gives error +ALTER TYPE planets ADD VALUE 'mercury'; + +-- unless IF NOT EXISTS is specified +ALTER TYPE planets ADD VALUE IF NOT EXISTS 'mercury'; + +-- should be neptune, not mercury +SELECT enum_last(NULL::planets); + +ALTER TYPE planets ADD VALUE IF NOT EXISTS 'pluto'; + +-- should be pluto, i.e. the new value +SELECT enum_last(NULL::planets); + +-- +-- Test inserting so many values that we have to renumber +-- This is not supported by YB. +-- + +create type insenum as enum ('L1', 'L2'); + +alter type insenum add value 'i1' before 'L2'; +alter type insenum add value 'i2' before 'L2'; +alter type insenum add value 'i3' before 'L2'; +alter type insenum add value 'i4' before 'L2'; +alter type insenum add value 'i5' before 'L2'; +alter type insenum add value 'i6' before 'L2'; +alter type insenum add value 'i7' before 'L2'; +alter type insenum add value 'i8' before 'L2'; +alter type insenum add value 'i9' before 'L2'; +alter type insenum add value 'i10' before 'L2'; +alter type insenum add value 'i11' before 'L2'; +alter type insenum add value 'i12' before 'L2'; +alter type insenum add value 'i13' before 'L2'; +alter type insenum add value 'i14' before 'L2'; +alter type insenum add value 'i15' before 'L2'; +alter type insenum add value 'i16' before 'L2'; +alter type insenum add value 'i17' before 'L2'; +alter type insenum add value 'i18' before 'L2'; +alter type insenum add value 'i19' before 'L2'; +alter type insenum add value 'i20' before 'L2'; +alter type insenum add value 'i21' before 'L2'; +alter type insenum add value 'i22' before 'L2'; +alter type insenum add value 'i23' before 'L2'; +alter type insenum add value 'i24' before 'L2'; +alter type insenum add value 'i25' before 'L2'; +alter type insenum add value 'i26' before 'L2'; +alter type insenum add value 'i27' before 'L2'; +alter type insenum add value 'i28' before 'L2'; +alter type insenum add value 'i29' before 'L2'; +alter type insenum add value 'i30' before 'L2'; + +-- The exact values of enumsortorder will now depend on the local properties +-- of float4, but in any reasonable implementation we should get at least +-- 20 splits before having to renumber; so only hide values > 20. + +SELECT enumlabel, + case when enumsortorder > 20 then null else cast(enumsortorder as float) end as so +FROM pg_enum +WHERE enumtypid = 'insenum'::regtype +ORDER BY enumsortorder; + +-- +-- Basic table creation, row selection +-- +CREATE TABLE enumtest (col rainbow); +INSERT INTO enumtest values ('red'), ('orange'), ('yellow'), ('green'); +COPY enumtest FROM stdin; +blue +purple +\. +-- ORDER BY is needed for Yugabyte +SELECT * FROM enumtest ORDER BY col; + +-- +-- Operators, no index +-- +SELECT * FROM enumtest WHERE col = 'orange'; +SELECT * FROM enumtest WHERE col <> 'orange' ORDER BY col; +SELECT * FROM enumtest WHERE col > 'yellow' ORDER BY col; +SELECT * FROM enumtest WHERE col >= 'yellow' ORDER BY col; +SELECT * FROM enumtest WHERE col < 'green' ORDER BY col; +SELECT * FROM enumtest WHERE col <= 'green' ORDER BY col; + +-- +-- Cast to/from text +-- +SELECT 'red'::rainbow::text || 'hithere'; +SELECT 'red'::text::rainbow = 'red'::rainbow; + +-- +-- Aggregates +-- +SELECT min(col) FROM enumtest; +SELECT max(col) FROM enumtest; +SELECT max(col) FROM enumtest WHERE col < 'green'; + +-- +-- Index tests, force use of index +-- +SET enable_seqscan = off; +SET enable_bitmapscan = off; + +-- +-- Btree index / opclass with the various operators +-- +CREATE UNIQUE INDEX enumtest_btree ON enumtest USING btree (col); +SELECT * FROM enumtest WHERE col = 'orange'; +SELECT * FROM enumtest WHERE col <> 'orange' ORDER BY col; +SELECT * FROM enumtest WHERE col > 'yellow' ORDER BY col; +SELECT * FROM enumtest WHERE col >= 'yellow' ORDER BY col; +SELECT * FROM enumtest WHERE col < 'green' ORDER BY col; +SELECT * FROM enumtest WHERE col <= 'green' ORDER BY col; +SELECT min(col) FROM enumtest; +SELECT max(col) FROM enumtest; +SELECT max(col) FROM enumtest WHERE col < 'green'; +DROP INDEX enumtest_btree; + +-- +-- Hash index / opclass with the = operator +-- +CREATE INDEX enumtest_hash ON enumtest USING hash (col); +SELECT * FROM enumtest WHERE col = 'orange'; +DROP INDEX enumtest_hash; + +-- +-- End index tests +-- +RESET enable_seqscan; +RESET enable_bitmapscan; + +-- +-- Domains over enums +-- +CREATE DOMAIN rgb AS rainbow CHECK (VALUE IN ('red', 'green', 'blue')); +SELECT 'red'::rgb; +SELECT 'purple'::rgb; +SELECT 'purple'::rainbow::rgb; +DROP DOMAIN rgb; + +-- +-- Arrays +-- +SELECT '{red,green,blue}'::rainbow[]; +SELECT ('{red,green,blue}'::rainbow[])[2]; +SELECT 'red' = ANY ('{red,green,blue}'::rainbow[]); +SELECT 'yellow' = ANY ('{red,green,blue}'::rainbow[]); +SELECT 'red' = ALL ('{red,green,blue}'::rainbow[]); +SELECT 'red' = ALL ('{red,red}'::rainbow[]); + +-- +-- Support functions +-- +SELECT enum_first(NULL::rainbow); +SELECT enum_last('green'::rainbow); +SELECT enum_range(NULL::rainbow); +SELECT enum_range('orange'::rainbow, 'green'::rainbow); +SELECT enum_range(NULL, 'green'::rainbow); +SELECT enum_range('orange'::rainbow, NULL); +SELECT enum_range(NULL::rainbow, NULL); + +-- +-- User functions, can't test perl/python etc here since may not be compiled. +-- +CREATE FUNCTION echo_me(anyenum) RETURNS text AS $$ +BEGIN +RETURN $1::text || 'omg'; +END +$$ LANGUAGE plpgsql; +SELECT echo_me('red'::rainbow); +-- +-- Concrete function should override generic one +-- +CREATE FUNCTION echo_me(rainbow) RETURNS text AS $$ +BEGIN +RETURN $1::text || 'wtf'; +END +$$ LANGUAGE plpgsql; +SELECT echo_me('red'::rainbow); +-- +-- If we drop the original generic one, we don't have to qualify the type +-- anymore, since there's only one match +-- +DROP FUNCTION echo_me(anyenum); +SELECT echo_me('red'); +DROP FUNCTION echo_me(rainbow); + +-- +-- RI triggers on enum types +-- +CREATE TABLE enumtest_parent (id rainbow PRIMARY KEY); +CREATE TABLE enumtest_child (parent rainbow REFERENCES enumtest_parent); +INSERT INTO enumtest_parent VALUES ('red'); +INSERT INTO enumtest_child VALUES ('red'); +INSERT INTO enumtest_child VALUES ('blue'); -- fail +DELETE FROM enumtest_parent; -- fail +-- +-- cross-type RI should fail +-- +CREATE TYPE bogus AS ENUM('good', 'bad', 'ugly'); +CREATE TABLE enumtest_bogus_child(parent bogus REFERENCES enumtest_parent); +DROP TYPE bogus; + +-- check renaming a value +ALTER TYPE rainbow RENAME VALUE 'red' TO 'crimson'; +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'rainbow'::regtype +ORDER BY 2; +-- check that renaming a non-existent value fails +ALTER TYPE rainbow RENAME VALUE 'red' TO 'crimson'; +-- check that renaming to an existent value fails +ALTER TYPE rainbow RENAME VALUE 'blue' TO 'green'; + +-- +-- check transactional behaviour of ALTER TYPE ... ADD VALUE +-- +CREATE TYPE bogus AS ENUM('good'); + +-- check that we can add new values to existing enums in a transaction +-- but we can't use them +BEGIN; +ALTER TYPE bogus ADD VALUE 'new'; +SAVEPOINT x; +SELECT 'new'::bogus; -- unsafe +ROLLBACK TO x; +SELECT enum_first(null::bogus); -- safe +SELECT enum_last(null::bogus); -- unsafe +ROLLBACK TO x; +SELECT enum_range(null::bogus); -- unsafe +ROLLBACK TO x; +COMMIT; +SELECT 'new'::bogus; -- now safe +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'bogus'::regtype +ORDER BY 2; + +-- check that we recognize the case where the enum already existed but was +-- modified in the current txn; this should not be considered safe +BEGIN; +ALTER TYPE bogus RENAME TO bogon; +ALTER TYPE bogon ADD VALUE 'bad'; +SELECT 'bad'::bogon; +ROLLBACK; + +-- but a renamed value is safe to use later in same transaction +BEGIN; +ALTER TYPE bogus RENAME VALUE 'good' to 'bad'; +SELECT 'bad'::bogus; +ROLLBACK; + +DROP TYPE bogus; + +-- check that values created during CREATE TYPE can be used in any case +BEGIN; +CREATE TYPE bogus AS ENUM('good','bad','ugly'); +ALTER TYPE bogus RENAME TO bogon; +select enum_range(null::bogon); +ROLLBACK; + +-- ideally, we'd allow this usage; but it requires keeping track of whether +-- the enum type was created in the current transaction, which is expensive +BEGIN; +CREATE TYPE bogus AS ENUM('good'); +ALTER TYPE bogus RENAME TO bogon; +ALTER TYPE bogon ADD VALUE 'bad'; +ALTER TYPE bogon ADD VALUE 'ugly'; +select enum_range(null::bogon); -- fails +ROLLBACK; + +-- +-- Cleanup +-- +DROP TABLE enumtest_child; +DROP TABLE enumtest_parent; +DROP TABLE enumtest; +DROP TYPE rainbow; + +-- +-- Verify properly cleaned up +-- +SELECT COUNT(*) FROM pg_type WHERE typname = 'rainbow'; +SELECT * FROM pg_enum WHERE NOT EXISTS + (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid); diff --git a/src/test/regress/sql/yb.port.errors.sql b/src/test/regress/sql/yb.port.errors.sql new file mode 100644 index 0000000000000..5920ae04a640e --- /dev/null +++ b/src/test/regress/sql/yb.port.errors.sql @@ -0,0 +1,381 @@ +-- +-- ERRORS +-- + +-- bad in postquel, but ok in PostgreSQL +select 1; + + +-- +-- UNSUPPORTED STUFF + +-- doesn't work +-- notify pg_class +-- + +-- +-- SELECT + +-- this used to be a syntax error, but now we allow an empty target list +select; + +-- no such relation +select * from nonesuch; + +-- bad name in target list +select nonesuch from pg_database; + +-- empty distinct list isn't OK +select distinct from pg_database; + +-- bad attribute name on lhs of operator +select * from pg_database where nonesuch = pg_database.datname; + +-- bad attribute name on rhs of operator +select * from pg_database where pg_database.datname = nonesuch; + +-- bad attribute name in select distinct on +select distinct on (foobar) * from pg_database; + +-- grouping with FOR UPDATE +select null from pg_database group by datname for update; +select null from pg_database group by grouping sets (()) for update; + + +-- +-- DELETE + +-- missing relation name (this had better not wildcard!) +delete from; + +-- no such relation +delete from nonesuch; + + +-- +-- DROP + +-- missing relation name (this had better not wildcard!) +drop table; + +-- no such relation +drop table nonesuch; + + +-- +-- ALTER TABLE + +-- relation renaming + +-- missing relation name +alter table rename; + +-- no such relation +alter table nonesuch rename to newnonesuch; + +-- no such relation +alter table nonesuch rename to stud_emp; + +-- conflict +alter table stud_emp rename to student; + +-- self-conflict +alter table stud_emp rename to stud_emp; + + +-- attribute renaming + +-- no such relation +alter table nonesuchrel rename column nonesuchatt to newnonesuchatt; + +-- no such attribute +alter table emp rename column nonesuchatt to newnonesuchatt; + +-- conflict +alter table emp rename column salary to manager; + +-- conflict +alter table emp rename column salary to ctid; + + +-- +-- TRANSACTION STUFF + +-- not in a xact +abort; + +-- not in a xact +end; + + +-- +-- CREATE AGGREGATE + +-- sfunc/finalfunc type disagreement +create aggregate newavg2 (sfunc = int4pl, + basetype = int4, + stype = int4, + finalfunc = int2um, + initcond = '0'); + +-- left out basetype +create aggregate newcnt1 (sfunc = int4inc, + stype = int4, + initcond = '0'); + + +-- +-- DROP INDEX + +-- missing index name +drop index; + +-- bad index name +drop index 314159; + +-- no such index +drop index nonesuch; + + +-- +-- DROP AGGREGATE + +-- missing aggregate name +drop aggregate; + +-- missing aggregate type +drop aggregate newcnt1; + +-- bad aggregate name +drop aggregate 314159 (int); + +-- bad aggregate type +drop aggregate newcnt (nonesuch); + +-- no such aggregate +drop aggregate nonesuch (int4); + +-- no such aggregate for type +drop aggregate newcnt (float4); + + +-- +-- DROP FUNCTION + +-- missing function name +drop function (); + +-- bad function name +drop function 314159(); + +-- no such function +drop function nonesuch(); + + +-- +-- DROP TYPE + +-- missing type name +drop type; + +-- bad type name +drop type 314159; + +-- no such type +drop type nonesuch; + + +-- +-- DROP OPERATOR + +-- missing everything +drop operator; + +-- bad operator name +drop operator equals; + +-- missing type list +drop operator ===; + +-- missing parentheses +drop operator int4, int4; + +-- missing operator name +drop operator (int4, int4); + +-- missing type list contents +drop operator === (); + +-- no such operator +drop operator === (int4); + +-- no such operator by that name +drop operator === (int4, int4); + +-- no such type1 +drop operator = (nonesuch); + +-- no such type1 +drop operator = ( , int4); + +-- no such type1 +drop operator = (nonesuch, int4); + +-- no such type2 +drop operator = (int4, nonesuch); + +-- no such type2 +drop operator = (int4, ); + + +-- +-- DROP RULE + +-- missing rule name +drop rule; + +-- bad rule name +drop rule 314159; + +-- no such rule +drop rule nonesuch on noplace; + +-- these postquel variants are no longer supported +drop tuple rule nonesuch; +drop instance rule nonesuch on noplace; +drop rewrite rule nonesuch; + +-- +-- Check that division-by-zero is properly caught. +-- + +select 1/0; + +select 1::int8/0; + +select 1/0::int8; + +select 1::int2/0; + +select 1/0::int2; + +select 1::numeric/0; + +select 1/0::numeric; + +select 1::float8/0; + +select 1/0::float8; + +select 1::float4/0; + +select 1/0::float4; + + +-- +-- Test psql's reporting of syntax error location +-- + +xxx; + +CREATE foo; + +CREATE TABLE ; + +CREATE TABLE +\g + +INSERT INTO foo VALUES(123) foo; + +INSERT INTO 123 +VALUES(123); + +INSERT INTO foo +VALUES(123) 123 +; + +-- with a tab +CREATE TABLE foo + (id INT4 UNIQUE NOT NULL, id2 TEXT NOT NULL PRIMARY KEY, + id3 INTEGER NOT NUL, + id4 INT4 UNIQUE NOT NULL, id5 TEXT UNIQUE NOT NULL); + +-- long line to be truncated on the left +CREATE TABLE foo(id INT4 UNIQUE NOT NULL, id2 TEXT NOT NULL PRIMARY KEY, id3 INTEGER NOT NUL, +id4 INT4 UNIQUE NOT NULL, id5 TEXT UNIQUE NOT NULL); + +-- long line to be truncated on the right +CREATE TABLE foo( +id3 INTEGER NOT NUL, id4 INT4 UNIQUE NOT NULL, id5 TEXT UNIQUE NOT NULL, id INT4 UNIQUE NOT NULL, id2 TEXT NOT NULL PRIMARY KEY); + +-- long line to be truncated both ways +CREATE TABLE foo(id INT4 UNIQUE NOT NULL, id2 TEXT NOT NULL PRIMARY KEY, id3 INTEGER NOT NUL, id4 INT4 UNIQUE NOT NULL, id5 TEXT UNIQUE NOT NULL); + +-- long line to be truncated on the left, many lines +CREATE +TEMPORARY +TABLE +foo(id INT4 UNIQUE NOT NULL, id2 TEXT NOT NULL PRIMARY KEY, id3 INTEGER NOT NUL, +id4 INT4 +UNIQUE +NOT +NULL, +id5 TEXT +UNIQUE +NOT +NULL) +; + +-- long line to be truncated on the right, many lines +CREATE +TEMPORARY +TABLE +foo( +id3 INTEGER NOT NUL, id4 INT4 UNIQUE NOT NULL, id5 TEXT UNIQUE NOT NULL, id INT4 UNIQUE NOT NULL, id2 TEXT NOT NULL PRIMARY KEY) +; + +-- long line to be truncated both ways, many lines +CREATE +TEMPORARY +TABLE +foo +(id +INT4 +UNIQUE NOT NULL, idx INT4 UNIQUE NOT NULL, idy INT4 UNIQUE NOT NULL, id2 TEXT NOT NULL PRIMARY KEY, id3 INTEGER NOT NUL, id4 INT4 UNIQUE NOT NULL, id5 TEXT UNIQUE NOT NULL, +idz INT4 UNIQUE NOT NULL, +idv INT4 UNIQUE NOT NULL); + +-- more than 10 lines... +CREATE +TEMPORARY +TABLE +foo +(id +INT4 +UNIQUE +NOT +NULL +, +idm +INT4 +UNIQUE +NOT +NULL, +idx INT4 UNIQUE NOT NULL, idy INT4 UNIQUE NOT NULL, id2 TEXT NOT NULL PRIMARY KEY, id3 INTEGER NOT NUL, id4 INT4 UNIQUE NOT NULL, id5 TEXT UNIQUE NOT NULL, +idz INT4 UNIQUE NOT NULL, +idv +INT4 +UNIQUE +NOT +NULL); + +-- YB: With default max_stack_depth, ASAN build detects stack-overflow first. +-- Prevent that by lowering max_stack_depth temporarily. +set max_stack_depth = '950kB'; + +-- Check that stack depth detection mechanism works and +-- max_stack_depth is not set too high +create function infinite_recurse() returns int as +'select infinite_recurse()' language sql; +\set VERBOSITY terse +select infinite_recurse(); diff --git a/src/test/regress/sql/yb.port.event_trigger.sql b/src/test/regress/sql/yb.port.event_trigger.sql new file mode 100644 index 0000000000000..e7a218103e663 --- /dev/null +++ b/src/test/regress/sql/yb.port.event_trigger.sql @@ -0,0 +1,433 @@ +-- show notice messages to see if triggers get called. +SET client_min_messages TO 'notice'; + +-- should fail, return type mismatch +create event trigger regress_event_trigger + on ddl_command_start + execute procedure pg_backend_pid(); + +-- OK +create function test_event_trigger() returns event_trigger as $$ +BEGIN + RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag; +END +$$ language plpgsql; + +-- should fail, event triggers cannot have declared arguments +create function test_event_trigger_arg(name text) +returns event_trigger as $$ BEGIN RETURN 1; END $$ language plpgsql; + +-- should fail, SQL functions cannot be event triggers +create function test_event_trigger_sql() returns event_trigger as $$ +SELECT 1 $$ language sql; + +-- should fail, no elephant_bootstrap entry point +create event trigger regress_event_trigger on elephant_bootstrap + execute procedure test_event_trigger(); + +-- OK +create event trigger regress_event_trigger on ddl_command_start + execute procedure test_event_trigger(); + +-- OK +create event trigger regress_event_trigger_end on ddl_command_end + execute function test_event_trigger(); + +-- should fail, food is not a valid filter variable +-- Currently tag is the only supported filter in postgres +create event trigger regress_event_trigger2 on ddl_command_start + when food in ('sandwich') + execute procedure test_event_trigger(); + +-- should fail, sandwich is not a valid command tag +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('sandwich') + execute procedure test_event_trigger(); + +-- should fail, create skunkcabbage is not a valid command tag +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('create table', 'create skunkcabbage') + execute procedure test_event_trigger(); + +-- should fail, can't have event triggers on event triggers +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('DROP EVENT TRIGGER') + execute procedure test_event_trigger(); + +-- should fail, can't have event triggers on global objects +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('CREATE ROLE') + execute procedure test_event_trigger(); + +-- should fail, can't have event triggers on global objects +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('CREATE DATABASE') + execute procedure test_event_trigger(); + +-- should fail, can't have event triggers on global objects +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('CREATE TABLESPACE') + execute procedure test_event_trigger(); + +-- should fail, can't have same filter variable twice +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('create table') and tag in ('CREATE FUNCTION') + execute procedure test_event_trigger(); + +-- should fail, can't have arguments +create event trigger regress_event_trigger2 on ddl_command_start + execute procedure test_event_trigger('argument not allowed'); + +-- OK +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('create table', 'CREATE FUNCTION') + execute procedure test_event_trigger(); + +-- OK +comment on event trigger regress_event_trigger is 'test comment'; + +-- drop as non-superuser should fail +create role regress_evt_user; +set role regress_evt_user; +create event trigger regress_event_trigger_noperms on ddl_command_start + execute procedure test_event_trigger(); +reset role; + +-- test enabling and disabling +alter event trigger regress_event_trigger disable; +-- fires _trigger2 and _trigger_end should fire, but not _trigger +create table event_trigger_fire1 (a int); +alter event trigger regress_event_trigger enable; +-- setting session_replication_role as replica disables all triggers +set session_replication_role = replica; +-- fires nothing +create table event_trigger_fire2 (a int); +alter event trigger regress_event_trigger enable replica; +-- fires only _trigger +create table event_trigger_fire3 (a int); +alter event trigger regress_event_trigger enable always; +-- fires only _trigger +create table event_trigger_fire4 (a int); +reset session_replication_role; +-- fires all three +create table event_trigger_fire5 (a int); +-- clean up +alter event trigger regress_event_trigger disable; +drop table event_trigger_fire2, event_trigger_fire3, event_trigger_fire4, event_trigger_fire5; + +-- regress_event_trigger_end should fire on these commands +grant all on table event_trigger_fire1 to public; +comment on table event_trigger_fire1 is 'here is a comment'; +revoke all on table event_trigger_fire1 from public; +drop table event_trigger_fire1; +-- create FDW +create foreign data wrapper useless; +create server useless_server foreign data wrapper useless; +create user mapping for regress_evt_user server useless_server; +alter default privileges for role regress_evt_user + revoke delete on tables from regress_evt_user; + +-- alter owner to non-superuser should fail +alter event trigger regress_event_trigger owner to regress_evt_user; + +-- alter owner to superuser should work +alter role regress_evt_user superuser; +alter event trigger regress_event_trigger owner to regress_evt_user; + +-- should fail, name collision +alter event trigger regress_event_trigger rename to regress_event_trigger2; + +-- OK +alter event trigger regress_event_trigger rename to regress_event_trigger3; + +-- should fail, doesn't exist any more +drop event trigger regress_event_trigger; + +-- TODO(YB): Disable notices as they will mention the objects this depends on. +-- The order of dependencies (from pg_depend) is random in YB. These dependencies are stored in +-- the system table called pg_depend. When you create an event trigger to raise notice while +-- dropping a table, it will drop the related tables stored in pg_depend and subsequently raise +-- notices. The order in which these notices are raised in postgres is dependent on the order in +-- which pg_depend entries are created. However, in YB such ordering is not present and hence +-- tables can be dropped in random orders. Notices also can be raised correspondingly in a random +-- order. Hence we set VERBOSITY to be terse, disabling the appearence of these notices for testing +-- purposes. +\set VERBOSITY terse +-- should fail, regress_evt_user owns some objects +drop role regress_evt_user; +\set VERBOSITY default +-- "owner of user mapping for regress_evt_user on server useless_server" +-- the above statement should be executed after enabling create user mapping + +-- cleanup before next test +-- these are all OK; the second one should emit a NOTICE +drop event trigger if exists regress_event_trigger2; +drop event trigger if exists regress_event_trigger2; +drop event trigger regress_event_trigger3; +drop event trigger regress_event_trigger_end; + +-- test support for dropped objects +CREATE SCHEMA schema_one authorization regress_evt_user; +CREATE SCHEMA schema_two authorization regress_evt_user; +CREATE SCHEMA audit_tbls authorization regress_evt_user; +CREATE TEMP TABLE a_temp_tbl (); +SET SESSION AUTHORIZATION regress_evt_user; + +CREATE TABLE schema_one.table_one(a int); +CREATE TABLE schema_one."table two"(a int); +CREATE TABLE schema_one.table_three(a int); +CREATE TABLE audit_tbls.schema_one_table_two(the_value text); + +CREATE TABLE schema_two.table_two(a int); +CREATE TABLE schema_two.table_three(a int, b text); +CREATE TABLE audit_tbls.schema_two_table_three(the_value text); + +CREATE OR REPLACE FUNCTION schema_two.add(int, int) RETURNS int LANGUAGE plpgsql + CALLED ON NULL INPUT + AS $$ BEGIN RETURN coalesce($1,0) + coalesce($2,0); END; $$; +CREATE AGGREGATE schema_two.newton + (BASETYPE = int, SFUNC = schema_two.add, STYPE = int); + +RESET SESSION AUTHORIZATION; + +CREATE TABLE undroppable_objs ( + object_type text, + object_identity text +); +INSERT INTO undroppable_objs VALUES +('table', 'schema_one.table_three'), +('table', 'audit_tbls.schema_two_table_three'); + +CREATE TABLE dropped_objects ( + type text, + schema text, + object text +); + +-- This tests errors raised within event triggers; the one in audit_tbls +-- uses 2nd-level recursive invocation via test_evtrig_dropped_objects(). +CREATE OR REPLACE FUNCTION undroppable() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +DECLARE + obj record; +BEGIN + PERFORM 1 FROM pg_tables WHERE tablename = 'undroppable_objs'; + IF NOT FOUND THEN + RAISE NOTICE 'table undroppable_objs not found, skipping'; + RETURN; + END IF; + FOR obj IN + SELECT * FROM pg_event_trigger_dropped_objects() JOIN + undroppable_objs USING (object_type, object_identity) + LOOP + RAISE EXCEPTION 'object % of type % cannot be dropped', + obj.object_identity, obj.object_type; + END LOOP; +END; +$$; + +CREATE EVENT TRIGGER undroppable ON sql_drop + EXECUTE PROCEDURE undroppable(); + +CREATE OR REPLACE FUNCTION test_evtrig_dropped_objects() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +DECLARE + obj record; +BEGIN + FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() + LOOP + IF obj.object_type = 'table' THEN + EXECUTE format('DROP TABLE IF EXISTS audit_tbls.%I', + format('%s_%s', obj.schema_name, obj.object_name)); + END IF; + + INSERT INTO dropped_objects + (type, schema, object) VALUES + (obj.object_type, obj.schema_name, obj.object_identity); + END LOOP; +END +$$; + +CREATE EVENT TRIGGER regress_event_trigger_drop_objects ON sql_drop + WHEN TAG IN ('drop table', 'drop function', 'drop view', + 'drop owned', 'drop schema', 'alter table') + EXECUTE PROCEDURE test_evtrig_dropped_objects(); + +ALTER TABLE schema_one.table_one DROP COLUMN a; +DROP SCHEMA schema_one, schema_two CASCADE; + +DELETE FROM undroppable_objs WHERE object_identity = 'audit_tbls.schema_two_table_three'; +DROP SCHEMA schema_one, schema_two CASCADE; + +DELETE FROM undroppable_objs WHERE object_identity = 'schema_one.table_three'; +DROP SCHEMA schema_one, schema_two CASCADE; + +-- ignoring tests related to cascade +-- SELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast'; + +DROP OWNED BY regress_evt_user; +SELECT * FROM dropped_objects WHERE type = 'schema' order by object asc; + +DROP ROLE regress_evt_user; + +DROP EVENT TRIGGER regress_event_trigger_drop_objects; +DROP EVENT TRIGGER undroppable; + +CREATE OR REPLACE FUNCTION event_trigger_report_dropped() + RETURNS event_trigger + LANGUAGE plpgsql +AS $$ +DECLARE r record; +BEGIN + FOR r IN SELECT * from pg_event_trigger_dropped_objects() + LOOP + IF NOT r.normal AND NOT r.original THEN + CONTINUE; + END IF; + RAISE NOTICE 'Event Trigger for drop'; + END LOOP; +END; $$; +CREATE EVENT TRIGGER regress_event_trigger_report_dropped ON sql_drop + EXECUTE PROCEDURE event_trigger_report_dropped(); +CREATE SCHEMA evttrig; +CREATE TABLE evttrig.one (col_a SERIAL PRIMARY KEY, col_b text DEFAULT 'forty two'); +CREATE INDEX one_idx ON evttrig.one (col_b); +CREATE TABLE evttrig.two (col_c INTEGER CHECK (col_c > 0) REFERENCES evttrig.one DEFAULT 42); + +-- Partitioned tables with a partitioned index +CREATE TABLE evttrig.parted ( + id int PRIMARY KEY) + PARTITION BY RANGE (id); +CREATE TABLE evttrig.part_1_10 PARTITION OF evttrig.parted (id) + FOR VALUES FROM (1) TO (10); +CREATE TABLE evttrig.part_10_20 PARTITION OF evttrig.parted (id) + FOR VALUES FROM (10) TO (20) PARTITION BY RANGE (id); +CREATE TABLE evttrig.part_10_15 PARTITION OF evttrig.part_10_20 (id) + FOR VALUES FROM (10) TO (15); +CREATE TABLE evttrig.part_15_20 PARTITION OF evttrig.part_10_20 (id) + FOR VALUES FROM (15) TO (20); + +ALTER TABLE evttrig.two DROP COLUMN col_c; +ALTER TABLE evttrig.one ALTER COLUMN col_b DROP DEFAULT; +ALTER TABLE evttrig.one DROP CONSTRAINT one_pkey; +DROP INDEX evttrig.one_idx; +\set VERBOSITY terse +DROP SCHEMA evttrig CASCADE; +\set VERBOSITY default +DROP TABLE a_temp_tbl; + +DROP EVENT TRIGGER regress_event_trigger_report_dropped; + +-- only allowed from within an event trigger function, should fail +select pg_event_trigger_table_rewrite_oid(); + +-- test Table Rewrite Event Trigger +CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE EXCEPTION 'rewrites not allowed'; +END; +$$; + +create event trigger no_rewrite_allowed on table_rewrite + execute procedure test_evtrig_no_rewrite(); + +create table rewriteme (id serial primary key, foo float, bar timestamptz); +insert into rewriteme + select x * 1.001 from generate_series(1, 500) as t(x); +alter table rewriteme alter column foo type numeric; +alter table rewriteme add column baz int default 0; + +-- test with more than one reason to rewrite a single table +CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'Table ''%'' is being rewritten (reason = %)', + pg_event_trigger_table_rewrite_oid()::regclass, + pg_event_trigger_table_rewrite_reason(); +END; +$$; + +alter table rewriteme + add column onemore int default 0, + add column another int default -1, + alter column foo type numeric(10,4); + +-- shouldn't trigger a table_rewrite event +alter table rewriteme alter column foo type numeric(12,4); +begin; +set timezone to 'UTC'; +alter table rewriteme alter column bar type timestamp; +set timezone to '0'; +alter table rewriteme alter column bar type timestamptz; +set timezone to 'Europe/London'; +alter table rewriteme alter column bar type timestamp; -- does rewrite +rollback; + +-- typed tables are rewritten when their type changes. Don't emit table +-- name, because firing order is not stable. +CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'Table is being rewritten (reason = %)', + pg_event_trigger_table_rewrite_reason(); +END; +$$; + +create type rewritetype as (a int); +create table rewritemetoo1 of rewritetype; +create table rewritemetoo2 of rewritetype; +alter type rewritetype alter attribute a type text cascade; + +-- but this doesn't work +create table rewritemetoo3 (a rewritetype); +alter type rewritetype alter attribute a type varchar cascade; + +drop table rewriteme; +drop event trigger no_rewrite_allowed; +drop function test_evtrig_no_rewrite(); + +-- test Row Security Event Trigger +RESET SESSION AUTHORIZATION; +CREATE TABLE event_trigger_test (a integer, b text); + +CREATE OR REPLACE FUNCTION start_command() +RETURNS event_trigger AS $$ +BEGIN +RAISE NOTICE '% - ddl_command_start', tg_tag; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION end_command() +RETURNS event_trigger AS $$ +BEGIN +RAISE NOTICE '% - ddl_command_end', tg_tag; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION drop_sql_command() +RETURNS event_trigger AS $$ +BEGIN +RAISE NOTICE '% - sql_drop', tg_tag; +END; +$$ LANGUAGE plpgsql; + +CREATE EVENT TRIGGER start_rls_command ON ddl_command_start + WHEN TAG IN ('CREATE POLICY', 'ALTER POLICY', 'DROP POLICY') EXECUTE PROCEDURE start_command(); + +CREATE EVENT TRIGGER end_rls_command ON ddl_command_end + WHEN TAG IN ('CREATE POLICY', 'ALTER POLICY', 'DROP POLICY') EXECUTE PROCEDURE end_command(); + +CREATE EVENT TRIGGER sql_drop_command ON sql_drop + WHEN TAG IN ('DROP POLICY') EXECUTE PROCEDURE drop_sql_command(); + +CREATE POLICY p1 ON event_trigger_test USING (FALSE); +ALTER POLICY p1 ON event_trigger_test USING (TRUE); +ALTER POLICY p1 ON event_trigger_test RENAME TO p2; +DROP POLICY p2 ON event_trigger_test; + +DROP EVENT TRIGGER start_rls_command; +DROP EVENT TRIGGER end_rls_command; +DROP EVENT TRIGGER sql_drop_command; + +RESET client_min_messages; diff --git a/src/test/regress/sql/yb.port.expressions.sql b/src/test/regress/sql/yb.port.expressions.sql new file mode 100644 index 0000000000000..c19f54f2afeed --- /dev/null +++ b/src/test/regress/sql/yb.port.expressions.sql @@ -0,0 +1,36 @@ +-- +-- expression evaluated tests that don't fit into a more specific file +-- + +-- +-- Tests for SQLValueFunction +-- + + +-- current_date (always matches because of transactional behaviour) +SELECT date(now())::text = current_date::text; + + +-- current_time / localtime +SELECT now()::timetz::text = current_time::text; +SELECT now()::time::text = localtime::text; + +-- current_timestamp / localtimestamp (always matches because of transactional behaviour) +SELECT current_timestamp = NOW(); +-- precision +SELECT length(current_timestamp::text) >= length(current_timestamp(0)::text); +-- localtimestamp +SELECT now()::timestamp::text = localtimestamp::text; + +-- current_role/user/user is tested in rolnames.sql + +-- current database / catalog +SELECT current_catalog = current_database(); + +-- current_schema +SELECT current_schema; +SET search_path = 'notme'; +SELECT current_schema; +SET search_path = 'pg_catalog'; +SELECT current_schema; +RESET search_path; diff --git a/src/test/regress/sql/yb.port.fast_default.sql b/src/test/regress/sql/yb.port.fast_default.sql new file mode 100644 index 0000000000000..dee51df710df7 --- /dev/null +++ b/src/test/regress/sql/yb.port.fast_default.sql @@ -0,0 +1,555 @@ +-- +-- ALTER TABLE ADD COLUMN DEFAULT test +-- + +SET search_path = fast_default; +CREATE SCHEMA fast_default; +CREATE TABLE m(id OID); +INSERT INTO m VALUES (NULL::OID); + +CREATE FUNCTION set(tabname name) RETURNS VOID +AS $$ +BEGIN + UPDATE m + SET id = (SELECT c.relfilenode + FROM pg_class AS c, pg_namespace AS s + WHERE c.relname = tabname + AND c.relnamespace = s.oid + AND s.nspname = 'fast_default'); +END; +$$ LANGUAGE 'plpgsql'; + +CREATE FUNCTION comp() RETURNS TEXT +AS $$ +BEGIN + RETURN (SELECT CASE + WHEN m.id = c.relfilenode THEN 'Unchanged' + ELSE 'Rewritten' + END + FROM m, pg_class AS c, pg_namespace AS s + WHERE c.relname = 't' + AND c.relnamespace = s.oid + AND s.nspname = 'fast_default'); +END; +$$ LANGUAGE 'plpgsql'; + +CREATE FUNCTION log_rewrite() RETURNS event_trigger +LANGUAGE plpgsql as +$func$ + +declare + this_schema text; +begin + select into this_schema relnamespace::regnamespace::text + from pg_class + where oid = pg_event_trigger_table_rewrite_oid(); + if this_schema = 'fast_default' + then + RAISE NOTICE 'rewriting table % for reason %', + pg_event_trigger_table_rewrite_oid()::regclass, + pg_event_trigger_table_rewrite_reason(); + end if; +end; +$func$; + +CREATE TABLE has_volatile AS +SELECT * FROM generate_series(1,10) id; + + +CREATE EVENT TRIGGER has_volatile_rewrite + ON table_rewrite + EXECUTE PROCEDURE log_rewrite(); + +-- only the last of these should trigger a rewrite +ALTER TABLE has_volatile ADD col1 int; +ALTER TABLE has_volatile ADD col2 int DEFAULT 1; +ALTER TABLE has_volatile ADD col3 timestamptz DEFAULT current_timestamp; +ALTER TABLE has_volatile ADD col4 int DEFAULT (random() * 10000)::int; + + + +-- Test a large sample of different datatypes +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1); + +SELECT set('t'); + +INSERT INTO T VALUES (1), (2); + +ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT 'hello', + ALTER COLUMN c_int SET DEFAULT 2; + +INSERT INTO T VALUES (3), (4); + + +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'world', + ALTER COLUMN c_bpchar SET DEFAULT 'dog'; + +INSERT INTO T VALUES (5), (6); + +ALTER TABLE T ADD COLUMN c_date DATE DEFAULT '2016-06-02', + ALTER COLUMN c_text SET DEFAULT 'cat'; + +INSERT INTO T VALUES (7), (8); + +ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP DEFAULT '2016-09-01 12:00:00', + ADD COLUMN c_timestamp_null TIMESTAMP, + ALTER COLUMN c_date SET DEFAULT '2010-01-01'; + +INSERT INTO T VALUES (9), (10); + +ALTER TABLE T ADD COLUMN c_array TEXT[] + DEFAULT '{"This", "is", "the", "real", "world"}', + ALTER COLUMN c_timestamp SET DEFAULT '1970-12-31 11:12:13', + ALTER COLUMN c_timestamp_null SET DEFAULT '2016-09-29 12:00:00'; + +INSERT INTO T VALUES (11), (12); + +ALTER TABLE T ADD COLUMN c_small SMALLINT DEFAULT -5, + ADD COLUMN c_small_null SMALLINT, + ALTER COLUMN c_array + SET DEFAULT '{"This", "is", "no", "fantasy"}'; + +INSERT INTO T VALUES (13), (14); + +ALTER TABLE T ADD COLUMN c_big BIGINT DEFAULT 180000000000018, + ALTER COLUMN c_small SET DEFAULT 9, + ALTER COLUMN c_small_null SET DEFAULT 13; + +INSERT INTO T VALUES (15), (16); + +ALTER TABLE T ADD COLUMN c_num NUMERIC DEFAULT 1.00000000001, + ALTER COLUMN c_big SET DEFAULT -9999999999999999; + +INSERT INTO T VALUES (17), (18); + +ALTER TABLE T ADD COLUMN c_time TIME DEFAULT '12:00:00', + ALTER COLUMN c_num SET DEFAULT 2.000000000000002; + +INSERT INTO T VALUES (19), (20); + +ALTER TABLE T ADD COLUMN c_interval INTERVAL DEFAULT '1 day', + ALTER COLUMN c_time SET DEFAULT '23:59:59'; + +INSERT INTO T VALUES (21), (22); + +ALTER TABLE T ADD COLUMN c_hugetext TEXT DEFAULT repeat('abcdefg',1000), + ALTER COLUMN c_interval SET DEFAULT '3 hours'; + +INSERT INTO T VALUES (23), (24); + +ALTER TABLE T ALTER COLUMN c_interval DROP DEFAULT, + ALTER COLUMN c_hugetext SET DEFAULT repeat('poiuyt', 1000); + +INSERT INTO T VALUES (25), (26); + +ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT, + ALTER COLUMN c_date DROP DEFAULT, + ALTER COLUMN c_text DROP DEFAULT, + ALTER COLUMN c_timestamp DROP DEFAULT, + ALTER COLUMN c_array DROP DEFAULT, + ALTER COLUMN c_small DROP DEFAULT, + ALTER COLUMN c_big DROP DEFAULT, + ALTER COLUMN c_num DROP DEFAULT, + ALTER COLUMN c_time DROP DEFAULT, + ALTER COLUMN c_hugetext DROP DEFAULT; + +INSERT INTO T VALUES (27), (28); + +SELECT pk, c_int, c_bpchar, c_text, c_date, c_timestamp, + c_timestamp_null, c_array, c_small, c_small_null, + c_big, c_num, c_time, c_interval, + c_hugetext = repeat('abcdefg',1000) as c_hugetext_origdef, + c_hugetext = repeat('poiuyt', 1000) as c_hugetext_newdef +FROM T ORDER BY pk; + +SELECT comp(); + +DROP TABLE T; + +-- Test expressions in the defaults +CREATE OR REPLACE FUNCTION foo(a INT) RETURNS TEXT AS $$ +DECLARE res TEXT := ''; + i INT; +BEGIN + i := 0; + WHILE (i < a) LOOP + res := res || chr(ascii('a') + i); + i := i + 1; + END LOOP; + RETURN res; +END; $$ LANGUAGE PLPGSQL STABLE; + +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT LENGTH(foo(6))); + +SELECT set('t'); + +INSERT INTO T VALUES (1), (2); + +ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT foo(4), + ALTER COLUMN c_int SET DEFAULT LENGTH(foo(8)); + +INSERT INTO T VALUES (3), (4); + +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT foo(6), + ALTER COLUMN c_bpchar SET DEFAULT foo(3); + +INSERT INTO T VALUES (5), (6); + +ALTER TABLE T ADD COLUMN c_date DATE + DEFAULT '2016-06-02'::DATE + LENGTH(foo(10)), + ALTER COLUMN c_text SET DEFAULT foo(12); + +INSERT INTO T VALUES (7), (8); + +ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP + DEFAULT '2016-09-01'::DATE + LENGTH(foo(10)), + ALTER COLUMN c_date + SET DEFAULT '2010-01-01'::DATE - LENGTH(foo(4)); + +INSERT INTO T VALUES (9), (10); + +ALTER TABLE T ADD COLUMN c_array TEXT[] + DEFAULT ('{"This", "is", "' || foo(4) || + '","the", "real", "world"}')::TEXT[], + ALTER COLUMN c_timestamp + SET DEFAULT '1970-12-31'::DATE + LENGTH(foo(30)); + +INSERT INTO T VALUES (11), (12); + +ALTER TABLE T ALTER COLUMN c_int DROP DEFAULT, + ALTER COLUMN c_array + SET DEFAULT ('{"This", "is", "' || foo(1) || + '", "fantasy"}')::text[]; + +INSERT INTO T VALUES (13), (14); + +ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT, + ALTER COLUMN c_date DROP DEFAULT, + ALTER COLUMN c_text DROP DEFAULT, + ALTER COLUMN c_timestamp DROP DEFAULT, + ALTER COLUMN c_array DROP DEFAULT; + +INSERT INTO T VALUES (15), (16); + +SELECT * FROM T ORDER BY pk; + +SELECT comp(); + +DROP TABLE T; + +DROP FUNCTION foo(INT); + +-- Fall back to full rewrite for volatile expressions +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY); + +INSERT INTO T VALUES (1); + +SELECT set('t'); + +-- now() is stable, because it returns the transaction timestamp +ALTER TABLE T ADD COLUMN c1 TIMESTAMP DEFAULT now(); + +SELECT comp(); + +-- clock_timestamp() is volatile +ALTER TABLE T ADD COLUMN c2 TIMESTAMP DEFAULT clock_timestamp(); + +SELECT comp(); + +DROP TABLE T; + +-- Simple queries +CREATE TABLE T (pk INT NOT NULL PRIMARY KEY); + +SELECT set('t'); + +INSERT INTO T SELECT * FROM generate_series(1, 10) a; + +ALTER TABLE T ADD COLUMN c_bigint BIGINT NOT NULL DEFAULT -1 DEFAULT -2; -- fail due to multiple default values + +ALTER TABLE T ADD COLUMN c_bigint BIGINT NOT NULL DEFAULT -1; + +INSERT INTO T SELECT b, b - 10 FROM generate_series(11, 20) a(b); + +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'hello'; + +INSERT INTO T SELECT b, b - 10, (b + 10)::text FROM generate_series(21, 30) a(b); + +-- WHERE clause +SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1; + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1; + +SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1; + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1; + + +-- COALESCE +SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text) +FROM T +ORDER BY pk LIMIT 10; + +-- Aggregate function +SELECT SUM(c_bigint), MAX(c_text COLLATE "C" ), MIN(c_text COLLATE "C") FROM T; + +-- ORDER BY +SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10; + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10; + +-- LIMIT +SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10; + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10; + +-- DELETE with RETURNING +WITH deleted AS (DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *) + SELECT * FROM deleted ORDER BY pk; +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *; + +-- UPDATE +UPDATE T SET c_text = '"' || c_text || '"' WHERE pk < 10; +SELECT * FROM T WHERE c_text LIKE '"%"' ORDER BY PK; + +SELECT comp(); + +DROP TABLE T; + + +-- Combine with other DDL +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY); + +SELECT set('t'); + +INSERT INTO T VALUES (1), (2); + +ALTER TABLE T ADD COLUMN c_int INT NOT NULL DEFAULT -1; + +INSERT INTO T VALUES (3), (4); + +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'Hello'; + +INSERT INTO T VALUES (5), (6); + +ALTER TABLE T ALTER COLUMN c_text SET DEFAULT 'world', + ALTER COLUMN c_int SET DEFAULT 1; + +INSERT INTO T VALUES (7), (8); + +SELECT * FROM T ORDER BY pk; + +-- Add an index +CREATE INDEX i ON T(c_int, c_text); + +SELECT c_text FROM T WHERE c_int = -1; + +SELECT comp(); + +-- query to exercise expand_tuple function +CREATE TABLE t1 AS +SELECT 1::int AS a , 2::int AS b +FROM generate_series(1,20) q; + +ALTER TABLE t1 ADD COLUMN c text; + +SELECT a, + stddev(cast((SELECT sum(1) FROM generate_series(1,20) x) AS float4)) + OVER (PARTITION BY a,b,c ORDER BY b) + AS z +FROM t1; + +DROP TABLE T; + +-- test that we account for missing columns without defaults correctly +-- in expand_tuple, and that rows are correctly expanded for triggers + +CREATE FUNCTION test_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ + +begin + raise notice 'old tuple: %', to_json(OLD)::text; + if TG_OP = 'DELETE' + then + return OLD; + else + return NEW; + end if; +end; + +$$; + +-- 2 new columns, both have defaults +CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); +INSERT INTO t (a,b,c) VALUES (1,2,3); +ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4; +ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5; +CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); +SELECT * FROM t; +UPDATE t SET y = 2; +SELECT * FROM t; +DROP TABLE t; + +-- 2 new columns, first has default +CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); +INSERT INTO t (a,b,c) VALUES (1,2,3); +ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4; +ALTER TABLE t ADD COLUMN y int; +CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); +SELECT * FROM t; +UPDATE t SET y = 2; +SELECT * FROM t; +DROP TABLE t; + +-- 2 new columns, second has default +CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); +INSERT INTO t (a,b,c) VALUES (1,2,3); +ALTER TABLE t ADD COLUMN x int; +ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5; +CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); +SELECT * FROM t; +UPDATE t SET y = 2; +SELECT * FROM t; +DROP TABLE t; + +-- 2 new columns, neither has default +CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); +INSERT INTO t (a,b,c) VALUES (1,2,3); +ALTER TABLE t ADD COLUMN x int; +ALTER TABLE t ADD COLUMN y int; +CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); +SELECT * FROM t; +UPDATE t SET y = 2; +SELECT * FROM t; +DROP TABLE t; + +-- same as last 4 tests but here the last original column has a NULL value +-- 2 new columns, both have defaults +CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); +INSERT INTO t (a,b,c) VALUES (1,2,NULL); +ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4; +ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5; +CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); +SELECT * FROM t; +UPDATE t SET y = 2; +SELECT * FROM t; +DROP TABLE t; + +-- 2 new columns, first has default +CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); +INSERT INTO t (a,b,c) VALUES (1,2,NULL); +ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4; +ALTER TABLE t ADD COLUMN y int; +CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); +SELECT * FROM t; +UPDATE t SET y = 2; +SELECT * FROM t; +DROP TABLE t; + +-- 2 new columns, second has default +CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); +INSERT INTO t (a,b,c) VALUES (1,2,NULL); +ALTER TABLE t ADD COLUMN x int; +ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5; +CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); +SELECT * FROM t; +UPDATE t SET y = 2; +SELECT * FROM t; +DROP TABLE t; + +-- 2 new columns, neither has default +CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int); +INSERT INTO t (a,b,c) VALUES (1,2,NULL); +ALTER TABLE t ADD COLUMN x int; +ALTER TABLE t ADD COLUMN y int; +CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger(); +SELECT * FROM t; +UPDATE t SET y = 2; +SELECT * FROM t; +DROP TABLE t; + +-- make sure expanded tuple has correct self pointer +-- it will be required by the RI trigger doing the cascading delete + +CREATE TABLE leader (a int PRIMARY KEY, b int); +CREATE TABLE follower (a int REFERENCES leader ON DELETE CASCADE, b int); +INSERT INTO leader VALUES (1, 1), (2, 2); +ALTER TABLE leader ADD c int; +ALTER TABLE leader DROP c; +DELETE FROM leader; + +-- check that ALTER TABLE ... ALTER TYPE does the right thing + +CREATE TABLE vtype( a integer); +INSERT INTO vtype VALUES (1); +ALTER TABLE vtype ADD COLUMN b DOUBLE PRECISION DEFAULT 0.2; +ALTER TABLE vtype ADD COLUMN c BOOLEAN DEFAULT true; +SELECT * FROM vtype; +ALTER TABLE vtype + ALTER b TYPE text USING b::text, + ALTER c TYPE text USING c::text; +SELECT * FROM vtype; + +-- also check the case that doesn't rewrite the table + +CREATE TABLE vtype2 (a int); +INSERT INTO vtype2 VALUES (1); +ALTER TABLE vtype2 ADD COLUMN b varchar(10) DEFAULT 'xxx'; +ALTER TABLE vtype2 ALTER COLUMN b SET DEFAULT 'yyy'; +INSERT INTO vtype2 VALUES (2); + +ALTER TABLE vtype2 ALTER COLUMN b TYPE varchar(20) USING b::varchar(20); +SELECT * FROM vtype2 ORDER BY a; + +-- YB Note: This test is disabled in YB because the INSERT will be aborted +-- due to the ALTER TABLE being executed in the same transaction. +-- Additionally, the referenced issue doesn't apply to YB relations because +-- missing column values for YB relations are filled in by DocDB, +-- so heap_getattr() will never have to look for missing attributes. +-- -- Ensure that defaults are checked when evaluating whether HOT update +-- -- is possible, this was broken for a while: +-- -- https://postgr.es/m/20190202133521.ylauh3ckqa7colzj%40alap3.anarazel.de +-- BEGIN; +-- CREATE TABLE t(); +-- INSERT INTO t DEFAULT VALUES; +-- ALTER TABLE t ADD COLUMN a int DEFAULT 1; +-- CREATE INDEX ON t(a); +-- -- set column with a default 1 to NULL, due to a bug that wasn't +-- -- noticed has heap_getattr buggily returned NULL for default columns +-- UPDATE t SET a = NULL; + +-- -- verify that index and non-index scans show the same result +-- SET LOCAL enable_seqscan = true; +-- SELECT * FROM t WHERE a IS NULL; +-- SET LOCAL enable_seqscan = false; +-- SELECT * FROM t WHERE a IS NULL; +-- ROLLBACK; + + +-- cleanup +DROP TABLE vtype; +DROP TABLE vtype2; +DROP TABLE follower; +DROP TABLE leader; +DROP FUNCTION test_trigger(); +DROP TABLE t1; +DROP FUNCTION set(name); +DROP FUNCTION comp(); +DROP TABLE m; +DROP TABLE has_volatile; +DROP EVENT TRIGGER has_volatile_rewrite; +DROP FUNCTION log_rewrite; +DROP SCHEMA fast_default; + +-- Leave a table with an active fast default in place, for pg_upgrade testing +set search_path = public; +create table has_fast_default(f1 int); +insert into has_fast_default values(1); +alter table has_fast_default add column f2 int default 42; +table has_fast_default; diff --git a/src/test/regress/sql/yb.port.float4.sql b/src/test/regress/sql/yb.port.float4.sql new file mode 100644 index 0000000000000..6a577c5d6328e --- /dev/null +++ b/src/test/regress/sql/yb.port.float4.sql @@ -0,0 +1,359 @@ +-- +-- FLOAT4 +-- + +CREATE TABLE FLOAT4_TBL (f1 float4); + +INSERT INTO FLOAT4_TBL(f1) VALUES (' 0.0'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('1004.30 '); +INSERT INTO FLOAT4_TBL(f1) VALUES (' -34.84 '); +INSERT INTO FLOAT4_TBL(f1) VALUES ('1.2345678901234e+20'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('1.2345678901234e-20'); + +-- test for over and under flow +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70'); + +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e70'::float8); +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e70'::float8); +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-70'::float8); +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-70'::float8); + +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e400'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e400'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('10e-400'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('-10e-400'); + +-- bad input +INSERT INTO FLOAT4_TBL(f1) VALUES (''); +INSERT INTO FLOAT4_TBL(f1) VALUES (' '); +INSERT INTO FLOAT4_TBL(f1) VALUES ('xyz'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('5.0.0'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('5 . 0'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('5. 0'); +INSERT INTO FLOAT4_TBL(f1) VALUES (' - 3.0'); +INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5'); + +-- special inputs +SELECT 'NaN'::float4; +SELECT 'nan'::float4; +SELECT ' NAN '::float4; +SELECT 'infinity'::float4; +SELECT ' -INFINiTY '::float4; +-- bad special inputs +SELECT 'N A N'::float4; +SELECT 'NaN x'::float4; +SELECT ' INFINITY x'::float4; + +SELECT 'Infinity'::float4 + 100.0; +SELECT 'Infinity'::float4 / 'Infinity'::float4; +SELECT '42'::float4 / 'Infinity'::float4; +SELECT 'nan'::float4 / 'nan'::float4; +SELECT 'nan'::float4 / '0'::float4; +SELECT 'nan'::numeric::float4; + +SELECT * FROM FLOAT4_TBL; + +SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 <> '1004.3'; + +SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 = '1004.3'; + +SELECT f.* FROM FLOAT4_TBL f WHERE '1004.3' > f.f1; + +SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 < '1004.3'; + +SELECT f.* FROM FLOAT4_TBL f WHERE '1004.3' >= f.f1; + +SELECT f.* FROM FLOAT4_TBL f WHERE f.f1 <= '1004.3'; + +SELECT f.f1, f.f1 * '-10' AS x FROM FLOAT4_TBL f + WHERE f.f1 > '0.0'; + +SELECT f.f1, f.f1 + '-10' AS x FROM FLOAT4_TBL f + WHERE f.f1 > '0.0'; + +SELECT f.f1, f.f1 / '-10' AS x FROM FLOAT4_TBL f + WHERE f.f1 > '0.0'; + +SELECT f.f1, f.f1 - '-10' AS x FROM FLOAT4_TBL f + WHERE f.f1 > '0.0'; + +-- test divide by zero +SELECT f.f1 / '0.0' from FLOAT4_TBL f; + +SELECT * FROM FLOAT4_TBL; + +-- test the unary float4abs operator +SELECT f.f1, @f.f1 AS abs_f1 FROM FLOAT4_TBL f; + +WITH ybtmp AS (DELETE FROM FLOAT4_TBL WHERE FLOAT4_TBL.f1 > '0.0' RETURNING *) + INSERT INTO FLOAT4_TBL SELECT * FROM ybtmp; -- YB: imitate PG ctid reallocation for the following update + +UPDATE FLOAT4_TBL + SET f1 = FLOAT4_TBL.f1 * '-1' + WHERE FLOAT4_TBL.f1 > '0.0'; + +SELECT * FROM FLOAT4_TBL; + +-- test edge-case coercions to integer +SELECT '32767.4'::float4::int2; +SELECT '32767.6'::float4::int2; +SELECT '-32768.4'::float4::int2; +SELECT '-32768.6'::float4::int2; +SELECT '2147483520'::float4::int4; +SELECT '2147483647'::float4::int4; +SELECT '-2147483648.5'::float4::int4; +SELECT '-2147483900'::float4::int4; +SELECT '9223369837831520256'::float4::int8; +SELECT '9223372036854775807'::float4::int8; +SELECT '-9223372036854775808.5'::float4::int8; +SELECT '-9223380000000000000'::float4::int8; + +-- Test for correct input rounding in edge cases. +-- These lists are from Paxson 1991, excluding subnormals and +-- inputs of over 9 sig. digits. + +SELECT float4send('5e-20'::float4); +SELECT float4send('67e14'::float4); +SELECT float4send('985e15'::float4); +SELECT float4send('55895e-16'::float4); +SELECT float4send('7038531e-32'::float4); +SELECT float4send('702990899e-20'::float4); + +SELECT float4send('3e-23'::float4); +SELECT float4send('57e18'::float4); +SELECT float4send('789e-35'::float4); +SELECT float4send('2539e-18'::float4); +SELECT float4send('76173e28'::float4); +SELECT float4send('887745e-11'::float4); +SELECT float4send('5382571e-37'::float4); +SELECT float4send('82381273e-35'::float4); +SELECT float4send('750486563e-38'::float4); + +-- Test that the smallest possible normalized input value inputs +-- correctly, either in 9-significant-digit or shortest-decimal +-- format. +-- +-- exact val is 1.1754943508... +-- shortest val is 1.1754944000 +-- midpoint to next val is 1.1754944208... + +SELECT float4send('1.17549435e-38'::float4); +SELECT float4send('1.1754944e-38'::float4); + +-- test output (and round-trip safety) of various values. +-- To ensure we're testing what we think we're testing, start with +-- float values specified by bit patterns (as a useful side effect, +-- this means we'll fail on non-IEEE platforms). + +create type xfloat4; +create function xfloat4in(cstring) returns xfloat4 immutable strict + language internal as 'int4in'; +create function xfloat4out(xfloat4) returns cstring immutable strict + language internal as 'int4out'; +create type xfloat4 (input = xfloat4in, output = xfloat4out, like = float4); +create cast (xfloat4 as float4) without function; +create cast (float4 as xfloat4) without function; +create cast (xfloat4 as integer) without function; +create cast (integer as xfloat4) without function; + +-- float4: seeeeeee emmmmmmm mmmmmmmm mmmmmmmm + +-- we don't care to assume the platform's strtod() handles subnormals +-- correctly; those are "use at your own risk". However we do test +-- subnormal outputs, since those are under our control. + +with testdata(bits) as (values + -- small subnormals + (x'00000001'), + (x'00000002'), (x'00000003'), + (x'00000010'), (x'00000011'), (x'00000100'), (x'00000101'), + (x'00004000'), (x'00004001'), (x'00080000'), (x'00080001'), + -- stress values + (x'0053c4f4'), -- 7693e-42 + (x'006c85c4'), -- 996622e-44 + (x'0041ca76'), -- 60419369e-46 + (x'004b7678'), -- 6930161142e-48 + -- taken from upstream testsuite + (x'00000007'), + (x'00424fe2'), + -- borderline between subnormal and normal + (x'007ffff0'), (x'007ffff1'), (x'007ffffe'), (x'007fffff')) +select float4send(flt) as ibits, + flt + from (select bits::integer::xfloat4::float4 as flt + from testdata + offset 0) s; + +with testdata(bits) as (values + (x'00000000'), + -- smallest normal values + (x'00800000'), (x'00800001'), (x'00800004'), (x'00800005'), + (x'00800006'), + -- small normal values chosen for short vs. long output + (x'008002f1'), (x'008002f2'), (x'008002f3'), + (x'00800e17'), (x'00800e18'), (x'00800e19'), + -- assorted values (random mantissae) + (x'01000001'), (x'01102843'), (x'01a52c98'), + (x'0219c229'), (x'02e4464d'), (x'037343c1'), (x'03a91b36'), + (x'047ada65'), (x'0496fe87'), (x'0550844f'), (x'05999da3'), + (x'060ea5e2'), (x'06e63c45'), (x'07f1e548'), (x'0fc5282b'), + (x'1f850283'), (x'2874a9d6'), + -- values around 5e-08 + (x'3356bf94'), (x'3356bf95'), (x'3356bf96'), + -- around 1e-07 + (x'33d6bf94'), (x'33d6bf95'), (x'33d6bf96'), + -- around 3e-07 .. 1e-04 + (x'34a10faf'), (x'34a10fb0'), (x'34a10fb1'), + (x'350637bc'), (x'350637bd'), (x'350637be'), + (x'35719786'), (x'35719787'), (x'35719788'), + (x'358637bc'), (x'358637bd'), (x'358637be'), + (x'36a7c5ab'), (x'36a7c5ac'), (x'36a7c5ad'), + (x'3727c5ab'), (x'3727c5ac'), (x'3727c5ad'), + -- format crossover at 1e-04 + (x'38d1b714'), (x'38d1b715'), (x'38d1b716'), + (x'38d1b717'), (x'38d1b718'), (x'38d1b719'), + (x'38d1b71a'), (x'38d1b71b'), (x'38d1b71c'), + (x'38d1b71d'), + -- + (x'38dffffe'), (x'38dfffff'), (x'38e00000'), + (x'38efffff'), (x'38f00000'), (x'38f00001'), + (x'3a83126e'), (x'3a83126f'), (x'3a831270'), + (x'3c23d709'), (x'3c23d70a'), (x'3c23d70b'), + (x'3dcccccc'), (x'3dcccccd'), (x'3dccccce'), + -- chosen to need 9 digits for 3dcccd70 + (x'3dcccd6f'), (x'3dcccd70'), (x'3dcccd71'), + -- + (x'3effffff'), (x'3f000000'), (x'3f000001'), + (x'3f333332'), (x'3f333333'), (x'3f333334'), + -- approach 1.0 with increasing numbers of 9s + (x'3f666665'), (x'3f666666'), (x'3f666667'), + (x'3f7d70a3'), (x'3f7d70a4'), (x'3f7d70a5'), + (x'3f7fbe76'), (x'3f7fbe77'), (x'3f7fbe78'), + (x'3f7ff971'), (x'3f7ff972'), (x'3f7ff973'), + (x'3f7fff57'), (x'3f7fff58'), (x'3f7fff59'), + (x'3f7fffee'), (x'3f7fffef'), + -- values very close to 1 + (x'3f7ffff0'), (x'3f7ffff1'), (x'3f7ffff2'), + (x'3f7ffff3'), (x'3f7ffff4'), (x'3f7ffff5'), + (x'3f7ffff6'), (x'3f7ffff7'), (x'3f7ffff8'), + (x'3f7ffff9'), (x'3f7ffffa'), (x'3f7ffffb'), + (x'3f7ffffc'), (x'3f7ffffd'), (x'3f7ffffe'), + (x'3f7fffff'), + (x'3f800000'), + (x'3f800001'), (x'3f800002'), (x'3f800003'), + (x'3f800004'), (x'3f800005'), (x'3f800006'), + (x'3f800007'), (x'3f800008'), (x'3f800009'), + -- values 1 to 1.1 + (x'3f80000f'), (x'3f800010'), (x'3f800011'), + (x'3f800012'), (x'3f800013'), (x'3f800014'), + (x'3f800017'), (x'3f800018'), (x'3f800019'), + (x'3f80001a'), (x'3f80001b'), (x'3f80001c'), + (x'3f800029'), (x'3f80002a'), (x'3f80002b'), + (x'3f800053'), (x'3f800054'), (x'3f800055'), + (x'3f800346'), (x'3f800347'), (x'3f800348'), + (x'3f8020c4'), (x'3f8020c5'), (x'3f8020c6'), + (x'3f8147ad'), (x'3f8147ae'), (x'3f8147af'), + (x'3f8ccccc'), (x'3f8ccccd'), (x'3f8cccce'), + -- + (x'3fc90fdb'), -- pi/2 + (x'402df854'), -- e + (x'40490fdb'), -- pi + -- + (x'409fffff'), (x'40a00000'), (x'40a00001'), + (x'40afffff'), (x'40b00000'), (x'40b00001'), + (x'411fffff'), (x'41200000'), (x'41200001'), + (x'42c7ffff'), (x'42c80000'), (x'42c80001'), + (x'4479ffff'), (x'447a0000'), (x'447a0001'), + (x'461c3fff'), (x'461c4000'), (x'461c4001'), + (x'47c34fff'), (x'47c35000'), (x'47c35001'), + (x'497423ff'), (x'49742400'), (x'49742401'), + (x'4b18967f'), (x'4b189680'), (x'4b189681'), + (x'4cbebc1f'), (x'4cbebc20'), (x'4cbebc21'), + (x'4e6e6b27'), (x'4e6e6b28'), (x'4e6e6b29'), + (x'501502f8'), (x'501502f9'), (x'501502fa'), + (x'51ba43b6'), (x'51ba43b7'), (x'51ba43b8'), + -- stress values + (x'1f6c1e4a'), -- 5e-20 + (x'59be6cea'), -- 67e14 + (x'5d5ab6c4'), -- 985e15 + (x'2cc4a9bd'), -- 55895e-16 + (x'15ae43fd'), -- 7038531e-32 + (x'2cf757ca'), -- 702990899e-20 + (x'665ba998'), -- 25933168707e13 + (x'743c3324'), -- 596428896559e20 + -- exercise fixed-point memmoves + (x'47f1205a'), + (x'4640e6ae'), + (x'449a5225'), + (x'42f6e9d5'), + (x'414587dd'), + (x'3f9e064b'), + -- these cases come from the upstream's testsuite + -- BoundaryRoundEven + (x'4c000004'), + (x'50061c46'), + (x'510006a8'), + -- ExactValueRoundEven + (x'48951f84'), + (x'45fd1840'), + -- LotsOfTrailingZeros + (x'39800000'), + (x'3b200000'), + (x'3b900000'), + (x'3bd00000'), + -- Regression + (x'63800000'), + (x'4b000000'), + (x'4b800000'), + (x'4c000001'), + (x'4c800b0d'), + (x'00d24584'), + (x'00d90b88'), + (x'45803f34'), + (x'4f9f24f7'), + (x'3a8722c3'), + (x'5c800041'), + (x'15ae43fd'), + (x'5d4cccfb'), + (x'4c800001'), + (x'57800ed8'), + (x'5f000000'), + (x'700000f0'), + (x'5f23e9ac'), + (x'5e9502f9'), + (x'5e8012b1'), + (x'3c000028'), + (x'60cde861'), + (x'03aa2a50'), + (x'43480000'), + (x'4c000000'), + -- LooksLikePow5 + (x'5D1502F9'), + (x'5D9502F9'), + (x'5E1502F9'), + -- OutputLength + (x'3f99999a'), + (x'3f9d70a4'), + (x'3f9df3b6'), + (x'3f9e0419'), + (x'3f9e0610'), + (x'3f9e064b'), + (x'3f9e0651'), + (x'03d20cfe') +) +select float4send(flt) as ibits, + flt, + flt::text::float4 as r_flt, + float4send(flt::text::float4) as obits, + float4send(flt::text::float4) = float4send(flt) as correct + from (select bits::integer::xfloat4::float4 as flt + from testdata + offset 0) s; + +-- clean up, lest opr_sanity complain +\set VERBOSITY terse \\ -- YB: suppress cascade details +drop type xfloat4 cascade; +\set VERBOSITY default \\ -- YB diff --git a/src/test/regress/sql/yb.port.float8.sql b/src/test/regress/sql/yb.port.float8.sql new file mode 100644 index 0000000000000..bf90230762bb4 --- /dev/null +++ b/src/test/regress/sql/yb.port.float8.sql @@ -0,0 +1,505 @@ +-- +-- FLOAT8 +-- + +-- +-- Build a table for testing +-- (This temporarily hides the table created in test_setup.sql) +-- YB note: but we also want to test YB tables instead of temporary tables, so +-- create a new table in a new namespace instead. +-- +CREATE SCHEMA yb_tmp; +SET search_path TO yb_tmp; +CREATE TABLE FLOAT8_TBL(f1 float8); + +INSERT INTO FLOAT8_TBL(f1) VALUES (' 0.0 '); +INSERT INTO FLOAT8_TBL(f1) VALUES ('1004.30 '); +INSERT INTO FLOAT8_TBL(f1) VALUES (' -34.84'); +INSERT INTO FLOAT8_TBL(f1) VALUES ('1.2345678901234e+200'); +INSERT INTO FLOAT8_TBL(f1) VALUES ('1.2345678901234e-200'); + +-- test for underflow and overflow handling +SELECT '10e400'::float8; +SELECT '-10e400'::float8; +SELECT '10e-400'::float8; +SELECT '-10e-400'::float8; + +-- test smallest normalized input +SELECT float8send('2.2250738585072014E-308'::float8); + +-- bad input +INSERT INTO FLOAT8_TBL(f1) VALUES (''); +INSERT INTO FLOAT8_TBL(f1) VALUES (' '); +INSERT INTO FLOAT8_TBL(f1) VALUES ('xyz'); +INSERT INTO FLOAT8_TBL(f1) VALUES ('5.0.0'); +INSERT INTO FLOAT8_TBL(f1) VALUES ('5 . 0'); +INSERT INTO FLOAT8_TBL(f1) VALUES ('5. 0'); +INSERT INTO FLOAT8_TBL(f1) VALUES (' - 3'); +INSERT INTO FLOAT8_TBL(f1) VALUES ('123 5'); + +-- special inputs +SELECT 'NaN'::float8; +SELECT 'nan'::float8; +SELECT ' NAN '::float8; +SELECT 'infinity'::float8; +SELECT ' -INFINiTY '::float8; +-- bad special inputs +SELECT 'N A N'::float8; +SELECT 'NaN x'::float8; +SELECT ' INFINITY x'::float8; + +SELECT 'Infinity'::float8 + 100.0; +SELECT 'Infinity'::float8 / 'Infinity'::float8; +SELECT '42'::float8 / 'Infinity'::float8; +SELECT 'nan'::float8 / 'nan'::float8; +SELECT 'nan'::float8 / '0'::float8; +SELECT 'nan'::numeric::float8; + +SELECT * FROM FLOAT8_TBL; + +SELECT f.* FROM FLOAT8_TBL f WHERE f.f1 <> '1004.3'; + +SELECT f.* FROM FLOAT8_TBL f WHERE f.f1 = '1004.3'; + +SELECT f.* FROM FLOAT8_TBL f WHERE '1004.3' > f.f1; + +SELECT f.* FROM FLOAT8_TBL f WHERE f.f1 < '1004.3'; + +SELECT f.* FROM FLOAT8_TBL f WHERE '1004.3' >= f.f1; + +SELECT f.* FROM FLOAT8_TBL f WHERE f.f1 <= '1004.3'; + +SELECT f.f1, f.f1 * '-10' AS x + FROM FLOAT8_TBL f + WHERE f.f1 > '0.0'; + +SELECT f.f1, f.f1 + '-10' AS x + FROM FLOAT8_TBL f + WHERE f.f1 > '0.0'; + +SELECT f.f1, f.f1 / '-10' AS x + FROM FLOAT8_TBL f + WHERE f.f1 > '0.0'; + +SELECT f.f1, f.f1 - '-10' AS x + FROM FLOAT8_TBL f + WHERE f.f1 > '0.0'; + +SELECT f.f1 ^ '2.0' AS square_f1 + FROM FLOAT8_TBL f where f.f1 = '1004.3'; + +-- absolute value +SELECT f.f1, @f.f1 AS abs_f1 + FROM FLOAT8_TBL f; + +-- truncate +SELECT f.f1, trunc(f.f1) AS trunc_f1 + FROM FLOAT8_TBL f; + +-- round +SELECT f.f1, round(f.f1) AS round_f1 + FROM FLOAT8_TBL f; + +-- ceil / ceiling +select ceil(f1) as ceil_f1 from float8_tbl f; +select ceiling(f1) as ceiling_f1 from float8_tbl f; + +-- floor +select floor(f1) as floor_f1 from float8_tbl f; + +-- sign +select sign(f1) as sign_f1 from float8_tbl f; + +-- avoid bit-exact output here because operations may not be bit-exact. +SET extra_float_digits = 0; + +-- square root +SELECT sqrt(float8 '64') AS eight; + +SELECT |/ float8 '64' AS eight; + +SELECT f.f1, |/f.f1 AS sqrt_f1 + FROM FLOAT8_TBL f + WHERE f.f1 > '0.0'; + +-- power +SELECT power(float8 '144', float8 '0.5'); +SELECT power(float8 'NaN', float8 '0.5'); +SELECT power(float8 '144', float8 'NaN'); +SELECT power(float8 'NaN', float8 'NaN'); +SELECT power(float8 '-1', float8 'NaN'); +SELECT power(float8 '1', float8 'NaN'); +SELECT power(float8 'NaN', float8 '0'); +SELECT power(float8 'inf', float8 '0'); +SELECT power(float8 '-inf', float8 '0'); +SELECT power(float8 '0', float8 'inf'); +SELECT power(float8 '0', float8 '-inf'); +SELECT power(float8 '1', float8 'inf'); +SELECT power(float8 '1', float8 '-inf'); +SELECT power(float8 '-1', float8 'inf'); +SELECT power(float8 '-1', float8 '-inf'); +SELECT power(float8 '0.1', float8 'inf'); +SELECT power(float8 '-0.1', float8 'inf'); +SELECT power(float8 '1.1', float8 'inf'); +SELECT power(float8 '-1.1', float8 'inf'); +SELECT power(float8 '0.1', float8 '-inf'); +SELECT power(float8 '-0.1', float8 '-inf'); +SELECT power(float8 '1.1', float8 '-inf'); +SELECT power(float8 '-1.1', float8 '-inf'); +SELECT power(float8 'inf', float8 '-2'); +SELECT power(float8 'inf', float8 '2'); +SELECT power(float8 'inf', float8 'inf'); +SELECT power(float8 'inf', float8 '-inf'); +-- Intel's icc misoptimizes the code that controls the sign of this result, +-- even with -mp1. Pending a fix for that, only test for "is it zero". +SELECT power(float8 '-inf', float8 '-2') = '0'; +SELECT power(float8 '-inf', float8 '-3'); +SELECT power(float8 '-inf', float8 '2'); +SELECT power(float8 '-inf', float8 '3'); +SELECT power(float8 '-inf', float8 '3.5'); +SELECT power(float8 '-inf', float8 'inf'); +SELECT power(float8 '-inf', float8 '-inf'); + +-- take exp of ln(f.f1) +SELECT f.f1, exp(ln(f.f1)) AS exp_ln_f1 + FROM FLOAT8_TBL f + WHERE f.f1 > '0.0'; + +-- check edge cases for exp +SELECT exp('inf'::float8), exp('-inf'::float8), exp('nan'::float8); + +-- cube root +SELECT ||/ float8 '27' AS three; + +SELECT f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f; + + +SELECT * FROM FLOAT8_TBL; + +WITH ybtmp AS (DELETE FROM FLOAT8_TBL WHERE FLOAT8_TBL.f1 > '0.0' RETURNING *) + INSERT INTO FLOAT8_TBL SELECT * FROM ybtmp; -- YB: imitate PG ctid reallocation for the following update + +UPDATE FLOAT8_TBL + SET f1 = FLOAT8_TBL.f1 * '-1' + WHERE FLOAT8_TBL.f1 > '0.0'; + +SELECT f.f1 * '1e200' from FLOAT8_TBL f; + +SELECT f.f1 ^ '1e200' from FLOAT8_TBL f; + +SELECT 0 ^ 0 + 0 ^ 1 + 0 ^ 0.0 + 0 ^ 0.5; + +SELECT ln(f.f1) from FLOAT8_TBL f where f.f1 = '0.0' ; + +SELECT ln(f.f1) from FLOAT8_TBL f where f.f1 < '0.0' ; + +SELECT exp(f.f1) from FLOAT8_TBL f; + +SELECT f.f1 / '0.0' from FLOAT8_TBL f; + +SELECT * FROM FLOAT8_TBL; + +-- hyperbolic functions +-- we run these with extra_float_digits = 0 too, since different platforms +-- tend to produce results that vary in the last place. +SELECT sinh(float8 '1'); +SELECT cosh(float8 '1'); +SELECT tanh(float8 '1'); +SELECT asinh(float8 '1'); +SELECT acosh(float8 '2'); +SELECT atanh(float8 '0.5'); +-- test Inf/NaN cases for hyperbolic functions +SELECT sinh(float8 'infinity'); +SELECT sinh(float8 '-infinity'); +SELECT sinh(float8 'nan'); +SELECT cosh(float8 'infinity'); +SELECT cosh(float8 '-infinity'); +SELECT cosh(float8 'nan'); +SELECT tanh(float8 'infinity'); +SELECT tanh(float8 '-infinity'); +SELECT tanh(float8 'nan'); +SELECT asinh(float8 'infinity'); +SELECT asinh(float8 '-infinity'); +SELECT asinh(float8 'nan'); +-- acosh(Inf) should be Inf, but some mingw versions produce NaN, so skip test +-- SELECT acosh(float8 'infinity'); +SELECT acosh(float8 '-infinity'); +SELECT acosh(float8 'nan'); +SELECT atanh(float8 'infinity'); +SELECT atanh(float8 '-infinity'); +SELECT atanh(float8 'nan'); + +RESET extra_float_digits; + +-- test for over- and underflow +INSERT INTO FLOAT8_TBL(f1) VALUES ('10e400'); + +INSERT INTO FLOAT8_TBL(f1) VALUES ('-10e400'); + +INSERT INTO FLOAT8_TBL(f1) VALUES ('10e-400'); + +INSERT INTO FLOAT8_TBL(f1) VALUES ('-10e-400'); + +DROP TABLE FLOAT8_TBL; + +DROP SCHEMA yb_tmp; -- YB note: reset the changes from above. +RESET search_path; -- YB + +-- Check the float8 values exported for use by other tests + +SELECT * FROM FLOAT8_TBL; + +-- test edge-case coercions to integer +SELECT '32767.4'::float8::int2; +SELECT '32767.6'::float8::int2; +SELECT '-32768.4'::float8::int2; +SELECT '-32768.6'::float8::int2; +SELECT '2147483647.4'::float8::int4; +SELECT '2147483647.6'::float8::int4; +SELECT '-2147483648.4'::float8::int4; +SELECT '-2147483648.6'::float8::int4; +SELECT '9223372036854773760'::float8::int8; +SELECT '9223372036854775807'::float8::int8; +SELECT '-9223372036854775808.5'::float8::int8; +SELECT '-9223372036854780000'::float8::int8; + +-- test exact cases for trigonometric functions in degrees + +SELECT x, + sind(x), + sind(x) IN (-1,-0.5,0,0.5,1) AS sind_exact +FROM (VALUES (0), (30), (90), (150), (180), + (210), (270), (330), (360)) AS t(x); + +SELECT x, + cosd(x), + cosd(x) IN (-1,-0.5,0,0.5,1) AS cosd_exact +FROM (VALUES (0), (60), (90), (120), (180), + (240), (270), (300), (360)) AS t(x); + +SELECT x, + tand(x), + tand(x) IN ('-Infinity'::float8,-1,0, + 1,'Infinity'::float8) AS tand_exact, + cotd(x), + cotd(x) IN ('-Infinity'::float8,-1,0, + 1,'Infinity'::float8) AS cotd_exact +FROM (VALUES (0), (45), (90), (135), (180), + (225), (270), (315), (360)) AS t(x); + +SELECT x, + asind(x), + asind(x) IN (-90,-30,0,30,90) AS asind_exact, + acosd(x), + acosd(x) IN (0,60,90,120,180) AS acosd_exact +FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x); + +SELECT x, + atand(x), + atand(x) IN (-90,-45,0,45,90) AS atand_exact +FROM (VALUES ('-Infinity'::float8), (-1), (0), (1), + ('Infinity'::float8)) AS t(x); + +SELECT x, y, + atan2d(y, x), + atan2d(y, x) IN (-90,0,90,180) AS atan2d_exact +FROM (SELECT 10*cosd(a), 10*sind(a) + FROM generate_series(0, 360, 90) AS t(a)) AS t(x,y); + +-- +-- test output (and round-trip safety) of various values. +-- To ensure we're testing what we think we're testing, start with +-- float values specified by bit patterns (as a useful side effect, +-- this means we'll fail on non-IEEE platforms). + +create type xfloat8; +create function xfloat8in(cstring) returns xfloat8 immutable strict + language internal as 'int8in'; +create function xfloat8out(xfloat8) returns cstring immutable strict + language internal as 'int8out'; +create type xfloat8 (input = xfloat8in, output = xfloat8out, like = float8); +create cast (xfloat8 as float8) without function; +create cast (float8 as xfloat8) without function; +create cast (xfloat8 as bigint) without function; +create cast (bigint as xfloat8) without function; + +-- float8: seeeeeee eeeeeeee eeeeeeee mmmmmmmm mmmmmmmm(x4) + +-- we don't care to assume the platform's strtod() handles subnormals +-- correctly; those are "use at your own risk". However we do test +-- subnormal outputs, since those are under our control. + +with testdata(bits) as (values + -- small subnormals + (x'0000000000000001'), + (x'0000000000000002'), (x'0000000000000003'), + (x'0000000000001000'), (x'0000000100000000'), + (x'0000010000000000'), (x'0000010100000000'), + (x'0000400000000000'), (x'0000400100000000'), + (x'0000800000000000'), (x'0000800000000001'), + -- these values taken from upstream testsuite + (x'00000000000f4240'), + (x'00000000016e3600'), + (x'0000008cdcdea440'), + -- borderline between subnormal and normal + (x'000ffffffffffff0'), (x'000ffffffffffff1'), + (x'000ffffffffffffe'), (x'000fffffffffffff')) +select float8send(flt) as ibits, + flt + from (select bits::bigint::xfloat8::float8 as flt + from testdata + offset 0) s; + +-- round-trip tests + +with testdata(bits) as (values + (x'0000000000000000'), + -- smallest normal values + (x'0010000000000000'), (x'0010000000000001'), + (x'0010000000000002'), (x'0018000000000000'), + -- + (x'3ddb7cdfd9d7bdba'), (x'3ddb7cdfd9d7bdbb'), (x'3ddb7cdfd9d7bdbc'), + (x'3e112e0be826d694'), (x'3e112e0be826d695'), (x'3e112e0be826d696'), + (x'3e45798ee2308c39'), (x'3e45798ee2308c3a'), (x'3e45798ee2308c3b'), + (x'3e7ad7f29abcaf47'), (x'3e7ad7f29abcaf48'), (x'3e7ad7f29abcaf49'), + (x'3eb0c6f7a0b5ed8c'), (x'3eb0c6f7a0b5ed8d'), (x'3eb0c6f7a0b5ed8e'), + (x'3ee4f8b588e368ef'), (x'3ee4f8b588e368f0'), (x'3ee4f8b588e368f1'), + (x'3f1a36e2eb1c432c'), (x'3f1a36e2eb1c432d'), (x'3f1a36e2eb1c432e'), + (x'3f50624dd2f1a9fb'), (x'3f50624dd2f1a9fc'), (x'3f50624dd2f1a9fd'), + (x'3f847ae147ae147a'), (x'3f847ae147ae147b'), (x'3f847ae147ae147c'), + (x'3fb9999999999999'), (x'3fb999999999999a'), (x'3fb999999999999b'), + -- values very close to 1 + (x'3feffffffffffff0'), (x'3feffffffffffff1'), (x'3feffffffffffff2'), + (x'3feffffffffffff3'), (x'3feffffffffffff4'), (x'3feffffffffffff5'), + (x'3feffffffffffff6'), (x'3feffffffffffff7'), (x'3feffffffffffff8'), + (x'3feffffffffffff9'), (x'3feffffffffffffa'), (x'3feffffffffffffb'), + (x'3feffffffffffffc'), (x'3feffffffffffffd'), (x'3feffffffffffffe'), + (x'3fefffffffffffff'), + (x'3ff0000000000000'), + (x'3ff0000000000001'), (x'3ff0000000000002'), (x'3ff0000000000003'), + (x'3ff0000000000004'), (x'3ff0000000000005'), (x'3ff0000000000006'), + (x'3ff0000000000007'), (x'3ff0000000000008'), (x'3ff0000000000009'), + -- + (x'3ff921fb54442d18'), + (x'4005bf0a8b14576a'), + (x'400921fb54442d18'), + -- + (x'4023ffffffffffff'), (x'4024000000000000'), (x'4024000000000001'), + (x'4058ffffffffffff'), (x'4059000000000000'), (x'4059000000000001'), + (x'408f3fffffffffff'), (x'408f400000000000'), (x'408f400000000001'), + (x'40c387ffffffffff'), (x'40c3880000000000'), (x'40c3880000000001'), + (x'40f869ffffffffff'), (x'40f86a0000000000'), (x'40f86a0000000001'), + (x'412e847fffffffff'), (x'412e848000000000'), (x'412e848000000001'), + (x'416312cfffffffff'), (x'416312d000000000'), (x'416312d000000001'), + (x'4197d783ffffffff'), (x'4197d78400000000'), (x'4197d78400000001'), + (x'41cdcd64ffffffff'), (x'41cdcd6500000000'), (x'41cdcd6500000001'), + (x'4202a05f1fffffff'), (x'4202a05f20000000'), (x'4202a05f20000001'), + (x'42374876e7ffffff'), (x'42374876e8000000'), (x'42374876e8000001'), + (x'426d1a94a1ffffff'), (x'426d1a94a2000000'), (x'426d1a94a2000001'), + (x'42a2309ce53fffff'), (x'42a2309ce5400000'), (x'42a2309ce5400001'), + (x'42d6bcc41e8fffff'), (x'42d6bcc41e900000'), (x'42d6bcc41e900001'), + (x'430c6bf52633ffff'), (x'430c6bf526340000'), (x'430c6bf526340001'), + (x'4341c37937e07fff'), (x'4341c37937e08000'), (x'4341c37937e08001'), + (x'4376345785d89fff'), (x'4376345785d8a000'), (x'4376345785d8a001'), + (x'43abc16d674ec7ff'), (x'43abc16d674ec800'), (x'43abc16d674ec801'), + (x'43e158e460913cff'), (x'43e158e460913d00'), (x'43e158e460913d01'), + (x'4415af1d78b58c3f'), (x'4415af1d78b58c40'), (x'4415af1d78b58c41'), + (x'444b1ae4d6e2ef4f'), (x'444b1ae4d6e2ef50'), (x'444b1ae4d6e2ef51'), + (x'4480f0cf064dd591'), (x'4480f0cf064dd592'), (x'4480f0cf064dd593'), + (x'44b52d02c7e14af5'), (x'44b52d02c7e14af6'), (x'44b52d02c7e14af7'), + (x'44ea784379d99db3'), (x'44ea784379d99db4'), (x'44ea784379d99db5'), + (x'45208b2a2c280290'), (x'45208b2a2c280291'), (x'45208b2a2c280292'), + -- + (x'7feffffffffffffe'), (x'7fefffffffffffff'), + -- round to even tests (+ve) + (x'4350000000000002'), + (x'4350000000002e06'), + (x'4352000000000003'), + (x'4352000000000004'), + (x'4358000000000003'), + (x'4358000000000004'), + (x'435f000000000020'), + -- round to even tests (-ve) + (x'c350000000000002'), + (x'c350000000002e06'), + (x'c352000000000003'), + (x'c352000000000004'), + (x'c358000000000003'), + (x'c358000000000004'), + (x'c35f000000000020'), + -- exercise fixed-point memmoves + (x'42dc12218377de66'), + (x'42a674e79c5fe51f'), + (x'4271f71fb04cb74c'), + (x'423cbe991a145879'), + (x'4206fee0e1a9e061'), + (x'41d26580b487e6b4'), + (x'419d6f34540ca453'), + (x'41678c29dcd6e9dc'), + (x'4132d687e3df217d'), + (x'40fe240c9fcb68c8'), + (x'40c81cd6e63c53d3'), + (x'40934a4584fd0fdc'), + (x'405edd3c07fb4c93'), + (x'4028b0fcd32f7076'), + (x'3ff3c0ca428c59f8'), + -- these cases come from the upstream's testsuite + -- LotsOfTrailingZeros) + (x'3e60000000000000'), + -- Regression + (x'c352bd2668e077c4'), + (x'434018601510c000'), + (x'43d055dc36f24000'), + (x'43e052961c6f8000'), + (x'3ff3c0ca2a5b1d5d'), + -- LooksLikePow5 + (x'4830f0cf064dd592'), + (x'4840f0cf064dd592'), + (x'4850f0cf064dd592'), + -- OutputLength + (x'3ff3333333333333'), + (x'3ff3ae147ae147ae'), + (x'3ff3be76c8b43958'), + (x'3ff3c083126e978d'), + (x'3ff3c0c1fc8f3238'), + (x'3ff3c0c9539b8887'), + (x'3ff3c0ca2a5b1d5d'), + (x'3ff3c0ca4283de1b'), + (x'3ff3c0ca43db770a'), + (x'3ff3c0ca428abd53'), + (x'3ff3c0ca428c1d2b'), + (x'3ff3c0ca428c51f2'), + (x'3ff3c0ca428c58fc'), + (x'3ff3c0ca428c59dd'), + (x'3ff3c0ca428c59f8'), + (x'3ff3c0ca428c59fb'), + -- 32-bit chunking + (x'40112e0be8047a7d'), + (x'40112e0be815a889'), + (x'40112e0be826d695'), + (x'40112e0be83804a1'), + (x'40112e0be84932ad'), + -- MinMaxShift + (x'0040000000000000'), + (x'007fffffffffffff'), + (x'0290000000000000'), + (x'029fffffffffffff'), + (x'4350000000000000'), + (x'435fffffffffffff'), + (x'1330000000000000'), + (x'133fffffffffffff'), + (x'3a6fa7161a4d6e0c') +) +select float8send(flt) as ibits, + flt, + flt::text::float8 as r_flt, + float8send(flt::text::float8) as obits, + float8send(flt::text::float8) = float8send(flt) as correct + from (select bits::bigint::xfloat8::float8 as flt + from testdata + offset 0) s; + +-- clean up, lest opr_sanity complain +\set VERBOSITY terse \\ -- YB: suppress cascade details +drop type xfloat8 cascade; +\set VERBOSITY default \\ -- YB diff --git a/src/test/regress/sql/yb.port.foreign_data.sql b/src/test/regress/sql/yb.port.foreign_data.sql new file mode 100644 index 0000000000000..654e50a8428b8 --- /dev/null +++ b/src/test/regress/sql/yb.port.foreign_data.sql @@ -0,0 +1,870 @@ +-- +-- Test foreign-data wrapper and server management. +-- + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +CREATE FUNCTION test_fdw_handler() + RETURNS fdw_handler + AS :'regresslib', 'test_fdw_handler' + LANGUAGE C; + +-- Clean up in case a prior regression run failed + +-- Suppress NOTICE messages when roles don't exist +SET client_min_messages TO 'warning'; + +DROP ROLE IF EXISTS regress_foreign_data_user, regress_test_role, regress_test_role2, regress_test_role_super, regress_test_indirect, regress_unprivileged_role; + +RESET client_min_messages; + +CREATE ROLE regress_foreign_data_user LOGIN SUPERUSER; +SET SESSION AUTHORIZATION 'regress_foreign_data_user'; + +CREATE ROLE regress_test_role; +CREATE ROLE regress_test_role2; +CREATE ROLE regress_test_role_super SUPERUSER; +CREATE ROLE regress_test_indirect; +CREATE ROLE regress_unprivileged_role; + +CREATE FOREIGN DATA WRAPPER dummy; +COMMENT ON FOREIGN DATA WRAPPER dummy IS 'useless'; +CREATE FOREIGN DATA WRAPPER postgresql VALIDATOR postgresql_fdw_validator; + +-- At this point we should have 2 built-in wrappers and no servers. +SELECT fdwname, fdwhandler::regproc, fdwvalidator::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3; +SELECT srvname, srvoptions FROM pg_foreign_server; +SELECT * FROM pg_user_mapping; + +-- CREATE FOREIGN DATA WRAPPER +CREATE FOREIGN DATA WRAPPER foo VALIDATOR bar; -- ERROR +CREATE FOREIGN DATA WRAPPER foo; +\dew + +CREATE FOREIGN DATA WRAPPER foo; -- duplicate +DROP FOREIGN DATA WRAPPER foo; +CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1'); +\dew+ + +DROP FOREIGN DATA WRAPPER foo; +CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1', testing '2'); -- ERROR +CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1', another '2'); +\dew+ + +DROP FOREIGN DATA WRAPPER foo; +SET ROLE regress_test_role; +CREATE FOREIGN DATA WRAPPER foo; -- ERROR +RESET ROLE; +CREATE FOREIGN DATA WRAPPER foo VALIDATOR postgresql_fdw_validator; +\dew+ + +-- HANDLER related checks +CREATE FUNCTION invalid_fdw_handler() RETURNS int LANGUAGE SQL AS 'SELECT 1;'; +CREATE FOREIGN DATA WRAPPER test_fdw HANDLER invalid_fdw_handler; -- ERROR +CREATE FOREIGN DATA WRAPPER test_fdw HANDLER test_fdw_handler HANDLER invalid_fdw_handler; -- ERROR +CREATE FOREIGN DATA WRAPPER test_fdw HANDLER test_fdw_handler; +DROP FOREIGN DATA WRAPPER test_fdw; + +-- ALTER FOREIGN DATA WRAPPER +ALTER FOREIGN DATA WRAPPER foo OPTIONS (nonexistent 'fdw'); -- ERROR + +ALTER FOREIGN DATA WRAPPER foo; -- ERROR +ALTER FOREIGN DATA WRAPPER foo VALIDATOR bar; -- ERROR +ALTER FOREIGN DATA WRAPPER foo NO VALIDATOR; +\dew+ + +ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '1', b '2'); +ALTER FOREIGN DATA WRAPPER foo OPTIONS (SET c '4'); -- ERROR +ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP c); -- ERROR +ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD x '1', DROP x); +\dew+ + +ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP a, SET b '3', ADD c '4'); +\dew+ + +ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '2'); +ALTER FOREIGN DATA WRAPPER foo OPTIONS (b '4'); -- ERROR +\dew+ + +SET ROLE regress_test_role; +ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD d '5'); -- ERROR +SET ROLE regress_test_role_super; +ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD d '5'); +\dew+ + +ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_test_role; -- ERROR +ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_test_role_super; +ALTER ROLE regress_test_role_super NOSUPERUSER; +SET ROLE regress_test_role_super; +ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD e '6'); -- ERROR +RESET ROLE; +\dew+ + +ALTER FOREIGN DATA WRAPPER foo RENAME TO foo1; +\dew+ +ALTER FOREIGN DATA WRAPPER foo1 RENAME TO foo; + +-- HANDLER related checks +ALTER FOREIGN DATA WRAPPER foo HANDLER invalid_fdw_handler; -- ERROR +ALTER FOREIGN DATA WRAPPER foo HANDLER test_fdw_handler HANDLER anything; -- ERROR +ALTER FOREIGN DATA WRAPPER foo HANDLER test_fdw_handler; +DROP FUNCTION invalid_fdw_handler(); + +-- DROP FOREIGN DATA WRAPPER +DROP FOREIGN DATA WRAPPER nonexistent; -- ERROR +DROP FOREIGN DATA WRAPPER IF EXISTS nonexistent; +\dew+ + +DROP ROLE regress_test_role_super; -- ERROR +SET ROLE regress_test_role_super; +DROP FOREIGN DATA WRAPPER foo; +RESET ROLE; +DROP ROLE regress_test_role_super; +\dew+ + +CREATE FOREIGN DATA WRAPPER foo; +CREATE SERVER s1 FOREIGN DATA WRAPPER foo; +COMMENT ON SERVER s1 IS 'foreign server'; +CREATE USER MAPPING FOR current_user SERVER s1; +CREATE USER MAPPING FOR current_user SERVER s1; -- ERROR +CREATE USER MAPPING IF NOT EXISTS FOR current_user SERVER s1; -- NOTICE +\dew+ +\des+ +\deu+ +DROP FOREIGN DATA WRAPPER foo; -- ERROR +SET ROLE regress_test_role; +DROP FOREIGN DATA WRAPPER foo CASCADE; -- ERROR +RESET ROLE; +DROP FOREIGN DATA WRAPPER foo CASCADE; +\dew+ +\des+ +\deu+ + +-- exercise CREATE SERVER +CREATE SERVER s1 FOREIGN DATA WRAPPER foo; -- ERROR +CREATE FOREIGN DATA WRAPPER foo OPTIONS ("test wrapper" 'true'); +CREATE SERVER s1 FOREIGN DATA WRAPPER foo; +CREATE SERVER s1 FOREIGN DATA WRAPPER foo; -- ERROR +CREATE SERVER IF NOT EXISTS s1 FOREIGN DATA WRAPPER foo; -- No ERROR, just NOTICE +CREATE SERVER s2 FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b'); +CREATE SERVER s3 TYPE 'oracle' FOREIGN DATA WRAPPER foo; +CREATE SERVER s4 TYPE 'oracle' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b'); +CREATE SERVER s5 VERSION '15.0' FOREIGN DATA WRAPPER foo; +CREATE SERVER s6 VERSION '16.0' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b'); +CREATE SERVER s7 TYPE 'oracle' VERSION '17.0' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b'); +CREATE SERVER s8 FOREIGN DATA WRAPPER postgresql OPTIONS (foo '1'); -- ERROR +CREATE SERVER s8 FOREIGN DATA WRAPPER postgresql OPTIONS (host 'localhost', dbname 's8db'); +\des+ +SET ROLE regress_test_role; +CREATE SERVER t1 FOREIGN DATA WRAPPER foo; -- ERROR: no usage on FDW +RESET ROLE; +GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role; +SET ROLE regress_test_role; +CREATE SERVER t1 FOREIGN DATA WRAPPER foo; +RESET ROLE; +\des+ + +REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM regress_test_role; +GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_indirect; +SET ROLE regress_test_role; +CREATE SERVER t2 FOREIGN DATA WRAPPER foo; -- ERROR +RESET ROLE; +GRANT regress_test_indirect TO regress_test_role; +SET ROLE regress_test_role; +CREATE SERVER t2 FOREIGN DATA WRAPPER foo; +\des+ +RESET ROLE; +REVOKE regress_test_indirect FROM regress_test_role; + +-- ALTER SERVER +ALTER SERVER s0; -- ERROR +ALTER SERVER s0 OPTIONS (a '1'); -- ERROR +ALTER SERVER s1 VERSION '1.0' OPTIONS (servername 's1'); +ALTER SERVER s2 VERSION '1.1'; +ALTER SERVER s3 OPTIONS ("tns name" 'orcl', port '1521'); +GRANT USAGE ON FOREIGN SERVER s1 TO regress_test_role; +GRANT USAGE ON FOREIGN SERVER s6 TO regress_test_role2 WITH GRANT OPTION; +\des+ +SET ROLE regress_test_role; +ALTER SERVER s1 VERSION '1.1'; -- ERROR +ALTER SERVER s1 OWNER TO regress_test_role; -- ERROR +RESET ROLE; +ALTER SERVER s1 OWNER TO regress_test_role; +GRANT regress_test_role2 TO regress_test_role; +SET ROLE regress_test_role; +ALTER SERVER s1 VERSION '1.1'; +ALTER SERVER s1 OWNER TO regress_test_role2; -- ERROR +RESET ROLE; +ALTER SERVER s8 OPTIONS (foo '1'); -- ERROR option validation +ALTER SERVER s8 OPTIONS (connect_timeout '30', SET dbname 'db1', DROP host); +SET ROLE regress_test_role; +ALTER SERVER s1 OWNER TO regress_test_indirect; -- ERROR +RESET ROLE; +GRANT regress_test_indirect TO regress_test_role; +SET ROLE regress_test_role; +ALTER SERVER s1 OWNER TO regress_test_indirect; +RESET ROLE; +GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_indirect; +SET ROLE regress_test_role; +ALTER SERVER s1 OWNER TO regress_test_indirect; +RESET ROLE; +DROP ROLE regress_test_indirect; -- ERROR +\des+ + +ALTER SERVER s8 RENAME to s8new; +\des+ +ALTER SERVER s8new RENAME to s8; + +-- DROP SERVER +DROP SERVER nonexistent; -- ERROR +DROP SERVER IF EXISTS nonexistent; +\des +SET ROLE regress_test_role; +DROP SERVER s2; -- ERROR +DROP SERVER s1; +RESET ROLE; +\des +ALTER SERVER s2 OWNER TO regress_test_role; +SET ROLE regress_test_role; +DROP SERVER s2; +RESET ROLE; +\des +CREATE USER MAPPING FOR current_user SERVER s3; +\deu +DROP SERVER s3; -- ERROR +DROP SERVER s3 CASCADE; +\des +\deu + +-- CREATE USER MAPPING +CREATE USER MAPPING FOR regress_test_missing_role SERVER s1; -- ERROR +CREATE USER MAPPING FOR current_user SERVER s1; -- ERROR +CREATE USER MAPPING FOR current_user SERVER s4; +CREATE USER MAPPING FOR user SERVER s4; -- ERROR duplicate +CREATE USER MAPPING FOR public SERVER s4 OPTIONS ("this mapping" 'is public'); +CREATE USER MAPPING FOR user SERVER s8 OPTIONS (username 'test', password 'secret'); -- ERROR +CREATE USER MAPPING FOR user SERVER s8 OPTIONS (user 'test', password 'secret'); +ALTER SERVER s5 OWNER TO regress_test_role; +ALTER SERVER s6 OWNER TO regress_test_indirect; +SET ROLE regress_test_role; +CREATE USER MAPPING FOR current_user SERVER s5; +CREATE USER MAPPING FOR current_user SERVER s6 OPTIONS (username 'test'); +CREATE USER MAPPING FOR current_user SERVER s7; -- ERROR +CREATE USER MAPPING FOR public SERVER s8; -- ERROR +RESET ROLE; + +ALTER SERVER t1 OWNER TO regress_test_indirect; +SET ROLE regress_test_role; +CREATE USER MAPPING FOR current_user SERVER t1 OPTIONS (username 'bob', password 'boo'); +CREATE USER MAPPING FOR public SERVER t1; +RESET ROLE; +\deu + +-- ALTER USER MAPPING +ALTER USER MAPPING FOR regress_test_missing_role SERVER s4 OPTIONS (gotcha 'true'); -- ERROR +ALTER USER MAPPING FOR user SERVER ss4 OPTIONS (gotcha 'true'); -- ERROR +ALTER USER MAPPING FOR public SERVER s5 OPTIONS (gotcha 'true'); -- ERROR +ALTER USER MAPPING FOR current_user SERVER s8 OPTIONS (username 'test'); -- ERROR +ALTER USER MAPPING FOR current_user SERVER s8 OPTIONS (DROP user, SET password 'public'); +SET ROLE regress_test_role; +ALTER USER MAPPING FOR current_user SERVER s5 OPTIONS (ADD modified '1'); +ALTER USER MAPPING FOR public SERVER s4 OPTIONS (ADD modified '1'); -- ERROR +ALTER USER MAPPING FOR public SERVER t1 OPTIONS (ADD modified '1'); +RESET ROLE; +\deu+ + +-- DROP USER MAPPING +DROP USER MAPPING FOR regress_test_missing_role SERVER s4; -- ERROR +DROP USER MAPPING FOR user SERVER ss4; +DROP USER MAPPING FOR public SERVER s7; -- ERROR +DROP USER MAPPING IF EXISTS FOR regress_test_missing_role SERVER s4; +DROP USER MAPPING IF EXISTS FOR user SERVER ss4; +DROP USER MAPPING IF EXISTS FOR public SERVER s7; +CREATE USER MAPPING FOR public SERVER s8; +SET ROLE regress_test_role; +DROP USER MAPPING FOR public SERVER s8; -- ERROR +RESET ROLE; +DROP SERVER s7; +\deu + +-- CREATE FOREIGN TABLE +CREATE SCHEMA foreign_schema; +CREATE SERVER s0 FOREIGN DATA WRAPPER dummy; +CREATE FOREIGN TABLE ft1 (); -- ERROR +CREATE FOREIGN TABLE ft1 () SERVER no_server; -- ERROR +CREATE FOREIGN TABLE ft1 ( + c1 integer OPTIONS ("param 1" 'val1') PRIMARY KEY, + c2 text OPTIONS (param2 'val2', param3 'val3'), + c3 date +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR +CREATE TABLE ref_table (id integer PRIMARY KEY); +CREATE FOREIGN TABLE ft1 ( + c1 integer OPTIONS ("param 1" 'val1') REFERENCES ref_table (id), + c2 text OPTIONS (param2 'val2', param3 'val3'), + c3 date +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR +DROP TABLE ref_table; +CREATE FOREIGN TABLE ft1 ( + c1 integer OPTIONS ("param 1" 'val1') NOT NULL, + c2 text OPTIONS (param2 'val2', param3 'val3'), + c3 date, + UNIQUE (c3) +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR +CREATE FOREIGN TABLE ft1 ( + c1 integer OPTIONS ("param 1" 'val1') NOT NULL, + c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> ''), + c3 date, + CHECK (c3 BETWEEN '1994-01-01'::date AND '1994-01-31'::date) +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); +COMMENT ON FOREIGN TABLE ft1 IS 'ft1'; +COMMENT ON COLUMN ft1.c1 IS 'ft1.c1'; +\d+ ft1 +\det+ +CREATE INDEX id_ft1_c2 ON ft1 (c2); -- ERROR +SELECT * FROM ft1; -- ERROR +EXPLAIN SELECT * FROM ft1; -- ERROR + +CREATE TABLE lt1 (a INT) PARTITION BY RANGE (a); +CREATE FOREIGN TABLE ft_part1 + PARTITION OF lt1 FOR VALUES FROM (0) TO (1000) SERVER s0; +CREATE INDEX ON lt1 (a); -- skips partition +CREATE UNIQUE INDEX ON lt1 (a); -- ERROR +ALTER TABLE lt1 ADD PRIMARY KEY (a); -- ERROR +DROP TABLE lt1; + +CREATE TABLE lt1 (a INT) PARTITION BY RANGE (a); +CREATE INDEX ON lt1 (a); +CREATE FOREIGN TABLE ft_part1 + PARTITION OF lt1 FOR VALUES FROM (0) TO (1000) SERVER s0; +CREATE FOREIGN TABLE ft_part2 (a INT) SERVER s0; +ALTER TABLE lt1 ATTACH PARTITION ft_part2 FOR VALUES FROM (1000) TO (2000); +DROP FOREIGN TABLE ft_part1, ft_part2; +DROP FOREIGN TABLE ft_part1; DROP FOREIGN TABLE ft_part2; -- YB workaround for above +CREATE UNIQUE INDEX ON lt1 (a); +ALTER TABLE lt1 ADD PRIMARY KEY (a); +CREATE FOREIGN TABLE ft_part1 + PARTITION OF lt1 FOR VALUES FROM (0) TO (1000) SERVER s0; -- ERROR +CREATE FOREIGN TABLE ft_part2 (a INT NOT NULL) SERVER s0; +ALTER TABLE lt1 ATTACH PARTITION ft_part2 + FOR VALUES FROM (1000) TO (2000); -- ERROR +DROP TABLE lt1; +DROP FOREIGN TABLE ft_part2; + +CREATE TABLE lt1 (a INT) PARTITION BY RANGE (a); +CREATE INDEX ON lt1 (a); +CREATE TABLE lt1_part1 + PARTITION OF lt1 FOR VALUES FROM (0) TO (1000) + PARTITION BY RANGE (a); +CREATE FOREIGN TABLE ft_part_1_1 + PARTITION OF lt1_part1 FOR VALUES FROM (0) TO (100) SERVER s0; +CREATE FOREIGN TABLE ft_part_1_2 (a INT) SERVER s0; +ALTER TABLE lt1_part1 ATTACH PARTITION ft_part_1_2 FOR VALUES FROM (100) TO (200); +CREATE UNIQUE INDEX ON lt1 (a); +ALTER TABLE lt1 ADD PRIMARY KEY (a); +DROP FOREIGN TABLE ft_part_1_1, ft_part_1_2; +DROP FOREIGN TABLE ft_part_1_1; DROP FOREIGN TABLE ft_part_1_2; -- YB workaround for above +CREATE UNIQUE INDEX ON lt1 (a); +ALTER TABLE lt1 ADD PRIMARY KEY (a); +CREATE FOREIGN TABLE ft_part_1_1 + PARTITION OF lt1_part1 FOR VALUES FROM (0) TO (100) SERVER s0; +CREATE FOREIGN TABLE ft_part_1_2 (a INT NOT NULL) SERVER s0; +ALTER TABLE lt1_part1 ATTACH PARTITION ft_part_1_2 FOR VALUES FROM (100) TO (200); +DROP TABLE lt1; +DROP FOREIGN TABLE ft_part_1_2; + +-- ALTER FOREIGN TABLE +COMMENT ON FOREIGN TABLE ft1 IS 'foreign table'; +COMMENT ON FOREIGN TABLE ft1 IS NULL; +COMMENT ON COLUMN ft1.c1 IS 'foreign column'; +COMMENT ON COLUMN ft1.c1 IS NULL; + +ALTER FOREIGN TABLE ft1 ADD COLUMN c4 integer; +ALTER FOREIGN TABLE ft1 ADD COLUMN c5 integer DEFAULT 0; +ALTER FOREIGN TABLE ft1 ADD COLUMN c6 integer; +ALTER FOREIGN TABLE ft1 ADD COLUMN c7 integer NOT NULL; +ALTER FOREIGN TABLE ft1 ADD COLUMN c8 integer; +ALTER FOREIGN TABLE ft1 ADD COLUMN c9 integer; +ALTER FOREIGN TABLE ft1 ADD COLUMN c10 integer OPTIONS (p1 'v1'); + +ALTER FOREIGN TABLE ft1 ALTER COLUMN c4 SET DEFAULT 0; +ALTER FOREIGN TABLE ft1 ALTER COLUMN c5 DROP DEFAULT; +ALTER FOREIGN TABLE ft1 ALTER COLUMN c6 SET NOT NULL; +ALTER FOREIGN TABLE ft1 ALTER COLUMN c7 DROP NOT NULL; +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE text; +ALTER FOREIGN TABLE ft1 ALTER COLUMN xmin OPTIONS (ADD p1 'v1'); -- ERROR +ALTER FOREIGN TABLE ft1 ALTER COLUMN c7 OPTIONS (ADD p1 'v1', ADD p2 'v2'), + ALTER COLUMN c8 OPTIONS (ADD p1 'v1', ADD p2 'v2'); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (SET p2 'V2', DROP p1); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET STATISTICS 10000; +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET (n_distinct = 100); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1; +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STORAGE PLAIN; +\d+ ft1 +-- can't change the column type if it's used elsewhere +CREATE TABLE use_ft1_column_type (x ft1); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR +DROP TABLE use_ft1_column_type; +ALTER FOREIGN TABLE ft1 ADD PRIMARY KEY (c7); -- ERROR +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID; +ALTER FOREIGN TABLE ft1 ALTER CONSTRAINT ft1_c9_check DEFERRABLE; -- ERROR +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const; +ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role; +ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@'); +ALTER FOREIGN TABLE ft1 DROP COLUMN no_column; -- ERROR +ALTER FOREIGN TABLE ft1 DROP COLUMN IF EXISTS no_column; +ALTER FOREIGN TABLE ft1 DROP COLUMN c9; +ALTER FOREIGN TABLE ft1 SET SCHEMA foreign_schema; +ALTER FOREIGN TABLE ft1 SET TABLESPACE ts; -- ERROR +ALTER FOREIGN TABLE foreign_schema.ft1 RENAME c1 TO foreign_column_1; +ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1; +\d foreign_schema.foreign_table_1 \\ -- YB: output differs because of failed ALTER above + +-- alter noexisting table +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c4 integer; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c6 integer; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c7 integer NOT NULL; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c8 integer; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c9 integer; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c10 integer OPTIONS (p1 'v1'); + +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c6 SET NOT NULL; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c7 DROP NOT NULL; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c8 TYPE char(10); +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c8 SET DATA TYPE text; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c7 OPTIONS (ADD p1 'v1', ADD p2 'v2'), + ALTER COLUMN c8 OPTIONS (ADD p1 'v1', ADD p2 'v2'); +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c8 OPTIONS (SET p2 'V2', DROP p1); + +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP CONSTRAINT IF EXISTS no_const; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP CONSTRAINT ft1_c1_check; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 OWNER TO regress_test_role; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@'); +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP COLUMN IF EXISTS no_column; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP COLUMN c9; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 SET SCHEMA foreign_schema; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 RENAME c1 TO foreign_column_1; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 RENAME TO foreign_table_1; + +-- Information schema + +SELECT * FROM information_schema.foreign_data_wrappers ORDER BY 1, 2; +SELECT * FROM information_schema.foreign_data_wrapper_options ORDER BY 1, 2, 3; +SELECT * FROM information_schema.foreign_servers ORDER BY 1, 2; +SELECT * FROM information_schema.foreign_server_options ORDER BY 1, 2, 3; +SELECT * FROM information_schema.user_mappings ORDER BY lower(authorization_identifier), 2, 3; +SELECT * FROM information_schema.user_mapping_options ORDER BY lower(authorization_identifier), 2, 3, 4; +SELECT * FROM information_schema.usage_privileges WHERE object_type LIKE 'FOREIGN%' AND object_name IN ('s6', 'foo') ORDER BY 1, 2, 3, 4, 5; +SELECT * FROM information_schema.role_usage_grants WHERE object_type LIKE 'FOREIGN%' AND object_name IN ('s6', 'foo') ORDER BY 1, 2, 3, 4, 5; +SELECT * FROM information_schema.foreign_tables ORDER BY 1, 2, 3; +SELECT * FROM information_schema.foreign_table_options ORDER BY 1, 2, 3, 4; +SET ROLE regress_test_role; +SELECT * FROM information_schema.user_mapping_options ORDER BY 1, 2, 3, 4; +SELECT * FROM information_schema.usage_privileges WHERE object_type LIKE 'FOREIGN%' AND object_name IN ('s6', 'foo') ORDER BY 1, 2, 3, 4, 5; +SELECT * FROM information_schema.role_usage_grants WHERE object_type LIKE 'FOREIGN%' AND object_name IN ('s6', 'foo') ORDER BY 1, 2, 3, 4, 5; +DROP USER MAPPING FOR current_user SERVER t1; +SET ROLE regress_test_role2; +SELECT * FROM information_schema.user_mapping_options ORDER BY 1, 2, 3, 4; +RESET ROLE; + + +-- has_foreign_data_wrapper_privilege +SELECT has_foreign_data_wrapper_privilege('regress_test_role', + (SELECT oid FROM pg_foreign_data_wrapper WHERE fdwname='foo'), 'USAGE'); +SELECT has_foreign_data_wrapper_privilege('regress_test_role', 'foo', 'USAGE'); +SELECT has_foreign_data_wrapper_privilege( + (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'), + (SELECT oid FROM pg_foreign_data_wrapper WHERE fdwname='foo'), 'USAGE'); +SELECT has_foreign_data_wrapper_privilege( + (SELECT oid FROM pg_foreign_data_wrapper WHERE fdwname='foo'), 'USAGE'); +SELECT has_foreign_data_wrapper_privilege( + (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'), 'foo', 'USAGE'); +SELECT has_foreign_data_wrapper_privilege('foo', 'USAGE'); +GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role; +SELECT has_foreign_data_wrapper_privilege('regress_test_role', 'foo', 'USAGE'); + +-- has_server_privilege +SELECT has_server_privilege('regress_test_role', + (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE'); +SELECT has_server_privilege('regress_test_role', 's8', 'USAGE'); +SELECT has_server_privilege( + (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'), + (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE'); +SELECT has_server_privilege( + (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE'); +SELECT has_server_privilege( + (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'), 's8', 'USAGE'); +SELECT has_server_privilege('s8', 'USAGE'); +GRANT USAGE ON FOREIGN SERVER s8 TO regress_test_role; +SELECT has_server_privilege('regress_test_role', 's8', 'USAGE'); +REVOKE USAGE ON FOREIGN SERVER s8 FROM regress_test_role; + +GRANT USAGE ON FOREIGN SERVER s4 TO regress_test_role; +DROP USER MAPPING FOR public SERVER s4; +ALTER SERVER s6 OPTIONS (DROP host, DROP dbname); +ALTER USER MAPPING FOR regress_test_role SERVER s6 OPTIONS (DROP username); +ALTER FOREIGN DATA WRAPPER foo VALIDATOR postgresql_fdw_validator; + +-- Privileges +SET ROLE regress_unprivileged_role; +CREATE FOREIGN DATA WRAPPER foobar; -- ERROR +ALTER FOREIGN DATA WRAPPER foo OPTIONS (gotcha 'true'); -- ERROR +ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_unprivileged_role; -- ERROR +DROP FOREIGN DATA WRAPPER foo; -- ERROR +GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role; -- ERROR +CREATE SERVER s9 FOREIGN DATA WRAPPER foo; -- ERROR +ALTER SERVER s4 VERSION '0.5'; -- ERROR +ALTER SERVER s4 OWNER TO regress_unprivileged_role; -- ERROR +DROP SERVER s4; -- ERROR +GRANT USAGE ON FOREIGN SERVER s4 TO regress_test_role; -- ERROR +CREATE USER MAPPING FOR public SERVER s4; -- ERROR +ALTER USER MAPPING FOR regress_test_role SERVER s6 OPTIONS (gotcha 'true'); -- ERROR +DROP USER MAPPING FOR regress_test_role SERVER s6; -- ERROR +RESET ROLE; + +GRANT USAGE ON FOREIGN DATA WRAPPER postgresql TO regress_unprivileged_role; +GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_unprivileged_role WITH GRANT OPTION; +SET ROLE regress_unprivileged_role; +CREATE FOREIGN DATA WRAPPER foobar; -- ERROR +ALTER FOREIGN DATA WRAPPER foo OPTIONS (gotcha 'true'); -- ERROR +DROP FOREIGN DATA WRAPPER foo; -- ERROR +GRANT USAGE ON FOREIGN DATA WRAPPER postgresql TO regress_test_role; -- WARNING +GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role; +CREATE SERVER s9 FOREIGN DATA WRAPPER postgresql; +ALTER SERVER s6 VERSION '0.5'; -- ERROR +DROP SERVER s6; -- ERROR +GRANT USAGE ON FOREIGN SERVER s6 TO regress_test_role; -- ERROR +GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role; +CREATE USER MAPPING FOR public SERVER s6; -- ERROR +CREATE USER MAPPING FOR public SERVER s9; +ALTER USER MAPPING FOR regress_test_role SERVER s6 OPTIONS (gotcha 'true'); -- ERROR +DROP USER MAPPING FOR regress_test_role SERVER s6; -- ERROR +RESET ROLE; + +REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM regress_unprivileged_role; -- ERROR +REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM regress_unprivileged_role CASCADE; +SET ROLE regress_unprivileged_role; +GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role; -- ERROR +CREATE SERVER s10 FOREIGN DATA WRAPPER foo; -- ERROR +ALTER SERVER s9 VERSION '1.1'; +GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role; +CREATE USER MAPPING FOR current_user SERVER s9; +DROP SERVER s9 CASCADE; +RESET ROLE; +CREATE SERVER s9 FOREIGN DATA WRAPPER foo; +GRANT USAGE ON FOREIGN SERVER s9 TO regress_unprivileged_role; +SET ROLE regress_unprivileged_role; +ALTER SERVER s9 VERSION '1.2'; -- ERROR +GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role; -- WARNING +CREATE USER MAPPING FOR current_user SERVER s9; +DROP SERVER s9 CASCADE; -- ERROR + +-- Check visibility of user mapping data +SET ROLE regress_test_role; +CREATE SERVER s10 FOREIGN DATA WRAPPER foo; +CREATE USER MAPPING FOR public SERVER s10 OPTIONS (user 'secret'); +CREATE USER MAPPING FOR regress_unprivileged_role SERVER s10 OPTIONS (user 'secret'); +-- owner of server can see some option fields +\deu+ +RESET ROLE; +-- superuser can see all option fields +\deu+ +-- unprivileged user cannot see any option field +SET ROLE regress_unprivileged_role; +\deu+ +RESET ROLE; +DROP SERVER s10 CASCADE; + +-- Triggers +CREATE FUNCTION dummy_trigger() RETURNS TRIGGER AS $$ + BEGIN + RETURN NULL; + END +$$ language plpgsql; + +CREATE TRIGGER trigtest_before_stmt BEFORE INSERT OR UPDATE OR DELETE +ON foreign_schema.foreign_table_1 +FOR EACH STATEMENT +EXECUTE PROCEDURE dummy_trigger(); + +CREATE TRIGGER trigtest_after_stmt AFTER INSERT OR UPDATE OR DELETE +ON foreign_schema.foreign_table_1 +FOR EACH STATEMENT +EXECUTE PROCEDURE dummy_trigger(); + +CREATE TRIGGER trigtest_after_stmt_tt AFTER INSERT OR UPDATE OR DELETE -- ERROR +ON foreign_schema.foreign_table_1 +REFERENCING NEW TABLE AS new_table +FOR EACH STATEMENT +EXECUTE PROCEDURE dummy_trigger(); + +CREATE TRIGGER trigtest_before_row BEFORE INSERT OR UPDATE OR DELETE +ON foreign_schema.foreign_table_1 +FOR EACH ROW +EXECUTE PROCEDURE dummy_trigger(); + +CREATE TRIGGER trigtest_after_row AFTER INSERT OR UPDATE OR DELETE +ON foreign_schema.foreign_table_1 +FOR EACH ROW +EXECUTE PROCEDURE dummy_trigger(); + +CREATE CONSTRAINT TRIGGER trigtest_constraint AFTER INSERT OR UPDATE OR DELETE +ON foreign_schema.foreign_table_1 +FOR EACH ROW +EXECUTE PROCEDURE dummy_trigger(); + +ALTER FOREIGN TABLE foreign_schema.foreign_table_1 + DISABLE TRIGGER trigtest_before_stmt; +ALTER FOREIGN TABLE foreign_schema.foreign_table_1 + ENABLE TRIGGER trigtest_before_stmt; + +DROP TRIGGER trigtest_before_stmt ON foreign_schema.foreign_table_1; +DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1; +DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1; +DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1; + +DROP FUNCTION dummy_trigger(); + +-- Table inheritance +CREATE TABLE fd_pt1 ( + c1 integer NOT NULL, + c2 text, + c3 date +); +CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1) + SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); +\d+ fd_pt1 +\d+ ft2 +DROP FOREIGN TABLE ft2; +\d+ fd_pt1 +CREATE FOREIGN TABLE ft2 ( + c1 integer NOT NULL, + c2 text, + c3 date +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); +\d+ ft2 +ALTER FOREIGN TABLE ft2 INHERIT fd_pt1; +\d+ fd_pt1 +\d+ ft2 +CREATE TABLE ct3() INHERITS(ft2); +CREATE FOREIGN TABLE ft3 ( + c1 integer NOT NULL, + c2 text, + c3 date +) INHERITS(ft2) + SERVER s0; +\d+ ft2 +\d+ ct3 +\d+ ft3 + +-- add attributes recursively +ALTER TABLE fd_pt1 ADD COLUMN c4 integer; +ALTER TABLE fd_pt1 ADD COLUMN c5 integer DEFAULT 0; +ALTER TABLE fd_pt1 ADD COLUMN c6 integer; +ALTER TABLE fd_pt1 ADD COLUMN c7 integer NOT NULL; +ALTER TABLE fd_pt1 ADD COLUMN c8 integer; +\d+ fd_pt1 +\d+ ft2 +\d+ ct3 +\d+ ft3 + +-- alter attributes recursively +ALTER TABLE fd_pt1 ALTER COLUMN c4 SET DEFAULT 0; +ALTER TABLE fd_pt1 ALTER COLUMN c5 DROP DEFAULT; +ALTER TABLE fd_pt1 ALTER COLUMN c6 SET NOT NULL; +ALTER TABLE fd_pt1 ALTER COLUMN c7 DROP NOT NULL; +ALTER TABLE fd_pt1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR +ALTER TABLE fd_pt1 ALTER COLUMN c8 TYPE char(10); +ALTER TABLE fd_pt1 ALTER COLUMN c8 SET DATA TYPE text; +ALTER TABLE fd_pt1 ALTER COLUMN c1 SET STATISTICS 10000; +ALTER TABLE fd_pt1 ALTER COLUMN c1 SET (n_distinct = 100); +ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STATISTICS -1; +ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL; +\d+ fd_pt1 +\d+ ft2 + +-- drop attributes recursively +ALTER TABLE fd_pt1 DROP COLUMN c4; +ALTER TABLE fd_pt1 DROP COLUMN c5; +ALTER TABLE fd_pt1 DROP COLUMN c6; +ALTER TABLE fd_pt1 DROP COLUMN c7; +ALTER TABLE fd_pt1 DROP COLUMN c8; +\d+ fd_pt1 +\d+ ft2 + +-- add constraints recursively +ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk1 CHECK (c1 > 0) NO INHERIT; +ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk2 CHECK (c2 <> ''); +-- connoinherit should be true for NO INHERIT constraint +SELECT relname, conname, contype, conislocal, coninhcount, connoinherit + FROM pg_class AS pc JOIN pg_constraint AS pgc ON (conrelid = pc.oid) + WHERE pc.relname = 'fd_pt1' + ORDER BY 1,2; +-- child does not inherit NO INHERIT constraints +\d+ fd_pt1 +\d+ ft2 +DROP FOREIGN TABLE ft2; -- ERROR +\set VERBOSITY terse \\ -- YB: suppress cascade details +DROP FOREIGN TABLE ft2 CASCADE; +\set VERBOSITY default \\ -- YB +CREATE FOREIGN TABLE ft2 ( + c1 integer NOT NULL, + c2 text, + c3 date +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); +-- child must have parent's INHERIT constraints +ALTER FOREIGN TABLE ft2 INHERIT fd_pt1; -- ERROR +ALTER FOREIGN TABLE ft2 ADD CONSTRAINT fd_pt1chk2 CHECK (c2 <> ''); +ALTER FOREIGN TABLE ft2 INHERIT fd_pt1; +-- child does not inherit NO INHERIT constraints +\d+ fd_pt1 +\d+ ft2 + +-- drop constraints recursively +ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk1 CASCADE; +ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk2 CASCADE; + +-- NOT VALID case +INSERT INTO fd_pt1 VALUES (1, 'fd_pt1'::text, '1994-01-01'::date); +ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID; +\d+ fd_pt1 +\d+ ft2 +-- VALIDATE CONSTRAINT need do nothing on foreign tables +ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3; +\d+ fd_pt1 +\d+ ft2 + +-- changes name of an attribute recursively +ALTER TABLE fd_pt1 RENAME COLUMN c1 TO f1; +ALTER TABLE fd_pt1 RENAME COLUMN c2 TO f2; +ALTER TABLE fd_pt1 RENAME COLUMN c3 TO f3; +-- changes name of a constraint recursively +ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check; +\d+ fd_pt1 +\d+ ft2 + +DROP TABLE fd_pt1 CASCADE; + +-- IMPORT FOREIGN SCHEMA +IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR +IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR +IMPORT FOREIGN SCHEMA s1 EXCEPT (t1) FROM SERVER s9 INTO public; -- ERROR +IMPORT FOREIGN SCHEMA s1 EXCEPT (t1, t2) FROM SERVER s9 INTO public +OPTIONS (option1 'value1', option2 'value2'); -- ERROR + +-- DROP FOREIGN TABLE +DROP FOREIGN TABLE no_table; -- ERROR +DROP FOREIGN TABLE IF EXISTS no_table; +DROP FOREIGN TABLE foreign_schema.foreign_table_1; + +-- REASSIGN OWNED/DROP OWNED of foreign objects +REASSIGN OWNED BY regress_test_role TO regress_test_role2; +DROP OWNED BY regress_test_role2; +DROP OWNED BY regress_test_role2 CASCADE; + +-- Foreign partition DDL stuff +CREATE TABLE fd_pt2 ( + c1 integer NOT NULL, + c2 text, + c3 date +) PARTITION BY LIST (c1); +CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1) + SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); +\d+ fd_pt2 +\d+ fd_pt2_1 + +-- partition cannot have additional columns +DROP FOREIGN TABLE fd_pt2_1; +CREATE FOREIGN TABLE fd_pt2_1 ( + c1 integer NOT NULL, + c2 text, + c3 date, + c4 char +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); +\d+ fd_pt2_1 +ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR + +DROP FOREIGN TABLE fd_pt2_1; +\d+ fd_pt2 +CREATE FOREIGN TABLE fd_pt2_1 ( + c1 integer NOT NULL, + c2 text, + c3 date +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); +\d+ fd_pt2_1 +-- no attach partition validation occurs for foreign tables +ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); +\d+ fd_pt2 +\d+ fd_pt2_1 + +-- cannot add column to a partition +ALTER TABLE fd_pt2_1 ADD c4 char; + +-- ok to have a partition's own constraints though +ALTER TABLE fd_pt2_1 ALTER c3 SET NOT NULL; +ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> ''); +\d+ fd_pt2 +\d+ fd_pt2_1 + +-- cannot drop inherited NOT NULL constraint from a partition +ALTER TABLE fd_pt2_1 ALTER c1 DROP NOT NULL; + +-- partition must have parent's constraints +ALTER TABLE fd_pt2 DETACH PARTITION fd_pt2_1; +ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL; +\d+ fd_pt2 +\d+ fd_pt2_1 +ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR +ALTER FOREIGN TABLE fd_pt2_1 ALTER c2 SET NOT NULL; +ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); + +ALTER TABLE fd_pt2 DETACH PARTITION fd_pt2_1; +ALTER TABLE fd_pt2 ADD CONSTRAINT fd_pt2chk1 CHECK (c1 > 0); +\d+ fd_pt2 +\d+ fd_pt2_1 +ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR +ALTER FOREIGN TABLE fd_pt2_1 ADD CONSTRAINT fd_pt2chk1 CHECK (c1 > 0); +ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); + +DROP FOREIGN TABLE fd_pt2_1; +DROP TABLE fd_pt2; + +-- foreign table cannot be part of partition tree made of temporary +-- relations. +CREATE TEMP TABLE temp_parted (a int) PARTITION BY LIST (a); +CREATE FOREIGN TABLE foreign_part PARTITION OF temp_parted DEFAULT + SERVER s0; -- ERROR +CREATE FOREIGN TABLE foreign_part (a int) SERVER s0; +ALTER TABLE temp_parted ATTACH PARTITION foreign_part DEFAULT; -- ERROR +DROP FOREIGN TABLE foreign_part; +DROP TABLE temp_parted; + +-- Cleanup +DROP SCHEMA foreign_schema CASCADE; +DROP ROLE regress_test_role; -- ERROR +DROP SERVER t1 CASCADE; +DROP USER MAPPING FOR regress_test_role SERVER s6; +DROP FOREIGN DATA WRAPPER foo CASCADE; +DROP SERVER s8 CASCADE; +DROP ROLE regress_test_indirect; +DROP ROLE regress_test_role; +DROP ROLE regress_unprivileged_role; -- ERROR +REVOKE ALL ON FOREIGN DATA WRAPPER postgresql FROM regress_unprivileged_role; +DROP ROLE regress_unprivileged_role; +DROP ROLE regress_test_role2; +DROP FOREIGN DATA WRAPPER postgresql CASCADE; +DROP FOREIGN DATA WRAPPER dummy CASCADE; +\c +DROP ROLE regress_foreign_data_user; + +-- At this point we should have no wrappers, no servers, and no mappings. +SELECT fdwname, fdwhandler, fdwvalidator, fdwoptions FROM pg_foreign_data_wrapper; +SELECT srvname, srvoptions FROM pg_foreign_server; +SELECT * FROM pg_user_mapping; diff --git a/src/test/regress/sql/yb.port.foreign_key.sql b/src/test/regress/sql/yb.port.foreign_key.sql new file mode 100644 index 0000000000000..29827b43413b4 --- /dev/null +++ b/src/test/regress/sql/yb.port.foreign_key.sql @@ -0,0 +1,1716 @@ +-- +-- FOREIGN KEY +-- +-- TODO: Run this test with REPEATABLE READ isolation level: -- https://github.com/yugabyte/yugabyte-db/issues/2604 +-- +-- MATCH FULL +-- +-- First test, check and cascade +-- + + +CREATE TABLE PKTABLE ( ptest1 int PRIMARY KEY, ptest2 text ); +CREATE TABLE FKTABLE ( ftest1 int REFERENCES PKTABLE MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int ); + +-- Insert test data into PKTABLE +INSERT INTO PKTABLE VALUES (1, 'Test1'); +INSERT INTO PKTABLE VALUES (2, 'Test2'); +INSERT INTO PKTABLE VALUES (3, 'Test3'); +INSERT INTO PKTABLE VALUES (4, 'Test4'); +INSERT INTO PKTABLE VALUES (5, 'Test5'); + +-- Insert successful rows into FK TABLE +INSERT INTO FKTABLE VALUES (1, 2); +INSERT INTO FKTABLE VALUES (2, 3); +INSERT INTO FKTABLE VALUES (3, 4); +INSERT INTO FKTABLE VALUES (NULL, 1); + +-- Insert a failed row into FK TABLE +INSERT INTO FKTABLE VALUES (100, 2); + +-- Check FKTABLE +SELECT * FROM FKTABLE ORDER BY ftest1; + +-- Delete a row from PK TABLE +DELETE FROM PKTABLE WHERE ptest1=1; + +-- Check FKTABLE for removal of matched row +SELECT * FROM FKTABLE ORDER BY ftest1; + +-- Update a row from PK TABLE +UPDATE PKTABLE SET ptest1=1 WHERE ptest1=2; + +-- Check FKTABLE for update of matched row +SELECT * FROM FKTABLE ORDER BY ftest1; + +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; + +-- +-- check set NULL and table constraint on multiple columns +-- +CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 text, PRIMARY KEY(ptest1, ptest2) ); +CREATE TABLE FKTABLE ( ftest1 int, ftest2 int, ftest3 int, CONSTRAINT constrname FOREIGN KEY(ftest1, ftest2) + REFERENCES PKTABLE MATCH FULL ON DELETE SET NULL ON UPDATE SET NULL); + +-- Test comments +COMMENT ON CONSTRAINT constrname_wrong ON FKTABLE IS 'fk constraint comment'; +COMMENT ON CONSTRAINT constrname ON FKTABLE IS 'fk constraint comment'; +COMMENT ON CONSTRAINT constrname ON FKTABLE IS NULL; + +-- Insert test data into PKTABLE +INSERT INTO PKTABLE VALUES (1, 2, 'Test1'); +INSERT INTO PKTABLE VALUES (1, 3, 'Test1-2'); +INSERT INTO PKTABLE VALUES (2, 4, 'Test2'); +INSERT INTO PKTABLE VALUES (3, 6, 'Test3'); +INSERT INTO PKTABLE VALUES (4, 8, 'Test4'); +INSERT INTO PKTABLE VALUES (5, 10, 'Test5'); + +-- Insert successful rows into FK TABLE +INSERT INTO FKTABLE VALUES (1, 2, 4); +INSERT INTO FKTABLE VALUES (1, 3, 5); +INSERT INTO FKTABLE VALUES (2, 4, 8); +INSERT INTO FKTABLE VALUES (3, 6, 12); +INSERT INTO FKTABLE VALUES (NULL, NULL, 0); + +-- Insert failed rows into FK TABLE +INSERT INTO FKTABLE VALUES (100, 2, 4); +INSERT INTO FKTABLE VALUES (2, 2, 4); +INSERT INTO FKTABLE VALUES (NULL, 2, 4); +INSERT INTO FKTABLE VALUES (1, NULL, 4); + +-- Check FKTABLE +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Delete a row from PK TABLE +DELETE FROM PKTABLE WHERE ptest1=1 and ptest2=2; + +-- Check that values of matched row are set to null. +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Delete another row from PK TABLE +DELETE FROM PKTABLE WHERE ptest1=5 and ptest2=10; + +-- Check FKTABLE (should be no change) +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Update a row from PK TABLE +UPDATE PKTABLE SET ptest1=1 WHERE ptest1=2; + +-- Check FKTABLE for update of matched row +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Try altering the column type where foreign keys are involved +ALTER TABLE PKTABLE ALTER COLUMN ptest1 TYPE bigint; +ALTER TABLE FKTABLE ALTER COLUMN ftest1 TYPE bigint; +SELECT * FROM PKTABLE ORDER BY ptest1, ptest2; +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2, ftest3; + +DROP TABLE PKTABLE CASCADE; +DROP TABLE FKTABLE; + +-- +-- check set default and table constraint on multiple columns +-- +CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 text, PRIMARY KEY(ptest1, ptest2) ); +CREATE TABLE FKTABLE ( ftest1 int DEFAULT -1, ftest2 int DEFAULT -2, ftest3 int, CONSTRAINT constrname2 FOREIGN KEY(ftest1, ftest2) + REFERENCES PKTABLE MATCH FULL ON DELETE SET DEFAULT ON UPDATE SET DEFAULT); + +-- Insert a value in PKTABLE for default +INSERT INTO PKTABLE VALUES (-1, -2, 'The Default!'); + +-- Insert test data into PKTABLE +INSERT INTO PKTABLE VALUES (1, 2, 'Test1'); +INSERT INTO PKTABLE VALUES (1, 3, 'Test1-2'); +INSERT INTO PKTABLE VALUES (2, 4, 'Test2'); +INSERT INTO PKTABLE VALUES (3, 6, 'Test3'); +INSERT INTO PKTABLE VALUES (4, 8, 'Test4'); +INSERT INTO PKTABLE VALUES (5, 10, 'Test5'); + +-- Insert successful rows into FK TABLE +INSERT INTO FKTABLE VALUES (1, 2, 4); +INSERT INTO FKTABLE VALUES (1, 3, 5); +INSERT INTO FKTABLE VALUES (2, 4, 8); +INSERT INTO FKTABLE VALUES (3, 6, 12); +INSERT INTO FKTABLE VALUES (NULL, NULL, 0); + +-- Insert failed rows into FK TABLE +INSERT INTO FKTABLE VALUES (100, 2, 4); +INSERT INTO FKTABLE VALUES (2, 2, 4); +INSERT INTO FKTABLE VALUES (NULL, 2, 4); +INSERT INTO FKTABLE VALUES (1, NULL, 4); + +-- Check FKTABLE +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2; + +-- Delete a row from PK TABLE +DELETE FROM PKTABLE WHERE ptest1=1 and ptest2=2; + +-- Check FKTABLE to check for removal +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2; + +-- Delete another row from PK TABLE +DELETE FROM PKTABLE WHERE ptest1=5 and ptest2=10; + +-- Check FKTABLE (should be no change) +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2; + +-- Update a row from PK TABLE +UPDATE PKTABLE SET ptest1=1 WHERE ptest1=2; + +-- Check FKTABLE for update of matched row +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Suppress cascade details because it doesn't mismatch what Postgres prints. +-- TODO(YB): PG show dependecy on table pktable, but for us it's either it or +-- index pktable_pkey (at random)! +\set VERBOSITY terse +-- this should fail for lack of CASCADE +DROP TABLE PKTABLE; +\set VERBOSITY default +DROP TABLE PKTABLE CASCADE; +DROP TABLE FKTABLE; + + +-- +-- First test, check with no on delete or on update +-- +CREATE TABLE PKTABLE ( ptest1 int PRIMARY KEY, ptest2 text ); +CREATE TABLE FKTABLE ( ftest1 int REFERENCES PKTABLE MATCH FULL, ftest2 int ); + +-- Insert test data into PKTABLE +INSERT INTO PKTABLE VALUES (1, 'Test1'); +INSERT INTO PKTABLE VALUES (2, 'Test2'); +INSERT INTO PKTABLE VALUES (3, 'Test3'); +INSERT INTO PKTABLE VALUES (4, 'Test4'); +INSERT INTO PKTABLE VALUES (5, 'Test5'); + +-- Insert successful rows into FK TABLE +INSERT INTO FKTABLE VALUES (1, 2); +INSERT INTO FKTABLE VALUES (2, 3); +INSERT INTO FKTABLE VALUES (3, 4); +INSERT INTO FKTABLE VALUES (NULL, 1); + +-- Insert a failed row into FK TABLE +INSERT INTO FKTABLE VALUES (100, 2); + +-- Check FKTABLE +SELECT * FROM FKTABLE ORDER BY ftest1, ftest2; + +-- Check PKTABLE +SELECT * FROM PKTABLE ORDER BY ptest1, ptest2; + +-- Delete a row from PK TABLE (should fail) +DELETE FROM PKTABLE WHERE ptest1=1; + +-- Delete a row from PK TABLE (should succeed) +DELETE FROM PKTABLE WHERE ptest1=5; + +-- Check PKTABLE for deletes +SELECT * FROM PKTABLE ORDER BY ptest1, ptest2; + +-- Update a row from PK TABLE (should fail) +UPDATE PKTABLE SET ptest1=0 WHERE ptest1=2; + +-- Update a row from PK TABLE (should succeed) +UPDATE PKTABLE SET ptest1=0 WHERE ptest1=4; + +-- Check PKTABLE for updates +SELECT * FROM PKTABLE ORDER BY ptest1, ptest2; + +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; + + +-- MATCH SIMPLE + +-- Base test restricting update/delete +CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 int, ptest4 text, PRIMARY KEY(ptest1, ptest2, ptest3) ); +CREATE TABLE FKTABLE ( ftest1 int, ftest2 int, ftest3 int, ftest4 int, CONSTRAINT constrname3 + FOREIGN KEY(ftest1, ftest2, ftest3) REFERENCES PKTABLE); + +-- Insert Primary Key values +INSERT INTO PKTABLE VALUES (1, 2, 3, 'test1'); +INSERT INTO PKTABLE VALUES (1, 3, 3, 'test2'); +INSERT INTO PKTABLE VALUES (2, 3, 4, 'test3'); +INSERT INTO PKTABLE VALUES (2, 4, 5, 'test4'); + +-- Insert Foreign Key values +INSERT INTO FKTABLE VALUES (1, 2, 3, 1); +INSERT INTO FKTABLE VALUES (NULL, 2, 3, 2); +INSERT INTO FKTABLE VALUES (2, NULL, 3, 3); +INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4); +INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5); + +-- Insert a failed values +INSERT INTO FKTABLE VALUES (1, 2, 7, 6); + +-- Show FKTABLE +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Try to update something that should fail +UPDATE PKTABLE set ptest2=5 where ptest2=2; + +-- Try to update something that should succeed +UPDATE PKTABLE set ptest1=1 WHERE ptest2=3; + +-- Try to delete something that should fail +DELETE FROM PKTABLE where ptest1=1 and ptest2=2 and ptest3=3; + +-- Try to delete something that should work +DELETE FROM PKTABLE where ptest1=2; + +-- Show PKTABLE and FKTABLE +SELECT * from PKTABLE ORDER BY ptest1, ptest2, ptest3; + +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; + +-- cascade update/delete +CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 int, ptest4 text, PRIMARY KEY(ptest1, ptest2, ptest3) ); +CREATE TABLE FKTABLE ( ftest1 int, ftest2 int, ftest3 int, ftest4 int, CONSTRAINT constrname3 + FOREIGN KEY(ftest1, ftest2, ftest3) REFERENCES PKTABLE + ON DELETE CASCADE ON UPDATE CASCADE); + +-- Insert Primary Key values +INSERT INTO PKTABLE VALUES (1, 2, 3, 'test1'); +INSERT INTO PKTABLE VALUES (1, 3, 3, 'test2'); +INSERT INTO PKTABLE VALUES (2, 3, 4, 'test3'); +INSERT INTO PKTABLE VALUES (2, 4, 5, 'test4'); + +-- Insert Foreign Key values +INSERT INTO FKTABLE VALUES (1, 2, 3, 1); +INSERT INTO FKTABLE VALUES (NULL, 2, 3, 2); +INSERT INTO FKTABLE VALUES (2, NULL, 3, 3); +INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4); +INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5); + +-- Insert a failed values +INSERT INTO FKTABLE VALUES (1, 2, 7, 6); + +-- Show FKTABLE +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Try to update something that will cascade +UPDATE PKTABLE set ptest2=5 where ptest2=2; + +-- Try to update something that should not cascade +UPDATE PKTABLE set ptest1=1 WHERE ptest2=3; + +-- Show PKTABLE and FKTABLE +SELECT * from PKTABLE ORDER BY ptest1, ptest2, ptest3; +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Try to delete something that should cascade +DELETE FROM PKTABLE where ptest1=1 and ptest2=5 and ptest3=3; + +-- Show PKTABLE and FKTABLE +SELECT * from PKTABLE ORDER BY ptest1, ptest2, ptest3; +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Try to delete something that should not have a cascade +DELETE FROM PKTABLE where ptest1=2; + +-- Show PKTABLE and FKTABLE +SELECT * from PKTABLE ORDER BY ptest1, ptest2, ptest3; +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; + +-- set null update / set default delete +CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 int, ptest4 text, PRIMARY KEY(ptest1, ptest2, ptest3) ); +CREATE TABLE FKTABLE ( ftest1 int DEFAULT 0, ftest2 int, ftest3 int, ftest4 int, CONSTRAINT constrname3 + FOREIGN KEY(ftest1, ftest2, ftest3) REFERENCES PKTABLE + ON DELETE SET DEFAULT ON UPDATE SET NULL); + +-- Insert Primary Key values +INSERT INTO PKTABLE VALUES (1, 2, 3, 'test1'); +INSERT INTO PKTABLE VALUES (1, 3, 3, 'test2'); +INSERT INTO PKTABLE VALUES (2, 3, 4, 'test3'); +INSERT INTO PKTABLE VALUES (2, 4, 5, 'test4'); + +-- Insert Foreign Key values +INSERT INTO FKTABLE VALUES (1, 2, 3, 1); +INSERT INTO FKTABLE VALUES (2, 3, 4, 1); +INSERT INTO FKTABLE VALUES (NULL, 2, 3, 2); +INSERT INTO FKTABLE VALUES (2, NULL, 3, 3); +INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4); +INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5); + +-- Insert a failed values +INSERT INTO FKTABLE VALUES (1, 2, 7, 6); + +-- Show FKTABLE +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Try to update something that will set null +UPDATE PKTABLE set ptest2=5 where ptest2=2; + +-- Try to update something that should not set null +UPDATE PKTABLE set ptest2=2 WHERE ptest2=3 and ptest1=1; + +-- Show PKTABLE and FKTABLE +SELECT * from PKTABLE ORDER BY ptest1, ptest2, ptest3; +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Try to delete something that should set default +DELETE FROM PKTABLE where ptest1=2 and ptest2=3 and ptest3=4; + +-- Show PKTABLE and FKTABLE +SELECT * from PKTABLE ORDER BY ptest1, ptest2, ptest3; +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Try to delete something that should not set default +DELETE FROM PKTABLE where ptest2=5; + +-- Show PKTABLE and FKTABLE +SELECT * from PKTABLE ORDER BY ptest1, ptest2, ptest3; +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; + +-- set default update / set null delete +CREATE TABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 int, ptest4 text, PRIMARY KEY(ptest1, ptest2, ptest3) ); +CREATE TABLE FKTABLE ( ftest1 int DEFAULT 0, ftest2 int DEFAULT -1, ftest3 int DEFAULT -2, ftest4 int, CONSTRAINT constrname3 + FOREIGN KEY(ftest1, ftest2, ftest3) REFERENCES PKTABLE + ON DELETE SET NULL ON UPDATE SET DEFAULT); + +-- Insert Primary Key values +INSERT INTO PKTABLE VALUES (1, 2, 3, 'test1'); +INSERT INTO PKTABLE VALUES (1, 3, 3, 'test2'); +INSERT INTO PKTABLE VALUES (2, 3, 4, 'test3'); +INSERT INTO PKTABLE VALUES (2, 4, 5, 'test4'); +INSERT INTO PKTABLE VALUES (2, -1, 5, 'test5'); + +-- Insert Foreign Key values +INSERT INTO FKTABLE VALUES (1, 2, 3, 1); +INSERT INTO FKTABLE VALUES (2, 3, 4, 1); +INSERT INTO FKTABLE VALUES (2, 4, 5, 1); +INSERT INTO FKTABLE VALUES (NULL, 2, 3, 2); +INSERT INTO FKTABLE VALUES (2, NULL, 3, 3); +INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4); +INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5); + +-- Insert a failed values +INSERT INTO FKTABLE VALUES (1, 2, 7, 6); + +-- Show FKTABLE +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Try to update something that will fail +UPDATE PKTABLE set ptest2=5 where ptest2=2; + +-- Try to update something that will set default +UPDATE PKTABLE set ptest1=0, ptest2=-1, ptest3=-2 where ptest2=2; +UPDATE PKTABLE set ptest2=10 where ptest2=4; + +-- Try to update something that should not set default +UPDATE PKTABLE set ptest2=2 WHERE ptest2=3 and ptest1=1; + +-- Show PKTABLE and FKTABLE +SELECT * from PKTABLE ORDER BY ptest1, ptest2, ptest3; +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Try to delete something that should set null +DELETE FROM PKTABLE where ptest1=2 and ptest2=3 and ptest3=4; + +-- Show PKTABLE and FKTABLE +SELECT * from PKTABLE ORDER BY ptest1, ptest2, ptest3; +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +-- Try to delete something that should not set null +DELETE FROM PKTABLE where ptest2=-1 and ptest3=5; + +-- Show PKTABLE and FKTABLE +SELECT * from PKTABLE ORDER BY ptest1, ptest2, ptest3; +SELECT * from FKTABLE ORDER BY ftest1, ftest2, ftest3; + +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; + +CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY); +CREATE TABLE FKTABLE_FAIL1 ( ftest1 int, CONSTRAINT fkfail1 FOREIGN KEY (ftest2) REFERENCES PKTABLE); +CREATE TABLE FKTABLE_FAIL2 ( ftest1 int, CONSTRAINT fkfail1 FOREIGN KEY (ftest1) REFERENCES PKTABLE(ptest2)); + +SELECT COUNT(*) FROM pg_class WHERE relname = 'FKTABLE_FAIL1'; +SELECT COUNT(*) FROM pg_class WHERE relname = 'FKTABLE_FAIL2'; +DROP TABLE FKTABLE_FAIL1; +DROP TABLE FKTABLE_FAIL2; +DROP TABLE PKTABLE; + +-- Test for referencing column number smaller than referenced constraint +CREATE TABLE PKTABLE (ptest1 int, ptest2 int, UNIQUE(ptest1, ptest2)); +CREATE TABLE FKTABLE_FAIL1 (ftest1 int REFERENCES pktable(ptest1)); + +SELECT COUNT(*) FROM pg_class WHERE relname = 'FKTABLE_FAIL1'; +DROP TABLE FKTABLE_FAIL1; +DROP TABLE PKTABLE; + +-- +-- Tests for mismatched types +-- +-- Basic one column, two table setup +CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY); +INSERT INTO PKTABLE VALUES(42); +-- This next should fail, because int=inet does not exist +CREATE TABLE FKTABLE (ftest1 inet REFERENCES pktable); +-- This should also fail for the same reason, but here we +-- give the column name +CREATE TABLE FKTABLE (ftest1 inet REFERENCES pktable(ptest1)); +-- This should succeed, even though they are different types, +-- because int=int8 exists and is a member of the integer opfamily +CREATE TABLE FKTABLE (ftest1 int8 REFERENCES pktable); +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed +UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail +DROP TABLE FKTABLE; +-- This should fail, because we'd have to cast numeric to int which is +-- not an implicit coercion (or use numeric=numeric, but that's not part +-- of the integer opfamily) +CREATE TABLE FKTABLE (ftest1 numeric REFERENCES pktable); +DROP TABLE PKTABLE; +-- On the other hand, this should work because int implicitly promotes to +-- numeric, and we allow promotion on the FK side +CREATE TABLE PKTABLE (ptest1 numeric PRIMARY KEY); +INSERT INTO PKTABLE VALUES(42); +CREATE TABLE FKTABLE (ftest1 int REFERENCES pktable); +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed +UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; + +-- TODO(YB) changing inet->text below as we don't support indexes on inet type yet. +-- Two columns, two tables +CREATE TABLE PKTABLE (ptest1 int, ptest2 text, PRIMARY KEY(ptest1, ptest2)); +-- This should fail, because we just chose really odd types +CREATE TABLE FKTABLE (ftest1 cidr, ftest2 timestamp, FOREIGN KEY(ftest1, ftest2) REFERENCES pktable); +-- Again, so should this... +CREATE TABLE FKTABLE (ftest1 cidr, ftest2 timestamp, FOREIGN KEY(ftest1, ftest2) REFERENCES pktable(ptest1, ptest2)); +-- This fails because we mixed up the column ordering +CREATE TABLE FKTABLE (ftest1 int, ftest2 text, FOREIGN KEY(ftest2, ftest1) REFERENCES pktable); +-- As does this... +CREATE TABLE FKTABLE (ftest1 int, ftest2 text, FOREIGN KEY(ftest2, ftest1) REFERENCES pktable(ptest1, ptest2)); +-- And again.. +CREATE TABLE FKTABLE (ftest1 int, ftest2 text, FOREIGN KEY(ftest1, ftest2) REFERENCES pktable(ptest2, ptest1)); +-- This works... +CREATE TABLE FKTABLE (ftest1 int, ftest2 text, FOREIGN KEY(ftest2, ftest1) REFERENCES pktable(ptest2, ptest1)); +DROP TABLE FKTABLE; +-- As does this +CREATE TABLE FKTABLE (ftest1 int, ftest2 text, FOREIGN KEY(ftest1, ftest2) REFERENCES pktable(ptest1, ptest2)); +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; + +-- Two columns, same table +-- Make sure this still works... +CREATE TABLE PKTABLE (ptest1 int, ptest2 text, ptest3 int, ptest4 text, PRIMARY KEY(ptest1, ptest2), FOREIGN KEY(ptest3, +ptest4) REFERENCES pktable(ptest1, ptest2)); +DROP TABLE PKTABLE; +-- And this, +CREATE TABLE PKTABLE (ptest1 int, ptest2 text, ptest3 int, ptest4 text, PRIMARY KEY(ptest1, ptest2), FOREIGN KEY(ptest3, +ptest4) REFERENCES pktable); +DROP TABLE PKTABLE; +-- This shouldn't (mixed up columns) +CREATE TABLE PKTABLE (ptest1 int, ptest2 text, ptest3 int, ptest4 text, PRIMARY KEY(ptest1, ptest2), FOREIGN KEY(ptest3, +ptest4) REFERENCES pktable(ptest2, ptest1)); +-- Nor should this... (same reason, we have 4,3 referencing 1,2 which mismatches types +CREATE TABLE PKTABLE (ptest1 int, ptest2 text, ptest3 int, ptest4 text, PRIMARY KEY(ptest1, ptest2), FOREIGN KEY(ptest4, +ptest3) REFERENCES pktable(ptest1, ptest2)); +-- Not this one either... Same as the last one except we didn't defined the columns being referenced. +CREATE TABLE PKTABLE (ptest1 int, ptest2 text, ptest3 int, ptest4 text, PRIMARY KEY(ptest1, ptest2), FOREIGN KEY(ptest4, +ptest3) REFERENCES pktable); +-- +-- Now some cases with inheritance +-- Basic 2 table case: 1 column of matching types. +create table pktable_base (base1 int not null); +create table pktable (ptest1 int, primary key(base1), unique(base1, ptest1)) inherits (pktable_base); +create table fktable (ftest1 int references pktable(base1)); +-- now some ins, upd, del +insert into pktable(base1) values (1); +insert into pktable(base1) values (2); +-- let's insert a non-existent fktable value +insert into fktable(ftest1) values (3); +-- let's make a valid row for that +insert into pktable(base1) values (3); +insert into fktable(ftest1) values (3); +-- let's try removing a row that should fail from pktable +delete from pktable where base1>2; +-- okay, let's try updating all of the base1 values to *4 +-- which should fail. +update pktable set base1=base1*4; +-- okay, let's try an update that should work. +update pktable set base1=base1*4 where base1<3; +-- and a delete that should work +delete from pktable where base1>3; +-- cleanup +drop table fktable; +delete from pktable; + +-- Now 2 columns 2 tables, matching types +create table fktable (ftest1 int, ftest2 int, foreign key(ftest1, ftest2) references pktable(base1, ptest1)); +-- now some ins, upd, del +insert into pktable(base1, ptest1) values (1, 1); +insert into pktable(base1, ptest1) values (2, 2); +-- let's insert a non-existent fktable value +insert into fktable(ftest1, ftest2) values (3, 1); +-- let's make a valid row for that +insert into pktable(base1,ptest1) values (3, 1); +insert into fktable(ftest1, ftest2) values (3, 1); +-- let's try removing a row that should fail from pktable +delete from pktable where base1>2; +-- okay, let's try updating all of the base1 values to *4 +-- which should fail. +update pktable set base1=base1*4; +-- okay, let's try an update that should work. +update pktable set base1=base1*4 where base1<3; +-- and a delete that should work +delete from pktable where base1>3; +-- cleanup +drop table fktable; +drop table pktable; +drop table pktable_base; + +-- Now we'll do one all in 1 table with 2 columns of matching types +create table pktable_base(base1 int not null, base2 int); +create table pktable(ptest1 int, ptest2 int, primary key(base1, ptest1), foreign key(base2, ptest2) references + pktable(base1, ptest1)) inherits (pktable_base); +insert into pktable (base1, ptest1, base2, ptest2) values (1, 1, 1, 1); +insert into pktable (base1, ptest1, base2, ptest2) values (2, 1, 1, 1); +insert into pktable (base1, ptest1, base2, ptest2) values (2, 2, 2, 1); +insert into pktable (base1, ptest1, base2, ptest2) values (1, 3, 2, 2); +-- fails (3,2) isn't in base1, ptest1 +insert into pktable (base1, ptest1, base2, ptest2) values (2, 3, 3, 2); +-- fails (2,2) is being referenced +delete from pktable where base1=2; +-- fails (1,1) is being referenced (twice) +update pktable set base1=3 where base1=1; +-- this sequence of two deletes will work, since after the first there will be no (2,*) references +delete from pktable where base2=2; +delete from pktable where base1=2; +drop table pktable; +drop table pktable_base; + +-- 2 columns (2 tables), mismatched types +create table pktable_base(base1 int not null); +create table pktable(ptest1 text, primary key(base1, ptest1)) inherits (pktable_base); -- YB: #17017: replace INET with TEXT because indexes on INET are not supported +-- just generally bad types (with and without column references on the referenced table) +create table fktable(ftest1 cidr, ftest2 int[], foreign key (ftest1, ftest2) references pktable); +create table fktable(ftest1 cidr, ftest2 int[], foreign key (ftest1, ftest2) references pktable(base1, ptest1)); +-- let's mix up which columns reference which +create table fktable(ftest1 int, ftest2 inet, foreign key(ftest2, ftest1) references pktable); +create table fktable(ftest1 int, ftest2 inet, foreign key(ftest2, ftest1) references pktable(base1, ptest1)); +create table fktable(ftest1 int, ftest2 inet, foreign key(ftest1, ftest2) references pktable(ptest1, base1)); +drop table pktable; +drop table pktable_base; + +-- 2 columns (1 table), mismatched types +create table pktable_base(base1 int not null, base2 int); +create table pktable(ptest1 text, ptest2 text[], primary key(base1, ptest1), foreign key(base2, ptest2) references -- YB: #17017: replace INET with TEXT because indexes on INET are not supported + pktable(base1, ptest1)) inherits (pktable_base); +create table pktable(ptest1 text, ptest2 text, primary key(base1, ptest1), foreign key(base2, ptest2) references -- YB: #17017: replace INET with TEXT because indexes on INET are not supported + pktable(ptest1, base1)) inherits (pktable_base); +create table pktable(ptest1 text, ptest2 text, primary key(base1, ptest1), foreign key(ptest2, base2) references -- YB: #17017: replace INET with TEXT because indexes on INET are not supported + pktable(base1, ptest1)) inherits (pktable_base); +create table pktable(ptest1 text, ptest2 text, primary key(base1, ptest1), foreign key(ptest2, base2) references -- YB: #17017: replace INET with TEXT because indexes on INET are not supported + pktable(base1, ptest1)) inherits (pktable_base); +drop table pktable; +drop table pktable_base; + +-- +-- Deferrable constraints +-- (right now, only FOREIGN KEY constraints can be deferred) +-- + +-- TODO YB does not yet support deferrable foreign key constraints. +-- Leaving the first failing statement uncommented so that this test +-- will fail when the feature is implemented (the full test should +-- be uncommented then). + +-- deferrable, explicitly deferred +CREATE TABLE pktable ( + id INT4 PRIMARY KEY, + other INT4 +); + +CREATE TABLE fktable ( + id INT4 PRIMARY KEY, + fk INT4 REFERENCES pktable DEFERRABLE +); + +-- default to immediate: should fail +INSERT INTO fktable VALUES (5, 10); + +-- explicitly defer the constraint +BEGIN; + +SET CONSTRAINTS ALL DEFERRED; + +INSERT INTO fktable VALUES (10, 15); +INSERT INTO pktable VALUES (15, 0); -- make the FK insert valid + +COMMIT; + +DROP TABLE fktable, pktable; + +-- deferrable, initially deferred +CREATE TABLE pktable ( + id INT4 PRIMARY KEY, + other INT4 +); + +CREATE TABLE fktable ( + id INT4 PRIMARY KEY, + fk INT4 REFERENCES pktable DEFERRABLE INITIALLY DEFERRED +); + +-- default to deferred, should succeed +BEGIN; + +INSERT INTO fktable VALUES (100, 200); +INSERT INTO pktable VALUES (200, 500); -- make the FK insert valid + +COMMIT; + +-- default to deferred, explicitly make immediate +BEGIN; + +SET CONSTRAINTS ALL IMMEDIATE; + +-- should fail +INSERT INTO fktable VALUES (500, 1000); + +COMMIT; + +DROP TABLE fktable, pktable; + +-- tricky behavior: according to SQL99, if a deferred constraint is set +-- to 'immediate' mode, it should be checked for validity *immediately*, +-- not when the current transaction commits (i.e. the mode change applies +-- retroactively) +CREATE TABLE pktable ( + id INT4 PRIMARY KEY, + other INT4 +); + +CREATE TABLE fktable ( + id INT4 PRIMARY KEY, + fk INT4 REFERENCES pktable DEFERRABLE +); + +BEGIN; + +SET CONSTRAINTS ALL DEFERRED; + +-- should succeed, for now +INSERT INTO fktable VALUES (1000, 2000); + +-- should cause transaction abort, due to preceding error +SET CONSTRAINTS ALL IMMEDIATE; + +INSERT INTO pktable VALUES (2000, 3); -- too late + +COMMIT; + +DROP TABLE fktable, pktable; + +-- deferrable, initially deferred +CREATE TABLE pktable ( + id INT4 PRIMARY KEY, + other INT4 +); + +CREATE TABLE fktable ( + id INT4 PRIMARY KEY, + fk INT4 REFERENCES pktable DEFERRABLE INITIALLY DEFERRED +); + +BEGIN; + +-- no error here +INSERT INTO fktable VALUES (100, 200); + +-- error here on commit +COMMIT; + +-- deferred in subtransactions +BEGIN; + +INSERT INTO pktable VALUES (100, 100); + +INSERT INTO pktable VALUES (200, 200); + +SAVEPOINT savept1; + +INSERT INTO fktable VALUES (100, 100); + +-- PK violation +INSERT INTO fktable VALUES (100, 200); + +ROLLBACK TO SAVEPOINT savept1; + +INSERT INTO fktable VALUES (100, 100); + +-- no error here +INSERT INTO fktable VALUES (200, 300); + +-- still no error +RELEASE SAVEPOINT savept1; + +-- error here on commit +COMMIT; + +SELECT * FROM pktable; +SELECT * FROM fktable; + +DROP TABLE fktable, pktable; + +-- test notice about expensive referential integrity checks, +-- where the index cannot be used because of type incompatibilities. + +CREATE TEMP TABLE pktable ( + id1 INT4 PRIMARY KEY, + id2 VARCHAR(4) UNIQUE, + id3 REAL UNIQUE, + UNIQUE(id1, id2, id3) +); + +CREATE TEMP TABLE fktable ( + x1 INT4 REFERENCES pktable(id1), + x2 VARCHAR(4) REFERENCES pktable(id2), + x3 REAL REFERENCES pktable(id3), + x4 TEXT, + x5 INT2 +); + +-- check individual constraints with alter table. + +-- should fail + +-- varchar does not promote to real +ALTER TABLE fktable ADD CONSTRAINT fk_2_3 +FOREIGN KEY (x2) REFERENCES pktable(id3); + +-- nor to int4 +ALTER TABLE fktable ADD CONSTRAINT fk_2_1 +FOREIGN KEY (x2) REFERENCES pktable(id1); + +-- real does not promote to int4 +ALTER TABLE fktable ADD CONSTRAINT fk_3_1 +FOREIGN KEY (x3) REFERENCES pktable(id1); + +-- int4 does not promote to text +ALTER TABLE fktable ADD CONSTRAINT fk_1_2 +FOREIGN KEY (x1) REFERENCES pktable(id2); + +-- should succeed + +-- int4 promotes to real +ALTER TABLE fktable ADD CONSTRAINT fk_1_3 +FOREIGN KEY (x1) REFERENCES pktable(id3); + +-- text is compatible with varchar +ALTER TABLE fktable ADD CONSTRAINT fk_4_2 +FOREIGN KEY (x4) REFERENCES pktable(id2); + +-- int2 is part of integer opfamily as of 8.0 +ALTER TABLE fktable ADD CONSTRAINT fk_5_1 +FOREIGN KEY (x5) REFERENCES pktable(id1); + +-- check multikey cases, especially out-of-order column lists + +-- these should work + +ALTER TABLE fktable ADD CONSTRAINT fk_123_123 +FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id1,id2,id3); + +ALTER TABLE fktable ADD CONSTRAINT fk_213_213 +FOREIGN KEY (x2,x1,x3) REFERENCES pktable(id2,id1,id3); + +ALTER TABLE fktable ADD CONSTRAINT fk_253_213 +FOREIGN KEY (x2,x5,x3) REFERENCES pktable(id2,id1,id3); + +-- these should fail + +ALTER TABLE fktable ADD CONSTRAINT fk_123_231 +FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id2,id3,id1); + +ALTER TABLE fktable ADD CONSTRAINT fk_241_132 +FOREIGN KEY (x2,x4,x1) REFERENCES pktable(id1,id3,id2); + +DROP TABLE pktable, fktable; + +-- test a tricky case: we can elide firing the FK check trigger during +-- an UPDATE if the UPDATE did not change the foreign key +-- field. However, we can't do this if our transaction was the one that +-- created the updated row and the trigger is deferred, since our UPDATE +-- will have invalidated the original newly-inserted tuple, and therefore +-- cause the on-INSERT RI trigger not to be fired. + +CREATE TEMP TABLE pktable ( + id int primary key, + other int +); + +CREATE TEMP TABLE fktable ( + id int primary key, + fk int references pktable deferrable initially deferred +); + +INSERT INTO pktable VALUES (5, 10); + +BEGIN; + +-- doesn't match PK, but no error yet +INSERT INTO fktable VALUES (0, 20); + +-- don't change FK +UPDATE fktable SET id = id + 1; + +-- should catch error from initial INSERT +COMMIT; + +-- check same case when insert is in a different subtransaction than update + +BEGIN; + +-- doesn't match PK, but no error yet +INSERT INTO fktable VALUES (0, 20); + +-- UPDATE will be in a subxact +SAVEPOINT savept1; + +-- don't change FK +UPDATE fktable SET id = id + 1; + +-- should catch error from initial INSERT +COMMIT; + +BEGIN; + +-- INSERT will be in a subxact +SAVEPOINT savept1; + +-- doesn't match PK, but no error yet +INSERT INTO fktable VALUES (0, 20); + +RELEASE SAVEPOINT savept1; + +-- don't change FK +UPDATE fktable SET id = id + 1; + +-- should catch error from initial INSERT +COMMIT; + +BEGIN; + +-- doesn't match PK, but no error yet +INSERT INTO fktable VALUES (0, 20); + +-- UPDATE will be in a subxact +SAVEPOINT savept1; + +-- don't change FK +UPDATE fktable SET id = id + 1; + +-- Roll back the UPDATE +ROLLBACK TO savept1; + +-- should catch error from initial INSERT +COMMIT; + +-- +-- check ALTER CONSTRAINT +-- + +INSERT INTO fktable VALUES (1, 5); + +ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY IMMEDIATE; + +BEGIN; + +-- doesn't match FK, should throw error now +UPDATE pktable SET id = 10 WHERE id = 5; + +COMMIT; + +BEGIN; + +-- doesn't match PK, should throw error now +INSERT INTO fktable VALUES (0, 20); + +COMMIT; + +-- try additional syntax +ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE; +-- illegal option +ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DEFERRED; + +-- test order of firing of FK triggers when several RI-induced changes need to +-- be made to the same row. This was broken by subtransaction-related +-- changes in 8.0. + +CREATE TEMP TABLE users ( + id INT PRIMARY KEY, + name VARCHAR NOT NULL +); + +INSERT INTO users VALUES (1, 'Jozko'); +INSERT INTO users VALUES (2, 'Ferko'); +INSERT INTO users VALUES (3, 'Samko'); + +CREATE TEMP TABLE tasks ( + id INT PRIMARY KEY, + owner INT REFERENCES users ON UPDATE CASCADE ON DELETE SET NULL, + worker INT REFERENCES users ON UPDATE CASCADE ON DELETE SET NULL, + checked_by INT REFERENCES users ON UPDATE CASCADE ON DELETE SET NULL +); + +INSERT INTO tasks VALUES (1,1,NULL,NULL); +INSERT INTO tasks VALUES (2,2,2,NULL); +INSERT INTO tasks VALUES (3,3,3,3); + +SELECT * FROM tasks; + +UPDATE users SET id = 4 WHERE id = 3; + +SELECT * FROM tasks; + +DELETE FROM users WHERE id = 4; + +SELECT * FROM tasks; + +-- could fail with only 2 changes to make, if row was already updated +BEGIN; +UPDATE tasks set id=id WHERE id=2; +SELECT * FROM tasks; +DELETE FROM users WHERE id = 2; +SELECT * FROM tasks; +COMMIT; + +-- +-- Test self-referential FK with CASCADE (bug #6268) +-- +create temp table selfref ( + a int primary key, + b int, + foreign key (b) references selfref (a) + on update cascade on delete cascade +); + +insert into selfref (a, b) +values + (0, 0), + (1, 1); + +begin; + update selfref set a = 123 where a = 0; + select a, b from selfref; + update selfref set a = 456 where a = 123; + select a, b from selfref; +commit; + +-- +-- Test that SET DEFAULT actions recognize updates to default values +-- +create temp table defp (f1 int primary key); +create temp table defc (f1 int default 0 + references defp on delete set default); +insert into defp values (0), (1), (2); +insert into defc values (2); +select * from defc; +delete from defp where f1 = 2; +select * from defc; +delete from defp where f1 = 0; -- fail +alter table defc alter column f1 set default 1; +delete from defp where f1 = 0; +select * from defc; +delete from defp where f1 = 1; -- fail + +-- +-- Test the difference between NO ACTION and RESTRICT +-- +create temp table pp (f1 int primary key); +create temp table cc (f1 int references pp on update no action); +insert into pp values(12); +insert into pp values(11); +update pp set f1=f1+1; +insert into cc values(13); +update pp set f1=f1+1; +update pp set f1=f1+1; -- fail +drop table pp, cc; + +create temp table pp (f1 int primary key); +create temp table cc (f1 int references pp on update restrict); +insert into pp values(12); +insert into pp values(11); +update pp set f1=f1+1; +insert into cc values(13); +update pp set f1=f1+1; -- fail +drop table pp, cc; + +-- +-- Test interaction of foreign-key optimization with rules (bug #14219) +-- +create temp table t1 (a integer primary key, b text); +create temp table t2 (a integer primary key, b integer references t1); +create rule r1 as on delete to t1 do delete from t2 where t2.b = old.a; + +explain (costs off) delete from t1 where a = 1; +delete from t1 where a = 1; + +-- Test a primary key with attributes located in later attnum positions +-- compared to the fk attributes. +create table pktable2 (a int, b int, c int, d int, e int, primary key (d, e)); +create table fktable2 (d int, e int, foreign key (d, e) references pktable2); +insert into pktable2 values (1, 2, 3, 4, 5); +insert into fktable2 values (4, 5); +delete from pktable2; +update pktable2 set d = 5; +drop table pktable2, fktable2; + +-- Test truncation of long foreign key names +create table pktable1 (a int primary key); +create table pktable2 (a int, b int, primary key (a, b)); +create table fktable2 ( + a int, + b int, + very_very_long_column_name_to_exceed_63_characters int, + foreign key (very_very_long_column_name_to_exceed_63_characters) references pktable1, + foreign key (a, very_very_long_column_name_to_exceed_63_characters) references pktable2, + foreign key (a, very_very_long_column_name_to_exceed_63_characters) references pktable2 +); +select conname from pg_constraint where conrelid = 'fktable2'::regclass order by conname; +drop table pktable1, pktable2, fktable2; + +-- +-- Test deferred FK check on a tuple deleted by a rolled-back subtransaction +-- +create table pktable2(f1 int primary key); +create table fktable2(f1 int references pktable2 deferrable initially deferred); +insert into pktable2 values(1); + +begin; +insert into fktable2 values(1); +savepoint x; +delete from fktable2; +rollback to x; +commit; + +begin; +insert into fktable2 values(2); +savepoint x; +delete from fktable2; +rollback to x; +commit; -- fail + +-- +-- Test that we prevent dropping FK constraint with pending trigger events +-- +begin; +insert into fktable2 values(2); +alter table fktable2 drop constraint fktable2_f1_fkey; +commit; + +begin; +delete from pktable2 where f1 = 1; +alter table fktable2 drop constraint fktable2_f1_fkey; +commit; + +drop table pktable2, fktable2; + + +-- +-- Foreign keys and partitioned tables +-- + +-- Creation of a partitioned hierarchy with irregular definitions +CREATE TABLE fk_notpartitioned_pk (fdrop1 int, a int, fdrop2 int, b int, + PRIMARY KEY (a, b)); +ALTER TABLE fk_notpartitioned_pk DROP COLUMN fdrop1, DROP COLUMN fdrop2; +CREATE TABLE fk_partitioned_fk (b int, fdrop1 int, a int) PARTITION BY RANGE (a, b); +ALTER TABLE fk_partitioned_fk DROP COLUMN fdrop1; +CREATE TABLE fk_partitioned_fk_1 (fdrop1 int, fdrop2 int, a int, fdrop3 int, b int); +ALTER TABLE fk_partitioned_fk_1 DROP COLUMN fdrop1, DROP COLUMN fdrop2, DROP COLUMN fdrop3; +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_1 FOR VALUES FROM (0,0) TO (1000,1000); +ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk; +CREATE TABLE fk_partitioned_fk_2 (b int, fdrop1 int, fdrop2 int, a int); +ALTER TABLE fk_partitioned_fk_2 DROP COLUMN fdrop1, DROP COLUMN fdrop2; +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000); + +CREATE TABLE fk_partitioned_fk_3 (fdrop1 int, fdrop2 int, fdrop3 int, fdrop4 int, b int, a int) + PARTITION BY HASH (a); +ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2, + DROP COLUMN fdrop3, DROP COLUMN fdrop4; +CREATE TABLE fk_partitioned_fk_3_0 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 0); +CREATE TABLE fk_partitioned_fk_3_1 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 1); +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3 + FOR VALUES FROM (2000,2000) TO (3000,3000); + +-- Creating a foreign key with ONLY on a partitioned table referencing +-- a non-partitioned table fails. +ALTER TABLE ONLY fk_partitioned_fk ADD FOREIGN KEY (a, b) + REFERENCES fk_notpartitioned_pk; +-- Adding a NOT VALID foreign key on a partitioned table referencing +-- a non-partitioned table fails. +ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) + REFERENCES fk_notpartitioned_pk NOT VALID; + +-- these inserts, targeting both the partition directly as well as the +-- partitioned table, should all fail +INSERT INTO fk_partitioned_fk (a,b) VALUES (500, 501); +INSERT INTO fk_partitioned_fk_1 (a,b) VALUES (500, 501); +INSERT INTO fk_partitioned_fk (a,b) VALUES (1500, 1501); +INSERT INTO fk_partitioned_fk_2 (a,b) VALUES (1500, 1501); +INSERT INTO fk_partitioned_fk (a,b) VALUES (2500, 2502); +INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2500, 2502); +INSERT INTO fk_partitioned_fk (a,b) VALUES (2501, 2503); +INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2501, 2503); + +-- but if we insert the values that make them valid, then they work +INSERT INTO fk_notpartitioned_pk VALUES (500, 501), (1500, 1501), + (2500, 2502), (2501, 2503); +INSERT INTO fk_partitioned_fk (a,b) VALUES (500, 501); +INSERT INTO fk_partitioned_fk (a,b) VALUES (1500, 1501); +INSERT INTO fk_partitioned_fk (a,b) VALUES (2500, 2502); +INSERT INTO fk_partitioned_fk (a,b) VALUES (2501, 2503); + +-- this update fails because there is no referenced row +UPDATE fk_partitioned_fk SET a = a + 1 WHERE a = 2501; +-- but we can fix it thusly: +INSERT INTO fk_notpartitioned_pk (a,b) VALUES (2502, 2503); +UPDATE fk_partitioned_fk SET a = a + 1 WHERE a = 2501; + +-- these updates would leave lingering rows in the referencing table; disallow +UPDATE fk_notpartitioned_pk SET b = 502 WHERE a = 500; +UPDATE fk_notpartitioned_pk SET b = 1502 WHERE a = 1500; +UPDATE fk_notpartitioned_pk SET b = 2504 WHERE a = 2500; +-- check psql behavior +\d fk_notpartitioned_pk +ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey; +-- done. +DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk; + +-- Altering a type referenced by a foreign key needs to drop/recreate the FK. +-- Ensure that works. +CREATE TABLE fk_notpartitioned_pk (a INT, PRIMARY KEY(a), CHECK (a > 0)); +CREATE TABLE fk_partitioned_fk (a INT REFERENCES fk_notpartitioned_pk(a) PRIMARY KEY) PARTITION BY RANGE(a); +CREATE TABLE fk_partitioned_fk_1 PARTITION OF fk_partitioned_fk FOR VALUES FROM (MINVALUE) TO (MAXVALUE); +INSERT INTO fk_notpartitioned_pk VALUES (1); +INSERT INTO fk_partitioned_fk VALUES (1); +ALTER TABLE fk_notpartitioned_pk ALTER COLUMN a TYPE bigint; +DELETE FROM fk_notpartitioned_pk WHERE a = 1; +DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk; + +-- Test some other exotic foreign key features: MATCH SIMPLE, ON UPDATE/DELETE +-- actions +CREATE TABLE fk_notpartitioned_pk (a int, b int, primary key (a, b)); +CREATE TABLE fk_partitioned_fk (a int default 2501, b int default 142857) PARTITION BY LIST (a); +CREATE TABLE fk_partitioned_fk_1 PARTITION OF fk_partitioned_fk FOR VALUES IN (NULL,500,501,502); +ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) + REFERENCES fk_notpartitioned_pk MATCH SIMPLE + ON DELETE SET NULL ON UPDATE SET NULL; +CREATE TABLE fk_partitioned_fk_2 PARTITION OF fk_partitioned_fk FOR VALUES IN (1500,1502); +CREATE TABLE fk_partitioned_fk_3 (a int, b int); +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3 FOR VALUES IN (2500,2501,2502,2503); + +-- this insert fails +INSERT INTO fk_partitioned_fk (a, b) VALUES (2502, 2503); +INSERT INTO fk_partitioned_fk_3 (a, b) VALUES (2502, 2503); +-- but since the FK is MATCH SIMPLE, this one doesn't +INSERT INTO fk_partitioned_fk_3 (a, b) VALUES (2502, NULL); +-- now create the referenced row ... +INSERT INTO fk_notpartitioned_pk VALUES (2502, 2503); +--- and now the same insert work +INSERT INTO fk_partitioned_fk_3 (a, b) VALUES (2502, 2503); +-- this always works +INSERT INTO fk_partitioned_fk (a,b) VALUES (NULL, NULL); + +-- ON UPDATE SET NULL +SELECT tableoid::regclass, a, b FROM fk_partitioned_fk WHERE b IS NULL ORDER BY a; +UPDATE fk_notpartitioned_pk SET a = a + 1 WHERE a = 2502; +SELECT tableoid::regclass, a, b FROM fk_partitioned_fk WHERE b IS NULL ORDER BY a; + +-- ON DELETE SET NULL +INSERT INTO fk_partitioned_fk VALUES (2503, 2503); +SELECT count(*) FROM fk_partitioned_fk WHERE a IS NULL; +DELETE FROM fk_notpartitioned_pk; +SELECT count(*) FROM fk_partitioned_fk WHERE a IS NULL; + +-- ON UPDATE/DELETE SET DEFAULT +ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey; +ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) + REFERENCES fk_notpartitioned_pk + ON DELETE SET DEFAULT ON UPDATE SET DEFAULT; +INSERT INTO fk_notpartitioned_pk VALUES (2502, 2503); +INSERT INTO fk_partitioned_fk_3 (a, b) VALUES (2502, 2503); +-- this fails, because the defaults for the referencing table are not present +-- in the referenced table: +UPDATE fk_notpartitioned_pk SET a = 1500 WHERE a = 2502; +-- but inserting the row we can make it work: +INSERT INTO fk_notpartitioned_pk VALUES (2501, 142857); +UPDATE fk_notpartitioned_pk SET a = 1500 WHERE a = 2502; +SELECT * FROM fk_partitioned_fk WHERE b = 142857; + +-- ON UPDATE/DELETE CASCADE +ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey; +ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) + REFERENCES fk_notpartitioned_pk + ON DELETE CASCADE ON UPDATE CASCADE; +UPDATE fk_notpartitioned_pk SET a = 2502 WHERE a = 2501; +SELECT * FROM fk_partitioned_fk WHERE b = 142857; + +-- Now you see it ... +SELECT * FROM fk_partitioned_fk WHERE b = 142857; +DELETE FROM fk_notpartitioned_pk WHERE b = 142857; +-- now you don't. +SELECT * FROM fk_partitioned_fk WHERE a = 142857; + +-- verify that DROP works +DROP TABLE fk_partitioned_fk_2; + +-- Test behavior of the constraint together with attaching and detaching +-- partitions. +CREATE TABLE fk_partitioned_fk_2 PARTITION OF fk_partitioned_fk FOR VALUES IN (1500,1502); +ALTER TABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_2; +-- TODO(YB): Uncomment when transactional DDLs are supported (#1404) +-- BEGIN; +-- DROP TABLE fk_partitioned_fk; +-- -- constraint should still be there +-- \d fk_partitioned_fk_2; +-- ROLLBACK; +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502); +DROP TABLE fk_partitioned_fk_2; +CREATE TABLE fk_partitioned_fk_2 (b int, c text, a int, + FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ON UPDATE CASCADE ON DELETE CASCADE); +ALTER TABLE fk_partitioned_fk_2 DROP COLUMN c; +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502); +-- should have only one constraint +\d fk_partitioned_fk_2 +DROP TABLE fk_partitioned_fk_2; + +CREATE TABLE fk_partitioned_fk_4 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE) PARTITION BY RANGE (b, a); +CREATE TABLE fk_partitioned_fk_4_1 PARTITION OF fk_partitioned_fk_4 FOR VALUES FROM (1,1) TO (100,100); +CREATE TABLE fk_partitioned_fk_4_2 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE SET NULL); +ALTER TABLE fk_partitioned_fk_4 ATTACH PARTITION fk_partitioned_fk_4_2 FOR VALUES FROM (100,100) TO (1000,1000); +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_4 FOR VALUES IN (3500,3502); +ALTER TABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_4; +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_4 FOR VALUES IN (3500,3502); +-- should only have one constraint +\d fk_partitioned_fk_4 +\d fk_partitioned_fk_4_1 +-- this one has an FK with mismatched properties +\d fk_partitioned_fk_4_2 + +CREATE TABLE fk_partitioned_fk_5 (a int, b int, + FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE, + FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE) + PARTITION BY RANGE (a); +CREATE TABLE fk_partitioned_fk_5_1 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk); +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_5 FOR VALUES IN (4500); +ALTER TABLE fk_partitioned_fk_5 ATTACH PARTITION fk_partitioned_fk_5_1 FOR VALUES FROM (0) TO (10); +ALTER TABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_5; +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_5 FOR VALUES IN (4500); +-- this one has two constraints, similar but not quite the one in the parent, +-- so it gets a new one +\d fk_partitioned_fk_5 +-- verify that it works to reattaching a child with multiple candidate +-- constraints +ALTER TABLE fk_partitioned_fk_5 DETACH PARTITION fk_partitioned_fk_5_1; +ALTER TABLE fk_partitioned_fk_5 ATTACH PARTITION fk_partitioned_fk_5_1 FOR VALUES FROM (0) TO (10); +\d fk_partitioned_fk_5_1 + +-- verify that attaching a table checks that the existing data satisfies the +-- constraint +CREATE TABLE fk_partitioned_fk_2 (a int, b int) PARTITION BY RANGE (b); +CREATE TABLE fk_partitioned_fk_2_1 PARTITION OF fk_partitioned_fk_2 FOR VALUES FROM (0) TO (1000); +CREATE TABLE fk_partitioned_fk_2_2 PARTITION OF fk_partitioned_fk_2 FOR VALUES FROM (1000) TO (2000); +INSERT INTO fk_partitioned_fk_2 VALUES (1600, 601), (1600, 1601); +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 + FOR VALUES IN (1600); +INSERT INTO fk_notpartitioned_pk VALUES (1600, 601), (1600, 1601); +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 + FOR VALUES IN (1600); + +-- leave these tables around intentionally + +-- Test creating a constraint at the parent that already exists in partitions. +-- There should be no duplicated constraints, and attempts to drop the +-- constraint in partitions should raise appropriate errors. +create schema fkpart0 + create table pkey (a int primary key) + create table fk_part (a int) partition by list (a) + create table fk_part_1 partition of fk_part + (foreign key (a) references fkpart0.pkey) for values in (1) + create table fk_part_23 partition of fk_part + (foreign key (a) references fkpart0.pkey) for values in (2, 3) + partition by list (a) + create table fk_part_23_2 partition of fk_part_23 for values in (2); +-- TODO(YB): Import remaining tests from postgres after creating schema is supported. +/* +alter table fkpart0.fk_part add foreign key (a) references fkpart0.pkey; +\d fkpart0.fk_part_1 \\ -- should have only one FK +alter table fkpart0.fk_part_1 drop constraint fk_part_1_a_fkey; + +\d fkpart0.fk_part_23 \\ -- should have only one FK +\d fkpart0.fk_part_23_2 \\ -- should have only one FK +alter table fkpart0.fk_part_23 drop constraint fk_part_23_a_fkey; +alter table fkpart0.fk_part_23_2 drop constraint fk_part_23_a_fkey; + +create table fkpart0.fk_part_4 partition of fkpart0.fk_part for values in (4); +\d fkpart0.fk_part_4 +alter table fkpart0.fk_part_4 drop constraint fk_part_a_fkey; + +create table fkpart0.fk_part_56 partition of fkpart0.fk_part + for values in (5,6) partition by list (a); +create table fkpart0.fk_part_56_5 partition of fkpart0.fk_part_56 + for values in (5); +\d fkpart0.fk_part_56 +alter table fkpart0.fk_part_56 drop constraint fk_part_a_fkey; +alter table fkpart0.fk_part_56_5 drop constraint fk_part_a_fkey; + +-- verify that attaching and detaching partitions maintains the right set of +-- triggers +create schema fkpart1 + create table pkey (a int primary key) + create table fk_part (a int) partition by list (a) + create table fk_part_1 partition of fk_part for values in (1) partition by list (a) + create table fk_part_1_1 partition of fk_part_1 for values in (1); +alter table fkpart1.fk_part add foreign key (a) references fkpart1.pkey; +insert into fkpart1.fk_part values (1); -- should fail +insert into fkpart1.pkey values (1); +insert into fkpart1.fk_part values (1); +delete from fkpart1.pkey where a = 1; -- should fail +alter table fkpart1.fk_part detach partition fkpart1.fk_part_1; +create table fkpart1.fk_part_1_2 partition of fkpart1.fk_part_1 for values in (2); +insert into fkpart1.fk_part_1 values (2); -- should fail +delete from fkpart1.pkey where a = 1; + +-- verify that attaching and detaching partitions manipulates the inheritance +-- properties of their FK constraints correctly +create schema fkpart2 + create table pkey (a int primary key) + create table fk_part (a int, constraint fkey foreign key (a) references fkpart2.pkey) partition by list (a) + create table fk_part_1 partition of fkpart2.fk_part for values in (1) partition by list (a) + create table fk_part_1_1 (a int, constraint my_fkey foreign key (a) references fkpart2.pkey); +alter table fkpart2.fk_part_1 attach partition fkpart2.fk_part_1_1 for values in (1); +alter table fkpart2.fk_part_1 drop constraint fkey; -- should fail +alter table fkpart2.fk_part_1_1 drop constraint my_fkey; -- should fail +alter table fkpart2.fk_part detach partition fkpart2.fk_part_1; +alter table fkpart2.fk_part_1 drop constraint fkey; -- ok +alter table fkpart2.fk_part_1_1 drop constraint my_fkey; -- doesn't exist + +\set VERBOSITY terse \\ -- suppress cascade details +drop schema fkpart0, fkpart1, fkpart2 cascade; +\set VERBOSITY default +*/ +-- TODO(jason): remove when issue #1721 is closed or closing. +DISCARD TEMP; + +-- Test a partitioned table as referenced table. + +-- Verify basic functionality with a regular partition creation and a partition +-- with a different column layout, as well as partitions added (created and +-- attached) after creating the foreign key. +CREATE SCHEMA fkpart3; +SET search_path TO fkpart3; + +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES FROM (0) TO (1000); +CREATE TABLE pk2 (b int, a int); +ALTER TABLE pk2 DROP COLUMN b; +ALTER TABLE pk2 ALTER a SET NOT NULL; +ALTER TABLE pk ATTACH PARTITION pk2 FOR VALUES FROM (1000) TO (2000); + +CREATE TABLE fk (a int) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (0) TO (750); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk; +CREATE TABLE fk2 (b int, a int) ; +ALTER TABLE fk2 DROP COLUMN b; +ALTER TABLE fk ATTACH PARTITION fk2 FOR VALUES FROM (750) TO (3500); + +CREATE TABLE pk3 PARTITION OF pk FOR VALUES FROM (2000) TO (3000); +CREATE TABLE pk4 (LIKE pk); +ALTER TABLE pk ATTACH PARTITION pk4 FOR VALUES FROM (3000) TO (4000); + +CREATE TABLE pk5 (c int, b int, a int NOT NULL) PARTITION BY RANGE (a); +ALTER TABLE pk5 DROP COLUMN b, DROP COLUMN c; +CREATE TABLE pk51 PARTITION OF pk5 FOR VALUES FROM (4000) TO (4500); +CREATE TABLE pk52 PARTITION OF pk5 FOR VALUES FROM (4500) TO (5000); +ALTER TABLE pk ATTACH PARTITION pk5 FOR VALUES FROM (4000) TO (5000); + +CREATE TABLE fk3 PARTITION OF fk FOR VALUES FROM (3500) TO (5000); + +-- these should fail: referenced value not present +INSERT into fk VALUES (1); +INSERT into fk VALUES (1000); +INSERT into fk VALUES (2000); +INSERT into fk VALUES (3000); +INSERT into fk VALUES (4000); +INSERT into fk VALUES (4500); +-- insert into the referenced table, now they should work +INSERT into pk VALUES (1), (1000), (2000), (3000), (4000), (4500); +INSERT into fk VALUES (1), (1000), (2000), (3000), (4000), (4500); + +-- should fail: referencing value present +DELETE FROM pk WHERE a = 1; +DELETE FROM pk WHERE a = 1000; +DELETE FROM pk WHERE a = 2000; +DELETE FROM pk WHERE a = 3000; +DELETE FROM pk WHERE a = 4000; +DELETE FROM pk WHERE a = 4500; +UPDATE pk SET a = 2 WHERE a = 1; +UPDATE pk SET a = 1002 WHERE a = 1000; +UPDATE pk SET a = 2002 WHERE a = 2000; +UPDATE pk SET a = 3002 WHERE a = 3000; +UPDATE pk SET a = 4002 WHERE a = 4000; +UPDATE pk SET a = 4502 WHERE a = 4500; +-- now they should work +DELETE FROM fk; +UPDATE pk SET a = 2 WHERE a = 1; +DELETE FROM pk WHERE a = 2; +UPDATE pk SET a = 1002 WHERE a = 1000; +DELETE FROM pk WHERE a = 1002; +UPDATE pk SET a = 2002 WHERE a = 2000; +DELETE FROM pk WHERE a = 2002; +UPDATE pk SET a = 3002 WHERE a = 3000; +DELETE FROM pk WHERE a = 3002; +UPDATE pk SET a = 4002 WHERE a = 4000; +DELETE FROM pk WHERE a = 4002; +UPDATE pk SET a = 4502 WHERE a = 4500; +DELETE FROM pk WHERE a = 4502; + +CREATE SCHEMA fkpart4; +SET search_path TO fkpart4; +-- dropping/detaching PARTITIONs is prevented if that would break +-- a foreign key's existing data +CREATE TABLE droppk (a int PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE droppk1 PARTITION OF droppk FOR VALUES FROM (0) TO (1000); +CREATE TABLE droppk_d PARTITION OF droppk DEFAULT; +CREATE TABLE droppk2 PARTITION OF droppk FOR VALUES FROM (1000) TO (2000) + PARTITION BY RANGE (a); +CREATE TABLE droppk21 PARTITION OF droppk2 FOR VALUES FROM (1000) TO (1400); +CREATE TABLE droppk2_d PARTITION OF droppk2 DEFAULT; +INSERT into droppk VALUES (1), (1000), (1500), (2000); +CREATE TABLE dropfk (a int REFERENCES droppk); +INSERT into dropfk VALUES (1), (1000), (1500), (2000); +-- these should all fail +ALTER TABLE droppk DETACH PARTITION droppk_d; +ALTER TABLE droppk2 DETACH PARTITION droppk2_d; +ALTER TABLE droppk DETACH PARTITION droppk1; +ALTER TABLE droppk DETACH PARTITION droppk2; +ALTER TABLE droppk2 DETACH PARTITION droppk21; +-- dropping partitions is disallowed +DROP TABLE droppk_d; +DROP TABLE droppk2_d; +DROP TABLE droppk1; +DROP TABLE droppk2; +DROP TABLE droppk21; +DELETE FROM dropfk; +-- dropping partitions is disallowed, even when no referencing values +DROP TABLE droppk_d; +DROP TABLE droppk2_d; +DROP TABLE droppk1; +-- but DETACH is allowed, and DROP afterwards works +ALTER TABLE droppk2 DETACH PARTITION droppk21; +DROP TABLE droppk2; + +-- Verify that initial constraint creation and cloning behave correctly +CREATE SCHEMA fkpart5; +SET search_path TO fkpart5; +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY LIST (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES IN (1) PARTITION BY LIST (a); +CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES IN (1); +CREATE TABLE fk (a int) PARTITION BY LIST (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES IN (1) PARTITION BY LIST (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES IN (1); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk; +CREATE TABLE pk2 PARTITION OF pk FOR VALUES IN (2); +CREATE TABLE pk3 (a int NOT NULL) PARTITION BY LIST (a); +CREATE TABLE pk31 PARTITION OF pk3 FOR VALUES IN (31); +CREATE TABLE pk32 (b int, a int NOT NULL); +ALTER TABLE pk32 DROP COLUMN b; +ALTER TABLE pk3 ATTACH PARTITION pk32 FOR VALUES IN (32); +ALTER TABLE pk ATTACH PARTITION pk3 FOR VALUES IN (31, 32); +CREATE TABLE fk2 PARTITION OF fk FOR VALUES IN (2); +CREATE TABLE fk3 (b int, a int); +ALTER TABLE fk3 DROP COLUMN b; +ALTER TABLE fk ATTACH PARTITION fk3 FOR VALUES IN (3); +SELECT pg_describe_object('pg_constraint'::regclass, oid, 0), confrelid::regclass, + CASE WHEN conparentid <> 0 THEN pg_describe_object('pg_constraint'::regclass, conparentid, 0) ELSE 'TOP' END +FROM pg_catalog.pg_constraint +WHERE conrelid IN (SELECT relid FROM pg_partition_tree('fk')) +ORDER BY conrelid::regclass::text, conname; +CREATE TABLE fk4 (LIKE fk); +INSERT INTO fk4 VALUES (50); +ALTER TABLE fk ATTACH PARTITION fk4 FOR VALUES IN (50); + +-- Verify constraint deferrability +CREATE SCHEMA fkpart9; +SET search_path TO fkpart9; +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY LIST (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES IN (1, 2) PARTITION BY LIST (a); +CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES IN (1); +CREATE TABLE pk3 PARTITION OF pk FOR VALUES IN (3); +CREATE TABLE fk (a int REFERENCES pk DEFERRABLE INITIALLY IMMEDIATE); +INSERT INTO fk VALUES (1); -- should fail +BEGIN; +SET CONSTRAINTS fk_a_fkey DEFERRED; +INSERT INTO fk VALUES (1); +COMMIT; -- should fail +BEGIN; +SET CONSTRAINTS fk_a_fkey DEFERRED; +INSERT INTO fk VALUES (1); +INSERT INTO pk VALUES (1); +COMMIT; -- OK +BEGIN; +SET CONSTRAINTS fk_a_fkey DEFERRED; +DELETE FROM pk WHERE a = 1; +DELETE FROM fk WHERE a = 1; +COMMIT; -- OK + +-- Verify constraint deferrability when changed by ALTER +-- Partitioned table at referencing end +CREATE TABLE pt(f1 int, f2 int, f3 int, PRIMARY KEY(f1,f2)); +CREATE TABLE ref(f1 int, f2 int, f3 int) + PARTITION BY list(f1); +CREATE TABLE ref1 PARTITION OF ref FOR VALUES IN (1); +CREATE TABLE ref2 PARTITION OF ref FOR VALUES in (2); +ALTER TABLE ref ADD FOREIGN KEY(f1,f2) REFERENCES pt; +ALTER TABLE ref ALTER CONSTRAINT ref_f1_f2_fkey + DEFERRABLE INITIALLY DEFERRED; +INSERT INTO pt VALUES(1,2,3); +INSERT INTO ref VALUES(1,2,3); +BEGIN; +DELETE FROM pt; +DELETE FROM ref; +ABORT; +DROP TABLE pt, ref; +-- Multi-level partitioning at referencing end +CREATE TABLE pt(f1 int, f2 int, f3 int, PRIMARY KEY(f1,f2)); +CREATE TABLE ref(f1 int, f2 int, f3 int) + PARTITION BY list(f1); +CREATE TABLE ref1_2 PARTITION OF ref FOR VALUES IN (1, 2) PARTITION BY list (f2); +CREATE TABLE ref1 PARTITION OF ref1_2 FOR VALUES IN (1); +CREATE TABLE ref2 PARTITION OF ref1_2 FOR VALUES IN (2) PARTITION BY list (f2); +CREATE TABLE ref22 PARTITION OF ref2 FOR VALUES IN (2); +ALTER TABLE ref ADD FOREIGN KEY(f1,f2) REFERENCES pt; +INSERT INTO pt VALUES(1,2,3); +INSERT INTO ref VALUES(1,2,3); +ALTER TABLE ref22 ALTER CONSTRAINT ref_f1_f2_fkey + DEFERRABLE INITIALLY IMMEDIATE; -- fails +ALTER TABLE ref ALTER CONSTRAINT ref_f1_f2_fkey + DEFERRABLE INITIALLY DEFERRED; +BEGIN; +DELETE FROM pt; +DELETE FROM ref; +ABORT; +DROP TABLE pt, ref; + +-- Partitioned table at referenced end +CREATE TABLE pt(f1 int, f2 int, f3 int, PRIMARY KEY(f1,f2)) + PARTITION BY LIST(f1); +CREATE TABLE pt1 PARTITION OF pt FOR VALUES IN (1); +CREATE TABLE pt2 PARTITION OF pt FOR VALUES IN (2); +CREATE TABLE ref(f1 int, f2 int, f3 int); +ALTER TABLE ref ADD FOREIGN KEY(f1,f2) REFERENCES pt; +ALTER TABLE ref ALTER CONSTRAINT ref_f1_f2_fkey + DEFERRABLE INITIALLY DEFERRED; +INSERT INTO pt VALUES(1,2,3); +INSERT INTO ref VALUES(1,2,3); +BEGIN; +DELETE FROM pt; +DELETE FROM ref; +ABORT; +DROP TABLE pt, ref; +-- Multi-level partitioning at at referenced end +CREATE TABLE pt(f1 int, f2 int, f3 int, PRIMARY KEY(f1,f2)) + PARTITION BY LIST(f1); +CREATE TABLE pt1_2 PARTITION OF pt FOR VALUES IN (1, 2) PARTITION BY LIST (f1); +CREATE TABLE pt1 PARTITION OF pt1_2 FOR VALUES IN (1); +CREATE TABLE pt2 PARTITION OF pt1_2 FOR VALUES IN (2); +CREATE TABLE ref(f1 int, f2 int, f3 int); +ALTER TABLE ref ADD FOREIGN KEY(f1,f2) REFERENCES pt; +ALTER TABLE ref ALTER CONSTRAINT ref_f1_f2_fkey1 + DEFERRABLE INITIALLY DEFERRED; -- fails +ALTER TABLE ref ALTER CONSTRAINT ref_f1_f2_fkey + DEFERRABLE INITIALLY DEFERRED; +INSERT INTO pt VALUES(1,2,3); +INSERT INTO ref VALUES(1,2,3); +BEGIN; +DELETE FROM pt; +DELETE FROM ref; +ABORT; +DROP TABLE pt, ref; + +DROP SCHEMA fkpart9 CASCADE; + +-- Verify ON UPDATE/DELETE behavior +CREATE SCHEMA fkpart6; +SET search_path TO fkpart6; +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES FROM (1) TO (50); +CREATE TABLE pk12 PARTITION OF pk1 FOR VALUES FROM (50) TO (100); +CREATE TABLE fk (a int) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE CASCADE ON DELETE CASCADE; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO pk VALUES (1); +INSERT INTO fk VALUES (1); +UPDATE pk SET a = 20; +SELECT tableoid::regclass, * FROM fk; +DELETE FROM pk WHERE a = 20; +SELECT tableoid::regclass, * FROM fk; +DROP TABLE fk; + +TRUNCATE TABLE pk; +INSERT INTO pk VALUES (20), (50); +CREATE TABLE fk (a int) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE SET NULL ON DELETE SET NULL; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO fk VALUES (20), (50); +UPDATE pk SET a = 21 WHERE a = 20; +DELETE FROM pk WHERE a = 50; +SELECT tableoid::regclass, * FROM fk; +DROP TABLE fk; + +TRUNCATE TABLE pk; +INSERT INTO pk VALUES (20), (30), (50); +CREATE TABLE fk (id int, a int DEFAULT 50) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE SET DEFAULT ON DELETE SET DEFAULT; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO fk VALUES (1, 20), (2, 30); +DELETE FROM pk WHERE a = 20 RETURNING *; +UPDATE pk SET a = 90 WHERE a = 30 RETURNING *; +SELECT tableoid::regclass, * FROM fk; +DROP TABLE fk; + +TRUNCATE TABLE pk; +INSERT INTO pk VALUES (20), (30); +CREATE TABLE fk (a int DEFAULT 50) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE RESTRICT ON DELETE RESTRICT; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO fk VALUES (20), (30); +DELETE FROM pk WHERE a = 20; +UPDATE pk SET a = 90 WHERE a = 30; +SELECT tableoid::regclass, * FROM fk; +DROP TABLE fk; diff --git a/src/test/regress/sql/yb.port.generated.sql b/src/test/regress/sql/yb.port.generated.sql new file mode 100644 index 0000000000000..0a1afdd884d11 --- /dev/null +++ b/src/test/regress/sql/yb.port.generated.sql @@ -0,0 +1,601 @@ +-- sanity check of system catalog +SELECT attrelid, attname, attgenerated FROM pg_attribute WHERE attgenerated NOT IN ('', 's'); + + +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; + +SELECT table_name, column_name, dependent_column FROM information_schema.column_column_usage ORDER BY 1, 2, 3; + +\d gtest1 + +-- duplicate generated +CREATE TABLE gtest_err_1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ALWAYS AS (a * 3) STORED); + +-- references to other generated columns, including self-references +CREATE TABLE gtest_err_2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STORED); +CREATE TABLE gtest_err_2b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STORED); +-- a whole-row var is a self-reference on steroids, so disallow that too +CREATE TABLE gtest_err_2c (a int PRIMARY KEY, + b int GENERATED ALWAYS AS (num_nulls(gtest_err_2c)) STORED); + +-- invalid reference +CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED); + +-- generation expression must be immutable +CREATE TABLE gtest_err_4 (a int PRIMARY KEY, b double precision GENERATED ALWAYS AS (random()) STORED); + +-- cannot have default/identity and generated +CREATE TABLE gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ALWAYS AS (a * 2) STORED); +CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2) STORED); + +-- reference to system column not allowed in generated column +-- (except tableoid, which we test below) +CREATE TABLE gtest_err_6a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37) STORED); -- YB note: xmin column not supported in YB + +-- various prohibited constructs +CREATE TABLE gtest_err_7a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (avg(a)) STORED); +CREATE TABLE gtest_err_7b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (row_number() OVER (ORDER BY a)) STORED); +CREATE TABLE gtest_err_7c (a int PRIMARY KEY, b int GENERATED ALWAYS AS ((SELECT a)) STORED); +CREATE TABLE gtest_err_7d (a int PRIMARY KEY, b int GENERATED ALWAYS AS (generate_series(1, a)) STORED); + +-- GENERATED BY DEFAULT not allowed +CREATE TABLE gtest_err_8 (a int PRIMARY KEY, b int GENERATED BY DEFAULT AS (a * 2) STORED); + +INSERT INTO gtest1 VALUES (1); +INSERT INTO gtest1 VALUES (2, DEFAULT); -- ok +INSERT INTO gtest1 VALUES (3, 33); -- error +INSERT INTO gtest1 VALUES (3, 33), (4, 44); -- error +INSERT INTO gtest1 VALUES (3, DEFAULT), (4, 44); -- error +INSERT INTO gtest1 VALUES (3, 33), (4, DEFAULT); -- error +INSERT INTO gtest1 VALUES (3, DEFAULT), (4, DEFAULT); -- ok + +SELECT * FROM gtest1 ORDER BY a; +DELETE FROM gtest1 WHERE a >= 3; + +UPDATE gtest1 SET b = DEFAULT WHERE a = 1; +UPDATE gtest1 SET b = 11 WHERE a = 1; -- error + +SELECT * FROM gtest1 ORDER BY a; + +SELECT a, b, b * 2 AS b2 FROM gtest1 ORDER BY a; +SELECT a, b FROM gtest1 WHERE b = 4 ORDER BY a; + +-- test that overflow error happens on write +INSERT INTO gtest1 VALUES (2000000000); +SELECT * FROM gtest1 ORDER BY 1 DESC; -- YB: add ordering +DELETE FROM gtest1 WHERE a = 2000000000; + +-- test with joins +CREATE TABLE gtestx (x int, y int); +INSERT INTO gtestx VALUES (11, 1), (22, 2), (33, 3); +SELECT * FROM gtestx, gtest1 WHERE gtestx.y = gtest1.a; +DROP TABLE gtestx; + +-- test UPDATE/DELETE quals +SELECT * FROM gtest1 ORDER BY a; +UPDATE gtest1 SET a = 3 WHERE b = 4; +SELECT * FROM gtest1 ORDER BY a; +DELETE FROM gtest1 WHERE b = 2; +SELECT * FROM gtest1 ORDER BY a; + +-- test MERGE +CREATE TABLE gtestm ( + id int PRIMARY KEY, + f1 int, + f2 int, + f3 int GENERATED ALWAYS AS (f1 * 2) STORED, + f4 int GENERATED ALWAYS AS (f2 * 2) STORED +); +INSERT INTO gtestm VALUES (1, 5, 100); +MERGE INTO gtestm t USING (VALUES (1, 10), (2, 20)) v(id, f1) ON t.id = v.id + WHEN MATCHED THEN UPDATE SET f1 = v.f1 + WHEN NOT MATCHED THEN INSERT VALUES (v.id, v.f1, 200); +-- YB note: port additional tests once MERGE is supported. +DROP TABLE gtestm; + +-- views +CREATE VIEW gtest1v AS SELECT * FROM gtest1; +SELECT * FROM gtest1v; +INSERT INTO gtest1v VALUES (4, 8); -- error +INSERT INTO gtest1v VALUES (5, DEFAULT); -- ok +INSERT INTO gtest1v VALUES (6, 66), (7, 77); -- error +INSERT INTO gtest1v VALUES (6, DEFAULT), (7, 77); -- error +INSERT INTO gtest1v VALUES (6, 66), (7, DEFAULT); -- error +INSERT INTO gtest1v VALUES (6, DEFAULT), (7, DEFAULT); -- ok + +ALTER VIEW gtest1v ALTER COLUMN b SET DEFAULT 100; +INSERT INTO gtest1v VALUES (8, DEFAULT); -- error +INSERT INTO gtest1v VALUES (8, DEFAULT), (9, DEFAULT); -- error + +SELECT * FROM gtest1v; +DELETE FROM gtest1v WHERE a >= 5; +DROP VIEW gtest1v; + +-- CTEs +WITH foo AS (SELECT * FROM gtest1) SELECT * FROM foo; + +-- inheritance +CREATE TABLE gtest1_1 () INHERITS (gtest1); +SELECT * FROM gtest1_1; +\d gtest1_1 +INSERT INTO gtest1_1 VALUES (4); +SELECT * FROM gtest1_1; +SELECT * FROM gtest1; + +CREATE TABLE gtest_normal (a int, b int); +CREATE TABLE gtest_normal_child (a int, b int GENERATED ALWAYS AS (a * 2) STORED) INHERITS (gtest_normal); +\d gtest_normal_child +INSERT INTO gtest_normal (a) VALUES (1); +INSERT INTO gtest_normal_child (a) VALUES (2); +SELECT * FROM gtest_normal; + +CREATE TABLE gtest_normal_child2 (a int, b int GENERATED ALWAYS AS (a * 3) STORED); +ALTER TABLE gtest_normal_child2 INHERIT gtest_normal; +INSERT INTO gtest_normal_child2 (a) VALUES (3); +SELECT * FROM gtest_normal; + +-- test inheritance mismatches between parent and child +CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS (a * 22) STORED) INHERITS (gtest1); -- error +CREATE TABLE gtestx (x int, b int DEFAULT 10) INHERITS (gtest1); -- error +CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS IDENTITY) INHERITS (gtest1); -- error + +CREATE TABLE gtestxx_1 (a int NOT NULL, b int); +ALTER TABLE gtestxx_1 INHERIT gtest1; -- error +CREATE TABLE gtestxx_2 (a int NOT NULL, b int GENERATED ALWAYS AS (a * 22) STORED); +ALTER TABLE gtestxx_2 INHERIT gtest1; -- error +CREATE TABLE gtestxx_3 (a int NOT NULL, b int GENERATED ALWAYS AS (a * 2) STORED); +ALTER TABLE gtestxx_3 INHERIT gtest1; -- ok +CREATE TABLE gtestxx_4 (b int GENERATED ALWAYS AS (a * 2) STORED, a int NOT NULL); +ALTER TABLE gtestxx_4 INHERIT gtest1; -- ok + +-- test multiple inheritance mismatches +CREATE TABLE gtesty (x int, b int); +CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty); -- error +DROP TABLE gtesty; + +CREATE TABLE gtesty (x int, b int GENERATED ALWAYS AS (x * 22) STORED); +CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty); -- error +DROP TABLE gtesty; + +CREATE TABLE gtesty (x int, b int DEFAULT 55); +CREATE TABLE gtest1_2 () INHERITS (gtest0, gtesty); -- error +DROP TABLE gtesty; + +-- test correct handling of GENERATED column that's only in child +CREATE TABLE gtestp (f1 int); +CREATE TABLE gtestc (f2 int GENERATED ALWAYS AS (f1+1) STORED) INHERITS(gtestp); +INSERT INTO gtestc values(42); +TABLE gtestc; +UPDATE gtestp SET f1 = f1 * 10; +TABLE gtestc; +DROP TABLE gtestp CASCADE; + +-- test stored update +CREATE TABLE gtest3 (a int, b int GENERATED ALWAYS AS (a * 3) STORED); +INSERT INTO gtest3 (a) VALUES (1), (2), (3), (NULL); +SELECT * FROM gtest3 ORDER BY a; +UPDATE gtest3 SET a = 22 WHERE a = 2; +SELECT * FROM gtest3 ORDER BY a; + +CREATE TABLE gtest3a (a text, b text GENERATED ALWAYS AS (a || '+' || a) STORED); +INSERT INTO gtest3a (a) VALUES ('a'), ('b'), ('c'), (NULL); +SELECT * FROM gtest3a ORDER BY a; +UPDATE gtest3a SET a = 'bb' WHERE a = 'b'; +SELECT * FROM gtest3a ORDER BY a; + +-- COPY +TRUNCATE gtest1; +INSERT INTO gtest1 (a) VALUES (1), (2); + +COPY gtest1 TO stdout; + +COPY gtest1 (a, b) TO stdout; + +COPY gtest1 FROM stdin; +3 +4 +\. + +COPY gtest1 (a, b) FROM stdin; + +SELECT * FROM gtest1 ORDER BY a; + +TRUNCATE gtest3; +INSERT INTO gtest3 (a) VALUES (1), (2); + +COPY gtest3 TO stdout; + +COPY gtest3 (a, b) TO stdout; + +COPY gtest3 FROM stdin; +3 +4 +\. + +COPY gtest3 (a, b) FROM stdin; + +SELECT * FROM gtest3 ORDER BY a; + +-- null values +CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED); +INSERT INTO gtest2 VALUES (1); +SELECT * FROM gtest2; + +-- simple column reference for varlena types +CREATE TABLE gtest_varlena (a varchar, b varchar GENERATED ALWAYS AS (a) STORED); +INSERT INTO gtest_varlena (a) VALUES('01234567890123456789'); +INSERT INTO gtest_varlena (a) VALUES(NULL); +SELECT * FROM gtest_varlena ORDER BY a; +DROP TABLE gtest_varlena; + +-- composite types +CREATE TYPE double_int as (a int, b int); +CREATE TABLE gtest4 ( + a int, + b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) STORED +); +INSERT INTO gtest4 VALUES (1), (6); +SELECT * FROM gtest4; + +DROP TABLE gtest4; +DROP TYPE double_int; + +-- using tableoid is allowed +CREATE TABLE gtest_tableoid ( + a int PRIMARY KEY, + b bool GENERATED ALWAYS AS (tableoid = 'gtest_tableoid'::regclass) STORED +); +INSERT INTO gtest_tableoid VALUES (1), (2); +ALTER TABLE gtest_tableoid ADD COLUMN + c regclass GENERATED ALWAYS AS (tableoid) STORED; +SELECT * FROM gtest_tableoid; + +-- drop column behavior +CREATE TABLE gtest10 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); +ALTER TABLE gtest10 DROP COLUMN b; -- fails +ALTER TABLE gtest10 DROP COLUMN b CASCADE; -- drops c too + +\d gtest10 + +CREATE TABLE gtest10a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +ALTER TABLE gtest10a DROP COLUMN b; +INSERT INTO gtest10a (a) VALUES (1); + +-- privileges +CREATE USER regress_user11; + +CREATE TABLE gtest11s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); +INSERT INTO gtest11s VALUES (1, 10), (2, 20); +GRANT SELECT (a, c) ON gtest11s TO regress_user11; + +CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL; +REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC; + +CREATE TABLE gtest12s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) STORED); +INSERT INTO gtest12s VALUES (1, 10), (2, 20); +GRANT SELECT (a, c) ON gtest12s TO regress_user11; + +SET ROLE regress_user11; +SELECT a, b FROM gtest11s; -- not allowed +SELECT a, c FROM gtest11s; -- allowed +SELECT gf1(10); -- not allowed +SELECT a, c FROM gtest12s; -- allowed +RESET ROLE; + +DROP FUNCTION gf1(int); -- fail +DROP TABLE gtest11s, gtest12s; +DROP FUNCTION gf1(int); +DROP USER regress_user11; + +-- check constraints +CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED CHECK (b < 50)); +INSERT INTO gtest20 (a) VALUES (10); -- ok +INSERT INTO gtest20 (a) VALUES (30); -- violates constraint + +CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest20a (a) VALUES (10); +INSERT INTO gtest20a (a) VALUES (30); +ALTER TABLE gtest20a ADD CHECK (b < 50); -- fails on existing row + +CREATE TABLE gtest20b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest20b (a) VALUES (10); +INSERT INTO gtest20b (a) VALUES (30); +ALTER TABLE gtest20b ADD CONSTRAINT chk CHECK (b < 50) NOT VALID; +ALTER TABLE gtest20b VALIDATE CONSTRAINT chk; -- fails on existing row + +-- not-null constraints +CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED NOT NULL); +INSERT INTO gtest21a (a) VALUES (1); -- ok +INSERT INTO gtest21a (a) VALUES (0); -- violates constraint + +CREATE TABLE gtest21b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED); +ALTER TABLE gtest21b ALTER COLUMN b SET NOT NULL; +INSERT INTO gtest21b (a) VALUES (1); -- ok +INSERT INTO gtest21b (a) VALUES (0); -- violates constraint +ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL; +INSERT INTO gtest21b (a) VALUES (0); -- ok now + +-- index constraints +CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) STORED UNIQUE); +INSERT INTO gtest22a VALUES (2); +INSERT INTO gtest22a VALUES (3); +INSERT INTO gtest22a VALUES (4); +CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) STORED, PRIMARY KEY (a, b)); +INSERT INTO gtest22b VALUES (2); +INSERT INTO gtest22b VALUES (2); + +-- indexes +CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +CREATE INDEX gtest22c_b_idx ON gtest22c (b); +CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); +CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; +\d gtest22c + +INSERT INTO gtest22c VALUES (1), (2), (3); +SET enable_seqscan TO off; +SET enable_bitmapscan TO off; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4; +SELECT * FROM gtest22c WHERE b = 4; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6; +SELECT * FROM gtest22c WHERE b * 3 = 6; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; +SELECT * FROM gtest22c WHERE a = 1 AND b > 0; +RESET enable_seqscan; +RESET enable_bitmapscan; + +-- foreign keys +CREATE TABLE gtest23a (x int PRIMARY KEY, y int); +INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33); + +CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON UPDATE CASCADE); -- error +CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON DELETE SET NULL); -- error + +CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x)); +\d gtest23b + +INSERT INTO gtest23b VALUES (1); -- ok +INSERT INTO gtest23b VALUES (5); -- error + +DROP TABLE gtest23b; +DROP TABLE gtest23a; + +CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) STORED, PRIMARY KEY (y)); +INSERT INTO gtest23p VALUES (1), (2), (3); + +CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y)); +INSERT INTO gtest23q VALUES (1, 2); -- ok +INSERT INTO gtest23q VALUES (2, 5); -- error + +-- domains +CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10); +CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest24 (a) VALUES (4); -- ok +INSERT INTO gtest24 (a) VALUES (6); -- error + +-- typed tables (currently not supported) +CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint); +CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED); +DROP TYPE gtest_type CASCADE; + +-- table partitions (currently not supported) +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); +CREATE TABLE gtest_child PARTITION OF gtest_parent ( + f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 2) STORED +) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error +DROP TABLE gtest_parent; + +-- partitioned table +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f1); +CREATE TABLE gtest_child PARTITION OF gtest_parent FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1); +SELECT * FROM gtest_parent; +SELECT * FROM gtest_child; +DROP TABLE gtest_parent; + +-- generated columns in partition key (not allowed) +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); + +-- ALTER TABLE ... ADD COLUMN +CREATE TABLE gtest25 (a int PRIMARY KEY); +INSERT INTO gtest25 VALUES (3), (4); +ALTER TABLE gtest25 ADD COLUMN b int GENERATED ALWAYS AS (a * 3) STORED; +SELECT * FROM gtest25 ORDER BY a; +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED; -- error +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error +ALTER TABLE gtest25 ADD COLUMN c int DEFAULT 42, + ADD COLUMN x int GENERATED ALWAYS AS (c * 4) STORED; +ALTER TABLE gtest25 ADD COLUMN d int DEFAULT 101; +ALTER TABLE gtest25 ALTER COLUMN d SET DATA TYPE float8, + ADD COLUMN y float8 GENERATED ALWAYS AS (d * 4) STORED; +SELECT * FROM gtest25 ORDER BY a; +\d gtest25 + +-- ALTER TABLE ... ALTER COLUMN +CREATE TABLE gtest27 ( + a int, + b int, + x int GENERATED ALWAYS AS ((a + b) * 2) STORED +); +INSERT INTO gtest27 (a, b) VALUES (3, 7), (4, 11); +ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error +ALTER TABLE gtest27 ALTER COLUMN x TYPE numeric; +\d gtest27 +SELECT * FROM gtest27 order by a; -- YB: add ordering +ALTER TABLE gtest27 ALTER COLUMN x TYPE boolean USING x <> 0; -- error +ALTER TABLE gtest27 ALTER COLUMN x DROP DEFAULT; -- error +-- It's possible to alter the column types this way: +ALTER TABLE gtest27 + DROP COLUMN x, + ALTER COLUMN a TYPE bigint, + ALTER COLUMN b TYPE bigint, + ADD COLUMN x bigint GENERATED ALWAYS AS ((a + b) * 2) STORED; +-- YB note: import additional tests once the above is supported. + +-- ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION +CREATE TABLE gtest29 ( + a int, + b int GENERATED ALWAYS AS (a * 2) STORED +); +INSERT INTO gtest29 (a) VALUES (3), (4); +ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION; -- error +ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION IF EXISTS; -- notice +ALTER TABLE gtest29 ALTER COLUMN b DROP EXPRESSION; +INSERT INTO gtest29 (a) VALUES (5); +INSERT INTO gtest29 (a, b) VALUES (6, 66); +SELECT * FROM gtest29; +\d gtest29 + +-- check that dependencies between columns have also been removed +ALTER TABLE gtest29 DROP COLUMN a; -- should not drop b +\d gtest29 + +-- with inheritance +CREATE TABLE gtest30 ( + a int, + b int GENERATED ALWAYS AS (a * 2) STORED +); +CREATE TABLE gtest30_1 () INHERITS (gtest30); +ALTER TABLE gtest30 ALTER COLUMN b DROP EXPRESSION; +\d gtest30 +\d gtest30_1 +DROP TABLE gtest30 CASCADE; +CREATE TABLE gtest30 ( + a int, + b int GENERATED ALWAYS AS (a * 2) STORED +); +CREATE TABLE gtest30_1 () INHERITS (gtest30); +ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; -- error +\d gtest30 +\d gtest30_1 +ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error + +-- triggers +CREATE TABLE gtest26 ( + a int PRIMARY KEY, + b int GENERATED ALWAYS AS (a * 2) STORED +); + +CREATE FUNCTION gtest_trigger_func() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + IF tg_op IN ('DELETE', 'UPDATE') THEN + RAISE INFO '%: %: old = %', TG_NAME, TG_WHEN, OLD; + END IF; + IF tg_op IN ('INSERT', 'UPDATE') THEN + RAISE INFO '%: %: new = %', TG_NAME, TG_WHEN, NEW; + END IF; + IF tg_op = 'DELETE' THEN + RETURN OLD; + ELSE + RETURN NEW; + END IF; +END +$$; + +CREATE TRIGGER gtest1 BEFORE DELETE OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (OLD.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest2a BEFORE INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.b < 0) -- error + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest2b BEFORE INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.* IS NOT NULL) -- error + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest2 BEFORE INSERT ON gtest26 + FOR EACH ROW + WHEN (NEW.a < 0) + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest3 AFTER DELETE OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (OLD.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest4 AFTER INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); + +INSERT INTO gtest26 (a) VALUES (-2), (0), (3); +SELECT * FROM gtest26 ORDER BY a; +UPDATE gtest26 SET a = a * -2; +SELECT * FROM gtest26 ORDER BY a; +DELETE FROM gtest26 WHERE a = -6; +SELECT * FROM gtest26 ORDER BY a; + +DROP TRIGGER gtest1 ON gtest26; +DROP TRIGGER gtest2 ON gtest26; +DROP TRIGGER gtest3 ON gtest26; + +-- Check that an UPDATE of "a" fires the trigger for UPDATE OF b, per +-- SQL standard. +CREATE FUNCTION gtest_trigger_func3() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + RAISE NOTICE 'OK'; + RETURN NEW; +END +$$; + +CREATE TRIGGER gtest11 BEFORE UPDATE OF b ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func3(); + +UPDATE gtest26 SET a = 1 WHERE a = 0; + +DROP TRIGGER gtest11 ON gtest26; +TRUNCATE gtest26; + +-- check that modifications of stored generated columns in triggers do +-- not get propagated +CREATE FUNCTION gtest_trigger_func4() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + NEW.a = 10; + NEW.b = 300; + RETURN NEW; +END; +$$; + +CREATE TRIGGER gtest12_01 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest12_02 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func4(); + +CREATE TRIGGER gtest12_03 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func(); + +INSERT INTO gtest26 (a) VALUES (1); +UPDATE gtest26 SET a = 11 WHERE a = 1; +SELECT * FROM gtest26 ORDER BY a; + +-- LIKE INCLUDING GENERATED and dropped column handling +CREATE TABLE gtest28a ( + a int, + b int, + c int, + x int GENERATED ALWAYS AS (b * 2) STORED +); + +ALTER TABLE gtest28a DROP COLUMN a; + +CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED); + +\d gtest28* diff --git a/src/test/regress/sql/yb.port.geometry.sql b/src/test/regress/sql/yb.port.geometry.sql new file mode 100644 index 0000000000000..86b642a17d065 --- /dev/null +++ b/src/test/regress/sql/yb.port.geometry.sql @@ -0,0 +1,539 @@ +-- +-- GEOMETRY +-- + +-- Back off displayed precision a little bit to reduce platform-to-platform +-- variation in results. +SET extra_float_digits TO -3; + +-- +-- Points +-- + +SELECT center(f1) AS center + FROM BOX_TBL; + +SELECT (@@ f1) AS center + FROM BOX_TBL; + +SELECT point(f1) AS center + FROM CIRCLE_TBL; + +SELECT (@@ f1) AS center + FROM CIRCLE_TBL; + +SELECT (@@ f1) AS center + FROM POLYGON_TBL + WHERE (# f1) > 2; + +-- "is horizontal" function +SELECT p1.f1 + FROM POINT_TBL p1 + WHERE ishorizontal(p1.f1, point '(0,0)'); + +-- "is horizontal" operator +SELECT p1.f1 + FROM POINT_TBL p1 + WHERE p1.f1 ?- point '(0,0)'; + +-- "is vertical" function +SELECT p1.f1 + FROM POINT_TBL p1 + WHERE isvertical(p1.f1, point '(5.1,34.5)'); + +-- "is vertical" operator +SELECT p1.f1 + FROM POINT_TBL p1 + WHERE p1.f1 ?| point '(5.1,34.5)'; + +-- Slope +SELECT p1.f1, p2.f1, slope(p1.f1, p2.f1) FROM POINT_TBL p1, POINT_TBL p2; + +-- Add point +SELECT p1.f1, p2.f1, p1.f1 + p2.f1 FROM POINT_TBL p1, POINT_TBL p2; + +-- Subtract point +SELECT p1.f1, p2.f1, p1.f1 - p2.f1 FROM POINT_TBL p1, POINT_TBL p2; + +-- Multiply with point +/*+Leading((p2 p1))*/ -- YB hint +SELECT p1.f1, p2.f1, p1.f1 * p2.f1 FROM POINT_TBL p1, POINT_TBL p2 WHERE p1.f1[0] BETWEEN 1 AND 1000; + +-- Underflow error +SELECT p1.f1, p2.f1, p1.f1 * p2.f1 FROM POINT_TBL p1, POINT_TBL p2 WHERE p1.f1[0] < 1; + +-- Divide by point +SELECT p1.f1, p2.f1, p1.f1 / p2.f1 FROM POINT_TBL p1, POINT_TBL p2 WHERE p2.f1[0] BETWEEN 1 AND 1000; + +-- Overflow error +SELECT p1.f1, p2.f1, p1.f1 / p2.f1 FROM POINT_TBL p1, POINT_TBL p2 WHERE p2.f1[0] > 1000; + +-- Division by 0 error +SELECT p1.f1, p2.f1, p1.f1 / p2.f1 FROM POINT_TBL p1, POINT_TBL p2 WHERE p2.f1 ~= '(0,0)'::point; + +-- Distance to line +SELECT p.f1, l.s, p.f1 <-> l.s AS dist_pl, l.s <-> p.f1 AS dist_lp FROM POINT_TBL p, LINE_TBL l; + +-- Distance to line segment +SELECT p.f1, l.s, p.f1 <-> l.s AS dist_ps, l.s <-> p.f1 AS dist_sp FROM POINT_TBL p, LSEG_TBL l; + +-- Distance to box +SELECT p.f1, b.f1, p.f1 <-> b.f1 AS dist_pb, b.f1 <-> p.f1 AS dist_bp FROM POINT_TBL p, BOX_TBL b; + +-- Distance to path +SELECT p.f1, p1.f1, p.f1 <-> p1.f1 AS dist_ppath, p1.f1 <-> p.f1 AS dist_pathp FROM POINT_TBL p, PATH_TBL p1; + +-- Distance to polygon +SELECT p.f1, p1.f1, p.f1 <-> p1.f1 AS dist_ppoly, p1.f1 <-> p.f1 AS dist_polyp FROM POINT_TBL p, POLYGON_TBL p1; + +-- Construct line through two points +SELECT p1.f1, p2.f1, line(p1.f1, p2.f1) + FROM POINT_TBL p1, POINT_TBL p2 WHERE p1.f1 <> p2.f1; + +-- Closest point to line +SELECT p.f1, l.s, p.f1 ## l.s FROM POINT_TBL p, LINE_TBL l; + +-- Closest point to line segment +SELECT p.f1, l.s, p.f1 ## l.s FROM POINT_TBL p, LSEG_TBL l; + +-- Closest point to box +SELECT p.f1, b.f1, p.f1 ## b.f1 FROM POINT_TBL p, BOX_TBL b; + +-- On line +SELECT p.f1, l.s FROM POINT_TBL p, LINE_TBL l WHERE p.f1 <@ l.s; + +-- On line segment +SELECT p.f1, l.s FROM POINT_TBL p, LSEG_TBL l WHERE p.f1 <@ l.s; + +-- On path +SELECT p.f1, p1.f1 FROM POINT_TBL p, PATH_TBL p1 WHERE p.f1 <@ p1.f1; + +-- +-- Lines +-- + +-- Vertical +SELECT s FROM LINE_TBL WHERE ?| s; + +-- Horizontal +SELECT s FROM LINE_TBL WHERE ?- s; + +-- Same as line +SELECT l1.s, l2.s FROM LINE_TBL l1, LINE_TBL l2 WHERE l1.s = l2.s; + +-- Parallel to line +SELECT l1.s, l2.s FROM LINE_TBL l1, LINE_TBL l2 WHERE l1.s ?|| l2.s; + +-- Perpendicular to line +SELECT l1.s, l2.s FROM LINE_TBL l1, LINE_TBL l2 WHERE l1.s ?-| l2.s; + +-- Distance to line +SELECT l1.s, l2.s, l1.s <-> l2.s FROM LINE_TBL l1, LINE_TBL l2; + +-- Intersect with line +SELECT l1.s, l2.s FROM LINE_TBL l1, LINE_TBL l2 WHERE l1.s ?# l2.s; + +-- Intersect with box +SELECT l.s, b.f1 FROM LINE_TBL l, BOX_TBL b WHERE l.s ?# b.f1; + +-- Intersection point with line +SELECT l1.s, l2.s, l1.s # l2.s FROM LINE_TBL l1, LINE_TBL l2; + +-- Closest point to line segment +SELECT l.s, l1.s, l.s ## l1.s FROM LINE_TBL l, LSEG_TBL l1; + +-- +-- Line segments +-- + +-- intersection +SELECT p.f1, l.s, l.s # p.f1 AS intersection + FROM LSEG_TBL l, POINT_TBL p; + +-- Length +SELECT s, @-@ s FROM LSEG_TBL; + +-- Vertical +SELECT s FROM LSEG_TBL WHERE ?| s; + +-- Horizontal +SELECT s FROM LSEG_TBL WHERE ?- s; + +-- Center +SELECT s, @@ s FROM LSEG_TBL; + +-- To point +SELECT s, s::point FROM LSEG_TBL; + +-- Has points less than line segment +SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s < l2.s; + +-- Has points less than or equal to line segment +SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s <= l2.s; + +-- Has points equal to line segment +SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s = l2.s; + +-- Has points greater than or equal to line segment +SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s >= l2.s; + +-- Has points greater than line segment +SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s > l2.s; + +-- Has points not equal to line segment +SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s != l2.s; + +-- Parallel with line segment +SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s ?|| l2.s; + +-- Perpendicular with line segment +SELECT l1.s, l2.s FROM LSEG_TBL l1, LSEG_TBL l2 WHERE l1.s ?-| l2.s; + +-- Distance to line +/*+Leading((l1 l))*/ -- YB hint +SELECT l.s, l1.s, l.s <-> l1.s AS dist_sl, l1.s <-> l.s AS dist_ls FROM LSEG_TBL l, LINE_TBL l1; + +-- Distance to line segment +SELECT l1.s, l2.s, l1.s <-> l2.s FROM LSEG_TBL l1, LSEG_TBL l2; + +-- Distance to box +SELECT l.s, b.f1, l.s <-> b.f1 AS dist_sb, b.f1 <-> l.s AS dist_bs FROM LSEG_TBL l, BOX_TBL b; + +-- Intersect with line segment +/*+Leading((l1 l))*/ -- YB hint +SELECT l.s, l1.s FROM LSEG_TBL l, LINE_TBL l1 WHERE l.s ?# l1.s; + +-- Intersect with box +SELECT l.s, b.f1 FROM LSEG_TBL l, BOX_TBL b WHERE l.s ?# b.f1; + +-- Intersection point with line segment +SELECT l1.s, l2.s, l1.s # l2.s FROM LSEG_TBL l1, LSEG_TBL l2; + +-- Closest point to line segment +SELECT l1.s, l2.s, l1.s ## l2.s FROM LSEG_TBL l1, LSEG_TBL l2; + +-- Closest point to box +SELECT l.s, b.f1, l.s ## b.f1 FROM LSEG_TBL l, BOX_TBL b; + +-- On line +SELECT l.s, l1.s FROM LSEG_TBL l, LINE_TBL l1 WHERE l.s <@ l1.s; + +-- On box +SELECT l.s, b.f1 FROM LSEG_TBL l, BOX_TBL b WHERE l.s <@ b.f1; + +-- +-- Boxes +-- + +SELECT box(f1) AS box FROM CIRCLE_TBL; + +-- translation +/*+Leading((p b))*/ -- YB hint +SELECT b.f1 + p.f1 AS translation + FROM BOX_TBL b, POINT_TBL p; + +/*+Leading((p b))*/ -- YB hint +SELECT b.f1 - p.f1 AS translation + FROM BOX_TBL b, POINT_TBL p; + +-- Multiply with point +SELECT b.f1, p.f1, b.f1 * p.f1 FROM BOX_TBL b, POINT_TBL p WHERE p.f1[0] BETWEEN 1 AND 1000; + +-- Overflow error +SELECT b.f1, p.f1, b.f1 * p.f1 FROM BOX_TBL b, POINT_TBL p WHERE p.f1[0] > 1000; + +-- Divide by point +SELECT b.f1, p.f1, b.f1 / p.f1 FROM BOX_TBL b, POINT_TBL p WHERE p.f1[0] BETWEEN 1 AND 1000; + +-- To box +SELECT f1::box + FROM POINT_TBL; + +SELECT bound_box(a.f1, b.f1) + FROM BOX_TBL a, BOX_TBL b; + +-- Below box +SELECT b1.f1, b2.f1, b1.f1 <^ b2.f1 FROM BOX_TBL b1, BOX_TBL b2; + +-- Above box +SELECT b1.f1, b2.f1, b1.f1 >^ b2.f1 FROM BOX_TBL b1, BOX_TBL b2; + +-- Intersection point with box +SELECT b1.f1, b2.f1, b1.f1 # b2.f1 FROM BOX_TBL b1, BOX_TBL b2; + +-- Diagonal +SELECT f1, diagonal(f1) FROM BOX_TBL; + +-- Distance to box +SELECT b1.f1, b2.f1, b1.f1 <-> b2.f1 FROM BOX_TBL b1, BOX_TBL b2; + +-- +-- Paths +-- + +-- Points +SELECT f1, npoints(f1) FROM PATH_TBL; + +-- Area +SELECT f1, area(f1) FROM PATH_TBL; + +-- Length +SELECT f1, @-@ f1 FROM PATH_TBL; + +-- To polygon +SELECT f1, f1::polygon FROM PATH_TBL WHERE isclosed(f1); + +-- Open path cannot be converted to polygon error +SELECT f1, f1::polygon FROM PATH_TBL WHERE isopen(f1); + +-- Has points less than path +SELECT p1.f1, p2.f1 FROM PATH_TBL p1, PATH_TBL p2 WHERE p1.f1 < p2.f1; + +-- Has points less than or equal to path +SELECT p1.f1, p2.f1 FROM PATH_TBL p1, PATH_TBL p2 WHERE p1.f1 <= p2.f1; + +-- Has points equal to path +SELECT p1.f1, p2.f1 FROM PATH_TBL p1, PATH_TBL p2 WHERE p1.f1 = p2.f1; + +-- Has points greater than or equal to path +SELECT p1.f1, p2.f1 FROM PATH_TBL p1, PATH_TBL p2 WHERE p1.f1 >= p2.f1; + +-- Has points greater than path +SELECT p1.f1, p2.f1 FROM PATH_TBL p1, PATH_TBL p2 WHERE p1.f1 > p2.f1; + +-- Add path +SELECT p1.f1, p2.f1, p1.f1 + p2.f1 FROM PATH_TBL p1, PATH_TBL p2; + +-- Add point +/*+Leading((p1 p))*/ -- YB hint +SELECT p.f1, p1.f1, p.f1 + p1.f1 FROM PATH_TBL p, POINT_TBL p1; + +-- Subtract point +/*+Leading((p1 p))*/ -- YB hint +SELECT p.f1, p1.f1, p.f1 - p1.f1 FROM PATH_TBL p, POINT_TBL p1; + +-- Multiply with point +/*+Leading((p1 p))*/ -- YB hint +SELECT p.f1, p1.f1, p.f1 * p1.f1 FROM PATH_TBL p, POINT_TBL p1; + +-- Divide by point +SELECT p.f1, p1.f1, p.f1 / p1.f1 FROM PATH_TBL p, POINT_TBL p1 WHERE p1.f1[0] BETWEEN 1 AND 1000; + +-- Division by 0 error +SELECT p.f1, p1.f1, p.f1 / p1.f1 FROM PATH_TBL p, POINT_TBL p1 WHERE p1.f1 ~= '(0,0)'::point; + +-- Distance to path +SELECT p1.f1, p2.f1, p1.f1 <-> p2.f1 FROM PATH_TBL p1, PATH_TBL p2; + +-- +-- Polygons +-- + +-- containment +/*+Leading((p poly))*/ -- YB hint +SELECT p.f1, poly.f1, poly.f1 @> p.f1 AS contains + FROM POLYGON_TBL poly, POINT_TBL p; + +/*+Leading((p poly))*/ -- YB hint +SELECT p.f1, poly.f1, p.f1 <@ poly.f1 AS contained + FROM POLYGON_TBL poly, POINT_TBL p; + +SELECT npoints(f1) AS npoints, f1 AS polygon + FROM POLYGON_TBL; + +SELECT polygon(f1) + FROM BOX_TBL; + +SELECT polygon(f1) + FROM PATH_TBL WHERE isclosed(f1); + +SELECT f1 AS open_path, polygon( pclose(f1)) AS polygon + FROM PATH_TBL + WHERE isopen(f1); + +-- To box +SELECT f1, f1::box FROM POLYGON_TBL; + +-- To path +SELECT f1, f1::path FROM POLYGON_TBL; + +-- Same as polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 ~= p2.f1; + +-- Contained by polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 <@ p2.f1; + +-- Contains polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 @> p2.f1; + +-- Overlap with polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 && p2.f1; + +-- Left of polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 << p2.f1; + +-- Overlap of left of polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 &< p2.f1; + +-- Right of polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 >> p2.f1; + +-- Overlap of right of polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 &> p2.f1; + +-- Below polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 <<| p2.f1; + +-- Overlap or below polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 &<| p2.f1; + +-- Above polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 |>> p2.f1; + +-- Overlap or above polygon +SELECT p1.f1, p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2 WHERE p1.f1 |&> p2.f1; + +-- Distance to polygon +SELECT p1.f1, p2.f1, p1.f1 <-> p2.f1 FROM POLYGON_TBL p1, POLYGON_TBL p2; + +-- +-- Circles +-- + +SELECT circle(f1, 50.0) + FROM POINT_TBL; + +SELECT circle(f1) + FROM BOX_TBL; + +SELECT circle(f1) + FROM POLYGON_TBL + WHERE (# f1) >= 3; + +/*+Leading((p1 c1))*/ -- YB hint +SELECT c1.f1 AS circle, p1.f1 AS point, (p1.f1 <-> c1.f1) AS distance + FROM CIRCLE_TBL c1, POINT_TBL p1 + WHERE (p1.f1 <-> c1.f1) > 0 + ORDER BY distance, area(c1.f1), p1.f1[0]; + +-- To polygon +SELECT f1, f1::polygon FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>'; + +-- To polygon with less points +SELECT f1, polygon(8, f1) FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>'; + +-- Error for insufficient number of points +SELECT f1, polygon(1, f1) FROM CIRCLE_TBL WHERE f1 >= '<(0,0),1>'; + +-- Zero radius error +SELECT f1, polygon(10, f1) FROM CIRCLE_TBL WHERE f1 < '<(0,0),1>'; + +-- Same as circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 ~= c2.f1; + +-- Overlap with circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 && c2.f1; + +-- Overlap or left of circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 &< c2.f1; + +-- Left of circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 << c2.f1; + +-- Right of circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 >> c2.f1; + +-- Overlap or right of circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 &> c2.f1; + +-- Contained by circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 <@ c2.f1; + +-- Contain by circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 @> c2.f1; + +-- Below circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 <<| c2.f1; + +-- Above circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 |>> c2.f1; + +-- Overlap or below circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 &<| c2.f1; + +-- Overlap or above circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 |&> c2.f1; + +-- Area equal with circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 = c2.f1; + +-- Area not equal with circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 != c2.f1; + +-- Area less than circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 < c2.f1; + +-- Area greater than circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 > c2.f1; + +-- Area less than or equal circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 <= c2.f1; + +-- Area greater than or equal circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 >= c2.f1; + +-- Area less than circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 < c2.f1; + +-- Area greater than circle +SELECT c1.f1, c2.f1 FROM CIRCLE_TBL c1, CIRCLE_TBL c2 WHERE c1.f1 < c2.f1; + +-- Add point +/*+Leading((p c))*/ -- YB hint +SELECT c.f1, p.f1, c.f1 + p.f1 FROM CIRCLE_TBL c, POINT_TBL p; + +-- Subtract point +/*+Leading((p c))*/ -- YB hint +SELECT c.f1, p.f1, c.f1 - p.f1 FROM CIRCLE_TBL c, POINT_TBL p; + +-- Multiply with point +/*+Leading((p c))*/ -- YB hint +SELECT c.f1, p.f1, c.f1 * p.f1 FROM CIRCLE_TBL c, POINT_TBL p; + +-- Divide by point +SELECT c.f1, p.f1, c.f1 / p.f1 FROM CIRCLE_TBL c, POINT_TBL p WHERE p.f1[0] BETWEEN 1 AND 1000; + +-- Overflow error +SELECT c.f1, p.f1, c.f1 / p.f1 FROM CIRCLE_TBL c, POINT_TBL p WHERE p.f1[0] > 1000; + +-- Division by 0 error +SELECT c.f1, p.f1, c.f1 / p.f1 FROM CIRCLE_TBL c, POINT_TBL p WHERE p.f1 ~= '(0,0)'::point; + +-- Distance to polygon +SELECT c.f1, p.f1, c.f1 <-> p.f1 FROM CIRCLE_TBL c, POLYGON_TBL p; + +-- Check index behavior for circles + +CREATE INDEX gcircleind ON circle_tbl USING gist (f1); + +SELECT * FROM circle_tbl WHERE f1 && circle(point(1,-2), 1) + ORDER BY area(f1); + +EXPLAIN (COSTS OFF) +SELECT * FROM circle_tbl WHERE f1 && circle(point(1,-2), 1) + ORDER BY area(f1); +SELECT * FROM circle_tbl WHERE f1 && circle(point(1,-2), 1) + ORDER BY area(f1); + +-- Check index behavior for polygons + +CREATE INDEX gpolygonind ON polygon_tbl USING gist (f1); + +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon + ORDER BY (poly_center(f1))[0]; + +EXPLAIN (COSTS OFF) +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon + ORDER BY (poly_center(f1))[0]; +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon + ORDER BY (poly_center(f1))[0]; diff --git a/src/test/regress/sql/yb.port.gin.sql b/src/test/regress/sql/yb.port.gin.sql new file mode 100644 index 0000000000000..fa1df0f5ec281 --- /dev/null +++ b/src/test/regress/sql/yb.port.gin.sql @@ -0,0 +1,38 @@ +-- +-- Test GIN indexes. +-- +-- There are other tests to test different GIN opclassed. This is for testing +-- GIN itself. + +-- Create and populate a test table with a GIN index. +create table gin_test_tbl(i int4[]) with (autovacuum_enabled = off); +create index gin_test_idx on gin_test_tbl using gin (i) + with (fastupdate = on, gin_pending_list_limit = 4096); +-- YB: create index without options because the one above fails. +create index gin_test_idx on gin_test_tbl using gin (i); +insert into gin_test_tbl select array[1, 2, g] from generate_series(1, 20000) g; +insert into gin_test_tbl select array[1, 3, g] from generate_series(1, 1000) g; + +select gin_clean_pending_list('gin_test_idx')>10 as many; -- flush the fastupdate buffers + +insert into gin_test_tbl select array[3, 1, g] from generate_series(1, 1000) g; + +vacuum gin_test_tbl; -- flush the fastupdate buffers + +select gin_clean_pending_list('gin_test_idx'); -- nothing to flush + +-- Test vacuuming +delete from gin_test_tbl where i @> array[2]; +vacuum gin_test_tbl; + +-- Disable fastupdate, and do more insertions. With fastupdate enabled, most +-- insertions (by flushing the list pages) cause page splits. Without +-- fastupdate, we get more churn in the GIN data leaf pages, and exercise the +-- recompression codepaths. +alter index gin_test_idx set (fastupdate = off); + +insert into gin_test_tbl select array[1, 2, g] from generate_series(1, 1000) g; +insert into gin_test_tbl select array[1, 3, g] from generate_series(1, 1000) g; + +delete from gin_test_tbl where i @> array[2]; +vacuum gin_test_tbl; diff --git a/src/test/regress/sql/yb.port.groupingsets.sql b/src/test/regress/sql/yb.port.groupingsets.sql new file mode 100644 index 0000000000000..19bb6075bf015 --- /dev/null +++ b/src/test/regress/sql/yb.port.groupingsets.sql @@ -0,0 +1,499 @@ +-- +-- grouping sets +-- + +-- test data sources + +create temp view gstest1(a,b,v) + as values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14), + (2,3,15), + (3,3,16),(3,4,17), + (4,1,18),(4,1,19); + +create temp table gstest2 (a integer, b integer, c integer, d integer, + e integer, f integer, g integer, h integer); +copy gstest2 from stdin; +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 2 +1 1 1 1 1 1 2 2 +1 1 1 1 1 2 2 2 +1 1 1 1 2 2 2 2 +1 1 1 2 2 2 2 2 +1 1 2 2 2 2 2 2 +1 2 2 2 2 2 2 2 +2 2 2 2 2 2 2 2 +\. + +create temp table gstest3 (a integer, b integer, c integer, d integer); +copy gstest3 from stdin; +1 1 1 1 +2 2 2 2 +\. +alter table gstest3 add primary key (a); + +create temp table gstest4(id integer, v integer, + unhashable_col bit(4), unsortable_col xid); +insert into gstest4 +values (1,1,b'0000','1'), (2,2,b'0001','1'), + (3,4,b'0010','2'), (4,8,b'0011','2'), + (5,16,b'0000','2'), (6,32,b'0001','2'), + (7,64,b'0010','1'), (8,128,b'0011','1'); + +create temp table gstest_empty (a integer, b integer, v integer); + +create function gstest_data(v integer, out a integer, out b integer) + returns setof record + as $f$ + begin + return query select v, i from generate_series(1,3) i; + end; + $f$ language plpgsql; + +-- basic functionality + +set enable_hashagg = false; -- test hashing explicitly later + +-- simple rollup with multiple plain aggregates, with and without ordering +-- (and with ordering differing from grouping) + +select a, b, grouping(a,b), sum(v), count(*), max(v) + from gstest1 group by rollup (a,b); +select a, b, grouping(a,b), sum(v), count(*), max(v) + from gstest1 group by rollup (a,b) order by a,b; +select a, b, grouping(a,b), sum(v), count(*), max(v) + from gstest1 group by rollup (a,b) order by b desc, a; +select a, b, grouping(a,b), sum(v), count(*), max(v) + from gstest1 group by rollup (a,b) order by coalesce(a,0)+coalesce(b,0); + +-- various types of ordered aggs +select a, b, grouping(a,b), + array_agg(v order by v), + string_agg(v::text, ':' order by v desc), + percentile_disc(0.5) within group (order by v), + rank(1,2,12) within group (order by a,b,v) + from gstest1 group by rollup (a,b) order by a,b; + +-- test usage of grouped columns in direct args of aggs +select grouping(a), a, array_agg(b), + rank(a) within group (order by b nulls first), + rank(a) within group (order by b nulls last) + from (values (1,1),(1,4),(1,5),(3,1),(3,2)) v(a,b) + group by rollup (a) order by a; + +-- nesting with window functions +select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum + from gstest2 group by rollup (a,b) order by rsum, a, b; + +-- nesting with grouping sets +select sum(c) from gstest2 + group by grouping sets((), grouping sets((), grouping sets(()))) + order by 1 desc; +select sum(c) from gstest2 + group by grouping sets((), grouping sets((), grouping sets(((a, b))))) + order by 1 desc; +select sum(c) from gstest2 + group by grouping sets(grouping sets(rollup(c), grouping sets(cube(c)))) + order by 1 desc; +select sum(c) from gstest2 + group by grouping sets(a, grouping sets(a, cube(b))) + order by 1 desc; +select sum(c) from gstest2 + group by grouping sets(grouping sets((a, (b)))) + order by 1 desc; +select sum(c) from gstest2 + group by grouping sets(grouping sets((a, b))) + order by 1 desc; +select sum(c) from gstest2 + group by grouping sets(grouping sets(a, grouping sets(a), a)) + order by 1 desc; +select sum(c) from gstest2 + group by grouping sets(grouping sets(a, grouping sets(a, grouping sets(a), ((a)), a, grouping sets(a), (a)), a)) + order by 1 desc; +select sum(c) from gstest2 + group by grouping sets((a,(a,b)), grouping sets((a,(a,b)),a)) + order by 1 desc; + +-- empty input: first is 0 rows, second 1, third 3 etc. +select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),a); +select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),()); +select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),()); +select sum(v), count(*) from gstest_empty group by grouping sets ((),(),()); + +-- empty input with joins tests some important code paths +select t1.a, t2.b, sum(t1.v), count(*) from gstest_empty t1, gstest_empty t2 + group by grouping sets ((t1.a,t2.b),()); + +-- simple joins, var resolution, GROUPING on join vars +select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a) + from gstest1 t1, gstest2 t2 + group by grouping sets ((t1.a, t2.b), ()); + +select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a) + from gstest1 t1 join gstest2 t2 on (t1.a=t2.a) + group by grouping sets ((t1.a, t2.b), ()); + +select a, b, grouping(a, b), sum(t1.v), max(t2.c) + from gstest1 t1 join gstest2 t2 using (a,b) + group by grouping sets ((a, b), ()); + +-- check that functionally dependent cols are not nulled +select a, d, grouping(a,b,c) + from gstest3 + group by grouping sets ((a,b), (a,c)); + +-- check that distinct grouping columns are kept separate +-- even if they are equal() +explain (costs off) +select g as alias1, g as alias2 + from generate_series(1,3) g + group by alias1, rollup(alias2); + +select g as alias1, g as alias2 + from generate_series(1,3) g + group by alias1, rollup(alias2); + +-- check that pulled-up subquery outputs still go to null when appropriate +select four, x + from (select four, ten, 'foo'::text as x from tenk1) as t + group by grouping sets (four, x) + having x = 'foo'; + +select four, x || 'x' + from (select four, ten, 'foo'::text as x from tenk1) as t + group by grouping sets (four, x) + order by four; + +select (x+y)*1, sum(z) + from (select 1 as x, 2 as y, 3 as z) s + group by grouping sets (x+y, x); + +select x, not x as not_x, q2 from + (select *, q1 = 1 as x from int8_tbl i1) as t + group by grouping sets(x, q2) + order by x, q2; + +-- check qual push-down rules for a subquery with grouping sets +explain (verbose, costs off) +select * from ( + select 1 as x, q1, sum(q2) + from int8_tbl i1 + group by grouping sets(1, 2) +) ss +where x = 1 and q1 = 123; + +select * from ( + select 1 as x, q1, sum(q2) + from int8_tbl i1 + group by grouping sets(1, 2) +) ss +where x = 1 and q1 = 123; + +-- check handling of pulled-up SubPlan in GROUPING() argument (bug #17479) +explain (verbose, costs off) +select grouping(ss.x) +from int8_tbl i1 +cross join lateral (select (select i1.q1) as x) ss +group by ss.x; + +select grouping(ss.x) +from int8_tbl i1 +cross join lateral (select (select i1.q1) as x) ss +group by ss.x; + +explain (verbose, costs off) +select (select grouping(ss.x)) +from int8_tbl i1 +cross join lateral (select (select i1.q1) as x) ss +group by ss.x; + +select (select grouping(ss.x)) +from int8_tbl i1 +cross join lateral (select (select i1.q1) as x) ss +group by ss.x; + +-- simple rescan tests + +select a, b, sum(v.x) + from (values (1),(2)) v(x), gstest_data(v.x) + group by rollup (a,b); + +select * + from (values (1),(2)) v(x), + lateral (select a, b, sum(v.x) from gstest_data(v.x) group by rollup (a,b)) s; + +-- min max optimization should still work with GROUP BY () +explain (costs off) + select min(unique1) from tenk1 GROUP BY (); + +-- Views with GROUPING SET queries +CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c) + from gstest2 group by rollup ((a,b,c),(c,d)); + +select pg_get_viewdef('gstest_view'::regclass, true); + +-- Nested queries with 3 or more levels of nesting +select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); +select(select (select grouping(e,f) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); +select(select (select grouping(c) from (values (1)) v2(c) GROUP BY c) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); + +-- Combinations of operations +select a, b, c, d from gstest2 group by rollup(a,b),grouping sets(c,d); +select a, b from (values (1,2),(2,3)) v(a,b) group by a,b, grouping sets(a); + +-- Tests for chained aggregates +select a, b, grouping(a,b), sum(v), count(*), max(v) + from gstest1 group by grouping sets ((a,b),(a+1,b+1),(a+2,b+2)) order by 3,6; +select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP((e+1),(f+1)); +select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY CUBE((e+1),(f+1)) ORDER BY (e+1),(f+1); +select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum + from gstest2 group by cube (a,b) order by rsum, a, b; +select a, b, sum(c) from (values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),(2,3,15),(3,3,16),(3,4,17),(4,1,18),(4,1,19)) v(a,b,c) group by rollup (a,b); +select a, b, sum(v.x) + from (values (1),(2)) v(x), gstest_data(v.x) + group by cube (a,b) order by a,b; + +-- Test reordering of grouping sets +explain (costs off) +select * from gstest1 group by grouping sets((a,b,v),(v)) order by v,b,a; + +-- Agg level check. This query should error out. +select (select grouping(a,b) from gstest2) from gstest2 group by a,b; + +--Nested queries +select a, b, sum(c), count(*) from gstest2 group by grouping sets (rollup(a,b),a); + +-- HAVING queries +select ten, sum(distinct four) from onek a +group by grouping sets((ten,four),(ten)) +having exists (select 1 from onek b where sum(distinct a.four) = b.four); + +-- Tests around pushdown of HAVING clauses, partially testing against previous bugs +select a,count(*) from gstest2 group by rollup(a) order by a; +select a,count(*) from gstest2 group by rollup(a) having a is distinct from 1 order by a; +explain (costs off) + select a,count(*) from gstest2 group by rollup(a) having a is distinct from 1 order by a; + +select v.c, (select count(*) from gstest2 group by () having v.c) + from (values (false),(true)) v(c) order by v.c; +explain (costs off) + select v.c, (select count(*) from gstest2 group by () having v.c) + from (values (false),(true)) v(c) order by v.c; + +-- HAVING with GROUPING queries +select ten, grouping(ten) from onek +group by grouping sets(ten) having grouping(ten) >= 0 +order by 2,1; +select ten, grouping(ten) from onek +group by grouping sets(ten, four) having grouping(ten) > 0 +order by 2,1; +select ten, grouping(ten) from onek +group by rollup(ten) having grouping(ten) > 0 +order by 2,1; +select ten, grouping(ten) from onek +group by cube(ten) having grouping(ten) > 0 +order by 2,1; +select ten, grouping(ten) from onek +group by (ten) having grouping(ten) >= 0 +order by 2,1; + +-- FILTER queries +select ten, sum(distinct four) filter (where four::text ~ '123') from onek a +group by rollup(ten); + +-- More rescan tests +select * from (values (1),(2)) v(a) left join lateral (select v.a, four, ten, count(*) from onek group by cube(four,ten)) s on true order by v.a,four,ten; +select array(select row(v.a,s1.*) from (select two,four, count(*) from onek group by cube(two,four) order by two,four) s1) from (values (1),(2)) v(a); + +-- Grouping on text columns +select sum(ten) from onek group by two, rollup(four::text) order by 1; +select sum(ten) from onek group by rollup(four::text), two order by 1; + +-- hashing support + +set enable_hashagg = true; + +-- failure cases + +select count(*) from gstest4 group by rollup(unhashable_col,unsortable_col); +select array_agg(v order by v) from gstest4 group by grouping sets ((id,unsortable_col),(id)); + +-- simple cases + +select a, b, grouping(a,b), sum(v), count(*), max(v) + from gstest1 group by grouping sets ((a),(b)) order by 3,1,2; +explain (costs off) select a, b, grouping(a,b), sum(v), count(*), max(v) + from gstest1 group by grouping sets ((a),(b)) order by 3,1,2; + +select a, b, grouping(a,b), sum(v), count(*), max(v) + from gstest1 group by cube(a,b) order by 3,1,2; +explain (costs off) select a, b, grouping(a,b), sum(v), count(*), max(v) + from gstest1 group by cube(a,b) order by 3,1,2; + +-- shouldn't try and hash +explain (costs off) + select a, b, grouping(a,b), array_agg(v order by v) + from gstest1 group by cube(a,b); + +-- unsortable cases +select unsortable_col, count(*) + from gstest4 group by grouping sets ((unsortable_col),(unsortable_col)) + order by unsortable_col::text; + +-- mixed hashable/sortable cases +select unhashable_col, unsortable_col, + grouping(unhashable_col, unsortable_col), + count(*), sum(v) + from gstest4 group by grouping sets ((unhashable_col),(unsortable_col)) + order by 3, 5; +explain (costs off) + select unhashable_col, unsortable_col, + grouping(unhashable_col, unsortable_col), + count(*), sum(v) + from gstest4 group by grouping sets ((unhashable_col),(unsortable_col)) + order by 3,5; + +select unhashable_col, unsortable_col, + grouping(unhashable_col, unsortable_col), + count(*), sum(v) + from gstest4 group by grouping sets ((v,unhashable_col),(v,unsortable_col)) + order by 3,5; +explain (costs off) + select unhashable_col, unsortable_col, + grouping(unhashable_col, unsortable_col), + count(*), sum(v) + from gstest4 group by grouping sets ((v,unhashable_col),(v,unsortable_col)) + order by 3,5; + +-- empty input: first is 0 rows, second 1, third 3 etc. +select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),a); +explain (costs off) + select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),a); +select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),()); +select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),()); +explain (costs off) + select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),()); +select sum(v), count(*) from gstest_empty group by grouping sets ((),(),()); +explain (costs off) + select sum(v), count(*) from gstest_empty group by grouping sets ((),(),()); + +-- check that functionally dependent cols are not nulled +select a, d, grouping(a,b,c) + from gstest3 + group by grouping sets ((a,b), (a,c)); +explain (costs off) + select a, d, grouping(a,b,c) + from gstest3 + group by grouping sets ((a,b), (a,c)); + +-- simple rescan tests + +select a, b, sum(v.x) + from (values (1),(2)) v(x), gstest_data(v.x) + group by grouping sets (a,b) + order by 1, 2, 3; +explain (costs off) + select a, b, sum(v.x) + from (values (1),(2)) v(x), gstest_data(v.x) + group by grouping sets (a,b) + order by 3, 1, 2; +select * + from (values (1),(2)) v(x), + lateral (select a, b, sum(v.x) from gstest_data(v.x) group by grouping sets (a,b)) s; +explain (costs off) + select * + from (values (1),(2)) v(x), + lateral (select a, b, sum(v.x) from gstest_data(v.x) group by grouping sets (a,b)) s; + +-- Tests for chained aggregates +select a, b, grouping(a,b), sum(v), count(*), max(v) + from gstest1 group by grouping sets ((a,b),(a+1,b+1),(a+2,b+2)) order by 3,6; +explain (costs off) + select a, b, grouping(a,b), sum(v), count(*), max(v) + from gstest1 group by grouping sets ((a,b),(a+1,b+1),(a+2,b+2)) order by 3,6; +select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum + from gstest2 group by cube (a,b) order by rsum, a, b; +explain (costs off) + select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum + from gstest2 group by cube (a,b) order by rsum, a, b; +select a, b, sum(v.x) + from (values (1),(2)) v(x), gstest_data(v.x) + group by cube (a,b) order by a,b; +explain (costs off) + select a, b, sum(v.x) + from (values (1),(2)) v(x), gstest_data(v.x) + group by cube (a,b) order by a,b; + +-- Verify that we correctly handle the child node returning a +-- non-minimal slot, which happens if the input is pre-sorted, +-- e.g. due to an index scan. +BEGIN; +SET LOCAL enable_hashagg = false; +EXPLAIN (COSTS OFF) SELECT a, b, count(*), max(a), max(b) FROM gstest3 GROUP BY GROUPING SETS(a, b,()) ORDER BY a, b; +SELECT a, b, count(*), max(a), max(b) FROM gstest3 GROUP BY GROUPING SETS(a, b,()) ORDER BY a, b; +SET LOCAL enable_seqscan = false; +EXPLAIN (COSTS OFF) SELECT a, b, count(*), max(a), max(b) FROM gstest3 GROUP BY GROUPING SETS(a, b,()) ORDER BY a, b; +SELECT a, b, count(*), max(a), max(b) FROM gstest3 GROUP BY GROUPING SETS(a, b,()) ORDER BY a, b; +COMMIT; + +-- More rescan tests +select * from (values (1),(2)) v(a) left join lateral (select v.a, four, ten, count(*) from onek group by cube(four,ten)) s on true order by v.a,four,ten; +select array(select row(v.a,s1.*) from (select two,four, count(*) from onek group by cube(two,four) order by two,four) s1) from (values (1),(2)) v(a); + +-- Rescan logic changes when there are no empty grouping sets, so test +-- that too: +select * from (values (1),(2)) v(a) left join lateral (select v.a, four, ten, count(*) from onek group by grouping sets(four,ten)) s on true order by v.a,four,ten; +select array(select row(v.a,s1.*) from (select two,four, count(*) from onek group by grouping sets(two,four) order by two,four) s1) from (values (1),(2)) v(a); + +-- test the knapsack + +set enable_indexscan = false; +set hash_mem_multiplier = 1.0; +set work_mem = '64kB'; +explain (costs off) + select unique1, + count(two), count(four), count(ten), + count(hundred), count(thousand), count(twothousand), + count(*) + from tenk1 group by grouping sets (unique1,twothousand,thousand,hundred,ten,four,two); +explain (costs off) + select unique1, + count(two), count(four), count(ten), + count(hundred), count(thousand), count(twothousand), + count(*) + from tenk1 group by grouping sets (unique1,hundred,ten,four,two); + +set work_mem = '384kB'; +explain (costs off) + select unique1, + count(two), count(four), count(ten), + count(hundred), count(thousand), count(twothousand), + count(*) + from tenk1 group by grouping sets (unique1,twothousand,thousand,hundred,ten,four,two); + +-- check collation-sensitive matching between grouping expressions +-- (similar to a check for aggregates, but there are additional code +-- paths for GROUPING, so check again here) + +select v||'a', case grouping(v||'a') when 1 then 1 else 0 end, count(*) + from unnest(array[1,1], array['a','b']) u(i,v) + group by rollup(i, v||'a') order by 1,3; +select v||'a', case when grouping(v||'a') = 1 then 1 else 0 end, count(*) + from unnest(array[1,1], array['a','b']) u(i,v) + group by rollup(i, v||'a') order by 1,3; + +-- Bug #16784 +create table bug_16784(i int, j int); +analyze bug_16784; +alter table bug_16784 set (autovacuum_enabled = 'false'); +-- YB: TODO(cost_model): enable the rest of the test. + +-- test handling of outer GroupingFunc within subqueries +explain (costs off) +select (select grouping(v1)) from (values ((select 1))) v(v1) group by cube(v1); +select (select grouping(v1)) from (values ((select 1))) v(v1) group by cube(v1); + +explain (costs off) +select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1; +select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1; + +-- end diff --git a/src/test/regress/sql/yb.port.horology.sql b/src/test/regress/sql/yb.port.horology.sql new file mode 100644 index 0000000000000..e2e151dd69155 --- /dev/null +++ b/src/test/regress/sql/yb.port.horology.sql @@ -0,0 +1,603 @@ +-- +-- HOROLOGY +-- + +SHOW TimeZone; -- Many of these tests depend on the prevailing settings +SHOW DateStyle; + +-- +-- Test various input formats +-- +SELECT timestamp with time zone '20011227 040506+08'; +SELECT timestamp with time zone '20011227 040506-08'; +SELECT timestamp with time zone '20011227 040506.789+08'; +SELECT timestamp with time zone '20011227 040506.789-08'; +SELECT timestamp with time zone '20011227T040506+08'; +SELECT timestamp with time zone '20011227T040506-08'; +SELECT timestamp with time zone '20011227T040506.789+08'; +SELECT timestamp with time zone '20011227T040506.789-08'; +SELECT timestamp with time zone '2001-12-27 04:05:06.789-08'; +SELECT timestamp with time zone '2001.12.27 04:05:06.789-08'; +SELECT timestamp with time zone '2001/12/27 04:05:06.789-08'; +SELECT timestamp with time zone '12/27/2001 04:05:06.789-08'; +-- should fail in mdy mode: +SELECT timestamp with time zone '27/12/2001 04:05:06.789-08'; +set datestyle to dmy; +SELECT timestamp with time zone '27/12/2001 04:05:06.789-08'; +reset datestyle; +SELECT timestamp with time zone 'Y2001M12D27H04M05S06.789+08'; +SELECT timestamp with time zone 'Y2001M12D27H04M05S06.789-08'; +SELECT timestamp with time zone 'Y2001M12D27H04MM05S06.789+08'; +SELECT timestamp with time zone 'Y2001M12D27H04MM05S06.789-08'; +SELECT timestamp with time zone 'J2452271+08'; +SELECT timestamp with time zone 'J2452271-08'; +SELECT timestamp with time zone 'J2452271.5+08'; +SELECT timestamp with time zone 'J2452271.5-08'; +SELECT timestamp with time zone 'J2452271 04:05:06+08'; +SELECT timestamp with time zone 'J2452271 04:05:06-08'; +SELECT timestamp with time zone 'J2452271T040506+08'; +SELECT timestamp with time zone 'J2452271T040506-08'; +SELECT timestamp with time zone 'J2452271T040506.789+08'; +SELECT timestamp with time zone 'J2452271T040506.789-08'; +-- German/European-style dates with periods as delimiters +SELECT timestamp with time zone '12.27.2001 04:05:06.789+08'; +SELECT timestamp with time zone '12.27.2001 04:05:06.789-08'; +SET DateStyle = 'German'; +SELECT timestamp with time zone '27.12.2001 04:05:06.789+08'; +SELECT timestamp with time zone '27.12.2001 04:05:06.789-08'; +SET DateStyle = 'ISO'; +-- As of 7.4, allow time without time zone having a time zone specified +SELECT time without time zone '040506.789+08'; +SELECT time without time zone '040506.789-08'; +SELECT time without time zone 'T040506.789+08'; +SELECT time without time zone 'T040506.789-08'; +SELECT time with time zone '040506.789+08'; +SELECT time with time zone '040506.789-08'; +SELECT time with time zone 'T040506.789+08'; +SELECT time with time zone 'T040506.789-08'; +SELECT time with time zone 'T040506.789 +08'; +SELECT time with time zone 'T040506.789 -08'; +SET DateStyle = 'Postgres, MDY'; +-- Check Julian dates BC +SELECT date 'J1520447' AS "Confucius' Birthday"; +SELECT date 'J0' AS "Julian Epoch"; + +-- +-- date, time arithmetic +-- + +SELECT date '1981-02-03' + time '04:05:06' AS "Date + Time"; +SELECT date '1991-02-03' + time with time zone '04:05:06 PST' AS "Date + Time PST"; +SELECT date '2001-02-03' + time with time zone '04:05:06 UTC' AS "Date + Time UTC"; +SELECT date '1991-02-03' + interval '2 years' AS "Add Two Years"; +SELECT date '2001-12-13' - interval '2 years' AS "Subtract Two Years"; +-- subtract time from date should not make sense; use interval instead +SELECT date '1991-02-03' - time '04:05:06' AS "Subtract Time"; +SELECT date '1991-02-03' - time with time zone '04:05:06 UTC' AS "Subtract Time UTC"; + +-- +-- timestamp, interval arithmetic +-- + +SELECT timestamp without time zone '1996-03-01' - interval '1 second' AS "Feb 29"; +SELECT timestamp without time zone '1999-03-01' - interval '1 second' AS "Feb 28"; +SELECT timestamp without time zone '2000-03-01' - interval '1 second' AS "Feb 29"; +SELECT timestamp without time zone '1999-12-01' + interval '1 month - 1 second' AS "Dec 31"; +SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '106000000 days' AS "Feb 23, 285506"; +SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '107000000 days' AS "Jan 20, 288244"; +SELECT timestamp without time zone 'Jan 1, 4713 BC' + interval '109203489 days' AS "Dec 31, 294276"; +SELECT timestamp without time zone '2000-01-01' - interval '2483590 days' AS "out of range"; +SELECT timestamp without time zone '294276-12-31 23:59:59' + interval '9223372036854775807 microseconds' AS "out of range"; +SELECT timestamp without time zone '12/31/294276' - timestamp without time zone '12/23/1999' AS "106751991 Days"; + +-- Shorthand values +-- Not directly usable for regression testing since these are not constants. +-- So, just try to test parser and hope for the best - thomas 97/04/26 +SELECT (timestamp without time zone 'today' = (timestamp without time zone 'yesterday' + interval '1 day')) as "True"; +SELECT (timestamp without time zone 'today' = (timestamp without time zone 'tomorrow' - interval '1 day')) as "True"; +SELECT (timestamp without time zone 'today 10:30' = (timestamp without time zone 'yesterday' + interval '1 day 10 hr 30 min')) as "True"; +SELECT (timestamp without time zone '10:30 today' = (timestamp without time zone 'yesterday' + interval '1 day 10 hr 30 min')) as "True"; +SELECT (timestamp without time zone 'tomorrow' = (timestamp without time zone 'yesterday' + interval '2 days')) as "True"; +SELECT (timestamp without time zone 'tomorrow 16:00:00' = (timestamp without time zone 'today' + interval '1 day 16 hours')) as "True"; +SELECT (timestamp without time zone '16:00:00 tomorrow' = (timestamp without time zone 'today' + interval '1 day 16 hours')) as "True"; +SELECT (timestamp without time zone 'yesterday 12:34:56' = (timestamp without time zone 'tomorrow' - interval '2 days - 12:34:56')) as "True"; +SELECT (timestamp without time zone '12:34:56 yesterday' = (timestamp without time zone 'tomorrow' - interval '2 days - 12:34:56')) as "True"; +SELECT (timestamp without time zone 'tomorrow' > 'now') as "True"; + +-- Convert from date and time to timestamp +-- This test used to be timestamp(date,time) but no longer allowed by grammar +-- to enable support for SQL99 timestamp type syntax. +SELECT date '1994-01-01' + time '11:00' AS "Jan_01_1994_11am"; +SELECT date '1994-01-01' + time '10:00' AS "Jan_01_1994_10am"; +SELECT date '1994-01-01' + timetz '11:00-5' AS "Jan_01_1994_8am"; +SELECT timestamptz(date '1994-01-01', time with time zone '11:00-5') AS "Jan_01_1994_8am"; + +SELECT d1 + interval '1 year' AS one_year FROM TIMESTAMP_TBL; +SELECT d1 - interval '1 year' AS one_year FROM TIMESTAMP_TBL; + +SELECT timestamp with time zone '1996-03-01' - interval '1 second' AS "Feb 29"; +SELECT timestamp with time zone '1999-03-01' - interval '1 second' AS "Feb 28"; +SELECT timestamp with time zone '2000-03-01' - interval '1 second' AS "Feb 29"; +SELECT timestamp with time zone '1999-12-01' + interval '1 month - 1 second' AS "Dec 31"; +SELECT timestamp with time zone '2000-01-01' - interval '2483590 days' AS "out of range"; +SELECT timestamp with time zone '294276-12-31 23:59:59 UTC' + interval '9223372036854775807 microseconds' AS "out of range"; + +SELECT (timestamp with time zone 'today' = (timestamp with time zone 'yesterday' + interval '1 day')) as "True"; +SELECT (timestamp with time zone 'today' = (timestamp with time zone 'tomorrow' - interval '1 day')) as "True"; +SELECT (timestamp with time zone 'tomorrow' = (timestamp with time zone 'yesterday' + interval '2 days')) as "True"; +SELECT (timestamp with time zone 'tomorrow' > 'now') as "True"; + +-- timestamp with time zone, interval arithmetic around DST change +-- (just for fun, let's use an intentionally nonstandard POSIX zone spec) +SET TIME ZONE 'CST7CDT,M4.1.0,M10.5.0'; +SELECT timestamp with time zone '2005-04-02 12:00-07' + interval '1 day' as "Apr 3, 12:00"; +SELECT timestamp with time zone '2005-04-02 12:00-07' + interval '24 hours' as "Apr 3, 13:00"; +SELECT timestamp with time zone '2005-04-03 12:00-06' - interval '1 day' as "Apr 2, 12:00"; +SELECT timestamp with time zone '2005-04-03 12:00-06' - interval '24 hours' as "Apr 2, 11:00"; +RESET TIME ZONE; + + +SELECT timestamptz(date '1994-01-01', time '11:00') AS "Jan_01_1994_10am"; +SELECT timestamptz(date '1994-01-01', time '10:00') AS "Jan_01_1994_9am"; +SELECT timestamptz(date '1994-01-01', time with time zone '11:00-8') AS "Jan_01_1994_11am"; +SELECT timestamptz(date '1994-01-01', time with time zone '10:00-8') AS "Jan_01_1994_10am"; +SELECT timestamptz(date '1994-01-01', time with time zone '11:00-5') AS "Jan_01_1994_8am"; + +SELECT d1 + interval '1 year' AS one_year FROM TIMESTAMPTZ_TBL; +SELECT d1 - interval '1 year' AS one_year FROM TIMESTAMPTZ_TBL; + +-- +-- time, interval arithmetic +-- + +SELECT CAST(time '01:02' AS interval) AS "+01:02"; +SELECT CAST(interval '02:03' AS time) AS "02:03:00"; +SELECT time '01:30' + interval '02:01' AS "03:31:00"; +SELECT time '01:30' - interval '02:01' AS "23:29:00"; +SELECT time '02:30' + interval '36:01' AS "14:31:00"; +SELECT time '03:30' + interval '1 month 04:01' AS "07:31:00"; +SELECT CAST(time with time zone '01:02-08' AS interval) AS "+00:01"; +SELECT CAST(interval '02:03' AS time with time zone) AS "02:03:00-08"; +SELECT time with time zone '01:30-08' - interval '02:01' AS "23:29:00-08"; +SELECT time with time zone '02:30-08' + interval '36:01' AS "14:31:00-08"; + +-- These two tests cannot be used because they default to current timezone, +-- which may be either -08 or -07 depending on the time of year. +-- SELECT time with time zone '01:30' + interval '02:01' AS "03:31:00-08"; +-- SELECT time with time zone '03:30' + interval '1 month 04:01' AS "07:31:00-08"; +-- Try the following two tests instead, as a poor substitute + +SELECT CAST(CAST(date 'today' + time with time zone '05:30' + + interval '02:01' AS time with time zone) AS time) AS "07:31:00"; + +SELECT CAST(cast(date 'today' + time with time zone '03:30' + + interval '1 month 04:01' as timestamp without time zone) AS time) AS "07:31:00"; + +SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract" + FROM TIMESTAMP_TBL t, INTERVAL_TBL i + WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01' + AND i.f1 BETWEEN '00:00' AND '23:00' + ORDER BY 1,2; + +SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract" + FROM TIME_TBL t, INTERVAL_TBL i + ORDER BY 1,2; + +SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract" + FROM TIMETZ_TBL t, INTERVAL_TBL i + ORDER BY 1,2; + +-- SQL9x OVERLAPS operator +-- test with time zone +SELECT (timestamp with time zone '2000-11-27', timestamp with time zone '2000-11-28') + OVERLAPS (timestamp with time zone '2000-11-27 12:00', timestamp with time zone '2000-11-30') AS "True"; + +SELECT (timestamp with time zone '2000-11-26', timestamp with time zone '2000-11-27') + OVERLAPS (timestamp with time zone '2000-11-27 12:00', timestamp with time zone '2000-11-30') AS "False"; + +SELECT (timestamp with time zone '2000-11-27', timestamp with time zone '2000-11-28') + OVERLAPS (timestamp with time zone '2000-11-27 12:00', interval '1 day') AS "True"; + +SELECT (timestamp with time zone '2000-11-27', interval '12 hours') + OVERLAPS (timestamp with time zone '2000-11-27 12:00', timestamp with time zone '2000-11-30') AS "False"; + +SELECT (timestamp with time zone '2000-11-27', interval '12 hours') + OVERLAPS (timestamp with time zone '2000-11-27', interval '12 hours') AS "True"; + +SELECT (timestamp with time zone '2000-11-27', interval '12 hours') + OVERLAPS (timestamp with time zone '2000-11-27 12:00', interval '12 hours') AS "False"; + +-- test without time zone +SELECT (timestamp without time zone '2000-11-27', timestamp without time zone '2000-11-28') + OVERLAPS (timestamp without time zone '2000-11-27 12:00', timestamp without time zone '2000-11-30') AS "True"; + +SELECT (timestamp without time zone '2000-11-26', timestamp without time zone '2000-11-27') + OVERLAPS (timestamp without time zone '2000-11-27 12:00', timestamp without time zone '2000-11-30') AS "False"; + +SELECT (timestamp without time zone '2000-11-27', timestamp without time zone '2000-11-28') + OVERLAPS (timestamp without time zone '2000-11-27 12:00', interval '1 day') AS "True"; + +SELECT (timestamp without time zone '2000-11-27', interval '12 hours') + OVERLAPS (timestamp without time zone '2000-11-27 12:00', timestamp without time zone '2000-11-30') AS "False"; + +SELECT (timestamp without time zone '2000-11-27', interval '12 hours') + OVERLAPS (timestamp without time zone '2000-11-27', interval '12 hours') AS "True"; + +SELECT (timestamp without time zone '2000-11-27', interval '12 hours') + OVERLAPS (timestamp without time zone '2000-11-27 12:00', interval '12 hours') AS "False"; + +-- test time and interval +SELECT (time '00:00', time '01:00') + OVERLAPS (time '00:30', time '01:30') AS "True"; + +SELECT (time '00:00', interval '1 hour') + OVERLAPS (time '00:30', interval '1 hour') AS "True"; + +SELECT (time '00:00', interval '1 hour') + OVERLAPS (time '01:30', interval '1 hour') AS "False"; + +-- SQL99 seems to want this to be false (and we conform to the spec). +-- istm that this *should* return true, on the theory that time +-- intervals can wrap around the day boundary - thomas 2001-09-25 +SELECT (time '00:00', interval '1 hour') + OVERLAPS (time '01:30', interval '1 day') AS "False"; + +CREATE TABLE TEMP_TIMESTAMP (f1 timestamp with time zone); + +-- get some candidate input values + +INSERT INTO TEMP_TIMESTAMP (f1) + SELECT d1 FROM TIMESTAMP_TBL + WHERE d1 BETWEEN '13-jun-1957' AND '1-jan-1997' + OR d1 BETWEEN '1-jan-1999' AND '1-jan-2010'; + +SELECT f1 AS "timestamp" + FROM TEMP_TIMESTAMP + ORDER BY "timestamp"; + +SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus + FROM TEMP_TIMESTAMP d, INTERVAL_TBL t + ORDER BY plus, "timestamp", "interval"; + +SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus + FROM TEMP_TIMESTAMP d, INTERVAL_TBL t + WHERE isfinite(d.f1) + ORDER BY minus, "timestamp", "interval"; + +SELECT d.f1 AS "timestamp", + timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero, + d.f1 - timestamp with time zone '1980-01-06 00:00 GMT' AS difference + FROM TEMP_TIMESTAMP d + ORDER BY difference; + +SELECT d1.f1 AS timestamp1, d2.f1 AS timestamp2, d1.f1 - d2.f1 AS difference + FROM TEMP_TIMESTAMP d1, TEMP_TIMESTAMP d2 + ORDER BY timestamp1, timestamp2, difference; + +-- +-- Conversions +-- + +SELECT f1 AS "timestamp", date(f1) AS date + FROM TEMP_TIMESTAMP + WHERE f1 <> timestamp 'now' + ORDER BY date, "timestamp"; + +DROP TABLE TEMP_TIMESTAMP; + +-- +-- Comparisons between datetime types, especially overflow cases +--- + +SELECT '2202020-10-05'::date::timestamp; -- fail +SELECT '2202020-10-05'::date > '2020-10-05'::timestamp as t; +SELECT '2020-10-05'::timestamp > '2202020-10-05'::date as f; + +SELECT '2202020-10-05'::date::timestamptz; -- fail +SELECT '2202020-10-05'::date > '2020-10-05'::timestamptz as t; +SELECT '2020-10-05'::timestamptz > '2202020-10-05'::date as f; + +-- This conversion may work depending on timezone +SELECT '4714-11-24 BC'::date::timestamptz; +SET TimeZone = 'UTC-2'; +SELECT '4714-11-24 BC'::date::timestamptz; -- fail + +SELECT '4714-11-24 BC'::date < '2020-10-05'::timestamptz as t; +SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::date as t; + +SELECT '4714-11-24 BC'::timestamp < '2020-10-05'::timestamptz as t; +SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::timestamp as t; + +RESET TimeZone; + +-- +-- Tests for BETWEEN +-- + +explain (costs off) +select count(*) from date_tbl + where f1 between '1997-01-01' and '1998-01-01'; +select count(*) from date_tbl + where f1 between '1997-01-01' and '1998-01-01'; + +explain (costs off) +select count(*) from date_tbl + where f1 not between '1997-01-01' and '1998-01-01'; +select count(*) from date_tbl + where f1 not between '1997-01-01' and '1998-01-01'; + +explain (costs off) +select count(*) from date_tbl + where f1 between symmetric '1997-01-01' and '1998-01-01'; +select count(*) from date_tbl + where f1 between symmetric '1997-01-01' and '1998-01-01'; + +explain (costs off) +select count(*) from date_tbl + where f1 not between symmetric '1997-01-01' and '1998-01-01'; +select count(*) from date_tbl + where f1 not between symmetric '1997-01-01' and '1998-01-01'; + +-- +-- Formats +-- + +SET DateStyle TO 'US,Postgres'; + +SHOW DateStyle; + +SELECT d1 AS us_postgres FROM TIMESTAMP_TBL; + +SET DateStyle TO 'US,ISO'; + +SELECT d1 AS us_iso FROM TIMESTAMP_TBL; + +SET DateStyle TO 'US,SQL'; + +SHOW DateStyle; + +SELECT d1 AS us_sql FROM TIMESTAMP_TBL; + +SET DateStyle TO 'European,Postgres'; + +SHOW DateStyle; + +INSERT INTO TIMESTAMP_TBL VALUES('13/06/1957'); + +SELECT count(*) as one FROM TIMESTAMP_TBL WHERE d1 = 'Jun 13 1957'; + +SELECT d1 AS european_postgres FROM TIMESTAMP_TBL; + +SET DateStyle TO 'European,ISO'; + +SHOW DateStyle; + +SELECT d1 AS european_iso FROM TIMESTAMP_TBL; + +SET DateStyle TO 'European,SQL'; + +SHOW DateStyle; + +SELECT d1 AS european_sql FROM TIMESTAMP_TBL; + +RESET DateStyle; + +-- +-- to_timestamp() +-- + +SELECT to_timestamp('0097/Feb/16 --> 08:14:30', 'YYYY/Mon/DD --> HH:MI:SS'); + +SELECT to_timestamp('97/2/16 8:14:30', 'FMYYYY/FMMM/FMDD FMHH:FMMI:FMSS'); + +SELECT to_timestamp('2011$03!18 23_38_15', 'YYYY-MM-DD HH24:MI:SS'); + +SELECT to_timestamp('1985 January 12', 'YYYY FMMonth DD'); + +SELECT to_timestamp('1985 FMMonth 12', 'YYYY "FMMonth" DD'); + +SELECT to_timestamp('1985 \ 12', 'YYYY \\ DD'); + +SELECT to_timestamp('My birthday-> Year: 1976, Month: May, Day: 16', + '"My birthday-> Year:" YYYY, "Month:" FMMonth, "Day:" DD'); + +SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD'); + +SELECT to_timestamp('15 "text between quote marks" 98 54 45', + E'HH24 "\\"text between quote marks\\"" YY MI SS'); + +SELECT to_timestamp('05121445482000', 'MMDDHH24MISSYYYY'); + +SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay'); + +SELECT to_timestamp('97/Feb/16', 'YYMonDD'); + +SELECT to_timestamp('97/Feb/16', 'YY:Mon:DD'); + +SELECT to_timestamp('97/Feb/16', 'FXYY:Mon:DD'); + +SELECT to_timestamp('97/Feb/16', 'FXYY/Mon/DD'); + +SELECT to_timestamp('19971116', 'YYYYMMDD'); + +SELECT to_timestamp('20000-1116', 'YYYY-MMDD'); + +SELECT to_timestamp('1997 AD 11 16', 'YYYY BC MM DD'); +SELECT to_timestamp('1997 BC 11 16', 'YYYY BC MM DD'); + +SELECT to_timestamp('1997 A.D. 11 16', 'YYYY B.C. MM DD'); +SELECT to_timestamp('1997 B.C. 11 16', 'YYYY B.C. MM DD'); + +SELECT to_timestamp('9-1116', 'Y-MMDD'); + +SELECT to_timestamp('95-1116', 'YY-MMDD'); + +SELECT to_timestamp('995-1116', 'YYY-MMDD'); + +SELECT to_timestamp('2005426', 'YYYYWWD'); + +SELECT to_timestamp('2005300', 'YYYYDDD'); + +SELECT to_timestamp('2005527', 'IYYYIWID'); + +SELECT to_timestamp('005527', 'IYYIWID'); + +SELECT to_timestamp('05527', 'IYIWID'); + +SELECT to_timestamp('5527', 'IIWID'); + +SELECT to_timestamp('2005364', 'IYYYIDDD'); + +SELECT to_timestamp('20050302', 'YYYYMMDD'); + +SELECT to_timestamp('2005 03 02', 'YYYYMMDD'); + +SELECT to_timestamp(' 2005 03 02', 'YYYYMMDD'); + +SELECT to_timestamp(' 20050302', 'YYYYMMDD'); + +SELECT to_timestamp('2011-12-18 11:38 AM', 'YYYY-MM-DD HH12:MI PM'); +SELECT to_timestamp('2011-12-18 11:38 PM', 'YYYY-MM-DD HH12:MI PM'); + +SELECT to_timestamp('2011-12-18 11:38 A.M.', 'YYYY-MM-DD HH12:MI P.M.'); +SELECT to_timestamp('2011-12-18 11:38 P.M.', 'YYYY-MM-DD HH12:MI P.M.'); + +SELECT to_timestamp('2011-12-18 11:38 +05', 'YYYY-MM-DD HH12:MI TZH'); +SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI TZH'); +SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM'); +SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM'); +SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM'); + +SELECT to_timestamp('2011-12-18 11:38 PST', 'YYYY-MM-DD HH12:MI TZ'); -- NYI + +SELECT to_timestamp('2018-11-02 12:34:56.025', 'YYYY-MM-DD HH24:MI:SS.MS'); + +SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('20181102123456123456', 'YYYYMMDDHH24MISSFF' || i) FROM generate_series(1, 6) i; + +SELECT to_date('1 4 1902', 'Q MM YYYY'); -- Q is ignored +SELECT to_date('3 4 21 01', 'W MM CC YY'); +SELECT to_date('2458872', 'J'); + +-- +-- Check handling of BC dates +-- + +SELECT to_date('44-02-01 BC','YYYY-MM-DD BC'); +SELECT to_date('-44-02-01','YYYY-MM-DD'); +SELECT to_date('-44-02-01 BC','YYYY-MM-DD BC'); +SELECT to_timestamp('44-02-01 11:12:13 BC','YYYY-MM-DD HH24:MI:SS BC'); +SELECT to_timestamp('-44-02-01 11:12:13','YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('-44-02-01 11:12:13 BC','YYYY-MM-DD HH24:MI:SS BC'); + +-- +-- Check handling of multiple spaces in format and/or input +-- + +SELECT to_timestamp('2011-12-18 23:38:15', 'YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('2011-12-18 23:38:15', 'YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('2011-12-18 23:38:15', 'YYYY-MM-DD HH24:MI:SS'); + +SELECT to_timestamp('2011-12-18 23:38:15', 'YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('2011-12-18 23:38:15', 'YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('2011-12-18 23:38:15', 'YYYY-MM-DD HH24:MI:SS'); + +SELECT to_timestamp('2000+ JUN', 'YYYY/MON'); +SELECT to_timestamp(' 2000 +JUN', 'YYYY/MON'); +SELECT to_timestamp(' 2000 +JUN', 'YYYY//MON'); +SELECT to_timestamp('2000 +JUN', 'YYYY//MON'); +SELECT to_timestamp('2000 + JUN', 'YYYY MON'); +SELECT to_timestamp('2000 ++ JUN', 'YYYY MON'); +SELECT to_timestamp('2000 + + JUN', 'YYYY MON'); +SELECT to_timestamp('2000 + + JUN', 'YYYY MON'); +SELECT to_timestamp('2000 -10', 'YYYY TZH'); +SELECT to_timestamp('2000 -10', 'YYYY TZH'); + +SELECT to_date('2011 12 18', 'YYYY MM DD'); +SELECT to_date('2011 12 18', 'YYYY MM DD'); +SELECT to_date('2011 12 18', 'YYYY MM DD'); + +SELECT to_date('2011 12 18', 'YYYY MM DD'); +SELECT to_date('2011 12 18', 'YYYY MM DD'); +SELECT to_date('2011 12 18', 'YYYY MM DD'); + +SELECT to_date('2011 12 18', 'YYYYxMMxDD'); +SELECT to_date('2011x 12x 18', 'YYYYxMMxDD'); +SELECT to_date('2011 x12 x18', 'YYYYxMMxDD'); + +-- +-- Check errors for some incorrect usages of to_timestamp() and to_date() +-- + +-- Mixture of date conventions (ISO week and Gregorian): +SELECT to_timestamp('2005527', 'YYYYIWID'); + +-- Insufficient characters in the source string: +SELECT to_timestamp('19971', 'YYYYMMDD'); + +-- Insufficient digit characters for a single node: +SELECT to_timestamp('19971)24', 'YYYYMMDD'); + +-- We don't accept full-length day or month names if short form is specified: +SELECT to_timestamp('Friday 1-January-1999', 'DY DD MON YYYY'); +SELECT to_timestamp('Fri 1-January-1999', 'DY DD MON YYYY'); +SELECT to_timestamp('Fri 1-Jan-1999', 'DY DD MON YYYY'); -- ok + +-- Value clobbering: +SELECT to_timestamp('1997-11-Jan-16', 'YYYY-MM-Mon-DD'); + +-- Non-numeric input: +SELECT to_timestamp('199711xy', 'YYYYMMDD'); + +-- Input that doesn't fit in an int: +SELECT to_timestamp('10000000000', 'FMYYYY'); + +-- Out-of-range and not-quite-out-of-range fields: +SELECT to_timestamp('2016-06-13 25:00:00', 'YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('2016-06-13 15:60:00', 'YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('2016-06-13 15:50:60', 'YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('2016-06-13 15:50:55', 'YYYY-MM-DD HH24:MI:SS'); -- ok +SELECT to_timestamp('2016-06-13 15:50:55', 'YYYY-MM-DD HH:MI:SS'); +SELECT to_timestamp('2016-13-01 15:50:55', 'YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('2016-02-30 15:50:55', 'YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('2016-02-29 15:50:55', 'YYYY-MM-DD HH24:MI:SS'); -- ok +SELECT to_timestamp('2015-02-29 15:50:55', 'YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSS'); -- ok +SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSS'); +SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS'); -- ok +SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS'); +SELECT to_date('2016-13-10', 'YYYY-MM-DD'); +SELECT to_date('2016-02-30', 'YYYY-MM-DD'); +SELECT to_date('2016-02-29', 'YYYY-MM-DD'); -- ok +SELECT to_date('2015-02-29', 'YYYY-MM-DD'); +SELECT to_date('2015 365', 'YYYY DDD'); -- ok +SELECT to_date('2015 366', 'YYYY DDD'); +SELECT to_date('2016 365', 'YYYY DDD'); -- ok +SELECT to_date('2016 366', 'YYYY DDD'); -- ok +SELECT to_date('2016 367', 'YYYY DDD'); +SELECT to_date('0000-02-01','YYYY-MM-DD'); -- allowed, though it shouldn't be + +-- +-- Check behavior with SQL-style fixed-GMT-offset time zone (cf bug #8572) +-- + +SET TIME ZONE 'America/New_York'; +SET TIME ZONE '-1.5'; + +SHOW TIME ZONE; + +SELECT '2012-12-12 12:00'::timestamptz; +SELECT '2012-12-12 12:00 America/New_York'::timestamptz; + +SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ'); +SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSS'); +SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSSS'); + +RESET TIME ZONE; diff --git a/src/test/regress/sql/yb.port.identity.sql b/src/test/regress/sql/yb.port.identity.sql new file mode 100644 index 0000000000000..017cebfa96416 --- /dev/null +++ b/src/test/regress/sql/yb.port.identity.sql @@ -0,0 +1,262 @@ +-- sanity check of system catalog +SELECT attrelid, attname, attidentity FROM pg_attribute WHERE attidentity NOT IN ('', 'a', 'd'); + + +CREATE TABLE itest1 (a int generated by default as identity, b text); +CREATE TABLE itest2 (a bigint generated always as identity, b text); +CREATE TABLE itest3 (a smallint generated by default as identity (start with 7 increment by 5), b text); +ALTER TABLE itest3 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error +-- Check that the sequence was not created. +SELECT COUNT(*) FROM pg_class WHERE relname = 'itest3_a_seq1'; + +SELECT table_name, column_name, column_default, is_nullable, is_identity, identity_generation, identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle FROM information_schema.columns WHERE table_name LIKE 'itest_' ORDER BY 1, 2; + +-- internal sequences should not be shown here +SELECT sequence_name FROM information_schema.sequences WHERE sequence_name LIKE 'itest%'; + +SELECT pg_get_serial_sequence('itest1', 'a'); + +\d itest1_a_seq + +CREATE TABLE itest4 (a int, b text); +ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, requires NOT NULL +ALTER TABLE itest4 ALTER COLUMN a SET NOT NULL; +ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- ok +ALTER TABLE itest4 ALTER COLUMN a DROP NOT NULL; -- error, disallowed +ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, already set +ALTER TABLE itest4 ALTER COLUMN b ADD GENERATED ALWAYS AS IDENTITY; -- error, wrong data type + +-- for later +ALTER TABLE itest4 ALTER COLUMN b SET DEFAULT ''; + +-- invalid column type +CREATE TABLE itest_err_1 (a text generated by default as identity); + +-- duplicate identity +CREATE TABLE itest_err_2 (a int generated always as identity generated by default as identity); + +-- cannot have default and identity +CREATE TABLE itest_err_3 (a int default 5 generated by default as identity); + +-- cannot combine serial and identity +CREATE TABLE itest_err_4 (a serial generated by default as identity); + +INSERT INTO itest1 DEFAULT VALUES; +INSERT INTO itest1 DEFAULT VALUES; +INSERT INTO itest2 DEFAULT VALUES; +INSERT INTO itest2 DEFAULT VALUES; +INSERT INTO itest3 DEFAULT VALUES; +INSERT INTO itest3 DEFAULT VALUES; +INSERT INTO itest4 DEFAULT VALUES; +INSERT INTO itest4 DEFAULT VALUES; + +SELECT * FROM itest1 ORDER BY a; +SELECT * FROM itest2 ORDER BY a; +SELECT * FROM itest3 ORDER BY a; +SELECT * FROM itest4 ORDER BY a; + + +-- VALUES RTEs + +INSERT INTO itest3 VALUES (DEFAULT, 'a'); +INSERT INTO itest3 VALUES (DEFAULT, 'b'), (DEFAULT, 'c'); + +SELECT * FROM itest3 ORDER BY a; + + +-- OVERRIDING tests + +INSERT INTO itest1 VALUES (10, 'xyz'); +INSERT INTO itest1 OVERRIDING USER VALUE VALUES (10, 'xyz'); + +SELECT * FROM itest1 ORDER BY a; + +INSERT INTO itest2 VALUES (10, 'xyz'); +INSERT INTO itest2 OVERRIDING SYSTEM VALUE VALUES (10, 'xyz'); + +SELECT * FROM itest2 ORDER BY a; + + +-- UPDATE tests + +UPDATE itest1 SET a = 101 WHERE a = 1; +UPDATE itest1 SET a = DEFAULT WHERE a = 2; +SELECT * FROM itest1 ORDER BY a; + +UPDATE itest2 SET a = 101 WHERE a = 1; +UPDATE itest2 SET a = DEFAULT WHERE a = 2; +SELECT * FROM itest2 ORDER BY a; + + +-- COPY tests + +CREATE TABLE itest9 (a int GENERATED ALWAYS AS IDENTITY, b text, c bigint); + +COPY itest9 FROM stdin; +100 foo 200 +101 bar 201 +\. + +COPY itest9 (b, c) FROM stdin; +foo2 202 +bar2 203 +\. + +SELECT * FROM itest9 ORDER BY c; + + +-- DROP IDENTITY tests + +ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY; +ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY; -- error +ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY IF EXISTS; -- noop + +INSERT INTO itest4 DEFAULT VALUES; -- fails because NOT NULL is not dropped +ALTER TABLE itest4 ALTER COLUMN a DROP NOT NULL; +INSERT INTO itest4 DEFAULT VALUES; +SELECT * FROM itest4 ORDER BY a; + +-- check that sequence is removed +SELECT sequence_name FROM itest4_a_seq; + + +-- test views + +CREATE TABLE itest10 (a int generated by default as identity, b text); +CREATE TABLE itest11 (a int generated always as identity, b text); + +CREATE VIEW itestv10 AS SELECT * FROM itest10; +CREATE VIEW itestv11 AS SELECT * FROM itest11; + +INSERT INTO itestv10 DEFAULT VALUES; +INSERT INTO itestv10 DEFAULT VALUES; + +INSERT INTO itestv11 DEFAULT VALUES; +INSERT INTO itestv11 DEFAULT VALUES; + +SELECT * FROM itestv10 ORDER BY a; +SELECT * FROM itestv11 ORDER BY a; + +INSERT INTO itestv10 VALUES (10, 'xyz'); +INSERT INTO itestv10 OVERRIDING USER VALUE VALUES (11, 'xyz'); + +SELECT * FROM itestv10 ORDER BY a; + +INSERT INTO itestv11 VALUES (10, 'xyz'); +INSERT INTO itestv11 OVERRIDING SYSTEM VALUE VALUES (11, 'xyz'); + +SELECT * FROM itestv11 ORDER BY a; + + +-- TODO (dmitry) +-- Enable next tests when ALTER TABLE and INHERITS will be implemented + +-- ADD COLUMN + +-- CREATE TABLE itest13 (a int); +-- add column to empty table +-- ALTER TABLE itest13 ADD COLUMN b int GENERATED BY DEFAULT AS IDENTITY; +-- INSERT INTO itest13 VALUES (1), (2), (3); +-- add column to populated table +-- ALTER TABLE itest13 ADD COLUMN c int GENERATED BY DEFAULT AS IDENTITY; +-- SELECT * FROM itest13; + + +-- various ALTER COLUMN tests + +-- fail, not allowed for identity columns +-- ALTER TABLE itest1 ALTER COLUMN a SET DEFAULT 1; + +-- fail, not allowed, already has a default +-- CREATE TABLE itest5 (a serial, b text); +-- ALTER TABLE itest5 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; + +ALTER TABLE itest3 ALTER COLUMN a TYPE int; +SELECT seqtypid::regtype FROM pg_sequence WHERE seqrelid = 'itest3_a_seq'::regclass; +\d itest3 + +ALTER TABLE itest3 ALTER COLUMN a TYPE text; -- error + + +-- ALTER COLUMN ... SET + +-- CREATE TABLE itest6 (a int GENERATED ALWAYS AS IDENTITY, b text); +-- INSERT INTO itest6 DEFAULT VALUES; + +-- ALTER TABLE itest6 ALTER COLUMN a SET GENERATED BY DEFAULT SET INCREMENT BY 2 SET START WITH 100 RESTART; +-- INSERT INTO itest6 DEFAULT VALUES; +-- INSERT INTO itest6 DEFAULT VALUES; +-- SELECT * FROM itest6; + +-- SELECT table_name, column_name, is_identity, identity_generation FROM information_schema.columns WHERE table_name = 'itest6'; + +-- ALTER TABLE itest6 ALTER COLUMN b SET INCREMENT BY 2; -- fail, not identity + + +-- prohibited direct modification of sequence + +-- ALTER SEQUENCE itest6_a_seq OWNED BY NONE; + + +-- inheritance + +-- CREATE TABLE itest7 (a int GENERATED ALWAYS AS IDENTITY); +-- INSERT INTO itest7 DEFAULT VALUES; +-- SELECT * FROM itest7; + +-- identity property is not inherited +-- CREATE TABLE itest7a (b text) INHERITS (itest7); + +-- make column identity in child table +-- CREATE TABLE itest7b (a int); +-- CREATE TABLE itest7c (a int GENERATED ALWAYS AS IDENTITY) INHERITS (itest7b); +-- INSERT INTO itest7c DEFAULT VALUES; +-- SELECT * FROM itest7c; + +-- CREATE TABLE itest7d (a int not null); +-- CREATE TABLE itest7e () INHERITS (itest7d); +-- ALTER TABLE itest7d ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; +-- ALTER TABLE itest7d ADD COLUMN b int GENERATED ALWAYS AS IDENTITY; -- error + +-- SELECT table_name, column_name, is_nullable, is_identity, identity_generation FROM information_schema.columns WHERE table_name LIKE 'itest7%' ORDER BY 1, 2; + +-- These ALTER TABLE variants will not recurse. +-- ALTER TABLE itest7 ALTER COLUMN a SET GENERATED BY DEFAULT; +-- ALTER TABLE itest7 ALTER COLUMN a RESTART; +-- ALTER TABLE itest7 ALTER COLUMN a DROP IDENTITY; + +-- privileges +-- CREATE USER regress_identity_user1; +-- CREATE TABLE itest8 (a int GENERATED ALWAYS AS IDENTITY, b text); +-- GRANT SELECT, INSERT ON itest8 TO regress_identity_user1; +-- SET ROLE regress_identity_user1; +-- INSERT INTO itest8 DEFAULT VALUES; +-- SELECT * FROM itest8; +-- RESET ROLE; +-- DROP TABLE itest8; +-- DROP USER regress_identity_user1; + + +-- typed tables (currently not supported) + +CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint); +CREATE TABLE itest12 OF itest_type (f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error +DROP TYPE itest_type CASCADE; + + +-- table partitions + +CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); +CREATE TABLE itest_child PARTITION OF itest_parent ( + f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY +) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error +DROP TABLE itest_parent; + +-- Identity columns must be NOT NULL (cf bug #16913) + +CREATE TABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NULL); -- fail +CREATE TABLE itest15 (id integer NULL GENERATED ALWAYS AS IDENTITY); -- fail +CREATE TABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NOT NULL); +DROP TABLE itest15; +CREATE TABLE itest15 (id integer NOT NULL GENERATED ALWAYS AS IDENTITY); +DROP TABLE itest15; diff --git a/src/test/regress/sql/yb.port.incremental_sort.sql b/src/test/regress/sql/yb.port.incremental_sort.sql new file mode 100644 index 0000000000000..14f780d6dc508 --- /dev/null +++ b/src/test/regress/sql/yb.port.incremental_sort.sql @@ -0,0 +1,206 @@ +-- When we have to sort the entire table, incremental sort will +-- be slower than plain sort, so it should not be used. +explain (costs off) +select * from (select * from tenk1 order by four) t order by four, ten; + +-- When there is a LIMIT clause, incremental sort is beneficial because +-- it only has to sort some of the groups, and not the entire table. +explain (costs off) +select * from (select * from tenk1 order by four) t order by four, ten +limit 1; + +-- When work_mem is not enough to sort the entire table, incremental sort +-- may be faster if individual groups still fit into work_mem. +set work_mem to '2MB'; +explain (costs off) +select * from (select * from tenk1 order by four) t order by four, ten; +reset work_mem; + +create table t(a integer, b integer); + +create or replace function explain_analyze_without_memory(query text) +returns table (out_line text) language plpgsql +as +$$ +declare + line text; +begin + for line in + execute 'explain (analyze, costs off, summary off, timing off) ' || query + loop + out_line := regexp_replace(line, '\d+kB', 'NNkB', 'g'); + return next; + end loop; +end; +$$; + +create or replace function explain_analyze_inc_sort_nodes(query text) +returns jsonb language plpgsql +as +$$ +declare + elements jsonb; + element jsonb; + matching_nodes jsonb := '[]'::jsonb; +begin + execute 'explain (analyze, costs off, summary off, timing off, format ''json'') ' || query into strict elements; + while jsonb_array_length(elements) > 0 loop + element := elements->0; + elements := elements - 0; + case jsonb_typeof(element) + when 'array' then + if jsonb_array_length(element) > 0 then + elements := elements || element; + end if; + when 'object' then + if element ? 'Plan' then + elements := elements || jsonb_build_array(element->'Plan'); + element := element - 'Plan'; + else + if element ? 'Plans' then + elements := elements || jsonb_build_array(element->'Plans'); + element := element - 'Plans'; + end if; + if (element->>'Node Type')::text = 'Incremental Sort' then + matching_nodes := matching_nodes || element; + end if; + end if; + end case; + end loop; + return matching_nodes; +end; +$$; + +create or replace function explain_analyze_inc_sort_nodes_without_memory(query text) +returns jsonb language plpgsql +as +$$ +declare + nodes jsonb := '[]'::jsonb; + node jsonb; + group_key text; + space_key text; +begin + for node in select * from jsonb_array_elements(explain_analyze_inc_sort_nodes(query)) t loop + for group_key in select unnest(array['Full-sort Groups', 'Pre-sorted Groups']::text[]) t loop + for space_key in select unnest(array['Sort Space Memory', 'Sort Space Disk']::text[]) t loop + node := jsonb_set(node, array[group_key, space_key, 'Average Sort Space Used'], '"NN"', false); + node := jsonb_set(node, array[group_key, space_key, 'Peak Sort Space Used'], '"NN"', false); + end loop; + end loop; + nodes := nodes || node; + end loop; + return nodes; +end; +$$; + +create or replace function explain_analyze_inc_sort_nodes_verify_invariants(query text) +returns bool language plpgsql +as +$$ +declare + node jsonb; + group_stats jsonb; + group_key text; + space_key text; +begin + for node in select * from jsonb_array_elements(explain_analyze_inc_sort_nodes(query)) t loop + for group_key in select unnest(array['Full-sort Groups', 'Pre-sorted Groups']::text[]) t loop + group_stats := node->group_key; + for space_key in select unnest(array['Sort Space Memory', 'Sort Space Disk']::text[]) t loop + if (group_stats->space_key->'Peak Sort Space Used')::bigint < (group_stats->space_key->'Peak Sort Space Used')::bigint then + raise exception '% has invalid max space < average space', group_key; + end if; + end loop; + end loop; + end loop; + return true; +end; +$$; + +-- A single large group tested around each mode transition point. +insert into t(a, b) select i/100 + 1, i + 1 from generate_series(0, 999) n(i); +analyze t; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 31; +select * from (select * from t order by a) s order by a, b limit 31; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 32; +select * from (select * from t order by a) s order by a, b limit 32; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 33; +select * from (select * from t order by a) s order by a, b limit 33; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 65; +select * from (select * from t order by a) s order by a, b limit 65; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 66; +select * from (select * from t order by a) s order by a, b limit 66; +delete from t; + +-- An initial large group followed by a small group. +insert into t(a, b) select i/50 + 1, i + 1 from generate_series(0, 999) n(i); +analyze t; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 55; +select * from (select * from t order by a) s order by a, b limit 55; +-- Test EXPLAIN ANALYZE with only a fullsort group. +select explain_analyze_without_memory('select * from (select * from t order by a) s order by a, b limit 55'); +select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from (select * from t order by a) s order by a, b limit 55')); +select explain_analyze_inc_sort_nodes_verify_invariants('select * from (select * from t order by a) s order by a, b limit 55'); +delete from t; + +-- An initial small group followed by a large group. +insert into t(a, b) select (case when i < 5 then i else 9 end), i from generate_series(1, 1000) n(i); +analyze t; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 70; +select * from (select * from t order by a) s order by a, b limit 70; +-- Checks case where we hit a group boundary at the last tuple of a batch. +-- Because the full sort state is bounded, we scan 64 tuples (the mode +-- transition point) but only retain 5. Thus when we transition modes, all +-- tuples in the full sort state have different prefix keys. +explain (costs off) select * from (select * from t order by a) s order by a, b limit 5; +select * from (select * from t order by a) s order by a, b limit 5; + +-- Test rescan. +begin; +-- We force the planner to choose a plan with incremental sort on the right side +-- of a nested loop join node. That way we trigger the rescan code path. +set local enable_hashjoin = off; +set local enable_mergejoin = off; +set local enable_material = off; +set local enable_sort = off; +explain (costs off) select * from t left join (select * from (select * from t order by a) v order by a, b) s on s.a = t.a where t.a in (1, 2); +select * from t left join (select * from (select * from t order by a) v order by a, b) s on s.a = t.a where t.a in (1, 2); +rollback; +-- Test EXPLAIN ANALYZE with both fullsort and presorted groups. +select explain_analyze_without_memory('select * from (select * from t order by a) s order by a, b limit 70'); +select jsonb_pretty(explain_analyze_inc_sort_nodes_without_memory('select * from (select * from t order by a) s order by a, b limit 70')); +select explain_analyze_inc_sort_nodes_verify_invariants('select * from (select * from t order by a) s order by a, b limit 70'); +delete from t; + +-- Small groups of 10 tuples each tested around each mode transition point. +insert into t(a, b) select i / 10, i from generate_series(1, 1000) n(i); +analyze t; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 31; +select * from (select * from t order by a) s order by a, b limit 31; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 32; +select * from (select * from t order by a) s order by a, b limit 32; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 33; +select * from (select * from t order by a) s order by a, b limit 33; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 65; +select * from (select * from t order by a) s order by a, b limit 65; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 66; +select * from (select * from t order by a) s order by a, b limit 66; +delete from t; + +-- Small groups of only 1 tuple each tested around each mode transition point. +insert into t(a, b) select i, i from generate_series(1, 1000) n(i); +analyze t; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 31; +select * from (select * from t order by a) s order by a, b limit 31; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 32; +select * from (select * from t order by a) s order by a, b limit 32; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 33; +select * from (select * from t order by a) s order by a, b limit 33; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 65; +select * from (select * from t order by a) s order by a, b limit 65; +explain (costs off) select * from (select * from t order by a) s order by a, b limit 66; +select * from (select * from t order by a) s order by a, b limit 66; +delete from t; + +drop table t; diff --git a/src/test/regress/sql/yb.port.index_including.sql b/src/test/regress/sql/yb.port.index_including.sql new file mode 100644 index 0000000000000..79859976d13c4 --- /dev/null +++ b/src/test/regress/sql/yb.port.index_including.sql @@ -0,0 +1,222 @@ +SET yb_use_hash_splitting_by_default = false; -- YB: TODO: remove this when it is set for ported regress tests by default +/* + * 1.test CREATE INDEX + * + * Deliberately avoid dropping objects in this section, to get some pg_dump + * coverage. + */ + +-- Regular index with included columns +CREATE TABLE tbl_include_reg (c1 int, c2 int, c3 int, c4 box); +INSERT INTO tbl_include_reg SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +CREATE INDEX tbl_include_reg_idx ON tbl_include_reg (c1, c2) INCLUDE (c3, c4); +-- duplicate column is pretty pointless, but we allow it anyway +CREATE INDEX ON tbl_include_reg (c1, c2) INCLUDE (c1, c3); +SELECT pg_get_indexdef(i.indexrelid) +FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid +WHERE i.indrelid = 'tbl_include_reg'::regclass ORDER BY c.relname; +\d tbl_include_reg_idx + +-- Unique index and unique constraint +CREATE TABLE tbl_include_unique1 (c1 int, c2 int, c3 int, c4 box); +INSERT INTO tbl_include_unique1 SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +CREATE UNIQUE INDEX tbl_include_unique1_idx_unique ON tbl_include_unique1 using btree (c1, c2) INCLUDE (c3, c4); +ALTER TABLE tbl_include_unique1 add UNIQUE USING INDEX tbl_include_unique1_idx_unique; +ALTER TABLE tbl_include_unique1 add UNIQUE (c1, c2) INCLUDE (c3, c4); +SELECT pg_get_indexdef(i.indexrelid) +FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid +WHERE i.indrelid = 'tbl_include_unique1'::regclass ORDER BY c.relname; + +-- Unique index and unique constraint. Both must fail. +CREATE TABLE tbl_include_unique2 (c1 int, c2 int, c3 int, c4 box); +INSERT INTO tbl_include_unique2 SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +CREATE UNIQUE INDEX tbl_include_unique2_idx_unique ON tbl_include_unique2 using btree (c1, c2) INCLUDE (c3, c4); +ALTER TABLE tbl_include_unique2 add UNIQUE (c1, c2) INCLUDE (c3, c4); + +-- PK constraint +CREATE TABLE tbl_include_pk (c1 int, c2 int, c3 int, c4 box); +INSERT INTO tbl_include_pk SELECT 1, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +ALTER TABLE tbl_include_pk add PRIMARY KEY (c1, c2) INCLUDE (c3, c4); +SELECT pg_get_indexdef(i.indexrelid) +FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid +WHERE i.indrelid = 'tbl_include_pk'::regclass ORDER BY c.relname; + +CREATE TABLE tbl_include_box (c1 int, c2 int, c3 int, c4 box); +INSERT INTO tbl_include_box SELECT 1, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +CREATE UNIQUE INDEX tbl_include_box_idx_unique ON tbl_include_box using btree (c1, c2) INCLUDE (c3, c4); +ALTER TABLE tbl_include_box add PRIMARY KEY USING INDEX tbl_include_box_idx_unique; +SELECT pg_get_indexdef(i.indexrelid) +FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid +WHERE i.indrelid = 'tbl_include_box'::regclass ORDER BY c.relname; + +-- PK constraint. Must fail. +CREATE TABLE tbl_include_box_pk (c1 int, c2 int, c3 int, c4 box); +INSERT INTO tbl_include_box_pk SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +ALTER TABLE tbl_include_box_pk add PRIMARY KEY (c1, c2) INCLUDE (c3, c4); + + +/* + * 2. Test CREATE TABLE with constraint + */ +CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, + CONSTRAINT covering UNIQUE(c1,c2) INCLUDE(c3,c4)); +SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid; +SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid; +-- ensure that constraint works +INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +DROP TABLE tbl; + +CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, + CONSTRAINT covering PRIMARY KEY(c1,c2) INCLUDE(c3,c4)); +SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid; +SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid; +-- ensure that constraint works +INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,300) AS x; +explain (costs off) +select * from tbl where (c1,c2,c3) < (2,5,1); -- YB: output differs from upstream PG likely because, in YB, pk index = table +select * from tbl where (c1,c2,c3) < (2,5,1); +-- row comparison that compares high key at page boundary +SET enable_seqscan = off; +explain (costs off) +select * from tbl where (c1,c2,c3) < (262,1,1) limit 1; -- YB: output differs from upstream PG likely because, in YB, pk index = table +select * from tbl where (c1,c2,c3) < (262,1,1) limit 1; +DROP TABLE tbl; +RESET enable_seqscan; + +CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, + UNIQUE(c1,c2) INCLUDE(c3,c4)); +SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid; +SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid; +-- ensure that constraint works +INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +DROP TABLE tbl; + +CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, + PRIMARY KEY(c1,c2) INCLUDE(c3,c4)); +SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid; +SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid; +-- ensure that constraint works +INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,10) AS x; +DROP TABLE tbl; + +CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, + EXCLUDE USING btree (c1 WITH =) INCLUDE(c3,c4)); +/* YB: disabled because above CREATE TABLE failed +SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid; +SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid; +-- ensure that constraint works +INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,10) AS x; +DROP TABLE tbl; +*/ -- YB + +/* + * 3.0 Test ALTER TABLE DROP COLUMN. + * Any column deletion leads to index deletion. + */ +CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 int); +CREATE UNIQUE INDEX tbl_idx ON tbl using btree(c1, c2, c3, c4); +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +ALTER TABLE tbl DROP COLUMN c3; +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +DROP TABLE tbl; + +/* + * 3.1 Test ALTER TABLE DROP COLUMN. + * Included column deletion leads to the index deletion, + * AS well AS key columns deletion. It's explained in documentation. + */ +CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box); +CREATE UNIQUE INDEX tbl_idx ON tbl using btree(c1, c2) INCLUDE(c3,c4); +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +ALTER TABLE tbl DROP COLUMN c3; +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +DROP TABLE tbl; + +/* + * 3.2 Test ALTER TABLE DROP COLUMN. + * Included column deletion leads to the index deletion. + * AS well AS key columns deletion. It's explained in documentation. + */ +CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4)); +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +ALTER TABLE tbl DROP COLUMN c3; +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +ALTER TABLE tbl DROP COLUMN c1; +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +DROP TABLE tbl; + +/* + * 3.3 Test ALTER TABLE SET STATISTICS + */ +CREATE TABLE tbl (c1 int, c2 int); +CREATE INDEX tbl_idx ON tbl (c1, (c1+0)) INCLUDE (c2); +ALTER INDEX tbl_idx ALTER COLUMN 1 SET STATISTICS 1000; +ALTER INDEX tbl_idx ALTER COLUMN 2 SET STATISTICS 1000; +ALTER INDEX tbl_idx ALTER COLUMN 3 SET STATISTICS 1000; +ALTER INDEX tbl_idx ALTER COLUMN 4 SET STATISTICS 1000; +DROP TABLE tbl; + +/* + * 4. CREATE INDEX CONCURRENTLY + */ +CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4)); +INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,1000) AS x; +CREATE UNIQUE INDEX CONCURRENTLY on tbl (c1, c2) INCLUDE (c3, c4); +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +DROP TABLE tbl; + + +/* + * 5. REINDEX + */ +CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4)); +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +ALTER TABLE tbl DROP COLUMN c3; +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +REINDEX INDEX tbl_c1_c2_c3_c4_key; +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +ALTER TABLE tbl DROP COLUMN c1; +SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; +DROP TABLE tbl; + +/* + * 7. Check various AMs. All but btree and lsm must fail. -- YB: updated comment + */ +CREATE TABLE tbl (c1 int,c2 int, c3 box, c4 box); +CREATE INDEX on tbl USING brin(c1, c2) INCLUDE (c3, c4); +CREATE INDEX on tbl USING gist(c3) INCLUDE (c1, c4); +CREATE INDEX on tbl USING spgist(c3) INCLUDE (c4); +CREATE INDEX on tbl USING gin(c1, c2) INCLUDE (c3, c4); +CREATE INDEX on tbl USING hash(c1, c2) INCLUDE (c3, c4); +CREATE INDEX on tbl USING rtree(c3) INCLUDE (c1, c4); +CREATE INDEX on tbl USING btree(c1, c2) INCLUDE (c3, c4); +DROP TABLE tbl; + +/* + * 8. Update, delete values in indexed table. + */ +CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box); +INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +CREATE UNIQUE INDEX tbl_idx_unique ON tbl using btree(c1, c2) INCLUDE (c3,c4); +UPDATE tbl SET c1 = 100 WHERE c1 = 2; +UPDATE tbl SET c1 = 1 WHERE c1 = 3; +-- should fail +UPDATE tbl SET c2 = 2 WHERE c1 = 1; +UPDATE tbl SET c3 = 1; +DELETE FROM tbl WHERE c1 = 5 OR c3 = 12; +DROP TABLE tbl; + +/* + * 9. Alter column type. + */ +CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4)); +INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x; +ALTER TABLE tbl ALTER c1 TYPE bigint; +ALTER TABLE tbl ALTER c3 TYPE bigint; +\d tbl +DROP TABLE tbl; diff --git a/src/test/regress/sql/yb.port.indexing.sql b/src/test/regress/sql/yb.port.indexing.sql new file mode 100644 index 0000000000000..0c89a3a5e92ee --- /dev/null +++ b/src/test/regress/sql/yb.port.indexing.sql @@ -0,0 +1,781 @@ +-- Creating an index on a partitioned table makes the partitions +-- automatically get the index +create table idxpart (a int, b int, c text) partition by range (a); +create table idxpart1 partition of idxpart for values from (0) to (10); +create table idxpart2 partition of idxpart for values from (10) to (100) + partition by range (b); +create table idxpart21 partition of idxpart2 for values from (0) to (100); +create index on idxpart (a); +select relname, relkind, inhparent::regclass + from pg_class left join pg_index ix on (indexrelid = oid) + left join pg_inherits on (ix.indexrelid = inhrelid) + where relname like 'idxpart%' order by relname; +drop table idxpart; + +-- Some unsupported features +create table idxpart (a int, b int, c text) partition by range (a); +create table idxpart1 partition of idxpart for values from (0) to (10); +create index concurrently on idxpart (a); +drop table idxpart; + +-- Verify bugfix with query on indexed partitioned table with no partitions +-- https://postgr.es/m/20180124162006.pmapfiznhgngwtjf@alvherre.pgsql +CREATE TABLE idxpart (col1 INT) PARTITION BY RANGE (col1); +CREATE INDEX ON idxpart (col1); +CREATE TABLE idxpart_two (col2 INT); +SELECT col2 FROM idxpart_two fk LEFT OUTER JOIN idxpart pk ON (col1 = col2); +DROP table idxpart, idxpart_two; + +-- Verify bugfix with index rewrite on ALTER TABLE / SET DATA TYPE +-- https://postgr.es/m/CAKcux6mxNCGsgATwf5CGMF8g4WSupCXicCVMeKUTuWbyxHOMsQ@mail.gmail.com +CREATE TABLE idxpart (a INT, b TEXT, c INT) PARTITION BY RANGE(a); +CREATE TABLE idxpart1 PARTITION OF idxpart FOR VALUES FROM (MINVALUE) TO (MAXVALUE); +CREATE INDEX partidx_abc_idx ON idxpart (a, b, c); +INSERT INTO idxpart (a, b, c) SELECT i, i, i FROM generate_series(1, 50) i; +ALTER TABLE idxpart ALTER COLUMN c TYPE numeric; +DROP TABLE idxpart; + +-- If a table without index is attached as partition to a table with +-- an index, the index is automatically created +create table idxpart (a int, b int, c text) partition by range (a); +create index idxparti on idxpart (a); +create index idxparti2 on idxpart (b, c); +create table idxpart1 (like idxpart); +\d idxpart1 +alter table idxpart attach partition idxpart1 for values from (0) to (10); +\d idxpart1 +\d+ idxpart1_a_idx +\d+ idxpart1_b_c_idx +drop table idxpart; + +-- If a partition already has an index, don't create a duplicative one +create table idxpart (a int, b int) partition by range (a, b); +create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10); +create index on idxpart1 (a, b); +create index on idxpart (a, b); +\d idxpart1 +select relname, relkind, inhparent::regclass + from pg_class left join pg_index ix on (indexrelid = oid) + left join pg_inherits on (ix.indexrelid = inhrelid) + where relname like 'idxpart%' order by relname; +drop table idxpart; + +-- DROP behavior for partitioned indexes +create table idxpart (a int) partition by range (a); +create index on idxpart (a); +create table idxpart1 partition of idxpart for values from (0) to (10); +-- YB Note: Disabling the below test temporarily because each run of the test can +-- throw one of two errors. The error message displayed contains the first dependency +-- resulting from the scan of pg_depend preventing the delete of the index. The index +-- depends on the partition table itself and on the index created on the parent +-- table, and either of the two error messages are possible. +-- drop index idxpart1_a_idx; -- no way +drop index idxpart_a_idx; -- both indexes go away +select relname, relkind from pg_class + where relname like 'idxpart%' order by relname; +create index on idxpart (a); +drop table idxpart1; -- the index on partition goes away too +select relname, relkind from pg_class + where relname like 'idxpart%' order by relname; +drop table idxpart; + +-- ALTER INDEX .. ATTACH, error cases +create table idxpart (a int, b int) partition by range (a, b); +create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10); +create index idxpart_a_b_idx on only idxpart (a, b); +create index idxpart1_a_b_idx on idxpart1 (a, b); +create index idxpart1_tst1 on idxpart1 (b, a); +create index idxpart1_tst2 on idxpart1 using hash (a); +create index idxpart1_tst3 on idxpart1 (a, b) where a > 10; + +alter index idxpart attach partition idxpart1; +alter index idxpart_a_b_idx attach partition idxpart1; +alter index idxpart_a_b_idx attach partition idxpart_a_b_idx; +alter index idxpart_a_b_idx attach partition idxpart1_b_idx; +alter index idxpart_a_b_idx attach partition idxpart1_tst1; +alter index idxpart_a_b_idx attach partition idxpart1_tst2; +alter index idxpart_a_b_idx attach partition idxpart1_tst3; +-- OK +alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx; +alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx; -- quiet + +-- reject dupe +create index idxpart1_2_a_b on idxpart1 (a, b); +alter index idxpart_a_b_idx attach partition idxpart1_2_a_b; +drop table idxpart; +-- make sure everything's gone +select indexrelid::regclass, indrelid::regclass + from pg_index where indexrelid::regclass::text like 'idxpart%'; + +-- Don't auto-attach incompatible indexes +create table idxpart (a int, b int) partition by range (a); +create table idxpart1 (a int, b int); +-- YB Note: "hash" index is automatically converted to "lsm". +create index on idxpart1 using hash (a); +\d idxpart1_a_idx +create index on idxpart1 (a) where b > 1; +create index on idxpart1 ((a + 0)); +create index on idxpart1 (a, a); +create index on idxpart (a); +-- YB Note: Since idxpart1_a_idx is converted to "lsm", it now has the same +-- signature as the index on the parent table. Hence, when idxpart1 is +-- attached as a child partition, a separate index is not created on idxpart1 +-- to match the parent. Instead idxpart1_a_idx is attached to the partitioned +-- index on the parent. +alter table idxpart attach partition idxpart1 for values from (0) to (1000); +\d idxpart1 +\d idxpart1_a_idx +drop table idxpart; + +-- If CREATE INDEX ONLY, don't create indexes on partitions; and existing +-- indexes on partitions don't change parent. ALTER INDEX ATTACH can change +-- the parent after the fact. +create table idxpart (a int) partition by range (a); +create table idxpart1 partition of idxpart for values from (0) to (100); +create table idxpart2 partition of idxpart for values from (100) to (1000) + partition by range (a); +create table idxpart21 partition of idxpart2 for values from (100) to (200); +create table idxpart22 partition of idxpart2 for values from (200) to (300); +create index on idxpart22 (a); +create index on only idxpart2 (a); +create index on idxpart (a); +-- Here we expect that idxpart1 and idxpart2 have a new index, but idxpart21 +-- does not; also, idxpart22 is not attached. +\d idxpart1 +\d idxpart2 +\d idxpart21 +select indexrelid::regclass, indrelid::regclass, inhparent::regclass + from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) +where indexrelid::regclass::text like 'idxpart%' + order by indexrelid::regclass::text collate "C"; +alter index idxpart2_a_idx attach partition idxpart22_a_idx; +select indexrelid::regclass, indrelid::regclass, inhparent::regclass + from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) +where indexrelid::regclass::text like 'idxpart%' + order by indexrelid::regclass::text collate "C"; +-- attaching idxpart22 is not enough to set idxpart22_a_idx valid ... +alter index idxpart2_a_idx attach partition idxpart22_a_idx; +\d idxpart2 +-- ... but this one is. +create index on idxpart21 (a); +alter index idxpart2_a_idx attach partition idxpart21_a_idx; +\d idxpart2 +drop table idxpart; + +-- When a table is attached a partition and it already has an index, a +-- duplicate index should not get created, but rather the index becomes +-- attached to the parent's index. +create table idxpart (a int, b int, c text) partition by range (a); +create index idxparti on idxpart (a); +create index idxparti2 on idxpart (b, c); +create table idxpart1 (like idxpart including indexes); +\d idxpart1 +select relname, relkind, inhparent::regclass + from pg_class left join pg_index ix on (indexrelid = oid) + left join pg_inherits on (ix.indexrelid = inhrelid) + where relname like 'idxpart%' order by relname; +alter table idxpart attach partition idxpart1 for values from (0) to (10); +\d idxpart1 +select relname, relkind, inhparent::regclass + from pg_class left join pg_index ix on (indexrelid = oid) + left join pg_inherits on (ix.indexrelid = inhrelid) + where relname like 'idxpart%' order by relname; +drop table idxpart; + +-- Verify that attaching an invalid index does not mark the parent index valid. +-- On the other hand, attaching a valid index marks not only its direct +-- ancestor valid, but also any indirect ancestor that was only missing the one +-- that was just made valid +create table idxpart (a int, b int) partition by range (a); +create table idxpart1 partition of idxpart for values from (1) to (1000) partition by range (a); +create table idxpart11 partition of idxpart1 for values from (1) to (100); +create index on only idxpart1 (a); +create index on only idxpart (a); +-- this results in two invalid indexes: +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +-- idxpart1_a_idx is not valid, so idxpart_a_idx should not become valid: +alter index idxpart_a_idx attach partition idxpart1_a_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +-- after creating and attaching this, both idxpart1_a_idx and idxpart_a_idx +-- should become valid +create index on idxpart11 (a); +alter index idxpart1_a_idx attach partition idxpart11_a_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +drop table idxpart; + +-- verify dependency handling during ALTER TABLE DETACH PARTITION +create table idxpart (a int) partition by range (a); +create table idxpart1 (like idxpart); +create index on idxpart1 (a); +create index on idxpart (a); +create table idxpart2 (like idxpart); +alter table idxpart attach partition idxpart1 for values from (0000) to (1000); +alter table idxpart attach partition idxpart2 for values from (1000) to (2000); +create table idxpart3 partition of idxpart for values from (2000) to (3000); +select relname, relkind from pg_class where relname like 'idxpart%' order by relname; +-- a) after detaching partitions, the indexes can be dropped independently +alter table idxpart detach partition idxpart1; +alter table idxpart detach partition idxpart2; +alter table idxpart detach partition idxpart3; +drop index idxpart1_a_idx; +drop index idxpart2_a_idx; +drop index idxpart3_a_idx; +select relname, relkind from pg_class where relname like 'idxpart%' order by relname; +drop table idxpart, idxpart1, idxpart2, idxpart3; +select relname, relkind from pg_class where relname like 'idxpart%' order by relname; + +create table idxpart (a int) partition by range (a); +create table idxpart1 (like idxpart); +create index on idxpart1 (a); +create index on idxpart (a); +create table idxpart2 (like idxpart); +alter table idxpart attach partition idxpart1 for values from (0000) to (1000); +alter table idxpart attach partition idxpart2 for values from (1000) to (2000); +create table idxpart3 partition of idxpart for values from (2000) to (3000); +-- b) after detaching, dropping the index on parent does not remove the others +select relname, relkind from pg_class where relname like 'idxpart%' order by relname; +alter table idxpart detach partition idxpart1; +alter table idxpart detach partition idxpart2; +alter table idxpart detach partition idxpart3; +drop index idxpart_a_idx; +select relname, relkind from pg_class where relname like 'idxpart%' order by relname; +drop table idxpart, idxpart1, idxpart2, idxpart3; +select relname, relkind from pg_class where relname like 'idxpart%' order by relname; + +-- Verify that expression indexes inherit correctly +create table idxpart (a int, b int) partition by range (a); +create table idxpart1 (like idxpart); +create index on idxpart1 ((a + b)); +create index on idxpart ((a + b)); +create table idxpart2 (like idxpart); +alter table idxpart attach partition idxpart1 for values from (0000) to (1000); +alter table idxpart attach partition idxpart2 for values from (1000) to (2000); +create table idxpart3 partition of idxpart for values from (2000) to (3000); +select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef + from pg_class join pg_inherits on inhrelid = oid, + lateral pg_get_indexdef(pg_class.oid) + where relkind in ('i', 'I') and relname like 'idxpart%' order by relname; +drop table idxpart; + +-- Verify behavior for collation (mis)matches +create table idxpart (a text) partition by range (a); +create table idxpart1 (like idxpart); +create table idxpart2 (like idxpart); +create index on idxpart2 (a collate "POSIX"); +create index on idxpart2 (a); +create index on idxpart2 (a collate "C"); +alter table idxpart attach partition idxpart1 for values from ('aaa') to ('bbb'); +alter table idxpart attach partition idxpart2 for values from ('bbb') to ('ccc'); +create table idxpart3 partition of idxpart for values from ('ccc') to ('ddd'); +create index on idxpart (a collate "C"); +create table idxpart4 partition of idxpart for values from ('ddd') to ('eee'); +select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef + from pg_class left join pg_inherits on inhrelid = oid, + lateral pg_get_indexdef(pg_class.oid) + where relkind in ('i', 'I') and relname like 'idxpart%' order by relname; +drop table idxpart; + +-- Verify behavior for opclass (mis)matches +create table idxpart (a text) partition by range (a); +create table idxpart1 (like idxpart); +create table idxpart2 (like idxpart); +create index on idxpart2 (a); +alter table idxpart attach partition idxpart1 for values from ('aaa') to ('bbb'); +alter table idxpart attach partition idxpart2 for values from ('bbb') to ('ccc'); +create table idxpart3 partition of idxpart for values from ('ccc') to ('ddd'); +create index on idxpart (a text_pattern_ops); +create table idxpart4 partition of idxpart for values from ('ddd') to ('eee'); +-- must *not* have attached the index we created on idxpart2 +select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef + from pg_class left join pg_inherits on inhrelid = oid, + lateral pg_get_indexdef(pg_class.oid) + where relkind in ('i', 'I') and relname like 'idxpart%' order by relname; +drop index idxpart_a_idx; +create index on only idxpart (a text_pattern_ops); +-- must reject +alter index idxpart_a_idx attach partition idxpart2_a_idx; +drop table idxpart; + +-- Verify that attaching indexes maps attribute numbers correctly +create table idxpart (col1 int, a int, col2 int, b int) partition by range (a); +create table idxpart1 (b int, col1 int, col2 int, col3 int, a int); +alter table idxpart drop column col1, drop column col2; +alter table idxpart1 drop column col1, drop column col2, drop column col3; +alter table idxpart attach partition idxpart1 for values from (0) to (1000); +create index idxpart_1_idx on only idxpart (b, a); +create index idxpart1_1_idx on idxpart1 (b, a); +create index idxpart1_1b_idx on idxpart1 (b); +-- test expressions and partial-index predicate, too +create index idxpart_2_idx on only idxpart ((b + a)) where a > 1; +create index idxpart1_2_idx on idxpart1 ((b + a)) where a > 1; +create index idxpart1_2b_idx on idxpart1 ((a + b)) where a > 1; +create index idxpart1_2c_idx on idxpart1 ((b + a)) where b > 1; +alter index idxpart_1_idx attach partition idxpart1_1b_idx; -- fail +alter index idxpart_1_idx attach partition idxpart1_1_idx; +alter index idxpart_2_idx attach partition idxpart1_2b_idx; -- fail +alter index idxpart_2_idx attach partition idxpart1_2c_idx; -- fail +alter index idxpart_2_idx attach partition idxpart1_2_idx; -- ok +select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef + from pg_class left join pg_inherits on inhrelid = oid, + lateral pg_get_indexdef(pg_class.oid) + where relkind in ('i', 'I') and relname like 'idxpart%' order by relname; +drop table idxpart; + +-- Make sure the partition columns are mapped correctly +create table idxpart (a int, b int, c text) partition by range (a); +create index idxparti on idxpart (a); +create index idxparti2 on idxpart (c, b); +create table idxpart1 (c text, a int, b int); +alter table idxpart attach partition idxpart1 for values from (0) to (10); +create table idxpart2 (c text, a int, b int); +create index on idxpart2 (a); +create index on idxpart2 (c, b); +alter table idxpart attach partition idxpart2 for values from (10) to (20); +select c.relname, pg_get_indexdef(indexrelid) + from pg_class c join pg_index i on c.oid = i.indexrelid + where indrelid::regclass::text like 'idxpart%' + order by indexrelid::regclass::text collate "C"; +drop table idxpart; + +-- Verify that columns are mapped correctly in expression indexes +create table idxpart (col1 int, col2 int, a int, b int) partition by range (a); +create table idxpart1 (col2 int, b int, col1 int, a int); +create table idxpart2 (col1 int, col2 int, b int, a int); +alter table idxpart drop column col1, drop column col2; +alter table idxpart1 drop column col1, drop column col2; +alter table idxpart2 drop column col1, drop column col2; +create index on idxpart2 (abs(b)); +alter table idxpart attach partition idxpart2 for values from (0) to (1); +create index on idxpart (abs(b)); +create index on idxpart ((b + 1)); +alter table idxpart attach partition idxpart1 for values from (1) to (2); +select c.relname, pg_get_indexdef(indexrelid) + from pg_class c join pg_index i on c.oid = i.indexrelid + where indrelid::regclass::text like 'idxpart%' + order by indexrelid::regclass::text collate "C"; +drop table idxpart; + +-- Verify that columns are mapped correctly for WHERE in a partial index +create table idxpart (col1 int, a int, col3 int, b int) partition by range (a); +alter table idxpart drop column col1, drop column col3; +create table idxpart1 (col1 int, col2 int, col3 int, col4 int, b int, a int); +alter table idxpart1 drop column col1, drop column col2, drop column col3, drop column col4; +alter table idxpart attach partition idxpart1 for values from (0) to (1000); +create table idxpart2 (col1 int, col2 int, b int, a int); +create index on idxpart2 (a) where b > 1000; +alter table idxpart2 drop column col1, drop column col2; +alter table idxpart attach partition idxpart2 for values from (1000) to (2000); +create index on idxpart (a) where b > 1000; +select c.relname, pg_get_indexdef(indexrelid) + from pg_class c join pg_index i on c.oid = i.indexrelid + where indrelid::regclass::text like 'idxpart%' + order by indexrelid::regclass::text collate "C"; +drop table idxpart; + +-- Column number mapping: dropped columns in the partition +create table idxpart1 (drop_1 int, drop_2 int, col_keep int, drop_3 int); +alter table idxpart1 drop column drop_1; +alter table idxpart1 drop column drop_2; +alter table idxpart1 drop column drop_3; +create index on idxpart1 (col_keep); +create table idxpart (col_keep int) partition by range (col_keep); +create index on idxpart (col_keep); +alter table idxpart attach partition idxpart1 for values from (0) to (1000); +\d idxpart +\d idxpart1 +select attrelid::regclass, attname, attnum from pg_attribute + where attrelid::regclass::text like 'idxpart%' and attnum > 0 + order by attrelid::regclass, attnum; +drop table idxpart; + +-- Column number mapping: dropped columns in the parent table +create table idxpart(drop_1 int, drop_2 int, col_keep int, drop_3 int) partition by range (col_keep); +alter table idxpart drop column drop_1; +alter table idxpart drop column drop_2; +alter table idxpart drop column drop_3; +create table idxpart1 (col_keep int); +create index on idxpart1 (col_keep); +create index on idxpart (col_keep); +alter table idxpart attach partition idxpart1 for values from (0) to (1000); +\d idxpart +\d idxpart1 +select attrelid::regclass, attname, attnum from pg_attribute + where attrelid::regclass::text like 'idxpart%' and attnum > 0 + order by attrelid::regclass, attnum; +drop table idxpart; + +-- +-- Constraint-related indexes +-- + +-- Verify that it works to add primary key / unique to partitioned tables +create table idxpart (a int primary key, b int) partition by range (a); +\d idxpart +-- multiple primary key on child should fail +create table failpart partition of idxpart (b primary key) for values from (0) to (100); +drop table idxpart; +-- primary key on child is okay if there's no PK in the parent, though +create table idxpart (a int) partition by range (a); +create table idxpart1pk partition of idxpart (a primary key) for values from (0) to (100); +\d idxpart1pk +drop table idxpart; + +-- Failing to use the full partition key is not allowed +create table idxpart (a int unique, b int) partition by range (a, b); +create table idxpart (a int, b int unique) partition by range (a, b); +create table idxpart (a int primary key, b int) partition by range (b, a); +create table idxpart (a int, b int primary key) partition by range (b, a); + +-- OK if you use them in some other order +create table idxpart (a int, b int, c text, primary key (a, b, c)) partition by range (b, c, a); +drop table idxpart; + +-- not other types of index-based constraints +create table idxpart (a int, exclude (a with = )) partition by range (a); + +-- no expressions in partition key for PK/UNIQUE +create table idxpart (a int primary key, b int) partition by range ((b + a)); +create table idxpart (a int unique, b int) partition by range ((b + a)); + +-- use ALTER TABLE to add a primary key +create table idxpart (a int, b int, c text) partition by range (a, b); +alter table idxpart add primary key (a); -- not an incomplete one though +alter table idxpart add primary key (a, b); -- this works +\d idxpart +create table idxpart1 partition of idxpart for values from (0, 0) to (1000, 1000); +\d idxpart1 +drop table idxpart; + +-- use ALTER TABLE to add a unique constraint +create table idxpart (a int, b int) partition by range (a, b); +alter table idxpart add unique (a); -- not an incomplete one though +alter table idxpart add unique (b, a); -- this works +\d idxpart +drop table idxpart; + +-- Exclusion constraints cannot be added +create table idxpart (a int, b int) partition by range (a); +alter table idxpart add exclude (a with =); +drop table idxpart; + +-- When (sub)partitions are created, they also contain the constraint +create table idxpart (a int, b int, primary key (a, b)) partition by range (a, b); +create table idxpart1 partition of idxpart for values from (1, 1) to (10, 10); +create table idxpart2 partition of idxpart for values from (10, 10) to (20, 20) + partition by range (b); +create table idxpart21 partition of idxpart2 for values from (10) to (15); +create table idxpart22 partition of idxpart2 for values from (15) to (20); +create table idxpart3 (b int not null, a int not null); +alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 30); +select conname, contype, conrelid::regclass, conindid::regclass, conkey + from pg_constraint where conrelid::regclass::text like 'idxpart%' + order by conname; +drop table idxpart; + +-- Verify that multi-layer partitioning honors the requirement that all +-- columns in the partition key must appear in primary/unique key +create table idxpart (a int, b int, primary key (a)) partition by range (a); +create table idxpart2 partition of idxpart +for values from (0) to (1000) partition by range (b); -- fail +drop table idxpart; + +-- Ditto for the ATTACH PARTITION case +create table idxpart (a int unique, b int) partition by range (a); +create table idxpart1 (a int not null, b int, unique (a, b)) + partition by range (a, b); +alter table idxpart attach partition idxpart1 for values from (1) to (1000); +DROP TABLE idxpart, idxpart1; + +-- Multi-layer partitioning works correctly in this case: +create table idxpart (a int, b int, primary key (a, b)) partition by range (a); +create table idxpart2 partition of idxpart for values from (0) to (1000) partition by range (b); +create table idxpart21 partition of idxpart2 for values from (0) to (1000); +select conname, contype, conrelid::regclass, conindid::regclass, conkey + from pg_constraint where conrelid::regclass::text like 'idxpart%' + order by conname; +drop table idxpart; + +-- If a partitioned table has a unique/PK constraint, then it's not possible +-- to drop the corresponding constraint in the children; nor it's possible +-- to drop the indexes individually. Dropping the constraint in the parent +-- gets rid of the lot. +create table idxpart (i int) partition by hash (i); +create table idxpart0 partition of idxpart (i) for values with (modulus 2, remainder 0); +create table idxpart1 partition of idxpart (i) for values with (modulus 2, remainder 1); +alter table idxpart0 add primary key(i); +alter table idxpart add primary key(i); +select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid, + conname, conislocal, coninhcount, connoinherit, convalidated + from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) + left join pg_constraint con on (idx.indexrelid = con.conindid) + where indrelid::regclass::text like 'idxpart%' + order by indexrelid::regclass::text collate "C"; +drop index idxpart0_pkey; -- fail +drop index idxpart1_pkey; -- fail +alter table idxpart0 drop constraint idxpart0_pkey; -- fail +alter table idxpart1 drop constraint idxpart1_pkey; -- fail +alter table idxpart drop constraint idxpart_pkey; -- ok +select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid, + conname, conislocal, coninhcount, connoinherit, convalidated + from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) + left join pg_constraint con on (idx.indexrelid = con.conindid) + where indrelid::regclass::text like 'idxpart%' + order by indexrelid::regclass::text collate "C"; +drop table idxpart; + +-- If the partition to be attached already has a primary key, fail if +-- it doesn't match the parent's PK. +CREATE TABLE idxpart (c1 INT PRIMARY KEY, c2 INT, c3 VARCHAR(10)) PARTITION BY RANGE(c1); +CREATE TABLE idxpart1 (LIKE idxpart); +ALTER TABLE idxpart1 ADD PRIMARY KEY (c1, c2); +ALTER TABLE idxpart ATTACH PARTITION idxpart1 FOR VALUES FROM (100) TO (200); +DROP TABLE idxpart, idxpart1; + +-- Ditto if there is some distance between the PKs (subpartitioning) +create table idxpart (a int, b int, primary key (a)) partition by range (a); +create table idxpart1 (a int not null, b int) partition by range (a); +create table idxpart11 (a int not null, b int primary key); +alter table idxpart1 attach partition idxpart11 for values from (0) to (1000); +alter table idxpart attach partition idxpart1 for values from (0) to (10000); +drop table idxpart, idxpart1, idxpart11; + +-- If a partitioned table has a constraint whose index is not valid, +-- attaching a missing partition makes it valid. +create table idxpart (a int) partition by range (a); +create table idxpart0 (like idxpart); +alter table idxpart0 add primary key (a); +alter table idxpart attach partition idxpart0 for values from (0) to (1000); +alter table only idxpart add primary key (a); +select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid, + conname, conislocal, coninhcount, connoinherit, convalidated + from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) + left join pg_constraint con on (idx.indexrelid = con.conindid) + where indrelid::regclass::text like 'idxpart%' + order by indexrelid::regclass::text collate "C"; +alter index idxpart_pkey attach partition idxpart0_pkey; +select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid, + conname, conislocal, coninhcount, connoinherit, convalidated + from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) + left join pg_constraint con on (idx.indexrelid = con.conindid) + where indrelid::regclass::text like 'idxpart%' + order by indexrelid::regclass::text collate "C"; +drop table idxpart; + +-- if a partition has a unique index without a constraint, does not attach +-- automatically; creates a new index instead. +create table idxpart (a int, b int) partition by range (a); +create table idxpart1 (a int not null, b int); +create unique index on idxpart1 (a); +alter table idxpart add primary key (a); +alter table idxpart attach partition idxpart1 for values from (1) to (1000); +select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid, + conname, conislocal, coninhcount, connoinherit, convalidated + from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) + left join pg_constraint con on (idx.indexrelid = con.conindid) + where indrelid::regclass::text like 'idxpart%' + order by indexrelid::regclass::text collate "C"; +drop table idxpart; + +-- Can't attach an index without a corresponding constraint +create table idxpart (a int, b int) partition by range (a); +create table idxpart1 (a int not null, b int); +create unique index on idxpart1 (a); +alter table idxpart attach partition idxpart1 for values from (1) to (1000); +alter table only idxpart add primary key (a); +alter index idxpart_pkey attach partition idxpart1_a_idx; -- fail +drop table idxpart; + +-- Test that unique constraints are working +create table idxpart (a int, b text, primary key (a, b)) partition by range (a); +create table idxpart1 partition of idxpart for values from (0) to (100000); +create table idxpart2 (c int, like idxpart); +insert into idxpart2 (c, a, b) values (42, 572814, 'inserted first'); +alter table idxpart2 drop column c; +create unique index on idxpart (a); +alter table idxpart attach partition idxpart2 for values from (100000) to (1000000); +insert into idxpart values (0, 'zero'), (42, 'life'), (2^16, 'sixteen'); +insert into idxpart select 2^g, format('two to power of %s', g) from generate_series(15, 17) g; +insert into idxpart values (16, 'sixteen'); +insert into idxpart (b, a) values ('one', 142857), ('two', 285714); +insert into idxpart select a * 2, b || b from idxpart where a between 2^16 and 2^19; +insert into idxpart values (572814, 'five'); +insert into idxpart values (857142, 'six'); +select tableoid::regclass, * from idxpart order by a; +drop table idxpart; + +-- test fastpath mechanism for index insertion +create table fastpath (a int, b text, c numeric); +create unique index fpindex1 on fastpath(a); + +insert into fastpath values (1, 'b1', 100.00); +insert into fastpath values (1, 'b1', 100.00); -- unique key check + +truncate fastpath; +insert into fastpath select generate_series(1,10000), 'b', 100; + +-- vacuum the table so as to improve chances of index-only scans. we can't +-- guarantee if index-only scans will be picked up in all cases or not, but +-- that fuzziness actually helps the test. +vacuum fastpath; + +set enable_seqscan to false; +set enable_bitmapscan to false; + +select sum(a) from fastpath where a = 6456; +select sum(a) from fastpath where a >= 5000 and a < 5700; + +-- drop the only index on the table and compute hashes for +-- a few queries which orders the results in various different ways. +drop index fpindex1; +truncate fastpath; +insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y; +select md5(string_agg(a::text, b order by a, b asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; + +-- now create a multi-column index with both column asc +create index fpindex2 on fastpath(a, b); +truncate fastpath; +insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y; +-- again, vacuum here either forces index-only scans or creates fuzziness +vacuum fastpath; +select md5(string_agg(a::text, b order by a, b asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; + +-- same queries with a different kind of index now. the final result must not +-- change irrespective of what kind of index we have. +drop index fpindex2; +create index fpindex3 on fastpath(a desc, b asc); +truncate fastpath; +insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y; +vacuum fastpath; +select md5(string_agg(a::text, b order by a, b asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; + +-- repeat again +drop index fpindex3; +create index fpindex4 on fastpath(a asc, b desc); +truncate fastpath; +insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y; +vacuum fastpath; +select md5(string_agg(a::text, b order by a, b asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; + +-- and again, this time indexing by (b, a). Note that column "b" has non-unique +-- values. +drop index fpindex4; +create index fpindex5 on fastpath(b asc, a desc); +truncate fastpath; +insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y; +vacuum fastpath; +select md5(string_agg(a::text, b order by a, b asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; + +-- one last time +drop index fpindex5; +create index fpindex6 on fastpath(b desc, a desc); +truncate fastpath; +insert into fastpath select y.x, 'b' || (y.x/10)::text, 100 from (select generate_series(1,10000) as x) y; +vacuum fastpath; +select md5(string_agg(a::text, b order by a, b asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by a desc, b desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a desc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; +select md5(string_agg(a::text, b order by b, a asc)) from fastpath + where a >= 1000 and a < 2000 and b > 'b1' and b < 'b3'; + +drop table fastpath; + +-- intentionally leave some objects around +create table idxpart (a int) partition by range (a); +create table idxpart1 partition of idxpart for values from (0) to (100); +create table idxpart2 partition of idxpart for values from (100) to (1000) + partition by range (a); +create table idxpart21 partition of idxpart2 for values from (100) to (200); +create table idxpart22 partition of idxpart2 for values from (200) to (300); +create index on idxpart22 (a); +create index on only idxpart2 (a); +alter index idxpart2_a_idx attach partition idxpart22_a_idx; +create index on idxpart (a); +create table idxpart_another (a int, b int, primary key (a, b)) partition by range (a); +create table idxpart_another_1 partition of idxpart_another for values from (0) to (100); + +-- Test that covering partitioned indexes work in various cases +create table covidxpart (a int, b int) partition by list (a); +create unique index on covidxpart (a) include (b); +create table covidxpart1 partition of covidxpart for values in (1); +create table covidxpart2 partition of covidxpart for values in (2); +insert into covidxpart values (1, 1); +insert into covidxpart values (1, 1); +create table covidxpart3 (b int, c int, a int); +alter table covidxpart3 drop c; +alter table covidxpart attach partition covidxpart3 for values in (3); +insert into covidxpart values (3, 1); +insert into covidxpart values (3, 1); +create table covidxpart4 (b int, a int); +create unique index on covidxpart4 (a) include (b); +create unique index on covidxpart4 (a); +alter table covidxpart attach partition covidxpart4 for values in (4); +insert into covidxpart values (4, 1); +insert into covidxpart values (4, 1); +create unique index on covidxpart (b) include (a); -- should fail + +-- check that detaching a partition also detaches the primary key constraint +create table parted_pk_detach_test (a int primary key) partition by list (a); +create table parted_pk_detach_test1 partition of parted_pk_detach_test for values in (1); +alter table parted_pk_detach_test1 drop constraint parted_pk_detach_test1_pkey; -- should fail +alter table parted_pk_detach_test detach partition parted_pk_detach_test1; +alter table parted_pk_detach_test1 drop constraint parted_pk_detach_test1_pkey; +drop table parted_pk_detach_test, parted_pk_detach_test1; +create table parted_uniq_detach_test (a int unique) partition by list (a); +create table parted_uniq_detach_test1 partition of parted_uniq_detach_test for values in (1); +alter table parted_uniq_detach_test1 drop constraint parted_uniq_detach_test1_a_key; -- should fail +alter table parted_uniq_detach_test detach partition parted_uniq_detach_test1; +alter table parted_uniq_detach_test1 drop constraint parted_uniq_detach_test1_a_key; +drop table parted_uniq_detach_test, parted_uniq_detach_test1; + +-- Test range indexes with both IN queries and range filters +create table range_in_inequality(r1 int, r2 int, primary key(r1 asc, r2 asc)); +insert into range_in_inequality select i/5, i%5 from generate_series(1,20) i; +select * from range_in_inequality; +select * from range_in_inequality where r1 in (1, 3) and r2 > 2; +select * from range_in_inequality where r1 in (0, 1, 3) and r2 > 2; + +-- YB Note: Adding cleanup, just in case. +drop table range_in_inequality; +drop table idxpart cascade; +drop table idxpart_another cascade; diff --git a/src/test/regress/sql/yb.port.indirect_toast.sql b/src/test/regress/sql/yb.port.indirect_toast.sql new file mode 100644 index 0000000000000..f3111b20298a1 --- /dev/null +++ b/src/test/regress/sql/yb.port.indirect_toast.sql @@ -0,0 +1,14 @@ +-- +-- Tests for external toast datums +-- + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +CREATE FUNCTION make_tuple_indirect (record) + RETURNS record + AS :'regresslib' + LANGUAGE C STRICT; diff --git a/src/test/regress/sql/yb.port.inet.sql b/src/test/regress/sql/yb.port.inet.sql new file mode 100644 index 0000000000000..01d69a8975f3c --- /dev/null +++ b/src/test/regress/sql/yb.port.inet.sql @@ -0,0 +1,155 @@ +-- +-- INET +-- + +-- prepare the table... + +DROP TABLE INET_TBL; +CREATE TABLE INET_TBL (c cidr, i inet); +INSERT INTO INET_TBL (c, i) VALUES ('192.168.1', '192.168.1.226/24'); +INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.0/26', '192.168.1.226'); +INSERT INTO INET_TBL (c, i) VALUES ('192.168.1', '192.168.1.0/24'); +INSERT INTO INET_TBL (c, i) VALUES ('192.168.1', '192.168.1.0/25'); +INSERT INTO INET_TBL (c, i) VALUES ('192.168.1', '192.168.1.255/24'); +INSERT INTO INET_TBL (c, i) VALUES ('192.168.1', '192.168.1.255/25'); +INSERT INTO INET_TBL (c, i) VALUES ('10', '10.1.2.3/8'); +INSERT INTO INET_TBL (c, i) VALUES ('10.0.0.0', '10.1.2.3/8'); +INSERT INTO INET_TBL (c, i) VALUES ('10.1.2.3', '10.1.2.3/32'); +INSERT INTO INET_TBL (c, i) VALUES ('10.1.2', '10.1.2.3/24'); +INSERT INTO INET_TBL (c, i) VALUES ('10.1', '10.1.2.3/16'); +INSERT INTO INET_TBL (c, i) VALUES ('10', '10.1.2.3/8'); +INSERT INTO INET_TBL (c, i) VALUES ('10', '11.1.2.3/8'); +INSERT INTO INET_TBL (c, i) VALUES ('10', '9.1.2.3/8'); +INSERT INTO INET_TBL (c, i) VALUES ('10:23::f1', '10:23::f1/64'); +INSERT INTO INET_TBL (c, i) VALUES ('10:23::8000/113', '10:23::ffff'); +INSERT INTO INET_TBL (c, i) VALUES ('::ffff:1.2.3.4', '::4.3.2.1/24'); +-- check that CIDR rejects invalid input: +INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.2/30', '192.168.1.226'); +INSERT INTO INET_TBL (c, i) VALUES ('1234::1234::1234', '::1.2.3.4'); +-- check that CIDR rejects invalid input when converting from text: +INSERT INTO INET_TBL (c, i) VALUES (cidr('192.168.1.2/30'), '192.168.1.226'); +INSERT INTO INET_TBL (c, i) VALUES (cidr('ffff:ffff:ffff:ffff::/24'), '::192.168.1.226'); + +SELECT '' AS ten, c AS cidr, i AS inet FROM INET_TBL ORDER BY c, i; + +-- now test some support functions + +SELECT '' AS ten, i AS inet, host(i), text(i), family(i) FROM INET_TBL ORDER BY c, i; +SELECT '' AS ten, c AS cidr, broadcast(c), + i AS inet, broadcast(i) FROM INET_TBL ORDER BY c, i; +SELECT '' AS ten, c AS cidr, network(c) AS "network(cidr)", + i AS inet, network(i) AS "network(inet)" FROM INET_TBL ORDER BY c, i; +SELECT '' AS ten, c AS cidr, masklen(c) AS "masklen(cidr)", + i AS inet, masklen(i) AS "masklen(inet)" FROM INET_TBL ORDER BY c, i; + +SELECT '' AS four, c AS cidr, masklen(c) AS "masklen(cidr)", + i AS inet, masklen(i) AS "masklen(inet)" FROM INET_TBL + WHERE masklen(c) <= 8 ORDER BY c, i; + +SELECT '' AS six, c AS cidr, i AS inet FROM INET_TBL + WHERE c = i ORDER BY c, i; + +SELECT '' AS ten, i, c, + i < c AS lt, i <= c AS le, i = c AS eq, + i >= c AS ge, i > c AS gt, i <> c AS ne, + i << c AS sb, i <<= c AS sbe, + i >> c AS sup, i >>= c AS spe, + i && c AS ovr + FROM INET_TBL ORDER BY c, i; + +SELECT max(i) AS max, min(i) AS min FROM INET_TBL; +SELECT max(c) AS max, min(c) AS min FROM INET_TBL; + +-- check the conversion to/from text and set_netmask +SELECT '' AS ten, set_masklen(inet(text(i)), 24) FROM INET_TBL ORDER BY c, i; + +-- check that lsm index works correctly +-- TODO(neil) Turn on INDEX when indexing non-empty table is allowed. +-- CREATE INDEX inet_idx1 ON inet_tbl(i); +SET enable_seqscan TO off; +SELECT * FROM inet_tbl WHERE i<<'192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i<<='192.168.1.0/24'::cidr ORDER BY c, i; +SET enable_seqscan TO on; +-- TODO(neil) Index Support +-- DROP INDEX inet_idx1; + +-- check that gist index works correctly +-- TODO(neil) Index Support +-- CREATE INDEX inet_idx2 ON inet_tbl using gist (i inet_ops); +SET enable_seqscan TO off; +SELECT * FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i <<= '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i && '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i >>= '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i >> '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i < '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i <= '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i = '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i >= '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i > '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i <> '192.168.1.0/24'::cidr ORDER BY c, i; + +-- test index-only scans +EXPLAIN (COSTS OFF) +SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY c, i; + +SET enable_seqscan TO on; +-- TODO(neil) Index Support +-- DROP INDEX inet_idx2; + +-- check that spgist index works correctly +-- TODO(neil) Index Support +-- CREATE INDEX inet_idx3 ON inet_tbl using spgist (i); +SET enable_seqscan TO off; +SELECT * FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i <<= '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i && '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i >>= '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i >> '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i < '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i <= '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i = '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i >= '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i > '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT * FROM inet_tbl WHERE i <> '192.168.1.0/24'::cidr ORDER BY c, i; + +-- test index-only scans +EXPLAIN (COSTS OFF) +SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY c, i; +SELECT i FROM inet_tbl WHERE i << '192.168.1.0/24'::cidr ORDER BY c, i; + +SET enable_seqscan TO on; +-- TODO(neil) Index Support +-- DROP INDEX inet_idx3; + +-- simple tests of inet boolean and arithmetic operators +SELECT i, ~i AS "~i" FROM inet_tbl ORDER BY c, i; +SELECT i, c, i & c AS "and" FROM inet_tbl ORDER BY c, i; +SELECT i, c, i | c AS "or" FROM inet_tbl ORDER BY c, i; +SELECT i, i + 500 AS "i+500" FROM inet_tbl ORDER BY c, i; +SELECT i, i - 500 AS "i-500" FROM inet_tbl ORDER BY c, i; +SELECT i, c, i - c AS "minus" FROM inet_tbl ORDER BY c, i; +SELECT '127.0.0.1'::inet + 257; +SELECT ('127.0.0.1'::inet + 257) - 257; +SELECT '127::1'::inet + 257; +SELECT ('127::1'::inet + 257) - 257; +SELECT '127.0.0.2'::inet - ('127.0.0.2'::inet + 500); +SELECT '127.0.0.2'::inet - ('127.0.0.2'::inet - 500); +SELECT '127::2'::inet - ('127::2'::inet + 500); +SELECT '127::2'::inet - ('127::2'::inet - 500); +-- these should give overflow errors: +SELECT '127.0.0.1'::inet + 10000000000; +SELECT '127.0.0.1'::inet - 10000000000; +SELECT '126::1'::inet - '127::2'::inet; +SELECT '127::1'::inet - '126::2'::inet; +-- but not these +SELECT '127::1'::inet + 10000000000; +SELECT '127::1'::inet - '127::2'::inet; + +-- insert one more row with addressed from different families +INSERT INTO INET_TBL (c, i) VALUES ('10', '10::/8'); +-- now, this one should fail +SELECT inet_merge(c, i) FROM INET_TBL ORDER BY c, i; +-- fix it by inet_same_family() condition +SELECT inet_merge(c, i) FROM INET_TBL WHERE inet_same_family(c, i) ORDER BY c, i; diff --git a/src/test/regress/sql/yb.port.inherit.sql b/src/test/regress/sql/yb.port.inherit.sql new file mode 100644 index 0000000000000..3420ebb766fd4 --- /dev/null +++ b/src/test/regress/sql/yb.port.inherit.sql @@ -0,0 +1,1100 @@ +-- +-- Test inheritance features +-- +CREATE TABLE a (aa TEXT); +CREATE TABLE b (bb TEXT) INHERITS (a); +CREATE TABLE c (cc TEXT) INHERITS (a); +CREATE TABLE d (dd TEXT) INHERITS (b,c,a); + +INSERT INTO a(aa) VALUES('aaa'); +INSERT INTO a(aa) VALUES('aaaa'); +INSERT INTO a(aa) VALUES('aaaaa'); +INSERT INTO a(aa) VALUES('aaaaaa'); +INSERT INTO a(aa) VALUES('aaaaaaa'); +INSERT INTO a(aa) VALUES('aaaaaaaa'); + +INSERT INTO b(aa) VALUES('bbb'); +INSERT INTO b(aa) VALUES('bbbb'); +INSERT INTO b(aa) VALUES('bbbbb'); +INSERT INTO b(aa) VALUES('bbbbbb'); +INSERT INTO b(aa) VALUES('bbbbbbb'); +INSERT INTO b(aa) VALUES('bbbbbbbb'); + +INSERT INTO c(aa) VALUES('ccc'); +INSERT INTO c(aa) VALUES('cccc'); +INSERT INTO c(aa) VALUES('ccccc'); +INSERT INTO c(aa) VALUES('cccccc'); +INSERT INTO c(aa) VALUES('ccccccc'); +INSERT INTO c(aa) VALUES('cccccccc'); + +INSERT INTO d(aa) VALUES('ddd'); +INSERT INTO d(aa) VALUES('dddd'); +INSERT INTO d(aa) VALUES('ddddd'); +INSERT INTO d(aa) VALUES('dddddd'); +INSERT INTO d(aa) VALUES('ddddddd'); +INSERT INTO d(aa) VALUES('dddddddd'); + +SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; +SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; +SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid; +SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid; +SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid; +SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid; +SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; +SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; + +WITH ybtmp AS (DELETE FROM a WHERE aa='aaaa' RETURNING *) -- YB: imitate PG ctid reallocation for the following update +INSERT INTO a SELECT * FROM ybtmp; -- YB: might require an update if this updates child tables in the future +UPDATE a SET aa='zzzz' WHERE aa='aaaa'; +WITH ybtmp AS (DELETE FROM a WHERE aa='aaaaa' RETURNING *) -- YB: imitate PG ctid reallocation for the following update +INSERT INTO a SELECT * FROM ybtmp; -- YB: might require an update if this updates child tables in the future +UPDATE ONLY a SET aa='zzzzz' WHERE aa='aaaaa'; +UPDATE b SET aa='zzz' WHERE aa='aaa'; +UPDATE ONLY b SET aa='zzz' WHERE aa='aaa'; +WITH ybtmp AS (DELETE FROM a WHERE aa LIKE 'aaa%' RETURNING *) -- YB: imitate PG ctid reallocation for the following update +INSERT INTO a SELECT * FROM ybtmp; -- YB: might require an update if this updates child tables in the future +UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%'; + +SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; +SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; +SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid; +SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid; +SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid; +SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid; +SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; +SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; + +UPDATE b SET aa='new'; + +SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; +SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; +SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid; +SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid; +SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid; +SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid; +SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; +SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; + +UPDATE a SET aa='new'; + +DELETE FROM ONLY c WHERE aa='new'; + +SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; +SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; +SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid; +SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid; +SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid; +SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid; +SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; +SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; + +DELETE FROM a; + +SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; +SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; +SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid; +SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid; +SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid; +SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid; +SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; +SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; + +-- Confirm PRIMARY KEY adds NOT NULL constraint to child table +CREATE TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a); /* YB: #26367 causes an alter col below to fail if this is a temp table */ +INSERT INTO z VALUES (NULL, 'text'); -- should fail + +-- Check inherited UPDATE with all children excluded +create table some_tab (a int, b int); +create table some_tab_child () inherits (some_tab); +insert into some_tab_child values(1,2); + +explain (verbose, costs off) +update some_tab set a = a + 1 where false; +update some_tab set a = a + 1 where false; +explain (verbose, costs off) +update some_tab set a = a + 1 where false returning b, a; +update some_tab set a = a + 1 where false returning b, a; +table some_tab; + +drop table some_tab cascade; + +-- Check UPDATE with inherited target and an inherited source table +create temp table foo(f1 int, f2 int); +create temp table foo2(f3 int) inherits (foo); +create temp table bar(f1 int, f2 int); +create temp table bar2(f3 int) inherits (bar); + +insert into foo values(1,1); +insert into foo values(3,3); +insert into foo2 values(2,2,2); +insert into foo2 values(3,3,3); +insert into bar values(1,1); +insert into bar values(2,2); +insert into bar values(3,3); +insert into bar values(4,4); +insert into bar2 values(1,1,1); +insert into bar2 values(2,2,2); +insert into bar2 values(3,3,3); +insert into bar2 values(4,4,4); + +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); + +select tableoid::regclass::text as relname, bar.* from bar order by 1,2; + +-- Check UPDATE with inherited target and an appendrel subquery +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; + +select tableoid::regclass::text as relname, bar.* from bar order by 1,2; + +-- Check UPDATE with *partitioned* inherited target and an appendrel subquery +create table some_tab (a int); +insert into some_tab values (0); +create table some_tab_child () inherits (some_tab); +insert into some_tab_child values (1); +create table parted_tab (a int, b char) partition by list (a); +create table parted_tab_part1 partition of parted_tab for values in (1); +create table parted_tab_part2 partition of parted_tab for values in (2); +create table parted_tab_part3 partition of parted_tab for values in (3); +insert into parted_tab values (1, 'a'), (2, 'a'), (3, 'a'); + +update parted_tab set b = 'b' +from + (select a from some_tab union all select a+1 from some_tab) ss (a) +where parted_tab.a = ss.a; +select tableoid::regclass::text as relname, parted_tab.* from parted_tab order by 1,2; + +truncate parted_tab; +insert into parted_tab values (1, 'a'), (2, 'a'), (3, 'a'); +update parted_tab set b = 'b' +from + (select 0 from parted_tab union all select 1 from parted_tab) ss (a) +where parted_tab.a = ss.a; +select tableoid::regclass::text as relname, parted_tab.* from parted_tab order by 1,2; + +-- modifies partition key, but no rows will actually be updated +explain update parted_tab set a = 2 where false; + +drop table parted_tab; + +-- Check UPDATE with multi-level partitioned inherited target +create table mlparted_tab (a int, b char, c text) partition by list (a); +create table mlparted_tab_part1 partition of mlparted_tab for values in (1); +create table mlparted_tab_part2 partition of mlparted_tab for values in (2) partition by list (b); +create table mlparted_tab_part3 partition of mlparted_tab for values in (3); +create table mlparted_tab_part2a partition of mlparted_tab_part2 for values in ('a'); +create table mlparted_tab_part2b partition of mlparted_tab_part2 for values in ('b'); +insert into mlparted_tab values (1, 'a'), (2, 'a'), (2, 'b'), (3, 'a'); + +update mlparted_tab mlp set c = 'xxx' +from + (select a from some_tab union all select a+1 from some_tab) ss (a) +where (mlp.a = ss.a and mlp.b = 'b') or mlp.a = 3; +select tableoid::regclass::text as relname, mlparted_tab.* from mlparted_tab order by 1,2; + +drop table mlparted_tab; +drop table some_tab cascade; + +/* Test multiple inheritance of column defaults */ + +CREATE TABLE firstparent (tomorrow date default now()::date + 1); +CREATE TABLE secondparent (tomorrow date default now() :: date + 1); +CREATE TABLE jointchild () INHERITS (firstparent, secondparent); -- ok +CREATE TABLE thirdparent (tomorrow date default now()::date - 1); +CREATE TABLE otherchild () INHERITS (firstparent, thirdparent); -- not ok +CREATE TABLE otherchild (tomorrow date default now()) + INHERITS (firstparent, thirdparent); -- ok, child resolves ambiguous default + +DROP TABLE firstparent, secondparent, jointchild, thirdparent, otherchild; + +-- Test changing the type of inherited columns +insert into d values('test','one','two','three'); +alter table a alter column aa type integer using bit_length(aa); +select * from d; + +-- The above verified that we can change the type of a multiply-inherited +-- column; but we should reject that if any definition was inherited from +-- an unrelated parent. +create temp table parent1(f1 int, f2 int); +create temp table parent2(f1 int, f3 bigint); +create temp table childtab(f4 int) inherits(parent1, parent2); +alter table parent1 alter column f1 type bigint; -- fail, conflict w/parent2 +/* TODO: YB #26367 +alter table parent1 alter column f2 type bigint; -- ok +*/ -- YB +-- Test non-inheritable parent constraints +create table p1(ff1 int); +alter table p1 add constraint p1chk check (ff1 > 0) no inherit; +alter table p1 add constraint p2chk check (ff1 > 10); +-- connoinherit should be true for NO INHERIT constraint +select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname = 'p1' order by 1,2; + +-- Test that child does not inherit NO INHERIT constraints +create table c1 () inherits (p1); +\d p1 +\d c1 + +-- Test that child does not override inheritable constraints of the parent +create table c2 (constraint p2chk check (ff1 > 10) no inherit) inherits (p1); --fails + +drop table p1 cascade; + +-- Tests for casting between the rowtypes of parent and child +-- tables. See the pgsql-hackers thread beginning Dec. 4/04 +create table base (i integer); +create table derived () inherits (base); +create table more_derived (like derived, b int) inherits (derived); +insert into derived (i) values (0); +select derived::base from derived; +select NULL::derived::base; +-- remove redundant conversions. +explain (verbose on, costs off) select row(i, b)::more_derived::derived::base from more_derived; +explain (verbose on, costs off) select (1, 2)::more_derived::derived::base; +drop table more_derived; +drop table derived; +drop table base; + +/* YB doesn't understand this function definition, nothing to do w/ inheritance +create table p1(ff1 int); +create table p2(f1 text); +create function p2text(p2) returns text as 'select $1.f1' language sql; +create table c1(f3 int) inherits(p1,p2); +insert into c1 values(123456789, 'hi', 42); +select p2text(c1.*) from c1; +drop function p2text(p2); +drop table c1; +drop table p2; +drop table p1; +*/ -- YB + +CREATE TABLE ac (aa TEXT); +alter table ac add constraint ac_check check (aa is not null); +CREATE TABLE bc (bb TEXT) INHERITS (ac); +select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; + +insert into ac (aa) values (NULL); +insert into bc (aa) values (NULL); + +alter table bc drop constraint ac_check; -- fail, disallowed +alter table ac drop constraint ac_check; +select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; + +-- try the unnamed-constraint case +alter table ac add check (aa is not null); +select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; + +insert into ac (aa) values (NULL); +insert into bc (aa) values (NULL); + +alter table bc drop constraint ac_aa_check; -- fail, disallowed +alter table ac drop constraint ac_aa_check; +select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; + +alter table ac add constraint ac_check check (aa is not null); +alter table bc no inherit ac; +select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; +alter table bc drop constraint ac_check; +select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; +alter table ac drop constraint ac_check; +select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; + +drop table bc; +drop table ac; + +create table ac (a int constraint check_a check (a <> 0)); +create table bc (a int constraint check_a check (a <> 0), b int constraint check_b check (b <> 0)) inherits (ac); +select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; + +drop table bc; +drop table ac; + +create table ac (a int constraint check_a check (a <> 0)); +create table bc (b int constraint check_b check (b <> 0)); +create table cc (c int constraint check_c check (c <> 0)) inherits (ac, bc); +select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc', 'cc') order by 1,2; + +alter table cc no inherit bc; +select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc', 'cc') order by 1,2; + +drop table cc; +drop table bc; +drop table ac; + +create table p1(f1 int); +create table p2(f2 int); +create table c1(f3 int) inherits(p1,p2); +insert into c1 values(1,-1,2); +alter table p2 add constraint cc check (f2>0); -- fail +alter table p2 add check (f2>0); -- check it without a name, too +delete from c1; +insert into c1 values(1,1,2); +alter table p2 add check (f2>0); +insert into c1 values(1,-1,2); -- fail +create table c2(f3 int) inherits(p1,p2); +\d c2 +create table c3 (f4 int) inherits(c1,c2); +\d c3 +drop table p1 cascade; +drop table p2 cascade; + +create table pp1 (f1 int); +create table cc1 (f2 text, f3 int) inherits (pp1); +alter table pp1 add column a1 int check (a1 > 0); +\d cc1 +create table cc2(f4 float) inherits(pp1,cc1); +\d cc2 +alter table pp1 add column a2 int check (a2 > 0); +\d cc2 +drop table pp1 cascade; + +-- Test for renaming in simple multiple inheritance +CREATE TABLE inht1 (a int, b int); +CREATE TABLE inhs1 (b int, c int); +CREATE TABLE inhts (d int) INHERITS (inht1, inhs1); + +ALTER TABLE inht1 RENAME a TO aa; +ALTER TABLE inht1 RENAME b TO bb; -- to be failed +ALTER TABLE inhts RENAME aa TO aaa; -- to be failed +ALTER TABLE inhts RENAME d TO dd; +\d+ inhts + +DROP TABLE inhts; + +-- Test for renaming in diamond inheritance +CREATE TABLE inht2 (x int) INHERITS (inht1); +CREATE TABLE inht3 (y int) INHERITS (inht1); +CREATE TABLE inht4 (z int) INHERITS (inht2, inht3); + +ALTER TABLE inht1 RENAME aa TO aaa; +\d+ inht4 + +CREATE TABLE inhts (d int) INHERITS (inht2, inhs1); +ALTER TABLE inht1 RENAME aaa TO aaaa; +ALTER TABLE inht1 RENAME b TO bb; -- to be failed +\d+ inhts + +WITH RECURSIVE r AS ( + SELECT 'inht1'::regclass AS inhrelid +UNION ALL + SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent +) +SELECT a.attrelid::regclass, a.attname, a.attinhcount, e.expected + FROM (SELECT inhrelid, count(*) AS expected FROM pg_inherits + WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid) e + JOIN pg_attribute a ON e.inhrelid = a.attrelid WHERE NOT attislocal + ORDER BY a.attrelid::regclass::name, a.attnum; + +DROP TABLE inht1, inhs1 CASCADE; + + +/* YB: Fails in debug builds due to #26227 +-- Test non-inheritable indices [UNIQUE, EXCLUDE] constraints +CREATE TABLE test_constraints (id int, val1 varchar, val2 int, UNIQUE(val1, val2)); +CREATE TABLE test_constraints_inh () INHERITS (test_constraints); +\d+ test_constraints +ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key; +\d+ test_constraints +\d+ test_constraints_inh +DROP TABLE test_constraints_inh; +DROP TABLE test_constraints; +*/ -- YB + +/* EXCLUDE + GIST is not supported by YB +CREATE TABLE test_ex_constraints ( + c circle, + EXCLUDE USING gist (c WITH &&) +); +CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints); +\d+ test_ex_constraints +ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl; +\d+ test_ex_constraints +\d+ test_ex_constraints_inh +DROP TABLE test_ex_constraints_inh; +DROP TABLE test_ex_constraints; +*/ -- YB + +-- Test non-inheritable foreign key constraints +CREATE TABLE test_primary_constraints(id int PRIMARY KEY); +CREATE TABLE test_foreign_constraints(id1 int REFERENCES test_primary_constraints(id)); +CREATE TABLE test_foreign_constraints_inh () INHERITS (test_foreign_constraints); +\d+ test_primary_constraints +\d+ test_foreign_constraints +ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey; +\d+ test_foreign_constraints +\d+ test_foreign_constraints_inh +DROP TABLE test_foreign_constraints_inh; +DROP TABLE test_foreign_constraints; +DROP TABLE test_primary_constraints; + +-- Test foreign key behavior +create table inh_fk_1 (a int primary key); +insert into inh_fk_1 values (1), (2), (3); +create table inh_fk_2 (x int primary key, y int references inh_fk_1 on delete cascade); +insert into inh_fk_2 values (11, 1), (22, 2), (33, 3); +create table inh_fk_2_child () inherits (inh_fk_2); +insert into inh_fk_2_child values (111, 1), (222, 2); +delete from inh_fk_1 where a = 1; +select * from inh_fk_1 order by 1; +select * from inh_fk_2 order by 1, 2; +drop table inh_fk_1, inh_fk_2, inh_fk_2_child; + +-- Test that parent and child CHECK constraints can be created in either order +create table p1(f1 int); +create table p1_c1() inherits(p1); + +alter table p1 add constraint inh_check_constraint1 check (f1 > 0); +alter table p1_c1 add constraint inh_check_constraint1 check (f1 > 0); + +alter table p1_c1 add constraint inh_check_constraint2 check (f1 < 10); +alter table p1 add constraint inh_check_constraint2 check (f1 < 10); + +select conrelid::regclass::text as relname, conname, conislocal, coninhcount +from pg_constraint where conname like 'inh\_check\_constraint%' +order by 1, 2; + +drop table p1 cascade; + +-- Test that a valid child can have not-valid parent, but not vice versa +create table invalid_check_con(f1 int); +create table invalid_check_con_child() inherits(invalid_check_con); + +alter table invalid_check_con_child add constraint inh_check_constraint check(f1 > 0) not valid; +alter table invalid_check_con add constraint inh_check_constraint check(f1 > 0); -- fail +alter table invalid_check_con_child drop constraint inh_check_constraint; + +insert into invalid_check_con values(0); + +alter table invalid_check_con_child add constraint inh_check_constraint check(f1 > 0); +alter table invalid_check_con add constraint inh_check_constraint check(f1 > 0) not valid; + +insert into invalid_check_con values(0); -- fail +insert into invalid_check_con_child values(0); -- fail + +select conrelid::regclass::text as relname, conname, + convalidated, conislocal, coninhcount, connoinherit +from pg_constraint where conname like 'inh\_check\_constraint%' +order by 1, 2; + +-- We don't drop the invalid_check_con* tables, to test dump/reload with + +-- +-- Test parameterized append plans for inheritance trees +-- + +create temp table patest0 (id, x) as + select x, x from generate_series(0,1000) x; +create temp table patest1() inherits (patest0); +insert into patest1 + select x, x from generate_series(0,1000) x; +create temp table patest2() inherits (patest0); +insert into patest2 + select x, x from generate_series(0,1000) x; +create index patest0i on patest0(id); +create index patest1i on patest1(id); +create index patest2i on patest2(id); +analyze patest0; +analyze patest1; +analyze patest2; + +/* YB: Disabled PG tests for query execution plans +explain (costs off) +select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1; +select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1; + +drop index patest2i; + +explain (costs off) +select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1; +select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1; +*/ -- YB + +drop table patest0 cascade; + +-- +-- Test merge-append plans for inheritance trees +-- + +create table matest0 (id serial primary key, name text); +create table matest1 (id integer primary key) inherits (matest0); +create table matest2 (id integer primary key) inherits (matest0); +create table matest3 (id integer primary key) inherits (matest0); + +create index matest0i on matest0 ((1-id)); +create index matest1i on matest1 ((1-id)); +-- create index matest2i on matest2 ((1-id)); -- intentionally missing +create index matest3i on matest3 ((1-id)); + +insert into matest1 (name) values ('Test 1'); +insert into matest1 (name) values ('Test 2'); +insert into matest2 (name) values ('Test 3'); +insert into matest2 (name) values ('Test 4'); +insert into matest3 (name) values ('Test 5'); +insert into matest3 (name) values ('Test 6'); + +/* YB: Disabled PG tests for query execution plans +set enable_indexscan = off; -- force use of seqscan/sort, so no merge +explain (verbose, costs off) select * from matest0 order by 1-id; +select * from matest0 order by 1-id; +explain (verbose, costs off) select min(1-id) from matest0; +select min(1-id) from matest0; +reset enable_indexscan; + +set enable_seqscan = off; -- plan with fewest seqscans should be merge +set enable_parallel_append = off; -- Don't let parallel-append interfere +explain (verbose, costs off) select * from matest0 order by 1-id; +select * from matest0 order by 1-id; +explain (verbose, costs off) select min(1-id) from matest0; +select min(1-id) from matest0; +reset enable_seqscan; +reset enable_parallel_append; +*/ -- YB +drop table matest0 cascade; + +-- +-- Check that use of an index with an extraneous column doesn't produce +-- a plan with extraneous sorting +-- + +create table matest0 (a int, b int, c int, d int); +create table matest1 () inherits(matest0); +create index matest0i on matest0 (b, c); +create index matest1i on matest1 (b, c); + +/* YB: Disabled PG tests for query execution plans +set enable_nestloop = off; -- we want a plan with two MergeAppends + +explain (costs off) +select t1.* from matest0 t1, matest0 t2 +where t1.b = t2.b and t2.c = t2.d +order by t1.b limit 10; + +reset enable_nestloop; +*/ -- YB + +drop table matest0 cascade; + +/* YB: Disabled PG tests for query execution plans +-- +-- Test merge-append for UNION ALL append relations +-- + +set enable_seqscan = off; +set enable_indexscan = on; +set enable_bitmapscan = off; + +-- Check handling of duplicated, constant, or volatile targetlist items +explain (costs off) +SELECT thousand, tenthous FROM tenk1 +UNION ALL +SELECT thousand, thousand FROM tenk1 +ORDER BY thousand, tenthous; + +explain (costs off) +SELECT thousand, tenthous, thousand+tenthous AS x FROM tenk1 +UNION ALL +SELECT 42, 42, hundred FROM tenk1 +ORDER BY thousand, tenthous; + +explain (costs off) +SELECT thousand, tenthous FROM tenk1 +UNION ALL +SELECT thousand, random()::integer FROM tenk1 +ORDER BY thousand, tenthous; + +-- Check min/max aggregate optimization +explain (costs off) +SELECT min(x) FROM + (SELECT unique1 AS x FROM tenk1 a + UNION ALL + SELECT unique2 AS x FROM tenk1 b) s; + +explain (costs off) +SELECT min(y) FROM + (SELECT unique1 AS x, unique1 AS y FROM tenk1 a + UNION ALL + SELECT unique2 AS x, unique2 AS y FROM tenk1 b) s; + +-- XXX planner doesn't recognize that index on unique2 is sufficiently sorted +explain (costs off) +SELECT x, y FROM + (SELECT thousand AS x, tenthous AS y FROM tenk1 a + UNION ALL + SELECT unique2 AS x, unique2 AS y FROM tenk1 b) s +ORDER BY x, y; + +-- exercise rescan code path via a repeatedly-evaluated subquery +explain (costs off) +SELECT + ARRAY(SELECT f.i FROM ( + (SELECT d + g.i FROM generate_series(4, 30, 3) d ORDER BY 1) + UNION ALL + (SELECT d + g.i FROM generate_series(0, 30, 5) d ORDER BY 1) + ) f(i) + ORDER BY f.i LIMIT 10) +FROM generate_series(1, 3) g(i); + +SELECT + ARRAY(SELECT f.i FROM ( + (SELECT d + g.i FROM generate_series(4, 30, 3) d ORDER BY 1) + UNION ALL + (SELECT d + g.i FROM generate_series(0, 30, 5) d ORDER BY 1) + ) f(i) + ORDER BY f.i LIMIT 10) +FROM generate_series(1, 3) g(i); + +reset enable_seqscan; +reset enable_indexscan; +reset enable_bitmapscan; +*/ -- YB + +-- +-- Check handling of MULTIEXPR SubPlans in inherited updates +-- +create table inhpar(f1 int, f2 name); +create table inhcld(f2 name, f1 int); +alter table inhcld inherit inhpar; +insert into inhpar select x, x::text from generate_series(1,5) x; +insert into inhcld select x::text, x from generate_series(6,10) x; + +explain (verbose, costs off) +update inhpar i set (f1, f2) = (select i.f1, i.f2 || '-' from int4_tbl limit 1); +update inhpar i set (f1, f2) = (select i.f1, i.f2 || '-' from int4_tbl limit 1); +select * from inhpar; + +drop table inhpar cascade; + +-- +-- And the same for partitioned cases +-- +create table inhpar(f1 int primary key, f2 name) partition by range (f1); +create table inhcld1(f2 name, f1 int primary key); +create table inhcld2(f1 int primary key, f2 name); +alter table inhpar attach partition inhcld1 for values from (1) to (5); +alter table inhpar attach partition inhcld2 for values from (5) to (100); +insert into inhpar select x, x::text from generate_series(1,10) x; + +explain (verbose, costs off) +update inhpar i set (f1, f2) = (select i.f1, i.f2 || '-' from int4_tbl limit 1); +update inhpar i set (f1, f2) = (select i.f1, i.f2 || '-' from int4_tbl limit 1); +select * from inhpar; + +-- Also check ON CONFLICT +insert into inhpar as i values (3), (7) on conflict (f1) + do update set (f1, f2) = (select i.f1, i.f2 || '+'); +select * from inhpar order by f1; -- tuple order might be unstable here + +drop table inhpar cascade; + +-- +-- Check handling of a constant-null CHECK constraint +-- +create table cnullparent (f1 int); +create table cnullchild (check (f1 = 1 or f1 = null)) inherits(cnullparent); +insert into cnullchild values(1); +insert into cnullchild values(2); +insert into cnullchild values(null); +select * from cnullparent; +select * from cnullparent where f1 = 2; +drop table cnullparent cascade; + +-- +-- Check use of temporary tables with inheritance trees +-- +create table inh_perm_parent (a1 int); +create temp table inh_temp_parent (a1 int); +create temp table inh_temp_child () inherits (inh_perm_parent); -- ok +create table inh_perm_child () inherits (inh_temp_parent); -- error +create temp table inh_temp_child_2 () inherits (inh_temp_parent); -- ok +insert into inh_perm_parent values (1); +insert into inh_temp_parent values (2); +insert into inh_temp_child values (3); +insert into inh_temp_child_2 values (4); +select tableoid::regclass, a1 from inh_perm_parent; +select tableoid::regclass, a1 from inh_temp_parent; +drop table inh_perm_parent cascade; +drop table inh_temp_parent cascade; + +-- +-- Check that constraint exclusion works correctly with partitions using +-- implicit constraints generated from the partition bound information. +-- +create table list_parted ( + a varchar +) partition by list (a); +create table part_ab_cd partition of list_parted for values in ('ab', 'cd'); +create table part_ef_gh partition of list_parted for values in ('ef', 'gh'); +create table part_null_xy partition of list_parted for values in (null, 'xy'); + +explain (costs off) select * from list_parted; +explain (costs off) select * from list_parted where a is null; +explain (costs off) select * from list_parted where a is not null; +explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef'); +explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd'); +explain (costs off) select * from list_parted where a = 'ab'; + +create table range_list_parted ( + a int, + b char(2) +) partition by range (a); +create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b); +create table part_1_10_ab partition of part_1_10 for values in ('ab'); +create table part_1_10_cd partition of part_1_10 for values in ('cd'); +create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b); +create table part_10_20_ab partition of part_10_20 for values in ('ab'); +create table part_10_20_cd partition of part_10_20 for values in ('cd'); +create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b); +create table part_21_30_ab partition of part_21_30 for values in ('ab'); +create table part_21_30_cd partition of part_21_30 for values in ('cd'); +create table part_40_inf partition of range_list_parted for values from (40) to (maxvalue) partition by list (b); +create table part_40_inf_ab partition of part_40_inf for values in ('ab'); +create table part_40_inf_cd partition of part_40_inf for values in ('cd'); +create table part_40_inf_null partition of part_40_inf for values in (null); + +explain (costs off) select * from range_list_parted; +explain (costs off) select * from range_list_parted where a = 5; +explain (costs off) select * from range_list_parted where b = 'ab'; +explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab'); + +/* Should select no rows because range partition key cannot be null */ +explain (costs off) select * from range_list_parted where a is null; + +/* Should only select rows from the null-accepting partition */ +explain (costs off) select * from range_list_parted where b is null; +explain (costs off) select * from range_list_parted where a is not null and a < 67; +explain (costs off) select * from range_list_parted where a >= 30; + +drop table list_parted; +drop table range_list_parted; + +-- check that constraint exclusion is able to cope with the partition +-- constraint emitted for multi-column range partitioned tables +create table mcrparted (a int, b int, c int) partition by range (a, abs(b), c); +create table mcrparted_def partition of mcrparted default; +create table mcrparted0 partition of mcrparted for values from (minvalue, minvalue, minvalue) to (1, 1, 1); +create table mcrparted1 partition of mcrparted for values from (1, 1, 1) to (10, 5, 10); +create table mcrparted2 partition of mcrparted for values from (10, 5, 10) to (10, 10, 10); +create table mcrparted3 partition of mcrparted for values from (11, 1, 1) to (20, 10, 10); +create table mcrparted4 partition of mcrparted for values from (20, 10, 10) to (20, 20, 20); +create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue); +explain (costs off) select * from mcrparted where a = 0; -- scans mcrparted0, mcrparted_def +explain (costs off) select * from mcrparted where a = 10 and abs(b) < 5; -- scans mcrparted1, mcrparted_def +explain (costs off) select * from mcrparted where a = 10 and abs(b) = 5; -- scans mcrparted1, mcrparted2, mcrparted_def +explain (costs off) select * from mcrparted where abs(b) = 5; -- scans all partitions +explain (costs off) select * from mcrparted where a > -1; -- scans all partitions +explain (costs off) select * from mcrparted where a = 20 and abs(b) = 10 and c > 10; -- scans mcrparted4 +explain (costs off) select * from mcrparted where a = 20 and c > 20; -- scans mcrparted3, mcrparte4, mcrparte5, mcrparted_def + +-- check that partitioned table Appends cope with being referenced in +-- subplans +create table parted_minmax (a int, b varchar(16)) partition by range (a); +create table parted_minmax1 partition of parted_minmax for values from (1) to (10); +create index parted_minmax1i on parted_minmax1 (a, b); +insert into parted_minmax values (1,'12345'); +/* YB: Disabled PG tests for query execution plans +explain (costs off) select min(a), max(a) from parted_minmax where b = '12345'; +select min(a), max(a) from parted_minmax where b = '12345'; +*/ -- YB +drop table parted_minmax; + +-- Test code that uses Append nodes in place of MergeAppend when the +-- partition ordering matches the desired ordering. + +create index mcrparted_a_abs_c_idx on mcrparted (a, abs(b), c); + +/* YB: Disabled PG tests for query execution plans +-- MergeAppend must be used when a default partition exists +explain (costs off) select * from mcrparted order by a, abs(b), c; +*/ -- YB +drop table mcrparted_def; + +-- Append is used for a RANGE partitioned table with no default +-- and no subpartitions +/* YB: Disabled PG tests for query execution plans +explain (costs off) select * from mcrparted order by a, abs(b), c; +-- Append is used with subpaths in reverse order with backwards index scans +explain (costs off) select * from mcrparted order by a desc, abs(b) desc, c desc; +*/ -- YB + +-- check that Append plan is used containing a MergeAppend for sub-partitions +-- that are unordered. +drop table mcrparted5; +create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue) partition by list (a); +create table mcrparted5a partition of mcrparted5 for values in(20); +create table mcrparted5_def partition of mcrparted5 default; + +/* YB: Disabled PG tests for query execution plans +explain (costs off) select * from mcrparted order by a, abs(b), c; +*/ -- YB +drop table mcrparted5_def; + +/* YB: Disabled PG tests for query execution plans +-- check that an Append plan is used and the sub-partitions are flattened +-- into the main Append when the sub-partition is unordered but contains +-- just a single sub-partition. +explain (costs off) select a, abs(b) from mcrparted order by a, abs(b), c; + +-- check that Append is used when the sub-partitioned tables are pruned +-- during planning. +explain (costs off) select * from mcrparted where a < 20 order by a, abs(b), c; +*/ -- YB + +set enable_bitmapscan to off; +set enable_sort to off; +create table mclparted (a int) partition by list(a); +create table mclparted1 partition of mclparted for values in(1); +create table mclparted2 partition of mclparted for values in(2); +create index on mclparted (a); + +-- Ensure an Append is used for a list partition with an order by. +/* YB: Disabled PG tests for query execution plans +explain (costs off) select * from mclparted order by a; +*/ -- YB + +-- Ensure a MergeAppend is used when a partition exists with interleaved +-- datums in the partition bound. +create table mclparted3_5 partition of mclparted for values in(3,5); +create table mclparted4 partition of mclparted for values in(4); + +/* YB: Disabled PG tests for query execution plans +explain (costs off) select * from mclparted order by a; +explain (costs off) select * from mclparted where a in(3,4,5) order by a; +*/ -- YB + +-- Introduce a NULL and DEFAULT partition so we can test more complex cases +create table mclparted_null partition of mclparted for values in(null); +create table mclparted_def partition of mclparted default; + +/* YB: Disabled PG tests for query execution plans +-- Append can be used providing we don't scan the interleaved partition +explain (costs off) select * from mclparted where a in(1,2,4) order by a; +explain (costs off) select * from mclparted where a in(1,2,4) or a is null order by a; +*/ -- YB + +-- Test a more complex case where the NULL partition allows some other value +drop table mclparted_null; +create table mclparted_0_null partition of mclparted for values in(0,null); + +/* YB: Disabled PG tests for query execution plans +-- Ensure MergeAppend is used since 0 and NULLs are in the same partition. +explain (costs off) select * from mclparted where a in(1,2,4) or a is null order by a; +explain (costs off) select * from mclparted where a in(0,1,2,4) order by a; + +-- Ensure Append is used when the null partition is pruned +explain (costs off) select * from mclparted where a in(1,2,4) order by a; + +-- Ensure MergeAppend is used when the default partition is not pruned +explain (costs off) select * from mclparted where a in(1,2,4,100) order by a; +*/ -- YB + +drop table mclparted; +reset enable_sort; +reset enable_bitmapscan; + +-- Ensure subplans which don't have a path with the correct pathkeys get +-- sorted correctly. +drop index mcrparted_a_abs_c_idx; +create index on mcrparted1 (a, abs(b), c); +create index on mcrparted2 (a, abs(b), c); +create index on mcrparted3 (a, abs(b), c); +create index on mcrparted4 (a, abs(b), c); + +/* YB: Disabled PG tests for query execution plans +explain (costs off) select * from mcrparted where a < 20 order by a, abs(b), c limit 1; + +set enable_bitmapscan = 0; +-- Ensure Append node can be used when the partition is ordered by some +-- pathkeys which were deemed redundant. +explain (costs off) select * from mcrparted where a = 10 order by a, abs(b), c; +reset enable_bitmapscan; +*/ -- YB + +drop table mcrparted; + +-- Ensure LIST partitions allow an Append to be used instead of a MergeAppend +create table bool_lp (b bool) partition by list(b); +create table bool_lp_true partition of bool_lp for values in(true); +create table bool_lp_false partition of bool_lp for values in(false); +create index on bool_lp (b); + +/* YB: Disabled PG tests for query execution plans +explain (costs off) select * from bool_lp order by b; +*/ -- YB + +drop table bool_lp; + +-- Ensure const bool quals can be properly detected as redundant +create table bool_rp (b bool, a int) partition by range(b,a); +create table bool_rp_false_1k partition of bool_rp for values from (false,0) to (false,1000); +create table bool_rp_true_1k partition of bool_rp for values from (true,0) to (true,1000); +create table bool_rp_false_2k partition of bool_rp for values from (false,1000) to (false,2000); +create table bool_rp_true_2k partition of bool_rp for values from (true,1000) to (true,2000); +create index on bool_rp (b,a); +/* YB: Disabled PG tests for query execution plans +explain (costs off) select * from bool_rp where b = true order by b,a; +explain (costs off) select * from bool_rp where b = false order by b,a; +explain (costs off) select * from bool_rp where b = true order by a; +explain (costs off) select * from bool_rp where b = false order by a; +*/ -- YB + +drop table bool_rp; + +-- Ensure an Append scan is chosen when the partition order is a subset of +-- the required order. +create table range_parted (a int, b int, c int) partition by range(a, b); +create table range_parted1 partition of range_parted for values from (0,0) to (10,10); +create table range_parted2 partition of range_parted for values from (10,10) to (20,20); +create index on range_parted (a,b,c); + +/* YB: Disabled PG tests for query execution plans +explain (costs off) select * from range_parted order by a,b,c; +explain (costs off) select * from range_parted order by a desc,b desc,c desc; +*/ -- YB + +drop table range_parted; + +/* YB: Disabled PG tests for query execution plans +-- Check that we allow access to a child table's statistics when the user +-- has permissions only for the parent table. +create table permtest_parent (a int, b text, c text) partition by list (a); +create table permtest_child (b text, c text, a int) partition by list (b); +create table permtest_grandchild (c text, b text, a int); +alter table permtest_child attach partition permtest_grandchild for values in ('a'); +alter table permtest_parent attach partition permtest_child for values in (1); +create index on permtest_parent (left(c, 3)); +insert into permtest_parent + select 1, 'a', left(md5(i::text), 5) from generate_series(0, 100) i; +analyze permtest_parent; +create role regress_no_child_access; +revoke all on permtest_grandchild from regress_no_child_access; +grant select on permtest_parent to regress_no_child_access; +set session authorization regress_no_child_access; +-- without stats access, these queries would produce hash join plans: +explain (costs off) + select * from permtest_parent p1 inner join permtest_parent p2 + on p1.a = p2.a and p1.c ~ 'a1$'; +explain (costs off) + select * from permtest_parent p1 inner join permtest_parent p2 + on p1.a = p2.a and left(p1.c, 3) ~ 'a1$'; +reset session authorization; +revoke all on permtest_parent from regress_no_child_access; +grant select(a,c) on permtest_parent to regress_no_child_access; +set session authorization regress_no_child_access; +explain (costs off) + select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2 + on p1.a = p2.a and p1.c ~ 'a1$'; +-- we will not have access to the expression index's stats here: +explain (costs off) + select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2 + on p1.a = p2.a and left(p1.c, 3) ~ 'a1$'; +reset session authorization; +revoke all on permtest_parent from regress_no_child_access; +drop role regress_no_child_access; +drop table permtest_parent; +*/ -- YB + +/* Disabled in YB due to https://github.com/yugabyte/yugabyte-db/issues/25911 +-- Verify that constraint errors across partition root / child are +-- handled correctly (Bug #16293) +CREATE TABLE errtst_parent ( + partid int not null, + shdata int not null, + data int NOT NULL DEFAULT 0, + CONSTRAINT shdata_small CHECK(shdata < 3) +) PARTITION BY RANGE (partid); + +-- fast defaults lead to attribute mapping being used in one +-- direction, but not the other +CREATE TABLE errtst_child_fastdef ( + partid int not null, + shdata int not null, + CONSTRAINT shdata_small CHECK(shdata < 3) +); + +-- no remapping in either direction necessary +CREATE TABLE errtst_child_plaindef ( + partid int not null, + shdata int not null, + data int NOT NULL DEFAULT 0, + CONSTRAINT shdata_small CHECK(shdata < 3), + CHECK(data < 10) +); + +-- remapping in both direction +CREATE TABLE errtst_child_reorder ( + data int NOT NULL DEFAULT 0, + shdata int not null, + partid int not null, + CONSTRAINT shdata_small CHECK(shdata < 3), + CHECK(data < 10) +); + +ALTER TABLE errtst_child_fastdef ADD COLUMN data int NOT NULL DEFAULT 0; +ALTER TABLE errtst_child_fastdef ADD CONSTRAINT errtest_child_fastdef_data_check CHECK (data < 10); + +ALTER TABLE errtst_parent ATTACH PARTITION errtst_child_fastdef FOR VALUES FROM (0) TO (10); +ALTER TABLE errtst_parent ATTACH PARTITION errtst_child_plaindef FOR VALUES FROM (10) TO (20); +ALTER TABLE errtst_parent ATTACH PARTITION errtst_child_reorder FOR VALUES FROM (20) TO (30); + +-- insert without child check constraint error +INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', '5'); +INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', '5'); +INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '5'); + +-- insert with child check constraint error +INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', '10'); +INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', '10'); +INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '10'); + +-- insert with child not null constraint error +INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', NULL); +INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', NULL); +INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', NULL); + +-- insert with shared check constraint error +INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '5', '5'); +INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '5', '5'); +INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '5', '5'); + +-- within partition update without child check constraint violation +BEGIN; +UPDATE errtst_parent SET data = data + 1 WHERE partid = 0; +UPDATE errtst_parent SET data = data + 1 WHERE partid = 10; +UPDATE errtst_parent SET data = data + 1 WHERE partid = 20; +ROLLBACK; + +-- within partition update with child check constraint violation +UPDATE errtst_parent SET data = data + 10 WHERE partid = 0; +UPDATE errtst_parent SET data = data + 10 WHERE partid = 10; +UPDATE errtst_parent SET data = data + 10 WHERE partid = 20; + +-- direct leaf partition update, without partition id violation +BEGIN; +UPDATE errtst_child_fastdef SET partid = 1 WHERE partid = 0; +UPDATE errtst_child_plaindef SET partid = 11 WHERE partid = 10; +UPDATE errtst_child_reorder SET partid = 21 WHERE partid = 20; +ROLLBACK; + +-- direct leaf partition update, with partition id violation +UPDATE errtst_child_fastdef SET partid = partid + 10 WHERE partid = 0; +UPDATE errtst_child_plaindef SET partid = partid + 10 WHERE partid = 10; +UPDATE errtst_child_reorder SET partid = partid + 10 WHERE partid = 20; + +-- partition move, without child check constraint violation +BEGIN; +UPDATE errtst_parent SET partid = 10, data = data + 1 WHERE partid = 0; +UPDATE errtst_parent SET partid = 20, data = data + 1 WHERE partid = 10; +UPDATE errtst_parent SET partid = 0, data = data + 1 WHERE partid = 20; +ROLLBACK; + +-- partition move, with child check constraint violation +UPDATE errtst_parent SET partid = 10, data = data + 10 WHERE partid = 0; +UPDATE errtst_parent SET partid = 20, data = data + 10 WHERE partid = 10; +UPDATE errtst_parent SET partid = 0, data = data + 10 WHERE partid = 20; + +-- partition move, without target partition +UPDATE errtst_parent SET partid = 30, data = data + 10 WHERE partid = 20; + +DROP TABLE errtst_parent; +*/ -- YB diff --git a/src/test/regress/sql/yb.port.insert.sql b/src/test/regress/sql/yb.port.insert.sql new file mode 100644 index 0000000000000..d79c0556b3e02 --- /dev/null +++ b/src/test/regress/sql/yb.port.insert.sql @@ -0,0 +1,585 @@ +-- +-- insert with DEFAULT in the target_list +-- +create table inserttest (col1 int4, col2 int4 NOT NULL, col3 text default 'testing'); +insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT, DEFAULT); +insert into inserttest (col2, col3) values (3, DEFAULT); +insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT); +insert into inserttest values (DEFAULT, 5, 'test'); +insert into inserttest values (DEFAULT, 7); + +select * from inserttest; + +-- +-- insert with similar expression / target_list values (all fail) +-- +insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT); +insert into inserttest (col1, col2, col3) values (1, 2); +insert into inserttest (col1) values (1, 2); +insert into inserttest (col1) values (DEFAULT, DEFAULT); + +select * from inserttest; + +-- +-- VALUES test +-- +insert into inserttest values(10, 20, '40'), (-1, 2, DEFAULT), + ((select 2), (select i from (values(3)) as foo (i)), 'values are fun!'); + +select * from inserttest; + +-- +-- TOASTed value test +-- +insert into inserttest values(30, 50, repeat('x', 10000)); + +select col1, col2, char_length(col3) from inserttest; + +drop table inserttest; + +-- +-- tuple larger than fillfactor -- YB table +-- +CREATE TABLE large_tuple_test (a int, b text) WITH (fillfactor = 10); +ALTER TABLE large_tuple_test ALTER COLUMN b SET STORAGE plain; + +DROP TABLE large_tuple_test; + +-- +-- check indirection (field/array assignment), cf bug #14265 +-- +-- these tests are aware that transformInsertStmt has 3 separate code paths +-- + +create type insert_test_type as (if1 int, if2 text[]); + +create table inserttest (f1 int, f2 int[], + f3 insert_test_type, f4 insert_test_type[]); + +insert into inserttest (f2[1], f2[2]) values (1,2); +insert into inserttest (f2[1], f2[2]) values (3,4), (5,6); +insert into inserttest (f2[1], f2[2]) select 7,8; +insert into inserttest (f2[1], f2[2]) values (1,default); -- not supported + +insert into inserttest (f3.if1, f3.if2) values (1,array['foo']); +insert into inserttest (f3.if1, f3.if2) values (1,'{foo}'), (2,'{bar}'); +insert into inserttest (f3.if1, f3.if2) select 3, '{baz,quux}'; +insert into inserttest (f3.if1, f3.if2) values (1,default); -- not supported + +insert into inserttest (f3.if2[1], f3.if2[2]) values ('foo', 'bar'); +insert into inserttest (f3.if2[1], f3.if2[2]) values ('foo', 'bar'), ('baz', 'quux'); +insert into inserttest (f3.if2[1], f3.if2[2]) select 'bear', 'beer'; + +insert into inserttest (f4[1].if2[1], f4[1].if2[2]) values ('foo', 'bar'); +insert into inserttest (f4[1].if2[1], f4[1].if2[2]) values ('foo', 'bar'), ('baz', 'quux'); +insert into inserttest (f4[1].if2[1], f4[1].if2[2]) select 'bear', 'beer'; + +select * from inserttest; + +-- also check reverse-listing +create table inserttest2 (f1 bigint, f2 text); +create rule irule1 as on insert to inserttest2 do also + insert into inserttest (f3.if2[1], f3.if2[2]) + values (new.f1,new.f2); +create rule irule2 as on insert to inserttest2 do also + insert into inserttest (f4[1].if1, f4[1].if2[2]) + values (1,'fool'),(new.f1,new.f2); +create rule irule3 as on insert to inserttest2 do also + insert into inserttest (f4[1].if1, f4[1].if2[2]) + select new.f1, new.f2; +\d+ inserttest2 + +drop table inserttest2; +drop table inserttest; +drop type insert_test_type; + +-- direct partition inserts should check partition bound constraint +create table range_parted ( + a text, + b int +) partition by range (a, (b+0)); + +-- no partitions, so fail +insert into range_parted values ('a', 11); + +create table part1 partition of range_parted for values from ('a', 1) to ('a', 10); +create table part2 partition of range_parted for values from ('a', 10) to ('a', 20); +create table part3 partition of range_parted for values from ('b', 1) to ('b', 10); +create table part4 partition of range_parted for values from ('b', 10) to ('b', 20); + +-- fail +insert into part1 values ('a', 11); +insert into part1 values ('b', 1); +-- ok +insert into part1 values ('a', 1); +-- fail +insert into part4 values ('b', 21); +insert into part4 values ('a', 10); +-- ok +insert into part4 values ('b', 10); + +-- fail (partition key a has a NOT NULL constraint) +insert into part1 values (null); +-- fail (expression key (b+0) cannot be null either) +insert into part1 values (1); + +create table list_parted ( + a text, + b int +) partition by list (lower(a)); +create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb'); +create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd'); +create table part_null partition of list_parted FOR VALUES IN (null); + +-- fail +insert into part_aa_bb values ('cc', 1); +insert into part_aa_bb values ('AAa', 1); +insert into part_aa_bb values (null); +-- ok +insert into part_cc_dd values ('cC', 1); +insert into part_null values (null, 0); + +-- check in case of multi-level partitioned table +create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b); +create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10); +create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20); + +-- test default partition +create table part_default partition of list_parted default; +-- Negative test: a row, which would fit in other partition, does not fit +-- default partition, even when inserted directly +insert into part_default values ('aa', 2); +insert into part_default values (null, 2); +-- ok +insert into part_default values ('Zz', 2); +-- test if default partition works as expected for multi-level partitioned +-- table as well as when default partition itself is further partitioned +drop table part_default; +create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a); +create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx'); +create table part_xx_yy_defpart partition of part_xx_yy default; +create table part_default partition of list_parted default partition by range(b); +create table part_default_p1 partition of part_default for values from (20) to (30); +create table part_default_p2 partition of part_default for values from (30) to (40); + +-- fail +insert into part_ee_ff1 values ('EE', 11); +insert into part_default_p2 values ('gg', 43); +-- fail (even the parent's, ie, part_ee_ff's partition constraint applies) +insert into part_ee_ff1 values ('cc', 1); +insert into part_default values ('gg', 43); +-- ok +insert into part_ee_ff1 values ('ff', 1); +insert into part_ee_ff2 values ('ff', 11); +insert into part_default_p1 values ('cd', 25); +insert into part_default_p2 values ('de', 35); +insert into list_parted values ('ab', 21); +insert into list_parted values ('xx', 1); +insert into list_parted values ('yy', 2); +select tableoid::regclass, * from list_parted; + +-- Check tuple routing for partitioned tables + +-- fail +insert into range_parted values ('a', 0); +-- ok +insert into range_parted values ('a', 1); +insert into range_parted values ('a', 10); +-- fail +insert into range_parted values ('a', 20); +-- ok +insert into range_parted values ('b', 1); +insert into range_parted values ('b', 10); +-- fail (partition key (b+0) is null) +insert into range_parted values ('a'); + +-- Check default partition +create table part_def partition of range_parted default; +-- fail +insert into part_def values ('b', 10); +-- ok +insert into part_def values ('c', 10); +insert into range_parted values (null, null); +insert into range_parted values ('a', null); +insert into range_parted values (null, 19); +insert into range_parted values ('b', 20); + +select tableoid::regclass, * from range_parted; +-- ok +insert into list_parted values (null, 1); +insert into list_parted (a) values ('aA'); +-- fail (partition of part_ee_ff not found in both cases) +insert into list_parted values ('EE', 0); +insert into part_ee_ff values ('EE', 0); +-- ok +insert into list_parted values ('EE', 1); +insert into part_ee_ff values ('EE', 10); +select tableoid::regclass, * from list_parted; + +-- some more tests to exercise tuple-routing with multi-level partitioning +create table part_gg partition of list_parted for values in ('gg') partition by range (b); +create table part_gg1 partition of part_gg for values from (minvalue) to (1); +create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b); +create table part_gg2_1 partition of part_gg2 for values from (1) to (5); +create table part_gg2_2 partition of part_gg2 for values from (5) to (10); + +create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b); +create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25); +create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30); + +truncate list_parted; +insert into list_parted values ('aa'), ('cc'); +insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a); +insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a); +insert into list_parted (b) values (1); +select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1; + +-- direct partition inserts should check hash partition bound constraint + +create table hash_parted ( + a int +) partition by hash (a part_test_int4_ops); +create table hpart0 partition of hash_parted for values with (modulus 4, remainder 0); +create table hpart1 partition of hash_parted for values with (modulus 4, remainder 1); +create table hpart2 partition of hash_parted for values with (modulus 4, remainder 2); +create table hpart3 partition of hash_parted for values with (modulus 4, remainder 3); + +insert into hash_parted values(generate_series(1,10)); + +-- direct insert of values divisible by 4 - ok; +insert into hpart0 values(12),(16); +-- fail; +insert into hpart0 values(11); +-- 11 % 4 -> 3 remainder i.e. valid data for hpart3 partition +insert into hpart3 values(11); + +-- view data +select tableoid::regclass as part, a, a%4 as "remainder = a % 4" +from hash_parted order by part; + +-- test \d+ output on a table which has both partitioned and unpartitioned +-- partitions +\d+ list_parted + +-- cleanup +drop table range_parted, list_parted; +drop table hash_parted; + +-- test that a default partition added as the first partition accepts any value +-- including null +create table list_parted (a int) partition by list (a); +create table part_default partition of list_parted default; +\d+ part_default +insert into part_default values (null); +insert into part_default values (1); +insert into part_default values (-1); +select tableoid::regclass, a from list_parted; +-- cleanup +drop table list_parted; + +-- more tests for certain multi-level partitioning scenarios +create table mlparted (a int, b int) partition by range (a, b); +create table mlparted1 (b int not null, a int not null) partition by range ((b+0)); +create table mlparted11 (like mlparted1); +alter table mlparted11 drop a; +alter table mlparted11 add a int; +alter table mlparted11 drop a; +alter table mlparted11 add a int not null; +-- attnum for key attribute 'a' is different in mlparted, mlparted1, and mlparted11 +select attrelid::regclass, attname, attnum +from pg_attribute +where attname = 'a' + and (attrelid = 'mlparted'::regclass + or attrelid = 'mlparted1'::regclass + or attrelid = 'mlparted11'::regclass) +order by attrelid::regclass::text; + +alter table mlparted1 attach partition mlparted11 for values from (2) to (5); +alter table mlparted attach partition mlparted1 for values from (1, 2) to (1, 10); + +-- check that "(1, 2)" is correctly routed to mlparted11. +insert into mlparted values (1, 2); +select tableoid::regclass, * from mlparted; + +-- check that proper message is shown after failure to route through mlparted1 +insert into mlparted (a, b) values (1, 5); + +truncate mlparted; +alter table mlparted add constraint check_b check (b = 3); + +-- have a BR trigger modify the row such that the check_b is violated +create function mlparted11_trig_fn() +returns trigger AS +$$ +begin + NEW.b := 4; + return NEW; +end; +$$ +language plpgsql; +create trigger mlparted11_trig before insert ON mlparted11 + for each row execute procedure mlparted11_trig_fn(); + +-- check that the correct row is shown when constraint check_b fails after +-- "(1, 2)" is routed to mlparted11 (actually "(1, 4)" would be shown due +-- to the BR trigger mlparted11_trig_fn) +insert into mlparted values (1, 2); +drop trigger mlparted11_trig on mlparted11; +drop function mlparted11_trig_fn(); + +-- check that inserting into an internal partition successfully results in +-- checking its partition constraint before inserting into the leaf partition +-- selected by tuple-routing +insert into mlparted1 (a, b) values (2, 3); + +-- check routing error through a list partitioned table when the key is null +create table lparted_nonullpart (a int, b char) partition by list (b); +create table lparted_nonullpart_a partition of lparted_nonullpart for values in ('a'); +insert into lparted_nonullpart values (1); +drop table lparted_nonullpart; + +-- check that RETURNING works correctly with tuple-routing +alter table mlparted drop constraint check_b; +create table mlparted12 partition of mlparted1 for values from (5) to (10); +create table mlparted2 (b int not null, a int not null); +alter table mlparted attach partition mlparted2 for values from (1, 10) to (1, 20); +create table mlparted3 partition of mlparted for values from (1, 20) to (1, 30); +create table mlparted4 (like mlparted); +alter table mlparted4 drop a; +alter table mlparted4 add a int not null; +alter table mlparted attach partition mlparted4 for values from (1, 30) to (1, 40); +with ins (a, b, c) as + (insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *) + select a, b, min(c), max(c) from ins group by a, b order by 1; + +alter table mlparted add c text; +create table mlparted5 (c text, a int not null, b int not null) partition by list (c); +create table mlparted5a (a int not null, c text, b int not null); +alter table mlparted5 attach partition mlparted5a for values in ('a'); +alter table mlparted attach partition mlparted5 for values from (1, 40) to (1, 50); +alter table mlparted add constraint check_b check (a = 1 and b < 45); +insert into mlparted values (1, 45, 'a'); +create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b'; return new; end; $$ language plpgsql; +create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func(); +insert into mlparted5 (a, b, c) values (1, 40, 'a'); +drop table mlparted5; +alter table mlparted drop constraint check_b; + +-- Check multi-level default partition +create table mlparted_def partition of mlparted default partition by range(a); +create table mlparted_def1 partition of mlparted_def for values from (40) to (50); +create table mlparted_def2 partition of mlparted_def for values from (50) to (60); +insert into mlparted values (40, 100); +insert into mlparted_def1 values (42, 100); +insert into mlparted_def2 values (54, 50); +-- fail +insert into mlparted values (70, 100); +insert into mlparted_def1 values (52, 50); +insert into mlparted_def2 values (34, 50); +-- ok +create table mlparted_defd partition of mlparted_def default; +insert into mlparted values (70, 100); + +select tableoid::regclass, * from mlparted_def; + +-- Check multi-level tuple routing with attributes dropped from the +-- top-most parent. First remove the last attribute. +alter table mlparted add d int, add e int; +alter table mlparted drop e; +create table mlparted5 partition of mlparted + for values from (1, 40) to (1, 50) partition by range (c); +create table mlparted5_ab partition of mlparted5 + for values from ('a') to ('c') partition by list (c); +-- This partitioned table should remain with no partitions. +create table mlparted5_cd partition of mlparted5 + for values from ('c') to ('e') partition by list (c); +create table mlparted5_a partition of mlparted5_ab for values in ('a'); +create table mlparted5_b (d int, b int, c text, a int); +alter table mlparted5_ab attach partition mlparted5_b for values in ('b'); +truncate mlparted; +insert into mlparted values (1, 2, 'a', 1); +insert into mlparted values (1, 40, 'a', 1); -- goes to mlparted5_a +insert into mlparted values (1, 45, 'b', 1); -- goes to mlparted5_b +insert into mlparted values (1, 45, 'c', 1); -- goes to mlparted5_cd, fails +insert into mlparted values (1, 45, 'f', 1); -- goes to mlparted5, fails +select tableoid::regclass, * from mlparted order by a, b, c, d; +alter table mlparted drop d; +truncate mlparted; +-- Remove the before last attribute. +alter table mlparted add e int, add d int; +alter table mlparted drop e; +insert into mlparted values (1, 2, 'a', 1); +insert into mlparted values (1, 40, 'a', 1); -- goes to mlparted5_a +insert into mlparted values (1, 45, 'b', 1); -- goes to mlparted5_b +insert into mlparted values (1, 45, 'c', 1); -- goes to mlparted5_cd, fails +insert into mlparted values (1, 45, 'f', 1); -- goes to mlparted5, fails +select tableoid::regclass, * from mlparted order by a, b, c, d; +alter table mlparted drop d; +drop table mlparted5; + +-- check that message shown after failure to find a partition shows the +-- appropriate key description (or none) in various situations +create table key_desc (a int, b int) partition by list ((a+0)); +create table key_desc_1 partition of key_desc for values in (1) partition by range (b); + +create user regress_insert_other_user; +grant select (a) on key_desc_1 to regress_insert_other_user; +grant insert on key_desc to regress_insert_other_user; + +set role regress_insert_other_user; +-- no key description is shown +insert into key_desc values (1, 1); + +reset role; +grant select (b) on key_desc_1 to regress_insert_other_user; +set role regress_insert_other_user; +-- key description (b)=(1) is now shown +insert into key_desc values (1, 1); + +-- key description is not shown if key contains expression +insert into key_desc values (2, 1); +reset role; +revoke all on key_desc from regress_insert_other_user; +revoke all on key_desc_1 from regress_insert_other_user; +drop role regress_insert_other_user; +drop table key_desc, key_desc_1; + +-- test minvalue/maxvalue restrictions +create table mcrparted (a int, b int, c int) partition by range (a, abs(b), c); +create table mcrparted0 partition of mcrparted for values from (minvalue, 0, 0) to (1, maxvalue, maxvalue); +create table mcrparted2 partition of mcrparted for values from (10, 6, minvalue) to (10, maxvalue, minvalue); +create table mcrparted4 partition of mcrparted for values from (21, minvalue, 0) to (30, 20, minvalue); + +-- check multi-column range partitioning expression enforces the same +-- constraint as what tuple-routing would determine it to be +create table mcrparted0 partition of mcrparted for values from (minvalue, minvalue, minvalue) to (1, maxvalue, maxvalue); +create table mcrparted1 partition of mcrparted for values from (2, 1, minvalue) to (10, 5, 10); +create table mcrparted2 partition of mcrparted for values from (10, 6, minvalue) to (10, maxvalue, maxvalue); +create table mcrparted3 partition of mcrparted for values from (11, 1, 1) to (20, 10, 10); +create table mcrparted4 partition of mcrparted for values from (21, minvalue, minvalue) to (30, 20, maxvalue); +create table mcrparted5 partition of mcrparted for values from (30, 21, 20) to (maxvalue, maxvalue, maxvalue); + +-- null not allowed in range partition +insert into mcrparted values (null, null, null); + +-- routed to mcrparted0 +insert into mcrparted values (0, 1, 1); +insert into mcrparted0 values (0, 1, 1); + +-- routed to mcparted1 +insert into mcrparted values (9, 1000, 1); +insert into mcrparted1 values (9, 1000, 1); +insert into mcrparted values (10, 5, -1); +insert into mcrparted1 values (10, 5, -1); +insert into mcrparted values (2, 1, 0); +insert into mcrparted1 values (2, 1, 0); + +-- routed to mcparted2 +insert into mcrparted values (10, 6, 1000); +insert into mcrparted2 values (10, 6, 1000); +insert into mcrparted values (10, 1000, 1000); +insert into mcrparted2 values (10, 1000, 1000); + +-- no partition exists, nor does mcrparted3 accept it +insert into mcrparted values (11, 1, -1); +insert into mcrparted3 values (11, 1, -1); + +-- routed to mcrparted5 +insert into mcrparted values (30, 21, 20); +insert into mcrparted5 values (30, 21, 20); +insert into mcrparted4 values (30, 21, 20); -- error + +-- check rows +select tableoid::regclass::text, * from mcrparted order by 1; + +-- cleanup +drop table mcrparted; + +-- check that a BR constraint can't make partition contain violating rows +create table brtrigpartcon (a int, b text) partition by list (a); +create table brtrigpartcon1 partition of brtrigpartcon for values in (1); +create or replace function brtrigpartcon1trigf() returns trigger as $$begin new.a := 2; return new; end$$ language plpgsql; +create trigger brtrigpartcon1trig before insert on brtrigpartcon1 for each row execute procedure brtrigpartcon1trigf(); +insert into brtrigpartcon values (1, 'hi there'); +insert into brtrigpartcon1 values (1, 'hi there'); + +-- check that the message shows the appropriate column description in a +-- situation where the partitioned table is not the primary ModifyTable node +create table inserttest3 (f1 text default 'foo', f2 text default 'bar', f3 int); +create role regress_coldesc_role; +grant insert on inserttest3 to regress_coldesc_role; +grant insert on brtrigpartcon to regress_coldesc_role; +revoke select on brtrigpartcon from regress_coldesc_role; +set role regress_coldesc_role; +with result as (insert into brtrigpartcon values (1, 'hi there') returning 1) + insert into inserttest3 (f3) select * from result; +reset role; + +-- cleanup +revoke all on inserttest3 from regress_coldesc_role; +revoke all on brtrigpartcon from regress_coldesc_role; +drop role regress_coldesc_role; +drop table inserttest3; +drop table brtrigpartcon; +drop function brtrigpartcon1trigf(); + +-- check that "do nothing" BR triggers work with tuple-routing +create table donothingbrtrig_test (a int, b text) partition by list (a); +create table donothingbrtrig_test1 (b text, a int); +create table donothingbrtrig_test2 (c text, b text, a int); +alter table donothingbrtrig_test2 drop column c; +create or replace function donothingbrtrig_func() returns trigger as $$begin raise notice 'b: %', new.b; return NULL; end$$ language plpgsql; +create trigger donothingbrtrig1 before insert on donothingbrtrig_test1 for each row execute procedure donothingbrtrig_func(); +create trigger donothingbrtrig2 before insert on donothingbrtrig_test2 for each row execute procedure donothingbrtrig_func(); +alter table donothingbrtrig_test attach partition donothingbrtrig_test1 for values in (1); +alter table donothingbrtrig_test attach partition donothingbrtrig_test2 for values in (2); +insert into donothingbrtrig_test values (1, 'foo'), (2, 'bar'); +copy donothingbrtrig_test from stdout; +1 baz +2 qux +\. +select tableoid::regclass, * from donothingbrtrig_test; + +-- cleanup +drop table donothingbrtrig_test; +drop function donothingbrtrig_func(); + +-- check multi-column range partitioning with minvalue/maxvalue constraints +create table mcrparted (a text, b int) partition by range(a, b); +create table mcrparted1_lt_b partition of mcrparted for values from (minvalue, minvalue) to ('b', minvalue); +create table mcrparted2_b partition of mcrparted for values from ('b', minvalue) to ('c', minvalue); +create table mcrparted3_c_to_common partition of mcrparted for values from ('c', minvalue) to ('common', minvalue); +create table mcrparted4_common_lt_0 partition of mcrparted for values from ('common', minvalue) to ('common', 0); +create table mcrparted5_common_0_to_10 partition of mcrparted for values from ('common', 0) to ('common', 10); +create table mcrparted6_common_ge_10 partition of mcrparted for values from ('common', 10) to ('common', maxvalue); +create table mcrparted7_gt_common_lt_d partition of mcrparted for values from ('common', maxvalue) to ('d', minvalue); +create table mcrparted8_ge_d partition of mcrparted for values from ('d', minvalue) to (maxvalue, maxvalue); + +\d+ mcrparted +\d+ mcrparted1_lt_b +\d+ mcrparted2_b +\d+ mcrparted3_c_to_common +\d+ mcrparted4_common_lt_0 +\d+ mcrparted5_common_0_to_10 +\d+ mcrparted6_common_ge_10 +\d+ mcrparted7_gt_common_lt_d +\d+ mcrparted8_ge_d + +insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10), + ('comm', -10), ('common', -10), ('common', 0), ('common', 10), + ('commons', 0), ('d', -10), ('e', 0); +select tableoid::regclass, * from mcrparted order by a, b; +drop table mcrparted; + +-- check that wholerow vars in the RETURNING list work with partitioned tables +create table returningwrtest (a int) partition by list (a); +create table returningwrtest1 partition of returningwrtest for values in (1); +insert into returningwrtest values (1) returning returningwrtest; + +-- check also that the wholerow vars in RETURNING list are converted as needed +alter table returningwrtest add b text; +create table returningwrtest2 (b text, c int, a int); +alter table returningwrtest2 drop c; +alter table returningwrtest attach partition returningwrtest2 for values in (2); +insert into returningwrtest values (2, 'foo') returning returningwrtest; +drop table returningwrtest; diff --git a/src/test/regress/sql/yb.port.insert_conflict.sql b/src/test/regress/sql/yb.port.insert_conflict.sql new file mode 100644 index 0000000000000..5b191a77a852e --- /dev/null +++ b/src/test/regress/sql/yb.port.insert_conflict.sql @@ -0,0 +1,594 @@ +-- +-- insert...on conflict do unique index inference +-- +create table insertconflicttest(key int4, fruit text); + +-- +-- Test unique index inference with operator class specifications and +-- named collations +-- +create unique index op_index_key on insertconflicttest(key, fruit text_pattern_ops); +create unique index collation_index_key on insertconflicttest(key, fruit collate "C"); +create unique index both_index_key on insertconflicttest(key, fruit collate "C" text_pattern_ops); +create unique index both_index_expr_key on insertconflicttest(key, lower(fruit) collate "C" text_pattern_ops); + +-- fails +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key) do nothing; +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit) do nothing; + +-- succeeds +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit) do nothing; +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit, key, fruit, key) do nothing; +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit), key, lower(fruit), key) do nothing; +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit) do update set fruit = excluded.fruit + where exists (select 1 from insertconflicttest ii where ii.key = excluded.key); +-- Neither collation nor operator class specifications are required -- +-- supplying them merely *limits* matches to indexes with matching opclasses +-- used for relevant indexes +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit text_pattern_ops) do nothing; +-- Okay, arbitrates using both index where text_pattern_ops opclass does and +-- does not appear. +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit collate "C") do nothing; +-- Okay, but only accepts the single index where both opclass and collation are +-- specified +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit collate "C" text_pattern_ops, key) do nothing; +-- Okay, but only accepts the single index where both opclass and collation are +-- specified (plus expression variant) +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) collate "C", key, key) do nothing; +-- Attribute appears twice, while not all attributes/expressions on attributes +-- appearing within index definition match in terms of both opclass and +-- collation. +-- +-- Works because every attribute in inference specification needs to be +-- satisfied once or more by cataloged index attribute, and as always when an +-- attribute in the cataloged definition has a non-default opclass/collation, +-- it still satisfied some inference attribute lacking any particular +-- opclass/collation specification. +-- +-- The implementation is liberal in accepting inference specifications on the +-- assumption that multiple inferred unique indexes will prevent problematic +-- cases. It rolls with unique indexes where attributes redundantly appear +-- multiple times, too (which is not tested here). +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit, key, fruit text_pattern_ops, key) do nothing; +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) collate "C" text_pattern_ops, key, key) do nothing; + +drop index op_index_key; +drop index collation_index_key; +drop index both_index_key; +drop index both_index_expr_key; + +-- +-- Make sure that cross matching of attribute opclass/collation does not occur +-- +create unique index cross_match on insertconflicttest(lower(fruit) collate "C", upper(fruit) text_pattern_ops); + +-- fails: +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) text_pattern_ops, upper(fruit) collate "C") do nothing; +-- works: +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) collate "C", upper(fruit) text_pattern_ops) do nothing; + +drop index cross_match; + +-- +-- Single key tests +-- +create unique index key_index on insertconflicttest(key); + +-- +-- Explain tests +-- +explain (costs off) insert into insertconflicttest values (0, 'Bilberry') on conflict (key) do update set fruit = excluded.fruit; +-- Should display qual actually attributable to internal sequential scan: +explain (costs off) insert into insertconflicttest values (0, 'Bilberry') on conflict (key) do update set fruit = excluded.fruit where insertconflicttest.fruit != 'Cawesh'; +-- With EXCLUDED.* expression in scan node: +explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key) do update set fruit = excluded.fruit where excluded.fruit != 'Elderberry'; +-- Does the same, but JSON format shows "Conflict Arbiter Index" as JSON array: +explain (costs off, format json) insert into insertconflicttest values (0, 'Bilberry') on conflict (key) do update set fruit = excluded.fruit where insertconflicttest.fruit != 'Lime' returning *; + +-- Fails (no unique index inference specification, required for do update variant): +insert into insertconflicttest values (1, 'Apple') on conflict do update set fruit = excluded.fruit; + +-- inference succeeds: +insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit; +insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key) do update set fruit = excluded.fruit; + +-- Succeed, since multi-assignment does not involve subquery: +insert into insertconflicttest +values (1, 'Apple'), (2, 'Orange') +on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key); + +-- Give good diagnostic message when EXCLUDED.* spuriously referenced from +-- RETURNING: +insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit; + +-- Only suggest .* column when inference element misspelled: +insert into insertconflicttest values (1, 'Apple') on conflict (keyy) do update set fruit = excluded.fruit; + +-- Have useful HINT for EXCLUDED.* RTE within UPDATE: +insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruitt; + +-- inference fails: +insert into insertconflicttest values (3, 'Kiwi') on conflict (key, fruit) do update set fruit = excluded.fruit; +insert into insertconflicttest values (4, 'Mango') on conflict (fruit, key) do update set fruit = excluded.fruit; +insert into insertconflicttest values (5, 'Lemon') on conflict (fruit) do update set fruit = excluded.fruit; +insert into insertconflicttest values (6, 'Passionfruit') on conflict (lower(fruit)) do update set fruit = excluded.fruit; + +-- Check the target relation can be aliased +insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (key) do update set fruit = excluded.fruit; -- ok, no reference to target table +insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (key) do update set fruit = ict.fruit; -- ok, alias +insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (key) do update set fruit = insertconflicttest.fruit; -- error, references aliased away name + +drop index key_index; + +-- +-- Composite key tests +-- +create unique index comp_key_index on insertconflicttest(key, fruit); + +-- inference succeeds: +insert into insertconflicttest values (7, 'Raspberry') on conflict (key, fruit) do update set fruit = excluded.fruit; +insert into insertconflicttest values (8, 'Lime') on conflict (fruit, key) do update set fruit = excluded.fruit; + +-- inference fails: +insert into insertconflicttest values (9, 'Banana') on conflict (key) do update set fruit = excluded.fruit; +insert into insertconflicttest values (10, 'Blueberry') on conflict (key, key, key) do update set fruit = excluded.fruit; +insert into insertconflicttest values (11, 'Cherry') on conflict (key, lower(fruit)) do update set fruit = excluded.fruit; +insert into insertconflicttest values (12, 'Date') on conflict (lower(fruit), key) do update set fruit = excluded.fruit; + +drop index comp_key_index; + +-- +-- Partial index tests, no inference predicate specified +-- +create unique index part_comp_key_index on insertconflicttest(key, fruit) where key < 5; +create unique index expr_part_comp_key_index on insertconflicttest(key, lower(fruit)) where key < 5; + +-- inference fails: +insert into insertconflicttest values (13, 'Grape') on conflict (key, fruit) do update set fruit = excluded.fruit; +insert into insertconflicttest values (14, 'Raisin') on conflict (fruit, key) do update set fruit = excluded.fruit; +insert into insertconflicttest values (15, 'Cranberry') on conflict (key) do update set fruit = excluded.fruit; +insert into insertconflicttest values (16, 'Melon') on conflict (key, key, key) do update set fruit = excluded.fruit; +insert into insertconflicttest values (17, 'Mulberry') on conflict (key, lower(fruit)) do update set fruit = excluded.fruit; +insert into insertconflicttest values (18, 'Pineapple') on conflict (lower(fruit), key) do update set fruit = excluded.fruit; + +drop index part_comp_key_index; +drop index expr_part_comp_key_index; + +-- +-- Expression index tests +-- +create unique index expr_key_index on insertconflicttest(lower(fruit)); + +-- inference succeeds: +insert into insertconflicttest values (20, 'Quince') on conflict (lower(fruit)) do update set fruit = excluded.fruit; +insert into insertconflicttest values (21, 'Pomegranate') on conflict (lower(fruit), lower(fruit)) do update set fruit = excluded.fruit; + +-- inference fails: +insert into insertconflicttest values (22, 'Apricot') on conflict (upper(fruit)) do update set fruit = excluded.fruit; +insert into insertconflicttest values (23, 'Blackberry') on conflict (fruit) do update set fruit = excluded.fruit; + +drop index expr_key_index; + +-- +-- Expression index tests (with regular column) +-- +create unique index expr_comp_key_index on insertconflicttest(key, lower(fruit)); +create unique index tricky_expr_comp_key_index on insertconflicttest(key, lower(fruit), upper(fruit)); + +-- inference succeeds: +insert into insertconflicttest values (24, 'Plum') on conflict (key, lower(fruit)) do update set fruit = excluded.fruit; +insert into insertconflicttest values (25, 'Peach') on conflict (lower(fruit), key) do update set fruit = excluded.fruit; +-- Should not infer "tricky_expr_comp_key_index" index: +explain (costs off) insert into insertconflicttest values (26, 'Fig') on conflict (lower(fruit), key, lower(fruit), key) do update set fruit = excluded.fruit; + +-- inference fails: +insert into insertconflicttest values (27, 'Prune') on conflict (key, upper(fruit)) do update set fruit = excluded.fruit; +insert into insertconflicttest values (28, 'Redcurrant') on conflict (fruit, key) do update set fruit = excluded.fruit; +insert into insertconflicttest values (29, 'Nectarine') on conflict (key) do update set fruit = excluded.fruit; + +drop index expr_comp_key_index; +drop index tricky_expr_comp_key_index; + +-- +-- Non-spurious duplicate violation tests +-- +create unique index key_index on insertconflicttest(key); +create unique index fruit_index on insertconflicttest(fruit); + +-- succeeds, since UPDATE happens to update "fruit" to existing value: +insert into insertconflicttest values (26, 'Fig') on conflict (key) do update set fruit = excluded.fruit; +-- fails, since UPDATE is to row with key value 26, and we're updating "fruit" +-- to a value that happens to exist in another row ('peach'): +insert into insertconflicttest values (26, 'Peach') on conflict (key) do update set fruit = excluded.fruit; -- YB: output is missing detail because YB handles duplicate detection differently +-- succeeds, since "key" isn't repeated/referenced in UPDATE, and "fruit" +-- arbitrates that statement updates existing "Fig" row: +insert into insertconflicttest values (25, 'Fig') on conflict (fruit) do update set fruit = excluded.fruit; + +drop index key_index; +drop index fruit_index; + +-- +-- Test partial unique index inference +-- +create unique index partial_key_index on insertconflicttest(key) where fruit like '%berry'; + +-- Succeeds +insert into insertconflicttest values (23, 'Blackberry') on conflict (key) where fruit like '%berry' do update set fruit = excluded.fruit; +insert into insertconflicttest as t values (23, 'Blackberry') on conflict (key) where fruit like '%berry' and t.fruit = 'inconsequential' do nothing; + +-- fails +insert into insertconflicttest values (23, 'Blackberry') on conflict (key) do update set fruit = excluded.fruit; +insert into insertconflicttest values (23, 'Blackberry') on conflict (key) where fruit like '%berry' or fruit = 'consequential' do nothing; +insert into insertconflicttest values (23, 'Blackberry') on conflict (fruit) where fruit like '%berry' do update set fruit = excluded.fruit; + +drop index partial_key_index; + +-- +-- Test that wholerow references to ON CONFLICT's EXCLUDED work +-- +create unique index plain on insertconflicttest(key); + +-- Succeeds, updates existing row: +insert into insertconflicttest as i values (23, 'Jackfruit') on conflict (key) do update set fruit = excluded.fruit + where i.* != excluded.* returning *; +-- No update this time, though: +insert into insertconflicttest as i values (23, 'Jackfruit') on conflict (key) do update set fruit = excluded.fruit + where i.* != excluded.* returning *; +-- Predicate changed to require match rather than non-match, so updates once more: +insert into insertconflicttest as i values (23, 'Jackfruit') on conflict (key) do update set fruit = excluded.fruit + where i.* = excluded.* returning *; +-- Assign: +insert into insertconflicttest as i values (23, 'Avocado') on conflict (key) do update set fruit = excluded.*::text + returning *; +-- deparse whole row var in WHERE and SET clauses: +explain (costs off) insert into insertconflicttest as i values (23, 'Avocado') on conflict (key) do update set fruit = excluded.fruit where excluded.* is null; +explain (costs off) insert into insertconflicttest as i values (23, 'Avocado') on conflict (key) do update set fruit = excluded.*::text; + +drop index plain; + +-- Cleanup +drop table insertconflicttest; + + +-- +-- Verify that EXCLUDED does not allow system column references. These +-- do not make sense because EXCLUDED isn't an already stored tuple +-- (and thus doesn't have a ctid etc). +-- +create table syscolconflicttest(key int4, data text); +insert into syscolconflicttest values (1); +insert into syscolconflicttest values (1) on conflict (key) do update set data = excluded.ctid::text; -- YB: output shows YB error message hit first before PG one +drop table syscolconflicttest; + +-- +-- Previous tests all managed to not test any expressions requiring +-- planner preprocessing ... +-- +create table insertconflict (a bigint, b bigint); + +create unique index insertconflicti1 on insertconflict(coalesce(a, 0)); + +create unique index insertconflicti2 on insertconflict(b) + where coalesce(a, 1) > 0; + +insert into insertconflict values (1, 2) +on conflict (coalesce(a, 0)) do nothing; + +insert into insertconflict values (1, 2) +on conflict (b) where coalesce(a, 1) > 0 do nothing; + +insert into insertconflict values (1, 2) +on conflict (b) where coalesce(a, 1) > 1 do nothing; + +drop table insertconflict; + +-- +-- test insertion through view +-- + +create table insertconflict (f1 int primary key, f2 text); +create view insertconflictv as + select * from insertconflict with cascaded check option; +create view insertconflictv as select * from insertconflict; -- YB: workaround view to replace above + +insert into insertconflictv values (1,'foo') + on conflict (f1) do update set f2 = excluded.f2; +select * from insertconflict; +insert into insertconflictv values (1,'bar') + on conflict (f1) do update set f2 = excluded.f2; +select * from insertconflict; + +drop view insertconflictv; +drop table insertconflict; + + +-- ****************************************************************** +-- * * +-- * Test inheritance (example taken from tutorial) * +-- * * +-- ****************************************************************** +create table cities ( + name text, + population float8, + altitude int -- (in ft) +); + +create table capitals ( + state char(2) +) inherits (cities); + +-- Create unique indexes. Due to a general limitation of inheritance, +-- uniqueness is only enforced per-relation. Unique index inference +-- specification will do the right thing, though. +create unique index cities_names_unique on cities (name); +create unique index capitals_names_unique on capitals (name); + +-- prepopulate the tables. +insert into cities values ('San Francisco', 7.24E+5, 63); +insert into cities values ('Las Vegas', 2.583E+5, 2174); +insert into cities values ('Mariposa', 1200, 1953); + +insert into capitals values ('Sacramento', 3.694E+5, 30, 'CA'); +insert into capitals values ('Madison', 1.913E+5, 845, 'WI'); + +-- Tests proper for inheritance: +select * from capitals; + +-- Succeeds: +insert into cities values ('Las Vegas', 2.583E+5, 2174) on conflict do nothing; +WITH ybtmp AS (DELETE FROM capitals WHERE name = 'Sacramento' RETURNING *) + INSERT INTO capitals SELECT * FROM ybtmp; -- YB: imitate PG ctid reallocation for the following update +insert into capitals values ('Sacramento', 4664.E+5, 30, 'CA') on conflict (name) do update set population = excluded.population; +-- Wrong "Sacramento", so do nothing: +insert into capitals values ('Sacramento', 50, 2267, 'NE') on conflict (name) do nothing; +select * from capitals; +with ybtmp as (delete from cities where name = 'Las Vegas' returning *) + insert into cities select * from ybtmp; -- YB: imitate PG ctid reallocation for the following update +insert into cities values ('Las Vegas', 5.83E+5, 2001) on conflict (name) do update set population = excluded.population, altitude = excluded.altitude; +select tableoid::regclass, * from cities; +insert into capitals values ('Las Vegas', 5.83E+5, 2222, 'NV') on conflict (name) do update set population = excluded.population; +-- Capitals will contain new capital, Las Vegas: +select * from capitals; +-- Cities contains two instances of "Las Vegas", since unique constraints don't +-- work across inheritance: +select tableoid::regclass, * from cities; +-- This only affects "cities" version of "Las Vegas": +insert into cities values ('Las Vegas', 5.86E+5, 2223) on conflict (name) do update set population = excluded.population, altitude = excluded.altitude; +select tableoid::regclass, * from cities; + +-- clean up +drop table capitals; +drop table cities; + + +-- Make sure a table named excluded is handled properly +create table excluded(key int primary key, data text); +insert into excluded values(1, '1'); +-- error, ambiguous +insert into excluded values(1, '2') on conflict (key) do update set data = excluded.data RETURNING *; +-- ok, aliased +insert into excluded AS target values(1, '2') on conflict (key) do update set data = excluded.data RETURNING *; +-- ok, aliased +insert into excluded AS target values(1, '2') on conflict (key) do update set data = target.data RETURNING *; +-- make sure excluded isn't a problem in returning clause +insert into excluded values(1, '2') on conflict (key) do update set data = 3 RETURNING excluded.*; + +-- clean up +drop table excluded; + + +-- check that references to columns after dropped columns are handled correctly +create table dropcol(key int primary key, drop1 int, keep1 text, drop2 numeric, keep2 float); +insert into dropcol(key, drop1, keep1, drop2, keep2) values(1, 1, '1', '1', 1); +-- set using excluded +insert into dropcol(key, drop1, keep1, drop2, keep2) values(1, 2, '2', '2', 2) on conflict(key) + do update set drop1 = excluded.drop1, keep1 = excluded.keep1, drop2 = excluded.drop2, keep2 = excluded.keep2 + where excluded.drop1 is not null and excluded.keep1 is not null and excluded.drop2 is not null and excluded.keep2 is not null + and dropcol.drop1 is not null and dropcol.keep1 is not null and dropcol.drop2 is not null and dropcol.keep2 is not null + returning *; +; +-- set using existing table +insert into dropcol(key, drop1, keep1, drop2, keep2) values(1, 3, '3', '3', 3) on conflict(key) + do update set drop1 = dropcol.drop1, keep1 = dropcol.keep1, drop2 = dropcol.drop2, keep2 = dropcol.keep2 + returning *; +; +alter table dropcol drop column drop1, drop column drop2; +-- set using excluded +insert into dropcol(key, keep1, keep2) values(1, '4', 4) on conflict(key) + do update set keep1 = excluded.keep1, keep2 = excluded.keep2 + where excluded.keep1 is not null and excluded.keep2 is not null + and dropcol.keep1 is not null and dropcol.keep2 is not null + returning *; +; +-- set using existing table +insert into dropcol(key, keep1, keep2) values(1, '5', 5) on conflict(key) + do update set keep1 = dropcol.keep1, keep2 = dropcol.keep2 + returning *; +; + +DROP TABLE dropcol; + +/* YB: EXCLUDE constraint not supported yet - see #997 +-- check handling of regular btree constraint along with gist constraint + +create table twoconstraints (f1 int unique, f2 box, + exclude using gist(f2 with &&)); +insert into twoconstraints values(1, '((0,0),(1,1))'); +insert into twoconstraints values(1, '((2,2),(3,3))'); -- fail on f1 +insert into twoconstraints values(2, '((0,0),(1,2))'); -- fail on f2 +insert into twoconstraints values(2, '((0,0),(1,2))') + on conflict on constraint twoconstraints_f1_key do nothing; -- fail on f2 +insert into twoconstraints values(2, '((0,0),(1,2))') + on conflict on constraint twoconstraints_f2_excl do nothing; -- do nothing +select * from twoconstraints; +drop table twoconstraints; +*/ -- YB + +-- check handling of self-conflicts at various isolation levels + +create table selfconflict (f1 int primary key, f2 int); + +begin transaction isolation level read committed; +insert into selfconflict values (1,1), (1,2) on conflict do nothing; +commit; + +begin transaction isolation level repeatable read; +insert into selfconflict values (2,1), (2,2) on conflict do nothing; +commit; + +begin transaction isolation level serializable; +insert into selfconflict values (3,1), (3,2) on conflict do nothing; +commit; + +begin transaction isolation level read committed; +insert into selfconflict values (4,1), (4,2) on conflict(f1) do update set f2 = 0; +commit; + +begin transaction isolation level repeatable read; +insert into selfconflict values (5,1), (5,2) on conflict(f1) do update set f2 = 0; +commit; + +begin transaction isolation level serializable; +insert into selfconflict values (6,1), (6,2) on conflict(f1) do update set f2 = 0; +commit; + +select * from selfconflict; + +drop table selfconflict; + +-- check ON CONFLICT handling with partitioned tables +create table parted_conflict_test (a int unique, b char) partition by list (a); +create table parted_conflict_test_1 partition of parted_conflict_test (b unique) for values in (1, 2); + +-- no indexes required here +insert into parted_conflict_test values (1, 'a') on conflict do nothing; + +-- index on a required, which does exist in parent +insert into parted_conflict_test values (1, 'a') on conflict (a) do nothing; +insert into parted_conflict_test values (1, 'a') on conflict (a) do update set b = excluded.b; + +-- targeting partition directly will work +insert into parted_conflict_test_1 values (1, 'a') on conflict (a) do nothing; +insert into parted_conflict_test_1 values (1, 'b') on conflict (a) do update set b = excluded.b; + +-- index on b required, which doesn't exist in parent +insert into parted_conflict_test values (2, 'b') on conflict (b) do update set a = excluded.a; + +-- targeting partition directly will work +insert into parted_conflict_test_1 values (2, 'b') on conflict (b) do update set a = excluded.a; + +-- should see (2, 'b') +select * from parted_conflict_test order by a; + +-- now check that DO UPDATE works correctly for target partition with +-- different attribute numbers +create table parted_conflict_test_2 (b char, a int unique); +alter table parted_conflict_test attach partition parted_conflict_test_2 for values in (3); +truncate parted_conflict_test; +insert into parted_conflict_test values (3, 'a') on conflict (a) do update set b = excluded.b; +insert into parted_conflict_test values (3, 'b') on conflict (a) do update set b = excluded.b; + +-- should see (3, 'b') +select * from parted_conflict_test order by a; + +-- case where parent will have a dropped column, but the partition won't +-- YB Note: Dropping and adding a column with the same name is not supported in YB. +-- Split the operation into two steps. +alter table parted_conflict_test drop b; +alter table parted_conflict_test add b char; +create table parted_conflict_test_3 partition of parted_conflict_test for values in (4); +truncate parted_conflict_test; +insert into parted_conflict_test (a, b) values (4, 'a') on conflict (a) do update set b = excluded.b; +insert into parted_conflict_test (a, b) values (4, 'b') on conflict (a) do update set b = excluded.b where parted_conflict_test.b = 'a'; + +-- should see (4, 'b') +select * from parted_conflict_test order by a; + +-- case with multi-level partitioning +create table parted_conflict_test_4 partition of parted_conflict_test for values in (5) partition by list (a); +create table parted_conflict_test_4_1 partition of parted_conflict_test_4 for values in (5); +truncate parted_conflict_test; +insert into parted_conflict_test (a, b) values (5, 'a') on conflict (a) do update set b = excluded.b; +insert into parted_conflict_test (a, b) values (5, 'b') on conflict (a) do update set b = excluded.b where parted_conflict_test.b = 'a'; + +-- should see (5, 'b') +select * from parted_conflict_test order by a; + +-- test with multiple rows +truncate parted_conflict_test; +insert into parted_conflict_test (a, b) values (1, 'a'), (2, 'a'), (4, 'a') on conflict (a) do update set b = excluded.b where excluded.b = 'b'; +insert into parted_conflict_test (a, b) values (1, 'b'), (2, 'c'), (4, 'b') on conflict (a) do update set b = excluded.b where excluded.b = 'b'; + +-- should see (1, 'b'), (2, 'a'), (4, 'b') +select * from parted_conflict_test order by a; + +drop table parted_conflict_test; + +-- test behavior of inserting a conflicting tuple into an intermediate +-- partitioning level +create table parted_conflict (a int primary key, b text) partition by range (a); +create table parted_conflict_1 partition of parted_conflict for values from (0) to (1000) partition by range (a); +create table parted_conflict_1_1 partition of parted_conflict_1 for values from (0) to (500); +insert into parted_conflict values (40, 'forty'); +insert into parted_conflict_1 values (40, 'cuarenta') + on conflict (a) do update set b = excluded.b; +drop table parted_conflict; + +-- same thing, but this time try to use an index that's created not in the +-- partition +create table parted_conflict (a int, b text) partition by range (a); +create table parted_conflict_1 partition of parted_conflict for values from (0) to (1000) partition by range (a); +create table parted_conflict_1_1 partition of parted_conflict_1 for values from (0) to (500); +create unique index on only parted_conflict_1 (a); +create unique index on only parted_conflict (a); +alter index parted_conflict_a_idx attach partition parted_conflict_1_a_idx; +insert into parted_conflict values (40, 'forty'); +insert into parted_conflict_1 values (40, 'cuarenta') + on conflict (a) do update set b = excluded.b; +drop table parted_conflict; + +-- test whole-row Vars in ON CONFLICT expressions +create table parted_conflict (a int, b text, c int) partition by range (a); +create table parted_conflict_1 (drp text, c int, a int, b text); +alter table parted_conflict_1 drop column drp; +create unique index on parted_conflict (a, b); +alter table parted_conflict attach partition parted_conflict_1 for values from (0) to (1000); +truncate parted_conflict; +insert into parted_conflict values (50, 'cincuenta', 1); +insert into parted_conflict values (50, 'cincuenta', 2) + on conflict (a, b) do update set (a, b, c) = row(excluded.*) + where parted_conflict = (50, text 'cincuenta', 1) and + excluded = (50, text 'cincuenta', 2); + +-- should see (50, 'cincuenta', 2) +select * from parted_conflict order by a; + +-- test with statement level triggers +create or replace function parted_conflict_update_func() returns trigger as $$ +declare + r record; +begin + for r in select * from inserted loop + raise notice 'a = %, b = %, c = %', r.a, r.b, r.c; + end loop; + return new; +end; +$$ language plpgsql; + +create trigger parted_conflict_update + after update on parted_conflict + referencing new table as inserted + for each statement + execute procedure parted_conflict_update_func(); + +/* YB: Disabled as create trigger above fails +truncate parted_conflict; + +insert into parted_conflict values (0, 'cero', 1); + +insert into parted_conflict values(0, 'cero', 1) + on conflict (a,b) do update set c = parted_conflict.c + 1; +*/ -- YB + +drop table parted_conflict; +drop function parted_conflict_update_func(); diff --git a/src/test/regress/sql/yb.port.int8.sql b/src/test/regress/sql/yb.port.int8.sql new file mode 100644 index 0000000000000..88b29213a13f5 --- /dev/null +++ b/src/test/regress/sql/yb.port.int8.sql @@ -0,0 +1,249 @@ +-- +-- INT8 +-- Test int8 64-bit integers. +-- + +-- int8_tbl was already created and filled in test_setup.sql. +-- Here we just try to insert bad values. + +INSERT INTO INT8_TBL(q1) VALUES (' '); +INSERT INTO INT8_TBL(q1) VALUES ('xxx'); +INSERT INTO INT8_TBL(q1) VALUES ('3908203590239580293850293850329485'); +INSERT INTO INT8_TBL(q1) VALUES ('-1204982019841029840928340329840934'); +INSERT INTO INT8_TBL(q1) VALUES ('- 123'); +INSERT INTO INT8_TBL(q1) VALUES (' 345 5'); +INSERT INTO INT8_TBL(q1) VALUES (''); + +SELECT * FROM INT8_TBL; + +-- int8/int8 cmp +SELECT * FROM INT8_TBL WHERE q2 = 4567890123456789; +SELECT * FROM INT8_TBL WHERE q2 <> 4567890123456789; +SELECT * FROM INT8_TBL WHERE q2 < 4567890123456789; +SELECT * FROM INT8_TBL WHERE q2 > 4567890123456789; +SELECT * FROM INT8_TBL WHERE q2 <= 4567890123456789; +SELECT * FROM INT8_TBL WHERE q2 >= 4567890123456789; + +-- int8/int4 cmp +SELECT * FROM INT8_TBL WHERE q2 = 456; +SELECT * FROM INT8_TBL WHERE q2 <> 456; +SELECT * FROM INT8_TBL WHERE q2 < 456; +SELECT * FROM INT8_TBL WHERE q2 > 456; +SELECT * FROM INT8_TBL WHERE q2 <= 456; +SELECT * FROM INT8_TBL WHERE q2 >= 456; + +-- int4/int8 cmp +SELECT * FROM INT8_TBL WHERE 123 = q1; +SELECT * FROM INT8_TBL WHERE 123 <> q1; +SELECT * FROM INT8_TBL WHERE 123 < q1; +SELECT * FROM INT8_TBL WHERE 123 > q1; +SELECT * FROM INT8_TBL WHERE 123 <= q1; +SELECT * FROM INT8_TBL WHERE 123 >= q1; + +-- int8/int2 cmp +SELECT * FROM INT8_TBL WHERE q2 = '456'::int2; +SELECT * FROM INT8_TBL WHERE q2 <> '456'::int2; +SELECT * FROM INT8_TBL WHERE q2 < '456'::int2; +SELECT * FROM INT8_TBL WHERE q2 > '456'::int2; +SELECT * FROM INT8_TBL WHERE q2 <= '456'::int2; +SELECT * FROM INT8_TBL WHERE q2 >= '456'::int2; + +-- int2/int8 cmp +SELECT * FROM INT8_TBL WHERE '123'::int2 = q1; +SELECT * FROM INT8_TBL WHERE '123'::int2 <> q1; +SELECT * FROM INT8_TBL WHERE '123'::int2 < q1; +SELECT * FROM INT8_TBL WHERE '123'::int2 > q1; +SELECT * FROM INT8_TBL WHERE '123'::int2 <= q1; +SELECT * FROM INT8_TBL WHERE '123'::int2 >= q1; + + +SELECT q1 AS plus, -q1 AS minus FROM INT8_TBL; + +SELECT q1, q2, q1 + q2 AS plus FROM INT8_TBL; +SELECT q1, q2, q1 - q2 AS minus FROM INT8_TBL; +SELECT q1, q2, q1 * q2 AS multiply FROM INT8_TBL; +SELECT q1, q2, q1 * q2 AS multiply FROM INT8_TBL + WHERE q1 < 1000 or (q2 > 0 and q2 < 1000); +SELECT q1, q2, q1 / q2 AS divide, q1 % q2 AS mod FROM INT8_TBL; + +SELECT q1, float8(q1) FROM INT8_TBL; +SELECT q2, float8(q2) FROM INT8_TBL; + +SELECT 37 + q1 AS plus4 FROM INT8_TBL; +SELECT 37 - q1 AS minus4 FROM INT8_TBL; +SELECT 2 * q1 AS "twice int4" FROM INT8_TBL; +SELECT q1 * 2 AS "twice int4" FROM INT8_TBL; + +-- int8 op int4 +SELECT q1 + 42::int4 AS "8plus4", q1 - 42::int4 AS "8minus4", q1 * 42::int4 AS "8mul4", q1 / 42::int4 AS "8div4" FROM INT8_TBL; +-- int4 op int8 +SELECT 246::int4 + q1 AS "4plus8", 246::int4 - q1 AS "4minus8", 246::int4 * q1 AS "4mul8", 246::int4 / q1 AS "4div8" FROM INT8_TBL; + +-- int8 op int2 +SELECT q1 + 42::int2 AS "8plus2", q1 - 42::int2 AS "8minus2", q1 * 42::int2 AS "8mul2", q1 / 42::int2 AS "8div2" FROM INT8_TBL; +-- int2 op int8 +SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 AS "2mul8", 246::int2 / q1 AS "2div8" FROM INT8_TBL; + +SELECT q2, abs(q2) FROM INT8_TBL; +SELECT min(q1), min(q2) FROM INT8_TBL; +SELECT max(q1), max(q2) FROM INT8_TBL; + + +-- TO_CHAR() +-- +SELECT to_char(q1, '9G999G999G999G999G999'), to_char(q2, '9,999,999,999,999,999') + FROM INT8_TBL; + +SELECT to_char(q1, '9G999G999G999G999G999D999G999'), to_char(q2, '9,999,999,999,999,999.999,999') + FROM INT8_TBL; + +SELECT to_char( (q1 * -1), '9999999999999999PR'), to_char( (q2 * -1), '9999999999999999.999PR') + FROM INT8_TBL; + +SELECT to_char( (q1 * -1), '9999999999999999S'), to_char( (q2 * -1), 'S9999999999999999') + FROM INT8_TBL; + +SELECT to_char(q2, 'MI9999999999999999') FROM INT8_TBL; +SELECT to_char(q2, 'FMS9999999999999999') FROM INT8_TBL; +SELECT to_char(q2, 'FM9999999999999999THPR') FROM INT8_TBL; +SELECT to_char(q2, 'SG9999999999999999th') FROM INT8_TBL; +SELECT to_char(q2, '0999999999999999') FROM INT8_TBL; +SELECT to_char(q2, 'S0999999999999999') FROM INT8_TBL; +SELECT to_char(q2, 'FM0999999999999999') FROM INT8_TBL; +SELECT to_char(q2, 'FM9999999999999999.000') FROM INT8_TBL; +-- YB: TODO: Disabled the test below as it relies on the current locale which is +-- configured in pg_regress using ALTER which we do not support yet. +-- SELECT to_char(q2, 'L9999999999999999.000') FROM INT8_TBL; +SELECT to_char(q2, 'FM9999999999999999.999') FROM INT8_TBL; +SELECT to_char(q2, 'S 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 . 9 9 9') FROM INT8_TBL; +SELECT to_char(q2, E'99999 "text" 9999 "9999" 999 "\\"text between quote marks\\"" 9999') FROM INT8_TBL; +SELECT to_char(q2, '999999SG9999999999') FROM INT8_TBL; + +-- check min/max values and overflow behavior + +select '-9223372036854775808'::int8; +select '-9223372036854775809'::int8; +select '9223372036854775807'::int8; +select '9223372036854775808'::int8; + +select -('-9223372036854775807'::int8); +select -('-9223372036854775808'::int8); + +select '9223372036854775800'::int8 + '9223372036854775800'::int8; +select '-9223372036854775800'::int8 + '-9223372036854775800'::int8; + +select '9223372036854775800'::int8 - '-9223372036854775800'::int8; +select '-9223372036854775800'::int8 - '9223372036854775800'::int8; + +select '9223372036854775800'::int8 * '9223372036854775800'::int8; + +select '9223372036854775800'::int8 / '0'::int8; +select '9223372036854775800'::int8 % '0'::int8; + +select abs('-9223372036854775808'::int8); + +select '9223372036854775800'::int8 + '100'::int4; +select '-9223372036854775800'::int8 - '100'::int4; +select '9223372036854775800'::int8 * '100'::int4; + +select '100'::int4 + '9223372036854775800'::int8; +select '-100'::int4 - '9223372036854775800'::int8; +select '100'::int4 * '9223372036854775800'::int8; + +select '9223372036854775800'::int8 + '100'::int2; +select '-9223372036854775800'::int8 - '100'::int2; +select '9223372036854775800'::int8 * '100'::int2; +select '-9223372036854775808'::int8 / '0'::int2; + +select '100'::int2 + '9223372036854775800'::int8; +select '-100'::int2 - '9223372036854775800'::int8; +select '100'::int2 * '9223372036854775800'::int8; +select '100'::int2 / '0'::int8; + +SELECT CAST(q1 AS int4) FROM int8_tbl WHERE q2 = 456; +SELECT CAST(q1 AS int4) FROM int8_tbl WHERE q2 <> 456; + +SELECT CAST(q1 AS int2) FROM int8_tbl WHERE q2 = 456; +SELECT CAST(q1 AS int2) FROM int8_tbl WHERE q2 <> 456; + +SELECT CAST('42'::int2 AS int8), CAST('-37'::int2 AS int8); + +SELECT CAST(q1 AS float4), CAST(q2 AS float8) FROM INT8_TBL; +SELECT CAST('36854775807.0'::float4 AS int8); +SELECT CAST('922337203685477580700.0'::float8 AS int8); + +SELECT CAST(q1 AS oid) FROM INT8_TBL; +SELECT oid::int8 FROM pg_class WHERE relname = 'pg_class'; + + +-- bit operations + +SELECT q1, q2, q1 & q2 AS "and", q1 | q2 AS "or", q1 # q2 AS "xor", ~q1 AS "not" FROM INT8_TBL; +SELECT q1, q1 << 2 AS "shl", q1 >> 3 AS "shr" FROM INT8_TBL; + + +-- generate_series + +SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::int8); +SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::int8, 0); +SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::int8, 2); + +-- corner case +SELECT (-1::int8<<63)::text; +SELECT ((-1::int8<<63)+1)::text; + +-- check sane handling of INT64_MIN overflow cases +SELECT (-9223372036854775808)::int8 * (-1)::int8; +SELECT (-9223372036854775808)::int8 / (-1)::int8; +SELECT (-9223372036854775808)::int8 % (-1)::int8; +SELECT (-9223372036854775808)::int8 * (-1)::int4; +SELECT (-9223372036854775808)::int8 / (-1)::int4; +SELECT (-9223372036854775808)::int8 % (-1)::int4; +SELECT (-9223372036854775808)::int8 * (-1)::int2; +SELECT (-9223372036854775808)::int8 / (-1)::int2; +SELECT (-9223372036854775808)::int8 % (-1)::int2; + +-- check rounding when casting from float +SELECT x, x::int8 AS int8_value +FROM (VALUES (-2.5::float8), + (-1.5::float8), + (-0.5::float8), + (0.0::float8), + (0.5::float8), + (1.5::float8), + (2.5::float8)) t(x); + +-- check rounding when casting from numeric +SELECT x, x::int8 AS int8_value +FROM (VALUES (-2.5::numeric), + (-1.5::numeric), + (-0.5::numeric), + (0.0::numeric), + (0.5::numeric), + (1.5::numeric), + (2.5::numeric)) t(x); + +-- test gcd() +SELECT a, b, gcd(a, b), gcd(a, -b), gcd(b, a), gcd(-b, a) +FROM (VALUES (0::int8, 0::int8), + (0::int8, 29893644334::int8), + (288484263558::int8, 29893644334::int8), + (-288484263558::int8, 29893644334::int8), + ((-9223372036854775808)::int8, 1::int8), + ((-9223372036854775808)::int8, 9223372036854775807::int8), + ((-9223372036854775808)::int8, 4611686018427387904::int8)) AS v(a, b); + +SELECT gcd((-9223372036854775808)::int8, 0::int8); -- overflow +SELECT gcd((-9223372036854775808)::int8, (-9223372036854775808)::int8); -- overflow + +-- test lcm() +SELECT a, b, lcm(a, b), lcm(a, -b), lcm(b, a), lcm(-b, a) +FROM (VALUES (0::int8, 0::int8), + (0::int8, 29893644334::int8), + (29893644334::int8, 29893644334::int8), + (288484263558::int8, 29893644334::int8), + (-288484263558::int8, 29893644334::int8), + ((-9223372036854775808)::int8, 0::int8)) AS v(a, b); + +SELECT lcm((-9223372036854775808)::int8, 1::int8); -- overflow +SELECT lcm(9223372036854775807::int8, 9223372036854775806::int8); -- overflow diff --git a/src/test/regress/sql/yb.port.join.sql b/src/test/regress/sql/yb.port.join.sql new file mode 100644 index 0000000000000..10e751090fca0 --- /dev/null +++ b/src/test/regress/sql/yb.port.join.sql @@ -0,0 +1,374 @@ +-- +-- JOIN +-- Test JOIN clauses +-- + +CREATE TABLE J1_TBL ( + i integer, + j integer, + t text +); + +CREATE TABLE J2_TBL ( + i integer, + k integer +); + + +INSERT INTO J1_TBL VALUES (1, 4, 'one'); +INSERT INTO J1_TBL VALUES (2, 3, 'two'); +INSERT INTO J1_TBL VALUES (3, 2, 'three'); +INSERT INTO J1_TBL VALUES (4, 1, 'four'); +INSERT INTO J1_TBL VALUES (5, 0, 'five'); +INSERT INTO J1_TBL VALUES (6, 6, 'six'); +INSERT INTO J1_TBL VALUES (7, 7, 'seven'); +INSERT INTO J1_TBL VALUES (8, 8, 'eight'); +INSERT INTO J1_TBL VALUES (0, NULL, 'zero'); +INSERT INTO J1_TBL VALUES (NULL, NULL, 'null'); +INSERT INTO J1_TBL VALUES (NULL, 0, 'zero'); + +INSERT INTO J2_TBL VALUES (1, -1); +INSERT INTO J2_TBL VALUES (2, 2); +INSERT INTO J2_TBL VALUES (3, -3); +INSERT INTO J2_TBL VALUES (2, 4); +INSERT INTO J2_TBL VALUES (5, -5); +INSERT INTO J2_TBL VALUES (5, -5); +INSERT INTO J2_TBL VALUES (0, NULL); +INSERT INTO J2_TBL VALUES (NULL, NULL); +INSERT INTO J2_TBL VALUES (NULL, 0); + +-- +-- CORRELATION NAMES +-- Make sure that table/column aliases are supported +-- before diving into more complex join syntax. +-- + +SELECT '' AS "xxx", * + FROM J1_TBL AS tx order by 1, 2, 3, 4; + +SELECT '' AS "xxx", * + FROM J1_TBL tx order by 1, 2, 3, 4; + +SELECT '' AS "xxx", * + FROM J1_TBL AS t1 (a, b, c) order by 1, 2, 3, 4; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) order by 1, 2, 3, 4; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) order by 1, 2, 3, 4, 5, 6; + +SELECT '' AS "xxx", t1.a, t2.e + FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) + WHERE t1.a = t2.d order by 1, 2, 3; + + +-- +-- CROSS JOIN +-- Qualifications are not allowed on cross joins, +-- which degenerate into a standard unqualified inner join. +-- + +SELECT '' AS "xxx", * + FROM J1_TBL CROSS JOIN J2_TBL order by 1, 2, 3, 4, 5, 6; + +-- ambiguous column +SELECT '' AS "xxx", i, k, t + FROM J1_TBL CROSS JOIN J2_TBL; + +-- resolve previous ambiguity by specifying the table name +SELECT '' AS "xxx", t1.i, k, t + FROM J1_TBL t1 CROSS JOIN J2_TBL t2 order by 1, 2, 3, 4; + +SELECT '' AS "xxx", ii, tt, kk + FROM (J1_TBL CROSS JOIN J2_TBL) + AS tx (ii, jj, tt, ii2, kk) order by 1, 2, 3, 4; + +SELECT '' AS "xxx", tx.ii, tx.jj, tx.kk + FROM (J1_TBL t1 (a, b, c) CROSS JOIN J2_TBL t2 (d, e)) + AS tx (ii, jj, tt, ii2, kk) order by 1, 2, 3, 4; + +SELECT '' AS "xxx", * + FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b order by 1, 2, 3, 4, 5, 6, 7, 8; + + +-- +-- +-- Inner joins (equi-joins) +-- +-- + +-- +-- Inner joins (equi-joins) with USING clause +-- The USING syntax changes the shape of the resulting table +-- by including a column in the USING clause only once in the result. +-- + +-- Inner equi-join on specified column +SELECT '' AS "xxx", * + FROM J1_TBL INNER JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + +-- Same as above, slightly different syntax +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, d) USING (a) + ORDER BY a, d; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, b) USING (b) + ORDER BY b, t1.a; + + +-- +-- NATURAL JOIN +-- Inner equi-join on all columns with the same name +-- + +SELECT '' AS "xxx", * + FROM J1_TBL NATURAL JOIN J2_TBL order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (a, d) order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a) order by 1, 2, 3, 4, 5; + +-- mismatch number of columns +-- currently, Postgres will fill in with underlying names +SELECT '' AS "xxx", * + FROM J1_TBL t1 (a, b) NATURAL JOIN J2_TBL t2 (a) order by 1, 2, 3, 4, 5; + + +-- +-- Inner joins (equi-joins) +-- + +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.i) order by 1, 2, 3, 4, 5, 6; + +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k) order by 1, 2, 3, 4, 5, 6; + + +-- +-- Non-equi-joins +-- + +SELECT '' AS "xxx", * + FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i <= J2_TBL.k) order by 1, 2, 3, 4, 5, 6; + + +-- +-- Outer joins +-- Note that OUTER is a noise word +-- + +SELECT '' AS "xxx", * + FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i) + ORDER BY i, k, t; + +SELECT '' AS "xxx", * + FROM J1_TBL LEFT JOIN J2_TBL USING (i) + ORDER BY i, k, t; + +SELECT '' AS "xxx", * + FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL RIGHT JOIN J2_TBL USING (i) order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i) + ORDER BY i, k, t; + +SELECT '' AS "xxx", * + FROM J1_TBL FULL JOIN J2_TBL USING (i) + ORDER BY i, k, t; + +SELECT '' AS "xxx", * + FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (k = 1) order by 1, 2, 3, 4, 5; + +SELECT '' AS "xxx", * + FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (i = 1) order by 1, 2, 3, 4, 5; + +-- +-- semijoin selectivity for <> +-- +-- explain (costs off) +-- select * from int4_tbl i4, tenk1 a +-- where exists(select * from tenk1 b +-- where a.twothousand = b.twothousand and a.fivethous <> b.fivethous) +-- and i4.f1 = a.tenthous; + + +-- +-- More complicated constructs +-- + +-- +-- Multiway full join +-- + +CREATE TABLE t1 (name TEXT, n INTEGER); +CREATE TABLE t2 (name TEXT, n INTEGER); +CREATE TABLE t3 (name TEXT, n INTEGER); + +INSERT INTO t1 VALUES ( 'bb', 11 ); +INSERT INTO t2 VALUES ( 'bb', 12 ); +INSERT INTO t2 VALUES ( 'cc', 22 ); +INSERT INTO t2 VALUES ( 'ee', 42 ); +INSERT INTO t3 VALUES ( 'bb', 13 ); +INSERT INTO t3 VALUES ( 'cc', 23 ); +INSERT INTO t3 VALUES ( 'dd', 33 ); + +SELECT * FROM t1 FULL JOIN t2 USING (name) FULL JOIN t3 USING (name) order by 1, 2, 3, 4; + +-- +-- Test interactions of join syntax and subqueries +-- + +-- Basic cases (we expect planner to pull up the subquery here) +SELECT * FROM +(SELECT * FROM t2) as s2 +INNER JOIN +(SELECT * FROM t3) s3 +USING (name) order by 1, 2, 3; + +SELECT * FROM +(SELECT * FROM t2) as s2 +LEFT JOIN +(SELECT * FROM t3) s3 +USING (name) order by 1, 2, 3; + +SELECT * FROM +(SELECT * FROM t2) as s2 +FULL JOIN +(SELECT * FROM t3) s3 +USING (name) order by 1, 2, 3; + +-- Cases with non-nullable expressions in subquery results; +-- make sure these go to null as expected +SELECT * FROM +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL INNER JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5; + +SELECT * FROM +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL LEFT JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5; + +SELECT * FROM +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL FULL JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4; + +SELECT * FROM +(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1 +NATURAL INNER JOIN +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL INNER JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5, 6, 7; + +SELECT * FROM +(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1 +NATURAL FULL JOIN +(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 +NATURAL FULL JOIN +(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3 order by 1, 2, 3, 4, 5, 6, 7; + +SELECT * FROM +(SELECT name, n as s1_n FROM t1) as s1 +NATURAL FULL JOIN + (SELECT * FROM + (SELECT name, n as s2_n FROM t2) as s2 + NATURAL FULL JOIN + (SELECT name, n as s3_n FROM t3) as s3 + ) ss2 order by 1, 2, 3, 4; + +SELECT * FROM +(SELECT name, n as s1_n FROM t1) as s1 +NATURAL FULL JOIN + (SELECT * FROM + (SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 + NATURAL FULL JOIN + (SELECT name, n as s3_n FROM t3) as s3 + ) ss2 order by 1, 2, 3, 4, 5; + + +-- Test for propagation of nullability constraints into sub-joins + +create temp table x (x1 int, x2 int); +insert into x values (1,11); +insert into x values (2,22); +insert into x values (3,null); +insert into x values (4,44); +insert into x values (5,null); + +create temp table y (y1 int, y2 int); +insert into y values (1,111); +insert into y values (2,222); +insert into y values (3,333); +insert into y values (4,null); + +select * from x order by 1, 2; +select * from y order by 1, 2; + +select * from x left join y on (x1 = y1 and x2 is not null) order by 1, 2, 3, 4; +select * from x left join y on (x1 = y1 and y2 is not null) order by 1, 2, 3, 4; + +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) order by 1, 2, 3, 4, 5, 6; +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1 and x2 is not null) order by 1, 2, 3, 4, 5, 6; +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1 and y2 is not null) order by 1, 2, 3, 4, 5, 6; +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1 and xx2 is not null) order by 1, 2, 3, 4, 5, 6; +-- these should NOT give the same answers as above +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) where (x2 is not null) order by 1; +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) where (y2 is not null) order by 1; +select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) +on (x1 = xx1) where (xx2 is not null) order by 1; + +-- +-- regression test: check for bug with propagation of implied equality +-- to outside an IN +-- +select count(*) from tenk1 a where unique1 in + (select unique1 from tenk1 b join tenk1 c using (unique1) + where b.unique2 = 42); + +-- +-- regression test: check for failure to generate a plan with multiple +-- degenerate IN clauses +-- +select count(*) from tenk1 x where + x.unique1 in (select a.f1 from int4_tbl a,float8_tbl b where a.f1=b.f1) and + x.unique1 = 0 and + x.unique1 in (select aa.f1 from int4_tbl aa,float8_tbl bb where aa.f1=bb.f1); + +-- try that with GEQO too +begin; +set geqo = on; +set geqo_threshold = 2; +select count(*) from tenk1 x where + x.unique1 in (select a.f1 from int4_tbl a,float8_tbl b where a.f1=b.f1) and + x.unique1 = 0 and + x.unique1 in (select aa.f1 from int4_tbl aa,float8_tbl bb where aa.f1=bb.f1); +rollback; + +-- +-- Test hints given on incorrect column references are useful +-- + +select t1.uunique1 from + tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, prefer "t1" suggestion +select t2.uunique1 from + tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, prefer "t2" suggestion +select uunique1 from + tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, suggest both at once diff --git a/src/test/regress/sql/yb.port.jsonb.sql b/src/test/regress/sql/yb.port.jsonb.sql new file mode 100644 index 0000000000000..ef1b4ffd14a6e --- /dev/null +++ b/src/test/regress/sql/yb.port.jsonb.sql @@ -0,0 +1,1523 @@ +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +CREATE TABLE testjsonb ( + j jsonb +); + +\set filename :abs_srcdir '/data/jsonb.data' +COPY testjsonb FROM :'filename'; + +-- Strings. +SELECT '""'::jsonb; -- OK. +SELECT $$''$$::jsonb; -- ERROR, single quotes are not allowed +SELECT '"abc"'::jsonb; -- OK +SELECT '"abc'::jsonb; -- ERROR, quotes not closed +SELECT '"abc +def"'::jsonb; -- ERROR, unescaped newline in string constant +SELECT '"\n\"\\"'::jsonb; -- OK, legal escapes +SELECT '"\v"'::jsonb; -- ERROR, not a valid JSON escape +-- see json_encoding test for input with unicode escapes + +-- Numbers. +SELECT '1'::jsonb; -- OK +SELECT '0'::jsonb; -- OK +SELECT '01'::jsonb; -- ERROR, not valid according to JSON spec +SELECT '0.1'::jsonb; -- OK +SELECT '9223372036854775808'::jsonb; -- OK, even though it's too large for int8 +SELECT '1e100'::jsonb; -- OK +SELECT '1.3e100'::jsonb; -- OK +SELECT '1f2'::jsonb; -- ERROR +SELECT '0.x1'::jsonb; -- ERROR +SELECT '1.3ex100'::jsonb; -- ERROR + +-- Arrays. +SELECT '[]'::jsonb; -- OK +SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::jsonb; -- OK +SELECT '[1,2]'::jsonb; -- OK +SELECT '[1,2,]'::jsonb; -- ERROR, trailing comma +SELECT '[1,2'::jsonb; -- ERROR, no closing bracket +SELECT '[1,[2]'::jsonb; -- ERROR, no closing bracket + +-- Objects. +SELECT '{}'::jsonb; -- OK +SELECT '{"abc"}'::jsonb; -- ERROR, no value +SELECT '{"abc":1}'::jsonb; -- OK +SELECT '{1:"abc"}'::jsonb; -- ERROR, keys must be strings +SELECT '{"abc",1}'::jsonb; -- ERROR, wrong separator +SELECT '{"abc"=1}'::jsonb; -- ERROR, totally wrong separator +SELECT '{"abc"::1}'::jsonb; -- ERROR, another wrong separator +SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::jsonb; -- OK +SELECT '{"abc":1:2}'::jsonb; -- ERROR, colon in wrong spot +SELECT '{"abc":1,3}'::jsonb; -- ERROR, no value + +-- Recursion. +SET max_stack_depth = '100kB'; +SELECT repeat('[', 10000)::jsonb; +SELECT repeat('{"a":', 10000)::jsonb; +RESET max_stack_depth; + +-- Miscellaneous stuff. +SELECT 'true'::jsonb; -- OK +SELECT 'false'::jsonb; -- OK +SELECT 'null'::jsonb; -- OK +SELECT ' true '::jsonb; -- OK, even with extra whitespace +SELECT 'true false'::jsonb; -- ERROR, too many values +SELECT 'true, false'::jsonb; -- ERROR, too many values +SELECT 'truf'::jsonb; -- ERROR, not a keyword +SELECT 'trues'::jsonb; -- ERROR, not a keyword +SELECT ''::jsonb; -- ERROR, no value +SELECT ' '::jsonb; -- ERROR, no value + +-- Multi-line JSON input to check ERROR reporting +SELECT '{ + "one": 1, + "two":"two", + "three": + true}'::jsonb; -- OK +SELECT '{ + "one": 1, + "two":,"two", -- ERROR extraneous comma before field "two" + "three": + true}'::jsonb; +SELECT '{ + "one": 1, + "two":"two", + "averyveryveryveryveryveryveryveryveryverylongfieldname":}'::jsonb; +-- ERROR missing value for last field + +-- make sure jsonb is passed through json generators without being escaped +SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']); + +-- anyarray column + +CREATE TEMP TABLE rows AS +SELECT x, 'txt' || x as y +FROM generate_series(1,3) AS x; + +analyze rows; + +select attname, to_jsonb(histogram_bounds) histogram_bounds +from pg_stats +where tablename = 'rows' and + schemaname = pg_my_temp_schema()::regnamespace::text +order by 1; + +-- to_jsonb, timestamps + +select to_jsonb(timestamp '2014-05-28 12:22:35.614298'); + +BEGIN; +SET LOCAL TIME ZONE 10.5; +select to_jsonb(timestamptz '2014-05-28 12:22:35.614298-04'); +SET LOCAL TIME ZONE -8; +select to_jsonb(timestamptz '2014-05-28 12:22:35.614298-04'); +COMMIT; + +select to_jsonb(date '2014-05-28'); + +select to_jsonb(date 'Infinity'); +select to_jsonb(date '-Infinity'); +select to_jsonb(timestamp 'Infinity'); +select to_jsonb(timestamp '-Infinity'); +select to_jsonb(timestamptz 'Infinity'); +select to_jsonb(timestamptz '-Infinity'); + +--jsonb_agg + +SELECT jsonb_agg(q) + FROM ( SELECT $$a$$ || x AS b, y AS c, + ARRAY[ROW(x.*,ARRAY[1,2,3]), + ROW(y.*,ARRAY[4,5,6])] AS z + FROM generate_series(1,2) x, + generate_series(4,5) y) q; + +SELECT jsonb_agg(q ORDER BY x, y) + FROM rows q; + +UPDATE rows SET x = NULL WHERE x = 1; + +SELECT jsonb_agg(q ORDER BY x NULLS FIRST, y) + FROM rows q; + +-- jsonb extraction functions +CREATE TEMP TABLE test_jsonb ( + json_type text, + test_json jsonb +); + +INSERT INTO test_jsonb VALUES +('scalar','"a scalar"'), +('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'), +('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}'); + +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar'; +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'object'; +SELECT test_json -> 'field2' FROM test_jsonb WHERE json_type = 'object'; + +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'scalar'; +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'object'; + +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'scalar'; +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json -> 9 FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'object'; + +SELECT test_json ->> 6 FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json ->> 7 FROM test_jsonb WHERE json_type = 'array'; + +SELECT test_json ->> 'field4' FROM test_jsonb WHERE json_type = 'object'; +SELECT test_json ->> 'field5' FROM test_jsonb WHERE json_type = 'object'; +SELECT test_json ->> 'field6' FROM test_jsonb WHERE json_type = 'object'; + +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'scalar'; +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'object'; + +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'scalar'; +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'array'; +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'object'; + +-- nulls +SELECT (test_json->'field3') IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json->>'field3') IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json->3) IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'array'; +SELECT (test_json->>3) IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'array'; + +-- corner cases +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> null::text; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> null::int; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> 1; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> 'z'; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> ''; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb -> 1; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb -> 3; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb -> 'z'; +select '{"a": "c", "b": null}'::jsonb -> 'b'; +select '"foo"'::jsonb -> 1; +select '"foo"'::jsonb -> 'z'; + +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb ->> null::text; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb ->> null::int; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb ->> 1; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb ->> 'z'; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb ->> ''; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb ->> 1; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb ->> 3; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb ->> 'z'; +select '{"a": "c", "b": null}'::jsonb ->> 'b'; +select '"foo"'::jsonb ->> 1; +select '"foo"'::jsonb ->> 'z'; + +-- equality and inequality +SELECT '{"x":"y"}'::jsonb = '{"x":"y"}'::jsonb; +SELECT '{"x":"y"}'::jsonb = '{"x":"z"}'::jsonb; + +SELECT '{"x":"y"}'::jsonb <> '{"x":"y"}'::jsonb; +SELECT '{"x":"y"}'::jsonb <> '{"x":"z"}'::jsonb; + +-- containment +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":null}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "g":null}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"g":null}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"c"}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":"q"}'); +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":null}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "g":null}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"g":null}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}'; + +SELECT '[1,2]'::jsonb @> '[1,2,2]'::jsonb; +SELECT '[1,1,2]'::jsonb @> '[1,2,2]'::jsonb; +SELECT '[[1,2]]'::jsonb @> '[[1,2,2]]'::jsonb; +SELECT '[1,2,2]'::jsonb <@ '[1,2]'::jsonb; +SELECT '[1,2,2]'::jsonb <@ '[1,1,2]'::jsonb; +SELECT '[[1,2,2]]'::jsonb <@ '[[1,2]]'::jsonb; + +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"g":null}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"c"}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b", "c":"q"}', '{"a":"b", "b":1, "c":null}'); +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b", "c":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b", "g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"c"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b", "c":"q"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +-- Raw scalar may contain another raw scalar, array may contain a raw scalar +SELECT '[5]'::jsonb @> '[5]'; +SELECT '5'::jsonb @> '5'; +SELECT '[5]'::jsonb @> '5'; +-- But a raw scalar cannot contain an array +SELECT '5'::jsonb @> '[5]'; +-- In general, one thing should always contain itself. Test array containment: +SELECT '["9", ["7", "3"], 1]'::jsonb @> '["9", ["7", "3"], 1]'::jsonb; +SELECT '["9", ["7", "3"], ["1"]]'::jsonb @> '["9", ["7", "3"], ["1"]]'::jsonb; +-- array containment string matching confusion bug +SELECT '{ "name": "Bob", "tags": [ "enim", "qui"]}'::jsonb @> '{"tags":["qu"]}'; + +-- array length +SELECT jsonb_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]'); +SELECT jsonb_array_length('[]'); +SELECT jsonb_array_length('{"f1":1,"f2":[5,6]}'); +SELECT jsonb_array_length('4'); + +-- each +SELECT jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}'); +SELECT jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; +SELECT * FROM jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q; +SELECT * FROM jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + +SELECT jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}'); +SELECT jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; +SELECT * FROM jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q; +SELECT * FROM jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + +-- exists +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'a'); +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'b'); +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'c'); +SELECT jsonb_exists('{"a":"null", "b":"qq"}', 'a'); +SELECT jsonb '{"a":null, "b":"qq"}' ? 'a'; +SELECT jsonb '{"a":null, "b":"qq"}' ? 'b'; +SELECT jsonb '{"a":null, "b":"qq"}' ? 'c'; +SELECT jsonb '{"a":"null", "b":"qq"}' ? 'a'; +-- array exists - array elements should behave as keys +SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; +-- type sensitive array exists - should return no rows (since "exists" only +-- matches strings that are either object keys or array elements) +SELECT count(*) from testjsonb WHERE j->'array' ? '5'::text; +-- However, a raw scalar is *contained* within the array +SELECT count(*) from testjsonb WHERE j->'array' @> '5'::jsonb; + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['a','b']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['b','a']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','a']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','d']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', '{}'::text[]); +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['a','b']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['b','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','d']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| '{}'::text[]; + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['a','b']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['b','a']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','a']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','d']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', '{}'::text[]); +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','b']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['b','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','d']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','a', 'b', 'b', 'b']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& '{}'::text[]; + +-- typeof +SELECT jsonb_typeof('{}') AS object; +SELECT jsonb_typeof('{"c":3,"p":"o"}') AS object; +SELECT jsonb_typeof('[]') AS array; +SELECT jsonb_typeof('["a", 1]') AS array; +SELECT jsonb_typeof('null') AS "null"; +SELECT jsonb_typeof('1') AS number; +SELECT jsonb_typeof('-1') AS number; +SELECT jsonb_typeof('1.0') AS number; +SELECT jsonb_typeof('1e2') AS number; +SELECT jsonb_typeof('-1.0') AS number; +SELECT jsonb_typeof('true') AS boolean; +SELECT jsonb_typeof('false') AS boolean; +SELECT jsonb_typeof('"hello"') AS string; +SELECT jsonb_typeof('"true"') AS string; +SELECT jsonb_typeof('"1.0"') AS string; + +-- jsonb_build_array, jsonb_build_object, jsonb_object_agg + +SELECT jsonb_build_array('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}'); +SELECT jsonb_build_array('a', NULL); -- ok +SELECT jsonb_build_array(VARIADIC NULL::text[]); -- ok +SELECT jsonb_build_array(VARIADIC '{}'::text[]); -- ok +SELECT jsonb_build_array(VARIADIC '{a,b,c}'::text[]); -- ok +SELECT jsonb_build_array(VARIADIC ARRAY['a', NULL]::text[]); -- ok +SELECT jsonb_build_array(VARIADIC '{1,2,3,4}'::text[]); -- ok +SELECT jsonb_build_array(VARIADIC '{1,2,3,4}'::int[]); -- ok +SELECT jsonb_build_array(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok + +SELECT jsonb_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}'); + +SELECT jsonb_build_object( + 'a', jsonb_build_object('b',false,'c',99), + 'd', jsonb_build_object('e',array[9,8,7]::int[], + 'f', (select row_to_json(r) from ( select relkind, oid::regclass as name from pg_class where relname = 'pg_class') r))); +SELECT jsonb_build_object('{a,b,c}'::text[]); -- error +SELECT jsonb_build_object('{a,b,c}'::text[], '{d,e,f}'::text[]); -- error, key cannot be array +SELECT jsonb_build_object('a', 'b', 'c'); -- error +SELECT jsonb_build_object(NULL, 'a'); -- error, key cannot be NULL +SELECT jsonb_build_object('a', NULL); -- ok +SELECT jsonb_build_object(VARIADIC NULL::text[]); -- ok +SELECT jsonb_build_object(VARIADIC '{}'::text[]); -- ok +SELECT jsonb_build_object(VARIADIC '{a,b,c}'::text[]); -- error +SELECT jsonb_build_object(VARIADIC ARRAY['a', NULL]::text[]); -- ok +SELECT jsonb_build_object(VARIADIC ARRAY[NULL, 'a']::text[]); -- error, key cannot be NULL +SELECT jsonb_build_object(VARIADIC '{1,2,3,4}'::text[]); -- ok +SELECT jsonb_build_object(VARIADIC '{1,2,3,4}'::int[]); -- ok +SELECT jsonb_build_object(VARIADIC '{{1,4},{2,5},{3,6}}'::int[][]); -- ok + +-- empty objects/arrays +SELECT jsonb_build_array(); + +SELECT jsonb_build_object(); + +-- make sure keys are quoted +SELECT jsonb_build_object(1,2); + +-- keys must be scalar and not null +SELECT jsonb_build_object(null,2); + +SELECT jsonb_build_object(r,2) FROM (SELECT 1 AS a, 2 AS b) r; + +SELECT jsonb_build_object(json '{"a":1,"b":2}', 3); + +SELECT jsonb_build_object('{1,2,3}'::int[], 3); + +-- handling of NULL values +SELECT jsonb_object_agg(1, NULL::jsonb); +SELECT jsonb_object_agg(NULL, '{"a":1}'); + +CREATE TEMP TABLE foo (serial_num int, name text, type text); +INSERT INTO foo VALUES (847001,'t15','GE1043'); +INSERT INTO foo VALUES (847002,'t16','GE1043'); +INSERT INTO foo VALUES (847003,'sub-alpha','GESS90'); + +SELECT jsonb_build_object('turbines',jsonb_object_agg(serial_num,jsonb_build_object('name',name,'type',type))) +FROM foo; + +SELECT jsonb_object_agg(name, type) FROM foo; + +INSERT INTO foo VALUES (999999, NULL, 'bar'); +SELECT jsonb_object_agg(name, type) FROM foo; + +-- edge case for parser +SELECT jsonb_object_agg(DISTINCT 'a', 'abc'); + +-- jsonb_object + +-- empty object, one dimension +SELECT jsonb_object('{}'); + +-- empty object, two dimensions +SELECT jsonb_object('{}', '{}'); + +-- one dimension +SELECT jsonb_object('{a,1,b,2,3,NULL,"d e f","a b c"}'); + +-- same but with two dimensions +SELECT jsonb_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}'); + +-- odd number error +SELECT jsonb_object('{a,b,c}'); + +-- one column error +SELECT jsonb_object('{{a},{b}}'); + +-- too many columns error +SELECT jsonb_object('{{a,b,c},{b,c,d}}'); + +-- too many dimensions error +SELECT jsonb_object('{{{a,b},{c,d}},{{b,c},{d,e}}}'); + +--two argument form of jsonb_object + +select jsonb_object('{a,b,c,"d e f"}','{1,2,3,"a b c"}'); + +-- too many dimensions +SELECT jsonb_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}', '{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}'); + +-- mismatched dimensions + +select jsonb_object('{a,b,c,"d e f",g}','{1,2,3,"a b c"}'); + +select jsonb_object('{a,b,c,"d e f"}','{1,2,3,"a b c",g}'); + +-- null key error + +select jsonb_object('{a,b,NULL,"d e f"}','{1,2,3,"a b c"}'); + +-- empty key is allowed + +select jsonb_object('{a,b,"","d e f"}','{1,2,3,"a b c"}'); + + + +-- extract_path, extract_path_as_text +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); + +-- extract_path nulls +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_false; +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_true; +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_false; +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_true; + +-- extract_path operators +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f4','f6']; +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','0']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','1']; + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f4','f6']; +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','0']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','1']; + +-- corner cases for same +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> '{}'; +select '[1,2,3]'::jsonb #> '{}'; +select '"foo"'::jsonb #> '{}'; +select '42'::jsonb #> '{}'; +select 'null'::jsonb #> '{}'; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a', null]; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a', '']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b','c']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b','c','d']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','z','c']; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #> array['a','1','b']; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #> array['a','z','b']; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #> array['1','b']; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #> array['z','b']; +select '[{"b": "c"}, {"b": null}]'::jsonb #> array['1','b']; +select '"foo"'::jsonb #> array['z']; +select '42'::jsonb #> array['f2']; +select '42'::jsonb #> array['0']; + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> '{}'; +select '[1,2,3]'::jsonb #>> '{}'; +select '"foo"'::jsonb #>> '{}'; +select '42'::jsonb #>> '{}'; +select 'null'::jsonb #>> '{}'; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a', null]; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a', '']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b','c']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b','c','d']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','z','c']; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #>> array['a','1','b']; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #>> array['a','z','b']; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #>> array['1','b']; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #>> array['z','b']; +select '[{"b": "c"}, {"b": null}]'::jsonb #>> array['1','b']; +select '"foo"'::jsonb #>> array['z']; +select '42'::jsonb #>> array['f2']; +select '42'::jsonb #>> array['0']; + +-- array_elements +SELECT jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]'); +SELECT * FROM jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q; +SELECT jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]'); +SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q; + +-- populate_record +CREATE TYPE jbpop AS (a text, b int, c timestamp); + +CREATE DOMAIN jsb_int_not_null AS int NOT NULL; +CREATE DOMAIN jsb_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3); +CREATE DOMAIN jsb_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3); + +create type jb_unordered_pair as (x int, y int); +create domain jb_ordered_pair as jb_unordered_pair check((value).x <= (value).y); + +CREATE TYPE jsbrec AS ( + i int, + ia _int4, + ia1 int[], + ia2 int[][], + ia3 int[][][], + ia1d jsb_int_array_1d, + ia2d jsb_int_array_2d, + t text, + ta text[], + c char(10), + ca char(10)[], + ts timestamp, + js json, + jsb jsonb, + jsa json[], + rec jbpop, + reca jbpop[] +); + +CREATE TYPE jsbrec_i_not_null AS ( + i jsb_int_not_null +); + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q; +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q; + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q; +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q; + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":[100,200,false],"x":43.2}') q; +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":[100,200,false],"x":43.2}') q; +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}') q; + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop, '{}') q; + +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"x": 43.2}') q; +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": null}') q; +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": 12345}') q; + +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": null}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": 123}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [1, "2", null, 4]}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1, 2], [3, 4]]}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], 2]}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], [2, 3]]}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": "{1,2,3}"}') q; + +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": null}') q; +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": 123}') q; +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": [1, "2", null, 4]}') q; +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": [[1, 2, 3]]}') q; + +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": null}') q; +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": 123}') q; +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": [1, "2", null, 4]}') q; +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": [1, "2", null]}') q; + +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [1, "2", null, 4]}') q; +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], [null, 4]]}') q; +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[], []]}') q; +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], [3]]}') q; +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], 3, 4]}') q; + +SELECT ia2d FROM jsonb_populate_record(NULL::jsbrec, '{"ia2d": [[1, "2"], [null, 4]]}') q; +SELECT ia2d FROM jsonb_populate_record(NULL::jsbrec, '{"ia2d": [[1, "2", 3], [null, 5, 6]]}') q; + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [1, "2", null, 4]}') q; +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [[1, 2], [null, 4]]}') q; +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[], []], [[], []], [[], []] ]}') q; +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2]], [[3, 4]] ]}') q; +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8]] ]}') q; +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8], [9, 10]] ]}') q; + +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": null}') q; +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": 123}') q; +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": [1, "2", null, 4]}') q; +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": [[1, 2, 3], {"k": "v"}]}') q; + +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": null}') q; +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaa"}') q; +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaaaaaaaaa"}') q; +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaaaaaaaaaaaa"}') q; + +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": null}') q; +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": 123}') q; +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": [1, "2", null, 4]}') q; +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": ["aaaaaaaaaaaaaaaa"]}') q; +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": [[1, 2, 3], {"k": "v"}]}') q; + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": null}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": true}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": 123.45}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": "123.45"}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": "abc"}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": [123, "123", null, {"key": "value"}]}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": {"a": "bbb", "b": null, "c": 123.45}}') q; + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": null}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": true}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": 123.45}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": "123.45"}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": "abc"}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": [123, "123", null, {"key": "value"}]}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": {"a": "bbb", "b": null, "c": 123.45}}') q; + +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": null}') q; +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": 123}') q; +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": [1, "2", null, 4]}') q; +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": ["aaa", null, [1, 2, "3", {}], { "k" : "v" }]}') q; + +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": 123}') q; +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": [1, 2]}') q; +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}') q; +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": "(abc,42,01.02.2003)"}') q; + +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": 123}') q; +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": [1, 2]}') q; +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}]}') q; +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": ["(abc,42,01.02.2003)"]}') q; +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": "{\"(abc,42,01.02.2003)\"}"}') q; + +SELECT rec FROM jsonb_populate_record( + row(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + row('x',3,'2012-12-31 15:30:56')::jbpop,NULL)::jsbrec, + '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}' +) q; + +-- anonymous record type +SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}'); +SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}'); +SELECT * FROM + jsonb_populate_record(null::record, '{"x": 776}') AS (x int, y int); + +-- composite domain +SELECT jsonb_populate_record(null::jb_ordered_pair, '{"x": 0, "y": 1}'); +SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 0}'); +SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 1, "y": 0}'); + +-- populate_recordset +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; + +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; + +-- anonymous record type +SELECT jsonb_populate_recordset(null::record, '[{"x": 0, "y": 1}]'); +SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]'); +SELECT i, jsonb_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]') +FROM (VALUES (1),(2)) v(i); +SELECT * FROM + jsonb_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int); + +-- empty array is a corner case +SELECT jsonb_populate_recordset(null::record, '[]'); +SELECT jsonb_populate_recordset(row(1,2), '[]'); +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[]') q; +SELECT * FROM + jsonb_populate_recordset(null::record, '[]') AS (x int, y int); + +-- composite domain +SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]'); +SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 0}, {"y": 3}]'); +SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 1, "y": 0}]'); + +-- negative cases where the wrong record type is supplied +select * from jsonb_populate_recordset(row(0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text); +select * from jsonb_populate_recordset(row(0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text); +select * from jsonb_populate_recordset(row(0::int,0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text); +select * from jsonb_populate_recordset(row(1000000000::int,50::int),'[{"b":"2"},{"a":"3"}]') q (a text, b text); + +-- jsonb_to_record and jsonb_to_recordset + +select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}') + as x(a int, b text, d text); + +select * from jsonb_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar","c":true}]') + as x(a int, b text, c boolean); + +select *, c is null as c_is_null +from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8, "ca": ["1 2", 3], "ia": [[1,2],[3,4]], "r": {"a": "aaa", "b": 123}}'::jsonb) + as t(a int, b jsonb, c text, x int, ca char(5)[], ia int[][], r jbpop); + +select *, c is null as c_is_null +from jsonb_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::jsonb) + as t(a int, b jsonb, c text, x int); + +select * from jsonb_to_record('{"ia": null}') as x(ia _int4); +select * from jsonb_to_record('{"ia": 123}') as x(ia _int4); +select * from jsonb_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4); +select * from jsonb_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4); +select * from jsonb_to_record('{"ia": [[1], 2]}') as x(ia _int4); +select * from jsonb_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4); + +select * from jsonb_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]); +select * from jsonb_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]); +select * from jsonb_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]); + +select * from jsonb_to_record('{"out": {"key": 1}}') as x(out json); +select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out json); +select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out json); +select * from jsonb_to_record('{"out": {"key": 1}}') as x(out jsonb); +select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out jsonb); +select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb); + +-- test type info caching in jsonb_populate_record() +CREATE TEMP TABLE jsbpoptest (js jsonb); + +INSERT INTO jsbpoptest +SELECT '{ + "jsa": [1, "2", null, 4], + "rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}, + "reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}] +}'::jsonb +FROM generate_series(1, 3); + +SELECT (jsonb_populate_record(NULL::jsbrec, js)).* FROM jsbpoptest; + +DROP TYPE jsbrec; +DROP TYPE jsbrec_i_not_null; +DROP DOMAIN jsb_int_not_null; +DROP DOMAIN jsb_int_array_1d; +DROP DOMAIN jsb_int_array_2d; +DROP DOMAIN jb_ordered_pair; +DROP TYPE jb_unordered_pair; + +-- indexing +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; +SELECT count(*) FROM testjsonb WHERE j ? 'public'; +SELECT count(*) FROM testjsonb WHERE j ? 'bar'; +SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; +SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null'; +SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public)'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.bar)'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) || exists($.disabled)'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) && exists($.disabled)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; +SELECT count(*) FROM testjsonb WHERE j @? '$'; +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; + +CREATE INDEX jidx ON testjsonb USING gin (j); +SET enable_seqscan = off; +-- YB note: yb_test_ybgin_disable_cost_factor setting is needed to really force +-- index scan even if it is detected to be not supported. +SET yb_test_ybgin_disable_cost_factor = 0.5; + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"array":["foo"]}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"array":["bar"]}'; +-- exercise GIN_SEARCH_MODE_ALL +SELECT count(*) FROM testjsonb WHERE j @> '{}'; +SELECT count(*) FROM testjsonb WHERE j ? 'public'; +SELECT count(*) FROM testjsonb WHERE j ? 'bar'; +SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; +SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.wait == null))'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.wait ? (@ == null))'; +SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "foo"'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "bar"'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.array[*] == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array ? (@[*] == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array[*] ? (@ == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public)'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.bar)'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) || exists($.disabled)'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) && exists($.disabled)'; +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$'; +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; + +-- array exists - array elements should behave as keys (for GIN index scans too) +CREATE INDEX jidx_array ON testjsonb USING gin((j->'array')); +SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; +-- type sensitive array exists - should return no rows (since "exists" only +-- matches strings that are either object keys or array elements) +SELECT count(*) from testjsonb WHERE j->'array' ? '5'::text; +-- However, a raw scalar is *contained* within the array +SELECT count(*) from testjsonb WHERE j->'array' @> '5'::jsonb; + +RESET yb_test_ybgin_disable_cost_factor; +RESET enable_seqscan; + +SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow; +SELECT key, count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow GROUP BY key ORDER BY count DESC, key; + +-- sort/hash +SELECT count(distinct j) FROM testjsonb; +SET enable_hashagg = off; +SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2; +SET enable_hashagg = on; +SET enable_sort = off; +SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2; +SELECT distinct * FROM (values (jsonb '{}' || ''::text),('{}')) v(j); +SET enable_sort = on; + +RESET enable_hashagg; +RESET enable_sort; + +DROP INDEX jidx; +DROP INDEX jidx_array; +-- btree +CREATE INDEX jidx ON testjsonb USING btree (j); +SET enable_seqscan = off; + +SELECT count(*) FROM testjsonb WHERE j > '{"p":1}'; +SELECT count(*) FROM testjsonb WHERE j = '{"pos":98, "line":371, "node":"CBA", "indexed":true}'; + +--gin path opclass +DROP INDEX jidx; +CREATE INDEX jidx ON testjsonb USING gin (j jsonb_path_ops); +SET enable_seqscan = off; +-- YB note: yb_test_ybgin_disable_cost_factor setting is needed to really force +-- index scan even if it is detected to be not supported. +SET yb_test_ybgin_disable_cost_factor = 0.5; + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; +-- exercise GIN_SEARCH_MODE_ALL +SELECT count(*) FROM testjsonb WHERE j @> '{}'; + +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.wait == null))'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.wait ? (@ == null))'; +SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "foo"'; +SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "bar"'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.array[*] == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array ? (@[*] == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array[*] ? (@ == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$'; +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; + +RESET yb_test_ybgin_disable_cost_factor; +RESET enable_seqscan; +DROP INDEX jidx; + +-- nested tests +SELECT '{"ff":{"a":12,"b":16}}'::jsonb; +SELECT '{"ff":{"a":12,"b":16},"qq":123}'::jsonb; +SELECT '{"aa":["a","aaa"],"qq":{"a":12,"b":16,"c":["c1","c2"],"d":{"d1":"d1","d2":"d2","d1":"d3"}}}'::jsonb; +SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2"],"d":{"d1":"d1","d2":"d2"}}}'::jsonb; +SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2",["c3"],{"c4":4}],"d":{"d1":"d1","d2":"d2"}}}'::jsonb; +SELECT '{"ff":["a","aaa"]}'::jsonb; + +SELECT + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'ff', + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'qq', + ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'Y') IS NULL AS f, + ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb ->> 'Y') IS NULL AS t, + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'x'; + +-- nested containment +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":[2,1],"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; +SELECT '["a","b"]'::jsonb @> '["a","b","c","b"]'; +SELECT '["a","b","c","b"]'::jsonb @> '["a","b"]'; +SELECT '["a","b","c",[1,2]]'::jsonb @> '["a",[1,2]]'; +SELECT '["a","b","c",[1,2]]'::jsonb @> '["b",[1,2]]'; + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1]}'; +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[2]}'; +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[3]}'; + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"c":3}]}'; +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4}]}'; +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},3]}'; +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},1]}'; + +-- check some corner cases for indexed nested containment (bug #13756) +create temp table nestjsonb (j jsonb); +insert into nestjsonb (j) values ('{"a":[["b",{"x":1}],["b",{"x":2}]],"c":3}'); +insert into nestjsonb (j) values ('[[14,2,3]]'); +insert into nestjsonb (j) values ('[1,[14,2,3]]'); +create index on nestjsonb using gin(j jsonb_path_ops); + +set enable_seqscan = on; +set enable_bitmapscan = off; +select * from nestjsonb where j @> '{"a":[[{"x":2}]]}'::jsonb; +select * from nestjsonb where j @> '{"c":3}'; +select * from nestjsonb where j @> '[[14]]'; +set enable_seqscan = off; +set enable_bitmapscan = on; +select * from nestjsonb where j @> '{"a":[[{"x":2}]]}'::jsonb; +select * from nestjsonb where j @> '{"c":3}'; +select * from nestjsonb where j @> '[[14]]'; +reset enable_seqscan; +reset enable_bitmapscan; + +-- nested object field / array index lookup +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'n'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'a'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'b'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'c'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd' -> '1'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'e'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 0; --expecting error + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 0; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 1; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 2; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 3; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 3 -> 1; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 4; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 5; +SELECT '["a","b","c",[1,2],null]'::jsonb -> -1; +SELECT '["a","b","c",[1,2],null]'::jsonb -> -5; +SELECT '["a","b","c",[1,2],null]'::jsonb -> -6; + +--nested path extraction +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{0}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{a}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,0}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,1}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,2}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,3}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-1}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-3}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-4}'; + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{0}'; +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{3}'; +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4}'; +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4,5}'; + +--nested exists +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'n'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'a'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'b'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'c'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'd'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'e'; + +-- jsonb_strip_nulls + +select jsonb_strip_nulls(null); + +select jsonb_strip_nulls('1'); + +select jsonb_strip_nulls('"a string"'); + +select jsonb_strip_nulls('null'); + +select jsonb_strip_nulls('[1,2,null,3,4]'); + +select jsonb_strip_nulls('{"a":1,"b":null,"c":[2,null,3],"d":{"e":4,"f":null}}'); + +select jsonb_strip_nulls('[1,{"a":1,"b":null,"c":2},3]'); + +-- an empty object is not null and should not be stripped +select jsonb_strip_nulls('{"a": {"b": null, "c": null}, "d": {} }'); + + +select jsonb_pretty('{"a": "test", "b": [1, 2, 3], "c": "test3", "d":{"dd": "test4", "dd2":{"ddd": "test5"}}}'); +select jsonb_pretty('[{"f1":1,"f2":null},2,null,[[{"x":true},6,7],8],3]'); +select jsonb_pretty('{"a":["b", "c"], "d": {"e":"f"}}'); + +select jsonb_concat('{"d": "test", "a": [1, 2]}', '{"g": "test2", "c": {"c1":1, "c2":2}}'); + +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"cq":"l", "b":"g", "fg":false}'; +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"aq":"l"}'; +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{"aa":"l"}'; +select '{"aa":1 , "b":2, "cq":3}'::jsonb || '{}'; + +select '["a", "b"]'::jsonb || '["c"]'; +select '["a", "b"]'::jsonb || '["c", "d"]'; +select '["c"]' || '["a", "b"]'::jsonb; + +select '["a", "b"]'::jsonb || '"c"'; +select '"c"' || '["a", "b"]'::jsonb; + +select '[]'::jsonb || '["a"]'::jsonb; +select '[]'::jsonb || '"a"'::jsonb; +select '"b"'::jsonb || '"a"'::jsonb; +select '{}'::jsonb || '{"a":"b"}'::jsonb; +select '[]'::jsonb || '{"a":"b"}'::jsonb; +select '{"a":"b"}'::jsonb || '[]'::jsonb; + +select '"a"'::jsonb || '{"a":1}'; +select '{"a":1}' || '"a"'::jsonb; + +select '[3]'::jsonb || '{}'::jsonb; +select '3'::jsonb || '[]'::jsonb; +select '3'::jsonb || '4'::jsonb; +select '3'::jsonb || '{}'::jsonb; + +select '["a", "b"]'::jsonb || '{"c":1}'; +select '{"c": 1}'::jsonb || '["a", "b"]'; + +select '{}'::jsonb || '{"cq":"l", "b":"g", "fg":false}'; + +select pg_column_size('{}'::jsonb || '{}'::jsonb) = pg_column_size('{}'::jsonb); +select pg_column_size('{"aa":1}'::jsonb || '{"b":2}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); +select pg_column_size('{"aa":1, "b":2}'::jsonb || '{}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); +select pg_column_size('{}'::jsonb || '{"aa":1, "b":2}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); + +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'a'); +select jsonb_delete('{"a":null , "b":2, "c":3}'::jsonb, 'a'); +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'b'); +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'c'); +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'd'); +select '{"a":1 , "b":2, "c":3}'::jsonb - 'a'; +select '{"a":null , "b":2, "c":3}'::jsonb - 'a'; +select '{"a":1 , "b":2, "c":3}'::jsonb - 'b'; +select '{"a":1 , "b":2, "c":3}'::jsonb - 'c'; +select '{"a":1 , "b":2, "c":3}'::jsonb - 'd'; +select pg_column_size('{"a":1 , "b":2, "c":3}'::jsonb - 'b') = pg_column_size('{"a":1, "b":2}'::jsonb); + +select '["a","b","c"]'::jsonb - 3; +select '["a","b","c"]'::jsonb - 2; +select '["a","b","c"]'::jsonb - 1; +select '["a","b","c"]'::jsonb - 0; +select '["a","b","c"]'::jsonb - -1; +select '["a","b","c"]'::jsonb - -2; +select '["a","b","c"]'::jsonb - -3; +select '["a","b","c"]'::jsonb - -4; + +select '{"a":1 , "b":2, "c":3}'::jsonb - '{b}'::text[]; +select '{"a":1 , "b":2, "c":3}'::jsonb - '{c,b}'::text[]; +select '{"a":1 , "b":2, "c":3}'::jsonb - '{}'::text[]; + +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{n}', '[1,2,3]'); +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '[1,2,3]'); +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,1,0}', '[1,2,3]'); +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,NULL,0}', '[1,2,3]'); + +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{n}', '{"1": 2}'); +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '{"1": 2}'); +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,1,0}', '{"1": 2}'); +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,NULL,0}', '{"1": 2}'); + +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '"test"'); +select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '{"f": "test"}'); + +select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{n}'); +select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{b,-1}'); +select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{d,1,0}'); + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{n}'; +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{b,-1}'; +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{b,-1e}'; -- invalid array subscript +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{d,1,0}'; + + +-- empty structure and error conditions for delete and replace + +select '"a"'::jsonb - 'a'; -- error +select '{}'::jsonb - 'a'; +select '[]'::jsonb - 'a'; +select '"a"'::jsonb - 1; -- error +select '{}'::jsonb - 1; -- error +select '[]'::jsonb - 1; +select '"a"'::jsonb #- '{a}'; -- error +select '{}'::jsonb #- '{a}'; +select '[]'::jsonb #- '{a}'; +select jsonb_set('"a"','{a}','"b"'); --error +select jsonb_set('{}','{a}','"b"', false); +select jsonb_set('[]','{1}','"b"', false); +select jsonb_set('[{"f1":1,"f2":null},2,null,3]', '{0}','[2,3,4]', false); + +-- jsonb_set adding instead of replacing + +-- prepend to array +select jsonb_set('{"a":1,"b":[0,1,2],"c":{"d":4}}','{b,-33}','{"foo":123}'); +-- append to array +select jsonb_set('{"a":1,"b":[0,1,2],"c":{"d":4}}','{b,33}','{"foo":123}'); +-- check nesting levels addition +select jsonb_set('{"a":1,"b":[4,5,[0,1,2],6,7],"c":{"d":4}}','{b,2,33}','{"foo":123}'); +-- add new key +select jsonb_set('{"a":1,"b":[0,1,2],"c":{"d":4}}','{c,e}','{"foo":123}'); +-- adding doesn't do anything if elements before last aren't present +select jsonb_set('{"a":1,"b":[0,1,2],"c":{"d":4}}','{x,-33}','{"foo":123}'); +select jsonb_set('{"a":1,"b":[0,1,2],"c":{"d":4}}','{x,y}','{"foo":123}'); +-- add to empty object +select jsonb_set('{}','{x}','{"foo":123}'); +--add to empty array +select jsonb_set('[]','{0}','{"foo":123}'); +select jsonb_set('[]','{99}','{"foo":123}'); +select jsonb_set('[]','{-99}','{"foo":123}'); +select jsonb_set('{"a": [1, 2, 3]}', '{a, non_integer}', '"new_value"'); +select jsonb_set('{"a": {"b": [1, 2, 3]}}', '{a, b, non_integer}', '"new_value"'); +select jsonb_set('{"a": {"b": [1, 2, 3]}}', '{a, b, NULL}', '"new_value"'); + +-- jsonb_set_lax + +\pset null NULL + +-- pass though non nulls to jsonb_set +select jsonb_set_lax('{"a":1,"b":2}','{b}','5') ; +select jsonb_set_lax('{"a":1,"b":2}','{d}','6', true) ; +-- using the default treatment +select jsonb_set_lax('{"a":1,"b":2}','{b}',null); +select jsonb_set_lax('{"a":1,"b":2}','{d}',null,true); +-- errors +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, true, null); +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, true, 'no_such_treatment'); +-- explicit treatments +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, null_value_treatment => 'raise_exception') as raise_exception; +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, null_value_treatment => 'return_target') as return_target; +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, null_value_treatment => 'delete_key') as delete_key; +select jsonb_set_lax('{"a":1,"b":2}', '{b}', null, null_value_treatment => 'use_json_null') as use_json_null; + +\pset null '' + +-- jsonb_insert +select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"'); +select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"', true); +select jsonb_insert('{"a": {"b": {"c": [0, 1, "test1", "test2"]}}}', '{a, b, c, 2}', '"new_value"'); +select jsonb_insert('{"a": {"b": {"c": [0, 1, "test1", "test2"]}}}', '{a, b, c, 2}', '"new_value"', true); +select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '{"b": "value"}'); +select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '["value1", "value2"]'); + +-- edge cases +select jsonb_insert('{"a": [0,1,2]}', '{a, 0}', '"new_value"'); +select jsonb_insert('{"a": [0,1,2]}', '{a, 0}', '"new_value"', true); +select jsonb_insert('{"a": [0,1,2]}', '{a, 2}', '"new_value"'); +select jsonb_insert('{"a": [0,1,2]}', '{a, 2}', '"new_value"', true); +select jsonb_insert('{"a": [0,1,2]}', '{a, -1}', '"new_value"'); +select jsonb_insert('{"a": [0,1,2]}', '{a, -1}', '"new_value"', true); +select jsonb_insert('[]', '{1}', '"new_value"'); +select jsonb_insert('[]', '{1}', '"new_value"', true); +select jsonb_insert('{"a": []}', '{a, 1}', '"new_value"'); +select jsonb_insert('{"a": []}', '{a, 1}', '"new_value"', true); +select jsonb_insert('{"a": [0,1,2]}', '{a, 10}', '"new_value"'); +select jsonb_insert('{"a": [0,1,2]}', '{a, -10}', '"new_value"'); + +-- jsonb_insert should be able to insert new value for objects, but not to replace +select jsonb_insert('{"a": {"b": "value"}}', '{a, c}', '"new_value"'); +select jsonb_insert('{"a": {"b": "value"}}', '{a, c}', '"new_value"', true); + +select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"'); +select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true); + +-- jsonb subscript +select ('123'::jsonb)['a']; +select ('123'::jsonb)[0]; +select ('123'::jsonb)[NULL]; +select ('{"a": 1}'::jsonb)['a']; +select ('{"a": 1}'::jsonb)[0]; +select ('{"a": 1}'::jsonb)['not_exist']; +select ('{"a": 1}'::jsonb)[NULL]; +select ('[1, "2", null]'::jsonb)['a']; +select ('[1, "2", null]'::jsonb)[0]; +select ('[1, "2", null]'::jsonb)['1']; +select ('[1, "2", null]'::jsonb)[1.0]; +select ('[1, "2", null]'::jsonb)[2]; +select ('[1, "2", null]'::jsonb)[3]; +select ('[1, "2", null]'::jsonb)[-2]; +select ('[1, "2", null]'::jsonb)[1]['a']; +select ('[1, "2", null]'::jsonb)[1][0]; +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b']; +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']; +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1]; +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a']; +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']; +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']; +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3']; +select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1']; +select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2]; + +-- slices are not supported +select ('{"a": 1}'::jsonb)['a':'b']; +select ('[1, "2", null]'::jsonb)[1:2]; +select ('[1, "2", null]'::jsonb)[:2]; +select ('[1, "2", null]'::jsonb)[1:]; +select ('[1, "2", null]'::jsonb)[:]; + +create TEMP TABLE test_jsonb_subscript ( + id int, + test_json jsonb +); + +insert into test_jsonb_subscript values +(1, '{}'), -- empty jsonb +(2, '{"key": "value"}'); -- jsonb with data + +-- update empty jsonb +update test_jsonb_subscript set test_json['a'] = '1' where id = 1; +select * from test_jsonb_subscript; + +-- update jsonb with some data +update test_jsonb_subscript set test_json['a'] = '1' where id = 2; +select * from test_jsonb_subscript; + +-- replace jsonb +update test_jsonb_subscript set test_json['a'] = '"test"'; +select * from test_jsonb_subscript; + +-- replace by object +update test_jsonb_subscript set test_json['a'] = '{"b": 1}'::jsonb; +select * from test_jsonb_subscript; + +-- replace by array +update test_jsonb_subscript set test_json['a'] = '[1, 2, 3]'::jsonb; +select * from test_jsonb_subscript; + +-- use jsonb subscription in where clause +select * from test_jsonb_subscript where test_json['key'] = '"value"'; +select * from test_jsonb_subscript where test_json['key_doesnt_exists'] = '"value"'; +select * from test_jsonb_subscript where test_json['key'] = '"wrong_value"'; + +-- NULL +update test_jsonb_subscript set test_json[NULL] = '1'; +update test_jsonb_subscript set test_json['another_key'] = NULL; +select * from test_jsonb_subscript; + +-- NULL as jsonb source +insert into test_jsonb_subscript values (3, NULL); +update test_jsonb_subscript set test_json['a'] = '1' where id = 3; +select * from test_jsonb_subscript; + +update test_jsonb_subscript set test_json = NULL where id = 3; +update test_jsonb_subscript set test_json[0] = '1'; +select * from test_jsonb_subscript; + +-- Fill the gaps logic +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '[0]'); + +update test_jsonb_subscript set test_json[5] = '1'; +select * from test_jsonb_subscript; + +update test_jsonb_subscript set test_json[-4] = '1'; +select * from test_jsonb_subscript; + +update test_jsonb_subscript set test_json[-8] = '1'; +select * from test_jsonb_subscript; + +-- keep consistent values position +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '[]'); + +update test_jsonb_subscript set test_json[5] = '1'; +select * from test_jsonb_subscript; + +-- create the whole path +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{}'); +update test_jsonb_subscript set test_json['a'][0]['b'][0]['c'] = '1'; +select * from test_jsonb_subscript; + +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{}'); +update test_jsonb_subscript set test_json['a'][2]['b'][2]['c'][2] = '1'; +select * from test_jsonb_subscript; + +-- create the whole path with already existing keys +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{"b": 1}'); +update test_jsonb_subscript set test_json['a'][0] = '2'; +select * from test_jsonb_subscript; + +-- the start jsonb is an object, first subscript is treated as a key +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{}'); +update test_jsonb_subscript set test_json[0]['a'] = '1'; +select * from test_jsonb_subscript; + +-- the start jsonb is an array +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '[]'); +update test_jsonb_subscript set test_json[0]['a'] = '1'; +update test_jsonb_subscript set test_json[2]['b'] = '2'; +select * from test_jsonb_subscript; + +-- overwriting an existing path +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{}'); +update test_jsonb_subscript set test_json['a']['b'][1] = '1'; +update test_jsonb_subscript set test_json['a']['b'][10] = '1'; +select * from test_jsonb_subscript; + +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '[]'); +update test_jsonb_subscript set test_json[0][0][0] = '1'; +update test_jsonb_subscript set test_json[0][0][1] = '1'; +select * from test_jsonb_subscript; + +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{}'); +update test_jsonb_subscript set test_json['a']['b'][10] = '1'; +update test_jsonb_subscript set test_json['a'][10][10] = '1'; +select * from test_jsonb_subscript; + +-- an empty sub element + +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{"a": {}}'); +update test_jsonb_subscript set test_json['a']['b']['c'][2] = '1'; +select * from test_jsonb_subscript; + +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{"a": []}'); +update test_jsonb_subscript set test_json['a'][1]['c'][2] = '1'; +select * from test_jsonb_subscript; + +-- trying replace assuming a composite object, but it's an element or a value + +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, '{"a": 1}'); +update test_jsonb_subscript set test_json['a']['b'] = '1'; +update test_jsonb_subscript set test_json['a']['b']['c'] = '1'; +update test_jsonb_subscript set test_json['a'][0] = '1'; +update test_jsonb_subscript set test_json['a'][0]['c'] = '1'; +update test_jsonb_subscript set test_json['a'][0][0] = '1'; + +-- trying replace assuming a composite object, but it's a raw scalar + +delete from test_jsonb_subscript; +insert into test_jsonb_subscript values (1, 'null'); +update test_jsonb_subscript set test_json[0] = '1'; +update test_jsonb_subscript set test_json[0][0] = '1'; + +-- try some things with short-header and toasted subscript values + +drop table test_jsonb_subscript; +create temp table test_jsonb_subscript ( + id text, + test_json jsonb +); + +insert into test_jsonb_subscript values('foo', '{"foo": "bar"}'); +insert into test_jsonb_subscript + select s, ('{"' || s || '": "bar"}')::jsonb from repeat('xyzzy', 500) s; +select length(id), test_json[id] from test_jsonb_subscript; +update test_jsonb_subscript set test_json[id] = '"baz"'; +select length(id), test_json[id] from test_jsonb_subscript; +\x +table test_jsonb_subscript; +\x + +-- jsonb to tsvector +select to_tsvector('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb); + +-- jsonb to tsvector with config +select to_tsvector('simple', '{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'::jsonb); + +-- jsonb to tsvector with stop words +select to_tsvector('english', '{"a": "aaa in bbb ddd ccc", "b": ["the eee fff ggg"], "c": {"d": "hhh. iii"}}'::jsonb); + +-- jsonb to tsvector with numeric values +select to_tsvector('english', '{"a": "aaa in bbb ddd ccc", "b": 123, "c": 456}'::jsonb); + +-- jsonb_to_tsvector +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"all"'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"key"'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"string"'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"numeric"'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"boolean"'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '["string", "numeric"]'); + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"all"'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"key"'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"string"'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"numeric"'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '"boolean"'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '["string", "numeric"]'); + +-- to_tsvector corner cases +select to_tsvector('""'::jsonb); +select to_tsvector('{}'::jsonb); +select to_tsvector('[]'::jsonb); +select to_tsvector('null'::jsonb); + +-- jsonb_to_tsvector corner cases +select jsonb_to_tsvector('""'::jsonb, '"all"'); +select jsonb_to_tsvector('{}'::jsonb, '"all"'); +select jsonb_to_tsvector('[]'::jsonb, '"all"'); +select jsonb_to_tsvector('null'::jsonb, '"all"'); + +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '""'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '{}'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '[]'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, 'null'); +select jsonb_to_tsvector('english', '{"a": "aaa in bbb", "b": 123, "c": 456, "d": true, "f": false, "g": null}'::jsonb, '["all", null]'); + +-- ts_headline for jsonb +select ts_headline('{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::jsonb, tsquery('bbb & ddd & hhh')); +select ts_headline('english', '{"a": "aaa bbb", "b": {"c": "ccc ddd fff"}, "d": ["ggg hhh", "iii jjj"]}'::jsonb, tsquery('bbb & ddd & hhh')); +select ts_headline('{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::jsonb, tsquery('bbb & ddd & hhh'), 'StartSel = <, StopSel = >'); +select ts_headline('english', '{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1": "ccc1 ddd1"}, "d": ["ggg hhh", "iii jjj"]}'::jsonb, tsquery('bbb & ddd & hhh'), 'StartSel = <, StopSel = >'); + +-- corner cases for ts_headline with jsonb +select ts_headline('null'::jsonb, tsquery('aaa & bbb')); +select ts_headline('{}'::jsonb, tsquery('aaa & bbb')); +select ts_headline('[]'::jsonb, tsquery('aaa & bbb')); + +-- casts +select 'true'::jsonb::bool; +select '[]'::jsonb::bool; +select '1.0'::jsonb::float; +select '[1.0]'::jsonb::float; +select '12345'::jsonb::int4; +select '"hello"'::jsonb::int4; +select '12345'::jsonb::numeric; +select '{}'::jsonb::numeric; +select '12345.05'::jsonb::numeric; +select '12345.05'::jsonb::float4; +select '12345.05'::jsonb::float8; +select '12345.05'::jsonb::int2; +select '12345.05'::jsonb::int4; +select '12345.05'::jsonb::int8; +select '12345.0000000000000000000000000000000000000000000005'::jsonb::numeric; +select '12345.0000000000000000000000000000000000000000000005'::jsonb::float4; +select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8; +select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2; +select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4; +select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8; diff --git a/src/test/regress/sql/yb.port.limit.sql b/src/test/regress/sql/yb.port.limit.sql new file mode 100644 index 0000000000000..fc98bca724a6e --- /dev/null +++ b/src/test/regress/sql/yb.port.limit.sql @@ -0,0 +1,210 @@ +-- +-- LIMIT +-- Check the LIMIT/OFFSET feature of SELECT +-- + +SELECT ''::text AS two, unique1, unique2, stringu1 + FROM onek WHERE unique1 > 50 + ORDER BY unique1 LIMIT 2; +SELECT ''::text AS five, unique1, unique2, stringu1 + FROM onek WHERE unique1 > 60 + ORDER BY unique1 LIMIT 5; +SELECT ''::text AS two, unique1, unique2, stringu1 + FROM onek WHERE unique1 > 60 AND unique1 < 63 + ORDER BY unique1 LIMIT 5; +SELECT ''::text AS three, unique1, unique2, stringu1 + FROM onek WHERE unique1 > 100 + ORDER BY unique1 LIMIT 3 OFFSET 20; +SELECT ''::text AS zero, unique1, unique2, stringu1 + FROM onek WHERE unique1 < 50 + ORDER BY unique1 DESC LIMIT 8 OFFSET 99; +SELECT ''::text AS eleven, unique1, unique2, stringu1 + FROM onek WHERE unique1 < 50 + ORDER BY unique1 DESC LIMIT 20 OFFSET 39; +SELECT ''::text AS ten, unique1, unique2, stringu1 + FROM onek + ORDER BY unique1 OFFSET 990; +SELECT ''::text AS five, unique1, unique2, stringu1 + FROM onek + ORDER BY unique1 OFFSET 990 LIMIT 5; +SELECT ''::text AS five, unique1, unique2, stringu1 + FROM onek + ORDER BY unique1 LIMIT 5 OFFSET 900; + +-- Test null limit and offset. The planner would discard a simple null +-- constant, so to ensure executor is exercised, do this: +select * from int8_tbl limit (case when random() < 0.5 then null::bigint end); +select * from int8_tbl offset (case when random() < 0.5 then null::bigint end); + +-- Test assorted cases involving backwards fetch from a LIMIT plan node +begin; + +declare c1 cursor for select * from int8_tbl limit 10; +fetch all in c1; +fetch 1 in c1; +/* YB: backward fetch is not supported, so we also can't "re-fetch" rows from the cursor +fetch backward 1 in c1; +fetch backward all in c1; +fetch backward 1 in c1; +fetch all in c1; +*/ -- YB: backward fetch is not supported + +declare c2 cursor for select * from int8_tbl limit 3; +fetch all in c2; +fetch 1 in c2; +/* YB: backward fetch is not supported, so we also can't "re-fetch" rows from the cursor +fetch backward 1 in c2; +fetch backward all in c2; +fetch backward 1 in c2; +fetch all in c2; +*/ -- YB: backward fetch is not supported + +declare c3 cursor for select * from int8_tbl offset 3; +fetch all in c3; +fetch 1 in c3; +/* YB: backward fetch is not supported, so we also can't "re-fetch" rows from the cursor +fetch backward 1 in c3; +fetch backward all in c3; +fetch backward 1 in c3; +fetch all in c3; +*/ -- YB: backward fetch is not supported + +declare c4 cursor for select * from int8_tbl offset 10; +fetch all in c4; +fetch 1 in c4; +/* YB: backward fetch is not supported, so we also can't "re-fetch" rows from the cursor +fetch backward 1 in c4; +fetch backward all in c4; +fetch backward 1 in c4; +fetch all in c4; +*/ -- YB: backward fetch is not supported + +declare c5 cursor for select * from int8_tbl order by q1 fetch first 2 rows with ties; +fetch all in c5; +fetch 1 in c5; +fetch backward 1 in c5; -- YB: include a failure so that if this is supported, this test will fail. +/* YB: backward fetch is not supported, so we also can't "re-fetch" rows from the cursor +fetch backward 1 in c5; +fetch all in c5; +fetch backward all in c5; +fetch all in c5; +fetch backward all in c5; +*/ -- YB: backward fetch is not supported +rollback; + +-- Stress test for variable LIMIT in conjunction with bounded-heap sorting + +SELECT + (SELECT n + FROM (VALUES (1)) AS x, + (SELECT n FROM generate_series(1,10) AS n + ORDER BY n LIMIT 1 OFFSET s-1) AS y) AS z + FROM generate_series(1,10) AS s; + +-- +-- Test behavior of volatile and set-returning functions in conjunction +-- with ORDER BY and LIMIT. +-- + +create temp sequence testseq; + +explain (verbose, costs off) +select unique1, unique2, nextval('testseq') + from tenk1 order by unique2 limit 10; + +select unique1, unique2, nextval('testseq') + from tenk1 order by unique2 limit 10; + +select currval('testseq'); + +explain (verbose, costs off) +select unique1, unique2, nextval('testseq') + from tenk1 order by tenthous limit 10; + +select unique1, unique2, nextval('testseq') + from tenk1 order by tenthous limit 10; + +select currval('testseq'); + +explain (verbose, costs off) +select unique1, unique2, generate_series(1,10) + from tenk1 order by unique2 limit 7; + +select unique1, unique2, generate_series(1,10) + from tenk1 order by unique2 limit 7; + +explain (verbose, costs off) +select unique1, unique2, generate_series(1,10) + from tenk1 order by tenthous limit 7; + +select unique1, unique2, generate_series(1,10) + from tenk1 order by tenthous limit 7; + +-- use of random() is to keep planner from folding the expressions together +explain (verbose, costs off) +select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2; + +select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2; + +explain (verbose, costs off) +select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2 +order by s2 desc; + +select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2 +order by s2 desc; + +-- test for failure to set all aggregates' aggtranstype +explain (verbose, costs off) +select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 + from tenk1 group by thousand order by thousand limit 3; + +select sum(tenthous) as s1, sum(tenthous) + random()*0 as s2 + from tenk1 group by thousand order by thousand limit 3; + +-- +-- FETCH FIRST +-- Check the WITH TIES clause +-- + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 2 ROW WITH TIES; + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST ROWS WITH TIES; + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 1 ROW WITH TIES; + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 2 ROW ONLY; + +-- SKIP LOCKED and WITH TIES are incompatible +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 1 ROW WITH TIES FOR UPDATE SKIP LOCKED; + +-- should fail +SELECT ''::text AS two, unique1, unique2, stringu1 + FROM onek WHERE unique1 > 50 + FETCH FIRST 2 ROW WITH TIES; + +-- test ruleutils +CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand FETCH FIRST 5 ROWS WITH TIES OFFSET 10; +\d+ limit_thousand_v_1 +CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand OFFSET 10 FETCH FIRST 5 ROWS ONLY; +\d+ limit_thousand_v_2 +CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand FETCH FIRST NULL ROWS WITH TIES; -- fails +CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand FETCH FIRST (NULL+1) ROWS WITH TIES; +\d+ limit_thousand_v_3 +CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand FETCH FIRST NULL ROWS ONLY; +\d+ limit_thousand_v_4 +-- leave these views diff --git a/src/test/regress/sql/yb.port.lock.sql b/src/test/regress/sql/yb.port.lock.sql new file mode 100644 index 0000000000000..9aa05e68186df --- /dev/null +++ b/src/test/regress/sql/yb.port.lock.sql @@ -0,0 +1,210 @@ +-- +-- Test the LOCK statement +-- + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +-- Setup +CREATE SCHEMA lock_schema1; +SET search_path = lock_schema1; +CREATE TABLE lock_tbl1 (a BIGINT); +CREATE TABLE lock_tbl1a (a BIGINT); +CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1; +CREATE VIEW lock_view2(a,b) AS SELECT * FROM lock_tbl1, lock_tbl1a; +CREATE VIEW lock_view3 AS SELECT * from lock_view2; +CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1; +CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a); +CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub; +CREATE ROLE regress_rol_lock1; +ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1; +GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1; + +-- Try all valid lock options; also try omitting the optional TABLE keyword. +BEGIN TRANSACTION; +LOCK TABLE lock_tbl1 IN ACCESS SHARE MODE; +LOCK lock_tbl1 IN ROW SHARE MODE; +LOCK TABLE lock_tbl1 IN ROW EXCLUSIVE MODE; +LOCK TABLE lock_tbl1 IN SHARE UPDATE EXCLUSIVE MODE; +LOCK TABLE lock_tbl1 IN SHARE MODE; +LOCK lock_tbl1 IN SHARE ROW EXCLUSIVE MODE; +LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE; +LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE; +ROLLBACK; + +-- Try using NOWAIT along with valid options. +BEGIN TRANSACTION; +LOCK TABLE lock_tbl1 IN ACCESS SHARE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN ROW SHARE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN ROW EXCLUSIVE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN SHARE UPDATE EXCLUSIVE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT; +LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT; +ROLLBACK; + +-- Verify that we can lock views. +BEGIN TRANSACTION; +LOCK TABLE lock_view1 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) +-- lock_view1 and lock_tbl1 are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; +ROLLBACK; +BEGIN TRANSACTION; +LOCK TABLE lock_view2 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) +-- lock_view1, lock_tbl1, and lock_tbl1a are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; +ROLLBACK; +BEGIN TRANSACTION; +LOCK TABLE lock_view3 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) +-- lock_view3, lock_view2, lock_tbl1, and lock_tbl1a are locked recursively. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; +ROLLBACK; +BEGIN TRANSACTION; +LOCK TABLE lock_view4 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) +-- lock_view4, lock_tbl1, and lock_tbl1a are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; +ROLLBACK; +BEGIN TRANSACTION; +LOCK TABLE lock_view5 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) +-- lock_view5, lock_tbl1, and lock_tbl1a are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; +ROLLBACK; +BEGIN TRANSACTION; +LOCK TABLE lock_view6 IN EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) +-- lock_view6 an lock_tbl1 are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' + order by relname; +ROLLBACK; +-- Verify that we cope with infinite recursion in view definitions. +CREATE OR REPLACE VIEW lock_view2 AS SELECT * from lock_view3; +BEGIN TRANSACTION; +LOCK TABLE lock_view2 IN EXCLUSIVE MODE; +ROLLBACK; +CREATE VIEW lock_view7 AS SELECT * from lock_view2; +BEGIN TRANSACTION; +LOCK TABLE lock_view7 IN EXCLUSIVE MODE; +ROLLBACK; + +-- Verify that we can lock a table with inheritance children. +CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1); +CREATE TABLE lock_tbl3 () INHERITS (lock_tbl2); +BEGIN TRANSACTION; +LOCK TABLE lock_tbl1 * IN ACCESS EXCLUSIVE MODE; +ROLLBACK; + +-- Child tables are locked without granting explicit permission to do so as +-- long as we have permission to lock the parent. +GRANT UPDATE ON TABLE lock_tbl1 TO regress_rol_lock1; +SET ROLE regress_rol_lock1; +-- fail when child locked directly +BEGIN; +LOCK TABLE lock_tbl2; +ROLLBACK; +BEGIN; +LOCK TABLE lock_tbl1 * IN ACCESS EXCLUSIVE MODE; +ROLLBACK; +BEGIN; +LOCK TABLE ONLY lock_tbl1; +ROLLBACK; +RESET ROLE; +REVOKE UPDATE ON TABLE lock_tbl1 FROM regress_rol_lock1; + +-- Tables referred to by views are locked without explicit permission to do so +-- as long as we have permission to lock the view itself. +SET ROLE regress_rol_lock1; +-- fail without permissions on the view +BEGIN; +LOCK TABLE lock_view1; +ROLLBACK; +RESET ROLE; +GRANT UPDATE ON TABLE lock_view1 TO regress_rol_lock1; +GRANT yb_db_admin TO regress_rol_lock1; -- YB: for pg_locks access +SET ROLE regress_rol_lock1; +BEGIN; +LOCK TABLE lock_view1 IN ACCESS EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) +-- lock_view1 and lock_tbl1 (plus children lock_tbl2 and lock_tbl3) are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'AccessExclusiveLock' + order by relname; +ROLLBACK; +RESET ROLE; +REVOKE UPDATE ON TABLE lock_view1 FROM regress_rol_lock1; +REVOKE yb_db_admin FROM regress_rol_lock1; -- YB: for pg_locks access + +-- Tables referred to by security invoker views require explicit permission to +-- be locked. +CREATE VIEW lock_view8 WITH (security_invoker) AS SELECT * FROM lock_tbl1; +SET ROLE regress_rol_lock1; +-- fail without permissions on the view +BEGIN; +LOCK TABLE lock_view8; +ROLLBACK; +RESET ROLE; +GRANT UPDATE ON TABLE lock_view8 TO regress_rol_lock1; +SET ROLE regress_rol_lock1; +-- fail without permissions on the table referenced by the view +BEGIN; +LOCK TABLE lock_view8; +ROLLBACK; +RESET ROLE; +GRANT UPDATE ON TABLE lock_tbl1 TO regress_rol_lock1; +BEGIN; +LOCK TABLE lock_view8 IN ACCESS EXCLUSIVE MODE; +SELECT pg_sleep(2); -- YB: sleep 2 second to ensure locks are propagated (via the transaction heartbeat) +-- lock_view8 and lock_tbl1 (plus children lock_tbl2 and lock_tbl3) are locked. +select relname from pg_locks l, pg_class c + where l.relation = c.oid and relname like '%lock_%' and mode = 'AccessExclusiveLock' + order by relname; +ROLLBACK; +RESET ROLE; +REVOKE UPDATE ON TABLE lock_view8 FROM regress_rol_lock1; + +-- +-- Clean up +-- +DROP VIEW lock_view8; +DROP VIEW lock_view7; +DROP VIEW lock_view6; +DROP VIEW lock_view5; +DROP VIEW lock_view4; +DROP VIEW lock_view3 CASCADE; +DROP VIEW lock_view1; +DROP TABLE lock_tbl3; +DROP TABLE lock_tbl2; +DROP TABLE lock_tbl1; +DROP TABLE lock_tbl1a; +DROP SCHEMA lock_schema1 CASCADE; +DROP ROLE regress_rol_lock1; + + +-- atomic ops tests +RESET search_path; + +CREATE FUNCTION test_atomic_ops() + RETURNS bool + AS :'regresslib' + LANGUAGE C; + +SELECT test_atomic_ops(); diff --git a/src/test/regress/sql/yb.port.macaddr.sql b/src/test/regress/sql/yb.port.macaddr.sql new file mode 100644 index 0000000000000..023c438addead --- /dev/null +++ b/src/test/regress/sql/yb.port.macaddr.sql @@ -0,0 +1,44 @@ +-- +-- macaddr +-- + +CREATE TABLE macaddr_data (a int, b macaddr); + +INSERT INTO macaddr_data VALUES (1, '08:00:2b:01:02:03'); +INSERT INTO macaddr_data VALUES (2, '08-00-2b-01-02-03'); +INSERT INTO macaddr_data VALUES (3, '08002b:010203'); +INSERT INTO macaddr_data VALUES (4, '08002b-010203'); +INSERT INTO macaddr_data VALUES (5, '0800.2b01.0203'); +INSERT INTO macaddr_data VALUES (6, '0800-2b01-0203'); +INSERT INTO macaddr_data VALUES (7, '08002b010203'); +INSERT INTO macaddr_data VALUES (8, '0800:2b01:0203'); -- invalid +INSERT INTO macaddr_data VALUES (9, 'not even close'); -- invalid + +INSERT INTO macaddr_data VALUES (10, '08:00:2b:01:02:04'); +INSERT INTO macaddr_data VALUES (11, '08:00:2b:01:02:02'); +INSERT INTO macaddr_data VALUES (12, '08:00:2a:01:02:03'); +INSERT INTO macaddr_data VALUES (13, '08:00:2c:01:02:03'); +INSERT INTO macaddr_data VALUES (14, '08:00:2a:01:02:04'); + +SELECT * FROM macaddr_data ORDER BY a; + +-- TODO(neil) Create index on non-empty table is not yet supported. +-- CREATE INDEX macaddr_data_lsm ON macaddr_data USING lsm (b); +-- CREATE INDEX macaddr_data_hash ON macaddr_data USING hash (b); + +SELECT a, b, trunc(b) FROM macaddr_data ORDER BY 2, 1; + +SELECT b < '08:00:2b:01:02:04' FROM macaddr_data WHERE a = 1; -- true +SELECT b > '08:00:2b:01:02:04' FROM macaddr_data WHERE a = 1; -- false +SELECT b > '08:00:2b:01:02:03' FROM macaddr_data WHERE a = 1; -- false +SELECT b <= '08:00:2b:01:02:04' FROM macaddr_data WHERE a = 1; -- true +SELECT b >= '08:00:2b:01:02:04' FROM macaddr_data WHERE a = 1; -- false +SELECT b = '08:00:2b:01:02:03' FROM macaddr_data WHERE a = 1; -- true +SELECT b <> '08:00:2b:01:02:04' FROM macaddr_data WHERE a = 1; -- true +SELECT b <> '08:00:2b:01:02:03' FROM macaddr_data WHERE a = 1; -- false + +SELECT ~b FROM macaddr_data ORDER BY a; +SELECT b & '00:00:00:ff:ff:ff' FROM macaddr_data ORDER BY a; +SELECT b | '01:02:03:04:05:06' FROM macaddr_data ORDER BY a; + +DROP TABLE macaddr_data; diff --git a/src/test/regress/sql/yb.port.macaddr8.sql b/src/test/regress/sql/yb.port.macaddr8.sql new file mode 100644 index 0000000000000..8f0d2ecf351a3 --- /dev/null +++ b/src/test/regress/sql/yb.port.macaddr8.sql @@ -0,0 +1,90 @@ +-- +-- macaddr8 +-- + +-- test various cases of valid and invalid input +-- valid +SELECT '08:00:2b:01:02:03 '::macaddr8; +SELECT ' 08:00:2b:01:02:03 '::macaddr8; +SELECT ' 08:00:2b:01:02:03'::macaddr8; +SELECT '08:00:2b:01:02:03:04:05 '::macaddr8; +SELECT ' 08:00:2b:01:02:03:04:05 '::macaddr8; +SELECT ' 08:00:2b:01:02:03:04:05'::macaddr8; + +SELECT '123 08:00:2b:01:02:03'::macaddr8; -- invalid +SELECT '08:00:2b:01:02:03 123'::macaddr8; -- invalid +SELECT '123 08:00:2b:01:02:03:04:05'::macaddr8; -- invalid +SELECT '08:00:2b:01:02:03:04:05 123'::macaddr8; -- invalid +SELECT '08:00:2b:01:02:03:04:05:06:07'::macaddr8; -- invalid +SELECT '08-00-2b-01-02-03-04-05-06-07'::macaddr8; -- invalid +SELECT '08002b:01020304050607'::macaddr8; -- invalid +SELECT '08002b01020304050607'::macaddr8; -- invalid +SELECT '0z002b0102030405'::macaddr8; -- invalid +SELECT '08002b010203xyza'::macaddr8; -- invalid + +SELECT '08:00-2b:01:02:03:04:05'::macaddr8; -- invalid +SELECT '08:00-2b:01:02:03:04:05'::macaddr8; -- invalid +SELECT '08:00:2b:01.02:03:04:05'::macaddr8; -- invalid +SELECT '08:00:2b:01.02:03:04:05'::macaddr8; -- invalid + +-- test converting a MAC address to modified EUI-64 for inclusion +-- in an ipv6 address +SELECT macaddr8_set7bit('00:08:2b:01:02:03'::macaddr8); + +CREATE TABLE macaddr8_data (a int, b macaddr8); + +INSERT INTO macaddr8_data VALUES (1, '08:00:2b:01:02:03'); +INSERT INTO macaddr8_data VALUES (2, '08-00-2b-01-02-03'); +INSERT INTO macaddr8_data VALUES (3, '08002b:010203'); +INSERT INTO macaddr8_data VALUES (4, '08002b-010203'); +INSERT INTO macaddr8_data VALUES (5, '0800.2b01.0203'); +INSERT INTO macaddr8_data VALUES (6, '0800-2b01-0203'); +INSERT INTO macaddr8_data VALUES (7, '08002b010203'); +INSERT INTO macaddr8_data VALUES (8, '0800:2b01:0203'); +INSERT INTO macaddr8_data VALUES (9, 'not even close'); -- invalid + +INSERT INTO macaddr8_data VALUES (10, '08:00:2b:01:02:04'); +INSERT INTO macaddr8_data VALUES (11, '08:00:2b:01:02:02'); +INSERT INTO macaddr8_data VALUES (12, '08:00:2a:01:02:03'); +INSERT INTO macaddr8_data VALUES (13, '08:00:2c:01:02:03'); +INSERT INTO macaddr8_data VALUES (14, '08:00:2a:01:02:04'); + +INSERT INTO macaddr8_data VALUES (15, '08:00:2b:01:02:03:04:05'); +INSERT INTO macaddr8_data VALUES (16, '08-00-2b-01-02-03-04-05'); +INSERT INTO macaddr8_data VALUES (17, '08002b:0102030405'); +INSERT INTO macaddr8_data VALUES (18, '08002b-0102030405'); +INSERT INTO macaddr8_data VALUES (19, '0800.2b01.0203.0405'); +INSERT INTO macaddr8_data VALUES (20, '08002b01:02030405'); +INSERT INTO macaddr8_data VALUES (21, '08002b0102030405'); + +SELECT * FROM macaddr8_data ORDER BY 1; + +-- TODO(neil) INDEX non-empty table. +-- CREATE INDEX macaddr8_data_lsm ON macaddr8_data USING lsm (b); +-- CREATE INDEX macaddr8_data_hash ON macaddr8_data USING hash (b); + +SELECT a, b, trunc(b) FROM macaddr8_data ORDER BY 2, 1; + +SELECT b < '08:00:2b:01:02:04' FROM macaddr8_data WHERE a = 1; -- true +SELECT b > '08:00:2b:ff:fe:01:02:04' FROM macaddr8_data WHERE a = 1; -- false +SELECT b > '08:00:2b:ff:fe:01:02:03' FROM macaddr8_data WHERE a = 1; -- false +SELECT b::macaddr <= '08:00:2b:01:02:04' FROM macaddr8_data WHERE a = 1; -- true +SELECT b::macaddr >= '08:00:2b:01:02:04' FROM macaddr8_data WHERE a = 1; -- false +SELECT b = '08:00:2b:ff:fe:01:02:03' FROM macaddr8_data WHERE a = 1; -- true +SELECT b::macaddr <> '08:00:2b:01:02:04'::macaddr FROM macaddr8_data WHERE a = 1; -- true +SELECT b::macaddr <> '08:00:2b:01:02:03'::macaddr FROM macaddr8_data WHERE a = 1; -- false + +SELECT b < '08:00:2b:01:02:03:04:06' FROM macaddr8_data WHERE a = 15; -- true +SELECT b > '08:00:2b:01:02:03:04:06' FROM macaddr8_data WHERE a = 15; -- false +SELECT b > '08:00:2b:01:02:03:04:05' FROM macaddr8_data WHERE a = 15; -- false +SELECT b <= '08:00:2b:01:02:03:04:06' FROM macaddr8_data WHERE a = 15; -- true +SELECT b >= '08:00:2b:01:02:03:04:06' FROM macaddr8_data WHERE a = 15; -- false +SELECT b = '08:00:2b:01:02:03:04:05' FROM macaddr8_data WHERE a = 15; -- true +SELECT b <> '08:00:2b:01:02:03:04:06' FROM macaddr8_data WHERE a = 15; -- true +SELECT b <> '08:00:2b:01:02:03:04:05' FROM macaddr8_data WHERE a = 15; -- false + +SELECT ~b FROM macaddr8_data ORDER BY a; +SELECT b & '00:00:00:ff:ff:ff' FROM macaddr8_data ORDER BY a; +SELECT b | '01:02:03:04:05:06' FROM macaddr8_data ORDER BY a; + +DROP TABLE macaddr8_data; diff --git a/src/test/regress/sql/yb.port.matview.sql b/src/test/regress/sql/yb.port.matview.sql new file mode 100644 index 0000000000000..d4562180912f3 --- /dev/null +++ b/src/test/regress/sql/yb.port.matview.sql @@ -0,0 +1,298 @@ +-- create a table to use as a basis for views and materialized views in various combinations +CREATE TABLE mvtest_t (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL); +INSERT INTO mvtest_t VALUES + (1, 'x', 2), + (2, 'x', 3), + (3, 'y', 5), + (4, 'y', 7), + (5, 'z', 11); + +-- we want a view based on the table, too, since views present additional challenges +CREATE VIEW mvtest_tv AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type; +SELECT * FROM mvtest_tv ORDER BY type; + +-- create a materialized view with no data, and confirm correct behavior +EXPLAIN (costs off) + CREATE MATERIALIZED VIEW mvtest_tm AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type WITH NO DATA; +CREATE MATERIALIZED VIEW mvtest_tm AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type WITH NO DATA; +SELECT relispopulated FROM pg_class WHERE oid = 'mvtest_tm'::regclass; +SELECT * FROM mvtest_tm ORDER BY type; +REFRESH MATERIALIZED VIEW mvtest_tm; +SELECT relispopulated FROM pg_class WHERE oid = 'mvtest_tm'::regclass; +CREATE UNIQUE INDEX mvtest_tm_type ON mvtest_tm (type); +SELECT * FROM mvtest_tm ORDER BY type; + +-- create various views +EXPLAIN (costs off) + CREATE MATERIALIZED VIEW mvtest_tvm AS SELECT * FROM mvtest_tv ORDER BY type; +CREATE MATERIALIZED VIEW mvtest_tvm AS SELECT * FROM mvtest_tv ORDER BY type; +SELECT * FROM mvtest_tvm ORDER BY type; +CREATE MATERIALIZED VIEW mvtest_tmm AS SELECT sum(totamt) AS grandtot FROM mvtest_tm; +CREATE MATERIALIZED VIEW mvtest_tvmm AS SELECT sum(totamt) AS grandtot FROM mvtest_tvm; +CREATE UNIQUE INDEX mvtest_tvmm_expr ON mvtest_tvmm ((grandtot > 0)); +CREATE UNIQUE INDEX mvtest_tvmm_pred ON mvtest_tvmm (grandtot) WHERE grandtot < 0; +CREATE VIEW mvtest_tvv AS SELECT sum(totamt) AS grandtot FROM mvtest_tv; +EXPLAIN (costs off) + CREATE MATERIALIZED VIEW mvtest_tvvm AS SELECT * FROM mvtest_tvv; +CREATE MATERIALIZED VIEW mvtest_tvvm AS SELECT * FROM mvtest_tvv; +CREATE VIEW mvtest_tvvmv AS SELECT * FROM mvtest_tvvm; +CREATE MATERIALIZED VIEW mvtest_bb AS SELECT * FROM mvtest_tvvmv; +CREATE INDEX mvtest_aa ON mvtest_bb (grandtot); + +-- check that plans seem reasonable +\d+ mvtest_tvm +\d+ mvtest_tvm +\d+ mvtest_tvvm +\d+ mvtest_bb + +-- test schema behavior +-- CREATE SCHEMA mvtest_mvschema; +-- ALTER MATERIALIZED VIEW mvtest_tvm SET SCHEMA mvtest_mvschema; +-- \d+ mvtest_tvm +-- \d+ mvtest_tvmm +-- SET search_path = mvtest_mvschema, public; +-- \d+ mvtest_tvm + +-- modify the underlying table data +INSERT INTO mvtest_t VALUES (6, 'z', 13); + +-- confirm pre- and post-refresh contents of fairly simple materialized views +SELECT * FROM mvtest_tm ORDER BY type; +SELECT * FROM mvtest_tvm ORDER BY type; +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_tm; +REFRESH MATERIALIZED VIEW mvtest_tvm; +SELECT * FROM mvtest_tm ORDER BY type; +SELECT * FROM mvtest_tvm ORDER BY type; +RESET search_path; + +-- confirm pre- and post-refresh contents of nested materialized views +EXPLAIN (costs off) + SELECT * FROM mvtest_tmm; +EXPLAIN (costs off) + SELECT * FROM mvtest_tvmm; +EXPLAIN (costs off) + SELECT * FROM mvtest_tvvm; +SELECT * FROM mvtest_tmm; +SELECT * FROM mvtest_tvmm; +SELECT * FROM mvtest_tvvm; +REFRESH MATERIALIZED VIEW mvtest_tmm; +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_tvmm; +REFRESH MATERIALIZED VIEW mvtest_tvmm; +REFRESH MATERIALIZED VIEW mvtest_tvvm; +EXPLAIN (costs off) + SELECT * FROM mvtest_tmm; +EXPLAIN (costs off) + SELECT * FROM mvtest_tvmm; +EXPLAIN (costs off) + SELECT * FROM mvtest_tvvm; +SELECT * FROM mvtest_tmm; +SELECT * FROM mvtest_tvmm; +SELECT * FROM mvtest_tvvm; + +-- test diemv when the mv does not exist +DROP MATERIALIZED VIEW IF EXISTS no_such_mv; + +-- make sure invalid combination of options is prohibited +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_tvmm WITH NO DATA; + +-- no tuple locks on materialized views +SELECT * FROM mvtest_tvvm FOR SHARE; + +-- test join of mv and view +SELECT type, m.totamt AS mtot, v.totamt AS vtot FROM mvtest_tm m LEFT JOIN mvtest_tv v USING (type) ORDER BY type; + +-- make sure that dependencies are reported properly when they block the drop +DROP TABLE mvtest_t; + +-- make sure dependencies are dropped and reported +-- and make sure that transactional behavior is correct on rollback +-- incidentally leaving some interesting materialized views for pg_dump testing +BEGIN; +DROP TABLE mvtest_t CASCADE; +ROLLBACK; + +-- some additional tests not using base tables +CREATE VIEW mvtest_vt1 AS SELECT 1 moo; +CREATE VIEW mvtest_vt2 AS SELECT moo, 2*moo FROM mvtest_vt1 UNION ALL SELECT moo, 3*moo FROM mvtest_vt1; +\d+ mvtest_vt2 +CREATE MATERIALIZED VIEW mv_test2 AS SELECT moo, 2*moo FROM mvtest_vt2 UNION ALL SELECT moo, 3*moo FROM mvtest_vt2; +\d+ mv_test2 +CREATE MATERIALIZED VIEW mv_test3 AS SELECT * FROM mv_test2 WHERE moo = 12345; +SELECT relispopulated FROM pg_class WHERE oid = 'mv_test3'::regclass; + +DROP VIEW mvtest_vt1 CASCADE; + +-- test that duplicate values on unique index prevent refresh +CREATE TABLE mvtest_foo(a, b) AS VALUES(1, 10); +CREATE MATERIALIZED VIEW mvtest_mv AS SELECT * FROM mvtest_foo; +CREATE UNIQUE INDEX ON mvtest_mv(a); +INSERT INTO mvtest_foo SELECT * FROM mvtest_foo; +REFRESH MATERIALIZED VIEW mvtest_mv; +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv; +DROP TABLE mvtest_foo CASCADE; + +-- make sure that all columns covered by unique indexes works +CREATE TABLE mvtest_foo(a, b, c) AS VALUES(1, 2, 3); +CREATE MATERIALIZED VIEW mvtest_mv AS SELECT * FROM mvtest_foo; +CREATE UNIQUE INDEX ON mvtest_mv (a); +CREATE UNIQUE INDEX ON mvtest_mv (b); +CREATE UNIQUE INDEX on mvtest_mv (c); +INSERT INTO mvtest_foo VALUES(2, 3, 4); +INSERT INTO mvtest_foo VALUES(3, 4, 5); +REFRESH MATERIALIZED VIEW mvtest_mv; +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv; +DROP TABLE mvtest_foo CASCADE; + +-- allow subquery to reference unpopulated matview if WITH NO DATA is specified +CREATE MATERIALIZED VIEW mvtest_mv1 AS SELECT 1 AS col1 WITH NO DATA; +CREATE MATERIALIZED VIEW mvtest_mv2 AS SELECT * FROM mvtest_mv1 + WHERE col1 = (SELECT LEAST(col1) FROM mvtest_mv1) WITH NO DATA; +DROP MATERIALIZED VIEW mvtest_mv1 CASCADE; + +-- make sure that types with unusual equality tests work +CREATE TABLE mvtest_boxes (id serial primary key, b box); +INSERT INTO mvtest_boxes (b) VALUES + ('(32,32),(31,31)'), + ('(2.0000004,2.0000004),(1,1)'), + ('(1.9999996,1.9999996),(1,1)'); +CREATE MATERIALIZED VIEW mvtest_boxmv AS SELECT * FROM mvtest_boxes; +CREATE UNIQUE INDEX mvtest_boxmv_id ON mvtest_boxmv (id); +UPDATE mvtest_boxes SET b = '(2,2),(1,1)' WHERE id = 2; +-- REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_boxmv; +REFRESH MATERIALIZED VIEW mvtest_boxmv; +SELECT * FROM mvtest_boxmv ORDER BY id; +DROP TABLE mvtest_boxes CASCADE; + +-- make sure that column names are handled correctly +CREATE TABLE mvtest_v (i int, j int); +CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj, kk) AS SELECT i, j FROM mvtest_v; -- error +CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj) AS SELECT i, j FROM mvtest_v; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_2 (ii) AS SELECT i, j FROM mvtest_v; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj, kk) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- error +CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok +CREATE MATERIALIZED VIEW mvtest_mv_v_4 (ii) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok +ALTER TABLE mvtest_v RENAME COLUMN i TO x; +INSERT INTO mvtest_v values (1, 2); +CREATE UNIQUE INDEX mvtest_mv_v_ii ON mvtest_mv_v (ii); +REFRESH MATERIALIZED VIEW mvtest_mv_v; +UPDATE mvtest_v SET j = 3 WHERE x = 1; +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv_v; +REFRESH MATERIALIZED VIEW mvtest_mv_v_2; +REFRESH MATERIALIZED VIEW mvtest_mv_v_3; +REFRESH MATERIALIZED VIEW mvtest_mv_v_4; +SELECT * FROM mvtest_v; +SELECT * FROM mvtest_mv_v; +SELECT * FROM mvtest_mv_v_2; +SELECT * FROM mvtest_mv_v_3; +SELECT * FROM mvtest_mv_v_4; +DROP TABLE mvtest_v CASCADE; + +-- Check that unknown literals are converted to "text" in CREATE MATVIEW, +-- so that we don't end up with unknown-type columns. +CREATE MATERIALIZED VIEW mv_unspecified_types AS + SELECT 42 as i, 42.5 as num, 'foo' as u, 'foo'::unknown as u2, null as n; +\d+ mv_unspecified_types +SELECT * FROM mv_unspecified_types; +DROP MATERIALIZED VIEW mv_unspecified_types; + +-- make sure that create WITH NO DATA does not plan the query (bug #13907) +create materialized view mvtest_error as select 1/0 as x; -- fail +create materialized view mvtest_error as select 1/0 as x with no data; +refresh materialized view mvtest_error; -- fail here +drop materialized view mvtest_error; + +-- make sure that matview rows can be referenced as source rows (bug #9398) +CREATE TABLE mvtest_v AS SELECT generate_series(1,10) AS a; +CREATE MATERIALIZED VIEW mvtest_mv_v AS SELECT a FROM mvtest_v WHERE a <= 5; +DELETE FROM mvtest_v WHERE EXISTS ( SELECT * FROM mvtest_mv_v WHERE mvtest_mv_v.a = mvtest_v.a ); +SELECT * FROM mvtest_v ORDER BY a; +SELECT * FROM mvtest_mv_v ORDER BY a; +DROP TABLE mvtest_v CASCADE; + +-- make sure running as superuser works when MV owned by another role (bug #11208) +CREATE ROLE regress_user_mvtest; +SET ROLE regress_user_mvtest; +-- this test case also checks for ambiguity in the queries issued by +-- refresh_by_match_merge(), by choosing column names that intentionally +-- duplicate all the aliases used in those queries +CREATE TABLE mvtest_foo_data AS SELECT i, + i+1 AS tid, + md5(random()::text) AS mv, + md5(random()::text) AS newdata, + md5(random()::text) AS newdata2, + md5(random()::text) AS diff + FROM generate_series(1, 10) i; +CREATE MATERIALIZED VIEW mvtest_mv_foo AS SELECT * FROM mvtest_foo_data; +CREATE MATERIALIZED VIEW mvtest_mv_foo AS SELECT * FROM mvtest_foo_data; +CREATE MATERIALIZED VIEW IF NOT EXISTS mvtest_mv_foo AS SELECT * FROM mvtest_foo_data; +CREATE UNIQUE INDEX ON mvtest_mv_foo (i); +RESET ROLE; +REFRESH MATERIALIZED VIEW mvtest_mv_foo; +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv_foo; +DROP OWNED BY regress_user_mvtest CASCADE; +DROP ROLE regress_user_mvtest; + +-- make sure that create WITH NO DATA works via SPI +BEGIN; +CREATE FUNCTION mvtest_func() + RETURNS void AS $$ +BEGIN + CREATE MATERIALIZED VIEW mvtest1 AS SELECT 1 AS x; + CREATE MATERIALIZED VIEW mvtest2 AS SELECT 1 AS x WITH NO DATA; +END; +$$ LANGUAGE plpgsql; +SELECT mvtest_func(); +SELECT * FROM mvtest1; +SELECT * FROM mvtest2; +ROLLBACK; + +-- INSERT privileges if relation owner is not allowed to insert. +CREATE SCHEMA matview_schema; +CREATE USER regress_matview_user; +ALTER DEFAULT PRIVILEGES FOR ROLE regress_matview_user + REVOKE INSERT ON TABLES FROM regress_matview_user; +GRANT ALL ON SCHEMA matview_schema TO public; + +SET SESSION AUTHORIZATION regress_matview_user; +CREATE MATERIALIZED VIEW matview_schema.mv_withdata1 (a) AS + SELECT generate_series(1, 10) WITH DATA; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW matview_schema.mv_withdata2 (a) AS + SELECT generate_series(1, 10) WITH DATA; +REFRESH MATERIALIZED VIEW matview_schema.mv_withdata2; +CREATE MATERIALIZED VIEW matview_schema.mv_nodata1 (a) AS + SELECT generate_series(1, 10) WITH NO DATA; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW matview_schema.mv_nodata2 (a) AS + SELECT generate_series(1, 10) WITH NO DATA; +REFRESH MATERIALIZED VIEW matview_schema.mv_nodata2; +RESET SESSION AUTHORIZATION; + +ALTER DEFAULT PRIVILEGES FOR ROLE regress_matview_user + GRANT INSERT ON TABLES TO regress_matview_user; + +DROP SCHEMA matview_schema CASCADE; +DROP USER regress_matview_user; + +-- CREATE MATERIALIZED VIEW ... IF NOT EXISTS +CREATE MATERIALIZED VIEW matview_ine_tab AS SELECT 1; +CREATE MATERIALIZED VIEW matview_ine_tab AS SELECT 1 / 0; -- error +CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS + SELECT 1 / 0; -- ok +CREATE MATERIALIZED VIEW matview_ine_tab AS + SELECT 1 / 0 WITH NO DATA; -- error +CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS + SELECT 1 / 0 WITH NO DATA; -- ok +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW matview_ine_tab AS + SELECT 1 / 0; -- error +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS + SELECT 1 / 0; -- ok +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW matview_ine_tab AS + SELECT 1 / 0 WITH NO DATA; -- error +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) + CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS + SELECT 1 / 0 WITH NO DATA; -- ok +DROP MATERIALIZED VIEW matview_ine_tab; diff --git a/src/test/regress/sql/yb.port.misc.sql b/src/test/regress/sql/yb.port.misc.sql new file mode 100644 index 0000000000000..4083698a89871 --- /dev/null +++ b/src/test/regress/sql/yb.port.misc.sql @@ -0,0 +1,280 @@ +-- +-- MISC +-- + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +\getenv abs_builddir PG_ABS_BUILDDIR +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +CREATE FUNCTION overpaid(emp) + RETURNS bool + AS :'regresslib' + LANGUAGE C STRICT; + +CREATE FUNCTION reverse_name(name) + RETURNS name + AS :'regresslib' + LANGUAGE C STRICT; + +-- +-- BTREE +-- +UPDATE onek + SET unique1 = onek.unique1 + 1; + +UPDATE onek + SET unique1 = onek.unique1 - 1; + +-- +-- BTREE partial +-- +-- UPDATE onek2 +-- SET unique1 = onek2.unique1 + 1; + +--UPDATE onek2 +-- SET unique1 = onek2.unique1 - 1; + +-- +-- BTREE shutting out non-functional updates +-- +-- the following two tests seem to take a long time on some +-- systems. This non-func update stuff needs to be examined +-- more closely. - jolly (2/22/96) +-- +SELECT two, stringu1, ten, string4 + INTO TABLE tmp + FROM onek; + +UPDATE tmp + SET stringu1 = reverse_name(onek.stringu1) + FROM onek + WHERE onek.stringu1 = 'JBAAAA' and + onek.stringu1 = tmp.stringu1; + +UPDATE tmp + SET stringu1 = reverse_name(onek2.stringu1) + FROM onek2 + WHERE onek2.stringu1 = 'JCAAAA' and + onek2.stringu1 = tmp.stringu1; + +DROP TABLE tmp; + +--UPDATE person* +-- SET age = age + 1; + +--UPDATE person* +-- SET age = age + 3 +-- WHERE name = 'linda'; + +-- +-- copy +-- +\set filename :abs_builddir '/results/onek.data' +COPY onek TO :'filename'; + +CREATE TABLE onek_copy (LIKE onek); -- YB: create YB table instead of temp table to exercise YB tables + +COPY onek_copy FROM :'filename'; + +SELECT * FROM onek EXCEPT ALL SELECT * FROM onek_copy; + +SELECT * FROM onek_copy EXCEPT ALL SELECT * FROM onek; + +\set filename :abs_builddir '/results/stud_emp.data' +COPY BINARY stud_emp TO :'filename'; + +CREATE TEMP TABLE stud_emp_copy (LIKE stud_emp); + +COPY BINARY stud_emp_copy FROM :'filename'; + +SELECT * FROM stud_emp_copy; + +-- +-- test data for postquel functions +-- + +CREATE TABLE hobbies_r ( + name text, + person text +); + +CREATE TABLE equipment_r ( + name text, + hobby text +); + +INSERT INTO hobbies_r (name, person) + SELECT 'posthacking', p.name + FROM person* p + WHERE p.name = 'mike' or p.name = 'jeff'; + +INSERT INTO hobbies_r (name, person) + SELECT 'basketball', p.name + FROM person p + WHERE p.name = 'joe' or p.name = 'sally'; + +INSERT INTO hobbies_r (name) VALUES ('skywalking'); + +INSERT INTO equipment_r (name, hobby) VALUES ('advil', 'posthacking'); + +INSERT INTO equipment_r (name, hobby) VALUES ('peet''s coffee', 'posthacking'); + +INSERT INTO equipment_r (name, hobby) VALUES ('hightops', 'basketball'); + +INSERT INTO equipment_r (name, hobby) VALUES ('guts', 'skywalking'); + +-- +-- postquel functions +-- + +CREATE FUNCTION hobbies(person) + RETURNS setof hobbies_r + AS 'select * from hobbies_r where person = $1.name' + LANGUAGE SQL; + +CREATE FUNCTION hobby_construct(text, text) + RETURNS hobbies_r + AS 'select $1 as name, $2 as hobby' + LANGUAGE SQL; + +CREATE FUNCTION hobby_construct_named(name text, hobby text) + RETURNS hobbies_r + AS 'select name, hobby' + LANGUAGE SQL; + +CREATE FUNCTION hobbies_by_name(hobbies_r.name%TYPE) + RETURNS hobbies_r.person%TYPE + AS 'select person from hobbies_r where name = $1' + LANGUAGE SQL; + +CREATE FUNCTION equipment(hobbies_r) + RETURNS setof equipment_r + AS 'select * from equipment_r where hobby = $1.name' + LANGUAGE SQL; + +CREATE FUNCTION equipment_named(hobby hobbies_r) + RETURNS setof equipment_r + AS 'select * from equipment_r where equipment_r.hobby = equipment_named.hobby.name' + LANGUAGE SQL; + +CREATE FUNCTION equipment_named_ambiguous_1a(hobby hobbies_r) + RETURNS setof equipment_r + AS 'select * from equipment_r where hobby = equipment_named_ambiguous_1a.hobby.name' + LANGUAGE SQL; + +CREATE FUNCTION equipment_named_ambiguous_1b(hobby hobbies_r) + RETURNS setof equipment_r + AS 'select * from equipment_r where equipment_r.hobby = hobby.name' + LANGUAGE SQL; + +CREATE FUNCTION equipment_named_ambiguous_1c(hobby hobbies_r) + RETURNS setof equipment_r + AS 'select * from equipment_r where hobby = hobby.name' + LANGUAGE SQL; + +CREATE FUNCTION equipment_named_ambiguous_2a(hobby text) + RETURNS setof equipment_r + AS 'select * from equipment_r where hobby = equipment_named_ambiguous_2a.hobby' + LANGUAGE SQL; + +CREATE FUNCTION equipment_named_ambiguous_2b(hobby text) + RETURNS setof equipment_r + AS 'select * from equipment_r where equipment_r.hobby = hobby' + LANGUAGE SQL; + +-- +-- mike does post_hacking, +-- joe and sally play basketball, and +-- everyone else does nothing. +-- +SELECT p.name, name(p.hobbies) FROM ONLY person p; + +-- +-- as above, but jeff also does post_hacking. +-- +SELECT p.name, name(p.hobbies) FROM person* p; + +-- +-- the next two queries demonstrate how functions generate bogus duplicates. +-- this is a "feature" .. +-- +SELECT DISTINCT hobbies_r.name, name(hobbies_r.equipment) FROM hobbies_r + ORDER BY 1,2; + +SELECT hobbies_r.name, (hobbies_r.equipment).name FROM hobbies_r; + +-- +-- mike needs advil and peet's coffee, +-- joe and sally need hightops, and +-- everyone else is fine. +-- +SELECT p.name, name(p.hobbies), name(equipment(p.hobbies)) FROM ONLY person p; + +-- +-- as above, but jeff needs advil and peet's coffee as well. +-- +SELECT p.name, name(p.hobbies), name(equipment(p.hobbies)) FROM person* p; + +-- +-- just like the last two, but make sure that the target list fixup and +-- unflattening is being done correctly. +-- +SELECT name(equipment(p.hobbies)), p.name, name(p.hobbies) FROM ONLY person p; + +SELECT (p.hobbies).equipment.name, p.name, name(p.hobbies) FROM person* p; + +SELECT (p.hobbies).equipment.name, name(p.hobbies), p.name FROM ONLY person p; + +SELECT name(equipment(p.hobbies)), name(p.hobbies), p.name FROM person* p; + + +SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer'))); + +SELECT name(equipment(hobby_construct_named(text 'skywalking', text 'mer'))); + +SELECT name(equipment_named(hobby_construct_named(text 'skywalking', text 'mer'))); + +SELECT name(equipment_named_ambiguous_1a(hobby_construct_named(text 'skywalking', text 'mer'))); + +SELECT name(equipment_named_ambiguous_1b(hobby_construct_named(text 'skywalking', text 'mer'))); + +SELECT name(equipment_named_ambiguous_1c(hobby_construct_named(text 'skywalking', text 'mer'))); + +SELECT name(equipment_named_ambiguous_2a(text 'skywalking')); + +SELECT name(equipment_named_ambiguous_2b(text 'skywalking')); + +/* YB: output sometimes gives joe, sometimes sally +SELECT hobbies_by_name('basketball'); +*/ -- YB + +SELECT name, overpaid(emp.*) FROM emp; + +-- +-- Try a few cases with SQL-spec row constructor expressions +-- +SELECT * FROM equipment(ROW('skywalking', 'mer')); + +SELECT name(equipment(ROW('skywalking', 'mer'))); + +SELECT *, name(equipment(h.*)) FROM hobbies_r h; + +SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h; + + +-- +-- functional joins +-- + +-- +-- instance rules +-- + +-- +-- rewrite rules +-- +DROP TABLE onek_copy; -- YB: mimic natural discard of temp tables for tables that have been converted from temp tables in the upstream PG to YB tables here diff --git a/src/test/regress/sql/yb.port.misc_functions.sql b/src/test/regress/sql/yb.port.misc_functions.sql new file mode 100644 index 0000000000000..e43dd31c588ee --- /dev/null +++ b/src/test/regress/sql/yb.port.misc_functions.sql @@ -0,0 +1,199 @@ +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +-- +-- num_nulls() +-- + +SELECT num_nonnulls(NULL); +SELECT num_nonnulls('1'); +SELECT num_nonnulls(NULL::text); +SELECT num_nonnulls(NULL::text, NULL::int); +SELECT num_nonnulls(1, 2, NULL::text, NULL::point, '', int8 '9', 1.0 / NULL); +SELECT num_nonnulls(VARIADIC '{1,2,NULL,3}'::int[]); +SELECT num_nonnulls(VARIADIC '{"1","2","3","4"}'::text[]); +SELECT num_nonnulls(VARIADIC ARRAY(SELECT CASE WHEN i <> 40 THEN i END FROM generate_series(1, 100) i)); + +SELECT num_nulls(NULL); +SELECT num_nulls('1'); +SELECT num_nulls(NULL::text); +SELECT num_nulls(NULL::text, NULL::int); +SELECT num_nulls(1, 2, NULL::text, NULL::point, '', int8 '9', 1.0 / NULL); +SELECT num_nulls(VARIADIC '{1,2,NULL,3}'::int[]); +SELECT num_nulls(VARIADIC '{"1","2","3","4"}'::text[]); +SELECT num_nulls(VARIADIC ARRAY(SELECT CASE WHEN i <> 40 THEN i END FROM generate_series(1, 100) i)); + +-- special cases +SELECT num_nonnulls(VARIADIC NULL::text[]); +SELECT num_nonnulls(VARIADIC '{}'::int[]); +SELECT num_nulls(VARIADIC NULL::text[]); +SELECT num_nulls(VARIADIC '{}'::int[]); + +-- should fail, one or more arguments is required +SELECT num_nonnulls(); +SELECT num_nulls(); + +-- +-- canonicalize_path() +-- + +CREATE FUNCTION test_canonicalize_path(text) + RETURNS text + AS :'regresslib' + LANGUAGE C STRICT IMMUTABLE; + +SELECT test_canonicalize_path('/'); +SELECT test_canonicalize_path('/./abc/def/'); +SELECT test_canonicalize_path('/./../abc/def'); +SELECT test_canonicalize_path('/./../../abc/def/'); +SELECT test_canonicalize_path('/abc/.././def/ghi'); +SELECT test_canonicalize_path('/abc/./../def/ghi//'); +SELECT test_canonicalize_path('/abc/def/../..'); +SELECT test_canonicalize_path('/abc/def/../../..'); +SELECT test_canonicalize_path('/abc/def/../../../../ghi/jkl'); +SELECT test_canonicalize_path('.'); +SELECT test_canonicalize_path('./'); +SELECT test_canonicalize_path('./abc/..'); +SELECT test_canonicalize_path('abc/../'); +SELECT test_canonicalize_path('abc/../def'); +SELECT test_canonicalize_path('..'); +SELECT test_canonicalize_path('../abc/def'); +SELECT test_canonicalize_path('../abc/..'); +SELECT test_canonicalize_path('../abc/../def'); +SELECT test_canonicalize_path('../abc/../../def/ghi'); +SELECT test_canonicalize_path('./abc/./def/.'); +SELECT test_canonicalize_path('./abc/././def/.'); +SELECT test_canonicalize_path('./abc/./def/.././ghi/../../../jkl/mno'); + +-- +-- pg_log_backend_memory_contexts() +-- +-- Memory contexts are logged and they are not returned to the function. +-- Furthermore, their contents can vary depending on the timing. However, +-- we can at least verify that the code doesn't fail, and that the +-- permissions are set properly. +-- + +SELECT pg_log_backend_memory_contexts(pg_backend_pid()); + +SELECT pg_log_backend_memory_contexts(pid) FROM pg_stat_activity + WHERE backend_type = 'checkpointer'; + +CREATE ROLE regress_log_memory; + +SELECT has_function_privilege('regress_log_memory', + 'pg_log_backend_memory_contexts(integer)', 'EXECUTE'); -- no + +GRANT EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) + TO regress_log_memory; + +SELECT has_function_privilege('regress_log_memory', + 'pg_log_backend_memory_contexts(integer)', 'EXECUTE'); -- yes + +SET ROLE regress_log_memory; +SELECT pg_log_backend_memory_contexts(pg_backend_pid()); +RESET ROLE; + +REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) + FROM regress_log_memory; + +DROP ROLE regress_log_memory; + +-- +-- Test some built-in SRFs +-- +-- The outputs of these are variable, so we can't just print their results +-- directly, but we can at least verify that the code doesn't fail. +-- +select setting as segsize +from pg_settings where name = 'wal_segment_size' +\gset + +select count(*) > 0 as ok from pg_ls_waldir(); +-- Test ProjectSet as well as FunctionScan +select count(*) > 0 as ok from (select pg_ls_waldir()) ss; +-- Test not-run-to-completion cases. +select * from pg_ls_waldir() limit 0; +select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss; +select (w).size = :segsize as ok +from (select pg_ls_waldir() w) ss where length((w).name) = 24 limit 1; + +select count(*) >= 0 as ok from pg_ls_archive_statusdir(); + +select * from (select pg_ls_dir('.') a) a where a = 'base' limit 1; +-- Test missing_ok (second argument) +select pg_ls_dir('does not exist', false, false); -- error +select pg_ls_dir('does not exist', true, false); -- ok +-- Test include_dot_dirs (third argument) +select count(*) = 1 as dot_found + from pg_ls_dir('.', false, true) as ls where ls = '.'; +select count(*) = 1 as dot_found + from pg_ls_dir('.', false, false) as ls where ls = '.'; + +select * from (select (pg_timezone_names()).name) ptn where name='UTC' limit 1; + +select count(*) > 0 from + (select pg_tablespace_databases(oid) as pts from pg_tablespace + where spcname = 'pg_default') pts + join pg_database db on pts.pts = db.oid; + +-- +-- Test replication slot directory functions +-- +CREATE ROLE regress_slot_dir_funcs; +-- Not available by default. +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_logicalsnapdir()', 'EXECUTE'); +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_logicalmapdir()', 'EXECUTE'); +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_replslotdir(text)', 'EXECUTE'); +GRANT pg_monitor TO regress_slot_dir_funcs; +-- Role is now part of pg_monitor, so these are available. +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_logicalsnapdir()', 'EXECUTE'); +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_logicalmapdir()', 'EXECUTE'); +SELECT has_function_privilege('regress_slot_dir_funcs', + 'pg_ls_replslotdir(text)', 'EXECUTE'); +DROP ROLE regress_slot_dir_funcs; + +-- +-- Test adding a support function to a subject function +-- + +CREATE FUNCTION my_int_eq(int, int) RETURNS bool + LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE + AS $$int4eq$$; + +-- By default, planner does not think that's selective +EXPLAIN (COSTS OFF) +SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1 +WHERE my_int_eq(a.unique2, 42); -- YB: output's a and b are flipped + +-- With support function that knows it's int4eq, we get a different plan +CREATE FUNCTION test_support_func(internal) + RETURNS internal + AS :'regresslib', 'test_support_func' + LANGUAGE C STRICT; + +ALTER FUNCTION my_int_eq(int, int) SUPPORT test_support_func; + +EXPLAIN (COSTS OFF) +SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1 +WHERE my_int_eq(a.unique2, 42); -- YB: TODO: investigate why NestLoop is still higher cost + +-- Also test non-default rowcount estimate +CREATE FUNCTION my_gen_series(int, int) RETURNS SETOF integer + LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE + AS $$generate_series_int4$$ + SUPPORT test_support_func; + +EXPLAIN (COSTS OFF) +SELECT * FROM tenk1 a JOIN my_gen_series(1,1000) g ON a.unique1 = g; + +EXPLAIN (COSTS OFF) +SELECT * FROM tenk1 a JOIN my_gen_series(1,10) g ON a.unique1 = g; diff --git a/src/test/regress/sql/yb.port.multirangetypes.sql b/src/test/regress/sql/yb.port.multirangetypes.sql new file mode 100644 index 0000000000000..097faf12d45b7 --- /dev/null +++ b/src/test/regress/sql/yb.port.multirangetypes.sql @@ -0,0 +1,853 @@ +-- Tests for multirange data types. + +-- +-- test input parser +-- + +-- negative tests; should fail +select ''::textmultirange; +select '{,}'::textmultirange; +select '{(,)}.'::textmultirange; +select '{[a,c),}'::textmultirange; +select '{,[a,c)}'::textmultirange; +select '{-[a,z)}'::textmultirange; +select '{[a,z) - }'::textmultirange; +select '{(",a)}'::textmultirange; +select '{(,,a)}'::textmultirange; +select '{(),a)}'::textmultirange; +select '{(a,))}'::textmultirange; +select '{(],a)}'::textmultirange; +select '{(a,])}'::textmultirange; +select '{[z,a]}'::textmultirange; + +-- should succeed +select '{}'::textmultirange; +select ' {} '::textmultirange; +select ' { empty, empty } '::textmultirange; +select ' {( " a " " a ", " z " " z " ) }'::textmultirange; +select textrange('\\\\', repeat('a', 200))::textmultirange; +select '{(,z)}'::textmultirange; +select '{(a,)}'::textmultirange; +select '{[,z]}'::textmultirange; +select '{[a,]}'::textmultirange; +select '{(,)}'::textmultirange; +select '{[ , ]}'::textmultirange; +select '{["",""]}'::textmultirange; +select '{[",",","]}'::textmultirange; +select '{["\\","\\"]}'::textmultirange; +select '{["""","\""]}'::textmultirange; +select '{(\\,a)}'::textmultirange; +select '{((,z)}'::textmultirange; +select '{([,z)}'::textmultirange; +select '{(!,()}'::textmultirange; +select '{(!,[)}'::textmultirange; +select '{[a,a]}'::textmultirange; +select '{[a,a],[a,b]}'::textmultirange; +select '{[a,b), [b,e]}'::textmultirange; +select '{[a,d), [b,f]}'::textmultirange; +select '{[a,a],[b,b]}'::textmultirange; +-- without canonicalization, we can't join these: +select '{[a,a], [b,b]}'::textmultirange; +-- with canonicalization, we can join these: +select '{[1,2], [3,4]}'::int4multirange; +select '{[a,a], [b,b], [c,c]}'::textmultirange; +select '{[a,d], [b,e]}'::textmultirange; +select '{[a,d), [d,e)}'::textmultirange; +-- these are allowed but normalize to empty: +select '{[a,a)}'::textmultirange; +select '{(a,a]}'::textmultirange; +select '{(a,a)}'::textmultirange; + +-- +-- test the constructor +--- +select textmultirange(); +select textmultirange(textrange('a', 'c')); +select textmultirange(textrange('a', 'c'), textrange('f', 'g')); +select textmultirange(textrange('\\\\', repeat('a', 200)), textrange('c', 'd')); + +-- +-- test casts, both a built-in range type and a user-defined one: +-- +select 'empty'::int4range::int4multirange; +select int4range(1, 3)::int4multirange; +select int4range(1, null)::int4multirange; +select int4range(null, null)::int4multirange; +select 'empty'::textrange::textmultirange; +select textrange('a', 'c')::textmultirange; +select textrange('a', null)::textmultirange; +select textrange(null, null)::textmultirange; + +-- +-- test unnest(multirange) function +-- +select unnest(int4multirange(int4range('5', '6'), int4range('1', '2'))); +select unnest(textmultirange(textrange('a', 'b'), textrange('d', 'e'))); +select unnest(textmultirange(textrange('\\\\', repeat('a', 200)), textrange('c', 'd'))); + +-- +-- create some test data and test the operators +-- + +CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE); +CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr); + +INSERT INTO nummultirange_test VALUES('{}'); +INSERT INTO nummultirange_test VALUES('{[,)}'); +INSERT INTO nummultirange_test VALUES('{[3,]}'); +INSERT INTO nummultirange_test VALUES('{[,), [3,]}'); +INSERT INTO nummultirange_test VALUES('{[, 5)}'); +INSERT INTO nummultirange_test VALUES(nummultirange()); +INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[])); +INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2))); +INSERT INTO nummultirange_test VALUES('{empty}'); +INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9))); +INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1))); + +SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr; +SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr; + +SELECT * FROM nummultirange_test WHERE nmr = '{}'; +SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}'; +SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}'; +SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}'; +SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}'; +SELECT * FROM nummultirange_test WHERE nmr < '{}'; +SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}'; +SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}'; +SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}'; +SELECT * FROM nummultirange_test WHERE nmr <= '{}'; +SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}'; +SELECT * FROM nummultirange_test WHERE nmr >= '{}'; +SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}'; +SELECT * FROM nummultirange_test WHERE nmr > '{}'; +SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}'; +SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}'; +SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}'; +SELECT * FROM nummultirange_test WHERE nmr <> '{}'; +SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}'; + +select nummultirange(numrange(2.0, 1.0)); +select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0)); + +analyze nummultirange_test; + +-- overlaps +SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr); +SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr; +SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2)); +SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2); +SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0))); +SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)); +SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0)); +SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0)); + +-- mr contains x +SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0); +SELECT * FROM nummultirange_test WHERE nmr @> 4.0; +SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2)); +SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2); +SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}'); +SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange; + +-- x is contained by mr +SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr); +SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr; +SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr); +SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr; +SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr); +SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr; + +-- overlaps +SELECT 'empty'::numrange && nummultirange(); +SELECT 'empty'::numrange && nummultirange(numrange(1,2)); +SELECT nummultirange() && 'empty'::numrange; +SELECT nummultirange(numrange(1,2)) && 'empty'::numrange; +SELECT nummultirange() && nummultirange(); +SELECT nummultirange() && nummultirange(numrange(1,2)); +SELECT nummultirange(numrange(1,2)) && nummultirange(); +SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8)); +SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8)); +SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4); +SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4)); +select '{(10,20),(30,40),(50,60)}'::nummultirange && '(42,92)'::numrange; + +-- contains +SELECT nummultirange() @> nummultirange(); +SELECT nummultirange() @> 'empty'::numrange; +SELECT nummultirange(numrange(null,null)) @> numrange(1,2); +SELECT nummultirange(numrange(null,null)) @> numrange(null,2); +SELECT nummultirange(numrange(null,null)) @> numrange(2,null); +SELECT nummultirange(numrange(null,5)) @> numrange(null,3); +SELECT nummultirange(numrange(null,5)) @> numrange(null,8); +SELECT nummultirange(numrange(5,null)) @> numrange(8,null); +SELECT nummultirange(numrange(5,null)) @> numrange(3,null); +SELECT nummultirange(numrange(1,5)) @> numrange(8,9); +SELECT nummultirange(numrange(1,5)) @> numrange(3,9); +SELECT nummultirange(numrange(1,5)) @> numrange(1,4); +SELECT nummultirange(numrange(1,5)) @> numrange(1,5); +SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5); +SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5); +SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7); +SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7); +SELECT '{[1,5)}'::nummultirange @> '{[1,5)}'; +SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}'; +SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}'; +SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}'; +SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}'; +select '{(10,20),(30,40),(50,60)}'::nummultirange @> '(52,56)'::numrange; +SELECT numrange(null,null) @> nummultirange(numrange(1,2)); +SELECT numrange(null,null) @> nummultirange(numrange(null,2)); +SELECT numrange(null,null) @> nummultirange(numrange(2,null)); +SELECT numrange(null,5) @> nummultirange(numrange(null,3)); +SELECT numrange(null,5) @> nummultirange(numrange(null,8)); +SELECT numrange(5,null) @> nummultirange(numrange(8,null)); +SELECT numrange(5,null) @> nummultirange(numrange(3,null)); +SELECT numrange(1,5) @> nummultirange(numrange(8,9)); +SELECT numrange(1,5) @> nummultirange(numrange(3,9)); +SELECT numrange(1,5) @> nummultirange(numrange(1,4)); +SELECT numrange(1,5) @> nummultirange(numrange(1,5)); +SELECT numrange(1,9) @> nummultirange(numrange(-4,-2), numrange(1,5)); +SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(8,9)); +SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(6,9)); +SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(6,10)); +SELECT '{[1,9)}' @> '{[1,5)}'::nummultirange; +SELECT '{[1,9)}' @> '{[-4,-2), [1,5)}'::nummultirange; +SELECT '{[1,9)}' @> '{[1,5), [8,9)}'::nummultirange; +SELECT '{[1,9)}' @> '{[1,5), [6,9)}'::nummultirange; +SELECT '{[1,9)}' @> '{[1,5), [6,10)}'::nummultirange; + +-- is contained by +SELECT nummultirange() <@ nummultirange(); +SELECT 'empty'::numrange <@ nummultirange(); +SELECT numrange(1,2) <@ nummultirange(numrange(null,null)); +SELECT numrange(null,2) <@ nummultirange(numrange(null,null)); +SELECT numrange(2,null) <@ nummultirange(numrange(null,null)); +SELECT numrange(null,3) <@ nummultirange(numrange(null,5)); +SELECT numrange(null,8) <@ nummultirange(numrange(null,5)); +SELECT numrange(8,null) <@ nummultirange(numrange(5,null)); +SELECT numrange(3,null) <@ nummultirange(numrange(5,null)); +SELECT numrange(8,9) <@ nummultirange(numrange(1,5)); +SELECT numrange(3,9) <@ nummultirange(numrange(1,5)); +SELECT numrange(1,4) <@ nummultirange(numrange(1,5)); +SELECT numrange(1,5) <@ nummultirange(numrange(1,5)); +SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5)); +SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9)); +SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9)); +SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9)); +SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange; +SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange; +SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange; +SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange; +SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange; +SELECT nummultirange(numrange(1,2)) <@ numrange(null,null); +SELECT nummultirange(numrange(null,2)) <@ numrange(null,null); +SELECT nummultirange(numrange(2,null)) <@ numrange(null,null); +SELECT nummultirange(numrange(null,3)) <@ numrange(null,5); +SELECT nummultirange(numrange(null,8)) <@ numrange(null,5); +SELECT nummultirange(numrange(8,null)) <@ numrange(5,null); +SELECT nummultirange(numrange(3,null)) <@ numrange(5,null); +SELECT nummultirange(numrange(8,9)) <@ numrange(1,5); +SELECT nummultirange(numrange(3,9)) <@ numrange(1,5); +SELECT nummultirange(numrange(1,4)) <@ numrange(1,5); +SELECT nummultirange(numrange(1,5)) <@ numrange(1,5); +SELECT nummultirange(numrange(-4,-2), numrange(1,5)) <@ numrange(1,9); +SELECT nummultirange(numrange(1,5), numrange(8,9)) <@ numrange(1,9); +SELECT nummultirange(numrange(1,5), numrange(6,9)) <@ numrange(1,9); +SELECT nummultirange(numrange(1,5), numrange(6,10)) <@ numrange(1,9); +SELECT '{[1,5)}'::nummultirange <@ '{[1,9)}'; +SELECT '{[-4,-2), [1,5)}'::nummultirange <@ '{[1,9)}'; +SELECT '{[1,5), [8,9)}'::nummultirange <@ '{[1,9)}'; +SELECT '{[1,5), [6,9)}'::nummultirange <@ '{[1,9)}'; +SELECT '{[1,5), [6,10)}'::nummultirange <@ '{[1,9)}'; + +-- overleft +SELECT 'empty'::numrange &< nummultirange(); +SELECT 'empty'::numrange &< nummultirange(numrange(1,2)); +SELECT nummultirange() &< 'empty'::numrange; +SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange; +SELECT nummultirange() &< nummultirange(); +SELECT nummultirange(numrange(1,2)) &< nummultirange(); +SELECT nummultirange() &< nummultirange(numrange(1,2)); +SELECT numrange(6,7) &< nummultirange(numrange(3,4)); +SELECT numrange(1,2) &< nummultirange(numrange(3,4)); +SELECT numrange(1,4) &< nummultirange(numrange(3,4)); +SELECT numrange(1,6) &< nummultirange(numrange(3,4)); +SELECT numrange(3.5,6) &< nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(6,7)) &< numrange(3,4); +SELECT nummultirange(numrange(1,2)) &< numrange(3,4); +SELECT nummultirange(numrange(1,4)) &< numrange(3,4); +SELECT nummultirange(numrange(1,6)) &< numrange(3,4); +SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4); +SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4)); + +-- overright +SELECT nummultirange() &> 'empty'::numrange; +SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange; +SELECT 'empty'::numrange &> nummultirange(); +SELECT 'empty'::numrange &> nummultirange(numrange(1,2)); +SELECT nummultirange() &> nummultirange(); +SELECT nummultirange() &> nummultirange(numrange(1,2)); +SELECT nummultirange(numrange(1,2)) &> nummultirange(); +SELECT nummultirange(numrange(3,4)) &> numrange(6,7); +SELECT nummultirange(numrange(3,4)) &> numrange(1,2); +SELECT nummultirange(numrange(3,4)) &> numrange(1,4); +SELECT nummultirange(numrange(3,4)) &> numrange(1,6); +SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6); +SELECT numrange(3,4) &> nummultirange(numrange(6,7)); +SELECT numrange(3,4) &> nummultirange(numrange(1,2)); +SELECT numrange(3,4) &> nummultirange(numrange(1,4)); +SELECT numrange(3,4) &> nummultirange(numrange(1,6)); +SELECT numrange(3,4) &> nummultirange(numrange(3.5,6)); +SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7)); +SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2)); +SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4)); +SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6)); +SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6)); + +-- meets +SELECT 'empty'::numrange -|- nummultirange(); +SELECT 'empty'::numrange -|- nummultirange(numrange(1,2)); +SELECT nummultirange() -|- 'empty'::numrange; +SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange; +SELECT nummultirange() -|- nummultirange(); +SELECT nummultirange(numrange(1,2)) -|- nummultirange(); +SELECT nummultirange() -|- nummultirange(numrange(1,2)); +SELECT numrange(1,2) -|- nummultirange(numrange(2,4)); +SELECT numrange(1,2) -|- nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(1,2)) -|- numrange(2,4); +SELECT nummultirange(numrange(1,2)) -|- numrange(3,4); +SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4)); +SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7)); +SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9)); +SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7)); + +-- strictly left +select 'empty'::numrange << nummultirange(); +select numrange(1,2) << nummultirange(); +select numrange(1,2) << nummultirange(numrange(3,4)); +select numrange(1,2) << nummultirange(numrange(0,4)); +select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8)); +select nummultirange() << 'empty'::numrange; +select nummultirange() << numrange(1,2); +select nummultirange(numrange(3,4)) << numrange(3,6); +select nummultirange(numrange(0,2)) << numrange(3,6); +select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6); +select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6); +select nummultirange() << nummultirange(); +select nummultirange() << nummultirange(numrange(1,2)); +select nummultirange(numrange(1,2)) << nummultirange(); +select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2)); +select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4)); +select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8)); +select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8)); + +-- strictly right +select nummultirange() >> 'empty'::numrange; +select nummultirange() >> numrange(1,2); +select nummultirange(numrange(3,4)) >> numrange(1,2); +select nummultirange(numrange(0,4)) >> numrange(1,2); +select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2); +select 'empty'::numrange >> nummultirange(); +select numrange(1,2) >> nummultirange(); +select numrange(3,6) >> nummultirange(numrange(3,4)); +select numrange(3,6) >> nummultirange(numrange(0,2)); +select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8)); +select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2)); +select nummultirange() >> nummultirange(); +select nummultirange(numrange(1,2)) >> nummultirange(); +select nummultirange() >> nummultirange(numrange(1,2)); +select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2)); +select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2)); +select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2)); +select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5)); + +-- union +SELECT nummultirange() + nummultirange(); +SELECT nummultirange() + nummultirange(numrange(1,2)); +SELECT nummultirange(numrange(1,2)) + nummultirange(); +SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2)); +SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4)); +SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4)); +SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9)); + +-- merge +SELECT range_merge(nummultirange()); +SELECT range_merge(nummultirange(numrange(1,2))); +SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8))); + +-- minus +SELECT nummultirange() - nummultirange(); +SELECT nummultirange() - nummultirange(numrange(1,2)); +SELECT nummultirange(numrange(1,2)) - nummultirange(); +SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange(); +SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2)); +SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4)); +SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4)); +SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2)); +SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3)); +SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8)); +SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2)); +SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4)); +SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null)); +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0)); +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4)); +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5)); +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9)); +SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9)); +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9)); +SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9)); + +-- intersection +SELECT nummultirange() * nummultirange(); +SELECT nummultirange() * nummultirange(numrange(1,2)); +SELECT nummultirange(numrange(1,2)) * nummultirange(); +SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange; +SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange; +SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange; +SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange; +SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange; +SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange; +SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange; +SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange; +SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange; +SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange; +SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange; + +-- test GiST index +create table test_multirange_gist(mr int4multirange); +insert into test_multirange_gist select int4multirange(int4range(g, g+10),int4range(g+20, g+30),int4range(g+40, g+50)) from generate_series(1,2000) g; +insert into test_multirange_gist select '{}'::int4multirange from generate_series(1,500) g; +insert into test_multirange_gist select int4multirange(int4range(g, g+10000)) from generate_series(1,1000) g; +insert into test_multirange_gist select int4multirange(int4range(NULL, g*10, '(]'), int4range(g*10, g*20, '(]')) from generate_series(1,100) g; +insert into test_multirange_gist select int4multirange(int4range(g*10, g*20, '(]'), int4range(g*20, NULL, '(]')) from generate_series(1,100) g; +create index test_mulrirange_gist_idx on test_multirange_gist using gist (mr); + +-- test statistics and selectivity estimation as well +-- +-- We don't check the accuracy of selectivity estimation, but at least check +-- it doesn't fall. +analyze test_multirange_gist; + +-- first, verify non-indexed results +SET enable_seqscan = t; +SET enable_indexscan = f; +SET enable_bitmapscan = f; + +select count(*) from test_multirange_gist where mr = '{}'::int4multirange; +select count(*) from test_multirange_gist where mr @> 'empty'::int4range; +select count(*) from test_multirange_gist where mr && 'empty'::int4range; +select count(*) from test_multirange_gist where mr <@ 'empty'::int4range; +select count(*) from test_multirange_gist where mr << 'empty'::int4range; +select count(*) from test_multirange_gist where mr >> 'empty'::int4range; +select count(*) from test_multirange_gist where mr &< 'empty'::int4range; +select count(*) from test_multirange_gist where mr &> 'empty'::int4range; +select count(*) from test_multirange_gist where mr -|- 'empty'::int4range; +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; +select count(*) from test_multirange_gist where mr && '{}'::int4multirange; +select count(*) from test_multirange_gist where mr <@ '{}'::int4multirange; +select count(*) from test_multirange_gist where mr << '{}'::int4multirange; +select count(*) from test_multirange_gist where mr >> '{}'::int4multirange; +select count(*) from test_multirange_gist where mr &< '{}'::int4multirange; +select count(*) from test_multirange_gist where mr &> '{}'::int4multirange; +select count(*) from test_multirange_gist where mr -|- '{}'::int4multirange; + +select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60)); +select count(*) from test_multirange_gist where mr @> 10; +select count(*) from test_multirange_gist where mr @> int4range(10,20); +select count(*) from test_multirange_gist where mr && int4range(10,20); +select count(*) from test_multirange_gist where mr <@ int4range(10,50); +select count(*) from test_multirange_gist where mr << int4range(100,500); +select count(*) from test_multirange_gist where mr >> int4range(100,500); +select count(*) from test_multirange_gist where mr &< int4range(100,500); +select count(*) from test_multirange_gist where mr &> int4range(100,500); +select count(*) from test_multirange_gist where mr -|- int4range(100,500); +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; +select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40)); +select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange; +select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange; +select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500)); +select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500)); +select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500)); +select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500)); +select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500)); + +-- now check same queries using index +SET enable_seqscan = f; +SET enable_indexscan = t; +SET enable_bitmapscan = f; + +select count(*) from test_multirange_gist where mr = '{}'::int4multirange; +select count(*) from test_multirange_gist where mr @> 'empty'::int4range; +select count(*) from test_multirange_gist where mr && 'empty'::int4range; +select count(*) from test_multirange_gist where mr <@ 'empty'::int4range; +select count(*) from test_multirange_gist where mr << 'empty'::int4range; +select count(*) from test_multirange_gist where mr >> 'empty'::int4range; +select count(*) from test_multirange_gist where mr &< 'empty'::int4range; +select count(*) from test_multirange_gist where mr &> 'empty'::int4range; +select count(*) from test_multirange_gist where mr -|- 'empty'::int4range; +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; +select count(*) from test_multirange_gist where mr && '{}'::int4multirange; +select count(*) from test_multirange_gist where mr <@ '{}'::int4multirange; +select count(*) from test_multirange_gist where mr << '{}'::int4multirange; +select count(*) from test_multirange_gist where mr >> '{}'::int4multirange; +select count(*) from test_multirange_gist where mr &< '{}'::int4multirange; +select count(*) from test_multirange_gist where mr &> '{}'::int4multirange; +select count(*) from test_multirange_gist where mr -|- '{}'::int4multirange; + +select count(*) from test_multirange_gist where mr @> 'empty'::int4range; +select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60)); +select count(*) from test_multirange_gist where mr @> 10; +select count(*) from test_multirange_gist where mr @> int4range(10,20); +select count(*) from test_multirange_gist where mr && int4range(10,20); +select count(*) from test_multirange_gist where mr <@ int4range(10,50); +select count(*) from test_multirange_gist where mr << int4range(100,500); +select count(*) from test_multirange_gist where mr >> int4range(100,500); +select count(*) from test_multirange_gist where mr &< int4range(100,500); +select count(*) from test_multirange_gist where mr &> int4range(100,500); +select count(*) from test_multirange_gist where mr -|- int4range(100,500); +select count(*) from test_multirange_gist where mr @> '{}'::int4multirange; +select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40)); +select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange; +select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange; +select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500)); +select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500)); +select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500)); +select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500)); +select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500)); + +drop table test_multirange_gist; + +-- +-- range_agg function +-- +create table reservations ( room_id integer not null, booked_during daterange ); +insert into reservations values +-- 1: has a meets and a gap +(1, daterange('2018-07-01', '2018-07-07')), +(1, daterange('2018-07-07', '2018-07-14')), +(1, daterange('2018-07-20', '2018-07-22')), +-- 2: just a single row +(2, daterange('2018-07-01', '2018-07-03')), +-- 3: one null range +(3, NULL), +-- 4: two null ranges +(4, NULL), +(4, NULL), +-- 5: a null range and a non-null range +(5, NULL), +(5, daterange('2018-07-01', '2018-07-03')), +-- 6: has overlap +(6, daterange('2018-07-01', '2018-07-07')), +(6, daterange('2018-07-05', '2018-07-10')), +-- 7: two ranges that meet: no gap or overlap +(7, daterange('2018-07-01', '2018-07-07')), +(7, daterange('2018-07-07', '2018-07-14')), +-- 8: an empty range +(8, 'empty'::daterange) +; +SELECT room_id, range_agg(booked_during) +FROM reservations +GROUP BY room_id +ORDER BY room_id; + +-- range_agg on a custom range type too +SELECT range_agg(r) +FROM (VALUES + ('[a,c]'::textrange), + ('[b,b]'::textrange), + ('[c,f]'::textrange), + ('[g,h)'::textrange), + ('[h,j)'::textrange) + ) t(r); + +-- range_agg with multirange inputs +select range_agg(nmr) from nummultirange_test; +select range_agg(nmr) from nummultirange_test where false; +select range_agg(null::nummultirange) from nummultirange_test; +select range_agg(nmr) from (values ('{}'::nummultirange)) t(nmr); +select range_agg(nmr) from (values ('{}'::nummultirange), ('{}'::nummultirange)) t(nmr); +select range_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr); +select range_agg(nmr) from (values ('{[1,2], [5,6]}'::nummultirange)) t(nmr); +select range_agg(nmr) from (values ('{[1,2], [2,3]}'::nummultirange)) t(nmr); +select range_agg(nmr) from (values ('{[1,2]}'::nummultirange), ('{[5,6]}'::nummultirange)) t(nmr); +select range_agg(nmr) from (values ('{[1,2]}'::nummultirange), ('{[2,3]}'::nummultirange)) t(nmr); + +-- +-- range_intersect_agg function +-- +select range_intersect_agg(nmr) from nummultirange_test; +select range_intersect_agg(nmr) from nummultirange_test where false; +select range_intersect_agg(null::nummultirange) from nummultirange_test; +select range_intersect_agg(nmr) from (values ('{[1,3]}'::nummultirange), ('{[6,12]}'::nummultirange)) t(nmr); +select range_intersect_agg(nmr) from (values ('{[1,6]}'::nummultirange), ('{[3,12]}'::nummultirange)) t(nmr); +select range_intersect_agg(nmr) from (values ('{[1,6], [10,12]}'::nummultirange), ('{[4,14]}'::nummultirange)) t(nmr); +-- test with just one input: +select range_intersect_agg(nmr) from (values ('{}'::nummultirange)) t(nmr); +select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr); +select range_intersect_agg(nmr) from (values ('{[1,6], [10,12]}'::nummultirange)) t(nmr); +select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0; + +create table nummultirange_test2(nmr nummultirange); +create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr); + +INSERT INTO nummultirange_test2 VALUES('{[, 5)}'); +INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2))); +INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2))); +INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()'))); +INSERT INTO nummultirange_test2 VALUES('{}'); + +select * from nummultirange_test2 where nmr = '{}'; +select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2)); +select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3)); + +set enable_nestloop=t; +set enable_hashjoin=f; +set enable_mergejoin=f; +select * from nummultirange_test natural join nummultirange_test2 order by nmr; +set enable_nestloop=f; +set enable_hashjoin=t; +set enable_mergejoin=f; +select * from nummultirange_test natural join nummultirange_test2 order by nmr; +set enable_nestloop=f; +set enable_hashjoin=f; +set enable_mergejoin=t; +select * from nummultirange_test natural join nummultirange_test2 order by nmr; + +set enable_nestloop to default; +set enable_hashjoin to default; +set enable_mergejoin to default; + +DROP TABLE nummultirange_test2; + +-- +-- Test user-defined multirange of floats +-- + +select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8; +create table float8multirange_test(f8mr float8multirange, i int); + +-- +-- Test multirange types over domains +-- + +create domain mydomain as int4; +create type mydomainrange as range(subtype=mydomain); +select '{[4,50)}'::mydomainmultirange @> 7::mydomain; +drop domain mydomain cascade; + +-- +-- Test domains over multirange types +-- + +create domain restrictedmultirange as int4multirange check (upper(value) < 10); +select '{[4,5)}'::restrictedmultirange @> 7; +select '{[4,50)}'::restrictedmultirange @> 7; -- should fail +drop domain restrictedmultirange; + +--- +-- Check automatic naming of multiranges +--- + +create type intr as range(subtype=int); +select intr_multirange(intr(1,10)); +drop type intr; +create type intmultirange as (x int, y int); +create type intrange as range(subtype=int); -- should fail +drop type intmultirange; +create type intr_multirange as (x int, y int); +create type intr as range(subtype=int); -- should fail +drop type intr_multirange; + +-- +-- Test multiple multirange types over the same subtype and manual naming of +-- the multirange type. +-- + +-- should fail +create type textrange1 as range(subtype=text, multirange_type_name=int, collation="C"); +-- should pass +create type textrange1 as range(subtype=text, multirange_type_name=multirange_of_text, collation="C"); +-- should pass, because existing _textrange1 is automatically renamed +create type textrange2 as range(subtype=text, multirange_type_name=_textrange1, collation="C"); + +select multirange_of_text(textrange2('a','Z')); -- should fail +select multirange_of_text(textrange1('a','Z')) @> 'b'::text; +select unnest(multirange_of_text(textrange1('a','b'), textrange1('d','e'))); +select _textrange1(textrange2('a','z')) @> 'b'::text; + +drop type textrange1; +drop type textrange2; + +-- +-- Test polymorphic type system +-- + +create function anyarray_anymultirange_func(a anyarray, r anymultirange) + returns anyelement as 'select $1[1] + lower($2);' language sql; + +select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20))); + +-- should fail +select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20))); + +drop function anyarray_anymultirange_func(anyarray, anymultirange); + +-- should fail +create function bogus_func(anyelement) + returns anymultirange as 'select int4multirange(int4range(1,10))' language sql; + +-- should fail +create function bogus_func(int) + returns anymultirange as 'select int4multirange(int4range(1,10))' language sql; + +create function range_add_bounds(anymultirange) + returns anyelement as 'select lower($1) + upper($1)' language sql; + +select range_add_bounds(int4multirange(int4range(1, 17))); +select range_add_bounds(nummultirange(numrange(1.0001, 123.123))); + +create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement) + as $$ select upper($1) + $2[1] $$ + language sql; + +select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]); +select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]); -- match failure + +create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange) + returns anycompatible as 'select $1[1] + lower($2);' language sql; + +select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20))); + +select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20))); + +-- should fail +select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20))); + +drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange); + +create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange) + returns anycompatible as 'select lower($1) + lower($2);' language sql; + +select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20))); + +-- should fail +select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20))); + +drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange); + +-- should fail +create function bogus_func(anycompatible) + returns anycompatiblerange as 'select int4range(1,10)' language sql; + +-- +-- Arrays of multiranges +-- + +select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))]; + +create table i8mr_array (f1 int, f2 int8multirange[]); +insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]); +select * from i8mr_array; +drop table i8mr_array; + +-- +-- Multiranges of arrays +-- + +select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1])); +select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2])); -- fail + +select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1])); +select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1])); + +-- +-- Ranges of composites +-- + +create type two_ints as (a int, b int); +create type two_ints_range as range (subtype = two_ints); + +-- with force_parallel_mode on, this exercises tqueue.c's range remapping +select *, row_to_json(upper(t)) as u from + (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))), + (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t); + +drop type two_ints cascade; + +-- +-- Check behavior when subtype lacks a hash function +-- + +set enable_sort = off; -- try to make it pick a hash setop implementation + +select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange; + +reset enable_sort; + +-- +-- OUT/INOUT/TABLE functions +-- + +-- infer anymultirange from anymultirange +create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text) + as $$ select $1, 'foo'::text $$ language sql; + +select * from mr_outparam_succeed(int4multirange(int4range(1,2))); + +-- infer anyarray from anymultirange +create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text) + as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql; + +select * from mr_outparam_succeed2(int4multirange(int4range(1,2))); + +-- infer anyrange from anymultirange +create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text) + as $$ select range_merge($1), 'foo'::text $$ language sql; +select * from mr_outparam_succeed3(int4multirange(int4range(1,2))); + +-- infer anymultirange from anyrange +create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text) + as $$ select multirange($1), 'foo'::text $$ language sql; + +select * from mr_outparam_succeed4(int4range(1,2)); + +-- infer anyelement from anymultirange +create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange) + as $$ select upper($1), $1 $$ language sql; + +select * from mr_inoutparam_succeed(int4multirange(int4range(1,2))); + +-- infer anyelement+anymultirange from anyelement+anymultirange +create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange) + as $$ select $1, $2 $$ language sql; + +select * from mr_table_succeed(123, int4multirange(int4range(1,11))); + +-- use anymultirange in plpgsql +create function mr_polymorphic(i anyrange) returns anymultirange + as $$ begin return multirange($1); end; $$ language plpgsql; +select mr_polymorphic(int4range(1, 4)); + +-- should fail +create function mr_outparam_fail(i anyelement, out r anymultirange, out t text) + as $$ select '[1,10]', 'foo' $$ language sql; + +--should fail +create function mr_inoutparam_fail(inout i anyelement, out r anymultirange) + as $$ select $1, '[1,10]' $$ language sql; + +--should fail +create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange) + as $$ select $1, '[1,10]' $$ language sql; diff --git a/src/test/regress/sql/yb.port.namespace.sql b/src/test/regress/sql/yb.port.namespace.sql new file mode 100644 index 0000000000000..a00ec7a49b885 --- /dev/null +++ b/src/test/regress/sql/yb.port.namespace.sql @@ -0,0 +1,73 @@ +-- +-- Regression tests for schemas (namespaces) +-- + +-- set the whitespace-only search_path to test that the +-- GUC list syntax is preserved during a schema creation +SELECT pg_catalog.set_config('search_path', ' ', false); + +CREATE SCHEMA test_ns_schema_1 + CREATE UNIQUE INDEX abc_a_idx ON abc (a) + + CREATE VIEW abc_view AS + SELECT a+1 AS a, b+1 AS b FROM abc + + CREATE TABLE abc ( + a serial, + b int UNIQUE + ); +CREATE SCHEMA test_ns_schema_1; -- YB: TODO(dmitry): workaround for schema creation with elements +CREATE TABLE test_ns_schema_1.abc (a serial, b int UNIQUE); -- YB +CREATE UNIQUE INDEX abc_a_idx ON test_ns_schema_1.abc (a); -- YB +CREATE VIEW test_ns_schema_1.abc_view AS SELECT a+1 AS a, b+1 AS b FROM test_ns_schema_1.abc; -- YB + +-- verify that the correct search_path restored on abort +SET search_path to public; +BEGIN; +SET search_path to public, test_ns_schema_1; +CREATE SCHEMA test_ns_schema_2; -- YB: TODO(dmitry): schema creation with elements +CREATE VIEW test_ns_schema_2.abc_view AS SELECT c FROM abc; -- YB +COMMIT; +DROP SCHEMA test_ns_schema_2 CASCADE; -- YB: manually drop SCHEMA because above COMMIT rollback fails to roll back CREATE SCHEMA DDL +SHOW search_path; + +-- verify that the correct search_path preserved +-- after creating the schema and on commit +BEGIN; +SET search_path to public, test_ns_schema_1; +CREATE SCHEMA test_ns_schema_2; -- YB: TODO(dmitry): schema creation with elements +CREATE VIEW test_ns_schema_2.abc_view AS SELECT a FROM abc; -- YB +SHOW search_path; +COMMIT; +SHOW search_path; +DROP SCHEMA test_ns_schema_2 CASCADE; + +-- verify that the objects were created +SELECT COUNT(*) FROM pg_class WHERE relnamespace = + (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_1'); + +INSERT INTO test_ns_schema_1.abc DEFAULT VALUES; +INSERT INTO test_ns_schema_1.abc DEFAULT VALUES; +INSERT INTO test_ns_schema_1.abc DEFAULT VALUES; + +SELECT * FROM test_ns_schema_1.abc; +SELECT * FROM test_ns_schema_1.abc_view; + +ALTER SCHEMA test_ns_schema_1 RENAME TO test_ns_schema_renamed; +SELECT COUNT(*) FROM pg_class WHERE relnamespace = + (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_1'); + +-- test IF NOT EXISTS cases +CREATE SCHEMA test_ns_schema_renamed; -- fail, already exists +CREATE SCHEMA IF NOT EXISTS test_ns_schema_renamed; -- ok with notice +CREATE SCHEMA IF NOT EXISTS test_ns_schema_renamed -- fail, disallowed + CREATE TABLE abc ( + a serial, + b int UNIQUE + ); + +DROP SCHEMA test_ns_schema_renamed CASCADE; + +-- verify that the objects were dropped +SELECT COUNT(*) FROM pg_class WHERE relnamespace = + (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_renamed'); diff --git a/src/test/regress/sql/yb.port.numeric.sql b/src/test/regress/sql/yb.port.numeric.sql new file mode 100644 index 0000000000000..424817ba04adb --- /dev/null +++ b/src/test/regress/sql/yb.port.numeric.sql @@ -0,0 +1,1437 @@ +-- +-- NUMERIC +-- + +CREATE TABLE num_data (id int4, val numeric(210,10)); +CREATE TABLE num_exp_add (id1 int4, id2 int4, expected numeric(210,10)); +CREATE TABLE num_exp_sub (id1 int4, id2 int4, expected numeric(210,10)); +CREATE TABLE num_exp_div (id1 int4, id2 int4, expected numeric(210,10)); +CREATE TABLE num_exp_mul (id1 int4, id2 int4, expected numeric(210,10)); +CREATE TABLE num_exp_sqrt (id int4, expected numeric(210,10)); +CREATE TABLE num_exp_ln (id int4, expected numeric(210,10)); +CREATE TABLE num_exp_log10 (id int4, expected numeric(210,10)); +CREATE TABLE num_exp_power_10_ln (id int4, expected numeric(210,10)); + +CREATE TABLE num_result (id1 int4, id2 int4, result numeric(210,10)); + + +-- ****************************** +-- * The following EXPECTED results are computed by bc(1) +-- * with a scale of 200 +-- ****************************** + +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_add VALUES (0,0,'0'); +INSERT INTO num_exp_sub VALUES (0,0,'0'); +INSERT INTO num_exp_mul VALUES (0,0,'0'); +INSERT INTO num_exp_div VALUES (0,0,'NaN'); +INSERT INTO num_exp_add VALUES (0,1,'0'); +INSERT INTO num_exp_sub VALUES (0,1,'0'); +INSERT INTO num_exp_mul VALUES (0,1,'0'); +INSERT INTO num_exp_div VALUES (0,1,'NaN'); +INSERT INTO num_exp_add VALUES (0,2,'-34338492.215397047'); +INSERT INTO num_exp_sub VALUES (0,2,'34338492.215397047'); +INSERT INTO num_exp_mul VALUES (0,2,'0'); +INSERT INTO num_exp_div VALUES (0,2,'0'); +INSERT INTO num_exp_add VALUES (0,3,'4.31'); +INSERT INTO num_exp_sub VALUES (0,3,'-4.31'); +INSERT INTO num_exp_mul VALUES (0,3,'0'); +INSERT INTO num_exp_div VALUES (0,3,'0'); +INSERT INTO num_exp_add VALUES (0,4,'7799461.4119'); +INSERT INTO num_exp_sub VALUES (0,4,'-7799461.4119'); +INSERT INTO num_exp_mul VALUES (0,4,'0'); +INSERT INTO num_exp_div VALUES (0,4,'0'); +INSERT INTO num_exp_add VALUES (0,5,'16397.038491'); +INSERT INTO num_exp_sub VALUES (0,5,'-16397.038491'); +INSERT INTO num_exp_mul VALUES (0,5,'0'); +INSERT INTO num_exp_div VALUES (0,5,'0'); +INSERT INTO num_exp_add VALUES (0,6,'93901.57763026'); +INSERT INTO num_exp_sub VALUES (0,6,'-93901.57763026'); +INSERT INTO num_exp_mul VALUES (0,6,'0'); +INSERT INTO num_exp_div VALUES (0,6,'0'); +INSERT INTO num_exp_add VALUES (0,7,'-83028485'); +INSERT INTO num_exp_sub VALUES (0,7,'83028485'); +INSERT INTO num_exp_mul VALUES (0,7,'0'); +INSERT INTO num_exp_div VALUES (0,7,'0'); +INSERT INTO num_exp_add VALUES (0,8,'74881'); +INSERT INTO num_exp_sub VALUES (0,8,'-74881'); +INSERT INTO num_exp_mul VALUES (0,8,'0'); +INSERT INTO num_exp_div VALUES (0,8,'0'); +INSERT INTO num_exp_add VALUES (0,9,'-24926804.045047420'); +INSERT INTO num_exp_sub VALUES (0,9,'24926804.045047420'); +INSERT INTO num_exp_mul VALUES (0,9,'0'); +INSERT INTO num_exp_div VALUES (0,9,'0'); +INSERT INTO num_exp_add VALUES (1,0,'0'); +INSERT INTO num_exp_sub VALUES (1,0,'0'); +INSERT INTO num_exp_mul VALUES (1,0,'0'); +INSERT INTO num_exp_div VALUES (1,0,'NaN'); +INSERT INTO num_exp_add VALUES (1,1,'0'); +INSERT INTO num_exp_sub VALUES (1,1,'0'); +INSERT INTO num_exp_mul VALUES (1,1,'0'); +INSERT INTO num_exp_div VALUES (1,1,'NaN'); +INSERT INTO num_exp_add VALUES (1,2,'-34338492.215397047'); +INSERT INTO num_exp_sub VALUES (1,2,'34338492.215397047'); +INSERT INTO num_exp_mul VALUES (1,2,'0'); +INSERT INTO num_exp_div VALUES (1,2,'0'); +INSERT INTO num_exp_add VALUES (1,3,'4.31'); +INSERT INTO num_exp_sub VALUES (1,3,'-4.31'); +INSERT INTO num_exp_mul VALUES (1,3,'0'); +INSERT INTO num_exp_div VALUES (1,3,'0'); +INSERT INTO num_exp_add VALUES (1,4,'7799461.4119'); +INSERT INTO num_exp_sub VALUES (1,4,'-7799461.4119'); +INSERT INTO num_exp_mul VALUES (1,4,'0'); +INSERT INTO num_exp_div VALUES (1,4,'0'); +INSERT INTO num_exp_add VALUES (1,5,'16397.038491'); +INSERT INTO num_exp_sub VALUES (1,5,'-16397.038491'); +INSERT INTO num_exp_mul VALUES (1,5,'0'); +INSERT INTO num_exp_div VALUES (1,5,'0'); +INSERT INTO num_exp_add VALUES (1,6,'93901.57763026'); +INSERT INTO num_exp_sub VALUES (1,6,'-93901.57763026'); +INSERT INTO num_exp_mul VALUES (1,6,'0'); +INSERT INTO num_exp_div VALUES (1,6,'0'); +INSERT INTO num_exp_add VALUES (1,7,'-83028485'); +INSERT INTO num_exp_sub VALUES (1,7,'83028485'); +INSERT INTO num_exp_mul VALUES (1,7,'0'); +INSERT INTO num_exp_div VALUES (1,7,'0'); +INSERT INTO num_exp_add VALUES (1,8,'74881'); +INSERT INTO num_exp_sub VALUES (1,8,'-74881'); +INSERT INTO num_exp_mul VALUES (1,8,'0'); +INSERT INTO num_exp_div VALUES (1,8,'0'); +INSERT INTO num_exp_add VALUES (1,9,'-24926804.045047420'); +INSERT INTO num_exp_sub VALUES (1,9,'24926804.045047420'); +INSERT INTO num_exp_mul VALUES (1,9,'0'); +INSERT INTO num_exp_div VALUES (1,9,'0'); +INSERT INTO num_exp_add VALUES (2,0,'-34338492.215397047'); +INSERT INTO num_exp_sub VALUES (2,0,'-34338492.215397047'); +INSERT INTO num_exp_mul VALUES (2,0,'0'); +INSERT INTO num_exp_div VALUES (2,0,'NaN'); +INSERT INTO num_exp_add VALUES (2,1,'-34338492.215397047'); +INSERT INTO num_exp_sub VALUES (2,1,'-34338492.215397047'); +INSERT INTO num_exp_mul VALUES (2,1,'0'); +INSERT INTO num_exp_div VALUES (2,1,'NaN'); +INSERT INTO num_exp_add VALUES (2,2,'-68676984.430794094'); +INSERT INTO num_exp_sub VALUES (2,2,'0'); +INSERT INTO num_exp_mul VALUES (2,2,'1179132047626883.596862135856320209'); +INSERT INTO num_exp_div VALUES (2,2,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (2,3,'-34338487.905397047'); +INSERT INTO num_exp_sub VALUES (2,3,'-34338496.525397047'); +INSERT INTO num_exp_mul VALUES (2,3,'-147998901.44836127257'); +INSERT INTO num_exp_div VALUES (2,3,'-7967167.56737750510440835266'); +INSERT INTO num_exp_add VALUES (2,4,'-26539030.803497047'); +INSERT INTO num_exp_sub VALUES (2,4,'-42137953.627297047'); +INSERT INTO num_exp_mul VALUES (2,4,'-267821744976817.8111137106593'); +INSERT INTO num_exp_div VALUES (2,4,'-4.40267480046830116685'); +INSERT INTO num_exp_add VALUES (2,5,'-34322095.176906047'); +INSERT INTO num_exp_sub VALUES (2,5,'-34354889.253888047'); +INSERT INTO num_exp_mul VALUES (2,5,'-563049578578.769242506736077'); +INSERT INTO num_exp_div VALUES (2,5,'-2094.18866914563535496429'); +INSERT INTO num_exp_add VALUES (2,6,'-34244590.637766787'); +INSERT INTO num_exp_sub VALUES (2,6,'-34432393.793027307'); +INSERT INTO num_exp_mul VALUES (2,6,'-3224438592470.18449811926184222'); +INSERT INTO num_exp_div VALUES (2,6,'-365.68599891479766440940'); +INSERT INTO num_exp_add VALUES (2,7,'-117366977.215397047'); +INSERT INTO num_exp_sub VALUES (2,7,'48689992.784602953'); +INSERT INTO num_exp_mul VALUES (2,7,'2851072985828710.485883795'); +INSERT INTO num_exp_div VALUES (2,7,'.41357483778485235518'); +INSERT INTO num_exp_add VALUES (2,8,'-34263611.215397047'); +INSERT INTO num_exp_sub VALUES (2,8,'-34413373.215397047'); +INSERT INTO num_exp_mul VALUES (2,8,'-2571300635581.146276407'); +INSERT INTO num_exp_div VALUES (2,8,'-458.57416721727870888476'); +INSERT INTO num_exp_add VALUES (2,9,'-59265296.260444467'); +INSERT INTO num_exp_sub VALUES (2,9,'-9411688.170349627'); +INSERT INTO num_exp_mul VALUES (2,9,'855948866655588.453741509242968740'); +INSERT INTO num_exp_div VALUES (2,9,'1.37757299946438931811'); +INSERT INTO num_exp_add VALUES (3,0,'4.31'); +INSERT INTO num_exp_sub VALUES (3,0,'4.31'); +INSERT INTO num_exp_mul VALUES (3,0,'0'); +INSERT INTO num_exp_div VALUES (3,0,'NaN'); +INSERT INTO num_exp_add VALUES (3,1,'4.31'); +INSERT INTO num_exp_sub VALUES (3,1,'4.31'); +INSERT INTO num_exp_mul VALUES (3,1,'0'); +INSERT INTO num_exp_div VALUES (3,1,'NaN'); +INSERT INTO num_exp_add VALUES (3,2,'-34338487.905397047'); +INSERT INTO num_exp_sub VALUES (3,2,'34338496.525397047'); +INSERT INTO num_exp_mul VALUES (3,2,'-147998901.44836127257'); +INSERT INTO num_exp_div VALUES (3,2,'-.00000012551512084352'); +INSERT INTO num_exp_add VALUES (3,3,'8.62'); +INSERT INTO num_exp_sub VALUES (3,3,'0'); +INSERT INTO num_exp_mul VALUES (3,3,'18.5761'); +INSERT INTO num_exp_div VALUES (3,3,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (3,4,'7799465.7219'); +INSERT INTO num_exp_sub VALUES (3,4,'-7799457.1019'); +INSERT INTO num_exp_mul VALUES (3,4,'33615678.685289'); +INSERT INTO num_exp_div VALUES (3,4,'.00000055260225961552'); +INSERT INTO num_exp_add VALUES (3,5,'16401.348491'); +INSERT INTO num_exp_sub VALUES (3,5,'-16392.728491'); +INSERT INTO num_exp_mul VALUES (3,5,'70671.23589621'); +INSERT INTO num_exp_div VALUES (3,5,'.00026285234387695504'); +INSERT INTO num_exp_add VALUES (3,6,'93905.88763026'); +INSERT INTO num_exp_sub VALUES (3,6,'-93897.26763026'); +INSERT INTO num_exp_mul VALUES (3,6,'404715.7995864206'); +INSERT INTO num_exp_div VALUES (3,6,'.00004589912234457595'); +INSERT INTO num_exp_add VALUES (3,7,'-83028480.69'); +INSERT INTO num_exp_sub VALUES (3,7,'83028489.31'); +INSERT INTO num_exp_mul VALUES (3,7,'-357852770.35'); +INSERT INTO num_exp_div VALUES (3,7,'-.00000005190989574240'); +INSERT INTO num_exp_add VALUES (3,8,'74885.31'); +INSERT INTO num_exp_sub VALUES (3,8,'-74876.69'); +INSERT INTO num_exp_mul VALUES (3,8,'322737.11'); +INSERT INTO num_exp_div VALUES (3,8,'.00005755799201399553'); +INSERT INTO num_exp_add VALUES (3,9,'-24926799.735047420'); +INSERT INTO num_exp_sub VALUES (3,9,'24926808.355047420'); +INSERT INTO num_exp_mul VALUES (3,9,'-107434525.43415438020'); +INSERT INTO num_exp_div VALUES (3,9,'-.00000017290624149854'); +INSERT INTO num_exp_add VALUES (4,0,'7799461.4119'); +INSERT INTO num_exp_sub VALUES (4,0,'7799461.4119'); +INSERT INTO num_exp_mul VALUES (4,0,'0'); +INSERT INTO num_exp_div VALUES (4,0,'NaN'); +INSERT INTO num_exp_add VALUES (4,1,'7799461.4119'); +INSERT INTO num_exp_sub VALUES (4,1,'7799461.4119'); +INSERT INTO num_exp_mul VALUES (4,1,'0'); +INSERT INTO num_exp_div VALUES (4,1,'NaN'); +INSERT INTO num_exp_add VALUES (4,2,'-26539030.803497047'); +INSERT INTO num_exp_sub VALUES (4,2,'42137953.627297047'); +INSERT INTO num_exp_mul VALUES (4,2,'-267821744976817.8111137106593'); +INSERT INTO num_exp_div VALUES (4,2,'-.22713465002993920385'); +INSERT INTO num_exp_add VALUES (4,3,'7799465.7219'); +INSERT INTO num_exp_sub VALUES (4,3,'7799457.1019'); +INSERT INTO num_exp_mul VALUES (4,3,'33615678.685289'); +INSERT INTO num_exp_div VALUES (4,3,'1809619.81714617169373549883'); +INSERT INTO num_exp_add VALUES (4,4,'15598922.8238'); +INSERT INTO num_exp_sub VALUES (4,4,'0'); +INSERT INTO num_exp_mul VALUES (4,4,'60831598315717.14146161'); +INSERT INTO num_exp_div VALUES (4,4,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (4,5,'7815858.450391'); +INSERT INTO num_exp_sub VALUES (4,5,'7783064.373409'); +INSERT INTO num_exp_mul VALUES (4,5,'127888068979.9935054429'); +INSERT INTO num_exp_div VALUES (4,5,'475.66281046305802686061'); +INSERT INTO num_exp_add VALUES (4,6,'7893362.98953026'); +INSERT INTO num_exp_sub VALUES (4,6,'7705559.83426974'); +INSERT INTO num_exp_mul VALUES (4,6,'732381731243.745115764094'); +INSERT INTO num_exp_div VALUES (4,6,'83.05996138436129499606'); +INSERT INTO num_exp_add VALUES (4,7,'-75229023.5881'); +INSERT INTO num_exp_sub VALUES (4,7,'90827946.4119'); +INSERT INTO num_exp_mul VALUES (4,7,'-647577464846017.9715'); +INSERT INTO num_exp_div VALUES (4,7,'-.09393717604145131637'); +INSERT INTO num_exp_add VALUES (4,8,'7874342.4119'); +INSERT INTO num_exp_sub VALUES (4,8,'7724580.4119'); +INSERT INTO num_exp_mul VALUES (4,8,'584031469984.4839'); +INSERT INTO num_exp_div VALUES (4,8,'104.15808298366741897143'); +INSERT INTO num_exp_add VALUES (4,9,'-17127342.633147420'); +INSERT INTO num_exp_sub VALUES (4,9,'32726265.456947420'); +INSERT INTO num_exp_mul VALUES (4,9,'-194415646271340.1815956522980'); +INSERT INTO num_exp_div VALUES (4,9,'-.31289456112403769409'); +INSERT INTO num_exp_add VALUES (5,0,'16397.038491'); +INSERT INTO num_exp_sub VALUES (5,0,'16397.038491'); +INSERT INTO num_exp_mul VALUES (5,0,'0'); +INSERT INTO num_exp_div VALUES (5,0,'NaN'); +INSERT INTO num_exp_add VALUES (5,1,'16397.038491'); +INSERT INTO num_exp_sub VALUES (5,1,'16397.038491'); +INSERT INTO num_exp_mul VALUES (5,1,'0'); +INSERT INTO num_exp_div VALUES (5,1,'NaN'); +INSERT INTO num_exp_add VALUES (5,2,'-34322095.176906047'); +INSERT INTO num_exp_sub VALUES (5,2,'34354889.253888047'); +INSERT INTO num_exp_mul VALUES (5,2,'-563049578578.769242506736077'); +INSERT INTO num_exp_div VALUES (5,2,'-.00047751189505192446'); +INSERT INTO num_exp_add VALUES (5,3,'16401.348491'); +INSERT INTO num_exp_sub VALUES (5,3,'16392.728491'); +INSERT INTO num_exp_mul VALUES (5,3,'70671.23589621'); +INSERT INTO num_exp_div VALUES (5,3,'3804.41728329466357308584'); +INSERT INTO num_exp_add VALUES (5,4,'7815858.450391'); +INSERT INTO num_exp_sub VALUES (5,4,'-7783064.373409'); +INSERT INTO num_exp_mul VALUES (5,4,'127888068979.9935054429'); +INSERT INTO num_exp_div VALUES (5,4,'.00210232958726897192'); +INSERT INTO num_exp_add VALUES (5,5,'32794.076982'); +INSERT INTO num_exp_sub VALUES (5,5,'0'); +INSERT INTO num_exp_mul VALUES (5,5,'268862871.275335557081'); +INSERT INTO num_exp_div VALUES (5,5,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (5,6,'110298.61612126'); +INSERT INTO num_exp_sub VALUES (5,6,'-77504.53913926'); +INSERT INTO num_exp_mul VALUES (5,6,'1539707782.76899778633766'); +INSERT INTO num_exp_div VALUES (5,6,'.17461941433576102689'); +INSERT INTO num_exp_add VALUES (5,7,'-83012087.961509'); +INSERT INTO num_exp_sub VALUES (5,7,'83044882.038491'); +INSERT INTO num_exp_mul VALUES (5,7,'-1361421264394.416135'); +INSERT INTO num_exp_div VALUES (5,7,'-.00019748690453643710'); +INSERT INTO num_exp_add VALUES (5,8,'91278.038491'); +INSERT INTO num_exp_sub VALUES (5,8,'-58483.961509'); +INSERT INTO num_exp_mul VALUES (5,8,'1227826639.244571'); +INSERT INTO num_exp_div VALUES (5,8,'.21897461960978085228'); +INSERT INTO num_exp_add VALUES (5,9,'-24910407.006556420'); +INSERT INTO num_exp_sub VALUES (5,9,'24943201.083538420'); +INSERT INTO num_exp_mul VALUES (5,9,'-408725765384.257043660243220'); +INSERT INTO num_exp_div VALUES (5,9,'-.00065780749354660427'); +INSERT INTO num_exp_add VALUES (6,0,'93901.57763026'); +INSERT INTO num_exp_sub VALUES (6,0,'93901.57763026'); +INSERT INTO num_exp_mul VALUES (6,0,'0'); +INSERT INTO num_exp_div VALUES (6,0,'NaN'); +INSERT INTO num_exp_add VALUES (6,1,'93901.57763026'); +INSERT INTO num_exp_sub VALUES (6,1,'93901.57763026'); +INSERT INTO num_exp_mul VALUES (6,1,'0'); +INSERT INTO num_exp_div VALUES (6,1,'NaN'); +INSERT INTO num_exp_add VALUES (6,2,'-34244590.637766787'); +INSERT INTO num_exp_sub VALUES (6,2,'34432393.793027307'); +INSERT INTO num_exp_mul VALUES (6,2,'-3224438592470.18449811926184222'); +INSERT INTO num_exp_div VALUES (6,2,'-.00273458651128995823'); +INSERT INTO num_exp_add VALUES (6,3,'93905.88763026'); +INSERT INTO num_exp_sub VALUES (6,3,'93897.26763026'); +INSERT INTO num_exp_mul VALUES (6,3,'404715.7995864206'); +INSERT INTO num_exp_div VALUES (6,3,'21786.90896293735498839907'); +INSERT INTO num_exp_add VALUES (6,4,'7893362.98953026'); +INSERT INTO num_exp_sub VALUES (6,4,'-7705559.83426974'); +INSERT INTO num_exp_mul VALUES (6,4,'732381731243.745115764094'); +INSERT INTO num_exp_div VALUES (6,4,'.01203949512295682469'); +INSERT INTO num_exp_add VALUES (6,5,'110298.61612126'); +INSERT INTO num_exp_sub VALUES (6,5,'77504.53913926'); +INSERT INTO num_exp_mul VALUES (6,5,'1539707782.76899778633766'); +INSERT INTO num_exp_div VALUES (6,5,'5.72674008674192359679'); +INSERT INTO num_exp_add VALUES (6,6,'187803.15526052'); +INSERT INTO num_exp_sub VALUES (6,6,'0'); +INSERT INTO num_exp_mul VALUES (6,6,'8817506281.4517452372676676'); +INSERT INTO num_exp_div VALUES (6,6,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (6,7,'-82934583.42236974'); +INSERT INTO num_exp_sub VALUES (6,7,'83122386.57763026'); +INSERT INTO num_exp_mul VALUES (6,7,'-7796505729750.37795610'); +INSERT INTO num_exp_div VALUES (6,7,'-.00113095617281538980'); +INSERT INTO num_exp_add VALUES (6,8,'168782.57763026'); +INSERT INTO num_exp_sub VALUES (6,8,'19020.57763026'); +INSERT INTO num_exp_mul VALUES (6,8,'7031444034.53149906'); +INSERT INTO num_exp_div VALUES (6,8,'1.25401073209839612184'); +INSERT INTO num_exp_add VALUES (6,9,'-24832902.467417160'); +INSERT INTO num_exp_sub VALUES (6,9,'25020705.622677680'); +INSERT INTO num_exp_mul VALUES (6,9,'-2340666225110.29929521292692920'); +INSERT INTO num_exp_div VALUES (6,9,'-.00376709254265256789'); +INSERT INTO num_exp_add VALUES (7,0,'-83028485'); +INSERT INTO num_exp_sub VALUES (7,0,'-83028485'); +INSERT INTO num_exp_mul VALUES (7,0,'0'); +INSERT INTO num_exp_div VALUES (7,0,'NaN'); +INSERT INTO num_exp_add VALUES (7,1,'-83028485'); +INSERT INTO num_exp_sub VALUES (7,1,'-83028485'); +INSERT INTO num_exp_mul VALUES (7,1,'0'); +INSERT INTO num_exp_div VALUES (7,1,'NaN'); +INSERT INTO num_exp_add VALUES (7,2,'-117366977.215397047'); +INSERT INTO num_exp_sub VALUES (7,2,'-48689992.784602953'); +INSERT INTO num_exp_mul VALUES (7,2,'2851072985828710.485883795'); +INSERT INTO num_exp_div VALUES (7,2,'2.41794207151503385700'); +INSERT INTO num_exp_add VALUES (7,3,'-83028480.69'); +INSERT INTO num_exp_sub VALUES (7,3,'-83028489.31'); +INSERT INTO num_exp_mul VALUES (7,3,'-357852770.35'); +INSERT INTO num_exp_div VALUES (7,3,'-19264149.65197215777262180974'); +INSERT INTO num_exp_add VALUES (7,4,'-75229023.5881'); +INSERT INTO num_exp_sub VALUES (7,4,'-90827946.4119'); +INSERT INTO num_exp_mul VALUES (7,4,'-647577464846017.9715'); +INSERT INTO num_exp_div VALUES (7,4,'-10.64541262725136247686'); +INSERT INTO num_exp_add VALUES (7,5,'-83012087.961509'); +INSERT INTO num_exp_sub VALUES (7,5,'-83044882.038491'); +INSERT INTO num_exp_mul VALUES (7,5,'-1361421264394.416135'); +INSERT INTO num_exp_div VALUES (7,5,'-5063.62688881730941836574'); +INSERT INTO num_exp_add VALUES (7,6,'-82934583.42236974'); +INSERT INTO num_exp_sub VALUES (7,6,'-83122386.57763026'); +INSERT INTO num_exp_mul VALUES (7,6,'-7796505729750.37795610'); +INSERT INTO num_exp_div VALUES (7,6,'-884.20756174009028770294'); +INSERT INTO num_exp_add VALUES (7,7,'-166056970'); +INSERT INTO num_exp_sub VALUES (7,7,'0'); +INSERT INTO num_exp_mul VALUES (7,7,'6893729321395225'); +INSERT INTO num_exp_div VALUES (7,7,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (7,8,'-82953604'); +INSERT INTO num_exp_sub VALUES (7,8,'-83103366'); +INSERT INTO num_exp_mul VALUES (7,8,'-6217255985285'); +INSERT INTO num_exp_div VALUES (7,8,'-1108.80577182462841041118'); +INSERT INTO num_exp_add VALUES (7,9,'-107955289.045047420'); +INSERT INTO num_exp_sub VALUES (7,9,'-58101680.954952580'); +INSERT INTO num_exp_mul VALUES (7,9,'2069634775752159.035758700'); +INSERT INTO num_exp_div VALUES (7,9,'3.33089171198810413382'); +INSERT INTO num_exp_add VALUES (8,0,'74881'); +INSERT INTO num_exp_sub VALUES (8,0,'74881'); +INSERT INTO num_exp_mul VALUES (8,0,'0'); +INSERT INTO num_exp_div VALUES (8,0,'NaN'); +INSERT INTO num_exp_add VALUES (8,1,'74881'); +INSERT INTO num_exp_sub VALUES (8,1,'74881'); +INSERT INTO num_exp_mul VALUES (8,1,'0'); +INSERT INTO num_exp_div VALUES (8,1,'NaN'); +INSERT INTO num_exp_add VALUES (8,2,'-34263611.215397047'); +INSERT INTO num_exp_sub VALUES (8,2,'34413373.215397047'); +INSERT INTO num_exp_mul VALUES (8,2,'-2571300635581.146276407'); +INSERT INTO num_exp_div VALUES (8,2,'-.00218067233500788615'); +INSERT INTO num_exp_add VALUES (8,3,'74885.31'); +INSERT INTO num_exp_sub VALUES (8,3,'74876.69'); +INSERT INTO num_exp_mul VALUES (8,3,'322737.11'); +INSERT INTO num_exp_div VALUES (8,3,'17373.78190255220417633410'); +INSERT INTO num_exp_add VALUES (8,4,'7874342.4119'); +INSERT INTO num_exp_sub VALUES (8,4,'-7724580.4119'); +INSERT INTO num_exp_mul VALUES (8,4,'584031469984.4839'); +INSERT INTO num_exp_div VALUES (8,4,'.00960079113741758956'); +INSERT INTO num_exp_add VALUES (8,5,'91278.038491'); +INSERT INTO num_exp_sub VALUES (8,5,'58483.961509'); +INSERT INTO num_exp_mul VALUES (8,5,'1227826639.244571'); +INSERT INTO num_exp_div VALUES (8,5,'4.56673929509287019456'); +INSERT INTO num_exp_add VALUES (8,6,'168782.57763026'); +INSERT INTO num_exp_sub VALUES (8,6,'-19020.57763026'); +INSERT INTO num_exp_mul VALUES (8,6,'7031444034.53149906'); +INSERT INTO num_exp_div VALUES (8,6,'.79744134113322314424'); +INSERT INTO num_exp_add VALUES (8,7,'-82953604'); +INSERT INTO num_exp_sub VALUES (8,7,'83103366'); +INSERT INTO num_exp_mul VALUES (8,7,'-6217255985285'); +INSERT INTO num_exp_div VALUES (8,7,'-.00090187120721280172'); +INSERT INTO num_exp_add VALUES (8,8,'149762'); +INSERT INTO num_exp_sub VALUES (8,8,'0'); +INSERT INTO num_exp_mul VALUES (8,8,'5607164161'); +INSERT INTO num_exp_div VALUES (8,8,'1.00000000000000000000'); +INSERT INTO num_exp_add VALUES (8,9,'-24851923.045047420'); +INSERT INTO num_exp_sub VALUES (8,9,'25001685.045047420'); +INSERT INTO num_exp_mul VALUES (8,9,'-1866544013697.195857020'); +INSERT INTO num_exp_div VALUES (8,9,'-.00300403532938582735'); +INSERT INTO num_exp_add VALUES (9,0,'-24926804.045047420'); +INSERT INTO num_exp_sub VALUES (9,0,'-24926804.045047420'); +INSERT INTO num_exp_mul VALUES (9,0,'0'); +INSERT INTO num_exp_div VALUES (9,0,'NaN'); +INSERT INTO num_exp_add VALUES (9,1,'-24926804.045047420'); +INSERT INTO num_exp_sub VALUES (9,1,'-24926804.045047420'); +INSERT INTO num_exp_mul VALUES (9,1,'0'); +INSERT INTO num_exp_div VALUES (9,1,'NaN'); +INSERT INTO num_exp_add VALUES (9,2,'-59265296.260444467'); +INSERT INTO num_exp_sub VALUES (9,2,'9411688.170349627'); +INSERT INTO num_exp_mul VALUES (9,2,'855948866655588.453741509242968740'); +INSERT INTO num_exp_div VALUES (9,2,'.72591434384152961526'); +INSERT INTO num_exp_add VALUES (9,3,'-24926799.735047420'); +INSERT INTO num_exp_sub VALUES (9,3,'-24926808.355047420'); +INSERT INTO num_exp_mul VALUES (9,3,'-107434525.43415438020'); +INSERT INTO num_exp_div VALUES (9,3,'-5783481.21694835730858468677'); +INSERT INTO num_exp_add VALUES (9,4,'-17127342.633147420'); +INSERT INTO num_exp_sub VALUES (9,4,'-32726265.456947420'); +INSERT INTO num_exp_mul VALUES (9,4,'-194415646271340.1815956522980'); +INSERT INTO num_exp_div VALUES (9,4,'-3.19596478892958416484'); +INSERT INTO num_exp_add VALUES (9,5,'-24910407.006556420'); +INSERT INTO num_exp_sub VALUES (9,5,'-24943201.083538420'); +INSERT INTO num_exp_mul VALUES (9,5,'-408725765384.257043660243220'); +INSERT INTO num_exp_div VALUES (9,5,'-1520.20159364322004505807'); +INSERT INTO num_exp_add VALUES (9,6,'-24832902.467417160'); +INSERT INTO num_exp_sub VALUES (9,6,'-25020705.622677680'); +INSERT INTO num_exp_mul VALUES (9,6,'-2340666225110.29929521292692920'); +INSERT INTO num_exp_div VALUES (9,6,'-265.45671195426965751280'); +INSERT INTO num_exp_add VALUES (9,7,'-107955289.045047420'); +INSERT INTO num_exp_sub VALUES (9,7,'58101680.954952580'); +INSERT INTO num_exp_mul VALUES (9,7,'2069634775752159.035758700'); +INSERT INTO num_exp_div VALUES (9,7,'.30021990699995814689'); +INSERT INTO num_exp_add VALUES (9,8,'-24851923.045047420'); +INSERT INTO num_exp_sub VALUES (9,8,'-25001685.045047420'); +INSERT INTO num_exp_mul VALUES (9,8,'-1866544013697.195857020'); +INSERT INTO num_exp_div VALUES (9,8,'-332.88556569820675471748'); +INSERT INTO num_exp_add VALUES (9,9,'-49853608.090094840'); +INSERT INTO num_exp_sub VALUES (9,9,'0'); +INSERT INTO num_exp_mul VALUES (9,9,'621345559900192.420120630048656400'); +INSERT INTO num_exp_div VALUES (9,9,'1.00000000000000000000'); +-- COMMIT TRANSACTION; -- YB +BEGIN TRANSACTION; +INSERT INTO num_exp_sqrt VALUES (0,'0'); +INSERT INTO num_exp_sqrt VALUES (1,'0'); +INSERT INTO num_exp_sqrt VALUES (2,'5859.90547836712524903505'); +INSERT INTO num_exp_sqrt VALUES (3,'2.07605394920266944396'); +INSERT INTO num_exp_sqrt VALUES (4,'2792.75158435189147418923'); +INSERT INTO num_exp_sqrt VALUES (5,'128.05092147657509145473'); +INSERT INTO num_exp_sqrt VALUES (6,'306.43364311096782703406'); +INSERT INTO num_exp_sqrt VALUES (7,'9111.99676251039939975230'); +INSERT INTO num_exp_sqrt VALUES (8,'273.64392922189960397542'); +INSERT INTO num_exp_sqrt VALUES (9,'4992.67503899937593364766'); +COMMIT TRANSACTION; +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_ln VALUES (0,'NaN'); +INSERT INTO num_exp_ln VALUES (1,'NaN'); +INSERT INTO num_exp_ln VALUES (2,'17.35177750493897715514'); +INSERT INTO num_exp_ln VALUES (3,'1.46093790411565641971'); +INSERT INTO num_exp_ln VALUES (4,'15.86956523951936572464'); +INSERT INTO num_exp_ln VALUES (5,'9.70485601768871834038'); +INSERT INTO num_exp_ln VALUES (6,'11.45000246622944403127'); +INSERT INTO num_exp_ln VALUES (7,'18.23469429965478772991'); +INSERT INTO num_exp_ln VALUES (8,'11.22365546576315513668'); +INSERT INTO num_exp_ln VALUES (9,'17.03145425013166006962'); +-- COMMIT TRANSACTION; -- YB +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_log10 VALUES (0,'NaN'); +INSERT INTO num_exp_log10 VALUES (1,'NaN'); +INSERT INTO num_exp_log10 VALUES (2,'7.53578122160797276459'); +INSERT INTO num_exp_log10 VALUES (3,'.63447727016073160075'); +INSERT INTO num_exp_log10 VALUES (4,'6.89206461372691743345'); +INSERT INTO num_exp_log10 VALUES (5,'4.21476541614777768626'); +INSERT INTO num_exp_log10 VALUES (6,'4.97267288886207207671'); +INSERT INTO num_exp_log10 VALUES (7,'7.91922711353275546914'); +INSERT INTO num_exp_log10 VALUES (8,'4.87437163556421004138'); +INSERT INTO num_exp_log10 VALUES (9,'7.39666659961986567059'); +-- COMMIT TRANSACTION; -- YB +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_power_10_ln VALUES (0,'NaN'); +INSERT INTO num_exp_power_10_ln VALUES (1,'NaN'); +INSERT INTO num_exp_power_10_ln VALUES (2,'224790267919917955.13261618583642653184'); +INSERT INTO num_exp_power_10_ln VALUES (3,'28.90266599445155957393'); +INSERT INTO num_exp_power_10_ln VALUES (4,'7405685069594999.07733999469386277636'); +INSERT INTO num_exp_power_10_ln VALUES (5,'5068226527.32127265408584640098'); +INSERT INTO num_exp_power_10_ln VALUES (6,'281839893606.99372343357047819067'); +INSERT INTO num_exp_power_10_ln VALUES (7,'1716699575118597095.42330819910640247627'); +INSERT INTO num_exp_power_10_ln VALUES (8,'167361463828.07491320069016125952'); +INSERT INTO num_exp_power_10_ln VALUES (9,'107511333880052007.04141124673540337457'); +-- COMMIT TRANSACTION; -- YB +BEGIN TRANSACTION; +INSERT INTO num_data VALUES (0, '0'); +INSERT INTO num_data VALUES (1, '0'); +INSERT INTO num_data VALUES (2, '-34338492.215397047'); +INSERT INTO num_data VALUES (3, '4.31'); +INSERT INTO num_data VALUES (4, '7799461.4119'); +INSERT INTO num_data VALUES (5, '16397.038491'); +INSERT INTO num_data VALUES (6, '93901.57763026'); +INSERT INTO num_data VALUES (7, '-83028485'); +INSERT INTO num_data VALUES (8, '74881'); +INSERT INTO num_data VALUES (9, '-24926804.045047420'); +COMMIT TRANSACTION; + +-- ****************************** +-- * Create indices for faster checks +-- ****************************** + +CREATE UNIQUE INDEX num_exp_add_idx ON num_exp_add (id1, id2); +CREATE UNIQUE INDEX num_exp_sub_idx ON num_exp_sub (id1, id2); +CREATE UNIQUE INDEX num_exp_div_idx ON num_exp_div (id1, id2); +CREATE UNIQUE INDEX num_exp_mul_idx ON num_exp_mul (id1, id2); +CREATE UNIQUE INDEX num_exp_sqrt_idx ON num_exp_sqrt (id); +CREATE UNIQUE INDEX num_exp_ln_idx ON num_exp_ln (id); +CREATE UNIQUE INDEX num_exp_log10_idx ON num_exp_log10 (id); +CREATE UNIQUE INDEX num_exp_power_10_ln_idx ON num_exp_power_10_ln (id); + +VACUUM ANALYZE num_exp_add; +VACUUM ANALYZE num_exp_sub; +VACUUM ANALYZE num_exp_div; +VACUUM ANALYZE num_exp_mul; +VACUUM ANALYZE num_exp_sqrt; +VACUUM ANALYZE num_exp_ln; +VACUUM ANALYZE num_exp_log10; +VACUUM ANALYZE num_exp_power_10_ln; + +-- ****************************** +-- * Now check the behaviour of the NUMERIC type +-- ****************************** + +-- ****************************** +-- * Addition check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val + t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_add t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val + t2.val, 10) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 10) as expected + FROM num_result t1, num_exp_add t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 10); + +-- ****************************** +-- * Subtraction check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val - t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_sub t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val - t2.val, 40) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 40) + FROM num_result t1, num_exp_sub t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 40); + +-- ****************************** +-- * Multiply check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val * t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_mul t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val * t2.val, 30) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 30) as expected + FROM num_result t1, num_exp_mul t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 30); + +-- ****************************** +-- * Division check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val / t2.val + FROM num_data t1, num_data t2 + WHERE t2.val != '0.0'; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_div t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val / t2.val, 80) + FROM num_data t1, num_data t2 + WHERE t2.val != '0.0'; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 80) as expected + FROM num_result t1, num_exp_div t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 80); + +-- ****************************** +-- * Square root check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, SQRT(ABS(val)) + FROM num_data; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_sqrt t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + +-- ****************************** +-- * Natural logarithm check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, LN(ABS(val)) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_ln t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + +-- ****************************** +-- * Logarithm base 10 check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, LOG(numeric '10', ABS(val)) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_log10 t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + +-- ****************************** +-- * POWER(10, LN(value)) check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, POWER(numeric '10', LN(ABS(round(val,200)))) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_power_10_ln t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + +-- ****************************** +-- * Check behavior with Inf and NaN inputs. It's easiest to handle these +-- * separately from the num_data framework used above, because some input +-- * combinations will throw errors. +-- ****************************** + +WITH v(x) AS + (VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan')) +SELECT x1, x2, + x1 + x2 AS sum, + x1 - x2 AS diff, + x1 * x2 AS prod +FROM v AS v1(x1), v AS v2(x2); + +WITH v(x) AS + (VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan')) +SELECT x1, x2, + x1 / x2 AS quot, + x1 % x2 AS mod, + div(x1, x2) AS div +FROM v AS v1(x1), v AS v2(x2) WHERE x2 != 0; + +SELECT 'inf'::numeric / '0'; +SELECT '-inf'::numeric / '0'; +SELECT 'nan'::numeric / '0'; +SELECT '0'::numeric / '0'; +SELECT 'inf'::numeric % '0'; +SELECT '-inf'::numeric % '0'; +SELECT 'nan'::numeric % '0'; +SELECT '0'::numeric % '0'; +SELECT div('inf'::numeric, '0'); +SELECT div('-inf'::numeric, '0'); +SELECT div('nan'::numeric, '0'); +SELECT div('0'::numeric, '0'); + +WITH v(x) AS + (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan')) +SELECT x, -x as minusx, abs(x), floor(x), ceil(x), sign(x), numeric_inc(x) as inc +FROM v; + +WITH v(x) AS + (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan')) +SELECT x, round(x), round(x,1) as round1, trunc(x), trunc(x,1) as trunc1 +FROM v; + +-- the large values fall into the numeric abbreviation code's maximal classes +WITH v(x) AS + (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('1e340'),('-1e340'), + ('inf'),('-inf'),('nan'), + ('inf'),('-inf'),('nan')) +SELECT substring(x::text, 1, 32) +FROM v ORDER BY x; + +WITH v(x) AS + (VALUES('0'::numeric),('1'),('4.2'),('inf'),('nan')) +SELECT x, sqrt(x) +FROM v; + +SELECT sqrt('-1'::numeric); +SELECT sqrt('-inf'::numeric); + +WITH v(x) AS + (VALUES('1'::numeric),('4.2'),('inf'),('nan')) +SELECT x, + log(x), + log10(x), + ln(x) +FROM v; + +SELECT ln('0'::numeric); +SELECT ln('-1'::numeric); +SELECT ln('-inf'::numeric); + +WITH v(x) AS + (VALUES('2'::numeric),('4.2'),('inf'),('nan')) +SELECT x1, x2, + log(x1, x2) +FROM v AS v1(x1), v AS v2(x2); + +SELECT log('0'::numeric, '10'); +SELECT log('10'::numeric, '0'); +SELECT log('-inf'::numeric, '10'); +SELECT log('10'::numeric, '-inf'); +SELECT log('inf'::numeric, '0'); +SELECT log('inf'::numeric, '-inf'); +SELECT log('-inf'::numeric, 'inf'); + +WITH v(x) AS + (VALUES('0'::numeric),('1'),('2'),('4.2'),('inf'),('nan')) +SELECT x1, x2, + power(x1, x2) +FROM v AS v1(x1), v AS v2(x2) WHERE x1 != 0 OR x2 >= 0; + +SELECT power('0'::numeric, '-1'); +SELECT power('0'::numeric, '-inf'); +SELECT power('-1'::numeric, 'inf'); +SELECT power('-2'::numeric, '3'); +SELECT power('-2'::numeric, '3.3'); +SELECT power('-2'::numeric, '-1'); +SELECT power('-2'::numeric, '-1.5'); +SELECT power('-2'::numeric, 'inf'); +SELECT power('-2'::numeric, '-inf'); +SELECT power('inf'::numeric, '-2'); +SELECT power('inf'::numeric, '-inf'); +SELECT power('-inf'::numeric, '2'); +SELECT power('-inf'::numeric, '3'); +SELECT power('-inf'::numeric, '4.5'); +SELECT power('-inf'::numeric, '-2'); +SELECT power('-inf'::numeric, '-3'); +SELECT power('-inf'::numeric, '0'); +SELECT power('-inf'::numeric, 'inf'); +SELECT power('-inf'::numeric, '-inf'); + +-- ****************************** +-- * miscellaneous checks for things that have been broken in the past... +-- ****************************** +-- numeric AVG used to fail on some platforms +SELECT AVG(val) FROM num_data; +SELECT MAX(val) FROM num_data; +SELECT MIN(val) FROM num_data; +SELECT STDDEV(val) FROM num_data; +SELECT VARIANCE(val) FROM num_data; + +-- Check for appropriate rounding and overflow +CREATE TABLE fract_only (id int, val numeric(4,4)); +INSERT INTO fract_only VALUES (1, '0.0'); +INSERT INTO fract_only VALUES (2, '0.1'); +INSERT INTO fract_only VALUES (3, '1.0'); -- should fail +INSERT INTO fract_only VALUES (4, '-0.9999'); +INSERT INTO fract_only VALUES (5, '0.99994'); +INSERT INTO fract_only VALUES (6, '0.99995'); -- should fail +INSERT INTO fract_only VALUES (7, '0.00001'); +INSERT INTO fract_only VALUES (8, '0.00017'); +INSERT INTO fract_only VALUES (9, 'NaN'); +INSERT INTO fract_only VALUES (10, 'Inf'); -- should fail +INSERT INTO fract_only VALUES (11, '-Inf'); -- should fail +SELECT * FROM fract_only; +DROP TABLE fract_only; + +-- Check conversion to integers +SELECT (-9223372036854775808.5)::int8; -- should fail +SELECT (-9223372036854775808.4)::int8; -- ok +SELECT 9223372036854775807.4::int8; -- ok +SELECT 9223372036854775807.5::int8; -- should fail +SELECT (-2147483648.5)::int4; -- should fail +SELECT (-2147483648.4)::int4; -- ok +SELECT 2147483647.4::int4; -- ok +SELECT 2147483647.5::int4; -- should fail +SELECT (-32768.5)::int2; -- should fail +SELECT (-32768.4)::int2; -- ok +SELECT 32767.4::int2; -- ok +SELECT 32767.5::int2; -- should fail + +-- Check inf/nan conversion behavior +SELECT 'NaN'::float8::numeric; +SELECT 'Infinity'::float8::numeric; +SELECT '-Infinity'::float8::numeric; +SELECT 'NaN'::numeric::float8; +SELECT 'Infinity'::numeric::float8; +SELECT '-Infinity'::numeric::float8; +SELECT 'NaN'::float4::numeric; +SELECT 'Infinity'::float4::numeric; +SELECT '-Infinity'::float4::numeric; +SELECT 'NaN'::numeric::float4; +SELECT 'Infinity'::numeric::float4; +SELECT '-Infinity'::numeric::float4; +SELECT '42'::int2::numeric; +SELECT 'NaN'::numeric::int2; +SELECT 'Infinity'::numeric::int2; +SELECT '-Infinity'::numeric::int2; +SELECT 'NaN'::numeric::int4; +SELECT 'Infinity'::numeric::int4; +SELECT '-Infinity'::numeric::int4; +SELECT 'NaN'::numeric::int8; +SELECT 'Infinity'::numeric::int8; +SELECT '-Infinity'::numeric::int8; + +-- Simple check that ceil(), floor(), and round() work correctly +CREATE TABLE ceil_floor_round (a numeric); +INSERT INTO ceil_floor_round VALUES ('-5.5'); +INSERT INTO ceil_floor_round VALUES ('-5.499999'); +INSERT INTO ceil_floor_round VALUES ('9.5'); +INSERT INTO ceil_floor_round VALUES ('9.4999999'); +INSERT INTO ceil_floor_round VALUES ('0.0'); +INSERT INTO ceil_floor_round VALUES ('0.0000001'); +INSERT INTO ceil_floor_round VALUES ('-0.000001'); +SELECT a, ceil(a), ceiling(a), floor(a), round(a) FROM ceil_floor_round; +DROP TABLE ceil_floor_round; + +-- Check rounding, it should round ties away from zero. +SELECT i as pow, + round((-2.5 * 10 ^ i)::numeric, -i), + round((-1.5 * 10 ^ i)::numeric, -i), + round((-0.5 * 10 ^ i)::numeric, -i), + round((0.5 * 10 ^ i)::numeric, -i), + round((1.5 * 10 ^ i)::numeric, -i), + round((2.5 * 10 ^ i)::numeric, -i) +FROM generate_series(-5,5) AS t(i); + +-- Testing for width_bucket(). For convenience, we test both the +-- numeric and float8 versions of the function in this file. + +-- errors +SELECT width_bucket(5.0, 3.0, 4.0, 0); +SELECT width_bucket(5.0, 3.0, 4.0, -5); +SELECT width_bucket(3.5, 3.0, 3.0, 888); +SELECT width_bucket(5.0::float8, 3.0::float8, 4.0::float8, 0); +SELECT width_bucket(5.0::float8, 3.0::float8, 4.0::float8, -5); +SELECT width_bucket(3.5::float8, 3.0::float8, 3.0::float8, 888); +SELECT width_bucket('NaN', 3.0, 4.0, 888); +SELECT width_bucket(0::float8, 'NaN', 4.0::float8, 888); +SELECT width_bucket(2.0, 3.0, '-inf', 888); +SELECT width_bucket(0::float8, '-inf', 4.0::float8, 888); + +-- normal operation +CREATE TABLE width_bucket_test (operand_num numeric, operand_f8 float8); + +COPY width_bucket_test (operand_num) FROM stdin; +-5.2 +-0.0000000001 +0.000000000001 +1 +1.99999999999999 +2 +2.00000000000001 +3 +4 +4.5 +5 +5.5 +6 +7 +8 +9 +9.99999999999999 +10 +10.0000000000001 +\. + +UPDATE width_bucket_test SET operand_f8 = operand_num::float8; + +SELECT + operand_num, + width_bucket(operand_num, 0, 10, 5) AS wb_1, + width_bucket(operand_f8, 0, 10, 5) AS wb_1f, + width_bucket(operand_num, 10, 0, 5) AS wb_2, + width_bucket(operand_f8, 10, 0, 5) AS wb_2f, + width_bucket(operand_num, 2, 8, 4) AS wb_3, + width_bucket(operand_f8, 2, 8, 4) AS wb_3f, + width_bucket(operand_num, 5.0, 5.5, 20) AS wb_4, + width_bucket(operand_f8, 5.0, 5.5, 20) AS wb_4f, + width_bucket(operand_num, -25, 25, 10) AS wb_5, + width_bucket(operand_f8, -25, 25, 10) AS wb_5f + FROM width_bucket_test; + +-- Check positive and negative infinity: we require +-- finite bucket bounds, but allow an infinite operand +SELECT width_bucket(0.0::numeric, 'Infinity'::numeric, 5, 10); -- error +SELECT width_bucket(0.0::numeric, 5, '-Infinity'::numeric, 20); -- error +SELECT width_bucket('Infinity'::numeric, 1, 10, 10), + width_bucket('-Infinity'::numeric, 1, 10, 10); +SELECT width_bucket(0.0::float8, 'Infinity'::float8, 5, 10); -- error +SELECT width_bucket(0.0::float8, 5, '-Infinity'::float8, 20); -- error +SELECT width_bucket('Infinity'::float8, 1, 10, 10), + width_bucket('-Infinity'::float8, 1, 10, 10); + +DROP TABLE width_bucket_test; + +-- Simple test for roundoff error when results should be exact +SELECT x, width_bucket(x::float8, 10, 100, 9) as flt, + width_bucket(x::numeric, 10, 100, 9) as num +FROM generate_series(0, 110, 10) x; +SELECT x, width_bucket(x::float8, 100, 10, 9) as flt, + width_bucket(x::numeric, 100, 10, 9) as num +FROM generate_series(0, 110, 10) x; + +-- +-- TO_CHAR() +-- +SELECT to_char(val, '9G999G999G999G999G999') + FROM num_data; + +SELECT to_char(val, '9G999G999G999G999G999D999G999G999G999G999') + FROM num_data; + +SELECT to_char(val, '9999999999999999.999999999999999PR') + FROM num_data; + +SELECT to_char(val, '9999999999999999.999999999999999S') + FROM num_data; + +SELECT to_char(val, 'MI9999999999999999.999999999999999') FROM num_data; +SELECT to_char(val, 'FMS9999999999999999.999999999999999') FROM num_data; +SELECT to_char(val, 'FM9999999999999999.999999999999999THPR') FROM num_data; +SELECT to_char(val, 'SG9999999999999999.999999999999999th') FROM num_data; +SELECT to_char(val, '0999999999999999.999999999999999') FROM num_data; +SELECT to_char(val, 'S0999999999999999.999999999999999') FROM num_data; +SELECT to_char(val, 'FM0999999999999999.999999999999999') FROM num_data; +SELECT to_char(val, 'FM9999999999999999.099999999999999') FROM num_data; +SELECT to_char(val, 'FM9999999999990999.990999999999999') FROM num_data; +SELECT to_char(val, 'FM0999999999999999.999909999999999') FROM num_data; +SELECT to_char(val, 'FM9999999990999999.099999999999999') FROM num_data; +-- YB: TODO: Disabled the test below as it relies on the current locale which is +-- configured in pg_regress using ALTER which we do not support yet. +-- SELECT to_char(val, 'L9999999999999999.099999999999999') FROM num_data; -- YB +SELECT to_char(val, 'FM9999999999999999.99999999999999') FROM num_data; +SELECT to_char(val, 'S 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 . 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9') FROM num_data; +SELECT to_char(val, 'FMS 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 . 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9') FROM num_data; +SELECT to_char(val, E'99999 "text" 9999 "9999" 999 "\\"text between quote marks\\"" 9999') FROM num_data; +SELECT to_char(val, '999999SG9999999999') FROM num_data; +SELECT to_char(val, 'FM9999999999999999.999999999999999') FROM num_data; +SELECT to_char(val, '9.999EEEE') FROM num_data; + +WITH v(val) AS + (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan')) +SELECT val, + to_char(val, '9.999EEEE') as numeric, + to_char(val::float8, '9.999EEEE') as float8, + to_char(val::float4, '9.999EEEE') as float4 +FROM v; + +WITH v(exp) AS + (VALUES(-16379),(-16378),(-1234),(-789),(-45),(-5),(-4),(-3),(-2),(-1),(0), + (1),(2),(3),(4),(5),(38),(275),(2345),(45678),(131070),(131071)) +SELECT exp, + to_char(('1.2345e'||exp)::numeric, '9.999EEEE') as numeric +FROM v; + +WITH v(val) AS + (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan')) +SELECT val, + to_char(val, 'MI9999999999.99') as numeric, + to_char(val::float8, 'MI9999999999.99') as float8, + to_char(val::float4, 'MI9999999999.99') as float4 +FROM v; + +WITH v(val) AS + (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan')) +SELECT val, + to_char(val, 'MI99.99') as numeric, + to_char(val::float8, 'MI99.99') as float8, + to_char(val::float4, 'MI99.99') as float4 +FROM v; + +SELECT to_char('100'::numeric, 'FM999.9'); +SELECT to_char('100'::numeric, 'FM999.'); +SELECT to_char('100'::numeric, 'FM999'); + +-- Check parsing of literal text in a format string +SELECT to_char('100'::numeric, 'foo999'); +SELECT to_char('100'::numeric, 'f\oo999'); +SELECT to_char('100'::numeric, 'f\\oo999'); +SELECT to_char('100'::numeric, 'f\"oo999'); +SELECT to_char('100'::numeric, 'f\\"oo999'); +SELECT to_char('100'::numeric, 'f"ool"999'); +SELECT to_char('100'::numeric, 'f"\ool"999'); +SELECT to_char('100'::numeric, 'f"\\ool"999'); +SELECT to_char('100'::numeric, 'f"ool\"999'); +SELECT to_char('100'::numeric, 'f"ool\\"999'); + +-- TO_NUMBER() +-- +SET lc_numeric = 'C'; +SELECT to_number('-34,338,492', '99G999G999'); +SELECT to_number('-34,338,492.654,878', '99G999G999D999G999'); +SELECT to_number('<564646.654564>', '999999.999999PR'); +SELECT to_number('0.00001-', '9.999999S'); +SELECT to_number('5.01-', 'FM9.999999S'); +SELECT to_number('5.01-', 'FM9.999999MI'); +SELECT to_number('5 4 4 4 4 8 . 7 8', '9 9 9 9 9 9 . 9 9'); +SELECT to_number('.01', 'FM9.99'); +SELECT to_number('.0', '99999999.99999999'); +SELECT to_number('0', '99.99'); +SELECT to_number('.-01', 'S99.99'); +SELECT to_number('.01-', '99.99S'); +SELECT to_number(' . 0 1-', ' 9 9 . 9 9 S'); +SELECT to_number('34,50','999,99'); +SELECT to_number('123,000','999G'); +SELECT to_number('123456','999G999'); +SELECT to_number('$1234.56','L9,999.99'); +SELECT to_number('$1234.56','L99,999.99'); +SELECT to_number('$1,234.56','L99,999.99'); +SELECT to_number('1234.56','L99,999.99'); +SELECT to_number('1,234.56','L99,999.99'); +SELECT to_number('42nd', '99th'); +RESET lc_numeric; + +-- +-- Input syntax +-- + +CREATE TABLE num_input_test (n1 numeric); + +-- good inputs +INSERT INTO num_input_test(n1) VALUES (' 123'); +INSERT INTO num_input_test(n1) VALUES (' 3245874 '); +INSERT INTO num_input_test(n1) VALUES (' -93853'); +INSERT INTO num_input_test(n1) VALUES ('555.50'); +INSERT INTO num_input_test(n1) VALUES ('-555.50'); +INSERT INTO num_input_test(n1) VALUES ('NaN '); +INSERT INTO num_input_test(n1) VALUES (' nan'); +INSERT INTO num_input_test(n1) VALUES (' inf '); +INSERT INTO num_input_test(n1) VALUES (' +inf '); +INSERT INTO num_input_test(n1) VALUES (' -inf '); +INSERT INTO num_input_test(n1) VALUES (' Infinity '); +INSERT INTO num_input_test(n1) VALUES (' +inFinity '); +INSERT INTO num_input_test(n1) VALUES (' -INFINITY '); + +-- bad inputs +INSERT INTO num_input_test(n1) VALUES (' '); +INSERT INTO num_input_test(n1) VALUES (' 1234 %'); +INSERT INTO num_input_test(n1) VALUES ('xyz'); +INSERT INTO num_input_test(n1) VALUES ('- 1234'); +INSERT INTO num_input_test(n1) VALUES ('5 . 0'); +INSERT INTO num_input_test(n1) VALUES ('5. 0 '); +INSERT INTO num_input_test(n1) VALUES (''); +INSERT INTO num_input_test(n1) VALUES (' N aN '); +INSERT INTO num_input_test(n1) VALUES ('+ infinity'); + +SELECT * FROM num_input_test; + +-- +-- Test precision and scale typemods +-- + +CREATE TABLE num_typemod_test ( + millions numeric(3, -6), + thousands numeric(3, -3), + units numeric(3, 0), + thousandths numeric(3, 3), + millionths numeric(3, 6) +); +\d num_typemod_test + +-- rounding of valid inputs +INSERT INTO num_typemod_test VALUES (123456, 123, 0.123, 0.000123, 0.000000123); +INSERT INTO num_typemod_test VALUES (654321, 654, 0.654, 0.000654, 0.000000654); +INSERT INTO num_typemod_test VALUES (2345678, 2345, 2.345, 0.002345, 0.000002345); +INSERT INTO num_typemod_test VALUES (7654321, 7654, 7.654, 0.007654, 0.000007654); +INSERT INTO num_typemod_test VALUES (12345678, 12345, 12.345, 0.012345, 0.000012345); +INSERT INTO num_typemod_test VALUES (87654321, 87654, 87.654, 0.087654, 0.000087654); +INSERT INTO num_typemod_test VALUES (123456789, 123456, 123.456, 0.123456, 0.000123456); +INSERT INTO num_typemod_test VALUES (987654321, 987654, 987.654, 0.987654, 0.000987654); +INSERT INTO num_typemod_test VALUES ('NaN', 'NaN', 'NaN', 'NaN', 'NaN'); + +SELECT scale(millions), * FROM num_typemod_test ORDER BY millions; + +-- invalid inputs +INSERT INTO num_typemod_test (millions) VALUES ('inf'); +INSERT INTO num_typemod_test (millions) VALUES (999500000); +INSERT INTO num_typemod_test (thousands) VALUES (999500); +INSERT INTO num_typemod_test (units) VALUES (999.5); +INSERT INTO num_typemod_test (thousandths) VALUES (0.9995); +INSERT INTO num_typemod_test (millionths) VALUES (0.0009995); + +-- +-- Test some corner cases for multiplication +-- + +select 4790999999999999999999999999999999999999999999999999999999999999999999999999999999999999 * 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; + +select 4789999999999999999999999999999999999999999999999999999999999999999999999999999999999999 * 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; + +select 4770999999999999999999999999999999999999999999999999999999999999999999999999999999999999 * 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; + +select 4769999999999999999999999999999999999999999999999999999999999999999999999999999999999999 * 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; + +select trim_scale((0.1 - 2e-16383) * (0.1 - 3e-16383)); + +-- +-- Test some corner cases for division +-- + +select 999999999999999999999::numeric/1000000000000000000000; +select div(999999999999999999999::numeric,1000000000000000000000); +select mod(999999999999999999999::numeric,1000000000000000000000); +select div(-9999999999999999999999::numeric,1000000000000000000000); +select mod(-9999999999999999999999::numeric,1000000000000000000000); +select div(-9999999999999999999999::numeric,1000000000000000000000)*1000000000000000000000 + mod(-9999999999999999999999::numeric,1000000000000000000000); +select mod (70.0,70) ; +select div (70.0,70) ; +select 70.0 / 70 ; +select 12345678901234567890 % 123; +select 12345678901234567890 / 123; +select div(12345678901234567890, 123); +select div(12345678901234567890, 123) * 123 + 12345678901234567890 % 123; + +-- +-- Test some corner cases for square root +-- + +select sqrt(1.000000000000003::numeric); +select sqrt(1.000000000000004::numeric); +select sqrt(96627521408608.56340355805::numeric); +select sqrt(96627521408608.56340355806::numeric); +select sqrt(515549506212297735.073688290367::numeric); +select sqrt(515549506212297735.073688290368::numeric); +select sqrt(8015491789940783531003294973900306::numeric); +select sqrt(8015491789940783531003294973900307::numeric); + +-- +-- Test code path for raising to integer powers +-- + +select 10.0 ^ -2147483648 as rounds_to_zero; +select 10.0 ^ -2147483647 as rounds_to_zero; +select 10.0 ^ 2147483647 as overflows; +select 117743296169.0 ^ 1000000000 as overflows; + +-- cases that used to return inaccurate results +select 3.789 ^ 21; +select 3.789 ^ 35; +select 1.2 ^ 345; +select 0.12 ^ (-20); +select 1.000000000123 ^ (-2147483648); +select coalesce(nullif(0.9999999999 ^ 23300000000000, 0), 0) as rounds_to_zero; +select round(((1 - 1.500012345678e-1000) ^ 1.45e1003) * 1e1000); + +-- cases that used to error out +select 0.12 ^ (-25); +select 0.5678 ^ (-85); +select coalesce(nullif(0.9999999999 ^ 70000000000000, 0), 0) as underflows; + +-- negative base to integer powers +select (-1.0) ^ 2147483646; +select (-1.0) ^ 2147483647; +select (-1.0) ^ 2147483648; +select (-1.0) ^ 1000000000000000; +select (-1.0) ^ 1000000000000001; + +-- +-- Tests for raising to non-integer powers +-- + +-- special cases +select 0.0 ^ 0.0; +select (-12.34) ^ 0.0; +select 12.34 ^ 0.0; +select 0.0 ^ 12.34; + +-- NaNs +select 'NaN'::numeric ^ 'NaN'::numeric; +select 'NaN'::numeric ^ 0; +select 'NaN'::numeric ^ 1; +select 0 ^ 'NaN'::numeric; +select 1 ^ 'NaN'::numeric; + +-- invalid inputs +select 0.0 ^ (-12.34); +select (-12.34) ^ 1.2; + +-- cases that used to generate inaccurate results +select 32.1 ^ 9.8; +select 32.1 ^ (-9.8); +select 12.3 ^ 45.6; +select 12.3 ^ (-45.6); + +-- big test +select 1.234 ^ 5678; + +-- +-- Tests for EXP() +-- + +-- special cases +select exp(0.0); +select exp(1.0); +select exp(1.0::numeric(71,70)); +select exp('nan'::numeric); +select exp('inf'::numeric); +select exp('-inf'::numeric); +select coalesce(nullif(exp(-5000::numeric), 0), 0) as rounds_to_zero; +select coalesce(nullif(exp(-10000::numeric), 0), 0) as underflows; + +-- cases that used to generate inaccurate results +select exp(32.999); +select exp(-32.999); +select exp(123.456); +select exp(-123.456); + +-- big test +select exp(1234.5678); + +-- +-- Tests for generate_series +-- +select * from generate_series(0.0::numeric, 4.0::numeric); +select * from generate_series(0.1::numeric, 4.0::numeric, 1.3::numeric); +select * from generate_series(4.0::numeric, -1.5::numeric, -2.2::numeric); +-- Trigger errors +select * from generate_series(-100::numeric, 100::numeric, 0::numeric); +select * from generate_series(-100::numeric, 100::numeric, 'nan'::numeric); +select * from generate_series('nan'::numeric, 100::numeric, 10::numeric); +select * from generate_series(0::numeric, 'nan'::numeric, 10::numeric); +select * from generate_series('inf'::numeric, 'inf'::numeric, 10::numeric); +select * from generate_series(0::numeric, 'inf'::numeric, 10::numeric); +select * from generate_series(0::numeric, '42'::numeric, '-inf'::numeric); +-- Checks maximum, output is truncated +select (i / (10::numeric ^ 131071))::numeric(1,0) + from generate_series(6 * (10::numeric ^ 131071), + 9 * (10::numeric ^ 131071), + 10::numeric ^ 131071) as a(i); +-- Check usage with variables +select * from generate_series(1::numeric, 3::numeric) i, generate_series(i,3) j; +select * from generate_series(1::numeric, 3::numeric) i, generate_series(1,i) j; +select * from generate_series(1::numeric, 3::numeric) i, generate_series(1,5,i) j; + +-- +-- Tests for LN() +-- + +-- Invalid inputs +select ln(-12.34); +select ln(0.0); + +-- Some random tests +select ln(1.2345678e-28); +select ln(0.0456789); +select ln(0.349873948359354029493948309745709580730482050975); +select ln(0.99949452); +select ln(1.00049687395); +select ln(1234.567890123456789); +select ln(5.80397490724e5); +select ln(9.342536355e34); + +-- +-- Tests for LOG() (base 10) +-- + +-- invalid inputs +select log(-12.34); +select log(0.0); + +-- some random tests +select log(1.234567e-89); +select log(3.4634998359873254962349856073435545); +select log(9.999999999999999999); +select log(10.00000000000000000); +select log(10.00000000000000001); +select log(590489.45235237); + +-- +-- Tests for LOG() (arbitrary base) +-- + +-- invalid inputs +select log(-12.34, 56.78); +select log(-12.34, -56.78); +select log(12.34, -56.78); +select log(0.0, 12.34); +select log(12.34, 0.0); +select log(1.0, 12.34); + +-- some random tests +select log(1.23e-89, 6.4689e45); +select log(0.99923, 4.58934e34); +select log(1.000016, 8.452010e18); +select log(3.1954752e47, 9.4792021e-73); + +-- +-- Tests for scale() +-- + +select scale(numeric 'NaN'); +select scale(numeric 'inf'); +select scale(NULL::numeric); +select scale(1.12); +select scale(0); +select scale(0.00); +select scale(1.12345); +select scale(110123.12475871856128); +select scale(-1123.12471856128); +select scale(-13.000000000000000); + +-- +-- Tests for min_scale() +-- + +select min_scale(numeric 'NaN') is NULL; -- should be true +select min_scale(numeric 'inf') is NULL; -- should be true +select min_scale(0); -- no digits +select min_scale(0.00); -- no digits again +select min_scale(1.0); -- no scale +select min_scale(1.1); -- scale 1 +select min_scale(1.12); -- scale 2 +select min_scale(1.123); -- scale 3 +select min_scale(1.1234); -- scale 4, filled digit +select min_scale(1.12345); -- scale 5, 2 NDIGITS +select min_scale(1.1000); -- 1 pos in NDIGITS +select min_scale(1e100); -- very big number + +-- +-- Tests for trim_scale() +-- + +select trim_scale(numeric 'NaN'); +select trim_scale(numeric 'inf'); +select trim_scale(1.120); +select trim_scale(0); +select trim_scale(0.00); +select trim_scale(1.1234500); +select trim_scale(110123.12475871856128000); +select trim_scale(-1123.124718561280000000); +select trim_scale(-13.00000000000000000000); +select trim_scale(1e100); + +-- +-- Tests for SUM() +-- + +-- cases that need carry propagation +SELECT SUM(9999::numeric) FROM generate_series(1, 100000); +SELECT SUM((-9999)::numeric) FROM generate_series(1, 100000); + +-- +-- Tests for VARIANCE() +-- +CREATE TABLE num_variance (a numeric); +INSERT INTO num_variance VALUES (0); +INSERT INTO num_variance VALUES (3e-500); +INSERT INTO num_variance VALUES (-3e-500); +INSERT INTO num_variance VALUES (4e-500 - 1e-16383); +INSERT INTO num_variance VALUES (-4e-500 + 1e-16383); + +-- variance is just under 12.5e-1000 and so should round down to 12e-1000 +SELECT trim_scale(variance(a) * 1e1000) FROM num_variance; + +-- check that parallel execution produces the same result +BEGIN; +ALTER TABLE num_variance SET (parallel_workers = 4); +SET LOCAL parallel_setup_cost = 0; +SET LOCAL max_parallel_workers_per_gather = 4; +SELECT trim_scale(variance(a) * 1e1000) FROM num_variance; +ROLLBACK; + +-- case where sum of squares would overflow but variance does not +DELETE FROM num_variance; +INSERT INTO num_variance SELECT 9e131071 + x FROM generate_series(1, 5) x; +SELECT variance(a) FROM num_variance; + +-- check that parallel execution produces the same result +BEGIN; +ALTER TABLE num_variance SET (parallel_workers = 4); +SET LOCAL parallel_setup_cost = 0; +SET LOCAL max_parallel_workers_per_gather = 4; +SELECT variance(a) FROM num_variance; +ROLLBACK; + +DROP TABLE num_variance; + +-- +-- Tests for GCD() +-- +SELECT a, b, gcd(a, b), gcd(a, -b), gcd(-b, a), gcd(-b, -a) +FROM (VALUES (0::numeric, 0::numeric), + (0::numeric, numeric 'NaN'), + (0::numeric, 46375::numeric), + (433125::numeric, 46375::numeric), + (43312.5::numeric, 4637.5::numeric), + (4331.250::numeric, 463.75000::numeric), + ('inf', '0'), + ('inf', '42'), + ('inf', 'inf') + ) AS v(a, b); + +-- +-- Tests for LCM() +-- +SELECT a,b, lcm(a, b), lcm(a, -b), lcm(-b, a), lcm(-b, -a) +FROM (VALUES (0::numeric, 0::numeric), + (0::numeric, numeric 'NaN'), + (0::numeric, 13272::numeric), + (13272::numeric, 13272::numeric), + (423282::numeric, 13272::numeric), + (42328.2::numeric, 1327.2::numeric), + (4232.820::numeric, 132.72000::numeric), + ('inf', '0'), + ('inf', '42'), + ('inf', 'inf') + ) AS v(a, b); + +SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overflow + +-- +-- Tests for factorial +-- +SELECT factorial(4); +SELECT factorial(15); +SELECT factorial(100000); +SELECT factorial(0); +SELECT factorial(-4); + +-- +-- Tests for pg_lsn() +-- +SELECT pg_lsn(23783416::numeric); +SELECT pg_lsn(0::numeric); +SELECT pg_lsn(18446744073709551615::numeric); +SELECT pg_lsn(-1::numeric); +SELECT pg_lsn(18446744073709551616::numeric); +SELECT pg_lsn('NaN'::numeric); diff --git a/src/test/regress/sql/yb.port.numeric_big.sql b/src/test/regress/sql/yb.port.numeric_big.sql new file mode 100644 index 0000000000000..a0219bafc6ec8 --- /dev/null +++ b/src/test/regress/sql/yb.port.numeric_big.sql @@ -0,0 +1,1366 @@ +-- ****************************** +-- * Test suite for the Postgres NUMERIC data type +-- ****************************** + +-- Must drop tables created by short numeric test. +DROP TABLE num_data; +DROP TABLE num_exp_add; +DROP TABLE num_exp_sub; +DROP TABLE num_exp_div; +DROP TABLE num_exp_mul; +DROP TABLE num_exp_sqrt; +DROP TABLE num_exp_ln; +DROP TABLE num_exp_log10; +DROP TABLE num_exp_power_10_ln; +DROP TABLE num_result; + +CREATE TABLE num_data (id int4, val numeric(1000,800)); +CREATE TABLE num_exp_add (id1 int4, id2 int4, expected numeric(1000,800)); +CREATE TABLE num_exp_sub (id1 int4, id2 int4, expected numeric(1000,800)); +CREATE TABLE num_exp_div (id1 int4, id2 int4, expected numeric(1000,800)); +CREATE TABLE num_exp_mul (id1 int4, id2 int4, expected numeric(1000,800)); +CREATE TABLE num_exp_sqrt (id int4, expected numeric(1000,800)); +CREATE TABLE num_exp_ln (id int4, expected numeric(1000,800)); +CREATE TABLE num_exp_log10 (id int4, expected numeric(1000,800)); +CREATE TABLE num_exp_power_10_ln (id int4, expected numeric(1000,800)); + +CREATE TABLE num_result (id1 int4, id2 int4, result numeric(1000,800)); + + +-- ****************************** +-- * The following EXPECTED results are computed by bc(1) +-- * with a scale of 1000 +-- ****************************** + +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_add VALUES (0,0,'0'); +INSERT INTO num_exp_sub VALUES (0,0,'0'); +INSERT INTO num_exp_mul VALUES (0,0,'0'); +INSERT INTO num_exp_div VALUES (0,0,'NaN'); +INSERT INTO num_exp_add VALUES (0,1,'85243.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (0,1,'-85243.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (0,1,'0'); +INSERT INTO num_exp_div VALUES (0,1,'0'); +INSERT INTO num_exp_add VALUES (0,2,'-994877526002806872754342148749241.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (0,2,'994877526002806872754342148749241.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (0,2,'0'); +INSERT INTO num_exp_div VALUES (0,2,'0'); +INSERT INTO num_exp_add VALUES (0,3,'-60302029489319384367663884408085757480.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (0,3,'60302029489319384367663884408085757480.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (0,3,'0'); +INSERT INTO num_exp_div VALUES (0,3,'0'); +INSERT INTO num_exp_add VALUES (0,4,'5329378275943663322215245.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (0,4,'-5329378275943663322215245.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (0,4,'0'); +INSERT INTO num_exp_div VALUES (0,4,'0'); +INSERT INTO num_exp_add VALUES (0,5,'-652755630.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (0,5,'652755630.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (0,5,'0'); +INSERT INTO num_exp_div VALUES (0,5,'0'); +INSERT INTO num_exp_add VALUES (0,6,'.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_sub VALUES (0,6,'-.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_mul VALUES (0,6,'0'); +INSERT INTO num_exp_div VALUES (0,6,'0'); +INSERT INTO num_exp_add VALUES (0,7,'-818934540071845742'); +INSERT INTO num_exp_sub VALUES (0,7,'818934540071845742'); +INSERT INTO num_exp_mul VALUES (0,7,'0'); +INSERT INTO num_exp_div VALUES (0,7,'0'); +INSERT INTO num_exp_add VALUES (0,8,'8496986223.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_sub VALUES (0,8,'-8496986223.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (0,8,'0'); +INSERT INTO num_exp_div VALUES (0,8,'0'); +INSERT INTO num_exp_add VALUES (0,9,'54863480.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (0,9,'-54863480.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (0,9,'0'); +INSERT INTO num_exp_div VALUES (0,9,'0'); +INSERT INTO num_exp_add VALUES (1,0,'85243.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (1,0,'85243.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (1,0,'0'); +INSERT INTO num_exp_div VALUES (1,0,'NaN'); +INSERT INTO num_exp_add VALUES (1,1,'170486.79080049955252152479695727201571965474311716541919780029226071455736587237347615553466832461907447637054203186991790701615551214692555785671028648640897898741246882118067609728317430043806625387779037980513762118868084887015059202190301421555269486602797852927777567694581746398790609996101506730430853942556475840126871131898407356048450541232591147357021858041662012293323494543567675306406079659294204054863522259037763051870433216859794083051717080761509518250300466106939998045710070'); +INSERT INTO num_exp_sub VALUES (1,1,'0'); +INSERT INTO num_exp_mul VALUES (1,1,'7266436459.363324713115467666113895787027372854351303425444968800459979742082292257107107767894843498525848597439323325297125474674300428669958003640228730876886174255457103020291514229439701871032118057857763809224712818579091741996335014138185389554630910658876423205103697147288306070059640369158894028731728589073730895396494400175420670713113234800826523252075036892246807434088405522834549449664122407363485486902219500109237667016524913027290777216477989904700729228025571098410870506256758678625928245828210775042611512394316804583459576285681159178280400209217948833631961377519855502763611693070238579591463373484424582723121059964236704135695706864890193388054537703767833595331866551990460050750959493829603581882430597105627056085260296454181999581594565113210481151487049158699087454047624433576922179904629'); +INSERT INTO num_exp_div VALUES (1,1,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (1,2,'-994877526002806872754342148663997.64812998474240514147207095573950146764154822009863493316394610578375247334825932838513167168342610420582834742950389452212867974756590355021495169819086060202117180229196935525386766373096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (1,2,'994877526002806872754342148834484.43893048429492666626902822775522112238466538551783273345620682034111834572173548391979999630250058057637037929942180153828419189449146140692523818459983958943364062347264545253704196416903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (1,2,'-84806738323879544552397401815149740513.8505875535743013876823142649666132764556588225959336097903898464616542203793600590311980154402068027051522932586050753865288419084437796768749509032177577451738712965496693249429231838833655025794915864261585848007162358912070811805298210095333433397862313304655108809804359760907473898420016370058274978588765092161529583480924554820756527238472641797198545539410039895140087686344382628317530286295498797849942258314364503000942821309916954725689781458590617068629906894951122301020797266469357701283289275708774593896770378558232444454118891917258610753077932026885574920166837998049508644891327208474213193224700658584824407382455480657734911543930195324144216374573825'); +INSERT INTO num_exp_div VALUES (1,2,'-.000000000000000000000000000085682300757901809257711279577127388124986344391495296640171942990079130291883279872719240502687189411421655284515420074848478500192127657883342858267913417679786356766341637336955924836847768457039175660279784295612167899455618405343686908907695358239088351870495830739180518509859269437015797489301844593920484927630172344269378248455657186218762679357609204333669024237648538465053048724383898528808961206696787294681884412485427843796696788390072124570957047672341581447744981862017791206857428430183366004980966398716823512288330174863890117558744630102020144500158878244146399686532935435591262767487823942606452349972401012308378888947381934278131785907155692007064636085000405504866631011593239041758448995933095907216863744502344014999804306234830774259496097549717476344048'); +INSERT INTO num_exp_add VALUES (1,3,'-60302029489319384367663884408085672236.83687099063256754698860828386302509843815398979402006244388708674093244201278399438376682321121138429850885935540924586964982855913223221441591310211730902799041126800414795030815514254713522692405212716783388698431088814919226444677188004928663343696636297536500970117716818423689175692808344185016908913828066250587407384563498516598672584120143890364303296142744031320345312431817858545326010704685255237541162931904446804064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (1,3,'60302029489319384367663884408085842723.62767149018508907178556555587874475318127115521321786273614780129829831438626014991843514783028586066905089122532715288580534070605779007112619958852628801540288008918482404759132944298520148080184250697297150817299173701934285646867489426483932830299434150464278537812298564822479785688909850915447762856384542090714278516461905872647123125352735037721325154184406043613668806975385533851732090363979459292404685190942209855935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (1,3,'-5140349743195574373979577554212527512597024.162480344833040409158673429491690439298506850052285119390701002577176786023622062742050099464897084793357329597395417632908812044304066963549928478520702505283307379218587635434673128958824348493758429380623577527186462464399974242800361134191519694694139153279582776168995426125926314513926640766117733774558011741611075336271613675760116784769700605008122422944290652448956922432960815546502965310676913079866511016221573557684245901002643719965652152439520727383305120298495304784052489867651462175349450610643411043707261107569691076730261762793560088893354750383257372118118753366377402045596735023445172252225346164608897913115394905485106225627590643805003075069931177395059698550161546962768768895596088478488887530518018212441345360153523733317120037436403475909117998647781920105313938836144009539683'); +INSERT INTO num_exp_div VALUES (1,3,'-.000000000000000000000000000000001413607404628860353773457807436398753936801768769045711604884548436548520368932184112069166807060840219636509423284498981041814526856251281381511288768719259120481595036745286884246627534964287523188738499223075292690431699417313258943941279343383979626641848305343592679057491670166887054819766294147341982669243114259272404203080347707713358471397866402657818267495050115642987782080912962056565478445923456884713049272637646637760989004917643369240372476411912794578381690666695711891846833983534126217706309741885844723208036219144146342212915129560758201609824034610223907791643110990898577049488934294259106725414517181607988173722432655731491050637087261030314548853334338835938120502930424813699221083197863303458179445322810087784892821862085562891180364134284641396475'); +INSERT INTO num_exp_add VALUES (1,4,'5329378275943663322300488.64471790965256505869684245785528331091076155554650629138833809683459634328609777839510066435612911583108717191216693735823717997111970662575497378762952496582183738308720094529950793570383580785385569873278068217936841324404119828637880370718028782103860007754579779716996004352284614661690063919125301052941328989181561787543541920734755989452320799185700078241880935083616978140555713297241612718277766918005268951861880490889884082730841740604517529391011862694381726143520658746305661338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (1,4,'-5329378275943663322130001.85391741010004353389988518583956365616764439012730849109607738227723047091262162286043233973705463946054514004224903034208166782419414876904468730122054597840936856190652484801633363526576955397606531892764306099068756437389060626447578949162759295501062154826802212022414257953494004665588557188694447110384853149054690655645134564686305448219729651828678220200218922790293483596988037990835533058983562863141746692824117439019450865871047657552800448629502344444081260036580660700595591338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (1,4,'454294299613767152878025320780.534199313974295807138790763501115780294529340799108297697573066187975311338382917022391830256203305238757334106943821060545424417350991354829668286194840925251162479496893943917530660694097932059166013476064988623431110002057735318529554555260199417935495388243829261809007709919225000608711536928171687251088217591210419208480251102484043683131687013687838713055660405381318396419588727500715930145098362997142075433472039319292466570912777345841400769387321465602989947078951135489852486382469990409873227894248208197179481868230244584527040573428134962626267135732247029762468417273891700661832893497067151409134724061246612631376075173287264787886064622106855886785805818642123776489793586531950438285720668411465570116161790343538663297713926678759640594912243360541590368666922379919514826022141331900181'); +INSERT INTO num_exp_div VALUES (1,4,'.000000000000000000015994998100440878014888861029956505927201309704413242103407885948184870841766875212766910686894450511886242468216220470061916924303252919423028993720180330014505454865704155281502763018913215741264982350384245753394656021401865680441649920273268554396350483440173848850052788410943178207336328451359951614056237100465802151856198860908371340425459435127133071447273887829397881221098443685586506647314622864702873235212396755866459409263439958011711379929751157260020133239574261188528305921244365838405372320186907437842180388704854605498842516581811515413843298370501194935797268161171428747542997504369133579105180311662221854071962295818264211400101689450830279979372422749150894553349570063000769685274875561760334738424509532610467832951796852051505383374693614022043010735004494395190'); +INSERT INTO num_exp_add VALUES (1,5,'-652670387.03916046850422757312745971450663862747133703839829692066597367760104802542475264601221776157515632293978442027199108085723617181683235487266149426304575903892721468296143475297345699313102262188759506518376019936160961709578829069446312051432780603656651983414612264636232727512091101057374054475214114364113300402823059519499217878746766275164739724770556122895799337810694888119810524986616938847385753562624139431982468828696587199570410008890188532132652095915565323400735066310142303225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (1,5,'652840873.82996096805674909792441698652235828221445420381749472095823439215841389779822880154688608619423079931032645214190898787339168396375791272937178074945473802633968350414211085025663129356908887576538544498889782055029046596593888271636613472988050090259449836342389832330814473910881711053475561205644968306669776242949930651397625234795216816397330872127577980937461350104018382663378200293023018506679957617487661691020231880567020416430204091941905612894161614165865789507675064355852373225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (1,5,'-55643106304872.575994253221940844841058071061962511162776681458310912066379595519265546225338405882027547140476045378015935579066580347282075024392379464189067155567624835346798806677988850250198082355055954078446421075165109896091047534711081616362392995575466807084807876544560268050611445006601394735810211678919646667455478469014906335433468365011768049600750224822391684377238242162320161552720449713229523135506671063115436813348612986916614320012995541575293478341408982118538094438068036422562665160411591652618670802973618768526197813319204816293073794413317669922144705633308090832805914096147659820167569140291210526520361556881576175809360614782817717579318298657744021133210954279487777567785280633309576696708168342539425395482429923273623865667723482418178781573723597156804085501875735112311466228778929147929'); +INSERT INTO num_exp_div VALUES (1,5,'-.000130590057635351941758745900947472461593749814351229292370661147301124533787181489468804246182606762727711479707901680546780430454163647774077629503207962424213266902732555945190365467801995495570282501722505521485829885605904543846887348545254658726343578684749830307120625129857380290225370772763609458975555029415082569247186899112975387051141777417911244576134390940441209829852154391377911942082738699481875795620569383196133124499983396562167632007454221121465745085962247988140942672429187053671899537331280701003778040796615094903602095098880716919238394057384949891444700347825726273725378453454782330181608182747900774711384845635284701538541452235224216112380245660177463043471814071809869894647262285332580556739424040615194137651616350340752691170045698234853734471923738591898290468792787543896'); +INSERT INTO num_exp_add VALUES (1,6,'85243.44233732197133191329295927531563604777955507322414928382967007765263923984471408038635831036097817458527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (1,6,'85243.34846317758118961150399799670008360696356209219504851646259063690472663252876207514831001425809630178527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (1,6,'4001.075404054519813215296429095020391062109905613738157927030437221793757373268325953178030040276107574363822832168160758728653712686313134828282109532831190239521843808940611025488601517574653932032236616573457735900045655665690517797280666732780030171712864961531623060353548802466577910774711998056232872212688464691036260746751992072745518373073825852119460094113694393273456369345499434994672730920070410547163082189385645712866100999708173472360864669110044660667614583576570496399103026286828660558854973376227247132815728164629722965145778698957093136175449225024685874279280018547740'); +INSERT INTO num_exp_div VALUES (1,6,'1816120.848909727306817960620941575637231136442992819290405125420545200026620306446043740992108329883383706060582482495616151605111275635501481354526017831484915013545483361715432312183101964395505340188909970344423950565285639911521082834494088840596716495422427543520536844348040681236845850482165744696068209384509064196671206362539077218412355776790921130042376467606683622970728503408501481791356294886150690067651815776445750760428874351556866105285911902433352126498951242195408782804314174041618879250740246352525074791310920062276490422853700893340860452528740673590486626464460321410814395342850270921486724297414692313177440726749004398703147904603937755702369682956482832074779404350351752662820773690162594400557957241676636030332988289683112176900913522668426137377289536793838959751008646843014106876005'); +INSERT INTO num_exp_add VALUES (1,7,'-818934540071760498.60459975022373923760152136399214017262844141729040109985386964272131706381326192223266583769046276181472898406504104649192224392653722107164485675679551050629376558940966195135841284978096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (1,7,'818934540071930985.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (1,7,'-69808760806266041400340.70700818693892852138813934414383886494691670042143650609934777814995087699409404201920249076407981012095999320858479644760715204999741683528746097757549835956359129287002171391961763797857794730120426599135099619822532290339000466211195776337667123320942107370731349851576864242697412616810236323676004067839744992733887503405311090677026008324895177587064547630828026123718296429295638934384446325302964896473296829265805737112709269803814942537657996725913938408781715328945194948010970'); +INSERT INTO num_exp_div VALUES (1,7,'-.000000000000104090609479936344103210175655521317012597986331111866307697262848964666360492361638117930801818899121383806224630563676018240181412174154250663423230239912527388431901852952893943812666142740182651125508583527237123596541789628675379232473721293630968882045044077795828674268595016625198802475186587918019739056755398151182369187670251750080227679555002307777300392769289647975058449905106584837938556260801229545589323224752038795423164214112897202147313792076165011373139219134850954217300915326944185918762838321705825423789073869940092569940135329697980600082436317664012683589681419530904283106912171330819469065141821685734295058255484933744156717782754922568796985634397878149984177882018261742637463462647452140104146195353696596211873925359508622779658904411330975862442989437933211964821'); +INSERT INTO num_exp_add VALUES (1,8,'8497071467.03603749330791582407836434318377133169438097066269854720538319012928851657498035372443556191720308219530866834905045144302106406146277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (1,8,'-8496900980.24523699375539429928140707116805167695126380524350074691312247557192264420150419818976723729812860582476663647913254442686555191453722107164485675679551050629376558940966195135841284978096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_mul VALUES (1,8,'724311956372274.0135050255361637906710330203036651743488213007179039756514944640108625580172737414192938789413338554327986697518463087452612658955180411327002900979574347739956600177846996063741787205122007268468674386396156638261992679442768654367111433834151087792255469957061758837789341439211010331332174981459471333376067541234901538285101103690622656631026001337239036711179989456674399137008584021283568040818388709554256523118702728176420022080138548890713013682480239784198421500241995499841675772793497485550923152267616622892846304530712344886979674416990935007952941652591352603797627920865960622077762568060903908151958000'); +INSERT INTO num_exp_div VALUES (1,8,'.000010032191786198542900505683562217892317481076466949299850809276743457759270150820565375820388277409258249926696079166209409657808406245382887790534127749833677458375931047385994887406206232330491317602830654688957983804698568410728278089250379255157030886262396950539100566975000094268415749476738358914633948867977798590927055566888255636132486899287919515638902721543629183577900872078173883974905921239149419877613723476347774771230668479296621531969573505480695490386225866950545725121902534610730154727385072738079149623798073810167706094070842646222833137345669922898403368997676634709281456818189049718956207208697021706186341405575300648248555331280690778367620868775005181264547924615247991795542738868003191757946979714250339430363902549866892041102771965653407197094250270379367437342632741280710'); +INSERT INTO num_exp_add VALUES (1,9,'54948723.74225051983134098996071145685528795757427462111901537365053896571438476055974853245403475510333627298551845046116291696445177112567064282766115207407461565363967417615506303416694032848457927390574251904212425813072768882213388082765916956736282110801611726537663292922699021333445658549608928179155685881583228490235606377831724593358583903616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (1,9,'-54778236.95145002027881946516375418483956830283115745569981757335827825115701888818627237691936643048426179661497641859124500994829625897874508497095086558766563666622720535497438693688376602804651302002795213923698663694204683995198328880575615535181012624198813873609885725228117274934655048553507421448724831939026752650108735245933317237310133362383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_mul VALUES (1,9,'4676749348240.390309875431213992853550297086049749814750492488995108783145961719774217441193547534210468967573344456866203963659951312519988497979489304488948342258375915152429008993288817366720647491166024151209542534474867042837694499222928509320280684557676243780452100132238968233413333851595648146954975713386711764268506890884764704949969602122157394714663532141060559896359465918874990769222345665160127552795532197771168442486088776803398878354288847069602460071745966589164282641033852314335279121191855487126430176047553895892632834940595958394834437871886013513058514896870683979585091413977173250824451205330441299000850618134248917380244749589254309567551846327349592529960432446947239714236828401206843011440433362544797025114476612133622499094287321570559088587999417440664282418005102546343020409520421747216'); +INSERT INTO num_exp_div VALUES (1,9,'.001553736563217204408368240901181555234014339476186598647410198373122572205209277343865051610898136462487966496673511261433286284257044548634547569923035899634327495195510767312478861719221916387940027268721306540663743713345337497285507595251328382906111997524508729275471287648008479480805967901972481289402930660848950039779707354469389216931774094174326513465502460315792834278614886136688161679443873815113442220055827192996984074129528034845339130162104547166079591654852164993577408422015514100323825529286511720963047269483211930770803479398243069649400360625259869765138545866815758888670363356947311319523139395191102286838888146829667276592755438606664644975648828848738708349790766370694194763606850690923803984129157519048493985198591771429264967247245289970213262206709011468289046840862597010969'); +INSERT INTO num_exp_add VALUES (2,0,'-994877526002806872754342148749241.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (2,0,'-994877526002806872754342148749241.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (2,0,'0'); +INSERT INTO num_exp_div VALUES (2,0,'NaN'); +INSERT INTO num_exp_add VALUES (2,1,'-994877526002806872754342148663997.64812998474240514147207095573950146764154822009863493316394610578375247334825932838513167168342610420582834742950389452212867974756590355021495169819086060202117180229196935525386766373096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (2,1,'-994877526002806872754342148834484.43893048429492666626902822775522112238466538551783273345620682034111834572173548391979999630250058057637037929942180153828419189449146140692523818459983958943364062347264545253704196416903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (2,1,'-84806738323879544552397401815149740513.8505875535743013876823142649666132764556588225959336097903898464616542203793600590311980154402068027051522932586050753865288419084437796768749509032177577451738712965496693249429231838833655025794915864261585848007162358912070811805298210095333433397862313304655108809804359760907473898420016370058274978588765092161529583480924554820756527238472641797198545539410039895140087686344382628317530286295498797849942258314364503000942821309916954725689781458590617068629906894951122301020797266469357701283289275708774593896770378558232444454118891917258610753077932026885574920166837998049508644891327208474213193224700658584824407382455480657734911543930195324144216374573825'); +INSERT INTO num_exp_div VALUES (2,1,'-11671021799770914903865020509.301561107153561058074179843542446420696517132461554451075945807420674211966679216615407057626541711186781735967334896541890595771915856783008831770988426637435694856170266346306640678577376310547806764332837625966429200996250687908930748245035578756314083608655163891041399241377675534416837659335561005203219889972336214863417948542956735403991871098341470996860469878038840964359144637726669728240650066795729910649523281308716277906908340457162235831526838308777581569974551673352306004330423694524256415657620427590352277556907586751621496248973165690360552007637570957980230685679819820147036159174977086193494572117089582758015847544798464543446227632367713941117001423437766840744488426025388612316819120660814681298624293065972395923651314350558006567251033289878238407790871784676348196394482477767774'); +INSERT INTO num_exp_add VALUES (2,2,'-1989755052005613745508684297498482.08706046903733180774109918349472259002621360561646766662015292612487081906999481230493166798592668478219872672892569606041287164205736495714018988279070019145481242576461480779090962790'); +INSERT INTO num_exp_sub VALUES (2,2,'0'); +INSERT INTO num_exp_mul VALUES (2,2,'989781291745465665243281323944996915810556285052564220274237162526.1617859904902612197894543199389468971679632139059029459520163585971122643624316475417489000981872666677202334180945949860058384424993911721081868337499377890298636260338063268639283065887210924895929155083478140340889209440025415565915964293989840603863813531303253038823629712989041722072693449251635519992922148998556112923060331794396659338057474019846675262291146025'); +INSERT INTO num_exp_div VALUES (2,2,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (2,3,'-60303024366845387174536638750234506721.2758014749274942132576365116182462208228193753118527959000939070820507877345194783035668195137119648748792386548310474079340204536236936213411512867171486174240518914767934028451971067161683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (2,3,'60301034611793381560791130065937008239.1887410058901624055165373281235236307966057696953851292799409809571799686645246659986351515277852800926805119259053513475211488115663286642009614039264484259692394657121785950542874788161683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (2,3,'59993133911282372667149627097418449223835595194300848703012380022306762.154418449236691515146061305380465061074531890529497774836941002526095632166401249277270674802626154774328055399254982998368191676630276960361274433270795772477146870294928855773172789856196219950097157391050424577381777627004101100872747943673762087675405200265837631665464736842180920496158545887039337399558993437594084473932658319914390365451919627956823980800124880375978662052111797881386060353490432427832058851094210488804887183034572364751639107535041308434932952695103493677600969712634416241541391613699710826602011076372592299807609658979777598672141389319098817824624950794758296679318319299142035'); +INSERT INTO num_exp_div VALUES (2,3,'.000016498242835741013709859217005931279826178662180173096568520102488480129191427472581644597420895622947234184547373944996197105916093347103336318249582032230903680989710242610024298937774441533502282949127537125997753002819456724709929935850697744632904111143787011103837624936502324835260843148595669524694347566421203164808527739207590986975750648112133699756328511947175496694080071202064255118777680958612315513441989609682655431197367166056616661045712867189326408877133865572680407329449150282415810958772293869902662884761202424695742898573841869524376684740249281181605067345203479719345061595919652192297531638467223956758315591610733251562492794891852151639643060692698365496208796638230566761231611376199140556503620471090364900792180618741355091923808605890415081571900697282725022629812561702118'); +INSERT INTO num_exp_add VALUES (2,4,'-994877520673428596810678826533995.79421257464236160757218576989993781147390382997132644206786872350652200243563770552469933194637146474528320738725486418004701192337175478117026439697031462361180324038544450723753402846519731908503949116978812841497201119103409772457270340059605961197538918709309004130294868847110690336360689446090125918336908930881873778405661757289469281163974774492810850778950071063044769131228124355961427111369335109426492177657001035045332525699055300921341010989742896430768506909949340276549373661076950964959025967328861569387160956730002517417236732463510495205173523163676450203614971844583064927040066684531931069310935516821795449174271052747559395296525950219449541557191520903507653089998307641491381797101485104546410643'); +INSERT INTO num_exp_sub VALUES (2,4,'-994877531332185148698005470964486.29284789439497020016891341359478477855230977564514122455228420261834881663435710678023233603955522003691551934167083188036585971868561017596992548582038556784300918537917030055337559943480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (2,4,'-5302078674303935968062773235453828254014583744527466365136.236414807326868572353809920518232561005161225922028750078608989965741402418802255050636954800114792425419735155504035469350521800895164087027043476055514245942961100610551646034472084954313670284875310691807937254054948742125729353864014122131419164449567115006621212424805182687707372956385102095255735458593389920872596796806885847543910224476727171570873698525606016990229936284811067826588349092841322512643043008589065847223683467371925773023109720951609815041012521485326120380123169545818055967455575736140138663815073081494226676896278654189873597341203197903408668523514375373841493189836809506003729379742035629498519683885268256481104619815130659628225053833297766479068686119691010593208135616363994230674606991733148502293102108193522604968743948323130517040609601859735899914987426089053869350663'); +INSERT INTO num_exp_div VALUES (2,4,'-186677971.517539861245390308778107722315862721823627804195528485535806132067679059453022306691281662574091826898288146790399178357754908901382135796783067563944022498807930452234032896817601590728156392188660701355670595952594500812333935362955625137944589981298793332621503315902294100258945995827423279442031218510259915311555745581797315793010762585658196457363672908315687720174516274528662385172326028870945153551774300419158584379602045442200523311437013776079979639415633358878239012925000523542907592866797199229858272764668664323316251874027468128770456766875866492004650352654523634716923150212263912760225390093339729495231675627059805624175587380165509763048913150826017167286786277908970769297060278191518730887417202276531151575412404467497036737825989088867451153485938272367300939127313445244028528055624'); +INSERT INTO num_exp_add VALUES (2,5,'-994877526002806872754342801504871.47809095279915423939648794226185974985600242391612965412218049794216637114648812993201775787765690351615479957141288239552036371132381627958673244764559862836085530643408020551049895730005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (2,5,'-994877526002806872754341495993610.60896951623817756834461124123286284017021118170033801249797242818270444792350668237291391010826978126604392715751281366489250793073354867755345743514510156309395711933053460228041067059994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (2,5,'649411906691138274293985410502516861224852.2323455192714410716272307781034189160865613770320102043319541634113746032638191509585045862973333645830298922352816245477556264222094036953195419857712804755170632292914187367964994214922001758104594052499795564860466055599417895782179851297585155129541589802249540436678824225950907268084876110445460948679383611117263673106597132046331719468816839434908155684738864149955129235751738204036443603521478609787295079710078973503970964790273461142497259987849074597264522099648376356902360358310245001183020992360260836105404118742418040965190000718736837422434593694808973939805954329718232693154128543253581495885789333274488461716809104532693754070810202831113003978085636579574171344721710232931261731022478029314435363413498991740750878099825781577297965642009156858479681236085226911858782115'); +INSERT INTO num_exp_div VALUES (2,5,'1524119409495532727030986.638577103454261465522025182901477334004986357902177024959076085490119358611626688213654669281670407680244740174673394111775678935383154847014211641601227316639834450258566053805263858706381900273201146454036688771735398324537667996974210741719621449948660517037619359095556637235980122706739013220201060795557114248610410815988952748489854367480813823114296393315170621979351958306734282429929421779129764262568942699813166237466796852578307944635545174715298176546980314973426586923195248536376403319094417073026382024413817222396402299695717290716014320518777088811749776114378145110676170242861393274018655137797545194817703831240390631723050378397773341835222892981773205967439339460305257986693600088957772328044922955990976285151896366292514128607363007421484320868718566256882080399264346243272770200676'); +INSERT INTO num_exp_add VALUES (2,6,'-994877526002806872754342148749240.99659316232359475297606895243958507460511031229368344962653674268847910587702140353344168594152240599109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (2,6,'-994877526002806872754342148749241.09046730671373705476503023105513751542110329332278421699361618343639171319297340877148998204440427879109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (2,6,'-46696638263247522384986521136500.479312417066793299922708112595886608370451213741279484136907754744903470430131032928908162742687359367826808123516519335458861613010646992354378739165872253762686683966945711430182491860196341344982195078000259063231136011430995647812149294224699587849791008794261026932467933475782780'); +INSERT INTO num_exp_div VALUES (2,6,'-21195986018643887410662481595901800.342199657994285865579781485758715114242459388977583220756870314514884887803267837816669111279417861218648323488364513921592045485003563036021370174294475403630933854767386355037781881144701319212711655881277140183173924089814927297045029394618083349813549439341772734606115369911736164723942330187830605893993276674913563980890459604886172701331890746621222114280438198802989678877404376001410627722336243835841751052795437979198996482216031399073597399901975686733315751292369326904428230195579137225651689857057115970784985439417129044974524632220457594191305254649113470116960582543784928547885740020507755033347968928034294570497118410435615856155184563329718831512839630769097935523279881940380220955993456451396417879773380305142918906742431812580562496634831735169817705720949712410595406012323294829461'); +INSERT INTO num_exp_add VALUES (2,7,'-994877526002807691688882220594983.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (2,7,'-994877526002806053819802076903499.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (2,7,'814739569184924399102711674444306584731316176345067.39834031417849342571224916231092924046722938910652929295271097903377854123984307101079073134405782275535446337229706620713104545454319555885847481531722101704765783025789147453570970090'); +INSERT INTO num_exp_div VALUES (2,7,'1214843772391778.127361407585140553741220126410637250571020684739034685508176000812180032686291124045768750332493129822580347351032145964983629059968936201592138368806173099130176852606440296388856520582890650384142745607345709716826703676313341953999327129144154152914234659001555055379537780751567782847296067128932113870102563522810980359433259696591977617184951677390423898232135100000764121508662830515405980450892222598485287609657612482190264517684867291774820716746063133066053446257163185646067618679478975882247893469409405379034723543061767846895135644429012095930584952053545016706315299076691015196261253199176743281648949731423486208098120903720124071047872917636988241710583721537777321338769039241700203546247947405745989053846970910400831817998342969657501678430211657755864160072525313889413731419647001970593'); +INSERT INTO num_exp_add VALUES (2,8,'-994877526002806872754333651763017.40289299098701084219066388457144979069028441485513418625082363021182982914675513019536443438529749838106171095037135009526312783302868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (2,8,'-994877526002806872754350645735464.68416747805032096555043529892327279933592919076133348036932929591304098992323968210956723360062918640113701577855434596514974380902868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (2,8,'-8453460632655529853033389979024265783461224.3195241893307807116624750282852146303290708492834695194274289713076935297734670940696121761483641291930931061232942894577813178566088927221374036301485916497770984757492912292002695944367308880163698595015497307574177176409203214324418237020500352652934909632442547242092296504047310806151851207329042221920888326000'); +INSERT INTO num_exp_div VALUES (2,8,'-117085929036205907700251.219065234073336548829793284434494573185718678644093751558890746941383215425734761534822966779511801033216479269605150574332107020180872343673157350081102818832254463561564431056604957702984438484261858890324442581609284935850435611342611117035589511568432559140282381526487115307554496353616929034919886387903446436924514812698404129456069856633480965357915969548215985452939172313964007318881987188665231550330515412104367728617802960792164260429920719961650164518261501571220901151359208484337831586551714193024143212288426326740373893030225940355268499071669300664200888186064836443459131985786957267268845966279576380786883200277187591448294590370986026461176853573555996139940001165172158855197070946665074838360933025833716166930231164328918316437195201546383664484983447934244744303265471044295601062898'); +INSERT INTO num_exp_add VALUES (2,9,'-994877526002806872754342093885760.69667996446358567630831677089993316481039076439881735980566785462673358516198695146576524119916430759085192883825888457383242076882081857926408611052522393579396644731758241837010163568445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_sub VALUES (2,9,'-994877526002806872754342203612721.39038050457374613143278241259478942521582284121765030681448507149813723390800786083916642678676237719134679789066681148658045087323654637787610377226547625566084597844703238942080799221554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (2,9,'-54582443595378013373024060492546032003692.4875677735896411267274323339692558458420972958075073392126734000341372096298914875892612108329218081214550050039133117695428196702128258481789017059073444323729583900855712795086447886053552786449313809589992185978097430132940882612817775035217244553616977182049775786664446683332098226841743818600819221587510039430478859412452506872131851471967577741190323481953867845129745440745526578327709351120432530702446916035797432129052518980799424635406993848916727957825620638983706180841278402925286540375225365057191075559133035'); +INSERT INTO num_exp_div VALUES (2,9,'-18133693300409132895168796.074616314168631402221003009151140409826855230810646429042722071403306917323628118792142878282108022292754325022530103525285999179488507720688317761243448898240836430183645778132937666952111134601563043980164547020295727057908447220163534134835130866457657964382363853570827467081988390359191484798677813656413640874450449802233520570178139244957518604566383671867773821069602665918688868868894979351219381089954104823746091972754649316823714354000113723793845707472924569647945844436702275724514171940901057842455729977729388911537391920702753167125695758365521631000334183494148229356487592577177344247694925635113222720411958290166668659311154664393442690740373285505786584987609789805525300762074682544164213490532272590665630428583216403362629445153016404037983825555019274338559686335405719430737559715778'); +INSERT INTO num_exp_add VALUES (3,0,'-60302029489319384367663884408085757480.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,0,'-60302029489319384367663884408085757480.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,0,'0'); +INSERT INTO num_exp_div VALUES (3,0,'NaN'); +INSERT INTO num_exp_add VALUES (3,1,'-60302029489319384367663884408085672236.83687099063256754698860828386302509843815398979402006244388708674093244201278399438376682321121138429850885935540924586964982855913223221441591310211730902799041126800414795030815514254713522692405212716783388698431088814919226444677188004928663343696636297536500970117716818423689175692808344185016908913828066250587407384563498516598672584120143890364303296142744031320345312431817858545326010704685255237541162931904446804064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (3,1,'-60302029489319384367663884408085842723.62767149018508907178556555587874475318127115521321786273614780129829831438626014991843514783028586066905089122532715288580534070605779007112619958852628801540288008918482404759132944298520148080184250697297150817299173701934285646867489426483932830299434150464278537812298564822479785688909850915447762856384542090714278516461905872647123125352735037721325154184406043613668806975385533851732090363979459292404685190942209855935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (3,1,'-5140349743195574373979577554212527512597024.162480344833040409158673429491690439298506850052285119390701002577176786023622062742050099464897084793357329597395417632908812044304066963549928478520702505283307379218587635434673128958824348493758429380623577527186462464399974242800361134191519694694139153279582776168995426125926314513926640766117733774558011741611075336271613675760116784769700605008122422944290652448956922432960815546502965310676913079866511016221573557684245901002643719965652152439520727383305120298495304784052489867651462175349450610643411043707261107569691076730261762793560088893354750383257372118118753366377402045596735023445172252225346164608897913115394905485106225627590643805003075069931177395059698550161546962768768895596088478488887530518018212441345360153523733317120037436403475909117998647781920105313938836144009539683'); +INSERT INTO num_exp_div VALUES (3,1,'-707409990019504668223608170643582.082425157530076679823177950190511141917761066423266390864536360056345386873500583953954967225431526056199231768143978526582904071798714789552447782850723926323452633811653766838064983821149041415149067433978085927687765773012158659685363079191901396502099956189371719135315616249471739677995520904113581848295732911534266040260836644379296158092198514963023001686666281725991605685524015227112003429486755206848316731257322742428352116058878710728614841247581716185886403744830796740424927494009978599974431617064012221450054532987372285996679180090592706458366967534834069977644215413076082570497451654516268857039718730203921980307096740864747006176117071983875364434497517026142488015705391255750729200497229031250705777282987863242056223584453312226818451807347197583925624299372040413470456696588043062815'); +INSERT INTO num_exp_add VALUES (3,2,'-60303024366845387174536638750234506721.2758014749274942132576365116182462208228193753118527959000939070820507877345194783035668195137119648748792386548310474079340204536236936213411512867171486174240518914767934028451971067161683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,2,'-60301034611793381560791130065937008239.1887410058901624055165373281235236307966057696953851292799409809571799686645246659986351515277852800926805119259053513475211488115663286642009614039264484259692394657121785950542874788161683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,2,'59993133911282372667149627097418449223835595194300848703012380022306762.154418449236691515146061305380465061074531890529497774836941002526095632166401249277270674802626154774328055399254982998368191676630276960361274433270795772477146870294928855773172789856196219950097157391050424577381777627004101100872747943673762087675405200265837631665464736842180920496158545887039337399558993437594084473932658319914390365451919627956823980800124880375978662052111797881386060353490432427832058851094210488804887183034572364751639107535041308434932952695103493677600969712634416241541391613699710826602011076372592299807609658979777598672141389319098817824624950794758296679318319299142035'); +INSERT INTO num_exp_div VALUES (3,2,'60612.515523995516156897729403721504966784736064970538891936016753206905080265887046037910122269129293912171105589512464185386239562077778499936203155976336284324712221812806801062157592930664021782540155687632208890794166119782594464410498356083266087045927038416810562596141871858142749062925965665039981381277808608946877852933015970874447235220989360704166270479475802673572039541121473138382812420076284458769543418652217394352637294823914346726065145538710933281768776286965107974980550163605068693568717671571780028113969794125200592691656568731359981803586296135840575095063824258761205175762907549288801963550628589530419118771779395037240198270853609924445368393952404606326559485235840170339343865253618184271158932135392539396160392488927771488269959497352568205940636180870805982484030168838833607478593'); +INSERT INTO num_exp_add VALUES (3,3,'-120604058978638768735327768816171514960.4645424808176566187741738397417698516194251450072379251800348880392307563990441443022019710414972449675597505807363987554551692651900222855421126906435970433932913571889719978994845855323367077258946341408053951573026251685351209154467743141259617399607044800077950793001538324616896138171819510046467177021260834130168590102540438924579570947287892808562845032715007493401411940720339239705810106866471452994584812284665666'); +INSERT INTO num_exp_sub VALUES (3,3,'0'); +INSERT INTO num_exp_mul VALUES (3,3,'3636334760530744652235488357607657374520053530993537920755375319352615385278.023608692512217812784472508939511216316773023870624171279878340621219698109986095090336065266376220109007718694455520948311677863167090936408887147442375455695868593092154861636486745490748828207939155392396090682312136290864359484540126174821846208064763823279315343506148025281475729723686566174395516982893064510403581479746673749128344955124070957545815390178764940816628194640888255387443237798761377617383817511745005525149990207764725040109364671749403389999498572538135588695345112358160274671918953118753964073105250116426665508214894805722798842017943220605600452911496071424281587802689830031742105619630787641205011894680546049982654601956546154572720177337696285354350903475239411654436042931409507429892682706228354459580412759920815932840348933425754970917910500027837428631661182510071352138858'); +INSERT INTO num_exp_div VALUES (3,3,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (3,4,'-60302029489314054989387940744763542234.98295358053252401308872309802346144227050959966671157134780970446370197110016237152333448347415674483796371931316021552756816073493808344537122580089676304958104270609762310229182150728136567294798680824019082599362332377530165818229609055765904048195574142709698758095302560470195171027219786996322461803443213101532716728918363951912367135900414238535625075942525108530051828834829820554490477645701692374399416239080329365045332525699055300921341010989742896430768506909949340276549373661076950964959025967328861569387160956730002517417236732463510495205173523163676450203614971844583064927040066684531931069310935516821795449174271052747559395296525950219449541557191520903507653089998307641491381797101485104546410643'); +INSERT INTO num_exp_sub VALUES (3,4,'-60302029489324713745939828071407972725.48158890028513260568545074171830840934891554534052635383222518357552878529888177277886748756734050012959603126757618322788700853025193884017088688974683399381224865109134889560766307825097103477790782590061456916367930139323346273315068375646692125800496305291080749834712822775973790354498408104142209966769395239768969172107040437333428573572464689550003374384624966403962290572373571842567623422963022155546431883766327294954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (3,4,'-321372325955692885069615337209737469749246561535004445508427591.072860243358366933071485495726715620133686420023451450292996945184959542770492705998350644739298629407567812798540119555932604687814429669592481327761428042980782672136901602006622227365754036664912989085940235439697789102358431343119457114603363936544931303133371137532006899162833369543279729021228901466728220729625107362063321334489394782322741444425117731922691457341543446841167138481424319752111748042440994701571955325673470021626946676976482516292402239416632497972073915818846704053624707839813514171497746804751780741682011937606462260710753056669269928580460921188286249923152921382198282201761171043384698319895970192114563900025573490442674225227682235790590616707857188385274186584856872573669591460447105688151281208238908470285147895678001948902280493477604361481216667716971590499226735103039'); +INSERT INTO num_exp_div VALUES (3,4,'-11315021446594.877643290091276308982961654569173523687151347727612592478433578066762912541361898899908505997444632820107356713116459078630334224890355872486337973552333755378190316811715776951317058334754704988120078733912131691682869448731717816749620336196719541702138949084375907248656748314375183301372633028246109596775255074617515860012417935744433243071057057560464360663978361945666099558526069794464437818864063206829678640156992474597480916575712563493776637239091589972373682399519931569163592317107392231951775499293572134702843085474656152913351183535194499521618027894129537558509428098859715020703897463518891082573242502356303078754574312965093639182648263511466558336912294702019648266054331227425119096294871153811412169351624751542166779635702042223762951850816568617453355571302500885410532963789364822647'); +INSERT INTO num_exp_add VALUES (3,5,'-60302029489319384367663884408738513110.66683195868931664491302527038538338065260819361151478340212147889934633981101279593065290940544218360883531149731823374304151252289014494378769385157204705433009477214625880056478643611622410268943757215673170753460135411513114716313801477916713433956086133878890802448531292334570886746283905390661877220497842493537338035961123751393889400517474762491881277080205381424363695095196058838349029211365212855028824622924678684631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (3,5,'-60302029489319384367663884407433001849.79771052212833997386114856935638647096681695139572314177791340913988441658803134837154906163605506135872443908341816501241365674229987734175441883907154998906319658504271319733469814941611260503645706198407368762270127105340397375230875953495882740039984314121888705481484090911598074635434289709802794549714765847764347865064280637851906308955404165593747173246944693509650424312007333558709071857299501674917023499921977975368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (3,5,'39362489275784146262776411377472433635883331946.794473520543457442955620133347015506556162839462623905489255080102447195050109095701660164272430316804466254467810714209179752718730906325952685817112992943656292503112803950215110778476301809440329937774061163668461957943313261962261081942055908935814323069621279128270849852239727888939033546870208376394878842958202403235309372240005941467570230067124830916866857395233038346727879951123599893174252558078732888910139309038957525961212820831321973219557165558911222848692996406741318948607549825343491479728117062814094258484536263158005174429922237853707635743736923521032098496725445243775790161216159399180889906705265012270270348146530113428221072591696851818281866095288773371414866822270689959827332258348570976075184933893434327278299820594014788148344260948638847457822697682605612771344335201258128'); +INSERT INTO num_exp_div VALUES (3,5,'92380711368470856513514428781.033155715252174277753317877861994356621252232374386687048394529670637693505779282500567256835271428113529026462111032257747830329068594622091282098767000694818101994264352932243278144124687156236926607422077479412495979777588932692081795130282128890441931602671468684153168580234070246201722180460130467506344034452687371838907269162119534950946217165384250603250357360223255177692065141037447374172264943732616165429783010079281851748804739433821308362193703012671569249508710820679009084891198169587484117171861141580870066764275087111843275285564262902405980617569581840831518012986031156042600391943605532635833608358301306456966765206853910579231447150839538731157206153540873916893579943906851149770881336811951119112558311734171557608362620988555075663589827484854016702489324791126228380209309587206299'); +INSERT INTO num_exp_add VALUES (3,6,'-60302029489319384367663884408085757480.1853341682137571584926062805631087054017160819890685789064777236456590745415460695320768374693076860837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,6,'-60302029489319384367663884408085757480.2792083126038994602815675591786611462177090630181693462735571643935716818574980747701251335721895588837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,6,'-2830400711649493468815157129316992649.40542786074520931471973065281957756940496588853021620372179463538053123396140685749478530925306163968207226329985017644835203709485594362663495728106061878665324856417118064730721101615473194292620972173690618491026470353143141125614124440035267592258385099934706896692953497971326605145704135723011753705907329979207428661473172503098296622281647255008204864404416199384701720347319806375450632245634238172654086373193251877533131784268854289406126119630708578053354762596511353053106459297339360827562281168219966099848212'); +INSERT INTO num_exp_div VALUES (3,6,'-1284742031601444539630782308463065726620.121021225455596762466053504195700643301310745151565435123335541550963124666304408503436412726848834604336377169205828654564329888653766451656774534718709065521243637375270687684572524302099749018591530352756390467862377335526634920857924031482455373589053524922608255779040656019538392173139295812160325688504210040741075388404155144782519528791757450256668977268409265390016721724966592135644698341754332845002439113523127047593325646484654291494607100188094186116001064043796216982681807318598789324900462932294782971663150070521334398542559480877366424630693734132836518604260869235580641521264976411493166969530737254118968281271908306432918913600567757535151861421384835424322504855607676315840963696944683182767935565256136130185809101891760917733694553800748568697830680328155128016670099315391685422333'); +INSERT INTO num_exp_add VALUES (3,7,'-60302029489319384368482818948157603222.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,7,'-60302029489319384366844949868013911738.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,7,'49383414785234649002982046297226894664526726187218771083.0993243619030008310875293647868815940421844461627295157812843657782639833900543200310573708100000958929315945039020410482966753145208427035917753919085618457760620513481628641658765820294863970581642745379331727722585319163262763708386199720411053619449096019862596221607526610103408936214184850115071874430846697061554769773328338028749631552202705583855831155461651414320570061181212214810086436100771547030013079997847086'); +INSERT INTO num_exp_div VALUES (3,7,'73634737013325927185.787791148221519354461791539553527545166847382784629235192342551464898036004011575416717008403527685470842765455409054592207142526523023201841973047779202013398235864494503216973882479116841765663948294836180515686647139678530220909072497288527276378202532400736141014848907023234659020093073127450778982904578906877634654521825977382116752537063128793631412296206704078569268566614023846282524151679028060869175439188773864994186109445961525301841201265289707928211114515861536069733921800160245586536759625418951427346236213019358749196674633237197452976517130405065120577692737021174118093373953642724512531935525024447977867020930500433287279183436509990047372809400167546185096048971157700858970777301410692908939206693154161335335755844997198191427289546263182822280127912118140820265025555165337881999926'); +INSERT INTO num_exp_add VALUES (3,8,'-60302029489319384367663884399588771256.5916339968771732477072012126949734214868901845505193155307646111690097978112797961939995859130827784737422228762767014427842766445950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,8,'-60302029489319384367663884416582743703.8729084839404833710669726270467964301325349604567186096492702768702209585877643481082023851284144664938175277044596973126708926205950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,8,'-512385513828318260570283740065493064477880918352.732624553690077857674083796435724202494963885926573907185100543184828131859183999195040110586155435203949963570735841632689374488877298209082579317039061893012560130258753218955057387206477423088065663401594359617882154814262843273526859406265633827109554791772242178864873774889091687515990672487380368975556580539271333144212685871370972163560839446696514092637412587953506052848750866803569213269271165856310101244342151576488190595936869490659700946174362872797854591188391982770203203644172999264143929484089237665313698600170041324566984832357000400'); +INSERT INTO num_exp_div VALUES (3,8,'-7096872691348467943606706217.907270287823269424282176534343841939501231816905820949045946136373255017076943323578903040918266385724756894003692978391468202345397178445216069294845721607024056189567609414049207292919519881725733381453217071918292453682942046440563446278374996563501512335133749731529362537349288419883140401056747081065947774593869673146309163791076953204291951821124894409171722911526435445719071769008713367057971351892550570642991097981458696464929009464411568672010548002196406312721789582428747564855324072212842315229302959908665089850886951261233852165624100634055045684536311382452553544676139507899503993644452161529145849579200003677255968757773363970434791501820320494192909660871475590637419913907191608957830524390049664686282439567943053924245852983990958276537000732363895444894582579142752920882750130052682'); +INSERT INTO num_exp_add VALUES (3,9,'-60302029489319384367663884408030893999.8854209703537480818248540990234567956069965340942024890856088355839135538265116174644003927269495876835324407641642359213535695803871472434650475144516723617632059130297610134243891145006222068960999879308472500422640481972089756410157246974765071949782242392661524488959954348903412713930092273629207697480131360047867213863018127928853922173643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (3,9,'-60302029489319384367663884408140620960.5791215104639085369493197407183130560124286109130354360944260524553172025725325268378015783145476572840273098165721628341015996848028750420770651761919246816300854441592109844750954710317145008297946462099581451150385769713261452744310496166494545449824802407416426304041583975713483424241727236417259479541129474082301376239522310995725648773643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (3,9,'-3308379209762459471107480259839508279070920437.883503980178028214343751083865562028455061662673132221930429904398963590401793045470444301883103141901787466923883803951815572606105617157736442670792467625964359169270739534412932791178258858918086886061702512427989129732248215348301444245772127142869263635282888226326427510486246184233225114523636171202034558843515894542952126988613018789833835507734620046994907453602573865012044120483116345444810078666601100257620969379968264504287700045822481492526688635364586344704730579892342786173395802035361824932075736340405960099542224953439044947229246847140957298841482874444906129049023002897135347878048572628834749795298712449864571996898774444932083319581439741625832405434317985988163261591679157437224404970927012111196724239860528859217322132733404472897289'); +INSERT INTO num_exp_div VALUES (3,9,'-1099128766678422054524173986658.839339966689456265703816212189145237878729886466041806078542573981227645802109969871638687985985845489422516004202630099080709709893022100481258818112345013009059633421290241583864468453396484606925071369550998772875840640325758308835852391176503689677263605949075815552026731067384737231681068134099746550363063940273625924224721503126912810251607546172009765059506591787282558727077669973711491157840340631805422942099954647016059576777054339588421998882440726473698513560202030309804089250300097589174314677765341104767702983421063649104691583044460507666600260994707192787133590502137391691330098102374713996115782701417107878938473243874299874872852713499024851414757892169376458916467621226859152075901273014182163212783658933754507272478777304254191033562324994395916168496097385872331012258027431094381'); +INSERT INTO num_exp_add VALUES (4,0,'5329378275943663322215245.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,0,'5329378275943663322215245.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,0,'0'); +INSERT INTO num_exp_div VALUES (4,0,'NaN'); +INSERT INTO num_exp_add VALUES (4,1,'5329378275943663322300488.64471790965256505869684245785528331091076155554650629138833809683459634328609777839510066435612911583108717191216693735823717997111970662575497378762952496582183738308720094529950793570383580785385569873278068217936841324404119828637880370718028782103860007754579779716996004352284614661690063919125301052941328989181561787543541920734755989452320799185700078241880935083616978140555713297241612718277766918005268951861880490889884082730841740604517529391011862694381726143520658746305661338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,1,'5329378275943663322130001.85391741010004353389988518583956365616764439012730849109607738227723047091262162286043233973705463946054514004224903034208166782419414876904468730122054597840936856190652484801633363526576955397606531892764306099068756437389060626447578949162759295501062154826802212022414257953494004665588557188694447110384853149054690655645134564686305448219729651828678220200218922790293483596988037990835533058983562863141746692824117439019450865871047657552800448629502344444081260036580660700595591338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,1,'454294299613767152878025320780.534199313974295807138790763501115780294529340799108297697573066187975311338382917022391830256203305238757334106943821060545424417350991354829668286194840925251162479496893943917530660694097932059166013476064988623431110002057735318529554555260199417935495388243829261809007709919225000608711536928171687251088217591210419208480251102484043683131687013687838713055660405381318396419588727500715930145098362997142075433472039319292466570912777345841400769387321465602989947078951135489852486382469990409873227894248208197179481868230244584527040573428134962626267135732247029762468417273891700661832893497067151409134724061246612631376075173287264787886064622106855886785805818642123776489793586531950438285720668411465570116161790343538663297713926678759640594912243360541590368666922379919514826022141331900181'); +INSERT INTO num_exp_div VALUES (4,1,'62519544780217042176.800424689664850775296526267109332647921183817056683200043718160298562843864918741523494444361916531159341418970534833628106062976341639276761669219281771109561175175033739624472497927501467465456946098280878993371659461957361369508794842102784763955539708800574418468150309301129490186416766691183270872711413796386178009615777589066235359283212636467980113350635181915492452697347977967985810294150853782607014649150457138118264698071689065469752702524632313088938504181640435324554007553994564705401249228914199354821595855823113730697333390936834057091883654016371107974899726642500486005445063301647520527084320363513388355471718583708935211830796440056542408492723718088396437530207347815505844074508948817594746824098278470533148171941442049323578854023683167934569551595335539887777638716651319134577441'); +INSERT INTO num_exp_add VALUES (4,2,'-994877520673428596810678826533995.79421257464236160757218576989993781147390382997132644206786872350652200243563770552469933194637146474528320738725486418004701192337175478117026439697031462361180324038544450723753402846519731908503949116978812841497201119103409772457270340059605961197538918709309004130294868847110690336360689446090125918336908930881873778405661757289469281163974774492810850778950071063044769131228124355961427111369335109426492177657001035045332525699055300921341010989742896430768506909949340276549373661076950964959025967328861569387160956730002517417236732463510495205173523163676450203614971844583064927040066684531931069310935516821795449174271052747559395296525950219449541557191520903507653089998307641491381797101485104546410643'); +INSERT INTO num_exp_sub VALUES (4,2,'994877531332185148698005470964486.29284789439497020016891341359478477855230977564514122455228420261834881663435710678023233603955522003691551934167083188036585971868561017596992548582038556784300918537917030055337559943480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,2,'-5302078674303935968062773235453828254014583744527466365136.236414807326868572353809920518232561005161225922028750078608989965741402418802255050636954800114792425419735155504035469350521800895164087027043476055514245942961100610551646034472084954313670284875310691807937254054948742125729353864014122131419164449567115006621212424805182687707372956385102095255735458593389920872596796806885847543910224476727171570873698525606016990229936284811067826588349092841322512643043008589065847223683467371925773023109720951609815041012521485326120380123169545818055967455575736140138663815073081494226676896278654189873597341203197903408668523514375373841493189836809506003729379742035629498519683885268256481104619815130659628225053833297766479068686119691010593208135616363994230674606991733148502293102108193522604968743948323130517040609601859735899914987426089053869350663'); +INSERT INTO num_exp_div VALUES (4,2,'-.000000005356818439105666775800262590702859770599410113087721172791624002387236505438218124867814437523686300450045582100868990117124343222534568799037421944272316277130975314766456260710406160143182498931595199129228915695802952695510723443157825968340043198200740606202264287904755124946591110599335909404657109057432686191440989434662797205973563889238804413861126260401987949920244286377128599413927273444061572120561496904543200956508673923547626768641271397088562966176629018606103663605145666976048261236691866387601532424530473754175270500777679603569715192364542901360534980926452487443629100484491344001509360344122933911316486556042277769848194790964257060927912344609376571637126617813506411190014141992988288983968823792971270853369317867326071952900448455162898476163801382836761898292684175721846'); +INSERT INTO num_exp_add VALUES (4,3,'-60302029489314054989387940744763542234.98295358053252401308872309802346144227050959966671157134780970446370197110016237152333448347415674483796371931316021552756816073493808344537122580089676304958104270609762310229182150728136567294798680824019082599362332377530165818229609055765904048195574142709698758095302560470195171027219786996322461803443213101532716728918363951912367135900414238535625075942525108530051828834829820554490477645701692374399416239080329365045332525699055300921341010989742896430768506909949340276549373661076950964959025967328861569387160956730002517417236732463510495205173523163676450203614971844583064927040066684531931069310935516821795449174271052747559395296525950219449541557191520903507653089998307641491381797101485104546410643'); +INSERT INTO num_exp_sub VALUES (4,3,'60302029489324713745939828071407972725.48158890028513260568545074171830840934891554534052635383222518357552878529888177277886748756734050012959603126757618322788700853025193884017088688974683399381224865109134889560766307825097103477790782590061456916367930139323346273315068375646692125800496305291080749834712822775973790354498408104142209966769395239768969172107040437333428573572464689550003374384624966403962290572373571842567623422963022155546431883766327294954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,3,'-321372325955692885069615337209737469749246561535004445508427591.072860243358366933071485495726715620133686420023451450292996945184959542770492705998350644739298629407567812798540119555932604687814429669592481327761428042980782672136901602006622227365754036664912989085940235439697789102358431343119457114603363936544931303133371137532006899162833369543279729021228901466728220729625107362063321334489394782322741444425117731922691457341543446841167138481424319752111748042440994701571955325673470021626946676976482516292402239416632497972073915818846704053624707839813514171497746804751780741682011937606462260710753056669269928580460921188286249923152921382198282201761171043384698319895970192114563900025573490442674225227682235790590616707857188385274186584856872573669591460447105688151281208238908470285147895678001948902280493477604361481216667716971590499226735103039'); +INSERT INTO num_exp_div VALUES (4,3,'-.000000000000088378091435340426596348183959201660680284222502095357746364378698792730669202270228092348823133529449019715406417264278615046537007844589547485282959556860316942508808911542109265489435572674031608663747132688980867386885961271358592278360097086532747883342438036287136994589308551796702164612609710942175900921197001888540314760352113821737014875886635147123114456910985089625906448913621495025509697742196814421833448856595853403450682101743559369637786458968714240975228615283970739279506239628546165569688434254286341567486905374255702980370754235630955328837646999003123103831262789115646588779721625156078607919060762857866951417867378220773543985422722165221371084387943737083254760594128718841665355053236168688218864433967871311858292181233490194833547273501436630325295640020916257836404'); +INSERT INTO num_exp_add VALUES (4,4,'10658756551887326644430490.49863531975260859259672764369484696707840594567381478248441547911182681419871940125553300409318375529163231195441596770031884779531385539479966108885007094423120594499372579331584157096960536182992101766042374317005597761793180455085459319880788077604922162581381991739410262305778619327278621107819748163326182138236252443188676485421061437672050451014378298442099857873910461737543751288077145777261329781147015644685997929909334948601889398157317978020514207138462986180101319446901252677846098070081948065342276861225678086539994965165526535072979009589652953672647099592770056310833870145919866630936137861378128966356409101651457894504881209406948099561100916885616958192984693820003384717017236405797029790907178714'); +INSERT INTO num_exp_sub VALUES (4,4,'0'); +INSERT INTO num_exp_mul VALUES (4,4,'28402272808100253242547006276715304015308580784958.804614276533085644370816876160290159450291717634111299841065255625515058118012211808741402904995080624675460593676923639082981788732031193774047612589113654423166826140872334380708795266307037944059108148612979119729408762532396036043629484049508789880964586236575769826806092391573178899640321403656891487586452524427223891405519836671312830183895761747460911777623703557946796784873885800089025388390522992806365773290733075927321101736155663727528284512100509273076328103465333687228713897893434161293693971954442699482857938492961830350598789444266860160794913830991304996676299650460125000959751177037694425217989910261807246272771711816326991282202653917488360776928533800529297474279497910326579608191975246060946079639658615178160271122713225105861574160788280907842327681375920919676063500116492292319'); +INSERT INTO num_exp_div VALUES (4,4,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (4,5,'5329378275943662669459614.81475694159581596077242547133292502869630735172901157043010370467618244548786897684821457816189831652076071977025794948484549600736179389638319303817478693948215387894509009504287664213474693208847025374388286162907794727810231557001266897729978691844410171412189947386181530441402903608214502713480332746271552746231631136145916685939539173054989927058122097304419584979598595477177513004218594211597809300517607260841648610322863666300637648662611916496850248528515936635845594390453288113296413254893687029540384176335735114863908372780241463999450547422213639667099644505472777149095004849805371205203850993689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,5,'5329378275943663974970875.68387837815679263182430217236192193838209859394480321205431177443564436871085042440731842593128543877087159218415801821547335178795206149841646805067528400474905206604863569827296492883485842974145076391654088154097803033982948898084192422150809385760511991169192044353228731864375715719064118394339415417054629392004621307042759799481522264617060523956256201137680272894311866260366238283858551565663520480629408383844349319586471282301251749494706061523663958609947049544255725056447964564549684815188261035801892684889942971676086592385285071073528462167439314005547455087297279161738865296114495425732286867689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,5,'-3478781676337858247983014311182511.567538638808357215203593479841446379226774481291286361639429856698999485760647422501864626078375852610019829111004807806660731243672830787729048847342063218718651165150612717759770504648306347926061960607388621011846314969634048226452709389995594961695723139571002939804473057725442880410434039783304583526414509590532906062732322732569475349107437896717416548237633532805602064623969799081086996320156575550896200848758685986331692388099427314008504506503745527468550106879602399030419569897808150076298414568875477195447656904373310322813412927463518325927626891046356679526447117311923853482118502868148386882363449163182892615259995945992014431502761210899772725227648729095696228388558331052524469604046072203605897109629560683446827492904111565278516043939137760721315953500281379039771826554155511347152'); +INSERT INTO num_exp_div VALUES (4,5,'-8164430956184510.184223536017248184022252663660196916321116266103608317725855237211273642694947892658721606226082017525816544904635887836163201565923338826779819876742736219975639586566502584026349778499211535661173597356253186281116862244165796632756909578140184577853088376334255860281874385669242675881761388233070861374295536603371778669602656670852115614651462552069294889723058758969660566508798011830996965570446030123780674316363670374970480994905368006454513642480180066435609577311074332150098288374616437489163254821095377348025470309665651059603665062887597814064136313866690824972464351274062540825405003954064175728198182815347642172934453828192850870808373638597839434504241236228591053696481146252072190903430582534862988719805163692697482513169856291048966811374872266165034373412719593685881972700171726777938'); +INSERT INTO num_exp_add VALUES (4,6,'5329378275943663322215245.29625473207137544719284446115519970394719946335145777492574745992986971075733570324679065009803281404581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,6,'5329378275943663322215245.20238058768123314540388318253964726313120648232235700755866801918195710344138369800874235399515094124581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,6,'250145412892811547138949.592621291590152419206270097656346630226508074074623894951308487425470437268130465956063593951784820669318897182831355375451719125809800516979013437732298382708070979871283132689492336823087794373113039154669229889503700598930220858275174342776478898670277868700384853696009897221747924643343353942154528501454689084608965009561564638167714973711022212547096732831847202912862290958304510651828842182545311077713664465815992616213663619529378061133917572474298028065850515876361609671565914027186063801852554353160801534696062207299890867876199323530337336273950892723090754719547285920090419070001019943385293110663922226230169381423410428577990604776655422105400452217085311617728003688836185608912367677734364834577573255789160419371322775733777518997638403409000055707558465286469808848200141192627396502735'); +INSERT INTO num_exp_div VALUES (4,6,'113543048739697485358574290.758354267447744932153707340542459183720907885610125346262898114677742971240785031722334497858930434531517077525413654346644836353208132641713415396062580605566225794048569430676355036264762949452090151450855446984773994337170590068740235544320694721909983307239491151139099779296496785240814600627140543144068640768857707110930453204162312973998304574796413938461971472337040811785231390930046688391955000749644938061585377150632133417156866197053052425576957646564943278156977176976876921235395711611898108821587442609611001702344783440618040704066809035404237786023075676374788819144406909313755996914145273176359246052899650387182222905558751208368173052381982668563471143298720677965028880626152749773712037769548408324298835212547215352657271696665387200792785056233953536347605130973626194099064678842085'); +INSERT INTO num_exp_add VALUES (4,7,'5329377457009123250369503.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,7,'5329379094878203394060987.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,7,'-4364411947278810125327066890819882483326918.05664098958260550284395870948992407314161088028674246708928421994893923699743452802989464864039994566042797942433140378990308345483670828497915478397481687305406460330009319949623844175096007381662809083363069100235985794575399268709260901964834244796150883807308976949196661411035264619638771824190014274817662519438658481432363824187693821267613212631153175155634316128036152465184903927860719447693468054624663668062006049759837326188252927823612718163916100588143128358998656306593393889422386501730237442526450419990376323903182669190482615734972147533221144682538647497701130447816148459762464395194383090936159579764712919396391813914821973715879062992249315474841639591907249142779103650773383644785606333916967894'); +INSERT INTO num_exp_div VALUES (4,7,'-6507697.520580964829176145824902679560705744817573189143227837387224410616222039115571544850095278317993922427931439719549137387753697989249394347047436951117850128104928719365703899136632100669607126357491484781141296021264049762417528697619931558728863308905257358126654378784709213859234056696519305650316810797382293500878834933984458810656133463638442959750083607649924453935287420620424368291770694630751828333903156364366745210911640207075765008558904788350844410055253643515389003711759818446776538393914018427075074171758415188027562645239606914126802490579848138218395145734902830046359100742374008993296019987093605275289913663224324033923096998194326249508491872193747944673057257521552387923218450155737056841633810711295424578984452176016198348344913655301417872189073133147510027427530833694019910340299'); +INSERT INTO num_exp_add VALUES (4,8,'5329378275943671819201468.88995490340795935797824952902333498786202536079000703830146057240651898748760197658486790165425772165585380839129948178510273188565692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,8,'5329378275943654825229021.60868041634464923461847811467151197921638058488380774418295490670530782671111742467066510243892603363577850356311648591521611590965692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,8,'45283653791262997781451381354094822.762732909505051438036873220502792213670540454778361182993875916509061144859281577740137081988678361247725064336120451090222456518107029158304937620179032477664627949959143233370320432203497828243297406462513350790251761540074946469824444452248386782451723637769289822576372357189700319768797708375563651655860093365309717823602754924352327588945034832436331911584742966378275504545736896430718939807674966738116698454215555860047859161126694019895490767779791933882712567492115664113775047192011252893773389940988533801360010782816196288710063568554147458866942816721046004257953642508395867837127678980002737669139369781058046396738606563716339660654364541530532834806205571191828994250708412638796240377704994928921528330863683630622922959130920715261879547446054261914770022377059156125037157979236658010950'); +INSERT INTO num_exp_div VALUES (4,8,'627208063620965.397582272040628872773601055303353339700043792111288801181637510303989399395425313995651311362368773096988861977687484912995632130587762386590996099363383976320342247076516604162469063709298438133327434461462906199160715395064249299615054970359309619951777972710299484596875999967582794277241285253106817446259313281064844416249524876385699646393555435017820686376877981018047574348711991428666249794623006175739581915209218834701034964043360823844816042368184094857692062884223864639972005010863342567608351008172649209459933114800143792514183138995700133608613158857147417653998048890116531052767737435620558349226865105888201598712435680481803901906613772821370519525404423549161696526405320391828194356063547089626322474164332505209233143121068245585662919687001395119229263995765376465304715643388771609446'); +INSERT INTO num_exp_add VALUES (4,9,'5329378275943663377078725.59616792993138452386059664269485161374191901124632386474661634799161523147237015531446709484039091244606359050341194730653343894986479159670583937529516163204904273806158788218327396375034882788180783796976731912141525319602448709213495905899041406302673881364465504945113279286939663215197485367850132991968081639290297033476859158044889351836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (4,9,'5329378275943663267351764.90246738982122406873613100099999535333648693442749091773779913112021158272634924594106590925279284284556872145100402039378540884544906379809382171355490931218216320693213791113256760721925653394811317969065642404864072442190731745871963413981746671302248281216916486794296983018838956112081135739969615171358100498945955409711817327376172085836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (4,9,'292388240303165948041827159734686.255558469787242316676287235194652580157149226950109397295920730296960145548003120827363226435916209781396711693581454960342091452830648929118261388933297036933167543189308061917640517578583521401267417187854611829815212778183983326568586118831109538377828156118900313778053576483381085207892754728937946691892849474364477434665960112125254104966566712906532318984871145605839506991591027939136026602051635433295687547552796828217859648186757719639965988287173297286034098497871707197092627676226053609131138590878743560287292934815277894463305001278326023708395571840850120055316276256138004565442099731931051413153564744766098053176049414330146267604802971221161572130161432525297614616942172815141372973870720928125699420370428856022295499447755488148545048400795053604349570217878099721865670458104653570360'); +INSERT INTO num_exp_div VALUES (4,9,'97138902640718538.241246716463110895614166618530828908023040947887095196830690221211560526562522274118188963051412359798837957512805692731972838989047910709158995922699598619854907969493232150042212406549916252602794415099066259707018021422154933830674786488990033885447289593742424717170197810316367637885248684134204152352748803532396210051700193575105804898183523770153431536054848843504020390623875664696278263569145547515663340450903772852615789980257449146000410036925975898331113013857953289990299253584950458042598491897496393582249411290555264437893099880371008957017323366523688894303458743415715114628052487518110654201696604914159777300997374156315186315524817636714210119873791848535246674326877611945112249137224923201544452904111118569299934059002046318394345055859769572070097973298522564724884895879226870720839'); +INSERT INTO num_exp_add VALUES (5,0,'-652755630.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,0,'-652755630.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (5,0,'0'); +INSERT INTO num_exp_div VALUES (5,0,'NaN'); +INSERT INTO num_exp_add VALUES (5,1,'-652670387.03916046850422757312745971450663862747133703839829692066597367760104802542475264601221776157515632293978442027199108085723617181683235487266149426304575903892721468296143475297345699313102262188759506518376019936160961709578829069446312051432780603656651983414612264636232727512091101057374054475214114364113300402823059519499217878746766275164739724770556122895799337810694888119810524986616938847385753562624139431982468828696587199570410008890188532132652095915565323400735066310142303225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,1,'-652840873.82996096805674909792441698652235828221445420381749472095823439215841389779822880154688608619423079931032645214190898787339168396375791272937178074945473802633968350414211085025663129356908887576538544498889782055029046596593888271636613472988050090259449836342389832330814473910881711053475561205644968306669776242949930651397625234795216816397330872127577980937461350104018382663378200293023018506679957617487661691020231880567020416430204091941905612894161614165865789507675064355852373225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (5,1,'-55643106304872.575994253221940844841058071061962511162776681458310912066379595519265546225338405882027547140476045378015935579066580347282075024392379464189067155567624835346798806677988850250198082355055954078446421075165109896091047534711081616362392995575466807084807876544560268050611445006601394735810211678919646667455478469014906335433468365011768049600750224822391684377238242162320161552720449713229523135506671063115436813348612986916614320012995541575293478341408982118538094438068036422562665160411591652618670802973618768526197813319204816293073794413317669922144705633308090832805914096147659820167569140291210526520361556881576175809360614782817717579318298657744021133210954279487777567785280633309576696708168342539425395482429923273623865667723482418178781573723597156804085501875735112311466228778929147929'); +INSERT INTO num_exp_div VALUES (5,1,'-7657.550797567691019915353529993301413746369700087741672762343206271266232635965032053368224472333368713006346867984576168784127503674579531243603836945595880917241997606783133673324236134063757452734295148763280059050480246827193380861494669624151921824660313516974440913733511526807313019192263170823268678149435664224184903925632177789052038092611394447709922076676981043877747276056677801802695466205531230350209787298926245402046182150996849906836743231861317120171583577624262765589605263477198809166390259128339127005924586833372241946051704497188891325715185091060185547236923494393813210904033520844572880475265306843414506359253445517738473745552980984097762509546161690823646176501838559393690565709795724159196133663168004773260451322595899506776323262195323943138344537866088159583331807728944620284996'); +INSERT INTO num_exp_add VALUES (5,2,'-994877526002806872754342801504871.47809095279915423939648794226185974985600242391612965412218049794216637114648812993201775787765690351615479957141288239552036371132381627958673244764559862836085530643408020551049895730005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,2,'994877526002806872754341495993610.60896951623817756834461124123286284017021118170033801249797242818270444792350668237291391010826978126604392715751281366489250793073354867755345743514510156309395711933053460228041067059994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (5,2,'649411906691138274293985410502516861224852.2323455192714410716272307781034189160865613770320102043319541634113746032638191509585045862973333645830298922352816245477556264222094036953195419857712804755170632292914187367964994214922001758104594052499795564860466055599417895782179851297585155129541589802249540436678824225950907268084876110445460948679383611117263673106597132046331719468816839434908155684738864149955129235751738204036443603521478609787295079710078973503970964790273461142497259987849074597264522099648376356902360358310245001183020992360260836105404118742418040965190000718736837422434593694808973939805954329718232693154128543253581495885789333274488461716809104532693754070810202831113003978085636579574171344721710232931261731022478029314435363413498991740750878099825781577297965642009156858479681236085226911858782115'); +INSERT INTO num_exp_div VALUES (5,2,'.000000000000000000000000656116570506105776235076334177868550033347254561166417969910286926369599900073757929714260350320362090452092025380232792749476245042480546813848702351830607516880397305138543526307608094143028291193163613755680419049060162928958489964834941920423432354996040147818253087783193280640282263490705632002572757216731766513434035163528102590524432221718194164133959630768718395847710529339782880381264265894322494716854757290930538739000043383104085867828258790010654331660516512156519838978751447311068903958136482041673109857552178367614498426226323001399275980281507353231821022591045797658991388304873240910526149138339658220844723880158150606035181559877351791752701872877147074033569061408920725522180134133183999181370354585872214368766629114773129541658653693832843354053701079334077'); +INSERT INTO num_exp_add VALUES (5,3,'-60302029489319384367663884408738513110.66683195868931664491302527038538338065260819361151478340212147889934633981101279593065290940544218360883531149731823374304151252289014494378769385157204705433009477214625880056478643611622410268943757215673170753460135411513114716313801477916713433956086133878890802448531292334570886746283905390661877220497842493537338035961123751393889400517474762491881277080205381424363695095196058838349029211365212855028824622924678684631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,3,'60302029489319384367663884407433001849.79771052212833997386114856935638647096681695139572314177791340913988441658803134837154906163605506135872443908341816501241365674229987734175441883907154998906319658504271319733469814941611260503645706198407368762270127105340397375230875953495882740039984314121888705481484090911598074635434289709802794549714765847764347865064280637851906308955404165593747173246944693509650424312007333558709071857299501674917023499921977975368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (5,3,'39362489275784146262776411377472433635883331946.794473520543457442955620133347015506556162839462623905489255080102447195050109095701660164272430316804466254467810714209179752718730906325952685817112992943656292503112803950215110778476301809440329937774061163668461957943313261962261081942055908935814323069621279128270849852239727888939033546870208376394878842958202403235309372240005941467570230067124830916866857395233038346727879951123599893174252558078732888910139309038957525961212820831321973219557165558911222848692996406741318948607549825343491479728117062814094258484536263158005174429922237853707635743736923521032098496725445243775790161216159399180889906705265012270270348146530113428221072591696851818281866095288773371414866822270689959827332258348570976075184933893434327278299820594014788148344260948638847457822697682605612771344335201258128'); +INSERT INTO num_exp_div VALUES (5,3,'.000000000000000000000000000010824770508763323320533297369674519056450544793568147911931789010432012750062661590994728968589403602468229106206242395792957238667714358401601098858606386995096923432407249369639633268143022787987190106724545750803196130511146323174462918572423414631798141263222875752767731279138952850500369328934959764805948568471324562210715908420467881411844098258193571194910997918428786213948547748701831331312040839544355427357749520227124858111324859160114175254197992204974033767300989488517391063188153561391320190653403747521648794370679322504188364455328709488846777004202196382575648619395139553279192346251133156445942281048959845827006761160755031086836046398020850814350246219929303018051720203943879538087954853996826539712240458022307680912400297508925714946398031304516583939283'); +INSERT INTO num_exp_add VALUES (5,4,'5329378275943662669459614.81475694159581596077242547133292502869630735172901157043010370467618244548786897684821457816189831652076071977025794948484549600736179389638319303817478693948215387894509009504287664213474693208847025374388286162907794727810231557001266897729978691844410171412189947386181530441402903608214502713480332746271552746231631136145916685939539173054989927058122097304419584979598595477177513004218594211597809300517607260841648610322863666300637648662611916496850248528515936635845594390453288113296413254893687029540384176335735114863908372780241463999450547422213639667099644505472777149095004849805371205203850993689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (5,4,'-5329378275943663974970875.68387837815679263182430217236192193838209859394480321205431177443564436871085042440731842593128543877087159218415801821547335178795206149841646805067528400474905206604863569827296492883485842974145076391654088154097803033982948898084192422150809385760511991169192044353228731864375715719064118394339415417054629392004621307042759799481522264617060523956256201137680272894311866260366238283858551565663520480629408383844349319586471282301251749494706061523663958609947049544255725056447964564549684815188261035801892684889942971676086592385285071073528462167439314005547455087297279161738865296114495425732286867689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (5,4,'-3478781676337858247983014311182511.567538638808357215203593479841446379226774481291286361639429856698999485760647422501864626078375852610019829111004807806660731243672830787729048847342063218718651165150612717759770504648306347926061960607388621011846314969634048226452709389995594961695723139571002939804473057725442880410434039783304583526414509590532906062732322732569475349107437896717416548237633532805602064623969799081086996320156575550896200848758685986331692388099427314008504506503745527468550106879602399030419569897808150076298414568875477195447656904373310322813412927463518325927626891046356679526447117311923853482118502868148386882363449163182892615259995945992014431502761210899772725227648729095696228388558331052524469604046072203605897109629560683446827492904111565278516043939137760721315953500281379039771826554155511347152'); +INSERT INTO num_exp_div VALUES (5,4,'-.000000000000000122482510461124748279475400009367345900846466958806966807399903713411658400733717078392550780910604704603123670767210550800752620037863340961255721285160854785449315208955654408132775022766783343331151895973970395232686910362226184006990485313002943710214511418310741271074710741339586430026286272098156531835438969774325517509155992092194349661122678547097423264670055720422496527272118788005921590521726691666219504214087867030003203385360001614199656989667055583749577099440092378355805901262289841168751608673297446473709956390142112843400255748161809121986096092991616144443486023218404881798896685413932215981950393130292001833627899480153863300557853617312991880655905907971211246077450786084079040513198340644157868678782195341316027563717617074364438885981635394382733697473265872796207'); +INSERT INTO num_exp_add VALUES (5,5,'-1305511260.86912143656097667105187670102899690968579124221579164162420806975946192322298144755910384776938712225011087241390006873062785578059026760203327501250049706526689818710354560323008828670011149765298051017265801991190008306172717341082925524420830693916101819757002096967047201422972812110849615680859082670783076645772990170896843113541983091562070596898134103833260687914713270783188725279639957354065711180111801123002700709263607616000614100832094145026813710081431112908410130665994676451253271560294574006261508508554207856812178219605043607074077914745225674338447810581824502012643860446309124220528435874'); +INSERT INTO num_exp_sub VALUES (5,5,'0'); +INSERT INTO num_exp_mul VALUES (5,5,'426089913064020811.057708378200224487694731586862745370027417544052374884336177893807736467646454486029424673621605232432043672119510371547153895504456723242262639262542904151307250842477327375961936454637964429999741717244285121019840463692418987118402683746281993192269229200465080358289645050337976214115902915692028162689089167194843185708212911364017271332623359100711545479273675423617018342297822477514128997410642005300368966199980354369928371655155437291469427189561877718971914040675572136507472590254222870537216617260612835805368361975725573009455402822669103118872235140158440342063571894152305875004532651814592458133460160514384171804043127771746596286988679698684698755896736275307574630777027620558428909546664763675431701332632828281070572045822129984625797185173815273651376003614106277727279230096226977335510'); +INSERT INTO num_exp_div VALUES (5,5,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (5,6,'-652755630.38762364608541718463145771120672223443489913059334543712856431450577465795351472116052777583325262472505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,6,'-652755630.48149779047555948642041898982227467525089211162244620449564375525368726526946672639857607193613449752505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (5,6,'-30638438.151446159804025029882398388155309149089870990062944469684482366692824338098201222171115395923414887930224163525189097571163687285244255335505387733673499447610577050114902372990462064696637481657064525319516004273769831260452832960893174173254560250804003884280384718123289136453955482855362019158401218620018346500189769819687260476334734259702665316562988639223597110627626759216850014150105605927773639897638043177685498804811787888811168524202700283461266793154726325540776914500415140842975457394524215869103737379109516024460317825645645301237375972914247141703084877141866316168268901439172491577729880760950895760711857112463508064820414904611059588717092145484656103798852859978690742216940980929562068'); +INSERT INTO num_exp_div VALUES (5,6,'-13907037655.047994416383638650569341223199042786813441967582376077478024677494832069402897226848055043557486983268019376307288565911231748501636517992289743940159005664424461285010295150828744259113760652210086696250085454819340987566229400805422509198052317518991183515696724846560872057916862620762789778660622787735923967096950195583369113574365386627110408307941105082873469072519133330718161987781080307947247163619814890462416622144825161521790673339279047700672881113718394727610096366361422482794458375587355933614201638489194194834709433413694420512869179976485096875057742460003147602405353823942488343056906912173170809084207937229591627643451380735179767199816663168139837088183577975769442341678933576388936845704303859241320794255052627716474860113993958556604381707826493168941926878481079724185426298004604'); +INSERT INTO num_exp_add VALUES (5,7,'-818934540724601372.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,7,'818934539419090111.56543928171951166447406164948550154515710437889210417918789596512026903838850927622044807611530643887494456379304996563468607210970486619898336249374975146736655090644822719838495585664994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (5,7,'534564131989234694540350103.27821462973515555648644772098605028371173048154132108733819196629002548296868548691993248746628993380136454426833349407578676005545111508293942736555269938962058196496152360848131645787941032968937794930046928523006455386861100809286408671908320322523368135203881520526880998279355848280412933152306299256343179622513731096363088094541514890135766460631462465021694553063366717467560655272004461368865264059368514271105464855575429914212085797297268595943955105608543373940035636033207568676745293499106348500559628723682588033431457023964317090780615020801564861497990103549650624438425421690193862533733474254'); +INSERT INTO num_exp_div VALUES (5,7,'.000000000797079129642393611556079160915147221153735075943759104977169600937534508973732991117540626046659124172765761873705978811124901421049332579161931652390647472911517923131800238903184679028518657818755558526885018755394697157094867449047655737107085020874974955627907737126958129710597811740696534189608639914753884882702680512272194316887744972931453458445314561564591875764930680945589486999586667912816485821717403892703364322658245615895415781719033810595358092343690359557942948213374234065052300866661453767599465059289920067095083062096458980564265691295895672503728815182981118876144075942348853666085714846210822847053889733510154276933759200630639642310562242207518883342516103725757482864105340008709446643820864294556778969997115586027866760708448174502158738150605938364482719960251612464993'); +INSERT INTO num_exp_add VALUES (5,8,'7844230593.20607652525116672615394735666141304947992676684520382624714879797087461877675155217754947572297228288498221620714146356962938009770486619898336249374975146736655090644822719838495585664994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_sub VALUES (5,8,'-9149741854.07519796181214339720582405769040995916571800906099546787135686773033654199973299973665332349235940513509308862104153230025723587829513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (5,8,'-5546455599206321494.0676583421119904300307105296377723816472192007866147764761501865875232824814135783697976183493106885436876081315217834621720906478074798596116645640251460842350553806256223963023430631066024389364515688765194373161385579258482225808660340732705687558150699172147896486727530192499184101617379930846663835628510376484675411350654979679181852179924386290069790336316958202582966248703889464308649631486542724072047294216362186036638115240070658004553260251510288423749333873893917690832829128021808383128393431810674177390352413548658782609064839524756041501835115152819802758773711821322162752064589750295542985780512921839490040396053737870038534216948323935020460307350020911362024271167085905714873548388570602799432705061561572854498075600'); +INSERT INTO num_exp_div VALUES (5,8,'-.076822018213756690975099471985461347542955923191183223634407380481978143225129486622351714276452369661632980197282261508936298649901018470846144321441236073683990324039849865750139470288565622579952182053792815638469841531577235191276257498209844422440366423136595067535337374223115507557306455001792362506235886189722508617024948653046102060677266555476719102193278190540414934812073355995577639986512222998268934000209944414236509139290657402937840986061987219441410741189615344050459067454369371094189930607834375561948483494321255500497786795636801854613881105643003358210407867114145806225724880370339074242480071595684502491827709175732777776915682786771730423733673667248186336046898260378049328204094804755195626798951644386924178161926128482002518979482630732440619051262620098544265763306253807191182'); +INSERT INTO num_exp_add VALUES (5,9,'-597892150.08771044822540810796370552966707032464017958269847934730769542644402913723848026909285133109089452632480800168074607090893991283808726990171062867538012237270000932798704781608969096508450960185964292594677356241956277714380500188870696516251767979457838109804726539408115452577436052503866633026489282425086547752714324273565900641436632912781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (5,9,'-707619110.78141098833556856308817117136192658504561165951731229431651264331543278598450117846625251667849259592530287073315399782168794294250299770032264633712037469256688885911649778714039732161560189579333758422588445749233730591792217152212229008169062714458263709952275557558931748845536759606982982654369800245696528893058665897330942472105350178781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (5,9,'-35812445701642379.972368737320206275515144213236752803936806738624588812089615098329765811617509505790110909629109400553415312470540217508070421816878544125783329593128638405659896184248784794258084116406472768709113030915308410565617764394827427154923321461158387012978726512246146545834669665093228316853342805604075936530371665576147966721599968786161939347726656168798065647411457701453987215491345496003650288850096338695703984042549594979897253521041581573388369367579323607093487743440894765114619634001789457486407909224339065748496715380572175183589195611952939575073075140094901024063428239223964510824958346570603142906309198033196987949067156046076497974760641964978711558209708743776024313916111738542765749928287600981397080809041007714387564206594515733287925008053261840295560398311905155157989225181164097547541'); +INSERT INTO num_exp_div VALUES (5,9,'-11.897816658873986795664687519069203701902563457968097729876034796143085813450454323128600602495745166997629078984618283588337379184733369491549230343315369634754204412939757136108898254582353378508832611703989221079986765793923635928759179573599208612516427628403686659479459867527627014558600521732194240404211484706621458983727740143568799713006127585168144158660566534382037451913967363675002134687952374080694449905223371627606557311710348820900963340884001770733452314715448053233208783321215998063958966729954113843581448912079950334969908657535514847005768455377990262943747367245613296497099716892292154137652893990339292671106003657659470243633112063075297194691349631518467702876183897580432003030164590920118726657290102377710611324297862045849839571689192181090062958059281673245670440852080202548743'); +INSERT INTO num_exp_add VALUES (6,0,'.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_sub VALUES (6,0,'.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_mul VALUES (6,0,'0'); +INSERT INTO num_exp_div VALUES (6,0,'NaN'); +INSERT INTO num_exp_add VALUES (6,1,'85243.44233732197133191329295927531563604777955507322414928382967007765263923984471408038635831036097817458527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (6,1,'-85243.34846317758118961150399799670008360696356209219504851646259063690472663252876207514831001425809630178527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (6,1,'4001.075404054519813215296429095020391062109905613738157927030437221793757373268325953178030040276107574363822832168160758728653712686313134828282109532831190239521843808940611025488601517574653932032236616573457735900045655665690517797280666732780030171712864961531623060353548802466577910774711998056232872212688464691036260746751992072745518373073825852119460094113694393273456369345499434994672730920070410547163082189385645712866100999708173472360864669110044660667614583576570496399103026286828660558854973376227247132815728164629722965145778698957093136175449225024685874279280018547740'); +INSERT INTO num_exp_div VALUES (6,1,'.000000550624150700285432940805295709861455424264970126953321538967550091614148982212874391026630805836518138806917934859138493583812313778188030836027246840794439412443826640206464415527687555214009725107630387889854278497875708390050387195108441635824296563108288712340902423706104029452615686971019125750530034798026103476074158922893374911891438688457439945897348811702908216883650280617098402133628688982793791562476980709924382381505517834196446365877784931355599480881104446907801805570471686295270927836995181422963320376948188855989986414581755633425437161760674162177776773597848142496583128607548351599750592863590334617838124741567654525843413232313914310487355539260264225486180000012813397807525203822863232682089295055713257835007742845010741137213301116647610033909062369843750685396196342928455'); +INSERT INTO num_exp_add VALUES (6,2,'-994877526002806872754342148749240.99659316232359475297606895243958507460511031229368344962653674268847910587702140353344168594152240599109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (6,2,'994877526002806872754342148749241.09046730671373705476503023105513751542110329332278421699361618343639171319297340877148998204440427879109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (6,2,'-46696638263247522384986521136500.479312417066793299922708112595886608370451213741279484136907754744903470430131032928908162742687359367826808123516519335458861613010646992354378739165872253762686683966945711430182491860196341344982195078000259063231136011430995647812149294224699587849791008794261026932467933475782780'); +INSERT INTO num_exp_div VALUES (6,2,'-.000000000000000000000000000000000047178744084866106587600962473825168237820701199970144691815329658682341685812472535816245052671243808078367856957579485152424914481414614360809698177236664771558713606961423658442962083541733004775309314926918118528217478256885324362912426275407382550929085958089798861918760121727491366034496581249711153289495601712583077918760003840368008056353090552282274780428335438032908213783490070198414584291402513547386013689752310173492320159738977752795528725029134841933604057954874523842273790958618375118974623107241366036640538085329921129023905888674299774726871808862832797230915933851225308164365269753526489223540580759951230801125605963901491073619448437890841032149898629231552019804656219062534881074125995130202820302133432951999011667568746004715268323913437054078537'); +INSERT INTO num_exp_add VALUES (6,3,'-60302029489319384367663884408085757480.1853341682137571584926062805631087054017160819890685789064777236456590745415460695320768374693076860837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (6,3,'60302029489319384367663884408085757480.2792083126038994602815675591786611462177090630181693462735571643935716818574980747701251335721895588837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (6,3,'-2830400711649493468815157129316992649.40542786074520931471973065281957756940496588853021620372179463538053123396140685749478530925306163968207226329985017644835203709485594362663495728106061878665324856417118064730721101615473194292620972173690618491026470353143141125614124440035267592258385099934706896692953497971326605145704135723011753705907329979207428661473172503098296622281647255008204864404416199384701720347319806375450632245634238172654086373193251877533131784268854289406126119630708578053354762596511353053106459297339360827562281168219966099848212'); +INSERT INTO num_exp_div VALUES (6,3,'-.000000000000000000000000000000000000000778366376597400971124059102619954214055884926284646546105035591052258074563706355894551049631537984053410850060739107742208523938741961208742831871056600773325053133977559789796700130019975964192371715826863472981072974742704091801166438465082519558956925444635729210849210496466189037623555622901738570979273502405907969114110345815802999687171113749364073269902319653450479463404003706147915064100959774312307195946966281098140229199529866429134937742584938255441169541436021827079647129394362379406256722903991353136733939395366152312959281905058592776286736536360235356737359904478313225848562436632109470589310799000750518904145312512621838935796912993778920622238202744037977772169066929474233952081158212174549695244127987299282384885288897893503991509410567351494'); +INSERT INTO num_exp_add VALUES (6,4,'5329378275943663322215245.29625473207137544719284446115519970394719946335145777492574745992986971075733570324679065009803281404581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (6,4,'-5329378275943663322215245.20238058768123314540388318253964726313120648232235700755866801918195710344138369800874235399515094124581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (6,4,'250145412892811547138949.592621291590152419206270097656346630226508074074623894951308487425470437268130465956063593951784820669318897182831355375451719125809800516979013437732298382708070979871283132689492336823087794373113039154669229889503700598930220858275174342776478898670277868700384853696009897221747924643343353942154528501454689084608965009561564638167714973711022212547096732831847202912862290958304510651828842182545311077713664465815992616213663619529378061133917572474298028065850515876361609671565914027186063801852554353160801534696062207299890867876199323530337336273950892723090754719547285920090419070001019943385293110663922226230169381423410428577990604776655422105400452217085311617728003688836185608912367677734364834577573255789160419371322775733777518997638403409000055707558465286469808848200141192627396502735'); +INSERT INTO num_exp_div VALUES (6,4,'.000000000000000000000000008807232244507937251856465017967626593430084223212999583902527587737263981869382895220711835510154989851222501080395520249593128253795609198666884523792646863341248402687314509176781281863891589925961900674092953408613128961234166906173266411035009516545964362406728942021813644419154548354247112601793685146960840364604115937119024575638240439041250900118977183124605578660115160551830946251713350556181960983267689939549506518185340972020820080460565392359379680036788592213479105831301723237102710863182596413567756605711230290883888612188805367801369264231165178487334557824054205160222371548005742602736713668548450400926514169967213301919971189065307721110805424950794015852531342286935114651278691214233054575660712537044810163930633456573860895791198853393107188289695511873068'); +INSERT INTO num_exp_add VALUES (6,5,'-652755630.38762364608541718463145771120672223443489913059334543712856431450577465795351472116052777583325262472505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (6,5,'652755630.48149779047555948642041898982227467525089211162244620449564375525368726526946672639857607193613449752505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (6,5,'-30638438.151446159804025029882398388155309149089870990062944469684482366692824338098201222171115395923414887930224163525189097571163687285244255335505387733673499447610577050114902372990462064696637481657064525319516004273769831260452832960893174173254560250804003884280384718123289136453955482855362019158401218620018346500189769819687260476334734259702665316562988639223597110627626759216850014150105605927773639897638043177685498804811787888811168524202700283461266793154726325540776914500415140842975457394524215869103737379109516024460317825645645301237375972914247141703084877141866316168268901439172491577729880760950895760711857112463508064820414904611059588717092145484656103798852859978690742216940980929562068'); +INSERT INTO num_exp_div VALUES (6,5,'-.000000000071906039575366987930696117572143566208825430801491864851999044659045681114433294052065377679745375399878664822361548237094424148992770296383642432040129230180142339557437679166815114510467763288057917694948929009212876391059413439647163295629904270262780935228234994930653489111444964446097124407804311494588517082748514970905563707392765567625639455978464081409330528324962333492925267647686759704415549221137291475247571296491073010175087298752769122449499990102435819414671847617062560524758344361194566796343756743243766853291113852464023843527189221162680613675369708907935197867458588904367993736363321133720345058432019986643353417257503619558797249295232894674255060861358071309619524800424087896023710729815248847792174290644245138831518072176198607255346603270853333176255533974364728342822'); +INSERT INTO num_exp_add VALUES (6,6,'.0938741443901423017889612786155524408159929810291007673670794407479126073159520052380482961028818728'); +INSERT INTO num_exp_sub VALUES (6,6,'0'); +INSERT INTO num_exp_mul VALUES (6,6,'.00220308874624532134736695825088747995945783791378828770826401323533973395137378460250799184832278118133622563295093909508983301127615815865216895482784469538070133388154961402881325731054433770884496'); +INSERT INTO num_exp_div VALUES (6,6,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (6,7,'-818934540071845741.9530629278049288491055193606922237795920035094854496163164602796260436963420239973809758519485590636'); +INSERT INTO num_exp_sub VALUES (6,7,'818934540071845742.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_mul VALUES (6,7,'-38438389630389612.0042045464692275627184627672063157323631169405883031379129843031477339360597564128205768842448328088'); +INSERT INTO num_exp_div VALUES (6,7,'-.000000000000000000057314803440765029050667129936880528769333499793237773980613524885506515999851858649385968476426313207429914995755091541422893944525222307473169425244462149015717526718376299808423552027796204632286454853167559026787019718806449038446612978917236245943248168920696452018925986743620392955122431521581268518101342690974749463089739042586011924590503136498488946387508310209984849243014542648765897536338824721211252335866349509669538308454367849024503312249951727948786393404944555844863805495937835281927012430439403132382055464307180153473189842433614777883826783689904293115204700185380661601223693428304020047393499702811581067120117405280772944184877279069842269329959037186324135435468322336398566440055479142909170224780318371473684868152271947368867666706912563225912012901437076773416'); +INSERT INTO num_exp_add VALUES (6,8,'8496986223.68757431572672621257436634648368772473081887846765003074279255322456188404621827857612554765910678041003765241409149793494330798800'); +INSERT INTO num_exp_sub VALUES (6,8,'-8496986223.59370017133658391078540506786813528391482589743854926337571311247664927673026627333807725155622490761003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (6,8,'398823655.819545574205652791249227663407026876411660299394659390409794761643751582473390322547798567169668246138880832642141417531427935520467563318363116897177899262525720710134129529640376020947774470933902793259531840625444267816319963200'); +INSERT INTO num_exp_div VALUES (6,8,'.000000000005523967081937952184172713994498918048454262874017009201501812494019618863622631634736130436187167745347383745890248619882896153083428308074678908731005176810208100004498415662458272149380846809398637385270265351808328466537502823071145089961996689711299405627596294988646826454676198092260759424935699382655736524042353938814268760468122584678267125994645166955751211397353140569987758938572953312303398024147927938612934833827734142292697389251052485981023756760420972614486278837214553818521196182883489483756785207650821722660455451660719560529693418375773124813290305501923899840247103166971466167032437598057958226806335324315214908788839919408525748236713611579486768218564733151121028172253396652755590051310396973181595992981076269789287489208817712754098019817792758730835341151711523474207'); +INSERT INTO num_exp_add VALUES (6,9,'54863480.39378734225015137845671346015520435061071252892396685718794832880965812803098645730572474084523997120024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (6,9,'-54863480.29991319786000907666775218153965190979471954789486608982086888806174552071503445206767644474235809840024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (6,9,'2575131.137912978352131546639620215541477987701194164886305951830806120142596646541302305984776928560906754259789485960991272272782091464270104432109904222200473616116525297615725803495463468272171161659654385929185160689572943852767523792651123455283534072794326647404332228203001469884016996499768656263775233430922446983838511590562929268821678518640501686017030536100955531423152839988008496919169395159653034847677470665418765966542111749439412'); +INSERT INTO num_exp_div VALUES (6,9,'.000000000855524875533453524582534418967571681572635027972658867593464437484123442242521660317156546196609749230372398872487667521984251509483676665788527375343148382604836976332389890799079878151841905152004537926201190193814594954194044560537664560344224646197027029681984683465852110060077865421064400958821808374370779297676624123638191407441015008434084079839721156870032377372497814037418047056438760664237367081226979226606227037631073946209105678283624370820396871058367779887709720661001099338250009251834581804647326512873792849059661525874160414378459696930831877643599421297749483849526695657467708603491876916749718079725746259119898269814551222336219537198318796277931946529242436502235147453584237994498566122973953203597470078105606906752099294162422474758048436539653041606499637623370030079916'); +INSERT INTO num_exp_add VALUES (7,0,'-818934540071845742'); +INSERT INTO num_exp_sub VALUES (7,0,'-818934540071845742'); +INSERT INTO num_exp_mul VALUES (7,0,'0'); +INSERT INTO num_exp_div VALUES (7,0,'NaN'); +INSERT INTO num_exp_add VALUES (7,1,'-818934540071760498.60459975022373923760152136399214017262844141729040109985386964272131706381326192223266583769046276181472898406504104649192224392653722107164485675679551050629376558940966195135841284978096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_sub VALUES (7,1,'-818934540071930985.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_mul VALUES (7,1,'-69808760806266041400340.70700818693892852138813934414383886494691670042143650609934777814995087699409404201920249076407981012095999320858479644760715204999741683528746097757549835956359129287002171391961763797857794730120426599135099619822532290339000466211195776337667123320942107370731349851576864242697412616810236323676004067839744992733887503405311090677026008324895177587064547630828026123718296429295638934384446325302964896473296829265805737112709269803814942537657996725913938408781715328945194948010970'); +INSERT INTO num_exp_div VALUES (7,1,'-9607014551997.140858001442365669993007297071681832468350855627077185145567261170534005832165603932891201648027598773639089125980996652005412450490063683624648655909636499261774535015914730479401090227915382926027949990128880284298688443593909017437720828163877690126019616194376778317148693270900349151496295698078575648169637635898560612738481294674167553369445426793073304518646116539082953755973571046622684332425840412198776081251646424875405772676893185726872613804612566569794177506268399878105117763696990094108960076591684779180089885283939385808214239337829666227427148603057941899878123459708920227867371285837642561064461118016739395972994827327543594846953341750907541716807985738518071480209106185726125017342997283356926976052909493074301401955202616191210810331245427141945840542129607439703255628683506772979'); +INSERT INTO num_exp_add VALUES (7,2,'-994877526002807691688882220594983.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (7,2,'994877526002806053819802076903499.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (7,2,'814739569184924399102711674444306584731316176345067.39834031417849342571224916231092924046722938910652929295271097903377854123984307101079073134405782275535446337229706620713104545454319555885847481531722101704765783025789147453570970090'); +INSERT INTO num_exp_div VALUES (7,2,'.000000000000000823151110229758332661330617426417726331211894330147399760458555778324097596176117291103184653828305857999638466183347321835058943563347767579219763002258622507889760416640758842509635599414768344140175277742935564567127659688612699366182158030839083982896107176174766408199870924563237827899202849733606842856491701660599599211106794572237923985121475458446997860253437578966578617985764298513928307852082168209458400544457824307270777530312648199364084272310536024283945598340590403612752287693234647719354745060851129534452514828239800716088248915975054881011343555492596002595181046121935660176097475159074973635534016835214952415720717896518544064238656360099884889450237541254761746029507300068198731306211736696956568648033834554273602524147075895460874922913883751452403825099444642503437'); +INSERT INTO num_exp_add VALUES (7,3,'-60302029489319384368482818948157603222.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (7,3,'60302029489319384366844949868013911738.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (7,3,'49383414785234649002982046297226894664526726187218771083.0993243619030008310875293647868815940421844461627295157812843657782639833900543200310573708100000958929315945039020410482966753145208427035917753919085618457760620513481628641658765820294863970581642745379331727722585319163262763708386199720411053619449096019862596221607526610103408936214184850115071874430846697061554769773328338028749631552202705583855831155461651414320570061181212214810086436100771547030013079997847086'); +INSERT INTO num_exp_div VALUES (7,3,'.000000000000000000013580546907080371873577430837141172674171921610919544849037647398734065712983603204704663262116138799357430947986241590690589753181299773842880079777640016786921825609617596862828930939366173224366864448436461306602680780407912534492687474933386043505172346330210659476505435994582446405414027199938970759003336829722057241708213838318628292667946636226143164221380503228191376939596663443230082698085439531600756771639601022064620204571458766303985028143400866776954225590745596639602613498355332049777798367675438365442468743270334407716567057368347458892075084694158566383133325959042076573734408841629149903649365079563374278550978052491499304166424686842598833319515705663176855033865872333988551611996194856472662292344160194821687681312501127516922809221030420253714666026321243515830'); +INSERT INTO num_exp_add VALUES (7,4,'5329377457009123250369503.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (7,4,'-5329379094878203394060987.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (7,4,'-4364411947278810125327066890819882483326918.05664098958260550284395870948992407314161088028674246708928421994893923699743452802989464864039994566042797942433140378990308345483670828497915478397481687305406460330009319949623844175096007381662809083363069100235985794575399268709260901964834244796150883807308976949196661411035264619638771824190014274817662519438658481432363824187693821267613212631153175155634316128036152465184903927860719447693468054624663668062006049759837326188252927823612718163916100588143128358998656306593393889422386501730237442526450419990376323903182669190482615734972147533221144682538647497701130447816148459762464395194383090936159579764712919396391813914821973715879062992249315474841639591907249142779103650773383644785606333916967894'); +INSERT INTO num_exp_div VALUES (7,4,'-.000000153664179510102140733858340480800294287837601105047285453457000254577644933901525444082336054243749405512900867540483190494113677173628646221933766421338612376123824684592850465460156248403574333545090544920568230979754949827013129083778435107488003838746926270955224758508832133483591156567868631938590248213604979638895901933775098150684618378235712437137852195098700137765601802898366867034641606131280434771339920637353140131159441790904703083143627590062236537714415872864218260252838432414759890832271190606933534662897006726154587341385852258168335058931957995901987808602365467861573344491265289043037273815504867254228957776127752540924854546837197432384563153608878864912196453587628891285275067452280357349897203095502806923463147414086919014592380804424300739713935051357374227246098303140106'); +INSERT INTO num_exp_add VALUES (7,5,'-818934540724601372.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (7,5,'-818934539419090111.56543928171951166447406164948550154515710437889210417918789596512026903838850927622044807611530643887494456379304996563468607210970486619898336249374975146736655090644822719838495585664994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_mul VALUES (7,5,'534564131989234694540350103.27821462973515555648644772098605028371173048154132108733819196629002548296868548691993248746628993380136454426833349407578676005545111508293942736555269938962058196496152360848131645787941032968937794930046928523006455386861100809286408671908320322523368135203881520526880998279355848280412933152306299256343179622513731096363088094541514890135766460631462465021694553063366717467560655272004461368865264059368514271105464855575429914212085797297268595943955105608543373940035636033207568676745293499106348500559628723682588033431457023964317090780615020801564861497990103549650624438425421690193862533733474254'); +INSERT INTO num_exp_div VALUES (7,5,'1254580584.048971438599349046867230181719371038956756285986415773300837165755558702217197735811549684202279755101552533605390208155708695952004683670878589028717509749282693444655857296902117478518511492735290086040573521482737598395369632843374456793385511847676556826348943588519880411018079886373631771830925920986588708409208527042927229627786932908015502292313887561198156623702404977221789649731458241770690830680067801377815840764873662400590343236662968218256211697981048576328148435241545372543075051594952109757428031762469834781538302930957095080167901199455226976113347018972534334210416375400979738414416582588689496706548495076287263281908191770792203069614447622517839588243746755480572371988630084226963919158931419126724681617069720048557166545204944250492282054791996953359013543036918134163144772567093'); +INSERT INTO num_exp_add VALUES (7,6,'-818934540071845741.9530629278049288491055193606922237795920035094854496163164602796260436963420239973809758519485590636'); +INSERT INTO num_exp_sub VALUES (7,6,'-818934540071845742.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_exp_mul VALUES (7,6,'-38438389630389612.0042045464692275627184627672063157323631169405883031379129843031477339360597564128205768842448328088'); +INSERT INTO num_exp_div VALUES (7,6,'-17447499423661151023.558342555162228919125358089491573318627107322332520978657843895009110781773496490472817700487707134216424855867015781267287628022535529641238372370292374146871103236048507252055787621394728096799222976387108688980537900309311204203302960751747509648304056939321473462375648710590981564101023812800603438271190184064874290215309040519813024962909469701968804925443161094255632624090623433640078421818321246597728308302979223833487133268472455479442002005374793705431817866798804822885690193667521606781156962792120052947767160957903073698536973292205899421787948529970837601521657406211962967291912148632072929662185840265855612193255596825032457033402506154930851214421895488796227471490998190312007513478459049382774782886773158311656817014322925167278223360446454868236479549745612973293185989975394307678926'); +INSERT INTO num_exp_add VALUES (7,7,'-1637869080143691484'); +INSERT INTO num_exp_sub VALUES (7,7,'0'); +INSERT INTO num_exp_mul VALUES (7,7,'670653780922685519356619170643530564'); +INSERT INTO num_exp_div VALUES (7,7,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (7,8,'-818934531574859518.35936275646834493832011429282408849567717761204690035294074716714939441961175772404289860039233415598996234758590850206505669201200'); +INSERT INTO num_exp_sub VALUES (7,8,'-818934548568831965.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (7,8,'-6958475505053954666339703437.48985528725312694198056665033448258303533387675711770743843194274181580881296671866212320171337132096489224277825857521033238709600'); +INSERT INTO num_exp_div VALUES (7,8,'-96379412.478435590945480884955616049873645089637121682284625533034225619945532704111492738646389632607594293500930307222576571876059094206480673293295865214240456906965855425738072430281475736130342229749511650392658808510082775031098547507966544723255869156056349218776847523349173551313282283869146710349521487706884633419341568648959204688757523312579312713453540395840470692533267158388401676533369105590789036132185107859069994833345453200014884023709597817280132465224778002071890368479648934317322270613208789859930618055792958996389145963056607200020526949699302565905917600478429628844015684879886549766473809801710003649193772354147104446894109928903223843036925147624639466770660174828940577089095480826473544099693433597812637069287644606693066736302793687011165899362920686114156254982709172925265118077531'); +INSERT INTO num_exp_add VALUES (7,9,'-818934540016982261.65314972994491977243776717915257186979728396159058352649559139156429817562698954531329940720620096519975256547379603654362598494779213610069399116912987384006656023443527501447464682173445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_sub VALUES (7,9,'-818934540126709222.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (7,9,'-44929599044588573810654775.83678007633232843418115790847152455559258007804727916986432256198687661496804050903769496933400455947645400628259699874770581538122521805603947464462448454681701547899144129061961394870320463199545502030106801911915987309444301341575451240764927967432593181449618816978119423290767783843864768557371257918447461479570164065303599994081990686'); +INSERT INTO num_exp_div VALUES (7,9,'-14926769772.797708334489652004325241753714626257641081061212878627972973992233480868793527325656854681817156284203427388055525855608883067129036717726368707982450450575794623567027457808927082390474261155500697096284790656757163047499531247323702909360444831707029353441147768321257650234732286165724178549576948957405037843360446785505536809409054071975214796532504678683693402401018726571884721963641317944453797513145055081061680091585467186975354801535734149952115333241283186621720677488342266420359417174224757781125498130120775969091933838082305123652811689513300403051544682523761263183781206840940347226802620226164265210810994106136738030959199259066517106713585343004140573604437146025585149934286364795122716971496775012412420105368351774715982565252533025207453326002101655121126631180162560463548157187175671'); +INSERT INTO num_exp_add VALUES (8,0,'8496986223.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_sub VALUES (8,0,'8496986223.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (8,0,'0'); +INSERT INTO num_exp_div VALUES (8,0,'NaN'); +INSERT INTO num_exp_add VALUES (8,1,'8497071467.03603749330791582407836434318377133169438097066269854720538319012928851657498035372443556191720308219530866834905045144302106406146277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (8,1,'8496900980.24523699375539429928140707116805167695126380524350074691312247557192264420150419818976723729812860582476663647913254442686555191453722107164485675679551050629376558940966195135841284978096687306110481009743118940565957556492470398904849289222365256698601073536111216152709126800604695001949246634784573028721762079936564434050796321975774729383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_mul VALUES (8,1,'724311956372274.0135050255361637906710330203036651743488213007179039756514944640108625580172737414192938789413338554327986697518463087452612658955180411327002900979574347739956600177846996063741787205122007268468674386396156638261992679442768654367111433834151087792255469957061758837789341439211010331332174981459471333376067541234901538285101103690622656631026001337239036711179989456674399137008584021283568040818388709554256523118702728176420022080138548890713013682480239784198421500241995499841675772793497485550923152267616622892846304530712344886979674416990935007952941652591352603797627920865960622077762568060903908151958000'); +INSERT INTO num_exp_div VALUES (8,1,'99679.115123747637190903598543851248555278745675862923884476564848911494649941770503156134872464666625927195645517181131678518619856156844072856993813601495176097972982587061507650426363887871820112714099226501603733968262566093655417466145183587899155614471697804006772915054739361437054029183182533671508695646413074668188590846200362324428338974890534273352188276373478524543505805545661569395314989170104140776362043880099775594658817242753124957385625811310332354760117110779649164022618274859298031549851269619167173746259018497289174255201452265070501056913033329291819570027877856677145579673495987354805150868813877928857472561883332547900866904764950837506993759536410161752469488392566682723027340638271076406246129989851281210810196699482980833204884400423019400653089825859983062096326294783573417554749'); +INSERT INTO num_exp_add VALUES (8,2,'-994877526002806872754333651763017.40289299098701084219066388457144979069028441485513418625082363021182982914675513019536443438529749838106171095037135009526312783302868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_sub VALUES (8,2,'994877526002806872754350645735464.68416747805032096555043529892327279933592919076133348036932929591304098992323968210956723360062918640113701577855434596514974380902868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_exp_mul VALUES (8,2,'-8453460632655529853033389979024265783461224.3195241893307807116624750282852146303290708492834695194274289713076935297734670940696121761483641291930931061232942894577813178566088927221374036301485916497770984757492912292002695944367308880163698595015497307574177176409203214324418237020500352652934909632442547242092296504047310806151851207329042221920888326000'); +INSERT INTO num_exp_div VALUES (8,2,'-.000000000000000000000008540735921314463871578184793632135730756619558669911183806487803411545406462244216408739432325839683804021466133071768612386706692296158696852363349481716813410857655324486448455846562309041306880675446880859847445987588059144788756984750993583865748280824370754934966494724951583311563735533173023858438364336214213295786266815116844775733072416507474834701984381586060478606371028156925222726225495235702395502085206072985373035972506738983640539009567237336002073370431753469632428303255926718930619221521257726366850472572830063284204851204189447233044832163423057501488364913539948261528280564870049935369825245920984413480757133585498984374354957754078525161296201228031555280486615145365039415418251448980923331334883673792135893857917681235883506783408111446970710546686739582471'); +INSERT INTO num_exp_add VALUES (8,3,'-60302029489319384367663884399588771256.5916339968771732477072012126949734214868901845505193155307646111690097978112797961939995859130827784737422228762767014427842766445950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (8,3,'60302029489319384367663884416582743703.8729084839404833710669726270467964301325349604567186096492702768702209585877643481082023851284144664938175277044596973126708926205950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (8,3,'-512385513828318260570283740065493064477880918352.732624553690077857674083796435724202494963885926573907185100543184828131859183999195040110586155435203949963570735841632689374488877298209082579317039061893012560130258753218955057387206477423088065663401594359617882154814262843273526859406265633827109554791772242178864873774889091687515990672487380368975556580539271333144212685871370972163560839446696514092637412587953506052848750866803569213269271165856310101244342151576488190595936869490659700946174362872797854591188391982770203203644172999264143929484089237665313698600170041324566984832357000400'); +INSERT INTO num_exp_div VALUES (8,3,'-.000000000000000000000000000140907135225782279761112255989433531718277338909398600029580768021365259747075253760824424092983497958717844671162530550507041138147836569244869107757945370200122955794509365120853536859837243314494576053441804831018954867623755033888264275704547752628348151132333655667171970175829826792355986148522268067032057293494927558322394395160508723637192234110428953945018965078022622950949911124494740703606109543716688008516750321047603009424529696862953094999450658951089435460411028678817795100630449046993274191915359520936265372754315076684798942557329584282177053819106884196674660057281227248874819417305259132106690385871316407455034281900110779740008476645291647094776093567400422266906817555937149628005629880142615126571231411138926043531449659320501743591992888328328980526602'); +INSERT INTO num_exp_add VALUES (8,4,'5329378275943671819201468.88995490340795935797824952902333498786202536079000703830146057240651898748760197658486790165425772165585380839129948178510273188565692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (8,4,'-5329378275943654825229021.60868041634464923461847811467151197921638058488380774418295490670530782671111742467066510243892603363577850356311648591521611590965692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (8,4,'45283653791262997781451381354094822.762732909505051438036873220502792213670540454778361182993875916509061144859281577740137081988678361247725064336120451090222456518107029158304937620179032477664627949959143233370320432203497828243297406462513350790251761540074946469824444452248386782451723637769289822576372357189700319768797708375563651655860093365309717823602754924352327588945034832436331911584742966378275504545736896430718939807674966738116698454215555860047859161126694019895490767779791933882712567492115664113775047192011252893773389940988533801360010782816196288710063568554147458866942816721046004257953642508395867837127678980002737669139369781058046396738606563716339660654364541530532834806205571191828994250708412638796240377704994928921528330863683630622922959130920715261879547446054261914770022377059156125037157979236658010950'); +INSERT INTO num_exp_div VALUES (8,4,'.000000000000001594367257057971052149628499448029056279649281098852958322409409919964709324200796473211884339143791758566019217634542932882694487712398244322522748736692741288668885362384266615527166964187404128216235057387796054457728789109537338988453837993084016408244895452291151218602815057669592284587317035387004942691671916981967449109983992675125005085762403043329820872839739877674121174083273716295673230993049263574856197011389828478636779342320299895806297835595427859271617831720398457416685435560152182883615601663820189195644140652141180949257192740185075408019971747810015931542757445763460947106918998459997631117642552273815713467150465548031203738878873114842844016176922502916339025283749846225376341878386377192605865913018132981323065698049618379727531925408677611856682983907951667054819'); +INSERT INTO num_exp_add VALUES (8,5,'7844230593.20607652525116672615394735666141304947992676684520382624714879797087461877675155217754947572297228288498221620714146356962938009770486619898336249374975146736655090644822719838495585664994425117350974491367099004404995846913641329458537237789584653041949090121498951516476399288513593944575192159570458664608461677113504914551578443229008454218964701550932948083369656042643364608405637360180021322967144409944099438498649645368196191999692949583952927486593144959284443545794934667002661774373364219852712996869245745722896071593910890197478196462961042627387162830776094709087748993678069776845437889735782063'); +INSERT INTO num_exp_sub VALUES (8,5,'9149741854.07519796181214339720582405769040995916571800906099546787135686773033654199973299973665332349235940513509308862104153230025723587829513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (8,5,'-5546455599206321494.0676583421119904300307105296377723816472192007866147764761501865875232824814135783697976183493106885436876081315217834621720906478074798596116645640251460842350553806256223963023430631066024389364515688765194373161385579258482225808660340732705687558150699172147896486727530192499184101617379930846663835628510376484675411350654979679181852179924386290069790336316958202582966248703889464308649631486542724072047294216362186036638115240070658004553260251510288423749333873893917690832829128021808383128393431810674177390352413548658782609064839524756041501835115152819802758773711821322162752064589750295542985780512921839490040396053737870038534216948323935020460307350020911362024271167085905714873548388570602799432705061561572854498075600'); +INSERT INTO num_exp_div VALUES (8,5,'-13.017101389051085341042057308965769356145255575582875626848796382322826525772114256699384710400140437710569924703769685567402446691691210934185000959063158239023412379691360587119206695513775971704926722817528818197919265145207032750407924774510773427697188520818450702875142190949766251178733262143962213111236591970766836685919581025629742334704854852196126735685421250263035895756028805974153787560164935038227108975229771590754808331856162035119882347418116049174638416621093907738608991987582465865527947015457540650512339263071898410531735438556948115098562123055444965056347091625748703503220861221718449714020622377233272042277814766996198081939221253025243417993701684007826177845003391944496774674489538520354606358872276671998045196738090133576377830721671972381371985771591052597345572374064920279182'); +INSERT INTO num_exp_add VALUES (8,6,'8496986223.68757431572672621257436634648368772473081887846765003074279255322456188404621827857612554765910678041003765241409149793494330798800'); +INSERT INTO num_exp_sub VALUES (8,6,'8496986223.59370017133658391078540506786813528391482589743854926337571311247664927673026627333807725155622490761003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (8,6,'398823655.819545574205652791249227663407026876411660299394659390409794761643751582473390322547798567169668246138880832642141417531427935520467563318363116897177899262525720710134129529640376020947774470933902793259531840625444267816319963200'); +INSERT INTO num_exp_div VALUES (8,6,'181029319177.110996740664566780784253502559986936959009611748146099327460471609593148344991059106574612143724330935988823134137686051475120980257829276671900076859337187540608483895641504622910361858962883971613675309676443079313179200981488761707281247447120551917205792352229666049191991270809865110506639390610910481490688182068719005593641339338678014189749279508731647492051879768743158839680867283217578754666643688259810863605002821607490100820241093473083445658378988069593782353275713240897038366242558466047071334385431080003439842348547427066389352198560236731403235927478177780757802759046212921140424771887928786549573201311120885052685761195784207710933764480136690216943336587118385525047554334029388869436622866247240903231799829259264158812528305210833683370536416861544931420820452512390255774498188962903'); +INSERT INTO num_exp_add VALUES (8,7,'-818934531574859518.35936275646834493832011429282408849567717761204690035294074716714939441961175772404289860039233415598996234758590850206505669201200'); +INSERT INTO num_exp_sub VALUES (8,7,'818934548568831965.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_exp_mul VALUES (8,7,'-6958475505053954666339703437.48985528725312694198056665033448258303533387675711770743843194274181580881296671866212320171337132096489224277825857521033238709600'); +INSERT INTO num_exp_div VALUES (8,7,'-.000000010375659845651632013446652385870617923988120764298690164486716047614260682259722116360931978511176121353975789418625836899338225571166376573732227571704071000348895791547943896682585450808398324252224265156214259224488248639550967292466343168350213394398101712526534464002532408445204630441167137710565437434313424987517531891145368203998329086865151248833625645567863740298397742783405267970015165358620026813812552194344790169289440822038223606218360105618852154152168496637886434061050281055613760360200323363465925493033734895631921307644481639236601187225135325401868178006133838932915485272554505684060229409404902185944047523033315868230944723282246159741659387362889777495094736963530708159604929268812778894177095572578862150793098548829744006499229853198046828954650334595737117597239208825268'); +INSERT INTO num_exp_add VALUES (8,8,'16993972447.28127448706331012335977141435182300864564477590619929411850566570121116077648455191420279921533168802007530482818299586988661597600'); +INSERT INTO num_exp_sub VALUES (8,8,'0'); +INSERT INTO num_exp_mul VALUES (8,8,'72198774884738777393.8687539247642452953425155400068591498151280875559609979248583367700231031634872342122563819478919600402159024059794279536786611373504966204744811722007869415559012475160471227957857756325962941799428857291371597146319816910515366298862558849452235442246081440000'); +INSERT INTO num_exp_div VALUES (8,8,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +INSERT INTO num_exp_add VALUES (8,9,'8551849703.98748751358673528924211852802333963452553842636251612056366144128630740476125273064380199240146487881028508694029546139131732304020786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (8,9,'8442122743.29378697347657483411765288632848337412010634954368317355484422441490375601523182127040080681386680920979021788788753447856929293579213610069399116912987384006656023443527501447464682173445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_mul VALUES (8,9,'466174236688165594.9218054325256670866060556227711696100465581464881295978997280335378678072434776702952026828137140986670189756965420183565968027969700090735690246176791371115610886533930223141650377886909408268207750238603105232560663571044993507074695683027062426288270199495225881785499139012931143826099668999261931834700467395442768201666740663642498098541516326470052372008385656719236306238735524802875519713512894448940917708118676095378518264553310312628830009314653641136566040400'); +INSERT INTO num_exp_div VALUES (8,9,'154.875085756903716715488911525453064308758123952566428258639786597308109810869086867746263482721081985848551254298524280231489145092826397833394044637104667137816928932471315095067524966582810436282901424423215992139000153713476369887383242289102867530775908269805285313842050961754114751975054515055089553180717444020378611767296609130477264722612784088270193199394531972594028420402254831778715196248487757266330454269044609134602570688339750190391651801546906342796660819535014295618246236706572780627362908121159003488810140236665846928586992082180006454824311789091323774002510945263351862712964422865623934112293184149374573706760114682326698881257123280119140924775171374360283137569618025005229268057970275164869735173660958715166148344076027212231446680947914004346760896298312286730627916684448923824769'); +INSERT INTO num_exp_add VALUES (9,0,'54863480.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (9,0,'54863480.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (9,0,'0'); +INSERT INTO num_exp_div VALUES (9,0,'NaN'); +INSERT INTO num_exp_add VALUES (9,1,'54948723.74225051983134098996071145685528795757427462111901537365053896571438476055974853245403475510333627298551845046116291696445177112567064282766115207407461565363967417615506303416694032848457927390574251904212425813072768882213388082765916956736282110801611726537663292922699021333445658549608928179155685881583228490235606377831724593358583903616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_exp_sub VALUES (9,1,'54778236.95145002027881946516375418483956830283115745569981757335827825115701888818627237691936643048426179661497641859124500994829625897874508497095086558766563666622720535497438693688376602804651302002795213923698663694204683995198328880575615535181012624198813873609885725228117274934655048553507421448724831939026752650108735245933317237310133362383704426321489070979168993853338252728216162346796960170352897972568238870481118474064783391570102958474141459619245240874849766946530000977144965'); +INSERT INTO num_exp_mul VALUES (9,1,'4676749348240.390309875431213992853550297086049749814750492488995108783145961719774217441193547534210468967573344456866203963659951312519988497979489304488948342258375915152429008993288817366720647491166024151209542534474867042837694499222928509320280684557676243780452100132238968233413333851595648146954975713386711764268506890884764704949969602122157394714663532141060559896359465918874990769222345665160127552795532197771168442486088776803398878354288847069602460071745966589164282641033852314335279121191855487126430176047553895892632834940595958394834437871886013513058514896870683979585091413977173250824451205330441299000850618134248917380244749589254309567551846327349592529960432446947239714236828401206843011440433362544797025114476612133622499094287321570559088587999417440664282418005102546343020409520421747216'); +INSERT INTO num_exp_div VALUES (9,1,'643.609749344751131516972294140174556703217311736700045690413622699888869645595256683013323517984528456698303984909359393772036036540901870537096836621035845014213031549051156299974682317824766457362427063305495772666640279328909129870227828460705733995380145417663304348663705694070309475835826101153850359826502235923289787750107778906593010060115662191620280031872002110849782776325630424918493602259707267214006217268630948545349980430128422952869610116216278256812581821942763705098526140427280008360043829906543029486315209818099697988089748683904695870401517598840185535891464842870210715421728852789815860153472208176465166954851895457846723102438114697692610933532992841803219018495137378534010155991355251803548866919409031477821173935696065078362044927492034445482457329200246282082707380974745411383781'); +INSERT INTO num_exp_add VALUES (9,2,'-994877526002806872754342093885760.69667996446358567630831677089993316481039076439881735980566785462673358516198695146576524119916430759085192883825888457383242076882081857926408611052522393579396644731758241837010163568445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_sub VALUES (9,2,'994877526002806872754342203612721.39038050457374613143278241259478942521582284121765030681448507149813723390800786083916642678676237719134679789066681148658045087323654637787610377226547625566084597844703238942080799221554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (9,2,'-54582443595378013373024060492546032003692.4875677735896411267274323339692558458420972958075073392126734000341372096298914875892612108329218081214550050039133117695428196702128258481789017059073444323729583900855712795086447886053552786449313809589992185978097430132940882612817775035217244553616977182049775786664446683332098226841743818600819221587510039430478859412452506872131851471967577741190323481953867845129745440745526578327709351120432530702446916035797432129052518980799424635406993848916727957825620638983706180841278402925286540375225365057191075559133035'); +INSERT INTO num_exp_div VALUES (9,2,'-.000000000000000000000000055145964114074763360265614481666934002579974728749248345352023099030383962250681574081874554842623852433135871821620640200582985140388676650602814646133317791813938390695683843848260103199745295436998313216878337673674660966362155480524935736646623766057029148471463569162153009963312016563281545776175277904913263614668092319707343286073000287493274965714031678784835459999763925833141049057636632430975424499618419962303087175237320046300285962065818926167792812657620724550768858763098967149546312995222223400007044549870620849992226072041407997925405957501929449911416474388622107825120486594723448780503829317691081601820425151593487431389373265285594626753418140874747955925763163132984655078996173911578832035721963554569605730262976354029623260224710106409129114204296314733036'); +INSERT INTO num_exp_add VALUES (9,3,'-60302029489319384367663884408030893999.8854209703537480818248540990234567956069965340942024890856088355839135538265116174644003927269495876835324407641642359213535695803871472434650475144516723617632059130297610134243891145006222068960999879308472500422640481972089756410157246974765071949782242392661524488959954348903412713930092273629207697480131360047867213863018127928853922173643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_sub VALUES (9,3,'60302029489319384367663884408140620960.5791215104639085369493197407183130560124286109130354360944260524553172025725325268378015783145476572840273098165721628341015996848028750420770651761919246816300854441592109844750954710317145008297946462099581451150385769713261452744310496166494545449824802407416426304041583975713483424241727236417259479541129474082301376239522310995725648773643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_exp_mul VALUES (9,3,'-3308379209762459471107480259839508279070920437.883503980178028214343751083865562028455061662673132221930429904398963590401793045470444301883103141901787466923883803951815572606105617157736442670792467625964359169270739534412932791178258858918086886061702512427989129732248215348301444245772127142869263635282888226326427510486246184233225114523636171202034558843515894542952126988613018789833835507734620046994907453602573865012044120483116345444810078666601100257620969379968264504287700045822481492526688635364586344704730579892342786173395802035361824932075736340405960099542224953439044947229246847140957298841482874444906129049023002897135347878048572628834749795298712449864571996898774444932083319581439741625832405434317985988163261591679157437224404970927012111196724239860528859217322132733404472897289'); +INSERT INTO num_exp_div VALUES (9,3,'-.000000000000000000000000000000909811507365065002714756487495210579371808512079908127938523896001746219475805196061435010714649189975968123072269549018826343830061696154665503565341929634172463095299662727352635590451263034658630449260378893723785917860125051787451512267088404686342938118993621396641623525252649748977992770709930435013456855344203854749977414354164157192885125263071636468941596567220391082793700307461350484216679632552883058303710297475827456761138832914743429330069022439380297715971317819244718196187172770061156794130040674050533617155253444764036426045091327368023602807193742585178432544430741520636125146531502042579276206322507516332917325631822606079220413965396706334639331097621824106950192993127113903265025719013680733760540930122186345919977470628988674677630636632053583144327'); +INSERT INTO num_exp_add VALUES (9,4,'5329378275943663377078725.59616792993138452386059664269485161374191901124632386474661634799161523147237015531446709484039091244606359050341194730653343894986479159670583937529516163204904273806158788218327396375034882788180783796976731912141525319602448709213495905899041406302673881364465504945113279286939663215197485367850132991968081639290297033476859158044889351836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_sub VALUES (9,4,'-5329378275943663267351764.90246738982122406873613100099999535333648693442749091773779913112021158272634924594106590925279284284556872145100402039378540884544906379809382171355490931218216320693213791113256760721925653394811317969065642404864072442190731745871963413981746671302248281216916486794296983018838956112081135739969615171358100498945955409711817327376172085836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_exp_mul VALUES (9,4,'292388240303165948041827159734686.255558469787242316676287235194652580157149226950109397295920730296960145548003120827363226435916209781396711693581454960342091452830648929118261388933297036933167543189308061917640517578583521401267417187854611829815212778183983326568586118831109538377828156118900313778053576483381085207892754728937946691892849474364477434665960112125254104966566712906532318984871145605839506991591027939136026602051635433295687547552796828217859648186757719639965988287173297286034098497871707197092627676226053609131138590878743560287292934815277894463305001278326023708395571840850120055316276256138004565442099731931051413153564744766098053176049414330146267604802971221161572130161432525297614616942172815141372973870720928125699420370428856022295499447755488148545048400795053604349570217878099721865670458104653570360'); +INSERT INTO num_exp_div VALUES (9,4,'.000000000000000010294536718194523982241053267404812827031741197656209184880073175960433631103885281961037127283726462743623757855378209281373475473018922090781553213750339001555832360656399849031527008437303091226051008068950896796359518673740801770866360774945096397034708173365378527676779736929035450380795854046109380272505550244458858231227568118355064007614608452292270378691774826689216790090661497154742954386244856792006376222923780801296832612827123778915598893970651480451509706836620045721191411824060983487064555397842027454385628620582036592315345973096405447742002746762099231557054678593446667904250189208490698468539396733604833688133512716508825505666644390119877423938820483653319376926639295680552194966870285838815705038244628263602997511842285889300557188773128635554621378148419364876651'); +INSERT INTO num_exp_add VALUES (9,5,'-597892150.08771044822540810796370552966707032464017958269847934730769542644402913723848026909285133109089452632480800168074607090893991283808726990171062867538012237270000932798704781608969096508450960185964292594677356241956277714380500188870696516251767979457838109804726539408115452577436052503866633026489282425086547752714324273565900641436632912781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_sub VALUES (9,5,'707619110.78141098833556856308817117136192658504561165951731229431651264331543278598450117846625251667849259592530287073315399782168794294250299770032264633712037469256688885911649778714039732161560189579333758422588445749233730591792217152212229008169062714458263709952275557558931748845536759606982982654369800245696528893058665897330942472105350178781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_exp_mul VALUES (9,5,'-35812445701642379.972368737320206275515144213236752803936806738624588812089615098329765811617509505790110909629109400553415312470540217508070421816878544125783329593128638405659896184248784794258084116406472768709113030915308410565617764394827427154923321461158387012978726512246146545834669665093228316853342805604075936530371665576147966721599968786161939347726656168798065647411457701453987215491345496003650288850096338695703984042549594979897253521041581573388369367579323607093487743440894765114619634001789457486407909224339065748496715380572175183589195611952939575073075140094901024063428239223964510824958346570603142906309198033196987949067156046076497974760641964978711558209708743776024313916111738542765749928287600981397080809041007714387564206594515733287925008053261840295560398311905155157989225181164097547541'); +INSERT INTO num_exp_div VALUES (9,5,'-.084049034261605466896663277055600903951276881294745183935726262038673990196778002490449355450474227878560465916800470848046625257516764244432096856845087412397406701521972651300484716852035267197801389708234913163750232707469240634303111868882057393120649919262424619226282082184091177505826009374043368623853156698509808569378758387708910629731005691079770517679511879694426434724918004419953301426679939010592502325130576915399009756468717124460489039474155719834555522581553817856854607844133431854471292027873672356863673617090151801474016666978499651970627896504709551656249007718965259502928591648533670568214972768900993459927860068104745163979267716597907297073374689384723943955361288974065531322408839914599555769945298758102515352082822617428033648130099822033393662643586331479103933840387663729387'); +INSERT INTO num_exp_add VALUES (9,6,'54863480.39378734225015137845671346015520435061071252892396685718794832880965812803098645730572474084523997120024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (9,6,'54863480.29991319786000907666775218153965190979471954789486608982086888806174552071503445206767644474235809840024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (9,6,'2575131.137912978352131546639620215541477987701194164886305951830806120142596646541302305984776928560906754259789485960991272272782091464270104432109904222200473616116525297615725803495463468272171161659654385929185160689572943852767523792651123455283534072794326647404332228203001469884016996499768656263775233430922446983838511590562929268821678518640501686017030536100955531423152839988008496919169395159653034847677470665418765966542111749439412'); +INSERT INTO num_exp_div VALUES (9,6,'1168873084.346566233232746391559830634361431940000227460271861554316197556566224118756340501278103405856646766537018954185964066240457859194626558143313125824412559635129130086906976028635444060218797992547370132082916380788496584864016645155338102476357490305222392452114945853620686975383081427840791892729407194179236897452655907829255937027286698570784397487382242990326347080472574546312522326038419753951437799831430690304084087684303035538181812523230890783372773953961677974396907303758903934808035747944477277528267001070234880092255363221274303820343225415479126819937070570562654065195009839593938440374000473302075568746771126391307584779249330981594640387657042725725493800876630516005713789705652827210295338592985225924959199657729900181287069808881130884115897407246324220524401243575641227725030779990490'); +INSERT INTO num_exp_add VALUES (9,7,'-818934540016982261.65314972994491977243776717915257186979728396159058352649559139156429817562698954531329940720620096519975256547379603654362598494779213610069399116912987384006656023443527501447464682173445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_sub VALUES (9,7,'818934540126709222.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_mul VALUES (9,7,'-44929599044588573810654775.83678007633232843418115790847152455559258007804727916986432256198687661496804050903769496933400455947645400628259699874770581538122521805603947464462448454681701547899144129061961394870320463199545502030106801911915987309444301341575451240764927967432593181449618816978119423290767783843864768557371257918447461479570164065303599994081990686'); +INSERT INTO num_exp_div VALUES (9,7,'-.000000000066993731076524206362744068866774567920404984046399050881532938231826344009126898802592302273719505485084766150904380671495128604515800845609713368334606489445184535043833069145643553083555507533900955661105251251918425885537513359541698046533092111969478225528665278023069818968531644884466229545497943710817187632203193468836772459599856856811131193744272314519908999458320275710240994009061040198159739169960258978462113813370513611735006229733329565083659159456172425715216475781507996483885669437855000029758892126410922067202159414570164537031153818197618428471046051340835826664787585016361564969663413176434498159140395476980277574789931364078570781760777773379636490084338326576889857824344578398580499610233575273027387501809967324874264742269453420400624883982643066864175851881870402856698'); +INSERT INTO num_exp_add VALUES (9,8,'8551849703.98748751358673528924211852802333963452553842636251612056366144128630740476125273064380199240146487881028508694029546139131732304020786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +INSERT INTO num_exp_sub VALUES (9,8,'-8442122743.29378697347657483411765288632848337412010634954368317355484422441490375601523182127040080681386680920979021788788753447856929293579213610069399116912987384006656023443527501447464682173445385303315267086044455246361273561294141518329233754041352632499787199926225490924591851865949646448441825186059741089695009429827829188117479084665641367'); +INSERT INTO num_exp_mul VALUES (9,8,'466174236688165594.9218054325256670866060556227711696100465581464881295978997280335378678072434776702952026828137140986670189756965420183565968027969700090735690246176791371115610886533930223141650377886909408268207750238603105232560663571044993507074695683027062426288270199495225881785499139012931143826099668999261931834700467395442768201666740663642498098541516326470052372008385656719236306238735524802875519713512894448940917708118676095378518264553310312628830009314653641136566040400'); +INSERT INTO num_exp_div VALUES (9,8,'.006456816440893715330247418029019114736889626790871612141686117271826070935285769018710680035004320626745647926106882508048159628931624522666638442625219959259156539178378186912871506893482633695438850964052285542425753626455183282159259999492971992739484319464700978750304962671213318202670228197968646486740006148091321740497272644910882302412140576608739962605210964504469426861972705740810533465451230811358870068391007718532021526225893542801514255726272411690175555142385382688220121052891017808391607717500701760375927811435030512071347521837090721052128992926357375527600337655573639413811262412492632491693179011503973930804928749370652038245414768103001067902012962988384812280453070895781287237746786414435546976395632454474312533482077585837153357017362048554313154580576238549196250793055676215164'); +INSERT INTO num_exp_add VALUES (9,9,'109726960.69370054011016045512446564169485626040543207681883294700881721687140364874602090937340118558759806960049486905240792691274803010441572779861201766174025231986687953112944997105070635653109229393369465827911089507277452877411716963341532491917294735000425600147549018150816296268100707103116349627880517820609981140344341623765041830668717266'); +INSERT INTO num_exp_sub VALUES (9,9,'0'); +INSERT INTO num_exp_mul VALUES (9,9,'3010001475769225.8286280957637941018500905354415197182850820227163907782811814730309044010416886791014702373809932926301368137684091094408663914110947072451332976891128659038142954192986392936981664792370678656287232795203974766040821110221158579481177539669363513848425151485663431478439528936592701070340012569297177488556353760756495238304538439278682066056721729656193616571456456325016960870401748115848423105783116854283646624807603476682295234280408938557209608025246638166902335016025467565869375885610813662767004038102486303756741615124814580306266901273803721191779461890468156043551004644728343579032524687612403663816107770451694666844862368101122025340182510019516924578414085461628689'); +INSERT INTO num_exp_div VALUES (9,9,'1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +-- COMMIT TRANSACTION; -- YB +BEGIN TRANSACTION; +INSERT INTO num_exp_sqrt VALUES (0,'0'); +INSERT INTO num_exp_sqrt VALUES (1,'291.964716019333021494947753821238960905461614737525349376826064492714634914263808902604580614735501799528494357560837535773816469841426747889103714048646989532842972129124080559131220979335403729022278994440514872845756198274805589586120535745968205107562348427941379641465378272611453955517402598409789621997041856848783989993820946766177453801729783316269310186191833995557234577548740940419224137195404391193633808203715191863638616433190672511651125299379882126530500870287424768024674231651229908224729856278167033444719242144302972892419034855417126978468296581589282861879645409909873113678361180607775255758820910366926076380306290306477790931129670172989289536405788838857428768869345763784112862591549008321546447442552533919976570125718481191724503352619626562352280522949665158335559389298720990302071'); +INSERT INTO num_exp_sqrt VALUES (2,'31541679188064906.712574384704440356216787857626740375004266523720148374188511622980520374202725176835435173058936870163875556102907654264048353814040480579464700545975346621546520503928314632418705230212623378642743044255181848913683862360044189531298446109955034944189751302497670367665492719604026161836224535961347218522748523360100432275693829501972749859329753224444694962089604095212784768854310289429208671271394086829270986183171968944659703708706544668326267327938226750760690620258967209626420981505237183055363540806281098871221581265173394406715458619627534396065960117454160969749739483126059760636526242783235685190739315590041294766649891987044641492234243404608847939002062827210734973778130441825067858641461599799772535304379732674727995848518807202053316225824685704785148921785964036119338754973714515974054'); +INSERT INTO num_exp_sqrt VALUES (3,'7765438138915239878.949520541017683429203286303188179443533225547096446554008374834292278237558244698868300666061834105683999048386497322007336816482648302911579331582895326423063492240235074387242190187374869842856897538718280497895072291181675294000739548676781615025944675912072664211455701112700937190832332966000160156597821149428032612782336278939437593991008833233156511435294360065004167893309428565243314846456225604669764879344135321428948841659419438769652686215993544390780212859309497190065178705035652106614050448518931820975038314187040226298661787490226917902356569717171481159691409131778764973037046501816919243659681416263730519167614043077472097520207347950292377914586524327206547377189493301153212000966249655331053184913579513686655963686155890934436604123384536027235444923674128269748280097789270784333442'); +INSERT INTO num_exp_sqrt VALUES (4,'2308544622905.016172868282330339228589083058636874526727829838244942341440716909466939214393597311710652963849541394758298277969240038668406494621950956862959196896847352631445328917063551082418729435554972200530109505384839391233286173517804321019323644218483570886304028175359854335870835404627608254205407525763332087823548640923282031978903399118139052814618531713327991857575390136755426466065839913887477577516426991104516201265995293600539957187007068885368699949673989051443005684755994465547159213587471972139403333249259808344536605314911144950465968669770276463111776581675944967401948957460097365849699783091843609965345747287667911324039374314413430490112443463386381631812537639503425989372084906324702158112088898424705684574998783112519152403201231176840068666882123684602080460378627639651465436618032671756'); +INSERT INTO num_exp_sqrt VALUES (5,'25549.082770905117529972076915050747181125832857399138345044265535151111965091602789684342996759657333588444489085160336703294705499665424408218434077722506748278242942379566431768762487954917389137120540138359870652558814224523699917122023018717544160579704907452934297025088008618627873220397030397424422097405152321366495319708580932627092620533785271831833326130796638935296720064431288560292191928489034307645738331451165431755179025359993690642194334018457793169983249853388987495489562746304107188105521296156525984787815685365255240654972150342496329030279439124533240114879332406941960563154881888172285475336782757262639979527682925214971861707635327995621436598536743180180978457735632181738067997521785965451385630326464388080990200265186437768409003553910194212076755448477164192901658547251079126833187'); +INSERT INTO num_exp_sqrt VALUES (6,'.216649653115510782473161631235601739254284877523828136703593069337209747459679979369185882839688430004369697316986054374456779366220242645866798278985273820408495361607183119980716020227424205519727777568954933592987351750339481522149106749713967143685591960510946511796062486795368200503801097611436787402191532618456991115230272084771674098613479989808680789347124789253499967359190605681912854639520917409710307182238065185749856554472717209097115325999946728168357936779767099041518574001682560265549916593333117469681763348860131760281253987626822958726920016922608371657319505153308390495179319529587670415367205193280809809356733443291197315823747505896510820272670040485083775482983378341120809542502350385555577946098824446199419354197416933858522419312733314383889554606932774046771497129486979593226'); +INSERT INTO num_exp_sqrt VALUES (7,'904950020.759072496304165474991957396337281699986101765045213964054286624338102141970514306010139529492299343393832200631760194440206005974547202512275476562767685193838576516154915404389465528270010938533075930081897392863141132529694804621418663424569202655893682412466871297412964570322984865326770090075582481194532433411398133265643849129084449161396724635797324126396071308557057830046688990212282866035593809633839882468628249964862932050189148498591642162462777480125024786829078066012617362076651920045684345679767223337287825546294839320770903419463644110383560050404456170063805115223954191445548226706113970164823214416171441655706141596091717118495955441099867737827763335880891937222647408575142200256804313345924443344596462585960919126827045197885802122062165934504665811115031150357820196176799560314653'); +INSERT INTO num_exp_sqrt VALUES (8,'92179.098626752893864900181023972781406074846653380680747862421481598042923358730531575438403865501429843141967819802251116774924400485954931201776260931315313253827346015775662310076094882239170765060649024538403329505426563390044695320714825481746233901773893996663258170360232639353378395244461670781152793416950717050461856097473105730100523010642696332151571372764781034028324977128554099993021459338419164426784774496292405945103200724413639660488309795423335142455569853549710795692020963174011003447023610692365550245567840477105794884132665155376243735213346877116105595296043532605899184658904822980397411096930267453332143879534914237169761039374689145860503772331147367757318826885494994339695470190886515765452545019167989882527248872835783707554463866334705735781549392895480816605355996057201589681125'); +INSERT INTO num_exp_sqrt VALUES (9,'7406.988615277484686670011157489572203134420118818648711986549881046321377798441006745317356200279801348355202517703531020643333388857073977704009782384103170022716610432579974132111487533733493986910583223121269323909760573942980360508642443245341392335557152177332615977623338526935953706604224108508582338123915133189529507760875123300397933931420500010248194253078118618381590347297853307090813639981736227771834732256867579490224181748450683295253634852775448770576585177080941820456051588076218688792321741398867304684922665590162004919486643750098085197190000638539994723704724550600891137853975703823903659121582583388450687255538838161486019214242094423895463814933532217776443473765708693285683261505695170847285063013324823850724236845500162436661946026097459146424122412596018946436589967013641971183281'); +COMMIT TRANSACTION; +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_ln VALUES (0,'NaN'); +INSERT INTO num_exp_ln VALUES (1,'11.353265918833698201334218522735144514838241118349715803442713722607336732214173255618762341321138898556011520430414052782971985419141860417968593746833898952016980791997105866598425597066404919489902082738711038276194174786383758877067916049129476352925010880025206629976454341252818402788928939407784629386362069592202090897264194883276572978998896242281239126931595483958092059051047739223830394259082355969005503976135238921488192773135287876801394308064862257453262299764712613486466254696464150007113953810688169396432889052881763511661127351872408811370081346456019961324265446884877073712053408327408917588393884214304220369626106333713688792094943405258431214313197283237071070354654837081449831786573831004911008790533179001070424813584405346221388686999574752038655226138085374176702005198770598232862'); +INSERT INTO num_exp_ln VALUES (2,'75.980172429959420723484178622920965327708652620924912610122049843800380131746381968266727388919414524075492921510147435877107720844487333947572033626887969846858337336557672107987074468763307953130616555202495401302128216460637786993535376622372745654109623249396257174895352222213037880060756992073605135503615371392439827458529942230210514752764526895030759481226199720092008002458654297737883219558685499445394647863430593136350562417924068100891680398878483362058595716232013516337079804607378041880078724811071904523716775991447489914128580100888252698281559809224785596795038122963619830942475652745611551345360922016753939774272970008770647516790944335173711498988149783075646985898883858697162003144539047532603946093022417842140993960433780913606807466518632121884254341907122163281927271483110212890483'); +INSERT INTO num_exp_ln VALUES (3,'86.992429107491709045555322727377654177072455841678650084144967727028762699430180506209786297136121512625728883607972513154010138109866327600596617277403558404624813332464431424791338402731178416819791932126837396086742033973404980654712734845137075562739300866280737071167943367603243180515859476717635339619107593771719314284984269343476343816253634799874584843436046260962736006310389088154751401911743739429257286834178656182340416539923956100441369280015412718483971113838923221170027312390404790743389872757674342133486652087007983701950040432125562287337697971646750563062524010514537132255605131615248097901911480464339325353279118429890601202554448469387179349495284716473293965884844451619766312048304583068386805927433174443889441171878078987788018564357316138422561213329104267180509029624308926098065'); +INSERT INTO num_exp_ln VALUES (4,'56.935276817066740776567329017240462885579486075188456418197311631774373422196025180114152248099799048545382060930401786002025479108787121595516444894009593031141335985913019897883627990503003577804436730367402618412514152465206336556967419434371593632864308139215157721913158949066717186782560422199668568894551013785702491365073449320535603830475158258853167712460432995074161536886421366716995573365924430692151761737886552457036412140640821310927642146210426044265504978418405684030862182425702683702307323138985481047994648222224089112998195621687911787785594701557252468626097576375468916953563766801336922479861708649876362257086586679701715813254414915314296890025577780265459584203893089574567331742100451277992780400302806430264717887468808962517029442262560742822875484362427192693300423729233467613910'); +INSERT INTO num_exp_ln VALUES (5,'20.296713391219923821414834924710998522858242536565236229645868008008504475111229451635162536658197320282791428572861452713483981402773630985812066048575864982038046409484905688236579134672910905547858248343712686247795669280482288748331949478864729205285910525962001251260319741279139167559906461672936902355959755164523720443059989357054368460911050707727029320725144824995614445423492687177126412520389766864793826362309254124276325522276592246655562770110024099522184080118637524912964002223613671995639705240767929562023556724031894855094820328152633412077228479168557819219970917880393852962560319397442566813746504969336443969816954424715197797253670026862362130664772772977978222813915593329422557592316429203293264572088112274848838446633519530653849595288125585730314673691986554304725866754516304420665'); +INSERT INTO num_exp_ln VALUES (6,'-3.058947463851998053084898503420969773173569760507671013593014983772013099601022840164736581595033399273677583253456908293015637115395777673836877852797643436458673662566205707359569792482081945396989472318998080581824382006377064185813936544714612287417301161454496258176319380348780934551188852900784476213986897306897793456700682073399936398243222895442594762628402487110466705108765286617060826203345783502301472192906817785365563881556293576463515218574477264521950513789471494214626744754200844840310516235570475410854073969787604451971790833680742315518808178608136598148628107328076871698598743664423452623124027059698038466681488746505289551548778131621576387262707147068500249466398507704796800459013580425992071957391417767257856002976954566094297724379688683375704613872658653366052459242767328235849'); +INSERT INTO num_exp_ln VALUES (7,'41.246780548917246608934265057073076900048579756649769602488660179351587788197892095257027979113051775079905924990472069951828742350559917110289416201523653941731339141666097617614477426376799479821365070373247490598890520285155435501242427296281987676879064510605563522117334502131946383957407685328562874307957108543536378261847119286989184256009392692140821396916222386573424618796707564187152459973446833193743614720624765332006827171872712331032607870580880807058576154429597725560836582655488602546786785520452359711161305828045237044625934404295366273012300148250900116489718279757540843657039519736455668388572899273464839528462223812926410544976290646668870192676914370659142463304861500879195867873346447316374869974900582948166687948531910220128160490935170837209017355954301127162240133341813847180541'); +INSERT INTO num_exp_ln VALUES (8,'22.862977375646110045361670561177818139082238721442691850491173190000619222046296383571431877856442345505931635735363450488731186880557789439424987680284612480261693386095598289519783790826332183796775862215503493910816035128476952347072320869461206895223935484838130924268616681347949695029657753251443811448783435000569829291535036468240771401957519222523032235686030017496209956550934543164421459898155836108824017735809352580723262896259290484291175350770265895317482371895188221452083719817251845416195168686335127805092334984596224320638378502008767433534450949989322562311171685891891122105437154553106840103473941148230953978989145470651955269817951560544095229079088083494695756914405635176899994279484466773598435268700064279990885608144109747858515514066444373797446449729058958270758597627587968112958'); +INSERT INTO num_exp_ln VALUES (9,'17.820358481980064387183481028572263407130633079314879566896470101569251997264841660326428805413719418277889123643557369421967068805165885825106611310020187894256310674762734896979157570968168599492401269694048046876387337971177513661006711375440365724346137980004810780215236524986274043416621637509807126148966029923572853117418545426960105154053049098579812135003711132897895016476695223444397389521434633067499404903493027304737402519428197015899833229473322655155458942323004249812974150129789653469524573801259946118454333405580647485894435301530550214095993989552176497867244278699359917247910082169086524111229983698975613609318418313798992088206507831757327320958918656453341769110558376097374227592021075267882222057385413453949580066342977546145482215220982989992069525148522710254796105001938615214263'); +-- COMMIT TRANSACTION; -- YB +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_log10 VALUES (0,'NaN'); +INSERT INTO num_exp_log10 VALUES (1,'4.930660740129727276654889314296515979425461685461970306647398411855044094312185293195497201658739777714943974003690119189101973212927970410047992001003936259467465542044528955416040460487922970233600641954269411521809500203864460110903973264337093883907933081597350982496469748131390809569321256206859934619579029279954574676601709408712255490686948453752571699579252140062805776361984468580258289509013081691778727372026090522694670379557247829136504595898935235926069699309392675806881162434168418505908116911054206058735257796918687777716036307205415038158583184624809880157060625643069601549803887864772092583549388533013233603450097615537162442973385137488450178790573546382354482351187412256794374383453695483855501587939419102008302408157959291557415763034668013452188944554607063362933134950906875499201'); +INSERT INTO num_exp_log10 VALUES (2,'32.997769620388965086774969704518222090258389987679691893351902336370051104718852164011301929506188893338106627980171059175447833290713847317665944354651476245003161501753612545484635275306181777040447675475670149066399611203341262105766118892586541910243351018829302798733989560900125591073082441126709911019648451232244139674063434385451279378543163944005973452562993913383659295688375546058256196254319767218634546732685705517341998116744642480938405113447415486950667007645850519659606476727681944251201236366198374488204017630268083077471516734133869728427050843306716313813724061560369884508660845630727190444623729815564381063131729592825825486515070406390371638817503915214206586939112681762984038333298146999891250107667687034785493312416966635780188163871680959873288697497561452228182734430749066579749'); +INSERT INTO num_exp_log10 VALUES (3,'37.780331928743475574895606142114739140772838801045013007323050327909196792739138159615327729728110344767302636436234256468332011934881494997184865617793179255006442447189720642997935223133982347184994174261506212652322213673745795726283311685835974151422721233207287206894148660531800622455957268888702309499182978182878524951883775154983702898237404558813230370364953160102391101897560104513279410610948028599674950811462114131673380477843456965645417025376374320207504913806546872166094337441573669261285052323206348035827948287081776955945081345131570610652073053464020209215624179904586956137079321655773178387441622685682721151900601340680061607114354850640946256225260430676099781727317540719923791064452012925902993317349390523278687089530234444415688602090547516647302454865526291471706301790881694022223'); +INSERT INTO num_exp_log10 VALUES (4,'24.726676547286224970759328746582840552419566534667446425423046931401641497155587075591229106937829957279943690528061985864558314570189069764367933957499905044566413640017549478921384160584906257607957223101377816440084188042395098536074479064548620374152344954289432050971466476174493306432228880930006524504974367146536665170956555486181410864034862861231267121149652317599303804477688621597163730470970207231328339082779056152481480926452142005969020950341307977091850953883445808399574256295803245530993204179747743812544604144379381347499056545148243304041538981954204310612049423688645476667184129189153715486929216331980316967699254518020077226689317148303152585009031597809279387172427408557115400021035692880631275593381822805377317270568779655383061987766693697518921188619814204902583361096973421134004'); +INSERT INTO num_exp_log10 VALUES (5,'8.814750626578650238811431417807018895270298639823442501111235973209197727215795256506525221092818797578008152140054383421240180435087611869193019443372556081555311825248667278358330916098378127100899126895012782320751838528480712942601038190627182482614147263228588284866661508052724762701223357327343090598060805245853527435948381893458352744679795853650453594546267600486696643924152372736774331080527157374379043696696647158270918245668579680394279565181670004245143555617589138267976417280970718829942998800499312890580011246294669585429723974582350357991472101919333996770115834067969654217063942059882195268353998096891812525364797586486311202350700339609637274043915687880562465121559531284337603363356183320193656553931871200575467929714875483123706358278876389849119105053294688326141759401230994901405'); +INSERT INTO num_exp_log10 VALUES (6,'-1.328484003982869642690619298690906747763234110040562640557173509402512757735587333095924652711056556491908059708986413635120656426593745303715671199761364516107844087845783714418487426723538440387069985879601248897538855843115404484229652166941838283489828419407478748732927617251897244190697443966424660881366993754577233476597163021768156814527570512834684713730559883782625870597080940193303268818336816535968869931456641949301731046034660616615392129109391145214470757259042172416816936479713743188047425796931722546185493217275537303458837771965375448968719169174136287532752370175863826715450565025635651343928205805494319778539652563499901671319955144823432132740582617949774638538594081514904904341299199113721131520557004571803778698005652464301037962272085633628653321081368256925971558076970172779715'); +INSERT INTO num_exp_log10 VALUES (7,'17.913249188669140643510654105014358282516966474257460687880559542190804665566625978925406311113121982595279826214959603627387555578965653325278444455875162277940655989601428868642914577248262147833499137348602966573601719040813549936948178463592211685237720748377879836890106515699728652218324794927458352954247096536337594789471529493944292143186953509162522579060020018226817623648563806559917579317916242706559131476179714031602207057714677845347616752450567251644277767418397621490301286115159509360375419599968738067461569666699939732107480135216621373057421990702923042287910730395998082514702629760389192370666675364405730936537832803383367187639209534697198515928978064543150195911463663617683085348965065679311986715357338675515370634753254774665197233934933271954463040729779956682570415317734489164385'); +INSERT INTO num_exp_log10 VALUES (8,'9.929264914121995501917993119394933531225401243275938207624866270551448544301376913376130982251708700134720886862945040266148728213253651323129942781577143957084726727561987639140151337848818195806259935747329665025823709044567138449084349729747202164413995795609659711723455165142329822773177102845804114214340046404641970845707372809306219463962664551623665322610139794354769767829380018857313559373283673392337954610346290037758389035140213224696023751541663171574697035012610534455189013755134090933979479069288110010954211669067225249755249337768792642303351914884187159646984708862430789018895140670365476746734456807215043628059581947593694929159076346249490593187993386780521089745819640214783614157516171005086731241769146397577246387886107367648843380733370112546792442909347322732196805316614555689762'); +INSERT INTO num_exp_log10 VALUES (9,'7.739283354261751283625223433456284905560931805428759681411970457812279544250432389511382263439324085689734710188041049046660480575958686859942980599595036769090747781359217248301544587434077376812293034848418204834388504169166350770257248896025815531248627658465029806509131631454856186387892627989218208026727504548130018922325585619738185507999433763118148418722504204066578294826264005398891049629199412773138457218976050467479292777172717500219850781664314597312411301296201533610562886229900497272268364496763758868455934979903774531992886483396489868888731578355541611359130188566524240259770918423445785338175040098706500034487703124623745259139247432324145633151895802637182446905097253961951018926565652497920605819785424451050191604602898777804133717341512568151920576684198443843944721398831404081859'); +-- COMMIT TRANSACTION; -- YB +-- BEGIN TRANSACTION; -- YB: avoid transaction to test all queries after failure +INSERT INTO num_exp_power_10_ln VALUES (0,'NaN'); +INSERT INTO num_exp_power_10_ln VALUES (1,'225561990715.277245515991117670624124484084762557459065170589803293759247930753528436379932442146759103295277479258327642314622036941865221478746258727236601688778946696303277607709407496616423493315166963938393760548678730128692212077086588682984700837334554241405763691119669847463520746595280034536307041368063462023793177898200220207765205127584303464304601759554817607633012272490650155253979182893585119965271975927569080191838676053084168631217591768468344106219831174026139608715965691941366334940196517120885214887008671956523579678156919416435031020452971977153991139145404842034138317592877675821045409772456977018293365238179815614004574330200783530118851005077771478448804470170641452481992602803877112958872108069738434946694089025321283178188028224338756015337492913115267362635647236447601252924834642796058'); +INSERT INTO num_exp_power_10_ln VALUES (2,'9553718264533556311125292459627965006385666643531070061102266984368939757379.536714147420215784125170401370065894858487440153494392538261078415409784085960333028254155527328359894197540839556987826344995348426293585457768226283066583722499658006242709930685932246087653832230889613022921575445199055131152661556678809191264086381976922223866204038615136758192929883317207903579770917317641181652055458721731297347443662717939116561947785705140374908203404860090658919334137955075887697259604047657534191202566335372150375993361370075961180728155127447781364264047857624746079509591666068708743260905728661917791822925979235918475633100283148558978385583805341715868143937062092264994833222352433299015979561976964779350640064096690062929265992966564232453102431600199173711947391200249130712039686700111791790265309426741120465259677894665532560198051256215915373145226284270408649736509'); +INSERT INTO num_exp_power_10_ln VALUES (3,'982718444846268846508445482774217796844461660819285525931206164100817251856409365450682.362683768066405322653747385034480250394145008573806022660379219602846285813744865438912887625784087005970975437905783802114553690522787857272953842288090141945268495451006273685577260054069522075046955466204804067271437138871789034722069934693546671607506851844248427950939791205412350536883779850165603116191193657054604569586553874805856647223849267039531773072343908345333155562072887754900969504551717514980465801806565999410206735831440712124661645970935112535081991606671600328471264697018198676317466846450405861359235297846597981143547119390922405594115478086038680663368675222949247096131378724350715530605691796680604309063173515781378545860473572389718345696107553363715518601596249508215455106779522851210398208919496668879040223859884166805448827948087400426315425231119801173387715922086154065273'); +INSERT INTO num_exp_power_10_ln VALUES (4,'861542720105376650266753999919217194383259935058507531116.774511336660822591851369622743235084609149542494189385785321912210129989390054947787009383210009523204976629456268332186620016067379702483800883493431423160815760933380418976582725913410929214462739708321325884209636272001805871036779154087677637129248122540412937033791526383240502286607736226090213753913654673523613612439527815137888202973659987501649474772884055648603290154867585312925699571949539600328906295652872654314913539778815035321695215634102441494403825526533235061083947035338872599854931230001361227174477274708230470794066733245241594719912710139298949856243576688344051439047966427547889756037265151798639614843866387316916203238068277912991427278268083231579195846744438643659745041780103653332041031419793815914447232121937821142169172566753399257291244398531365781832297786941359729799400'); +INSERT INTO num_exp_power_10_ln VALUES (5,'198021976607570296508.271597639984889464620426933601643322058775615235389194561064983706229795978402690473201671702614911129095149240715527556855309177671128442458698638704394974473956869419481315262823632891676087912529523219333012290621046361106033860210270638559271706082115529424772192777643046125905852037759566224116373416253787241195450409652089019290072319861181399387753223422998872180810295299831487867222464355713552301775702554189470264147325049133532522718679336524769566984150923939420759804463781082299907043016120177416779442865059261387111806785876531152192378576258351599534512031062777609734092707165605364139201322351960602280089186180302246827234844736393745487324460438448807241887783263546165171099497316415863122023114646876909575845860402164818094500541234974716577550807551946414081410743197768993152975501'); +INSERT INTO num_exp_power_10_ln VALUES (6,'.000873076977206566818052116526263730226812004454463281371489634779519089200224205946321120805055212090024554381349223642352209212670470260295303361873760972918129853308169576675500721645609379420329169271088810484607337679253503247351324049221970104335289487989027621978310506220905131150125321713385148268584530413680037620544212746920563790371941626294733473967065607791756894237438288480748407449237446113996117912144587258434808327522518688617394025018756570740098795745692805352377041347367240475846033282850136270250633825482156304826383360291164928049344226886150285595932088884965511963310715773499733217615863523253012606066583814112265708693122563204149232245895551314975524172504103194858904869273185785182598234060315036187756490539352752560361560286717869643902435677448962235275054804452967413005'); +INSERT INTO num_exp_power_10_ln VALUES (7,'176514565873872717825163931126806100435750.096278384530154766967061948052237623936423931849868926020451465515367348890410352640552194499619062823622476972850692557798609619250753020363520533767813563613425606228355802781302735485038377521515850536680425059519814786118919994914180918228654298075183514200191737597656810036850772127169441661576862538643715648802139886576391427423689320082366572297580054381937437005879583216745596935643579262248665490169331304003204939561361718554509909313409421397022626924406091551900222555950699170864234411017062042057683304265485826061096835531732950909546314722726990314852356462874701181085379772134121978510387397276859318242238150439474660772561390798432890789762504242822787017140808209820627435991445529404692793744568204608385843245177656436105160780897472099970336514833257055017279707999437302548655364559'); +INSERT INTO num_exp_power_10_ln VALUES (8,'72941951052009383458167.300747500436981484566111756088702608000390737594784514635592222758882092500858797317505303492923829092720870826490477962201959426813271424853341826896270963213736922458746003100613943600855942721319226948714369219316345322636075285343544788982588956431405042577296229122673590336976893594798942025893296105815818487227300314490440902574022885833779324177053242170024559675073866612316965636832258283516275906085642459351367507561963945012828379111856700009391438637054015804558386733558956649061672420804826896303889067785497738203077050774825608647969196321506624991188638449047860249367840775936911749905927108478444112230174584693363226143549933224252679398881354887872642908328737917862751077365602631600279486028043329404269490375935308156815477700961014566228692743960491745353377403533037122586797765130'); +INSERT INTO num_exp_power_10_ln VALUES (9,'661239032819374816.097553651299556484820492272269662685578275493609248662925676004753503494252951243895572437264999063878330704584509915845096232798927524470286655554736724913758600775591269525423912692080421094644542553026831758426157681271572808657664918053119324646138457659418857926209701677786068580819823633713337632456905824562235373422309621872998037966404189020165296080436871220718574009921789858751384547836431858428729570977259373272041837411903005303672798845573379758630607982213326716018594073712340609488043353995410508475153538231445235003980586600882223782814368245305160648543466496726973755388826656879616734762068443462618454921858705377028522664844761719759342490380417060255776725333319537746890406213693117052223545525717132695297770810635066731941724108167146710297146989770382041617889670713111888375717'); +-- COMMIT TRANSACTION; -- YB +BEGIN TRANSACTION; +INSERT INTO num_data VALUES (0, '0'); +INSERT INTO num_data VALUES (1, '85243.39540024977626076239847863600785982737155858270959890014613035727868293618673807776733416230953723818527101593495895350807775607346277892835514324320448949370623441059033804864158715021903312693889518990256881059434042443507529601095150710777634743301398926463888783847290873199395304998050753365215426971278237920063435565949203678024225270616295573678510929020831006146661747271783837653203039829647102027431761129518881525935216608429897041525858540380754759125150233053469999022855035'); +INSERT INTO num_data VALUES (2, '-994877526002806872754342148749241.04353023451866590387054959174736129501310680280823383331007646306243540953499740615246583399296334239109936336446284803020643582102868247857009494139535009572740621288230740389545481395'); +INSERT INTO num_data VALUES (3, '-60302029489319384367663884408085757480.2322712404088283093870869198708849258097125725036189625900174440196153781995220721511009855207486224837798752903681993777275846325950111427710563453217985216966456785944859989497422927661683538629473170704026975786513125842675604577233871570629808699803522400038975396500769162308448069085909755023233588510630417065084295051270219462289785473643946404281422516357503746700705970360169619852905053433235726497292406142332833'); +INSERT INTO num_data VALUES (4, '5329378275943663322215245.24931765987630429629836382184742348353920297283690739124220773955591340709935970062776650204659187764581615597720798385015942389765692769739983054442503547211560297249686289665792078548480268091496050883021187158502798880896590227542729659940394038802461081290690995869705131152889309663639310553909874081663091069118126221594338242710530718836025225507189149221049928936955230868771875644038572888630664890573507822342998964954667474300944699078658989010257103569231493090050659723450626338923049035040974032671138430612839043269997482582763267536489504794826476836323549796385028155416935072959933315468068930689064483178204550825728947252440604703474049780550458442808479096492346910001692358508618202898514895453589357'); +INSERT INTO num_data VALUES (5, '-652755630.43456071828048833552593835051449845484289562110789582081210403487973096161149072377955192388469356112505543620695003436531392789029513380101663750625024853263344909355177280161504414335005574882649025508632900995595004153086358670541462762210415346958050909878501048483523600711486406055424807840429541335391538322886495085448421556770991545781035298449067051916630343957356635391594362639819978677032855590055900561501350354631803808000307050416047072513406855040715556454205065332997338225626635780147287003130754254277103928406089109802521803537038957372612837169223905290912251006321930223154562110264217937'); +INSERT INTO num_data VALUES (6, '0.0469370721950711508944806393077762204079964905145503836835397203739563036579760026190241480514409364'); +INSERT INTO num_data VALUES (7, '-818934540071845742'); +INSERT INTO num_data VALUES (8, '8496986223.64063724353165506167988570717591150432282238795309964705925283285060558038824227595710139960766584401003765241409149793494330798800'); +INSERT INTO num_data VALUES (9, '054863480.34685027005508022756223282084742813020271603840941647350440860843570182437301045468670059279379903480024743452620396345637401505220786389930600883087012615993343976556472498552535317826554614696684732913955544753638726438705858481670766245958647367500212800073774509075408148134050353551558174813940258910304990570172170811882520915334358633'); +COMMIT TRANSACTION; + +-- ****************************** +-- * Create indices for faster checks +-- ****************************** + +CREATE UNIQUE INDEX num_exp_add_idx ON num_exp_add (id1, id2); +CREATE UNIQUE INDEX num_exp_sub_idx ON num_exp_sub (id1, id2); +CREATE UNIQUE INDEX num_exp_div_idx ON num_exp_div (id1, id2); +CREATE UNIQUE INDEX num_exp_mul_idx ON num_exp_mul (id1, id2); +CREATE UNIQUE INDEX num_exp_sqrt_idx ON num_exp_sqrt (id); +CREATE UNIQUE INDEX num_exp_ln_idx ON num_exp_ln (id); +CREATE UNIQUE INDEX num_exp_log10_idx ON num_exp_log10 (id); +CREATE UNIQUE INDEX num_exp_power_10_ln_idx ON num_exp_power_10_ln (id); + +VACUUM ANALYZE num_exp_add; +VACUUM ANALYZE num_exp_sub; +VACUUM ANALYZE num_exp_div; +VACUUM ANALYZE num_exp_mul; +VACUUM ANALYZE num_exp_sqrt; +VACUUM ANALYZE num_exp_ln; +VACUUM ANALYZE num_exp_log10; +VACUUM ANALYZE num_exp_power_10_ln; + +-- ****************************** +-- * Now check the behaviour of the NUMERIC type +-- ****************************** + +-- ****************************** +-- * Addition check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val + t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_add t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val + t2.val, 10) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 10) as expected + FROM num_result t1, num_exp_add t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 10); + +-- ****************************** +-- * Subtraction check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val - t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_sub t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val - t2.val, 40) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 40) + FROM num_result t1, num_exp_sub t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 40); + +-- ****************************** +-- * Multiply check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val * t2.val + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_mul t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val * t2.val, 30) + FROM num_data t1, num_data t2; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 30) as expected + FROM num_result t1, num_exp_mul t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 30); + +-- ****************************** +-- * Division check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, t1.val / t2.val + FROM num_data t1, num_data t2 + WHERE t2.val != '0.0'; +SELECT t1.id1, t1.id2, t1.result, t2.expected + FROM num_result t1, num_exp_div t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != t2.expected; + +DELETE FROM num_result; +INSERT INTO num_result SELECT t1.id, t2.id, round(t1.val / t2.val, 80) + FROM num_data t1, num_data t2 + WHERE t2.val != '0.0'; +SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 80) as expected + FROM num_result t1, num_exp_div t2 + WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2 + AND t1.result != round(t2.expected, 80); + +-- ****************************** +-- * Square root check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, SQRT(ABS(val)) + FROM num_data; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_sqrt t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + +-- ****************************** +-- * Natural logarithm check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, LN(ABS(val)) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_ln t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + +-- ****************************** +-- * Logarithm base 10 check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, LOG('10'::numeric, ABS(val)) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_log10 t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + +-- ****************************** +-- * POW(10, LN(value)) check +-- ****************************** +DELETE FROM num_result; +INSERT INTO num_result SELECT id, 0, POW(numeric '10', LN(ABS(round(val,1000)))) + FROM num_data + WHERE val != '0.0'; +SELECT t1.id1, t1.result, t2.expected + FROM num_result t1, num_exp_power_10_ln t2 + WHERE t1.id1 = t2.id + AND t1.result != t2.expected; + + +-- +-- Test code path for raising to integer powers +-- + +-- base less than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of POW(): +-- +-- for p in {-20..20} +-- do +-- b="0.084738" +-- r=$(bc -ql <<< "scale=500 ; $b^$p" | head -n 1) +-- echo "($b, $p, $r)," +-- done + +WITH t(b, p, bc_result) AS (VALUES +(0.084738, -20, 2744326694304960114888.7859130502035257), +(0.084738, -19, 232548755422013710215.4459407000481464), +(0.084738, -18, 19705716436950597776.2364581230406798), +(0.084738, -17, 1669822999434319754.3627249884302211), +(0.084738, -16, 141497461326065387.3451885900696001), +(0.084738, -15, 11990211877848128.7928565907453178), +(0.084738, -14, 1016026574105094.7376490817865767), +(0.084738, -13, 86096059836517.5178789078924309), +(0.084738, -12, 7295607918426.8214300228969888), +(0.084738, -11, 618215223791.6519943372802450), +(0.084738, -10, 52386321633.6570066961524534), +(0.084738, -9, 4439112122.5928274334185666), +(0.084738, -8, 376161483.0442710110530225), +(0.084738, -7, 31875171.7502054369346110), +(0.084738, -6, 2701038.3037689083149651), +(0.084738, -5, 228880.5837847697527935), +(0.084738, -4, 19394.8829087538193122), +(0.084738, -3, 1643.4835879219811409), +(0.084738, -2, 139.2655122733328379), +(0.084738, -1, 11.8010809790176780), +(0.084738, 0, 1), +(0.084738, 1, .084738), +(0.084738, 2, .007180528644), +(0.084738, 3, .0006084636362353), +(0.084738, 4, .0000515599916073), +(0.084738, 5, .0000043690905688), +(0.084738, 6, .0000003702279966), +(0.084738, 7, .0000000313723800), +(0.084738, 8, .0000000026584327), +(0.084738, 9, .0000000002252703), +(0.084738, 10, .0000000000190890), +(0.084738, 11, .0000000000016176), +(0.084738, 12, .0000000000001371), +(0.084738, 13, .0000000000000116), +(0.084738, 14, .0000000000000010), +(0.084738, 15, .0000000000000001), +(0.084738, 16, .0000000000000000), +(0.084738, 17, .0000000000000000), +(0.084738, 18, .0000000000000000), +(0.084738, 19, .0000000000000000), +(0.084738, 20, .0000000000000000)) +SELECT b, p, bc_result, b^p AS power, b^p - bc_result AS diff FROM t; + +-- base greater than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of POW(): +-- +-- for p in {-20..20} +-- do +-- b="37.821637" +-- r=$(bc -ql <<< "scale=500 ; $b^$p" | head -n 1) +-- echo "($b, $p, $r)," +-- done + +WITH t(b, p, bc_result) AS (VALUES +(37.821637, -20, .0000000000000000), +(37.821637, -19, .0000000000000000), +(37.821637, -18, .0000000000000000), +(37.821637, -17, .0000000000000000), +(37.821637, -16, .0000000000000000), +(37.821637, -15, .0000000000000000), +(37.821637, -14, .0000000000000000), +(37.821637, -13, .0000000000000000), +(37.821637, -12, .0000000000000000), +(37.821637, -11, .0000000000000000), +(37.821637, -10, .0000000000000002), +(37.821637, -9, .0000000000000063), +(37.821637, -8, .0000000000002388), +(37.821637, -7, .0000000000090327), +(37.821637, -6, .0000000003416316), +(37.821637, -5, .0000000129210673), +(37.821637, -4, .0000004886959182), +(37.821637, -3, .0000184832796213), +(37.821637, -2, .0006990678924066), +(37.821637, -1, .0264398920649574), +(37.821637, 0, 1), +(37.821637, 1, 37.821637), +(37.821637, 2, 1430.476225359769), +(37.821637, 3, 54102.9525326873775219), +(37.821637, 4, 2046262.2313195326271135), +(37.821637, 5, 77392987.3197773940323425), +(37.821637, 6, 2927129472.7542235178972258), +(37.821637, 7, 110708828370.5116321107718772), +(37.821637, 8, 4187189119324.7924539711577286), +(37.821637, 9, 158366346921451.9852944363360812), +(37.821637, 10, 5989674486279224.5007355092228730), +(37.821637, 11, 226539294168214309.7083246628376531), +(37.821637, 12, 8568086950266418559.9938312759931069), +(37.821637, 13, 324059074417413536066.1494087598581043), +(37.821637, 14, 12256444679171401239980.3109258799733927), +(37.821637, 15, 463558801566202198479885.2069857662592280), +(37.821637, 16, 17532552720991931019508170.1002855156233684), +(37.821637, 17, 663109844696719094948877928.0672523682648687), +(37.821637, 18, 25079899837245684700124994552.6717306599041850), +(37.821637, 19, 948562867640665366544581398598.1275771806665398), +(37.821637, 20, 35876200451584291931921101974730.6901038166532866)) +SELECT b, p, bc_result, b^p AS power, b^p - bc_result AS diff FROM t; + +-- +-- Tests for raising to non-integer powers +-- + +-- base less than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of POW(): +-- +-- for n in {-20..20} +-- do +-- b="0.06933247" +-- p="$n.342987" +-- r=$(bc -ql <<< "scale=500 ; e($p*l($b))" | head -n 1) +-- echo "($b, $p, $r)," +-- done + +WITH t(b, p, bc_result) AS (VALUES +(0.06933247, -20.342987, 379149253615977128356318.39406340), +(0.06933247, -19.342987, 26287354251852125772450.59436685), +(0.06933247, -18.342987, 1822567200045909954554.65766042), +(0.06933247, -17.342987, 126363085720167050546.86216560), +(0.06933247, -16.342987, 8761064849800910427.02880469), +(0.06933247, -15.342987, 607426265866876128.15466179), +(0.06933247, -14.342987, 42114363355427213.14899924), +(0.06933247, -13.342987, 2919892833909256.59283660), +(0.06933247, -12.342987, 202443382310228.51544515), +(0.06933247, -11.342987, 14035899730722.44924025), +(0.06933247, -10.342987, 973143597003.32229028), +(0.06933247, -9.342987, 67470449244.92493259), +(0.06933247, -8.342987, 4677892898.16028054), +(0.06933247, -7.342987, 324329869.02491071), +(0.06933247, -6.342987, 22486590.914273551), +(0.06933247, -5.342987, 1559050.8899661435), +(0.06933247, -4.342987, 108092.84905705095), +(0.06933247, -3.342987, 7494.3442144625131), +(0.06933247, -2.342987, 519.60139541889576), +(0.06933247, -1.342987, 36.025248159838727), +(0.06933247, 0.342987, .40036522320023350), +(0.06933247, 1.342987, .02775830982657349), +(0.06933247, 2.342987, .001924552183301612), +(0.06933247, 3.342987, .0001334339565121935), +(0.06933247, 4.342987, .000009251305786862961), +(0.06933247, 5.342987, .0000006414158809285026), +(0.06933247, 6.342987, .00000004447094732199898), +(0.06933247, 7.342987, .000000003083280621074075), +(0.06933247, 8.342987, .0000000002137714611621997), +(0.06933247, 9.342987, .00000000001482130341788437), +(0.06933247, 10.342987, .000000000001027597574581366), +(0.06933247, 11.342987, .00000000000007124587801173530), +(0.06933247, 12.342987, .000000000000004939652699872298), +(0.06933247, 13.342987, .0000000000000003424783226243151), +(0.06933247, 14.342987, .00000000000000002374486802900065), +(0.06933247, 15.342987, .000000000000000001646290350274646), +(0.06933247, 16.342987, .0000000000000000001141413763217064), +(0.06933247, 17.342987, .000000000000000000007913703549583420), +(0.06933247, 18.342987, .0000000000000000000005486766139403860), +(0.06933247, 19.342987, .00000000000000000000003804110487572339), +(0.06933247, 20.342987, .000000000000000000000002637483762562946)) +SELECT b, p, bc_result, b^p AS power, b^p - bc_result AS diff FROM t; + +-- base greater than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of POW(): +-- +-- for n in {-20..20} +-- do +-- b="27.234987" +-- p="$n.230957" +-- r=$(bc -ql <<< "scale=500 ; e($p*l($b))" | head -n 1) +-- echo "($b, $p, $r)," +-- done + +WITH t(b, p, bc_result) AS (VALUES +(27.234987, -20.230957, .000000000000000000000000000009247064512095633), +(27.234987, -19.230957, .0000000000000000000000000002518436817750859), +(27.234987, -18.230957, .000000000000000000000000006858959399176602), +(27.234987, -17.230957, .0000000000000000000000001868036700701026), +(27.234987, -16.230957, .000000000000000000000005087595525911532), +(27.234987, -15.230957, .0000000000000000000001385605980094587), +(27.234987, -14.230957, .000000000000000000003773696085499835), +(27.234987, -13.230957, .0000000000000000001027765638305389), +(27.234987, -12.230957, .000000000000000002799118379829397), +(27.234987, -11.230957, .00000000000000007623395268611469), +(27.234987, -10.230957, .000000000000002076230710364949), +(27.234987, -9.230957, .00000000000005654611640579014), +(27.234987, -8.230957, .000000000001540032745212181), +(27.234987, -7.230957, .00000000004194277179542807), +(27.234987, -6.230957, .000000001142310844592450), +(27.234987, -5.230957, .00000003111082100243440), +(27.234987, -4.230957, .0000008473028055606278), +(27.234987, -3.230957, .00002307628089450723), +(27.234987, -2.230957, .0006284822101702527), +(27.234987, -1.230957, .01711670482371810), +(27.234987, 0.230957, 2.1451253063142300), +(27.234987, 1.230957, 58.422459830839071), +(27.234987, 2.230957, 1591.1349340009243), +(27.234987, 3.230957, 43334.539242761031), +(27.234987, 4.230957, 1180215.6129275865), +(27.234987, 5.230957, 32143156.875279851), +(27.234987, 6.230957, 875418459.63720737), +(27.234987, 7.230957, 23842010367.779367), +(27.234987, 8.230957, 649336842420.336290), +(27.234987, 9.230957, 17684680461938.907402), +(27.234987, 10.230957, 481642042480060.137900), +(27.234987, 11.230957, 13117514765597885.614921), +(27.234987, 12.230957, 357255344113366461.949871), +(27.234987, 13.230957, 9729844652608062117.440722), +(27.234987, 14.230957, 264992192625800087863.690528), +(27.234987, 15.230957, 7217058921265161257566.469315), +(27.234987, 16.230957, 196556505898890690402726.443417), +(27.234987, 17.230957, 5353213882921711267539279.451015), +(27.234987, 18.230957, 145794710509592328389185797.837767), +(27.234987, 19.230957, 3970717045397510438979206144.696206), +(27.234987, 20.230957, 108142427112079606637962972621.121293)) +SELECT b, p, bc_result, b^p AS power, b^p - bc_result AS diff FROM t; + +-- Inputs close to overflow +-- +-- bc(1) results computed with a scale of 2700 and truncated to 4 decimal +-- places. + +WITH t(b, p, bc_result) AS (VALUES +(0.12, -2829.8369, 58463948950011752465280493160293790845494328939320966633018493248607815580903065923369555885857984675501574162389726507612128133630191173383130639968378879506624785786843501848666498440326970769604109017960864573408272864266102690849952650095786874354625921641729880352858506454246180842452983243549491658464046163869265572232996388827878976066830374513768599285647145439771472435206769249126377164951470622827631950210853282324510655982757098065657709137845327135766013147354253426364240746381620690117663724329288646510198895137275207992825719846135857839292915100523542874885080351683587865157015032404901182924720371819942957083390475846809517968191151435281268695782594904484795360890092607679215675240583291240729468370895035823777914792823688291214492607109455017754453939895630226174304357121900605689015734289765672740769194115142607443713769825894380064727556869268488695795705030158832909348803019429370973064732712469794182891757241046263341655894972953512257981661670321890336672832647028099324621932563236459127918144141230217523147304565594514812518826936144181257723061181656522095236928347413997136815409159361412494284201481609684892562646522086577634100783077813105675590737823924220663206479031113753135119759722725207724879578900186075841393115040465401462266086907464970054073340036852442184414587772177753008511913377364966775792477387717262694468450099866775550614257191941835797445874557362115814601886902749237439492398087966544817154173072811937702110580330775581851211123491341435883319798273456296794954514173820352334127081705706502510709179711510240917772628308487366740741280043704807717608366220401933596364641284631036907635403895053036499618723044314773148779735006542501244942039455169872946018271985844759209768927953340447524637670938413827595013338859796135512187473850161303598087634723542727044978083220970836296653305188470017342167913572166172051819741354902582606590658382067039498769674611071582171914886494269818475850690414812481252963932223686078322390396586222238852602472958831686564971334200490182175112490433364675164900946902818404704835106260174052265784055642968397240262737313737007322288203637798365320295080314524864099419556398713380156353062937736280885716820226469419928595465390700629307079710611273715705695938635644841913194091407807776191951797748706106000922803167645881087385311847268311361092838264814899353459146959869764278464187826798546290981492648723002412475976344071283321798061003719251864595518596639432393032991023409676558943539937377229130132816883146259468718344018277257037013406135980469482324577407154032999045733141275895.3432), +(1.2, 32908.8896, 58463467728170833376633133695001863276259293590926929026251227859007891876739460057725441400966420577009060860805883032969522911803372870882799865787473726926215148161529632590083389287080925059682489116446754279752928005457087175157581627230586554364417068189211136840990661174760199073702207450133797324318403866058202372178813998850887986769280847189341565507156189065295823921162851958925352114220880236114784962150135485415106748467247897246441194126125699204912883449386043559785865023459356275014504597646990160571664166410683323036984805434677654413174177920726210827006973855410386789516533036723888687725436216478665958434776205940192130053647653715221076841771578099896259902368829351569726536927952661429685419815305418450230567773264738536471211804481206474781470237730069753206249915908804615495060673071058534441654604668770343616386612119048579369195201590008082689834456232255266932976831478404670192731621439902738547169253818323045451045749609624500171633897705543164388470746657118050314064066768449450440405619135824055131398727045420324382226572368236570500391463795989258779677208133531636928003546809249007993065200108076924439703799231711400266122025052209803513232429907231051873161206025860851056337427740362763618748092029386371493898291580557004812947013231371383576580415676519066503391905962989205397824064923920045371823949776899815750413244195402085917098964452866825666226141169411712884994564949174271056284898570445214367063763956186792886147126466387576513166370247576466566827375268334148320298849218878848928271566491769458471357076035396330179659440244425914213309776100351793665960978678576150833311810944729586040624059867137538839913141142139636023129691775489034134511666020819676247950267220131499463010350308195762769192775344260909521732256844149916046793599150786757764962585268686580124987490115873389726527572428003433405659445349155536369077209682951123806333170190998931670309088422483075609203671527331975811507450670132060984691061148836994322505371265263690017938762760088575875666254883673433331627055180154954694693433502522592907190906966067656027637884202418119121728966267936832338377284832958974299187166554160783467156478554899314000348357280306042140481751668215838656488457943830180819301102535170705017482946779698265096226184239631924271857062033454725540956591929965181603262502135610768915716020374362368495244256420143645126927013882334008435586481691725030031204304273292938132599127402133470745819213047706793887965197191137237066440328777206799072470374264316425913530947082957300047105685634407092811630672103242089966046839626911122.7149)) +SELECT b, p, bc_result, b^p AS power, b^p - bc_result AS diff FROM t; + +-- +-- Tests for EXP() +-- + +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of EXP(): +-- +-- for n in {-20..20} +-- do +-- x="$n.29837" +-- r=$(bc -ql <<< "scale=500 ; e($x)" | head -n 1) +-- echo "($x, $r)," +-- done + +WITH t(x, bc_result) AS (VALUES +(-20.29837, .000000001529431101152222), +(-19.29837, .000000004157424770142192), +(-18.29837, .00000001130105220586304), +(-17.29837, .00000003071944485366452), +(-16.29837, .00000008350410872606600), +(-15.29837, .0000002269877013517336), +(-14.29837, .0000006170165438681061), +(-13.29837, .000001677224859055276), +(-12.29837, .000004559169856609741), +(-11.29837, .00001239310857408049), +(-10.29837, .00003368796183504298), +(-9.29837, .00009157337449401917), +(-8.29837, .0002489222398577673), +(-7.29837, .0006766408013046928), +(-6.29837, .001839300394580514), +(-5.29837, .004999736839665763), +(-4.29837, .01359069379834070), +(-3.29837, .03694333598818056), +(-2.29837, .1004223988993283), +(-1.29837, .2729763820983097), +(0.29837, 1.3476603299656679), +(1.29837, 3.6633205858807959), +(2.29837, 9.9579377804197108), +(3.29837, 27.068481317440698), +(4.29837, 73.579760889182206), +(5.29837, 200.01052696742555), +(6.29837, 543.68498095607070), +(7.29837, 1477.8890041389891), +(8.29837, 4017.3188244304487), +(9.29837, 10920.204759575742), +(10.29837, 29684.194161006717), +(11.29837, 80690.005580314652), +(12.29837, 219338.17590722828), +(13.29837, 596222.97785597218), +(14.29837, 1620702.0864156289), +(15.29837, 4405525.0308492653), +(16.29837, 11975458.636179032), +(17.29837, 32552671.598188404), +(18.29837, 88487335.673150406), +(19.29837, 240533516.60908059), +(20.29837, 653837887.33381570)) +SELECT x, bc_result, exp(x), exp(x)-bc_result AS diff FROM t; + +-- +-- Tests for LN() +-- + +-- input very small +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(10^-$p)" | head -n 1) +-- echo "('1.0e-$p', $l)," +-- done + +WITH t(x, bc_result) AS (VALUES +('1.0e-1', -2.3025850929940457), +('1.0e-2', -4.6051701859880914), +('1.0e-3', -6.9077552789821371), +('1.0e-4', -9.2103403719761827), +('1.0e-5', -11.512925464970228), +('1.0e-6', -13.815510557964274), +('1.0e-7', -16.118095650958320), +('1.0e-8', -18.420680743952365), +('1.0e-9', -20.723265836946411), +('1.0e-10', -23.025850929940457), +('1.0e-11', -25.328436022934503), +('1.0e-12', -27.631021115928548), +('1.0e-13', -29.933606208922594), +('1.0e-14', -32.236191301916640), +('1.0e-15', -34.5387763949106853), +('1.0e-16', -36.84136148790473094), +('1.0e-17', -39.143946580898776628), +('1.0e-18', -41.4465316738928223123), +('1.0e-19', -43.74911676688686799634), +('1.0e-20', -46.051701859880913680360), +('1.0e-21', -48.3542869528749593643778), +('1.0e-22', -50.65687204586900504839581), +('1.0e-23', -52.959457138863050732413803), +('1.0e-24', -55.2620422318570964164317949), +('1.0e-25', -57.56462732485114210044978637), +('1.0e-26', -59.867212417845187784467777822), +('1.0e-27', -62.1697975108392334684857692765), +('1.0e-28', -64.47238260383327915250376073116), +('1.0e-29', -66.774967696827324836521752185847), +('1.0e-30', -69.0775527898213705205397436405309), +('1.0e-31', -71.38013788281541620455773509521529), +('1.0e-32', -73.682722975809461888575726549899655), +('1.0e-33', -75.9853080688035075725937180045840189), +('1.0e-34', -78.28789316179755325661170945926838306), +('1.0e-35', -80.590478254791598940629700913952747266), +('1.0e-36', -82.8930633477856446246476923686371114736), +('1.0e-37', -85.19564844077969030866568382332147568124), +('1.0e-38', -87.498233533773735992683675278005839888842), +('1.0e-39', -89.8008186267677816767016667326902040964430), +('1.0e-40', -92.10340371976182736071965818737456830404406)) +SELECT x, bc_result, ln(x::numeric), ln(x::numeric)-bc_result AS diff FROM t; + +-- input very close to but smaller than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(1-10^-$p)" | head -n 1) +-- echo "('1.0e-$p', $l)," +-- done + +WITH t(x, bc_result) AS (VALUES +('1.0e-1', -.10536051565782630), +('1.0e-2', -.010050335853501441), +('1.0e-3', -.0010005003335835335), +('1.0e-4', -.00010000500033335834), +('1.0e-5', -.000010000050000333336), +('1.0e-6', -.0000010000005000003333), +('1.0e-7', -.00000010000000500000033), +('1.0e-8', -.000000010000000050000000), +('1.0e-9', -.0000000010000000005000000), +('1.0e-10', -.00000000010000000000500000), +('1.0e-11', -.000000000010000000000050000), +('1.0e-12', -.0000000000010000000000005000), +('1.0e-13', -.00000000000010000000000000500), +('1.0e-14', -.000000000000010000000000000050), +('1.0e-15', -.0000000000000010000000000000005), +('1.0e-16', -.00000000000000010000000000000001), +('1.0e-17', -.000000000000000010000000000000000), +('1.0e-18', -.0000000000000000010000000000000000), +('1.0e-19', -.00000000000000000010000000000000000), +('1.0e-20', -.000000000000000000010000000000000000), +('1.0e-21', -.0000000000000000000010000000000000000), +('1.0e-22', -.00000000000000000000010000000000000000), +('1.0e-23', -.000000000000000000000010000000000000000), +('1.0e-24', -.0000000000000000000000010000000000000000), +('1.0e-25', -.00000000000000000000000010000000000000000), +('1.0e-26', -.000000000000000000000000010000000000000000), +('1.0e-27', -.0000000000000000000000000010000000000000000), +('1.0e-28', -.00000000000000000000000000010000000000000000), +('1.0e-29', -.000000000000000000000000000010000000000000000), +('1.0e-30', -.0000000000000000000000000000010000000000000000), +('1.0e-31', -.00000000000000000000000000000010000000000000000), +('1.0e-32', -.000000000000000000000000000000010000000000000000), +('1.0e-33', -.0000000000000000000000000000000010000000000000000), +('1.0e-34', -.00000000000000000000000000000000010000000000000000), +('1.0e-35', -.000000000000000000000000000000000010000000000000000), +('1.0e-36', -.0000000000000000000000000000000000010000000000000000), +('1.0e-37', -.00000000000000000000000000000000000010000000000000000), +('1.0e-38', -.000000000000000000000000000000000000010000000000000000), +('1.0e-39', -.0000000000000000000000000000000000000010000000000000000), +('1.0e-40', -.00000000000000000000000000000000000000010000000000000000)) +SELECT '1-'||x, bc_result, ln(1.0-x::numeric), ln(1.0-x::numeric)-bc_result AS diff FROM t; + +-- input very close to but larger than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(1+10^-$p)" | head -n 1) +-- echo "('1.0e-$p', $l)," +-- done + +WITH t(x, bc_result) AS (VALUES +('1.0e-1', .09531017980432486), +('1.0e-2', .009950330853168083), +('1.0e-3', .0009995003330835332), +('1.0e-4', .00009999500033330834), +('1.0e-5', .000009999950000333331), +('1.0e-6', .0000009999995000003333), +('1.0e-7', .00000009999999500000033), +('1.0e-8', .000000009999999950000000), +('1.0e-9', .0000000009999999995000000), +('1.0e-10', .00000000009999999999500000), +('1.0e-11', .000000000009999999999950000), +('1.0e-12', .0000000000009999999999995000), +('1.0e-13', .00000000000009999999999999500), +('1.0e-14', .000000000000009999999999999950), +('1.0e-15', .0000000000000009999999999999995), +('1.0e-16', .00000000000000010000000000000000), +('1.0e-17', .000000000000000010000000000000000), +('1.0e-18', .0000000000000000010000000000000000), +('1.0e-19', .00000000000000000010000000000000000), +('1.0e-20', .000000000000000000010000000000000000), +('1.0e-21', .0000000000000000000010000000000000000), +('1.0e-22', .00000000000000000000010000000000000000), +('1.0e-23', .000000000000000000000010000000000000000), +('1.0e-24', .0000000000000000000000010000000000000000), +('1.0e-25', .00000000000000000000000010000000000000000), +('1.0e-26', .000000000000000000000000010000000000000000), +('1.0e-27', .0000000000000000000000000010000000000000000), +('1.0e-28', .00000000000000000000000000010000000000000000), +('1.0e-29', .000000000000000000000000000010000000000000000), +('1.0e-30', .0000000000000000000000000000010000000000000000), +('1.0e-31', .00000000000000000000000000000010000000000000000), +('1.0e-32', .000000000000000000000000000000010000000000000000), +('1.0e-33', .0000000000000000000000000000000010000000000000000), +('1.0e-34', .00000000000000000000000000000000010000000000000000), +('1.0e-35', .000000000000000000000000000000000010000000000000000), +('1.0e-36', .0000000000000000000000000000000000010000000000000000), +('1.0e-37', .00000000000000000000000000000000000010000000000000000), +('1.0e-38', .000000000000000000000000000000000000010000000000000000), +('1.0e-39', .0000000000000000000000000000000000000010000000000000000), +('1.0e-40', .00000000000000000000000000000000000000010000000000000000)) +SELECT '1+'||x, bc_result, ln(1.0+x::numeric), ln(1.0+x::numeric)-bc_result AS diff FROM t; + +-- input very large +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(10^$p)" | head -n 1) +-- echo "('1.0e$p', $l)," +-- done + +WITH t(x, bc_result) AS (VALUES +('1.0e1', 2.3025850929940457), +('1.0e2', 4.6051701859880914), +('1.0e3', 6.9077552789821371), +('1.0e4', 9.2103403719761827), +('1.0e5', 11.512925464970228), +('1.0e6', 13.815510557964274), +('1.0e7', 16.118095650958320), +('1.0e8', 18.420680743952365), +('1.0e9', 20.723265836946411), +('1.0e10', 23.025850929940457), +('1.0e11', 25.328436022934503), +('1.0e12', 27.631021115928548), +('1.0e13', 29.933606208922594), +('1.0e14', 32.236191301916640), +('1.0e15', 34.538776394910685), +('1.0e16', 36.841361487904731), +('1.0e17', 39.143946580898777), +('1.0e18', 41.446531673892822), +('1.0e19', 43.749116766886868), +('1.0e20', 46.051701859880914), +('1.0e21', 48.354286952874959), +('1.0e22', 50.656872045869005), +('1.0e23', 52.959457138863051), +('1.0e24', 55.262042231857096), +('1.0e25', 57.564627324851142), +('1.0e26', 59.867212417845188), +('1.0e27', 62.169797510839233), +('1.0e28', 64.472382603833279), +('1.0e29', 66.774967696827325), +('1.0e30', 69.077552789821371), +('1.0e31', 71.380137882815416), +('1.0e32', 73.682722975809462), +('1.0e33', 75.985308068803508), +('1.0e34', 78.287893161797553), +('1.0e35', 80.590478254791599), +('1.0e36', 82.893063347785645), +('1.0e37', 85.195648440779690), +('1.0e38', 87.498233533773736), +('1.0e39', 89.800818626767782), +('1.0e40', 92.103403719761827)) +SELECT x, bc_result, ln(x::numeric), ln(x::numeric)-bc_result AS diff FROM t; + +-- input huge +-- +-- bc(1) results computed with a scale of 1000 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..10} +-- do +-- l=$(bc -ql <<< "scale=1000 ; l(10^${p}00)" | head -n 1) +-- echo "('1.0e${p}00', $l)," +-- done + +WITH t(x, bc_result) AS (VALUES +('1.0e100', 230.25850929940457), +('1.0e200', 460.51701859880914), +('1.0e300', 690.77552789821371), +('1.0e400', 921.03403719761827), +('1.0e500', 1151.2925464970228), +('1.0e600', 1381.5510557964274), +('1.0e700', 1611.8095650958320), +('1.0e800', 1842.0680743952365), +('1.0e900', 2072.3265836946411), +('1.0e1000', 2302.5850929940457)) +SELECT x, bc_result, ln(x::numeric), ln(x::numeric)-bc_result AS diff FROM t; + +-- inputs with 1000 decimal places +-- +-- bc(1) results computed with a scale of 2000 and rounded to 1000 decimal +-- places + +WITH t(x, bc_result) AS (VALUES +(484990182159328900690402236933516249572671683638747490717351807610531884491845416923860371219625151551889257298200816555016472471293780254009492949585031653913930735918829139712249577547959394351523545901788627247613322896296041868431769047433229466634098452564756860190085118463828382895145244362033728480588969626012192733802377468089120757046364393407262957242230928854711898925295251902007136232994524624903257456111389508582206404271734668422903183500589303866613158037169610592539145461637447957948521714058034772237111009429638870236361143304703683377693378577075353794118557951847394763531830696578809001981568860219578880229402696449243344235099860421846016326538272155937175661905904288335499593232232926636205909086901191153907183842087577811871344870731324067822883041265129394268082883745408414994.8967939438561591657171240282983703914075472645212002662497023142663831371447287624846942598424990784971781730103682951722370983277124599054059027055336437808366784501932987082321905202623642371063626378290734289114618092750984153422293450048717769065428713836637664433167768445609659527458911187829232316677137895259433038764404970599325009178297626038331436654541552998098529141205301472138026818453893127265938030066392881979113522757891639646670670272542401773230506961559808927249585675430838495658225557294666522469887436551840596777627408780618586500922973500018513068499587683746133637919751545157547095670767246977244726331271787622126889459658539988980096764323712767863722912919120929339399753431689512753214200090670880647731689804555417871258907716687575767185444541243606329768784843125926070743277339790277626515824924290352180761378846035233155198504033292692893297993698953705472933411199778880561376633444249703838589180474329586470353212010427945060694794274109764269805332803290229, + 1864.3702986939570026328504202935192533137907736189919154633800554877738455118081651650863235106905871352085850240570561347180517240105510505203972860921397909573687877993477806728098306202020229409548306695695574102950949468160529713610952021974630774784174851619325758380143625473386495586347322798415543385655090746985183329114860118551572428921774322172798724455202876781611633419444058398798142214904998877857425038669920064728855823072107227506485770367799671977282350083029452784747350395161797215115525867416898416360638482342253129160308632504217096916335590470843180746834864303790913372081974355613359678634194879425862536147988835528973291020680020540866655622823550861337486588647231688134992810403147262346312159819432914207194632564009749236609081399504118359354620598232725290537215007867979331582119891661859015726276335168158288396939655310210558566592649049602925182137256134162660116182293851038854455437841571331011002023088829768308520393956515509475418031437505751407687618234418262), +(87190145885430429849953615409019208993240447426362428988181639909267773304254748257120061524000254226856815085523676417146197197996896030672521334101413071112068202429835905642444187493717977611730127126387257253646790849384975208460867137315507010888782632024640844766297185244443116696943912406389670302370461137850160539373600494054874979342373255280815156048999900951842673141766630630919020492255966628630634124452614590400422133958133100159154995520080124736657520969784129924799670552560034302960877087853678350801769339861812435411200669026902417951572668727488315537985378304242438181615160041688723201917323705450185975141141262578884689500612295576288125956289035673242989906973367691922065122033180281670221390667818909912035903387888639331486823729897326624516015340.0330856710565117793999512551468220085711713631167607285185762046751452975325645379302403715842570486302993296501788672462090620871511446272026693318239212657949496275318383141403236705902077406660768573015707706831878445598837931116223956945944726162551477136715847593742032488181481888084716920605114101902724395659898621880016853548602514706686907951229872573180602614761229992106144727082722940736406782659562775289407005631298246624198606031298081220736931229256511054595028182057216042683060059115371651410352645266000330509331097811566633211452233019461903115970558624057877018778178814946285827512359903934291318219271464841957435711594154280905473802599888081783098187210283997106131616471807951265003903143099667366508222327805543948921694362089860577380749774036318574113007382111997454202845559941557812813566442364810680529092880773126707073967537693927177460459341763934709686530005721141046645111784404932103241501569571235364365556796422998363930810983452790309019295181282099408260156, + 1793.5767085750017553306932533574391150814202249805881581227430032600579405884415934520704053351781361105595296647510475380766428668443641914861849764330704062323054023252886955844207807229267936432730818329225450152491146839618683772020068682795388746108876393249306737841247788224204701299467519965182171772253974884845661168860422489046657965359832930382114760565628765599962013955588754803194908990025689040598990346417563277021386852342928910383706995866844541160576254266641602065102228267316550706943783591722246885978355472097314691737807509436806788803362444745551013400341861820755594413819894154786253014501454443272120342005711761286524843010157182464200556865694401941794983935172457481497909987740544409272349152397774548604845897687504977786762391359552407068124283290504752932824699865504970420939586707791994870941813718246825616335675307740641350673558328821461530563823677144691877374809441673507467507447891562257806191361453045937798278733402269265623588493124129181374135958668436774), +(93936642222690597390233191619858485419795942047468396309991947772747208870873993801669373075421461116465960407843923269693395211616591453397070258466704654943689268224479477016161636938138334729982904232438440955361656138189836032891825113139184685132178764873033678116450665758561650355252211196676137179184043639278410827092182700922151290703747496962700158844772453483316974221113826173404445159281421213715669245417896170368554410830320000019029956317336703559699859949692222685614036912057150632902650913831404804982509990655560731349634628713944739168096272097122388116038119844786988276635032016787352796502360718569977397214936366251320294621522016.6483354941025384161536675750898007896744690911429670830432784905421638721478353275821072200938900938046264210604940707974410950770029535636602548377806284157951164875821446035013896786653932045182167021839184824627082391478016195098055107001433336586881395912782883663046617432598969149948351689103230162742769845955320418573803127107923535948653168889411316007796459064267436246637115946581149511513369842911210359447262641996566147462977170742544980481275049898092152042927981394239266559286915303786701737610786594006685748456635797125029722684151298695274097006242412384086302106763844070230264910503179385988626477852818174114043927841085089058972074427820150462261941575665882880501074676800316585217150509780489224388148722603385921057007086785238310735038314861960410473809826927329368597558806004392175746233568789445929554890241140656324160187253042639339549705859147930476532359840809944163908006480881926041259363654863689570520534301207043189181147254153307163555433328278834311658232337, + 1510.4332713542154696529645934345554302578243896764921637693542962119938599884313210100957753316832762996428481801312323020427109678979117469716796746760060470871840325255146954580681101106876674367471955788143763250819168311353856748872452260808797135108102729064040463343792765872545182299889360257515315869180266759715933989413256377582681707188367254513700731642913479683031478361835565783219287780434673712341147656477670848734998849030451414278832848680301511646182446524915091598080243532068451726548537866633622180283865668708517173065893429240665300584705585310049892047293928733753369421499719516009692095913169665213597158441636480707309244604139865130782756488091268094213446272360006907802989573582755585110277620911226015342778471352130366770729972784317323917141031824334355639769512749560550167491709646539950725523461943580211843652293561678342656010571108219244870234329176123205423872844099992204896411752620881541000940129833754169391528449211839693800724450201835161044717173715867437)) +SELECT trim_scale(ln(x::numeric)-bc_result) AS diff FROM t; + +-- +-- Tests for LOG() (base 10) +-- + +-- input very small, exact result known +WITH t(x) AS (SELECT '1e-'||n FROM generate_series(1, 100) g(n)) +SELECT x, log(x::numeric) FROM t; + +-- input very small, non-exact results +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..50..7} +-- do +-- for d in {9..1..3} +-- do +-- l=$(bc -ql <<< "scale=500 ; l($d*10^-$p) / l(10)" | head -n 1) +-- echo "('${d}.0e-$p', $l)," +-- done +-- done + +WITH t(x, bc_result) AS (VALUES +('9.0e-1', -.04575749056067513), +('6.0e-1', -.2218487496163564), +('3.0e-1', -.5228787452803376), +('9.0e-8', -7.045757490560675), +('6.0e-8', -7.221848749616356), +('3.0e-8', -7.522878745280338), +('9.0e-15', -14.0457574905606751), +('6.0e-15', -14.2218487496163564), +('3.0e-15', -14.5228787452803376), +('9.0e-22', -21.04575749056067512540994), +('6.0e-22', -21.22184874961635636749123), +('3.0e-22', -21.52287874528033756270497), +('9.0e-29', -28.045757490560675125409944193490), +('6.0e-29', -28.221848749616356367491233202020), +('3.0e-29', -28.522878745280337562704972096745), +('9.0e-36', -35.0457574905606751254099441934897693816), +('6.0e-36', -35.2218487496163563674912332020203916640), +('3.0e-36', -35.5228787452803375627049720967448846908), +('9.0e-43', -42.04575749056067512540994419348976938159974227), +('6.0e-43', -42.22184874961635636749123320202039166403168125), +('3.0e-43', -42.52287874528033756270497209674488469079987114), +('9.0e-50', -49.045757490560675125409944193489769381599742271618608), +('6.0e-50', -49.221848749616356367491233202020391664031681254347196), +('3.0e-50', -49.522878745280337562704972096744884690799871135809304)) +SELECT x, bc_result, log(x::numeric), log(x::numeric)-bc_result AS diff FROM t; + +-- input very close to but smaller than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40..7} +-- do +-- for d in {9..1..3} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(1-$d*10^-$p) / l(10)" | head -n 1) +-- echo "('${d}.0e-$p', $l)," +-- done +-- done + +WITH t(x, bc_result) AS (VALUES +('9.0e-1', -1.0000000000000000), +('6.0e-1', -.3979400086720376), +('3.0e-1', -.1549019599857432), +('9.0e-8', -.000000039086505130185422), +('6.0e-8', -.000000026057669695925208), +('3.0e-8', -.000000013028834652530076), +('9.0e-15', -.0000000000000039086503371292840), +('6.0e-15', -.0000000000000026057668914195188), +('3.0e-15', -.0000000000000013028834457097574), +('9.0e-22', -.00000000000000000000039086503371292664), +('6.0e-22', -.00000000000000000000026057668914195110), +('3.0e-22', -.00000000000000000000013028834457097555), +('9.0e-29', -.000000000000000000000000000039086503371292664), +('6.0e-29', -.000000000000000000000000000026057668914195110), +('3.0e-29', -.000000000000000000000000000013028834457097555), +('9.0e-36', -.0000000000000000000000000000000000039086503371292664), +('6.0e-36', -.0000000000000000000000000000000000026057668914195110), +('3.0e-36', -.0000000000000000000000000000000000013028834457097555)) +SELECT '1-'||x, bc_result, log(1.0-x::numeric), log(1.0-x::numeric)-bc_result AS diff FROM t; + +-- input very close to but larger than 1 +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {1..40..7} +-- do +-- for d in {9..1..3} +-- do +-- l=$(bc -ql <<< "scale=500 ; l(1+$d*10^-$p) / l(10)" | head -n 1) +-- echo "('${d}.0e-$p', $l)," +-- done +-- done + +WITH t(x, bc_result) AS (VALUES +('9.0e-1', .2787536009528290), +('6.0e-1', .2041199826559248), +('3.0e-1', .1139433523068368), +('9.0e-8', .000000039086501612400118), +('6.0e-8', .000000026057668132465074), +('3.0e-8', .000000013028834261665042), +('9.0e-15', .0000000000000039086503371292489), +('6.0e-15', .0000000000000026057668914195031), +('3.0e-15', .0000000000000013028834457097535), +('9.0e-22', .00000000000000000000039086503371292664), +('6.0e-22', .00000000000000000000026057668914195110), +('3.0e-22', .00000000000000000000013028834457097555), +('9.0e-29', .000000000000000000000000000039086503371292664), +('6.0e-29', .000000000000000000000000000026057668914195110), +('3.0e-29', .000000000000000000000000000013028834457097555), +('9.0e-36', .0000000000000000000000000000000000039086503371292664), +('6.0e-36', .0000000000000000000000000000000000026057668914195110), +('3.0e-36', .0000000000000000000000000000000000013028834457097555)) +SELECT '1+'||x, bc_result, log(1.0+x::numeric), log(1.0+x::numeric)-bc_result AS diff FROM t; + +-- input very large, exact result known +WITH t(x) AS (SELECT '1e'||n FROM generate_series(1, 100) g(n)) +SELECT x, log(x::numeric) FROM t; + +-- input very large, non-exact results +-- +-- bc(1) results computed with a scale of 500 and truncated using the script +-- below, and then rounded by hand to match the precision of LN(): +-- +-- for p in {10..50..7} +-- do +-- for d in {2..9..3} +-- do +-- l=$(bc -ql <<< "scale=500 ; l($d*10^$p) / l(10)" | head -n 1) +-- echo "('${d}.0e$p', $l)," +-- done +-- done + +WITH t(x, bc_result) AS (VALUES +('2.0e10', 10.301029995663981), +('5.0e10', 10.698970004336019), +('8.0e10', 10.903089986991944), +('2.0e17', 17.301029995663981), +('5.0e17', 17.698970004336019), +('8.0e17', 17.903089986991944), +('2.0e24', 24.301029995663981), +('5.0e24', 24.698970004336019), +('8.0e24', 24.903089986991944), +('2.0e31', 31.301029995663981), +('5.0e31', 31.698970004336019), +('8.0e31', 31.903089986991944), +('2.0e38', 38.301029995663981), +('5.0e38', 38.698970004336019), +('8.0e38', 38.903089986991944), +('2.0e45', 45.30102999566398), +('5.0e45', 45.69897000433602), +('8.0e45', 45.90308998699194)) +SELECT x, bc_result, log(x::numeric), log(x::numeric)-bc_result AS diff FROM t; diff --git a/src/test/regress/sql/yb.port.partition_aggregate.sql b/src/test/regress/sql/yb.port.partition_aggregate.sql new file mode 100644 index 0000000000000..e14c710db8b79 --- /dev/null +++ b/src/test/regress/sql/yb.port.partition_aggregate.sql @@ -0,0 +1,348 @@ +-- +-- PARTITION_AGGREGATE +-- Test partitionwise aggregation on partitioned tables +-- +-- Note: to ensure plan stability, it's a good idea to make the partitions of +-- any one partitioned table in this test all have different numbers of rows. +-- + +-- YB: Use colocated DB and enable parallel execution. +SET client_min_messages TO 'warning'; +DROP DATABASE IF EXISTS px_colocated WITH (force); +CREATE DATABASE px_colocated WITH colocation = on; +ALTER DATABASE px_colocated SET yb_parallel_range_rows = 1000; +ALTER DATABASE px_colocated SET yb_enable_parallel_append = on; +ALTER DATABASE px_colocated SET yb_enable_cbo = on; +\c px_colocated + +-- Enable partitionwise aggregate, which by default is disabled. +SET enable_partitionwise_aggregate TO true; +-- Enable partitionwise join, which by default is disabled. +SET enable_partitionwise_join TO true; +-- Disable parallel plans. +SET max_parallel_workers_per_gather TO 0; +-- Disable incremental sort, which can influence selected plans due to fuzz factor. +SET enable_incremental_sort TO off; + +-- +-- Tests for list partitioned tables. +-- +CREATE TABLE pagg_tab (a int, b int, c text, d int) PARTITION BY LIST(c); +CREATE TABLE pagg_tab_p1 PARTITION OF pagg_tab FOR VALUES IN ('0000', '0001', '0002', '0003', '0004'); +CREATE TABLE pagg_tab_p2 PARTITION OF pagg_tab FOR VALUES IN ('0005', '0006', '0007', '0008'); +CREATE TABLE pagg_tab_p3 PARTITION OF pagg_tab FOR VALUES IN ('0009', '0010', '0011'); +INSERT INTO pagg_tab SELECT i % 20, i % 30, to_char(i % 12, 'FM0000'), i % 30 FROM generate_series(0, 2999) i; +ANALYZE pagg_tab; + +-- When GROUP BY clause matches; full aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT c, sum(a), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY c HAVING avg(d) < 15 ORDER BY 1, 2, 3; +SELECT c, sum(a), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY c HAVING avg(d) < 15 ORDER BY 1, 2, 3; + +-- When GROUP BY clause does not match; partial aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT a, sum(b), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY a HAVING avg(d) < 15 ORDER BY 1, 2, 3; +SELECT a, sum(b), avg(b), count(*), min(a), max(b) FROM pagg_tab GROUP BY a HAVING avg(d) < 15 ORDER BY 1, 2, 3; + +-- Check with multiple columns in GROUP BY +EXPLAIN (COSTS OFF) +SELECT a, c, count(*) FROM pagg_tab GROUP BY a, c; +-- Check with multiple columns in GROUP BY, order in GROUP BY is reversed +EXPLAIN (COSTS OFF) +SELECT a, c, count(*) FROM pagg_tab GROUP BY c, a; +-- Check with multiple columns in GROUP BY, order in target-list is reversed +EXPLAIN (COSTS OFF) +SELECT c, a, count(*) FROM pagg_tab GROUP BY a, c; + +-- Test when input relation for grouping is dummy +EXPLAIN (COSTS OFF) +SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; +SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; +EXPLAIN (COSTS OFF) +SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; +SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; + +-- Test GroupAggregate paths by disabling hash aggregates. +SET enable_hashagg TO false; + +-- When GROUP BY clause matches full aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT c, sum(a), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; +SELECT c, sum(a), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; + +-- When GROUP BY clause does not match; partial aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT a, sum(b), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; +SELECT a, sum(b), avg(b), count(*) FROM pagg_tab GROUP BY 1 HAVING avg(d) < 15 ORDER BY 1, 2, 3; + +-- Test partitionwise grouping without any aggregates +EXPLAIN (COSTS OFF) +SELECT c FROM pagg_tab GROUP BY c ORDER BY 1; +SELECT c FROM pagg_tab GROUP BY c ORDER BY 1; +EXPLAIN (COSTS OFF) +SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1; +SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1; + +RESET enable_hashagg; + +-- ROLLUP, partitionwise aggregation does not apply +EXPLAIN (COSTS OFF) +SELECT c, sum(a) FROM pagg_tab GROUP BY rollup(c) ORDER BY 1, 2; + +-- ORDERED SET within the aggregate. +-- Full aggregation; since all the rows that belong to the same group come +-- from the same partition, having an ORDER BY within the aggregate doesn't +-- make any difference. +EXPLAIN (COSTS OFF) +SELECT c, sum(b order by a) FROM pagg_tab GROUP BY c ORDER BY 1, 2; +-- Since GROUP BY clause does not match with PARTITION KEY; we need to do +-- partial aggregation. However, ORDERED SET are not partial safe and thus +-- partitionwise aggregation plan is not generated. +EXPLAIN (COSTS OFF) +SELECT a, sum(b order by a) FROM pagg_tab GROUP BY a ORDER BY 1, 2; + + +-- JOIN query + +CREATE TABLE pagg_tab1(x int, y int) PARTITION BY RANGE(x); +CREATE TABLE pagg_tab1_p1 PARTITION OF pagg_tab1 FOR VALUES FROM (0) TO (10); +CREATE TABLE pagg_tab1_p2 PARTITION OF pagg_tab1 FOR VALUES FROM (10) TO (20); +CREATE TABLE pagg_tab1_p3 PARTITION OF pagg_tab1 FOR VALUES FROM (20) TO (30); + +CREATE TABLE pagg_tab2(x int, y int) PARTITION BY RANGE(y); +CREATE TABLE pagg_tab2_p1 PARTITION OF pagg_tab2 FOR VALUES FROM (0) TO (10); +CREATE TABLE pagg_tab2_p2 PARTITION OF pagg_tab2 FOR VALUES FROM (10) TO (20); +CREATE TABLE pagg_tab2_p3 PARTITION OF pagg_tab2 FOR VALUES FROM (20) TO (30); + +INSERT INTO pagg_tab1 SELECT i % 30, i % 20 FROM generate_series(0, 299, 2) i; +INSERT INTO pagg_tab2 SELECT i % 20, i % 30 FROM generate_series(0, 299, 3) i; + +ANALYZE pagg_tab1; +ANALYZE pagg_tab2; + +-- When GROUP BY clause matches; full aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; +SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; + +-- Check with whole-row reference; partitionwise aggregation does not apply +EXPLAIN (COSTS OFF) +SELECT t1.x, sum(t1.y), count(t1) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; +SELECT t1.x, sum(t1.y), count(t1) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; + +-- GROUP BY having other matching key +EXPLAIN (COSTS OFF) +SELECT t2.y, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t2.y ORDER BY 1, 2, 3; + +-- When GROUP BY clause does not match; partial aggregation is performed for each partition. +-- Also test GroupAggregate paths by disabling hash aggregates. +SET enable_hashagg TO false; +EXPLAIN (COSTS OFF) +SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3; +SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3; +RESET enable_hashagg; + +-- Check with LEFT/RIGHT/FULL OUTER JOINs which produces NULL values for +-- aggregation + +-- LEFT JOIN, should produce partial partitionwise aggregation plan as +-- GROUP BY is on nullable column +EXPLAIN (COSTS OFF) +SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; +SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; + +-- RIGHT JOIN, should produce full partitionwise aggregation plan as +-- GROUP BY is on non-nullable column +EXPLAIN (COSTS OFF) +SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; +SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; + +-- FULL JOIN, should produce partial partitionwise aggregation plan as +-- GROUP BY is on nullable column +EXPLAIN (COSTS OFF) +SELECT a.x, sum(b.x) FROM pagg_tab1 a FULL OUTER JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x ORDER BY 1 NULLS LAST; +SELECT a.x, sum(b.x) FROM pagg_tab1 a FULL OUTER JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x ORDER BY 1 NULLS LAST; + +-- LEFT JOIN, with dummy relation on right side, ideally +-- should produce full partitionwise aggregation plan as GROUP BY is on +-- non-nullable columns. +-- But right now we are unable to do partitionwise join in this case. +EXPLAIN (COSTS OFF) +SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; +SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; + +-- FULL JOIN, with dummy relations on both sides, ideally +-- should produce partial partitionwise aggregation plan as GROUP BY is on +-- nullable columns. +-- But right now we are unable to do partitionwise join in this case. +EXPLAIN (COSTS OFF) +SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a FULL JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; +SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a FULL JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20 GROUP BY a.x, b.y ORDER BY 1, 2; + +-- Empty join relation because of empty outer side, no partitionwise agg plan +EXPLAIN (COSTS OFF) +SELECT a.x, a.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x = 1 AND x = 2) a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x, a.y ORDER BY 1, 2; +SELECT a.x, a.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x = 1 AND x = 2) a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY a.x, a.y ORDER BY 1, 2; + + +-- Partition by multiple columns + +CREATE TABLE pagg_tab_m (a int, b int, c int) PARTITION BY RANGE(a, ((a+b)/2)); +CREATE TABLE pagg_tab_m_p1 PARTITION OF pagg_tab_m FOR VALUES FROM (0, 0) TO (12, 12); +CREATE TABLE pagg_tab_m_p2 PARTITION OF pagg_tab_m FOR VALUES FROM (12, 12) TO (22, 22); +CREATE TABLE pagg_tab_m_p3 PARTITION OF pagg_tab_m FOR VALUES FROM (22, 22) TO (30, 30); +INSERT INTO pagg_tab_m SELECT i % 30, i % 40, i % 50 FROM generate_series(0, 2999) i; +ANALYZE pagg_tab_m; + +-- Partial aggregation as GROUP BY clause does not match with PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a HAVING avg(c) < 22 ORDER BY 1, 2, 3; +SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a HAVING avg(c) < 22 ORDER BY 1, 2, 3; + +-- Full aggregation as GROUP BY clause matches with PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a, (a+b)/2 HAVING sum(b) < 50 ORDER BY 1, 2, 3; +SELECT a, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY a, (a+b)/2 HAVING sum(b) < 50 ORDER BY 1, 2, 3; + +-- Full aggregation as PARTITION KEY is part of GROUP BY clause +EXPLAIN (COSTS OFF) +SELECT a, c, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY (a+b)/2, 2, 1 HAVING sum(b) = 50 AND avg(c) > 25 ORDER BY 1, 2, 3; +SELECT a, c, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY (a+b)/2, 2, 1 HAVING sum(b) = 50 AND avg(c) > 25 ORDER BY 1, 2, 3; + + +-- Test with multi-level partitioning scheme + +CREATE TABLE pagg_tab_ml (a int, b int, c text) PARTITION BY RANGE(a); +CREATE TABLE pagg_tab_ml_p1 PARTITION OF pagg_tab_ml FOR VALUES FROM (0) TO (12); +CREATE TABLE pagg_tab_ml_p2 PARTITION OF pagg_tab_ml FOR VALUES FROM (12) TO (20) PARTITION BY LIST (c); +CREATE TABLE pagg_tab_ml_p2_s1 PARTITION OF pagg_tab_ml_p2 FOR VALUES IN ('0000', '0001', '0002'); +CREATE TABLE pagg_tab_ml_p2_s2 PARTITION OF pagg_tab_ml_p2 FOR VALUES IN ('0003'); + +-- This level of partitioning has different column positions than the parent +CREATE TABLE pagg_tab_ml_p3(b int, c text, a int) PARTITION BY RANGE (b); +CREATE TABLE pagg_tab_ml_p3_s1(c text, a int, b int); +CREATE TABLE pagg_tab_ml_p3_s2 PARTITION OF pagg_tab_ml_p3 FOR VALUES FROM (7) TO (10); + +ALTER TABLE pagg_tab_ml_p3 ATTACH PARTITION pagg_tab_ml_p3_s1 FOR VALUES FROM (0) TO (7); +ALTER TABLE pagg_tab_ml ATTACH PARTITION pagg_tab_ml_p3 FOR VALUES FROM (20) TO (30); + +INSERT INTO pagg_tab_ml SELECT i % 30, i % 10, to_char(i % 4, 'FM0000') FROM generate_series(0, 29999) i; +ANALYZE pagg_tab_ml; + +-- For Parallel Append +SET max_parallel_workers_per_gather TO 2; +SET parallel_setup_cost = 0; + +-- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY +-- for level 1 only. For subpartitions, GROUP BY clause does not match with +-- PARTITION KEY, but still we do not see a partial aggregation as array_agg() +-- is not partial agg safe. +EXPLAIN (COSTS OFF) +SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; +SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; + +-- Without ORDER BY clause, to test Gather at top-most path +EXPLAIN (COSTS OFF) +SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3; + +RESET parallel_setup_cost; + +-- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY +-- for level 1 only. For subpartitions, GROUP BY clause does not match with +-- PARTITION KEY, thus we will have a partial aggregation for them. +EXPLAIN (COSTS OFF) +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; + +-- Partial aggregation at all levels as GROUP BY clause does not match with +-- PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b ORDER BY 1, 2, 3; +SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b HAVING avg(a) < 15 ORDER BY 1, 2, 3; + +-- Full aggregation at all levels as GROUP BY clause matches with PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; + +-- Parallelism within partitionwise aggregates + +SET min_parallel_table_scan_size TO '8kB'; +SET parallel_setup_cost TO 0; + +-- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY +-- for level 1 only. For subpartitions, GROUP BY clause does not match with +-- PARTITION KEY, thus we will have a partial aggregation for them. +EXPLAIN (COSTS OFF) +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3; + +-- Partial aggregation at all levels as GROUP BY clause does not match with +-- PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b ORDER BY 1, 2, 3; +SELECT b, sum(a), count(*) FROM pagg_tab_ml GROUP BY b HAVING avg(a) < 15 ORDER BY 1, 2, 3; + +-- Full aggregation at all levels as GROUP BY clause matches with PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; +SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a, b, c HAVING avg(b) > 7 ORDER BY 1, 2, 3; + + +-- Parallelism within partitionwise aggregates (single level) + +-- Add few parallel setup cost, so that we will see a plan which gathers +-- partially created paths even for full aggregation and sticks a single Gather +-- followed by finalization step. +-- Without this, the cost of doing partial aggregation + Gather + finalization +-- for each partition and then Append over it turns out to be same and this +-- wins as we add it first. This parallel_setup_cost plays a vital role in +-- costing such plans. +SET parallel_setup_cost TO 10; + +CREATE TABLE pagg_tab_para(x int, y int) PARTITION BY RANGE(x); +CREATE TABLE pagg_tab_para_p1 PARTITION OF pagg_tab_para FOR VALUES FROM (0) TO (12); +CREATE TABLE pagg_tab_para_p2 PARTITION OF pagg_tab_para FOR VALUES FROM (12) TO (22); +CREATE TABLE pagg_tab_para_p3 PARTITION OF pagg_tab_para FOR VALUES FROM (22) TO (30); + +INSERT INTO pagg_tab_para SELECT i % 30, i % 20 FROM generate_series(0, 29999) i; + +ANALYZE pagg_tab_para; + +-- When GROUP BY clause matches; full aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; +SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; + +-- When GROUP BY clause does not match; partial aggregation is performed for each partition. +EXPLAIN (COSTS OFF) +SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3; +SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3; + +-- Test when parent can produce parallel paths but not any (or some) of its children +-- (Use one more aggregate to tilt the cost estimates for the plan we want) +ALTER TABLE pagg_tab_para_p1 SET (parallel_workers = 0); +ALTER TABLE pagg_tab_para_p3 SET (parallel_workers = 0); +ANALYZE pagg_tab_para; + +EXPLAIN (COSTS OFF) +SELECT x, sum(y), avg(y), sum(x+y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; +SELECT x, sum(y), avg(y), sum(x+y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; + +ALTER TABLE pagg_tab_para_p2 SET (parallel_workers = 0); +ANALYZE pagg_tab_para; + +-- YB: yet another aggregate is required to "tilt the cost estimates" to get +-- the desired plan. Only one more aggregate is not enough because of higher +-- scan costs relative to vanilla PG's execution nodes. +EXPLAIN (COSTS OFF) +SELECT x, sum(y), avg(y), sum(x+y), count(*), avg(x+y) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; -- YB use one more agg +SELECT x, sum(y), avg(y), sum(x+y), count(*), avg(x+y) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; -- YB use one more agg + +-- Reset parallelism parameters to get partitionwise aggregation plan. +RESET min_parallel_table_scan_size; +RESET parallel_setup_cost; + +EXPLAIN (COSTS OFF) +SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; +SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) < 7 ORDER BY 1, 2, 3; diff --git a/src/test/regress/sql/yb.port.partition_join.sql b/src/test/regress/sql/yb.port.partition_join.sql new file mode 100644 index 0000000000000..af3d56918cd9b --- /dev/null +++ b/src/test/regress/sql/yb.port.partition_join.sql @@ -0,0 +1,1335 @@ +-- +-- PARTITION_JOIN +-- Test partitionwise join between partitioned tables +-- + +-- Enable partitionwise join, which by default is disabled. +SET enable_partitionwise_join to true; +-- YB: explicitly set enable_partitionwise_aggregate to original pg default. +SET enable_partitionwise_aggregate to false; + +-- +-- partitioned by a single column +-- +CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600); +CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES FROM (250) TO (500); +INSERT INTO prt1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 2 = 0; +CREATE INDEX iprt1_p1_a on prt1_p1(a); +CREATE INDEX iprt1_p2_a on prt1_p2(a); +CREATE INDEX iprt1_p3_a on prt1_p3(a); +ANALYZE prt1; + +CREATE TABLE prt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250); +CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES FROM (250) TO (500); +CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 3 = 0; +CREATE INDEX iprt2_p1_b on prt2_p1(b); +CREATE INDEX iprt2_p2_b on prt2_p2(b); +CREATE INDEX iprt2_p3_b on prt2_p3(b); +ANALYZE prt2; + +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; + +-- left outer join, with whole-row reference; partitionwise join does not apply +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; + +-- right outer join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; + +-- full outer join, with placeholder vars +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; + +-- Join with pruned partitions from joining relations +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b; + +-- Currently we can't do partitioned join if nullable-side partitions are pruned +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; + +-- Currently we can't do partitioned join if nullable-side partitions are pruned +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 OR t2.a = 0 ORDER BY t1.a, t2.b; +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 OR t2.a = 0 ORDER BY t1.a, t2.b; + +-- Semi-join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a; + +-- Anti-join with aggregates +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT sum(t1.a), avg(t1.a), sum(t1.b), avg(t1.b) FROM prt1 t1 WHERE NOT EXISTS (SELECT 1 FROM prt2 t2 WHERE t1.a = t2.b); +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT sum(t1.a), avg(t1.a), sum(t1.b), avg(t1.b) FROM prt1 t1 WHERE NOT EXISTS (SELECT 1 FROM prt2 t2 WHERE t1.a = t2.b); + +-- lateral reference +EXPLAIN (COSTS OFF) +SELECT * FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss + ON t1.a = ss.t2a WHERE t1.b = 0 ORDER BY t1.a; +SELECT * FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss + ON t1.a = ss.t2a WHERE t1.b = 0 ORDER BY t1.a; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss + ON t1.c = ss.t2c WHERE (t1.b + coalesce(ss.t2b, 0)) = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss + ON t1.c = ss.t2c WHERE (t1.b + coalesce(ss.t2b, 0)) = 0 ORDER BY t1.a; + +-- bug with inadequate sort key representation +SET enable_partitionwise_aggregate TO true; +SET enable_hashjoin TO false; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) + WHERE a BETWEEN 490 AND 510 + GROUP BY 1, 2 ORDER BY 1, 2; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) + WHERE a BETWEEN 490 AND 510 + GROUP BY 1, 2 ORDER BY 1, 2; + +RESET enable_partitionwise_aggregate; +-- YB: explicitly set enable_partitionwise_aggregate to original pg default. +SET enable_partitionwise_aggregate to false; +RESET enable_hashjoin; + +-- +-- partitioned by expression +-- +CREATE TABLE prt1_e (a int, b int, c int) PARTITION BY RANGE(((a + b)/2)); +CREATE TABLE prt1_e_p1 PARTITION OF prt1_e FOR VALUES FROM (0) TO (250); +CREATE TABLE prt1_e_p2 PARTITION OF prt1_e FOR VALUES FROM (250) TO (500); +CREATE TABLE prt1_e_p3 PARTITION OF prt1_e FOR VALUES FROM (500) TO (600); +INSERT INTO prt1_e SELECT i, i, i % 25 FROM generate_series(0, 599, 2) i; +CREATE INDEX iprt1_e_p1_ab2 on prt1_e_p1(((a+b)/2)); +CREATE INDEX iprt1_e_p2_ab2 on prt1_e_p2(((a+b)/2)); +CREATE INDEX iprt1_e_p3_ab2 on prt1_e_p3(((a+b)/2)); +ANALYZE prt1_e; + +CREATE TABLE prt2_e (a int, b int, c int) PARTITION BY RANGE(((b + a)/2)); +CREATE TABLE prt2_e_p1 PARTITION OF prt2_e FOR VALUES FROM (0) TO (250); +CREATE TABLE prt2_e_p2 PARTITION OF prt2_e FOR VALUES FROM (250) TO (500); +CREATE TABLE prt2_e_p3 PARTITION OF prt2_e FOR VALUES FROM (500) TO (600); +INSERT INTO prt2_e SELECT i, i, i % 25 FROM generate_series(0, 599, 3) i; +ANALYZE prt2_e; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.c = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.c = 0 ORDER BY t1.a, t2.b; + +-- +-- N-way join +-- +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; + +-- +-- 3-way full join +-- +EXPLAIN (COSTS OFF) +SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) + WHERE a BETWEEN 490 AND 510; +SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) + WHERE a BETWEEN 490 AND 510; + +-- +-- 4-way full join +-- +EXPLAIN (COSTS OFF) +SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) FULL JOIN prt1 p4 (a,b,c) USING (a, b) + WHERE a BETWEEN 490 AND 510; +SELECT COUNT(*) FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) FULL JOIN prt2 p3(b,a,c) USING (a, b) FULL JOIN prt1 p4 (a,b,c) USING (a, b) + WHERE a BETWEEN 490 AND 510; + +-- Cases with non-nullable expressions in subquery results; +-- make sure these go to null as expected +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM prt1_e WHERE prt1_e.c = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b; +SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM prt1_e WHERE prt1_e.c = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b; + +-- Semi-join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.a = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.b = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.a = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.b = 0 ORDER BY t1.a; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; + +-- test merge joins +SET enable_hashjoin TO off; +SET enable_nestloop TO off; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; + +-- MergeAppend on nullable column +-- This should generate a partitionwise join, but currently fails to +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; + +-- merge join when expression with whole-row reference needs to be sorted; +-- partitionwise join does not apply +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; + +RESET enable_hashjoin; +RESET enable_nestloop; + +-- +-- partitioned by multiple columns +-- +CREATE TABLE prt1_m (a int, b int, c int) PARTITION BY RANGE(a, ((a + b)/2)); +CREATE TABLE prt1_m_p1 PARTITION OF prt1_m FOR VALUES FROM (0, 0) TO (250, 250); +CREATE TABLE prt1_m_p2 PARTITION OF prt1_m FOR VALUES FROM (250, 250) TO (500, 500); +CREATE TABLE prt1_m_p3 PARTITION OF prt1_m FOR VALUES FROM (500, 500) TO (600, 600); +INSERT INTO prt1_m SELECT i, i, i % 25 FROM generate_series(0, 599, 2) i; +ANALYZE prt1_m; + +CREATE TABLE prt2_m (a int, b int, c int) PARTITION BY RANGE(((b + a)/2), b); +CREATE TABLE prt2_m_p1 PARTITION OF prt2_m FOR VALUES FROM (0, 0) TO (250, 250); +CREATE TABLE prt2_m_p2 PARTITION OF prt2_m FOR VALUES FROM (250, 250) TO (500, 500); +CREATE TABLE prt2_m_p3 PARTITION OF prt2_m FOR VALUES FROM (500, 500) TO (600, 600); +INSERT INTO prt2_m SELECT i, i, i % 25 FROM generate_series(0, 599, 3) i; +ANALYZE prt2_m; + +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.c = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.c = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b; +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.c = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.c = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b; + +-- +-- tests for list partitioned tables. +-- +CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0003', '0004', '0010'); +CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0001', '0005', '0002', '0009'); +CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN ('0006', '0007', '0008', '0011'); +INSERT INTO plt1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE plt1; + +CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0000', '0003', '0004', '0010'); +CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0001', '0005', '0002', '0009'); +CREATE TABLE plt2_p3 PARTITION OF plt2 FOR VALUES IN ('0006', '0007', '0008', '0011'); +INSERT INTO plt2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i; +ANALYZE plt2; + +-- +-- list partitioned by expression +-- +CREATE TABLE plt1_e (a int, b int, c text) PARTITION BY LIST(ltrim(c, 'A')); +CREATE TABLE plt1_e_p1 PARTITION OF plt1_e FOR VALUES IN ('0000', '0003', '0004', '0010'); +CREATE TABLE plt1_e_p2 PARTITION OF plt1_e FOR VALUES IN ('0001', '0005', '0002', '0009'); +CREATE TABLE plt1_e_p3 PARTITION OF plt1_e FOR VALUES IN ('0006', '0007', '0008', '0011'); +INSERT INTO plt1_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE plt1_e; + +-- test partition matching with N-way join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; + +-- joins where one of the relations is proven empty +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a = 1 AND t1.a = 2; + +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 LEFT JOIN prt2 t2 ON t1.a = t2.b; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b, prt1 t3 WHERE t2.b = t3.a; + +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB + +-- +-- tests for hash partitioned tables. +-- +CREATE TABLE pht1 (a int, b int, c text) PARTITION BY HASH(c); +CREATE TABLE pht1_p1 PARTITION OF pht1 FOR VALUES WITH (MODULUS 3, REMAINDER 0); +CREATE TABLE pht1_p2 PARTITION OF pht1 FOR VALUES WITH (MODULUS 3, REMAINDER 1); +CREATE TABLE pht1_p3 PARTITION OF pht1 FOR VALUES WITH (MODULUS 3, REMAINDER 2); +INSERT INTO pht1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE pht1; + +CREATE TABLE pht2 (a int, b int, c text) PARTITION BY HASH(c); +CREATE TABLE pht2_p1 PARTITION OF pht2 FOR VALUES WITH (MODULUS 3, REMAINDER 0); +CREATE TABLE pht2_p2 PARTITION OF pht2 FOR VALUES WITH (MODULUS 3, REMAINDER 1); +CREATE TABLE pht2_p3 PARTITION OF pht2 FOR VALUES WITH (MODULUS 3, REMAINDER 2); +INSERT INTO pht2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i; +ANALYZE pht2; + +-- +-- hash partitioned by expression +-- +CREATE TABLE pht1_e (a int, b int, c text) PARTITION BY HASH(ltrim(c, 'A')); +CREATE TABLE pht1_e_p1 PARTITION OF pht1_e FOR VALUES WITH (MODULUS 3, REMAINDER 0); +CREATE TABLE pht1_e_p2 PARTITION OF pht1_e FOR VALUES WITH (MODULUS 3, REMAINDER 1); +CREATE TABLE pht1_e_p3 PARTITION OF pht1_e FOR VALUES WITH (MODULUS 3, REMAINDER 2); +INSERT INTO pht1_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 299, 2) i; +ANALYZE pht1_e; + +-- test partition matching with N-way join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; + +-- test default partition behavior for range +ALTER TABLE prt1 DETACH PARTITION prt1_p3; +ALTER TABLE prt1 ATTACH PARTITION prt1_p3 DEFAULT; +ANALYZE prt1; +ALTER TABLE prt2 DETACH PARTITION prt2_p3; +ALTER TABLE prt2 ATTACH PARTITION prt2_p3 DEFAULT; +ANALYZE prt2; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB + +-- test default partition behavior for list +ALTER TABLE plt1 DETACH PARTITION plt1_p3; +ALTER TABLE plt1 ATTACH PARTITION plt1_p3 DEFAULT; +ANALYZE plt1; +ALTER TABLE plt2 DETACH PARTITION plt2_p3; +ALTER TABLE plt2 ATTACH PARTITION plt2_p3 DEFAULT; +ANALYZE plt2; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT avg(t1.a), avg(t2.b), t1.c, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.a % 25 = 0 GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c; +*/ -- YB: The explain plan changes significantly between PG and YB +-- +-- multiple levels of partitioning +-- +CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES FROM (0) TO (250); +CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES FROM (250) TO (500) PARTITION BY LIST (c); +CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES IN ('0000', '0001'); +CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES IN ('0002', '0003'); +CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES FROM (500) TO (600) PARTITION BY RANGE (b); +CREATE TABLE prt1_l_p3_p1 PARTITION OF prt1_l_p3 FOR VALUES FROM (0) TO (13); +CREATE TABLE prt1_l_p3_p2 PARTITION OF prt1_l_p3 FOR VALUES FROM (13) TO (25); +INSERT INTO prt1_l SELECT i, i % 25, to_char(i % 4, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE prt1_l; + +CREATE TABLE prt2_l (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE prt2_l_p1 PARTITION OF prt2_l FOR VALUES FROM (0) TO (250); +CREATE TABLE prt2_l_p2 PARTITION OF prt2_l FOR VALUES FROM (250) TO (500) PARTITION BY LIST (c); +CREATE TABLE prt2_l_p2_p1 PARTITION OF prt2_l_p2 FOR VALUES IN ('0000', '0001'); +CREATE TABLE prt2_l_p2_p2 PARTITION OF prt2_l_p2 FOR VALUES IN ('0002', '0003'); +CREATE TABLE prt2_l_p3 PARTITION OF prt2_l FOR VALUES FROM (500) TO (600) PARTITION BY RANGE (a); +CREATE TABLE prt2_l_p3_p1 PARTITION OF prt2_l_p3 FOR VALUES FROM (0) TO (13); +CREATE TABLE prt2_l_p3_p2 PARTITION OF prt2_l_p3 FOR VALUES FROM (13) TO (25); +INSERT INTO prt2_l SELECT i % 25, i, to_char(i % 4, 'FM0000') FROM generate_series(0, 599, 3) i; +ANALYZE prt2_l; + +-- inner join, qual covering only top-level partitions +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t1.b = 0 ORDER BY t1.a, t2.b; + +-- right join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.a = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.a = 0 ORDER BY t1.a, t2.b; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.a = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c) ORDER BY t1.a, t2.b; +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.a = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c) ORDER BY t1.a, t2.b; + +-- lateral partitionwise join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT * FROM prt1_l t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss + ON t1.a = ss.t2a AND t1.c = ss.t2c WHERE t1.b = 0 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB + +SELECT * FROM prt1_l t1 LEFT JOIN LATERAL + (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss + ON t1.a = ss.t2a AND t1.c = ss.t2c WHERE t1.b = 0 ORDER BY t1.a; + +-- join with one side empty +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c; + +-- Test case to verify proper handling of subqueries in a partitioned delete. +-- The weird-looking lateral join is just there to force creation of a +-- nestloop parameter within the subquery, which exposes the problem if the +-- planner fails to make multiple copies of the subquery as appropriate. +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +DELETE FROM prt1_l +WHERE EXISTS ( + SELECT 1 + FROM int4_tbl, + LATERAL (SELECT int4_tbl.f1 FROM int8_tbl LIMIT 2) ss + WHERE prt1_l.c IS NULL); + +*/ -- YB: The explain plan changes significantly between PG and YB +-- +-- negative testcases +-- +CREATE TABLE prt1_n (a int, b int, c varchar) PARTITION BY RANGE(c); +CREATE TABLE prt1_n_p1 PARTITION OF prt1_n FOR VALUES FROM ('0000') TO ('0250'); +CREATE TABLE prt1_n_p2 PARTITION OF prt1_n FOR VALUES FROM ('0250') TO ('0500'); +INSERT INTO prt1_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 499, 2) i; +ANALYZE prt1_n; + +CREATE TABLE prt2_n (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE prt2_n_p1 PARTITION OF prt2_n FOR VALUES IN ('0000', '0003', '0004', '0010', '0006', '0007'); +CREATE TABLE prt2_n_p2 PARTITION OF prt2_n FOR VALUES IN ('0001', '0005', '0002', '0009', '0008', '0011'); +INSERT INTO prt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE prt2_n; + +CREATE TABLE prt3_n (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE prt3_n_p1 PARTITION OF prt3_n FOR VALUES IN ('0000', '0004', '0006', '0007'); +CREATE TABLE prt3_n_p2 PARTITION OF prt3_n FOR VALUES IN ('0001', '0002', '0008', '0010'); +CREATE TABLE prt3_n_p3 PARTITION OF prt3_n FOR VALUES IN ('0003', '0005', '0009', '0011'); +INSERT INTO prt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE prt3_n; + +CREATE TABLE prt4_n (a int, b int, c text) PARTITION BY RANGE(a); +CREATE TABLE prt4_n_p1 PARTITION OF prt4_n FOR VALUES FROM (0) TO (300); +CREATE TABLE prt4_n_p2 PARTITION OF prt4_n FOR VALUES FROM (300) TO (500); +CREATE TABLE prt4_n_p3 PARTITION OF prt4_n FOR VALUES FROM (500) TO (600); +INSERT INTO prt4_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE prt4_n; + +-- partitionwise join can not be applied if the partition ranges differ +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2 WHERE t1.a = t2.a; + +/*+Set(yb_bnl_batch_size 1024) YbBatchedNL(t1 t2)*/ EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2 WHERE t1.a = t2.a; +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a and t1.a = t3.b; + +-- partitionwise join can not be applied if there are no equi-join conditions +-- between partition keys +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON (t1.a < t2.b); + +-- equi-join with join condition on partial keys does not qualify for +-- partitionwise join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1, prt2_m t2 WHERE t1.a = (t2.b + t2.a)/2; +*/ -- YB: The explain plan changes significantly between PG and YB + +-- equi-join between out-of-order partition key columns does not qualify for +-- partitionwise join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 LEFT JOIN prt2_m t2 ON t1.a = t2.b; + +-- equi-join between non-key columns does not qualify for partitionwise join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 LEFT JOIN prt2_m t2 ON t1.c = t2.c; + +-- partitionwise join can not be applied for a join between list and range +-- partitioned tables +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 LEFT JOIN prt2_n t2 ON (t1.c = t2.c); + +-- partitionwise join can not be applied between tables with different +-- partition lists +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 JOIN prt2_n t2 ON (t1.c = t2.c) JOIN plt1 t3 ON (t1.c = t3.c); +*/ -- YB: The explain plan changes significantly between PG and YB + +-- partitionwise join can not be applied for a join between key column and +-- non-key column +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 FULL JOIN prt1 t2 ON (t1.c = t2.c); + +-- +-- Test some other plan types in a partitionwise join (unfortunately, +-- we need larger tables to get the planner to choose these plan types) +-- +create temp table prtx1 (a integer, b integer, c integer) + partition by range (a); +create temp table prtx1_1 partition of prtx1 for values from (1) to (11); +create temp table prtx1_2 partition of prtx1 for values from (11) to (21); +create temp table prtx1_3 partition of prtx1 for values from (21) to (31); +create temp table prtx2 (a integer, b integer, c integer) + partition by range (a); +create temp table prtx2_1 partition of prtx2 for values from (1) to (11); +create temp table prtx2_2 partition of prtx2 for values from (11) to (21); +create temp table prtx2_3 partition of prtx2 for values from (21) to (31); +insert into prtx1 select 1 + i%30, i, i + from generate_series(1,1000) i; +insert into prtx2 select 1 + i%30, i, i + from generate_series(1,500) i, generate_series(1,10) j; +create index on prtx2 (b); +create index on prtx2 (c); +analyze prtx1; +analyze prtx2; + +set enable_indexscan = off; -- YB: force bitmap scan + +explain (costs off) +select * from prtx1 +where not exists (select 1 from prtx2 + where prtx2.a=prtx1.a and prtx2.b=prtx1.b and prtx2.c=123) + and a<20 and c=120; + +select * from prtx1 +where not exists (select 1 from prtx2 + where prtx2.a=prtx1.a and prtx2.b=prtx1.b and prtx2.c=123) + and a<20 and c=120; + +explain (costs off) +select * from prtx1 +where not exists (select 1 from prtx2 + where prtx2.a=prtx1.a and (prtx2.b=prtx1.b+1 or prtx2.c=99)) + and a<20 and c=91; + +select * from prtx1 +where not exists (select 1 from prtx2 + where prtx2.a=prtx1.a and (prtx2.b=prtx1.b+1 or prtx2.c=99)) + and a<20 and c=91; + +-- +-- Test advanced partition-matching algorithm for partitioned join +-- + +-- Tests for range-partitioned tables +CREATE TABLE prt1_adv (a int, b int, c varchar) PARTITION BY RANGE (a); +CREATE TABLE prt1_adv_p1 PARTITION OF prt1_adv FOR VALUES FROM (100) TO (200); +CREATE TABLE prt1_adv_p2 PARTITION OF prt1_adv FOR VALUES FROM (200) TO (300); +CREATE TABLE prt1_adv_p3 PARTITION OF prt1_adv FOR VALUES FROM (300) TO (400); +CREATE INDEX prt1_adv_a_idx ON prt1_adv (a); +INSERT INTO prt1_adv SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(100, 399) i; +ANALYZE prt1_adv; + +CREATE TABLE prt2_adv (a int, b int, c varchar) PARTITION BY RANGE (b); +CREATE TABLE prt2_adv_p1 PARTITION OF prt2_adv FOR VALUES FROM (100) TO (150); +CREATE TABLE prt2_adv_p2 PARTITION OF prt2_adv FOR VALUES FROM (200) TO (300); +CREATE TABLE prt2_adv_p3 PARTITION OF prt2_adv FOR VALUES FROM (350) TO (500); +CREATE INDEX prt2_adv_b_idx ON prt2_adv (b); +INSERT INTO prt2_adv_p1 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(100, 149) i; +INSERT INTO prt2_adv_p2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(200, 299) i; +INSERT INTO prt2_adv_p3 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(350, 499) i; +ANALYZE prt2_adv; + +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; +SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; +SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; + +-- Test cases where one side has an extra partition +CREATE TABLE prt2_adv_extra PARTITION OF prt2_adv FOR VALUES FROM (500) TO (MAXVALUE); +INSERT INTO prt2_adv SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(500, 599) i; +ANALYZE prt2_adv; + +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; +SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + +-- left join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.b, t1.c, t2.a, t2.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a; +*/ -- YB: The explain plan changes significantly between PG and YB + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; +SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + +-- anti join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt2_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt1_adv t2 WHERE t1.b = t2.a) AND t1.a = 0 ORDER BY t1.b; + +-- full join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; + +-- 3-way join where not every pair of relations can do partitioned join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) INNER JOIN prt1_adv t3 ON (t1.b = t3.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a, t3.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) INNER JOIN prt1_adv t3 ON (t1.b = t3.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a, t3.a; + +DROP TABLE prt2_adv_extra; + +-- Test cases where a partition on one side matches multiple partitions on +-- the other side; we currently can't do partitioned join in such cases +ALTER TABLE prt2_adv DETACH PARTITION prt2_adv_p3; +-- Split prt2_adv_p3 into two partitions so that prt1_adv_p3 matches both +CREATE TABLE prt2_adv_p3_1 PARTITION OF prt2_adv FOR VALUES FROM (350) TO (375); +CREATE TABLE prt2_adv_p3_2 PARTITION OF prt2_adv FOR VALUES FROM (375) TO (500); +INSERT INTO prt2_adv SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(350, 499) i; +ANALYZE prt2_adv; + +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; + +-- full join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 175 phv, * FROM prt1_adv WHERE prt1_adv.b = 0) t1 FULL JOIN (SELECT 425 phv, * FROM prt2_adv WHERE prt2_adv.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB + +DROP TABLE prt2_adv_p3_1; +DROP TABLE prt2_adv_p3_2; +ANALYZE prt2_adv; + +-- Test default partitions +ALTER TABLE prt1_adv DETACH PARTITION prt1_adv_p1; +-- Change prt1_adv_p1 to the default partition +ALTER TABLE prt1_adv ATTACH PARTITION prt1_adv_p1 DEFAULT; +ALTER TABLE prt1_adv DETACH PARTITION prt1_adv_p3; +ANALYZE prt1_adv; + +-- We can do partitioned join even if only one of relations has the default +-- partition +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; + +-- Restore prt1_adv_p3 +ALTER TABLE prt1_adv ATTACH PARTITION prt1_adv_p3 FOR VALUES FROM (300) TO (400); +ANALYZE prt1_adv; + +-- Restore prt2_adv_p3 +ALTER TABLE prt2_adv ATTACH PARTITION prt2_adv_p3 FOR VALUES FROM (350) TO (500); +ANALYZE prt2_adv; + +-- Partitioned join can't be applied because the default partition of prt1_adv +-- matches prt2_adv_p1 and prt2_adv_p3 +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB + +ALTER TABLE prt2_adv DETACH PARTITION prt2_adv_p3; +-- Change prt2_adv_p3 to the default partition +ALTER TABLE prt2_adv ATTACH PARTITION prt2_adv_p3 DEFAULT; +ANALYZE prt2_adv; + +-- Partitioned join can't be applied because the default partition of prt1_adv +-- matches prt2_adv_p1 and prt2_adv_p3 +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB + +DROP TABLE prt1_adv_p3; +ANALYZE prt1_adv; + +DROP TABLE prt2_adv_p3; +ANALYZE prt2_adv; + +CREATE TABLE prt3_adv (a int, b int, c varchar) PARTITION BY RANGE (a); +CREATE TABLE prt3_adv_p1 PARTITION OF prt3_adv FOR VALUES FROM (200) TO (300); +CREATE TABLE prt3_adv_p2 PARTITION OF prt3_adv FOR VALUES FROM (300) TO (400); +CREATE INDEX prt3_adv_a_idx ON prt3_adv (a); +INSERT INTO prt3_adv SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(200, 399) i; +ANALYZE prt3_adv; + +-- 3-way join to test the default partition of a join relation +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) LEFT JOIN prt3_adv t3 ON (t1.a = t3.a) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) LEFT JOIN prt3_adv t3 ON (t1.a = t3.a) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a; + +DROP TABLE prt1_adv; +DROP TABLE prt2_adv; +DROP TABLE prt3_adv; + +-- Test interaction of partitioned join with partition pruning +CREATE TABLE prt1_adv (a int, b int, c varchar) PARTITION BY RANGE (a); +CREATE TABLE prt1_adv_p1 PARTITION OF prt1_adv FOR VALUES FROM (100) TO (200); +CREATE TABLE prt1_adv_p2 PARTITION OF prt1_adv FOR VALUES FROM (200) TO (300); +CREATE TABLE prt1_adv_p3 PARTITION OF prt1_adv FOR VALUES FROM (300) TO (400); +CREATE INDEX prt1_adv_a_idx ON prt1_adv (a); +INSERT INTO prt1_adv SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(100, 399) i; +ANALYZE prt1_adv; + +CREATE TABLE prt2_adv (a int, b int, c varchar) PARTITION BY RANGE (b); +CREATE TABLE prt2_adv_p1 PARTITION OF prt2_adv FOR VALUES FROM (100) TO (200); +CREATE TABLE prt2_adv_p2 PARTITION OF prt2_adv FOR VALUES FROM (200) TO (400); +CREATE INDEX prt2_adv_b_idx ON prt2_adv (b); +INSERT INTO prt2_adv SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(100, 399) i; +ANALYZE prt2_adv; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; + +DROP TABLE prt1_adv_p3; +CREATE TABLE prt1_adv_default PARTITION OF prt1_adv DEFAULT; +ANALYZE prt1_adv; + +CREATE TABLE prt2_adv_default PARTITION OF prt2_adv DEFAULT; +ANALYZE prt2_adv; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; + +DROP TABLE prt1_adv; +DROP TABLE prt2_adv; + + +-- Tests for list-partitioned tables +CREATE TABLE plt1_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt1_adv_p1 PARTITION OF plt1_adv FOR VALUES IN ('0001', '0003'); +CREATE TABLE plt1_adv_p2 PARTITION OF plt1_adv FOR VALUES IN ('0004', '0006'); +CREATE TABLE plt1_adv_p3 PARTITION OF plt1_adv FOR VALUES IN ('0008', '0009'); +INSERT INTO plt1_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9); +ANALYZE plt1_adv; + +CREATE TABLE plt2_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt2_adv_p1 PARTITION OF plt2_adv FOR VALUES IN ('0002', '0003'); +CREATE TABLE plt2_adv_p2 PARTITION OF plt2_adv FOR VALUES IN ('0004', '0006'); +CREATE TABLE plt2_adv_p3 PARTITION OF plt2_adv FOR VALUES IN ('0007', '0009'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +ANALYZE plt2_adv; + +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +-- semi join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +-- anti join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + +-- Test cases where one side has an extra partition +CREATE TABLE plt2_adv_extra PARTITION OF plt2_adv FOR VALUES IN ('0000'); +INSERT INTO plt2_adv_extra VALUES (0, 0, '0000'); +ANALYZE plt2_adv; + +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +-- semi join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +-- left join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt2_adv t1 LEFT JOIN plt1_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB + +-- anti join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + +-- anti join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt2_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt1_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + +-- full join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + +DROP TABLE plt2_adv_extra; + +-- Test cases where a partition on one side matches multiple partitions on +-- the other side; we currently can't do partitioned join in such cases +ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p2; +-- Split plt2_adv_p2 into two partitions so that plt1_adv_p2 matches both +CREATE TABLE plt2_adv_p2_1 PARTITION OF plt2_adv FOR VALUES IN ('0004'); +CREATE TABLE plt2_adv_p2_2 PARTITION OF plt2_adv FOR VALUES IN ('0006'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (4, 6); +ANALYZE plt2_adv; + +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + +-- full join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; +*/ -- YB: The explain plan changes significantly between PG and YB + +DROP TABLE plt2_adv_p2_1; +DROP TABLE plt2_adv_p2_2; +-- Restore plt2_adv_p2 +ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p2 FOR VALUES IN ('0004', '0006'); + +-- Test NULL partitions +ALTER TABLE plt1_adv DETACH PARTITION plt1_adv_p1; +-- Change plt1_adv_p1 to the NULL partition +CREATE TABLE plt1_adv_p1_null PARTITION OF plt1_adv FOR VALUES IN (NULL, '0001', '0003'); +INSERT INTO plt1_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3); +INSERT INTO plt1_adv VALUES (-1, -1, NULL); +ANALYZE plt1_adv; + +ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p3; +-- Change plt2_adv_p3 to the NULL partition +CREATE TABLE plt2_adv_p3_null PARTITION OF plt2_adv FOR VALUES IN (NULL, '0007', '0009'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (7, 9); +INSERT INTO plt2_adv VALUES (-1, -1, NULL); +ANALYZE plt2_adv; + +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +-- semi join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +-- anti join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + +DROP TABLE plt1_adv_p1_null; +-- Restore plt1_adv_p1 +ALTER TABLE plt1_adv ATTACH PARTITION plt1_adv_p1 FOR VALUES IN ('0001', '0003'); +-- Add to plt1_adv the extra NULL partition containing only NULL values as the +-- key values +CREATE TABLE plt1_adv_extra PARTITION OF plt1_adv FOR VALUES IN (NULL); +INSERT INTO plt1_adv VALUES (-1, -1, NULL); +ANALYZE plt1_adv; + +DROP TABLE plt2_adv_p3_null; +-- Restore plt2_adv_p3 +ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p3 FOR VALUES IN ('0007', '0009'); +ANALYZE plt2_adv; + +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +-- left join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB + +-- full join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + +-- Add to plt2_adv the extra NULL partition containing only NULL values as the +-- key values +CREATE TABLE plt2_adv_extra PARTITION OF plt2_adv FOR VALUES IN (NULL); +INSERT INTO plt2_adv VALUES (-1, -1, NULL); +ANALYZE plt2_adv; + +-- inner join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +-- left join +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 FULL JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + +-- 3-way join to test the NULL partition of a join relation +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt1_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt1_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; + +DROP TABLE plt1_adv_extra; +DROP TABLE plt2_adv_extra; + +-- Test default partitions +ALTER TABLE plt1_adv DETACH PARTITION plt1_adv_p1; +-- Change plt1_adv_p1 to the default partition +ALTER TABLE plt1_adv ATTACH PARTITION plt1_adv_p1 DEFAULT; +DROP TABLE plt1_adv_p3; +ANALYZE plt1_adv; + +DROP TABLE plt2_adv_p3; +ANALYZE plt2_adv; + +-- We can do partitioned join even if only one of relations has the default +-- partition +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p2; +-- Change plt2_adv_p2 to contain '0005' in addition to '0004' and '0006' as +-- the key values +CREATE TABLE plt2_adv_p2_ext PARTITION OF plt2_adv FOR VALUES IN ('0004', '0005', '0006'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (4, 5, 6); +ANALYZE plt2_adv; + +-- Partitioned join can't be applied because the default partition of plt1_adv +-- matches plt2_adv_p1 and plt2_adv_p2_ext +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +ALTER TABLE plt2_adv DETACH PARTITION plt2_adv_p2_ext; +-- Change plt2_adv_p2_ext to the default partition +ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p2_ext DEFAULT; +ANALYZE plt2_adv; + +-- Partitioned join can't be applied because the default partition of plt1_adv +-- matches plt2_adv_p1 and plt2_adv_p2_ext +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +DROP TABLE plt2_adv_p2_ext; +-- Restore plt2_adv_p2 +ALTER TABLE plt2_adv ATTACH PARTITION plt2_adv_p2 FOR VALUES IN ('0004', '0006'); +ANALYZE plt2_adv; + +CREATE TABLE plt3_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt3_adv_p1 PARTITION OF plt3_adv FOR VALUES IN ('0004', '0006'); +CREATE TABLE plt3_adv_p2 PARTITION OF plt3_adv FOR VALUES IN ('0007', '0009'); +INSERT INTO plt3_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (4, 6, 7, 9); +ANALYZE plt3_adv; + +-- 3-way join to test the default partition of a join relation +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt3_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt3_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; + +-- Test cases where one side has the default partition while the other side +-- has the NULL partition +DROP TABLE plt2_adv_p1; +-- Add the NULL partition to plt2_adv +CREATE TABLE plt2_adv_p1_null PARTITION OF plt2_adv FOR VALUES IN (NULL, '0001', '0003'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3); +INSERT INTO plt2_adv VALUES (-1, -1, NULL); +ANALYZE plt2_adv; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +DROP TABLE plt2_adv_p1_null; +-- Add the NULL partition that contains only NULL values as the key values +CREATE TABLE plt2_adv_p1_null PARTITION OF plt2_adv FOR VALUES IN (NULL); +INSERT INTO plt2_adv VALUES (-1, -1, NULL); +ANALYZE plt2_adv; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; + +DROP TABLE plt1_adv; +DROP TABLE plt2_adv; +DROP TABLE plt3_adv; + +-- Test interaction of partitioned join with partition pruning +CREATE TABLE plt1_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt1_adv_p1 PARTITION OF plt1_adv FOR VALUES IN ('0001'); +CREATE TABLE plt1_adv_p2 PARTITION OF plt1_adv FOR VALUES IN ('0002'); +CREATE TABLE plt1_adv_p3 PARTITION OF plt1_adv FOR VALUES IN ('0003'); +CREATE TABLE plt1_adv_p4 PARTITION OF plt1_adv FOR VALUES IN (NULL, '0004', '0005'); +INSERT INTO plt1_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 2, 3, 4, 5); +INSERT INTO plt1_adv VALUES (-1, -1, NULL); +ANALYZE plt1_adv; + +CREATE TABLE plt2_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt2_adv_p1 PARTITION OF plt2_adv FOR VALUES IN ('0001', '0002'); +CREATE TABLE plt2_adv_p2 PARTITION OF plt2_adv FOR VALUES IN (NULL); +CREATE TABLE plt2_adv_p3 PARTITION OF plt2_adv FOR VALUES IN ('0003'); +CREATE TABLE plt2_adv_p4 PARTITION OF plt2_adv FOR VALUES IN ('0004', '0005'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 10, 'FM0000') FROM generate_series(1, 299) i WHERE i % 10 IN (1, 2, 3, 4, 5); +INSERT INTO plt2_adv VALUES (-1, -1, NULL); +ANALYZE plt2_adv; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; + +CREATE TABLE plt1_adv_default PARTITION OF plt1_adv DEFAULT; +ANALYZE plt1_adv; + +CREATE TABLE plt2_adv_default PARTITION OF plt2_adv DEFAULT; +ANALYZE plt2_adv; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; + +DROP TABLE plt1_adv; +DROP TABLE plt2_adv; + +-- Test the process_outer_partition() code path +CREATE TABLE plt1_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt1_adv_p1 PARTITION OF plt1_adv FOR VALUES IN ('0000', '0001', '0002'); +CREATE TABLE plt1_adv_p2 PARTITION OF plt1_adv FOR VALUES IN ('0003', '0004'); +INSERT INTO plt1_adv SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i; +ANALYZE plt1_adv; + +CREATE TABLE plt2_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt2_adv_p1 PARTITION OF plt2_adv FOR VALUES IN ('0002'); +CREATE TABLE plt2_adv_p2 PARTITION OF plt2_adv FOR VALUES IN ('0003', '0004'); +INSERT INTO plt2_adv SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (2, 3, 4); +ANALYZE plt2_adv; + +CREATE TABLE plt3_adv (a int, b int, c text) PARTITION BY LIST (c); +CREATE TABLE plt3_adv_p1 PARTITION OF plt3_adv FOR VALUES IN ('0001'); +CREATE TABLE plt3_adv_p2 PARTITION OF plt3_adv FOR VALUES IN ('0003', '0004'); +INSERT INTO plt3_adv SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (1, 3, 4); +ANALYZE plt3_adv; + +-- This tests that when merging partitions from plt1_adv and plt2_adv in +-- merge_list_bounds(), process_outer_partition() returns an already-assigned +-- merged partition when re-called with plt1_adv_p1 for the second list value +-- '0001' of that partition +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.c = t2.c)) FULL JOIN plt3_adv t3 ON (t1.c = t3.c) WHERE coalesce(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.c = t2.c)) FULL JOIN plt3_adv t3 ON (t1.c = t3.c) WHERE coalesce(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; + +DROP TABLE plt1_adv; +DROP TABLE plt2_adv; +DROP TABLE plt3_adv; + + +-- Tests for multi-level partitioned tables +CREATE TABLE alpha (a double precision, b int, c text) PARTITION BY RANGE (a); +CREATE TABLE alpha_neg PARTITION OF alpha FOR VALUES FROM ('-Infinity') TO (0) PARTITION BY RANGE (b); +CREATE TABLE alpha_pos PARTITION OF alpha FOR VALUES FROM (0) TO (10.0) PARTITION BY LIST (c); +CREATE TABLE alpha_neg_p1 PARTITION OF alpha_neg FOR VALUES FROM (100) TO (200); +CREATE TABLE alpha_neg_p2 PARTITION OF alpha_neg FOR VALUES FROM (200) TO (300); +CREATE TABLE alpha_neg_p3 PARTITION OF alpha_neg FOR VALUES FROM (300) TO (400); +CREATE TABLE alpha_pos_p1 PARTITION OF alpha_pos FOR VALUES IN ('0001', '0003'); +CREATE TABLE alpha_pos_p2 PARTITION OF alpha_pos FOR VALUES IN ('0004', '0006'); +CREATE TABLE alpha_pos_p3 PARTITION OF alpha_pos FOR VALUES IN ('0008', '0009'); +INSERT INTO alpha_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 399) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9); +INSERT INTO alpha_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 399) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9); +ANALYZE alpha; + +CREATE TABLE beta (a double precision, b int, c text) PARTITION BY RANGE (a); +CREATE TABLE beta_neg PARTITION OF beta FOR VALUES FROM (-10.0) TO (0) PARTITION BY RANGE (b); +CREATE TABLE beta_pos PARTITION OF beta FOR VALUES FROM (0) TO ('Infinity') PARTITION BY LIST (c); +CREATE TABLE beta_neg_p1 PARTITION OF beta_neg FOR VALUES FROM (100) TO (150); +CREATE TABLE beta_neg_p2 PARTITION OF beta_neg FOR VALUES FROM (200) TO (300); +CREATE TABLE beta_neg_p3 PARTITION OF beta_neg FOR VALUES FROM (350) TO (500); +CREATE TABLE beta_pos_p1 PARTITION OF beta_pos FOR VALUES IN ('0002', '0003'); +CREATE TABLE beta_pos_p2 PARTITION OF beta_pos FOR VALUES IN ('0004', '0006'); +CREATE TABLE beta_pos_p3 PARTITION OF beta_pos FOR VALUES IN ('0007', '0009'); +INSERT INTO beta_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 149) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +INSERT INTO beta_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(200, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +INSERT INTO beta_neg SELECT -1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(350, 499) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +INSERT INTO beta_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(100, 149) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +INSERT INTO beta_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(200, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +INSERT INTO beta_pos SELECT 1.0, i, to_char(i % 10, 'FM0000') FROM generate_series(350, 499) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +ANALYZE beta; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b, t2.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b, t2.b; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b; +*/ -- YB: The explain plan changes significantly between PG and YB +SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b; + +-- partitionwise join with fractional paths +CREATE TABLE fract_t (id BIGINT, PRIMARY KEY (id)) PARTITION BY RANGE (id); +CREATE TABLE fract_t0 PARTITION OF fract_t FOR VALUES FROM ('0') TO ('1000'); +CREATE TABLE fract_t1 PARTITION OF fract_t FOR VALUES FROM ('1000') TO ('2000'); + +-- insert data +INSERT INTO fract_t (id) (SELECT generate_series(0, 1999)); +ANALYZE fract_t; + +-- verify plan; nested index only scans +SET max_parallel_workers_per_gather = 0; +SET enable_partitionwise_join = on; + +/* YB: The explain plan changes significantly between PG and YB +EXPLAIN (COSTS OFF) +SELECT * FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY id ASC LIMIT 10; + +EXPLAIN (COSTS OFF) +SELECT * FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY id DESC LIMIT 10; +*/ -- YB: The explain plan changes significantly between PG and YB + +-- cleanup +DROP TABLE fract_t; + +RESET max_parallel_workers_per_gather; +RESET enable_partitionwise_join; diff --git a/src/test/regress/sql/yb.port.partition_prune.sql b/src/test/regress/sql/yb.port.partition_prune.sql new file mode 100644 index 0000000000000..5bbbb856e8c83 --- /dev/null +++ b/src/test/regress/sql/yb.port.partition_prune.sql @@ -0,0 +1,1237 @@ +-- +-- Test partitioning planner code +-- + +-- Force generic plans to be used for all prepared statements in this file. +set plan_cache_mode = force_generic_plan; +-- YB: explicitly set enable_partitionwise_aggregate to original pg default. +SET enable_partitionwise_aggregate to false; + +create table lp (a char) partition by list (a); +create table lp_default partition of lp default; +create table lp_ef partition of lp for values in ('e', 'f'); +create table lp_ad partition of lp for values in ('a', 'd'); +create table lp_bc partition of lp for values in ('b', 'c'); +create table lp_g partition of lp for values in ('g'); +create table lp_null partition of lp for values in (null); +explain (costs off) select * from lp; +explain (costs off) select * from lp where a > 'a' and a < 'd'; +explain (costs off) select * from lp where a > 'a' and a <= 'd'; +explain (costs off) select * from lp where a = 'a'; +explain (costs off) select * from lp where 'a' = a; /* commuted */ +explain (costs off) select * from lp where a is not null; +explain (costs off) select * from lp where a is null; +explain (costs off) select * from lp where a = 'a' or a = 'c'; +explain (costs off) select * from lp where a is not null and (a = 'a' or a = 'c'); +explain (costs off) select * from lp where a <> 'g'; +explain (costs off) select * from lp where a <> 'a' and a <> 'd'; +explain (costs off) select * from lp where a not in ('a', 'd'); + +-- collation matches the partitioning collation, pruning works +create table coll_pruning (a text collate "C") partition by list (a); +create table coll_pruning_a partition of coll_pruning for values in ('a'); +create table coll_pruning_b partition of coll_pruning for values in ('b'); +create table coll_pruning_def partition of coll_pruning default; +explain (costs off) select * from coll_pruning where a collate "C" = 'a' collate "C"; +-- collation doesn't match the partitioning collation, no pruning occurs +explain (costs off) select * from coll_pruning where a collate "POSIX" = 'a' collate "POSIX"; + +create table rlp (a int, b varchar) partition by range (a); +create table rlp_default partition of rlp default partition by list (a); +create table rlp_default_default partition of rlp_default default; +create table rlp_default_10 partition of rlp_default for values in (10); +create table rlp_default_30 partition of rlp_default for values in (30); +create table rlp_default_null partition of rlp_default for values in (null); +create table rlp1 partition of rlp for values from (minvalue) to (1); +create table rlp2 partition of rlp for values from (1) to (10); + +create table rlp3 (b varchar, a int) partition by list (b varchar_ops); +create table rlp3_default partition of rlp3 default; +create table rlp3abcd partition of rlp3 for values in ('ab', 'cd'); +create table rlp3efgh partition of rlp3 for values in ('ef', 'gh'); +create table rlp3nullxy partition of rlp3 for values in (null, 'xy'); +alter table rlp attach partition rlp3 for values from (15) to (20); + +create table rlp4 partition of rlp for values from (20) to (30) partition by range (a); +create table rlp4_default partition of rlp4 default; +create table rlp4_1 partition of rlp4 for values from (20) to (25); +create table rlp4_2 partition of rlp4 for values from (25) to (29); + +create table rlp5 partition of rlp for values from (31) to (maxvalue) partition by range (a); +create table rlp5_default partition of rlp5 default; +create table rlp5_1 partition of rlp5 for values from (31) to (40); + +explain (costs off) select * from rlp where a < 1; +explain (costs off) select * from rlp where 1 > a; /* commuted */ +explain (costs off) select * from rlp where a <= 1; +explain (costs off) select * from rlp where a = 1; +explain (costs off) select * from rlp where a = 1::bigint; /* same as above */ +explain (costs off) select * from rlp where a = 1::numeric; /* no pruning */ +explain (costs off) select * from rlp where a <= 10; +explain (costs off) select * from rlp where a > 10; +explain (costs off) select * from rlp where a < 15; +explain (costs off) select * from rlp where a <= 15; +explain (costs off) select * from rlp where a > 15 and b = 'ab'; +explain (costs off) select * from rlp where a = 16; +explain (costs off) select * from rlp where a = 16 and b in ('not', 'in', 'here'); +explain (costs off) select * from rlp where a = 16 and b < 'ab'; +explain (costs off) select * from rlp where a = 16 and b <= 'ab'; +explain (costs off) select * from rlp where a = 16 and b is null; +explain (costs off) select * from rlp where a = 16 and b is not null; +explain (costs off) select * from rlp where a is null; +explain (costs off) select * from rlp where a is not null; +explain (costs off) select * from rlp where a > 30; +explain (costs off) select * from rlp where a = 30; /* only default is scanned */ +explain (costs off) select * from rlp where a <= 31; +explain (costs off) select * from rlp where a = 1 or a = 7; +explain (costs off) select * from rlp where a = 1 or b = 'ab'; + +explain (costs off) select * from rlp where a > 20 and a < 27; +explain (costs off) select * from rlp where a = 29; +explain (costs off) select * from rlp where a >= 29; +explain (costs off) select * from rlp where a < 1 or (a > 20 and a < 25); + +-- where clause contradicts sub-partition's constraint +explain (costs off) select * from rlp where a = 20 or a = 40; +explain (costs off) select * from rlp3 where a = 20; /* empty */ + +-- redundant clauses are eliminated +explain (costs off) select * from rlp where a > 1 and a = 10; /* only default */ +explain (costs off) select * from rlp where a > 1 and a >=15; /* rlp3 onwards, including default */ +explain (costs off) select * from rlp where a = 1 and a = 3; /* empty */ +explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = 15); + +-- multi-column keys +create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +create table mc3p_default partition of mc3p default; +create table mc3p0 partition of mc3p for values from (minvalue, minvalue, minvalue) to (1, 1, 1); +create table mc3p1 partition of mc3p for values from (1, 1, 1) to (10, 5, 10); +create table mc3p2 partition of mc3p for values from (10, 5, 10) to (10, 10, 10); +create table mc3p3 partition of mc3p for values from (10, 10, 10) to (10, 10, 20); +create table mc3p4 partition of mc3p for values from (10, 10, 20) to (10, maxvalue, maxvalue); +create table mc3p5 partition of mc3p for values from (11, 1, 1) to (20, 10, 10); +create table mc3p6 partition of mc3p for values from (20, 10, 10) to (20, 20, 20); +create table mc3p7 partition of mc3p for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue); + +explain (costs off) select * from mc3p where a = 1; +explain (costs off) select * from mc3p where a = 1 and abs(b) < 1; +explain (costs off) select * from mc3p where a = 1 and abs(b) = 1; +explain (costs off) select * from mc3p where a = 1 and abs(b) = 1 and c < 8; +explain (costs off) select * from mc3p where a = 10 and abs(b) between 5 and 35; +explain (costs off) select * from mc3p where a > 10; +explain (costs off) select * from mc3p where a >= 10; +explain (costs off) select * from mc3p where a < 10; +explain (costs off) select * from mc3p where a <= 10 and abs(b) < 10; +explain (costs off) select * from mc3p where a = 11 and abs(b) = 0; +explain (costs off) select * from mc3p where a = 20 and abs(b) = 10 and c = 100; +explain (costs off) select * from mc3p where a > 20; +explain (costs off) select * from mc3p where a >= 20; +explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20); +explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20) or a < 1; +explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20) or a < 1 or a = 1; +explain (costs off) select * from mc3p where a = 1 or abs(b) = 1 or c = 1; +explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 and abs(b) = 10); +explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 and abs(b) = 9); + +-- a simpler multi-column keys case +create table mc2p (a int, b int) partition by range (a, b); +create table mc2p_default partition of mc2p default; +create table mc2p0 partition of mc2p for values from (minvalue, minvalue) to (1, minvalue); +create table mc2p1 partition of mc2p for values from (1, minvalue) to (1, 1); +create table mc2p2 partition of mc2p for values from (1, 1) to (2, minvalue); +create table mc2p3 partition of mc2p for values from (2, minvalue) to (2, 1); +create table mc2p4 partition of mc2p for values from (2, 1) to (2, maxvalue); +create table mc2p5 partition of mc2p for values from (2, maxvalue) to (maxvalue, maxvalue); + +explain (costs off) select * from mc2p where a < 2; +explain (costs off) select * from mc2p where a = 2 and b < 1; +explain (costs off) select * from mc2p where a > 1; +explain (costs off) select * from mc2p where a = 1 and b > 1; + +-- all partitions but the default one should be pruned +explain (costs off) select * from mc2p where a = 1 and b is null; +explain (costs off) select * from mc2p where a is null and b is null; +explain (costs off) select * from mc2p where a is null and b = 1; +explain (costs off) select * from mc2p where a is null; +explain (costs off) select * from mc2p where b is null; + +-- boolean partitioning +create table boolpart (a bool) partition by list (a); +create table boolpart_default partition of boolpart default; +create table boolpart_t partition of boolpart for values in ('true'); +create table boolpart_f partition of boolpart for values in ('false'); + +explain (costs off) select * from boolpart where a in (true, false); +explain (costs off) select * from boolpart where a = false; +explain (costs off) select * from boolpart where not a = false; +explain (costs off) select * from boolpart where a is true or a is not true; +explain (costs off) select * from boolpart where a is not true; +explain (costs off) select * from boolpart where a is not true and a is not false; +explain (costs off) select * from boolpart where a is unknown; +explain (costs off) select * from boolpart where a is not unknown; + +create table boolrangep (a bool, b bool, c int) partition by range (a,b,c); +create table boolrangep_tf partition of boolrangep for values from ('true', 'false', 0) to ('true', 'false', 100); +create table boolrangep_ft partition of boolrangep for values from ('false', 'true', 0) to ('false', 'true', 100); +create table boolrangep_ff1 partition of boolrangep for values from ('false', 'false', 0) to ('false', 'false', 50); +create table boolrangep_ff2 partition of boolrangep for values from ('false', 'false', 50) to ('false', 'false', 100); + +-- try a more complex case that's been known to trip up pruning in the past +explain (costs off) select * from boolrangep where not a and not b and c = 25; + +-- test scalar-to-array operators +create table coercepart (a varchar) partition by list (a); +create table coercepart_ab partition of coercepart for values in ('ab'); +create table coercepart_bc partition of coercepart for values in ('bc'); +create table coercepart_cd partition of coercepart for values in ('cd'); + +explain (costs off) select * from coercepart where a in ('ab', to_char(125, '999')); +explain (costs off) select * from coercepart where a ~ any ('{ab}'); +explain (costs off) select * from coercepart where a !~ all ('{ab}'); +explain (costs off) select * from coercepart where a ~ any ('{ab,bc}'); +explain (costs off) select * from coercepart where a !~ all ('{ab,bc}'); +explain (costs off) select * from coercepart where a = any ('{ab,bc}'); +explain (costs off) select * from coercepart where a = any ('{ab,null}'); +explain (costs off) select * from coercepart where a = any (null::text[]); +explain (costs off) select * from coercepart where a = all ('{ab}'); +explain (costs off) select * from coercepart where a = all ('{ab,bc}'); +explain (costs off) select * from coercepart where a = all ('{ab,null}'); +explain (costs off) select * from coercepart where a = all (null::text[]); + +drop table coercepart; + +CREATE TABLE part (a INT, b INT) PARTITION BY LIST (a); +CREATE TABLE part_p1 PARTITION OF part FOR VALUES IN (-2,-1,0,1,2); +CREATE TABLE part_p2 PARTITION OF part DEFAULT PARTITION BY RANGE(a); +CREATE TABLE part_p2_p1 PARTITION OF part_p2 DEFAULT; +CREATE TABLE part_rev (b INT, c INT, a INT); +ALTER TABLE part ATTACH PARTITION part_rev FOR VALUES IN (3); -- fail +ALTER TABLE part_rev DROP COLUMN c; +ALTER TABLE part ATTACH PARTITION part_rev FOR VALUES IN (3); -- now it's ok +INSERT INTO part VALUES (-1,-1), (1,1), (2,NULL), (NULL,-2),(NULL,NULL); +EXPLAIN (COSTS OFF) SELECT tableoid::regclass as part, a, b FROM part WHERE a IS NULL ORDER BY 1, 2, 3; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM part p(x) ORDER BY x; + +-- +-- some more cases +-- + +-- +-- pruning for partitioned table appearing inside a sub-query +-- +-- pruning won't work for mc3p, because some keys are Params +explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = t1.b and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1; + +-- pruning should work fine, because values for a prefix of keys (a, b) are +-- available +explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.c = t1.b and abs(t2.b) = 1 and t2.a = 1) s where t1.a = 1; + +-- also here, because values for all keys are provided +explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = 1 and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1; + +-- +-- pruning with clauses containing <> operator +-- + +-- doesn't prune range partitions +create table rp (a int) partition by range (a); +create table rp0 partition of rp for values from (minvalue) to (1); +create table rp1 partition of rp for values from (1) to (2); +create table rp2 partition of rp for values from (2) to (maxvalue); + +explain (costs off) select * from rp where a <> 1; +explain (costs off) select * from rp where a <> 1 and a <> 2; + +-- null partition should be eliminated due to strict <> clause. +explain (costs off) select * from lp where a <> 'a'; + +-- ensure we detect contradictions in clauses; a can't be NULL and NOT NULL. +explain (costs off) select * from lp where a <> 'a' and a is null; +explain (costs off) select * from lp where (a <> 'a' and a <> 'd') or a is null; + +-- check that it also works for a partitioned table that's not root, +-- which in this case are partitions of rlp that are themselves +-- list-partitioned on b +explain (costs off) select * from rlp where a = 15 and b <> 'ab' and b <> 'cd' and b <> 'xy' and b is not null; + +-- +-- different collations for different keys with same expression +-- +create table coll_pruning_multi (a text) partition by range (substr(a, 1) collate "POSIX", substr(a, 1) collate "C"); +create table coll_pruning_multi1 partition of coll_pruning_multi for values from ('a', 'a') to ('a', 'e'); +create table coll_pruning_multi2 partition of coll_pruning_multi for values from ('a', 'e') to ('a', 'z'); +create table coll_pruning_multi3 partition of coll_pruning_multi for values from ('b', 'a') to ('b', 'e'); + +-- no pruning, because no value for the leading key +explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' collate "C"; + +-- pruning, with a value provided for the leading key +explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'a' collate "POSIX"; + +-- pruning, with values provided for both keys +explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' collate "C" and substr(a, 1) = 'a' collate "POSIX"; + +-- +-- LIKE operators don't prune +-- +create table like_op_noprune (a text) partition by list (a); +create table like_op_noprune1 partition of like_op_noprune for values in ('ABC'); +create table like_op_noprune2 partition of like_op_noprune for values in ('BCD'); +explain (costs off) select * from like_op_noprune where a like '%BC'; + +-- +-- tests wherein clause value requires a cross-type comparison function +-- +create table lparted_by_int2 (a smallint) partition by list (a); +create table lparted_by_int2_1 partition of lparted_by_int2 for values in (1); +create table lparted_by_int2_16384 partition of lparted_by_int2 for values in (16384); +explain (costs off) select * from lparted_by_int2 where a = 100000000000000; + +create table rparted_by_int2 (a smallint) partition by range (a); +create table rparted_by_int2_1 partition of rparted_by_int2 for values from (1) to (10); +create table rparted_by_int2_16384 partition of rparted_by_int2 for values from (10) to (16384); +-- all partitions pruned +explain (costs off) select * from rparted_by_int2 where a > 100000000000000; +create table rparted_by_int2_maxvalue partition of rparted_by_int2 for values from (16384) to (maxvalue); +-- all partitions but rparted_by_int2_maxvalue pruned +explain (costs off) select * from rparted_by_int2 where a > 100000000000000; + +drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, boolrangep, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2; + +-- +-- Test Partition pruning for HASH partitioning +-- +-- Use hand-rolled hash functions and operator classes to get predictable +-- result on different machines. See the definitions of +-- part_part_test_int4_ops and part_test_text_ops in insert.sql. +-- + +create or replace function part_hashint4_noop(value int4, seed int8) +returns int8 as $$ +select value + seed; +$$ language sql immutable; + +create operator class part_test_int4_ops +for type int4 +using hash as +operator 1 =, +function 2 part_hashint4_noop(int4, int8); + +create or replace function part_hashtext_length(value text, seed int8) +RETURNS int8 AS $$ +select length(coalesce(value, ''))::int8 +$$ language sql immutable; + +create operator class part_test_text_ops +for type text +using hash as +operator 1 =, +function 2 part_hashtext_length(text, int8); + +create table hp (a int, b text, c int) + partition by hash (a part_test_int4_ops, b part_test_text_ops); +create table hp0 partition of hp for values with (modulus 4, remainder 0); +create table hp3 partition of hp for values with (modulus 4, remainder 3); +create table hp1 partition of hp for values with (modulus 4, remainder 1); +create table hp2 partition of hp for values with (modulus 4, remainder 2); + +insert into hp values (null, null, 0); +insert into hp values (1, null, 1); +insert into hp values (1, 'xxx', 2); +insert into hp values (null, 'xxx', 3); +insert into hp values (2, 'xxx', 4); +insert into hp values (1, 'abcde', 5); +select tableoid::regclass, * from hp order by c; + +-- partial keys won't prune, nor would non-equality conditions +explain (costs off) select * from hp where a = 1; +explain (costs off) select * from hp where b = 'xxx'; +explain (costs off) select * from hp where a is null; +explain (costs off) select * from hp where b is null; +explain (costs off) select * from hp where a < 1 and b = 'xxx'; +explain (costs off) select * from hp where a <> 1 and b = 'yyy'; +explain (costs off) select * from hp where a <> 1 and b <> 'xxx'; + +-- pruning should work if either a value or a IS NULL clause is provided for +-- each of the keys +explain (costs off) select * from hp where a is null and b is null; +explain (costs off) select * from hp where a = 1 and b is null; +explain (costs off) select * from hp where a = 1 and b = 'xxx'; +explain (costs off) select * from hp where a is null and b = 'xxx'; +explain (costs off) select * from hp where a = 2 and b = 'xxx'; +explain (costs off) select * from hp where a = 1 and b = 'abcde'; +explain (costs off) select * from hp where (a = 1 and b = 'abcde') or (a = 2 and b = 'xxx') or (a is null and b is null); + +-- test pruning when not all the partitions exist +drop table hp1; +drop table hp3; +explain (costs off) select * from hp where a = 1 and b = 'abcde'; +explain (costs off) select * from hp where a = 1 and b = 'abcde' and + (c = 2 or c = 3); +drop table hp2; +explain (costs off) select * from hp where a = 1 and b = 'abcde' and + (c = 2 or c = 3); + +drop table hp; + +-- +-- Test runtime partition pruning +-- +create table ab (a int not null, b int not null) partition by list (a); +create table ab_a2 partition of ab for values in(2) partition by list (b); +create table ab_a2_b1 partition of ab_a2 for values in (1); +create table ab_a2_b2 partition of ab_a2 for values in (2); +create table ab_a2_b3 partition of ab_a2 for values in (3); +create table ab_a1 partition of ab for values in(1) partition by list (b); +create table ab_a1_b1 partition of ab_a1 for values in (1); +create table ab_a1_b2 partition of ab_a1 for values in (2); +create table ab_a1_b3 partition of ab_a1 for values in (3); +create table ab_a3 partition of ab for values in(3) partition by list (b); +create table ab_a3_b1 partition of ab_a3 for values in (1); +create table ab_a3_b2 partition of ab_a3 for values in (2); +create table ab_a3_b3 partition of ab_a3 for values in (3); + +-- Disallow index only scans as concurrent transactions may stop visibility +-- bits being set causing "Heap Fetches" to be unstable in the EXPLAIN ANALYZE +-- output. +set enable_indexonlyscan = off; + +prepare ab_q1 (int, int, int) as +select * from ab where a between $1 and $2 and b <= $3; + +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2, 3); +explain (analyze, costs off, summary off, timing off) execute ab_q1 (1, 2, 3); + +deallocate ab_q1; + +-- Runtime pruning after optimizer pruning +prepare ab_q1 (int, int) as +select a from ab where a between $1 and $2 and b < 3; + +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2); +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 4); + +-- Ensure a mix of PARAM_EXTERN and PARAM_EXEC Params work together at +-- different levels of partitioning. +prepare ab_q2 (int, int) as +select a from ab where a between $1 and $2 and b < (select 3); + +explain (analyze, costs off, summary off, timing off) execute ab_q2 (2, 2); + +-- As above, but swap the PARAM_EXEC Param to the first partition level +prepare ab_q3 (int, int) as +select a from ab where b between $1 and $2 and a < (select 3); + +explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2); + +-- Test a backwards Append scan +create table list_part (a int) partition by list (a); +create table list_part1 partition of list_part for values in (1); +create table list_part2 partition of list_part for values in (2); +create table list_part3 partition of list_part for values in (3); +create table list_part4 partition of list_part for values in (4); + +insert into list_part select generate_series(1,4); + +begin; + +-- Don't select an actual value out of the table as the order of the Append's +-- subnodes may not be stable. +declare cur SCROLL CURSOR for select 1 from list_part where a > (select 1) and a < (select 4); +/* YB: cannot fetch backward +-- move beyond the final row +move 3 from cur; +-- Ensure we get two rows. +fetch backward all from cur; +*/ -- YB: cannot fetch backward +commit; + +begin; + +-- Test run-time pruning using stable functions +create function list_part_fn(int) returns int as $$ begin return $1; end;$$ language plpgsql stable; + +-- Ensure pruning works using a stable function containing no Vars +explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1); + +-- Ensure pruning does not take place when the function has a Var parameter +explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(a); + +-- Ensure pruning does not take place when the expression contains a Var. +explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1) + a; + +rollback; + +drop table list_part; + +-- Parallel append + +-- Parallel queries won't necessarily get as many workers as the planner +-- asked for. This affects not only the "Workers Launched:" field of EXPLAIN +-- results, but also row counts and loop counts for parallel scans, Gathers, +-- and everything in between. This function filters out the values we can't +-- rely on to be stable. +-- This removes enough info that you might wonder why bother with EXPLAIN +-- ANALYZE at all. The answer is that we need to see '(never executed)' +-- notations because that's the only way to verify runtime pruning. +create function explain_parallel_append(text) returns setof text +language plpgsql as +$$ +declare + ln text; +begin + /* YB: parallelism is hard to force. Just ignore the plan entirely. + for ln in + execute format('explain (analyze, costs off, summary off, timing off) %s', + $1) + loop + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + ln := regexp_replace(ln, 'Rows Removed by Filter: \d+', 'Rows Removed by Filter: N'); + return next ln; + end loop; + */ -- YB: parallelism is hard to force. Just ignore the plan entirely. +end; +$$; + +prepare ab_q4 (int, int) as +select avg(a) from ab where a between $1 and $2 and b < 4; + +-- 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 = 2; + +select explain_parallel_append('execute ab_q4 (2, 2)'); + +-- Test run-time pruning with IN lists. +prepare ab_q5 (int, int, int) as +select avg(a) from ab where a in($1,$2,$3) and b < 4; + +select explain_parallel_append('execute ab_q5 (1, 1, 1)'); +select explain_parallel_append('execute ab_q5 (2, 3, 3)'); + +-- Try some params whose values do not belong to any partition. +select explain_parallel_append('execute ab_q5 (33, 44, 55)'); + +-- Test Parallel Append with PARAM_EXEC Params +select explain_parallel_append('select count(*) from ab where (a = (select 1) or a = (select 3)) and b = 2'); + +-- Test pruning during parallel nested loop query +create table lprt_a (a int not null); +-- Insert some values we won't find in ab +insert into lprt_a select 0 from generate_series(1,100); + +-- and insert some values that we should find. +insert into lprt_a values(1),(1); + +analyze lprt_a; + +create index ab_a2_b1_a_idx on ab_a2_b1 (a); +create index ab_a2_b2_a_idx on ab_a2_b2 (a); +create index ab_a2_b3_a_idx on ab_a2_b3 (a); +create index ab_a1_b1_a_idx on ab_a1_b1 (a); +create index ab_a1_b2_a_idx on ab_a1_b2 (a); +create index ab_a1_b3_a_idx on ab_a1_b3 (a); +create index ab_a3_b1_a_idx on ab_a3_b1 (a); +create index ab_a3_b2_a_idx on ab_a3_b2 (a); +create index ab_a3_b3_a_idx on ab_a3_b3 (a); + +set enable_hashjoin = 0; +set enable_mergejoin = 0; +set enable_memoize = 0; + +select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(0, 0, 1)'); + +-- Ensure the same partitions are pruned when we make the nested loop +-- parameter an Expr rather than a plain Param. +select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a + 0 where a.a in(0, 0, 1)'); + +insert into lprt_a values(3),(3); + +select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 3)'); +select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)'); + +delete from lprt_a where a = 1; + +select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)'); + +reset enable_hashjoin; +reset enable_mergejoin; +reset enable_memoize; +reset parallel_setup_cost; +reset parallel_tuple_cost; +reset min_parallel_table_scan_size; +reset max_parallel_workers_per_gather; + +set enable_indexscan = false; -- YB: try to force bitmap scans for the following plans +set yb_enable_bitmapscan = true; -- YB: try to force bitmap scans for the following plans + +-- Test run-time partition pruning with an initplan +explain (analyze, costs off, summary off, timing off) +select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1 from lprt_a); + +-- Test run-time partition pruning with UNION ALL parents +explain (analyze, costs off, summary off, timing off) +select * from (select * from ab where a = 1 union all select * from ab) ab where b = (select 1); + +-- A case containing a UNION ALL with a non-partitioned child. +explain (analyze, costs off, summary off, timing off) +select * from (select * from ab where a = 1 union all (values(10,5)) union all select * from ab) ab where b = (select 1); + +-- Another UNION ALL test, but containing a mix of exec init and exec run-time pruning. +create table xy_1 (x int, y int); +insert into xy_1 values(100,-10); + +set enable_bitmapscan = 0; +set enable_indexscan = 0; + +prepare ab_q6 as +select * from ( + select tableoid::regclass,a,b from ab +union all + select tableoid::regclass,x,y from xy_1 +union all + select tableoid::regclass,a,b from ab +) ab where a = $1 and b = (select -10); + +-- Ensure the xy_1 subplan is not pruned. +explain (analyze, costs off, summary off, timing off) execute ab_q6(1); + +-- Ensure we see just the xy_1 row. +execute ab_q6(100); + +reset enable_bitmapscan; +-- reset enable_indexscan; -- YB: keep forcing bitmap scans for the next plan + +deallocate ab_q1; +deallocate ab_q2; +deallocate ab_q3; +deallocate ab_q4; +deallocate ab_q5; +deallocate ab_q6; + +-- UPDATE on a partition subtree has been seen to have problems. +insert into ab values (1,2); +explain (analyze, costs off, summary off, timing off) +update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; +table ab; + +reset yb_enable_bitmapscan; -- YB +reset enable_indexscan; -- YB +-- Test UPDATE where source relation has run-time pruning enabled +truncate ab; +insert into ab values (1, 1), (1, 2), (1, 3), (2, 1); +explain (analyze, costs off, summary off, timing off) +update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1); +select tableoid::regclass, * from ab; + +drop table ab, lprt_a; + +-- Join +create table tbl1(col1 int); +insert into tbl1 values (501), (505); + +-- Basic table +create table tprt (col1 int) partition by range (col1); +create table tprt_1 partition of tprt for values from (1) to (501); +create table tprt_2 partition of tprt for values from (501) to (1001); +create table tprt_3 partition of tprt for values from (1001) to (2001); +create table tprt_4 partition of tprt for values from (2001) to (3001); +create table tprt_5 partition of tprt for values from (3001) to (4001); +create table tprt_6 partition of tprt for values from (4001) to (5001); + +create index tprt1_idx on tprt_1 (col1); +create index tprt2_idx on tprt_2 (col1); +create index tprt3_idx on tprt_3 (col1); +create index tprt4_idx on tprt_4 (col1); +create index tprt5_idx on tprt_5 (col1); +create index tprt6_idx on tprt_6 (col1); + +insert into tprt values (10), (20), (501), (502), (505), (1001), (4500); + +set enable_hashjoin = off; +set enable_mergejoin = off; + +/* YB: The explain plan changes significantly between PG and YB +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 > tprt.col1; + +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; +*/ -- YB: The explain plan changes significantly between PG and YB + +/*+Set(yb_bnl_batch_size 1024)*/ explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 > tprt.col1 +order by tbl1.col1, tprt.col1; + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + +-- Multiple partitions +insert into tbl1 values (1001), (1010), (1011); +/* YB: The explain plan changes significantly between PG and YB +explain (analyze, costs off, summary off, timing off) +select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1; + +explain (analyze, costs off, summary off, timing off) +select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1; +*/ -- YB: The explain plan changes significantly between PG and YB + +/*+Set(yb_bnl_batch_size 1024)*/ explain (analyze, costs off, summary off, timing off) +select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1; + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 > tprt.col1 +order by tbl1.col1, tprt.col1; + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + +-- Last partition +delete from tbl1; +insert into tbl1 values (4400); +/* YB: The explain plan changes significantly between PG and YB +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 < tprt.col1; +*/ -- YB: The explain plan changes significantly between PG and YB + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 < tprt.col1 +order by tbl1.col1, tprt.col1; + +-- No matching partition +delete from tbl1; +insert into tbl1 values (10000); +/* YB: The explain plan changes significantly between PG and YB +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; +*/ -- YB: The explain plan changes significantly between PG and YB + +/*+Set(yb_bnl_batch_size 1024)*/ explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + +drop table tbl1, tprt; + +-- Test with columns defined in varying orders between each level +create table part_abc (a int not null, b int not null, c int not null) partition by list (a); +create table part_bac (b int not null, a int not null, c int not null) partition by list (b); +create table part_cab (c int not null, a int not null, b int not null) partition by list (c); +create table part_abc_p1 (a int not null, b int not null, c int not null); + +alter table part_abc attach partition part_bac for values in(1); +alter table part_bac attach partition part_cab for values in(2); +alter table part_cab attach partition part_abc_p1 for values in(3); + +prepare part_abc_q1 (int, int, int) as +select * from part_abc where a = $1 and b = $2 and c = $3; + +-- Single partition should be scanned. +explain (analyze, costs off, summary off, timing off) execute part_abc_q1 (1, 2, 3); + +deallocate part_abc_q1; + +drop table part_abc; + +-- Ensure that an Append node properly handles a sub-partitioned table +-- matching without any of its leaf partitions matching the clause. +create table listp (a int, b int) partition by list (a); +create table listp_1 partition of listp for values in(1) partition by list (b); +create table listp_1_1 partition of listp_1 for values in(1); +create table listp_2 partition of listp for values in(2) partition by list (b); +create table listp_2_1 partition of listp_2 for values in(2); +select * from listp where b = 1; + +-- Ensure that an Append node properly can handle selection of all first level +-- partitions before finally detecting the correct set of 2nd level partitions +-- which match the given parameter. +prepare q1 (int,int) as select * from listp where b in ($1,$2); + +explain (analyze, costs off, summary off, timing off) execute q1 (1,1); + +explain (analyze, costs off, summary off, timing off) execute q1 (2,2); + +-- Try with no matching partitions. +explain (analyze, costs off, summary off, timing off) execute q1 (0,0); + +deallocate q1; + +-- Test more complex cases where a not-equal condition further eliminates partitions. +prepare q1 (int,int,int,int) as select * from listp where b in($1,$2) and $3 <> b and $4 <> b; + +-- Both partitions allowed by IN clause, but one disallowed by <> clause +explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,0); + +-- Both partitions allowed by IN clause, then both excluded again by <> clauses. +explain (analyze, costs off, summary off, timing off) execute q1 (1,2,2,1); + +-- Ensure Params that evaluate to NULL properly prune away all partitions +explain (analyze, costs off, summary off, timing off) +select * from listp where a = (select null::int); + +drop table listp; + +-- +-- check that stable query clauses are only used in run-time pruning +-- +create table stable_qual_pruning (a timestamp) partition by range (a); +create table stable_qual_pruning1 partition of stable_qual_pruning + for values from ('2000-01-01') to ('2000-02-01'); +create table stable_qual_pruning2 partition of stable_qual_pruning + for values from ('2000-02-01') to ('2000-03-01'); +create table stable_qual_pruning3 partition of stable_qual_pruning + for values from ('3000-02-01') to ('3000-03-01'); + +-- comparison against a stable value requires run-time pruning +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning where a < localtimestamp; + +-- timestamp < timestamptz comparison is only stable, not immutable +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning where a < '2000-02-01'::timestamptz; + +-- check ScalarArrayOp cases +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(array['2010-02-01', '2020-01-01']::timestamp[]); +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(array['2000-02-01', '2010-01-01']::timestamp[]); +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(array['2000-02-01', localtimestamp]::timestamp[]); +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(array['2010-02-01', '2020-01-01']::timestamptz[]); +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(array['2000-02-01', '2010-01-01']::timestamptz[]); +explain (analyze, costs off, summary off, timing off) +select * from stable_qual_pruning + where a = any(null::timestamptz[]); + +drop table stable_qual_pruning; + +-- +-- Check that pruning with composite range partitioning works correctly when +-- it must ignore clauses for trailing keys once it has seen a clause with +-- non-inclusive operator for an earlier key +-- +create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +create table mc3p0 partition of mc3p + for values from (0, 0, 0) to (0, maxvalue, maxvalue); +create table mc3p1 partition of mc3p + for values from (1, 1, 1) to (2, minvalue, minvalue); +create table mc3p2 partition of mc3p + for values from (2, minvalue, minvalue) to (3, maxvalue, maxvalue); +insert into mc3p values (0, 1, 1), (1, 1, 1), (2, 1, 1); + +explain (analyze, costs off, summary off, timing off) +select * from mc3p where a < 3 and abs(b) = 1; + +-- +-- Check that pruning with composite range partitioning works correctly when +-- a combination of runtime parameters is specified, not all of whose values +-- are available at the same time +-- +prepare ps1 as + select * from mc3p where a = $1 and abs(b) < (select 3); +explain (analyze, costs off, summary off, timing off) +execute ps1(1); +deallocate ps1; +prepare ps2 as + select * from mc3p where a <= $1 and abs(b) < (select 3); +explain (analyze, costs off, summary off, timing off) +execute ps2(1); +deallocate ps2; + +drop table mc3p; + +-- Ensure runtime pruning works with initplans params with boolean types +create table boolvalues (value bool not null); +insert into boolvalues values('t'),('f'); + +create table boolp (a bool) partition by list (a); +create table boolp_t partition of boolp for values in('t'); +create table boolp_f partition of boolp for values in('f'); + +explain (analyze, costs off, summary off, timing off) +select * from boolp where a = (select value from boolvalues where value); + +explain (analyze, costs off, summary off, timing off) +select * from boolp where a = (select value from boolvalues where not value); + +drop table boolp; + +-- +-- Test run-time pruning of MergeAppend subnodes +-- +set enable_seqscan = off; +set enable_sort = off; +create table ma_test (a int, b int) partition by range (a); +create table ma_test_p1 partition of ma_test for values from (0) to (10); +create table ma_test_p2 partition of ma_test for values from (10) to (20); +create table ma_test_p3 partition of ma_test for values from (20) to (30); +insert into ma_test select x,x from generate_series(0,29) t(x); +create index on ma_test (b ASC); -- YB: change to ASC to allow for range queries + +analyze ma_test; +prepare mt_q1 (int) as select a from ma_test where a >= $1 and a % 10 = 5 order by b; + +explain (analyze, costs off, summary off, timing off) execute mt_q1(15); +execute mt_q1(15); +explain (analyze, costs off, summary off, timing off) execute mt_q1(25); +execute mt_q1(25); +-- Ensure MergeAppend behaves correctly when no subplans match +explain (analyze, costs off, summary off, timing off) execute mt_q1(35); +execute mt_q1(35); + +deallocate mt_q1; + +prepare mt_q2 (int) as select * from ma_test where a >= $1 order by b limit 1; + +-- Ensure output list looks sane when the MergeAppend has no subplans. +explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35); + +deallocate mt_q2; + +-- ensure initplan params properly prune partitions +explain (analyze, costs off, summary off, timing off) select * from ma_test where a >= (select min(b) from ma_test_p2) order by b; + +reset enable_seqscan; +reset enable_sort; + +drop table ma_test; + +reset enable_indexonlyscan; + +-- +-- check that pruning works properly when the partition key is of a +-- pseudotype +-- + +-- array type list partition key +create table pp_arrpart (a int[]) partition by list (a); +create table pp_arrpart1 partition of pp_arrpart for values in ('{1}'); +create table pp_arrpart2 partition of pp_arrpart for values in ('{2, 3}', '{4, 5}'); +explain (costs off) select * from pp_arrpart where a = '{1}'; +explain (costs off) select * from pp_arrpart where a = '{1, 2}'; +explain (costs off) select * from pp_arrpart where a in ('{4, 5}', '{1}'); +explain (costs off) update pp_arrpart set a = a where a = '{1}'; +explain (costs off) delete from pp_arrpart where a = '{1}'; +drop table pp_arrpart; + +-- array type hash partition key +create table pph_arrpart (a int[]) partition by hash (a); +create table pph_arrpart1 partition of pph_arrpart for values with (modulus 2, remainder 0); +create table pph_arrpart2 partition of pph_arrpart for values with (modulus 2, remainder 1); +insert into pph_arrpart values ('{1}'), ('{1, 2}'), ('{4, 5}'); +select tableoid::regclass, * from pph_arrpart order by 1,2; -- YB: add more ordering +explain (costs off) select * from pph_arrpart where a = '{1}'; +explain (costs off) select * from pph_arrpart where a = '{1, 2}'; +explain (costs off) select * from pph_arrpart where a in ('{4, 5}', '{1}'); +drop table pph_arrpart; + +-- enum type list partition key +create type pp_colors as enum ('green', 'blue', 'black'); +create table pp_enumpart (a pp_colors) partition by list (a); +create table pp_enumpart_green partition of pp_enumpart for values in ('green'); +create table pp_enumpart_blue partition of pp_enumpart for values in ('blue'); +explain (costs off) select * from pp_enumpart where a = 'blue'; +explain (costs off) select * from pp_enumpart where a = 'black'; +drop table pp_enumpart; +drop type pp_colors; + +-- record type as partition key +create type pp_rectype as (a int, b int); +create table pp_recpart (a pp_rectype) partition by list (a); +create table pp_recpart_11 partition of pp_recpart for values in ('(1,1)'); +create table pp_recpart_23 partition of pp_recpart for values in ('(2,3)'); +explain (costs off) select * from pp_recpart where a = '(1,1)'::pp_rectype; +explain (costs off) select * from pp_recpart where a = '(1,2)'::pp_rectype; +drop table pp_recpart; +drop type pp_rectype; + +-- range type partition key +create table pp_intrangepart (a int4range) partition by list (a); +create table pp_intrangepart12 partition of pp_intrangepart for values in ('[1,2]'); +create table pp_intrangepart2inf partition of pp_intrangepart for values in ('[2,)'); +explain (costs off) select * from pp_intrangepart where a = '[1,2]'::int4range; +explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range; +drop table pp_intrangepart; + +-- +-- Ensure the enable_partition_prune GUC properly disables partition pruning. +-- + +create table pp_lp (a int, value int) partition by list (a); +create table pp_lp1 partition of pp_lp for values in(1); +create table pp_lp2 partition of pp_lp for values in(2); + +explain (costs off) select * from pp_lp where a = 1; +explain (costs off) update pp_lp set value = 10 where a = 1; +explain (costs off) delete from pp_lp where a = 1; + +set enable_partition_pruning = off; + +set constraint_exclusion = 'partition'; -- this should not affect the result. + +explain (costs off) select * from pp_lp where a = 1; +explain (costs off) update pp_lp set value = 10 where a = 1; +explain (costs off) delete from pp_lp where a = 1; + +set constraint_exclusion = 'off'; -- this should not affect the result. + +explain (costs off) select * from pp_lp where a = 1; +explain (costs off) update pp_lp set value = 10 where a = 1; +explain (costs off) delete from pp_lp where a = 1; + +drop table pp_lp; + +-- Ensure enable_partition_prune does not affect non-partitioned tables. + +create table inh_lp (a int, value int); +create table inh_lp1 (a int, value int, check(a = 1)) inherits (inh_lp); +create table inh_lp2 (a int, value int, check(a = 2)) inherits (inh_lp); + +set constraint_exclusion = 'partition'; + +-- inh_lp2 should be removed in the following 3 cases. +explain (costs off) select * from inh_lp where a = 1; +explain (costs off) update inh_lp set value = 10 where a = 1; +explain (costs off) delete from inh_lp where a = 1; + +-- Ensure we don't exclude normal relations when we only expect to exclude +-- inheritance children +explain (costs off) update inh_lp1 set value = 10 where a = 2; + +drop table inh_lp cascade; + +reset enable_partition_pruning; +reset constraint_exclusion; + +-- Check pruning for a partition tree containing only temporary relations +create temp table pp_temp_parent (a int) partition by list (a); +create temp table pp_temp_part_1 partition of pp_temp_parent for values in (1); +create temp table pp_temp_part_def partition of pp_temp_parent default; +explain (costs off) select * from pp_temp_parent where true; +explain (costs off) select * from pp_temp_parent where a = 2; +drop table pp_temp_parent; + +-- Stress run-time partition pruning a bit more, per bug reports +create temp table p (a int, b int, c int) partition by list (a); +create temp table p1 partition of p for values in (1); +create temp table p2 partition of p for values in (2); +create temp table q (a int, b int, c int) partition by list (a); +create temp table q1 partition of q for values in (1) partition by list (b); +create temp table q11 partition of q1 for values in (1) partition by list (c); +create temp table q111 partition of q11 for values in (1); +create temp table q2 partition of q for values in (2) partition by list (b); +create temp table q21 partition of q2 for values in (1); +create temp table q22 partition of q2 for values in (2); + +insert into q22 values (2, 2, 3); + +explain (costs off) +select * +from ( + select * from p + union all + select * from q1 + union all + select 1, 1, 1 + ) s(a, b, c) +where s.a = 1 and s.b = 1 and s.c = (select 1); + +select * +from ( + select * from p + union all + select * from q1 + union all + select 1, 1, 1 + ) s(a, b, c) +where s.a = 1 and s.b = 1 and s.c = (select 1); + +prepare q (int, int) as +select * +from ( + select * from p + union all + select * from q1 + union all + select 1, 1, 1 + ) s(a, b, c) +where s.a = $1 and s.b = $2 and s.c = (select 1); + +explain (costs off) execute q (1, 1); +execute q (1, 1); + +drop table p, q; + +-- Ensure run-time pruning works correctly when we match a partitioned table +-- on the first level but find no matching partitions on the second level. +create table listp (a int, b int) partition by list (a); +create table listp1 partition of listp for values in(1); +create table listp2 partition of listp for values in(2) partition by list(b); +create table listp2_10 partition of listp2 for values in (10); + +-- YB: #26410 +explain (analyze, costs off, summary off, timing off) +select * from listp where a = (select 2) and b <> 10; + +-- +-- check that a partition directly accessed in a query is excluded with +-- constraint_exclusion = on +-- + +-- turn off partition pruning, so that it doesn't interfere +set enable_partition_pruning to off; + +-- setting constraint_exclusion to 'partition' disables exclusion +set constraint_exclusion to 'partition'; +explain (costs off) select * from listp1 where a = 2; +explain (costs off) update listp1 set a = 1 where a = 2; +-- constraint exclusion enabled +set constraint_exclusion to 'on'; +explain (costs off) select * from listp1 where a = 2; +explain (costs off) update listp1 set a = 1 where a = 2; + +reset constraint_exclusion; +reset enable_partition_pruning; + +drop table listp; + +-- Ensure run-time pruning works correctly for nested Append nodes +set parallel_setup_cost to 0; +set parallel_tuple_cost to 0; + +create table listp (a int) partition by list(a); +create table listp_12 partition of listp for values in(1,2) partition by list(a); +create table listp_12_1 partition of listp_12 for values in(1); +create table listp_12_2 partition of listp_12 for values in(2); + +-- Force the 2nd subnode of the Append to be non-parallel. This results in +-- a nested Append node because the mixed parallel / non-parallel paths cannot +-- be pulled into the top-level Append. +alter table listp_12_1 set (parallel_workers = 0); + +-- Ensure that listp_12_2 is not scanned. (The nested Append is not seen in +-- the plan as it's pulled in setref.c due to having just a single subnode). +select explain_parallel_append('select * from listp where a = (select 1);'); + +-- Like the above but throw some more complexity at the planner by adding +-- a UNION ALL. We expect both sides of the union not to scan the +-- non-required partitions. +select explain_parallel_append( +'select * from listp where a = (select 1) + union all +select * from listp where a = (select 2);'); + +drop table listp; +reset parallel_tuple_cost; +reset parallel_setup_cost; + +-- Test case for run-time pruning with a nested Merge Append +set enable_sort to 0; +create table rangep (a int, b int) partition by range (a); +create table rangep_0_to_100 partition of rangep for values from (0) to (100) partition by list (b); +-- We need 3 sub-partitions. 1 to validate pruning worked and another two +-- because a single remaining partition would be pulled up to the main Append. +create table rangep_0_to_100_1 partition of rangep_0_to_100 for values in(1); +create table rangep_0_to_100_2 partition of rangep_0_to_100 for values in(2); +create table rangep_0_to_100_3 partition of rangep_0_to_100 for values in(3); +create table rangep_100_to_200 partition of rangep for values from (100) to (200); +create index on rangep (a ASC); -- YB: change to ASC to allow for range queries + +-- Ensure run-time pruning works on the nested Merge Append +explain (analyze on, costs off, timing off, summary off) +select * from rangep where b IN((select 1),(select 2)) order by a; +reset enable_sort; +drop table rangep; + +-- +-- Check that gen_prune_steps_from_opexps() works well for various cases of +-- clauses for different partition keys +-- + +create table rp_prefix_test1 (a int, b varchar) partition by range(a, b); +create table rp_prefix_test1_p1 partition of rp_prefix_test1 for values from (1, 'a') to (1, 'b'); +create table rp_prefix_test1_p2 partition of rp_prefix_test1 for values from (2, 'a') to (2, 'b'); + +-- Don't call get_steps_using_prefix() with the last partition key b plus +-- an empty prefix +explain (costs off) select * from rp_prefix_test1 where a <= 1 and b = 'a'; + +create table rp_prefix_test2 (a int, b int, c int) partition by range(a, b, c); +create table rp_prefix_test2_p1 partition of rp_prefix_test2 for values from (1, 1, 0) to (1, 1, 10); +create table rp_prefix_test2_p2 partition of rp_prefix_test2 for values from (2, 2, 0) to (2, 2, 10); + +-- Don't call get_steps_using_prefix() with the last partition key c plus +-- an invalid prefix (ie, b = 1) +explain (costs off) select * from rp_prefix_test2 where a <= 1 and b = 1 and c >= 0; + +create table rp_prefix_test3 (a int, b int, c int, d int) partition by range(a, b, c, d); +create table rp_prefix_test3_p1 partition of rp_prefix_test3 for values from (1, 1, 1, 0) to (1, 1, 1, 10); +create table rp_prefix_test3_p2 partition of rp_prefix_test3 for values from (2, 2, 2, 0) to (2, 2, 2, 10); + +-- Test that get_steps_using_prefix() handles a prefix that contains multiple +-- clauses for the partition key b (ie, b >= 1 and b >= 2) +explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b >= 2 and c >= 2 and d >= 0; + +-- Test that get_steps_using_prefix() handles a prefix that contains multiple +-- clauses for the partition key b (ie, b >= 1 and b = 2) (This also tests +-- that the caller arranges clauses in that prefix in the required order) +explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b = 2 and c = 2 and d >= 0; + +create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops); +create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 2, remainder 0); +create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 2, remainder 1); + +-- Test that get_steps_using_prefix() handles non-NULL step_nullkeys +explain (costs off) select * from hp_prefix_test where a = 1 and b is null and c = 1 and d = 1; + +drop table rp_prefix_test1; +drop table rp_prefix_test2; +drop table rp_prefix_test3; +drop table hp_prefix_test; + +-- +-- Check that gen_partprune_steps() detects self-contradiction from clauses +-- regardless of the order of the clauses (Here we use a custom operator to +-- prevent the equivclass.c machinery from reordering the clauses) +-- + +create operator === ( + leftarg = int4, + rightarg = int4, + procedure = int4eq, + commutator = ===, + hashes +); +create operator class part_test_int4_ops2 +for type int4 +using hash as +operator 1 ===, +function 2 part_hashint4_noop(int4, int8); + +create table hp_contradict_test (a int, b int) partition by hash (a part_test_int4_ops2, b part_test_int4_ops2); +create table hp_contradict_test_p1 partition of hp_contradict_test for values with (modulus 2, remainder 0); +create table hp_contradict_test_p2 partition of hp_contradict_test for values with (modulus 2, remainder 1); + +explain (costs off) select * from hp_contradict_test where a is null and a === 1 and b === 1; +explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 and a is null; + +drop table hp_contradict_test; +drop operator class part_test_int4_ops2 using hash; +drop operator ===(int4, int4); diff --git a/src/test/regress/sql/yb.port.pg_lsn.sql b/src/test/regress/sql/yb.port.pg_lsn.sql new file mode 100644 index 0000000000000..0fe69e5c26957 --- /dev/null +++ b/src/test/regress/sql/yb.port.pg_lsn.sql @@ -0,0 +1,54 @@ +-- +-- PG_LSN +-- + +CREATE TABLE PG_LSN_TBL (f1 pg_lsn); + +-- Largest and smallest input +INSERT INTO PG_LSN_TBL VALUES ('0/0'); +INSERT INTO PG_LSN_TBL VALUES ('FFFFFFFF/FFFFFFFF'); + +-- Incorrect input +INSERT INTO PG_LSN_TBL VALUES ('G/0'); +INSERT INTO PG_LSN_TBL VALUES ('-1/0'); +INSERT INTO PG_LSN_TBL VALUES (' 0/12345678'); +INSERT INTO PG_LSN_TBL VALUES ('ABCD/'); +INSERT INTO PG_LSN_TBL VALUES ('/ABCD'); +DROP TABLE PG_LSN_TBL; + +-- Operators +SELECT '0/16AE7F8' = '0/16AE7F8'::pg_lsn; +SELECT '0/16AE7F8'::pg_lsn != '0/16AE7F7'; +SELECT '0/16AE7F7' < '0/16AE7F8'::pg_lsn; +SELECT '0/16AE7F8' > pg_lsn '0/16AE7F7'; +SELECT '0/16AE7F7'::pg_lsn - '0/16AE7F8'::pg_lsn; +SELECT '0/16AE7F8'::pg_lsn - '0/16AE7F7'::pg_lsn; + +-- Check lsm and hash opclasses +EXPLAIN (COSTS OFF) +SELECT DISTINCT (i || '/' || j)::pg_lsn f + FROM generate_series(1, 10) i, + generate_series(1, 10) j, + generate_series(1, 5) k + WHERE i <= 10 AND j > 0 AND j <= 10 + ORDER BY f; + +SELECT DISTINCT (i || '/' || j)::pg_lsn f + FROM generate_series(1, 10) i, + generate_series(1, 10) j, + generate_series(1, 5) k + WHERE i <= 10 AND j > 0 AND j <= 10 + ORDER BY f; + +-- Check ordering +CREATE TABLE PG_LSN_TBL_ASC(a INT, b PG_LSN, PRIMARY KEY(a HASH, b ASC)); +INSERT INTO PG_LSN_TBL_ASC VALUES + (1, 'DEADBEAF/DEADBEAF'), + (1, '7FFFFFFF/FFFFFFFF'), + (1, '80000000/00000000'), + (1, '0/0'), + (1, '0/1'), + (1, 'FFFFFFFF/FFFFFFFF'), + (1, '0/64'), + (1, 'FFFFFFFF/FFFFFF9C'); +SELECT b FROM PG_LSN_TBL_ASC WHERE a=1; diff --git a/src/test/regress/sql/yb.port.plpgsql.sql b/src/test/regress/sql/yb.port.plpgsql.sql new file mode 100644 index 0000000000000..eba68a859eb45 --- /dev/null +++ b/src/test/regress/sql/yb.port.plpgsql.sql @@ -0,0 +1,4864 @@ +-- +-- PLPGSQL +-- +-- Scenario: +-- +-- A building with a modern TP cable installation where any +-- of the wall connectors can be used to plug in phones, +-- ethernet interfaces or local office hubs. The backside +-- of the wall connectors is wired to one of several patch- +-- fields in the building. +-- +-- In the patchfields, there are hubs and all the slots +-- representing the wall connectors. In addition there are +-- slots that can represent a phone line from the central +-- phone system. +-- +-- Triggers ensure consistency of the patching information. +-- +-- Functions are used to build up powerful views that let +-- you look behind the wall when looking at a patchfield +-- or into a room. +-- + + +create table Room ( + roomno char(8), + comment text +); + +create unique index Room_rno on Room using btree (roomno bpchar_ops); + + +create table WSlot ( + slotname char(20), + roomno char(8), + slotlink char(20), + backlink char(20) +); + +create unique index WSlot_name on WSlot using btree (slotname bpchar_ops); + + +create table PField ( + name text, + comment text +); + +create unique index PField_name on PField using btree (name text_ops); + + +create table PSlot ( + slotname char(20), + pfname text, + slotlink char(20), + backlink char(20) +); + +create unique index PSlot_name on PSlot using btree (slotname bpchar_ops); + + +create table PLine ( + slotname char(20), + phonenumber char(20), + comment text, + backlink char(20) +); + +create unique index PLine_name on PLine using btree (slotname bpchar_ops); + + +create table Hub ( + name char(14), + comment text, + nslots integer +); + +create unique index Hub_name on Hub using btree (name bpchar_ops); + + +create table HSlot ( + slotname char(20), + hubname char(14), + slotno integer, + slotlink char(20) +); + +create unique index HSlot_name on HSlot using btree (slotname bpchar_ops); +create index HSlot_hubname on HSlot using btree (hubname bpchar_ops); + + +create table System ( + name text, + comment text +); + +create unique index System_name on System using btree (name text_ops); + + +create table IFace ( + slotname char(20), + sysname text, + ifname text, + slotlink char(20) +); + +create unique index IFace_name on IFace using btree (slotname bpchar_ops); + + +create table PHone ( + slotname char(20), + comment text, + slotlink char(20) +); + +create unique index PHone_name on PHone using btree (slotname bpchar_ops); + + +-- ************************************************************ +-- * +-- * Trigger procedures and functions for the patchfield +-- * test of PL/pgSQL +-- * +-- ************************************************************ + + +-- ************************************************************ +-- * AFTER UPDATE on Room +-- * - If room no changes let wall slots follow +-- ************************************************************ +create function tg_room_au() returns trigger as ' +begin + if new.roomno != old.roomno then + update WSlot set roomno = new.roomno where roomno = old.roomno; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_room_au after update + on Room for each row execute procedure tg_room_au(); + + +-- ************************************************************ +-- * AFTER DELETE on Room +-- * - delete wall slots in this room +-- ************************************************************ +create function tg_room_ad() returns trigger as ' +begin + delete from WSlot where roomno = old.roomno; + return old; +end; +' language plpgsql; + +create trigger tg_room_ad after delete + on Room for each row execute procedure tg_room_ad(); + + +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on WSlot +-- * - Check that room exists +-- ************************************************************ +create function tg_wslot_biu() returns trigger as $$ +begin + if count(*) = 0 from Room where roomno = new.roomno then + raise exception 'Room % does not exist', new.roomno; + end if; + return new; +end; +$$ language plpgsql; + +create trigger tg_wslot_biu before insert or update + on WSlot for each row execute procedure tg_wslot_biu(); + + +-- ************************************************************ +-- * AFTER UPDATE on PField +-- * - Let PSlots of this field follow +-- ************************************************************ +create function tg_pfield_au() returns trigger as ' +begin + if new.name != old.name then + update PSlot set pfname = new.name where pfname = old.name; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_pfield_au after update + on PField for each row execute procedure tg_pfield_au(); + + +-- ************************************************************ +-- * AFTER DELETE on PField +-- * - Remove all slots of this patchfield +-- ************************************************************ +create function tg_pfield_ad() returns trigger as ' +begin + delete from PSlot where pfname = old.name; + return old; +end; +' language plpgsql; + +create trigger tg_pfield_ad after delete + on PField for each row execute procedure tg_pfield_ad(); + + +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on PSlot +-- * - Ensure that our patchfield does exist +-- ************************************************************ +create function tg_pslot_biu() returns trigger as $proc$ +declare + pfrec record; + ps alias for new; +begin + select into pfrec * from PField where name = ps.pfname; + if not found then + raise exception $$Patchfield "%" does not exist$$, ps.pfname; + end if; + return ps; +end; +$proc$ language plpgsql; + +create trigger tg_pslot_biu before insert or update + on PSlot for each row execute procedure tg_pslot_biu(); + + +-- ************************************************************ +-- * AFTER UPDATE on System +-- * - If system name changes let interfaces follow +-- ************************************************************ +create function tg_system_au() returns trigger as ' +begin + if new.name != old.name then + update IFace set sysname = new.name where sysname = old.name; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_system_au after update + on System for each row execute procedure tg_system_au(); + + +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on IFace +-- * - set the slotname to IF.sysname.ifname +-- ************************************************************ +create function tg_iface_biu() returns trigger as $$ +declare + sname text; + sysrec record; +begin + select into sysrec * from system where name = new.sysname; + if not found then + raise exception $q$system "%" does not exist$q$, new.sysname; + end if; + sname := 'IF.' || new.sysname; + sname := sname || '.'; + sname := sname || new.ifname; + if length(sname) > 20 then + raise exception 'IFace slotname "%" too long (20 char max)', sname; + end if; + new.slotname := sname; + return new; +end; +$$ language plpgsql; + +create trigger tg_iface_biu before insert or update + on IFace for each row execute procedure tg_iface_biu(); + + +-- ************************************************************ +-- * AFTER INSERT or UPDATE or DELETE on Hub +-- * - insert/delete/rename slots as required +-- ************************************************************ +create function tg_hub_a() returns trigger as ' +declare + hname text; + dummy integer; +begin + if tg_op = ''INSERT'' then + dummy := tg_hub_adjustslots(new.name, 0, new.nslots); + return new; + end if; + if tg_op = ''UPDATE'' then + if new.name != old.name then + update HSlot set hubname = new.name where hubname = old.name; + end if; + dummy := tg_hub_adjustslots(new.name, old.nslots, new.nslots); + return new; + end if; + if tg_op = ''DELETE'' then + dummy := tg_hub_adjustslots(old.name, old.nslots, 0); + return old; + end if; +end; +' language plpgsql; + +create trigger tg_hub_a after insert or update or delete + on Hub for each row execute procedure tg_hub_a(); + + +-- ************************************************************ +-- * Support function to add/remove slots of Hub +-- ************************************************************ +create function tg_hub_adjustslots(hname bpchar, + oldnslots integer, + newnslots integer) +returns integer as ' +begin + if newnslots = oldnslots then + return 0; + end if; + if newnslots < oldnslots then + delete from HSlot where hubname = hname and slotno > newnslots; + return 0; + end if; + for i in oldnslots + 1 .. newnslots loop + insert into HSlot (slotname, hubname, slotno, slotlink) + values (''HS.dummy'', hname, i, ''''); + end loop; + return 0; +end +' language plpgsql; + +-- Test comments +COMMENT ON FUNCTION tg_hub_adjustslots_wrong(bpchar, integer, integer) IS 'function with args'; +COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS 'function with args'; +COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS NULL; + +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on HSlot +-- * - prevent from manual manipulation +-- * - set the slotname to HS.hubname.slotno +-- ************************************************************ +create function tg_hslot_biu() returns trigger as ' +declare + sname text; + xname HSlot.slotname%TYPE; + hubrec record; +begin + select into hubrec * from Hub where name = new.hubname; + if not found then + raise exception ''no manual manipulation of HSlot''; + end if; + if new.slotno < 1 or new.slotno > hubrec.nslots then + raise exception ''no manual manipulation of HSlot''; + end if; + if tg_op = ''UPDATE'' and new.hubname != old.hubname then + if count(*) > 0 from Hub where name = old.hubname then + raise exception ''no manual manipulation of HSlot''; + end if; + end if; + sname := ''HS.'' || trim(new.hubname); + sname := sname || ''.''; + sname := sname || new.slotno::text; + if length(sname) > 20 then + raise exception ''HSlot slotname "%" too long (20 char max)'', sname; + end if; + new.slotname := sname; + return new; +end; +' language plpgsql; + +create trigger tg_hslot_biu before insert or update + on HSlot for each row execute procedure tg_hslot_biu(); + + +-- ************************************************************ +-- * BEFORE DELETE on HSlot +-- * - prevent from manual manipulation +-- ************************************************************ +create function tg_hslot_bd() returns trigger as ' +declare + hubrec record; +begin + select into hubrec * from Hub where name = old.hubname; + if not found then + return old; + end if; + if old.slotno > hubrec.nslots then + return old; + end if; + raise exception ''no manual manipulation of HSlot''; +end; +' language plpgsql; + +create trigger tg_hslot_bd before delete + on HSlot for each row execute procedure tg_hslot_bd(); + + +-- ************************************************************ +-- * BEFORE INSERT on all slots +-- * - Check name prefix +-- ************************************************************ +create function tg_chkslotname() returns trigger as ' +begin + if substr(new.slotname, 1, 2) != tg_argv[0] then + raise exception ''slotname must begin with %'', tg_argv[0]; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_chkslotname before insert + on PSlot for each row execute procedure tg_chkslotname('PS'); + +create trigger tg_chkslotname before insert + on WSlot for each row execute procedure tg_chkslotname('WS'); + +create trigger tg_chkslotname before insert + on PLine for each row execute procedure tg_chkslotname('PL'); + +create trigger tg_chkslotname before insert + on IFace for each row execute procedure tg_chkslotname('IF'); + +create trigger tg_chkslotname before insert + on PHone for each row execute procedure tg_chkslotname('PH'); + + +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on all slots with slotlink +-- * - Set slotlink to empty string if NULL value given +-- ************************************************************ +create function tg_chkslotlink() returns trigger as ' +begin + if new.slotlink isnull then + new.slotlink := ''''; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_chkslotlink before insert or update + on PSlot for each row execute procedure tg_chkslotlink(); + +create trigger tg_chkslotlink before insert or update + on WSlot for each row execute procedure tg_chkslotlink(); + +create trigger tg_chkslotlink before insert or update + on IFace for each row execute procedure tg_chkslotlink(); + +create trigger tg_chkslotlink before insert or update + on HSlot for each row execute procedure tg_chkslotlink(); + +create trigger tg_chkslotlink before insert or update + on PHone for each row execute procedure tg_chkslotlink(); + + +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on all slots with backlink +-- * - Set backlink to empty string if NULL value given +-- ************************************************************ +create function tg_chkbacklink() returns trigger as ' +begin + if new.backlink isnull then + new.backlink := ''''; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_chkbacklink before insert or update + on PSlot for each row execute procedure tg_chkbacklink(); + +create trigger tg_chkbacklink before insert or update + on WSlot for each row execute procedure tg_chkbacklink(); + +create trigger tg_chkbacklink before insert or update + on PLine for each row execute procedure tg_chkbacklink(); + + +-- ************************************************************ +-- * BEFORE UPDATE on PSlot +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_pslot_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from PSlot where slotname = old.slotname; + insert into PSlot ( + slotname, + pfname, + slotlink, + backlink + ) values ( + new.slotname, + new.pfname, + new.slotlink, + new.backlink + ); + return null; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_pslot_bu before update + on PSlot for each row execute procedure tg_pslot_bu(); + + +-- ************************************************************ +-- * BEFORE UPDATE on WSlot +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_wslot_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from WSlot where slotname = old.slotname; + insert into WSlot ( + slotname, + roomno, + slotlink, + backlink + ) values ( + new.slotname, + new.roomno, + new.slotlink, + new.backlink + ); + return null; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_wslot_bu before update + on WSlot for each row execute procedure tg_Wslot_bu(); + + +-- ************************************************************ +-- * BEFORE UPDATE on PLine +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_pline_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from PLine where slotname = old.slotname; + insert into PLine ( + slotname, + phonenumber, + comment, + backlink + ) values ( + new.slotname, + new.phonenumber, + new.comment, + new.backlink + ); + return null; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_pline_bu before update + on PLine for each row execute procedure tg_pline_bu(); + + +-- ************************************************************ +-- * BEFORE UPDATE on IFace +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_iface_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from IFace where slotname = old.slotname; + insert into IFace ( + slotname, + sysname, + ifname, + slotlink + ) values ( + new.slotname, + new.sysname, + new.ifname, + new.slotlink + ); + return null; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_iface_bu before update + on IFace for each row execute procedure tg_iface_bu(); + + +-- ************************************************************ +-- * BEFORE UPDATE on HSlot +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_hslot_bu() returns trigger as ' +begin + if new.slotname != old.slotname or new.hubname != old.hubname then + delete from HSlot where slotname = old.slotname; + insert into HSlot ( + slotname, + hubname, + slotno, + slotlink + ) values ( + new.slotname, + new.hubname, + new.slotno, + new.slotlink + ); + return null; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_hslot_bu before update + on HSlot for each row execute procedure tg_hslot_bu(); + + +-- ************************************************************ +-- * BEFORE UPDATE on PHone +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_phone_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from PHone where slotname = old.slotname; + insert into PHone ( + slotname, + comment, + slotlink + ) values ( + new.slotname, + new.comment, + new.slotlink + ); + return null; + end if; + return new; +end; +' language plpgsql; + +create trigger tg_phone_bu before update + on PHone for each row execute procedure tg_phone_bu(); + + +-- ************************************************************ +-- * AFTER INSERT or UPDATE or DELETE on slot with backlink +-- * - Ensure that the opponent correctly points back to us +-- ************************************************************ +create function tg_backlink_a() returns trigger as ' +declare + dummy integer; +begin + if tg_op = ''INSERT'' then + if new.backlink != '''' then + dummy := tg_backlink_set(new.backlink, new.slotname); + end if; + return new; + end if; + if tg_op = ''UPDATE'' then + if new.backlink != old.backlink then + if old.backlink != '''' then + dummy := tg_backlink_unset(old.backlink, old.slotname); + end if; + if new.backlink != '''' then + dummy := tg_backlink_set(new.backlink, new.slotname); + end if; + else + if new.slotname != old.slotname and new.backlink != '''' then + dummy := tg_slotlink_set(new.backlink, new.slotname); + end if; + end if; + return new; + end if; + if tg_op = ''DELETE'' then + if old.backlink != '''' then + dummy := tg_backlink_unset(old.backlink, old.slotname); + end if; + return old; + end if; +end; +' language plpgsql; + + +create trigger tg_backlink_a after insert or update or delete + on PSlot for each row execute procedure tg_backlink_a('PS'); + +create trigger tg_backlink_a after insert or update or delete + on WSlot for each row execute procedure tg_backlink_a('WS'); + +create trigger tg_backlink_a after insert or update or delete + on PLine for each row execute procedure tg_backlink_a('PL'); + + +-- ************************************************************ +-- * Support function to set the opponents backlink field +-- * if it does not already point to the requested slot +-- ************************************************************ +create function tg_backlink_set(myname bpchar, blname bpchar) +returns integer as ' +declare + mytype char(2); + link char(4); + rec record; +begin + mytype := substr(myname, 1, 2); + link := mytype || substr(blname, 1, 2); + if link = ''PLPL'' then + raise exception + ''backlink between two phone lines does not make sense''; + end if; + if link in (''PLWS'', ''WSPL'') then + raise exception + ''direct link of phone line to wall slot not permitted''; + end if; + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.backlink != blname then + update PSlot set backlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.backlink != blname then + update WSlot set backlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PL'' then + select into rec * from PLine where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.backlink != blname then + update PLine set backlink = blname where slotname = myname; + end if; + return 0; + end if; + raise exception ''illegal backlink beginning with %'', mytype; +end; +' language plpgsql; + + +-- ************************************************************ +-- * Support function to clear out the backlink field if +-- * it still points to specific slot +-- ************************************************************ +create function tg_backlink_unset(bpchar, bpchar) +returns integer as ' +declare + myname alias for $1; + blname alias for $2; + mytype char(2); + rec record; +begin + mytype := substr(myname, 1, 2); + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.backlink = blname then + update PSlot set backlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.backlink = blname then + update WSlot set backlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PL'' then + select into rec * from PLine where slotname = myname; + if not found then + return 0; + end if; + if rec.backlink = blname then + update PLine set backlink = '''' where slotname = myname; + end if; + return 0; + end if; +end +' language plpgsql; + + +-- ************************************************************ +-- * AFTER INSERT or UPDATE or DELETE on slot with slotlink +-- * - Ensure that the opponent correctly points back to us +-- ************************************************************ +create function tg_slotlink_a() returns trigger as ' +declare + dummy integer; +begin + if tg_op = ''INSERT'' then + if new.slotlink != '''' then + dummy := tg_slotlink_set(new.slotlink, new.slotname); + end if; + return new; + end if; + if tg_op = ''UPDATE'' then + if new.slotlink != old.slotlink then + if old.slotlink != '''' then + dummy := tg_slotlink_unset(old.slotlink, old.slotname); + end if; + if new.slotlink != '''' then + dummy := tg_slotlink_set(new.slotlink, new.slotname); + end if; + else + if new.slotname != old.slotname and new.slotlink != '''' then + dummy := tg_slotlink_set(new.slotlink, new.slotname); + end if; + end if; + return new; + end if; + if tg_op = ''DELETE'' then + if old.slotlink != '''' then + dummy := tg_slotlink_unset(old.slotlink, old.slotname); + end if; + return old; + end if; +end; +' language plpgsql; + + +create trigger tg_slotlink_a after insert or update or delete + on PSlot for each row execute procedure tg_slotlink_a('PS'); + +create trigger tg_slotlink_a after insert or update or delete + on WSlot for each row execute procedure tg_slotlink_a('WS'); + +create trigger tg_slotlink_a after insert or update or delete + on IFace for each row execute procedure tg_slotlink_a('IF'); + +create trigger tg_slotlink_a after insert or update or delete + on HSlot for each row execute procedure tg_slotlink_a('HS'); + +create trigger tg_slotlink_a after insert or update or delete + on PHone for each row execute procedure tg_slotlink_a('PH'); + + +-- ************************************************************ +-- * Support function to set the opponents slotlink field +-- * if it does not already point to the requested slot +-- ************************************************************ +create function tg_slotlink_set(bpchar, bpchar) +returns integer as ' +declare + myname alias for $1; + blname alias for $2; + mytype char(2); + link char(4); + rec record; +begin + mytype := substr(myname, 1, 2); + link := mytype || substr(blname, 1, 2); + if link = ''PHPH'' then + raise exception + ''slotlink between two phones does not make sense''; + end if; + if link in (''PHHS'', ''HSPH'') then + raise exception + ''link of phone to hub does not make sense''; + end if; + if link in (''PHIF'', ''IFPH'') then + raise exception + ''link of phone to hub does not make sense''; + end if; + if link in (''PSWS'', ''WSPS'') then + raise exception + ''slotlink from patchslot to wallslot not permitted''; + end if; + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update PSlot set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update WSlot set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''IF'' then + select into rec * from IFace where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update IFace set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''HS'' then + select into rec * from HSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update HSlot set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PH'' then + select into rec * from PHone where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update PHone set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + raise exception ''illegal slotlink beginning with %'', mytype; +end; +' language plpgsql; + + +-- ************************************************************ +-- * Support function to clear out the slotlink field if +-- * it still points to specific slot +-- ************************************************************ +create function tg_slotlink_unset(bpchar, bpchar) +returns integer as ' +declare + myname alias for $1; + blname alias for $2; + mytype char(2); + rec record; +begin + mytype := substr(myname, 1, 2); + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update PSlot set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update WSlot set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''IF'' then + select into rec * from IFace where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update IFace set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''HS'' then + select into rec * from HSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update HSlot set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PH'' then + select into rec * from PHone where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update PHone set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; +end; +' language plpgsql; + + +-- ************************************************************ +-- * Describe the backside of a patchfield slot +-- ************************************************************ +create function pslot_backlink_view(bpchar) +returns text as ' +<> +declare + rec record; + bltype char(2); + retval text; +begin + select into rec * from PSlot where slotname = $1; + if not found then + return ''''; + end if; + if rec.backlink = '''' then + return ''-''; + end if; + bltype := substr(rec.backlink, 1, 2); + if bltype = ''PL'' then + declare + rec record; + begin + select into rec * from PLine where slotname = "outer".rec.backlink; + retval := ''Phone line '' || trim(rec.phonenumber); + if rec.comment != '''' then + retval := retval || '' (''; + retval := retval || rec.comment; + retval := retval || '')''; + end if; + return retval; + end; + end if; + if bltype = ''WS'' then + select into rec * from WSlot where slotname = rec.backlink; + retval := trim(rec.slotname) || '' in room ''; + retval := retval || trim(rec.roomno); + retval := retval || '' -> ''; + return retval || wslot_slotlink_view(rec.slotname); + end if; + return rec.backlink; +end; +' language plpgsql; + + +-- ************************************************************ +-- * Describe the front of a patchfield slot +-- ************************************************************ +create function pslot_slotlink_view(bpchar) +returns text as ' +declare + psrec record; + sltype char(2); + retval text; +begin + select into psrec * from PSlot where slotname = $1; + if not found then + return ''''; + end if; + if psrec.slotlink = '''' then + return ''-''; + end if; + sltype := substr(psrec.slotlink, 1, 2); + if sltype = ''PS'' then + retval := trim(psrec.slotlink) || '' -> ''; + return retval || pslot_backlink_view(psrec.slotlink); + end if; + if sltype = ''HS'' then + retval := comment from Hub H, HSlot HS + where HS.slotname = psrec.slotlink + and H.name = HS.hubname; + retval := retval || '' slot ''; + retval := retval || slotno::text from HSlot + where slotname = psrec.slotlink; + return retval; + end if; + return psrec.slotlink; +end; +' language plpgsql; + + +-- ************************************************************ +-- * Describe the front of a wall connector slot +-- ************************************************************ +create function wslot_slotlink_view(bpchar) +returns text as ' +declare + rec record; + sltype char(2); + retval text; +begin + select into rec * from WSlot where slotname = $1; + if not found then + return ''''; + end if; + if rec.slotlink = '''' then + return ''-''; + end if; + sltype := substr(rec.slotlink, 1, 2); + if sltype = ''PH'' then + select into rec * from PHone where slotname = rec.slotlink; + retval := ''Phone '' || trim(rec.slotname); + if rec.comment != '''' then + retval := retval || '' (''; + retval := retval || rec.comment; + retval := retval || '')''; + end if; + return retval; + end if; + if sltype = ''IF'' then + declare + syrow System%RowType; + ifrow IFace%ROWTYPE; + begin + select into ifrow * from IFace where slotname = rec.slotlink; + select into syrow * from System where name = ifrow.sysname; + retval := syrow.name || '' IF ''; + retval := retval || ifrow.ifname; + if syrow.comment != '''' then + retval := retval || '' (''; + retval := retval || syrow.comment; + retval := retval || '')''; + end if; + return retval; + end; + end if; + return rec.slotlink; +end; +' language plpgsql; + + + +-- ************************************************************ +-- * View of a patchfield describing backside and patches +-- ************************************************************ +create view Pfield_v1 as select PF.pfname, PF.slotname, + pslot_backlink_view(PF.slotname) as backside, + pslot_slotlink_view(PF.slotname) as patch + from PSlot PF; + + +-- +-- First we build the house - so we create the rooms +-- +insert into Room values ('001', 'Entrance'); +insert into Room values ('002', 'Office'); +insert into Room values ('003', 'Office'); +insert into Room values ('004', 'Technical'); +insert into Room values ('101', 'Office'); +insert into Room values ('102', 'Conference'); +insert into Room values ('103', 'Restroom'); +insert into Room values ('104', 'Technical'); +insert into Room values ('105', 'Office'); +insert into Room values ('106', 'Office'); + +-- +-- Second we install the wall connectors +-- +insert into WSlot values ('WS.001.1a', '001', '', ''); +insert into WSlot values ('WS.001.1b', '001', '', ''); +insert into WSlot values ('WS.001.2a', '001', '', ''); +insert into WSlot values ('WS.001.2b', '001', '', ''); +insert into WSlot values ('WS.001.3a', '001', '', ''); +insert into WSlot values ('WS.001.3b', '001', '', ''); + +insert into WSlot values ('WS.002.1a', '002', '', ''); +insert into WSlot values ('WS.002.1b', '002', '', ''); +insert into WSlot values ('WS.002.2a', '002', '', ''); +insert into WSlot values ('WS.002.2b', '002', '', ''); +insert into WSlot values ('WS.002.3a', '002', '', ''); +insert into WSlot values ('WS.002.3b', '002', '', ''); + +insert into WSlot values ('WS.003.1a', '003', '', ''); +insert into WSlot values ('WS.003.1b', '003', '', ''); +insert into WSlot values ('WS.003.2a', '003', '', ''); +insert into WSlot values ('WS.003.2b', '003', '', ''); +insert into WSlot values ('WS.003.3a', '003', '', ''); +insert into WSlot values ('WS.003.3b', '003', '', ''); + +insert into WSlot values ('WS.101.1a', '101', '', ''); +insert into WSlot values ('WS.101.1b', '101', '', ''); +insert into WSlot values ('WS.101.2a', '101', '', ''); +insert into WSlot values ('WS.101.2b', '101', '', ''); +insert into WSlot values ('WS.101.3a', '101', '', ''); +insert into WSlot values ('WS.101.3b', '101', '', ''); + +insert into WSlot values ('WS.102.1a', '102', '', ''); +insert into WSlot values ('WS.102.1b', '102', '', ''); +insert into WSlot values ('WS.102.2a', '102', '', ''); +insert into WSlot values ('WS.102.2b', '102', '', ''); +insert into WSlot values ('WS.102.3a', '102', '', ''); +insert into WSlot values ('WS.102.3b', '102', '', ''); + +insert into WSlot values ('WS.105.1a', '105', '', ''); +insert into WSlot values ('WS.105.1b', '105', '', ''); +insert into WSlot values ('WS.105.2a', '105', '', ''); +insert into WSlot values ('WS.105.2b', '105', '', ''); +insert into WSlot values ('WS.105.3a', '105', '', ''); +insert into WSlot values ('WS.105.3b', '105', '', ''); + +insert into WSlot values ('WS.106.1a', '106', '', ''); +insert into WSlot values ('WS.106.1b', '106', '', ''); +insert into WSlot values ('WS.106.2a', '106', '', ''); +insert into WSlot values ('WS.106.2b', '106', '', ''); +insert into WSlot values ('WS.106.3a', '106', '', ''); +insert into WSlot values ('WS.106.3b', '106', '', ''); + +-- +-- Now create the patch fields and their slots +-- +insert into PField values ('PF0_1', 'Wallslots basement'); + +-- +-- The cables for these will be made later, so they are unconnected for now +-- +insert into PSlot values ('PS.base.a1', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a2', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a3', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a4', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a5', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a6', 'PF0_1', '', ''); + +-- +-- These are already wired to the wall connectors +-- +insert into PSlot values ('PS.base.b1', 'PF0_1', '', 'WS.002.1a'); +insert into PSlot values ('PS.base.b2', 'PF0_1', '', 'WS.002.1b'); +insert into PSlot values ('PS.base.b3', 'PF0_1', '', 'WS.002.2a'); +insert into PSlot values ('PS.base.b4', 'PF0_1', '', 'WS.002.2b'); +insert into PSlot values ('PS.base.b5', 'PF0_1', '', 'WS.002.3a'); +insert into PSlot values ('PS.base.b6', 'PF0_1', '', 'WS.002.3b'); + +insert into PSlot values ('PS.base.c1', 'PF0_1', '', 'WS.003.1a'); +insert into PSlot values ('PS.base.c2', 'PF0_1', '', 'WS.003.1b'); +insert into PSlot values ('PS.base.c3', 'PF0_1', '', 'WS.003.2a'); +insert into PSlot values ('PS.base.c4', 'PF0_1', '', 'WS.003.2b'); +insert into PSlot values ('PS.base.c5', 'PF0_1', '', 'WS.003.3a'); +insert into PSlot values ('PS.base.c6', 'PF0_1', '', 'WS.003.3b'); + +-- +-- This patchfield will be renamed later into PF0_2 - so its +-- slots references in pfname should follow +-- +insert into PField values ('PF0_X', 'Phonelines basement'); + +insert into PSlot values ('PS.base.ta1', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta2', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta3', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta4', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta5', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta6', 'PF0_X', '', ''); + +insert into PSlot values ('PS.base.tb1', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb2', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb3', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb4', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb5', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb6', 'PF0_X', '', ''); + +insert into PField values ('PF1_1', 'Wallslots first floor'); + +insert into PSlot values ('PS.first.a1', 'PF1_1', '', 'WS.101.1a'); +insert into PSlot values ('PS.first.a2', 'PF1_1', '', 'WS.101.1b'); +insert into PSlot values ('PS.first.a3', 'PF1_1', '', 'WS.101.2a'); +insert into PSlot values ('PS.first.a4', 'PF1_1', '', 'WS.101.2b'); +insert into PSlot values ('PS.first.a5', 'PF1_1', '', 'WS.101.3a'); +insert into PSlot values ('PS.first.a6', 'PF1_1', '', 'WS.101.3b'); + +insert into PSlot values ('PS.first.b1', 'PF1_1', '', 'WS.102.1a'); +insert into PSlot values ('PS.first.b2', 'PF1_1', '', 'WS.102.1b'); +insert into PSlot values ('PS.first.b3', 'PF1_1', '', 'WS.102.2a'); +insert into PSlot values ('PS.first.b4', 'PF1_1', '', 'WS.102.2b'); +insert into PSlot values ('PS.first.b5', 'PF1_1', '', 'WS.102.3a'); +insert into PSlot values ('PS.first.b6', 'PF1_1', '', 'WS.102.3b'); + +insert into PSlot values ('PS.first.c1', 'PF1_1', '', 'WS.105.1a'); +insert into PSlot values ('PS.first.c2', 'PF1_1', '', 'WS.105.1b'); +insert into PSlot values ('PS.first.c3', 'PF1_1', '', 'WS.105.2a'); +insert into PSlot values ('PS.first.c4', 'PF1_1', '', 'WS.105.2b'); +insert into PSlot values ('PS.first.c5', 'PF1_1', '', 'WS.105.3a'); +insert into PSlot values ('PS.first.c6', 'PF1_1', '', 'WS.105.3b'); + +insert into PSlot values ('PS.first.d1', 'PF1_1', '', 'WS.106.1a'); +insert into PSlot values ('PS.first.d2', 'PF1_1', '', 'WS.106.1b'); +insert into PSlot values ('PS.first.d3', 'PF1_1', '', 'WS.106.2a'); +insert into PSlot values ('PS.first.d4', 'PF1_1', '', 'WS.106.2b'); +insert into PSlot values ('PS.first.d5', 'PF1_1', '', 'WS.106.3a'); +insert into PSlot values ('PS.first.d6', 'PF1_1', '', 'WS.106.3b'); + +-- +-- Now we wire the wall connectors 1a-2a in room 001 to the +-- patchfield. In the second update we make an error, and +-- correct it after +-- +update PSlot set backlink = 'WS.001.1a' where slotname = 'PS.base.a1'; +update PSlot set backlink = 'WS.001.1b' where slotname = 'PS.base.a3'; +select * from WSlot where roomno = '001' order by slotname; +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; +update PSlot set backlink = 'WS.001.2a' where slotname = 'PS.base.a3'; +select * from WSlot where roomno = '001' order by slotname; +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; +update PSlot set backlink = 'WS.001.1b' where slotname = 'PS.base.a2'; +select * from WSlot where roomno = '001' order by slotname; +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + +-- +-- Same procedure for 2b-3b but this time updating the WSlot instead +-- of the PSlot. Due to the triggers the result is the same: +-- WSlot and corresponding PSlot point to each other. +-- +update WSlot set backlink = 'PS.base.a4' where slotname = 'WS.001.2b'; +update WSlot set backlink = 'PS.base.a6' where slotname = 'WS.001.3a'; +select * from WSlot where roomno = '001' order by slotname; +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; +update WSlot set backlink = 'PS.base.a6' where slotname = 'WS.001.3b'; +select * from WSlot where roomno = '001' order by slotname; +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; +update WSlot set backlink = 'PS.base.a5' where slotname = 'WS.001.3a'; +select * from WSlot where roomno = '001' order by slotname; +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + +insert into PField values ('PF1_2', 'Phonelines first floor'); + +insert into PSlot values ('PS.first.ta1', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta2', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta3', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta4', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta5', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta6', 'PF1_2', '', ''); + +insert into PSlot values ('PS.first.tb1', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb2', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb3', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb4', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb5', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb6', 'PF1_2', '', ''); + +-- +-- Fix the wrong name for patchfield PF0_2 +-- +update PField set name = 'PF0_2' where name = 'PF0_X'; + +select * from PSlot order by slotname; +select * from WSlot order by slotname; + +-- +-- Install the central phone system and create the phone numbers. +-- They are wired on insert to the patchfields. Again the +-- triggers automatically tell the PSlots to update their +-- backlink field. +-- +insert into PLine values ('PL.001', '-0', 'Central call', 'PS.base.ta1'); +insert into PLine values ('PL.002', '-101', '', 'PS.base.ta2'); +insert into PLine values ('PL.003', '-102', '', 'PS.base.ta3'); +insert into PLine values ('PL.004', '-103', '', 'PS.base.ta5'); +insert into PLine values ('PL.005', '-104', '', 'PS.base.ta6'); +insert into PLine values ('PL.006', '-106', '', 'PS.base.tb2'); +insert into PLine values ('PL.007', '-108', '', 'PS.base.tb3'); +insert into PLine values ('PL.008', '-109', '', 'PS.base.tb4'); +insert into PLine values ('PL.009', '-121', '', 'PS.base.tb5'); +insert into PLine values ('PL.010', '-122', '', 'PS.base.tb6'); +insert into PLine values ('PL.015', '-134', '', 'PS.first.ta1'); +insert into PLine values ('PL.016', '-137', '', 'PS.first.ta3'); +insert into PLine values ('PL.017', '-139', '', 'PS.first.ta4'); +insert into PLine values ('PL.018', '-362', '', 'PS.first.tb1'); +insert into PLine values ('PL.019', '-363', '', 'PS.first.tb2'); +insert into PLine values ('PL.020', '-364', '', 'PS.first.tb3'); +insert into PLine values ('PL.021', '-365', '', 'PS.first.tb5'); +insert into PLine values ('PL.022', '-367', '', 'PS.first.tb6'); +insert into PLine values ('PL.028', '-501', 'Fax entrance', 'PS.base.ta2'); +insert into PLine values ('PL.029', '-502', 'Fax first floor', 'PS.first.ta1'); + +-- +-- Buy some phones, plug them into the wall and patch the +-- phone lines to the corresponding patchfield slots. +-- +insert into PHone values ('PH.hc001', 'Hicom standard', 'WS.001.1a'); +update PSlot set slotlink = 'PS.base.ta1' where slotname = 'PS.base.a1'; +insert into PHone values ('PH.hc002', 'Hicom standard', 'WS.002.1a'); +update PSlot set slotlink = 'PS.base.ta5' where slotname = 'PS.base.b1'; +insert into PHone values ('PH.hc003', 'Hicom standard', 'WS.002.2a'); +update PSlot set slotlink = 'PS.base.tb2' where slotname = 'PS.base.b3'; +insert into PHone values ('PH.fax001', 'Canon fax', 'WS.001.2a'); +update PSlot set slotlink = 'PS.base.ta2' where slotname = 'PS.base.a3'; + +-- +-- Install a hub at one of the patchfields, plug a computers +-- ethernet interface into the wall and patch it to the hub. +-- +insert into Hub values ('base.hub1', 'Patchfield PF0_1 hub', 16); +insert into System values ('orion', 'PC'); +insert into IFace values ('IF', 'orion', 'eth0', 'WS.002.1b'); +update PSlot set slotlink = 'HS.base.hub1.1' where slotname = 'PS.base.b2'; + +-- +-- Now we take a look at the patchfield +-- +select * from PField_v1 where pfname = 'PF0_1' order by slotname; +select * from PField_v1 where pfname = 'PF0_2' order by slotname; + +-- +-- Finally we want errors +-- +insert into PField values ('PF1_1', 'should fail due to unique index'); +update PSlot set backlink = 'WS.not.there' where slotname = 'PS.base.a1'; +update PSlot set backlink = 'XX.illegal' where slotname = 'PS.base.a1'; +update PSlot set slotlink = 'PS.not.there' where slotname = 'PS.base.a1'; +update PSlot set slotlink = 'XX.illegal' where slotname = 'PS.base.a1'; +insert into HSlot values ('HS', 'base.hub1', 1, ''); +insert into HSlot values ('HS', 'base.hub1', 20, ''); +delete from HSlot; +insert into IFace values ('IF', 'notthere', 'eth0', ''); +insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', ''); + + +-- +-- The following tests are unrelated to the scenario outlined above; +-- they merely exercise specific parts of PL/pgSQL +-- + +-- +-- Test recursion, per bug report 7-Sep-01 +-- +CREATE FUNCTION recursion_test(int,int) RETURNS text AS ' +DECLARE rslt text; +BEGIN + IF $1 <= 0 THEN + rslt = CAST($2 AS TEXT); + ELSE + rslt = CAST($1 AS TEXT) || '','' || recursion_test($1 - 1, $2); + END IF; + RETURN rslt; +END;' LANGUAGE plpgsql; + +SELECT recursion_test(4,3); + +-- +-- Test the FOUND magic variable +-- +CREATE TABLE found_test_tbl (a int); + +create function test_found() + returns boolean as ' + declare + begin + insert into found_test_tbl values (1); + if FOUND then + insert into found_test_tbl values (2); + end if; + + update found_test_tbl set a = 100 where a = 1; + if FOUND then + insert into found_test_tbl values (3); + end if; + + delete from found_test_tbl where a = 9999; -- matches no rows + if not FOUND then + insert into found_test_tbl values (4); + end if; + + for i in 1 .. 10 loop + -- no need to do anything + end loop; + if FOUND then + insert into found_test_tbl values (5); + end if; + + -- never executes the loop + for i in 2 .. 1 loop + -- no need to do anything + end loop; + if not FOUND then + insert into found_test_tbl values (6); + end if; + return true; + end;' language plpgsql; + +select test_found(); +-- YB note: add ordering. +select * from found_test_tbl order by ((a % 98) + 0.5 * (a / 100)); + +-- +-- Test set-returning functions for PL/pgSQL +-- + +create function test_table_func_rec() returns setof found_test_tbl as ' +DECLARE + rec RECORD; +BEGIN + FOR rec IN select * from found_test_tbl LOOP + RETURN NEXT rec; + END LOOP; + RETURN; +END;' language plpgsql; + +-- YB note: add ordering. +select * from test_table_func_rec() order by ((a % 98) + 0.5 * (a / 100)); + +create function test_table_func_row() returns setof found_test_tbl as ' +DECLARE + row found_test_tbl%ROWTYPE; +BEGIN + FOR row IN select * from found_test_tbl LOOP + RETURN NEXT row; + END LOOP; + RETURN; +END;' language plpgsql; + +-- YB note: add ordering. +select * from test_table_func_row() order by ((a % 98) + 0.5 * (a / 100)); + +create function test_ret_set_scalar(int,int) returns setof int as ' +DECLARE + i int; +BEGIN + FOR i IN $1 .. $2 LOOP + RETURN NEXT i + 1; + END LOOP; + RETURN; +END;' language plpgsql; + +select * from test_ret_set_scalar(1,10); + +create function test_ret_set_rec_dyn(int) returns setof record as ' +DECLARE + retval RECORD; +BEGIN + IF $1 > 10 THEN + SELECT INTO retval 5, 10, 15; + RETURN NEXT retval; + RETURN NEXT retval; + ELSE + SELECT INTO retval 50, 5::numeric, ''xxx''::text; + RETURN NEXT retval; + RETURN NEXT retval; + END IF; + RETURN; +END;' language plpgsql; + +SELECT * FROM test_ret_set_rec_dyn(1500) AS (a int, b int, c int); +SELECT * FROM test_ret_set_rec_dyn(5) AS (a int, b numeric, c text); + +create function test_ret_rec_dyn(int) returns record as ' +DECLARE + retval RECORD; +BEGIN + IF $1 > 10 THEN + SELECT INTO retval 5, 10, 15; + RETURN retval; + ELSE + SELECT INTO retval 50, 5::numeric, ''xxx''::text; + RETURN retval; + END IF; +END;' language plpgsql; + +SELECT * FROM test_ret_rec_dyn(1500) AS (a int, b int, c int); +SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text); + +-- +-- Test some simple polymorphism cases. +-- + +create function f1(x anyelement) returns anyelement as $$ +begin + return x + 1; +end$$ language plpgsql; + +select f1(42) as int, f1(4.5) as num; +select f1(point(3,4)); -- fail for lack of + operator + +drop function f1(x anyelement); + +create function f1(x anyelement) returns anyarray as $$ +begin + return array[x + 1, x + 2]; +end$$ language plpgsql; + +select f1(42) as int, f1(4.5) as num; + +drop function f1(x anyelement); + +create function f1(x anyarray) returns anyelement as $$ +begin + return x[1]; +end$$ language plpgsql; + +select f1(array[2,4]) as int, f1(array[4.5, 7.7]) as num; + +select f1(stavalues1) from pg_statistic; -- fail, can't infer element type + +drop function f1(x anyarray); + +create function f1(x anyarray) returns anyarray as $$ +begin + return x; +end$$ language plpgsql; + +select f1(array[2,4]) as int, f1(array[4.5, 7.7]) as num; + +select f1(stavalues1) from pg_statistic; -- fail, can't infer element type + +drop function f1(x anyarray); + +-- fail, can't infer type: +create function f1(x anyelement) returns anyrange as $$ +begin + return array[x + 1, x + 2]; +end$$ language plpgsql; + +create function f1(x anyrange) returns anyarray as $$ +begin + return array[lower(x), upper(x)]; +end$$ language plpgsql; + +select f1(int4range(42, 49)) as int, f1(float8range(4.5, 7.8)) as num; + +drop function f1(x anyrange); + +create function f1(x anycompatible, y anycompatible) returns anycompatiblearray as $$ +begin + return array[x, y]; +end$$ language plpgsql; + +select f1(2, 4) as int, f1(2, 4.5) as num; + +drop function f1(x anycompatible, y anycompatible); + +create function f1(x anycompatiblerange, y anycompatible, z anycompatible) returns anycompatiblearray as $$ +begin + return array[lower(x), upper(x), y, z]; +end$$ language plpgsql; + +select f1(int4range(42, 49), 11, 2::smallint) as int, f1(float8range(4.5, 7.8), 7.8, 11::real) as num; + +select f1(int4range(42, 49), 11, 4.5) as fail; -- range type doesn't fit + +drop function f1(x anycompatiblerange, y anycompatible, z anycompatible); + +-- fail, can't infer type: +create function f1(x anycompatible) returns anycompatiblerange as $$ +begin + return array[x + 1, x + 2]; +end$$ language plpgsql; + +create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$ +begin + return x; +end$$ language plpgsql; + +select f1(int4range(42, 49), array[11]) as int, f1(float8range(4.5, 7.8), array[7]) as num; + +drop function f1(x anycompatiblerange, y anycompatiblearray); + +create function f1(a anyelement, b anyarray, + c anycompatible, d anycompatible, + OUT x anyarray, OUT y anycompatiblearray) +as $$ +begin + x := a || b; + y := array[c, d]; +end$$ language plpgsql; + +select x, pg_typeof(x), y, pg_typeof(y) + from f1(11, array[1, 2], 42, 34.5); +select x, pg_typeof(x), y, pg_typeof(y) + from f1(11, array[1, 2], point(1,2), point(3,4)); +select x, pg_typeof(x), y, pg_typeof(y) + from f1(11, '{1,2}', point(1,2), '(3,4)'); +select x, pg_typeof(x), y, pg_typeof(y) + from f1(11, array[1, 2.2], 42, 34.5); -- fail + +drop function f1(a anyelement, b anyarray, + c anycompatible, d anycompatible); + +-- +-- Test handling of OUT parameters, including polymorphic cases. +-- Note that RETURN is optional with OUT params; we try both ways. +-- + +-- wrong way to do it: +create function f1(in i int, out j int) returns int as $$ +begin + return i+1; +end$$ language plpgsql; + +create function f1(in i int, out j int) as $$ +begin + j := i+1; + return; +end$$ language plpgsql; + +select f1(42); +select * from f1(42); + +create or replace function f1(inout i int) as $$ +begin + i := i+1; +end$$ language plpgsql; + +select f1(42); +select * from f1(42); + +drop function f1(int); + +create function f1(in i int, out j int) returns setof int as $$ +begin + j := i+1; + return next; + j := i+2; + return next; + return; +end$$ language plpgsql; + +select * from f1(42); + +drop function f1(int); + +create function f1(in i int, out j int, out k text) as $$ +begin + j := i; + j := j+1; + k := 'foo'; +end$$ language plpgsql; + +select f1(42); +select * from f1(42); + +drop function f1(int); + +create function f1(in i int, out j int, out k text) returns setof record as $$ +begin + j := i+1; + k := 'foo'; + return next; + j := j+1; + k := 'foot'; + return next; +end$$ language plpgsql; + +select * from f1(42); + +drop function f1(int); + +create function duplic(in i anyelement, out j anyelement, out k anyarray) as $$ +begin + j := i; + k := array[j,j]; + return; +end$$ language plpgsql; + +select * from duplic(42); +select * from duplic('foo'::text); + +drop function duplic(anyelement); + +create function duplic(in i anycompatiblerange, out j anycompatible, out k anycompatiblearray) as $$ +begin + j := lower(i); + k := array[lower(i),upper(i)]; + return; +end$$ language plpgsql; + +select * from duplic(int4range(42,49)); +select * from duplic(textrange('aaa', 'bbb')); + +drop function duplic(anycompatiblerange); + +-- +-- test PERFORM +-- + +create table perform_test ( + a INT, + b INT +); + +create function perform_simple_func(int) returns boolean as ' +BEGIN + IF $1 < 20 THEN + INSERT INTO perform_test VALUES ($1, $1 + 10); + RETURN TRUE; + ELSE + RETURN FALSE; + END IF; +END;' language plpgsql; + +create function perform_test_func() returns void as ' +BEGIN + IF FOUND then + INSERT INTO perform_test VALUES (100, 100); + END IF; + + PERFORM perform_simple_func(5); + + IF FOUND then + INSERT INTO perform_test VALUES (100, 100); + END IF; + + PERFORM perform_simple_func(50); + + IF FOUND then + INSERT INTO perform_test VALUES (100, 100); + END IF; + + RETURN; +END;' language plpgsql; + +SELECT perform_test_func(); +-- YB note: add ordering. +SELECT * FROM perform_test ORDER BY a; + +drop table perform_test; + +/* + * TODO(jason): the following test section was moved to src/pl/plpgsql/src by + * upstream PG 6726d8d476b424633ebdc7068da3f5a6e6da10af. Since YB does not yet + * support tests there, leave it here for now. + */ +-- +-- Test error trapping +-- + +create function trap_zero_divide(int) returns int as $$ +declare x int; + sx smallint; +begin + begin -- start a subtransaction + raise notice 'should see this'; + x := 100 / $1; + raise notice 'should see this only if % <> 0', $1; + sx := $1; + raise notice 'should see this only if % fits in smallint', $1; + if $1 < 0 then + raise exception '% is less than zero', $1; + end if; + exception + when division_by_zero then + raise notice 'caught division_by_zero'; + x := -1; + when NUMERIC_VALUE_OUT_OF_RANGE then + raise notice 'caught numeric_value_out_of_range'; + x := -2; + end; + return x; +end$$ language plpgsql; + +select trap_zero_divide(50); +select trap_zero_divide(0); +select trap_zero_divide(100000); +select trap_zero_divide(-100); + +create function trap_matching_test(int) returns int as $$ +declare x int; + sx smallint; + y int; +begin + begin -- start a subtransaction + x := 100 / $1; + sx := $1; + select into y unique1 from tenk1 where unique2 = + (select unique2 from tenk1 b where ten = $1); + exception + when data_exception then -- category match + raise notice 'caught data_exception'; + x := -1; + when NUMERIC_VALUE_OUT_OF_RANGE OR CARDINALITY_VIOLATION then + raise notice 'caught numeric_value_out_of_range or cardinality_violation'; + x := -2; + end; + return x; +end$$ language plpgsql; + +select trap_matching_test(50); +select trap_matching_test(0); +select trap_matching_test(100000); +select trap_matching_test(1); + +create temp table foo (f1 int); + +create function subxact_rollback_semantics() returns int as $$ +declare x int; +begin + x := 1; + insert into foo values(x); + begin + x := x + 1; + insert into foo values(x); + raise exception 'inner'; + exception + when others then + x := x * 10; + end; + insert into foo values(x); + return x; +end$$ language plpgsql; + +-- TODO:(dmitry) Uncomment when issue #1541 will be resolved +-- select subxact_rollback_semantics(); +select * from foo order by f1; +drop table foo; + +create function trap_timeout() returns void as $$ +begin + declare x int; + begin + -- we assume this will take longer than 2 seconds: + select count(*) into x from tenk1 a, tenk1 b, tenk1 c; + exception + when others then + raise notice 'caught others?'; + when query_canceled then + raise notice 'nyeah nyeah, can''t stop me'; + end; + -- Abort transaction to abandon the statement_timeout setting. Otherwise, + -- the next top-level statement would be vulnerable to the timeout. + raise exception 'end of function'; +end$$ language plpgsql; + +begin; +set statement_timeout to 2000; +select trap_timeout(); +rollback; + +-- Test for pass-by-ref values being stored in proper context +create function test_variable_storage() returns text as $$ +declare x text; +begin + x := '1234'; + begin + x := x || '5678'; + -- force error inside subtransaction SPI context + perform trap_zero_divide(-100); + exception + when others then + x := x || '9012'; + end; + return x; +end$$ language plpgsql; + +select test_variable_storage(); + +-- +-- test foreign key error trapping +-- + +create temp table master(f1 int primary key); + +create temp table slave(f1 int references master deferrable); + +insert into master values(1); +insert into slave values(1); +insert into slave values(2); -- fails + +create function trap_foreign_key(int) returns int as $$ +begin + begin -- start a subtransaction + insert into slave values($1); + exception + when foreign_key_violation then + raise notice 'caught foreign_key_violation'; + return 0; + end; + return 1; +end$$ language plpgsql; + +create function trap_foreign_key_2() returns int as $$ +begin + begin -- start a subtransaction + set constraints all immediate; + exception + when foreign_key_violation then + raise notice 'caught foreign_key_violation'; + return 0; + end; + return 1; +end$$ language plpgsql; + +select trap_foreign_key(1); +select trap_foreign_key(2); -- detects FK violation + +begin; + set constraints all deferred; + select trap_foreign_key(2); -- should not detect FK violation + savepoint x; + set constraints all immediate; -- fails + rollback to x; + select trap_foreign_key_2(); -- detects FK violation +commit; -- still fails + +drop function trap_foreign_key(int); +drop function trap_foreign_key_2(); + +-- +-- Test proper snapshot handling in simple expressions +-- + +create temp table users(login text, id serial); + +create function sp_id_user(a_login text) returns int as $$ +declare x int; +begin + select into x id from users where login = a_login; + if found then return x; end if; + return 0; +end$$ language plpgsql stable; + +insert into users values('user1'); + +select sp_id_user('user1'); +select sp_id_user('userx'); + +create function sp_add_user(a_login text) returns int as $$ +declare my_id_user int; +begin + my_id_user = sp_id_user( a_login ); + IF my_id_user > 0 THEN + RETURN -1; -- error code for existing user + END IF; + INSERT INTO users ( login ) VALUES ( a_login ); + my_id_user = sp_id_user( a_login ); + IF my_id_user = 0 THEN + RETURN -2; -- error code for insertion failure + END IF; + RETURN my_id_user; +end$$ language plpgsql; + +select sp_add_user('user1'); +select sp_add_user('user2'); +select sp_add_user('user2'); +select sp_add_user('user3'); +select sp_add_user('user3'); + +drop function sp_add_user(text); +drop function sp_id_user(text); + +-- +-- tests for refcursors +-- +create table rc_test (a int, b int); +copy rc_test from stdin; +5 10 +50 100 +500 1000 +\. + +create function return_unnamed_refcursor() returns refcursor as $$ +declare + rc refcursor; +begin + open rc for select a from rc_test; + return rc; +end +$$ language plpgsql; + +create function use_refcursor(rc refcursor) returns int as $$ +declare + rc refcursor; + x record; +begin + rc := return_unnamed_refcursor(); + fetch next from rc into x; + return x.a; +end +$$ language plpgsql; + +select use_refcursor(return_unnamed_refcursor()); + +create function return_refcursor(rc refcursor) returns refcursor as $$ +begin + open rc for select a from rc_test; + return rc; +end +$$ language plpgsql; + +create function refcursor_test1(refcursor) returns refcursor as $$ +begin + perform return_refcursor($1); + return $1; +end +$$ language plpgsql; + +begin; + +select refcursor_test1('test1'); +fetch next in test1; + +select refcursor_test1('test2'); +fetch all from test2; + +commit; + +-- should fail +fetch next from test1; + +create function refcursor_test2(int, int) returns boolean as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; + nonsense record; +begin + open c1($1, $2); + fetch c1 into nonsense; + close c1; + if found then + return true; + else + return false; + end if; +end +$$ language plpgsql; + +select refcursor_test2(20000, 20000) as "Should be false", + refcursor_test2(20, 20) as "Should be true"; + +-- should fail +create function constant_refcursor() returns refcursor as $$ +declare + rc constant refcursor; +begin + open rc for select a from rc_test; + return rc; +end +$$ language plpgsql; + +select constant_refcursor(); + +-- but it's okay like this +create or replace function constant_refcursor() returns refcursor as $$ +declare + rc constant refcursor := 'my_cursor_name'; +begin + open rc for select a from rc_test; + return rc; +end +$$ language plpgsql; + +select constant_refcursor(); + +-- +-- tests for cursors with named parameter arguments +-- +create function namedparmcursor_test1(int, int) returns boolean as $$ +declare + c1 cursor (param1 int, param12 int) for select * from rc_test where a > param1 and b > param12; + nonsense record; +begin + open c1(param12 := $2, param1 := $1); + fetch c1 into nonsense; + close c1; + if found then + return true; + else + return false; + end if; +end +$$ language plpgsql; + +select namedparmcursor_test1(20000, 20000) as "Should be false", + namedparmcursor_test1(20, 20) as "Should be true"; + +-- mixing named and positional argument notations +create function namedparmcursor_test2(int, int) returns boolean as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; + nonsense record; +begin + open c1(param1 := $1, $2); + fetch c1 into nonsense; + close c1; + if found then + return true; + else + return false; + end if; +end +$$ language plpgsql; +select namedparmcursor_test2(20, 20); + +-- mixing named and positional: param2 is given twice, once in named notation +-- and second time in positional notation. Should throw an error at parse time +create function namedparmcursor_test3() returns void as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; +begin + open c1(param2 := 20, 21); +end +$$ language plpgsql; + +-- mixing named and positional: same as previous test, but param1 is duplicated +create function namedparmcursor_test4() returns void as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; +begin + open c1(20, param1 := 21); +end +$$ language plpgsql; + +-- duplicate named parameter, should throw an error at parse time +create function namedparmcursor_test5() returns void as $$ +declare + c1 cursor (p1 int, p2 int) for + select * from tenk1 where thousand = p1 and tenthous = p2; +begin + open c1 (p2 := 77, p2 := 42); +end +$$ language plpgsql; + +-- not enough parameters, should throw an error at parse time +create function namedparmcursor_test6() returns void as $$ +declare + c1 cursor (p1 int, p2 int) for + select * from tenk1 where thousand = p1 and tenthous = p2; +begin + open c1 (p2 := 77); +end +$$ language plpgsql; + +-- division by zero runtime error, the context given in the error message +-- should be sensible +create function namedparmcursor_test7() returns void as $$ +declare + c1 cursor (p1 int, p2 int) for + select * from tenk1 where thousand = p1 and tenthous = p2; +begin + open c1 (p2 := 77, p1 := 42/0); +end $$ language plpgsql; +select namedparmcursor_test7(); + +-- check that line comments work correctly within the argument list (there +-- is some special handling of this case in the code: the newline after the +-- comment must be preserved when the argument-evaluating query is +-- constructed, otherwise the comment effectively comments out the next +-- argument, too) +create function namedparmcursor_test8() returns int4 as $$ +declare + c1 cursor (p1 int, p2 int) for + select count(*) from tenk1 where thousand = p1 and tenthous = p2; + n int4; +begin + open c1 (77 -- test + , 42); + fetch c1 into n; + return n; +end $$ language plpgsql; +select namedparmcursor_test8(); + +-- cursor parameter name can match plpgsql variable or unreserved keyword +create function namedparmcursor_test9(p1 int) returns int4 as $$ +declare + c1 cursor (p1 int, p2 int, debug int) for + select count(*) from tenk1 where thousand = p1 and tenthous = p2 + and four = debug; + p2 int4 := 1006; + n int4; +begin + open c1 (p1 := p1, p2 := p2, debug := 2); + fetch c1 into n; + return n; +end $$ language plpgsql; +select namedparmcursor_test9(6); + +-- +-- tests for "raise" processing +-- +create function raise_test1(int) returns int as $$ +begin + raise notice 'This message has too many parameters!', $1; + return $1; +end; +$$ language plpgsql; + +create function raise_test2(int) returns int as $$ +begin + raise notice 'This message has too few parameters: %, %, %', $1, $1; + return $1; +end; +$$ language plpgsql; + +create function raise_test3(int) returns int as $$ +begin + raise notice 'This message has no parameters (despite having %% signs in it)!'; + return $1; +end; +$$ language plpgsql; + +select raise_test3(1); + +-- Test re-RAISE inside a nested exception block. This case is allowed +-- by Oracle's PL/SQL but was handled differently by PG before 9.1. + +CREATE FUNCTION reraise_test() RETURNS void AS $$ +BEGIN + BEGIN + RAISE syntax_error; + EXCEPTION + WHEN syntax_error THEN + BEGIN + raise notice 'exception % thrown in inner block, reraising', sqlerrm; + RAISE; + EXCEPTION + WHEN OTHERS THEN + raise notice 'RIGHT - exception % caught in inner block', sqlerrm; + END; + END; +EXCEPTION + WHEN OTHERS THEN + raise notice 'WRONG - exception % caught in outer block', sqlerrm; +END; +$$ LANGUAGE plpgsql; + +SELECT reraise_test(); + +-- +-- reject function definitions that contain malformed SQL queries at +-- compile-time, where possible +-- +create function bad_sql1() returns int as $$ +declare a int; +begin + a := 5; + Johnny Yuma; + a := 10; + return a; +end$$ language plpgsql; + +create function bad_sql2() returns int as $$ +declare r record; +begin + for r in select I fought the law, the law won LOOP + raise notice 'in loop'; + end loop; + return 5; +end;$$ language plpgsql; + +-- a RETURN expression is mandatory, except for void-returning +-- functions, where it is not allowed +create function missing_return_expr() returns int as $$ +begin + return ; +end;$$ language plpgsql; + +create function void_return_expr() returns void as $$ +begin + return 5; +end;$$ language plpgsql; + +-- VOID functions are allowed to omit RETURN +create function void_return_expr() returns void as $$ +begin + perform 2+2; +end;$$ language plpgsql; + +select void_return_expr(); + +-- but ordinary functions are not +create function missing_return_expr() returns int as $$ +begin + perform 2+2; +end;$$ language plpgsql; + +select missing_return_expr(); + +drop function void_return_expr(); +drop function missing_return_expr(); + +-- +-- EXECUTE ... INTO test +-- + +create table eifoo (i integer, y integer); +create type eitype as (i integer, y integer); + +create or replace function execute_into_test(varchar) returns record as $$ +declare + _r record; + _rt eifoo%rowtype; + _v eitype; + i int; + j int; + k int; +begin + execute 'insert into '||$1||' values(10,15)'; + execute 'select (row).* from (select row(10,1)::eifoo) s' into _r; + raise notice '% %', _r.i, _r.y; + execute 'select * from '||$1||' limit 1' into _rt; + raise notice '% %', _rt.i, _rt.y; + execute 'select *, 20 from '||$1||' limit 1' into i, j, k; + raise notice '% % %', i, j, k; + execute 'select 1,2' into _v; + return _v; +end; $$ language plpgsql; + +select execute_into_test('eifoo'); + +drop table eifoo cascade; +drop type eitype cascade; + +-- +-- SQLSTATE and SQLERRM test +-- + +create function excpt_test1() returns void as $$ +begin + raise notice '% %', sqlstate, sqlerrm; +end; $$ language plpgsql; +-- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION +-- blocks +select excpt_test1(); + +create function excpt_test2() returns void as $$ +begin + begin + begin + raise notice '% %', sqlstate, sqlerrm; + end; + end; +end; $$ language plpgsql; +-- should fail +select excpt_test2(); + +create function excpt_test3() returns void as $$ +begin + begin + raise exception 'user exception'; + exception when others then + raise notice 'caught exception % %', sqlstate, sqlerrm; + begin + raise notice '% %', sqlstate, sqlerrm; + perform 10/0; + exception + when substring_error then + -- this exception handler shouldn't be invoked + raise notice 'unexpected exception: % %', sqlstate, sqlerrm; + when division_by_zero then + raise notice 'caught exception % %', sqlstate, sqlerrm; + end; + raise notice '% %', sqlstate, sqlerrm; + end; +end; $$ language plpgsql; +select excpt_test3(); + +create function excpt_test4() returns text as $$ +begin + begin perform 1/0; + exception when others then return sqlerrm; end; +end; $$ language plpgsql; +select excpt_test4(); + +drop function excpt_test1(); +drop function excpt_test2(); +drop function excpt_test3(); +drop function excpt_test4(); + +-- parameters of raise stmt can be expressions +create function raise_exprs() returns void as $$ +declare + a integer[] = '{10,20,30}'; + c varchar = 'xyz'; + i integer; +begin + i := 2; + raise notice '%; %; %; %; %; %', a, a[i], c, (select c || 'abc'), row(10,'aaa',NULL,30), NULL; +end;$$ language plpgsql; + +select raise_exprs(); +drop function raise_exprs(); + +-- regression test: verify that multiple uses of same plpgsql datum within +-- a SQL command all get mapped to the same $n parameter. The return value +-- of the SELECT is not important, we only care that it doesn't fail with +-- a complaint about an ungrouped column reference. +create function multi_datum_use(p1 int) returns bool as $$ +declare + x int; + y int; +begin + select into x,y unique1/p1, unique1/$1 from tenk1 group by unique1/p1; + return x = y; +end$$ language plpgsql; + +select multi_datum_use(42); + +-- +-- Test STRICT limiter in both planned and EXECUTE invocations. +-- Note that a data-modifying query is quasi strict (disallow multi rows) +-- by default in the planned case, but not in EXECUTE. +-- + +create temp table foo (f1 int, f2 int); + +insert into foo values (1,2), (3,4); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should work + insert into foo values(5,6) returning * into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should fail due to implicit strict + insert into foo values(7,8),(9,10) returning * into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should work + execute 'insert into foo values(5,6) returning *' into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- this should work since EXECUTE isn't as picky + execute 'insert into foo values(7,8),(9,10) returning *' into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +-- YB note: add ordering. +select * from foo order by f1; + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should work + select * from foo where f1 = 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should fail, no rows + select * from foo where f1 = 0 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should fail, too many rows + select * from foo where f1 > 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should work + execute 'select * from foo where f1 = 3' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should fail, no rows + execute 'select * from foo where f1 = 0' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- should fail, too many rows + execute 'select * from foo where f1 > 3' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +drop function stricttest(); + +-- test printing parameters after failure due to STRICT + +set plpgsql.print_strict_params to true; + +create or replace function stricttest() returns void as $$ +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- no rows + select * from foo where f1 = p1 and f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare +x record; +p1 int := 2; +p3 text := $a$'Valame Dios!' dijo Sancho; 'no le dije yo a vuestra merced que mirase bien lo que hacia?'$a$; +begin + -- no rows + select * from foo where f1 = p1 and f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- too many rows + select * from foo where f1 > p1 or f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- too many rows, no params + select * from foo where f1 > 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- no rows + execute 'select * from foo where f1 = $1 or f1::text = $2' using 0, 'foo' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- too many rows + execute 'select * from foo where f1 > $1' using 1 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +declare x record; +begin + -- too many rows, no parameters + execute 'select * from foo where f1 > 3' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +create or replace function stricttest() returns void as $$ +-- override the global +#print_strict_params off +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- too many rows + select * from foo where f1 > p1 or f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +reset plpgsql.print_strict_params; + +create or replace function stricttest() returns void as $$ +-- override the global +#print_strict_params on +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- too many rows + select * from foo where f1 > p1 or f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; + +select stricttest(); + +-- test warnings and errors +set plpgsql.extra_warnings to 'all'; +set plpgsql.extra_warnings to 'none'; +set plpgsql.extra_errors to 'all'; +set plpgsql.extra_errors to 'none'; + +-- test warnings when shadowing a variable + +set plpgsql.extra_warnings to 'shadowed_variables'; + +-- simple shadowing of input and output parameters +create or replace function shadowtest(in1 int) + returns table (out1 int) as $$ +declare +in1 int; +out1 int; +begin +end +$$ language plpgsql; +select shadowtest(1); + +set plpgsql.extra_warnings to 'shadowed_variables'; +select shadowtest(1); +create or replace function shadowtest(in1 int) + returns table (out1 int) as $$ +declare +in1 int; +out1 int; +begin +end +$$ language plpgsql; +select shadowtest(1); +drop function shadowtest(int); + +-- shadowing in a second DECLARE block +create or replace function shadowtest() + returns void as $$ +declare +f1 int; +begin + declare + f1 int; + begin + end; +end$$ language plpgsql; +drop function shadowtest(); + +-- several levels of shadowing +create or replace function shadowtest(in1 int) + returns void as $$ +declare +in1 int; +begin + declare + in1 int; + begin + end; +end$$ language plpgsql; +drop function shadowtest(int); + +-- shadowing in cursor definitions +create or replace function shadowtest() + returns void as $$ +declare +f1 int; +c1 cursor (f1 int) for select 1; +begin +end$$ language plpgsql; +drop function shadowtest(); + +-- test errors when shadowing a variable + +set plpgsql.extra_errors to 'shadowed_variables'; + +create or replace function shadowtest(f1 int) + returns boolean as $$ +declare f1 int; begin return 1; end $$ language plpgsql; + +select shadowtest(1); + +reset plpgsql.extra_errors; +reset plpgsql.extra_warnings; + +create or replace function shadowtest(f1 int) + returns boolean as $$ +declare f1 int; begin return 1; end $$ language plpgsql; + +select shadowtest(1); + +-- runtime extra checks +set plpgsql.extra_warnings to 'too_many_rows'; + +do $$ +declare x int; +begin + select v from generate_series(1,2) g(v) into x; +end; +$$; + +set plpgsql.extra_errors to 'too_many_rows'; + +do $$ +declare x int; +begin + select v from generate_series(1,2) g(v) into x; +end; +$$; + +reset plpgsql.extra_errors; +reset plpgsql.extra_warnings; + +set plpgsql.extra_warnings to 'strict_multi_assignment'; + +do $$ +declare + x int; + y int; +begin + select 1 into x, y; + select 1,2 into x, y; + select 1,2,3 into x, y; +end +$$; + +set plpgsql.extra_errors to 'strict_multi_assignment'; + +do $$ +declare + x int; + y int; +begin + select 1 into x, y; + select 1,2 into x, y; + select 1,2,3 into x, y; +end +$$; + +create table test_01(a int, b int, c int); + +alter table test_01 drop column a; + +-- the check is active only when source table is not empty +insert into test_01 values(10,20); + +do $$ +declare + x int; + y int; +begin + select * from test_01 into x, y; -- should be ok + raise notice 'ok'; + select * from test_01 into x; -- should to fail +end; +$$; + +do $$ +declare + t test_01; +begin + select 1, 2 into t; -- should be ok + raise notice 'ok'; + select 1, 2, 3 into t; -- should fail; +end; +$$; + +do $$ +declare + t test_01; +begin + select 1 into t; -- should fail; +end; +$$; + +drop table test_01; + +reset plpgsql.extra_errors; +reset plpgsql.extra_warnings; + +-- test scrollable cursor support + +create function sc_test() returns setof integer as $$ +declare + c scroll cursor for select f1 from int4_tbl; + x integer; +begin + open c; + fetch last from c into x; + while found loop + return next x; + fetch prior from c into x; + end loop; + close c; +end; +$$ language plpgsql; + +-- YB note: add ordering. +select * from sc_test() ORDER BY (sign(sc_test) - abs(sc_test)); + +create or replace function sc_test() returns setof integer as $$ +declare + c no scroll cursor for select f1 from int4_tbl; + x integer; +begin + open c; + fetch last from c into x; + while found loop + return next x; + fetch prior from c into x; + end loop; + close c; +end; +$$ language plpgsql; + +select * from sc_test(); -- fails because of NO SCROLL specification + +create or replace function sc_test() returns setof integer as $$ +declare + c refcursor; + x integer; +begin + open c scroll for select f1 from int4_tbl; + fetch last from c into x; + while found loop + return next x; + fetch prior from c into x; + end loop; + close c; +end; +$$ language plpgsql; + +-- YB note: add ordering. +select * from sc_test() ORDER BY (sign(sc_test) - abs(sc_test)); + +create or replace function sc_test() returns setof integer as $$ +declare + c refcursor; + x integer; +begin + -- YB note: add ordering. + open c scroll for execute 'select f1 from int4_tbl ORDER BY (sign(f1) - abs(f1)) DESC'; + fetch last from c into x; + while found loop + return next x; + fetch relative -2 from c into x; + end loop; + close c; +end; +$$ language plpgsql; + +select * from sc_test(); + +create or replace function sc_test() returns setof integer as $$ +declare + c refcursor; + x integer; +begin + open c scroll for execute 'select f1 from int4_tbl'; + fetch last from c into x; + while found loop + return next x; + move backward 2 from c; + fetch relative -1 from c into x; + end loop; + close c; +end; +$$ language plpgsql; + +drop function sc_test(); + +-- test qualified variable names + +create function pl_qual_names (param1 int) returns void as $$ +<> +declare + param1 int := 1; +begin + <> + declare + param1 int := 2; + begin + raise notice 'param1 = %', param1; + raise notice 'pl_qual_names.param1 = %', pl_qual_names.param1; + raise notice 'outerblock.param1 = %', outerblock.param1; + raise notice 'innerblock.param1 = %', innerblock.param1; + end; +end; +$$ language plpgsql; + +select pl_qual_names(42); + +drop function pl_qual_names(int); + +-- tests for RETURN QUERY +create function ret_query1(out int, out int) returns setof record as $$ +begin + $1 := -1; + $2 := -2; + return next; + return query select x + 1, x * 10 from generate_series(0, 10) s (x); + return next; +end; +$$ language plpgsql; + +select * from ret_query1(); + +create type record_type as (x text, y int, z boolean); + +create or replace function ret_query2(lim int) returns setof record_type as $$ +begin + return query select md5(s.x::text), s.x, s.x > 0 + from generate_series(-8, lim) s (x) where s.x % 2 = 0; +end; +$$ language plpgsql; + +select * from ret_query2(8); + +-- test EXECUTE USING +create function exc_using(int, text) returns int as $$ +declare i int; +begin + for i in execute 'select * from generate_series(1,$1)' using $1+1 loop + raise notice '%', i; + end loop; + execute 'select $2 + $2*3 + length($1)' into i using $2,$1; + return i; +end +$$ language plpgsql; + +select exc_using(5, 'foobar'); + +drop function exc_using(int, text); + +create or replace function exc_using(int) returns void as $$ +declare + c refcursor; + i int; +begin + open c for execute 'select * from generate_series(1,$1)' using $1+1; + loop + fetch c into i; + exit when not found; + raise notice '%', i; + end loop; + close c; + return; +end; +$$ language plpgsql; + +select exc_using(5); + +drop function exc_using(int); + +-- test FOR-over-cursor + +create or replace function forc01() returns void as $$ +declare + c cursor(r1 integer, r2 integer) + for select * from generate_series(r1,r2) i; + c2 cursor + for select * from generate_series(41,43) i; +begin + for r in c(5,7) loop + raise notice '% from %', r.i, c; + end loop; + -- again, to test if cursor was closed properly + for r in c(9,10) loop + raise notice '% from %', r.i, c; + end loop; + -- and test a parameterless cursor + for r in c2 loop + raise notice '% from %', r.i, c2; + end loop; + -- and try it with a hand-assigned name + raise notice 'after loop, c2 = %', c2; + c2 := 'special_name'; + for r in c2 loop + raise notice '% from %', r.i, c2; + end loop; + raise notice 'after loop, c2 = %', c2; + -- and try it with a generated name + -- (which we can't show in the output because it's variable) + c2 := null; + for r in c2 loop + raise notice '%', r.i; + end loop; + raise notice 'after loop, c2 = %', c2; + return; +end; +$$ language plpgsql; + +select forc01(); + +-- try updating the cursor's current row + +create temp table forc_test as + select n as i, n as j from generate_series(1,10) n; + +create or replace function forc01() returns void as $$ +declare + c cursor for select * from forc_test; +begin + for r in c loop + raise notice '%, %', r.i, r.j; + update forc_test set i = i * 100, j = r.j * 2 where current of c; + end loop; +end; +$$ language plpgsql; + +drop function forc01(); + +-- fail because cursor has no query bound to it + +create or replace function forc_bad() returns void as $$ +declare + c refcursor; +begin + for r in c loop + raise notice '%', r.i; + end loop; +end; +$$ language plpgsql; + +-- test RETURN QUERY EXECUTE + +create or replace function return_dquery() +returns setof int as $$ +begin + return query execute 'select * from (values(10),(20)) f'; + return query execute 'select * from (values($1),($2)) f' using 40,50; +end; +$$ language plpgsql; + +select * from return_dquery(); + +drop function return_dquery(); + +-- test RETURN QUERY with dropped columns + +create table tabwithcols(a int, b int, c int, d int); +insert into tabwithcols values(10,20,30,40),(50,60,70,80); + +create or replace function returnqueryf() +returns setof tabwithcols as $$ +begin + -- YB note: add ordering. + return query select * from tabwithcols order by a; + return query execute 'select * from tabwithcols order by a'; +end; +$$ language plpgsql; + +select * from returnqueryf(); + +alter table tabwithcols drop column b; + +select * from returnqueryf(); + +alter table tabwithcols drop column d; + +select * from returnqueryf(); + +alter table tabwithcols add column d int; + +select * from returnqueryf(); + +drop function returnqueryf(); +drop table tabwithcols; + +-- +-- Tests for composite-type results +-- + +create type compostype as (x int, y varchar); + +-- test: use of variable of composite type in return statement +create or replace function compos() returns compostype as $$ +declare + v compostype; +begin + v := (1, 'hello'); + return v; +end; +$$ language plpgsql; + +select compos(); + +-- test: use of variable of record type in return statement +create or replace function compos() returns compostype as $$ +declare + v record; +begin + v := (1, 'hello'::varchar); + return v; +end; +$$ language plpgsql; + +select compos(); + +-- test: use of row expr in return statement +create or replace function compos() returns compostype as $$ +begin + return (1, 'hello'::varchar); +end; +$$ language plpgsql; + +select compos(); + +-- this does not work currently (no implicit casting) +create or replace function compos() returns compostype as $$ +begin + return (1, 'hello'); +end; +$$ language plpgsql; + +select compos(); + +-- ... but this does +create or replace function compos() returns compostype as $$ +begin + return (1, 'hello')::compostype; +end; +$$ language plpgsql; + +select compos(); + +drop function compos(); + +-- test: return a row expr as record. +create or replace function composrec() returns record as $$ +declare + v record; +begin + v := (1, 'hello'); + return v; +end; +$$ language plpgsql; + +select composrec(); + +-- test: return row expr in return statement. +create or replace function composrec() returns record as $$ +begin + return (1, 'hello'); +end; +$$ language plpgsql; + +select composrec(); + +drop function composrec(); + +-- test: row expr in RETURN NEXT statement. +create or replace function compos() returns setof compostype as $$ +begin + for i in 1..3 + loop + return next (1, 'hello'::varchar); + end loop; + return next null::compostype; + return next (2, 'goodbye')::compostype; +end; +$$ language plpgsql; + +select * from compos(); + +drop function compos(); + +-- test: use invalid expr in return statement. +create or replace function compos() returns compostype as $$ +begin + return 1 + 1; +end; +$$ language plpgsql; + +select compos(); + +-- RETURN variable is a different code path ... +create or replace function compos() returns compostype as $$ +declare x int := 42; +begin + return x; +end; +$$ language plpgsql; + +select * from compos(); + +drop function compos(); + +-- test: invalid use of composite variable in scalar-returning function +create or replace function compos() returns int as $$ +declare + v compostype; +begin + v := (1, 'hello'); + return v; +end; +$$ language plpgsql; + +select compos(); + +-- test: invalid use of composite expression in scalar-returning function +create or replace function compos() returns int as $$ +begin + return (1, 'hello')::compostype; +end; +$$ language plpgsql; + +select compos(); + +drop function compos(); +drop type compostype; + +-- +-- Tests for 8.4's new RAISE features +-- + +create or replace function raise_test() returns void as $$ +begin + raise notice '% % %', 1, 2, 3 + using errcode = '55001', detail = 'some detail info', hint = 'some hint'; + raise '% % %', 1, 2, 3 + using errcode = 'division_by_zero', detail = 'some detail info'; +end; +$$ language plpgsql; + +select raise_test(); + +-- Since we can't actually see the thrown SQLSTATE in default psql output, +-- test it like this; this also tests re-RAISE + +create or replace function raise_test() returns void as $$ +begin + raise 'check me' + using errcode = 'division_by_zero', detail = 'some detail info'; + exception + when others then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; + +select raise_test(); + +create or replace function raise_test() returns void as $$ +begin + raise 'check me' + using errcode = '1234F', detail = 'some detail info'; + exception + when others then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; + +select raise_test(); + +-- SQLSTATE specification in WHEN +create or replace function raise_test() returns void as $$ +begin + raise 'check me' + using errcode = '1234F', detail = 'some detail info'; + exception + when sqlstate '1234F' then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; + +select raise_test(); + +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero using detail = 'some detail info'; + exception + when others then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; + +select raise_test(); + +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero; +end; +$$ language plpgsql; + +select raise_test(); + +create or replace function raise_test() returns void as $$ +begin + raise sqlstate '1234F'; +end; +$$ language plpgsql; + +select raise_test(); + +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero using message = 'custom' || ' message'; +end; +$$ language plpgsql; + +select raise_test(); + +create or replace function raise_test() returns void as $$ +begin + raise using message = 'custom' || ' message', errcode = '22012'; +end; +$$ language plpgsql; + +select raise_test(); + +-- conflict on message +create or replace function raise_test() returns void as $$ +begin + raise notice 'some message' using message = 'custom' || ' message', errcode = '22012'; +end; +$$ language plpgsql; + +select raise_test(); + +-- conflict on errcode +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero using message = 'custom' || ' message', errcode = '22012'; +end; +$$ language plpgsql; + +select raise_test(); + +-- nothing to re-RAISE +create or replace function raise_test() returns void as $$ +begin + raise; +end; +$$ language plpgsql; + +select raise_test(); + +-- test access to exception data +create function zero_divide() returns int as $$ +declare v int := 0; +begin + return 10 / v; +end; +$$ language plpgsql; + +create or replace function raise_test() returns void as $$ +begin + raise exception 'custom exception' + using detail = 'some detail of custom exception', + hint = 'some hint related to custom exception'; +end; +$$ language plpgsql; + +create function stacked_diagnostics_test() returns void as $$ +declare _sqlstate text; + _message text; + _context text; +begin + perform zero_divide(); +exception when others then + get stacked diagnostics + _sqlstate = returned_sqlstate, + _message = message_text, + _context = pg_exception_context; + raise notice 'sqlstate: %, message: %, context: [%]', + _sqlstate, _message, replace(_context, E'\n', ' <- '); +end; +$$ language plpgsql; + +select stacked_diagnostics_test(); + +create or replace function stacked_diagnostics_test() returns void as $$ +declare _detail text; + _hint text; + _message text; +begin + perform raise_test(); +exception when others then + get stacked diagnostics + _message = message_text, + _detail = pg_exception_detail, + _hint = pg_exception_hint; + raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; +end; +$$ language plpgsql; + +select stacked_diagnostics_test(); + +-- fail, cannot use stacked diagnostics statement outside handler +create or replace function stacked_diagnostics_test() returns void as $$ +declare _detail text; + _hint text; + _message text; +begin + get stacked diagnostics + _message = message_text, + _detail = pg_exception_detail, + _hint = pg_exception_hint; + raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; +end; +$$ language plpgsql; + +select stacked_diagnostics_test(); + +drop function zero_divide(); +drop function stacked_diagnostics_test(); + +-- check cases where implicit SQLSTATE variable could be confused with +-- SQLSTATE as a keyword, cf bug #5524 +create or replace function raise_test() returns void as $$ +begin + perform 1/0; +exception + when sqlstate '22012' then + raise notice using message = sqlstate; + raise sqlstate '22012' using message = 'substitute message'; +end; +$$ language plpgsql; + +select raise_test(); + +drop function raise_test(); + +-- test passing column_name, constraint_name, datatype_name, table_name +-- and schema_name error fields + +create or replace function stacked_diagnostics_test() returns void as $$ +declare _column_name text; + _constraint_name text; + _datatype_name text; + _table_name text; + _schema_name text; +begin + raise exception using + column = '>>some column name<<', + constraint = '>>some constraint name<<', + datatype = '>>some datatype name<<', + table = '>>some table name<<', + schema = '>>some schema name<<'; +exception when others then + get stacked diagnostics + _column_name = column_name, + _constraint_name = constraint_name, + _datatype_name = pg_datatype_name, + _table_name = table_name, + _schema_name = schema_name; + raise notice 'column %, constraint %, type %, table %, schema %', + _column_name, _constraint_name, _datatype_name, _table_name, _schema_name; +end; +$$ language plpgsql; + +select stacked_diagnostics_test(); + +drop function stacked_diagnostics_test(); + +-- test variadic functions + +create or replace function vari(variadic int[]) +returns void as $$ +begin + for i in array_lower($1,1)..array_upper($1,1) loop + raise notice '%', $1[i]; + end loop; end; +$$ language plpgsql; + +select vari(1,2,3,4,5); +select vari(3,4,5); +select vari(variadic array[5,6,7]); + +drop function vari(int[]); + +-- coercion test +create or replace function pleast(variadic numeric[]) +returns numeric as $$ +declare aux numeric = $1[array_lower($1,1)]; +begin + for i in array_lower($1,1)+1..array_upper($1,1) loop + if $1[i] < aux then aux := $1[i]; end if; + end loop; + return aux; +end; +$$ language plpgsql immutable strict; + +select pleast(10,1,2,3,-16); +select pleast(10.2,2.2,-1.1); +select pleast(10.2,10, -20); +select pleast(10,20, -1.0); + +-- in case of conflict, non-variadic version is preferred +create or replace function pleast(numeric) +returns numeric as $$ +begin + raise notice 'non-variadic function called'; + return $1; +end; +$$ language plpgsql immutable strict; + +select pleast(10); + +drop function pleast(numeric[]); +drop function pleast(numeric); + +-- test table functions + +create function tftest(int) returns table(a int, b int) as $$ +begin + return query select $1, $1+i from generate_series(1,5) g(i); +end; +$$ language plpgsql immutable strict; + +select * from tftest(10); + +create or replace function tftest(a1 int) returns table(a int, b int) as $$ +begin + a := a1; b := a1 + 1; + return next; + a := a1 * 10; b := a1 * 10 + 1; + return next; +end; +$$ language plpgsql immutable strict; + +select * from tftest(10); + +drop function tftest(int); + +create function rttest() +returns setof int as $$ +declare rc int; +begin + return query values(10),(20); + get diagnostics rc = row_count; + raise notice '% %', found, rc; + return query select * from (values(10),(20)) f(a) where false; + get diagnostics rc = row_count; + raise notice '% %', found, rc; + return query execute 'values(10),(20)'; + get diagnostics rc = row_count; + raise notice '% %', found, rc; + return query execute 'select * from (values(10),(20)) f(a) where false'; + get diagnostics rc = row_count; + raise notice '% %', found, rc; +end; +$$ language plpgsql; + +select * from rttest(); + +-- check some error cases, too + +create or replace function rttest() +returns setof int as $$ +begin + return query select 10 into no_such_table; +end; +$$ language plpgsql; + +select * from rttest(); + +create or replace function rttest() +returns setof int as $$ +begin + return query execute 'select 10 into no_such_table'; +end; +$$ language plpgsql; + +select * from rttest(); + +select * from no_such_table; + +drop function rttest(); + +-- Test for proper cleanup at subtransaction exit. This example +-- exposed a bug in PG 8.2. + +CREATE FUNCTION leaker_1(fail BOOL) RETURNS INTEGER AS $$ +DECLARE + v_var INTEGER; +BEGIN + BEGIN + v_var := (leaker_2(fail)).error_code; + EXCEPTION + WHEN others THEN RETURN 0; + END; + RETURN 1; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION leaker_2(fail BOOL, OUT error_code INTEGER, OUT new_id INTEGER) + RETURNS RECORD AS $$ +BEGIN + IF fail THEN + RAISE EXCEPTION 'fail ...'; + END IF; + error_code := 1; + new_id := 1; + RETURN; +END; +$$ LANGUAGE plpgsql; + +SELECT * FROM leaker_1(false); +SELECT * FROM leaker_1(true); + +DROP FUNCTION leaker_1(bool); +DROP FUNCTION leaker_2(bool); + +-- Test for appropriate cleanup of non-simple expression evaluations +-- (bug in all versions prior to August 2010) + +CREATE FUNCTION nonsimple_expr_test() RETURNS text[] AS $$ +DECLARE + arr text[]; + lr text; + i integer; +BEGIN + arr := array[array['foo','bar'], array['baz', 'quux']]; + lr := 'fool'; + i := 1; + -- use sub-SELECTs to make expressions non-simple + arr[(SELECT i)][(SELECT i+1)] := (SELECT lr); + RETURN arr; +END; +$$ LANGUAGE plpgsql; + +SELECT nonsimple_expr_test(); + +DROP FUNCTION nonsimple_expr_test(); + +CREATE FUNCTION nonsimple_expr_test() RETURNS integer AS $$ +declare + i integer NOT NULL := 0; +begin + begin + i := (SELECT NULL::integer); -- should throw error + exception + WHEN OTHERS THEN + i := (SELECT 1::integer); + end; + return i; +end; +$$ LANGUAGE plpgsql; + +SELECT nonsimple_expr_test(); + +DROP FUNCTION nonsimple_expr_test(); + +-- +-- Test cases involving recursion and error recovery in simple expressions +-- (bugs in all versions before October 2010). The problems are most +-- easily exposed by mutual recursion between plpgsql and sql functions. +-- + +create function recurse(float8) returns float8 as +$$ +begin + if ($1 > 0) then + return sql_recurse($1 - 1); + else + return $1; + end if; +end; +$$ language plpgsql; + +-- "limit" is to prevent this from being inlined +create function sql_recurse(float8) returns float8 as +$$ select recurse($1) limit 1; $$ language sql; + +select recurse(10); + +create function error1(text) returns text language sql as +$$ SELECT relname::text FROM pg_class c WHERE c.oid = $1::regclass $$; + +create function error2(p_name_table text) returns text language plpgsql as $$ +begin + return error1(p_name_table); +end$$; + +BEGIN; +create table public.stuffs (stuff text); +SAVEPOINT a; +select error2('nonexistent.stuffs'); +ROLLBACK TO a; +select error2('public.stuffs'); +rollback; + +drop function error2(p_name_table text); +drop function error1(text); + +-- Test for proper handling of cast-expression caching + +create function sql_to_date(integer) returns date as $$ +select $1::text::date +$$ language sql immutable strict; + +create cast (integer as date) with function sql_to_date(integer) as assignment; + +create function cast_invoker(integer) returns date as $$ +begin + return $1; +end$$ language plpgsql; + +select cast_invoker(20150717); +select cast_invoker(20150718); -- second call crashed in pre-release 9.5 + +begin; +select cast_invoker(20150717); +select cast_invoker(20150718); +savepoint s1; +select cast_invoker(20150718); +select cast_invoker(-1); -- fails +rollback to savepoint s1; +select cast_invoker(20150719); +select cast_invoker(20150720); +commit; + +drop function cast_invoker(integer); +drop function sql_to_date(integer) cascade; + +-- Test handling of cast cache inside DO blocks +-- (to check the original crash case, this must be a cast not previously +-- used in this session) + +begin; +do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$; +do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$; +end; + +-- Test for consistent reporting of error context + +create function fail() returns int language plpgsql as $$ +begin + return 1/0; +end +$$; + +select fail(); +select fail(); + +drop function fail(); + +-- Test handling of string literals. + +set standard_conforming_strings = off; + +create or replace function strtest() returns text as $$ +begin + raise notice 'foo\\bar\041baz'; + return 'foo\\bar\041baz'; +end +$$ language plpgsql; + +select strtest(); + +create or replace function strtest() returns text as $$ +begin + raise notice E'foo\\bar\041baz'; + return E'foo\\bar\041baz'; +end +$$ language plpgsql; + +select strtest(); + +set standard_conforming_strings = on; + +create or replace function strtest() returns text as $$ +begin + raise notice 'foo\\bar\041baz\'; + return 'foo\\bar\041baz\'; +end +$$ language plpgsql; + +select strtest(); + +create or replace function strtest() returns text as $$ +begin + raise notice E'foo\\bar\041baz'; + return E'foo\\bar\041baz'; +end +$$ language plpgsql; + +select strtest(); + +drop function strtest(); + +-- Test anonymous code blocks. + +DO $$ +DECLARE r record; +BEGIN + FOR r IN SELECT rtrim(roomno) AS roomno, comment FROM Room ORDER BY roomno + LOOP + RAISE NOTICE '%, %', r.roomno, r.comment; + END LOOP; +END$$; + +-- these are to check syntax error reporting +DO LANGUAGE plpgsql $$begin return 1; end$$; + +DO $$ +DECLARE r record; +BEGIN + FOR r IN SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno + LOOP + RAISE NOTICE '%, %', r.roomno, r.comment; + END LOOP; +END$$; + +-- Check handling of errors thrown from/into anonymous code blocks. +do $outer$ +begin + for i in 1..10 loop + begin + execute $ex$ + do $$ + declare x int = 0; + begin + x := 1 / x; + end; + $$; + $ex$; + exception when division_by_zero then + raise notice 'caught division by zero'; + end; + end loop; +end; +$outer$; + +-- Check variable scoping -- a var is not available in its own or prior +-- default expressions, but it is available in later ones. + +do $$ +declare x int := x + 1; -- error +begin + raise notice 'x = %', x; +end; +$$; + +do $$ +declare y int := x + 1; -- error + x int := 42; +begin + raise notice 'x = %, y = %', x, y; +end; +$$; + +do $$ +declare x int := 42; + y int := x + 1; +begin + raise notice 'x = %, y = %', x, y; +end; +$$; + +do $$ +declare x int := 42; +begin + declare y int := x + 1; + x int := x + 2; + z int := x * 10; + begin + raise notice 'x = %, y = %, z = %', x, y, z; + end; +end; +$$; + +-- Check handling of conflicts between plpgsql vars and table columns. + +set plpgsql.variable_conflict = error; + +create function conflict_test() returns setof int8_tbl as $$ +declare r record; + q1 bigint := 42; +begin + for r in select q1,q2 from int8_tbl loop + return next r; + end loop; +end; +$$ language plpgsql; + +select * from conflict_test(); + +create or replace function conflict_test() returns setof int8_tbl as $$ +#variable_conflict use_variable +declare r record; + q1 bigint := 42; +begin + for r in select q1,q2 from int8_tbl loop + return next r; + end loop; +end; +$$ language plpgsql; + +select * from conflict_test(); + +create or replace function conflict_test() returns setof int8_tbl as $$ +#variable_conflict use_column +declare r record; + q1 bigint := 42; +begin + for r in select q1,q2 from int8_tbl loop + return next r; + end loop; +end; +$$ language plpgsql; + +select * from conflict_test(); + +drop function conflict_test(); + +-- Check that an unreserved keyword can be used as a variable name + +create function unreserved_test() returns int as $$ +declare + forward int := 21; +begin + forward := forward * 2; + return forward; +end +$$ language plpgsql; + +select unreserved_test(); + +create or replace function unreserved_test() returns int as $$ +declare + return int := 42; +begin + return := return + 1; + return return; +end +$$ language plpgsql; + +select unreserved_test(); + +create or replace function unreserved_test() returns int as $$ +declare + comment int := 21; +begin + comment := comment * 2; + comment on function unreserved_test() is 'this is a test'; + return comment; +end +$$ language plpgsql; + +select unreserved_test(); + +select obj_description('unreserved_test()'::regprocedure, 'pg_proc'); + +drop function unreserved_test(); + +-- +-- Test FOREACH over arrays +-- + +create function foreach_test(anyarray) +returns void as $$ +declare x int; +begin + foreach x in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; + +select foreach_test(ARRAY[1,2,3,4]); +select foreach_test(ARRAY[[1,2],[3,4]]); + +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int; +begin + foreach x slice 1 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; + +-- should fail +select foreach_test(ARRAY[1,2,3,4]); +select foreach_test(ARRAY[[1,2],[3,4]]); + +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int[]; +begin + foreach x slice 1 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; + +select foreach_test(ARRAY[1,2,3,4]); +select foreach_test(ARRAY[[1,2],[3,4]]); + +-- higher level of slicing +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int[]; +begin + foreach x slice 2 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; + +-- should fail +select foreach_test(ARRAY[1,2,3,4]); +-- ok +select foreach_test(ARRAY[[1,2],[3,4]]); +select foreach_test(ARRAY[[[1,2]],[[3,4]]]); + +create type xy_tuple AS (x int, y int); + +-- iteration over array of records +create or replace function foreach_test(anyarray) +returns void as $$ +declare r record; +begin + foreach r in array $1 + loop + raise notice '%', r; + end loop; + end; +$$ language plpgsql; + +select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); +select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); + +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int; y int; +begin + foreach x, y in array $1 + loop + raise notice 'x = %, y = %', x, y; + end loop; + end; +$$ language plpgsql; + +select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); +select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); + +-- slicing over array of composite types +create or replace function foreach_test(anyarray) +returns void as $$ +declare x xy_tuple[]; +begin + foreach x slice 1 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; + +select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); +select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); + +drop function foreach_test(anyarray); +drop type xy_tuple; + +-- +-- Assorted tests for array subscript assignment +-- + +create temp table rtype (id int, ar text[]); + +create function arrayassign1() returns text[] language plpgsql as $$ +declare + r record; +begin + r := row(12, '{foo,bar,baz}')::rtype; + r.ar[2] := 'replace'; + return r.ar; +end$$; + +select arrayassign1(); +select arrayassign1(); -- try again to exercise internal caching + +create domain orderedarray as int[2] + constraint sorted check (value[1] < value[2]); + +select '{1,2}'::orderedarray; +select '{2,1}'::orderedarray; -- fail + +create function testoa(x1 int, x2 int, x3 int) returns orderedarray +language plpgsql as $$ +declare res orderedarray; +begin + res := array[x1, x2]; + res[2] := x3; + return res; +end$$; + +select testoa(1,2,3); +select testoa(1,2,3); -- try again to exercise internal caching +select testoa(2,1,3); -- fail at initial assign +select testoa(1,2,1); -- fail at update + +drop function arrayassign1(); +drop function testoa(x1 int, x2 int, x3 int); + + +-- +-- Test handling of expanded arrays +-- + +create function returns_rw_array(int) returns int[] +language plpgsql as $$ + declare r int[]; + begin r := array[$1, $1]; return r; end; +$$ stable; + +create function consumes_rw_array(int[]) returns int +language plpgsql as $$ + begin return $1[1]; end; +$$ stable; + +select consumes_rw_array(returns_rw_array(42)); + +-- bug #14174 +explain (verbose, costs off) +select i, a from + (select returns_rw_array(1) as a offset 0) ss, + lateral consumes_rw_array(a) i; + +select i, a from + (select returns_rw_array(1) as a offset 0) ss, + lateral consumes_rw_array(a) i; + +explain (verbose, costs off) +select consumes_rw_array(a), a from returns_rw_array(1) a; + +select consumes_rw_array(a), a from returns_rw_array(1) a; + +explain (verbose, costs off) +select consumes_rw_array(a), a from + (values (returns_rw_array(1)), (returns_rw_array(2))) v(a); + +select consumes_rw_array(a), a from + (values (returns_rw_array(1)), (returns_rw_array(2))) v(a); + +do $$ +declare a int[] := array[1,2]; +begin + a := a || 3; + raise notice 'a = %', a; +end$$; + + +-- +-- Test access to call stack +-- + +create function inner_func(int) +returns int as $$ +declare _context text; +begin + get diagnostics _context = pg_context; + raise notice '***%***', _context; + -- lets do it again, just for fun.. + get diagnostics _context = pg_context; + raise notice '***%***', _context; + raise notice 'lets make sure we didnt break anything'; + return 2 * $1; +end; +$$ language plpgsql; + +create or replace function outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into inner_func()'; + myresult := inner_func($1); + raise notice 'inner_func() done'; + return myresult; +end; +$$ language plpgsql; + +create or replace function outer_outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into outer_func()'; + myresult := outer_func($1); + raise notice 'outer_func() done'; + return myresult; +end; +$$ language plpgsql; + +select outer_outer_func(10); +-- repeated call should to work +select outer_outer_func(20); + +drop function outer_outer_func(int); +drop function outer_func(int); +drop function inner_func(int); + +-- access to call stack from exception +create function inner_func(int) +returns int as $$ +declare + _context text; + sx int := 5; +begin + begin + perform sx / 0; + exception + when division_by_zero then + get diagnostics _context = pg_context; + raise notice '***%***', _context; + end; + + -- lets do it again, just for fun.. + get diagnostics _context = pg_context; + raise notice '***%***', _context; + raise notice 'lets make sure we didnt break anything'; + return 2 * $1; +end; +$$ language plpgsql; + +create or replace function outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into inner_func()'; + myresult := inner_func($1); + raise notice 'inner_func() done'; + return myresult; +end; +$$ language plpgsql; + +create or replace function outer_outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into outer_func()'; + myresult := outer_func($1); + raise notice 'outer_func() done'; + return myresult; +end; +$$ language plpgsql; + +select outer_outer_func(10); +-- repeated call should to work +select outer_outer_func(20); + +drop function outer_outer_func(int); +drop function outer_func(int); +drop function inner_func(int); + +-- +-- Test ASSERT +-- + +do $$ +begin + assert 1=1; -- should succeed +end; +$$; + +do $$ +begin + assert 1=0; -- should fail +end; +$$; + +do $$ +begin + assert NULL; -- should fail +end; +$$; + +-- check controlling GUC +set plpgsql.check_asserts = off; +do $$ +begin + assert 1=0; -- won't be tested +end; +$$; +reset plpgsql.check_asserts; + +-- test custom message +do $$ +declare var text := 'some value'; +begin + assert 1=0, format('assertion failed, var = "%s"', var); +end; +$$; + +-- ensure assertions are not trapped by 'others' +do $$ +begin + assert 1=0, 'unhandled assertion'; +exception when others then + null; -- do nothing +end; +$$; + +-- Test use of plpgsql in a domain check constraint (cf. bug #14414) + +create function plpgsql_domain_check(val int) returns boolean as $$ +begin return val > 0; end +$$ language plpgsql immutable; + +create domain plpgsql_domain as integer check(plpgsql_domain_check(value)); + +do $$ +declare v_test plpgsql_domain; +begin + v_test := 1; +end; +$$; + +do $$ +declare v_test plpgsql_domain := 1; +begin + v_test := 0; -- fail +end; +$$; + +-- Test handling of expanded array passed to a domain constraint (bug #14472) + +create function plpgsql_arr_domain_check(val int[]) returns boolean as $$ +begin return val[1] > 0; end +$$ language plpgsql immutable; + +create domain plpgsql_arr_domain as int[] check(plpgsql_arr_domain_check(value)); + +do $$ +declare v_test plpgsql_arr_domain; +begin + v_test := array[1]; + v_test := v_test || 2; +end; +$$; + +do $$ +declare v_test plpgsql_arr_domain := array[1]; +begin + v_test := 0 || v_test; -- fail +end; +$$; + +-- +-- test usage of transition tables in AFTER triggers +-- + +CREATE TABLE transition_table_base (id int PRIMARY KEY, val text); + +CREATE FUNCTION transition_table_base_ins_func() + RETURNS trigger + LANGUAGE plpgsql +AS $$ +DECLARE + t text; + l text; +BEGIN + t = ''; + FOR l IN EXECUTE + $q$ + EXPLAIN (TIMING off, COSTS off, VERBOSE on) + SELECT * FROM newtable + $q$ LOOP + t = t || l || E'\n'; + END LOOP; + + RAISE INFO '%', t; + RETURN new; +END; +$$; + +CREATE TRIGGER transition_table_base_ins_trig + AFTER INSERT ON transition_table_base + REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable + FOR EACH STATEMENT + EXECUTE PROCEDURE transition_table_base_ins_func(); + +CREATE TRIGGER transition_table_base_ins_trig + AFTER INSERT ON transition_table_base + REFERENCING NEW TABLE AS newtable + FOR EACH STATEMENT + EXECUTE PROCEDURE transition_table_base_ins_func(); + +INSERT INTO transition_table_base VALUES (1, 'One'), (2, 'Two'); +INSERT INTO transition_table_base VALUES (3, 'Three'), (4, 'Four'); + +CREATE OR REPLACE FUNCTION transition_table_base_upd_func() + RETURNS trigger + LANGUAGE plpgsql +AS $$ +DECLARE + t text; + l text; +BEGIN + t = ''; + FOR l IN EXECUTE + $q$ + EXPLAIN (TIMING off, COSTS off, VERBOSE on) + SELECT * FROM oldtable ot FULL JOIN newtable nt USING (id) + $q$ LOOP + t = t || l || E'\n'; + END LOOP; + + RAISE INFO '%', t; + RETURN new; +END; +$$; + +CREATE TRIGGER transition_table_base_upd_trig + AFTER UPDATE ON transition_table_base + REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable + FOR EACH STATEMENT + EXECUTE PROCEDURE transition_table_base_upd_func(); + +UPDATE transition_table_base + SET val = '*' || val || '*' + WHERE id BETWEEN 2 AND 3; + +CREATE TABLE transition_table_level1 +( + level1_no serial NOT NULL , + level1_node_name varchar(255), + PRIMARY KEY (level1_no) +) WITHOUT OIDS; + +CREATE TABLE transition_table_level2 +( + level2_no serial NOT NULL , + parent_no int NOT NULL, + level1_node_name varchar(255), + PRIMARY KEY (level2_no) +) WITHOUT OIDS; + +CREATE TABLE transition_table_status +( + level int NOT NULL, + node_no int NOT NULL, + status int, + PRIMARY KEY (level, node_no) +) WITHOUT OIDS; + +CREATE FUNCTION transition_table_level1_ri_parent_del_func() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ + DECLARE n bigint; + BEGIN + PERFORM FROM p JOIN transition_table_level2 c ON c.parent_no = p.level1_no; + IF FOUND THEN + RAISE EXCEPTION 'RI error'; + END IF; + RETURN NULL; + END; +$$; + +CREATE TRIGGER transition_table_level1_ri_parent_del_trigger + AFTER DELETE ON transition_table_level1 + REFERENCING OLD TABLE AS p + FOR EACH STATEMENT EXECUTE PROCEDURE + transition_table_level1_ri_parent_del_func(); + +CREATE FUNCTION transition_table_level1_ri_parent_upd_func() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ + DECLARE + x int; + BEGIN + WITH p AS (SELECT level1_no, sum(delta) cnt + FROM (SELECT level1_no, 1 AS delta FROM i + UNION ALL + SELECT level1_no, -1 AS delta FROM d) w + GROUP BY level1_no + HAVING sum(delta) < 0) + SELECT level1_no + FROM p JOIN transition_table_level2 c ON c.parent_no = p.level1_no + INTO x; + IF FOUND THEN + RAISE EXCEPTION 'RI error'; + END IF; + RETURN NULL; + END; +$$; + +CREATE TRIGGER transition_table_level1_ri_parent_upd_trigger + AFTER UPDATE ON transition_table_level1 + REFERENCING OLD TABLE AS d NEW TABLE AS i + FOR EACH STATEMENT EXECUTE PROCEDURE + transition_table_level1_ri_parent_upd_func(); + +CREATE FUNCTION transition_table_level2_ri_child_insupd_func() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ + BEGIN + PERFORM FROM i + LEFT JOIN transition_table_level1 p + ON p.level1_no IS NOT NULL AND p.level1_no = i.parent_no + WHERE p.level1_no IS NULL; + IF FOUND THEN + RAISE EXCEPTION 'RI error'; + END IF; + RETURN NULL; + END; +$$; + +CREATE TRIGGER transition_table_level2_ri_child_ins_trigger + AFTER INSERT ON transition_table_level2 + REFERENCING NEW TABLE AS i + FOR EACH STATEMENT EXECUTE PROCEDURE + transition_table_level2_ri_child_insupd_func(); + +CREATE TRIGGER transition_table_level2_ri_child_upd_trigger + AFTER UPDATE ON transition_table_level2 + REFERENCING NEW TABLE AS i + FOR EACH STATEMENT EXECUTE PROCEDURE + transition_table_level2_ri_child_insupd_func(); + +-- create initial test data +INSERT INTO transition_table_level1 (level1_no) + SELECT generate_series(1,200); +ANALYZE transition_table_level1; + +INSERT INTO transition_table_level2 (level2_no, parent_no) + SELECT level2_no, level2_no / 50 + 1 AS parent_no + FROM generate_series(1,9999) level2_no; +ANALYZE transition_table_level2; + +INSERT INTO transition_table_status (level, node_no, status) + SELECT 1, level1_no, 0 FROM transition_table_level1; + +INSERT INTO transition_table_status (level, node_no, status) + SELECT 2, level2_no, 0 FROM transition_table_level2; +ANALYZE transition_table_status; + +INSERT INTO transition_table_level1(level1_no) + SELECT generate_series(201,1000); +ANALYZE transition_table_level1; + +-- behave reasonably if someone tries to modify a transition table +CREATE FUNCTION transition_table_level2_bad_usage_func() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ + BEGIN + INSERT INTO dx VALUES (1000000, 1000000, 'x'); + RETURN NULL; + END; +$$; + +CREATE TRIGGER transition_table_level2_bad_usage_trigger + AFTER DELETE ON transition_table_level2 + REFERENCING OLD TABLE AS dx + FOR EACH STATEMENT EXECUTE PROCEDURE + transition_table_level2_bad_usage_func(); + +DELETE FROM transition_table_level2 + WHERE level2_no BETWEEN 301 AND 305; + +DROP TRIGGER transition_table_level2_bad_usage_trigger + ON transition_table_level2; + +-- attempt modifications which would break RI (should all fail) +DELETE FROM transition_table_level1 + WHERE level1_no = 25; + +UPDATE transition_table_level1 SET level1_no = -1 + WHERE level1_no = 30; + +INSERT INTO transition_table_level2 (level2_no, parent_no) + VALUES (10000, 10000); + +UPDATE transition_table_level2 SET parent_no = 2000 + WHERE level2_no = 40; + + +-- attempt modifications which would not break RI (should all succeed) +DELETE FROM transition_table_level1 + WHERE level1_no BETWEEN 201 AND 1000; + +DELETE FROM transition_table_level1 + WHERE level1_no BETWEEN 100000000 AND 100000010; + +SELECT count(*) FROM transition_table_level1; + +DELETE FROM transition_table_level2 + WHERE level2_no BETWEEN 211 AND 220; + +SELECT count(*) FROM transition_table_level2; + +CREATE TABLE alter_table_under_transition_tables +( + id int PRIMARY KEY, + name text +); + +CREATE FUNCTION alter_table_under_transition_tables_upd_func() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ +BEGIN + RAISE WARNING 'old table = %, new table = %', + (SELECT string_agg(id || '=' || name, ',') FROM d), + (SELECT string_agg(id || '=' || name, ',') FROM i); + RAISE NOTICE 'one = %', (SELECT 1 FROM alter_table_under_transition_tables LIMIT 1); + RETURN NULL; +END; +$$; + +-- should fail, TRUNCATE is not compatible with transition tables +CREATE TRIGGER alter_table_under_transition_tables_upd_trigger + AFTER TRUNCATE OR UPDATE ON alter_table_under_transition_tables + REFERENCING OLD TABLE AS d NEW TABLE AS i + FOR EACH STATEMENT EXECUTE PROCEDURE + alter_table_under_transition_tables_upd_func(); + +-- should work +CREATE TRIGGER alter_table_under_transition_tables_upd_trigger + AFTER UPDATE ON alter_table_under_transition_tables + REFERENCING OLD TABLE AS d NEW TABLE AS i + FOR EACH STATEMENT EXECUTE PROCEDURE + alter_table_under_transition_tables_upd_func(); + +INSERT INTO alter_table_under_transition_tables + VALUES (1, '1'), (2, '2'), (3, '3'); +UPDATE alter_table_under_transition_tables + SET name = name || name; + +-- now change 'name' to an integer to see what happens... +ALTER TABLE alter_table_under_transition_tables + ALTER COLUMN name TYPE int USING name::integer; +UPDATE alter_table_under_transition_tables + SET name = (name::text || name::text)::integer; + +-- now drop column 'name' +ALTER TABLE alter_table_under_transition_tables + DROP column name; +UPDATE alter_table_under_transition_tables + SET id = id; + +-- +-- Test multiple reference to a transition table +-- + +CREATE TABLE multi_test (i int); +INSERT INTO multi_test VALUES (1); + +CREATE OR REPLACE FUNCTION multi_test_trig() RETURNS trigger +LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'count = %', (SELECT COUNT(*) FROM new_test); + RAISE NOTICE 'count union = %', + (SELECT COUNT(*) + FROM (SELECT * FROM new_test UNION ALL SELECT * FROM new_test) ss); + RETURN NULL; +END$$; + +CREATE TRIGGER my_trigger AFTER UPDATE ON multi_test + REFERENCING NEW TABLE AS new_test OLD TABLE as old_test + FOR EACH STATEMENT EXECUTE PROCEDURE multi_test_trig(); + +UPDATE multi_test SET i = i; + +DROP TABLE multi_test; +DROP FUNCTION multi_test_trig(); + +-- +-- Check type parsing and record fetching from partitioned tables +-- + +CREATE TABLE partitioned_table (a int, b text) PARTITION BY LIST (a); +CREATE TABLE pt_part1 PARTITION OF partitioned_table FOR VALUES IN (1); +CREATE TABLE pt_part2 PARTITION OF partitioned_table FOR VALUES IN (2); + +INSERT INTO partitioned_table VALUES (1, 'Row 1'); +INSERT INTO partitioned_table VALUES (2, 'Row 2'); + +CREATE OR REPLACE FUNCTION get_from_partitioned_table(partitioned_table.a%type) +RETURNS partitioned_table AS $$ +DECLARE + a_val partitioned_table.a%TYPE; + result partitioned_table%ROWTYPE; +BEGIN + a_val := $1; + SELECT * INTO result FROM partitioned_table WHERE a = a_val; + RETURN result; +END; $$ LANGUAGE plpgsql; + +SELECT * FROM get_from_partitioned_table(1) AS t; + +CREATE OR REPLACE FUNCTION list_partitioned_table() +RETURNS SETOF partitioned_table.a%TYPE AS $$ +DECLARE + row partitioned_table%ROWTYPE; + a_val partitioned_table.a%TYPE; +BEGIN + FOR row IN SELECT * FROM partitioned_table ORDER BY a LOOP + a_val := row.a; + RETURN NEXT a_val; + END LOOP; + RETURN; +END; $$ LANGUAGE plpgsql; + +SELECT * FROM list_partitioned_table() AS t; + +-- +-- Check argument name is used instead of $n in error message +-- +CREATE FUNCTION fx(x WSlot) RETURNS void AS $$ +BEGIN + GET DIAGNOSTICS x = ROW_COUNT; + RETURN; +END; $$ LANGUAGE plpgsql; + +/* + * TODO(jason): the following section belongs in src/pl/plpgsql/src, but YB + * does not yet support running tests there, so that's why it is here. + */ +-- inner COMMIT with output arguments + +CREATE PROCEDURE test_proc7c(x int, INOUT a int, INOUT b numeric) +LANGUAGE plpgsql +AS $$ +BEGIN + a := x / 10; + b := x / 2; + COMMIT; +END; +$$; + +CREATE PROCEDURE test_proc7cc(_x int) +LANGUAGE plpgsql +AS $$ +DECLARE _a int; _b numeric; +BEGIN + CALL test_proc7c(_x, _a, _b); + RAISE NOTICE '_x: %,_a: %, _b: %', _x, _a, _b; +END +$$; + +CALL test_proc7cc(10); diff --git a/src/test/regress/sql/yb.port.polygon.sql b/src/test/regress/sql/yb.port.polygon.sql new file mode 100644 index 0000000000000..f53b2cb630942 --- /dev/null +++ b/src/test/regress/sql/yb.port.polygon.sql @@ -0,0 +1,142 @@ +-- +-- POLYGON +-- +-- polygon logic +-- + +CREATE TABLE POLYGON_TBL(f1 polygon); + + +INSERT INTO POLYGON_TBL(f1) VALUES ('(2.0,0.0),(2.0,4.0),(0.0,0.0)'); + +INSERT INTO POLYGON_TBL(f1) VALUES ('(3.0,1.0),(3.0,3.0),(1.0,0.0)'); + +INSERT INTO POLYGON_TBL(f1) VALUES ('(1,2),(3,4),(5,6),(7,8)'); +INSERT INTO POLYGON_TBL(f1) VALUES ('(7,8),(5,6),(3,4),(1,2)'); -- Reverse +INSERT INTO POLYGON_TBL(f1) VALUES ('(1,2),(7,8),(5,6),(3,-4)'); + +-- degenerate polygons +INSERT INTO POLYGON_TBL(f1) VALUES ('(0.0,0.0)'); + +INSERT INTO POLYGON_TBL(f1) VALUES ('(0.0,1.0),(0.0,1.0)'); + +-- bad polygon input strings +INSERT INTO POLYGON_TBL(f1) VALUES ('0.0'); + +INSERT INTO POLYGON_TBL(f1) VALUES ('(0.0 0.0'); + +INSERT INTO POLYGON_TBL(f1) VALUES ('(0,1,2)'); + +INSERT INTO POLYGON_TBL(f1) VALUES ('(0,1,2,3'); + +INSERT INTO POLYGON_TBL(f1) VALUES ('asdf'); + + +SELECT * FROM POLYGON_TBL; + +-- +-- Test the SP-GiST index +-- + +CREATE TABLE quad_poly_tbl (id int, p polygon); + +INSERT INTO quad_poly_tbl + SELECT (x - 1) * 100 + y, polygon(circle(point(x * 10, y * 10), 1 + (x + y) % 10)) + FROM generate_series(1, 100) x, + generate_series(1, 100) y; + +INSERT INTO quad_poly_tbl + SELECT i, polygon '((200, 300),(210, 310),(230, 290))' + FROM generate_series(10001, 11000) AS i; + +INSERT INTO quad_poly_tbl + VALUES + (11001, NULL), + (11002, NULL), + (11003, NULL); + +CREATE INDEX quad_poly_tbl_idx ON quad_poly_tbl USING spgist(p); + +-- get reference results for ORDER BY distance from seq scan +SET enable_seqscan = ON; +SET enable_indexscan = OFF; +SET enable_bitmapscan = OFF; + +CREATE TEMP TABLE quad_poly_tbl_ord_seq2 AS +SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id +FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; + +-- check results from index scan +SET enable_seqscan = OFF; +SET enable_indexscan = OFF; +SET enable_bitmapscan = ON; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p << polygon '((300,300),(400,600),(600,500),(700,200))'; +SELECT count(*) FROM quad_poly_tbl WHERE p << polygon '((300,300),(400,600),(600,500),(700,200))'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p &< polygon '((300,300),(400,600),(600,500),(700,200))'; +SELECT count(*) FROM quad_poly_tbl WHERE p &< polygon '((300,300),(400,600),(600,500),(700,200))'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p && polygon '((300,300),(400,600),(600,500),(700,200))'; +SELECT count(*) FROM quad_poly_tbl WHERE p && polygon '((300,300),(400,600),(600,500),(700,200))'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p &> polygon '((300,300),(400,600),(600,500),(700,200))'; +SELECT count(*) FROM quad_poly_tbl WHERE p &> polygon '((300,300),(400,600),(600,500),(700,200))'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p >> polygon '((300,300),(400,600),(600,500),(700,200))'; +SELECT count(*) FROM quad_poly_tbl WHERE p >> polygon '((300,300),(400,600),(600,500),(700,200))'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p <<| polygon '((300,300),(400,600),(600,500),(700,200))'; +SELECT count(*) FROM quad_poly_tbl WHERE p <<| polygon '((300,300),(400,600),(600,500),(700,200))'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p &<| polygon '((300,300),(400,600),(600,500),(700,200))'; +SELECT count(*) FROM quad_poly_tbl WHERE p &<| polygon '((300,300),(400,600),(600,500),(700,200))'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p |&> polygon '((300,300),(400,600),(600,500),(700,200))'; +SELECT count(*) FROM quad_poly_tbl WHERE p |&> polygon '((300,300),(400,600),(600,500),(700,200))'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p |>> polygon '((300,300),(400,600),(600,500),(700,200))'; +SELECT count(*) FROM quad_poly_tbl WHERE p |>> polygon '((300,300),(400,600),(600,500),(700,200))'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; +SELECT count(*) FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p @> polygon '((340,550),(343,552),(341,553))'; +SELECT count(*) FROM quad_poly_tbl WHERE p @> polygon '((340,550),(343,552),(341,553))'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(230, 290))'; +SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(230, 290))'; + +-- test ORDER BY distance +SET enable_indexscan = ON; +SET enable_bitmapscan = OFF; + +EXPLAIN (COSTS OFF) +SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id +FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; + +CREATE TEMP TABLE quad_poly_tbl_ord_idx2 AS +SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id +FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; + +SELECT * +FROM quad_poly_tbl_ord_seq2 seq FULL JOIN quad_poly_tbl_ord_idx2 idx + ON seq.n = idx.n AND seq.id = idx.id AND + (seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL) +WHERE seq.id IS NULL OR idx.id IS NULL; + +RESET enable_seqscan; +RESET enable_indexscan; +RESET enable_bitmapscan; diff --git a/src/test/regress/sql/yb.port.portals.sql b/src/test/regress/sql/yb.port.portals.sql new file mode 100644 index 0000000000000..a719668a70290 --- /dev/null +++ b/src/test/regress/sql/yb.port.portals.sql @@ -0,0 +1,563 @@ +-- +-- Cursor regression tests +-- + +BEGIN; + +DECLARE foo1 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +DECLARE foo2 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; + +DECLARE foo3 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +DECLARE foo4 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; + +DECLARE foo5 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +DECLARE foo6 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; + +DECLARE foo7 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +DECLARE foo8 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; + +DECLARE foo9 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +DECLARE foo10 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; + +DECLARE foo11 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +DECLARE foo12 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; + +DECLARE foo13 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +DECLARE foo14 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; + +DECLARE foo15 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +DECLARE foo16 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; + +DECLARE foo17 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +DECLARE foo18 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; + +DECLARE foo19 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +DECLARE foo20 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; + +DECLARE foo21 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +DECLARE foo22 SCROLL CURSOR FOR SELECT * FROM tenk2 ORDER BY unique2; + +DECLARE foo23 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +FETCH 1 in foo1; + +FETCH NEXT in foo1; + +FETCH FORWARD in foo1; + +FETCH FORWARD 2 in foo1; + +FETCH 2 in foo2; + +FETCH 3 in foo2; + +FETCH 3 in foo3; + +FETCH 4 in foo4; + +FETCH 5 in foo5; + +FETCH 6 in foo6; + +FETCH 7 in foo7; + +FETCH 8 in foo8; + +FETCH 9 in foo9; + +FETCH 10 in foo10; + +FETCH 11 in foo11; + +FETCH 12 in foo12; + +FETCH 13 in foo13; + +FETCH 14 in foo14; + +FETCH 15 in foo15; + +FETCH 16 in foo16; + +FETCH 17 in foo17; + +FETCH 18 in foo18; + +FETCH 19 in foo19; + +FETCH 20 in foo20; + +FETCH 21 in foo21; + +FETCH 22 in foo22; + +FETCH 23 in foo23; + +-- FETCH backward 1 in foo23; + +-- FETCH backward 2 in foo22; + +-- FETCH backward 3 in foo21; + +-- FETCH backward 4 in foo20; + +-- FETCH backward 5 in foo19; + +-- FETCH backward 6 in foo18; + +-- FETCH backward 7 in foo17; + +-- FETCH backward 8 in foo16; + +-- FETCH backward 9 in foo15; + +-- FETCH backward 10 in foo14; + +-- FETCH backward 11 in foo13; + +-- FETCH backward 12 in foo12; + +-- FETCH backward 13 in foo11; + +-- FETCH backward 14 in foo10; + +-- FETCH backward 15 in foo9; + +-- FETCH backward 16 in foo8; + +-- FETCH backward 17 in foo7; + +-- FETCH backward 18 in foo6; + +-- FETCH backward 19 in foo5; + +-- FETCH backward 20 in foo4; + +-- FETCH backward 21 in foo3; + +-- FETCH backward 22 in foo2; + +-- FETCH backward 23 in foo1; + +CLOSE foo1; + +CLOSE foo2; + +CLOSE foo3; + +CLOSE foo4; + +CLOSE foo5; + +CLOSE foo6; + +CLOSE foo7; + +CLOSE foo8; + +CLOSE foo9; + +CLOSE foo10; + +CLOSE foo11; + +CLOSE foo12; + +-- leave some cursors open, to test that auto-close works. + +-- record this in the system view as well (do not query the time field there +-- however) +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors ORDER BY 1; + +END; + +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; + +-- +-- NO SCROLL disallows backward fetching +-- + +BEGIN; + +DECLARE foo24 NO SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; + +FETCH 1 FROM foo24; + +-- FETCH BACKWARD 1 FROM foo24; -- should fail + +END; + +-- +-- Cursors outside transaction blocks +-- + + +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; + +BEGIN; + +DECLARE foo25 SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2 ORDER BY unique2; + +FETCH FROM foo25; + +FETCH FROM foo25; + +COMMIT; + +FETCH FROM foo25; + +-- FETCH BACKWARD FROM foo25; + +-- FETCH ABSOLUTE -1 FROM foo25; + +FETCH -3 FROM foo25; + +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; + +CLOSE foo25; + +-- +-- ROLLBACK should close holdable cursors +-- + +BEGIN; + +DECLARE foo26 CURSOR WITH HOLD FOR SELECT * FROM tenk1 ORDER BY unique2; + +ROLLBACK; + +-- should fail +FETCH FROM foo26; + +-- +-- Parameterized DECLARE needs to insert param values into the cursor portal +-- + +BEGIN; + +CREATE FUNCTION declares_cursor(text) + RETURNS void + AS 'DECLARE c CURSOR FOR SELECT stringu1 FROM tenk1 WHERE stringu1 LIKE $1;' + LANGUAGE SQL; + +SELECT declares_cursor('AB%'); + +FETCH ALL FROM c; + +CLOSE c; + +-- Run the same statements but with FORWARD option. +SELECT declares_cursor('AB%'); + +FETCH FORWARD ALL FROM c; + +ROLLBACK; + +-- +-- Test behavior of both volatile and stable functions inside a cursor; +-- in particular we want to see what happens during commit of a holdable +-- cursor +-- + +create temp table tt1(f1 int); + +create function count_tt1_v() returns int8 as +'select count(*) from tt1' language sql volatile; + +create function count_tt1_s() returns int8 as +'select count(*) from tt1' language sql stable; + +begin; + +insert into tt1 values(1); + +declare c1 cursor for select count_tt1_v(), count_tt1_s(); + +insert into tt1 values(2); + +fetch all from c1; + +rollback; + +begin; + +insert into tt1 values(1); + +declare c2 cursor with hold for select count_tt1_v(), count_tt1_s(); + +insert into tt1 values(2); + +commit; + +delete from tt1; + +fetch all from c2; + +drop function count_tt1_v(); +drop function count_tt1_s(); + + +-- Create a cursor with the BINARY option and check the pg_cursors view +BEGIN; +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; +DECLARE bc BINARY CURSOR FOR SELECT * FROM tenk1; +SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors ORDER BY 1; +ROLLBACK; + +-- We should not see the portal that is created internally to +-- implement EXECUTE in pg_cursors +PREPARE cprep AS + SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; +EXECUTE cprep; + +-- test CLOSE ALL; +SELECT name FROM pg_cursors ORDER BY 1; +CLOSE ALL; +SELECT name FROM pg_cursors ORDER BY 1; +BEGIN; +DECLARE foo1 CURSOR WITH HOLD FOR SELECT 1; +DECLARE foo2 CURSOR WITHOUT HOLD FOR SELECT 1; +SELECT name FROM pg_cursors ORDER BY 1; +CLOSE ALL; +SELECT name FROM pg_cursors ORDER BY 1; +COMMIT; + +-- +-- Tests for updatable cursors +-- + +CREATE TEMP TABLE uctest(f1 int, f2 text); +INSERT INTO uctest VALUES (1, 'one'), (2, 'two'), (3, 'three'); +SELECT * FROM uctest ORDER BY f1; + +-- Check DELETE WHERE CURRENT +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM uctest ORDER BY f1; +FETCH 2 FROM c1; +DELETE FROM uctest WHERE CURRENT OF c1; +-- should show deletion +SELECT * FROM uctest; +-- cursor did not move +FETCH ALL FROM c1; +-- cursor is insensitive +MOVE BACKWARD ALL IN c1; +FETCH ALL FROM c1; +COMMIT; +-- should still see deletion +SELECT * FROM uctest; + +-- Check UPDATE WHERE CURRENT; this time use FOR UPDATE +BEGIN; +-- DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; -- YB: Disable 'FOR UPDATE' due to github #27149 +DECLARE c1 CURSOR FOR SELECT * FROM uctest; +FETCH c1; +UPDATE uctest SET f1 = 8 WHERE CURRENT OF c1; +SELECT * FROM uctest; +COMMIT; +SELECT * FROM uctest; + +-- Check repeated-update and update-then-delete cases +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM uctest; +FETCH c1; +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; +SELECT * FROM uctest; +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; +SELECT * FROM uctest; +-- insensitive cursor should not show effects of updates or deletes +-- FETCH RELATIVE 0 FROM c1; +DELETE FROM uctest WHERE CURRENT OF c1; +SELECT * FROM uctest; +DELETE FROM uctest WHERE CURRENT OF c1; -- no-op +SELECT * FROM uctest; +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op +SELECT * FROM uctest; +-- FETCH RELATIVE 0 FROM c1; +ROLLBACK; +SELECT * FROM uctest; + +BEGIN; +-- DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; -- YB: Disable 'FOR UPDATE' due to github #27149 +DECLARE c1 CURSOR FOR SELECT * FROM uctest; +FETCH c1; +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; +SELECT * FROM uctest; +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; +SELECT * FROM uctest; +DELETE FROM uctest WHERE CURRENT OF c1; +SELECT * FROM uctest; +DELETE FROM uctest WHERE CURRENT OF c1; -- no-op +SELECT * FROM uctest; +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op +SELECT * FROM uctest; +--- sensitive cursors cannot currently scroll back, so this is an error: +-- FETCH RELATIVE 0 FROM c1; +ROLLBACK; +SELECT * FROM uctest; + +-- Check inheritance cases +CREATE TEMP TABLE ucchild () inherits (uctest); +INSERT INTO ucchild values(100, 'hundred'); +SELECT * FROM uctest; + +BEGIN; +-- DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; -- YB: Disable 'FOR UPDATE' due to #27149 +DECLARE c1 CURSOR FOR SELECT * FROM uctest; +FETCH 1 FROM c1; +-- UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- YB: CURRENT OF is not supported #737 +FETCH 1 FROM c1; +-- UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- YB: CURRENT OF is not supported #737 +FETCH 1 FROM c1; +-- UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- YB: CURRENT OF is not supported #737 +FETCH 1 FROM c1; +COMMIT; +SELECT * FROM uctest; + +-- Can update from a self-join, but only if FOR UPDATE says which to use +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5; +FETCH 1 FROM c1; +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail +ROLLBACK; +BEGIN; +-- DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5 FOR UPDATE; -- YB: Disable 'FOR UPDATE' due to github #27149 +DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5; +FETCH 1 FROM c1; +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail +ROLLBACK; +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5 FOR SHARE OF a; +FETCH 1 FROM c1; +UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; +SELECT * FROM uctest; +ROLLBACK; + +-- Check various error cases + +DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no such cursor +DECLARE cx CURSOR WITH HOLD FOR SELECT * FROM uctest; +DELETE FROM uctest WHERE CURRENT OF cx; -- fail, can't use held cursor +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM tenk2; +DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table +ROLLBACK; +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM tenk2 FOR SHARE; +DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table +ROLLBACK; +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM tenk1 JOIN tenk2 USING (unique1); +DELETE FROM tenk1 WHERE CURRENT OF c; -- fail, cursor is on a join +ROLLBACK; +BEGIN; +DECLARE c CURSOR FOR SELECT f1,count(*) FROM uctest GROUP BY f1; +DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor is on aggregation +ROLLBACK; +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM uctest; +DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no current row +ROLLBACK; +BEGIN; +-- DECLARE c1 CURSOR FOR SELECT MIN(f1) FROM uctest FOR UPDATE; -- YB: Disable 'FOR UPDATE' due to github #27149 +DECLARE c1 CURSOR FOR SELECT MIN(f1) FROM uctest; +ROLLBACK; + +-- WHERE CURRENT OF may someday work with views, but today is not that day. +-- For now, just make sure it errors out cleanly. +CREATE TEMP VIEW ucview AS SELECT * FROM uctest; +CREATE RULE ucrule AS ON DELETE TO ucview DO INSTEAD + DELETE FROM uctest WHERE f1 = OLD.f1; +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM ucview; +FETCH FROM c1; +DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported +ROLLBACK; + +-- Check WHERE CURRENT OF with an index-only scan +BEGIN; +EXPLAIN (costs off) +DECLARE c1 CURSOR FOR SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA'; +DECLARE c1 CURSOR FOR SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA'; +FETCH FROM c1; +DELETE FROM onek WHERE CURRENT OF c1; +SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA'; +ROLLBACK; + +-- Check behavior with rewinding to a previous child scan node, +-- as per bug #15395 +BEGIN; +CREATE TABLE current_check (currentid int, payload text); +CREATE TABLE current_check_1 () INHERITS (current_check); +CREATE TABLE current_check_2 () INHERITS (current_check); +INSERT INTO current_check_1 SELECT i, 'p' || i FROM generate_series(1,9) i; +INSERT INTO current_check_2 SELECT i, 'P' || i FROM generate_series(10,19) i; + +DECLARE c1 SCROLL CURSOR FOR SELECT * FROM current_check; + +/* YB: FETCH ABSOLUTE, DELETE .. WHERE CURRENT OF not supported #737 #6514 +-- This tests the fetch-backwards code path +FETCH ABSOLUTE 12 FROM c1; +FETCH ABSOLUTE 8 FROM c1; +DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *; + +-- This tests the ExecutorRewind code path +FETCH ABSOLUTE 13 FROM c1; +FETCH ABSOLUTE 1 FROM c1; +DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *; +*/ -- YB + +SELECT * FROM current_check ORDER BY currentid; -- YB: make order match PG results +ROLLBACK; + +-- Make sure snapshot management works okay, per bug report in +-- 235395b90909301035v7228ce63q392931f15aa74b31@mail.gmail.com +-- TODO(#12494): reverse this change +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +CREATE TABLE cursor (a int); +INSERT INTO cursor VALUES (1); +-- DECLARE c1 NO SCROLL CURSOR FOR SELECT * FROM cursor FOR UPDATE; -- YB: github #27149 +-- UPDATE cursor SET a = 2; +FETCH ALL FROM c1; +COMMIT; +/* YB: The below DROP TABLE is commented because the above FETCH ALL command fails +in YB, so the transaction aborts and the table 'cursor' is rolled back. +This isn't the case in PG. +DROP TABLE cursor; +*/ -- YB +-- Check rewinding a cursor containing a stable function in LIMIT, +-- per bug report in 8336843.9833.1399385291498.JavaMail.root@quick +begin; +create function nochange(int) returns int + as 'select $1 limit 1' language sql stable; +declare c cursor for select * from int8_tbl limit nochange(3); +fetch all from c; +move backward all in c; +fetch all from c; +rollback; + +-- Check handling of non-backwards-scan-capable plans with scroll cursors +begin; +explain (costs off) declare c1 cursor for select (select 42) as x; +explain (costs off) declare c1 scroll cursor for select (select 42) as x; +declare c1 scroll cursor for select (select 42) as x; +fetch all in c1; +fetch backward all in c1; +rollback; +begin; +explain (costs off) declare c2 cursor for select generate_series(1,3) as g; +explain (costs off) declare c2 scroll cursor for select generate_series(1,3) as g; +declare c2 scroll cursor for select generate_series(1,3) as g; +fetch all in c2; +fetch backward all in c2; +rollback; diff --git a/src/test/regress/sql/yb.port.prepare.sql b/src/test/regress/sql/yb.port.prepare.sql new file mode 100644 index 0000000000000..3f0fc64da685d --- /dev/null +++ b/src/test/regress/sql/yb.port.prepare.sql @@ -0,0 +1,83 @@ +-- Regression tests for prepareable statements. We query the content +-- of the pg_prepared_statements view as prepared statements are +-- created and removed. + +SELECT name, statement, parameter_types FROM pg_prepared_statements; + +PREPARE q1 AS SELECT 1 AS a; +EXECUTE q1; + +SELECT name, statement, parameter_types FROM pg_prepared_statements; + +-- should fail +PREPARE q1 AS SELECT 2; + +-- should succeed +DEALLOCATE q1; +PREPARE q1 AS SELECT 2; +EXECUTE q1; + +PREPARE q2 AS SELECT 2 AS b; +SELECT name, statement, parameter_types FROM pg_prepared_statements; + +-- sql92 syntax +DEALLOCATE PREPARE q1; + +SELECT name, statement, parameter_types FROM pg_prepared_statements; + +DEALLOCATE PREPARE q2; +-- the view should return the empty set again +SELECT name, statement, parameter_types FROM pg_prepared_statements; + +-- parameterized queries +PREPARE q2(text) AS + SELECT datname, datistemplate, datallowconn + FROM pg_database WHERE datname = $1; + +EXECUTE q2('postgres'); + +PREPARE q3(text, int, float, boolean, smallint) AS + SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR + ten = $3::bigint OR true = $4 OR odd = $5::int) + ORDER BY unique1; + +EXECUTE q3('AAAAxx', 5::smallint, 10.5::float, false, 4::bigint); + +-- too few params +EXECUTE q3('bool'); + +-- too many params +EXECUTE q3('bytea', 5::smallint, 10.5::float, false, 4::bigint, true); + +-- wrong param types +EXECUTE q3(5::smallint, 10.5::float, false, 4::bigint, 'bytea'); + +-- invalid type +PREPARE q4(nonexistenttype) AS SELECT $1; + +-- create table as execute +PREPARE q5(int, text) AS + SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2 + ORDER BY unique1; +CREATE TEMPORARY TABLE q5_prep_results AS EXECUTE q5(200, 'DTAAAA'); +/* YB: uncomment when above CREATE TABLE AS EXECUTE is supported +SELECT * FROM q5_prep_results; +CREATE TEMPORARY TABLE q5_prep_nodata AS EXECUTE q5(200, 'DTAAAA') + WITH NO DATA; +SELECT * FROM q5_prep_nodata; +*/ -- YB +SELECT 1; -- YB: add intermediate dummy query to avoid comments from polluting the below select from pg_prepared_statements + +-- unknown or unspecified parameter types: should succeed +PREPARE q6 AS + SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2; +PREPARE q7(unknown) AS + SELECT * FROM road WHERE thepath = $1; + +SELECT name, statement, parameter_types FROM pg_prepared_statements + ORDER BY name; + +-- test DEALLOCATE ALL; +DEALLOCATE ALL; +SELECT name, statement, parameter_types FROM pg_prepared_statements + ORDER BY name; diff --git a/src/test/regress/sql/yb.port.privileges.sql b/src/test/regress/sql/yb.port.privileges.sql new file mode 100644 index 0000000000000..7b9c175a3df21 --- /dev/null +++ b/src/test/regress/sql/yb.port.privileges.sql @@ -0,0 +1,1664 @@ +-- +-- Test access privileges +-- + +-- Clean up in case a prior regression run failed + +-- Suppress NOTICE messages when users/groups don't exist +SET client_min_messages TO 'warning'; + +DROP ROLE IF EXISTS regress_priv_group1; +DROP ROLE IF EXISTS regress_priv_group2; + +DROP ROLE IF EXISTS regress_priv_user1; +DROP ROLE IF EXISTS regress_priv_user2; +DROP ROLE IF EXISTS regress_priv_user3; +DROP ROLE IF EXISTS regress_priv_user4; +DROP ROLE IF EXISTS regress_priv_user5; +DROP ROLE IF EXISTS regress_priv_user6; +DROP ROLE IF EXISTS regress_priv_user7; + +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid; +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject + +RESET client_min_messages; + +-- test proper begins here + +CREATE USER regress_priv_user1; +CREATE USER regress_priv_user2; +CREATE USER regress_priv_user3; +CREATE USER regress_priv_user4; +CREATE USER regress_priv_user5; +CREATE USER regress_priv_user5; -- duplicate +CREATE USER regress_priv_user6; +CREATE USER regress_priv_user7; +CREATE USER regress_priv_user8; +CREATE USER regress_priv_user9; +CREATE USER regress_priv_user10; +CREATE ROLE regress_priv_role; + +GRANT pg_read_all_data TO regress_priv_user6; +GRANT pg_write_all_data TO regress_priv_user7; +GRANT pg_read_all_settings TO regress_priv_user8 WITH ADMIN OPTION; + +SET SESSION AUTHORIZATION regress_priv_user8; +GRANT pg_read_all_settings TO regress_priv_user9 WITH ADMIN OPTION; + +SET SESSION AUTHORIZATION regress_priv_user9; +GRANT pg_read_all_settings TO regress_priv_user10; + +SET SESSION AUTHORIZATION regress_priv_user8; +REVOKE pg_read_all_settings FROM regress_priv_user10; +REVOKE ADMIN OPTION FOR pg_read_all_settings FROM regress_priv_user9; +REVOKE pg_read_all_settings FROM regress_priv_user9; + +RESET SESSION AUTHORIZATION; +REVOKE ADMIN OPTION FOR pg_read_all_settings FROM regress_priv_user8; + +SET SESSION AUTHORIZATION regress_priv_user8; +SET ROLE pg_read_all_settings; +RESET ROLE; + +RESET SESSION AUTHORIZATION; +REVOKE pg_read_all_settings FROM regress_priv_user8; + +DROP USER regress_priv_user10; +DROP USER regress_priv_user9; +DROP USER regress_priv_user8; + +CREATE GROUP regress_priv_group1; +CREATE GROUP regress_priv_group2 WITH USER regress_priv_user1, regress_priv_user2; + +ALTER GROUP regress_priv_group1 ADD USER regress_priv_user4; + +ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -- duplicate +ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2; +GRANT regress_priv_group2 TO regress_priv_user4 WITH ADMIN OPTION; + +-- prepare non-leakproof function for later +CREATE FUNCTION leak(integer,integer) RETURNS boolean + AS 'int4lt' + LANGUAGE internal IMMUTABLE STRICT; -- but deliberately not LEAKPROOF +ALTER FUNCTION leak(integer,integer) OWNER TO regress_priv_user1; + +-- test owner privileges + +GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY CURRENT_ROLE; +REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY foo; -- error +REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY regress_priv_user2; -- error +REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY CURRENT_USER; +REVOKE regress_priv_role FROM regress_priv_user1 GRANTED BY CURRENT_ROLE; +DROP ROLE regress_priv_role; + +SET SESSION AUTHORIZATION regress_priv_user1; +SELECT session_user, current_user; + +CREATE TABLE atest1 ( a int, b text ); +SELECT * FROM atest1; +INSERT INTO atest1 VALUES (1, 'one'); +DELETE FROM atest1; +UPDATE atest1 SET a = 1 WHERE b = 'blech'; +TRUNCATE atest1; +BEGIN; +LOCK atest1 IN ACCESS EXCLUSIVE MODE; +COMMIT; + +REVOKE ALL ON atest1 FROM PUBLIC; +SELECT * FROM atest1; + +GRANT ALL ON atest1 TO regress_priv_user2; +GRANT SELECT ON atest1 TO regress_priv_user3, regress_priv_user4; +SELECT * FROM atest1; + +CREATE TABLE atest2 (col1 varchar(10), col2 boolean); +GRANT SELECT ON atest2 TO regress_priv_user2; +GRANT UPDATE ON atest2 TO regress_priv_user3; +GRANT INSERT ON atest2 TO regress_priv_user4 GRANTED BY CURRENT_USER; +GRANT TRUNCATE ON atest2 TO regress_priv_user5 GRANTED BY CURRENT_ROLE; + +GRANT TRUNCATE ON atest2 TO regress_priv_user4 GRANTED BY regress_priv_user5; -- error + + +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT session_user, current_user; + +-- try various combinations of queries on atest1 and atest2 + +SELECT * FROM atest1; -- ok +SELECT * FROM atest2; -- ok +INSERT INTO atest1 VALUES (2, 'two'); -- ok +INSERT INTO atest2 VALUES ('foo', true); -- fail +INSERT INTO atest1 SELECT 1, b FROM atest1; -- ok +UPDATE atest1 SET a = 1 WHERE a = 2; -- ok +UPDATE atest2 SET col2 = NOT col2; -- fail +SELECT * FROM atest1 FOR UPDATE; -- ok +SELECT * FROM atest2 FOR UPDATE; -- fail +DELETE FROM atest2; -- fail +TRUNCATE atest2; -- fail +BEGIN; +LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- fail +COMMIT; +COPY atest2 FROM stdin; -- fail +GRANT ALL ON atest1 TO PUBLIC; -- fail + +-- checks in subquery, both ok +SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) ); +SELECT * FROM atest2 WHERE ( col1 IN ( SELECT b FROM atest1 ) ); + +SET SESSION AUTHORIZATION regress_priv_user6; +SELECT * FROM atest1; -- ok +SELECT * FROM atest2; -- ok +INSERT INTO atest2 VALUES ('foo', true); -- fail + +SET SESSION AUTHORIZATION regress_priv_user7; +SELECT * FROM atest1; -- fail +SELECT * FROM atest2; -- fail +INSERT INTO atest2 VALUES ('foo', true); -- ok +UPDATE atest2 SET col2 = true; -- ok +DELETE FROM atest2; -- ok + +-- Make sure we are not able to modify system catalogs +UPDATE pg_catalog.pg_class SET relname = '123'; -- fail +DELETE FROM pg_catalog.pg_class; -- fail +UPDATE pg_toast.pg_toast_1213 SET chunk_id = 1; -- fail + +SET SESSION AUTHORIZATION regress_priv_user3; +SELECT session_user, current_user; + +SELECT * FROM atest1; -- ok +SELECT * FROM atest2; -- fail +INSERT INTO atest1 VALUES (2, 'two'); -- fail +INSERT INTO atest2 VALUES ('foo', true); -- fail +INSERT INTO atest1 SELECT 1, b FROM atest1; -- fail +UPDATE atest1 SET a = 1 WHERE a = 2; -- fail +UPDATE atest2 SET col2 = NULL; -- ok +UPDATE atest2 SET col2 = NOT col2; -- fails; requires SELECT on atest2 +UPDATE atest2 SET col2 = true FROM atest1 WHERE atest1.a = 5; -- ok +SELECT * FROM atest1 FOR UPDATE; -- fail +SELECT * FROM atest2 FOR UPDATE; -- fail +DELETE FROM atest2; -- fail +TRUNCATE atest2; -- fail +BEGIN; +LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- ok +COMMIT; +COPY atest2 FROM stdin; -- fail + +-- checks in subquery, both fail +SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) ); +SELECT * FROM atest2 WHERE ( col1 IN ( SELECT b FROM atest1 ) ); + +SET SESSION AUTHORIZATION regress_priv_user4; +COPY atest2 FROM stdin; -- ok +bar true +\. +SELECT * FROM atest1; -- ok + + +-- test leaky-function protections in selfuncs + +-- regress_priv_user1 will own a table and provide views for it. +SET SESSION AUTHORIZATION regress_priv_user1; + +CREATE TABLE atest12 as + SELECT x AS a, 10001 - x AS b FROM generate_series(1,10000) x; +CREATE INDEX ON atest12 (a); +CREATE INDEX ON atest12 (abs(a)); +-- results below depend on having quite accurate stats for atest12, so... +ALTER TABLE atest12 SET (autovacuum_enabled = off); +SET default_statistics_target = 10000; +VACUUM ANALYZE atest12; +RESET default_statistics_target; + +CREATE OPERATOR <<< (procedure = leak, leftarg = integer, rightarg = integer, + restrict = scalarltsel); + +-- views with leaky operator +CREATE VIEW atest12v AS + SELECT * FROM atest12 WHERE b <<< 5; +CREATE VIEW atest12sbv WITH (security_barrier=true) AS + SELECT * FROM atest12 WHERE b <<< 5; +GRANT SELECT ON atest12v TO PUBLIC; +GRANT SELECT ON atest12sbv TO PUBLIC; + +/* YB: TODO: investigate query plan differences between YB and postgres +-- This plan should use nestloop, knowing that few rows will be selected. +EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; + +-- And this one. +EXPLAIN (COSTS OFF) SELECT * FROM atest12 x, atest12 y + WHERE x.a = y.b and abs(y.a) <<< 5; + +-- This should also be a nestloop, but the security barrier forces the inner +-- scan to be materialized +EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y WHERE x.a = y.b; +*/ -- YB + +-- Check if regress_priv_user2 can break security. +SET SESSION AUTHORIZATION regress_priv_user2; + +CREATE FUNCTION leak2(integer,integer) RETURNS boolean + AS $$begin raise notice 'leak % %', $1, $2; return $1 > $2; end$$ + LANGUAGE plpgsql immutable; +CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer, + restrict = scalargtsel); + +-- This should not show any "leak" notices before failing. +EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0; + +/* YB: TODO: investigate query plan differences between YB and postgres +-- These plans should continue to use a nestloop, since they execute with the +-- privileges of the view owner. +EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; +EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y WHERE x.a = y.b; + +-- A non-security barrier view does not guard against information leakage. +EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y + WHERE x.a = y.b and abs(y.a) <<< 5; + +-- But a security barrier view isolates the leaky operator. +EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y + WHERE x.a = y.b and abs(y.a) <<< 5; +*/ -- YB + +-- Now regress_priv_user1 grants sufficient access to regress_priv_user2. +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT (a, b) ON atest12 TO PUBLIC; +SET SESSION AUTHORIZATION regress_priv_user2; + +/* YB: TODO: investigate query plan differences between YB and postgres +-- regress_priv_user2 should continue to get a good row estimate. +EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; + +-- But not for this, due to lack of table-wide permissions needed +-- to make use of the expression index's statistics. +EXPLAIN (COSTS OFF) SELECT * FROM atest12 x, atest12 y + WHERE x.a = y.b and abs(y.a) <<< 5; +*/ -- YB + +-- clean up (regress_priv_user1's objects are all dropped later) +DROP FUNCTION leak2(integer, integer) CASCADE; + + +-- groups + +SET SESSION AUTHORIZATION regress_priv_user3; +CREATE TABLE atest3 (one int, two int, three int); +GRANT DELETE ON atest3 TO GROUP regress_priv_group2; + +SET SESSION AUTHORIZATION regress_priv_user1; + +SELECT * FROM atest3; -- fail +DELETE FROM atest3; -- ok + +/* YB: avoid transaction because regress_priv_user1 privileges get inherited from regress_priv_group2 in further queries if not rolled back +BEGIN; +*/ -- YB +RESET SESSION AUTHORIZATION; +ALTER ROLE regress_priv_user1 NOINHERIT; +SET SESSION AUTHORIZATION regress_priv_user1; +DELETE FROM atest3; +/* YB: manually roll back changes due to lack of transaction +ROLLBACK; +*/ -- YB +RESET SESSION AUTHORIZATION; -- YB: manual rollback +ALTER ROLE regress_priv_user1 INHERIT; -- YB: manual rollback (see #1404) + +-- views + +SET SESSION AUTHORIZATION regress_priv_user3; + +CREATE VIEW atestv1 AS SELECT * FROM atest1; -- ok +/* The next *should* fail, but it's not implemented that way yet. */ +CREATE VIEW atestv2 AS SELECT * FROM atest2; +CREATE VIEW atestv3 AS SELECT * FROM atest3; -- ok +/* Empty view is a corner case that failed in 9.2. */ +CREATE VIEW atestv0 AS SELECT 0 as x WHERE false; -- ok + +SELECT * FROM atestv1; -- ok +SELECT * FROM atestv2; -- fail +GRANT SELECT ON atestv1, atestv3 TO regress_priv_user4; +GRANT SELECT ON atestv2 TO regress_priv_user2; + +SET SESSION AUTHORIZATION regress_priv_user4; + +SELECT * FROM atestv1; -- ok +SELECT * FROM atestv2; -- fail +SELECT * FROM atestv3; -- ok +SELECT * FROM atestv0; -- fail + +-- Appendrels excluded by constraints failed to check permissions in 8.4-9.2. +select * from + ((select a.q1 as x from int8_tbl a offset 0) + union all + (select b.q2 as x from int8_tbl b offset 0)) ss +where false; + +set constraint_exclusion = on; +select * from + ((select a.q1 as x, random() from int8_tbl a where q1 > 0) + union all + (select b.q2 as x, random() from int8_tbl b where q2 > 0)) ss +where x < 0; +reset constraint_exclusion; + +CREATE VIEW atestv4 AS SELECT * FROM atestv3; -- nested view +SELECT * FROM atestv4; -- ok +GRANT SELECT ON atestv4 TO regress_priv_user2; + +SET SESSION AUTHORIZATION regress_priv_user2; + +-- Two complex cases: + +SELECT * FROM atestv3; -- fail +SELECT * FROM atestv4; -- ok (even though regress_priv_user2 cannot access underlying atestv3) + +SELECT * FROM atest2; -- ok +SELECT * FROM atestv2; -- fail (even though regress_priv_user2 can access underlying atest2) + +-- Test column level permissions + +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE TABLE atest5 (one int, two int unique, three int, four int unique); +CREATE TABLE atest6 (one int, two int, blue int); +GRANT SELECT (one), INSERT (two), UPDATE (three) ON atest5 TO regress_priv_user4; +GRANT ALL (one) ON atest5 TO regress_priv_user3; + +INSERT INTO atest5 VALUES (1,2,3); + +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT * FROM atest5; -- fail +SELECT one FROM atest5; -- ok +COPY atest5 (one) TO stdout; -- ok +SELECT two FROM atest5; -- fail +COPY atest5 (two) TO stdout; -- fail +SELECT atest5 FROM atest5; -- fail +COPY atest5 (one,two) TO stdout; -- fail +SELECT 1 FROM atest5; -- ok +SELECT 1 FROM atest5 a JOIN atest5 b USING (one); -- ok +SELECT 1 FROM atest5 a JOIN atest5 b USING (two); -- fail +SELECT 1 FROM atest5 a NATURAL JOIN atest5 b; -- fail +SELECT * FROM (atest5 a JOIN atest5 b USING (one)) j; -- fail +SELECT j.* FROM (atest5 a JOIN atest5 b USING (one)) j; -- fail +SELECT (j.*) IS NULL FROM (atest5 a JOIN atest5 b USING (one)) j; -- fail +SELECT one FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- ok +SELECT j.one FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- ok +SELECT two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail +SELECT j.two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail +SELECT y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail +SELECT j.y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail +SELECT * FROM (atest5 a JOIN atest5 b USING (one)); -- fail +SELECT a.* FROM (atest5 a JOIN atest5 b USING (one)); -- fail +SELECT (a.*) IS NULL FROM (atest5 a JOIN atest5 b USING (one)); -- fail +SELECT two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail +SELECT a.two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail +SELECT y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail +SELECT b.y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail +SELECT y FROM (atest5 a LEFT JOIN atest5 b(one,x,y,z) USING (one)); -- fail +SELECT b.y FROM (atest5 a LEFT JOIN atest5 b(one,x,y,z) USING (one)); -- fail +SELECT y FROM (atest5 a FULL JOIN atest5 b(one,x,y,z) USING (one)); -- fail +SELECT b.y FROM (atest5 a FULL JOIN atest5 b(one,x,y,z) USING (one)); -- fail +SELECT 1 FROM atest5 WHERE two = 2; -- fail +SELECT * FROM atest1, atest5; -- fail +SELECT atest1.* FROM atest1, atest5; -- ok +SELECT atest1.*,atest5.one FROM atest1, atest5; -- ok +SELECT atest1.*,atest5.one FROM atest1 JOIN atest5 ON (atest1.a = atest5.two); -- fail +SELECT atest1.*,atest5.one FROM atest1 JOIN atest5 ON (atest1.a = atest5.one); -- ok +SELECT one, two FROM atest5; -- fail + +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT (one,two) ON atest6 TO regress_priv_user4; + +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT one, two FROM atest5 NATURAL JOIN atest6; -- fail still + +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT (two) ON atest5 TO regress_priv_user4; + +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT one, two FROM atest5 NATURAL JOIN atest6; -- ok now + +-- test column-level privileges for INSERT and UPDATE +INSERT INTO atest5 (two) VALUES (3); -- ok +COPY atest5 FROM stdin; -- fail +COPY atest5 (two) FROM stdin; -- ok +1 +\. +INSERT INTO atest5 (three) VALUES (4); -- fail +INSERT INTO atest5 VALUES (5,5,5); -- fail +UPDATE atest5 SET three = 10; -- ok +UPDATE atest5 SET one = 8; -- fail +UPDATE atest5 SET three = 5, one = 2; -- fail +-- Check that column level privs are enforced in RETURNING +-- Ok. +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10; +-- Error. No SELECT on column three. +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10 RETURNING atest5.three; +-- Ok. May SELECT on column "one": +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10 RETURNING atest5.one; +-- Check that column level privileges are enforced for EXCLUDED +-- Ok. we may select one +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = EXCLUDED.one; +-- Error. No select rights on three +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = EXCLUDED.three; +INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set one = 8; -- fails (due to UPDATE) +INSERT INTO atest5(three) VALUES (4) ON CONFLICT (two) DO UPDATE set three = 10; -- fails (due to INSERT) + +-- Check that the columns in the inference require select privileges +INSERT INTO atest5(four) VALUES (4); -- fail + +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT INSERT (four) ON atest5 TO regress_priv_user4; +SET SESSION AUTHORIZATION regress_priv_user4; + +INSERT INTO atest5(four) VALUES (4) ON CONFLICT (four) DO UPDATE set three = 3; -- fails (due to SELECT) +INSERT INTO atest5(four) VALUES (4) ON CONFLICT ON CONSTRAINT atest5_four_key DO UPDATE set three = 3; -- fails (due to SELECT) +INSERT INTO atest5(four) VALUES (4); -- ok + +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT (four) ON atest5 TO regress_priv_user4; +SET SESSION AUTHORIZATION regress_priv_user4; + +INSERT INTO atest5(four) VALUES (4) ON CONFLICT (four) DO UPDATE set three = 3; -- ok +INSERT INTO atest5(four) VALUES (4) ON CONFLICT ON CONSTRAINT atest5_four_key DO UPDATE set three = 3; -- ok + +SET SESSION AUTHORIZATION regress_priv_user1; +REVOKE ALL (one) ON atest5 FROM regress_priv_user4; +GRANT SELECT (one,two,blue) ON atest6 TO regress_priv_user4; + +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT one FROM atest5; -- fail +UPDATE atest5 SET one = 1; -- fail +SELECT atest6 FROM atest6; -- ok +COPY atest6 TO stdout; -- ok + +-- test column privileges with MERGE +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE TABLE mtarget (a int, b text); +CREATE TABLE msource (a int, b text); +INSERT INTO mtarget VALUES (1, 'init1'), (2, 'init2'); +INSERT INTO msource VALUES (1, 'source1'), (2, 'source2'), (3, 'source3'); + +GRANT SELECT (a) ON msource TO regress_priv_user4; +GRANT SELECT (a) ON mtarget TO regress_priv_user4; +GRANT INSERT (a,b) ON mtarget TO regress_priv_user4; +GRANT UPDATE (b) ON mtarget TO regress_priv_user4; + +SET SESSION AUTHORIZATION regress_priv_user4; + +-- +-- test source privileges +-- + +-- fail (no SELECT priv on s.b) +MERGE INTO mtarget t USING msource s ON t.a = s.a +WHEN MATCHED THEN + UPDATE SET b = s.b +WHEN NOT MATCHED THEN + INSERT VALUES (a, NULL); +-- YB: port further queries when above works + +-- check error reporting with column privs +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2)); +GRANT SELECT (c1) ON t1 TO regress_priv_user2; +GRANT INSERT (c1, c2, c3) ON t1 TO regress_priv_user2; +GRANT UPDATE (c1, c2, c3) ON t1 TO regress_priv_user2; + +-- seed data +INSERT INTO t1 VALUES (1, 1, 1); +INSERT INTO t1 VALUES (1, 2, 1); +INSERT INTO t1 VALUES (2, 1, 2); +INSERT INTO t1 VALUES (2, 2, 2); +INSERT INTO t1 VALUES (3, 1, 3); + +SET SESSION AUTHORIZATION regress_priv_user2; +INSERT INTO t1 (c1, c2) VALUES (1, 1); -- fail, but row not shown +UPDATE t1 SET c2 = 1; -- fail, but row not shown +INSERT INTO t1 (c1, c2) VALUES (null, null); -- fail, but see columns being inserted +INSERT INTO t1 (c3) VALUES (null); -- fail, but see columns being inserted or have SELECT +INSERT INTO t1 (c1) VALUES (5); -- fail, but see columns being inserted or have SELECT +UPDATE t1 SET c3 = 10; -- fail, but see columns with SELECT rights, or being modified + +SET SESSION AUTHORIZATION regress_priv_user1; +DROP TABLE t1; + +-- check error reporting with column privs on a partitioned table +CREATE TABLE errtst(a text, b text NOT NULL, c text, secret1 text, secret2 text) PARTITION BY LIST (a); +CREATE TABLE errtst_part_1(secret2 text, c text, a text, b text NOT NULL, secret1 text); +CREATE TABLE errtst_part_2(secret1 text, secret2 text, a text, c text, b text NOT NULL); + +ALTER TABLE errtst ATTACH PARTITION errtst_part_1 FOR VALUES IN ('aaa'); +ALTER TABLE errtst ATTACH PARTITION errtst_part_2 FOR VALUES IN ('aaaa'); + +GRANT SELECT (a, b, c) ON TABLE errtst TO regress_priv_user2; +GRANT UPDATE (a, b, c) ON TABLE errtst TO regress_priv_user2; +GRANT INSERT (a, b, c) ON TABLE errtst TO regress_priv_user2; + +INSERT INTO errtst_part_1 (a, b, c, secret1, secret2) +VALUES ('aaa', 'bbb', 'ccc', 'the body', 'is in the attic'); + +SET SESSION AUTHORIZATION regress_priv_user2; + +-- Perform a few updates that violate the NOT NULL constraint. Make sure +-- the error messages don't leak the secret fields. + +-- simple insert. +INSERT INTO errtst (a, b) VALUES ('aaa', NULL); +-- simple update. +UPDATE errtst SET b = NULL; +-- partitioning key is updated, doesn't move the row. +UPDATE errtst SET a = 'aaa', b = NULL; +-- row is moved to another partition. +UPDATE errtst SET a = 'aaaa', b = NULL; + +-- row is moved to another partition. This differs from the previous case in +-- that the new partition is excluded by constraint exclusion, so its +-- ResultRelInfo is not created at ExecInitModifyTable, but needs to be +-- constructed on the fly when the updated tuple is routed to it. +UPDATE errtst SET a = 'aaaa', b = NULL WHERE a = 'aaa'; + +SET SESSION AUTHORIZATION regress_priv_user1; +DROP TABLE errtst; + +-- test column-level privileges when involved with DELETE +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER TABLE atest6 ADD COLUMN three integer; +GRANT DELETE ON atest5 TO regress_priv_user3; +GRANT SELECT (two) ON atest5 TO regress_priv_user3; +REVOKE ALL (one) ON atest5 FROM regress_priv_user3; +GRANT SELECT (one) ON atest5 TO regress_priv_user4; + +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT atest6 FROM atest6; -- fail +SELECT one FROM atest5 NATURAL JOIN atest6; -- fail + +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER TABLE atest6 DROP COLUMN three; + +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT atest6 FROM atest6; -- ok +SELECT one FROM atest5 NATURAL JOIN atest6; -- ok + +SET SESSION AUTHORIZATION regress_priv_user1; +ALTER TABLE atest6 DROP COLUMN two; +REVOKE SELECT (one,blue) ON atest6 FROM regress_priv_user4; + +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT * FROM atest6; -- fail +SELECT 1 FROM atest6; -- fail + +SET SESSION AUTHORIZATION regress_priv_user3; +DELETE FROM atest5 WHERE one = 1; -- fail +DELETE FROM atest5 WHERE two = 2; -- ok + +-- check inheritance cases +SET SESSION AUTHORIZATION regress_priv_user1; +CREATE TABLE atestp1 (f1 int, f2 int); +CREATE TABLE atestp2 (fx int, fy int); +CREATE TABLE atestc (fz int) INHERITS (atestp1, atestp2); +GRANT SELECT(fx,fy,tableoid) ON atestp2 TO regress_priv_user2; +GRANT SELECT(fx) ON atestc TO regress_priv_user2; + +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT fx FROM atestp2; -- ok +SELECT fy FROM atestp2; -- ok +SELECT atestp2 FROM atestp2; -- ok +SELECT tableoid FROM atestp2; -- ok +SELECT fy FROM atestc; -- fail + +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT(fy,tableoid) ON atestc TO regress_priv_user2; + +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT fx FROM atestp2; -- still ok +SELECT fy FROM atestp2; -- ok +SELECT atestp2 FROM atestp2; -- ok +SELECT tableoid FROM atestp2; -- ok + +-- child's permissions do not apply when operating on parent +SET SESSION AUTHORIZATION regress_priv_user1; +REVOKE ALL ON atestc FROM regress_priv_user2; +GRANT ALL ON atestp1 TO regress_priv_user2; +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT f2 FROM atestp1; -- ok +SELECT f2 FROM atestc; -- fail +DELETE FROM atestp1; -- ok +DELETE FROM atestc; -- fail +UPDATE atestp1 SET f1 = 1; -- ok +UPDATE atestc SET f1 = 1; -- fail +TRUNCATE atestp1; -- ok +TRUNCATE atestc; -- fail +BEGIN; +LOCK atestp1; +END; +BEGIN; +LOCK atestc; +END; + +-- privileges on functions, languages + +-- switch to superuser +\c - + +REVOKE ALL PRIVILEGES ON LANGUAGE sql FROM PUBLIC; +GRANT USAGE ON LANGUAGE sql TO regress_priv_user1; -- ok +GRANT USAGE ON LANGUAGE c TO PUBLIC; -- fail + +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT USAGE ON LANGUAGE sql TO regress_priv_user2; -- fail +CREATE FUNCTION priv_testfunc1(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql; +CREATE FUNCTION priv_testfunc2(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql; +CREATE AGGREGATE priv_testagg1(int) (sfunc = int4pl, stype = int4); +CREATE PROCEDURE priv_testproc1(int) AS 'select $1;' LANGUAGE sql; + +REVOKE ALL ON FUNCTION priv_testfunc1(int), priv_testfunc2(int), priv_testagg1(int) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION priv_testfunc1(int), priv_testfunc2(int), priv_testagg1(int) TO regress_priv_user2; +REVOKE ALL ON FUNCTION priv_testproc1(int) FROM PUBLIC; -- fail, not a function +REVOKE ALL ON PROCEDURE priv_testproc1(int) FROM PUBLIC; +GRANT EXECUTE ON PROCEDURE priv_testproc1(int) TO regress_priv_user2; +GRANT USAGE ON FUNCTION priv_testfunc1(int) TO regress_priv_user3; -- semantic error +GRANT USAGE ON FUNCTION priv_testagg1(int) TO regress_priv_user3; -- semantic error +GRANT USAGE ON PROCEDURE priv_testproc1(int) TO regress_priv_user3; -- semantic error +GRANT ALL PRIVILEGES ON FUNCTION priv_testfunc1(int) TO regress_priv_user4; +GRANT ALL PRIVILEGES ON FUNCTION priv_testfunc_nosuch(int) TO regress_priv_user4; +GRANT ALL PRIVILEGES ON FUNCTION priv_testagg1(int) TO regress_priv_user4; +GRANT ALL PRIVILEGES ON PROCEDURE priv_testproc1(int) TO regress_priv_user4; + +CREATE FUNCTION priv_testfunc4(boolean) RETURNS text + AS 'select col1 from atest2 where col2 = $1;' + LANGUAGE sql SECURITY DEFINER; +GRANT EXECUTE ON FUNCTION priv_testfunc4(boolean) TO regress_priv_user3; + +SET SESSION AUTHORIZATION regress_priv_user2; +SELECT priv_testfunc1(5), priv_testfunc2(5); -- ok +CREATE FUNCTION priv_testfunc3(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql; -- fail +SELECT priv_testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- ok +CALL priv_testproc1(6); -- ok + +SET SESSION AUTHORIZATION regress_priv_user3; +SELECT priv_testfunc1(5); -- fail +SELECT priv_testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- fail +CALL priv_testproc1(6); -- fail +SELECT col1 FROM atest2 WHERE col2 = true; -- fail +SELECT priv_testfunc4(true); -- ok + +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT priv_testfunc1(5); -- ok +SELECT priv_testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- ok +CALL priv_testproc1(6); -- ok + +DROP FUNCTION priv_testfunc1(int); -- fail +DROP AGGREGATE priv_testagg1(int); -- fail +DROP PROCEDURE priv_testproc1(int); -- fail + +\c - + +DROP FUNCTION priv_testfunc1(int); -- ok +-- restore to sanity +GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC; + +-- verify privilege checks on array-element coercions +/* YB: avoid transaction because using int8 throws error in further queries if not rolled back +BEGIN; +*/ -- YB +SELECT '{1}'::int4[]::int8[]; +REVOKE ALL ON FUNCTION int8(integer) FROM PUBLIC; +SELECT '{1}'::int4[]::int8[]; --superuser, succeed +SET SESSION AUTHORIZATION regress_priv_user4; +SELECT '{1}'::int4[]::int8[]; --other user, fail +/* YB: manually roll back changes due to lack of transaction +ROLLBACK; +*/ -- YB + +RESET SESSION AUTHORIZATION; -- YB: manual rollback +GRANT ALL ON FUNCTION int8(integer) TO PUBLIC; -- YB: manual rollback +SET SESSION AUTHORIZATION regress_priv_user4; -- YB: manual rollback (see #1404) +-- privileges on types + +-- switch to superuser +\c - + +CREATE TYPE priv_testtype1 AS (a int, b text); +REVOKE USAGE ON TYPE priv_testtype1 FROM PUBLIC; +GRANT USAGE ON TYPE priv_testtype1 TO regress_priv_user2; +GRANT USAGE ON TYPE _priv_testtype1 TO regress_priv_user2; -- fail +GRANT USAGE ON DOMAIN priv_testtype1 TO regress_priv_user2; -- fail + +CREATE DOMAIN priv_testdomain1 AS int; +REVOKE USAGE on DOMAIN priv_testdomain1 FROM PUBLIC; +GRANT USAGE ON DOMAIN priv_testdomain1 TO regress_priv_user2; +GRANT USAGE ON TYPE priv_testdomain1 TO regress_priv_user2; -- ok + +SET SESSION AUTHORIZATION regress_priv_user1; + +-- commands that should fail + +CREATE AGGREGATE priv_testagg1a(priv_testdomain1) (sfunc = int4_sum, stype = bigint); + +CREATE DOMAIN priv_testdomain2a AS priv_testdomain1; + +CREATE DOMAIN priv_testdomain3a AS int; +CREATE FUNCTION castfunc(int) RETURNS priv_testdomain3a AS $$ SELECT $1::priv_testdomain3a $$ LANGUAGE SQL; +CREATE CAST (priv_testdomain1 AS priv_testdomain3a) WITH FUNCTION castfunc(int); +DROP FUNCTION castfunc(int) CASCADE; +DROP DOMAIN priv_testdomain3a; + +CREATE FUNCTION priv_testfunc5a(a priv_testdomain1) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; +CREATE FUNCTION priv_testfunc6a(b int) RETURNS priv_testdomain1 LANGUAGE SQL AS $$ SELECT $1::priv_testdomain1 $$; + +CREATE OPERATOR !+! (PROCEDURE = int4pl, LEFTARG = priv_testdomain1, RIGHTARG = priv_testdomain1); + +CREATE TABLE test5a (a int, b priv_testdomain1); +CREATE TABLE test6a OF priv_testtype1; +CREATE TABLE test10a (a int[], b priv_testtype1[]); + +CREATE TABLE test9a (a int, b int); +ALTER TABLE test9a ADD COLUMN c priv_testdomain1; +ALTER TABLE test9a ALTER COLUMN b TYPE priv_testdomain1; + +CREATE TYPE test7a AS (a int, b priv_testdomain1); + +CREATE TYPE test8a AS (a int, b int); +ALTER TYPE test8a ADD ATTRIBUTE c priv_testdomain1; +ALTER TYPE test8a ALTER ATTRIBUTE b TYPE priv_testdomain1; + +CREATE TABLE test11a AS (SELECT 1::priv_testdomain1 AS a); + +REVOKE ALL ON TYPE priv_testtype1 FROM PUBLIC; + +SET SESSION AUTHORIZATION regress_priv_user2; + +-- commands that should succeed + +CREATE AGGREGATE priv_testagg1b(priv_testdomain1) (sfunc = int4_sum, stype = bigint); + +CREATE DOMAIN priv_testdomain2b AS priv_testdomain1; + +CREATE DOMAIN priv_testdomain3b AS int; +CREATE FUNCTION castfunc(int) RETURNS priv_testdomain3b AS $$ SELECT $1::priv_testdomain3b $$ LANGUAGE SQL; +CREATE CAST (priv_testdomain1 AS priv_testdomain3b) WITH FUNCTION castfunc(int); + +CREATE FUNCTION priv_testfunc5b(a priv_testdomain1) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; +CREATE FUNCTION priv_testfunc6b(b int) RETURNS priv_testdomain1 LANGUAGE SQL AS $$ SELECT $1::priv_testdomain1 $$; + +CREATE OPERATOR !! (PROCEDURE = priv_testfunc5b, RIGHTARG = priv_testdomain1); + +CREATE TABLE test5b (a int, b priv_testdomain1); +CREATE TABLE test6b OF priv_testtype1; +CREATE TABLE test10b (a int[], b priv_testtype1[]); + +CREATE TABLE test9b (a int, b int); +ALTER TABLE test9b ADD COLUMN c priv_testdomain1; +ALTER TABLE test9b ALTER COLUMN b TYPE priv_testdomain1; + +CREATE TYPE test7b AS (a int, b priv_testdomain1); + +CREATE TYPE test8b AS (a int, b int); +ALTER TYPE test8b ADD ATTRIBUTE c priv_testdomain1; +ALTER TYPE test8b ALTER ATTRIBUTE b TYPE priv_testdomain1; + +CREATE TABLE test11b AS (SELECT 1::priv_testdomain1 AS a); + +REVOKE ALL ON TYPE priv_testtype1 FROM PUBLIC; + +\c - +DROP AGGREGATE priv_testagg1b(priv_testdomain1); +DROP DOMAIN priv_testdomain2b; +DROP OPERATOR !! (NONE, priv_testdomain1); +DROP FUNCTION priv_testfunc5b(a priv_testdomain1); +DROP FUNCTION priv_testfunc6b(b int); +DROP TABLE test5b; +DROP TABLE test6b; +DROP TABLE test9b; +DROP TABLE test10b; +DROP TYPE test7b; +DROP TYPE test8b; +DROP CAST (priv_testdomain1 AS priv_testdomain3b); +DROP FUNCTION castfunc(int) CASCADE; +DROP DOMAIN priv_testdomain3b; +DROP TABLE test11b; + +DROP TYPE priv_testtype1; -- ok +DROP DOMAIN priv_testdomain1; -- ok + + +-- truncate +SET SESSION AUTHORIZATION regress_priv_user5; +TRUNCATE atest2; -- ok +TRUNCATE atest3; -- fail + +-- has_table_privilege function + +-- bad-input checks +select has_table_privilege(NULL,'pg_authid','select'); +select has_table_privilege('pg_shad','select'); +select has_table_privilege('nosuchuser','pg_authid','select'); +select has_table_privilege('pg_authid','sel'); +select has_table_privilege(-999999,'pg_authid','update'); +select has_table_privilege(1,'select'); + +-- superuser +\c - + +select has_table_privilege(current_user,'pg_authid','select'); +select has_table_privilege(current_user,'pg_authid','insert'); + +select has_table_privilege(t2.oid,'pg_authid','update') +from (select oid from pg_roles where rolname = current_user) as t2; +select has_table_privilege(t2.oid,'pg_authid','delete') +from (select oid from pg_roles where rolname = current_user) as t2; + +-- 'rule' privilege no longer exists, but for backwards compatibility +-- has_table_privilege still recognizes the keyword and says FALSE +select has_table_privilege(current_user,t1.oid,'rule') +from (select oid from pg_class where relname = 'pg_authid') as t1; +select has_table_privilege(current_user,t1.oid,'references') +from (select oid from pg_class where relname = 'pg_authid') as t1; + +select has_table_privilege(t2.oid,t1.oid,'select') +from (select oid from pg_class where relname = 'pg_authid') as t1, + (select oid from pg_roles where rolname = current_user) as t2; +select has_table_privilege(t2.oid,t1.oid,'insert') +from (select oid from pg_class where relname = 'pg_authid') as t1, + (select oid from pg_roles where rolname = current_user) as t2; + +select has_table_privilege('pg_authid','update'); +select has_table_privilege('pg_authid','delete'); +select has_table_privilege('pg_authid','truncate'); + +select has_table_privilege(t1.oid,'select') +from (select oid from pg_class where relname = 'pg_authid') as t1; +select has_table_privilege(t1.oid,'trigger') +from (select oid from pg_class where relname = 'pg_authid') as t1; + +-- non-superuser +SET SESSION AUTHORIZATION regress_priv_user3; + +select has_table_privilege(current_user,'pg_class','select'); +select has_table_privilege(current_user,'pg_class','insert'); + +select has_table_privilege(t2.oid,'pg_class','update') +from (select oid from pg_roles where rolname = current_user) as t2; +select has_table_privilege(t2.oid,'pg_class','delete') +from (select oid from pg_roles where rolname = current_user) as t2; + +select has_table_privilege(current_user,t1.oid,'references') +from (select oid from pg_class where relname = 'pg_class') as t1; + +select has_table_privilege(t2.oid,t1.oid,'select') +from (select oid from pg_class where relname = 'pg_class') as t1, + (select oid from pg_roles where rolname = current_user) as t2; +select has_table_privilege(t2.oid,t1.oid,'insert') +from (select oid from pg_class where relname = 'pg_class') as t1, + (select oid from pg_roles where rolname = current_user) as t2; + +select has_table_privilege('pg_class','update'); +select has_table_privilege('pg_class','delete'); +select has_table_privilege('pg_class','truncate'); + +select has_table_privilege(t1.oid,'select') +from (select oid from pg_class where relname = 'pg_class') as t1; +select has_table_privilege(t1.oid,'trigger') +from (select oid from pg_class where relname = 'pg_class') as t1; + +select has_table_privilege(current_user,'atest1','select'); +select has_table_privilege(current_user,'atest1','insert'); + +select has_table_privilege(t2.oid,'atest1','update') +from (select oid from pg_roles where rolname = current_user) as t2; +select has_table_privilege(t2.oid,'atest1','delete') +from (select oid from pg_roles where rolname = current_user) as t2; + +select has_table_privilege(current_user,t1.oid,'references') +from (select oid from pg_class where relname = 'atest1') as t1; + +select has_table_privilege(t2.oid,t1.oid,'select') +from (select oid from pg_class where relname = 'atest1') as t1, + (select oid from pg_roles where rolname = current_user) as t2; +select has_table_privilege(t2.oid,t1.oid,'insert') +from (select oid from pg_class where relname = 'atest1') as t1, + (select oid from pg_roles where rolname = current_user) as t2; + +select has_table_privilege('atest1','update'); +select has_table_privilege('atest1','delete'); +select has_table_privilege('atest1','truncate'); + +select has_table_privilege(t1.oid,'select') +from (select oid from pg_class where relname = 'atest1') as t1; +select has_table_privilege(t1.oid,'trigger') +from (select oid from pg_class where relname = 'atest1') as t1; + +-- has_column_privilege function + +-- bad-input checks (as non-super-user) +select has_column_privilege('pg_authid',NULL,'select'); +select has_column_privilege('pg_authid','nosuchcol','select'); +select has_column_privilege(9999,'nosuchcol','select'); +select has_column_privilege(9999,99::int2,'select'); +select has_column_privilege('pg_authid',99::int2,'select'); +select has_column_privilege(9999,99::int2,'select'); + +create temp table mytable(f1 int, f2 int, f3 int); +alter table mytable drop column f2; +select has_column_privilege('mytable','f2','select'); +select has_column_privilege('mytable','........pg.dropped.2........','select'); +select has_column_privilege('mytable',2::int2,'select'); +select has_column_privilege('mytable',99::int2,'select'); +revoke select on table mytable from regress_priv_user3; +select has_column_privilege('mytable',2::int2,'select'); +select has_column_privilege('mytable',99::int2,'select'); +drop table mytable; + +-- Grant options + +SET SESSION AUTHORIZATION regress_priv_user1; + +CREATE TABLE atest4 (a int); + +GRANT SELECT ON atest4 TO regress_priv_user2 WITH GRANT OPTION; +GRANT UPDATE ON atest4 TO regress_priv_user2; +GRANT SELECT ON atest4 TO GROUP regress_priv_group1 WITH GRANT OPTION; + +SET SESSION AUTHORIZATION regress_priv_user2; + +GRANT SELECT ON atest4 TO regress_priv_user3; +GRANT UPDATE ON atest4 TO regress_priv_user3; -- fail + +SET SESSION AUTHORIZATION regress_priv_user1; + +REVOKE SELECT ON atest4 FROM regress_priv_user3; -- does nothing +SELECT has_table_privilege('regress_priv_user3', 'atest4', 'SELECT'); -- true +REVOKE SELECT ON atest4 FROM regress_priv_user2; -- fail +REVOKE GRANT OPTION FOR SELECT ON atest4 FROM regress_priv_user2 CASCADE; -- ok +SELECT has_table_privilege('regress_priv_user2', 'atest4', 'SELECT'); -- true +SELECT has_table_privilege('regress_priv_user3', 'atest4', 'SELECT'); -- false + +SELECT has_table_privilege('regress_priv_user1', 'atest4', 'SELECT WITH GRANT OPTION'); -- true + + +-- security-restricted operations +\c - +CREATE ROLE regress_sro_user; + +-- Check that index expressions and predicates are run as the table's owner + +-- A dummy index function checking current_user +CREATE FUNCTION sro_ifun(int) RETURNS int AS $$ +BEGIN + -- Below we set the table's owner to regress_sro_user + ASSERT current_user = 'regress_sro_user', + format('sro_ifun(%s) called by %s', $1, current_user); + RETURN $1; +END; +$$ LANGUAGE plpgsql IMMUTABLE; +-- Create a table owned by regress_sro_user +CREATE TABLE sro_tab (a int); +ALTER TABLE sro_tab OWNER TO regress_sro_user; +INSERT INTO sro_tab VALUES (1), (2), (3); +-- Create an expression index with a predicate +CREATE INDEX sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0))) + WHERE sro_ifun(a + 10) > sro_ifun(10); +DROP INDEX sro_idx; +-- Do the same concurrently +CREATE INDEX CONCURRENTLY sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0))) + WHERE sro_ifun(a + 10) > sro_ifun(10); +-- REINDEX +REINDEX TABLE sro_tab; +REINDEX INDEX sro_idx; +REINDEX TABLE CONCURRENTLY sro_tab; +DROP INDEX sro_idx; +-- CLUSTER +CREATE INDEX sro_cluster_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0))); +CLUSTER sro_tab USING sro_cluster_idx; +DROP INDEX sro_cluster_idx; +-- BRIN index +CREATE INDEX sro_brin ON sro_tab USING brin ((sro_ifun(a) + sro_ifun(0))); +-- YB: port further queries when above works +-- Check with a partitioned table +CREATE TABLE sro_ptab (a int) PARTITION BY RANGE (a); +ALTER TABLE sro_ptab OWNER TO regress_sro_user; +CREATE TABLE sro_part PARTITION OF sro_ptab FOR VALUES FROM (1) TO (10); +ALTER TABLE sro_part OWNER TO regress_sro_user; +INSERT INTO sro_ptab VALUES (1), (2), (3); +CREATE INDEX sro_pidx ON sro_ptab ((sro_ifun(a) + sro_ifun(0))) + WHERE sro_ifun(a + 10) > sro_ifun(10); +REINDEX TABLE sro_ptab; +REINDEX INDEX CONCURRENTLY sro_pidx; + +SET SESSION AUTHORIZATION regress_sro_user; +CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS + 'GRANT regress_priv_group2 TO regress_sro_user'; +CREATE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS + 'DECLARE c CURSOR WITH HOLD FOR SELECT unwanted_grant(); SELECT true'; +-- REFRESH of this MV will queue a GRANT at end of transaction +CREATE MATERIALIZED VIEW sro_mv AS SELECT mv_action() WITH NO DATA; +REFRESH MATERIALIZED VIEW sro_mv; +\c - +REFRESH MATERIALIZED VIEW sro_mv; + +SET SESSION AUTHORIZATION regress_sro_user; +-- INSERT to this table will queue a GRANT at end of transaction +CREATE TABLE sro_trojan_table (); +CREATE FUNCTION sro_trojan() RETURNS trigger LANGUAGE plpgsql AS + 'BEGIN PERFORM unwanted_grant(); RETURN NULL; END'; +CREATE CONSTRAINT TRIGGER t AFTER INSERT ON sro_trojan_table + INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE sro_trojan(); +-- YB: port the remaining queries when the above is fixed + +-- Admin options + +SET SESSION AUTHORIZATION regress_priv_user4; +CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS + 'GRANT regress_priv_group2 TO regress_priv_user5'; +GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION +SET ROLE regress_priv_group2; +GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege + +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no ADMIN OPTION +SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN +SET ROLE regress_priv_group2; +GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help + +SET SESSION AUTHORIZATION regress_priv_group2; +GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin + +SET SESSION AUTHORIZATION regress_priv_user4; +DROP FUNCTION dogrant_ok(); +REVOKE regress_priv_group2 FROM regress_priv_user5; + + +-- has_sequence_privilege tests +\c - + +CREATE SEQUENCE x_seq; + +GRANT USAGE on x_seq to regress_priv_user2; + +SELECT has_sequence_privilege('regress_priv_user1', 'atest1', 'SELECT'); +SELECT has_sequence_privilege('regress_priv_user1', 'x_seq', 'INSERT'); +SELECT has_sequence_privilege('regress_priv_user1', 'x_seq', 'SELECT'); + +SET SESSION AUTHORIZATION regress_priv_user2; + +SELECT has_sequence_privilege('x_seq', 'USAGE'); + +-- largeobject privilege tests +\c - +SET SESSION AUTHORIZATION regress_priv_user1; + +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT lo_create(1001); +SELECT lo_create(1002); +SELECT lo_create(1003); +SELECT lo_create(1004); +SELECT lo_create(1005); +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject + +GRANT ALL ON LARGE OBJECT 1001 TO PUBLIC; +GRANT SELECT ON LARGE OBJECT 1003 TO regress_priv_user2; +GRANT SELECT,UPDATE ON LARGE OBJECT 1004 TO regress_priv_user2; +GRANT ALL ON LARGE OBJECT 1005 TO regress_priv_user2; +GRANT SELECT ON LARGE OBJECT 1005 TO regress_priv_user2 WITH GRANT OPTION; + +GRANT SELECT, INSERT ON LARGE OBJECT 1001 TO PUBLIC; -- to be failed +GRANT SELECT, UPDATE ON LARGE OBJECT 1001 TO nosuchuser; -- to be failed +GRANT SELECT, UPDATE ON LARGE OBJECT 999 TO PUBLIC; -- to be failed + +\c - +SET SESSION AUTHORIZATION regress_priv_user2; + +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT lo_create(2001); +SELECT lo_create(2002); + +SELECT loread(lo_open(1001, x'20000'::int), 32); -- allowed, for now +SELECT lowrite(lo_open(1001, x'40000'::int), 'abcd'); -- fail, wrong mode + +SELECT loread(lo_open(1001, x'40000'::int), 32); +SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied +SELECT loread(lo_open(1003, x'40000'::int), 32); +SELECT loread(lo_open(1004, x'40000'::int), 32); + +SELECT lowrite(lo_open(1001, x'20000'::int), 'abcd'); +SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied +SELECT lowrite(lo_open(1003, x'20000'::int), 'abcd'); -- to be denied +SELECT lowrite(lo_open(1004, x'20000'::int), 'abcd'); +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject + +GRANT SELECT ON LARGE OBJECT 1005 TO regress_priv_user3; +GRANT UPDATE ON LARGE OBJECT 1006 TO regress_priv_user3; -- to be denied +REVOKE ALL ON LARGE OBJECT 2001, 2002 FROM PUBLIC; +GRANT ALL ON LARGE OBJECT 2001 TO regress_priv_user3; + +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT lo_unlink(1001); -- to be denied +SELECT lo_unlink(2002); +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject + +\c - +-- confirm ACL setting +SELECT oid, pg_get_userbyid(lomowner) ownername, lomacl FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid; + +SET SESSION AUTHORIZATION regress_priv_user3; + +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT loread(lo_open(1001, x'40000'::int), 32); +SELECT loread(lo_open(1003, x'40000'::int), 32); -- to be denied +SELECT loread(lo_open(1005, x'40000'::int), 32); + +SELECT lo_truncate(lo_open(1005, x'20000'::int), 10); -- to be denied +SELECT lo_truncate(lo_open(2001, x'20000'::int), 10); +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject + +-- compatibility mode in largeobject permission +\c - +SET lo_compat_privileges = false; -- default setting +SET SESSION AUTHORIZATION regress_priv_user4; + +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied +SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied +SELECT lo_truncate(lo_open(1002, x'20000'::int), 10); -- to be denied +SELECT lo_put(1002, 1, 'abcd'); -- to be denied +SELECT lo_unlink(1002); -- to be denied +SELECT lo_export(1001, '/dev/null'); -- to be denied +SELECT lo_import('/dev/null'); -- to be denied +SELECT lo_import('/dev/null', 2003); -- to be denied +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject + +\c - +SET lo_compat_privileges = true; -- compatibility mode +SET SESSION AUTHORIZATION regress_priv_user4; + +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT loread(lo_open(1002, x'40000'::int), 32); +SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); +SELECT lo_truncate(lo_open(1002, x'20000'::int), 10); +SELECT lo_unlink(1002); +SELECT lo_export(1001, '/dev/null'); -- to be denied +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject + +-- don't allow unpriv users to access pg_largeobject contents +\c - +SELECT * FROM pg_largeobject LIMIT 0; + +SET SESSION AUTHORIZATION regress_priv_user1; +SELECT * FROM pg_largeobject LIMIT 0; -- to be denied +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject + +-- pg_signal_backend can't signal superusers +RESET SESSION AUTHORIZATION; +BEGIN; +CREATE OR REPLACE FUNCTION terminate_nothrow(pid int) RETURNS bool + LANGUAGE plpgsql SECURITY DEFINER SET client_min_messages = error AS $$ +BEGIN + RETURN pg_terminate_backend($1); +EXCEPTION WHEN OTHERS THEN + RETURN false; +END$$; +ALTER FUNCTION terminate_nothrow OWNER TO pg_signal_backend; +SELECT backend_type FROM pg_stat_activity +WHERE CASE WHEN COALESCE(usesysid, 10) = 10 THEN terminate_nothrow(pid) END; +ROLLBACK; + +-- test pg_database_owner +RESET SESSION AUTHORIZATION; +GRANT pg_database_owner TO regress_priv_user1; +GRANT regress_priv_user1 TO pg_database_owner; +CREATE TABLE datdba_only (); +ALTER TABLE datdba_only OWNER TO pg_database_owner; +REVOKE DELETE ON datdba_only FROM pg_database_owner; +SELECT + pg_has_role('regress_priv_user1', 'pg_database_owner', 'USAGE') as priv, + pg_has_role('regress_priv_user1', 'pg_database_owner', 'MEMBER') as mem, + pg_has_role('regress_priv_user1', 'pg_database_owner', + 'MEMBER WITH ADMIN OPTION') as admin; + +/* YB: avoid transaction because not rolling back owner change causes discrepancy in further queries +BEGIN; +*/ -- YB +DO $$BEGIN EXECUTE format( + 'ALTER DATABASE %I OWNER TO regress_priv_group2', current_catalog); END$$; +SELECT + pg_has_role('regress_priv_user1', 'pg_database_owner', 'USAGE') as priv, + pg_has_role('regress_priv_user1', 'pg_database_owner', 'MEMBER') as mem, + pg_has_role('regress_priv_user1', 'pg_database_owner', + 'MEMBER WITH ADMIN OPTION') as admin; +SET SESSION AUTHORIZATION regress_priv_user1; +TABLE information_schema.enabled_roles ORDER BY role_name COLLATE "C"; +TABLE information_schema.applicable_roles ORDER BY role_name COLLATE "C"; +INSERT INTO datdba_only DEFAULT VALUES; +/* YB: avoid transaction: SAVEPOINT q; */ DELETE FROM datdba_only; /* YB: avoid transaction: ROLLBACK to q; */ +SET SESSION AUTHORIZATION regress_priv_user2; +TABLE information_schema.enabled_roles; +INSERT INTO datdba_only DEFAULT VALUES; +/* YB: manually roll back changes due to lack of transaction +ROLLBACK; +*/ -- YB + +RESET SESSION AUTHORIZATION; -- YB: manual rollback +DO $$BEGIN EXECUTE format( -- YB: manual rollback + 'ALTER DATABASE %I OWNER TO postgres', current_catalog); END$$; -- YB: manual rollback (see #1404) +-- test default ACLs +\c - + +CREATE SCHEMA testns; +GRANT ALL ON SCHEMA testns TO regress_priv_user1; + +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- no +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no + +-- placeholder for test with duplicated schema and role names +ALTER DEFAULT PRIVILEGES IN SCHEMA testns,testns GRANT SELECT ON TABLES TO public,public; + +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- no +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); + +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- yes +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT INSERT ON TABLES TO regress_priv_user1; + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); + +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- yes +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- yes + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns REVOKE INSERT ON TABLES FROM regress_priv_user1; + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); + +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- yes +SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no + +ALTER DEFAULT PRIVILEGES FOR ROLE regress_priv_user1 REVOKE EXECUTE ON FUNCTIONS FROM public; + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT USAGE ON SCHEMAS TO regress_priv_user2; -- error + +-- +-- Testing blanket default grants is very hazardous since it might change +-- the privileges attached to objects created by concurrent regression tests. +-- To avoid that, be sure to revoke the privileges again before committing. +-- +BEGIN; + +ALTER DEFAULT PRIVILEGES GRANT USAGE ON SCHEMAS TO regress_priv_user2; + +CREATE SCHEMA testns2; + +SELECT has_schema_privilege('regress_priv_user2', 'testns2', 'USAGE'); -- yes +SELECT has_schema_privilege('regress_priv_user6', 'testns2', 'USAGE'); -- yes +SELECT has_schema_privilege('regress_priv_user2', 'testns2', 'CREATE'); -- no + +ALTER DEFAULT PRIVILEGES REVOKE USAGE ON SCHEMAS FROM regress_priv_user2; + +CREATE SCHEMA testns3; + +SELECT has_schema_privilege('regress_priv_user2', 'testns3', 'USAGE'); -- no +SELECT has_schema_privilege('regress_priv_user2', 'testns3', 'CREATE'); -- no + +ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_priv_user2; + +CREATE SCHEMA testns4; + +SELECT has_schema_privilege('regress_priv_user2', 'testns4', 'USAGE'); -- yes +SELECT has_schema_privilege('regress_priv_user2', 'testns4', 'CREATE'); -- yes + +ALTER DEFAULT PRIVILEGES REVOKE ALL ON SCHEMAS FROM regress_priv_user2; + +COMMIT; + +-- Test for DROP OWNED BY with shared dependencies. This is done in a +-- separate, rollbacked, transaction to avoid any trouble with other +-- regression sessions. +-- YB: TODO(#1404): The ROLLBACK statement in this transaction will not actually revert the DROP of large object 2001 after DROP OWNED BY regress_priv_user2, regress_priv_user2. +-- YB: if it is owned by regress_priv_user2. This object is needed during a later SELECT lo_unlink(oid). Change the owner away from regress_priv_user2 to avoid dropping the object in the first place. +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLS to pg_largeobject +ALTER LARGE OBJECT 2001 OWNER TO postgres; -- YB: change owner to avoid drop +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLS to pg_largeobject +BEGIN; +ALTER DEFAULT PRIVILEGES GRANT ALL ON FUNCTIONS TO regress_priv_user2; +ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_priv_user2; +ALTER DEFAULT PRIVILEGES GRANT ALL ON SEQUENCES TO regress_priv_user2; +ALTER DEFAULT PRIVILEGES GRANT ALL ON TABLES TO regress_priv_user2; +ALTER DEFAULT PRIVILEGES GRANT ALL ON TYPES TO regress_priv_user2; +SELECT count(*) FROM pg_shdepend + WHERE deptype = 'a' AND + refobjid = 'regress_priv_user2'::regrole AND + classid = 'pg_default_acl'::regclass; +DROP OWNED BY regress_priv_user2, regress_priv_user2; +SELECT count(*) FROM pg_shdepend + WHERE deptype = 'a' AND + refobjid = 'regress_priv_user2'::regrole AND + classid = 'pg_default_acl'::regclass; +ROLLBACK; + +CREATE SCHEMA testns5; + +SELECT has_schema_privilege('regress_priv_user2', 'testns5', 'USAGE'); -- no +SELECT has_schema_privilege('regress_priv_user2', 'testns5', 'CREATE'); -- no + +SET ROLE regress_priv_user1; + +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; +CREATE AGGREGATE testns.agg1(int) (sfunc = int4pl, stype = int4); +CREATE PROCEDURE testns.bar() AS 'select 1' LANGUAGE sql; + +SELECT has_function_privilege('regress_priv_user2', 'testns.foo()', 'EXECUTE'); -- no +SELECT has_function_privilege('regress_priv_user2', 'testns.agg1(int)', 'EXECUTE'); -- no +SELECT has_function_privilege('regress_priv_user2', 'testns.bar()', 'EXECUTE'); -- no + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT EXECUTE ON ROUTINES to public; + +DROP FUNCTION testns.foo(); +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; +DROP AGGREGATE testns.agg1(int); +CREATE AGGREGATE testns.agg1(int) (sfunc = int4pl, stype = int4); +DROP PROCEDURE testns.bar(); +CREATE PROCEDURE testns.bar() AS 'select 1' LANGUAGE sql; + +SELECT has_function_privilege('regress_priv_user2', 'testns.foo()', 'EXECUTE'); -- yes +SELECT has_function_privilege('regress_priv_user2', 'testns.agg1(int)', 'EXECUTE'); -- yes +SELECT has_function_privilege('regress_priv_user2', 'testns.bar()', 'EXECUTE'); -- yes (counts as function here) + +DROP FUNCTION testns.foo(); +DROP AGGREGATE testns.agg1(int); +DROP PROCEDURE testns.bar(); + +ALTER DEFAULT PRIVILEGES FOR ROLE regress_priv_user1 REVOKE USAGE ON TYPES FROM public; + +CREATE DOMAIN testns.priv_testdomain1 AS int; + +SELECT has_type_privilege('regress_priv_user2', 'testns.priv_testdomain1', 'USAGE'); -- no + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT USAGE ON TYPES to public; + +DROP DOMAIN testns.priv_testdomain1; +CREATE DOMAIN testns.priv_testdomain1 AS int; + +SELECT has_type_privilege('regress_priv_user2', 'testns.priv_testdomain1', 'USAGE'); -- yes + +DROP DOMAIN testns.priv_testdomain1; + +RESET ROLE; + +SELECT count(*) + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname = 'testns'; + +DROP SCHEMA testns CASCADE; +DROP SCHEMA testns2 CASCADE; +DROP SCHEMA testns3 CASCADE; +DROP SCHEMA testns4 CASCADE; +DROP SCHEMA testns5 CASCADE; + +SELECT d.* -- check that entries went away + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname IS NULL AND defaclnamespace != 0; + + +-- Grant on all objects of given type in a schema +\c - + +CREATE SCHEMA testns; +CREATE TABLE testns.t1 (f1 int); +CREATE TABLE testns.t2 (f1 int); + +SELECT has_table_privilege('regress_priv_user1', 'testns.t1', 'SELECT'); -- false + +GRANT ALL ON ALL TABLES IN SCHEMA testns TO regress_priv_user1; + +SELECT has_table_privilege('regress_priv_user1', 'testns.t1', 'SELECT'); -- true +SELECT has_table_privilege('regress_priv_user1', 'testns.t2', 'SELECT'); -- true + +REVOKE ALL ON ALL TABLES IN SCHEMA testns FROM regress_priv_user1; + +SELECT has_table_privilege('regress_priv_user1', 'testns.t1', 'SELECT'); -- false +SELECT has_table_privilege('regress_priv_user1', 'testns.t2', 'SELECT'); -- false + +CREATE FUNCTION testns.priv_testfunc(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql; +CREATE AGGREGATE testns.priv_testagg(int) (sfunc = int4pl, stype = int4); +CREATE PROCEDURE testns.priv_testproc(int) AS 'select 3' LANGUAGE sql; + +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testfunc(int)', 'EXECUTE'); -- true by default +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testagg(int)', 'EXECUTE'); -- true by default +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- true by default + +REVOKE ALL ON ALL FUNCTIONS IN SCHEMA testns FROM PUBLIC; + +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testfunc(int)', 'EXECUTE'); -- false +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testagg(int)', 'EXECUTE'); -- false +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- still true, not a function + +REVOKE ALL ON ALL PROCEDURES IN SCHEMA testns FROM PUBLIC; + +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- now false + +GRANT ALL ON ALL ROUTINES IN SCHEMA testns TO PUBLIC; + +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testfunc(int)', 'EXECUTE'); -- true +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testagg(int)', 'EXECUTE'); -- true +SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- true + +\set VERBOSITY terse \\ -- YB: suppress cascade details +DROP SCHEMA testns CASCADE; +\set VERBOSITY default \\ -- YB + + +-- Change owner of the schema & and rename of new schema owner +\c - + +CREATE ROLE regress_schemauser1 superuser login; +CREATE ROLE regress_schemauser2 superuser login; + +SET SESSION ROLE regress_schemauser1; +CREATE SCHEMA testns; + +SELECT nspname, rolname FROM pg_namespace, pg_roles WHERE pg_namespace.nspname = 'testns' AND pg_namespace.nspowner = pg_roles.oid; + +ALTER SCHEMA testns OWNER TO regress_schemauser2; +ALTER ROLE regress_schemauser2 RENAME TO regress_schemauser_renamed; +SELECT nspname, rolname FROM pg_namespace, pg_roles WHERE pg_namespace.nspname = 'testns' AND pg_namespace.nspowner = pg_roles.oid; + +set session role regress_schemauser_renamed; +\set VERBOSITY terse \\ -- YB: suppress cascade details +DROP SCHEMA testns CASCADE; +\set VERBOSITY default \\ -- YB + +-- clean up +\c - + +DROP ROLE regress_schemauser1; +DROP ROLE regress_schemauser_renamed; + + +-- test that dependent privileges are revoked (or not) properly +\c - + +set session role regress_priv_user1; +create table dep_priv_test (a int); +grant select on dep_priv_test to regress_priv_user2 with grant option; +grant select on dep_priv_test to regress_priv_user3 with grant option; +set session role regress_priv_user2; +grant select on dep_priv_test to regress_priv_user4 with grant option; +set session role regress_priv_user3; +grant select on dep_priv_test to regress_priv_user4 with grant option; +set session role regress_priv_user4; +grant select on dep_priv_test to regress_priv_user5; +\dp dep_priv_test +set session role regress_priv_user2; +revoke select on dep_priv_test from regress_priv_user4 cascade; +\dp dep_priv_test +set session role regress_priv_user3; +revoke select on dep_priv_test from regress_priv_user4 cascade; +\dp dep_priv_test +set session role regress_priv_user1; +drop table dep_priv_test; + + +-- clean up + +\c + +drop sequence x_seq; + +DROP AGGREGATE priv_testagg1(int); +DROP FUNCTION priv_testfunc2(int); +DROP FUNCTION priv_testfunc4(boolean); +DROP PROCEDURE priv_testproc1(int); + +DROP VIEW atestv0; +DROP VIEW atestv1; +DROP VIEW atestv2; +-- this should cascade to drop atestv4 +DROP VIEW atestv3 CASCADE; +-- this should complain "does not exist" +DROP VIEW atestv4; + +DROP TABLE atest1; +DROP TABLE atest2; +DROP TABLE atest3; +DROP TABLE atest4; +DROP TABLE atest5; +DROP TABLE atest6; +DROP TABLE atestc; +DROP TABLE atestp1; +DROP TABLE atestp2; + +SET yb_non_ddl_txn_for_sys_tables_allowed=1; -- YB: allow DMLs for pg_largeobject +SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid; +SET yb_non_ddl_txn_for_sys_tables_allowed=0; -- YB: disable DMLs for pg_largeobject + +DROP GROUP regress_priv_group1; +DROP GROUP regress_priv_group2; + +-- these are needed to clean up permissions +REVOKE USAGE ON LANGUAGE sql FROM regress_priv_user1; +DROP OWNED BY regress_priv_user1; + +DROP USER regress_priv_user1; +DROP USER regress_priv_user2; +DROP USER regress_priv_user3; +DROP USER regress_priv_user4; +DROP USER regress_priv_user5; +DROP USER regress_priv_user6; +DROP USER regress_priv_user7; +DROP USER regress_priv_user8; -- does not exist + + +-- permissions with LOCK TABLE +CREATE USER regress_locktable_user; +CREATE TABLE lock_table (a int); + +-- LOCK TABLE and SELECT permission +GRANT SELECT ON lock_table TO regress_locktable_user; +SET SESSION AUTHORIZATION regress_locktable_user; +BEGIN; +LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should fail +ROLLBACK; +BEGIN; +LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass +COMMIT; +BEGIN; +LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail +ROLLBACK; +\c +REVOKE SELECT ON lock_table FROM regress_locktable_user; + +-- LOCK TABLE and INSERT permission +GRANT INSERT ON lock_table TO regress_locktable_user; +SET SESSION AUTHORIZATION regress_locktable_user; +BEGIN; +LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass +COMMIT; +BEGIN; +LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail +ROLLBACK; +BEGIN; +LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail +ROLLBACK; +\c +REVOKE INSERT ON lock_table FROM regress_locktable_user; + +-- LOCK TABLE and UPDATE permission +GRANT UPDATE ON lock_table TO regress_locktable_user; +SET SESSION AUTHORIZATION regress_locktable_user; +BEGIN; +LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass +COMMIT; +BEGIN; +LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail +ROLLBACK; +BEGIN; +LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass +COMMIT; +\c +REVOKE UPDATE ON lock_table FROM regress_locktable_user; + +-- LOCK TABLE and DELETE permission +GRANT DELETE ON lock_table TO regress_locktable_user; +SET SESSION AUTHORIZATION regress_locktable_user; +BEGIN; +LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass +COMMIT; +BEGIN; +LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail +ROLLBACK; +BEGIN; +LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass +COMMIT; +\c +REVOKE DELETE ON lock_table FROM regress_locktable_user; + +-- LOCK TABLE and TRUNCATE permission +GRANT TRUNCATE ON lock_table TO regress_locktable_user; +SET SESSION AUTHORIZATION regress_locktable_user; +BEGIN; +LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass +COMMIT; +BEGIN; +LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail +ROLLBACK; +BEGIN; +LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass +COMMIT; +\c +REVOKE TRUNCATE ON lock_table FROM regress_locktable_user; + +-- clean up +DROP TABLE lock_table; +DROP USER regress_locktable_user; + +-- test to check privileges of system views pg_shmem_allocations and +-- pg_backend_memory_contexts. + +-- switch to superuser +\c - + +CREATE ROLE regress_readallstats; + +SELECT has_table_privilege('regress_readallstats','pg_backend_memory_contexts','SELECT'); -- no +SELECT has_table_privilege('regress_readallstats','pg_shmem_allocations','SELECT'); -- no + +GRANT pg_read_all_stats TO regress_readallstats; + +SELECT has_table_privilege('regress_readallstats','pg_backend_memory_contexts','SELECT'); -- yes +SELECT has_table_privilege('regress_readallstats','pg_shmem_allocations','SELECT'); -- yes + +-- run query to ensure that functions within views can be executed +SET ROLE regress_readallstats; +SELECT COUNT(*) >= 0 AS ok FROM pg_backend_memory_contexts; +SELECT COUNT(*) >= 0 AS ok FROM pg_shmem_allocations; +RESET ROLE; + +-- clean up +DROP ROLE regress_readallstats; diff --git a/src/test/regress/sql/yb.port.publication.sql b/src/test/regress/sql/yb.port.publication.sql new file mode 100644 index 0000000000000..bc3865df04ff1 --- /dev/null +++ b/src/test/regress/sql/yb.port.publication.sql @@ -0,0 +1,193 @@ +-- +-- PUBLICATION +-- +CREATE ROLE regress_publication_user LOGIN SUPERUSER; +CREATE ROLE regress_publication_user2; +CREATE ROLE regress_publication_user_dummy LOGIN NOSUPERUSER; +SET SESSION AUTHORIZATION 'regress_publication_user'; + +-- suppress warning that depends on wal_level +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_default; +RESET client_min_messages; + +COMMENT ON PUBLICATION testpub_default IS 'test publication'; +SELECT obj_description(p.oid, 'pg_publication') FROM pg_publication p; + +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpib_ins_trunct WITH (publish = insert); +RESET client_min_messages; + +ALTER PUBLICATION testpub_default SET (publish = update); + +-- error cases +CREATE PUBLICATION testpub_xxx WITH (foo); +CREATE PUBLICATION testpub_xxx WITH (publish = 'cluster, vacuum'); + +\dRp + +ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete'); + +\dRp + +--- adding tables +CREATE SCHEMA pub_test; +CREATE TABLE testpub_tbl1 (id serial primary key, data text); +CREATE TABLE pub_test.testpub_nopk (foo int, bar int); +CREATE VIEW testpub_view AS SELECT 1; +CREATE TABLE testpub_parted (a int) PARTITION BY LIST (a); + +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_foralltables FOR ALL TABLES WITH (publish = 'insert'); +RESET client_min_messages; +-- YB Note: publishing subset of records is unsupported (#19250). +CREATE PUBLICATION testpub_foralltables FOR ALL TABLES; +ALTER PUBLICATION testpub_foralltables SET (publish = 'insert, update'); +-- YB Note: publishing subset of records is unsupported (#19250). +ALTER PUBLICATION testpub_foralltables SET (publish = 'insert, update, delete, truncate'); + +CREATE TABLE testpub_tbl2 (id serial primary key, data text); +-- fail - can't add to for all tables publication +ALTER PUBLICATION testpub_foralltables ADD TABLE testpub_tbl2; +-- fail - can't drop from all tables publication +ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2; +-- fail - can't add to for all tables publication +ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk; + +SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables'; +\d+ testpub_tbl2 +\dRp+ testpub_foralltables + +DROP TABLE testpub_tbl2; +DROP PUBLICATION testpub_foralltables; + +CREATE TABLE testpub_tbl3 (a int); +CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3); +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3; +CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3; +RESET client_min_messages; +\dRp+ testpub3 +\dRp+ testpub4 + +DROP TABLE testpub_tbl3, testpub_tbl3a; +DROP PUBLICATION testpub3, testpub4; +-- YB Note: Drop publications one by one to ensure cleanup. +-- These will fail now but will pass once we support streaming +-- tables without pk. +DROP PUBLICATION testpub3; +DROP PUBLICATION testpub4; + +-- Tests for partitioned tables +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_forparted; +CREATE PUBLICATION testpub_forparted1; +RESET client_min_messages; +CREATE TABLE testpub_parted1 (LIKE testpub_parted); +CREATE TABLE testpub_parted2 (LIKE testpub_parted); +ALTER PUBLICATION testpub_forparted1 SET (publish='insert'); +ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted1 FOR VALUES IN (1); +ALTER TABLE testpub_parted ATTACH PARTITION testpub_parted2 FOR VALUES IN (2); +-- works despite missing REPLICA IDENTITY, because updates are not replicated +UPDATE testpub_parted1 SET a = 1; +-- only parent is listed as being in publication, not the partition +ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted; +DROP TABLE testpub_parted1, testpub_parted2; +DROP PUBLICATION testpub_forparted, testpub_forparted1; +-- YB Note: Drop publications one by one to ensure cleanup. +DROP PUBLICATION testpub_forparted; +DROP PUBLICATION testpub_forparted1; + +-- fail - view +CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_view; + +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1, pub_test.testpub_nopk; +-- YB Note: tables without pk are unsupported. +CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1; +RESET client_min_messages; +-- fail - already added +ALTER PUBLICATION testpub_fortbl ADD TABLE testpub_tbl1; +-- fail - already added +CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1; + +\dRp+ testpub_fortbl + +-- fail - view +ALTER PUBLICATION testpub_default ADD TABLE testpub_view; + +ALTER PUBLICATION testpub_default ADD TABLE testpub_tbl1; +ALTER PUBLICATION testpub_default SET TABLE testpub_tbl1; +ALTER PUBLICATION testpub_default ADD TABLE pub_test.testpub_nopk; + +ALTER PUBLICATION testpib_ins_trunct ADD TABLE pub_test.testpub_nopk, testpub_tbl1; + +\d+ pub_test.testpub_nopk +\d+ testpub_tbl1 +\dRp+ testpub_default + +ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1, pub_test.testpub_nopk; +-- YB Note: tables without pk are unsupported. +ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1; +-- fail - nonexistent +ALTER PUBLICATION testpub_default DROP TABLE pub_test.testpub_nopk; + +\d+ testpub_tbl1 + +-- permissions +SET ROLE regress_publication_user2; +CREATE PUBLICATION testpub2; -- fail + +SET ROLE regress_publication_user; +GRANT CREATE ON DATABASE yugabyte TO regress_publication_user2; +SET ROLE regress_publication_user2; +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION testpub2; -- ok +RESET client_min_messages; + +ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- fail + +SET ROLE regress_publication_user; +GRANT regress_publication_user TO regress_publication_user2; +SET ROLE regress_publication_user2; +ALTER PUBLICATION testpub2 ADD TABLE testpub_tbl1; -- ok + +DROP PUBLICATION testpub2; + +SET ROLE regress_publication_user; +REVOKE CREATE ON DATABASE yugabyte FROM regress_publication_user2; + +DROP TABLE testpub_parted; +DROP VIEW testpub_view; +-- YB Note: We do not allow dropping of tables that are part of a non-ALL TABLES publication. +DROP TABLE testpub_tbl1; +ALTER PUBLICATION testpub_fortbl DROP TABLE testpub_tbl1; -- YB: Drop the table from publication first. +DROP TABLE testpub_tbl1; + +\dRp+ testpub_default + +-- fail - must be owner of publication +SET ROLE regress_publication_user_dummy; +ALTER PUBLICATION testpub_default RENAME TO testpub_dummy; +RESET ROLE; + +ALTER PUBLICATION testpub_default RENAME TO testpub_foo; + +\dRp testpub_foo + +-- rename back to keep the rest simple +ALTER PUBLICATION testpub_foo RENAME TO testpub_default; + +ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2; + +\dRp testpub_default + +DROP PUBLICATION testpub_default; +DROP PUBLICATION testpib_ins_trunct; +DROP PUBLICATION testpub_fortbl; + +DROP SCHEMA pub_test CASCADE; + +RESET SESSION AUTHORIZATION; +DROP ROLE regress_publication_user, regress_publication_user2; +DROP ROLE regress_publication_user_dummy; diff --git a/src/test/regress/sql/yb.port.rangetypes.sql b/src/test/regress/sql/yb.port.rangetypes.sql new file mode 100644 index 0000000000000..65001b27c7f94 --- /dev/null +++ b/src/test/regress/sql/yb.port.rangetypes.sql @@ -0,0 +1,449 @@ +-- Tests for range data types. + +-- +-- test input parser +-- (type textrange was already made in test_setup.sql) +-- + +-- negative tests; should fail +select ''::textrange; +select '-[a,z)'::textrange; +select '[a,z) - '::textrange; +select '(",a)'::textrange; +select '(,,a)'::textrange; +select '(),a)'::textrange; +select '(a,))'::textrange; +select '(],a)'::textrange; +select '(a,])'::textrange; +select '[z,a]'::textrange; + +-- should succeed +select ' empty '::textrange; +select ' ( empty, empty ) '::textrange; +select ' ( " a " " a ", " z " " z " ) '::textrange; +select '(a,)'::textrange; +select '[,z]'::textrange; +select '[a,]'::textrange; +select '(,)'::textrange; +select '[ , ]'::textrange; +select '["",""]'::textrange; +select '[",",","]'::textrange; +select '["\\","\\"]'::textrange; +select '(\\,a)'::textrange; +select '((,z)'::textrange; +select '([,z)'::textrange; +select '(!,()'::textrange; +select '(!,[)'::textrange; +select '[a,a]'::textrange; +-- these are allowed but normalize to empty: +select '[a,a)'::textrange; +select '(a,a]'::textrange; +select '(a,a)'::textrange; + +-- +-- create some test data and test the operators +-- + +CREATE TABLE numrange_test (nr NUMRANGE); +create index numrange_test_btree on numrange_test(nr); + +INSERT INTO numrange_test VALUES('[,)'); +INSERT INTO numrange_test VALUES('[3,]'); +INSERT INTO numrange_test VALUES('[, 5)'); +INSERT INTO numrange_test VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test VALUES('empty'); +INSERT INTO numrange_test VALUES(numrange(1.7, 1.7, '[]')); + +SELECT nr, isempty(nr), lower(nr), upper(nr) FROM numrange_test; +SELECT nr, lower_inc(nr), lower_inf(nr), upper_inc(nr), upper_inf(nr) FROM numrange_test; + +SELECT * FROM numrange_test WHERE range_contains(nr, numrange(1.9,1.91)); +SELECT * FROM numrange_test WHERE nr @> numrange(1.0,10000.1); +SELECT * FROM numrange_test WHERE range_contained_by(numrange(-1e7,-10000.1), nr); +SELECT * FROM numrange_test WHERE 1.9 <@ nr; + +select * from numrange_test where nr = 'empty'; +select * from numrange_test where nr = '(1.1, 2.2)'; +select * from numrange_test where nr = '[1.1, 2.2)'; +select * from numrange_test where nr < 'empty'; +select * from numrange_test where nr < numrange(-1000.0, -1000.0,'[]'); +select * from numrange_test where nr < numrange(0.0, 1.0,'[]'); +select * from numrange_test where nr < numrange(1000.0, 1001.0,'[]'); +select * from numrange_test where nr <= 'empty'; +select * from numrange_test where nr >= 'empty'; +select * from numrange_test where nr > 'empty'; +select * from numrange_test where nr > numrange(-1001.0, -1000.0,'[]'); +select * from numrange_test where nr > numrange(0.0, 1.0,'[]'); +select * from numrange_test where nr > numrange(1000.0, 1000.0,'[]'); + +select numrange(2.0, 1.0); + +select numrange(2.0, 3.0) -|- numrange(3.0, 4.0); +select range_adjacent(numrange(2.0, 3.0), numrange(3.1, 4.0)); +select range_adjacent(numrange(2.0, 3.0), numrange(3.1, null)); +select numrange(2.0, 3.0, '[]') -|- numrange(3.0, 4.0, '()'); +select numrange(1.0, 2.0) -|- numrange(2.0, 3.0,'[]'); +select range_adjacent(numrange(2.0, 3.0, '(]'), numrange(1.0, 2.0, '(]')); + +select numrange(1.1, 3.3) <@ numrange(0.1,10.1); +select numrange(0.1, 10.1) <@ numrange(1.1,3.3); + +select numrange(1.1, 2.2) - numrange(2.0, 3.0); +select numrange(1.1, 2.2) - numrange(2.2, 3.0); +select numrange(1.1, 2.2,'[]') - numrange(2.0, 3.0); +select range_minus(numrange(10.1,12.2,'[]'), numrange(110.0,120.2,'(]')); +select range_minus(numrange(10.1,12.2,'[]'), numrange(0.0,120.2,'(]')); + +select numrange(4.5, 5.5, '[]') && numrange(5.5, 6.5); +select numrange(1.0, 2.0) << numrange(3.0, 4.0); +select numrange(1.0, 3.0,'[]') << numrange(3.0, 4.0,'[]'); +select numrange(1.0, 3.0,'()') << numrange(3.0, 4.0,'()'); +select numrange(1.0, 2.0) >> numrange(3.0, 4.0); +select numrange(3.0, 70.0) &< numrange(6.6, 100.0); + +select numrange(1.1, 2.2) < numrange(1.0, 200.2); +select numrange(1.1, 2.2) < numrange(1.1, 1.2); + +select numrange(1.0, 2.0) + numrange(2.0, 3.0); +select numrange(1.0, 2.0) + numrange(1.5, 3.0); +select numrange(1.0, 2.0) + numrange(2.5, 3.0); -- should fail + +select range_merge(numrange(1.0, 2.0), numrange(2.0, 3.0)); +select range_merge(numrange(1.0, 2.0), numrange(1.5, 3.0)); +select range_merge(numrange(1.0, 2.0), numrange(2.5, 3.0)); -- shouldn't fail + +select numrange(1.0, 2.0) * numrange(2.0, 3.0); +select numrange(1.0, 2.0) * numrange(1.5, 3.0); +select numrange(1.0, 2.0) * numrange(2.5, 3.0); + +select range_intersect_agg(nr) from numrange_test; +select range_intersect_agg(nr) from numrange_test where false; +select range_intersect_agg(nr) from numrange_test where nr @> 4.0; + +analyze numrange_test; + +create table numrange_test2(nr numrange); +create index numrange_test2_hash_idx on numrange_test2 using hash (nr); + +INSERT INTO numrange_test2 VALUES('[, 5)'); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2,'()')); +INSERT INTO numrange_test2 VALUES('empty'); + +select * from numrange_test2 where nr = 'empty'::numrange; +select * from numrange_test2 where nr = numrange(1.1, 2.2); +select * from numrange_test2 where nr = numrange(1.1, 2.3); + +set enable_nestloop=t; +set enable_hashjoin=f; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; +set enable_nestloop=f; +set enable_hashjoin=t; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; +set enable_nestloop=f; +set enable_hashjoin=f; +set enable_mergejoin=t; +select * from numrange_test natural join numrange_test2 order by nr; + +set enable_nestloop to default; +set enable_hashjoin to default; +set enable_mergejoin to default; + +-- keep numrange_test around to help exercise dump/reload +DROP TABLE numrange_test2; + +-- +-- Apply a subset of the above tests on a collatable type, too +-- + +CREATE TABLE textrange_test (tr textrange); +create index textrange_test_btree on textrange_test(tr); + +INSERT INTO textrange_test VALUES('[,)'); +INSERT INTO textrange_test VALUES('["a",]'); +INSERT INTO textrange_test VALUES('[,"q")'); +INSERT INTO textrange_test VALUES(textrange('b', 'g')); +INSERT INTO textrange_test VALUES('empty'); +INSERT INTO textrange_test VALUES(textrange('d', 'd', '[]')); + +SELECT tr, isempty(tr), lower(tr), upper(tr) FROM textrange_test; +SELECT tr, lower_inc(tr), lower_inf(tr), upper_inc(tr), upper_inf(tr) FROM textrange_test; + +SELECT * FROM textrange_test WHERE range_contains(tr, textrange('f', 'fx')); +SELECT * FROM textrange_test WHERE tr @> textrange('a', 'z'); +SELECT * FROM textrange_test WHERE range_contained_by(textrange('0','9'), tr); +SELECT * FROM textrange_test WHERE 'e'::text <@ tr; + +select * from textrange_test where tr = 'empty'; +select * from textrange_test where tr = '("b","g")'; +select * from textrange_test where tr = '["b","g")'; +select * from textrange_test where tr < 'empty'; + + +-- test canonical form for int4range +select int4range(1, 10, '[]'); +select int4range(1, 10, '[)'); +select int4range(1, 10, '(]'); +select int4range(1, 10, '()'); +select int4range(1, 2, '()'); + +-- test canonical form for daterange +select daterange('2000-01-10'::date, '2000-01-20'::date, '[]'); +select daterange('2000-01-10'::date, '2000-01-20'::date, '[)'); +select daterange('2000-01-10'::date, '2000-01-20'::date, '(]'); +select daterange('2000-01-10'::date, '2000-01-20'::date, '()'); +select daterange('2000-01-10'::date, '2000-01-11'::date, '()'); +select daterange('2000-01-10'::date, '2000-01-11'::date, '(]'); +select daterange('-infinity'::date, '2000-01-01'::date, '()'); +select daterange('-infinity'::date, '2000-01-01'::date, '[)'); +select daterange('2000-01-01'::date, 'infinity'::date, '[)'); +select daterange('2000-01-01'::date, 'infinity'::date, '[]'); + +-- test GiST index that's been built incrementally +create table test_range_gist(ir int4range); +create index test_range_gist_idx on test_range_gist using gist (ir); + +-- test SP-GiST index that's been built incrementally +create table test_range_spgist(ir int4range); +create index test_range_spgist_idx on test_range_spgist using spgist (ir); + +-- test elem <@ range operator +create table test_range_elem(i int4); +create index test_range_elem_idx on test_range_elem (i); +insert into test_range_elem select i from generate_series(1,100) i; + +SET enable_seqscan = f; + +select count(*) from test_range_elem where i <@ int4range(10,50); + +-- also test spgist index on anyrange expression +create index on test_range_elem using spgist(int4range(i,i+10)); +explain (costs off) +select count(*) from test_range_elem where int4range(i,i+10) <@ int4range(10,30); +select count(*) from test_range_elem where int4range(i,i+10) <@ int4range(10,30); + +RESET enable_seqscan; + +drop table test_range_elem; + +-- +-- Btree_gist is not included by default, so to test exclusion +-- constraints with range types, use singleton int ranges for the "=" +-- portion of the constraint. +-- + +create table test_range_excl( + room int4range, + speaker int4range, + during tsrange, + exclude using gist (room with =, during with &&), + exclude using gist (speaker with =, during with &&) +); + +-- test bigint ranges +select int8range(10000000000::int8, 20000000000::int8,'(]'); +-- test tstz ranges +set timezone to '-08'; +select '[2010-01-01 01:00:00 -05, 2010-01-01 02:00:00 -08)'::tstzrange; +-- should fail +select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)'::tstzrange; +set timezone to default; + +-- +-- Test user-defined range of floats +-- (type float8range was already made in test_setup.sql) +-- + +--should fail +create type bogus_float8range as range (subtype=float8, subtype_diff=float4mi); + +select '[123.001, 5.e9)'::float8range @> 888.882::float8; +create table float8range_test(f8r float8range, i int); +insert into float8range_test values(float8range(-100.00007, '1.111113e9'), 42); +select * from float8range_test; +drop table float8range_test; + +-- +-- Test range types over domains +-- + +create domain mydomain as int4; +create type mydomainrange as range(subtype=mydomain); +select '[4,50)'::mydomainrange @> 7::mydomain; +drop domain mydomain; -- fail +drop domain mydomain cascade; + +-- +-- Test domains over range types +-- + +create domain restrictedrange as int4range check (upper(value) < 10); +select '[4,5)'::restrictedrange @> 7; +select '[4,50)'::restrictedrange @> 7; -- should fail +drop domain restrictedrange; + +-- +-- Test multiple range types over the same subtype +-- + +create type textrange1 as range(subtype=text, collation="C"); +create type textrange2 as range(subtype=text, collation="C"); + +select textrange1('a','Z') @> 'b'::text; +select textrange2('a','z') @> 'b'::text; + +drop type textrange1; +drop type textrange2; + +-- +-- Test polymorphic type system +-- + +create function anyarray_anyrange_func(a anyarray, r anyrange) + returns anyelement as 'select $1[1] + lower($2);' language sql; + +select anyarray_anyrange_func(ARRAY[1,2], int4range(10,20)); + +-- should fail +select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20)); + +drop function anyarray_anyrange_func(anyarray, anyrange); + +-- should fail +create function bogus_func(anyelement) + returns anyrange as 'select int4range(1,10)' language sql; + +-- should fail +create function bogus_func(int) + returns anyrange as 'select int4range(1,10)' language sql; + +create function range_add_bounds(anyrange) + returns anyelement as 'select lower($1) + upper($1)' language sql; + +select range_add_bounds(int4range(1, 17)); +select range_add_bounds(numrange(1.0001, 123.123)); + +create function rangetypes_sql(q anyrange, b anyarray, out c anyelement) + as $$ select upper($1) + $2[1] $$ + language sql; + +select rangetypes_sql(int4range(1,10), ARRAY[2,20]); +select rangetypes_sql(numrange(1,10), ARRAY[2,20]); -- match failure + +create function anycompatiblearray_anycompatiblerange_func(a anycompatiblearray, r anycompatiblerange) + returns anycompatible as 'select $1[1] + lower($2);' language sql; + +select anycompatiblearray_anycompatiblerange_func(ARRAY[1,2], int4range(10,20)); + +select anycompatiblearray_anycompatiblerange_func(ARRAY[1,2], numrange(10,20)); + +-- should fail +select anycompatiblearray_anycompatiblerange_func(ARRAY[1.1,2], int4range(10,20)); + +drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, anycompatiblerange); + +-- should fail +create function bogus_func(anycompatible) + returns anycompatiblerange as 'select int4range(1,10)' language sql; + +-- +-- Arrays of ranges +-- + +select ARRAY[numrange(1.1, 1.2), numrange(12.3, 155.5)]; + +create table i8r_array (f1 int, f2 int8range[]); +insert into i8r_array values (42, array[int8range(1,10), int8range(2,20)]); +select * from i8r_array; +drop table i8r_array; + +-- +-- Ranges of arrays +-- + +create type arrayrange as range (subtype=int4[]); + +select arrayrange(ARRAY[1,2], ARRAY[2,1]); +select arrayrange(ARRAY[2,1], ARRAY[1,2]); -- fail + +select array[1,1] <@ arrayrange(array[1,2], array[2,1]); +select array[1,3] <@ arrayrange(array[1,2], array[2,1]); + +-- +-- Ranges of composites +-- + +create type two_ints as (a int, b int); +create type two_ints_range as range (subtype = two_ints); + +-- with force_parallel_mode on, this exercises tqueue.c's range remapping +select *, row_to_json(upper(t)) as u from + (values (two_ints_range(row(1,2), row(3,4))), + (two_ints_range(row(5,6), row(7,8)))) v(t); + +-- this must be rejected to avoid self-inclusion issues: +alter type two_ints add attribute c two_ints_range; + +drop type two_ints cascade; + +-- +-- Check behavior when subtype lacks a hash function +-- + +create type cashrange as range (subtype = money); + +set enable_sort = off; -- try to make it pick a hash setop implementation + +select '(2,5)'::cashrange except select '(5,6)'::cashrange; + +reset enable_sort; + +-- +-- OUT/INOUT/TABLE functions +-- + +-- infer anyrange from anyrange +create function outparam_succeed(i anyrange, out r anyrange, out t text) + as $$ select $1, 'foo'::text $$ language sql; + +select * from outparam_succeed(int4range(1,2)); + +create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray) + as $$ select array[lower($1), upper($1)], array[upper($1), lower($1)] $$ + language sql; + +select * from outparam2_succeed(int4range(1,11)); + +-- infer anyarray from anyrange +create function outparam_succeed2(i anyrange, out r anyarray, out t text) + as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql; + +select * from outparam_succeed2(int4range(int4range(1,2))); + +-- infer anyelement from anyrange +create function inoutparam_succeed(out i anyelement, inout r anyrange) + as $$ select upper($1), $1 $$ language sql; + +select * from inoutparam_succeed(int4range(1,2)); + +create function table_succeed(r anyrange) + returns table(l anyelement, u anyelement) + as $$ select lower($1), upper($1) $$ + language sql; + +select * from table_succeed(int4range(1,11)); + +-- should fail +create function outparam_fail(i anyelement, out r anyrange, out t text) + as $$ select '[1,10]', 'foo' $$ language sql; + +--should fail +create function inoutparam_fail(inout i anyelement, out r anyrange) + as $$ select $1, '[1,10]' $$ language sql; + +--should fail +create function table_fail(i anyelement) returns table(i anyelement, r anyrange) + as $$ select $1, '[1,10]' $$ language sql; diff --git a/src/test/regress/sql/yb.port.replica_identity.sql b/src/test/regress/sql/yb.port.replica_identity.sql new file mode 100644 index 0000000000000..ee29786f211bc --- /dev/null +++ b/src/test/regress/sql/yb.port.replica_identity.sql @@ -0,0 +1,39 @@ +-- YB note: deferable unique constraints and OIDs are not supported for user tables +CREATE TABLE test_replica_identity ( + id serial primary key, + keya text not null, + keyb text not null, + nonkey text, + -- CONSTRAINT test_replica_identity_unique_defer UNIQUE (keya, keyb) DEFERRABLE, + CONSTRAINT test_replica_identity_unique_nondefer UNIQUE (keya, keyb) +);-- WITH OIDS; + +CREATE INDEX test_replica_identity_keyab ON test_replica_identity (keya, keyb); + +-- default is 'c' (CHANGE) for user created tables +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; +-- but 'none' for system tables +SELECT relreplident FROM pg_class WHERE oid = 'pg_class'::regclass; +SELECT relreplident FROM pg_class WHERE oid = 'pg_constraint'::regclass; + +-- YB Note: REPLICA IDENTITY mode INDEX is not supported yet, we will assert the failure message +-- here. Other queries for testing INDEX mode should be ported whenever the support is added. +ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab; + +-- YB Note: Since REPLICA IDENTITY mode INDEX is not supported yet, this will be 'c' (CHANGE) +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; + +---- +-- Make sure non index cases work +---- +ALTER TABLE test_replica_identity REPLICA IDENTITY DEFAULT; +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; +SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident; + +ALTER TABLE test_replica_identity REPLICA IDENTITY FULL; +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; +\d+ test_replica_identity +ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING; +SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; + +DROP TABLE test_replica_identity; diff --git a/src/test/regress/sql/yb.port.rowsecurity.sql b/src/test/regress/sql/yb.port.rowsecurity.sql new file mode 100644 index 0000000000000..3d837889933c4 --- /dev/null +++ b/src/test/regress/sql/yb.port.rowsecurity.sql @@ -0,0 +1,2088 @@ +-- +-- Test of Row-level security feature +-- + +-- Clean up in case a prior regression run failed + +-- Suppress NOTICE messages when users/groups don't exist +SET client_min_messages TO 'warning'; + +DROP USER IF EXISTS regress_rls_alice; +DROP USER IF EXISTS regress_rls_bob; +DROP USER IF EXISTS regress_rls_carol; +DROP USER IF EXISTS regress_rls_dave; +DROP USER IF EXISTS regress_rls_exempt_user; +DROP ROLE IF EXISTS regress_rls_group1; +DROP ROLE IF EXISTS regress_rls_group2; + +DROP SCHEMA IF EXISTS regress_rls_schema CASCADE; + +RESET client_min_messages; + +-- initial setup +CREATE USER regress_rls_alice NOLOGIN; +CREATE USER regress_rls_bob NOLOGIN; +CREATE USER regress_rls_carol NOLOGIN; +CREATE USER regress_rls_dave NOLOGIN; +CREATE USER regress_rls_exempt_user BYPASSRLS NOLOGIN; +CREATE ROLE regress_rls_group1 NOLOGIN; +CREATE ROLE regress_rls_group2 NOLOGIN; + +GRANT regress_rls_group1 TO regress_rls_bob; +GRANT regress_rls_group2 TO regress_rls_carol; + +CREATE SCHEMA regress_rls_schema; +GRANT ALL ON SCHEMA regress_rls_schema to public; +SET search_path = regress_rls_schema; + +-- setup of malicious function +CREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool + COST 0.0000001 LANGUAGE plpgsql + AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END'; +GRANT EXECUTE ON FUNCTION f_leak(text) TO public; + +-- BASIC Row-Level Security Scenario + +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE uaccount ( + pguser name primary key, + seclv int +); +GRANT SELECT ON uaccount TO public; +INSERT INTO uaccount VALUES + ('regress_rls_alice', 99), + ('regress_rls_bob', 1), + ('regress_rls_carol', 2), + ('regress_rls_dave', 3); + +CREATE TABLE category ( + cid int primary key, + cname text +); +GRANT ALL ON category TO public; +INSERT INTO category VALUES + (11, 'novel'), + (22, 'science fiction'), + (33, 'technology'), + (44, 'manga'); + +CREATE TABLE document ( + did int primary key, + cid int references category(cid), + dlevel int not null, + dauthor name, + dtitle text +); +GRANT ALL ON document TO public; +INSERT INTO document VALUES + ( 1, 11, 1, 'regress_rls_bob', 'my first novel'), + ( 2, 11, 2, 'regress_rls_bob', 'my second novel'), + ( 3, 22, 2, 'regress_rls_bob', 'my science fiction'), + ( 4, 44, 1, 'regress_rls_bob', 'my first manga'), + ( 5, 44, 2, 'regress_rls_bob', 'my second manga'), + ( 6, 22, 1, 'regress_rls_carol', 'great science fiction'), + ( 7, 33, 2, 'regress_rls_carol', 'great technology book'), + ( 8, 44, 1, 'regress_rls_carol', 'great manga'), + ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'), + (10, 33, 2, 'regress_rls_dave', 'awesome technology book'); + +ALTER TABLE document ENABLE ROW LEVEL SECURITY; + +-- user's security level must be higher than or equal to document's +CREATE POLICY p1 ON document AS PERMISSIVE + USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user)); + +-- try to create a policy of bogus type +CREATE POLICY p1 ON document AS UGLY + USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user)); + +-- but Dave isn't allowed to anything at cid 50 or above +-- this is to make sure that we sort the policies by name first +-- when applying WITH CHECK, a later INSERT by Dave should fail due +-- to p1r first +CREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave + USING (cid <> 44 AND cid < 50); + +-- and Dave isn't allowed to see manga documents +CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave + USING (cid <> 44); + +\dp +\d document +SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename = 'document' ORDER BY policyname; + +-- viewpoint from regress_rls_bob +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; + +-- try a sampled version +SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0) + WHERE f_leak(dtitle) ORDER BY did; + +-- viewpoint from regress_rls_carol +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; + +-- try a sampled version +SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0) + WHERE f_leak(dtitle) ORDER BY did; + +EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); +EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); + +-- viewpoint from regress_rls_dave +SET SESSION AUTHORIZATION regress_rls_dave; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; + +EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); +EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); + +-- 44 would technically fail for both p2r and p1r, but we should get an error +-- back from p1r for this because it sorts first +INSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail +-- Just to see a p2r error +INSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail + +-- only owner can change policies +ALTER POLICY p1 ON document USING (true); --fail +DROP POLICY p1 ON document; --fail + +SET SESSION AUTHORIZATION regress_rls_alice; +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; +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did; + +-- viewpoint from rls_regres_carol again +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did; + +EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); +EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); + +-- interaction of FK/PK constraints +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE POLICY p2 ON category + USING (CASE WHEN current_user = 'regress_rls_bob' THEN cid IN (11, 33) + WHEN current_user = 'regress_rls_carol' THEN cid IN (22, 44) + ELSE false END); + +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; +DELETE FROM category WHERE cid = 33; -- fails with FK violation + +-- 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; +INSERT INTO document VALUES (11, 33, 1, current_user, 'hoge'); + +-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row +SET SESSION AUTHORIZATION regress_rls_bob; +INSERT INTO document VALUES (8, 44, 1, 'regress_rls_bob', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see +SELECT * FROM document WHERE did = 8; -- and confirm we can't see it + +-- RLS policies are checked before constraints +INSERT INTO document VALUES (8, 44, 1, 'regress_rls_carol', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation +UPDATE document SET did = 8, dauthor = 'regress_rls_carol' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation + +-- database superuser does bypass RLS policy when enabled +RESET SESSION AUTHORIZATION; +SET row_security TO ON; +SELECT * FROM document; +SELECT * FROM category; + +-- database superuser does bypass RLS policy when disabled +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM document; +SELECT * FROM category; + +-- database non-superuser with bypass privilege can bypass RLS policy when disabled +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO OFF; +SELECT * FROM document; +SELECT * FROM category; + +-- RLS policy does not apply to table owner when RLS enabled. +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +SELECT * FROM document; +SELECT * FROM category; + +-- RLS policy does not apply to table owner when RLS disabled. +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO OFF; +SELECT * FROM document; +SELECT * FROM category; + +-- +-- Table inheritance and RLS policy +-- +SET SESSION AUTHORIZATION regress_rls_alice; + +SET row_security TO ON; + +CREATE TABLE t1 (id int not null primary key, a int, junk1 text, b text); +ALTER TABLE t1 DROP COLUMN junk1; -- just a disturbing factor +GRANT ALL ON t1 TO public; + +COPY t1 FROM stdin WITH ; +101 1 aba +102 2 bbb +103 3 ccc +104 4 dad +\. + +CREATE TABLE t2 (c float) INHERITS (t1); +GRANT ALL ON t2 TO public; + +COPY t2 FROM stdin; +201 1 abc 1.1 +202 2 bcd 2.2 +203 3 cde 3.3 +204 4 def 4.4 +\. + +CREATE TABLE t3 (id int not null primary key, c text, b text, a int); +ALTER TABLE t3 INHERIT t1; +GRANT ALL ON t3 TO public; + +COPY t3(id, a,b,c) FROM stdin; +301 1 xxx X +302 2 yyy Y +303 3 zzz Z +\. + +CREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number +CREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number + +ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE t2 ENABLE ROW LEVEL SECURITY; + +SET SESSION AUTHORIZATION regress_rls_bob; + +SELECT * FROM t1; +EXPLAIN (COSTS OFF) SELECT * FROM t1; + +SELECT * FROM t1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + +-- reference to system column +SELECT tableoid::regclass, * FROM t1; +EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1; + +-- reference to whole-row reference +SELECT *, t1 FROM t1; +EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1; + +-- for share/update lock +SELECT * FROM t1 FOR SHARE; +EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE; + +SELECT * FROM t1 WHERE f_leak(b) FOR SHARE; +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE; + +-- union all query +SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3; +EXPLAIN (COSTS OFF) SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3; + +-- superuser is allowed to bypass RLS checks +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM t1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + +-- non-superuser with bypass privilege can bypass RLS policy when disabled +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO OFF; +SELECT * FROM t1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + +-- +-- Partitioned Tables +-- + +SET SESSION AUTHORIZATION regress_rls_alice; + +CREATE TABLE part_document ( + did int, + cid int, + dlevel int not null, + dauthor name, + dtitle text +) PARTITION BY RANGE (cid); +GRANT ALL ON part_document TO public; + +-- Create partitions for document categories +CREATE TABLE part_document_fiction PARTITION OF part_document FOR VALUES FROM (11) to (12); +CREATE TABLE part_document_satire PARTITION OF part_document FOR VALUES FROM (55) to (56); +CREATE TABLE part_document_nonfiction PARTITION OF part_document FOR VALUES FROM (99) to (100); + +GRANT ALL ON part_document_fiction TO public; +GRANT ALL ON part_document_satire TO public; +GRANT ALL ON part_document_nonfiction TO public; + +INSERT INTO part_document VALUES + ( 1, 11, 1, 'regress_rls_bob', 'my first novel'), + ( 2, 11, 2, 'regress_rls_bob', 'my second novel'), + ( 3, 99, 2, 'regress_rls_bob', 'my science textbook'), + ( 4, 55, 1, 'regress_rls_bob', 'my first satire'), + ( 5, 99, 2, 'regress_rls_bob', 'my history book'), + ( 6, 11, 1, 'regress_rls_carol', 'great science fiction'), + ( 7, 99, 2, 'regress_rls_carol', 'great technology book'), + ( 8, 55, 2, 'regress_rls_carol', 'great satire'), + ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'), + (10, 99, 2, 'regress_rls_dave', 'awesome technology book'); + +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 +CREATE POLICY pp1 ON part_document AS PERMISSIVE + USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user)); + +-- Dave is only allowed to see cid < 55 +CREATE POLICY pp1r ON part_document AS RESTRICTIVE TO regress_rls_dave + USING (cid < 55); + +\d+ part_document +SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname; + +-- viewpoint from regress_rls_bob +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + +-- viewpoint from regress_rls_carol +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + +-- viewpoint from regress_rls_dave +SET SESSION AUTHORIZATION regress_rls_dave; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + +-- pp1 ERROR +INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail +-- pp1r ERROR +INSERT INTO part_document VALUES (100, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail + +-- Show that RLS policy does not apply for direct inserts to children +-- This should fail with RLS POLICY pp1r violation. +INSERT INTO part_document VALUES (100, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail +-- But this should succeed. +INSERT INTO part_document_satire VALUES (100, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- success +-- We still cannot see the row using the parent +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +-- But we can if we look directly +SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did; + +-- Turn on RLS and create policy on child to show RLS is checked before constraints +SET SESSION AUTHORIZATION regress_rls_alice; +ALTER TABLE part_document_satire ENABLE ROW LEVEL SECURITY; +CREATE POLICY pp3 ON part_document_satire AS RESTRICTIVE + USING (cid < 55); +-- This should fail with RLS violation now. +SET SESSION AUTHORIZATION regress_rls_dave; +INSERT INTO part_document_satire VALUES (101, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail +-- And now we cannot see directly into the partition either, due to RLS +SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did; +-- The parent looks same as before +-- viewpoint from regress_rls_dave +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + +-- viewpoint from regress_rls_carol +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + +-- only owner can change policies +ALTER POLICY pp1 ON part_document USING (true); --fail +DROP POLICY pp1 ON part_document; --fail + +SET SESSION AUTHORIZATION regress_rls_alice; +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; + +-- viewpoint from rls_regres_carol again +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + +-- database superuser does bypass RLS policy when enabled +RESET SESSION AUTHORIZATION; +SET row_security TO ON; +SELECT * FROM part_document ORDER BY did; +SELECT * FROM part_document_satire ORDER by did; + +-- database non-superuser with bypass privilege can bypass RLS policy when disabled +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO OFF; +SELECT * FROM part_document ORDER BY did; +SELECT * FROM part_document_satire ORDER by did; + +-- RLS policy does not apply to table owner when RLS enabled. +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +SELECT * FROM part_document ORDER by did; +SELECT * FROM part_document_satire ORDER by did; + +-- When RLS disabled, other users get ERROR. +SET SESSION AUTHORIZATION regress_rls_dave; +SET row_security TO OFF; +SELECT * FROM part_document ORDER by did; +SELECT * FROM part_document_satire ORDER by did; + +-- Check behavior with a policy that uses a SubPlan not an InitPlan. +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +CREATE POLICY pp3 ON part_document AS RESTRICTIVE + USING ((SELECT dlevel <= seclv FROM uaccount WHERE pguser = current_user)); + +SET SESSION AUTHORIZATION regress_rls_carol; +INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_carol', 'testing pp3'); -- fail + +----- Dependencies ----- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; + +CREATE TABLE dependee (x integer, y integer); + +CREATE TABLE dependent (x integer, y integer); +CREATE POLICY d1 ON dependent FOR ALL + TO PUBLIC + USING (x = (SELECT d.x FROM dependee d WHERE d.y = y)); + +DROP TABLE dependee; -- Should fail without CASCADE due to dependency on row security qual? + +DROP TABLE dependee CASCADE; + +EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified + +----- RECURSION ---- + +-- +-- Simple recursion +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE rec1 (x integer, y integer); +CREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y)); +ALTER TABLE rec1 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rec1; -- fail, direct recursion + +-- +-- Mutual recursion +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE rec2 (a integer, b integer); +ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y)); +CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b)); +ALTER TABLE rec2 ENABLE ROW LEVEL SECURITY; + +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rec1; -- fail, mutual recursion + +-- +-- Mutual recursion via views +-- +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW rec1v AS SELECT * FROM rec1; +CREATE VIEW rec2v AS SELECT * FROM rec2; +SET SESSION AUTHORIZATION regress_rls_alice; +ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y)); +ALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b)); + +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rec1; -- fail, mutual recursion via views + +-- +-- Mutual recursion via .s.b views +-- +SET SESSION AUTHORIZATION regress_rls_bob; + +DROP VIEW rec1v, rec2v CASCADE; -- YB: fails because drop multiple objects not supported (see #880) +DROP VIEW rec1v CASCADE; -- YB: workaround +DROP VIEW rec2v CASCADE; -- YB: workaround + +CREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1; +CREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2; +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y)); +CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b)); + +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rec1; -- fail, mutual recursion via s.b. views + +-- +-- recursive RLS and VIEWs in policy +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE s1 (a int, b text); +INSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x); + +CREATE TABLE s2 (x int, y text); +INSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x); + +GRANT SELECT ON s1, s2 TO regress_rls_bob; + +CREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%')); +CREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%')); +CREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1)); + +ALTER TABLE s1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE s2 ENABLE ROW LEVEL SECURITY; + +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%'; +SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion) + +INSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion) + +SET SESSION AUTHORIZATION regress_rls_alice; +DROP POLICY p3 on s1; +ALTER POLICY p2 ON s2 USING (x % 2 = 0); + +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM s1 WHERE f_leak(b); -- OK +EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b); + +SET SESSION AUTHORIZATION regress_rls_alice; +ALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM s1 WHERE f_leak(b); -- OK +EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b); + +SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%'; +EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%'; + +SET SESSION AUTHORIZATION regress_rls_alice; +ALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%')); +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion via view) + +-- prepared statement with regress_rls_alice privilege +PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1; +EXECUTE p1(2); +EXPLAIN (COSTS OFF) EXECUTE p1(2); + +-- superuser is allowed to bypass RLS checks +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM t1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + +-- plan cache should be invalidated +EXECUTE p1(2); +EXPLAIN (COSTS OFF) EXECUTE p1(2); + +PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1; +EXECUTE p2(2); +EXPLAIN (COSTS OFF) EXECUTE p2(2); + +-- also, case when privilege switch from superuser +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +EXECUTE p2(2); +EXPLAIN (COSTS OFF) EXECUTE p2(2); + +-- +-- UPDATE / DELETE and Row-level security +-- +SET SESSION AUTHORIZATION regress_rls_bob; +EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b); +UPDATE t1 SET b = b || b WHERE f_leak(b); + +EXPLAIN (COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b); +UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b); + +-- returning clause with system column +UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; +UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *; +UPDATE t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; + +-- updates with from clause +EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t3 +WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b); + +UPDATE t2 SET b=t2.b FROM t3 +WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b); + +EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); + +UPDATE t1 SET b=t1.b FROM t2 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); + +EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); + +UPDATE t2 SET b=t2.b FROM t1 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); + +-- updates with from clause self join +EXPLAIN (COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2 +WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b +AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2; + +UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2 +WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b +AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2; + +EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 +WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b +AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; + +UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 +WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b +AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; + +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM t1 ORDER BY a,b; + +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b); + +/* YB: these queries fail in YB and are tracked by #26577 +DELETE FROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; +DELETE FROM t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; +*/ -- YB + +-- +-- S.b. view on top of Row-level security +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE b1 (a int, b text); +INSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x); + +CREATE POLICY p1 ON b1 USING (a % 2 = 0); +ALTER TABLE b1 ENABLE ROW LEVEL SECURITY; +GRANT ALL ON b1 TO regress_rls_bob; + +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION; +-- YB: port further queries when above works + +-- +-- INSERT ... ON CONFLICT DO UPDATE and Row-level security +-- + +SET SESSION AUTHORIZATION regress_rls_alice; +DROP POLICY p1 ON document; +DROP POLICY p1r ON document; + +CREATE POLICY p1 ON document FOR SELECT USING (true); +CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user); +CREATE POLICY p3 ON document FOR UPDATE + USING (cid = (SELECT cid from category WHERE cname = 'novel')) + WITH CHECK (dauthor = current_user); + +SET SESSION AUTHORIZATION regress_rls_bob; + +-- Exists... +SELECT * FROM document WHERE did = 2; + +-- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since +-- alternative UPDATE path happens to be taken): +INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor; + +-- Violates USING qual for UPDATE policy p3. +-- +-- UPDATE path is taken, but UPDATE fails purely because *existing* row to be +-- updated is not a "novel"/cid 11 (row is not leaked, even though we have +-- SELECT privileges sufficient to see the row in this instance): +INSERT INTO document VALUES (33, 22, 1, 'regress_rls_bob', 'okay science fiction'); -- preparation for next statement +INSERT INTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'Some novel, replaces sci-fi') -- takes UPDATE path + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle; +-- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs +-- not violated): +INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; +-- Fine (we INSERT, so "cid = 33" ("technology") isn't evaluated): +INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; +-- Fine (same query, but we UPDATE, so "cid = 33", ("technology") is not the +-- case in respect of *existing* tuple): +INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; +-- Same query a third time, but now fails due to existing tuple finally not +-- passing quals: +INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; +-- Don't fail just because INSERT doesn't satisfy WITH CHECK option that +-- originated as a barrier/USING() qual from the UPDATE. Note that the UPDATE +-- path *isn't* taken, and so UPDATE-related policy does not apply: +INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; +-- But this time, the same statement fails, because the UPDATE path is taken, +-- and updating the row just inserted falls afoul of security barrier qual +-- (enforced as WCO) -- what we might have updated target tuple to is +-- irrelevant, in fact. +INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; + +-- Test default USING qual enforced as WCO +SET SESSION AUTHORIZATION regress_rls_alice; +DROP POLICY p1 ON document; +DROP POLICY p2 ON document; +DROP POLICY p3 ON document; + +CREATE POLICY p3_with_default ON document FOR UPDATE + USING (cid = (SELECT cid from category WHERE cname = 'novel')); + +SET SESSION AUTHORIZATION regress_rls_bob; +-- Just because WCO-style enforcement of USING quals occurs with +-- existing/target tuple does not mean that the implementation can be allowed +-- to fail to also enforce this qual against the final tuple appended to +-- relation (since in the absence of an explicit WCO, this is also interpreted +-- as an UPDATE/ALL WCO in general). +-- +-- UPDATE path is taken here (fails due to existing tuple). Note that this is +-- not reported as a "USING expression", because it's an RLS UPDATE check that originated as +-- a USING qual for the purposes of RLS in general, as opposed to an explicit +-- USING qual that is ordinarily a security barrier. We leave it up to the +-- UPDATE to make this fail: +INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; + +-- UPDATE path is taken here. Existing tuple passes, since its cid +-- corresponds to "novel", but default USING qual is enforced against +-- post-UPDATE tuple too (as always when updating with a policy that lacks an +-- explicit WCO), and so this fails: +INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *; + +SET SESSION AUTHORIZATION regress_rls_alice; +DROP POLICY p3_with_default ON document; + +-- +-- Test ALL policies with ON CONFLICT DO UPDATE (much the same as existing UPDATE +-- tests) +-- +CREATE POLICY p3_with_all ON document FOR ALL + USING (cid = (SELECT cid from category WHERE cname = 'novel')) + WITH CHECK (dauthor = current_user); + +SET SESSION AUTHORIZATION regress_rls_bob; + +-- Fails, since ALL WCO is enforced in insert path: +INSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33; +-- Fails, since ALL policy USING qual is enforced (existing, target tuple is in +-- violation, since it has the "manga" cid): +INSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle; +-- Fails, since ALL WCO are enforced: +INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol'; + +-- +-- MERGE +-- +RESET SESSION AUTHORIZATION; +DROP POLICY p3_with_all ON document; + +ALTER TABLE document ADD COLUMN dnotes text DEFAULT ''; +-- all documents are readable +CREATE POLICY p1 ON document FOR SELECT USING (true); +-- one may insert documents only authored by them +CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user); +-- one may only update documents in 'novel' category +CREATE POLICY p3 ON document FOR UPDATE + USING (cid = (SELECT cid from category WHERE cname = 'novel')) + WITH CHECK (dauthor = current_user); +-- one may only delete documents in 'manga' category +CREATE POLICY p4 ON document FOR DELETE + USING (cid = (SELECT cid from category WHERE cname = 'manga')); + +SELECT * FROM document ORDER BY (CASE WHEN (did = 2) THEN 50 ELSE did END); -- YB: add ordering + +SET SESSION AUTHORIZATION regress_rls_bob; + +-- Fails, since update violates WITH CHECK qual on dauthor +MERGE INTO document d +USING (SELECT 1 as sdid) s +ON did = s.sdid +WHEN MATCHED THEN + UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dauthor = 'regress_rls_alice'; +-- YB: port further queries when above works + +-- +-- ROLE/GROUP +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE z1 (a int, b text); +CREATE TABLE z2 (a int, b text); + +GRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2, + regress_rls_bob, regress_rls_carol; + +INSERT INTO z1 VALUES + (1, 'aba'), + (2, 'bbb'), + (3, 'ccc'), + (4, 'dad'); + +CREATE POLICY p1 ON z1 TO regress_rls_group1 USING (a % 2 = 0); +CREATE POLICY p2 ON z1 TO regress_rls_group2 USING (a % 2 = 1); + +ALTER TABLE z1 ENABLE ROW LEVEL SECURITY; + +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM z1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + +PREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) EXECUTE plancache_test; + +PREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2; +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; + +PREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b); +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + +SET ROLE regress_rls_group1; +SELECT * FROM z1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + +EXPLAIN (COSTS OFF) EXECUTE plancache_test; +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM z1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + +EXPLAIN (COSTS OFF) EXECUTE plancache_test; +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + +SET ROLE regress_rls_group2; +SELECT * FROM z1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + +EXPLAIN (COSTS OFF) EXECUTE plancache_test; +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + +-- +-- Views should follow policy for view owner. +-- +-- View and Table owner are the same. +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); +GRANT SELECT ON rls_view TO regress_rls_bob; + +-- Query as role that is not owner of view or table. Should return all records. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +-- Query as view/table owner. Should return all records. +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; +DROP VIEW rls_view; + +-- View and Table owners are different. +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); +GRANT SELECT ON rls_view TO regress_rls_alice; + +-- Query as role that is not owner of view but is owner of table. +-- Should return records based on view owner policies. +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +-- Query as role that is not owner of table but is owner of view. +-- Should return records based on view owner policies. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +-- Query as role that is not the owner of the table or view without permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; --fail - permission denied. +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. + +-- Query as role that is not the owner of the table or view with permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +GRANT SELECT ON rls_view TO regress_rls_carol; + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +-- Policy requiring access to another table. +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE z1_blacklist (a int); +INSERT INTO z1_blacklist VALUES (3), (4); +CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist)); + +-- Query as role that is not owner of table but is owner of view without permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; --fail - permission denied. +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. + +-- Query as role that is not the owner of the table or view without permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; --fail - permission denied. +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. + +-- Query as role that is not owner of table but is owner of view with permissions. +SET SESSION AUTHORIZATION regress_rls_alice; +GRANT SELECT ON z1_blacklist TO regress_rls_bob; + +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +-- Query as role that is not the owner of the table or view with permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +SET SESSION AUTHORIZATION regress_rls_alice; +REVOKE SELECT ON z1_blacklist FROM regress_rls_bob; +DROP POLICY p3 ON z1; + +SET SESSION AUTHORIZATION regress_rls_bob; +DROP VIEW rls_view; + +-- +-- Security invoker views should follow policy for current user. +-- +-- View and table owner are the same. +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE VIEW rls_view WITH (security_invoker) AS + SELECT * FROM z1 WHERE f_leak(b); +GRANT SELECT ON rls_view TO regress_rls_bob; +GRANT SELECT ON rls_view TO regress_rls_carol; + +-- Query as table owner. Should return all records. +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +-- Queries as other users. +-- Should return records based on current user's policies. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +-- View and table owners are different. +SET SESSION AUTHORIZATION regress_rls_alice; +DROP VIEW rls_view; + +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW rls_view WITH (security_invoker) AS + SELECT * FROM z1 WHERE f_leak(b); +GRANT SELECT ON rls_view TO regress_rls_alice; +GRANT SELECT ON rls_view TO regress_rls_carol; + +-- Query as table owner. Should return all records. +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +-- Queries as other users. +-- Should return records based on current user's policies. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +-- Policy requiring access to another table. +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist)); + +-- Query as role that is not owner of table but is owner of view without permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; --fail - permission denied. +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. + +-- Query as role that is not the owner of the table or view without permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; --fail - permission denied. +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. + +-- Query as role that is not owner of table but is owner of view with permissions. +SET SESSION AUTHORIZATION regress_rls_alice; +GRANT SELECT ON z1_blacklist TO regress_rls_bob; + +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +-- Query as role that is not the owner of the table or view without permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; --fail - permission denied. +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. + +-- Query as role that is not the owner of the table or view with permissions. +SET SESSION AUTHORIZATION regress_rls_alice; +GRANT SELECT ON z1_blacklist TO regress_rls_carol; + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM rls_view; +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + +SET SESSION AUTHORIZATION regress_rls_bob; +DROP VIEW rls_view; + +-- +-- Command specific +-- +SET SESSION AUTHORIZATION regress_rls_alice; + +CREATE TABLE x1 (a int, b text, c text); +GRANT ALL ON x1 TO PUBLIC; + +INSERT INTO x1 VALUES + (1, 'abc', 'regress_rls_bob'), + (2, 'bcd', 'regress_rls_bob'), + (3, 'cde', 'regress_rls_carol'), + (4, 'def', 'regress_rls_carol'), + (5, 'efg', 'regress_rls_bob'), + (6, 'fgh', 'regress_rls_bob'), + (7, 'fgh', 'regress_rls_carol'), + (8, 'fgh', 'regress_rls_carol'); + +CREATE POLICY p0 ON x1 FOR ALL USING (c = current_user); +CREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0); +CREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1); +CREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0); +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; +UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *; + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC; +UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *; +DELETE FROM x1 WHERE f_leak(b) RETURNING *; + +-- +-- Duplicate Policy Names +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE y1 (a int, b text); +CREATE TABLE y2 (a int, b text); + +GRANT ALL ON y1, y2 TO regress_rls_bob; + +CREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0); +CREATE POLICY p2 ON y1 FOR SELECT USING (a > 2); +CREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1); --fail +CREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0); --OK + +ALTER TABLE y1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE y2 ENABLE ROW LEVEL SECURITY; + +-- +-- Expression structure with SBV +-- +-- Create view as table owner. RLS should NOT be applied. +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE VIEW rls_sbv WITH (security_barrier) AS + SELECT * FROM y1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1); +DROP VIEW rls_sbv; + +-- Create view as role that does not own table. RLS should be applied. +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW rls_sbv WITH (security_barrier) AS + SELECT * FROM y1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1); +DROP VIEW rls_sbv; + +-- +-- Expression structure +-- +SET SESSION AUTHORIZATION regress_rls_alice; +INSERT INTO y2 (SELECT x, md5(x::text) FROM generate_series(0,20) x); +CREATE POLICY p2 ON y2 USING (a % 3 = 0); +CREATE POLICY p3 ON y2 USING (a % 4 = 0); + +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM y2 WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b); + +-- +-- Qual push-down of leaky functions, when not referring to table +-- +SELECT * FROM y2 WHERE f_leak('abc'); -- YB: output has difference in number of NOTICEs due to expression pushdown +EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc'); + +CREATE TABLE test_qual_pushdown ( + abc text +); + +INSERT INTO test_qual_pushdown VALUES ('abc'),('def'); + +SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc); +EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc); + +SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b); +EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b); + +DROP TABLE test_qual_pushdown; + +-- +-- Plancache invalidate on user change. +-- +RESET SESSION AUTHORIZATION; + +\set VERBOSITY terse \\ -- YB: suppress cascade details +DROP TABLE t1 CASCADE; +\set VERBOSITY default \\ -- YB + +CREATE TABLE t1 (a integer); + +GRANT SELECT ON t1 TO regress_rls_bob, regress_rls_carol; + +CREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0); +CREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0); + +ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; + +-- Prepare as regress_rls_bob +SET ROLE regress_rls_bob; +PREPARE role_inval AS SELECT * FROM t1; +-- Check plan +EXPLAIN (COSTS OFF) EXECUTE role_inval; + +-- Change to regress_rls_carol +SET ROLE regress_rls_carol; +-- Check plan- should be different +EXPLAIN (COSTS OFF) EXECUTE role_inval; + +-- Change back to regress_rls_bob +SET ROLE regress_rls_bob; +-- Check plan- should be back to original +EXPLAIN (COSTS OFF) EXECUTE role_inval; + +-- +-- CTE and RLS +-- +RESET SESSION AUTHORIZATION; +DROP TABLE t1 CASCADE; +CREATE TABLE t1 (a integer, b text); +CREATE POLICY p1 ON t1 USING (a % 2 = 0); + +ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; + +GRANT ALL ON t1 TO regress_rls_bob; + +INSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(0,20) x); + +SET SESSION AUTHORIZATION regress_rls_bob; + +WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1; +EXPLAIN (COSTS OFF) +WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1; + +WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail +WITH ybtmp AS (DELETE FROM t1 RETURNING *) + INSERT INTO t1 SELECT * FROM ybtmp; -- YB: imitate PG ctid reallocation for the following update +WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok + +WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail +WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok + +-- +-- Rename Policy +-- +RESET SESSION AUTHORIZATION; +ALTER POLICY p1 ON t1 RENAME TO p1; --fail + +SELECT polname, relname + FROM pg_policy pol + JOIN pg_class pc ON (pc.oid = pol.polrelid) + WHERE relname = 't1'; + +ALTER POLICY p1 ON t1 RENAME TO p2; --ok + +SELECT polname, relname + FROM pg_policy pol + JOIN pg_class pc ON (pc.oid = pol.polrelid) + WHERE relname = 't1'; + +-- +-- Check INSERT SELECT +-- +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE TABLE t2 (a integer, b text); +INSERT INTO t2 (SELECT * FROM t1); +EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM t1); +SELECT * FROM t2; +EXPLAIN (COSTS OFF) SELECT * FROM t2; +CREATE TABLE t3 AS SELECT * FROM t1; +SELECT * FROM t3; +SELECT * INTO t4 FROM t1; +SELECT * FROM t4; + +-- +-- RLS with JOIN +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE blog (id integer, author text, post text); +CREATE TABLE comment (blog_id integer, message text); + +GRANT ALL ON blog, comment TO regress_rls_bob; + +CREATE POLICY blog_1 ON blog USING (id % 2 = 0); + +ALTER TABLE blog ENABLE ROW LEVEL SECURITY; + +INSERT INTO blog VALUES + (1, 'alice', 'blog #1'), + (2, 'bob', 'blog #1'), + (3, 'alice', 'blog #2'), + (4, 'alice', 'blog #3'), + (5, 'john', 'blog #1'); + +INSERT INTO comment VALUES + (1, 'cool blog'), + (1, 'fun blog'), + (3, 'crazy blog'), + (5, 'what?'), + (4, 'insane!'), + (2, 'who did it?'); + +SET SESSION AUTHORIZATION regress_rls_bob; +-- Check RLS JOIN with Non-RLS. +/*+ HashJoin(comment blog) Leading((comment blog)) */ -- YB: use pg_hint to ensure same query plan and result ordering as postgres +SELECT id, author, message FROM blog JOIN comment ON id = blog_id; +-- Check Non-RLS JOIN with RLS. +/*+ HashJoin(comment blog) Leading((comment blog)) */ -- YB: use pg_hint to ensure same query plan and result ordering as postgres +SELECT id, author, message FROM comment JOIN blog ON id = blog_id; + +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE POLICY comment_1 ON comment USING (blog_id < 4); + +ALTER TABLE comment ENABLE ROW LEVEL SECURITY; + +SET SESSION AUTHORIZATION regress_rls_bob; +-- Check RLS JOIN RLS +SELECT id, author, message FROM blog JOIN comment ON id = blog_id; +SELECT id, author, message FROM comment JOIN blog ON id = blog_id; + +SET SESSION AUTHORIZATION regress_rls_alice; +DROP TABLE blog, comment; + +-- +-- Default Deny Policy +-- +RESET SESSION AUTHORIZATION; +DROP POLICY p2 ON t1; +ALTER TABLE t1 OWNER TO regress_rls_alice; + +-- Check that default deny does not apply to superuser. +RESET SESSION AUTHORIZATION; +SELECT * FROM t1; +EXPLAIN (COSTS OFF) SELECT * FROM t1; + +-- Check that default deny does not apply to table owner. +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM t1; +EXPLAIN (COSTS OFF) SELECT * FROM t1; + +-- Check that default deny applies to non-owner/non-superuser when RLS on. +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +SELECT * FROM t1; +EXPLAIN (COSTS OFF) SELECT * FROM t1; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM t1; +EXPLAIN (COSTS OFF) SELECT * FROM t1; + +-- +-- COPY TO/FROM +-- + +RESET SESSION AUTHORIZATION; +DROP TABLE copy_t CASCADE; +CREATE TABLE copy_t (a integer, b text); +CREATE POLICY p1 ON copy_t USING (a % 2 = 0); + +ALTER TABLE copy_t ENABLE ROW LEVEL SECURITY; + +GRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user; + +INSERT INTO copy_t (SELECT x, md5(x::text) FROM generate_series(0,10) x); + +-- Check COPY TO as Superuser/owner. +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; + +-- Check COPY TO as user with permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO OFF; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok + +-- Check COPY TO as user with permissions and BYPASSRLS +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO OFF; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok + +-- Check COPY TO as user without permissions. SET row_security TO OFF; +SET SESSION AUTHORIZATION regress_rls_carol; +SET row_security TO OFF; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied + +-- Check COPY relation TO; keep it just one row to avoid reordering issues +RESET SESSION AUTHORIZATION; +SET row_security TO ON; +CREATE TABLE copy_rel_to (a integer, b text); +CREATE POLICY p1 ON copy_rel_to USING (a % 2 = 0); + +ALTER TABLE copy_rel_to ENABLE ROW LEVEL SECURITY; + +GRANT ALL ON copy_rel_to TO regress_rls_bob, regress_rls_exempt_user; + +INSERT INTO copy_rel_to VALUES (1, md5('1')); + +-- Check COPY TO as Superuser/owner. +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; +SET row_security TO ON; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; + +-- Check COPY TO as user with permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO OFF; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS +SET row_security TO ON; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok + +-- Check COPY TO as user with permissions and BYPASSRLS +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO OFF; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok +SET row_security TO ON; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok + +-- Check COPY TO as user without permissions. SET row_security TO OFF; +SET SESSION AUTHORIZATION regress_rls_carol; +SET row_security TO OFF; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied +SET row_security TO ON; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied + +-- Check COPY FROM as Superuser/owner. +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +COPY copy_t FROM STDIN; --ok +1 abc +2 bcd +3 cde +4 def +\. +SET row_security TO ON; +COPY copy_t FROM STDIN; --ok +1 abc +2 bcd +3 cde +4 def +\. + +-- Check COPY FROM as user with permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO OFF; +COPY copy_t FROM STDIN; --fail - would be affected by RLS. +SET row_security TO ON; +COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS. + +-- Check COPY FROM as user with permissions and BYPASSRLS +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO ON; +COPY copy_t FROM STDIN; --ok +1 abc +2 bcd +3 cde +4 def +\. + +-- Check COPY FROM as user without permissions. +SET SESSION AUTHORIZATION regress_rls_carol; +SET row_security TO OFF; +COPY copy_t FROM STDIN; --fail - permission denied. +SET row_security TO ON; +COPY copy_t FROM STDIN; --fail - permission denied. + +RESET SESSION AUTHORIZATION; +DROP TABLE copy_t; +DROP TABLE copy_rel_to CASCADE; + +-- Check WHERE CURRENT OF +SET SESSION AUTHORIZATION regress_rls_alice; + +CREATE TABLE current_check (currentid int, payload text, rlsuser text); +GRANT ALL ON current_check TO PUBLIC; + +INSERT INTO current_check VALUES + (1, 'abc', 'regress_rls_bob'), + (2, 'bcd', 'regress_rls_bob'), + (3, 'cde', 'regress_rls_bob'), + (4, 'def', 'regress_rls_bob'); + +CREATE POLICY p1 ON current_check FOR SELECT USING (currentid % 2 = 0); +CREATE POLICY p2 ON current_check FOR DELETE USING (currentid = 4 AND rlsuser = current_user); +CREATE POLICY p3 ON current_check FOR UPDATE USING (currentid = 4) WITH CHECK (rlsuser = current_user); + +ALTER TABLE current_check ENABLE ROW LEVEL SECURITY; + +SET SESSION AUTHORIZATION regress_rls_bob; + +-- Can SELECT even rows +SELECT * FROM current_check; + +-- Cannot UPDATE row 2 +UPDATE current_check SET payload = payload || '_new' WHERE currentid = 2 RETURNING *; + +BEGIN; + +DECLARE current_check_cursor SCROLL CURSOR FOR SELECT * FROM current_check; +-- Returns rows that can be seen according to SELECT policy, like plain SELECT +-- above (even rows) +FETCH ABSOLUTE 1 FROM current_check_cursor; +-- YB: Port further queries when above is supported + +COMMIT; + +-- +-- check pg_stats view filtering +-- +SET row_security TO ON; +SET SESSION AUTHORIZATION regress_rls_alice; +ANALYZE current_check; +-- Stats visible +SELECT row_security_active('current_check'); +SELECT attname, most_common_vals FROM pg_stats + WHERE tablename = 'current_check' + ORDER BY 1; + +SET SESSION AUTHORIZATION regress_rls_bob; +-- Stats not visible +SELECT row_security_active('current_check'); +SELECT attname, most_common_vals FROM pg_stats + WHERE tablename = 'current_check' + ORDER BY 1; + +-- +-- Collation support +-- +BEGIN; +CREATE TABLE coll_t (c) AS VALUES ('bar'::text); +CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C")); +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; +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM coll_t; +ROLLBACK; +DROP TABLE coll_t; -- YB: workaround for lack of transaction DDL + +-- +-- Shared Object Dependencies +-- +RESET SESSION AUTHORIZATION; +BEGIN; +CREATE ROLE regress_rls_eve; +CREATE ROLE regress_rls_frank; +CREATE TABLE tbl1 (c) AS VALUES ('bar'::text); +GRANT SELECT ON TABLE tbl1 TO regress_rls_eve; +CREATE POLICY P ON tbl1 TO regress_rls_eve, regress_rls_frank USING (true); +SELECT refclassid::regclass, deptype + FROM pg_depend + WHERE classid = 'pg_policy'::regclass + AND refobjid = 'tbl1'::regclass; +SELECT refclassid::regclass, deptype + FROM pg_shdepend + WHERE classid = 'pg_policy'::regclass + AND refobjid IN ('regress_rls_eve'::regrole, 'regress_rls_frank'::regrole); + +SAVEPOINT q; +DROP ROLE regress_rls_eve; --fails due to dependency on POLICY p +ROLLBACK TO q; + +ALTER POLICY p ON tbl1 TO regress_rls_frank USING (true); +SAVEPOINT q; +DROP ROLE regress_rls_eve; --fails due to dependency on GRANT SELECT +ROLLBACK TO q; + +REVOKE ALL ON TABLE tbl1 FROM regress_rls_eve; +SAVEPOINT q; +DROP ROLE regress_rls_eve; --succeeds +ROLLBACK TO q; + +SAVEPOINT q; +DROP ROLE regress_rls_frank; --fails due to dependency on POLICY p +ROLLBACK TO q; + +DROP POLICY p ON tbl1; +SAVEPOINT q; +DROP ROLE regress_rls_frank; -- succeeds +ROLLBACK TO q; + +ROLLBACK; -- cleanup +DROP TABLE tbl1; -- YB: workaround for lack of transactional DDL + +-- +-- Converting table to view +-- +BEGIN; +CREATE TABLE t (c int); +CREATE POLICY p ON t USING (c % 2 = 1); +ALTER TABLE t ENABLE ROW LEVEL SECURITY; + +SAVEPOINT q; +CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD + SELECT * FROM generate_series(1,5) t0(c); -- fails due to row-level security enabled +ROLLBACK TO q; + +ALTER TABLE t DISABLE ROW LEVEL SECURITY; +SAVEPOINT q; +CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD + SELECT * FROM generate_series(1,5) t0(c); -- fails due to policy p on t +ROLLBACK TO q; + +DROP POLICY p ON t; +CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD + SELECT * FROM generate_series(1,5) t0(c); -- succeeds +ROLLBACK; +DROP VIEW t; -- YB: manually ROLLBACK and DROP the VIEW conversion above to prevent conflict with CREATE TABLE below (see #1404) + +-- +-- Policy expression handling +-- +BEGIN; +CREATE TABLE t (c) AS VALUES ('bar'::text); +CREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions +ROLLBACK; +DROP TABLE t; -- YB: workaround for lack of transactional DDL + +-- +-- Non-target relations are only subject to SELECT policies +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE r1 (a int); +CREATE TABLE r2 (a int); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO r2 VALUES (10), (20); + +GRANT ALL ON r1, r2 TO regress_rls_bob; + +CREATE POLICY p1 ON r1 USING (true); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; + +CREATE POLICY p1 ON r2 FOR SELECT USING (true); +CREATE POLICY p2 ON r2 FOR INSERT WITH CHECK (false); +CREATE POLICY p3 ON r2 FOR UPDATE USING (false); +CREATE POLICY p4 ON r2 FOR DELETE USING (false); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; + +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM r1; +SELECT * FROM r2; + +-- r2 is read-only +INSERT INTO r2 VALUES (2); -- Not allowed +UPDATE r2 SET a = 2 RETURNING *; -- Updates nothing +DELETE FROM r2 RETURNING *; -- Deletes nothing + +-- r2 can be used as a non-target relation in DML +INSERT INTO r1 SELECT a + 1 FROM r2 RETURNING *; -- OK +UPDATE r1 SET a = r2.a + 2 FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK +DELETE FROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK +SELECT * FROM r1; +SELECT * FROM r2; + +SET SESSION AUTHORIZATION regress_rls_alice; +DROP TABLE r1; +DROP TABLE r2; + +-- +-- FORCE ROW LEVEL SECURITY applies RLS to owners too +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security = on; +CREATE TABLE r1 (a int); +INSERT INTO r1 VALUES (10), (20); + +CREATE POLICY p1 ON r1 USING (false); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; + +-- No error, but no rows +TABLE r1; + +-- RLS error +INSERT INTO r1 VALUES (1); + +-- No error (unable to see any rows to update) +UPDATE r1 SET a = 1; +TABLE r1; + +-- No error (unable to see any rows to delete) +DELETE FROM r1; +TABLE r1; + +SET row_security = off; +-- these all fail, would be affected by RLS +TABLE r1; +UPDATE r1 SET a = 1; +DELETE FROM r1; + +DROP TABLE r1; + +-- +-- FORCE ROW LEVEL SECURITY does not break RI +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security = on; +CREATE TABLE r1 (a int PRIMARY KEY); +CREATE TABLE r2 (a int REFERENCES r1); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO r2 VALUES (10), (20); + +-- Create policies on r2 which prevent the +-- owner from seeing any rows, but RI should +-- still see them. +CREATE POLICY p1 ON r2 USING (false); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r2 FORCE ROW LEVEL SECURITY; + +-- Errors due to rows in r2 +DELETE FROM r1; + +-- Reset r2 to no-RLS +DROP POLICY p1 ON r2; +ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; +ALTER TABLE r2 DISABLE ROW LEVEL SECURITY; + +-- clean out r2 for INSERT test below +DELETE FROM r2; + +-- Change r1 to not allow rows to be seen +CREATE POLICY p1 ON r1 USING (false); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; + +-- No rows seen +TABLE r1; + +-- No error, RI still sees that row exists in r1 +INSERT INTO r2 VALUES (10); + +DROP TABLE r2; +DROP TABLE r1; + +-- Ensure cascaded DELETE works +CREATE TABLE r1 (a int PRIMARY KEY); +CREATE TABLE r2 (a int REFERENCES r1 ON DELETE CASCADE); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO r2 VALUES (10), (20); + +-- Create policies on r2 which prevent the +-- owner from seeing any rows, but RI should +-- still see them. +CREATE POLICY p1 ON r2 USING (false); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r2 FORCE ROW LEVEL SECURITY; + +-- Deletes all records from both +DELETE FROM r1; + +-- Remove FORCE from r2 +ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; + +-- As owner, we now bypass RLS +-- verify no rows in r2 now +TABLE r2; + +DROP TABLE r2; +DROP TABLE r1; + +-- Ensure cascaded UPDATE works +CREATE TABLE r1 (a int PRIMARY KEY); +CREATE TABLE r2 (a int REFERENCES r1 ON UPDATE CASCADE); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO r2 VALUES (10), (20); + +-- Create policies on r2 which prevent the +-- owner from seeing any rows, but RI should +-- still see them. +CREATE POLICY p1 ON r2 USING (false); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r2 FORCE ROW LEVEL SECURITY; + +-- Updates records in both +UPDATE r1 SET a = a+5; + +-- Remove FORCE from r2 +ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; + +-- As owner, we now bypass RLS +-- verify records in r2 updated +TABLE r2; + +DROP TABLE r2; +DROP TABLE r1; + +-- +-- Test INSERT+RETURNING applies SELECT policies as +-- WithCheckOptions (meaning an error is thrown) +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security = on; +CREATE TABLE r1 (a int); + +CREATE POLICY p1 ON r1 FOR SELECT USING (false); +CREATE POLICY p2 ON r1 FOR INSERT WITH CHECK (true); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; + +-- Works fine +INSERT INTO r1 VALUES (10), (20); + +-- No error, but no rows +TABLE r1; + +SET row_security = off; +-- fail, would be affected by RLS +TABLE r1; + +SET row_security = on; + +-- Error +INSERT INTO r1 VALUES (10), (20) RETURNING *; + +DROP TABLE r1; + +-- +-- Test UPDATE+RETURNING applies SELECT policies as +-- WithCheckOptions (meaning an error is thrown) +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security = on; +CREATE TABLE r1 (a int PRIMARY KEY); + +CREATE POLICY p1 ON r1 FOR SELECT USING (a < 20); +CREATE POLICY p2 ON r1 FOR UPDATE USING (a < 20) WITH CHECK (true); +CREATE POLICY p3 ON r1 FOR INSERT WITH CHECK (true); +INSERT INTO r1 VALUES (10); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; + +-- Works fine +UPDATE r1 SET a = 30; + +-- Show updated rows +ALTER TABLE r1 NO FORCE ROW LEVEL SECURITY; +TABLE r1; +-- reset value in r1 for test with RETURNING +UPDATE r1 SET a = 10; + +-- Verify row reset +TABLE r1; + +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; + +-- Error +UPDATE r1 SET a = 30 RETURNING *; + +-- UPDATE path of INSERT ... ON CONFLICT DO UPDATE should also error out +INSERT INTO r1 VALUES (10) + ON CONFLICT (a) DO UPDATE SET a = 30 RETURNING *; + +-- Should still error out without RETURNING (use of arbiter always requires +-- SELECT permissions) +INSERT INTO r1 VALUES (10) + ON CONFLICT (a) DO UPDATE SET a = 30; +INSERT INTO r1 VALUES (10) + ON CONFLICT ON CONSTRAINT r1_pkey DO UPDATE SET a = 30; + +DROP TABLE r1; + +-- Check dependency handling +RESET SESSION AUTHORIZATION; +CREATE TABLE dep1 (c1 int); +CREATE TABLE dep2 (c1 int); + +CREATE POLICY dep_p1 ON dep1 TO regress_rls_bob USING (c1 > (select max(dep2.c1) from dep2)); +ALTER POLICY dep_p1 ON dep1 TO regress_rls_bob,regress_rls_carol; + +-- Should return one +SELECT count(*) = 1 FROM pg_depend + WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') + AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2'); + +ALTER POLICY dep_p1 ON dep1 USING (true); + +-- Should return one +SELECT count(*) = 1 FROM pg_shdepend + WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') + AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_bob'); + +-- Should return one +SELECT count(*) = 1 FROM pg_shdepend + WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') + AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_carol'); + +-- Should return zero +SELECT count(*) = 0 FROM pg_depend + WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') + AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2'); + +-- DROP OWNED BY testing +RESET SESSION AUTHORIZATION; + +CREATE ROLE regress_rls_dob_role1; +CREATE ROLE regress_rls_dob_role2; + +CREATE TABLE dob_t1 (c1 int); +CREATE TABLE dob_t2 (c1 int) PARTITION BY RANGE (c1); + +CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1 USING (true); +DROP OWNED BY regress_rls_dob_role1; +DROP POLICY p1 ON dob_t1; -- should fail, already gone + +CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true); +DROP OWNED BY regress_rls_dob_role1; +DROP POLICY p1 ON dob_t1; -- should succeed + +-- same cases with duplicate polroles entries +CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role1 USING (true); +DROP OWNED BY regress_rls_dob_role1; +DROP POLICY p1 ON dob_t1; -- should fail, already gone + +CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role1,regress_rls_dob_role2 USING (true); +DROP OWNED BY regress_rls_dob_role1; +DROP POLICY p1 ON dob_t1; -- should succeed + +-- partitioned target +CREATE POLICY p1 ON dob_t2 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true); +DROP OWNED BY regress_rls_dob_role1; +DROP POLICY p1 ON dob_t2; -- should succeed + +DROP USER regress_rls_dob_role1; +DROP USER regress_rls_dob_role2; + +-- Bug #15708: view + table with RLS should check policies as view owner +CREATE TABLE ref_tbl (a int); +INSERT INTO ref_tbl VALUES (1); + +CREATE TABLE rls_tbl (a int); +INSERT INTO rls_tbl VALUES (10); +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +CREATE POLICY p1 ON rls_tbl USING (EXISTS (SELECT 1 FROM ref_tbl)); + +GRANT SELECT ON ref_tbl TO regress_rls_bob; +GRANT SELECT ON rls_tbl TO regress_rls_bob; + +CREATE VIEW rls_view AS SELECT * FROM rls_tbl; +ALTER VIEW rls_view OWNER TO regress_rls_bob; +GRANT SELECT ON rls_view TO regress_rls_alice; + +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM ref_tbl; -- Permission denied +SELECT * FROM rls_tbl; -- Permission denied +SELECT * FROM rls_view; -- OK +RESET SESSION AUTHORIZATION; + +DROP VIEW rls_view; +DROP TABLE rls_tbl; +DROP TABLE ref_tbl; + +-- Leaky operator test +CREATE TABLE rls_tbl (a int); +INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x; +ANALYZE rls_tbl; + +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +GRANT SELECT ON rls_tbl TO regress_rls_alice; + +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE FUNCTION op_leak(int, int) RETURNS bool + AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END' + LANGUAGE plpgsql; +CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int, + restrict = scalarltsel); +SELECT * FROM rls_tbl WHERE a <<< 1000; +DROP OPERATOR <<< (int, int); +DROP FUNCTION op_leak(int, int); +RESET SESSION AUTHORIZATION; +DROP TABLE rls_tbl; + +-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE rls_tbl (a int, b int, c int); +CREATE POLICY p1 ON rls_tbl USING (rls_tbl >= ROW(1,1,1)); + +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +ALTER TABLE rls_tbl FORCE ROW LEVEL SECURITY; + +INSERT INTO rls_tbl SELECT 10, 20, 30; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rls_tbl + SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss; +INSERT INTO rls_tbl + SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss; +SELECT * FROM rls_tbl; + +DROP TABLE rls_tbl; +RESET SESSION AUTHORIZATION; + +-- CVE-2023-2455: inlining an SRF may introduce an RLS dependency +create table rls_t (c text); +insert into rls_t values ('invisible to bob'); +alter table rls_t enable row level security; +grant select on rls_t to regress_rls_alice, regress_rls_bob; +create policy p1 on rls_t for select to regress_rls_alice using (true); +create policy p2 on rls_t for select to regress_rls_bob using (false); +create function rls_f () returns setof rls_t + stable language sql + as $$ select * from rls_t $$; +prepare q as select current_user, * from rls_f(); +set role regress_rls_alice; +execute q; +set role regress_rls_bob; +execute q; + +-- make sure RLS dependencies in CTEs are handled +reset role; +create or replace function rls_f() returns setof rls_t + stable language sql + as $$ with cte as (select * from rls_t) select * from cte $$; +prepare r as select current_user, * from rls_f(); +set role regress_rls_alice; +execute r; +set role regress_rls_bob; +execute r; + +-- make sure RLS dependencies in subqueries are handled +reset role; +create or replace function rls_f() returns setof rls_t + stable language sql + as $$ select * from (select * from rls_t) _ $$; +prepare s as select current_user, * from rls_f(); +set role regress_rls_alice; +execute s; +set role regress_rls_bob; +execute s; + +-- make sure RLS dependencies in sublinks are handled +reset role; +create or replace function rls_f() returns setof rls_t + stable language sql + as $$ select exists(select * from rls_t)::text $$; +prepare t as select current_user, * from rls_f(); +set role regress_rls_alice; +execute t; +set role regress_rls_bob; +execute t; + +-- make sure RLS dependencies are handled when coercion projections are inserted +reset role; +create or replace function rls_f() returns setof rls_t + stable language sql + as $$ select * from (select array_agg(c) as cs from rls_t) _ group by cs $$; +prepare u as select current_user, * from rls_f(); +set role regress_rls_alice; +execute u; +set role regress_rls_bob; +execute u; + +-- make sure RLS dependencies in security invoker views are handled +reset role; +create view rls_v with (security_invoker) as select * from rls_t; +grant select on rls_v to regress_rls_alice, regress_rls_bob; +create or replace function rls_f() returns setof rls_t + stable language sql + as $$ select * from rls_v $$; +prepare v as select current_user, * from rls_f(); +set role regress_rls_alice; +execute v; +set role regress_rls_bob; +execute v; + +RESET ROLE; +DROP FUNCTION rls_f(); +DROP VIEW rls_v; +DROP TABLE rls_t; + +-- +-- Clean up objects +-- +RESET SESSION AUTHORIZATION; + +DROP SCHEMA regress_rls_schema CASCADE; -- YB: output lacks bv1 due to comment-out above + +DROP USER regress_rls_alice; +DROP USER regress_rls_bob; +DROP USER regress_rls_carol; +DROP USER regress_rls_dave; +DROP USER regress_rls_exempt_user; +DROP ROLE regress_rls_group1; +DROP ROLE regress_rls_group2; + +-- Arrange to have a few policies left over, for testing +-- pg_dump/pg_restore +CREATE SCHEMA regress_rls_schema; +CREATE TABLE rls_tbl (c1 int); +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +CREATE POLICY p1 ON rls_tbl USING (c1 > 5); +CREATE POLICY p2 ON rls_tbl FOR SELECT USING (c1 <= 3); +CREATE POLICY p3 ON rls_tbl FOR UPDATE USING (c1 <= 3) WITH CHECK (c1 > 5); +CREATE POLICY p4 ON rls_tbl FOR DELETE USING (c1 <= 3); + +CREATE TABLE rls_tbl_force (c1 int); +ALTER TABLE rls_tbl_force ENABLE ROW LEVEL SECURITY; +ALTER TABLE rls_tbl_force FORCE ROW LEVEL SECURITY; +CREATE POLICY p1 ON rls_tbl_force USING (c1 = 5) WITH CHECK (c1 < 5); +CREATE POLICY p2 ON rls_tbl_force FOR SELECT USING (c1 = 8); +CREATE POLICY p3 ON rls_tbl_force FOR UPDATE USING (c1 = 8) WITH CHECK (c1 >= 5); +CREATE POLICY p4 ON rls_tbl_force FOR DELETE USING (c1 = 8); diff --git a/src/test/regress/sql/yb.port.rowtypes.sql b/src/test/regress/sql/yb.port.rowtypes.sql new file mode 100644 index 0000000000000..8c74b2c0bc29f --- /dev/null +++ b/src/test/regress/sql/yb.port.rowtypes.sql @@ -0,0 +1,508 @@ +-- +-- ROWTYPES +-- + +-- Make both a standalone composite type and a table rowtype + +create type complex as (r float8, i float8); + +create temp table fullname (first text, last text); + +-- Nested composite + +create type quad as (c1 complex, c2 complex); + +-- Some simple tests of I/O conversions and row construction + +select (1.1,2.2)::complex, row((3.3,4.4),(5.5,null))::quad; + +select row('Joe', 'Blow')::fullname, '(Joe,Blow)'::fullname; + +select '(Joe,von Blow)'::fullname, '(Joe,d''Blow)'::fullname; + +select '(Joe,"von""Blow")'::fullname, E'(Joe,d\\\\Blow)'::fullname; + +select '(Joe,"Blow,Jr")'::fullname; + +select '(Joe,)'::fullname; -- ok, null 2nd column +select '(Joe)'::fullname; -- bad +select '(Joe,,)'::fullname; -- bad +select '[]'::fullname; -- bad +select ' (Joe,Blow) '::fullname; -- ok, extra whitespace +select '(Joe,Blow) /'::fullname; -- bad + +create temp table quadtable(f1 int, q quad); + +insert into quadtable values (1, ((3.3,4.4),(5.5,6.6))); +insert into quadtable values (2, ((null,4.4),(5.5,6.6))); + +select * from quadtable; + +select f1, q.c1 from quadtable; -- fails, q is a table reference + +select f1, (q).c1, (qq.q).c1.i from quadtable qq; + +create temp table people (fn fullname, bd date); + +insert into people values ('(Joe,Blow)', '1984-01-10'); + +select * from people; + +-- at the moment this will not work due to ALTER TABLE inadequacy: +alter table fullname add column suffix text default ''; + +-- but this should work: +alter table fullname add column suffix text default null; + +select * from people; + +-- test insertion/updating of subfields +update people set fn.suffix = 'Jr'; + +select * from people; + +insert into quadtable (f1, q.c1.r, q.c2.i) values(44,55,66); + +update quadtable set q.c1.r = 12 where f1 = 2; + +update quadtable set q.c1 = 12; -- error, type mismatch + +select * from quadtable; + +-- The object here is to ensure that toasted references inside +-- composite values don't cause problems. The large f1 value will +-- be toasted inside pp, it must still work after being copied to people. + +create temp table pp (f1 text); +-- insert into pp values (repeat('abcdefghijkl', 100000)); -- YB: TODO(jason): uncomment when issue #2003 is closed or closing. + +insert into people select ('Jim', f1, null)::fullname, current_date from pp; + +select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people; -- YB: output is missing a row due to commented out INSERT above + +-- Test row comparison semantics. Prior to PG 8.2 we did this in a totally +-- non-spec-compliant way. + +select ROW(1,2) < ROW(1,3) as true; +select ROW(1,2) < ROW(1,1) as false; +select ROW(1,2) < ROW(1,NULL) as null; +select ROW(1,2,3) < ROW(1,3,NULL) as true; -- the NULL is not examined +select ROW(11,'ABC') < ROW(11,'DEF') as true; +select ROW(11,'ABC') > ROW(11,'DEF') as false; +select ROW(12,'ABC') > ROW(11,'DEF') as true; + +-- = and <> have different NULL-behavior than < etc +select ROW(1,2,3) < ROW(1,NULL,4) as null; +select ROW(1,2,3) = ROW(1,NULL,4) as false; +select ROW(1,2,3) <> ROW(1,NULL,4) as true; + +-- We allow operators beyond the six standard ones, if they have btree +-- operator classes. +select ROW('ABC','DEF') ~<=~ ROW('DEF','ABC') as true; +select ROW('ABC','DEF') ~>=~ ROW('DEF','ABC') as false; +select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail; + +-- Comparisons of ROW() expressions can cope with some type mismatches +select ROW(1,2) = ROW(1,2::int8); +select ROW(1,2) in (ROW(3,4), ROW(1,2)); +select ROW(1,2) in (ROW(3,4), ROW(1,2::int8)); + +-- Check row comparison with a subselect +select unique1, unique2 from tenk1 +where (unique1, unique2) < any (select ten, ten from tenk1 where hundred < 3) + and unique1 <= 20 +order by 1; + +-- Also check row comparison with an indexable condition +explain (costs off) +select thousand, tenthous from tenk1 +where (thousand, tenthous) >= (997, 5000) +order by thousand, tenthous; + +select thousand, tenthous from tenk1 +where (thousand, tenthous) >= (997, 5000) +order by thousand, tenthous; + +explain (costs off) +select thousand, tenthous, four from tenk1 +where (thousand, tenthous, four) > (998, 5000, 3) +order by thousand, tenthous; -- YB: TODO(tim): update output when bitmap scan is supported + +select thousand, tenthous, four from tenk1 +where (thousand, tenthous, four) > (998, 5000, 3) +order by thousand, tenthous; + +explain (costs off) +select thousand, tenthous from tenk1 +where (998, 5000) < (thousand, tenthous) +order by thousand, tenthous; + +select thousand, tenthous from tenk1 +where (998, 5000) < (thousand, tenthous) +order by thousand, tenthous; + +explain (costs off) +select thousand, hundred from tenk1 +where (998, 5000) < (thousand, hundred) +order by thousand, hundred; -- YB: TODO(tim): update output when bitmap scan is supported + +select thousand, hundred from tenk1 +where (998, 5000) < (thousand, hundred) +order by thousand, hundred; + +-- Test case for bug #14010: indexed row comparisons fail with nulls +create temp table test_table (a text, b text); +insert into test_table values ('a', 'b'); +insert into test_table select 'a', null from generate_series(1,1000); +insert into test_table values ('b', 'a'); +create index on test_table (a,b); +set enable_sort = off; + +explain (costs off) +select a,b from test_table where (a,b) > ('a','a') order by a,b; + +select a,b from test_table where (a,b) > ('a','a') order by a,b; + +reset enable_sort; + +-- Check row comparisons with IN +select * from int8_tbl i8 where i8 in (row(123,456)); -- fail, type mismatch + +explain (costs off) +select * from int8_tbl i8 +where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)'); + +select * from int8_tbl i8 +where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)'); + +-- Check ability to select columns from an anonymous rowtype +select (row(1, 2.0)).f1; +select (row(1, 2.0)).f2; +select (row(1, 2.0)).nosuch; -- fail +select (row(1, 2.0)).*; +select (r).f1 from (select row(1, 2.0) as r) ss; +select (r).f3 from (select row(1, 2.0) as r) ss; -- fail +select (r).* from (select row(1, 2.0) as r) ss; + +-- Check some corner cases involving empty rowtypes +select ROW(); +select ROW() IS NULL; +select ROW() = ROW(); + +-- Check ability to create arrays of anonymous rowtypes +select array[ row(1,2), row(3,4), row(5,6) ]; + +-- Check ability to compare an anonymous row to elements of an array +select row(1,1.1) = any (array[ row(7,7.7), row(1,1.1), row(0,0.0) ]); +select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]); + +-- Check behavior with a non-comparable rowtype +create type cantcompare as (p point, r float8); +create temp table cc (f1 cantcompare); +insert into cc values('("(1,2)",3)'); +insert into cc values('("(4,5)",6)'); +select * from cc order by f1; -- fail, but should complain about cantcompare + +-- +-- Tests for record_{eq,cmp} +-- + +create type testtype1 as (a int, b int); + +-- all true +select row(1, 2)::testtype1 < row(1, 3)::testtype1; +select row(1, 2)::testtype1 <= row(1, 3)::testtype1; +select row(1, 2)::testtype1 = row(1, 2)::testtype1; +select row(1, 2)::testtype1 <> row(1, 3)::testtype1; +select row(1, 3)::testtype1 >= row(1, 2)::testtype1; +select row(1, 3)::testtype1 > row(1, 2)::testtype1; + +-- all false +select row(1, -2)::testtype1 < row(1, -3)::testtype1; +select row(1, -2)::testtype1 <= row(1, -3)::testtype1; +select row(1, -2)::testtype1 = row(1, -3)::testtype1; +select row(1, -2)::testtype1 <> row(1, -2)::testtype1; +select row(1, -3)::testtype1 >= row(1, -2)::testtype1; +select row(1, -3)::testtype1 > row(1, -2)::testtype1; + +-- true, but see *< below +select row(1, -2)::testtype1 < row(1, 3)::testtype1; + +-- mismatches +create type testtype3 as (a int, b text); +select row(1, 2)::testtype1 < row(1, 'abc')::testtype3; +select row(1, 2)::testtype1 <> row(1, 'abc')::testtype3; +create type testtype5 as (a int); +select row(1, 2)::testtype1 < row(1)::testtype5; +select row(1, 2)::testtype1 <> row(1)::testtype5; + +-- non-comparable types +create type testtype6 as (a int, b point); +select row(1, '(1,2)')::testtype6 < row(1, '(1,3)')::testtype6; +select row(1, '(1,2)')::testtype6 <> row(1, '(1,3)')::testtype6; + +drop type testtype1, testtype3, testtype5, testtype6; + +-- +-- Tests for record_image_{eq,cmp} +-- + +create type testtype1 as (a int, b int); + +-- all true +select row(1, 2)::testtype1 *< row(1, 3)::testtype1; +select row(1, 2)::testtype1 *<= row(1, 3)::testtype1; +select row(1, 2)::testtype1 *= row(1, 2)::testtype1; +select row(1, 2)::testtype1 *<> row(1, 3)::testtype1; +select row(1, 3)::testtype1 *>= row(1, 2)::testtype1; +select row(1, 3)::testtype1 *> row(1, 2)::testtype1; + +-- all false +select row(1, -2)::testtype1 *< row(1, -3)::testtype1; +select row(1, -2)::testtype1 *<= row(1, -3)::testtype1; +select row(1, -2)::testtype1 *= row(1, -3)::testtype1; +select row(1, -2)::testtype1 *<> row(1, -2)::testtype1; +select row(1, -3)::testtype1 *>= row(1, -2)::testtype1; +select row(1, -3)::testtype1 *> row(1, -2)::testtype1; + +-- This returns the "wrong" order because record_image_cmp works on +-- unsigned datums without knowing about the actual data type. +select row(1, -2)::testtype1 *< row(1, 3)::testtype1; + +-- other types +create type testtype2 as (a smallint, b bool); -- byval different sizes +select row(1, true)::testtype2 *< row(2, true)::testtype2; +select row(-2, true)::testtype2 *< row(-1, true)::testtype2; +select row(0, false)::testtype2 *< row(0, true)::testtype2; +select row(0, false)::testtype2 *<> row(0, true)::testtype2; + +create type testtype3 as (a int, b text); -- variable length +select row(1, 'abc')::testtype3 *< row(1, 'abd')::testtype3; +select row(1, 'abc')::testtype3 *< row(1, 'abcd')::testtype3; +select row(1, 'abc')::testtype3 *> row(1, 'abd')::testtype3; +select row(1, 'abc')::testtype3 *<> row(1, 'abd')::testtype3; + +create type testtype4 as (a int, b point); -- by ref, fixed length +select row(1, '(1,2)')::testtype4 *< row(1, '(1,3)')::testtype4; +select row(1, '(1,2)')::testtype4 *<> row(1, '(1,3)')::testtype4; + +-- mismatches +select row(1, 2)::testtype1 *< row(1, 'abc')::testtype3; +select row(1, 2)::testtype1 *<> row(1, 'abc')::testtype3; +create type testtype5 as (a int); +select row(1, 2)::testtype1 *< row(1)::testtype5; +select row(1, 2)::testtype1 *<> row(1)::testtype5; + +-- non-comparable types +create type testtype6 as (a int, b point); +select row(1, '(1,2)')::testtype6 *< row(1, '(1,3)')::testtype6; +select row(1, '(1,2)')::testtype6 *>= row(1, '(1,3)')::testtype6; +select row(1, '(1,2)')::testtype6 *<> row(1, '(1,3)')::testtype6; + +-- anonymous rowtypes in coldeflists +select q.a, q.b = row(2), q.c = array[row(3)], q.d = row(row(4)) from + unnest(array[row(1, row(2), array[row(3)], row(row(4))), + row(2, row(3), array[row(4)], row(row(5)))]) + as q(a int, b record, c record[], d record); + +drop type testtype1, testtype2, testtype3, testtype4, testtype5, testtype6; + + +-- +-- Test case derived from bug #5716: check multiple uses of a rowtype result +-- + +BEGIN; + +CREATE TABLE price ( + id SERIAL PRIMARY KEY, + active BOOLEAN NOT NULL, + price NUMERIC +); + +CREATE TYPE price_input AS ( + id INTEGER, + price NUMERIC +); + +CREATE TYPE price_key AS ( + id INTEGER +); + +CREATE FUNCTION price_key_from_table(price) RETURNS price_key AS $$ + SELECT $1.id +$$ LANGUAGE SQL; + +CREATE FUNCTION price_key_from_input(price_input) RETURNS price_key AS $$ + SELECT $1.id +$$ LANGUAGE SQL; + +insert into price values (1,false,42), (10,false,100), (11,true,17.99); + +UPDATE price + SET active = true, price = input_prices.price + FROM unnest(ARRAY[(10, 123.00), (11, 99.99)]::price_input[]) input_prices + WHERE price_key_from_table(price.*) = price_key_from_input(input_prices.*); + +select * from price order by id; -- YB: add ordering since PK is default HASH instead of ASC; output differs with upstream PG on 123 + +rollback; + +-- +-- Test case derived from bug #9085: check * qualification of composite +-- parameters for SQL functions +-- + +create temp table compos (f1 int, f2 text); + +create function fcompos1(v compos) returns void as $$ +insert into compos values (v); -- fail +$$ language sql; + +create function fcompos1(v compos) returns void as $$ +insert into compos values (v.*); +$$ language sql; + +create function fcompos2(v compos) returns void as $$ +select fcompos1(v); +$$ language sql; + +create function fcompos3(v compos) returns void as $$ +select fcompos1(fcompos3.v.*); +$$ language sql; + +select fcompos1(row(1,'one')); +select fcompos2(row(2,'two')); +select fcompos3(row(3,'three')); +select * from compos; + +-- +-- We allow I/O conversion casts from composite types to strings to be +-- invoked via cast syntax, but not functional syntax. This is because +-- the latter is too prone to be invoked unintentionally. +-- +select cast (fullname as text) from fullname; +select fullname::text from fullname; +select text(fullname) from fullname; -- error +select fullname.text from fullname; -- error +-- same, but RECORD instead of named composite type: +select cast (row('Jim', 'Beam') as text); +select (row('Jim', 'Beam'))::text; +select text(row('Jim', 'Beam')); -- error +select (row('Jim', 'Beam')).text; -- error + +-- +-- Check the equivalence of functional and column notation +-- +insert into fullname values ('Joe', 'Blow'); + +select f.last from fullname f; +select last(f) from fullname f; + +create function longname(fullname) returns text language sql +as $$select $1.first || ' ' || $1.last$$; + +select f.longname from fullname f; +select longname(f) from fullname f; + +-- Starting in v11, the notational form does matter if there's ambiguity +alter table fullname add column longname text; + +select f.longname from fullname f; +select longname(f) from fullname f; + +-- +-- Test that composite values are seen to have the correct column names +-- (bug #11210 and other reports) +-- + +select row_to_json(i) from int8_tbl i; +-- since "i" is of type "int8_tbl", attaching aliases doesn't change anything: +select row_to_json(i) from int8_tbl i(x,y); + +-- in these examples, we'll report the exposed column names of the subselect: +select row_to_json(ss) from + (select q1, q2 from int8_tbl) as ss; +select row_to_json(ss) from + (select q1, q2 from int8_tbl offset 0) as ss; +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl) as ss; +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl offset 0) as ss; +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl) as ss(x,y); +select row_to_json(ss) from + (select q1 as a, q2 as b from int8_tbl offset 0) as ss(x,y); + +explain (costs off) +select row_to_json(q) from + (select thousand, tenthous from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; +select row_to_json(q) from + (select thousand, tenthous from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; +select row_to_json(q) from + (select thousand as x, tenthous as y from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q; +select row_to_json(q) from + (select thousand as x, tenthous as y from tenk1 + where thousand = 42 and tenthous < 2000 offset 0) q(a,b); + +/* YB: TODO(jason): uncomment when issue #1129 is closed or closing. +create temp table tt1 as select * from int8_tbl limit 2; +create temp table tt2 () inherits(tt1); +insert into tt2 values(0,0); +select row_to_json(r) from (select q2,q1 from tt1 offset 0) r; + +-- check no-op rowtype conversions +create temp table tt3 () inherits(tt2); +insert into tt3 values(33,44); +select row_to_json(tt3::tt2::tt1) from tt3; +*/ -- YB + +-- +-- IS [NOT] NULL should not recurse into nested composites (bug #14235) +-- + +explain (verbose, costs off) +select r, r is null as isnull, r is not null as isnotnull +from (values (1,row(1,2)), (1,row(null,null)), (1,null), + (null,row(1,2)), (null,row(null,null)), (null,null) ) r(a,b); + +select r, r is null as isnull, r is not null as isnotnull +from (values (1,row(1,2)), (1,row(null,null)), (1,null), + (null,row(1,2)), (null,row(null,null)), (null,null) ) r(a,b); + +explain (verbose, costs off) +with r(a,b) as materialized + (values (1,row(1,2)), (1,row(null,null)), (1,null), + (null,row(1,2)), (null,row(null,null)), (null,null) ) +select r, r is null as isnull, r is not null as isnotnull from r; + +with r(a,b) as materialized + (values (1,row(1,2)), (1,row(null,null)), (1,null), + (null,row(1,2)), (null,row(null,null)), (null,null) ) +select r, r is null as isnull, r is not null as isnotnull from r; + + +-- +-- Tests for component access / FieldSelect +-- +CREATE TABLE compositetable(a text, b text); +INSERT INTO compositetable(a, b) VALUES('fa', 'fb'); + +-- composite type columns can't directly be accessed (error) +SELECT d.a FROM (SELECT compositetable AS d FROM compositetable) s; +-- but can be accessed with proper parens +SELECT (d).a, (d).b FROM (SELECT compositetable AS d FROM compositetable) s; +-- system columns can't be accessed in composite types (error) +SELECT (d).ctid FROM (SELECT compositetable AS d FROM compositetable) s; + +-- accessing non-existing column in NULL datum errors out +SELECT (NULL::compositetable).nonexistent; +-- existing column in a NULL composite yield NULL +SELECT (NULL::compositetable).a; +-- oids can't be accessed in composite types (error) +SELECT (NULL::compositetable).oid; + +DROP TABLE compositetable; diff --git a/src/test/regress/sql/yb.port.rules.sql b/src/test/regress/sql/yb.port.rules.sql new file mode 100644 index 0000000000000..b20fae1249588 --- /dev/null +++ b/src/test/regress/sql/yb.port.rules.sql @@ -0,0 +1,850 @@ +-- +-- RULES +-- From Jan's original setup_ruletest.sql and run_ruletest.sql +-- - thomas 1998-09-13 +-- + +-- +-- Tables and rules for the view test +-- +create table rtest_t1 (a int4, b int4); +create table rtest_t2 (a int4, b int4); +create table rtest_t3 (a int4, b int4); + +create view rtest_v1 as select * from rtest_t1; +create rule rtest_v1_ins as on insert to rtest_v1 do instead + insert into rtest_t1 values (new.a, new.b); +create rule rtest_v1_upd as on update to rtest_v1 do instead + update rtest_t1 set a = new.a, b = new.b + where a = old.a; +create rule rtest_v1_del as on delete to rtest_v1 do instead + delete from rtest_t1 where a = old.a; +-- Test comments +COMMENT ON RULE rtest_v1_bad ON rtest_v1 IS 'bad rule'; +COMMENT ON RULE rtest_v1_del ON rtest_v1 IS 'delete rule'; +COMMENT ON RULE rtest_v1_del ON rtest_v1 IS NULL; +-- +-- Tables and rules for the constraint update/delete test +-- +-- Note: +-- Now that we have multiple action rule support, we check +-- both possible syntaxes to define them (The last action +-- can but must not have a semicolon at the end). +-- +create table rtest_system (sysname text, sysdesc text); +create table rtest_interface (sysname text, ifname text); +create table rtest_person (pname text, pdesc text); +create table rtest_admin (pname text, sysname text); + +create rule rtest_sys_upd as on update to rtest_system do also ( + update rtest_interface set sysname = new.sysname + where sysname = old.sysname; + update rtest_admin set sysname = new.sysname + where sysname = old.sysname + ); + +create rule rtest_sys_del as on delete to rtest_system do also ( + delete from rtest_interface where sysname = old.sysname; + delete from rtest_admin where sysname = old.sysname; + ); + +create rule rtest_pers_upd as on update to rtest_person do also + update rtest_admin set pname = new.pname where pname = old.pname; + +create rule rtest_pers_del as on delete to rtest_person do also + delete from rtest_admin where pname = old.pname; + +-- +-- Tables and rules for the logging test +-- +create table rtest_emp (ename char(20), salary money); +create table rtest_emplog (ename char(20), who name, action char(10), newsal money, oldsal money); +create table rtest_empmass (ename char(20), salary money); + +create rule rtest_emp_ins as on insert to rtest_emp do + insert into rtest_emplog values (new.ename, current_user, + 'hired', new.salary, '0.00'); + +create rule rtest_emp_upd as on update to rtest_emp where new.salary != old.salary do + insert into rtest_emplog values (new.ename, current_user, + 'honored', new.salary, old.salary); + +create rule rtest_emp_del as on delete to rtest_emp do + insert into rtest_emplog values (old.ename, current_user, + 'fired', '0.00', old.salary); + +-- +-- Tables and rules for the multiple cascaded qualified instead +-- rule test +-- +create table rtest_t4 (a int4, b text); +create table rtest_t5 (a int4, b text); +create table rtest_t6 (a int4, b text); +create table rtest_t7 (a int4, b text); +create table rtest_t8 (a int4, b text); +create table rtest_t9 (a int4, b text); + +create rule rtest_t4_ins1 as on insert to rtest_t4 + where new.a >= 10 and new.a < 20 do instead + insert into rtest_t5 values (new.a, new.b); + +create rule rtest_t4_ins2 as on insert to rtest_t4 + where new.a >= 20 and new.a < 30 do + insert into rtest_t6 values (new.a, new.b); + +create rule rtest_t5_ins as on insert to rtest_t5 + where new.a > 15 do + insert into rtest_t7 values (new.a, new.b); + +create rule rtest_t6_ins as on insert to rtest_t6 + where new.a > 25 do instead + insert into rtest_t8 values (new.a, new.b); + +-- +-- Tables and rules for the rule fire order test +-- +-- As of PG 7.3, the rules should fire in order by name, regardless +-- of INSTEAD attributes or creation order. +-- +create table rtest_order1 (a int4); +create table rtest_order2 (a int4, b int4, c text); + +create sequence rtest_seq; + +create rule rtest_order_r3 as on insert to rtest_order1 do instead + insert into rtest_order2 values (new.a, nextval('rtest_seq'), + 'rule 3 - this should run 3rd'); + +create rule rtest_order_r4 as on insert to rtest_order1 + where a < 100 do instead + insert into rtest_order2 values (new.a, nextval('rtest_seq'), + 'rule 4 - this should run 4th'); + +create rule rtest_order_r2 as on insert to rtest_order1 do + insert into rtest_order2 values (new.a, nextval('rtest_seq'), + 'rule 2 - this should run 2nd'); + +create rule rtest_order_r1 as on insert to rtest_order1 do instead + insert into rtest_order2 values (new.a, nextval('rtest_seq'), + 'rule 1 - this should run 1st'); + +-- +-- Tables and rules for the instead nothing test +-- +create table rtest_nothn1 (a int4, b text); +create table rtest_nothn2 (a int4, b text); +create table rtest_nothn3 (a int4, b text); +create table rtest_nothn4 (a int4, b text); + +create rule rtest_nothn_r1 as on insert to rtest_nothn1 + where new.a >= 10 and new.a < 20 do instead nothing; + +create rule rtest_nothn_r2 as on insert to rtest_nothn1 + where new.a >= 30 and new.a < 40 do instead nothing; + +create rule rtest_nothn_r3 as on insert to rtest_nothn2 + where new.a >= 100 do instead + insert into rtest_nothn3 values (new.a, new.b); + +create rule rtest_nothn_r4 as on insert to rtest_nothn2 + do instead nothing; + +-- +-- Tests on a view that is select * of a table +-- and has insert/update/delete instead rules to +-- behave close like the real table. +-- + +-- +-- We need test date later +-- +insert into rtest_t2 values (1, 21); +insert into rtest_t2 values (2, 22); +insert into rtest_t2 values (3, 23); + +insert into rtest_t3 values (1, 31); +insert into rtest_t3 values (2, 32); +insert into rtest_t3 values (3, 33); +insert into rtest_t3 values (4, 34); +insert into rtest_t3 values (5, 35); + +-- insert values +insert into rtest_v1 values (1, 11); +insert into rtest_v1 values (2, 12); +select * from rtest_v1 ORDER BY a, b; + +-- delete with constant expression +delete from rtest_v1 where a = 1; +select * from rtest_v1; +insert into rtest_v1 values (1, 11); +delete from rtest_v1 where b = 12; +select * from rtest_v1; +insert into rtest_v1 values (2, 12); +insert into rtest_v1 values (2, 13); +select * from rtest_v1 ORDER BY a, b; +** Remember the delete rule on rtest_v1: It says +** DO INSTEAD DELETE FROM rtest_t1 WHERE a = old.a +** So this time both rows with a = 2 must get deleted +\p +\r +delete from rtest_v1 where b = 12; +select * from rtest_v1; +delete from rtest_v1; + +-- insert select +insert into rtest_v1 select * from rtest_t2; +select * from rtest_v1 ORDER BY a, b; +delete from rtest_v1; + +-- same with swapped targetlist +insert into rtest_v1 (b, a) select b, a from rtest_t2; +select * from rtest_v1 ORDER BY a, b; + +-- now with only one target attribute +insert into rtest_v1 (a) select a from rtest_t3; +select * from rtest_v1 ORDER BY a, b; +select * from rtest_v1 where b isnull ORDER BY a, b; + +-- let attribute a differ (must be done on rtest_t1 - see above) +update rtest_t1 set a = a + 10 where b isnull; +delete from rtest_v1 where b isnull; +select * from rtest_v1 ORDER BY a, b; + +-- now updates with constant expression +update rtest_v1 set b = 42 where a = 2; +select * from rtest_v1 ORDER BY a, b; +update rtest_v1 set b = 99 where b = 42; +select * from rtest_v1 ORDER BY a, b; +update rtest_v1 set b = 88 where b < 50; +select * from rtest_v1 ORDER BY a, b; +delete from rtest_v1; +insert into rtest_v1 select rtest_t2.a, rtest_t3.b + from rtest_t2, rtest_t3 + where rtest_t2.a = rtest_t3.a; +select * from rtest_v1 ORDER BY a, b; + +-- updates in a mergejoin +update rtest_v1 set b = rtest_t2.b from rtest_t2 where rtest_v1.a = rtest_t2.a; + +select * from rtest_v1 ORDER BY a, b; +insert into rtest_v1 select * from rtest_t3; +select * from rtest_v1 ORDER BY a, b; +update rtest_t1 set a = a + 10 where b > 30; +select * from rtest_v1 ORDER BY a, b; +update rtest_v1 set a = rtest_t3.a + 20 from rtest_t3 where rtest_v1.b = rtest_t3.b; +select * from rtest_v1 ORDER BY a, b; + +-- +-- Test for constraint updates/deletes +-- +insert into rtest_system values ('orion', 'Linux Jan Wieck'); +insert into rtest_system values ('notjw', 'WinNT Jan Wieck (notebook)'); +insert into rtest_system values ('neptun', 'Fileserver'); + +insert into rtest_interface values ('orion', 'eth0'); +insert into rtest_interface values ('orion', 'eth1'); +insert into rtest_interface values ('notjw', 'eth0'); +insert into rtest_interface values ('neptun', 'eth0'); + +insert into rtest_person values ('jw', 'Jan Wieck'); +insert into rtest_person values ('bm', 'Bruce Momjian'); + +insert into rtest_admin values ('jw', 'orion'); +insert into rtest_admin values ('jw', 'notjw'); +insert into rtest_admin values ('bm', 'neptun'); + +update rtest_system set sysname = 'pluto' where sysname = 'neptun'; + +select * from rtest_interface ORDER BY sysname, ifname; +select * from rtest_admin ORDER BY pname, sysname; + +update rtest_person set pname = 'jwieck' where pdesc = 'Jan Wieck'; + +-- Note: use ORDER BY here to ensure consistent output across all systems. +-- The above UPDATE affects two rows with equal keys, so they could be +-- updated in either order depending on the whim of the local qsort(). + +select * from rtest_admin order by pname, sysname; + +delete from rtest_system where sysname = 'orion'; + +select * from rtest_interface ORDER BY sysname, ifname; +select * from rtest_admin ORDER BY pname, sysname; + +-- +-- Rule qualification test +-- +insert into rtest_emp values ('wiecc', '5000.00'); +insert into rtest_emp values ('gates', '80000.00'); +update rtest_emp set ename = 'wiecx' where ename = 'wiecc'; +update rtest_emp set ename = 'wieck', salary = '6000.00' where ename = 'wiecx'; +update rtest_emp set salary = '7000.00' where ename = 'wieck'; +delete from rtest_emp where ename = 'gates'; + +select ename, who = current_user as "matches user", action, newsal, oldsal from rtest_emplog order by ename, action, newsal; +insert into rtest_empmass values ('meyer', '4000.00'); +insert into rtest_empmass values ('maier', '5000.00'); +insert into rtest_empmass values ('mayr', '6000.00'); +insert into rtest_emp select * from rtest_empmass; +select ename, who = current_user as "matches user", action, newsal, oldsal from rtest_emplog order by ename, action, newsal; +update rtest_empmass set salary = salary + '1000.00'; +update rtest_emp set salary = rtest_empmass.salary from rtest_empmass where rtest_emp.ename = rtest_empmass.ename; +select ename, who = current_user as "matches user", action, newsal, oldsal from rtest_emplog order by ename, action, newsal; +delete from rtest_emp using rtest_empmass where rtest_emp.ename = rtest_empmass.ename; +select ename, who = current_user as "matches user", action, newsal, oldsal from rtest_emplog order by ename, action, newsal; + +-- +-- Multiple cascaded qualified instead rule test +-- +insert into rtest_t4 values (1, 'Record should go to rtest_t4'); +insert into rtest_t4 values (2, 'Record should go to rtest_t4'); +insert into rtest_t4 values (10, 'Record should go to rtest_t5'); +insert into rtest_t4 values (15, 'Record should go to rtest_t5'); +insert into rtest_t4 values (19, 'Record should go to rtest_t5 and t7'); +insert into rtest_t4 values (20, 'Record should go to rtest_t4 and t6'); +insert into rtest_t4 values (26, 'Record should go to rtest_t4 and t8'); +insert into rtest_t4 values (28, 'Record should go to rtest_t4 and t8'); +insert into rtest_t4 values (30, 'Record should go to rtest_t4'); +insert into rtest_t4 values (40, 'Record should go to rtest_t4'); + +select * from rtest_t4 ORDER BY a, b; +select * from rtest_t5 ORDER BY a, b; +select * from rtest_t6 ORDER BY a, b; +select * from rtest_t7 ORDER BY a, b; +select * from rtest_t8 ORDER BY a, b; + +delete from rtest_t4; +delete from rtest_t5; +delete from rtest_t6; +delete from rtest_t7; +delete from rtest_t8; + +insert into rtest_t9 values (1, 'Record should go to rtest_t4'); +insert into rtest_t9 values (2, 'Record should go to rtest_t4'); +insert into rtest_t9 values (10, 'Record should go to rtest_t5'); +insert into rtest_t9 values (15, 'Record should go to rtest_t5'); +insert into rtest_t9 values (19, 'Record should go to rtest_t5 and t7'); +insert into rtest_t9 values (20, 'Record should go to rtest_t4 and t6'); +insert into rtest_t9 values (26, 'Record should go to rtest_t4 and t8'); +insert into rtest_t9 values (28, 'Record should go to rtest_t4 and t8'); +insert into rtest_t9 values (30, 'Record should go to rtest_t4'); +insert into rtest_t9 values (40, 'Record should go to rtest_t4'); + +insert into rtest_t4 select * from rtest_t9 where a < 20; + +select * from rtest_t4 ORDER BY a, b; +select * from rtest_t5 ORDER BY a, b; +select * from rtest_t6; +select * from rtest_t7; +select * from rtest_t8; + +insert into rtest_t4 select * from rtest_t9 where b ~ 'and t8'; + +select * from rtest_t4 ORDER BY a, b; +select * from rtest_t5 ORDER BY a, b; +select * from rtest_t6; +select * from rtest_t7; +select * from rtest_t8 ORDER BY a, b; + +insert into rtest_t4 select a + 1, b from rtest_t9 where a in (20, 30, 40); + +select * from rtest_t4 ORDER BY a, b; +select * from rtest_t5 ORDER BY a, b; +select * from rtest_t6; +select * from rtest_t7; +select * from rtest_t8 ORDER BY a, b; + +-- +-- Check that the ordering of rules fired is correct +-- +insert into rtest_order1 values (1); +select * from rtest_order2 ORDER BY a, b, c; + +-- +-- Check if instead nothing w/without qualification works +-- +insert into rtest_nothn1 values (1, 'want this'); +insert into rtest_nothn1 values (2, 'want this'); +insert into rtest_nothn1 values (10, 'don''t want this'); +insert into rtest_nothn1 values (19, 'don''t want this'); +insert into rtest_nothn1 values (20, 'want this'); +insert into rtest_nothn1 values (29, 'want this'); +insert into rtest_nothn1 values (30, 'don''t want this'); +insert into rtest_nothn1 values (39, 'don''t want this'); +insert into rtest_nothn1 values (40, 'want this'); +insert into rtest_nothn1 values (50, 'want this'); +insert into rtest_nothn1 values (60, 'want this'); + +select * from rtest_nothn1 ORDER BY a, b; + +insert into rtest_nothn2 values (10, 'too small'); +insert into rtest_nothn2 values (50, 'too small'); +insert into rtest_nothn2 values (100, 'OK'); +insert into rtest_nothn2 values (200, 'OK'); + +select * from rtest_nothn2; +select * from rtest_nothn3 ORDER BY a, b; + +delete from rtest_nothn1; +delete from rtest_nothn2; +delete from rtest_nothn3; + +insert into rtest_nothn4 values (1, 'want this'); +insert into rtest_nothn4 values (2, 'want this'); +insert into rtest_nothn4 values (10, 'don''t want this'); +insert into rtest_nothn4 values (19, 'don''t want this'); +insert into rtest_nothn4 values (20, 'want this'); +insert into rtest_nothn4 values (29, 'want this'); +insert into rtest_nothn4 values (30, 'don''t want this'); +insert into rtest_nothn4 values (39, 'don''t want this'); +insert into rtest_nothn4 values (40, 'want this'); +insert into rtest_nothn4 values (50, 'want this'); +insert into rtest_nothn4 values (60, 'want this'); + +insert into rtest_nothn1 select * from rtest_nothn4; + +select * from rtest_nothn1 ORDER BY a, b; + +delete from rtest_nothn4; + +insert into rtest_nothn4 values (10, 'too small'); +insert into rtest_nothn4 values (50, 'too small'); +insert into rtest_nothn4 values (100, 'OK'); +insert into rtest_nothn4 values (200, 'OK'); + +insert into rtest_nothn2 select * from rtest_nothn4; + +select * from rtest_nothn2; +select * from rtest_nothn3 ORDER BY a, b; + +create table rtest_view1 (a int4, b text, v bool); +create table rtest_view2 (a int4); +create table rtest_view3 (a int4, b text); +create table rtest_view4 (a int4, b text, c int4); +create view rtest_vview1 as select a, b from rtest_view1 X + where 0 < (select count(*) from rtest_view2 Y where Y.a = X.a); +create view rtest_vview2 as select a, b from rtest_view1 where v; +create view rtest_vview3 as select a, b from rtest_vview2 X + where 0 < (select count(*) from rtest_view2 Y where Y.a = X.a); +create view rtest_vview4 as select X.a, X.b, count(Y.a) as refcount + from rtest_view1 X, rtest_view2 Y + where X.a = Y.a + group by X.a, X.b; +create function rtest_viewfunc1(int4) returns int4 as + 'select count(*)::int4 from rtest_view2 where a = $1' + language sql; +create view rtest_vview5 as select a, b, rtest_viewfunc1(a) as refcount + from rtest_view1; + +insert into rtest_view1 values (1, 'item 1', 't'); +insert into rtest_view1 values (2, 'item 2', 't'); +insert into rtest_view1 values (3, 'item 3', 't'); +insert into rtest_view1 values (4, 'item 4', 'f'); +insert into rtest_view1 values (5, 'item 5', 't'); +insert into rtest_view1 values (6, 'item 6', 'f'); +insert into rtest_view1 values (7, 'item 7', 't'); +insert into rtest_view1 values (8, 'item 8', 't'); + +insert into rtest_view2 values (2); +insert into rtest_view2 values (2); +insert into rtest_view2 values (4); +insert into rtest_view2 values (5); +insert into rtest_view2 values (7); +insert into rtest_view2 values (7); +insert into rtest_view2 values (7); +insert into rtest_view2 values (7); + +select * from rtest_vview1 ORDER BY a, b; +select * from rtest_vview2 ORDER BY a, b; +select * from rtest_vview3 ORDER BY a, b; +select * from rtest_vview4 order by a, b; +select * from rtest_vview5 ORDER BY a, b, refcount; + +insert into rtest_view3 select * from rtest_vview1 where a < 7; +select * from rtest_view3 ORDER BY a, b; +delete from rtest_view3; + +insert into rtest_view3 select * from rtest_vview2 where a != 5 and b !~ '2'; +select * from rtest_view3 ORDER BY a, b; +delete from rtest_view3; + +insert into rtest_view3 select * from rtest_vview3; +select * from rtest_view3 ORDER BY a, b; +delete from rtest_view3; + +insert into rtest_view4 select * from rtest_vview4 where 3 > refcount; +select * from rtest_view4 order by a, b; +delete from rtest_view4; + +insert into rtest_view4 select * from rtest_vview5 where a > 2 and refcount = 0; +select * from rtest_view4 ORDER BY a, b, c; +delete from rtest_view4; +-- +-- Test for computations in views +-- +create table rtest_comp ( + part text, + unit char(4), + size float +); + + +create table rtest_unitfact ( + unit char(4), + factor float +); + +create view rtest_vcomp as + select X.part, (X.size * Y.factor) as size_in_cm + from rtest_comp X, rtest_unitfact Y + where X.unit = Y.unit; + + +insert into rtest_unitfact values ('m', 100.0); +insert into rtest_unitfact values ('cm', 1.0); +insert into rtest_unitfact values ('inch', 2.54); + +insert into rtest_comp values ('p1', 'm', 5.0); +insert into rtest_comp values ('p2', 'm', 3.0); +insert into rtest_comp values ('p3', 'cm', 5.0); +insert into rtest_comp values ('p4', 'cm', 15.0); +insert into rtest_comp values ('p5', 'inch', 7.0); +insert into rtest_comp values ('p6', 'inch', 4.4); + +select * from rtest_vcomp order by part; + +select * from rtest_vcomp where size_in_cm > 10.0 order by size_in_cm using >; + +-- +-- In addition run the (slightly modified) queries from the +-- programmers manual section on the rule system. +-- +CREATE TABLE shoe_data ( + shoename char(10), -- primary key + sh_avail integer, -- available # of pairs + slcolor char(10), -- preferred shoelace color + slminlen float, -- minimum shoelace length + slmaxlen float, -- maximum shoelace length + slunit char(8) -- length unit +); + +CREATE TABLE shoelace_data ( + sl_name char(10), -- primary key + sl_avail integer, -- available # of pairs + sl_color char(10), -- shoelace color + sl_len float, -- shoelace length + sl_unit char(8) -- length unit +); + +CREATE TABLE unit ( + un_name char(8), -- the primary key + un_fact float -- factor to transform to cm +); + +CREATE VIEW shoe AS + SELECT sh.shoename, + sh.sh_avail, + sh.slcolor, + sh.slminlen, + sh.slminlen * un.un_fact AS slminlen_cm, + sh.slmaxlen, + sh.slmaxlen * un.un_fact AS slmaxlen_cm, + sh.slunit + FROM shoe_data sh, unit un + WHERE sh.slunit = un.un_name; + +CREATE VIEW shoelace AS + SELECT s.sl_name, + s.sl_avail, + s.sl_color, + s.sl_len, + s.sl_unit, + s.sl_len * u.un_fact AS sl_len_cm + FROM shoelace_data s, unit u + WHERE s.sl_unit = u.un_name; + +CREATE VIEW shoe_ready AS + SELECT rsh.shoename, + rsh.sh_avail, + rsl.sl_name, + rsl.sl_avail, + int4smaller(rsh.sh_avail, rsl.sl_avail) AS total_avail + FROM shoe rsh, shoelace rsl + WHERE rsl.sl_color = rsh.slcolor + AND rsl.sl_len_cm >= rsh.slminlen_cm + AND rsl.sl_len_cm <= rsh.slmaxlen_cm; + +INSERT INTO unit VALUES ('cm', 1.0); +INSERT INTO unit VALUES ('m', 100.0); +INSERT INTO unit VALUES ('inch', 2.54); + +INSERT INTO shoe_data VALUES ('sh1', 2, 'black', 70.0, 90.0, 'cm'); +INSERT INTO shoe_data VALUES ('sh2', 0, 'black', 30.0, 40.0, 'inch'); +INSERT INTO shoe_data VALUES ('sh3', 4, 'brown', 50.0, 65.0, 'cm'); +INSERT INTO shoe_data VALUES ('sh4', 3, 'brown', 40.0, 50.0, 'inch'); + +INSERT INTO shoelace_data VALUES ('sl1', 5, 'black', 80.0, 'cm'); +INSERT INTO shoelace_data VALUES ('sl2', 6, 'black', 100.0, 'cm'); +INSERT INTO shoelace_data VALUES ('sl3', 0, 'black', 35.0 , 'inch'); +INSERT INTO shoelace_data VALUES ('sl4', 8, 'black', 40.0 , 'inch'); +INSERT INTO shoelace_data VALUES ('sl5', 4, 'brown', 1.0 , 'm'); +INSERT INTO shoelace_data VALUES ('sl6', 0, 'brown', 0.9 , 'm'); +INSERT INTO shoelace_data VALUES ('sl7', 7, 'brown', 60 , 'cm'); +INSERT INTO shoelace_data VALUES ('sl8', 1, 'brown', 40 , 'inch'); + +-- SELECTs in doc +SELECT * FROM shoelace ORDER BY sl_name; +SELECT * FROM shoe_ready WHERE total_avail >= 2 ORDER BY 1; + + CREATE TABLE shoelace_log ( + sl_name char(10), -- shoelace changed + sl_avail integer, -- new available value + log_who name, -- who did it + log_when timestamp -- when + ); + +-- Want "log_who" to be CURRENT_USER, +-- but that is non-portable for the regression test +-- - thomas 1999-02-21 + + CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data + WHERE NEW.sl_avail != OLD.sl_avail + DO INSERT INTO shoelace_log VALUES ( + NEW.sl_name, + NEW.sl_avail, + 'Al Bundy', + 'epoch' + ); + +UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7'; + +SELECT * FROM shoelace_log; + + CREATE RULE shoelace_ins AS ON INSERT TO shoelace + DO INSTEAD + INSERT INTO shoelace_data VALUES ( + NEW.sl_name, + NEW.sl_avail, + NEW.sl_color, + NEW.sl_len, + NEW.sl_unit); + + CREATE RULE shoelace_upd AS ON UPDATE TO shoelace + DO INSTEAD + UPDATE shoelace_data SET + sl_name = NEW.sl_name, + sl_avail = NEW.sl_avail, + sl_color = NEW.sl_color, + sl_len = NEW.sl_len, + sl_unit = NEW.sl_unit + WHERE sl_name = OLD.sl_name; + + CREATE RULE shoelace_del AS ON DELETE TO shoelace + DO INSTEAD + DELETE FROM shoelace_data + WHERE sl_name = OLD.sl_name; + + CREATE TABLE shoelace_arrive ( + arr_name char(10), + arr_quant integer + ); + + CREATE TABLE shoelace_ok ( + ok_name char(10), + ok_quant integer + ); + + CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok + DO INSTEAD + UPDATE shoelace SET + sl_avail = sl_avail + NEW.ok_quant + WHERE sl_name = NEW.ok_name; + +INSERT INTO shoelace_arrive VALUES ('sl3', 10); +INSERT INTO shoelace_arrive VALUES ('sl6', 20); +INSERT INTO shoelace_arrive VALUES ('sl8', 20); + +SELECT * FROM shoelace ORDER BY sl_name; + +insert into shoelace_ok select * from shoelace_arrive; + +SELECT * FROM shoelace ORDER BY sl_name; + +SELECT * FROM shoelace_log ORDER BY sl_name; + + CREATE VIEW shoelace_obsolete AS + SELECT * FROM shoelace WHERE NOT EXISTS + (SELECT shoename FROM shoe WHERE slcolor = sl_color); + + CREATE VIEW shoelace_candelete AS + SELECT * FROM shoelace_obsolete WHERE sl_avail = 0; + +insert into shoelace values ('sl9', 0, 'pink', 35.0, 'inch', 0.0); +insert into shoelace values ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0); +-- Unsupported (even though a similar updatable view construct is) +insert into shoelace values ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0) + on conflict do nothing; + +SELECT * FROM shoelace_obsolete ORDER BY sl_len_cm; +SELECT * FROM shoelace_candelete; + +DELETE FROM shoelace WHERE EXISTS + (SELECT * FROM shoelace_candelete + WHERE sl_name = shoelace.sl_name); + +SELECT * FROM shoelace ORDER BY sl_name; + +SELECT * FROM shoe ORDER BY shoename; +SELECT count(*) FROM shoe; + + +-- +-- Simple test of qualified ON INSERT ... this did not work in 7.0 ... +-- +create table rules_foo (f1 int); +create table rules_foo2 (f1 int); + +create rule rules_foorule as on insert to rules_foo where f1 < 100 +do instead nothing; + +insert into rules_foo values(1); +insert into rules_foo values(1001); +select * from rules_foo; + +drop rule rules_foorule on rules_foo; + +-- this should fail because f1 is not exposed for unqualified reference: +create rule rules_foorule as on insert to rules_foo where f1 < 100 +do instead insert into rules_foo2 values (f1); +-- this is the correct way: +create rule rules_foorule as on insert to rules_foo where f1 < 100 +do instead insert into rules_foo2 values (new.f1); + +insert into rules_foo values(2); +insert into rules_foo values(100); + +select * from rules_foo ORDER BY f1; +select * from rules_foo2; + +drop rule rules_foorule on rules_foo; +drop table rules_foo; +drop table rules_foo2; + + +-- +-- Test rules containing INSERT ... SELECT, which is a very ugly special +-- case as of 7.1. Example is based on bug report from Joel Burton. +-- +create table pparent (pid int, txt text); +insert into pparent values (1,'parent1'); +insert into pparent values (2,'parent2'); + +create table cchild (pid int, descrip text); +insert into cchild values (1,'descrip1'); + +create view vview as + select pparent.pid, txt, descrip from + pparent left join cchild using (pid); + +create rule rrule as + on update to vview do instead +( + insert into cchild (pid, descrip) + select old.pid, new.descrip where old.descrip isnull; + update cchild set descrip = new.descrip where cchild.pid = old.pid; +); + +select * from vview ORDER BY pid, txt, descrip; +update vview set descrip='test1' where pid=1; +select * from vview ORDER BY pid, txt, descrip; +update vview set descrip='test2' where pid=2; +select * from vview ORDER BY pid, txt, descrip; +update vview set descrip='test3' where pid=3; +select * from vview ORDER BY pid, txt, descrip; +select * from cchild ORDER BY pid, descrip; + +drop rule rrule on vview; +drop view vview; +drop table pparent; +drop table cchild; + + +-- +-- Check that ruleutils are working +-- + +-- temporarily disable fancy output, so view changes create less diff noise +\a\t + +-- TODO(jason): port `create_view`, `matview`, and `tablesample` Postgres +-- regress tests, then add the appropriate rows to the output. +SELECT viewname, definition FROM pg_views +WHERE schemaname = 'pg_catalog' +ORDER BY viewname; + +SELECT tablename, rulename, definition FROM pg_rules +WHERE schemaname = 'pg_catalog' +ORDER BY tablename, rulename; + +-- restore normal output mode +\a\t + +-- +-- CREATE OR REPLACE RULE +-- + +CREATE TABLE ruletest_tbl (a int, b int); +CREATE TABLE ruletest_tbl2 (a int, b int); + +CREATE OR REPLACE RULE myrule AS ON INSERT TO ruletest_tbl + DO INSTEAD INSERT INTO ruletest_tbl2 VALUES (10, 10); + +INSERT INTO ruletest_tbl VALUES (99, 99); + +CREATE OR REPLACE RULE myrule AS ON INSERT TO ruletest_tbl + DO INSTEAD INSERT INTO ruletest_tbl2 VALUES (1000, 1000); + +INSERT INTO ruletest_tbl VALUES (99, 99); + +SELECT * FROM ruletest_tbl2 ORDER BY a, b; +-- test FOR UPDATE in rules + +create table rules_base(f1 int, f2 int); +insert into rules_base values(1,2), (11,12); +create rule r1 as on update to rules_base do instead + select * from rules_base where f1 = 1 for update; +update rules_base set f2 = f2 + 1; +create or replace rule r1 as on update to rules_base do instead + select * from rules_base where f1 = 11 for update of rules_base; +update rules_base set f2 = f2 + 1; +create or replace rule r1 as on update to rules_base do instead + select * from rules_base where f1 = 11 for update of old; -- error +drop table rules_base; + +-- +-- Test non-SELECT rule on security invoker view. +-- Should use view owner's permissions. +-- +CREATE USER regress_rule_user1; + +CREATE TABLE ruletest_t1 (x int); +CREATE TABLE ruletest_t2 (x int); +CREATE VIEW ruletest_v1 WITH (security_invoker=true) AS + SELECT * FROM ruletest_t1; +GRANT INSERT ON ruletest_v1 TO regress_rule_user1; + +CREATE RULE rule1 AS ON INSERT TO ruletest_v1 + DO INSTEAD INSERT INTO ruletest_t2 VALUES (NEW.*); + +SET SESSION AUTHORIZATION regress_rule_user1; +INSERT INTO ruletest_v1 VALUES (1); + +RESET SESSION AUTHORIZATION; +SELECT * FROM ruletest_t1; +SELECT * FROM ruletest_t2; + +DROP VIEW ruletest_v1; +DROP TABLE ruletest_t2; +DROP TABLE ruletest_t1; + +DROP USER regress_rule_user1; diff --git a/src/test/regress/sql/yb.port.select.sql b/src/test/regress/sql/yb.port.select.sql new file mode 100644 index 0000000000000..ed795dc2a0091 --- /dev/null +++ b/src/test/regress/sql/yb.port.select.sql @@ -0,0 +1,265 @@ +-- +-- SELECT +-- +SET yb_enable_bitmapscan TO on; + +-- btree index +-- awk '{if($1<10){print;}else{next;}}' onek.data | sort +0n -1 +-- +SELECT * FROM onek + WHERE onek.unique1 < 10 + ORDER BY onek.unique1; + +-- +-- awk '{if($1<20){print $1,$14;}else{next;}}' onek.data | sort +0nr -1 +-- +SELECT onek.unique1, onek.stringu1 FROM onek + WHERE onek.unique1 < 20 + ORDER BY unique1 using >; + +-- +-- awk '{if($1>980){print $1,$14;}else{next;}}' onek.data | sort +1d -2 +-- +SELECT onek.unique1, onek.stringu1 FROM onek + WHERE onek.unique1 > 980 + ORDER BY stringu1 using <; + +-- +-- awk '{if($1>980){print $1,$16;}else{next;}}' onek.data | +-- sort +1d -2 +0nr -1 +-- +SELECT onek.unique1, onek.string4 FROM onek + WHERE onek.unique1 > 980 + ORDER BY string4 using <, unique1 using >; + +-- +-- awk '{if($1>980){print $1,$16;}else{next;}}' onek.data | +-- sort +1dr -2 +0n -1 +-- +SELECT onek.unique1, onek.string4 FROM onek + WHERE onek.unique1 > 980 + ORDER BY string4 using >, unique1 using <; + +-- +-- awk '{if($1<20){print $1,$16;}else{next;}}' onek.data | +-- sort +0nr -1 +1d -2 +-- +SELECT onek.unique1, onek.string4 FROM onek + WHERE onek.unique1 < 20 + ORDER BY unique1 using >, string4 using <; + +-- +-- awk '{if($1<20){print $1,$16;}else{next;}}' onek.data | +-- sort +0n -1 +1dr -2 +-- +SELECT onek.unique1, onek.string4 FROM onek + WHERE onek.unique1 < 20 + ORDER BY unique1 using <, string4 using >; + +-- +-- test partial btree indexes +-- +-- As of 7.2, planner probably won't pick an indexscan without stats, +-- so ANALYZE first. Also, we want to prevent it from picking a bitmapscan +-- followed by sort, because that could hide index ordering problems. +-- +ANALYZE onek2; + +SET enable_seqscan TO off; +SET enable_bitmapscan TO off; +SET enable_sort TO off; + +-- +-- awk '{if($1<10){print $0;}else{next;}}' onek.data | sort +0n -1 +-- +SELECT onek2.* FROM onek2 WHERE onek2.unique1 < 10; + +-- +-- awk '{if($1<20){print $1,$14;}else{next;}}' onek.data | sort +0nr -1 +-- +SELECT onek2.unique1, onek2.stringu1 FROM onek2 + WHERE onek2.unique1 < 20 + ORDER BY unique1 using >; + +-- +-- awk '{if($1>980){print $1,$14;}else{next;}}' onek.data | sort +1d -2 +-- +SELECT onek2.unique1, onek2.stringu1 FROM onek2 + WHERE onek2.unique1 > 980; + +RESET enable_seqscan; +RESET enable_bitmapscan; +RESET enable_sort; + +-- +-- awk '{print $1,$2;}' person.data | +-- awk '{if(NF!=2){print $3,$2;}else{print;}}' - emp.data | +-- awk '{if(NF!=2){print $3,$2;}else{print;}}' - student.data | +-- awk 'BEGIN{FS=" ";}{if(NF!=2){print $4,$5;}else{print;}}' - stud_emp.data +-- +-- SELECT name, age FROM person*; ??? check if different +SELECT p.name, p.age FROM person* p; -- YB: output missing rows due to lack of INHERITS support + +-- +-- awk '{print $1,$2;}' person.data | +-- awk '{if(NF!=2){print $3,$2;}else{print;}}' - emp.data | +-- awk '{if(NF!=2){print $3,$2;}else{print;}}' - student.data | +-- awk 'BEGIN{FS=" ";}{if(NF!=1){print $4,$5;}else{print;}}' - stud_emp.data | +-- sort +1nr -2 +-- +SELECT p.name, p.age FROM person* p ORDER BY age using >, name; -- YB: output missing rows due to lack of INHERITS support + +-- +-- Test some cases involving whole-row Var referencing a subquery +-- +select foo from (select 1 offset 0) as foo; +select foo from (select null offset 0) as foo; +select foo from (select 'xyzzy',1,null offset 0) as foo; + +-- +-- Test VALUES lists +-- +select * from onek, (values(147, 'RFAAAA'), (931, 'VJAAAA')) as v (i, j) + WHERE onek.unique1 = v.i and onek.stringu1 = v.j; + +-- a more complex case +-- looks like we're coding lisp :-) +select * from onek, + (values ((select i from + (values(10000), (2), (389), (1000), (2000), ((select 10029))) as foo(i) + order by i asc limit 1))) bar (i) + where onek.unique1 = bar.i; + +-- try VALUES in a subquery +select * from onek + where (unique1,ten) in (values (1,1), (20,0), (99,9), (17,99)) + order by unique1; + +-- VALUES is also legal as a standalone query or a set-operation member +VALUES (1,2), (3,4+4), (7,77.7); + +VALUES (1,2), (3,4+4), (7,77.7) +UNION ALL +SELECT 2+2, 57 +UNION ALL +TABLE int8_tbl; + +-- corner case: VALUES with no columns +CREATE TEMP TABLE nocols(); +INSERT INTO nocols DEFAULT VALUES; +SELECT * FROM nocols n, LATERAL (VALUES(n.*)) v; + +-- +-- Test ORDER BY options +-- + +CREATE TEMP TABLE foo (f1 int); + +INSERT INTO foo VALUES (42),(3),(10),(7),(null),(null),(1); + +SELECT * FROM foo ORDER BY f1; +SELECT * FROM foo ORDER BY f1 ASC; -- same thing +SELECT * FROM foo ORDER BY f1 NULLS FIRST; +SELECT * FROM foo ORDER BY f1 DESC; +SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; + +-- check if indexscans do the right things +CREATE INDEX fooi ON foo (f1); +SET enable_sort = false; + +SELECT * FROM foo ORDER BY f1; +SELECT * FROM foo ORDER BY f1 NULLS FIRST; +SELECT * FROM foo ORDER BY f1 DESC; +SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; + +DROP INDEX fooi; +CREATE INDEX fooi ON foo (f1 DESC); + +SELECT * FROM foo ORDER BY f1; +SELECT * FROM foo ORDER BY f1 NULLS FIRST; +SELECT * FROM foo ORDER BY f1 DESC; +SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; + +DROP INDEX fooi; +CREATE INDEX fooi ON foo (f1 DESC NULLS LAST); + +SELECT * FROM foo ORDER BY f1; +SELECT * FROM foo ORDER BY f1 NULLS FIRST; +SELECT * FROM foo ORDER BY f1 DESC; +SELECT * FROM foo ORDER BY f1 DESC NULLS LAST; + +-- +-- Test planning of some cases with partial indexes +-- + +-- partial index is usable +explain (costs off) +select * from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; +select * from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; +-- actually run the query with an analyze to use the partial index +explain (costs off, analyze on, timing off, summary off) +select * from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; +explain (costs off) +select unique2 from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; +select unique2 from onek2 where unique2 = 11 and stringu1 = 'ATAAAA'; +-- partial index predicate implies clause, so no need for retest +explain (costs off) +select * from onek2 where unique2 = 11 and stringu1 < 'B'; +select * from onek2 where unique2 = 11 and stringu1 < 'B'; +explain (costs off) +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; +-- but if it's an update target, must retest anyway +explain (costs off) +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B' for update; +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B' for update; +-- partial index is not applicable +explain (costs off) +select unique2 from onek2 where unique2 = 11 and stringu1 < 'C'; +select unique2 from onek2 where unique2 = 11 and stringu1 < 'C'; +-- partial index implies clause, but bitmap scan must recheck predicate anyway +SET enable_indexscan TO off; +explain (costs off) +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; +select unique2 from onek2 where unique2 = 11 and stringu1 < 'B'; +RESET enable_indexscan; +-- check multi-index cases too +explain (costs off) +select unique1, unique2 from onek2 + where (unique2 = 11 or unique1 = 0) and stringu1 < 'B'; +select unique1, unique2 from onek2 + where (unique2 = 11 or unique1 = 0) and stringu1 < 'B'; +explain (costs off) +select unique1, unique2 from onek2 + where (unique2 = 11 and stringu1 < 'B') or unique1 = 0; +select unique1, unique2 from onek2 + where (unique2 = 11 and stringu1 < 'B') or unique1 = 0; + +-- +-- Test some corner cases that have been known to confuse the planner +-- + +-- ORDER BY on a constant doesn't really need any sorting +SELECT 1 AS x ORDER BY x; + +-- But ORDER BY on a set-valued expression does +create function sillysrf(int) returns setof int as + 'values (1),(10),(2),($1)' language sql immutable; + +select sillysrf(42); +select sillysrf(-1) order by 1; + +drop function sillysrf(int); + +-- X = X isn't a no-op, it's effectively X IS NOT NULL assuming = is strict +-- (see bug #5084) +select * from (values (2),(null),(1)) v(k) where k = k order by k; +select * from (values (2),(null),(1)) v(k) where k = k; + +-- Test partitioned tables with no partitions, which should be handled the +-- same as the non-inheritance case when expanding its RTE. +create table list_parted_tbl (a int,b int) partition by list (a); +create table list_parted_tbl1 partition of list_parted_tbl + for values in (1) partition by list(b); +explain (costs off) select * from list_parted_tbl; +drop table list_parted_tbl; diff --git a/src/test/regress/sql/yb.port.select_distinct.sql b/src/test/regress/sql/yb.port.select_distinct.sql new file mode 100644 index 0000000000000..d2ec63f0c91b9 --- /dev/null +++ b/src/test/regress/sql/yb.port.select_distinct.sql @@ -0,0 +1,76 @@ +-- +-- SELECT_DISTINCT +-- + +-- +-- awk '{print $3;}' onek.data | sort -n | uniq +-- +SELECT DISTINCT two FROM onek ORDER BY 1; + +-- +-- awk '{print $5;}' onek.data | sort -n | uniq +-- +SELECT DISTINCT ten FROM onek ORDER BY 1; + +-- +-- awk '{print $16;}' onek.data | sort -d | uniq +-- +SELECT DISTINCT string4 FROM onek ORDER BY 1; + +-- +-- awk '{print $3,$16,$5;}' onek.data | sort -d | uniq | +-- sort +0n -1 +1d -2 +2n -3 +-- +SELECT DISTINCT two, string4, ten + FROM onek + ORDER BY two using <, string4 using <, ten using <; + +-- +-- awk '{print $2;}' person.data | +-- awk '{if(NF!=1){print $2;}else{print;}}' - emp.data | +-- awk '{if(NF!=1){print $2;}else{print;}}' - student.data | +-- awk 'BEGIN{FS=" ";}{if(NF!=1){print $5;}else{print;}}' - stud_emp.data | +-- sort -n -r | uniq +-- TODO(#1129): add missing rows 23, 60 when inheritance is supported. +-- +SELECT DISTINCT p.age FROM person* p ORDER BY age using >; + +-- +-- Check mentioning same column more than once +-- + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT count(*) FROM + (SELECT DISTINCT two, four, two FROM tenk1) ss; + +SELECT count(*) FROM + (SELECT DISTINCT two, four, two FROM tenk1) ss; + +-- +-- Also, some tests of IS DISTINCT FROM, which doesn't quite deserve its +-- very own regression file. +-- + +CREATE TEMP TABLE disttable (f1 integer); +INSERT INTO DISTTABLE VALUES(1); +INSERT INTO DISTTABLE VALUES(2); +INSERT INTO DISTTABLE VALUES(3); +INSERT INTO DISTTABLE VALUES(NULL); + +-- basic cases +SELECT f1, f1 IS DISTINCT FROM 2 as "not 2" FROM disttable; +SELECT f1, f1 IS DISTINCT FROM NULL as "not null" FROM disttable; +SELECT f1, f1 IS DISTINCT FROM f1 as "false" FROM disttable; +SELECT f1, f1 IS DISTINCT FROM f1+1 as "not null" FROM disttable; + +-- check that optimizer constant-folds it properly +SELECT 1 IS DISTINCT FROM 2 as "yes"; +SELECT 2 IS DISTINCT FROM 2 as "no"; +SELECT 2 IS DISTINCT FROM null as "yes"; +SELECT null IS DISTINCT FROM null as "no"; + +-- negated form +SELECT 1 IS NOT DISTINCT FROM 2 as "no"; +SELECT 2 IS NOT DISTINCT FROM 2 as "yes"; +SELECT 2 IS NOT DISTINCT FROM null as "no"; +SELECT null IS NOT DISTINCT FROM null as "yes"; diff --git a/src/test/regress/sql/yb.port.sequence.sql b/src/test/regress/sql/yb.port.sequence.sql new file mode 100644 index 0000000000000..1924d3a809525 --- /dev/null +++ b/src/test/regress/sql/yb.port.sequence.sql @@ -0,0 +1,428 @@ +-- +-- CREATE SEQUENCE +-- + +-- various error cases +CREATE SEQUENCE sequence_testx INCREMENT BY 0; +CREATE SEQUENCE sequence_testx INCREMENT BY -1 MINVALUE 20; +CREATE SEQUENCE sequence_testx INCREMENT BY 1 MAXVALUE -20; +CREATE SEQUENCE sequence_testx INCREMENT BY -1 START 10; +CREATE SEQUENCE sequence_testx INCREMENT BY 1 START -10; +CREATE SEQUENCE sequence_testx CACHE 0; + +-- OWNED BY errors +CREATE SEQUENCE sequence_testx OWNED BY nobody; -- nonsense word +CREATE SEQUENCE sequence_testx OWNED BY pg_class_oid_index.oid; -- not a table +SET SESSION AUTHORIZATION postgres; -- YB: the following query expects to be run as the owner of pg_class +CREATE SEQUENCE sequence_testx OWNED BY pg_class.relname; -- not same schema +RESET SESSION AUTHORIZATION; -- YB: undo above +CREATE TABLE sequence_test_table (a int); +CREATE SEQUENCE sequence_testx OWNED BY sequence_test_table.b; -- wrong column +DROP TABLE sequence_test_table; + +-- sequence data types +CREATE SEQUENCE sequence_test5 AS integer; +CREATE SEQUENCE sequence_test6 AS smallint; +CREATE SEQUENCE sequence_test7 AS bigint; +CREATE SEQUENCE sequence_test8 AS integer MAXVALUE 100000; +CREATE SEQUENCE sequence_test9 AS integer INCREMENT BY -1; +CREATE SEQUENCE sequence_test10 AS integer MINVALUE -100000 START 1; +CREATE SEQUENCE sequence_test11 AS smallint; +CREATE SEQUENCE sequence_test12 AS smallint INCREMENT -1; +CREATE SEQUENCE sequence_test13 AS smallint MINVALUE -32768; +CREATE SEQUENCE sequence_test14 AS smallint MAXVALUE 32767 INCREMENT -1; +CREATE SEQUENCE sequence_testx AS text; +CREATE SEQUENCE sequence_testx AS nosuchtype; + +CREATE SEQUENCE sequence_testx AS smallint MAXVALUE 100000; +CREATE SEQUENCE sequence_testx AS smallint MINVALUE -100000; + +ALTER SEQUENCE sequence_test5 AS smallint; -- success, max will be adjusted +ALTER SEQUENCE sequence_test8 AS smallint; -- fail, max has to be adjusted +ALTER SEQUENCE sequence_test8 AS smallint MAXVALUE 20000; -- ok now +ALTER SEQUENCE sequence_test9 AS smallint; -- success, min will be adjusted +ALTER SEQUENCE sequence_test10 AS smallint; -- fail, min has to be adjusted +ALTER SEQUENCE sequence_test10 AS smallint MINVALUE -20000; -- ok now + +ALTER SEQUENCE sequence_test11 AS int; -- max will be adjusted +ALTER SEQUENCE sequence_test12 AS int; -- min will be adjusted +ALTER SEQUENCE sequence_test13 AS int; -- min and max will be adjusted +ALTER SEQUENCE sequence_test14 AS int; -- min and max will be adjusted + +--- +--- test creation of SERIAL column +--- + +CREATE TABLE serialTest1 (f1 text, f2 serial); + +INSERT INTO serialTest1 VALUES ('foo'); +INSERT INTO serialTest1 VALUES ('bar'); +INSERT INTO serialTest1 VALUES ('force', 100); +INSERT INTO serialTest1 VALUES ('wrong', NULL); + +SELECT * FROM serialTest1 ORDER BY f2; -- YB: add ordering + +SELECT pg_get_serial_sequence('serialTest1', 'f2'); + +-- test smallserial / bigserial +CREATE TABLE serialTest2 (f1 text, f2 serial, f3 smallserial, f4 serial2, + f5 bigserial, f6 serial8); + +INSERT INTO serialTest2 (f1) + VALUES ('test_defaults'); + +INSERT INTO serialTest2 (f1, f2, f3, f4, f5, f6) + VALUES ('test_max_vals', 2147483647, 32767, 32767, 9223372036854775807, + 9223372036854775807), + ('test_min_vals', -2147483648, -32768, -32768, -9223372036854775808, + -9223372036854775808); + +-- All these INSERTs should fail: +INSERT INTO serialTest2 (f1, f3) + VALUES ('bogus', -32769); + +INSERT INTO serialTest2 (f1, f4) + VALUES ('bogus', -32769); + +INSERT INTO serialTest2 (f1, f3) + VALUES ('bogus', 32768); + +INSERT INTO serialTest2 (f1, f4) + VALUES ('bogus', 32768); + +INSERT INTO serialTest2 (f1, f5) + VALUES ('bogus', -9223372036854775809); + +INSERT INTO serialTest2 (f1, f6) + VALUES ('bogus', -9223372036854775809); + +INSERT INTO serialTest2 (f1, f5) + VALUES ('bogus', 9223372036854775808); + +INSERT INTO serialTest2 (f1, f6) + VALUES ('bogus', 9223372036854775808); + +SELECT * FROM serialTest2 ORDER BY f2 ASC; + +SELECT nextval('serialTest2_f2_seq'); +SELECT nextval('serialTest2_f3_seq'); +SELECT nextval('serialTest2_f4_seq'); +SELECT nextval('serialTest2_f5_seq'); +SELECT nextval('serialTest2_f6_seq'); + +-- basic sequence operations using both text and oid references +CREATE SEQUENCE sequence_test; +CREATE SEQUENCE IF NOT EXISTS sequence_test; + +SELECT nextval('sequence_test'::text); +SELECT nextval('sequence_test'::regclass); +SELECT currval('sequence_test'::text); +SELECT currval('sequence_test'::regclass); +SELECT setval('sequence_test'::text, 32); +SELECT nextval('sequence_test'::regclass); +SELECT setval('sequence_test'::text, 99, false); +SELECT nextval('sequence_test'::regclass); +SELECT setval('sequence_test'::regclass, 32); +SELECT nextval('sequence_test'::text); +SELECT setval('sequence_test'::regclass, 99, false); +SELECT nextval('sequence_test'::text); +DISCARD SEQUENCES; +SELECT currval('sequence_test'::regclass); + +DROP SEQUENCE sequence_test; + +-- renaming sequences +CREATE SEQUENCE foo_seq; +ALTER TABLE foo_seq RENAME TO foo_seq_new; +SELECT * FROM foo_seq_new; +SELECT nextval('foo_seq_new'); +SELECT nextval('foo_seq_new'); +-- log_cnt can be higher if there is a checkpoint just at the right +-- time, so just test for the expected range +SELECT last_value, log_cnt IN (0) AS log_cnt_ok, is_called FROM foo_seq_new; -- YB: log_cnt is always zero +DROP SEQUENCE foo_seq_new; + +-- renaming serial sequences +ALTER TABLE serialtest1_f2_seq RENAME TO serialtest1_f2_foo; +INSERT INTO serialTest1 VALUES ('more'); +SELECT * FROM serialTest1 ORDER BY substr(f1, 3); -- YB: add ordering + +-- +-- Check dependencies of serial and ordinary sequences +-- +CREATE TEMP SEQUENCE myseq2; +CREATE TEMP SEQUENCE myseq3; +CREATE TEMP TABLE t1 ( + f1 serial, + f2 int DEFAULT nextval('myseq2'), + f3 int DEFAULT nextval('myseq3'::text) +); +-- Both drops should fail, but with different error messages: +DROP SEQUENCE t1_f1_seq; +DROP SEQUENCE myseq2; +-- This however will work: +DROP SEQUENCE myseq3; +DROP TABLE t1; +-- Fails because no longer existent: +DROP SEQUENCE t1_f1_seq; +-- Now OK: +DROP SEQUENCE myseq2; + +-- +-- Alter sequence +-- + +ALTER SEQUENCE IF EXISTS sequence_test2 RESTART WITH 24 + INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE; + +ALTER SEQUENCE serialTest1 CYCLE; -- error, not a sequence + +CREATE SEQUENCE sequence_test2 START WITH 32; +CREATE SEQUENCE sequence_test4 INCREMENT BY -1; + +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test4'); + +ALTER SEQUENCE sequence_test2 RESTART; +SELECT nextval('sequence_test2'); + +ALTER SEQUENCE sequence_test2 RESTART WITH 0; -- error +ALTER SEQUENCE sequence_test4 RESTART WITH 40; -- error + +-- test CYCLE and NO CYCLE +ALTER SEQUENCE sequence_test2 RESTART WITH 24 + INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE; +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); -- cycled + +ALTER SEQUENCE sequence_test2 RESTART WITH 24 + NO CYCLE; +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); -- error + +ALTER SEQUENCE sequence_test2 RESTART WITH -24 START WITH -24 + INCREMENT BY -4 MINVALUE -36 MAXVALUE -5 CYCLE; +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); -- cycled + +ALTER SEQUENCE sequence_test2 RESTART WITH -24 + NO CYCLE; +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); +SELECT nextval('sequence_test2'); -- error + +-- reset +ALTER SEQUENCE IF EXISTS sequence_test2 RESTART WITH 32 START WITH 32 + INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE; + +SELECT setval('sequence_test2', -100); -- error +SELECT setval('sequence_test2', 100); -- error +SELECT setval('sequence_test2', 5); + +CREATE SEQUENCE sequence_test3; -- not read from, to test is_called + + +-- Information schema +SELECT * FROM information_schema.sequences + WHERE sequence_name ~ ANY(ARRAY['sequence_test', 'serialtest']) + ORDER BY sequence_name ASC; + +SELECT schemaname, sequencename, start_value, min_value, max_value, increment_by, cycle, cache_size -- YB: YB doesn't store last_value in pg_sequences +FROM pg_sequences +WHERE sequencename ~ ANY(ARRAY['sequence_test', 'serialtest']) + ORDER BY sequencename ASC; + + +SELECT * FROM pg_sequence_parameters('sequence_test4'::regclass); + + +\d sequence_test4 +\d serialtest2_f2_seq + + +-- Test comments +COMMENT ON SEQUENCE asdf IS 'won''t work'; +COMMENT ON SEQUENCE sequence_test2 IS 'will work'; +COMMENT ON SEQUENCE sequence_test2 IS NULL; + +-- Test lastval() +CREATE SEQUENCE seq; +SELECT nextval('seq'); +SELECT lastval(); +SELECT setval('seq', 99); +SELECT lastval(); +DISCARD SEQUENCES; +SELECT lastval(); + +CREATE SEQUENCE seq2; +SELECT nextval('seq2'); +SELECT lastval(); + +DROP SEQUENCE seq2; +-- should fail +SELECT lastval(); + +-- unlogged sequences +-- (more tests in src/test/recovery/) +CREATE UNLOGGED SEQUENCE sequence_test_unlogged; +ALTER SEQUENCE sequence_test_unlogged SET LOGGED; +\d sequence_test_unlogged +ALTER SEQUENCE sequence_test_unlogged SET UNLOGGED; +\d sequence_test_unlogged +DROP SEQUENCE sequence_test_unlogged; + +-- Test sequences in read-only transactions +CREATE TEMPORARY SEQUENCE sequence_test_temp1; +START TRANSACTION READ ONLY; +SELECT nextval('sequence_test_temp1'); -- ok +SELECT nextval('sequence_test2'); -- error +ROLLBACK; +START TRANSACTION READ ONLY; +SELECT setval('sequence_test_temp1', 1); -- ok +SELECT setval('sequence_test2', 1); -- error +ROLLBACK; + +-- privileges tests + +CREATE USER regress_seq_user; + +-- nextval +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT SELECT ON seq3 TO regress_seq_user; +SELECT nextval('seq3'); +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK + +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT UPDATE ON seq3 TO regress_seq_user; +SELECT nextval('seq3'); +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK + +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT USAGE ON seq3 TO regress_seq_user; +SELECT nextval('seq3'); +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK + +-- currval +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT SELECT ON seq3 TO regress_seq_user; +SELECT currval('seq3'); +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK + +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT UPDATE ON seq3 TO regress_seq_user; +SELECT currval('seq3'); +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK + +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT USAGE ON seq3 TO regress_seq_user; +SELECT currval('seq3'); +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK + +-- lastval +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT SELECT ON seq3 TO regress_seq_user; +SELECT lastval(); +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK + +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT UPDATE ON seq3 TO regress_seq_user; +SELECT lastval(); +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK + +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +SELECT nextval('seq3'); +REVOKE ALL ON seq3 FROM regress_seq_user; +GRANT USAGE ON seq3 TO regress_seq_user; +SELECT lastval(); +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK + +-- setval +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +CREATE SEQUENCE seq3; +REVOKE ALL ON seq3 FROM regress_seq_user; +SAVEPOINT save; +SELECT setval('seq3', 5); +ROLLBACK TO save; +GRANT UPDATE ON seq3 TO regress_seq_user; +SELECT setval('seq3', 5); +SELECT nextval('seq3'); +ROLLBACK; +DROP SEQUENCE seq3; -- YB: sequences are not transactional, so need to manually drop in addition to above ROLLBACK + +-- ALTER SEQUENCE +BEGIN; +SET LOCAL SESSION AUTHORIZATION regress_seq_user; +ALTER SEQUENCE sequence_test2 START WITH 1; +ROLLBACK; + +-- Sequences should get wiped out as well: +DROP TABLE serialTest1, serialTest2; + +-- Make sure sequences are gone: +SELECT * FROM information_schema.sequences WHERE sequence_name IN + ('sequence_test2', 'serialtest2_f2_seq', 'serialtest2_f3_seq', + 'serialtest2_f4_seq', 'serialtest2_f5_seq', 'serialtest2_f6_seq') + ORDER BY sequence_name ASC; + +DROP USER regress_seq_user; +DROP SEQUENCE seq; + +-- cache tests +CREATE SEQUENCE test_seq1 CACHE 10; +SELECT nextval('test_seq1'); +SELECT nextval('test_seq1'); +SELECT nextval('test_seq1'); + +DROP SEQUENCE test_seq1; diff --git a/src/test/regress/sql/yb.port.stats_ext.sql b/src/test/regress/sql/yb.port.stats_ext.sql new file mode 100644 index 0000000000000..7afbbbeb79b4f --- /dev/null +++ b/src/test/regress/sql/yb.port.stats_ext.sql @@ -0,0 +1,1668 @@ +-- Generic extended statistics support + +-- +-- Note: tables for which we check estimated row counts should be created +-- with autovacuum_enabled = off, so that we don't have unstable results +-- from auto-analyze happening when we didn't expect it. +-- + +-- check the number of estimated/actual rows in the top node +create function check_estimated_rows(text) returns table (estimated int, actual int) +language plpgsql as +$$ +declare + ln text; + tmp text[]; + first_row bool := true; +begin + for ln in + execute format('explain analyze %s', $1) + loop + if first_row then + first_row := false; + tmp := regexp_match(ln, 'rows=(\d*) .* rows=(\d*)'); + return query select tmp[1]::int, tmp[2]::int; + end if; + end loop; +end; +$$; + +-- YB note: enable CBO +SET yb_enable_optimizer_statistics TO on; +SET yb_enable_base_scans_cost_model TO on; + +-- Verify failures +CREATE TABLE ext_stats_test (x text, y int, z int); +CREATE STATISTICS tst; +CREATE STATISTICS tst ON a, b; +CREATE STATISTICS tst FROM sometab; +CREATE STATISTICS tst ON a, b FROM nonexistent; +CREATE STATISTICS tst ON a, b FROM ext_stats_test; +CREATE STATISTICS tst ON x, x, y FROM ext_stats_test; +CREATE STATISTICS tst ON x, x, y, x, x, y, x, x, y FROM ext_stats_test; +CREATE STATISTICS tst ON x, x, y, x, x, (x || 'x'), (y + 1), (x || 'x'), (x || 'x'), (y + 1) FROM ext_stats_test; +CREATE STATISTICS tst ON (x || 'x'), (x || 'x'), (y + 1), (x || 'x'), (x || 'x'), (y + 1), (x || 'x'), (x || 'x'), (y + 1) FROM ext_stats_test; +CREATE STATISTICS tst ON (x || 'x'), (x || 'x'), y FROM ext_stats_test; +CREATE STATISTICS tst (unrecognized) ON x, y FROM ext_stats_test; +-- incorrect expressions +CREATE STATISTICS tst ON (y) FROM ext_stats_test; -- single column reference +CREATE STATISTICS tst ON y + z FROM ext_stats_test; -- missing parentheses +CREATE STATISTICS tst ON (x, y) FROM ext_stats_test; -- tuple expression +DROP TABLE ext_stats_test; + +-- Ensure stats are dropped sanely, and test IF NOT EXISTS while at it +CREATE TABLE ab1 (a INTEGER, b INTEGER, c INTEGER); +CREATE STATISTICS IF NOT EXISTS ab1_a_b_stats ON a, b FROM ab1; +COMMENT ON STATISTICS ab1_a_b_stats IS 'new comment'; +CREATE ROLE regress_stats_ext; +SET SESSION AUTHORIZATION regress_stats_ext; +COMMENT ON STATISTICS ab1_a_b_stats IS 'changed comment'; +DROP STATISTICS ab1_a_b_stats; +ALTER STATISTICS ab1_a_b_stats RENAME TO ab1_a_b_stats_new; +RESET SESSION AUTHORIZATION; +DROP ROLE regress_stats_ext; + +CREATE STATISTICS IF NOT EXISTS ab1_a_b_stats ON a, b FROM ab1; +DROP STATISTICS ab1_a_b_stats; + +CREATE SCHEMA regress_schema_2; +CREATE STATISTICS regress_schema_2.ab1_a_b_stats ON a, b FROM ab1; + +-- Let's also verify the pg_get_statisticsobjdef output looks sane. +SELECT pg_get_statisticsobjdef(oid) FROM pg_statistic_ext WHERE stxname = 'ab1_a_b_stats'; + +DROP STATISTICS regress_schema_2.ab1_a_b_stats; + +-- Ensure statistics are dropped when columns are +CREATE STATISTICS ab1_b_c_stats ON b, c FROM ab1; +CREATE STATISTICS ab1_a_b_c_stats ON a, b, c FROM ab1; +CREATE STATISTICS ab1_b_a_stats ON b, a FROM ab1; +ALTER TABLE ab1 DROP COLUMN a; +\d ab1 +-- Ensure statistics are dropped when table is +SELECT stxname FROM pg_statistic_ext WHERE stxname LIKE 'ab1%'; +DROP TABLE ab1; +SELECT stxname FROM pg_statistic_ext WHERE stxname LIKE 'ab1%'; + +-- Ensure things work sanely with SET STATISTICS 0 +CREATE TABLE ab1 (a INTEGER, b INTEGER); +ALTER TABLE ab1 ALTER a SET STATISTICS 0; +INSERT INTO ab1 SELECT a, a%23 FROM generate_series(1, 1000) a; +CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; +ANALYZE ab1; +ALTER TABLE ab1 ALTER a SET STATISTICS -1; +-- setting statistics target 0 skips the statistics, without printing any message, so check catalog +ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0; +\d ab1 +ANALYZE ab1; +SELECT stxname, stxdndistinct, stxddependencies, stxdmcv, stxdinherit + FROM pg_statistic_ext s LEFT JOIN pg_statistic_ext_data d ON (d.stxoid = s.oid) + WHERE s.stxname = 'ab1_a_b_stats'; +ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1; +\d+ ab1 +-- partial analyze doesn't build stats either +ANALYZE ab1 (a); +ANALYZE ab1; +DROP TABLE ab1; +ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0; +ALTER STATISTICS IF EXISTS ab1_a_b_stats SET STATISTICS 0; + +/* YB: uncomment when INHERITS is supported (#5956) +-- Ensure we can build statistics for tables with inheritance. +CREATE TABLE ab1 (a INTEGER, b INTEGER); +CREATE TABLE ab1c () INHERITS (ab1); +INSERT INTO ab1 VALUES (1,1); +CREATE STATISTICS ab1_a_b_stats ON a, b FROM ab1; +ANALYZE ab1; +DROP TABLE ab1 CASCADE; + +-- Tests for stats with inheritance +CREATE TABLE stxdinh(a int, b int); +CREATE TABLE stxdinh1() INHERITS(stxdinh); +CREATE TABLE stxdinh2() INHERITS(stxdinh); +INSERT INTO stxdinh SELECT mod(a,50), mod(a,100) FROM generate_series(0, 1999) a; +INSERT INTO stxdinh1 SELECT mod(a,100), mod(a,100) FROM generate_series(0, 999) a; +INSERT INTO stxdinh2 SELECT mod(a,100), mod(a,100) FROM generate_series(0, 999) a; +VACUUM ANALYZE stxdinh, stxdinh1, stxdinh2; +-- Ensure non-inherited stats are not applied to inherited query +-- Without stats object, it looks like this +SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinh* GROUP BY 1, 2'); +SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinh* WHERE a = 0 AND b = 0'); +CREATE STATISTICS stxdinh ON a, b FROM stxdinh; +VACUUM ANALYZE stxdinh, stxdinh1, stxdinh2; +-- See if the extended stats affect the estimates +SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinh* GROUP BY 1, 2'); +-- Dependencies are applied at individual relations (within append), so +-- this estimate changes a bit because we improve estimates for the parent +SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinh* WHERE a = 0 AND b = 0'); +-- Ensure correct (non-inherited) stats are applied to inherited query +SELECT * FROM check_estimated_rows('SELECT a, b FROM ONLY stxdinh GROUP BY 1, 2'); +SELECT * FROM check_estimated_rows('SELECT a, b FROM ONLY stxdinh WHERE a = 0 AND b = 0'); +DROP TABLE stxdinh, stxdinh1, stxdinh2; +*/ -- YB + +-- Ensure inherited stats ARE applied to inherited query in partitioned table +CREATE TABLE stxdinp(i int, a int, b int) PARTITION BY RANGE (i); +CREATE TABLE stxdinp1 PARTITION OF stxdinp FOR VALUES FROM (1) TO (100); +INSERT INTO stxdinp SELECT 1, a/100, a/100 FROM generate_series(1, 999) a; +CREATE STATISTICS stxdinp ON (a + 1), a, b FROM stxdinp; +ANALYZE stxdinp; -- partitions are processed recursively -- YB: just use ANALYZE because VACUUM ANALYZE is no-op + +SELECT 1 FROM pg_statistic_ext WHERE stxrelid = 'stxdinp'::regclass; +SELECT * FROM check_estimated_rows('SELECT a, b FROM stxdinp GROUP BY 1, 2'); +SELECT * FROM check_estimated_rows('SELECT a + 1, b FROM ONLY stxdinp GROUP BY 1, 2'); +DROP TABLE stxdinp; + +-- basic test for statistics on expressions +CREATE TABLE ab1 (a INTEGER, b INTEGER, c TIMESTAMP, d TIMESTAMPTZ); + +-- expression stats may be built on a single expression column +CREATE STATISTICS ab1_exprstat_1 ON (a+b) FROM ab1; + +-- with a single expression, we only enable expression statistics +CREATE STATISTICS ab1_exprstat_2 ON (a+b) FROM ab1; +SELECT stxkind FROM pg_statistic_ext WHERE stxname = 'ab1_exprstat_2'; + +-- adding anything to the expression builds all statistics kinds +CREATE STATISTICS ab1_exprstat_3 ON (a+b), a FROM ab1; +SELECT stxkind FROM pg_statistic_ext WHERE stxname = 'ab1_exprstat_3'; + +-- date_trunc on timestamptz is not immutable, but that should not matter +CREATE STATISTICS ab1_exprstat_4 ON date_trunc('day', d) FROM ab1; + +-- date_trunc on timestamp is immutable +CREATE STATISTICS ab1_exprstat_5 ON date_trunc('day', c) FROM ab1; + +-- check use of a boolean-returning expression +CREATE STATISTICS ab1_exprstat_6 ON + (case a when 1 then true else false end), b FROM ab1; + +-- insert some data and run analyze, to test that these cases build properly +INSERT INTO ab1 +SELECT x / 10, x / 3, + '2020-10-01'::timestamp + x * interval '1 day', + '2020-10-01'::timestamptz + x * interval '1 day' +FROM generate_series(1, 100) x; +ANALYZE ab1; + +-- apply some stats +SELECT * FROM check_estimated_rows('SELECT * FROM ab1 WHERE (case a when 1 then true else false end) AND b=2'); + +DROP TABLE ab1; + +-- Verify supported object types for extended statistics +CREATE schema tststats; + +CREATE TABLE tststats.t (a int, b int, c text); +CREATE INDEX ti ON tststats.t (a, b); +CREATE SEQUENCE tststats.s; +CREATE VIEW tststats.v AS SELECT * FROM tststats.t; +CREATE MATERIALIZED VIEW tststats.mv AS SELECT * FROM tststats.t; +CREATE TYPE tststats.ty AS (a int, b int, c text); +CREATE FOREIGN DATA WRAPPER extstats_dummy_fdw; +CREATE SERVER extstats_dummy_srv FOREIGN DATA WRAPPER extstats_dummy_fdw; +CREATE FOREIGN TABLE tststats.f (a int, b int, c text) SERVER extstats_dummy_srv; +CREATE TABLE tststats.pt (a int, b int, c text) PARTITION BY RANGE (a, b); +CREATE TABLE tststats.pt1 PARTITION OF tststats.pt FOR VALUES FROM (-10, -10) TO (10, 10); + +CREATE STATISTICS tststats.s1 ON a, b FROM tststats.t; +CREATE STATISTICS tststats.s2 ON a, b FROM tststats.ti; +CREATE STATISTICS tststats.s3 ON a, b FROM tststats.s; +CREATE STATISTICS tststats.s4 ON a, b FROM tststats.v; +CREATE STATISTICS tststats.s5 ON a, b FROM tststats.mv; +CREATE STATISTICS tststats.s6 ON a, b FROM tststats.ty; +CREATE STATISTICS tststats.s7 ON a, b FROM tststats.f; +CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt; +CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1; +/* YB: uncomment when text value TOASTing is supported (#2003) +DO $$ +DECLARE + relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass; +BEGIN + EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname; +EXCEPTION WHEN wrong_object_type THEN + RAISE NOTICE 'stats on toast table not created'; +END; +$$; +*/ -- YB + +DROP SCHEMA tststats CASCADE; +DROP FOREIGN DATA WRAPPER extstats_dummy_fdw CASCADE; + +-- n-distinct tests +CREATE TABLE ndistinct ( + filler1 TEXT, + filler2 NUMERIC, + a INT, + b INT, + filler3 DATE, + c INT, + d INT +) +WITH (autovacuum_enabled = off); + +-- over-estimates when using only per-column statistics +INSERT INTO ndistinct (a, b, c, filler1) + SELECT i/100, i/100, i/100, cash_words((i/100)::money) + FROM generate_series(1,1000) s(i); + +ANALYZE ndistinct; + +-- Group Aggregate, due to over-estimate of the number of groups +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c, d'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c, d'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (a+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + +-- correct command +CREATE STATISTICS s10 ON a, b, c FROM ndistinct; + +ANALYZE ndistinct; + +SELECT s.stxkind, d.stxdndistinct + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxrelid = 'ndistinct'::regclass + AND d.stxoid = s.oid; + +-- minor improvement, make sure the ctid does not break the matching +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY ctid, a, b'); + +-- Hash Aggregate, thanks to estimates improved by the statistic +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c'); + +-- partial improvement (match on attributes) +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (a+1)'); + +-- expressions - no improvement +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + +-- last two plans keep using Group Aggregate, because 'd' is not covered +-- by the statistic and while it's NULL-only we assume 200 values for it +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c, d'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c, d'); + +TRUNCATE TABLE ndistinct; + +-- under-estimates when using only per-column statistics +INSERT INTO ndistinct (a, b, c, filler1) + SELECT mod(i,13), mod(i,17), mod(i,19), + cash_words(mod(i,23)::int::money) + FROM generate_series(1,1000) s(i); + +ANALYZE ndistinct; + +SELECT s.stxkind, d.stxdndistinct + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxrelid = 'ndistinct'::regclass + AND d.stxoid = s.oid; + +-- correct estimates +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c, d'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c, d'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, d'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (a+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + +DROP STATISTICS s10; + +SELECT s.stxkind, d.stxdndistinct + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxrelid = 'ndistinct'::regclass + AND d.stxoid = s.oid; + +-- dropping the statistics results in under-estimates +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, c, d'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY b, c, d'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, d'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (a+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + +-- ndistinct estimates with statistics on expressions +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + +CREATE STATISTICS s10 (ndistinct) ON (a+1), (b+100), (2*c) FROM ndistinct; + +ANALYZE ndistinct; + +SELECT s.stxkind, d.stxdndistinct + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxrelid = 'ndistinct'::regclass + AND d.stxoid = s.oid; + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a+1), (b+100), (2*c)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (a+1), (b+100)'); + +DROP STATISTICS s10; + +-- a mix of attributes and expressions +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (2*c)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (2*c)'); + +CREATE STATISTICS s10 (ndistinct) ON a, b, (2*c) FROM ndistinct; + +ANALYZE ndistinct; + +SELECT s.stxkind, d.stxdndistinct + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxrelid = 'ndistinct'::regclass + AND d.stxoid = s.oid; + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (2*c)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (2*c)'); + +DROP STATISTICS s10; + +-- combination of multiple ndistinct statistics, with/without expressions +TRUNCATE ndistinct; + +-- two mostly independent groups of columns +INSERT INTO ndistinct (a, b, c, d) + SELECT mod(i,3), mod(i,9), mod(i,5), mod(i,20) + FROM generate_series(1,1000) s(i); + +ANALYZE ndistinct; + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + +-- basic statistics on both attributes (no expressions) +CREATE STATISTICS s11 (ndistinct) ON a, b FROM ndistinct; + +CREATE STATISTICS s12 (ndistinct) ON c, d FROM ndistinct; + +ANALYZE ndistinct; + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + + +-- replace the second statistics by statistics on expressions + +DROP STATISTICS s12; + +CREATE STATISTICS s12 (ndistinct) ON (c * 10), (d - 1) FROM ndistinct; + +ANALYZE ndistinct; + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + + +-- replace the second statistics by statistics on both attributes and expressions + +DROP STATISTICS s12; + +CREATE STATISTICS s12 (ndistinct) ON c, d, (c * 10), (d - 1) FROM ndistinct; + +ANALYZE ndistinct; + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + + +-- replace the other statistics by statistics on both attributes and expressions + +DROP STATISTICS s11; + +CREATE STATISTICS s11 (ndistinct) ON a, b, (a*5), (b+1) FROM ndistinct; + +ANALYZE ndistinct; + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + + +-- replace statistics by somewhat overlapping ones (this expected to get worse estimate +-- because the first statistics shall be applied to 3 columns, and the second one can't +-- be really applied) + +DROP STATISTICS s11; +DROP STATISTICS s12; + +CREATE STATISTICS s11 (ndistinct) ON a, b, (a*5), (b+1) FROM ndistinct; +CREATE STATISTICS s12 (ndistinct) ON a, (b+1), (c * 10) FROM ndistinct; + +ANALYZE ndistinct; + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), b'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY (a*5), (b+1), c'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, b, (c*10)'); + +SELECT * FROM check_estimated_rows('SELECT COUNT(*) FROM ndistinct GROUP BY a, (b+1), c, (d - 1)'); + +DROP STATISTICS s11; +DROP STATISTICS s12; + +-- functional dependencies tests +CREATE TABLE functional_dependencies ( + filler1 TEXT, + filler2 NUMERIC, + a INT, + b TEXT, + filler3 DATE, + c INT, + d TEXT +) +WITH (autovacuum_enabled = off); + +CREATE INDEX fdeps_ab_idx ON functional_dependencies (a, b); +CREATE INDEX fdeps_abc_idx ON functional_dependencies (a, b, c); + +-- random data (no functional dependencies) +INSERT INTO functional_dependencies (a, b, c, filler1) + SELECT mod(i, 5), mod(i, 7), mod(i, 11), i FROM generate_series(1,1000) s(i); + +ANALYZE functional_dependencies; + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + +-- create statistics +CREATE STATISTICS func_deps_stat (dependencies) ON a, b, c FROM functional_dependencies; + +ANALYZE functional_dependencies; + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + +-- a => b, a => c, b => c +TRUNCATE functional_dependencies; +DROP STATISTICS func_deps_stat; + +-- now do the same thing, but with expressions +INSERT INTO functional_dependencies (a, b, c, filler1) + SELECT i, i, i, i FROM generate_series(1,5000) s(i); + +ANALYZE functional_dependencies; + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1 AND mod(c, 7) = 1'); + +-- create statistics +CREATE STATISTICS func_deps_stat (dependencies) ON (mod(a,11)), (mod(b::int, 13)), (mod(c, 7)) FROM functional_dependencies; + +ANALYZE functional_dependencies; + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE mod(a, 11) = 1 AND mod(b::int, 13) = 1 AND mod(c, 7) = 1'); + +-- a => b, a => c, b => c +TRUNCATE functional_dependencies; +DROP STATISTICS func_deps_stat; + +INSERT INTO functional_dependencies (a, b, c, filler1) + SELECT mod(i,100), mod(i,50), mod(i,25), i FROM generate_series(1,5000) s(i); + +ANALYZE functional_dependencies; + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + +-- IN +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b IN (''1'', ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b IN (''1'', ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c IN (1)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); + +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND (b = ''1'' OR b = ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 2 OR a = 51 OR a = 52) AND (b = ''1'' OR b = ''2'')'); + +-- OR clauses referencing different attributes +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR b = ''1'') AND b = ''1'''); + +-- ANY +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = ANY (ARRAY[1])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); + +-- ANY with inequalities should not benefit from functional dependencies +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a >= ANY (ARRAY[1, 51]) AND b <= ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a <= ANY (ARRAY[1, 2, 51, 52]) AND b >= ANY (ARRAY[''1'', ''2''])'); + +-- ALL (should not benefit from functional dependencies) +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ALL (ARRAY[''1''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ALL (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b = ALL (ARRAY[''1'', ''2''])'); + +-- create statistics +CREATE STATISTICS func_deps_stat (dependencies) ON a, b, c FROM functional_dependencies; + +ANALYZE functional_dependencies; + +-- print the detected dependencies +SELECT dependencies FROM pg_stats_ext WHERE statistics_name = 'func_deps_stat'; + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + +-- IN +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b IN (''1'', ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b IN (''1'', ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c IN (1)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)'); + +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND (b = ''1'' OR b = ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 2 OR a = 51 OR a = 52) AND (b = ''1'' OR b = ''2'')'); + +-- OR clauses referencing different attributes are incompatible +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR b = ''1'') AND b = ''1'''); + +-- ANY +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = ANY (ARRAY[1])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])'); + +-- ANY with inequalities should not benefit from functional dependencies +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a >= ANY (ARRAY[1, 51]) AND b <= ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a <= ANY (ARRAY[1, 2, 51, 52]) AND b >= ANY (ARRAY[''1'', ''2''])'); + +-- ALL (should not benefit from functional dependencies) +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ALL (ARRAY[''1''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ALL (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b = ALL (ARRAY[''1'', ''2''])'); + +-- changing the type of column c causes all its stats to be dropped, reverting +-- to default estimates without any statistics, i.e. 0.5% selectivity for each +-- condition +ALTER TABLE functional_dependencies ALTER COLUMN c TYPE numeric; + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + +ANALYZE functional_dependencies; + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = 1 AND b = ''1'' AND c = 1'); + +DROP STATISTICS func_deps_stat; + +-- now try functional dependencies with expressions + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = 2 AND upper(b) = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = 2 AND upper(b) = ''1'' AND (c + 1) = 2'); + +-- IN +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) IN (''1'', ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) IN (''1'', ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 52, 102, 152) AND upper(b) IN (''1'', ''26'') AND (c + 1) = 2'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 52, 102, 152) AND upper(b) IN (''1'', ''26'') AND (c + 1) IN (2)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 52, 54, 102, 104, 152, 154) AND upper(b) IN (''1'', ''2'', ''26'', ''27'') AND (c + 1) IN (2, 3)'); + +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 102) AND upper(b) = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 102) AND (upper(b) = ''1'' OR upper(b) = ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 4 OR (a * 2) = 102 OR (a * 2) = 104) AND (upper(b) = ''1'' OR upper(b) = ''2'')'); + +-- OR clauses referencing different attributes +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR upper(b) = ''1'') AND upper(b) = ''1'''); + +-- ANY +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 102]) AND upper(b) = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 102]) AND upper(b) = ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 4, 102, 104]) AND upper(b) = ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 52, 102, 152]) AND upper(b) = ANY (ARRAY[''1'', ''26'']) AND (c + 1) = 2'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 52, 102, 152]) AND upper(b) = ANY (ARRAY[''1'', ''26'']) AND (c + 1) = ANY (ARRAY[2])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 4, 52, 54, 102, 104, 152, 154]) AND upper(b) = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND (c + 1) = ANY (ARRAY[2, 3])'); + +-- ANY with inequalities should not benefit from functional dependencies +-- the estimates however improve thanks to having expression statistics +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) < ANY (ARRAY[2, 102]) AND upper(b) > ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) >= ANY (ARRAY[2, 102]) AND upper(b) <= ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) <= ANY (ARRAY[2, 4, 102, 104]) AND upper(b) >= ANY (ARRAY[''1'', ''2''])'); + +-- ALL (should not benefit from functional dependencies) +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ALL (ARRAY[''1''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ALL (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) = ALL (ARRAY[''1'', ''2''])'); + +-- create statistics on expressions +CREATE STATISTICS func_deps_stat (dependencies) ON (a * 2), upper(b), (c + 1) FROM functional_dependencies; + +ANALYZE functional_dependencies; + +-- print the detected dependencies +SELECT dependencies FROM pg_stats_ext WHERE statistics_name = 'func_deps_stat'; + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = 2 AND upper(b) = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = 2 AND upper(b) = ''1'' AND (c + 1) = 2'); + +-- IN +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) IN (''1'', ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) IN (''1'', ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 52, 102, 152) AND upper(b) IN (''1'', ''26'') AND (c + 1) = 2'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 52, 102, 152) AND upper(b) IN (''1'', ''26'') AND (c + 1) IN (2)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 52, 54, 102, 104, 152, 154) AND upper(b) IN (''1'', ''2'', ''26'', ''27'') AND (c + 1) IN (2, 3)'); + +-- OR clauses referencing the same attribute +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 102) AND upper(b) = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 102) AND (upper(b) = ''1'' OR upper(b) = ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 4 OR (a * 2) = 102 OR (a * 2) = 104) AND (upper(b) = ''1'' OR upper(b) = ''2'')'); + +-- OR clauses referencing different attributes +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR upper(b) = ''1'') AND upper(b) = ''1'''); + +-- ANY +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 102]) AND upper(b) = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 102]) AND upper(b) = ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 4, 102, 104]) AND upper(b) = ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 52, 102, 152]) AND upper(b) = ANY (ARRAY[''1'', ''26'']) AND (c + 1) = 2'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 52, 102, 152]) AND upper(b) = ANY (ARRAY[''1'', ''26'']) AND (c + 1) = ANY (ARRAY[2])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) = ANY (ARRAY[2, 4, 52, 54, 102, 104, 152, 154]) AND upper(b) = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND (c + 1) = ANY (ARRAY[2, 3])'); + +-- ANY with inequalities should not benefit from functional dependencies +-- the estimates however improve thanks to having expression statistics +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) < ANY (ARRAY[2, 102]) AND upper(b) > ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) >= ANY (ARRAY[2, 102]) AND upper(b) <= ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) <= ANY (ARRAY[2, 4, 102, 104]) AND upper(b) >= ANY (ARRAY[''1'', ''2''])'); + +-- ALL (should not benefit from functional dependencies) +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ALL (ARRAY[''1''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 102) AND upper(b) = ALL (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a * 2) IN (2, 4, 102, 104) AND upper(b) = ALL (ARRAY[''1'', ''2''])'); + +-- check the ability to use multiple functional dependencies +CREATE TABLE functional_dependencies_multi ( + a INTEGER, + b INTEGER, + c INTEGER, + d INTEGER +) +WITH (autovacuum_enabled = off); + +INSERT INTO functional_dependencies_multi (a, b, c, d) + SELECT + mod(i,7), + mod(i,7), + mod(i,11), + mod(i,11) + FROM generate_series(1,5000) s(i); + +ANALYZE functional_dependencies_multi; + +-- estimates without any functional dependencies +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE a = 0 AND b = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE 0 = a AND 0 = b'); +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE c = 0 AND d = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE a = 0 AND b = 0 AND c = 0 AND d = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE 0 = a AND b = 0 AND 0 = c AND d = 0'); + +-- create separate functional dependencies +CREATE STATISTICS functional_dependencies_multi_1 (dependencies) ON a, b FROM functional_dependencies_multi; +CREATE STATISTICS functional_dependencies_multi_2 (dependencies) ON c, d FROM functional_dependencies_multi; + +ANALYZE functional_dependencies_multi; + +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE a = 0 AND b = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE 0 = a AND 0 = b'); +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE c = 0 AND d = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE a = 0 AND b = 0 AND c = 0 AND d = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies_multi WHERE 0 = a AND b = 0 AND 0 = c AND d = 0'); + +DROP TABLE functional_dependencies_multi; + +-- MCV lists +CREATE TABLE mcv_lists ( + filler1 TEXT, + filler2 NUMERIC, + a INT, + b VARCHAR, + filler3 DATE, + c INT, + d TEXT, + ia INT[] +) +WITH (autovacuum_enabled = off); + +-- random data (no MCV list) +INSERT INTO mcv_lists (a, b, c, filler1) + SELECT mod(i,37), mod(i,41), mod(i,43), mod(i,47) FROM generate_series(1,5000) s(i); + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'' AND c = 1'); + +-- create statistics +CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists; + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'' AND c = 1'); + +TRUNCATE mcv_lists; +DROP STATISTICS mcv_lists_stats; + +-- random data (no MCV list), but with expression +INSERT INTO mcv_lists (a, b, c, filler1) + SELECT i, i, i, i FROM generate_series(1,1000) s(i); + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 AND mod(b::int,11) = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 AND mod(b::int,11) = 1 AND mod(c,13) = 1'); + +-- create statistics +CREATE STATISTICS mcv_lists_stats (mcv) ON (mod(a,7)), (mod(b::int,11)), (mod(c,13)) FROM mcv_lists; + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 AND mod(b::int,11) = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 AND mod(b::int,11) = 1 AND mod(c,13) = 1'); + +-- 100 distinct combinations, all in the MCV list +TRUNCATE mcv_lists; +DROP STATISTICS mcv_lists_stats; + +INSERT INTO mcv_lists (a, b, c, ia, filler1) + SELECT mod(i,100), mod(i,50), mod(i,25), array[mod(i,25)], i + FROM generate_series(1,5000) s(i); + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = a AND ''1'' = b'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 1 AND b < ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > a AND ''1'' > b'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= 0 AND b <= ''0'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 0 >= a AND ''0'' >= b'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'' AND c = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 5 AND b < ''1'' AND c < 5'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 5 AND ''1'' > b AND 5 > c'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= 4 AND b <= ''0'' AND c <= 4'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 4 >= a AND ''0'' >= b AND 4 >= c'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52) AND b IN ( ''1'', ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52, NULL) AND b IN ( ''1'', ''2'', NULL)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[NULL, 1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2'', NULL])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, 2, 3]) AND b IN (''1'', ''2'', ''3'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, NULL, 2, 3]) AND b IN (''1'', ''2'', NULL, ''3'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3, NULL])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', ''3'') AND c > ANY (ARRAY[1, 2, 3])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', NULL, ''3'') AND c > ANY (ARRAY[1, 2, NULL, 3])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)'); + +-- create statistics +CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c, ia FROM mcv_lists; + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = a AND ''1'' = b'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 1 AND b < ''1'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > a AND ''1'' > b'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= 0 AND b <= ''0'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 0 >= a AND ''0'' >= b'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'' AND c = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 5 AND b < ''1'' AND c < 5'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < 5 AND ''1'' > b AND 5 > c'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= 4 AND b <= ''0'' AND c <= 4'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 4 >= a AND ''0'' >= b AND 4 >= c'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52) AND b IN ( ''1'', ''2'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52, NULL) AND b IN ( ''1'', ''2'', NULL)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[NULL, 1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2'', NULL])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, 2, 3]) AND b IN (''1'', ''2'', ''3'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, NULL, 2, 3]) AND b IN (''1'', ''2'', NULL, ''3'')'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3, NULL])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', ''3'') AND c > ANY (ARRAY[1, 2, 3])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', NULL, ''3'') AND c > ANY (ARRAY[1, 2, NULL, 3])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)'); + +-- check change of unrelated column type does not reset the MCV statistics +ALTER TABLE mcv_lists ALTER COLUMN d TYPE VARCHAR(64); + +SELECT d.stxdmcv IS NOT NULL + FROM pg_statistic_ext s, pg_statistic_ext_data d + WHERE s.stxname = 'mcv_lists_stats' + AND d.stxoid = s.oid; + +-- check change of column type resets the MCV statistics +ALTER TABLE mcv_lists ALTER COLUMN c TYPE numeric; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); + + +-- 100 distinct combinations, all in the MCV list, but with expressions +TRUNCATE mcv_lists; +DROP STATISTICS mcv_lists_stats; + +INSERT INTO mcv_lists (a, b, c, filler1) + SELECT i, i, i, i FROM generate_series(1,1000) s(i); + +ANALYZE mcv_lists; + +-- without any stats on the expressions, we have to use default selectivities, which +-- is why the estimates here are different from the pre-computed case above + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,20) AND 1 = mod(b::int,10)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < 1 AND mod(b::int,10) < 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,20) AND 1 > mod(b::int,10)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1 AND mod(c,5) = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 OR mod(b::int,10) = 1 OR mod(c,25) = 1 OR d IS NOT NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) IN (1, 2, 51, 52, NULL) AND mod(b::int,10) IN ( 1, 2, NULL)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,10) = ANY (ARRAY[1, 2])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,10) IN (1, 2, NULL, 3)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < ALL (ARRAY[4, 5]) AND mod(b::int,10) IN (1, 2, 3) AND mod(c,5) > ANY (ARRAY[1, 2, 3])'); + +-- create statistics with expressions only (we create three separate stats, in order not to build more complex extended stats) +CREATE STATISTICS mcv_lists_stats_1 ON (mod(a,20)) FROM mcv_lists; +CREATE STATISTICS mcv_lists_stats_2 ON (mod(b::int,10)) FROM mcv_lists; +CREATE STATISTICS mcv_lists_stats_3 ON (mod(c,5)) FROM mcv_lists; + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,20) AND 1 = mod(b::int,10)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < 1 AND mod(b::int,10) < 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,20) AND 1 > mod(b::int,10)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1 AND mod(c,5) = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 OR mod(b::int,10) = 1 OR mod(c,25) = 1 OR d IS NOT NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) IN (1, 2, 51, 52, NULL) AND mod(b::int,10) IN ( 1, 2, NULL)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,10) = ANY (ARRAY[1, 2])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,10) IN (1, 2, NULL, 3)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < ALL (ARRAY[4, 5]) AND mod(b::int,10) IN (1, 2, 3) AND mod(c,5) > ANY (ARRAY[1, 2, 3])'); + +DROP STATISTICS mcv_lists_stats_1; +DROP STATISTICS mcv_lists_stats_2; +DROP STATISTICS mcv_lists_stats_3; + +-- create statistics with both MCV and expressions +CREATE STATISTICS mcv_lists_stats (mcv) ON (mod(a,20)), (mod(b::int,10)), (mod(c,5)) FROM mcv_lists; + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 = mod(a,20) AND 1 = mod(b::int,10)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < 1 AND mod(b::int,10) < 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE 1 > mod(a,20) AND 1 > mod(b::int,10)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 AND mod(b::int,10) = 1 AND mod(c,5) = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 OR mod(b::int,10) = 1 OR mod(c,25) = 1 OR d IS NOT NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) IN (1, 2, 51, 52, NULL) AND mod(b::int,10) IN ( 1, 2, NULL)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = ANY (ARRAY[1, 2, 51, 52]) AND mod(b::int,10) = ANY (ARRAY[1, 2])'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) <= ANY (ARRAY[1, NULL, 2, 3]) AND mod(b::int,10) IN (1, 2, NULL, 3)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) < ALL (ARRAY[4, 5]) AND mod(b::int,10) IN (1, 2, 3) AND mod(c,5) > ANY (ARRAY[1, 2, 3])'); + +-- we can't use the statistic for OR clauses that are not fully covered (missing 'd' attribute) +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,20) = 1 OR mod(b::int,10) = 1 OR mod(c,5) = 1 OR d IS NOT NULL'); + +-- 100 distinct combinations with NULL values, all in the MCV list +TRUNCATE mcv_lists; +DROP STATISTICS mcv_lists_stats; + +INSERT INTO mcv_lists (a, b, c, filler1) + SELECT + (CASE WHEN mod(i,100) = 1 THEN NULL ELSE mod(i,100) END), + (CASE WHEN mod(i,50) = 1 THEN NULL ELSE mod(i,50) END), + (CASE WHEN mod(i,25) = 1 THEN NULL ELSE mod(i,25) END), + i + FROM generate_series(1,5000) s(i); + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NULL AND c IS NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NOT NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NOT NULL AND b IS NULL AND c IS NOT NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (0, 1) AND b IN (''0'', ''1'')'); + +-- create statistics +CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists; + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NULL AND c IS NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND b IS NOT NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NOT NULL AND b IS NULL AND c IS NOT NULL'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (0, 1) AND b IN (''0'', ''1'')'); + +-- test pg_mcv_list_items with a very simple (single item) MCV list +TRUNCATE mcv_lists; +INSERT INTO mcv_lists (a, b, c) SELECT 1, 2, 3 FROM generate_series(1,1000) s(i); +ANALYZE mcv_lists; + +SELECT m.* + FROM pg_statistic_ext s, pg_statistic_ext_data d, + pg_mcv_list_items(d.stxdmcv) m + WHERE s.stxname = 'mcv_lists_stats' + AND d.stxoid = s.oid; + +-- 2 distinct combinations with NULL values, all in the MCV list +TRUNCATE mcv_lists; +DROP STATISTICS mcv_lists_stats; + +INSERT INTO mcv_lists (a, b, c, d) + SELECT + NULL, -- always NULL + (CASE WHEN mod(i,2) = 0 THEN NULL ELSE 'x' END), + (CASE WHEN mod(i,2) = 0 THEN NULL ELSE 0 END), + (CASE WHEN mod(i,2) = 0 THEN NULL ELSE 'x' END) + FROM generate_series(1,5000) s(i); + +ANALYZE mcv_lists; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE b = ''x'' OR d = ''x'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''x'' OR d = ''x'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND (b = ''x'' OR d = ''x'')'); + +-- create statistics +CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, d FROM mcv_lists; + +ANALYZE mcv_lists; + +-- test pg_mcv_list_items with MCV list containing variable-length data and NULLs +SELECT m.* + FROM pg_statistic_ext s, pg_statistic_ext_data d, + pg_mcv_list_items(d.stxdmcv) m + WHERE s.stxname = 'mcv_lists_stats' + AND d.stxoid = s.oid; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE b = ''x'' OR d = ''x'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''x'' OR d = ''x'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IS NULL AND (b = ''x'' OR d = ''x'')'); + +-- mcv with pass-by-ref fixlen types, e.g. uuid +CREATE TABLE mcv_lists_uuid ( + a UUID, + b UUID, + c UUID +) +WITH (autovacuum_enabled = off); + +INSERT INTO mcv_lists_uuid (a, b, c) + SELECT + md5(mod(i,100)::text)::uuid, + md5(mod(i,50)::text)::uuid, + md5(mod(i,25)::text)::uuid + FROM generate_series(1,5000) s(i); + +ANALYZE mcv_lists_uuid; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_uuid WHERE a = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND b = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_uuid WHERE a = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND b = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND c = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'''); + +CREATE STATISTICS mcv_lists_uuid_stats (mcv) ON a, b, c + FROM mcv_lists_uuid; + +ANALYZE mcv_lists_uuid; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_uuid WHERE a = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND b = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'''); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_uuid WHERE a = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND b = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'' AND c = ''1679091c-5a88-0faf-6fb5-e6087eb1b2dc'''); + +DROP TABLE mcv_lists_uuid; + +-- mcv with arrays +CREATE TABLE mcv_lists_arrays ( + a TEXT[], + b NUMERIC[], + c INT[] +) +WITH (autovacuum_enabled = off); + +INSERT INTO mcv_lists_arrays (a, b, c) + SELECT + ARRAY[md5((i/100)::text), md5((i/100-1)::text), md5((i/100+1)::text)], + ARRAY[(i/100-1)::numeric/1000, (i/100)::numeric/1000, (i/100+1)::numeric/1000], + ARRAY[(i/100-1), i/100, (i/100+1)] + FROM generate_series(1,5000) s(i); + +CREATE STATISTICS mcv_lists_arrays_stats (mcv) ON a, b, c + FROM mcv_lists_arrays; + +ANALYZE mcv_lists_arrays; + +-- mcv with bool +CREATE TABLE mcv_lists_bool ( + a BOOL, + b BOOL, + c BOOL +) +WITH (autovacuum_enabled = off); + +INSERT INTO mcv_lists_bool (a, b, c) + SELECT + (mod(i,2) = 0), (mod(i,4) = 0), (mod(i,8) = 0) + FROM generate_series(1,10000) s(i); + +ANALYZE mcv_lists_bool; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE a AND b AND c'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND b AND c'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND NOT b AND c'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND b AND NOT c'); + +CREATE STATISTICS mcv_lists_bool_stats (mcv) ON a, b, c + FROM mcv_lists_bool; + +ANALYZE mcv_lists_bool; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE a AND b AND c'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND b AND c'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND NOT b AND c'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND b AND NOT c'); + +-- mcv covering just a small fraction of data +CREATE TABLE mcv_lists_partial ( + a INT, + b INT, + c INT +); + +-- 10 frequent groups, each with 100 elements +INSERT INTO mcv_lists_partial (a, b, c) + SELECT + mod(i,10), + mod(i,10), + mod(i,10) + FROM generate_series(0,999) s(i); + +-- 100 groups that will make it to the MCV list (includes the 10 frequent ones) +INSERT INTO mcv_lists_partial (a, b, c) + SELECT + i, + i, + i + FROM generate_series(0,99) s(i); + +-- 4000 groups in total, most of which won't make it (just a single item) +INSERT INTO mcv_lists_partial (a, b, c) + SELECT + i, + i, + i + FROM generate_series(0,3999) s(i); + +ANALYZE mcv_lists_partial; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 AND b = 0 AND c = 0'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 0'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 10 AND b = 10 AND c = 10'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 10 OR b = 10 OR c = 10'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 AND b = 0 AND c = 10'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 10'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0 AND c = 0) OR (a = 1 AND b = 1 AND c = 1) OR (a = 2 AND b = 2 AND c = 2)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0) OR (a = 0 AND c = 0) OR (b = 0 AND c = 0)'); + +CREATE STATISTICS mcv_lists_partial_stats (mcv) ON a, b, c + FROM mcv_lists_partial; + +ANALYZE mcv_lists_partial; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 AND b = 0 AND c = 0'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 0'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 10 AND b = 10 AND c = 10'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 10 OR b = 10 OR c = 10'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 AND b = 0 AND c = 10'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 10'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0 AND c = 0) OR (a = 1 AND b = 1 AND c = 1) OR (a = 2 AND b = 2 AND c = 2)'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0) OR (a = 0 AND c = 0) OR (b = 0 AND c = 0)'); + +DROP TABLE mcv_lists_partial; + +-- check the ability to use multiple MCV lists +CREATE TABLE mcv_lists_multi ( + a INTEGER, + b INTEGER, + c INTEGER, + d INTEGER +) +WITH (autovacuum_enabled = off); + +INSERT INTO mcv_lists_multi (a, b, c, d) + SELECT + mod(i,5), + mod(i,5), + mod(i,7), + mod(i,7) + FROM generate_series(1,5000) s(i); + +ANALYZE mcv_lists_multi; + +-- estimates without any mcv statistics +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 AND b = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE c = 0 AND d = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE b = 0 AND c = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE b = 0 OR c = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 AND b = 0 AND c = 0 AND d = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE (a = 0 AND b = 0) OR (c = 0 AND d = 0)'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 OR b = 0 OR c = 0 OR d = 0'); + +-- create separate MCV statistics +CREATE STATISTICS mcv_lists_multi_1 (mcv) ON a, b FROM mcv_lists_multi; +CREATE STATISTICS mcv_lists_multi_2 (mcv) ON c, d FROM mcv_lists_multi; + +ANALYZE mcv_lists_multi; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 AND b = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE c = 0 AND d = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE b = 0 AND c = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE b = 0 OR c = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 AND b = 0 AND c = 0 AND d = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE (a = 0 AND b = 0) OR (c = 0 AND d = 0)'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_multi WHERE a = 0 OR b = 0 OR c = 0 OR d = 0'); + +DROP TABLE mcv_lists_multi; + + +-- statistics on integer expressions +CREATE TABLE expr_stats (a int, b int, c int); +INSERT INTO expr_stats SELECT mod(i,10), mod(i,10), mod(i,10) FROM generate_series(1,1000) s(i); +ANALYZE expr_stats; + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (2*a) = 0 AND (3*b) = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (a+b) = 0 AND (a-b) = 0'); + +CREATE STATISTICS expr_stats_1 (mcv) ON (a+b), (a-b), (2*a), (3*b) FROM expr_stats; +ANALYZE expr_stats; + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (2*a) = 0 AND (3*b) = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE (a+b) = 0 AND (a-b) = 0'); + +DROP STATISTICS expr_stats_1; +DROP TABLE expr_stats; + +-- statistics on a mix columns and expressions +CREATE TABLE expr_stats (a int, b int, c int); +INSERT INTO expr_stats SELECT mod(i,10), mod(i,10), mod(i,10) FROM generate_series(1,1000) s(i); +ANALYZE expr_stats; + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (2*a) = 0 AND (3*b) = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 3 AND b = 3 AND (a-b) = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND b = 1 AND (a-b) = 0'); + +CREATE STATISTICS expr_stats_1 (mcv) ON a, b, (2*a), (3*b), (a+b), (a-b) FROM expr_stats; +ANALYZE expr_stats; + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (2*a) = 0 AND (3*b) = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 3 AND b = 3 AND (a-b) = 0'); +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND b = 1 AND (a-b) = 0'); + +DROP TABLE expr_stats; + +-- statistics on expressions with different data types +CREATE TABLE expr_stats (a int, b name, c text); +INSERT INTO expr_stats SELECT mod(i,10), md5(mod(i,10)::text), md5(mod(i,10)::text) FROM generate_series(1,1000) s(i); +ANALYZE expr_stats; + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b || c) <= ''z'' AND (c || b) >= ''0'''); + +CREATE STATISTICS expr_stats_1 (mcv) ON a, b, (b || c), (c || b) FROM expr_stats; +ANALYZE expr_stats; + +SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b || c) <= ''z'' AND (c || b) >= ''0'''); + +DROP TABLE expr_stats; + +-- test handling of a mix of compatible and incompatible expressions +CREATE TABLE expr_stats_incompatible_test ( + c0 double precision, + c1 boolean NOT NULL +); + +CREATE STATISTICS expr_stat_comp_1 ON c0, c1 FROM expr_stats_incompatible_test; + +INSERT INTO expr_stats_incompatible_test VALUES (1234,false), (5678,true); +ANALYZE expr_stats_incompatible_test; + +SELECT c0 FROM ONLY expr_stats_incompatible_test WHERE +( + upper('x') LIKE ('x'||('[0,1]'::int4range)) + AND + (c0 IN (0, 1) OR c1) +); + +DROP TABLE expr_stats_incompatible_test; + +-- Permission tests. Users should not be able to see specific data values in +-- the extended statistics, if they lack permission to see those values in +-- the underlying table. +-- +-- Currently this is only relevant for MCV stats. +CREATE SCHEMA tststats; + +CREATE TABLE tststats.priv_test_tbl ( + a int, + b int +); + +INSERT INTO tststats.priv_test_tbl + SELECT mod(i,5), mod(i,10) FROM generate_series(1,100) s(i); + +CREATE STATISTICS tststats.priv_test_stats (mcv) ON a, b + FROM tststats.priv_test_tbl; + +ANALYZE tststats.priv_test_tbl; + +-- Check printing info about extended statistics by \dX +create table stts_t1 (a int, b int); +create statistics stts_1 (ndistinct) on a, b from stts_t1; +create statistics stts_2 (ndistinct, dependencies) on a, b from stts_t1; +create statistics stts_3 (ndistinct, dependencies, mcv) on a, b from stts_t1; + +create table stts_t2 (a int, b int, c int); +create statistics stts_4 on b, c from stts_t2; + +create table stts_t3 (col1 int, col2 int, col3 int); +create statistics stts_hoge on col1, col2, col3 from stts_t3; + +create schema stts_s1; +create schema stts_s2; +create statistics stts_s1.stts_foo on col1, col2 from stts_t3; +create statistics stts_s2.stts_yama (dependencies, mcv) on col1, col3 from stts_t3; + +insert into stts_t1 select i,i from generate_series(1,100) i; +analyze stts_t1; +set search_path to public, stts_s1, stts_s2, tststats; + +\dX +\dX stts_? +\dX *stts_hoge +\dX+ +\dX+ stts_? +\dX+ *stts_hoge +\dX+ stts_s2.stts_yama + +set search_path to public, stts_s1; +\dX + +create role regress_stats_ext nosuperuser; +set role regress_stats_ext; +\dX +reset role; + +drop table stts_t1, stts_t2, stts_t3; +drop schema stts_s1, stts_s2 cascade; -- YB: fails because dropping multiple schemas is not supported (#880) +drop schema stts_s1 cascade; -- YB: workaround for above +drop schema stts_s2 cascade; -- YB: workaround for above +drop user regress_stats_ext; +reset search_path; + +-- User with no access +CREATE USER regress_stats_user1; +GRANT USAGE ON SCHEMA tststats TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +SELECT * FROM tststats.priv_test_tbl; -- Permission denied + +-- Check individual columns if we don't have table privilege +SELECT * FROM tststats.priv_test_tbl + WHERE a = 1 and tststats.priv_test_tbl.* > (1, 1) is not null; + +-- Attempt to gain access using a leaky operator +CREATE FUNCTION op_leak(int, int) RETURNS bool + AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END' + LANGUAGE plpgsql; +CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int, + restrict = scalarltsel); +SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied +DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied + +-- Grant access via a security barrier view, but hide all data +RESET SESSION AUTHORIZATION; +CREATE VIEW tststats.priv_test_view WITH (security_barrier=true) + AS SELECT * FROM tststats.priv_test_tbl WHERE false; +GRANT SELECT, DELETE ON tststats.priv_test_view TO regress_stats_user1; + +-- Should now have access via the view, but see nothing and leak nothing +SET SESSION AUTHORIZATION regress_stats_user1; +SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak +DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak + +-- Grant table access, but hide all data with RLS +RESET SESSION AUTHORIZATION; +ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY; +GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1; + +-- Should now have direct table access, but see nothing and leak nothing +SET SESSION AUTHORIZATION regress_stats_user1; +SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak +DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak + +-- Tidy up +DROP OPERATOR <<< (int, int); +DROP FUNCTION op_leak(int, int); +RESET SESSION AUTHORIZATION; +DROP SCHEMA tststats CASCADE; +DROP USER regress_stats_user1; diff --git a/src/test/regress/sql/yb.port.stats_import.sql b/src/test/regress/sql/yb.port.stats_import.sql new file mode 100644 index 0000000000000..60e50d15c11b0 --- /dev/null +++ b/src/test/regress/sql/yb.port.stats_import.sql @@ -0,0 +1,962 @@ +CREATE SCHEMA stats_import; + +CREATE TYPE stats_import.complex_type AS ( + a integer, + b real, + c text, + d date, + e jsonb); + +CREATE TABLE stats_import.test( + id INTEGER PRIMARY KEY, + name text, + comp stats_import.complex_type, + arange int4range, + tags text[] +) WITH (autovacuum_enabled = false); + +SELECT + pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relpages', 18::integer, + 'reltuples', 21::real, + 'relallvisible', 24::integer); + +-- CREATE INDEX on a table with autovac disabled should not overwrite +-- stats +CREATE INDEX test_i ON stats_import.test(id); + +SELECT relname, relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass +ORDER BY relname; + +SELECT pg_clear_relation_stats('stats_import', 'test'); + +-- +-- relstats tests +-- + +-- error: schemaname missing +SELECT pg_catalog.pg_restore_relation_stats( + 'relname', 'test', + 'relpages', 17::integer); + +-- error: relname missing +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relpages', 17::integer); + +--- error: schemaname is wrong type +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 3.6::float, + 'relname', 'test', + 'relpages', 17::integer); + +--- error: relname is wrong type +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 0::oid, + 'relpages', 17::integer); + +-- error: relation not found +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'nope', + 'relpages', 17::integer); + +-- error: odd number of variadic arguments cannot be pairs +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relallvisible'); + +-- error: argument name is NULL +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + NULL, '17'::integer); + +-- starting stats +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test_i'::regclass; + +-- regular indexes have special case locking rules +BEGIN; +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test_i', + 'relpages', 18::integer); + +-- YB: DDLs run execute in internal transactions, that get auto committed. +SELECT mode FROM pg_locks +WHERE relation = 'stats_import.test'::regclass AND + pid = pg_backend_pid() AND granted; + +-- YB: DDLs run execute in internal transactions, that get auto committed. +SELECT mode FROM pg_locks +WHERE relation = 'stats_import.test_i'::regclass AND + pid = pg_backend_pid() AND granted; + +COMMIT; + +-- relpages may be -1 for partitioned tables +CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i); +CREATE TABLE stats_import.part_child_1 + PARTITION OF stats_import.part_parent + FOR VALUES FROM (0) TO (10) + WITH (autovacuum_enabled = false); + +CREATE INDEX part_parent_i ON stats_import.part_parent(i); + +ANALYZE stats_import.part_parent; + +SELECT relpages +FROM pg_class +WHERE oid = 'stats_import.part_parent'::regclass; + +-- +-- Partitioned indexes aren't analyzed but it is possible to set +-- stats. The locking rules are different from normal indexes due to +-- the rules for in-place updates: both the partitioned table and the +-- partitioned index are locked in ShareUpdateExclusive mode. +-- +BEGIN; + +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'part_parent_i', + 'relpages', 2::integer); + +-- YB: DDLs run execute in internal transactions, that get auto committed. +SELECT mode FROM pg_locks +WHERE relation = 'stats_import.part_parent'::regclass AND + pid = pg_backend_pid() AND granted; + +-- YB: DDLs run execute in internal transactions, that get auto committed. +SELECT mode FROM pg_locks +WHERE relation = 'stats_import.part_parent_i'::regclass AND + pid = pg_backend_pid() AND granted; + +COMMIT; + +SELECT relpages +FROM pg_class +WHERE oid = 'stats_import.part_parent_i'::regclass; + +-- ok: set all relstats, with version, no bounds checking +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'version', 150000::integer, + 'relpages', '-17'::integer, + 'reltuples', 400::real, + 'relallvisible', 4::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- ok: set just relpages, rest stay same +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relpages', '16'::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- ok: set just reltuples, rest stay same +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', '500'::real); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- ok: set just relallvisible, rest stay same +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relallvisible', 5::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- warn: bad relpages type, rest updated +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relpages', 'nope'::text, + 'reltuples', 400.0::real, + 'relallvisible', 4::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- unrecognized argument name, rest ok +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'relpages', '171'::integer, + 'nope', 10::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- ok: clear stats +SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'test'); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- invalid relkinds for statistics +CREATE SEQUENCE stats_import.testseq; + +SELECT pg_catalog.pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'testseq'); + +SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq'); + +CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test; + +SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview'); + +-- +-- attribute stats +-- + +-- error: schemaname missing +SELECT pg_catalog.pg_restore_attribute_stats( + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.1::real); + +-- error: schema does not exist +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'nope', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.1::real); + +-- error: relname missing +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.1::real); + +-- error: relname does not exist +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'nope', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.1::real); + +-- error: relname null +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', NULL, + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.1::real); + +-- error: NULL attname +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', NULL, + 'inherited', false::boolean, + 'null_frac', 0.1::real); + +-- error: attname doesn't exist +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'nope', + 'inherited', false::boolean, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); + +-- error: both attname and attnum +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'attnum', 1::smallint, + 'inherited', false::boolean, + 'null_frac', 0.1::real); + +-- error: neither attname nor attnum +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'inherited', false::boolean, + 'null_frac', 0.1::real); + +-- error: attribute is system column +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'xmin', + 'inherited', false::boolean, + 'null_frac', 0.1::real); + +-- error: inherited null +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', NULL::boolean, + 'null_frac', 0.1::real); + +-- ok: just the fixed values, with version, no stakinds +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.2::real, + 'avg_width', 5::integer, + 'n_distinct', 0.6::real); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- +-- ok: restore by attnum, we normally reserve this for +-- indexes, but there is no reason it shouldn't work +-- for any stat-having relation. +-- +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attnum', 1::smallint, + 'inherited', false::boolean, + 'null_frac', 0.4::real); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: unrecognized argument name, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.2::real, + 'nope', 0.5::real); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: mcv / mcf null mismatch part 1, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.21::real, + 'most_common_freqs', '{0.1,0.2,0.3}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: mcv / mcf null mismatch part 2, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.21::real, + 'most_common_vals', '{1,2,3}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: mcf type mismatch, mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.22::real, + 'most_common_vals', '{2,1,3}'::text, + 'most_common_freqs', '{0.2,0.1}'::double precision[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: mcv cast failure, mcv-pair fails, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.23::real, + 'most_common_vals', '{2,four,3}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- ok: mcv+mcf +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'most_common_vals', '{2,1,3}'::text, + 'most_common_freqs', '{0.3,0.25,0.05}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: NULL in histogram array, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.24::real, + 'histogram_bounds', '{1,NULL,3,4}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- ok: histogram_bounds +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'histogram_bounds', '{1,2,3,4}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: elem_count_histogram null element, rest get set +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'tags', + 'inherited', false::boolean, + 'null_frac', 0.25::real, + 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + +-- ok: elem_count_histogram +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'tags', + 'inherited', false::boolean, + 'null_frac', 0.26::real, + 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + +-- warn: range stats on a scalar type, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.27::real, + 'range_empty_frac', 0.5::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: range_empty_frac range_length_hist null mismatch, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'arange', + 'inherited', false::boolean, + 'null_frac', 0.28::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- warn: range_empty_frac range_length_hist null mismatch part 2, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'arange', + 'inherited', false::boolean, + 'null_frac', 0.29::real, + 'range_empty_frac', 0.5::real + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- ok: range_empty_frac + range_length_hist +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'arange', + 'inherited', false::boolean, + 'range_empty_frac', 0.5::real, + 'range_length_histogram', '{399,499,Infinity}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- warn: range bounds histogram on scalar, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.31::real, + 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- ok: range_bounds_histogram +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'arange', + 'inherited', false::boolean, + 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- warn: cannot set most_common_elems for range type, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'arange', + 'inherited', false::boolean, + 'null_frac', 0.32::real, + 'most_common_elems', '{3,1}'::text, + 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- warn: scalars can't have mcelem, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.33::real, + 'most_common_elems', '{1,3}'::text, + 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- warn: mcelem / mcelem mismatch, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'tags', + 'inherited', false::boolean, + 'null_frac', 0.34::real, + 'most_common_elems', '{one,two}'::text + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + +-- warn: mcelem / mcelem null mismatch part 2, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'tags', + 'inherited', false::boolean, + 'null_frac', 0.35::real, + 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + +-- ok: mcelem +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'tags', + 'inherited', false::boolean, + 'most_common_elems', '{one,three}'::text, + 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'tags'; + +-- warn: scalars can't have elem_count_histogram, rest ok +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'attname', 'id', + 'inherited', false::boolean, + 'null_frac', 0.36::real, + 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1}'::real[] + ); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- +-- Test the ability to exactly copy data from one table to an identical table, +-- correctly reconstructing the stakind order as well as the staopN and +-- stacollN values. Because oids are not stable across databases, we can only +-- test this when the source and destination are on the same database +-- instance. For that reason, we borrow and adapt a query found in fe_utils +-- and used by pg_dump/pg_upgrade. +-- +INSERT INTO stats_import.test +SELECT 1, 'one', (1, 1.1, 'ONE', '2001-01-01', '{ "xkey": "xval" }')::stats_import.complex_type, int4range(1,4), array['red','green'] +UNION ALL +SELECT 2, 'two', (2, 2.2, 'TWO', '2002-02-02', '[true, 4, "six"]')::stats_import.complex_type, int4range(1,4), array['blue','yellow'] +UNION ALL +SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type, int4range(-1,1), array['"orange"', 'purple', 'cyan'] +UNION ALL +SELECT 4, 'four', NULL, int4range(0,100), NULL; + +CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1)); + +-- Generate statistics on table with data +ANALYZE stats_import.test; + +CREATE TABLE stats_import.test_clone ( LIKE stats_import.test ) + WITH (autovacuum_enabled = false); + +CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1)); + +-- +-- Copy stats from test to test_clone, and is_odd to is_odd_clone +-- +SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.* +FROM pg_catalog.pg_stats AS s +CROSS JOIN LATERAL + pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'stats_import', + 'relname', s.tablename::text || '_clone', + 'attname', s.attname::text, + 'inherited', s.inherited, + 'version', 150000, + 'null_frac', s.null_frac, + 'avg_width', s.avg_width, + 'n_distinct', s.n_distinct, + 'most_common_vals', s.most_common_vals::text, + 'most_common_freqs', s.most_common_freqs, + 'histogram_bounds', s.histogram_bounds::text, + 'correlation', s.correlation, + 'most_common_elems', s.most_common_elems::text, + 'most_common_elem_freqs', s.most_common_elem_freqs, + 'elem_count_histogram', s.elem_count_histogram, + 'range_bounds_histogram', s.range_bounds_histogram::text, + 'range_empty_frac', s.range_empty_frac, + 'range_length_histogram', s.range_length_histogram::text) AS r +WHERE s.schemaname = 'stats_import' +AND s.tablename IN ('test', 'is_odd') +ORDER BY s.tablename, s.attname, s.inherited; + +SELECT c.relname, COUNT(*) AS num_stats +FROM pg_class AS c +JOIN pg_statistic s ON s.starelid = c.oid +WHERE c.relnamespace = 'stats_import'::regnamespace +AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone') +GROUP BY c.relname +ORDER BY c.relname; + +-- check test minus test_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass; + +-- check test_clone minus test +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'test_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.test'::regclass; + +-- check is_odd minus is_odd_clone +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass; + +-- check is_odd_clone minus is_odd +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd_clone'::regclass +EXCEPT +SELECT + a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct, + s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5, + s.staop1, s.staop2, s.staop3, s.staop4, s.staop5, + s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5, + s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5, + s.stavalues1::text AS sv1, s.stavalues2::text AS sv2, + s.stavalues3::text AS sv3, s.stavalues4::text AS sv4, + s.stavalues5::text AS sv5, 'is_odd_clone' AS direction +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'stats_import.is_odd'::regclass; + +-- attribute stats exist before a clear, but not after +SELECT COUNT(*) +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +SELECT pg_catalog.pg_clear_attribute_stats( + schemaname => 'stats_import', + relname => 'test', + attname => 'arange', + inherited => false); + +SELECT COUNT(*) +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'arange'; + +-- temp tables +CREATE TEMP TABLE stats_temp(i int); +SELECT pg_restore_relation_stats( + 'schemaname', 'pg_temp', + 'relname', 'stats_temp', + 'relpages', '-19'::integer, + 'reltuples', 401::real, + 'relallvisible', 5::integer); + +SELECT relname, relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'pg_temp.stats_temp'::regclass +ORDER BY relname; + +SELECT pg_catalog.pg_restore_attribute_stats( + 'schemaname', 'pg_temp', + 'relname', 'stats_temp', + 'attname', 'i', + 'inherited', false::boolean, + 'null_frac', 0.0123::real + ); + +SELECT tablename, null_frac +FROM pg_stats +WHERE schemaname like 'pg_temp%' +AND tablename = 'stats_temp' +AND inherited = false +AND attname = 'i'; +DROP TABLE stats_temp; +DROP SCHEMA stats_import CASCADE; diff --git a/src/test/regress/sql/yb.port.strings.sql b/src/test/regress/sql/yb.port.strings.sql new file mode 100644 index 0000000000000..8c379182cb9d6 --- /dev/null +++ b/src/test/regress/sql/yb.port.strings.sql @@ -0,0 +1,855 @@ +-- +-- STRINGS +-- Test various data entry syntaxes. +-- + +-- SQL string continuation syntax +-- E021-03 character string literals +SELECT 'first line' +' - next line' + ' - third line' + AS "Three lines to one"; + +-- illegal string continuation syntax +SELECT 'first line' +' - next line' /* this comment is not allowed here */ +' - third line' + AS "Illegal comment within continuation"; + +-- Unicode escapes +SET standard_conforming_strings TO on; + +SELECT U&'d\0061t\+000061' AS U&"d\0061t\+000061"; +SELECT U&'d!0061t\+000061' UESCAPE '!' AS U&"d*0061t\+000061" UESCAPE '*'; +SELECT U&'a\\b' AS "a\b"; + +SELECT U&' \' UESCAPE '!' AS "tricky"; +SELECT 'tricky' AS U&"\" UESCAPE '!'; + +SELECT U&'wrong: \061'; +SELECT U&'wrong: \+0061'; +SELECT U&'wrong: +0061' UESCAPE +; +SELECT U&'wrong: +0061' UESCAPE '+'; + +SELECT U&'wrong: \db99'; +SELECT U&'wrong: \db99xy'; +SELECT U&'wrong: \db99\\'; +SELECT U&'wrong: \db99\0061'; +SELECT U&'wrong: \+00db99\+000061'; +SELECT U&'wrong: \+2FFFFF'; + +-- while we're here, check the same cases in E-style literals +SELECT E'd\u0061t\U00000061' AS "data"; +SELECT E'a\\b' AS "a\b"; +SELECT E'wrong: \u061'; +SELECT E'wrong: \U0061'; +SELECT E'wrong: \udb99'; +SELECT E'wrong: \udb99xy'; +SELECT E'wrong: \udb99\\'; +SELECT E'wrong: \udb99\u0061'; +SELECT E'wrong: \U0000db99\U00000061'; +SELECT E'wrong: \U002FFFFF'; + +SET standard_conforming_strings TO off; + +SELECT U&'d\0061t\+000061' AS U&"d\0061t\+000061"; +SELECT U&'d!0061t\+000061' UESCAPE '!' AS U&"d*0061t\+000061" UESCAPE '*'; + +SELECT U&' \' UESCAPE '!' AS "tricky"; +SELECT 'tricky' AS U&"\" UESCAPE '!'; + +SELECT U&'wrong: \061'; +SELECT U&'wrong: \+0061'; +SELECT U&'wrong: +0061' UESCAPE '+'; + +RESET standard_conforming_strings; + +-- bytea +SET bytea_output TO hex; +SELECT E'\\xDeAdBeEf'::bytea; +SELECT E'\\x De Ad Be Ef '::bytea; +SELECT E'\\xDeAdBeE'::bytea; +SELECT E'\\xDeAdBeEx'::bytea; +SELECT E'\\xDe00BeEf'::bytea; +SELECT E'DeAdBeEf'::bytea; +SELECT E'De\\000dBeEf'::bytea; +SELECT E'De\123dBeEf'::bytea; +SELECT E'De\\123dBeEf'::bytea; +SELECT E'De\\678dBeEf'::bytea; + +SET bytea_output TO escape; +SELECT E'\\xDeAdBeEf'::bytea; +SELECT E'\\x De Ad Be Ef '::bytea; +SELECT E'\\xDe00BeEf'::bytea; +SELECT E'DeAdBeEf'::bytea; +SELECT E'De\\000dBeEf'::bytea; +SELECT E'De\\123dBeEf'::bytea; + +-- +-- test conversions between various string types +-- E021-10 implicit casting among the character data types +-- + +SELECT CAST(f1 AS text) AS "text(char)" FROM CHAR_TBL; + +SELECT CAST(f1 AS text) AS "text(varchar)" FROM VARCHAR_TBL; + +SELECT CAST(name 'namefield' AS text) AS "text(name)"; + +-- since this is an explicit cast, it should truncate w/o error: +SELECT CAST(f1 AS char(10)) AS "char(text)" FROM TEXT_TBL; +-- note: implicit-cast case is tested in char.sql + +SELECT CAST(f1 AS char(20)) AS "char(text)" FROM TEXT_TBL; + +SELECT CAST(f1 AS char(10)) AS "char(varchar)" FROM VARCHAR_TBL; + +SELECT CAST(name 'namefield' AS char(10)) AS "char(name)"; + +SELECT CAST(f1 AS varchar) AS "varchar(text)" FROM TEXT_TBL; + +SELECT CAST(f1 AS varchar) AS "varchar(char)" FROM CHAR_TBL; + +SELECT CAST(name 'namefield' AS varchar) AS "varchar(name)"; + +-- +-- test SQL string functions +-- E### and T### are feature reference numbers from SQL99 +-- + +-- E021-09 trim function +SELECT TRIM(BOTH FROM ' bunch o blanks ') = 'bunch o blanks' AS "bunch o blanks"; + +SELECT TRIM(LEADING FROM ' bunch o blanks ') = 'bunch o blanks ' AS "bunch o blanks "; + +SELECT TRIM(TRAILING FROM ' bunch o blanks ') = ' bunch o blanks' AS " bunch o blanks"; + +SELECT TRIM(BOTH 'x' FROM 'xxxxxsome Xsxxxxx') = 'some Xs' AS "some Xs"; + +-- E021-06 substring expression +SELECT SUBSTRING('1234567890' FROM 3) = '34567890' AS "34567890"; + +SELECT SUBSTRING('1234567890' FROM 4 FOR 3) = '456' AS "456"; + +-- test overflow cases +SELECT SUBSTRING('string' FROM 2 FOR 2147483646) AS "tring"; +SELECT SUBSTRING('string' FROM -10 FOR 2147483646) AS "string"; +SELECT SUBSTRING('string' FROM -10 FOR -2147483646) AS "error"; + +-- T581 regular expression substring (with SQL's bizarre regexp syntax) +SELECT SUBSTRING('abcdefg' SIMILAR 'a#"(b_d)#"%' ESCAPE '#') AS "bcd"; +-- obsolete SQL99 syntax +SELECT SUBSTRING('abcdefg' FROM 'a#"(b_d)#"%' FOR '#') AS "bcd"; + +-- No match should return NULL +SELECT SUBSTRING('abcdefg' SIMILAR '#"(b_d)#"%' ESCAPE '#') IS NULL AS "True"; + +-- Null inputs should return NULL +SELECT SUBSTRING('abcdefg' SIMILAR '%' ESCAPE NULL) IS NULL AS "True"; +SELECT SUBSTRING(NULL SIMILAR '%' ESCAPE '#') IS NULL AS "True"; +SELECT SUBSTRING('abcdefg' SIMILAR NULL ESCAPE '#') IS NULL AS "True"; + +-- The first and last parts should act non-greedy +SELECT SUBSTRING('abcdefg' SIMILAR 'a#"%#"g' ESCAPE '#') AS "bcdef"; +SELECT SUBSTRING('abcdefg' SIMILAR 'a*#"%#"g*' ESCAPE '#') AS "abcdefg"; + +-- Vertical bar in any part affects only that part +SELECT SUBSTRING('abcdefg' SIMILAR 'a|b#"%#"g' ESCAPE '#') AS "bcdef"; +SELECT SUBSTRING('abcdefg' SIMILAR 'a#"%#"x|g' ESCAPE '#') AS "bcdef"; +SELECT SUBSTRING('abcdefg' SIMILAR 'a#"%|ab#"g' ESCAPE '#') AS "bcdef"; + +-- Can't have more than two part separators +SELECT SUBSTRING('abcdefg' SIMILAR 'a*#"%#"g*#"x' ESCAPE '#') AS "error"; + +-- Postgres extension: with 0 or 1 separator, assume parts 1 and 3 are empty +SELECT SUBSTRING('abcdefg' SIMILAR 'a#"%g' ESCAPE '#') AS "bcdefg"; +SELECT SUBSTRING('abcdefg' SIMILAR 'a%g' ESCAPE '#') AS "abcdefg"; + +-- substring() with just two arguments is not allowed by SQL spec; +-- we accept it, but we interpret the pattern as a POSIX regexp not SQL +SELECT SUBSTRING('abcdefg' FROM 'c.e') AS "cde"; + +-- With a parenthesized subexpression, return only what matches the subexpr +SELECT SUBSTRING('abcdefg' FROM 'b(.*)f') AS "cde"; +-- Check case where we have a match, but not a subexpression match +SELECT SUBSTRING('foo' FROM 'foo(bar)?') IS NULL AS t; + +-- Check behavior of SIMILAR TO, which uses largely the same regexp variant +SELECT 'abcdefg' SIMILAR TO '_bcd%' AS true; +SELECT 'abcdefg' SIMILAR TO 'bcd%' AS false; +SELECT 'abcdefg' SIMILAR TO '_bcd#%' ESCAPE '#' AS false; +SELECT 'abcd%' SIMILAR TO '_bcd#%' ESCAPE '#' AS true; +-- Postgres uses '\' as the default escape character, which is not per spec +SELECT 'abcdefg' SIMILAR TO '_bcd\%' AS false; +-- and an empty string to mean "no escape", which is also not per spec +SELECT 'abcd\efg' SIMILAR TO '_bcd\%' ESCAPE '' AS true; +-- these behaviors are per spec, though: +SELECT 'abcdefg' SIMILAR TO '_bcd%' ESCAPE NULL AS null; +SELECT 'abcdefg' SIMILAR TO '_bcd#%' ESCAPE '##' AS error; + +-- Test backslash escapes in regexp_replace's replacement string +SELECT regexp_replace('1112223333', E'(\\d{3})(\\d{3})(\\d{4})', E'(\\1) \\2-\\3'); +SELECT regexp_replace('foobarrbazz', E'(.)\\1', E'X\\&Y', 'g'); +SELECT regexp_replace('foobarrbazz', E'(.)\\1', E'X\\\\Y', 'g'); +-- not an error, though perhaps it should be: +SELECT regexp_replace('foobarrbazz', E'(.)\\1', E'X\\Y\\1Z\\'); + +SELECT regexp_replace('AAA BBB CCC ', E'\\s+', ' ', 'g'); +SELECT regexp_replace('AAA', '^|$', 'Z', 'g'); +SELECT regexp_replace('AAA aaa', 'A+', 'Z', 'gi'); +-- invalid regexp option +SELECT regexp_replace('AAA aaa', 'A+', 'Z', 'z'); + +-- extended regexp_replace tests +SELECT regexp_replace('A PostgreSQL function', 'A|e|i|o|u', 'X', 1); +SELECT regexp_replace('A PostgreSQL function', 'A|e|i|o|u', 'X', 1, 2); +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 0, 'i'); +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 1, 'i'); +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 2, 'i'); +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 3, 'i'); +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 9, 'i'); +SELECT regexp_replace('A PostgreSQL function', 'A|e|i|o|u', 'X', 7, 0, 'i'); +-- 'g' flag should be ignored when N is specified +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, 1, 'g'); +-- errors +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', -1, 0, 'i'); +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', 1, -1, 'i'); +-- erroneous invocation of non-extended form +SELECT regexp_replace('A PostgreSQL function', 'a|e|i|o|u', 'X', '1'); + +-- regexp_count tests +SELECT regexp_count('123123123123123', '(12)3'); +SELECT regexp_count('123123123123', '123', 1); +SELECT regexp_count('123123123123', '123', 3); +SELECT regexp_count('123123123123', '123', 33); +SELECT regexp_count('ABCABCABCABC', 'Abc', 1, ''); +SELECT regexp_count('ABCABCABCABC', 'Abc', 1, 'i'); +-- errors +SELECT regexp_count('123123123123', '123', 0); +SELECT regexp_count('123123123123', '123', -3); + +-- regexp_like tests +SELECT regexp_like('Steven', '^Ste(v|ph)en$'); +SELECT regexp_like('a'||CHR(10)||'d', 'a.d', 'n'); +SELECT regexp_like('a'||CHR(10)||'d', 'a.d', 's'); +SELECT regexp_like('abc', ' a . c ', 'x'); +SELECT regexp_like('abc', 'a.c', 'g'); -- error + +-- regexp_instr tests +SELECT regexp_instr('abcdefghi', 'd.f'); +SELECT regexp_instr('abcdefghi', 'd.q'); +SELECT regexp_instr('abcabcabc', 'a.c'); +SELECT regexp_instr('abcabcabc', 'a.c', 2); +SELECT regexp_instr('abcabcabc', 'a.c', 1, 3); +SELECT regexp_instr('abcabcabc', 'a.c', 1, 4); +SELECT regexp_instr('abcabcabc', 'A.C', 1, 2, 0, 'i'); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 0); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 1); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 2); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 3); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 4); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 5); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 0); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 1); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 2); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 3); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 4); +SELECT regexp_instr('1234567890', '(123)(4(56)(78))', 1, 1, 1, 'i', 5); +-- Check case where we have a match, but not a subexpression match +SELECT regexp_instr('foo', 'foo(bar)?', 1, 1, 0, '', 1); +-- errors +SELECT regexp_instr('abcabcabc', 'a.c', 0, 1); +SELECT regexp_instr('abcabcabc', 'a.c', 1, 0); +SELECT regexp_instr('abcabcabc', 'a.c', 1, 1, -1); +SELECT regexp_instr('abcabcabc', 'a.c', 1, 1, 2); +SELECT regexp_instr('abcabcabc', 'a.c', 1, 1, 0, 'g'); +SELECT regexp_instr('abcabcabc', 'a.c', 1, 1, 0, '', -1); + +-- regexp_substr tests +SELECT regexp_substr('abcdefghi', 'd.f'); +SELECT regexp_substr('abcdefghi', 'd.q') IS NULL AS t; +SELECT regexp_substr('abcabcabc', 'a.c'); +SELECT regexp_substr('abcabcabc', 'a.c', 2); +SELECT regexp_substr('abcabcabc', 'a.c', 1, 3); +SELECT regexp_substr('abcabcabc', 'a.c', 1, 4) IS NULL AS t; +SELECT regexp_substr('abcabcabc', 'A.C', 1, 2, 'i'); +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 0); +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 1); +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 2); +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 3); +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 4); +SELECT regexp_substr('1234567890', '(123)(4(56)(78))', 1, 1, 'i', 5) IS NULL AS t; +-- Check case where we have a match, but not a subexpression match +SELECT regexp_substr('foo', 'foo(bar)?', 1, 1, '', 1) IS NULL AS t; +-- errors +SELECT regexp_substr('abcabcabc', 'a.c', 0, 1); +SELECT regexp_substr('abcabcabc', 'a.c', 1, 0); +SELECT regexp_substr('abcabcabc', 'a.c', 1, 1, 'g'); +SELECT regexp_substr('abcabcabc', 'a.c', 1, 1, '', -1); + +-- set so we can tell NULL from empty string +\pset null '\\N' + +-- return all matches from regexp +SELECT regexp_matches('foobarbequebaz', $re$(bar)(beque)$re$); + +-- test case insensitive +SELECT regexp_matches('foObARbEqUEbAz', $re$(bar)(beque)$re$, 'i'); + +-- global option - more than one match +SELECT regexp_matches('foobarbequebazilbarfbonk', $re$(b[^b]+)(b[^b]+)$re$, 'g'); + +-- empty capture group (matched empty string) +SELECT regexp_matches('foobarbequebaz', $re$(bar)(.*)(beque)$re$); +-- no match +SELECT regexp_matches('foobarbequebaz', $re$(bar)(.+)(beque)$re$); +-- optional capture group did not match, null entry in array +SELECT regexp_matches('foobarbequebaz', $re$(bar)(.+)?(beque)$re$); + +-- no capture groups +SELECT regexp_matches('foobarbequebaz', $re$barbeque$re$); + +-- start/end-of-line matches are of zero length +SELECT regexp_matches('foo' || chr(10) || 'bar' || chr(10) || 'bequq' || chr(10) || 'baz', '^', 'mg'); +SELECT regexp_matches('foo' || chr(10) || 'bar' || chr(10) || 'bequq' || chr(10) || 'baz', '$', 'mg'); +SELECT regexp_matches('1' || chr(10) || '2' || chr(10) || '3' || chr(10) || '4' || chr(10), '^.?', 'mg'); +SELECT regexp_matches(chr(10) || '1' || chr(10) || '2' || chr(10) || '3' || chr(10) || '4' || chr(10), '.?$', 'mg'); +SELECT regexp_matches(chr(10) || '1' || chr(10) || '2' || chr(10) || '3' || chr(10) || '4', '.?$', 'mg'); + +-- give me errors +SELECT regexp_matches('foobarbequebaz', $re$(bar)(beque)$re$, 'gz'); +SELECT regexp_matches('foobarbequebaz', $re$(barbeque$re$); +SELECT regexp_matches('foobarbequebaz', $re$(bar)(beque){2,1}$re$); + +-- split string on regexp +SELECT foo, length(foo) FROM regexp_split_to_table('the quick brown fox jumps over the lazy dog', $re$\s+$re$) AS foo; +SELECT regexp_split_to_array('the quick brown fox jumps over the lazy dog', $re$\s+$re$); + +SELECT foo, length(foo) FROM regexp_split_to_table('the quick brown fox jumps over the lazy dog', $re$\s*$re$) AS foo; +SELECT regexp_split_to_array('the quick brown fox jumps over the lazy dog', $re$\s*$re$); +SELECT foo, length(foo) FROM regexp_split_to_table('the quick brown fox jumps over the lazy dog', '') AS foo; +SELECT regexp_split_to_array('the quick brown fox jumps over the lazy dog', ''); +-- case insensitive +SELECT foo, length(foo) FROM regexp_split_to_table('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'i') AS foo; +SELECT regexp_split_to_array('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'i'); +-- no match of pattern +SELECT foo, length(foo) FROM regexp_split_to_table('the quick brown fox jumps over the lazy dog', 'nomatch') AS foo; +SELECT regexp_split_to_array('the quick brown fox jumps over the lazy dog', 'nomatch'); +-- some corner cases +SELECT regexp_split_to_array('123456','1'); +SELECT regexp_split_to_array('123456','6'); +SELECT regexp_split_to_array('123456','.'); +SELECT regexp_split_to_array('123456',''); +SELECT regexp_split_to_array('123456','(?:)'); +SELECT regexp_split_to_array('1',''); +-- errors +SELECT foo, length(foo) FROM regexp_split_to_table('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'zippy') AS foo; +SELECT regexp_split_to_array('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'iz'); +-- global option meaningless for regexp_split +SELECT foo, length(foo) FROM regexp_split_to_table('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'g') AS foo; +SELECT regexp_split_to_array('thE QUick bROWn FOx jUMPs ovEr The lazy dOG', 'e', 'g'); + +-- change NULL-display back +\pset null '' + +-- E021-11 position expression +SELECT POSITION('4' IN '1234567890') = '4' AS "4"; + +SELECT POSITION('5' IN '1234567890') = '5' AS "5"; + +-- T312 character overlay function +SELECT OVERLAY('abcdef' PLACING '45' FROM 4) AS "abc45f"; + +SELECT OVERLAY('yabadoo' PLACING 'daba' FROM 5) AS "yabadaba"; + +SELECT OVERLAY('yabadoo' PLACING 'daba' FROM 5 FOR 0) AS "yabadabadoo"; + +SELECT OVERLAY('babosa' PLACING 'ubb' FROM 2 FOR 4) AS "bubba"; + +-- +-- test LIKE +-- Be sure to form every test as a LIKE/NOT LIKE pair. +-- + +-- simplest examples +-- E061-04 like predicate +SELECT 'hawkeye' LIKE 'h%' AS "true"; +SELECT 'hawkeye' NOT LIKE 'h%' AS "false"; + +SELECT 'hawkeye' LIKE 'H%' AS "false"; +SELECT 'hawkeye' NOT LIKE 'H%' AS "true"; + +SELECT 'hawkeye' LIKE 'indio%' AS "false"; +SELECT 'hawkeye' NOT LIKE 'indio%' AS "true"; + +SELECT 'hawkeye' LIKE 'h%eye' AS "true"; +SELECT 'hawkeye' NOT LIKE 'h%eye' AS "false"; + +SELECT 'indio' LIKE '_ndio' AS "true"; +SELECT 'indio' NOT LIKE '_ndio' AS "false"; + +SELECT 'indio' LIKE 'in__o' AS "true"; +SELECT 'indio' NOT LIKE 'in__o' AS "false"; + +SELECT 'indio' LIKE 'in_o' AS "false"; +SELECT 'indio' NOT LIKE 'in_o' AS "true"; + +SELECT 'abc'::name LIKE '_b_' AS "true"; +SELECT 'abc'::name NOT LIKE '_b_' AS "false"; + +SELECT 'abc'::bytea LIKE '_b_'::bytea AS "true"; +SELECT 'abc'::bytea NOT LIKE '_b_'::bytea AS "false"; + +-- unused escape character +SELECT 'hawkeye' LIKE 'h%' ESCAPE '#' AS "true"; +SELECT 'hawkeye' NOT LIKE 'h%' ESCAPE '#' AS "false"; + +SELECT 'indio' LIKE 'ind_o' ESCAPE '$' AS "true"; +SELECT 'indio' NOT LIKE 'ind_o' ESCAPE '$' AS "false"; + +-- escape character +-- E061-05 like predicate with escape clause +SELECT 'h%' LIKE 'h#%' ESCAPE '#' AS "true"; +SELECT 'h%' NOT LIKE 'h#%' ESCAPE '#' AS "false"; + +SELECT 'h%wkeye' LIKE 'h#%' ESCAPE '#' AS "false"; +SELECT 'h%wkeye' NOT LIKE 'h#%' ESCAPE '#' AS "true"; + +SELECT 'h%wkeye' LIKE 'h#%%' ESCAPE '#' AS "true"; +SELECT 'h%wkeye' NOT LIKE 'h#%%' ESCAPE '#' AS "false"; + +SELECT 'h%awkeye' LIKE 'h#%a%k%e' ESCAPE '#' AS "true"; +SELECT 'h%awkeye' NOT LIKE 'h#%a%k%e' ESCAPE '#' AS "false"; + +SELECT 'indio' LIKE '_ndio' ESCAPE '$' AS "true"; +SELECT 'indio' NOT LIKE '_ndio' ESCAPE '$' AS "false"; + +SELECT 'i_dio' LIKE 'i$_d_o' ESCAPE '$' AS "true"; +SELECT 'i_dio' NOT LIKE 'i$_d_o' ESCAPE '$' AS "false"; + +SELECT 'i_dio' LIKE 'i$_nd_o' ESCAPE '$' AS "false"; +SELECT 'i_dio' NOT LIKE 'i$_nd_o' ESCAPE '$' AS "true"; + +SELECT 'i_dio' LIKE 'i$_d%o' ESCAPE '$' AS "true"; +SELECT 'i_dio' NOT LIKE 'i$_d%o' ESCAPE '$' AS "false"; + +SELECT 'a_c'::bytea LIKE 'a$__'::bytea ESCAPE '$'::bytea AS "true"; +SELECT 'a_c'::bytea NOT LIKE 'a$__'::bytea ESCAPE '$'::bytea AS "false"; + +-- escape character same as pattern character +SELECT 'maca' LIKE 'm%aca' ESCAPE '%' AS "true"; +SELECT 'maca' NOT LIKE 'm%aca' ESCAPE '%' AS "false"; + +SELECT 'ma%a' LIKE 'm%a%%a' ESCAPE '%' AS "true"; +SELECT 'ma%a' NOT LIKE 'm%a%%a' ESCAPE '%' AS "false"; + +SELECT 'bear' LIKE 'b_ear' ESCAPE '_' AS "true"; +SELECT 'bear' NOT LIKE 'b_ear' ESCAPE '_' AS "false"; + +SELECT 'be_r' LIKE 'b_e__r' ESCAPE '_' AS "true"; +SELECT 'be_r' NOT LIKE 'b_e__r' ESCAPE '_' AS "false"; + +SELECT 'be_r' LIKE '__e__r' ESCAPE '_' AS "false"; +SELECT 'be_r' NOT LIKE '__e__r' ESCAPE '_' AS "true"; + + +-- +-- test ILIKE (case-insensitive LIKE) +-- Be sure to form every test as an ILIKE/NOT ILIKE pair. +-- + +SELECT 'hawkeye' ILIKE 'h%' AS "true"; +SELECT 'hawkeye' NOT ILIKE 'h%' AS "false"; + +SELECT 'hawkeye' ILIKE 'H%' AS "true"; +SELECT 'hawkeye' NOT ILIKE 'H%' AS "false"; + +SELECT 'hawkeye' ILIKE 'H%Eye' AS "true"; +SELECT 'hawkeye' NOT ILIKE 'H%Eye' AS "false"; + +SELECT 'Hawkeye' ILIKE 'h%' AS "true"; +SELECT 'Hawkeye' NOT ILIKE 'h%' AS "false"; + +SELECT 'ABC'::name ILIKE '_b_' AS "true"; +SELECT 'ABC'::name NOT ILIKE '_b_' AS "false"; + +-- +-- test %/_ combination cases, cf bugs #4821 and #5478 +-- + +SELECT 'foo' LIKE '_%' as t, 'f' LIKE '_%' as t, '' LIKE '_%' as f; +SELECT 'foo' LIKE '%_' as t, 'f' LIKE '%_' as t, '' LIKE '%_' as f; + +SELECT 'foo' LIKE '__%' as t, 'foo' LIKE '___%' as t, 'foo' LIKE '____%' as f; +SELECT 'foo' LIKE '%__' as t, 'foo' LIKE '%___' as t, 'foo' LIKE '%____' as f; + +SELECT 'jack' LIKE '%____%' AS t; + + +-- +-- basic tests of LIKE with indexes +-- + +CREATE TABLE texttest (a text PRIMARY KEY, b int); +SELECT * FROM texttest WHERE a LIKE '%1%'; + +CREATE TABLE byteatest (a bytea PRIMARY KEY, b int); +SELECT * FROM byteatest WHERE a LIKE '%1%'; + +DROP TABLE texttest, byteatest; + + +-- +-- test implicit type conversion +-- + +-- E021-07 character concatenation +SELECT 'unknown' || ' and unknown' AS "Concat unknown types"; + +SELECT text 'text' || ' and unknown' AS "Concat text to unknown type"; + +SELECT char(20) 'characters' || ' and text' AS "Concat char to unknown type"; + +SELECT text 'text' || char(20) ' and characters' AS "Concat text to char"; + +SELECT text 'text' || varchar ' and varchar' AS "Concat text to varchar"; + +-- +-- test substr with toasted text values +-- +CREATE TABLE toasttest(f1 text); + +insert into toasttest values(repeat('1234567890',10000)); +insert into toasttest values(repeat('1234567890',10000)); + +-- +-- Ensure that some values are uncompressed, to test the faster substring +-- operation used in that case +-- +alter table toasttest alter column f1 set storage external; +insert into toasttest values(repeat('1234567890',10000)); +insert into toasttest values(repeat('1234567890',10000)); + +-- If the starting position is zero or less, then return from the start of the string +-- adjusting the length to be consistent with the "negative start" per SQL. +SELECT substr(f1, -1, 5) from toasttest; + +-- If the length is less than zero, an ERROR is thrown. +SELECT substr(f1, 5, -1) from toasttest; + +-- If no third argument (length) is provided, the length to the end of the +-- string is assumed. +SELECT substr(f1, 99995) from toasttest; + +-- If start plus length is > string length, the result is truncated to +-- string length +SELECT substr(f1, 99995, 10) from toasttest; + +TRUNCATE TABLE toasttest; +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +-- expect >0 blocks +SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty + FROM pg_class where relname = 'toasttest'; + +TRUNCATE TABLE toasttest; +ALTER TABLE toasttest set (toast_tuple_target = 4080); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +INSERT INTO toasttest values (repeat('1234567890',300)); +-- expect 0 blocks +SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty + FROM pg_class where relname = 'toasttest'; + +DROP TABLE toasttest; + +-- +-- test substr with toasted bytea values +-- +CREATE TABLE toasttest(f1 bytea); + +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); + +-- +-- Ensure that some values are uncompressed, to test the faster substring +-- operation used in that case +-- +alter table toasttest alter column f1 set storage external; +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest values(decode(repeat('1234567890',10000),'escape')); + +-- If the starting position is zero or less, then return from the start of the string +-- adjusting the length to be consistent with the "negative start" per SQL. +SELECT substr(f1, -1, 5) from toasttest; + +-- If the length is less than zero, an ERROR is thrown. +SELECT substr(f1, 5, -1) from toasttest; + +-- If no third argument (length) is provided, the length to the end of the +-- string is assumed. +SELECT substr(f1, 99995) from toasttest; + +-- If start plus length is > string length, the result is truncated to +-- string length +SELECT substr(f1, 99995, 10) from toasttest; + +DROP TABLE toasttest; + +-- test internally compressing datums + +-- this tests compressing a datum to a very small size which exercises a +-- corner case in packed-varlena handling: even though small, the compressed +-- datum must be given a 4-byte header because there are no bits to indicate +-- compression in a 1-byte header + +CREATE TABLE toasttest (c char(4096)); +INSERT INTO toasttest VALUES('x'); +SELECT length(c), c::text FROM toasttest; +SELECT c FROM toasttest; +DROP TABLE toasttest; + +-- +-- test length +-- + +SELECT length('abcdef') AS "length_6"; + +-- +-- test strpos +-- + +SELECT strpos('abcdef', 'cd') AS "pos_3"; + +SELECT strpos('abcdef', 'xy') AS "pos_0"; + +SELECT strpos('abcdef', '') AS "pos_1"; + +SELECT strpos('', 'xy') AS "pos_0"; + +SELECT strpos('', '') AS "pos_1"; + +-- +-- test replace +-- +SELECT replace('abcdef', 'de', '45') AS "abc45f"; + +SELECT replace('yabadabadoo', 'ba', '123') AS "ya123da123doo"; + +SELECT replace('yabadoo', 'bad', '') AS "yaoo"; + +-- +-- test split_part +-- +select split_part('','@',1) AS "empty string"; + +select split_part('','@',-1) AS "empty string"; + +select split_part('joeuser@mydatabase','',1) AS "joeuser@mydatabase"; + +select split_part('joeuser@mydatabase','',2) AS "empty string"; + +select split_part('joeuser@mydatabase','',-1) AS "joeuser@mydatabase"; + +select split_part('joeuser@mydatabase','',-2) AS "empty string"; + +select split_part('joeuser@mydatabase','@',0) AS "an error"; + +select split_part('joeuser@mydatabase','@@',1) AS "joeuser@mydatabase"; + +select split_part('joeuser@mydatabase','@@',2) AS "empty string"; + +select split_part('joeuser@mydatabase','@',1) AS "joeuser"; + +select split_part('joeuser@mydatabase','@',2) AS "mydatabase"; + +select split_part('joeuser@mydatabase','@',3) AS "empty string"; + +select split_part('@joeuser@mydatabase@','@',2) AS "joeuser"; + +select split_part('joeuser@mydatabase','@',-1) AS "mydatabase"; + +select split_part('joeuser@mydatabase','@',-2) AS "joeuser"; + +select split_part('joeuser@mydatabase','@',-3) AS "empty string"; + +select split_part('@joeuser@mydatabase@','@',-2) AS "mydatabase"; + +-- +-- test to_hex +-- +select to_hex(256*256*256 - 1) AS "ffffff"; + +select to_hex(256::bigint*256::bigint*256::bigint*256::bigint - 1) AS "ffffffff"; + +-- +-- MD5 test suite - from IETF RFC 1321 +-- (see: ftp://ftp.rfc-editor.org/in-notes/rfc1321.txt) +-- +select md5('') = 'd41d8cd98f00b204e9800998ecf8427e' AS "TRUE"; + +select md5('a') = '0cc175b9c0f1b6a831c399e269772661' AS "TRUE"; + +select md5('abc') = '900150983cd24fb0d6963f7d28e17f72' AS "TRUE"; + +select md5('message digest') = 'f96b697d7cb7938d525a2f31aaf161d0' AS "TRUE"; + +select md5('abcdefghijklmnopqrstuvwxyz') = 'c3fcd3d76192e4007dfb496cca67e13b' AS "TRUE"; + +select md5('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') = 'd174ab98d277d9f5a5611c2c9f419d9f' AS "TRUE"; + +select md5('12345678901234567890123456789012345678901234567890123456789012345678901234567890') = '57edf4a22be3c955ac49da2e2107b67a' AS "TRUE"; + +select md5(''::bytea) = 'd41d8cd98f00b204e9800998ecf8427e' AS "TRUE"; + +select md5('a'::bytea) = '0cc175b9c0f1b6a831c399e269772661' AS "TRUE"; + +select md5('abc'::bytea) = '900150983cd24fb0d6963f7d28e17f72' AS "TRUE"; + +select md5('message digest'::bytea) = 'f96b697d7cb7938d525a2f31aaf161d0' AS "TRUE"; + +select md5('abcdefghijklmnopqrstuvwxyz'::bytea) = 'c3fcd3d76192e4007dfb496cca67e13b' AS "TRUE"; + +select md5('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'::bytea) = 'd174ab98d277d9f5a5611c2c9f419d9f' AS "TRUE"; + +select md5('12345678901234567890123456789012345678901234567890123456789012345678901234567890'::bytea) = '57edf4a22be3c955ac49da2e2107b67a' AS "TRUE"; + +-- +-- SHA-2 +-- +SET bytea_output TO hex; + +SELECT sha224(''); +SELECT sha224('The quick brown fox jumps over the lazy dog.'); + +SELECT sha256(''); +SELECT sha256('The quick brown fox jumps over the lazy dog.'); + +SELECT sha384(''); +SELECT sha384('The quick brown fox jumps over the lazy dog.'); + +SELECT sha512(''); +SELECT sha512('The quick brown fox jumps over the lazy dog.'); + +-- +-- encode/decode +-- +SELECT encode('\x1234567890abcdef00', 'hex'); +SELECT decode('1234567890abcdef00', 'hex'); +SELECT encode(('\x' || repeat('1234567890abcdef0001', 7))::bytea, 'base64'); +SELECT decode(encode(('\x' || repeat('1234567890abcdef0001', 7))::bytea, + 'base64'), 'base64'); +SELECT encode('\x1234567890abcdef00', 'escape'); +SELECT decode(encode('\x1234567890abcdef00', 'escape'), 'escape'); + +-- +-- get_bit/set_bit etc +-- +SELECT get_bit('\x1234567890abcdef00'::bytea, 43); +SELECT get_bit('\x1234567890abcdef00'::bytea, 99); -- error +SELECT set_bit('\x1234567890abcdef00'::bytea, 43, 0); +SELECT set_bit('\x1234567890abcdef00'::bytea, 99, 0); -- error +SELECT get_byte('\x1234567890abcdef00'::bytea, 3); +SELECT get_byte('\x1234567890abcdef00'::bytea, 99); -- error +SELECT set_byte('\x1234567890abcdef00'::bytea, 7, 11); +SELECT set_byte('\x1234567890abcdef00'::bytea, 99, 11); -- error + +-- +-- test behavior of escape_string_warning and standard_conforming_strings options +-- +set escape_string_warning = off; +set standard_conforming_strings = off; + +show escape_string_warning; +show standard_conforming_strings; + +set escape_string_warning = on; +set standard_conforming_strings = on; + +show escape_string_warning; +show standard_conforming_strings; + +select 'a\bcd' as f1, 'a\b''cd' as f2, 'a\b''''cd' as f3, 'abcd\' as f4, 'ab\''cd' as f5, '\\' as f6; + +set standard_conforming_strings = off; + +select 'a\\bcd' as f1, 'a\\b\'cd' as f2, 'a\\b\'''cd' as f3, 'abcd\\' as f4, 'ab\\\'cd' as f5, '\\\\' as f6; + +set escape_string_warning = off; +set standard_conforming_strings = on; + +select 'a\bcd' as f1, 'a\b''cd' as f2, 'a\b''''cd' as f3, 'abcd\' as f4, 'ab\''cd' as f5, '\\' as f6; + +set standard_conforming_strings = off; + +select 'a\\bcd' as f1, 'a\\b\'cd' as f2, 'a\\b\'''cd' as f3, 'abcd\\' as f4, 'ab\\\'cd' as f5, '\\\\' as f6; + +reset standard_conforming_strings; + + +-- +-- Additional string functions +-- +SET bytea_output TO escape; + +SELECT initcap('hi THOMAS'); + +SELECT lpad('hi', 5, 'xy'); +SELECT lpad('hi', 5); +SELECT lpad('hi', -5, 'xy'); +SELECT lpad('hello', 2); +SELECT lpad('hi', 5, ''); + +SELECT rpad('hi', 5, 'xy'); +SELECT rpad('hi', 5); +SELECT rpad('hi', -5, 'xy'); +SELECT rpad('hello', 2); +SELECT rpad('hi', 5, ''); + +SELECT ltrim('zzzytrim', 'xyz'); + +SELECT translate('', '14', 'ax'); +SELECT translate('12345', '14', 'ax'); + +SELECT ascii('x'); +SELECT ascii(''); + +SELECT chr(65); +SELECT chr(0); + +SELECT repeat('Pg', 4); +SELECT repeat('Pg', -4); + +SELECT SUBSTRING('1234567890'::bytea FROM 3) "34567890"; +SELECT SUBSTRING('1234567890'::bytea FROM 4 FOR 3) AS "456"; +SELECT SUBSTRING('string'::bytea FROM 2 FOR 2147483646) AS "tring"; +SELECT SUBSTRING('string'::bytea FROM -10 FOR 2147483646) AS "string"; +SELECT SUBSTRING('string'::bytea FROM -10 FOR -2147483646) AS "error"; + +SELECT trim(E'\\000'::bytea from E'\\000Tom\\000'::bytea); +SELECT trim(leading E'\\000'::bytea from E'\\000Tom\\000'::bytea); +SELECT trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea); +SELECT btrim(E'\\000trim\\000'::bytea, E'\\000'::bytea); +SELECT btrim(''::bytea, E'\\000'::bytea); +SELECT btrim(E'\\000trim\\000'::bytea, ''::bytea); +SELECT encode(overlay(E'Th\\000omas'::bytea placing E'Th\\001omas'::bytea from 2),'escape'); +SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 8),'escape'); +SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 5 for 3),'escape'); + +SELECT bit_count('\x1234567890'::bytea); + +SELECT unistr('\0064at\+0000610'); +SELECT unistr('d\u0061t\U000000610'); +SELECT unistr('a\\b'); +-- errors: +SELECT unistr('wrong: \db99'); +SELECT unistr('wrong: \db99\0061'); +SELECT unistr('wrong: \+00db99\+000061'); +SELECT unistr('wrong: \+2FFFFF'); +SELECT unistr('wrong: \udb99\u0061'); +SELECT unistr('wrong: \U0000db99\U00000061'); +SELECT unistr('wrong: \U002FFFFF'); +SELECT unistr('wrong: \xyz'); diff --git a/src/test/regress/sql/yb.port.subselect.sql b/src/test/regress/sql/yb.port.subselect.sql new file mode 100644 index 0000000000000..1dd8a1276ddab --- /dev/null +++ b/src/test/regress/sql/yb.port.subselect.sql @@ -0,0 +1,17 @@ +-- +-- SUBSELECT +-- + +-- another variant of that (bug #16213) +explain (verbose, costs off) +select * from +(values + (3 not in (select * from (values (1), (2)) ss1)), + (false) +) ss; + +select * from +(values + (3 not in (select * from (values (1), (2)) ss1)), + (false) +) ss; diff --git a/src/test/regress/sql/yb.port.test_setup.sql b/src/test/regress/sql/yb.port.test_setup.sql new file mode 100644 index 0000000000000..583e8cc6e1e34 --- /dev/null +++ b/src/test/regress/sql/yb.port.test_setup.sql @@ -0,0 +1,261 @@ +-- +-- TEST_SETUP --- prepare environment expected by regression test scripts +-- + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +-- +-- Postgres formerly made the public schema read/write by default, +-- and most of the core regression tests still expect that. +-- +GRANT ALL ON SCHEMA public TO public; + +-- +-- These tables have traditionally been referenced by many tests, +-- so create and populate them. Insert only non-error values here. +-- (Some subsequent tests try to insert erroneous values. That's okay +-- because the table won't actually change. Do not change the contents +-- of these tables in later tests, as it may affect other tests.) +-- + +CREATE TABLE CHAR_TBL(f1 char(4)); + +INSERT INTO CHAR_TBL (f1) VALUES + ('a'), + ('ab'), + ('abcd'), + ('abcd '); +VACUUM CHAR_TBL; + +CREATE TABLE FLOAT8_TBL(f1 float8); + +INSERT INTO FLOAT8_TBL(f1) VALUES + ('0.0'), + ('-34.84'), + ('-1004.30'), + ('-1.2345678901234e+200'), + ('-1.2345678901234e-200'); +VACUUM FLOAT8_TBL; + +CREATE TABLE INT2_TBL(f1 int2); + +INSERT INTO INT2_TBL(f1) VALUES + ('0 '), + (' 1234 '), + (' -1234'), + ('32767'), -- largest and smallest values + ('-32767'); +VACUUM INT2_TBL; + +CREATE TABLE INT4_TBL(f1 int4); + +INSERT INTO INT4_TBL(f1) VALUES + (' 0 '), + ('123456 '), + (' -123456'), + ('2147483647'), -- largest and smallest values + ('-2147483647'); +VACUUM INT4_TBL; + +CREATE TABLE INT8_TBL(q1 int8, q2 int8); + +INSERT INTO INT8_TBL VALUES + (' 123 ',' 456'), + ('123 ','4567890123456789'), + ('4567890123456789','123'), + (+4567890123456789,'4567890123456789'), + ('+4567890123456789','-4567890123456789'); +VACUUM INT8_TBL; + +CREATE TABLE POINT_TBL(f1 point); + +INSERT INTO POINT_TBL(f1) VALUES + ('(0.0,0.0)'), + ('(-10.0,0.0)'), + ('(-3.0,4.0)'), + ('(5.1, 34.5)'), + ('(-5.0,-12.0)'), + ('(1e-300,-1e-300)'), -- To underflow + ('(1e+300,Inf)'), -- To overflow + ('(Inf,1e+300)'), -- Transposed + (' ( Nan , NaN ) '), + ('10.0,10.0'); +-- We intentionally don't vacuum point_tbl here; geometry depends on that + +-- YB note: add primary key for sorting +CREATE TABLE TEXT_TBL (f1 text PRIMARY KEY); + +INSERT INTO TEXT_TBL VALUES + ('doh!'), + ('hi de ho neighbor'); +VACUUM TEXT_TBL; + +CREATE TABLE VARCHAR_TBL(f1 varchar(4)); + +INSERT INTO VARCHAR_TBL (f1) VALUES + ('a'), + ('ab'), + ('abcd'), + ('abcd '); +VACUUM VARCHAR_TBL; + +CREATE TABLE onek ( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +); + +\set filename :abs_srcdir '/data/onek.data' +COPY onek FROM :'filename'; +VACUUM ANALYZE onek; + +CREATE TABLE onek2 AS SELECT * FROM onek; +VACUUM ANALYZE onek2; + +CREATE TABLE tenk1 ( + unique1 int4, + unique2 int4, + two int4, + four int4, + ten int4, + twenty int4, + hundred int4, + thousand int4, + twothousand int4, + fivethous int4, + tenthous int4, + odd int4, + even int4, + stringu1 name, + stringu2 name, + string4 name +); + +\set filename :abs_srcdir '/data/tenk.data' +COPY tenk1 FROM :'filename'; +VACUUM ANALYZE tenk1; + +CREATE TABLE tenk2 AS SELECT * FROM tenk1; +VACUUM ANALYZE tenk2; + +CREATE TABLE person ( + name text, + age int4, + location point +); + +\set filename :abs_srcdir '/data/person.data' +COPY person FROM :'filename'; +VACUUM ANALYZE person; + +CREATE TABLE emp ( + salary int4, + manager name +) INHERITS (person); + +\set filename :abs_srcdir '/data/emp.data' +COPY emp FROM :'filename'; +VACUUM ANALYZE emp; + + +CREATE TABLE student ( + gpa float8 +) INHERITS (person); + +\set filename :abs_srcdir '/data/student.data' +COPY student FROM :'filename'; +VACUUM ANALYZE student; + + +CREATE TABLE stud_emp ( + percent int4 +) INHERITS (emp, student); +\set filename :abs_srcdir '/data/stud_emp.data' +COPY stud_emp FROM :'filename'; + +CREATE TABLE road ( + name text, + thepath path +); + +\set filename :abs_srcdir '/data/streets.data' +COPY road FROM :'filename'; +VACUUM ANALYZE road; + +CREATE TABLE ihighway () INHERITS (road); +INSERT INTO ihighway + SELECT * + FROM ONLY road + WHERE name ~ 'I- .*'; +VACUUM ANALYZE ihighway; + +CREATE TABLE shighway ( + surface text +) INHERITS (road); +INSERT INTO shighway + SELECT *, 'asphalt' + FROM ONLY road + WHERE name ~ 'State Hwy.*'; +VACUUM ANALYZE shighway; +-- +-- Also create some non-built-in range types. +-- + +create type float8range as range (subtype = float8, subtype_diff = float8mi); + +create type textrange as range (subtype = text, collation = "C"); + +-- +-- Create some C functions that will be used by various tests. +-- + +CREATE FUNCTION binary_coercible(oid, oid) + RETURNS bool + AS :'regresslib', 'binary_coercible' + LANGUAGE C STRICT STABLE PARALLEL SAFE; + +CREATE FUNCTION ttdummy () + RETURNS trigger + AS :'regresslib' + LANGUAGE C; + +-- Use hand-rolled hash functions and operator classes to get predictable +-- result on different machines. The hash function for int4 simply returns +-- the sum of the values passed to it and the one for text returns the length +-- of the non-empty string value passed to it or 0. + +create function part_hashint4_noop(value int4, seed int8) + returns int8 as $$ + select value + seed; + $$ language sql strict immutable parallel safe; + +create operator class part_test_int4_ops for type int4 using hash as + operator 1 =, + function 2 part_hashint4_noop(int4, int8); + +create function part_hashtext_length(value text, seed int8) + returns int8 as $$ + select length(coalesce(value, ''))::int8 + $$ language sql strict immutable parallel safe; + +create operator class part_test_text_ops for type text using hash as + operator 1 =, + function 2 part_hashtext_length(text, int8); diff --git a/src/test/regress/sql/yb.port.text.sql b/src/test/regress/sql/yb.port.text.sql new file mode 100644 index 0000000000000..d733061581a02 --- /dev/null +++ b/src/test/regress/sql/yb.port.text.sql @@ -0,0 +1,114 @@ +-- +-- TEXT +-- + +SELECT text 'this is a text string' = text 'this is a text string' AS true; + +SELECT text 'this is a text string' = text 'this is a text strin' AS false; + +-- text_tbl was already created and filled in test_setup.sql. +SELECT '' AS two, * FROM TEXT_TBL; + +-- As of 8.3 we have removed most implicit casts to text, so that for example +-- this no longer works: + +select length(42); + +-- But as a special exception for usability's sake, we still allow implicit +-- casting to text in concatenations, so long as the other input is text or +-- an unknown literal. So these work: + +select 'four: '::text || 2+2; +select 'four: ' || 2+2; + +-- but not this: + +select 3 || 4.0; + +/* + * various string functions + */ +select concat('one'); +select concat(1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD')); +select concat_ws('#','one'); +select concat_ws('#',1,2,3,'hello',true, false, to_date('20100309','YYYYMMDD')); +select concat_ws(',',10,20,null,30); +select concat_ws('',10,20,null,30); +select concat_ws(NULL,10,20,null,30) is null; +select reverse('abcde'); +select i, left('ahoj', i), right('ahoj', i) from generate_series(-5, 5) t(i) order by i; +select quote_literal(''); +select quote_literal('abc'''); +select quote_literal(e'\\'); +-- check variadic labeled argument +select concat(variadic array[1,2,3]); +select concat_ws(',', variadic array[1,2,3]); +select concat_ws(',', variadic NULL::int[]); +select concat(variadic NULL::int[]) is NULL; +select concat(variadic '{}'::int[]) = ''; +--should fail +select concat_ws(',', variadic 10); + +/* + * format + */ +select format(NULL); +select format('Hello'); +select format('Hello %s', 'World'); +select format('Hello %%'); +select format('Hello %%%%'); +-- should fail +select format('Hello %s %s', 'World'); +select format('Hello %s'); +select format('Hello %x', 20); +-- check literal and sql identifiers +select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', 10, 'Hello'); +select format('%s%s%s','Hello', NULL,'World'); +select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', 10, NULL); +select format('INSERT INTO %I VALUES(%L,%L)', 'mytab', NULL, 'Hello'); +-- should fail, sql identifier cannot be NULL +select format('INSERT INTO %I VALUES(%L,%L)', NULL, 10, 'Hello'); +-- check positional placeholders +select format('%1$s %3$s', 1, 2, 3); +select format('%1$s %12$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); +-- should fail +select format('%1$s %4$s', 1, 2, 3); +select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); +select format('%0$s', 'Hello'); +select format('%*0$s', 'Hello'); +select format('%1$', 1); +select format('%1$1', 1); +-- check mix of positional and ordered placeholders +select format('Hello %s %1$s %s', 'World', 'Hello again'); +select format('Hello %s %s, %2$s %2$s', 'World', 'Hello again'); +-- check variadic labeled arguments +select format('%s, %s', variadic array['Hello','World']); +select format('%s, %s', variadic array[1, 2]); +select format('%s, %s', variadic array[true, false]); +select format('%s, %s', variadic array[true, false]::text[]); +-- check variadic with positional placeholders +select format('%2$s, %1$s', variadic array['first', 'second']); +select format('%2$s, %1$s', variadic array[1, 2]); +-- variadic argument can be array type NULL, but should not be referenced +select format('Hello', variadic NULL::int[]); +-- variadic argument allows simulating more than FUNC_MAX_ARGS parameters +select format(string_agg('%s',','), variadic array_agg(i)) +from generate_series(1,200) g(i); +-- check field widths and left, right alignment +select format('>>%10s<<', 'Hello'); +select format('>>%10s<<', NULL); +select format('>>%10s<<', ''); +select format('>>%-10s<<', ''); +select format('>>%-10s<<', 'Hello'); +select format('>>%-10s<<', NULL); +select format('>>%1$10s<<', 'Hello'); +select format('>>%1$-10I<<', 'Hello'); +select format('>>%2$*1$L<<', 10, 'Hello'); +select format('>>%2$*1$L<<', 10, NULL); +select format('>>%2$*1$L<<', -10, NULL); +select format('>>%*s<<', 10, 'Hello'); +select format('>>%*1$s<<', 10, 'Hello'); +select format('>>%-s<<', 'Hello'); +select format('>>%10L<<', NULL); +select format('>>%2$*1$L<<', NULL, 'Hello'); +select format('>>%2$*1$L<<', 0, 'Hello'); diff --git a/src/test/regress/sql/yb.port.transactions.sql b/src/test/regress/sql/yb.port.transactions.sql new file mode 100644 index 0000000000000..1de4e1560010b --- /dev/null +++ b/src/test/regress/sql/yb.port.transactions.sql @@ -0,0 +1,613 @@ +-- +-- TRANSACTIONS +-- + +BEGIN; + +CREATE TABLE xacttest (a smallint, b real); +INSERT INTO xacttest VALUES + (56, 7.8), + (100, 99.097), + (0, 0.09561), + (42, 324.78); +INSERT INTO xacttest (a, b) VALUES (777, 777.777); + +END; + +-- should retrieve one value-- +SELECT a FROM xacttest WHERE a > 100; + + +BEGIN; + +CREATE TABLE disappear (a int4); + +DELETE FROM xacttest; + +-- should be empty +SELECT * FROM xacttest; + +ABORT; + +-- should not exist +SELECT count(oid) FROM pg_class WHERE relname = 'disappear'; -- YB: change to count, TODO: transactional DDL + +-- should have members again +SELECT * FROM xacttest; + + +-- Read-only tests + +CREATE TABLE writetest (a int); +CREATE TEMPORARY TABLE temptest (a int); + +BEGIN; +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE; -- ok +ROLLBACK; BEGIN ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE; -- YB workaround of above failure +SELECT * FROM writetest; -- ok +SET TRANSACTION READ WRITE; --fail +COMMIT; + +BEGIN; +SET TRANSACTION READ ONLY; -- ok +SET TRANSACTION READ WRITE; -- ok +-- YB: above fails even without a savepoint in YB. If that is fixed, port remaining queries +COMMIT; + +BEGIN; +SET TRANSACTION READ WRITE; -- ok +SAVEPOINT x; +SET TRANSACTION READ WRITE; -- ok +SET TRANSACTION READ ONLY; -- ok +SELECT * FROM writetest; -- ok +SET TRANSACTION READ ONLY; -- ok +SET TRANSACTION READ WRITE; --fail +COMMIT; + +BEGIN; +SET TRANSACTION READ WRITE; -- ok +SAVEPOINT x; +SET TRANSACTION READ ONLY; -- ok +SELECT * FROM writetest; -- ok +ROLLBACK TO SAVEPOINT x; +SHOW transaction_read_only; -- off +SAVEPOINT y; +SET TRANSACTION READ ONLY; -- ok +SELECT * FROM writetest; -- ok +RELEASE SAVEPOINT y; +SHOW transaction_read_only; -- off +COMMIT; + +SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY; + +DROP TABLE writetest; -- fail +INSERT INTO writetest VALUES (1); -- fail +SELECT * FROM writetest; -- ok +DELETE FROM temptest; -- ok +UPDATE temptest SET a = 0 FROM writetest WHERE temptest.a = 1 AND writetest.a = temptest.a; -- ok +PREPARE test AS UPDATE writetest SET a = 0; -- ok +EXECUTE test; -- fail +SELECT * FROM writetest, temptest; -- ok +CREATE TABLE test AS SELECT * FROM writetest; -- fail + +START TRANSACTION READ WRITE; +DROP TABLE writetest; -- ok +COMMIT; + +-- Subtransactions, basic tests +-- create & drop tables +SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE; +CREATE TABLE trans_foobar (a int); +BEGIN; + CREATE TABLE trans_foo (a int); + SAVEPOINT one; +/* YB: avoid DROP TABLE since it is not rolled back due to lack of transactional DDL + DROP TABLE trans_foo; +*/ -- YB + CREATE TABLE trans_bar (a int); + ROLLBACK TO SAVEPOINT one; + DROP TABLE trans_bar; -- YB: above CREATE TABLE is not rolled back due to lack of transactional DDL + RELEASE SAVEPOINT one; + SAVEPOINT two; + CREATE TABLE trans_baz (a int); + RELEASE SAVEPOINT two; + drop TABLE trans_foobar; + CREATE TABLE trans_barbaz (a int); +COMMIT; +-- should exist: trans_barbaz, trans_baz, trans_foo +SELECT * FROM trans_foo; -- should be empty +SELECT * FROM trans_bar; -- shouldn't exist +SELECT * FROM trans_barbaz; -- should be empty +SELECT * FROM trans_baz; -- should be empty + +-- inserts +BEGIN; + INSERT INTO trans_foo VALUES (1); + SAVEPOINT one; + INSERT into trans_bar VALUES (1); + ROLLBACK TO one; + RELEASE SAVEPOINT one; + SAVEPOINT two; + INSERT into trans_barbaz VALUES (1); + RELEASE two; + SAVEPOINT three; + SAVEPOINT four; + INSERT INTO trans_foo VALUES (2); + RELEASE SAVEPOINT four; + ROLLBACK TO SAVEPOINT three; + RELEASE SAVEPOINT three; + INSERT INTO trans_foo VALUES (3); +COMMIT; +SELECT * FROM trans_foo; -- should have 1 and 3 +SELECT * FROM trans_barbaz; -- should have 1 + +-- test whole-tree commit +BEGIN; + SAVEPOINT one; + SELECT trans_foo; + ROLLBACK TO SAVEPOINT one; + RELEASE SAVEPOINT one; + SAVEPOINT two; + CREATE TABLE savepoints (a int); + SAVEPOINT three; + INSERT INTO savepoints VALUES (1); + SAVEPOINT four; + INSERT INTO savepoints VALUES (2); + SAVEPOINT five; + INSERT INTO savepoints VALUES (3); + ROLLBACK TO SAVEPOINT five; +COMMIT; +COMMIT; -- should not be in a transaction block +SELECT * FROM savepoints; + +-- test whole-tree rollback +BEGIN; + SAVEPOINT one; + DELETE FROM savepoints WHERE a=1; + RELEASE SAVEPOINT one; + SAVEPOINT two; + DELETE FROM savepoints WHERE a=1; + SAVEPOINT three; + DELETE FROM savepoints WHERE a=2; +ROLLBACK; +COMMIT; -- should not be in a transaction block + +SELECT * FROM savepoints; + +-- test whole-tree commit on an aborted subtransaction +BEGIN; + INSERT INTO savepoints VALUES (4); + SAVEPOINT one; + INSERT INTO savepoints VALUES (5); + SELECT trans_foo; +COMMIT; +SELECT * FROM savepoints; + +BEGIN; + INSERT INTO savepoints VALUES (6); + SAVEPOINT one; + INSERT INTO savepoints VALUES (7); + RELEASE SAVEPOINT one; + INSERT INTO savepoints VALUES (8); +COMMIT; +-- rows 6 and 8 should have been created by the same xact +SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=8; +-- rows 6 and 7 should have been created by different xacts +SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=7; + +BEGIN; + INSERT INTO savepoints VALUES (9); + SAVEPOINT one; + INSERT INTO savepoints VALUES (10); + ROLLBACK TO SAVEPOINT one; + INSERT INTO savepoints VALUES (11); +COMMIT; +SELECT a FROM savepoints WHERE a in (9, 10, 11); +-- rows 9 and 11 should have been created by different xacts +SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=9 AND b.a=11; + +BEGIN; + INSERT INTO savepoints VALUES (12); + SAVEPOINT one; + INSERT INTO savepoints VALUES (13); + SAVEPOINT two; + INSERT INTO savepoints VALUES (14); + ROLLBACK TO SAVEPOINT one; + INSERT INTO savepoints VALUES (15); + SAVEPOINT two; + INSERT INTO savepoints VALUES (16); + SAVEPOINT three; + INSERT INTO savepoints VALUES (17); +COMMIT; +SELECT a FROM savepoints WHERE a BETWEEN 12 AND 17; + +BEGIN; + INSERT INTO savepoints VALUES (18); + SAVEPOINT one; + INSERT INTO savepoints VALUES (19); + SAVEPOINT two; + INSERT INTO savepoints VALUES (20); + ROLLBACK TO SAVEPOINT one; + INSERT INTO savepoints VALUES (21); + ROLLBACK TO SAVEPOINT one; + INSERT INTO savepoints VALUES (22); +COMMIT; +SELECT a FROM savepoints WHERE a BETWEEN 18 AND 22; + +DROP TABLE savepoints; + +-- only in a transaction block: +SAVEPOINT one; +ROLLBACK TO SAVEPOINT one; +RELEASE SAVEPOINT one; + +-- Only "rollback to" allowed in aborted state +BEGIN; + SAVEPOINT one; + SELECT 0/0; + SAVEPOINT two; -- ignored till the end of ... + RELEASE SAVEPOINT one; -- ignored till the end of ... + ROLLBACK TO SAVEPOINT one; + SELECT 1; +COMMIT; +SELECT 1; -- this should work + +-- check non-transactional behavior of cursors +BEGIN; + DECLARE c CURSOR FOR SELECT unique2 FROM tenk1 ORDER BY unique2; + SAVEPOINT one; + FETCH 10 FROM c; + ROLLBACK TO SAVEPOINT one; + FETCH 10 FROM c; + RELEASE SAVEPOINT one; + FETCH 10 FROM c; + CLOSE c; + DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1 ORDER BY unique2; + SAVEPOINT two; + FETCH 10 FROM c; + ROLLBACK TO SAVEPOINT two; + -- c is now dead to the world ... + FETCH 10 FROM c; + ROLLBACK TO SAVEPOINT two; + RELEASE SAVEPOINT two; + FETCH 10 FROM c; +COMMIT; + +-- +-- Check that "stable" functions are really stable. They should not be +-- able to see the partial results of the calling query. (Ideally we would +-- also check that they don't see commits of concurrent transactions, but +-- that's a mite hard to do within the limitations of pg_regress.) +-- +select * from xacttest; + +-- YB: imitate PG ctid reallocation for the following updates: row a=0 should be first: +create temp table ybtmp as select * from xacttest where a > 0; +delete from xacttest where a > 0; -- YB +insert into xacttest select * from ybtmp; +drop table ybtmp; +create or replace function max_xacttest() returns smallint language sql as +'select max(a) from xacttest' stable; + +begin; +update xacttest set a = max_xacttest() + 10 where a > 0; +select * from xacttest; -- YB: TODO(#22947): fix output +rollback; + +-- But a volatile function can see the partial results of the calling query +create or replace function max_xacttest() returns smallint language sql as +'select max(a) from xacttest' volatile; + +begin; +update xacttest set a = max_xacttest() + 10 where a > 0; +select * from xacttest; +rollback; + +-- Now the same test with plpgsql (since it depends on SPI which is different) +create or replace function max_xacttest() returns smallint language plpgsql as +'begin return max(a) from xacttest; end' stable; + +begin; +update xacttest set a = max_xacttest() + 10 where a > 0; +select * from xacttest; -- YB: TODO(#22947): fix output +rollback; + +create or replace function max_xacttest() returns smallint language plpgsql as +'begin return max(a) from xacttest; end' volatile; + +begin; +update xacttest set a = max_xacttest() + 10 where a > 0; +select * from xacttest; +rollback; + + +-- test case for problems with dropping an open relation during abort +BEGIN; + savepoint x; + CREATE TABLE koju (a INT UNIQUE); + INSERT INTO koju VALUES (1); + INSERT INTO koju VALUES (1); + rollback to x; + + /* YB: above CREATE TABLE is not rolled back due to lack of transactional DDL + CREATE TABLE koju (a INT UNIQUE); + */ -- YB + INSERT INTO koju VALUES (1); + INSERT INTO koju VALUES (1); +ROLLBACK; + +DROP TABLE trans_foo; +DROP TABLE trans_baz; +DROP TABLE trans_barbaz; + + +-- test case for problems with revalidating an open relation during abort +create function inverse(int) returns float8 as +$$ +begin + analyze revalidate_bug; + return 1::float8/$1; +exception + when division_by_zero then return 0; +end$$ language plpgsql volatile; + +create table revalidate_bug (c float8 unique); +insert into revalidate_bug values (1); +insert into revalidate_bug values (inverse(0)); + +drop table revalidate_bug; +drop function inverse(int); + + +-- verify that cursors created during an aborted subtransaction are +-- closed, but that we do not rollback the effect of any FETCHs +-- performed in the aborted subtransaction +begin; + +savepoint x; +create table abc (a int); +insert into abc values (5); +insert into abc values (10); +declare foo cursor for select * from abc; +fetch from foo; +rollback to x; + +-- should fail +fetch from foo; +commit; +drop table abc; -- YB: above CREATE TABLE is not rolled back due to lack of transactional DDL + +begin; + +create table abc (a int); +insert into abc values (5); +insert into abc values (10); +insert into abc values (15); +declare foo cursor for select * from abc; + +fetch from foo; + +savepoint x; +fetch from foo; +rollback to x; + +fetch from foo; + +abort; +drop table abc; -- YB: above CREATE TABLE is not rolled back due to lack of transactional DDL + + +-- Test for proper cleanup after a failure in a cursor portal +-- that was created in an outer subtransaction +CREATE FUNCTION invert(x float8) RETURNS float8 LANGUAGE plpgsql AS +$$ begin return 1/x; end $$; + +CREATE FUNCTION create_temp_tab() RETURNS text +LANGUAGE plpgsql AS $$ +BEGIN + CREATE TEMP TABLE new_table (f1 float8); + -- case of interest is that we fail while holding an open + -- relcache reference to new_table + INSERT INTO new_table SELECT invert(0.0); + RETURN 'foo'; +END $$; + +BEGIN; +DECLARE ok CURSOR FOR SELECT * FROM int8_tbl; +DECLARE ctt CURSOR FOR SELECT create_temp_tab(); +FETCH ok; +SAVEPOINT s1; +FETCH ok; -- should work +FETCH ctt; -- error occurs here +ROLLBACK TO s1; +FETCH ok; -- should work +FETCH ctt; -- must be rejected +COMMIT; + +DROP FUNCTION create_temp_tab(); +DROP FUNCTION invert(x float8); + + +-- Tests for AND CHAIN + +CREATE TABLE abc (a int); + +-- set nondefault value so we have something to override below +SET default_transaction_read_only = on; + +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES (1); +INSERT INTO abc VALUES (2); +COMMIT AND CHAIN; -- TBLOCK_END +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES ('error'); +INSERT INTO abc VALUES (3); -- check it's really aborted +COMMIT AND CHAIN; -- TBLOCK_ABORT_END +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES (4); +COMMIT; + +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +SAVEPOINT x; +INSERT INTO abc VALUES ('error'); +COMMIT AND CHAIN; -- TBLOCK_ABORT_PENDING +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES (5); +COMMIT; + +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +SAVEPOINT x; +COMMIT AND CHAIN; -- TBLOCK_SUBCOMMIT +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +COMMIT; + +-- different mix of options just for fun +START TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ WRITE, NOT DEFERRABLE; +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES (6); +ROLLBACK AND CHAIN; -- TBLOCK_ABORT_PENDING +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES ('error'); +ROLLBACK AND CHAIN; -- TBLOCK_ABORT_END +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +ROLLBACK; + +-- not allowed outside a transaction block +COMMIT AND CHAIN; -- error +ROLLBACK AND CHAIN; -- error + +SELECT * FROM abc ORDER BY 1; + +RESET default_transaction_read_only; + +DROP TABLE abc; + + +-- Test assorted behaviors around the implicit transaction block created +-- when multiple SQL commands are sent in a single Query message. These +-- tests rely on the fact that psql will not break SQL commands apart at a +-- backslash-quoted semicolon, but will send them as one Query. + +create temp table i_table (f1 int); + +-- psql will show all results of a multi-statement Query +SELECT 1\; SELECT 2\; SELECT 3; + +-- this implicitly commits: +insert into i_table values(1)\; select * from i_table; +-- 1/0 error will cause rolling back the whole implicit transaction +insert into i_table values(2)\; select * from i_table\; select 1/0; +select * from i_table; + +rollback; -- we are not in a transaction at this point + +-- can use regular begin/commit/rollback within a single Query +begin\; insert into i_table values(3)\; commit; +rollback; -- we are not in a transaction at this point +begin\; insert into i_table values(4)\; rollback; +rollback; -- we are not in a transaction at this point + +-- begin converts implicit transaction into a regular one that +-- can extend past the end of the Query +select 1\; begin\; insert into i_table values(5); +commit; +select 1\; begin\; insert into i_table values(6); +rollback; + +-- commit in implicit-transaction state commits but issues a warning. +insert into i_table values(7)\; commit\; insert into i_table values(8)\; select 1/0; +-- similarly, rollback aborts but issues a warning. +insert into i_table values(9)\; rollback\; select 2; + +select * from i_table; + +rollback; -- we are not in a transaction at this point + +-- implicit transaction block is still a transaction block, for e.g. VACUUM +SELECT 1\; VACUUM; +SELECT 1\; COMMIT\; VACUUM; + +-- we disallow savepoint-related commands in implicit-transaction state +SELECT 1\; SAVEPOINT sp; +SELECT 1\; COMMIT\; SAVEPOINT sp; +ROLLBACK TO SAVEPOINT sp\; SELECT 2; +SELECT 2\; RELEASE SAVEPOINT sp\; SELECT 3; + +-- but this is OK, because the BEGIN converts it to a regular xact +SELECT 1\; BEGIN\; SAVEPOINT sp\; ROLLBACK TO SAVEPOINT sp\; COMMIT; + + +-- Tests for AND CHAIN in implicit transaction blocks + +SET TRANSACTION READ ONLY\; COMMIT AND CHAIN; -- error +SHOW transaction_read_only; + +SET TRANSACTION READ ONLY\; ROLLBACK AND CHAIN; -- error +SHOW transaction_read_only; + +CREATE TABLE abc (a int); + +-- COMMIT/ROLLBACK + COMMIT/ROLLBACK AND CHAIN +INSERT INTO abc VALUES (7)\; COMMIT\; INSERT INTO abc VALUES (8)\; COMMIT AND CHAIN; -- 7 commit, 8 error +INSERT INTO abc VALUES (9)\; ROLLBACK\; INSERT INTO abc VALUES (10)\; ROLLBACK AND CHAIN; -- 9 rollback, 10 error + +-- COMMIT/ROLLBACK AND CHAIN + COMMIT/ROLLBACK +INSERT INTO abc VALUES (11)\; COMMIT AND CHAIN\; INSERT INTO abc VALUES (12)\; COMMIT; -- 11 error, 12 not reached +INSERT INTO abc VALUES (13)\; ROLLBACK AND CHAIN\; INSERT INTO abc VALUES (14)\; ROLLBACK; -- 13 error, 14 not reached + +-- START TRANSACTION + COMMIT/ROLLBACK AND CHAIN +START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO abc VALUES (15)\; COMMIT AND CHAIN; -- 15 ok +SHOW transaction_isolation; -- transaction is active at this point +COMMIT; + +START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO abc VALUES (16)\; ROLLBACK AND CHAIN; -- 16 ok +SHOW transaction_isolation; -- transaction is active at this point +ROLLBACK; + +SET default_transaction_isolation = 'read committed'; + +-- START TRANSACTION + COMMIT/ROLLBACK + COMMIT/ROLLBACK AND CHAIN +START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO abc VALUES (17)\; COMMIT\; INSERT INTO abc VALUES (18)\; COMMIT AND CHAIN; -- 17 commit, 18 error +SHOW transaction_isolation; -- out of transaction block + +START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO abc VALUES (19)\; ROLLBACK\; INSERT INTO abc VALUES (20)\; ROLLBACK AND CHAIN; -- 19 rollback, 20 error +SHOW transaction_isolation; -- out of transaction block + +RESET default_transaction_isolation; + +SELECT * FROM abc ORDER BY 1; + +DROP TABLE abc; + + +-- Test for successful cleanup of an aborted transaction at session exit. +-- THIS MUST BE THE LAST TEST IN THIS FILE. + +begin; +select 1/0; +rollback to X; + +-- DO NOT ADD ANYTHING HERE. diff --git a/src/test/regress/sql/yb.port.triggers.sql b/src/test/regress/sql/yb.port.triggers.sql new file mode 100644 index 0000000000000..a0a99a2a555d1 --- /dev/null +++ b/src/test/regress/sql/yb.port.triggers.sql @@ -0,0 +1,2585 @@ +-- +-- TRIGGERS +-- + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set autoinclib :libdir '/autoinc' :dlsuffix +\set refintlib :libdir '/refint' :dlsuffix +\set regresslib :libdir '/regress' :dlsuffix + +CREATE FUNCTION autoinc () + RETURNS trigger + AS :'autoinclib' + LANGUAGE C; + +CREATE FUNCTION check_primary_key () + RETURNS trigger + AS :'refintlib' + LANGUAGE C; + +CREATE FUNCTION check_foreign_key () + RETURNS trigger + AS :'refintlib' + LANGUAGE C; + +CREATE FUNCTION trigger_return_old () + RETURNS trigger + AS :'regresslib' + LANGUAGE C; + +CREATE FUNCTION set_ttdummy (int4) + RETURNS int4 + AS :'regresslib' + LANGUAGE C STRICT; + +create table pkeys (pkey1 int4 not null, pkey2 text not null); +create table fkeys (fkey1 int4, fkey2 text, fkey3 int); +create table fkeys2 (fkey21 int4, fkey22 text, pkey23 int not null); + +create index fkeys_i on fkeys (fkey1, fkey2); +create index fkeys2_i on fkeys2 (fkey21, fkey22); +create index fkeys2p_i on fkeys2 (pkey23); + +insert into pkeys values (10, '1'); +insert into pkeys values (20, '2'); +insert into pkeys values (30, '3'); +insert into pkeys values (40, '4'); +insert into pkeys values (50, '5'); +insert into pkeys values (60, '6'); +create unique index pkeys_i on pkeys (pkey1, pkey2); + +-- +-- For fkeys: +-- (fkey1, fkey2) --> pkeys (pkey1, pkey2) +-- (fkey3) --> fkeys2 (pkey23) +-- +create trigger check_fkeys_pkey_exist + before insert or update on fkeys + for each row + execute function + check_primary_key ('fkey1', 'fkey2', 'pkeys', 'pkey1', 'pkey2'); + +create trigger check_fkeys_pkey2_exist + before insert or update on fkeys + for each row + execute function check_primary_key ('fkey3', 'fkeys2', 'pkey23'); + +-- +-- For fkeys2: +-- (fkey21, fkey22) --> pkeys (pkey1, pkey2) +-- +create trigger check_fkeys2_pkey_exist + before insert or update on fkeys2 + for each row + execute procedure + check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2'); + +-- Test comments +COMMENT ON TRIGGER check_fkeys2_pkey_bad ON fkeys2 IS 'wrong'; +COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS 'right'; +COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS NULL; + +-- +-- For pkeys: +-- ON DELETE/UPDATE (pkey1, pkey2) CASCADE: +-- fkeys (fkey1, fkey2) and fkeys2 (fkey21, fkey22) +-- +create trigger check_pkeys_fkey_cascade + before delete or update on pkeys + for each row + execute procedure + check_foreign_key (2, 'cascade', 'pkey1', 'pkey2', + 'fkeys', 'fkey1', 'fkey2', 'fkeys2', 'fkey21', 'fkey22'); + +-- +-- For fkeys2: +-- ON DELETE/UPDATE (pkey23) RESTRICT: +-- fkeys (fkey3) +-- +create trigger check_fkeys2_fkey_restrict + before delete or update on fkeys2 + for each row + execute procedure check_foreign_key (1, 'restrict', 'pkey23', 'fkeys', 'fkey3'); + +insert into fkeys2 values (10, '1', 1); +insert into fkeys2 values (30, '3', 2); +insert into fkeys2 values (40, '4', 5); +insert into fkeys2 values (50, '5', 3); +-- no key in pkeys +insert into fkeys2 values (70, '5', 3); + +insert into fkeys values (10, '1', 2); +insert into fkeys values (30, '3', 3); +insert into fkeys values (40, '4', 2); +insert into fkeys values (50, '5', 2); +-- no key in pkeys +insert into fkeys values (70, '5', 1); +-- no key in fkeys2 +insert into fkeys values (60, '6', 4); + +delete from pkeys where pkey1 = 30 and pkey2 = '3'; +delete from pkeys where pkey1 = 40 and pkey2 = '4'; +update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 50 and pkey2 = '5'; +update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 10 and pkey2 = '1'; + +SELECT trigger_name, event_manipulation, event_object_schema, event_object_table, + action_order, action_condition, action_orientation, action_timing, + action_reference_old_table, action_reference_new_table + FROM information_schema.triggers + WHERE event_object_table in ('pkeys', 'fkeys', 'fkeys2') + ORDER BY trigger_name COLLATE "C", 2; + +DROP TABLE pkeys; +DROP TABLE fkeys; +DROP TABLE fkeys2; + +-- Check behavior when trigger returns unmodified trigtuple +create table trigtest (f1 int, f2 text); + +create trigger trigger_return_old + before insert or delete or update on trigtest + for each row execute procedure trigger_return_old(); + +insert into trigtest values(1, 'foo'); +select * from trigtest; +update trigtest set f2 = f2 || 'bar'; +select * from trigtest; +delete from trigtest; +select * from trigtest; + +drop table trigtest; + +create sequence ttdummy_seq increment 10 start 0 minvalue 0; + +create table tttest ( + price_id int4, + price_val int4, + price_on int4, + price_off int4 default 999999 +); + +create trigger ttdummy + before delete or update on tttest + for each row + execute procedure + ttdummy (price_on, price_off); + +create trigger ttserial + before insert or update on tttest + for each row + execute procedure + autoinc (price_on, ttdummy_seq); + +insert into tttest values (1, 1, null); +insert into tttest values (2, 2, null); +insert into tttest values (3, 3, 0); + +select * from tttest order by price_id; +delete from tttest where price_id = 2; +select * from tttest order by price_id; +-- what do we see ? + +-- get current prices +select * from tttest where price_off = 999999 order by price_id; + +-- change price for price_id == 3 +update tttest set price_val = 30 where price_id = 3 and price_on = 30; +select * from tttest order by price_id, price_val; + +-- now we want to change price_id in ALL tuples +-- this gets us not what we need +update tttest set price_id = 5 where price_id = 3; +select * from tttest order by price_id, price_val; + +-- restore data as before last update: +select set_ttdummy(0); +delete from tttest where price_id = 5; +update tttest set price_off = 999999 where price_val = 30; +select * from tttest order by price_id, price_val; + +-- and try change price_id now! +update tttest set price_id = 5 where price_id = 3; +select * from tttest order by price_id, price_val; +-- isn't it what we need ? + +select set_ttdummy(1); + +-- we want to correct some "date" +update tttest set price_on = -1 where price_id = 1; +-- but this doesn't work + +-- try in this way +select set_ttdummy(0); +update tttest set price_on = -1 where price_id = 1; +select * from tttest order by price_id, price_val; +-- isn't it what we need ? + +-- get price for price_id == 5 as it was @ "date" 35 +select * from tttest where price_on <= 35 and price_off > 35 and price_id = 5; + +drop table tttest; +drop sequence ttdummy_seq; + +-- +-- tests for per-statement triggers +-- + +CREATE TABLE log_table (tstamp timestamp default timeofday()::timestamp); + +CREATE TABLE main_table (a int unique, b int); + +COPY main_table (a,b) FROM stdin; +5 10 +20 20 +30 10 +50 35 +80 15 +\. + +CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS ' +BEGIN + RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; + RETURN NULL; +END;'; + +CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt'); + +CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt'); + +-- +-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified, +-- CREATE TRIGGER should default to 'FOR EACH STATEMENT' +-- +CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table +EXECUTE PROCEDURE trigger_func('after_upd_stmt'); + +-- Both insert and update statement level triggers (before and after) should +-- fire. Doesn't fire UPDATE before trigger, but only because one isn't +-- defined. +INSERT INTO main_table (a, b) VALUES (5, 10) ON CONFLICT (a) + DO UPDATE SET b = EXCLUDED.b; + +CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row'); + +INSERT INTO main_table DEFAULT VALUES; + +UPDATE main_table SET a = a + 1 WHERE b < 30; +-- UPDATE that effects zero rows should still call per-statement trigger +UPDATE main_table SET a = a + 2 WHERE b > 100; + +-- constraint now unneeded +ALTER TABLE main_table DROP CONSTRAINT main_table_a_key; + +-- COPY should fire per-row and per-statement INSERT triggers +COPY main_table (a, b) FROM stdin; +30 40 +50 60 +\. + +SELECT * FROM main_table ORDER BY a, b; + +-- +-- test triggers with WHEN clause +-- + +CREATE TRIGGER modified_a BEFORE UPDATE OF a ON main_table +FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('modified_a'); +CREATE TRIGGER modified_any BEFORE UPDATE OF a ON main_table +FOR EACH ROW WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECUTE PROCEDURE trigger_func('modified_any'); +CREATE TRIGGER insert_a AFTER INSERT ON main_table +FOR EACH ROW WHEN (NEW.a = 123) EXECUTE PROCEDURE trigger_func('insert_a'); +CREATE TRIGGER delete_a AFTER DELETE ON main_table +FOR EACH ROW WHEN (OLD.a = 123) EXECUTE PROCEDURE trigger_func('delete_a'); +CREATE TRIGGER insert_when BEFORE INSERT ON main_table +FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('insert_when'); +CREATE TRIGGER delete_when AFTER DELETE ON main_table +FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('delete_when'); +SELECT trigger_name, event_manipulation, event_object_schema, event_object_table, + action_order, action_condition, action_orientation, action_timing, + action_reference_old_table, action_reference_new_table + FROM information_schema.triggers + WHERE event_object_table IN ('main_table') + ORDER BY trigger_name COLLATE "C", 2; +INSERT INTO main_table (a) VALUES (123), (456); +COPY main_table FROM stdin; +123 999 +456 999 +\. +DELETE FROM main_table WHERE a IN (123, 456); +SELECT * FROM main_table ORDER BY a, b; +-- In YB we cannot rely on row order inside update so use multiple calls to check behavior. +-- Note: trigger_func returns null so if called as before-trigger it will cancel the op. +-- Update rows where 'a' is already 50 (no modify_a notices): +-- 1. expect 1 modify_any notice (there are 2 rows but one has b=60 already). +-- 2. expect 1 after_upd_row notice (for the b=60 row as the other is cancelled by the trigger). +-- 3. expect 1 after_upd_stmt notice (once per statement). +UPDATE main_table SET a = 50, b = 60 WHERE a = 50; +-- Update rows where 'a' is not 50: +-- 1. expect 5 modify_a notices (one for each row). +-- 2. expect no modify_any or after_upd_row notices as modify_a trigger call cancels the op. +-- 3. expect 1 after_upd_stmt notice (once per statement). +UPDATE main_table SET a = 50, b = 60 WHERE a != 50; +-- Check end state should be the same as start as trigger_func returns null cancelling row ops. +SELECT * FROM main_table ORDER BY a, b; +SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a'; +SELECT pg_get_triggerdef(oid, false) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a'; +SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_any'; +DROP TRIGGER modified_a ON main_table; +DROP TRIGGER modified_any ON main_table; +DROP TRIGGER insert_a ON main_table; +DROP TRIGGER delete_a ON main_table; +DROP TRIGGER insert_when ON main_table; +DROP TRIGGER delete_when ON main_table; + +-- Test column-level triggers +DROP TRIGGER after_upd_row_trig ON main_table; + +CREATE TRIGGER before_upd_a_row_trig BEFORE UPDATE OF a ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_a_row'); +CREATE TRIGGER after_upd_b_row_trig AFTER UPDATE OF b ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_b_row'); +CREATE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row'); + +CREATE TRIGGER before_upd_a_stmt_trig BEFORE UPDATE OF a ON main_table +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_upd_a_stmt'); +CREATE TRIGGER after_upd_b_stmt_trig AFTER UPDATE OF b ON main_table +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_upd_b_stmt'); + +SELECT pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'after_upd_a_b_row_trig'; + +UPDATE main_table SET a = 50; +UPDATE main_table SET b = 10; + +-- +-- Test case for bug with BEFORE trigger followed by AFTER trigger with WHEN +-- + +CREATE TABLE some_t (some_col boolean NOT NULL); +CREATE FUNCTION dummy_update_func() RETURNS trigger AS $$ +BEGIN + RAISE NOTICE 'dummy_update_func(%) called: action = %, old = %, new = %', + TG_ARGV[0], TG_OP, OLD, NEW; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER some_trig_before BEFORE UPDATE ON some_t FOR EACH ROW + EXECUTE PROCEDURE dummy_update_func('before'); +CREATE TRIGGER some_trig_aftera AFTER UPDATE ON some_t FOR EACH ROW + WHEN (NOT OLD.some_col AND NEW.some_col) + EXECUTE PROCEDURE dummy_update_func('aftera'); +CREATE TRIGGER some_trig_afterb AFTER UPDATE ON some_t FOR EACH ROW + WHEN (NOT NEW.some_col) + EXECUTE PROCEDURE dummy_update_func('afterb'); +INSERT INTO some_t VALUES (TRUE); +UPDATE some_t SET some_col = TRUE; +UPDATE some_t SET some_col = FALSE; +UPDATE some_t SET some_col = TRUE; +DROP TABLE some_t; + +-- bogus cases +CREATE TRIGGER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col'); +CREATE TRIGGER error_upd_a_a BEFORE UPDATE OF a, a ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_a_a'); +CREATE TRIGGER error_ins_a BEFORE INSERT OF a ON main_table +FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_ins_a'); +CREATE TRIGGER error_ins_when BEFORE INSERT OR UPDATE ON main_table +FOR EACH ROW WHEN (OLD.a <> NEW.a) +EXECUTE PROCEDURE trigger_func('error_ins_old'); +CREATE TRIGGER error_del_when BEFORE DELETE OR UPDATE ON main_table +FOR EACH ROW WHEN (OLD.a <> NEW.a) +EXECUTE PROCEDURE trigger_func('error_del_new'); +CREATE TRIGGER error_del_when BEFORE INSERT OR UPDATE ON main_table +FOR EACH ROW WHEN (NEW.tableoid <> 0) +EXECUTE PROCEDURE trigger_func('error_when_sys_column'); +CREATE TRIGGER error_stmt_when BEFORE UPDATE OF a ON main_table +FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*) +EXECUTE PROCEDURE trigger_func('error_stmt_when'); + +-- check dependency restrictions +\set VERBOSITY terse \\ -- suppress cascade details +ALTER TABLE main_table DROP COLUMN b; +\set VERBOSITY default +-- TODO Skip this test in YB as DDLs are not yet transactional #1383. +/* +-- this should succeed, but we'll roll it back to keep the triggers around +begin; +DROP TRIGGER after_upd_a_b_row_trig ON main_table; +DROP TRIGGER after_upd_b_row_trig ON main_table; +DROP TRIGGER after_upd_b_stmt_trig ON main_table; +ALTER TABLE main_table DROP COLUMN b; +rollback; +*/ + +-- Test enable/disable triggers + +create table trigtest (i serial primary key); +-- test that disabling RI triggers works +create table trigtest2 (i int references trigtest(i) on delete cascade); + +create function trigtest() returns trigger as $$ +begin + raise notice '% % % %', TG_RELNAME, TG_OP, TG_WHEN, TG_LEVEL; + return new; +end;$$ language plpgsql; + +create trigger trigtest_b_row_tg before insert or update or delete on trigtest +for each row execute procedure trigtest(); +create trigger trigtest_a_row_tg after insert or update or delete on trigtest +for each row execute procedure trigtest(); +create trigger trigtest_b_stmt_tg before insert or update or delete on trigtest +for each statement execute procedure trigtest(); +create trigger trigtest_a_stmt_tg after insert or update or delete on trigtest +for each statement execute procedure trigtest(); + +insert into trigtest default values; +alter table trigtest disable trigger trigtest_b_row_tg; +insert into trigtest default values; +alter table trigtest disable trigger user; +insert into trigtest default values; +alter table trigtest enable trigger trigtest_a_stmt_tg; +insert into trigtest default values; +set session_replication_role = replica; +insert into trigtest default values; -- does not trigger +alter table trigtest enable always trigger trigtest_a_stmt_tg; +insert into trigtest default values; -- now it does +reset session_replication_role; +insert into trigtest2 values(1); +insert into trigtest2 values(2); +delete from trigtest where i=2; +select * from trigtest2; +alter table trigtest disable trigger all; +delete from trigtest where i=1; +select * from trigtest2; +-- ensure we still insert, even when all triggers are disabled +insert into trigtest default values; +select * from trigtest order by i; +drop table trigtest2; +drop table trigtest; + + +-- dump trigger data +CREATE TABLE trigger_test ( + i int, + v varchar +); + +CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger +LANGUAGE plpgsql AS $$ + +declare + + argstr text; + relid text; + +begin + + relid := TG_relid::regclass; + + -- plpgsql can't discover its trigger data in a hash like perl and python + -- can, or by a sort of reflection like tcl can, + -- so we have to hard code the names. + raise NOTICE 'TG_NAME: %', TG_name; + raise NOTICE 'TG_WHEN: %', TG_when; + raise NOTICE 'TG_LEVEL: %', TG_level; + raise NOTICE 'TG_OP: %', TG_op; + raise NOTICE 'TG_RELID::regclass: %', relid; + raise NOTICE 'TG_RELNAME: %', TG_relname; + raise NOTICE 'TG_TABLE_NAME: %', TG_table_name; + raise NOTICE 'TG_TABLE_SCHEMA: %', TG_table_schema; + raise NOTICE 'TG_NARGS: %', TG_nargs; + + argstr := '['; + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; + argstr := argstr || ']'; + raise NOTICE 'TG_ARGV: %', argstr; + + if TG_OP != 'INSERT' then + raise NOTICE 'OLD: %', OLD; + end if; + + if TG_OP != 'DELETE' then + raise NOTICE 'NEW: %', NEW; + end if; + + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; + +end; +$$; + +CREATE TRIGGER show_trigger_data_trig +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +insert into trigger_test values(1,'insert'); +update trigger_test set v = 'update' where i = 1; +delete from trigger_test; + +DROP TRIGGER show_trigger_data_trig on trigger_test; + +DROP FUNCTION trigger_data(); + +DROP TABLE trigger_test; + +-- +-- Test use of row comparisons on OLD/NEW +-- + +-- In YB adding primary key to ensure f1 order is stable (ascending). +CREATE TABLE trigger_test (h int, f1 int, f2 text, f3 text, primary key (h HASH, f1 ASC)); + +-- this is the obvious (and wrong...) way to compare rows +CREATE FUNCTION mytrigger() RETURNS trigger LANGUAGE plpgsql as $$ +begin + if row(old.*) = row(new.*) then + raise notice 'row % not changed', new.f1; + else + raise notice 'row % changed', new.f1; + end if; + return new; +end$$; + +CREATE TRIGGER t +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE mytrigger(); + +INSERT INTO trigger_test VALUES(1, 1, 'foo', 'bar'); +INSERT INTO trigger_test VALUES(1, 2, 'baz', 'quux'); + +UPDATE trigger_test SET f3 = 'bar'; +UPDATE trigger_test SET f3 = NULL; +-- this demonstrates that the above isn't really working as desired: +UPDATE trigger_test SET f3 = NULL; + +-- the right way when considering nulls is +CREATE OR REPLACE FUNCTION mytrigger() RETURNS trigger LANGUAGE plpgsql as $$ +begin + if row(old.*) is distinct from row(new.*) then + raise notice 'row % changed', new.f1; + else + raise notice 'row % not changed', new.f1; + end if; + return new; +end$$; + +UPDATE trigger_test SET f3 = 'bar'; +UPDATE trigger_test SET f3 = NULL; +UPDATE trigger_test SET f3 = NULL; + +DROP TABLE trigger_test; + +DROP FUNCTION mytrigger(); + +-- Test snapshot management in serializable transactions involving triggers +-- per bug report in 6bc73d4c0910042358k3d1adff3qa36f8df75198ecea@mail.gmail.com +CREATE FUNCTION serializable_update_trig() RETURNS trigger LANGUAGE plpgsql AS +$$ +declare + rec record; +begin + new.description = 'updated in trigger'; + return new; +end; +$$; + +CREATE TABLE serializable_update_tab ( + id int, + filler text, + description text +); + +CREATE TRIGGER serializable_update_trig BEFORE UPDATE ON serializable_update_tab + FOR EACH ROW EXECUTE PROCEDURE serializable_update_trig(); + +INSERT INTO serializable_update_tab SELECT a, repeat('xyzxz', 100), 'new' + FROM generate_series(1, 50) a; + +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +UPDATE serializable_update_tab SET description = 'no no', id = 1 WHERE id = 1; +COMMIT; +SELECT description FROM serializable_update_tab WHERE id = 1; +DROP TABLE serializable_update_tab; + +-- minimal update trigger + +CREATE TABLE min_updates_test ( + f1 text, + f2 int, + f3 int); + +INSERT INTO min_updates_test VALUES ('a',1,2),('b','2',null); + +CREATE TRIGGER z_min_update +BEFORE UPDATE ON min_updates_test +FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); + +\set QUIET false + +UPDATE min_updates_test SET f1 = f1; + +UPDATE min_updates_test SET f2 = f2 + 1; + +UPDATE min_updates_test SET f3 = 2 WHERE f3 is null; + +\set QUIET true + +SELECT * FROM min_updates_test order by f1; + +DROP TABLE min_updates_test; + +-- +-- Test triggers on views +-- + +CREATE VIEW main_view AS SELECT a, b FROM main_table; + +-- VIEW trigger function +CREATE OR REPLACE FUNCTION view_trigger() RETURNS trigger +LANGUAGE plpgsql AS $$ +declare + argstr text := ''; +begin + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; + + raise notice '% % % % (%)', TG_RELNAME, TG_WHEN, TG_OP, TG_LEVEL, argstr; + + if TG_LEVEL = 'ROW' then + if TG_OP = 'INSERT' then + raise NOTICE 'NEW: %', NEW; + INSERT INTO main_table VALUES (NEW.a, NEW.b); + RETURN NEW; + end if; + + if TG_OP = 'UPDATE' then + raise NOTICE 'OLD: %, NEW: %', OLD, NEW; + UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b; + if NOT FOUND then RETURN NULL; end if; + RETURN NEW; + end if; + + if TG_OP = 'DELETE' then + raise NOTICE 'OLD: %', OLD; + DELETE FROM main_table WHERE a = OLD.a AND b = OLD.b; + if NOT FOUND then RETURN NULL; end if; + RETURN OLD; + end if; + end if; + + RETURN NULL; +end; +$$; + +-- Before row triggers aren't allowed on views +CREATE TRIGGER invalid_trig BEFORE INSERT ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row'); + +CREATE TRIGGER invalid_trig BEFORE UPDATE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row'); + +CREATE TRIGGER invalid_trig BEFORE DELETE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row'); + +-- After row triggers aren't allowed on views +CREATE TRIGGER invalid_trig AFTER INSERT ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row'); + +CREATE TRIGGER invalid_trig AFTER UPDATE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row'); + +CREATE TRIGGER invalid_trig AFTER DELETE ON main_view +FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row'); + +-- Truncate triggers aren't allowed on views +CREATE TRIGGER invalid_trig BEFORE TRUNCATE ON main_view +EXECUTE PROCEDURE trigger_func('before_tru_row'); + +CREATE TRIGGER invalid_trig AFTER TRUNCATE ON main_view +EXECUTE PROCEDURE trigger_func('before_tru_row'); + +-- INSTEAD OF triggers aren't allowed on tables +CREATE TRIGGER invalid_trig INSTEAD OF INSERT ON main_table +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins'); + +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_table +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd'); + +CREATE TRIGGER invalid_trig INSTEAD OF DELETE ON main_table +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del'); + +-- Don't support WHEN clauses with INSTEAD OF triggers +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view +FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE view_trigger('instead_of_upd'); + +-- Don't support column-level INSTEAD OF triggers +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE OF a ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd'); + +-- Don't support statement-level INSTEAD OF triggers +CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view +EXECUTE PROCEDURE view_trigger('instead_of_upd'); + +-- Valid INSTEAD OF triggers +CREATE TRIGGER instead_of_insert_trig INSTEAD OF INSERT ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins'); + +CREATE TRIGGER instead_of_update_trig INSTEAD OF UPDATE ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd'); + +CREATE TRIGGER instead_of_delete_trig INSTEAD OF DELETE ON main_view +FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del'); + +-- Valid BEFORE statement VIEW triggers +CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_ins_stmt'); + +CREATE TRIGGER before_upd_stmt_trig BEFORE UPDATE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_upd_stmt'); + +CREATE TRIGGER before_del_stmt_trig BEFORE DELETE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_del_stmt'); + +-- Valid AFTER statement VIEW triggers +CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_ins_stmt'); + +CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_upd_stmt'); + +CREATE TRIGGER after_del_stmt_trig AFTER DELETE ON main_view +FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_del_stmt'); + +\set QUIET false + +-- Insert into view using trigger + +INSERT INTO main_view VALUES (20, 30); +INSERT INTO main_view VALUES (21, 31) RETURNING a, b; + +-- Table trigger will prevent updates +UPDATE main_view SET b = 31 WHERE a = 20; +UPDATE main_view SET b = 32 WHERE a = 21 AND b = 31 RETURNING a, b; + +-- Remove table trigger to allow updates +DROP TRIGGER before_upd_a_row_trig ON main_table; +UPDATE main_view SET b = 31 WHERE a = 20; +UPDATE main_view SET b = 32 WHERE a = 21 AND b = 31 RETURNING a, b; +SELECT * from main_view order by a, b; + +-- Before and after stmt triggers should fire even when no rows are affected +UPDATE main_view SET b = 0 WHERE false; + +-- Delete from view using trigger +DELETE FROM main_view WHERE a IN (10,20); +DELETE FROM main_view WHERE a = 31 RETURNING a, b; +SELECT * from main_view order by a, b; + +\set QUIET true + +-- Describe view should list triggers +\d main_view + +-- Test dropping view triggers +DROP TRIGGER instead_of_insert_trig ON main_view; +DROP TRIGGER instead_of_delete_trig ON main_view; +\d+ main_view +DROP VIEW main_view; + +-- +-- Test triggers on a join view +-- +CREATE TABLE country_table ( + country_id serial primary key, + country_name text unique not null, + continent text not null +); + +INSERT INTO country_table (country_name, continent) + VALUES ('Japan', 'Asia'), + ('UK', 'Europe'), + ('USA', 'North America') + RETURNING *; + +CREATE TABLE city_table ( + city_id serial primary key, + city_name text not null, + population bigint, + country_id int references country_table +); + +CREATE VIEW city_view AS + SELECT city_id, city_name, population, country_name, continent + FROM city_table ci + LEFT JOIN country_table co ON co.country_id = ci.country_id; + +CREATE FUNCTION city_insert() RETURNS trigger LANGUAGE plpgsql AS $$ +declare + ctry_id int; +begin + if NEW.country_name IS NOT NULL then + SELECT country_id, continent INTO ctry_id, NEW.continent + FROM country_table WHERE country_name = NEW.country_name; + if NOT FOUND then + raise exception 'No such country: "%"', NEW.country_name; + end if; + else + NEW.continent := NULL; + end if; + + if NEW.city_id IS NOT NULL then + INSERT INTO city_table + VALUES(NEW.city_id, NEW.city_name, NEW.population, ctry_id); + else + INSERT INTO city_table(city_name, population, country_id) + VALUES(NEW.city_name, NEW.population, ctry_id) + RETURNING city_id INTO NEW.city_id; + end if; + + RETURN NEW; +end; +$$; + +CREATE TRIGGER city_insert_trig INSTEAD OF INSERT ON city_view +FOR EACH ROW EXECUTE PROCEDURE city_insert(); + +CREATE FUNCTION city_delete() RETURNS trigger LANGUAGE plpgsql AS $$ +begin + DELETE FROM city_table WHERE city_id = OLD.city_id; + if NOT FOUND then RETURN NULL; end if; + RETURN OLD; +end; +$$; + +CREATE TRIGGER city_delete_trig INSTEAD OF DELETE ON city_view +FOR EACH ROW EXECUTE PROCEDURE city_delete(); + +CREATE FUNCTION city_update() RETURNS trigger LANGUAGE plpgsql AS $$ +declare + ctry_id int; +begin + if NEW.country_name IS DISTINCT FROM OLD.country_name then + SELECT country_id, continent INTO ctry_id, NEW.continent + FROM country_table WHERE country_name = NEW.country_name; + if NOT FOUND then + raise exception 'No such country: "%"', NEW.country_name; + end if; + + UPDATE city_table SET city_name = NEW.city_name, + population = NEW.population, + country_id = ctry_id + WHERE city_id = OLD.city_id; + else + UPDATE city_table SET city_name = NEW.city_name, + population = NEW.population + WHERE city_id = OLD.city_id; + NEW.continent := OLD.continent; + end if; + + if NOT FOUND then RETURN NULL; end if; + RETURN NEW; +end; +$$; + +CREATE TRIGGER city_update_trig INSTEAD OF UPDATE ON city_view +FOR EACH ROW EXECUTE PROCEDURE city_update(); + +\set QUIET false + +-- INSERT .. RETURNING +INSERT INTO city_view(city_name) VALUES('Tokyo') RETURNING *; +INSERT INTO city_view(city_name, population) VALUES('London', 7556900) RETURNING *; +INSERT INTO city_view(city_name, country_name) VALUES('Washington DC', 'USA') RETURNING *; +INSERT INTO city_view(city_id, city_name) VALUES(123456, 'New York') RETURNING *; +INSERT INTO city_view VALUES(234567, 'Birmingham', 1016800, 'UK', 'EU') RETURNING *; + +-- UPDATE .. RETURNING +UPDATE city_view SET country_name = 'Japon' WHERE city_name = 'Tokyo'; -- error +UPDATE city_view SET country_name = 'Japan' WHERE city_name = 'Takyo'; -- no match +UPDATE city_view SET country_name = 'Japan' WHERE city_name = 'Tokyo' RETURNING *; -- OK + +UPDATE city_view SET population = 13010279 WHERE city_name = 'Tokyo' RETURNING *; +UPDATE city_view SET country_name = 'UK' WHERE city_name = 'New York' RETURNING *; +UPDATE city_view SET country_name = 'USA', population = 8391881 WHERE city_name = 'New York' RETURNING *; +UPDATE city_view SET continent = 'EU' WHERE continent = 'Europe' RETURNING *; +UPDATE city_view v1 SET country_name = v2.country_name FROM city_view v2 + WHERE v2.city_name = 'Birmingham' AND v1.city_name = 'London' RETURNING *; + +-- DELETE .. RETURNING +DELETE FROM city_view WHERE city_name = 'Birmingham' RETURNING *; + +\set QUIET true + +-- read-only view with WHERE clause +CREATE VIEW european_city_view AS + SELECT * FROM city_view WHERE continent = 'Europe'; +SELECT count(*) FROM european_city_view; + +CREATE FUNCTION no_op_trig_fn() RETURNS trigger LANGUAGE plpgsql +AS 'begin RETURN NULL; end'; + +CREATE TRIGGER no_op_trig INSTEAD OF INSERT OR UPDATE OR DELETE +ON european_city_view FOR EACH ROW EXECUTE PROCEDURE no_op_trig_fn(); + +\set QUIET false + +INSERT INTO european_city_view VALUES (0, 'x', 10000, 'y', 'z'); +UPDATE european_city_view SET population = 10000; +DELETE FROM european_city_view; + +\set QUIET true + +-- rules bypassing no-op triggers +CREATE RULE european_city_insert_rule AS ON INSERT TO european_city_view +DO INSTEAD INSERT INTO city_view +VALUES (NEW.city_id, NEW.city_name, NEW.population, NEW.country_name, NEW.continent) +RETURNING *; + +CREATE RULE european_city_update_rule AS ON UPDATE TO european_city_view +DO INSTEAD UPDATE city_view SET + city_name = NEW.city_name, + population = NEW.population, + country_name = NEW.country_name +WHERE city_id = OLD.city_id +RETURNING NEW.*; + +CREATE RULE european_city_delete_rule AS ON DELETE TO european_city_view +DO INSTEAD DELETE FROM city_view WHERE city_id = OLD.city_id RETURNING *; + +\set QUIET false + +-- INSERT not limited by view's WHERE clause, but UPDATE AND DELETE are +INSERT INTO european_city_view(city_name, country_name) + VALUES ('Cambridge', 'USA') RETURNING *; +UPDATE european_city_view SET country_name = 'UK' + WHERE city_name = 'Cambridge'; +DELETE FROM european_city_view WHERE city_name = 'Cambridge'; + +-- UPDATE and DELETE via rule and trigger +UPDATE city_view SET country_name = 'UK' + WHERE city_name = 'Cambridge' RETURNING *; +UPDATE european_city_view SET population = 122800 + WHERE city_name = 'Cambridge' RETURNING *; +DELETE FROM european_city_view WHERE city_name = 'Cambridge' RETURNING *; + +-- join UPDATE test +UPDATE city_view v SET population = 599657 + FROM city_table ci, country_table co + WHERE ci.city_name = 'Washington DC' and co.country_name = 'USA' + AND v.city_id = ci.city_id AND v.country_name = co.country_name + RETURNING co.country_id, v.country_name, + v.city_id, v.city_name, v.population; + +\set QUIET true + +SELECT * FROM city_view order by city_id; + +DROP TABLE city_table CASCADE; +DROP TABLE country_table; + + +-- Test pg_trigger_depth() + +create table depth_a (id int not null primary key); +create table depth_b (id int not null primary key); +create table depth_c (id int not null primary key); + +create function depth_a_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + insert into depth_b values (new.id); + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + return new; +end; +$$; +create trigger depth_a_tr before insert on depth_a + for each row execute procedure depth_a_tf(); + +create function depth_b_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + begin + execute 'insert into depth_c values (' || new.id::text || ')'; + exception + when sqlstate 'U9999' then + raise notice 'SQLSTATE = U9999: depth = %', pg_trigger_depth(); + end; + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + if new.id = 1 then + execute 'insert into depth_c values (' || new.id::text || ')'; + end if; + return new; +end; +$$; +create trigger depth_b_tr before insert on depth_b + for each row execute procedure depth_b_tf(); + +create function depth_c_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + if new.id = 1 then + raise exception sqlstate 'U9999'; + end if; + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + return new; +end; +$$; +create trigger depth_c_tr before insert on depth_c + for each row execute procedure depth_c_tf(); + +select pg_trigger_depth(); +insert into depth_a values (1); +select pg_trigger_depth(); +insert into depth_a values (2); +select pg_trigger_depth(); + +drop table depth_a; +drop table depth_b; +drop table depth_c; +drop function depth_a_tf(); +drop function depth_b_tf(); +drop function depth_c_tf(); + +-- +-- Test updates to rows during firing of BEFORE ROW triggers. +-- As of 9.2, such cases should be rejected (see bug #6123). +-- + +create temp table parent ( + aid int not null primary key, + val1 text, + val2 text, + val3 text, + val4 text, + bcnt int not null default 0); +create temp table child ( + bid int not null primary key, + aid int not null, + val1 text); + +create function parent_upd_func() + returns trigger language plpgsql as +$$ +begin + if old.val1 <> new.val1 then + new.val2 = new.val1; + delete from child where child.aid = new.aid and child.val1 = new.val1; + end if; + return new; +end; +$$; +create trigger parent_upd_trig before update on parent + for each row execute procedure parent_upd_func(); + +create function parent_del_func() + returns trigger language plpgsql as +$$ +begin + delete from child where aid = old.aid; + return old; +end; +$$; +create trigger parent_del_trig before delete on parent + for each row execute procedure parent_del_func(); + +create function child_ins_func() + returns trigger language plpgsql as +$$ +begin + update parent set bcnt = bcnt + 1 where aid = new.aid; + return new; +end; +$$; +create trigger child_ins_trig after insert on child + for each row execute procedure child_ins_func(); + +create function child_del_func() + returns trigger language plpgsql as +$$ +begin + update parent set bcnt = bcnt - 1 where aid = old.aid; + return old; +end; +$$; +create trigger child_del_trig after delete on child + for each row execute procedure child_del_func(); + +insert into parent values (1, 'a', 'a', 'a', 'a', 0); +insert into child values (10, 1, 'b'); +select * from parent; select * from child; + +update parent set val1 = 'b' where aid = 1; -- should fail +select * from parent; select * from child; + +delete from parent where aid = 1; -- should fail +select * from parent; select * from child; + +-- replace the trigger function with one that restarts the deletion after +-- having modified a child +create or replace function parent_del_func() + returns trigger language plpgsql as +$$ +begin + delete from child where aid = old.aid; + if found then + delete from parent where aid = old.aid; + return null; -- cancel outer deletion + end if; + return old; +end; +$$; + +delete from parent where aid = 1; +select * from parent; select * from child; + +drop table parent; +drop table child; + +drop function parent_upd_func(); +drop function parent_del_func(); +drop function child_ins_func(); +drop function child_del_func(); + +-- similar case, but with a self-referencing FK so that parent and child +-- rows can be affected by a single operation + +create temp table self_ref_trigger ( + id int primary key, + parent int references self_ref_trigger, + data text, + nchildren int not null default 0 +); + +create function self_ref_trigger_ins_func() + returns trigger language plpgsql as +$$ +begin + if new.parent is not null then + update self_ref_trigger set nchildren = nchildren + 1 + where id = new.parent; + end if; + return new; +end; +$$; +create trigger self_ref_trigger_ins_trig before insert on self_ref_trigger + for each row execute procedure self_ref_trigger_ins_func(); + +create function self_ref_trigger_del_func() + returns trigger language plpgsql as +$$ +begin + if old.parent is not null then + update self_ref_trigger set nchildren = nchildren - 1 + where id = old.parent; + end if; + return old; +end; +$$; +create trigger self_ref_trigger_del_trig before delete on self_ref_trigger + for each row execute procedure self_ref_trigger_del_func(); + +insert into self_ref_trigger values (1, null, 'root'); +insert into self_ref_trigger values (2, 1, 'root child A'); +insert into self_ref_trigger values (3, 1, 'root child B'); +insert into self_ref_trigger values (4, 2, 'grandchild 1'); +insert into self_ref_trigger values (5, 3, 'grandchild 2'); + +update self_ref_trigger set data = 'root!' where id = 1; + +select * from self_ref_trigger; + +delete from self_ref_trigger; + +select * from self_ref_trigger; + +drop table self_ref_trigger; +drop function self_ref_trigger_ins_func(); +drop function self_ref_trigger_del_func(); + +-- +-- Check that statement triggers work correctly even with all children excluded +-- + +create table stmt_trig_on_empty_upd (a int); +create table stmt_trig_on_empty_upd1 () inherits (stmt_trig_on_empty_upd); +create function update_stmt_notice() returns trigger as $$ +begin + raise notice 'updating %', TG_TABLE_NAME; + return null; +end; +$$ language plpgsql; +create trigger before_stmt_trigger + before update on stmt_trig_on_empty_upd + execute procedure update_stmt_notice(); +create trigger before_stmt_trigger + before update on stmt_trig_on_empty_upd1 + execute procedure update_stmt_notice(); + +-- inherited no-op update +update stmt_trig_on_empty_upd set a = a where false returning a+1 as aa; +-- simple no-op update +update stmt_trig_on_empty_upd1 set a = a where false returning a+1 as aa; + +drop table stmt_trig_on_empty_upd cascade; +drop function update_stmt_notice(); + +-- +-- Check that index creation (or DDL in general) is prohibited in a trigger +-- + +create table trigger_ddl_table ( + col1 integer, + col2 integer +); + +create function trigger_ddl_func() returns trigger as $$ +begin + alter table trigger_ddl_table add primary key (col1); + return new; +end$$ language plpgsql; + +create trigger trigger_ddl_func before insert on trigger_ddl_table for each row + execute procedure trigger_ddl_func(); + +insert into trigger_ddl_table values (1, 42); -- fail + +create or replace function trigger_ddl_func() returns trigger as $$ +begin + create index on trigger_ddl_table (col2); + return new; +end$$ language plpgsql; + +insert into trigger_ddl_table values (1, 42); -- fail + +drop table trigger_ddl_table; +drop function trigger_ddl_func(); + +-- +-- Verify behavior of before and after triggers with INSERT...ON CONFLICT +-- DO UPDATE +-- +create table upsert (key int4 primary key, color text); + +create function upsert_before_func() + returns trigger language plpgsql as +$$ +begin + if (TG_OP = 'UPDATE') then + raise warning 'before update (old): %', old.*::text; + raise warning 'before update (new): %', new.*::text; + elsif (TG_OP = 'INSERT') then + raise warning 'before insert (new): %', new.*::text; + if new.key % 2 = 0 then + new.key := new.key + 1; + new.color := new.color || ' trig modified'; + raise warning 'before insert (new, modified): %', new.*::text; + end if; + end if; + return new; +end; +$$; +create trigger upsert_before_trig before insert or update on upsert + for each row execute procedure upsert_before_func(); + +create function upsert_after_func() + returns trigger language plpgsql as +$$ +begin + if (TG_OP = 'UPDATE') then + raise warning 'after update (old): %', old.*::text; + raise warning 'after update (new): %', new.*::text; + elsif (TG_OP = 'INSERT') then + raise warning 'after insert (new): %', new.*::text; + end if; + return null; +end; +$$; +create trigger upsert_after_trig after insert or update on upsert + for each row execute procedure upsert_after_func(); + +insert into upsert values(1, 'black') on conflict (key) do update set color = 'updated ' || upsert.color; +insert into upsert values(2, 'red') on conflict (key) do update set color = 'updated ' || upsert.color; +insert into upsert values(3, 'orange') on conflict (key) do update set color = 'updated ' || upsert.color; +insert into upsert values(4, 'green') on conflict (key) do update set color = 'updated ' || upsert.color; +insert into upsert values(5, 'purple') on conflict (key) do update set color = 'updated ' || upsert.color; +insert into upsert values(6, 'white') on conflict (key) do update set color = 'updated ' || upsert.color; +insert into upsert values(7, 'pink') on conflict (key) do update set color = 'updated ' || upsert.color; +insert into upsert values(8, 'yellow') on conflict (key) do update set color = 'updated ' || upsert.color; + +select * from upsert order by key; + +drop table upsert; +drop function upsert_before_func(); +drop function upsert_after_func(); + +-- +-- Verify that triggers with transition tables are not allowed on +-- views +-- + +create table my_table (i int); +create view my_view as select * from my_table; +create function my_trigger_function() returns trigger as $$ begin end; $$ language plpgsql; +create trigger my_trigger after update on my_view referencing old table as old_table + for each statement execute procedure my_trigger_function(); +drop function my_trigger_function(); +drop view my_view; +drop table my_table; + +-- +-- Verify cases that are unsupported with partitioned tables +-- +create table parted_trig (a int) partition by list (a); +create function trigger_nothing() returns trigger + language plpgsql as $$ begin end; $$; +create trigger failed instead of update on parted_trig + for each row execute procedure trigger_nothing(); +create trigger failed after update on parted_trig + referencing old table as old_table + for each row execute procedure trigger_nothing(); +drop table parted_trig; + +-- +-- Verify trigger creation for partitioned tables, and drop behavior +-- +create table trigpart (a int, b int) partition by range (a); +create table trigpart1 partition of trigpart for values from (0) to (1000); +create trigger trg1 after insert on trigpart for each row execute procedure trigger_nothing(); +create table trigpart2 partition of trigpart for values from (1000) to (2000); +create table trigpart3 (like trigpart); +alter table trigpart attach partition trigpart3 for values from (2000) to (3000); +create table trigpart4 partition of trigpart for values from (3000) to (4000) partition by range (a); +create table trigpart41 partition of trigpart4 for values from (3000) to (3500); +create table trigpart42 (like trigpart); +alter table trigpart4 attach partition trigpart42 for values from (3500) to (4000); +select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger + where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text; +-- Disabling the below tests temporarily because each run of the test can +-- throw one of two errors. The error message displayed contains the first dependency +-- resulting from the scan of pg_depend preventing the delete of the trigger. The trigger +-- depends on the partition table itself and on the trigger created on the partitioned +-- table, and either of the two error messages are possible. +/* +drop trigger trg1 on trigpart1; -- fail +drop trigger trg1 on trigpart2; -- fail +*/ +drop table trigpart2; -- ok, trigger should be gone in that partition +select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger + where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text; +drop trigger trg1 on trigpart; -- ok, all gone +select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger + where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text; + +-- check detach behavior +create trigger trg1 after insert on trigpart for each row execute procedure trigger_nothing(); +\d trigpart3 +alter table trigpart detach partition trigpart3; +drop trigger trg1 on trigpart3; -- fail due to "does not exist" +alter table trigpart detach partition trigpart4; +drop trigger trg1 on trigpart41; -- fail due to "does not exist" +drop table trigpart4; +alter table trigpart attach partition trigpart3 for values from (2000) to (3000); +alter table trigpart detach partition trigpart3; +alter table trigpart attach partition trigpart3 for values from (2000) to (3000); +drop table trigpart3; + +select tgrelid::regclass::text, tgname, tgfoid::regproc, tgenabled, tgisinternal from pg_trigger + where tgname ~ '^trg1' order by 1; +create table trigpart3 (like trigpart); +create trigger trg1 after insert on trigpart3 for each row execute procedure trigger_nothing(); +\d trigpart3 +alter table trigpart attach partition trigpart3 FOR VALUES FROM (2000) to (3000); -- fail +drop table trigpart3; + +drop table trigpart; +drop function trigger_nothing(); + +-- +-- Verify that triggers are fired for partitioned tables +-- +create table parted_stmt_trig (a int) partition by list (a); +create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1); +create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2); + +create table parted2_stmt_trig (a int) partition by list (a); +create table parted2_stmt_trig1 partition of parted2_stmt_trig for values in (1); +create table parted2_stmt_trig2 partition of parted2_stmt_trig for values in (2); + +create or replace function trigger_notice() returns trigger as $$ + begin + raise notice 'trigger % on % % % for %', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL; + if TG_LEVEL = 'ROW' then + return NEW; + end if; + return null; + end; + $$ language plpgsql; + +-- insert/update/delete statement-level triggers on the parent +create trigger trig_ins_before before insert on parted_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_ins_after after insert on parted_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_upd_before before update on parted_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_upd_after after update on parted_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_del_before before delete on parted_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_del_after after delete on parted_stmt_trig + for each statement execute procedure trigger_notice(); + +-- insert/update/delete row-level triggers on the parent +create trigger trig_ins_after_parent after insert on parted_stmt_trig + for each row execute procedure trigger_notice(); +create trigger trig_upd_after_parent after update on parted_stmt_trig + for each row execute procedure trigger_notice(); +create trigger trig_del_after_parent after delete on parted_stmt_trig + for each row execute procedure trigger_notice(); + +-- insert/update/delete row-level triggers on the first partition +create trigger trig_ins_before_child before insert on parted_stmt_trig1 + for each row execute procedure trigger_notice(); +create trigger trig_ins_after_child after insert on parted_stmt_trig1 + for each row execute procedure trigger_notice(); +create trigger trig_upd_before_child before update on parted_stmt_trig1 + for each row execute procedure trigger_notice(); +create trigger trig_upd_after_child after update on parted_stmt_trig1 + for each row execute procedure trigger_notice(); +create trigger trig_del_before_child before delete on parted_stmt_trig1 + for each row execute procedure trigger_notice(); +create trigger trig_del_after_child after delete on parted_stmt_trig1 + for each row execute procedure trigger_notice(); + +-- insert/update/delete statement-level triggers on the parent +create trigger trig_ins_before_3 before insert on parted2_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_ins_after_3 after insert on parted2_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_upd_before_3 before update on parted2_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_upd_after_3 after update on parted2_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_del_before_3 before delete on parted2_stmt_trig + for each statement execute procedure trigger_notice(); +create trigger trig_del_after_3 after delete on parted2_stmt_trig + for each statement execute procedure trigger_notice(); + +with ins (a) as ( + insert into parted2_stmt_trig values (1), (2) returning a +) insert into parted_stmt_trig select a from ins returning tableoid::regclass, a; + +with upd as ( + update parted2_stmt_trig set a = a +) update parted_stmt_trig set a = a; + +delete from parted_stmt_trig; + +-- insert via copy on the parent +copy parted_stmt_trig(a) from stdin; +1 +2 +\. + +-- insert via copy on the first partition +copy parted_stmt_trig1(a) from stdin; +1 +\. + +-- Disabling a trigger in the parent table should disable children triggers too +alter table parted_stmt_trig disable trigger trig_ins_after_parent; +insert into parted_stmt_trig values (1); +alter table parted_stmt_trig enable trigger trig_ins_after_parent; +insert into parted_stmt_trig values (1); + +drop table parted_stmt_trig, parted2_stmt_trig; + +-- Verify that triggers fire in alphabetical order +create table parted_trig (a int) partition by range (a); +create table parted_trig_1 partition of parted_trig for values from (0) to (1000) + partition by range (a); +create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100); +create table parted_trig_2 partition of parted_trig for values from (1000) to (2000); +create trigger zzz after insert on parted_trig for each row execute procedure trigger_notice(); +create trigger mmm after insert on parted_trig_1_1 for each row execute procedure trigger_notice(); +create trigger aaa after insert on parted_trig_1 for each row execute procedure trigger_notice(); +create trigger bbb after insert on parted_trig for each row execute procedure trigger_notice(); +create trigger qqq after insert on parted_trig_1_1 for each row execute procedure trigger_notice(); +insert into parted_trig values (50), (1500); +drop table parted_trig; + +-- test irregular partitions (i.e., different column definitions), +-- including that the WHEN clause works +create function bark(text) returns bool language plpgsql immutable + as $$ begin raise notice '% <- woof!', $1; return true; end; $$; +create or replace function trigger_notice_ab() returns trigger as $$ + begin + raise notice 'trigger % on % % % for %: (a,b)=(%,%)', + TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL, + NEW.a, NEW.b; + if TG_LEVEL = 'ROW' then + return NEW; + end if; + return null; + end; + $$ language plpgsql; +create table parted_irreg_ancestor (fd text, b text, fd2 int, fd3 int, a int) + partition by range (b); +alter table parted_irreg_ancestor drop column fd, + drop column fd2, drop column fd3; +create table parted_irreg (fd int, a int, fd2 int, b text) + partition by range (b); +alter table parted_irreg drop column fd, drop column fd2; +alter table parted_irreg_ancestor attach partition parted_irreg + for values from ('aaaa') to ('zzzz'); +create table parted1_irreg (b text, fd int, a int); +alter table parted1_irreg drop column fd; +alter table parted_irreg attach partition parted1_irreg + for values from ('aaaa') to ('bbbb'); +create trigger parted_trig after insert on parted_irreg + for each row execute procedure trigger_notice_ab(); +create trigger parted_trig_odd after insert on parted_irreg for each row + when (bark(new.b) AND new.a % 2 = 1) execute procedure trigger_notice_ab(); +-- we should hear barking for every insert, but parted_trig_odd only emits +-- noise for odd values of a. parted_trig does it for all inserts. +insert into parted_irreg values (1, 'aardvark'), (2, 'aanimals'); +insert into parted1_irreg values ('aardwolf', 2); +insert into parted_irreg_ancestor values ('aasvogel', 3); +drop table parted_irreg_ancestor; + +-- Before triggers and partitions +create table parted (a int, b int, c text) partition by list (a); +create table parted_1 partition of parted for values in (1) + partition by list (b); +create table parted_1_1 partition of parted_1 for values in (1); +create function parted_trigfunc() returns trigger language plpgsql as $$ +begin + new.a = new.a + 1; + return new; +end; +$$; +insert into parted values (1, 1, 'uno uno v1'); -- works +create trigger t before insert or update or delete on parted + for each row execute function parted_trigfunc(); +insert into parted values (1, 1, 'uno uno v2'); -- fail +update parted set c = c || 'v3'; -- fail +create or replace function parted_trigfunc() returns trigger language plpgsql as $$ +begin + new.b = new.b + 1; + return new; +end; +$$; +insert into parted values (1, 1, 'uno uno v4'); -- fail +update parted set c = c || 'v5'; -- fail +create or replace function parted_trigfunc() returns trigger language plpgsql as $$ +begin + new.c = new.c || ' did '|| TG_OP; + return new; +end; +$$; +insert into parted values (1, 1, 'uno uno'); -- works +update parted set c = c || ' v6'; -- works +-- YB note: add order by desc to match PG ordering +select tableoid::regclass, * from parted order by c desc; + +-- update itself moves tuple to new partition; trigger still works +truncate table parted; +create table parted_2 partition of parted for values in (2); +insert into parted values (1, 1, 'uno uno v5'); +update parted set a = 2; +select tableoid::regclass, * from parted; + +-- both trigger and update change the partition +create or replace function parted_trigfunc2() returns trigger language plpgsql as $$ +begin + new.a = new.a + 1; + return new; +end; +$$; +create trigger t2 before update on parted + for each row execute function parted_trigfunc2(); +truncate table parted; +insert into parted values (1, 1, 'uno uno v6'); +create table parted_3 partition of parted for values in (3); +update parted set a = a + 1; +select tableoid::regclass, * from parted; +-- there's no partition for a=0, but this update works anyway because +-- the trigger causes the tuple to be routed to another partition +update parted set a = 0; +select tableoid::regclass, * from parted; + +drop table parted; +create table parted (a int, b int, c text) partition by list ((a + b)); +create or replace function parted_trigfunc() returns trigger language plpgsql as $$ +begin + new.a = new.a + new.b; + return new; +end; +$$; +create table parted_1 partition of parted for values in (1, 2); +create table parted_2 partition of parted for values in (3, 4); +create trigger t before insert or update on parted + for each row execute function parted_trigfunc(); +insert into parted values (0, 1, 'zero win'); +insert into parted values (1, 1, 'one fail'); +insert into parted values (1, 2, 'two fail'); +select * from parted; +drop table parted; +drop function parted_trigfunc(); + +-- +-- Constraint triggers and partitioned tables +create table parted_constr_ancestor (a int, b text) + partition by range (b); +create table parted_constr (a int, b text) + partition by range (b); +alter table parted_constr_ancestor attach partition parted_constr + for values from ('aaaa') to ('zzzz'); +create table parted1_constr (a int, b text); +alter table parted_constr attach partition parted1_constr + for values from ('aaaa') to ('bbbb'); +create constraint trigger parted_trig after insert on parted_constr_ancestor + deferrable + for each row execute procedure trigger_notice_ab(); +/* +create constraint trigger parted_trig_two after insert on parted_constr + deferrable initially deferred + for each row when (bark(new.b) AND new.a % 2 = 1) + execute procedure trigger_notice_ab(); + +-- The immediate constraint is fired immediately; the WHEN clause of the +-- deferred constraint is also called immediately. The deferred constraint +-- is fired at commit time. +begin; +insert into parted_constr values (1, 'aardvark'); +insert into parted1_constr values (2, 'aardwolf'); +insert into parted_constr_ancestor values (3, 'aasvogel'); +commit; + +-- The WHEN clause is immediate, and both constraint triggers are fired at +-- commit time. +begin; +set constraints parted_trig deferred; +insert into parted_constr values (1, 'aardvark'); +insert into parted1_constr values (2, 'aardwolf'), (3, 'aasvogel'); +commit; +*/ +drop table parted_constr_ancestor; +drop function bark(text); +-- Test that the WHEN clause is set properly to partitions +create table parted_trigger (a int, b text) partition by range (a); +create table parted_trigger_1 partition of parted_trigger for values from (0) to (1000); +create table parted_trigger_2 (drp int, a int, b text); +alter table parted_trigger_2 drop column drp; +alter table parted_trigger attach partition parted_trigger_2 for values from (1000) to (2000); +create trigger parted_trigger after update on parted_trigger + for each row when (new.a % 2 = 1 and length(old.b) >= 2) execute procedure trigger_notice_ab(); +create table parted_trigger_3 (b text, a int) partition by range (length(b)); +create table parted_trigger_3_1 partition of parted_trigger_3 for values from (1) to (3); +create table parted_trigger_3_2 partition of parted_trigger_3 for values from (3) to (5); +alter table parted_trigger attach partition parted_trigger_3 for values from (2000) to (3000); +insert into parted_trigger values + (0, 'a'), (1, 'bbb'), (2, 'bcd'), (3, 'c'), + (1000, 'c'), (1001, 'ddd'), (1002, 'efg'), (1003, 'f'), + (2000, 'e'), (2001, 'fff'), (2002, 'ghi'), (2003, 'h'); +update parted_trigger set a = a + 2; -- notice for odd 'a' values, long 'b' values +drop table parted_trigger; + +-- try a constraint trigger, also +create table parted_referenced (a int); +create table unparted_trigger (a int, b text); -- for comparison purposes +create table parted_trigger (a int, b text) partition by range (a); +create table parted_trigger_1 partition of parted_trigger for values from (0) to (1000); +create table parted_trigger_2 (drp int, a int, b text); +alter table parted_trigger_2 drop column drp; +alter table parted_trigger attach partition parted_trigger_2 for values from (1000) to (2000); +create constraint trigger parted_trigger after update on parted_trigger + from parted_referenced + for each row execute procedure trigger_notice_ab(); +/* +create constraint trigger parted_trigger after update on unparted_trigger + from parted_referenced + for each row execute procedure trigger_notice_ab(); +create table parted_trigger_3 (b text, a int) partition by range (length(b)); +create table parted_trigger_3_1 partition of parted_trigger_3 for values from (1) to (3); +create table parted_trigger_3_2 partition of parted_trigger_3 for values from (3) to (5); +alter table parted_trigger attach partition parted_trigger_3 for values from (2000) to (3000); +select tgname, conname, t.tgrelid::regclass, t.tgconstrrelid::regclass, + c.conrelid::regclass, c.confrelid::regclass + from pg_trigger t join pg_constraint c on (t.tgconstraint = c.oid) + where tgname = 'parted_trigger' + order by t.tgrelid::regclass::text; +*/ +drop table parted_referenced, parted_trigger, unparted_trigger; + +-- verify that the "AFTER UPDATE OF columns" event is propagated correctly +create table parted_trigger (a int, b text) partition by range (a); +create table parted_trigger_1 partition of parted_trigger for values from (0) to (1000); +create table parted_trigger_2 (drp int, a int, b text); +alter table parted_trigger_2 drop column drp; +alter table parted_trigger attach partition parted_trigger_2 for values from (1000) to (2000); +create trigger parted_trigger after update of b on parted_trigger + for each row execute procedure trigger_notice_ab(); +create table parted_trigger_3 (b text, a int) partition by range (length(b)); +create table parted_trigger_3_1 partition of parted_trigger_3 for values from (1) to (4); +create table parted_trigger_3_2 partition of parted_trigger_3 for values from (4) to (8); +alter table parted_trigger attach partition parted_trigger_3 for values from (2000) to (3000); +insert into parted_trigger values (0, 'a'), (1000, 'c'), (2000, 'e'), (2001, 'eeee'); +update parted_trigger set a = a + 2; -- no notices here +update parted_trigger set b = b || 'b'; -- all triggers should fire +drop table parted_trigger; + +drop function trigger_notice_ab(); + +-- Make sure we don't end up with unnecessary copies of triggers, when +-- cloning them. +create table trg_clone (a int) partition by range (a); +create table trg_clone1 partition of trg_clone for values from (0) to (1000); +alter table trg_clone add constraint uniq unique (a) deferrable; +/* +create table trg_clone2 partition of trg_clone for values from (1000) to (2000); +create table trg_clone3 partition of trg_clone for values from (2000) to (3000) + partition by range (a); +create table trg_clone_3_3 partition of trg_clone3 for values from (2000) to (2100); +select tgrelid::regclass, count(*) from pg_trigger + where tgrelid::regclass in ('trg_clone', 'trg_clone1', 'trg_clone2', + 'trg_clone3', 'trg_clone_3_3') + group by tgrelid::regclass order by tgrelid::regclass; +*/ +drop table trg_clone; + +-- Test the interaction between ALTER TABLE .. DISABLE TRIGGER and +-- both kinds of inheritance. Historically, legacy inheritance has +-- not recursed to children, so that behavior is preserved. +create table parent (a int); +create table child1 () inherits (parent); +create function trig_nothing() returns trigger language plpgsql + as $$ begin return null; end $$; +create trigger tg after insert on parent + for each row execute function trig_nothing(); +create trigger tg after insert on child1 + for each row execute function trig_nothing(); +alter table parent disable trigger tg; +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text; +alter table only parent enable always trigger tg; +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text; +drop table parent, child1; + +create table parent (a int) partition by list (a); +create table child1 partition of parent for values in (1); +create trigger tg after insert on parent + for each row execute procedure trig_nothing(); +create trigger tg_stmt after insert on parent + for statement execute procedure trig_nothing(); +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgname; +alter table only parent enable always trigger tg; -- no recursion because ONLY +alter table parent enable always trigger tg_stmt; -- no recursion because statement trigger +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgname; +-- The following is a no-op for the parent trigger but not so +-- for the child trigger, so recursion should be applied. +alter table parent enable always trigger tg; +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgname; +-- This variant malfunctioned in some releases. +alter table parent disable trigger user; +select tgrelid::regclass, tgname, tgenabled from pg_trigger + where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgname; +drop table parent, child1; + +-- Check processing of foreign key triggers +create table parent (a int primary key, f int references parent) + partition by list (a); +create table child1 partition of parent for values in (1); +select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, + tgfoid::regproc, tgenabled + from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgfoid; +alter table parent disable trigger all; +select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, + tgfoid::regproc, tgenabled + from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgfoid; +drop table parent, child1; + +-- Verify that firing state propagates correctly on creation, too +CREATE TABLE trgfire (i int) PARTITION BY RANGE (i); +CREATE TABLE trgfire1 PARTITION OF trgfire FOR VALUES FROM (1) TO (10); +CREATE OR REPLACE FUNCTION tgf() RETURNS trigger LANGUAGE plpgsql + AS $$ begin raise exception 'except'; end $$; +CREATE TRIGGER tg AFTER INSERT ON trgfire FOR EACH ROW EXECUTE FUNCTION tgf(); +INSERT INTO trgfire VALUES (1); +ALTER TABLE trgfire DISABLE TRIGGER tg; +INSERT INTO trgfire VALUES (1); +CREATE TABLE trgfire2 PARTITION OF trgfire FOR VALUES FROM (10) TO (20); +INSERT INTO trgfire VALUES (11); +CREATE TABLE trgfire3 (LIKE trgfire); +ALTER TABLE trgfire ATTACH PARTITION trgfire3 FOR VALUES FROM (20) TO (30); +INSERT INTO trgfire VALUES (21); +CREATE TABLE trgfire4 PARTITION OF trgfire FOR VALUES FROM (30) TO (40) PARTITION BY LIST (i); +CREATE TABLE trgfire4_30 PARTITION OF trgfire4 FOR VALUES IN (30); +INSERT INTO trgfire VALUES (30); +CREATE TABLE trgfire5 (LIKE trgfire) PARTITION BY LIST (i); +CREATE TABLE trgfire5_40 PARTITION OF trgfire5 FOR VALUES IN (40); +ALTER TABLE trgfire ATTACH PARTITION trgfire5 FOR VALUES FROM (40) TO (50); +INSERT INTO trgfire VALUES (40); +SELECT tgrelid::regclass, tgenabled FROM pg_trigger + WHERE tgrelid::regclass IN (SELECT oid from pg_class where relname LIKE 'trgfire%') + ORDER BY tgrelid::regclass::text; +ALTER TABLE trgfire ENABLE TRIGGER tg; +INSERT INTO trgfire VALUES (1); +INSERT INTO trgfire VALUES (11); +INSERT INTO trgfire VALUES (21); +INSERT INTO trgfire VALUES (30); +INSERT INTO trgfire VALUES (40); +DROP TABLE trgfire; +DROP FUNCTION tgf(); +-- +-- Test the interaction between transition tables and both kinds of +-- inheritance. We'll dump the contents of the transition tables in a +-- format that shows the attribute order, so that we can distinguish +-- tuple formats (though not dropped attributes). +-- +-- Let these function be created since they are also used in other tests. +create or replace function dump_insert() returns trigger language plpgsql as +$$ + begin + raise notice 'trigger = %, new table = %', + TG_NAME, + (select string_agg(new_table::text, ', ' order by a) from new_table); + return null; + end; +$$; + +create or replace function dump_update() returns trigger language plpgsql as +$$ + begin + raise notice 'trigger = %, old table = %, new table = %', + TG_NAME, + (select string_agg(old_table::text, ', ' order by a) from old_table), + (select string_agg(new_table::text, ', ' order by a) from new_table); + return null; + end; +$$; + +create or replace function dump_delete() returns trigger language plpgsql as +$$ + begin + raise notice 'trigger = %, old table = %', + TG_NAME, + (select string_agg(old_table::text, ', ' order by a) from old_table); + return null; + end; +$$; + +-- +-- Verify behavior of statement triggers on partition hierarchy with +-- transition tables. Tuples should appear to each trigger in the +-- format of the relation the trigger is attached to. +-- + +-- set up a partition hierarchy with some different TupleDescriptors +create table parent (a text, b int) partition by list (a); + +-- a child matching parent +create table child1 partition of parent for values in ('AAA'); + +-- a child with a dropped column +create table child2 (x int, a text, b int); +alter table child2 drop column x; +alter table parent attach partition child2 for values in ('BBB'); + +-- a child with a different column order +create table child3 (b int, a text); +alter table parent attach partition child3 for values in ('CCC'); +create trigger parent_insert_trig + after insert on parent referencing new table as new_table + for each statement execute procedure dump_insert(); +/* YB: Transition tables (REFERENCING clause) are not supported +create trigger parent_update_trig + after update on parent referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger parent_delete_trig + after delete on parent referencing old table as old_table + for each statement execute procedure dump_delete(); +create trigger child1_insert_trig + after insert on child1 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child1_update_trig + after update on child1 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child1_delete_trig + after delete on child1 referencing old table as old_table + for each statement execute procedure dump_delete(); + +create trigger child2_insert_trig + after insert on child2 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child2_update_trig + after update on child2 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child2_delete_trig + after delete on child2 referencing old table as old_table + for each statement execute procedure dump_delete(); + +create trigger child3_insert_trig + after insert on child3 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child3_update_trig + after update on child3 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child3_delete_trig + after delete on child3 referencing old table as old_table + for each statement execute procedure dump_delete(); + +SELECT trigger_name, event_manipulation, event_object_schema, event_object_table, + action_order, action_condition, action_orientation, action_timing, + action_reference_old_table, action_reference_new_table + FROM information_schema.triggers + WHERE event_object_table IN ('parent', 'child1', 'child2', 'child3') + ORDER BY trigger_name COLLATE "C", 2; + +-- insert directly into children sees respective child-format tuples +insert into child1 values ('AAA', 42); +insert into child2 values ('BBB', 42); +insert into child3 values (42, 'CCC'); + +-- update via parent sees parent-format tuples +update parent set b = b + 1; + +-- delete via parent sees parent-format tuples +delete from parent; + +-- insert into parent sees parent-format tuples +insert into parent values ('AAA', 42); +insert into parent values ('BBB', 42); +insert into parent values ('CCC', 42); + +-- delete from children sees respective child-format tuples +delete from child1; +delete from child2; +delete from child3; + +-- copy into parent sees parent-format tuples +copy parent (a, b) from stdin; +AAA 42 +BBB 42 +CCC 42 +\. + +-- DML affecting parent sees tuples collected from children even if +-- there is no transition table trigger on the children +drop trigger child1_insert_trig on child1; +drop trigger child1_update_trig on child1; +drop trigger child1_delete_trig on child1; +drop trigger child2_insert_trig on child2; +drop trigger child2_update_trig on child2; +drop trigger child2_delete_trig on child2; +drop trigger child3_insert_trig on child3; +drop trigger child3_update_trig on child3; +drop trigger child3_delete_trig on child3; +delete from parent; + +-- copy into parent sees tuples collected from children even if there +-- is no transition-table trigger on the children +copy parent (a, b) from stdin; +AAA 42 +BBB 42 +CCC 42 +\. + +-- insert into parent with a before trigger on a child tuple before +-- insertion, and we capture the newly modified row in parent format +create or replace function intercept_insert() returns trigger language plpgsql as +$$ + begin + new.b = new.b + 1000; + return new; + end; +$$; + +create trigger intercept_insert_child3 + before insert on child3 + for each row execute procedure intercept_insert(); + + +-- insert, parent trigger sees post-modification parent-format tuple +insert into parent values ('AAA', 42), ('BBB', 42), ('CCC', 66); + +-- copy, parent trigger sees post-modification parent-format tuple +copy parent (a, b) from stdin; +AAA 42 +BBB 42 +CCC 234 +\. + +drop table child1, child2, child3, parent; +drop function intercept_insert(); + +-- +-- Verify prohibition of row triggers with transition triggers on +-- partitions +-- +create table parent (a text, b int) partition by list (a); +create table child partition of parent for values in ('AAA'); + +-- adding row trigger with transition table fails +create trigger child_row_trig + after insert on child referencing new table as new_table + for each row execute procedure dump_insert(); + +-- detaching it first works +alter table parent detach partition child; + +create trigger child_row_trig + after insert on child referencing new table as new_table + for each row execute procedure dump_insert(); + +-- but now we're not allowed to reattach it +alter table parent attach partition child for values in ('AAA'); + +-- drop the trigger, and now we're allowed to attach it again +drop trigger child_row_trig on child; +alter table parent attach partition child for values in ('AAA'); + +drop table child, parent; + +-- +-- Verify behavior of statement triggers on (non-partition) +-- inheritance hierarchy with transition tables; similar to the +-- partition case, except there is no rerouting on insertion and child +-- tables can have extra columns +-- + +-- set up inheritance hierarchy with different TupleDescriptors +create table parent (a text, b int); + +-- a child matching parent +create table child1 () inherits (parent); + +-- a child with a different column order +create table child2 (b int, a text); +alter table child2 inherit parent; + +-- a child with an extra column +create table child3 (c text) inherits (parent); + +create trigger parent_insert_trig + after insert on parent referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger parent_update_trig + after update on parent referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger parent_delete_trig + after delete on parent referencing old table as old_table + for each statement execute procedure dump_delete(); + +create trigger child1_insert_trig + after insert on child1 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child1_update_trig + after update on child1 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child1_delete_trig + after delete on child1 referencing old table as old_table + for each statement execute procedure dump_delete(); + +create trigger child2_insert_trig + after insert on child2 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child2_update_trig + after update on child2 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child2_delete_trig + after delete on child2 referencing old table as old_table + for each statement execute procedure dump_delete(); + +create trigger child3_insert_trig + after insert on child3 referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger child3_update_trig + after update on child3 referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger child3_delete_trig + after delete on child3 referencing old table as old_table + for each statement execute procedure dump_delete(); + +-- insert directly into children sees respective child-format tuples +insert into child1 values ('AAA', 42); +insert into child2 values (42, 'BBB'); +insert into child3 values ('CCC', 42, 'foo'); + +-- update via parent sees parent-format tuples +update parent set b = b + 1; + +-- delete via parent sees parent-format tuples +delete from parent; + +-- reinsert values into children for next test... +insert into child1 values ('AAA', 42); +insert into child2 values (42, 'BBB'); +insert into child3 values ('CCC', 42, 'foo'); + +-- delete from children sees respective child-format tuples +delete from child1; +delete from child2; +delete from child3; + +-- copy into parent sees parent-format tuples (no rerouting, so these +-- are really inserted into the parent) +copy parent (a, b) from stdin; +AAA 42 +BBB 42 +CCC 42 +\. + +-- same behavior for copy if there is an index (interesting because rows are +-- captured by a different code path in copy.c if there are indexes) +create index on parent(b); +copy parent (a, b) from stdin; +DDD 42 +\. + +-- DML affecting parent sees tuples collected from children even if +-- there is no transition table trigger on the children +drop trigger child1_insert_trig on child1; +drop trigger child1_update_trig on child1; +drop trigger child1_delete_trig on child1; +drop trigger child2_insert_trig on child2; +drop trigger child2_update_trig on child2; +drop trigger child2_delete_trig on child2; +drop trigger child3_insert_trig on child3; +drop trigger child3_update_trig on child3; +drop trigger child3_delete_trig on child3; +delete from parent; + +drop table child1, child2, child3, parent; + +-- +-- Verify prohibition of row triggers with transition triggers on +-- inheritance children +-- +create table parent (a text, b int); +create table child () inherits (parent); + +-- adding row trigger with transition table fails +create trigger child_row_trig + after insert on child referencing new table as new_table + for each row execute procedure dump_insert(); + +-- disinheriting it first works +alter table child no inherit parent; + +create trigger child_row_trig + after insert on child referencing new table as new_table + for each row execute procedure dump_insert(); + +-- but now we're not allowed to make it inherit anymore +alter table child inherit parent; + +-- drop the trigger, and now we're allowed to make it inherit again +drop trigger child_row_trig on child; +alter table child inherit parent; +*/ +drop table parent; -- YB: child table was commented out above + + +-- +-- Verify behavior of queries with wCTEs, where multiple transition +-- tuplestores can be active at the same time because there are +-- multiple DML statements that might fire triggers with transition +-- tables +-- +create table table1 (a int); +create table table2 (a text); +create trigger table1_trig + after insert on table1 referencing new table as new_table + for each statement execute procedure dump_insert(); + +-- TODO When transition tables ('REFERENCING' clause) are supported in YB enable the tests below. +/* +create trigger table2_trig + after insert on table2 referencing new table as new_table + for each statement execute procedure dump_insert(); + +with wcte as (insert into table1 values (42)) + insert into table2 values ('hello world'); + +with wcte as (insert into table1 values (43)) + insert into table1 values (44); + +select * from table1; +select * from table2; + +drop table table1; +drop table table2; + +-- +-- Verify behavior of INSERT ... ON CONFLICT DO UPDATE ... with +-- transition tables. +-- + +create table my_table (a int primary key, b text); +create trigger my_table_insert_trig + after insert on my_table referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger my_table_update_trig + after update on my_table referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); + +-- inserts only +insert into my_table values (1, 'AAA'), (2, 'BBB') + on conflict (a) do + update set b = my_table.b || ':' || excluded.b; + +-- mixture of inserts and updates +insert into my_table values (1, 'AAA'), (2, 'BBB'), (3, 'CCC'), (4, 'DDD') + on conflict (a) do + update set b = my_table.b || ':' || excluded.b; + +-- updates only +insert into my_table values (3, 'CCC'), (4, 'DDD') + on conflict (a) do + update set b = my_table.b || ':' || excluded.b; + +-- +-- now using a partitioned table +-- + +create table iocdu_tt_parted (a int primary key, b text) partition by list (a); +create table iocdu_tt_parted1 partition of iocdu_tt_parted for values in (1); +create table iocdu_tt_parted2 partition of iocdu_tt_parted for values in (2); +create table iocdu_tt_parted3 partition of iocdu_tt_parted for values in (3); +create table iocdu_tt_parted4 partition of iocdu_tt_parted for values in (4); +create trigger iocdu_tt_parted_insert_trig + after insert on iocdu_tt_parted referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger iocdu_tt_parted_update_trig + after update on iocdu_tt_parted referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); + +-- inserts only +insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB') + on conflict (a) do + update set b = iocdu_tt_parted.b || ':' || excluded.b; + +-- mixture of inserts and updates +insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB'), (3, 'CCC'), (4, 'DDD') + on conflict (a) do + update set b = iocdu_tt_parted.b || ':' || excluded.b; + +-- updates only +insert into iocdu_tt_parted values (3, 'CCC'), (4, 'DDD') + on conflict (a) do + update set b = iocdu_tt_parted.b || ':' || excluded.b; + +drop table iocdu_tt_parted; + +-- +-- Verify that you can't create a trigger with transition tables for +-- more than one event. +-- + +create trigger my_table_multievent_trig + after insert or update on my_table referencing new table as new_table + for each statement execute procedure dump_insert(); + +-- +-- Verify that you can't create a trigger with transition tables with +-- a column list. +-- + +create trigger my_table_col_update_trig + after update of b on my_table referencing new table as new_table + for each statement execute procedure dump_insert(); + +drop table my_table; + +-- +-- Test firing of triggers with transition tables by foreign key cascades +-- + +create table refd_table (a int primary key, b text); +create table trig_table (a int, b text, + foreign key (a) references refd_table on update cascade on delete cascade +); + +create trigger trig_table_before_trig + before insert or update or delete on trig_table + for each statement execute procedure trigger_func('trig_table'); +create trigger trig_table_insert_trig + after insert on trig_table referencing new table as new_table + for each statement execute procedure dump_insert(); +create trigger trig_table_update_trig + after update on trig_table referencing old table as old_table new table as new_table + for each statement execute procedure dump_update(); +create trigger trig_table_delete_trig + after delete on trig_table referencing old table as old_table + for each statement execute procedure dump_delete(); + +insert into refd_table values + (1, 'one'), + (2, 'two'), + (3, 'three'); +insert into trig_table values + (1, 'one a'), + (1, 'one b'), + (2, 'two a'), + (2, 'two b'), + (3, 'three a'), + (3, 'three b'); + +update refd_table set a = 11 where b = 'one'; + +select * from trig_table; + +delete from refd_table where length(b) = 3; + +select * from trig_table; + +drop table refd_table, trig_table; + +-- +-- self-referential FKs are even more fun +-- + +create table self_ref (a int primary key, + b int references self_ref(a) on delete cascade); + +create trigger self_ref_before_trig + before delete on self_ref + for each statement execute procedure trigger_func('self_ref'); +create trigger self_ref_r_trig + after delete on self_ref referencing old table as old_table + for each row execute procedure dump_delete(); +create trigger self_ref_s_trig + after delete on self_ref referencing old table as old_table + for each statement execute procedure dump_delete(); + +insert into self_ref values (1, null), (2, 1), (3, 2); + +delete from self_ref where a = 1; + +-- without AR trigger, cascaded deletes all end up in one transition table +drop trigger self_ref_r_trig on self_ref; + +insert into self_ref values (1, null), (2, 1), (3, 2), (4, 3); + +delete from self_ref where a = 1; + +drop table self_ref; +*/ + +-- cleanup +drop function dump_insert(); +drop function dump_update(); +drop function dump_delete(); + +-- +-- Rows per transaction should be disabled if table contains non Referential Integrity triggers. +-- +CREATE TABLE tbl(k INT PRIMARY KEY); +CREATE TABLE shadow_tbl(k INT PRIMARY KEY); + +CREATE OR REPLACE FUNCTION trigger_func() RETURNS trigger AS $$ +BEGIN + INSERT INTO shadow_tbl VALUES(NEW.k); + RETURN NEW; +END; $$ +LANGUAGE 'plpgsql'; + +CREATE TRIGGER tbl_insert_trigger AFTER INSERT + ON tbl FOR EACH ROW EXECUTE PROCEDURE trigger_func(); + +-- A warning should be shown disabling the ROWS_PER_TRANSACTION value. +COPY tbl FROM STDIN WITH (ROWS_PER_TRANSACTION 2); +1 +2 +3 +4 +5 +6 +\. + +DROP TRIGGER tbl_insert_trigger ON tbl; +DROP TABLE shadow_tbl; +DROP TABLE tbl; + +-- +-- Tests for CREATE OR REPLACE TRIGGER +-- +create table my_table (id integer); + +create function funcA() returns trigger as $$ +begin + raise notice 'hello from funcA'; + return null; +end; $$ language plpgsql; + +create function funcB() returns trigger as $$ +begin + raise notice 'hello from funcB'; + return null; +end; $$ language plpgsql; + +create trigger my_trig + after insert on my_table + for each row execute procedure funcA(); + +create trigger my_trig + before insert on my_table + for each row execute procedure funcB(); -- should fail + +insert into my_table values (1); + +create or replace trigger my_trig + before insert on my_table + for each row execute procedure funcB(); -- OK + +insert into my_table values (2); -- this insert should become a no-op + +table my_table; + +drop table my_table; + +-- test CREATE OR REPLACE TRIGGER on partition table +create table parted_trig (a int) partition by range (a); +create table parted_trig_1 partition of parted_trig + for values from (0) to (1000) partition by range (a); +create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100); +create table parted_trig_2 partition of parted_trig for values from (1000) to (2000); +create table default_parted_trig partition of parted_trig default; + +-- test that trigger can be replaced by another one +-- at the same level of partition table +create or replace trigger my_trig + after insert on parted_trig + for each row execute procedure funcA(); +insert into parted_trig (a) values (50); +create or replace trigger my_trig + after insert on parted_trig + for each row execute procedure funcB(); +insert into parted_trig (a) values (50); + +-- test that child trigger cannot be replaced directly +create or replace trigger my_trig + after insert on parted_trig + for each row execute procedure funcA(); +insert into parted_trig (a) values (50); +create or replace trigger my_trig + after insert on parted_trig_1 + for each row execute procedure funcB(); -- should fail +insert into parted_trig (a) values (50); +drop trigger my_trig on parted_trig; +insert into parted_trig (a) values (50); + +-- test that user trigger can be overwritten by one defined at upper level +create trigger my_trig + after insert on parted_trig_1 + for each row execute procedure funcA(); +insert into parted_trig (a) values (50); +create trigger my_trig + after insert on parted_trig + for each row execute procedure funcB(); -- should fail +insert into parted_trig (a) values (50); +create or replace trigger my_trig + after insert on parted_trig + for each row execute procedure funcB(); +insert into parted_trig (a) values (50); + +-- cleanup +drop table parted_trig; +drop function funcA(); +drop function funcB(); + +-- Leave around some objects for other tests +create table trigger_parted (a int primary key) partition by list (a); +create function trigger_parted_trigfunc() returns trigger language plpgsql as + $$ begin end; $$; +create trigger aft_row after insert or update on trigger_parted + for each row execute function trigger_parted_trigfunc(); +create table trigger_parted_p1 partition of trigger_parted for values in (1) + partition by list (a); +create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1); +create table trigger_parted_p2 partition of trigger_parted for values in (2) + partition by list (a); +create table trigger_parted_p2_2 partition of trigger_parted_p2 for values in (2); +alter table only trigger_parted_p2 disable trigger aft_row; +alter table trigger_parted_p2_2 enable always trigger aft_row; +-- Test trigger renaming on partitioned tables +create table grandparent (id int, primary key (id)) partition by range (id); +create table middle partition of grandparent for values from (1) to (10) +partition by range (id); +create table chi partition of middle for values from (1) to (5); +create table cho partition of middle for values from (6) to (10); +create function f () returns trigger as +$$ begin return new; end; $$ +language plpgsql; +create trigger a after insert on grandparent +for each row execute procedure f(); + +alter trigger a on grandparent rename to b; +select tgrelid::regclass, tgname, +(select tgname from pg_trigger tr where tr.oid = pg_trigger.tgparentid) parent_tgname +from pg_trigger where tgrelid in (select relid from pg_partition_tree('grandparent')) +order by tgname, tgrelid::regclass::text COLLATE "C"; +alter trigger a on only grandparent rename to b; -- ONLY not supported +alter trigger b on middle rename to c; -- can't rename trigger on partition +create trigger c after insert on middle +for each row execute procedure f(); +alter trigger b on grandparent rename to c; + +-- Rename cascading does not affect statement triggers +create trigger p after insert on grandparent for each statement execute function f(); +create trigger p after insert on middle for each statement execute function f(); +alter trigger p on grandparent rename to q; +select tgrelid::regclass, tgname, +(select tgname from pg_trigger tr where tr.oid = pg_trigger.tgparentid) parent_tgname +from pg_trigger where tgrelid in (select relid from pg_partition_tree('grandparent')) +order by tgname, tgrelid::regclass::text COLLATE "C"; + +drop table grandparent; + +-- Trigger renaming does not recurse on legacy inheritance +create table parent (a int); +create table child () inherits (parent); +create trigger parenttrig after insert on parent +for each row execute procedure f(); +create trigger parenttrig after insert on child +for each row execute procedure f(); +alter trigger parenttrig on parent rename to anothertrig; +\d+ child + +drop table parent, child; +drop function f(); diff --git a/src/test/regress/sql/yb.port.truncate.sql b/src/test/regress/sql/yb.port.truncate.sql new file mode 100644 index 0000000000000..00eee527376f3 --- /dev/null +++ b/src/test/regress/sql/yb.port.truncate.sql @@ -0,0 +1,353 @@ +-- Test basic TRUNCATE functionality. +CREATE TABLE truncate_a (col1 integer primary key); +INSERT INTO truncate_a VALUES (1); +INSERT INTO truncate_a VALUES (2); +SELECT * FROM truncate_a; +-- Roll truncate back +BEGIN; +TRUNCATE truncate_a; +ROLLBACK; +SELECT * FROM truncate_a; -- YB: TRUNCATE is currently not rolled back +INSERT INTO truncate_a VALUES (1); -- YB: add rows back +INSERT INTO truncate_a VALUES (2); -- YB: add rows back +-- Commit the truncate this time +BEGIN; +TRUNCATE truncate_a; +COMMIT; +SELECT * FROM truncate_a; + +-- Test foreign-key checks +CREATE TABLE trunc_b (a int REFERENCES truncate_a); +CREATE TABLE trunc_c (a serial PRIMARY KEY); +CREATE TABLE trunc_d (a int REFERENCES trunc_c); +CREATE TABLE trunc_e (a int REFERENCES truncate_a, b int REFERENCES trunc_c); + +TRUNCATE TABLE truncate_a; -- fail +TRUNCATE TABLE truncate_a,trunc_b; -- fail +TRUNCATE TABLE truncate_a,trunc_b,trunc_e; -- ok +TRUNCATE TABLE truncate_a,trunc_e; -- fail +TRUNCATE TABLE trunc_c; -- fail +TRUNCATE TABLE trunc_c,trunc_d; -- fail +TRUNCATE TABLE trunc_c,trunc_d,trunc_e; -- ok +TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a; -- fail +TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a,trunc_b; -- ok + +TRUNCATE TABLE truncate_a RESTRICT; -- fail +TRUNCATE TABLE truncate_a CASCADE; -- ok + +-- circular references +ALTER TABLE truncate_a ADD FOREIGN KEY (col1) REFERENCES trunc_c; + +-- Add some data to verify that truncating actually works ... +INSERT INTO trunc_c VALUES (1); +INSERT INTO truncate_a VALUES (1); +INSERT INTO trunc_b VALUES (1); +INSERT INTO trunc_d VALUES (1); +INSERT INTO trunc_e VALUES (1,1); +TRUNCATE TABLE trunc_c; +TRUNCATE TABLE trunc_c,truncate_a; +TRUNCATE TABLE trunc_c,truncate_a,trunc_d; +TRUNCATE TABLE trunc_c,truncate_a,trunc_d,trunc_e; +TRUNCATE TABLE trunc_c,truncate_a,trunc_d,trunc_e,trunc_b; + +-- Verify that truncating did actually work +SELECT * FROM truncate_a + UNION ALL + SELECT * FROM trunc_c + UNION ALL + SELECT * FROM trunc_b + UNION ALL + SELECT * FROM trunc_d; +SELECT * FROM trunc_e; + +-- Add data again to test TRUNCATE ... CASCADE +INSERT INTO trunc_c VALUES (1); +INSERT INTO truncate_a VALUES (1); +INSERT INTO trunc_b VALUES (1); +INSERT INTO trunc_d VALUES (1); +INSERT INTO trunc_e VALUES (1,1); + +TRUNCATE TABLE trunc_c CASCADE; -- ok + +SELECT * FROM truncate_a + UNION ALL + SELECT * FROM trunc_c + UNION ALL + SELECT * FROM trunc_b + UNION ALL + SELECT * FROM trunc_d; +SELECT * FROM trunc_e; + +DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE; + +-- Test TRUNCATE with inheritance + +CREATE TABLE trunc_f (col1 integer primary key); +INSERT INTO trunc_f VALUES (1); +INSERT INTO trunc_f VALUES (2); + +CREATE TABLE trunc_fa (col2a text) INHERITS (trunc_f); +INSERT INTO trunc_fa VALUES (3, 'three'); + +CREATE TABLE trunc_fb (col2b int) INHERITS (trunc_f); +INSERT INTO trunc_fb VALUES (4, 444); + +CREATE TABLE trunc_faa (col3 text) INHERITS (trunc_fa); +INSERT INTO trunc_faa VALUES (5, 'five', 'FIVE'); +CREATE OR REPLACE PROCEDURE yb_reset_trunc_f() +LANGUAGE plpgsql +AS $$ +BEGIN + DELETE FROM trunc_f; + INSERT INTO trunc_f VALUES (1); + INSERT INTO trunc_f VALUES (2); + INSERT INTO trunc_fa VALUES (3, 'three'); + INSERT INTO trunc_fb VALUES (4, 444); + INSERT INTO trunc_faa VALUES (5, 'five', 'FIVE'); +END; +$$; + +BEGIN; +SELECT * FROM trunc_f; +TRUNCATE trunc_f; +SELECT * FROM trunc_f; +ROLLBACK; + +CALL yb_reset_trunc_f(); -- YB: TRUNCATE is nontransactional so cannot be rolled back + +BEGIN; +SELECT * FROM trunc_f; +TRUNCATE ONLY trunc_f; +SELECT * FROM trunc_f; +ROLLBACK; + +CALL yb_reset_trunc_f(); -- YB: TRUNCATE is nontransactional so cannot be rolled back + +BEGIN; +SELECT * FROM trunc_f; +SELECT * FROM trunc_fa; +SELECT * FROM trunc_faa; +TRUNCATE ONLY trunc_fb, ONLY trunc_fa; +SELECT * FROM trunc_f; +SELECT * FROM trunc_fa; +SELECT * FROM trunc_faa; +ROLLBACK; + +CALL yb_reset_trunc_f(); -- YB: TRUNCATE is nontransactional so cannot be rolled back + +BEGIN; +SELECT * FROM trunc_f; +SELECT * FROM trunc_fa; +SELECT * FROM trunc_faa; +TRUNCATE ONLY trunc_fb, trunc_fa; +SELECT * FROM trunc_f; +SELECT * FROM trunc_fa; +SELECT * FROM trunc_faa; +ROLLBACK; + +\set VERBOSITY terse \\ -- YB: suppress cascade details +DROP TABLE trunc_f CASCADE; +\set VERBOSITY default \\ -- YB + +-- Test ON TRUNCATE triggers + +CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text); +CREATE TABLE trunc_trigger_log (tgop text, tglevel text, tgwhen text, + tgargv text, tgtable name, rowcount bigint); + +CREATE FUNCTION trunctrigger() RETURNS trigger as $$ +declare c bigint; +begin + execute 'select count(*) from ' || quote_ident(tg_table_name) into c; + insert into trunc_trigger_log values + (TG_OP, TG_LEVEL, TG_WHEN, TG_ARGV[0], tg_table_name, c); + return null; +end; +$$ LANGUAGE plpgsql; + +-- basic before trigger +INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux'); + +CREATE TRIGGER t +BEFORE TRUNCATE ON trunc_trigger_test +FOR EACH STATEMENT +EXECUTE PROCEDURE trunctrigger('before trigger truncate'); + +SELECT count(*) as "Row count in test table" FROM trunc_trigger_test; +SELECT * FROM trunc_trigger_log; +TRUNCATE trunc_trigger_test; +SELECT count(*) as "Row count in test table" FROM trunc_trigger_test; +SELECT * FROM trunc_trigger_log; + +DROP TRIGGER t ON trunc_trigger_test; + +truncate trunc_trigger_log; + +-- same test with an after trigger +INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux'); + +CREATE TRIGGER tt +AFTER TRUNCATE ON trunc_trigger_test +FOR EACH STATEMENT +EXECUTE PROCEDURE trunctrigger('after trigger truncate'); + +SELECT count(*) as "Row count in test table" FROM trunc_trigger_test; +SELECT * FROM trunc_trigger_log; +TRUNCATE trunc_trigger_test; +SELECT count(*) as "Row count in test table" FROM trunc_trigger_test; +SELECT * FROM trunc_trigger_log; + +DROP TABLE trunc_trigger_test; +DROP TABLE trunc_trigger_log; + +DROP FUNCTION trunctrigger(); + +-- test TRUNCATE ... RESTART IDENTITY +CREATE SEQUENCE truncate_a_id1 START WITH 33; +CREATE TABLE truncate_a (id serial, + id1 integer default nextval('truncate_a_id1'), PRIMARY KEY (id ASC)); -- YB: add pk for query ordering +ALTER SEQUENCE truncate_a_id1 OWNED BY truncate_a.id1; + +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + +TRUNCATE truncate_a; + +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + +TRUNCATE truncate_a RESTART IDENTITY; + +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + +CREATE TABLE truncate_b (id int GENERATED ALWAYS AS IDENTITY (START WITH 44), PRIMARY KEY (id ASC)); -- YB: add pk for query ordering + +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + +TRUNCATE truncate_b; + +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + +TRUNCATE truncate_b RESTART IDENTITY; + +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + +/* YB: uncomment when TRUNCATE rollback is supported +-- check rollback of a RESTART IDENTITY operation +BEGIN; +TRUNCATE truncate_a RESTART IDENTITY; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; +ROLLBACK; +*/ -- YB +INSERT INTO truncate_a DEFAULT VALUES; +INSERT INTO truncate_a DEFAULT VALUES; +SELECT * FROM truncate_a; + +DROP TABLE truncate_a; + +SELECT nextval('truncate_a_id1'); -- fail, seq should have been dropped + +-- partitioned table +CREATE TABLE truncparted (a int, b char) PARTITION BY LIST (a); +-- error, can't truncate a partitioned table +TRUNCATE ONLY truncparted; +CREATE TABLE truncparted1 PARTITION OF truncparted FOR VALUES IN (1); +INSERT INTO truncparted VALUES (1, 'a'); +-- error, must truncate partitions +TRUNCATE ONLY truncparted; +TRUNCATE truncparted; +DROP TABLE truncparted; + +-- foreign key on partitioned table: partition key is referencing column. +-- Make sure truncate did execute on all tables +CREATE FUNCTION tp_ins_data() RETURNS void LANGUAGE plpgsql AS $$ + BEGIN + INSERT INTO truncprim VALUES (1), (100), (150); + INSERT INTO truncpart VALUES (1), (100), (150); + END +$$; +CREATE FUNCTION tp_chk_data(OUT pktb regclass, OUT pkval int, OUT fktb regclass, OUT fkval int) + RETURNS SETOF record LANGUAGE plpgsql AS $$ + BEGIN + RETURN QUERY SELECT + pk.tableoid::regclass, pk.a, fk.tableoid::regclass, fk.a + FROM truncprim pk FULL JOIN truncpart fk USING (a) + ORDER BY 2, 4; + END +$$; +CREATE TABLE truncprim (a int PRIMARY KEY); +CREATE TABLE truncpart (a int REFERENCES truncprim) + PARTITION BY RANGE (a); +CREATE TABLE truncpart_1 PARTITION OF truncpart FOR VALUES FROM (0) TO (100); +CREATE TABLE truncpart_2 PARTITION OF truncpart FOR VALUES FROM (100) TO (200) + PARTITION BY RANGE (a); +CREATE TABLE truncpart_2_1 PARTITION OF truncpart_2 FOR VALUES FROM (100) TO (150); +CREATE TABLE truncpart_2_d PARTITION OF truncpart_2 DEFAULT; + +TRUNCATE TABLE truncprim; -- should fail + +select tp_ins_data(); +-- should truncate everything +TRUNCATE TABLE truncprim, truncpart; +select * from tp_chk_data(); + +select tp_ins_data(); +-- should truncate everything +TRUNCATE TABLE truncprim CASCADE; +SELECT * FROM tp_chk_data(); + +SELECT tp_ins_data(); +-- should truncate all partitions +TRUNCATE TABLE truncpart; +SELECT * FROM tp_chk_data(); +DROP TABLE truncprim, truncpart; +DROP FUNCTION tp_ins_data(), tp_chk_data(); + +-- test cascade when referencing a partitioned table +CREATE TABLE trunc_a (a INT PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE trunc_a1 PARTITION OF trunc_a FOR VALUES FROM (0) TO (10); +CREATE TABLE trunc_a2 PARTITION OF trunc_a FOR VALUES FROM (10) TO (20) + PARTITION BY RANGE (a); +CREATE TABLE trunc_a21 PARTITION OF trunc_a2 FOR VALUES FROM (10) TO (12); +CREATE TABLE trunc_a22 PARTITION OF trunc_a2 FOR VALUES FROM (12) TO (16); +CREATE TABLE trunc_a2d PARTITION OF trunc_a2 DEFAULT; +CREATE TABLE trunc_a3 PARTITION OF trunc_a FOR VALUES FROM (20) TO (30); +INSERT INTO trunc_a VALUES (0), (5), (10), (15), (20), (25); + +-- truncate a partition cascading to a table +CREATE TABLE ref_b ( + b INT PRIMARY KEY, + a INT REFERENCES trunc_a(a) ON DELETE CASCADE +); +INSERT INTO ref_b VALUES (10, 0), (50, 5), (100, 10), (150, 15); + +TRUNCATE TABLE trunc_a1 CASCADE; +SELECT a FROM ref_b; + +DROP TABLE ref_b; + +-- truncate a partition cascading to a partitioned table +CREATE TABLE ref_c ( + c INT PRIMARY KEY, + a INT REFERENCES trunc_a(a) ON DELETE CASCADE +) PARTITION BY RANGE (c); +CREATE TABLE ref_c1 PARTITION OF ref_c FOR VALUES FROM (100) TO (200); +CREATE TABLE ref_c2 PARTITION OF ref_c FOR VALUES FROM (200) TO (300); +INSERT INTO ref_c VALUES (100, 10), (150, 15), (200, 20), (250, 25); + +TRUNCATE TABLE trunc_a21 CASCADE; +SELECT a as "from table ref_c" FROM ref_c; +SELECT a as "from table trunc_a" FROM trunc_a ORDER BY a; + +DROP TABLE trunc_a, ref_c; diff --git a/src/test/regress/sql/yb.port.tsdicts.sql b/src/test/regress/sql/yb.port.tsdicts.sql new file mode 100644 index 0000000000000..60906f6549a24 --- /dev/null +++ b/src/test/regress/sql/yb.port.tsdicts.sql @@ -0,0 +1,203 @@ +--Test text search dictionaries and configurations + +-- Test ISpell dictionary with ispell affix file +CREATE TEXT SEARCH DICTIONARY ispell ( + Template=ispell, + DictFile=ispell_sample, + AffFile=ispell_sample +); + +SELECT ts_lexize('ispell', 'skies'); +SELECT ts_lexize('ispell', 'bookings'); +SELECT ts_lexize('ispell', 'booking'); +SELECT ts_lexize('ispell', 'foot'); +SELECT ts_lexize('ispell', 'foots'); +SELECT ts_lexize('ispell', 'rebookings'); +SELECT ts_lexize('ispell', 'rebooking'); +SELECT ts_lexize('ispell', 'rebook'); +SELECT ts_lexize('ispell', 'unbookings'); +SELECT ts_lexize('ispell', 'unbooking'); +SELECT ts_lexize('ispell', 'unbook'); + +SELECT ts_lexize('ispell', 'footklubber'); +SELECT ts_lexize('ispell', 'footballklubber'); +SELECT ts_lexize('ispell', 'ballyklubber'); +SELECT ts_lexize('ispell', 'footballyklubber'); + +-- Test ISpell dictionary with hunspell affix file +CREATE TEXT SEARCH DICTIONARY hunspell ( + Template=ispell, + DictFile=ispell_sample, + AffFile=hunspell_sample +); + +SELECT ts_lexize('hunspell', 'skies'); +SELECT ts_lexize('hunspell', 'bookings'); +SELECT ts_lexize('hunspell', 'booking'); +SELECT ts_lexize('hunspell', 'foot'); +SELECT ts_lexize('hunspell', 'foots'); +SELECT ts_lexize('hunspell', 'rebookings'); +SELECT ts_lexize('hunspell', 'rebooking'); +SELECT ts_lexize('hunspell', 'rebook'); +SELECT ts_lexize('hunspell', 'unbookings'); +SELECT ts_lexize('hunspell', 'unbooking'); +SELECT ts_lexize('hunspell', 'unbook'); + +SELECT ts_lexize('hunspell', 'footklubber'); +SELECT ts_lexize('hunspell', 'footballklubber'); +SELECT ts_lexize('hunspell', 'ballyklubber'); +SELECT ts_lexize('hunspell', 'footballyklubber'); + +-- Test ISpell dictionary with hunspell affix file with FLAG long parameter +CREATE TEXT SEARCH DICTIONARY hunspell_long ( + Template=ispell, + DictFile=hunspell_sample_long, + AffFile=hunspell_sample_long +); + +SELECT ts_lexize('hunspell_long', 'skies'); +SELECT ts_lexize('hunspell_long', 'bookings'); +SELECT ts_lexize('hunspell_long', 'booking'); +SELECT ts_lexize('hunspell_long', 'foot'); +SELECT ts_lexize('hunspell_long', 'foots'); +SELECT ts_lexize('hunspell_long', 'rebookings'); +SELECT ts_lexize('hunspell_long', 'rebooking'); +SELECT ts_lexize('hunspell_long', 'rebook'); +SELECT ts_lexize('hunspell_long', 'unbookings'); +SELECT ts_lexize('hunspell_long', 'unbooking'); +SELECT ts_lexize('hunspell_long', 'unbook'); +SELECT ts_lexize('hunspell_long', 'booked'); + +SELECT ts_lexize('hunspell_long', 'footklubber'); +SELECT ts_lexize('hunspell_long', 'footballklubber'); +SELECT ts_lexize('hunspell_long', 'ballyklubber'); +SELECT ts_lexize('hunspell_long', 'ballsklubber'); +SELECT ts_lexize('hunspell_long', 'footballyklubber'); +SELECT ts_lexize('hunspell_long', 'ex-machina'); + +-- Test ISpell dictionary with hunspell affix file with FLAG num parameter +CREATE TEXT SEARCH DICTIONARY hunspell_num ( + Template=ispell, + DictFile=hunspell_sample_num, + AffFile=hunspell_sample_num +); + +SELECT ts_lexize('hunspell_num', 'skies'); +SELECT ts_lexize('hunspell_num', 'sk'); +SELECT ts_lexize('hunspell_num', 'bookings'); +SELECT ts_lexize('hunspell_num', 'booking'); +SELECT ts_lexize('hunspell_num', 'foot'); +SELECT ts_lexize('hunspell_num', 'foots'); +SELECT ts_lexize('hunspell_num', 'rebookings'); +SELECT ts_lexize('hunspell_num', 'rebooking'); +SELECT ts_lexize('hunspell_num', 'rebook'); +SELECT ts_lexize('hunspell_num', 'unbookings'); +SELECT ts_lexize('hunspell_num', 'unbooking'); +SELECT ts_lexize('hunspell_num', 'unbook'); +SELECT ts_lexize('hunspell_num', 'booked'); + +SELECT ts_lexize('hunspell_num', 'footklubber'); +SELECT ts_lexize('hunspell_num', 'footballklubber'); +SELECT ts_lexize('hunspell_num', 'ballyklubber'); +SELECT ts_lexize('hunspell_num', 'footballyklubber'); + +-- Synonym dictionary +CREATE TEXT SEARCH DICTIONARY synonym ( + Template=synonym, + Synonyms=synonym_sample +); + +SELECT ts_lexize('synonym', 'PoStGrEs'); +SELECT ts_lexize('synonym', 'Gogle'); +SELECT ts_lexize('synonym', 'indices'); + +-- Create and simple test thesaurus dictionary +-- More tests in configuration checks because ts_lexize() +-- cannot pass more than one word to thesaurus. +CREATE TEXT SEARCH DICTIONARY thesaurus ( + Template=thesaurus, + DictFile=thesaurus_sample, + Dictionary=english_stem +); + +SELECT ts_lexize('thesaurus', 'one'); + +-- Test ispell dictionary in configuration +CREATE TEXT SEARCH CONFIGURATION ispell_tst ( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION ispell_tst ALTER MAPPING FOR + word, numword, asciiword, hword, numhword, asciihword, hword_part, hword_numpart, hword_asciipart + WITH ispell, english_stem; + +SELECT to_tsvector('ispell_tst', 'Booking the skies after rebookings for footballklubber from a foot'); +SELECT to_tsquery('ispell_tst', 'footballklubber'); +SELECT to_tsquery('ispell_tst', 'footballyklubber:b & rebookings:A & sky'); + +-- Test ispell dictionary with hunspell affix in configuration +CREATE TEXT SEARCH CONFIGURATION hunspell_tst ( + COPY=ispell_tst +); + +ALTER TEXT SEARCH CONFIGURATION hunspell_tst ALTER MAPPING + REPLACE ispell WITH hunspell; + +SELECT to_tsvector('hunspell_tst', 'Booking the skies after rebookings for footballklubber from a foot'); +SELECT to_tsquery('hunspell_tst', 'footballklubber'); +SELECT to_tsquery('hunspell_tst', 'footballyklubber:b & rebookings:A & sky'); + +SELECT to_tsquery('hunspell_tst', 'footballyklubber:b <-> sky'); +SELECT phraseto_tsquery('hunspell_tst', 'footballyklubber sky'); + +-- Test ispell dictionary with hunspell affix with FLAG long in configuration +ALTER TEXT SEARCH CONFIGURATION hunspell_tst ALTER MAPPING + REPLACE hunspell WITH hunspell_long; + +SELECT to_tsvector('hunspell_tst', 'Booking the skies after rebookings for footballklubber from a foot'); +SELECT to_tsquery('hunspell_tst', 'footballklubber'); +SELECT to_tsquery('hunspell_tst', 'footballyklubber:b & rebookings:A & sky'); + +-- Test ispell dictionary with hunspell affix with FLAG num in configuration +ALTER TEXT SEARCH CONFIGURATION hunspell_tst ALTER MAPPING + REPLACE hunspell_long WITH hunspell_num; + +SELECT to_tsvector('hunspell_tst', 'Booking the skies after rebookings for footballklubber from a foot'); +SELECT to_tsquery('hunspell_tst', 'footballklubber'); +SELECT to_tsquery('hunspell_tst', 'footballyklubber:b & rebookings:A & sky'); + +-- Test synonym dictionary in configuration +CREATE TEXT SEARCH CONFIGURATION synonym_tst ( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION synonym_tst ALTER MAPPING FOR + asciiword, hword_asciipart, asciihword + WITH synonym, english_stem; + +SELECT to_tsvector('synonym_tst', 'Postgresql is often called as postgres or pgsql and pronounced as postgre'); +SELECT to_tsvector('synonym_tst', 'Most common mistake is to write Gogle instead of Google'); +SELECT to_tsvector('synonym_tst', 'Indexes or indices - Which is right plural form of index?'); +SELECT to_tsquery('synonym_tst', 'Index & indices'); + +-- test thesaurus in configuration +-- see thesaurus_sample.ths to understand 'odd' resulting tsvector +CREATE TEXT SEARCH CONFIGURATION thesaurus_tst ( + COPY=synonym_tst +); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR + asciiword, hword_asciipart, asciihword + WITH synonym, thesaurus, english_stem; + +SELECT to_tsvector('thesaurus_tst', 'one postgres one two one two three one'); +SELECT to_tsvector('thesaurus_tst', 'Supernovae star is very new star and usually called supernovae (abbreviation SN)'); +SELECT to_tsvector('thesaurus_tst', 'Booking tickets is looking like a booking a tickets'); + +-- invalid: non-lowercase quoted identifiers +CREATE TEXT SEARCH DICTIONARY tsdict_case +( + Template = ispell, + "DictFile" = ispell_sample, + "AffFile" = ispell_sample +); diff --git a/src/test/regress/sql/yb.port.tsearch.sql b/src/test/regress/sql/yb.port.tsearch.sql new file mode 100644 index 0000000000000..e751f87937592 --- /dev/null +++ b/src/test/regress/sql/yb.port.tsearch.sql @@ -0,0 +1,679 @@ +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +-- +-- Sanity checks for text search catalogs +-- +-- NB: we assume the oidjoins test will have caught any dangling links, +-- that is OID or REGPROC fields that are not zero and do not match some +-- row in the linked-to table. However, if we want to enforce that a link +-- field can't be 0, we have to check it here. + +-- Find unexpected zero link entries + +SELECT oid, prsname +FROM pg_ts_parser +WHERE prsnamespace = 0 OR prsstart = 0 OR prstoken = 0 OR prsend = 0 OR + -- prsheadline is optional + prslextype = 0; + +SELECT oid, dictname +FROM pg_ts_dict +WHERE dictnamespace = 0 OR dictowner = 0 OR dicttemplate = 0; + +SELECT oid, tmplname +FROM pg_ts_template +WHERE tmplnamespace = 0 OR tmpllexize = 0; -- tmplinit is optional + +SELECT oid, cfgname +FROM pg_ts_config +WHERE cfgnamespace = 0 OR cfgowner = 0 OR cfgparser = 0; + +SELECT mapcfg, maptokentype, mapseqno +FROM pg_ts_config_map +WHERE mapcfg = 0 OR mapdict = 0; + +-- Look for pg_ts_config_map entries that aren't one of parser's token types +SELECT * FROM + ( SELECT oid AS cfgid, (ts_token_type(cfgparser)).tokid AS tokid + FROM pg_ts_config ) AS tt +RIGHT JOIN pg_ts_config_map AS m + ON (tt.cfgid=m.mapcfg AND tt.tokid=m.maptokentype) +WHERE + tt.cfgid IS NULL OR tt.tokid IS NULL; + +-- Load some test data +CREATE TABLE test_tsvector( + t text, + a tsvector +); + +\set filename :abs_srcdir '/data/tsearch.data' +COPY test_tsvector FROM :'filename'; + +ANALYZE test_tsvector; + +-- test basic text search behavior without indexes, then with + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt'; +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)'; +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*'; +SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}'); +SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'wd:A'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'wd:D'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!wd:A'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!wd:D'; + +create index wowidx on test_tsvector using gist (a); +-- YB: port remaining queries when gist is supported + +CREATE INDEX wowidx ON test_tsvector USING gin (a); + +SET enable_seqscan=OFF; +SET yb_test_ybgin_disable_cost_factor = 0.5; -- YB: enable_seqscan=OFF is not enough to force index scan +-- GIN only supports bitmapscan, so no need to test plain indexscan +-- YB note: ybgin is the opposite: it supports indexscan, not bitmapscan + +explain (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt'; +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)'; +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*'; +SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}'); +SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'wd:A'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'wd:D'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!wd:A'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!wd:D'; + +-- Test optimization of non-empty GIN_SEARCH_MODE_ALL queries +EXPLAIN (COSTS OFF) +SELECT count(*) FROM test_tsvector WHERE a @@ '!qh'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!qh'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr' AND a @@ '!qh'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr' AND a @@ '!qh'; + +RESET yb_test_ybgin_disable_cost_factor; +RESET enable_seqscan; + +INSERT INTO test_tsvector VALUES ('???', 'DFG:1A,2B,6C,10 FGH'); +SELECT * FROM ts_stat('SELECT a FROM test_tsvector') ORDER BY ndoc DESC, nentry DESC, word LIMIT 10; +SELECT * FROM ts_stat('SELECT a FROM test_tsvector', 'AB') ORDER BY ndoc DESC, nentry DESC, word; + +--dictionaries and to_tsvector + +SELECT ts_lexize('english_stem', 'skies'); +SELECT ts_lexize('english_stem', 'identity'); + +SELECT * FROM ts_token_type('default'); + +SELECT * FROM ts_parse('default', '345 qwe@efd.r '' http://www.com/ http://aew.werc.ewr/?ad=qwe&dw 1aew.werc.ewr/?ad=qwe&dw 2aew.werc.ewr http://3aew.werc.ewr/?ad=qwe&dw http://4aew.werc.ewr http://5aew.werc.ewr:8100/? ad=qwe&dw 6aew.werc.ewr:8100/?ad=qwe&dw 7aew.werc.ewr:8100/?ad=qwe&dw=%20%32 +4.0e-10 qwe qwe qwqwe 234.435 455 5.005 teodor@stack.net teodor@123-stack.net 123_teodor@stack.net 123-teodor@stack.net qwe-wer asdf qwer jf sdjk ewr1> ewri2 +/usr/local/fff /awdf/dwqe/4325 rewt/ewr wefjn /wqe-324/ewr gist.h gist.h.c gist.c. readline 4.2 4.2. 4.2, readline-4.2 readline-4.2. 234 + wow < jqw <> qwerty'); + +SELECT to_tsvector('english', '345 qwe@efd.r '' http://www.com/ http://aew.werc.ewr/?ad=qwe&dw 1aew.werc.ewr/?ad=qwe&dw 2aew.werc.ewr http://3aew.werc.ewr/?ad=qwe&dw http://4aew.werc.ewr http://5aew.werc.ewr:8100/? ad=qwe&dw 6aew.werc.ewr:8100/?ad=qwe&dw 7aew.werc.ewr:8100/?ad=qwe&dw=%20%32 +4.0e-10 qwe qwe qwqwe 234.435 455 5.005 teodor@stack.net teodor@123-stack.net 123_teodor@stack.net 123-teodor@stack.net qwe-wer asdf qwer jf sdjk ewr1> ewri2 +/usr/local/fff /awdf/dwqe/4325 rewt/ewr wefjn /wqe-324/ewr gist.h gist.h.c gist.c. readline 4.2 4.2. 4.2, readline-4.2 readline-4.2. 234 + wow < jqw <> qwerty'); + +SELECT length(to_tsvector('english', '345 qwe@efd.r '' http://www.com/ http://aew.werc.ewr/?ad=qwe&dw 1aew.werc.ewr/?ad=qwe&dw 2aew.werc.ewr http://3aew.werc.ewr/?ad=qwe&dw http://4aew.werc.ewr http://5aew.werc.ewr:8100/? ad=qwe&dw 6aew.werc.ewr:8100/?ad=qwe&dw 7aew.werc.ewr:8100/?ad=qwe&dw=%20%32 +4.0e-10 qwe qwe qwqwe 234.435 455 5.005 teodor@stack.net teodor@123-stack.net 123_teodor@stack.net 123-teodor@stack.net qwe-wer asdf qwer jf sdjk ewr1> ewri2 +/usr/local/fff /awdf/dwqe/4325 rewt/ewr wefjn /wqe-324/ewr gist.h gist.h.c gist.c. readline 4.2 4.2. 4.2, readline-4.2 readline-4.2. 234 + wow < jqw <> qwerty')); + +-- ts_debug + +SELECT * from ts_debug('english', 'abc&nm1;def©ghiõjkl'); + +-- check parsing of URLs +SELECT * from ts_debug('english', 'http://www.harewoodsolutions.co.uk/press.aspx'); +SELECT * from ts_debug('english', 'http://aew.wer0c.ewr/id?ad=qwe&dw'); +SELECT * from ts_debug('english', 'http://5aew.werc.ewr:8100/?'); +SELECT * from ts_debug('english', '5aew.werc.ewr:8100/?xx'); +SELECT token, alias, + dictionaries, dictionaries is null as dnull, array_dims(dictionaries) as ddims, + lexemes, lexemes is null as lnull, array_dims(lexemes) as ldims +from ts_debug('english', 'a title'); + +-- to_tsquery + +SELECT to_tsquery('english', 'qwe & sKies '); +SELECT to_tsquery('simple', 'qwe & sKies '); +SELECT to_tsquery('english', '''the wether'':dc & '' sKies '':BC '); +SELECT to_tsquery('english', 'asd&(and|fghj)'); +SELECT to_tsquery('english', '(asd&and)|fghj'); +SELECT to_tsquery('english', '(asd&!and)|fghj'); +SELECT to_tsquery('english', '(the|and&(i&1))&fghj'); + +SELECT plainto_tsquery('english', 'the and z 1))& fghj'); +SELECT plainto_tsquery('english', 'foo bar') && plainto_tsquery('english', 'asd'); +SELECT plainto_tsquery('english', 'foo bar') || plainto_tsquery('english', 'asd fg'); +SELECT plainto_tsquery('english', 'foo bar') || !!plainto_tsquery('english', 'asd fg'); +SELECT plainto_tsquery('english', 'foo bar') && 'asd | fg'; + +-- Check stop word deletion, a and s are stop-words +SELECT to_tsquery('english', '!(a & !b) & c'); +SELECT to_tsquery('english', '!(a & !b)'); + +SELECT to_tsquery('english', '(1 <-> 2) <-> a'); +SELECT to_tsquery('english', '(1 <-> a) <-> 2'); +SELECT to_tsquery('english', '(a <-> 1) <-> 2'); +SELECT to_tsquery('english', 'a <-> (1 <-> 2)'); +SELECT to_tsquery('english', '1 <-> (a <-> 2)'); +SELECT to_tsquery('english', '1 <-> (2 <-> a)'); + +SELECT to_tsquery('english', '(1 <-> 2) <3> a'); +SELECT to_tsquery('english', '(1 <-> a) <3> 2'); +SELECT to_tsquery('english', '(a <-> 1) <3> 2'); +SELECT to_tsquery('english', 'a <3> (1 <-> 2)'); +SELECT to_tsquery('english', '1 <3> (a <-> 2)'); +SELECT to_tsquery('english', '1 <3> (2 <-> a)'); + +SELECT to_tsquery('english', '(1 <3> 2) <-> a'); +SELECT to_tsquery('english', '(1 <3> a) <-> 2'); +SELECT to_tsquery('english', '(a <3> 1) <-> 2'); +SELECT to_tsquery('english', 'a <-> (1 <3> 2)'); +SELECT to_tsquery('english', '1 <-> (a <3> 2)'); +SELECT to_tsquery('english', '1 <-> (2 <3> a)'); + +SELECT to_tsquery('english', '((a <-> 1) <-> 2) <-> s'); +SELECT to_tsquery('english', '(2 <-> (a <-> 1)) <-> s'); +SELECT to_tsquery('english', '((1 <-> a) <-> 2) <-> s'); +SELECT to_tsquery('english', '(2 <-> (1 <-> a)) <-> s'); +SELECT to_tsquery('english', 's <-> ((a <-> 1) <-> 2)'); +SELECT to_tsquery('english', 's <-> (2 <-> (a <-> 1))'); +SELECT to_tsquery('english', 's <-> ((1 <-> a) <-> 2)'); +SELECT to_tsquery('english', 's <-> (2 <-> (1 <-> a))'); + +SELECT to_tsquery('english', '((a <-> 1) <-> s) <-> 2'); +SELECT to_tsquery('english', '(s <-> (a <-> 1)) <-> 2'); +SELECT to_tsquery('english', '((1 <-> a) <-> s) <-> 2'); +SELECT to_tsquery('english', '(s <-> (1 <-> a)) <-> 2'); +SELECT to_tsquery('english', '2 <-> ((a <-> 1) <-> s)'); +SELECT to_tsquery('english', '2 <-> (s <-> (a <-> 1))'); +SELECT to_tsquery('english', '2 <-> ((1 <-> a) <-> s)'); +SELECT to_tsquery('english', '2 <-> (s <-> (1 <-> a))'); + +SELECT to_tsquery('english', 'foo <-> (a <-> (the <-> bar))'); +SELECT to_tsquery('english', '((foo <-> a) <-> the) <-> bar'); +SELECT to_tsquery('english', 'foo <-> a <-> the <-> bar'); +SELECT phraseto_tsquery('english', 'PostgreSQL can be extended by the user in many ways'); + + +SELECT ts_rank_cd(to_tsvector('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +'), to_tsquery('english', 'paint&water')); + +SELECT ts_rank_cd(to_tsvector('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +'), to_tsquery('english', 'breath&motion&water')); + +SELECT ts_rank_cd(to_tsvector('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +'), to_tsquery('english', 'ocean')); + +SELECT ts_rank_cd(to_tsvector('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +'), to_tsquery('english', 'painted <-> Ship')); + +SELECT ts_rank_cd(strip(to_tsvector('both stripped')), + to_tsquery('both & stripped')); + +SELECT ts_rank_cd(to_tsvector('unstripped') || strip(to_tsvector('stripped')), + to_tsquery('unstripped & stripped')); + +--headline tests +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'paint&water')); + +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'breath&motion&water')); + +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'ocean')); + +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', phraseto_tsquery('english', 'painted Ocean')); + +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', phraseto_tsquery('english', 'idle as a painted Ship')); + +SELECT ts_headline('english', +'Lorem ipsum urna. Nullam nullam ullamcorper urna.', +to_tsquery('english','Lorem') && phraseto_tsquery('english','ullamcorper urna'), +'MaxWords=100, MinWords=1'); + +SELECT ts_headline('english', ' + + + +Sea view wow foo bar qq +YES   +ff-bg + + +', +to_tsquery('english', 'sea&foo'), 'HighlightAll=true'); + +SELECT ts_headline('simple', '1 2 3 1 3'::text, '1 <-> 3', 'MaxWords=2, MinWords=1'); +SELECT ts_headline('simple', '1 2 3 1 3'::text, '1 & 3', 'MaxWords=4, MinWords=1'); +SELECT ts_headline('simple', '1 2 3 1 3'::text, '1 <-> 3', 'MaxWords=4, MinWords=1'); + +--Check if headline fragments work +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'ocean'), 'MaxFragments=1'); + +--Check if more than one fragments are displayed +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'Coleridge & stuck'), 'MaxFragments=2'); + +--Fragments when there all query words are not in the document +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'ocean & seahorse'), 'MaxFragments=1'); + +--FragmentDelimiter option +SELECT ts_headline('english', ' +Day after day, day after day, + We stuck, nor breath nor motion, +As idle as a painted Ship + Upon a painted Ocean. +Water, water, every where + And all the boards did shrink; +Water, water, every where, + Nor any drop to drink. +S. T. Coleridge (1772-1834) +', to_tsquery('english', 'Coleridge & stuck'), 'MaxFragments=2,FragmentDelimiter=***'); + +--Fragments with phrase search +SELECT ts_headline('english', +'Lorem ipsum urna. Nullam nullam ullamcorper urna.', +to_tsquery('english','Lorem') && phraseto_tsquery('english','ullamcorper urna'), +'MaxFragments=100, MaxWords=100, MinWords=1'); + +--Rewrite sub system + +CREATE TABLE test_tsquery (txtkeyword TEXT, txtsample TEXT); +\set ECHO none +\copy test_tsquery from stdin +'New York' new <-> york | big <-> apple | nyc +Moscow moskva | moscow +'Sanct Peter' Peterburg | peter | 'Sanct Peterburg' +foo & bar & qq foo & (bar | qq) & city +1 & (2 <-> 3) 2 <-> 4 +5 <-> 6 5 <-> 7 +\. +\set ECHO all + +ALTER TABLE test_tsquery ADD COLUMN keyword tsquery; +UPDATE test_tsquery SET keyword = to_tsquery('english', txtkeyword); +ALTER TABLE test_tsquery ADD COLUMN sample tsquery; +UPDATE test_tsquery SET sample = to_tsquery('english', txtsample::text); + + +SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new <-> york'; + +CREATE UNIQUE INDEX bt_tsq ON test_tsquery (keyword); + +/* YB: uncomment when above CREATE INDEX is supported +SET enable_seqscan=OFF; + +SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new <-> york'; +SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new <-> york'; + +RESET enable_seqscan; +*/ -- YB + +SELECT ts_rewrite('foo & bar & qq & new & york', 'new & york'::tsquery, 'big & apple | nyc | new & york & city'); +SELECT ts_rewrite(ts_rewrite('new & !york ', 'york', '!jersey'), + 'jersey', 'mexico'); + +SELECT ts_rewrite('moscow', 'SELECT keyword, sample FROM test_tsquery'::text ); +SELECT ts_rewrite('moscow & hotel', 'SELECT keyword, sample FROM test_tsquery'::text ); +SELECT ts_rewrite('bar & qq & foo & (new <-> york)', 'SELECT keyword, sample FROM test_tsquery'::text ); + +SELECT ts_rewrite( 'moscow', 'SELECT keyword, sample FROM test_tsquery'); +SELECT ts_rewrite( 'moscow & hotel', 'SELECT keyword, sample FROM test_tsquery'); +SELECT ts_rewrite( 'bar & qq & foo & (new <-> york)', 'SELECT keyword, sample FROM test_tsquery'); + +SELECT ts_rewrite('1 & (2 <-> 3)', 'SELECT keyword, sample FROM test_tsquery'::text ); +SELECT ts_rewrite('1 & (2 <2> 3)', 'SELECT keyword, sample FROM test_tsquery'::text ); +SELECT ts_rewrite('5 <-> (1 & (2 <-> 3))', 'SELECT keyword, sample FROM test_tsquery'::text ); +SELECT ts_rewrite('5 <-> (6 | 8)', 'SELECT keyword, sample FROM test_tsquery'::text ); + +-- Check empty substitution +SELECT ts_rewrite(to_tsquery('5 & (6 | 5)'), to_tsquery('5'), to_tsquery('')); +SELECT ts_rewrite(to_tsquery('!5'), to_tsquery('5'), to_tsquery('')); + +SELECT keyword FROM test_tsquery WHERE keyword @> 'new'; +SELECT keyword FROM test_tsquery WHERE keyword @> 'moscow'; +SELECT keyword FROM test_tsquery WHERE keyword <@ 'new'; +SELECT keyword FROM test_tsquery WHERE keyword <@ 'moscow'; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query; + +CREATE INDEX qq ON test_tsquery USING gist (keyword tsquery_ops); +/* YB: uncomment when above CREATE INDEX is supported +SET enable_seqscan=OFF; + +SELECT keyword FROM test_tsquery WHERE keyword @> 'new'; +SELECT keyword FROM test_tsquery WHERE keyword @> 'moscow'; +SELECT keyword FROM test_tsquery WHERE keyword <@ 'new'; +SELECT keyword FROM test_tsquery WHERE keyword <@ 'moscow'; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query; +SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query; +*/ -- YB + +SELECT ts_rewrite(tsquery_phrase('foo', 'foo'), 'foo', 'bar | baz'); +SELECT to_tsvector('foo bar') @@ + ts_rewrite(tsquery_phrase('foo', 'foo'), 'foo', 'bar | baz'); +SELECT to_tsvector('bar baz') @@ + ts_rewrite(tsquery_phrase('foo', 'foo'), 'foo', 'bar | baz'); + +/* YB: uncomment when above CREATE INDEX is supported +RESET enable_seqscan; +*/ -- YB + +--test GUC +SET default_text_search_config=simple; + +SELECT to_tsvector('SKIES My booKs'); +SELECT plainto_tsquery('SKIES My booKs'); +SELECT to_tsquery('SKIES & My | booKs'); + +SET default_text_search_config=english; + +SELECT to_tsvector('SKIES My booKs'); +SELECT plainto_tsquery('SKIES My booKs'); +SELECT to_tsquery('SKIES & My | booKs'); + +--trigger +CREATE TRIGGER tsvectorupdate +BEFORE UPDATE OR INSERT ON test_tsvector +FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger(a, 'pg_catalog.english', t); + +SELECT count(*) FROM test_tsvector WHERE a @@ to_tsquery('345&qwerty'); +INSERT INTO test_tsvector (t) VALUES ('345 qwerty'); +SELECT count(*) FROM test_tsvector WHERE a @@ to_tsquery('345&qwerty'); +UPDATE test_tsvector SET t = null WHERE t = '345 qwerty'; +SELECT count(*) FROM test_tsvector WHERE a @@ to_tsquery('345&qwerty'); + +INSERT INTO test_tsvector (t) VALUES ('345 qwerty'); + +SELECT count(*) FROM test_tsvector WHERE a @@ to_tsquery('345&qwerty'); + +-- Test inlining of immutable constant functions + +-- to_tsquery(text) is not immutable, so it won't be inlined +explain (costs off) +select * from test_tsquery, to_tsquery('new') q where txtsample @@ q; + +-- to_tsquery(regconfig, text) is an immutable function. +-- That allows us to get rid of using function scan and join at all. +explain (costs off) +select * from test_tsquery, to_tsquery('english', 'new') q where txtsample @@ q; + +-- test finding items in GIN's pending list +create temp table pendtest (ts tsvector); +create index pendtest_idx on pendtest using gin(ts); +insert into pendtest values (to_tsvector('Lore ipsam')); +insert into pendtest values (to_tsvector('Lore ipsum')); +select * from pendtest where 'ipsu:*'::tsquery @@ ts; +select * from pendtest where 'ipsa:*'::tsquery @@ ts; +select * from pendtest where 'ips:*'::tsquery @@ ts; +select * from pendtest where 'ipt:*'::tsquery @@ ts; +select * from pendtest where 'ipi:*'::tsquery @@ ts; + +--check OP_PHRASE on index +create temp table phrase_index_test(fts tsvector); +insert into phrase_index_test values ('A fat cat has just eaten a rat.'); +insert into phrase_index_test values (to_tsvector('english', 'A fat cat has just eaten a rat.')); +create index phrase_index_test_idx on phrase_index_test using gin(fts); +set enable_seqscan = off; +select * from phrase_index_test where fts @@ phraseto_tsquery('english', 'fat cat'); +set enable_seqscan = on; + +-- test websearch_to_tsquery function +select websearch_to_tsquery('simple', 'I have a fat:*ABCD cat'); +select websearch_to_tsquery('simple', 'orange:**AABBCCDD'); +select websearch_to_tsquery('simple', 'fat:A!cat:B|rat:C<'); +select websearch_to_tsquery('simple', 'fat:A : cat:B'); + +select websearch_to_tsquery('simple', 'fat*rat'); +select websearch_to_tsquery('simple', 'fat-rat'); +select websearch_to_tsquery('simple', 'fat_rat'); + +-- weights are completely ignored +select websearch_to_tsquery('simple', 'abc : def'); +select websearch_to_tsquery('simple', 'abc:def'); +select websearch_to_tsquery('simple', 'a:::b'); +select websearch_to_tsquery('simple', 'abc:d'); +select websearch_to_tsquery('simple', ':'); + +-- these operators are ignored +select websearch_to_tsquery('simple', 'abc & def'); +select websearch_to_tsquery('simple', 'abc | def'); +select websearch_to_tsquery('simple', 'abc <-> def'); +select websearch_to_tsquery('simple', 'abc (pg or class)'); + +-- NOT is ignored in quotes +select websearch_to_tsquery('english', 'My brand new smartphone'); +select websearch_to_tsquery('english', 'My brand "new smartphone"'); +select websearch_to_tsquery('english', 'My brand "new -smartphone"'); + +-- test OR operator +select websearch_to_tsquery('simple', 'cat or rat'); +select websearch_to_tsquery('simple', 'cat OR rat'); +select websearch_to_tsquery('simple', 'cat "OR" rat'); +select websearch_to_tsquery('simple', 'cat OR'); +select websearch_to_tsquery('simple', 'OR rat'); +select websearch_to_tsquery('simple', '"fat cat OR rat"'); +select websearch_to_tsquery('simple', 'fat (cat OR rat'); +select websearch_to_tsquery('simple', 'or OR or'); + +-- OR is an operator here ... +select websearch_to_tsquery('simple', '"fat cat"or"fat rat"'); +select websearch_to_tsquery('simple', 'fat or(rat'); +select websearch_to_tsquery('simple', 'fat or)rat'); +select websearch_to_tsquery('simple', 'fat or&rat'); +select websearch_to_tsquery('simple', 'fat or|rat'); +select websearch_to_tsquery('simple', 'fat or!rat'); +select websearch_to_tsquery('simple', 'fat orrat'); +select websearch_to_tsquery('simple', 'fat or '); + +-- ... but not here +select websearch_to_tsquery('simple', 'abc orange'); +select websearch_to_tsquery('simple', 'abc OR1234'); +select websearch_to_tsquery('simple', 'abc or-abc'); +select websearch_to_tsquery('simple', 'abc OR_abc'); + +-- test quotes +select websearch_to_tsquery('english', '"pg_class pg'); +select websearch_to_tsquery('english', 'pg_class pg"'); +select websearch_to_tsquery('english', '"pg_class pg"'); +select websearch_to_tsquery('english', '"pg_class : pg"'); +select websearch_to_tsquery('english', 'abc "pg_class pg"'); +select websearch_to_tsquery('english', '"pg_class pg" def'); +select websearch_to_tsquery('english', 'abc "pg pg_class pg" def'); +select websearch_to_tsquery('english', ' or "pg pg_class pg" or '); +select websearch_to_tsquery('english', '""pg pg_class pg""'); +select websearch_to_tsquery('english', 'abc """"" def'); +select websearch_to_tsquery('english', 'cat -"fat rat"'); +select websearch_to_tsquery('english', 'cat -"fat rat" cheese'); +select websearch_to_tsquery('english', 'abc "def -"'); +select websearch_to_tsquery('english', 'abc "def :"'); + +select websearch_to_tsquery('english', '"A fat cat" has just eaten a -rat.'); +select websearch_to_tsquery('english', '"A fat cat" has just eaten OR !rat.'); +select websearch_to_tsquery('english', '"A fat cat" has just (+eaten OR -rat)'); + +select websearch_to_tsquery('english', 'this is ----fine'); +select websearch_to_tsquery('english', '(()) )))) this ||| is && -fine, "dear friend" OR good'); +select websearch_to_tsquery('english', 'an old <-> cat " is fine &&& too'); + +select websearch_to_tsquery('english', '"A the" OR just on'); +select websearch_to_tsquery('english', '"a fat cat" ate a rat'); + +select to_tsvector('english', 'A fat cat ate a rat') @@ + websearch_to_tsquery('english', '"a fat cat" ate a rat'); + +select to_tsvector('english', 'A fat grey cat ate a rat') @@ + websearch_to_tsquery('english', '"a fat cat" ate a rat'); + +-- cases handled by gettoken_tsvector() +select websearch_to_tsquery(''''); +select websearch_to_tsquery('''abc''''def'''); +select websearch_to_tsquery('\abc'); +select websearch_to_tsquery('\'); diff --git a/src/test/regress/sql/yb.port.update.sql b/src/test/regress/sql/yb.port.update.sql new file mode 100644 index 0000000000000..3e3ea75a3c0da --- /dev/null +++ b/src/test/regress/sql/yb.port.update.sql @@ -0,0 +1,550 @@ +-- Test ON CONFLICT DO UPDATE with partitioned table and non-identical children + +CREATE TABLE upsert_test ( + a INT PRIMARY KEY, + b TEXT +) PARTITION BY LIST (a); + +CREATE TABLE upsert_test_1 PARTITION OF upsert_test FOR VALUES IN (1); +CREATE TABLE upsert_test_2 (b TEXT, a INT PRIMARY KEY); +ALTER TABLE upsert_test ATTACH PARTITION upsert_test_2 FOR VALUES IN (2); + +INSERT INTO upsert_test VALUES(1, 'Boo'), (2, 'Zoo'); +-- uncorrelated sub-select: +WITH aaa AS (SELECT 1 AS a, 'Foo' AS b) INSERT INTO upsert_test + VALUES (1, 'Bar') ON CONFLICT(a) + DO UPDATE SET (b, a) = (SELECT b, a FROM aaa) RETURNING *; +-- correlated sub-select: +WITH aaa AS (SELECT 1 AS ctea, ' Foo' AS cteb) INSERT INTO upsert_test + VALUES (1, 'Bar'), (2, 'Baz') ON CONFLICT(a) + DO UPDATE SET (b, a) = (SELECT upsert_test.b||cteb, upsert_test.a FROM aaa) RETURNING *; + +DROP TABLE upsert_test; + + +--------------------------- +-- UPDATE with row movement +--------------------------- + +-- When a partitioned table receives an UPDATE to the partitioned key and the +-- new values no longer meet the partition's bound, the row must be moved to +-- the correct partition for the new partition key (if one exists). We must +-- also ensure that updatable views on partitioned tables properly enforce any +-- WITH CHECK OPTION that is defined. The situation with triggers in this case +-- also requires thorough testing as partition key updates causing row +-- movement convert UPDATEs into DELETE+INSERT. + +CREATE TABLE range_parted ( + a text, + b bigint, + c numeric, + d int, + e varchar +) PARTITION BY RANGE (a, b); + +-- Create partitions intentionally in descending bound order, so as to test +-- that update-row-movement works with the leaf partitions not in bound order. +CREATE TABLE part_b_20_b_30 (e varchar, c numeric, a text, b bigint, d int); +ALTER TABLE range_parted ATTACH PARTITION part_b_20_b_30 FOR VALUES FROM ('b', 20) TO ('b', 30); +CREATE TABLE part_b_10_b_20 (e varchar, c numeric, a text, b bigint, d int) PARTITION BY RANGE (c); +CREATE TABLE part_b_1_b_10 PARTITION OF range_parted FOR VALUES FROM ('b', 1) TO ('b', 10); +ALTER TABLE range_parted ATTACH PARTITION part_b_10_b_20 FOR VALUES FROM ('b', 10) TO ('b', 20); +CREATE TABLE part_a_10_a_20 PARTITION OF range_parted FOR VALUES FROM ('a', 10) TO ('a', 20); +CREATE TABLE part_a_1_a_10 PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('a', 10); + +-- Check that partition-key UPDATE works sanely on a partitioned table that +-- does not have any child partitions. +UPDATE part_b_10_b_20 set b = b - 6; + +-- Create some more partitions following the above pattern of descending bound +-- order, but let's make the situation a bit more complex by having the +-- attribute numbers of the columns vary from their parent partition. +CREATE TABLE part_c_100_200 (e varchar, c numeric, a text, b bigint, d int) PARTITION BY range (abs(d)); +ALTER TABLE part_c_100_200 DROP COLUMN e, DROP COLUMN c, DROP COLUMN a; +ALTER TABLE part_c_100_200 ADD COLUMN c numeric, ADD COLUMN e varchar, ADD COLUMN a text; +ALTER TABLE part_c_100_200 DROP COLUMN b; +ALTER TABLE part_c_100_200 ADD COLUMN b bigint; +CREATE TABLE part_d_1_15 PARTITION OF part_c_100_200 FOR VALUES FROM (1) TO (15); +CREATE TABLE part_d_15_20 PARTITION OF part_c_100_200 FOR VALUES FROM (15) TO (20); + +ALTER TABLE part_b_10_b_20 ATTACH PARTITION part_c_100_200 FOR VALUES FROM (100) TO (200); + +CREATE TABLE part_c_1_100 (e varchar, d int, c numeric, b bigint, a text); +ALTER TABLE part_b_10_b_20 ATTACH PARTITION part_c_1_100 FOR VALUES FROM (1) TO (100); + +\set init_range_parted 'truncate range_parted; insert into range_parted VALUES (''a'', 1, 1, 1), (''a'', 10, 200, 1), (''b'', 12, 96, 1), (''b'', 13, 97, 2), (''b'', 15, 105, 16), (''b'', 17, 105, 19)' +\set show_data 'select tableoid::regclass::text COLLATE "C" partname, * from range_parted ORDER BY 1, 2, 3, 4, 5, 6' +:init_range_parted; +:show_data; + +-- The order of subplans should be in bound order +EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97; + +-- fail, row movement happens only within the partition subtree. +UPDATE part_c_100_200 set c = c - 20, d = c WHERE c = 105 AND b = 15; +-- fail, no partition key update, so no attempt to move tuple, +-- but "a = 'a'" violates partition constraint enforced by root partition) +UPDATE part_b_10_b_20 set a = 'a' WHERE b = 12; +-- ok, partition key update, no constraint violation +UPDATE range_parted set d = d - 10 WHERE d > 10; +-- ok, no partition key update, no constraint violation +UPDATE range_parted set e = d; +-- No row found +UPDATE part_c_1_100 set c = c + 20 WHERE c = 98; +-- ok, row movement +WITH x AS (UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a) +SELECT * FROM x ORDER BY b; +:show_data; + +-- fail, row movement happens only within the partition subtree. +UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 AND d = 2 returning *; +-- ok, row movement, with subset of rows moved into different partition. +WITH x AS (UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c AS f) +SELECT * FROM x ORDER BY a, f; + +:show_data; + +-- Common table needed for multiple test scenarios. +CREATE TABLE mintab(c1 int); +INSERT into mintab VALUES (120); + +-- update partition key using updatable view. +CREATE VIEW upview AS SELECT * FROM range_parted WHERE (select c > c1 FROM mintab) WITH CHECK OPTION; +/* +The following tests should be enabled after VIEW WITH CHECK OPTION +is supported. +-- ok +UPDATE upview set c = 199 WHERE b = 4; +-- fail, check option violation +UPDATE upview set c = 120 WHERE b = 4; +-- fail, row movement with check option violation +-- ok, row movement, check option passes +UPDATE upview set a = 'b', b = 15 WHERE b = 4; +-- cleanup +DROP VIEW upview; +*/ +-- RETURNING having whole-row vars. +:init_range_parted; +WITH x AS (UPDATE range_parted set c = 95 WHERE a = 'b' and b > 10 and c > 100 returning (range_parted), *) +SELECT * FROM x ORDER BY b; +:show_data; + +-- The following tests computing RETURNING when the source and the destination +-- partitions of a UPDATE row movement operation have different tuple +-- descriptors, which has been shown to be problematic in the cases where the +-- RETURNING targetlist contains non-target relation attributes that are +-- computed by referring to the source partition plan's output tuple. Also, +-- a trigger on the destination relation may change the tuple, which must be +-- reflected in the RETURNING output, so we test that too. +CREATE TABLE part_c_1_c_20 (LIKE range_parted); +-- YB Note: Split the below statement into 2 Alter statements to work around GH #20342 +-- ALTER TABLE part_c_1_c_20 DROP a, DROP b, ADD a text, ADD b bigint; +ALTER TABLE part_c_1_c_20 DROP a, DROP b; +ALTER TABLE part_c_1_c_20 ADD a text, ADD b bigint; +ALTER TABLE range_parted ATTACH PARTITION part_c_1_c_20 FOR VALUES FROM ('c', 1) TO ('c', 20); +CREATE FUNCTION trigfunc () RETURNS TRIGGER LANGUAGE plpgsql as $$ BEGIN NEW.e := 'in trigfunc()'; RETURN NEW; END; $$; +CREATE TRIGGER trig BEFORE INSERT ON part_c_1_c_20 FOR EACH ROW EXECUTE FUNCTION trigfunc(); +UPDATE range_parted r set a = 'c' FROM (VALUES ('a', 1), ('a', 10), ('b', 12)) s(x, y) WHERE s.x = r.a AND s.y = r.b RETURNING tableoid::regclass, *; + +DROP TRIGGER trig ON part_c_1_c_20; +DROP FUNCTION trigfunc; + +:init_range_parted; +CREATE FUNCTION trigfunc () RETURNS TRIGGER LANGUAGE plpgsql as $$ BEGIN RETURN NULL; END; $$; +CREATE TRIGGER trig BEFORE INSERT ON part_c_1_c_20 FOR EACH ROW EXECUTE FUNCTION trigfunc(); +UPDATE range_parted r set a = 'c' FROM (VALUES ('a', 1), ('a', 10), ('b', 12)) s(x, y) WHERE s.x = r.a AND s.y = r.b RETURNING tableoid::regclass, *; +:show_data; + +DROP TABLE part_c_1_c_20; +DROP FUNCTION trigfunc; + +-- Transition tables with update row movement +:init_range_parted; + +CREATE FUNCTION trans_updatetrigfunc() RETURNS trigger LANGUAGE plpgsql AS +$$ + begin + raise notice 'trigger = %, old table = %, new table = %', + TG_NAME, + (select string_agg(old_table::text, ', ' ORDER BY a) FROM old_table), + (select string_agg(new_table::text, ', ' ORDER BY a) FROM new_table); + return null; + end; +$$; + +-- Enable the following tests after REFERENCING clause (transition tables) is supported. +CREATE TRIGGER trans_updatetrig + AFTER UPDATE ON range_parted REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc(); + +/* +UPDATE range_parted set c = (case when c = 96 then 110 else c + 1 end ) WHERE a = 'b' and b > 10 and c >= 96; +:show_data; +:init_range_parted; + +-- Enabling OLD TABLE capture for both DELETE as well as UPDATE stmt triggers +-- should not cause DELETEd rows to be captured twice. Similar thing for +-- INSERT triggers and inserted rows. +CREATE TRIGGER trans_deletetrig + AFTER DELETE ON range_parted REFERENCING OLD TABLE AS old_table + FOR EACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc(); +CREATE TRIGGER trans_inserttrig + AFTER INSERT ON range_parted REFERENCING NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc(); +UPDATE range_parted set c = c + 50 WHERE a = 'b' and b > 10 and c >= 96; +:show_data; +DROP TRIGGER trans_deletetrig ON range_parted; +DROP TRIGGER trans_inserttrig ON range_parted; +-- Don't drop trans_updatetrig yet. It is required below. + +-- Test with transition tuple conversion happening for rows moved into the +-- new partition. This requires a trigger that references transition table +-- (we already have trans_updatetrig). For inserted rows, the conversion +-- is not usually needed, because the original tuple is already compatible with +-- the desired transition tuple format. But conversion happens when there is a +-- BR trigger because the trigger can change the inserted row. So install a +-- BR triggers on those child partitions where the rows will be moved. +CREATE FUNCTION func_parted_mod_b() RETURNS trigger AS $$ +BEGIN + NEW.b = NEW.b + 1; + return NEW; +END $$ language plpgsql; +CREATE TRIGGER trig_c1_100 BEFORE UPDATE OR INSERT ON part_c_1_100 + FOR EACH ROW EXECUTE PROCEDURE func_parted_mod_b(); +CREATE TRIGGER trig_d1_15 BEFORE UPDATE OR INSERT ON part_d_1_15 + FOR EACH ROW EXECUTE PROCEDURE func_parted_mod_b(); +CREATE TRIGGER trig_d15_20 BEFORE UPDATE OR INSERT ON part_d_15_20 + FOR EACH ROW EXECUTE PROCEDURE func_parted_mod_b(); +:init_range_parted; +UPDATE range_parted set c = (case when c = 96 then 110 else c + 1 end) WHERE a = 'b' and b > 10 and c >= 96; +:show_data; +:init_range_parted; +UPDATE range_parted set c = c + 50 WHERE a = 'b' and b > 10 and c >= 96; +:show_data; + +-- Case where per-partition tuple conversion map array is allocated, but the +-- map is not required for the particular tuple that is routed, thanks to +-- matching table attributes of the partition and the target table. +:init_range_parted; +UPDATE range_parted set b = 15 WHERE b = 1; +:show_data; +DROP TRIGGER trans_updatetrig ON range_parted; +DROP TRIGGER trig_c1_100 ON part_c_1_100; +DROP TRIGGER trig_d1_15 ON part_d_1_15; +DROP TRIGGER trig_d15_20 ON part_d_15_20; +DROP FUNCTION func_parted_mod_b(); +*/ +-- RLS policies with update-row-movement +----------------------------------------- + +ALTER TABLE range_parted ENABLE ROW LEVEL SECURITY; +CREATE USER regress_range_parted_user; +GRANT ALL ON range_parted, mintab TO regress_range_parted_user; +CREATE POLICY seeall ON range_parted AS PERMISSIVE FOR SELECT USING (true); +CREATE POLICY policy_range_parted ON range_parted for UPDATE USING (true) WITH CHECK (c % 2 = 0); + +:init_range_parted; +SET SESSION AUTHORIZATION regress_range_parted_user; +-- This should fail with RLS violation error while moving row from +-- part_a_10_a_20 to part_d_1_15, because we are setting 'c' to an odd number. +UPDATE range_parted set a = 'b', c = 151 WHERE a = 'a' and c = 200; + +RESET SESSION AUTHORIZATION; +-- Create a trigger on part_d_1_15 +CREATE FUNCTION func_d_1_15() RETURNS trigger AS $$ +BEGIN + NEW.c = NEW.c + 1; -- Make even numbers odd, or vice versa + return NEW; +END $$ LANGUAGE plpgsql; +CREATE TRIGGER trig_d_1_15 BEFORE INSERT ON part_d_1_15 + FOR EACH ROW EXECUTE PROCEDURE func_d_1_15(); + +:init_range_parted; +SET SESSION AUTHORIZATION regress_range_parted_user; + +-- Here, RLS checks should succeed while moving row from part_a_10_a_20 to +-- part_d_1_15. Even though the UPDATE is setting 'c' to an odd number, the +-- trigger at the destination partition again makes it an even number. +UPDATE range_parted set a = 'b', c = 151 WHERE a = 'a' and c = 200; + +RESET SESSION AUTHORIZATION; +:init_range_parted; +SET SESSION AUTHORIZATION regress_range_parted_user; +-- This should fail with RLS violation error. Even though the UPDATE is setting +-- 'c' to an even number, the trigger at the destination partition again makes +-- it an odd number. +UPDATE range_parted set a = 'b', c = 150 WHERE a = 'a' and c = 200; + +-- Cleanup +RESET SESSION AUTHORIZATION; +DROP TRIGGER trig_d_1_15 ON part_d_1_15; +DROP FUNCTION func_d_1_15(); + +-- Policy expression contains SubPlan +RESET SESSION AUTHORIZATION; +:init_range_parted; +CREATE POLICY policy_range_parted_subplan on range_parted + AS RESTRICTIVE for UPDATE USING (true) + WITH CHECK ((SELECT range_parted.c <= c1 FROM mintab)); +SET SESSION AUTHORIZATION regress_range_parted_user; +-- fail, mintab has row with c1 = 120 +UPDATE range_parted set a = 'b', c = 122 WHERE a = 'a' and c = 200; +-- ok +UPDATE range_parted set a = 'b', c = 120 WHERE a = 'a' and c = 200; + +-- RLS policy expression contains whole row. + +RESET SESSION AUTHORIZATION; +:init_range_parted; +CREATE POLICY policy_range_parted_wholerow on range_parted AS RESTRICTIVE for UPDATE USING (true) + WITH CHECK (range_parted = row('b', 10, 112, 1, NULL)::range_parted); +SET SESSION AUTHORIZATION regress_range_parted_user; +-- ok, should pass the RLS check +UPDATE range_parted set a = 'b', c = 112 WHERE a = 'a' and c = 200; +RESET SESSION AUTHORIZATION; +:init_range_parted; +SET SESSION AUTHORIZATION regress_range_parted_user; +-- fail, the whole row RLS check should fail +UPDATE range_parted set a = 'b', c = 116 WHERE a = 'a' and c = 200; + +-- Cleanup +RESET SESSION AUTHORIZATION; +DROP POLICY policy_range_parted ON range_parted; +DROP POLICY policy_range_parted_subplan ON range_parted; +DROP POLICY policy_range_parted_wholerow ON range_parted; +REVOKE ALL ON range_parted, mintab FROM regress_range_parted_user; +DROP USER regress_range_parted_user; +DROP TABLE mintab; + + +-- statement triggers with update row movement +--------------------------------------------------- + +:init_range_parted; +CREATE FUNCTION trigfunc() returns trigger language plpgsql as +$$ + begin + raise notice 'trigger = % fired on table % during %', + TG_NAME, TG_TABLE_NAME, TG_OP; + return null; + end; +$$; +-- Triggers on root partition +CREATE TRIGGER parent_delete_trig + AFTER DELETE ON range_parted for each statement execute procedure trigfunc(); +CREATE TRIGGER parent_update_trig + AFTER UPDATE ON range_parted for each statement execute procedure trigfunc(); +CREATE TRIGGER parent_insert_trig + AFTER INSERT ON range_parted for each statement execute procedure trigfunc(); + +-- Triggers on leaf partition part_c_1_100 +CREATE TRIGGER c1_delete_trig + AFTER DELETE ON part_c_1_100 for each statement execute procedure trigfunc(); +CREATE TRIGGER c1_update_trig + AFTER UPDATE ON part_c_1_100 for each statement execute procedure trigfunc(); +CREATE TRIGGER c1_insert_trig + AFTER INSERT ON part_c_1_100 for each statement execute procedure trigfunc(); + +-- Triggers on leaf partition part_d_1_15 +CREATE TRIGGER d1_delete_trig + AFTER DELETE ON part_d_1_15 for each statement execute procedure trigfunc(); +CREATE TRIGGER d1_update_trig + AFTER UPDATE ON part_d_1_15 for each statement execute procedure trigfunc(); +CREATE TRIGGER d1_insert_trig + AFTER INSERT ON part_d_1_15 for each statement execute procedure trigfunc(); +-- Triggers on leaf partition part_d_15_20 +CREATE TRIGGER d15_delete_trig + AFTER DELETE ON part_d_15_20 for each statement execute procedure trigfunc(); +CREATE TRIGGER d15_update_trig + AFTER UPDATE ON part_d_15_20 for each statement execute procedure trigfunc(); +CREATE TRIGGER d15_insert_trig + AFTER INSERT ON part_d_15_20 for each statement execute procedure trigfunc(); + +-- Move all rows from part_c_100_200 to part_c_1_100. None of the delete or +-- insert statement triggers should be fired. +UPDATE range_parted set c = c - 50 WHERE c > 97; +:show_data; + +DROP TRIGGER parent_delete_trig ON range_parted; +DROP TRIGGER parent_update_trig ON range_parted; +DROP TRIGGER parent_insert_trig ON range_parted; +DROP TRIGGER c1_delete_trig ON part_c_1_100; +DROP TRIGGER c1_update_trig ON part_c_1_100; +DROP TRIGGER c1_insert_trig ON part_c_1_100; +DROP TRIGGER d1_delete_trig ON part_d_1_15; +DROP TRIGGER d1_update_trig ON part_d_1_15; +DROP TRIGGER d1_insert_trig ON part_d_1_15; +DROP TRIGGER d15_delete_trig ON part_d_15_20; +DROP TRIGGER d15_update_trig ON part_d_15_20; +DROP TRIGGER d15_insert_trig ON part_d_15_20; + + +-- Creating default partition for range +:init_range_parted; +create table part_def partition of range_parted default; +\d+ part_def +insert into range_parted values ('c', 9); +-- ok +update part_def set a = 'd' where a = 'c'; +-- fail +update part_def set a = 'a' where a = 'd'; + +:show_data; + +-- Update row movement from non-default to default partition. +-- fail, default partition is not under part_a_10_a_20; +UPDATE part_a_10_a_20 set a = 'ad' WHERE a = 'a'; +-- ok +UPDATE range_parted set a = 'ad' WHERE a = 'a'; +UPDATE range_parted set a = 'bd' WHERE a = 'b'; +:show_data; +-- Update row movement from default to non-default partitions. +-- ok +UPDATE range_parted set a = 'a' WHERE a = 'ad'; +UPDATE range_parted set a = 'b' WHERE a = 'bd'; +:show_data; + +-- Cleanup: range_parted no longer needed. +DROP TABLE range_parted; + +CREATE TABLE list_parted ( + a text, + b int +) PARTITION BY list (a); +CREATE TABLE list_part1 PARTITION OF list_parted for VALUES in ('a', 'b'); +CREATE TABLE list_default PARTITION OF list_parted default; +INSERT into list_part1 VALUES ('a', 1); +INSERT into list_default VALUES ('d', 10); + +-- fail +UPDATE list_default set a = 'a' WHERE a = 'd'; +-- ok +UPDATE list_default set a = 'x' WHERE a = 'd'; + +DROP TABLE list_parted; + +-------------- +-- Some more update-partition-key test scenarios below. This time use list +-- partitions. +-------------- + +-- Setup for list partitions +CREATE TABLE list_parted (a numeric, b int, c int8) PARTITION BY list (a); +CREATE TABLE sub_parted PARTITION OF list_parted for VALUES in (1) PARTITION BY list (b); + +CREATE TABLE sub_part1(b int, c int8, a numeric); +ALTER TABLE sub_parted ATTACH PARTITION sub_part1 for VALUES in (1); +CREATE TABLE sub_part2(b int, c int8, a numeric); +ALTER TABLE sub_parted ATTACH PARTITION sub_part2 for VALUES in (2); + +CREATE TABLE list_part1(a numeric, b int, c int8); +ALTER TABLE list_parted ATTACH PARTITION list_part1 for VALUES in (2,3); + +INSERT into list_parted VALUES (2,5,50); +INSERT into list_parted VALUES (3,6,60); +INSERT into sub_parted VALUES (1,1,60); +INSERT into sub_parted VALUES (1,2,10); + +-- Test partition constraint violation when intermediate ancestor is used and +-- constraint is inherited from upper root. +UPDATE sub_parted set a = 2 WHERE c = 10; + +-- Test update-partition-key, where the unpruned partitions do not have their +-- partition keys updated. +SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1; +UPDATE list_parted set b = c + a WHERE a = 2; +SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1; + + +-- Test the case where BR UPDATE triggers change the partition key. +CREATE FUNCTION func_parted_mod_b() returns trigger as $$ +BEGIN + NEW.b = 2; -- This is changing partition key column. + return NEW; +END $$ LANGUAGE plpgsql; +CREATE TRIGGER parted_mod_b before update on sub_part1 + for each row execute procedure func_parted_mod_b(); + +SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; + +-- This should do the tuple routing even though there is no explicit +-- partition-key update, because there is a trigger on sub_part1. +UPDATE list_parted set c = 70 WHERE b = 1; +SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; + +DROP TRIGGER parted_mod_b ON sub_part1; + +-- If BR DELETE trigger prevented DELETE from happening, we should also skip +-- the INSERT if that delete is part of UPDATE=>DELETE+INSERT. +CREATE OR REPLACE FUNCTION func_parted_mod_b() returns trigger as $$ +BEGIN + raise notice 'Trigger: Got OLD row %, but returning NULL', OLD; + return NULL; +END $$ LANGUAGE plpgsql; +CREATE TRIGGER trig_skip_delete before delete on sub_part2 + for each row execute procedure func_parted_mod_b(); +UPDATE list_parted set b = 1 WHERE c = 70; +SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; +-- Drop the trigger. Now the row should be moved. +DROP TRIGGER trig_skip_delete ON sub_part2; +UPDATE list_parted set b = 1 WHERE c = 70; +SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; +DROP FUNCTION func_parted_mod_b(); + +-- UPDATE partition-key with FROM clause. If join produces multiple output +-- rows for the same row to be modified, we should tuple-route the row only +-- once. There should not be any rows inserted. +CREATE TABLE non_parted (id int); +INSERT into non_parted VALUES (1), (1), (1), (2), (2), (2), (3), (3), (3); +UPDATE list_parted t1 set a = 2 FROM non_parted t2 WHERE t1.a = t2.id and a = 1; +SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; +DROP TABLE non_parted; + +-- Cleanup: list_parted no longer needed. +DROP TABLE list_parted; + +-- create custom operator class and hash function, for the same reason +-- explained in alter_table.sql +create or replace function dummy_hashint4(a int4, seed int8) returns int8 as +$$ begin return (a + seed); end; $$ language 'plpgsql' immutable; +create operator class custom_opclass for type int4 using hash as +operator 1 = , function 2 dummy_hashint4(int4, int8); + +create table hash_parted ( + a int, + b int +) partition by hash (a custom_opclass, b custom_opclass); +create table hpart1 partition of hash_parted for values with (modulus 2, remainder 1); +create table hpart2 partition of hash_parted for values with (modulus 4, remainder 2); +create table hpart3 partition of hash_parted for values with (modulus 8, remainder 0); +create table hpart4 partition of hash_parted for values with (modulus 8, remainder 4); +insert into hpart1 values (1, 1); +insert into hpart2 values (2, 5); +insert into hpart4 values (3, 4); + +-- fail +update hpart1 set a = 3, b=4 where a = 1; +-- ok, row movement +update hash_parted set b = b - 1 where b = 1; +-- ok +update hash_parted set b = b + 8 where b = 1; + +-- cleanup +drop table hash_parted; +drop operator class custom_opclass using hash; +drop function dummy_hashint4(a int4, seed int8); +CREATE TABLE parted (a int, b text) PARTITION BY RANGE(a); +CREATE TABLE part_a_1_5 PARTITION OF parted (a, b, PRIMARY KEY(a)) FOR VALUES FROM (1) TO (5); +CREATE TABLE part_a_5_10 PARTITION OF parted (a, b, PRIMARY KEY(a)) FOR VALUES FROM (5) TO (10); +INSERT INTO parted VALUES (1, '1'), (2, '2'), (4, '4'), (6, '6'), (8, '8'); +-- Test whether single row optimization is invoked when +-- only one partition is being updated. +EXPLAIN UPDATE parted SET b='5' WHERE a = 1; +UPDATE parted SET b='5' WHERE a = 1; +-- Verify that single row optimization is not invoked when +-- multiple partitions are being updated. +EXPLAIN UPDATE parted SET b='6' WHERE a > 1; +UPDATE parted SET b='6' WHERE a > 1; +-- Verify that the updates happened successfully. +SELECT * FROM parted ORDER BY a; +DROP TABLE parted; diff --git a/src/test/regress/sql/yb.port.uuid.sql b/src/test/regress/sql/yb.port.uuid.sql new file mode 100644 index 0000000000000..bbe95fce5bff6 --- /dev/null +++ b/src/test/regress/sql/yb.port.uuid.sql @@ -0,0 +1,81 @@ +-- regression test for the uuid datatype +-- creating test tables +CREATE TABLE guid1 +( + guid_field UUID, + text_field TEXT DEFAULT(now()) +); +CREATE TABLE guid2 +( + guid_field UUID, + text_field TEXT DEFAULT(now()) +); + +-- inserting invalid data tests +-- too long +INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111F'); +-- too short +INSERT INTO guid1(guid_field) VALUES('{11111111-1111-1111-1111-11111111111}'); +-- valid data but invalid format +INSERT INTO guid1(guid_field) VALUES('111-11111-1111-1111-1111-111111111111'); +INSERT INTO guid1(guid_field) VALUES('{22222222-2222-2222-2222-222222222222 '); +-- invalid data +INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-G111-111111111111'); +INSERT INTO guid1(guid_field) VALUES('11+11111-1111-1111-1111-111111111111'); + +--inserting three input formats +INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111'); +INSERT INTO guid1(guid_field) VALUES('{22222222-2222-2222-2222-222222222222}'); +INSERT INTO guid1(guid_field) VALUES('3f3e3c3b3a3039383736353433a2313e'); + +-- ordering test +SELECT guid_field FROM guid1 ORDER BY guid_field ASC; +SELECT guid_field FROM guid1 ORDER BY guid_field DESC; + +-- = operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field = '3f3e3c3b-3a30-3938-3736-353433a2313e'; + +-- <> operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field <> '11111111111111111111111111111111'; + +-- < operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field < '22222222-2222-2222-2222-222222222222'; + +-- <= operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field <= '22222222-2222-2222-2222-222222222222'; + +-- > operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field > '22222222-2222-2222-2222-222222222222'; + +-- >= operator test +SELECT COUNT(*) FROM guid1 WHERE guid_field >= '22222222-2222-2222-2222-222222222222'; + +---------------------------------------------------------------------------------------------------- +-- Uncomment the following test cases once INDEX is supported in YugaByte + +-- lsm and hash index creation test +-- CREATE INDEX guid1_lsm ON guid1 USING LSM (guid_field); +-- CREATE INDEX guid1_hash ON guid1 USING HASH (guid_field); + +-- unique index test +-- CREATE UNIQUE INDEX guid1_unique_LSM ON guid1 USING LSM (guid_field); +-- should fail +-- INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111'); + +-- check to see whether the new indexes are actually there +-- SELECT count(*) FROM pg_class WHERE relkind='i' AND relname LIKE 'guid%'; +---------------------------------------------------------------------------------------------------- + +-- populating the test tables with additional records +INSERT INTO guid1(guid_field) VALUES('44444444-4444-4444-4444-444444444444'); +INSERT INTO guid2(guid_field) VALUES('11111111-1111-1111-1111-111111111111'); +INSERT INTO guid2(guid_field) VALUES('{22222222-2222-2222-2222-222222222222}'); +INSERT INTO guid2(guid_field) VALUES('3f3e3c3b3a3039383736353433a2313e'); + +-- join test +SELECT COUNT(*) FROM guid1 g1 INNER JOIN guid2 g2 ON g1.guid_field = g2.guid_field; +SELECT COUNT(*) FROM guid1 g1 LEFT JOIN guid2 g2 ON g1.guid_field = g2.guid_field + WHERE g2.guid_field IS NULL; + +-- clean up +DROP TABLE guid1, guid2 CASCADE; diff --git a/src/test/regress/sql/yb.port.varchar.sql b/src/test/regress/sql/yb.port.varchar.sql new file mode 100644 index 0000000000000..1078732295f78 --- /dev/null +++ b/src/test/regress/sql/yb.port.varchar.sql @@ -0,0 +1,80 @@ +-- +-- VARCHAR +-- + +-- +-- Build a table for testing +-- (This temporarily hides the table created in test_setup.sql) +-- YB note: but we also want to test YB tables instead of temporary tables, so +-- create a new table in a new namespace instead. +-- +CREATE SCHEMA yb_tmp; +SET search_path TO yb_tmp; + +-- YB note: add ordering column to match upstream PG output ordering. Also +-- adjust SELECTs below to not use * and instead use f1 in order to avoid +-- outputting up the ordering column. +CREATE TABLE VARCHAR_TBL(ybid serial, f1 varchar(1), PRIMARY KEY (ybid ASC)); + +INSERT INTO VARCHAR_TBL (f1) VALUES ('a'); + +INSERT INTO VARCHAR_TBL (f1) VALUES ('A'); + +-- any of the following three input formats are acceptable +INSERT INTO VARCHAR_TBL (f1) VALUES ('1'); + +INSERT INTO VARCHAR_TBL (f1) VALUES (2); + +INSERT INTO VARCHAR_TBL (f1) VALUES ('3'); + +-- zero-length char +INSERT INTO VARCHAR_TBL (f1) VALUES (''); + +-- try varchar's of greater than 1 length +INSERT INTO VARCHAR_TBL (f1) VALUES ('cd'); +INSERT INTO VARCHAR_TBL (f1) VALUES ('c '); + + +SELECT '' AS seven, f1 FROM VARCHAR_TBL; + +SELECT '' AS six, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 <> 'a'; + +SELECT '' AS one, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 = 'a'; + +SELECT '' AS five, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 < 'a'; + +SELECT '' AS six, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 <= 'a'; + +SELECT '' AS one, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 > 'a'; + +SELECT '' AS two, c.f1 + FROM VARCHAR_TBL c + WHERE c.f1 >= 'a'; + +DROP TABLE VARCHAR_TBL; + +-- YB note: reset the changes from above. +DROP SCHEMA yb_tmp; +RESET search_path; + +-- +-- Now test longer arrays of char +-- +-- This varchar_tbl was already created and filled in test_setup.sql. +-- Here we just try to insert bad values. +-- + +INSERT INTO VARCHAR_TBL (f1) VALUES ('abcde'); + +-- YB note: add query ordering to match upstream PG output ordering. +SELECT '' AS four, * FROM VARCHAR_TBL ORDER BY 1, 2; diff --git a/src/test/regress/sql/yb.port.window.sql b/src/test/regress/sql/yb.port.window.sql new file mode 100644 index 0000000000000..7f8fb73ce2307 --- /dev/null +++ b/src/test/regress/sql/yb.port.window.sql @@ -0,0 +1,1302 @@ +-- +-- WINDOW FUNCTIONS +-- + +CREATE TEMPORARY TABLE empsalary ( + depname varchar, + empno bigint, + salary int, + enroll_date date +); + +INSERT INTO empsalary VALUES +('develop', 10, 5200, '2007-08-01'), +('sales', 1, 5000, '2006-10-01'), +('personnel', 5, 3500, '2007-12-10'), +('sales', 4, 4800, '2007-08-08'), +('personnel', 2, 3900, '2006-12-23'), +('develop', 7, 4200, '2008-01-01'), +('develop', 9, 4500, '2008-01-01'), +('sales', 3, 4800, '2007-08-01'), +('develop', 8, 6000, '2006-10-01'), +('develop', 11, 5200, '2007-08-15'); + +SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary; + +SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM empsalary ORDER by 1,2,3,4; + +-- with GROUP BY +SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1 +GROUP BY four, ten ORDER BY four, ten; + +SELECT depname, empno, salary, sum(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname) ORDER by 1,2,3,4; + +SELECT depname, empno, salary, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary) ORDER BY rank() OVER w; + +-- empty window specification +SELECT COUNT(*) OVER () FROM tenk1 WHERE unique2 < 10 ORDER by 1; + +SELECT COUNT(*) OVER w FROM tenk1 WHERE unique2 < 10 WINDOW w AS () ORDER by 1; + +-- no window operation +SELECT four FROM tenk1 WHERE FALSE WINDOW w AS (PARTITION BY ten); + +-- cumulative aggregate +SELECT sum(four) OVER (PARTITION BY ten ORDER BY unique2) AS sum_1, ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT row_number() OVER (ORDER BY unique2) FROM tenk1 WHERE unique2 < 10 ORDER by 1; + +SELECT rank() OVER (PARTITION BY four ORDER BY ten) AS rank_1, ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT dense_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT percent_rank() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT cume_dist() OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT ntile(3) OVER (ORDER BY ten, four), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT ntile(NULL) OVER (ORDER BY ten, four), ten, four FROM tenk1 LIMIT 2; + +SELECT lag(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT lag(ten, four) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT lag(ten, four, 0) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT lead(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT lead(ten * 2, 1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT lead(ten * 2, 1, -1) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT first_value(ten) OVER (PARTITION BY four ORDER BY ten), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +-- last_value returns the last row of the frame, which is CURRENT ROW in ORDER BY window. +-- Changed from original window.sql: ORDER BY unique1 instead of ORDER BY ten +-- See for more information: https://github.com/yugabyte/yugabyte-db/issues/4832 +SELECT last_value(four) OVER (ORDER BY unique1), ten, four FROM tenk1 WHERE unique2 < 10 ORDER by 1,2,3; + +SELECT last_value(ten) OVER (PARTITION BY four), ten, four FROM + (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten)s + ORDER BY four, ten; + +SELECT nth_value(ten, four + 1) OVER (PARTITION BY four), ten, four + FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten)s; + +SELECT ten, two, sum(hundred) AS gsum, sum(sum(hundred)) OVER (PARTITION BY two ORDER BY ten) AS wsum +FROM tenk1 GROUP BY ten, two; + +SELECT count(*) OVER (PARTITION BY four), four FROM (SELECT * FROM tenk1 WHERE two = 1)s WHERE unique2 < 10 ORDER by 1,2; + +SELECT (count(*) OVER (PARTITION BY four ORDER BY ten) + + sum(hundred) OVER (PARTITION BY four ORDER BY ten))::varchar AS cntsum + FROM tenk1 WHERE unique2 < 10 ORDER by 1; + +-- opexpr with different windows evaluation. +SELECT * FROM( + SELECT count(*) OVER (PARTITION BY four ORDER BY ten) + + sum(hundred) OVER (PARTITION BY two ORDER BY ten) AS total, + count(*) OVER (PARTITION BY four ORDER BY ten) AS fourcount, + sum(hundred) OVER (PARTITION BY two ORDER BY ten) AS twosum + FROM tenk1 +)sub +WHERE total <> fourcount + twosum; + +SELECT avg(four) OVER (PARTITION BY four ORDER BY thousand / 100) FROM tenk1 WHERE unique2 < 10 ORDER by 1; + +SELECT ten, two, sum(hundred) AS gsum, sum(sum(hundred)) OVER win AS wsum +FROM tenk1 GROUP BY ten, two WINDOW win AS (PARTITION BY two ORDER BY ten) ORDER by 1,2,3,4; + +-- more than one window with GROUP BY +SELECT sum(salary), + row_number() OVER (ORDER BY depname), + sum(sum(salary)) OVER (ORDER BY depname DESC) +FROM empsalary GROUP BY depname; + +-- identical windows with different names +SELECT sum(salary) OVER w1, count(*) OVER w2 +FROM empsalary WINDOW w1 AS (ORDER BY salary), w2 AS (ORDER BY salary) ORDER by 1,2; + +-- subplan +SELECT lead(ten, (SELECT two FROM tenk1 WHERE s.unique2 = unique2)) OVER (PARTITION BY four ORDER BY ten) +FROM tenk1 s WHERE unique2 < 10 ORDER by 1; + +-- empty table +SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 WHERE FALSE)s; + +-- mixture of agg/wfunc in the same window +SELECT sum(salary) OVER w, rank() OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary DESC) ORDER by 1,2; + +-- strict aggs +SELECT empno, depname, salary, bonus, depadj, MIN(bonus) OVER (ORDER BY empno), MAX(depadj) OVER () FROM( + SELECT *, + CASE WHEN enroll_date < '2008-01-01' THEN 2008 - extract(YEAR FROM enroll_date) END * 500 AS bonus, + CASE WHEN + AVG(salary) OVER (PARTITION BY depname) < salary + THEN 200 END AS depadj FROM empsalary +)s +ORDER by 1,2,3,4,5,6,7; + +-- window function over ungrouped agg over empty row set (bug before 9.1) +SELECT SUM(COUNT(f1)) OVER () FROM int4_tbl WHERE f1=42; + +-- window function with ORDER BY an expression involving aggregates (9.1 bug) +select ten, + sum(unique1) + sum(unique2) as res, + rank() over (order by sum(unique1) + sum(unique2)) as rank +from tenk1 +group by ten order by ten; + +-- window and aggregate with GROUP BY expression (9.2 bug) +explain (costs off) +select first_value(max(x)) over (), y + from (select unique1 as x, ten+four as y from tenk1) ss + group by y; + +-- test non-default frame specifications +SELECT four, ten, + sum(ten) over (partition by four order by ten), + last_value(ten) over (partition by four order by ten) +FROM (select distinct ten, four from tenk1) ss +ORDER by 1,2,3,4; + +SELECT four, ten, + sum(ten) over (partition by four order by ten range between unbounded preceding and current row), + last_value(ten) over (partition by four order by ten range between unbounded preceding and current row) +FROM (select distinct ten, four from tenk1) ss +ORDER by 1,2,3,4; + +SELECT four, ten, + sum(ten) over (partition by four order by ten range between unbounded preceding and unbounded following), + last_value(ten) over (partition by four order by ten range between unbounded preceding and unbounded following) +FROM (select distinct ten, four from tenk1) ss +ORDER by 1,2,3,4; + +SELECT four, ten/4 as two, + sum(ten/4) over (partition by four order by ten/4 range between unbounded preceding and current row), + last_value(ten/4) over (partition by four order by ten/4 range between unbounded preceding and current row) +FROM (select distinct ten, four from tenk1) ss +ORDER by 1,2,3,4; + +SELECT four, ten/4 as two, + sum(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row), + last_value(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row) +FROM (select distinct ten, four from tenk1) ss +ORDER by 1,2,3,4; + +SELECT sum(unique1) over (order by four range between current row and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by unique1 rows between current row and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by unique1 rows between 2 preceding and 2 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by unique1 rows between 2 preceding and 2 following exclude no others), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by unique1 rows between 2 preceding and 2 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +-- Changed from original window.sql (lines 218- 252): ORDER BY unique1 added. +-- See for more information: https://github.com/yugabyte/yugabyte-db/issues/4832 +SELECT first_value(unique1) over (ORDER BY unique1 rows between current row and 2 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT first_value(unique1) over (order by unique1 rows between current row and 2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT first_value(unique1) over (ORDER BY unique1 rows between current row and 2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT last_value(unique1) over (order by unique1 rows between current row and 2 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT last_value(unique1) over (order by unique1 rows between current row and 2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT last_value(unique1) over (order by unique1 rows between current row and 2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by unique1 rows between 2 preceding and 1 preceding), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by unique1 rows between 1 following and 3 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by unique1 rows between unbounded preceding and 1 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (w range between current row and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four) ORDER by 1,2,3; + +SELECT sum(unique1) over (w range between unbounded preceding and current row exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four) ORDER by 1,2,3; + +SELECT sum(unique1) over (w range between unbounded preceding and current row exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four) ORDER by 1,2,3; + +SELECT sum(unique1) over (w range between unbounded preceding and current row exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four) ORDER by 1,2,3; + +SELECT first_value(unique1) over w, + nth_value(unique1, 2) over w AS nth_2, + last_value(unique1) over w, unique1, four +FROM tenk1 WHERE unique1 < 10 +WINDOW w AS (order by unique1 range between current row and unbounded following) ORDER by 1,2,3,4,5; + +SELECT sum(unique1) over + (order by unique1 + rows (SELECT unique1 FROM tenk1 ORDER BY unique1 LIMIT 1) + 1 PRECEDING), + unique1 +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2; + +CREATE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following) as sum_rows + FROM generate_series(1, 10) i; + +SELECT * FROM v_window; + +SELECT pg_get_viewdef('v_window'); + +--TODO: remove the following drop view's after closing issue #4888 +drop view v_window; + +CREATE OR REPLACE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following + exclude current row) as sum_rows FROM generate_series(1, 10) i; + +SELECT * FROM v_window; + +SELECT pg_get_viewdef('v_window'); + +drop view v_window; + +CREATE OR REPLACE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following + exclude group) as sum_rows FROM generate_series(1, 10) i; + +SELECT * FROM v_window; + +SELECT pg_get_viewdef('v_window'); + +drop view v_window; + +CREATE OR REPLACE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following + exclude ties) as sum_rows FROM generate_series(1, 10) i; + +SELECT * FROM v_window; + +SELECT pg_get_viewdef('v_window'); + +drop view v_window; + +CREATE OR REPLACE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following + exclude no others) as sum_rows FROM generate_series(1, 10) i; + +SELECT * FROM v_window; + +SELECT pg_get_viewdef('v_window'); + +drop view v_window; + +CREATE OR REPLACE TEMP VIEW v_window AS + SELECT i, sum(i) over (order by i groups between 1 preceding and 1 following) as sum_rows FROM generate_series(1, 10) i; + +SELECT * FROM v_window; + +SELECT pg_get_viewdef('v_window'); + +DROP VIEW v_window; + +CREATE TEMP VIEW v_window AS + SELECT i, min(i) over (order by i range between '1 day' preceding and '10 days' following) as min_i + FROM generate_series(now(), now()+'100 days'::interval, '1 hour') i; + +SELECT pg_get_viewdef('v_window'); + +-- RANGE offset PRECEDING/FOLLOWING tests + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four desc range between 2::int8 preceding and 1::int2 preceding), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude no others), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::int2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::int2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following + exclude current row),unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following), + salary, enroll_date from empsalary ORDER by 1,2,3; + +select sum(salary) over (order by enroll_date desc range between '1 year'::interval preceding and '1 year'::interval following), + salary, enroll_date from empsalary ORDER by 1,2,3; + +select sum(salary) over (order by enroll_date desc range between '1 year'::interval following and '1 year'::interval following), + salary, enroll_date from empsalary ORDER by 1,2,3; + +select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following + exclude current row), salary, enroll_date from empsalary ORDER by 1,2,3; + +select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following + exclude group), salary, enroll_date from empsalary ORDER by 1,2,3; + +select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following + exclude ties), salary, enroll_date from empsalary ORDER by 1,2,3; + +select first_value(salary) over(order by salary range between 1000 preceding and 1000 following), + lead(salary) over(order by salary range between 1000 preceding and 1000 following), + nth_value(salary, 1) over(order by salary range between 1000 preceding and 1000 following), + salary from empsalary ORDER by 1,2,3,4; + +select last_value(salary) over(order by salary range between 1000 preceding and 1000 following), + lag(salary) over(order by salary range between 1000 preceding and 1000 following), + salary from empsalary ORDER by 1,2,3; + +select first_value(salary) over(order by salary range between 1000 following and 3000 following + exclude current row), + lead(salary) over(order by salary range between 1000 following and 3000 following exclude ties), + nth_value(salary, 1) over(order by salary range between 1000 following and 3000 following + exclude ties), + salary from empsalary ORDER by 1,2,3,4; + +select last_value(salary) over(order by salary range between 1000 following and 3000 following + exclude group), + lag(salary) over(order by salary range between 1000 following and 3000 following exclude group), + salary from empsalary ORDER by 1,2,3; + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude ties), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude ties), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude ties), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude group), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude group), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude current row), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude current row), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + +-- RANGE offset PRECEDING/FOLLOWING with null values +select x, y, + first_value(y) over w, + last_value(y) over w +from + (select x, x as y from generate_series(1,5) as x + union all select null, 42 + union all select null, 43) ss +window w as + (order by x asc nulls first range between 2 preceding and 2 following) +ORDER by 1,2,3,4; + +select x, y, + first_value(y) over w, + last_value(y) over w +from + (select x, x as y from generate_series(1,5) as x + union all select null, 42 + union all select null, 43) ss +window w as + (order by x asc nulls last range between 2 preceding and 2 following) +ORDER by 1,2,3,4; + +select x, y, + first_value(y) over w, + last_value(y) over w +from + (select x, x as y from generate_series(1,5) as x + union all select null, 42 + union all select null, 43) ss +window w as + (order by x desc nulls first range between 2 preceding and 2 following) +ORDER by 1,2,3,4; + +select x, y, + first_value(y) over w, + last_value(y) over w +from + (select x, x as y from generate_series(1,5) as x + union all select null, 42 + union all select null, 43) ss +window w as + (order by x desc nulls last range between 2 preceding and 2 following) +ORDER by 1,2,3,4; + +-- Check overflow behavior for various integer sizes + +select x, last_value(x) over (order by x::smallint range between current row and 2147450884 following) +from generate_series(32764, 32766) x ORDER by 1,2; + +select x, last_value(x) over (order by x::smallint desc range between current row and 2147450885 following) +from generate_series(-32766, -32764) x ORDER by 1,2; + +select x, last_value(x) over (order by x range between current row and 4 following) +from generate_series(2147483644, 2147483646) x ORDER by 1,2; + +select x, last_value(x) over (order by x desc range between current row and 5 following) +from generate_series(-2147483646, -2147483644) x ORDER by 1,2; + +select x, last_value(x) over (order by x range between current row and 4 following) +from generate_series(9223372036854775804, 9223372036854775806) x ORDER by 1,2; + +select x, last_value(x) over (order by x desc range between current row and 5 following) +from generate_series(-9223372036854775806, -9223372036854775804) x ORDER by 1,2; + +-- Test in_range for other numeric datatypes + +create temp table numerics( + id int, + f_float4 float4, + f_float8 float8, + f_numeric numeric +); + +insert into numerics values +(0, '-infinity', '-infinity', '-1000'), -- numeric type lacks infinities +(1, -3, -3, -3), +(2, -1, -1, -1), +(3, 0, 0, 0), +(4, 1.1, 1.1, 1.1), +(5, 1.12, 1.12, 1.12), +(6, 2, 2, 2), +(7, 100, 100, 100), +(8, 'infinity', 'infinity', '1000'), +(9, 'NaN', 'NaN', 'NaN'); + +select id, f_float4, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float4 range between + 1 preceding and 1 following) +ORDER by 1,2,3,4; +select id, f_float4, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float4 range between + 1 preceding and 1.1::float4 following) +ORDER by 1,2,3,4; +select id, f_float4, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float4 range between + 'inf' preceding and 'inf' following) +ORDER by 1,2,3,4; +select id, f_float4, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float4 range between + 1.1 preceding and 'NaN' following); -- error, NaN disallowed + +select id, f_float8, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float8 range between + 1 preceding and 1 following) +ORDER by 1,2,3,4; +select id, f_float8, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float8 range between + 1 preceding and 1.1::float8 following) +ORDER by 1,2,3,4; +select id, f_float8, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float8 range between + 'inf' preceding and 'inf' following) +ORDER by 1,2,3,4; +select id, f_float8, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_float8 range between + 1.1 preceding and 'NaN' following); -- error, NaN disallowed + +select id, f_numeric, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_numeric range between + 1 preceding and 1 following) +ORDER by 1,2,3,4; +select id, f_numeric, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_numeric range between + 1 preceding and 1.1::numeric following) +ORDER by 1,2,3,4; +select id, f_numeric, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_numeric range between + 1 preceding and 1.1::float8 following); -- currently unsupported +select id, f_numeric, first_value(id) over w, last_value(id) over w +from numerics +window w as (order by f_numeric range between + 1.1 preceding and 'NaN' following); -- error, NaN disallowed + +-- Test in_range for other datetime datatypes + +create temp table datetimes( + id int, + f_time time, + f_timetz timetz, + f_interval interval, + f_timestamptz timestamptz, + f_timestamp timestamp +); + +insert into datetimes values +(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'), +(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'), +(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'), +(4, '14:00', '14:00 BST', '4 years', '2002-10-19 10:23:54+01', '2002-10-19 10:23:54'), +(5, '15:00', '15:00 BST', '5 years', '2003-10-19 10:23:54+01', '2003-10-19 10:23:54'), +(6, '15:00', '15:00 BST', '5 years', '2004-10-19 10:23:54+01', '2004-10-19 10:23:54'), +(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'), +(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'), +(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'), +(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'); + +select id, f_time, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_time range between + '70 min'::interval preceding and '2 hours'::interval following) +ORDER by 1,2,3,4; + +select id, f_time, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_time desc range between + '70 min' preceding and '2 hours' following) +ORDER by 1,2,3,4; + +select id, f_timetz, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timetz range between + '70 min'::interval preceding and '2 hours'::interval following) +ORDER by 1,2,3,4; + +select id, f_timetz, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timetz desc range between + '70 min' preceding and '2 hours' following) +ORDER by 1,2,3,4; + +select id, f_interval, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_interval range between + '1 year'::interval preceding and '1 year'::interval following) +ORDER by 1,2,3,4; + +select id, f_interval, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_interval desc range between + '1 year' preceding and '1 year' following) +ORDER by 1,2,3,4; + +select id, f_timestamptz, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timestamptz range between + '1 year'::interval preceding and '1 year'::interval following) +ORDER by 1,2,3,4; + +select id, f_timestamptz, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timestamptz desc range between + '1 year' preceding and '1 year' following) +ORDER by 1,2,3,4; + +select id, f_timestamp, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timestamp range between + '1 year'::interval preceding and '1 year'::interval following) +ORDER by 1,2,3,4; + +select id, f_timestamp, first_value(id) over w, last_value(id) over w +from datetimes +window w as (order by f_timestamp desc range between + '1 year' preceding and '1 year' following) +ORDER by 1,2,3,4; + +-- RANGE offset PRECEDING/FOLLOWING error cases +select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_date from empsalary; + +select sum(salary) over (range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_date from empsalary; + +select sum(salary) over (order by depname range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_date from empsalary; + +select max(enroll_date) over (order by enroll_date range between 1 preceding and 2 following + exclude ties), salary, enroll_date from empsalary; + +select max(enroll_date) over (order by salary range between -1 preceding and 2 following + exclude ties), salary, enroll_date from empsalary; + +select max(enroll_date) over (order by salary range between 1 preceding and -2 following + exclude ties), salary, enroll_date from empsalary; + +select max(enroll_date) over (order by salary range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_date from empsalary; + +select max(enroll_date) over (order by enroll_date range between '1 year'::interval preceding and '-2 years'::interval following + exclude ties), salary, enroll_date from empsalary; + +-- GROUPS tests + +SELECT sum(unique1) over (order by four groups between unbounded preceding and current row), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four groups between unbounded preceding and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four groups between current row and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four groups between 1 preceding and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four groups between 1 following and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four groups between unbounded preceding and 2 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four groups between 2 preceding and 1 preceding), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four groups between 0 preceding and 0 following), + unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following + exclude current row), unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following + exclude group), unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following + exclude ties), unique1, four +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3; + +SELECT sum(unique1) over (partition by ten + order by four groups between 0 preceding and 0 following),unique1, four, ten +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3,4; + +SELECT sum(unique1) over (partition by ten + order by four groups between 0 preceding and 0 following exclude current row), unique1, four, ten +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3,4; + +SELECT sum(unique1) over (partition by ten + order by four groups between 0 preceding and 0 following exclude group), unique1, four, ten +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3,4; + +SELECT sum(unique1) over (partition by ten + order by four groups between 0 preceding and 0 following exclude ties), unique1, four, ten +FROM tenk1 WHERE unique1 < 10 ORDER by 1,2,3,4; + +select first_value(salary) over(order by enroll_date groups between 1 preceding and 1 following), + lead(salary) over(order by enroll_date groups between 1 preceding and 1 following), + nth_value(salary, 1) over(order by enroll_date groups between 1 preceding and 1 following), + salary, enroll_date from empsalary ORDER by 1,2,3,4,5; + +select last_value(salary) over(order by enroll_date groups between 1 preceding and 1 following), + lag(salary) over(order by enroll_date groups between 1 preceding and 1 following), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + +select first_value(salary) over(order by enroll_date groups between 1 following and 3 following + exclude current row), + lead(salary) over(order by enroll_date groups between 1 following and 3 following exclude ties), + nth_value(salary, 1) over(order by enroll_date groups between 1 following and 3 following + exclude ties), + salary, enroll_date from empsalary ORDER by 1,2,3,4,5; + +select last_value(salary) over(order by enroll_date groups between 1 following and 3 following + exclude group), + lag(salary) over(order by enroll_date groups between 1 following and 3 following exclude group), + salary, enroll_date from empsalary ORDER by 1,2,3,4; + +-- Show differences in offset interpretation between ROWS, RANGE, and GROUPS +WITH cte (x) AS ( + SELECT * FROM generate_series(1, 35, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following) +ORDER by 1,2; + +WITH cte (x) AS ( + SELECT * FROM generate_series(1, 35, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x range between 1 preceding and 1 following) +ORDER by 1,2; + +WITH cte (x) AS ( + SELECT * FROM generate_series(1, 35, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following) +ORDER by 1,2; + +WITH cte (x) AS ( + select 1 union all select 1 union all select 1 union all + SELECT * FROM generate_series(5, 49, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following) +ORDER by 1,2; + +WITH cte (x) AS ( + select 1 union all select 1 union all select 1 union all + SELECT * FROM generate_series(5, 49, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x range between 1 preceding and 1 following) +ORDER by 1,2; + +WITH cte (x) AS ( + select 1 union all select 1 union all select 1 union all + SELECT * FROM generate_series(5, 49, 2) +) +SELECT x, (sum(x) over w) +FROM cte +WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following) +ORDER by 1,2; + +-- with UNION +SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0; + +-- check some degenerate cases +create temp table t1 (f1 int, f2 int8); +insert into t1 values (1,1),(1,2),(2,2); + +select f1, sum(f1) over (partition by f1 + range between 1 preceding and 1 following) +from t1 where f1 = f2; -- error, must have order by +explain (costs off) +select f1, sum(f1) over (partition by f1 order by f2 + range between 1 preceding and 1 following) +from t1 where f1 = f2 +ORDER by 1,2; +select f1, sum(f1) over (partition by f1 order by f2 + range between 1 preceding and 1 following) +from t1 where f1 = f2 +ORDER by 1,2; +select f1, sum(f1) over (partition by f1, f1 order by f2 + range between 2 preceding and 1 preceding) +from t1 where f1 = f2 +ORDER by 1,2; +select f1, sum(f1) over (partition by f1, f2 order by f2 + range between 1 following and 2 following) +from t1 where f1 = f2 +ORDER by 1,2; + +select f1, sum(f1) over (partition by f1 + groups between 1 preceding and 1 following) +from t1 where f1 = f2; -- error, must have order by +explain (costs off) +select f1, sum(f1) over (partition by f1 order by f2 + groups between 1 preceding and 1 following) +from t1 where f1 = f2 +ORDER by 1,2; +select f1, sum(f1) over (partition by f1 order by f2 + groups between 1 preceding and 1 following) +from t1 where f1 = f2 +ORDER by 1,2; +select f1, sum(f1) over (partition by f1, f1 order by f2 + groups between 2 preceding and 1 preceding) +from t1 where f1 = f2 +ORDER by 1,2; +select f1, sum(f1) over (partition by f1, f2 order by f2 + groups between 1 following and 2 following) +from t1 where f1 = f2 +ORDER by 1,2; + +-- ordering by a non-integer constant is allowed +SELECT rank() OVER (ORDER BY length('abc')) ORDER by 1; + +-- can't order by another window function +SELECT rank() OVER (ORDER BY rank() OVER (ORDER BY random())); + +-- some other errors +SELECT * FROM empsalary WHERE row_number() OVER (ORDER BY salary) < 10; + +SELECT * FROM empsalary INNER JOIN tenk1 ON row_number() OVER (ORDER BY salary) < 10; + +SELECT rank() OVER (ORDER BY 1), count(*) FROM empsalary GROUP BY 1; + +SELECT * FROM rank() OVER (ORDER BY random()); + +DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())) > 10; + +DELETE FROM empsalary RETURNING rank() OVER (ORDER BY random()); + +SELECT count(*) OVER w FROM tenk1 WINDOW w AS (ORDER BY unique1), w AS (ORDER BY unique1); + +SELECT rank() OVER (PARTITION BY four, ORDER BY ten) FROM tenk1; + +SELECT count() OVER () FROM tenk1; + +SELECT generate_series(1, 100) OVER () FROM empsalary; + +SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1; + +SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1; + +-- filter + +SELECT sum(salary), row_number() OVER (ORDER BY depname), sum( + sum(salary) FILTER (WHERE enroll_date > '2007-01-01') +) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum", + depname +FROM empsalary GROUP BY depname ORDER by 1,2,3,4; + +-- Test pushdown of quals into a subquery containing window functions + +-- pushdown is safe because all PARTITION BY clauses include depname: +EXPLAIN (COSTS OFF) +SELECT * FROM + (SELECT depname, + sum(salary) OVER (PARTITION BY depname) depsalary, + min(salary) OVER (PARTITION BY depname || 'A', depname) depminsalary + FROM empsalary) emp +WHERE depname = 'sales'; + +-- pushdown is unsafe because there's a PARTITION BY clause without depname: +EXPLAIN (COSTS OFF) +SELECT * FROM + (SELECT depname, + sum(salary) OVER (PARTITION BY enroll_date) enroll_salary, + min(salary) OVER (PARTITION BY depname) depminsalary + FROM empsalary) emp +WHERE depname = 'sales'; + +-- cleanup +DROP TABLE empsalary; + +-- test user-defined window function with named args and default args +CREATE FUNCTION nth_value_def(val anyelement, n integer = 1) RETURNS anyelement + LANGUAGE internal WINDOW IMMUTABLE STRICT AS 'window_nth_value'; + +SELECT nth_value_def(n := 2, val := ten) OVER (PARTITION BY four), ten, four + FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s + ORDER by 1,2,3; + +SELECT nth_value_def(ten) OVER (PARTITION BY four), ten, four + FROM (SELECT * FROM tenk1 WHERE unique2 < 10 ORDER BY four, ten) s + ORDER by 1,2,3; + +-- +-- Test the basic moving-aggregate machinery +-- + +-- create aggregates that record the series of transform calls (these are +-- intentionally not true inverses) + +CREATE FUNCTION logging_sfunc_nonstrict(text, anyelement) RETURNS text AS +$$ SELECT COALESCE($1, '') || '*' || quote_nullable($2) $$ +LANGUAGE SQL IMMUTABLE; + +CREATE FUNCTION logging_msfunc_nonstrict(text, anyelement) RETURNS text AS +$$ SELECT COALESCE($1, '') || '+' || quote_nullable($2) $$ +LANGUAGE SQL IMMUTABLE; + +CREATE FUNCTION logging_minvfunc_nonstrict(text, anyelement) RETURNS text AS +$$ SELECT $1 || '-' || quote_nullable($2) $$ +LANGUAGE SQL IMMUTABLE; + +CREATE AGGREGATE logging_agg_nonstrict (anyelement) +( + stype = text, + sfunc = logging_sfunc_nonstrict, + mstype = text, + msfunc = logging_msfunc_nonstrict, + minvfunc = logging_minvfunc_nonstrict +); + +CREATE AGGREGATE logging_agg_nonstrict_initcond (anyelement) +( + stype = text, + sfunc = logging_sfunc_nonstrict, + mstype = text, + msfunc = logging_msfunc_nonstrict, + minvfunc = logging_minvfunc_nonstrict, + initcond = 'I', + minitcond = 'MI' +); + +CREATE FUNCTION logging_sfunc_strict(text, anyelement) RETURNS text AS +$$ SELECT $1 || '*' || quote_nullable($2) $$ +LANGUAGE SQL STRICT IMMUTABLE; + +CREATE FUNCTION logging_msfunc_strict(text, anyelement) RETURNS text AS +$$ SELECT $1 || '+' || quote_nullable($2) $$ +LANGUAGE SQL STRICT IMMUTABLE; + +CREATE FUNCTION logging_minvfunc_strict(text, anyelement) RETURNS text AS +$$ SELECT $1 || '-' || quote_nullable($2) $$ +LANGUAGE SQL STRICT IMMUTABLE; + +CREATE AGGREGATE logging_agg_strict (text) +( + stype = text, + sfunc = logging_sfunc_strict, + mstype = text, + msfunc = logging_msfunc_strict, + minvfunc = logging_minvfunc_strict +); + +CREATE AGGREGATE logging_agg_strict_initcond (anyelement) +( + stype = text, + sfunc = logging_sfunc_strict, + mstype = text, + msfunc = logging_msfunc_strict, + minvfunc = logging_minvfunc_strict, + initcond = 'I', + minitcond = 'MI' +); + +-- test strict and non-strict cases +SELECT + p::text || ',' || i::text || ':' || COALESCE(v::text, 'NULL') AS row, + logging_agg_nonstrict(v) over wnd as nstrict, + logging_agg_nonstrict_initcond(v) over wnd as nstrict_init, + logging_agg_strict(v::text) over wnd as strict, + logging_agg_strict_initcond(v) over wnd as strict_init +FROM (VALUES + (1, 1, NULL), + (1, 2, 'a'), + (1, 3, 'b'), + (1, 4, NULL), + (1, 5, NULL), + (1, 6, 'c'), + (2, 1, NULL), + (2, 2, 'x'), + (3, 1, 'z') +) AS t(p, i, v) +WINDOW wnd AS (PARTITION BY P ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +ORDER BY p, i; + +-- and again, but with filter +SELECT + p::text || ',' || i::text || ':' || + CASE WHEN f THEN COALESCE(v::text, 'NULL') ELSE '-' END as row, + logging_agg_nonstrict(v) filter(where f) over wnd as nstrict_filt, + logging_agg_nonstrict_initcond(v) filter(where f) over wnd as nstrict_init_filt, + logging_agg_strict(v::text) filter(where f) over wnd as strict_filt, + logging_agg_strict_initcond(v) filter(where f) over wnd as strict_init_filt +FROM (VALUES + (1, 1, true, NULL), + (1, 2, false, 'a'), + (1, 3, true, 'b'), + (1, 4, false, NULL), + (1, 5, false, NULL), + (1, 6, false, 'c'), + (2, 1, false, NULL), + (2, 2, true, 'x'), + (3, 1, true, 'z') +) AS t(p, i, f, v) +WINDOW wnd AS (PARTITION BY p ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +ORDER BY p, i; + +-- test that volatile arguments disable moving-aggregate mode +SELECT + i::text || ':' || COALESCE(v::text, 'NULL') as row, + logging_agg_strict(v::text) + over wnd as inverse, + logging_agg_strict(v::text || CASE WHEN random() < 0 then '?' ELSE '' END) + over wnd as noinverse +FROM (VALUES + (1, 'a'), + (2, 'b'), + (3, 'c') +) AS t(i, v) +WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +ORDER BY i; + +SELECT + i::text || ':' || COALESCE(v::text, 'NULL') as row, + logging_agg_strict(v::text) filter(where true) + over wnd as inverse, + logging_agg_strict(v::text) filter(where random() >= 0) + over wnd as noinverse +FROM (VALUES + (1, 'a'), + (2, 'b'), + (3, 'c') +) AS t(i, v) +WINDOW wnd AS (ORDER BY i ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +ORDER BY i; + +-- test that non-overlapping windows don't use inverse transitions +SELECT + logging_agg_strict(v::text) OVER wnd +FROM (VALUES + (1, 'a'), + (2, 'b'), + (3, 'c') +) AS t(i, v) +WINDOW wnd AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW) +ORDER BY i; + +-- test that returning NULL from the inverse transition functions +-- restarts the aggregation from scratch. The second aggregate is supposed +-- to test cases where only some aggregates restart, the third one checks +-- that one aggregate restarting doesn't cause others to restart. + +CREATE FUNCTION sum_int_randrestart_minvfunc(int4, int4) RETURNS int4 AS +$$ SELECT CASE WHEN random() < 0.2 THEN NULL ELSE $1 - $2 END $$ +LANGUAGE SQL STRICT; + +CREATE AGGREGATE sum_int_randomrestart (int4) +( + stype = int4, + sfunc = int4pl, + mstype = int4, + msfunc = int4pl, + minvfunc = sum_int_randrestart_minvfunc +); + +WITH +vs AS ( + SELECT i, (random() * 100)::int4 AS v + FROM generate_series(1, 100) AS i +), +sum_following AS ( + SELECT i, SUM(v) OVER + (ORDER BY i DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS s + FROM vs +) +SELECT DISTINCT + sum_following.s = sum_int_randomrestart(v) OVER fwd AS eq1, + -sum_following.s = sum_int_randomrestart(-v) OVER fwd AS eq2, + 100*3+(vs.i-1)*3 = length(logging_agg_nonstrict(''::text) OVER fwd) AS eq3 +FROM vs +JOIN sum_following ON sum_following.i = vs.i +WINDOW fwd AS ( + ORDER BY vs.i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +); + +-- +-- Test various built-in aggregates that have moving-aggregate support +-- + +-- test inverse transition functions handle NULLs properly +SELECT i,AVG(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,AVG(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,AVG(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1.5),(2,2.5),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,SUM(v::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,SUM(v::money) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,'1.10'),(2,'2.20'),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,SUM(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,SUM(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1.1),(2,2.2),(3,NULL),(4,NULL)) t(i,v); + +SELECT SUM(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1.01),(2,2),(3,3)) v(i,n); + +SELECT i,COUNT(v) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,COUNT(*) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + +SELECT VAR_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT VAR_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT VAR_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT VAR_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT VAR_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT VAR_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT VAR_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT VAR_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT VARIANCE(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT VARIANCE(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT VARIANCE(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT VARIANCE(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT STDDEV_POP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + +SELECT STDDEV_POP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + +SELECT STDDEV_POP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + +SELECT STDDEV_POP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + +SELECT STDDEV_SAMP(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + +SELECT STDDEV_SAMP(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + +SELECT STDDEV_SAMP(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + +SELECT STDDEV_SAMP(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(1,NULL),(2,600),(3,470),(4,170),(5,430),(6,300)) r(i,n); + +SELECT STDDEV(n::bigint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT STDDEV(n::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT STDDEV(n::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +SELECT STDDEV(n::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) + FROM (VALUES(0,NULL),(1,600),(2,470),(3,170),(4,430),(5,300)) r(i,n); + +-- test that inverse transition functions work with various frame options +SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v); + +SELECT i,SUM(v::int) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) + FROM (VALUES(1,1),(2,2),(3,3),(4,4)) t(i,v); + +-- ensure aggregate over numeric properly recovers from NaN values +SELECT a, b, + SUM(b) OVER(ORDER BY A ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) +FROM (VALUES(1,1::numeric),(2,2),(3,'NaN'),(4,3),(5,4)) t(a,b); + +-- It might be tempting for someone to add an inverse trans function for +-- float and double precision. This should not be done as it can give incorrect +-- results. This test should fail if anyone ever does this without thinking too +-- hard about it. +SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING),'999999999999999999999D9') + FROM (VALUES(1,1e20),(2,1)) n(i,n); + +SELECT i, b, bool_and(b) OVER w, bool_or(b) OVER w + FROM (VALUES (1,true), (2,true), (3,false), (4,false), (5,true)) v(i,b) + WINDOW w AS (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING); diff --git a/src/test/regress/sql/yb.port.with.sql b/src/test/regress/sql/yb.port.with.sql new file mode 100644 index 0000000000000..c0e9f67c4c2fd --- /dev/null +++ b/src/test/regress/sql/yb.port.with.sql @@ -0,0 +1,1587 @@ +-- +-- Tests for common table expressions (WITH query, ... SELECT ...) +-- + +-- Basic WITH +WITH q1(x,y) AS (SELECT 1,2) +SELECT * FROM q1, q1 AS q2; + +-- Multiple uses are evaluated only once +SELECT count(*) FROM ( + WITH q1(x) AS (SELECT random() FROM generate_series(1, 5)) + SELECT * FROM q1 + UNION + SELECT * FROM q1 +) ss; + +-- WITH RECURSIVE + +-- sum of 1..100 +WITH RECURSIVE t(n) AS ( + VALUES (1) +UNION ALL + SELECT n+1 FROM t WHERE n < 100 +) +SELECT sum(n) FROM t; + +WITH RECURSIVE t(n) AS ( + SELECT (VALUES(1)) +UNION ALL + SELECT n+1 FROM t WHERE n < 5 +) +SELECT * FROM t; + +-- UNION DISTINCT requires hashable type +WITH RECURSIVE t(n) AS ( + VALUES (1::money) +UNION + SELECT n+1::money FROM t WHERE n < 100::money +) +SELECT sum(n) FROM t; + +-- recursive view +CREATE RECURSIVE VIEW nums (n) AS + VALUES (1) +UNION ALL + SELECT n+1 FROM nums WHERE n < 5; + +SELECT * FROM nums; + +CREATE OR REPLACE RECURSIVE VIEW nums (n) AS + VALUES (1) +UNION ALL + SELECT n+1 FROM nums WHERE n < 6; + +SELECT * FROM nums; + +-- This is an infinite loop with UNION ALL, but not with UNION +WITH RECURSIVE t(n) AS ( + SELECT 1 +UNION + SELECT 10-n FROM t) +SELECT * FROM t; + +-- This'd be an infinite loop, but outside query reads only as much as needed +WITH RECURSIVE t(n) AS ( + VALUES (1) +UNION ALL + SELECT n+1 FROM t) +SELECT * FROM t LIMIT 10; + +-- UNION case should have same property +WITH RECURSIVE t(n) AS ( + SELECT 1 +UNION + SELECT n+1 FROM t) +SELECT * FROM t LIMIT 10; + +-- Test behavior with an unknown-type literal in the WITH +WITH q AS (SELECT 'foo' AS x) +SELECT x, pg_typeof(x) FROM q; + +WITH RECURSIVE t(n) AS ( + SELECT 'foo' +UNION ALL + SELECT n || ' bar' FROM t WHERE length(n) < 20 +) +SELECT n, pg_typeof(n) FROM t; + +-- In a perfect world, this would work and resolve the literal as int ... +-- but for now, we have to be content with resolving to text too soon. +WITH RECURSIVE t(n) AS ( + SELECT '7' +UNION ALL + SELECT n+1 FROM t WHERE n < 10 +) +SELECT n, pg_typeof(n) FROM t; + +-- Deeply nested WITH caused a list-munging problem in v13 +-- Detection of cross-references and self-references +WITH RECURSIVE w1(c1) AS + (WITH w2(c2) AS + (WITH w3(c3) AS + (WITH w4(c4) AS + (WITH w5(c5) AS + (WITH RECURSIVE w6(c6) AS + (WITH w6(c6) AS + (WITH w8(c8) AS + (SELECT 1) + SELECT * FROM w8) + SELECT * FROM w6) + SELECT * FROM w6) + SELECT * FROM w5) + SELECT * FROM w4) + SELECT * FROM w3) + SELECT * FROM w2) +SELECT * FROM w1; +-- Detection of invalid self-references +WITH RECURSIVE outermost(x) AS ( + SELECT 1 + UNION (WITH innermost1 AS ( + SELECT 2 + UNION (WITH innermost2 AS ( + SELECT 3 + UNION (WITH innermost3 AS ( + SELECT 4 + UNION (WITH innermost4 AS ( + SELECT 5 + UNION (WITH innermost5 AS ( + SELECT 6 + UNION (WITH innermost6 AS + (SELECT 7) + SELECT * FROM innermost6)) + SELECT * FROM innermost5)) + SELECT * FROM innermost4)) + SELECT * FROM innermost3)) + SELECT * FROM innermost2)) + SELECT * FROM outermost + UNION SELECT * FROM innermost1) + ) + SELECT * FROM outermost ORDER BY 1; + +-- +-- Some examples with a tree +-- +-- department structure represented here is as follows: +-- +-- ROOT-+->A-+->B-+->C +-- | | +-- | +->D-+->F +-- +->E-+->G + +CREATE TEMP TABLE department ( + id INTEGER PRIMARY KEY, -- department ID + parent_department INTEGER REFERENCES department, -- upper department ID + name TEXT -- department name +); + +INSERT INTO department VALUES (0, NULL, 'ROOT'); +INSERT INTO department VALUES (1, 0, 'A'); +INSERT INTO department VALUES (2, 1, 'B'); +INSERT INTO department VALUES (3, 2, 'C'); +INSERT INTO department VALUES (4, 2, 'D'); +INSERT INTO department VALUES (5, 0, 'E'); +INSERT INTO department VALUES (6, 4, 'F'); +INSERT INTO department VALUES (7, 5, 'G'); + + +-- extract all departments under 'A'. Result should be A, B, C, D and F +WITH RECURSIVE subdepartment AS +( + -- non recursive term + SELECT name as root_name, * FROM department WHERE name = 'A' + + UNION ALL + + -- recursive term + SELECT sd.root_name, d.* FROM department AS d, subdepartment AS sd + WHERE d.parent_department = sd.id +) +SELECT * FROM subdepartment ORDER BY name; + +-- extract all departments under 'A' with "level" number +WITH RECURSIVE subdepartment(level, id, parent_department, name) AS +( + -- non recursive term + SELECT 1, * FROM department WHERE name = 'A' + + UNION ALL + + -- recursive term + SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd + WHERE d.parent_department = sd.id +) +SELECT * FROM subdepartment ORDER BY name; + +-- extract all departments under 'A' with "level" number. +-- Only shows level 2 or more +WITH RECURSIVE subdepartment(level, id, parent_department, name) AS +( + -- non recursive term + SELECT 1, * FROM department WHERE name = 'A' + + UNION ALL + + -- recursive term + SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd + WHERE d.parent_department = sd.id +) +SELECT * FROM subdepartment WHERE level >= 2 ORDER BY name; + +-- "RECURSIVE" is ignored if the query has no self-reference +WITH RECURSIVE subdepartment AS +( + -- note lack of recursive UNION structure + SELECT * FROM department WHERE name = 'A' +) +SELECT * FROM subdepartment ORDER BY name; + +-- inside subqueries +SELECT count(*) FROM ( + WITH RECURSIVE t(n) AS ( + SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 500 + ) + SELECT * FROM t) AS t WHERE n < ( + SELECT count(*) FROM ( + WITH RECURSIVE t(n) AS ( + SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 100 + ) + SELECT * FROM t WHERE n < 50000 + ) AS t WHERE n < 100); + +-- use same CTE twice at different subquery levels +WITH q1(x,y) AS ( + SELECT hundred, sum(ten) FROM tenk1 GROUP BY hundred + ) +SELECT count(*) FROM q1 WHERE y > (SELECT sum(y)/100 FROM q1 qsub); + +-- via a VIEW +CREATE TEMPORARY VIEW vsubdepartment AS + WITH RECURSIVE subdepartment AS + ( + -- non recursive term + SELECT * FROM department WHERE name = 'A' + UNION ALL + -- recursive term + SELECT d.* FROM department AS d, subdepartment AS sd + WHERE d.parent_department = sd.id + ) + SELECT * FROM subdepartment; + +SELECT * FROM vsubdepartment ORDER BY name; + +-- Check reverse listing +SELECT pg_get_viewdef('vsubdepartment'::regclass); +SELECT pg_get_viewdef('vsubdepartment'::regclass, true); + +-- Another reverse-listing example +CREATE VIEW sums_1_100 AS +WITH RECURSIVE t(n) AS ( + VALUES (1) +UNION ALL + SELECT n+1 FROM t WHERE n < 100 +) +SELECT sum(n) FROM t; + +\d+ sums_1_100 + +-- corner case in which sub-WITH gets initialized first +with recursive q as ( + select * from department + union all + (with x as (select * from q) + select * from x) + ) +select * from q limit 24; + +with recursive q as ( + select * from department + union all + (with recursive x as ( + select * from department + union all + (select * from q union all select * from x) + ) + select * from x) + ) +select * from q limit 32; + +-- recursive term has sub-UNION +WITH RECURSIVE t(i,j) AS ( + VALUES (1,2) + UNION ALL + SELECT t2.i, t.j+1 FROM + (SELECT 2 AS i UNION ALL SELECT 3 AS i) AS t2 + JOIN t ON (t2.i = t.i+1)) + + SELECT * FROM t; + +-- +-- different tree example +-- +CREATE TEMPORARY TABLE tree( + id INTEGER PRIMARY KEY, + parent_id INTEGER REFERENCES tree(id) +); + +INSERT INTO tree +VALUES (1, NULL), (2, 1), (3,1), (4,2), (5,2), (6,2), (7,3), (8,3), + (9,4), (10,4), (11,7), (12,7), (13,7), (14, 9), (15,11), (16,11); + +-- +-- get all paths from "second level" nodes to leaf nodes +-- +WITH RECURSIVE t(id, path) AS ( + VALUES(1,ARRAY[]::integer[]) +UNION ALL + SELECT tree.id, t.path || tree.id + FROM tree JOIN t ON (tree.parent_id = t.id) +) +SELECT t1.*, t2.* FROM t AS t1 JOIN t AS t2 ON + (t1.path[1] = t2.path[1] AND + array_upper(t1.path,1) = 1 AND + array_upper(t2.path,1) > 1) + ORDER BY t1.id, t2.id; + +-- just count 'em +WITH RECURSIVE t(id, path) AS ( + VALUES(1,ARRAY[]::integer[]) +UNION ALL + SELECT tree.id, t.path || tree.id + FROM tree JOIN t ON (tree.parent_id = t.id) +) +SELECT t1.id, count(t2.*) FROM t AS t1 JOIN t AS t2 ON + (t1.path[1] = t2.path[1] AND + array_upper(t1.path,1) = 1 AND + array_upper(t2.path,1) > 1) + GROUP BY t1.id + ORDER BY t1.id; + +-- this variant tickled a whole-row-variable bug in 8.4devel +WITH RECURSIVE t(id, path) AS ( + VALUES(1,ARRAY[]::integer[]) +UNION ALL + SELECT tree.id, t.path || tree.id + FROM tree JOIN t ON (tree.parent_id = t.id) +) +SELECT t1.id, t2.path, t2 FROM t AS t1 JOIN t AS t2 ON +(t1.id=t2.id); + +-- SEARCH clause + +create temp table graph0( f int, t int, label text ); + +insert into graph0 values + (1, 2, 'arc 1 -> 2'), + (1, 3, 'arc 1 -> 3'), + (2, 3, 'arc 2 -> 3'), + (1, 4, 'arc 1 -> 4'), + (4, 5, 'arc 4 -> 5'); + +explain (verbose, costs off) +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select * from search_graph order by seq; + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select * from search_graph order by seq; + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union distinct + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select * from search_graph order by seq; + +explain (verbose, costs off) +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq +select * from search_graph order by seq; + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq +select * from search_graph order by seq; + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union distinct + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq +select * from search_graph order by seq; + +-- a constant initial value causes issues for EXPLAIN +explain (verbose, costs off) +with recursive test as ( + select 1 as x + union all + select x + 1 + from test +) search depth first by x set y +select * from test limit 5; + +with recursive test as ( + select 1 as x + union all + select x + 1 + from test +) search depth first by x set y +select * from test limit 5; + +explain (verbose, costs off) +with recursive test as ( + select 1 as x + union all + select x + 1 + from test +) search breadth first by x set y +select * from test limit 5; + +with recursive test as ( + select 1 as x + union all + select x + 1 + from test +) search breadth first by x set y +select * from test limit 5; + +-- various syntax errors +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by foo, tar set seq +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set label +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t, f set seq +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select * from search_graph order by seq; + +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + (select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t) +) search depth first by f, t set seq +select * from search_graph order by seq; + +-- check that we distinguish same CTE name used at different levels +-- (this case could be supported, perhaps, but it isn't today) +with recursive x(col) as ( + select 1 + union + (with x as (select * from x) + select * from x) +) search depth first by col set seq +select * from x; + +-- test ruleutils and view expansion +create temp view v_search as +with recursive search_graph(f, t, label) as ( + select * from graph0 g + union all + select g.* + from graph0 g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq +select f, t, label from search_graph; + +select pg_get_viewdef('v_search'); + +select * from v_search; + +-- +-- test cycle detection +-- +create temp table graph( f int, t int, label text ); + +insert into graph values + (1, 2, 'arc 1 -> 2'), + (1, 3, 'arc 1 -> 3'), + (2, 3, 'arc 2 -> 3'), + (1, 4, 'arc 1 -> 4'), + (4, 5, 'arc 4 -> 5'), + (5, 1, 'arc 5 -> 1'); + +with recursive search_graph(f, t, label, is_cycle, path) as ( + select *, false, array[row(g.f, g.t)] from graph g + union all + select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t) + from graph g, search_graph sg + where g.f = sg.t and not is_cycle +) +select * from search_graph; + +-- UNION DISTINCT exercises row type hashing support +with recursive search_graph(f, t, label, is_cycle, path) as ( + select *, false, array[row(g.f, g.t)] from graph g + union distinct + select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t) + from graph g, search_graph sg + where g.f = sg.t and not is_cycle +) +select * from search_graph; + +-- ordering by the path column has same effect as SEARCH DEPTH FIRST +with recursive search_graph(f, t, label, is_cycle, path) as ( + select *, false, array[row(g.f, g.t)] from graph g + union all + select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t) + from graph g, search_graph sg + where g.f = sg.t and not is_cycle +) +select * from search_graph order by path; + +-- CYCLE clause + +explain (verbose, costs off) +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle using path +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle using path +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph g + union distinct + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to 'Y' default 'N' using path +select * from search_graph; + +explain (verbose, costs off) +with recursive test as ( + select 0 as x + union all + select (x + 1) % 10 + from test +) cycle x set is_cycle using path +select * from test; + +with recursive test as ( + select 0 as x + union all + select (x + 1) % 10 + from test +) cycle x set is_cycle using path +select * from test; + +with recursive test as ( + select 0 as x + union all + select (x + 1) % 10 + from test + where not is_cycle -- redundant, but legal +) cycle x set is_cycle using path +select * from test; + +-- multiple CTEs +with recursive +graph(f, t, label) as ( + values (1, 2, 'arc 1 -> 2'), + (1, 3, 'arc 1 -> 3'), + (2, 3, 'arc 2 -> 3'), + (1, 4, 'arc 1 -> 4'), + (4, 5, 'arc 4 -> 5'), + (5, 1, 'arc 5 -> 1') +), +search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to true default false using path +select f, t, label from search_graph; + +-- star expansion +with recursive a as ( + select 1 as b + union all + select * from a +) cycle b set c using p +select * from a; + +-- search+cycle +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search depth first by f, t set seq + cycle f, t set is_cycle using path +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search breadth first by f, t set seq + cycle f, t set is_cycle using path +select * from search_graph; + +-- various syntax errors +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle foo, tar set is_cycle using path +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to true default 55 using path +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to point '(1,1)' default point '(0,0)' using path +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set label to true default false using path +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to true default false using label +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set foo to true default false using foo +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t, f set is_cycle to true default false using path +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search depth first by f, t set foo + cycle f, t set foo to true default false using path +select * from search_graph; + +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) search depth first by f, t set foo + cycle f, t set is_cycle to true default false using foo +select * from search_graph; + +-- test ruleutils and view expansion +create temp view v_cycle1 as +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle using path +select f, t, label from search_graph; + +create temp view v_cycle2 as +with recursive search_graph(f, t, label) as ( + select * from graph g + union all + select g.* + from graph g, search_graph sg + where g.f = sg.t +) cycle f, t set is_cycle to 'Y' default 'N' using path +select f, t, label from search_graph; + +select pg_get_viewdef('v_cycle1'); +select pg_get_viewdef('v_cycle2'); + +select * from v_cycle1; +select * from v_cycle2; + +-- +-- test multiple WITH queries +-- +WITH RECURSIVE + y (id) AS (VALUES (1)), + x (id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5) +SELECT * FROM x; + +-- forward reference OK +WITH RECURSIVE + x(id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5), + y(id) AS (values (1)) + SELECT * FROM x; + +WITH RECURSIVE + x(id) AS + (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5), + y(id) AS + (VALUES (1) UNION ALL SELECT id+1 FROM y WHERE id < 10) + SELECT y.*, x.* FROM y LEFT JOIN x USING (id); + +WITH RECURSIVE + x(id) AS + (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5), + y(id) AS + (VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 10) + SELECT y.*, x.* FROM y LEFT JOIN x USING (id); + +WITH RECURSIVE + x(id) AS + (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ), + y(id) AS + (SELECT * FROM x UNION ALL SELECT * FROM x), + z(id) AS + (SELECT * FROM x UNION ALL SELECT id+1 FROM z WHERE id < 10) + SELECT * FROM z; + +WITH RECURSIVE + x(id) AS + (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ), + y(id) AS + (SELECT * FROM x UNION ALL SELECT * FROM x), + z(id) AS + (SELECT * FROM y UNION ALL SELECT id+1 FROM z WHERE id < 10) + SELECT * FROM z; + +-- +-- Test WITH attached to a data-modifying statement +-- + +CREATE TEMPORARY TABLE y (a INTEGER); +INSERT INTO y SELECT generate_series(1, 10); + +WITH t AS ( + SELECT a FROM y +) +INSERT INTO y +SELECT a+20 FROM t RETURNING *; + +SELECT * FROM y; + +WITH t AS ( + SELECT a FROM y +) +UPDATE y SET a = y.a-10 FROM t WHERE y.a > 20 AND t.a = y.a RETURNING y.a; + +SELECT * FROM y; + +WITH RECURSIVE t(a) AS ( + SELECT 11 + UNION ALL + SELECT a+1 FROM t WHERE a < 50 +) +DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a; + +SELECT * FROM y; + +DROP TABLE y; + +-- +-- error cases +-- + +WITH x(n, b) AS (SELECT 1) +SELECT * FROM x; + +-- INTERSECT +WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x) + SELECT * FROM x; + +WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x) + SELECT * FROM x; + +-- EXCEPT +WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x) + SELECT * FROM x; + +WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x) + SELECT * FROM x; + +-- no non-recursive term +WITH RECURSIVE x(n) AS (SELECT n FROM x) + SELECT * FROM x; + +-- recursive term in the left hand side (strictly speaking, should allow this) +WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1) + SELECT * FROM x; + +CREATE TABLE y (a INTEGER, ybsort SERIAL, PRIMARY KEY (ybsort ASC)); -- YB: exercise YB table instead of temp table +INSERT INTO y SELECT generate_series(1, 10); + +-- LEFT JOIN + +WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1 + UNION ALL + SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10) +SELECT * FROM x; + +-- RIGHT JOIN +WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1 + UNION ALL + SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10) +SELECT * FROM x; + +-- FULL JOIN +WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1 + UNION ALL + SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10) +SELECT * FROM x; + +-- subquery +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x + WHERE n IN (SELECT * FROM x)) + SELECT * FROM x; + +-- aggregate functions +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) FROM x) + SELECT * FROM x; + +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FROM x) + SELECT * FROM x; + +-- ORDER BY +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1) + SELECT * FROM x; + +-- LIMIT/OFFSET +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1) + SELECT * FROM x; + +-- FOR UPDATE +WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x FOR UPDATE) + SELECT * FROM x; + +-- target list has a recursive query name +WITH RECURSIVE x(id) AS (values (1) + UNION ALL + SELECT (SELECT * FROM x) FROM x WHERE id < 5 +) SELECT * FROM x; + +-- mutual recursive query (not implemented) +WITH RECURSIVE + x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id < 5), + y (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 5) +SELECT * FROM x; + +-- non-linear recursion is not allowed +WITH RECURSIVE foo(i) AS + (values (1) + UNION ALL + (SELECT i+1 FROM foo WHERE i < 10 + UNION ALL + SELECT i+1 FROM foo WHERE i < 5) +) SELECT * FROM foo; + +WITH RECURSIVE foo(i) AS + (values (1) + UNION ALL + SELECT * FROM + (SELECT i+1 FROM foo WHERE i < 10 + UNION ALL + SELECT i+1 FROM foo WHERE i < 5) AS t +) SELECT * FROM foo; + +WITH RECURSIVE foo(i) AS + (values (1) + UNION ALL + (SELECT i+1 FROM foo WHERE i < 10 + EXCEPT + SELECT i+1 FROM foo WHERE i < 5) +) SELECT * FROM foo; + +WITH RECURSIVE foo(i) AS + (values (1) + UNION ALL + (SELECT i+1 FROM foo WHERE i < 10 + INTERSECT + SELECT i+1 FROM foo WHERE i < 5) +) SELECT * FROM foo; + +-- Wrong type induced from non-recursive term +WITH RECURSIVE foo(i) AS + (SELECT i FROM (VALUES(1),(2)) t(i) + UNION ALL + SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10) +SELECT * FROM foo; + +-- rejects different typmod, too (should we allow this?) +WITH RECURSIVE foo(i) AS + (SELECT i::numeric(3,0) FROM (VALUES(1),(2)) t(i) + UNION ALL + SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10) +SELECT * FROM foo; + +-- disallow OLD/NEW reference in CTE +CREATE TEMPORARY TABLE x (n integer); +CREATE RULE r2 AS ON UPDATE TO x DO INSTEAD + WITH t AS (SELECT OLD.*) UPDATE y SET a = t.n FROM t; + +-- +-- test for bug #4902 +-- +with cte(foo) as ( values(42) ) values((select foo from cte)); +with cte(foo) as ( select 42 ) select * from ((select foo from cte)) q; + +-- test CTE referencing an outer-level variable (to see that changed-parameter +-- signaling still works properly after fixing this bug) +SELECT * FROM ( -- YB +select ( with cte(foo) as ( values(f1) ) + select (select foo from cte) ) +from int4_tbl +LIMIT ALL) ybview ORDER BY (abs(foo) - sign(foo)); -- YB ordering + +SELECT * FROM ( -- YB +select ( with cte(foo) as ( values(f1) ) + values((select foo from cte)) ) +from int4_tbl +LIMIT ALL) ybview ORDER BY (abs(column1) - sign(column1)); -- YB ordering + +-- +-- test for nested-recursive-WITH bug +-- +WITH RECURSIVE t(j) AS ( + WITH RECURSIVE s(i) AS ( + VALUES (1) + UNION ALL + SELECT i+1 FROM s WHERE i < 10 + ) + SELECT i FROM s + UNION ALL + SELECT j+1 FROM t WHERE j < 10 +) +SELECT * FROM t; + +-- +-- test WITH attached to intermediate-level set operation +-- + +WITH outermost(x) AS ( + SELECT 1 + UNION (WITH innermost as (SELECT 2) + SELECT * FROM innermost + UNION SELECT 3) +) +SELECT * FROM outermost ORDER BY 1; + +WITH outermost(x) AS ( + SELECT 1 + UNION (WITH innermost as (SELECT 2) + SELECT * FROM outermost -- fail + UNION SELECT * FROM innermost) +) +SELECT * FROM outermost ORDER BY 1; + +WITH RECURSIVE outermost(x) AS ( + SELECT 1 + UNION (WITH innermost as (SELECT 2) + SELECT * FROM outermost + UNION SELECT * FROM innermost) +) +SELECT * FROM outermost ORDER BY 1; + +WITH RECURSIVE outermost(x) AS ( + WITH innermost as (SELECT 2 FROM outermost) -- fail + SELECT * FROM innermost + UNION SELECT * from outermost +) +SELECT * FROM outermost ORDER BY 1; + +-- +-- This test will fail with the old implementation of PARAM_EXEC parameter +-- assignment, because the "q1" Var passed down to A's targetlist subselect +-- looks exactly like the "A.id" Var passed down to C's subselect, causing +-- the old code to give them the same runtime PARAM_EXEC slot. But the +-- lifespans of the two parameters overlap, thanks to B also reading A. +-- + +with +A as ( select q2 as id, (select q1) as x from int8_tbl ), +B as ( select id, row_number() over (partition by id) as r from A ), +C as ( select A.id, array(select B.id from B where B.id = A.id) from A ) +select * from C; + +-- +-- Test CTEs read in non-initialization orders +-- + +WITH RECURSIVE + tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)), + iter (id_key, row_type, link) AS ( + SELECT 0, 'base', 17 + UNION ALL ( + WITH remaining(id_key, row_type, link, min) AS ( + SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER () + FROM tab INNER JOIN iter USING (link) + WHERE tab.id_key > iter.id_key + ), + first_remaining AS ( + SELECT id_key, row_type, link + FROM remaining + WHERE id_key=min + ), + effect AS ( + SELECT tab.id_key, 'new'::text, tab.link + FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key + WHERE e.row_type = 'false' + ) + SELECT * FROM first_remaining + UNION ALL SELECT * FROM effect + ) + ) +SELECT * FROM iter; + +WITH RECURSIVE + tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)), + iter (id_key, row_type, link) AS ( + SELECT 0, 'base', 17 + UNION ( + WITH remaining(id_key, row_type, link, min) AS ( + SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER () + FROM tab INNER JOIN iter USING (link) + WHERE tab.id_key > iter.id_key + ), + first_remaining AS ( + SELECT id_key, row_type, link + FROM remaining + WHERE id_key=min + ), + effect AS ( + SELECT tab.id_key, 'new'::text, tab.link + FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key + WHERE e.row_type = 'false' + ) + SELECT * FROM first_remaining + UNION ALL SELECT * FROM effect + ) + ) +SELECT * FROM iter; + +-- +-- Data-modifying statements in WITH +-- + +-- INSERT ... RETURNING +WITH t AS ( + INSERT INTO y + VALUES + (11), + (12), + (13), + (14), + (15), + (16), + (17), + (18), + (19), + (20) + RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column + +-- UPDATE ... RETURNING +WITH t AS ( + UPDATE y + SET a=a+1 + RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column + +-- DELETE ... RETURNING +WITH t AS ( + DELETE FROM y + WHERE a <= 10 + RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column + +-- forward reference +WITH RECURSIVE t AS ( + INSERT INTO y + SELECT a+5 FROM t2 WHERE a > 5 + RETURNING * +), t2 AS ( + UPDATE y SET a=a-11, ybsort=nextval('y_ybsort_seq') RETURNING * -- YB: mimic upstream PG's new ctid allocation +) +SELECT a FROM t -- YB: avoid ybsort column +UNION ALL +SELECT a FROM t2; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column + +-- unconditional DO INSTEAD rule +CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD + INSERT INTO y VALUES(42) RETURNING *; + +WITH t AS ( + DELETE FROM y RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column + +DROP RULE y_rule ON y; + +-- check merging of outer CTE with CTE in a rule action +CREATE TEMP TABLE bug6051 AS + select i from generate_series(1,3) as t(i); + +SELECT * FROM bug6051; + +WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) +INSERT INTO bug6051 SELECT * FROM t1; + +SELECT * FROM bug6051; + +CREATE TEMP TABLE bug6051_2 (i int); + +CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD + INSERT INTO bug6051_2 + VALUES(NEW.i); + +WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) +INSERT INTO bug6051 SELECT * FROM t1; + +SELECT * FROM bug6051; +SELECT * FROM bug6051_2; + +-- check INSERT...SELECT rule actions are disallowed on commands +-- that have modifyingCTEs +CREATE OR REPLACE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD + INSERT INTO bug6051_2 + SELECT NEW.i; + +WITH t1 AS ( DELETE FROM bug6051 RETURNING * ) +INSERT INTO bug6051 SELECT * FROM t1; + +-- silly example to verify that hasModifyingCTE flag is propagated +CREATE TEMP TABLE bug6051_3 AS + SELECT a FROM generate_series(11,13) AS a; + +CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD + SELECT i FROM bug6051_2; + +BEGIN; SET LOCAL force_parallel_mode = on; + +WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * ) + INSERT INTO bug6051_3 SELECT * FROM t1; + +COMMIT; + +SELECT * FROM bug6051_3; + +-- check case where CTE reference is removed due to optimization +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q1 FROM +( + WITH t_cte AS (SELECT * FROM int8_tbl t) + SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub + FROM int8_tbl i8 +) ss; + +SELECT q1 FROM +( + WITH t_cte AS (SELECT * FROM int8_tbl t) + SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub + FROM int8_tbl i8 +) ss; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q1 FROM +( + WITH t_cte AS MATERIALIZED (SELECT q1, q2 FROM int8_tbl t) -- YB: avoid ybsort column + SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub + FROM int8_tbl i8 +) ss; + +SELECT q1 FROM +( + WITH t_cte AS MATERIALIZED (SELECT * FROM int8_tbl t) + SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub + FROM int8_tbl i8 +) ss; + +-- a truly recursive CTE in the same list +WITH RECURSIVE t(a) AS ( + SELECT 0 + UNION ALL + SELECT a+1 FROM t WHERE a+1 < 5 +), t2 as ( + INSERT INTO y + SELECT * FROM t RETURNING * +) +SELECT a FROM t2 JOIN y USING (a) ORDER BY a; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column + +-- data-modifying WITH in a modifying statement +WITH t AS ( + DELETE FROM y + WHERE a <= 10 + RETURNING * +) +INSERT INTO y SELECT -a FROM t RETURNING a; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column + +-- check that WITH query is run to completion even if outer query isn't +WITH t AS ( + UPDATE y SET a = a * 100 RETURNING * +) +SELECT a FROM t LIMIT 10; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column + +-- data-modifying WITH containing INSERT...ON CONFLICT DO UPDATE +CREATE TABLE withz AS SELECT i AS k, (i || ' v')::text v FROM generate_series(1, 16, 3) i; +ALTER TABLE withz ADD UNIQUE (k); + +WITH t AS ( + INSERT INTO withz SELECT i, 'insert' + FROM generate_series(0, 16) i + ON CONFLICT (k) DO UPDATE SET v = withz.v || ', now update' + RETURNING * +) +SELECT k, v, a FROM t JOIN y ON t.k = y.a ORDER BY a, k; -- YB: avoid ybsort column + +-- Test EXCLUDED.* reference within CTE +WITH aa AS ( + INSERT INTO withz VALUES(1, 5) ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v + WHERE withz.k != EXCLUDED.k + RETURNING * +) +SELECT * FROM aa; + +-- New query/snapshot demonstrates side-effects of previous query. +SELECT * FROM withz ORDER BY k; + +-- +-- Ensure subqueries within the update clause work, even if they +-- reference outside values +-- +WITH aa AS (SELECT 1 a, 2 b) +INSERT INTO withz VALUES(1, 'insert') +ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1); +WITH aa AS (SELECT 1 a, 2 b) +INSERT INTO withz VALUES(1, 'insert') +ON CONFLICT (k) DO UPDATE SET v = ' update' WHERE withz.k = (SELECT a FROM aa); +WITH aa AS (SELECT 1 a, 2 b) +INSERT INTO withz VALUES(1, 'insert') +ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1); +WITH aa AS (SELECT 'a' a, 'b' b UNION ALL SELECT 'a' a, 'b' b) +INSERT INTO withz VALUES(1, 'insert') +ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 'a' LIMIT 1); +WITH aa AS (SELECT 1 a, 2 b) +INSERT INTO withz VALUES(1, (SELECT b || ' insert' FROM aa WHERE a = 1 )) +ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1); + +-- Update a row more than once, in different parts of a wCTE. That is +-- an allowed, presumably very rare, edge case, but since it was +-- broken in the past, having a test seems worthwhile. +WITH simpletup AS ( + SELECT 2 k, 'Green' v), +upsert_cte AS ( + INSERT INTO withz VALUES(2, 'Blue') ON CONFLICT (k) DO + UPDATE SET (k, v) = (SELECT k, v FROM simpletup WHERE simpletup.k = withz.k) + RETURNING k, v) +INSERT INTO withz VALUES(2, 'Red') ON CONFLICT (k) DO +UPDATE SET (k, v) = (SELECT k, v FROM upsert_cte WHERE upsert_cte.k = withz.k) +RETURNING k, v; -- YB: output is different from PG (see #6782) + +DROP TABLE withz; + +-- WITH referenced by MERGE statement +CREATE TABLE m AS SELECT i AS k, (i || ' v')::text v FROM generate_series(1, 16, 3) i; +ALTER TABLE m ADD UNIQUE (k); + +WITH RECURSIVE cte_basic AS (SELECT 1 a, 'cte_basic val' b) +MERGE INTO m USING (select 0 k, 'merge source SubPlan' v) o ON m.k=o.k +WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1) +WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); + +-- Basic: +WITH cte_basic AS MATERIALIZED (SELECT 1 a, 'cte_basic val' b) +MERGE INTO m USING (select 0 k, 'merge source SubPlan' v offset 0) o ON m.k=o.k +WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1) +WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); -- YB: port more queries from the original test once this query is fixed + +DROP TABLE m; + +-- check that run to completion happens in proper ordering + +TRUNCATE TABLE y; +INSERT INTO y SELECT generate_series(1, 3); +CREATE TABLE yy (a INTEGER, ybsort INT); -- YB: exercise YB table + +ALTER TABLE y DROP CONSTRAINT y_pkey; -- YB: ybsort column needs to be non-unique for the next queries +WITH RECURSIVE t1 AS ( + INSERT INTO y SELECT a FROM y ORDER BY y.ybsort RETURNING * -- YB: add ordering; avoid selecting ybsort column to auto-generate it during insert +), t2 AS ( + INSERT INTO yy SELECT * FROM t1 RETURNING * +) +SELECT 1; + +SELECT a FROM y ORDER BY ybsort; -- YB: avoid ybsort column; add ordering +SELECT a FROM yy ORDER BY ybsort; -- YB: avoid ybsort column; add ordering + +WITH RECURSIVE t1 AS ( + INSERT INTO yy SELECT * FROM t2 RETURNING * +), t2 AS ( + INSERT INTO y SELECT a FROM y ORDER BY y.ybsort RETURNING * -- YB: add ordering; avoid selecting ybsort column to auto-generate it during insert +) +SELECT 1; + +SELECT a FROM y ORDER BY ybsort; -- YB: avoid ybsort column; add ordering +SELECT a FROM yy ORDER BY ybsort; -- YB: avoid ybsort column; add ordering + +-- triggers + +TRUNCATE TABLE y; +ALTER TABLE y ADD PRIMARY KEY (ybsort ASC); -- YB: ybsort column can be unique again +INSERT INTO y SELECT generate_series(1, 10); + +CREATE FUNCTION y_trigger() RETURNS trigger AS $$ +begin + raise notice 'y_trigger: a = %', new.a; + return new; +end; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER y_trig BEFORE INSERT ON y FOR EACH ROW + EXECUTE PROCEDURE y_trigger(); + +WITH t AS ( + INSERT INTO y + VALUES + (21), + (22), + (23) + RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column + +DROP TRIGGER y_trig ON y; + +CREATE TRIGGER y_trig AFTER INSERT ON y FOR EACH ROW + EXECUTE PROCEDURE y_trigger(); + +WITH t AS ( + INSERT INTO y + VALUES + (31), + (32), + (33) + RETURNING * +) +SELECT a FROM t LIMIT 1; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column + +DROP TRIGGER y_trig ON y; + +CREATE OR REPLACE FUNCTION y_trigger() RETURNS trigger AS $$ +begin + raise notice 'y_trigger'; + return null; +end; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER y_trig AFTER INSERT ON y FOR EACH STATEMENT + EXECUTE PROCEDURE y_trigger(); + +WITH t AS ( + INSERT INTO y + VALUES + (41), + (42), + (43) + RETURNING * +) +SELECT a FROM t; -- YB: avoid ybsort column + +SELECT a FROM y; -- YB: avoid ybsort column + +DROP TRIGGER y_trig ON y; +DROP FUNCTION y_trigger(); + +-- WITH attached to inherited UPDATE or DELETE + +CREATE TEMP TABLE parent ( id int, val text ); +CREATE TEMP TABLE child1 ( ) INHERITS ( parent ); +CREATE TEMP TABLE child2 ( ) INHERITS ( parent ); +INSERT INTO parent VALUES ( 1, 'p1' ); +INSERT INTO child1 VALUES ( 11, 'c11' ),( 12, 'c12' ); +INSERT INTO child2 VALUES ( 23, 'c21' ),( 24, 'c22' ); +WITH rcte AS ( SELECT sum(id) AS totalid FROM parent ) +UPDATE parent SET id = id + totalid FROM rcte; +SELECT * FROM parent; +WITH wcte AS ( INSERT INTO child1 VALUES ( 42, 'new' ) RETURNING id AS newid ) +UPDATE parent SET id = id + newid FROM wcte; +SELECT * FROM parent; +WITH rcte AS ( SELECT max(id) AS maxid FROM parent ) +DELETE FROM parent USING rcte WHERE id = maxid; +SELECT * FROM parent; +WITH wcte AS ( INSERT INTO child2 VALUES ( 42, 'new2' ) RETURNING id AS newid ) +DELETE FROM parent USING wcte WHERE id = newid; +SELECT * FROM parent; +-- check EXPLAIN VERBOSE for a wCTE with RETURNING +EXPLAIN (VERBOSE, COSTS OFF) +WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 ) +DELETE FROM a_star USING wcte WHERE aa = q2; + +-- error cases + +-- data-modifying WITH tries to use its own output +WITH RECURSIVE t AS ( + INSERT INTO y + SELECT * FROM t +) +VALUES(FALSE); -- YB: it's unclear why the line number in output is different from upstream. + +-- no RETURNING in a referenced data-modifying WITH +WITH t AS ( + INSERT INTO y VALUES(0) +) +SELECT * FROM t; + +-- data-modifying WITH allowed only at the top level +SELECT * FROM ( + WITH t AS (UPDATE y SET a=a+1 RETURNING *) + SELECT * FROM t +) ss; + +-- most variants of rules aren't allowed +CREATE RULE y_rule AS ON INSERT TO y WHERE a=0 DO INSTEAD DELETE FROM y; +WITH t AS ( + INSERT INTO y VALUES(0) +) +VALUES(FALSE); +CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTHING; +WITH t AS ( + INSERT INTO y VALUES(0) +) +VALUES(FALSE); +CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTIFY foo; +WITH t AS ( + INSERT INTO y VALUES(0) +) +VALUES(FALSE); +CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO ALSO NOTIFY foo; +WITH t AS ( + INSERT INTO y VALUES(0) +) +VALUES(FALSE); +CREATE OR REPLACE RULE y_rule AS ON INSERT TO y + DO INSTEAD (NOTIFY foo; NOTIFY bar); +WITH t AS ( + INSERT INTO y VALUES(0) +) +VALUES(FALSE); +DROP RULE y_rule ON y; + +-- check that parser lookahead for WITH doesn't cause any odd behavior +create table foo (with baz); -- fail, WITH is a reserved word +create table foo (with ordinality); -- fail, WITH is a reserved word +with ordinality as (select 1 as x) select * from ordinality; + +-- check sane response to attempt to modify CTE relation +WITH with_test AS (SELECT 42) INSERT INTO with_test VALUES (1); + +-- check response to attempt to modify table with same name as a CTE (perhaps +-- surprisingly it works, because CTEs don't hide tables from data-modifying +-- statements) +create temp table with_test (i int); +with with_test as (select 42) insert into with_test select * from with_test; +select * from with_test; +drop table with_test; diff --git a/src/test/regress/sql/yb.port.xmlmap.sql b/src/test/regress/sql/yb.port.xmlmap.sql new file mode 100644 index 0000000000000..16582bf6abd8a --- /dev/null +++ b/src/test/regress/sql/yb.port.xmlmap.sql @@ -0,0 +1,63 @@ +CREATE SCHEMA testxmlschema; + +CREATE TABLE testxmlschema.test1 (a int, b text); +INSERT INTO testxmlschema.test1 VALUES (1, 'one'), (2, 'two'), (-1, null); +CREATE DOMAIN testxmldomain AS varchar; +CREATE TABLE testxmlschema.test2 (z int, y varchar(500), x char(6), + w numeric(9,2), v smallint, u bigint, t real, + s time, stz timetz, r timestamp, rtz timestamptz, q date, + p xml, o testxmldomain, n bool, m bytea, aaa text); +ALTER TABLE testxmlschema.test2 DROP COLUMN aaa; +INSERT INTO testxmlschema.test2 VALUES (55, 'abc', 'def', + 98.6, 2, 999, 0, + '21:07', '21:11 +05', '2009-06-08 21:07:30', '2009-06-08 21:07:30 -07', '2009-06-08', + NULL, 'ABC', true, 'XYZ'); + +SELECT table_to_xml('testxmlschema.test1', false, false, ''); +SELECT table_to_xml('testxmlschema.test1', true, false, 'foo'); +SELECT table_to_xml('testxmlschema.test1', false, true, ''); +SELECT table_to_xml('testxmlschema.test1', true, true, ''); +SELECT table_to_xml('testxmlschema.test2', false, false, ''); + +SELECT table_to_xmlschema('testxmlschema.test1', false, false, ''); +SELECT table_to_xmlschema('testxmlschema.test1', true, false, ''); +SELECT table_to_xmlschema('testxmlschema.test1', false, true, 'foo'); +SELECT table_to_xmlschema('testxmlschema.test1', true, true, ''); +SELECT table_to_xmlschema('testxmlschema.test2', false, false, ''); + +SELECT table_to_xml_and_xmlschema('testxmlschema.test1', false, false, ''); +SELECT table_to_xml_and_xmlschema('testxmlschema.test1', true, false, ''); +SELECT table_to_xml_and_xmlschema('testxmlschema.test1', false, true, ''); +SELECT table_to_xml_and_xmlschema('testxmlschema.test1', true, true, 'foo'); + +SELECT query_to_xml('SELECT * FROM testxmlschema.test1', false, false, ''); +SELECT query_to_xmlschema('SELECT * FROM testxmlschema.test1', false, false, ''); +SELECT query_to_xml_and_xmlschema('SELECT * FROM testxmlschema.test1', true, true, ''); + +DECLARE xc CURSOR WITH HOLD FOR SELECT * FROM testxmlschema.test1 ORDER BY 1, 2; +SELECT cursor_to_xml('xc'::refcursor, 5, false, true, ''); +SELECT cursor_to_xmlschema('xc'::refcursor, false, true, ''); +MOVE BACKWARD ALL IN xc; +SELECT cursor_to_xml('xc'::refcursor, 5, true, false, ''); +SELECT cursor_to_xmlschema('xc'::refcursor, true, false, ''); + +SELECT schema_to_xml('testxmlschema', false, true, ''); +SELECT schema_to_xml('testxmlschema', true, false, ''); +SELECT schema_to_xmlschema('testxmlschema', false, true, ''); +SELECT schema_to_xmlschema('testxmlschema', true, false, ''); +SELECT schema_to_xml_and_xmlschema('testxmlschema', true, true, 'foo'); + + +-- test that domains are transformed like their base types + +CREATE DOMAIN testboolxmldomain AS bool; +CREATE DOMAIN testdatexmldomain AS date; + +CREATE TABLE testxmlschema.test3 + AS SELECT true c1, + true::testboolxmldomain c2, + '2013-02-21'::date c3, + '2013-02-21'::testdatexmldomain c4; + +SELECT xmlforest(c1, c2, c3, c4) FROM testxmlschema.test3; +SELECT table_to_xml('testxmlschema.test3', true, true, ''); diff --git a/src/test/regress/yb_analyze_schedule b/src/test/regress/yb_analyze_schedule new file mode 100644 index 0000000000000..af7ec03ad4225 --- /dev/null +++ b/src/test/regress/yb_analyze_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_analyze_schedule +# +#################################################################################################### +# This testsuite contains testcases pertinent to YB ANALYZE command. +#################################################################################################### +test: yb.orig.analyze +test: yb.orig.analyze_system_tables diff --git a/src/test/regress/yb_arrays_schedule b/src/test/regress/yb_arrays_schedule new file mode 100644 index 0000000000000..084cb5494c5f5 --- /dev/null +++ b/src/test/regress/yb_arrays_schedule @@ -0,0 +1,12 @@ +# src/test/regress/yb_arrays_schedule +# +#################################################################################################### +# This suite includes tests on array datatypes. Some are ported from PostgreSQL original tests; +# others are created from Yugabyte. +#################################################################################################### +# Dependency for `yb.port.arrays` +test: yb.port.test_setup + +test: yb.port.arrays +test: yb.orig.int2_array +test: yb.orig.int4_array diff --git a/src/test/regress/yb_async_schedule b/src/test/regress/yb_async_schedule new file mode 100644 index 0000000000000..8f25ff9ef2515 --- /dev/null +++ b/src/test/regress/yb_async_schedule @@ -0,0 +1,2 @@ +# src/test/regress/yb_async_schedule +test: yb.orig.async diff --git a/src/test/regress/yb_auth_schedule b/src/test/regress/yb_auth_schedule new file mode 100644 index 0000000000000..a79a5a3cc5bf4 --- /dev/null +++ b/src/test/regress/yb_auth_schedule @@ -0,0 +1,5 @@ +# src/test/regress/yb_auth_schedule +# This schedule is for non-ported tests only. + +test: yb.orig.roles +test: yb.orig.rowsecurity diff --git a/src/test/regress/yb_beta_extensions_schedule b/src/test/regress/yb_beta_extensions_schedule new file mode 100644 index 0000000000000..b5aa80b59c416 --- /dev/null +++ b/src/test/regress/yb_beta_extensions_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_beta_extensions_schedule +# +#################################################################################################### +# Extensions Testsuites +#################################################################################################### +test: yb.orig.beta_extensions diff --git a/src/test/regress/yb_big_parallelquery_serial_schedule b/src/test/regress/yb_big_parallelquery_serial_schedule new file mode 100644 index 0000000000000..4b720b2d1d7b7 --- /dev/null +++ b/src/test/regress/yb_big_parallelquery_serial_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_big_parallelquery_serial_schedule +# +#################################################################################################### +# Postgres parallel query feature on big tables. +#################################################################################################### + +test: yb.orig.parallel_big_colocated diff --git a/src/test/regress/yb_bitmap_plan_schedule b/src/test/regress/yb_bitmap_plan_schedule new file mode 100644 index 0000000000000..e396ef0d7a41a --- /dev/null +++ b/src/test/regress/yb_bitmap_plan_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_bitmap_plan_schedule +# +#################################################################################################### +# Optimizer's Bitmap Plan Choices for YB Relations. +#################################################################################################### +test: yb.orig.bitmap_scan_plan diff --git a/src/test/regress/yb_bitmap_scans_schedule b/src/test/regress/yb_bitmap_scans_schedule new file mode 100644 index 0000000000000..9e6ae2fa0c8be --- /dev/null +++ b/src/test/regress/yb_bitmap_scans_schedule @@ -0,0 +1,11 @@ +# src/test/regress/yb_bitmap_scans_schedule +# +#################################################################################################### +# Bitmap Scans for YB Relations (Bitmap Index Scan + YB Bitmap Table Scan). +#################################################################################################### +test: yb.orig.bitmap_scans_flags +test: yb.orig.bitmap_scans +test: yb.orig.bitmap_scans_colo +test: yb.orig.bitmap_scans_joins +test: yb.orig.bitmap_scans_system +test: yb.orig.bitmap_scans_distinct diff --git a/src/test/regress/yb_buffering_with_sql_errors_schedule b/src/test/regress/yb_buffering_with_sql_errors_schedule new file mode 100644 index 0000000000000..00350d4d69325 --- /dev/null +++ b/src/test/regress/yb_buffering_with_sql_errors_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_buffering_with_sql_errors_schedule +# +#################################################################################################### +# This includes tests related to buffering in PL/pgSQL. +#################################################################################################### +test: yb.orig.buffering_with_sql_errors diff --git a/src/test/regress/yb_catalog_cache_schedule b/src/test/regress/yb_catalog_cache_schedule new file mode 100644 index 0000000000000..4e6b8d472829b --- /dev/null +++ b/src/test/regress/yb_catalog_cache_schedule @@ -0,0 +1,5 @@ +# Dependency for yb_cacheinfo and yb_catalog_cache_hashing +test: yb.orig.create_function + +test: yb.orig.cacheinfo +test: yb.orig.catalog_cache_hashing diff --git a/src/test/regress/yb_colocated_tables_with_tablespaces_schedule b/src/test/regress/yb_colocated_tables_with_tablespaces_schedule new file mode 100644 index 0000000000000..4dae10e016f90 --- /dev/null +++ b/src/test/regress/yb_colocated_tables_with_tablespaces_schedule @@ -0,0 +1,4 @@ +#################################################################################################### +# This testsuite contains testcases pertinent to YB Colocated Tables with Tablespaces. +#################################################################################################### +test: yb.orig.colocated_tables_with_tablespaces diff --git a/src/test/regress/yb_commands/bitmap_hint_iter_Q2.sql b/src/test/regress/yb_commands/bitmap_hint_iter_Q2.sql new file mode 100644 index 0000000000000..5a675b742a2c8 --- /dev/null +++ b/src/test/regress/yb_commands/bitmap_hint_iter_Q2.sql @@ -0,0 +1,4 @@ +-- Per-P BitmapScan hint: set Q2 from current :P, then iterate Q. +-- Unquoted so :P is expanded (\set does not expand variables in single quotes). +\set Q2 /*+ BitmapScan(:P) */ +\i :iter_Q2 diff --git a/src/test/regress/yb_commands/index_scan_null_queries.sql b/src/test/regress/yb_commands/index_scan_null_queries.sql new file mode 100644 index 0000000000000..4d7d52fca3783 --- /dev/null +++ b/src/test/regress/yb_commands/index_scan_null_queries.sql @@ -0,0 +1,60 @@ +-- Queries for the null scan key tests + +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename +\set P1 ':explain' +\set P2 +\set explain 'EXPLAIN (ANALYZE, COSTS OFF, DIST ON, TIMING OFF, SUMMARY OFF)' + +SET client_min_messages = DEBUG1; +\set YB_DISABLE_ERROR_PREFIX on +SET yb_bnl_batch_size to 1; + +-- Should return empty results (actual rows=0) +-- The plans should not show any "Recheck" +\set Q1 '/*+ IndexScan(t1) NestLoop(t2 t1) Leading((t2 t1)) */' +\set query ':explain :Q1 SELECT * FROM nulltest t1 JOIN nulltest2 t2 ON t1.a = t2.x;' +:query + +\set query ':explain :Q1 SELECT * FROM nulltest t1 JOIN nulltest2 t2 ON t1.a <= t2.x;' +:query + +\set query ':explain :Q1 SELECT * FROM nulltest t1 JOIN nulltest2 t2 ON t1.a BETWEEN t2.x AND t2.x + 2;' +:query + +\set query ':explain :Q1 SELECT * FROM nulltest t1 JOIN nulltest2 t2 ON (t1.a, t1.b) = (t2.x, t2.y);' +:query + +\set query ':explain :Q1 SELECT * FROM nulltest t1 JOIN nulltest2 t2 ON (t1.a, t1.b) <= (t2.x, t2.y);' +:query + +\set Q1 '/*+ IndexScan(t1) */' +\set Q2 '/*+ IndexScan(t1 i_nulltest_ba) */' +\set query ':explain :Q SELECT * FROM nulltest t1 WHERE (a, b) <= (null, 1);' +\i :iter_Q2 + +\set query ':explain :Q SELECT a FROM nulltest t1 WHERE a IN (null, null);' +\i :iter_Q2 + + +-- Should return 1s +\set Q1 '/*+ IndexScan(t1) */' +\set Q2 '/*+ IndexScan(t1 i_nulltest_ba) */' +\set query ':P :Q SELECT a FROM nulltest t1 WHERE a IN (null, 1);' +\set Pnext :iter_Q2 +\i :iter_P2 + +/*+ IndexScan(t1) */ +\set query ':P :Q SELECT a FROM nulltest t1 WHERE (a, b) <= (2, null);' +\i :iter_P2 + + +-- Should return nulls +\set Q1 '/*+ IndexScan(t1) */' +\set query ':P :Q1 SELECT a FROM nulltest t1 WHERE a IS NULL;' +\set Pnext :iter_query +\i :iter_P2 + +RESET client_min_messages; +\unset YB_DISABLE_ERROR_PREFIX diff --git a/src/test/regress/yb_commands/iter_P2.sql b/src/test/regress/yb_commands/iter_P2.sql new file mode 100644 index 0000000000000..427d685c747c1 --- /dev/null +++ b/src/test/regress/yb_commands/iter_P2.sql @@ -0,0 +1,8 @@ +\set _echo_P :ECHO +\set ECHO none +-- Iterate P over P1, P2. Set :Pnext to the next iterator or :iter_query. +\set P :P1 +\i :Pnext +\set P :P2 +\i :Pnext +\set ECHO :_echo_P diff --git a/src/test/regress/yb_commands/iter_P3.sql b/src/test/regress/yb_commands/iter_P3.sql new file mode 100644 index 0000000000000..e549f10444cfb --- /dev/null +++ b/src/test/regress/yb_commands/iter_P3.sql @@ -0,0 +1,10 @@ +\set _echo_P :ECHO +\set ECHO none +-- Iterate P over P1..P3. Set :Pnext to the next iterator or :iter_query. +\set P :P1 +\i :Pnext +\set P :P2 +\i :Pnext +\set P :P3 +\i :Pnext +\set ECHO :_echo_P diff --git a/src/test/regress/yb_commands/iter_P4.sql b/src/test/regress/yb_commands/iter_P4.sql new file mode 100644 index 0000000000000..90018d76e50c6 --- /dev/null +++ b/src/test/regress/yb_commands/iter_P4.sql @@ -0,0 +1,12 @@ +\set _echo_P :ECHO +\set ECHO none +-- Iterate P over P1..P4. Set :Pnext to the next iterator or :iter_query. +\set P :P1 +\i :Pnext +\set P :P2 +\i :Pnext +\set P :P3 +\i :Pnext +\set P :P4 +\i :Pnext +\set ECHO :_echo_P diff --git a/src/test/regress/yb_commands/iter_P5.sql b/src/test/regress/yb_commands/iter_P5.sql new file mode 100644 index 0000000000000..b10914258426c --- /dev/null +++ b/src/test/regress/yb_commands/iter_P5.sql @@ -0,0 +1,14 @@ +\set _echo_P :ECHO +\set ECHO none +-- Iterate P over P1..P5. Set :Pnext to the next iterator or :iter_query. +\set P :P1 +\i :Pnext +\set P :P2 +\i :Pnext +\set P :P3 +\i :Pnext +\set P :P4 +\i :Pnext +\set P :P5 +\i :Pnext +\set ECHO :_echo_P diff --git a/src/test/regress/yb_commands/iter_Q2.sql b/src/test/regress/yb_commands/iter_Q2.sql new file mode 100644 index 0000000000000..dd6e29ed0e417 --- /dev/null +++ b/src/test/regress/yb_commands/iter_Q2.sql @@ -0,0 +1,8 @@ +\set _echo_Q :ECHO +\set ECHO none +-- Iterate Q over Q1, Q2. Set :Qnext to the next iterator or :iter_query. +\set Q :Q1 +\i :Qnext +\set Q :Q2 +\i :Qnext +\set ECHO :_echo_Q diff --git a/src/test/regress/yb_commands/iter_Q3.sql b/src/test/regress/yb_commands/iter_Q3.sql new file mode 100644 index 0000000000000..529d0bac83c6f --- /dev/null +++ b/src/test/regress/yb_commands/iter_Q3.sql @@ -0,0 +1,10 @@ +\set _echo_Q :ECHO +\set ECHO none +-- Iterate Q over Q1..Q3. Set :Qnext to the next iterator or :iter_query. +\set Q :Q1 +\i :Qnext +\set Q :Q2 +\i :Qnext +\set Q :Q3 +\i :Qnext +\set ECHO :_echo_Q diff --git a/src/test/regress/yb_commands/iter_Q4.sql b/src/test/regress/yb_commands/iter_Q4.sql new file mode 100644 index 0000000000000..39b0490083dea --- /dev/null +++ b/src/test/regress/yb_commands/iter_Q4.sql @@ -0,0 +1,12 @@ +\set _echo_Q :ECHO +\set ECHO none +-- Iterate Q over Q1..Q4. Set :Qnext to the next iterator or :iter_query. +\set Q :Q1 +\i :Qnext +\set Q :Q2 +\i :Qnext +\set Q :Q3 +\i :Qnext +\set Q :Q4 +\i :Qnext +\set ECHO :_echo_Q diff --git a/src/test/regress/yb_commands/iter_Q5.sql b/src/test/regress/yb_commands/iter_Q5.sql new file mode 100644 index 0000000000000..b4b2cf6d11086 --- /dev/null +++ b/src/test/regress/yb_commands/iter_Q5.sql @@ -0,0 +1,14 @@ +\set _echo_Q :ECHO +\set ECHO none +-- Iterate Q over Q1..Q5. Set :Qnext to the next iterator or :iter_query. +\set Q :Q1 +\i :Qnext +\set Q :Q2 +\i :Qnext +\set Q :Q3 +\i :Qnext +\set Q :Q4 +\i :Qnext +\set Q :Q5 +\i :Qnext +\set ECHO :_echo_Q diff --git a/src/test/regress/yb_commands/iter_R2.sql b/src/test/regress/yb_commands/iter_R2.sql new file mode 100644 index 0000000000000..7dac07bd73902 --- /dev/null +++ b/src/test/regress/yb_commands/iter_R2.sql @@ -0,0 +1,8 @@ +\set _echo_R :ECHO +\set ECHO none +-- Iterate R over R1, R2. Set :Rnext to the next iterator or :iter_query. +\set R :R1 +\i :Rnext +\set R :R2 +\i :Rnext +\set ECHO :_echo_R diff --git a/src/test/regress/yb_commands/iter_R3.sql b/src/test/regress/yb_commands/iter_R3.sql new file mode 100644 index 0000000000000..b3bd5d1287f0a --- /dev/null +++ b/src/test/regress/yb_commands/iter_R3.sql @@ -0,0 +1,10 @@ +\set _echo_R :ECHO +\set ECHO none +-- Iterate R over R1..R3. Set :Rnext to the next iterator or :iter_query. +\set R :R1 +\i :Rnext +\set R :R2 +\i :Rnext +\set R :R3 +\i :Rnext +\set ECHO :_echo_R diff --git a/src/test/regress/yb_commands/iter_R4.sql b/src/test/regress/yb_commands/iter_R4.sql new file mode 100644 index 0000000000000..992479a3e315e --- /dev/null +++ b/src/test/regress/yb_commands/iter_R4.sql @@ -0,0 +1,12 @@ +\set _echo_R :ECHO +\set ECHO none +-- Iterate R over R1..R4. Set :Rnext to the next iterator or :iter_query. +\set R :R1 +\i :Rnext +\set R :R2 +\i :Rnext +\set R :R3 +\i :Rnext +\set R :R4 +\i :Rnext +\set ECHO :_echo_R diff --git a/src/test/regress/yb_commands/iter_R5.sql b/src/test/regress/yb_commands/iter_R5.sql new file mode 100644 index 0000000000000..3b929234db5dc --- /dev/null +++ b/src/test/regress/yb_commands/iter_R5.sql @@ -0,0 +1,14 @@ +\set _echo_R :ECHO +\set ECHO none +-- Iterate R over R1..R5. Set :Rnext to the next iterator or :iter_query. +\set R :R1 +\i :Rnext +\set R :R2 +\i :Rnext +\set R :R3 +\i :Rnext +\set R :R4 +\i :Rnext +\set R :R5 +\i :Rnext +\set ECHO :_echo_R diff --git a/src/test/regress/yb_commands/iter_query.sql b/src/test/regress/yb_commands/iter_query.sql new file mode 100644 index 0000000000000..6a232aeac2fee --- /dev/null +++ b/src/test/regress/yb_commands/iter_query.sql @@ -0,0 +1,3 @@ +-- Terminal iterator: executes :query. +\set YB_DISABLE_ERROR_PREFIX on +:query diff --git a/src/test/regress/yb_commands/legacy_join_plan_queries.sql b/src/test/regress/yb_commands/legacy_join_plan_queries.sql new file mode 100644 index 0000000000000..7263f88421816 --- /dev/null +++ b/src/test/regress/yb_commands/legacy_join_plan_queries.sql @@ -0,0 +1,91 @@ +-- +-- m x n, 1 x n and semijoin on pk, unique secondary and non-unique secondary +-- indexed column. +-- + +-- hash pk +explain (costs off, summary off) +select * from t1 join t2 on t1.a = t2.k1; + +explain (costs off, summary off) +select * from t1 join t2 on t1.id = 7 and t1.a = t2.k1; + +explain (costs off, summary off) +select * from t1 where exists (select 0 from t2 where t1.a = t2.k1); + + +-- unique secondary hash index +explain (costs off, summary off) +select * from t1 join t2 on t1.a = t2.k2; + +explain (costs off, summary off) +select * from t1 join t2 on t1.id = 7 and t1.a = t2.k2; + +explain (costs off, summary off) +select * from t1 where exists (select 0 from t2 where t1.a = t2.k2); + + +-- non-unique secondary hash index +explain (costs off, summary off) +select * from t1 join t2 on t1.a = t2.k3; + +explain (costs off, summary off) +select * from t1 join t2 on t1.id = 7 and t1.a = t2.k3; + +explain (costs off, summary off) +select * from t1 where exists (select 0 from t2 where t1.a = t2.k3); + + +-- unique secondary hash index partial key match +explain (costs off, summary off) +select * from t1 join t2 on t1.a = t2.k4; + +explain (costs off, summary off) +select * from t1 join t2 on t1.id = 7 and t1.a = t2.k4; + +explain (costs off, summary off) +select * from t1 where exists (select 0 from t2 where t1.a = t2.k4); + + +-- pk +explain (costs off, summary off) +select * from t1 join t2r t2 on t1.a = t2.k1; + +explain (costs off, summary off) +select * from t1 join t2r t2 on t1.id = 7 and t1.a = t2.k1; + +explain (costs off, summary off) +select * from t1 where exists (select 0 from t2r t2 where t1.a = t2.k1); + + +-- unique secondary index +explain (costs off, summary off) +select * from t1 join t2r t2 on t1.a = t2.k2; + +explain (costs off, summary off) +select * from t1 join t2r t2 on t1.id = 7 and t1.a = t2.k2; + +explain (costs off, summary off) +select * from t1 where exists (select 0 from t2r t2 where t1.a = t2.k2); + + +-- non-unique secondary index +explain (costs off, summary off) +select * from t1 join t2r t2 on t1.a = t2.k3; + +explain (costs off, summary off) +select * from t1 join t2r t2 on t1.id = 7 and t1.a = t2.k3; + +explain (costs off, summary off) +select * from t1 where exists (select 0 from t2r t2 where t1.a = t2.k3); + + +-- unique secondary index partial key match +explain (costs off, summary off) +select * from t1 join t2r t2 on t1.a = t2.k4; + +explain (costs off, summary off) +select * from t1 join t2r t2 on t1.id = 7 and t1.a = t2.k4; + +explain (costs off, summary off) +select * from t1 where exists (select 0 from t2r t2 where t1.a = t2.k4); diff --git a/src/test/regress/yb_commands/legacy_join_plan_queries_with_costs.sql b/src/test/regress/yb_commands/legacy_join_plan_queries_with_costs.sql new file mode 100644 index 0000000000000..17d9aa0ebdbda --- /dev/null +++ b/src/test/regress/yb_commands/legacy_join_plan_queries_with_costs.sql @@ -0,0 +1,4 @@ +explain (costs on, summary off) +select * from t1 join t2 on t2.k1 = t1.id join t2 t3 on t3.k3 = t1.id +where t3.k4 in (select t5.k2 from t1 t4 join t2r t5 on t4.id = t5.k3 and t4.id = 222) + and t2.v = false and t3.k2 <> 123; diff --git a/src/test/regress/yb_commands/merge_scan_setup.sql b/src/test/regress/yb_commands/merge_scan_setup.sql new file mode 100644 index 0000000000000..0da562c38e063 --- /dev/null +++ b/src/test/regress/yb_commands/merge_scan_setup.sql @@ -0,0 +1,12 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set filename :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :filename + +\set explain 'EXPLAIN (ANALYZE, DIST, VERBOSE, COSTS OFF, SUMMARY OFF, TIMING OFF)' +\set off '/*+Set(yb_max_merge_scan_streams 0)*/' +\set on '/*+Set(yb_max_merge_scan_streams 64)*/' +\set P1 ':explain' +\set P2 +\set Q1 ':off' +\set Q2 ':on' +\set Pnext :iter_Q2 diff --git a/src/test/regress/yb_commands/parallel_snapshot_switching_query.sql b/src/test/regress/yb_commands/parallel_snapshot_switching_query.sql new file mode 100644 index 0000000000000..1c6de10a30cb3 --- /dev/null +++ b/src/test/regress/yb_commands/parallel_snapshot_switching_query.sql @@ -0,0 +1,11 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set pq_param :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :pq_param +\set P1 'EXPLAIN (COSTS OFF)' +\set P2 +\set query ':P SELECT count(*) FROM pss_test WHERE pss_snapshot_fn(k) = k;' +BEGIN; +\i :iter_P2 +COMMIT; +-- Also run outside a transaction block. +\i :iter_P2 diff --git a/src/test/regress/yb_commands/parallel_snapshot_switching_query_with_gather_merge.sql b/src/test/regress/yb_commands/parallel_snapshot_switching_query_with_gather_merge.sql new file mode 100644 index 0000000000000..7dc4d48966559 --- /dev/null +++ b/src/test/regress/yb_commands/parallel_snapshot_switching_query_with_gather_merge.sql @@ -0,0 +1,12 @@ +\getenv abs_srcdir PG_ABS_SRCDIR +\set pq_param :abs_srcdir '/yb_commands/parameterized_query.sql' +\i :pq_param +\set P1 'EXPLAIN (COSTS OFF)' +\set P2 +-- The ORDER BY on the primary key with a parallel scan should produce a Gather Merge plan. +\set query ':P SELECT count(*) FROM (SELECT k FROM pss_test WHERE pss_snapshot_fn(k) = k ORDER BY k) t;' +BEGIN; +\i :iter_P2 +COMMIT; +-- Also run outside a transaction block. +\i :iter_P2 diff --git a/src/test/regress/yb_commands/parameterized_query.sql b/src/test/regress/yb_commands/parameterized_query.sql new file mode 100644 index 0000000000000..8b89d62916ce0 --- /dev/null +++ b/src/test/regress/yb_commands/parameterized_query.sql @@ -0,0 +1,51 @@ +\set _echo_pq :ECHO +\set ECHO none +-- Infrastructure for running a parameterized query in regress tests. +-- Define :query with :P, :Q, :R placeholders, assign parameter values +-- (P1, P2, ...), and use iter files to iterate over them. Each dimension +-- chains to the next via Pnext, Qnext, Rnext (all default to :iter_query). +-- +-- 1D iteration over hints: +-- \set P1 '/*+Set(guc off)*/' +-- \set P2 '/*+Set(guc on)*/' +-- \set query ':P SELECT * FROM t ORDER BY k LIMIT 5;' +-- \i :iter_P2 +-- +-- 2D iteration over hints x tables: +-- \set P1 '/*+Set(guc off)*/' +-- \set P2 '/*+Set(guc on)*/' +-- \set Pnext :iter_Q2 +-- \set Q1 'table_a' +-- \set Q2 'table_b' +-- \set query ':P SELECT * FROM :Q ORDER BY k LIMIT 5;' +-- \i :iter_P2 +-- +-- Explain-then-run pattern (all explains before all runs): +-- \set P1 ':explain' +-- \set P2 +-- \set explain 'EXPLAIN (COSTS OFF)' +-- \set query ':P SELECT * FROM t ORDER BY k LIMIT 5;' +-- \i :iter_P2 + +-- File paths for parameter iteration. +\getenv abs_srcdir PG_ABS_SRCDIR +\set iter_query :abs_srcdir '/yb_commands/iter_query.sql' +\set iter_P2 :abs_srcdir '/yb_commands/iter_P2.sql' +\set iter_P3 :abs_srcdir '/yb_commands/iter_P3.sql' +\set iter_P4 :abs_srcdir '/yb_commands/iter_P4.sql' +\set iter_P5 :abs_srcdir '/yb_commands/iter_P5.sql' +\set iter_Q2 :abs_srcdir '/yb_commands/iter_Q2.sql' +\set iter_Q3 :abs_srcdir '/yb_commands/iter_Q3.sql' +\set iter_Q4 :abs_srcdir '/yb_commands/iter_Q4.sql' +\set iter_Q5 :abs_srcdir '/yb_commands/iter_Q5.sql' +\set iter_R2 :abs_srcdir '/yb_commands/iter_R2.sql' +\set iter_R3 :abs_srcdir '/yb_commands/iter_R3.sql' +\set iter_R4 :abs_srcdir '/yb_commands/iter_R4.sql' +\set iter_R5 :abs_srcdir '/yb_commands/iter_R5.sql' + +-- Default terminal iterators: execute the query after the last value. +\set Pnext :iter_query +\set Qnext :iter_query +\set Rnext :iter_query + +\set ECHO :_echo_pq diff --git a/src/test/regress/yb_commands/planner_size_estimates_cmds.sql b/src/test/regress/yb_commands/planner_size_estimates_cmds.sql new file mode 100644 index 0000000000000..7ce72d4b919a9 --- /dev/null +++ b/src/test/regress/yb_commands/planner_size_estimates_cmds.sql @@ -0,0 +1,62 @@ +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT * FROM s_10000; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT * FROM s_1000; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT * FROM s_100; + +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT val1 FROM s_10000 WHERE val1 <= 4000; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT val FROM s_1000 WHERE val <= 4000; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT val FROM s_100 WHERE val <= 4000; + +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT val1 FROM s_10000 WHERE val1 <= 40; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT val FROM s_1000 WHERE val <= 40; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT val FROM s_100 WHERE val <= 40; + +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT val1 FROM s_10000 WHERE val1 <= 40 AND val1 > 20; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT val1 FROM s_10000 WHERE val1 <= 40 AND val1 > 20 AND val1 > 10; + +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT id FROM s_10000 WHERE id <= 4000; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT id FROM s_1000 WHERE id <= 4000; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT id FROM s_100 WHERE id <= 4000; + +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT id FROM s_10000 WHERE id <= 40; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT id FROM s_1000 WHERE id <= 40; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT id FROM s_100 WHERE id <= 40; + +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT id FROM s_10000 WHERE id <= 40 AND id > 20; +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT id FROM s_10000 WHERE id <= 40 AND id > 20 AND id > 10; + + +CREATE INDEX s_10000_val1 on s_10000(val1); +CREATE INDEX s_10000_val2 on s_10000(val2); +CREATE INDEX s_10000_val3 on s_10000(val3); + +-- PK index should prioritize primary key index in YB if selectivity is same for both indexes. +-- This is because PK index in YB is always an IndexOnlyScan whereas secondary index could be an +-- IndexScan and not an Index only scan if necessary columns are not included. +-- a. SINGLE ROW SELECTIVITY +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT * FROM s_10000 WHERE id = 2200 AND r = 2200 AND val2 = 200; +-- b. SINGLE KEY SELECTIVITY +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT * FROM s_10000 WHERE id = 2200 AND val2 = 200; + +-- When you have multiple secondary indexes, the default cost model chooses the index that first +-- present in the list of qualifiers as a part of the query. This is because, both the indexes have +-- selectivity values equal to HASH SCAN SELECTIVITY. However, we should choose the index that is +-- the best. +EXPLAIN (ANALYZE, SUMMARY off, TIMING off) SELECT * FROM s_10000 WHERE val3 = 20 AND val2 = 220; + +DROP INDEX s_10000_val1; +DROP INDEX s_10000_val2; +DROP INDEX s_10000_val3; + +-- We have to make sure that selectivity does not apply for non YB tables (eg. foreign tables) +CREATE EXTENSION file_fdw; +COPY (SELECT * FROM s_100) TO '/tmp/tmp.csv' DELIMITER ','; +CREATE SERVER file_fdw_test_srv FOREIGN DATA WRAPPER file_fdw; +CREATE FOREIGN TABLE temp_foreign ( + id INT, + val INT +) SERVER file_fdw_test_srv +OPTIONS (format 'csv', filename '/tmp/tmp.csv', header 'TRUE'); +EXPLAIN SELECT * FROM temp_foreign; +DROP FOREIGN TABLE temp_foreign; +DROP SERVER file_fdw_test_srv CASCADE; +DROP EXTENSION file_fdw; diff --git a/src/test/regress/yb_ctid_schedule b/src/test/regress/yb_ctid_schedule new file mode 100644 index 0000000000000..84221962e038e --- /dev/null +++ b/src/test/regress/yb_ctid_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_ctid_schedule +# +#################################################################################################### +# This suite includes ybctid tests. +#################################################################################################### +test: yb.orig.ctid diff --git a/src/test/regress/yb_cursor_low_prefetching_schedule b/src/test/regress/yb_cursor_low_prefetching_schedule new file mode 100644 index 0000000000000..fab234b8ccd01 --- /dev/null +++ b/src/test/regress/yb_cursor_low_prefetching_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_cursor_low_prefetching_schedule +# +#################################################################################################### +# This suite includes tests on CURSOR with low prefetching. +#################################################################################################### +test: yb.orig.cursor_preserve_in_txn_limit diff --git a/src/test/regress/yb_cursor_schedule b/src/test/regress/yb_cursor_schedule new file mode 100644 index 0000000000000..6dc4a92e15725 --- /dev/null +++ b/src/test/regress/yb_cursor_schedule @@ -0,0 +1,20 @@ +# src/test/regress/yb_cursor_schedule +# +#################################################################################################### +# This suite includes tests on CURSOR. +#################################################################################################### +# Generic cursor test. +test: yb.orig.cursor + +# Dependencies. +test: yb.depd.onek +test: yb.depd.tenk1 +test: yb.depd.tenk2 +test: yb.depd.onek2 + +# Run portals tests. +test: yb.port.portals +test: portals_p2 + +# Test on cursor for temp table. +test: yb.port.combocid diff --git a/src/test/regress/yb_ddl_savepoint_schedule b/src/test/regress/yb_ddl_savepoint_schedule new file mode 100644 index 0000000000000..4d2ca57f15c8c --- /dev/null +++ b/src/test/regress/yb_ddl_savepoint_schedule @@ -0,0 +1,9 @@ +# src/test/regress/yb_ddl_savepoint_schedule +# +############################################################################### +# This testsuite contains testcases related to DDL Savepoint support. +############################################################################### +test: yb.orig.ddl_savepoint +test: yb.orig.ddl_exceptions +test: yb.orig.ddl_rc_rollback +test: yb.orig.ddl_savepoint_toggle diff --git a/src/test/regress/yb_ddl_txn_block_schedule b/src/test/regress/yb_ddl_txn_block_schedule new file mode 100644 index 0000000000000..df362c3495314 --- /dev/null +++ b/src/test/regress/yb_ddl_txn_block_schedule @@ -0,0 +1,15 @@ +# src/test/regress/yb_ddl_txn_block_schedule +# +############################################################################### +# This testsuite contains testcases related to DDL Transaction Blocks. +############################################################################### +test: yb.orig.ddl_txn_block +test: yb.orig.ddl_txn_isolation +test: yb.orig.ddl_txn_triggers +test: yb.orig.ddl_txn_event_triggers +test: yb.orig.ddl_txn_recursive_functions +test: yb.orig.ddl_txn_procedures +test: yb.orig.ddl_txn_visibility +test: yb.orig.ddl_txn_fk +test: yb.orig.ddl_txn_copy +test: yb.orig.ddl_txn_indices diff --git a/src/test/regress/yb_distinct_pushdown_schedule b/src/test/regress/yb_distinct_pushdown_schedule new file mode 100644 index 0000000000000..839140b650864 --- /dev/null +++ b/src/test/regress/yb_distinct_pushdown_schedule @@ -0,0 +1,8 @@ +# src/test/regress/yb_distinct_pushdown_schedule +# +#################################################################################################### +# Postgres Testsuites: This suite includes tests on distinct pushdown. +#################################################################################################### +test: yb.orig.distinct_pushdown_base +test: yb.orig.distinct_pushdown_pred +test: yb.orig.distinct_pushdown_join diff --git a/src/test/regress/yb_dml_serial_schedule b/src/test/regress/yb_dml_serial_schedule new file mode 100644 index 0000000000000..6cffc89dc7699 --- /dev/null +++ b/src/test/regress/yb_dml_serial_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_dml_serial_schedule +test: yb.orig.dml_insert_conflict +test: yb.orig.dml_read_time +test: yb.orig.dml_pushdown +test: yb.orig.dml_scankey +test: yb.orig.dml +test: yb.orig.dml_derived_equalities diff --git a/src/test/regress/yb_extensions_schedule b/src/test/regress/yb_extensions_schedule new file mode 100644 index 0000000000000..a7fc8e4466205 --- /dev/null +++ b/src/test/regress/yb_extensions_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_extensions_schedule +# +#################################################################################################### +# Extensions Testsuites +#################################################################################################### +test: yb.orig.extensions +test: yb.orig.hints diff --git a/src/test/regress/yb_feature_colocation_schedule b/src/test/regress/yb_feature_colocation_schedule new file mode 100644 index 0000000000000..1a390a37009f3 --- /dev/null +++ b/src/test/regress/yb_feature_colocation_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_feature_colocation_schedule +# +#################################################################################################### +# Colocation Testsuites +#################################################################################################### +test: yb.orig.feature_colocation diff --git a/src/test/regress/yb_feature_database_serial_schedule b/src/test/regress/yb_feature_database_serial_schedule new file mode 100644 index 0000000000000..579a51dc1d6db --- /dev/null +++ b/src/test/regress/yb_feature_database_serial_schedule @@ -0,0 +1,8 @@ +# src/test/regress/yb_feature_database_serial_schedule +# +#################################################################################################### +# YB Feature Database Testsuite: Used for reporting supported and unsupported features. +# This testsuite does some basic check for database features. +#################################################################################################### +test: yb.orig.feature_alter_rename +test: yb.orig.feature_db diff --git a/src/test/regress/yb_feature_legacy_colocation_schedule b/src/test/regress/yb_feature_legacy_colocation_schedule new file mode 100644 index 0000000000000..c6d0e3a2e0e4d --- /dev/null +++ b/src/test/regress/yb_feature_legacy_colocation_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_feature_legacy_colocation_schedule +# +#################################################################################################### +# Colocation Testsuites +#################################################################################################### +test: yb.orig.feature_legacy_colocation diff --git a/src/test/regress/yb_feature_partition_serial_schedule b/src/test/regress/yb_feature_partition_serial_schedule new file mode 100644 index 0000000000000..e22303cf66ad2 --- /dev/null +++ b/src/test/regress/yb_feature_partition_serial_schedule @@ -0,0 +1,10 @@ +# src/test/regress/yb_feature_partition_serial_schedule +# +#################################################################################################### +# YB Feature Partition Testsuite: Used for reporting supported and unsupported features. +# This testsuite does some basic check for partitioning features. +#################################################################################################### +test: yb.orig.feature_hash_types +test: yb.orig.feature_range_types +test: yb.orig.feature_pk_split +test: yb.orig.feature_pk_split_desc diff --git a/src/test/regress/yb_feature_serial_schedule b/src/test/regress/yb_feature_serial_schedule new file mode 100644 index 0000000000000..6ddc2e5704eef --- /dev/null +++ b/src/test/regress/yb_feature_serial_schedule @@ -0,0 +1,17 @@ +# src/test/regress/yb_feature_serial_schedule +# +#################################################################################################### +# YB Feature Testsuite: Used for reporting supported and unsupported features. +# This testsuite walks through the syntax rules and quickly checks +# whether or not a feature is supported. +#################################################################################################### +test: yb.orig.feature_types +test: yb.orig.feature_insert +test: yb.orig.feature_delete +test: yb.orig.feature_update +test: yb.orig.feature_select +test: yb.orig.feature_config +test: yb.orig.feature_copy +test: yb.orig.feature_copyselect +test: yb.orig.feature_alter_table +test: yb.orig.feature_temp diff --git a/src/test/regress/yb_fetch_limit_schedule b/src/test/regress/yb_fetch_limit_schedule new file mode 100644 index 0000000000000..80a79f746e598 --- /dev/null +++ b/src/test/regress/yb_fetch_limit_schedule @@ -0,0 +1,8 @@ +#################################################################################################### +# Prefetch Limit Testsuite +# Test for setting row / size prefetch limits +# * yb_fetch_row_limit +# * yb_fetch_size_limit +#################################################################################################### +test: yb.orig.fetch_limits +test: yb.orig.fetch_limits_joins diff --git a/src/test/regress/yb_foreign_data_schedule b/src/test/regress/yb_foreign_data_schedule new file mode 100644 index 0000000000000..71a6c176e49be --- /dev/null +++ b/src/test/regress/yb_foreign_data_schedule @@ -0,0 +1,3 @@ +# src/test/regress/yb_foreign_data_schedule +# This schedule includes YB-introduced tests related to postgres foreign data. +test: yb.orig.foreign_data diff --git a/src/test/regress/yb_foreign_key_schedule b/src/test/regress/yb_foreign_key_schedule new file mode 100644 index 0000000000000..0ac204d943538 --- /dev/null +++ b/src/test/regress/yb_foreign_key_schedule @@ -0,0 +1,2 @@ +# src/test/regress/yb_foreign_key_schedule +test: yb.orig.foreign_key diff --git a/src/test/regress/yb_gin_schedule b/src/test/regress/yb_gin_schedule new file mode 100644 index 0000000000000..371fca64fb492 --- /dev/null +++ b/src/test/regress/yb_gin_schedule @@ -0,0 +1,16 @@ +# src/test/regress/yb_gin_schedule +# +################################################################################ +# YB GIN Testsuite: Yugabyte-owned (non-ported) gin index tests. +################################################################################ +# Upstream postgres ported tests. +test: yb.port.gin + +test: yb.orig.gin +test: yb.orig.ybgin +test: yb.orig.ybgin_nulls +test: yb.orig.ybgin_search_modes +test: yb.orig.ybgin_misc +test: yb.orig.ybgin_cost_estimate +test: yb.orig.ybgin_operators +test: yb.orig.ybgin_pushdown diff --git a/src/test/regress/yb_hash_in_schedule b/src/test/regress/yb_hash_in_schedule new file mode 100644 index 0000000000000..1157bedca9542 --- /dev/null +++ b/src/test/regress/yb_hash_in_schedule @@ -0,0 +1,8 @@ +# src/test/regress/yb_hash_in_schedule +# +#################################################################################################### +# This testsuite includes tests on IN conditions on hash keys. These tests rely on +# the YsqlRequestLimit flag being set to 1. +# The tests in this schedule are not ported from PostgreSQL original tests +#################################################################################################### +test: yb.orig.hash_in_queries diff --git a/src/test/regress/yb_hash_index_serial_schedule b/src/test/regress/yb_hash_index_serial_schedule new file mode 100644 index 0000000000000..d3c0aca26d6c7 --- /dev/null +++ b/src/test/regress/yb_hash_index_serial_schedule @@ -0,0 +1,9 @@ +# src/test/regress/yb_large_table_serial_schedule +# +#################################################################################################### +# LARGE TABLE Testsuite +# Test for table of 50K rows. +#################################################################################################### +test: yb.orig.create_large_table_indexes +test: yb.orig.large_copy +test: yb.orig.index_selectivity diff --git a/src/test/regress/yb_index_check_schedule b/src/test/regress/yb_index_check_schedule new file mode 100644 index 0000000000000..97f6d6d730d70 --- /dev/null +++ b/src/test/regress/yb_index_check_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_index_check_schedule +# +################################################################################ +# YB index consistency checker tests. +################################################################################ + +test: yb.orig.yb_index_check diff --git a/src/test/regress/yb_index_schedule b/src/test/regress/yb_index_schedule new file mode 100644 index 0000000000000..0e436ce0a41a3 --- /dev/null +++ b/src/test/regress/yb_index_schedule @@ -0,0 +1,15 @@ +# src/test/regress/yb_index_schedule +# This schedule is for non-ported tests only. +test: yb.orig.index_including +test: yb.orig.indexing_order +test: yb.orig.create_index +test: yb.orig.create_index_update_reltuples +test: yb.orig.index_scan +test: yb.orig.reindex +test: yb.orig.secondary_index_scan +test: yb.orig.index_recheck +test: yb.orig.index_only_scan_decoded_pk + +test: yb.orig.index_scan_null_create +test: yb.orig.index_scan_null_hash +test: yb.orig.index_scan_null_asc diff --git a/src/test/regress/yb_inherit_schedule b/src/test/regress/yb_inherit_schedule new file mode 100644 index 0000000000000..d5d21cebee0d5 --- /dev/null +++ b/src/test/regress/yb_inherit_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_inherit_schedule +# +#################################################################################################### +# Triggers Testsuites +#################################################################################################### + +test: yb.orig.inherit diff --git a/src/test/regress/yb_insert_on_conflict_schedule b/src/test/regress/yb_insert_on_conflict_schedule new file mode 100644 index 0000000000000..d10d015cd22d3 --- /dev/null +++ b/src/test/regress/yb_insert_on_conflict_schedule @@ -0,0 +1,8 @@ +# Dependency for yb.orig.insert_on_conflict. +test: yb.orig.create_function + +test: yb.orig.insert_on_conflict +test: yb.orig.insert_on_conflict_multiplan +test: yb.orig.insert_on_conflict_with +test: yb.orig.insert_on_conflict_with_spi +test: yb.orig.insert_on_conflict_with_explain diff --git a/src/test/regress/yb_join_plans_schedule b/src/test/regress/yb_join_plans_schedule new file mode 100644 index 0000000000000..f83ec4ba913f0 --- /dev/null +++ b/src/test/regress/yb_join_plans_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_join_plans_schedule +# +#################################################################################################### +# This includes tests for Join plans +#################################################################################################### +test: yb.orig.join_batching_plans +test: yb.orig.legacy_join_plans diff --git a/src/test/regress/yb_join_schedule b/src/test/regress/yb_join_schedule new file mode 100644 index 0000000000000..9bf34890106f1 --- /dev/null +++ b/src/test/regress/yb_join_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_join_schedule +# +#################################################################################################### +# This includes tests for Joins +#################################################################################################### +test: yb.orig.join_batching +test: yb.orig.join_batching_parallel diff --git a/src/test/regress/yb_large_table_backfill_index_schedule b/src/test/regress/yb_large_table_backfill_index_schedule new file mode 100644 index 0000000000000..7e5ac2beb8c29 --- /dev/null +++ b/src/test/regress/yb_large_table_backfill_index_schedule @@ -0,0 +1,11 @@ +#################################################################################################### +# LARGE TABLE Testsuite +# Test for table of 50K rows. +#################################################################################################### +test: yb.orig.create_large_table +test: yb.orig.large_copy +# Ensure copy succeed +test: yb.orig.select_large +test: yb.orig.create_index_large_table_backfill_index +# Ensure backfill succeeded +test: yb.orig.select_large_index_backfill diff --git a/src/test/regress/yb_large_table_serial_schedule b/src/test/regress/yb_large_table_serial_schedule new file mode 100644 index 0000000000000..0cd19fa9e6801 --- /dev/null +++ b/src/test/regress/yb_large_table_serial_schedule @@ -0,0 +1,14 @@ +# src/test/regress/yb_large_table_serial_schedule +# +#################################################################################################### +# LARGE TABLE Testsuite +# Test for table of 50K rows. +#################################################################################################### +test: yb.orig.create_large_table +test: yb.orig.large_copy +test: yb.orig.select_large +test: yb.orig.large_copy_abort +test: yb.orig.select_large_abort +test: yb.orig.large_copy_ddl +test: yb.orig.select_large_ddl +test: yb.orig.update_large diff --git a/src/test/regress/yb_large_table_tablet_split_schedule b/src/test/regress/yb_large_table_tablet_split_schedule new file mode 100644 index 0000000000000..15f70b6965fba --- /dev/null +++ b/src/test/regress/yb_large_table_tablet_split_schedule @@ -0,0 +1,7 @@ +#################################################################################################### +# LARGE TABLE Testsuite +# Test for table of 50K rows. +#################################################################################################### +test: yb.orig.create_large_table_tablet_split +test: yb.orig.large_copy +test: yb.orig.select_large diff --git a/src/test/regress/yb_levenshtein_pushdown_schedule b/src/test/regress/yb_levenshtein_pushdown_schedule new file mode 100644 index 0000000000000..45f2ee1f58322 --- /dev/null +++ b/src/test/regress/yb_levenshtein_pushdown_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_distinct_pushdown_schedule +# +#################################################################################################### +# Postgres Testsuites: This suite includes tests on distinct pushdown. +#################################################################################################### +test: yb.orig.levenshtein_pushdown diff --git a/src/test/regress/yb_limit_schedule b/src/test/regress/yb_limit_schedule new file mode 100644 index 0000000000000..759470d59b9a3 --- /dev/null +++ b/src/test/regress/yb_limit_schedule @@ -0,0 +1,9 @@ +#################################################################################################### +# Limit Testsuite +# Test for FETCH FIRST and LIMIT clause +# In Vanilla PG, this test is part of parallel_schedule +#################################################################################################### +test: yb.port.test_setup +test: yb.port.create_index + +test: yb.port.limit diff --git a/src/test/regress/yb_matview_schedule b/src/test/regress/yb_matview_schedule new file mode 100644 index 0000000000000..40d8c8ad2a46d --- /dev/null +++ b/src/test/regress/yb_matview_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_matview_schedule +# +# This file is for non-ported tests only. +#################################################################################################### +# Tests for materialized views +#################################################################################################### +test: yb.orig.matview diff --git a/src/test/regress/yb_merge_scan_schedule b/src/test/regress/yb_merge_scan_schedule new file mode 100644 index 0000000000000..7b5196060279a --- /dev/null +++ b/src/test/regress/yb_merge_scan_schedule @@ -0,0 +1,34 @@ +# src/test/regress/yb_merge_scan_schedule +# +# Test pggate merge sorting of IN queries that are not part of the sort order. +# +# The main structure of the tests in this schedule is to compare running a +# query with merge scan on and off. The explain plan should have "Merge ..." +# fields under index scan nodes to indicate that. Keep in mind that some +# negative tests expect merge scan to NOT be used: those should only explain +# analyze and not run the queries because there's no point in running the +# queries. +# +# SELECT targets generally follow a pattern of this order: +# 1. Columns equal to constant +# 1. Columns part of sorting, where non-key column "n" is always the last sort +# column (except for cases where it is absolutely not needed such as +# DISTINCT and GROUP BY) in order to provide consistently ordered results +# 1. Columns part of SAOP +# 1. Optionally, other columns + +# Setup is used by tests in later lines. +test: yb.orig.merge_scan_setup + +# This test reads r5n. +test: yb.orig.merge_scan_temp + +# These tests each touch different tables. +test: yb.orig.merge_scan_range +test: yb.orig.merge_scan_hash +test: yb.orig.merge_scan_partition +test: yb.orig.merge_scan_colocated + +# These tests touch multiple tables. +test: yb.orig.merge_scan_join +test: yb.orig.merge_scan_derive diff --git a/src/test/regress/yb_misc_catalog_version_increment_schedule b/src/test/regress/yb_misc_catalog_version_increment_schedule new file mode 100644 index 0000000000000..eeeaab8155394 --- /dev/null +++ b/src/test/regress/yb_misc_catalog_version_increment_schedule @@ -0,0 +1,10 @@ +# src/test/regress/yb_misc_catalog_version_increment_schedule +# +#################################################################################################### +# This testsuite includes tests that test the catalog version increment when +# yb_test_make_all_ddl_statements_incrementing is set to true. This needs to be a +# separate schedule because the flag requires a cluster restart to apply. +# The tests in this schedule are not ported from PostgreSQL original tests. +#################################################################################################### + +test: yb.orig.catalog_version_increment diff --git a/src/test/regress/yb_misc_independent_schedule b/src/test/regress/yb_misc_independent_schedule new file mode 100644 index 0000000000000..027e33a02b547 --- /dev/null +++ b/src/test/regress/yb_misc_independent_schedule @@ -0,0 +1,16 @@ +# src/test/regress/yb_misc_independent_schedule +# +#################################################################################################### +# This testsuite includes independent tests that do not dependent on each other. +# The tests in this schedule are not ported from PostgreSQL original tests. +#################################################################################################### + +test: yb.orig.catalog_version +test: yb.orig.alter_operator +test: yb.orig.boolean +test: yb.orig.errors +test: yb.orig.truncate +test: yb.orig.local_tablets +test: yb.orig.generated +test: yb.orig.tablet_metadata +test: yb.orig.yb_get_tablet_for_key diff --git a/src/test/regress/yb_misc_serial2_schedule b/src/test/regress/yb_misc_serial2_schedule new file mode 100644 index 0000000000000..12ba99b87bf4c --- /dev/null +++ b/src/test/regress/yb_misc_serial2_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_misc_serial2_schedule +# +#################################################################################################### +# See comments in src/test/regress/yb_misc_serial_schedule. +#################################################################################################### +test: yb.orig.get_range_split_clause diff --git a/src/test/regress/yb_misc_serial3_schedule b/src/test/regress/yb_misc_serial3_schedule new file mode 100644 index 0000000000000..3e421363dd4e7 --- /dev/null +++ b/src/test/regress/yb_misc_serial3_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_misc_serial3_schedule +# +#################################################################################################### +# See comments in src/test/regress/yb_misc_serial_schedule. +#################################################################################################### +test: yb.orig.create_table_like diff --git a/src/test/regress/yb_misc_serial4_schedule b/src/test/regress/yb_misc_serial4_schedule new file mode 100644 index 0000000000000..9cd2dc4193777 --- /dev/null +++ b/src/test/regress/yb_misc_serial4_schedule @@ -0,0 +1,9 @@ +# src/test/regress/yb_misc_serial4_schedule +# +#################################################################################################### +# See comments in src/test/regress/yb_misc_serial_schedule. +#################################################################################################### +test: yb.orig.explicit_row_lock_planning +test: yb.orig.explicit_row_lock_batching +test: yb.port.fast_default +test: yb.orig.with diff --git a/src/test/regress/yb_misc_serial5_schedule b/src/test/regress/yb_misc_serial5_schedule new file mode 100644 index 0000000000000..2f7707a1d9e04 --- /dev/null +++ b/src/test/regress/yb_misc_serial5_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_misc_serial5_schedule +# +#################################################################################################### +# See comments in src/test/regress/yb_misc_serial_schedule. +#################################################################################################### +test: yb.orig.util +test: yb.orig.explain diff --git a/src/test/regress/yb_misc_serial_schedule b/src/test/regress/yb_misc_serial_schedule new file mode 100644 index 0000000000000..e157598a1543e --- /dev/null +++ b/src/test/regress/yb_misc_serial_schedule @@ -0,0 +1,22 @@ +# src/test/regress/yb_misc_serial_schedule +# +#################################################################################################### +# This testsuite includes miscellaneous tests that are not yet characterized to a proper testsuite. +# This schedule and its siblings schedules (yb_misc_serialN_schedule) are designed to contain a +# reasonable number of tests in order to avoid test timeout for each of these schedules. +# Because some tests are larger and take longer time to run, the number of tests in each +# schedule can be quite different: one schedule can contain quite a few tests if each test +# is small, another schedule can contain only one test if the test is large. +# The tests in these schedules are not ported from PostgreSQL original tests. +#################################################################################################### +test: yb.port.expressions +test: yb.orig.compound_key +test: yb.orig.schema +test: yb.orig.select +test: yb.orig.guc +test: yb.orig.depend +test: yb.orig.query_consistent_snapshot +test: yb.orig.obj_properties_functions +test: yb.orig.dependency +test: yb.orig.create_language +test: yb.orig.view diff --git a/src/test/regress/yb_non_txn_copy_schedule b/src/test/regress/yb_non_txn_copy_schedule new file mode 100644 index 0000000000000..53fb28de91d18 --- /dev/null +++ b/src/test/regress/yb_non_txn_copy_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_non_txn_copy_schedule +# +#################################################################################################### +# This test schedule runs on a cluster with the non-transactional COPY gflag enabled. +#################################################################################################### +test: yb.orig.non_txn_copy diff --git a/src/test/regress/yb_parallel_plans_schedule b/src/test/regress/yb_parallel_plans_schedule new file mode 100644 index 0000000000000..058fd11fa53f8 --- /dev/null +++ b/src/test/regress/yb_parallel_plans_schedule @@ -0,0 +1,6 @@ +# src/test/regress/parallel_plans_schedule +# +#################################################################################################### +# Tests for planner's parallel vs. serial plan choices +#################################################################################################### +test: yb.orig.parallel_plans diff --git a/src/test/regress/yb_parallelquery_serial_schedule b/src/test/regress/yb_parallelquery_serial_schedule new file mode 100644 index 0000000000000..d9b1b53616948 --- /dev/null +++ b/src/test/regress/yb_parallelquery_serial_schedule @@ -0,0 +1,16 @@ +# src/test/regress/yb_parallelquery_serial_schedule +# +#################################################################################################### +# Postgres parallel query feature. +#################################################################################################### +# Dependencies for yb.orig.select_parallel. +test: yb.depd.tenk1 +test: yb.depd.tenk2 +test: yb.depd.int4_tbl + +test: yb.orig.parallel_hashed +test: yb.orig.parallel_ranged +test: yb.orig.parallel_colocated +test: yb.orig.select_parallel +test: yb.orig.parallel_append +test: yb.orig.parallel_snapshot_switching diff --git a/src/test/regress/yb_partition_prune_schedule b/src/test/regress/yb_partition_prune_schedule new file mode 100644 index 0000000000000..2d230a20219f7 --- /dev/null +++ b/src/test/regress/yb_partition_prune_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_partition_prune_schedule +# +########################################################################## +test: yb.port.partition_prune +test: yb.orig.partition_prune +test: yb.orig.partition_prune_plancache diff --git a/src/test/regress/yb_partitions_schedule b/src/test/regress/yb_partitions_schedule new file mode 100644 index 0000000000000..d97202e93dade --- /dev/null +++ b/src/test/regress/yb_partitions_schedule @@ -0,0 +1,11 @@ +# src/test/regress/yb_partitions_schedule +# +############################################################################### +# This testsuite contains testcases related to YSQL table partitioning added by +# yugabyte. +############################################################################### +test: yb.orig.partition_aggregate +test: yb.orig.partition_fk +test: yb.orig.partition_pk +test: yb.orig.partition_alter +test: yb.orig.partition_out_of_order diff --git a/src/test/regress/yb_percentile_schedule b/src/test/regress/yb_percentile_schedule new file mode 100644 index 0000000000000..de7bccb36fcef --- /dev/null +++ b/src/test/regress/yb_percentile_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_percentile_schedule +# +#################################################################################################### +# Tests for yb_get_percentile jsonb function +#################################################################################################### +test: yb.orig.hdr_percentile diff --git a/src/test/regress/yb_pg_advisory_lock_schedule b/src/test/regress/yb_pg_advisory_lock_schedule new file mode 100644 index 0000000000000..2ff997538040f --- /dev/null +++ b/src/test/regress/yb_pg_advisory_lock_schedule @@ -0,0 +1,2 @@ +# src/test/regress/yb_pg_advisory_lock_schedule +test: yb.port.advisory_lock diff --git a/src/test/regress/yb_pg_aggregates_schedule b/src/test/regress/yb_pg_aggregates_schedule new file mode 100644 index 0000000000000..779a170571d25 --- /dev/null +++ b/src/test/regress/yb_pg_aggregates_schedule @@ -0,0 +1,12 @@ +# src/test/regress/yb_pg_aggregates_schedule +# +#################################################################################################### +# This includes tests for Aggregates Functions +#################################################################################################### +# Dependencies for `yb.port.aggregates` +test: yb.port.test_setup +test: yb.depd.create_index + +test: create_aggregate +test: yb.port.aggregates +test: yb.orig.aggregates diff --git a/src/test/regress/yb_pg_async_schedule b/src/test/regress/yb_pg_async_schedule new file mode 100644 index 0000000000000..0af2eb752f429 --- /dev/null +++ b/src/test/regress/yb_pg_async_schedule @@ -0,0 +1,2 @@ +# src/test/regress/yb_pg_async_schedule +test: async diff --git a/src/test/regress/yb_pg_auth_schedule b/src/test/regress/yb_pg_auth_schedule new file mode 100644 index 0000000000000..4fc076c96bad3 --- /dev/null +++ b/src/test/regress/yb_pg_auth_schedule @@ -0,0 +1,14 @@ +# src/test/regress/yb_pg_auth_serial_schedule +# This schedule is for ported tests only. + +# Dependency for yb.port.privileges +test: yb.port.test_setup + +test: roleattributes +test: yb.port.privileges +test: init_privs +# TODO(bkolagani): Enable the below once #26734 is addresses. The test includes DDL transactional +# blocks with savepoints, which isn't currently supported. +# test: yb.port.rowsecurity +test: password +test: yb.port.create_role diff --git a/src/test/regress/yb_pg_constraints_schedule b/src/test/regress/yb_pg_constraints_schedule new file mode 100644 index 0000000000000..767a2e64e89a9 --- /dev/null +++ b/src/test/regress/yb_pg_constraints_schedule @@ -0,0 +1,2 @@ +# src/test/regress/yb_pg_constraints_schedule +test: yb.port.constraints diff --git a/src/test/regress/yb_pg_copy_schedule b/src/test/regress/yb_pg_copy_schedule new file mode 100644 index 0000000000000..94c26ee51b7ef --- /dev/null +++ b/src/test/regress/yb_pg_copy_schedule @@ -0,0 +1,10 @@ +# src/test/regress/yb_pg_copy_schedule +# +#################################################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests. +# This suite includes all tests on the COPY command that are enabled for YugaByte. +#################################################################################################### +test: yb.port.copy +test: copyselect +test: copydml +test: yb.port.copy2 diff --git a/src/test/regress/yb_pg_dml_serial_schedule b/src/test/regress/yb_pg_dml_serial_schedule new file mode 100644 index 0000000000000..72302ad71d5ca --- /dev/null +++ b/src/test/regress/yb_pg_dml_serial_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_pg_dml_serial_schedule + +# Dependency for yb.port.incremental_sort. +test: yb.depd.tenk1 + +test: yb.port.insert_conflict +test: yb.port.incremental_sort diff --git a/src/test/regress/yb_pg_foreign_data_schedule b/src/test/regress/yb_pg_foreign_data_schedule new file mode 100644 index 0000000000000..c1c808f218b45 --- /dev/null +++ b/src/test/regress/yb_pg_foreign_data_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_pg_foreign_data_schedule +# +#################################################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests. +# This includes tests related to postgres foreign data. +#################################################################################################### +test: yb.port.foreign_data diff --git a/src/test/regress/yb_pg_foreign_key_schedule b/src/test/regress/yb_pg_foreign_key_schedule new file mode 100644 index 0000000000000..3e2bbaa64eb2e --- /dev/null +++ b/src/test/regress/yb_pg_foreign_key_schedule @@ -0,0 +1,2 @@ +# src/test/regress/yb_pg_foreign_key_schedule +test: yb.port.foreign_key diff --git a/src/test/regress/yb_pg_generated_schedule b/src/test/regress/yb_pg_generated_schedule new file mode 100644 index 0000000000000..b3e985ecc1a5a --- /dev/null +++ b/src/test/regress/yb_pg_generated_schedule @@ -0,0 +1,2 @@ +# src/test/regress/yb_pg_generated_schedule +test: yb.port.generated diff --git a/src/test/regress/yb_pg_index_schedule b/src/test/regress/yb_pg_index_schedule new file mode 100644 index 0000000000000..bbc88e78fa629 --- /dev/null +++ b/src/test/regress/yb_pg_index_schedule @@ -0,0 +1,3 @@ +# src/test/regress/yb_pg_index_schedule +# This schedule is for ported tests only. +test: yb.port.indexing diff --git a/src/test/regress/yb_pg_inherit_schedule b/src/test/regress/yb_pg_inherit_schedule new file mode 100644 index 0000000000000..30b6c0380cd80 --- /dev/null +++ b/src/test/regress/yb_pg_inherit_schedule @@ -0,0 +1,8 @@ +# src/test/regress/yb_pg_inherit_schedule +# +#################################################################################################### +# Triggers Testsuites +#################################################################################################### + +test: yb.port.test_setup +test: yb.port.inherit diff --git a/src/test/regress/yb_pg_join_schedule b/src/test/regress/yb_pg_join_schedule new file mode 100644 index 0000000000000..d3fa78ba24781 --- /dev/null +++ b/src/test/regress/yb_pg_join_schedule @@ -0,0 +1,12 @@ +# src/test/regress/yb_pg_join_schedule +# +#################################################################################################### +# This includes tests for Join Clause +#################################################################################################### +# Dependencies for `yb.port.join` +test: yb.depd.float8_tbl +test: yb.depd.int4_tbl +test: yb.depd.tenk1 +test: yb.depd.tenk2 + +test: yb.port.join diff --git a/src/test/regress/yb_pg_json_schedule b/src/test/regress/yb_pg_json_schedule new file mode 100644 index 0000000000000..211550604ed2c --- /dev/null +++ b/src/test/regress/yb_pg_json_schedule @@ -0,0 +1,11 @@ +# src/test/regress/yb_pg_json_schedule +# +#################################################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests. +# This suite includes all tests on datatypes that are enabled for YugaByte. +#################################################################################################### +test: json +test: yb.port.jsonb +test: jsonpath +test: jsonpath_encoding +test: jsonb_jsonpath diff --git a/src/test/regress/yb_pg_lock_schedule b/src/test/regress/yb_pg_lock_schedule new file mode 100644 index 0000000000000..a4682a68a80e0 --- /dev/null +++ b/src/test/regress/yb_pg_lock_schedule @@ -0,0 +1,2 @@ +# src/test/regress/yb_pg_lock_schedule +test: yb.port.lock diff --git a/src/test/regress/yb_pg_matview_schedule b/src/test/regress/yb_pg_matview_schedule new file mode 100644 index 0000000000000..4477d8bdeceb1 --- /dev/null +++ b/src/test/regress/yb_pg_matview_schedule @@ -0,0 +1,10 @@ +# src/test/regress/yb_pg_matview_schedule +# +# This file is for ported tests only. +#################################################################################################### +# Tests for materialized views +#################################################################################################### +# Dependencies for `yb.port.matview` +test: yb.port.test_setup + +test: yb.port.matview diff --git a/src/test/regress/yb_pg_misc_independent_1_schedule b/src/test/regress/yb_pg_misc_independent_1_schedule new file mode 100644 index 0000000000000..f8bd8a85b2640 --- /dev/null +++ b/src/test/regress/yb_pg_misc_independent_1_schedule @@ -0,0 +1,16 @@ +# src/test/regress/yb_pg_misc_independent_1_schedule +# +#################################################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests - Part 1. +# This includes miscellaneous tests that are not yet characterized to a proper testsuite. Unlike +# yb_pg_misc_serial_schedule, the tests in this schedule have no dependencies. +#################################################################################################### +test: comments +test: yb.port.index_including +test: yb.port.create_function_sql +test: create_cast +test: yb.port.drop_if_exists +test: yb.port.case +test: yb.port.namespace +test: security_label +test: yb.port.drop_operator diff --git a/src/test/regress/yb_pg_misc_independent_2_schedule b/src/test/regress/yb_pg_misc_independent_2_schedule new file mode 100644 index 0000000000000..b88ad8f225c16 --- /dev/null +++ b/src/test/regress/yb_pg_misc_independent_2_schedule @@ -0,0 +1,15 @@ +# src/test/regress/yb_pg_misc_independent_2_schedule +# +#################################################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests - Part 2. +# This includes miscellaneous tests that are not yet characterized to a proper testsuite. Unlike +# yb_pg_misc_serial_schedule, the tests in this schedule have no dependencies. +#################################################################################################### +test: yb.port.identity +test: yb.port.alter_generic +test: alter_operator +test: dbsize +test: yb.port.indirect_toast +test: yb.port.conversion +test: yb.port.truncate +test: yb.port.sequence diff --git a/src/test/regress/yb_pg_misc_serial_schedule b/src/test/regress/yb_pg_misc_serial_schedule new file mode 100644 index 0000000000000..271002b64ba32 --- /dev/null +++ b/src/test/regress/yb_pg_misc_serial_schedule @@ -0,0 +1,27 @@ +# src/test/regress/yb_pg_misc_serial_schedule +# +#################################################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests. +# This includes miscellaneous tests that are not yet characterized to a proper testsuite. Unlike +# yb_pg_misc_independent_serial_schedule, the tests in this schedule have dependencies. +#################################################################################################### +# Dependency for create_procedure, yb.port.create_index, yb.port.errors, random, yb.port.misc, +# yb.port.misc_functions, yb.port.prepare, yb.port.with +test: yb.port.test_setup + +test: yb.port.create_function_c +test: yb.port.create_misc +test: create_operator +test: create_procedure +test: yb.port.create_table +test: yb.port.create_type +test: yb.port.create_index +test: yb.port.create_view +test: yb.port.create_am +test: yb.port.errors +test: random +test: yb.port.misc +test: yb.port.misc_functions +test: yb.port.prepare +# with depends on create_misc +test: yb.port.with diff --git a/src/test/regress/yb_pg_partition_join_schedule b/src/test/regress/yb_pg_partition_join_schedule new file mode 100644 index 0000000000000..6094cc0c66d3b --- /dev/null +++ b/src/test/regress/yb_pg_partition_join_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_pg_partition_join_schedule +# +########################################################################### +# This testsuite contains testcases ported from postgres partitionwise join +# regression tests. +########################################################################### +test: yb.port.partition_join diff --git a/src/test/regress/yb_pg_partitions_misc_schedule b/src/test/regress/yb_pg_partitions_misc_schedule new file mode 100644 index 0000000000000..23b1d5fe4be0c --- /dev/null +++ b/src/test/regress/yb_pg_partitions_misc_schedule @@ -0,0 +1,9 @@ +# src/test/regress/yb_pg_partitions_misc_schedule +# +##################################################################### +# This testsuite contains testcases ported from postgres partitioning +# regression tests, namely update and partition_aggregate. +##################################################################### +test: yb.port.update +test: yb.port.partition_aggregate +test: partition_info diff --git a/src/test/regress/yb_pg_plpgsql_schedule b/src/test/regress/yb_pg_plpgsql_schedule new file mode 100644 index 0000000000000..1c39a29a3c762 --- /dev/null +++ b/src/test/regress/yb_pg_plpgsql_schedule @@ -0,0 +1,10 @@ +# src/test/regress/yb_pg_plpgsql_schedule +# +#################################################################################################### +# This includes tests related to PL/pgSQL. +#################################################################################################### +# Dependency. +test: yb.port.test_setup + +test: yb.port.plpgsql +test: yb.orig.plpgsql diff --git a/src/test/regress/yb_pg_pushdown_serial_schedule b/src/test/regress/yb_pg_pushdown_serial_schedule new file mode 100644 index 0000000000000..5dab29fbb8afd --- /dev/null +++ b/src/test/regress/yb_pg_pushdown_serial_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_pg_pushdown_serial_schedule +# +#################################################################################################### +# Postgres Testsuites: This suite includes tests on expression pushdown. +#################################################################################################### +test: yb.orig.dml_single_row +test: yb.orig.select_no_pushdown diff --git a/src/test/regress/yb_pg_rules_schedule b/src/test/regress/yb_pg_rules_schedule new file mode 100644 index 0000000000000..35c5459bb89ce --- /dev/null +++ b/src/test/regress/yb_pg_rules_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_pg_rules_schedule +# +#################################################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests. +# This includes tests related to rules. +#################################################################################################### +test: yb.port.rules diff --git a/src/test/regress/yb_pg_select_schedule b/src/test/regress/yb_pg_select_schedule new file mode 100644 index 0000000000000..b7505d67bc31d --- /dev/null +++ b/src/test/regress/yb_pg_select_schedule @@ -0,0 +1,18 @@ +# src/test/regress/yb_pg_select_schedule +# +############################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests. +# This includes upstream ported tests with "select". +############################################################################### +# Dependencies. +test: yb.depd.int4_tbl +test: yb.depd.int8_tbl +test: yb.depd.onek +test: yb.depd.tenk1 +test: yb.depd.person +test: yb.depd.onek2 + +test: yb.port.select +test: yb.port.select_distinct +test: select_distinct_on +test: yb.port.subselect diff --git a/src/test/regress/yb_pg_stat_schedule b/src/test/regress/yb_pg_stat_schedule new file mode 100644 index 0000000000000..ec43dc74bec89 --- /dev/null +++ b/src/test/regress/yb_pg_stat_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_pg_stat_schedule +# +#################################################################################################### +# Testsuite for tests covering pg_stat collection behavior. +#################################################################################################### +test: yb.orig.pg_stat +test: yb.orig.pg_stat_backend diff --git a/src/test/regress/yb_pg_stats_ext_schedule b/src/test/regress/yb_pg_stats_ext_schedule new file mode 100644 index 0000000000000..71f310b3b1a27 --- /dev/null +++ b/src/test/regress/yb_pg_stats_ext_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_extended_stats_schedule +# +#################################################################################################### +# This testsuite contains testcases pertinent to YB ANALYZE command. +#################################################################################################### +test: yb.port.stats_ext diff --git a/src/test/regress/yb_pg_stats_import_schedule b/src/test/regress/yb_pg_stats_import_schedule new file mode 100644 index 0000000000000..789ad7596abcd --- /dev/null +++ b/src/test/regress/yb_pg_stats_import_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_pg_stats_import_schedule +# +#################################################################################################### +# This testsuite contains testcases pertinent to stats import. +#################################################################################################### +test: yb.port.stats_import diff --git a/src/test/regress/yb_pg_table_schedule b/src/test/regress/yb_pg_table_schedule new file mode 100644 index 0000000000000..6b5f54b2fabaf --- /dev/null +++ b/src/test/regress/yb_pg_table_schedule @@ -0,0 +1,14 @@ +# src/test/regress/yb_pg_table_schedule +# This schedule is for ported tests only. + +# Dependencies for yb.port.alter_table +test: yb.depd.int8_tbl +test: yb.depd.onek +test: yb.depd.part_test_int4_ops +# TODO(jason): yb.port.insert is no longer a dependency, so move it to +# yb_pg_dml_serial_schedule. (Note: yb.depd.part_test_int4_ops is a dependency +# for yb.port.insert.) +test: yb.port.insert + +test: yb.port.create_table_like +test: yb.port.alter_table diff --git a/src/test/regress/yb_pg_transactions_schedule b/src/test/regress/yb_pg_transactions_schedule new file mode 100644 index 0000000000000..615d0235bf738 --- /dev/null +++ b/src/test/regress/yb_pg_transactions_schedule @@ -0,0 +1,8 @@ +# src/test/regress/yb_pg_transactions_schedule +# +#################################################################################################### +# Ported transaction tests. +#################################################################################################### + +test: yb.port.test_setup +test: yb.port.transactions diff --git a/src/test/regress/yb_pg_types_datetime_schedule b/src/test/regress/yb_pg_types_datetime_schedule new file mode 100644 index 0000000000000..c8ef6c55f0997 --- /dev/null +++ b/src/test/regress/yb_pg_types_datetime_schedule @@ -0,0 +1,9 @@ +# src/test/regress/yb_pg_types_datetime_schedule +# PORTED TESTS ONLY +test: date +test: time +test: timetz +test: timestamp +test: timestamptz +test: interval +test: yb.port.horology diff --git a/src/test/regress/yb_pg_types_geo_serial_schedule b/src/test/regress/yb_pg_types_geo_serial_schedule new file mode 100644 index 0000000000000..a56ddbeccc1e9 --- /dev/null +++ b/src/test/regress/yb_pg_types_geo_serial_schedule @@ -0,0 +1,17 @@ +# src/test/regress/yb_pg_types_geometry_serial_schedule +# +#################################################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests. +# This suite includes all tests on geometry datatypes that are enabled for YugaByte. +#################################################################################################### +# Dependency for point. +test: yb.port.test_setup + +test: point +test: lseg +test: line +test: yb.port.box +test: path +test: yb.port.polygon +test: circle +test: yb.port.geometry diff --git a/src/test/regress/yb_pg_types_misc_serial_schedule b/src/test/regress/yb_pg_types_misc_serial_schedule new file mode 100644 index 0000000000000..3eed97dbbb380 --- /dev/null +++ b/src/test/regress/yb_pg_types_misc_serial_schedule @@ -0,0 +1,13 @@ +# src/test/regress/yb_pg_type_serial_schedule +# +#################################################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests. +# This suite includes all tests on datatypes that are enabled for YugaByte. +#################################################################################################### +test: yb.port.boolean +# LOG sequence number +test: yb.port.pg_lsn +test: yb.port.inet +test: yb.port.macaddr +test: yb.port.macaddr8 +test: yb.orig.hash_func diff --git a/src/test/regress/yb_pg_types_numeric_schedule b/src/test/regress/yb_pg_types_numeric_schedule new file mode 100644 index 0000000000000..160c78d0c788a --- /dev/null +++ b/src/test/regress/yb_pg_types_numeric_schedule @@ -0,0 +1,20 @@ +# src/test/regress/yb_pg_types_numeric_schedule +# +#################################################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests. +# This suite includes all tests on numeric datatypes that are enabled for YugaByte. +#################################################################################################### +# Dependency for int2, int4, yb.port.int8, yb.port.float8. +test: yb.port.test_setup + +test: int2 +test: int4 +test: yb.port.int8 +test: yb.port.float4 +test: yb.port.float8 +test: yb.port.numeric +test: yb.port.numeric_big +test: money +test: numerology + +test: yb.orig.money diff --git a/src/test/regress/yb_pg_types_string_schedule b/src/test/regress/yb_pg_types_string_schedule new file mode 100644 index 0000000000000..6c1ca5c153220 --- /dev/null +++ b/src/test/regress/yb_pg_types_string_schedule @@ -0,0 +1,24 @@ +# src/test/regress/yb_pg_types_string_schedule +# This schedule is for ported tests only. + +# Dependency for char, yb.port.varchar, yb.port.text, yb.port.strings. +test: yb.port.test_setup + +test: char +test: name +test: yb.port.varchar +test: yb.port.text +test: bit +test: yb.port.uuid +test: yb.port.strings +test: tstypes + +test: unicode + +test: yb.port.collate +test: yb.port.collate.icu.utf8 + +test: yb.port.tsearch +test: yb.port.tsdicts + +test: yb.port.compression diff --git a/src/test/regress/yb_pg_types_udt_serial_schedule b/src/test/regress/yb_pg_types_udt_serial_schedule new file mode 100644 index 0000000000000..a8b5144bac85b --- /dev/null +++ b/src/test/regress/yb_pg_types_udt_serial_schedule @@ -0,0 +1,16 @@ +# src/test/regress/yb_pg_types_udt_serial_schedule +# This schedule is for ported tests only. + +# Dependency for yb.port.rangetypes, yb.port.rowtypes +test: yb.port.test_setup +# Dependency for yb.port.rowtypes +test: yb.depd.tenk1 + +# Enum types +test: yb.port.enum +# Range types +test: yb.port.rangetypes +test: yb.port.multirangetypes + +# Composite types +test: yb.port.rowtypes diff --git a/src/test/regress/yb_pg_window_schedule b/src/test/regress/yb_pg_window_schedule new file mode 100644 index 0000000000000..cf7557a4dfc10 --- /dev/null +++ b/src/test/regress/yb_pg_window_schedule @@ -0,0 +1,12 @@ +# src/test/regress/yb_pg_window_schedule +# +#################################################################################################### +# This includes tests for Window Functions +#################################################################################################### +# Dependencies +test: yb.port.test_setup +test: yb.depd.tenk1 +test: yb.depd.tenk2 + +test: yb.port.groupingsets +test: yb.port.window diff --git a/src/test/regress/yb_pg_xml_schedule b/src/test/regress/yb_pg_xml_schedule new file mode 100644 index 0000000000000..49638d299a988 --- /dev/null +++ b/src/test/regress/yb_pg_xml_schedule @@ -0,0 +1,11 @@ +# src/test/regress/yb_pg_xml_schedule +# +#################################################################################################### +# Postgres Testsuites: Porting from PostgreSQL original tests. +# XML-related tests. +#################################################################################################### +# Dependency for xml +test: yb.port.test_setup + +test: yb.port.xmlmap +test: xml diff --git a/src/test/regress/yb_planner_serial_schedule b/src/test/regress/yb_planner_serial_schedule new file mode 100644 index 0000000000000..caf22fdfafae2 --- /dev/null +++ b/src/test/regress/yb_planner_serial_schedule @@ -0,0 +1,14 @@ +# src/test/regress/yb_planner_serial_schedule +# +#################################################################################################### +# Testsuite for tests covering planner/optimizer behavior. +#################################################################################################### +test: yb.orig.planner_indexes +test: yb.orig.planner_joins +test: yb.orig.planner_join_order +test: yb.orig.planner_size_estimates +test: yb.orig.planner_heuristic_selectivity +test: yb.orig.planner_base_scans_cost_model_colocated +test: yb.orig.planner_base_scans_cost_model +test: yb.orig.row_estimates +test: yb.orig.optimizer_guc diff --git a/src/test/regress/yb_planner_taqo_schedule b/src/test/regress/yb_planner_taqo_schedule new file mode 100644 index 0000000000000..51a67f7f1184b --- /dev/null +++ b/src/test/regress/yb_planner_taqo_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_planner_taqo_schedule +# +#################################################################################################### +# Testsuite for TAQO workloads covering planner/optimizer behavior. +#################################################################################################### +test: yb.orig.planner_taqo_seek_next_estimation +test: yb.orig.planner_taqo_tuning_tests diff --git a/src/test/regress/yb_proc_schedule b/src/test/regress/yb_proc_schedule new file mode 100644 index 0000000000000..309babf87cb1e --- /dev/null +++ b/src/test/regress/yb_proc_schedule @@ -0,0 +1,14 @@ +# src/test/regress/yb_proc_schedule +# +#################################################################################################### +# This suite includes postgres proc tests. +#################################################################################################### +# This test should be first for consistent output. +test: yb.orig.yb_hash_code + +test: yb.orig.function +test: yb.orig.functional_indexes +test: yb.orig.functional_index_multiple_column +test: yb.orig.get_current_transaction_priority +test: yb.orig.lock_status +test: yb.orig.lock_row_for_update diff --git a/src/test/regress/yb_profile_schedule b/src/test/regress/yb_profile_schedule new file mode 100644 index 0000000000000..8389a8260ba1a --- /dev/null +++ b/src/test/regress/yb_profile_schedule @@ -0,0 +1,12 @@ +# src/test/regress/yb_profile_schedule +# +#################################################################################################### +# PROFILE Testsuite +# - Test DDL Statements for profile +# - Test DDL Statements for role_profile +# - Test that DDL Statements can be run by admins only. +# - Test that increment and disable code works correctly using a test backdoor +#################################################################################################### +test: yb.orig.profile +test: yb.orig.role_profile +test: yb.orig.profile_permissions diff --git a/src/test/regress/yb_publication_schedule b/src/test/regress/yb_publication_schedule new file mode 100644 index 0000000000000..ef6c0f26c3a38 --- /dev/null +++ b/src/test/regress/yb_publication_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_publication_schedule +# +#################################################################################################### +# Postgres Testsuites: This suite includes tests on publication statements. +#################################################################################################### +test: yb.port.publication +test: yb.orig.publication diff --git a/src/test/regress/yb_pushdown_serial_schedule b/src/test/regress/yb_pushdown_serial_schedule new file mode 100644 index 0000000000000..08bf0a831b6d1 --- /dev/null +++ b/src/test/regress/yb_pushdown_serial_schedule @@ -0,0 +1,10 @@ +# src/test/regress/yb_pushdown_serial_schedule +# +#################################################################################################### +# LARGE TABLE Testsuite +# Test for table whose index key columns are not in the same order with the table. +#################################################################################################### +test: yb.orig.dml_systable_scan +test: yb.orig.create_table_scattering_keys +test: yb.orig.large_copy +test: yb.orig.dml_key_pushdown diff --git a/src/test/regress/yb_replica_identity_schedule b/src/test/regress/yb_replica_identity_schedule new file mode 100644 index 0000000000000..c3fb8c3bf90d9 --- /dev/null +++ b/src/test/regress/yb_replica_identity_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_replica_identity_schedule +# +#################################################################################################### +# Postgres Testsuites: This suite includes tests pertaining to replica identity +#################################################################################################### +test: yb.port.replica_identity +test: yb.orig.replica_identity diff --git a/src/test/regress/yb_replication_slot_schedule b/src/test/regress/yb_replication_slot_schedule new file mode 100644 index 0000000000000..59ed5aa5c043f --- /dev/null +++ b/src/test/regress/yb_replication_slot_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_replication_slot_schedule +# +#################################################################################################### +# Postgres Testsuites: This suite includes tests on replication slot functions. +#################################################################################################### +test: yb.orig.replication_slot diff --git a/src/test/regress/yb_reset_analyze_schedule b/src/test/regress/yb_reset_analyze_schedule new file mode 100644 index 0000000000000..92a192722fcac --- /dev/null +++ b/src/test/regress/yb_reset_analyze_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_reset_analyze_schedule +# +#################################################################################################### +# This testsuite contains testcases pertinent to yb_reset_analyze function. +#################################################################################################### +test: yb.orig.reset_analyze diff --git a/src/test/regress/yb_scan_target_schedule b/src/test/regress/yb_scan_target_schedule new file mode 100644 index 0000000000000..2af01831fcf64 --- /dev/null +++ b/src/test/regress/yb_scan_target_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_scan_target_schedule +# +#################################################################################################### +# This suite validates that targets are properly set for different scan types +#################################################################################################### +test: yb.orig.no_explicit_target +test: yb.orig.system_targets diff --git a/src/test/regress/yb_secondary_index_scan_serial_schedule b/src/test/regress/yb_secondary_index_scan_serial_schedule new file mode 100644 index 0000000000000..9b7eccc16c438 --- /dev/null +++ b/src/test/regress/yb_secondary_index_scan_serial_schedule @@ -0,0 +1,14 @@ +# src/test/regress/yb_secondary_index_scan_serial_schedule +# +#################################################################################################### +# Secondary Index Scan with large table. +# Test for table of 55K rows. +#################################################################################################### +test: yb.orig.create_table_55k_secondary_index +test: yb.orig.copy_55k_rows +# Due to github issue #4488 on ASAN failure, we split the "yb.orig.secondary_index_scan" into two +# different tests. +# - Test "yb.orig.secondary_index_scan" checks correctness and is moved to "yb_index_schedule". +# - Test "yb.orig.perf_secondary_index_scan" checks performance and is kept here. +# Once the issue is fixed, the two tests can be merged into one to be run in the same session. +test: yb.orig.perf_secondary_index_scan diff --git a/src/test/regress/yb_stat_schedule b/src/test/regress/yb_stat_schedule new file mode 100644 index 0000000000000..ed27d9a5e0922 --- /dev/null +++ b/src/test/regress/yb_stat_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_stat_schedule +# +#################################################################################################### +# Testsuite for tests covering yb_stat collection behavior. +#################################################################################################### +test: yb.orig.stat diff --git a/src/test/regress/yb_table_schedule b/src/test/regress/yb_table_schedule new file mode 100644 index 0000000000000..61f004f5cb406 --- /dev/null +++ b/src/test/regress/yb_table_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_table_schedule +# This schedule is for non-ported tests only. + +test: yb.orig.alter_table +test: yb.orig.alter_table_rewrite +test: yb.orig.create_table +test: yb.orig.drop_table diff --git a/src/test/regress/yb_tablegroup_deprecation_schedule b/src/test/regress/yb_tablegroup_deprecation_schedule new file mode 100644 index 0000000000000..4e5cd746b52d1 --- /dev/null +++ b/src/test/regress/yb_tablegroup_deprecation_schedule @@ -0,0 +1,6 @@ +# src/test/regress/yb_tablegroup_deprecation_schedule +# +#################################################################################################### +# TABLEGROUP Testsuite: TABLEGROUP Deprecation Warning +#################################################################################################### +test: yb.orig.tablegroup_deprecation diff --git a/src/test/regress/yb_tablegroups_schedule b/src/test/regress/yb_tablegroups_schedule new file mode 100644 index 0000000000000..078c921e90000 --- /dev/null +++ b/src/test/regress/yb_tablegroups_schedule @@ -0,0 +1,14 @@ +# src/test/regress/yb_tablegroups_schedule +# +#################################################################################################### +# TABLEGROUP Testsuite: All DDL Statments on Tablegroups +# - CREATE +# - DROP +# - (Future) ALTER +#################################################################################################### +test: yb.orig.tablegroup +test: yb.orig.tablegroup_permissions +test: yb.orig.tablegroup_dml +test: yb.orig.tablegroup_alter +test: yb.orig.tablegroup_colocation_id +test: yb.orig.tablegroup_partitioned_table diff --git a/src/test/regress/yb_tablespaces_schedule b/src/test/regress/yb_tablespaces_schedule new file mode 100644 index 0000000000000..3e64861d44408 --- /dev/null +++ b/src/test/regress/yb_tablespaces_schedule @@ -0,0 +1,7 @@ +# src/test/regress/yb_pg_tablespaces_schedule +# +#################################################################################################### +# This testsuite contains testcases pertinent to YB Tablespaces. +#################################################################################################### +test: yb.orig.tablespaces +test: yb.orig.is_local_table diff --git a/src/test/regress/yb_transactions_schedule b/src/test/regress/yb_transactions_schedule new file mode 100644 index 0000000000000..b83dc2b6630ca --- /dev/null +++ b/src/test/regress/yb_transactions_schedule @@ -0,0 +1,8 @@ +# src/test/regress/yb_transactions_schedule +# +#################################################################################################### +# YB Transaction Testsuite: Used for reporting supported/unsupported operations in context +# of different transaction types. +#################################################################################################### +test: yb.orig.transactions +test: yb.orig.internal_subtransactions diff --git a/src/test/regress/yb_triggers_schedule b/src/test/regress/yb_triggers_schedule new file mode 100644 index 0000000000000..33e2c19be73e8 --- /dev/null +++ b/src/test/regress/yb_triggers_schedule @@ -0,0 +1,12 @@ +# src/test/regress/yb_triggers_schedule +# +#################################################################################################### +# Triggers Testsuites +#################################################################################################### +# Dependency for `yb.port.triggers` +test: yb.port.test_setup + +test: yb.port.triggers +test: yb.orig.triggers +test: yb.port.event_trigger +test: yb.orig.event_trigger diff --git a/src/test/regress/yb_types_datetime_schedule b/src/test/regress/yb_types_datetime_schedule new file mode 100644 index 0000000000000..c340e761ceb4b --- /dev/null +++ b/src/test/regress/yb_types_datetime_schedule @@ -0,0 +1,4 @@ +# src/test/regress/yb_types_datetime_schedule +# NON-PORTED TESTS ONLY +test: yb.orig.time +test: yb.orig.interval diff --git a/src/test/regress/yb_types_numeric_schedule b/src/test/regress/yb_types_numeric_schedule new file mode 100644 index 0000000000000..91609dae92c26 --- /dev/null +++ b/src/test/regress/yb_types_numeric_schedule @@ -0,0 +1,4 @@ +# src/test/regress/yb_types_numeric_schedule +# NON-PORTED TESTS ONLY +test: yb.orig.numeric +test: yb.orig.oid diff --git a/src/test/regress/yb_types_string_schedule b/src/test/regress/yb_types_string_schedule new file mode 100644 index 0000000000000..7c040a708456f --- /dev/null +++ b/src/test/regress/yb_types_string_schedule @@ -0,0 +1,8 @@ +# src/test/regress/yb_types_string_schedule +# This schedule is for non-ported tests only. + +test: yb.orig.char +test: yb.orig.collate.icu.utf8 +test: yb.orig.bytea +test: yb.orig.tsdicts +test: yb.orig.ts_remote_filter diff --git a/src/test/regress/yb_types_udt_serial_schedule b/src/test/regress/yb_types_udt_serial_schedule new file mode 100644 index 0000000000000..fe33b9b9ce5d3 --- /dev/null +++ b/src/test/regress/yb_types_udt_serial_schedule @@ -0,0 +1,15 @@ +# src/test/regress/yb_types_udt_serial_schedule +# This schedule is for non-ported tests only. + +# Dependency for `yb.orig.base_type` +test: yb.orig.create_function + +# Overall UDTs +test: yb.orig.create_type +test: yb.orig.alter_type + +# Base types +test: yb.orig.base_type + +# Operator Class +test: yb.orig.operator_class diff --git a/src/test/regress/yb_update_optimized_schedule b/src/test/regress/yb_update_optimized_schedule new file mode 100644 index 0000000000000..7685106575a6d --- /dev/null +++ b/src/test/regress/yb_update_optimized_schedule @@ -0,0 +1,9 @@ +# src/test/regress/yb_update_optimized_schedule +# +#################################################################################################### +# Testsuite for tests covering UPDATE query optimizations. +#################################################################################################### +test: yb.orig.update_optimize_base +test: yb.orig.update_optimize_indices +test: yb.orig.update_optimize_triggers +test: yb.orig.update_optimize_index_updates diff --git a/src/timezone/zic.c b/src/timezone/zic.c index d5ef8b7db3d7d..e1a0348ecbcfa 100644 --- a/src/timezone/zic.c +++ b/src/timezone/zic.c @@ -1394,18 +1394,22 @@ gethms(char const *string, char const *errstring) case 8: ok = '0' <= xr && xr <= '9'; /* fallthrough */ + yb_switch_fallthrough(); case 7: ok &= ssx == '.'; if (ok && noise) warning(_("fractional seconds rejected by" " pre-2018 versions of zic")); /* fallthrough */ + yb_switch_fallthrough(); case 5: ok &= mmx == ':'; /* fallthrough */ + yb_switch_fallthrough(); case 3: ok &= hhx == ':'; /* fallthrough */ + yb_switch_fallthrough(); case 1: break; } diff --git a/src/tools/pg_bsd_indent/indent.c b/src/tools/pg_bsd_indent/indent.c index 2622cc6227af0..eda74e7fa2b3a 100644 --- a/src/tools/pg_bsd_indent/indent.c +++ b/src/tools/pg_bsd_indent/indent.c @@ -296,6 +296,7 @@ main(int argc, char **argv) * done earlier. */ force_nl = false; + yb_switch_fallthrough(); case form_feed: break; case comment: @@ -353,6 +354,7 @@ main(int argc, char **argv) goto sw_buffer; } /* FALLTHROUGH */ + yb_switch_fallthrough(); default: /* it is the start of a normal statement */ { int remove_newlines; @@ -923,6 +925,7 @@ main(int argc, char **argv) if (ps.p_l_follow > 0) goto copy_id; /* FALLTHROUGH */ + yb_switch_fallthrough(); case decl: /* we have a declaration type (int, etc.) */ parse(decl); /* let parser worry about indentation */ if (ps.last_token == rparen && ps.tos <= 1) { diff --git a/src/tools/pg_bsd_indent/parse.c b/src/tools/pg_bsd_indent/parse.c index e707da639c71e..b038d43a07f9e 100644 --- a/src/tools/pg_bsd_indent/parse.c +++ b/src/tools/pg_bsd_indent/parse.c @@ -97,6 +97,7 @@ parse(int tk) /* tk: the code for the construct scanned */ ps.i_l_follow = ps.il[ps.tos--]; /* the rest is the same as for dolit and forstmt */ /* FALLTHROUGH */ + yb_switch_fallthrough(); case dolit: /* 'do' */ case forstmt: /* for (...) */ ps.p_stack[++ps.tos] = tk; @@ -304,6 +305,7 @@ reduce(void) /* */ case_ind = ps.cstk[ps.tos - 1]; /* FALLTHROUGH */ + yb_switch_fallthrough(); case decl: /* finish of a declaration */ case elsehead: /* < else> */ diff --git a/src/tools/pgindent/README b/src/tools/pgindent/README index 1941b34b5b21f..7b89de662a6fa 100644 --- a/src/tools/pgindent/README +++ b/src/tools/pgindent/README @@ -1,3 +1,46 @@ +YB Foreword +=========== + +Yugabyte makes use of pgindent, but due to differences, the workflow is +different. + +- Yugabyte does not have a buildfarm that generates typedefs.list. Therefore, + the types have to be added somewhat manually. + - Add Yugabyte-added type names to yb_typedefs.list. These should have "yb" + (case insensitive) present in the name. Here is one way to collect these + items (assuming ~/code/postgres has upstream postgres code checked out at + the corresponding commit): + + diff \ + <(cd src/postgres; git ls-files ^third-party-extensions ../yb/yql/pggate/***/ybc_*.h | ctags -L - --languages=c,c++ --c-kinds=t --c++-kinds=t -f /dev/stdout | awk '{print$1}' | uniq) \ + <(cd ~/code/postgres; git ls-files | ctags -L - --languages=c,c++ --c-kinds=t --c++-kinds=t -f /dev/stdout | awk ' {print$1}' | uniq) \ + | grep '<' \ + | sed 's/< //' + + ctags can find most type names. The command above compares the ctags + output between Yugabyte code and upstream postgres code and reports the + types that are defined in Yugabyte only. All these should have "yb" (case + insensitive) present in them, and it should not be the case that upstream + postgres has type names that Yugabyte does not (though this may show up in + case there are future upstream PG commit point imports). See the test plan + of commit 6a56ce7bcdeb0478c791efaa322b58c61007fd39 that should serve as a + sanity check before proceeding to use pgindent. + + In addition to the types found by the above command, here are a few more + items that should be added manually: + - Any Form_foo should be accompanied by FormData_foo. + - Any types defined using YB_DEFINE_HANDLE_TYPE should have a corresponding + entry. + - Add further third-party typedefs to yb_thirdparty_typedefs.list. For + example, types in hdrhistogram or the standard library. +- Yugabyte has other files which might have to be exempt from modification by + pgindent. Add such items to yb_exclude_file_patterns in the same manner as + exclude_file_patterns. +- The existing typedefs.list and exclude_file_patterns should not be touched + and should be left under the control of upstream postgres. In order to make + use of the YB variants of these files, run yb_pgindent, a wrapper around + pgindent that merges the additional YB files with the PG ones. + pgindent'ing the PostgreSQL source tree ======================================= diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent index 2bcd211fb5c3d..e7bbf33bf190f 100755 --- a/src/tools/pgindent/pgindent +++ b/src/tools/pgindent/pgindent @@ -18,6 +18,8 @@ my $INDENT_VERSION = "2.1.2"; # Our standard indent settings my $indent_opts = "-bad -bap -bbb -bc -bl -cli1 -cp33 -cdb -nce -d0 -di12 -nfc1 -i4 -l79 -lp -lpl -nip -npro -sac -tpg -ts4"; +# YB: add more settings +$indent_opts .= " -nfcb"; my $devnull = File::Spec->devnull; @@ -119,7 +121,7 @@ sub load_typedefs $tdtry = "$tdtry/.."; } die "cannot locate typedefs file \"$typedefs_file\"\n" - unless $typedefs_file && -f $typedefs_file; + unless $typedefs_file; # YB: allow /dev/*, /proc/self/fd/* open(my $typedefs_fh, '<', $typedefs_file) || die "cannot open typedefs file \"$typedefs_file\": $!\n"; diff --git a/src/tools/pgindent/yb_exclude_file_patterns b/src/tools/pgindent/yb_exclude_file_patterns new file mode 100644 index 0000000000000..0b0f1b9dd5e1b --- /dev/null +++ b/src/tools/pgindent/yb_exclude_file_patterns @@ -0,0 +1,7 @@ +# YB list of filename patterns to exclude from pgindent runs +# +# Third party extensions don't necessarily conform to PG's style. +third-party-extensions/ +# +# For some reason, this file gets oddly formatted over multiple iterations. +src/interfaces/ecpg/include/sqlca.h diff --git a/src/tools/pgindent/yb_pgindent b/src/tools/pgindent/yb_pgindent new file mode 100755 index 0000000000000..0967d32d85f06 --- /dev/null +++ b/src/tools/pgindent/yb_pgindent @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# +# Copyright (c) YugabyteDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# Wrapper around pgindent to supply all *typedefs.list and +# *exclude_file_patterns. Also automatically switches to the right directory +# and references the built pg_bsd_indent. + +set -euo pipefail + +PGINDENT=src/tools/pgindent/pgindent +PG_BSD_INDENT=../../build/latest/postgres_build/src/tools/pg_bsd_indent/pg_bsd_indent + +if ! [ -f "$PGINDENT" ]; then + if [ $# -gt 0 ]; then + echo "WARNING: Automatically switching to postgres root directory, so any paths referenced in commandline arguments may be offset. Run this script from within the postgres root directory to avoid this discrepancy." >&2 + fi + repo_dir=$(git rev-parse --show-toplevel) + cd "$repo_dir"/src/postgres +fi +if ! [ -f "$PGINDENT" ]; then + echo "Could not find pgindent: are you in the YugabyteDB repo?" >&2 + exit 1 +fi +if ! [ -f "$PG_BSD_INDENT" ]; then + echo "Could not find pg_bsd_indent: did you build postgres?" >&2 + exit 1 +fi + +"$PGINDENT" \ + --indent="$PG_BSD_INDENT" \ + --typedefs=<(find src -name '*typedefs.list' -print0 | xargs -0 cat) \ + --excludes=<(find src -name '*exclude_file_patterns' -print0 | xargs -0 cat) \ + "$@" diff --git a/src/tools/pgindent/yb_thirdparty_typedefs.list b/src/tools/pgindent/yb_thirdparty_typedefs.list new file mode 100644 index 0000000000000..d2234e81506a8 --- /dev/null +++ b/src/tools/pgindent/yb_thirdparty_typedefs.list @@ -0,0 +1,5 @@ +count_t +hdr_format_type +hdr_histogram +int16_t +int64_t diff --git a/src/tools/pgindent/yb_typedefs.list b/src/tools/pgindent/yb_typedefs.list new file mode 100644 index 0000000000000..f870e01ac487f --- /dev/null +++ b/src/tools/pgindent/yb_typedefs.list @@ -0,0 +1,390 @@ +FormData_pg_yb_migration +FormData_pg_yb_profile +FormData_pg_yb_role_profile +FormData_pg_yb_tablegroup +FormData_yb_pg_catalog_version +FormData_yb_pg_invalidation_messages +FormData_yb_pg_logical_client_version +Form_pg_yb_migration +Form_pg_yb_profile +Form_pg_yb_role_profile +Form_pg_yb_tablegroup +Form_yb_pg_catalog_version +Form_yb_pg_invalidation_messages +Form_yb_pg_logical_client_version +YBExecuteMessageFunctorContext +YBFunctor +YBIndexPermissions +YBLogicalDecodeEnableYBSpecficReplicaIdentityCB +YBLogicalDecodeSchemaChangeCB +YBParallelPartitionKeys +YBParallelPartitionKeysData +YBQueryRetryData +YBReorderBufferSchemaChangeCB +YBSharedInvalMessageHeader +YBTableSpaceOpts +YBUnackedTransactionInfo +YBUpdateOptimizationOptions +YbAddTupleToOuterBatchFn_t +YbAdditionalNegCacheIds +YbAsh +YbAshQueryPlanPairStack +YbAshTrackNestedQueries +YbAttnumBmsState +YbAttrProcessorState +YbAttrToColIdxMap +YbBNLHashClauseInfo +YbBackfillIndexStmt +YbBackfillInfo +YbBackgroundWorkerHandle +YbBatchedExpr +YbBatchedNestLoop +YbBatchedNestLoopState +YbBitMatrix +YbBitmapIndexScan +YbBitmapIndexScanState +YbBitmapTablePath +YbBitmapTableScan +YbBitmapTableScanState +YbBound +YbBucketTupleInfo +YbBundleInfo +YbCRSLsnType +YbCRSOrderingMode +YbCacheReplacementAlgorithmEnum +YbCatCListIterator +YbCatCacheInfo +YbCatNamePfId +YbCatalogCacheTable +YbCatalogMessageList +YbCatalogModificationAspects +YbCatalogVersionType +YbChildScan +YbChildScanData +YbCommandInfo +YbConcurrencyContext +YbCostModel +YbCounters +YbCreateProfileStmt +YbCreateTableGroupStmt +YbCustomCache +YbDatabaseAndRelfileNodeOid +YbDatabaseConnectionWorkerInfo +YbDatumDecodeOptions +YbDdlMode +YbDdlModeOptional +YbDdlTransactionState +YbDefaultSysScan +YbDefaultSysScanData +YbDetailSorter +YbDoubleCounters +YbDropProfileStmt +YbEndFn_t +YbEnumLabelAssignmentMapEntry +YbEnumLabelMapEntry +YbExplainCommitStatState +YbExplainExecStats +YbExplainState +YbExprColrefDesc +YbFKTriggerScanDesc +YbFKTriggerScanDescData +YbFKTriggerVTable +YbFdwExecState +YbFdwPlanState +YbFetchKeysParam +YbFetchStatus +YbFlushTupleFn_t +YbFreeBatchFn_t +YbFuncCallContext +YbGateTestCase +YbGeolocationDistance +YbGetNewOuterTupleFn_t +YbGetNormalizedQueryFuncPtr +YbGucOidAssignHook +YbGucOidCheckHook +YbHistInterval +YbHistPair +YbHistParseState +YbIndexBuildCallback +YbIndexCondTypes +YbIndexPathInfo +YbIndexProcessorState +YbInsertOnConflictBatchState +YbInsertOnConflictPhase +YbInstrumentation +YbIntCounters +YbIssueDetectionCheck +YbIssueDetectionPlan +YbJumbleState +YbListenerQueueScanCurrentXactState +YbLocationLen +YbLockMechanism +YbLogicalClientVersionType +YbLsmBuildState +YbMergeScanInfo +YbMergeScanSaopColInfo +YbNLBatchStatus +YbNLBucketInfo +YbNameToOidMapEntry +YbNewRelMapEntry +YbNextRangeResult +YbNotifsPollerInitStatus +YbNotifsPollerShmemData +YbObjectLockMode +YbOidAssignmentMapEntry +YbOptSplit +YbOptionalReadPointHandle +YbPFetchTable +YbPFetchTableInfo +YbPFetchTableState +YbParentScan +YbParentScanData +YbParitionedTableOptions +YbPartitionPruneStepFuncOp +YbPathInfo +YbPgAttributeRow +YbPgBackendCatalogVersionStatus +YbPgExecOutParam +YbPgFdwServerType +YbPgFuncCallContextData +YbPgInheritsCacheEntry +YbPgInheritsCacheEntryData +YbPgMemTracker +YbPgRpcStats +YbPgssCounters +YbPgssFillInConstantLengths +YbPgssQueryNeedingNormalization +YbPlanInfo +YbPlanStatePtr +YbPrefetcherStarterFunctor +YbPrefetcherStarterWithCache +YbPushdownExprs +YbQdPgssStoreKind +YbQpmConfiguration +YbQpmHashEntry +YbQpmHashKey +YbQpmInfoEntry +YbQpmLruClock +YbQpmLruEntry +YbQpmSharedState +YbQpmTiming +YbQpmTrackEnum +YbQueryConstantsMetadata +YbQueryDiagnosticsBundles +YbQueryDiagnosticsEntry +YbQueryDiagnosticsMetadata +YbQueryDiagnosticsParams +YbQueryDiagnosticsPgss +YbQueryDiagnosticsStatusType +YbRange +YbReCacheInfo +YbRegisterOuterMatchFn_t +YbRelationAttrsProcessingState +YbResetBatchFn_t +YbRowBounds +YbRunWithPrefetcherContext +YbSample +YbSampleData +YbScanDesc +YbScanDescData +YbScanPlan +YbScanPlanData +YbSendDirectFn +YbSeqScan +YbSeqScanState +YbSessionStats +YbSkippableEntities +YbSkippableEntityType +YbSortInfo +YbSplitType +YbStatLabel +YbStatLabelData +YbStatementType +YbSubstituteContext +YbSysCatalogModificationAspect +YbSysScanBase +YbSysScanBaseData +YbSysScanVirtualTable +YbTBMIterateResult +YbTBMIteratingState +YbTBMIterator +YbTIDBitmap +YbTableCacheInfo +YbTableCacheType +YbTableDistribution +YbTablePrefetcherState +YbTablegroupInfo +YbTempNamespaceSuffixBuffer +YbTerminatedQueriesBuffer +YbTerminatedQuery +YbTraceparentResult +YbTruncateType +YbTupleBitmap +YbTupleCache +YbTupleCacheEntry +YbTupleCacheIterator +YbTupleCacheKeyExtractor +YbUpdateAffectedEntities +YbUpdateRelationCacheState +YbUserIdAndSecContext +YbValidateOkeysState +YbVirtualWalRecord +YbcAdvisoryLockId +YbcAdvisoryLockMode +YbcAshAcquireBufferLock +YbcAshConstQueryIdType +YbcAshGetNextCircularBufferSlot +YbcAshMetadata +YbcAshQueryPlanPair +YbcAshSample +YbcAutoAnalyzeInfo +YbcBindColumn +YbcCatalogMessageList +YbcCatalogMessageLists +YbcCloneInfo +YbcCloudInfo +YbcConnectionMetrics +YbcConstSliceSet +YbcConstSliceVector +YbcCreateMemoryContextFn +YbcCstringToTextWithLenFn +YbcDeleteMemoryContextFn +YbcFlushDebugContext +YbcGetTableKeyRangesCallback +YbcHeapSnapshotSample +YbcLsnType +YbcMetricsInfo +YbcObjectLockId +YbcObjectLockMode +YbcOrderingMode +YbcOtelSpanContext +YbcPallocFn +YbcPgAshConfig +YbcPgAtomicUint64 +YbcPgAttrValueDescriptor +YbcPgBoundType +YbcPgCallbacks +YbcPgChangeRecordBatch +YbcPgCollationInfo +YbcPgColumnInfo +YbcPgDataType +YbcPgDatabaseCloneInfo +YbcPgDatumFromData +YbcPgDatumKind +YbcPgDatumMessage +YbcPgDatumToData +YbcPgExecEventMetric +YbcPgExecOutParamValue +YbcPgExecParameters +YbcPgExecReadWriteStats +YbcPgExecStats +YbcPgExecStatsState +YbcPgExecStorageMetrics +YbcPgExplicitRowLockErrorInfo +YbcPgExplicitRowLockParams +YbcPgExplicitRowLockStatus +YbcPgExpr +YbcPgFunction +YbcPgGFlagsAccessor +YbcPgGlobalTabletsDescriptor +YbcPgGlobalViewRead +YbcPgInitPostgresInfo +YbcPgInitTransactionData +YbcPgInsertOnConflictKeyInfo +YbcPgInsertOnConflictKeyState +YbcPgJwtAuthOptions +YbcPgKeyValue +YbcPgLastKnownCatalogVersionInfo +YbcPgLocalTabletsDescriptor +YbcPgMemctx +YbcPgMetricsCaptureType +YbcPgOid +YbcPgPrepareParameters +YbcPgReplicaIdentityDescriptor +YbcPgReplicationSlotSnapshotAction +YbcPgRowMessage +YbcPgRowMessageAction +YbcPgServerMetricsInfo +YbcPgSession +YbcPgSessionState +YbcPgSessionTxnInfo +YbcPgSharedDataPlaceholder +YbcPgSplitDatum +YbcPgStatement +YbcPgSysColumns +YbcPgSysTablePrefetcherCacheMode +YbcPgTableDesc +YbcPgTableLocalityInfo +YbcPgTabletsDescriptor +YbcPgThreadLocalRegexpCache +YbcPgThreadLocalRegexpCacheCleanup +YbcPgTransactionSetting +YbcPgTxnSnapshot +YbcPgTypeAttrs +YbcPgTypeEntities +YbcPgTypeEntity +YbcPgUuid +YbcPgVectorDistType +YbcPgVectorIdxOptions +YbcPgVectorIdxType +YbcPgXLogRecPtr +YbcPgYBTupleIdDescriptor +YbcPgYbrowidMode +YbcPgmEntry +YbcPostgresCallbacks +YbcReadAfterCommitVisibilityEnum +YbcReadHybridTime +YbcReadPointHandle +YbcRecordTempRelationDDL_hook_type +YbcRemotePgExecResult +YbcReplicationInfo +YbcReplicationSlotDescriptor +YbcReplicationSlotHashRange +YbcRpczEntry +YbcSamplingAlgorithmEnum +YbcServerDescriptor +YbcSliceSet +YbcSliceVector +YbcSlotEntryDescriptor +YbcSortKey +YbcStatus +YbcSwitchMemoryContextFn +YbcTableProperties +YbcTablePropertiesData +YbcTcmallocStats +YbcTxnPriorityRequirement +YbcTypeEntityProvider +YbcUpdateInitPostgresMetricsFn +YbcWaitEventDescriptor +YbcWaitEventInfo +YbcWaitEventInfoPtr +YbcXClusterReplicationRole +YbcYCQLStatementStats +YbcYsqlBenchMetricEntry +YbgError +YbgErrorData +YbgExprContext +YbgMemoryContext +YbgPreparedExpr +YbgReservoirState +YbgStatus +YbgStatusData +YbgTypeDesc +YbginBuildState +YbginScanOpaque +YbginScanOpaqueData +pgssYbReader +pgssYbReaderContext +yb_ambackfill_function +yb_ambindschema_function +yb_amdelete_function +yb_amgetbitmap_function +yb_aminsert_function +yb_ammightrecheck_function +yb_amupdate_function +yb_assign_for_write_function +yb_bind_for_write_function +yb_hdr_iter +yb_keylen_t +yb_relopt_oid +yb_replace_varnos_context diff --git a/yb-extensions/Makefile b/yb-extensions/Makefile new file mode 100644 index 0000000000000..8357f31383cc6 --- /dev/null +++ b/yb-extensions/Makefile @@ -0,0 +1,29 @@ +DIRS = \ + pg_dist_rag \ + yb_pg_metrics \ + yb_test_extension \ + yb_xcluster_ddl_replication \ + yb_ycql_utils \ + +BUILDDIRS = $(DIRS:%=build-%) +INSTALLDIRS = $(DIRS:%=install-%) +CLEANDIRS = $(DIRS:%=clean-%) + +all: $(BUILDDIRS) +$(DIRS): $(BUILDDIRS) +$(BUILDDIRS): + $(MAKE) -C $(@:build-%=%) + +clean: $(CLEANDIRS) +$(CLEANDIRS): + $(MAKE) -C $(@:clean-%=%) clean + +install: $(INSTALLDIRS) +$(INSTALLDIRS): + $(MAKE) -C $(@:install-%=%) install + +.PHONY: subdirs $(DIRS) +.PHONY: subdirs $(BUILDDIRS) +.PHONY: subdirs $(INSTALLDIRS) +.PHONY: subdirs $(CLEANDIRS) +.PHONY: all install clean diff --git a/yb-extensions/README b/yb-extensions/README new file mode 100644 index 0000000000000..41889ef03b3ba --- /dev/null +++ b/yb-extensions/README @@ -0,0 +1,6 @@ +This directory contains extensions that are maintained by YugabyteDB. + +See also + +- ../contrib +- ../third-party-extensions diff --git a/yb-extensions/pg_dist_rag/Makefile b/yb-extensions/pg_dist_rag/Makefile new file mode 100644 index 0000000000000..f8cdd0302529e --- /dev/null +++ b/yb-extensions/pg_dist_rag/Makefile @@ -0,0 +1,15 @@ +# Makefile for pg_dist_rag extension + +EXTENSION = pg_dist_rag +EXTVERSION = 0.0.1 + +MODULE_big = $(EXTENSION) +DATA = $(EXTENSION)--$(EXTVERSION).sql + +TESTS = $(wildcard sql/*.sql) +REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) + +PG_CONFIG ?= pg_config + +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) diff --git a/yb-extensions/pg_dist_rag/README.md b/yb-extensions/pg_dist_rag/README.md new file mode 100644 index 0000000000000..3dee326cc57a4 --- /dev/null +++ b/yb-extensions/pg_dist_rag/README.md @@ -0,0 +1,255 @@ +# pg_dist_rag + +A PostgreSQL extension for distributed Retrieval-Augmented Generation (RAG) pipelines. It manages document sources, vector indexes, embedding pipelines, and work queues -- all from within SQL. + +## Prerequisites + +- PostgreSQL with the `pgvector` extension installed (`vector` type support) +- The `pg_dist_rag` extension installed in your database + +## Installation + +```sql +CREATE EXTENSION IF NOT EXISTS pg_dist_rag; +``` + +This creates the `dist_rag` schema with all tables, types, functions, and views. + +## Core Concepts + +| Concept | Description | +|---------|-------------| +| **Source** | A pointer to a collection of documents (e.g. an S3 bucket, a URL). | +| **Vector Index** | A named index that stores embeddings for documents from one or more sources. | +| **Document** | An individual file tracked under a source, processed through the pipeline. | +| **Pipeline** | The processing workflow that chunks documents and generates embeddings. | +| **Work Queue** | Internal task queue that coordinates source creation and document preprocessing. | + +## Usage + +### 1. Create a Source + +Register a document source URI. This also queues a `CREATE_SOURCE` task in the work queue. + +```sql +-- Minimal: just a URI +SELECT dist_rag.create_source( + r_source_uri := 's3://my-bucket/documents/' +); + +-- With metadata and cloud secrets provider +SELECT dist_rag.create_source( + r_source_uri := 's3://my-bucket/documents/', + r_metadata := '{"language": "english", "type": "documentation"}'::jsonb, + r_secrets_provider := 'AWS', + r_secrets_provider_params := '{"api_key": "secret123", "region": "us-east-1"}'::jsonb +); +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `r_source_uri` | `TEXT` | *(required)* | URI of the document source | +| `r_metadata` | `JSONB` | `'{}'` | Arbitrary metadata for filtering | +| `r_secrets_provider` | `secrets_provider_enum` | `'LOCAL'` | One of: `LOCAL`, `AWS`, `GCP`, `AZURE`, `HASHICORP_VAULT` | +| `r_secrets_provider_params` | `JSONB` | `'{}'` | Provider-specific credentials/config | + +**Returns:** `UUID` -- the source ID. + +### 2. Initialize a Vector Index + +Create a named vector index, optionally associating it with existing sources. This also creates a dynamic backing table to store embeddings. + +```sql +-- Create an index with sources attached +SELECT dist_rag.init_vector_index( + r_index_name := 'my_knowledge_base', + r_sources := ARRAY['', '']::UUID[], + r_embedding_model_params := '{"dimensions": 1536}'::jsonb, + r_ai_provider := 'OPENAI' +); + +-- Create an empty index (add sources later) +SELECT dist_rag.init_vector_index( + r_index_name := 'my_empty_index', + r_embedding_model_params := '{"dimensions": 1536}'::jsonb +); +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `r_index_name` | `VARCHAR(50)` | `'pg_rag_default_store'` | Unique name for the index | +| `r_sources` | `UUID[]` | `ARRAY[]::UUID[]` | Source IDs to associate | +| `r_chunk_params` | `JSONB` | `'{}'` | Chunking configuration for all attached sources | +| `r_ai_provider` | `ai_provider_enum` | `'OPENAI'` | One of: `OPENAI`, `LOCAL` | +| `r_embedding_model_params` | `JSONB` | `'{}'` | Must contain `"dimensions"` key (e.g. `{"dimensions": 1536}`) | + +**Returns:** `UUID` -- the vector index ID. + +> **Note:** The `embedding_model_params` JSONB **must** include a `"dimensions"` key with a positive integer value. The extension creates a `vector(N)` column in the backing table using this value. + +### 3. Add a Source to an Existing Index + +Attach additional sources to an already-created vector index, optionally with custom chunking parameters. + +```sql +-- Add with default chunk params +SELECT dist_rag.add_source_to_index( + r_index_id := '', + r_source_id := '' +); + +-- Add with custom chunk params +SELECT dist_rag.add_source_to_index( + r_index_id := '', + r_source_id := '', + r_chunk_params := '{"chunk_size": 512, "overlap": 50, "strategy": "recursive"}'::jsonb +); +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `r_index_id` | `UUID` | *(required)* | The vector index to add the source to | +| `r_source_id` | `UUID` | *(required)* | The source to attach | +| `r_chunk_params` | `JSONB` | `'{}'` | Chunking configuration for this source | + +### 4. Build the Index + +Kick off the preprocessing pipeline for all documents across all sources in an index. Each document gets a `PREPROCESS` task queued in the work queue. + +```sql +-- Build by index ID +SELECT dist_rag.build_index(r_index_id := ''); + +-- Build by index name +SELECT dist_rag.build_index(r_index_name := 'my_knowledge_base'); +``` + +**Parameters (provide exactly one):** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `r_index_id` | `UUID` | `NULL` | The index ID | +| `r_index_name` | `VARCHAR(50)` | `NULL` | The index name | + +> **Note:** You must provide **either** `r_index_id` or `r_index_name`, not both. + +### 5. Monitor Pipelines + +Two views are available for observing pipeline progress and statistics. + +#### Detailed Pipeline View + +```sql +-- All pipeline details across all indexes +SELECT * FROM dist_rag.vector_index_pipeline_details; + +-- Filter by a specific index +SELECT * FROM dist_rag.vector_index_pipeline_details +WHERE index_name = 'my_knowledge_base'; +``` + +Columns include: `index_id`, `index_name`, `ai_provider`, `source_uri`, `document_name`, `document_status`, `pipeline_status`, `chunks_processed`, `embeddings_persisted`, `current_step`, `last_error_message`, and timestamps. + +#### Aggregated Pipeline Stats + +```sql +-- Summary stats per document across all indexes +SELECT * FROM dist_rag.pipeline_stats; + +-- Filter by a specific index +SELECT index_name, document_name, calls, total_chunks_processed, + total_embeddings_persisted, completion_rate_percent +FROM dist_rag.pipeline_stats +WHERE index_name = 'my_knowledge_base'; +``` + +Columns include: `index_id`, `index_name`, `document_name`, `calls`, `total_chunks_processed`, `total_embeddings_persisted`, `total_exec_time_seconds`, `successful_completions`, `completion_rate_percent`, and more. + +## Complete Example + +```sql +-- Step 1: Install the extension +CREATE EXTENSION IF NOT EXISTS pg_dist_rag; + +-- Step 2: Create document sources +SELECT dist_rag.create_source( + r_source_uri := 'https://docs.example.com/api-reference/' +) AS api_source_id; +-- returns: e.g. 'a1b2c3d4-...' + +SELECT dist_rag.create_source( + r_source_uri := 's3://company-docs/engineering/', + r_metadata := '{"team": "engineering", "access": "internal"}'::jsonb, + r_secrets_provider := 'AWS', + r_secrets_provider_params := '{"region": "us-east-1"}'::jsonb +) AS eng_source_id; +-- returns: e.g. 'e5f6g7h8-...' + +-- Step 3: Initialize a vector index with both sources +SELECT dist_rag.init_vector_index( + r_index_name := 'engineering_kb', + r_sources := ARRAY['a1b2c3d4-...', 'e5f6g7h8-...']::UUID[], + r_ai_provider := 'OPENAI', + r_embedding_model_params := '{"dimensions": 1536, "model": "text-embedding-ada-002"}'::jsonb +); + +-- Step 4: Build the index (queues all documents for preprocessing) +SELECT dist_rag.build_index(r_index_name := 'engineering_kb'); + +-- Step 5: Monitor progress +SELECT index_name, document_name, pipeline_status, chunks_processed, current_step +FROM dist_rag.vector_index_pipeline_details +WHERE index_name = 'engineering_kb'; + +-- Step 6: Check overall stats +SELECT document_name, calls, total_chunks_processed, completion_rate_percent +FROM dist_rag.pipeline_stats +WHERE index_name = 'engineering_kb'; +``` + +## Schema Reference + +### Tables + +| Table | Description | +|-------|-------------| +| `dist_rag.sources` | Registered document sources | +| `dist_rag.vector_indexes` | Vector index metadata | +| `dist_rag.vector_index_source_mappings` | Many-to-many mapping between indexes and sources | +| `dist_rag.documents` | Individual documents belonging to sources | +| `dist_rag.pipeline_details` | Per-document pipeline execution records | +| `dist_rag.work_queue` | Internal task queue with lease-based locking | + +### Enum Types + +| Type | Values | +|------|--------| +| `secrets_provider_enum` | `LOCAL`, `AWS`, `GCP`, `AZURE`, `HASHICORP_VAULT` | +| `create_source_status_enum` | `QUEUED`, `IN_PROGRESS`, `COMPLETED`, `FAILED` | +| `ai_provider_enum` | `OPENAI`, `LOCAL` | +| `index_build_status` | `INIT`, `IN_PROGRESS`, `NOT_STARTED` | +| `document_processing_status_enum` | `NOT_STARTED`, `QUEUED`, `PROCESSING`, `COMPLETED`, `FAILED`, `RETRY` | +| `pipeline_status_enum` | `PROCESSING`, `COMPLETED`, `FAILED` | +| `task_type_enum` | `CREATE_SOURCE`, `PREPROCESS` | +| `task_queue_status_enum` | `QUEUED`, `IN_PROGRESS`, `COMPLETED`, `FAILED` | + +### Views + +| View | Description | +|------|-------------| +| `dist_rag.vector_index_pipeline_details` | Detailed per-document pipeline execution info across all indexes | +| `dist_rag.pipeline_stats` | Aggregated pipeline statistics per document per index | + +## Running Tests + +```bash +psql -U postgres -d testdb -f sql/pg_dist_rag_test.sql +``` + +The test suite validates all public functions, error handling, view correctness, and schema integrity. diff --git a/yb-extensions/pg_dist_rag/pg_dist_rag--0.0.1.sql b/yb-extensions/pg_dist_rag/pg_dist_rag--0.0.1.sql new file mode 100644 index 0000000000000..daff50ed98eed --- /dev/null +++ b/yb-extensions/pg_dist_rag/pg_dist_rag--0.0.1.sql @@ -0,0 +1,585 @@ +CREATE SCHEMA if not exists dist_rag; + +CREATE TYPE dist_rag.secrets_provider_enum AS ENUM ( + 'LOCAL', 'AWS', 'GCP', 'AZURE', 'HASHICORP_VAULT' +); + +CREATE TYPE dist_rag.create_source_status_enum AS ENUM ( + 'QUEUED', 'IN_PROGRESS', 'COMPLETED', 'FAILED' +); + +CREATE TABLE dist_rag.sources ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + source_uri TEXT, + + --metadata filters for a specific source + metadata JSONB, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + completed_at TIMESTAMP, + status dist_rag.create_source_status_enum NOT NULL DEFAULT 'QUEUED', + -- security + secrets_provider dist_rag.secrets_provider_enum NOT NULL DEFAULT 'LOCAL', + secrets_provider_params JSONB + +); + +CREATE TYPE dist_rag.ai_provider_enum AS ENUM ( + 'OPENAI', 'LOCAL' +); + +CREATE TYPE dist_rag.index_build_status AS ENUM ( + 'INIT', 'IN_PROGRESS', 'NOT_STARTED' +); + +CREATE TABLE dist_rag.vector_indexes ( + + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- Vector Index details + index_name VARCHAR(50) NOT NULL DEFAULT 'pg_rag_default_store', + -- Specify m and ef construction + index_options JSONB, + index_creation_status dist_rag.index_build_status NOT NULL, + + -- model provider and API details + ai_provider dist_rag.ai_provider_enum NOT NULL, + embedding_model_params JSONB NOT NULL DEFAULT '{}', + secrets_provider dist_rag.secrets_provider_enum NOT NULL DEFAULT 'LOCAL', + secrets_provider_params JSONB + +); + +CREATE UNIQUE INDEX idx_vector_indexes_index_name ON dist_rag.vector_indexes(index_name); + + + +CREATE TABLE dist_rag.vector_index_source_mappings ( + + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + index_id UUID NOT NULL REFERENCES dist_rag.vector_indexes(id), + source_id UUID NOT NULL REFERENCES dist_rag.sources(id), + + --chunk + chunk_params JSONB NOT NULL DEFAULT '{}' + +); + +CREATE INDEX idx_vector_index_source_mappings_index_id_source_id ON dist_rag.vector_index_source_mappings(index_id, source_id); + + +CREATE TYPE dist_rag.document_processing_status_enum AS ENUM ( + 'NOT_STARTED', 'QUEUED', 'PROCESSING', 'COMPLETED', 'FAILED', 'RETRY' +); + +CREATE TABLE dist_rag.documents ( + document_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + source_id UUID NOT NULL REFERENCES dist_rag.sources(id), + + -- indiviual document metadata + document_name TEXT, + document_uri TEXT, + document_checksum TEXT, + + -- Current state + status dist_rag.document_processing_status_enum NOT NULL DEFAULT 'QUEUED' + +); + +CREATE INDEX idx_documents_source_id ON dist_rag.documents(source_id); + +CREATE TYPE dist_rag.pipeline_status_enum AS ENUM ( + 'PROCESSING', 'COMPLETED', 'FAILED' +); + +CREATE TABLE dist_rag.pipeline_details ( + pipeline_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + document_id UUID NOT NULL REFERENCES dist_rag.documents(document_id), + document_name TEXT NOT NULL, + + -- Current state + status dist_rag.pipeline_status_enum NOT NULL DEFAULT 'PROCESSING', + + -- Timing + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + last_started_at TIMESTAMP, + completed_at TIMESTAMP, + + -- embedding generation tracking + chunks_processed INTEGER, + embeddings_persisted INTEGER, + + -- Metadata (captured at pipeline start, never changes) + metadata_snapshot JSONB NOT NULL, + + -- Progress tracking + current_step VARCHAR(50), + last_error_message TEXT +); + +CREATE INDEX idx_pipeline_details_document_id ON dist_rag.pipeline_details(document_id); + +CREATE TYPE dist_rag.task_type_enum AS ENUM( + 'CREATE_SOURCE','PREPROCESS' +); + +CREATE TYPE dist_rag.task_queue_status_enum AS ENUM ( + 'QUEUED', 'IN_PROGRESS', 'COMPLETED', 'FAILED' +); + +CREATE TABLE dist_rag.work_queue ( + + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + -- task details + task_type dist_rag.task_type_enum NOT NULL, + task_status dist_rag.task_queue_status_enum NOT NULL DEFAULT 'QUEUED', + task_details JSONB NOT NULL, + current_worker UUID, + + -- lease/optimistic locking + lease_token UUID UNIQUE, + lease_acquired_at TIMESTAMP, + lease_expires_at TIMESTAMP, + + -- process timestamps + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + started_at TIMESTAMP, + completed_at TIMESTAMP + +); + +-- Index optimized for worker task polling: filters by task_status and orders by created_at +CREATE INDEX idx_work_queue_task_status_created_at ON dist_rag.work_queue(task_status, created_at); + +CREATE OR REPLACE FUNCTION dist_rag.create_source( + r_source_uri TEXT, + r_metadata JSONB DEFAULT '{}', + r_secrets_provider dist_rag.secrets_provider_enum DEFAULT 'LOCAL', + r_secrets_provider_params JSONB DEFAULT '{}' +) +RETURNS UUID +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + v_id UUID; +BEGIN + -- Validate required parameter + IF r_source_uri IS NULL OR r_source_uri = '' THEN + RAISE EXCEPTION 'source_uri is required and cannot be NULL or empty'; + END IF; + + -- Insert source and capture the ID + INSERT INTO dist_rag.sources (source_uri, metadata, secrets_provider, secrets_provider_params) + VALUES (r_source_uri, COALESCE(r_metadata, '{}'::jsonb), r_secrets_provider, r_secrets_provider_params) + RETURNING id INTO v_id; + + -- Validate insertion + IF v_id IS NULL THEN + RAISE EXCEPTION 'Failed to create source - no ID returned'; + END IF; + + -- Insert work queue entry for source creation + INSERT INTO dist_rag.work_queue (task_type, task_status, task_details) + VALUES ( + 'CREATE_SOURCE'::dist_rag.task_type_enum, + 'QUEUED'::dist_rag.task_queue_status_enum, + jsonb_build_object('source_id', v_id) + ); + + RETURN v_id; +EXCEPTION WHEN OTHERS THEN + RAISE EXCEPTION 'Error creating source: % - %', SQLSTATE, SQLERRM; +END; +$$; + +COMMENT ON FUNCTION dist_rag.create_source(TEXT, JSONB, dist_rag.secrets_provider_enum, JSONB) +IS 'Create a new RAG source'; + + +CREATE OR REPLACE FUNCTION dist_rag._create_vector_index_table( + r_index_name VARCHAR(50), + r_vector_dimensions INTEGER, + r_index_options JSONB DEFAULT '{}' +) +RETURNS VOID +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + v_distance_metric TEXT; + v_m INTEGER; + v_ef_construction INTEGER; + v_ops_class TEXT; +BEGIN + + -- validate required parameters + IF r_index_name IS NULL OR r_index_name = '' THEN + RAISE EXCEPTION 'index_name is required and cannot be NULL or empty'; + END IF; + + IF r_vector_dimensions IS NULL OR r_vector_dimensions <= 0 THEN + RAISE EXCEPTION 'vector_dimensions is required and cannot be NULL or less than 1'; + END IF; + + -- Extract index options with defaults + v_distance_metric := COALESCE(r_index_options ->> 'distance_metric', 'cosine'); + v_m := COALESCE((r_index_options ->> 'm')::INTEGER, 16); + v_ef_construction := COALESCE((r_index_options ->> 'ef_construction')::INTEGER, 64); + + -- Map friendly distance metric name to pgvector operator class + v_ops_class := CASE v_distance_metric + WHEN 'cosine' THEN 'vector_cosine_ops' + WHEN 'l2' THEN 'vector_l2_ops' + WHEN 'ip' THEN 'vector_ip_ops' + ELSE NULL + END; + + IF v_ops_class IS NULL THEN + RAISE EXCEPTION 'Invalid distance_metric "%". Must be one of: cosine, l2, ip', v_distance_metric; + END IF; + + -- Create the vector store table + EXECUTE 'CREATE TABLE ' || quote_ident(r_index_name) || ' ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + chunk_text TEXT NOT NULL, + embeddings vector(' || r_vector_dimensions || ') NOT NULL, + document_id UUID NOT NULL, + metadata_filters JSONB NOT NULL DEFAULT ''' || '{}' || ''' + )'; + + -- Create HNSW index on the embeddings column + EXECUTE 'CREATE INDEX ' || quote_ident('idx_' || r_index_name || '_embeddings') + || ' ON ' || quote_ident(r_index_name) + || ' USING ybhnsw (embeddings ' || v_ops_class || ')' + || ' WITH (m = ' || v_m || ', ef_construction = ' || v_ef_construction || ')'; + +END; +$$; + +CREATE OR REPLACE FUNCTION dist_rag.init_vector_index( + r_index_name VARCHAR(50) DEFAULT 'pg_rag_default_store', + r_sources UUID[] DEFAULT ARRAY[]::UUID[], + r_chunk_params JSONB DEFAULT '{}', + r_ai_provider dist_rag.ai_provider_enum DEFAULT 'OPENAI', + r_embedding_model_params JSONB DEFAULT '{}', + r_index_options JSONB DEFAULT '{"distance_metric": "cosine", "m": 16, "ef_construction": 64}' +) +RETURNS UUID +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + v_index_id UUID; + v_source UUID; + v_vector_dimensions INTEGER; + v_source_count INTEGER; + v_sources_processed INTEGER := 0; +BEGIN + + -- Validate required parameters + IF r_index_name IS NULL OR r_index_name = '' THEN + RAISE EXCEPTION 'index_name is required and cannot be NULL or empty'; + END IF; + + -- Check if the index_name already exists and raise an error if it does + IF EXISTS ( + SELECT 1 FROM dist_rag.vector_indexes WHERE index_name = r_index_name + ) THEN + RAISE EXCEPTION 'Vector index with name "%" already exists', r_index_name; + END IF; + + -- Validate embedding model parameters contain dimensions + IF r_embedding_model_params IS NULL OR r_embedding_model_params ->> 'dimensions' IS NULL THEN + RAISE EXCEPTION 'embedding_model_params must contain "dimensions" key'; + END IF; + + -- Extract and validate vector dimensions + v_vector_dimensions := (r_embedding_model_params ->> 'dimensions')::INTEGER; + IF v_vector_dimensions IS NULL OR v_vector_dimensions <= 0 THEN + RAISE EXCEPTION 'vector_dimensions must be a positive integer'; + END IF; + + -- Validate all source IDs exist before proceeding (fail-fast check) + IF r_sources IS NOT NULL AND array_length(r_sources, 1) > 0 THEN + SELECT COUNT(*) INTO v_source_count + FROM dist_rag.sources + WHERE id = ANY(r_sources); + + IF v_source_count != array_length(r_sources, 1) THEN + RAISE EXCEPTION 'One or more source IDs do not exist in dist_rag.sources'; + END IF; + END IF; + + -- BEGIN TRANSACTION: All operations below must succeed together or rollback together + + -- Step 1: Create the vector index table and HNSW index on embeddings + BEGIN + PERFORM dist_rag._create_vector_index_table(r_index_name, v_vector_dimensions, r_index_options); + EXCEPTION WHEN OTHERS THEN + RAISE EXCEPTION 'Failed to create vector index table "%": % - %', r_index_name, SQLSTATE, SQLERRM; + END; + + -- Step 2: Insert vector index metadata + BEGIN + INSERT INTO dist_rag.vector_indexes ( + index_name, + index_options, + index_creation_status, + ai_provider, + embedding_model_params + ) + VALUES ( + r_index_name, + r_index_options, + 'INIT'::dist_rag.index_build_status, + r_ai_provider, + r_embedding_model_params + ) + RETURNING id INTO v_index_id; + + IF v_index_id IS NULL THEN + RAISE EXCEPTION 'Failed to insert vector index metadata - no ID returned'; + END IF; + EXCEPTION WHEN OTHERS THEN + RAISE EXCEPTION 'Failed to insert vector index metadata: % - %', SQLSTATE, SQLERRM; + END; + + -- Step 3: Create mappings for each source (this will validate foreign keys) + IF r_sources IS NOT NULL AND array_length(r_sources, 1) > 0 THEN + BEGIN + FOREACH v_source IN ARRAY r_sources LOOP + INSERT INTO dist_rag.vector_index_source_mappings (index_id, source_id, chunk_params) + VALUES (v_index_id, v_source, r_chunk_params); + v_sources_processed := v_sources_processed + 1; + END LOOP; + EXCEPTION WHEN OTHERS THEN + RAISE EXCEPTION 'Failed to create source mappings (processed % of % sources): % - %', + v_sources_processed, array_length(r_sources, 1), SQLSTATE, SQLERRM; + END; + END IF; + + RETURN v_index_id; + +EXCEPTION WHEN OTHERS THEN + -- If any step fails, the entire transaction will be rolled back + RAISE EXCEPTION 'Error initializing vector index: % - %', SQLSTATE, SQLERRM; +END; +$$; + +COMMENT ON FUNCTION dist_rag.init_vector_index(VARCHAR, UUID[], JSONB, dist_rag.ai_provider_enum, JSONB, JSONB) +IS 'Initialize a new vector index with optional HNSW index configuration via r_index_options'; + +CREATE OR REPLACE FUNCTION dist_rag.add_source_to_index( + r_index_id UUID, + r_source_id UUID, + r_chunk_params JSONB DEFAULT '{}' +) +RETURNS VOID +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +BEGIN + INSERT INTO dist_rag.vector_index_source_mappings (index_id, source_id, chunk_params) + VALUES (r_index_id, r_source_id, r_chunk_params); +EXCEPTION WHEN OTHERS THEN + RAISE EXCEPTION 'Error adding source to index: % - %', SQLSTATE, SQLERRM; +END; +$$; + +COMMENT ON FUNCTION dist_rag.add_source_to_index(UUID, UUID, JSONB) +IS 'Add a source to a vector index'; + + +CREATE OR REPLACE FUNCTION dist_rag._queue_source_documents( + r_source_id UUID, + r_index_id UUID +) +RETURNS INTEGER +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + v_document RECORD; + v_count INTEGER := 0; +BEGIN + -- Validate required parameter + IF r_source_id IS NULL THEN + RAISE EXCEPTION 'source_id is required and cannot be NULL'; + END IF; + + -- Validate required parameter + IF r_index_id IS NULL THEN + RAISE EXCEPTION 'index_id is required and cannot be NULL'; + END IF; + + -- Insert work queue entries for all documents in this source + INSERT INTO dist_rag.work_queue ( + task_type, + task_status, + task_details, + created_at + ) + SELECT + 'PREPROCESS'::dist_rag.task_type_enum, + 'QUEUED'::dist_rag.task_queue_status_enum, + jsonb_build_object( + 'index_id', r_index_id, + 'source_id', r_source_id, + 'document_id', d.document_id, + 'document_name', d.document_name, + 'document_uri', d.document_uri, + 'document_status', d.status + ), + NOW() + FROM dist_rag.documents d + WHERE d.source_id = r_source_id; + + GET DIAGNOSTICS v_count = ROW_COUNT; + + RAISE NOTICE 'Number of Documents Queued % for Preprocessing', v_count; + RETURN v_count; + +EXCEPTION WHEN OTHERS THEN + RAISE EXCEPTION 'Error queuing documents for source %: % - %', r_source_id, SQLSTATE, SQLERRM; +END; +$$; + +CREATE OR REPLACE FUNCTION dist_rag.build_index( + r_index_id UUID DEFAULT NULL, + r_index_name VARCHAR(50) DEFAULT NULL +) +RETURNS VOID +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + v_index_id UUID; + v_count INTEGER; +BEGIN + -- Validate that exactly one parameter is provided + IF (r_index_id IS NULL AND r_index_name IS NULL) THEN + RAISE EXCEPTION 'Either index_id or index_name is required and cannot both be NULL'; + END IF; + + IF (r_index_id IS NOT NULL AND r_index_name IS NOT NULL) THEN + RAISE EXCEPTION 'Provide only one of index_id or index_name, not both'; + END IF; + + -- If index_name is provided, resolve it to index_id + IF r_index_name IS NOT NULL THEN + SELECT id INTO v_index_id + FROM dist_rag.vector_indexes + WHERE index_name = r_index_name; + + IF v_index_id IS NULL THEN + RAISE EXCEPTION 'Vector index with name "%" does not exist', r_index_name; + END IF; + ELSE + v_index_id := r_index_id; + END IF; + + -- Queue all documents for all sources associated with this index + INSERT INTO dist_rag.work_queue ( + task_type, + task_status, + task_details, + created_at + ) + SELECT + 'PREPROCESS'::dist_rag.task_type_enum, + 'QUEUED'::dist_rag.task_queue_status_enum, + jsonb_build_object( + 'index_id', v_index_id, + 'source_id', m.source_id, + 'document_id', d.document_id, + 'document_name', d.document_name, + 'document_uri', d.document_uri, + 'document_status', d.status + ), + NOW() + FROM dist_rag.vector_index_source_mappings m + JOIN dist_rag.documents d ON d.source_id = m.source_id + WHERE m.index_id = v_index_id; + + GET DIAGNOSTICS v_count = ROW_COUNT; + + RAISE NOTICE 'Index build kicked off successfully for index_id: %, documents queued: %', v_index_id, v_count; + +EXCEPTION WHEN OTHERS THEN + RAISE EXCEPTION 'Error building index: % - %', SQLSTATE, SQLERRM; +END; +$$; + +-- ============================================ +-- VIEWS +-- ============================================ + + +CREATE OR REPLACE VIEW dist_rag.vector_index_pipeline_details AS +SELECT + vi.id as index_id, + vi.index_name, + vi.ai_provider, + vi.index_creation_status, + s.id as source_id, + s.source_uri, + d.document_id, + d.document_name, + d.document_uri, + d.document_checksum, + d.status as document_status, + pd.pipeline_id, + pd.status as pipeline_status, + pd.chunks_processed, + pd.embeddings_persisted, + pd.current_step, + pd.last_error_message, + pd.created_at as pipeline_created_at, + pd.last_started_at as pipeline_last_started_at, + pd.completed_at as pipeline_completed_at, + pd.metadata_snapshot +FROM dist_rag.vector_indexes vi +INNER JOIN dist_rag.vector_index_source_mappings vism ON vi.id = vism.index_id +INNER JOIN dist_rag.sources s ON vism.source_id = s.id +INNER JOIN dist_rag.documents d ON s.id = d.source_id +LEFT JOIN dist_rag.pipeline_details pd ON d.document_id = pd.document_id +ORDER BY vi.index_name, s.source_uri, d.document_name, pd.created_at DESC; + +COMMENT ON VIEW dist_rag.vector_index_pipeline_details +IS 'Shows all pipeline details for documents across all vector indexes. Query by index_id or index_name to filter results.'; + +CREATE OR REPLACE VIEW dist_rag.pipeline_stats AS +SELECT + vi.id as index_id, + vi.index_name, + vi.ai_provider, + s.source_uri, + d.document_id, + d.document_name, + COUNT(pd.pipeline_id) as calls, + COALESCE(SUM(pd.chunks_processed), 0) as total_chunks_processed, + COALESCE(SUM(pd.embeddings_persisted), 0) as total_embeddings_persisted, + COALESCE(SUM(EXTRACT(EPOCH FROM (pd.completed_at - pd.created_at))), 0) as total_exec_time_seconds, + COUNT(CASE WHEN pd.status = 'COMPLETED' THEN 1 END) as successful_completions, + ROUND(100.0 * COUNT(CASE WHEN pd.status = 'COMPLETED' THEN 1 END) / NULLIF(COUNT(pd.pipeline_id), 0), 2) as completion_rate_percent, + MAX(pd.completed_at) as last_completed_at, + (array_agg(pd.last_error_message ORDER BY pd.created_at DESC) FILTER (WHERE pd.last_error_message IS NOT NULL))[1] as last_error_message, + MIN(pd.created_at) as first_pipeline_started_at +FROM dist_rag.vector_indexes vi +INNER JOIN dist_rag.vector_index_source_mappings vism ON vi.id = vism.index_id +INNER JOIN dist_rag.sources s ON vism.source_id = s.id +INNER JOIN dist_rag.documents d ON s.id = d.source_id +LEFT JOIN dist_rag.pipeline_details pd ON d.document_id = pd.document_id +GROUP BY vi.id, vi.index_name, vi.ai_provider, s.source_uri, d.document_id, d.document_name +ORDER BY vi.index_name, s.source_uri, d.document_name; + +COMMENT ON VIEW dist_rag.pipeline_stats +IS 'Shows all pipeline stats for all vector indexes. Query by index_id or index_name to filter results.'; + +-- ============================================ +-- PERMISSIONS +-- ============================================ +GRANT USAGE ON SCHEMA dist_rag TO PUBLIC; +GRANT ALL ON ALL SEQUENCES IN SCHEMA dist_rag TO PUBLIC; +GRANT ALL ON ALL TABLES IN SCHEMA dist_rag TO PUBLIC; +GRANT ALL ON ALL FUNCTIONS IN SCHEMA dist_rag TO PUBLIC; diff --git a/yb-extensions/pg_dist_rag/pg_dist_rag.control b/yb-extensions/pg_dist_rag/pg_dist_rag.control new file mode 100644 index 0000000000000..ef2f9dc1249f6 --- /dev/null +++ b/yb-extensions/pg_dist_rag/pg_dist_rag.control @@ -0,0 +1,5 @@ +comment = 'Distributed RAG (Retrieval-Augmented Generation) extension for PostgreSQL' +default_version = '0.0.1' +relocatable = false +requires = 'vector' + diff --git a/yb-extensions/pg_dist_rag/sql/pg_dist_rag_test.sql b/yb-extensions/pg_dist_rag/sql/pg_dist_rag_test.sql new file mode 100644 index 0000000000000..549b66d77c5a5 --- /dev/null +++ b/yb-extensions/pg_dist_rag/sql/pg_dist_rag_test.sql @@ -0,0 +1,535 @@ +-- ============================================ +-- pg_dist_rag Extension - Test Suite +-- ============================================ +-- This file can be used to validate the extension functionality +-- Run with: psql -U postgres -d testdb -f sql/pg_dist_rag_test.sql + +-- Create extension (if not already created) +CREATE EXTENSION IF NOT EXISTS pg_dist_rag; + +-- ============================================ +-- Test 1: Create a basic source +-- ============================================ +DO $$ +DECLARE + v_source_id UUID; +BEGIN + RAISE NOTICE '=== Test 1: Create a basic source ==='; + v_source_id := dist_rag.create_source( + r_source_uri := 's3://proto-automated-embedding/playgentic_documents/sample/' + ); + ASSERT v_source_id IS NOT NULL, 'Source ID should not be NULL'; + RAISE NOTICE 'PASS: Created source with ID %', v_source_id; +END $$; + +-- ================================================ +-- Test 2: Create source with metadata and secrets +-- ================================================ +DO $$ +DECLARE + v_source_id UUID; + v_metadata JSONB; + v_secrets JSONB; +BEGIN + RAISE NOTICE '=== Test 2: Create source with metadata and secrets ==='; + v_metadata := jsonb_build_object('language', 'english', 'type', 'documentation'); + v_secrets := jsonb_build_object('api_key', 'secret123', 'region', 'us-east-1'); + v_source_id := dist_rag.create_source( + r_source_uri := 's3://proto-automated-embedding/playgentic_documents/sample/', + r_metadata := v_metadata, + r_secrets_provider := 'AWS', + r_secrets_provider_params := v_secrets + ); + ASSERT v_source_id IS NOT NULL, 'Source ID should not be NULL'; + -- Verify metadata was stored + ASSERT (SELECT metadata->>'language' FROM dist_rag.sources WHERE id = v_source_id) = 'english', + 'Metadata should be stored correctly'; + RAISE NOTICE 'PASS: Created source with metadata - ID: %', v_source_id; +END $$; + +-- ============================================ +-- Test 3: Create source and verify work queue entry +-- ============================================ +DO $$ +DECLARE + v_source_id UUID; + v_work_queue_count INT; +BEGIN + RAISE NOTICE '=== Test 3: Create source and verify work queue entry ==='; + v_source_id := dist_rag.create_source( + r_source_uri := 's3://proto-automated-embedding/playgentic_documents/sample/' + ); + -- Verify work queue entry was created + SELECT COUNT(*) INTO v_work_queue_count + FROM dist_rag.work_queue + WHERE task_details->>'source_id' = v_source_id::TEXT + AND task_type = 'CREATE_SOURCE'::dist_rag.task_type_enum; + ASSERT v_work_queue_count = 1, 'Should have exactly one work queue entry'; + RAISE NOTICE 'PASS: Work queue entry created for source %', v_source_id; +END $$; + +-- ============================================ +-- Test 4: Initialize vector index with sources +-- ============================================ +DO $$ +DECLARE + v_source_id_1 UUID; + v_source_id_2 UUID; + v_index_id UUID; + v_mapping_count INT; +BEGIN + RAISE NOTICE '=== Test 4: Initialize vector index with sources ==='; + -- Create two sources + v_source_id_1 := dist_rag.create_source(r_source_uri := 's3://proto-automated-embedding/playgentic_documents/sample/'); + v_source_id_2 := dist_rag.create_source(r_source_uri := 's3://proto-automated-embedding/playgentic_documents/drills/'); + -- Initialize vector index with both sources + v_index_id := dist_rag.init_vector_index( + r_index_name := 'test_index_1', + r_sources := ARRAY[v_source_id_1, v_source_id_2]::UUID[], + r_ai_provider := 'OPENAI', + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + ASSERT v_index_id IS NOT NULL, 'Index ID should not be NULL'; + -- Verify mappings were created + SELECT COUNT(*) INTO v_mapping_count + FROM dist_rag.vector_index_source_mappings + WHERE index_id = v_index_id; + ASSERT v_mapping_count = 2, 'Should have 2 source mappings'; + RAISE NOTICE 'PASS: Vector index created with % source mappings', v_mapping_count; +END $$; + +-- ============================================ +-- Test 4b: Initialize vector index with defaults +-- ============================================ +DO $$ +DECLARE + v_index_id UUID; + v_mapping_count INT; +BEGIN + RAISE NOTICE '=== Test 4b: Initialize vector index with defaults (no sources) ==='; + -- Initialize vector index with only index name (using default empty sources array) + v_index_id := dist_rag.init_vector_index( + r_index_name := 'test_index_defaults', + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + ASSERT v_index_id IS NOT NULL, 'Index ID should not be NULL'; + -- Verify no mappings were created (because sources is empty array) + SELECT COUNT(*) INTO v_mapping_count + FROM dist_rag.vector_index_source_mappings + WHERE index_id = v_index_id; + ASSERT v_mapping_count = 0, 'Should have 0 source mappings when no sources provided'; + RAISE NOTICE 'PASS: Vector index created with defaults - mappings: %', v_mapping_count; +END $$; + +-- ============================================ +-- Test 5: Add source to existing index +-- ============================================ +DO $$ +DECLARE + v_source_id UUID; + v_index_id UUID; + v_mapping_count INT; +BEGIN + RAISE NOTICE '=== Test 5: Add source to existing index ==='; + -- Create a source and index (with default empty sources) + v_source_id := dist_rag.create_source(r_source_uri := 's3://proto-automated-embedding/playgentic_documents/drills/'); + v_index_id := dist_rag.init_vector_index( + r_index_name := 'test_index_2', + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + -- r_sources uses default empty array + ); + -- Add first source to the index + PERFORM dist_rag.add_source_to_index( + r_index_id := v_index_id, + r_source_id := v_source_id + ); + -- Add second source to the index + PERFORM dist_rag.add_source_to_index( + r_index_id := v_index_id, + r_source_id := (SELECT id FROM dist_rag.sources LIMIT 1 OFFSET 1) + ); + -- Verify the mappings were added + SELECT COUNT(*) INTO v_mapping_count + FROM dist_rag.vector_index_source_mappings + WHERE index_id = v_index_id; + ASSERT v_mapping_count = 2, 'Should have 2 source mappings after adding'; + RAISE NOTICE 'PASS: Sources added to index - total mappings: %', v_mapping_count; +END $$; + +-- ============================================ +-- Test 6: Queue source documents +-- ============================================ +DO $$ +DECLARE + v_source_id UUID; + v_doc_1 UUID; + v_doc_2 UUID; + v_index_id UUID; + v_queued_count INT; +BEGIN + RAISE NOTICE '=== Test 6: Queue source documents ==='; + -- Create a source + v_source_id := dist_rag.create_source(r_source_uri := 's3://proto-automated-embedding/playgentic_documents/drills/'); + -- Create an index for this source + v_index_id := dist_rag.init_vector_index( + r_index_name := 'queue_test_index', + r_sources := ARRAY[v_source_id]::UUID[], + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + -- Add documents for this source + INSERT INTO dist_rag.documents (source_id, document_name, document_uri, status) + VALUES (v_source_id, 'doc1.pdf', 'https://example.com/doc1.pdf', 'QUEUED'::dist_rag.document_processing_status_enum) + RETURNING document_id INTO v_doc_1; + INSERT INTO dist_rag.documents (source_id, document_name, document_uri, status) + VALUES (v_source_id, 'doc2.txt', 'https://example.com/doc2.txt', 'QUEUED'::dist_rag.document_processing_status_enum) + RETURNING document_id INTO v_doc_2; + -- Queue documents + v_queued_count := dist_rag._queue_source_documents(v_source_id, v_index_id); + ASSERT v_queued_count = 2, 'Should have queued 2 documents'; + -- Verify work queue entries + SELECT COUNT(*) INTO v_queued_count + FROM dist_rag.work_queue + WHERE task_type = 'PREPROCESS'::dist_rag.task_type_enum + AND task_details->>'source_id' = v_source_id::TEXT + AND task_details->>'index_id' = v_index_id::TEXT; + ASSERT v_queued_count = 2, 'Should have 2 preprocess tasks in work queue'; + RAISE NOTICE 'PASS: Queued % documents from source', v_queued_count; +END $$; + +-- ============================================ +-- Test 7: Build index (full workflow) +-- ============================================ +DO $$ +DECLARE + v_source_id_1 UUID; + v_source_id_2 UUID; + v_index_id UUID; + v_preprocess_count INT; + v_doc_count INT; +BEGIN + RAISE NOTICE '=== Test 7: Build index (full workflow) ==='; + -- Create sources + v_source_id_1 := dist_rag.create_source(r_source_uri := 'https://source1.com/docs/'); + v_source_id_2 := dist_rag.create_source(r_source_uri := 'https://source2.com/docs/'); + -- Add documents to sources + INSERT INTO dist_rag.documents (source_id, document_name, document_uri) + VALUES (v_source_id_1, 'report1.pdf', 'https://example.com/report1.pdf'), + (v_source_id_1, 'report2.pdf', 'https://example.com/report2.pdf'), + (v_source_id_2, 'guide1.pdf', 'https://example.com/guide1.pdf'); + -- Get document count + SELECT COUNT(*) INTO v_doc_count FROM dist_rag.documents WHERE source_id IN (v_source_id_1, v_source_id_2); + -- Initialize index with both sources + v_index_id := dist_rag.init_vector_index( + r_index_name := 'build_test_index', + r_sources := ARRAY[v_source_id_1, v_source_id_2]::UUID[], + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + -- Build the index + PERFORM dist_rag.build_index(v_index_id); + -- Verify preprocess tasks were queued + SELECT COUNT(*) INTO v_preprocess_count + FROM dist_rag.work_queue + WHERE task_type = 'PREPROCESS'::dist_rag.task_type_enum + AND task_details->>'index_id' = v_index_id::TEXT; + ASSERT v_preprocess_count >= v_doc_count, 'Should have queued all documents'; + RAISE NOTICE 'PASS: Index built with % documents queued for preprocessing', v_preprocess_count; +END $$; + +-- ============================================ +-- Test 7b: Build index by name +-- ============================================ +DO $$ +DECLARE + v_source_id UUID; + v_index_id UUID; + v_preprocess_count INT; +BEGIN + RAISE NOTICE '=== Test 7b: Build index by name ==='; + -- Create source and documents + v_source_id := dist_rag.create_source(r_source_uri := 'https://byname.com/docs/'); + INSERT INTO dist_rag.documents (source_id, document_name, document_uri) + VALUES (v_source_id, 'byname_doc.pdf', 'https://byname.com/byname_doc.pdf'); + -- Initialize index + v_index_id := dist_rag.init_vector_index( + r_index_name := 'build_by_name_index', + r_sources := ARRAY[v_source_id]::UUID[], + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + -- Build index using name instead of ID + PERFORM dist_rag.build_index(r_index_name := 'build_by_name_index'); + -- Verify preprocess tasks were queued + SELECT COUNT(*) INTO v_preprocess_count + FROM dist_rag.work_queue + WHERE task_type = 'PREPROCESS'::dist_rag.task_type_enum + AND task_details->>'index_id' = v_index_id::TEXT; + ASSERT v_preprocess_count >= 1, 'Should have queued at least 1 document by name'; + RAISE NOTICE 'PASS: Build index by name queued % documents', v_preprocess_count; +END $$; + +-- ============================================ +-- Test 8: Error handling - NULL source_uri +-- ============================================ +DO $$ +BEGIN + RAISE NOTICE '=== Test 9: Error handling - NULL source_uri ==='; + -- Try to create source without URI + PERFORM dist_rag.create_source(r_source_uri := NULL); + RAISE NOTICE 'FAIL: Should have raised an exception for NULL source_uri'; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'PASS: Correctly caught error when source_uri is NULL - %', SQLERRM; +END $$; + +-- ============================================ +-- Test 8b: Error handling - Empty string source_uri +-- ============================================ +DO $$ +BEGIN + RAISE NOTICE '=== Test 9b: Error handling - Empty string source_uri ==='; + PERFORM dist_rag.create_source(r_source_uri := ''); + RAISE NOTICE 'FAIL: Should have raised an exception for empty source_uri'; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'PASS: Correctly caught error when source_uri is empty - %', SQLERRM; +END $$; + +-- ============================================ +-- Test 9: Error handling - Build index with no sources +-- ============================================ +DO $$ +DECLARE + v_index_id UUID; +BEGIN + RAISE NOTICE '=== Test 10: Build index with no sources (should handle gracefully) ==='; + -- Create an empty index + v_index_id := dist_rag.init_vector_index( + r_index_name := 'empty_index', + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + -- Build the empty index (should not error) + PERFORM dist_rag.build_index(v_index_id); + RAISE NOTICE 'PASS: Build index handled empty sources gracefully'; +END $$; + +-- ============================================ +-- Test 10: Error handling - Duplicate index name +-- ============================================ +DO $$ +DECLARE + v_index_id UUID; +BEGIN + RAISE NOTICE '=== Test 11: Error handling - Duplicate index name ==='; + -- First index should succeed + v_index_id := dist_rag.init_vector_index( + r_index_name := 'duplicate_name_test', + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + -- Second index with same name should fail + v_index_id := dist_rag.init_vector_index( + r_index_name := 'duplicate_name_test', + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + RAISE NOTICE 'FAIL: Should have raised an exception for duplicate index name'; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'PASS: Correctly caught duplicate index name error - %', SQLERRM; +END $$; + +-- ============================================ +-- Test 11: Error handling - Missing dimensions in embedding_model_params +-- ============================================ +DO $$ +DECLARE + v_index_id UUID; +BEGIN + RAISE NOTICE '=== Test 12: Error handling - Missing dimensions in embedding_model_params ==='; + v_index_id := dist_rag.init_vector_index( + r_index_name := 'missing_dims_index', + r_embedding_model_params := jsonb_build_object('model', 'text-embedding-ada-002') + ); + RAISE NOTICE 'FAIL: Should have raised an exception for missing dimensions'; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'PASS: Correctly caught missing dimensions error - %', SQLERRM; +END $$; + +-- ============================================ +-- Test 12: Error handling - Invalid source IDs in init_vector_index +-- ============================================ +DO $$ +DECLARE + v_index_id UUID; + v_fake_id UUID := gen_random_uuid(); +BEGIN + RAISE NOTICE '=== Test 13: Error handling - Invalid source IDs in init_vector_index ==='; + v_index_id := dist_rag.init_vector_index( + r_index_name := 'invalid_sources_index', + r_sources := ARRAY[v_fake_id]::UUID[], + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + RAISE NOTICE 'FAIL: Should have raised an exception for invalid source IDs'; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'PASS: Correctly caught invalid source IDs error - %', SQLERRM; +END $$; + +-- ============================================ +-- Test 13: Error handling - build_index with both params NULL +-- ============================================ +DO $$ +BEGIN + RAISE NOTICE '=== Test 14: Error handling - build_index with both params NULL ==='; + PERFORM dist_rag.build_index(r_index_id := NULL, r_index_name := NULL); + RAISE NOTICE 'FAIL: Should have raised an exception for both NULL params'; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'PASS: Correctly caught both NULL params error - %', SQLERRM; +END $$; + +-- ============================================ +-- Test 14: Error handling - build_index with both params provided +-- ============================================ +DO $$ +DECLARE + v_source_id UUID; + v_index_id UUID; +BEGIN + RAISE NOTICE '=== Test 15: Error handling - build_index with both params provided ==='; + v_source_id := dist_rag.create_source(r_source_uri := 'https://both-params.com/docs/'); + v_index_id := dist_rag.init_vector_index( + r_index_name := 'both_params_index', + r_sources := ARRAY[v_source_id]::UUID[], + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + PERFORM dist_rag.build_index(r_index_id := v_index_id, r_index_name := 'both_params_index'); + RAISE NOTICE 'FAIL: Should have raised an exception for both params provided'; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'PASS: Correctly caught both params provided error - %', SQLERRM; +END $$; + +-- ============================================ +-- Test 15: Error handling - build_index with non-existent name +-- ============================================ +DO $$ +BEGIN + RAISE NOTICE '=== Test 16: Error handling - build_index with non-existent name ==='; + PERFORM dist_rag.build_index(r_index_name := 'this_index_does_not_exist'); + RAISE NOTICE 'FAIL: Should have raised an exception for non-existent index name'; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'PASS: Correctly caught non-existent index name error - %', SQLERRM; +END $$; + +-- ============================================ +-- Test 16: add_source_to_index with custom chunk_params +-- ============================================ +DO $$ +DECLARE + v_source_id UUID; + v_index_id UUID; + v_chunk_params JSONB; + v_stored_params JSONB; +BEGIN + RAISE NOTICE '=== Test 17: add_source_to_index with custom chunk_params ==='; + v_source_id := dist_rag.create_source(r_source_uri := 'https://chunk-params.com/docs/'); + v_index_id := dist_rag.init_vector_index( + r_index_name := 'chunk_params_index', + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + v_chunk_params := jsonb_build_object('chunk_size', 512, 'overlap', 50, 'strategy', 'recursive'); + PERFORM dist_rag.add_source_to_index( + r_index_id := v_index_id, + r_source_id := v_source_id, + r_chunk_params := v_chunk_params + ); + -- Verify chunk_params were stored correctly + SELECT chunk_params INTO v_stored_params + FROM dist_rag.vector_index_source_mappings + WHERE index_id = v_index_id AND source_id = v_source_id; + ASSERT v_stored_params IS NOT NULL, 'chunk_params should be stored'; + ASSERT (v_stored_params->>'chunk_size')::INT = 512, 'chunk_size should be 512'; + ASSERT (v_stored_params->>'overlap')::INT = 50, 'overlap should be 50'; + RAISE NOTICE 'PASS: Custom chunk_params stored correctly - %', v_stored_params; +END $$; + +-- ============================================ +-- Test 17: Views - vector_index_pipeline_details +-- ============================================ +DO $$ +DECLARE + v_source_id UUID; + v_index_id UUID; + v_doc_id UUID; + v_view_count INT; +BEGIN + RAISE NOTICE '=== Test 18: Views - vector_index_pipeline_details ==='; + -- Create source, index, and documents + v_source_id := dist_rag.create_source(r_source_uri := 'https://view-test.com/docs/'); + v_index_id := dist_rag.init_vector_index( + r_index_name := 'view_test_index', + r_sources := ARRAY[v_source_id]::UUID[], + r_embedding_model_params := jsonb_build_object('dimensions', 1536) + ); + INSERT INTO dist_rag.documents (source_id, document_name, document_uri) + VALUES (v_source_id, 'view_doc.pdf', 'https://view-test.com/view_doc.pdf') + RETURNING document_id INTO v_doc_id; + -- Insert a pipeline detail for this document + INSERT INTO dist_rag.pipeline_details ( + document_id, document_name, status, chunks_processed, embeddings_persisted, + current_step, metadata_snapshot, created_at, completed_at + ) VALUES ( + v_doc_id, 'view_doc.pdf', 'COMPLETED', 10, 10, + 'DONE', '{"model": "text-embedding-ada-002"}'::jsonb, NOW() - interval '5 minutes', NOW() + ); + -- Verify the view returns data + SELECT COUNT(*) INTO v_view_count + FROM dist_rag.vector_index_pipeline_details + WHERE index_id = v_index_id; + ASSERT v_view_count >= 1, 'vector_index_pipeline_details should have at least 1 row'; + RAISE NOTICE 'PASS: vector_index_pipeline_details returned % rows for index', v_view_count; +END $$; + +-- ============================================ +-- Test 19: Views - pipeline_stats +-- ============================================ +DO $$ +DECLARE + v_stats_count INT; + v_total_chunks INT; + v_completion_rate NUMERIC; +BEGIN + RAISE NOTICE '=== Test 19: Views - pipeline_stats ==='; + -- Use data created in Test 18 + SELECT COUNT(*) INTO v_stats_count + FROM dist_rag.pipeline_stats + WHERE index_name = 'view_test_index'; + ASSERT v_stats_count >= 1, 'pipeline_stats should have at least 1 row for view_test_index'; + -- Verify aggregated fields + SELECT total_chunks_processed, completion_rate_percent + INTO v_total_chunks, v_completion_rate + FROM dist_rag.pipeline_stats + WHERE index_name = 'view_test_index' + LIMIT 1; + ASSERT v_total_chunks >= 10, 'Should report at least 10 chunks processed'; + ASSERT v_completion_rate = 100.00, 'Completion rate should be 100% for 1 completed pipeline'; + RAISE NOTICE 'PASS: pipeline_stats - chunks: %, completion rate: %%', v_total_chunks, v_completion_rate; +END $$; + +-- ============================================ +-- Final Test Report +-- ============================================ +DO $$ +DECLARE + v_total_sources INT; + v_total_indexes INT; + v_total_work_items INT; + v_total_documents INT; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE '=== TEST SUMMARY ==='; + SELECT COUNT(*) INTO v_total_sources FROM dist_rag.sources; + SELECT COUNT(*) INTO v_total_indexes FROM dist_rag.vector_indexes; + SELECT COUNT(*) INTO v_total_work_items FROM dist_rag.work_queue; + SELECT COUNT(*) INTO v_total_documents FROM dist_rag.documents; + RAISE NOTICE 'Total sources created: %', v_total_sources; + RAISE NOTICE 'Total indexes created: %', v_total_indexes; + RAISE NOTICE 'Total work queue items: %', v_total_work_items; + RAISE NOTICE 'Total documents added: %', v_total_documents; + RAISE NOTICE ''; + RAISE NOTICE '✓ All tests completed successfully!'; + RAISE NOTICE ''; +END $$; + diff --git a/yb-extensions/pg_dist_rag/yb_schedule b/yb-extensions/pg_dist_rag/yb_schedule new file mode 100644 index 0000000000000..faf0c6c80ba0d --- /dev/null +++ b/yb-extensions/pg_dist_rag/yb_schedule @@ -0,0 +1 @@ +test: pg_dist_rag_test diff --git a/yb-extensions/yb_pg_metrics/Makefile b/yb-extensions/yb_pg_metrics/Makefile new file mode 100644 index 0000000000000..7f8b6f48ab873 --- /dev/null +++ b/yb-extensions/yb_pg_metrics/Makefile @@ -0,0 +1,20 @@ +# contrib/yb_pg_metrics/Makefile + +MODULE_big = yb_pg_metrics +OBJS = yb_pg_metrics.o $(WIN32RES) + +EXTENSION = yb_pg_metrics +DATA = +PGFILEDESC = "yb_pg_metrics - YugaByte metrics" +SHLIB_LINK += -L$(YB_BUILD_ROOT)/lib -lserver_process -lyb_pggate_util -lyb_pggate_webserver + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/yb_pg_metrics +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/yb-extensions/yb_pg_metrics/yb_pg_metrics.c b/yb-extensions/yb_pg_metrics/yb_pg_metrics.c new file mode 100644 index 0000000000000..2bc1f2c6e1bc0 --- /dev/null +++ b/yb-extensions/yb_pg_metrics/yb_pg_metrics.c @@ -0,0 +1,1488 @@ +/*------------------------------------------------------------------------- + * + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include +#include +#include +#include + +#include "access/htup_details.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_database.h" +#include "common/ip.h" +#include "datatype/timestamp.h" +#include "executor/instrument.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "parser/parsetree.h" +#include "pg_yb_utils.h" +#include "pgstat.h" +#include "postmaster/bgworker.h" +#include "postmaster/postmaster.h" +#include "storage/ipc.h" +#include "storage/latch.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "storage/shmem.h" +#include "tcop/utility.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/datetime.h" +#include "utils/relcache.h" +#include "utils/syscache.h" +#include "yb/yql/pggate/webserver/ybc_pg_webserver_wrapper.h" +#include "yb/yql/pggate/ybc_pggate.h" + +#define YSQL_METRIC_PREFIX "yb_ysqlserver_" +#define YSQL_LATENCY_METRIC_PREFIX "handler_latency_yb_ysqlserver_SQLProcessor_" + +#define NumBackendStatSlots (MaxBackends + NUM_AUXPROCTYPES) + +PG_MODULE_MAGIC; + +typedef enum YbStatementType +{ + Select, + Insert, + Delete, + Update, + Begin, + Commit, + Rollback, + Other, + Single_Shard_Transaction, + SingleShardTransaction, + Transaction, + AggregatePushdown, + CatCacheRefresh, + CatCacheDeltaRefresh, + CatCacheMisses, + CatCacheIdMisses_Start, + CatCacheIdMisses_0 = CatCacheIdMisses_Start, + CatCacheIdMisses_1, + CatCacheIdMisses_2, + CatCacheIdMisses_3, + CatCacheIdMisses_4, + CatCacheIdMisses_5, + CatCacheIdMisses_6, + CatCacheIdMisses_7, + CatCacheIdMisses_8, + CatCacheIdMisses_9, + CatCacheIdMisses_10, + CatCacheIdMisses_11, + CatCacheIdMisses_12, + CatCacheIdMisses_13, + CatCacheIdMisses_14, + CatCacheIdMisses_15, + CatCacheIdMisses_16, + CatCacheIdMisses_17, + CatCacheIdMisses_18, + CatCacheIdMisses_19, + CatCacheIdMisses_20, + CatCacheIdMisses_21, + CatCacheIdMisses_22, + CatCacheIdMisses_23, + CatCacheIdMisses_24, + CatCacheIdMisses_25, + CatCacheIdMisses_26, + CatCacheIdMisses_27, + CatCacheIdMisses_28, + CatCacheIdMisses_29, + CatCacheIdMisses_30, + CatCacheIdMisses_31, + CatCacheIdMisses_32, + CatCacheIdMisses_33, + CatCacheIdMisses_34, + CatCacheIdMisses_35, + CatCacheIdMisses_36, + CatCacheIdMisses_37, + CatCacheIdMisses_38, + CatCacheIdMisses_39, + CatCacheIdMisses_40, + CatCacheIdMisses_41, + CatCacheIdMisses_42, + CatCacheIdMisses_43, + CatCacheIdMisses_44, + CatCacheIdMisses_45, + CatCacheIdMisses_46, + CatCacheIdMisses_47, + CatCacheIdMisses_48, + CatCacheIdMisses_49, + CatCacheIdMisses_50, + CatCacheIdMisses_51, + CatCacheIdMisses_52, + CatCacheIdMisses_53, + CatCacheIdMisses_54, + CatCacheIdMisses_55, + CatCacheIdMisses_56, + CatCacheIdMisses_57, + CatCacheIdMisses_58, + CatCacheIdMisses_59, + CatCacheIdMisses_60, + CatCacheIdMisses_61, + CatCacheIdMisses_62, + CatCacheIdMisses_63, + CatCacheIdMisses_64, + CatCacheIdMisses_65, + CatCacheIdMisses_66, + CatCacheIdMisses_67, + CatCacheIdMisses_68, + CatCacheIdMisses_69, + CatCacheIdMisses_70, + CatCacheIdMisses_71, + CatCacheIdMisses_72, + CatCacheIdMisses_73, + CatCacheIdMisses_74, + CatCacheIdMisses_75, + CatCacheIdMisses_76, + CatCacheIdMisses_77, + CatCacheIdMisses_78, + CatCacheIdMisses_79, + CatCacheIdMisses_80, + CatCacheIdMisses_81, + CatCacheIdMisses_82, + CatCacheIdMisses_83, + CatCacheIdMisses_84, + CatCacheIdMisses_End = CatCacheIdMisses_84, + CatCacheTableMisses_Start, + CatCacheTableMisses_0 = CatCacheTableMisses_Start, + CatCacheTableMisses_1, + CatCacheTableMisses_2, + CatCacheTableMisses_3, + CatCacheTableMisses_4, + CatCacheTableMisses_5, + CatCacheTableMisses_6, + CatCacheTableMisses_7, + CatCacheTableMisses_8, + CatCacheTableMisses_9, + CatCacheTableMisses_10, + CatCacheTableMisses_11, + CatCacheTableMisses_12, + CatCacheTableMisses_13, + CatCacheTableMisses_14, + CatCacheTableMisses_15, + CatCacheTableMisses_16, + CatCacheTableMisses_17, + CatCacheTableMisses_18, + CatCacheTableMisses_19, + CatCacheTableMisses_20, + CatCacheTableMisses_21, + CatCacheTableMisses_22, + CatCacheTableMisses_23, + CatCacheTableMisses_24, + CatCacheTableMisses_25, + CatCacheTableMisses_26, + CatCacheTableMisses_27, + CatCacheTableMisses_28, + CatCacheTableMisses_29, + CatCacheTableMisses_30, + CatCacheTableMisses_31, + CatCacheTableMisses_32, + CatCacheTableMisses_33, + CatCacheTableMisses_34, + CatCacheTableMisses_35, + CatCacheTableMisses_36, + CatCacheTableMisses_37, + CatCacheTableMisses_38, + CatCacheTableMisses_39, + CatCacheTableMisses_40, + CatCacheTableMisses_41, + CatCacheTableMisses_42, + CatCacheTableMisses_43, + CatCacheTableMisses_44, + CatCacheTableMisses_45, + CatCacheTableMisses_46, + CatCacheTableMisses_47, + CatCacheTableMisses_48, + CatCacheTableMisses_49, + CatCacheTableMisses_50, + CatCacheTableMisses_End = CatCacheTableMisses_50, + HintCacheRefresh, + HintCacheHits, + HintCacheMisses, + AuthorizedConnection, + RelCachePreload, + CatCacheListMisses_Start, + CatCacheListMisses_0 = CatCacheListMisses_Start, + CatCacheListMisses_1, CatCacheListMisses_2, CatCacheListMisses_3, + CatCacheListMisses_4, CatCacheListMisses_5, CatCacheListMisses_6, + CatCacheListMisses_7, CatCacheListMisses_8, CatCacheListMisses_9, + CatCacheListMisses_10, CatCacheListMisses_11, CatCacheListMisses_12, + CatCacheListMisses_13, CatCacheListMisses_14, CatCacheListMisses_15, + CatCacheListMisses_16, CatCacheListMisses_17, CatCacheListMisses_18, + CatCacheListMisses_19, CatCacheListMisses_20, CatCacheListMisses_21, + CatCacheListMisses_22, CatCacheListMisses_23, CatCacheListMisses_24, + CatCacheListMisses_25, CatCacheListMisses_26, CatCacheListMisses_27, + CatCacheListMisses_28, CatCacheListMisses_29, CatCacheListMisses_30, + CatCacheListMisses_31, CatCacheListMisses_32, CatCacheListMisses_33, + CatCacheListMisses_34, CatCacheListMisses_35, CatCacheListMisses_36, + CatCacheListMisses_37, CatCacheListMisses_38, CatCacheListMisses_39, + CatCacheListMisses_40, CatCacheListMisses_41, CatCacheListMisses_42, + CatCacheListMisses_43, CatCacheListMisses_44, CatCacheListMisses_45, + CatCacheListMisses_46, CatCacheListMisses_47, CatCacheListMisses_48, + CatCacheListMisses_49, CatCacheListMisses_50, + CatCacheListMisses_End = CatCacheListMisses_50, + CatCacheNegMisses_Start, + CatCacheNegMisses_0 = CatCacheNegMisses_Start, + CatCacheNegMisses_1, CatCacheNegMisses_2, CatCacheNegMisses_3, + CatCacheNegMisses_4, CatCacheNegMisses_5, CatCacheNegMisses_6, + CatCacheNegMisses_7, CatCacheNegMisses_8, CatCacheNegMisses_9, + CatCacheNegMisses_10, CatCacheNegMisses_11, CatCacheNegMisses_12, + CatCacheNegMisses_13, CatCacheNegMisses_14, CatCacheNegMisses_15, + CatCacheNegMisses_16, CatCacheNegMisses_17, CatCacheNegMisses_18, + CatCacheNegMisses_19, CatCacheNegMisses_20, CatCacheNegMisses_21, + CatCacheNegMisses_22, CatCacheNegMisses_23, CatCacheNegMisses_24, + CatCacheNegMisses_25, CatCacheNegMisses_26, CatCacheNegMisses_27, + CatCacheNegMisses_28, CatCacheNegMisses_29, CatCacheNegMisses_30, + CatCacheNegMisses_31, CatCacheNegMisses_32, CatCacheNegMisses_33, + CatCacheNegMisses_34, CatCacheNegMisses_35, CatCacheNegMisses_36, + CatCacheNegMisses_37, CatCacheNegMisses_38, CatCacheNegMisses_39, + CatCacheNegMisses_40, CatCacheNegMisses_41, CatCacheNegMisses_42, + CatCacheNegMisses_43, CatCacheNegMisses_44, CatCacheNegMisses_45, + CatCacheNegMisses_46, CatCacheNegMisses_47, CatCacheNegMisses_48, + CatCacheNegMisses_49, CatCacheNegMisses_50, + CatCacheNegMisses_End = CatCacheNegMisses_50, + kMaxStatementType +} YbStatementType; +int num_entries = kMaxStatementType; +YbcPgmEntry *ybpgm_table = NULL; + +/* Statement nesting level is used when setting up dml statements. + * - Some state variables are set up for the top-level query but not the nested query. + * - Time recorder is initialized and used for top-level query only. + */ +static int statement_nesting_level = 0; + +/* Block nesting level is used when setting up execution block such as "DO $$ ... END $$;". + * - Some state variables are set up for the top level block but not the nested blocks. + */ +static int block_nesting_level = 0; + +/* + * Flag to determine whether a transaction block has been entered. + */ +static bool is_inside_transaction_block = false; + +/* + * Flag to determine whether a DML or Other statement type has been executed. + * Multiple statements will count as a single transaction within a transaction block. + * DDL statements which are autonomous will be counted as its own transaction + * even within a transaction block. + */ +static bool is_statement_executed = false; + +char *metric_node_name = NULL; +struct WebserverWrapper *webserver = NULL; +int port = 0; +static bool log_accesses = false; +static bool log_tcmalloc_stats = false; +static int webserver_profiler_sample_period_bytes = 0; +static int num_backends = 0; +static YbcRpczEntry *rpcz = NULL; +static MemoryContext ybrpczMemoryContext = NULL; +PgBackendStatus *backendStatusArray = NULL; +extern int MaxConnections; + +static long last_catcache_refresh_val = 0; +static long last_catcache_delta_refresh_val = 0; +static long last_cache_misses_val = 0; +static long last_cache_id_misses_val[SysCacheSize] = {0}; +static long last_cache_table_misses_val[YbNumCatalogCacheTables] = {0}; +static long last_cache_list_misses_val[YbNumCatalogCacheTables] = {0}; +static long last_cache_neg_misses_val[YbNumCatalogCacheTables] = {0}; + +static long last_hint_cache_refreshes_val = 0; +static long last_hint_cache_hits_val = 0; +static long last_hint_cache_misses_val = 0; + +static long last_authorized_connection_val = 0; +static long last_relcache_preload_val = 0; + +static volatile sig_atomic_t got_SIGHUP = false; +static volatile sig_atomic_t got_SIGTERM = false; + +void _PG_init(void); + +/* + * Variables used for storing the previous values of used hooks. + */ +static shmem_request_hook_type prev_shmem_request_hook = NULL; +static shmem_startup_hook_type prev_shmem_startup_hook = NULL; +static ExecutorStart_hook_type prev_ExecutorStart = NULL; +static ExecutorEnd_hook_type prev_ExecutorEnd = NULL; +static ExecutorRun_hook_type prev_ExecutorRun = NULL; +static ExecutorFinish_hook_type prev_ExecutorFinish = NULL; +static ProcessUtility_hook_type prev_ProcessUtility = NULL; + +static void set_metric_names(void); +static void ybpgm_shmem_request(void); +static void ybpgm_startup_hook(void); +static Size ybpgm_memsize(void); +static bool isTopLevelStatement(void); +static void ybpgm_ExecutorStart(QueryDesc *queryDesc, int eflags); +static void ybpgm_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, + uint64 count, bool execute_once); +static void ybpgm_ExecutorFinish(QueryDesc *queryDesc); +static void ybpgm_ExecutorEnd(QueryDesc *queryDesc); +static void ybpgm_ProcessUtility(PlannedStmt *pstmt, const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, QueryCompletion *qc); +static void ybpgm_Store(YbStatementType type, uint64_t time, uint64_t rows); +static void ybpgm_StoreCount(YbStatementType type, uint64_t time, uint64_t count); + +static void ws_sighup_handler(SIGNAL_ARGS); +static void ws_sigterm_handler(SIGNAL_ARGS); + +/* + * Function used for checking if the current statement is a top level statement. + */ +bool +isTopLevelStatement(void) +{ + return statement_nesting_level == 0; +} + +static void +IncStatementNestingLevel(void) +{ + statement_nesting_level++; +} + +static void +DecStatementNestingLevel(void) +{ + statement_nesting_level--; +} + +bool +isTopLevelBlock(void) +{ + return block_nesting_level == 0; +} + +static void +IncBlockNestingLevel(void) +{ + block_nesting_level++; +} + +static void +DecBlockNestingLevel(void) +{ + block_nesting_level--; +} + +void +set_metric_names(void) +{ + for (int i = 0; i < kMaxStatementType; i++) + { + ybpgm_table[i].table_name[0] = '\0'; + ybpgm_table[i].count_help[0] = '\0'; + ybpgm_table[i].sum_help[0] = '\0'; + } + + strcpy(ybpgm_table[Select].name, YSQL_LATENCY_METRIC_PREFIX "SelectStmt"); + strcpy(ybpgm_table[Insert].name, YSQL_LATENCY_METRIC_PREFIX "InsertStmt"); + strcpy(ybpgm_table[Delete].name, YSQL_LATENCY_METRIC_PREFIX "DeleteStmt"); + strcpy(ybpgm_table[Update].name, YSQL_LATENCY_METRIC_PREFIX "UpdateStmt"); + strcpy(ybpgm_table[Begin].name, YSQL_LATENCY_METRIC_PREFIX "BeginStmt"); + strcpy(ybpgm_table[Commit].name, YSQL_LATENCY_METRIC_PREFIX "CommitStmt"); + strcpy(ybpgm_table[Rollback].name, YSQL_LATENCY_METRIC_PREFIX "RollbackStmt"); + strcpy(ybpgm_table[Other].name, YSQL_LATENCY_METRIC_PREFIX "OtherStmts"); + /* Deprecated. Names with "_"s may cause confusion to metric consumers. */ + strcpy(ybpgm_table[Single_Shard_Transaction].name, + YSQL_LATENCY_METRIC_PREFIX "Single_Shard_Transactions"); + strcpy(ybpgm_table[SingleShardTransaction].name, + YSQL_LATENCY_METRIC_PREFIX "SingleShardTransactions"); + strcpy(ybpgm_table[Transaction].name, YSQL_LATENCY_METRIC_PREFIX "Transactions"); + strcpy(ybpgm_table[AggregatePushdown].name, + YSQL_LATENCY_METRIC_PREFIX "AggregatePushdowns"); + strcpy(ybpgm_table[CatCacheRefresh].name, + YSQL_METRIC_PREFIX "CatCacheRefresh"); + strcpy(ybpgm_table[CatCacheDeltaRefresh].name, + YSQL_METRIC_PREFIX "CatCacheDeltaRefresh"); + strcpy(ybpgm_table[CatCacheMisses].name, YSQL_METRIC_PREFIX "CatalogCacheMisses"); + for (int i = CatCacheIdMisses_Start; i <= CatCacheIdMisses_End; ++i) + { + int cache_id = i - CatCacheIdMisses_Start; + + strcpy(ybpgm_table[i].name, YSQL_METRIC_PREFIX "CatalogCacheMisses"); + const char *index_name = YbGetCatalogCacheIndexName(cache_id); + + Assert(strlen(index_name) < YB_PG_METRIC_NAME_LEN); + snprintf(ybpgm_table[i].table_name, YB_PG_METRIC_NAME_LEN, "%s", + index_name); + } + + for (int i = CatCacheTableMisses_Start; i <= CatCacheTableMisses_End; ++i) + { + int table_id = i - CatCacheTableMisses_Start; + + strcpy(ybpgm_table[i].name, YSQL_METRIC_PREFIX "CatalogCacheTableMisses"); + const char *table_name = YbGetCatalogCacheTableNameFromTableId(table_id); + + Assert(strlen(table_name) < YB_PG_METRIC_NAME_LEN); + snprintf(ybpgm_table[i].table_name, YB_PG_METRIC_NAME_LEN, "%s", + table_name); + } + + for (int i = CatCacheListMisses_Start; i <= CatCacheListMisses_End; ++i) + { + int table_id = i - CatCacheListMisses_Start; + + strcpy(ybpgm_table[i].name, YSQL_METRIC_PREFIX "CatalogCacheListMisses"); + const char *table_name = YbGetCatalogCacheTableNameFromTableId(table_id); + + Assert(strlen(table_name) < YB_PG_METRIC_NAME_LEN); + snprintf(ybpgm_table[i].table_name, YB_PG_METRIC_NAME_LEN, "%s", + table_name); + } + + for (int i = CatCacheNegMisses_Start; i <= CatCacheNegMisses_End; ++i) + { + int table_id = i - CatCacheNegMisses_Start; + + strcpy(ybpgm_table[i].name, YSQL_METRIC_PREFIX "CatalogCacheNegMisses"); + const char *table_name = YbGetCatalogCacheTableNameFromTableId(table_id); + + Assert(strlen(table_name) < YB_PG_METRIC_NAME_LEN); + snprintf(ybpgm_table[i].table_name, YB_PG_METRIC_NAME_LEN, "%s", + table_name); + } + + strcpy(ybpgm_table[HintCacheRefresh].name, + YSQL_METRIC_PREFIX "HintCacheRefresh"); + strcpy(ybpgm_table[HintCacheHits].name, + YSQL_METRIC_PREFIX "HintCacheHits"); + strcpy(ybpgm_table[HintCacheMisses].name, + YSQL_METRIC_PREFIX "HintCacheMisses"); + + strcpy(ybpgm_table[AuthorizedConnection].name, + YSQL_METRIC_PREFIX "AuthorizedConnection"); + strcpy(ybpgm_table[RelCachePreload].name, + YSQL_METRIC_PREFIX "RelCachePreload"); + + strcpy(ybpgm_table[Select].count_help, + "Number of SELECT statements that have been executed"); + strcpy(ybpgm_table[Select].sum_help, + "Total time (microseconds) spent executing SELECT statements"); + + strcpy(ybpgm_table[Insert].count_help, + "Number of INSERT statements that have been executed"); + strcpy(ybpgm_table[Insert].sum_help, + "Total time (microseconds) spent executing INSERT statements"); + + strcpy(ybpgm_table[Delete].count_help, + "Number of DELETE statements that have been executed"); + strcpy(ybpgm_table[Delete].sum_help, + "Total time (microseconds) spent executing DELETE statements"); + + strcpy(ybpgm_table[Update].count_help, + "Number of UPDATE statements that have been executed"); + strcpy(ybpgm_table[Update].sum_help, + "Total time (microseconds) spent executing UPDATE statements"); + + strcpy(ybpgm_table[Begin].count_help, + "Number of BEGIN statements that have been executed"); + strcpy(ybpgm_table[Begin].sum_help, + "Total time (microseconds) spent executing BEGIN statements"); + + strcpy(ybpgm_table[Commit].count_help, + "Number of COMMIT statements that have been executed"); + strcpy(ybpgm_table[Commit].sum_help, + "Total time (microseconds) spent executing COMMIT statements"); + + strcpy(ybpgm_table[Rollback].count_help, + "Number of ROLLBACK statements that have been executed"); + strcpy(ybpgm_table[Rollback].sum_help, + "Total time (microseconds) spent executing ROLLBACK statements"); + + strcpy(ybpgm_table[Other].count_help, + "Number of other statements that have been executed"); + strcpy(ybpgm_table[Other].sum_help, + "Total time (microseconds) spent executing other statements"); + + strcpy(ybpgm_table[Single_Shard_Transaction].count_help, + "Number of single shard transactions that have been executed (deprecated)"); + strcpy(ybpgm_table[Single_Shard_Transaction].sum_help, + "Total time (microseconds) spent executing single shard transactions (deprecated)"); + + strcpy(ybpgm_table[SingleShardTransaction].count_help, + "Number of single shard transactions that have been executed"); + strcpy(ybpgm_table[SingleShardTransaction].sum_help, + "Total time (microseconds) spent executing single shard transactions"); + + strcpy(ybpgm_table[Transaction].count_help, + "Number of transactions that have been executed"); + strcpy(ybpgm_table[Transaction].sum_help, + "Total time (microseconds) spent executing transactions"); + + strcpy(ybpgm_table[AggregatePushdown].count_help, + "Number of aggregate pushdowns"); + strcpy(ybpgm_table[AggregatePushdown].sum_help, + "Total time (microseconds) spent executing aggregate pushdowns"); + strcpy(ybpgm_table[CatCacheRefresh].count_help, + "Number of full catalog cache refreshes"); + strcpy(ybpgm_table[CatCacheRefresh].sum_help, + "Not applicable"); + strcpy(ybpgm_table[CatCacheDeltaRefresh].count_help, + "Number of incremental catalog cache refreshes"); + strcpy(ybpgm_table[CatCacheDeltaRefresh].sum_help, + "Not applicable"); + + + strcpy(ybpgm_table[CatCacheMisses].count_help, + "Total number of catalog cache misses"); + strcpy(ybpgm_table[CatCacheMisses].sum_help, "Not applicable"); + + for (int i = CatCacheIdMisses_Start; i <= CatCacheIdMisses_End; ++i) + { + snprintf(ybpgm_table[i].count_help, YB_PG_METRIC_NAME_LEN, + "Number of catalog cache misses for index %s", + ybpgm_table[i].table_name); + strcpy(ybpgm_table[i].sum_help, "Not applicable"); + } + + for (int i = CatCacheTableMisses_Start; i <= CatCacheTableMisses_End; ++i) + { + snprintf(ybpgm_table[i].count_help, YB_PG_METRIC_NAME_LEN, + "Number of catalog cache misses for table %s", + ybpgm_table[i].table_name); + strcpy(ybpgm_table[i].sum_help, "Not applicable"); + } + + for (int i = CatCacheListMisses_Start; i <= CatCacheListMisses_End; ++i) + { + snprintf(ybpgm_table[i].count_help, YB_PG_METRIC_NAME_LEN, + "Number of catalog cache list misses for table %s", + ybpgm_table[i].table_name); + strcpy(ybpgm_table[i].sum_help, "Not applicable"); + } + + for (int i = CatCacheNegMisses_Start; i <= CatCacheNegMisses_End; ++i) + { + snprintf(ybpgm_table[i].count_help, YB_PG_METRIC_NAME_LEN, + "Number of catalog cache negative misses for table %s", + ybpgm_table[i].table_name); + strcpy(ybpgm_table[i].sum_help, "Not applicable"); + } + + strcpy(ybpgm_table[HintCacheRefresh].count_help, + "Number of hint cache refreshes"); + strcpy(ybpgm_table[HintCacheRefresh].sum_help, "Not applicable"); + + strcpy(ybpgm_table[HintCacheHits].count_help, + "Number of hint cache hits"); + strcpy(ybpgm_table[HintCacheHits].sum_help, "Not applicable"); + + strcpy(ybpgm_table[HintCacheMisses].count_help, + "Number of hint cache misses"); + strcpy(ybpgm_table[HintCacheMisses].sum_help, "Not applicable"); + + strcpy(ybpgm_table[AuthorizedConnection].count_help, + "Number of authorized connections"); + strcpy(ybpgm_table[AuthorizedConnection].sum_help, "Not applicable"); + + strcpy(ybpgm_table[RelCachePreload].count_help, + "Number of relcache preloads"); + strcpy(ybpgm_table[RelCachePreload].sum_help, "Not applicable"); +} + +/* + * Function to calculate milliseconds elapsed from start_time to stop_time. + */ +int64 +getElapsedMs(TimestampTz start_time, TimestampTz stop_time) +{ + long secs; + int microsecs; + + TimestampDifference(start_time, stop_time, &secs, µsecs); + + long millisecs = (secs * 1000) + (microsecs / 1000); + + return millisecs; +} + +void +pullRpczEntries(void) +{ + ybrpczMemoryContext = AllocSetContextCreate(TopMemoryContext, + "YB RPCz memory context", + ALLOCSET_SMALL_SIZES); + + MemoryContext oldcontext = MemoryContextSwitchTo(ybrpczMemoryContext); + + rpcz = (YbcRpczEntry *) palloc(sizeof(YbcRpczEntry) * NumBackendStatSlots); + + num_backends = NumBackendStatSlots; + volatile PgBackendStatus *beentry = backendStatusArray; + + for (int i = 0; i < NumBackendStatSlots; i++) + { + /* + * To prevent locking overhead, the BackendStatusArray in postgres + * maintains a st_changecount field for each entry. This field is + * incremented once before a backend starts modifying the entry, and + * once after it is done modifying the entry. So, we check if + * st_changecount changes while we're copying the entry or if its odd. + * The check for odd is needed for when a backend has begun changing + * the entry but hasn't finished. + */ + int attempt = 1; + + while (yb_pgstat_log_read_activity(beentry, ++attempt)) + { + int before_changecount; + int after_changecount; + + before_changecount = beentry->st_changecount; + + rpcz[i].proc_id = beentry->st_procpid; + + /* avoid filling any more fields if invalid */ + if (beentry->st_procpid <= 0) + break; + + rpcz[i].db_oid = beentry->st_databaseid; + rpcz[i].query_id = beentry->st_query_id; + rpcz[i].leader_pid = -1; + PGPROC *proc = NULL; + + if (beentry->st_backendType == B_BACKEND || + beentry->st_backendType == YB_AUTO_ANALYZE_BACKEND) + proc = BackendPidGetProc(rpcz[i].proc_id); + else if (beentry->st_backendType != YB_YSQL_CONN_MGR) + { + /* + * For an auxiliary process, retrieve process info from + * AuxiliaryProcs stored in shared-memory. + */ + proc = AuxiliaryPidGetProc(beentry->st_procpid); + } + + if (proc != NULL) + { + PGPROC *leader = proc->lockGroupLeader; + + if (leader != NULL) + { + rpcz[i].leader_pid = leader->pid; + } + } + + rpcz[i].query = (char *) palloc(pgstat_track_activity_query_size); + strcpy(rpcz[i].query, (char *) beentry->st_activity_raw); + + rpcz[i].application_name = (char *) palloc(NAMEDATALEN); + strcpy(rpcz[i].application_name, (char *) beentry->st_appname); + + rpcz[i].db_name = (char *) palloc(NAMEDATALEN); + strcpy(rpcz[i].db_name, beentry->st_databasename); + + rpcz[i].process_start_timestamp = beentry->st_proc_start_timestamp; + rpcz[i].transaction_start_timestamp = beentry->st_xact_start_timestamp; + rpcz[i].query_start_timestamp = beentry->st_activity_start_timestamp; + + rpcz[i].backend_type = (char *) palloc(40); + strcpy(rpcz[i].backend_type, GetBackendTypeDesc(beentry->st_backendType)); + + rpcz[i].backend_active = 0; + rpcz[i].backend_status = (char *) palloc(30); + switch (beentry->st_state) + { + case STATE_IDLE: + strcpy(rpcz[i].backend_status, "idle"); + break; + case STATE_RUNNING: + rpcz[i].backend_active = 1; + strcpy(rpcz[i].backend_status, "active"); + break; + case STATE_IDLEINTRANSACTION: + strcpy(rpcz[i].backend_status, "idle in transaction"); + break; + case STATE_FASTPATH: + rpcz[i].backend_active = 1; + strcpy(rpcz[i].backend_status, "fastpath function call"); + break; + case STATE_IDLEINTRANSACTION_ABORTED: + strcpy(rpcz[i].backend_status, "idle in transaction (aborted)"); + break; + case STATE_DISABLED: + strcpy(rpcz[i].backend_status, "disabled"); + break; + case STATE_UNDEFINED: + strcpy(rpcz[i].backend_status, ""); + break; + } + + char remote_host[NI_MAXHOST]; + char remote_port[NI_MAXSERV]; + int ret; + + remote_host[0] = '\0'; + remote_port[0] = '\0'; + ret = pg_getnameinfo_all((struct sockaddr_storage *) &beentry->st_clientaddr.addr, + beentry->st_clientaddr.salen, + remote_host, sizeof(remote_host), + remote_port, sizeof(remote_port), + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret == 0) + { + rpcz[i].host = (char *) palloc(NI_MAXHOST); + rpcz[i].port = (char *) palloc(NI_MAXSERV); + clean_ipv6_addr(beentry->st_clientaddr.addr.ss_family, remote_host); + strcpy(rpcz[i].host, remote_host); + strcpy(rpcz[i].port, remote_port); + } + else + { + rpcz[i].host = NULL; + rpcz[i].port = NULL; + } + after_changecount = beentry->st_changecount; + + if (before_changecount == after_changecount && + (before_changecount & 1) == 0) + break; + } + beentry++; + } + MemoryContextSwitchTo(oldcontext); +} + +void +freeRpczEntries(void) +{ + MemoryContextDelete(ybrpczMemoryContext); + ybrpczMemoryContext = NULL; +} + +/* SIGHUP: set flag to re-read config file at next convenient time */ +void +ws_sighup_handler(SIGNAL_ARGS) +{ + int save_errno = errno; + + got_SIGHUP = true; + SetLatch(MyLatch); + + errno = save_errno; +} + + +/* SIGTERM: time to die */ +static void +ws_sigterm_handler(SIGNAL_ARGS) +{ + int save_errno = errno; + + got_SIGTERM = true; + SetLatch(MyLatch); + + errno = save_errno; +} + +/* + * Function that is executed when the YSQL webserver process is started. + * We don't use the argument "unused", however, a postgres background worker's function + * is required to have an argument of type Datum. + */ +void +webserver_worker_main(Datum unused) +{ + YBCInitThreading(); + /* + * We call YBCInit here so that HandleYBStatus can correctly report potential error. + */ + HandleYBStatus(YBCInit(NULL /* argv[0] */ , + palloc, + NULL /* cstring_to_text_with_len_fn */ , + NULL /* YbSwitchPgGateMemoryContext */ , + NULL /* YbCreatePgGateMemoryContext */ , + NULL /* YbDeletePgGateMemoryContext */ )); + + backendStatusArray = getBackendStatusArray(); + + BackgroundWorkerUnblockSignals(); + + /* + * Assert that shared memory is allocated to backendStatusArray before this webserver + * is started. + */ + if (!backendStatusArray) + ereport(FATAL, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("shared memory not allocated to BackendStatusArray before starting YSQL webserver"))); + + webserver = CreateWebserver(ListenAddresses, port); + + RegisterMetrics(ybpgm_table, num_entries, metric_node_name); + + YbcPostgresCallbacks callbacks; + + callbacks.pullRpczEntries = pullRpczEntries; + callbacks.freeRpczEntries = freeRpczEntries; + callbacks.getTimestampTz = GetCurrentTimestamp; + callbacks.getTimestampTzDiffMs = getElapsedMs; + callbacks.getTimestampTzToStr = timestamptz_to_str; + + YbcConnectionMetrics conn_metrics; + + conn_metrics.max_conn = &MaxConnections; + conn_metrics.too_many_conn = yb_too_many_conn; + conn_metrics.new_conn = yb_new_conn; + + RegisterRpczEntries(&callbacks, &num_backends, &rpcz, &conn_metrics); + HandleYBStatus(StartWebserver(webserver)); + + pqsignal(SIGHUP, ws_sighup_handler); + pqsignal(SIGTERM, ws_sigterm_handler); + + SetWebserverConfig(webserver, log_accesses, log_tcmalloc_stats, + webserver_profiler_sample_period_bytes); + + int rc; + + while (!got_SIGTERM) + { + rc = WaitLatch(MyLatch, WL_LATCH_SET | WL_POSTMASTER_DEATH, -1, PG_WAIT_EXTENSION); + ResetLatch(MyLatch); + + if (rc & WL_POSTMASTER_DEATH) + break; + + if (got_SIGHUP) + { + got_SIGHUP = false; + ProcessConfigFile(PGC_SIGHUP); + SetWebserverConfig(webserver, log_accesses, log_tcmalloc_stats, + webserver_profiler_sample_period_bytes); + } + } + + if (webserver) + { + DestroyWebserver(webserver); + webserver = NULL; + } + + if (rpcz != NULL && ybrpczMemoryContext != NULL) + { + MemoryContext oldcontext = MemoryContextSwitchTo(ybrpczMemoryContext); + + pfree(rpcz); + MemoryContextSwitchTo(oldcontext); + } + + if (rc & WL_POSTMASTER_DEATH) + proc_exit(1); + + proc_exit(0); +} + +/* + * Module load callback + */ +void +_PG_init(void) +{ + if (!process_shared_preload_libraries_in_progress) + return; + + /* + * Parameters that we expect to receive from the tserver process when it starts up postmaster. + * We set the flags GUC_NO_SHOW_ALL, GUC_NO_RESET_ALL, GUC_NOT_IN_SAMPLE, GUC_DISALLOW_IN_FILE + * so that these parameters aren’t visible, resettable, or configurable by our end user. + */ + DefineCustomStringVariable("yb_pg_metrics.node_name", + "Node name for YB metrics", + NULL, + &metric_node_name, + "", + PGC_POSTMASTER, + (GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE), + NULL, NULL, NULL); + + DefineCustomIntVariable("yb_pg_metrics.port", + "Port for YSQL webserver", + NULL, + &port, + 0, 0, INT_MAX, + PGC_POSTMASTER, + (GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE), + NULL, NULL, NULL); + + DefineCustomBoolVariable("yb_pg_metrics.log_accesses", + "Log each request received by the YSQL webserver", + NULL, + &log_accesses, + false, + PGC_SUSET, + 0, + NULL, NULL, NULL); + + DefineCustomBoolVariable("yb_pg_metrics.log_tcmalloc_stats", + "Log each request received by the YSQL webserver", + NULL, + &log_tcmalloc_stats, + false, + PGC_SUSET, + 0, + NULL, NULL, NULL); + + DefineCustomIntVariable("yb_pg_metrics.webserver_profiler_sample_period_bytes", + "The interval at which Google TCMalloc should " + "sample allocations in the YSQL webserver. If this" + " is 0, sampling is disabled. ", + NULL, + &webserver_profiler_sample_period_bytes, + 1024 * 1024, 0, INT_MAX, + PGC_SUSET, + 0, + NULL, NULL, NULL); + + BackgroundWorker worker; + + if (!IsBinaryUpgrade) + { + /* Registering the YSQL webserver as a background worker */ + MemSet(&worker, 0, sizeof(BackgroundWorker)); + strcpy(worker.bgw_name, "YSQL webserver"); + worker.bgw_flags = BGWORKER_SHMEM_ACCESS; + worker.bgw_start_time = BgWorkerStart_PostmasterStart; + /* Value of 1 allows the background worker for webserver to restart */ + worker.bgw_restart_time = 1; + worker.bgw_main_arg = (Datum) 0; + strcpy(worker.bgw_library_name, "yb_pg_metrics"); + strcpy(worker.bgw_function_name, "webserver_worker_main"); + worker.bgw_notify_pid = 0; + if (getenv("FLAGS_yb_webserver_oom_score_adj") != NULL) + strncpy(worker.bgw_oom_score_adj, + getenv("FLAGS_yb_webserver_oom_score_adj"), + BGW_MAXLEN); + + RegisterBackgroundWorker(&worker); + } + + /* + * Set the value of the hooks. + */ + + prev_shmem_request_hook = shmem_request_hook; + shmem_request_hook = ybpgm_shmem_request; + + prev_shmem_startup_hook = shmem_startup_hook; + shmem_startup_hook = ybpgm_startup_hook; + + prev_ExecutorStart = ExecutorStart_hook; + ExecutorStart_hook = ybpgm_ExecutorStart; + + prev_ExecutorRun = ExecutorRun_hook; + ExecutorRun_hook = ybpgm_ExecutorRun; + + prev_ExecutorFinish = ExecutorFinish_hook; + ExecutorFinish_hook = ybpgm_ExecutorFinish; + + prev_ExecutorEnd = ExecutorEnd_hook; + ExecutorEnd_hook = ybpgm_ExecutorEnd; + + prev_ProcessUtility = ProcessUtility_hook; + ProcessUtility_hook = ybpgm_ProcessUtility; + static_assert(SysCacheSize == CatCacheIdMisses_End - CatCacheIdMisses_Start + 1, + "Wrong catalog cache number"); +} + +/* + * shmem_request hook: request additional shared resources. We'll allocate or + * attach to the shared resources in ybpgm_startup_hook(). + */ +static void +ybpgm_shmem_request(void) +{ + if (prev_shmem_request_hook) + prev_shmem_request_hook(); + + RequestAddinShmemSpace(ybpgm_memsize()); + RequestNamedLWLockTranche("yb_pg_metrics", 1); +} + +static void +ybpgm_InitPostgres() +{ + /* Authorized connections metric */ + long current_authorized_connections = YbGetAuthorizedConnections(); + long total_delta = current_authorized_connections - last_authorized_connection_val; + + last_authorized_connection_val = current_authorized_connections; + ybpgm_StoreCount(AuthorizedConnection, 0, total_delta); + + /* Relcache preloads metric */ + long current_relcache_preloads = YbGetRelCachePreloads(); + + total_delta = current_relcache_preloads - last_relcache_preload_val; + last_relcache_preload_val = current_relcache_preloads; + ybpgm_StoreCount(RelCachePreload, 0, total_delta); +} + +/* + * Allocate or attach to shared memory. + */ +static void +ybpgm_startup_hook(void) +{ + if (prev_shmem_startup_hook) + prev_shmem_startup_hook(); + + bool found; + + ybpgm_table = ShmemInitStruct("yb_pg_metrics", + num_entries * sizeof(struct YbcPgmEntry), + &found); + set_metric_names(); + YBCSetUpdateInitPostgresMetricsFn(&ybpgm_InitPostgres); +} + +static void +ybpgm_ExecutorStart(QueryDesc *queryDesc, int eflags) +{ + /* + * Each PORTAL execution will run the following steps. + * 1- ExecutorStart() + * 2- Execute statements in the portal. Some statement execution (CURSOR + * execution) can open a nested PORTAL. Our metric routines will ignore + * the nested PORTAL for now. + * 3- ExecutorEnd() + */ + if (prev_ExecutorStart) + prev_ExecutorStart(queryDesc, eflags); + else + standard_ExecutorStart(queryDesc, eflags); + + /* + * PORTAL run can be nested inside another PORTAL, and we only run metric + * routines for the top level portal statement. The current design of + * using global variable "statement_nesting_level" is very flawed as it + * cannot find the starting and ending point of a top statement execution. + * For now, as a workaround, "queryDesc" attribute is used as an indicator + * for logging metric. Whenever "time value" is not null, it is logged at + * the end of a portal run. + * - When starting, we allocate "queryDesc->totaltime". + * - When ending, we check for "queryDesc->totaltime". If not null, its + * metric is log. + */ + if (isTopLevelStatement() && !queryDesc->totaltime) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt); + queryDesc->totaltime = InstrAlloc(1, INSTRUMENT_TIMER, false); + MemoryContextSwitchTo(oldcxt); + } +} + +static void +ybpgm_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count, + bool execute_once) +{ + IncStatementNestingLevel(); + PG_TRY(); + { + if (prev_ExecutorRun) + prev_ExecutorRun(queryDesc, direction, count, execute_once); + else + standard_ExecutorRun(queryDesc, direction, count, execute_once); + DecStatementNestingLevel(); + } + PG_CATCH(); + { + DecStatementNestingLevel(); + PG_RE_THROW(); + } + PG_END_TRY(); +} + +static void +ybpgm_ExecutorFinish(QueryDesc *queryDesc) +{ + IncStatementNestingLevel(); + PG_TRY(); + { + if (prev_ExecutorFinish) + prev_ExecutorFinish(queryDesc); + else + standard_ExecutorFinish(queryDesc); + DecStatementNestingLevel(); + } + PG_CATCH(); + { + DecStatementNestingLevel(); + PG_RE_THROW(); + } + PG_END_TRY(); +} + +static void +ybpgm_ExecutorEnd(QueryDesc *queryDesc) +{ + YbStatementType type; + + switch (queryDesc->operation) + { + case CMD_SELECT: + type = Select; + break; + case CMD_INSERT: + type = Insert; + break; + case CMD_DELETE: + type = Delete; + break; + case CMD_UPDATE: + type = Update; + break; + default: + type = Other; + break; + } + + if (!yb_is_calling_internal_sql_for_ddl) + is_statement_executed = true; + + /* + * Collecting metric. + * - Only processing metric for top level statement in top level portal. + * For example, CURSOR execution can have many nested portal and nested + * statement. The metric for all of the nested items are not processed. + * - However, it's difficult to know the starting and ending point of a + * statement, we check for not null "queryDesc->totaltime". + * - The design for this metric module for using global state variables is + * very flawed, so we use this not-null check for now. + */ + if (isTopLevelStatement() && queryDesc->totaltime) + { + InstrEndLoop(queryDesc->totaltime); + const uint64_t time = (uint64_t) (queryDesc->totaltime->total * 1000000.0); + const uint64 rows_count = queryDesc->estate->es_processed; + + ybpgm_Store(type, time, rows_count); + + if (queryDesc->estate->yb_es_is_single_row_modify_txn) + { + ybpgm_Store(Single_Shard_Transaction, time, rows_count); + ybpgm_Store(SingleShardTransaction, time, rows_count); + } + + if (!is_inside_transaction_block) + ybpgm_Store(Transaction, time, rows_count); + + if (IsA(queryDesc->planstate, AggState) && + castNode(AggState, queryDesc->planstate)->yb_pushdown_supported) + ybpgm_Store(AggregatePushdown, time, rows_count); + + long current_count = YbGetCatCacheRefreshes(); + long total_delta = current_count - last_catcache_refresh_val; + + last_catcache_refresh_val = current_count; + /* Set the time parameter to 0 as we don't have metrics for that. */ + ybpgm_StoreCount(CatCacheRefresh, 0, total_delta); + + current_count = YbGetCatCacheDeltaRefreshes(); + total_delta = current_count - last_catcache_delta_refresh_val; + last_catcache_delta_refresh_val = current_count; + /* Set the time parameter to 0 as we don't have metrics for that. */ + ybpgm_StoreCount(CatCacheDeltaRefresh, 0, total_delta); + + long current_cache_misses = YbGetCatCacheMisses(); + long *current_cache_id_misses = YbGetCatCacheIdMisses(); + long *current_cache_table_misses = YbGetCatCacheTableMisses(); + long *current_cache_list_misses = YbGetCatCacheListMisses(); + long *current_cache_neg_misses = YbGetCatCacheNegMisses(); + + total_delta = current_cache_misses - last_cache_misses_val; + + last_cache_misses_val = current_cache_misses; + + /* + * Currently we set the time parameter to 0 as we don't have metrics + * for that available TODO: Get timing metrics for catalog cache + * misses + */ + ybpgm_StoreCount(CatCacheMisses, 0, total_delta); + if (total_delta > 0) + for (int i = CatCacheIdMisses_Start; i <= CatCacheIdMisses_End; ++i) + { + int j = i - CatCacheIdMisses_Start; + + ybpgm_StoreCount(i, 0, + (current_cache_id_misses[j] - + last_cache_id_misses_val[j])); + last_cache_id_misses_val[j] = current_cache_id_misses[j]; + } + for (int i = CatCacheTableMisses_Start; + i <= CatCacheTableMisses_End; + ++i) + { + int j = i - CatCacheTableMisses_Start; + + ybpgm_StoreCount(i, 0, + (current_cache_table_misses[j] - + last_cache_table_misses_val[j])); + last_cache_table_misses_val[j] = current_cache_table_misses[j]; + } + for (int i = CatCacheListMisses_Start; + i <= CatCacheListMisses_End; + ++i) + { + int j = i - CatCacheListMisses_Start; + + ybpgm_StoreCount(i, 0, + (current_cache_list_misses[j] - + last_cache_list_misses_val[j])); + last_cache_list_misses_val[j] = current_cache_list_misses[j]; + } + for (int i = CatCacheNegMisses_Start; + i <= CatCacheNegMisses_End; + ++i) + { + int j = i - CatCacheNegMisses_Start; + + ybpgm_StoreCount(i, 0, + (current_cache_neg_misses[j] - + last_cache_neg_misses_val[j])); + last_cache_neg_misses_val[j] = current_cache_neg_misses[j]; + } + + /* Hint cache metrics */ + long current_hint_cache_refreshes = YbGetHintCacheRefreshes(); + + total_delta = current_hint_cache_refreshes - last_hint_cache_refreshes_val; + last_hint_cache_refreshes_val = current_hint_cache_refreshes; + ybpgm_StoreCount(HintCacheRefresh, 0, total_delta); + + long current_hint_cache_hits = YbGetHintCacheHits(); + + total_delta = current_hint_cache_hits - last_hint_cache_hits_val; + last_hint_cache_hits_val = current_hint_cache_hits; + ybpgm_StoreCount(HintCacheHits, 0, total_delta); + + long current_hint_cache_misses = YbGetHintCacheMisses(); + + total_delta = current_hint_cache_misses - last_hint_cache_misses_val; + last_hint_cache_misses_val = current_hint_cache_misses; + ybpgm_StoreCount(HintCacheMisses, 0, total_delta); + } + + IncStatementNestingLevel(); + PG_TRY(); + { + if (prev_ExecutorEnd) + prev_ExecutorEnd(queryDesc); + else + standard_ExecutorEnd(queryDesc); + DecStatementNestingLevel(); + } + PG_CATCH(); + { + DecStatementNestingLevel(); + PG_RE_THROW(); + } + PG_END_TRY(); +} + +/* + * Estimate shared memory space needed. + */ +static Size +ybpgm_memsize(void) +{ + Size size; + + size = MAXALIGN(num_entries * sizeof(struct YbcPgmEntry)); + + return size; +} + +/* + * Get the statement type for a transactional statement. + */ +static YbStatementType +ybpgm_getStatementType(TransactionStmt *stmt) +{ + YbStatementType type = Other; + + switch (stmt->kind) + { + case TRANS_STMT_BEGIN: + case TRANS_STMT_START: + type = Begin; + break; + case TRANS_STMT_COMMIT: + case TRANS_STMT_COMMIT_PREPARED: + type = Commit; + break; + case TRANS_STMT_ROLLBACK: + case TRANS_STMT_ROLLBACK_TO: + case TRANS_STMT_ROLLBACK_PREPARED: + type = Rollback; + break; + case TRANS_STMT_SAVEPOINT: + case TRANS_STMT_RELEASE: + case TRANS_STMT_PREPARE: + type = Other; + break; + default: + elog(ERROR, "unrecognized statement kind: %d", stmt->kind); + } + return type; +} + +/* + * Hook used for tracking "Other" statements. + */ +static void +ybpgm_ProcessUtility(PlannedStmt *pstmt, const char *queryString, + bool readOnlyTree, ProcessUtilityContext context, + ParamListInfo params, QueryEnvironment *queryEnv, + DestReceiver *dest, QueryCompletion *qc) +{ + if (isTopLevelBlock() && + !IsA(pstmt->utilityStmt, ExecuteStmt) && + !IsA(pstmt->utilityStmt, PrepareStmt) && + !IsA(pstmt->utilityStmt, DeallocateStmt)) + { + instr_time start; + instr_time end; + YbStatementType type; + + if (IsA(pstmt->utilityStmt, TransactionStmt)) + { + TransactionStmt *stmt = (TransactionStmt *) (pstmt->utilityStmt); + + type = ybpgm_getStatementType(stmt); + } + else + type = Other; + + INSTR_TIME_SET_CURRENT(start); + + IncBlockNestingLevel(); + PG_TRY(); + { + if (prev_ProcessUtility) + prev_ProcessUtility(pstmt, queryString, readOnlyTree, context, + params, queryEnv, dest, qc); + else + standard_ProcessUtility(pstmt, queryString, readOnlyTree, + context, params, queryEnv, dest, qc); + DecBlockNestingLevel(); + } + PG_CATCH(); + { + DecBlockNestingLevel(); + PG_RE_THROW(); + } + PG_END_TRY(); + + INSTR_TIME_SET_CURRENT(end); + INSTR_TIME_SUBTRACT(end, start); + + bool requires_autonomous_transaction = false; + YbDdlModeOptional ddl_mode = + YbGetDdlMode(pstmt, context, &requires_autonomous_transaction); + + if (ddl_mode.has_value) + ybpgm_Store(Transaction, INSTR_TIME_GET_MICROSEC(end), 0); + else if (type == Other) + is_statement_executed = true; + + if (type == Begin && !is_inside_transaction_block) + { + is_inside_transaction_block = true; + is_statement_executed = false; + } + if (type == Rollback) + { + is_inside_transaction_block = false; + is_statement_executed = false; + } + /* + * TODO: Once savepoint and rollback to specific transaction are supported, + * transaction block counter needs to be revisited. + * Current logic is to increment non-empty transaction block by 1 + * if non-DDL statement types executed prior to committing. + */ + if (type == Commit) + { + if (qc->commandTag != CMDTAG_ROLLBACK && + is_inside_transaction_block && + is_statement_executed) + { + ybpgm_Store(Transaction, INSTR_TIME_GET_MICROSEC(end), 0); + } + is_inside_transaction_block = false; + is_statement_executed = false; + } + + ybpgm_Store(type, INSTR_TIME_GET_MICROSEC(end), 0 /* rows */ ); + } + else + { + if (prev_ProcessUtility) + prev_ProcessUtility(pstmt, queryString, readOnlyTree, context, + params, queryEnv, dest, qc); + else + standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, + params, queryEnv, dest, qc); + } +} + +static void +ybpgm_Store(YbStatementType type, uint64_t time, uint64_t rows) +{ + if (yb_is_calling_internal_sql_for_ddl) + return; + + struct YbcPgmEntry *entry = &ybpgm_table[type]; + + entry->total_time += time; + entry->calls += 1; + entry->rows += rows; +} + +static void +ybpgm_StoreCount(YbStatementType type, uint64_t time, uint64_t count) +{ + if (yb_is_calling_internal_sql_for_ddl) + return; + + struct YbcPgmEntry *entry = &ybpgm_table[type]; + + entry->total_time += time; + entry->calls += count; + entry->rows += count; +} diff --git a/yb-extensions/yb_pg_metrics/yb_pg_metrics.control b/yb-extensions/yb_pg_metrics/yb_pg_metrics.control new file mode 100644 index 0000000000000..28fdb79b17f73 --- /dev/null +++ b/yb-extensions/yb_pg_metrics/yb_pg_metrics.control @@ -0,0 +1,3 @@ +# yb_pg_metrics extension +comment = 'track execution statistics of all YSQL statements executed' +module_pathname = '$libdir/yb_pg_metrics' diff --git a/yb-extensions/yb_test_extension/Makefile b/yb-extensions/yb_test_extension/Makefile new file mode 100644 index 0000000000000..28890baa4bb79 --- /dev/null +++ b/yb-extensions/yb_test_extension/Makefile @@ -0,0 +1,19 @@ +# contrib/yb_test_extension/Makefile + +PGFILEDESC = "yb_test_extension - An extension for testing purposes only. \ + Different kinds of database objects supported by YB are created \ + in this extension to improve test coverage." + +EXTENSION = yb_test_extension +DATA = yb_test_extension--1.0.sql + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/yb_test_extension +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/yb-extensions/yb_test_extension/yb_test_extension--1.0.sql b/yb-extensions/yb_test_extension/yb_test_extension--1.0.sql new file mode 100644 index 0000000000000..462b836695f87 --- /dev/null +++ b/yb-extensions/yb_test_extension/yb_test_extension--1.0.sql @@ -0,0 +1,110 @@ +/* contrib/yb_test_extension/yb_test_extension--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use '''CREATE EXTENSION yb_test_extension''' to load this file. \quit + +-- Create all kinds of database objects supported by YB which are not included +-- in existing supported extensions to improve test coverage. +-- Unsupported database objects are excluded from this test extension, namely, +-- OPERATOR CLASS, OPERATOR FAMILY, TRANSFORM, ACCESS METHOD, CONVERSION. + +-- Trigger +CREATE TABLE tbl (k INT, v INT); + +CREATE OR REPLACE FUNCTION increment_v() +RETURNS TRIGGER +AS +$$ +BEGIN + NEW.v = NEW.v + 1; + RETURN NEW; +END; +$$ +LANGUAGE plpgsql; + +CREATE TRIGGER increment_v BEFORE INSERT ON tbl +FOR EACH ROW EXECUTE PROCEDURE increment_v(); + +-- Event Trigger +CREATE TABLE command_tag (k serial, tag text); + +CREATE OR REPLACE FUNCTION record_drop_command() +RETURNS EVENT_TRIGGER +AS +$$ +BEGIN + INSERT INTO command_tag(tag) VALUES (TG_TAG); +END; +$$ +LANGUAGE plpgsql; + +CREATE EVENT TRIGGER record_drop_command ON sql_drop EXECUTE PROCEDURE record_drop_command(); + +-- Sequence +CREATE SEQUENCE test_sequence START 101; + +-- Privilege +GRANT ALL ON test_sequence TO yb_fdw; + +-- TS PARSER +-- Create a text search parser same as the parser: default +CREATE TEXT SEARCH PARSER test_parser ( + START = prsd_start, + GETTOKEN = prsd_nexttoken, + END = prsd_end, + HEADLINE = prsd_headline, + LEXTYPES = prsd_lextype +); + +-- TS DICTIONARY +CREATE TEXT SEARCH DICTIONARY test_dict ( + TEMPLATE = simple, + STOPWORDS = english +); + +-- TS CONFIGURATION +CREATE TEXT SEARCH CONFIGURATION test_config (COPY=english); + +-- TS TEMPLATE +-- Create a text search template same as the template: simple +CREATE TEXT SEARCH TEMPLATE test_template ( + INIT = dsimple_init, + LEXIZE = dsimple_lexize +); + +-- Test Collation +CREATE COLLATION test_collation FROM "en-x-icu"; + +-- Test Language +-- Create a procedural language same as plpgsql +CREATE FUNCTION test_call_handler() +RETURNS LANGUAGE_HANDLER AS '$libdir/plpgsql', +'plpgsql_call_handler' LANGUAGE C; + +CREATE FUNCTION test_inline_handler(internal) +RETURNS VOID AS '$libdir/plpgsql', +'plpgsql_inline_handler' LANGUAGE C; + +CREATE FUNCTION test_validator(oid) +RETURNS VOID AS '$libdir/plpgsql', +'plpgsql_validator' LANGUAGE C; + +CREATE LANGUAGE test_language HANDLER test_call_handler INLINE test_inline_handler +VALIDATOR test_validator; + +-- Test ENUM Type +CREATE TYPE test_enum AS ENUM('c', 'b', 'a'); + +-- Test Range Type +-- Create a range type same as int4range +CREATE TYPE test_range AS RANGE ( + SUBTYPE = int4, + SUBTYPE_OPCLASS = int4_ops, + SUBTYPE_DIFF = int4range_subdiff +); + +-- Test Composite Type +CREATE TYPE test_composite AS (col1 text, col2 text); + +-- Add a reference to target schema to test schema names (CVE-2023-39417) +SELECT 1 AS @extschema@; diff --git a/yb-extensions/yb_test_extension/yb_test_extension.control b/yb-extensions/yb_test_extension/yb_test_extension.control new file mode 100644 index 0000000000000..88237ee4ce7b1 --- /dev/null +++ b/yb-extensions/yb_test_extension/yb_test_extension.control @@ -0,0 +1,4 @@ +# yb_test_extension extension +default_version = '1.0' +comment = 'an extension for tesing purposes only' +module_pathname = '$libdir/yb_test_extension' diff --git a/yb-extensions/yb_xcluster_ddl_replication/Makefile b/yb-extensions/yb_xcluster_ddl_replication/Makefile new file mode 100644 index 0000000000000..1e8be7f0e634b --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/Makefile @@ -0,0 +1,25 @@ +# contrib/yb_xcluster_ddl_replication/Makefile + +PGFILEDESC = "yb_xcluster_ddl_replication - YugaByte xCluster DDL replication" + +EXTENSION = yb_xcluster_ddl_replication +DATA = yb_xcluster_ddl_replication--1.0.sql +SHLIB_LINK += -L$(YB_BUILD_ROOT)/lib -lyb_pggate -lyb_pggate_util + +MODULE_big = yb_xcluster_ddl_replication +OBJS = \ + yb_xcluster_ddl_replication.o \ + extension_util.o \ + json_util.o \ + source_ddl_end_handler.o + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/yb_xcluster_ddl_replication +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/yb-extensions/yb_xcluster_ddl_replication/expected/colocated_setup.out b/yb-extensions/yb_xcluster_ddl_replication/expected/colocated_setup.out new file mode 100644 index 0000000000000..98f688a58a049 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/expected/colocated_setup.out @@ -0,0 +1,68 @@ +CREATE DATABASE colocation_test colocation = true; +\c colocation_test +\i sql/setup.sql +-- Initial test setup. +CREATE EXTENSION yb_xcluster_ddl_replication; +-- workaround for lack of CREATE ROLE IF NOT EXISTS +DO $$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'testuser') THEN + CREATE ROLE testuser LOGIN; + END IF; +END +$$; +-- Setup function, to be called at the top of each test file. +CREATE PROCEDURE TEST_reset() + LANGUAGE SQL AS +$$ + SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'source'; + DELETE FROM yb_xcluster_ddl_replication.ddl_queue; + DELETE FROM yb_xcluster_ddl_replication.replicated_ddls; +$$; +-- Verify that both tables have the same keys. +CREATE OR REPLACE FUNCTION TEST_verify_replicated_ddls() + RETURNS boolean + LANGUAGE SQL AS +$$ + SELECT (count(1) = 0) FROM yb_xcluster_ddl_replication.ddl_queue a + FULL OUTER JOIN yb_xcluster_ddl_replication.replicated_ddls b + USING (ddl_end_time, query_id) + WHERE a.yb_data IS NULL OR b.yb_data IS NULL; +$$; +-- Select from ddl_queue, but redact any oids / colocation ids. +CREATE OR REPLACE FUNCTION TEST_filtered_ddl_queue() + RETURNS SETOF yb_xcluster_ddl_replication.ddl_queue + LANGUAGE plpgsql AS +$$ +DECLARE + rec RECORD; + new_array JSONB; + element JSONB; + k TEXT; + v TEXT; +BEGIN + for rec in + select * from yb_xcluster_ddl_replication.ddl_queue + loop + if rec.yb_data->'new_rel_map' is null then + return next rec; + continue; + end if; + new_array := '[]'::jsonb; + for element in + select * from jsonb_array_elements(rec.yb_data->'new_rel_map') + loop + for k, v in + select * from jsonb_each_text(element) + loop + if k = 'relfile_oid' or k = 'colocation_id' then + element := jsonb_set(element, ('{' || k || '}')::text[], '"***"'); + end if; + end loop; + new_array := new_array || element; + end loop; + rec.yb_data := jsonb_set(rec.yb_data, '{new_rel_map}', new_array); + return next rec; + end loop; +END +$$; diff --git a/yb-extensions/yb_xcluster_ddl_replication/expected/create_colocated_table.out b/yb-extensions/yb_xcluster_ddl_replication/expected/create_colocated_table.out new file mode 100644 index 0000000000000..e3149dbc66179 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/expected/create_colocated_table.out @@ -0,0 +1,40 @@ +\c colocation_test +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + source +(1 row) + +-- Verify that temporary objects are allowed but not captured. +CREATE TEMP TABLE temp_foo(i int PRIMARY KEY); +-- Verify that colocated tables are allowed. +CREATE TABLE coloc_foo(i int PRIMARY KEY); +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE coloc_foo(i int PRIMARY KEY)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "coloc_foo", "relfile_oid": "***", "colocation_id": "***", "rel_namespace": "public"}]} +(1 row) + +-- Verify that non-colocated table is captured. +CREATE TABLE non_coloc_foo(i int PRIMARY KEY) WITH (COLOCATION = false); +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE coloc_foo(i int PRIMARY KEY)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "coloc_foo", "relfile_oid": "***", "colocation_id": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE TABLE non_coloc_foo(i int PRIMARY KEY) WITH (COLOCATION = false)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "non_coloc_foo", "relfile_oid": "***", "rel_namespace": "public"}]} +(2 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +-------------------------------------------------------------------------------------- + {"query": "CREATE TABLE coloc_foo(i int PRIMARY KEY)"} + {"query": "CREATE TABLE non_coloc_foo(i int PRIMARY KEY) WITH (COLOCATION = false)"} +(2 rows) + +select * from TEST_verify_replicated_ddls(); + test_verify_replicated_ddls +----------------------------- + t +(1 row) + diff --git a/yb-extensions/yb_xcluster_ddl_replication/expected/create_drop_index.out b/yb-extensions/yb_xcluster_ddl_replication/expected/create_drop_index.out new file mode 100644 index 0000000000000..81b25c2a250c4 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/expected/create_drop_index.out @@ -0,0 +1,93 @@ +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + source +(1 row) + +CREATE SCHEMA create_index; +SET search_path TO create_index; +-- Test temp table and index. +CREATE TEMP TABLE temp_foo(i int PRIMARY KEY, a int); +CREATE INDEX foo_idx_temp on temp_foo(a); +DROP INDEX foo_idx_temp; +DROP TABLE temp_foo; +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE SCHEMA create_index", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE SCHEMA"} +(1 row) + +-- Create base table. +CREATE TABLE foo(i int PRIMARY KEY, a int, b text, c int); +-- Create indexes. +CREATE INDEX foo_idx_simple ON foo(a); +CREATE UNIQUE INDEX foo_idx_unique ON foo(b); +CREATE INDEX foo_idx_filtered ON foo(c ASC, a) WHERE a > c; +-- Test that role is captured properly. +CREATE ROLE new_role SUPERUSER; +SET ROLE new_role; +CREATE INDEX foo_idx_include ON foo(lower(b)) INCLUDE (a) SPLIT INTO 2 TABLETS; +SET ROLE NONE; +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE SCHEMA create_index", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE SCHEMA"} + {"user": "yugabyte", "query": "CREATE TABLE foo(i int PRIMARY KEY, a int, b text, c int)", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "foo", "relfile_oid": "***", "rel_namespace": "create_index"}]} + {"user": "yugabyte", "query": "CREATE INDEX foo_idx_simple ON foo(a)", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "foo_idx_simple", "relfile_oid": "***", "rel_namespace": "create_index"}]} + {"user": "yugabyte", "query": "CREATE UNIQUE INDEX foo_idx_unique ON foo(b)", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "foo_idx_unique", "relfile_oid": "***", "rel_namespace": "create_index"}]} + {"user": "yugabyte", "query": "CREATE INDEX foo_idx_filtered ON foo(c ASC, a) WHERE a > c", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "foo_idx_filtered", "relfile_oid": "***", "rel_namespace": "create_index"}]} + {"user": "new_role", "query": "CREATE INDEX foo_idx_include ON foo(lower(b)) INCLUDE (a) SPLIT INTO 2 TABLETS", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "foo_idx_include", "relfile_oid": "***", "rel_namespace": "create_index"}]} +(6 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +--------------------------------------------------------------------------------------------- + {"query": "CREATE SCHEMA create_index"} + {"query": "CREATE TABLE foo(i int PRIMARY KEY, a int, b text, c int)"} + {"query": "CREATE INDEX foo_idx_simple ON foo(a)"} + {"query": "CREATE UNIQUE INDEX foo_idx_unique ON foo(b)"} + {"query": "CREATE INDEX foo_idx_filtered ON foo(c ASC, a) WHERE a > c"} + {"query": "CREATE INDEX foo_idx_include ON foo(lower(b)) INCLUDE (a) SPLIT INTO 2 TABLETS"} +(6 rows) + +-- Now drop these indexes. +-- Drop two indexes by themselves. +DROP INDEX foo_idx_unique; +DROP INDEX foo_idx_filtered; +-- Drop base table and cascade deletion of other indexes. +DROP TABLE foo; +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE SCHEMA create_index", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE SCHEMA"} + {"user": "yugabyte", "query": "CREATE TABLE foo(i int PRIMARY KEY, a int, b text, c int)", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "foo", "relfile_oid": "***", "rel_namespace": "create_index"}]} + {"user": "yugabyte", "query": "CREATE INDEX foo_idx_simple ON foo(a)", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "foo_idx_simple", "relfile_oid": "***", "rel_namespace": "create_index"}]} + {"user": "yugabyte", "query": "CREATE UNIQUE INDEX foo_idx_unique ON foo(b)", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "foo_idx_unique", "relfile_oid": "***", "rel_namespace": "create_index"}]} + {"user": "yugabyte", "query": "CREATE INDEX foo_idx_filtered ON foo(c ASC, a) WHERE a > c", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "foo_idx_filtered", "relfile_oid": "***", "rel_namespace": "create_index"}]} + {"user": "new_role", "query": "CREATE INDEX foo_idx_include ON foo(lower(b)) INCLUDE (a) SPLIT INTO 2 TABLETS", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "foo_idx_include", "relfile_oid": "***", "rel_namespace": "create_index"}]} + {"user": "yugabyte", "query": "DROP INDEX foo_idx_unique", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP INDEX"} + {"user": "yugabyte", "query": "DROP INDEX foo_idx_filtered", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP INDEX"} + {"user": "yugabyte", "query": "DROP TABLE foo", "schema": "create_index", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP TABLE"} +(9 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +--------------------------------------------------------------------------------------------- + {"query": "CREATE SCHEMA create_index"} + {"query": "CREATE TABLE foo(i int PRIMARY KEY, a int, b text, c int)"} + {"query": "CREATE INDEX foo_idx_simple ON foo(a)"} + {"query": "CREATE UNIQUE INDEX foo_idx_unique ON foo(b)"} + {"query": "CREATE INDEX foo_idx_filtered ON foo(c ASC, a) WHERE a > c"} + {"query": "CREATE INDEX foo_idx_include ON foo(lower(b)) INCLUDE (a) SPLIT INTO 2 TABLETS"} + {"query": "DROP INDEX foo_idx_unique"} + {"query": "DROP INDEX foo_idx_filtered"} + {"query": "DROP TABLE foo"} +(9 rows) + +select * from public.TEST_verify_replicated_ddls(); + test_verify_replicated_ddls +----------------------------- + t +(1 row) + diff --git a/yb-extensions/yb_xcluster_ddl_replication/expected/create_drop_table.out b/yb-extensions/yb_xcluster_ddl_replication/expected/create_drop_table.out new file mode 100644 index 0000000000000..9815e87029aa1 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/expected/create_drop_table.out @@ -0,0 +1,111 @@ +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + source +(1 row) + +-- Verify that temporary objects are not captured. +CREATE TEMP TABLE temp_foo(i int PRIMARY KEY); +DROP TABLE temp_foo; +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +--------- +(0 rows) + +-- Verify that regular tables are captured. +CREATE TABLE foo(i int PRIMARY KEY); +INSERT INTO foo(i) VALUES (1), (2), (3); +-- Check with manual replication flags enabled, ddl string is captured with flag. +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 1; +CREATE TABLE manual_foo(i int PRIMARY KEY); +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 0; +-- Verify that extra info is captured. +CREATE TABLE extra_foo(i int PRIMARY KEY) WITH (COLOCATION = false) SPLIT INTO 1 TABLETS; +-- Verify that info for unique constraint indexes are also captured. +CREATE TABLE unique_foo(i int PRIMARY KEY, u text UNIQUE); +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE foo(i int PRIMARY KEY)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "foo", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE TABLE manual_foo(i int PRIMARY KEY)", "schema": "public", "version": 1, "command_tag": "CREATE TABLE", "manual_replication": true} + {"user": "yugabyte", "query": "CREATE TABLE extra_foo(i int PRIMARY KEY) WITH (COLOCATION = false) SPLIT INTO 1 TABLETS", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "extra_foo", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE TABLE unique_foo(i int PRIMARY KEY, u text UNIQUE)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "unique_foo", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "unique_foo_u_key", "relfile_oid": "***", "rel_namespace": "public"}]} +(4 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------- + {"query": "CREATE TABLE foo(i int PRIMARY KEY)"} + {"query": "CREATE TABLE manual_foo(i int PRIMARY KEY)"} + {"query": "CREATE TABLE extra_foo(i int PRIMARY KEY) WITH (COLOCATION = false) SPLIT INTO 1 TABLETS"} + {"query": "CREATE TABLE unique_foo(i int PRIMARY KEY, u text UNIQUE)"} +(4 rows) + +-- Test tables partitioned by their primary key or a column. +CREATE TABLE foo_partitioned_by_pkey(id int, PRIMARY KEY (id)) PARTITION BY RANGE (id); +CREATE TABLE foo_partitioned_by_col(id int) PARTITION BY RANGE (id); +CREATE TABLE partition1 PARTITION OF foo_partitioned_by_col FOR VALUES FROM (1) TO (10); +CREATE TABLE partition2 PARTITION OF foo_partitioned_by_col FOR VALUES FROM (10) TO (20); +INSERT INTO foo_partitioned_by_col(id) VALUES (1), (2), (3), (10), (11), (12); +-- Test for relations that trigger nonconcurrent backfills. +CREATE INDEX NONCONCURRENTLY nonconcurrent_foo on foo(i); +CREATE INDEX NONCONCURRENTLY on foo(i); -- test without a name +ALTER TABLE foo ADD CONSTRAINT constraint_foo UNIQUE (i); +CREATE UNIQUE INDEX partitioned_index ON foo_partitioned_by_col(id); +-- Now test dropping these tables. +DROP TABLE foo; +-- Check with manual replication flags enabled, ddl string is captured with flag. +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 1; +DROP TABLE manual_foo; +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 0; +DROP TABLE extra_foo; +DROP TABLE unique_foo; +DROP TABLE foo_partitioned_by_pkey; +DROP TABLE foo_partitioned_by_col; +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE foo(i int PRIMARY KEY)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "foo", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE TABLE manual_foo(i int PRIMARY KEY)", "schema": "public", "version": 1, "command_tag": "CREATE TABLE", "manual_replication": true} + {"user": "yugabyte", "query": "CREATE TABLE extra_foo(i int PRIMARY KEY) WITH (COLOCATION = false) SPLIT INTO 1 TABLETS", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "extra_foo", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE TABLE unique_foo(i int PRIMARY KEY, u text UNIQUE)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "unique_foo", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "unique_foo_u_key", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE TABLE foo_partitioned_by_pkey(id int, PRIMARY KEY (id)) PARTITION BY RANGE (id)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "foo_partitioned_by_pkey", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE TABLE foo_partitioned_by_col(id int) PARTITION BY RANGE (id)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "foo_partitioned_by_col", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE TABLE partition1 PARTITION OF foo_partitioned_by_col FOR VALUES FROM (1) TO (10)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "partition1", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE TABLE partition2 PARTITION OF foo_partitioned_by_col FOR VALUES FROM (10) TO (20)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "partition2", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE INDEX NONCONCURRENTLY nonconcurrent_foo on foo(i)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "nonconcurrent_foo", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE INDEX NONCONCURRENTLY on foo(i)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "foo_i_idx", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE foo ADD CONSTRAINT constraint_foo UNIQUE (i)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER TABLE", "new_rel_map": [{"is_index": true, "rel_name": "constraint_foo", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE UNIQUE INDEX partitioned_index ON foo_partitioned_by_col(id)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "partitioned_index", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "partition1_id_idx", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "partition2_id_idx", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "DROP TABLE foo", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP TABLE"} + {"user": "yugabyte", "query": "DROP TABLE manual_foo", "schema": "public", "version": 1, "command_tag": "DROP TABLE", "manual_replication": true} + {"user": "yugabyte", "query": "DROP TABLE extra_foo", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP TABLE"} + {"user": "yugabyte", "query": "DROP TABLE unique_foo", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP TABLE"} + {"user": "yugabyte", "query": "DROP TABLE foo_partitioned_by_pkey", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP TABLE"} + {"user": "yugabyte", "query": "DROP TABLE foo_partitioned_by_col", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP TABLE"} +(18 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------- + {"query": "CREATE TABLE foo(i int PRIMARY KEY)"} + {"query": "CREATE TABLE manual_foo(i int PRIMARY KEY)"} + {"query": "CREATE TABLE extra_foo(i int PRIMARY KEY) WITH (COLOCATION = false) SPLIT INTO 1 TABLETS"} + {"query": "CREATE TABLE unique_foo(i int PRIMARY KEY, u text UNIQUE)"} + {"query": "CREATE TABLE foo_partitioned_by_pkey(id int, PRIMARY KEY (id)) PARTITION BY RANGE (id)"} + {"query": "CREATE TABLE foo_partitioned_by_col(id int) PARTITION BY RANGE (id)"} + {"query": "CREATE TABLE partition1 PARTITION OF foo_partitioned_by_col FOR VALUES FROM (1) TO (10)"} + {"query": "CREATE TABLE partition2 PARTITION OF foo_partitioned_by_col FOR VALUES FROM (10) TO (20)"} + {"query": "CREATE INDEX NONCONCURRENTLY nonconcurrent_foo on foo(i)"} + {"query": "CREATE INDEX NONCONCURRENTLY on foo(i)"} + {"query": "ALTER TABLE foo ADD CONSTRAINT constraint_foo UNIQUE (i)"} + {"query": "CREATE UNIQUE INDEX partitioned_index ON foo_partitioned_by_col(id)"} + {"query": "DROP TABLE foo"} + {"query": "DROP TABLE manual_foo"} + {"query": "DROP TABLE extra_foo"} + {"query": "DROP TABLE unique_foo"} + {"query": "DROP TABLE foo_partitioned_by_pkey"} + {"query": "DROP TABLE foo_partitioned_by_col"} +(18 rows) + diff --git a/yb-extensions/yb_xcluster_ddl_replication/expected/materialized_views.out b/yb-extensions/yb_xcluster_ddl_replication/expected/materialized_views.out new file mode 100644 index 0000000000000..ce3f2afbd3c2d --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/expected/materialized_views.out @@ -0,0 +1,80 @@ +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + source +(1 row) + +CREATE TABLE products ( + id INT PRIMARY KEY, + category TEXT, + price NUMERIC +); +CREATE MATERIALIZED VIEW product_summary AS + SELECT category, COUNT(*) as item_count + FROM products + GROUP BY category; +COMMENT ON MATERIALIZED VIEW product_summary + IS 'Stores the total count of products per category.'; +ALTER MATERIALIZED VIEW product_summary RENAME TO product_inventory; +REFRESH MATERIALIZED VIEW product_inventory; +DROP MATERIALIZED VIEW product_inventory; +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + {"user": "yugabyte", "query": "CREATE TABLE products (\n id INT PRIMARY KEY,\n category TEXT,\n price NUMERIC\n)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "products", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE MATERIALIZED VIEW product_summary AS\n SELECT category, COUNT(*) as item_count\n FROM products\n GROUP BY category", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE MATERIALIZED VIEW", "new_rel_map": [{"rel_name": "product_summary", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "COMMENT ON MATERIALIZED VIEW product_summary\n IS 'Stores the total count of products per category.'", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "COMMENT"} + {"user": "yugabyte", "query": "ALTER MATERIALIZED VIEW product_summary RENAME TO product_inventory", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER MATERIALIZED VIEW"} + {"user": "yugabyte", "query": "REFRESH MATERIALIZED VIEW product_inventory", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "REFRESH MATERIALIZED VIEW", "new_rel_map": [{"rel_name": "product_inventory", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "DROP MATERIALIZED VIEW product_inventory", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP MATERIALIZED VIEW"} +(6 rows) + +-- Test dropping materialized views using other DROP commands +-- DROP SCHEMA +CREATE SCHEMA IF NOT EXISTS permanent_schema; +CREATE TABLE permanent_schema.my_table (id INT); +CREATE MATERIALIZED VIEW permanent_schema.my_mview AS SELECT 'hello' AS greeting; +DROP SCHEMA permanent_schema CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to materialized view permanent_schema.my_mview +drop cascades to table permanent_schema.my_table +-- DROP ROLE +CREATE ROLE drop_me_role; +SET ROLE drop_me_role; +CREATE TEMP TABLE my_temp_object (id INT); +CREATE MATERIALIZED VIEW my_mview AS SELECT 'hello' AS greeting; +RESET ROLE; +DROP OWNED BY drop_me_role; -- fail due to temp object +ERROR: cannot drop temporary and permanent objects in one command +DETAIL: This database is being replicated using xCluster automatic mode, which does not support DDLs that simultaneously drop both temporary and permanent objects. +HINT: Drop the temporary objects separately from the permanent ones using multiple commands. +SET ROLE drop_me_role; +DROP TABLE my_temp_object; +CREATE TABLE real_table (id INT); +RESET ROLE; +DROP OWNED BY drop_me_role; -- should succeed now +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + {"user": "yugabyte", "query": "CREATE TABLE products (\n id INT PRIMARY KEY,\n category TEXT,\n price NUMERIC\n)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "products", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE MATERIALIZED VIEW product_summary AS\n SELECT category, COUNT(*) as item_count\n FROM products\n GROUP BY category", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE MATERIALIZED VIEW", "new_rel_map": [{"rel_name": "product_summary", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "COMMENT ON MATERIALIZED VIEW product_summary\n IS 'Stores the total count of products per category.'", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "COMMENT"} + {"user": "yugabyte", "query": "ALTER MATERIALIZED VIEW product_summary RENAME TO product_inventory", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER MATERIALIZED VIEW"} + {"user": "yugabyte", "query": "REFRESH MATERIALIZED VIEW product_inventory", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "REFRESH MATERIALIZED VIEW", "new_rel_map": [{"rel_name": "product_inventory", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "DROP MATERIALIZED VIEW product_inventory", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP MATERIALIZED VIEW"} + {"user": "yugabyte", "query": "CREATE SCHEMA IF NOT EXISTS permanent_schema", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE SCHEMA"} + {"user": "yugabyte", "query": "CREATE TABLE permanent_schema.my_table (id INT)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "my_table", "relfile_oid": "***", "rel_namespace": "permanent_schema"}]} + {"user": "yugabyte", "query": "CREATE MATERIALIZED VIEW permanent_schema.my_mview AS SELECT 'hello' AS greeting", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE MATERIALIZED VIEW", "new_rel_map": [{"rel_name": "my_mview", "relfile_oid": "***", "rel_namespace": "permanent_schema"}]} + {"user": "yugabyte", "query": "DROP SCHEMA permanent_schema CASCADE", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP SCHEMA"} + {"user": "drop_me_role", "query": "CREATE MATERIALIZED VIEW my_mview AS SELECT 'hello' AS greeting", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE MATERIALIZED VIEW", "new_rel_map": [{"rel_name": "my_mview", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "drop_me_role", "query": "CREATE TABLE real_table (id INT)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "real_table", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "DROP OWNED BY drop_me_role", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP OWNED"} +(13 rows) + +select * from TEST_verify_replicated_ddls(); + test_verify_replicated_ddls +----------------------------- + t +(1 row) + diff --git a/yb-extensions/yb_xcluster_ddl_replication/expected/mixed_temporary_and_persistent.out b/yb-extensions/yb_xcluster_ddl_replication/expected/mixed_temporary_and_persistent.out new file mode 100644 index 0000000000000..2f5b4426defca --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/expected/mixed_temporary_and_persistent.out @@ -0,0 +1,27 @@ +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + source +(1 row) + +-- Test dropping mix of temp and regular tables. +CREATE TEMP TABLE temp_foo(i int PRIMARY KEY); +CREATE TABLE foo(i int PRIMARY KEY); +DROP TABLE temp_foo, foo; -- should fail +ERROR: cannot drop temporary and permanent objects in one command +DETAIL: This database is being replicated using xCluster automatic mode, which does not support DDLs that simultaneously drop both temporary and permanent objects. +HINT: Drop the temporary objects separately from the permanent ones using multiple commands. +DROP TABLE foo, temp_foo; -- should fail +ERROR: cannot drop temporary and permanent objects in one command +DETAIL: This database is being replicated using xCluster automatic mode, which does not support DDLs that simultaneously drop both temporary and permanent objects. +HINT: Drop the temporary objects separately from the permanent ones using multiple commands. +DROP TABLE temp_foo; +DROP TABLE foo; +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE foo(i int PRIMARY KEY)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "foo", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "DROP TABLE foo", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "DROP TABLE"} +(2 rows) + diff --git a/yb-extensions/yb_xcluster_ddl_replication/expected/routines.out b/yb-extensions/yb_xcluster_ddl_replication/expected/routines.out new file mode 100644 index 0000000000000..97bd94006aa2c --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/expected/routines.out @@ -0,0 +1,95 @@ +-- Check connections start in role not_automatic_mode. +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + not_automatic_mode +(1 row) + +-- Check can override with every possible role. +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'unspecified'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + not_automatic_mode +(1 row) + +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'unavailable'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); +ERROR: unable to fetch replication role +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'not_automatic_mode'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + not_automatic_mode +(1 row) + +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'automatic_source'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + source +(1 row) + +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'automatic_target'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + target +(1 row) + +-- Shortcuts for automatic roles. +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'source'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + source +(1 row) + +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'target'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + target +(1 row) + +-- Check for invalid roles. +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'invalid'; +ERROR: invalid value for parameter "yb_xcluster_ddl_replication.test_replication_role_override": "invalid" +HINT: Available values: , NONE, SOURCE, TARGET. +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + target +(1 row) + +-- Check we can turn off override. +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'source'; +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'none'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + not_automatic_mode +(1 row) + +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'target'; +SET yb_xcluster_ddl_replication.TEST_replication_role_override = ''; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + not_automatic_mode +(1 row) + +-- Check override cannot be called if you are not superuser but +-- get_replication_role can. +SET SESSION AUTHORIZATION testuser; +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'target'; +ERROR: permission denied to set parameter "yb_xcluster_ddl_replication.test_replication_role_override" +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + not_automatic_mode +(1 row) + +-- Check no select access to tables. +SELECT * FROM yb_xcluster_ddl_replication.ddl_queue; +ERROR: permission denied for table ddl_queue diff --git a/yb-extensions/yb_xcluster_ddl_replication/expected/setup.out b/yb-extensions/yb_xcluster_ddl_replication/expected/setup.out new file mode 100644 index 0000000000000..469c94ee6275b --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/expected/setup.out @@ -0,0 +1,65 @@ +-- Initial test setup. +CREATE EXTENSION yb_xcluster_ddl_replication; +-- workaround for lack of CREATE ROLE IF NOT EXISTS +DO $$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'testuser') THEN + CREATE ROLE testuser LOGIN; + END IF; +END +$$; +-- Setup function, to be called at the top of each test file. +CREATE PROCEDURE TEST_reset() + LANGUAGE SQL AS +$$ + SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'source'; + DELETE FROM yb_xcluster_ddl_replication.ddl_queue; + DELETE FROM yb_xcluster_ddl_replication.replicated_ddls; +$$; +-- Verify that both tables have the same keys. +CREATE OR REPLACE FUNCTION TEST_verify_replicated_ddls() + RETURNS boolean + LANGUAGE SQL AS +$$ + SELECT (count(1) = 0) FROM yb_xcluster_ddl_replication.ddl_queue a + FULL OUTER JOIN yb_xcluster_ddl_replication.replicated_ddls b + USING (ddl_end_time, query_id) + WHERE a.yb_data IS NULL OR b.yb_data IS NULL; +$$; +-- Select from ddl_queue, but redact any oids / colocation ids. +CREATE OR REPLACE FUNCTION TEST_filtered_ddl_queue() + RETURNS SETOF yb_xcluster_ddl_replication.ddl_queue + LANGUAGE plpgsql AS +$$ +DECLARE + rec RECORD; + new_array JSONB; + element JSONB; + k TEXT; + v TEXT; +BEGIN + for rec in + select * from yb_xcluster_ddl_replication.ddl_queue + loop + if rec.yb_data->'new_rel_map' is null then + return next rec; + continue; + end if; + new_array := '[]'::jsonb; + for element in + select * from jsonb_array_elements(rec.yb_data->'new_rel_map') + loop + for k, v in + select * from jsonb_each_text(element) + loop + if k = 'relfile_oid' or k = 'colocation_id' then + element := jsonb_set(element, ('{' || k || '}')::text[], '"***"'); + end if; + end loop; + new_array := new_array || element; + end loop; + rec.yb_data := jsonb_set(rec.yb_data, '{new_rel_map}', new_array); + return next rec; + end loop; +END +$$; diff --git a/yb-extensions/yb_xcluster_ddl_replication/expected/table_rewrite.out b/yb-extensions/yb_xcluster_ddl_replication/expected/table_rewrite.out new file mode 100644 index 0000000000000..c13161ad54112 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/expected/table_rewrite.out @@ -0,0 +1,256 @@ +CALL TEST_reset(); +SELECT yb_data FROM TEST_filtered_ddl_queue(); + yb_data +--------- +(0 rows) + +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + source +(1 row) + +-- Test 1: ADD and DROP PRIMARY KEY +CREATE TABLE base_table(a int, b int); +CREATE INDEX idx ON base_table(b ASC); +INSERT INTO base_table SELECT i, i%2 FROM generate_series(1, 100) as i; +-- triggers table rewrite +ALTER TABLE base_table ADD PRIMARY KEY (a ASC); +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE base_table(a int, b int)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "base_table", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE INDEX idx ON base_table(b ASC)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "idx", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE base_table ADD PRIMARY KEY (a ASC)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER TABLE", "new_rel_map": [{"rel_name": "base_table", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "idx", "relfile_oid": "***", "rel_namespace": "public"}]} +(3 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------- + {"query": "CREATE TABLE base_table(a int, b int)"} + {"query": "CREATE INDEX idx ON base_table(b ASC)"} + {"query": "ALTER TABLE base_table ADD PRIMARY KEY (a ASC)"} +(3 rows) + +-- triggers table rewrite +ALTER TABLE base_table DROP CONSTRAINT base_table_pkey; +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + {"user": "yugabyte", "query": "CREATE TABLE base_table(a int, b int)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "base_table", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE INDEX idx ON base_table(b ASC)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "idx", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE base_table ADD PRIMARY KEY (a ASC)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER TABLE", "new_rel_map": [{"rel_name": "base_table", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "idx", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE base_table DROP CONSTRAINT base_table_pkey", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER TABLE", "new_rel_map": [{"rel_name": "base_table", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "idx", "relfile_oid": "***", "rel_namespace": "public"}]} +(4 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +--------------------------------------------------------------------- + {"query": "CREATE TABLE base_table(a int, b int)"} + {"query": "CREATE INDEX idx ON base_table(b ASC)"} + {"query": "ALTER TABLE base_table ADD PRIMARY KEY (a ASC)"} + {"query": "ALTER TABLE base_table DROP CONSTRAINT base_table_pkey"} +(4 rows) + +DROP TABLE base_table; +-- Test 2: ADD COLUMN with PRIMARY KEY +CALL TEST_reset(); +CREATE TABLE base_table2(a int, b int); +CREATE INDEX idx2 ON base_table2(b ASC); +-- triggers table rewrite +ALTER TABLE base_table2 ADD COLUMN c int PRIMARY KEY; +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + {"user": "yugabyte", "query": "CREATE TABLE base_table2(a int, b int)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "base_table2", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE INDEX idx2 ON base_table2(b ASC)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "idx2", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE base_table2 ADD COLUMN c int PRIMARY KEY", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER TABLE", "new_rel_map": [{"rel_name": "base_table2", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "idx2", "relfile_oid": "***", "rel_namespace": "public"}]} +(3 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------- + {"query": "CREATE TABLE base_table2(a int, b int)"} + {"query": "CREATE INDEX idx2 ON base_table2(b ASC)"} + {"query": "ALTER TABLE base_table2 ADD COLUMN c int PRIMARY KEY"} +(3 rows) + +DROP TABLE base_table2; +-- Test 3: ADD COLUMN with DEFAULT volatile expression +CALL TEST_reset(); +CREATE TABLE base_table3(a int, b int); +CREATE INDEX idx3 ON base_table3(b ASC); +INSERT INTO base_table3 SELECT i, i%2 FROM generate_series(1, 100) as i; +-- triggers table rewrite +ALTER TABLE base_table3 ADD COLUMN created_at TIMESTAMP DEFAULT clock_timestamp() NOT NULL; +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE base_table3(a int, b int)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "base_table3", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE INDEX idx3 ON base_table3(b ASC)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "idx3", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE base_table3 ADD COLUMN created_at TIMESTAMP DEFAULT clock_timestamp() NOT NULL", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER TABLE", "new_rel_map": [{"rel_name": "base_table3", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "idx3", "relfile_oid": "***", "rel_namespace": "public"}]} +(3 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +--------------------------------------------------------------------------------------------------------- + {"query": "CREATE TABLE base_table3(a int, b int)"} + {"query": "CREATE INDEX idx3 ON base_table3(b ASC)"} + {"query": "ALTER TABLE base_table3 ADD COLUMN created_at TIMESTAMP DEFAULT clock_timestamp() NOT NULL"} +(3 rows) + +DROP TABLE base_table3; +-- Test 4: ADD COLUMN GENERATED ALWAYS AS IDENTITY +CALL TEST_reset(); +CREATE TABLE base_table4(a int, b int); +CREATE INDEX idx4 ON base_table4(b ASC); +INSERT INTO base_table4 SELECT i, i%2 FROM generate_series(1, 100) as i; +-- triggers table rewrite +ALTER TABLE base_table4 ADD COLUMN new_id INT GENERATED ALWAYS AS IDENTITY; +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE base_table4(a int, b int)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "base_table4", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE INDEX idx4 ON base_table4(b ASC)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "idx4", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE base_table4 ADD COLUMN new_id INT GENERATED ALWAYS AS IDENTITY", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER TABLE", "new_rel_map": [{"rel_name": "base_table4", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "idx4", "relfile_oid": "***", "rel_namespace": "public"}], "sequence_info": [{"oid": 16573, "name": "base_table4_new_id_seq", "schema": "public"}, {"oid": 16573, "name": "base_table4_new_id_seq", "schema": "public"}]} +(3 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +----------------------------------------------------------------------------------------- + {"query": "CREATE TABLE base_table4(a int, b int)"} + {"query": "CREATE INDEX idx4 ON base_table4(b ASC)"} + {"query": "ALTER TABLE base_table4 ADD COLUMN new_id INT GENERATED ALWAYS AS IDENTITY"} +(3 rows) + +DROP TABLE base_table4; +-- Test 5: ALTER COLUMN TYPE on indexed column +CALL TEST_reset(); +CREATE TABLE base_table5(a int, b int); +CREATE INDEX idx5 ON base_table5(b ASC); +INSERT INTO base_table5 SELECT i, i%2 FROM generate_series(1, 100) as i; +-- triggers table rewrite +ALTER TABLE base_table5 ALTER COLUMN b TYPE float USING(random()); +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE base_table5(a int, b int)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "base_table5", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE INDEX idx5 ON base_table5(b ASC)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "idx5", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE base_table5 ALTER COLUMN b TYPE float USING(random())", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER TABLE", "new_rel_map": [{"rel_name": "base_table5", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "idx5", "relfile_oid": "***", "rel_namespace": "public"}]} +(3 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +-------------------------------------------------------------------------------- + {"query": "CREATE TABLE base_table5(a int, b int)"} + {"query": "CREATE INDEX idx5 ON base_table5(b ASC)"} + {"query": "ALTER TABLE base_table5 ALTER COLUMN b TYPE float USING(random())"} +(3 rows) + +DROP TABLE base_table5; +-- Test 6: ALTER COLUMN TYPE on non-indexed column +CALL TEST_reset(); +CREATE TABLE base_table6(a int, b int); +CREATE INDEX idx6 ON base_table6(b ASC); +INSERT INTO base_table6 SELECT i, i%2 FROM generate_series(1, 100) as i; +-- triggers table rewrite +ALTER TABLE base_table6 ALTER COLUMN a TYPE float USING(random()); +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE base_table6(a int, b int)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "base_table6", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE INDEX idx6 ON base_table6(b ASC)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "idx6", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE base_table6 ALTER COLUMN a TYPE float USING(random())", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER TABLE", "new_rel_map": [{"rel_name": "base_table6", "relfile_oid": "***", "rel_namespace": "public"}, {"is_index": true, "rel_name": "idx6", "relfile_oid": "***", "rel_namespace": "public"}]} +(3 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +-------------------------------------------------------------------------------- + {"query": "CREATE TABLE base_table6(a int, b int)"} + {"query": "CREATE INDEX idx6 ON base_table6(b ASC)"} + {"query": "ALTER TABLE base_table6 ALTER COLUMN a TYPE float USING(random())"} +(3 rows) + +DROP TABLE base_table6; +-- Test 7: ALTER COLUMN TYPE with dependent index (no table rewrite and reindex, index rebuild only) +-- Test 7a: varchar to text +CALL TEST_reset(); +CREATE TABLE t1(a varchar); +INSERT INTO t1(a) SELECT 'row_' || i FROM generate_series(1, 100) as i; +CREATE INDEX idx1 ON t1(a); +-- trigger index rebuild +ALTER TABLE t1 ALTER COLUMN a TYPE text; +-- Verify the index recreation are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE t1(a varchar)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "t1", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE INDEX idx1 ON t1(a)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "idx1", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE t1 ALTER COLUMN a TYPE text", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER TABLE"} +(3 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +------------------------------------------------------ + {"query": "CREATE TABLE t1(a varchar)"} + {"query": "CREATE INDEX idx1 ON t1(a)"} + {"query": "ALTER TABLE t1 ALTER COLUMN a TYPE text"} +(3 rows) + +-- Test 7b: changing collation +CALL TEST_reset(); +CREATE TABLE t2(a text COLLATE "C"); +INSERT INTO t2(a) SELECT 'row_' || i FROM generate_series(1, 50) as i; +CREATE INDEX idx2 ON t2(a); +-- trigger index rebuild +ALTER TABLE t2 ALTER COLUMN a TYPE text COLLATE "en_US"; +-- Verify the index recreation are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE t2(a text COLLATE \"C\")", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "t2", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "CREATE INDEX idx2 ON t2(a)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE INDEX", "new_rel_map": [{"is_index": true, "rel_name": "idx2", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE t2 ALTER COLUMN a TYPE text COLLATE \"en_US\"", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "ALTER TABLE", "new_rel_map": [{"is_index": true, "rel_name": "idx2", "relfile_oid": "***", "rel_namespace": "public"}]} +(3 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------ + {"query": "CREATE TABLE t2(a text COLLATE \"C\")"} + {"query": "CREATE INDEX idx2 ON t2(a)"} + {"query": "ALTER TABLE t2 ALTER COLUMN a TYPE text COLLATE \"en_US\""} +(3 rows) + +DROP TABLE t1; +DROP TABLE t2; +-- Test 8: multiple table rewrites with manual replication flag +CALL TEST_reset(); +CREATE TABLE manual_base(a int, b int); +INSERT INTO manual_base SELECT i, i%2 FROM generate_series(1, 50) as i; +-- Test with manual replication flag enabled +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 1; +ALTER TABLE manual_base ADD PRIMARY KEY (a ASC); +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 0; +-- Verify manual replication flag is captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + {"user": "yugabyte", "query": "CREATE TABLE manual_base(a int, b int)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "manual_base", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "ALTER TABLE manual_base ADD PRIMARY KEY (a ASC)", "schema": "public", "version": 1, "command_tag": "ALTER TABLE", "manual_replication": true} +(2 rows) + +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + yb_data +-------------------------------------------------------------- + {"query": "CREATE TABLE manual_base(a int, b int)"} + {"query": "ALTER TABLE manual_base ADD PRIMARY KEY (a ASC)"} +(2 rows) + +DROP TABLE manual_base; diff --git a/yb-extensions/yb_xcluster_ddl_replication/expected/truncate.out b/yb-extensions/yb_xcluster_ddl_replication/expected/truncate.out new file mode 100644 index 0000000000000..a1c34817033ee --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/expected/truncate.out @@ -0,0 +1,22 @@ +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + get_replication_role +---------------------- + source +(1 row) + +CREATE TEMP TABLE temp_foo(i int PRIMARY KEY); +CREATE TABLE foo(i int PRIMARY KEY); +TRUNCATE foo, temp_foo; -- should fail +ERROR: unsupported mix of temporary and permanent objects +DETAIL: This database is being replicated using xCluster automatic mode, which does not support DDLs that simultaneously use both temporary and permanent objects. +HINT: Using multiple commands, manipulate the temporary objects separately from the permanent ones. +TRUNCATE foo; +TRUNCATE temp_foo; +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + yb_data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"user": "yugabyte", "query": "CREATE TABLE foo(i int PRIMARY KEY)", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "CREATE TABLE", "new_rel_map": [{"rel_name": "foo", "relfile_oid": "***", "rel_namespace": "public"}]} + {"user": "yugabyte", "query": "TRUNCATE foo", "schema": "public", "version": 1, "variables": {"timezone": "America/Los_Angeles", "datestyle": "Postgres, MDY", "intervalstyle": "postgres_verbose", "default_tablespace": "", "check_function_bodies": "on", "standard_conforming_strings": "on", "yb_use_hash_splitting_by_default": "on"}, "command_tag": "TRUNCATE TABLE", "new_rel_map": [{"rel_name": "foo", "relfile_oid": "***", "rel_namespace": "public"}]} +(2 rows) + diff --git a/yb-extensions/yb_xcluster_ddl_replication/extension_util.c b/yb-extensions/yb_xcluster_ddl_replication/extension_util.c new file mode 100644 index 0000000000000..1e442aeb41d31 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/extension_util.c @@ -0,0 +1,342 @@ +/*----------------------------------------------------------------------------- + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + *----------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "catalog/pg_extension.h" +#include "catalog/pg_extension_d.h" +#include "catalog/pg_type.h" +#include "executor/spi.h" +#include "extension_util.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/relcache.h" +#include "utils/syscache.h" + +const char *kManualReplicationErrorMsg = +"To manually replicate, run DDL on the source followed by the target with " +"SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = true"; + +int64 +GetInt64FromVariable(const char *var, const char *var_name) +{ + if (!var || strcmp(var, "") == 0) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("error parsing %s: %s", var_name, var))); + + char *endp = NULL; + int64 ret = strtoll(var, &endp, 10); + + if (*endp != '\0') + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("error parsing %s: %s", var_name, var))); + + return ret; +} + +static Oid cached_extension_owner_oid = InvalidOid; /* Cached for a pg + * connection. */ +Oid +XClusterExtensionOwner(void) +{ + if (cached_extension_owner_oid > InvalidOid) + return cached_extension_owner_oid; + + Relation extensionRelation = table_open(ExtensionRelationId, + AccessShareLock); + + ScanKeyData entry[1]; + + ScanKeyInit(&entry[0], Anum_pg_extension_extname, BTEqualStrategyNumber, + F_NAMEEQ, CStringGetDatum(EXTENSION_NAME)); + + SysScanDesc scanDescriptor = systable_beginscan(extensionRelation, + ExtensionNameIndexId, true, + NULL, 1, entry); + + HeapTuple extensionTuple = systable_getnext(scanDescriptor); + + if (!HeapTupleIsValid(extensionTuple)) + { + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("%s extension is not loaded", EXTENSION_NAME))); + } + + Form_pg_extension extensionForm = (Form_pg_extension) GETSTRUCT(extensionTuple); + Oid extensionOwner = extensionForm->extowner; + + systable_endscan(scanDescriptor); + table_close(extensionRelation, AccessShareLock); + + /* Cache this value for future calls. */ + cached_extension_owner_oid = extensionOwner; + return extensionOwner; +} + +Oid +SPI_GetOidIfExists(HeapTuple spi_tuple, int column_id) +{ + bool is_null; + Oid oid = DatumGetObjectId(SPI_getbinval(spi_tuple, SPI_tuptable->tupdesc, + column_id, &is_null)); + + if (is_null) + return InvalidOid; + return oid; +} + +Oid +SPI_GetOid(HeapTuple spi_tuple, int column_id) +{ + Oid oid = SPI_GetOidIfExists(spi_tuple, column_id); + + if (oid == InvalidOid) + elog(ERROR, "Found NULL value when parsing oid (column %d)", column_id); + return oid; +} + +char * +SPI_GetText(HeapTuple spi_tuple, int column_id) +{ + return SPI_getvalue(spi_tuple, SPI_tuptable->tupdesc, column_id); +} + +bool +SPI_GetBool(HeapTuple spi_tuple, int column_id) +{ + bool is_null; + bool val = DatumGetBool(SPI_getbinval(spi_tuple, SPI_tuptable->tupdesc, + column_id, &is_null)); + + if (is_null) + elog(ERROR, "Found NULL value when parsing bool (column %d)", column_id); + return val; +} + +CollectedCommand * +GetCollectedCommand(HeapTuple spi_tuple, int column_id) +{ + bool isnull; + Pointer command_datum = DatumGetPointer(SPI_getbinval(spi_tuple, + SPI_tuptable->tupdesc, column_id, + &isnull)); + + if (isnull) + elog(ERROR, "Found NULL value when parsing command (column %d)", column_id); + return (CollectedCommand *) command_datum; +} + +/* + * elog's an ERROR if column column_id of spi_tuple does not hold a (possibly + * NULL) text[] value. Otherwise, examines that value and + * + * - returns NULL if the the value does not have an element at index + * element_index + * - otherwise returns the element of the array at that index as a palloc'ed + * C string. + */ +char * +SPI_TextArrayGetElement(HeapTuple spi_tuple, int column_id, int element_index) +{ + bool is_null; + Datum array_datum; + ArrayType *array; + Oid element_type; + int16 typlen; + bool typbyval; + char typalign; + Datum *elements; + int num_elements; + char *result = NULL; + + array_datum = SPI_getbinval(spi_tuple, SPI_tuptable->tupdesc, column_id, &is_null); + if (is_null) + return NULL; + + array = DatumGetArrayTypeP(array_datum); + element_type = ARR_ELEMTYPE(array); + + if (element_type != TEXTOID) + elog(ERROR, "Expected text[] but found different type %u", + element_type); + + get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign); + deconstruct_array(array, element_type, typlen, typbyval, typalign, + &elements, /* elog on NULL values */ NULL, + &num_elements); + + if (element_index >= 0 && element_index < num_elements) + { + result = pstrdup(TextDatumGetCString(elements[element_index])); + } + pfree(elements); + + return result; +} + +bool +IsTempSchema(const char *schema_name) +{ + return schema_name && !strcmp(schema_name, "pg_temp"); +} + +bool +IsTemporaryHelper(char *thing_kind, Oid thing_oid, char *thing_table, + char *relation_oid_column) +{ + StringInfoData query; + bool isnull; + Datum is_temp_datum; + HeapTuple tuple; + TupleDesc tupdesc; + + /* + * Each kind of "thing" has a dedicated table indexed by its OID. We need + * to move from that row to the relation it is associated with in order + * to find that relation's temporary-ness. + */ + + SPI_push(); + initStringInfo(&query); + appendStringInfo(&query, + "SELECT (c.relpersistence = 't') " + "FROM pg_catalog.pg_class AS c " + "JOIN pg_catalog.%s AS t ON c.oid = t.%s " + "WHERE t.oid = %u", + thing_table, relation_oid_column, thing_oid); + + int exec_result = SPI_execute(query.data, /* read_only= */ true, + /* tcount= */ 0); + if (exec_result != SPI_OK_SELECT) + elog(ERROR, "SPI_exec failed (error %d): %s", exec_result, query.data); + pfree(query.data); + + if (SPI_processed == 0) + elog(ERROR, "could not find %s with OID %u", thing_kind, thing_oid); + + tupdesc = SPI_tuptable->tupdesc; + tuple = SPI_tuptable->vals[0]; + is_temp_datum = SPI_getbinval(tuple, tupdesc, 1, &isnull); + /* A null result here would indicate catalog corruption */ + if (isnull) + elog(ERROR, "relpersistence check returned null for %s %u", thing_kind, + thing_oid); + + SPI_pop(); + return DatumGetBool(is_temp_datum); +} + +bool +IsTemporaryPolicy(Oid policy_oid) +{ + return IsTemporaryHelper("policy", policy_oid, "pg_policy", "polrelid"); +} + +bool +IsTemporaryTrigger(Oid trigger_oid) +{ + return IsTemporaryHelper("trigger", trigger_oid, "pg_trigger", "tgrelid"); +} + +bool +IsTemporaryRule(Oid rule_oid) +{ + return IsTemporaryHelper("rule", rule_oid, "pg_rewrite", "ev_class"); +} + +Oid +GetColocationIdForTableRewrite(Relation *rel) +{ + /* + * Table rewrites have some staleness with updating the relcache and + * yb_table_properties, especially with indexes. Need to explicitly get a + * new table desc to get the up to date values. + */ + Oid dbid = YBCGetDatabaseOid(*rel); + Oid relfileNodeId = YbGetRelfileNodeId(*rel); + bool not_found = false; + YbcPgTableDesc yb_tabledesc = NULL; + YbcTablePropertiesData table_props; + + HandleYBStatusIgnoreNotFound(YBCPgGetTableDesc(dbid, + relfileNodeId, + &yb_tabledesc), + ¬_found); + if (not_found) + return InvalidOid; + + HandleYBStatusIgnoreNotFound(YBCPgGetTableProperties(yb_tabledesc, + &table_props), + ¬_found); + if (not_found || !table_props.is_colocated) + return InvalidOid; + + return table_props.colocation_id; +} + +Oid +GetColocationIdFromRelation(Relation *rel, bool is_table_rewrite) +{ + if (is_table_rewrite) + return GetColocationIdForTableRewrite(rel); + + YbcTableProperties table_props = YbTryGetTableProperties(*rel); + + if (!table_props || !table_props->is_colocated) + return InvalidOid; + + return table_props->colocation_id; +} + +char * +get_typname(Oid pg_type_oid) +{ + HeapTuple type_tuple = SearchSysCache1(TYPEOID, + ObjectIdGetDatum(pg_type_oid)); + + if (!HeapTupleIsValid(type_tuple)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type OID %u not found", pg_type_oid))); + } + + Form_pg_type type_form = (Form_pg_type) GETSTRUCT(type_tuple); + char *type_name = pstrdup(NameStr(type_form->typname)); + + ReleaseSysCache(type_tuple); + return type_name; +} + +bool +IsExtensionDdl(CommandTag command_tag) +{ + if (command_tag == CMDTAG_CREATE_EXTENSION || + command_tag == CMDTAG_DROP_EXTENSION || + command_tag == CMDTAG_ALTER_EXTENSION) + { + return true; + } + + return false; +} diff --git a/yb-extensions/yb_xcluster_ddl_replication/extension_util.h b/yb-extensions/yb_xcluster_ddl_replication/extension_util.h new file mode 100644 index 0000000000000..84920260d807d --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/extension_util.h @@ -0,0 +1,103 @@ +/*----------------------------------------------------------------------------- + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + *----------------------------------------------------------------------------- + */ + +#ifndef YB_XCLUSTER_DDL_REPLICATION_UTIL +#define YB_XCLUSTER_DDL_REPLICATION_UTIL + +#include "postgres.h" + +#include "tcop/deparse_utility.h" +#include "utils/memutils.h" + +#define EXTENSION_NAME "yb_xcluster_ddl_replication" +#define DDL_QUEUE_TABLE_NAME "ddl_queue" +#define REPLICATED_DDLS_TABLE_NAME "replicated_ddls" + +#define INIT_MEM_CONTEXT_AND_SPI_CONNECT(desc) \ + do \ + { \ + context_new = AllocSetContextCreate(CurrentMemoryContext, desc, \ + ALLOCSET_DEFAULT_SIZES); \ + context_old = MemoryContextSwitchTo(context_new); \ + GetUserIdAndSecContext(&save_userid, &save_sec_context); \ + SetUserIdAndSecContext(XClusterExtensionOwner(), \ + SECURITY_RESTRICTED_OPERATION); \ + if (SPI_connect() != SPI_OK_CONNECT) \ + elog(ERROR, "SPI_connect failed"); \ + } while (false) + +#define CLOSE_MEM_CONTEXT_AND_SPI \ + do \ + { \ + if (SPI_finish() != SPI_OK_FINISH) \ + elog(ERROR, "SPI_finish() failed"); \ + SetUserIdAndSecContext(save_userid, save_sec_context); \ + MemoryContextSwitchTo(context_old); \ + MemoryContextDelete(context_new); \ + } while (false) + +/* Handle old PG11 and newer PG15 code. */ +#if (PG_VERSION_NUM < 120000) +#define table_open(r, l) heap_open(r, l) +#define table_close(r, l) heap_close(r, l) +#endif + +/* Global variables. */ +extern const char *kManualReplicationErrorMsg; + +/* Get int64 value from string extension variable. */ +extern int64 GetInt64FromVariable(const char *var, const char *var_name); + +/* + * XClusterExtensionOwner returns the oid of the user that owns the extension. + * This is used in INIT_MEM_CONTEXT_AND_SPI_CONNECT to allow the extension to + * update its objects. + */ +extern Oid XClusterExtensionOwner(void); + +extern Oid SPI_GetOid(HeapTuple spi_tuple, int column_id); + +/* Returns InvalidOid (0) if value doesn't exist/is null. */ +extern Oid SPI_GetOidIfExists(HeapTuple spi_tuple, int column_id); + +extern char *SPI_GetText(HeapTuple spi_tuple, int column_id); + +extern bool SPI_GetBool(HeapTuple spi_tuple, int column_id); + +extern CollectedCommand *GetCollectedCommand(HeapTuple spi_tuple, int column_id); + +extern char *SPI_TextArrayGetElement(HeapTuple spi_tuple, int column_id, + int element_index); + +/* If true, any object in this schema is a temporary object. */ +extern bool IsTempSchema(const char *schema_name); + +extern bool IsTemporaryPolicy(Oid policy_oid); + +extern bool IsTemporaryTrigger(Oid trigger_oid); + +extern bool IsTemporaryRule(Oid rule_oid); + +/* Returns the relation's colocation id or InvalidOid (0) if not colocated. */ +extern Oid GetColocationIdFromRelation(Relation *rel, bool is_table_rewrite); + +extern char *get_typname(Oid pg_type_oid); + +extern bool IsExtensionDdl(CommandTag command_tag); + +#endif diff --git a/yb-extensions/yb_xcluster_ddl_replication/json_util.c b/yb-extensions/yb_xcluster_ddl_replication/json_util.c new file mode 100644 index 0000000000000..3112a51f8d0b2 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/json_util.c @@ -0,0 +1,78 @@ +/*----------------------------------------------------------------------------- + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + *----------------------------------------------------------------------------- + */ + +#include "json_util.h" +#include "utils/fmgrprotos.h" + +void +AddJsonKey(JsonbParseState *state, char *key_buf) +{ + JsonbValue key; + + key.type = jbvString; + key.val.string.len = strlen(key_buf); + key.val.string.val = pstrdup(key_buf); + + (void) pushJsonbValue(&state, WJB_KEY, &key); +} + +void +AddNumericJsonEntry(JsonbParseState *state, char *key_buf, int64 val) +{ + AddJsonKey(state, key_buf); + + JsonbValue value; + + value.type = jbvNumeric; + value.val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric, val)); + + (void) pushJsonbValue(&state, WJB_VALUE, &value); +} + +void +AddBoolJsonEntry(JsonbParseState *state, char *key_buf, bool val) +{ + AddJsonKey(state, key_buf); + + JsonbValue value; + + value.type = jbvBool; + value.val.boolean = val; + + (void) pushJsonbValue(&state, WJB_VALUE, &value); +} + +void +AddStringJsonEntry(JsonbParseState *state, char *key_buf, const char *val) +{ + AddNStringJsonEntry(state, key_buf, val, strlen(val)); +} + +void +AddNStringJsonEntry(JsonbParseState *state, char *key_buf, const char *val, Size len) +{ + AddJsonKey(state, key_buf); + + JsonbValue value; + + value.type = jbvString; + value.val.string.len = len; + value.val.string.val = pnstrdup(val, len); + + (void) pushJsonbValue(&state, WJB_VALUE, &value); +} diff --git a/yb-extensions/yb_xcluster_ddl_replication/json_util.h b/yb-extensions/yb_xcluster_ddl_replication/json_util.h new file mode 100644 index 0000000000000..526736ca24d54 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/json_util.h @@ -0,0 +1,32 @@ +/*----------------------------------------------------------------------------- + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + *----------------------------------------------------------------------------- + */ + +#ifndef YB_XCLUSTER_DDL_REPLICATION_JSON_UTIL +#define YB_XCLUSTER_DDL_REPLICATION_JSON_UTIL + +#include "postgres.h" + +#include "utils/jsonb.h" + +extern void AddJsonKey(JsonbParseState *state, char *key_buf); +extern void AddNumericJsonEntry(JsonbParseState *state, char *key_buf, int64 val); +extern void AddStringJsonEntry(JsonbParseState *state, char *key_buf, const char *val); +extern void AddNStringJsonEntry(JsonbParseState *state, char *key_buf, const char *val, Size len); +extern void AddBoolJsonEntry(JsonbParseState *state, char *key_buf, bool val); + +#endif diff --git a/yb-extensions/yb_xcluster_ddl_replication/source_ddl_end_handler.c b/yb-extensions/yb_xcluster_ddl_replication/source_ddl_end_handler.c new file mode 100644 index 0000000000000..ee7136ec51267 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/source_ddl_end_handler.c @@ -0,0 +1,1170 @@ +/* Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + *----------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/partition.h" +#include "catalog/pg_am_d.h" +#include "catalog/pg_amop_d.h" +#include "catalog/pg_amproc_d.h" +#include "catalog/pg_attrdef_d.h" +#include "catalog/pg_cast_d.h" +#include "catalog/pg_collation_d.h" +#include "catalog/pg_constraint_d.h" +#include "catalog/pg_conversion_d.h" +#include "catalog/pg_extension_d.h" +#include "catalog/pg_foreign_data_wrapper_d.h" +#include "catalog/pg_foreign_server_d.h" +#include "catalog/pg_foreign_table_d.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_namespace_d.h" +#include "catalog/pg_opclass_d.h" +#include "catalog/pg_operator_d.h" +#include "catalog/pg_opfamily_d.h" +#include "catalog/pg_policy_d.h" +#include "catalog/pg_proc_d.h" +#include "catalog/pg_publication_d.h" +#include "catalog/pg_publication_namespace_d.h" +#include "catalog/pg_publication_rel_d.h" +#include "catalog/pg_rewrite_d.h" +#include "catalog/pg_statistic_ext.h" +#include "catalog/pg_subscription_d.h" +#include "catalog/pg_subscription_rel_d.h" +#include "catalog/pg_trigger_d.h" +#include "catalog/pg_ts_config_d.h" +#include "catalog/pg_ts_config_map_d.h" +#include "catalog/pg_ts_dict_d.h" +#include "catalog/pg_ts_parser_d.h" +#include "catalog/pg_ts_template_d.h" +#include "catalog/pg_type_d.h" +#include "catalog/pg_user_mapping_d.h" +#include "executor/spi.h" +#include "extension_util.h" +#include "json_util.h" +#include "lib/stringinfo.h" +#include "pg_yb_utils.h" +#include "source_ddl_end_handler.h" +#include "tcop/cmdtag.h" +#include "utils/builtins.h" +#include "utils/jsonb.h" +#include "utils/lsyscache.h" +#include "utils/palloc.h" +#include "utils/rel.h" + +#define DDL_END_CLASSID_COLUMN_ID 1 +#define DDL_END_OBJID_COLUMN_ID 2 +#define DDL_END_COMMAND_TAG_COLUMN_ID 3 +#define DDL_END_SCHEMA_NAME_COLUMN_ID 4 +#define DDL_END_COMMAND_COLUMN_ID 5 + +#define SQL_DROP_CLASS_ID_COLUMN_ID 1 +#define SQL_DROP_IS_TEMP_COLUMN_ID 2 +#define SQL_DROP_OBJECT_TYPE_COLUMN_ID 3 +#define SQL_DROP_SCHEMA_NAME_COLUMN_ID 4 +#define SQL_DROP_OBJECT_NAME_COLUMN_ID 5 +#define SQL_DROP_ADDRESS_NAMES_COLUMN_ID 6 + +#define TABLE_REWRITE_OBJID_COLUMN_ID 1 + +static List *rewritten_table_oid_list = NIL; + +/* DDLs ignored for replication. */ +#define IGNORED_DDL_LIST \ + X(CMDTAG_ALTER_PUBLICATION) \ + X(CMDTAG_ALTER_SUBSCRIPTION) \ + X(CMDTAG_CREATE_PUBLICATION) \ + X(CMDTAG_CREATE_SUBSCRIPTION) \ + X(CMDTAG_DROP_PUBLICATION) \ + X(CMDTAG_DROP_SUBSCRIPTION) + +#define ALLOWED_DDL_LIST \ + X(CMDTAG_COMMENT) \ + X(CMDTAG_CREATE_ACCESS_METHOD) \ + X(CMDTAG_CREATE_AGGREGATE) \ + X(CMDTAG_CREATE_CAST) \ + X(CMDTAG_CREATE_COLLATION) \ + X(CMDTAG_CREATE_DOMAIN) \ + X(CMDTAG_CREATE_EXTENSION) \ + X(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER) \ + X(CMDTAG_CREATE_FOREIGN_TABLE) \ + X(CMDTAG_CREATE_FUNCTION) \ + X(CMDTAG_CREATE_OPERATOR) \ + X(CMDTAG_CREATE_OPERATOR_CLASS) \ + X(CMDTAG_CREATE_OPERATOR_FAMILY) \ + X(CMDTAG_CREATE_POLICY) \ + X(CMDTAG_CREATE_PROCEDURE) \ + X(CMDTAG_CREATE_ROUTINE) \ + X(CMDTAG_CREATE_RULE) \ + X(CMDTAG_CREATE_SCHEMA) \ + X(CMDTAG_CREATE_SERVER) \ + X(CMDTAG_CREATE_STATISTICS) \ + X(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION) \ + X(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY) \ + X(CMDTAG_CREATE_TEXT_SEARCH_PARSER) \ + X(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE) \ + X(CMDTAG_CREATE_TRIGGER) \ + X(CMDTAG_CREATE_USER_MAPPING) \ + X(CMDTAG_CREATE_VIEW) \ + X(CMDTAG_ALTER_AGGREGATE) \ + X(CMDTAG_ALTER_CAST) \ + X(CMDTAG_ALTER_COLLATION) \ + X(CMDTAG_ALTER_DEFAULT_PRIVILEGES) \ + X(CMDTAG_ALTER_DOMAIN) \ + X(CMDTAG_ALTER_EXTENSION) \ + X(CMDTAG_ALTER_FUNCTION) \ + X(CMDTAG_ALTER_MATERIALIZED_VIEW) \ + X(CMDTAG_ALTER_OPERATOR) \ + X(CMDTAG_ALTER_OPERATOR_CLASS) \ + X(CMDTAG_ALTER_OPERATOR_FAMILY) \ + X(CMDTAG_ALTER_POLICY) \ + X(CMDTAG_ALTER_PROCEDURE) \ + X(CMDTAG_ALTER_ROUTINE) \ + X(CMDTAG_ALTER_RULE) \ + X(CMDTAG_ALTER_SCHEMA) \ + X(CMDTAG_ALTER_STATISTICS) \ + X(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION) \ + X(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY) \ + X(CMDTAG_ALTER_TEXT_SEARCH_PARSER) \ + X(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE) \ + X(CMDTAG_ALTER_TRIGGER) \ + X(CMDTAG_ALTER_VIEW) \ + X(CMDTAG_DROP_ACCESS_METHOD) \ + X(CMDTAG_DROP_AGGREGATE) \ + X(CMDTAG_DROP_CAST) \ + X(CMDTAG_DROP_COLLATION) \ + X(CMDTAG_DROP_DOMAIN) \ + X(CMDTAG_DROP_EXTENSION) \ + X(CMDTAG_DROP_FOREIGN_DATA_WRAPPER) \ + X(CMDTAG_DROP_FOREIGN_TABLE) \ + X(CMDTAG_DROP_FUNCTION) \ + X(CMDTAG_DROP_OPERATOR) \ + X(CMDTAG_DROP_OPERATOR_CLASS) \ + X(CMDTAG_DROP_OPERATOR_FAMILY) \ + X(CMDTAG_DROP_OWNED) \ + X(CMDTAG_DROP_POLICY) \ + X(CMDTAG_DROP_PROCEDURE) \ + X(CMDTAG_DROP_ROUTINE) \ + X(CMDTAG_DROP_RULE) \ + X(CMDTAG_DROP_SCHEMA) \ + X(CMDTAG_DROP_SEQUENCE) \ + X(CMDTAG_DROP_SERVER) \ + X(CMDTAG_DROP_STATISTICS) \ + X(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION) \ + X(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY) \ + X(CMDTAG_DROP_TEXT_SEARCH_PARSER) \ + X(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE) \ + X(CMDTAG_DROP_TRIGGER) \ + X(CMDTAG_DROP_TYPE) \ + X(CMDTAG_DROP_USER_MAPPING) \ + X(CMDTAG_DROP_VIEW) \ + X(CMDTAG_GRANT) \ + X(CMDTAG_IMPORT_FOREIGN_SCHEMA) \ + X(CMDTAG_REVOKE) \ + X(CMDTAG_SECURITY_LABEL) + +/* Struct Definitions. */ +typedef struct YbNewRelMapEntry +{ + char *name; + char *namespace; + Oid relfile_oid; + Oid colocation_id; + bool is_index; +} YbNewRelMapEntry; + +typedef struct YbEnumLabelMapEntry +{ + Oid enum_oid; + Oid label_oid; + char *label_name; +} YbEnumLabelMapEntry; + +typedef struct YbNameToOidMapEntry +{ + char *schema; + char *name; + Oid oid; +} YbNameToOidMapEntry; + +/* Forward Declarations. */ +static bool ShouldReplicateNewRelation(Oid rel_oid, List **new_rel_list, bool is_table_rewrite); +static bool ShouldReplicateTruncatedRelation(Oid rel_oid, List **new_rel_list); + +bool +IsIndex(Relation rel) +{ + return (rel->rd_rel->relkind == RELKIND_INDEX || + rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX); +} + +bool +IsPrimaryIndex(Relation rel) +{ + return (IsIndex(rel) && rel->rd_index && rel->rd_index->indisprimary); +} + +bool +IsPassThroughDdlCommandSupported(CommandTag command_tag) +{ + switch (command_tag) + { +#define X(CMD_TAG_VALUE) case CMD_TAG_VALUE: return true; + ALLOWED_DDL_LIST +#undef X + default: + return false; + } + + return false; +} + +static bool +IsIgnoredDdlCommand(CommandTag command_tag) +{ + switch (command_tag) + { +#define X(CMD_TAG_VALUE) case CMD_TAG_VALUE: return true; + IGNORED_DDL_LIST +#undef X + default: + return false; + } + + return false; +} + +bool +IsPassThroughDdlSupported(const char *command_tag_name) +{ + if (command_tag_name == NULL || *command_tag_name == '\0') + return false; + + CommandTag command_tag = GetCommandTagEnum(command_tag_name); + + return IsPassThroughDdlCommandSupported(command_tag); +} + +static bool +IsSequence(Oid rel_oid) +{ + Relation rel = RelationIdGetRelation(rel_oid); + + if (!rel) + elog(ERROR, "Could not find relation with OID %d", rel_oid); + + const char relkind = rel->rd_rel->relkind; + + RelationClose(rel); + return relkind == RELKIND_SEQUENCE; +} + +void +ReplicateAssociatedIndexes(Oid rel_oid, List **new_rel_list, bool is_table_rewrite) +{ + Relation rel = RelationIdGetRelation(rel_oid); + if (!rel) + elog(ERROR, "Could not find relation with OID %u", rel_oid); + if (IsIndex(rel)) + { + /* Avoid recursion by only going table->index not also index->table. */ + RelationClose(rel); + return; + } + + List *indexes = RelationGetIndexList(rel); + ListCell *cell; + + foreach(cell, indexes) + { + Oid index_oid = lfirst_oid(cell); + + ShouldReplicateNewRelation(index_oid, new_rel_list, is_table_rewrite); + } + + RelationClose(rel); +} + +void +ReplicateInheritedRelations(Oid rel_oid, List **new_rel_list, bool is_table_rewrite) +{ + List *children = find_inheritance_children(rel_oid, NoLock); + ListCell *cell; + + foreach(cell, children) + { + Oid child_oid = lfirst_oid(cell); + + ShouldReplicateNewRelation(child_oid, new_rel_list, is_table_rewrite); + } +} + +bool +ShouldReplicateRelationHelper(Oid rel_oid, List **new_rel_list, bool is_table_rewrite, + bool include_inheritance_children) +{ + char *name; + Oid namespace_oid; + char *namespace; + Relation rel = RelationIdGetRelation(rel_oid); + if (!rel) + elog(ERROR, "Could not find relation with OID %u", rel_oid); + name = RelationGetRelationName(rel); + namespace_oid = RelationGetNamespace(rel); + namespace = get_namespace_name(namespace_oid); + + /* Ignore temporary tables. */ + if (!IsYBBackedRelation(rel)) + { + RelationClose(rel); + return false; + } + /* Primary indexes are YB-backed, but don't have table properties. */ + if (IsPrimaryIndex(rel)) + { + RelationClose(rel); + return true; + } + + /* + * Add the new relation to the list of relations to replicate if not already + * present. + */ + ListCell *cell; + bool found = false; + foreach (cell, *new_rel_list) + { + YbNewRelMapEntry *existing_entry = (YbNewRelMapEntry *) lfirst(cell); + if (!strcmp(existing_entry->name, name) && + !strcmp(existing_entry->namespace, namespace)) + { + found = true; + break; + } + } + if (!found) + { + YbNewRelMapEntry *new_rel_entry = palloc(sizeof(struct YbNewRelMapEntry)); + + new_rel_entry->name = pstrdup(RelationGetRelationName(rel)); + new_rel_entry->namespace = pstrdup(namespace); + new_rel_entry->relfile_oid = YbGetRelfileNodeId(rel); + new_rel_entry->colocation_id = GetColocationIdFromRelation(&rel, is_table_rewrite); + new_rel_entry->is_index = IsIndex(rel); + + *new_rel_list = lappend(*new_rel_list, new_rel_entry); + } + + RelationClose(rel); + + ReplicateAssociatedIndexes(rel_oid, new_rel_list, is_table_rewrite); + + if (include_inheritance_children) + ReplicateInheritedRelations(rel_oid, new_rel_list, is_table_rewrite); + + return true; +} + +/* + * This function handles both new relation from create table/index, + * and also new relations as a result of table rewrites. + * Returns whether the relation should be replicated (eg false if + * table is a temp table or a primary key index). + * + * This function does not handle sequences. + */ +bool +ShouldReplicateNewRelation(Oid rel_oid, List **new_rel_list, + bool is_table_rewrite) +{ + return ShouldReplicateRelationHelper(rel_oid, new_rel_list, is_table_rewrite, + true /* include_inheritance_children */ ); +} + +/* + * This function handles TRUNCATE TABLE. + * Returns whether the relation should be replicated (eg false if + * table is a temp table or a primary key index). + * Child relations are explicitly included in the relation list of a + * TRUNCATE DDL, so do not include them again. + */ +bool +ShouldReplicateTruncatedRelation(Oid rel_oid, List **new_rel_list) +{ + return ShouldReplicateRelationHelper(rel_oid, new_rel_list, + true /* is_table_rewrite */ , + false /* include_inheritance_children */ ); +} + +void +HandleAlterColumnAddIndex(AlterTableCmd *subcmd, Oid rel_oid, + List **new_rel_list) +{ + /* Need to fetch the index oid from the index name. */ + IndexStmt *index = (IndexStmt *) subcmd->def; + + /* Skip primary keys for YB. */ + if (index->primary) + return; + + if (index->idxname == NULL) + { + /* + * Default name is being used. It is difficult for us to get the name + * that did end up getting picked for this index. In this case, we + * will just collect info for all indexes on the table. + */ + Relation rel = RelationIdGetRelation(rel_oid); + List *indexes = RelationGetIndexList(rel); + + RelationClose(rel); + ListCell *cell; + + foreach(cell, indexes) + { + Oid index_oid = lfirst_oid(cell); + + ShouldReplicateNewRelation(index_oid, new_rel_list, /* is_table_rewrite= */ false); + } + + list_free(indexes); + return; + } + + /* We do have the index name, so can use that. */ + Relation rel = RelationIdGetRelation(rel_oid); + Oid index_oid = get_relname_relid(index->idxname, + RelationGetNamespace(rel)); + + RelationClose(rel); + + if (index_oid == InvalidOid) + elog(ERROR, + "Could not find index with name %s for parent relation %d", + index->idxname, + rel_oid); + + /* Once we have the OID, we can capture its info. */ + ShouldReplicateNewRelation(index_oid, new_rel_list, /* is_table_rewrite= */ false); +} + +void +CheckAlterColumnTypeDDL(Oid rel_oid, CollectedCommand *cmd, List **new_rel_list, + bool is_table_rewrite, bool is_temporary_object) +{ + /* Ignore temp objects. */ + if (is_temporary_object) + return; + + if (cmd && cmd->type == SCT_AlterTable && cmd->d.alterTable.subcmds) + { + ListCell *cell; + + foreach(cell, cmd->d.alterTable.subcmds) + { + AlterTableCmd *subcmd = castNode(AlterTableCmd, + ((CollectedATSubcmd *) lfirst(cell))->parsetree); + + switch (subcmd->subtype) + { + case AT_AddIndex: + case AT_ReAddIndex: + { + HandleAlterColumnAddIndex(subcmd, rel_oid, new_rel_list); + break; + } + default: + break; + } + } + } +} + +void +ProcessRewrittenIndexes(Oid rel_oid, const char *schema_name, List **new_rel_list) +{ + Relation rewritten_table = RelationIdGetRelation(rel_oid); + + if (!rewritten_table) + elog(ERROR, "Could not find relation with oid %d", rel_oid); + + char *rewritten_table_name = pstrdup(RelationGetRelationName(rewritten_table)); + + RelationClose(rewritten_table); + + StringInfoData query_buf; + + initStringInfo(&query_buf); + appendStringInfo(&query_buf, + "SELECT c.oid FROM pg_class c " + "JOIN pg_indexes i ON c.relname = i.indexname " + "WHERE i.tablename = %s AND i.schemaname = %s;", + quote_literal_cstr(rewritten_table_name), + quote_literal_cstr(schema_name)); + + /* + * Preserve current state of SPI_processed and SPI_tuptable because they + * will be overwritten by the next SPI_execute call. This ensures that + * caller's expected state remains unchanged. + */ + int saved_processed = SPI_processed; + SPITupleTable *saved_tuptable = SPI_tuptable; + + int exec_res = SPI_execute(query_buf.data, /* readonly */ true, /* tcount */ 0); + + if (exec_res != SPI_OK_SELECT) + elog(ERROR, "SPI_exec failed (error %d): %s", exec_res, query_buf.data); + + for (int i = 0; i < SPI_processed; i++) + { + HeapTuple spi_tuple = SPI_tuptable->vals[i]; + Oid rewritten_index_oid = SPI_GetOid(spi_tuple, 1); + + ShouldReplicateNewRelation(rewritten_index_oid, new_rel_list, /* is_table_rewrite */ true); + } + + SPI_processed = saved_processed; + SPI_tuptable = saved_tuptable; +} + +void +ProcessNewRelationsList(JsonbParseState *state, List **rel_list) +{ + if (!*rel_list) + return; + + /* Add the extra context to the JSON output. */ + AddJsonKey(state, "new_rel_map"); + (void) pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + + ListCell *l; + + foreach(l, *rel_list) + { + YbNewRelMapEntry *entry = (YbNewRelMapEntry *) lfirst(l); + + (void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + AddStringJsonEntry(state, "rel_name", entry->name); + AddStringJsonEntry(state, "rel_namespace", entry->namespace); + AddNumericJsonEntry(state, "relfile_oid", entry->relfile_oid); + if (entry->colocation_id) + AddNumericJsonEntry(state, "colocation_id", entry->colocation_id); + if (entry->is_index) + AddBoolJsonEntry(state, "is_index", true); + (void) pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + pfree(entry->name); + pfree(entry); + } + + (void) pushJsonbValue(&state, WJB_END_ARRAY, NULL); +} + +bool +ShouldReplicateAlterReplication(Oid rel_oid) +{ + Relation rel = RelationIdGetRelation(rel_oid); + + if (!rel) + elog(ERROR, "Could not find relation with oid %d", rel_oid); + /* Ignore temporary tables. */ + if (!IsYBBackedRelation(rel)) + { + RelationClose(rel); + return false; + } + + RelationClose(rel); + return true; +} + +static void +GetEnumLabels(Oid enum_oid, List **enum_label_list) +{ + StringInfoData query; + + initStringInfo(&query); + appendStringInfo(&query, + "SELECT enumlabel, oid FROM pg_catalog.pg_enum WHERE " + "enumtypid = %u", + enum_oid); + int exec_result = SPI_execute(query.data, /* readonly */ true, /* tcount */ 0); + + if (exec_result != SPI_OK_SELECT) + elog(ERROR, "SPI_exec failed (error %d): %s", exec_result, query.data); + pfree(query.data); + + for (int i = 0; i < SPI_processed; i++) + { + HeapTuple tuple = SPI_tuptable->vals[i]; + TupleDesc tupdesc = SPI_tuptable->tupdesc; + + YbEnumLabelMapEntry *enum_label_entry = + palloc(sizeof(YbEnumLabelMapEntry)); + + enum_label_entry->enum_oid = enum_oid; + enum_label_entry->label_name = + SPI_GetText(tuple, SPI_fnumber(tupdesc, "enumlabel")); + enum_label_entry->label_oid = + SPI_GetOid(tuple, SPI_fnumber(tupdesc, "oid")); + *enum_label_list = lappend(*enum_label_list, enum_label_entry); + } +} + +static void +AddNameToOidInfo(char *schema, char *name, Oid oid, + List **name_to_oid_info_list) +{ + YbNameToOidMapEntry *name_to_oid_info_entry = + palloc(sizeof(YbNameToOidMapEntry)); + + name_to_oid_info_entry->name = name; + name_to_oid_info_entry->schema = pstrdup(schema); + name_to_oid_info_entry->oid = oid; + *name_to_oid_info_list = lappend(*name_to_oid_info_list, + name_to_oid_info_entry); +} + +static void +AddSequenceInfo(Oid pg_class_oid, char *schema, List **sequence_info_list) +{ + char *name = get_rel_name(pg_class_oid); + + if (!name) + elog(ERROR, "Unable to find name of sequence with pg_class OID %u", + pg_class_oid); + if (!schema) + elog(ERROR, "Schema of sequence with pg_class OID %u unknown", + pg_class_oid); + AddNameToOidInfo(schema, name, pg_class_oid, sequence_info_list); +} + +static void +AddTypeInfo(Oid pg_type_oid, char *schema, List **type_info_list) +{ + char *name = get_typname(pg_type_oid); + + if (!name) + elog(ERROR, "Unable to find name of type with pg_type OID %u", + pg_type_oid); + if (!schema) + elog(ERROR, "Schema of type with pg_type OID %u unknown", + pg_type_oid); + AddNameToOidInfo(schema, name, pg_type_oid, type_info_list); +} + +typedef struct YbCommandInfo +{ + Oid class_id; + Oid oid; + char *command_tag_name; + char *schema; /* may be NULL */ + CollectedCommand *command; +} YbCommandInfo; + +static int +GetSourceEventTriggerDDLCommands(YbCommandInfo **info_array_out) +{ + StringInfoData query_buf; + + initStringInfo(&query_buf); + appendStringInfo(&query_buf, + "SELECT classid, objid, command_tag, schema_name, command " + "FROM pg_catalog.pg_event_trigger_ddl_commands()"); + int exec_res = SPI_execute(query_buf.data, + true, /* readonly */ + 0); /* tcount */ + + if (exec_res != SPI_OK_SELECT) + elog(ERROR, "SPI_exec failed (error %d): %s", exec_res, query_buf.data); + + YbCommandInfo *info_array = palloc(SPI_processed * sizeof(YbCommandInfo)); + int num_of_rows = SPI_processed; + + for (int row = 0; row < num_of_rows; row++) + { + HeapTuple spi_tuple = SPI_tuptable->vals[row]; + YbCommandInfo *info = &info_array[row]; + + info->command_tag_name = + SPI_GetText(spi_tuple, DDL_END_COMMAND_TAG_COLUMN_ID); + CommandTag command_tag = GetCommandTagEnum(info->command_tag_name); + + /* + * Only commands that don't have an oid are GRANT, REVOKE, and + * ALTER DEFAULT PRIVILEGES. + */ + if (command_tag != CMDTAG_GRANT && command_tag != CMDTAG_REVOKE + && command_tag != CMDTAG_ALTER_DEFAULT_PRIVILEGES) + { + info->class_id = SPI_GetOid(spi_tuple, DDL_END_CLASSID_COLUMN_ID); + info->oid = SPI_GetOid(spi_tuple, DDL_END_OBJID_COLUMN_ID); + } + else + { + info->class_id = InvalidOid; + info->oid = InvalidOid; + } + info->schema = SPI_GetText(spi_tuple, DDL_END_SCHEMA_NAME_COLUMN_ID); + info->command = GetCollectedCommand(spi_tuple, DDL_END_COMMAND_COLUMN_ID); + } + + *info_array_out = info_array; + return num_of_rows; +} + +void +PushEnumLabelMap(JsonbParseState *state, char *map_key, + List *enum_label_list) +{ + if (!enum_label_list) + return; + + /*---------- + * Add the enum_label_list to the JSON output. We use a flat array of + * entries because JSON doesn't allow maps on composite values. + * + * If two entries have the same enum and label OIDs, then the + * remaining fields are guaranteed to be the same. + *---------- + */ + AddJsonKey(state, map_key); + (void) pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + + ListCell *l; + + foreach(l, enum_label_list) + { + YbEnumLabelMapEntry *entry = (YbEnumLabelMapEntry *) lfirst(l); + + (void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + AddNumericJsonEntry(state, "enum_oid", entry->enum_oid); + AddStringJsonEntry(state, "label", entry->label_name); + AddNumericJsonEntry(state, "label_oid", entry->label_oid); + (void) pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + pfree(entry->label_name); + pfree(entry); + } + + (void) pushJsonbValue(&state, WJB_END_ARRAY, NULL); +} + +void +PushNameToOidMap(JsonbParseState *state, char *map_key, + List *name_to_oid_info_list) +{ + if (!name_to_oid_info_list) + return; + + /*---------- + * Add the name_to_oid_info_list to the JSON output. We use a flat array + * of entries because JSON doesn't allow maps on composite values. + * + * If two entries have the same schema and name, then the oid field is + * guaranteed to be the same. + *---------- + */ + AddJsonKey(state, map_key); + (void) pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + + ListCell *l; + + foreach(l, name_to_oid_info_list) + { + YbNameToOidMapEntry *entry = (YbNameToOidMapEntry *) lfirst(l); + + (void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + AddStringJsonEntry(state, "schema", entry->schema); + AddStringJsonEntry(state, "name", entry->name); + AddNumericJsonEntry(state, "oid", entry->oid); + (void) pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + pfree(entry->schema); + pfree(entry->name); + pfree(entry); + } + + (void) pushJsonbValue(&state, WJB_END_ARRAY, NULL); +} + +void +PushVariable(JsonbParseState *state, char *guc_name) +{ + char *value = GetConfigOptionByName(guc_name, NULL, false); + if (!value) + return; + + AddStringJsonEntry(state, guc_name, value); +} + +void +PushVariableMap(JsonbParseState *state) +{ + AddJsonKey(state, "variables"); + (void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + /*---------- + * To maximize safety, we always record the session variables. + * + * This protects us against the source and target clusters having + * different defaults and changes of which DDLs use these variables. + *---------- + */ +#define X(name) PushVariable(state, name); +#include "xcluster_ddl_replication_gucs.def" +#undef X + + (void) pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} + +bool +ProcessSourceEventTriggerDDLCommands(JsonbParseState *state) +{ + /* + * First copy the command information that we get by using SPI_execute so + * we can use SPI_execute while processing each command. (Each + * SPI_execute call invalidates the results of the previous one; trying to + * avoid this by using SPI_connect would force us into a different memory + * context, which would be inconvenient .) + */ + YbCommandInfo *info_array; + int num_of_rows = GetSourceEventTriggerDDLCommands(&info_array); + + List *new_rel_list = NIL; + List *enum_label_list = NIL; + List *sequence_info_list = NIL; + List *type_info_list = NIL; + bool found_temp = false; + + /* + * As long as there is at least one command that needs to be replicated, we + * will set this to true and replicate the entire query string. + */ + bool should_replicate_ddl = false; + + for (int row = 0; row < num_of_rows; row++) + { + YbCommandInfo *info = &info_array[row]; + Oid obj_id = info->oid; + const char *command_tag_name = info->command_tag_name; + CommandTag command_tag = GetCommandTagEnum(info->command_tag_name); + char *schema = info->schema; + + bool is_temporary_object = IsTempSchema(schema); + /* + * The above works for most objects, but in a few cases Postgres does + * not provide the schema; we thus have to handle those separately here. + */ + if (info->class_id == PolicyRelationId) + is_temporary_object = IsTemporaryPolicy(info->oid); + else if (info->class_id == TriggerRelationId) + is_temporary_object = IsTemporaryTrigger(info->oid); + else if (info->class_id == RewriteRelationId) + is_temporary_object = IsTemporaryRule(info->oid); + found_temp |= is_temporary_object; + + if (command_tag == CMDTAG_CREATE_TABLE || + command_tag == CMDTAG_CREATE_INDEX || + command_tag == CMDTAG_CREATE_TABLE_AS || + command_tag == CMDTAG_SELECT_INTO || + command_tag == CMDTAG_CREATE_MATERIALIZED_VIEW) + { + should_replicate_ddl |= + ShouldReplicateNewRelation(obj_id, &new_rel_list, /* is_table_rewrite */ false); + } + else if (command_tag == CMDTAG_CREATE_TYPE || + command_tag == CMDTAG_ALTER_TYPE) + { + if (type_is_enum(obj_id)) + GetEnumLabels(obj_id, &enum_label_list); + AddTypeInfo(obj_id, schema, &type_info_list); + should_replicate_ddl |= true; + } + else if (command_tag == CMDTAG_CREATE_SEQUENCE || + command_tag == CMDTAG_ALTER_SEQUENCE) + { + AddSequenceInfo(obj_id, schema, &sequence_info_list); + should_replicate_ddl |= !is_temporary_object; + } + else if (command_tag == CMDTAG_ALTER_TABLE && + IsSequence(obj_id)) + { + /* + * This is one of: + * - ALTER TABLE OWNER TO ... + * - ALTER TABLE RENAME TO ... + * - ALTER TABLE SET SCHEMA ... + * + * None of which change sequence metadata (i.e., pg_sequence) or + * data. + */ + should_replicate_ddl |= !is_temporary_object; + } + else if (command_tag == CMDTAG_ALTER_TABLE && + list_member_oid(rewritten_table_oid_list, obj_id)) + { + rewritten_table_oid_list = list_delete_oid(rewritten_table_oid_list, obj_id); + + /* + * We need to also add the rewritten table to the new_rel_list, + * because in case of a table rewrite, the associated old DocDB + * table is discarded and a new one is created, and the relfile_oid + * of the table is updated to reference this new DocDB table, + */ + should_replicate_ddl |= + ShouldReplicateNewRelation(obj_id, &new_rel_list, /* is_table_rewrite */ true); + + /* + * Add all indexes that are associated with this table, as table + * rewrite will also rewrite all dependent index tables. + */ + const char *schema_name = info->schema; + + ProcessRewrittenIndexes(obj_id, schema_name, &new_rel_list); + } + else if (command_tag == CMDTAG_ALTER_TABLE || + command_tag == CMDTAG_ALTER_INDEX) + { + /* Perform additional checks on subcommands. */ + CollectedCommand *cmd = info->command; + + CheckAlterColumnTypeDDL(obj_id, cmd, &new_rel_list, + /* is_table_rewrite */ false, + is_temporary_object); + + should_replicate_ddl |= ShouldReplicateAlterReplication(obj_id); + } + else if (command_tag == CMDTAG_TRUNCATE_TABLE) + { + if (!is_temporary_object) + should_replicate_ddl |= + ShouldReplicateTruncatedRelation(obj_id, &new_rel_list); + } + else if (command_tag == CMDTAG_REFRESH_MATERIALIZED_VIEW) + { + /* Refresh is a table rewrite. */ + should_replicate_ddl |= + ShouldReplicateNewRelation(obj_id, &new_rel_list, + /* is_table_rewrite= */ true); + const char *schema_name = info->schema; + ProcessRewrittenIndexes(obj_id, schema_name, &new_rel_list); + } + else if (IsPassThroughDdlSupported(command_tag_name)) + { + should_replicate_ddl = !is_temporary_object; + } + else if (IsIgnoredDdlCommand(command_tag)) + { + continue; + } + else + { + elog(ERROR, "Unsupported DDL: %s\n%s", command_tag_name, + kManualReplicationErrorMsg); + } + } + + if (should_replicate_ddl && found_temp) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported mix of temporary and permanent objects"), + errdetail("This database is being replicated using xCluster " + "automatic mode, which does not support DDLs " + "that simultaneously use both temporary and " + "permanent objects."), + errhint("Using multiple commands, manipulate the temporary " + "objects separately from the permanent ones."))); + } + + ProcessNewRelationsList(state, &new_rel_list); + + /* + * Add non-empty OID assignment maps to JSON. + */ + PushEnumLabelMap(state, "enum_label_info", enum_label_list); + PushNameToOidMap(state, "sequence_info", sequence_info_list); + PushNameToOidMap(state, "type_info", type_info_list); + + /* + * Record session variables that are known to meaningfully affect the DDL + * execution. + */ + PushVariableMap(state); + + return should_replicate_ddl; +} + +void +ProcessSourceEventTriggerTableRewrite() +{ + StringInfoData query_buf; + + initStringInfo(&query_buf); + appendStringInfo(&query_buf, "SELECT pg_event_trigger_table_rewrite_oid()"); + int exec_res = SPI_execute(query_buf.data, /* readonly */ true, /* tcount */ 0); + + if (exec_res != SPI_OK_SELECT) + elog(ERROR, "SPI_exec failed (error %d): %s", exec_res, query_buf.data); + + HeapTuple spi_tuple = SPI_tuptable->vals[0]; + Oid rewritten_table_oid = SPI_GetOid(spi_tuple, TABLE_REWRITE_OBJID_COLUMN_ID); + + /* This event trigger doesn't trigger on parent tables, so need to check. */ + Oid parent_table_oid = InvalidOid; + + if (has_superclass(rewritten_table_oid)) + parent_table_oid = get_partition_parent(rewritten_table_oid, + /* even_if_detached= */ true); + + /* + * Add the rewritten table to the list of rewritten tables, so that ddl end + * event triggers can process the rewritten table. + */ + MemoryContext oldcontext = MemoryContextSwitchTo(TopMemoryContext); + + rewritten_table_oid_list = lappend_oid(rewritten_table_oid_list, rewritten_table_oid); + if (parent_table_oid != InvalidOid) + rewritten_table_oid_list = lappend_oid(rewritten_table_oid_list, parent_table_oid); + MemoryContextSwitchTo(oldcontext); +} + +bool +ProcessSourceEventTriggerDroppedObjects(CommandTag tag) +{ + StringInfoData query_buf; + + initStringInfo(&query_buf); + appendStringInfo(&query_buf, "SELECT classid, is_temporary, object_type, " + "schema_name, object_name, address_names FROM " + "pg_catalog.pg_event_trigger_dropped_objects()"); + int exec_res = + SPI_execute(query_buf.data, /* readonly */ true, /* tcount */ 0); + + if (exec_res != SPI_OK_SELECT) + elog(ERROR, "SPI_exec failed (error %d): %s", exec_res, query_buf.data); + + bool found_temp = false; + /* + * As long as there is at least one command that needs to be replicated, we + * will set this to true and replicate the entire query string. + */ + bool should_replicate_ddl = false; + + for (int row = 0; row < SPI_processed; row++) + { + HeapTuple spi_tuple = SPI_tuptable->vals[row]; + Oid class_id = SPI_GetOid(spi_tuple, SQL_DROP_CLASS_ID_COLUMN_ID); + char *first_address_name = SPI_TextArrayGetElement(spi_tuple, + SQL_DROP_ADDRESS_NAMES_COLUMN_ID, 0); + + /* The following works for named objects */ + bool is_temp = SPI_GetBool(spi_tuple, SQL_DROP_IS_TEMP_COLUMN_ID); + + /* And following works for unnamed objects */ + if (first_address_name && IsTempSchema(first_address_name)) + is_temp = true; + + if (is_temp) + { + found_temp = true; + /* Postgres does not support temporary materialized views. */ + continue; + } + + switch (class_id) + { + case AccessMethodRelationId: + case AccessMethodOperatorRelationId: + case AccessMethodProcedureRelationId: + case AttrDefaultRelationId: + case CastRelationId: + case CollationRelationId: + case ConstraintRelationId: + case ConversionRelationId: + case ExtensionRelationId: + case ForeignDataWrapperRelationId: + case ForeignServerRelationId: + case ForeignTableRelationId: + case NamespaceRelationId: + case OperatorClassRelationId: + case OperatorFamilyRelationId: + case OperatorRelationId: + case PolicyRelationId: + case ProcedureRelationId: + case RelationRelationId: + case RewriteRelationId: + case StatisticExtRelationId: + case TriggerRelationId: + case TSConfigRelationId: + case TSConfigMapRelationId: + case TSDictionaryRelationId: + case TSParserRelationId: + case TSTemplateRelationId: + case TypeRelationId: + case UserMappingRelationId: + should_replicate_ddl = true; + break; + /* + * Ignore logical replication objects (not replicated via xCluster). + * Note: Schema-level publications (PublicationNamespaceRelationId) + * and SUBSCRIPTION are not supported yet in YB. + */ + case PublicationRelationId: + case PublicationRelRelationId: + case PublicationNamespaceRelationId: + case SubscriptionRelationId: + case SubscriptionRelRelationId: + continue; + default: + { + const char *object_type = SPI_GetText(spi_tuple, + SQL_DROP_OBJECT_TYPE_COLUMN_ID); + const char *schema_name = SPI_GetText(spi_tuple, + SQL_DROP_SCHEMA_NAME_COLUMN_ID); + const char *object_name = SPI_GetText(spi_tuple, + SQL_DROP_OBJECT_NAME_COLUMN_ID); + + elog(ERROR, + "unsupported Drop DDL for xCluster replicated DB: %s " + "(class_id: %d), object_name: %s.%s\n%s", + object_type, class_id, schema_name, object_name, + kManualReplicationErrorMsg); + } + } + } + + if (found_temp && should_replicate_ddl) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot drop temporary and permanent objects in one command"), + errdetail("This database is being replicated using xCluster " + "automatic mode, which does not support DDLs " + "that simultaneously drop both temporary and " + "permanent objects."), + errhint("Drop the temporary objects separately from the " + "permanent ones using multiple commands."))); + + return should_replicate_ddl; +} + +void +ClearRewrittenTableOidList() +{ + list_free(rewritten_table_oid_list); + rewritten_table_oid_list = NIL; +} diff --git a/yb-extensions/yb_xcluster_ddl_replication/source_ddl_end_handler.h b/yb-extensions/yb_xcluster_ddl_replication/source_ddl_end_handler.h new file mode 100644 index 0000000000000..5e3bbc113fe40 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/source_ddl_end_handler.h @@ -0,0 +1,54 @@ +/*----------------------------------------------------------------------------- + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + *----------------------------------------------------------------------------- + */ + +#ifndef YB_XCLUSTER_DDL_REPLICATION_SOURCE_DDL_END +#define YB_XCLUSTER_DDL_REPLICATION_SOURCE_DDL_END + +#include "postgres.h" + +#include "tcop/cmdtag.h" +#include "utils/jsonb.h" + +extern bool IsPassThroughDdlCommandSupported(CommandTag command_tag); + +/* + * Iterate over pg_catalog.pg_event_trigger_ddl_commands() and process each base + * command (a single query may be composed of multiple base commands). + * + * There are three return cases for this function: + * 1. The DDL contains some unsupported command - This function throws an error + * which aborts the transaction. + * 2. The DDL is valid but does not need to be replicated (eg all the objects in + * the DDL are temp) - This function returns false. + * 3. The DDL is valid and should be replicated - This function returns true. + */ +extern bool ProcessSourceEventTriggerDDLCommands(JsonbParseState *state); + +/* + * Same as above but for pg_catalog.pg_event_trigger_dropped_objects(). + */ +extern bool ProcessSourceEventTriggerDroppedObjects(CommandTag tag); + +/* + * Retrieve and store the OID of the table that is about to be rewritten. + */ +extern void ProcessSourceEventTriggerTableRewrite(); + +extern void ClearRewrittenTableOidList(); + +#endif diff --git a/yb-extensions/yb_xcluster_ddl_replication/sql/colocated_setup.sql b/yb-extensions/yb_xcluster_ddl_replication/sql/colocated_setup.sql new file mode 100644 index 0000000000000..5064213c7b416 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/sql/colocated_setup.sql @@ -0,0 +1,3 @@ +CREATE DATABASE colocation_test colocation = true; +\c colocation_test +\i sql/setup.sql diff --git a/yb-extensions/yb_xcluster_ddl_replication/sql/create_colocated_table.sql b/yb-extensions/yb_xcluster_ddl_replication/sql/create_colocated_table.sql new file mode 100644 index 0000000000000..17be6cddff55c --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/sql/create_colocated_table.sql @@ -0,0 +1,19 @@ +\c colocation_test +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +-- Verify that temporary objects are allowed but not captured. +CREATE TEMP TABLE temp_foo(i int PRIMARY KEY); + +-- Verify that colocated tables are allowed. +CREATE TABLE coloc_foo(i int PRIMARY KEY); + +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + +-- Verify that non-colocated table is captured. +CREATE TABLE non_coloc_foo(i int PRIMARY KEY) WITH (COLOCATION = false); + +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +select * from TEST_verify_replicated_ddls(); diff --git a/yb-extensions/yb_xcluster_ddl_replication/sql/create_drop_index.sql b/yb-extensions/yb_xcluster_ddl_replication/sql/create_drop_index.sql new file mode 100644 index 0000000000000..0f4689a920307 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/sql/create_drop_index.sql @@ -0,0 +1,46 @@ +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +CREATE SCHEMA create_index; +SET search_path TO create_index; + +-- Test temp table and index. +CREATE TEMP TABLE temp_foo(i int PRIMARY KEY, a int); +CREATE INDEX foo_idx_temp on temp_foo(a); + +DROP INDEX foo_idx_temp; +DROP TABLE temp_foo; + +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + +-- Create base table. +CREATE TABLE foo(i int PRIMARY KEY, a int, b text, c int); + +-- Create indexes. +CREATE INDEX foo_idx_simple ON foo(a); + +CREATE UNIQUE INDEX foo_idx_unique ON foo(b); + +CREATE INDEX foo_idx_filtered ON foo(c ASC, a) WHERE a > c; + +-- Test that role is captured properly. +CREATE ROLE new_role SUPERUSER; +SET ROLE new_role; +CREATE INDEX foo_idx_include ON foo(lower(b)) INCLUDE (a) SPLIT INTO 2 TABLETS; +SET ROLE NONE; + +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +-- Now drop these indexes. +-- Drop two indexes by themselves. +DROP INDEX foo_idx_unique; +DROP INDEX foo_idx_filtered; + +-- Drop base table and cascade deletion of other indexes. +DROP TABLE foo; + +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +select * from public.TEST_verify_replicated_ddls(); diff --git a/yb-extensions/yb_xcluster_ddl_replication/sql/create_drop_table.sql b/yb-extensions/yb_xcluster_ddl_replication/sql/create_drop_table.sql new file mode 100644 index 0000000000000..3a041573ae0e2 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/sql/create_drop_table.sql @@ -0,0 +1,55 @@ +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +-- Verify that temporary objects are not captured. +CREATE TEMP TABLE temp_foo(i int PRIMARY KEY); +DROP TABLE temp_foo; + +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + +-- Verify that regular tables are captured. +CREATE TABLE foo(i int PRIMARY KEY); +INSERT INTO foo(i) VALUES (1), (2), (3); + +-- Check with manual replication flags enabled, ddl string is captured with flag. +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 1; +CREATE TABLE manual_foo(i int PRIMARY KEY); +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 0; + +-- Verify that extra info is captured. +CREATE TABLE extra_foo(i int PRIMARY KEY) WITH (COLOCATION = false) SPLIT INTO 1 TABLETS; + +-- Verify that info for unique constraint indexes are also captured. +CREATE TABLE unique_foo(i int PRIMARY KEY, u text UNIQUE); + +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +-- Test tables partitioned by their primary key or a column. +CREATE TABLE foo_partitioned_by_pkey(id int, PRIMARY KEY (id)) PARTITION BY RANGE (id); +CREATE TABLE foo_partitioned_by_col(id int) PARTITION BY RANGE (id); +CREATE TABLE partition1 PARTITION OF foo_partitioned_by_col FOR VALUES FROM (1) TO (10); +CREATE TABLE partition2 PARTITION OF foo_partitioned_by_col FOR VALUES FROM (10) TO (20); +INSERT INTO foo_partitioned_by_col(id) VALUES (1), (2), (3), (10), (11), (12); + +-- Test for relations that trigger nonconcurrent backfills. +CREATE INDEX NONCONCURRENTLY nonconcurrent_foo on foo(i); +CREATE INDEX NONCONCURRENTLY on foo(i); -- test without a name +ALTER TABLE foo ADD CONSTRAINT constraint_foo UNIQUE (i); +CREATE UNIQUE INDEX partitioned_index ON foo_partitioned_by_col(id); + +-- Now test dropping these tables. +DROP TABLE foo; + +-- Check with manual replication flags enabled, ddl string is captured with flag. +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 1; +DROP TABLE manual_foo; +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 0; + +DROP TABLE extra_foo; +DROP TABLE unique_foo; +DROP TABLE foo_partitioned_by_pkey; +DROP TABLE foo_partitioned_by_col; + +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; diff --git a/yb-extensions/yb_xcluster_ddl_replication/sql/materialized_views.sql b/yb-extensions/yb_xcluster_ddl_replication/sql/materialized_views.sql new file mode 100644 index 0000000000000..3310831fe9e58 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/sql/materialized_views.sql @@ -0,0 +1,49 @@ +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +CREATE TABLE products ( + id INT PRIMARY KEY, + category TEXT, + price NUMERIC +); + +CREATE MATERIALIZED VIEW product_summary AS + SELECT category, COUNT(*) as item_count + FROM products + GROUP BY category; + +COMMENT ON MATERIALIZED VIEW product_summary + IS 'Stores the total count of products per category.'; + +ALTER MATERIALIZED VIEW product_summary RENAME TO product_inventory; + +REFRESH MATERIALIZED VIEW product_inventory; + +DROP MATERIALIZED VIEW product_inventory; + +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; + + +-- Test dropping materialized views using other DROP commands +-- DROP SCHEMA +CREATE SCHEMA IF NOT EXISTS permanent_schema; +CREATE TABLE permanent_schema.my_table (id INT); +CREATE MATERIALIZED VIEW permanent_schema.my_mview AS SELECT 'hello' AS greeting; +DROP SCHEMA permanent_schema CASCADE; + +-- DROP ROLE +CREATE ROLE drop_me_role; +SET ROLE drop_me_role; +CREATE TEMP TABLE my_temp_object (id INT); +CREATE MATERIALIZED VIEW my_mview AS SELECT 'hello' AS greeting; +RESET ROLE; +DROP OWNED BY drop_me_role; -- fail due to temp object + +SET ROLE drop_me_role; +DROP TABLE my_temp_object; +CREATE TABLE real_table (id INT); +RESET ROLE; +DROP OWNED BY drop_me_role; -- should succeed now + +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +select * from TEST_verify_replicated_ddls(); diff --git a/yb-extensions/yb_xcluster_ddl_replication/sql/mixed_temporary_and_persistent.sql b/yb-extensions/yb_xcluster_ddl_replication/sql/mixed_temporary_and_persistent.sql new file mode 100644 index 0000000000000..d5cf92966cace --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/sql/mixed_temporary_and_persistent.sql @@ -0,0 +1,13 @@ +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + + +-- Test dropping mix of temp and regular tables. +CREATE TEMP TABLE temp_foo(i int PRIMARY KEY); +CREATE TABLE foo(i int PRIMARY KEY); +DROP TABLE temp_foo, foo; -- should fail +DROP TABLE foo, temp_foo; -- should fail +DROP TABLE temp_foo; +DROP TABLE foo; + +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; diff --git a/yb-extensions/yb_xcluster_ddl_replication/sql/routines.sql b/yb-extensions/yb_xcluster_ddl_replication/sql/routines.sql new file mode 100644 index 0000000000000..a9cfa9241f99c --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/sql/routines.sql @@ -0,0 +1,53 @@ +-- Check connections start in role not_automatic_mode. +SELECT yb_xcluster_ddl_replication.get_replication_role(); + + +-- Check can override with every possible role. +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'unspecified'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'unavailable'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'not_automatic_mode'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'automatic_source'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'automatic_target'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +-- Shortcuts for automatic roles. +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'source'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'target'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + + +-- Check for invalid roles. +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'invalid'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + + + +-- Check we can turn off override. +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'source'; +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'none'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'target'; +SET yb_xcluster_ddl_replication.TEST_replication_role_override = ''; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + + +-- Check override cannot be called if you are not superuser but +-- get_replication_role can. +SET SESSION AUTHORIZATION testuser; +SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'target'; +SELECT yb_xcluster_ddl_replication.get_replication_role(); + + +-- Check no select access to tables. +SELECT * FROM yb_xcluster_ddl_replication.ddl_queue; diff --git a/yb-extensions/yb_xcluster_ddl_replication/sql/setup.sql b/yb-extensions/yb_xcluster_ddl_replication/sql/setup.sql new file mode 100644 index 0000000000000..bfc6f8ca59ae6 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/sql/setup.sql @@ -0,0 +1,69 @@ +-- Initial test setup. +CREATE EXTENSION yb_xcluster_ddl_replication; + +-- workaround for lack of CREATE ROLE IF NOT EXISTS +DO $$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'testuser') THEN + CREATE ROLE testuser LOGIN; + END IF; +END +$$; + +-- Setup function, to be called at the top of each test file. +CREATE PROCEDURE TEST_reset() + LANGUAGE SQL AS +$$ + SET yb_xcluster_ddl_replication.TEST_replication_role_override = 'source'; + DELETE FROM yb_xcluster_ddl_replication.ddl_queue; + DELETE FROM yb_xcluster_ddl_replication.replicated_ddls; +$$; + +-- Verify that both tables have the same keys. +CREATE OR REPLACE FUNCTION TEST_verify_replicated_ddls() + RETURNS boolean + LANGUAGE SQL AS +$$ + SELECT (count(1) = 0) FROM yb_xcluster_ddl_replication.ddl_queue a + FULL OUTER JOIN yb_xcluster_ddl_replication.replicated_ddls b + USING (ddl_end_time, query_id) + WHERE a.yb_data IS NULL OR b.yb_data IS NULL; +$$; + +-- Select from ddl_queue, but redact any oids / colocation ids. +CREATE OR REPLACE FUNCTION TEST_filtered_ddl_queue() + RETURNS SETOF yb_xcluster_ddl_replication.ddl_queue + LANGUAGE plpgsql AS +$$ +DECLARE + rec RECORD; + new_array JSONB; + element JSONB; + k TEXT; + v TEXT; +BEGIN + for rec in + select * from yb_xcluster_ddl_replication.ddl_queue + loop + if rec.yb_data->'new_rel_map' is null then + return next rec; + continue; + end if; + new_array := '[]'::jsonb; + for element in + select * from jsonb_array_elements(rec.yb_data->'new_rel_map') + loop + for k, v in + select * from jsonb_each_text(element) + loop + if k = 'relfile_oid' or k = 'colocation_id' then + element := jsonb_set(element, ('{' || k || '}')::text[], '"***"'); + end if; + end loop; + new_array := new_array || element; + end loop; + rec.yb_data := jsonb_set(rec.yb_data, '{new_rel_map}', new_array); + return next rec; + end loop; +END +$$; diff --git a/yb-extensions/yb_xcluster_ddl_replication/sql/table_rewrite.sql b/yb-extensions/yb_xcluster_ddl_replication/sql/table_rewrite.sql new file mode 100644 index 0000000000000..c0a88d89fa8d1 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/sql/table_rewrite.sql @@ -0,0 +1,145 @@ +CALL TEST_reset(); +SELECT yb_data FROM TEST_filtered_ddl_queue(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +-- Test 1: ADD and DROP PRIMARY KEY +CREATE TABLE base_table(a int, b int); +CREATE INDEX idx ON base_table(b ASC); +INSERT INTO base_table SELECT i, i%2 FROM generate_series(1, 100) as i; + +-- triggers table rewrite +ALTER TABLE base_table ADD PRIMARY KEY (a ASC); + +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +-- triggers table rewrite +ALTER TABLE base_table DROP CONSTRAINT base_table_pkey; + +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +DROP TABLE base_table; + +-- Test 2: ADD COLUMN with PRIMARY KEY +CALL TEST_reset(); +CREATE TABLE base_table2(a int, b int); +CREATE INDEX idx2 ON base_table2(b ASC); + +-- triggers table rewrite +ALTER TABLE base_table2 ADD COLUMN c int PRIMARY KEY; + +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +DROP TABLE base_table2; + +-- Test 3: ADD COLUMN with DEFAULT volatile expression +CALL TEST_reset(); +CREATE TABLE base_table3(a int, b int); +CREATE INDEX idx3 ON base_table3(b ASC); +INSERT INTO base_table3 SELECT i, i%2 FROM generate_series(1, 100) as i; + +-- triggers table rewrite +ALTER TABLE base_table3 ADD COLUMN created_at TIMESTAMP DEFAULT clock_timestamp() NOT NULL; + +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +DROP TABLE base_table3; + +-- Test 4: ADD COLUMN GENERATED ALWAYS AS IDENTITY +CALL TEST_reset(); +CREATE TABLE base_table4(a int, b int); +CREATE INDEX idx4 ON base_table4(b ASC); +INSERT INTO base_table4 SELECT i, i%2 FROM generate_series(1, 100) as i; + +-- triggers table rewrite +ALTER TABLE base_table4 ADD COLUMN new_id INT GENERATED ALWAYS AS IDENTITY; + +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +DROP TABLE base_table4; + +-- Test 5: ALTER COLUMN TYPE on indexed column +CALL TEST_reset(); +CREATE TABLE base_table5(a int, b int); +CREATE INDEX idx5 ON base_table5(b ASC); +INSERT INTO base_table5 SELECT i, i%2 FROM generate_series(1, 100) as i; + +-- triggers table rewrite +ALTER TABLE base_table5 ALTER COLUMN b TYPE float USING(random()); + +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +DROP TABLE base_table5; + +-- Test 6: ALTER COLUMN TYPE on non-indexed column +CALL TEST_reset(); +CREATE TABLE base_table6(a int, b int); +CREATE INDEX idx6 ON base_table6(b ASC); +INSERT INTO base_table6 SELECT i, i%2 FROM generate_series(1, 100) as i; + +-- triggers table rewrite +ALTER TABLE base_table6 ALTER COLUMN a TYPE float USING(random()); + +-- verify the table rewrite are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +DROP TABLE base_table6; + +-- Test 7: ALTER COLUMN TYPE with dependent index (no table rewrite and reindex, index rebuild only) +-- Test 7a: varchar to text +CALL TEST_reset(); +CREATE TABLE t1(a varchar); +INSERT INTO t1(a) SELECT 'row_' || i FROM generate_series(1, 100) as i; +CREATE INDEX idx1 ON t1(a); + +-- trigger index rebuild +ALTER TABLE t1 ALTER COLUMN a TYPE text; + +-- Verify the index recreation are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +-- Test 7b: changing collation +CALL TEST_reset(); +CREATE TABLE t2(a text COLLATE "C"); +INSERT INTO t2(a) SELECT 'row_' || i FROM generate_series(1, 50) as i; +CREATE INDEX idx2 ON t2(a); + +-- trigger index rebuild +ALTER TABLE t2 ALTER COLUMN a TYPE text COLLATE "en_US"; + +-- Verify the index recreation are captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +DROP TABLE t1; +DROP TABLE t2; + +-- Test 8: multiple table rewrites with manual replication flag +CALL TEST_reset(); +CREATE TABLE manual_base(a int, b int); +INSERT INTO manual_base SELECT i, i%2 FROM generate_series(1, 50) as i; + +-- Test with manual replication flag enabled +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 1; +ALTER TABLE manual_base ADD PRIMARY KEY (a ASC); +SET yb_xcluster_ddl_replication.enable_manual_ddl_replication = 0; + +-- Verify manual replication flag is captured +SELECT yb_data FROM TEST_filtered_ddl_queue() ORDER BY ddl_end_time; +SELECT yb_data FROM yb_xcluster_ddl_replication.replicated_ddls ORDER BY ddl_end_time; + +DROP TABLE manual_base; + diff --git a/yb-extensions/yb_xcluster_ddl_replication/sql/truncate.sql b/yb-extensions/yb_xcluster_ddl_replication/sql/truncate.sql new file mode 100644 index 0000000000000..8c5862fa80baf --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/sql/truncate.sql @@ -0,0 +1,10 @@ +CALL TEST_reset(); +SELECT yb_xcluster_ddl_replication.get_replication_role(); + +CREATE TEMP TABLE temp_foo(i int PRIMARY KEY); +CREATE TABLE foo(i int PRIMARY KEY); +TRUNCATE foo, temp_foo; -- should fail +TRUNCATE foo; +TRUNCATE temp_foo; + +SELECT yb_data FROM public.TEST_filtered_ddl_queue() ORDER BY ddl_end_time; diff --git a/yb-extensions/yb_xcluster_ddl_replication/xcluster_ddl_replication_gucs.def b/yb-extensions/yb_xcluster_ddl_replication/xcluster_ddl_replication_gucs.def new file mode 100644 index 0000000000000..92cc37ef8e8d0 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/xcluster_ddl_replication_gucs.def @@ -0,0 +1,33 @@ +/* Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/* + * List of GUC variables captured during xCluster DDL replication. + * These all need to be lowercase. + */ + +/* This affects what tablespace a table is created in. */ +X("default_tablespace") +/* This affects whether a table is range or hash started. */ +X("yb_use_hash_splitting_by_default") + +/* Settings that affect parsing of string literals. */ +X("standard_conforming_strings") +/* Settings that affect parsing of dates, times, and intervals. */ +X("datestyle") +X("timezone") +X("intervalstyle") +/* Settings that affect parsing of function bodies. */ +X("check_function_bodies") diff --git a/yb-extensions/yb_xcluster_ddl_replication/yb_schedule b/yb-extensions/yb_xcluster_ddl_replication/yb_schedule new file mode 100644 index 0000000000000..0261c2d7ecfc7 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/yb_schedule @@ -0,0 +1,15 @@ +test: setup + +test: routines + +test: create_drop_table +test: create_drop_index + +test: mixed_temporary_and_persistent +test: truncate +test: materialized_views + +test: colocated_setup +test: create_colocated_table + +test: table_rewrite diff --git a/yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication--1.0.sql b/yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication--1.0.sql new file mode 100644 index 0000000000000..b9ca6b4b3263d --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication--1.0.sql @@ -0,0 +1,107 @@ +-- Complain if script is sourced in psql, rather than via CREATE EXTENSION. +\echo Use "CREATE EXTENSION yb_xcluster_ddl_replication" to load this file \quit + +/* ------------------------------------------------------------------------- */ +/* Create extension tables. */ + +/* + * This table contains DDLs done on the source database. + * + * It has one row per DDL; older rows are cleaned up after a while. + * It is replicated from the source to target universe. + */ +CREATE TABLE yb_xcluster_ddl_replication.ddl_queue( + ddl_end_time bigint NOT NULL, + query_id bigint NOT NULL, + yb_data jsonb NOT NULL, + PRIMARY KEY (ddl_end_time ASC, query_id ASC)) WITH (COLOCATION = false); + +/* + * This table contains DDLs that have been performed on this database. + * There are separate instances of this table in the source and target + * universes, with no replication performed between them. + * + * It has one row per DDL plus one special row; older rows are cleaned + * up after a while. + * + * The DDLs that still need to be performed on the target database are + * those contained in rows in the ddl_queue table that do not have + * matching rows in this table. + * + * The special row has key (1, 1); its yb_data field contains + * information about DDL commit times (i.e., encodes a + * xcluster::SafeTimeBatch). + */ +CREATE TABLE yb_xcluster_ddl_replication.replicated_ddls( + ddl_end_time bigint NOT NULL, + query_id bigint NOT NULL, + yb_data jsonb NOT NULL, + PRIMARY KEY (ddl_end_time ASC, query_id ASC)) WITH (COLOCATION = false); + +/* ------------------------------------------------------------------------- */ +/* Create routines for user of extension. */ + +CREATE FUNCTION yb_xcluster_ddl_replication.get_replication_role() + RETURNS text + LANGUAGE C + AS 'MODULE_PATHNAME', 'get_replication_role'; + +/* ------------------------------------------------------------------------- */ +/* Create event triggers. */ + +CREATE FUNCTION yb_xcluster_ddl_replication.handle_ddl_start() + RETURNS event_trigger + LANGUAGE C + AS 'MODULE_PATHNAME', 'handle_ddl_start'; + +CREATE EVENT TRIGGER yb_xcluster_ddl_replication_handle_ddl_start_trigger + ON ddl_command_start + EXECUTE FUNCTION yb_xcluster_ddl_replication.handle_ddl_start(); + +CREATE EVENT TRIGGER yb_xcluster_ddl_replication_handle_truncate_start_trigger + ON ddl_command_start + WHEN TAG in ('TRUNCATE TABLE') + EXECUTE FUNCTION yb_xcluster_ddl_replication.handle_ddl_start(); + +CREATE FUNCTION yb_xcluster_ddl_replication.handle_ddl_end() + RETURNS event_trigger + LANGUAGE C + AS 'MODULE_PATHNAME', 'handle_ddl_end'; + +CREATE EVENT TRIGGER yb_xcluster_ddl_replication_handle_ddl_end_trigger + ON ddl_command_end + EXECUTE FUNCTION yb_xcluster_ddl_replication.handle_ddl_end(); + +CREATE EVENT TRIGGER yb_xcluster_ddl_replication_handle_truncate_end_trigger + ON ddl_command_end + WHEN TAG in ('TRUNCATE TABLE') + EXECUTE FUNCTION yb_xcluster_ddl_replication.handle_ddl_end(); + +CREATE FUNCTION yb_xcluster_ddl_replication.handle_sql_drop() + RETURNS event_trigger + LANGUAGE C + AS 'MODULE_PATHNAME', 'handle_sql_drop'; + +CREATE EVENT TRIGGER yb_xcluster_ddl_replication_handle_sql_drop_trigger + ON sql_drop + EXECUTE FUNCTION yb_xcluster_ddl_replication.handle_sql_drop(); + +CREATE FUNCTION yb_xcluster_ddl_replication.handle_table_rewrite() + RETURNS event_trigger + LANGUAGE C + AS 'MODULE_PATHNAME', 'handle_table_rewrite'; + +CREATE EVENT TRIGGER yb_xcluster_ddl_replication_handle_table_rewrite_trigger + ON table_rewrite + EXECUTE FUNCTION yb_xcluster_ddl_replication.handle_table_rewrite(); + +/* ------------------------------------------------------------------------- */ +/* Set allowed access. */ + +GRANT USAGE ON SCHEMA yb_xcluster_ddl_replication TO PUBLIC; +-- At this point access for non-super users is allowed only to functions and procedures. + +REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA yb_xcluster_ddl_replication FROM PUBLIC; +REVOKE EXECUTE ON ALL PROCEDURES IN SCHEMA yb_xcluster_ddl_replication FROM PUBLIC; + +GRANT EXECUTE ON FUNCTION yb_xcluster_ddl_replication.get_replication_role() TO PUBLIC; diff --git a/yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication.c b/yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication.c new file mode 100644 index 0000000000000..67b833b7638db --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication.c @@ -0,0 +1,818 @@ +/*----------------------------------------------------------------------------- + * Copyright (c) YugabyteDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + *----------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/pg_type_d.h" +#include "commands/event_trigger.h" +#include "commands/extension.h" +#include "executor/spi.h" +#include "extension_util.h" +#include "json_util.h" +#include "source_ddl_end_handler.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" +#include "utils/queryjumble.h" + +PG_MODULE_MAGIC; + +#define INSERT_INTO_TABLE_NUM_ARGS 3 + +/* + * Extension variables. + */ + +static bool enable_manual_ddl_replication = false; +char *ddl_queue_primary_key_ddl_end_time = NULL; +char *ddl_queue_primary_key_queue_id = NULL; + +static const struct config_enum_entry replication_role_overrides[] = { + {"", XCLUSTER_ROLE_UNSPECIFIED, /* hidden */ false}, + {"NONE", XCLUSTER_ROLE_UNSPECIFIED, /* hidden */ false}, + {"UNSPECIFIED", XCLUSTER_ROLE_UNSPECIFIED, /* hidden */ true}, + {"NOT_AUTOMATIC_MODE", XCLUSTER_ROLE_NOT_AUTOMATIC_MODE, /* hidden */ true}, + {"UNAVAILABLE", XCLUSTER_ROLE_UNAVAILABLE, /* hidden */ true}, + {"SOURCE", XCLUSTER_ROLE_AUTOMATIC_SOURCE, /* hidden */ false}, + {"AUTOMATIC_SOURCE", XCLUSTER_ROLE_AUTOMATIC_SOURCE, /* hidden */ true}, + {"TARGET", XCLUSTER_ROLE_AUTOMATIC_TARGET, /* hidden */ false}, + {"AUTOMATIC_TARGET", XCLUSTER_ROLE_AUTOMATIC_TARGET, /* hidden */ true}, +{NULL, 0, false}}; + +/* + * Call FetchReplicationRole() at the start of every DDL to fill this variable + * in before using it. + */ +static int replication_role = XCLUSTER_ROLE_UNAVAILABLE; +static int replication_role_override = XCLUSTER_ROLE_UNSPECIFIED; + +/* + * Information about the current DDL. + * The query string, location and length represent the substring of the query + * that is currently being processed. This enables to capture only the DDL part + * of a multi-statement query, or a FUNCTION with multiple statements. + */ +const char *query_string = NULL; +int query_location = 0; +int query_len = 0; +Node *parsetree = NULL; + +/* Information about an extension that is being created. */ +bool is_in_extension_ddl = false; +bool is_self_extension_ddl = false; +char *current_extension_name = NULL; + +/* + * Util functions. + */ +static void RecordTempRelationDDL(); +static void XClusterProcessUtility(PlannedStmt *pstmt, + const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, + QueryCompletion *qc); + +/* + * Per DDL Variables. + */ + +/* + * This is updated as the DDL triggers run, ending up with the decision of + * whether or not to replicate the DDL that is currently running. + * + * Once this becomes true, it remains true for the rest of the DDL. + */ +static bool yb_should_replicate_ddl = false; + +static YbcRecordTempRelationDDL_hook_type prev_YBCRecordTempRelationDDL = NULL; +static ProcessUtility_hook_type prev_ProcessUtility = NULL; + +/* + * The GUC variables `enable_manual_ddl_replication` and + * `TEST_replication_role_override` cannot be supported using connection + * manager due to how GUC variables are supported through connection manager. + * Any modifications to these variables should cause the connection to become + * sticky so as to not allow internal changes to the variables to occur while + * not servicing an active client connection. + * + * These assign hooks have no purpose if connection manager is not being used. + */ + +static void +assign_enable_manual_ddl_replication(bool newval, void *extra) +{ + if (!YbIsClientYsqlConnMgr()) + return; + elog(LOG, "Making connection sticky for setting enable_manual_ddl_replication"); + yb_ysql_conn_mgr_sticky_guc = true; +} + +static void +assign_TEST_replication_role_override(int newval, void *extra) +{ + if (!YbIsClientYsqlConnMgr()) + return; + elog(LOG, "Making connection sticky for setting TEST_replication_role_override"); + yb_ysql_conn_mgr_sticky_guc = true; +} + +/* + * _PG_init gets called when the extension is loaded. + */ +void +_PG_init(void) +{ + if (IsBinaryUpgrade) + return; + + DefineCustomBoolVariable("yb_xcluster_ddl_replication.enable_manual_ddl_replication", + gettext_noop("Temporarily disable automatic xCluster DDL replication - DDLs will have " + "to be manually executed on the target."), + gettext_noop("DDL strings will still be captured and replicated, but will be marked " + "with a 'manual_replication' flag."), + &enable_manual_ddl_replication, + false, + PGC_USERSET, + 0, + NULL, assign_enable_manual_ddl_replication, NULL); + + DefineCustomStringVariable("yb_xcluster_ddl_replication.ddl_queue_primary_key_ddl_end_time", + gettext_noop("Internal use only: Used by HandleTargetDDLEnd function."), + NULL, + &ddl_queue_primary_key_ddl_end_time, + "", + PGC_SUSET, + 0, + NULL, NULL, NULL); + + DefineCustomStringVariable("yb_xcluster_ddl_replication.ddl_queue_primary_key_query_id", + gettext_noop("Internal use only: Used by HandleTargetDDLEnd function."), + NULL, + &ddl_queue_primary_key_queue_id, + "", + PGC_SUSET, + 0, + NULL, NULL, NULL); + + DefineCustomEnumVariable("yb_xcluster_ddl_replication.TEST_replication_role_override", + gettext_noop("Test override for replication role."), + NULL, + &replication_role_override, + XCLUSTER_ROLE_UNSPECIFIED, + replication_role_overrides, + PGC_SUSET, + 0, + NULL, assign_TEST_replication_role_override, NULL); + + prev_YBCRecordTempRelationDDL = YBCRecordTempRelationDDL_hook; + YBCRecordTempRelationDDL_hook = RecordTempRelationDDL; + + prev_ProcessUtility = ProcessUtility_hook; + ProcessUtility_hook = XClusterProcessUtility; + +} + +void +FetchReplicationRole() +{ + if (replication_role_override != XCLUSTER_ROLE_UNSPECIFIED) + replication_role = replication_role_override; + else + replication_role = YBCGetXClusterRole(MyDatabaseId); + + if (replication_role == XCLUSTER_ROLE_UNAVAILABLE) + { + ereport(ERROR, + (errcode(ERRCODE_YB_ERROR), + errmsg("unable to fetch replication role"))); + } +} + +bool +IsDisabled(CommandTag command_tag) +{ + /* + * Disabled if we are not in xCluster automatic mode. + * Disabled for our own extension. + * Disabled if we are in an extension DDL and not handling the top level + * query. + */ + return (replication_role != XCLUSTER_ROLE_AUTOMATIC_SOURCE && + replication_role != XCLUSTER_ROLE_AUTOMATIC_TARGET) || + is_self_extension_ddl || + (is_in_extension_ddl && !IsExtensionDdl(command_tag)); +} + +bool +IsReplicationSource() +{ + return (replication_role == XCLUSTER_ROLE_AUTOMATIC_SOURCE); +} + +bool +IsReplicationTarget() +{ + return (replication_role == XCLUSTER_ROLE_AUTOMATIC_TARGET); +} + +PG_FUNCTION_INFO_V1(get_replication_role); +Datum +get_replication_role(PG_FUNCTION_ARGS) +{ + FetchReplicationRole(); + char *role_name; + + switch (replication_role) + { + case XCLUSTER_ROLE_UNSPECIFIED: + role_name = "unspecified"; + break; + case XCLUSTER_ROLE_UNAVAILABLE: + role_name = "unavailable"; + break; + case XCLUSTER_ROLE_NOT_AUTOMATIC_MODE: + role_name = "not_automatic_mode"; + break; + case XCLUSTER_ROLE_AUTOMATIC_SOURCE: + role_name = "source"; + break; + case XCLUSTER_ROLE_AUTOMATIC_TARGET: + role_name = "target"; + break; + default: + role_name = "unknown"; + break; + } + PG_RETURN_TEXT_P(cstring_to_text(role_name)); +} + +void +InsertIntoTable(const char *table_name, int64 ddl_end_time, int64 query_id, + Jsonb *yb_data) +{ + Oid arg_types[INSERT_INTO_TABLE_NUM_ARGS]; + Datum arg_vals[INSERT_INTO_TABLE_NUM_ARGS]; + StringInfoData query_buf; + + initStringInfo(&query_buf); + appendStringInfo(&query_buf, + "INSERT INTO %s.%s (ddl_end_time, query_id, yb_data) values " + "($1,$2,$3)", + EXTENSION_NAME, table_name); + + arg_types[0] = INT8OID; + arg_vals[0] = Int64GetDatum(ddl_end_time); + + arg_types[1] = INT8OID; + arg_vals[1] = Int64GetDatum(query_id); + + arg_types[2] = JSONBOID; + arg_vals[2] = PointerGetDatum(yb_data); + + int exec_res = SPI_execute_with_args(query_buf.data, INSERT_INTO_TABLE_NUM_ARGS, arg_types, + arg_vals, /* Nulls */ NULL, /* readonly */ false, + /* tuple-count limit */ 1); + + if (exec_res != SPI_OK_INSERT) + elog(ERROR, "SPI_exec failed (error %d): %s", exec_res, query_buf.data); +} + +void +InsertIntoReplicatedDDLs(int64 ddl_end_time, int64 query_id) +{ + /* Create memory context for handling json creation + query execution. */ + MemoryContext context_new, + context_old; + Oid save_userid; + int save_sec_context; + + INIT_MEM_CONTEXT_AND_SPI_CONNECT("yb_xcluster_ddl_replication.InsertIntoReplicatedDDLs context"); + + JsonbParseState *state = NULL; + + (void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + (void) AddNStringJsonEntry(state, "query", query_string, query_len); + JsonbValue *jsonb_val = pushJsonbValue(&state, WJB_END_OBJECT, NULL); + Jsonb *jsonb = JsonbValueToJsonb(jsonb_val); + + InsertIntoTable(REPLICATED_DDLS_TABLE_NAME, ddl_end_time, query_id, jsonb); + + CLOSE_MEM_CONTEXT_AND_SPI; +} + +void +HandleSourceDDLEnd(EventTriggerData *trig_data) +{ + /* Create memory context for handling json creation + query execution. */ + MemoryContext context_new, + context_old; + Oid save_userid; + int save_sec_context; + + INIT_MEM_CONTEXT_AND_SPI_CONNECT("yb_xcluster_ddl_replication.HandleSourceDDLEnd context"); + + /* Begin constructing json, fill common fields first. */ + JsonbParseState *state = NULL; + + (void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + (void) AddNumericJsonEntry(state, "version", 1); + (void) AddNStringJsonEntry(state, "query", query_string, query_len); + (void) AddStringJsonEntry(state, "command_tag", + GetCommandTagName(trig_data->tag)); + + const char *current_user = GetUserNameFromId(save_userid, false); + + if (current_user) + (void) AddStringJsonEntry(state, "user", current_user); + + LOCAL_FCINFO(fcinfo, 0); + InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL); + const char *cur_schema = DatumGetCString(current_schema(fcinfo)); + + if (cur_schema) + (void) AddStringJsonEntry(state, "schema", cur_schema); + + if (enable_manual_ddl_replication) + { + (void) AddBoolJsonEntry(state, "manual_replication", true); + } + else + { + yb_should_replicate_ddl |= ProcessSourceEventTriggerDDLCommands(state); + } + + if (yb_should_replicate_ddl) + { + /* Construct the jsonb and insert completed row into ddl_queue table. */ + JsonbValue *jsonb_val = pushJsonbValue(&state, WJB_END_OBJECT, NULL); + Jsonb *jsonb = JsonbValueToJsonb(jsonb_val); + + /* + * Compute the current time in micros since epoch. + * This is the ddl_end time, which is after all the docdb schema changes for + * the DDL have occurred (note that the pg catalog changes are not yet + * visible as the txn hasn't committed yet). + * Current time also give us a distinct and ordered time for DDLs within a + * transaction, as opposed to using now(), which is the same within a txn. + * + * TODO (#25999): We will also use this time as the safe time to run the DDL + * on the target, by waiting for other pollers to catch up to this safe time + * before running the DDL. Since this time is after the docdb schema is + * applied, we are fine at this point to catch up the pg catalog changes. + */ + TimestampTz epoch_time = (GetCurrentTimestamp() - SetEpochTimestamp()); + int64 query_id = random(); + + InsertIntoTable(DDL_QUEUE_TABLE_NAME, epoch_time, query_id, jsonb); + + /* + * Also insert into the replicated_ddls table to handle switchovers. + * + * During switchover, we have a middle state with A target <-> B + * target. In this state, A is polling from B, and so ddl_queue on A + * could try to process its ddl_queue entries. But since we write to + * replicated_ddls on A, the ddl_queue handler will see that all DDLs + * in the queue have been processed. + */ + InsertIntoReplicatedDDLs(epoch_time, query_id); + } + + CLOSE_MEM_CONTEXT_AND_SPI; +} + +void +HandleTargetDDLEnd(EventTriggerData *trig_data) +{ + /* Manual DDLs are not captured at all on the target. */ + if (enable_manual_ddl_replication) + return; + + /* + * DDLs on target are blocked in pg_client_session before they can modify + * the catalog, so if a user executed DDL got this far then it means this is a + * pass through DDL command. + */ + if (!yb_xcluster_automatic_mode_target_ddl) + return; + + /* + * We expect ddl_queue_primary_key_* variables to have been set earlier in + * the transaction by the ddl_queue handler. + */ + int64 pkey_ddl_end_time = GetInt64FromVariable(ddl_queue_primary_key_ddl_end_time, + "ddl_queue_primary_key_ddl_end_time"); + int64 pkey_query_id = GetInt64FromVariable(ddl_queue_primary_key_queue_id, + "ddl_queue_primary_key_query_id"); + + InsertIntoReplicatedDDLs(pkey_ddl_end_time, pkey_query_id); +} + +void +HandleSourceSQLDrop(EventTriggerData *trig_data) +{ + if (enable_manual_ddl_replication) + return; + + /* Create memory context for handling query execution. */ + MemoryContext context_new, + context_old; + Oid save_userid; + int save_sec_context; + + INIT_MEM_CONTEXT_AND_SPI_CONNECT("yb_xcluster_ddl_replication.HandleSourceSQLDrop context"); + + yb_should_replicate_ddl |= ProcessSourceEventTriggerDroppedObjects(trig_data->tag); + + CLOSE_MEM_CONTEXT_AND_SPI; +} + +void +HandleSourceTableRewrite(EventTriggerData *trig_data) +{ + if (enable_manual_ddl_replication) + return; + + /* Create memory context for handling query execution. */ + MemoryContext context_new, + context_old; + Oid save_userid; + int save_sec_context; + + INIT_MEM_CONTEXT_AND_SPI_CONNECT("yb_xcluster_ddl_replication.HandleSourceTableRewrite context"); + + ProcessSourceEventTriggerTableRewrite(); + + CLOSE_MEM_CONTEXT_AND_SPI; +} + +void +HandleSourceDDLStart(EventTriggerData *trig_data) +{ + /* By default we don't replicate. */ + yb_should_replicate_ddl = false; + if (enable_manual_ddl_replication) + { + /* + * Always replicate manual DDLs regardless of what they are. + * Will show up on the target with a manual_replication field set. + */ + yb_should_replicate_ddl = true; + return; + } + + ClearRewrittenTableOidList(); +} + +/* + * xCluster: Prevent replication from halting on phantom indexes (invalid + * indexes that exist on the source but not the target). DDLs referencing them + * are silently skipped rather than forcing replication to halt. + */ +static void +yb_xcluster_handle_phantom_indexes(Node *parsetree) +{ + if (yb_xcluster_automatic_mode_target_ddl) + { + switch (nodeTag(parsetree)) + { + case T_DropStmt: + { + DropStmt *stmt = (DropStmt *) parsetree; + if (stmt->removeType == OBJECT_INDEX) + stmt->missing_ok = true; + break; + } + case T_RenameStmt: + { + RenameStmt *stmt = (RenameStmt *) parsetree; + if (stmt->renameType == OBJECT_INDEX) + stmt->missing_ok = true; + break; + } + case T_AlterTableStmt: + { + AlterTableStmt *stmt = (AlterTableStmt *) parsetree; + if (stmt->objtype == OBJECT_INDEX) + stmt->missing_ok = true; + break; + } + default: + break; + } + } +} + +void +HandleTargetDDLStart(EventTriggerData *trig_data) +{ + yb_xcluster_target_ddl_bypass = false; + + yb_xcluster_handle_phantom_indexes(parsetree); + + /* Bypass DDLs executed in manual mode, or from the target poller. */ + if (enable_manual_ddl_replication || yb_xcluster_automatic_mode_target_ddl) + { + yb_xcluster_target_ddl_bypass = true; + return; + } + + /* + * Temp relations are bypassed in RecordTempRelationDDL. + * DDLs that are not caught by the trigger (ex CREATE DATABASE) are bypassed + * in XClusterProcessUtility. + */ +} + +static char * +GetExtensionName(CommandTag tag) +{ + /* + * In the case where we are creating our own extension, we only start + * capturing DDLs mid way and miss the top level command tag. So rely instead + * on the creating_extension global variable. + */ + if (creating_extension) + tag = CMDTAG_CREATE_EXTENSION; + + switch (tag) + { + case CMDTAG_CREATE_EXTENSION: + return ((CreateExtensionStmt *) parsetree)->extname; + case CMDTAG_ALTER_EXTENSION: + return ((AlterExtensionStmt *) parsetree)->extname; + case CMDTAG_DROP_EXTENSION: + { + DropStmt *stmt = (DropStmt *) parsetree; + + /* Ensure there is at least one object in the list. */ + if (stmt->objects == NULL || list_length(stmt->objects) != 1) + { + elog(WARNING, "Unexpected number of objects in DROP EXTENSION statement"); + return NULL; + } + + Node *object = linitial(stmt->objects); + + if (!IsA(object, String)) + { + elog(WARNING, "Unexpected object type in DROP EXTENSION statement"); + return NULL; + } + + return strVal(castNode(String, object)); + } + default: + return NULL; + } +} + +void +EvaluateExtensionDDL(CommandTag command_tag) +{ + if (!is_in_extension_ddl) + return; + + if (current_extension_name == NULL) + { + current_extension_name = GetExtensionName(command_tag); + + if (current_extension_name != NULL && + strcmp(current_extension_name, EXTENSION_NAME) == 0) + is_self_extension_ddl = true; + } + + if (is_self_extension_ddl) + return; + + /* Disallow extensions that contain complex DDLs like CREATE TABLE. */ + if (!IsPassThroughDdlCommandSupported(command_tag)) + { + elog(ERROR, + "Database is replicating DDLs for xCluster. Extension %s is not " + "supported because it contains unsupported DDLs within the " + "extension script. Create the extension before adding the " + "database to xCluster.\n" + "Unsupported DDL within extension : %s\n", + current_extension_name, GetCommandTagName(command_tag)); + } +} + +PG_FUNCTION_INFO_V1(handle_ddl_start); +Datum +handle_ddl_start(PG_FUNCTION_ARGS) +{ + if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */ + elog(ERROR, "not fired by event trigger manager"); + + EventTriggerData *trig_data = (EventTriggerData *) fcinfo->context; + + FetchReplicationRole(); + EvaluateExtensionDDL(trig_data->tag); + + if (IsDisabled(trig_data->tag)) + PG_RETURN_NULL(); + + /* + * Given a possibly multi-statement source string, confine our attention to + * the relevant part of the string. + */ + query_string = CleanQuerytext(query_string, &query_location, &query_len); + + if (IsReplicationSource()) + { + HandleSourceDDLStart(trig_data); + } + if (IsReplicationTarget()) + { + HandleTargetDDLStart(trig_data); + } + + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(handle_ddl_end); +Datum +handle_ddl_end(PG_FUNCTION_ARGS) +{ + if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */ + elog(ERROR, "not fired by event trigger manager"); + + EventTriggerData *trig_data = (EventTriggerData *) fcinfo->context; + + if (IsDisabled(trig_data->tag)) + PG_RETURN_NULL(); + + Assert(query_string != NULL); + Assert(query_len > 0); + + /* + * Capture the DDL as long as its not a step within another Extension DDL + * batch. + */ + if (IsReplicationSource()) + { + HandleSourceDDLEnd(trig_data); + } + if (IsReplicationTarget()) + { + HandleTargetDDLEnd(trig_data); + } + + query_string = NULL; + query_len = 0; + + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(handle_sql_drop); +Datum +handle_sql_drop(PG_FUNCTION_ARGS) +{ + if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */ + elog(ERROR, "not fired by event trigger manager"); + + EventTriggerData *trig_data = (EventTriggerData *) fcinfo->context; + + if (IsDisabled(trig_data->tag)) + PG_RETURN_NULL(); + + if (IsReplicationSource()) + { + HandleSourceSQLDrop(trig_data); + } + + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(handle_table_rewrite); +Datum +handle_table_rewrite(PG_FUNCTION_ARGS) +{ + if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */ + elog(ERROR, "not fired by event trigger manager"); + + EventTriggerData *trig_data = (EventTriggerData *) fcinfo->context; + + if (IsDisabled(trig_data->tag)) + PG_RETURN_NULL(); + + if (IsReplicationSource()) + { + HandleSourceTableRewrite(trig_data); + } + + PG_RETURN_NULL(); +} + +static void +RecordTempRelationDDL() +{ + /* + * If we are manually running a DDL on a temp relation on the target, then do not block it. + */ + if (IsReplicationTarget()) + yb_xcluster_target_ddl_bypass = true; + + if (prev_YBCRecordTempRelationDDL) + prev_YBCRecordTempRelationDDL(); +} + +void +HandleQueryStart(PlannedStmt *pstmt, const char *queryString, bool is_top_level_query) +{ + /* + * For DDLs that are handled by the handle_ddl_start event trigger, + * HandleTargetDDLStart will set yb_xcluster_target_ddl_bypass to false and + * then allow them on a case by case basis. For any DDL that is not handled + * by the trigger, we will set yb_xcluster_target_ddl_bypass to true and + * allow it to pass through. + */ + yb_xcluster_target_ddl_bypass = true; + + /* + * For extension DDLs, only capture the top level query. + */ + if (!is_in_extension_ddl) + { + query_location = pstmt->stmt_location; + query_len = pstmt->stmt_len; + query_string = queryString; + parsetree = pstmt->utilityStmt; + } + + if (is_top_level_query && IsExtensionDdl(CreateCommandTag(parsetree))) + { + is_in_extension_ddl = true; + } +} + +void +HandleQueryEnd(bool is_top_level_query) +{ + if (is_top_level_query) + { + is_in_extension_ddl = false; + is_self_extension_ddl = false; + current_extension_name = NULL; + } + + /* + * For extension DDLs, retain the query string and length until the top + * level query ends. + */ + if (!is_in_extension_ddl) + { + query_string = NULL; + query_len = 0; + } +} + +static void +XClusterProcessUtility(PlannedStmt *pstmt, + const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, + QueryCompletion *qc) +{ + bool is_top_level_query = (context == PROCESS_UTILITY_TOPLEVEL); + bool isCompleteQuery = (context != PROCESS_UTILITY_SUBCOMMAND); + + if (isCompleteQuery) + HandleQueryStart(pstmt, queryString, is_top_level_query); + + PG_TRY(); + { + if (prev_ProcessUtility) + prev_ProcessUtility(pstmt, queryString, readOnlyTree, context, + params, queryEnv, dest, qc); + else + standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, + params, queryEnv, dest, qc); + } + PG_FINALLY(); + { + if (isCompleteQuery) + HandleQueryEnd(is_top_level_query); + } + PG_END_TRY(); +} diff --git a/yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication.control b/yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication.control new file mode 100644 index 0000000000000..e3b520664b3b8 --- /dev/null +++ b/yb-extensions/yb_xcluster_ddl_replication/yb_xcluster_ddl_replication.control @@ -0,0 +1,7 @@ +# yb_xcluster_ddl_replication extension + +comment = 'module used to replicate ddls for xcluster replication' +default_version = '1.0' +module_pathname = '$libdir/yb_xcluster_ddl_replication' +relocatable = false +schema = yb_xcluster_ddl_replication diff --git a/yb-extensions/yb_ycql_utils/Makefile b/yb-extensions/yb_ycql_utils/Makefile new file mode 100644 index 0000000000000..4a68a16e0a394 --- /dev/null +++ b/yb-extensions/yb_ycql_utils/Makefile @@ -0,0 +1,21 @@ +# contrib/yb_ycql_utils/Makefile + +PGFILEDESC = "yb_ycql_utils - An extension for fetching YCQL data in PG side." + +MODULE_big = yb_ycql_utils +OBJS = yb_ycql_utils.o $(WIN32RES) + +EXTENSION = yb_ycql_utils +DATA = yb_ycql_utils--1.0.sql yb_ycql_utils--1.0--1.1.sql +SHLIB_LINK += -L$(YB_BUILD_ROOT)/lib -lyb_pggate -lyb_pggate_util + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/yb_ycql_utils +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/yb-extensions/yb_ycql_utils/expected/basic.out b/yb-extensions/yb_ycql_utils/expected/basic.out new file mode 100644 index 0000000000000..d64cd4ddce6ad --- /dev/null +++ b/yb-extensions/yb_ycql_utils/expected/basic.out @@ -0,0 +1,11 @@ +create extension yb_ycql_utils; +select ycql_stat_statements(); + ycql_stat_statements +---------------------- +(0 rows) + +select * from ycql_stat_statements; + queryid | query | is_prepared | calls | total_time | min_time | max_time | mean_time | stddev_time | keyspace +---------+-------+-------------+-------+------------+----------+----------+-----------+-------------+---------- +(0 rows) + diff --git a/yb-extensions/yb_ycql_utils/sql/basic.sql b/yb-extensions/yb_ycql_utils/sql/basic.sql new file mode 100644 index 0000000000000..f0128a99df074 --- /dev/null +++ b/yb-extensions/yb_ycql_utils/sql/basic.sql @@ -0,0 +1,3 @@ +create extension yb_ycql_utils; +select ycql_stat_statements(); +select * from ycql_stat_statements; diff --git a/yb-extensions/yb_ycql_utils/yb_cql_utils.conf b/yb-extensions/yb_ycql_utils/yb_cql_utils.conf new file mode 100644 index 0000000000000..c6093dbaee0ec --- /dev/null +++ b/yb-extensions/yb_ycql_utils/yb_cql_utils.conf @@ -0,0 +1 @@ +shared_preload_libraries = 'yb_ycql_utils' diff --git a/yb-extensions/yb_ycql_utils/yb_schedule b/yb-extensions/yb_ycql_utils/yb_schedule new file mode 100644 index 0000000000000..1488ef7caf9ac --- /dev/null +++ b/yb-extensions/yb_ycql_utils/yb_schedule @@ -0,0 +1,3 @@ +#contrib/yb_ycql_utils/yb_schedule + +test: basic diff --git a/yb-extensions/yb_ycql_utils/yb_ycql_utils--1.0--1.1.sql b/yb-extensions/yb_ycql_utils/yb_ycql_utils--1.0--1.1.sql new file mode 100644 index 0000000000000..82e32f77f87c7 --- /dev/null +++ b/yb-extensions/yb_ycql_utils/yb_ycql_utils--1.0--1.1.sql @@ -0,0 +1,34 @@ +/* yb-extensions/yb_ycql_utils/yb_ycql_utils--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION yb_ycql_utils UPDATE TO '1.1'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION yb_ycql_utils DROP VIEW ycql_stat_statements; +ALTER EXTENSION yb_ycql_utils DROP FUNCTION ycql_stat_statements(); + +/* Then we can drop them */ +DROP VIEW IF EXISTS ycql_stat_statements; +DROP FUNCTION IF EXISTS ycql_stat_statements(); + +CREATE FUNCTION ycql_stat_statements( + OUT queryid int8, + OUT query text, + OUT is_prepared bool, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8, + OUT keyspace text +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW ycql_stat_statements AS + SELECT * + FROM ycql_stat_statements(); + +GRANT SELECT ON ycql_stat_statements TO PUBLIC; diff --git a/yb-extensions/yb_ycql_utils/yb_ycql_utils--1.0.sql b/yb-extensions/yb_ycql_utils/yb_ycql_utils--1.0.sql new file mode 100644 index 0000000000000..b4b2ba2694993 --- /dev/null +++ b/yb-extensions/yb_ycql_utils/yb_ycql_utils--1.0.sql @@ -0,0 +1,22 @@ +/* yb-extensions/yb_ycql_utils/yb_ycql_utils--1.0.sql */ + +CREATE FUNCTION ycql_stat_statements( + OUT queryid int8, + OUT query text, + OUT is_prepared bool, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW ycql_stat_statements AS + SELECT * + FROM ycql_stat_statements(); + +GRANT SELECT ON ycql_stat_statements TO PUBLIC; diff --git a/yb-extensions/yb_ycql_utils/yb_ycql_utils.c b/yb-extensions/yb_ycql_utils/yb_ycql_utils.c new file mode 100644 index 0000000000000..850434583fba6 --- /dev/null +++ b/yb-extensions/yb_ycql_utils/yb_ycql_utils.c @@ -0,0 +1,95 @@ +#include "postgres.h" + +#include "funcapi.h" +#include "pg_yb_utils.h" +#include "postmaster/syslogger.h" +#include "utils/builtins.h" +#include "yb/yql/pggate/ybc_pggate.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(ycql_stat_statements); + +/* In yb_ycql_utils v1.0 the number of columns = 9 */ +static const int ycql_stat_statements_num_cols_v1_1 = 10; + +Datum +ycql_stat_statements(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + int i = 0; + + InitMaterializedSRF(fcinfo, 0); + + const int ncols = rsinfo->setDesc->natts; + Datum values[ncols]; + bool nulls[ncols]; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); + + /* Switch context to construct returned data structures */ + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* Build a tuple descriptor */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errmsg_internal("return type must be a row type"))); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + YbcYCQLStatementStats *stat_list = NULL; + size_t num_stats = 0; + + HandleYBStatus(YBCYcqlStatementStats(&stat_list, &num_stats)); + + for (i = 0; i < num_stats; ++i) + { + YbcYCQLStatementStats *stats = (YbcYCQLStatementStats *) stat_list + i; + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[0] = Int64GetDatum(stats->queryid); + values[1] = CStringGetTextDatum(stats->query); + values[2] = BoolGetDatum(stats->is_prepared); + values[3] = Int64GetDatum(stats->calls); + values[4] = Float8GetDatumFast(stats->total_time); + values[5] = Float8GetDatumFast(stats->min_time); + values[6] = Float8GetDatumFast(stats->max_time); + values[7] = Float8GetDatumFast(stats->mean_time); + values[8] = Float8GetDatumFast(stats->stddev_time); + + if (ncols >= ycql_stat_statements_num_cols_v1_1) + values[9] = CStringGetTextDatum(stats->keyspace); + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + + /* clean up and return the tuplestore */ + tuplestore_donestoring(tupstore); + if (stat_list) + { + pfree(stat_list); + } + return (Datum) 0; +} diff --git a/yb-extensions/yb_ycql_utils/yb_ycql_utils.control b/yb-extensions/yb_ycql_utils/yb_ycql_utils.control new file mode 100644 index 0000000000000..2a7bac6e280fd --- /dev/null +++ b/yb-extensions/yb_ycql_utils/yb_ycql_utils.control @@ -0,0 +1,5 @@ +# yb_ycql_utils extension +comment = 'An extension for fetching YCQL data in PG side' +default_version = '1.1' +module_pathname = '$libdir/yb_ycql_utils' +relocatable = true From 95f79e3141b9aff743b808476b7c775614c6e26d Mon Sep 17 00:00:00 2001 From: Fizaa Luthra Date: Tue, 9 Jun 2026 12:54:54 -0400 Subject: [PATCH 2/2] add resolution notes --- pg19mergeresolutions.md | 4836 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 4836 insertions(+) create mode 100644 pg19mergeresolutions.md diff --git a/pg19mergeresolutions.md b/pg19mergeresolutions.md new file mode 100644 index 0000000000000..325d7c9436ae7 --- /dev/null +++ b/pg19mergeresolutions.md @@ -0,0 +1,4836 @@ +Conflict resolutions: + +All these files share the same pattern: PG replaced `/* FALL THRU */` / `/* FALLTHROUGH */` / `/* fall through */` comments with the `pg_fallthrough;` macro. YB independently added `yb_switch_fallthrough()` macro calls for the same purpose. Both macros expand to `__attribute__((fallthrough))`, so `pg_fallthrough;` is sufficient. + +- src/port/snprintf.c, contrib/btree_gin/btree_gin.c, contrib/pgcrypto/pgp-info.c, src/backend/access/transam/xlogrecovery.c, src/backend/regex/regc_lex.c, src/backend/regex/regcomp.c, src/backend/replication/walreceiver.c, src/backend/replication/walreceiverfuncs.c, src/backend/utils/adt/xml.c, src/backend/utils/sort/tuplestore.c, src/interfaces/libpq/fe-secure.c, src/pl/tcl/pltcl.c, src/timezone/zic.c, src/backend/executor/nodeHash.c, src/pl/plpgsql/src/pl_exec.c, src/tools/pg_bsd_indent/parse.c, src/backend/utils/adt/formatting.c, src/backend/utils/adt/jsonpath.c, src/backend/utils/mb/mbutils.c, src/interfaces/ecpg/pgtypeslib/interval.c, src/common/hashfn.c, src/backend/partitioning/partprune.c, src/common/wchar.c, contrib/pg_trgm/trgm_gin.c, contrib/pg_trgm/trgm_gist.c, src/backend/executor/nodeTidrangescan.c, src/backend/optimizer/util/clauses.c, src/backend/executor/nodeHashjoin.c: + - all `pg_fallthrough` / `yb_switch_fallthrough()` conflict sites: + - PG commit 8354b9d6b602ea549bc8d85cb404771505662a7b replaced fallthrough comments with `pg_fallthrough;` macro. + - YB added `yb_switch_fallthrough()` macro calls for the same purpose (various YB commits per file). + - Took PG's `pg_fallthrough;`. Both macros are functionally identical (`__attribute__((fallthrough))`), and PG's is the standard going forward. + +LSM AM checks: PG commit ce62f2f2a0a48d021f250ba84dfcab5d45ddc914 generalized many `BTREE_AM_OID` checks using new fields (`amconsistentequality`, `amconsistentordering`, `amtranslatestrategy`, `amtranslatecmptype`). YB had added explicit `LSM_AM_OID` checks at those same sites. All such conflicts are resolved by taking PG's generalized form; LSM is covered because `ybcinhandler` sets these fields to the btree equivalents. See src/backend/access/yb_access/yb_lsm.c entry below for the ybcinhandler update. + +- src/backend/access/yb_access/yb_lsm.c (ybcinhandler): + - PG commit ce62f2f2a0a48d021f250ba84dfcab5d45ddc914 added `amconsistentequality`, `amconsistentordering`, `amtranslatestrategy`, and `amtranslatecmptype` to `IndexAmRoutine`. + - YB added explicit `LSM_AM_OID` checks at those sites. + - Took PG's generalized form; added `amconsistentequality=true`, `amconsistentordering=true`, `amtranslatestrategy=bttranslatestrategy`, `amtranslatecmptype=bttranslatecmptype` to `ybcinhandler`. + +- src/backend/jit/jit.c: + - `jit_enabled` declaration: PG commit 7f8c88c2b872cb74882ab93dcb05529dab2a10bc changed jit default to off; YB commit c1a4632453751094ada4a39ec26f97c7170d2567 added `/* YB: changed to false */` comment. Kept PG's declaration with YB comment. + +- src/backend/catalog/partition.c: + - `build_attrmap_by_name` call: + - kept both PGG (`bool missing_ok`) and YB (`bool yb_ignore_type_mismatch`) arguments. + +- src/backend/access/common/attmap.c: + - `build_attrmap_by_name` definition and `build_attrmap_by_name_if_req` call: kept both PG and YB parameters/arguments. + +- src/include/access/attmap.h: + - `build_attrmap_by_name` declaration: + - kept both PG and YB parameters. + +- src/backend/storage/lmgr/s_lock.c: + - copyright header comment: PG updated copyright year to 2026; YB added `YB: change 2 or so minutes to 30 seconds.` comment. Kept both. + +- src/backend/utils/adt/int8.c: + - includes: PG added `#include "utils/fmgroids.h"`, removed `#include "utils/lsyscache.h"`; YB added `#include `. Kept PG's `fmgroids.h` and YB's ``. + +- src/backend/utils/sort/logtape.c: + - `thisbuf` / `datablocknum` declarations: + - adjacent line conflict. + - PG commit b1e5c9fa9ac4399895bf312398c5d441baba0c3b changed `long datablocknum` to `int64 datablocknum`. + - YB added null-pointer guard for `thisbuf` with explanatory comment. + - Combined both: YB's changes + PG's `int64` type. + +- src/bin/pg_upgrade/controldata.c: + - shutdown check: PG removed `\n` from `"shut down"` comparison; YB added `!is_yugabyte_enabled() &&` guard. Combined both. + +- src/test/regress/pg_regress.c: + - psql binary name: PG added `-q` flag; YB renamed `psql` to `ysqlsh`. Combined: `"\"%s%sysqlsh\" -X -q"`. + +- src/test/regress/regress.c: + - includes and macros: PG added `TEXTDOMAIN` macro; YB added `#include "utils/syscache.h"`. Kept both: YB's include first, then PG's macro. + +- src/backend/utils/hash/dynahash.c: + - `HASH_ENTER_NULL` case reordering: PG commit 9c911ec065df0f660e3add65d986f95928914375 moved `case HASH_ENTER: case HASH_ENTER_NULL:`; YB had added `yb_switch_fallthrough()`. Took PG's changes. + +- src/backend/utils/mb/conversion_procs/euc_tw_and_big5/big5.c: + - `break` vs `yb_switch_fallthrough()`: PG changed to `break;` before `default:`; YB had `yb_switch_fallthrough();`. Took PG's `break;`. + +- contrib/isn/isn.c: + - `break` vs `yb_switch_fallthrough()`: same as big5.c above. + +- contrib/auto_explain/auto_explain.c: + - PG_MODULE_MAGIC + includes: + - PG commit 9324c8c580655800331b0582b770e88c01b7a5c4 introduced `PG_MODULE_MAGIC_EXT` macro. + - YB added `#include "pg_yb_utils.h"`. + - Kept YB include + PG's `PG_MODULE_MAGIC_EXT`. + - GUC variables: + - PG commit e972dff6c30447ebcfa2f8601b67f926247463b6 added `auto_explain_log_extension_options` and related structs. + - YB commit 2ff1d9624194b36973e92d03205c8797f648ae75 added `auto_explain_log_dist`. YB commit 5cf466690abb9c5bb78fbe13c5dc404751c28f50 added `auto_explain_log_debug` and `yb_auto_explain_debug_metrics_needed`. + - Kept both. PG GUCs, then YB GUCs appended after PG GUCs, YB variables section placed after the GUCs, before the `auto_explain_option` structs. + +- contrib/fuzzystrmatch/fuzzystrmatch.c: + - PG_MODULE_MAGIC + includes: + - PG changed to `PG_MODULE_MAGIC_EXT`. + - YB added `#include "common/pg_yb_common.h"`. + - Kept YB include + PG's `PG_MODULE_MAGIC_EXT`. + +- contrib/pgcrypto/px-crypt.c: + - crypt generator table: + - PG commit 749a9e20c9790006f3af47f7a8faf4ad8dc358d9 added `sha256crypt` and `sha512crypt` entries. + - YB commit d6368fb4ad6b9f2ed38d4967800de38c13074289 renamed 4 existing entries. + - Kept YB's `yb_` prefixed names for existing entries + PG's new sha entries with original names. + +- contrib/pgcrypto/px-crypt.h: + - function declarations: + - PG commit 749a9e20c9790006f3af47f7a8faf4ad8dc358d9 added sha256/sha512 declarations. + - YB commit d6368fb4ad6b9f2ed38d4967800de38c13074289 added `yb_` prefix to 4 existing declarations. + - Kept YB's `yb_` prefixed declarations + PG's new sha declarations. Add a YB_TODO_PG19MERGE to add a `yb_` prefix to the new functions. + +- contrib/pg_trgm/trgm_regexp.c: + - printSourceNFA, variable declarations: + - PG commit cdaa67565867ba443afb66b9e82023d65487dc7c moved `int state` and `int i` into for-loop declarations. + - YB commit d714b60297567789913c68f5937e5e16943ce6d9 added `const char *yb_tmp_dir = YbGetTmpDir();`. + - Kept only YB's `yb_tmp_dir`. + +- src/backend/nodes/nodeFuncs.c: + - exprType() switch: + - PG added `case T_MergeSupportFunc`. + - YB added `case T_YbBatchedExpr`. + - Kept both cases (PG first, YB after). + - expression_tree_mutator T_MergeSupportFunc / T_YbBatchedExpr: + - PG added `T_MergeSupportFunc` handler and removed `(Node *)` cast from `copyObject()`. + - YB added `T_YbBatchedExpr` handler. + - Kept PG's `T_MergeSupportFunc` (without cast) then YB's `T_YbBatchedExpr`. + - expression_tree_mutator T_PartitionPruneStepCombine / T_YbPartitionPruneStepFuncOp: + - PG removed `(Node *)` cast from `copyObject()` return. + - YB added `T_YbPartitionPruneStepFuncOp` handler. + - Kept PG's cast-free return + YB's new handler. + +- src/backend/parser/parse_relation.c: + - `selectedCols` / attribute number: + - PG commit a61b1f74823c9c4f79c95226a461f1e7a367764b moved `selectedCols` from `RangeTblEntry` to `RTEPermissionInfo` (`perminfo->selectedCols`). + - YB commit 1308c0d131c1344a290d7ab491e186e4850fb5cb replaced `FirstLowInvalidHeapAttributeNumber` with `YBGetFirstLowInvalidAttributeNumberFromOid(rte->relid)` (later touched by commit 06cd4073e15e99b983787faaa1fb5f9f717a5e04). + - Combined both: `perminfo->selectedCols` with YB's attribute number function. + +- src/backend/optimizer/prep/prepjointree.c: + - PlannerInfo fields: + - PG added `subroot->assumeReplanning = false`. + - YB added `yb_cur_batched_relids` and other `yb_` batching fields. + - Kept both: PG's field first, then YB's batching fields. + +- src/backend/access/common/toast_compression.c: + - default compression method: + - PG commit 34dfca293432e206b8f80431f81535aff69782ca introduced `DEFAULT_TOAST_COMPRESSION` macro. + - YB commit 40f556645f42e36ae80df470ba0c428d778b0c0b hardcoded `TOAST_LZ4_COMPRESSION`. + - Took PG's `DEFAULT_TOAST_COMPRESSION` (which selects LZ4 if available). + +- src/backend/access/index/indexam.c: + - new functions: + - PG commit c1ec02be1d79eac95160dea7ced32ace84664617 added `index_insert_cleanup()`. + - YB added `yb_index_delete()` and `yb_index_update()`. + - Kept both: PG's function first, then YB's functions. + +- src/backend/access/table/toast_helper.c: + - Datum/pointer conversion: + - PG commit 0f5ade7a367c16d823c75a81abb10e2ec98b4206 wrapped `value` in `DatumGetPointer`. + - YB PG15 initial merge 55782d561e55ef972f2470a4ae887dd791bb4a97 wrapped `value` in `PointerGetDatum`. + - Took PG's `DatumGetPointer(value)`. + +- src/backend/access/transam/xlog.c: + - GUC defaults: + - adjacent line conflict. + - PG commit 8d140c58229d6224882f881d9b62ba06236e71d3 renamed `sync_method` to `wal_sync_method`. + - YB commit 22b6a4730da37612836f17fc29d54790c78db952 changed `wal_level` default to `WAL_LEVEL_LOGICAL` with explanatory comment. + - Kept PG's `wal_sync_method` + YB's `WAL_LEVEL_LOGICAL` default. + +- src/backend/bootstrap/bootparse.y: + - includes: + - PG added `#include "bootparse.h"`. + - YB added YB includes. + - Kept both. + +- src/backend/bootstrap/bootscanner.l: + - token definitions: + - PG commit 3e4bacb171001644583ac14e29ae1b09ce818c92 changed `yylval.kw` to `yylval->kw`. + - YB commit be0908b45827c69ff010d9263ff865310ce722a1 added `yb_declare` and `primary` tokens. + - Used PG's `yylval->kw` arrow syntax for all entries + adding YB's new tokens. + +- src/backend/libpq/crypt.c: + - includes and variables: + - PG added `password_expiration_warning_threshold` and `md5_password_warnings`. + - YB added YB includes (`pg_yb_utils.h`, `ybc_pggate.h`). + - Kept both: YB includes first, then PG's variables. + +- src/backend/commands/view.c: + - includes and function declaration: + - PG commit 20e58105badff383bd43f0b97e532771768f94df renamed `checkViewTupleDesc` to `checkViewColumns`. + - YB added YB includes. + - Kept YB includes + PG's renamed function declaration. + +- src/backend/commands/dropcmds.c: + - ownership check: + - PG commit afbfc02983f86c4d71825efa6befd547fe81a926 changed `pg_namespace_ownercheck` to `object_ownercheck`. + - YB commit cc010e90cb8c9634ea678f4c9b5b1f4a8b9a51f5 added `&& !is_yb_db_admin_droppable_object` guard (later touched by commit 06cd4073e15e99b983787faaa1fb5f9f717a5e04). + - Combined both: PG's `object_ownercheck` + YB's admin guard. + +- src/backend/replication/repl_gram.y: + - snapshot_action alternatives: + - PG added `K_UPLOAD_MANIFEST`. + - YB added `K_YB_SEQUENCE`, `K_YB_HYBRID_TIME`, `K_YB_ROW`, `K_YB_TRANSACTION`. + - Kept both: PG's entry first, then YB's entries. + +- src/bin/psql/startup.c: + - SetVariableHooks calls: + - PG added `WATCH_INTERVAL` hook. + - YB added `YB_DISABLE_ERROR_PREFIX` hook. + - Kept both. + +- src/backend/utils/adt/regexp.c: + - RE_compile_and_cache variable declarations: + - PG commit bea3d7e3831fa6a1395eadbad7d97cebc7aa8aee added `MemoryContext oldcontext`. + - YB commit a20d9474f423a1d365c2eb2e25eea0d70646a166 added `cached_re_str *re_array = YbGetReCacheInfo().array`. + - Kept both declarations. + +- src/backend/nodes/read.c: + - function definitions and ifdef guard: + - PG commit a292c98d62ddc0ad681f772ab91bf68ee399cb4b changed `#ifdef WRITE_READ_PARSE_PLAN_TREES` to `#ifdef DEBUG_NODE_TESTS_ENABLED`. + - YB added `ybDeserializeNode()` function. + - Kept YB's function + PG's `DEBUG_NODE_TESTS_ENABLED` guard. + +- src/backend/commands/constraint.c: + - index_insert call: + - PG commit 41d2c6f952edc4841763d05296b65f3c0edad4f2 added `index_insert_cleanup()` call after `index_insert()`. + - YB commit 73bad43e870fa2d596e3ab09ca7ac1b4d44f2e75 added `false /* yb_shared_insert */` argument. + - Kept both: YB's extra argument + PG's cleanup call. + +- src/backend/executor/nodeBitmapIndexscan.c: + - `tbm_create` call: + - PG commit 041e8b95b8cd251bfec6a3c9c3dd6614de6a4c9b changed `work_mem * 1024L` to `work_mem * (Size) 1024`. + - YB (commit a81450b9dbcb1690b9bfdbd547e458ec589651e9 and others) changed `tbm` to `bitmap`. + - Kept both `(Size)` and `bitmap` + +- src/backend/executor/nodeBitmapOr.c: + - `tbm_create` call: + - same as above + +- src/backend/utils/sort/sortsupport.c: + - `amcanorder` check: generalized AM check pattern + +- src/backend/utils/adt/jsonb_util.c: + - `uniqueifyJsonbObject` call and fallthrough: + - PG changed `uniqueifyJsonbObject` and added `pg_fallthrough`. + - YB added `yb_switch_fallthrough()`. + - Took PG's new API + `pg_fallthrough`. + +- src/backend/replication/logical/worker.c: + - `ExecuteTruncateGuts` final argument: + - PG added `!MySubscription->runasowner` argument. + - YB added `false /* yb_is_top_level */` argument. + - Kept both arguments: PG's first, then YB's. + +- src/backend/optimizer/plan/setrefs.c: + - set_subqueryscan_references: + - PG added `record_elided_node()` call. + - YB added some logic to deal with hint aliases. + - Kept both: PG's `record_elided_node()` first, then YB's hint alias block. + +- src/backend/optimizer/util/appendinfo.c: + - add_row_identity_columns: + - PG commit 5f2e179bd31e5f5803005101eb12a8d7bf8db8f3 removed `commandType == CMD_MERGE ||` from the if condition. + - YB added an if `IsYBRelation` block and moved PG's if block into an else-if. + - Put YB's `IsYBRelation` block first, then PG's condition (without `CMD_MERGE`) in `else if`. + +- src/backend/postmaster/autovacuum.c: + - `InitPostgres` call: + - PG commit 15b4c46c328b25be9463db6d2960eeb16a784aad replaced two boolean params (`load_session_libraries`, `override_allow_connections`) with a single bitwise `flags` param (`INIT_PG_OVERRIDE_ALLOW_CONNS`). + - YB commit caa636fcd02f59d0dfab103b52fd59703745ff64 removed a previously-YB-added `session_id` parameter from `InitPostgres`, reformatting the call onto one line in the process. + - Took PG's version. + +- src/backend/utils/adt/dbsize.c: + - calculate_indexes_size: + - PG commit b0a55e43299c4ea2a9a8c757f9c26352407d0ccc changed `rd_node` to `rd_locator`. + - YB commit 8b2f5c5165700db851a1429bd6e92f157c1ca4b1 added `IsYBRelation(idxRel)` check with YB-specific `YBCPgGetTableDiskSize` call, and moved the PG code to the else block. + - Kept YB's `IsYBRelation` block first, PG's `rd_locator`-based loop in the `else` branch. + +- src/backend/commands/copyfrom.c: + - CopyFromErrorCallback: + - PG commit 97da48246d34807196b404626f019c767b7af0df added `relname_only` early-return logic, and PG commit a2145605ee3d92faccd769010059b110c44104ff changed `cstate->opts.binary` to `cstate->opts.format == COPY_FORMAT_BINARY`. + - YB PG15 initial merge 55782d561e55ef972f2470a4ae887dd791bb4a97 added `pgstat_progress_update_param(PROGRESS_COPY_STATUS, CP_ERROR)`. + - Kept both: YB's progress update first, then PG's relname_only guard. + +- src/backend/commands/createas.c: + - ExecCreateTableAs: + - PG commit 4b74ebf726d444ba820830cad986a1f92f724649 added `RefreshMatViewByOid()` call for materialized views. + - YB commit 159dde75013bf54db488e4959bc9b2011cda254d added `yb_xcluster_automatic_mode_target_ddl` block. + - Kept both: PG's refresh call first, then YB's xCluster block. + +- src/backend/executor/nodeSubplan.c: + - ExecHashSubPlan: + - PG commit 0f5738202b812a976e8612c85399b52d16a0abb6 changed `FindTupleHashEntry` API to use `lhs_hash_expr` instead of `lhs_hash_funcs`. PG commit abdeacdb0920d94dec7500d09f6f29fbb2f6310d removed `ExecClearTuple(slot)` calls and early returns, replacing them with a `result` variable assignment. + - YB commit 49ed1065dabd2a9456b830c69b4bb8d584491ff8 added `node->hashtable->keyColIdx` as an extra argument to `FindTupleHashEntry`. + - Took PG's changes, while keeping YB's `node->hashtable->keyColIdx` argument. + +- src/backend/tcop/pquery.c: + - CreateQueryDesc top of the function: + - PG commit 1b105f9472bdb9a68f709778afafb494997267bd changed to `palloc_object(QueryDesc)`. + - YB added `YbPgMemResetStmtConsumption()` call. + - Kept YB's `YbPgMemResetStmtConsumption()` + PG's `palloc_object`. + - CreateQueryDesc field assignment: + - PG commit 2c16deee2f7d52d6567dcbad046f74a8e880ee52 changed `totaltime` to `query_instr`. + - YB commit 536e4b1974268041302274d6a45ffba157d10283 added `yb_query_stats` field initialization. + - Kept PG's `query_instr` + YB's `yb_query_stats`. + +- src/bin/psql/tab-complete.in.c: + - match_previous_words: + - PG commit 4b3d173629f4cd7ab6cd700d1053af5d5c7c9e37 added `SPLIT PARTITION` completions. PG commit f2e4cc427951b7c46629fb7625a22f7898586f3a added `MERGE PARTITIONS` completions. + - YB commit ec65f397d75beeec92182b8902ae7e1c41aa7c11 added `ALTER TABLEGROUP` completions (originally placed below TABLESPACE; PG15 initial merge commit 55782d561e55ef972f2470a4ae887dd791bb4a97 moved it above TABLESPACE, right after DETACH PARTITION). + - Kept both: PG's partition completions first, then YB's tablegroup completions. + +- src/bin/pg_dump/pg_dump.h: + - DumpableObjectType enum: + - PG commit 9a17be1e244a45a77de25ed2ada246fd34e4557d added `DO_SUBSCRIPTION_REL` and bumped `NUM_DUMPABLE_OBJECT_TYPES`. + - YB added `DO_TABLEGROUP`. + - Kept both: `DO_SUBSCRIPTION_REL` then `DO_TABLEGROUP`, `NUM_DUMPABLE_OBJECT_TYPES` set to `DO_TABLEGROUP + 1`. + +- src/bin/pg_dump/pg_dump_sort.c: + - `dbObjectTypePriority` array: + - PG commit afd8ef39094b0dff9d1f2bfecb1d9fa056b85e19 changed to C99 designated initializer syntax. PG also added `DO_SUBSCRIPTION_REL` and `DO_REL_STATS` entries. + - YB commit 01a8c97ac545386c3439f231e5da6a43492f7020 added `DO_TABLEGROUP` entry. + - Took PG's designated initializer style + adding `[DO_TABLEGROUP] = PRIO_TABLEGROUP,`. + +- src/bin/pg_dump/pg_backup.h: + - _dumpOptions struct: + - PG commit 67a2fbb8f9e9f75df08208e75da412c43a814688 added `restrict_key` field. + - YB added YB-specific fields (`no_tablegroups`, etc.). + - Kept both: PG's field first, then YB's fields. + +- src/bin/pg_dump/common.c: + - flagInhAttrs: + - PG commit 8bf6ec3ba3a44448817af47a080587f3b71bee08 refined condition to `foundSameGenerated && !foundDiffGenerated` with `DUMP_COMPONENT_NONE`. + - YB commit 6deb862d0affa6124c446098d94ec944405ffaae added `!dopt->include_yb_metadata` guard. + - Combined PG's refined logic with YB's metadata guard. + +- contrib/seg/Makefile: + - REGRESS list: + - PG commit 681d9e4621aac0a9c71364b6f54f00f6d8c4337f (CVE-2023-2454 fix) added `security` test. PG commit 68dfecbef210dc000271553cfcb2342989d4ca0f added `partition` test. + - YB only has `seg`. YB did import PG commit 681d9e4621aac0a9c71364b6f54f00f6d8c4337f via commit dac89aaf39b11c794c4d9eb2e9a6db76a3d19c97, but it omit the test files and the Makefile change. + - Took PG's `security seg partition`. The test files `security.sql` and `security.out` are present in PG19. + +- src/backend/access/Makefile: + - SUBDIRS: + - PG commit 449e798c77ed9a03f8bb04e5d324d4e3cfbbae8e added `sequence` subdirectory. PG also changed to multi-line backslash format. + - YB PG15 initial merge commit 55782d561e55ef972f2470a4ae887dd791bb4a97 added `yb_access` and `ybgin` subdirectories. + - Kept PG's multi-line format with `sequence` + YB's `yb_access` and `ybgin`. + +- src/backend/access/heap/heapam_handler.c: + - heapam_relation_copy_for_cluster switch block: + - PG commits 28d534e2ae0ac888b5460f977a10cd9bb017ef98 wrapped the switch block inside `if (!concurrent)` (for REPACK CONCURRENTLY), increasing indentation by one level. PG commit 852558b9ec9d54194195a7b7418d57e83a2fda70 changed `LockBuffer(buf, BUFFER_LOCK_SHARE)` to `LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE)` with detailed comment about hint bits. PG commit 8354b9d6b602ea549bc8d85cb404771505662a7b replaced `/* fall through */` comment with `pg_fallthrough;` inside the `HEAPTUPLE_RECENTLY_DEAD` case. + - YB added `yb_switch_fallthrough()` after the `HEAPTUPLE_RECENTLY_DEAD` fall-through comment. + - Took PG's changes. + +- src/backend/access/index/genam.c: + - systable_beginscan: + - PG added an `Assert`. + - YB added `IsYBRelation` early return to `ybc_systable_beginscan`. + - Kept both: PG's assert first, then YB's `IsYBRelation` early return. + +- src/backend/catalog/system_functions.sql: + - function definitions: + - PG commits 759b03b24ce96f0ba6d734b570d1a6f4a0fb1177 and f95d73ed433207c4323802dc96e52f3e5553a86c simplified creation of built-in functions with default arguments and non-default ACLs so that they no longer require definitions in system_functions.sql, only entries in pg_proc.dat + - YB commits fc46b4029bf6762522eac4c8160cfb4ff3c5390d and c23b217504aa5fc7437977cf763c35d7f8468398 added YB-specific parameters to `pg_create_logical_replication_slot`. + - Took PG's removal. YB's parameters will be handled in `pg_proc.dat`. + +- src/backend/catalog/system_views.sql: + - pg_replication_slots view columns: PG added columns; YB added columns. Kept both: PG's first, YB's appended. + +- src/backend/catalog/toasting.c: + - index_create() call: + - PG commit 23382b0f8b21e3f5330d765d1abfcef58d086111 renamed variables (`collationIds`, `opclassIds`). PG commit 784162357130f63b5130cd6517db21451692f9b3 added first `NULL` argument, PG commit 6a004f1be87d34cfe51acf2fe2552d2b08a79273 added second `NULL` argument. + - YB added YB-specific trailing parameters (`skip_index_backfill`, `is_colocated`, etc.) (last touched by lint changes 06cd4073e15e99b983787faaa1fb5f9f717a5e04). + - Combined PG's renamed variables + YB's extra trailing parameters. + +- src/backend/commands/collationcmds.c: + - pg_import_system_collations locale handling: + - PG commit bf03cfd162176d543da79f9398131abc251ddbb9 refactored to `create_collation_from_locale()` (which includes the ASCII filter internally). + - YB commit 6c8deb0504274a16bae390dcbabf45aa5ac23fbb added `YBIsSupportedLibcLocale` filter. + - Kept YB's locale filter before PG's `create_collation_from_locale()` call. (PG's ASCII filter is now after YB's locale filter). + +- src/backend/executor/execScan.c: + - ExecScan: + - PG commit fb9f955025f7609fd3da0d7e33b77438ddc765de refactored ExecScan to call `ExecScanExtended()`. + - YB added `yb_exec_params.limit_use_default = true`. + - Kept PG's changes and moved YB's logic to `ExecScanExtended()` (in execScan.h). + +- src/backend/lib/Makefile: + - OBJS list: + - PG commit 5af0263afd7beaf947e22115b7e9ade000b0387d removed `binaryheap.o`. + - YB added `yb_percentile.o`. + - Kept PG's removal of `binaryheap.o` and keeping YB's `yb_percentile.o`. + +- src/backend/optimizer/path/equivclass.c: + - end-of-file - new functions: + - PG added new functions. + - YB added `yb_find_ec_member_for_var`. + - Kept both. + +- src/backend/optimizer/prep/prepunion.c: + - choose_hashed_setop function: + - PG commit 8d96f57d5cc79c0c51050bb707c19bf07d2895eb removed `choose_hashed_setop`. + - YB modified `choose_hashed_setop` with `IsYugaByteEnabled()` logic. + - Left the function commented out with a YB_TODO_PG19MERGE. + +- src/backend/replication/logical/origin.c: + - replorigin_state_clear catalog deletion: + - PG commit 3e577ff602fe3438ac60771c4a6d027d881619b0 removed `replorigin_drop_guts`, refactored it into `replorigin_state_clear` and moved the catalog tuple deletion logic into `replorigin_drop_by_name`. + - YB commit 98b8079235fc30bf0f8365e48601999ca2cb3be7 and others added DDL transaction nesting logic inside `replorigin_drop_guts` and changed the CatalogTupleDelete call. + - Accepted PG's changes and keeping YB's DDL nesting logic and CatalogTupleDelete call inside `replorigin_drop_by_name`. + +- src/backend/replication/logical/Makefile: + - OBJS list: PG added `applyparallelworker.o` and `conflict.o`; YB added `yb_decode.o` and `yb_virtual_wal_client.o`. Kept both: YB objects first, then PG's. + +- src/backend/replication/logical/snapbuild.c: + - includes and struct definition: + - PG commit e2fd615ecc177493b9a961a640ec0dcc4a25755c moved `struct SnapBuild` definition to `snapbuild_internal.h`. + - YB added `#include "pg_yb_utils.h"`. + - `struct SnapBuild` has no YB-specific fields; kept YB's include, accepted PG's removal of the struct (now in `snapbuild_internal.h`). + +- src/backend/rewrite/rewriteDefine.c: + - DefineQueryRewrite: + - PG commit b23cd185fd5410e5204683933f848d4583e34b35 completely removed the logic for converting relation to view + - YB added a check to prevent converting system tables + - Accepted PG's removal, discarding YB's changes + +- src/backend/storage/ipc/ipci.c: + - AttachSharedMemoryStructs: + - PG commits 283e823f9dcb03d0be720928b261628af06d3fd4 (new shmem registration mechanism) through 9b5acad3f40fa6015f367fbf887ae5c1a93a3698 (convert all remaining subsystems) replaced all individual `*ShmemInit()` calls with `ShmemAttachRequested()` and `RegisterShmemCallbacks` API. + - YB added `YbAshShmemInit()`, `YbQueryDiagnosticsShmemInit()`, `YbQpmShmemInit()`, `YbTerminatedQueriesShmemInit()`. + - Took PG's `ShmemAttachRequested()` + appending YB's shmem init calls. Added YB_TODO_PG19MERGE to verify if YB calls need changes. + +- src/backend/utils/adt/network.c: + - match_network_subset: generalized AM check pattern. + +- src/backend/utils/init/miscinit.c: + - GetBackendTypeDesc switch: + - PG commit dbf8cfb4f02eb9ec5525de1761675f9babfd30e3 replaced explicit case list with `PG_PROCTYPE` macro from `proctypelist.h`. + - YB added `YB_YSQL_CONN_MGR`, `YB_YSQL_CONN_MGR_WAL_SENDER`, `YB_AUTO_ANALYZE_BACKEND`, `YB_INDEX_BACKFILL_DDL`, `YB_MATVIEW_REFRESH_DDL` cases. + - Took PG's macro expansion + appending YB's custom cases after. Add a YB_TODO_PG19MERGE to use PG macro for YB cases as well. + +- src/backend/utils/misc/Makefile: + - OBJS list: + - PG added `conffiles.o`. + - YB added `pg_yb_utils.o`, `yb_ash.o`, `yb_exceptions_for_func_pushdown.o`, etc. + - Kept both: YB objects first, then PG's `conffiles.o`. + +- src/backend/utils/misc/help_config.c: + - mixedStruct union moved into config_generic: + - PG commit a13833c35f9e07fe978bf6fad984d6f5f25f59cd moved the `mixedStruct` union into `config_generic` and removed the standalone `mixedStruct` (union of config_* types) typedef. Function signatures changed from `mixedStruct *` to `const struct config_generic *`. + - YB commit 399c47632a45b66545b7f4579cbaa1d70abc2c89 added `struct yb_config_oid oid` member to `mixedStruct`. + - Accepted PG's removal of standalone `mixedStruct` in `help_config.c`. Removed `struct yb_config_oid oid` from the union for now as it needs to be reworked (either integrated into PG's new `config_generic`, or kept as the PG15 form with the embedded `config_generic` -- see build fixes). + +- src/bin/pg_dump/Makefile: + - pg_dumpall / ysql_dumpall target: + - PG commit c1da7281060d646f863e920a1aac3b9dbc997672 removed dumputils.o and added `$OBJS`. + - YB changed to `ysql_dumpall` and uses `$(YB_CCLD)`. + - Accepted PG's changes and keeping YB's `ysql_dumpall`, `$(YB_CCLD)`. + +- src/bin/pg_waldump/.gitignore: + - file entries: + - PG commit db6957bae8d7716785aa3748b25a9a4b7c3ff304 added `/rmgrdesc_utils.c` entry. + - YB commented out the source file entries. + - Took YB's commented-out style for `*desc.c` files (symlinked by YB's build, per commit 56b36bdb38c7fea8a13a5e489e08046b289623f5) + keeping PG's `/rmgrdesc_utils.c` uncommented for now (not matched by the `*desc.c` wildcard in the Makefile, so it gets copied not symlinked). Added a YB_TODO_PG19MERGE comment to consider adding `rmgrdesc_utils.c` to the symlink list in the Makefile. + +- src/include/Makefile: + - SUBDIRS: PG added `archive`, switched to multi-line backslash format; YB added `ybgate`. Kept PG's format with `archive` + YB's `ybgate`. + +- src/include/access/amapi.h: + - IndexAmRoutine struct fields: + - PG added `amtranslatestrategy` and `amtranslatecmptype` function pointers. + - YB added additional fields. + - Kept both: PG's functions pointers first, then YB's fields. + +- src/include/access/heapam.h: + - function declarations: + - PG replaced `heap_inplace_update` with `heap_inplace_update_and_unlock` (commit a07e03fd8fa7daf4d1356f7cb501ffe784ea6257) and added new functions. + - The PG15 backpatch for a07e03fd8fa7daf4d1356f7cb501ffe784ea6257 retained `heap_inplace_update` as deprecated, which arrived in YB via merge commit e99df6f4d97e5c002d3c4b89c74a778ad0ac0932. + - YB also added `yb_shared_update` parameter to `heap_inplace_update`. + - Accepted PG's declarations only. + +- src/backend/access/heap/heapam.c: + - heap_inplace_unlock / heap_inplace_update: + - See above resolution. Dropped `heap_inplace_update` declaration and definition from `heapam.c` - no callers exist in YB. Added a YB_TODO_PG19MERGE to a comment in `ybModifyTable.c` referencing this function. + +- src/include/access/htup_details.h: + - HeapTupleNoNulls / HeapTupleHeaderHasNulls: + - PG changed `HeapTupleNoNulls` from a macro to an inline function. + - YB commit 12e23adb6ac517cde3bacfcbe64a8292470701cf added `HeapTupleHeaderHasNulls` + - Kept PG's inline `HeapTupleNoNulls` and making YB's macro an inline function as well. + +- src/include/access/tupconvert.h: + - `execute_attr_map_cols` signature: + - PG commit bfcf1b34805f70df48eedeec237230d0cc1154a6 renamed parameter from `inbitmap` to `in_cols`. + - YB commit bb737232781348657e8efc44e89c7f38c6c63013 added `Relation rel` parameter. Although this commit is a PG import, the additional parameter was a YB change. + - Combined both: PG's `in_cols` name + YB's extra parameter renamed to `yb_rel`. + +- src/include/access/xact.h: + - Isolation level comment block: + - PG changed the comment. + - YB added a YB note in the comment. + - Kept PG's rewritten comment and appending YB's Read Committed note. + +- src/include/catalog/catversion.h: + - CATALOG_VERSION_NO: + - PG updated to `202604061`. + - YB has `202209061` with a comment about not needing to bump on import. + - Took PG's version `202604061` and keeping YB's comment. + +- src/include/catalog/heap.h: + - InsertPgAttributeTuples signature: + - adjacent line conflict. + - PG commit d939cb2fd612acde0304913213cfbdb01994e682 changed `Datum *attoptions` to `const FormExtraData_pg_attribute tupdesc_extra[]`. + - YB added `bool yb_relisshared` parameter. + - Took PG's new parameter type and appending YB's `bool yb_relisshared`. + +- src/include/catalog/pg_default_acl.h: + - Default ACL object types: + - PG added `DEFACLOBJ_LARGEOBJECT 'L'`. + - YB added `DEFACLOBJ_TABLEGROUP 'g'`. + - Kept both defines, PG's first then YB's. + +- src/include/catalog/pg_opfamily.h: + - Boolean opfamily macro: + - adjacent line conflict. + - PG commit ff720a597c0a53a8fcdf2cf4e45248dc5c37f9ab renamed `IsBooleanOpfamily` to `IsBuiltinBooleanOpfamily` with a comment about non-core opfamilies. + - YB commit 5c6b021522ac936a5e5150f79c1a2de61a481a3e added `BOOL_LSM_FAM_OID` to the condition. + - Used PG's macro name `IsBuiltinBooleanOpfamily` and adding YB's `BOOL_LSM_FAM_OID` to the OR condition. + +- src/include/commands/copy.h: + - `DoCopy` declaration and batch macro: + - PG commit bfcf1b34805f70df48eedeec237230d0cc1154a6 renamed `DoCopy` parameter from `state` to `pstate`. + - YB added `DEFAULT_BATCH_ROWS_PER_TRANSACTION` macro and `yb_default_copy_from_rows_per_transaction` extern. + - Kept YB's macro/extern first, then PG's `DoCopy` declaration. + +- src/include/commands/copyfrom_internal.h: + - `CopyInsertMethod` enum: + - PG updated enum comments. + - YB added a comment about `YBCExecuteInsert`. + - Took PG's enum descriptions and keeping YB's comment about YB insert methods. + +- src/include/commands/explain.h: + - ExplainState extraction: + - PG commit c65bc2e1d14a2d4daed7c1921ac518f2c5ac3d17 moved `ExplainState` to `explain_state.h`, leaving only a forward declaration. + - YB commit 536e4b1974268041302274d6a45ffba157d10283 added `YbExplainExecStats` struct YB also has a bunch of YB fields in `ExplainState`. + - Took PG's forward declaration in `explain.h` + adding YB include. YB-specific fields moved to `explain_state.h` where PG relocated the struct. + +- src/include/commands/repack.h: + - `finish_heap_swap` signature: + - PG renamed `minMulti` to `cutoffMulti`. + - YB commit ce62d6c567cb358a9595def17d9092b27b7e26b4 added `yb_copy_split_options` parameter (last touched by 5627af5d6009b6d5b14bbbb552f6fd2a9ae43c3a). YB commit 03507f91525729e1eb0e2790c1a96e3a5c19e4e3 added `changedIndexNames`, `changedIndexSplitOpts` parameters. + - Used PG's `cutoffMulti` name and appending YB's extra parameters. + +- src/include/commands/tablecmds.h: + - `ExecuteTruncateGuts` signature: + - PG added `bool run_as_table_owner`. + - YB added `bool yb_is_top_level`. + - Kept both parameters, PG's first then YB's. + +- src/include/executor/execExpr.h: + - ExprEvalStep union members: + - PG commit 6ee30209a6f161d0a267a33f090c70c579c87c00 added `is_json` struct. PG commit 6185c9737cf48c9540782d88f12bd2912d6ca1cc added `jsonexpr`, `jsonexpr_coercion` structs. + - YB commit ada31e719db783e6c26b1b0b8c9447174ad7cbc5 added `row_array_compare` struct. + - Kept PG's JSON structs first, then YB's `row_array_compare`. + +- src/include/executor/execdesc.h: + - QueryDesc instrumentation field: + - PG commit 2c16deee2f7d52d6567dcbad046f74a8e880ee52 changed `totaltime` to `query_instr` with updated comment. + - YB commit 536e4b1974268041302274d6a45ffba157d10283 added `yb_query_stats` field. + - Took PG's `query_instr`, appending YB's `yb_query_stats`, add a YB_TODO_PG19MERGE to update the YB comment and verify if the note about the life cycle still applies to `query_instr`. + +- src/include/fmgr.h: + - FmgrInfo struct: + - adjacent line conflict. + - PG commit 4bd91912987d794c48dd4ba4c337906bd23759be changed fmgr.h typedefs to use `Node *fn_expr`. + - YB commit 544122690311196d40059d9ea1311211df216247 added `void *fn_alt` (last touched by lint fix f0f54c4a7ade831f367aceb936800cbe18bf4822). + - Took PG's `Node *fn_expr` type and adding YB's `void *fn_alt` field. + +- src/include/libpq/libpq-be.h: + - Port struct fields: + - PG commit d951052a9e02bfacad8bd6f0f53a4dcd3b7e6d1f moved `authn_id` and its comment block out of `Port` into a new `ClientConnectionInfo` struct. + - YB has YB fields. + - Kept YB's fields after `hba`. + +- src/include/nodes/nodeFuncs.h: + - End-of-file declarations: + - PG commit 1c27d16e6e5c1f463bbe1e9ece88dda811235165 removed the `struct PlanState;` forward declaration and the `planstate_tree_walker` function declaration from the end of the file (moved to the top of the file). + - YB commit 9ed10e1cc21f64540dd775993ca59adacac48284 added `YbPlanStateTryGetAggrefs` declaration after `planstate_tree_walker`. + - Kept YB's `YbPlanStateTryGetAggrefs` declaration. + +- src/include/optimizer/pathnode.h: + - Function declarations: + - PG commit 8e11859102f947e6145acdd809e5cdcdfbe90fa5 added `create_rel_agg_info`. + - YB added YB functions. + - Kept PG's function first, then appended YB's functions. + +- src/include/optimizer/paths.h: + - Function declarations: + - PG added `ec_clear_derived_clauses`. + - YB added `yb_find_ec_member_for_var`. + - Kept both declarations. + +- src/include/optimizer/planner.h: + - Function declarations: + - PG added some functions. + - YB added YB functions. + - Kept PG's declarations first, then YB's. + +- src/include/optimizer/restrictinfo.h: + - Inline function and declarations: + - PG commit 2453196107de66cff0257feef2ff8585dcf9d924 moved `clause_sides_match_join` inline function here. + - YB added YB batching-related functions (`yb_can_hash_batched_rinfo`, `yb_can_batch_rinfo`). + - Kept PG's inline function, then appended YB's batching functions. + +- src/include/partitioning/partdesc.h: + - Function declarations: + - PG commit e033696596566d422a0eae47adca371a210ed730 changed the `#endif` guard comment from `PARTCACHE_H` to `PARTDESC_H`. + - YB added `RelationBuildPartitionDesc` extern declaration before `#endif`. + - Kept YB's extern declaration before `#endif` with PG's corrected `PARTDESC_H` guard name. + +- src/include/partitioning/partprune.h: + - Function declarations: + - PG (commit d4d1fc527bdb333d818038081c17ed7d9b1697c1 and others) changed `make_partition_pruneinfo` return type to `int` and removed `struct` keyword from parameter types. + - YB added `yb_oids` parameter to `prune_append_rel_partitions`. + - Took PG's `int` return type and adding YB's `yb_oids` parameter. + +- src/include/postmaster/postmaster.h: + - Function declarations: + - PG commit f1baed18bc3db50c72bfb00b6247b47689158445 added `#ifdef WIN32` block. + - YB commit 44a0bfb01cc0678246350160782898b80cc3ded5 added `YbProcessStartupPacket` and related declarations. + - Kept both: YB's declarations, then PG's `#ifdef WIN32` block. + +- src/include/regex/regguts.h: + - `STACK_TOO_DEEP` / `CANCEL_REQUESTED` area: + - PG commit db4f21e4a34b1d5a3f7123e28e77f575d1a971ea removed the `CANCEL_REQUESTED` macro. + - YB commit 8033dd42d675e62a16f7322f20b146c3cdf63e06 modified PG's `STACK_TOO_DEEP` macro (with `IsMultiThreadedMode()` check). + - Kept YB's modified `STACK_TOO_DEEP`. Removed the `CANCEL_REQUESTED` macro. + +- src/include/replication/logicalproto.h: + - `logicalrep_write_update` signature: + - PG (commit e65dbc9927fb86aa3c8a914ede6a6ae934384f5a and others) added `PublishGencolsType include_gencols_type` parameter, changed formatting. + - YB commit fddfec620d291de3423277812bbe6097dfcbce3d added `bool *yb_old_is_omitted` and `bool *yb_new_is_omitted` parameters (last touched by merge commit c177183b65d47090b59af81d15e09329ae065571). + - Kept PG's `include_gencols_type` parameter first, then appending YB's `yb_old_is_omitted` and `yb_new_is_omitted` parameters. + +- src/include/replication/output_plugin.h: + - OutputPluginOptions fields: + - PG added `bool need_shared_catalogs`. + - YB added `List *yb_publication_names`. + - Kept both. + +- src/include/parser/kwlist.h: + - new keywords (3 conflicts): + - PG added `"node"`, `"source"`, `"split" -> SPLIT`, `"target"` + - YB added `"split" -> _YB_SPLIT_P`,`"nonconcurrently" -> _YB_NONCONCURRENTLY_P`, `"tablets" -> _YB_TABLETS_P`. + - Interleaved the non-conflicting entries alphabetically. For the `"split"` collision, dropped YB's `_YB_SPLIT_P` and renamed the YB grammar to use PG's `SPLIT` token in gram.y. Safe because PG's `SPLIT` appears only in `ALTER TABLE ... SPLIT PARTITION` (alter_table_cmd) while YB's `SPLIT` appears only in `YbOptSplit` under CREATE TABLE/INDEX, with disjoint lookaheads (`PARTITION` vs `'('`/`INTO`/`AT`). + +- src/include/storage/proc.h: + - PGPROC struct layout: + - PG commit 2e0853176f8f28a7684aa8b5af73446332960725 rearranged fields and added "Status reporting" header. + - YB added YB fields. + - Kept PG's layout with "Status reporting" section, then appending all YB-specific fields after `wait_event_info`. + +- src/include/tcop/tcopprot.h: + - Signal handler declarations: + - PG commit 3691edfab97187789b8a1cbb9dce4acf0ecd8f5a changed `FloatExceptionHandler` to use `pg_noreturn`. PG commit 0da096d78e1e49645ff9baf6e425d3c47c5a5dc0 changed `RecoveryConflictInterrupt` to `HandleRecoveryConflictInterrupt`. + - YB commit ca25fb9eb52b8bdbdc66987edcf5596ad61b7da9 added `YbCriticalSignalHandler`. + - Took PG's `pg_noreturn` and `HandleRecoveryConflictInterrupt` + adding YB's `YbCriticalSignalHandler`. + +- src/include/utils/catcache.h: + - Cache statistics types: + - PG commit 9c047da51f270b25fe03ee114e1de0c64aa9cc18 changed `long` to `uint64` for `cc_invals`, `cc_lsearches`, `cc_lhits`. + - YB added `yb_cc_size_bytes` field (as `long`). + - Took PG's `uint64` types and keeping YB's `yb_cc_size_bytes` (with `uint64`). + +- src/include/utils/guc_tables.h: + - GUC struct definition: + - PG commit a13833c35f9e07fe978bf6fad984d6f5f25f59cd reorganized GUC structs, moving type-specific structs above `config_generic`, effectively moving `config_generic` down within the file. + - YB commit 450c0dd0d5b8e3e0d0bbb0314c01f3a0b78cf746 added `ysql_conn_mgr_saved_default` field and `YB_GUC_VALUE_RESET`/`YB_GUC_DEFAULT_RESET` defines. + - Kept PG's struct definition and adding YB's field and defines in the new location. + +- contrib/file_fdw/file_fdw.c: + - PG_MODULE_MAGIC_EXT: + - PG commit 55527368bd07248e91e3d37a782bf66b76f06865 replaced `PG_MODULE_MAGIC` with `PG_MODULE_MAGIC_EXT`. + - YB added YB includes. + - Kept YB includes then PG's `PG_MODULE_MAGIC_EXT`. + - NextCopyFrom on_error handling: + - PG (commit a1c4c8a9e1e3a53996dafa1f4ee6d4f7de2c58b2 and others) wrapped `NextCopyFrom` in an outer `if`, added `on_error`/`reject_limit` retry loop with `goto retry`, and switched 2nd arg from `NULL` to `econtext`. + - YB commit d3a6eb327e90ae0f5b659c9027828030ed987062 added a trailing `skip_row` bool parameter to `NextCopyFrom` + - Kept PG's changes, and passing `false /* skip_row */` for the YB parameter. + +- contrib/postgres_fdw/option.c: + - postgres_fdw_validator: + - PG commit 8ad51b5f446b5c19ba2c0033a0f7b3180b3b6d95 added `analyze_sampling` `else if` validation branch. + - YB commit 67eb85a7d65146ffeca9730c0e5a99c20419fa68 added `server_type` `else if` validation branch. + - Kept both: PG's `analyze_sampling` branch first, then YB's `server_type` branch. + - InitPgFdwOptions: + - PG commit a2eb99a01e015a76682911ae3980762f6ee6ac8c changed `gssdeleg` to `gssdelegation`. + - YB commit 67eb85a7d65146ffeca9730c0e5a99c20419fa68 added `server_type` entry. + - Kept both: PG's `gssdelegation` entry first, then YB's `server_type` entry. + +- contrib/postgres_fdw/deparse.c: + - deparseFromExprForRel: + - PG commit 824dbea3e41efa3b35094163c834988dea7eb139 added a `JOIN_SEMI` branch and moved the existing JOIN ON deparse into the `else` arm. + - YB added `context.yb_min_attr = fpinfo->yb_min_attr;` to the context init. + - Took PG's restructuring and adding YB's `yb_min_attr` assignment in each new `context` init site. + - deparseUpdateSql/deparseDeleteSql variables (2 conflicts): + - PG added `additional_conds` variable. + - YB added `yb_min_attr` variable. + - Kept both variables. + +- doc/src/sgml/ref/allfiles.sgml: + - SGML entity declarations: + - PG commit 2f094e7ac691abc9d2fe0f4dcf0feac4a6ce1d9c added `createPropertyGraph` and `dropPropertyGraph` entities. + - YB commit 7ad368777cdc4a1037c0ab277ef978bf73140bda added `createProfile` and `dropProfile` entities. + - Kept both: PG's entities first, then YB's. + +- src/backend/access/brin/brin.c: + - Below header file includes: + - PG commit b437571714707bc6466abde1a0af5e69aaade09c added parallel index build code below the header file includes. + - YB added YB include `#include "utils/guc.h"`. + - Kept PG's changes and dropping YB's include since PG commit 0a20ff54f5e66158930d5328f89f087d4e9ab400 already added `#include "utils/guc.h"` above. + - table_index_build_range_scan call: + - PG commit 7f798aca1d5df290aafad41180baea0ae311b4ee removed `(void *)` cast from `state` argument. + - YB commit 3ccf55af8c24e0f4bb112d8aee08d4434cebd979 introduced `bfinfo`/`bfresult`. The `NULL` trailing arguments were added during the PG15 initial merge(55782d561e55ef972f2470a4ae887dd791bb4a97). + - Took PG's cast removal and keeping YB's trailing `bfinfo`/`bfresult` arguments. + +- src/backend/access/transam/varsup.c: + - shmem variables: + - PG commit b31ba5310b5176402b60abc0454a033b1210ab75 renamed `ShmemVariableCache` to `TransamVariables`, and other commits c6d55714ba4c282dcf5fb5fe5ef2a5cad0b06e81 added more code below. + - YB added `YB_OID_PREFETCH` and `ysql_upgrade_next_oid`. + - Kept YB's original ordering: `YB_OID_PREFETCH` define right after `VAR_OID_PREFETCH`, then PG's `TransamVariables` and shmem API, then `ysql_upgrade_next_oid`. + - GetNewObjectId: + - PG commit b31ba5310b5176402b60abc0454a033b1210ab75 changed `ShmemVariableCache` references to `TransamVariables`. + - YB commit ca30a3ab5252858103cf6f3f92697821e9b718df added `YBCReserveOids` path. YB commit 8a3278d913d0d0cb4dcf7cc9df98c630e2bd456f added `ysql_enable_pg_per_database_oid_allocator` guard. + - Changed YB's `YBCReserveOids` block to use `TransamVariables` (renamed from `ShmemVariableCache`); preserved PG's changes in the else block. + +- src/backend/bootstrap/bootstrap.c: + - includes and globals: + - PG commit 935e675f3c9efd0e39bf33db15ab85049cc4ee7c removed `bootstrap_data_checksum_version` global. + - YB has YB includes. + - Kept YB includes, dropping `bootstrap_data_checksum_version`. + - scanner init / YBCCreateDatabase: + - PG commit 3e4bacb171001644583ac14e29ae1b09ce818c92 added `boot_yylex_init`. + - YB commit ca30a3ab5252858103cf6f3f92697821e9b718df added `YBCCreateDatabase` call. + - Kept both: PG's `boot_yylex_init` block then YB's `YBCCreateDatabase` block. + +- src/backend/catalog/Makefile: + - build system (2 conflicts): + - PG commit 6ab2e8385d55e0b73bb8bbc41d9c286f5f7f357f moved all bki-generation machinery out of src/backend/catalog/Makefile and into src/include/catalog/Makefile, and slimmed install-data/uninstall-data here to just the static *.sql files. + - YB content inside the conflict markers: YB pg_yb_*.h in `CATALOG_HEADERS`; YB *.dat in `POSTGRES_BKI_DATA`; `yb_bki-stamp` rule (yb_genbki.pl -> yb_postgres.bki) wired into distprep + header-stamp; `yb_postgres.bki` install line; uninstall additions ( `yb_system_functions.sql`, `yb_system_views.sql`); `clean`/`maintainer-clean` covering yb_bki-stamp + yb_postgres.bki. + - Took PG's install-data, PG's slim uninstall-data list extended with YB's three additions (`yb_system_functions.sql`, `yb_system_views.sql`). Ported YB pg_yb_*.h, YB *.dat, `yb_bki-stamp` rule (wired into `generated-headers`), and `yb_postgres.bki` install/uninstall/clean into `src/include/catalog/Makefile`. Dropped `header-stamp` dep + `distprep` wiring since PG removed those. + +- src/backend/catalog/pg_publication.c: + - includes: + - PG added published_rel, removed publication_translate_columns. + - YB added YB includes and `yb_pg_relation_is_publishable` declaration. + - Combined both changes. + - is_table_publication / yb_pg_relation_is_publishable: + - PG commit 493f8c6439cf64d75883c650b5dd573d8fe0664b added `is_table_publication()`. PG commit 7054186c4ebe24e63254651e2ae9b36efae90d4e added `check_and_fetch_column_list()`. + - YB commit ef3577d3cd65a3fbc8de9cff0c36c5fafc026dc3 added `yb_pg_relation_is_publishable` call in `pg_relation_is_publishable`. + - Kept PG's new functions and YB's modification to `pg_relation_is_publishable`. + +- src/backend/catalog/pg_shdepend.c: + - dependency type cases: + - PG added `SHARED_DEPENDENCY_INITACL` case. + - YB added `SHARED_DEPENDENCY_PROFILE` case. + - Kept both cases. + - end-of-file functions: + - PG added `shdepReassignOwned_Owner()` and `shdepReassignOwned_InitAcl()`. + - YB added YB functions. + - Kept both. + +- src/backend/catalog/pg_type.c: + - includes and declarations: + - PG commit 70988b7b0a0bd03c59a2314d0b5bcf2135692349 removed `makeUniqueTypeName`. + - YB added YB includes. + - Kept YB's includes, removed `makeUniqueTypeName`. + - IsYsqlUpgrade guard: + - PG changed the comment below the YB block. + - YB added `IsYsqlUpgrade` early-return guard. + - Kept YB's guard block with PG's comment wording. + +- src/backend/commands/alter.c: + - ExecRenameStmt: + - PG commit 2f094e7ac691abc9d2fe0f4dcf0feac4a6ce1d9c added `case OBJECT_PROPGRAPH`. + - YB added an argument to the `RenameRelation` call. + - Kept PG's case and YB's argument. + - AlterObjectNamespace_oid: + - PG commit 89e5ef7e21812916c9cf9fcf56e45f0f74034656 switched from `OCLASS_*` enum values (via `getObjectClass()`) to direct `*RelationId` constants, and replaced the explicit "ignore" case list with `default:` + Assert. + - YB added `OCLASS_YBTBLGROUP`, `OCLASS_YBPROFILE`, `OCLASS_YBROLE_PROFILE` to the explicit "ignore" case list. + - Took PG's `default:` + Assert, which implicitly covers YB's object classes. + +- src/backend/commands/copy.c: + - attribute number computation: + - PG commit a61b1f74823c9c4f79c95226a461f1e7a367764b moved `attno` definition and added `Bitmapset **bms` variable. + - YB replaced `FirstLowInvalidHeapAttributeNumber` with `YBGetFirstLowInvalidAttributeNumber`. + - Kept PG's definition placement and `bms` variable, using YB's attribute number function. + - COPY options: + - PG added `force_array`, `on_error`, `log_verbosity`, `reject_limit` options. + - YB added YB options `rows_per_transaction`, `skip`, `disable_fk_check`, `replace`. + - Kept PG's options first, then YB options. + +- src/backend/commands/copyfromparse.c: + - CopyGetData: + - PG added `WAIT_EVENT_COPY_FROM_READ`. + - YB added conditional `WAIT_EVENT_YB_COPY_COMMAND_STREAM_READ`. + - Took PG's wait event. Added a YB_TODO_PG19MERGE. + - NextCopyFrom: + - PG commit 7717f63006935de00fafd000bff450280508adf1 refactored inline text/binary parsing into `CopyFromOneRow` callback. + - YB added `skip_row` parameter for skipping format checking on invalid rows. + - Took PG's callback approach. Added YB_TODO_PG19MERGE to port `skip_row` logic into the new callbacks. + +- src/backend/commands/foreigncmds.c: + - CreateForeignServer pointer check: + - PG commit a5b35fcedb542587e7d8b8fcd21a2e0995b82d2f removed `PointerIsValid()`. + - YB added `yb_validate_server_options()`. + - Kept YB's validation call with PG's `DatumGetPointer != NULL` style. + - AlterForeignServer: + - Same pattern. Resolved same way. + +- src/Makefile.global.in: + - CPPFLAGS: + - PG commits 65c298f61fc70f2f960437c05649f71b862e2c48, 8eadd5c73c44708ecd45b9fd3ac54a550511d16f added `LIBNUMA_CFLAGS`, `LIBURING_CFLAGS`. PG commit 4300d8b6a79d61abb5ca9f901df7bde7a49322b6 changed from `:= ... $(CPPFLAGS)` to `+=`. + - YB added `YB_SRC_ROOT` check and `-I$(YB_SRC_ROOT)/src`. + - Used PG's `+=` style with all flags plus YB's `YB_SRC_ROOT` guard and `-I` path. + - prove_installcheck: + - PG commit aeb8ea361a0a321a0e1cbc79a4cd3ec0b1191bf2 added `share_contrib_dir`. + - YB commit 745bd761e6926c5816a0716716d8548eacbb3d96 imported PG commit c098509927f9a49ebceb301a2cb6a477ecd4ac3c which added `REGRESS_SHLIB`. PG later removed it in commit a0fc813266467d666b8f09ccccaccb700326a296 (moved to `src/test/recovery/Makefile` only, the only test that needs it); however, the YB PG15 merge did not drop it. + - Kept PG's `share_contrib_dir` and dropping `REGRESS_SHLIB`. + - pg_config_ext.h / stamp-ext-h rule: + - PG commit 962da900ac8f0927f1af2fd811ca67fa163c873a removed `pg_config_ext.h` and `stamp-ext-h`. + - YB commit c5ddc7bad7137d51a5c4ec6416bbea7492aeb133 had added `YB_PG_SKIP_CONFIG_STATUS` guard to `stamp-ext-h`. + - Dropped both entries (accepting PG's removal). + +- src/backend/access/common/reloptions.c: + - untransformRelOptions pstrdup: + - PG commit 6710e83a675eda798544fea4cdcb89eef7f39403 removed `pstrdup` calls on both `s` and `p` (passing them directly to `makeDefElem`/`makeString`). + - YB commit 0d9ced1053242ed270d75c5d2b5a02bc4190a900 added `pfree(s)`. + - Took PG's change, discarding YB side. + - StdRdOptions: + - PG commits 4d6a66f675815a5d40a650d4dcfb5ddb89c6ad2f, 052026c9b903380b428a4c9ba2ec90726db81288 changed to `vacuum_truncate` to `TERNARY` type and added `vacuum_max_eager_freeze_failure_rate`. + - YB added YB options. + - Kept PG's changes first, then YB options. + - partitioned_table_reloptions: + - PG commit 4f981df8e0b7bc00d22ab0db65579589c9d4bb8c changed to error out instead of running validation when reloptions are set for a partitioned table. + - YB commit b5f581483f31dddacdb0ccff2d8d51a116ab0662 added support for specifying colocation reloption for parent partitioned tables. + - Kept YB's `IsYugaByteEnabled()` block first, then PG's error. + +- src/backend/access/nbtree/nbtutils.c: + - btree preprocessing split (3 conflicts): + - PG commit 597b1ffbf12352a3863a894f16741864aaf2242f moved most of this file (`_bt_preprocess_array_keys`, `_bt_sort_array_elements`, `_bt_compare_array_elements`, `_bt_find_extreme_element`, `_bt_preprocess_keys`, `_bt_compare_scankey_args`, `_bt_fix_scankey_strategy`, `_bt_mark_scankey_required`, `_bt_check_rowcompare`, `BTSortArrayContext`) into a new file `nbtpreprocesskeys.c`. The new `_bt_sort_array_elements` is `static` and has a different signature (`ScanKey, FmgrInfo *sortproc, bool, Datum*, int` instead of `IndexScanDesc, ScanKey, bool, Datum*, int`). + - YB un-static-ified `_bt_sort_array_elements` (and dropped its forward decl), added an `IsYugaByteEnabled() && !RegProcedureIsValid(cmp_proc)` fallback inside it that uses `lookup_type_cache(elemtype, TYPECACHE_CMP_PROC)`, added `utils/builtins.h` and `utils/typcache.h` includes, and exposed `extern int _bt_sort_array_elements(...)` in nbtree.h. yb_scan.c calls it with the old signature. + - Took PG's slim nbtutils.c (dropped the YB `utils/builtins.h` / `utils/typcache.h` includes since the YB-modified function moved out); removed the YB-added extern from nbtree.h; wrapped the yb_scan.c call site with YB_TODO_PG19MERGE (commented out via `//`, set `*culled_num_elems = num_valid;` as placeholder) since the new function is `static` AND has an incompatible signature - YB caller needs to be rewritten to resolve the FmgrInfo (with the YB fallback) up front and call the new function (after exposing it or adding a wrapper in nbtpreprocesskeys.c). + +- src/backend/catalog/namespace.c: + - MatchNamedCall signature: + - Adjacent line conflict. + - PG added `fgc_flags` parameter. + - YB added `YbBuildTempNameSuffix` declaration. + - Kept both. + - temp namespace naming: + - PG commit 024c521117579a6d356050ad3d78fdc95e44eefa renamed `MyBackendId` to `MyProcNumber`. + - YB commit bde7acd5bd33240037fd389849c28015705a959d added temp namespace suffix. + - Ported YB's suffix logic to use PG's `MyProcNumber`. + +- src/backend/commands/event_trigger.c: + - EventTriggerSupportsObjectType: + - PG replaced the explicit ObjectType case enumeration (and the "intentionally no default" comment) with `default: return true;`. + - YB added `OBJECT_YBPROFILE` / `OBJECT_YBTABLEGROUP` cases (return false). + - Inserted YB's two cases before PG's `default: return true;`; dropped YB's "intentionally no default" comment block since PG added a default. + - EventTriggerSupportsObject: + - PG renamed `EventTriggerSupportsObjectClass(ObjectClass objclass)` to `EventTriggerSupportsObject(const ObjectAddress *object)` and switched the switch from `OCLASS_*` enum cases to `RelationId` cases on `object->classId`; also added `default: return true;`. + - YB added `OCLASS_YBTBLGROUP`, `OCLASS_YBPROFILE`, `OCLASS_YBROLE_PROFILE` cases (return false). + - Ported YB's cases to PG's RelationId style: `YbTablegroupRelationId`, `YbProfileRelationId`, `YbRoleProfileRelationId`, placed before `default: return true;`. Added `catalog/pg_yb_tablegroup.h`, `pg_yb_profile.h`, `pg_yb_role_profile.h` includes for the RelationId macros. + - palloc_array: + - PG changed to use `palloc_array`. + - YB added `else command->d.atscfg.dictIds = NULL;` guard. + - Kept PG's `palloc_array` and YB's `else` NULL guard. + +- contrib/postgres_fdw/postgres_fdw.c: + - PG_MODULE_MAGIC and YB includes: + - PG commit 55527368bd07248e91e3d37a782bf66b76f06865 replaced `PG_MODULE_MAGIC` with `PG_MODULE_MAGIC_EXT`. + - YB added YB-specific includes. + - Kept YB includes then PG's `PG_MODULE_MAGIC_EXT`. + - postgresGetForeignPaths: + - PG commit 9e9931d2bf40e2fea447d779c2e133c2c1256ef3 adds 4th argument (NIL) to `add_paths_with_pathkeys_for_rel` call. + - YB wraps call in `yb_server_type` check. + - Kept YB's conditional guard; updated to PG's 4-arg signature. + - postgresBeginForeignScan: + - PG commit 599b33b9492dfefd1219c1d31801f40b3ba90b0d changed and moved `user_id` assignment. + - YB commit 54ec9a1a2e2c94dfb0e59516fcfcb4e66149cf19 added federated YugabyteDB server type checking. + - Kept YB's federated server checks and discarded the old `user_id` assignment. + - fetch_more_data function restructure: + - PG commit 80aa9848befc13c188d2775a859deaf172fdd3a2 removed the `PG_TRY()` block, simplified `pgfdw_get_result` (2 args to 1) and `pgfdw_report_error` (5 args to 3). + - YB commit 65ec75f603937d63636c1668aeeca64970b9a226 added `else if (fsstate->yb_gvr)` branch, moved error check from `else` branch to a common location after the if/else if/else, and added conditional `eof_reached` for `yb_gvr`. However, the error check inside the `if (async_capable)` branch was not removed (likely an oversight). YB commit ec3a95daf09e888a177cce8d2de00bcb7627c3ef changed the YB block to use `YbGlobalViewReadExecScan`. + - Took PG's changes (no `PG_TRY()`, new API signatures, per-branch error checks); applied YB's additions: `else if (fsstate->yb_gvr)` branch (with its own per-branch error check, matching PG's style) and conditional `eof_reached`. Kept per-branch error checks instead of YB's common check to match PG's style. + +- contrib/test_decoding/test_decoding.c: + - PG_MODULE_MAGIC + extern decls: + - PG commit 55527368bd07248e91e3d37a782bf66b76f06865 replaced PG_MODULE_MAGIC with PG_MODULE_MAGIC_EXT; commit fd4bad1655391582f639527c325fc4a99680cc64 dropped `_PG_init`/`_PG_output_plugin_init` extern decls. + - YB added pg_yb_utils.h include. + - Kept YB include + PG_MODULE_MAGIC_EXT; dropped externs. + - tuple_to_stringinfo unchanged-toast branch: + - PG commit 0f5ade7a367c16d823c75a81abb10e2ec98b4206 wrapped `origval` with `DatumGetPointer()`. + - YB added `yb_send_unchanged_toasted ||` short-circuit. + - Combined. + - pg_decode_change tuple_to_stringinfo calls (4 conflicts): + - PG commit 08e6344fd6423210b339e92c069bb979ba4e7cd6 removed ReorderBufferTupleBuf; `change->data.tp.{new,old}tuple` is now `HeapTuple` (no `->tuple`/`->yb_is_omitted`). + - YB added a `yb_is_omitted` arg. + - Used `change->data.tp.{new,old}tuple` directly and passing `NULL /* YB_TODO_PG19MERGE: yb_is_omitted */`. yb_is_omitted storage needs rework (there's a more detailed TODO in `reorderbuffer.h`). + +- src/backend/Makefile: + - SUBDIRS list: + - PG reformatted SUBDIRS and added some subdirs. + - YB added `ybgate` subdirectory. + - Kept PG's multi-line format; appended YB's `ybgate` at the end. + - LDFLAGS override vs YB build system block: + - PG commit 9db49fc5bfdc0126be03f4b8986013e59d93b91d added `override LDFLAGS := $(LDFLAGS) $(LDFLAGS_EX) $(LDFLAGS_EX_BE)`. + - YB commit 56b36bdb38c7fea8a13a5e489e08046b289623f5 (and others) added YB build block (`YB_BUILD_ROOT`, `LIBS`, `SHLIB_LINK`, etc.). + - Kept YB's build block; placed PG's `override LDFLAGS` line after `SHLIB_EXPORTS`. + - Link commands (4 conflict): + - PG commit 9db49fc5bfdc0126be03f4b8986013e59d93b91d removed explicit `$(LDFLAGS_EX)` and `$(export_dynamic)` from link commands. + - YB commit 1564c6d5f552b68553921e66375e0fa1cecaf229 replaced `$(CC)` with `$(YB_CCLD)`. + - Used `$(YB_CCLD)` with PG's simplified flags. + +- src/backend/access/transam/parallel.c: + - FixedParallelState struct: + - PG commit 024c521117579a6d356050ad3d78fdc95e44eefa renamed `BackendId parallel_leader_backend_id` to `ProcNumber parallel_leader_proc_number`. + - YB added YB session fields. + - Kept PG's `ProcNumber`; appended YB's session fields. + - InitializeParallelDSM fps assignment: + - PG commit 024c521117579a6d356050ad3d78fdc95e44eefa assigns `MyProcNumber`. + - YB commit 2f91e3ef37754c96d5074cd33fa2e4c401c52513 dumps YB session state. + - Kept PG's `MyProcNumber` assignment; appended YB's session state dump. + - ReinitializeParallelDSM: + - PG commit f1a6e622bd94735c36d72c663813b55c442739b4 added MemoryContext save/restore. + - YB added YB TODO comment. + - Kept both YB's TODO comment and PG's MemoryContext restore. + - ParallelWorkerMain: + - PG commit 15b4c46c328b25be9463db6d2960eeb16a784aad added `BGWORKER_BYPASS_ROLELOGINCHECK` flag to the `BackgroundWorkerInitializeConnectionByOid` call. + - YB commit caa636fcd02f59d0dfab103b52fd59703745ff64 added YB session init branch (`YbBackgroundWorkerInitializeConnectionByOid`). + - Kept YB's if/else branching; added PG's flag to both branches. + +- src/backend/catalog/catalog.c: + - GetNewOidWithIndex do-while loop: + - PG commit 7fbcee1b2d5f1012c67942126881bd492e95077e adds completion logging. + - YB commit 8d0ef3f7f8c49a8d9bec302cdcc0c40f5d9e785b changed while loop condition to use a new DoesOidExistInRelation helper. + - Kept YB's helper as loop condition and PG's completion logging after loop. Also, rename DoesOidExistInRelation to YbDoesOidExistInRelation and mark it static. + - GetNewRelFileNumber variable declarations: + - PG commits b0a55e43299c4ea2a9a8c757f9c26352407d0ccc and others changed `RelFileNodeBackend to RelFileLocatorBackend`, and `BackendId backendid` to `ProcNumber procNumber` + - YB commit dae4d523d97acdf7436f12824a967d333db09a7a added YbGetAllRelfilenodes() htab and 8d0ef3f7f8c49a8d9bec302cdcc0c40f5d9e785b removed the unused PG declarations (rpath, collides, backend) in the altered function body. + - Kept PG's rlocator, along with YB's HTAB initialization. + - GetNewRelFileNumber Assert and relpersistence switch: + - PG commit 024c521117579a6d356050ad3d78fdc95e44eefa replaced backend-id usages in the switch with proc number. + - YB commit 8d0ef3f7f8c49a8d9bec302cdcc0c40f5d9e785b extracted the switch into `GetBackendOidFromRelPersistence()` helper. YB also relaxed Assert with YB exceptions (`yb_binary_restore`, `yb_extension_upgrade`). + - Kept YB's `GetBackendOidFromRelPersistence()` helper; updated it to use PG's `ProcNumber` API. Kept YB's relaxed Assert. Renamed `GetBackendOidFromRelPersistence` to `YbGetBackendOidFromRelPersistence` and marked it static. + - GetNewRelFileNumber backend assignment: + - PG commit 024c521117579a6d356050ad3d78fdc95e44eefa introduced `procNumber` temp variable and assigns `rlocator.backend = procNumber`. + - YB assigned `rnode.backend` using the helper `GetBackendOidFromRelPersistence` and added a block for YbDatabaseIdForNewObjectId assignment below (adjacent line conflict). + - Used PG's `rlocator.backend` with YB's assignment and block. + - GetNewRelFileNumber collision check loop: + - PG changed `rpath` and `rnode`. + - YB commit 8d0ef3f7f8c49a8d9bec302cdcc0c40f5d9e785b removed the PG code for rpath check and changed the logic of the loop. + - Discarded PG's changes; kept YB's. Removed the inapplicable PG comment above the conflict region. + +- src/backend/catalog/dependency.c: + - object_classes[] array: + - PG commit ef5e2e90859a39efdd3a78e528c544b585295a78 removed the array. + - YB added OCLASS_YBTBLGROUP/YBPROFILE/YBROLE_PROFILE entries. + - Dropped the array. + - pg_fallthrough in findDependentObjects: + - fallthrough pattern. + - doDeletion global object types and YbTablegroupRelationId: + - PG commit 89e5ef7e21812916c9cf9fcf56e45f0f74034656 switched from OCLASS_* enums to *RelationId constants. + - YB commit 7a13a29b22f656473b9d5cb951e7dccb0b006beb added OCLASS_YBPROFILE/YBROLE_PROFILE. + - Converted all YB cases to *RelationId style: `YbTablegroupRelationId` (added by commit 48dc70a0775eed9d598a4c2a6ccd9ef3b965695d), `YbProfileRelationId`, `YbRoleProfileRelationId`. + - getObjectClass() function: + - PG commit 89e5ef7e21812916c9cf9fcf56e45f0f74034656 removed getObjectClass(). + - YB added YB-specific cases. + - Dropped getObjectClass() - no callers remain in the codebase. + +- src/backend/catalog/indexing.c: + - CatalogIndexInsert signature: + - PG commit 19d8e2308bc51ec4ab993ce90077342c915dd116 added TU_UpdateIndexes parameter. + - YB added bool yb_shared_insert parameter. + - Kept both parameters. + - CatalogTupleInsert call and YBCatalogTupleInsert: + - PG commit 19d8e2308bc51ec4ab993ce90077342c915dd116 added TU_All arg. + - YB (commit 5c28dc4a654cc246d0da0807e18d07b81cfe45eb and others) added an arg for yb_shared_insert, + YBCatalogTupleInsert with shared insert logic. + - Passed both TU_All and yb_shared_insert, kept YB's function. + - CatalogTupleInsertWithInfo, CatalogTuplesMultiInsertWithInfo (2 conflicts): + - Same pattern: PG added argument for TU_UpdateIndexes parameter, YB added argument for yb_shared_insert parameter (and yb_shared_insert logic in CatalogTupleInsertWithInfo). + - Combined both changes in each function. + - CatalogTupleUpdate, CatalogTupleUpdateWithInfo (2 conflicts): + - PG added TU_UpdateIndexes out-parameter to `simple_heap_update` and passes it to `CatalogIndexInsert`. + - YB added `YBCatalogTupleUpdate`. + - Kept YB's `YBCatalogTupleUpdate` in the `IsYugaByteEnabled()` branch and PG's changes in the else branch. Also, in CatalogTupleUpdateWithInfo, remove the redundant `CatalogTupleCheckConstraints` inside the else block, since it is already called once before the if/else block. + - CatalogTupleDelete signature: + - PG commit e1ac846f3d2836dcfa0ad15310e28d0a0b495500 changed `ItemPointer tid` to `const ItemPointerData *tid`. + - YB changed to `HeapTuple yb_tup`. + - Took YB's HeapTuple signature for YB's delete path. + +- src/backend/commands/async.c: + - globals: + - PG added `int max_notify_queue_pages` GUC. + - YB added FormData_pg_attribute defs for pg_yb_notifications, the YbNotificationsAtts table, pg_yb_notifications_relation/reloid/relfilenode, ybNotifsPollerPendingEntries, YbListenerQueueScanCurrentXactState, YbNotifsPollerShmemData. + - Kept both: PG's GUC line first, then the YB block. + - function declarations: + - PG removed the `asyncQueuePageDiff` doc comment. + - YB added a block of yb* static helper declarations (NOTIFY/LISTEN/poller helpers). + - Kept YB's declarations and dropped the `asyncQueuePageDiff` comment. + - BecomeRegisteredListener loop + setup (2 conflicts): + - PG commit 024c521117579a6d356050ad3d78fdc95e44eefa and others renamed `BackendId` to `ProcNumber` / `InvalidBackendId` to `INVALID_PROC_NUMBER` / `MyBackendId` to `MyProcNumber` (and changed indexing); added `QUEUE_BACKEND_WAKEUP_PENDING` / `QUEUE_BACKEND_IS_ADVANCING` assignments. + - YB added `bool ybIsFirstListenerOnNode = QUEUE_FIRST_LISTENER == InvalidBackendId;` and an `if (IsYugaByteEnabled()) max = head;` block before the QUEUE_BACKEND_* assignments. + - Took PG's renames + new assignments and porting YB's two additions to use `INVALID_PROC_NUMBER` / `MyProcNumber`. + - PrepareTableEntriesForListen (formerly Exec_ListenCommit body): + - PG commit 282b1cde9dedf456ecf02eb27caf086023a7bb71 removed `Exec_ListenCommit` and added `PrepareTableEntriesForListen` + - YB commit 81186956ca98516f9634ae9541b7e707d07405ca added a `YbIsClientYsqlConnMgr()` block at the end of the original Exec_ListenCommit. + - Took PG's changes. Added a YB_TODO_PG19MERGE. + - asyncQueueUnregister: + - PG changed to INVALID_PROC_NUMBER + - YB added ybCleanupListenState(). + - Kept PG's changes; appended YB's cleanup call. + - SignalBackends second loop: + - PG commit 282b1cde9dedf456ecf02eb27caf086023a7bb71 rewrote SignalBackends + - YB had appended `|| IsYugaByteEnabled()` to a condition that no longer exists. + - Took PG's check. Added a YB_TODO_PG19MERGE. + - asyncQueueProcessPageEntries commit check: + - PG added a `LocalChannelTableIsEmpty()` quick-check + comment before `TransactionIdDidCommit(qe->xid)`. + - YB added `IsYugaByteEnabled() ||` short-circuit to the commit check. + - Kept PG's quick-check + comment, then combining: `if (IsYugaByteEnabled() || TransactionIdDidCommit(qe->xid))`. + +- src/backend/commands/copyto.c: + - CopySendEndOfRow WAIT_EVENT_COPY_TO_WRITE: + - PG commit e05a24c2d4eab3dd76741dc6e6c18bb0584771c5 and others added WAIT_EVENT_COPY_TO_WRITE, removed the line termination block. + - YB added YB-specific wait event. + - Kept both wait events. Added a YB_TODO_PG19MERGE. + - CopySendEndOfRow pgstat_report_wait_end: + - PG commit e05a24c2d4eab3dd76741dc6e6c18bb0584771c5 added pgstat_report_wait_end + - YB also added pgstat_report_wait_end guarded by IsYugabyteEnabled() + - Kept PG's pgstat_report_wait_end. + - BeginCopyTo data_dest_cb: + - PG commit 9fcdf2c787ac6da330165ea3cd50ec5155943a2b added a `if (data_dest_cb)` branch. + - YB commit 5e1a3dc5de25fb42f3291a3633c597f90cacef59 adds CP_IN_PROG tracking above the if/else block. + - Combined both. + - DoCopyTo (2 conflicts): + - PG commit 4bea91f21f61d01bd40a4191a4a8c82d0959fffe extracted the per-relation scan loop into a new CopyRelationTo helper. + - YB commit 0542f163aaf0b2b21347601cb61f634e7ccbce70 adds YB memory context management. + - Kept PG's changes and ported YB's memory context management into PG's CopyRelationTo (after root_slot and map setup). + +- src/backend/commands/dbcommands.c: + - createdb variable declarations and checks: + - PG commit b380a56a3f9556588a89013b765d67947d54f7d0 added newline check. + - YB added YB variables and IsYsqlUpgrade check. + - Kept both. + - createdb option processing: + - PG commit fce7cb6da09b56462fc734e789348376848caf4c renamed dcollversion to collversionEl. + - YB added dcolocated/dclonetime processing. + - Kept YB's options and PG's collversionEl naming. + - createdb locale validation: + - PG commit 5b40feab594c3019fd6b09e46f97f5b367050cf9 added provider-specific error hints. + - YB commit 292786f5e2ede375f1261739aa9a5f8d01e6250e added YbCheckUnsupportedLibcLocale() calls. + - Kept PG's hints; appended YB's locale checks. + - RenameDatabase variable declarations: + - PG commit b380a56a3f9556588a89013b765d67947d54f7d0 added newline check. + - YB commit 5e0cc5a2b4e1962a86261d971bce7f4ac82d6bab added connection manager variables. + - Kept both. + - AlterDatabase array initialization: + - PG commit 9fd45870c1436b477264c0c82eb195df52bc0919 added zero-initialization. + - YB commit 25c7f934df8b4f6c138cc9ac88b9e3ecc5ce01be added unsupported_options array. + - Kept both. + +- src/backend/commands/functioncmds.c: + - CreateFunction namespace ACL check: + - adjacent line conflict. + - PG commit c727f511bd7bf3c58063737bcf7a8f331346f253 refactored to `object_aclcheck`. + - YB commit 453b4d8337fb7b14981b47c3a108037d6035dee4 added `IsYbDbAdminUser` bypass. + - Used PG's API with YB's bypass. + - CreateFunction language ACL check: + - PG commit c727f511bd7bf3c58063737bcf7a8f331346f253 refactored to `object_aclcheck`. PG commit 3e0fff2e6888e39b0ad5cdfdb78bc1c2bb2b22c9 removed block-local `AclResult aclresult` declaration. + - YB commit 453b4d8337fb7b14981b47c3a108037d6035dee4 added `IsYbDbAdminUser` bypass. + - Used PG's API and function-scope declaration with YB's bypass. + - AlterFunction owner check: + - similar to above. + +- src/backend/commands/matview.c: + - SetMatViewPopulatedState call: + - PG changed `stmt->skipData` arg to `skipData`. + - YB added `yb_in_place_refresh` argument. + - Used PG's `!skipData` with YB's third argument. + - make_new_heap / data generation block: + - PG replaced LockRelationOid with Assert and restructured data generation. + - YB added argument for `yb_copy_split_options` and xCluster guard. + - Combined PG's restructured block with YB's arguments and guard. + - opcmethod Assert: + - PG removed the `Assert` entirely. + - YB extended the `Assert` to include LSM_AM_OID. + - Accepted PG's removal. + - finish_heap_swap call: + - PG commit 28d534e2ae0ac888b5460f977a10cd9bb017ef98 added `true /* reindex */` argument. + - YB commit added YB-specific arguments. + - Kept PG's reindex arg; appended YB's args. + - is_usable_unique_index relam check: generalized AM check pattern + +- src/backend/commands/opclasscmds.c: + - amcanorder check in assignProcTypes: generalized AM check pattern + - Error messages in assignProcTypes (3 conflicts): + - PG commit ce62f2f2a0a48d021f250ba84dfcab5d45ddc914 changed "btree" to "ordering" in error messages. + - YB commit 586c51901f49075310f9b7405d585aea287b4ad2 changed "btree" to `%s, yb_amname` variable. + - Took PG's generalized message; removed unused yb_am variable. + +- src/backend/commands/publicationcmds.c: + - pub_contains_invalid_column: + - PG commit 87ce27de6963091f4a365f80bcdb06b9da098f00 restructured the function: moved `idattrs` loop, added `REPLICA_IDENTITY_FULL` early-return block with generated columns checks + - YB commit cff97c04736af739b40f777f97015fa56f7aa696 replaced `FirstLowInvalidHeapAttributeNumber` with `YBGetFirstLowInvalidAttributeNumber(relation)` for yb_minattr offset in the idattrs loop. + - Kept PG's restructured code; applied YB's comment and yb_minattr offset to the attnum calculation in the idattrs loop. + - CreatePublication superuser check: + - PG commit 96b37849734673e7c82fb86c4f0a46a28f500ac8 extended check for `for_all_sequences`. + - YB commit 2465f743a5e64ff8671cd59edb73a034ddbb9d29 added `IsYbDbAdminUser` bypass for `for_all_tables`. + - Combined PG's extended check with YB's bypass. Scoped the `IsYbDbAdminUser` bypass to `stmt->for_all_tables` only, preserving YB's original behavior of only allowing yb_db_admin for FOR ALL TABLES (not FOR ALL SEQUENCES). + - AlterPublicationOwner_internal owner check: + - PG commit 96b37849734673e7c82fb86c4f0a46a28f500ac8 consolidated check and added `puballsequences`. + - YB commit 2465f743a5e64ff8671cd59edb73a034ddbb9d29 added `IsYbDbAdminUser` bypass for `puballtables`. + - Kept PG's consolidated check structure and scoping `IsYbDbAdminUser` bypass to `puballtables` only. + - AlterPublicationOwner variable and YB guard: + - PG removed `subid`, added `pubid`. + - YB added `yb_enable_replication_commands` guard. + - Kept PG's `pubid` rename; added YB's guard. + +- src/backend/commands/schemacmds.c: + - owner/ACL checks (5 conflicts: CreateSchemaCommand ACL, RenameSchema owner + ACL, AlterSchemaOwner_internal owner + ACL): + - PG commit c727f511bd7bf3c58063737bcf7a8f331346f253 refactored `pg_database_aclcheck` to `object_aclcheck`. PG commit afbfc02983f86c4d71825efa6befd547fe81a926 refactored `pg_namespace_ownercheck` to `object_ownercheck`. + - YB commit f930e4b7b176ca956fabf35b255ef60e68c9be12 added `IsYbDbAdminUser` bypasses. + - Used PG's APIs with YB's bypasses. + - AlterSchemaOwner_internal role membership check: + - PG commit 3d14e171e9e2236139e8976f3309a588bcc8683b renamed `check_is_member_of_role` to `check_can_set_role`. + - YB commit f930e4b7b176ca956fabf35b255ef60e68c9be12 replaced PG's `check_is_member_of_role` with inline `is_member_of_role()` + `IsYbDbAdminUser` guard + manual `ereport`. + - Resolved with `IsYugaByteEnabled()` guard: YB path keeps inline `is_member_of_role` + `IsYbDbAdminUser` bypass, PG path uses `check_can_set_role`. + +- src/backend/commands/sequence.c: + - ResetSequence: + - PG commit b0a55e43299c4ea2a9a8c757f9c26352407d0ccc renamed `RelationSetNewRelfilenode` to `RelationSetNewRelfilenumber`. + - YB (commit f2bb921dfb035561c8b566bacc5c16d133d3425c and others) wrapped PG code in `else` block of `IsYugaByteEnabled()`, adds `yb_copy_split_options` and `preserved_index_split_options` parameters. + - Kept YB's branching; used PG's renamed `RelationSetNewRelfilenumber` with YB's split options parameters. + - AlterSequence read sequence data: + - PG commit ba3d93b2e806a877f26922e0f9e1845d0ef1511b replaced `Form_pg_sequence_data` parameter with separate `last_value`, `reset_state`, `is_called` out-parameters, PG commit 414e75540f058b23377219586abb3008507f7099 fixed comment. + - YB commit a036e5f5dc3fc9c500c82fbabf6762a150eef327 read from the sequence via `YBCReadSequenceTuple` into `last_val`/`is_called`. + - Merged YB's `last_val`/`is_called` into PG's `last_value`/`is_called` variable (removing duplicate declarations), moved new PG code into the PG branch, and initialized `last_value = 0` / `is_called = false` at the top of the YB branch (matching original YB defaults). + - AlterSequence/SequenceChangePersistence RelationSetNewRelfilenumber (2 conflicts): + - PG commit b0a55e43299c4ea2a9a8c757f9c26352407d0ccc renamed the function. + - YB added YB parameters. + - Used PG's renamed `RelationSetNewRelfilenumber` with YB's parameters. + - nextval_internal YB sequence logic: + - PG (commit 414e75540f058b23377219586abb3008507f7099 and others) fixed comment, moved rescnt definition. + - YB commit 9765a42806c616c975262c9b9b38ba22b0becfa8 added YB sequence pushdown. + - Kept YB's `IsYugaByteEnabled()` block before PG path, applying PG's comment typo fix. + - SetSequence read_seq_tuple: + - similar to above. + - init_params RESTART crosscheck: + - PG commit ba3d93b2e806a877f26922e0f9e1845d0ef1511b changed to use `*last_value` with `PRId64`. + - YB commit 94583031243b7a9cd6c6824b776161b911d28557 added `YbUsingSequenceOidAssignment()` guard. + - Used PG's formatting with YB's guard. + - pg_sequence_last_value YB early return: + - PG commit 7967d10c5b49ccb82f67a0b80678a1a932bccdee (and 3cb2f13ac500983c9c6b1eef3b3c2091c26f3040) refactored the aclcheck to return NULL instead of erroring. + - YB added `IsYugaByteEnabled()` early return below the error in the old PG code. + - Kept YB's early return. + +- src/backend/commands/tablespace.c: + - CreateTableSpace location validation: + - PG commit b380a56a3f9556588a89013b765d67947d54f7d0 added newline check. + - YB commit 38fc89021d77f249c2bcf39f28b2dbdacf66d327 moved validation inside `!IsYugaByteEnabled()` guard. + - Placed PG's newline check inside `!IsYugaByteEnabled()` guard alongside the other location validation checks. + - CreateTableSpace xlog registration: + - PG commit ed5e5f071033c8bdaabc8d9cd015f89aa3ccfeef removes `(char *)` casts from `XLogRegisterData` calls. + - YB commit 38fc89021d77f249c2bcf39f28b2dbdacf66d327 wrapped PG code in `!IsYugaByteEnabled()` guard. + - Kept YB's guard; applied PG's cast removal. + - DropTableSpace checkpoint and barrier: + - PG commit bb938e2c3c7a955090f8b68b5bf75d064f6a36a0 renamed `CHECKPOINT_IMMEDIATE` to `CHECKPOINT_FAST`. + - YB commit f701d7e554c1e9a2e12b048f445e4182ae280a17 wrapped PG code in `!IsYugaByteEnabled()` guard. + - Applied PG's rename within YB's guard block. + - DropTableSpace xlog registration: + - PG commit ed5e5f071033c8bdaabc8d9cd015f89aa3ccfeef removes `(char *)` cast from `XLogRegisterData` call. + - YB commit f701d7e554c1e9a2e12b048f445e4182ae280a17 wraps in `!IsYugaByteEnabled()` guard. + - Kept YB's guard; applied PG's cast removal. + +- src/backend/catalog/index.c: + - AppendAttributeTuples forward declaration and definition (2 conflicts): + - PG (commit 6a004f1be87d34cfe51acf2fe2552d2b08a79273 and others) added `const NullableDatum *stattargets` parameter and made `attopts` const. + - YB commit 5c28dc4a654cc246d0da0807e18d07b81cfe45eb added `bool yb_relisshared` parameter. + - Combined both changes. + - InsertPgAttributeTuples call in AppendAttributeTuples: + - PG changed fourth arg from `attopts` to `attrs_extra` (FormExtraData_pg_attribute). + - YB commit 5c28dc4a654cc246d0da0807e18d07b81cfe45eb added arg for `yb_relisshared` parameter. + - Combined both changes. + - index_create binary upgrade OID allocation: + - PG commit b0a55e43299c4ea2a9a8c757f9c26352407d0ccc renamed relfilenode to relfilenumber. + - YB commits 63ffdae951eacb0da9aee08cf8247052446623a8 and db96bd1cdb8948e587875f3ca7e1be66894a825b added yb_binary_restore and yb_ignore_pg_class_oids conditional logic, yb_ignore_relfilenode_ids conditional logic. + - Kept YB's yb_binary_restore conditional logic using PG's relfilenumber naming. Added a YB_TODO_PG19MERGE. + - index_create binary upgrade relfilenumber override: + - similar to above. + - index_create AppendAttributeTuples call: + - PG changed args to `opclassOptions, stattargets`. + - YB added `shared_relation` for yb_relisshared. + - Combined both. + - index_create partition and normal dependencies (2 conflicts): + - PG commit 23382b0f8b21e3f5330d765d1abfcef58d086111 renamed collationObjectId to collationIds, classObjectId to opclassIds. + - YB commit 2bae4c5a0bac79cdd03f6df24992e3f7ec3a0299 wrapped partition and normal dependencies inside `if (!yb_is_catalog_rel)` block. + - Kept YB's yb_is_catalog_rel guard with PG's variable names. + - index_create index_build call: + - PG commit caec9d9fadf1b04741ac554470c46bc1f8e89d19 added `progress` arg to index_build call. + - YB added yb_test_block_index_phase test hook before index_build call. + - Kept YB's test hook and added PG's new arg. + - BuildSpeculativeIndexInfo LSM_AM_OID check: generalized AM check pattern + - index_update_stats systable_inplace_update_finish: + - PG commit 8114224719401da8e30131310f1a227781cac6eb changed comment. + - YB commit 5c28dc4a654cc246d0da0807e18d07b81cfe45eb added arg for `yb_shared_update`. + - Kept YB's call and PG's updated comment. + - index_build progress reporting: + - PG commit caec9d9fadf1b04741ac554470c46bc1f8e89d19 added `if (progress)` guard around progress reporting block. + - YB added YB-specific progress phase. + - Kept YB's code, then used PG's `else if (progress)` for the standard PG block. + - reindex_index signature: + - PG commit f21848de20130146bc8039504af40bd24add54cd added `const ReindexStmt *stmt` first parameter. + - YB added YB parameters. + - Combined both. + - reindex_index body (RelationSetNewRelfilenumber + index_build): + - PG renamed RelationSetNewRelfilenode to RelationSetNewRelfilenumber, added progress parameter to index_build. + - YB added YbUseUnsafeTruncate guard, added partitioned index skip for index_build, added parameter to RelationSetNewRelfilenode. + - Kept YB's YbUnsafeTruncate guard, using PG's renamed `RelationSetNewRelfilenumber` with YB's split options parameters, keeping partitioned index skip, adding PG's progress parameter to index_build. + - reindex_index pg_index update comment: + - PG removed comment related to early pruning. + - YB added YB partitioned index comment. + - Kept YB's partitioned index comment. + - reindex_relation signature, call to reindex_index (2 conflicts): + - similar to reindex_index signature. + - reindex_relation result and toast handling: + - PG commit f2bf8fb04886e3ea82e7f7f86696ac78e06b7e60 moved toast reindexing before the index loop; end of function is now just `result |= (indexIds != NIL);`. + - YB commit 2fb7ea649683574f7d6a62edaf0318166d5eb5a1 added YB parameters to reindex_relation call for toast. + - Took PG's `result |= (indexIds != NIL)` (toast already handled earlier); updated the PG toast call site to pass YB parameters (is_yb_table_rewrite=false, yb_copy_split_options, NIL changedIndexNames/changedIndexSplitOpts). + +- src/backend/executor/execIndexing.c: + - check_exclusion_or_unique_constraint forward declaration: + - PG commit bfcf1b34805f70df48eedeec237230d0cc1154a6 renamed `errorOK` to `violationOK`. + - YB added `bool ybUseIndexOnlyScan` and `TupleTableSlot **ybConflictSlot` parameters. + - Used PG's `violationOK`; kept YB's parameters. + - YbExecUpdateIndexTuples indisvalid check: + - PG commit b7271aa1d71acda712a372213633fdb55c1465c1 replaced `bool noDupErr`/`bool update` with `uint32 flags` bitmask in `ExecInsertIndexTuples`. + - YB commit 68f7de82addd7eaed4b30db0b1c5d8c1dc590dc0 added the `indisvalid` check as part of the YB-specific update logic. YB commit f0a5db706e85412ec85a83f8286c892094d83688 moved the PG code from `ExecInsertIndexTuples` into `YbExecDoInsertIndexTuple` helper and made some changes to the function body. + - Took YB's side and then applied the PG changes (`uint32 flags` bitmask) to YB's `YbExecDoInsertIndexTuple`, updated the `YbExecDoInsertIndexTuple` calls in `ExecInsertIndexTuples` and `YbExecUpdateIndexTuples`. + - ExecCheckIndexConstraints signature: + - PG added `const ItemPointerData *tupleid` parameter. + - YB added `TupleTableSlot **ybConflictSlot` parameter. + - Combined both changes. + - check_exclusion_or_unique_constraint existing_slot cleanup: + - PG commit bc32a12e0db2df203a9cb2315461578e08568b9c added USE_INJECTION_POINTS block after slot drop. + - YB commit efd4cb7fea876ed9c13d9ce94ea989ed52aeaf69 made the slot drop conditional. + - Kept YB's conditional slot drop; appended PG's injection point block after it. + +- src/include/catalog/index.h: + - reindex_index declaration: + - PG commit f21848de20130146bc8039504af40bd24add54cd added `const ReindexStmt *stmt` parameter. + - YB added YB paramaters; + - Combined both. + - reindex_relation declaration: + - similar to above. + +- src/backend/catalog/objectaddress.c: + - ObjectTypeMap[] tablegroup entry: + - PG commit 89e5ef7e21812916c9cf9fcf56e45f0f74034656 removed OCLASS_* comments from ObjectTypeMap. + - YB added OBJECT_YBTABLEGROUP entry. + - Kept YB's tablegroup entry without the comment. + - get_object_address_defacl DEFACLOBJ cases (2 conflicts): + - PG commit 0d6c4776647feeee26f3e29fff6a5edb222fa260 added DEFACLOBJ_LARGEOBJECT case. + - YB commit added DEFACLOBJ_TABLEGROUP. + - Kept both cases. + - pg_fallthrough vs yb_switch_fallthrough (2 conflicts): fallthrough pattern + - check_object_ownership OBJECT_TRIGGER + OBJECT_DATABASE: + - PG commit afbfc02983f86c4d71825efa6befd547fe81a926 replaced `pg_class_ownercheck` with generic `object_ownercheck(RelationRelationId, ...)`. Same commit replaces `pg_database_ownercheck` with generic `object_ownercheck` and groups OBJECT_DATABASE with other strVal types. + - YB commit 311e253ae8da89a39fb21fde8d7e66d7aac0a1d6 moved OBJECT_TRIGGER into a separate case and added `IsYbDbAdminUser` check. + - Kept OBJECT_TRIGGER as a separate case with PG's `object_ownercheck(RelationRelationId, ...)` plus YB's `IsYbDbAdminUser` check. Dropped OBJECT_DATABASE. + - check_object_ownership OBJECT_SCHEMA: + - PG commit afbfc02983f86c4d71825efa6befd547fe81a926 moved OBJECT_SCHEMA into the generic object_ownercheck() group (with OBJECT_SUBSCRIPTION, OBJECT_TABLESPACE and others). + - YB commit f930e4b7b176ca956fabf35b255ef60e68c9be12 added IsYbDbAdminUser check for OBJECT_SCHEMA ownership. + - Accepted PG's generic grouping for all cases, but pulling OBJECT_SCHEMA out as a separate case with `object_ownercheck()` plus YB's `IsYbDbAdminUser` check. + - check_object_ownership OBJECT_YBTABLEGROUP: + - PG commit afbfc02983f86c4d71825efa6befd547fe81a926 moved OBJECT_TABLESPACE, OBJECT_TSDICTIONARY, OBJECT_TSCONFIGURATION. + - YB commit 48dc70a0775eed9d598a4c2a6ccd9ef3b965695d added OBJECT_YBTABLEGROUP ownership check. + - Kept YB's OBJECT_YBTABLEGROUP. + - check_object_ownership OBJECT_YBPROFILE: + - PG commit aca992040951c7665f1701cd25d48808eda7a809 removed default case. + - YB commit 7a13a29b22f656473b9d5cb951e7dccb0b006beb added OBJECT_YBPROFILE ownership check. + - Placed YB's OBJECT_YBPROFILE case above PG's unsupported object type group. + - getObjectDescription/getObjectTypeDescription/getObjectIdentityParts tablegroup cases (3 conflicts): + - PG commit 89e5ef7e21812916c9cf9fcf56e45f0f74034656 changed switch from getObjectClass() (OCLASS_* enum) to object->classId (catalog relation OIDs like TableSpaceRelationId). + - YB commit 48dc70a0775eed9d598a4c2a6ccd9ef3b965695d added OCLASS_YBTBLGROUP case for tablegroup descriptions. + - Added `case YbTablegroupRelationId:` before `case TableSpaceRelationId:` in all three functions, adapting YB's tablegroup descriptions to PG's classId-based switch. + - getObjectDescription/getObjectTypeDescription profile/role_profile cases + default (2 conflicts): + - PG commit 89e5ef7e21812916c9cf9fcf56e45f0f74034656 added `default: elog(ERROR, ...)` for classId-based. + - YB commit 7a13a29b22f656473b9d5cb951e7dccb0b006beb added OCLASS_YBPROFILE and OCLASS_YBROLE_PROFILE cases. + - Used `case YbProfileRelationId:` and `case YbRoleProfileRelationId:` plus PG's `default:` error case. + - getObjectDescription DEFACLOBJ_LARGEOBJECT vs DEFACLOBJ_TABLEGROUP: + - PG added DEFACLOBJ_LARGEOBJECT case. + - YB added DEFACLOBJ_TABLEGROUP case. + - Kept both cases. + - getObjectIdentityParts DEFACLOBJ_LARGEOBJECT vs DEFACLOBJ_TABLEGROUP: + - same as above. + - Auto-merged OCLASS_* case labels (outside conflict markers): + - PG commit 89e5ef7e21812916c9cf9fcf56e45f0f74034656 switched from OCLASS_* enum to catalog relation OIDs. + - Replaced all auto-merged OCLASS_YBPROFILE with YbProfileRelationId, OCLASS_YBROLE_PROFILE with YbRoleProfileRelationId, and OCLASS_YBTBLGROUP with YbTablegroupRelationId. + +- src/backend/executor/execProcnode.c: + - Forward declarations: + - PG commit 544000288ec8f7dc6a1e0285821adc47324ecd33 moved ExecProcNodeInstr to instrument.c. + - YB commit cba0949d09f2905c4c10f9114f238b453137c7e8 added ExecProcNodeYbDistTrace and YbGetExecNodeSpanName. + - Removed ExecProcNodeInstr forward declaration, keeping YB's ExecProcNodeYbDistTrace. Made YbGetExecNodeSpanName non-static (now declared extern in executor.h) so instrument.c can call it. + - YbGetExecNodeSpanName, ExecProcNodeInstr, ExecProcNodeYbDistTrace function bodies: + - PG commit 544000288ec8f7dc6a1e0285821adc47324ecd33 moved ExecProcNodeInstr to instrument.c. + - YB commit cba0949d09f2905c4c10f9114f238b453137c7e8 added distributed tracing (YBCIsDistTraceActive/YB_DIST_TRACE spans) and session stats (YbUpdateSessionStats) to ExecProcNodeInstr, plus YbGetExecNodeSpanName and ExecProcNodeYbDistTrace. + - Accepted PG's relocation: moved ExecProcNodeInstr to instrument.c with YB's tracing and stats additions. Kept YbGetExecNodeSpanName and ExecProcNodeYbDistTrace in execProcnode.c. Added `#include "pg_yb_utils.h"` to instrument.c and extern declaration for YbGetExecNodeSpanName in executor.h. + +- src/backend/executor/execUtils.c: + - ExecGetInsertedCols ri_RootToPartitionMap: + - PG (commit fb958b5da86da69651f6fb9f540c2cfb1346cdc5 and others) refactored some code. + - YB added a 3rd arg `relinfo->ri_RelationDesc` to execute_attr_map_cols calls (PG15 base had 2-arg calls). + - Took PG's refactoring and preserving YB's 3rd arg to execute_attr_map_cols. + - ExecGetUpdatedCols ri_RootToPartitionMap: + - same as above. + +- src/backend/access/transam/xact.c: + - StartTransaction: + - PG commit 51efe38cb92f4b15b68811bcce9ab878fbc71ea5 added code to "schedule transaction timeout". + - YB added `YBStartTransaction()` and some code for connection manager. + - Kept PG TransactionTimeout first, then YB additions. + - CommitTransaction/AbortTransaction TRACE macros (2 conflicts): + - PG commit ab355e3a88de745607f6dd4c21f0119b5c68f2ad changed MyProc->lxid to MyProc->vxid.lxid. + - YB commit a8daf734d07ed8b722b1392235d0e169598d85a5 added YB_DIST_TRACE_* calls. + - Kept YB's YB_DIST_TRACE_* with PG's vxid.lxid API. + - PrepareTransaction PostPrepare_Locks/PredicateLocks (2 conflicts): + - PG commit 62a17a92833d1eaa60d8ea372663290942a1e8eb changed xid to fxid. + - YB wrapped PostPrepare_*Locks calls with YBGetObjectLockMode guard. + - Kept YB's guard with PG's fxid argument. + - YB transaction functions (YbSetTxnUsesTempRel, etc.): + - PG commit fefd9a3fed275cecd9ed4091b00698deed39b92e refactored CommitTransactionCommand into wrapper + CommitTransactionCommandInternal, changing the context just below where YB inserts functions. + - YB added YbSetTxnUsesTempRel, YBMarkTxnUsesTempRelAndSetTxnId, YbCurrentTxnUsesTempRel between RestoreTransactionCharacteristics and CommitTransactionCommand. + - Inserted YB functions before CommitTransactionCommand. + - CommitTransactionCommand refactoring (2 conflicts): + - PG (commit fefd9a3fed275cecd9ed4091b00698deed39b92e and others) refactored into wrapper+internal structure. + - YB commit 60ac2325a90d4a5ce7f964da435accc2f8d4825c added elog debug logging. YB commit 527768dd2e0ed200e462ab7eaf5f46f87110bf79 added connection manager logic. + - Used PG's wrapper+internal structure with YB's additions inside. + - BeginInternalSubTransaction: + - PG commit 0075d78947e3800c5a807f48fd901f16db91101b added ExitOnAnyError restoration after StartTransactionCommand(). + - YB (commit 7dfc2e7c05aa988dfa368a3bdcd5cff4e8462227 and others) replaced StartTransactionCommand() with YBStartTransactionCommandInternal and added YbBeginInternalSubTransactionForReadCommittedStatement and YBTransactionContainsNonReadCommittedSavepoint functions. + - Kept YB's YBStartTransactionCommandInternal replacement and appended functions, plus PG's ExitOnAnyError restoration. + - ShowTransactionStateRec format string: + - PG commit 2268f2b91b5513cbf430d1cca488203d66103b3a removed `(unsigned int)` casts from format arguments. + - YB added ybDataSent/ybDataSentForCurrQuery fields to the format string. + - Took PG's cast removal and extending the format string with YB's fields. + +- src/backend/commands/vacuum.c: + - Includes: + - PG added macro. + - YB added YB includes. + - Placed YB includes first, then PG's macro. + - vacuum() VACUUM no-op for YugabyteDB: + - PG commit 2252fcd4276cfeabae8786ab7c5a421dd674743e removed `Assert(params != NULL)`. + - YB added VACUUM no-op notice block before the Assert. + - Kept YB's block with a `IsYugabyteEnabled()` guard and dropped the Assert to match PG. + +- src/backend/commands/variable.c: + - check_transaction_isolation: + - PG added !InitializingParallelWorker guard. + - YB added read committed warning. + - Kept YB's warning and PG's guard. + - check_session_authorization: + - PG changed to superuser_arg(GetAuthenticatedUserId()). + - YB added IsYbDbAdminUserNosuper check. + - Used PG's API with YB's check. + +- src/backend/executor/nodeAgg.c: + - ExecAgg fallthrough: fallthrough pattern + - ExecInitAgg aclcheck and YB pushdown: + - PG commit c727f511bd7bf3c58063737bcf7a8f331346f253 changed pg_proc_aclcheck to object_aclcheck. + - YB added pushdown logic. + - Kept YB's pushdown logic with PG's object_aclcheck. + +- src/backend/executor/instrument.c: + - InstrAggYbPgRpcStats: + - PG commit 5a79e78501f46bd3ac7fbd0ff84cf1e20dbafd19 updated the comment for InstrAggNode. + - YB added InstrAggYbPgRpcStats and YbInstrAggRpcMetrics right above InstrAggNode. + - Kept YB's helpers and PG's updated comment. + - Trigger instrumentation functions: + - PG commit 7d9b74df53e9268bd638274f1415ebfeecf0de51 added InstrAllocTrigger/InstrStartTrigger/InstrStopTrigger (right below InstrAggNode). + - YB commit 88e82ed9d10ec32bc6421e85c44470006171902c added YB-specific instrumentation logic to InstrAggNode. + - Kept PG's trigger functions with YB's logic inside InstrAggNode. + +- src/backend/catalog/aclchk.c: + - objectNamesToOids switch cases: + - PG commit d31bbfb6590e586f731345960311861d5eb4c23f consolidated most object lookups into a `default` case using `get_object_address()`. + - YB added OBJECT_YBTABLEGROUP case with `get_tablegroup_oid`. + - Took PG's consolidation; kept YB's OBJECT_YBTABLEGROUP case. + - SetDefaultACLsInSchemas, RemoveRoleFromObjectACL, get_user_default_acl LARGEOBJECT/TABLEGROUP: + - PG added OBJECT_LARGEOBJECT / DEFACLOBJ_LARGEOBJECT cases. + - YB added OBJECT_YBTABLEGROUP / DEFACLOBJ_TABLEGROUP cases. + - Kept both PG's LARGEOBJECT and YB's TABLEGROUP cases. + - ExecGrant_Relation: + - PG commit 9fd45870c1436b477264c0c82eb195df52bc0919 removed Memset calls. + - YB indented PG code inside the `!IsYugaByteEnabled()` guard. + - Took PG's changes, indented as per YB. + - ExecGrant consolidation (2 conflicts): + - PG commit 369f09e420efe27359b06b69c0265f4aec5c2134 consolidated individual ExecGrant_* functions into ExecGrant_common. + - YB added a `YbCheckAclCopiesEqual` optimization to the individual functions. + - Took PG's consolidated code. `YbCheckAclCopiesEqual` is already in ExecGrant_common (from non-conflict code). Added `ExecGrant_Tablegroup` as a standalone function since it uses YB-specific catalog. + - pg_tablegroup_aclmask: + - PG commit 403ac226ddd6071245b7b283861c26960ea7293f added pg_type_aclmask_ext. + - YB added pg_tablegroup_aclmask. + - Kept both. + - pg_tablespace/fdw/server/type_aclmask consolidation: + - PG commit c727f511bd7bf3c58063737bcf7a8f331346f253 consolidated individual `pg_*_aclmask` functions into `object_aclmask`/`object_aclmask_ext`. + - YB had `IsYbDbAdminUser` bypass in `pg_tablespace_aclmask`. + - Took PG's consolidation. Ported `IsYbDbAdminUser` for tablespace into `object_aclmask_ext`. + - Individual aclcheck/ownercheck consolidation (2 conflicts): + - PG commit afbfc02983f86c4d71825efa6befd547fe81a926 consolidated ownercheck functions into `object_ownercheck`. PG commit c727f511bd7bf3c58063737bcf7a8f331346f253 consolidated aclcheck functions into `object_aclcheck`. + - YB had `IsYbDbAdminUser` bypasses and `pg_tablegroup_ownercheck`/`pg_tablegroup_aclcheck`. + - Took PG's consolidation. Ported `IsYbDbAdminUser` for tablespace and event trigger into `object_ownercheck`. Kept YB's `pg_tablegroup_aclcheck` and `pg_tablegroup_ownercheck` as standalone functions. + - recordExtensionInitPrivWorker CatalogTupleDelete API: + - PG changed the comment above CatalogTupleDelete. + - YB changed the signature to use oldtuple (HeapTuple) (adjacent line conflict). + - Kept PG's comment and YB's CatalogTupleDelete call. + - ReplaceRoleInInitPriv/RemoveRoleFromInitPriv + YbCheckAclCopiesEqual: + - PG added ReplaceRoleInInitPriv and RemoveRoleFromInitPriv. + - YB added YbCheckAclCopiesEqual helper. + - Kept both. + +- src/backend/commands/user.c: + - CreateRole privilege checks: + - PG commit f1358ca52dd7b8cedd29c6f2f8c163914f03ea2e changed privilege model: instead of requiring SUPERUSER for REPLICATION/BYPASSRLS, each attribute now requires the creating role to have that same attribute ("you can't give permissions you don't have"). + - YB added `IsYbDbAdminUser` bypass allowing yb_db_admin to create bypassrls users without SUPERUSER. + - Took PG's flat check structure. Ported `IsYbDbAdminUser` bypass into the bypassrls check. Added YB_TODO_PG19MERGE to verify the bypass is still appropriate under the new privilege model. + - AlterRole privilege checks: + - PG commits cf5eb37c5ee0cc54c80d95c1695d7fca1f7c68cb, f1358ca52dd7b8cedd29c6f2f8c163914f03ea2e restructured into CREATEROLE+ADMIN flow with attribute-based checks (same "you can't give permissions you don't have" model as CreateRole). + - YB added `IsYbDbAdminUser` bypasses for bypassrls and profile management. + - Took PG's structure as-is. Added YB_TODO_PG19MERGE to port YB's bypasses into the new model. + - DropRole two-pass process (3 conflicts): + - PG commit 6566133c5f52771198aca07ed18f84519fac1be7 restructured the code into a two-pass process. + - YB added YBDetailSorted and YbRemoveRoleProfileForRoleIfExists, and changed CatalogTupleDelete call. + - Kept PG's changes, porting YB code to second pass with PG's API. + - ReassignOwnedObjects: + - PG added `errdetail` to reassign permission error. + - YB added superuser reassign check. + - Kept both. + - DelRoleMems `RRG_DELETE_GRANT`: + - PG added `deleteSharedDependencyRecordsFor` + - YB changed `CatalogTupleDelete` call. + - Kept PG's `deleteSharedDependencyRecordsFor` with YB's CatalogTupleDelete. + +- src/backend/catalog/heap.c: + - YB system attribute definitions: + - PG added `const` qualifier on `SysAtt[]`. + - YB added `ybctid`, `ybuniqueidxkeysuffix`, `ybidxbasectid` system attributes and `YbSystemAttributeDefinition`. + - Kept all YB attributes and PG's `const` qualifier on `SysAtt[]` (and also `YbSysAtt[]`). + - InsertPgAttributeTuples signature: + - PG commit d939cb2fd612acde0304913213cfbdb01994e682 replaced Datum *attoptions with FormExtraData_pg_attribute tupdesc_extra[]. + - YB added bool yb_relisshared. + - Used PG's tupdesc_extra with YB's yb_relisshared appended. + - AddNewAttributeTuples InsertPgAttributeTuples call: + - PG commit 4f622503d6de975ac87448aea5cea7de4bc140d5 removed the `attstattarget = -1` default loop. + - YB changed the InsertPgAttributeTuples call (adjacent line conflict). + - Accepted PG's removal and YB's change. + - AddNewAttributeTuples for loop: + - PG commit 65b71dec2d577e9ef7423773a88fdd075f3eb97f changed `tupdesc->attrs[i]` to `TupleDescAttr(tupdesc, i)`. + - YB wrapped PG code in an if block (and indented it). + - Kept YB's structure with PG's changes inside the block. + - heap_create_with_catalog comment: + - Kept both: PG's `relrewrite` doc and YB's `yb_use_initdb_acl` doc. + - heap_create_with_catalog OID allocation: + - PG renamed to relfilenumber (changed comment) + - YB added yb_binary_restore/yb_extension_upgrade logic, IsYsqlUpgrade guards. + - Combined PG's naming with YB's conditional logic. + - heap_create_with_catalog access method dependency: + - PG added `RELKIND_PARTITIONED_TABLE` check to access method dependency link. + - YB indented PG code inside `IsYsqlUpgrade` guard. + - Kept YB's changes; applied PG's partitioned table check (and comment) into the access method block at 3-tab indentation matching the YB guard nesting. + - RemoveAttributeById `atttypid` / branch flattening: + - PG commit `7ef2912519fdea0dd0f6747c4dc008c99dc51e90` removed `if (attnum < 0)` / `else` branching (system OID column drop no longer supported), flattening the function. + - YB changed `CatalogTupleDelete(attr_rel, &tuple->t_self)` to `CatalogTupleDelete(attr_rel, tuple)` in the if branch. + - Took PG's flat code. + - RemoveAttributeById tuple update: + - PG (commit 7ef2912519fdea0dd0f6747c4dc008c99dc51e90 and others) removed `if (attnum < 0)` / `else` branching and refactored the function. + - YB changed to use `CatalogTupleDelete` + `CatalogTupleInsert` instead of `CatalogTupleUpdate` (can't update primary key). Existing YB code seems to have a bug: it inserts the tuple before the missing value update, so the `atthasmissing` clearing is lost. + - Kept PG's restructured code and YB's logic to update the catalog tuple. + - heap_drop_with_catalog: + - PG changed CatalogTupleDelete arguments; YB changed the CatalogTupleDelete API. + - Kept YB's HeapTuple API with PG's correct variable names (`ftrel, fttuple`). + - AddRelationNewConstraints (2 conflicts): + - PG (commit 95f650674d2ceea1ba6440a9b0ae89ed3867fd7e, b0e96f311985bceba79825214f8e43f65afa653aand others) did some refactoring (ex: removed the "If the DEFAULT is volatile..." block). + - YB added `relisshared` checks before default storage and constraint processing. + - Kept YB's checks with PG's structure. + +- src/backend/commands/trigger.c: + - Forward declarations (2 conflicts): + - PG added check_modified_virtual_generated, FireAfterTriggerBatchCallbacks. + - YB added YbTriggerNameCmp, afterTriggerCheckState. + - Kept all declarations. + - RangeVarCallbackForRenameTrigger: + - PG commit afbfc02983f86c4d71825efa6befd547fe81a926 changed pg_class_ownercheck to object_ownercheck. + - YB added IsYbDbAdminUser check. + - Used PG's API with YB's bypass. + - AfterTriggersData struct: + - PG added batch_callbacks. + - YB added ybc_txn_fdw_tuplestores. + - Kept both fields. + - pg_fallthrough in AfterTriggerExecute: fallthrough pattern + - AfterTriggerBeginXact/EndXact (2 conflicts): + - PG added afterTriggerFiringDepth assert/reset. + - YB added ybc_txn_fdw_tuplestores assert/cleanup. + - Kept both. + - End-of-file function definitions: + - Kept PG functions then YB functions. + +- src/include/utils/numeric.h: + - includes: kept both PG and YB includes. + +- src/include/utils/jsonfuncs.h: + - PG commit 3c152a27b06313fe27bd47079658f928e291986b added some declarations to jsonfuncs.h. + - YB added JSON text manipulation functions (`json_get_int_value`, `get_json_array_element`, etc.). + - Kept both. + +- src/include/utils/plancache.h: + - PG changed forward declarations. + - YB added GUC variables `yb_test_planner_custom_plan_threshold` and `enable_choose_custom_plan_for_partition_pruning`. + - Placed YB's GUC variable declarations first, then PG's forward declarations. + +- src/include/utils/relcache.h: + - PG commit b0a55e43299c4ea2a9a8c757f9c26352407d0ccc renamed `RelationSetNewRelfilenode` to `RelationSetNewRelfilenumber` and `RelationAssumeNewRelfilenode` to `RelationAssumeNewRelfilelocator`. + - YB added `yb_copy_split_options` and `preserved_index_split_options` parameters to `RelationSetNewRelfilenode`. + - Used PG's renamed function names with YB's additional parameters. + +- src/pl/plpgsql/src/plpgsql.h: + - PG commit 0dca5d68d7bebf2c1036fd84875533afef6df992 removed `use_count` field from `PLpgSQL_function` struct. + - YB has `yb_catalog_version` and `yb_invalid` fields. + - Dropped PG's removed `use_count`; kept YB's two fields. + +- src/test/regress/GNUmakefile: + - PG commit b1720fe63f344adeb8a75b22e8f31b127c814f35 removed the contrib/spi module copying block (moved testing to contrib/spi). + - YB commit 79c1e31fd89c803e1907b9ea9fe6799c0a59a9ee had wrapped the block with `YB_BUILD_TYPE` guard. + - Dropped the block entirely, matching PG's removal. + +- src/interfaces/ecpg/preproc/ecpg.c: + - PG commit df8b8968d4095f44acd6de03b4add65f9709b79d reordered switch cases alphabetically and changed `case 'h'` to directly set `auto_create_c = true` instead of falling through to `case 'c'`. + - YB added `yb_switch_fallthrough()` before the old `case 'c'` fallthrough. + - Took PG's reordered code. + +- src/interfaces/libpq/Makefile: + - PG commit b0635bfda0535a7fc36cd11d10eecec4e2a96330 added shlib/stlib-specific object rules, 4a8e6f43a6b56289cd3806b239c20ae31aa4cf2e refactored exit() check into `libpq_check.pl` Perl script, which already handles `__tsan_func_exit`. + - YB commit cfeda577fdbdd62eebfa24d207000ca23b5c76f5 added `__tsan_func_exit` exclusion to the old inline check. + - Took PG's refactored approach entirely. YB's TSAN exclusion is already handled by PG's `libpq_check.pl`. + +- src/interfaces/libpq/exports.txt: + - PG added exports 187-211 (PQconnectionUsedGSSAPI through PQgetThreadLock). + - YB added `YbPQsaveMessageField` at ordinal 10001. + - Kept both (PG first, then YB). + +- src/pl/plpgsql/src/pl_gram.y: + - includes: kept both PG and YB includes. + +- src/backend/executor/nodeIndexonlyscan.c: + - Scan direction: + - PG commit e9aaf06328c7f962f8586618981e9763d31402a3 replaced `ScanDirectionIsBackward` and surrounding code with `ScanDirectionCombine()`. + - YB added `NoMovementScanDirection` and surrounding if guard. + - Used PG's `ScanDirectionCombine`, then YB's if block. + - New functions at end-of-file: + - PG added functions. + - YB added `yb_agg_pushdown_init_scan_slot` and `yb_store_index_tuple_decoded_pk`. + - Kept both (PG's functions first, then YB's). + +- src/backend/executor/nodeLimit.c: + - Two pg_fallthrough vs yb_switch_fallthrough conflicts: + - PG changed `FALLTHROUGH` comment to `pg_fallthrough`. + - YB added LIMIT pushdown logic (`yb_exec_params.limit_count`/`limit_offset`) (commit 4405799654486b844cfc2251793d9d550c097d1f), and `yb_switch_fallthrough()`. + - Kept YB's LIMIT pushdown logic and used PG's `pg_fallthrough`. + +- src/backend/libpq/auth-scram.c: + - #ifdef USE_SSL guard (2 conflicts): + - PG commit 8e278b65766446f29085fe686723961c4b216e6f changed `#ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH` to `#ifdef USE_SSL`. + - YB commit b9588a4d58937a614968bc9ba733b5efb7ca5aa1 added `ysql_enable_scram_channel_binding` gflag check to the `if` condition. + - Used PG's `#ifdef USE_SSL` with YB's gflag guard in the `if` condition. + +- src/backend/optimizer/util/relnode.c: + - ParamPathInfo initialization (2 conflicts): + - PG added `ppi->ppi_serials` field. + - YB added `ppi->yb_ppi_req_outer_batched` field. + - Kept both field assignments. + +- src/backend/parser/analyze.c: + - column permission bitmaps (2 conflicts): + - PG commit a61b1f74823c9c4f79c95226a461f1e7a367764b moved `insertedCols`/`updatedCols` from `RangeTblEntry` to `RTEPermissionInfo` (`perminfo`). + - YB replaced `FirstLowInvalidHeapAttributeNumber` with `YBGetFirstLowInvalidAttributeNumber(pstate->p_target_relation)`. + - Used PG's `perminfo` API with YB's attribute number offset function. + +- src/backend/postmaster/bgworker.c: + - Background worker list: + - PG commit 1eb09ed63a8d8063dc6bb75c8f31ec564bf35250 changed entries to use C99 designated initializers (`.fn_name`, `.fn_addr`). PG commit 28d534e2ae0ac888b5460f977a10cd9bb017ef98 added `RepackWorkerMain`. + - YB added `YbAshMain`, `YbQueryDiagnosticsMain`, `YbQueryDiagnosticsDatabaseConnectionWorkerMain`, `YbNotifsPollerMain`. + - Kept PG's entries in designated-initializer format; appended YB entries in the same format. + - New functions at end-of-file: + - PG added `TerminateBackgroundWorkersForDatabase`. + - YB added `YbBackgroundWorkerHandleSize`. + - Kept both functions. + +- src/backend/replication/syncrep.c: + - Two conflicts in `SyncRepCleanupAtProcExit`: + - PG commit 12605414a7d63ccbe36de2b530847bdfc9cf7447 replaced `SHMQueueIsDetached`/`SHMQueueDelete` with `dlist_node_is_detached`/`dlist_delete_thoroughly`. + - YB commit 70fbb382e1a913c651a6dbc85a75bcf2e25d32a8 changed `MyProc` to `yb_proc` parameter for cleanup after terminated connections. + - Used PG's dlist API with YB's `yb_proc` parameter. + +- src/backend/rewrite/rewriteHandler.c: + - Forward declarations: + - PG (commit 5f2e179bd31e5f5803005101eb12a8d7bf8db8f3 and others) removed `view_has_instead_trigger` forward declaration, added `expand_generated_columns_internal` declaration. + - YB commit 7e222b42902b5d73067e96ce58a26e679f11a618 added `view_rel` and `base_relid` params to `adjust_view_column_set` to use `YBGetFirstLowInvalidAttributeNumber` instead of `FirstLowInvalidHeapAttributeNumber`. + - Dropped `view_has_instead_trigger` forward declaration, keeping YB's 4-arg `adjust_view_column_set`, and PG's `expand_generated_columns_internal`. + - `adjust_view_column_set` call site: + - PG commit a61b1f74823c9c4f79c95226a461f1e7a367764b moved columns from `RangeTblEntry` to `RTEPermissionInfo` (`perminfo`). + - YB commit 7e222b42902b5d73067e96ce58a26e679f11a618 added `view` and `relid` params to the call for `YBGetFirstLowInvalidAttributeNumber` support. + - Used PG's `perminfo` API with YB's additional `view` and `relid` arguments. + +- src/backend/storage/ipc/procsignal.c: + - CleanupProcSignalState: + - PG commits 9d9b9d46f3c509c722ebbf2a1e7dc6296a6c711d, a460251f0a1ac987f0225203ff9593704da0b1a9, and others added spinlock-based cleanup, `pss_cancel_key_len` reset, and `pss_barrierGeneration` write to `CleanupProcSignalState`. + - YB commit 70fbb382e1a913c651a6dbc85a75bcf2e25d32a8 extracted code from `CleanupProcSignalState` into helper `CleanupProcSignalStateInternal` and added wrapper `CleanupProcSignalStateForProc`. YB also changed `MyProc` to `proc` and `ConditionVariableBroadcast(&slot->pss_barrierCV)` to `YbConditionVariableBroadcastForProc(&slot->pss_barrierCV, proc)`. + - Renamed all YB-added functions to have a `Yb` prefix (changes in procsignal.h and postmaster.c); ported PG's `CleanupProcSignalState` changes into `YbCleanupProcSignalStateInternal`; applied YB's `MyProc`-to-`proc` and `ConditionVariableBroadcast`-to-`YbConditionVariableBroadcastForProc` changes. `CleanupProcSignalState` now calls the YB helper. The previous `pss_idx` argument is computed as `(int)(slot - ProcSignal->psh_slot)`, matching PG's inline computation in the log message. Changed `YbCleanupProcSignalStateForProc` to use proc number (0-indexed). Added a YB_TODO_PG19MERGE. + - procsignal interrupt checks: + - PG (commit 17f51ea818753093f929b4c235f3b89ebcc7c5fb and others ) consolidated `PROCSIG_RECOVERY_CONFLICT_DATABASE` into `PROCSIG_RECOVERY_CONFLICT`, added `PROCSIG_PARALLEL_APPLY_MESSAGE` check. + - YB added `PROCSIG_LOG_HEAP_SNAPSHOT`, `PROCSIG_LOG_HEAP_SNAPSHOT_PEAK` and `YB_PROCSIG_LOG_CATCACHE_STATS` checkss. + - Kept PG's changes, then appended YB's checks. + +- src/backend/storage/lmgr/condition_variable.c: + - ConditionVariableCancelSleep + YbConditionVariableCancelSleepForProc: + - PG commit 5ffb7c775062ef18756e515ac96f06d012cbb950 changed the function `ConditionVariableCancelSleep`. + - YB commits f90da744e9c3cb24d4c4622664f9895433a451d2, 61730dec6c64f6b2c9f91f31c67559d7bed9acf9 added `YbConditionVariableCancelSleepForProc` (for process-specific cleanup). + - Took PG's `bool`-returning `ConditionVariableCancelSleep` (without signal-passing); appended YB's `YbConditionVariableCancelSleepForProc` after it. Added a YB_TODO_PG19MERGE. + - YbConditionVariableBroadcastForProc pgprocno: + - PG commit 28f3915b73f75bd1b50ba070f56b34241fe53fd1 changed `pgprocno = MyProcNumber` (from `MyProc->pgprocno`). + - YB commits 61730dec6c64f6b2c9f91f31c67559d7bed9acf9, 61730dec6c64f6b2c9f91f31c67559d7bed9acf9 refactored broadcast into `YbConditionVariableBroadcastForProc` taking `given_proc` parameter, using `given_proc->pgprocno`. + - Kept YB's `given_proc->pgprocno` since the function signature takes a `given_proc` parameter. Note: `pgprocno` field no longer exists; this will be dealt with during compilation fixes. + +- src/backend/storage/lmgr/lock.c: + - PostPrepare_Locks: + - PG commit 62a17a92833d1eaa60d8ea372663290942a1e8eb changed parameter to `FullTransactionId fxid`. + - YB commit f7d79516a7e158a1bb7b0f7e1dc9df56e930e389 added early return guard for `YBGetObjectLockMode() != PG_OBJECT_LOCK_MODE`. + - Kept YB's early return guard, then using PG's `fxid` in `TwoPhaseGetDummyProc(fxid, false)`. + - VirtualXactLockTableCleanup: + - PG commit 024c521117579a6d356050ad3d78fdc95e44eefa changed backend id to proc number. + - YB added early return guard for `YBGetObjectLockMode() != PG_OBJECT_LOCK_MODE`. + - Kept YB's early return guard, then using PG's `INVALID_PROC_NUMBER` assertion. + +- src/backend/executor/nodeTidscan.c: + - IsCTIDVar macro: + - PG commit 13d53aa7a83383c9d1343e7d725e615f8678aea8 simplified the macro to just `SelfItemPointerAttributeNumber` check. + - YB added `YBTupleIdAttributeNumber` check. + - Kept PG's simplified macro and added `YBTupleIdAttributeNumber` check. + - ExecEndTidScan: + - PG removed `ExecFreeExprContext` and `ExecClearTuple` cleanup, leaving only `table_endscan`. + - YB added `IsYBRelation` branch for `ybc_heap_endscan`. + - Kept YB's branching and dropped the cleanup code PG removed. + - ExecInitScanTupleSlot call: + - PG commit c456e39113809376f6604e720910ccd24e18e034 added `TTS_FLAG_OBEYS_NOT_NULL_CONSTRAINTS` argument. + - YB commit a421251665351582bf98247acfb51bddd86c7148 added `IsYBRelation` ternary for `TTSOpsVirtual` vs `table_slot_callbacks`. + - Kept YB's `IsYBRelation` ternary and added PG's `TTS_FLAG_OBEYS_NOT_NULL_CONSTRAINTS` as the additional argument. + +- src/backend/storage/ipc/procarray.c: + - ProcArrayRemove: + - PG commit b31ba5310b5176402b60abc0454a033b1210ab75 renamed `ShmemVariableCache` to `TransamVariables`. + - YB indented `ShmemVariableCache` inside if block. + - Used PG's `TransamVariables->xactCompletionCount++` inside YB's block. + - TransactionIdIsActive: + - PG removed `TransactionIdIsActive` function. + - YB added a `IsYugaByteEnabled()` early return. + - Discarded the function. + - GetSnapshotData YB snapshot logic: + - PG commit f691f5b80a85c66d715b4340ffabb503eb19393e removed `GetSnapshotDataInitOldSnapshot`. + - YB added catalog snapshot read point logic, and `YbLogSnapshotData`. + - Kept YB's catalog snapshot management code. + +- src/backend/utils/adt/selfuncs.c: + - includes: + - PG commit added some macros and structs. + - YB added YB includes. + - Took PG's additions entirely; placed YB includes before PG's changes. + - get_actual_variable_range early returns: + - PG added `rte->relkind == RELKIND_PARTITIONED_TABLE` early return. + - YB added `IsYBRelationById(rte->relid)` early return. + - Kept both early returns: PG's partitioned table check first, then YB's relation check. + - get_actual_variable_range index AM check: generalized AM check pattern + +- src/backend/optimizer/path/joinpath.c: + - includes and hook comment: + - PG changed comment to plural "Hooks for plugins...". + - YB added includes. + - Kept YB includes first, then PG's comment. Removed duplicate `restrictinfo.h` (already included by PG). + - add_paths_to_joinrel save_jointype: + - PG commit 24225ad9aafc576295e210026d8ffa9f50d61145 changed `jointype` to `save_jointype`. + - YB added planner trace debug logging (adjacent line conflict). + - Used PG's `save_jointype` and kept YB's debug trace block. + - try_nestloop_path PATH_PARAM_BY_PARENT: + - PG commit b7e2121ab7d6166b835a46ceaab1b6a6dc589703 removed the `PATH_PARAM_BY_PARENT`/`reparameterize_path_by_child` block. + - YB added an adjacent `yb_batched_clause_final_check` block. + - Dropped the reparameterization block, keeping YB's `yb_batched_clause_final_check`. + - match_unsorted_outer create_material_path call: + - PG commit 4020b370f214315b8c10430301898ac21658143f added `true` argument to `create_material_path`. + - YB added `yb_assign_unique_path_node_id` call (adjacent line conflict). + - Used PG's call and kept YB's block. + +- src/backend/utils/adt/pgstatfuncs.c: + - PG_STAT_GET_RELENTRY_INT64 macro: + - PG commit 83a1a1b56645b7a55ec00e44f8018116ee87c720 removed `pg_stat_get_numscans`, added `PG_STAT_GET_RELENTRY_INT64`. + - YB added `extern bool yb_retrieved_concurrent_index_progress` and `yb_pg_stat_retrieve_concurrent_index_progress` declarations. + - Placed YB's declarations right after the includes, then all PG macros together. + - progress command check: + - PG commit d7e39d72ca1c6f188b400d7d58813ff5b5b79064 removed the `!beentry ||` null check. + - YB commit 5e1a3dc5de25fb42f3291a3633c597f90cacef59 added `PROGRESS_COMMAND_COPY` exception to report even after command finishes. + - Took PG's removal of the null check; kept YB's `PROGRESS_COMMAND_COPY` exception. + - pg_stat_get_activity PG_STAT_GET_ACTIVITY_COLS: + - PG commit 697f8d266cfb33409f7ccf3319f4448477066329 changed column count to 31. + - YB commit d2a700a3a35edc3a0e74a5ee72d36e9cac7319c1 added `YB_PG_STAT_GET_ACTIVITY_COLS` and `YB_BACKEND_XID_COL`. YB did not change PG_STAT_GET_ACTIVITY_COLS (this is an adjacent line conflict). + - Used PG's 31 column count and kept YB's additional field definitions. + - pg_stat_get_activity values/nulls arrays: + - PG commit 9fd45870c1436b477264c0c82eb195df52bc0919 added `= {0}` initializers. + - YB changed to use `PG_STAT_GET_ACTIVITY_COLS + YB_PG_STAT_GET_ACTIVITY_COLS` size. + - Used YB's combined size with PG's `= {0}` initializer style. + +- src/backend/executor/nodeIndexscan.c: + - IndexNext: + - PG commit e9aaf06328c7f962f8586618981e9763d31402a3 changed scan direction logic. + - YB commit added `NoMovementScanDirection` for YB relations. + - Kept PG's `ScanDirectionCombine`; appended YB's `NoMovementScanDirection` check. + - ExecIndexBuildScanKeys amcanorder check: generalized AM check pattern. + - ExecIndexScanInitializeDSM, ExecIndexScanInitializeWorker: + - PG added more arguments to index_beginscan_parallel call. + - YB added `yb_init_index_scandesc`, `yb_agg_pushdown_init_scan_slot`. + - Kept PG's new parameter and YB's init functions. + - New functions at end-of-file: PG added instrumentation functions, YB commit added `yb_agg_pushdown_init_scan_slot`; keep both. + +- src/bin/pg_upgrade/check.c: + - `pg_fatal`/`fprintf`: PG added many new call sites; YB_TODO_PG19MERGE added to handle migration to `yb_fatal`/`yb_fprintf_and_log`. + - Forward declarations / `DataTypesUsageChecks`: + - PG added code below the forward declarations. + - YB added forward declarations for YB check functions + `#define YB_SUPERUSER`. + - Placed YB declarations and `#define YB_SUPERUSER` below PG forward declarations and before `DataTypesUsageChecks`. + - check_and_dump_old_cluster, start_postmaster / early checks: + - PG commit 4b56bb4ab4856070d5ea4aeafdd663d8bf96b874 changed `live_check` to `user_opts.live_check`. PG also added some other checks. + - YB `is_yugabyte_enabled()` guard on `start_postmaster`, plus `yb_check_upgrade_compatibility_guc`, `yb_check_old_cluster_user`, `yb_check_yugabyte_user`, `yb_check_system_databases_exist` block. + - Took PG's changes, wrapped `start_postmaster` with `!is_yugabyte_enabled()`, kept YB check block. + - check_and_dump_old_cluster, check_is_install_user / check_for_prepared_transactions / check_for_isn_and_int8_passing_mismatch guards: + - PG commits f638aafd1ea8505750ca9546e0e0b2e4cf31027d removed `check_proper_datallowconn`, and 347758b1206364e3bec5ad6cd649b4ba9fe1be7b removed `check_for_composite_data_type_usage` / `check_for_reg_data_type_usage`. + - YB added `!is_yugabyte_enabled()` guards on `check_is_install_user` `check_for_isn_and_int8_passing_mismatch`, and `check_for_prepared_transactions`. + - Kept the three calls with their YB `!is_yugabyte_enabled()` guards, dropping the other function calls. + - check_and_dump_old_cluster, old version checks (check_for_jsonb_9_4_usage, old_9_3_check_for_line_data_type_usage) + YB checks: + - PG commit 347758b1206364e3bec5ad6cd649b4ba9fe1be7b removed these calls (consolidated into `data_types_usage_checks[]` framework). + - YB added `!is_yugabyte_enabled()` on old version checks, and added additional yb checks. + - Dropped old PG version checks (if they're executed as part of data_types_usage_checks, that should be harmless), keeping all YB-specific checks. + - check_and_dump_old_cluster, stop_postmaster guard: + - PG commit 4b56bb4ab4856070d5ea4aeafdd663d8bf96b874 changed `live_check` to `user_opts.live_check`. + - YB added `is_yugabyte_enabled()` guard. + - Combined: `if (!is_yugabyte_enabled() && !user_opts.live_check)`. + - check_for_connection_status error reporting: + - PG commit f638aafd1ea8505750ca9546e0e0b2e4cf31027d added new error text mentioning `datconnlimit`, plus new `check_for_unsupported_encodings` function after it. + - YB used `yb_fatal` pattern instead of `pg_fatal`. + - Took PG's error text and new function, using YB `yb_fatal` pattern. + - check_for_user_defined_postfix_ops error reporting: + - PG commit c34eabfbbfd3d3799bc7bc61f22b1fe730c53fe8 replaced inline query with UpgradeTask framework. + - YB had old inline query with `yb_fprintf_and_log`, `yb_fatal`. + - Took PG's changes with YB `yb_fatal` error pattern. + - check_for_incompatible_polymorphics (2 conflicts): + - PG commit cf2f82a37cc35895b67c83dd2b33d2fcf4688a55 replaced inline query with UpgradeTask framework. + - YB had old inline query with `yb_fprintf_and_log`, `yb_fatal`. + - Took PG's changes with YB `yb_fatal` error pattern. + - check_for_not_null_inheritance / old check_for_removed_data_type_usage area: + - PG commit f295494d338c452617f966d4d1f13a726cd72661 added `check_for_not_null_inheritance`. PG commit 347758b1206364e3bec5ad6cd649b4ba9fe1be7b removed `check_for_removed_data_type_usage`. + - YB changed code in check_for_removed_data_type_usage to use `yb_fatal` error pattern. + - Took PG's `check_for_not_null_inheritance` as-is (new PG function, kept `pg_fatal`). + - check_for_gist_inet_ops / old check_for_removed_data_type_usage and check_for_jsonb_9_4_usage function definitions: + - PG commit b352d3d80b94269be6e627b5223902fe785d766f added `check_for_gist_inet_ops`. PG commit 347758b1206364e3bec5ad6cd649b4ba9fe1be7b removed `check_for_removed_data_type_usage` and `check_for_jsonb_9_4_usage` function definitions. + - YB had `check_for_removed_data_type_usage` and `check_for_jsonb_9_4_usage` with `yb_fatal` error pattern. + - Took PG's `check_for_gist_inet_ops` as-is (new PG function, kept `pg_fatal`). + +- src/bin/pg_dump/pg_dump.c: + - Forward declaration (`binary_upgrade_set_pg_class_oids`): + - PG commit 6e1c4a03a978ed3574124d8f2be22ba2e5a4b1e9 removed `is_index` parameter. + - YB added `yb_binary_upgrade_preserve_index_tablegroup_oid`. + - Combined both. Fixed YB call site in `dumpTableSchema` (`tbinfo->primaryKeyIndex` block) that passed `true` as `is_index` to drop the extra arg. + - Long options switch in `main()` (cases 12+): + - PG added new cases. + - YB assigned case 12 to `--read-time`. + - Renumbered YB's `--read-time` to case 26 (after PG's last case 25 `--restrict-key`); moved it to the bottom of the switch. All PG cases 12-25 kept; YB's case 26 added. Renumbered `read-time` to 26 in `long_options` in `main()`. + - check_mut_excl_opts / sequence_data: + - PG commit 7c8280eeb5872f5c2663b562a9c6fcf8ec8a4b82 replaced individual `if` checks with `check_mut_excl_opts` helper. PG commit 9c49f0e8cd7d59e240f5da88decf2d62d8a4ad0d removed `if (dopt.binary_upgrade) dopt.sequence_data = 1;` and replaced with `--sequence-data` CLI option. + - YB (commit ccd9a1106ade392e18cd46bd5d02c3889028e0d9 and others) added YB validation checks and modified the `sequence_data` condition. + - Kept YB validation checks; then PG's `check_mut_excl_opts` calls. Commented out YB's `sequence_data` block with YB_TODO_PG19MERGE to verify whether `include_yb_metadata` callers should pass `--sequence-data` instead. + - Help text: PG added `--restrict-key`, YB added `--read-time`; keep both. + - selectDumpableExtension, extension OID check: + - PG added `(Oid)` cast. + - YB commit 3063cadc3db09da8522552f96c7b50f0497448f6 added `strcmp("plpgsql", ...)` exception to the `if` condition. + - Combined both changes. + - dumpDatabase database query: + - PG (commit f696c0cd5f299f1b51e214efc55a22a782cc175d and others) changed to `appendPQExpBufferStr`, added more version checks. + - YB commit b0184bd20b04e8f7de5fa9f7b90982b60817c124 (later touched by other commits) extracted the inline query into `ybQueryDatabaseData()` and called it from both `dumpDatabase` and `getDatabaseOid`. No YB-specific changes were made to the query logic. + - Kept YB's `ybQueryDatabaseData`, taking PG's changes into `ybQueryDatabaseData`. + - pg_largeobject_metadata comment: PG added to the comment, YB added YB note; keep both. + - binary_upgrade_set_pg_class_oids RelFileNumber check: + - PG commit b0a55e43299c4ea2a9a8c757f9c26352407d0ccc renamed to `RelFileNumberIsValid(entry->relfilenumber)` and uses `entry->relkind`. + - YB added `IsYugabyteEnabled` guard for partitioned tables. + - Combined both changes. + - getExtensions malloc: + - PG commit 6736dea14afbe239588dad1c947ceb6e50adbf72 changed to `pg_malloc_array`. + - YB added `yb_dumpable_extensions_with_config_relations` allocation. + - Kept both; used PG's `pg_malloc_array` style in YB code. + - getIndexes index query: + - PG commit 8b26769bc441fffa8aad31dddc484c2f4043d2c9 changed `appendPQExpBuffer` to `appendPQExpBufferStr`. + - YB commit 023480252c847a8153f1fa1e9af8021120a484a2 added `i.indoption` column. + - Added YB's `i.indoption` using PG's `appendPQExpBufferStr`. + - getTableAttrs mallocs: + - PG commit 6736dea14afbe239588dad1c947ceb6e50adbf72 changed to `pg_malloc_array`. PG added some new fields, removed others. + - YB commit 023480252c847a8153f1fa1e9af8021120a484a2 added `primaryKeyIndex`. + - Kept PG's changes and YB's `primaryKeyIndex`. + - Stats query (pg_stats): + - PG added `>= 190000` branch with `tableid` join. + - YB added `yb_int_pg_stats_v11` view fallback for older YB versions. + - Kept PG's `190000` branch first, then YB's view fallback in the `>= 90400` branch. + - dumpACL: + - PG updated comment about "large object ACLs". + - YB commit 406a974a6ba046918b9c05b7c8d498f61fd8cf45 added workaround for `pg_stat_statements_reset` signature change. + - Kept YB's workaround and PG's comment change. + - dumpTableSchema view drop/table drop (3 conflicts): + - PG commit 2f094e7ac691abc9d2fe0f4dcf0feac4a6ce1d9c removed/moved drop commands. + - YB added `|| dopt->include_yb_metadata` (adjacent line conflict). + - Kept YB's `|| dopt->include_yb_metadata`. + - dumpTableSchema YB colocation/table properties block: + - PG added a comment. + - YB extended the if block for binary upgrade mode to also dump the index oid and relfilenode for pkey indexes, and added logic for tablegroups and colocation. + - Kept YB's changes followed by PG's comment; updated `binary_upgrade_set_pg_class_oids` call to new signature. + - NOT NULL constraint printing: + - PG commit 14e87ffa5c543b5f30ead7413084c25f7735039f changed not null tracking and updated comment. + - YB added `|| dopt->include_yb_metadata`. + - Took PG's changes + `|| dopt->include_yb_metadata`. + - dumpTableSchema dropped columns recreation: + - PG commit b3f0e0503f333938df638a8f499909ce4901b40c refactored drop column recreation. + - YB added YB logic for preserving column ordering for inherited tables. + - Took PG's changes; added YB_TODO_PG19MERGE. + - dumpTableSchema free calls: + - PG made `free()` unconditionally. + - YB added `freeYbcTablePropertiesIfRequired`. + - Took PG's unconditional frees + YB's cleanup. + - PRIMARY KEY / UNIQUE constraint (2 conflicts): + - PG commits d9595232579a3a9fadf4ce0b4cd58c1a3fc3b2f7 and fc0438b4e80535419a4e54dba87642cdf84defda added `NULLS NOT DISTINCT` handling, `conperiod` (WITHOUT OVERLAPS) + INCLUDE columns. + - YB commits 77f4fab592c0d0ff0f5b528e13c7b27501eea4ca and 67443f08384b8a56f4e586de127cedd025a5a931 added `dump_index_for_constraint` conditional and moved the default PG logic into the else block. + - Kept PG's NULLS NOT DISTINCT + conperiod + INCLUDE within YB's conditional structure. + - End-of-file functions: PG added functions, YB added others; keep both (PG first). + +- src/backend/utils/cache/syscache.c: + - cacheinfo[] table: + - PG commit 9b1a6f50b91dca6610932650c8c81a3c924259f9 moved cacheinfo[] into the generated catalog/syscache_info.h (genbki.pl). + - YB added `YBTABLEGROUPOID` and `YBCONSTRAINTRELIDTYPIDNAME` to `cacheinfo[]`. + - Kept PG's changes and adding MAKE_SYSCACHE declarations to the YB catalog headers: `MAKE_SYSCACHE(YBTABLEGROUPOID, pg_yb_tablegroup_oid_index, 4)` in pg_yb_tablegroup.h and `MAKE_SYSCACHE(YBCONSTRAINTRELIDTYPIDNAME, pg_constraint_conrelid_contypid_conname_index, 16)` in pg_constraint.h. The third arg is nbuckets, copied from the trailing field of the corresponding cacheinfo[] entry in syscache.c (4 for YBTABLEGROUPOID, 16 for YBCONSTRAINTRELIDTYPIDNAME). + - SearchSysCache* cacheId assertion (5 conflicts): + - PG changed the Assert. + - YB added a multi-thread-mode block. + - Kept PG's assertion; prepended YB's `IsMultiThreadedMode` error block at all 5 sites. + +- src/include/utils/syscache.h: + - includes: + - PG commit 9b1a6f50b91dca6610932650c8c81a3c924259f9 moved `SysCacheIdentifier` enum to auto-generated `catalog/syscache_ids.h`. + - YB added YB-specific entries (`YBTABLEGROUPOID`, `YBCONSTRAINTRELIDTYPIDNAME`) to the enum, an include, and a comment about stable syscache ids. + - Kept PG's changes, adding a YB_TODO_PG19MERGE to reconsider the YB comment. The YB caches were already handled by the resolution for src/backend/utils/cache/syscache.c. + +- src/backend/utils/adt/pg_upgrade_support.c: + - End-of-file, new functions (2 conflicts): + - PG added new functions. + - YB added `binary_upgrade_set_next_tablegroup_oid`, `binary_upgrade_set_next_tablegroup_default`. + - Kept all PG functions first, then the two YB tablegroup functions at the bottom of the file. + +- src/backend/utils/init/globals.c: + - MyDatabaseHasLoginEventTriggers / YB database globals: + - PG added `MyDatabaseHasLoginEventTriggers`. + - YB added `MyDatabaseColocated`, `MyColocatedDatabaseLegacy`, and other `Yb`-prefixed globals. + - Kept both sets of globals. + - End-of-file globals: PG added some globals, YB added others; keep both. + +- src/bin/pg_upgrade/version.c: + - jsonb_9_4_check_applicable function body: + - PG commit 347758b1206364e3bec5ad6cd649b4ba9fe1be7b removed `check_for_data_type_usage` and added `jsonb_9_4_check_applicable`. + - YB had old `check_for_data_type_usage` body with `yb_fprintf_and_log`. + - Took PG's changes. The TODO in check.c covers migration to `yb_fprintf_and_log`. + - process_extension_updates callback / old sql_identifier check: + - similar to above. + +- src/backend/libpq/auth.c: + - auth_failed forward declaration and definition (2 conflicts): + - PG commit c4ff16339f07d1e253bdf18e5da5fa25f62a750d added `int elevel` parameter. + - YB added `bool yb_role_is_locked_out` parameter. + - Combined both changes. + - RADIUS forward declarations and implementation: + - PG commit a1643d40b308911cc725e62d3c5f7904b426aa09 removed RADIUS support entirely, commit e21d6f297158db1383a7c9a668ebe1048f2eac39 moved `PG_MAX_AUTH_TOKEN_LENGTH` to `auth.h` + - YB added `YbCheckJwtAuth`. + - Followed PG's removals; kept YB's JWT. + - auth_failed, Assert / passthrough logic: + - PG commit c4ff16339f07d1e253bdf18e5da5fa25f62a750d added `Assert(elevel >= FATAL)`. + - YB commit 44a0bfb01cc0678246350160782898b80cc3ded5 added auth passthrough logic (`yb_is_auth_passthrough`, `port->yb_has_auth_passthrough_failed`). + - Kept PG's Assert first, then YB's additions. + - auth_failed, cdetail format string: + - PG commit 1b73d0b1c3934f703d68031957d37c2a9765e798 added `sourcefile` in format string. + - YB commit 785b8e3045f2cbe6c38bbcb6f347c751f6700715 added `maskedline` fallback to `rawline`. + - Combined both changes. + - auth_failed, ereport call: + - PG commit c4ff16339f07d1e253bdf18e5da5fa25f62a750d changed `FATAL` to `elevel` parameter. + - YB added `YbAuthFailedErrorLevel` + `YbSendFatalForLogicalConnectionPacket`. + - Kept YB's changes. + - auth_failed, pg_unreachable(): + - PG commit c4ff16339f07d1e253bdf18e5da5fa25f62a750d added `pg_unreachable()` after ereport. + - YB commit 44a0bfb01cc0678246350160782898b80cc3ded5 added comment saying function can return in auth passthrough mode. + - Kept `pg_unreachable()` guarded with `if (!yb_is_auth_passthrough)`, keeping YB comment. + - set_authn_id: + - PG added `MyClientConnectionInfo.auth_method` and moved `authn_id` from `Port` to `MyClientConnectionInfo`. + - YB commit cc95c8d045ea20dcf28403b4180206cf812636ea added auth passthrough logic. + - Combined both: YB's memory optimization on PG's `authn_id`, plus `auth_method` assignment. + - ClientAuthentication, OAuth vs JWT case: + - PG added `case uaOAuth:`. + - YB added `case uaYbJWT:`. + - Kept both. + - ClientAuthentication, auth_failed call: + - PG commit c4ff16339f07d1e253bdf18e5da5fa25f62a750d added `abandoned ? FATAL_CLIENT_ONLY : FATAL` elevel. + - YB added `yb_role_is_locked_out` parameter. + - Merged both parameters. + - recv_password_packet comment: + - PG commit a1643d40b308911cc725e62d3c5f7904b426aa09 removed RADIUS mention. + - YB added comment (adjacent line conflict). + - Kept PG's updated comment; added YB's passthrough comment. + - fallthrough: pg_fallthrough vs yb_switch_fallthrough: keep pg_fallthrough. + - LDAP bind password: + - PG commit 419a8dd8142afef790dafd91ba39afac2ca48aaf added `ldap_password_hook` inline. + - YB commit 8f4e959e40157d202987026e24da5ff81ec4aa09 used pre-computed `hba_password` from `get_ldap_password()`. + - Kept YB's `hba_password` approach with `pfree`. + - RADIUS code block: + - PG commit a1643d40b308911cc725e62d3c5f7904b426aa09 removed RADIUS entirely. + - YB added JWT. + - Dropped RADIUS; kept YB's JWT. + +- src/backend/libpq/pqcomm.c: + - Static variable declarations (`PqSendPointer`, `PqSendStart`): + - PG changed types from `int` to `size_t`. + - YB added `PqSendYbSavedBufPos` and `PqSendYbNonRestartableData`. + - Took PG's `size_t` types, and then YB's variables (with `size_t` / `bool`). + - Section comment + YB buffer position functions: + - PG commit 4945e4ed4a72c3ff41560ccef722c3d70ae07dbb changed section header comment. + - YB added `YBSaveOutputBufferPosition()` and `YBRestoreOutputBufferPosition()`. + - Kept YB functions before PG's renamed section comment. + - AcceptConnection socket config: + - PG moved socket config from `StreamConnection` to `pq_init()`, renamed function to `AcceptConnection`. + - YB commit 96abb52a43b002a11043075c10926abf6ca3677d removed file-scope `#define PQ_SEND_BUFFER_SIZE 8192` (replaced with dynamic `YBGetYsqlOutputBufferSize()`), re-added it inside `#ifdef WIN32` in the socket config block. + - Took PG's restructuring; ported `#define PQ_SEND_BUFFER_SIZE 8192` into `pq_init()`'s `#ifdef WIN32` block (WIN32 SO_SNDBUF optimization still references it). + - internal_flush / internal_flush_buffer refactoring: + - PG commit c4ab7da60617f020e8d75b1584d0754005d71830 split `internal_flush` into wrapper calling generic `internal_flush_buffer`. + - YB commit 7799304e5f60b97e26764ab7496f3306485fb61a added `YBMarkDataSent()` call. + - Kept PG's wrapper (just calls `internal_flush_buffer`); kept YB logic (`YBMarkDataSent()`) in `internal_flush_buffer`. + - Error/success-path buffer resets in `internal_flush_buffer` (2 conflicts): + - PG commit c4ab7da60617f020e8d75b1584d0754005d71830 changed `PqSendStart = PqSendPointer = 0;` to `*start = *end = 0`. + - YB added `PqSendYbSavedBufPos = 0` right below. + - Took PG's changes; appended YB's `PqSendYbSavedBufPos = 0` after each reset. + +- src/backend/libpq/hba.c: + - Ident mapping comment: + - PG commit a28269708844246fb1ec00a536b391cac0a64972 removed `NOTE: ... `. + - YB added a YB note. + - Dropped PG NOTE, kept YB note. + - UserAuthName array: + - PG commit a1643d40b308911cc725e62d3c5f7904b426aa09 removed `"radius"`, b3f0be788afc17d2206e1ae1c731d8aeda1f2f59 added `"oauth"`. + - YB commit 7ec9b8d328214108367b027e99ef7e5d149d1523 added `"jwt"`. + - Took PG's changes, appending YB's `"jwt"`. + - Function forward declarations: + - Adjacent line conflict. + - PG commit efc981627a723d91e86865fb363d793282e473d1 renamed `tokenize_inc_file` to `tokenize_expand_file` with added `depth` parameter. + - YB added `yb_tokenize_hardcoded` and `yb_tokenize_line`. + - Used PG's `tokenize_expand_file`; appended YB declarations below. + - parse_hba_line, auth method parsing: + - PG commit a1643d40b308911cc725e62d3c5f7904b426aa09 removed `uaRADIUS`, b3f0be788afc17d2206e1ae1c731d8aeda1f2f59 added `uaOAuth`. + - YB commit 7ec9b8d328214108367b027e99ef7e5d149d1523 added `uaYbJWT`. + - Kept PG's `uaOAuth` first, then YB's `uaYbJWT`. + - parse_hba_line, mandatory auth args validation: + - PG removed RADIUS validation block. + - YB commit 18bb9b843b6bd38cd75a61a390537dc67594a032 added RADIUS + JWT validation. + - Dropped RADIUS validation, keeping YB's JWT validation. + - parse_hba_line, end of function: + - PG commit b3f0be788afc17d2206e1ae1c731d8aeda1f2f59 added OAuth enforcement block. + - YB commit 785b8e3045f2cbe6c38bbcb6f347c751f6700715 added ldapbindpasswd logic. + - Kept both. + - parse_hba_auth_opt, "map" option: + - PG added `uaOAuth`, YB added `uaYbJWT`; keep both. + - load_ident error cleanup: + - PG commit a28269708844246fb1ec00a536b391cac0a64972 removed the foreach loop. + - YB added `yb_ident_context` guard. + - Dropped obsolete foreach loop; kept YB's guard. + +- src/backend/commands/indexcmds.c: + - DefineIndex, lock acquisition: + - PG commit 23382b0f8b21e3f5330d765d1abfcef58d086111 renamed `relationId` to `tableId`. + - YB added `IsYugaByteEnabled() ? AccessShareLock :` conditional to the line above (adjacent line conflict). + - Kept YB's conditional with PG's `tableId`. + - DefineIndex, eq_strategy + LSM_AM_OID: generalized AM check pattern + - build_attrmap_by_name call: + - PG added 3rd arg `bool missing_ok`. + - YB added 3rd arg `bool yb_ignore_type_mismatch`. + - Passed both: `false, false /* yb_ignore_type_mismatch */`. + - DefineIndex, concurrent build + WaitForOlderSnapshots + YB else: + - PG (commit bc32a12e0db2df203a9cb2315461578e08568b9c and others) made changes to concurrent build logic -- renamed `relationId` to `tableId`, added `INJECTION_POINT("define-index-before-set-valid")` etc. + - YB wraps PG concurrent build logic in `if (!IsYugaByteEnabled())` and has an `else` branch YB logic. + - Kept YB's `if/else` structure. Ported all of PG's concurrent build changes to the if block, changed multiple references of `relationId` to `tableId`. + - DefineIndex including column ASC/DESC error message: + - PG added `parser_errposition(pstate, attribute->location)`. + - YB changed message text to include "ASC/DESC/HASH options". + - Kept YB's "ASC/DESC/HASH options" message text with PG's `parser_errposition`. + - DefineIndex ResolveOpClass + YB collation check: + - PG commit 23382b0f8b21e3f5330d765d1abfcef58d086111 renamed `classOidP` to `opclassOids`. + - YB added `YbCheckCollationRestrictions` call after `ResolveOpClass`. + - Used PG's `opclassOids`, keeping YB's collation check (updated to use `opclassOids`). + - DefineIndex index column options DESC/HASH: + - PG commit 23382b0f8b21e3f5330d765d1abfcef58d086111 renamed `colOptionP` to `colOptions`. + - YB commit b9a7f457f52c7f6655ffd3ddc256f93835370558 added `INDOPTION_HASH`. + - Used PG's `colOptions`, keeping YB's HASH option block. Also fixed `colOptionP[attn]` references outside conflict markers to `colOptions[attn]`. + - Unordered AM ASC/DESC error message: same as "Including column ASC/DESC error message" above. + - reindex_index calls (2 conflicts): + - PG commit f21848de20130146bc8039504af40bd24add54cd added `stmt` as first parameter. + - YB added trailing params (`is_yb_table_rewrite`, `yb_copy_split_options`, `preserved_index_split_options`). YB commit ccd891e108d00e08657d882e4799388e8d20b274 added `if (!IsYugaByteEnabled()) PopActiveSnapshot()` guard at second call site. + - Kept PG's `stmt` first param, then YB's trailing params. At second call site, kept YB's conditional `PopActiveSnapshot`. + +- src/bin/pg_dump/pg_dumpall.c: + - buildShSecLabels forward declaration + connection function declarations: + - PG commit c1da7281060d646f863e920a1aac3b9dbc997672 removed `connectDatabase`, `constructConnStr`, `executeQuery` forward declarations, YB added `const char *yb_indent` parameter to `buildShSecLabels`. + - Kept YB's `yb_indent` parameter on `buildShSecLabels`, dropping the removed forward declarations. + - long_options: PG added options, YB added others; keep both. + - Option validation: PG added some validations, YB added others; keep both. + - pgdumpopts + file open: + - PG added `sequence_data` pgdumpopt, archive format directory creation (`create_or_open_dir`), and restrict key generation. + - YB added `include_yb_metadata`, `yb_dump_role_checks` pgdumpopts. + - Placed YB pgdumpopts right after PG's `sequence_data`, before PG's file open block. + - Cluster dump header: + - PG commit 763aaa06f03401584d07db71256fc0ab47235cce added `archDumpFormat != archNull` block, moved existing code into else block, YB changed "PostgreSQL" to "YSQL". + - Took PG's archive creation block, using "YSQL" in the text format `else` path. + - Dump complete message: + - PG commit 763aaa06f03401584d07db71256fc0ab47235cce moved dump complete log and fclose inside `archDumpFormat == archNull` guard. YB changed "PostgreSQL" to "YSQL". + - Took PG's structure with "YSQL". + - Help text: + - PG changed to "exports...as an SQL script or to other formats". YB changed "PostgreSQL" to "YSQL". + - Combined. + - dumpRoles, Roles header `if (PQntuples(res) > 0)` opening brace: + - PG added `&& archDumpFormat == archNull` condition. YB added an opening brace for `include_yb_metadata` block. + - Kept both: PG's `archDumpFormat == archNull` condition and YB's `include_yb_metadata` block, keeping the brace. + - dumpRoles, role comments: + - PG uses separate `comment_buf`. YB wrote comment into `buf` with `yb_indent` and `yb_frolename`. + - Used PG's `comment_buf` but with YB's `yb_indent` and `yb_frolename`. + - dumpRoles, `buildShSecLabels` call + output path: + - PG uses `seclabel_buf` (separate buffer) + `archDumpFormat` branching with `ArchiveEntry` for roles, comments, security labels. YB used `buf` with `yb_indent`, had `yb_need_endif`/`include_yb_metadata` appends, `free(yb_frolename)`. + - Used PG's `seclabel_buf` with YB's `yb_indent` parameter. Kept YB's `yb_need_endif` and `include_yb_metadata` appends before the output branching. Kept PG's `archDumpFormat` branching with `ArchiveEntry`. Kept `free(yb_frolename)`. + - dumpRoleMembership, GRANT generation (2 conflicts): + - PG commit ce6b672e4455820a0348214be0da1a024c3f619f replaced the simple per-row GRANT; Took PG's code; added YB_TODO_PG19MERGE to integrate YB's code. + - dumpRoleMembership, trailing newlines: + - PG added `archDumpFormat == archNull` guard. YB had conditional second newline for `yb_dump_role_checks`. + - Combined both: `archDumpFormat == archNull` guard with YB's conditional second newline inside. + - dumpTablespaces, header `if (PQntuples(res) > 0)` opening brace: + - PG added `&& archDumpFormat == archNull` condition (single-line, no brace). YB had a brace for `include_yb_metadata` block. + - Kept both: PG's condition and YB's brace for the multi-statement block. + - dumpTablespaces, LOCATION value and spcoptions: + - PG commit a72d613b4c91462d9405c4e1b05c42d33013c333 added `is_absolute_path` check (empty string for in-place tablespaces) before `appendPQExpBufferStr(buf, ";\n")`. + - YB commit 0ce7f408edcdbda8860b80f034f4397d79be8195 added `ybProcessTablespaceSpcOptions` to the `spcoptions` block, and moved PG's `appendPQExpBufferStr(buf, ";\n")` to below this block. + - Kept PG's `is_absolute_path` check; restored PG's `appendPQExpBufferStr(buf, ";\n")` guarded with `!IsYugabyteEnabled`; guarded YB's version with `IsYugabyteEnabled`. + - dumpTablespaces, buildShSecLabels call + output path: PG changed to use `seclabel_buf`, YB added `yb_indent` parameter `""` and `include_yb_metadata` newline block; combined. + - dumpUserConfig: + - PG added `archDumpFormat` branching with `ArchiveEntry`. YB added `yb_dump_role_checks` argument to `makeAlterConfigCommand`. + - Kept YB's `yb_dump_role_checks` parameter + PG's `ArchiveEntry` output branching. + - end-of-file function definitions: + - keep both PG's and YB's functions. + +- src/backend/commands/explain.c: + - ExplainQuery: + - PG commit c65bc2e1d14a2d4daed7c1921ac518f2c5ac3d17 refactored inline option parsing into `ParseExplainOptionList()`. + - YB added options: dist, debug, commit, hints, uids, planid, queryid, plus validation logic (`yb_run_with_explain_analyze`, `YbToggleSessionStatsTimer`, etc.). + - Kept PG's `ParseExplainOptionList()` call; ported YB's custom options, validations, and `YbIsTimingNeeded` helper into explain_state.c. + - ExplainOnePlan declarations: + - PG added `serializeMetrics`. + - YB commit 2dd38ff21f550676d43c264d8831a0e80376a1db added `show_variable_fields`. + - Kept both. + - ExplainOnePlan show buffer usage: + - PG commit 5de890e3610d5a12cdaea36413d967cf5c544e20 added `EXPLAIN (MEMORY)` and changed the condition to `peek_buffer_usage(es, bufusage) || mem_counters`. PG commit 9d2d9728b8d546434aade4f9667a59666588edd6 moved the block for query id to `ExplainPrintPlan` + - YB added `show_variable_fields` to buffer usage condition, and `es->ybShowQueryId` to the query id condition. + - Kept PG's changes; moved YB's conditions to the appropriate locations. + - ExplainNode show_indexsearches_info (2 conflicts): PG added `show_indexsearches_info`, YB added `show_yb_rpc_stats`/`show_yb_planning_stats`; kept both. + - ExplainNode fallthrough: pg_fallthrough vs yb_switch_fallthrough: keep pg_fallthrough. + - ExplainNode T_YbSeqScan: PG added `show_scan_io_usage`/`show_ctescan_info`, YB added `show_yb_rpc_stats`/`T_YbSeqScan`; kept both. + - show_hash_info: + - PG commit 0de37b51065bc5b5848d65a9bb6f932ef392374f changed `ExplainPropertyInteger` to `ExplainPropertyUInteger`. + - YB commit ccb94810019b47e695a4e87d2508e0ad88830c13 added a `show_variable_fields` guard around it, and another `!show_variable_fields` block. + - Kept PG's `ExplainPropertyUInteger` inside YB's structure. + - show_memoize_info: + - PG added `if (es->costs)` block above `if (!es->analyze)`. + - YB added `|| yb_explain_hide_non_deterministic_field` to early return for `!es->analyze` (adjacent line conflict). + - Kept PG's new block and YB's condition. + - show_memory_counters / show_yb_rpc_stats: PG added `show_memory_counters`, YB added `show_yb_rpc_stats`; keep both. + - end-of-file functions: + - PG commit 9173e8b604636633a8e3aca54bb56a437bffa718 moved functions out of this file. + - YB added new functions below the PG functions; PG functions in the conflict had no YB changes. + - Took PG's removal; kept YB's functions. + +- src/backend/executor/execMain.c: + - ExecBuildSlotValueDescription signature: + - PG commit 9758174e2e5cd278cf37e0980da76b51890e0011 made function non-static and moved the declaration. + - YB commit e89d75bc16af0e819a56edd75126ba0ccc42174d changed function signature to pass `Relation rel`. + - Took PG's change (declaration moved; YB's `Relation rel` handled in the definition conflict below). + - standard_ExecutorStart: PG added an assert, YB added `YbSetMetricsCaptureTypeIfUnset`; kept both. + - standard_ExecutorRun: PG added an assert, YB added `YBBeginOperationsBuffering`; kept both. + - standard_ExecutorFinish: + - PG commit 2c16deee2f7d52d6567dcbad046f74a8e880ee52 changed `totaltime` to `query_instr`. + - YB added `YBEndOperationsBuffering` right above `InstrStopNode(queryDesc->totaltime, 0);`. + - Took PG's change; kept YB's `YBEndOperationsBuffering` block. + - standard_ExecutorEnd: + - PG changed `totaltime` to `query_instr`. + - YB added code below `queryDesc->totaltime = NULL;`. + - Took PG's change; kept YB's additions. + - InitPlan: + - PG commit a61b1f74823c9c4f79c95226a461f1e7a367764b changed `ExecCheckRTPerms` to `ExecCheckPermissions`. + - YB added a guard to `ExecCheckRTPerms`. + - Kept PG's change within YB's guard. + - ExecConstraints NOT NULL modified-columns check: + - PG commit cdc168ad4b22ea4183f966688b245cabb5935d1f added virtual generated column NOT NULL handling and extracted error reporting into `ReportNotNullViolationError`. + - YB added early `modifiedCols` computation to skip NOT NULL checks on unmodified columns during single-row updates without a target tuple. + - Kept YB's skip-check block before PG's `ATTRIBUTE_GENERATED_VIRTUAL` / `ReportNotNullViolationError` logic; added YB_TODO_PG19MERGE. + - ExecBuildSlotValueDescription definition: + - PG commit 9758174e2e5cd278cf37e0980da76b51890e0011 made the function non-static. + - YB commit e89d75bc16af0e819a56edd75126ba0ccc42174d changed function signature to pass `Relation rel`. + - Kept PG's signature; changed YB's `YBGetFirstLowInvalidAttributeNumber` call to `YBGetFirstLowInvalidAttributeNumberFromOid`; removed reloid definition; fixed `ExecBuildSlotValueDescription` call sites. + +- src/backend/executor/execPartition.c: + - build_attrmap_by_name calls (3 conflicts): + - PG added `missing_ok`. + - YB added `yb_ignore_type_mismatch`. + - Passed both arguments. + - ON CONFLICT restructuring: + - PG commit 88327092ff06c48676d2a603420089bf493770f3 wrapped DO UPDATE logic in `if (node->onConflictAction == ONCONFLICT_UPDATE)` guard. + - YB added `yb_ignore_type_mismatch` to `build_attrmap_by_name` calls. + - Took PG's structural changes; added YB's argument. + +- src/backend/utils/activity/pgstat.c: + - globals: PG's `upgstat_report_fixed`, YB's `yb_retrieved_concurrent_index_progress`: kept both. + - PGSTAT_KIND_BACKEND entry in pgstat_kind_builtin_infos array: + - PG commit 9aea73fc61d4e77e000724ce0b2f896590a10e03 added `PGSTAT_KIND_BACKEND` entry for backend-level statistics. + - YB side is empty (matches PG15 base). + - Kept PG's `PGSTAT_KIND_BACKEND` entry. + +- src/include/utils/wait_event.h: + - `#include` / enum area: + - PG commit fa88928470b538c0ec0289e4d69ee12356c5a8ce moved wait event enums and class defines to auto-generated `utils/wait_event_types.h`, replaced inline enums with `#include "utils/wait_event_types.h"`. + - YB added YB-specific wait events (`WAIT_EVENT_YB_*` entries in each category) and `#include "yb_ash.h"` to inline enums. + - Kept PG's `#include "utils/wait_event_types.h"`; kept YB's `#include "yb_ash.h"` below PG includes; added YB's wait events to wait_event_names.txt; added YB_TODO_PG19MERGE for documenting them. + +- src/backend/utils/activity/wait_event.c: + - declarations: PG added wait event infrastructure, YB added `yb_my_wait_event_info` and `yb_not_applicable`; kept both. + - `#include "utils/pgstat_wait_event.c"`: + - PG commit fa88928470b538c0ec0289e4d69ee12356c5a8ce replaced manual wait event name functions with `#include "utils/pgstat_wait_event.c"` (generated from wait_event_names.txt). + - YB added wait event description functions and YB wait events to PG's `pgstat_get_wait_*` functions (now auto-generated). + - Kept PG's changes; kept YB's wait event description functions. YB wait events were already moved to wait_event_names.txt in the resolution above. + +- src/backend/utils/adt/numeric.c: + - INT128 signature change vs numeric_to_double_no_overflow declaration: + - PG commit d699687b329e031cd90e967b39c3fd8a53ef8208 changed `int128` to `INT128` in `int128_to_numericvar` and removed `#ifdef HAVE_INT128` guard + `numericvar_to_int128`. + - PG also removed `numeric_to_double_no_overflow` but YB retained it as part of the PG15 initial merge (55782d561e55ef972f2470a4ae887dd791bb4a97) under `#ifdef YB_TODO`. + - Took PG's `INT128` signature, kept YB's `#ifdef YB_TODO` block for `numeric_to_double_no_overflow`. + - pg_fallthrough vs yb_switch_fallthrough(): + - keep pg_fallthrough. + +- src/backend/utils/adt/rangetypes.c: + - range_send SendFunctionCall vs StringInfoSendFunctionCall: + - PG commit 0f5ade7a367c16d823c75a81abb10e2ec98b420 and others made some changes to RANGE_HAS_LBOUND and RANGE_HAS_UBOUND blocks. + - YB commit 544122690311196d40059d9ea1311211df216247 removed PG code from this site and added `StringInfoSendFunctionCall` wrapper + - Kept both: YB's `StringInfoSendFunctionCall` guarded by `IsYugaByteEnabled()`, PG's `SendFunctionCall` + `pq_sendint32`/`pq_sendbytes` in `else` block. + +- src/backend/utils/cache/lsyscache.c: + - btree-AM checks generalized (6 conflicts): generalized AM check pattern + +- src/backend/utils/cache/plancache.c: + - yb_plan_references_pg_rel vs USE_VALGRIND block: + - PG commit b102c8c4733cf76ff0635dc440ee8dd11487ed95 added `USE_VALGRIND` block. + - YB added `yb_plan_references_pg_rel` assignment. + - Kept both; YB's assignment before PG's `USE_VALGRIND` block. + - StmtPlanRequiresRevalidation/BuildingPlanRequiresSnapshot vs YbIsCachedQueryValid: + - Kept both PG and YB functions. + +- src/backend/utils/error/elog.c: + - errfinish: + - PG commit 8305629afe64c9065369d022e91be9f16f3972fa refactored error location setting into `set_stack_entry_location()` static function. + - YB added `if IsYugaByteEnabled()` block and moved PG code under `else`. + - Kept YB's changes with PG's in the `else` block. + - FreeErrorDataContents: + - PG commit 8305629afe64c9065369d022e91be9f16f3972fa moved out `pfree(edata)` into wrapper `FreeErrorData`. + - YB added `yb_owns_file_and_func` cleanup. + - Kept YB's cleanup; dropped `pfree(edata)`. + +- src/backend/utils/init/postinit.c: + - includes: + - PG added variable declarations below the includes. + - YB added YB-specific includes. + - Placed YB includes below PG includes, followed by new PG declarations. + - CheckMyDatabase: + - PG commit 1c461a8d8d3c7a4655fdb944ffca94b1e49e5b3d replaced inline collation init with `init_database_collation()`. PG commit 844385d12e7515f3461e53728f0a9358bc35b6a5 removed `database_ctype_is_c`. + - YB added `YbPresetDatabaseCollation` block. + - Kept YB block before PG's `init_database_collation()`. + +- src/backend/utils/misc/ps_status.c: + - includes: PG added `#if !defined(WIN32)` block, YB added `leak_annotations.h` include; kept YB's first, then PG's. + - allocate_new_environ: + - PG commit db01c90b2f024298b08dca8aed6b43a2347dee0e added some logic for Valgrind. + - YB commit ad17a80e30f42e13d9b80950663a71fe133a5046 added `allocate_new_environ()` with `__lsan_ignore_object()`. + - Kept both. + +- src/bin/pg_dump/dumputils.h: + - sanitize_line(): + - PG commit 70693c645f6e490b9ed450e8611e94ab7af3aad2 added `sanitize_line()` declaration. + - YB added `PGconn *yb_conn` parameter to `buildACLCommands` signature (adjacent line conflict). + - Kept PG's declaration; retained YB's modified `buildACLCommands` signature. + - create_or_open_dir: + - PG added declarations below `makeAlterConfigCommand`. + - YB added `yb_dump_role_checks` parameter to `makeAlterConfigCommand` and declaration `YBWwrapInRoleChecks`. + - Kept all new PG function declarations; retained YB's `yb_dump_role_checks` parameter + `YBWwrapInRoleChecks`. + +- src/bin/pg_upgrade/pg_upgrade.h: + - ClusterInfo struct: + - PG added fields `nsubs` and `sub_retain_dead_tuples`. + - YB added `yb_hostaddr` and `yb_user`. + - Kept both sets of fields. + - UserOpts structs: + - PG added `default_char_signedness`. + - YB added `yb_working_dir`. + - Kept both fields. + +- src/bin/psql/help.c: + - HELPN / HELP0 (2 conflicts): + - PG commit b4336515b0803b83a4d8f102006986e5863d2c49 removes environment-variable-based defaults, changing `HELPN` with default to `HELP0` without default for --dbname + - YB changes some default values. + - Kept PG's changes. + +- src/include/access/genam.h: + - includes: + - PG (commit 81fc3e28e383d9a21843a5ab845a1bd1a1042e12 and others) added `typedef struct` declarations and removed `struct IndexInfo;` + - YB added YB-specific includes. + - kept YB includes first, then PG's forward declarations below. + - index_insert_cleanup vs yb_shared_insert param and yb_index_delete/yb_index_update: + - PG added `index_insert_cleanup()` declaration. + - YB added `yb_shared_insert` parameter to `index_insert` and added `yb_index_delete()`, `yb_index_update()` declarations + - Kept PG's `index_insert_cleanup()` and retained YB's `yb_shared_insert` parameter and function declarations. + +- src/include/access/relscan.h: + - struct field additions in TableScanDescData / IndexScanDescData / SysScanDescData (3 conflicts): + - PG added new fields + - YB added new fields + - Kept both: PG's new field plus all YB fields, in each struct. + +- src/include/catalog/dependency.h: + - SHARED_DEPENDENCY_PROFILE enum value: PG added `SHARED_DEPENDENCY_INITACL = 'i'`, YB added `SHARED_DEPENDENCY_PROFILE = 'f'`; kept both. + - ObjectClass enum: + - PG commit 89e5ef7e21812916c9cf9fcf56e45f0f74034656 removed `ObjectClass` enum from dependency.h. + - YB added `OCLASS_YBTBLGROUP`, `OCLASS_YBPROFILE`, `OCLASS_YBROLE_PROFILE`. + - Took PG's removal. + +- src/include/catalog/indexing.h: + - CatalogTupleUpdate: + - PG commit e1ac846f3d2836dcfa0ad15310e28d0a0b495500 added `const` qualifier to `CatalogTupleUpdate` parameter. + - YB added `bool yb_shared_insert` parameter to `CatalogTuplesMultiInsertWithInfo`. + - Kept both changes; PG's `const` and YB's `yb_shared_insert`. + - YB CatalogTupleDelete signature and YBCatalogTupleInsert: + - PG commit e1ac846f3d2836dcfa0ad15310e28d0a0b495500 added `const` qualifier to `CatalogTupleDelete` parameter. + - YB (commit 954a64b513f7a5713709c664093cb56848bb6369 and others) changed `CatalogTupleDelete` to take `HeapTuple tup` instead of `ItemPointer tid`, and added `YBCatalogTupleInsert` declaration. + - Kept YB's `CatalogTupleDelete(Relation, HeapTuple)` and retained `YBCatalogTupleInsert`. + +- src/include/commands/async.h: + - includes: + - PG commit 53c2a97a92665be6bd7d70bd62ae6158fe4db96e removed `NUM_NOTIFY_BUFFERS`. + - YB added `#include "storage/proc.h"`. + - Kept YB include, dropped `NUM_NOTIFY_BUFFERS`. + - functions end-of-file: PG added `AsyncNotifyFreezeXids()`, YB added `YbNotifsPollerMain()` and `YbCleanupListenStateForProc()`; kept both. + +- src/include/commands/progress.h: + - PROGRESS_COPY slot: + - PG commit 729439607ad210dbb446e31754e8627d7e3f7dda added `PROGRESS_COPY_TUPLES_SKIPPED`. + - YB added `PROGRESS_COPY_STATUS`. + - Kept both: renumbered YB's `PROGRESS_COPY_STATUS` to slot 7 so PG's `PROGRESS_COPY_TUPLES_SKIPPED` keeps slot 6. + - DATACHECKSUMS vs CREATE INDEX progress params: PG commit f19c0eccae9680f5785b11cdc58ef571998caec9 added `PROGRESS_DATACHECKSUMS_*` parameters, YB added `YB_PROGRESS_CREATEIDX_*` parameters; kept both blocks (PG's first). + +- src/include/commands/trigger.h: + - ExecARUpdateTriggers parameter naming and yb_skip_entities: + - PG commit a601366a460f68472bf70c4d94c57baa0a3ed1b2 renamed trigger function parameters to `oldslot`/`newslot` for consistency. + - YB added `const YbSkippableEntities *yb_skip_entities` parameter to trigger function. + - Adopted PG's `oldslot`/`newslot` parameter naming; kept YB's `yb_skip_entities` parameter. + - functions end-of-file: PG commit b7b27eb41a5cc0b45a1a9ce5c1cde5883d7bc358 added `AfterTriggerBatchCallback` and `RegisterAfterTriggerBatchCallback`, YB added `YbAddTriggerFKReferenceIntent` and `YbGetTriggerDepth`; kept both. + +- src/include/executor/instrument.h: + - YbInstrumentation struct vs comment/struct split: + - PG commit 5a79e78501f46bd3ac7fbd0ff84cf1e20dbafd19 split `Instrumentation` into base + `NodeInstrumentation` and changed comment. + - YB added `YbPgRpcStats` and `YbInstrumentation` struct. + - Kept YB struct before PG's changes. + - NodeInstrumentation closing vs YB `yb_instr` field: + - PG commit 5a79e78501f46bd3ac7fbd0ff84cf1e20dbafd19 moved `bufusage`/`walusage` into `NodeInstrumentation`. + - YB added `yb_instr` field to the (old) `Instrumentation` struct. + - Kept `yb_instr` in `NodeInstrumentation`; dropped `bufusage`/`walusage`. + +- src/include/libpq/hba.h: + - UserAuth enum: + - PG commit a1643d40b308911cc725e62d3c5f7904b426aa09 removed `uaRADIUS` (RADIUS support dropped); PG commit b3f0be788afc17d2206e1ae1c731d8aeda1f2f59 added `uaOAuth` for OAuth/OAUTHBEARER SASL support. + - YB added `uaYbJWT`. + - Dropped `uaRADIUS`; kept `uaOAuth`; added `uaYbJWT` after it with `USER_AUTH_LAST` set to `uaYbJWT`. + - HbaLine struct: + - PG removed RADIUS `HbaLine` fields and added OAuth fields. + - YB added `maskedline` and `yb_jwt_*` fields for JWT authentication. + - Dropped RADIUS fields; kept PG's OAuth fields; appended YB's `maskedline` and `yb_jwt_*` fields. + +- src/include/nodes/tidbitmap.h: + - includes and TBM_MAX_TUPLES_PER_PAGE: PG added `TBM_MAX_TUPLES_PER_PAGE` macro, YB added YB includes; kept both (YB include first, then PG's macro). + - YbTupleBitmap and TBMPrivateIterator: + - PG commit 7f9d4187e7bab10329cc00aff34cfba08248d4be renamed `TBMIterator` to `TBMPrivateIterator`. + - YB added `YbTupleBitmap` union. + - Kept YB's `YbTupleBitmap` union; took PG's `TBMPrivateIterator` rename. + +- src/include/postgres.h: + - varlena/TOAST definitions vs YB cmdtag.h include: + - PG (commit d952373a987bad331c0e499463159dd142ced1ef and others) moved varlena/TOAST definitions to `varatt.h` and added a comment. + - YB added `#include "tcop/cmdtag.h"`. No YB changes to varlena definitions (verified identical to PG15 base). + - Dropped varlena definitions; kept YB include and PG's new comment. + - YB password redaction functions vs Float8GetDatum removal: + - PG commit ee54046601de2d14ca9107ba04c50178d9b52fe6 removed `#ifdef USE_FLOAT8_BYVAL` and the corresponding `#else` block. + - YB added `YbRedactPasswordIfExists` and `YbParseCommandTag` declarations with comment + - Kept YB declarations. + +- src/include/replication/logical.h: + - LogicalDecodingContext fields: + - PG commit 29d0a77fa6606f9c01ba17311fc452dabd3f793d added `processing_required`; PG15 backport commit aee8c2b95492e1b0a228184cd9147b1c0749d673 added `in_create` to `LogicalDecodingContext` (on master commit bb19b70081e2248f242cd00227abff5b1e105eb6 the fix uses `in_slot_creation` in `SnapBuild` instead, so PG doesn't have `in_create` here). + - YB added `yb_start_decoding_at` and `yb_needs_relcache_invalidation`. + - Kept PG's `processing_required`; dropped `in_create` (not needed on PG19); kept YB fields. + - YB function declarations: PG added new functions, YB added others; kept both. + +- src/include/replication/snapbuild.h: + - SnapBuildExportSnapshot: + - PG renamed parameter from `snapstate` to `builder`. + - YB had an extra newline after the function declaration. + - Took PG's changes. + - SnapBuildSnapshotExists vs YB snapshot function: + - PG added `SnapBuildSnapshotExists`; PG15 backport commit 272248a0c1b18a82c4266e0dc3b526d4d2637de3 added `SnapBuildXidSetCatalogChanges` (backport-only, never on PG master - master commit 7f13ac812313666a2fbb8dacfbee67e78d2ba0bc used a different approach). + - YB added `YbSnapBuildExportSnapshotWithReadTime`. + - Kept PG's `SnapBuildSnapshotExists`; dropped `SnapBuildXidSetCatalogChanges`; kept YB's function. + +- src/include/storage/procsignal.h: + - ProcSignalReason: + - PG commit 17f51ea818753093f929b4c235f3b89ebcc7c5fb and others consolidated individual `PROCSIG_RECOVERY_CONFLICT_*` into single `PROCSIG_RECOVERY_CONFLICT`, changed `NUM_PROCSIGNALS` from enum sentinel to `#define`. + - YB commit d10c20c94c70035d3928aa89a1fd3ece6c881326 added `YB_PROCSIG_LOG_CATCACHE_STATS`; YB commit 73ae4051d7ec41383aa7b4ae7621b87628aeef9d added `PROCSIG_LOG_HEAP_SNAPSHOT` and `PROCSIG_LOG_HEAP_SNAPSHOT_PEAK`. + - Kept PG's consolidated `PROCSIG_RECOVERY_CONFLICT` and `#define NUM_PROCSIGNALS`; kept YB's additional signals. + - ProcSignalHeader and CleanupProcSignalStateForProc: PG commit 9d9b9d46f3c509c722ebbf2a1e7dc6296a6c711d added some declarations, YB added `CleanupProcSignalStateForProc`; kept both (YB first, then PG). + +- src/include/storage/sinval.h: + - includes: `relfilelocator.h` vs `relfilenode.h`: + - PG renamed `relfilenode.h` to `relfilelocator.h`. + - YB added `#include ` for `pid_t`. + - Kept PG's `relfilelocator.h`; kept YB's `sys/types.h`. + - SharedInvalSmgrMsg struct: + - PG renamed `RelFileNode rnode` to `RelFileLocator rlocator` and `backend ID` to `backend procno`. + - YB added `yb_version` and `yb_sender_pid` fields (adjacent line conflict). + - Kept PG's renames; kept YB's fields. + +- src/include/storage/spin.h: + - Inline spinlock functions vs YB spinlock tracking (2 conflicts): + - PG commit bfc321b4723e8ca5fd3adb22a2727ec5c55d3808 converted `SpinLockInit`/`SpinLockAcquire`/`SpinLockRelease` macros to `static inline` functions. + - YB commit ea4513abfdc26aebfc73da32bfa79a8a8f6e12ee added `ybSpinLocksAcquired` tracking to `SpinLockAcquire`/`SpinLockRelease` macros; YB added `miscadmin.h` and `proc.h` includes. + - Ported YB's spinlock counting logic directly into PG's `static inline` functions instead of macro overrides; kept YB's includes (note: later updated by build fixes, this resolution is stale). + +- src/include/tcop/cmdtaglist.h: + - CREATE/DROP PROPERTY GRAPH vs CREATE/DROP PROFILE command tags: + - PG commit 2f094e7ac691abc9d2fe0f4dcf0feac4a6ce1d9c added `CMDTAG_CREATE_PROPERTY_GRAPH`, `CMDTAG_DROP_PROPERTY_GRAPH`. + - YB added `CMDTAG_CREATE_PROFILE` and `CMDTAG_DROP_PROFILE`. + - Kept all entries in alphabetical order: `CREATE_PROFILE` before `CREATE_PROPERTY_GRAPH`, `DROP_PROFILE` before `DROP_PROPERTY_GRAPH`. + +- src/include/utils/backend_status.h: + - PgBackendStatus: + - PG commit c3eda50b0648005281c2a3cf95375708f8ef97fc and others changed `st_query_id` from `uint64` to `int64`; added `int64 st_plan_id` field. + - YB added YB-specific fields. + - Kept PG's changes; added YB's fields. + - LocalPgBackendStatus subxact fields: same as above. + +- src/backend/catalog/pg_enum.c: + - `oids` allocation: + - PG commit 1b105f9472bdb9a68f709778afafb494997267bd changed `(Oid *) palloc(...)` to `palloc_array(Oid, num_elems)` and moved the allocation below a new comment block about OID allocation strategy. YB side of this conflict had the old PG allocation style. + - Took PG's changes. + +- src/backend/commands/repack.c / src/backend/commands/cluster.c: + - PG commit c0b53ec06309f955455c7d71da277991d0da4ec0 renamed `cluster.c` to `repack.c` + - YB added includes; added extra params to `make_new_heap`,`finish_heap_swap`, `heap_create_with_catalog` calls/definitions; added `YbRelationSetNewRelfileNode` call after `heap_create_with_catalog`; added YB temp-toast else-branch; added MATVIEW transient-relkind swap in `swap_relation_files`; gated `performDeletion` with `yb_test_table_rewrite_keep_old_table`. + - Ported YB additions into `repack.c`; removed src/backend/commands/cluster.c. + +- src/backend/nodes/queryjumblefuncs.c / src/backend/utils/misc/queryjumble.c: + - PG commits 8eba3e3f020843a7641121e778e161b58ec2e490, 2ecbb0a49359759b46dd82df4ac3a083c36b1db4 and others moved `queryjumble.c` to `src/backend/nodes/queryjumblefuncs.c` and restructured `JumbleQuery` (now takes only `Query *`; uses `DoJumble`; queryId is `int64`). + - YB added `yb_compute_backfill_query_id` and a `YbBackfillIndexStmt` short-circuit in `JumbleQuery`. + - Ported YB additions into `src/backend/nodes/queryjumblefuncs.c`: added `YbBackfillIndexStmt` short-circuit to `JumbleQuery` with a `YB_TODO_PG19MERGE`; switched the helper return type from `uint64` to `int64`; removed src/backend/utils/misc/queryjumble.c. + +- contrib/adminpack/Makefile, contrib/adminpack/adminpack.c: + - PG commit cc09e6549f2bd2142b154d7d9802fb7a0abc643e removed the adminpack contrib extension. + - YB added `YBCheckServerAccessIsAllowed` calls to adminpack.c and a `SHLIB_LINK` to the Makefile + - Dropped adminpack + +- contrib/passwordcheck/passwordcheck.c: + - PG modified `passwordcheck.c`. + - YB commit e05a448cfbdff6b88a2d767113a8e885a8f3b591 rewrote `passwordcheck.c`. YB commit 485c4e5732c9a755aa9bc2cc1e83184eb9c89aab later moved that YB content into `passwordcheck_extra.c` and made `passwordcheck.c` a symlink to it. + - Kept YB's symlink; added a `YB_TODO_PG19MERGE` to port PG's changes; removed PG's passwordcheck.c. + +- src/backend/commands/tablecmds.c: + - ATExecAddIndex forward declaration: + - PG added `ATPrepAddPrimaryKey` and `verifyNotNullPKCompatible` declarations + - YB added `List **yb_wqueue` first parameter and changed `Relation rel` to `Relation *rel` (adjacent line conflict). + - Kept PG's new declarations, using YB's extended `ATExecAddIndex` signature. + - ATAddCheckNNConstraint forward declaration: + - PG commit 14e87ffa5c543b5f30ead7413084c25f7735039f changed `ATAddCheckConstraint` to `ATAddCheckNNConstraint` + - YB added `List **yb_wqueue` to `ATExecAddIndexConstraint` (adjacent line conflict). + - Kept PG's changed declaration and YB's additional parameter. + - ATExecDropConstraint forward declaration: + - PG commit 14e87ffa5c543b5f30ead7413084c25f7735039f removed `bool recursing` parameter. + - YB added `List **yb_wqueue`, `AlteredTableInfo *tab`, and changed `Relation rel` to `Relation *yb_mutable_rel`. + - Combined both changes. + - RebuildConstraintComment forward declaration: + - PG changed the signature for `RebuildConstraintComment`. + - YB added YB params to `ATPostAlterTypeParse` (adjacent line conflict). + - Took PG's new declaration; preserved YB params. + - AttachPartitionEnsureIndexes forward declaration: PG added `List **wqueue`, YB added `List **wqueue`; kept PG's parameter. + - end of forward declarations: PG added new declarations, YB added others; kept both. + - DefineRelation partitioned table/temp tablespace check: kept both PG's unlogged partitioned table check and YB's temporary tablespace check. + - DefineRelation, CloneFkReferencing, addFkRecurseReferencing, CloneFkReferenced, ATPrepAlterColumnType, AttachPartitionEnsureIndexes, ATExecAttachPartitionIdx build_attrmap_by_name calls (7 conflicts): kept both PG and YB parameters. + - ATExecChangeOwner fallthrough: pg_fallthrough vs yb_switch_fallthrough: keep pg_fallthrough. + - RangeVarCallbackForDropRelation, RangeVarCallbackOwnsRelation, RangeVarCallbackForAlterRelation, ATSimplePermissions (4 conflicts): + - PG changed `pg_class_ownercheck` to `object_ownercheck`. + - YB added `IsYbDbAdminUser` check. + - Used PG's `object_ownercheck` with YB's `IsYbDbAdminUser` guard. + - ExecuteTruncateGuts signature and call site: kept both PG's `run_as_table_owner` and YB's `yb_is_top_level` parameters and other YB changes at the call site. + - ExecuteTruncateGuts body (3 conflicts): + - PG renamed `relfilenode` to `relfilenumber`. + - YB added unsafe truncate guard, `IsYBRelation` check, and extra `yb_copy_split_options`/`preserved_index_split_options` params. + - Kept both; used PG's renames with YB additions. + - ExecuteTruncateGuts reindex_relation call: PG added a parameter at the start, YB added others; kept both. + - ATPrepChangePersistence: + - PG commit 9f87da1cffda0e07ff3babcfa84abe9e849ccd95 refactored inline persistence-change logic into `ATPrepChangePersistence(tab, rel, toLogged)`. + - YB had inline `IsYugaByteEnabled()` guard making UNLOGGED a no-op with NOTICE. + - Kept PG's refactored call; moved YB's early-return guard into `ATPrepChangePersistence` body. + - ATPostAlterTypeCleanup: + - PG commit 5d06e99a3cfc23bbc217b4d78b8c070ad52f720e added `pass == AT_PASS_SET_EXPRESSION` to the guard. + - YB's old table rewrite added a bypass for this cleanup for YB relations (commit 025f52d50843411b060fa22a6f778dffdbe7da27 added `!IsYBRelation(tab->rel)` and commit 833fc83d8e699544e139b872e8e0be0c9978b8a7 added `yb_enable_alter_table_rewrite` override) + - Kept PG's new comment and guard; appended YB's comment and guard. + - ATExecCmd ATExecDropColumn call: + - PG commit 840ff5f451cd9a391d237fc60894fea7ad82a189 consolidated `AT_DropColumn`/`AT_DropColumnRecurse` into single case using `cmd->recurse`. + - YB added `tab /* yb_tab */` parameter. + - Used PG's consolidated call with YB's extra param. + - ATExecCmd ATExecDropConstraint call: same as above but YB also added `wqueue` and `yb_mutable_rel`. + - ATExecCmd AT_MergePartitions: PG added `AT_MergePartitions`, `AT_SplitPartition`, YB added `AT_YbAlterIndexAttributeType`; kept both cases. + - ATRewriteTable: + - PG added `SO_NONE` arg to `scan` assignment. + - YB added a block right above `scan` assignment. + - Kept YB's block with PG's new call. + - ATPrepAddPrimaryKey: + - PG added `ATPrepAddPrimaryKey` and `verifyNotNullPKCompatible`. + - YB added `YBGetNotNullConstraintAttr` macro. + - Kept both; moved YB's macro further down the file above the function it is used. + - ATExecAddIndex: + - PG added a new arg at the start of `DefineIndex`. + - YB added a comment above the call and changed `rel` to `yb_mutable_rel`. + - Combined both changes. + - ATAddForeignKeyConstraint: generalized AM check pattern + - validateForeignKeyConstraint left join check: + - PG added `!hasperiod`; updated comment. + - YB added `IsYBRelation()` guard; updated comment. + - Combined both. + - validateForeignKeyConstraint AllocSetContextCreate: + - PG added arg to `table_beginscan` call. + - YB added some `IsYBRelation` checks. + - Kept YB's structure and PG's extra arg. + - ATExecDropConstraint definition: + - PG removed the parameter `recursing`. + - YB added some parameters. + - Combined both changes. + - ATExecDropConstraint after variable declarations: + - PG commit 14e87ffa5c543b5f30ead7413084c25f7735039f removed `recursing` check and `ATSimplePermissions` call. + - YB changed arg in `ATSimplePermissions` call. + - Took PG's removal; dropped YB's changes. + - ATExecDropConstraint dropconstraint_internal: + - PG moved code into `dropconstraint_internal`. + - YB changed `rel` to `*yb_mutable_rel` in the moved code. + - Kept PG's `dropconstraint_internal`; changed `rel` to `*yb_mutable_rel` in the call. + - ATExecDropConstraint errors (2 conflicts): kept PG's changes; used `*yb_mutable_rel` in both errors. + - dropconstraint_internal early return for partitioned/inherited constraints: + - PG added `con->contype != CONSTRAINT_NOTNULL` and some other code above. + - YB changed to `*yb_mutable_rel` (this code was previously in `ATExecDropConstraint`). + - Took PG's changes. + - dropconstraint_internal partitioned table ONLY check: + - PG commit 4dea33ce765d65d8807d343ca6535a3d067a63da removed the error for drop of constraints only on partitioned tables; PG also changed `foreach` to `foreach_oid`. + - YB changed `rel` to `yb_mutable_rel`. + - Took PG's changes; dropped YB's changes. Note: dropconstraint_internal currently doesn't have YB added parameters. This will be fixed as part of compilation/test fixes. + - dropconstraint_internal recurse to children: + - PG refactored to `dropconstraint_internal`. + - YB added YB parameters to `ATExecDropConstraint` call. + - Took PG's changes; dropped YB's changes. + - ATExecAlterColumnType RememberAllDependentForRebuilding: + - PG (commit 5d06e99a3cfc23bbc217b4d78b8c070ad52f720e and others) refactored dependency tracking to `RememberAllDependentForRebuilding`; removed `OCLASS_*` enum values. + - YB added `OCLASS_YBTBLGROUP`, `OCLASS_YBPROFILE`, `OCLASS_YBROLE_PROFILE`. + - Took PG's changes; dropped YB's changes (`default` case in `RememberAllDependentForRebuilding` handles the YB cases). + - ATExecAlterColumnType attTup updates: + - PG added more field updates. + - YB added `!yb_clone_table` guard (used for legacy rewrite). + - Kept PG's changes; wrapped the entire block with `!yb_clone_table` guard. + - ATExecAlterColumnType StoreAttrDefault call: + - PG added `(void)` and changed the parameters. + - YB changed `rel` to `*yb_mutable_rel`. + - Kept PG's call with `*yb_mutable_rel`. + - ATExecAttachPartition: + - PG moved some function calls into `attachPartitionTable` helper. + - YB added YB arg (`wqueue`) to `AttachPartitionEnsureIndexes`. + - Took PG's changes; dropped YB's changes (the YB parameter is no longer required as PG also added `wqueue` to `AttachPartitionEnsureIndexes`). + - AttachPartitionEnsureIndexes definition - signature: PG added `List **wqueue`, YB added `List **yb_wqueue`; took PG's changes; dropped YB's. + - AttachPartitionEnsureIndexes DefineIndex: + - PG changed parameters for `generateClonedIndexStmt` and `DefineIndex`. + - YB added a block between the two calls. + - Kept PG's new calls and YB's block with `yb_wqueue` changed to `wqueue`. + - End-of-file definitions (3 conflicts): PG added new functions, YB added others; kept all PG functions first, then YB's. + +- src/include/nodes/nodes.h: + - NodeTag enum replaced with nodetags.h include: + - PG commit 964d01ae90c314eb31132c2e7712d5d9fc237331 replaced the manual `NodeTag` enum with `#include "nodes/nodetags.h"` for auto-generated node tags. + - YB added YB-specific node tags in the manual enum. + - Adopted PG's `nodetags.h` approach; YB node tags will be auto-added to the generated `nodetags.h`. + - OnConflictAction: PG added `ONCONFLICT_SELECT`, YB added `ONCONFLICT_YB_REPLACE`; kept both. + +- src/backend/nodes/copyfuncs.c: + - PG commit 964d01ae90c314eb31132c2e7712d5d9fc237331 auto-generates node copy functions from struct definitions, replacing all manually-written copy functions. + - YB had added copy functions for YB-specific node types and extra fields on PG nodes. + - Adopted PG's auto-generation framework; removed all YB copy functions since the generator reads YB struct definitions from headers. Exceptions: `_copyYbUpdateAffectedEntities` (`YbCopyBitMatrix`, anonymous embedded structs) and `_copyYbBatchedNestLoop` (pointer arrays needing per-element deep copy) kept as custom implementations (with in-lined parent fields as required). `pg_node_attr(custom_copy_equal, custom_read_write)` added to both structs in `plannodes.h`. `YB_TODO_PG19MERGE` added for a more thorough audit. + +- src/backend/nodes/readfuncs.c: + - same as above. exceptions: _readYbUpdateAffectedEntities (palloc/loop logic), _readYbBatchedNestLoop (palloc/loop logic), _readScalarArrayOpExpr. + +- src/backend/nodes/outfuncs.c: + - same as above. exceptions: _outYbUpdateAffectedEntities (loop logic), _outYbBatchedNestLoop (loop logic), _outScalarArrayOpExpr. + +- src/backend/nodes/equalfuncs.c: + - same as above. exceptions: _equalYbUpdateAffectedEntities + +- src/tools/pg_bsd_indent/indent.c: + - 3 break/pg_fallthrough vs yb_switch_fallthrough; accept break/pg_fallthrough + +- src/include/nodes/primnodes.h: + - RowCompareExpr comment: + - PG commit 6339f6468e8217f556e38482626250dc72d7cd00 trimmed the comment. + - YB commit ada31e719db783e6c26b1b0b8c9447174ad7cbc5 added YB note to comment. + - Took PG's trimmed comment; appended YB's note. + - RowCompareExpr struct fields: + - PG commit 5d29d525ffe028fdf6b2d3ff7502243e56c6c79a reformatted struct comments to separate lines; PG commit 6339f6468e8217f556e38482626250dc72d7cd00 changed `RowCompareType rctype` to `CompareType cmptype`. + - YB commit ada31e719db783e6c26b1b0b8c9447174ad7cbc5 changed `List *rargs` to `Node *rargs`. + - Took PG's formatting and `CompareType cmptype`; kept YB's `Node *rargs` type change. + +- src/backend/executor/execGrouping.c: + - includes: + - PG changed `TupleHashTableMatch` signature. + - YB added YB includes above `TupleHashTableMatch` declaration (adjacent line conflict). + - Kept YB includes above PG's new signature. + - BuildTupleHashTable, FindTupleHashEntry, LookupTupleHashEntry hashtable field assignment (4 conflicts): + - PG commit c106ef08071ad611fdf4febb3a8d2da441272a6d changed `tablecxt` to `tuplescxt`; PG commit 0f5738202b812a976e8612c85399b52d16a0abb6 changed `in_hash_funcs` to `in_hash_expr`. + - YB added `yb_keyColExprs`, `yb_in_keycolExprs`, `in_keyColIdx` (adjacent line conflicts). + - Combined both changes. + - FindTupleHashEntry: + - PG commit 0f5738202b812a976e8612c85399b52d16a0abb6 changed parameter `hashfuncs` to `hashexpr`. + - YB added `keyColIdx`. + - Combined both. + - TupleHashTableHash_internal variable declarations: + - PG commit 0f5738202b812a976e8612c85399b52d16a0abb6 replaced `hashfuncs` and `i` declarations with `isnull`. + - YB added `eval_exprs`. + - Combined both changes. + - TupleHashTableHash_internal if (tuple == NULL) block: + - PG commit 0f5738202b812a976e8612c85399b52d16a0abb6 replaced `hashfunctions` and `slot` assignment. + - YB added `keyColIdx` and `eval_exprs` assignment. + - Combined both changes. + - TupleHashTableHash_internal else block: + - PG commit 0f5738202b812a976e8612c85399b52d16a0abb6 completely replaced the for loop with `ExecEvalExpr`. + - YB added YB logic to the old for loop. + - Took PG's changes; added a `YB_TODO_PG19MERGE`. + +- src/backend/executor/nodeModifyTable.c: + - declarations (2 conflicts): + - PG added new declarations `ExecOnConflictLockRow`, `ExecOnConflictSelect`, `ExecForPortionOfLeftovers`. + - YB added YB parameters `yb_oldtuple`, `ybConflictSlot` to `ExecCrossPartitionUpdateForeignKey`, `ExecOnConflictUpdate` respectively. + - Combined both changes. + - ExecInitGenerated: + - PG added `if (cmdtype == CMD_UPDATE)` guard around `ri_extraUpdatedCols` update. + - YB replaced `FirstLowInvalidHeapAttributeNumber` with `YBGetFirstLowInvalidAttributeNumber(rel)`. + - Kept PG's guard; used YB's attribute number function. + - YbExecInsertPrologue definition: + - PG changed `inserted_destrel` to `insert_destrel` in the comment above `ExecInsert`. + - YB commit a120c2588cab65ebe497e0b8c64bd826b54a99e1 split `ExecInsert` into `YbExecInsertPrologue` and `YbExecInsertAct`. + - Kept YB's structure; applied PG's `ExecInsert` comment update. + - YbExecInsertAct ExecCheckIndexConstraints call: + - PG commit 9758174e2e5cd278cf37e0980da76b51890e0011 added an additional `tupleid` parameter to `ExecCheckIndexConstraints` (this call site passes `&invalidItemPtr` as an argument). + - YB commit a120c2588cab65ebe497e0b8c64bd826b54a99e1 moved this logic into `YbExecInsertAct` and replaced the `ExecCheckIndexConstraints` call with `YbExecCheckIndexConstraints`, which calls `ExecCheckIndexConstraints` internally. + - Kept YB's change; added a new `tupleid` parameter to `YbExecCheckIndexConstraints` to pass to `ExecCheckIndexConstraints`. Updated other `YbExecCheckIndexConstraints` call in `ExecUpdateAct` as well. + - YbExecInsertAct speculative insert: + - PG commit bc32a12e0db2df203a9cb2315461578e08568b9c added `INJECTION_POINT("exec-insert-before-insert-speculative")`; PG commit b7271aa1d71acda712a372213633fdb55c1465c1 changed `ExecInsertIndexTuples` signature: reordered params; replaced `bool update` and `bool noDupErr` with a bitmask (`EIIT_IS_UPDATE`, `EIIT_NO_DUPE_ERROR`). + - YB commit a120c2588cab65ebe497e0b8c64bd826b54a99e1 moved PG's speculative insert logic into an else block; added a YB `if (IsYBRelation)` block with `YBCHeapInsert`. + - Kept YB's if/else structure. Ported PG's `INJECTION_POINT` and updated the `ExecInsertIndexTuples` call in the else block. Updated YB's if block `ExecInsertIndexTuples` call as well (used `EIIT_IS_UPDATE | EIIT_NO_DUPE_ERROR` -- mapped from old `update=true, noDupErr=true`). + - YbExecInsertAct resultRelInfo->ri_NumIndices check ExecInsertIndexTuples call: + - PG commit b7271aa1d71acda712a372213633fdb55c1465c1 changed `ExecInsertIndexTuples` signature (same as above). + - YB moved this logic from `ExecInsert` into `YbExecInsertAct`; added an if/else block with separate `ExecInsertIndexTuples` calls (PG logic in else block). + - Kept YB's if/else structure. Updated all `ExecInsertIndexTuples` calls to PG's new signature. + - ExecDeleteAct: + - PG commit db89a47115f0c7e664832f4b41cb03130b8a4fbe added `option` argument to `table_tuple_delete` call. + - YB added YB-specific logic and `resultRelationDesc`. + - Combined both changes. + - ExecUpdateAct: + - PG commit c649fa24a42ba89bf5460c7110e4fc8eeca65959 changed the if condition from `context->relaction != NULL` to `context->mtstate->operation == CMD_MERGE`. + - YB added an `elog(ERROR, ...)` inside the if block (adjacent line conflict). + - Kept PG's condition, YB's error. + - ExecUpdateEpilogue: + - PG commit 19d8e2308bc51ec4ab993ce90077342c915dd116 added `TU_Summarizing` / `EIIT_ONLY_SUMMARIZING` flag logic; PG commit b7271aa1d71acda712a372213633fdb55c1465c1 changed `ExecInsertIndexTuples` to bitmask signature; PG commit 8e72d914c52876525a90b28444453de8085c866f added `ExecForPortionOfLeftovers` call. + - YB commit f0a5db706e85412ec85a83f8286c892094d83688 added if/else split with `YbExecUpdateIndexTuples` for YB relations. + - Kept YB's if/else structure. Ported PG's `TU_None`/`TU_Summarizing` flag logic and bitmask `ExecInsertIndexTuples` signature into the else block. Kept `ExecForPortionOfLeftovers` outside the if/else (applies to both paths). + - ExecOnConflictLockRow / ExecOnConflictUpdate refactoring (2 conflicts): + - PG commit 88327092ff06c48676d2a603420089bf493770f3 extracted the tuple locking logic from `ExecOnConflictUpdate` into a new `ExecOnConflictLockRow` function; `ExecOnConflictUpdate` now calls `ExecOnConflictLockRow`. + - YB commit 11001d370f5ee07284a169e02c6a1581aa8f97ff added `if (IsYBRelation) goto yb_skip_transaction_control_check` locking logic (now in `ExecOnConflictLockRow`); YB commit efd4cb7fea876ed9c13d9ce94ea989ed52aeaf69 added `ybConflictSlot` parameter to `ExecOnConflictUpdate`; YB also added `ybOldTuple`/`ybShouldFree` locals and YB-specific tuple visibility handling. + - Resolutions: + - header mismatch: took PG's `ExecOnConflictLockRow` header; moved conflicting YB `ExecOnConflictUpdate` header to the definition below. + - locking logic: in `ExecOnConflictLockRow`, added YB's `if (IsYBRelation(relation))` skip block before the `table_tuple_lock` call; updated `ExecOnConflictUpdate`: added `ybConflictSlot` parameter, added `ybOldTuple`/`ybShouldFree` locals. + - ExecOnConflictUpdate ExecUpdate call: + - PG commit 80feb727c869cc0b2e12bd1543bafa449be9c8e2 added `existing` as the new argument for the `oldSlot` parameter in `ExecUpdate`. + - YB commit 161efd6fe7dfa52896dd7e5c0c8641082ab4ef1d and a87fee24675c52723ac4dc9495acf344c165761c changed PG arguments `conflictTid`, `NULL` to `ybTid`, `ybOldTuple`. + - Combined both changes. + - ExecMergeMatched: + - PG commit 5f2e179bd31e5f5803005101eb12a8d7bf8db8f3 moved `ExecUpdateAct` into the else block (after the INSTEAD OF trigger check). + - YB added arguments for YB parameters. + - Took PG's structure; added YB arguments to the new location. + - ExecMergeNotMatched: + - PG changed `(void) ExecInsert(...)` to `rslot = ExecInsert(...)`. + - YB added an extra argument to `ExecInsert` (for `blockInsertStmt`). + - Combined both changes. + - ExecGetUpdateNewTuple call in ExecModifyTable: + - YB moved PG logic into an if/else block (right above the conflict marker). + - PG did not make any changes to this line that aren't already present in YB. + - Dropped PG's side of this conflict. + +- src/backend/optimizer/path/allpaths.c: + - declarations: + - PG added parameter child_append_relid_sets to get_singleton_append_subpath and accumulate_append_subpath + - YB commit 2a308fc8b326f792dd1de5c9686b46c54158b32f removed forward declaration for get_singleton_append_subpath and made the function non-static + - Accepted PG's changes to accumulate_append_subpath, and ported changes to get_singleton_append_subpath declaration in paths.h + - make_one_rel: + - PG commit 2489d76c4906f4461a364ca8ad7e0751ead8aa0d removed all_baserels construction from this site. + - YB (commit 3ca3ac8f38efd580704ae55ea364a4bf868f83ff and others) added is_yb_relation / yb_pushable initialization loop + - Dropped the all_baserels loop (moved by PG), keeping only YB's loop. + - set_rel_pathlist: + - PG added set_grouped_rel_pathlist. + - YB added ybTraceCheapestPaths. + - Kept both. + - add_paths_to_append_rel batching check: + - PG commit 7358abcc6076f4b2530d10126ab379f8aea612a5 renamed subpaths_valid to parameterized_valid and refactored subpath storage into a struct. + - YB commit 8ac82f424701adecbf5a2537e7ca95eff4126dae added yb_has_same_batching_reqs(subpaths) check before add_path. + - Kept both: YB's batching check adapted to PG's renamed variable (parameterized_valid) and struct field (parameterized.subpaths). + - get_singleton_append_subpath signature: + - PG added parameter `child_append_relid_sets`. + - YB commit 2a308fc8b326f792dd1de5c9686b46c54158b32f made the function non-static. + - Combined both changes. + - standard_join_search: PG commit 8e11859102f947e6145acdd809e5cdcdfbe90fa5 added an if block, YB also added one at the same location; kept both. + - end-of-file functions: + - PG commit 77db132637661f6e01497959128fb650330552b4 removed debug support functions at the bottom of the file and replaced usages with `pprint()`. + - YB added YB functions and made YB modifications to PG debug support functions. + - Kept YB helpers; discarded PG debug support functions. + +- src/backend/optimizer/path/costsize.c: + - final_cost_nestloop: + - PG commit e22253467942fdb100087787c3e1e3a8620c54b2 removed disabled node cost penalty. + - YB added some checks for BNL. + - Took PG's changes; added a `YB_TODO_PG19MERGE` to revisit. + - compute_semi_anti_join_factors: + - PG commit 6190d828cd25ae20c0a8548765a0e1b880f1f66d removed the block of `norm_sjinfo.* =` and replaced it with `init_dummy_sjinfo`. + - YB commit 50eceddc53178ce31a0c7248c81d32b43cde27c8 renamed `norm_sjinfo` to `temp_sjinfo`. + - Took PG's changes; renamed argument `norm_sjinfo` to `temp_sjinfo` in the `init_dummy_sjinfo` call. + - end-of-file functions: PG added a function, YB added others; kept both. + +- src/backend/optimizer/path/pathkeys.c: + - includes + GUC: PG added `enable_group_by_reordering` GUC variable, YB added includes; kept both. + - make_pathkey_from_sortinfo: + - PG replaced `BTGreaterStrategyNumber`/`BTLessStrategyNumber` with `CompareType` enum (`COMPARE_GT`/`COMPARE_LT`). + - YB added `yb_is_hash` check using `BTEqualStrategyNumber` for hash indexes. + - Kept YB's structure with PG's changes in the else block; changed YB's hash check to PG's `CompareType`: `COMPARE_EQ` for hash. + - get_cheapest_path_for_pathkeys: + - PG commit 9caf042088e7416ed612e52519ee15f0717e86a7 moved `require_parallel_safe` check to before cost comparison. + - YB added `yb_prefer_bnl` / `YB_PATH_NEEDS_BATCHED_RELS` preference check. + - Kept only YB's BNL preference. + - truncate_useless_pathkeys comment: PG updated comment wording, YB added distinct index scan documentation; kept both (PG's updated description + YB's distinct index scan notes). + - truncate_useless_pathkeys body: + - PG commit e3b9e44689051d5bee199e333c748aac83396378 refactored function body. + - YB added `yb_distinct_nkeys` check. + - Took PG's changes; appended YB's `yb_distinct_nkeys` check at the end. + +- src/backend/optimizer/plan/createplan.c: + - create_append_plan: + - PG commit 55a780e9476a753354a6db887e92125c7886ca6d replaced `Sort *sort = make_sort(...)` with `Plan *sort_plan;` + an `if (enable_incremental_sort && presorted_keys > 0)` / else branching on `make_incrementalsort` vs `make_sort`. Variable renamed `sort` -> `sort_plan`. + - YB inserted `yb_assign_unique_plan_node_id(root, (Plan *) sort);` between `make_sort(...)` and `label_sort_with_costsize(...)`. + - Kept PG's if/else; added `yb_assign_unique_plan_node_id(root, sort_plan);` after. + - create_merge_append_plan: same as above. + - create_memoize_plan make_memoize call: + - PG added trailing args `est_calls`, `est_unique_keys`, `est_hit_ratio` to `make_memoize` call. + - YB replaced the `singlerow` arg with `yb_singlerow`. + - Passed `yb_singlerow`; appended PG's three new args. + - create_unique_plan: + - PG commit 24225ad9aafc576295e210026d8ffa9f50d61145 removed the existing `create_unique_plan` definition and renamed `create_upper_unique_plan` to `create_unique_plan` with some changes in the definition. + - YB added `yb_assign_unique_plan_node_id(root, (Plan *) sort);` between `make_sort_from_sortclauses` and `label_sort_with_costsize` in the `UNIQUE_PATH_SORT` branch. + - Added a `YB_TODO_PG19MERGE` to port the lost `yb_assign_unique_plan_node_id` if needed. + - create_nestloop_plan make_nestloop / YbBatchedNestLoop: + - PG commit a16ef313f2c21225e89ddb9168f30601f21c7d07 and others added an arg to `identify_current_nestloop_params`, plus a foreach loop before `make_nestloop`. + - YB commit 49ed1065dabd2a9456b830c69b4bb8d584491ff8 wrapped the `make_nestloop(...)` call in `if (yb_is_batched) { make_YbBatchedNestLoop(...); prepare_sort_from_pathkeys(...); } else { make_nestloop(...); }`. + - Took PG's new arg + loop; placed YB's if/else around the final `make_*nestloop` call. + - create_mergejoin_plan: + - PG commit 55a780e9476a753354a6db887e92125c7886ca6d replaced `Sort *sort = make_sort_from_pathkeys(...)` with `Plan *sort_plan;` + Assert + if/else on `make_incrementalsort_from_pathkeys` vs `make_sort_from_pathkeys`. + - YB inserted `yb_assign_unique_plan_node_id(root, (Plan *) sort);` between `make_sort_from_pathkeys(...)` and `label_sort_with_costsize(...)`. + - Same resolution as Append/MergeAppend: added `yb_assign_unique_plan_node_id(root, sort_plan);` after if/else. + - replace_nestloop_params_mutator: + - PG reformatted the `expression_tree_mutator` call and removed the `void` cast. + - YB added two special-case branches. + - Kept both YB branches; used PG's call. + +- src/include/utils/guc.h: + - GucSource enum trailing entry: + - YB added `YSQL_CONN_MGR` (SET SESSION PARAMETER packet) as the last enum value. + - Kept YB's added entry. + - log_min_messages declaration: + - PG commit 38e0190ced714b33c43c9676d768cc6814fc662a changed `int log_min_messages` to `int log_min_messages[]` (per-process-type array). + - YB added `int yb_log_min_backtraces;`. + - Combined: PG's array form + YB's new var. + +- src/include/utils/memutils.h: + - GetMemoryChunkContext + MemoryContextCreate decls: + - PG commit c6e0fe1f2a08505544c410f613839664eea9eb21 moved both; `GetMemoryChunkContext` is now declared `extern` higher in `memutils.h` (with the body in `mcxt.c`). + - YB added a YB null-pointer `YBC_LOG_ERROR_STACK_TRACE` check. + - Took PG's changes; moved YB's block to the new location in `mcxt.c`. + - end-of-file functions: kept both PG's and YB's additions. + +- src/include/utils/rel.h: + - ForeignKeyCacheInfo struct: + - PG commit eec0040c4bcd650993bb058ebdf61ab94171fda4 and others changed formatting and added a new `bool conenforced;` field. + - YB added `Oid ybconindid;` (oid of index supporting the FK constraint). + - Kept PG's structure; inserted `Oid ybconindid;` after `conenforced` with a `/* YB: */` comment. + - StdRdOptions struct: + - PG commit 4d6a66f675815a5d40a650d4dcfb5ddb89c6ad2f changed `bool vacuum_truncate` to `pg_ternary vacuum_truncate`; PG commit 052026c9b903380b428a4c9ba2ec90726db81288 added `double vacuum_max_eager_freeze_failure_rate;`. + - YB added 5 fields: `bool colocated; Oid tablegroup_oid; Oid colocation_id; Oid table_oid; Oid row_type_oid;`. + - Kept PG's new `pg_ternary` type + new field; appended YB's 5 fields. + +- src/backend/optimizer/util/plancat.c: + - index AM info copy block: + - PG commit 3c569049b7b502bb4952483d19ce622ff0af5fd6 wrapped the `rd_indam` field copies in a new `if (indexRelation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)` guard (PG also added an `else` branch that NULLs the AM fields for partitioned indexes). + - YB added 3 fields here: `info->yb_amhasgetbitmap = (amroutine->yb_amgetbitmap != NULL);`, `info->yb_amiscopartitioned = amroutine->yb_amiscopartitioned;`, `info->yb_cached_ybctid_size = 0;`. + - Took PG's guarded structure; ported YB's 3 fields into the new if/else blocks. + - index sort-info loop: + - PG commit 3c569049b7b502bb4952483d19ce622ff0af5fd6 restructured code (moved the for loop into a different location). + - YB added a `if (IsYBRelation(relation)` block within the for loop. + - Kept PG's new structure; wrapped YB's hash-column branching around the `reverse_sort`/`nulls_first` assignments. + - index size-estimation guard: + - PG wrapped the size-estimation block in `if (indexRelation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)` (with an `else`). + - YB added `!IsYBRelation(indexRelation)` to a smaller block. + - Took PG's new structure; moved YB's condition to the new location. + +- src/backend/replication/pgoutput/pgoutput.c: + - module magic + YB include: + - PG replaced `PG_MODULE_MAGIC;` with `PG_MODULE_MAGIC_EXT` and dropped the `extern` declaration for `_PG_output_plugin_init`. + - YB added includes. + - Kept PG's changes; kept YB's includes. + - pgoutput_change: + - PG commit da324d6cd45bbbcc1682cc2fcbc4f575281916af refactored this function. + - YB added YB-specific logic. + - Took PG's changes; passed NULL for YB params in `logicalrep_write_update` for now; added a `YB_TODO_PG19MERGE`. + - end-of-file functions: + - PG removed `update_replication_progress`. + - YB added `yb_support_yb_specific_replica_identity(bool)`. + - Kept YB's function. + +- src/backend/utils/activity/backend_status.c: + - top-of-file static decls: PG added some declarations, YB added `static char *DatabaseNameBuffer = NULL;`; kept both. + - BackendStatusShmemRequest body: + - PG commit 9b5acad3f40fa6015f367fbf887ae5c1a93a3698 refactored from a returns-`Size` function (manual `add_size`/`mul_size` accounting + return) into a per-callback set of `ShmemRequestStruct(...)` calls. + - YB appended its own `size = add_size(size, mul_size(NAMEDATALEN, NumBackendStatSlots));` (database-name buffer) and `size = add_size(size, sizeof(uint64_t));` (`yb_new_conn` metric) lines. + - Took PG's new structure; added a `YB_TODO_PG19MERGE` for YB additions. + - pgstat_bestart initial-population: + - PG commit c76db55c9085d0b7984ea337576e45a7d1268b97 split `bestart` - at this site `st_databaseid = InvalidOid; st_userid = InvalidOid;`, with a separate post-database phase populating them after authentication. + - YB added `yb_new_conn` counter increment, a `YBIsEnabledInPostgresEnvVar()` block looking up the database name into `st_databasename`, and an extended userid block accepting YB-specific backend types (`YB_AUTO_ANALYZE_BACKEND`, `YB_YSQL_CONN_MGR`, `YB_YSQL_CONN_MGR_WAL_SENDER`). + - Kept PG's changes; kept YB's `yb_new_conn` increment at this location; moved the other two blocks into the new location in `pgstat_bestart_final`. + +- src/backend/utils/mmgr/generation.c: + - GenerationAllocLarge entry: + - PG (commit a0cd95448067273a5cf92ad578a1e2de3b62aa2f and others) split the over-sized chunk path into its own `GenerationAllocLarge` function and added `MemoryContextCheckSize(context, size, flags);` at the top. + - YB added `YbPgMemAddConsumption(blksize);` after the `block = malloc(blksize)` allocation in the inline (pre-split) version. + - Took PG's new entry; inserted `YbPgMemAddConsumption(blksize);` immediately after PG's malloc inside `GenerationAllocLarge`. + - GenerationAllocChunkFromBlock body: + - PG commit a0cd95448067273a5cf92ad578a1e2de3b62aa2f moved the "allocate a new block if needed" logic out of the inline `GenerationAlloc` (now lives in `GenerationAllocFromNewBlock`). + - YB had previously added `YbPgMemAddConsumption(blksize);` inside that old logic block. + - Took PG's removal; added `YbPgMemAddConsumption(blksize);` after PG's malloc in `GenerationAllocFromNewBlock`. + - GenerationReset empty-block free path: + - PG simplified to `else GenerationBlockFree(set, block);`. + - YB added `YbPgMemSubConsumption(freed_sz);`. + - Took PG's call form (the existing `GenerationBlockFree` body already includes `YbPgMemSubConsumption(freed_sz);`). + - Added a `YB_TODO_PG19MERGE` for this file in case this needs to be looked at more thoroughly. + +- src/backend/utils/mmgr/mcxt.c: + - top-of-file infrastructure block: + - PG added new macros and helpers. + - YB added `#define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE 1024`. + - Kept PG's full block; appended YB's `#define` after it. + - MemoryContextReset body: + - PG removed trailing `VALGRIND_DESTROY_MEMPOOL` / `VALGRIND_CREATE_MEMPOOL`. + - YB commit ff80cfddee1f6a44b3916a95281a47b84ad5cb5f added a newline before these calls. + - Took PG's deletion. + - MemoryContextStatsDetail tail + MemoryContextStatsUsage: + - PG commit 4c1973fcaecd9ef11de14ac55d3ec1432f6b82dc added a closing brace. + - YB added `if (IsYugaByteEnabled())` block printing `YbcTcmallocStats` to this function; YB also added `MemoryContextStatsUsage`. + - Combined both changes. + +- src/bin/pg_dump/dumputils.c: + - REVOKE/GRANT/GRANT-WITH-GRANT-OPTION formatting blocks (3 conflicts): + - PG added `if (name && *name)` block. + - YB changed to `yb_sql` buffer. + - Kept PG's changes; used YB's `yb_sql`. + +- src/bin/pg_upgrade/dump.c: + - generate_old_dump globals call: + - PG added new args. + - YB renamed the binary to `ysql_dumpall`. + - Kept PG's changes; used YB binary name `ysql_dumpall`. + - generate_old_dump per-db call: + - PG added new args. + - YB renamed the binary to `ysql_dump`. + - Kept PG's changes; used YB binary name `ysql_dump`. + - statistics flag: + - PG commit 6a46089e458f2d700dd3b8c3f6fc782de933529a renamed `--with-statistics` to `--statistics`. + - YB commit 1d0241f523c27a57af0105d898f7327f5cd71adc imported PG commits to preserve statistics during upgrade. + - Kept PG's rename. + +- src/bin/pg_upgrade/option.c: + - getopt_long shortopts string: + - PG's string was `b:B:cd:D:j:kNo:O:p:P:rs:U:v`. + - YB added YB-specific options `h:`, `H:`, `S:`, `w:`. + - Merged into `b:B:cd:D:h:H:j:kNo:O:p:P:rs:S:U:vw:`; updated YB comment. + - usage() printf block: + - PG added `--set-char-signedness=OPTION`, `--swap`, `--sync-method=METHOD` lines. + - YB added `-h/--old-host`, `-H/--new-host`, `-s/--old-socketdir`, `-S/--new-socketdir`, `-w/--yb-working-dir` lines. + - Kept both blocks (PG's first, YB's after). + - get_sock_dir entry: + - PG changed the platform guard from `#if defined(HAVE_UNIX_SOCKETS) && !defined(WIN32)` to `#if !defined(WIN32)` and changed `if (!live_check)` to `if (!user_opts.live_check || cluster == &new_cluster)`. + - YB added an assert. + - Kept YB's assert above PG's new #if. + +- src/backend/optimizer/plan/planner.c: + - standard_planner PlannerGlobal init block: + - PG added `glob->partition_directory = NULL` and `glob->rel_notnullatts_hash = NULL`. + - YB added hint/plan an `IsYugaByteEnabled()` block that sets up hint alias mappings for target relations. + - Kept both: PG fields first, then YB fields and block. + - subquery_planner PlannerInfo init (2 conflicts): + - PG added `root->assumeReplanning = false` and `root->join_domains = list_make1(makeNode(JoinDomain))` and removed + `root->partColsUpdated` (commit 812221b204276b884d2b14ef56aabd9e1946be81) + - YB added batched NL fields + - Kept both: PG's `assumeReplanning` and `join_domains`, YB's batched relids and hint fields all preserved. + - create_distinct_paths sorted-path loop: + - PG commits 7ab80ac1caf9f48064190802e1068ef89e2883c4, 24225ad9aafc576295e210026d8ffa9f50d61145, 3c6fc58209f24b959ee18f5d19ef96403d08f15c refactored code. + - YB commits aa2c9b4da0847306f2af774e1f99c496c2b1b0f6 and c4b2c39c98f0fca8f6e4c5a0fa0f5d9e9f724c95 added a `yb_distinct_paths` skip and an `UpperUniquePath` unwrap, plus a separate explicit-sort block after the loop for the cheapest path. + - Took PG's changes; added YB_TODO_PG19MERGE. + - create_final_distinct_paths allow_hash && grouping_is_hashable check: + - PG changed `parse->distinctClause` to `root->processed_distinctClause`. + - YB added to the condition. + - Used PG's `root->processed_distinctClause`; kept YB's `yb_distinct_paths` guard. + - plan_cluster_use_sort and plan_create_index_workers inits (2 conflicts): + - PG added `root->join_domains = list_make1(makeNode(JoinDomain))`. + - YB added hint fields (`ybBlockId`, `ybHintedJoinsOuter/Inner`, `ybProhibitedJoinTypes/Joins`). + - Kept both in each location. + - End-of-file function additions (2 conflicts): + - Kept both PG and YB functions. + +- src/backend/regex/regc_pg_locale.c: + - file body: + - PG commit 5a38104b364234615c780656a8b2424f96ed9efa restructured locale handling: removed PG_Locale_Strategy enum, removed pg_regex_strategy and pg_regex_collation static vars, moved pg_char_properties table and PG_IS* macros into utils/pg_locale_c.h, and rewrote regc_wc_is*/regc_wc_to* to a simple `if (pg_regex_locale->ctype_is_c)` branch. + - YB added `#include "pg_yb_utils.h"`, marked the three static vars YB_THREAD_LOCAL (commit 0e51069b56acc2768879c4408334801af99a422c), and added `yb_switch_fallthrough()` calls inside the per-strategy switches. + - Took PG's rewritten bodies, dropping yb_switch_fallthrough (no switches anymore), keeping YB include and `YB_THREAD_LOCAL` on the surviving `pg_regex_locale` declaration. + +- src/backend/optimizer/util/inherit.c: + - expand_partitioned_rtentry: + - PG dropped the local `live_parts` variable (uses `relinfo->live_parts` directly). + - YB added a 2nd `partdesc->oids` arg to `prune_append_rel_partitions`. + - Kept PG's structure with YB's extra arg. + - expand_single_inheritance_child: + - PG commits a61b1f74823c9c4f79c95226a461f1e7a367764b and 3f7836ff651ad710fef52fa87b248ecdfc6468dc removed the entire selectedCols/insertedCols/updatedCols translation block: those fields moved from RangeTblEntry to RTEPermissionInfo and child RTEs get `perminfoindex = 0` (no perm checks on children). + - YB added parameter `yb_min_attr` to translate_col_privs call. + - Took PG's removal. + - translate_col_privs_multilevel return: + - PG added code before the return. + - YB added a 3rd `yb_min_attr` arg (using `YBGetFirstLowInvalidAttributeNumberFromOid(appinfo->parent_reloid)`). + - Kept PG's code; kept YB's augmented return. + - apply_child_basequals local var name and make_restrictinfo call (2 conflicts): + - PG added the local variable `childrinfo` to construct the RestrictInfo, perform some checks and then append it to `childquals` (previously PG directly appended to `childquals`) + - YB also independently created a local variable `childri` to construct the RestrictInfo and added a `childrel->is_yb_relation` block evaluates `yb_pushable`. + - Took PG's name and signature; appended YB's is_yb_relation block. + - expand_partitioned_rtentry recursive translate_col_privs call (outside of conflict markers): + - PG added a new translate_col_privs call here for `child_updatedCols` (2-arg). + - YB changed translate_col_privs to require a 3rd `yb_min_attr` arg. + - Passed `YBGetFirstLowInvalidAttributeNumber(parentrel)` as the 3rd arg. + +- src/backend/replication/slot.c: + - ReplicationSlotCreate signature: + - PG added parameters + - YB added others + - Combined both changes. + - ReplicationSlotCleanup body: + - PG commit 67c20979ce72b8c236622e5603f9775968ff501c added local variables `found_valid_logicalslot` and `dropped_logical` to `ReplicationSlotCleanup` and changed the function's code. + - YB refactored ReplicationSlotCleanup into a wrapper plus a helper `ReplicationSlotCleanupForProc` + - Kept YB's structure; moved PG's new variables into `ReplicationSlotCleanupForProc`. + - ReplicationSlotCleanupForProc loop predicate: + - PG renamed `slot->active_pid` to and uses `MyProcNumber`, also added code above the condition. + - YB compares `s->active_pid == proc->pid` since the helper takes a PGPROC* (commit 70fbb382e1a913c651a6dbc85a75bcf2e25d32a8) + - Kept PG's changes; kept YB's `proc->pid`. + - ReplicationSlotDrop body: + - PG added an if block below `ReplicationSlotAcquire` plus changed the function to take a third bool argument. + - YB added a IsYugaByteEnabled() block before ReplicationSlotAcquire + - Ordered YB's early-return block first, then PG's `ReplicationSlotAcquire(name, nowait, false)` followed by the if block. + +- src/backend/optimizer/util/pathnode.c: + - compare_path_costs / compare_path_costs_fuzzily disabled-nodes priority (2 conflicts): + - PG added a `disabled_nodes` priority check at the top that trumps cost. + - YB added a hint priority block (ybHasHintedUid / ybIsHinted) at the same location. + - Kept both: PG's disabled_nodes check first, then YB's hint priority block. + - add_path: + - PG commit e22253467942fdb100087787c3e1e3a8620c54b2 added disabled_nodes to the if condition + - YB added a `ybNewPathCostsMore` boolean with branching. + - Kept YB's structure and PG's disabled_nodes-or-cost test in the final else if. + - add_partial_path: + - PG updated a comment. + - YB added a `yb_enable_planner_trace` debug ereport block. + - Kept YB's block; took PG's updated comment. + - create_append_path: + - PG broadened the condition to `rel->reloptkind == RELOPT_BASEREL && root && input.subpaths != NIL` and renamed `subpaths` -> `input.subpaths` + - YB added a block for `yb_cur_batched_relids` (adjacent line conflict). + - Took PG's broader gate; nested YB's batching block under an inner `IS_PARTITIONED_REL(rel)` check, using `input.subpaths`. + - create_unique_path (semijoin form, before create_gather_merge_path): + - PG commit 24225ad9aafc576295e210026d8ffa9f50d61145 removed the 4-arg `create_unique_path(root, rel, subpath, sjinfo)` semijoin function and simultaneously renamed `create_upper_unique_path` -> `create_unique_path` (DISTINCT form). Semijoin uniquification now happens via `create_unique_paths` (plural) in planner.c, which builds UniquePaths through the renamed `create_unique_path` and `create_agg_path` constructors. + - YB added `yb_assign_unique_path_node_id` and `yb_propagate_fields` calls inside the old semijoin function. + - Resolved with a YB_TODO_PG19MERGE: kept the old YB-side semijoin function commented out above the renamed `create_unique_path` (DISTINCT form), to be reviewed before deleting. The two YB hooks (`yb_assign_unique_path_node_id`, `yb_propagate_fields`) are already invoked by the renamed `create_unique_path` and by `create_agg_path`, both of which `create_unique_paths` (planner.c) calls into, so the hooks may be redundant; confirm before removing. + - create_nestloop_path: + - PG declared `Relids outerrelids` and changed usages of `outer_path->parent->relids` to `outerrelids` + - YB added `inner_req_batched`/`outer_req_unbatched` locals and an `is_batched` early-skip guarding the bms_overlap test, but used `outer_path->parent->relids`. + - Kept PG's `outerrelids`; used it inside YB's batching test. + - create_agg_path (2 whitespace conflicts): + - Kept YB calls. + - create_setop_path (2 conflicts): + - PG commit 27627929528e24a547d1058a5444b35491057a56 changed `create_setop_path` to take separate `leftpath` and `rightpath` instead of a single `subpath`, and stores them as `pathnode->leftpath`/`rightpath`. + - YB added `yb_assign_unique_path_node_id` call and `yb_propagate_fields` call using subpath. + - Kept PG's changes, and YB's `yb_assign_unique_path_node_id`, switched YB propagation to `yb_propagate_fields2(parent, &leftpath->yb_path_info, &rightpath->yb_path_info)`. + +- src/backend/replication/slotfuncs.c: + - create_physical_replication_slot ReplicationSlotCreate call: + - PG added three trailing bool args (repack, failover, synced). + - YB added five trailing args (yb_plugin_name, yb_snapshot_action, yb_consistent_snapshot_time, lsn_type, yb_ordering_mode). + - Combined: PG bools first, then YB args. + - create_logical_replication_slot body (2 conflicts): + - PG added the three new args to ReplicationSlotCreate, added EnsureLogicalDecodingEnabled() and an Assert before CreateInitDecodingContext, and added a `not repack` arg to CreateInitDecodingContext. + - YB added five extra args to ReplicationSlotCreate and gated the CreateInitDecodingContext call behind `if (!IsYugaByteEnabled())`, added a YB comment. + - Kept YB's structure, collapsed both into one ReplicationSlotCreate with PG and YB args combined, kept EnsureLogicalDecodingEnabled and then put PG's CreateInitDecodingContext (with `not repack`) above the existing YB `if (!IsYugaByteEnabled())` block. + - pg_create_logical_replication_slot arg parsing: + - PG added `bool failover = PG_GETARG_BOOL(4)`. + - YB added two PG_ARGISNULL/PG_GETARG_NAME pairs at index 4 and 5 for yb_lsn_type and yb_ordering_mode. + - Kept PG_GETARG_BOOL(4) for failover; shifted YB's args to indices 5 and 6. + - pg_get_replication_slots PG_GET_REPLICATION_SLOTS_COLS: + - PG bumped the count to 21 (added failover, synced, slotsync_skip_reason, etc.). + - YB added a separate YB_PG_GET_REPLICATION_SLOTS_COLS = 4 (adjacent line conflict). + - Kept PG's 21 plus YB's 4-column macro for the trailing yb-specific fields. + - pg_get_replication_slots loop bound: + - PG changed to `max_replication_slots + max_repack_replication_slots`. + - YB iterated over `yb_totalslots` (yb_numreplicationslots when IsYugaByteEnabled, max_replication_slots otherwise). + - Updated `yb_totalslots` assignment to `max_replication_slots + max_repack_replication_slots` when `!IsYugabyteEnabled()`. + - pg_get_replication_slots if/else blocks: + - PG switched from `active_pid != 0` to `active_proc != INVALID_PROC_NUMBER`. + - YB branched on IsYugaByteEnabled to emit yb_stream_active. + - Kept YB's branch; used PG's active_proc/INVALID_PROC_NUMBER spelling in the non-YB block. + - WALAVAIL switch in pg_get_replication_slots: + - PG commit 15f8203a5975d6b9b78e2c64e213ed964b50c044 and others restructured the invalidated check to `slot_contents.data.invalidated != RS_INVAL_NONE` and switched the WALAVAIL_REMOVED branch to use `slot->active_proc` / INVALID_PROC_NUMBER. + - YB wrapped the original switch in `if (IsYugaByteEnabled()) { yb-branch } else { pg15-switch }`. + - Kept YB's structure; moved PG's changes to the else block. + +- src/backend/replication/walsender.c: + - parseCreateReplSlotOptions signature + locals (2 conflicts): + - PG added a `bool *failover` out-param (and matching `failover_given` local). + - YB added two out-params `YbCRSLsnType *lsn_type, YbCRSOrderingMode *ordering_mode` (and matching `lsn_type_given`, `ordering_mode_given` locals). + - Combined: PG failover first, then YB lsn_type/ordering_mode. + - parseCreateReplSlotOptions: + - PG added an `else if (defname == "failover")` block. + - YB added two `else if (defname == "lsn_type" / "ordering_mode")` blocks. + - Kept all three else-if blocks back-to-back. + - CreateReplicationSlot parseCreateReplSlotOptions call: + - PG passed `&failover`. + - YB passed `&lsn_type, &yb_ordering_mode`. + - Passed all three. + - CreateReplicationSlot ReplicationSlotCreate call for physical slot: + - PG added `false, false, false` (repack, failover, synced). + - YB added `cmd->plugin, snapshot_action, NULL, lsn_type, yb_ordering_mode` and moved call to !IsYugabyteEnabled block. + - Combined. + - CreateReplicationSlot ReplicationSlotCreate call for logical slot: + - PG added `false, failover, false` (repack, failover, synced). PG commit e83aa9f92fdd88c2912cc43a61fd9f59f4c8f4d3 simplified the branching structure -- combined the else block (for logical slots) and the if (logical slot) block below. + - YB added the same five YB args as before; wrapped the slot-creation in `if (!IsYugaByteEnabled())` guard. + - Kept PG's ReplicationSlotCreate call with YB's args and inside the `if (!IsYugaByteEnabled())` guard. + - CreateReplicationSlot CreateInitDecodingContext: + - PG added EnsureLogicalDecodingEnabled + Assert and a `false /* not repack */` arg to CreateInitDecodingContext. + - YB wrapped the entire snapshot-build path in `if (IsYugaByteEnabled()) { yb path } else { pg path }`. + - Kept PG's EnsureLogicalDecodingEnabled + Assert; kept YB's if/else block with PG's changes to `CreateInitDecodingContext` inside the else block. + - CreateReplicationSlot xloc snprintf: + - PG commit e83aa9f92fdd88c2912cc43a61fd9f59f4c8f4d3 and others restructured code (removed ReplicationSlotMarkDirty and surrounding code) and bumped the format to `%X/%08X`. + - YB added an IsYugaByteEnabled() branch returning `0/2` and kept the PG `%X/%X` form for non-YB. + - Took PG's removal; kept YB's branch using PG's `%X/%08X` in the non-YB arm. + - XLogSendLogical flushPtr update: + - PG commit 0fdab27ad68a059a1663fa5ce48d76333f1bd74c and others replaced the `flushPtr == InvalidXLogRecPtr` re-init with a `if (!XLogRecPtrIsValid(flushPtr) || ...)` block that uses GetXLogReplayRecPtr for am_cascading_walsender. + - YB added a YBCGetFlushRecPtr() branch. + - Kept PG's changes; added an early `if (IsYugaByteEnabled()) flushPtr = YBCGetFlushRecPtr();` before PG's am_cascading_walsender check. + - WalSndWakeup tail: + - PG commit bc971f4025c378ce500d86597c34b0ef996d4d8c replaced the entire function body + - YB added an `if (YBIsEnabledInPostgresEnvVar()) return;` early return + - Took PG's changes; kept YB's early return. + +- src/backend/replication/logical/logical.c: + - CheckLogicalDecodingRequirements: + - PG commit 67c20979ce72b8c236622e5603f9775968ff501c removed the `wal_level < WAL_LEVEL_LOGICAL` check. + - YB wrapped the check in `if (!IsYugaByteEnabled())`. + - Took PG's removal. + - StartupDecodingContext callbacks (update_progress_txn vs yb_schema_change): + - PG added `ctx->reorder->update_progress_txn` + - YB added `ctx->reorder->yb_schema_change`. + - Kept both assignments. + - StartupDecodingContext in_create + yb_needs_relcache_invalidation block: + - PG15 backpatch commit aee8c2b95492e1b0a228184cd9147b1c0749d673 added the `LogicalDecodingContext.in_create` field and `ctx->in_create = in_create;` as a backport fix; PG master fixed the same bug via bb19b70081e2248f242cd00227abff5b1e105eb6 without that field. YB inherited the field through PG15-stable, not as a YB authored change. + - YB also added a `yb_needs_relcache_invalidation` HASHCTL init under `if (IsYugaByteEnabled())`. + - Dropped `ctx->in_create = in_create;` (field doesn't exist in PG19); kept the YB hash-table init. + - cb_wrapper functions (update_progress_txn_cb_wrapper vs yb_schema_change_cb_wrapper) (2 conflicts): + - PG added a new update_progress_txn_cb_wrapper. + - YB added a yb_schema_change_cb_wrapper at the same location. + - Kept both functions back-to-back. + - LogicalConfirmReceivedLocation candidate-lsn predicate: + - PG commit a2b02293bc65dbb2401cb19c724f52c6ee0f2faf switched the predicate from `!= InvalidXLogRecPtr` to the `XLogRecPtrIsValid()` macro. + - YB gated the entire block under `!IsYugaByteEnabled()` + - Kept YB's `!IsYugaByteEnabled()` gate; used PG's `XLogRecPtrIsValid()` for the inner predicate. + - LogicalConfirmReceivedLocation else-branch confirmed_flush update: + - PG commit ad5eaf390c58294e2e4c1509aa87bf13261a5d15 added `if (lsn > MyReplicationSlot->data.confirmed_flush) confirmed_flush = lsn;` to prevent moving confirmed_flush backwards. + - YB added a restart_lsn update (adjacent line conflict). + - Kept PG's changes; appended YB's. + +- src/backend/parser/gram.y: + - %union new types: + - PG added `ReturningClause *retclause; ReturningOptionKind retoptionkind;`. + - YB added `YbOptSplit *splitopt; char *grpopt; YbRowBounds *rowbounds;`. + - Kept both blocks. + - %type sort/index_params line: + - PG only reformatted. + - YB renamed `index_params` -> `yb_index_params` (YB's nonterminal supports HASH key declarations). + - Took PG's two-line split with YB's `yb_index_params` rename. + - %type block addition: + - PG added a block of new %type declarations. + - YB added its own /* YB types */ block. + - Kept both. + - precedence block (UNBOUNDED/IDENT row): + - PG added to the IDENT-precedence row and to the Op precedence line. + - YB added `_YB_HASH_P`, `NO_OPCLASS`, `EXPR_LIST` + - Kept PG's IDENT-row and Op-row additions, with YB's precedence declarations between them. + - toplevel_stmt list / ClusterStmt: + - PG commit c0b53ec06309f955455c7d71da277991d0da4ec0 renamed `ClusterStmt` to `RepackStmt`. + - YB added `{ parser_ybc_not_support(...) }`. + - Added `parser_ybc_not_support` to `RepackStmt`. + - UNIQUE ConstraintElem: + - PG added a NULL arg to `processCASbits`. + - YB added a loop below for populating yb_index_params. + - Kept PG's signature and YB's yb_index_params loop. + - PRIMARY KEY ConstraintElem (2 conflicts): + - PG changed the rule head to `'(' columnList opt_without_overlaps ')'` and added `n->without_overlaps = $5;` plus the 9th NULL arg to processCASbits. + - YB changed the rule head to `'(' yb_index_params ')'`, iterates yb_index_params to populate `n->keys`, errors on TABLESPACE on PK, and stores `n->yb_index_params = $4`. + - Kept YB's `yb_index_params` followed by `opt_without_overlaps`. Kept YB's body (positions shifted +1 from $5 onward), added PG's `n->without_overlaps = $5;` and 9th NULL arg to processCASbits. + - DomainConstraint / opt_no_inherit: + - PG added `DomainConstraint` and `DomainConstraintElem` + - YB wrapped opt_no_inherit's NO INHERIT branch with `parser_ybc_beta_feature("inheritance")`. + - Kept PG's new productions and YB's beta-feature wrapper. + - object_type_any_name DROP COLLATION/CONVERSION: + - PG added `| PROPERTY GRAPH { OBJECT_PROPGRAPH }`. + - YB added `parser_ybc_not_support` checks to the COLLATION and CONVERSION_P branches. + - Kept PG's PROPGRAPH branch and YB's wrapped COLLATION/CONVERSION branches. + - FetchStmt: + - PG commit bee23ea4ddc46198c95a4e73a83f453c09e04bf8 added `n->location` and `n->direction_keyword` fields to every FetchStmt production. + - YB added `parser_ybc_signal_unsupported(...)` calls to PRIOR/FIRST_P/LAST_P/ABSOLUTE_P/RELATIVE_P productions. + - Kept PG's changes; added the YB ybc_signal_unsupported calls into PRIOR/FIRST_P/LAST_P/ABSOLUTE_P/RELATIVE_P. + - IndexStmt rule head: + - PG commit 7d158e8cb44b602ab76a3660b9f5f5c5c5992a1f changed `opt_index_name` to generalized `opt_single_name`. + - YB added `yb_opt_concurrently_index`, `yb_index_params`, `YbOptSplit` + - Adopted PG's `opt_single_name` rename inside YB's structure. + - IndexStmt opt_concurrently / yb_opt_concurrently_index / opt_index_name: + - PG commit 7d158e8cb44b602ab76a3660b9f5f5c5c5992a1f removed the `opt_concurrently:` and `opt_index_name:` definitions (centralized as shared helpers earlier; `opt_index_name` was changed to `opt_single_name`). + - YB added `yb_opt_concurrently_index` + - Kept `yb_opt_concurrently_index` + - index_elem_options (rule head): + - PG commit 7d158e8cb44b602ab76a3660b9f5f5c5c5992a1f changed `opt_class` to `opt_qualified_name` + - YB added `opt_yb_index_sort_order`, `yb_opt_alias` + - Kept YB's form with the old `opt_class`; added YB_TODO_PG19MERGE (see opt_class definition resolution below). + - index_elem `'(' a_expr ')' index_elem_options` body: + - PG commit 62299bbd90d69e2273d3e2ba35af5953d20ca037 added `$$->location = @1;`. + - YB unwrapped ColumnRef into `$$->name` and only set `$$->expr` for non-ColumnRef nodes and added other YB rules below. + - Took YB's unwrap logic and rules; added PG's `$$->location = @1;`. + - opt_class definition: + - PG commit 7d158e8cb44b602ab76a3660b9f5f5c5c5992a1f removed `opt_class` (replaced by `opt_qualified_name`). + - YB commit 1553aeb762b7c122d886111d123ecbbcebc5c70f added `%prec NO_OPCLASS` on the empty production to break a shift/reduce conflict. + - Kept YB's `opt_class:` definition with a YB_TODO_PG19MERGE. Also kept the matching `%type opt_class` declaration so bison can type-check `$$` in the production and `$2` in index_elem_options. + - ReindexStmt productions (2 conflicts): + - PG commits 83011ce7d7f42b744a93d2b0819597d0aa94e9cc and 1dfe3ef3f960d6924eb1f18facf4fbdae6e1cc1d reworked the REINDEX grammar: added `opt_utility_option_list` to all production heads, started passing options through (`n->params = $2;` instead of `NIL`), and removed the three explicit `REINDEX '(' utility_option_list ')' ...` forms. + - YB added `parser_ybc_not_support(@1, "REINDEX CONCURRENTLY")` + "Only support INDEX target" check to the index-target production, and `parser_ybc_not_support(@1, "REINDEX SCHEMA/DATABASE/SYSTEM")` to the multi-table production. + - Took PG's reworked grammar everywhere; ported YB's not_support + INDEX-only check into the new productions with PG's shifted positions. + - RepackStmt productions (3 conflicts): + - PG commit c0b53ec06309f955455c7d71da277991d0da4ec0 renamed ClusterStmt to RepackStmt and added new REPACK productions alongside the existing CLUSTER ones. + - YB added `parser_ybc_not_support` to CLUSTER productions. + - Took PG's changes; not_support gating is handled at the toplevel (`| RepackStmt { parser_ybc_not_support(@1, "REPACK/CLUSTER"); }` - see toplevel_stmt resolution above). + +- src/backend/parser/parse_utilcmd.c: + - CONSTR_PRIMARY pg_fallthrough vs yb_switch_fallthrough: accept pg_fallthrough. + - build_attrmap_by_name call: + - PG added an arg for `bool missing_ok`. + - YB independently added an arg for `bool yb_ignore_type_mismatch`. + - Passed both `false`s (merged signature takes both). + - transformIndexConstraint comment: + - PG commit 190dc27998d5b7b4c36e12bebe62f7176f4b4507 changed the comment. + - YB changed `btree` to `btree/lsm`. + - Took PG's comment with YB's btree/lsm rename. + - transformIndexConstraint loop `i < index_form->indnkeyatts` branch: + - PG added a "If a PK, ensure the columns get not null constraints" block. + - YB added an `IsYugaByteEnabled()` block. + - Kept both. + - transformIndexConstraint iparam field assignment: + - PG commit 62299bbd90d69e2273d3e2ba35af5953d20ca037 added `iparam->location = -1`. PG commit 14e87ffa5c543b5f30ead7413084c25f7735039f removed the post-loop `AT_SetNotNull` block (NOT NULL is now a catalog constraint). + - YB commit 6fe8bd22c733c7a5725bb50b35193137c3315d08 changed `iparam->ordering`/`nulls_ordering` from `NIL`/`SORTBY_DEFAULT` to propagation from `index_elem`. YB added `notnullcmd->yb_is_add_primary_key = true;` inside the block. + - Kept both changes to `iparams->*`; added YB_TODO_PG19MERGE for `yb_is_add_primary_key` flag. + +- src/backend/storage/ipc/sinvaladt.c: + - shmInvalBuffer declaration / NumProcStateSlots: + - PG added #define NumProcStateSlots. + - YB commit 70fbb382e1a913c651a6dbc85a75bcf2e25d32a8 dropped `static` and added `extern struct SISeg *shmInvalBuffer;` to sinvaladt.h. The extern was later dropped and no consumer references the variable from outside sinvaladt.c. + - Took PG's changes; kept `static`. + - CleanupInvalidationStateInternal (3 conflicts): + - PG commit 024c521117579a6d356050ad3d78fdc95e44eefa renamed `MyBackendId`/`proc->backendId - 1` to `MyProcNumber`/`proc->procNumber` (0-based), changed the inactive-slot scan to `pgprocnos[i] == MyProcNumber`, and moved `BackendIdGetProc`/`BackendIdGetTransactionIds` out of this file to procarray.c as `ProcNumberGetProc`/`ProcNumberGetTransactionIds`. + - YB extracted the body into `CleanupInvalidationStateInternal(SISeg *segP, PGPROC *proc)` and added `CleanupInvalidationStateForProc(PGPROC *proc)` (called from the postmaster.c). + - Kept YB's helper split; ported PG's renames inside the helper using `proc->procNumber`; dropped the orphaned PG `BackendIdGetProc`/`BackendIdGetTransactionIds` bodies (now in procarray.c). Renamed YB-specific helpers to `YbCleanupInvalidationStateInternal` / `YbCleanupInvalidationStateForProc` (callers updated). + - SICleanupQueue loop / yb_reset_candidates: + - PG changed loop to iterate `numProcs` + - YB added yb_reset_candidates / yb_num_reset_candidates and a comment block (adjacent line conflict). + - Combined both changes. + +- src/backend/storage/lmgr/lwlock.c: + - BuiltinTrancheNames array: + - PG commit da952b415f4444fcc85ea79c3f006af142d3c90a and 2047ad068139f0b8c6da73d0b845ca9ba30fb33d introduced storage/lwlocklist.h with PG_LWLOCK / PG_LWLOCKTRANCHE macros, removed the entries in lwlock.c (now auto-generated by including lwlocklist.h with the macros expanded as array initializers). + - YB appended five YB tranche names (YB_ASH_*, YB_QUERY_DIAGNOSTICS*, YB_TERMINATED_QUERIES) to the list. + - Kept PG's changes and registered the five YB tranches via PG_LWLOCKTRANCHE entries appended at the end of src/include/storage/lwlocklist.h. Also added the five matching entries (YbAshCircularBuffer, YbAshMetadata, YbQueryDiagnostics, YbQueryDiagnosticsCircularBuffer, YbTerminatedQueries) to src/backend/utils/activity/wait_event_names.txt in the same order, since generate-lwlocknames.pl cross-checks the two lists and dies on mismatch. + - LWLockQueueSelf / LWLockDequeueSelf proclist arg (2 conflicts): + - PG changed proc->pgprocno to MyProcNumber. + - YB passed proc->pgprocno (using YB's KilledProcToClean fallback proc). + - Passed `GetNumberFromPGProc(proc)`. + - LWLockAcquire prelude: + - PG changed to Assert (from AssertArg) + - YB added code above the Assert. + - Kept YB's code; took PG's Assert. + +- src/include/storage/lwlock.h: + - BuiltinTrancheIds enum: + - PG commit 2047ad068139f0b8c6da73d0b845ca9ba30fb33d refactored enum with `LWTRANCHE_INVALID` and `#include`-based structure. + - YB added `LWTRANCHE_YB_*`s. + - Kept PG's enum structure, removed YB's `LWTRANCHE_YB_*`. + +- src/backend/replication/logical/proto.c: + - logicalrep_write_tuple forward decl / definition / call sites, logicalrep_write_update definition (7 conflicts): + - PG added `PublishGencolsType include_gencols_type` parameter. + - YB added `bool *yb_is_omitted` parameter (and `yb_old_is_omitted`/`yb_new_is_omitted` for logicalrep_write_update). + - Combined both sides for all conflicts. + +- src/backend/replication/logical/reorderbuffer.c: + - YB includes: + - PG added MAX_DISTR_INVAL_MSG_PER_TXN macro. + - YB added `#include "pg_yb_utils.h"` and `#include "replication/walsender_private.h"`. + - Kept both, with YB includes placed below PG includes. + - REORDER_BUFFER_CHANGE_INSERT switch fallthrough: kept pg_fallthrough. + - reloid lookup via RelidByRelfilenumber (2 conflicts): + - PG renamed RelidByRelfilenode to RelidByRelfilenumber; relnode.spcNode/relNode to rlocator.spcOid/relNumber. + - YB wrapped the lookup in an else branch of `if (IsYugaByteEnabled())` + - Kept YB's else wrapper and called PG's renamed RelidByRelfilenumber with rlocator fields; ditto for the `could not open relation` elog. + - rbtxn_is_prepared checks (2 conflicts): + - PG renamed rbtxn_prepared to rbtxn_is_prepared. + - YB added `if (!IsYugaByteEnabled())` to the condition. + - Combined. + - txn->base_snapshot == NULL block (inside ReorderBufferReplay): + - PG commit abfb29648f9adcde84656afde1d50bc8b8e9b6e0 renamed rbtxn_prepared to rbtxn_is_prepared + - YB moved PG code into `!IsYugaByteEnabled()` block + - Kept YB's structure and taking PG's rbtxn_is_prepared rename + - ReorderBufferCheckMemoryLimit `logical_decoding_work_mem` checks (2 conflicts): + - PG commits 5de94a041ed7a51b571db2030ba87600c7fc6262, 072ee847ad4c3fb52b0c24f7dddbe0798bd70c24, d3b6183dd988928dd369b4b7d641917e77f1ae4e and others restructured the if/else before the while-loop and changed `* 1024L` to `* (Size) 1024`. + - YB substituted `logical_decoding_work_mem` with `IsYugaByteEnabled() ? yb_reorderbuffer_max_changes_in_memory : logical_decoding_work_mem` at all memory-limit checks. + - Adopted PG's restructure; applied YB's ternary at all three memory-limit references. + - ReorderBufferCheckMemoryLimit streaming/serialize sites (2 conflicts): + - PG commit 072ee847ad4c3fb52b0c24f7dddbe0798bd70c24 added `ReorderBufferCheckAndTruncateAbortedTXN` continue checks before the StreamTXN and SerializeTXN calls. + - YB added `if (IsYugaByteEnabled()) elog(DEBUG1, ...)` lines before the same StreamTXN and SerializeTXN calls. + - Kept PG's abort-truncation continues followed by YB's DEBUG1 elogs at both sites. + - End-of-file functions: + - PG added ReorderBufferGetInvalidations. + - YB added YBAllocateIsOmittedArray and YBReorderBufferSchemaChange. + - Kept all three. + +- src/backend/storage/lmgr/proc.c: + - ProcStructLock declaration: + - PG commit 7984ce7a1d21819865e473f17cb6b928cf58a10d removed ProcStructLock from this location. + - YB commit e5caabcfb079ac7d8616484353f15716f5f5ba22 added `RetryMaxBackoffMsecs` / `RetryMinBackoffMsecs` / `RetryBackoffMultiplier`; YB later added `yb_max_query_layer_retries` (adjacent line conflict) + - Kept YB's retry GUCs; dropped ProcStructLock. + - ProcGlobalShmemInit loop: + - PG changed to deference via local `proc` pointer. + - YB added LWLockInitialize for yb_ash_metadata_lock. + - Kept PG's `proc->` form; ported YB's yb_ash_metadata_lock init. + - ProcGlobalShmemInit tail: + - PG changed to index PreparedXactProcs at FIRST_PREPARED_XACT_PROC_NUMBER and removed ProcStructLock setup. + - YB added yb_too_many_conn shmem alloc. + - Kept PG's changes, YB's yb_too_many_conn alloc. + - InitProcess too-many-backends branch: + - PG changed to ProcGlobal->freeProcsLock and uses AmWalSenderProcess(). + - YB incremented yb_too_many_conn. + - Kept PG's changes, kept YB's yb_too_many_conn increment. + - InitProcess post-acquisition cleanup: + - PG commit 2bbc261ddbdfee2def5d14ee9fcc09c70bdf84e6 removed MarkPostmasterChildActive call. + - YB added three YB MyProc state initializers. + - Dropped PG-removed call; kept YB's three initializers (and comment). + - ProcKill (4 conflicts): + - PG commit 7984ce7a1d21819865e473f17cb6b928cf58a10d moved ProcStructLock onto `ProcGlobal->freeProcsLock` and converted the freelist to dlist (procgloballist becomes `dlist_head *`, push_head/push_tail). PG also added a `proc->pid = 0; proc->vxid.procNumber/lxid` reset block before the lock acquire. PG commit 2bbc261ddbdfee2def5d14ee9fcc09c70bdf84e6 removed the `MarkPostmasterChildInactive()` call; PG also removed the `kill(AutovacuumLauncherPid, SIGUSR2)` block; + - YB extracted the lock-group-detach into `RemoveLockGroupLeader(PGPROC *)` and the freelist return into `ReleaseProcToFreeList(PGPROC *)`. YB also added an `if (IsYugaByteEnabled()) YBOnPostgresBackendShutdown();` call. + - Kept YB's helper split; ported PG's changes (`&ProcGlobal->freeProcsLock`, dlist ops, `dlist_head *procgloballist`) into both helpers; kept PG's "Mark proc no longer in use" proc->pid block; dropped the `MarkPostmasterChildInactive` block and the autovac wakeup. Renamed helpers `YbRemoveLockGroupLeader` / `YbReleaseProcToFreeList` (callers updated). + +- src/backend/tcop/postgres.c: + - SocketBackend message-type switch (Bind/Parse): + - PG renamed message constants to PqMsg_* enums. + - YB added 'n' / 'p' YSQL Conn Mgr handling with yb_switch_fallthrough() into 'B'/'P'. + - Kept YB's 'n'/'p' cases (with pg_fallthrough) before PqMsg_Bind / PqMsg_Parse with a YB_TODO_PG19MERGE. + - exec_simple_query locals: + - PG added `const char *cmdtagname;`, `size_t cmdtaglen;`. + - YB added `bool yb_collect_commit_stats = false;`. + - Kept all three. + - exec_parse_message ParseComplete emit: + - PG commit f4b54e1ed9853ab9aff524494866823f951b1e7f changed `pq_putemptymessage('1')` to `pq_putemptymessage(PqMsg_ParseComplete)`. + - YB parameterized the function (`whereToSendOutput` -> `output_dest` arg + `yb_parse_custom_parse_complete` arg) and wrapped the emit in an if/else with PG's ParseComplete in the else. + - Kept YB's structure with PG's `PqMsg_ParseComplete`. + - ProcessRecoveryConflictInterrupt cases (3 conflicts): + - PG removed the fallthrough from each case and replaced it with a return. + - YB added `yb_switch_fallthrough()`. + - Kept PG's changes. + - ProcessInterrupts, stack-depth helpers (set_stack_base, stack_is_too_deep, check_max_stack_depth, IA64 helpers) (3 conflicts): + - PG added ParallelApplyMessagePending / SlotSyncShutdownPending / RepackMessagePending. PG moved the stack depth helpers into src/backend/utils/misc/stack_depth.c. + - YB added YbLogCatcacheStatsPending and LogHeapSnapshotPending. YB added an ADDRESS_SANITIZER block inside stack_is_too_deep, and some other macros. + - Kept all five process interrupt calls. Accepted PG's removal of the stack depth helpers and added a YB_TODO_PG19MERGE. + - PostgresMain PostmasterContext drop comment: + - PG trimmed the comment. + - YB added a YB note. + - Kept PG's short comment plus the YB note. + - PostgresMain ReadyForQuery first-time setup-duration log + YB ASH unset: + - PG added the `conn_timing.ready_for_use` first-time setup-duration log block. + - YB added a yb_is_ash_metadata_set unset block in the same place. + - Kept both blocks back-to-back. + - PostgresMain Parse case (2 conflicts): + - PG renamed 'P' -> PqMsg_Parse and called added valgrind_report_error_query. + - YB added 'n'/'p' YSQL Conn Mgr cases, expanded exec_parse_message with extra args, and wrapped in PG_TRY/PG_CATCH. + - Kept YB's 'n'/'p' cases (using pg_fallthrough), PG's PqMsg_Parse name, YB's PG_TRY wrapper and expanded exec_parse_message args with PG's valgrind_report_error_query after the PG_TRY/PG_CATCH. + - PostgresMain Execute case: + - PG added a comment below exec_execute_message. + - YB removed exec_execute_message and replaced it with a PG_TRY/PG_CATCH that calls yb_exec_execute_message. + - Kept YB's PG_TRY/PG_CATCH followed by the new PG comment. + - PostgresMain Sync case: + - PG renamed 'S' -> PqMsg_Sync and added EndImplicitTransactionBlock + valgrind_report_error_query. + - YB wrapped finish_xact_command in a PG_TRY/PG_CATCH. + - Combined: PG's EndImplicitTransactionBlock, YB's PG_TRY around finish_xact_command, valgrind_report_error_query, then YB's YbRefreshSessionStatsDuringExecution. + - PostgresMain EOF fallthrough: kept pg_fallthrough. + +- src/backend/tcop/utility.c: + - standard_ProcessUtility T_CheckPointStmt body / T_ReindexStmt case: + - PG commit a4f126516e688736bfed332b44a0c221b8dc118a refactored the T_CheckPointStmt body into `ExecCheckpoint()`. Commit f21848de20130146bc8039504af40bd24add54cd moved T_ReindexStmt. + - YB commit 986ea6a8eca22c3aa0e807f85b60c677783da87e added `CHECKPOINT_CAUSE_CLIENT |` to the inline `RequestCheckpoint` flags (CHECKPOINT_CAUSE_CLIENT is a YB-defined macro in xlog.h). + - Took PG's changes; ported YB's `CHECKPOINT_CAUSE_CLIENT |` into `RequestCheckpoint` inside `ExecCheckpoint` (checkpointer.c). + - standard_ProcessUtility T_WaitStmt / T_YbCreateProfileStmt / T_YbDropProfileStmt: + - PG added T_WaitStmt. + - YB added T_YbCreateProfileStmt / T_YbDropProfileStmt cases. + - Kept all three. + - ProcessUtilitySlow partitioned table comment: + - PG updated the comment + - YB added a "YB: ..." line. + - Kept PG's full text; appended YB's note. + - ProcessUtilitySlow T_RefreshMatViewStmt PG_TRY: + - PG changed `PG_TRY()` to `PG_TRY(2)` + - YB added two volatile locals (yb_old_type / yb_type_changed) before PG_TRY() (adjacent line conflict). + - Kept YB's volatile locals; used PG's PG_TRY(2). + - ExecDropStmt fallthrough: + - PG used `pg_fallthrough;`. + - YB used `yb_switch_fallthrough();`. + - Kept `pg_fallthrough;`. + - UtilityReturnsTuples / UtilityTupleDescriptor: + - PG added T_WaitStmt entries. + - YB added T_YbBackfillIndexStmt entries. + - Kept both cases in each function. + +- src/include/replication/reorderbuffer.h: + - DebugLogicalRepStreamingMode enum: + - PG (commit 08e6344fd6423210b339e92c069bb979ba4e7cd6 and others) added `DebugLogicalRepStreamingMode` enum, removed the `ReorderBufferTupleBuf` struct + - YB commit fddfec620d291de3423277812bbe6097dfcbce3d added fields inside ReorderBufferTupleBuf + - Took PG's removal; added YB_TODO_PG19MERGE for YB's fields. + - ReorderBufferChange struct: + - PG commit 08e6344fd6423210b339e92c069bb979ba4e7cd6 changed `newtuple` from `ReorderBufferTupleBuf *` to `HeapTuple`. + - YB added `Oid yb_table_oid` alongside. + - Combined both changes. + - ReorderBufferUpdateProgressTxnCB / YBReorderBufferSchemaChangeCB: kept both callback typedefs. + +- src/include/replication/slot.h: + - includes: + - PG added macros. + - YB added includes. + - Kept both. + - ReplicationSlotPersistentData: + - PG added some fields. + - YB added others. + - Kept both. + - ReplicationSlotCreate / ReplicationSlotDrop signatures: + - PG added repack/failover/synced bool params to Create. + - YB added 5 yb_* params to Create and 2 yb_* params to Drop, and some extern PGDLLIMPORT declarations. + - Combined both changes. + +- src/pl/plpgsql/src/pl_comp.c: + - plpgsql_compile: + - PG commit 0dca5d68d7bebf2c1036fd84875533afef6df992 refactored the entire compile/lookup into cached_function_compile() helper. + - YB added inline plpgsql_HashTableLookup with extensive YB invalidation logic (yb_invalid, yb_catalog_version freshness, trigger-fn secondary-entry sweep). + - Adopted PG's cached_function_compile() call; added YB_TODO_PG19MERGE for the invalidation logic port. + - plpgsql_compile_callback: + - PG added function->has_exception_block + - YB added function->yb_catalog_version + - Kept both. + - plpgsql_add_initdatums break vs yb_switch_fallthrough: kept `break`. + +- src/backend/utils/adt/datetime.c: + - DecodeTimeOnly: + - PG (commit 5b3c5953553bb9fb0b171abc6041e7c7e9ca5b4d and others) removed the switch and renamed the local `val` to `value`. + - YB added `yb_switch_fallthrough();`. + - Took PG's changes. + - pg_fallthrough vs yb_switch_fallthrough (3 conflicts): keep pg_fallthrough. + +- src/backend/utils/adt/pg_locale.c: + - includes: + - PG added the PGLOCALE_SUPPORT_ERROR macro and TEXTBUFLEN define. + - YB added an include. + - Kept both. + - lc_collate_is_c / lc_ctype_is_c (2 conflicts): + - PG commits 06421b08436414b42cd169501005f15adee986f1 and 51edc4ca54f826cfac012c7306eee479f07a5dc7 removed both functions entirely (replaced by per-locale provider helpers). + - YB added YB-specific code inside both function bodies (YBCPgIsYugaByteEnabled assert, YB_THREAD_LOCAL caches) and added an `extern bool lc_collate_is_c(Oid)` declaration to `src/include/utils/builtins.h` (commit 6c8deb0504274a16bae390dcbabf45aa5ac23fbb). + - Added YB_TODO_PG19MERGE: function bodies commented out, YB extern in builtins.h removed, `lc_collate_is_c` callers in ybginget.c and pg_yb_utils.c commented out. + - pg_newlocale_from_collation: + - PG commit 3aa2373c114124f62e80016d8939331fcb4d5586 extracted the inline `SearchSysCache1` + locale-construction block out of `lookup_collation_cache` into a new `create_pg_locale(collid, CollationCacheContext)`; Follow-up commit 1ba0782ce90cb4261098de59b49ae5cb2326566b further split `create_pg_locale` into per-provider helpers. + - YB added `YbCheckUnsupportedLibcLocale` calls inside the libc branch of the inline construction. + - Kept PG's `create_pg_locale(...)` changes; YB_TODO_PG19MERGE to port `YbCheckUnsupportedLibcLocale`. + +- src/backend/utils/misc/postgresql.conf.sample: + - all conflicts: + - Took PG's new entries and reformatted indentation; added YB's additions. + +- src/backend/utils/mmgr/slab.c: + - SlabReset block-free site: + - PG added VALGRIND_MEMPOOL_FREE before free(block) and removed nblocks + - YB added freed_sz tracking and YbPgMemSubConsumption + - Kept both. + - SlabDelete: + - Same as above. + - SlabAllocFromNewBlock: + - Same as above. + - SlabFree empty-block path: + - PG commit d21ded75fdbc18d68be6e6c172f0f842c50e9263 moved the block-free (`free(block)` and surrounding code) into an else branch. + - YB added YbPgMemSubConsumption(freed_sz) after `free(block)`. + - Moved YB's YbPgMemSubConsumption into PG's else branch. + +- src/backend/utils/sort/tuplesort.c: + - top-of-file include + sort-type macros / yb_sort_type_name: + - PG commit d0b193c0fad13cf35122b0d3dc805c76e323e8bf removed the sort-type defines, PARALLEL_SORT macro (moved into tuplesortvariants.c / tuplesort.h). + - YB added pg_yb_utils.h include, YB_SORT_UNINITIALIZED define, and yb_sort_type_name helper. + - Took PG's removal; kept the YB include; moved yb_sort_type_name definition to tuplesortvariants.c; added YB_SORT_UNINITIALIZED define and yb_sort_type_name extern declaration in `tuplesort.h` (as they have references in both tuplesort.c and tuplesortvariants.c). + - Tuplesortstate struct: + - PG commit ec92fe98356a8a36427fe9ef52873b50fe17852e replaced nKeys/sortopt/tuples and other fields on Tuplesortstate with a TuplesortPublic base member. + - YB added `int yb_sort_type` + - Kept PG's changes (yb_sort_type moved to tuplesort.h as part of build fixes -- see notes below). + - tuplesort_begin_common init: + - PG commit ec92fe98356a8a36427fe9ef52873b50fe17852e rewrote field accesses to state->base.*. + - YB added state->yb_sort_type = YB_SORT_UNINITIALIZED. + - Took PG's body; inserted YB's yb_sort_type init. + - tuplesort_begin_heap/cluster/index_btree/index_hash/index_gist/datum: + - PG commit d0b193c0fad13cf35122b0d3dc805c76e323e8bf moved these functions into tuplesortvariants.c. + - YB added a state->yb_sort_type = HEAP_SORT/CLUSTER_SORT/INDEX_SORT/DATUM_SORT assignment in each function (and LSM_AM_OID alongside BTREE_AM_OID in cluster's assert). + - Took PG's removal; ported each yb_sort_type assignment + LSM_AM_OID check into the corresponding function in tuplesortvariants.c. + +- src/include/executor/executor.h: + - EXEC_FLAG comment block: + - PG added WITH_NO_DATA description. + - YB added YB_AGG_PARENT description. + - Kept both. + - FindTupleHashEntry signature: + - PG replaced FmgrInfo *hashfunctions with ExprState *hashexpr. + - YB added an AttrNumber *keyColIdx param. + - Kept both. + - ExecConstraints / ExecRelGenVirtualNotNull: + - PG added ExecRelGenVirtualNotNull extern. + - YB added a ModifyTableState *mstate parameter to ExecConstraints. + - Kept both. + - ExecCheckIndexConstraints signature: + - PG added const ItemPointerData *tupleid parameter. + - YB added TupleTableSlot **ybConflictSlot parameter. + - Kept both. + +- src/include/pgstat.h: + - PgStat_Kind enum: + - PG commit d35ea27e51c05cbe3575d50a6b99d64f20a3a742 moved PgStat_Kind from an enum in pgstat.h to #define values in src/include/utils/pgstat_kind.h. + - YB added PGSTAT_KIND_YB_TERMINATED_QUERIES + - Took PG's removal; added `#define PGSTAT_KIND_YB_TERMINATED_QUERIES 25` to pgstat_kind.h. + - PgStat_WalCounters / inline YB structs: + - PG inserted typedef struct PgStat_WalCounters at this position. + - YB carried a pre-existing #ifdef YB_TODO block (PG15-merge remnant) with ProgressCommandType / PgBackendStatus / LocalPgBackendStatus copies; PG commit e1025044cd4e7f33f7304aed54d5778b8a82cd5d already moved these to utils/backend_status.h. + - Kept PG's PgStat_WalCounters typedef and the YB #ifdef YB_TODO block as-is with a YB_TODO_PG19MERGE. + - pgstat_update_parallel_workers_stats / yb_pgstat_clear_entry_pid: + - PG added pgstat_update_parallel_workers_stats extern. + - YB added yb_pgstat_clear_entry_pid extern; the adjacent pgstat_report_query_termination extern is already wrapped in #ifdef YB_TODO (PG-stats refactor pending). + - Kept both PG and YB externs; preserved the #ifdef YB_TODO pgstat_report_query_termination block with a YB_TODO_PG19MERGE. + - end-of-file PendingWalStats + YB externs: + - PG removed `extern PGDLLIMPORT PgStat_WalStats PendingWalStats;` (moved to backend_status.h). + - YB kept the PendingWalStats line and appended yb_pgstat_report_allocated_mem_bytes / yb_pgstat_set_catalog_version / yb_pgstat_set_has_catalog_version externs. + - Kept YB's three yb_pgstat_* externs. + +- src/interfaces/libpq/fe-connect.c: + - PQconninfoOptions table tail: + - PG added entries before the terminating sentinel. + - YB added a yb_auto_analyze entry. + - Kept all PG entries followed by the YB entry. + - pqConnectOptions2 pguser default: + - PG dropped the `if (conn->pguser)` guard before free(). + - YB hard-coded strdup("postgres") for `conn->pguser` (adjacent line conflict). + - Kept PG's unguarded free() and YB's "postgres" default. + - pqConnectOptions2 dbName default: + - Same as above (hardcoded default "yugabyte"). + - freePGconn: + - PG (commit 02c408e21a6e78ff246ea7a1beb4669634fa9c4c and others) dropped the `if (x) free(x)` guards and removed some fields. + - YB added `if (conn->yb_auto_analyze) free(conn->yb_auto_analyze)`. + - Kept PG's changes; appended free(conn->yb_auto_analyze) (unguarded). + +- src/test/regress/pg_regress_main.c: + - PG changes vs ysqlsh_cmd/ysqlsh rename (4 conflicts): + - Took PG's changes; kept YB's rename to ysqlsh_cmd. + +- src/backend/utils/adt/ruleutils.c: + - pg_get_indexdef_worker forward decl + propgraph helpers: + - PG added new declarations. + - YB extended pg_get_indexdef_worker with YB params. + - Kept YB's expanded signature; kept PG's new declarations. + - pg_get_constraintdef_worker PK column decompile: + - PG added new arg to decompile_column_index_array call and added WITHOUT OVERLAPS append. + - YB branched on is_yb_alter_table + IsYBRelationById to call yb_decompile_pk_column_index_array (preserves HASH/ASC/DESC). + - Combined: YB branch, fallback to PG's new call, then PG's WITHOUT OVERLAPS append. + - find_param_referent NestLoop check: + - PG commit adaf34241acd83afaa45a8b614b6484a285da847 removed `in_same_plan_level` + - YB extended the predicate to also recognize YbBatchedNestLoop. + - Followed PG; kept YB's YbBatchedNestLoop branch. + - get_rule_expr fallthrough: kept `pg_fallthrough`. + - get_rule_expr T_MergeSupportFunc / T_YbBatchedExpr: + - PG added T_MergeSupportFunc case + - YB added T_YbBatchedExpr case + - Kept both cases. + +- src/backend/utils/mmgr/aset.c: + - AllocSetDelete full freelist block: + - PG commit bb049a79d3447e97c0d4fa220600c423c4474bf9 added VALGRIND_DESTROY_MEMPOOL(oldset) before free(oldset). + - YB added freed_sz and YbPgMemSubConsumption(freed_sz) after free(oldset). + - Kept both. + - AllocSetDelete block-free site: + - PG commit bb049a79d3447e97c0d4fa220600c423c4474bf9 added VALGRIND_MEMPOOL_FREE; PG commit 2c2eb0d6b27f498851bace47fc19e4c7fc90af4f changed `block != set->keeper` to `!IsKeeperBlock(set, block)`. + - YB added freed_sz = ASET_BLOCK_TOTAL_SIZE(block) and YbPgMemSubConsumption(freed_sz) after free(block). + - Kept PG's IsKeeperBlock + VALGRIND_MEMPOOL_FREE; added YB's freed_sz tracking after free(block). + - AllocSetAlloc large-chunk path / new-block path (2 conflicts): + - PG commit 413c18401dcc170636429127e2494d8beba4b92f extracted the large-chunk and new-block paths into AllocSetAllocLarge / AllocSetAllocFromNewBlock / AllocSetAllocChunkFromBlock helpers. + - YB added YbPgMemAddConsumption(blksize) inline at the post-malloc site in both paths. + - Adopted PG's helpers; ported YbPgMemAddConsumption(blksize) into AllocSetAllocLarge and AllocSetAllocFromNewBlock at the malloc-success site; stashed the YB inline bodies in `#if 0` with a YB_TODO_PG19MERGE. + - AllocSetFree block-remove site: + - PG changed context->mem_allocated to set->header.mem_allocated. + - YB added freed_sz = ASET_BLOCK_TOTAL_SIZE(block) before the mem_allocated decrement + - Kept PG's set->header.mem_allocated; kept YB's freed_sz local before it. + +- src/bin/psql/describe.c: + - default ACLs CASE block (2 conflicts): + - PG added DEFACLOBJ_LARGEOBJECT (sixth WHEN). + - YB added DEFACLOBJ_TABLEGROUP. + - Extended the CASE format to seven WHENs; kept both objects. + - describeOneTableDetails \d index column-list query: + - PG added c2.reltablespace + conditional con.conperiod selection. + - YB added i.indexrelid for yb_table_properties. + - Appended YB's i.indexrelid; appended PG's con.conperiod logic; added YB_TODO_PG19MERGE (breaks column numbering). + - describeOneTableDetails \d Replica Identity (gate + label-rendering ternary -- 2 conflicts): + - PG switched from raw chars to named constants (REPLICA_IDENTITY_INDEX/DEFAULT/NOTHING). + - YB commit 4da9d867bdd46af982a885d46184aa6dc785289e added YB_REPLICA_IDENTITY_CHANGE and made it the default for user tables, so the gate is per-schema and the rendering ternary a case that maps DEFAULT to "DEFAULT" case. + - Kept YB's per-schema gate and rendering, expressed with PG's named constants. + +- src/include/nodes/parsenodes.h: + - SortByDir enum: YB added SORTBY_HASH; kept it. + - RTEPermissionInfo / RangeTblEntry tail: + - PG (commit a61b1f74823c9c4f79c95226a461f1e7a367764b and others) split permission-checking fields out of RangeTblEntry into a new RTEPermissionInfo struct, removed extraUpdatedCols, and relocated securityQuals. + - YB added 4 yb fields (ybHintAlias, ybUniqueBaseId, ybScannedObjectName, ybSchemaName). + - Took PG's changes; appended YB's 4 yb fields to RangeTblEntry with `pg_node_attr(query_jumble_ignore)`. + - ObjectType / AlterTableType YB additions (2 conflicts): YB added OBJECT_YBPROFILE / AT_YbAlterIndexAttributeType; kept both. + - Constraint struct: + - PG (commit fbc93b8b5f59cfb23c22a26a46ca7bcc826be442 and others) moved skip_validation/initially_valid earlier in the struct and moved `location` field at the end + - YB added yb_index_params + - Took PG's changes; appended yb_index_params with `pg_node_attr(query_jumble_ignore)`. + +- src/backend/optimizer/path/indxpath.c: + - get_index_paths build_index_paths call: + - PG commit 5bf748b86bc6786a3fc57fc7ce296c37da6564b0 removed the `skip_lower_saop` argument and removed the `if (skip_lower_saop)` block. + - YB added a `yb_bitmap_idx_pushdowns` parameter to build_index_paths inside `if (skip_lower_saop)`. + - Kept PG's changes with a YB_TODO_PG19MERGE. + - build_index_paths comment: + - PG removed the comment for `skip_lower_saop`. + - YB added to the comment. + - Kept YB's comment. + - build_index_paths body: + - PG 5bf748b86bc6786a3fc57fc7ce296c37da6564b0 removed `skip_lower_saop` and restructured this block. + - YB added yb_hash_code_on_left and is_yb_index handling. + - Kept PG's changes with a YB_TODO_PG19MERGE. + - match_join_clauses_to_index: + - PG commit 627d63419e22054551327216d2b2de3e6977fade removed the `else` that wrapped match_clause_to_index. + - YB added a parameter to match_clause_to_index. + - Kept PG's changes; kept YB's additional parameter. + - expand_indexqual_rowcompare: + - PG commit c23e3e6beb273ae8c0f8e616edb7ed1acb0271c4 replaced `list_truncate(list_copy(...), n)` with the new `list_copy_head(list, n)` helper. + - YB casted the `non_var_args` assignment to `(Node *)` because YB commit ada31e719db783e6c26b1b0b8c9447174ad7cbc5 changed `RowCompareExpr.rargs` from `List *` to `Node *` (see also -- the YB comment in primnodes.h for RowCompareExpr) + - Adopted PG's `list_copy_head` form; preserved YB's `(Node *)` cast on `rc->rargs`. + - relation_has_unique_index_for: + - PG commit bf9ee294e567654231c5b2fef09b8a5367907366 removed exprlist/oprlist params (and the assertion). + - YB added a block for ybIndexList. + - Kept YB's ybIndexList block. + +- src/backend/utils/cache/inval.c: + - TransInvalidationInfo tail: + - PG moved CurrentCmdInvalidMsgs and RelcacheInitFileInval into the new InvalidationInfo base struct above. + - YB added YbNumInvalMsgsInTxn[YB_SUBGROUP_COUNT]. + - Kept only YbNumInvalMsgsInTxn. + - relsync_callback_list / yb_accept_inval_failed_version: + - PG added relsync_callback_*. + - YB added yb_accept_inval_failed_version + yb_refresh_cache_in_progress globals. + - Kept both blocks. + - RegisterRelcacheInvalidation YB global-DDL detection: + - PG commit 243e9b40f1b2dd09d6e5bf91ebf6e822a2cd3704 switched `transInvalInfo->...` to `info->...` (InvalidationInfo* param); commit 3abe9dc18892b9f69bb48a2eb21fbe5cf348a489 added RegisterRelsyncInvalidation. + - YB added a YbSetIsGlobalDDL gate. + - Kept PG's changes; appended YB's gate after `info->RelcacheInitFileInval = true;`. + - InvalidateSystemCachesExtended / CallSystemCacheCallbacks: + - PG (commit 3abe9dc18892b9f69bb48a2eb21fbe5cf348a489 and others) added a relsync_callback_list iteration loop inside InvalidateSystemCachesExtended and re-ordered the functions in this file. + - YB extended InvalidateSystemCachesExtended's signature with a `bool yb_callback` arg and added CallSystemCacheCallbacks below. + - Ported YB's changes to the new InvalidateSystemCachesExtended location (YB's `bool yb_callback` param, YB's gate wrapping the cache reset, PG's three callback loops (syscache + relcache + relsync) kept); appended YB's CallSystemCacheCallbacks helper. + - AcceptInvalidationMessages: + - PG added an USE_ASSERT_CHECKING block calling AssertCouldGetRelation. + - YB added a YbIsInvalidationMessageEnabled block. + - Kept both, PG block first. + - CacheInvalidateSmgr: + - PG added a StaticAssertDecl. + - YB added a yb_test_inval_message_portability hook and yb_version write. + - Kept both, PG static-assert first. + +- src/bin/pg_dump/t/002_pg_dump.pl: + - all 6 conflicts: + - No YB changes to this file, only PG imports. The conflicts exist only because yb-pg15 cherry-picks included PG cleanups for this file that YB master's imports left behind. Resolved all by taking PG's version. + +- src/include/nodes/execnodes.h: + - top-of-file forward references: + - PG converted the whole list to typedef-style forward refs. + - YB added an include. + - Kept PG's typedef list; prepended YB's ybc_pg_typedefs.h include. + - EState: + - PG removed es_resultrelinfo_extra; YB had it inside EState. + - Dropped the field, dropped PG's closing brace for the struct (YB added additional fields below the conflict marker). + - TupleHashTableData YB key-expression fields: + - PG made whitespace changes for `} TupleHashTableData;`. + - YB added yb_keyColExprs / in_keyColIdx / yb_in_keycolExprs. + - Kept the YB additions before the closing brace. + - ModifyTableState YB attributes: + - PG added mt_updateColnosLists / mt_mergeActionLists / mt_mergeJoinConditions. + - YB added yb_fetch_target_tuple / yb_is_update_optimization_enabled / yb_is_inplace_index_update_enabled / yb_ioc_state. + - Kept both. + - YbBitmapIndexScanState / SharedBitmapState / ParallelBitmapHeapState: + - PG removed/moved SharedBitmapState and ParallelBitmapHeapState. + - YB added YbBitmapIndexScanState in this region. + - Kept YbBitmapIndexScanState; dropped the PG-removed structs. + - TidScanState YB fields: + - PG removed HeapTupleData tss_htup. + - YB added List *yb_tss_aggrefs. + - Kept List *yb_tss_aggrefs. + +- src/backend/utils/adt/ri_triggers.c: + - RI_ConstraintInfo struct tail: + - PG added conindid, pk_is_partitioned and *fpmeta. + - YB also independently added conindid (commit ca81018844a4c0b68fc386c16495a3fe02d5fd43) + - Kept PG's changes. + - RI_FKey_check fast paths: + - PG added a `ri_fastpath_is_applicable` short-circuit before SPI_connect, then SPI_connect, then table_open(pk_rel). + - YB added a separate IsYBRelation(pk_rel) fast path that needs pk_rel already open and short-circuits before SPI work. + - Kept PG's `ri_fastpath_is_applicable` block, then table_open(pk_rel), then YB's IsYBRelation fast path, then SPI_connect. This swaps PG's SPI_connect/table_open order so YB's fast path can short-circuit before SPI connect. + - RI_Initial_Check: + - PG changed pkrte and fkrte to pk_perminfo and fk_perminfo. + - YB used YBGetFirstLowInvalidAttributeNumber(pk_rel/fk_rel). + - Kept PG's `pk_perminfo->selectedCols` / `fk_perminfo->selectedCols` writes; used YBGetFirstLowInvalidAttributeNumber. + - ri_LoadConstraintInfo conindid + hasperiod: + - PG added `riinfo->hasperiod = conForm->conperiod` (before DeconstructFkConstraintRow) and `riinfo->conindid = conForm->conindid` (after, outside the confict marker). + - YB added `riinfo->conindid = conForm->conindid` before DeconstructFkConstraintRow. + - Kept PG's hasperiod + PG's conindid; discarded YB's assignment. + - ri_PerformCheck SPI_execute_snapshot: + - PG updated the comment above SPI_execute_snapshot. + - YB wrapped the call in PG_TRY/PG_CATCH. + - Kept PG's comment plus YB's PG_TRY wrapper. + - End-of-file function definitions (2 conflicts): + - PG added the ri_FastPath* helpers + - YB added YbAddTriggerFKReferenceIntent and HasNonRITrigger. + - Kept PG's functions; kept YB's. + +- src/backend/utils/resowner/resowner.c: + - ResourceOwnerRelease: both PG and YB independently added #ifdef blocks; kept both. + - remaining conflicts: + - PG commit b8bff07daa85c837a2747b4d35cd5a27e73fb7b2 reworked the ResourceOwner design. Took PG's side; stubbed YB's three `ResourceOwner*YbPgInheritsRef*` helpers as no-ops with a YB_TODO_PG19MERGE pointing at `catcache_resowner_desc` as the re-port template. + +- src/include/utils/resowner_private.h: + - PG removed the header in commit 06a0f4d52be3a52a74725dd29c66cd486256a209; the prior refactor b8bff07daa85c837a2747b4d35cd5a27e73fb7b2 had already moved its decls into the owning subsystem headers, leaving the file empty. + - YB added `ResourceOwnerEnlargeYbPgInheritsRefs` / `Remember` / `Forget` decls. + - Kept the file as a YB-only header (just the three decls) with a YB_TODO_PG19MERGE. + +- src/backend/utils/time/snapmgr.c: + - top-of-file YB / OldSnapshot includes: + - PG removed an include and some globals. + - YB added includes. + - Kept YB includes. + - SerializedSnapshotData tail / SnapMgrShmemSize / SnapMgrInit: + - PG removed whenTaken and lsn fields, SnapMgrShmemSize and SnapMgrInit. + - YB added yb_read_point_handle and some YB helpers. + - Kept only `YbOptionalReadPointHandle yb_read_point_handle` on SerializedSnapshotData; kept all the YB helpers. + - PushActiveSnapshotWithLevel, AtSubAbort_Snapshot (2 conflicts): + - PG removed OldestActiveSnapshot null check. + - YB added YbLogActiveSnapshot and YBCOnActiveSnapshotChange. + - Kept YbLogActiveSnapshot and YBCOnActiveSnapshotChange. + - ImportSnapshot: + - PG split AllocateFile error handling into ENOENT vs other-errno cases. + - YB wrapped the PG body in an else block. + - Adopted YB's if/else; ported PG's ENOENT split into the else branch. + - SerializeSnapshot whenTaken/lsn/yb_read_point_handle: + - PG removed whenTaken and lsn. + - YB added yb_read_point_handle. + - Kept only yb_read_point_handle. + - End-of-file functions: + - PG added the ResOwnerReleaseSnapshot callback. + - YB added YbGetCatalogSnapshotReadPoint and YbInvalidateCatalogSnapshot helpers. + - Kept both. + +- src/bin/pg_dump/pg_backup_archiver.c: + - InitDumpOptions: + - PG defaulted false. + - YB defaulted true. + - Kept YB's default (true). + - RestoreArchive commit gating + YB auto-analyze re-enable: + - PG added `|| ropt->txn_size > 0)` to the condition. + - YB inserted a yb_disable_auto_analyze=off re-enable block right before (adjacent line conflict). + - Kept YB's auto-analyze re-enable block, then PG's combined single_txn || txn_size>0 gate. + - _doSetFixedOutputState StartTransaction: + - PG added a StartTransaction block. + - YB emitted `\set use_tablespaces`, `\set use_roles` and a yb_disable_auto_analyze=on toggle. + - Emitted YB's metadata toggles first, then PG's StartTransaction. + - _getObjectDescription: + - PG removed `USER MAPPING`. + - YB added `TABLEGROUP` (adjacent line conflict). + - Kept PG's changes; kept YB's `TABLEGROUP`. + - _printTocEntry te->defn emission: + - PG commit 0f129052150487afab9fe64889c5bf7534f7bbc3 added txn_size using semicolon-counting. + - YB added a binary-upgrade special-case that flips AH->outputKind to OUTPUT_OTHERDATA so each statement in the defn is sent as a separate request. + - Combined: YB's outputKind special-case if/else first, then PG's semicolon-counting txn_size logic. + - _printTocEntry owner-emission desc-list test: + - PG simplified the test to a single `BLOB METADATA` branch. + - YB added TABLEGROUP. + - Kept PG's BLOB METADATA + generic _getObjectDescription else; left a YB_TODO_PG19MERGE. + - _printTocEntry owner-emission body: + - PG commit a45c78e3284b269085e9a0cbd0ea3b236b7180fa added the BLOB METADATA if branch; PG commit 3655b46aa0b8156aced0b9ee3d10875632f84407 simplified the generic else branch via _getObjectDescription. + - YB added some `include_yb_metadata` / `\if :use_roles` logic. + - Kept PG's changes. Same YB_TODO_PG19MERGE as above. + +- src/include/catalog/pg_proc.dat: + - pg_stat_get_activity columns: + - PG added a `gss_delegation` column. + - YB added a `yb_backend_xid` column at the end and an adjacent entry for yb_pg_stat_get_queries. + - Kept both pg_stat_get_activity columns and yb_pg_stat_get_queries. + - jsonb_path_*_tz functions: + - PG has 5 timezone-aware jsonpath functions at oids 1177, 1179, 1180, 2023, 2030. + - YB commit e591a963176fd0901707b9b175c1824e9c750147 moved these into the YB OID range (8005-8009) at the end of the file. The signatures/fields are identical to the PG entries; this was a pure OID renumber. The move appears unnecessary (these are not YB functions, and oids 1177/1179/1180/2023/2030 don't collide with any YB-added entry). + - Kept PG's original location and OIDs. Deleted YB's duplicate copies at oids 8005-8009. + - pg_get_replication_slots columns: + - PG added two_phase_at, inactive_since, conflicting, invalidation_reason, failover, synced, slotsync_skip_reason. + - YB added yb_stream_id, yb_restart_commit_ht, yb_lsn_type, yb_restart_time. + - Combined. + - pg_create_logical_replication_slot signature: + - PG added a `failover` arg. + - YB added `yb_lsn_type name, yb_ordering_mode name` args. + - Combined. + - binary_upgrade_* additions: + - PG added 6312/6319/6320/9159 (logical-slot/sub-rel/replorigin/conflict-detection helpers). + - YB added 8095/8100 (yb_binary_upgrade_set_next_pg_enum_sortorder / set_next_colocation_id). + - Kept all six. + - Statistics import functions: + - PG has these at oids 6362 / 6397 / 6363 / 6398. + - YB commit 715eb578ed84cfbb7c927055563daca8e3216e21 backported them from PG and assigned YB-range oids 8091-8094. No YB specific fields were added. + - Kept PG's entries. + - end-of-file block: + - PG added new entries. + - YB added others. + - Kept both sets of entries. + +- src/include/utils/elog.h: + - top-of-file include: + - PG added an include and a `struct Node;` forward decl. + - YB added an include. + - Kept both (YB include after PG include). + - ereport_domain macro (pg_integer_constant_p path): + - PG commit 59c2f03d1ece7b9b215751a508b3a84728419246 replaced the bare `__builtin_constant_p` callsite with a portable wrapper, renaming the macro `HAVE__BUILTIN_CONSTANT_P` to `HAVE_PG_BUILTIN_INTEGER_CONSTANT_P`. PG commit 8e2acda2b098bf53120721e16a7b1055c4e5b3a6 then renamed the wrapper and macro to `pg_integer_constant_p` / `HAVE_PG_INTEGER_CONSTANT_P`. PG commit 320f92b744b44f961e5d56f5f21de003e8027a7f replaced `PG_FUNCNAME_MACRO` with `__func__`. + - YB wrapped errstart/errfinish in an `if (IsMultiThreadedMode())` block with yb_errstart / yb_errfinish / yb_errstart_cold and a `YB_TODO` note. + - Kept YB's IsMultiThreadedMode branch structure with PG's renamed identifiers in both branches. Preserved the YB note (renamed to YB_TODO_PG19MERGE). + - ereport_domain fallback (no constant_p): + - Same as above. + - PG_TRY / PG_CATCH / PG_FINALLY / PG_END_TRY (3 conflicts): + - PG commit 112f0225dbfe8af217294bfa0bd227f3302a1658 added the suffix `##__VA_ARGS__` so PG_TRY can be nested. + - YB replaced direct PG_exception_stack reads/writes with yb_get_exception_stack/yb_set_exception_stack, and added yb_reset_error_status() in PG_END_TRY. + - Kept PG's `##__VA_ARGS__` suffix; used YB's yb_get_/yb_set_exception_stack helpers (and yb_reset_error_status() in PG_END_TRY). + +- src/include/nodes/pathnodes.h: + - PlannerGlobal: + - PG added several new fields and added pg_node_attr to partition_directory + - YB added other fields (all yb prefixed) + - Kept PG's fields; appended YB's fields. + - PlannerInfo: + - PG added new fields and removed partColsUpdated. + - YB added other fields (all yb prefixed). + - Kept PG's changes; appended YB's yb_-prefixed fields. Added a YB_TODO_PG19MERGE to evaluate whether the YB fields need `pg_node_attr`. + - RelOptInfo: + - PG made formatting changes and added `pg_node_attr` to some fields. + - YB added other yb prefixed fields. + - Kept PG's changes; appended YB's yb_* fields. Added a YB_TODO_PG19MERGE similar to above. + - IndexOptInfo amcostestimate signature + YB: + - PG changed amcostestimate declaration. + - YB added 6 yb fields. + - Kept PG's full signature; appended YB's 6 yb fields. + - ParamPathInfo: + - PG added `Bitmapset *ppi_serials`. + - YB added `Relids yb_ppi_req_outer_batched`. + - Kept both. + - Path struct comment + tail (2 conflicts): + - PG added to the comment above the struct and reformatted the `pathkeys` comment. + - YB also added to the comment and added additional yb fields. + - Kept both comments + YB's fields. + - RestrictInfo: + - PG added `pg_node_attr(equal_ignore)` to left/right_hasheqoperator. + - YB added `bool yb_pushable` and `List *yb_batched_rinfo`. + - Kept both. + +- src/include/nodes/plannodes.h: + - all 8 conflicts: + - PG reformatted comments to live on their own lines above each field (and added some new fields). + - YB used the old inline-comment format and appended its own fields. + - Took PG's changes; appended YB's fields (with the new comment format). + +- src/backend/utils/adt/timestamp.c: + - top-of-file YB include block: + - PG removed `__FAST_MATH__` guard and `SAMESIGN` macro. + - YB added includes. + - Kept YB's includes. + - pg_fallthrough vs yb_switch_fallthrough (7 conflicts): kept `pg_fallthrough`. + - End-of-file functions: kept both PG and YB functions. + +- src/bin/pg_upgrade/pg_upgrade.c: + - includes: + - PG added a macro and some forward decls. + - YB added an include. + - Kept both. + - setup() forward decl: + - PG added create_logical_replication_slots and create_conflict_detection_slot forward decls, and removed the live_check parameter. + - YB added yb_execute_extension_updates. + - Kept PG's changes and YB's yb_execute_extension_updates. + - main() GetDataDirectoryCreatePerm: + - PG changed pg_fatal's format to use %m specifier. + - YB wrapped PG logic in `if (!(is_yugabyte_enabled() && user_opts.check))` with an else branch for the YB online-upgrade flow. + - Kept YB's wrapping; applied PG's %m format change in both branches. + - main() check_cluster_versions / get_sock_dir: + - PG dropped the `live_check` arg from get_sock_dir and from check_cluster_compatibility. + - YB wrapped check_cluster_versions/get_sock_dir in `if (is_yugabyte_enabled()) yb_check_cluster_versions(); else { ... }` and gated check_cluster_compatibility with `!is_yugabyte_enabled()`. + - Kept YB's wrapping; applied PG's signature changes. + - main() destructive changes block: + - PG added set_new_cluster_char_signedness and removed the `/* New now using xids of the old system */` comment. + - YB wrapped the entire block in `if (!is_yugabyte_enabled())` . + - Split YB's guard so set_new_cluster_char_signedness runs unconditionally between two `!is_yugabyte_enabled()` guards (one wrapping copy_xact_xlog_xid, the other wrapping start_postmaster); dropped PG-removed comment. + - main() post-restore finalization block (2 conflicts): + - PG made changes to this block (added another transfer mode TRANSFER_MODE_SWAP, migrated logical replication slots, etc.) + - YB moved all of the PG logic under a `if (!is_yugabyte_enabled())`. + - Kept PG's new code in `if (!is_yugabyte_enabled())`. + - create_new_objects pg_restore: + - PG added `--transaction-size=%d` above `--dbname postgres`. + - YB changed to `--dbname yugabyte`. + - Kept PG's --transaction-size and YB's `--dbname yugabyte`. + - end-of-file functions: + - PG helpers. + - YB added yb_execute_extension_updates. + - Kept both. + +- src/bin/pgbench/pgbench.c: + - includes + globals (2 conflicts): + - PG added a comment and changed main_pid to static. + - YB added some includes and YB globals. + - Kept both. + - pg_fallthrough vs yb_switch_fallthrough (3 conflicts): Kept `pg_fallthrough;`. + - initPopulateTable: + - PG rewrote the COPY-statement construction (uses the `table` parameter). + - YB renamed the target tables from `pgbench_*` to `ysql_bench_*`. + - Took PG's changes. + - GetTableInfo error message: + - PG changed to backslash-quoted `\"search_path\"`. + - YB changed to `ysql_bench_accounts`. + - Combined: YB's `ysql_bench_accounts` with PG's `\"search_path\"` quoting. + - main long_options / switch (2 conflicts): + - PG added exit-on-abort/debug/continue-on-error at slots 16/17/18. + - YB added yb-metrics-bind-address/port and yb-connection-init-sql at slots 16/17/18. + - Kept PG's three options at slots 16/17/18 and shifting YB's three to slots 19/20/21; updated the matching `case 19/20/21:` arms in pgbench's getopt_long switch. + +- src/backend/utils/cache/catcache.c: + - CatalogCacheComputeHashValue / CatalogCacheComputeTupleHashValue fallthroughs (4 conflicts): fallthrough pattern + - CatCachePrintStats (3 conflicts): + - PG changed types from `long` to `uint64` and used PRIu64 format strings. + - YB added a `long yb_cc_size_bytes`, changed log level to `yb_log_level`, added yb_cc_size_bytes / yb_cc_size to the debug output. + - Kept PG's uint64 counters / PRIu64 formats and YB's yb_log_level + yb_cc_size accounting. + - SearchCatCache yb_cc_is_fully_loaded gate: + - PG moved the `memcpy(cur_skey, ...)` and the preceding comment block out of the do-while retry loop, updated the preceding comment, changed `IndexScanOK(cache, cur_skey)` to `IndexScanOK(cache)`, etc. + - YB wrapped the entire block in an `if` block, adding YB miss counters and a yb_debug_log_catcache_events block inside the gate before the existing scan; YB made no changes to the loop internals. + - Kept YB's gate + miss counters + debug log; PG's updates all apply inside the gate. + - CatalogCacheCreateEntry (2 conflicts): + - PG switched to using MemoryContextAlloc. + - YB switched to `oldcxt = MemoryContextSwitchTo(CacheMemoryContext)` plus palloc and bumped yb_cc_size_bytes under #ifdef CATCACHE_STATS. + - Kept PG's changes; appended YB's. + - RelationHasCachedLists vs ResourceOwner callbacks: + - PG added `/* ResourceOwner callbacks */` comment. + - YB commit e89d75bc16af0e819a56edd75126ba0ccc42174d added the `RelationHasCachedLists` helper. + - Kept both. + +- src/backend/utils/cache/relcache.c: + - RelationRebuildRelation nailed rel branch: + - PG commit 2b9b8ebbf8a2f621d1cecc4db759700f9ce6ce66 split RelationClearRelation into a smaller RelationClearRelation, RelationRebuildRelation and RelationInvalidateRelation. + - YB added a yb_debug_log_catcache_events "Delete relcache entry..." log inside the PG `if (!rebuild) { delete }` block. + - Followed PG's split (RelationReloadNailed in the nailed-rel branch of RelationRebuildRelation); ported YB's delete log into the new RelationClearRelation. + - RelationRebuildRelation rebuild logging: + - PG added `Assert(relation->rd_rel->relkind == newrel->rd_rel->relkind);` (in the new RelationRebuildRelation from the split via PG commit 2b9b8ebbf8a2f621d1cecc4db759700f9ce6ce66). + - YB added a yb_debug_log_catcache_events Rebuild log line. + - Kept both. + - YbRelationCacheInvalidate / RelationCloseSmgrByOid: + - PG commit 21d9c3ee4ef74e2229341d39811c97f85071c90a removed RelationCloseSmgrByOid. + - YB added YbRelationCacheInvalidate. + - Kept YB's YbRelationCacheInvalidate. + - RelationBuildLocalRelation: + - PG renamed `InvalidOid` to `InvalidRelFileNumber` and `relfilenode` to `relfilenumber` in RelationMapUpdateMap call. + - YB wrapped the map-update in `if (!IsYBRelation(rel))`. + - Kept PG's renamed names and YB's IsYBRelation guard. + - RelationSetNewRelfilenumber signature: + - PG renamed RelationSetNewRelfilenode to RelationSetNewRelfilenumber. + - YB added `bool yb_copy_split_options, YbOptSplit *preserved_index_split_options` params. + - Kept PG's name + YB's two extra params. + - RelationSetNewRelfilenumber drop-old / create-new blocks: + - PG renamed rd_node to rd_locator, newrnode to newrlocator, table_relation_set_new_filenode to table_relation_set_new_filelocator, etc. + - YB wrapped PG code into the `else` block of `if (IsYBRelation(relation)) { /* DocDB drop+create */ } else { ... }` and inverted the order in the else (create-new before drop-old) -- order flip introduced by YB merge commit 5627af5d6009b6d5b14bbbb552f6fd2a9ae43c3a. + - Followed PG inside YB's else: drop-old block first, create-new second, all with PG's new names. + - RelationGetFKeyList: + - PG added `info->conenforced = constraint->conenforced;`. + - YB added `info->ybconindid = constraint->conindid;`. + - Kept both writes. + - RelationGetIndexList: + - PG added `relation->rd_ispkdeferrable = pkdeferrable;` and a `&& !pkdeferrable` clause. + - YB widened the predicate to also accept YB_REPLICA_IDENTITY_CHANGE. + - Kept PG's pkdeferrable handling and widening to also accept YB_REPLICA_IDENTITY_CHANGE. + - RelationGetIndexAttrBitmap attr_offset / *attrs (3 conflicts): + - PG commit 19d8e2308bc51ec4ab993ce90077342c915dd116 split the single `indexattrs` bitmap into `hotblockingattrs` / `summarizedattrs`, selected per index via a `Bitmapset **attrs` pointer; uses `attrnum - FirstLowInvalidHeapAttributeNumber` offset. + - YB added `attrnum - attr_offset` (where `attr_offset = YBGetFirstLowInvalidAttributeNumber(relation)`) and switched `pull_varattnos` -> `pull_varattnos_min_attr(..., attr_offset + 1)`. + - Combined: PG's `*attrs` indirection + YB's attr_offset (`*attrs = bms_add_member(*attrs, attrnum - attr_offset)`), and `pull_varattnos_min_attr(..., attrs, attr_offset + 1)`. + +- src/bin/initdb/initdb.c: + - functions below includes and macros: + - PG added save_global_locale_t typedef and save/restore_global_locale helpers. + - YB added IsEnvSet / IsYugaByteGlobalClusterInitdb / IsYugaByteLocalNodeInitdb / YBIsMajorUpgradeInitDb / yb_pclose_check helpers. + - Kept both. + - test_config_settings: + - PG added AV_SLOTS_FOR_CONNS. + - YB added a comment about trying 300 connections first. + - Kept PG's AV_SLOTS_FOR_CONNS macro and then YB's trial_conns comment. + - setup_config (4 conflicts): + - PG changed pg_hba creation: removed the HAVE_UNIX_SOCKETS / HAVE_IPV6 ifdefs, switched from `::1` to `::1/128`, dropped the trailing `free(conflines)`. + - YB wrapped the entire pg_hba creation block in `if (!IsYugaByteGlobalClusterInitdb() && !IsYugaByteLocalNodeInitdb())` (skip when running under YB). + - Kept YB's gate with all of PG's changes inside it. + - bootstrap_template1 bki_lines (2 conflicts): + - PG added DATLOCALE + ICU_RULES replacements and removed FLOAT8PASSBYVAL + ICU_LOCALE replacements. + - YB wrapped the bki_lines substitution block in `if (!IsYugaByteLocalNodeInitdb())`. + - Kept YB's gate with PG's changes inside. + - setup_depend pg_stop_making_pinned_objects / VACUUM: + - PG commit 1bd47d0dca9f63dc26abc00d4912857440cd101c collapsed the `pg_depend_setup[]` array + foreach loop into a single inline `PG_CMD_PUTS("SELECT pg_stop_making_pinned_objects();...")`. + - YB added a `strncmp(*line, "VACUUM", 6) == 0 continue` skip inside the loop. The array only ever contained pg_stop_making_pinned_objects (no VACUUM), so YB's skip was dead code. + - Adopted PG's compact form; YB's dead VACUUM-skip drops with the loop. + - setup_collation pg_import_system_collations: + - PG changed the comment. + - YB wrapped the call in `if (!IsYugaByteGlobalClusterInitdb() || YBIsCollationEnabled())`. + - Kept PG's trimmed comment inside YB's gate. + - make_template0 (2 conflicts): + - PG commit 1bd47d0dca9f63dc26abc00d4912857440cd101c collapsed the `template0_setup[]` array + foreach loop into inline `PG_CMD_PUTS` calls for CREATE DATABASE, UPDATE template0 collversion NULL, UPDATE template1 collversion, REVOKE x2, COMMENT, and VACUUM pg_database. + - YB added a YB-mode dispatch over the `template0_setup[]` array: under `IsYugaByteGlobalClusterInitdb()`, emit only [0] (CREATE, replaced by an env-OID PG_CMD_PRINTF when YBIsMajorUpgradeInitDb), [2] (UPDATE template1 collversion), [3] (REVOKE template1), [4] (REVOKE template0); skip [1] (UPDATE template0 collversion NULL), [5] (COMMENT), [6] (VACUUM). + - Followed PG's inline form. Kept YB's CREATE-DATABASE branch (PG_CMD_PRINTF with env OID for major upgrade, PG's PG_CMD_PUTS otherwise). Each of [1] (UPDATE template0 collversion NULL), [5] (COMMENT), [6] (VACUUM) is gated with `if (!IsYugaByteGlobalClusterInitdb())` to match YB's skip behavior. + - setlocales: + - PG updated the `set empty lc_ ...` comment + - YB added a block that prepopulates `locale` and `lc_collate = C`. + - Kept YB's locale defaulting block before PG's updated comment. + +- src/backend/postmaster/postmaster.c: + - top-of-file declarations: + - PG removed the `PgArchStartupAllowed` macro + - YB added a `CleanupKilledProcess` declaration. + - Kept YB's declaration; dropped the `PgArchStartupAllowed` macro (no callers in the merged tree). + - PostmasterMain optarg -C/-D (2 conflicts): + - PG renamed `optarg` accessor to `optctx.optarg`. + - YB wrapped the optarg with `yb_postmaster_strdup`. + - Combined: `yb_postmaster_strdup(optctx.optarg)`. + - PostmasterMain wal_level / max_wal_senders error: + - PG changed the error message to use a quoted identifier. + - YB added a `!YBIsEnabledInPostgresEnvVar()` guard on the max_wal_senders branch (adjacent line conflict). + - Kept both: PG's quoted-identifier message + YB's guard. + - ServerLoop pre-loop init: + - PG commit 7389aad63666a2cac18cd6d7496378d7f50ef37b removed `nSockets = initMasks(&readmask)`. + - YB added APPLE/linux env-var setup. + - Kept YB's env-var setup. + - ServerLoop event handling: + - PG commit 7389aad63666a2cac18cd6d7496378d7f50ef37b restructured the loop. + - YB added #ifdef __APPLE__ block. + - Kept PG's changes with a YB_TODO_PG19MERGE. + - ServerLoop missing-process restarts: + - PG collapsed per-process restart logic into `LaunchMissingBackgroundProcesses()`. + - YB added a guard around the autovac launcher start. + - Took PG's changes with a YB_TODO_PG19MERGE. + - ServerLoop shutdown condition: + - PG removed the `SendStop` check. + - YB added `(YBIsEnabledInPostgresEnvVar() && Shutdown >= FastShutdown)`. + - Kept YB's clause; dropped the PG-removed SendStop reference. + - initMasks / ProcessStartupPacket mega-block: + - PG commit 05c3980e7f473ac2061dad9bbb7a9f0ede0279d9 and others removed/moved these functions to backend_startup.c. + - YB made changes to several functions. + - Added a YB_TODO_PG19MERGE. Moved `YbProcessStartupPacket` (a wrapper around the `ProcessStartupPacket`) to backend_startup.c. + - process_pm_reload_request SIGHUP block: + - PG replaced the per-child signal_child with `SignalChildren(SIGHUP, btmask_all_except(B_DEAD_END_BACKEND))`. + - YB added conn_mgr SIGHUP LCV logic. + - Kept YB conn_mgr block + PG's `SignalChildren(...)` in place of the per-child loop. + - process_pm_child_exit start of the loop: + - PG commit a78af0427015449269fb7d9d8c6057cfcb740149 changed the logic to use PMChild slots. PG commit 7389aad63666a2cac18cd6d7496378d7f50ef37b also renamed reaper to process_pm_child_exit. + - YB added a PGPROC scan with crash-containment checks (ybLWLockAcquired, ybSpinLocksAcquired, ybInitializationCompleted, ybTerminationStarted, ybEnteredCriticalSection). + - Kept PG's changes with a YB_TODO_PG19MERGE. + - process_pm_child_exit StartWorkerNeeded: + - PG collapsed per-process restart logic into `LaunchMissingBackgroundProcesses()` and replaced it with `StartWorkerNeeded = true;` (commit 3354f85284dc5439c25b57e002e62a88490aca1e) + - YB added a guard around the autovac launcher start. + - Took PG's changes with a YB_TODO_PG19MERGE. + - process_pm_child_exit CleanupBackend: + - PG commit a78af0427015449269fb7d9d8c6057cfcb740149 changed the logic to use PMChild slots. + - YB added a `foundProcStruct` check and `yb_pgstat_clear_entry_pid` call. + - Took PG's changes; added YB_TODO_PG19MERGE. + - CleanupBackgroundWorker / CleanupKilledProcess block: + - PG commit 28a520c0b77325a97bafd0f57cc7bd0dd523b71e collapsed `CleanupBackend` + `CleanupBackgroundWorker` into a single `CleanupBackend(PMChild *bp)`. + - YB made no changes to CleanupBackgroundWorker; YB added a new `CleanupKilledProcess` helper immediately after it. + - Took PG's changes; added YB_TODO_PG19MERGE for `CleanupKilledProcess` to change backend id usages. + - CleanupBackend process-name + LogChildExit relocation: + - PG commit 28a520c0b77325a97bafd0f57cc7bd0dd523b71e combined `CleanupBackend` and `CleanupBackgroundWorker`. As a result, the `LogChildExit` at the top of this function was removed; new call sites were added later in the function body; PG also added the procname block at the top of CleanupBackend. + - YB had wrapped the early `LogChildExit` in `if (YBIsEnabledInPostgresEnvVar())`, elevated the level for abnormal exits to `WARNING`, and added `yb_report_query_termination`. + - Took PG's procname construction at the top, keeping YB's `yb_report_query_termination` SIGKILL/SIGSEGV block guarded by `YBIsEnabledInPostgresEnvVar()`, and applying YB's `DEBUG2 : WARNING` elevation guarded by `YBIsEnabledInPostgresEnvVar()` at all the new call-sites in this function. + - HandleChildCrash restructure into HandleFatalError (3 conflicts): + - PG changed the body of `HandleChildCrash` across several commits: d239c1a8e5b6ac467b3479bf3840e3d297a40bef, 8edd8c77c88e75822334ccb8376d2c151d6e5615, 463a2ebd9fda4fa94833838d0a372f7fd53b5b8a and others. + - YB modified the old `HandleChildCrash` body. + - Took PG's changes with a YB_TODO_PG19MERGE. + - BackendStartup tail: + - PG removed `BackendList` / `ShmemBackendArrayAdd`. + - YB added `SetOomScoreAdjForPid` after the registration. + - Kept YB's `SetOomScoreAdjForPid`. + - BackendInitialize: + - PG commits 05c3980e7f473ac2061dad9bbb7a9f0ede0279d9 and f1baed18bc3db50c72bfb00b6247b47689158445 relocated these functions to backend_startup.c / launch_backend.c. + - Of these, only `BackendInitialize` had YB modifications + - Added a YB_TODO_PG19MERGE for `BackendInitialize`. + - StartChildProcess: + - PG added `AssignPostmasterChildSlot` and replaced the `AuxProcType` argument with a `BackendType` (commit 067701f57758f9baed5bd9d868539738d77bfa92). + - YB added an early-return keyed on the old `AuxProcType` values. + - Took PG's `AssignPostmasterChildSlot`; rewrote YB's early-return against the new `BackendType` (`B_*`) values. + - MaxLivePostmasterChildren: + - PG commits a78af0427015449269fb7d9d8c6057cfcb740149 and others removed some functions; moved others to pmchild.c / bgworker.c. + - YB made changes to BackgroundWorkerInitializeConnection, BackgroundWorkerInitializeConnectionByOid and added a helper YbBackgroundWorkerInitializeConnectionByOid. + - Added a YB_TODO_PG19MERGE. Commented out the reference to YbBackgroundWorkerInitializeConnectionByOid in parallel.c. + - StartBackgroundWorker: + - PG commit aafc05de1bf5c0324cb5e690c6742118c1ac4af6 removed the fork and the switch statement. + - YB added `SetOomScoreAdjForPid(MyProcPid, rw->rw_worker.bgw_oom_score_adj)`. + - Took PG's changes with a YB_TODO_PG19MERGE. + - bgworker_should_start_now fallthroughs (2 conflicts): + - PG used `pg_fallthrough`. + - YB used `yb_switch_fallthrough()`. + - Took PG's `pg_fallthrough`. + +- contrib/pg_stat_statements/pg_stat_statements.c, contrib/pg_stat_statements/Makefile, contrib/pg_stat_statements/pg_stat_statements.control: + - Not resolved. Conflict markers preserved in all three files. + - Build-skipped temporarily by removing `pg_stat_statements` from contrib/Makefile SUBDIRS and from contrib/meson.build, so the unresolved markers don't block the cluster build. + - Full resolution deferred to a dedicated pass. + +- src/include/commands/variable.h: + - PG commit 0a20ff54f5e66158930d5328f89f087d4e9ab400 split `guc.c`; PG-side declarations moved from `variable.h` to `src/include/utils/guc_hooks.h` and the file was removed. + - YB added YB-specific GUC hook declarations. + - Kept `variable.h` as a slimmed YB-only header containing just the YB declarations. Retained the include in `guc.c`, `pg_yb_utils.c` and `postgres.c`. + +- src/backend/utils/misc/guc.c: + - includes: + - PG removed PG_KRB_SRVTAB macro. + - YB added includes. + - Kept YB includes. + - declarations: + - This conflict is due to a massive refactor. A lot of YB code is interleaved with PG code that was removed from this file. The resolution is broken into the following parts: + - Part 1: + - PG commit 0a20ff54f5e66158930d5328f89f087d4e9ab400 moved check/assign/show hook functions out of guc.c to the variable's home module (and removed their forward declarations). + - YB added forward declarations for YB functions and hooks. + - Kept YB's forward decls in this file; added YB_TODO_PG19MERGE for cleanup. + - Part 2: + - PG commit 0a20ff54f5e66158930d5328f89f087d4e9ab400 moved enum-options arrays out of this file to guc_tables.c + - YB added YB-specific enum-option arrays (yb_batch_detection_mechanism_options, yb_read_after_commit_visibility_options,yb_sampling_algorithm_options, + yb_cost_model_options, + yb_qpm_track_options, + yb_cache_replacement_algorithm_options, + yb_qpm_plan_format_options) + - Moved YB's enum-option arrays to guc_tables.c. + - Part 3: + - PG commit 0a20ff54f5e66158930d5328f89f087d4e9ab400 moved the GUC globals and the static "dummies" section into guc_tables.c. + - YB added `yb_log_min_backtraces` to the GUC globals and a section of YB GUCs below the "dummies" section. Specifically: + - yb_enable_memory_tracking + - yb_effective_transaction_isolation_level_string + - yb_xcluster_consistency_level_string + - yb_read_time_string + - yb_neg_catcache_ids_string + - yb_conn_mgr_modifying_defaults; + - yb_test_skip_binding_scan_keys + - yb_bypass_cond_recheck + - yb_pushdown_is_not_null + - yb_pushdown_strict_inequality + - Moved YB's GUCs to guc_tables.c. + - Part 4: + - PG commit 0a20ff54f5e66158930d5328f89f087d4e9ab400 moved config_type_names to guc_tables.c + - YB added "oid" to config_type_names + - Added "oid" to config_type_names in guc_tables.c. + - The remaining code in the conflict is PG code that was untouched by YB and moved by PG's refactor. + - Contents of GUC tables: + - This conflict is due to a massive refactor. PG commit 0a20ff54f5e66158930d5328f89f087d4e9ab400 moved the GUC definitions out of guc.c into guc_tables.c, and 63599896545c7869f7dd28cd593e8b548983d613 then moved them into guc_parameters.dat (Perl hash format processed by gen_guc_tables.pl into guc_tables.inc.c). Two kinds of YB content interleaved with the moved PG content: YB-modified PG GUC entries, and YB-added GUC entries (plus all surrounding YB comments). + - Resolution shape: YB-added per-type GUC entries are kept as trimmed `static struct config_X ConfigureNames[]` arrays in `guc.c`; YB modifications to existing PG GUCs are applied directly to the corresponding `guc_parameters.dat` entries with a `# YB` comment. + - Part 1: ConfigureNamesBool + - YB-modified PG bool GUCs (4): + - enable_partitionwise_aggregate (boot_val false -> true) + - jit (no-op; PG19 already defaults to false) + - transaction_deferrable (add assign_hook assign_transaction_deferrable) + - transaction_read_only (add assign_hook assign_transaction_read_only) + - YB-added bool GUCs: 140 (kept inline in guc.c's `ConfigureNamesBool[]`). + - Part 2: ConfigureNamesInt + - YB-modified PG int GUCs (4): + - lock_timeout (add assign_hook YBCSetLockTimeout) + - max_connections (add show_hook yb_show_maxconnections) + - max_replication_slots (add assign_hook yb_assign_max_replication_slots) + - temp_file_limit (boot_val -1 -> 1024 * 1024) + - YB-added int GUCs: 48 (kept inline in guc.c's `ConfigureNamesInt[]`). + - Part 3: ConfigureNamesOid + - YB-added GUC type with no PG analog; `guc_parameters.dat` only models bool/int/real/string/enum. Kept the manual `static struct yb_config_oid ConfigureNamesOid[]` in `guc.c` with a YB_TODO_PG19MERGE. + - Part 4: ConfigureNamesReal + - YB-modified PG real GUCs (2): + - parallel_setup_cost (boot_val DEFAULT_PARALLEL_SETUP_COST -> YB_DEFAULT_PARALLEL_SETUP_COST) + - parallel_tuple_cost (boot_val DEFAULT_PARALLEL_TUPLE_COST -> YB_DEFAULT_PARALLEL_TUPLE_COST) + - YB-added real GUCs: 7 (kept inline in guc.c's `ConfigureNamesReal[]`). + - Part 5: ConfigureNamesString + - YB-modified PG string GUCs (0): none. + - YB-added string GUCs: 9 (kept inline in guc.c's `ConfigureNamesString[]`). + - Part 6: ConfigureNamesEnum + - YB-modified PG enum GUCs (4): + - default_toast_compression (boot_val DEFAULT_TOAST_COMPRESSION -> TOAST_LZ4_COMPRESSION) + - default_transaction_isolation (add check_hook check_yb_default_xact_isolation) + - transaction_isolation (add assign_hook yb_assign_XactIsoLevel) + - wal_level (boot_val WAL_LEVEL_REPLICA -> WAL_LEVEL_LOGICAL; PG's YB-NOTE comment about logical-replication client compatibility preserved as a `# YB:` comment in .dat) + - YB-added enum GUCs: 8 (kept inline in guc.c's `ConfigureNamesEnum[]`). + - PG commit a13833c35f9e07fe978bf6fad984d6f5f25f59cd changed GUC structs' layout. The ConfigureNames* arrays use the old layout and won't compile against PG19's structs. Wrapped them in #if 0. + - map_old_guc_names: + - Both sides added entries; kept both. + - guc_strdup: + - PG commit 407b50f2d421bca5b134a0033176ea8f8c68dc6b changed to guc_malloc + - YB added __lsan_ignore_object(data) + - Kept PG's changes; added YB's __lsan_ignore_object. + - string_field_used: + - PG changed the for loop + - YB added a `ysql_conn_mgr_saved_default` check above the for loop (adjacent line conflict) + - Kept PG's for loop with YB's block. + - extra_field_used: + - PG simplified to a single `gconf->reset_extra` read (removed the switch) and changed the for loop. + - YB added PGC_OID case to the switch, plus a `ysql_conn_mgr_saved_default` check + - Took PG; ported YB's `ysql_conn_mgr_saved_default` check; ported the PGC_OID branch as a single `gconf->vartype == PGC_OID` guarded check against `((struct yb_config_oid *) gconf)->reset_extra`. + - build_guc_variables: + - PG commit 0a20ff54f5e66158930d5328f89f087d4e9ab400 reworked from per-type ConfigureNamesBool/Int/Real/String/Enum arrays + sorted `guc_variables` array to a single unified `ConfigureNames[]` + hash table (`guc_hashtab`). + - YB added a ConfigureNamesOid loop to the set of per-type loops. + - Took PG's changes with a YB_TODO_PG19MERGE (since YB GUCs are #if 0-ed out). + - find_option / define_custom_variable: + - PG made changes at the top of the function + - YB added a `#ifdef ADDRESS_SANITIZER` block creating a placeholder `struct config_generic` with `name` set. + - Kept PG's changes, added a YB_TODO_PG19MERGE. + - ResetAllOptions / AtEOXact_GUC / ReportChangedGUCOptions: + - PG (f13b2088fa2d4455936e65459b77698a4452f932) changed GUC reporting. + - YB commits eda482d8bb7464cca3f6d74102a7697407db7e27 and others added connection manager logic. + - Took PG's changes with a YB_TODO_PG19MERGE. + - ReportGUCOption comment: + - PG trimmed the comment. + - YB added to the comment. + - Took PG's new comment with YB's addition. + - ReportGUCOption pq_beginmessage: + - PG changed 'S' to PqMsg_ParameterStatus in pq_beginmessage. + - YB commit eda482d8bb7464cca3f6d74102a7697407db7e27 and others added a connection manager if/else block. + - Kept YB's block with PG's changes in the else branch. + - ReportGUCOption tail: + - PG removed `record->status &= ~GUC_NEEDS_REPORT` + - YB added updates to `record->status` guarded by `YbIsClientYsqlConnMgr` + - Took PG's changes; added YB_TODO_PG19MERGE. + - set_config_with_handle pg_fallthrough rename (trivial): + - PG modernized `/* FALLTHROUGH */` + YB's `yb_switch_fallthrough()` macro into PG's new `pg_fallthrough` macro. + - Took PG's `pg_fallthrough`. + - set_config_with_handle changeVal block (5 sites): + - PG commit f13b2088fa2d4455936e65459b77698a4452f932 changed conf->gen.source/scontext/srole to record.*. + - YB added `if (gucReset) conf->gen.status |= YB_GUC_VALUE_RESET;` after the source/scontext assignments. + - Took PG's record.* form and set_guc_source(), porting YB's gucReset block to use record->status. Also update the PGC_OID changeVal block that was auto-merged without conflict. + - set_config_with_handle session_authorization: + - PG changed `set_config_option_ext` to `set_config_with_handle` and `conf->gen.name` to `record->name`. + - YB added a block to set `yb_ysql_conn_mgr_sticky_guc`. + - Took PG's rename; kept YB's sticky block with `record->flags` instead of `conf->gen.flags`. + - set_config_with_handle final report-gate: + - PG (f13b2088fa2d4455936e65459b77698a4452f932) changed GUC reporting. + - YB added connection manager logic. + - Took PG's changes; added YB_TODO_PG19MERGE. + - ExecSetVariableStmt: + - PG commit 0a20ff54f5e66158930d5328f89f087d4e9ab400 moved `ExecSetVariableStmt` (and other functions) to `guc_funcs.c`, leaving an empty stub on this side of the conflict. + - YB made modifications to `ExecSetVariableStmt`. + - Kept a YB_TODO_PG19MERGE; the YB modifications need to be re-ported into `guc_funcs.c`'s `ExecSetVariableStmt`. + - DefineCustomStringVariable: + - PG commit a13833c35f9e07fe978bf6fad984d6f5f25f59cd changed `init_custom_variable` to return `struct config_generic *` and introduced `var->_string.*` member access. + - YB added `var->gen.flags |= GUC_YB_CUSTOM_STICKY;`. + - Took PG's form; adapted the YB sticky line to `var->flags |= GUC_YB_CUSTOM_STICKY`. + - ShowGUCOption: + - PG commit 0a20ff54f5e66158930d5328f89f087d4e9ab400 and others removed/moved many functions and updated the comment for `ShowGUCOption`. + - YB made changes to `GetConfigOptionsByNum` (removed by PG). + - Took PG's comment update; added YB_TODO_PG19MERGE. + - call_oid_check_hook: + - PG changed the signature for call_real_check_hook. + - YB added call_oid_check_hook above. + - Kept PG's new signature; kept YB's hook. + - check/assign/show hook implementations: + - This conflict is related to the one described under "declarations". + - PG commit 0a20ff54f5e66158930d5328f89f087d4e9ab400 moved check/assign/show hook functions out of guc.c. YB added YB-specific hook functions. There were no YB changes made to PG hooks. + - Kept YB's hooks. + +Build fixes: + +- python/yugabyte/build_postgres.py: + - skip third party extensions build: + - Third party extensions have not been merged, skip them for now. + - genbki step target moved: + - PG commit 6ab2e8385d55e0b73bb8bbc41d9c286f5f7f357f moved all bki-generation machinery (including the `bki-stamp` target) from src/backend/catalog/Makefile into src/include/catalog/Makefile. The merge resolution put `yb_bki-stamp` in the new location alongside `bki-stamp` (see pg19mergeresolutions.md src/backend/catalog/Makefile entry). + - Changed the genbki invocation in build_postgres.py to `make -C src/include/catalog generated-headers` + +- src/postgres/contrib/passwordcheck/passwordcheck_extra.c + - pg_md5_encrypt call + - Fixed argument to match updated signature. + +- src/postgres/contrib/postgres_fdw/postgres_fdw.c: + - YbGlobalViewReadExecScan libpqsrv_PGresult wrapper: + - PG commit 7d8f5957792421ec3bb9d1b9b6ca25d689d974b7 added a memory context safe PGresult wrapper (see libpq/libpq-be-fe-helpers.h -- `#define PGresult libpqsrv_PGresult`) + - Wrapped return values with `libpqsrv_PQwrap(...)`. + +- src/Makefile_ybpostgres.shlib: + - PG droppped the `RANLIB` Make variable from Makefile.shlib + - Removed some `RANLIB` references in this file + - Notes for future cleanup: + - This file seems to be a copy of Makefile.shlib, should these it be resynced? + - 3 other `$(RANLIB)` sites remain + +- src/backend/access/common/reloptions.c: + - PointerIsValid + - Removed by PG commit a5b35fcedb542587e7d8b8fcd21a2e0995b82d2f, replaced with `!= NULL`. + - Update call site in ybExcludeNonPersistentReloptions. + - PG commit 69f98fce5bfb82260c66bdae88b6293146cf79ec removed the `values` name from the union, so update YB's `option->values.oid_val`. + - Added pg_yb_utils.h. + +- src/backend/access/common/toast_compression.c: + - pglz_compress_datum DatumGetPointer wrap: + - PG15 initial merge added an unnecessary DatumGetPointer wrap (not present in vanilla PG15). Remove it. + +- src/backend/access/index/indexam.c: + - yb_free_dummy_baserel_index pfree: + - Root cause: PG commit bc6374cd76abb2e6a48c4b57c0b5a7baa5babd67 changed IndexAmRoutines to be statically allocated structs. Remove the pfree for `relation->rd_indam`. + +- src/backend/access/ybgin/ybginget.c: + - PG commit 944e81bf99db2b5b70b8a389d4f273534da73f74 changed `GinScanEntryData.matchResult` from `TBMIterateResult *` to `TBMIterateResult`. PG's analogous spot in ginget.c switched to `entry->matchResult.blockno = InvalidBlockNumber;`. + - Matched PG's pattern. + +- src/backend/access/ybgin/ybginutil.c: + - PG made `index_create()`'s `collationIds`, `opclassIds`, `coloptions` parameters `const`. + - Added const qualifiers to the parameters of ybginbindschema. + +- src/backend/bootstrap/bootparse.y: + - PG changed "oldNode" to "oldNumber". PG also extended the signature of `DefineIndex` to include `pstate` (ParseState) and `total_parts`. + - Changed `stmt->oldNode = InvalidOid` to `stmt->oldNumber = InvalidRelFileNumber`. Added the missing arguments to the DefineIndex call (`NULL` and `-1`) to match the other call sites in this file. + +- src/backend/catalog/aclchk.c: + - select_best_grantor call: + - PG commit dd1398f1378799acc60c3ed85d82439b2ff69141 changed the first parameter for `select_best_grantor` to `const RoleSpec *`. + - Updated the YB call site to use `istmt->grantor` like the other PG call sites. + - pg_aclmask: + - PG commit c727f511bd7bf3c58063737bcf7a8f331346f253 renamed the parameter from `table_oid` to `object_oid`. Update YB's `pg_tablegroup_aclmask` call. + - CatalogTupleDelete calls: + - Updated to match YB's signature. + +- src/backend/catalog/catalog.c: + - Added some includes. + - YbGetAllRelfilenodes: + - Changed `%lu` to`%llu` on the `SPI_processed` log message. + - DoesRelFileExist: + - `relpath()` returns `RelPathStr` instead of `char *` + - updated `rpath` type, removed pfree(). + - RelfileNode related renames: + - PG commit b0a55e43299c4ea2a9a8c757f9c26352407d0ccc renamed relfilenode and related fields. + - Fixed all instances to match the new PG naming. + (RelFileNode -> RelFileLocator rename and friends): + +- src/backend/catalog/heap.c: + - attcacheoff removed: + - PG commit 02a8d0c45253eb54e57b1974c8627e5be3e1d852 removed attcacheoff column. + - Removed references from YB's initializers + - Renamed relfilenode to relfilenumber (due to PG's RelFileNode rename) + - Updated CatalogTupleInsertWithInfo to pass false for `yb_shared_insert`. + +- src/backend/catalog/index.c: + - Removed `PointerIsValid` references, replaced with explicit null check. + - Updated `YBCCreateIndex` call site to use the new `opclassIds` parameter name. + +- src/backend/catalog/pg_enum.c: + - Updated CatalogTuplesMultiInsertWithInfo to pass false for `yb_shared_insert`. + +- src/backend/catalog/pg_publication.c: + - Added includes. + - Updated publication-relation helper calls in `yb_pg_get_publications_tables` to match the new names and signatures as per PG commit 96b37849734673e7c82fb86c4f0a46a28f500ac8. + +- src/backend/catalog/yb_catalog/yb_catalog_version.c: + - FuncnameGetCandidates call: + - Pass `NULL` for the new `int *fgc_flags` parameter. + - Changed `%lu` to `%llu` for SPI_processed. + +- src/backend/catalog/yb_catalog/yb_type.c: + - Used `DatumGetPointer` as needed. + - Used `%lld` for `bytes` (see PG commit 962da900ac8f0927f1af2fd811ca67fa163c873a). + +- src/backend/catalog/yb_genbki.pl: + - index declaration emit format: + - PG commit 226d0a6b989bec6cb0cc3bfc07df9fd417644782 split the index declaration into separate `table_name` and `index_decl` fields. PG's genbki.pl was updated to emit `declare ... index NAME OID on TABLE using AM(...)` by interpolating both fields. + - Changed yb_genbki.pl to match PG's genbki.pl. + +- src/backend/commands/analyze.c: + - Passed `true` for PG's `bool is_analyze` parameter in `vacuum_delay_point`. + - Passed `false` for YB's `bool yb_shared_insert` parameter in `CatalogTupleInsertWithInfo`. + +- src/backend/commands/async.c: + - PG removed attcacheoff; removed it from YB initializers + - ProcNumber / BackendId: + - PG commit 024c521117579a6d356050ad3d78fdc95e44eefa replaced BackendIds with 0-based ProcNumbers. + - Updated YB code to reflect the change. + - Updated `CheckSlotRequirements` and `ReplicationSlotAcquire` calls with arguments for new PG parameters. + - Fixed some pointer type mismatches. + +- src/backend/commands/copyfrom.c: + - PG reordered the parameters of ExecInsertIndexTuples and changed the per-call bool flags into a uint32 options bitmask. + - Updated the YB ExecInsertIndexTuples call site. + +- src/backend/commands/dbcommands.c: + - PG renamed the DefElem locals in `createdb()`. + - Updated YB code to use the new names. + +- src/backend/commands/event_trigger.c: + - Updated systable_inplace_update_finish to pass false for `yb_shared_insert`. + +- src/backend/commands/explain.c: + - ExplainPropertyText call: + - PG changed `ExplainProperty` to static. + - Updated to use the `ExplainPropertyText(label, buf, es)` wrapper instead (that calls `ExplainProperty` with `qlabel, NULL, value, false, es`). + - yb_instr: + - Added a YB_TODO_PG19MERGE and blocked off the stale code (refer to the merge conflict resolution notes for instrument.h). + +- src/backend/commands/functioncmds.c: + - Changed `check_is_member_of_role` to `check_can_set_role` as per PG commit commit 3d14e171e9e2236139e8976f3309a588bcc8683b. + - Changed `pg_namespace_aclcheck` to generalized `object_aclcheck` with a `NamespaceRelationId` argument. + +- src/backend/commands/indexcmds.c: + - Changed `pg_database_aclcheck` to generalized `object_aclcheck` with a `DatabaseRelationId` argument. + +- src/backend/commands/repack_worker.c: + - Added YB arguments to `ReplicationSlotCreate` (along with a YB_TODO_PG19MERGE for review). + +- src/backend/commands/sequence.c: + - Used `last_value` as argument in YBCInsertSequenceTuple call (refer to the merge conflict resolutio notes for sequence.c). + - Fixed a type mismatch: `ysql_sequence_cache_minval` is `int32_t*` (deferenced gives `int` not `int64`). + +- src/backend/commands/tablespace.c: + - Removed `PointerIsValid`, replaced with an explict null check. + +- src/backend/commands/tsearchcmds.c: + - Updated `CatalogTuplesMultiInsertWithInfo` calls to pass `false` for `bool yb_shared_insert`. + +- src/backend/commands/yb_builtin.c: + - PG commit 36b3d52459aecd4f8bc39a4604e42186c48aa9d2 removed both the HAVE_GETRUSAGE macro and the rusagestub.h shim. sys/resource.h is now unconditionally available. + - Replaced the `#ifndef HAVE_GETRUSAGE / #include "rusagestub.h" / #endif` block with `#include `. Removed the previous conditional sys/resource.h include. + +- src/backend/commands/yb_cmds.c + - Added an include. + - Added const qualifiers in a few places. + - Updated `build_attrmap_by_name` call with new arg. + - AT_*Recurse references: + - PG commit 840ff5f451cd9a391d237fc60894fea7ad82a189 got rid of recursion marker values in `AlterTableType`, it is signalled using `cmd->recurse` instead. + - Added a `cmd->recurse` check as appropriate. + +- src/backend/commands/yb_profile.c: + - Removed `uaRADIUS'` (PG commit a1643d40b308911cc725e62d3c5f7904b426aa09 removed RADIUS support). + +- src/backend/commands/yb_tablegroup.c: + - Added an include. + - Similar to functioncmds.c. + +- src/backend/executor/execExpr.c: + - PG renamed RowCompareExpr.rctype/ROWCOMPARE_EQ to cmptype/COMPARE_EQ. + +- src/backend/executor/execExprInterp.c: + - Changed bits8 to unit8 (per PG commit bab2f27eaaad77f799ecc224f9e11b09adb07d5a). + +- src/backend/executor/execIndexing.c: + - `attrs` no longer exists in `struct TupleDescData`; use `TupleDescAttr` instead. + - PG added a IndexScanInstrumentation* and a flags parameter to index_beginscan. + - Passed NULL for IndexScanInstrumentation* and 0 for flags. + +- src/backend/executor/execMain.c: + - Updated InstrAlloc call to pass a single arg (instrument_options); the old n=1 / async=false params are gone. + +- src/backend/executor/execPartition.c: + - `ExecInitRoutingInfo` no longer takes a rootRelInfo parameter + - Updated to use `mtstate->rootResultRelInfo->ri_ybUseIndexOnlyScanForIocRead`. + +- src/backend/executor/execProcnode.c: + - YbGetExecNodeSpanName switch: PG removed and renamed many node-tags. Removed the non-existent ones and added a default case with a YB_TODO_PG19MERGE. + +- src/backend/executor/execReplication.c: + - ExecCheckIndexConstraints call: Added the trailing `ybConflictSlot` argument with a YB_TODO_PG19MERGE. + +- src/backend/executor/functions.c: + - PG commit 0dca5d68d7bebf2c1036fd84875533afef6df992 refactored SQLFunctionCache. Updated `fcache->fname` to `fcache->func->fname`. + +- src/backend/executor/nodeIndexonlyscan.c: + - Updated ExecInitScanTupleSlot call to pass 0 for flags. + +- src/backend/executor/nodeIndexscan.c: + - PG renamed RowCompareExpr.rctype/ROWCOMPARE_EQ to cmptype/COMPARE_EQ. Updated YB code accordingly. + - Updated ExecInitScanTupleSlot call to pass 0 for flags. + +- src/backend/executor/nodeModifyTable.c: + - Added an include. + - ExecForPortionOfLeftovers: passed NULL for YB param blockInsertStmt in ExecInsert call. + - ExecUpdateAct: PG commit 7fee7871b4302e916577df130344060d0f9b8004 split ri_NumGeneratedNeeded into ri_NumGeneratedNeededI and ri_NumGeneratedNeededU. Changed ri_NumGeneratedNeeded to ri_NumGeneratedNeededU for now, added a YB_TODO_PG19MERGE. + - ExecUpdateAct: Removed updateCxt->updated assignment -- PG commit 362de947cd7e8c826d9b3c5dc2590348263ed3c1 removed the field. + - ExecInitMerge: build_attrmap_by_name calls -- passed false for yb_ignore_type_mismatch. + - ExecModifyTable: #if 0-ed context.relaction assignment -- PG commit c649fa24a42ba89bf5460c7110e4fc8eeca65959 removed the field, and seems to have replaced it with ModifyTableState.mt_merge_action. Added a YB_TODO_PG19MERGE. + - YbFetchColumnsMarkedForUpdate: + - PG commit a61b1f74823c9c4f79c95226a461f1e7a367764b (moved updatedCols from RangeTableEntry into RTEPermissionInfo) + - PG commit fb958b5da86da69651f6fb9f540c2cfb1346cdc5 (replaced ri_RootToPartitionMap with ri_RootToChildMap; reads now go through ExecGetRootToChildMap, which asserts ri_RootResultRelInfo != NULL). + - Kept the early-return gated on ri_RootResultRelInfo (in place of the old ri_RootToChildMap field check) so non-partition relinfos bail before the call. ExecGetRootToChildMap is invoked only inside the conditional, with a NULL-result fast path. + +- src/backend/executor/nodeTidscan.c: + - PG commit 2a600a93c7be5b0bf8cacb1af78009db12bc4857 changed Datum to uint64_t; added a uintptr_t * cast for ybctidList in HandleYBStatus call. + - Passed `0` for new parameter `flags` in ExecInitScanTupleSlot call. + +- src/backend/executor/nodeYbBatchedNestloop.c: + - Added includes. + - FlushTupleHash: updated to use helper TupleHashEntryGetAdditional. + - GetNewOuterTupleHash: + - Updated to use helper TupleHashEntryGetAdditional. + - Updated FindTupleHashEntry call with new args, added a YB_TODO_PG19MERGE. + - GetNewOuterTupleHash: + - Added a YB_TODO_PG19MERGE for FindTupleHashEntry call. + - Updated to use helper TupleHashEntryGetAdditional. + - AddTupleToOuterBatchHash: + - PG commit c106ef08071ad611fdf4febb3a8d2da441272a6d removed tablecxt and switched to tuplescxt. + - Removed the palloc0 for additional, updated to use helper TupleHashEntryGetAdditional, and added a YB_TODO_PG19MERGE for the allocation. + - FreeBatchHash, EndHash: changed tablecxt to tuplescxt. + - ExecEndYbBatchedNestLoop: removed ExecFreeExprContext (as per PG commit d060e921ea5aa47b6265174c32e1128cebdbc3df), added a YB_TODO_PG19MERGE. +- src/backend/executor/nodeYbBitmapIndexscan.c: + - Passed NULL for IndexScanInstrumentation* instrument in index_beginscan_bitmap call. +- src/backend/executor/nodeYbBitmapTablescan.c: + - Passed 0 for flags in ExecInitScanTupleSlot calls. + - ExecEndYbBitmapTableScan: same as ExecEndYbBatchedNestLoop above. +- src/backend/executor/nodeYbSeqscan.c: + - Same as above. + +- src/backend/libpq/auth.c: + - Updated `auth_failed` call sites to pass `FATAL` for the new `elevel` parameter. + - PG removed the `HAVE_UNIX_SOCKETS` build-config define (Unix sockets are now universally assumed). Removed the `#ifdef HAVE_UNIX_SOCKETS` / `#else Assert(false); #endif` wrapper. + +- src/backend/optimizer/path/allpaths.c: + - Removed T_UpperUniquePath as it no longer exists in PG. + +- src/backend/optimizer/path/costsize.c: + - Added an include. + - Removed delay_upper_joins assignment (field removed by PG). + - PG commit 462bb7f12851c215dfc21a88ae0ed4bf7fcb36a3 removed bms_first_member; fixed by using bms_next_member. + - Updated estimate_array_length call to pass `root` as an argument. + +- src/backend/optimizer/path/indxpath.c: + - Updated generate_join_implied_equalities calls to pass `NULL` for `sjinfo`. + - PG commit 24225ad9aafc576295e210026d8ffa9f50d61145 removed UpperUniquePath; changed YB code to use UniquePath with a YB_TODO_PG19MERGE. + - Removed argument for skip_lower_saop out parameter from build_index_paths calls. + - Silenced unsued warning for `yb_hash_code_on_left` with a YB_TODO_PG19MERGE. + +- src/backend/optimizer/path/pathkeys.c: + - Updated the args for make_pathkey_from_sortop with a YB_TODO_PG19MERGE. + +- src/postgres/src/backend/tcop/postgres.c: + - Added includes. + - YBPrepareCacheRefreshIfNeeded: passed false for synced_only in ReplicationSlotCleanup (added YB_TODO_PG19MERGE). + - Updated log_min_messages as per PG commit 38e0190ced714b33c43c9676d768cc6814fc662a. + - yb_is_dml_command: replication_scanner_* now take a yyscanner handle. + - Removed PointerIsValid usages (replaced with NULL checks). + - authn_id moved from Port to ClientConnectionInfo + +- src/postgres/src/backend/utils/activity/backend_status.c: + - Added includes. + - Added declaration for `found`. + - Changed beentry->st_backendType to MyBackendType. + - yb_pgstat_add_session_info: replaced MyAuxProcType / NotAnAuxProcess with MyBackendType / B_INVALID. + +- src/postgres/src/backend/utils/activity/pgstat_backend.c: + - Added YB_TODO_PG19MERGE: opt YB-only BackendTypes out of backend-stats tracking for now. + +- src/backend/utils/activity/pgstat_io.c: + - Same as above. + +- src/backend/utils/activity/pgstat_replslot.c: + - Pass false for `need_lock` in pgstat_report_replslot call. + +- src/backend/optimizer/plan/planner.c: + - Added an include. + - Added declaration for `lc`. + - Changed `%lu` to `UINT64_FORMAT`. + +- src/backend/optimizer/plan/setrefs.c + - fix_upper_expr gained `NullingRelsMatch nrm_match` before num_exec; inserted NRM_EQUAL at 6 YB call sites. + +- src/backend/optimizer/util/pathnode.c: + - Changed UpperUniquePath / create_upper_unique_path references to UniquePath / create_upper_path (added YB_TODO_PG19MERGE comments). + - Removed FLAT_COPY_PATH reference (added YB_TODO_PG19MERGE comment). + +- src/backend/optimizer/util/yb_merge_scan.c: + - Added an include. + - PG renamed PathKey.pk_strategy to pk_cmptype and uses get_opfamily_member_for_cmptype for CompareType lookups. Updated YB code to match. + +- src/backend/optimizer/util/ybplan.c: + - log_min_messages is now a per process type array (PG commit 38e0190ced714b33c43c9676d768cc6814fc662a). + - Added `false` as an argument for new paramter `bool deferrable_ok` to RelationGetPrimaryKeyIndex. + +- src/backend/postmaster/postmaster.c: + - Silenced some unused warnings. + - Changed `proc->isBackgroundWorker` to `proc->backendType == B_BG_WORKER`. + +- src/backend/replication/logical/launcher.c: + - Added args for YB parameters with a YB_TODO_PG19MERGE. + +- src/backend/replication/logical/slotsync.c: + - Same as above. + +- src/backend/replication/logical/yb_virtual_wal_client.c: + - PG commit 96b37849734673e7c82fb86c4f0a46a28f500ac8 removed `GetAllTablesPublicationRelations` (generalized to `GetAllPublicationRelations`); removed YB call site and added a YB_TODO_PG19MERGE. + - Updated log_min_messages as per PG commit 38e0190ced714b33c43c9676d768cc6814fc662a. + - Changed `%lu` to `UINT64_FORMAT`. + +- src/backend/replication/slot.c: + - Updated `ReplicationSlotValidateName` call to pass `false` for `allow_reserved_name`. + - Changed `active_pid` / `MyProcPid` to `active_proc` / `MyProcNumber`. + - PG added `synced_only` parameter to `ReplicationSlotCleanup`; added the parameter to YB's `ReplicationSlotCleanupForProc`. + +- src/postgres/src/backend/replication/slotfuncs.c: + - Same `active_proc` rename as above, added a YB_TODO_PG19MERGE to revisit YbcReplicationSlotDescriptor. + - PG commit 15f8203a5975d6b9b78e2c64e213ed964b50c044 replaced invalidated_at (LSN) with invalidated (enum cause); updated YB code to match. + +- src/backend/storage/lmgr/condition_variable.c: + - YbConditionVariableCancelSleepForProc, YbConditionVariableBroadcastForProc: updated to use proc->vxid.procNumber. + +- src/include/replication/slot.h: + - Added `synced_only` parameter to `ReplicationSlotCleanup`. + +- src/backend/replication/walsender.c: + - Updated `ReplicationSlotCreate` call with new args with a YB_TODO_PG19MERGE. + +- src/backend/statistics/extended_stats_funcs.c: + - Fixed CatalogTupleDelete call site. + +- src/backend/storage/buffer/bufmgr.c: + - Updated ReadBuffer_common and PageAddItemExtended calls to match new signature. + +- src/backend/storage/ipc/pmsignal.c: + - Added an include. + +- src/backend/storage/ipc/sinvaladt.c: + - PG moved procNumber into vxid (commit 024c521117579a6d356050ad3d78fdc95e44eefa). + +- src/backend/storage/lmgr/lmgr.c: + - Updated LockHeldByMe call with new argument `false` for PG parameter `orstronger`. Added a YB_TODO_PG19MERGE. + +- src/backend/storage/lmgr/lock.c: + - Changed backendId / InvalidBackendId to procNumber / INVALID_PROC_NUMBER. + +- src/backend/utils/activity/yb_terminated_queries.c: + - Removed tuplestore_donestoring as per PG commit 75680c3d805e2323cd437ac567f0677fdfc7b680. + +- src/backend/utils/adt/int8.c: + - Added an include. + +- src/backend/utils/adt/rangetypes.c: + - Added an include. + +- src/backend/utils/adt/ri_triggers.c: + - Added an include. + - Fixed up `MakeTupleTableSlot` and `ri_ReportViolation` with new arguments for PG parameters; Added a YB_TODO_PG19MERGE for `ri_ReportViolation`. + +- src/backend/utils/adt/selfuncs.c: + - Added an include. + +- src/backend/utils/cache/inval.c: + - CurrentCmdInvalidMsgs moved into nested `.ii`. + +- src/backend/utils/cache/plancache.c: + - Renamed OverrideSearchPathMatchesCurrent to SearchPathMatchesCurrentEnvironment as per PG commit d3a38318ac614f20a9e2e163bba083d15be54f06. + +- src/bin/pg_dump/pg_dump.c: + - Passed NULL for new PG parameter `tag` in `dumpACL` call. + +- src/bin/pg_upgrade/pg_upgrade.c: + - live_check parameter no longer exists; updated to use user_opts.live_check. + +- src/backend/utils/cache/yb_inheritscache.c: + - Changed `%ld` to `INT64_FORMAT`. + +- src/backend/utils/misc/yb_exceptions_for_func_pushdown.c: + - Renamed `F_RANDOM` to `F_RANDOM_`. + +- src/include/access/ybgin.h: + - Added const qualifiers to `ybginbindschema` parameters. + +- src/include/commands/explain_state.h: + - Added an include. + +- src/include/commands/yb_cmds.h: + - Added an include. + - Added const qualifiers to some parameters. + +- src/backend/utils/misc/yb_tcmalloc_utils.c: + - Removed tuplestore_donestoring as per PG commit 75680c3d805e2323cd437ac567f0677fdfc7b680. + +- src/backend/utils/mmgr/mcxt.c: + - AssertArg doesn't exist; changed to Assert. + +- src/backend/utils/misc/yb_index_check.c: + - Added new args for PG params `permInfos` and `unpruned_relids` in `ExecInitRangeTable` call. + +- src/include/catalog/pg_opclass.dat, src/include/catalog/pg_opfamily.dat, src/include/catalog/pg_proc.dat: + - OID collisions with PG. PG oids will likely change once PG19 branch is cut. Change YB oids to work-around the collision for now and add YB_TODO_PG19MERGE. + +- src/postgres/src/include/catalog/pg_yb_catalog_version.h, src/include/catalog/pg_yb_invalidation_messages.h, src/include/catalog/pg_yb_logical_client_version.h, src/postgres/src/include/catalog/pg_yb_profile.h, src/include/catalog/pg_yb_role_profile.h, src/include/catalog/pg_yb_tablegroup.h: + - Updated DECLARE_*_INDEX calls as per PG commit 226d0a6b989bec6cb0cc3bfc07df9fd417644782. + - OID collision with PG in pg_yb_role_profile.h. Same as above. + +- src/postgres/src/include/storage/sinvaladt.h: + - Added forward decl for PGPROC. + +- src/backend/executor/ybOptimizeModifyTable.c: + - Updated log_min_messages as per PG commit 38e0190ced714b33c43c9676d768cc6814fc662a. + +- src/backend/access/yb_access/yb_lsm.c: + - PG commit 92fe23d93aa3bbbc40fca669cabc4a4d7975e327 and others added parameters to amestimateparallelscan. Added a wrapper yb_estimate_parallel_size_am (over the existing yb_estimate_parallel_size) that matches the new signature. + - Added const qualifiers to parameters of ybcinbindschema. + +- src/backend/commands/tablecmds.c: + - Changed pg_database_aclcheck to generalized object_aclcheck with a DatabaseRelationId argument. + - Changed RelationSetNewRelfilenode to RelationSetNewRelfilenumber. + - Added NULL argument (for stmt) to reindex_relation call. + - dropconstraint_internal: + - #if 0-ed out/altered some YB code, that doesn't compile due to PG's refactoring (added YB_TODO_PG19MERGE). + - RemoveInheritance: added YB arg to build_attrmap_by_name call. + - createPartitionTable: fixed heap_create_with_catalog call to pass args for YB params (reltablegroup = InvalidOid, yb_use_initdb_acl = false), added YB_TODO_PG19MERGE to verify. + - YbATCopyStats: pass NULL/true for escontext param in stringToQualifiedNameList/check_rights in CreateStatistics. + - YbATCopyMiscMetadata: set new tuple's attstattarget to NULL (with a YB_TODO_PG19MERGE) because attstattarget no longer exists in FormData_pg_attribute. + - YbATCloneTableAndGetMappings: passed false for PG's missing_ok param in build_attrmap_by_name call. + - YbATCreateSimilarForeignKey: updated transformFkeyCheckAttrs, CreateConstraintEntry and createForeignKeyActionTriggers calls with new args, added YB_TODO_PG19MERGE for CreateConstraintEntry. + - YbATValidateChangeForeignKeyType, YbATCopyTableRowsUnchecked: changed `tupdesc->attrs[i]` to `TupleDescAttr(tupdesc, i)`. + - YbATCopyIndexes: updated DefineIndex call with new args (NULL/-1 for pstate/total_parts). + +- src/backend/executor/execGrouping.c: + - YbBuildTupleHashTableExt: Stubbed this function as it needs to be refactored (PG refactored TupleHashTableData, BuildTupleHashTable(Ext)). + +- src/backend/libpq/hba.c: + - Added an include. + - yb_tokenize_line: passed 0 for depth in next_field_expand call. + - linecxt no longer exists (see PG commit efc981627a723d91e86865fb363d793282e473d1). Removed the memory context switch. + - Renamed hbaPort to Port. + +- src/backend/utils/cache/catcache.c: + - PG commit 473182c9523afad10e9507145690d902a0bc7f04 replaced cc_lists with hashed cc_lbucket[]. Changed dlist_push_head, dlist_is_empty calls and added YB_TODO_PG19MERGE for further review. + - Added DatumGetPointer to some VARSIZE() calls. + - Fixed up yb_log_catcache_stats to use proc number (instead of backend id). + +- src/backend/utils/cache/relcache.c: + - Added an include. + - Fixed up code to use proc number (instead of backend id). + - Made RelfileNumber related renames. + - Made other trivial names. + +- src/backend/utils/init/postinit.c + - Added an include. + - Renamed Log_connections to log_connections. + - Changed pg_database_aclcheck to generalized object_aclcheck. + - default_locale is no longer extern; #if 0-ed off some YB code that uses it. + - InitPostgres/YbInitPostgres/InitPostgresImpl: PG commit 4800a5dfb4c46d22b5d05f16c615bea6ff24a2bb unified the older bool args into a unified bitmask. Updated the signatures, call sites and other relevant code for YbInitPostgres/InitPostgresImpl (including the declaration in miscadmin.h). + +- src/backend/utils/misc/pg_yb_utils.c: + - Fixed up includes. + - Stubbed YbGetSessionReplicationOriginId to return InvalidReplOriginId (added a YB_TODO_PG19MERGE). + - Passed 0 for `flags` in MakeTupleTableSlot call. + - Changed `AssertArg` to `Assert`. + - Updated log_min_messages usages -- it is now a per process type array (PG commit 38e0190ced714b33c43c9676d768cc6814fc662a). + - Removed tuplestore_donestoring usages. + - Passed `false` for `deferrable_ok` in RelationGetPrimaryKeyIndex call. + - GetMetricsAsJsonbDatum: pushJsonbValue now takes JsonbInState * and writes the final value into state.result. + - YBComputeNonCSortKey: #if 0-ed out a block (with a YB_TODO_PG19MERGE) that doesn't compile. + - YBRequiresCacheToCheckLocale: removed POSIX_COLLATION_OID usage for now with a YB_TODO_PG19MERGE. + - Changed session_auth_is_superuser to current_role_is_superuser as per PG commit 0fef8775382886bef023aee67cb744711ed7a32f. + - Updated build_attrmap_by_name call. + - Changed bms_first_member usage to bms_next_member. + +- src/backend/utils/misc/yb_ash.c: + - Added includes. + - yb_ash_ExecutorRun: removed execute_once. + - Used AmBackgroundWorkerProcess() as appropriate. + - Changed HandleMainLoopInterrupts to ProcessMainLoopInterrupts as per PG commit 635f580120b99f6df71d7c12749b22acde61c5ad. + +- src/backend/utils/misc/yb_jumblefuncs.c: + - Added an include. + - Made several changes to match PG's fields/naming. + +- src/backend/utils/misc/yb_qpm.c + - Added includes. + - Removed execute_once from yb_qpm_ExecutorRun. + - Changed destLen / uncompressBufferSize to uLongf as compress/uncompress take uLongf. + - QueryDesc.totaltime doesn't exist (PG added query_instr instead). #if 0-ed out some YB code with YB_TODO_PG19MERGE. + - Removed LWLockRegisterTranche (rolled into the new LWLockNewTrancheId). + - Dropped an arg for ShmemInitHash. + - Changed the types of some fields in YbPlanStatePtr as per PG changes. + - Changed `%ld` format to INT64_FORMAT. + +- src/backend/utils/mmgr/aset.c: + - PG dropped the explicit keeper pointer; use KeeperBlock(). + +- src/backend/utils/mmgr/slab.c: + - SlabContextCreate: PG dropped the explicit headerSize local; used the Slab_CONTEXT_HDRSZ(chunksPerBlock) that is also used for the malloc above. + - SlabDelete: SlabContext no longer stores headerSize; recomputed using Slab_CONTEXT_HDRSZ. + +- src/backend/utils/sort/tuplesort.c: + - Moved yb_sort_type to TuplesortPublic (in tuplesort.h) as PG made Tuplesortstate opaque to tuplesortvariants.c. Updated yb_sort_type references in this file. + +- src/backend/utils/sort/tuplesortvariants.c: + - Related to above. + +- src/backend/ybgate/ybgate_api.c: + - Added explicit (Datum *) casts. + +- src/bin/initdb/initdb.c: + - Changed pg_attribute_noreturn to pg_noreturn as per PG commit 76f4b92bac87fa54bd6dd8bd53e59f93127ec2ef. + +- src/bin/psql/tab-complete.in.c: + - Moved mid-string "YB" comment. + +- src/common/exec.c: + - exec_pipe_read_line: + - PG commit 5c7038d70bb9c4d28a80b0a2051f73fafab5af3f reworked pipe_read_line() to return a freshly allocated buffer instead of taking a caller-supplied one. + - Updated YB code to match (here and in port.h, username.c). + +- src/include/access/yb_scan.h, src/include/nodes/execnodes.h + - Added include(s). + +- src/include/access/amapi.h: + - Added const qualifiers to some parameters. + +- src/backend/replication/logical/reorderbuffer.c: + - #if 0-ed yb_is_omitted usages. + +- src/backend/replication/logical/yb_decode.c: + - #if 0-ed a few functions that need to be reworked (due to yb_is_omitted usages). + - YBDecodeCommit: updated to use UINT64_FORMAT + - Updated log_min_messages as per PG commit 38e0190ced714b33c43c9676d768cc6814fc662a. + - Used TupleDescAttr. + +- src/include/nodes/parsenodes.h: + - Added struct name to `typedef enum` so that the regex in gen_node_support.pl can pick it up (see PG commit 964d01ae90c314eb31132c2e7712d5d9fc237331). + - Changed YB field to use PG's typedef `uint64` so that gen_node_support.pl can handle it properly. + +- src/include/nodes/pathnodes.h: + - Same as above. + +- src/include/nodes/plannodes.h: + - Some changes are the same as above. + - Made several other changes to support gen_node_support.pl autogeneration: + - Added `pg_node_attr(array_size(numCols))` to YbSortInfo.sortColIdx, sortOperators, collations, nullsFirst, matching the pattern used by PG's Sort struct at plannodes. + - Added `no_query_jumble` to YbUpdateAffectedEntities. + - Added `read_as(NULL)` to ybScannedObjectName and ybSkewTableName. + +- src/include/pg_yb_utils.h: + - Changed PG_FUNCNAME_MACRO to __func__ (see PG commit 320f92b744b44f961e5d56f5f21de003e8027a7f). + +- src/include/yb_qpm.h, src/include/yb_query_diagnostics.h: + - Fixed includes. + +- src/include/ybctid.h: + - Added DatumGetPointer to some VARSIZE() calls. + +- src/postgres/src/pl/plpgsql/src/pl_gram.y: + - ybc_not_support: PG commit 7b27f5fd36cb3270e8ac25aefd73b552663d1392 refactored how the scanner state is accessed. We probably need a yyscanner parameter in order to use parser_errposition. Removed parser_errposition for now, added a YB_TODO_PG19MERGE. + +- src/postgres/yb-extensions/Makefile: + - Dropped yb_xcluster_ddl_replication for now (added a YB_TODO_PG19MERGE). + +- src/postgres/yb-extensions/yb_ycql_utils/yb_ycql_utils.c: + - Removed tuplestore_donestoring. + +- src/yb/yql/pggate/CMakeLists.txt, src/yb/yql/pggate/util/CMakeLists.txt: + - PG commit 6ab2e8385d55e0b73bb8bbc41d9c286f5f7f357f moved headers into `src/include/catalog/` (from `src/backend/catalog/`). Changed `include_directories(...src/backend")` to `include_directories(...src/include")`. + +- src/yb/yql/pgwrapper/libpq_utils.cc: + - Added handling for PGRES_TUPLES_CHUNK. + +- src/backend/commands/vacuum.c: + - VacuumParams is now passed in as const, so we can't mutate params->options in place. Created a writeable copy and then reassigned params instead. + +- src/postgres/src/backend/utils/init/miscinit.c: + - Removed AuthenticatedUserIsSuperuser assignment as it no longer exists (see PG commit a0363ab7aafda7d16ae59e72d86866c02ad3d657). + +- src/backend/optimizer/plan/createplan.c + - Added an include. + - Renamed rctype / ROWCOMPARE_EQ to cmptype / COMPARE_EQ. + - Updated get_singleton_append_subpath call to take unused_child_relids arg. + - Replaced make_result with make_one_row_result, added a YB_TODO_PG19MERGE. + - Fetched updatedCols through RTEPermissionInfo as per PG commit a61b1f74823c9c4f79c95226a461f1e7a367764b. + +- src/backend/optimizer/plan/initsplan.c: + - Updated make_restrictinfo calls as per new signature. + - PG dropped EquivalenceMember.em_nullable_relids -- replaced with em_relids for now and added a YB_TODO_PG19MERGE. + +- src/backend/utils/adt/pgstatfuncs.c: + - PG commits 3d51cb5197ea7eabc65f5e0184aae60b8f7f9528 and 024c521117579a6d356050ad3d78fdc95e44eefa reworked/renamed pgstate beentry retrieval functions. Temporarily patched up YB call sites for now; however, these need to be revisited (should the functions expect a proc number as an argument or an index? Also there seems to be no equivalent of the old pgstat_fetch_stat_local_beentry). + - Fixed up a UUIDPGetDatum call site with a const pg_uuid_t * cast. + +- src/backend/utils/misc/yb_query_diagnostics.c: + - Added includes. + - Made several changes to match PG signatures and renames. + - Removed tuplestore_donestoring call. + - YbQueryDiagnostics_ExecutorEnd, YbQueryDiagnosticsAccumulatePgss: #if 0-ed QueryDesc.totaltime, BufferUsage.blk_read_time, BufferUsage.blk_write_time usages (added YB_TODO_PG19MERGE). + - Changed format- strings. + - HandleMainLoopInterrupts renamed to ProcessMainLoopInterrupts. + +- src/postgres/src/backend/utils/cache/syscache.c: + - Added includes. + - Updated all structs and YbCheckCatalogCacheIds with the new pg_extension and pg_propgraph caches. Also, reordered them alphabetically since PG now autogenerates the SysCacheIdentifier enum alphabetically. + - Fixed up some format strings. + +- src/include/utils/syscache.h + - Updated YbCatalogCacheTable with the new pg_extension and pg_propgraph caches (alphabetical order). + +- src/postgres/yb-extensions/yb_pg_metrics/yb_pg_metrics.c: + - Changed NUM_AUXPROCTYPES to NUM_AUXILIARY_PROCS as per PG commit ab355e3a88de745607f6dd4c21f0119b5c68f2ad. + - Extended YbStatementType to include new PG caches. + - Updated the signature for ybpgm_ExecutorRun. + - Added STATE_STARTING handling in pullRpczEntries. + - ybpgm_ExecutorStart, ybpgm_ExecutorEnd: #if 0-ed out QueryDesc.totaltime usages (added YB_TODO_PG19MERGE). + +- src/include/miscadmin.h, src/include/storage/spin.h, src/backend/storage/lmgr/proc.c: + - YB added three coupled things that together form an include tangle: + - `#include "storage/proc.h"` inside miscadmin.h (under `#ifndef FRONTEND`), commented "for MyProc", so anything that included miscadmin.h got MyProc transitively. + - In spin.h, YB ported its old macro-based `MyProc->ybSpinLocksAcquired++ / --` tracking into PG SpinLockAcquire / SpinLockRelease functions with `#include "miscadmin.h"` and `#include "storage/proc.h"` at the top of spin.h. + - In miscadmin.h itself, YB modified the START_CRIT_SECTION / END_CRIT_SECTION macros to set `MyProc->ybEnteredCriticalSection = true/false`. The macros are expanded at thousands of call sites across the backend; each call site only worked because of (1) above. + - PG commit 084e42bc7109673e46527b0a0f284edf539c3285 added a `BackendType backendType` field to PGPROC. `BackendType` is defined in miscadmin.h. This causes a cycle where `BackendType` is undeclared. + - Fix: + - Removed YB's `#include "storage/proc.h"` + - Moved the spinlock-counter bumping out of spin.h's inline functions into two new non-inline helpers `YbSpinLockTrackAcquire()` / `YbSpinLockTrackRelease()` in src/backend/storage/lmgr/proc.c (where proc.h and miscadmin.h are both fully visible). spin.h now contains only `extern void YbSpinLockTrack{Acquire,Release}(void);` plus the calls from inside the inline SpinLockAcquire / SpinLockRelease bodies, and no longer pulls in miscadmin.h or proc.h. + - Moved the YB body of START_CRIT_SECTION / END_CRIT_SECTION (the MyProc->ybEnteredCriticalSection assignment) out of the macros into two new non-inline helpers `YbEnterCriticalSection()` / `YbExitCriticalSection()` in proc.c. The macros in miscadmin.h call those helpers instead of accessing MyProc directly. + - Added `YB_TODO_PG19MERGE`s. + +- src/backend/nodes/nodeFuncs.c: + - Added explicit `(Node *)` cast to argument for `walker`. + +- src/backend/nodes/gen_node_support.pl: + - YbTIDBitmap is a special node (NodeTag carried in struct, but no copy/equal/read/out support), analogous to PG's own `TIDBitmap`. Listed YbTIDBitmap in `@extra_tags` array (like PG handles `TIDBitmap`). + - per-type branches added for YbPushdownExprs / YbPlanInfo / YbPathInfo / YbIndexPathInfo: + - YB has four structs that are embedded by value inside node-tagged Plan/Path nodes. + - Followed PG's QualCost precedent where PG hardcodes `WRITE_FLOAT_FIELD($f.startup)` etc. to teach the out/read dispatcher about QualCost's internal layout. Added analogous `elsif ($t eq 'YbPushdownExprs') { ... }` etc. branches. + +- src/backend/utils/misc/help_config.c: + - printMixedStruct PGC_OID: PG's config_generic.union (see commit a13833c35f9e07fe978bf6fad984d6f5f25f59cd) doesn't contain yb_config_oid. PGC_OID branch references `structToPrint->oid.{reset_val,min,max}` which would only work if YB folds its Oid GUC into the union. + - Stubbed the PGC_OID print branch to emit just `OID\t0\t0\tUINT_MAX` until yb_config_oid is migrated into config_generic's union. + +- src/include/utils/guc_tables.h: + - Moved yb_config_oid below config_generic and added a YB_TODO_PG19MERGE -- yb_config_oid hasn't been ported to the new PG layout (it still embeds `struct config_generic gen` as its first field, rather than living as a union member inside config_generic). + +- src/include/utils/guc_hooks.h: + - Added extern decl for `yb_show_maxconnections` as its referenced in `guc_parameters.dat`. + +- src/backend/utils/misc/guc.c: + - Added includes. + - Added yb_conn_mgr_modifying_defaults extern decl (moved to guc_tables.c). + - Marked many declarations as pg_attribute_unused (due to YB GUCs being #if 0-ed out). + - string_field_used: updated ysql_conn_mgr_saved_default field access as per new GUC structs layout. + - Made changes to yb_reset_conn_mgr_default: PG moved reset_val/reset_extra/etc into the config_generic union ._bool/_int/_real/_string/_enum, and set_extra_field now takes config_generic*. Skipped PGC_OID for now. report_needed was dropped. + - AtEOXact_GUC: Fixed flags and name accesses to use gconf. + - parse_and_validate_value: name accessed via record->name. + - parse_and_validate_value call: no longer takes name arg. + - DefineCustomOidVariable: PG dropped the sizeof arg; PG always allocates sizeof(struct config_generic). + - check_reserved_prefixes: Replaced num_guc_variables / guc_variables with get_guc_variables(&num) as per PG changes. + - check_GUC_init switch (USE_ASSERT_CHECKING-only): PG added a switch on gconf->vartype covering PGC_BOOL/INT/REAL/STRING/ENUM. Added a case for YB's PGC_OID using `(struct yb_config_oid *) gconf` and InvalidOid as the sentinel (parallel to PGC_INT's zero check). + +- src/postgres/src/backend/utils/resowner/resowner.c: + - Added an include. + +- src/postgres/src/backend/utils/misc/guc_tables.c + - Added includes. + - Marked many declarations as pg_attribute_unused (due to YB GUCs being #if 0-ed out). + - Made yb_conn_mgr_modifying_defaults non-static as its used in guc.c